From 78c248d330245b2f74267590d3d0f6af952c99f3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 6 Feb 2016 14:05:15 -0800 Subject: [PATCH] Support drag-and-drop to set cover images on workboard cards Summary: This was slightly more complex than I believed, but not too terrible. Test Plan: {F1096126} - Also used some normal file uploaders to make sure I didn't break that. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15202 --- resources/builtin/image-526x526.png | Bin 0 -> 2435 bytes resources/celerity/map.php | 65 ++++---- src/__phutil_library_map__.php | 2 + .../editor/ManiphestTransactionEditor.php | 79 +++++++++ .../maniphest/storage/ManiphestTask.php | 4 + .../storage/ManiphestTransaction.php | 4 + .../PhabricatorProjectApplication.php | 1 + .../PhabricatorProjectBoardViewController.php | 3 + .../PhabricatorProjectController.php | 49 ++++++ .../PhabricatorProjectCoverController.php | 53 ++++++ .../PhabricatorProjectMoveController.php | 37 +---- .../css/phui/workboards/phui-workcard.css | 4 + .../projects/behavior-project-boards.js | 34 ++++ webroot/rsrc/js/core/DragAndDropFileUpload.js | 155 +++++++++--------- webroot/rsrc/js/core/FileUpload.js | 1 + 15 files changed, 352 insertions(+), 139 deletions(-) create mode 100644 resources/builtin/image-526x526.png create mode 100644 src/applications/project/controller/PhabricatorProjectCoverController.php diff --git a/resources/builtin/image-526x526.png b/resources/builtin/image-526x526.png new file mode 100644 index 0000000000000000000000000000000000000000..853539d21aec0c1af03257e10ceeab52a50bcc0b GIT binary patch literal 2435 zcmeAS@N?(olHy`uVBq!ia0y~yVB!N|4kn;T{7s=eASGGi8c`CQpH@Ur(SQadTTvFZr20(BV`AhCuwx}3MyviWbNkvxzTyTzlqz} z=hoO=Ffg8bCh^y$;$Q@f^4of@~^+|KUZJtfhA87q_C zKX|YpYVEeDwM9E)w5Fabn|t{1;T`Ax{Hd|2`=ui4!7Adt=hX7X2NxH+yI)h9Td+yv z$fHL|RzI`Pv9D}+?_ZfvR`xAHVecJxvr8X?=ikdQ+yBgD0@sgy!4tPXe0zJlwYBv| zYg1EGW8=y7-z_o)CUUvGi|>s-kg-f@OVrwadHZW@Ny^7sAAWpfVc6=y8pL?i-*wL& zccE?Dw`))J;&RB@a{Fzt^+Y8_kV|6zsRcz%KNqfZX@|j^mzS4kXJ^NMQQpr4l&W0S zaa`xLRIl6q`~E9CdT*R<@A>fL_dBcdzWtQQa-=(`s?la_xs-K>FOSR_v+QFhyRs?KE?zC!`4-P>FKA{ z-{0lt=E|B&E_wPcYVEYL_xJb9Z+6|6@$tvU$Ft4zcl828(?(R2S$$8x$no#rzh8cy zk*qv-+PVJcQPTY_w#F_L>?XMmEO2<_wL!V3~h>cfb_-iu;0!5e&uUh z$)OKV9-fy}6g3c1V%Qs(UtRs1$NTcd&Wd%Wxl5S3YZLmU={tWLK7|LJu*zr0?GrPb+8D_0(SdfL=nY-Piv71v*1U-!4->#M8WJEP7B zU+nxdy}lvD!EjmEt0zyE{ANgh@Ld>NNdhd6Nh*=C*8xCL>=M{32kbVEI%Q-;P-`?1 rN$&nEoqcips@}UX6wLY0BEcYgc&4%S6!SU2RuzM%tDnm{r-UW|Uft$t literal 0 HcmV?d00001 diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 077303a539..ed0bf7dd88 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,10 +8,10 @@ return array( 'names' => array( 'core.pkg.css' => 'a7d4cf8f', - 'core.pkg.js' => 'ef5e33db', + 'core.pkg.js' => '808ae845', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', - 'differential.pkg.js' => '5c2ba922', + 'differential.pkg.js' => '6b42b4bc', 'diffusion.pkg.css' => 'f45955ed', 'diffusion.pkg.js' => '3a9a8bfa', 'maniphest.pkg.css' => '4845691a', @@ -155,7 +155,7 @@ return array( 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', 'rsrc/css/phui/phui-two-column-view.css' => 'c75bfc5b', 'rsrc/css/phui/workboards/phui-workboard.css' => 'b07a5524', - 'rsrc/css/phui/workboards/phui-workcard.css' => 'adf34f58', + 'rsrc/css/phui/workboards/phui-workcard.css' => 'a869098a', 'rsrc/css/phui/workboards/phui-workpanel.css' => 'e1bd8d04', 'rsrc/css/sprite-login.css' => '60e8560e', 'rsrc/css/sprite-menu.css' => '9dd65b92', @@ -414,7 +414,7 @@ return array( 'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef', 'rsrc/js/application/policy/behavior-policy-control.js' => 'd0c516d5', 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', - 'rsrc/js/application/projects/behavior-project-boards.js' => '48470f95', + 'rsrc/js/application/projects/behavior-project-boards.js' => '5191522f', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', 'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb', 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', @@ -446,9 +446,9 @@ return array( 'rsrc/js/application/uiexample/gesture-example.js' => '558829c2', 'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5', 'rsrc/js/core/Busy.js' => '59a7976a', - 'rsrc/js/core/DragAndDropFileUpload.js' => 'ad10aeac', + 'rsrc/js/core/DragAndDropFileUpload.js' => 'da044194', 'rsrc/js/core/DraggableList.js' => '8905523d', - 'rsrc/js/core/FileUpload.js' => '477359c8', + 'rsrc/js/core/FileUpload.js' => '680ea2c8', 'rsrc/js/core/Hovercard.js' => '1bd28176', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', @@ -654,7 +654,7 @@ return array( 'javelin-behavior-phui-profile-menu' => '12884df9', 'javelin-behavior-policy-control' => 'd0c516d5', 'javelin-behavior-policy-rule-editor' => '5e9f347c', - 'javelin-behavior-project-boards' => '48470f95', + 'javelin-behavior-project-boards' => '5191522f', 'javelin-behavior-project-create' => '065227cc', 'javelin-behavior-quicksand-blacklist' => '7927a7d3', 'javelin-behavior-recurring-edit' => '5f1c4d5f', @@ -741,11 +741,11 @@ return array( 'phabricator-core-css' => '5b3563c8', 'phabricator-countdown-css' => 'e7544472', 'phabricator-dashboard-css' => 'eb458607', - 'phabricator-drag-and-drop-file-upload' => 'ad10aeac', + 'phabricator-drag-and-drop-file-upload' => 'da044194', 'phabricator-draggable-list' => '8905523d', 'phabricator-fatal-config-template-css' => '8e6c6fcd', 'phabricator-feed-css' => 'ecd4ec57', - 'phabricator-file-upload' => '477359c8', + 'phabricator-file-upload' => '680ea2c8', 'phabricator-filetree-view-css' => 'fccf9f82', 'phabricator-flag-css' => '5337623f', 'phabricator-keyboard-shortcut' => '1ae869f2', @@ -832,7 +832,7 @@ return array( 'phui-timeline-view-css' => '2efceff8', 'phui-two-column-view-css' => 'c75bfc5b', 'phui-workboard-view-css' => 'b07a5524', - 'phui-workcard-view-css' => 'adf34f58', + 'phui-workcard-view-css' => 'a869098a', 'phui-workpanel-view-css' => 'e1bd8d04', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '8cf6d262', @@ -1133,11 +1133,6 @@ return array( 'javelin-dom', 'javelin-workflow', ), - '477359c8' => array( - 'javelin-install', - 'javelin-dom', - 'phabricator-notification', - ), 47830651 => array( 'javelin-behavior', 'javelin-dom', @@ -1154,15 +1149,6 @@ return array( 'javelin-dom', 'javelin-workflow', ), - '48470f95' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', - ), '49b73b36' => array( 'javelin-behavior', 'javelin-dom', @@ -1204,6 +1190,16 @@ return array( 'javelin-typeahead-source', 'javelin-util', ), + '5191522f' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', + 'phabricator-drag-and-drop-file-upload', + ), '519705ea' => array( 'javelin-install', 'javelin-dom', @@ -1331,6 +1327,11 @@ return array( 'javelin-request', 'javelin-workflow', ), + '680ea2c8' => array( + 'javelin-install', + 'javelin-dom', + 'phabricator-notification', + ), '6882e80a' => array( 'javelin-dom', ), @@ -1674,14 +1675,6 @@ return array( 'javelin-util', 'phabricator-busy', ), - 'ad10aeac' => array( - 'javelin-install', - 'javelin-util', - 'javelin-request', - 'javelin-dom', - 'javelin-uri', - 'phabricator-file-upload', - ), 'b064af76' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1897,6 +1890,14 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), + 'da044194' => array( + 'javelin-install', + 'javelin-util', + 'javelin-request', + 'javelin-dom', + 'javelin-uri', + 'phabricator-file-upload', + ), 'dbbf48b6' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9e65dda01a..76105fdda8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2880,6 +2880,7 @@ phutil_register_library_map(array( 'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php', 'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php', 'PhabricatorProjectCoreTestCase' => 'applications/project/__tests__/PhabricatorProjectCoreTestCase.php', + 'PhabricatorProjectCoverController' => 'applications/project/controller/PhabricatorProjectCoverController.php', 'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php', 'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php', 'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php', @@ -7304,6 +7305,7 @@ phutil_register_library_map(array( ), 'PhabricatorProjectController' => 'PhabricatorController', 'PhabricatorProjectCoreTestCase' => 'PhabricatorTestCase', + 'PhabricatorProjectCoverController' => 'PhabricatorProjectController', 'PhabricatorProjectCustomField' => 'PhabricatorCustomField', 'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage', diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index 46e2a617e8..8473b3c933 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -28,6 +28,7 @@ final class ManiphestTransactionEditor $types[] = ManiphestTransaction::TYPE_UNBLOCK; $types[] = ManiphestTransaction::TYPE_PARENT; $types[] = ManiphestTransaction::TYPE_COLUMN; + $types[] = ManiphestTransaction::TYPE_COVER_IMAGE; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -66,6 +67,8 @@ final class ManiphestTransactionEditor return $xaction->getOldValue(); case ManiphestTransaction::TYPE_SUBPRIORITY: return $object->getSubpriority(); + case ManiphestTransaction::TYPE_COVER_IMAGE: + return $object->getCoverImageFilePHID(); case ManiphestTransaction::TYPE_MERGED_INTO: case ManiphestTransaction::TYPE_MERGED_FROM: return null; @@ -92,6 +95,7 @@ final class ManiphestTransactionEditor case ManiphestTransaction::TYPE_MERGED_INTO: case ManiphestTransaction::TYPE_MERGED_FROM: case ManiphestTransaction::TYPE_UNBLOCK: + case ManiphestTransaction::TYPE_COVER_IMAGE: return $xaction->getNewValue(); case ManiphestTransaction::TYPE_PARENT: case ManiphestTransaction::TYPE_COLUMN: @@ -161,6 +165,32 @@ final class ManiphestTransactionEditor case ManiphestTransaction::TYPE_MERGED_INTO: $object->setStatus(ManiphestTaskStatus::getDuplicateStatus()); return; + case ManiphestTransaction::TYPE_COVER_IMAGE: + $file_phid = $xaction->getNewValue(); + + if ($file_phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($file_phid)) + ->executeOne(); + } else { + $file = null; + } + + if (!$file || !$file->isTransformableImage()) { + $object->setProperty('cover.filePHID', null); + $object->setProperty('cover.thumbnailPHID', null); + return; + } + + $xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD; + + $xform = PhabricatorFileTransform::getTransformByKey($xform_key) + ->executeTransform($file); + + $object->setProperty('cover.filePHID', $file->getPHID()); + $object->setProperty('cover.thumbnailPHID', $xform->getPHID()); + return; case ManiphestTransaction::TYPE_MERGED_FROM: case ManiphestTransaction::TYPE_PARENT: case ManiphestTransaction::TYPE_COLUMN: @@ -819,6 +849,41 @@ final class ManiphestTransactionEditor } } break; + case ManiphestTransaction::TYPE_COVER_IMAGE: + foreach ($xactions as $xaction) { + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + if (!$new) { + continue; + } + + if ($new === $old) { + continue; + } + + $file = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($new)) + ->executeOne(); + if (!$file) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('File "%s" is not valid.', $new), + $xaction); + continue; + } + + if (!$file->isTransformableImage()) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('File "%s" is not a valid image file.', $new), + $xaction); + continue; + } + } + break; } return $errors; @@ -941,5 +1006,19 @@ final class ManiphestTransactionEditor ->executeOne(); } + protected function extractFilePHIDsFromCustomTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + $phids = parent::extractFilePHIDsFromCustomTransaction($object, $xaction); + + switch ($xaction->getTransactionType()) { + case ManiphestTransaction::TYPE_COVER_IMAGE: + $phids[] = $xaction->getNewValue(); + break; + } + + return $phids; + } + } diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php index 5b6c4129dd..2571b7a516 100644 --- a/src/applications/maniphest/storage/ManiphestTask.php +++ b/src/applications/maniphest/storage/ManiphestTask.php @@ -226,6 +226,10 @@ final class ManiphestTask extends ManiphestDAO return idx($this->properties, $key, $default); } + public function getCoverImageFilePHID() { + return idx($this->properties, 'cover.filePHID'); + } + public function getCoverImageThumbnailPHID() { return idx($this->properties, 'cover.thumbnailPHID'); } diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index 6c2c8c306f..812ac0a7de 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -16,6 +16,7 @@ final class ManiphestTransaction const TYPE_UNBLOCK = 'unblock'; const TYPE_PARENT = 'parent'; const TYPE_COLUMN = 'column'; + const TYPE_COVER_IMAGE = 'cover-image'; // NOTE: this type is deprecated. Keep it around for legacy installs // so any transactions render correctly. @@ -162,6 +163,9 @@ final class ManiphestTransaction sort($new_cols); return ($old_cols === $new_cols); + case self::TYPE_COVER_IMAGE: + // At least for now, don't show these. + return true; } return parent::shouldHide(); diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php index 5f3cb9e090..bd321bbaa8 100644 --- a/src/applications/project/application/PhabricatorProjectApplication.php +++ b/src/applications/project/application/PhabricatorProjectApplication.php @@ -72,6 +72,7 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { '(?:query/(?P[^/]+)/)?' => 'PhabricatorProjectBoardViewController', 'move/(?P[1-9]\d*)/' => 'PhabricatorProjectMoveController', + 'cover/' => 'PhabricatorProjectCoverController', 'board/(?P[1-9]\d*)/' => array( 'edit/(?:(?P\d+)/)?' => 'PhabricatorProjectColumnEditController', diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 1f00a8a7a6..ce8a63062e 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -219,6 +219,9 @@ final class PhabricatorProjectBoardViewController 'projectPHID' => $project->getPHID(), 'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'), 'createURI' => $this->getCreateURI(), + 'uploadURI' => '/file/dropupload/', + 'coverURI' => $this->getApplicationURI('cover/'), + 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(), 'order' => $this->sortKey, ); $this->initBehavior( diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php index 8687e0a3a7..e995461ba6 100644 --- a/src/applications/project/controller/PhabricatorProjectController.php +++ b/src/applications/project/controller/PhabricatorProjectController.php @@ -147,4 +147,53 @@ abstract class PhabricatorProjectController extends PhabricatorController { return $this; } + protected function newCardResponse($board_phid, $object_phid) { + $viewer = $this->getViewer(); + + $project = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($board_phid)) + ->executeOne(); + if (!$project) { + return new Aphront404Response(); + } + + // Reload the object so it reflects edits which have been applied. + $object = id(new ManiphestTaskQuery()) + ->setViewer($viewer) + ->withPHIDs(array($object_phid)) + ->needProjectPHIDs(true) + ->executeOne(); + if (!$object) { + return new Aphront404Response(); + } + + $except_phids = array($board_phid); + if ($project->getHasSubprojects() || $project->getHasMilestones()) { + $descendants = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withAncestorProjectPHIDs($except_phids) + ->execute(); + foreach ($descendants as $descendant) { + $except_phids[] = $descendant->getPHID(); + } + } + + $rendering_engine = id(new PhabricatorBoardRenderingEngine()) + ->setViewer($viewer) + ->setObjects(array($object)) + ->setExcludedProjectPHIDs($except_phids); + + $card = $rendering_engine->renderCard($object->getPHID()); + + $item = $card->getItem(); + $item->addClass('phui-workcard'); + + return id(new AphrontAjaxResponse()) + ->setContent( + array( + 'task' => $item, + )); + } + } diff --git a/src/applications/project/controller/PhabricatorProjectCoverController.php b/src/applications/project/controller/PhabricatorProjectCoverController.php new file mode 100644 index 0000000000..22f787e56b --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectCoverController.php @@ -0,0 +1,53 @@ +getViewer(); + + $request->validateCSRF(); + + $board_phid = $request->getStr('boardPHID'); + $object_phid = $request->getStr('objectPHID'); + $file_phid = $request->getStr('filePHID'); + + $object = id(new ManiphestTaskQuery()) + ->setViewer($viewer) + ->withPHIDs(array($object_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$object) { + return new Aphront404Response(); + } + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if (!$file) { + return new Aphront404Response(); + } + + $xactions = array(); + + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(ManiphestTransaction::TYPE_COVER_IMAGE) + ->setNewValue($file->getPHID()); + + $editor = id(new ManiphestTransactionEditor()) + ->setActor($viewer) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request); + + $editor->applyTransactions($object, $xactions); + + return $this->newCardResponse($board_phid, $object_phid); + } + +} diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php index 8cc8a355af..4aa9e0eec2 100644 --- a/src/applications/project/controller/PhabricatorProjectMoveController.php +++ b/src/applications/project/controller/PhabricatorProjectMoveController.php @@ -7,13 +7,14 @@ final class PhabricatorProjectMoveController $viewer = $request->getViewer(); $id = $request->getURIData('id'); + $request->validateCSRF(); + $column_phid = $request->getStr('columnPHID'); $object_phid = $request->getStr('objectPHID'); $after_phid = $request->getStr('afterPHID'); $before_phid = $request->getStr('beforePHID'); $order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER); - $project = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->requireCapabilities( @@ -175,39 +176,7 @@ final class PhabricatorProjectMoveController $editor->applyTransactions($object, $xactions); - // Reload the object so it reflects edits which have been applied. - $object = id(new ManiphestTaskQuery()) - ->setViewer($viewer) - ->withPHIDs(array($object_phid)) - ->needProjectPHIDs(true) - ->executeOne(); - - $except_phids = array($board_phid); - if ($project->getHasSubprojects() || $project->getHasMilestones()) { - $descendants = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->withAncestorProjectPHIDs($except_phids) - ->execute(); - foreach ($descendants as $descendant) { - $except_phids[] = $descendant->getPHID(); - } - } - - $rendering_engine = id(new PhabricatorBoardRenderingEngine()) - ->setViewer($viewer) - ->setObjects(array($object)) - ->setExcludedProjectPHIDs($except_phids); - - $card = $rendering_engine->renderCard($object->getPHID()); - - $item = $card->getItem(); - $item->addClass('phui-workcard'); - - return id(new AphrontAjaxResponse()) - ->setContent( - array( - 'task' => $item, - )); + return $this->newCardResponse($board_phid, $object_phid); } } diff --git a/webroot/rsrc/css/phui/workboards/phui-workcard.css b/webroot/rsrc/css/phui/workboards/phui-workcard.css index dbdee0103a..e8a6b44942 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workcard.css +++ b/webroot/rsrc/css/phui/workboards/phui-workcard.css @@ -106,6 +106,10 @@ width: 263px; } +.phui-workcard.phui-object-item.phui-workcard-upload-target { + background-color: {$sh-greenbackground}; +} + /* - Draggable Colors --------------------------------------------------------*/ diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js index 8d9b61cc8c..6ae321df8a 100644 --- a/webroot/rsrc/js/application/projects/behavior-project-boards.js +++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js @@ -7,6 +7,7 @@ * javelin-stratcom * javelin-workflow * phabricator-draggable-list + * phabricator-drag-and-drop-file-upload */ JX.behavior('project-boards', function(config, statics) { @@ -348,6 +349,39 @@ JX.behavior('project-boards', function(config, statics) { init_board(); } }); + + if (JX.PhabricatorDragAndDropFileUpload.isSupported()) { + var drop = new JX.PhabricatorDragAndDropFileUpload('project-card') + .setURI(config.uploadURI) + .setChunkThreshold(config.chunkThreshold); + + drop.listen('didBeginDrag', function(node) { + JX.DOM.alterClass(node, 'phui-workcard-upload-target', true); + }); + + drop.listen('didEndDrag', function(node) { + JX.DOM.alterClass(node, 'phui-workcard-upload-target', false); + }); + + drop.listen('didUpload', function(file) { + var node = file.getTargetNode(); + + var data = { + boardPHID: statics.projectPHID, + objectPHID: JX.Stratcom.getData(node).objectPHID, + filePHID: file.getPHID() + }; + + new JX.Workflow(config.coverURI, data) + .setHandler(function(r) { + JX.DOM.replace(node, JX.$H(r.task)); + }) + .start(); + }); + + drop.start(); + } + return true; } diff --git a/webroot/rsrc/js/core/DragAndDropFileUpload.js b/webroot/rsrc/js/core/DragAndDropFileUpload.js index cee617061c..c3c9801a17 100644 --- a/webroot/rsrc/js/core/DragAndDropFileUpload.js +++ b/webroot/rsrc/js/core/DragAndDropFileUpload.js @@ -11,8 +11,12 @@ JX.install('PhabricatorDragAndDropFileUpload', { - construct : function(node) { - this._node = node; + construct : function(target) { + if (JX.DOM.isNode(target)) { + this._node = target; + } else { + this._sigil = target; + } }, events : [ @@ -39,6 +43,7 @@ JX.install('PhabricatorDragAndDropFileUpload', { members : { _node : null, + _sigil: null, _depth : 0, _isEnabled: false, @@ -53,18 +58,21 @@ JX.install('PhabricatorDragAndDropFileUpload', { _updateDepth : function(delta) { if (this._depth === 0 && delta > 0) { - this.invoke('didBeginDrag'); + this.invoke('didBeginDrag', this._getTarget()); } this._depth += delta; if (this._depth === 0 && delta < 0) { - this.invoke('didEndDrag'); + this.invoke('didEndDrag', this._getTarget()); } }, - start : function() { + _getTarget: function() { + return this._target || this._node; + }, + start : function() { // TODO: move this to JX.DOM.contains()? function contains(container, child) { @@ -80,87 +88,87 @@ JX.install('PhabricatorDragAndDropFileUpload', { // Firefox has some issues sometimes; implement this click handler so // the user can recover. See T5188. - JX.DOM.listen( - this._node, - 'click', - null, - JX.bind(this, function (e) { - if (!this.getIsEnabled()) { - return; - } - if (this._depth) { - e.kill(); - // Force depth to 0. - this._updateDepth(-this._depth); - } - })); + var on_click = JX.bind(this, function (e) { + if (!this.getIsEnabled()) { + return; + } + + if (this._depth) { + e.kill(); + // Force depth to 0. + this._updateDepth(-this._depth); + } + }); // We track depth so that the _node may have children inside of it and // not become unselected when they are dragged over. - JX.DOM.listen( - this._node, - 'dragenter', - null, - JX.bind(this, function(e) { - if (!this.getIsEnabled()) { - return; - } + var on_dragenter = JX.bind(this, function(e) { + if (!this.getIsEnabled()) { + return; + } - if (contains(this._node, e.getTarget())) { - this._updateDepth(1); - } - })); + if (!this._node && !this._depth) { + this._target = e.getNode(this._sigil); + } - JX.DOM.listen( - this._node, - 'dragleave', - null, - JX.bind(this, function(e) { - if (!this.getIsEnabled()) { - return; - } + if (contains(this._getTarget(), e.getTarget())) { + this._updateDepth(1); + } + }); - if (contains(this._node, e.getTarget())) { - this._updateDepth(-1); - } - })); + var on_dragleave = JX.bind(this, function(e) { + if (!this.getIsEnabled()) { + return; + } - JX.DOM.listen( - this._node, - 'dragover', - null, - JX.bind(this, function(e) { - if (!this.getIsEnabled()) { - return; - } + if (contains(this._getTarget(), e.getTarget())) { + this._updateDepth(-1); + } + }); - // NOTE: We must set this, or Chrome refuses to drop files from the - // download shelf. - e.getRawEvent().dataTransfer.dropEffect = 'copy'; - e.kill(); - })); + var on_dragover = JX.bind(this, function(e) { + if (!this.getIsEnabled()) { + return; + } - JX.DOM.listen( - this._node, - 'drop', - null, - JX.bind(this, function(e) { - if (!this.getIsEnabled()) { - return; - } + // NOTE: We must set this, or Chrome refuses to drop files from the + // download shelf. + e.getRawEvent().dataTransfer.dropEffect = 'copy'; + e.kill(); + }); - e.kill(); + var on_drop = JX.bind(this, function(e) { + if (!this.getIsEnabled()) { + return; + } - var files = e.getRawEvent().dataTransfer.files; - for (var ii = 0; ii < files.length; ii++) { - this._sendRequest(files[ii]); - } + e.kill(); - // Force depth to 0. - this._updateDepth(-this._depth); - })); + var files = e.getRawEvent().dataTransfer.files; + for (var ii = 0; ii < files.length; ii++) { + this._sendRequest(files[ii]); + } - if (JX.PhabricatorDragAndDropFileUpload.isPasteSupported()) { + // Force depth to 0. + this._updateDepth(-this._depth); + }); + + if (this._node) { + JX.DOM.listen(this._node, 'click', null, on_click); + JX.DOM.listen(this._node, 'dragenter', null, on_dragenter); + JX.DOM.listen(this._node, 'dragleave', null, on_dragleave); + JX.DOM.listen(this._node, 'dragover', null, on_dragover); + JX.DOM.listen(this._node, 'drop', null, on_drop); + } else { + JX.Stratcom.listen('click', this._sigil, on_click); + JX.Stratcom.listen('dragenter', this._sigil, on_dragenter); + JX.Stratcom.listen('dragleave', this._sigil, on_dragleave); + JX.Stratcom.listen('dragover', this._sigil, on_dragover); + JX.Stratcom.listen('drop', this._sigil, on_drop); + } + + if (JX.PhabricatorDragAndDropFileUpload.isPasteSupported() && + this._node) { JX.DOM.listen( this._node, 'paste', @@ -399,6 +407,7 @@ JX.install('PhabricatorDragAndDropFileUpload', { .setURI(r.uri) .setMarkup(r.html) .setStatus('done') + .setTargetNode(this._getTarget()) .update(); this.invoke('didUpload', file); diff --git a/webroot/rsrc/js/core/FileUpload.js b/webroot/rsrc/js/core/FileUpload.js index eff1121c59..22ba33f97a 100644 --- a/webroot/rsrc/js/core/FileUpload.js +++ b/webroot/rsrc/js/core/FileUpload.js @@ -23,6 +23,7 @@ JX.install('PhabricatorFileUpload', { URI: null, status: null, markup: null, + targetNode: null, error: null },