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