mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Page: Use bouncing logo instead of loading spinner for fallback (#79028)
* use bouncing logo instead of loading spinner for page loader * translate loading text * update unit test * extract bouncing logo out into component
This commit is contained in:
parent
7451f41967
commit
09445e0ecc
95
public/app/core/components/BouncingLoader/BouncingLoader.tsx
Normal file
95
public/app/core/components/BouncingLoader/BouncingLoader.tsx
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import { css, keyframes } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { t } from '../../internationalization';
|
||||||
|
|
||||||
|
export function BouncingLoader() {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.container}
|
||||||
|
aria-live="polite"
|
||||||
|
role="status"
|
||||||
|
aria-label={t('bouncing-loader.label', 'Loading')}
|
||||||
|
>
|
||||||
|
<div className={styles.bounce}>
|
||||||
|
<img alt="" src="public/img/grafana_icon.svg" className={styles.logo} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fadeIn = keyframes({
|
||||||
|
'0%': {
|
||||||
|
opacity: 0,
|
||||||
|
animationTimingFunction: 'cubic-bezier(0, 0, 0.5, 1)',
|
||||||
|
},
|
||||||
|
'100%': {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const bounce = keyframes({
|
||||||
|
'from, to': {
|
||||||
|
transform: 'translateY(0px)',
|
||||||
|
animationTimingFunction: 'cubic-bezier(0.3, 0, 0.1, 1)',
|
||||||
|
},
|
||||||
|
'50%': {
|
||||||
|
transform: 'translateY(-50px)',
|
||||||
|
animationTimingFunction: 'cubic-bezier(0.9, 0, 0.7, 1)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const squash = keyframes({
|
||||||
|
'0%': {
|
||||||
|
transform: 'scaleX(1.3) scaleY(0.8)',
|
||||||
|
animationTimingFunction: 'cubic-bezier(0.3, 0, 0.1, 1)',
|
||||||
|
},
|
||||||
|
'15%': {
|
||||||
|
transform: 'scaleX(0.75) scaleY(1.25)',
|
||||||
|
animationTimingFunction: 'cubic-bezier(0, 0, 0.7, 0.75)',
|
||||||
|
},
|
||||||
|
'55%': {
|
||||||
|
transform: 'scaleX(1.05) scaleY(0.95)',
|
||||||
|
animationTimingFunction: 'cubic-bezier(0.9, 0, 1, 1)',
|
||||||
|
},
|
||||||
|
'95%': {
|
||||||
|
transform: 'scaleX(0.75) scaleY(1.25)',
|
||||||
|
animationTimingFunction: 'cubic-bezier(0, 0, 0, 1)',
|
||||||
|
},
|
||||||
|
'100%': {
|
||||||
|
transform: 'scaleX(1.3) scaleY(0.8)',
|
||||||
|
animationTimingFunction: 'cubic-bezier(0, 0, 0.7, 1)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
container: css({
|
||||||
|
opacity: 0,
|
||||||
|
animationName: fadeIn,
|
||||||
|
animationIterationCount: 1,
|
||||||
|
animationDuration: '0.9s',
|
||||||
|
animationDelay: '0.5s',
|
||||||
|
animationFillMode: 'forwards',
|
||||||
|
}),
|
||||||
|
|
||||||
|
bounce: css({
|
||||||
|
textAlign: 'center',
|
||||||
|
animationName: bounce,
|
||||||
|
animationDuration: '0.9s',
|
||||||
|
animationIterationCount: 'infinite',
|
||||||
|
}),
|
||||||
|
|
||||||
|
logo: css({
|
||||||
|
display: 'inline-block',
|
||||||
|
animationName: squash,
|
||||||
|
animationDuration: '0.9s',
|
||||||
|
animationIterationCount: 'infinite',
|
||||||
|
width: '60px',
|
||||||
|
height: '60px',
|
||||||
|
}),
|
||||||
|
});
|
@ -53,7 +53,7 @@ describe('GrafanaRoute', () => {
|
|||||||
|
|
||||||
setup({ route: { component: PageComponent, path: '' } });
|
setup({ route: { component: PageComponent, path: '' } });
|
||||||
|
|
||||||
expect(await screen.findByText('Loading...')).toBeInTheDocument();
|
expect(await screen.findByLabelText('Loading')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Shows error on page error', async () => {
|
it('Shows error on page error', async () => {
|
||||||
|
@ -1,19 +1,27 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { LoadingPlaceholder, useStyles2 } from '@grafana/ui';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
import { useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { BouncingLoader } from '../components/BouncingLoader/BouncingLoader';
|
||||||
|
|
||||||
export function GrafanaRouteLoading() {
|
export function GrafanaRouteLoading() {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.loadingPage}>
|
<div
|
||||||
<LoadingPlaceholder text={'Loading...'} />
|
className={cx(styles.loadingPage, {
|
||||||
|
[styles.loadingPageDockedNav]: config.featureToggles.dockedMegaMenu,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<BouncingLoader />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyles = () => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
loadingPage: css({
|
loadingPage: css({
|
||||||
height: '100%',
|
height: '100%',
|
||||||
flexDrection: 'column',
|
flexDrection: 'column',
|
||||||
@ -21,4 +29,7 @@ const getStyles = () => ({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}),
|
}),
|
||||||
|
loadingPageDockedNav: css({
|
||||||
|
backgroundColor: theme.colors.background.primary,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
@ -25,6 +25,9 @@
|
|||||||
"user": "Nutzer"
|
"user": "Nutzer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"bouncing-loader": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
"browse-dashboards": {
|
"browse-dashboards": {
|
||||||
"action": {
|
"action": {
|
||||||
"cancel-button": "Abbrechen",
|
"cancel-button": "Abbrechen",
|
||||||
|
@ -25,6 +25,9 @@
|
|||||||
"user": "User"
|
"user": "User"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"bouncing-loader": {
|
||||||
|
"label": "Loading"
|
||||||
|
},
|
||||||
"browse-dashboards": {
|
"browse-dashboards": {
|
||||||
"action": {
|
"action": {
|
||||||
"cancel-button": "Cancel",
|
"cancel-button": "Cancel",
|
||||||
|
@ -25,6 +25,9 @@
|
|||||||
"user": "Usuario"
|
"user": "Usuario"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"bouncing-loader": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
"browse-dashboards": {
|
"browse-dashboards": {
|
||||||
"action": {
|
"action": {
|
||||||
"cancel-button": "Cancelar",
|
"cancel-button": "Cancelar",
|
||||||
|
@ -25,6 +25,9 @@
|
|||||||
"user": "Utilisateur"
|
"user": "Utilisateur"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"bouncing-loader": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
"browse-dashboards": {
|
"browse-dashboards": {
|
||||||
"action": {
|
"action": {
|
||||||
"cancel-button": "Annuler",
|
"cancel-button": "Annuler",
|
||||||
|
@ -25,6 +25,9 @@
|
|||||||
"user": "Ůşęř"
|
"user": "Ůşęř"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"bouncing-loader": {
|
||||||
|
"label": "Ŀőäđįʼnģ"
|
||||||
|
},
|
||||||
"browse-dashboards": {
|
"browse-dashboards": {
|
||||||
"action": {
|
"action": {
|
||||||
"cancel-button": "Cäʼnčęľ",
|
"cancel-button": "Cäʼnčęľ",
|
||||||
|
@ -25,6 +25,9 @@
|
|||||||
"user": "用户"
|
"user": "用户"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"bouncing-loader": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
"browse-dashboards": {
|
"browse-dashboards": {
|
||||||
"action": {
|
"action": {
|
||||||
"cancel-button": "取消",
|
"cancel-button": "取消",
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
animation-name: preloader-fade-in;
|
animation-name: preloader-fade-in;
|
||||||
animation-iteration-count: 1;
|
animation-iteration-count: 1;
|
||||||
animation-duration: 0.9s;
|
animation-duration: 0.9s;
|
||||||
animation-delay: 1.35s;
|
animation-delay: 0.5s;
|
||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +81,7 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
animation-name: preloader-fade-in;
|
animation-name: preloader-fade-in;
|
||||||
animation-duration: 0.9s;
|
animation-duration: 0.9s;
|
||||||
animation-delay: 1.8s;
|
animation-delay: 0.5s;
|
||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,10 +172,9 @@
|
|||||||
<div class="preloader">
|
<div class="preloader">
|
||||||
<div class="preloader__enter">
|
<div class="preloader__enter">
|
||||||
<div class="preloader__bounce">
|
<div class="preloader__bounce">
|
||||||
<div class="preloader__logo"></div>
|
<div class="preloader__logo" aria-live="polite" role="status" aria-label="Loading Grafana"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="preloader__text">Loading Grafana</div>
|
|
||||||
<div class="preloader__text preloader__text--fail">
|
<div class="preloader__text preloader__text--fail">
|
||||||
<p>
|
<p>
|
||||||
<strong>If you're seeing this Grafana has failed to load its application files</strong>
|
<strong>If you're seeing this Grafana has failed to load its application files</strong>
|
||||||
|
Loading…
Reference in New Issue
Block a user