diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ 'conpherence.pkg.css' => '3c8a0668', 'conpherence.pkg.js' => '020aebcf', 'core.pkg.css' => 'b797945d', - 'core.pkg.js' => 'f9c2509b', + 'core.pkg.js' => 'eaca003c', 'differential.pkg.css' => '8d8360fb', 'differential.pkg.js' => '67e02996', 'diffusion.pkg.css' => '42c75c37', @@ -178,7 +178,7 @@ 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'e86de308', 'rsrc/css/phui/workboards/phui-workboard.css' => '74fc9d98', 'rsrc/css/phui/workboards/phui-workcard.css' => '9e9eb0df', - 'rsrc/css/phui/workboards/phui-workpanel.css' => 'c5b408ad', + 'rsrc/css/phui/workboards/phui-workpanel.css' => 'e5461a51', 'rsrc/css/sprite-login.css' => '18b368a6', 'rsrc/css/sprite-tokens.css' => 'f1896dc5', 'rsrc/css/syntax/syntax-default.css' => '055fc231', @@ -408,15 +408,16 @@ 'rsrc/js/application/phortune/phortune-credit-card-form.js' => 'd12d214f', 'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9', 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '9347f172', - 'rsrc/js/application/projects/WorkboardBoard.js' => '9d59f098', + 'rsrc/js/application/projects/WorkboardBoard.js' => 'ba6e36b0', 'rsrc/js/application/projects/WorkboardCard.js' => '0392a5d8', 'rsrc/js/application/projects/WorkboardCardTemplate.js' => '2a61f8d4', - 'rsrc/js/application/projects/WorkboardColumn.js' => 'ec5c5ce0', + 'rsrc/js/application/projects/WorkboardColumn.js' => 'c344eb3c', 'rsrc/js/application/projects/WorkboardController.js' => '42c7a5a7', + 'rsrc/js/application/projects/WorkboardDropEffect.js' => '101121be', 'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d', - 'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'b65351bd', + 'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b', 'rsrc/js/application/projects/WorkboardOrderTemplate.js' => '03e8891f', - 'rsrc/js/application/projects/behavior-project-boards.js' => '412af9d4', + 'rsrc/js/application/projects/behavior-project-boards.js' => 'cd7c9d4f', 'rsrc/js/application/projects/behavior-project-create.js' => '34c53422', 'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9', 'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68', @@ -437,7 +438,7 @@ 'rsrc/js/application/uiexample/notification-example.js' => '29819b75', 'rsrc/js/core/Busy.js' => '5202e831', 'rsrc/js/core/DragAndDropFileUpload.js' => '4370900d', - 'rsrc/js/core/DraggableList.js' => '8bc7d797', + 'rsrc/js/core/DraggableList.js' => 'c9ad6f70', 'rsrc/js/core/Favicon.js' => '7930776a', 'rsrc/js/core/FileUpload.js' => 'ab85e184', 'rsrc/js/core/Hovercard.js' => '074f0783', @@ -657,7 +658,7 @@ 'javelin-behavior-phuix-example' => 'c2c500a7', 'javelin-behavior-policy-control' => '0eaa33a9', 'javelin-behavior-policy-rule-editor' => '9347f172', - 'javelin-behavior-project-boards' => '412af9d4', + 'javelin-behavior-project-boards' => 'cd7c9d4f', 'javelin-behavior-project-create' => '34c53422', 'javelin-behavior-quicksand-blacklist' => '5a6f6a06', 'javelin-behavior-read-only-warning' => 'b9109f8f', @@ -729,13 +730,14 @@ 'javelin-view-renderer' => '9aae2b66', 'javelin-view-visitor' => '308f9fe4', 'javelin-websocket' => 'fdc13e4e', - 'javelin-workboard-board' => '9d59f098', + 'javelin-workboard-board' => 'ba6e36b0', 'javelin-workboard-card' => '0392a5d8', 'javelin-workboard-card-template' => '2a61f8d4', - 'javelin-workboard-column' => 'ec5c5ce0', + 'javelin-workboard-column' => 'c344eb3c', 'javelin-workboard-controller' => '42c7a5a7', + 'javelin-workboard-drop-effect' => '101121be', 'javelin-workboard-header' => '111bfd2d', - 'javelin-workboard-header-template' => 'b65351bd', + 'javelin-workboard-header-template' => 'ebe83a6b', 'javelin-workboard-order-template' => '03e8891f', 'javelin-workflow' => '958e9045', 'maniphest-report-css' => '3d53188b', @@ -761,7 +763,7 @@ 'phabricator-diff-changeset-list' => '04023d82', 'phabricator-diff-inline' => 'a4a14a94', 'phabricator-drag-and-drop-file-upload' => '4370900d', - 'phabricator-draggable-list' => '8bc7d797', + 'phabricator-draggable-list' => 'c9ad6f70', 'phabricator-fatal-config-template-css' => '20babf50', 'phabricator-favicon' => '7930776a', 'phabricator-feed-css' => 'd8b6e3f8', @@ -860,7 +862,7 @@ 'phui-workboard-color-css' => 'e86de308', 'phui-workboard-view-css' => '74fc9d98', 'phui-workcard-view-css' => '9e9eb0df', - 'phui-workpanel-view-css' => 'c5b408ad', + 'phui-workpanel-view-css' => 'e5461a51', 'phuix-action-list-view' => 'c68f183f', 'phuix-action-view' => 'aaa08f3b', 'phuix-autocomplete' => '8f139ef0', @@ -1001,6 +1003,10 @@ 'javelin-workflow', 'phuix-icon-view', ), + '101121be' => array( + 'javelin-install', + 'javelin-dom', + ), '111bfd2d' => array( 'javelin-install', ), @@ -1227,15 +1233,6 @@ 'javelin-behavior', 'javelin-uri', ), - '412af9d4' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-workboard-controller', - ), '4234f572' => array( 'syntax-default-css', ), @@ -1593,14 +1590,6 @@ 'javelin-dom', 'javelin-typeahead-normalizer', ), - '8bc7d797' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - 'javelin-vector', - 'javelin-magical-init', - ), '8c2ed2bf' => array( 'javelin-behavior', 'javelin-dom', @@ -1725,18 +1714,6 @@ 'javelin-uri', 'phabricator-textareautils', ), - '9d59f098' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', - 'javelin-workboard-column', - 'javelin-workboard-header-template', - 'javelin-workboard-card-template', - 'javelin-workboard-order-template', - ), '9f081f05' => array( 'javelin-behavior', 'javelin-dom', @@ -1885,9 +1862,6 @@ 'javelin-stratcom', 'javelin-dom', ), - 'b65351bd' => array( - 'javelin-install', - ), 'b7b73831' => array( 'javelin-behavior', 'javelin-dom', @@ -1906,6 +1880,18 @@ 'javelin-uri', 'phabricator-notification', ), + 'ba6e36b0' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', + 'javelin-workboard-column', + 'javelin-workboard-header-template', + 'javelin-workboard-card-template', + 'javelin-workboard-order-template', + ), 'bdce4d78' => array( 'javelin-install', 'javelin-util', @@ -1930,15 +1916,17 @@ 'javelin-dom', 'phuix-button-view', ), + 'c344eb3c' => array( + 'javelin-install', + 'javelin-workboard-card', + 'javelin-workboard-header', + ), 'c3703a16' => array( 'javelin-behavior', 'javelin-aphlict', 'phabricator-phtize', 'javelin-dom', ), - 'c5b408ad' => array( - 'phui-workcard-view-css', - ), 'c687e867' => array( 'javelin-behavior', 'javelin-dom', @@ -1978,6 +1966,24 @@ 'javelin-util', 'phabricator-keyboard-shortcut-manager', ), + 'c9ad6f70' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + 'javelin-vector', + 'javelin-magical-init', + ), + 'cd7c9d4f' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-workboard-controller', + 'javelin-workboard-drop-effect', + ), 'cf32921f' => array( 'javelin-behavior', 'javelin-dom', @@ -2038,6 +2044,9 @@ 'javelin-dom', 'javelin-history', ), + 'e5461a51' => array( + 'phui-workcard-view-css', + ), 'e562708c' => array( 'javelin-install', ), @@ -2068,14 +2077,12 @@ 'javelin-install', 'javelin-event', ), + 'ebe83a6b' => array( + 'javelin-install', + ), 'ec4e31c0' => array( 'phui-timeline-view-css', ), - 'ec5c5ce0' => array( - 'javelin-install', - 'javelin-workboard-card', - 'javelin-workboard-header', - ), 'ee77366f' => array( 'aphront-dialog-view-css', ), diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4094,6 +4094,7 @@ 'PhabricatorProjectDefaultController' => 'applications/project/controller/PhabricatorProjectDefaultController.php', 'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php', 'PhabricatorProjectDetailsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php', + 'PhabricatorProjectDropEffect' => 'applications/project/icon/PhabricatorProjectDropEffect.php', 'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php', 'PhabricatorProjectEditEngine' => 'applications/project/engine/PhabricatorProjectEditEngine.php', 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', @@ -10219,6 +10220,7 @@ 'PhabricatorProjectDefaultController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField', 'PhabricatorProjectDetailsProfileMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorProjectDropEffect' => 'Phobject', 'PhabricatorProjectEditController' => 'PhabricatorProjectController', 'PhabricatorProjectEditEngine' => 'PhabricatorEditEngine', 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -540,8 +540,8 @@ ->setExcludedProjectPHIDs($select_phids); $templates = array(); - $column_maps = array(); $all_tasks = array(); + $column_templates = array(); foreach ($visible_columns as $column_phid => $column) { $column_tasks = $column_phids[$column_phid]; @@ -606,18 +606,28 @@ 'pointLimit' => $column->getPointLimit(), )); + $card_phids = array(); foreach ($column_tasks as $task) { $object_phid = $task->getPHID(); $card = $rendering_engine->renderCard($object_phid); $templates[$object_phid] = hsprintf('%s', $card->getItem()); - $column_maps[$column_phid][] = $object_phid; + $card_phids[] = $object_phid; $all_tasks[$object_phid] = $task; } $panel->setCards($cards); $board->addPanel($panel); + + $drop_effects = $column->getDropEffects(); + $drop_effects = mpull($drop_effects, 'toDictionary'); + + $column_templates[] = array( + 'columnPHID' => $column_phid, + 'effects' => $drop_effects, + 'cardPHIDs' => $card_phids, + ); } $order_key = $this->sortKey; @@ -661,9 +671,9 @@ 'headers' => $headers, 'headerKeys' => $header_keys, 'templateMap' => $templates, - 'columnMaps' => $column_maps, 'orderMaps' => $vector_map, 'propertyMaps' => $properties, + 'columnTemplates' => $column_templates, 'boardID' => $board_id, 'projectPHID' => $project->getPHID(), diff --git a/src/applications/project/icon/PhabricatorProjectDropEffect.php b/src/applications/project/icon/PhabricatorProjectDropEffect.php new file mode 100644 --- /dev/null +++ b/src/applications/project/icon/PhabricatorProjectDropEffect.php @@ -0,0 +1,45 @@ +icon = $icon; + return $this; + } + + public function getIcon() { + return $this->icon; + } + + public function setColor($color) { + $this->color = $color; + return $this; + } + + public function getColor() { + return $this->color; + } + + public function setContent($content) { + $this->content = $content; + return $this; + } + + public function getContent() { + return $this->content; + } + + public function toDictionary() { + return array( + 'icon' => $this->getIcon(), + 'color' => $this->getColor(), + 'content' => hsprintf('%s', $this->getContent()), + ); + } + +} diff --git a/src/applications/project/order/PhabricatorProjectColumnHeader.php b/src/applications/project/order/PhabricatorProjectColumnHeader.php --- a/src/applications/project/order/PhabricatorProjectColumnHeader.php +++ b/src/applications/project/order/PhabricatorProjectColumnHeader.php @@ -9,6 +9,7 @@ private $name; private $icon; private $editProperties; + private $dropEffects = array(); public function setOrderKey($order_key) { $this->orderKey = $order_key; @@ -64,6 +65,15 @@ return $this->editProperties; } + public function addDropEffect(PhabricatorProjectDropEffect $effect) { + $this->dropEffects[] = $effect; + return $this; + } + + public function getDropEffects() { + return $this->dropEffects; + } + public function toDictionary() { return array( 'order' => $this->getOrderKey(), @@ -71,6 +81,7 @@ 'template' => hsprintf('%s', $this->newView()), 'vector' => $this->getSortVector(), 'editProperties' => $this->getEditProperties(), + 'effects' => mpull($this->getDropEffects(), 'toDictionary'), ); } diff --git a/src/applications/project/order/PhabricatorProjectColumnOrder.php b/src/applications/project/order/PhabricatorProjectColumnOrder.php --- a/src/applications/project/order/PhabricatorProjectColumnOrder.php +++ b/src/applications/project/order/PhabricatorProjectColumnOrder.php @@ -196,6 +196,10 @@ ->setOrderKey($this->getColumnOrderKey()); } + final protected function newEffect() { + return new PhabricatorProjectDropEffect(); + } + final public function toDictionary() { return array( 'orderKey' => $this->getColumnOrderKey(), diff --git a/src/applications/project/order/PhabricatorProjectColumnOwnerOrder.php b/src/applications/project/order/PhabricatorProjectColumnOwnerOrder.php --- a/src/applications/project/order/PhabricatorProjectColumnOwnerOrder.php +++ b/src/applications/project/order/PhabricatorProjectColumnOwnerOrder.php @@ -122,16 +122,23 @@ $header_key = $this->newHeaderKeyForOwnerPHID($owner_phid); $owner_image = null; + $effect_content = null; if ($owner_phid === null) { $owner = null; $sort_vector = $this->newSortVectorForUnowned(); $owner_name = pht('Not Assigned'); + + $effect_content = pht('Remove task assignee.'); } else { $owner = idx($owner_users, $owner_phid); if ($owner) { $sort_vector = $this->newSortVectorForOwner($owner); $owner_name = $owner->getUsername(); $owner_image = $owner->getProfileImageURI(); + + $effect_content = pht( + 'Assign task to %s.', + phutil_tag('strong', array(), $owner_name)); } else { $sort_vector = $this->newSortVectorForOwnerPHID($owner_phid); $owner_name = pht('Unknown User ("%s")', $owner_phid); @@ -159,6 +166,14 @@ 'value' => $owner_phid, )); + if ($effect_content !== null) { + $header->addDropEffect( + $this->newEffect() + ->setIcon($owner_icon) + ->setColor($owner_color) + ->setContent($effect_content)); + } + $headers[] = $header; } diff --git a/src/applications/project/order/PhabricatorProjectColumnPriorityOrder.php b/src/applications/project/order/PhabricatorProjectColumnPriorityOrder.php --- a/src/applications/project/order/PhabricatorProjectColumnPriorityOrder.php +++ b/src/applications/project/order/PhabricatorProjectColumnPriorityOrder.php @@ -65,6 +65,14 @@ $icon_view = id(new PHUIIconView()) ->setIcon($priority_icon, $priority_color); + $drop_effect = $this->newEffect() + ->setIcon($priority_icon) + ->setColor($priority_color) + ->setContent( + pht( + 'Change priority to %s.', + phutil_tag('strong', array(), $priority_name))); + $header = $this->newHeader() ->setHeaderKey($header_key) ->setSortVector($sort_vector) @@ -73,7 +81,8 @@ ->setEditProperties( array( 'value' => (int)$priority, - )); + )) + ->addDropEffect($drop_effect); $headers[] = $header; } diff --git a/src/applications/project/order/PhabricatorProjectColumnStatusOrder.php b/src/applications/project/order/PhabricatorProjectColumnStatusOrder.php --- a/src/applications/project/order/PhabricatorProjectColumnStatusOrder.php +++ b/src/applications/project/order/PhabricatorProjectColumnStatusOrder.php @@ -72,6 +72,14 @@ $icon_view = id(new PHUIIconView()) ->setIcon($status_icon, $status_color); + $drop_effect = $this->newEffect() + ->setIcon($status_icon) + ->setColor($status_color) + ->setContent( + pht( + 'Change status to %s.', + phutil_tag('strong', array(), $status_name))); + $header = $this->newHeader() ->setHeaderKey($header_key) ->setSortVector($sort_vector) @@ -80,7 +88,8 @@ ->setEditProperties( array( 'value' => $status_key, - )); + )) + ->addDropEffect($drop_effect); $headers[] = $header; } diff --git a/src/applications/project/storage/PhabricatorProjectColumn.php b/src/applications/project/storage/PhabricatorProjectColumn.php --- a/src/applications/project/storage/PhabricatorProjectColumn.php +++ b/src/applications/project/storage/PhabricatorProjectColumn.php @@ -218,6 +218,41 @@ $this->getProject()->getID()); } + public function getDropEffects() { + $effects = array(); + + $proxy = $this->getProxy(); + if ($proxy && $proxy->isMilestone()) { + $effects[] = id(new PhabricatorProjectDropEffect()) + ->setIcon($proxy->getProxyColumnIcon()) + ->setColor('violet') + ->setContent( + pht( + 'Move to milestone %s.', + phutil_tag('strong', array(), $this->getDisplayName()))); + } else { + $effects[] = id(new PhabricatorProjectDropEffect()) + ->setIcon('fa-columns') + ->setColor('blue') + ->setContent( + pht( + 'Move to column %s.', + phutil_tag('strong', array(), $this->getDisplayName()))); + } + + + if ($this->canHaveTrigger()) { + $trigger = $this->getTrigger(); + if ($trigger) { + foreach ($trigger->getDropEffects() as $trigger_effect) { + $effects[] = $trigger_effect; + } + } + } + + return $effects; + } + /* -( PhabricatorConduitResultInterface )---------------------------------- */ diff --git a/src/applications/project/storage/PhabricatorProjectTrigger.php b/src/applications/project/storage/PhabricatorProjectTrigger.php --- a/src/applications/project/storage/PhabricatorProjectTrigger.php +++ b/src/applications/project/storage/PhabricatorProjectTrigger.php @@ -170,6 +170,19 @@ return $this->triggerRules; } + public function getDropEffects() { + $effects = array(); + + $rules = $this->getTriggerRules(); + foreach ($rules as $rule) { + foreach ($rule->getDropEffects() as $effect) { + $effects[] = $effect; + } + } + + return $effects; + } + public function getRulesDescription() { $rules = $this->getTriggerRules(); if (!$rules) { diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php --- a/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php @@ -19,4 +19,8 @@ return array(); } + protected function newDropEffects($value) { + return array(); + } + } diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php --- a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php @@ -38,4 +38,21 @@ ); } + protected function newDropEffects($value) { + $status_name = ManiphestTaskStatus::getTaskStatusName($value); + $status_icon = ManiphestTaskStatus::getStatusIcon($value); + $status_color = ManiphestTaskStatus::getStatusColor($value); + + $content = pht( + 'Change status to %s.', + phutil_tag('strong', array(), $status_name)); + + return array( + $this->newEffect() + ->setIcon($status_icon) + ->setColor($status_color) + ->setContent($content), + ); + } + } diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerRule.php --- a/src/applications/project/trigger/PhabricatorProjectTriggerRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerRule.php @@ -40,6 +40,7 @@ abstract public function getDescription(); abstract protected function assertValidRuleValue($value); abstract protected function newDropTransactions($object, $value); + abstract protected function newDropEffects($value); final public function getDropTransactions($object, $value) { return $this->newDropTransactions($object, $value); @@ -86,4 +87,12 @@ return $this->getObject()->getApplicationTransactionTemplate(); } + final public function getDropEffects() { + return $this->newDropEffects($this->getValue()); + } + + final protected function newEffect() { + return new PhabricatorProjectDropEffect(); + } + } diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php --- a/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php @@ -19,4 +19,8 @@ return array(); } + protected function newDropEffects($value) { + return array(); + } + } diff --git a/webroot/rsrc/css/phui/workboards/phui-workpanel.css b/webroot/rsrc/css/phui/workboards/phui-workpanel.css --- a/webroot/rsrc/css/phui/workboards/phui-workpanel.css +++ b/webroot/rsrc/css/phui/workboards/phui-workpanel.css @@ -178,3 +178,39 @@ margin-left: 36px; overflow: hidden; } + +.workboard-drop-preview { + pointer-events: none; + position: absolute; + bottom: 12px; + right: 12px; + width: 300px; + border-radius: 3px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); + border: 1px solid {$lightblueborder}; + padding: 4px 0; +} + +.workboard-drop-preview:hover { + opacity: 0.25; +} + +.workboard-drop-preview li { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin: 4px 8px; + color: {$greytext}; +} + +.workboard-drop-preview li .phui-icon-view { + position: relative; + display: inline-block; + text-align: center; + width: 24px; + height: 18px; + padding-top: 6px; + border-radius: 3px; + background: {$bluebackground}; + margin-right: 6px; +} diff --git a/webroot/rsrc/js/application/projects/WorkboardBoard.js b/webroot/rsrc/js/application/projects/WorkboardBoard.js --- a/webroot/rsrc/js/application/projects/WorkboardBoard.js +++ b/webroot/rsrc/js/application/projects/WorkboardBoard.js @@ -39,6 +39,8 @@ _columns: null, _headers: null, _cards: null, + _dropPreviewNode: null, + _dropPreviewListNode: null, getRoot: function() { return this._root; @@ -180,6 +182,8 @@ list.setCompareOnReorder(true); } + list.setTargetChangeHandler(JX.bind(this, this._didChangeDropTarget)); + list.listen('didDrop', JX.bind(this, this._onmovecard, list)); lists.push(list); @@ -190,23 +194,89 @@ } }, - _findCardsInColumn: function(column_node) { - return JX.DOM.scry(column_node, 'li', 'project-card'); - }, + _didChangeDropTarget: function(src_list, src_node, dst_list, dst_node) { + var node = this._getDropPreviewNode(); - _onmovecard: function(list, item, after_node, src_list) { - list.lock(); - JX.DOM.alterClass(item, 'drag-sending', true); + if (!dst_list) { + // The card is being dragged into a dead area, like the left menu. + JX.DOM.remove(node); + return; + } + + if (dst_node === false) { + // The card is being dragged over itself, so dropping it won't + // affect anything. + JX.DOM.remove(node); + return; + } var src_phid = JX.Stratcom.getData(src_list.getRootNode()).columnPHID; - var dst_phid = JX.Stratcom.getData(list.getRootNode()).columnPHID; + var dst_phid = JX.Stratcom.getData(dst_list.getRootNode()).columnPHID; - var item_phid = JX.Stratcom.getData(item).objectPHID; - var data = { - objectPHID: item_phid, - columnPHID: dst_phid, - order: this.getOrder() - }; + var src_column = this.getColumn(src_phid); + var dst_column = this.getColumn(dst_phid); + + var effects = []; + + if (src_column !== dst_column) { + effects = effects.concat(dst_column.getDropEffects()); + } + + var context = this._getDropContext(dst_node); + if (context.headerKey) { + var header = this.getHeaderTemplate(context.headerKey); + effects = effects.concat(header.getDropEffects()); + } + + if (!effects.length) { + JX.DOM.remove(node); + return; + } + + var items = []; + for (var ii = 0; ii < effects.length; ii++) { + var effect = effects[ii]; + items.push(effect.newNode()); + } + + JX.DOM.setContent(this._getDropPreviewListNode(), items); + + document.body.appendChild(node); + }, + + _getDropPreviewNode: function() { + if (!this._dropPreviewNode) { + var attributes = { + className: 'workboard-drop-preview' + }; + + var content = [ + this._getDropPreviewListNode() + ]; + + this._dropPreviewNode = JX.$N('div', attributes, content); + } + + return this._dropPreviewNode; + }, + + _getDropPreviewListNode: function() { + if (!this._dropPreviewListNode) { + var attributes = {}; + this._dropPreviewListNode = JX.$N('ul', attributes); + } + + return this._dropPreviewListNode; + }, + + _findCardsInColumn: function(column_node) { + return JX.DOM.scry(column_node, 'li', 'project-card'); + }, + + _getDropContext: function(after_node, item) { + var header_key; + var before_phid; + var after_phid; // We're going to send an "afterPHID" and a "beforePHID" if the card // was dropped immediately adjacent to another card. If a card was @@ -231,26 +301,28 @@ if (after_data) { if (after_data.objectPHID) { - data.afterPHID = after_data.objectPHID; + after_phid = after_data.objectPHID; } } - var before_data; - var before_card = item.nextSibling; - while (before_card) { - before_data = JX.Stratcom.getData(before_card); - if (before_data.objectPHID) { - break; - } - if (before_data.headerKey) { - break; + if (item) { + var before_data; + var before_card = item.nextSibling; + while (before_card) { + before_data = JX.Stratcom.getData(before_card); + if (before_data.objectPHID) { + break; + } + if (before_data.headerKey) { + break; + } + before_card = before_card.nextSibling; } - before_card = before_card.nextSibling; - } - if (before_data) { - if (before_data.objectPHID) { - data.beforePHID = before_data.objectPHID; + if (before_data) { + if (before_data.objectPHID) { + before_phid = before_data.objectPHID; + } } } @@ -265,12 +337,44 @@ } if (header_data) { - var header_key = header_data.headerKey; - if (header_key) { - var properties = this.getHeaderTemplate(header_key) - .getEditProperties(); - data.header = JX.JSON.stringify(properties); - } + header_key = header_data.headerKey; + } + + return { + headerKey: header_key, + afterPHID: after_phid, + beforePHID: before_phid + }; + }, + + _onmovecard: function(list, item, after_node, src_list) { + list.lock(); + JX.DOM.alterClass(item, 'drag-sending', true); + + var src_phid = JX.Stratcom.getData(src_list.getRootNode()).columnPHID; + var dst_phid = JX.Stratcom.getData(list.getRootNode()).columnPHID; + + var item_phid = JX.Stratcom.getData(item).objectPHID; + var data = { + objectPHID: item_phid, + columnPHID: dst_phid, + order: this.getOrder() + }; + + var context = this._getDropContext(after_node); + + if (context.afterPHID) { + data.afterPHID = context.afterPHID; + } + + if (context.beforePHID) { + data.beforePHID = context.beforePHID; + } + + if (context.headerKey) { + var properties = this.getHeaderTemplate(context.headerKey) + .getEditProperties(); + data.header = JX.JSON.stringify(properties); } var visible_phids = []; diff --git a/webroot/rsrc/js/application/projects/WorkboardColumn.js b/webroot/rsrc/js/application/projects/WorkboardColumn.js --- a/webroot/rsrc/js/application/projects/WorkboardColumn.js +++ b/webroot/rsrc/js/application/projects/WorkboardColumn.js @@ -25,6 +25,7 @@ this._headers = {}; this._objects = []; this._naturalOrder = []; + this._dropEffects = []; }, members: { @@ -40,6 +41,7 @@ _pointsContentNode: null, _dirty: true, _objects: null, + _dropEffects: null, getPHID: function() { return this._phid; @@ -71,6 +73,15 @@ return this; }, + setDropEffects: function(effects) { + this._dropEffects = effects; + return this; + }, + + getDropEffects: function() { + return this._dropEffects; + }, + getPointsNode: function() { return this._pointsNode; }, diff --git a/webroot/rsrc/js/application/projects/WorkboardDropEffect.js b/webroot/rsrc/js/application/projects/WorkboardDropEffect.js new file mode 100644 --- /dev/null +++ b/webroot/rsrc/js/application/projects/WorkboardDropEffect.js @@ -0,0 +1,35 @@ +/** + * @provides javelin-workboard-drop-effect + * @requires javelin-install + * javelin-dom + * @javelin + */ + +JX.install('WorkboardDropEffect', { + + properties: { + icon: null, + color: null, + content: null + }, + + statics: { + newFromDictionary: function(map) { + return new JX.WorkboardDropEffect() + .setIcon(map.icon) + .setColor(map.color) + .setContent(JX.$H(map.content)); + } + }, + + members: { + newNode: function() { + var icon = new JX.PHUIXIconView() + .setIcon(this.getIcon()) + .setColor(this.getColor()) + .getNode(); + + return JX.$N('li', {}, [icon, this.getContent()]); + } + } +}); diff --git a/webroot/rsrc/js/application/projects/WorkboardHeaderTemplate.js b/webroot/rsrc/js/application/projects/WorkboardHeaderTemplate.js --- a/webroot/rsrc/js/application/projects/WorkboardHeaderTemplate.js +++ b/webroot/rsrc/js/application/projects/WorkboardHeaderTemplate.js @@ -14,7 +14,8 @@ template: null, order: null, vector: null, - editProperties: null + editProperties: null, + dropEffects: [] }, members: { diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js --- 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 * javelin-workboard-controller + * javelin-workboard-drop-effect */ JX.behavior('project-boards', function(config, statics) { @@ -88,12 +89,24 @@ } var ii; - var column_maps = config.columnMaps; - for (var column_phid in column_maps) { - var column = board.getColumn(column_phid); - var column_map = column_maps[column_phid]; - for (ii = 0; ii < column_map.length; ii++) { - column.newCard(column_map[ii]); + var jj; + var effects; + + for (ii = 0; ii < config.columnTemplates.length; ii++) { + var spec = config.columnTemplates[ii]; + + var column = board.getColumn(spec.columnPHID); + + effects = []; + for (jj = 0; jj < spec.effects.length; jj++) { + effects.push( + JX.WorkboardDropEffect.newFromDictionary( + spec.effects[jj])); + } + column.setDropEffects(effects); + + for (jj = 0; jj < spec.cardPHIDs.length; jj++) { + column.newCard(spec.cardPHIDs[jj]); } } @@ -115,11 +128,19 @@ for (ii = 0; ii < headers.length; ii++) { var header = headers[ii]; + effects = []; + for (jj = 0; jj < header.effects.length; jj++) { + effects.push( + JX.WorkboardDropEffect.newFromDictionary( + header.effects[jj])); + } + board.getHeaderTemplate(header.key) .setOrder(header.order) .setNodeHTMLTemplate(header.template) .setVector(header.vector) - .setEditProperties(header.editProperties); + .setEditProperties(header.editProperties) + .setDropEffects(effects); } var orders = config.orders; diff --git a/webroot/rsrc/js/core/DraggableList.js b/webroot/rsrc/js/core/DraggableList.js --- a/webroot/rsrc/js/core/DraggableList.js +++ b/webroot/rsrc/js/core/DraggableList.js @@ -45,7 +45,8 @@ outerContainer: null, hasInfiniteHeight: false, compareOnMove: false, - compareOnReorder: false + compareOnReorder: false, + targetChangeHandler: null }, members : { @@ -53,6 +54,7 @@ _dragging : null, _locked : 0, _target : null, + _lastTarget: null, _targets : null, _ghostHandler : null, _ghostNode : null, @@ -372,6 +374,19 @@ return this; }, + _didChangeTarget: function(dst_list, dst_node) { + if (dst_node === this._lastTarget) { + return; + } + + this._lastTarget = dst_node; + + var handler = this.getTargetChangeHandler(); + if (handler) { + handler(this, this._dragging, dst_list, dst_node); + } + }, + _setIsDropTarget: function(is_target) { var root = this.getRootNode(); JX.DOM.alterClass(root, 'drag-target-list', is_target); @@ -540,6 +555,8 @@ } } + this._didChangeTarget(target_list, cur_target); + this._updateAutoscroll(this._cursorPosition); var f = JX.$V(this._frame); @@ -673,6 +690,8 @@ group[ii]._clearTarget(); } + this._didChangeTarget(null, null); + JX.DOM.alterClass(dragging, 'drag-dragging', false); JX.Tooltip.unlock();