[MM-58726] Add a loading screen to initial load of the web app (#27740)

Этот коммит содержится в:
M-ZubairAhmed
2024-08-26 12:41:59 +00:00
коммит произвёл GitHub
родитель eafe056bea
Коммит 5d7aa17a27
12 изменённых файлов: 849 добавлений и 64 удалений

Просмотреть файл

@@ -0,0 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {InitialLoadingScreenClass} from './initial_loading_screen_class';
const InitialLoadingScreen = new InitialLoadingScreenClass();
export default InitialLoadingScreen;

Просмотреть файл

@@ -0,0 +1,359 @@
body {
margin: 0;
}
.LoadingScreen.disabled {
opacity: 0;
}
.LoadingScreen {
--background-color: #f4f4f6;
--background-color-highlight: #ffffff;
--stipple-color: #1e325c;
--stipple-opacity: 0.08;
position: absolute;
z-index: 100;
top: 0px;
bottom: 0px;
display: flex;
overflow: hidden;
width: 100vw;
height: 100vh;
align-items: center;
justify-content: center;
opacity: 1;
transition: opacity 150ms 0ms ease-out, visibility 150ms 0ms step-start;
vertical-align: middle;
visibility: visible;
}
.LoadingScreen--darkMode {
--background-color-highlight: #28427b;
--background-color: #1e325c;
--stipple-color: #14213e;
--stipple-opacity: 0.8;
}
.LoadingScreen__background,
.LoadingScreen__background > svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.LoadingScreen__background {
background: var(--background-color);
}
.LoadingScreen .Pill__stipple {
fill: var(--stipple-color);
fill-opacity: var(--stipple-opacity);
}
.LoadingScreen .Pill__gradient {
stop-color: var(--background-color);
}
.LoadingScreen .Pill__gradientHighlight {
stop-color: var(--background-color-highlight);
}
.LoadingScreen--loaded {
opacity: 0;
transition: opacity 150ms 0ms ease-in, visibility 150ms 0ms step-end;
visibility: hidden;
}
.LoadingAnimation {
--fade-duration: 150ms;
--colour: #1E325C;
--animation-initial-delay: 750ms;
--animation-start-duration: 750ms;
--animation-end-duration: 600ms;
--animation-spinner-speed: 500ms;
--animation-spinner-mask-stroke-length: 169.6;
--ease-in-cubic: cubic-bezier(0.32, 0, 0.67, 0);
--ease-in: var(--ease-in-cubic);
--ease-in-out-cubic: cubic-bezier(0.65, 0, 0.35, 1);
--ease-in-out: var(--ease-in-out-cubic);
--ease-in-out-compass-shrink: cubic-bezier(0.1, 0.25, 0.3, 1);
opacity: 1;
transform: scale3d(1, 1, 1);
}
.LoadingAnimation--darkMode {
--colour: white;
}
.LoadingAnimation g,
.LoadingAnimation rect,
.LoadingAnimation path,
.LoadingAnimation circle {
transform-origin: center center;
}
.LoadingAnimation svg {
color: var(--colour);
}
.LoadingAnimation .LoadingAnimation__spinner-gradient-color {
stop-color: var(--colour);
}
.LoadingAnimation .LoadingAnimation__spinner-container {
opacity: 0;
transform: scale3D(2.08, 2.08, 1) rotate3d(0, 0, 1, -10deg);
}
.LoadingAnimation .LoadingAnimation__spinner-mask-container {
transform: rotate3d(0, 0, 1, -86deg);
}
.LoadingAnimation .LoadingAnimation__spinner-mask {
stroke-dasharray: var(--animation-spinner-mask-stroke-length);
stroke-dashoffset: var(--animation-spinner-mask-stroke-length);
transform: scale3d(1.03, 1.03, 1);
}
.LoadingAnimation .LoadingAnimation__compass {
opacity: 1;
transform: scale3d(1, 1, 1);
}
.LoadingAnimation .LoadingAnimation__compass-needle-container {
transform: scale3d(1, 1, 1);
}
.LoadingAnimation .LoadingAnimation__compass-needle,
.LoadingAnimation .LoadingAnimation__compass-needle-front-mask,
.LoadingAnimation .LoadingAnimation__compass-needle-behind-mask {
transform: rotate3d(0, 0, 1, 0deg);
transform-origin: 54px 46px;
}
.LoadingAnimation .LoadingAnimation__compass-base-mask-container {
transform: rotate3d(0, 0, 1, -86deg);
}
.LoadingAnimation .LoadingAnimation__compass-base-mask {
stroke-dasharray: var(--animation-spinner-mask-stroke-length);
stroke-dashoffset: var(--animation-spinner-mask-stroke-length);
}
.LoadingAnimation--loading {
--fade-in-duration: 150ms;
--fade-in-delay: 0ms;
transform: scale3d(1, 1, 1);
}
.LoadingAnimation.LoadingAnimation--loading .LoadingAnimation__spinner-container {
--shrink-duration: calc(var(--animation-end-duration) * 0.5);
--shrink-delay: calc( ( var(--animation-start-duration) + var(--animation-end-duration) ) * 0.91 + var(--animation-initial-delay));
--fade-in-duration: calc(var(--animation-end-duration) * 0.25);
--fade-in-delay: calc( var(--animation-start-duration) + var(--animation-end-duration) * 0.24 + var(--animation-initial-delay));
animation: LoadingAnimation__spinner-shrink var(--shrink-duration) var(--shrink-delay) var(--ease-in-out-compass-shrink) forwards, LoadingAnimation__fade-in var(--fade-in-duration) var(--fade-in-delay) var(--ease-in) forwards;
}
.LoadingAnimation.LoadingAnimation--loading .LoadingAnimation__spinner-mask {
--reveal-duration: var(--animation-end-duration);
--reveal-delay: calc(var(--animation-start-duration) + var(--animation-initial-delay));
animation: LoadingAnimation__spinner-reveal var(--reveal-duration) var(--reveal-delay) var(--ease-in) forwards;
}
.LoadingAnimation.LoadingAnimation--loading .LoadingAnimation__compass {
--shrink-duration: calc(var(--animation-end-duration) * 0.5);
--shrink-delay: calc( ( var(--animation-start-duration) + var(--animation-end-duration) ) * 0.91 + var(--animation-initial-delay));
animation: LoadingAnimation__compass-shrink var(--shrink-duration) var(--shrink-delay) var(--ease-in-out-compass-shrink) forwards;
}
.LoadingAnimation.LoadingAnimation--loading .LoadingAnimation__compass-needle-container {
--shrink-duration: calc(var(--animation-end-duration) * 0.25);
--shrink-delay: calc( var(--animation-start-duration) + var(--animation-end-duration) - var(--animation-end-duration) * 0.25 + var(--animation-initial-delay));
animation: LoadingAnimation__needle-shrink var(--shrink-duration) var(--shrink-delay) var(--ease-in) forwards;
}
.LoadingAnimation.LoadingAnimation--loading .LoadingAnimation__compass-needle {
--spin-left-duration: var(--animation-start-duration);
--spin-left-delay: var(--animation-initial-delay);
--spin-right-duration: var(--animation-end-duration);
--spin-right-delay: calc(var(--animation-start-duration) + var(--animation-initial-delay));
--fade-out-duration: calc(var(--animation-end-duration) * 0.25);
--fade-out-delay: calc( var(--animation-start-duration) + var(--animation-end-duration) - var(--animation-end-duration) * 0.25 + var(--animation-initial-delay));
animation:
LoadingAnimation__needle-spin-left var(--spin-left-duration) var(--spin-left-delay) var(--ease-in-out) forwards,
LoadingAnimation__needle-spin-right var(--spin-right-duration) var(--spin-right-delay) var(--ease-in) forwards,
LoadingAnimation__fade-out var(--fade-out-duration) var(--fade-out-delay) var(--ease-in) forwards;
}
.LoadingAnimation.LoadingAnimation--loading .LoadingAnimation__compass-needle-behind-mask {
--spin-left-duration: var(--animation-start-duration);
--spin-left-delay: var(--animation-initial-delay);
--spin-right-duration: calc(var(--animation-end-duration) * 0.3666);
--spin-right-delay: calc(var(--animation-start-duration) + var(--animation-initial-delay));
animation: LoadingAnimation__needle-mask-spin-left var(--spin-left-duration) var(--spin-left-delay) var(--ease-in-out) forwards, LoadingAnimation__needle-mask-spin-right var(--spin-right-duration) var(--spin-right-delay) var(--ease-in) forwards;
}
.LoadingAnimation.LoadingAnimation--loading .LoadingAnimation__compass-needle-front-mask {
--spin-left-duration: var(--animation-start-duration);
--spin-left-delay: var(--animation-initial-delay);
--spin-right-duration: var(--animation-end-duration);
--spin-right-delay: calc(var(--animation-start-duration) + var(--animation-initial-delay));
animation: LoadingAnimation__needle-spin-left var(--spin-left-duration) var(--spin-left-delay) var(--ease-in-out) forwards, LoadingAnimation__needle-spin-right var(--spin-right-duration) var(--spin-right-delay) var(--ease-in) forwards;
}
.LoadingAnimation.LoadingAnimation--loading .LoadingAnimation__compass-base-mask {
--conceal-duration: var(--animation-end-duration);
--conceal-delay: calc(var(--animation-start-duration) + var(--animation-initial-delay));
animation: LoadingAnimation__compass-base-conceal var(--conceal-duration) var(--conceal-delay) var(--ease-in) forwards;
}
.LoadingAnimation.LoadingAnimation--spinning .LoadingAnimation__spinner {
--spin-duration: var(--animation-spinner-speed);
--spin-delay: calc( ( var(--animation-start-duration) + var(--animation-end-duration) ) * 0.95 + var(--animation-initial-delay));
animation: LoadingAnimation__spinner-spin var(--spin-duration) var(--spin-delay) linear infinite;
}
.LoadingAnimation--loaded {
--duration: 150ms;
--delay: 0ms;
animation: LoadingAnimation__fade-out var(--duration) var(--delay) var(--ease-in) forwards, LoadingAnimation__shrink var(--duration) var(--delay) var(--ease-in) forwards;
}
.LoadingAnimation--loaded .LoadingAnimation__spinner-container {
opacity: 1;
transform: scale3D(1, 1, 1) rotate3d(0, 0, 1, -10deg);
}
.LoadingAnimation--loaded .LoadingAnimation__spinner-mask {
stroke-dashoffset: 0;
}
.LoadingAnimation--loaded .LoadingAnimation__compass {
transform: scale3D(0.4166666667, 0.4166666667, 1);
}
.LoadingAnimation--loaded .LoadingAnimation__compass-needle-container {
transform: scale3d(0.35, 0.35, 1);
}
.LoadingAnimation--loaded .LoadingAnimation__compass-needle {
opacity: 0;
transform: rotate3d(0, 0, 1, 405deg);
}
.LoadingAnimation--loaded .LoadingAnimation__compass-needle-behind-mask {
transform: rotate3d(0, 0, 1, 0deg);
}
.LoadingAnimation--loaded .LoadingAnimation__compass-needle-front-mask {
transform: rotate3d(0, 0, 1, 405deg);
}
.LoadingAnimation--loaded .LoadingAnimation__compass-base-mask {
stroke-dashoffset: 0;
}
@keyframes LoadingAnimation__fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes LoadingAnimation__fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes LoadingAnimation__shrink {
0% {
transform: scale3d(1, 1, 1);
}
100% {
transform: scale3d(0.35, 0.35, 1);
}
}
@keyframes LoadingAnimation__spinner-shrink {
0% {
transform: scale3D(2.08, 2.08, 1) rotate3d(0, 0, 1, -10deg);
}
100% {
transform: scale3D(1, 1, 1) rotate3d(0, 0, 1, -10deg);
}
}
@keyframes LoadingAnimation__spinner-spin {
from {
transform: rotate3d(0, 0, 1, 0deg);
}
to {
transform: rotate3d(0, 0, 1, 359deg);
}
}
@keyframes LoadingAnimation__spinner-reveal {
0%, 5% {
stroke-dashoffset: var(--animation-spinner-mask-stroke-length);
}
95%, 100% {
stroke-dashoffset: 0;
}
}
@keyframes LoadingAnimation__needle-spin-left {
0% {
transform: rotate3d(0, 0, 1, 0deg);
}
100% {
transform: rotate3d(0, 0, 1, -20deg);
}
}
@keyframes LoadingAnimation__needle-spin-right {
0% {
transform: rotate3d(0, 0, 1, -20deg);
}
100% {
transform: rotate3d(0, 0, 1, 405deg);
}
}
@keyframes LoadingAnimation__needle-mask-spin-left {
0% {
transform: rotate3d(0, 0, 1, 0deg);
}
100% {
transform: rotate3d(0, 0, 1, -20deg);
}
}
@keyframes LoadingAnimation__needle-mask-spin-right {
0% {
transform: rotate3d(0, 0, 1, -20deg);
}
100% {
transform: rotate3d(0, 0, 1, 0deg);
}
}
@keyframes LoadingAnimation__needle-shrink {
0% {
transform: scale3d(1, 1, 1);
}
100% {
transform: scale3d(0.35, 0.35, 1);
}
}
@keyframes LoadingAnimation__compass-shrink {
0% {
transform: scale3D(1, 1, 1);
}
100% {
transform: scale3D(0.4166666667, 0.4166666667, 1);
}
}
@keyframes LoadingAnimation__compass-base-conceal {
0%, 5% {
stroke-dashoffset: var(--animation-spinner-mask-stroke-length);
}
95%, 100% {
stroke-dashoffset: 0;
}
}

Просмотреть файл

@@ -0,0 +1,122 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {isDesktopApp} from 'utils/user_agent';
const ANIMATION_CLASS_FOR_MATTERMOST_LOGO_HIDE = 'LoadingAnimation__compass-shrink';
const ANIMATION_CLASS_FOR_COMPLETE_LOADER_HIDE = 'LoadingAnimation__shrink';
const LOADING_CLASS_FOR_SCREEN = 'LoadingScreen LoadingScreen--darkMode';
const LOADING_COMPLETE_CLASS_FOR_SCREEN = 'LoadingScreen LoadingScreen--darkMode LoadingScreen--loaded';
const STATIC_CLASS_FOR_ANIMATION = 'LoadingAnimation LoadingAnimation--darkMode';
const LOADING_CLASS_FOR_ANIMATION = STATIC_CLASS_FOR_ANIMATION + ' LoadingAnimation--spinning LoadingAnimation--loading';
const LOADING_COMPLETE_CLASS_FOR_ANIMATION = STATIC_CLASS_FOR_ANIMATION + ' LoadingAnimation--spinning LoadingAnimation--loaded';
const DESTROY_DELAY_AFTER_ANIMATION_END = 1000;
export class InitialLoadingScreenClass {
private isLoading: boolean | null = true;
private loadingScreenElement: HTMLElement | null;
private loadingAnimationElement: HTMLElement | null;
private initialLoadingScreenCSS: HTMLLinkElement | null;
constructor() {
this.loadingScreenElement = document.getElementById('initialPageLoadingScreen');
this.loadingAnimationElement = document.getElementById('initialPageLoadingAnimation');
this.initialLoadingScreenCSS = document.getElementById('initialLoadingScreenCSS') as HTMLLinkElement | null;
this.handleAnimationEndEvent = this.handleAnimationEndEvent.bind(this);
}
private handleAnimationEndEvent(event: AnimationEvent) {
if (!this.loadingAnimationElement) {
return;
}
if (event.animationName === ANIMATION_CLASS_FOR_MATTERMOST_LOGO_HIDE || event.animationName === ANIMATION_CLASS_FOR_COMPLETE_LOADER_HIDE) {
if (!this.isLoading) {
this.loadingAnimationElement.className = STATIC_CLASS_FOR_ANIMATION;
// Automatically destroy the loading screen after the animation has finished.
// Should be changed if we want to loading animation to start again.
setTimeout(() => {
this.destroy();
}, DESTROY_DELAY_AFTER_ANIMATION_END);
}
}
}
private addAnimationEndListener() {
if (this.loadingAnimationElement) {
this.loadingAnimationElement.addEventListener('animationend', this.handleAnimationEndEvent);
}
}
private removeAnimationEndListener() {
if (this.loadingAnimationElement) {
this.loadingAnimationElement.removeEventListener('animationend', this.handleAnimationEndEvent);
}
}
private destroy() {
this.removeAnimationEndListener();
if (this.loadingScreenElement) {
this.loadingScreenElement.remove();
this.loadingScreenElement = null;
}
if (this.initialLoadingScreenCSS) {
this.initialLoadingScreenCSS.remove();
this.initialLoadingScreenCSS = null;
}
if (this.loadingAnimationElement) {
this.loadingAnimationElement = null;
}
this.isLoading = null;
}
/**
* The loading animations are always started as soon as the loading indicator is shown in the screen for the first time.
* But we still want to have this start method incase we need to start the loading animations manually any time.
* If we do want to do that then we should remove the set timeout destroy call doing above.
*/
public start() {
if (isDesktopApp()) {
// Let Mattermost desktop handle the loading screen
this.destroy();
return;
}
this.addAnimationEndListener();
if (!this.loadingScreenElement || !this.loadingAnimationElement) {
// eslint-disable-next-line no-console
console.error('InitialLoadingScreen: No loading screen or animation element found');
return;
}
this.isLoading = true;
this.loadingScreenElement.className = LOADING_CLASS_FOR_SCREEN;
this.loadingAnimationElement.className = LOADING_CLASS_FOR_ANIMATION;
}
public stop() {
if (!this.loadingScreenElement || !this.loadingAnimationElement) {
return;
}
this.isLoading = false;
this.loadingScreenElement.className = LOADING_COMPLETE_CLASS_FOR_SCREEN;
this.loadingAnimationElement.className = LOADING_COMPLETE_CLASS_FOR_ANIMATION;
}
}

Просмотреть файл

@@ -0,0 +1,221 @@
<div
id="initialPageLoadingScreen"
class="LoadingScreen LoadingScreen--darkMode"
>
<div class="LoadingScreen__background">
<svg
viewBox="0 0 1366 728"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid slice"
>
<g style="transform: translate(676px, -200px)">
<g
style="
transform-origin: 450px 267.5px;
transform: rotate(45deg);
"
>
<use href="#pill" />
</g>
</g>
<g style="transform: translate(125px, 0px)">
<g
style="
transform-origin: 450px 267.5px;
transform: rotate(-135deg);
"
>
<use href="#pill" />
</g>
</g>
<g style="transform: translate(700px, 330px)">
<g
style="
transform-origin: 450px 267.5px;
transform: rotate(-135deg);
"
>
<use href="#pill" />
</g>
</g>
<g style="transform: translate(-280px, 190px)">
<g
style="
transform-origin: 450px 267.5px;
transform: rotate(45deg);
"
>
<use href="#pill" />
</g>
</g>
<defs>
<mask id="stippleMask">
<image width="900" height="535" />
</mask>
<g id="pill" class="Pill">
<rect
class="Pill__stipple"
x="0"
y="0"
width="900"
height="535"
mask="url(#stippleMask)"
/>
<path
class="Pill__shape"
d="M600 40H0V490H600C724.264 490 825 389.264 825 265C825 140.736 724.264 40 600 40Z"
fill="url(#pillGradient)"
/>
</g>
<linearGradient id="pillGradient">
<stop
class="Pill__gradient"
offset="20%"
stop-opacity="1"
/>
<stop
class="Pill__gradientHighlight"
offset="95%"
stop-opacity="1"
/>
</linearGradient>
</defs>
</svg>
</div>
<div
id="initialPageLoadingAnimation"
class="LoadingAnimation LoadingAnimation--darkMode LoadingAnimation--spinning LoadingAnimation--loading"
>
<svg
width="104"
height="104"
viewBox="0 0 104 104"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<linearGradient
id="LoadingAnimation__spinner-gradient"
x1="0%"
y1="72px"
x2="0%"
y2="32px"
gradientUnits="userSpaceOnUse"
>
<stop
offset="0"
class="LoadingAnimation__spinner-gradient-color"
stop-opacity="1"
/>
<stop
offset="1"
class="LoadingAnimation__spinner-gradient-color"
stop-opacity="0"
/>
</linearGradient>
<mask id="LoadingAnimation__base-wipe-mask">
<rect x="0" y="0" width="104" height="104" fill="white" />
<g class="LoadingAnimation__compass-base-mask-container">
<circle
class="LoadingAnimation__compass-base-mask"
r="27"
cx="52"
cy="52"
fill="white"
stroke="black"
stroke-width="54"
/>
</g>
</mask>
<mask id="LoadingAnimation__base-mask">
<rect x="0" y="0" width="104" height="104" fill="white" />
<circle r="37" cx="54" cy="46" fill="black" />
<g class="LoadingAnimation__compass-needle-behind-mask">
<g transform="translate(54,46)">
<g transform="translate(-29, -61.3)">
<path
d="M38.5984 0C45.476 1.07762 51.9794 3.28918 57.9108 6.43722V61.1566C57.9108 77.1373 44.9364 90.1119 28.9554 90.1119C12.9744 90.1119 0 77.1373 0 61.1566C0 55.3848 1.69443 50.0063 4.60763 45.4861L38.5984 0Z"
fill="black"
/>
</g>
</g>
</g>
<g class="LoadingAnimation__compass-needle-front-mask">
<g transform="translate(54,46)">
<g transform="translate(-29,-61.3)">
<path
d="M38.5984 0C45.476 1.07762 51.9794 3.28918 57.9108 6.43722V61.1566C57.9108 77.1373 44.9364 90.1119 28.9554 90.1119C12.9744 90.1119 0 77.1373 0 61.1566C0 55.3848 1.69443 50.0063 4.60763 45.4861L38.5984 0Z"
fill="black"
/>
</g>
</g>
</g>
</mask>
<mask id="LoadingAnimation__spinner-left-half-mask">
<rect x="0" y="0" width="52" height="104" fill="white" />
<circle
class="LoadingAnimation__spinner-mask"
r="20"
cx="52"
cy="52"
fill="black"
/>
</mask>
<mask id="LoadingAnimation__spinner-right-half-mask">
<rect x="52" y="0" width="52" height="104" fill="white" />
<circle
class="LoadingAnimation__spinner-mask"
r="20"
cx="52"
cy="52"
fill="black"
/>
</mask>
</defs>
<g class="LoadingAnimation__spinner-container">
<g class="LoadingAnimation__spinner">
<circle
r="25"
cx="52"
cy="52"
fill="currentColor"
mask="url(#LoadingAnimation__spinner-left-half-mask)"
/>
<circle
r="25"
cx="52"
cy="52"
fill="url(#LoadingAnimation__spinner-gradient)"
mask="url(#LoadingAnimation__spinner-right-half-mask)"
/>
</g>
</g>
<g class="LoadingAnimation__compass">
<g
class="LoadingAnimation__compass-base-container"
mask="url(#LoadingAnimation__base-wipe-mask)"
>
<circle
class="LoadingAnimation__compass-base"
r="52"
cx="52"
cy="52"
fill="currentColor"
mask="url(#LoadingAnimation__base-mask)"
/>
</g>
<g class="LoadingAnimation__compass-needle-container">
<g class="LoadingAnimation__compass-needle">
<g transform="translate(54,46)">
<g transform="translate(-15,-42)">
<path
d="M29.9539 1.4977C29.9539 0.670968 29.2827 0 28.4562 0C27.9597 0 27.5192 0.242028 27.2468 0.614415C27.216 0.656555 27.1873 0.700359 27.1609 0.745666L3.66519 32.1191C1.38202 34.7479 0 38.1803 0 41.9355C0 50.207 6.70541 56.9124 14.977 56.9124C23.2485 56.9124 29.9539 50.207 29.9539 41.9355L29.9539 41.9013V1.50252C29.9539 1.50091 29.9539 1.49931 29.9539 1.4977Z"
fill="currentColor"
/>
</g>
</g>
</g>
</g>
</g>
</svg>
</div>
</div>