Add opening images in a modal window
This commit is contained in:
		
							parent
							
								
									f8f40f15da
								
							
						
					
					
						commit
						de50eff6ac
					
				
					 12 changed files with 146 additions and 8 deletions
				
			
		
							
								
								
									
										15
									
								
								app/assets/javascripts/components/actions/modal.jsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/assets/javascripts/components/actions/modal.jsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
|   }; | ||||
| }; | ||||
|  | @ -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 ( | ||||
|  |  | |||
|  | @ -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} />; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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)); | ||||
|   } | ||||
| 
 | ||||
| }); | ||||
|  |  | |||
|  | @ -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} />; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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} | ||||
|  |  | |||
|  | @ -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); | ||||
|  | @ -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> | ||||
|     ); | ||||
|   } | ||||
|  |  | |||
|  | @ -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, | ||||
| }); | ||||
|  |  | |||
							
								
								
									
										21
									
								
								app/assets/javascripts/components/reducers/modal.jsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/assets/javascripts/components/reducers/modal.jsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
|   } | ||||
| }; | ||||
|  | @ -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" | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
		Reference in a new issue