|
|
@ -1,69 +1,141 @@ |
|
|
|
import React from 'react'; |
|
|
|
import PropTypes from 'prop-types'; |
|
|
|
import ImmutablePropTypes from 'react-immutable-proptypes'; |
|
|
|
import ImmutablePureComponent from 'react-immutable-pure-component'; |
|
|
|
import Permalink from 'flavours/glitch/components/permalink'; |
|
|
|
import { displayMedia } from 'flavours/glitch/util/initial_state'; |
|
|
|
import { autoPlayGif, displayMedia } from 'flavours/glitch/util/initial_state'; |
|
|
|
import classNames from 'classnames'; |
|
|
|
import { decode } from 'blurhash'; |
|
|
|
import { isIOS } from 'flavours/glitch/util/is_mobile'; |
|
|
|
|
|
|
|
export default class MediaItem extends ImmutablePureComponent { |
|
|
|
|
|
|
|
static propTypes = { |
|
|
|
media: ImmutablePropTypes.map.isRequired, |
|
|
|
attachment: ImmutablePropTypes.map.isRequired, |
|
|
|
displayWidth: PropTypes.number.isRequired, |
|
|
|
onOpenMedia: PropTypes.func.isRequired, |
|
|
|
}; |
|
|
|
|
|
|
|
state = { |
|
|
|
visible: displayMedia !== 'hide_all' && !this.props.media.getIn(['status', 'sensitive']) || displayMedia === 'show_all', |
|
|
|
visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all', |
|
|
|
loaded: false, |
|
|
|
}; |
|
|
|
|
|
|
|
handleClick = () => { |
|
|
|
if (!this.state.visible) { |
|
|
|
this.setState({ visible: true }); |
|
|
|
return true; |
|
|
|
componentDidMount () { |
|
|
|
if (this.props.attachment.get('blurhash')) { |
|
|
|
this._decode(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
componentDidUpdate (prevProps) { |
|
|
|
if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) { |
|
|
|
this._decode(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
render () { |
|
|
|
const { media } = this.props; |
|
|
|
const { visible } = this.state; |
|
|
|
const status = media.get('status'); |
|
|
|
const focusX = media.getIn(['meta', 'focus', 'x']); |
|
|
|
const focusY = media.getIn(['meta', 'focus', 'y']); |
|
|
|
const x = ((focusX / 2) + .5) * 100; |
|
|
|
const y = ((focusY / -2) + .5) * 100; |
|
|
|
const style = {}; |
|
|
|
|
|
|
|
let label, icon, title; |
|
|
|
|
|
|
|
if (media.get('type') === 'gifv') { |
|
|
|
label = <span className='media-gallery__gifv__label'>GIF</span>; |
|
|
|
_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 }); |
|
|
|
} |
|
|
|
|
|
|
|
handleMouseEnter = e => { |
|
|
|
if (this.hoverToPlay()) { |
|
|
|
e.target.play(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
handleMouseLeave = e => { |
|
|
|
if (this.hoverToPlay()) { |
|
|
|
e.target.pause(); |
|
|
|
e.target.currentTime = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
hoverToPlay () { |
|
|
|
return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1; |
|
|
|
} |
|
|
|
|
|
|
|
handleClick = e => { |
|
|
|
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { |
|
|
|
e.preventDefault(); |
|
|
|
|
|
|
|
if (this.state.visible) { |
|
|
|
this.props.onOpenMedia(this.props.attachment); |
|
|
|
} else { |
|
|
|
this.setState({ visible: true }); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
render () { |
|
|
|
const { attachment, displayWidth } = this.props; |
|
|
|
const { visible, loaded } = this.state; |
|
|
|
|
|
|
|
const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`; |
|
|
|
const height = width; |
|
|
|
const status = attachment.get('status'); |
|
|
|
|
|
|
|
let thumbnail = ''; |
|
|
|
|
|
|
|
if (attachment.get('type') === 'unknown') { |
|
|
|
// Skip
|
|
|
|
} else if (attachment.get('type') === 'image') { |
|
|
|
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0; |
|
|
|
const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0; |
|
|
|
const x = ((focusX / 2) + .5) * 100; |
|
|
|
const y = ((focusY / -2) + .5) * 100; |
|
|
|
|
|
|
|
thumbnail = ( |
|
|
|
<img |
|
|
|
src={attachment.get('preview_url')} |
|
|
|
alt={attachment.get('description')} |
|
|
|
title={attachment.get('description')} |
|
|
|
style={{ objectPosition: `${x}% ${y}%` }} |
|
|
|
onLoad={this.handleImageLoad} |
|
|
|
/> |
|
|
|
); |
|
|
|
} else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) { |
|
|
|
const autoPlay = !isIOS() && autoPlayGif; |
|
|
|
|
|
|
|
if (visible) { |
|
|
|
style.backgroundImage = `url(${media.get('preview_url')})`; |
|
|
|
style.backgroundPosition = `${x}% ${y}%`; |
|
|
|
title = media.get('description'); |
|
|
|
} else { |
|
|
|
icon = ( |
|
|
|
<span className='account-gallery__item__icons'> |
|
|
|
<i className='fa fa-eye-slash' /> |
|
|
|
</span> |
|
|
|
thumbnail = ( |
|
|
|
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}> |
|
|
|
<video |
|
|
|
className='media-gallery__item-gifv-thumbnail' |
|
|
|
aria-label={attachment.get('description')} |
|
|
|
title={attachment.get('description')} |
|
|
|
role='application' |
|
|
|
src={attachment.get('url')} |
|
|
|
onMouseEnter={this.handleMouseEnter} |
|
|
|
onMouseLeave={this.handleMouseLeave} |
|
|
|
autoPlay={autoPlay} |
|
|
|
loop |
|
|
|
muted |
|
|
|
/> |
|
|
|
<span className='media-gallery__gifv__label'>GIF</span> |
|
|
|
</div> |
|
|
|
); |
|
|
|
title = status.get('spoiler_text') || media.get('description'); |
|
|
|
} |
|
|
|
|
|
|
|
return ( |
|
|
|
<div className='account-gallery__item'> |
|
|
|
<Permalink |
|
|
|
to={`/statuses/${status.get('id')}`} |
|
|
|
href={status.get('url')} |
|
|
|
style={style} |
|
|
|
title={title} |
|
|
|
onInterceptClick={this.handleClick} |
|
|
|
> |
|
|
|
{icon} |
|
|
|
{label} |
|
|
|
</Permalink> |
|
|
|
<div className='account-gallery__item' style={{ width, height }}> |
|
|
|
<a className='media-gallery__item-thumbnail' href={status.get('url')} target='_blank' style={{ cursor: 'pointer' }} onClick={this.handleClick}> |
|
|
|
<canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })} /> |
|
|
|
{visible && thumbnail} |
|
|
|
</a> |
|
|
|
</div> |
|
|
|
); |
|
|
|
} |
|
|
|