diff options
Diffstat (limited to 'app/javascript/flavours/glitch/components')
-rw-r--r-- | app/javascript/flavours/glitch/components/media_gallery.js | 129 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/components/modal_root.js | 84 |
2 files changed, 148 insertions, 65 deletions
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js index 309308d25..925132b07 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.js +++ b/app/javascript/flavours/glitch/components/media_gallery.js @@ -33,10 +33,6 @@ const messages = defineMessages({ class Item extends React.PureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { attachment: ImmutablePropTypes.map.isRequired, standalone: PropTypes.bool, @@ -73,7 +69,7 @@ class Item extends React.PureComponent { handleClick = (e) => { const { index, onClick } = this.props; - if (this.context.router && e.button === 0) { + if (e.button === 0) { e.preventDefault(); onClick(index); } @@ -136,16 +132,21 @@ class Item extends React.PureComponent { let thumbnail = ''; if (attachment.get('type') === 'image') { - const previewUrl = attachment.get('preview_url'); + 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'; const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null; - const sizes = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null; + const sizes = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null; + + 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 = ( <a @@ -154,7 +155,14 @@ class Item extends React.PureComponent { onClick={this.handleClick} target='_blank' > - <img className={letterbox ? 'letterbox' : null} src={previewUrl} srcSet={srcSet} sizes={sizes} alt={attachment.get('description')} title={attachment.get('description')} /> + <img + className={letterbox ? 'letterbox' : null} + src={previewUrl} + srcSet={srcSet} + sizes={sizes} + alt={attachment.get('description')} + title={attachment.get('description')} + style={{ objectPosition: `${x}% ${y}%` }} /> </a> ); } else if (attachment.get('type') === 'gifv') { @@ -225,30 +233,59 @@ export default class MediaGallery extends React.PureComponent { this.props.onOpenMedia(this.props.media, index); } + handleRef = (node) => { + if (node && this.isStandaloneEligible()) { + // offsetWidth triggers a layout, so only calculate when we need to + this.setState({ + width: node.offsetWidth, + }); + } + } + + isStandaloneEligible() { + const { media, standalone } = this.props; + return standalone && media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']); + } + render () { - const { - handleClick, - handleOpen, - } = this; - const { - fullwidth, - intl, - letterbox, - media, - sensitive, - standalone, - } = this.props; - const { visible } = this.state; + const { media, intl, sensitive, letterbox, fullwidth } = this.props; + const { width, visible } = this.state; const size = media.take(4).size; + + let children; + + const style = {}; + + if (this.isStandaloneEligible() && width) { + style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']); + } + + if (!visible) { + let warning = <FormattedMessage {...(sensitive ? messages.warning : messages.hidden)} />; + + children = ( + <button className='media-spoiler' type='button' onClick={this.handleOpen}> + <span className='media-spoiler__warning'>{warning}</span> + <span className='media-spoiler__trigger'><FormattedMessage {...messages.toggle} /></span> + </button> + ); + } else { + if (this.isStandaloneEligible()) { + children = <Item standalone attachment={media.get(0)} onClick={this.handleClick} />; + } else { + children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} letterbox={letterbox} />); + } + } + const computedClass = classNames('media-gallery', `size-${size}`, { 'full-width': fullwidth }); return ( - <div className={computedClass}> + <div className={computedClass} style={style} ref={this.handleRef}> {visible ? ( <div className='sensitive-info'> <IconButton icon='eye' - onClick={handleOpen} + onClick={this.handleOpen} overlay title={intl.formatMessage(messages.toggle_visible)} /> @@ -259,46 +296,8 @@ export default class MediaGallery extends React.PureComponent { ) : null} </div> ) : null} - {function () { - switch (true) { - case !visible: - return ( - <button - className='media-spoiler' - type='button' - onClick={handleOpen} - > - <span className='media-spoiler__warning'> - <FormattedMessage {...(sensitive ? messages.warning : messages.hidden)} /> - </span> - <span className='media-spoiler__trigger'> - <FormattedMessage {...messages.toggle} /> - </span> - </button> - ); - case standalone && media.size === 1 && !!media.getIn([0, 'meta', 'small', 'aspect']): - return ( - <Item - attachment={media.get(0)} - onClick={handleClick} - standalone - /> - ); - default: - return media.take(4).map( - (attachment, i) => ( - <Item - attachment={attachment} - index={i} - key={attachment.get('id')} - letterbox={letterbox} - onClick={handleClick} - size={size} - /> - ) - ); - } - }()} + + {children} </div> ); } diff --git a/app/javascript/flavours/glitch/components/modal_root.js b/app/javascript/flavours/glitch/components/modal_root.js new file mode 100644 index 000000000..789e117c7 --- /dev/null +++ b/app/javascript/flavours/glitch/components/modal_root.js @@ -0,0 +1,84 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export default class ModalRoot extends React.PureComponent { + + static propTypes = { + children: PropTypes.node, + onClose: PropTypes.func.isRequired, + }; + + state = { + revealed: !!this.props.children, + }; + + activeElement = this.state.revealed ? document.activeElement : null; + + handleKeyUp = (e) => { + if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) + && !!this.props.children && !this.props.props.noEsc) { + this.props.onClose(); + } + } + + componentDidMount () { + window.addEventListener('keyup', this.handleKeyUp, false); + } + + componentWillReceiveProps (nextProps) { + if (!!nextProps.children && !this.props.children) { + this.activeElement = document.activeElement; + + this.getSiblings().forEach(sibling => sibling.setAttribute('inert', true)); + } else if (!nextProps.children) { + this.setState({ revealed: false }); + } + } + + componentDidUpdate (prevProps) { + if (!this.props.children && !!prevProps.children) { + this.getSiblings().forEach(sibling => sibling.removeAttribute('inert')); + this.activeElement.focus(); + this.activeElement = null; + } + if (this.props.children) { + requestAnimationFrame(() => { + this.setState({ revealed: true }); + }); + } + } + + componentWillUnmount () { + window.removeEventListener('keyup', this.handleKeyUp); + } + + getSiblings = () => { + return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node); + } + + setRef = ref => { + this.node = ref; + } + + render () { + const { children, onClose } = this.props; + const { revealed } = this.state; + const visible = !!children; + + if (!visible) { + return ( + <div className='modal-root' ref={this.setRef} style={{ opacity: 0 }} /> + ); + } + + return ( + <div className='modal-root' ref={this.setRef} style={{ opacity: revealed ? 1 : 0 }}> + <div style={{ pointerEvents: visible ? 'auto' : 'none' }}> + <div role='presentation' className='modal-root__overlay' onClick={onClose} /> + <div role='dialog' className='modal-root__container'>{children}</div> + </div> + </div> + ); + } + +} |