Add new loading animation

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-05-04 19:21:26 +02:00
parent 85eff24be1
commit bd58bca39c
7 changed files with 242 additions and 72 deletions

View file

@ -1,35 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React from 'react'
import LogoColor from '../../../common/hedge-doc-logo/logo_color.svg'
import styles from './animations.module.scss'
export interface HedgeDocLogoProps {
animation: AnimationType
}
export enum AnimationType {
JUMP = 'animation-jump',
SHAKE = 'animation-shake'
}
/**
* Shows an animated hedgedoc logo.
*
* @param animation The name of the animation
*/
export const AnimatedHedgeDocLogo: React.FC<HedgeDocLogoProps> = ({ animation }) => {
return (
<LogoColor
className={`w-auto ${styles[animation]}`}
title={'HedgeDoc logo'}
alt={'HedgeDoc logo'}
height={256}
width={256}
/>
)
}

View file

@ -0,0 +1,97 @@
/*!
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@import "keyframes";
.rows {
transition: opacity 0.2s;
.row {
position: absolute;
top: 50%;
left: 50%;
}
.particle {
width: 24px;
height: 24px;
position: absolute;
font-size: 1.3em !important;
top: calc(50% - 12px);
left: calc(50% - 12px);
animation: particle 3s infinite;
}
@for $i from 1 through 12 {
.row:nth-child(#{$i}) {
transform: rotateZ(30deg * ($i - 1));
@for $j from 1 through 10 {
& .particle:nth-child(#{$j}) {
opacity: 0;
animation-timing-function: ease-out;
animation-delay: -$i * 830ms - $j * 600ms;
}
}
}
}
}
.logo {
z-index: 1000;
position: relative;
font-size: 3em;
height: 240px;
width: 203px;
color: #ffffff;
text-shadow: 4px 4px 0 #3b4045;
.overlay {
color: rgb(181, 31, 8);
height: 0;
overflow: hidden;
animation: fill 6s infinite;
width: 100%;
&, :global(.fa) {
position: absolute;
bottom: 0;
left: 0;
}
}
}
.pulse {
animation: 3s pulse infinite;
box-shadow: #404040 0 0 200px 100px;
position: absolute;
left: 0;
top: 0;
width: 1px;
height: 1px;
border-radius: 100%;
margin: auto;
right: 0;
bottom: 0;
}
.error {
.channels {
opacity: 0;
}
.pulse {
animation: none;
}
.logo {
.overlay {
animation: none;
}
color: rgb(181, 31, 8);
animation: 1s shake;
}
}

View file

@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useMemo } from 'react'
import { createNumberRangeArray } from '../../common/number-range/number-range'
import { RandomIcon } from './random-icon'
import styles from './animations.module.scss'
/**
* Shows a number of {@link RandomIcon random icons in a row}.
*/
export const IconRow: React.FC = () => {
const children = useMemo(() => createNumberRangeArray(5).map((index) => <RandomIcon key={index}></RandomIcon>), [])
return <div className={styles.row}>{children}</div>
}

View file

@ -1,34 +1,10 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
/*!
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@keyframes animation-jump {
0% {
transform: scale(1, 1) translateY(0);
}
10% {
transform: scale(1.1, .9) translateY(0);
}
30% {
transform: scale(.9, 1.1) translateY(-100px);
}
50% {
transform: scale(1.05, .95) translateY(0);
}
57% {
transform: scale(1, 1) translateY(-7px);
}
64% {
transform: scale(1, 1) translateY(0);
}
100% {
transform: scale(1, 1) translateY(0);
}
}
@keyframes animation-shake {
@keyframes shake {
0% {
transform: translate(1px, 1px) rotate(0deg);
}
@ -64,14 +40,54 @@
}
}
.animation-jump {
transform-origin: bottom;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-name: animation-jump;
animation-timing-function: cubic-bezier(0.280, 0.840, 0.420, 1);
@keyframes particle {
0% {
opacity: 0.3;
transform: translate(300px, 300px) rotateZ(360deg);
border-radius: 0;
color: #ffffff;
}
20% {
border-radius: 0;
}
70% {
opacity: 1;
transform: translate(120px, 120px) rotateZ(180deg);
border-radius: 10px;
}
90% {
opacity: 0;
}
100% {
transform: translate(0px, 0px) rotateZ(0deg);
color: rgb(181, 31, 8);
}
}
.animation-shake {
animation: animation-shake 0.3s ease-in-out;
@keyframes fill {
0% {
height: 0%;
}
50% {
height: 70%;
}
100%{
height: 100%;
}
}
@keyframes pulse {
0% {
box-shadow: #ffffff00 0 0 200px 100px;
}
30% {
box-shadow: #ffffff33 0 0 200px 100px;
}
100% {
box-shadow: #ffffff00 0 0 200px 100px;
}
}

View file

@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useMemo } from 'react'
import styles from './animations.module.scss'
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
import { IconRow } from './icon-row'
import { createNumberRangeArray } from '../../common/number-range/number-range'
export interface HedgeDocLogoProps {
error: boolean
}
/**
* Shows a loading animation.
*
* @param error Defines if the error animation should be shown instead
*/
export const LoadingAnimation: React.FC<HedgeDocLogoProps> = ({ error }) => {
const iconRows = useMemo(() => createNumberRangeArray(12).map((index) => <IconRow key={index} />), [])
return (
<div className={`position-relative ${error ? styles.error : ''}`}>
<div className={styles.logo}>
<div>
<ForkAwesomeIcon icon={'pencil'} className={styles.background} size={'5x'}></ForkAwesomeIcon>
</div>
<div className={`${styles.overlay}`}>
<ForkAwesomeIcon icon={'pencil'} size={'5x'}></ForkAwesomeIcon>
</div>
</div>
<div className={styles.pulse}></div>
<div className={styles.rows}>{iconRows}</div>
</div>
)
}

View file

@ -6,7 +6,7 @@
import React from 'react'
import { Alert } from 'react-bootstrap'
import { AnimatedHedgeDocLogo, AnimationType } from './animated-hedge-doc-logo/animated-hedge-doc-logo'
import { LoadingAnimation } from './loading-animation'
import { ShowIf } from '../../common/show-if/show-if'
import styles from '../application-loader.module.scss'
@ -24,7 +24,7 @@ export const LoadingScreen: React.FC<LoadingScreenProps> = ({ failedTaskName })
<div className={`${styles.loader} ${styles.middle} text-light overflow-hidden`}>
<div className='mb-3 text-light'>
<span className={`d-block`}>
<AnimatedHedgeDocLogo animation={failedTaskName ? AnimationType.SHAKE : AnimationType.JUMP} />
<LoadingAnimation error={!!failedTaskName} />
</span>
</div>
<ShowIf condition={!!failedTaskName}>

View file

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useMemo } from 'react'
import type { IconName } from '../../common/fork-awesome/types'
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
import styles from './animations.module.scss'
const elements: IconName[] = [
'file-text',
'markdown',
'pencil',
'bold',
'italic',
'align-justify',
'tag',
'user',
'file',
'keyboard-o',
'cog',
'font'
]
/**
* Chooses a random fork awesome icon from a predefined set and renders it.
*/
export const RandomIcon: React.FC = () => {
const icon = useMemo(() => elements[Math.floor(Math.random() * elements.length)], [])
return <ForkAwesomeIcon icon={icon} className={styles.particle}></ForkAwesomeIcon>
}