[MM-58726] Add a loading screen to initial load of the web app (#27740)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
eafe056bea
Коммит
5d7aa17a27
@@ -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>
|
||||
17
webapp/channels/src/components/no_script/no_script_template.html
Обычный файл
17
webapp/channels/src/components/no_script/no_script_template.html
Обычный файл
@@ -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
сгенерированный
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": {
|
||||
|
||||
Ссылка в новой задаче
Block a user