You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

144 lines
4.1 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import ImmutablePropTypes from 'react-immutable-proptypes';
  4. import ImmutablePureComponent from 'react-immutable-pure-component';
  5. import { autoPlayGif, displayMedia } from 'mastodon/initial_state';
  6. import classNames from 'classnames';
  7. import { decode } from 'blurhash';
  8. import { isIOS } from 'mastodon/is_mobile';
  9. export default class MediaItem extends ImmutablePureComponent {
  10. static propTypes = {
  11. attachment: ImmutablePropTypes.map.isRequired,
  12. displayWidth: PropTypes.number.isRequired,
  13. onOpenMedia: PropTypes.func.isRequired,
  14. };
  15. state = {
  16. visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
  17. loaded: false,
  18. };
  19. componentDidMount () {
  20. if (this.props.attachment.get('blurhash')) {
  21. this._decode();
  22. }
  23. }
  24. componentDidUpdate (prevProps) {
  25. if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) {
  26. this._decode();
  27. }
  28. }
  29. _decode () {
  30. const hash = this.props.attachment.get('blurhash');
  31. const pixels = decode(hash, 32, 32);
  32. if (pixels) {
  33. const ctx = this.canvas.getContext('2d');
  34. const imageData = new ImageData(pixels, 32, 32);
  35. ctx.putImageData(imageData, 0, 0);
  36. }
  37. }
  38. setCanvasRef = c => {
  39. this.canvas = c;
  40. }
  41. handleImageLoad = () => {
  42. this.setState({ loaded: true });
  43. }
  44. handleMouseEnter = e => {
  45. if (this.hoverToPlay()) {
  46. e.target.play();
  47. }
  48. }
  49. handleMouseLeave = e => {
  50. if (this.hoverToPlay()) {
  51. e.target.pause();
  52. e.target.currentTime = 0;
  53. }
  54. }
  55. hoverToPlay () {
  56. return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1;
  57. }
  58. handleClick = e => {
  59. if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
  60. e.preventDefault();
  61. if (this.state.visible) {
  62. this.props.onOpenMedia(this.props.attachment);
  63. } else {
  64. this.setState({ visible: true });
  65. }
  66. }
  67. }
  68. render () {
  69. const { attachment, displayWidth } = this.props;
  70. const { visible, loaded } = this.state;
  71. const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`;
  72. const height = width;
  73. const status = attachment.get('status');
  74. let thumbnail = '';
  75. if (attachment.get('type') === 'unknown') {
  76. // Skip
  77. } else if (attachment.get('type') === 'image') {
  78. const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
  79. const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
  80. const x = ((focusX / 2) + .5) * 100;
  81. const y = ((focusY / -2) + .5) * 100;
  82. thumbnail = (
  83. <img
  84. src={attachment.get('preview_url')}
  85. alt={attachment.get('description')}
  86. title={attachment.get('description')}
  87. style={{ objectPosition: `${x}% ${y}%` }}
  88. onLoad={this.handleImageLoad}
  89. />
  90. );
  91. } else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) {
  92. const autoPlay = !isIOS() && autoPlayGif;
  93. thumbnail = (
  94. <div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
  95. <video
  96. className='media-gallery__item-gifv-thumbnail'
  97. aria-label={attachment.get('description')}
  98. title={attachment.get('description')}
  99. role='application'
  100. src={attachment.get('url')}
  101. onMouseEnter={this.handleMouseEnter}
  102. onMouseLeave={this.handleMouseLeave}
  103. autoPlay={autoPlay}
  104. loop
  105. muted
  106. />
  107. <span className='media-gallery__gifv__label'>GIF</span>
  108. </div>
  109. );
  110. }
  111. return (
  112. <div className='account-gallery__item' style={{ width, height }}>
  113. <a className='media-gallery__item-thumbnail' href={status.get('url')} target='_blank' onClick={this.handleClick}>
  114. <canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })} />
  115. {visible && thumbnail}
  116. </a>
  117. </div>
  118. );
  119. }
  120. }