forked from fedi/mastodon
Add next/previous navigation in modal for media attachments
This commit is contained in:
parent
21972bb398
commit
44fad0160f
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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} >
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue