diff options
12 files changed, 146 insertions, 8 deletions
diff --git a/app/assets/javascripts/components/actions/modal.jsx b/app/assets/javascripts/components/actions/modal.jsx new file mode 100644 index 000000000..89dbc7947 --- /dev/null +++ b/app/assets/javascripts/components/actions/modal.jsx @@ -0,0 +1,15 @@ +export const MEDIA_OPEN = 'MEDIA_OPEN'; +export const MODAL_CLOSE = 'MODAL_CLOSE'; + +export function openMedia(url) { + return { + type: MEDIA_OPEN, + url: url + }; +}; + +export function closeModal() { + return { + type: MODAL_CLOSE + }; +}; diff --git a/app/assets/javascripts/components/components/media_gallery.jsx b/app/assets/javascripts/components/components/media_gallery.jsx index 432de9074..bdb456a08 100644 --- a/app/assets/javascripts/components/components/media_gallery.jsx +++ b/app/assets/javascripts/components/components/media_gallery.jsx @@ -5,11 +5,21 @@ const MediaGallery = React.createClass({ propTypes: { media: ImmutablePropTypes.list.isRequired, - height: React.PropTypes.number.isRequired + height: React.PropTypes.number.isRequired, + onOpenMedia: React.PropTypes.func.isRequired }, mixins: [PureRenderMixin], + handleClick (url, e) { + if (e.button === 0) { + e.preventDefault(); + this.props.onOpenMedia(url); + } + + e.stopPropagation(); + }, + render () { var children = this.props.media.take(4); var size = children.size; @@ -25,7 +35,7 @@ const MediaGallery = React.createClass({ if (size === 1) { width = 100; } - + if (size === 4 || (size === 3 && i > 0)) { height = 50; } @@ -64,7 +74,11 @@ const MediaGallery = React.createClass({ } } - return <a key={attachment.get('id')} href={attachment.get('url')} target='_blank' style={{ boxSizing: 'border-box', position: 'relative', left: left, top: top, right: right, bottom: bottom, float: 'left', textDecoration: 'none', border: 'none', display: 'block', width: `${width}%`, height: `${height}%`, background: `url(${attachment.get('preview_url')}) no-repeat center`, backgroundSize: 'cover', cursor: 'zoom-in' }} />; + 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('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' }} /> + </div> + ); }); return ( diff --git a/app/assets/javascripts/components/components/status.jsx b/app/assets/javascripts/components/components/status.jsx index 8855c6c12..3fdb9a80c 100644 --- a/app/assets/javascripts/components/components/status.jsx +++ b/app/assets/javascripts/components/components/status.jsx @@ -21,6 +21,7 @@ const Status = React.createClass({ onFavourite: React.PropTypes.func, onReblog: React.PropTypes.func, onDelete: React.PropTypes.func, + onOpenMedia: React.PropTypes.func, me: React.PropTypes.number }, @@ -67,7 +68,7 @@ const Status = React.createClass({ if (status.getIn(['media_attachments', 0, 'type']) === 'video') { media = <VideoPlayer media={status.getIn(['media_attachments', 0])} />; } else { - media = <MediaGallery media={status.get('media_attachments')} height={110} />; + media = <MediaGallery media={status.get('media_attachments')} height={110} onOpenMedia={this.props.onOpenMedia} />; } } diff --git a/app/assets/javascripts/components/containers/status_container.jsx b/app/assets/javascripts/components/containers/status_container.jsx index b4d3740d9..0f4b8ee16 100644 --- a/app/assets/javascripts/components/containers/status_container.jsx +++ b/app/assets/javascripts/components/containers/status_container.jsx @@ -12,6 +12,7 @@ import { unfavourite } from '../actions/interactions'; import { deleteStatus } from '../actions/statuses'; +import { openMedia } from '../actions/modal'; const makeMapStateToProps = () => { const getStatus = makeGetStatus(); @@ -52,6 +53,10 @@ const mapDispatchToProps = (dispatch) => ({ onMention (account) { dispatch(mentionCompose(account)); + }, + + onOpenMedia (url) { + dispatch(openMedia(url)); } }); diff --git a/app/assets/javascripts/components/features/status/components/detailed_status.jsx b/app/assets/javascripts/components/features/status/components/detailed_status.jsx index ffa536ae8..9f8e9b6cc 100644 --- a/app/assets/javascripts/components/features/status/components/detailed_status.jsx +++ b/app/assets/javascripts/components/features/status/components/detailed_status.jsx @@ -14,7 +14,8 @@ const DetailedStatus = React.createClass({ }, propTypes: { - status: ImmutablePropTypes.map.isRequired + status: ImmutablePropTypes.map.isRequired, + onOpenMedia: React.PropTypes.func.isRequired }, mixins: [PureRenderMixin], @@ -36,7 +37,7 @@ const DetailedStatus = React.createClass({ if (status.getIn(['media_attachments', 0, 'type']) === 'video') { media = <VideoPlayer media={status.getIn(['media_attachments', 0])} width={317} height={178} />; } else { - media = <MediaGallery media={status.get('media_attachments')} height={300} />; + media = <MediaGallery media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} />; } } diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx index f4ca8ff92..dc29a87c7 100644 --- a/app/assets/javascripts/components/features/status/index.jsx +++ b/app/assets/javascripts/components/features/status/index.jsx @@ -22,6 +22,7 @@ import { import { ScrollContainer } from 'react-router-scroll'; import ColumnBackButton from '../../components/column_back_button'; import StatusContainer from '../../containers/status_container'; +import { openMedia } from '../../actions/modal'; const makeMapStateToProps = () => { const getStatus = makeGetStatus(); @@ -78,6 +79,10 @@ const Status = React.createClass({ this.props.dispatch(mentionCompose(account)); }, + handleOpenMedia (url) { + this.props.dispatch(openMedia(url)); + }, + renderChildren (list) { return list.map(id => <StatusContainer key={id} id={id} />); }, @@ -112,7 +117,7 @@ const Status = React.createClass({ <div style={{ overflowY: 'scroll', flex: '1 1 auto' }} className='scrollable'> {ancestors} - <DetailedStatus status={status} me={me} /> + <DetailedStatus status={status} me={me} onOpenMedia={this.handleOpenMedia} /> <ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} onMention={this.handleMentionClick} /> {descendants} diff --git a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx new file mode 100644 index 000000000..323125e30 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx @@ -0,0 +1,67 @@ +import { connect } from 'react-redux'; +import { SkyLightStateless } from 'react-skylight'; +import { closeModal } from '../../../actions/modal'; + +const mapStateToProps = state => ({ + url: state.getIn(['modal', 'url']), + isVisible: state.getIn(['modal', 'open']) +}); + +const mapDispatchToProps = dispatch => ({ + onCloseClicked () { + dispatch(closeModal()); + }, + + onOverlayClicked () { + dispatch(closeModal()); + } +}); + +const styles = { + overlayStyles: { + + }, + + dialogStyles: { + width: '600px', + color: '#282c37', + fontSize: '16px', + lineHeight: '37px', + marginTop: '-300px', + left: '0', + right: '0', + marginLeft: 'auto', + marginRight: 'auto', + height: 'auto' + }, + + imageStyle: { + display: 'block', + maxWidth: '100%', + height: 'auto', + margin: '0 auto' + } +}; + +const Modal = React.createClass({ + + propTypes: { + url: React.PropTypes.string, + isVisible: React.PropTypes.bool, + onCloseClicked: React.PropTypes.func, + onOverlayClicked: React.PropTypes.func + }, + + render () { + const { url, ...other } = this.props; + + return ( + <SkyLightStateless {...other} dialogStyles={styles.dialogStyles} overlayStyles={styles.overlayStyles}> + <img src={url} style={styles.imageStyle} /> + </SkyLightStateless> + ); + } + +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Modal); diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx index 06a9d2f50..655b1e2ee 100644 --- a/app/assets/javascripts/components/features/ui/index.jsx +++ b/app/assets/javascripts/components/features/ui/index.jsx @@ -7,6 +7,7 @@ import MentionsTimeline from '../mentions_timeline'; import Compose from '../compose'; import MediaQuery from 'react-responsive'; import TabsBar from './components/tabs_bar'; +import ModalContainer from './containers/modal_container'; const UI = React.createClass({ @@ -36,6 +37,7 @@ const UI = React.createClass({ <NotificationsContainer /> <LoadingBarContainer style={{ backgroundColor: '#2b90d9', left: '0', top: '0' }} /> + <ModalContainer /> </div> ); } diff --git a/app/assets/javascripts/components/reducers/index.jsx b/app/assets/javascripts/components/reducers/index.jsx index da9ab1a21..e9256b8ec 100644 --- a/app/assets/javascripts/components/reducers/index.jsx +++ b/app/assets/javascripts/components/reducers/index.jsx @@ -5,6 +5,7 @@ import compose from './compose'; import follow from './follow'; import notifications from './notifications'; import { loadingBarReducer } from 'react-redux-loading-bar'; +import modal from './modal'; export default combineReducers({ timelines, @@ -13,4 +14,5 @@ export default combineReducers({ follow, notifications, loadingBar: loadingBarReducer, + modal, }); diff --git a/app/assets/javascripts/components/reducers/modal.jsx b/app/assets/javascripts/components/reducers/modal.jsx new file mode 100644 index 000000000..b529b6aa8 --- /dev/null +++ b/app/assets/javascripts/components/reducers/modal.jsx @@ -0,0 +1,21 @@ +import { MEDIA_OPEN, MODAL_CLOSE } from '../actions/modal'; +import Immutable from 'immutable'; + +const initialState = Immutable.Map({ + url: '', + open: false +}); + +export default function modal(state = initialState, action) { + switch(action.type) { + case MEDIA_OPEN: + return state.withMutations(map => { + map.set('url', action.url); + map.set('open', true); + }); + case MODAL_CLOSE: + return state.set('open', false); + default: + return state; + } +}; diff --git a/package.json b/package.json index 78560f717..558e56541 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ }, "dependencies": { "react-responsive": "^1.1.5", - "react-router-scroll": "^0.3.2" + "react-router-scroll": "^0.3.2", + "react-skylight": "^0.4.1" } } diff --git a/yarn.lock b/yarn.lock index 6ee239818..766ca3114 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3927,6 +3927,10 @@ react-simple-dropdown@^1.1.4: dependencies: classnames "^2.1.2" +react-skylight: + version "0.4.1" + resolved "https://registry.yarnpkg.com/react-skylight/-/react-skylight-0.4.1.tgz#07d1af6dea0a50a5d8122a786a8ce8bc6bdf2241" + react@^15.3.2: version "15.3.2" resolved "https://registry.yarnpkg.com/react/-/react-15.3.2.tgz#a7bccd2fee8af126b0317e222c28d1d54528d09e" |