Commit 1277db94 authored by epriestley's avatar epriestley
Browse files

When users hover over a column trigger menu, show a "preview" with the rules instead of a tooltip

Summary:
Ref T5474. The first rough cut of triggers showed some of the trigger rules in a tooltip when you hover over the "add/remove" trigger menu.

This isn't great since we don't have much room and it's a bit finnicky / hard to read.

Since we have a better way to show effects now in the drop preview, just use that instead. When you hover over the trigger menu, preview the trigger in the "drop effect" element, with a "Trigger: such-and-such" header.

Test Plan:
  - This is pretty tough to screenshot.
  - Hovered over menu, got a sensible preview of the trigger effects.
  - Dragged a card over the menu, no preview.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T5474

Differential Revision: https://secure.phabricator.com/D20304
parent 614f39b8
......@@ -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',
),
......
......@@ -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;
......
......@@ -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();
......
......@@ -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;
}
}
......@@ -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 )------------------------- */
......
......@@ -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)');
}
......
......@@ -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');
}
......
......@@ -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() {
......
......@@ -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)');
}
......
......@@ -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;
}
......@@ -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);