parent
13dfd8d109
commit
60ebfa182f
@ -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); |
|
@ -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 = { |
|
||||||
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); |
export default connect(mapStateToProps, mapDispatchToProps)(ModalRoot); |
||||||
|
Loading…
Reference in new issue