diff options
Diffstat (limited to 'webapp/components')
-rw-r--r-- | webapp/components/audio_video_preview.jsx | 12 | ||||
-rw-r--r-- | webapp/components/code_preview.jsx | 18 | ||||
-rw-r--r-- | webapp/components/create_comment.jsx | 36 | ||||
-rw-r--r-- | webapp/components/create_post.jsx | 47 | ||||
-rw-r--r-- | webapp/components/file_attachment.jsx | 251 | ||||
-rw-r--r-- | webapp/components/file_attachment_list.jsx | 55 | ||||
-rw-r--r-- | webapp/components/file_attachment_list_container.jsx | 90 | ||||
-rw-r--r-- | webapp/components/file_info_preview.jsx | 88 | ||||
-rw-r--r-- | webapp/components/file_preview.jsx | 83 | ||||
-rw-r--r-- | webapp/components/file_upload.jsx | 9 | ||||
-rw-r--r-- | webapp/components/get_public_link_modal.jsx | 6 | ||||
-rw-r--r-- | webapp/components/pdf_preview.jsx | 24 | ||||
-rw-r--r-- | webapp/components/post_view/components/commented_on_files_message_container.jsx | 88 | ||||
-rw-r--r-- | webapp/components/post_view/components/post_body.jsx | 31 | ||||
-rw-r--r-- | webapp/components/rhs_comment.jsx | 12 | ||||
-rw-r--r-- | webapp/components/rhs_root_post.jsx | 12 | ||||
-rw-r--r-- | webapp/components/view_image.jsx | 140 |
17 files changed, 489 insertions, 513 deletions
diff --git a/webapp/components/audio_video_preview.jsx b/webapp/components/audio_video_preview.jsx index dd2e910b3..4956900a9 100644 --- a/webapp/components/audio_video_preview.jsx +++ b/webapp/components/audio_video_preview.jsx @@ -76,10 +76,8 @@ export default class AudioVideoPreview extends React.Component { if (!this.state.canPlay) { return ( <FileInfoPreview - filename={this.props.filename} - fileUrl={this.props.fileUrl} fileInfo={this.props.fileInfo} - formatMessage={this.props.formatMessage} + fileUrl={this.props.fileUrl} /> ); } @@ -94,7 +92,7 @@ export default class AudioVideoPreview extends React.Component { // add a key to the video to prevent React from using an old video source while a new one is loading return ( <video - key={this.props.filename} + key={this.props.fileInfo.id} ref='video' style={{maxHeight: this.props.maxHeight}} data-setup='{}' @@ -112,9 +110,7 @@ export default class AudioVideoPreview extends React.Component { } AudioVideoPreview.propTypes = { - filename: React.PropTypes.string.isRequired, - fileUrl: React.PropTypes.string.isRequired, fileInfo: React.PropTypes.object.isRequired, - maxHeight: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]).isRequired, - formatMessage: React.PropTypes.func.isRequired + fileUrl: React.PropTypes.string.isRequired, + maxHeight: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]).isRequired }; diff --git a/webapp/components/code_preview.jsx b/webapp/components/code_preview.jsx index 6625f45f4..852b26e25 100644 --- a/webapp/components/code_preview.jsx +++ b/webapp/components/code_preview.jsx @@ -38,7 +38,7 @@ export default class CodePreview extends React.Component { } updateStateFromProps(props) { - var usedLanguage = SyntaxHighlighting.getLanguageFromFilename(props.filename); + const usedLanguage = SyntaxHighlighting.getLanguageFromFileExtension(props.fileInfo.extension); if (!usedLanguage || props.fileInfo.size > Constants.CODE_PREVIEW_MAX_FILE_SIZE) { this.setState({code: '', lang: '', loading: false, success: false}); @@ -64,8 +64,8 @@ export default class CodePreview extends React.Component { this.setState({loading: false, success: false}); } - static support(filename) { - return Boolean(SyntaxHighlighting.getLanguageFromFilename(filename)); + static supports(fileInfo) { + return Boolean(SyntaxHighlighting.getLanguageFromFileExtension(fileInfo.extension)); } render() { @@ -83,10 +83,8 @@ export default class CodePreview extends React.Component { if (!this.state.success) { return ( <FileInfoPreview - filename={this.props.filename} - fileUrl={this.props.fileUrl} fileInfo={this.props.fileInfo} - formatMessage={this.props.formatMessage} + fileUrl={this.props.fileUrl} /> ); } @@ -106,12 +104,10 @@ export default class CodePreview extends React.Component { const highlighted = SyntaxHighlighting.highlight(this.state.lang, this.state.code); - const fileName = this.props.filename.substring(this.props.filename.lastIndexOf('/') + 1, this.props.filename.length); - return ( <div className='post-code'> <span className='post-code__language'> - {`${fileName} - ${language}`} + {`${this.props.fileInfo.name} - ${language}`} </span> <code className='hljs'> <table> @@ -129,8 +125,6 @@ export default class CodePreview extends React.Component { } CodePreview.propTypes = { - filename: React.PropTypes.string.isRequired, - fileUrl: React.PropTypes.string.isRequired, fileInfo: React.PropTypes.object.isRequired, - formatMessage: React.PropTypes.func.isRequired + fileUrl: React.PropTypes.string.isRequired }; diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx index 2f0698510..133c2e6d2 100644 --- a/webapp/components/create_comment.jsx +++ b/webapp/components/create_comment.jsx @@ -55,7 +55,7 @@ export default class CreateComment extends React.Component { this.state = { messageText: draft.message, uploadsInProgress: draft.uploadsInProgress, - previews: draft.previews, + fileInfos: draft.fileInfos, submitting: false, ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'), showPostDeletedModal: false @@ -99,10 +99,10 @@ export default class CreateComment extends React.Component { } const post = {}; - post.filenames = []; + post.file_ids = []; post.message = this.state.messageText; - if (post.message.trim().length === 0 && this.state.previews.length === 0) { + if (post.message.trim().length === 0 && this.state.fileInfos.length === 0) { return; } @@ -126,7 +126,7 @@ export default class CreateComment extends React.Component { post.channel_id = this.props.channelId; post.root_id = this.props.rootId; post.parent_id = this.props.rootId; - post.filenames = this.state.previews; + post.file_ids = this.state.fileInfos.map((info) => info.id); const time = Utils.getTimestamp(); post.pending_post_id = `${userId}:${time}`; post.user_id = userId; @@ -163,7 +163,7 @@ export default class CreateComment extends React.Component { messageText: '', submitting: false, postError: null, - previews: [], + fileInfos: [], serverError: null }); } @@ -245,7 +245,7 @@ export default class CreateComment extends React.Component { this.focusTextbox(); } - handleFileUploadComplete(filenames, clientIds) { + handleFileUploadComplete(fileInfos, clientIds) { const draft = PostStore.getCommentDraft(this.props.rootId); // remove each finished file from uploads @@ -257,10 +257,10 @@ export default class CreateComment extends React.Component { } } - draft.previews = draft.previews.concat(filenames); + draft.fileInfos = draft.fileInfos.concat(fileInfos); PostStore.storeCommentDraft(this.props.rootId, draft); - this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews}); + this.setState({uploadsInProgress: draft.uploadsInProgress, fileInfos: draft.fileInfos}); } handleUploadError(err, clientId) { @@ -281,11 +281,11 @@ export default class CreateComment extends React.Component { } removePreview(id) { - const previews = this.state.previews; + const fileInfos = this.state.fileInfos; const uploadsInProgress = this.state.uploadsInProgress; - // id can either be the path of an uploaded file or the client id of an in progress upload - let index = previews.indexOf(id); + // id can either be the id of an uploaded file or the client id of an in progress upload + let index = fileInfos.findIndex((info) => info.id === id); if (index === -1) { index = uploadsInProgress.indexOf(id); @@ -294,26 +294,26 @@ export default class CreateComment extends React.Component { this.refs.fileUpload.getWrappedInstance().cancelUpload(id); } } else { - previews.splice(index, 1); + fileInfos.splice(index, 1); } const draft = PostStore.getCommentDraft(this.props.rootId); - draft.previews = previews; + draft.fileInfos = fileInfos; draft.uploadsInProgress = uploadsInProgress; PostStore.storeCommentDraft(this.props.rootId, draft); - this.setState({previews, uploadsInProgress}); + this.setState({fileInfos, uploadsInProgress}); } componentWillReceiveProps(newProps) { if (newProps.rootId !== this.props.rootId) { const draft = PostStore.getCommentDraft(newProps.rootId); - this.setState({messageText: draft.message, uploadsInProgress: draft.uploadsInProgress, previews: draft.previews}); + this.setState({messageText: draft.message, uploadsInProgress: draft.uploadsInProgress, fileInfos: draft.fileInfos}); } } getFileCount() { - return this.state.previews.length + this.state.uploadsInProgress.length; + return this.state.fileInfos.length + this.state.uploadsInProgress.length; } focusTextbox() { @@ -350,10 +350,10 @@ export default class CreateComment extends React.Component { } let preview = null; - if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) { + if (this.state.fileInfos.length > 0 || this.state.uploadsInProgress.length > 0) { preview = ( <FilePreview - files={this.state.previews} + fileInfos={this.state.fileInfos} onRemove={this.removePreview} uploadsInProgress={this.state.uploadsInProgress} /> diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx index bfacd0644..d3417e419 100644 --- a/webapp/components/create_post.jsx +++ b/webapp/components/create_post.jsx @@ -67,7 +67,7 @@ export default class CreatePost extends React.Component { channelId: ChannelStore.getCurrentId(), messageText: draft.messageText, uploadsInProgress: draft.uploadsInProgress, - previews: draft.previews, + fileInfos: draft.fileInfos, submitting: false, initialText: draft.messageText, ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'), @@ -79,14 +79,14 @@ export default class CreatePost extends React.Component { getCurrentDraft() { const draft = PostStore.getCurrentDraft(); - const safeDraft = {previews: [], messageText: '', uploadsInProgress: []}; + const safeDraft = {fileInfos: [], messageText: '', uploadsInProgress: []}; if (draft) { if (draft.message) { safeDraft.messageText = draft.message; } - if (draft.previews) { - safeDraft.previews = draft.previews; + if (draft.fileInfos) { + safeDraft.fileInfos = draft.fileInfos; } if (draft.uploadsInProgress) { safeDraft.uploadsInProgress = draft.uploadsInProgress; @@ -104,10 +104,10 @@ export default class CreatePost extends React.Component { } const post = {}; - post.filenames = []; + post.file_ids = []; post.message = this.state.messageText; - if (post.message.trim().length === 0 && this.state.previews.length === 0) { + if (post.message.trim().length === 0 && this.state.fileInfos.length === 0) { return; } @@ -122,7 +122,7 @@ export default class CreatePost extends React.Component { if (post.message.indexOf('/') === 0) { PostStore.storeDraft(this.state.channelId, null); - this.setState({messageText: '', postError: null, previews: []}); + this.setState({messageText: '', postError: null, fileInfos: []}); ChannelActions.executeCommand( this.state.channelId, @@ -153,7 +153,7 @@ export default class CreatePost extends React.Component { sendMessage(post) { post.channel_id = this.state.channelId; - post.filenames = this.state.previews; + post.file_ids = this.state.fileInfos.map((info) => info.id); const time = Utils.getTimestamp(); const userId = UserStore.getCurrentId(); @@ -163,7 +163,7 @@ export default class CreatePost extends React.Component { post.parent_id = this.state.parentId; GlobalActions.emitUserPostedEvent(post); - this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); + this.setState({messageText: '', submitting: false, postError: null, fileInfos: [], serverError: null}); Client.createPost(post, (data) => { @@ -236,7 +236,7 @@ export default class CreatePost extends React.Component { this.focusTextbox(); } - handleFileUploadComplete(filenames, clientIds, channelId) { + handleFileUploadComplete(fileInfos, clientIds, channelId) { const draft = PostStore.getDraft(channelId); // remove each finished file from uploads @@ -248,11 +248,11 @@ export default class CreatePost extends React.Component { } } - draft.previews = draft.previews.concat(filenames); + draft.fileInfos = draft.fileInfos.concat(fileInfos); PostStore.storeDraft(channelId, draft); if (channelId === this.state.channelId) { - this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews}); + this.setState({uploadsInProgress: draft.uploadsInProgress, fileInfos: draft.fileInfos}); } } @@ -282,11 +282,11 @@ export default class CreatePost extends React.Component { } removePreview(id) { - const previews = Object.assign([], this.state.previews); + const fileInfos = Object.assign([], this.state.fileInfos); const uploadsInProgress = this.state.uploadsInProgress; - // id can either be the path of an uploaded file or the client id of an in progress upload - let index = previews.indexOf(id); + // id can either be the id of an uploaded file or the client id of an in progress upload + let index = fileInfos.findIndex((info) => info.id === id); if (index === -1) { index = uploadsInProgress.indexOf(id); @@ -295,15 +295,15 @@ export default class CreatePost extends React.Component { this.refs.fileUpload.getWrappedInstance().cancelUpload(id); } } else { - previews.splice(index, 1); + fileInfos.splice(index, 1); } const draft = PostStore.getCurrentDraft(); - draft.previews = previews; + draft.fileInfos = fileInfos; draft.uploadsInProgress = uploadsInProgress; PostStore.storeCurrentDraft(draft); - this.setState({previews, uploadsInProgress}); + this.setState({fileInfos, uploadsInProgress}); } componentWillMount() { @@ -336,6 +336,7 @@ export default class CreatePost extends React.Component { PreferenceStore.removeChangeListener(this.onPreferenceChange); document.removeEventListener('keydown', this.showShortcuts); } + showShortcuts(e) { if ((e.ctrlKey || e.metaKey) && e.keyCode === Constants.KeyCodes.FORWARD_SLASH) { e.preventDefault(); @@ -359,7 +360,7 @@ export default class CreatePost extends React.Component { if (this.state.channelId !== channelId) { const draft = this.getCurrentDraft(); - this.setState({channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress}); + this.setState({channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, fileInfos: draft.fileInfos, uploadsInProgress: draft.uploadsInProgress}); } } @@ -374,11 +375,11 @@ export default class CreatePost extends React.Component { getFileCount(channelId) { if (channelId === this.state.channelId) { - return this.state.previews.length + this.state.uploadsInProgress.length; + return this.state.fileInfos.length + this.state.uploadsInProgress.length; } const draft = PostStore.getDraft(channelId); - return draft.previews.length + draft.uploadsInProgress.length; + return draft.fileInfos.length + draft.uploadsInProgress.length; } handleKeyDown(e) { @@ -474,10 +475,10 @@ export default class CreatePost extends React.Component { } let preview = null; - if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) { + if (this.state.fileInfos.length > 0 || this.state.uploadsInProgress.length > 0) { preview = ( <FilePreview - files={this.state.previews} + fileInfos={this.state.fileInfos} onRemove={this.removePreview} uploadsInProgress={this.state.uploadsInProgress} /> diff --git a/webapp/components/file_attachment.jsx b/webapp/components/file_attachment.jsx index cba9d8288..23d8d2446 100644 --- a/webapp/components/file_attachment.jsx +++ b/webapp/components/file_attachment.jsx @@ -1,204 +1,111 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; -import ReactDOM from 'react-dom'; -import * as utils from 'utils/utils.jsx'; -import Client from 'client/web_client.jsx'; import Constants from 'utils/constants.jsx'; +import FileStore from 'stores/file_store.jsx'; +import * as Utils from 'utils/utils.jsx'; -import {intlShape, injectIntl, defineMessages} from 'react-intl'; import {Tooltip, OverlayTrigger} from 'react-bootstrap'; -const holders = defineMessages({ - download: { - id: 'file_attachment.download', - defaultMessage: 'Download' - } -}); - import React from 'react'; -class FileAttachment extends React.Component { +export default class FileAttachment extends React.Component { constructor(props) { super(props); this.loadFiles = this.loadFiles.bind(this); - this.addBackgroundImage = this.addBackgroundImage.bind(this); this.onAttachmentClick = this.onAttachmentClick.bind(this); - this.canSetState = false; - this.state = {fileSize: -1}; + this.state = { + loaded: Utils.getFileType(props.fileInfo.extension) !== 'image' + }; } + componentDidMount() { this.loadFiles(); } - componentDidUpdate(prevProps) { - if (this.props.filename !== prevProps.filename) { - this.loadFiles(); - } - } - loadFiles() { - this.canSetState = true; - - var filename = this.props.filename; - - if (filename) { - var fileInfo = this.getFileInfoFromName(filename); - var type = utils.getFileType(fileInfo.ext); - - if (type === 'image') { - var self = this; // Need this reference since we use the given "this" - $('<img/>').attr('src', fileInfo.path + '_thumb.jpg').on('load', (function loadWrapper(path, name) { - return function loader() { - $(this).remove(); - if (name in self.refs) { - var imgDiv = ReactDOM.findDOMNode(self.refs[name]); - - $(imgDiv).removeClass('post-image__load'); - $(imgDiv).addClass('post-image'); - - var width = this.width || $(this).width(); - var height = this.height || $(this).height(); - - if (width < Constants.THUMBNAIL_WIDTH && - height < Constants.THUMBNAIL_HEIGHT) { - $(imgDiv).addClass('small'); - } else { - $(imgDiv).addClass('normal'); - } - self.addBackgroundImage(name, path); - } - }; - }(fileInfo.path, filename))); - } + componentWillReceiveProps(nextProps) { + if (nextProps.fileInfo.id !== this.props.fileInfo.id) { + this.setState({ + loaded: Utils.getFileType(nextProps.fileInfo.extension) !== 'image' + }); } } - componentWillUnmount() { - // keep track of when this component is mounted so that we can asynchronously change state without worrying about whether or not we're mounted - this.canSetState = false; - } - shouldComponentUpdate(nextProps, nextState) { - if (!utils.areObjectsEqual(nextProps, this.props)) { - return true; - } - - // the only time this object should update is when it receives an updated file size which we can usually handle without re-rendering - if (nextState.fileSize !== this.state.fileSize) { - if (this.refs.fileSize) { - // update the UI element to display the file size without re-rendering the whole component - ReactDOM.findDOMNode(this.refs.fileSize).innerHTML = utils.fileSizeToString(nextState.fileSize); - return false; - } - - // we can't find the element that should hold the file size so we must not have rendered yet - return true; + componentDidUpdate(prevProps) { + if (!this.state.loaded && this.props.fileInfo.id !== prevProps.fileInfo.id) { + this.loadFiles(); } - - return true; - } - getFileInfoFromName(name) { - var fileInfo = utils.splitFileLocation(name); - - fileInfo.path = Client.getFilesRoute() + '/get' + fileInfo.path; - - return fileInfo; } - addBackgroundImage(name, path) { - var fileUrl = path; - if (name in this.refs) { - if (!path) { - fileUrl = this.getFileInfoFromName(name).path; - } + loadFiles() { + const fileInfo = this.props.fileInfo; + const fileType = Utils.getFileType(fileInfo.extension); - var imgDiv = ReactDOM.findDOMNode(this.refs[name]); - var re1 = new RegExp(' ', 'g'); - var re2 = new RegExp('\\(', 'g'); - var re3 = new RegExp('\\)', 'g'); - var url = fileUrl.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); + if (fileType === 'image') { + const thumbnailUrl = FileStore.getFileThumbnailUrl(fileInfo.id); - $(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)'); - } - } - removeBackgroundImage(name) { - if (name in this.refs) { - $(ReactDOM.findDOMNode(this.refs[name])).css('background-image', 'initial'); + const img = new Image(); + img.onload = () => { + this.setState({loaded: true}); + }; + img.load(thumbnailUrl); } } + onAttachmentClick(e) { e.preventDefault(); this.props.handleImageClick(this.props.index); } + render() { - var filename = this.props.filename; + const fileInfo = this.props.fileInfo; + const fileName = fileInfo.name; + const fileUrl = FileStore.getFileUrl(fileInfo.id); - var fileInfo = utils.splitFileLocation(filename); - var fileUrl = utils.getFileUrl(filename); - var type = utils.getFileType(fileInfo.ext); + let thumbnail; + if (this.state.loaded) { + const type = Utils.getFileType(fileInfo.extension); - var thumbnail; - if (type === 'image') { - thumbnail = ( - <div - ref={filename} - className='post-image__load' - /> - ); - } else { - thumbnail = <div className={'file-icon ' + utils.getIconClassName(type)}/>; - } + if (type === 'image') { + let className = 'post-image'; - var fileSizeString = ''; - if (this.state.fileSize < 0) { - Client.getFileInfo( - filename, - (data) => { - if (this.canSetState) { - this.setState({fileSize: parseInt(data.size, 10)}); - } - }, - () => { - // Do nothing + if (fileInfo.width < Constants.THUMBNAIL_WIDTH && fileInfo.height < Constants.THUMBNAIL_HEIGHT) { + className += ' small'; + } else { + className += ' normal'; } - ); + + thumbnail = ( + <div + className={className} + style={{ + backgroundImage: `url(${FileStore.getFileThumbnailUrl(fileInfo.id)})` + }} + /> + ); + } else { + thumbnail = <div className={'file-icon ' + Utils.getIconClassName(type)}/>; + } } else { - fileSizeString = utils.fileSizeToString(this.state.fileSize); + thumbnail = <div className='post-image__load'/>; } - var filenameString = decodeURIComponent(utils.getFileName(filename)); - var trimmedFilename; - if (filenameString.length > 35) { - trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + '...'; + let trimmedFilename; + if (fileName.length > 35) { + trimmedFilename = fileName.substring(0, Math.min(35, fileName.length)) + '...'; } else { - trimmedFilename = filenameString; + trimmedFilename = fileName; } - var filenameOverlay = ( - <OverlayTrigger - delayShow={1000} - placement='top' - overlay={<Tooltip id='file-name__tooltip'>{this.props.intl.formatMessage(holders.download) + ' "' + filenameString + '"'}</Tooltip>} - > - <a - href={fileUrl} - download={filenameString} - className='post-image__name' - target='_blank' - rel='noopener noreferrer' - > - {trimmedFilename} - </a> - </OverlayTrigger> - ); + let filenameOverlay; if (this.props.compactDisplay) { filenameOverlay = ( <OverlayTrigger delayShow={1000} placement='top' - overlay={<Tooltip id='file-name__tooltip'>{filenameString}</Tooltip>} + overlay={<Tooltip id='file-name__tooltip'>{fileName}</Tooltip>} > <a href='#' @@ -214,13 +121,28 @@ class FileAttachment extends React.Component { </a> </OverlayTrigger> ); + } else { + filenameOverlay = ( + <OverlayTrigger + delayShow={1000} + placement='top' + overlay={<Tooltip id='file-name__tooltip'>{Utils.localizeMessage('file_attachment.download', 'Download') + ' "' + fileName + '"'}</Tooltip>} + > + <a + href={fileUrl} + download={fileName} + className='post-image__name' + target='_blank' + rel='noopener noreferrer' + > + {trimmedFilename} + </a> + </OverlayTrigger> + ); } return ( - <div - className='post-image__column' - key={filename} - > + <div className='post-image__column'> <a className='post-image__thumbnail' href='#' @@ -233,17 +155,15 @@ class FileAttachment extends React.Component { <div> <a href={fileUrl} - download={filenameString} + download={fileName} className='post-image__download' target='_blank' rel='noopener noreferrer' > - <span - className='fa fa-download' - /> + <span className='fa fa-download'/> </a> - <span className='post-image__type'>{fileInfo.ext.toUpperCase()}</span> - <span className='post-image__size'>{fileSizeString}</span> + <span className='post-image__type'>{fileInfo.extension.toUpperCase()}</span> + <span className='post-image__size'>{Utils.fileSizeToString(fileInfo.size)}</span> </div> </div> </div> @@ -252,10 +172,7 @@ class FileAttachment extends React.Component { } FileAttachment.propTypes = { - intl: intlShape.isRequired, - - // a list of file pathes displayed by the parent FileAttachmentList - filename: React.PropTypes.string.isRequired, + fileInfo: React.PropTypes.object.isRequired, // the index of this attachment preview in the parent FileAttachmentList index: React.PropTypes.number.isRequired, @@ -264,6 +181,4 @@ FileAttachment.propTypes = { handleImageClick: React.PropTypes.func, compactDisplay: React.PropTypes.bool -}; - -export default injectIntl(FileAttachment); +};
\ No newline at end of file diff --git a/webapp/components/file_attachment_list.jsx b/webapp/components/file_attachment_list.jsx index e4b841769..3df4684be 100644 --- a/webapp/components/file_attachment_list.jsx +++ b/webapp/components/file_attachment_list.jsx @@ -13,25 +13,34 @@ export default class FileAttachmentList extends React.Component { this.handleImageClick = this.handleImageClick.bind(this); - this.state = {showPreviewModal: false, startImgId: 0}; + this.state = {showPreviewModal: false, startImgIndex: 0}; } + handleImageClick(indexClicked) { - this.setState({showPreviewModal: true, startImgId: indexClicked}); + this.setState({showPreviewModal: true, startImgIndex: indexClicked}); } + render() { - var filenames = this.props.filenames; + const postFiles = []; + if (this.props.fileInfos && this.props.fileInfos.length > 0) { + for (let i = 0; i < Math.min(this.props.fileInfos.length, Constants.MAX_DISPLAY_FILES); i++) { + const fileInfo = this.props.fileInfos[i]; - var postFiles = []; - for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { - postFiles.push( - <FileAttachment - key={'file_attachment_' + i} - filename={filenames[i]} - index={i} - handleImageClick={this.handleImageClick} - compactDisplay={this.props.compactDisplay} - /> - ); + postFiles.push( + <FileAttachment + key={fileInfo.id} + fileInfo={this.props.fileInfos[i]} + index={i} + handleImageClick={this.handleImageClick} + compactDisplay={this.props.compactDisplay} + /> + ); + } + } else if (this.props.fileCount > 0) { + for (let i = 0; i < Math.min(this.props.fileCount, Constants.MAX_DISPLAY_FILES); i++) { + // Add a placeholder to avoid pop-in once we get the file infos for this post + postFiles.push(<div className='post-image__column post-image__column--placeholder'/>); + } } return ( @@ -42,10 +51,8 @@ export default class FileAttachmentList extends React.Component { <ViewImageModal show={this.state.showPreviewModal} onModalDismissed={() => this.setState({showPreviewModal: false})} - channelId={this.props.channelId} - userId={this.props.userId} - startId={this.state.startImgId} - filenames={filenames} + startId={this.state.startImgIndex} + fileInfos={this.props.fileInfos} /> </div> ); @@ -53,15 +60,7 @@ export default class FileAttachmentList extends React.Component { } FileAttachmentList.propTypes = { - - // a list of file pathes displayed by this - filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, - - // the channel that this is part of - channelId: React.PropTypes.string, - - // the user that owns the post that this is attached to - userId: React.PropTypes.string, - + fileCount: React.PropTypes.number.isRequired, + fileInfos: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, compactDisplay: React.PropTypes.bool }; diff --git a/webapp/components/file_attachment_list_container.jsx b/webapp/components/file_attachment_list_container.jsx new file mode 100644 index 000000000..f9ad3814c --- /dev/null +++ b/webapp/components/file_attachment_list_container.jsx @@ -0,0 +1,90 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import * as AsyncClient from 'utils/async_client.jsx'; +import FileStore from 'stores/file_store.jsx'; + +import FileAttachmentList from './file_attachment_list.jsx'; + +export default class FileAttachmentListContainer extends React.Component { + static propTypes = { + post: React.PropTypes.object.isRequired, + compactDisplay: React.PropTypes.bool.isRequired + } + + constructor(props) { + super(props); + + this.handleFileChange = this.handleFileChange.bind(this); + + this.state = { + fileInfos: FileStore.getInfosForPost(props.post.id) + }; + } + + componentDidMount() { + FileStore.addChangeListener(this.handleFileChange); + + if (this.props.post.id && !FileStore.hasInfosForPost(this.props.post.id)) { + AsyncClient.getFileInfosForPost(this.props.post.channel_id, this.props.post.id); + } + } + + componentWillReceiveProps(nextProps) { + if (nextProps.post.id !== this.props.post.id) { + this.setState({ + fileInfos: FileStore.getInfosForPost(nextProps.post.id) + }); + + if (nextProps.post.id && !FileStore.hasInfosForPost(nextProps.post.id)) { + AsyncClient.getFileInfosForPost(nextProps.post.channel_id, nextProps.post.id); + } + } + } + + shouldComponentUpdate(nextProps, nextState) { + if (this.props.post.id !== nextProps.post.id) { + return true; + } + + if (this.props.compactDisplay !== nextProps.compactDisplay) { + return true; + } + + // fileInfos are treated as immutable by the FileStore + if (nextState.fileInfos !== this.state.fileInfos) { + return true; + } + + return false; + } + + handleFileChange() { + this.setState({ + fileInfos: FileStore.getInfosForPost(this.props.post.id) + }); + } + + componentWillUnmount() { + FileStore.removeChangeListener(this.handleFileChange); + } + + render() { + let fileCount = 0; + if (this.props.post.file_ids) { + fileCount = this.props.post.file_ids.length; + } else if (this.props.post.filenames) { + fileCount = this.props.post.filenames.length; + } + + return ( + <FileAttachmentList + fileCount={fileCount} + fileInfos={this.state.fileInfos} + compactDisplay={this.props.compactDisplay} + /> + ); + } +} diff --git a/webapp/components/file_info_preview.jsx b/webapp/components/file_info_preview.jsx index b3d16b6a6..51825ce5b 100644 --- a/webapp/components/file_info_preview.jsx +++ b/webapp/components/file_info_preview.jsx @@ -1,59 +1,59 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import React from 'react'; + import * as Utils from 'utils/utils.jsx'; -import {defineMessages} from 'react-intl'; -import React from 'react'; -import {Link} from 'react-router/es6'; - -const holders = defineMessages({ - type: { - id: 'file_info_preview.type', - defaultMessage: 'File type ' - }, - size: { - id: 'file_info_preview.size', - defaultMessage: 'Size ' - } -}); +export default class FileInfoPreview extends React.Component { + shouldComponentUpdate(nextProps) { + if (nextProps.fileUrl !== this.props.fileUrl) { + return true; + } -export default function FileInfoPreview({filename, fileUrl, fileInfo, formatMessage}) { - // non-image files include a section providing details about the file - const infoParts = []; + if (!Utils.areObjectsEqual(nextProps.fileInfo, this.props.fileInfo)) { + return true; + } - if (fileInfo.extension !== '') { - infoParts.push(formatMessage(holders.type) + fileInfo.extension.toUpperCase()); + return false; } - infoParts.push(formatMessage(holders.size) + Utils.fileSizeToString(fileInfo.size)); - - const infoString = infoParts.join(', '); - - const name = decodeURIComponent(Utils.getFileName(filename)); - - return ( - <div className='file-details__container'> - <Link - className={'file-details__preview'} - to={fileUrl} - target='_blank' - rel='noopener noreferrer' - > - <span className='file-details__preview-helper'/> - <img src={Utils.getPreviewImagePath(filename)}/> - </Link> - <div className='file-details'> - <div className='file-details__name'>{name}</div> - <div className='file-details__info'>{infoString}</div> + render() { + const fileInfo = this.props.fileInfo; + const fileUrl = this.props.fileUrl; + + // non-image files include a section providing details about the file + const infoParts = []; + + if (fileInfo.extension !== '') { + infoParts.push(Utils.localizeMessage('file_info_preview.type', 'File type ') + fileInfo.extension.toUpperCase()); + } + + infoParts.push(Utils.localizeMessage('file_info_preview.size', 'Size ') + Utils.fileSizeToString(fileInfo.size)); + + const infoString = infoParts.join(', '); + + return ( + <div className='file-details__container'> + <a + className={'file-details__preview'} + to={fileUrl} + target='_blank' + rel='noopener noreferrer' + > + <span className='file-details__preview-helper'/> + <img src={Utils.getFileIconPath(fileInfo)}/> + </a> + <div className='file-details'> + <div className='file-details__name'>{fileInfo.name}</div> + <div className='file-details__info'>{infoString}</div> + </div> </div> - </div> - ); + ); + } } FileInfoPreview.propTypes = { - filename: React.PropTypes.string.isRequired, - fileUrl: React.PropTypes.string.isRequired, fileInfo: React.PropTypes.object.isRequired, - formatMessage: React.PropTypes.func.isRequired + fileUrl: React.PropTypes.string.isRequired }; diff --git a/webapp/components/file_preview.jsx b/webapp/components/file_preview.jsx index 46ce43a6f..53cec7f7b 100644 --- a/webapp/components/file_preview.jsx +++ b/webapp/components/file_preview.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import FileStore from 'stores/file_store.jsx'; import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; @@ -21,63 +22,43 @@ export default class FilePreview extends React.Component { } } - handleRemove(e) { - var previewDiv = e.target.parentNode.parentNode; - - if (previewDiv.hasAttribute('data-filename')) { - this.props.onRemove(previewDiv.getAttribute('data-filename')); - } else if (previewDiv.hasAttribute('data-client-id')) { - this.props.onRemove(previewDiv.getAttribute('data-client-id')); - } + handleRemove(id) { + this.props.onRemove(id); } render() { var previews = []; - this.props.files.forEach((fullFilename) => { - var filename = fullFilename; - var originalFilename = filename; - var filenameSplit = filename.split('.'); - var ext = filenameSplit[filenameSplit.length - 1]; - var type = Utils.getFileType(ext); - - filename = Utils.getFileUrl(filename); + this.props.fileInfos.forEach((info) => { + const type = Utils.getFileType(info.extension); + let className = 'file-preview'; + let previewImage; if (type === 'image') { - previews.push( - <div - key={filename} - className='file-preview' - data-filename={originalFilename} - > - <img - className='file-preview__image' - src={filename} - /> - <a - className='file-preview__remove' - onClick={this.handleRemove} - > - <i className='fa fa-remove'/> - </a> - </div> + previewImage = ( + <img + className='file-preview__image' + src={FileStore.getFileUrl(info.id)} + /> ); } else { - previews.push( - <div - key={filename} - className='file-preview custom-file' - data-filename={originalFilename} - > - <div className={'file-icon ' + Utils.getIconClassName(type)}/> - <a - className='file-preview__remove' - onClick={this.handleRemove} - > - <i className='fa fa-remove'/> - </a> - </div> - ); + className += ' custom-file'; + previewImage = <div className={'file-icon ' + Utils.getIconClassName(type)}/>; } + + previews.push( + <div + key={info.id} + className={className} + > + {previewImage} + <a + className='file-preview__remove' + onClick={this.handleRemove.bind(this, info.id)} + > + <i className='fa fa-remove'/> + </a> + </div> + ); }); this.props.uploadsInProgress.forEach((clientId) => { @@ -94,7 +75,7 @@ export default class FilePreview extends React.Component { /> <a className='file-preview__remove' - onClick={this.handleRemove} + onClick={this.handleRemove.bind(this, clientId)} > <i className='fa fa-remove'/> </a> @@ -111,11 +92,11 @@ export default class FilePreview extends React.Component { } FilePreview.defaultProps = { - files: [], + fileInfos: [], uploadsInProgress: [] }; FilePreview.propTypes = { onRemove: React.PropTypes.func.isRequired, - files: React.PropTypes.array, + fileInfos: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, uploadsInProgress: React.PropTypes.array }; diff --git a/webapp/components/file_upload.jsx b/webapp/components/file_upload.jsx index 39abec7e4..9eff25ab5 100644 --- a/webapp/components/file_upload.jsx +++ b/webapp/components/file_upload.jsx @@ -49,13 +49,12 @@ class FileUpload extends React.Component { this.keyUpload = this.keyUpload.bind(this); this.state = { - maxFileSize: global.window.mm_config.MaxFileSize, requests: {} }; } fileUploadSuccess(channelId, data) { - this.props.onFileUpload(data.filenames, data.client_ids, channelId); + this.props.onFileUpload(data.file_infos, data.client_ids, channelId); const requests = Object.assign({}, this.state.requests); for (var j = 0; j < data.client_ids.length; j++) { @@ -81,7 +80,7 @@ class FileUpload extends React.Component { const tooLargeFiles = []; for (let i = 0; i < files.length && numUploads < uploadsRemaining; i++) { - if (files[i].size > this.state.maxFileSize) { + if (files[i].size > global.mm_config.MaxFileSize) { tooLargeFiles.push(files[i]); continue; } @@ -112,9 +111,9 @@ class FileUpload extends React.Component { } else if (tooLargeFiles.length > 1) { var tooLargeFilenames = tooLargeFiles.map((file) => file.name).join(', '); - this.props.onUploadError(formatMessage(holders.filesAbove, {max: (this.state.maxFileSize / 1048576), filenames: tooLargeFilenames})); + this.props.onUploadError(formatMessage(holders.filesAbove, {max: (global.mm_config.MaxFileSize / 1048576), filenames: tooLargeFilenames})); } else if (tooLargeFiles.length > 0) { - this.props.onUploadError(formatMessage(holders.fileAbove, {max: (this.state.maxFileSize / 1048576), filename: tooLargeFiles[0].name})); + this.props.onUploadError(formatMessage(holders.fileAbove, {max: (global.mm_config.MaxFileSize / 1048576), filename: tooLargeFiles[0].name})); } } diff --git a/webapp/components/get_public_link_modal.jsx b/webapp/components/get_public_link_modal.jsx index 49fd891be..851a78f80 100644 --- a/webapp/components/get_public_link_modal.jsx +++ b/webapp/components/get_public_link_modal.jsx @@ -23,7 +23,7 @@ export default class GetPublicLinkModal extends React.Component { this.state = { show: false, - filename: '', + fileId: '', link: '' }; } @@ -34,7 +34,7 @@ export default class GetPublicLinkModal extends React.Component { componentDidUpdate(prevProps, prevState) { if (this.state.show && !prevState.show) { - AsyncClient.getPublicLink(decodeURIComponent(this.state.filename), this.handlePublicLink); + AsyncClient.getPublicLink(this.state.fileId, this.handlePublicLink); } } @@ -51,7 +51,7 @@ export default class GetPublicLinkModal extends React.Component { handleToggle(value, args) { this.setState({ show: value, - filename: args.filename, + fileId: args.fileId, link: '' }); } diff --git a/webapp/components/pdf_preview.jsx b/webapp/components/pdf_preview.jsx index 7f0f06c03..2cb0a324c 100644 --- a/webapp/components/pdf_preview.jsx +++ b/webapp/components/pdf_preview.jsx @@ -3,8 +3,6 @@ import FileInfoPreview from './file_info_preview.jsx'; -import * as Utils from 'utils/utils.jsx'; - import loadingGif from 'images/load.gif'; import React from 'react'; @@ -109,18 +107,8 @@ export default class PDFPreview extends React.Component { } } - static support(filename) { - const fileInfo = Utils.splitFileLocation(filename); - const ext = fileInfo.ext; - if (!ext) { - return false; - } - - if (ext === 'pdf') { - return true; - } - - return false; + static supports(fileInfo) { + return fileInfo.extension === 'pdf'; } render() { @@ -138,10 +126,8 @@ export default class PDFPreview extends React.Component { if (!this.state.success) { return ( <FileInfoPreview - filename={this.props.filename} - fileUrl={this.props.fileUrl} fileInfo={this.props.fileInfo} - formatMessage={this.props.formatMessage} + fileUrl={this.props.fileUrl} /> ); } @@ -185,8 +171,6 @@ export default class PDFPreview extends React.Component { } PDFPreview.propTypes = { - filename: React.PropTypes.string.isRequired, - fileUrl: React.PropTypes.string.isRequired, fileInfo: React.PropTypes.object.isRequired, - formatMessage: React.PropTypes.func.isRequired + fileUrl: React.PropTypes.string.isRequired }; diff --git a/webapp/components/post_view/components/commented_on_files_message_container.jsx b/webapp/components/post_view/components/commented_on_files_message_container.jsx new file mode 100644 index 000000000..5325a7644 --- /dev/null +++ b/webapp/components/post_view/components/commented_on_files_message_container.jsx @@ -0,0 +1,88 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import * as AsyncClient from 'utils/async_client.jsx'; +import FileStore from 'stores/file_store.jsx'; +import * as Utils from 'utils/utils.jsx'; + +export default class CommentedOnFilesMessageContainer extends React.Component { + static propTypes = { + parentPostChannelId: React.PropTypes.string.isRequired, + parentPostId: React.PropTypes.string.isRequired + } + + constructor(props) { + super(props); + + this.handleFileChange = this.handleFileChange.bind(this); + + this.state = { + fileInfos: FileStore.getInfosForPost(this.props.parentPostId) + }; + } + + componentDidMount() { + FileStore.addChangeListener(this.handleFileChange); + + if (!FileStore.hasInfosForPost(this.props.parentPostId)) { + AsyncClient.getFileInfosForPost(this.props.parentPostChannelId, this.props.parentPostId); + } + } + + componentWillReceiveProps(nextProps) { + if (nextProps.parentPostId !== this.props.parentPostId) { + this.setState({ + fileInfos: FileStore.getInfosForPost(this.props.parentPostId) + }); + + if (!FileStore.hasInfosForPost(this.props.parentPostId)) { + AsyncClient.getFileInfosForPost(this.props.parentPostChannelId, this.props.parentPostId); + } + } + } + + shouldComponentUpdate(nextProps, nextState) { + if (nextProps.parentPostId !== this.props.parentPostId) { + return true; + } + + if (nextProps.parentPostChannelId !== this.props.parentPostChannelId) { + return true; + } + + // fileInfos are treated as immutable by the FileStore + if (nextState.fileInfos !== this.state.fileInfos) { + return true; + } + + return false; + } + + handleFileChange() { + this.setState({ + fileInfos: FileStore.getInfosForPost(this.props.parentPostId) + }); + } + + componentWillUnmount() { + FileStore.removeChangeListener(this.handleFileChange); + } + + render() { + let message = ' '; + + if (this.state.fileInfos && this.state.fileInfos.length > 0) { + message = this.state.fileInfos[0].name; + + if (this.state.fileInfos.length === 2) { + message += Utils.localizeMessage('post_body.plusOne', ' plus 1 other file'); + } else if (this.state.fileInfos.length > 2) { + message += Utils.localizeMessage('post_body.plusMore', ' plus {count} other files').replace('{count}', (this.state.fileInfos.length - 1).toString()); + } + } + + return <span>{message}</span>; + } +} diff --git a/webapp/components/post_view/components/post_body.jsx b/webapp/components/post_view/components/post_body.jsx index 5c02e9c40..c23939c1f 100644 --- a/webapp/components/post_view/components/post_body.jsx +++ b/webapp/components/post_view/components/post_body.jsx @@ -1,11 +1,12 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import FileAttachmentList from 'components/file_attachment_list.jsx'; import UserStore from 'stores/user_store.jsx'; import * as Utils from 'utils/utils.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; import Constants from 'utils/constants.jsx'; +import CommentedOnFilesMessageContainer from './commented_on_files_message_container.jsx'; +import FileAttachmentListContainer from 'components/file_attachment_list_container.jsx'; import PostBodyAdditionalContent from './post_body_additional_content.jsx'; import PostMessageContainer from './post_message_container.jsx'; import PendingPostOptions from './pending_post_options.jsx'; @@ -22,6 +23,7 @@ export default class PostBody extends React.Component { this.removePost = this.removePost.bind(this); } + shouldComponentUpdate(nextProps) { if (nextProps.isCommentMention !== this.props.isCommentMention) { return true; @@ -56,7 +58,6 @@ export default class PostBody extends React.Component { render() { const post = this.props.post; - const filenames = this.props.post.filenames; const parentPost = this.props.parentPost; let comment = ''; @@ -94,14 +95,13 @@ export default class PostBody extends React.Component { let message = ''; if (parentPost.message) { message = Utils.replaceHtmlEntities(parentPost.message); - } else if (parentPost.filenames.length) { - message = parentPost.filenames[0].split('/').pop(); - - if (parentPost.filenames.length === 2) { - message += Utils.localizeMessage('post_body.plusOne', ' plus 1 other file'); - } else if (parentPost.filenames.length > 2) { - message += Utils.localizeMessage('post_body.plusMore', ' plus {count} other files').replace('{count}', (parentPost.filenames.length - 1).toString()); - } + } else if (parentPost.file_ids && parentPost.file_ids.length > 0) { + message = ( + <CommentedOnFilesMessageContainer + parentPostChannelId={parentPost.channel_id} + parentPostId={parentPost.id} + /> + ); } comment = ( @@ -140,14 +140,11 @@ export default class PostBody extends React.Component { ); } - let fileAttachmentHolder = ''; - if (filenames && filenames.length > 0) { + let fileAttachmentHolder = null; + if ((post.file_ids && post.file_ids.length > 0) || (post.filenames && post.filenames.length > 0)) { fileAttachmentHolder = ( - <FileAttachmentList - - filenames={filenames} - channelId={post.channel_id} - userId={post.user_id} + <FileAttachmentListContainer + post={post} compactDisplay={this.props.compactDisplay} /> ); diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx index e1af1227b..18e4b4d1c 100644 --- a/webapp/components/rhs_comment.jsx +++ b/webapp/components/rhs_comment.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import UserProfile from './user_profile.jsx'; -import FileAttachmentList from './file_attachment_list.jsx'; +import FileAttachmentListContainer from './file_attachment_list_container.jsx'; import PendingPostOptions from 'components/post_view/components/pending_post_options.jsx'; import PostMessageContainer from 'components/post_view/components/post_message_container.jsx'; import ProfilePicture from 'components/profile_picture.jsx'; @@ -295,13 +295,11 @@ export default class RhsComment extends React.Component { var dropdown = this.createDropdown(); - var fileAttachment; - if (post.filenames && post.filenames.length > 0) { + let fileAttachment = null; + if (post.file_ids && post.file_ids.length > 0) { fileAttachment = ( - <FileAttachmentList - filenames={post.filenames} - channelId={post.channel_id} - userId={post.user_id} + <FileAttachmentListContainer + post={post} compactDisplay={this.props.compactDisplay} /> ); diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx index 09ab17ba5..983469f50 100644 --- a/webapp/components/rhs_root_post.jsx +++ b/webapp/components/rhs_root_post.jsx @@ -4,7 +4,7 @@ import UserProfile from './user_profile.jsx'; import PostBodyAdditionalContent from 'components/post_view/components/post_body_additional_content.jsx'; import PostMessageContainer from 'components/post_view/components/post_message_container.jsx'; -import FileAttachmentList from './file_attachment_list.jsx'; +import FileAttachmentListContainer from './file_attachment_list_container.jsx'; import ProfilePicture from 'components/profile_picture.jsx'; import ChannelStore from 'stores/channel_store.jsx'; @@ -242,13 +242,11 @@ export default class RhsRootPost extends React.Component { ); } - var fileAttachment; - if (post.filenames && post.filenames.length > 0) { + let fileAttachment = null; + if (post.file_ids && post.file_ids.length > 0) { fileAttachment = ( - <FileAttachmentList - filenames={post.filenames} - channelId={post.channel_id} - userId={post.user_id} + <FileAttachmentListContainer + post={post} compactDisplay={this.props.compactDisplay} /> ); diff --git a/webapp/components/view_image.jsx b/webapp/components/view_image.jsx index c9f558725..385138d54 100644 --- a/webapp/components/view_image.jsx +++ b/webapp/components/view_image.jsx @@ -11,7 +11,6 @@ import * as GlobalActions from 'actions/global_actions.jsx'; import FileStore from 'stores/file_store.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -19,19 +18,11 @@ const KeyCodes = Constants.KeyCodes; import $ from 'jquery'; import React from 'react'; -import {intlShape, injectIntl, defineMessages} from 'react-intl'; import {Modal} from 'react-bootstrap'; import loadingGif from 'images/load.gif'; -const holders = defineMessages({ - loading: { - id: 'view_image.loading', - defaultMessage: 'Loading ' - } -}); - -class ViewImageModal extends React.Component { +export default class ViewImageModal extends React.Component { constructor(props) { super(props); @@ -45,18 +36,15 @@ class ViewImageModal extends React.Component { this.onModalShown = this.onModalShown.bind(this); this.onModalHidden = this.onModalHidden.bind(this); - this.onFileStoreChange = this.onFileStoreChange.bind(this); - this.handleGetPublicLink = this.handleGetPublicLink.bind(this); this.onMouseEnterImage = this.onMouseEnterImage.bind(this); this.onMouseLeaveImage = this.onMouseLeaveImage.bind(this); this.state = { imgId: this.props.startId, - fileInfo: null, imgHeight: '100%', - loaded: Utils.fillArray(false, this.props.filenames.length), - progress: Utils.fillArray(0, this.props.filenames.length), + loaded: Utils.fillArray(false, this.props.fileInfos.length), + progress: Utils.fillArray(0, this.props.fileInfos.length), showFooter: false }; } @@ -66,7 +54,7 @@ class ViewImageModal extends React.Component { e.stopPropagation(); } let id = this.state.imgId + 1; - if (id > this.props.filenames.length - 1) { + if (id > this.props.fileInfos.length - 1) { id = 0; } this.showImage(id); @@ -78,7 +66,7 @@ class ViewImageModal extends React.Component { } let id = this.state.imgId - 1; if (id < 0) { - id = this.props.filenames.length - 1; + id = this.props.fileInfos.length - 1; } this.showImage(id); } @@ -95,8 +83,6 @@ class ViewImageModal extends React.Component { $(window).on('keyup', this.handleKeyPress); this.showImage(nextProps.startId); - - FileStore.addChangeListener(this.onFileStoreChange); } onModalHidden() { @@ -105,8 +91,6 @@ class ViewImageModal extends React.Component { if (this.refs.video) { this.refs.video.stop(); } - - FileStore.removeChangeListener(this.onFileStoreChange); } componentWillReceiveProps(nextProps) { @@ -116,64 +100,36 @@ class ViewImageModal extends React.Component { this.onModalHidden(); } - if (!Utils.areObjectsEqual(this.props.filenames, nextProps.filenames)) { + if (this.props.fileInfos !== nextProps.fileInfos) { this.setState({ - loaded: Utils.fillArray(false, nextProps.filenames.length), - progress: Utils.fillArray(0, nextProps.filenames.length) + loaded: Utils.fillArray(false, nextProps.fileInfos.length), + progress: Utils.fillArray(0, nextProps.fileInfos.length) }); } } - onFileStoreChange(filename) { - const id = this.props.filenames.indexOf(filename); - - if (id !== -1) { - if (id === this.state.imgId) { - this.setState({ - fileInfo: FileStore.getInfo(filename) - }); - } - - if (!this.state.loaded[id]) { - this.loadImage(id, filename); - } - } - } - showImage(id) { this.setState({imgId: id}); const imgHeight = $(window).height() - 100; this.setState({imgHeight}); - const filename = this.props.filenames[id]; - - if (!FileStore.hasInfo(filename)) { - // the image will actually be loaded once we know what we need to load - AsyncClient.getFileInfo(filename); - return; - } - - this.setState({ - fileInfo: FileStore.getInfo(filename) - }); - if (!this.state.loaded[id]) { - this.loadImage(id, filename); + this.loadImage(id); } } - loadImage(id, filename) { - const fileInfo = FileStore.getInfo(filename); + loadImage(index) { + const fileInfo = this.props.fileInfos[index]; const fileType = Utils.getFileType(fileInfo.extension); if (fileType === 'image') { let previewUrl; if (fileInfo.has_image_preview) { - previewUrl = Utils.getPreviewImagePath(filename); + previewUrl = FileStore.getFilePreviewUrl(fileInfo.id); } else { // some images (eg animated gifs) just show the file itself and not a preview - previewUrl = Utils.getFileUrl(filename); + previewUrl = FileStore.getFileUrl(fileInfo.id); } const img = new Image(); @@ -181,19 +137,19 @@ class ViewImageModal extends React.Component { previewUrl, () => { const progress = this.state.progress; - progress[id] = img.completedPercentage; + progress[index] = img.completedPercentage; this.setState({progress}); } ); img.onload = () => { const loaded = this.state.loaded; - loaded[id] = true; + loaded[index] = true; this.setState({loaded}); }; } else { // there's nothing to load for non-image files var loaded = this.state.loaded; - loaded[id] = true; + loaded[index] = true; this.setState({loaded}); } } @@ -201,7 +157,7 @@ class ViewImageModal extends React.Component { handleGetPublicLink() { this.props.onModalDismissed(); - GlobalActions.showGetPublicLinkModal(this.props.filenames[this.state.imgId]); + GlobalActions.showGetPublicLinkModal(this.props.fileInfos[this.state.imgId].id); } onMouseEnterImage() { @@ -213,63 +169,52 @@ class ViewImageModal extends React.Component { } render() { - if (this.props.filenames.length < 1 || this.props.filenames.length - 1 < this.state.imgId) { - return <div/>; + if (this.props.fileInfos.length < 1 || this.props.fileInfos.length - 1 < this.state.imgId) { + return null; } - const filename = this.props.filenames[this.state.imgId]; - const fileUrl = Utils.getFileUrl(filename); + const fileInfo = this.props.fileInfos[this.state.imgId]; + const fileUrl = FileStore.getFileUrl(fileInfo.id); - var content; + let content; if (this.state.loaded[this.state.imgId]) { - // this.state.fileInfo is for the current image and we shoudl have it before we load the image - const fileInfo = this.state.fileInfo; const fileType = Utils.getFileType(fileInfo.extension); if (fileType === 'image') { content = ( <ImagePreview - filename={filename} - fileUrl={fileUrl} fileInfo={fileInfo} + fileUrl={fileUrl} maxHeight={this.state.imgHeight} /> ); } else if (fileType === 'video' || fileType === 'audio') { content = ( <AudioVideoPreview - filename={filename} + fileInfo={fileInfo} fileUrl={fileUrl} - fileInfo={this.state.fileInfo} maxHeight={this.state.imgHeight} - formatMessage={this.props.intl.formatMessage} /> ); - } else if (PDFPreview.support(filename)) { + } else if (PDFPreview.supports(fileInfo)) { content = ( <PDFPreview - filename={filename} - fileUrl={fileUrl} fileInfo={fileInfo} - formatMessage={this.props.intl.formatMessage} + fileUrl={fileUrl} /> ); - } else if (CodePreview.support(filename)) { + } else if (CodePreview.supports(fileInfo)) { content = ( <CodePreview - filename={filename} - fileUrl={fileUrl} fileInfo={fileInfo} - formatMessage={this.props.intl.formatMessage} + fileUrl={fileUrl} /> ); } else { content = ( <FileInfoPreview - filename={filename} - fileUrl={fileUrl} fileInfo={fileInfo} - formatMessage={this.props.intl.formatMessage} + fileUrl={fileUrl} /> ); } @@ -280,14 +225,14 @@ class ViewImageModal extends React.Component { content = ( <LoadingImagePreview progress={progress} - loading={this.props.intl.formatMessage(holders.loading)} + loading={Utils.localizeMessage('view_image.loading', 'Loading ')} /> ); } let leftArrow = null; let rightArrow = null; - if (this.props.filenames.length > 1) { + if (this.props.fileInfos.length > 1) { leftArrow = ( <a ref='previewArrowLeft' @@ -346,8 +291,8 @@ class ViewImageModal extends React.Component { <ViewImagePopoverBar show={this.state.showFooter} fileId={this.state.imgId} - totalFiles={this.props.filenames.length} - filename={name} + totalFiles={this.props.fileInfos.length} + filename={fileInfo.name} fileURL={fileUrl} onGetPublicLink={this.handleGetPublicLink} /> @@ -363,19 +308,13 @@ class ViewImageModal extends React.Component { ViewImageModal.defaultProps = { show: false, - filenames: [], - channelId: '', - userId: '', + fileInfos: [], startId: 0 }; ViewImageModal.propTypes = { - intl: intlShape.isRequired, show: React.PropTypes.bool.isRequired, onModalDismissed: React.PropTypes.func.isRequired, - filenames: React.PropTypes.array, - modalId: React.PropTypes.string, - channelId: React.PropTypes.string, - userId: React.PropTypes.string, + fileInfos: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, startId: React.PropTypes.number }; @@ -405,10 +344,10 @@ LoadingImagePreview.propTypes = { loading: React.PropTypes.string }; -function ImagePreview({filename, fileUrl, fileInfo, maxHeight}) { +function ImagePreview({fileInfo, fileUrl, maxHeight}) { let previewUrl; if (fileInfo.has_preview_image) { - previewUrl = Utils.getPreviewImagePath(filename); + previewUrl = FileStore.getFilePreviewUrl(fileInfo.id); } else { previewUrl = fileUrl; } @@ -429,10 +368,7 @@ function ImagePreview({filename, fileUrl, fileInfo, maxHeight}) { } ImagePreview.propTypes = { - filename: React.PropTypes.string.isRequired, - fileUrl: React.PropTypes.string.isRequired, fileInfo: React.PropTypes.object.isRequired, + fileUrl: React.PropTypes.string.isRequired, maxHeight: React.PropTypes.number.isRequired }; - -export default injectIntl(ViewImageModal); |