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.

246 lines
5.6 KiB

  1. import ImmutablePropTypes from 'react-immutable-proptypes';
  2. import PureRenderMixin from 'react-addons-pure-render-mixin';
  3. import IconButton from './icon_button';
  4. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  5. import { isIOS } from '../is_mobile';
  6. const messages = defineMessages({
  7. toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }
  8. });
  9. const outerStyle = {
  10. marginTop: '8px',
  11. overflow: 'hidden',
  12. width: '100%',
  13. boxSizing: 'border-box',
  14. position: 'relative'
  15. };
  16. const spoilerStyle = {
  17. textAlign: 'center',
  18. height: '100%',
  19. cursor: 'pointer',
  20. display: 'flex',
  21. alignItems: 'center',
  22. justifyContent: 'center',
  23. flexDirection: 'column'
  24. };
  25. const spoilerSpanStyle = {
  26. display: 'block',
  27. fontSize: '14px',
  28. };
  29. const spoilerSubSpanStyle = {
  30. display: 'block',
  31. fontSize: '11px',
  32. fontWeight: '500'
  33. };
  34. const spoilerButtonStyle = {
  35. position: 'absolute',
  36. top: '4px',
  37. left: '4px',
  38. zIndex: '100'
  39. };
  40. const itemStyle = {
  41. boxSizing: 'border-box',
  42. position: 'relative',
  43. float: 'left',
  44. border: 'none',
  45. display: 'block'
  46. };
  47. const thumbStyle = {
  48. display: 'block',
  49. width: '100%',
  50. height: '100%',
  51. textDecoration: 'none',
  52. backgroundSize: 'cover',
  53. cursor: 'zoom-in'
  54. };
  55. const gifvThumbStyle = {
  56. position: 'relative',
  57. zIndex: '1',
  58. width: '100%',
  59. height: '100%',
  60. objectFit: 'cover',
  61. top: '50%',
  62. transform: 'translateY(-50%)',
  63. cursor: 'zoom-in'
  64. };
  65. const Item = React.createClass({
  66. propTypes: {
  67. attachment: ImmutablePropTypes.map.isRequired,
  68. index: React.PropTypes.number.isRequired,
  69. size: React.PropTypes.number.isRequired,
  70. onClick: React.PropTypes.func.isRequired
  71. },
  72. mixins: [PureRenderMixin],
  73. handleClick (e) {
  74. const { index, onClick } = this.props;
  75. if (e.button === 0) {
  76. e.preventDefault();
  77. onClick(index);
  78. }
  79. e.stopPropagation();
  80. },
  81. render () {
  82. const { attachment, index, size } = this.props;
  83. let width = 50;
  84. let height = 100;
  85. let top = 'auto';
  86. let left = 'auto';
  87. let bottom = 'auto';
  88. let right = 'auto';
  89. if (size === 1) {
  90. width = 100;
  91. }
  92. if (size === 4 || (size === 3 && index > 0)) {
  93. height = 50;
  94. }
  95. if (size === 2) {
  96. if (index === 0) {
  97. right = '2px';
  98. } else {
  99. left = '2px';
  100. }
  101. } else if (size === 3) {
  102. if (index === 0) {
  103. right = '2px';
  104. } else if (index > 0) {
  105. left = '2px';
  106. }
  107. if (index === 1) {
  108. bottom = '2px';
  109. } else if (index > 1) {
  110. top = '2px';
  111. }
  112. } else if (size === 4) {
  113. if (index === 0 || index === 2) {
  114. right = '2px';
  115. }
  116. if (index === 1 || index === 3) {
  117. left = '2px';
  118. }
  119. if (index < 2) {
  120. bottom = '2px';
  121. } else {
  122. top = '2px';
  123. }
  124. }
  125. let thumbnail = '';
  126. if (attachment.get('type') === 'image') {
  127. thumbnail = (
  128. <a
  129. href={attachment.get('remote_url') ? attachment.get('remote_url') : attachment.get('url')}
  130. onClick={this.handleClick}
  131. target='_blank'
  132. style={{ background: `url(${attachment.get('preview_url')}) no-repeat center`, ...thumbStyle }}
  133. />
  134. );
  135. } else if (attachment.get('type') === 'gifv') {
  136. thumbnail = (
  137. <video
  138. src={attachment.get('url')}
  139. onClick={this.handleClick}
  140. autoPlay={!isIOS()}
  141. loop={true}
  142. muted={true}
  143. style={gifvThumbStyle}
  144. />
  145. );
  146. }
  147. return (
  148. <div key={attachment.get('id')} style={{ ...itemStyle, left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
  149. {thumbnail}
  150. </div>
  151. );
  152. }
  153. });
  154. const MediaGallery = React.createClass({
  155. getInitialState () {
  156. return {
  157. visible: !this.props.sensitive
  158. };
  159. },
  160. propTypes: {
  161. sensitive: React.PropTypes.bool,
  162. media: ImmutablePropTypes.list.isRequired,
  163. height: React.PropTypes.number.isRequired,
  164. onOpenMedia: React.PropTypes.func.isRequired,
  165. intl: React.PropTypes.object.isRequired
  166. },
  167. mixins: [PureRenderMixin],
  168. handleOpen (e) {
  169. this.setState({ visible: !this.state.visible });
  170. },
  171. handleClick (index) {
  172. this.props.onOpenMedia(this.props.media, index);
  173. },
  174. render () {
  175. const { media, intl, sensitive } = this.props;
  176. let children;
  177. if (!this.state.visible) {
  178. let warning;
  179. if (sensitive) {
  180. warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
  181. } else {
  182. warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
  183. }
  184. children = (
  185. <div role='button' tabIndex='0' style={spoilerStyle} className='media-spoiler' onClick={this.handleOpen}>
  186. <span style={spoilerSpanStyle}>{warning}</span>
  187. <span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
  188. </div>
  189. );
  190. } else {
  191. const size = media.take(4).size;
  192. children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />);
  193. }
  194. return (
  195. <div style={{ ...outerStyle, height: `${this.props.height}px` }}>
  196. <div style={{ ...spoilerButtonStyle, display: !this.state.visible ? 'none' : 'block' }}>
  197. <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
  198. </div>
  199. {children}
  200. </div>
  201. );
  202. }
  203. });
  204. export default injectIntl(MediaGallery);