diff options
Diffstat (limited to 'app/javascript/mastodon/features/ui/components/image_loader.js')
-rw-r--r-- | app/javascript/mastodon/features/ui/components/image_loader.js | 116 |
1 files changed, 96 insertions, 20 deletions
diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.js index 5c3879970..52c3a898b 100644 --- a/app/javascript/mastodon/features/ui/components/image_loader.js +++ b/app/javascript/mastodon/features/ui/components/image_loader.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; export default class ImageLoader extends React.PureComponent { @@ -20,46 +21,121 @@ export default class ImageLoader extends React.PureComponent { error: false, } - componentWillMount() { - this._loadImage(this.props.src); + removers = []; + + get canvasContext() { + if (!this.canvas) { + return null; + } + this._canvasContext = this._canvasContext || this.canvas.getContext('2d'); + return this._canvasContext; + } + + componentDidMount () { + this.loadImage(this.props); + } + + componentWillReceiveProps (nextProps) { + if (this.props.src !== nextProps.src) { + this.loadImage(nextProps); + } } - componentWillReceiveProps(props) { - this._loadImage(props.src); + loadImage (props) { + this.removeEventListeners(); + this.setState({ loading: true, error: false }); + Promise.all([ + this.loadPreviewCanvas(props), + this.loadOriginalImage(props), + ]) + .then(() => { + this.setState({ loading: false, error: false }); + this.clearPreviewCanvas(); + }) + .catch(() => this.setState({ loading: false, error: true })); } - _loadImage(src) { + loadPreviewCanvas = ({ previewSrc, width, height }) => new Promise((resolve, reject) => { const image = new Image(); + const removeEventListeners = () => { + image.removeEventListener('error', handleError); + image.removeEventListener('load', handleLoad); + }; + const handleError = () => { + removeEventListeners(); + reject(); + }; + const handleLoad = () => { + removeEventListeners(); + this.canvasContext.drawImage(image, 0, 0, width, height); + resolve(); + }; + image.addEventListener('error', handleError); + image.addEventListener('load', handleLoad); + image.src = previewSrc; + this.removers.push(removeEventListeners); + }) - image.onerror = () => this.setState({ loading: false, error: true }); - image.onload = () => this.setState({ loading: false, error: false }); + clearPreviewCanvas () { + const { width, height } = this.canvas; + this.canvasContext.clearRect(0, 0, width, height); + } + loadOriginalImage = ({ src }) => new Promise((resolve, reject) => { + const image = new Image(); + const removeEventListeners = () => { + image.removeEventListener('error', handleError); + image.removeEventListener('load', handleLoad); + }; + const handleError = () => { + removeEventListeners(); + reject(); + }; + const handleLoad = () => { + removeEventListeners(); + resolve(); + }; + image.addEventListener('error', handleError); + image.addEventListener('load', handleLoad); image.src = src; + this.removers.push(removeEventListeners); + }); - this.setState({ loading: true }); + removeEventListeners () { + this.removers.forEach(listeners => listeners()); + this.removers = []; } - render() { - const { alt, src, previewSrc, width, height } = this.props; + setCanvasRef = c => { + this.canvas = c; + } + + render () { + const { alt, src, width, height } = this.props; const { loading } = this.state; + const className = classNames('image-loader', { + 'image-loader--loading': loading, + }); + return ( - <div className='image-loader'> - <img - alt={alt} - className='image-loader__img' - src={src} + <div className={className}> + <canvas + className='image-loader__preview-canvas' width={width} height={height} + ref={this.setCanvasRef} /> - {loading && + {!loading && ( <img - alt='' - src={previewSrc} - className='image-loader__preview-img' + alt={alt} + className='image-loader__img' + src={src} + width={width} + height={height} /> - } + )} </div> ); } |