diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 80a207f711..8fe49199e9 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -916,7 +916,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-aphront-drag-and-drop' => array( - 'uri' => '/res/724c922b/rsrc/js/application/core/behavior-drag-and-drop.js', + 'uri' => '/res/0910fc0a/rsrc/js/application/core/behavior-drag-and-drop.js', 'type' => 'js', 'requires' => array( @@ -929,7 +929,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-aphront-drag-and-drop-textarea' => array( - 'uri' => '/res/99f58821/rsrc/js/application/core/behavior-drag-and-drop-textarea.js', + 'uri' => '/res/ad737ce4/rsrc/js/application/core/behavior-drag-and-drop-textarea.js', 'type' => 'js', 'requires' => array( @@ -1301,7 +1301,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-files-drag-and-drop' => array( - 'uri' => '/res/3a7a2a8a/rsrc/js/application/core/behavior-files-drag-and-drop.js', + 'uri' => '/res/4893f577/rsrc/js/application/core/behavior-files-drag-and-drop.js', 'type' => 'js', 'requires' => array( @@ -2361,7 +2361,7 @@ celerity_register_resource_map(array( ), 'phabricator-drag-and-drop-file-upload' => array( - 'uri' => '/res/63a06ad9/rsrc/js/application/core/DragAndDropFileUpload.js', + 'uri' => '/res/0db5b9d5/rsrc/js/application/core/DragAndDropFileUpload.js', 'type' => 'js', 'requires' => array( @@ -2370,6 +2370,7 @@ celerity_register_resource_map(array( 2 => 'javelin-request', 3 => 'javelin-dom', 4 => 'javelin-uri', + 5 => 'phabricator-file-upload', ), 'disk' => '/rsrc/js/application/core/DragAndDropFileUpload.js', ), @@ -2397,6 +2398,16 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/css/application/feed/feed.css', ), + 'phabricator-file-upload' => + array( + 'uri' => '/res/98503231/rsrc/js/application/core/FileUpload.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + ), + 'disk' => '/rsrc/js/application/core/FileUpload.js', + ), 'phabricator-filetree-view-css' => array( 'uri' => '/res/be0ab498/rsrc/css/layout/phabricator-filetree-view.css', @@ -2512,7 +2523,7 @@ celerity_register_resource_map(array( ), 'phabricator-notification-css' => array( - 'uri' => '/res/77e8c821/rsrc/css/aphront/notification.css', + 'uri' => '/res/91197237/rsrc/css/aphront/notification.css', 'type' => 'css', 'requires' => array( @@ -2636,7 +2647,7 @@ celerity_register_resource_map(array( ), 'phabricator-remarkup-css' => array( - 'uri' => '/res/66b4cd42/rsrc/css/core/remarkup.css', + 'uri' => '/res/2e0d0042/rsrc/css/core/remarkup.css', 'type' => 'css', 'requires' => array( @@ -3003,7 +3014,7 @@ celerity_register_resource_map(array( ), array( 'packages' => array( - 'c88632b0' => + '315e8536' => array( 'name' => 'core.pkg.css', 'symbols' => @@ -3032,7 +3043,7 @@ celerity_register_resource_map(array( 21 => 'phabricator-flag-css', 22 => 'aphront-error-view-css', ), - 'uri' => '/res/pkg/c88632b0/core.pkg.css', + 'uri' => '/res/pkg/315e8536/core.pkg.css', 'type' => 'css', ), '3a455e4f' => @@ -3084,7 +3095,7 @@ celerity_register_resource_map(array( 'uri' => '/res/pkg/2ba14b3d/differential.pkg.css', 'type' => 'css', ), - 'a5cb9310' => + 'a569df32' => array( 'name' => 'differential.pkg.js', 'symbols' => @@ -3107,7 +3118,7 @@ celerity_register_resource_map(array( 15 => 'differential-inline-comment-editor', 16 => 'javelin-behavior-differential-dropdown-menus', ), - 'uri' => '/res/pkg/a5cb9310/differential.pkg.js', + 'uri' => '/res/pkg/a569df32/differential.pkg.js', 'type' => 'js', ), 'c8ce2d88' => @@ -3199,23 +3210,23 @@ celerity_register_resource_map(array( 'reverse' => array( 'aphront-attached-file-view-css' => '7839ae2d', - 'aphront-crumbs-view-css' => 'c88632b0', - 'aphront-dialog-view-css' => 'c88632b0', - 'aphront-error-view-css' => 'c88632b0', - 'aphront-form-view-css' => 'c88632b0', + 'aphront-crumbs-view-css' => '315e8536', + 'aphront-dialog-view-css' => '315e8536', + 'aphront-error-view-css' => '315e8536', + 'aphront-form-view-css' => '315e8536', 'aphront-headsup-action-list-view-css' => '2ba14b3d', - 'aphront-headsup-view-css' => 'c88632b0', - 'aphront-list-filter-view-css' => 'c88632b0', - 'aphront-pager-view-css' => 'c88632b0', - 'aphront-panel-view-css' => 'c88632b0', - 'aphront-side-nav-view-css' => 'c88632b0', - 'aphront-table-view-css' => 'c88632b0', - 'aphront-tokenizer-control-css' => 'c88632b0', - 'aphront-tooltip-css' => 'c88632b0', - 'aphront-typeahead-control-css' => 'c88632b0', + 'aphront-headsup-view-css' => '315e8536', + 'aphront-list-filter-view-css' => '315e8536', + 'aphront-pager-view-css' => '315e8536', + 'aphront-panel-view-css' => '315e8536', + 'aphront-side-nav-view-css' => '315e8536', + 'aphront-table-view-css' => '315e8536', + 'aphront-tokenizer-control-css' => '315e8536', + 'aphront-tooltip-css' => '315e8536', + 'aphront-typeahead-control-css' => '315e8536', 'differential-changeset-view-css' => '2ba14b3d', 'differential-core-view-css' => '2ba14b3d', - 'differential-inline-comment-editor' => 'a5cb9310', + 'differential-inline-comment-editor' => 'a569df32', 'differential-local-commits-view-css' => '2ba14b3d', 'differential-results-table-css' => '2ba14b3d', 'differential-revision-add-comment-css' => '2ba14b3d', @@ -3229,20 +3240,20 @@ celerity_register_resource_map(array( 'inline-comment-summary-css' => '2ba14b3d', 'javelin-behavior' => '6c45a1d8', 'javelin-behavior-aphront-basic-tokenizer' => '81c9cd69', - 'javelin-behavior-aphront-drag-and-drop' => 'a5cb9310', - 'javelin-behavior-aphront-drag-and-drop-textarea' => 'a5cb9310', + 'javelin-behavior-aphront-drag-and-drop' => 'a569df32', + 'javelin-behavior-aphront-drag-and-drop-textarea' => 'a569df32', 'javelin-behavior-aphront-form-disable-on-submit' => '3a455e4f', 'javelin-behavior-audit-preview' => '5e68be89', - 'javelin-behavior-differential-accept-with-errors' => 'a5cb9310', - 'javelin-behavior-differential-add-reviewers-and-ccs' => 'a5cb9310', - 'javelin-behavior-differential-comment-jump' => 'a5cb9310', - 'javelin-behavior-differential-diff-radios' => 'a5cb9310', - 'javelin-behavior-differential-dropdown-menus' => 'a5cb9310', - 'javelin-behavior-differential-edit-inline-comments' => 'a5cb9310', - 'javelin-behavior-differential-feedback-preview' => 'a5cb9310', - 'javelin-behavior-differential-keyboard-navigation' => 'a5cb9310', - 'javelin-behavior-differential-populate' => 'a5cb9310', - 'javelin-behavior-differential-show-more' => 'a5cb9310', + 'javelin-behavior-differential-accept-with-errors' => 'a569df32', + 'javelin-behavior-differential-add-reviewers-and-ccs' => 'a569df32', + 'javelin-behavior-differential-comment-jump' => 'a569df32', + 'javelin-behavior-differential-diff-radios' => 'a569df32', + 'javelin-behavior-differential-dropdown-menus' => 'a569df32', + 'javelin-behavior-differential-edit-inline-comments' => 'a569df32', + 'javelin-behavior-differential-feedback-preview' => 'a569df32', + 'javelin-behavior-differential-keyboard-navigation' => 'a569df32', + 'javelin-behavior-differential-populate' => 'a569df32', + 'javelin-behavior-differential-show-more' => 'a569df32', 'javelin-behavior-diffusion-commit-graph' => '5e68be89', 'javelin-behavior-diffusion-pull-lastmodified' => '5e68be89', 'javelin-behavior-maniphest-batch-selector' => '7707de41', @@ -3252,12 +3263,12 @@ celerity_register_resource_map(array( 'javelin-behavior-maniphest-transaction-preview' => '7707de41', 'javelin-behavior-phabricator-autofocus' => '3a455e4f', 'javelin-behavior-phabricator-keyboard-shortcuts' => '3a455e4f', - 'javelin-behavior-phabricator-object-selector' => 'a5cb9310', + 'javelin-behavior-phabricator-object-selector' => 'a569df32', 'javelin-behavior-phabricator-oncopy' => '3a455e4f', 'javelin-behavior-phabricator-tooltips' => '3a455e4f', 'javelin-behavior-phabricator-watch-anchor' => '3a455e4f', 'javelin-behavior-refresh-csrf' => '3a455e4f', - 'javelin-behavior-repository-crossreference' => 'a5cb9310', + 'javelin-behavior-repository-crossreference' => 'a569df32', 'javelin-behavior-workflow' => '3a455e4f', 'javelin-dom' => '6c45a1d8', 'javelin-event' => '6c45a1d8', @@ -3278,15 +3289,15 @@ celerity_register_resource_map(array( 'javelin-workflow' => '3a455e4f', 'maniphest-task-summary-css' => '7839ae2d', 'maniphest-transaction-detail-css' => '7839ae2d', - 'phabricator-app-buttons-css' => 'c88632b0', + 'phabricator-app-buttons-css' => '315e8536', 'phabricator-content-source-view-css' => '2ba14b3d', - 'phabricator-core-buttons-css' => 'c88632b0', - 'phabricator-core-css' => 'c88632b0', - 'phabricator-directory-css' => 'c88632b0', - 'phabricator-drag-and-drop-file-upload' => 'a5cb9310', + 'phabricator-core-buttons-css' => '315e8536', + 'phabricator-core-css' => '315e8536', + 'phabricator-directory-css' => '315e8536', + 'phabricator-drag-and-drop-file-upload' => 'a569df32', 'phabricator-dropdown-menu' => '3a455e4f', - 'phabricator-flag-css' => 'c88632b0', - 'phabricator-jump-nav' => 'c88632b0', + 'phabricator-flag-css' => '315e8536', + 'phabricator-jump-nav' => '315e8536', 'phabricator-keyboard-shortcut' => '3a455e4f', 'phabricator-keyboard-shortcut-manager' => '3a455e4f', 'phabricator-menu-item' => '3a455e4f', @@ -3294,11 +3305,11 @@ celerity_register_resource_map(array( 'phabricator-paste-file-upload' => '3a455e4f', 'phabricator-prefab' => '3a455e4f', 'phabricator-project-tag-css' => '7839ae2d', - 'phabricator-remarkup-css' => 'c88632b0', - 'phabricator-shaped-request' => 'a5cb9310', - 'phabricator-standard-page-view' => 'c88632b0', + 'phabricator-remarkup-css' => '315e8536', + 'phabricator-shaped-request' => 'a569df32', + 'phabricator-standard-page-view' => '315e8536', 'phabricator-tooltip' => '3a455e4f', - 'phabricator-transaction-view-css' => 'c88632b0', - 'syntax-highlighting-css' => 'c88632b0', + 'phabricator-transaction-view-css' => '315e8536', + 'syntax-highlighting-css' => '315e8536', ), )); diff --git a/webroot/rsrc/css/aphront/notification.css b/webroot/rsrc/css/aphront/notification.css index b4c4505957..a905463a5a 100644 --- a/webroot/rsrc/css/aphront/notification.css +++ b/webroot/rsrc/css/aphront/notification.css @@ -38,3 +38,13 @@ background: #ffa0ff; border: 1px solid #aa60aa; } + +.jx-notification-done { + background: #d0ffd0; + border: 1px solid #60aa60; +} + +.jx-notification-error { + background: #ffd0d0; + border: 1px solid #aa6060; +} diff --git a/webroot/rsrc/js/application/core/DragAndDropFileUpload.js b/webroot/rsrc/js/application/core/DragAndDropFileUpload.js index 048be550b4..5743341b50 100644 --- a/webroot/rsrc/js/application/core/DragAndDropFileUpload.js +++ b/webroot/rsrc/js/application/core/DragAndDropFileUpload.js @@ -4,6 +4,7 @@ * javelin-request * javelin-dom * javelin-uri + * phabricator-file-upload * @provides phabricator-drag-and-drop-file-upload * @javelin */ @@ -14,7 +15,7 @@ JX.install('PhabricatorDragAndDropFileUpload', { this._node = node; }, - events : ['willUpload', 'didUpload'], + events : ['willUpload', 'progress', 'didUpload', 'didError'], statics : { isSupported : function() { @@ -88,24 +89,80 @@ JX.install('PhabricatorDragAndDropFileUpload', { var files = e.getRawEvent().dataTransfer.files; for (var ii = 0; ii < files.length; ii++) { - var file = files[ii]; - - this.invoke('willUpload', file); - - var up_uri = JX.$U(this.getURI()) - .setQueryParam('name', file.name) - .toString(); - - new JX.Request(up_uri, JX.bind(this, function(r) { - this.invoke('didUpload', r); - })) - .setFile(file) - .send(); + this._sendRequest(files[ii]); } // Force depth to 0. this._updateDepth(-this._depth); })); + }, + _sendRequest : function(spec) { + var file = new JX.PhabricatorFileUpload() + .setName(spec.name) + .setTotalBytes(spec.size) + .setStatus('uploading') + .update(); + + this.invoke('willUpload', file); + + var up_uri = JX.$U(this.getURI()) + .setQueryParam('name', file.getName()) + .toString(); + + var onupload = JX.bind(this, function(r) { + if (r.error) { + file + .setStatus('error') + .setError(r.error) + .update(); + + this.invoke('didError', file); + } else { + file + .setID(r.id) + .setPHID(r.phid) + .setURI(r.uri) + .setMarkup(r.html) + .setStatus('done') + .update(); + + this.invoke('didUpload', file); + } + }); + + var req = new JX.Request(up_uri, onupload); + + var onerror = JX.bind(this, function(error) { + file.setStatus('error'); + + if (error) { + file.setError(error.code + ': ' + error.info); + } else { + var xhr = req.getTransport(); + if (xhr.responseText) { + file.setError('Server responded: ' + xhr.responseText); + } + } + + file.update(); + this.invoke('didError', file); + }); + + var onprogress = JX.bind(this, function(progress) { + file + .setTotalBytes(progress.total) + .setUploadedBytes(progress.loaded) + .update(); + + this.invoke('progress', file); + }); + + req.listen('error', onerror); + req.listen('uploadprogress', onprogress); + + req + .setFile(spec) + .send(); } }, properties: { diff --git a/webroot/rsrc/js/application/core/FileUpload.js b/webroot/rsrc/js/application/core/FileUpload.js new file mode 100644 index 0000000000..5fa6dac4d7 --- /dev/null +++ b/webroot/rsrc/js/application/core/FileUpload.js @@ -0,0 +1,111 @@ +/** + * @requires javelin-install + * @provides phabricator-file-upload + * @javelin + */ + +JX.install('PhabricatorFileUpload', { + + construct : function() { + this._notification = new JX.Notification(); + }, + + properties : { + name : null, + totalBytes : null, + uploadedBytes : null, + ID : null, + PHID : null, + URI : null, + status : null, + markup : null, + error : null + }, + + members : { + _notification : null, + + update : function() { + if (!this._notification) { + return; + } + + this._notification + .setDuration(0) + .show(); + + switch (this.getStatus()) { + case 'done': + var link = JX.$N('a', {href: this.getURI()}, 'F' + this.getID()); + + var content = [ + JX.$N('strong', {}, ['Upload Complete (', link, ')']), + JX.$N('br'), + this.getName() + ]; + + this._notification + .setContent(content) + .alterClassName('jx-notification-done', true) + .setDuration(12000); + this._notification = null; + break; + case 'error': + var content = [ + JX.$N('strong', {}, 'Upload Failure'), + JX.$N('br'), + this.getName(), + JX.$N('br'), + JX.$N('br'), + this.getError() + ]; + + this._notification + .setContent(content) + .alterClassName('jx-notification-error', true); + this._notification = null; + break; + default: + var info = ''; + if (this.getTotalBytes()) { + var p = this._renderPercentComplete(); + var f = this._renderFileSize(); + info = ' (' + p + ' of ' + f + ')'; + } + + info = 'Uploading "' + this.getName() + '"' + info + '...'; + + this._notification + .setContent(info); + break; + } + + return this; + }, + _renderPercentComplete : function() { + if (!this.getTotalBytes()) { + return null; + } + var ratio = this.getUploadedBytes() / this.getTotalBytes(); + return parseInt(100 * ratio) + '%'; + }, + _renderFileSize : function() { + if (!this.getTotalBytes()) { + return null; + } + + var s = 3; + var n = this.getTotalBytes(); + while (s && n >= 1000) { + n = Math.round(n / 100); + n = n / 10; + s--; + } + + s = ['GB', 'MB', 'KB', 'bytes'][s]; + return n + ' ' + s; + } + } + +}); + diff --git a/webroot/rsrc/js/application/core/behavior-drag-and-drop-textarea.js b/webroot/rsrc/js/application/core/behavior-drag-and-drop-textarea.js index 8e9e02d442..9ad1cd11fb 100644 --- a/webroot/rsrc/js/application/core/behavior-drag-and-drop-textarea.js +++ b/webroot/rsrc/js/application/core/behavior-drag-and-drop-textarea.js @@ -12,7 +12,7 @@ JX.behavior('aphront-drag-and-drop-textarea', function(config) { var target = JX.$(config.target); function onupload(f) { - JX.TextAreaUtils.setSelectionText(target, '{F' + f.id + '}'); + JX.TextAreaUtils.setSelectionText(target, '{F' + f.getID() + '}'); } if (JX.PhabricatorDragAndDropFileUpload.isSupported()) { diff --git a/webroot/rsrc/js/application/core/behavior-drag-and-drop.js b/webroot/rsrc/js/application/core/behavior-drag-and-drop.js index 1a064a2e21..6788f38d0e 100644 --- a/webroot/rsrc/js/application/core/behavior-drag-and-drop.js +++ b/webroot/rsrc/js/application/core/behavior-drag-and-drop.js @@ -32,7 +32,7 @@ JX.behavior('aphront-drag-and-drop', function(config) { }); drop.listen('didUpload', function(f) { - files[f.phid] = f; + files[f.getPHID()] = f; // This redraws "Upload complete!" pending--; @@ -59,13 +59,13 @@ JX.behavior('aphront-drag-and-drop', function(config) { var items = []; for (var k in files) { var file = files[k]; - items.push(JX.$N('div', {}, JX.$H(file.html))); + items.push(JX.$N('div', {}, JX.$H(file.getMarkup()))); items.push(JX.$N( 'input', { type: "hidden", - name: config.name + "[" + file.phid + "]", - value: file.phid + name: config.name + "[" + file.getPHID() + "]", + value: file.getPHID() })); } diff --git a/webroot/rsrc/js/application/core/behavior-files-drag-and-drop.js b/webroot/rsrc/js/application/core/behavior-files-drag-and-drop.js index 95146e59df..72fa7a389a 100644 --- a/webroot/rsrc/js/application/core/behavior-files-drag-and-drop.js +++ b/webroot/rsrc/js/application/core/behavior-files-drag-and-drop.js @@ -16,6 +16,7 @@ JX.behavior('files-drag-and-drop', function(config) { var pending = 0; var files = []; + var errors = false; var control = JX.$(config.control); // Show the control, since we have browser support. @@ -34,14 +35,14 @@ JX.behavior('files-drag-and-drop', function(config) { files.push(f); pending--; - if (pending == 0) { + if (pending == 0 && !errors) { // If whatever the user dropped in has finished uploading, send them to // their uploads. var uri; uri = JX.$U(config.browseURI); var ids = []; for (var ii = 0; ii < files.length; ii++) { - ids.push(files[ii].id); + ids.push(files[ii].getID()); } uri.setQueryParam('h', ids.join('-')); @@ -56,6 +57,12 @@ JX.behavior('files-drag-and-drop', function(config) { } }); + drop.listen('didError', function(f) { + pending--; + errors = true; + redraw(); + }); + drop.start(); redraw();