diff options
author | Christopher Speller <crspeller@gmail.com> | 2015-08-18 08:46:14 -0400 |
---|---|---|
committer | Christopher Speller <crspeller@gmail.com> | 2015-08-18 08:46:14 -0400 |
commit | 96d1eb1c800a427e31e63970e57d0824a3bc91e3 (patch) | |
tree | 4bf9b926fa3877de9bafaafcc0831e724e6fc7a3 /web/react | |
parent | ab197e98358f4f48b81669182a361b6641132029 (diff) | |
parent | 2c098d7711eda893f903329ab64528a7d387a6e8 (diff) | |
download | chat-96d1eb1c800a427e31e63970e57d0824a3bc91e3.tar.gz chat-96d1eb1c800a427e31e63970e57d0824a3bc91e3.tar.bz2 chat-96d1eb1c800a427e31e63970e57d0824a3bc91e3.zip |
Merge pull request #378 from rgarmsen2295/mm-316c
MM-316 Allows users to drag and drop files from their computer to the center pane or RHS to upload them
Diffstat (limited to 'web/react')
-rw-r--r-- | web/react/components/create_comment.jsx | 22 | ||||
-rw-r--r-- | web/react/components/create_post.jsx | 22 | ||||
-rw-r--r-- | web/react/components/file_upload.jsx | 101 | ||||
-rw-r--r-- | web/react/components/file_upload_overlay.jsx | 26 | ||||
-rw-r--r-- | web/react/components/post_right.jsx | 3 | ||||
-rw-r--r-- | web/react/pages/channel.jsx | 7 |
6 files changed, 157 insertions, 24 deletions
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 78e06c532..885efab7a 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -122,16 +122,20 @@ module.exports = React.createClass({ this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']}); }, handleUploadError: function(err, clientId) { - var draft = PostStore.getCommentDraft(this.props.rootId); + if (clientId !== -1) { + var draft = PostStore.getCommentDraft(this.props.rootId); - var index = draft['uploadsInProgress'].indexOf(clientId); - if (index !== -1) { - draft['uploadsInProgress'].splice(index, 1); - } + var index = draft['uploadsInProgress'].indexOf(clientId); + if (index !== -1) { + draft['uploadsInProgress'].splice(index, 1); + } - PostStore.storeCommentDraft(this.props.rootId, draft); + PostStore.storeCommentDraft(this.props.rootId, draft); - this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err}); + this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err}); + } else { + this.setState({serverError: err}); + } }, clearPreviews: function() { this.setState({previews: []}); @@ -222,7 +226,9 @@ module.exports = React.createClass({ getFileCount={this.getFileCount} onUploadStart={this.handleUploadStart} onFileUpload={this.handleFileUploadComplete} - onUploadError={this.handleUploadError} /> + onUploadError={this.handleUploadError} + postType='comment' + channelId={this.props.channelId} /> </div> <MsgTyping channelId={this.props.channelId} parentId={this.props.rootId} /> <div className={postFooterClassName}> diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 9ca1d5388..377e7bd34 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -145,16 +145,20 @@ module.exports = React.createClass({ this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']}); }, handleUploadError: function(err, clientId) { - var draft = PostStore.getDraft(this.state.channelId); + if (clientId !== -1) { + var draft = PostStore.getDraft(this.state.channelId); - var index = draft['uploadsInProgress'].indexOf(clientId); - if (index !== -1) { - draft['uploadsInProgress'].splice(index, 1); - } + var index = draft['uploadsInProgress'].indexOf(clientId); + if (index !== -1) { + draft['uploadsInProgress'].splice(index, 1); + } - PostStore.storeDraft(this.state.channelId, draft); + PostStore.storeDraft(this.state.channelId, draft); - this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err}); + this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err}); + } else { + this.setState({serverError: err}); + } }, removePreview: function(id) { var previews = this.state.previews; @@ -262,7 +266,9 @@ module.exports = React.createClass({ getFileCount={this.getFileCount} onUploadStart={this.handleUploadStart} onFileUpload={this.handleFileUploadComplete} - onUploadError={this.handleUploadError} /> + onUploadError={this.handleUploadError} + postType='post' + channelId='' /> </div> <div className={postFooterClassName}> {postError} diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index c1fab669c..7497ec330 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -12,7 +12,9 @@ module.exports = React.createClass({ onUploadError: React.PropTypes.func, getFileCount: React.PropTypes.func, onFileUpload: React.PropTypes.func, - onUploadStart: React.PropTypes.func + onUploadStart: React.PropTypes.func, + channelId: React.PropTypes.string, + postType: React.PropTypes.string }, getInitialState: function() { return {requests: {}}; @@ -21,7 +23,7 @@ module.exports = React.createClass({ var element = $(this.refs.fileInput.getDOMNode()); var files = element.prop('files'); - var channelId = ChannelStore.getCurrentId(); + var channelId = this.props.channelId || ChannelStore.getCurrentId(); this.props.onUploadError(null); @@ -61,8 +63,8 @@ module.exports = React.createClass({ this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId); var requests = this.state.requests; - for (var i = 0; i < parsedData.client_ids.length; i++) { - delete requests[parsedData.client_ids[i]]; + for (var j = 0; j < parsedData.client_ids.length; j++) { + delete requests[parsedData.client_ids[j]]; } this.setState({requests: requests}); }.bind(this), @@ -87,10 +89,94 @@ module.exports = React.createClass({ } } catch(e) {} }, + handleDrop: function(e) { + this.props.onUploadError(null); + + var files = e.originalEvent.dataTransfer.files; + var channelId = this.props.channelId || ChannelStore.getCurrentId(); + + if (typeof files !== 'string' && files.length) { + var numFiles = files.length; + + var numToUpload = Math.min(Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId), numFiles); + + if (numFiles > numToUpload) { + this.props.onUploadError('Uploads limited to ' + Constants.MAX_UPLOAD_FILES + ' files maximum. Please use additional posts for more files.'); + } + + for (var i = 0; i < files.length && i < numToUpload; i++) { + if (files[i].size > Constants.MAX_FILE_SIZE) { + this.props.onUploadError('Files must be no more than ' + Constants.MAX_FILE_SIZE / 1000000 + ' MB'); + continue; + } + + // generate a unique id that can be used by other components to refer back to this file upload + var 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, + function(data) { + var parsedData = $.parseJSON(data); + this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId); + + var requests = this.state.requests; + for (var j = 0; j < parsedData.client_ids.length; j++) { + delete requests[parsedData.client_ids[j]]; + } + this.setState({requests: requests}); + }.bind(this), + function(err) { + this.props.onUploadError(err, clientId); + }.bind(this) + ); + + var requests = this.state.requests; + requests[clientId] = request; + this.setState({requests: requests}); + + this.props.onUploadStart([clientId], channelId); + } + } else { + this.props.onUploadError('Invalid file upload', -1); + } + }, componentDidMount: function() { var inputDiv = this.refs.input.getDOMNode(); var self = this; + if (this.props.postType === 'post') { + $('.row.main').dragster({ + enter: function() { + $('.center-file-overlay').removeClass('hidden'); + }, + leave: function() { + $('.center-file-overlay').addClass('hidden'); + }, + drop: function(dragsterEvent, e) { + $('.center-file-overlay').addClass('hidden'); + self.handleDrop(e); + } + }); + } else if (this.props.postType === 'comment') { + $('.post-right__container').dragster({ + enter: function() { + $('.right-file-overlay').removeClass('hidden'); + }, + leave: function() { + $('.right-file-overlay').addClass('hidden'); + }, + drop: function(dragsterEvent, e) { + $('.right-file-overlay').addClass('hidden'); + self.handleDrop(e); + } + }); + } + document.addEventListener('paste', function(e) { var textarea = $(inputDiv.parentNode.parentNode).find('.custom-textarea')[0]; @@ -133,14 +219,13 @@ module.exports = React.createClass({ continue; } - var channelId = ChannelStore.getCurrentId(); + var channelId = this.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) { @@ -165,8 +250,8 @@ module.exports = React.createClass({ self.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId); var requests = self.state.requests; - for (var i = 0; i < parsedData.client_ids.length; i++) { - delete requests[parsedData.client_ids[i]]; + for (var j = 0; j < parsedData.client_ids.length; j++) { + delete requests[parsedData.client_ids[j]]; } self.setState({requests: requests}); }, diff --git a/web/react/components/file_upload_overlay.jsx b/web/react/components/file_upload_overlay.jsx new file mode 100644 index 000000000..f35556371 --- /dev/null +++ b/web/react/components/file_upload_overlay.jsx @@ -0,0 +1,26 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +module.exports = React.createClass({ + displayName: 'FileUploadOverlay', + propTypes: { + overlayType: React.PropTypes.string + }, + render: function() { + var overlayClass = 'file-overlay hidden'; + if (this.props.overlayType === 'right') { + overlayClass += ' right-file-overlay'; + } else if (this.props.overlayType === 'center') { + overlayClass += ' center-file-overlay'; + } + + return ( + <div className={overlayClass}> + <div> + <i className='fa fa-upload'></i> + <span>Drop a file to upload it.</span> + </div> + </div> + ); + } +}); diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx index ad8b54012..e46979ff7 100644 --- a/web/react/components/post_right.jsx +++ b/web/react/components/post_right.jsx @@ -11,6 +11,7 @@ var SearchBox =require('./search_bar.jsx'); var CreateComment = require( './create_comment.jsx' ); var Constants = require('../utils/constants.jsx'); var FileAttachmentList = require('./file_attachment_list.jsx'); +var FileUploadOverlay = require('./file_upload_overlay.jsx'); var ActionTypes = Constants.ActionTypes; RhsHeaderPost = React.createClass({ @@ -296,6 +297,8 @@ module.exports = React.createClass({ return ( <div className="post-right__container"> + <FileUploadOverlay + overlayType='right' /> <div className="search-bar__container sidebar--right__search-header">{searchForm}</div> <div className="sidebar-right__body"> <RhsHeaderPost fromSearch={this.props.fromSearch} isMentionSearch={this.props.isMentionSearch} /> diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 929499715..6e4baa582 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -34,6 +34,7 @@ var ChannelInfoModal = require('../components/channel_info_modal.jsx'); var AccessHistoryModal = require('../components/access_history_modal.jsx'); var ActivityLogModal = require('../components/activity_log_modal.jsx'); var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx') +var FileUploadOverlay = require('../components/file_upload_overlay.jsx'); var AsyncClient = require('../utils/async_client.jsx'); @@ -224,4 +225,10 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann document.getElementById('removed_from_channel_modal') ); + React.render( + <FileUploadOverlay + overlayType='center' />, + document.getElementById('file_upload_overlay') + ); + }; |