diff options
author | Corey Hulen <corey@hulen.com> | 2016-01-06 14:47:25 -0600 |
---|---|---|
committer | Corey Hulen <corey@hulen.com> | 2016-01-06 14:47:25 -0600 |
commit | fe7d27ba4b04537e39357227bcc55f8b92ee6ee2 (patch) | |
tree | 53fec096f1e388538553067e545d01189c8edaf2 | |
parent | 0eb145218edb37d911cc5ec135b0006f866a44b2 (diff) | |
parent | a6ba1a17fe22a72d7058621cb29e9f60d9de0a08 (diff) | |
download | chat-fe7d27ba4b04537e39357227bcc55f8b92ee6ee2.tar.gz chat-fe7d27ba4b04537e39357227bcc55f8b92ee6ee2.tar.bz2 chat-fe7d27ba4b04537e39357227bcc55f8b92ee6ee2.zip |
Merge pull request #1815 from hmhealey/plt771
PLT-771 Improved Audio/Video preview for unsupported formats
-rw-r--r-- | web/react/components/audio_video_preview.jsx | 114 | ||||
-rw-r--r-- | web/react/components/file_attachment.jsx | 4 | ||||
-rw-r--r-- | web/react/components/file_info_preview.jsx | 31 | ||||
-rw-r--r-- | web/react/components/file_preview.jsx | 7 | ||||
-rw-r--r-- | web/react/components/view_image.jsx | 104 | ||||
-rw-r--r-- | web/react/utils/utils.jsx | 23 |
6 files changed, 186 insertions, 97 deletions
diff --git a/web/react/components/audio_video_preview.jsx b/web/react/components/audio_video_preview.jsx new file mode 100644 index 000000000..7d00fbdaa --- /dev/null +++ b/web/react/components/audio_video_preview.jsx @@ -0,0 +1,114 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import Constants from '../utils/constants.jsx'; +import FileInfoPreview from './file_info_preview.jsx'; +import * as Utils from '../utils/utils.jsx'; + +export default class AudioVideoPreview extends React.Component { + constructor(props) { + super(props); + + this.handleFileInfoChanged = this.handleFileInfoChanged.bind(this); + this.handleLoadError = this.handleLoadError.bind(this); + + this.stop = this.stop.bind(this); + + this.state = { + canPlay: true + }; + } + + componentWillMount() { + this.handleFileInfoChanged(this.props.fileInfo); + } + + componentDidMount() { + if (this.refs.source) { + $(ReactDOM.findDOMNode(this.refs.source)).one('error', this.handleLoadError); + } + } + + componentWillReceiveProps(nextProps) { + if (this.props.fileUrl !== nextProps.fileUrl) { + this.handleFileInfoChanged(nextProps.fileInfo); + } + } + + handleFileInfoChanged(fileInfo) { + let video = ReactDOM.findDOMNode(this.refs.video); + if (!video) { + video = document.createElement('video'); + } + + const canPlayType = video.canPlayType(fileInfo.mime_type); + + this.setState({ + canPlay: canPlayType === 'probably' || canPlayType === 'maybe' + }); + } + + componentDidUpdate() { + if (this.refs.source) { + $(ReactDOM.findDOMNode(this.refs.source)).one('error', this.handleLoadError); + } + } + + handleLoadError() { + this.setState({ + canPlay: false + }); + } + + stop() { + if (this.refs.video) { + const video = ReactDOM.findDOMNode(this.refs.video); + video.pause(); + video.currentTime = 0; + } + } + + render() { + if (!this.state.canPlay) { + return ( + <FileInfoPreview + filename={this.props.filename} + fileUrl={this.props.fileUrl} + fileInfo={this.props.fileInfo} + /> + ); + } + + let width = Constants.WEB_VIDEO_WIDTH; + let height = Constants.WEB_VIDEO_HEIGHT; + if (Utils.isMobile()) { + width = Constants.MOBILE_VIDEO_WIDTH; + height = Constants.MOBILE_VIDEO_HEIGHT; + } + + // 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} + ref='video' + style={{maxHeight: this.props.maxHeight}} + data-setup='{}' + controls='controls' + width={width} + height={height} + > + <source + ref='source' + src={this.props.fileUrl} + /> + </video> + ); + } +} + +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 +}; diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index 2474b3d8a..c10269680 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -125,10 +125,6 @@ export default class FileAttachment extends React.Component { getFileInfoFromName(name) { var fileInfo = utils.splitFileLocation(name); - // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) { - fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1]; - } fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path; return fileInfo; diff --git a/web/react/components/file_info_preview.jsx b/web/react/components/file_info_preview.jsx new file mode 100644 index 000000000..4b76cd162 --- /dev/null +++ b/web/react/components/file_info_preview.jsx @@ -0,0 +1,31 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Utils from '../utils/utils.jsx'; + +export default function FileInfoPreview({filename, fileUrl, fileInfo}) { + // non-image files include a section providing details about the file + let infoString = 'File type ' + fileInfo.extension.toUpperCase(); + if (fileInfo.size > 0) { + infoString += ', Size ' + Utils.fileSizeToString(fileInfo.size); + } + + const name = decodeURIComponent(Utils.getFileName(filename)); + + return ( + <div className='file-details__container'> + <a + className={'file-details__preview'} + href={fileUrl} + target='_blank' + > + <span className='file-details__preview-helper' /> + <img src={Utils.getPreviewImagePath(filename)}/> + </a> + <div className='file-details'> + <div className='file-details__name'>{name}</div> + <div className='file-details__info'>{infoString}</div> + </div> + </div> + ); +} diff --git a/web/react/components/file_preview.jsx b/web/react/components/file_preview.jsx index d625a811e..265d3f367 100644 --- a/web/react/components/file_preview.jsx +++ b/web/react/components/file_preview.jsx @@ -35,12 +35,7 @@ export default class FilePreview extends React.Component { var ext = filenameSplit[filenameSplit.length - 1]; var type = Utils.getFileType(ext); - // This is a temporary patch to fix issue with old files using absolute paths - - if (filename.indexOf('/api/v1/files/get') !== -1) { - filename = filename.split('/api/v1/files/get')[1]; - } - filename = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename + '?' + Utils.getSessionIndex(); + filename = Utils.getFileUrl(filename); if (type === 'image') { previews.push( diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index 196a44bd0..31ec91248 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -4,7 +4,9 @@ import * as AsyncClient from '../utils/async_client.jsx'; import * as Client from '../utils/client.jsx'; import * as Utils from '../utils/utils.jsx'; +import AudioVideoPreview from './audio_video_preview.jsx'; import Constants from '../utils/constants.jsx'; +import FileInfoPreview from './file_info_preview.jsx'; import FileStore from '../stores/file_store.jsx'; import ViewImagePopoverBar from './view_image_popover_bar.jsx'; const Modal = ReactBootstrap.Modal; @@ -27,7 +29,6 @@ export default class ViewImageModal extends React.Component { this.onFileStoreChange = this.onFileStoreChange.bind(this); this.getPublicLink = this.getPublicLink.bind(this); - this.getPreviewImagePath = this.getPreviewImagePath.bind(this); this.onMouseEnterImage = this.onMouseEnterImage.bind(this); this.onMouseLeaveImage = this.onMouseLeaveImage.bind(this); @@ -83,9 +84,7 @@ export default class ViewImageModal extends React.Component { $(window).off('keyup', this.handleKeyPress); if (this.refs.video) { - var video = ReactDOM.findDOMNode(this.refs.video); - video.pause(); - video.currentTime = 0; + this.refs.video.stop(); } FileStore.removeChangeListener(this.onFileStoreChange); @@ -152,7 +151,7 @@ export default class ViewImageModal extends React.Component { if (fileType === 'image') { let previewUrl; if (fileInfo.has_image_preview) { - previewUrl = fileInfo.getPreviewImagePath(filename); + previewUrl = Utils.getPreviewImagePath(filename); } else { // some images (eg animated gifs) just show the file itself and not a preview previewUrl = Utils.getFileUrl(filename); @@ -198,25 +197,6 @@ export default class ViewImageModal extends React.Component { ); } - getPreviewImagePath(filename) { - // Returns the path to a preview image that can be used to represent a file. - var fileInfo = Utils.splitFileLocation(filename); - var fileType = Utils.getFileType(fileInfo.ext); - - if (fileType === 'image') { - // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) { - fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1]; - } - fileInfo.path = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path; - - return fileInfo.path + '_preview.jpg?' + Utils.getSessionIndex(); - } - - // only images have proper previews, so just use a placeholder icon for non-images - return Utils.getPreviewImagePathForFileType(fileType); - } - onMouseEnterImage() { this.setState({showFooter: true}); } @@ -237,72 +217,33 @@ export default class ViewImageModal extends React.Component { 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 extension = Utils.splitFileLocation(filename).ext; - const fileType = Utils.getFileType(extension); + const fileType = Utils.getFileType(fileInfo.extension); if (fileType === 'image') { - let previewUrl; - if (fileInfo.has_preview_image) { - previewUrl = this.getPreviewImagePath(filename); - } else { - previewUrl = fileUrl; - } - content = ( <ImagePreview + filename={filename} fileUrl={fileUrl} - previewUrl={previewUrl} + fileInfo={fileInfo} maxHeight={this.state.imgHeight} /> ); } else if (fileType === 'video' || fileType === 'audio') { - let width = Constants.WEB_VIDEO_WIDTH; - let height = Constants.WEB_VIDEO_HEIGHT; - if (Utils.isMobile()) { - width = Constants.MOBILE_VIDEO_WIDTH; - height = Constants.MOBILE_VIDEO_HEIGHT; - } - content = ( - <video - style={{maxHeight: this.state.imgHeight}} - ref='video' - data-setup='{}' - controls='controls' - width={width} - height={height} - > - <source src={Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename + '?' + Utils.getSessionIndex()} /> - </video> + <AudioVideoPreview + filename={filename} + fileUrl={fileUrl} + fileInfo={this.state.fileInfo} + maxHeight={this.state.imgHeight} + /> ); } else { - // non-image files include a section providing details about the file - let infoString = 'File type ' + fileInfo.extension.toUpperCase(); - if (fileInfo.size > 0) { - infoString += ', Size ' + Utils.fileSizeToString(fileInfo.size); - } - - const name = decodeURIComponent(Utils.getFileName(filename)); - content = ( - <div className='file-details__container'> - <a - className={'file-details__preview'} - href={fileUrl} - target='_blank' - > - <span className='file-details__preview-helper' /> - <img - ref='image' - src={this.getPreviewImagePath(filename)} - /> - </a> - <div className='file-details'> - <div className='file-details__name'>{name}</div> - <div className='file-details__info'>{infoString}</div> - </div> - </div> + <FileInfoPreview + filename={filename} + fileUrl={fileUrl} + fileInfo={fileInfo} + /> ); } } else { @@ -424,7 +365,14 @@ function LoadingImagePreview({progress}) { ); } -function ImagePreview({maxHeight, fileUrl, previewUrl}) { +function ImagePreview({filename, fileUrl, fileInfo, maxHeight}) { + let previewUrl; + if (fileInfo.has_preview_image) { + previewUrl = Utils.getPreviewImagePath(filename); + } else { + previewUrl = fileUrl; + } + return ( <a href={fileUrl} diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index a808c9be3..e84fdf671 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -527,6 +527,19 @@ export function splitFileLocation(fileLocation) { return {ext: ext, name: filename, path: filePath}; } +export function getPreviewImagePath(filename) { + // Returns the path to a preview image that can be used to represent a file. + const fileInfo = splitFileLocation(filename); + const fileType = getFileType(fileInfo.ext); + + if (fileType === 'image') { + return getFileUrl(fileInfo.path + '_preview.jpg'); + } + + // only images have proper previews, so just use a placeholder icon for non-images + return getPreviewImagePathForFileType(fileType); +} + export function toTitleCase(str) { function doTitleCase(txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); @@ -1050,15 +1063,7 @@ export function fileSizeToString(bytes) { // Converts a filename (like those attached to Post objects) to a url that can be used to retrieve attachments from the server. export function getFileUrl(filename) { - var url = filename; - - // This is a temporary patch to fix issue with old files using absolute paths - if (url.indexOf('/api/v1/files/get') !== -1) { - url = filename.split('/api/v1/files/get')[1]; - } - url = getWindowLocationOrigin() + '/api/v1/files/get' + url + '?' + getSessionIndex(); - - return url; + return getWindowLocationOrigin() + '/api/v1/files/get' + filename + '?' + getSessionIndex(); } // Gets the name of a file (including extension) from a given url or file path. |