diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/react/components/channel_loader.jsx | 9 | ||||
-rw-r--r-- | web/react/components/file_attachment.jsx | 10 | ||||
-rw-r--r-- | web/react/components/file_attachment_list.jsx | 16 | ||||
-rw-r--r-- | web/react/components/post_body.jsx | 1 | ||||
-rw-r--r-- | web/react/components/rhs_comment.jsx | 1 | ||||
-rw-r--r-- | web/react/components/rhs_root_post.jsx | 1 | ||||
-rw-r--r-- | web/react/components/settings_sidebar.jsx | 3 | ||||
-rw-r--r-- | web/react/components/view_image.jsx | 232 | ||||
-rw-r--r-- | web/react/components/view_image_popover_bar.jsx | 66 | ||||
-rw-r--r-- | web/sass-files/sass/partials/_modal.scss | 17 |
10 files changed, 190 insertions, 166 deletions
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index 962ba26ee..39c86405c 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -109,6 +109,13 @@ export default class ChannelLoader extends React.Component { $('.modal-body').css('overflow-y', 'auto'); $('.modal-body').css('max-height', $(window).height() * 0.7); }); + + /* Prevent backspace from navigating back a page */ + $(window).on('keydown.preventBackspace', (e) => { + if (e.which === 8 && !$(e.target).is('input, textarea')) { + e.preventDefault(); + } + }); } componentWillUnmount() { clearInterval(this.intervalId); @@ -123,6 +130,8 @@ export default class ChannelLoader extends React.Component { $('body').off('mouseenter mouseleave', '.post.post--comment.same--root'); $('.modal').off('show.bs.modal'); + + $(window).off('keydown.preventBackspace'); } onSocketChange(msg) { if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) { diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index c9aa06a97..888f24aa5 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -143,10 +143,7 @@ export default class FileAttachment extends React.Component { > <a className='post-image__thumbnail' href='#' - onClick={this.props.handleImageClick} - data-img-id={this.props.index} - data-toggle='modal' - data-target={'#' + this.props.modalId} + onClick={() => this.props.handleImageClick(this.props.index)} > {thumbnail} </a> @@ -187,9 +184,6 @@ FileAttachment.propTypes = { // the index of this attachment preview in the parent FileAttachmentList index: React.PropTypes.number.isRequired, - // the identifier of the modal dialog used to preview files - modalId: React.PropTypes.string.isRequired, - - // handler for when the thumbnail is clicked + // handler for when the thumbnail is clicked passed the index above handleImageClick: React.PropTypes.func }; diff --git a/web/react/components/file_attachment_list.jsx b/web/react/components/file_attachment_list.jsx index abe72089a..212d4a958 100644 --- a/web/react/components/file_attachment_list.jsx +++ b/web/react/components/file_attachment_list.jsx @@ -11,23 +11,21 @@ export default class FileAttachmentList extends React.Component { this.handleImageClick = this.handleImageClick.bind(this); - this.state = {startImgId: 0}; + this.state = {showPreviewModal: false, startImgId: 0}; } - handleImageClick(e) { - this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'), 10)}); + handleImageClick(indexClicked) { + this.setState({showPreviewModal: true, startImgId: indexClicked}); } render() { var filenames = this.props.filenames; - var modalId = this.props.modalId; var postFiles = []; for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { postFiles.push( <FileAttachment - key={i} + key={'file_attachment_' + i} filename={filenames[i]} index={i} - modalId={modalId} handleImageClick={this.handleImageClick} /> ); @@ -39,9 +37,10 @@ export default class FileAttachmentList extends React.Component { {postFiles} </div> <ViewImageModal + show={this.state.showPreviewModal} + onModalDismissed={() => this.setState({showPreviewModal: false})} channelId={this.props.channelId} userId={this.props.userId} - modalId={modalId} startId={this.state.startImgId} filenames={filenames} /> @@ -55,9 +54,6 @@ FileAttachmentList.propTypes = { // a list of file pathes displayed by this filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, - // the identifier of the modal dialog used to preview files - modalId: React.PropTypes.string.isRequired, - // the channel that this is part of channelId: React.PropTypes.string, diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index dbbcdc409..6e98e4aba 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -141,7 +141,6 @@ export default class PostBody extends React.Component { fileAttachmentHolder = ( <FileAttachmentList filenames={filenames} - modalId={`view_image_modal_${post.id}`} channelId={post.channel_id} userId={post.user_id} /> diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx index 4432a6a26..5b4694eb1 100644 --- a/web/react/components/rhs_comment.jsx +++ b/web/react/components/rhs_comment.jsx @@ -163,7 +163,6 @@ export default class RhsComment extends React.Component { fileAttachment = ( <FileAttachmentList filenames={post.filenames} - modalId={'rhs_comment_view_image_modal_' + post.id} channelId={post.channel_id} userId={post.user_id} /> diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx index 6289b9314..13ab0c982 100644 --- a/web/react/components/rhs_root_post.jsx +++ b/web/react/components/rhs_root_post.jsx @@ -111,7 +111,6 @@ export default class RhsRootPost extends React.Component { fileAttachment = ( <FileAttachmentList filenames={post.filenames} - modalId={'rhs_view_image_modal_' + post.id} channelId={post.channel_id} userId={post.user_id} /> diff --git a/web/react/components/settings_sidebar.jsx b/web/react/components/settings_sidebar.jsx index e5cbd6e92..4c4675788 100644 --- a/web/react/components/settings_sidebar.jsx +++ b/web/react/components/settings_sidebar.jsx @@ -7,7 +7,8 @@ export default class SettingsSidebar extends React.Component { this.handleClick = this.handleClick.bind(this); } - handleClick(tab) { + handleClick(tab, e) { + e.preventDefault(); this.props.updateTab(tab.name); $('.settings-modal').addClass('display--content'); } diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index dafcdd9f9..8db63e196 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -3,6 +3,8 @@ var Client = require('../utils/client.jsx'); var Utils = require('../utils/utils.jsx'); +var ViewImagePopoverBar = require('./view_image_popover_bar.jsx'); +var Modal = ReactBootstrap.Modal; export default class ViewImageModal extends React.Component { constructor(props) { @@ -16,6 +18,10 @@ export default class ViewImageModal extends React.Component { this.handleKeyPress = this.handleKeyPress.bind(this); this.getPublicLink = this.getPublicLink.bind(this); this.getPreviewImagePath = this.getPreviewImagePath.bind(this); + this.onModalShown = this.onModalShown.bind(this); + this.onModalHidden = this.onModalHidden.bind(this); + this.onMouseEnterImage = this.onMouseEnterImage.bind(this); + this.onMouseLeaveImage = this.onMouseLeaveImage.bind(this); var loaded = []; var progress = []; @@ -23,9 +29,18 @@ export default class ViewImageModal extends React.Component { loaded.push(false); progress.push(0); } - this.state = {imgId: this.props.startId, viewed: false, loaded: loaded, progress: progress, images: {}, fileSizes: {}}; + this.state = { + imgId: this.props.startId, + imgHeight: '100%', + loaded: loaded, + progress: progress, + images: {}, + fileSizes: {}, + showFooter: false + }; } - handleNext() { + handleNext(e) { + e.stopPropagation(); var id = this.state.imgId + 1; if (id > this.props.filenames.length - 1) { id = 0; @@ -33,7 +48,8 @@ export default class ViewImageModal extends React.Component { this.setState({imgId: id}); this.loadImage(id); } - handlePrev() { + handlePrev(e) { + e.stopPropagation(); var id = this.state.imgId - 1; if (id < 0) { id = this.props.filenames.length - 1; @@ -50,15 +66,27 @@ export default class ViewImageModal extends React.Component { this.handlePrev(); } } - componentWillReceiveProps(nextProps) { + onModalShown(nextProps) { this.setState({imgId: nextProps.startId}); + this.loadImage(nextProps.startId); + } + onModalHidden() { + if (this.refs.video) { + var video = React.findDOMNode(this.refs.video); + video.pause(); + video.currentTime = 0; + } + } + componentWillReceiveProps(nextProps) { + if (nextProps.show === true && this.props.show === false) { + this.onModalShown(nextProps); + } else if (nextProps.show === false && this.props.show === true) { + this.onModalHidden(); + } } loadImage(id) { var imgHeight = $(window).height() - 100; - if (this.state.loaded[id] || this.state.images[id]) { - $('.modal .modal-image .image-wrapper img').css('max-height', imgHeight); - return; - } + this.setState({imgHeight}); var filename = this.props.filenames[id]; @@ -68,84 +96,27 @@ export default class ViewImageModal extends React.Component { if (fileType === 'image') { var img = new Image(); img.load(this.getPreviewImagePath(filename), - function load() { - var progress = this.state.progress; - progress[id] = img.completedPercentage; - this.setState({progress: progress}); - }.bind(this)); - img.onload = (function onload(imgid) { - return function onloadReturn() { - var loaded = this.state.loaded; - loaded[imgid] = true; - this.setState({loaded: loaded}); - $(React.findDOMNode(this.refs.image)).css('max-height', imgHeight); - }.bind(this); - }.bind(this)(id)); + () => { + const progress = this.state.progress; + progress[id] = img.completedPercentage; + this.setState({progress}); + }); + img.onload = () => { + const loaded = this.state.loaded; + loaded[id] = true; + this.setState({loaded}); + }; var images = this.state.images; images[id] = img; - this.setState({images: images}); + this.setState({images}); } else { // there's nothing to load for non-image files var loaded = this.state.loaded; loaded[id] = true; - this.setState({loaded: loaded}); - } - } - componentDidUpdate() { - if (this.state.loaded[this.state.imgId]) { - if (this.refs.imageWrap) { - $(React.findDOMNode(this.refs.imageWrap)).removeClass('default'); - } + this.setState({loaded}); } } componentDidMount() { - $('#' + this.props.modalId).on('shown.bs.modal', function onModalShow() { - this.setState({viewed: true}); - this.loadImage(this.state.imgId); - }.bind(this)); - - $('#' + this.props.modalId).on('hidden.bs.modal', function onModalHide() { - if (this.refs.video) { - var video = React.findDOMNode(this.refs.video); - video.pause(); - video.currentTime = 0; - } - }.bind(this)); - - $(React.findDOMNode(this.refs.modal)).click(function onModalClick(e) { - if (e.target === this || e.target === React.findDOMNode(this.refs.imageBody)) { - $('.image_modal').modal('hide'); - } - }.bind(this)); - - $(React.findDOMNode(this.refs.imageWrap)).hover( - function onModalHover() { - $(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show'); - }.bind(this), function offModalHover() { - $(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show'); - }.bind(this) - ); - - if (this.refs.previewArrowLeft) { - $(React.findDOMNode(this.refs.previewArrowLeft)).hover( - function onModalHover() { - $(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show'); - }.bind(this), function offModalHover() { - $(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show'); - }.bind(this) - ); - } - - if (this.refs.previewArrowRight) { - $(React.findDOMNode(this.refs.previewArrowRight)).hover( - function onModalHover() { - $(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show'); - }.bind(this), function offModalHover() { - $(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show'); - }.bind(this) - ); - } - $(window).on('keyup', this.handleKeyPress); // keep track of whether or not this component is mounted so we can safely set the state asynchronously @@ -189,6 +160,12 @@ export default class ViewImageModal extends React.Component { // only images have proper previews, so just use a placeholder icon for non-images return Utils.getPreviewImagePathForFileType(fileType); } + onMouseEnterImage() { + this.setState({showFooter: true}); + } + onMouseLeaveImage() { + this.setState({showFooter: false}); + } render() { if (this.props.filenames.length < 1 || this.props.filenames.length - 1 < this.state.imgId) { return <div/>; @@ -299,23 +276,6 @@ export default class ViewImageModal extends React.Component { bgClass = 'black-bg'; } - var publicLink = ''; - if (global.window.config.EnablePublicLink === 'true') { - publicLink = ( - <div> - <a - href='#' - className='public-link text' - data-title='Public Image' - onClick={this.getPublicLink} - > - Get Public Link - </a> - <span className='text'> | </span> - </div> - ); - } - var leftArrow = ''; var rightArrow = ''; if (this.props.filenames.length > 1) { @@ -342,65 +302,61 @@ export default class ViewImageModal extends React.Component { ); } + let closeButtonClass = 'modal-close'; + if (this.state.showFooter) { + closeButtonClass += ' modal-close--show'; + } + return ( - <div - className='modal fade image_modal' - ref='modal' - id={this.props.modalId} - tabIndex='-1' - role='dialog' - aria-hidden='true' + <Modal + show={this.props.show} + onHide={this.props.onModalDismissed} + className='image_modal' + dialogClassName='modal-image' > - <div className='modal-dialog modal-image'> - <div className='modal-content image-content'> + <Modal.Body + modalClassName='image-body' + onClick={this.props.onModalDismissed} + > + <div + className={'image-wrapper ' + bgClass} + style={{maxHeight: this.state.imgHeight}} + onMouseEnter={this.onMouseEnterImage} + onMouseLeave={this.onMouseLeaveImage} + onClick={(e) => e.stopPropagation()} + > <div - ref='imageBody' - className='modal-body image-body' - > - <div - ref='imageWrap' - className={'image-wrapper default ' + bgClass} - > - <div - className='modal-close' - data-dismiss='modal' - /> - {content} - <div - ref='imageFooter' - className='modal-button-bar' - > - <span className='pull-left text'>{'File ' + (this.state.imgId + 1) + ' of ' + this.props.filenames.length}</span> - <div className='image-links'> - {publicLink} - <a - href={fileUrl} - download={name} - className='text' - > - Download - </a> - </div> - </div> - </div> - {leftArrow} - {rightArrow} - </div> + className={closeButtonClass} + onClick={this.props.onModalDismissed} + /> + {content} + <ViewImagePopoverBar + show={this.state.showFooter} + fileId={this.state.imgId} + totalFiles={this.props.filenames.length} + filename={name} + fileURL={fileUrl} + onGetPublicLinkPressed={this.getPublicLink} + /> </div> - </div> - </div> + {leftArrow} + {rightArrow} + </Modal.Body> + </Modal> ); } } ViewImageModal.defaultProps = { + show: false, filenames: [], - modalId: '', channelId: '', userId: '', startId: 0 }; ViewImageModal.propTypes = { + show: React.PropTypes.bool.isRequired, + onModalDismissed: React.PropTypes.func.isRequired, filenames: React.PropTypes.array, modalId: React.PropTypes.string, channelId: React.PropTypes.string, diff --git a/web/react/components/view_image_popover_bar.jsx b/web/react/components/view_image_popover_bar.jsx new file mode 100644 index 000000000..68817d751 --- /dev/null +++ b/web/react/components/view_image_popover_bar.jsx @@ -0,0 +1,66 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +export default class ViewImagePopoverBar extends React.Component { + constructor(props) { + super(props); + } + render() { + var publicLink = ''; + if (global.window.config.EnablePublicLink === 'true') { + publicLink = ( + <div> + <a + href='#' + className='public-link text' + data-title='Public Image' + onClick={this.getPublicLink} + > + {'Get Public Link'} + </a> + <span className='text'>{' | '}</span> + </div> + ); + } + + var footerClass = 'modal-button-bar'; + if (this.props.show) { + footerClass += ' footer--show'; + } + + return ( + <div + ref='imageFooter' + className={footerClass} + > + <span className='pull-left text'>{'File ' + (this.props.fileId + 1) + ' of ' + this.props.totalFiles}</span> + <div className='image-links'> + {publicLink} + <a + href={this.props.fileURL} + download={this.props.filename} + className='text' + > + {'Download'} + </a> + </div> + </div> + ); + } +} +ViewImagePopoverBar.defaultProps = { + show: false, + imgId: 0, + totalFiles: 0, + filename: '', + fileURL: '' +}; + +ViewImagePopoverBar.propTypes = { + show: React.PropTypes.bool.isRequired, + fileId: React.PropTypes.number.isRequired, + totalFiles: React.PropTypes.number.isRequired, + filename: React.PropTypes.string.isRequired, + fileURL: React.PropTypes.string.isRequired, + onGetPublicLinkPressed: React.PropTypes.func.isRequired +}; diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index 62067c3b8..b0d5a0f7d 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -190,16 +190,13 @@ position: relative; max-width: 90%; min-height: 100px; - min-width: 320px; + min-width: 240px; @include border-radius(3px); display: table; margin: 0 auto; &:hover { @include border-radius(3px 3px 0 0); } - &:hover .modal-close { - @include opacity(1); - } &.default { width: 100%; height: 80%; @@ -213,8 +210,15 @@ right: -13px; top: -13px; @include opacity(0); + -webkit-transition: opacity 0.6s; + -moz-transition: opacity 0.6s; + -o-transition: opacity 0.6s; + transition: opacity 0.6s; cursor: pointer; z-index: 9999; + &.modal-close--show { + @include opacity(1); + } } > a { background: #FFF; @@ -226,7 +230,7 @@ max-height: 100%; } } - .image-content { + .modal-content{ box-shadow: none; background: rgba(0, 0, 0, 0); width: 100%; @@ -310,6 +314,7 @@ } } + // Invite New Member .invite { margin-right: 40px; @@ -324,4 +329,4 @@ padding-left: 0; } } -}
\ No newline at end of file +} |