Merge pull request #1815 from hmhealey/plt771

PLT-771 Improved Audio/Video preview for unsupported formats
This commit is contained in:
Corey Hulen
2016-01-06 14:47:25 -06:00
6 changed files with 186 additions and 97 deletions

View File

@@ -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
};

View File

@@ -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;

View File

@@ -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>
);
}

View File

@@ -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(

View File

@@ -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}

View File

@@ -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.