mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Merge pull request #1815 from hmhealey/plt771
PLT-771 Improved Audio/Video preview for unsupported formats
This commit is contained in:
114
web/react/components/audio_video_preview.jsx
Normal file
114
web/react/components/audio_video_preview.jsx
Normal 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
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
31
web/react/components/file_info_preview.jsx
Normal file
31
web/react/components/file_info_preview.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user