diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c12e141770..ffb8209f41 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -179,7 +179,7 @@ return array( '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' => 'e5461a51', + 'rsrc/css/phui/workboards/phui-workpanel.css' => '4e4ec9f0', 'rsrc/css/sprite-login.css' => '18b368a6', 'rsrc/css/sprite-tokens.css' => 'f1896dc5', 'rsrc/css/syntax/syntax-default.css' => '055fc231', @@ -409,16 +409,16 @@ return array( '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' => '2f893acd', + 'rsrc/js/application/projects/WorkboardBoard.js' => '31766c31', 'rsrc/js/application/projects/WorkboardCard.js' => '0392a5d8', 'rsrc/js/application/projects/WorkboardCardTemplate.js' => '2a61f8d4', - 'rsrc/js/application/projects/WorkboardColumn.js' => 'c344eb3c', + 'rsrc/js/application/projects/WorkboardColumn.js' => 'c3d24e63', 'rsrc/js/application/projects/WorkboardController.js' => '42c7a5a7', - 'rsrc/js/application/projects/WorkboardDropEffect.js' => 'c808589e', + 'rsrc/js/application/projects/WorkboardDropEffect.js' => '8e0aa661', 'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d', 'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b', 'rsrc/js/application/projects/WorkboardOrderTemplate.js' => '03e8891f', - 'rsrc/js/application/projects/behavior-project-boards.js' => 'cd7c9d4f', + 'rsrc/js/application/projects/behavior-project-boards.js' => '8512e4ea', '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', @@ -664,7 +664,7 @@ return array( 'javelin-behavior-phuix-example' => 'c2c500a7', 'javelin-behavior-policy-control' => '0eaa33a9', 'javelin-behavior-policy-rule-editor' => '9347f172', - 'javelin-behavior-project-boards' => 'cd7c9d4f', + 'javelin-behavior-project-boards' => '8512e4ea', 'javelin-behavior-project-create' => '34c53422', 'javelin-behavior-quicksand-blacklist' => '5a6f6a06', 'javelin-behavior-read-only-warning' => 'b9109f8f', @@ -737,12 +737,12 @@ return array( 'javelin-view-renderer' => '9aae2b66', 'javelin-view-visitor' => '308f9fe4', 'javelin-websocket' => 'fdc13e4e', - 'javelin-workboard-board' => '2f893acd', + 'javelin-workboard-board' => '31766c31', 'javelin-workboard-card' => '0392a5d8', 'javelin-workboard-card-template' => '2a61f8d4', - 'javelin-workboard-column' => 'c344eb3c', + 'javelin-workboard-column' => 'c3d24e63', 'javelin-workboard-controller' => '42c7a5a7', - 'javelin-workboard-drop-effect' => 'c808589e', + 'javelin-workboard-drop-effect' => '8e0aa661', 'javelin-workboard-header' => '111bfd2d', 'javelin-workboard-header-template' => 'ebe83a6b', 'javelin-workboard-order-template' => '03e8891f', @@ -869,7 +869,7 @@ return array( 'phui-workboard-color-css' => 'e86de308', 'phui-workboard-view-css' => '74fc9d98', 'phui-workcard-view-css' => '9e9eb0df', - 'phui-workpanel-view-css' => 'e5461a51', + 'phui-workpanel-view-css' => '4e4ec9f0', 'phuix-action-list-view' => 'c68f183f', 'phuix-action-view' => 'aaa08f3b', 'phuix-autocomplete' => '8f139ef0', @@ -1178,7 +1178,11 @@ return array( 'phuix-autocomplete', 'javelin-mask', ), - '2f893acd' => array( + '308f9fe4' => array( + 'javelin-install', + 'javelin-util', + ), + '31766c31' => array( 'javelin-install', 'javelin-dom', 'javelin-util', @@ -1190,10 +1194,6 @@ return array( 'javelin-workboard-card-template', 'javelin-workboard-order-template', ), - '308f9fe4' => array( - 'javelin-install', - 'javelin-util', - ), '32755edb' => array( 'javelin-install', 'javelin-util', @@ -1351,6 +1351,9 @@ return array( 'phuix-icon-view', 'javelin-behavior-phabricator-gesture', ), + '4e4ec9f0' => array( + 'phui-workcard-view-css', + ), '4e61fa88' => array( 'javelin-behavior', 'javelin-aphlict', @@ -1591,6 +1594,16 @@ return array( 'javelin-dom', 'javelin-vector', ), + '8512e4ea' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-workboard-controller', + 'javelin-workboard-drop-effect', + ), '87428eb2' => array( 'javelin-behavior', 'javelin-diffusion-locate-file-source', @@ -1636,6 +1649,10 @@ return array( 'phabricator-shaped-request', 'conpherence-thread-manager', ), + '8e0aa661' => array( + 'javelin-install', + 'javelin-dom', + ), '8e2d9a28' => array( 'phui-theme-css', ), @@ -1940,17 +1957,17 @@ return array( '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', ), + 'c3d24e63' => array( + 'javelin-install', + 'javelin-workboard-card', + 'javelin-workboard-header', + ), 'c687e867' => array( 'javelin-behavior', 'javelin-dom', @@ -1979,10 +1996,6 @@ return array( 'phuix-icon-view', 'phabricator-busy', ), - 'c808589e' => array( - 'javelin-install', - 'javelin-dom', - ), 'c8147a20' => array( 'javelin-behavior', 'javelin-dom', @@ -2002,16 +2015,6 @@ return array( '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', @@ -2072,9 +2075,6 @@ return array( 'javelin-dom', 'javelin-history', ), - 'e5461a51' => array( - 'phui-workcard-view-css', - ), 'e562708c' => array( 'javelin-install', ), diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 6d0c8721fc..4e4ff81b4e 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -623,10 +623,20 @@ final class PhabricatorProjectBoardViewController $drop_effects = $column->getDropEffects(); $drop_effects = mpull($drop_effects, 'toDictionary'); + $preview_effect = null; + if ($column->canHaveTrigger()) { + $trigger = $column->getTrigger(); + if ($trigger) { + $preview_effect = $trigger->getPreviewEffect() + ->toDictionary(); + } + } + $column_templates[] = array( 'columnPHID' => $column_phid, 'effects' => $drop_effects, 'cardPHIDs' => $card_phids, + 'triggerPreviewEffect' => $preview_effect, ); } @@ -652,12 +662,8 @@ final class PhabricatorProjectBoardViewController $properties = array(); foreach ($all_tasks as $task) { - $properties[$task->getPHID()] = array( - 'points' => (double)$task->getPoints(), - 'status' => $task->getStatus(), - 'priority' => (int)$task->getPriority(), - 'owner' => $task->getOwnerPHID(), - ); + $properties[$task->getPHID()] = + PhabricatorBoardResponseEngine::newTaskProperties($task); } $behavior_config = array( @@ -1263,26 +1269,15 @@ final class PhabricatorProjectBoardViewController $trigger_icon = 'fa-cogs grey'; } - if ($trigger) { - $trigger_tip = array( - pht('%s: %s', $trigger->getObjectName(), $trigger->getDisplayName()), - $trigger->getRulesDescription(), - ); - $trigger_tip = implode("\n", $trigger_tip); - } else { - $trigger_tip = pht('No column trigger.'); - } - $trigger_button = id(new PHUIIconView()) ->setIcon($trigger_icon) ->setHref('#') ->addSigil('boards-dropdown-menu') - ->addSigil('has-tooltip') + ->addSigil('trigger-preview') ->setMetadata( array( 'items' => hsprintf('%s', $trigger_menu), - 'tip' => $trigger_tip, - 'size' => 300, + 'columnPHID' => $column->getPHID(), )); return $trigger_button; diff --git a/src/applications/project/engine/PhabricatorBoardResponseEngine.php b/src/applications/project/engine/PhabricatorBoardResponseEngine.php index 36c5e81150..fb5299a857 100644 --- a/src/applications/project/engine/PhabricatorBoardResponseEngine.php +++ b/src/applications/project/engine/PhabricatorBoardResponseEngine.php @@ -131,10 +131,7 @@ final class PhabricatorBoardResponseEngine extends Phobject { $card['headers'][$order_key] = $header; } - $card['properties'] = array( - 'points' => (double)$object->getPoints(), - 'status' => $object->getStatus(), - ); + $card['properties'] = self::newTaskProperties($object); } if ($card_phid === $object_phid) { @@ -159,6 +156,15 @@ final class PhabricatorBoardResponseEngine extends Phobject { ->setContent($payload); } + public static function newTaskProperties($task) { + return array( + 'points' => (double)$task->getPoints(), + 'status' => $task->getStatus(), + 'priority' => (int)$task->getPriority(), + 'owner' => $task->getOwnerPHID(), + ); + } + private function buildTemplate($object) { $viewer = $this->getViewer(); $object_phid = $this->getObjectPHID(); diff --git a/src/applications/project/icon/PhabricatorProjectDropEffect.php b/src/applications/project/icon/PhabricatorProjectDropEffect.php index 8e68261fa4..3d61f9bcef 100644 --- a/src/applications/project/icon/PhabricatorProjectDropEffect.php +++ b/src/applications/project/icon/PhabricatorProjectDropEffect.php @@ -7,6 +7,8 @@ final class PhabricatorProjectDropEffect private $color; private $content; private $conditions = array(); + private $isTriggerEffect; + private $isHeader; public function setIcon($icon) { $this->icon = $icon; @@ -40,6 +42,8 @@ final class PhabricatorProjectDropEffect 'icon' => $this->getIcon(), 'color' => $this->getColor(), 'content' => hsprintf('%s', $this->getContent()), + 'isTriggerEffect' => $this->getIsTriggerEffect(), + 'isHeader' => $this->getIsHeader(), 'conditions' => $this->getConditions(), ); } @@ -58,4 +62,22 @@ final class PhabricatorProjectDropEffect return $this->conditions; } + public function setIsTriggerEffect($is_trigger_effect) { + $this->isTriggerEffect = $is_trigger_effect; + return $this; + } + + public function getIsTriggerEffect() { + return $this->isTriggerEffect; + } + + public function setIsHeader($is_header) { + $this->isHeader = $is_header; + return $this; + } + + public function getIsHeader() { + return $this->isHeader; + } + } diff --git a/src/applications/project/storage/PhabricatorProjectTrigger.php b/src/applications/project/storage/PhabricatorProjectTrigger.php index 5029c2caea..a499f0bcf8 100644 --- a/src/applications/project/storage/PhabricatorProjectTrigger.php +++ b/src/applications/project/storage/PhabricatorProjectTrigger.php @@ -198,36 +198,6 @@ final class PhabricatorProjectTrigger return $effects; } - public function getRulesDescription() { - $rules = $this->getTriggerRules(); - if (!$rules) { - return pht('Does nothing.'); - } - - $things = array(); - - $count = count($rules); - $limit = 3; - - if ($count > $limit) { - $show_rules = array_slice($rules, 0, ($limit - 1)); - } else { - $show_rules = $rules; - } - - foreach ($show_rules as $rule) { - $things[] = $rule->getDescription(); - } - - if ($count > $limit) { - $things[] = pht( - '(Applies %s more actions.)', - new PhutilNumber($count - $limit)); - } - - return implode("\n", $things); - } - public function newDropTransactions( PhabricatorUser $viewer, PhabricatorProjectColumn $column, @@ -265,6 +235,15 @@ final class PhabricatorProjectTrigger return $trigger_xactions; } + public function getPreviewEffect() { + $header = pht('Trigger: %s', $this->getDisplayName()); + + return id(new PhabricatorProjectDropEffect()) + ->setIcon('fa-cogs') + ->setColor('blue') + ->setIsHeader(true) + ->setContent($header); + } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php index 184d818aa5..ba53b77e75 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php @@ -16,12 +16,6 @@ final class PhabricatorProjectTriggerInvalidRule return $this->exception; } - public function getDescription() { - return pht( - 'Invalid rule (of type "%s").', - $this->getRecord()->getType()); - } - public function getSelectControlName() { return pht('(Invalid Rule)'); } diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php index 5b1ad2db36..b11d7567de 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php @@ -5,14 +5,6 @@ final class PhabricatorProjectTriggerManiphestStatusRule const TRIGGERTYPE = 'task.status'; - public function getDescription() { - $value = $this->getValue(); - - return pht( - 'Changes status to "%s".', - ManiphestTaskStatus::getTaskStatusName($value)); - } - public function getSelectControlName() { return pht('Change status to'); } diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerRule.php index 49fdbf8a93..c75c15a1ab 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerRule.php @@ -37,7 +37,6 @@ abstract class PhabricatorProjectTriggerRule return $this->getRecord()->getValue(); } - abstract public function getDescription(); abstract public function getSelectControlName(); abstract public function getRuleViewLabel(); abstract public function getRuleViewDescription($value); @@ -111,7 +110,8 @@ abstract class PhabricatorProjectTriggerRule } final protected function newEffect() { - return new PhabricatorProjectDropEffect(); + return id(new PhabricatorProjectDropEffect()) + ->setIsTriggerEffect(true); } final public function toDictionary() { diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php index f71ee44ad7..925a369bae 100644 --- a/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php +++ b/src/applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php @@ -5,12 +5,6 @@ final class PhabricatorProjectTriggerUnknownRule const TRIGGERTYPE = 'unknown'; - public function getDescription() { - return pht( - 'Unknown rule (of type "%s").', - $this->getRecord()->getType()); - } - public function getSelectControlName() { return pht('(Unknown Rule)'); } diff --git a/webroot/rsrc/css/phui/workboards/phui-workpanel.css b/webroot/rsrc/css/phui/workboards/phui-workpanel.css index ce0e7885a8..97600ff5e6 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workpanel.css +++ b/webroot/rsrc/css/phui/workboards/phui-workpanel.css @@ -201,6 +201,7 @@ text-overflow: ellipsis; margin: 4px 8px; color: {$greytext}; + border-radius: 3px; } .workboard-drop-preview li .phui-icon-view { @@ -214,3 +215,13 @@ background: {$bluebackground}; margin-right: 6px; } + +.workboard-drop-preview .workboard-drop-preview-header { + background: {$sky}; + color: #fff; +} + +.workboard-drop-preview .workboard-drop-preview-header .phui-icon-view { + background: {$blue}; + color: #fff; +} diff --git a/webroot/rsrc/js/application/projects/WorkboardBoard.js b/webroot/rsrc/js/application/projects/WorkboardBoard.js index 5a55a2d905..f96e82fb8b 100644 --- a/webroot/rsrc/js/application/projects/WorkboardBoard.js +++ b/webroot/rsrc/js/application/projects/WorkboardBoard.js @@ -41,6 +41,8 @@ JX.install('WorkboardBoard', { _cards: null, _dropPreviewNode: null, _dropPreviewListNode: null, + _previewPHID: null, + _hidePreivew: false, getRoot: function() { return this._root; @@ -141,6 +143,82 @@ JX.install('WorkboardBoard', { this._columns[phid] = new JX.WorkboardColumn(this, phid, node); } + + var on_over = JX.bind(this, this._showTriggerPreview); + var on_out = JX.bind(this, this._hideTriggerPreview); + JX.Stratcom.listen('mouseover', 'trigger-preview', on_over); + JX.Stratcom.listen('mouseout', 'trigger-preview', on_out); + }, + + _showTriggerPreview: function(e) { + if (this._disablePreview) { + return; + } + + var target = e.getTarget(); + var node = e.getNode('trigger-preview'); + + if (target !== node) { + return; + } + + var phid = JX.Stratcom.getData(node).columnPHID; + var column = this._columns[phid]; + + // Bail out if we don't know anything about this column. + if (!column) { + return; + } + + if (phid === this._previewPHID) { + return; + } + + this._previewPHID = phid; + + var effects = column.getDropEffects(); + + var triggers = []; + for (var ii = 0; ii < effects.length; ii++) { + if (effects[ii].getIsTriggerEffect()) { + triggers.push(effects[ii]); + } + } + + if (triggers.length) { + var header = column.getTriggerPreviewEffect(); + triggers = [header].concat(triggers); + } + + this._showEffects(triggers); + }, + + _hideTriggerPreview: function(e) { + if (this._disablePreview) { + return; + } + + var target = e.getTarget(); + + if (target !== e.getNode('trigger-preview')) { + return; + } + + this._removeTriggerPreview(); + }, + + _removeTriggerPreview: function() { + this._showEffects([]); + this._previewPHID = null; + }, + + _beginDrag: function() { + this._disablePreview = true; + this._showEffects([]); + }, + + _endDrag: function() { + this._disablePreview = false; }, _setupDragHandlers: function() { @@ -186,6 +264,9 @@ JX.install('WorkboardBoard', { list.listen('didDrop', JX.bind(this, this._onmovecard, list)); + list.listen('didBeginDrag', JX.bind(this, this._beginDrag)); + list.listen('didEndDrag', JX.bind(this, this._endDrag)); + lists.push(list); } @@ -195,18 +276,16 @@ JX.install('WorkboardBoard', { }, _didChangeDropTarget: function(src_list, src_node, dst_list, dst_node) { - var node = this._getDropPreviewNode(); - if (!dst_list) { // The card is being dragged into a dead area, like the left menu. - JX.DOM.remove(node); + this._showEffects([]); return; } if (dst_node === false) { // The card is being dragged over itself, so dropping it won't // affect anything. - JX.DOM.remove(node); + this._showEffects([]); return; } @@ -217,7 +296,6 @@ JX.install('WorkboardBoard', { var dst_column = this.getColumn(dst_phid); var effects = []; - if (src_column !== dst_column) { effects = effects.concat(dst_column.getDropEffects()); } @@ -239,6 +317,12 @@ JX.install('WorkboardBoard', { } effects = visible; + this._showEffects(effects); + }, + + _showEffects: function(effects) { + var node = this._getDropPreviewNode(); + if (!effects.length) { JX.DOM.remove(node); return; @@ -251,7 +335,6 @@ JX.install('WorkboardBoard', { } JX.DOM.setContent(this._getDropPreviewListNode(), items); - document.body.appendChild(node); }, diff --git a/webroot/rsrc/js/application/projects/WorkboardColumn.js b/webroot/rsrc/js/application/projects/WorkboardColumn.js index 593afea776..a9bf0f8cc5 100644 --- a/webroot/rsrc/js/application/projects/WorkboardColumn.js +++ b/webroot/rsrc/js/application/projects/WorkboardColumn.js @@ -28,6 +28,10 @@ JX.install('WorkboardColumn', { this._dropEffects = []; }, + properties: { + triggerPreviewEffect: null + }, + members: { _phid: null, _root: null, diff --git a/webroot/rsrc/js/application/projects/WorkboardDropEffect.js b/webroot/rsrc/js/application/projects/WorkboardDropEffect.js index fc8b2eaa58..0c729fc517 100644 --- a/webroot/rsrc/js/application/projects/WorkboardDropEffect.js +++ b/webroot/rsrc/js/application/projects/WorkboardDropEffect.js @@ -11,6 +11,8 @@ JX.install('WorkboardDropEffect', { icon: null, color: null, content: null, + isTriggerEffect: false, + isHeader: false, conditions: [] }, @@ -20,6 +22,8 @@ JX.install('WorkboardDropEffect', { .setIcon(map.icon) .setColor(map.color) .setContent(JX.$H(map.content)) + .setIsTriggerEffect(map.isTriggerEffect) + .setIsHeader(map.isHeader) .setConditions(map.conditions || []); } }, @@ -31,7 +35,13 @@ JX.install('WorkboardDropEffect', { .setColor(this.getColor()) .getNode(); - return JX.$N('li', {}, [icon, this.getContent()]); + var attributes = {}; + + if (this.getIsHeader()) { + attributes.className = 'workboard-drop-preview-header'; + } + + return JX.$N('li', attributes, [icon, this.getContent()]); }, isEffectVisibleForCard: function(card) { diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js index f25599391f..daec59155f 100644 --- a/webroot/rsrc/js/application/projects/behavior-project-boards.js +++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js @@ -108,6 +108,12 @@ JX.behavior('project-boards', function(config, statics) { for (jj = 0; jj < spec.cardPHIDs.length; jj++) { column.newCard(spec.cardPHIDs[jj]); } + + if (spec.triggerPreviewEffect) { + column.setTriggerPreviewEffect( + JX.WorkboardDropEffect.newFromDictionary( + spec.triggerPreviewEffect)); + } } var order_maps = config.orderMaps;