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 { |
||||
closeModal, |
||||
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'; |
||||
import { closeModal } from '../../../actions/modal'; |
||||
import ModalRoot from '../components/modal_root'; |
||||
|
||||
const mapStateToProps = state => ({ |
||||
media: state.getIn(['modal', 'media']), |
||||
index: state.getIn(['modal', 'index']), |
||||
isVisible: state.getIn(['modal', 'open']) |
||||
type: state.get('modal').modalType, |
||||
props: state.get('modal').modalProps |
||||
}); |
||||
|
||||
const mapDispatchToProps = dispatch => ({ |
||||
onCloseClicked () { |
||||
onClose () { |
||||
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