summaryrefslogtreecommitdiffstats
path: root/webapp/components/file_upload.jsx
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2016-03-14 08:50:46 -0400
committerChristopher Speller <crspeller@gmail.com>2016-03-16 18:02:55 -0400
commit12896bd23eeba79884245c1c29fdc568cf21a7fa (patch)
tree4e7f83d3e2564b9b89d669e9f7905ff11768b11a /webapp/components/file_upload.jsx
parent29fe6a3d13c9c7aa490fffebbe5d1b5fdf1e3090 (diff)
downloadchat-12896bd23eeba79884245c1c29fdc568cf21a7fa.tar.gz
chat-12896bd23eeba79884245c1c29fdc568cf21a7fa.tar.bz2
chat-12896bd23eeba79884245c1c29fdc568cf21a7fa.zip
Converting to Webpack. Stage 1.
Diffstat (limited to 'webapp/components/file_upload.jsx')
-rw-r--r--webapp/components/file_upload.jsx340
1 files changed, 340 insertions, 0 deletions
diff --git a/webapp/components/file_upload.jsx b/webapp/components/file_upload.jsx
new file mode 100644
index 000000000..8d2ec5ac8
--- /dev/null
+++ b/webapp/components/file_upload.jsx
@@ -0,0 +1,340 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import $ from 'jquery';
+import 'jquery-dragster/jquery.dragster.js';
+import ReactDOM from 'react-dom';
+import * as Client from 'utils/client.jsx';
+import Constants from 'utils/constants.jsx';
+import ChannelStore from 'stores/channel_store.jsx';
+import * as Utils from 'utils/utils.jsx';
+
+import {intlShape, injectIntl, defineMessages} from 'react-intl';
+
+const holders = defineMessages({
+ limited: {
+ id: 'file_upload.limited',
+ defaultMessage: 'Uploads limited to {count} files maximum. Please use additional posts for more files.'
+ },
+ filesAbove: {
+ id: 'file_upload.filesAbove',
+ defaultMessage: 'Files above {max}MB could not be uploaded: {filenames}'
+ },
+ fileAbove: {
+ id: 'file_upload.fileAbove',
+ defaultMessage: 'File above {max}MB could not be uploaded: {filename}'
+ },
+ pasted: {
+ id: 'file_upload.pasted',
+ defaultMessage: 'Image Pasted at '
+ }
+});
+
+import React from 'react';
+
+class FileUpload extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.uploadFiles = this.uploadFiles.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleDrop = this.handleDrop.bind(this);
+ this.cancelUpload = this.cancelUpload.bind(this);
+
+ this.state = {
+ requests: {}
+ };
+ }
+
+ fileUploadSuccess(channelId, data) {
+ this.props.onFileUpload(data.filenames, data.client_ids, channelId);
+
+ var requests = this.state.requests;
+ for (var j = 0; j < data.client_ids.length; j++) {
+ delete requests[data.client_ids[j]];
+ }
+ this.setState({requests});
+ }
+
+ fileUploadFail(clientId, err) {
+ this.props.onUploadError(err, clientId);
+ }
+
+ uploadFiles(files) {
+ // clear any existing errors
+ this.props.onUploadError(null);
+
+ var channelId = this.props.channelId || ChannelStore.getCurrentId();
+
+ var uploadsRemaining = Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId);
+ var numUploads = 0;
+
+ // keep track of how many files have been too large
+ var tooLargeFiles = [];
+
+ for (let i = 0; i < files.length && numUploads < uploadsRemaining; i++) {
+ if (files[i].size > Constants.MAX_FILE_SIZE) {
+ tooLargeFiles.push(files[i]);
+ continue;
+ }
+
+ // generate a unique id that can be used by other components to refer back to this upload
+ const clientId = Utils.generateId();
+
+ // prepare data to be uploaded
+ var formData = new FormData();
+ formData.append('channel_id', channelId);
+ formData.append('files', files[i], files[i].name);
+ formData.append('client_ids', clientId);
+
+ var request = Client.uploadFile(formData,
+ this.fileUploadSuccess.bind(this, channelId),
+ this.fileUploadFail.bind(this, clientId)
+ );
+
+ var requests = this.state.requests;
+ requests[clientId] = request;
+ this.setState({requests});
+
+ this.props.onUploadStart([clientId], channelId);
+
+ numUploads += 1;
+ }
+
+ const {formatMessage} = this.props.intl;
+ if (files.length > uploadsRemaining) {
+ this.props.onUploadError(formatMessage(holders.limited, {count: Constants.MAX_UPLOAD_FILES}));
+ } else if (tooLargeFiles.length > 1) {
+ var tooLargeFilenames = tooLargeFiles.map((file) => file.name).join(', ');
+
+ this.props.onUploadError(formatMessage(holders.filesAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), filenames: tooLargeFilenames}));
+ } else if (tooLargeFiles.length > 0) {
+ this.props.onUploadError(formatMessage(holders.fileAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), filename: tooLargeFiles[0].name}));
+ }
+ }
+
+ handleChange(e) {
+ if (e.target.files.length > 0) {
+ this.uploadFiles(e.target.files);
+
+ Utils.clearFileInput(e.target);
+ }
+ }
+
+ handleDrop(e) {
+ this.props.onUploadError(null);
+
+ var files = e.originalEvent.dataTransfer.files;
+
+ if (typeof files !== 'string' && files.length) {
+ this.uploadFiles(files);
+ }
+ }
+
+ componentDidMount() {
+ var inputDiv = ReactDOM.findDOMNode(this.refs.input);
+ var self = this;
+ const {formatMessage} = this.props.intl;
+
+ if (this.props.postType === 'post') {
+ $('.row.main').dragster({
+ enter(dragsterEvent, e) {
+ var files = e.originalEvent.dataTransfer;
+
+ if (Utils.isFileTransfer(files)) {
+ $('.center-file-overlay').removeClass('hidden');
+ }
+ },
+ leave(dragsterEvent, e) {
+ var files = e.originalEvent.dataTransfer;
+
+ if (Utils.isFileTransfer(files)) {
+ $('.center-file-overlay').addClass('hidden');
+ }
+ },
+ drop(dragsterEvent, e) {
+ $('.center-file-overlay').addClass('hidden');
+ self.handleDrop(e);
+ }
+ });
+ } else if (this.props.postType === 'comment') {
+ $('.post-right__container').dragster({
+ enter(dragsterEvent, e) {
+ var files = e.originalEvent.dataTransfer;
+
+ if (Utils.isFileTransfer(files)) {
+ $('.right-file-overlay').removeClass('hidden');
+ }
+ },
+ leave(dragsterEvent, e) {
+ var files = e.originalEvent.dataTransfer;
+
+ if (Utils.isFileTransfer(files)) {
+ $('.right-file-overlay').addClass('hidden');
+ }
+ },
+ drop(dragsterEvent, e) {
+ $('.right-file-overlay').addClass('hidden');
+ self.handleDrop(e);
+ }
+ });
+ }
+
+ document.addEventListener('paste', (e) => {
+ if (!e.clipboardData) {
+ return;
+ }
+
+ var textarea = $(inputDiv.parentNode.parentNode).find('.custom-textarea')[0];
+
+ if (textarea !== e.target && !$.contains(textarea, e.target)) {
+ return;
+ }
+
+ self.props.onUploadError(null);
+
+ // This looks redundant, but must be done this way due to
+ // setState being an asynchronous call
+ var items = e.clipboardData.items;
+ var numItems = 0;
+ if (items) {
+ for (let i = 0; i < items.length; i++) {
+ if (items[i].type.indexOf('image') !== -1) {
+ var testExt = items[i].type.split('/')[1].toLowerCase();
+
+ if (Constants.IMAGE_TYPES.indexOf(testExt) < 0) {
+ continue;
+ }
+
+ numItems++;
+ }
+ }
+
+ var numToUpload = Math.min(Constants.MAX_UPLOAD_FILES - self.props.getFileCount(ChannelStore.getCurrentId()), numItems);
+
+ if (numItems > numToUpload) {
+ self.props.onUploadError(formatMessage(holders.limited, {count: Constants.MAX_UPLOAD_FILES}));
+ }
+
+ for (var i = 0; i < items.length && i < numToUpload; i++) {
+ if (items[i].type.indexOf('image') !== -1) {
+ var file = items[i].getAsFile();
+
+ var ext = items[i].type.split('/')[1].toLowerCase();
+
+ if (Constants.IMAGE_TYPES.indexOf(ext) < 0) {
+ continue;
+ }
+
+ var channelId = self.props.channelId || ChannelStore.getCurrentId();
+
+ // generate a unique id that can be used by other components to refer back to this file upload
+ var clientId = Utils.generateId();
+
+ var formData = new FormData();
+ formData.append('channel_id', channelId);
+ var d = new Date();
+ var hour;
+ if (d.getHours() < 10) {
+ hour = '0' + d.getHours();
+ } else {
+ hour = String(d.getHours());
+ }
+ var min;
+ if (d.getMinutes() < 10) {
+ min = '0' + d.getMinutes();
+ } else {
+ min = String(d.getMinutes());
+ }
+
+ var name = formatMessage(holders.pasted) + d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate() + ' ' + hour + '-' + min + '.' + ext;
+ formData.append('files', file, name);
+ formData.append('client_ids', clientId);
+
+ var request = Client.uploadFile(formData,
+ self.fileUploadSuccess.bind(self, channelId),
+ self.fileUploadFail.bind(self, clientId)
+ );
+
+ var requests = self.state.requests;
+ requests[clientId] = request;
+ self.setState({requests});
+
+ self.props.onUploadStart([clientId], channelId);
+ }
+ }
+ }
+ });
+ }
+
+ componentWillUnmount() {
+ let target;
+ if (this.props.postType === 'post') {
+ target = $('.row.main');
+ } else {
+ target = $('.post-right__container');
+ }
+
+ // jquery-dragster doesn't provide a function to unregister itself so do it manually
+ target.off('dragenter dragleave dragover drop dragster:enter dragster:leave dragster:over dragster:drop');
+ }
+
+ cancelUpload(clientId) {
+ var requests = this.state.requests;
+ var request = requests[clientId];
+
+ if (request) {
+ request.abort();
+
+ delete requests[clientId];
+ this.setState({requests});
+ }
+ }
+
+ render() {
+ let multiple = true;
+ if (Utils.isMobileApp()) {
+ // iOS WebViews don't upload videos properly in multiple mode
+ multiple = false;
+ }
+
+ let accept = '';
+ if (Utils.isIosChrome()) {
+ // iOS Chrome can't upload videos at all
+ accept = 'image/*';
+ }
+
+ return (
+ <span
+ ref='input'
+ className='btn btn-file'
+ >
+ <span>
+ <i className='glyphicon glyphicon-paperclip'/>
+ </span>
+ <input
+ ref='fileInput'
+ type='file'
+ onChange={this.handleChange}
+ onClick={this.props.onClick}
+ multiple={multiple}
+ accept={accept}
+ />
+ </span>
+ );
+ }
+}
+
+FileUpload.propTypes = {
+ intl: intlShape.isRequired,
+ onUploadError: React.PropTypes.func,
+ getFileCount: React.PropTypes.func,
+ onClick: React.PropTypes.func,
+ onFileUpload: React.PropTypes.func,
+ onUploadStart: React.PropTypes.func,
+ onTextDrop: React.PropTypes.func,
+ channelId: React.PropTypes.string,
+ postType: React.PropTypes.string
+};
+
+export default injectIntl(FileUpload, {withRef: true});