summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
authorCorey Hulen <corey@hulen.com>2015-09-24 08:59:00 -0700
committerCorey Hulen <corey@hulen.com>2015-09-24 08:59:00 -0700
commit819ef528b858f81f239406ed3a05f9170eee2c93 (patch)
treebc2e67a0e5364d36d22a681a5fbb56b008b15ce8 /web
parenta7d75d3bceef5fa405e72968806c905bcc9d8b31 (diff)
parentc5d78f828a48ba88b8a89a0564bcb8352e84b396 (diff)
downloadchat-819ef528b858f81f239406ed3a05f9170eee2c93.tar.gz
chat-819ef528b858f81f239406ed3a05f9170eee2c93.tar.bz2
chat-819ef528b858f81f239406ed3a05f9170eee2c93.zip
Merge pull request #779 from mattermost/plt-294
PLT-294 Converting preview image modal to use react-bootstrap.
Diffstat (limited to 'web')
-rw-r--r--web/react/components/file_attachment.jsx10
-rw-r--r--web/react/components/file_attachment_list.jsx16
-rw-r--r--web/react/components/post_body.jsx1
-rw-r--r--web/react/components/rhs_comment.jsx1
-rw-r--r--web/react/components/rhs_root_post.jsx1
-rw-r--r--web/react/components/view_image.jsx232
-rw-r--r--web/react/components/view_image_popover_bar.jsx66
-rw-r--r--web/sass-files/sass/partials/_modal.scss17
8 files changed, 179 insertions, 165 deletions
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 4d1892a69..dc49fbdb1 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 e661bdce1..caa8c5c1e 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/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
+}