@ -7,6 +7,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { isIOS } from '../is_mobile' ;
import classNames from 'classnames' ;
import { autoPlayGif , displayMedia } from '../initial_state' ;
import { decode } from 'blurhash' ;
const messages = defineMessages ( {
toggle _visible : { id : 'media_gallery.toggle_visible' , defaultMessage : 'Toggle visibility' } ,
@ -21,6 +22,7 @@ class Item extends React.PureComponent {
size : PropTypes . number . isRequired ,
onClick : PropTypes . func . isRequired ,
displayWidth : PropTypes . number ,
visible : PropTypes . bool . isRequired ,
} ;
static defaultProps = {
@ -29,6 +31,10 @@ class Item extends React.PureComponent {
size : 1 ,
} ;
state = {
loaded : false ,
} ;
handleMouseEnter = ( e ) => {
if ( this . hoverToPlay ( ) ) {
e . target . play ( ) ;
@ -62,8 +68,40 @@ class Item extends React.PureComponent {
e . stopPropagation ( ) ;
}
componentDidMount ( ) {
if ( this . props . attachment . get ( 'blurhash' ) ) {
this . _decode ( ) ;
}
}
componentDidUpdate ( prevProps ) {
if ( prevProps . attachment . get ( 'blurhash' ) !== this . props . attachment . get ( 'blurhash' ) && this . props . attachment . get ( 'blurhash' ) ) {
this . _decode ( ) ;
}
}
_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 } ) ;
}
render ( ) {
const { attachment , index , size , standalone , displayWidth } = this . props ;
const { attachment , index , size , standalone , displayWidth , visible } = this . props ;
let width = 50 ;
let height = 100 ;
@ -116,12 +154,20 @@ class Item extends React.PureComponent {
let thumbnail = '' ;
if ( attachment . get ( 'type' ) === 'image' ) {
if ( attachment . get ( 'type' ) === 'unknown' ) {
return (
< div className = { classNames ( 'media-gallery__item' , { standalone } ) } key = { attachment . get ( 'id' ) } style = { { left : left , top : top , right : right , bottom : bottom , width : ` ${ width } % ` , height : ` ${ height } % ` } } >
< a className = 'media-gallery__item-thumbnail' href = { attachment . get ( 'remote_url' ) } target = '_blank' style = { { cursor : 'pointer' } } >
< canvas width = { 32 } height = { 32 } ref = { this . setCanvasRef } className = 'media-gallery__preview' / >
< / a >
< / d i v >
) ;
} else if ( attachment . get ( 'type' ) === 'image' ) {
const previewUrl = attachment . get ( 'preview_url' ) ;
const previewWidth = attachment . getIn ( [ 'meta' , 'small' , 'width' ] ) ;
const originalUrl = attachment . get ( 'url' ) ;
const originalWidth = attachment . getIn ( [ 'meta' , 'original' , 'width' ] ) ;
const originalUrl = attachment . get ( 'url' ) ;
const originalWidth = attachment . getIn ( [ 'meta' , 'original' , 'width' ] ) ;
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number' ;
@ -147,6 +193,7 @@ class Item extends React.PureComponent {
alt = { attachment . get ( 'description' ) }
title = { attachment . get ( 'description' ) }
style = { { objectPosition : ` ${ x } % ${ y } % ` } }
onLoad = { this . handleImageLoad }
/ >
< / a >
) ;
@ -176,7 +223,8 @@ class Item extends React.PureComponent {
return (
< div className = { classNames ( 'media-gallery__item' , { standalone } ) } key = { attachment . get ( 'id' ) } style = { { left : left , top : top , right : right , bottom : bottom , width : ` ${ width } % ` , height : ` ${ height } % ` } } >
{ thumbnail }
< canvas width = { 32 } height = { 32 } ref = { this . setCanvasRef } className = { classNames ( 'media-gallery__preview' , { 'media-gallery__preview--hidden' : visible && this . state . loaded } ) } / >
{ visible && thumbnail }
< / d i v >
) ;
}
@ -225,6 +273,7 @@ class MediaGallery extends React.PureComponent {
if ( node /*&& this.isStandaloneEligible()*/ ) {
// offsetWidth triggers a layout, so only calculate when we need to
if ( this . props . cacheWidth ) this . props . cacheWidth ( node . offsetWidth ) ;
this . setState ( {
width : node . offsetWidth ,
} ) ;
@ -242,7 +291,7 @@ class MediaGallery extends React.PureComponent {
const width = this . state . width || defaultWidth ;
let children ;
let children , spoilerButton ;
const style = { } ;
@ -256,35 +305,28 @@ class MediaGallery extends React.PureComponent {
style . height = height ;
}
if ( ! visible ) {
let warning ;
const size = media . take ( 4 ) . size ;
if ( sensitive ) {
warning = < FormattedMessage id = 'status.sensitive_warning' defaultMessage = 'Sensitive content' / > ;
} else {
warning = < FormattedMessage id = 'status.media_hidden' defaultMessage = 'Media hidden' / > ;
}
if ( this . isStandaloneEligible ( ) ) {
children = < Item standalone onClick = { this . handleClick } attachment = { media . get ( 0 ) } displayWidth = { width } visible = { visible } / > ;
} else {
children = media . take ( 4 ) . map ( ( attachment , i ) => < Item key = { attachment . get ( 'id' ) } onClick = { this . handleClick } attachment = { attachment } index = { i } size = { size } displayWidth = { width } visible = { visible } / > ) ;
}
children = (
< button type = 'button' className = 'media-spoiler' onClick = { this . handleOpen } style = { style } ref = { this . handleRef } >
< span className = 'media-spoiler__warning' > { warning } < / s p a n >
< span className = 'media-spoiler__trigger' > < FormattedMessage id = 'status.sensitive_toggle' defaultMessage = 'Click to view' / > < / s p a n >
if ( visible ) {
spoilerButton = < IconButton title = { intl . formatMessage ( messages . toggle _visible ) } icon = { visible ? 'eye' : 'eye-slash' } overlay onClick = { this . handleOpen } / > ;
} else {
spoilerButton = (
< button type = 'button' onClick = { this . handleOpen } className = 'spoiler-button__overlay' >
< span className = 'spoiler-button__overlay__label' > { sensitive ? < FormattedMessage id = 'status.sensitive_warning' defaultMessage = 'Sensitive content' / > : < FormattedMessage id = 'status.media_hidden' defaultMessage = 'Media hidden' / > } < / s p a n >
< / b u t t o n >
) ;
} else {
const size = media . take ( 4 ) . size ;
if ( this . isStandaloneEligible ( ) ) {
children = < Item standalone onClick = { this . handleClick } attachment = { media . get ( 0 ) } displayWidth = { width } / > ;
} else {
children = media . take ( 4 ) . map ( ( attachment , i ) => < Item key = { attachment . get ( 'id' ) } onClick = { this . handleClick } attachment = { attachment } index = { i } size = { size } displayWidth = { width } / > ) ;
}
}
return (
< div className = 'media-gallery' style = { style } ref = { this . handleRef } >
< div className = { classNames ( 'spoiler-button' , { 'spoiler-button-- visible ': visible } ) } >
< IconButton title = { intl . formatMessage ( messages . toggle _visible ) } icon = { visible ? 'eye' : 'eye-slash' } overlay onClick = { this . handleOpen } / >
< div className = { classNames ( 'spoiler-button' , { 'spoiler-button--minified' : visible } ) } >
{ spoilerButton }
< / d i v >
{ children }