about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2016-10-24 18:07:40 +0200
committerEugen Rochko <eugen@zeonfederated.com>2016-10-24 18:08:23 +0200
commitde50eff6acda8b28940cff9b955cfbded3c68b58 (patch)
tree314686b14b18c144de9740c54c2c672dd02d85ed
parentf8f40f15dafca65dc07d5c5c19fb9a9dc3473dd6 (diff)
Add opening images in a modal window
-rw-r--r--app/assets/javascripts/components/actions/modal.jsx15
-rw-r--r--app/assets/javascripts/components/components/media_gallery.jsx20
-rw-r--r--app/assets/javascripts/components/components/status.jsx3
-rw-r--r--app/assets/javascripts/components/containers/status_container.jsx5
-rw-r--r--app/assets/javascripts/components/features/status/components/detailed_status.jsx5
-rw-r--r--app/assets/javascripts/components/features/status/index.jsx7
-rw-r--r--app/assets/javascripts/components/features/ui/containers/modal_container.jsx67
-rw-r--r--app/assets/javascripts/components/features/ui/index.jsx2
-rw-r--r--app/assets/javascripts/components/reducers/index.jsx2
-rw-r--r--app/assets/javascripts/components/reducers/modal.jsx21
-rw-r--r--package.json3
-rw-r--r--yarn.lock4
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"