From 44fad0160f4b390a97fae8bb23cdc98ccf5649d9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 5 Feb 2017 02:48:11 +0100 Subject: [PATCH] Add next/previous navigation in modal for media attachments --- .../javascripts/components/actions/modal.jsx | 20 ++++- .../components/components/lightbox.jsx | 6 +- .../components/components/media_gallery.jsx | 11 +-- .../containers/status_container.jsx | 4 +- .../components/features/status/index.jsx | 4 +- .../ui/containers/modal_container.jsx | 89 +++++++++++++++++-- .../javascripts/components/reducers/modal.jsx | 19 +++- 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 89dbc79471..d19218c485 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 1e3a889551..6464845395 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({ {({ backgroundOpacity, opacity, y }) =>
-
+
{children}
diff --git a/app/assets/javascripts/components/components/media_gallery.jsx b/app/assets/javascripts/components/components/media_gallery.jsx index 7e92abe2d2..a13448d0b4 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 (
- +
); }); } - + return (
diff --git a/app/assets/javascripts/components/containers/status_container.jsx b/app/assets/javascripts/components/containers/status_container.jsx index 1704a8cc22..f5fb09d522 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 993c649d22..894fa31766 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 53d1624629..0ffbfe8b30 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 = () => (
); +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 =
; + } + + if (hasRight) { + rightNav =
; + } return ( + {leftNav} + + + {rightNav} ); } diff --git a/app/assets/javascripts/components/reducers/modal.jsx b/app/assets/javascripts/components/reducers/modal.jsx index ac53ea2102..07da65771d 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; }