forked from cybrespace/mastodon
		
	Made modal system more generic
This commit is contained in:
		
							parent
							
								
									13dfd8d109
								
							
						
					
					
						commit
						60ebfa182f
					
				
					 10 changed files with 285 additions and 290 deletions
				
			
		| 
						 | 
					@ -1,14 +1,11 @@
 | 
				
			||||||
export const MEDIA_OPEN  = 'MEDIA_OPEN';
 | 
					export const MODAL_OPEN  = 'MODAL_OPEN';
 | 
				
			||||||
export const MODAL_CLOSE = 'MODAL_CLOSE';
 | 
					export const MODAL_CLOSE = 'MODAL_CLOSE';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const MODAL_INDEX_DECREASE = 'MODAL_INDEX_DECREASE';
 | 
					export function openModal(type, props) {
 | 
				
			||||||
export const MODAL_INDEX_INCREASE = 'MODAL_INDEX_INCREASE';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function openMedia(media, index) {
 | 
					 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    type: MEDIA_OPEN,
 | 
					    type: MODAL_OPEN,
 | 
				
			||||||
    media,
 | 
					    modalType: type,
 | 
				
			||||||
    index
 | 
					    modalProps: props
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,15 +14,3 @@ export function closeModal() {
 | 
				
			||||||
    type: MODAL_CLOSE
 | 
					    type: MODAL_CLOSE
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export function decreaseIndexInModal() {
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    type: MODAL_INDEX_DECREASE
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function increaseIndexInModal() {
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    type: MODAL_INDEX_INCREASE
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,82 +0,0 @@
 | 
				
			||||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
 | 
					 | 
				
			||||||
import IconButton from './icon_button';
 | 
					 | 
				
			||||||
import { Motion, spring } from 'react-motion';
 | 
					 | 
				
			||||||
import { injectIntl } from 'react-intl';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const overlayStyle = {
 | 
					 | 
				
			||||||
  position: 'fixed',
 | 
					 | 
				
			||||||
  top: '0',
 | 
					 | 
				
			||||||
  left: '0',
 | 
					 | 
				
			||||||
  width: '100%',
 | 
					 | 
				
			||||||
  height: '100%',
 | 
					 | 
				
			||||||
  background: 'rgba(0, 0, 0, 0.5)',
 | 
					 | 
				
			||||||
  display: 'flex',
 | 
					 | 
				
			||||||
  justifyContent: 'center',
 | 
					 | 
				
			||||||
  alignContent: 'center',
 | 
					 | 
				
			||||||
  flexDirection: 'row',
 | 
					 | 
				
			||||||
  zIndex: '9999'
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const dialogStyle = {
 | 
					 | 
				
			||||||
  color: '#282c37',
 | 
					 | 
				
			||||||
  boxShadow: '0 0 30px rgba(0, 0, 0, 0.8)',
 | 
					 | 
				
			||||||
  margin: 'auto',
 | 
					 | 
				
			||||||
  position: 'relative'
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const closeStyle = {
 | 
					 | 
				
			||||||
  position: 'absolute',
 | 
					 | 
				
			||||||
  top: '4px',
 | 
					 | 
				
			||||||
  right: '4px'
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Lightbox = React.createClass({
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  propTypes: {
 | 
					 | 
				
			||||||
    isVisible: React.PropTypes.bool,
 | 
					 | 
				
			||||||
    onOverlayClicked: React.PropTypes.func,
 | 
					 | 
				
			||||||
    onCloseClicked: React.PropTypes.func,
 | 
					 | 
				
			||||||
    intl: React.PropTypes.object.isRequired,
 | 
					 | 
				
			||||||
    children: React.PropTypes.node
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  mixins: [PureRenderMixin],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  componentDidMount () {
 | 
					 | 
				
			||||||
    this._listener = e => {
 | 
					 | 
				
			||||||
      if (this.props.isVisible && e.key === 'Escape') {
 | 
					 | 
				
			||||||
        this.props.onCloseClicked();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    window.addEventListener('keyup', this._listener);
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  componentWillUnmount () {
 | 
					 | 
				
			||||||
    window.removeEventListener('keyup', this._listener);
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  stopPropagation (e) {
 | 
					 | 
				
			||||||
    e.stopPropagation();
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  render () {
 | 
					 | 
				
			||||||
    const { intl, isVisible, onOverlayClicked, onCloseClicked, children } = this.props;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <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', pointerEvents: !isVisible ? 'none' : 'auto'}} onClick={onOverlayClicked}>
 | 
					 | 
				
			||||||
            <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>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      </Motion>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default injectIntl(Lightbox);
 | 
					 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,7 @@ import {
 | 
				
			||||||
} from '../actions/accounts';
 | 
					} from '../actions/accounts';
 | 
				
			||||||
import { deleteStatus } from '../actions/statuses';
 | 
					import { deleteStatus } from '../actions/statuses';
 | 
				
			||||||
import { initReport } from '../actions/reports';
 | 
					import { initReport } from '../actions/reports';
 | 
				
			||||||
import { openMedia } from '../actions/modal';
 | 
					import { openModal } from '../actions/modal';
 | 
				
			||||||
import { createSelector } from 'reselect'
 | 
					import { createSelector } from 'reselect'
 | 
				
			||||||
import { isMobile } from '../is_mobile'
 | 
					import { isMobile } from '../is_mobile'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,7 +63,7 @@ const mapDispatchToProps = (dispatch) => ({
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onOpenMedia (media, index) {
 | 
					  onOpenMedia (media, index) {
 | 
				
			||||||
    dispatch(openMedia(media, index));
 | 
					    dispatch(openModal('MEDIA', { media, index }));
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onBlock (account) {
 | 
					  onBlock (account) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@ import {
 | 
				
			||||||
import { ScrollContainer } from 'react-router-scroll';
 | 
					import { ScrollContainer } from 'react-router-scroll';
 | 
				
			||||||
import ColumnBackButton from '../../components/column_back_button';
 | 
					import ColumnBackButton from '../../components/column_back_button';
 | 
				
			||||||
import StatusContainer from '../../containers/status_container';
 | 
					import StatusContainer from '../../containers/status_container';
 | 
				
			||||||
import { openMedia } from '../../actions/modal';
 | 
					import { openModal } from '../../actions/modal';
 | 
				
			||||||
import { isMobile } from '../../is_mobile'
 | 
					import { isMobile } from '../../is_mobile'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const makeMapStateToProps = () => {
 | 
					const makeMapStateToProps = () => {
 | 
				
			||||||
| 
						 | 
					@ -99,7 +99,7 @@ const Status = React.createClass({
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleOpenMedia (media, index) {
 | 
					  handleOpenMedia (media, index) {
 | 
				
			||||||
    this.props.dispatch(openMedia(media, index));
 | 
					    this.props.dispatch(openModal('MEDIA', { media, index }));
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleReport (status) {
 | 
					  handleReport (status) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,134 @@
 | 
				
			||||||
 | 
					import Lightbox from '../../../components/lightbox';
 | 
				
			||||||
 | 
					import LoadingIndicator from '../../../components/loading_indicator';
 | 
				
			||||||
 | 
					import PureRenderMixin from 'react-addons-pure-render-mixin';
 | 
				
			||||||
 | 
					import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
				
			||||||
 | 
					import ExtendedVideoPlayer from '../../../components/extended_video_player';
 | 
				
			||||||
 | 
					import ImageLoader from 'react-imageloader';
 | 
				
			||||||
 | 
					import { defineMessages, injectIntl } from 'react-intl';
 | 
				
			||||||
 | 
					import IconButton from '../../../components/icon_button';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const messages = defineMessages({
 | 
				
			||||||
 | 
					  close: { id: 'lightbox.close', defaultMessage: 'Close' }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const leftNavStyle = {
 | 
				
			||||||
 | 
					  position: 'absolute',
 | 
				
			||||||
 | 
					  background: 'rgba(0, 0, 0, 0.5)',
 | 
				
			||||||
 | 
					  padding: '30px 15px',
 | 
				
			||||||
 | 
					  cursor: 'pointer',
 | 
				
			||||||
 | 
					  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',
 | 
				
			||||||
 | 
					  fontSize: '24px',
 | 
				
			||||||
 | 
					  top: '0',
 | 
				
			||||||
 | 
					  right: '-61px',
 | 
				
			||||||
 | 
					  boxSizing: 'border-box',
 | 
				
			||||||
 | 
					  height: '100%',
 | 
				
			||||||
 | 
					  display: 'flex',
 | 
				
			||||||
 | 
					  alignItems: 'center'
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const closeStyle = {
 | 
				
			||||||
 | 
					  position: 'absolute',
 | 
				
			||||||
 | 
					  top: '4px',
 | 
				
			||||||
 | 
					  right: '4px'
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MediaModal = React.createClass({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  propTypes: {
 | 
				
			||||||
 | 
					    media: ImmutablePropTypes.list.isRequired,
 | 
				
			||||||
 | 
					    index: React.PropTypes.number.isRequired,
 | 
				
			||||||
 | 
					    onClose: React.PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					    intl: React.PropTypes.object.isRequired
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getInitialState () {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      index: null
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  mixins: [PureRenderMixin],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleNextClick () {
 | 
				
			||||||
 | 
					    this.setState({ index: (this.getIndex() + 1) % this.props.media.size});
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handlePrevClick () {
 | 
				
			||||||
 | 
					    this.setState({ index: (this.getIndex() - 1) % this.props.media.size});
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleKeyUp (e) {
 | 
				
			||||||
 | 
					    switch(e.key) {
 | 
				
			||||||
 | 
					    case 'ArrowLeft':
 | 
				
			||||||
 | 
					      this.handlePrevClick();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case 'ArrowRight':
 | 
				
			||||||
 | 
					      this.handleNextClick();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  componentDidMount () {
 | 
				
			||||||
 | 
					    window.addEventListener('keyup', this.handleKeyUp, false);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  componentWillUnmount () {
 | 
				
			||||||
 | 
					    window.removeEventListener('keyup', this.handleKeyUp);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getIndex () {
 | 
				
			||||||
 | 
					    return this.state.index !== null ? this.state.index : this.props.index;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  render () {
 | 
				
			||||||
 | 
					    const { media, intl, onClose } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const index = this.getIndex();
 | 
				
			||||||
 | 
					    const attachment = media.get(index);
 | 
				
			||||||
 | 
					    const url = attachment.get('url');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let leftNav, rightNav, content;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    leftNav = rightNav = content = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (media.size > 1) {
 | 
				
			||||||
 | 
					      leftNav  = <div style={leftNavStyle} className='modal-container__nav' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>;
 | 
				
			||||||
 | 
					      rightNav = <div style={rightNavStyle} className='modal-container__nav' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (attachment.get('type') === 'image') {
 | 
				
			||||||
 | 
					      content = <ImageLoader src={url} imgProps={{ style: { display: 'block' } }} />;
 | 
				
			||||||
 | 
					    } else if (attachment.get('type') === 'gifv') {
 | 
				
			||||||
 | 
					      content = <ExtendedVideoPlayer src={url} />;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <div className='modal-root__modal media-modal'>
 | 
				
			||||||
 | 
					        {leftNav}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					          <IconButton title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} style={closeStyle} />
 | 
				
			||||||
 | 
					          {content}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {rightNav}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default injectIntl(MediaModal);
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,80 @@
 | 
				
			||||||
 | 
					import PureRenderMixin from 'react-addons-pure-render-mixin';
 | 
				
			||||||
 | 
					import MediaModal from './media_modal';
 | 
				
			||||||
 | 
					import { TransitionMotion, spring } from 'react-motion';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MODAL_COMPONENTS = {
 | 
				
			||||||
 | 
					  'MEDIA': MediaModal
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ModalRoot = React.createClass({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  propTypes: {
 | 
				
			||||||
 | 
					    type: React.PropTypes.string,
 | 
				
			||||||
 | 
					    props: React.PropTypes.object,
 | 
				
			||||||
 | 
					    onClose: React.PropTypes.func.isRequired
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  mixins: [PureRenderMixin],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleKeyUp (e) {
 | 
				
			||||||
 | 
					    if (e.key === 'Escape' && !!this.props.type) {
 | 
				
			||||||
 | 
					      this.props.onClose();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  componentDidMount () {
 | 
				
			||||||
 | 
					    window.addEventListener('keyup', this.handleKeyUp, false);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  componentWillUnmount () {
 | 
				
			||||||
 | 
					    window.removeEventListener('keyup', this.handleKeyUp);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  willEnter () {
 | 
				
			||||||
 | 
					    return { opacity: 0, scale: 0.98 };
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  willLeave () {
 | 
				
			||||||
 | 
					    return { opacity: spring(0), scale: spring(0.98) };
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  render () {
 | 
				
			||||||
 | 
					    const { type, props, onClose } = this.props;
 | 
				
			||||||
 | 
					    const items = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!!type) {
 | 
				
			||||||
 | 
					      items.push({
 | 
				
			||||||
 | 
					        key: type,
 | 
				
			||||||
 | 
					        data: { type, props },
 | 
				
			||||||
 | 
					        style: { opacity: spring(1), scale: spring(1, { stiffness: 120, damping: 14 }) }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <TransitionMotion
 | 
				
			||||||
 | 
					        styles={items}
 | 
				
			||||||
 | 
					        willEnter={this.willEnter}
 | 
				
			||||||
 | 
					        willLeave={this.willLeave}>
 | 
				
			||||||
 | 
					        {interpolatedStyles =>
 | 
				
			||||||
 | 
					          <div className='modal-root'>
 | 
				
			||||||
 | 
					            {interpolatedStyles.map(({ key, data: { type, props }, style }) => {
 | 
				
			||||||
 | 
					              const SpecificComponent = MODAL_COMPONENTS[type];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              return (
 | 
				
			||||||
 | 
					                <div key={key}>
 | 
				
			||||||
 | 
					                  <div className='modal-root__overlay' style={{ opacity: style.opacity, transform: `translateZ(0px)` }} onClick={onClose} />
 | 
				
			||||||
 | 
					                  <div className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}>
 | 
				
			||||||
 | 
					                    <SpecificComponent {...props} onClose={onClose} />
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            })}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      </TransitionMotion>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ModalRoot;
 | 
				
			||||||
| 
						 | 
					@ -1,170 +1,16 @@
 | 
				
			||||||
import { connect } from 'react-redux';
 | 
					import { connect } from 'react-redux';
 | 
				
			||||||
import {
 | 
					import { closeModal } from '../../../actions/modal';
 | 
				
			||||||
  closeModal,
 | 
					import ModalRoot from '../components/modal_root';
 | 
				
			||||||
  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';
 | 
					 | 
				
			||||||
import ExtendedVideoPlayer from '../../../components/extended_video_player';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mapStateToProps = state => ({
 | 
					const mapStateToProps = state => ({
 | 
				
			||||||
  media: state.getIn(['modal', 'media']),
 | 
					  type: state.get('modal').modalType,
 | 
				
			||||||
  index: state.getIn(['modal', 'index']),
 | 
					  props: state.get('modal').modalProps
 | 
				
			||||||
  isVisible: state.getIn(['modal', 'open'])
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mapDispatchToProps = dispatch => ({
 | 
					const mapDispatchToProps = dispatch => ({
 | 
				
			||||||
  onCloseClicked () {
 | 
					  onClose () {
 | 
				
			||||||
    dispatch(closeModal());
 | 
					    dispatch(closeModal());
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					 | 
				
			||||||
  onOverlayClicked () {
 | 
					 | 
				
			||||||
    dispatch(closeModal());
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onNextClicked () {
 | 
					 | 
				
			||||||
    dispatch(increaseIndexInModal());
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onPrevClicked () {
 | 
					 | 
				
			||||||
    dispatch(decreaseIndexInModal());
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const imageStyle = {
 | 
					export default connect(mapStateToProps, mapDispatchToProps)(ModalRoot);
 | 
				
			||||||
  display: 'block',
 | 
					 | 
				
			||||||
  maxWidth: '80vw',
 | 
					 | 
				
			||||||
  maxHeight: '80vh'
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const loadingStyle = {
 | 
					 | 
				
			||||||
  width: '400px',
 | 
					 | 
				
			||||||
  paddingBottom: '120px'
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const preloader = () => (
 | 
					 | 
				
			||||||
  <div className='modal-container--preloader' style={loadingStyle}>
 | 
					 | 
				
			||||||
    <LoadingIndicator />
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const leftNavStyle = {
 | 
					 | 
				
			||||||
  position: 'absolute',
 | 
					 | 
				
			||||||
  background: 'rgba(0, 0, 0, 0.5)',
 | 
					 | 
				
			||||||
  padding: '30px 15px',
 | 
					 | 
				
			||||||
  cursor: 'pointer',
 | 
					 | 
				
			||||||
  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',
 | 
					 | 
				
			||||||
  fontSize: '24px',
 | 
					 | 
				
			||||||
  top: '0',
 | 
					 | 
				
			||||||
  right: '-61px',
 | 
					 | 
				
			||||||
  boxSizing: 'border-box',
 | 
					 | 
				
			||||||
  height: '100%',
 | 
					 | 
				
			||||||
  display: 'flex',
 | 
					 | 
				
			||||||
  alignItems: 'center'
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Modal = React.createClass({
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  propTypes: {
 | 
					 | 
				
			||||||
    media: ImmutablePropTypes.list,
 | 
					 | 
				
			||||||
    index: React.PropTypes.number.isRequired,
 | 
					 | 
				
			||||||
    isVisible: React.PropTypes.bool,
 | 
					 | 
				
			||||||
    onCloseClicked: 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();
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  componentDidMount () {
 | 
					 | 
				
			||||||
    this._listener = e => {
 | 
					 | 
				
			||||||
      if (!this.props.isVisible) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      switch(e.key) {
 | 
					 | 
				
			||||||
      case 'ArrowLeft':
 | 
					 | 
				
			||||||
        this.props.onPrevClicked();
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      case 'ArrowRight':
 | 
					 | 
				
			||||||
        this.props.onNextClicked();
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    window.addEventListener('keyup', this._listener);
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  componentWillUnmount () {
 | 
					 | 
				
			||||||
    window.removeEventListener('keyup', this._listener);
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  render () {
 | 
					 | 
				
			||||||
    const { media, index, ...other } = this.props;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!media) {
 | 
					 | 
				
			||||||
      return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const attachment = media.get(index);
 | 
					 | 
				
			||||||
    const url = attachment.get('url');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let leftNav, rightNav, content;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    leftNav = rightNav = content = '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (media.size > 1) {
 | 
					 | 
				
			||||||
      leftNav  = <div style={leftNavStyle} className='modal-container--nav' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>;
 | 
					 | 
				
			||||||
      rightNav = <div style={rightNavStyle} className='modal-container--nav' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (attachment.get('type') === 'image') {
 | 
					 | 
				
			||||||
      content = (
 | 
					 | 
				
			||||||
        <ImageLoader
 | 
					 | 
				
			||||||
          src={url}
 | 
					 | 
				
			||||||
          preloader={preloader}
 | 
					 | 
				
			||||||
          imgProps={{ style: imageStyle }}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    } else if (attachment.get('type') === 'gifv') {
 | 
					 | 
				
			||||||
      content = <ExtendedVideoPlayer src={url} />;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <Lightbox {...other}>
 | 
					 | 
				
			||||||
        {leftNav}
 | 
					 | 
				
			||||||
        {content}
 | 
					 | 
				
			||||||
        {rightNav}
 | 
					 | 
				
			||||||
      </Lightbox>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default connect(mapStateToProps, mapDispatchToProps)(Modal);
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,7 +47,9 @@ const UI = React.createClass({
 | 
				
			||||||
      this.dragTargets.push(e.target);
 | 
					      this.dragTargets.push(e.target);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (e.dataTransfer && e.dataTransfer.files.length > 0) {
 | 
				
			||||||
      this.setState({ draggingOver: true });
 | 
					      this.setState({ draggingOver: true });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleDragOver (e) {
 | 
					  handleDragOver (e) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,31 +1,17 @@
 | 
				
			||||||
import {
 | 
					import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal';
 | 
				
			||||||
  MEDIA_OPEN,
 | 
					 | 
				
			||||||
  MODAL_CLOSE,
 | 
					 | 
				
			||||||
  MODAL_INDEX_DECREASE,
 | 
					 | 
				
			||||||
  MODAL_INDEX_INCREASE
 | 
					 | 
				
			||||||
} from '../actions/modal';
 | 
					 | 
				
			||||||
import Immutable from 'immutable';
 | 
					import Immutable from 'immutable';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initialState = Immutable.Map({
 | 
					const initialState = {
 | 
				
			||||||
  media: null,
 | 
					  modalType: null,
 | 
				
			||||||
  index: 0,
 | 
					  modalProps: {}
 | 
				
			||||||
  open: false
 | 
					};
 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function modal(state = initialState, action) {
 | 
					export default function modal(state = initialState, action) {
 | 
				
			||||||
  switch(action.type) {
 | 
					  switch(action.type) {
 | 
				
			||||||
  case MEDIA_OPEN:
 | 
					  case MODAL_OPEN:
 | 
				
			||||||
    return state.withMutations(map => {
 | 
					    return { modalType: action.modalType, modalProps: action.modalProps };
 | 
				
			||||||
      map.set('media', action.media);
 | 
					 | 
				
			||||||
      map.set('index', action.index);
 | 
					 | 
				
			||||||
      map.set('open', true);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  case MODAL_CLOSE:
 | 
					  case MODAL_CLOSE:
 | 
				
			||||||
    return state.set('open', false);
 | 
					    return initialState;
 | 
				
			||||||
  case MODAL_INDEX_DECREASE:
 | 
					 | 
				
			||||||
    return state.update('index', index => (index - 1) % state.get('media').size);
 | 
					 | 
				
			||||||
  case MODAL_INDEX_INCREASE:
 | 
					 | 
				
			||||||
    return state.update('index', index => (index + 1) % state.get('media').size);
 | 
					 | 
				
			||||||
  default:
 | 
					  default:
 | 
				
			||||||
    return state;
 | 
					    return state;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1311,7 +1311,7 @@ button.active i.fa-retweet {
 | 
				
			||||||
  color: $color3;
 | 
					  color: $color3;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.modal-container--nav {
 | 
					.modal-container__nav {
 | 
				
			||||||
  color: $color5;
 | 
					  color: $color5;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1848,3 +1848,47 @@ button.active i.fa-retweet {
 | 
				
			||||||
    text-decoration: underline;
 | 
					    text-decoration: underline;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-root__overlay {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: 0;
 | 
				
			||||||
 | 
					  left: 0;
 | 
				
			||||||
 | 
					  right: 0;
 | 
				
			||||||
 | 
					  bottom: 0;
 | 
				
			||||||
 | 
					  z-index: 9999;
 | 
				
			||||||
 | 
					  opacity: 0;
 | 
				
			||||||
 | 
					  background: rgba($color8, 0.7);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-root__container {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: 0;
 | 
				
			||||||
 | 
					  left: 0;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  align-content: space-around;
 | 
				
			||||||
 | 
					  z-index: 9999;
 | 
				
			||||||
 | 
					  opacity: 0;
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  user-select: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.modal-root__modal {
 | 
				
			||||||
 | 
					  pointer-events: auto;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.media-modal {
 | 
				
			||||||
 | 
					  max-width: 80vw;
 | 
				
			||||||
 | 
					  max-height: 80vh;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  img, video {
 | 
				
			||||||
 | 
					    max-width: 80vw;
 | 
				
			||||||
 | 
					    max-height: 80vh;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue