[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 удалений

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

@@ -149,6 +149,7 @@
"enzyme-to-json": "3.6.2",
"eslint-plugin-no-only-tests": "3.1.0",
"external-remotes-plugin": "1.0.0",
"html-loader": "5.1.0",
"html-webpack-plugin": "5.5.0",
"identity-obj-proxy": "3.0.0",
"image-webpack-loader": "8.1.0",

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

@@ -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>

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

@@ -0,0 +1,17 @@
<noscript
style="
background-color: #ffffff;
width: 100vw;
position: absolute;
z-index: 101;
font-size: 14px;
height: 40px;
display: flex;
justify-content: center;
flex-wrap: nowrap;
align-items: center;
font-family: sans-serif;
"
>
To use Mattermost, please enable JavaScript.
</noscript>

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

@@ -18,7 +18,7 @@ import {StoragePrefixes} from 'utils/constants';
import {handleLoginLogoutSignal, redirectToOnboardingOrDefaultTeam} from './actions';
import type {Props} from './root';
import Root from './root';
import Root, {doesRouteBelongToTeamControllerRoutes} from './root';
jest.mock('mattermost-redux/client/rudder', () => ({
rudderAnalytics: {
@@ -358,3 +358,49 @@ describe('components/Root', () => {
});
});
});
describe('doesRouteBelongToTeamControllerRoutes', () => {
test('should return true for some of team_controller routes', () => {
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_1/messages/abc')).toBe(true);
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_1/messages')).toBe(true);
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_1/channels/cde')).toBe(true);
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_1/channels')).toBe(true);
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_1/threads/efg')).toBe(true);
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_1/threads')).toBe(true);
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_1/drafts')).toBe(true);
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_1/integrations/klm')).toBe(true);
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_1/emoji/nop')).toBe(true);
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_1/integrations')).toBe(true);
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_1/emoji')).toBe(true);
});
test('should return false for other of team_controller routes', () => {
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_2')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_2/pl/permalink123')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/team_name_example_2/needs_team_component_plugin')).toBe(false);
});
test('should return false for other routes of root', () => {
expect(doesRouteBelongToTeamControllerRoutes('/plug/custom_route_component')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/main_component_product_1')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/product_1/public')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/_redirect/pl/message_1')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/preparing-workspace')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/mfa')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/create_team')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/oauth/authorize')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/select_team')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/admin_console')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/landing')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/terms_of_service')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/claim')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/do_verify_email')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/should_verify_email')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/signup_user_complete')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/reset_password_complete')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/reset_password')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/access_problem')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/login')).toBe(false);
expect(doesRouteBelongToTeamControllerRoutes('/error')).toBe(false);
});
});

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

