diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2017-02-05 02:48:11 +0100 |
---|---|---|
committer | Eugen Rochko <eugen@zeonfederated.com> | 2017-02-05 02:48:11 +0100 |
commit | 44fad0160f4b390a97fae8bb23cdc98ccf5649d9 (patch) | |
tree | ef09713b0e616ba9054740f983643554be6b1559 /app/assets/javascripts/components | |
parent | 21972bb39886942d6946757ff8c8f9fe329bb20f (diff) |
Add next/previous navigation in modal for media attachments
Diffstat (limited to 'app/assets/javascripts/components')
7 files changed, 132 insertions, 21 deletions
diff --git a/app/assets/javascripts/components/actions/modal.jsx b/app/assets/javascripts/components/actions/modal.jsx index 89dbc7947..d19218c48 100644 --- a/app/assets/javascripts/components/actions/modal.jsx +++ b/app/assets/javascripts/components/actions/modal.jsx @@ -1,10 +1,14 @@ export const MEDIA_OPEN = 'MEDIA_OPEN'; export const MODAL_CLOSE = 'MODAL_CLOSE'; -export function openMedia(url) { +export const MODAL_INDEX_DECREASE = 'MODAL_INDEX_DECREASE'; +export const MODAL_INDEX_INCREASE = 'MODAL_INDEX_INCREASE'; + +export function openMedia(media, index) { return { type: MEDIA_OPEN, - url: url + media, + index }; }; @@ -13,3 +17,15 @@ export function closeModal() { type: MODAL_CLOSE }; }; + +export function decreaseIndexInModal() { + return { + type: MODAL_INDEX_DECREASE + }; +}; + +export function increaseIndexInModal() { + return { + type: MODAL_INDEX_INCREASE + }; +}; diff --git a/app/assets/javascripts/components/components/lightbox.jsx b/app/assets/javascripts/components/components/lightbox.jsx index 1e3a88955..646484539 100644 --- a/app/assets/javascripts/components/components/lightbox.jsx +++ b/app/assets/javascripts/components/components/lightbox.jsx @@ -56,6 +56,10 @@ const Lightbox = React.createClass({ window.removeEventListener('keyup', this._listener); }, + stopPropagation (e) { + e.stopPropagation(); + }, + render () { const { intl, isVisible, onOverlayClicked, onCloseClicked, children } = this.props; @@ -63,7 +67,7 @@ const Lightbox = React.createClass({ <Motion defaultStyle={{ backgroundOpacity: 0, opacity: 0, y: -400 }} style={{ backgroundOpacity: spring(isVisible ? 50 : 0), opacity: isVisible ? spring(200) : 0, y: spring(isVisible ? 0 : -400, { stiffness: 150, damping: 12 }) }}> {({ backgroundOpacity, opacity, y }) => <div className='lightbox' style={{...overlayStyle, background: `rgba(0, 0, 0, ${backgroundOpacity / 100})`, display: Math.floor(backgroundOpacity) === 0 ? 'none' : 'flex'}} onClick={onOverlayClicked}> - <div style={{...dialogStyle, transform: `translateY(${y}px)`, opacity: opacity / 100 }}> + <div style={{...dialogStyle, transform: `translateY(${y}px)`, opacity: opacity / 100 }} onClick={this.stopPropagation}> <IconButton title={intl.formatMessage({ id: 'lightbox.close', defaultMessage: 'Close' })} icon='times' onClick={onCloseClicked} size={16} style={closeStyle} /> {children} </div> diff --git a/app/assets/javascripts/components/components/media_gallery.jsx b/app/assets/javascripts/components/components/media_gallery.jsx index 7e92abe2d..a13448d0b 100644 --- a/app/assets/javascripts/components/components/media_gallery.jsx +++ b/app/assets/javascripts/components/components/media_gallery.jsx @@ -57,15 +57,16 @@ const MediaGallery = React.createClass({ sensitive: React.PropTypes.bool, media: ImmutablePropTypes.list.isRequired, height: React.PropTypes.number.isRequired, - onOpenMedia: React.PropTypes.func.isRequired + onOpenMedia: React.PropTypes.func.isRequired, + intl: React.PropTypes.object.isRequired }, mixins: [PureRenderMixin], - handleClick (url, e) { + handleClick (index, e) { if (e.button === 0) { e.preventDefault(); - this.props.onOpenMedia(url); + this.props.onOpenMedia(this.props.media, index); } e.stopPropagation(); @@ -151,12 +152,12 @@ const MediaGallery = React.createClass({ return ( <div key={attachment.get('id')} style={{ boxSizing: 'border-box', position: 'relative', left: left, top: top, right: right, bottom: bottom, float: 'left', border: 'none', display: 'block', width: `${width}%`, height: `${height}%` }}> - <a href={attachment.get('remote_url') ? attachment.get('remote_url') : attachment.get('url')} onClick={this.handleClick.bind(this, attachment.get('url'))} target='_blank' style={{ display: 'block', width: '100%', height: '100%', background: `url(${attachment.get('preview_url')}) no-repeat center`, textDecoration: 'none', backgroundSize: 'cover', cursor: 'zoom-in' }} /> + <a href={attachment.get('remote_url') ? attachment.get('remote_url') : attachment.get('url')} onClick={this.handleClick.bind(this, i)} target='_blank' style={{ display: 'block', width: '100%', height: '100%', background: `url(${attachment.get('preview_url')}) no-repeat center`, textDecoration: 'none', backgroundSize: 'cover', cursor: 'zoom-in' }} /> </div> ); }); } - + return ( <div style={{ ...outerStyle, height: `${this.props.height}px` }}> <div style={spoilerButtonStyle} > diff --git a/app/assets/javascripts/components/containers/status_container.jsx b/app/assets/javascripts/components/containers/status_container.jsx index 1704a8cc2..f5fb09d52 100644 --- a/app/assets/javascripts/components/containers/status_container.jsx +++ b/app/assets/javascripts/components/containers/status_container.jsx @@ -91,8 +91,8 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(mentionCompose(account, router)); }, - onOpenMedia (url) { - dispatch(openMedia(url)); + onOpenMedia (media, index) { + dispatch(openMedia(media, index)); }, onBlock (account) { diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx index 993c649d2..894fa3176 100644 --- a/app/assets/javascripts/components/features/status/index.jsx +++ b/app/assets/javascripts/components/features/status/index.jsx @@ -84,8 +84,8 @@ const Status = React.createClass({ this.props.dispatch(mentionCompose(account, router)); }, - handleOpenMedia (url) { - this.props.dispatch(openMedia(url)); + handleOpenMedia (media, index) { + this.props.dispatch(openMedia(media, index)); }, renderChildren (list) { diff --git a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx index 53d162462..0ffbfe8b3 100644 --- a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx +++ b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx @@ -1,12 +1,18 @@ import { connect } from 'react-redux'; -import { closeModal } from '../../../actions/modal'; +import { + closeModal, + decreaseIndexInModal, + increaseIndexInModal +} from '../../../actions/modal'; import Lightbox from '../../../components/lightbox'; import ImageLoader from 'react-imageloader'; import LoadingIndicator from '../../../components/loading_indicator'; import PureRenderMixin from 'react-addons-pure-render-mixin'; +import ImmutablePropTypes from 'react-immutable-proptypes'; const mapStateToProps = state => ({ - url: state.getIn(['modal', 'url']), + media: state.getIn(['modal', 'media']), + index: state.getIn(['modal', 'index']), isVisible: state.getIn(['modal', 'open']) }); @@ -17,6 +23,14 @@ const mapDispatchToProps = dispatch => ({ onOverlayClicked () { dispatch(closeModal()); + }, + + onNextClicked () { + dispatch(increaseIndexInModal()); + }, + + onPrevClicked () { + dispatch(decreaseIndexInModal()); } }); @@ -38,27 +52,92 @@ const preloader = () => ( </div> ); +const leftNavStyle = { + position: 'absolute', + background: 'rgba(0, 0, 0, 0.5)', + padding: '30px 15px', + cursor: 'pointer', + color: '#fff', + fontSize: '24px', + top: '0', + left: '-61px', + boxSizing: 'border-box', + height: '100%', + display: 'flex', + alignItems: 'center' +}; + +const rightNavStyle = { + position: 'absolute', + background: 'rgba(0, 0, 0, 0.5)', + padding: '30px 15px', + cursor: 'pointer', + color: '#fff', + fontSize: '24px', + top: '0', + right: '-61px', + boxSizing: 'border-box', + height: '100%', + display: 'flex', + alignItems: 'center' +}; + const Modal = React.createClass({ propTypes: { - url: React.PropTypes.string, + media: ImmutablePropTypes.list, + index: React.PropTypes.number.isRequired, isVisible: React.PropTypes.bool, onCloseClicked: React.PropTypes.func, - onOverlayClicked: React.PropTypes.func + onOverlayClicked: React.PropTypes.func, + onNextClicked: React.PropTypes.func, + onPrevClicked: React.PropTypes.func }, mixins: [PureRenderMixin], + handleNextClick () { + this.props.onNextClicked(); + }, + + handlePrevClick () { + this.props.onPrevClicked(); + }, + render () { - const { url, ...other } = this.props; + const { media, index, ...other } = this.props; + + if (!media) { + return null; + } + + const url = media.get(index).get('url'); + const hasLeft = index > 0; + const hasRight = index + 1 < media.size; + + let leftNav, rightNav; + + leftNav = rightNav = ''; + + if (hasLeft) { + leftNav = <div style={leftNavStyle} onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>; + } + + if (hasRight) { + rightNav = <div style={rightNavStyle} onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>; + } return ( <Lightbox {...other}> + {leftNav} + <ImageLoader src={url} preloader={preloader} imgProps={{ style: imageStyle }} /> + + {rightNav} </Lightbox> ); } diff --git a/app/assets/javascripts/components/reducers/modal.jsx b/app/assets/javascripts/components/reducers/modal.jsx index ac53ea210..07da65771 100644 --- a/app/assets/javascripts/components/reducers/modal.jsx +++ b/app/assets/javascripts/components/reducers/modal.jsx @@ -1,8 +1,14 @@ -import { MEDIA_OPEN, MODAL_CLOSE } from '../actions/modal'; -import Immutable from 'immutable'; +import { + MEDIA_OPEN, + MODAL_CLOSE, + MODAL_INDEX_DECREASE, + MODAL_INDEX_INCREASE +} from '../actions/modal'; +import Immutable from 'immutable'; const initialState = Immutable.Map({ - url: '', + media: null, + index: 0, open: false }); @@ -10,11 +16,16 @@ export default function modal(state = initialState, action) { switch(action.type) { case MEDIA_OPEN: return state.withMutations(map => { - map.set('url', action.url); + map.set('media', action.media); + map.set('index', action.index); map.set('open', true); }); case MODAL_CLOSE: return state.set('open', false); + case MODAL_INDEX_DECREASE: + return state.update('index', index => Math.max(index - 1, 0)); + case MODAL_INDEX_INCREASE: + return state.update('index', index => Math.min(index + 1, state.get('media').size - 1)); default: return state; } |