#366: add loader for qr code and remove layout jumps after content was loaded

This commit is contained in:
SleepWalker 2017-11-19 22:17:23 +02:00
parent 5f4256634f
commit 4afbbd0efb
6 changed files with 104 additions and 18 deletions

View File

@ -5,6 +5,7 @@ import classNames from 'classnames';
import { FormattedMessage as Message } from 'react-intl';
import { ImageLoader } from 'components/ui/loader';
import profileForm from 'components/profile/profileForm.scss';
import messages from '../MultiFactorAuth.intl.json';
@ -14,7 +15,8 @@ export default function KeyForm({secret, qrCodeSrc}: {
secret: string,
qrCodeSrc: string
}) {
const formattedSecret = formatSecret(secret);
// we are using invisible symbol (\u2063) as a placeholder till we get actual secret
const formattedSecret = formatSecret(secret) || '\u2063';
return (
<div className={profileForm.formBody}>
@ -26,7 +28,7 @@ export default function KeyForm({secret, qrCodeSrc}: {
<div className={profileForm.formRow}>
<div className={styles.qrCode}>
<img src={qrCodeSrc} alt={secret} />
<ImageLoader ratio={1} src={qrCodeSrc} alt={secret} />
</div>
</div>

View File

@ -1,5 +1,7 @@
.qrCode {
text-align: center;
width: 242px;
margin: 0 auto;
img {
width: 242px;

View File

@ -1,14 +1,13 @@
import PropTypes from 'prop-types';
// @flow
import type { Skin } from 'components/ui';
import React from 'react';
import classNames from 'classnames';
import { skins, SKIN_DARK } from 'components/ui';
import styles from './componentLoader.scss';
export default function ComponentLoader(props) {
const {skin} = props;
export default function ComponentLoader({ skin }: {
skin: Skin,
}) {
return (
<div className={classNames(styles.componentLoader, styles[`${skin}ComponentLoader`])}>
<div className={styles.spins}>
@ -20,11 +19,6 @@ export default function ComponentLoader(props) {
);
}
ComponentLoader.propTypes = {
skin: PropTypes.oneOf(skins),
};
ComponentLoader.defaultProps = {
skin: SKIN_DARK
skin: 'dark'
};

View File

@ -0,0 +1,64 @@
// @flow
import React from 'react';
import classNames from 'classnames';
import { ComponentLoader } from 'components/ui/loader';
import styles from './imageLoader.scss';
export default class ImageLoader extends React.Component<{
src: string,
alt: string,
ratio: number, // width:height ratio
onLoad?: Function,
}, {
isLoading: bool
}> {
state = {
isLoading: true
};
componentWillMount() {
this.preloadImage();
}
preloadImage() {
const img = new Image();
img.onload = () => this.imageLoaded();
img.onerror = () => this.preloadImage();
img.src = this.props.src;
}
imageLoaded() {
this.setState({ isLoading: false });
if (this.props.onLoad) {
this.props.onLoad();
}
}
render() {
const { isLoading } = this.state;
const { src, alt, ratio } = this.props;
return (
<div className={styles.container}>
<div style={{
height: 0,
paddingBottom: `${ratio * 100}%`
}} />
{isLoading && (
<div className={styles.loader}>
<ComponentLoader />
</div>
)}
<div className={classNames(styles.image, {
[styles.imageLoaded]: !isLoading
})}>
<img src={src} alt={alt} />
</div>
</div>
);
}
}

View File

@ -0,0 +1,25 @@
.container {
position: relative;
z-index: 1;
}
.loader {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.image {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0;
transition: 0.4s ease-in;
}
.imageLoaded {
opacity: 1;
}

View File

@ -1,5 +1,4 @@
import ComponentLoader from './ComponentLoader';
// @flow
export {
ComponentLoader
};
export {default as ImageLoader} from './ImageLoader';
export {default as ComponentLoader} from './ComponentLoader';