@@ -28,6 +28,7 @@ import OpenPluginInstallPost from 'components/custom_open_plugin_install_post_re
import GlobalHeader from 'components/global_header/global_header';
import {HFRoute} from 'components/header_footer_route/header_footer_route';
import {HFTRoute, LoggedInHFTRoute} from 'components/header_footer_template_route';
import InitialLoadingScreen from 'components/initial_loading_screen';
import MobileViewWatcher from 'components/mobile_view_watcher';
import ModalController from 'components/modal_controller';
import LaunchingWorkspace, {LAUNCHING_WORKSPACE_FULLSCREEN_Z_INDEX} from 'components/preparing_workspace/launching_workspace';
@@ -309,7 +310,7 @@ export default class Root extends React.PureComponent<Props, State> {
BrowserStore.setLandingPageSeen(true);
};
componentDidUpdate(prevProps: Props) {
componentDidUpdate(prevProps: Props, prevState: State) {
if (!deepEqual(prevProps.theme, this.props.theme)) {
Utils.applyTheme(this.props.theme);
}
@@ -333,6 +334,12 @@ export default class Root extends React.PureComponent<Props, State> {
if (!prevProps.isConfigLoaded && this.props.isConfigLoaded) {
this.setRudderConfig();
}
if (prevState.shouldMountAppRoutes === false && this.state.shouldMountAppRoutes === true) {
if (!doesRouteBelongToTeamControllerRoutes(this.props.location.pathname)) {
InitialLoadingScreen.stop();
}
}
}
captureUTMParams() {
@@ -600,3 +607,8 @@ export default class Root extends React.PureComponent<Props, State> {
);
}
}
export function doesRouteBelongToTeamControllerRoutes(pathname: RouteComponentProps['location']['pathname']): boolean {
const TEAM_CONTROLLER_PATH_PATTERN = /^\/([a-z0-9\-_]+)\/(channels|messages|threads|drafts|integrations|emoji)(\/.*)?$/;
return TEAM_CONTROLLER_PATH_PATTERN.test(pathname);
}

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

@@ -16,6 +16,7 @@ import LocalStorageStore from 'stores/local_storage_store';
import {makeAsyncComponent} from 'components/async_load';
import ChannelController from 'components/channel_layout/channel_controller';
import useTelemetryIdentitySync from 'components/common/hooks/useTelemetryIdentifySync';
import InitialLoadingScreen from 'components/initial_loading_screen';
import Constants from 'utils/constants';
import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard';
@@ -53,6 +54,7 @@ function TeamController(props: Props) {
useTelemetryIdentitySync();
useEffect(() => {
InitialLoadingScreen.stop();
async function fetchInitialChannels() {
await props.fetchAllMyTeamsChannelsAndChannelMembersREST();

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

@@ -19,68 +19,13 @@
<link rel="icon" type="image/png" href="/static/images/favicon/favicon-default-64x64.png" sizes="64x64">
<link rel="icon" type="image/png" href="/static/images/favicon/favicon-default-96x96.png" sizes="96x96">
<!-- CSS Should always go first -->
<link rel='stylesheet' class='code_theme'>
<style>
.error-screen {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
padding-top: 50px;
max-width: 750px;
font-size: 14px;
color: #333333;
margin: auto;
display: none;
line-height: 1.5;
}
.error-screen h2 {
font-size: 30px;
font-weight: normal;
line-height: 1.2;
}
.error-screen ul {
padding-left: 15px;
line-height: 1.7;
margin-top: 0;
margin-bottom: 10px;
}
.error-screen hr {
color: #ddd;
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee;
}
.error-screen-visible {
display: block;
}
</style>
<!-- We need this CSS first to show the initial loading screen -->
<link id="initialLoadingScreenCSS" rel='stylesheet' href='/static/css/initial_loading_screen.css'>
</head>
<body class='font--open_sans enable-animations'>
<div id='root'>
<div class='error-screen'>
<h2>Cannot connect to Mattermost</h2>
<hr/>
<p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p>
<br/>
</div>
<div
class='loading-screen'
style='position: relative'
>
<div class='loading__content'>
<div class='round round-1'></div>
<div class='round round-2'></div>
<div class='round round-3'></div>
</div>
</div>
</div>
<%= require('html-loader!./components/initial_loading_screen/initial_loading_screen_template.html').default %>
<div id='root'></div>
<div id='root-portal'></div>
<noscript>
To use Mattermost, please enable JavaScript.
</noscript>
<%= require('html-loader!./components/no_script/no_script_template.html').default %>
</body>
</html>

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

@@ -133,7 +133,7 @@ var config = {
'mattermost-redux': 'packages/mattermost-redux/src',
'@mui/styled-engine': '@mui/styled-engine-sc',
// This alias restricts single version of styled components acros all packages
// This alias restricts single version of styled components across all packages
'styled-components': path.resolve(__dirname, '..', 'node_modules', 'styled-components'),
},
extensions: ['.ts', '.tsx', '.js', '.jsx'],
@@ -195,6 +195,7 @@ var config = {
{from: 'src/images/payment_processing.png', to: 'images'},
{from: 'src/images/purchase_alert.png', to: 'images'},
{from: '../node_modules/pdfjs-dist/cmaps', to: 'cmaps'},
{from: 'src/components/initial_loading_screen/initial_loading_screen.css', to: 'css'},
],
}),

53
webapp/package-lock.json сгенерированный
Просмотреть файл

@@ -198,6 +198,7 @@
"enzyme-to-json": "3.6.2",
"eslint-plugin-no-only-tests": "3.1.0",
"external-remotes-plugin": "1.0.0",
"html-loader": "5.1.0",
"html-webpack-plugin": "5.5.0",
"identity-obj-proxy": "3.0.0",
"image-webpack-loader": "8.1.0",
@@ -12443,6 +12444,56 @@
"dev": true,
"license": "MIT"
},
"node_modules/html-loader": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/html-loader/-/html-loader-5.1.0.tgz",
"integrity": "sha512-Jb3xwDbsm0W3qlXrCZwcYqYGnYz55hb6aoKQTlzyZPXsPpi6tHXzAfqalecglMQgNvtEfxrCQPaKT90Irt5XDA==",
"dev": true,
"dependencies": {
"html-minifier-terser": "^7.2.0",
"parse5": "^7.1.2"
},
"engines": {
"node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.0.0"
}
},
"node_modules/html-loader/node_modules/commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"dev": true,
"engines": {
"node": ">=14"
}
},
"node_modules/html-loader/node_modules/html-minifier-terser": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz",
"integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==",
"dev": true,
"dependencies": {
"camel-case": "^4.1.2",
"clean-css": "~5.3.2",
"commander": "^10.0.0",
"entities": "^4.4.0",
"param-case": "^3.0.4",
"relateurl": "^0.2.7",
"terser": "^5.15.1"
},
"bin": {
"html-minifier-terser": "cli.js"
},
"engines": {
"node": "^14.13.1 || >=16.0.0"
}
},
"node_modules/html-minifier-terser": {
"version": "6.1.0",
"dev": true,
@@ -23719,7 +23770,7 @@
"typescript": "^5.0.0"
},
"peerDependencies": {
"@mattermost/types": "9.3.0",
"@mattermost/types": "^9.3.0 || ^10.0.0",
"typescript": "^4.3.0 || ^5.0.0"
},
"peerDependenciesMeta": {