mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 06:42:42 +01:00
Preview the effects of a drag-and-drop operation on workboards
Summary: Ref T10335. Ref T5474. When you drag-and-drop a card on a workboard, show a UI hint which lists all the things that the operation will do. This shows: column moves; changes because of dragging a card to a different header; and changes which will be caused by triggers. Not implemented here: - Actions are currently shown even if they have no effect. For example, if you drag a "Normal" task to a different column, it says "Change priority to Normal.". I plan to hide actions which have no effect, but figuring this out is a little bit tricky. - I'd like to make "trigger effects" vs "non-trigger effects" a little more clear in the future, probably. Test Plan: Dragged stuff between columns and headers, and into columns with triggers. Got appropriate preview text hints previewing what the action would do in the UI. (This is tricky to take a screenshot of since it only shows up while the mouse cursor is down.) Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T10335, T5474 Differential Revision: https://secure.phabricator.com/D20299
This commit is contained in:
parent
149f8cc959
commit
5dca1569b5
22 changed files with 522 additions and 101 deletions
|
@ -10,7 +10,7 @@ return array(
|
|||
'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 @@ 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' => '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 @@ 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' => '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 @@ return array(
|
|||
'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 @@ return array(
|
|||
'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 @@ return array(
|
|||
'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 @@ return array(
|
|||
'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 @@ return array(
|
|||
'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 @@ return array(
|
|||
'javelin-workflow',
|
||||
'phuix-icon-view',
|
||||
),
|
||||
'101121be' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
),
|
||||
'111bfd2d' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
|
@ -1227,15 +1233,6 @@ return array(
|
|||
'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 @@ return array(
|
|||
'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 @@ return array(
|
|||
'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 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
),
|
||||
'b65351bd' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
'b7b73831' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1906,6 +1880,18 @@ return array(
|
|||
'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 @@ 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',
|
||||
),
|
||||
'c5b408ad' => array(
|
||||
'phui-workcard-view-css',
|
||||
),
|
||||
'c687e867' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1978,6 +1966,24 @@ return array(
|
|||
'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 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-history',
|
||||
),
|
||||
'e5461a51' => array(
|
||||
'phui-workcard-view-css',
|
||||
),
|
||||
'e562708c' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
|
@ -2068,14 +2077,12 @@ return array(
|
|||
'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',
|
||||
),
|
||||
|
|
|
@ -4094,6 +4094,7 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectDefaultController' => 'PhabricatorProjectBoardController',
|
||||
'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField',
|
||||
'PhabricatorProjectDetailsProfileMenuItem' => 'PhabricatorProfileMenuItem',
|
||||
'PhabricatorProjectDropEffect' => 'Phobject',
|
||||
'PhabricatorProjectEditController' => 'PhabricatorProjectController',
|
||||
'PhabricatorProjectEditEngine' => 'PhabricatorEditEngine',
|
||||
'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController',
|
||||
|
|
|
@ -540,8 +540,8 @@ final class PhabricatorProjectBoardViewController
|
|||
->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 @@ final class PhabricatorProjectBoardViewController
|
|||
'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 @@ final class PhabricatorProjectBoardViewController
|
|||
'headers' => $headers,
|
||||
'headerKeys' => $header_keys,
|
||||
'templateMap' => $templates,
|
||||
'columnMaps' => $column_maps,
|
||||
'orderMaps' => $vector_map,
|
||||
'propertyMaps' => $properties,
|
||||
'columnTemplates' => $column_templates,
|
||||
|
||||
'boardID' => $board_id,
|
||||
'projectPHID' => $project->getPHID(),
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectDropEffect
|
||||
extends Phobject {
|
||||
|
||||
private $icon;
|
||||
private $color;
|
||||
private $content;
|
||||
|
||||
public function setIcon($icon) {
|
||||
$this->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()),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@ final class PhabricatorProjectColumnHeader
|
|||
private $name;
|
||||
private $icon;
|
||||
private $editProperties;
|
||||
private $dropEffects = array();
|
||||
|
||||
public function setOrderKey($order_key) {
|
||||
$this->orderKey = $order_key;
|
||||
|
@ -64,6 +65,15 @@ final class PhabricatorProjectColumnHeader
|
|||
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 @@ final class PhabricatorProjectColumnHeader
|
|||
'template' => hsprintf('%s', $this->newView()),
|
||||
'vector' => $this->getSortVector(),
|
||||
'editProperties' => $this->getEditProperties(),
|
||||
'effects' => mpull($this->getDropEffects(), 'toDictionary'),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -196,6 +196,10 @@ abstract class PhabricatorProjectColumnOrder
|
|||
->setOrderKey($this->getColumnOrderKey());
|
||||
}
|
||||
|
||||
final protected function newEffect() {
|
||||
return new PhabricatorProjectDropEffect();
|
||||
}
|
||||
|
||||
final public function toDictionary() {
|
||||
return array(
|
||||
'orderKey' => $this->getColumnOrderKey(),
|
||||
|
|
|
@ -122,16 +122,23 @@ final class PhabricatorProjectColumnOwnerOrder
|
|||
$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 @@ final class PhabricatorProjectColumnOwnerOrder
|
|||
'value' => $owner_phid,
|
||||
));
|
||||
|
||||
if ($effect_content !== null) {
|
||||
$header->addDropEffect(
|
||||
$this->newEffect()
|
||||
->setIcon($owner_icon)
|
||||
->setColor($owner_color)
|
||||
->setContent($effect_content));
|
||||
}
|
||||
|
||||
$headers[] = $header;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,14 @@ final class PhabricatorProjectColumnPriorityOrder
|
|||
$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 @@ final class PhabricatorProjectColumnPriorityOrder
|
|||
->setEditProperties(
|
||||
array(
|
||||
'value' => (int)$priority,
|
||||
));
|
||||
))
|
||||
->addDropEffect($drop_effect);
|
||||
|
||||
$headers[] = $header;
|
||||
}
|
||||
|
|
|
@ -72,6 +72,14 @@ final class PhabricatorProjectColumnStatusOrder
|
|||
$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 @@ final class PhabricatorProjectColumnStatusOrder
|
|||
->setEditProperties(
|
||||
array(
|
||||
'value' => $status_key,
|
||||
));
|
||||
))
|
||||
->addDropEffect($drop_effect);
|
||||
|
||||
$headers[] = $header;
|
||||
}
|
||||
|
|
|
@ -218,6 +218,41 @@ final class PhabricatorProjectColumn
|
|||
$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 )---------------------------------- */
|
||||
|
||||
|
|
|
@ -170,6 +170,19 @@ final class PhabricatorProjectTrigger
|
|||
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) {
|
||||
|
|
|
@ -19,4 +19,8 @@ final class PhabricatorProjectTriggerInvalidRule
|
|||
return array();
|
||||
}
|
||||
|
||||
protected function newDropEffects($value) {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,4 +38,21 @@ final class PhabricatorProjectTriggerManiphestStatusRule
|
|||
);
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ abstract class PhabricatorProjectTriggerRule
|
|||
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 @@ abstract class PhabricatorProjectTriggerRule
|
|||
return $this->getObject()->getApplicationTransactionTemplate();
|
||||
}
|
||||
|
||||
final public function getDropEffects() {
|
||||
return $this->newDropEffects($this->getValue());
|
||||
}
|
||||
|
||||
final protected function newEffect() {
|
||||
return new PhabricatorProjectDropEffect();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,4 +19,8 @@ final class PhabricatorProjectTriggerUnknownRule
|
|||
return array();
|
||||
}
|
||||
|
||||
protected function newDropEffects($value) {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ JX.install('WorkboardBoard', {
|
|||
_columns: null,
|
||||
_headers: null,
|
||||
_cards: null,
|
||||
_dropPreviewNode: null,
|
||||
_dropPreviewListNode: null,
|
||||
|
||||
getRoot: function() {
|
||||
return this._root;
|
||||
|
@ -180,6 +182,8 @@ JX.install('WorkboardBoard', {
|
|||
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 @@ 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);
|
||||
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(dst_list.getRootNode()).columnPHID;
|
||||
|
||||
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');
|
||||
},
|
||||
|
||||
_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()
|
||||
};
|
||||
_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 @@ JX.install('WorkboardBoard', {
|
|||
|
||||
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 (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;
|
||||
}
|
||||
if (before_data.headerKey) {
|
||||
break;
|
||||
}
|
||||
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 @@ JX.install('WorkboardBoard', {
|
|||
}
|
||||
|
||||
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 = [];
|
||||
|
|
|
@ -25,6 +25,7 @@ JX.install('WorkboardColumn', {
|
|||
this._headers = {};
|
||||
this._objects = [];
|
||||
this._naturalOrder = [];
|
||||
this._dropEffects = [];
|
||||
},
|
||||
|
||||
members: {
|
||||
|
@ -40,6 +41,7 @@ JX.install('WorkboardColumn', {
|
|||
_pointsContentNode: null,
|
||||
_dirty: true,
|
||||
_objects: null,
|
||||
_dropEffects: null,
|
||||
|
||||
getPHID: function() {
|
||||
return this._phid;
|
||||
|
@ -71,6 +73,15 @@ JX.install('WorkboardColumn', {
|
|||
return this;
|
||||
},
|
||||
|
||||
setDropEffects: function(effects) {
|
||||
this._dropEffects = effects;
|
||||
return this;
|
||||
},
|
||||
|
||||
getDropEffects: function() {
|
||||
return this._dropEffects;
|
||||
},
|
||||
|
||||
getPointsNode: function() {
|
||||
return this._pointsNode;
|
||||
},
|
||||
|
|
35
webroot/rsrc/js/application/projects/WorkboardDropEffect.js
Normal file
35
webroot/rsrc/js/application/projects/WorkboardDropEffect.js
Normal file
|
@ -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()]);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -14,7 +14,8 @@ JX.install('WorkboardHeaderTemplate', {
|
|||
template: null,
|
||||
order: null,
|
||||
vector: null,
|
||||
editProperties: null
|
||||
editProperties: null,
|
||||
dropEffects: []
|
||||
},
|
||||
|
||||
members: {
|
||||
|
|
|
@ -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 @@ JX.behavior('project-boards', function(config, statics) {
|
|||
}
|
||||
|
||||
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 @@ JX.behavior('project-boards', function(config, statics) {
|
|||
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;
|
||||
|
|
|
@ -45,7 +45,8 @@ JX.install('DraggableList', {
|
|||
outerContainer: null,
|
||||
hasInfiniteHeight: false,
|
||||
compareOnMove: false,
|
||||
compareOnReorder: false
|
||||
compareOnReorder: false,
|
||||
targetChangeHandler: null
|
||||
},
|
||||
|
||||
members : {
|
||||
|
@ -53,6 +54,7 @@ JX.install('DraggableList', {
|
|||
_dragging : null,
|
||||
_locked : 0,
|
||||
_target : null,
|
||||
_lastTarget: null,
|
||||
_targets : null,
|
||||
_ghostHandler : null,
|
||||
_ghostNode : null,
|
||||
|
@ -372,6 +374,19 @@ JX.install('DraggableList', {
|
|||
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 @@ JX.install('DraggableList', {
|
|||
}
|
||||
}
|
||||
|
||||
this._didChangeTarget(target_list, cur_target);
|
||||
|
||||
this._updateAutoscroll(this._cursorPosition);
|
||||
|
||||
var f = JX.$V(this._frame);
|
||||
|
@ -673,6 +690,8 @@ JX.install('DraggableList', {
|
|||
group[ii]._clearTarget();
|
||||
}
|
||||
|
||||
this._didChangeTarget(null, null);
|
||||
|
||||
JX.DOM.alterClass(dragging, 'drag-dragging', false);
|
||||
JX.Tooltip.unlock();
|
||||
|
||||
|
|
Loading…
Reference in a new issue