PLT-3105 Files table migration (#4068)
* Implemented initial changes for files table * Removed *_benchmark_test.go files * Re-implemented GetPublicFile and added support for old path * Localization for files table * Moved file system code into utils package * Finished server-side changes and added initial upgrade script * Added getPostFiles api * Re-add Extension and HasPreviewImage fields to FileInfo * Removed unused translation * Fixed merge conflicts left over after permissions changes * Forced FileInfo.extension to be lower case * Changed FileUploadResponse to contain the FileInfos instead of FileIds * Fixed permissions on getFile* calls * Fixed notifications for file uploads * Added initial version of client code for files changes * Permanently added FileIds field to Post object and removed Post.HasFiles * Updated PostStore.Update to be usable in more circumstances * Re-added Filenames field and switched file migration to be entirely lazy-loaded * Increased max listener count for FileStore * Removed unused fileInfoCache * Moved file system code back into api * Removed duplicate test case * Fixed unit test running on ports other than 8065 * Renamed HasPermissionToPostContext to HasPermissionToChannelByPostContext * Refactored handleImages to make it more easily understandable * Renamed getPostFiles to getFileInfosForPost * Re-added pre-FileIds posts to analytics * Changed files to be saved as their ids as opposed to id/filename.ext * Renamed FileInfo.UserId to FileInfo.CreatorId * Fixed detection of language in CodePreview * Fixed switching between threads in the RHS not loading new files * Add serverside protection against a rare bug where the client sends the same file twice for a single post * Refactored the important parts of uploadFile api call into a function that can be called without a web context
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
a2deeed597
Коммит
8a0e649f98
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.fileInfo.id !== this.props.fileInfo.id) {
|
||||
this.setState({
|
||||
loaded: Utils.getFileType(nextProps.fileInfo.extension) !== 'image'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.filename !== prevProps.filename) {
|
||||
if (!this.state.loaded && this.props.fileInfo.id !== prevProps.fileInfo.id) {
|
||||
this.loadFiles();
|
||||
}
|
||||
}
|
||||
|
||||
loadFiles() {
|
||||
this.canSetState = true;
|
||||
const fileInfo = this.props.fileInfo;
|
||||
const fileType = Utils.getFileType(fileInfo.extension);
|
||||
|
||||
var filename = this.props.filename;
|
||||
if (fileType === 'image') {
|
||||
const thumbnailUrl = FileStore.getFileThumbnailUrl(fileInfo.id);
|
||||
|
||||
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)));
|
||||
}
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
this.setState({loaded: true});
|
||||
};
|
||||
img.load(thumbnailUrl);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
$(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)');
|
||||
}
|
||||
}
|
||||
removeBackgroundImage(name) {
|
||||
if (name in this.refs) {
|
||||
$(ReactDOM.findDOMNode(this.refs[name])).css('background-image', 'initial');
|
||||
}
|
||||
}
|
||||
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);
|
||||
};
|
||||
@@ -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});
|
||||
}
|
||||
render() {
|
||||
var filenames = this.props.filenames;
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
handleImageClick(indexClicked) {
|
||||
this.setState({showPreviewModal: true, startImgIndex: indexClicked});
|
||||
}
|
||||
|
||||
render() {
|
||||
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];
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
90
webapp/components/file_attachment_list_container.jsx
Обычный файл
90
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
export default class FileInfoPreview extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (nextProps.fileUrl !== this.props.fileUrl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const holders = defineMessages({
|
||||
type: {
|
||||
id: 'file_info_preview.type',
|
||||
defaultMessage: 'File type '
|
||||
},
|
||||
size: {
|
||||
id: 'file_info_preview.size',
|
||||
defaultMessage: 'Size '
|
||||
}
|
||||
});
|
||||
if (!Utils.areObjectsEqual(nextProps.fileInfo, this.props.fileInfo)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export default function FileInfoPreview({filename, fileUrl, fileInfo, formatMessage}) {
|
||||
// non-image files include a section providing details about the file
|
||||
const infoParts = [];
|
||||
|
||||
if (fileInfo.extension !== '') {
|
||||
infoParts.push(formatMessage(holders.type) + fileInfo.extension.toUpperCase());
|
||||
return false;
|
||||
}
|
||||
|
||||
infoParts.push(formatMessage(holders.size) + Utils.fileSizeToString(fileInfo.size));
|
||||
render() {
|
||||
const fileInfo = this.props.fileInfo;
|
||||
const fileUrl = this.props.fileUrl;
|
||||
|
||||
const infoString = infoParts.join(', ');
|
||||
// non-image files include a section providing details about the file
|
||||
const infoParts = [];
|
||||
|
||||
const name = decodeURIComponent(Utils.getFileName(filename));
|
||||
if (fileInfo.extension !== '') {
|
||||
infoParts.push(Utils.localizeMessage('file_info_preview.type', 'File type ') + fileInfo.extension.toUpperCase());
|
||||
}
|
||||
|
||||
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>
|
||||
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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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: ''
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Ссылка в новой задаче
Block a user