Replace repetitive blurhash code with component (#14267)
This commit replaces all unnecessarily repeated code for decoding and embedding blurhash canvases with separate component - <Blurhash>. Under the hood Blurhash component will use effect dependent on its props. This gives a few benefits: it will only be re-rendered whenever the hash or width/height/dummy props update, and will not render if canvas won't get to the final DOM, because then effect won't fire, which prevents weird bugs like #14257.
This commit is contained in:
		
							parent
							
								
									5b7a93b02c
								
							
						
					
					
						commit
						61c07c3731
					
				
					 5 changed files with 102 additions and 125 deletions
				
			
		| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
import { decode } from 'blurhash';
 | 
			
		||||
import Blurhash from 'mastodon/components/blurhash';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import Icon from 'mastodon/components/icon';
 | 
			
		||||
import { autoPlayGif, displayMedia } from 'mastodon/initial_state';
 | 
			
		||||
import { autoPlayGif, displayMedia, useBlurhash } from 'mastodon/initial_state';
 | 
			
		||||
import { isIOS } from 'mastodon/is_mobile';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
| 
						 | 
				
			
			@ -21,34 +21,6 @@ export default class MediaItem extends ImmutablePureComponent {
 | 
			
		|||
    loaded: false,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  componentDidMount () {
 | 
			
		||||
    if (this.props.attachment.get('blurhash')) {
 | 
			
		||||
      this._decode();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidUpdate (prevProps) {
 | 
			
		||||
    if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) {
 | 
			
		||||
      this._decode();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _decode () {
 | 
			
		||||
    const hash   = this.props.attachment.get('blurhash');
 | 
			
		||||
    const pixels = decode(hash, 32, 32);
 | 
			
		||||
 | 
			
		||||
    if (pixels) {
 | 
			
		||||
      const ctx       = this.canvas.getContext('2d');
 | 
			
		||||
      const imageData = new ImageData(pixels, 32, 32);
 | 
			
		||||
 | 
			
		||||
      ctx.putImageData(imageData, 0, 0);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setCanvasRef = c => {
 | 
			
		||||
    this.canvas = c;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleImageLoad = () => {
 | 
			
		||||
    this.setState({ loaded: true });
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -152,7 +124,13 @@ export default class MediaItem extends ImmutablePureComponent {
 | 
			
		|||
    return (
 | 
			
		||||
      <div className='account-gallery__item' style={{ width, height }}>
 | 
			
		||||
        <a className='media-gallery__item-thumbnail' href={status.get('url')} onClick={this.handleClick} title={title} target='_blank' rel='noopener noreferrer'>
 | 
			
		||||
          <canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })} />
 | 
			
		||||
          <Blurhash
 | 
			
		||||
            hash={attachment.get('blurhash')}
 | 
			
		||||
            className={classNames('media-gallery__preview', {
 | 
			
		||||
              'media-gallery__preview--hidden': visible && loaded,
 | 
			
		||||
            })}
 | 
			
		||||
            dummy={!useBlurhash}
 | 
			
		||||
          />
 | 
			
		||||
          {visible && thumbnail}
 | 
			
		||||
          {!visible && icon}
 | 
			
		||||
        </a>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ import punycode from 'punycode';
 | 
			
		|||
import classnames from 'classnames';
 | 
			
		||||
import Icon from 'mastodon/components/icon';
 | 
			
		||||
import { useBlurhash } from 'mastodon/initial_state';
 | 
			
		||||
import { decode } from 'blurhash';
 | 
			
		||||
import Blurhash from 'mastodon/components/blurhash';
 | 
			
		||||
import { debounce } from 'lodash';
 | 
			
		||||
 | 
			
		||||
const IDNA_PREFIX = 'xn--';
 | 
			
		||||
| 
						 | 
				
			
			@ -93,38 +93,12 @@ export default class Card extends React.PureComponent {
 | 
			
		|||
 | 
			
		||||
  componentDidMount () {
 | 
			
		||||
    window.addEventListener('resize', this.handleResize, { passive: true });
 | 
			
		||||
 | 
			
		||||
    if (this.props.card && this.props.card.get('blurhash') && this.canvas) {
 | 
			
		||||
      this._decode();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentWillUnmount () {
 | 
			
		||||
    window.removeEventListener('resize', this.handleResize);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidUpdate (prevProps) {
 | 
			
		||||
    const { card } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (card.get('blurhash') && (!prevProps.card || prevProps.card.get('blurhash') !== card.get('blurhash')) && this.canvas) {
 | 
			
		||||
      this._decode();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _decode () {
 | 
			
		||||
    if (!useBlurhash) return;
 | 
			
		||||
 | 
			
		||||
    const hash   = this.props.card.get('blurhash');
 | 
			
		||||
    const pixels = decode(hash, 32, 32);
 | 
			
		||||
 | 
			
		||||
    if (pixels) {
 | 
			
		||||
      const ctx       = this.canvas.getContext('2d');
 | 
			
		||||
      const imageData = new ImageData(pixels, 32, 32);
 | 
			
		||||
 | 
			
		||||
      ctx.putImageData(imageData, 0, 0);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _setDimensions () {
 | 
			
		||||
    const width = this.node.offsetWidth;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -182,10 +156,6 @@ export default class Card extends React.PureComponent {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setCanvasRef = c => {
 | 
			
		||||
    this.canvas = c;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleImageLoad = () => {
 | 
			
		||||
    this.setState({ previewLoaded: true });
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -238,7 +208,15 @@ export default class Card extends React.PureComponent {
 | 
			
		|||
    );
 | 
			
		||||
 | 
			
		||||
    let embed     = '';
 | 
			
		||||
    let canvas = <canvas width={32} height={32} ref={this.setCanvasRef} className={classnames('status-card__image-preview', { 'status-card__image-preview--hidden' : revealed && this.state.previewLoaded })} />;
 | 
			
		||||
    let canvas = (
 | 
			
		||||
      <Blurhash
 | 
			
		||||
        className={classnames('status-card__image-preview', {
 | 
			
		||||
          'status-card__image-preview--hidden': revealed && this.state.previewLoaded,
 | 
			
		||||
        })}
 | 
			
		||||
        hash={card.get('blurhash')}
 | 
			
		||||
        dummy={!useBlurhash}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
    let thumbnail = <img src={card.get('image')} alt='' style={{ width: horizontal ? width : null, height: horizontal ? height : null, visibility: revealed ? null : 'hidden' }} onLoad={this.handleImageLoad} className='status-card__image-image' />;
 | 
			
		||||
    let spoilerButton = (
 | 
			
		||||
      <button type='button' onClick={this.handleReveal} className='spoiler-button__overlay'>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ import classNames from 'classnames';
 | 
			
		|||
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
 | 
			
		||||
import { displayMedia, useBlurhash } from '../../initial_state';
 | 
			
		||||
import Icon from 'mastodon/components/icon';
 | 
			
		||||
import { decode } from 'blurhash';
 | 
			
		||||
import Blurhash from 'mastodon/components/blurhash';
 | 
			
		||||
 | 
			
		||||
const messages = defineMessages({
 | 
			
		||||
  play: { id: 'video.play', defaultMessage: 'Play' },
 | 
			
		||||
| 
						 | 
				
			
			@ -169,10 +169,6 @@ class Video extends React.PureComponent {
 | 
			
		|||
    this.volume = c;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setCanvasRef = c => {
 | 
			
		||||
    this.canvas = c;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleClickRoot = e => e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
  handlePlay = () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -289,10 +285,6 @@ class Video extends React.PureComponent {
 | 
			
		|||
 | 
			
		||||
    window.addEventListener('scroll', this.handleScroll);
 | 
			
		||||
    window.addEventListener('resize', this.handleResize, { passive: true });
 | 
			
		||||
 | 
			
		||||
    if (this.props.blurhash) {
 | 
			
		||||
      this._decode();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentWillUnmount () {
 | 
			
		||||
| 
						 | 
				
			
			@ -315,24 +307,6 @@ class Video extends React.PureComponent {
 | 
			
		|||
    if (prevState.revealed && !this.state.revealed && this.video) {
 | 
			
		||||
      this.video.pause();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (prevProps.blurhash !== this.props.blurhash && this.props.blurhash) {
 | 
			
		||||
      this._decode();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _decode () {
 | 
			
		||||
    if (!useBlurhash) return;
 | 
			
		||||
 | 
			
		||||
    const hash   = this.props.blurhash;
 | 
			
		||||
    const pixels = decode(hash, 32, 32);
 | 
			
		||||
 | 
			
		||||
    if (pixels) {
 | 
			
		||||
      const ctx       = this.canvas.getContext('2d');
 | 
			
		||||
      const imageData = new ImageData(pixels, 32, 32);
 | 
			
		||||
 | 
			
		||||
      ctx.putImageData(imageData, 0, 0);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleResize = debounce(() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -438,7 +412,7 @@ class Video extends React.PureComponent {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, link, editable } = this.props;
 | 
			
		||||
    const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, link, editable, blurhash } = this.props;
 | 
			
		||||
    const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
 | 
			
		||||
    const progress = (currentTime / duration) * 100;
 | 
			
		||||
    const playerStyle = {};
 | 
			
		||||
| 
						 | 
				
			
			@ -481,7 +455,13 @@ class Video extends React.PureComponent {
 | 
			
		|||
        onClick={this.handleClickRoot}
 | 
			
		||||
        tabIndex={0}
 | 
			
		||||
      >
 | 
			
		||||
        <canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': revealed })} />
 | 
			
		||||
        <Blurhash
 | 
			
		||||
          hash={blurhash}
 | 
			
		||||
          className={classNames('media-gallery__preview', {
 | 
			
		||||
            'media-gallery__preview--hidden': revealed,
 | 
			
		||||
          })}
 | 
			
		||||
          dummy={!useBlurhash}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        {(revealed || editable) && <video
 | 
			
		||||
          ref={this.setVideoRef}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Reference in a new issue