diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 19541c41d5..58f03c026d 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -7,6 +7,15 @@ */ celerity_register_resource_map(array( + 'aphront-attached-file-view-css' => + array( + 'uri' => '/res/79bc2c2e/rsrc/css/aphront/attached-file-view.css', + 'type' => 'css', + 'requires' => + array( + ), + 'disk' => '/rsrc/css/aphront/attached-file-view.css', + ), 'aphront-crumbs-view-css' => array( 'uri' => '/res/9009e6bd/rsrc/css/aphront/crumbs-view.css', @@ -45,7 +54,7 @@ celerity_register_resource_map(array( ), 'aphront-form-view-css' => array( - 'uri' => '/res/dadf31b1/rsrc/css/aphront/form-view.css', + 'uri' => '/res/38a347da/rsrc/css/aphront/form-view.css', 'type' => 'css', 'requires' => array( @@ -270,6 +279,16 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/javelin/lib/behavior.js', ), + 0 => + array( + 'uri' => '/res/39de799e/rsrc/js/javelin/docs/Base.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + ), + 'disk' => '/rsrc/js/javelin/docs/Base.js', + ), 'javelin-behavior-aphront-basic-tokenizer' => array( 'uri' => '/res/bce3961b/rsrc/js/application/core/behavior-tokenizer.js', @@ -284,6 +303,19 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/application/core/behavior-tokenizer.js', ), + 'javelin-behavior-aphront-drag-and-drop' => + array( + 'uri' => '/res/170115f4/rsrc/js/application/core/behavior-drag-and-drop.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-dom', + 2 => 'javelin-util', + 3 => 'phabricator-drag-and-drop-file-upload', + ), + 'disk' => '/rsrc/js/application/core/behavior-drag-and-drop.js', + ), 'javelin-behavior-aphront-form-disable-on-submit' => array( 'uri' => '/res/6c659ede/rsrc/js/application/core/behavior-form.js', @@ -477,18 +509,6 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/application/maniphest/behavior-transaction-controls.js', ), - 'javelin-behavior-maniphest-transaction-drag-and-drop' => - array( - 'uri' => '/res/5bf1f40c/rsrc/js/application/maniphest/behavior-transaction-drag-and-drop.js', - 'type' => 'js', - 'requires' => - array( - 0 => 'javelin-behavior', - 1 => 'javelin-dom', - 2 => 'phabricator-drag-and-drop-file-upload', - ), - 'disk' => '/rsrc/js/application/maniphest/behavior-transaction-drag-and-drop.js', - ), 'javelin-behavior-maniphest-transaction-expand' => array( 'uri' => '/res/966410de/rsrc/js/application/maniphest/behavior-transaction-expand.js', @@ -671,16 +691,6 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/javelin/lib/control/typeahead/Typeahead.js', ), - 0 => - array( - 'uri' => '/res/39de799e/rsrc/js/javelin/docs/Base.js', - 'type' => 'js', - 'requires' => - array( - 0 => 'javelin-install', - ), - 'disk' => '/rsrc/js/javelin/docs/Base.js', - ), 'javelin-typeahead-normalizer' => array( 'uri' => '/res/a5d60e3c/rsrc/js/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js', @@ -888,7 +898,7 @@ celerity_register_resource_map(array( ), 'phabricator-drag-and-drop-file-upload' => array( - 'uri' => '/res/711efc61/rsrc/js/application/core/DragAndDropFileUpload.js', + 'uri' => '/res/63a06ad9/rsrc/js/application/core/DragAndDropFileUpload.js', 'type' => 'js', 'requires' => array( @@ -1025,7 +1035,26 @@ celerity_register_resource_map(array( 'uri' => '/res/pkg/33f413ef/typeahead.pkg.js', 'type' => 'js', ), - 'ac3f56cc' => + 'd985d27a' => + array ( + 'name' => 'javelin.pkg.js', + 'symbols' => + array ( + 0 => 'javelin-util', + 1 => 'javelin-install', + 2 => 'javelin-event', + 3 => 'javelin-stratcom', + 4 => 'javelin-behavior', + 5 => 'javelin-request', + 6 => 'javelin-vector', + 7 => 'javelin-dom', + 8 => 'javelin-json', + 9 => 'javelin-uri', + ), + 'uri' => '/res/pkg/d985d27a/javelin.pkg.js', + 'type' => 'js', + ), + 'e8e3f8ab' => array ( 'name' => 'core.pkg.css', 'symbols' => @@ -1046,28 +1075,9 @@ celerity_register_resource_map(array( 13 => 'phabricator-remarkup-css', 14 => 'syntax-highlighting-css', ), - 'uri' => '/res/pkg/ac3f56cc/core.pkg.css', + 'uri' => '/res/pkg/e8e3f8ab/core.pkg.css', 'type' => 'css', ), - 'd985d27a' => - array ( - 'name' => 'javelin.pkg.js', - 'symbols' => - array ( - 0 => 'javelin-util', - 1 => 'javelin-install', - 2 => 'javelin-event', - 3 => 'javelin-stratcom', - 4 => 'javelin-behavior', - 5 => 'javelin-request', - 6 => 'javelin-vector', - 7 => 'javelin-dom', - 8 => 'javelin-json', - 9 => 'javelin-uri', - ), - 'uri' => '/res/pkg/d985d27a/javelin.pkg.js', - 'type' => 'js', - ), 'ed383f69' => array ( 'name' => 'differential.pkg.js', @@ -1085,15 +1095,15 @@ celerity_register_resource_map(array( ), 'reverse' => array ( - 'aphront-crumbs-view-css' => 'ac3f56cc', - 'aphront-dialog-view-css' => 'ac3f56cc', - 'aphront-form-view-css' => 'ac3f56cc', - 'aphront-list-filter-view-css' => 'ac3f56cc', - 'aphront-panel-view-css' => 'ac3f56cc', - 'aphront-side-nav-view-css' => 'ac3f56cc', - 'aphront-table-view-css' => 'ac3f56cc', - 'aphront-tokenizer-control-css' => 'ac3f56cc', - 'aphront-typeahead-control-css' => 'ac3f56cc', + 'aphront-crumbs-view-css' => 'e8e3f8ab', + 'aphront-dialog-view-css' => 'e8e3f8ab', + 'aphront-form-view-css' => 'e8e3f8ab', + 'aphront-list-filter-view-css' => 'e8e3f8ab', + 'aphront-panel-view-css' => 'e8e3f8ab', + 'aphront-side-nav-view-css' => 'e8e3f8ab', + 'aphront-table-view-css' => 'e8e3f8ab', + 'aphront-tokenizer-control-css' => 'e8e3f8ab', + 'aphront-typeahead-control-css' => 'e8e3f8ab', 'differential-changeset-view-css' => '1ac25e8a', 'differential-core-view-css' => '1ac25e8a', 'differential-revision-add-comment-css' => '1ac25e8a', @@ -1128,11 +1138,11 @@ celerity_register_resource_map(array( 'javelin-util' => 'd985d27a', 'javelin-vector' => 'd985d27a', 'javelin-workflow' => '122a6b6d', - 'phabricator-core-buttons-css' => 'ac3f56cc', - 'phabricator-core-css' => 'ac3f56cc', - 'phabricator-directory-css' => 'ac3f56cc', - 'phabricator-remarkup-css' => 'ac3f56cc', - 'phabricator-standard-page-view' => 'ac3f56cc', - 'syntax-highlighting-css' => 'ac3f56cc', + 'phabricator-core-buttons-css' => 'e8e3f8ab', + 'phabricator-core-css' => 'e8e3f8ab', + 'phabricator-directory-css' => 'e8e3f8ab', + 'phabricator-remarkup-css' => 'e8e3f8ab', + 'phabricator-standard-page-view' => 'e8e3f8ab', + 'syntax-highlighting-css' => 'e8e3f8ab', ), )); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f69183a285..9ede479f5a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -13,6 +13,7 @@ phutil_register_library_map(array( 'Aphront404Response' => 'aphront/response/404', 'AphrontAjaxResponse' => 'aphront/response/ajax', 'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration', + 'AphrontAttachedFileView' => 'view/control/attachedfile', 'AphrontController' => 'aphront/controller', 'AphrontCrumbsView' => 'view/layout/crumbs', 'AphrontDatabaseConnection' => 'storage/connection/base', @@ -26,6 +27,7 @@ phutil_register_library_map(array( 'AphrontFormCheckboxControl' => 'view/form/control/checkbox', 'AphrontFormControl' => 'view/form/control/base', 'AphrontFormDividerControl' => 'view/form/control/divider', + 'AphrontFormDragAndDropUploadControl' => 'view/form/control/draganddropupload', 'AphrontFormFileControl' => 'view/form/control/file', 'AphrontFormMarkupControl' => 'view/form/control/markup', 'AphrontFormPasswordControl' => 'view/form/control/password', @@ -528,6 +530,7 @@ phutil_register_library_map(array( 'Aphront400Response' => 'AphrontResponse', 'Aphront404Response' => 'AphrontResponse', 'AphrontAjaxResponse' => 'AphrontResponse', + 'AphrontAttachedFileView' => 'AphrontView', 'AphrontCrumbsView' => 'AphrontView', 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', 'AphrontDefaultApplicationController' => 'AphrontController', @@ -538,6 +541,7 @@ phutil_register_library_map(array( 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', 'AphrontFormDividerControl' => 'AphrontFormControl', + 'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl', 'AphrontFormFileControl' => 'AphrontFormControl', 'AphrontFormMarkupControl' => 'AphrontFormControl', 'AphrontFormPasswordControl' => 'AphrontFormControl', diff --git a/src/applications/files/controller/dropupload/PhabricatorFileDropUploadController.php b/src/applications/files/controller/dropupload/PhabricatorFileDropUploadController.php index 98760b1c4b..d50fbd181a 100644 --- a/src/applications/files/controller/dropupload/PhabricatorFileDropUploadController.php +++ b/src/applications/files/controller/dropupload/PhabricatorFileDropUploadController.php @@ -30,12 +30,13 @@ class PhabricatorFileDropUploadController extends PhabricatorFileController { 'name' => $request->getStr('name'), )); + $view = new AphrontAttachedFileView(); + $view->setFile($file); + return id(new AphrontAjaxResponse())->setContent( array( - 'name' => $file->getName(), 'phid' => $file->getPHID(), - 'size' => $file->getByteSize(), - 'uri' => $file->getViewURI(), + 'html' => $view->render(), )); } diff --git a/src/applications/files/controller/dropupload/__init__.php b/src/applications/files/controller/dropupload/__init__.php index 9d934b5d22..cfef1bd064 100644 --- a/src/applications/files/controller/dropupload/__init__.php +++ b/src/applications/files/controller/dropupload/__init__.php @@ -9,6 +9,7 @@ phutil_require_module('phabricator', 'aphront/response/ajax'); phutil_require_module('phabricator', 'applications/files/controller/base'); phutil_require_module('phabricator', 'applications/files/storage/file'); +phutil_require_module('phabricator', 'view/control/attachedfile'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/files/storage/file/PhabricatorFile.php b/src/applications/files/storage/file/PhabricatorFile.php index 3f49627093..56d1a35fa7 100644 --- a/src/applications/files/storage/file/PhabricatorFile.php +++ b/src/applications/files/storage/file/PhabricatorFile.php @@ -202,6 +202,10 @@ class PhabricatorFile extends PhabricatorFileDAO { return PhabricatorFileURI::getViewURIForPHID($this->getPHID()); } + public function getThumb60x45URI() { + return '/file/xform/thumb-60x45/'.$this->getPHID().'/'; + } + public function isViewableInBrowser() { return ($this->getViewableMimeType() !== null); } diff --git a/src/applications/maniphest/controller/taskdetail/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/taskdetail/ManiphestTaskDetailController.php index f79d98b54e..662d13db17 100644 --- a/src/applications/maniphest/controller/taskdetail/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/taskdetail/ManiphestTaskDetailController.php @@ -222,6 +222,8 @@ class ManiphestTaskDetailController extends ManiphestController { $draft_text = null; } + $panel_id = celerity_generate_unique_node_id(); + $comment_form = new AphrontFormView(); $comment_form ->setUser($user) @@ -286,12 +288,11 @@ class ManiphestTaskDetailController extends ManiphestController { ->setValue($draft_text) ->setID('transaction-comments')) ->appendChild( - id(new AphrontFormMarkupControl()) + id(new AphrontFormDragAndDropUploadControl()) ->setLabel('Attached Files') - ->setValue( - '
'. - 'None'. - '
')) + ->setName('files') + ->setDragAndDropTarget($panel_id) + ->setActivatedClass('aphront-panel-view-drag-and-drop')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Avast!')); @@ -335,22 +336,11 @@ class ManiphestTaskDetailController extends ManiphestController { 'map' => $control_map, )); - $id = celerity_generate_unique_node_id(); - $comment_panel = new AphrontPanelView(); $comment_panel->appendChild($comment_form); - $comment_panel->setID($id); + $comment_panel->setID($panel_id); $comment_panel->addClass('aphront-panel-accent'); - $comment_panel->setHeader('Leap Into Action'); - - Javelin::initBehavior( - 'maniphest-transaction-drag-and-drop', - array( - 'target' => $id, - 'activatedClass' => 'aphront-panel-view-drag-and-drop', - 'uri' => '/file/dropupload/', - 'list' => 'file-list', - )); + $comment_panel->setHeader('Weigh In'); $preview_panel = '
diff --git a/src/applications/maniphest/controller/taskdetail/__init__.php b/src/applications/maniphest/controller/taskdetail/__init__.php index 9012c933dc..d9f4b18063 100644 --- a/src/applications/maniphest/controller/taskdetail/__init__.php +++ b/src/applications/maniphest/controller/taskdetail/__init__.php @@ -21,8 +21,8 @@ phutil_require_module('phabricator', 'applications/phid/handle/data'); phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phabricator', 'infrastructure/javelin/api'); phutil_require_module('phabricator', 'view/form/base'); +phutil_require_module('phabricator', 'view/form/control/draganddropupload'); phutil_require_module('phabricator', 'view/form/control/file'); -phutil_require_module('phabricator', 'view/form/control/markup'); phutil_require_module('phabricator', 'view/form/control/select'); phutil_require_module('phabricator', 'view/form/control/submit'); phutil_require_module('phabricator', 'view/form/control/textarea'); diff --git a/src/view/control/attachedfile/AphrontAttachedFileView.php b/src/view/control/attachedfile/AphrontAttachedFileView.php new file mode 100644 index 0000000000..4c9371c1b7 --- /dev/null +++ b/src/view/control/attachedfile/AphrontAttachedFileView.php @@ -0,0 +1,73 @@ +file = $file; + return $this; + } + + public function render() { + require_celerity_resource('aphront-attached-file-view-css'); + + $file = $this->file; + $phid = $file->getPHID(); + + $thumb = phutil_render_tag( + 'img', + array( + 'src' => $file->getThumb60x45URI(), + 'width' => 60, + 'height' => 45, + )); + + $name = phutil_render_tag( + 'a', + array( + 'href' => $file->getViewURI(), + 'target' => '_blank', + ), + phutil_escape_html($file->getName())); + $size = number_format($file->getByteSize()).' bytes'; + + $remove = javelin_render_tag( + 'a', + array( + 'class' => 'button grey', + 'sigil' => 'aphront-attached-file-view-remove', + // NOTE: Using 'ref' here instead of 'meta' because the file upload + // endpoint doesn't receive request metadata and thus can't generate + // a valid response with node metadata. + 'ref' => $file->getPHID(), + ), + "\xE2\x9C\x96"); // "Heavy Multiplication X" + + return + ' + + + + + +
'.$thumb.''.$name.'
'.$size.'
'.$remove.'
'; + } + +} diff --git a/src/view/control/attachedfile/__init__.php b/src/view/control/attachedfile/__init__.php new file mode 100644 index 0000000000..64cbaacb45 --- /dev/null +++ b/src/view/control/attachedfile/__init__.php @@ -0,0 +1,16 @@ +setControlID(celerity_generate_unique_node_id()); + $this->setControlStyle('display: none;'); + } + + protected function getCustomControlClass() { + return 'aphront-form-drag-and-drop-upload'; + } + + public function setDragAndDropTarget($id) { + $this->dragAndDropTarget = $id; + return $this; + } + + public function setActivatedClass($class) { + $this->activatedClass = $class; + return $this; + } + + protected function renderInput() { + require_celerity_resource('aphront-attached-file-view-css'); + + $list_id = celerity_generate_unique_node_id(); + + $files = $this->getValue(); + $value = array(); + foreach ($files as $file) { + $view = new AphrontAttachedFileView(); + $view->setFile($file); + $value[$file->getPHID()] = array( + 'phid' => $file->getPHID(), + 'html' => $view->render(), + ); + } + + Javelin::initBehavior( + 'aphront-drag-and-drop', + array( + 'control' => $this->getControlID(), + 'name' => $this->getName(), + 'value' => nonempty($value, null), + 'list' => $list_id, + 'uri' => '/file/dropupload/', + 'target' => $this->dragAndDropTarget, + 'activatedClass' => $this->activatedClass, + )); + + return phutil_render_tag( + 'div', + array( + 'id' => $list_id, + 'class' => 'aphront-form-drag-and-drop-file-list', + ), + ''); + } + +} diff --git a/src/view/form/control/draganddropupload/__init__.php b/src/view/form/control/draganddropupload/__init__.php new file mode 100644 index 0000000000..4cff5b0f5e --- /dev/null +++ b/src/view/form/control/draganddropupload/__init__.php @@ -0,0 +1,18 @@ +Upload complete!'); + } else { + arrow = String.fromCharCode(0x21EA); + status = JX.$H( + arrow + ' Drag and Drop files here to upload them.'); + } + } else { + status = JX.$H( + 'Uploading ' + parseInt(pending, 10) + ' files...'); + } + status = JX.$N('div', {className: 'drag-and-drop-instructions'}, status); + + items.push(status); + JX.DOM.setContent(list, items); + } + +}); + diff --git a/webroot/rsrc/js/application/maniphest/behavior-transaction-drag-and-drop.js b/webroot/rsrc/js/application/maniphest/behavior-transaction-drag-and-drop.js deleted file mode 100644 index 81373bfda2..0000000000 --- a/webroot/rsrc/js/application/maniphest/behavior-transaction-drag-and-drop.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @provides javelin-behavior-maniphest-transaction-drag-and-drop - * @requires javelin-behavior - * javelin-dom - * phabricator-drag-and-drop-file-upload - */ - -JX.behavior('maniphest-transaction-drag-and-drop', function(config) { - - var files = []; - - var drop = new JX.PhabricatorDragAndDropFileUpload(JX.$(config.target)) - .setActivatedClass(config.activatedClass) - .setURI(config.uri); - - drop.listen('didUpload', function(f) { - files.push(f); - redraw(); - }); - - drop.start(); - - function redraw() { - var items = []; - for (var ii = 0; ii < files.length; ii++) { - items.push(JX.$N('div', {}, files[ii].name)); - items.push(JX.$N( - 'input', - { - type: "hidden", - name: "files[" + files[ii].phid + "]", - value: files[ii].phid - })); - } - JX.DOM.setContent(JX.$(config.list), items); - } - -}); -