UX: Introduces a splash screen behind a hidden site setting (#17094)

This PR introduces a new hidden site setting that allows admins to display a splash screen while site assets load.

The splash screen can be enabled via the `splash_screen` hidden site setting.

This is what the splash screen currently looks like

5ceb72f085.mp4

Once site assets load, the splash screen is automatically removed.

To control the loading text that shows in the splash screen, you can change the preloader_text translation string in admin > customize > text
This commit is contained in:
Joe 2022-06-22 04:35:46 +08:00 committed by GitHub
parent 624c684d51
commit e82a2ce9ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 209 additions and 2 deletions

View File

@ -52,6 +52,9 @@ const Discourse = Application.extend({
start() { start() {
document.querySelector("noscript")?.remove(); document.querySelector("noscript")?.remove();
// The app booted. Remove the splash screen
document.querySelector("#d-splash")?.remove();
if (Error.stackTraceLimit) { if (Error.stackTraceLimit) {
// We need Errors to have full stack traces for `lib/source-identifier` // We need Errors to have full stack traces for `lib/source-identifier`
Error.stackTraceLimit = Infinity; Error.stackTraceLimit = Infinity;

View File

@ -0,0 +1,59 @@
html {
background: var(--secondary);
// needed because this sheet loads early and we want no scroll bars until
// the splash is removed.
overflow: hidden !important;
}
#d-splash {
display: grid;
place-items: center;
position: relative;
backface-visibility: hidden;
.preloader-image {
max-width: 100%;
height: 100vh;
object-fit: none;
}
.preloader-text {
padding-top: 5em;
position: absolute;
display: grid;
grid-auto-flow: column;
place-items: center;
&:after {
animation: loading-text 3s infinite;
content: "";
position: absolute;
top: 5em;
margin: 0 0.1em;
left: 100%;
// TODO: this needs R2 RTL magic
.rtl & {
left: 0;
right: 100%;
}
}
}
}
@keyframes loading-text {
0% {
content: "";
}
25% {
content: ".";
}
50% {
content: "..";
}
75% {
content: "...";
}
}

View File

@ -429,6 +429,11 @@ module ApplicationHelper
", app-argument=discourse://new?siteUrl=#{Discourse.base_url}" : "" ", app-argument=discourse://new?siteUrl=#{Discourse.base_url}" : ""
end end
def include_splash_screen?
# A bit basic for now but will be expanded later
SiteSetting.splash_screen
end
def allow_plugins? def allow_plugins?
!request.env[ApplicationController::NO_PLUGINS] !request.env[ApplicationController::NO_PLUGINS]
end end

View File

@ -0,0 +1,17 @@
<%- unless customization_disabled? %>
<section id="d-splash">
<%= discourse_stylesheet_link_tag 'd_splash', theme_id: nil %>
<img
class="preloader-image"
src="/images/preloader.svg"
alt="<%=SiteSetting.title%>"
>
<div class="preloader-text">
<span>
<%= I18n.t("js.preloader_text") %>
</span>
</div>
</section>
<%- end %>

View File

@ -9,6 +9,10 @@
<%= render partial: "layouts/head" %> <%= render partial: "layouts/head" %>
<%= discourse_csrf_tags %> <%= discourse_csrf_tags %>
<%- if include_splash_screen? %>
<link rel="preload" as="image" href="/images/preloader.svg">
<%- end %>
<%- if SiteSetting.enable_escaped_fragments? %> <%- if SiteSetting.enable_escaped_fragments? %>
<meta name="fragment" content="!"> <meta name="fragment" content="!">
<%- end %> <%- end %>
@ -70,6 +74,10 @@
</head> </head>
<body class="<%= body_classes %>"> <body class="<%= body_classes %>">
<%- if include_splash_screen? %>
<%= render partial: "common/discourse_splash" %>
<%- end %>
<discourse-assets> <discourse-assets>
<discourse-assets-stylesheets> <discourse-assets-stylesheets>
<%= render partial: "common/discourse_stylesheet" %> <%= render partial: "common/discourse_stylesheet" %>

View File

@ -3691,6 +3691,8 @@ en:
create_post: "Reply / See" create_post: "Reply / See"
readonly: "See" readonly: "See"
preloader_text: "Loading"
lightbox: lightbox:
download: "download" download: "download"
open: "original image" open: "original image"

View File

@ -2369,6 +2369,8 @@ en:
use_name_for_username_suggestions: "Use a user's full name when suggesting usernames." use_name_for_username_suggestions: "Use a user's full name when suggesting usernames."
suggest_weekends_in_date_pickers: "Include weekends (Saturday and Sunday) in date picker suggestions (disable this if you use Discourse only on weekdays, Monday through Friday)." suggest_weekends_in_date_pickers: "Include weekends (Saturday and Sunday) in date picker suggestions (disable this if you use Discourse only on weekdays, Monday through Friday)."
splash_screen: "Displays a temporary loading screen while site assets load"
errors: errors:
invalid_css_color: "Invalid color. Enter a color name or hex value." invalid_css_color: "Invalid color. Enter a color name or hex value."
invalid_email: "Invalid email address." invalid_email: "Invalid email address."
@ -2422,7 +2424,7 @@ en:
google_oauth2_hd_groups: "You must first set 'google oauth2 hd' before enabling this setting." google_oauth2_hd_groups: "You must first set 'google oauth2 hd' before enabling this setting."
search_tokenize_chinese_enabled: "You must disable 'search_tokenize_chinese' before enabling this setting." search_tokenize_chinese_enabled: "You must disable 'search_tokenize_chinese' before enabling this setting."
search_tokenize_japanese_enabled: "You must disable 'search_tokenize_japanese' before enabling this setting." search_tokenize_japanese_enabled: "You must disable 'search_tokenize_japanese' before enabling this setting."
discourse_connect_cannot_be_enabled_if_second_factor_enforced: "You cannot enable DiscourseConnect if 2FA is enforced." discourse_connect_cannot_be_enabled_if_second_factor_enforced: "You cannot enable DiscourseConnect if 2FA is enforced."
placeholder: placeholder:
discourse_connect_provider_secrets: discourse_connect_provider_secrets:

View File

@ -2424,6 +2424,10 @@ uncategorized:
default: true default: true
hidden: true hidden: true
splash_screen:
default: false
hidden: true
suggest_weekends_in_date_pickers: suggest_weekends_in_date_pickers:
client: true client: true
default: true default: true

View File

@ -0,0 +1,85 @@
<svg
version="1.1"
height="2000"
width="2000"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<style>
:root {
/* these need to be injected dynamicly to match theme colors */
--primary: #222222;
--secondary: #ffffff;
--tertiary: #f15c21;
--highlight: #f0ea88;
--quaternary: #65ccff;
--success: #009900;
}
/* these styles need to live here because the SVG has a different scope */
.dots {
animation-name: loader;
animation-timing-function: ease-in-out;
animation-duration: 3s;
animation-iteration-count: infinite;
stroke: #fff;
stroke-width: 0.5px;
}
.dots:first-child {
fill: var(--tertiary);
animation-delay: 1.4s;
}
.dots:nth-child(2) {
fill: var(--tertiary);
animation-delay: 1.3s;
opacity: 0.6;
}
.dots:nth-child(3) {
fill: var(--highlight);
animation-delay: 1.2s;
}
.dots:nth-child(4) {
fill: var(--quaternary);
animation-delay: 1.1s;
}
.dots:nth-child(5) {
fill: var(--quaternary);
animation-delay: 1s;
}
.container {
transform: translateX(-125px);
}
@keyframes loader {
15% {
transform: translateX(0);
}
45% {
transform: translatex(calc(250px));
}
65% {
transform: translatex(calc(250px));
}
95% {
transform: translateX(0);
}
}
</style>
<g class="container">
<circle class="dots" cy="50%" r="10" cx="50vw"></circle>
<circle class="dots" cy="50%" r="10" cx="50vw"></circle>
<circle class="dots" cy="50%" r="10" cx="50vw"></circle>
<circle class="dots" cy="50%" r="10" cx="50vw"></circle>
<circle class="dots" cy="50%" r="10" cx="50vw"></circle>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -11,7 +11,7 @@ describe Stylesheet::Compiler do
it "can compile '#{path}' css" do it "can compile '#{path}' css" do
css, _map = Stylesheet::Compiler.compile_asset(path) css, _map = Stylesheet::Compiler.compile_asset(path)
expect(css.length).to be > 1000 expect(css.length).to be > 500
end end
end end
end end

View File

@ -529,6 +529,28 @@ RSpec.describe ApplicationController do
end end
end end
describe "splash_screen" do
let(:admin) { Fabricate(:admin) }
before do
admin
end
it 'adds a preloader splash screen when enabled' do
get '/'
expect(response.status).to eq(200)
expect(response.body).not_to include("d-splash")
SiteSetting.splash_screen = true
get '/'
expect(response.status).to eq(200)
expect(response.body).to include("d-splash")
end
end
describe 'Delegated auth' do describe 'Delegated auth' do
let :public_key do let :public_key do
<<~TXT <<~TXT