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.

145 lines
4.2 KiB

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