mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-22 05:20:56 +01:00
Rewrite workboards to have way more bugs
Summary: Ref T4900. Briefly: - Much more layout and rendering is now done in Javascript. - This should otherwise be identical to the behavior at HEAD, except that: - editing a task and removing the current board from it no longer removes the task; and - points still don't work. However, this can now plausibly support realtime workboard updates and other complex state-based behaviors like points calculations in a future change. Test Plan: - Changed card covers. - Moved cards. - Sorted board by priority and natural. - Added new cards. - Edited cards in place. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4900 Differential Revision: https://secure.phabricator.com/D15234
This commit is contained in:
parent
01084bfe22
commit
0bf3519045
15 changed files with 936 additions and 542 deletions
|
@ -415,8 +415,11 @@ return array(
|
|||
'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef',
|
||||
'rsrc/js/application/policy/behavior-policy-control.js' => 'd0c516d5',
|
||||
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c',
|
||||
'rsrc/js/application/projects/Workboard.js' => '088b2495',
|
||||
'rsrc/js/application/projects/behavior-project-boards.js' => '37eb99e4',
|
||||
'rsrc/js/application/projects/WorkboardBoard.js' => '069d6dd3',
|
||||
'rsrc/js/application/projects/WorkboardCard.js' => '2fcefa17',
|
||||
'rsrc/js/application/projects/WorkboardColumn.js' => 'e8f303bb',
|
||||
'rsrc/js/application/projects/WorkboardController.js' => 'fa1378c3',
|
||||
'rsrc/js/application/projects/behavior-project-boards.js' => 'e1b56d72',
|
||||
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
|
||||
'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb',
|
||||
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
|
||||
|
@ -656,7 +659,7 @@ return array(
|
|||
'javelin-behavior-phui-profile-menu' => '12884df9',
|
||||
'javelin-behavior-policy-control' => 'd0c516d5',
|
||||
'javelin-behavior-policy-rule-editor' => '5e9f347c',
|
||||
'javelin-behavior-project-boards' => '37eb99e4',
|
||||
'javelin-behavior-project-boards' => 'e1b56d72',
|
||||
'javelin-behavior-project-create' => '065227cc',
|
||||
'javelin-behavior-quicksand-blacklist' => '7927a7d3',
|
||||
'javelin-behavior-recurring-edit' => '5f1c4d5f',
|
||||
|
@ -723,7 +726,10 @@ return array(
|
|||
'javelin-view-renderer' => '6c2b09a2',
|
||||
'javelin-view-visitor' => 'efe49472',
|
||||
'javelin-websocket' => 'e292eaf4',
|
||||
'javelin-workboard' => '088b2495',
|
||||
'javelin-workboard-board' => '069d6dd3',
|
||||
'javelin-workboard-card' => '2fcefa17',
|
||||
'javelin-workboard-column' => 'e8f303bb',
|
||||
'javelin-workboard-controller' => 'fa1378c3',
|
||||
'javelin-workflow' => '5b2e3e2b',
|
||||
'lightbox-attachment-css' => '7acac05d',
|
||||
'maniphest-batch-editor' => 'b0f0b6d5',
|
||||
|
@ -913,6 +919,15 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
),
|
||||
'069d6dd3' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'phabricator-draggable-list',
|
||||
'javelin-workboard-column',
|
||||
),
|
||||
'06c32383' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-typeahead-ondemand-source',
|
||||
|
@ -930,16 +945,6 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-vector',
|
||||
),
|
||||
'088b2495' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'phabricator-draggable-list',
|
||||
'phabricator-drag-and-drop-file-upload',
|
||||
),
|
||||
'0a3f3021' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1077,6 +1082,9 @@ return array(
|
|||
'2ee659ce' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
'2fcefa17' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
'327a00d1' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1096,17 +1104,6 @@ return array(
|
|||
'javelin-vector',
|
||||
'phuix-autocomplete',
|
||||
),
|
||||
'37eb99e4' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'phabricator-draggable-list',
|
||||
'phabricator-drag-and-drop-file-upload',
|
||||
'javelin-workboard',
|
||||
),
|
||||
'3ab51e2c' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-behavior-device',
|
||||
|
@ -1937,6 +1934,15 @@ return array(
|
|||
'javelin-dom',
|
||||
'phabricator-prefab',
|
||||
),
|
||||
'e1b56d72' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'javelin-workboard-controller',
|
||||
),
|
||||
'e1d25dfb' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -2004,6 +2010,10 @@ return array(
|
|||
'e6e25838' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
'e8f303bb' => array(
|
||||
'javelin-install',
|
||||
'javelin-workboard-card',
|
||||
),
|
||||
'e9581f08' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -2088,6 +2098,16 @@ return array(
|
|||
'javelin-vector',
|
||||
'javelin-magical-init',
|
||||
),
|
||||
'fa1378c3' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-vector',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'phabricator-drag-and-drop-file-upload',
|
||||
'javelin-workboard-board',
|
||||
),
|
||||
'fb20ac8d' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-aphlict',
|
||||
|
|
|
@ -1820,6 +1820,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorBitbucketAuthProvider' => 'applications/auth/provider/PhabricatorBitbucketAuthProvider.php',
|
||||
'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php',
|
||||
'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php',
|
||||
'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php',
|
||||
'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php',
|
||||
'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php',
|
||||
'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php',
|
||||
|
@ -6058,6 +6059,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorBitbucketAuthProvider' => 'PhabricatorOAuth1AuthProvider',
|
||||
'PhabricatorBoardLayoutEngine' => 'Phobject',
|
||||
'PhabricatorBoardRenderingEngine' => 'Phobject',
|
||||
'PhabricatorBoardResponseEngine' => 'Phobject',
|
||||
'PhabricatorBot' => 'PhabricatorDaemon',
|
||||
'PhabricatorBotChannel' => 'PhabricatorBotTarget',
|
||||
'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler',
|
||||
|
|
|
@ -9,6 +9,7 @@ final class ManiphestTaskEditController extends ManiphestController {
|
|||
->addContextParameter('responseType')
|
||||
->addContextParameter('columnPHID')
|
||||
->addContextParameter('order')
|
||||
->addContextParameter('visiblePHIDs')
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
|
|
|
@ -289,7 +289,11 @@ final class ManiphestEditEngine
|
|||
$viewer = $request->getViewer();
|
||||
|
||||
$column_phid = $request->getStr('columnPHID');
|
||||
$order = $request->getStr('order');
|
||||
|
||||
$visible_phids = $request->getStrList('visiblePHIDs');
|
||||
if (!$visible_phids) {
|
||||
$visible_phids = array();
|
||||
}
|
||||
|
||||
$column = id(new PhabricatorProjectColumnQuery())
|
||||
->setViewer($viewer)
|
||||
|
@ -299,98 +303,15 @@ final class ManiphestEditEngine
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
// If the workboard's project and all descendant projects have been removed
|
||||
// from the card's project list, we are going to remove it from the board
|
||||
// completely.
|
||||
|
||||
// TODO: If the user did something sneaky and changed a subproject, we'll
|
||||
// currently leave the card where it was but should really move it to the
|
||||
// proper new column.
|
||||
|
||||
$board_phid = $column->getProjectPHID();
|
||||
$object_phid = $task->getPHID();
|
||||
|
||||
$descendant_projects = id(new PhabricatorProjectQuery())
|
||||
return id(new PhabricatorBoardResponseEngine())
|
||||
->setViewer($viewer)
|
||||
->withAncestorProjectPHIDs(array($column->getProjectPHID()))
|
||||
->execute();
|
||||
$board_phids = mpull($descendant_projects, 'getPHID', 'getPHID');
|
||||
$board_phids[$board_phid] = $board_phid;
|
||||
|
||||
$project_map = array_fuse($task->getProjectPHIDs());
|
||||
$remove_card = !array_intersect_key($board_phids, $project_map);
|
||||
|
||||
// TODO: Maybe the caller should pass a list of visible task PHIDs so we
|
||||
// know which ones we need to reorder? This is a HUGE overfetch.
|
||||
$objects = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withEdgeLogicPHIDs(
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
||||
PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
|
||||
array($board_phids))
|
||||
->setViewer($viewer)
|
||||
->execute();
|
||||
$objects = mpull($objects, null, 'getPHID');
|
||||
|
||||
$layout_engine = id(new PhabricatorBoardLayoutEngine())
|
||||
->setViewer($viewer)
|
||||
->setBoardPHIDs(array($board_phid))
|
||||
->setObjectPHIDs(array_keys($objects))
|
||||
->executeLayout();
|
||||
|
||||
$positions = $layout_engine->getColumnObjectPositions(
|
||||
$board_phid,
|
||||
$column_phid);
|
||||
|
||||
$column_phids = $layout_engine->getColumnObjectPHIDs(
|
||||
$board_phid,
|
||||
$column_phid);
|
||||
|
||||
$column_tasks = array_select_keys($objects, $column_phids);
|
||||
|
||||
if ($order == PhabricatorProjectColumn::ORDER_NATURAL) {
|
||||
// TODO: This is a little bit awkward, because PHP and JS use
|
||||
// slightly different sort order parameters to achieve the same
|
||||
// effect. It would be good to unify this a bit at some point.
|
||||
$sort_map = array();
|
||||
foreach ($positions as $position) {
|
||||
$sort_map[$position->getObjectPHID()] = array(
|
||||
-$position->getSequence(),
|
||||
$position->getID(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$sort_map = mpull(
|
||||
$column_tasks,
|
||||
'getPrioritySortVector',
|
||||
'getPHID');
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'removeFromBoard' => $remove_card,
|
||||
'sortMap' => $sort_map,
|
||||
);
|
||||
|
||||
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
|
||||
->setViewer($viewer)
|
||||
->setObjects(array($task))
|
||||
->setExcludedProjectPHIDs($board_phids);
|
||||
|
||||
$card = $rendering_engine->renderCard($task->getPHID());
|
||||
|
||||
$item = $card->getItem();
|
||||
$item->addClass('phui-workcard');
|
||||
|
||||
$payload = array(
|
||||
'tasks' => $item,
|
||||
'data' => $data,
|
||||
);
|
||||
|
||||
return id(new AphrontAjaxResponse())
|
||||
->setContent(
|
||||
array(
|
||||
'tasks' => $item,
|
||||
'data' => $data,
|
||||
));
|
||||
->setBoardPHID($board_phid)
|
||||
->setObjectPHID($object_phid)
|
||||
->setVisiblePHIDs($visible_phids)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -194,14 +194,6 @@ final class ManiphestTask extends ManiphestDAO
|
|||
return ManiphestTaskStatus::isClosedStatus($this->getStatus());
|
||||
}
|
||||
|
||||
public function getPrioritySortVector() {
|
||||
return array(
|
||||
$this->getPriority(),
|
||||
-$this->getSubpriority(),
|
||||
$this->getID(),
|
||||
);
|
||||
}
|
||||
|
||||
public function setProperty($key, $value) {
|
||||
$this->properties[$key] = $value;
|
||||
return $this;
|
||||
|
@ -219,6 +211,16 @@ final class ManiphestTask extends ManiphestDAO
|
|||
return idx($this->properties, 'cover.thumbnailPHID');
|
||||
}
|
||||
|
||||
public function getWorkboardOrderVectors() {
|
||||
return array(
|
||||
PhabricatorProjectColumn::ORDER_PRIORITY => array(
|
||||
(int)-$this->getPriority(),
|
||||
(double)-$this->getSubpriority(),
|
||||
(int)-$this->getID(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorSubscribableInterface )----------------------------------- */
|
||||
|
||||
|
|
|
@ -238,20 +238,6 @@ final class PhabricatorProjectBoardViewController
|
|||
'boardPHID' => $project->getPHID(),
|
||||
));
|
||||
|
||||
$behavior_config = array(
|
||||
'boardID' => $board_id,
|
||||
'projectPHID' => $project->getPHID(),
|
||||
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
|
||||
'createURI' => $this->getCreateURI(),
|
||||
'uploadURI' => '/file/dropupload/',
|
||||
'coverURI' => $this->getApplicationURI('cover/'),
|
||||
'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(),
|
||||
'order' => $this->sortKey,
|
||||
);
|
||||
$this->initBehavior(
|
||||
'project-boards',
|
||||
$behavior_config);
|
||||
|
||||
$visible_columns = array();
|
||||
$column_phids = array();
|
||||
$visible_phids = array();
|
||||
|
@ -297,6 +283,9 @@ final class PhabricatorProjectBoardViewController
|
|||
->setEditMap($task_can_edit_map)
|
||||
->setExcludedProjectPHIDs($select_phids);
|
||||
|
||||
$templates = array();
|
||||
$column_maps = array();
|
||||
$all_tasks = array();
|
||||
foreach ($visible_columns as $column_phid => $column) {
|
||||
$column_tasks = $column_phids[$column_phid];
|
||||
|
||||
|
@ -356,14 +345,35 @@ final class PhabricatorProjectBoardViewController
|
|||
));
|
||||
|
||||
foreach ($column_tasks as $task) {
|
||||
$card = $rendering_engine->renderCard($task->getPHID());
|
||||
$cards->addItem($card->getItem());
|
||||
$object_phid = $task->getPHID();
|
||||
|
||||
$card = $rendering_engine->renderCard($object_phid);
|
||||
$templates[$object_phid] = hsprintf('%s', $card->getItem());
|
||||
$column_maps[$column_phid][] = $object_phid;
|
||||
|
||||
$all_tasks[$object_phid] = $task;
|
||||
}
|
||||
|
||||
$panel->setCards($cards);
|
||||
$board->addPanel($panel);
|
||||
}
|
||||
|
||||
$behavior_config = array(
|
||||
'boardID' => $board_id,
|
||||
'projectPHID' => $project->getPHID(),
|
||||
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
|
||||
'createURI' => $this->getCreateURI(),
|
||||
'uploadURI' => '/file/dropupload/',
|
||||
'coverURI' => $this->getApplicationURI('cover/'),
|
||||
'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(),
|
||||
'order' => $this->sortKey,
|
||||
'templateMap' => $templates,
|
||||
'columnMaps' => $column_maps,
|
||||
'orderMaps' => mpull($all_tasks, 'getWorkboardOrderVectors'),
|
||||
);
|
||||
$this->initBehavior('project-boards', $behavior_config);
|
||||
|
||||
|
||||
$sort_menu = $this->buildSortMenu(
|
||||
$viewer,
|
||||
$this->sortKey);
|
||||
|
|
|
@ -150,51 +150,18 @@ abstract class PhabricatorProjectController extends PhabricatorController {
|
|||
protected function newCardResponse($board_phid, $object_phid) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$project = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($board_phid))
|
||||
->executeOne();
|
||||
if (!$project) {
|
||||
return new Aphront404Response();
|
||||
$request = $this->getRequest();
|
||||
$visible_phids = $request->getStrList('visiblePHIDs');
|
||||
if (!$visible_phids) {
|
||||
$visible_phids = array();
|
||||
}
|
||||
|
||||
// Reload the object so it reflects edits which have been applied.
|
||||
$object = id(new ManiphestTaskQuery())
|
||||
return id(new PhabricatorBoardResponseEngine())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($object_phid))
|
||||
->needProjectPHIDs(true)
|
||||
->executeOne();
|
||||
if (!$object) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$except_phids = array($board_phid);
|
||||
if ($project->getHasSubprojects() || $project->getHasMilestones()) {
|
||||
$descendants = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withAncestorProjectPHIDs($except_phids)
|
||||
->execute();
|
||||
foreach ($descendants as $descendant) {
|
||||
$except_phids[] = $descendant->getPHID();
|
||||
}
|
||||
}
|
||||
|
||||
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
|
||||
->setViewer($viewer)
|
||||
->setObjects(array($object))
|
||||
->setExcludedProjectPHIDs($except_phids);
|
||||
|
||||
$card = $rendering_engine->renderCard($object->getPHID());
|
||||
|
||||
$item = $card->getItem();
|
||||
$item->addClass('phui-workcard');
|
||||
|
||||
return id(new AphrontAjaxResponse())
|
||||
->setContent(
|
||||
array(
|
||||
'objectPHID' => $object->getPHID(),
|
||||
'cardHTML' => $item,
|
||||
));
|
||||
->setBoardPHID($board_phid)
|
||||
->setObjectPHID($object_phid)
|
||||
->setVisiblePHIDs($visible_phids)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorBoardResponseEngine extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $boardPHID;
|
||||
private $objectPHID;
|
||||
private $visiblePHIDs;
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
public function setBoardPHID($board_phid) {
|
||||
$this->boardPHID = $board_phid;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBoardPHID() {
|
||||
return $this->boardPHID;
|
||||
}
|
||||
|
||||
public function setObjectPHID($object_phid) {
|
||||
$this->objectPHID = $object_phid;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObjectPHID() {
|
||||
return $this->objectPHID;
|
||||
}
|
||||
|
||||
public function setVisiblePHIDs(array $visible_phids) {
|
||||
$this->visiblePHIDs = $visible_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getVisiblePHIDs() {
|
||||
return $this->visiblePHIDs;
|
||||
}
|
||||
|
||||
public function buildResponse() {
|
||||
$viewer = $this->getViewer();
|
||||
$object_phid = $this->getObjectPHID();
|
||||
$board_phid = $this->getBoardPHID();
|
||||
|
||||
// Load all the other tasks that are visible in the affected columns and
|
||||
// perform layout for them.
|
||||
$visible_phids = $this->getAllVisiblePHIDs();
|
||||
|
||||
$layout_engine = id(new PhabricatorBoardLayoutEngine())
|
||||
->setViewer($viewer)
|
||||
->setBoardPHIDs(array($board_phid))
|
||||
->setObjectPHIDs($visible_phids)
|
||||
->executeLayout();
|
||||
|
||||
$object_columns = $layout_engine->getObjectColumns(
|
||||
$board_phid,
|
||||
$object_phid);
|
||||
|
||||
$natural = array();
|
||||
foreach ($object_columns as $column_phid => $column) {
|
||||
$column_object_phids = $layout_engine->getColumnObjectPHIDs(
|
||||
$board_phid,
|
||||
$column_phid);
|
||||
$natural[$column_phid] = array_values($column_object_phids);
|
||||
}
|
||||
|
||||
$all_visible = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($visible_phids)
|
||||
->execute();
|
||||
|
||||
$order_maps = array();
|
||||
foreach ($all_visible as $visible) {
|
||||
$order_maps[$visible->getPHID()] = $visible->getWorkboardOrderVectors();
|
||||
}
|
||||
|
||||
$template = $this->buildTemplate();
|
||||
|
||||
$payload = array(
|
||||
'objectPHID' => $object_phid,
|
||||
'cardHTML' => $template,
|
||||
'columnMaps' => $natural,
|
||||
'orderMaps' => $order_maps,
|
||||
);
|
||||
|
||||
return id(new AphrontAjaxResponse())
|
||||
->setContent($payload);
|
||||
}
|
||||
|
||||
private function buildTemplate() {
|
||||
$viewer = $this->getViewer();
|
||||
$object_phid = $this->getObjectPHID();
|
||||
|
||||
$excluded_phids = $this->loadExcludedProjectPHIDs();
|
||||
|
||||
$object = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($object_phid))
|
||||
->needProjectPHIDs(true)
|
||||
->executeOne();
|
||||
if (!$object) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
|
||||
->setViewer($viewer)
|
||||
->setObjects(array($object))
|
||||
->setExcludedProjectPHIDs($excluded_phids);
|
||||
|
||||
$card = $rendering_engine->renderCard($object_phid);
|
||||
|
||||
return hsprintf('%s', $card->getItem());
|
||||
}
|
||||
|
||||
private function loadExcludedProjectPHIDs() {
|
||||
$viewer = $this->getViewer();
|
||||
$board_phid = $this->getBoardPHID();
|
||||
|
||||
$exclude_phids = array($board_phid);
|
||||
|
||||
$descendants = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withAncestorProjectPHIDs($exclude_phids)
|
||||
->execute();
|
||||
|
||||
foreach ($descendants as $descendant) {
|
||||
$exclude_phids[] = $descendant->getPHID();
|
||||
}
|
||||
|
||||
return array_fuse($exclude_phids);
|
||||
}
|
||||
|
||||
private function getAllVisiblePHIDs() {
|
||||
$visible_phids = $this->getVisiblePHIDs();
|
||||
$visible_phids[] = $this->getObjectPHID();
|
||||
$visible_phids = array_fuse($visible_phids);
|
||||
return $visible_phids;
|
||||
}
|
||||
|
||||
}
|
|
@ -78,10 +78,6 @@ final class ProjectBoardTaskCard extends Phobject {
|
|||
->setHref('/T'.$task->getID())
|
||||
->addSigil('project-card')
|
||||
->setDisabled($task->isClosed())
|
||||
->setMetadata(
|
||||
array(
|
||||
'objectPHID' => $task->getPHID(),
|
||||
))
|
||||
->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setName(pht('Edit'))
|
||||
|
@ -115,6 +111,8 @@ final class ProjectBoardTaskCard extends Phobject {
|
|||
$card->addAttribute($tag_list);
|
||||
}
|
||||
|
||||
$card->addClass('phui-workcard');
|
||||
|
||||
return $card;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,242 +0,0 @@
|
|||
/**
|
||||
* @provides javelin-workboard
|
||||
* @requires javelin-install
|
||||
* javelin-dom
|
||||
* javelin-util
|
||||
* javelin-vector
|
||||
* javelin-stratcom
|
||||
* javelin-workflow
|
||||
* phabricator-draggable-list
|
||||
* phabricator-drag-and-drop-file-upload
|
||||
* @javelin
|
||||
*/
|
||||
|
||||
JX.install('Workboard', {
|
||||
|
||||
construct: function(config) {
|
||||
this._config = config;
|
||||
|
||||
this._boardNodes = {};
|
||||
this._columnMap = {};
|
||||
},
|
||||
|
||||
properties: {
|
||||
uploadURI: null,
|
||||
coverURI: null,
|
||||
moveURI: null,
|
||||
chunkThreshold: null
|
||||
},
|
||||
|
||||
members: {
|
||||
_config: null,
|
||||
_boardNodes: null,
|
||||
_currentBoard: null,
|
||||
|
||||
_panOrigin: null,
|
||||
_panNode: null,
|
||||
_panX: null,
|
||||
|
||||
_columnMap: null,
|
||||
|
||||
start: function() {
|
||||
this._setupCoverImageHandlers();
|
||||
this._setupPanHandlers();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
addBoard: function(board_phid, board_node) {
|
||||
this._currentBoard = board_phid;
|
||||
this._boardNodes[board_phid] = board_node;
|
||||
this._setupDragHandlers(board_node);
|
||||
},
|
||||
|
||||
_getConfig: function() {
|
||||
return this._config;
|
||||
},
|
||||
|
||||
_setupCoverImageHandlers: function() {
|
||||
if (!JX.PhabricatorDragAndDropFileUpload.isSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var drop = new JX.PhabricatorDragAndDropFileUpload('project-card')
|
||||
.setURI(this.getUploadURI())
|
||||
.setChunkThreshold(this.getChunkThreshold());
|
||||
|
||||
drop.listen('didBeginDrag', function(node) {
|
||||
JX.DOM.alterClass(node, 'phui-workcard-upload-target', true);
|
||||
});
|
||||
|
||||
drop.listen('didEndDrag', function(node) {
|
||||
JX.DOM.alterClass(node, 'phui-workcard-upload-target', false);
|
||||
});
|
||||
|
||||
drop.listen('didUpload', JX.bind(this, this._oncoverupload));
|
||||
|
||||
drop.start();
|
||||
},
|
||||
|
||||
_oncoverupload: function(file) {
|
||||
var node = file.getTargetNode();
|
||||
var board = JX.DOM.findAbove(node, 'div', 'jx-workboard');
|
||||
|
||||
var data = {
|
||||
boardPHID: JX.Stratcom.getData(board).boardPHID,
|
||||
objectPHID: JX.Stratcom.getData(node).objectPHID,
|
||||
filePHID: file.getPHID()
|
||||
};
|
||||
|
||||
new JX.Workflow(this.getCoverURI(), data)
|
||||
.setHandler(JX.bind(this, this._queueCardUpdate))
|
||||
.start();
|
||||
},
|
||||
|
||||
_setupPanHandlers: function() {
|
||||
var mousedown = JX.bind(this, this._onpanmousedown);
|
||||
var mousemove = JX.bind(this, this._onpanmousemove);
|
||||
var mouseup = JX.bind(this, this._onpanmouseup);
|
||||
|
||||
JX.Stratcom.listen('mousedown', 'workboard-shadow', mousedown);
|
||||
JX.Stratcom.listen('mousemove', null, mousemove);
|
||||
JX.Stratcom.listen('mouseup', null, mouseup);
|
||||
},
|
||||
|
||||
_onpanmousedown: function(e) {
|
||||
if (!JX.Device.isDesktop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.getNode('workpanel')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (JX.Stratcom.pass()) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.kill();
|
||||
|
||||
this._panOrigin = JX.$V(e);
|
||||
this._panNode = e.getNode('workboard-shadow');
|
||||
this._panX = this._panNode.scrollLeft;
|
||||
},
|
||||
|
||||
_onpanmousemove: function(e) {
|
||||
if (!this._panOrigin) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cursor = JX.$V(e);
|
||||
this._panNode.scrollLeft = this._panX + (this._panOrigin.x - cursor.x);
|
||||
},
|
||||
|
||||
_onpanmouseup: function() {
|
||||
this._panOrigin = null;
|
||||
},
|
||||
|
||||
|
||||
_setupDragHandlers: function(board_node) {
|
||||
var columns = this._findBoardColumns(board_node);
|
||||
var column;
|
||||
var ii;
|
||||
var lists = [];
|
||||
|
||||
for (ii = 0; ii < columns.length; ii++) {
|
||||
column = columns[ii];
|
||||
|
||||
var list = new JX.DraggableList('project-card', column)
|
||||
.setOuterContainer(board_node)
|
||||
.setFindItemsHandler(JX.bind(this, this._findCardsInColumn, column))
|
||||
.setCanDragX(true)
|
||||
.setHasInfiniteHeight(true);
|
||||
|
||||
// TODO: Restore these behaviors.
|
||||
// list.listen('didSend', JX.bind(list, onupdate, cols[ii]));
|
||||
// list.listen('didReceive', JX.bind(list, onupdate, cols[ii]));
|
||||
// onupdate(cols[ii]);
|
||||
|
||||
list.listen('didDrop', JX.bind(this, this._onmovecard, list));
|
||||
|
||||
lists.push(list);
|
||||
}
|
||||
|
||||
for (ii = 0; ii < lists.length; ii++) {
|
||||
lists[ii].setGroup(lists);
|
||||
}
|
||||
},
|
||||
|
||||
_findBoardColumns: function(board_node) {
|
||||
return JX.DOM.scry(board_node, 'ul', 'project-column');
|
||||
},
|
||||
|
||||
_findCardsInColumn: function(column_node) {
|
||||
return JX.DOM.scry(column_node, 'li', 'project-card');
|
||||
},
|
||||
|
||||
_onmovecard: function(list, item, after_node) {
|
||||
list.lock();
|
||||
JX.DOM.alterClass(item, 'drag-sending', true);
|
||||
|
||||
var item_phid = JX.Stratcom.getData(item).objectPHID;
|
||||
var data = {
|
||||
objectPHID: item_phid,
|
||||
columnPHID: JX.Stratcom.getData(list.getRootNode()).columnPHID
|
||||
};
|
||||
|
||||
if (after_node) {
|
||||
data.afterPHID = JX.Stratcom.getData(after_node).objectPHID;
|
||||
}
|
||||
|
||||
var before_node = item.nextSibling;
|
||||
if (before_node) {
|
||||
var before_phid = JX.Stratcom.getData(before_node).objectPHID;
|
||||
if (before_phid) {
|
||||
data.beforePHID = before_phid;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should be managed per-board.
|
||||
var config = this._getConfig();
|
||||
data.order = config.order;
|
||||
|
||||
new JX.Workflow(this.getMoveURI(), data)
|
||||
.setHandler(JX.bind(this, this._oncardupdate, item, list))
|
||||
.start();
|
||||
},
|
||||
|
||||
_oncardupdate: function(item, list, response) {
|
||||
list.unlock();
|
||||
JX.DOM.alterClass(item, 'drag-sending', false);
|
||||
|
||||
this._queueCardUpdate(response);
|
||||
},
|
||||
|
||||
_queueCardUpdate: function(response) {
|
||||
var board_node = this._boardNodes[this._currentBoard];
|
||||
|
||||
var columns = this._findBoardColumns(board_node);
|
||||
var cards;
|
||||
var ii;
|
||||
var jj;
|
||||
var data;
|
||||
|
||||
for (ii = 0; ii < columns.length; ii++) {
|
||||
cards = this._findCardsInColumn(columns[ii]);
|
||||
for (jj = 0; jj < cards.length; jj++) {
|
||||
data = JX.Stratcom.getData(cards[jj]);
|
||||
if (data.objectPHID == response.objectPHID) {
|
||||
this._replaceCard(cards[jj], JX.$H(response.cardHTML));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_replaceCard: function(old_node, new_node) {
|
||||
JX.DOM.replace(old_node, new_node);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
221
webroot/rsrc/js/application/projects/WorkboardBoard.js
Normal file
221
webroot/rsrc/js/application/projects/WorkboardBoard.js
Normal file
|
@ -0,0 +1,221 @@
|
|||
/**
|
||||
* @provides javelin-workboard-board
|
||||
* @requires javelin-install
|
||||
* javelin-dom
|
||||
* javelin-util
|
||||
* javelin-stratcom
|
||||
* javelin-workflow
|
||||
* phabricator-draggable-list
|
||||
* javelin-workboard-column
|
||||
* @javelin
|
||||
*/
|
||||
|
||||
JX.install('WorkboardBoard', {
|
||||
|
||||
construct: function(controller, phid, root) {
|
||||
this._controller = controller;
|
||||
this._phid = phid;
|
||||
this._root = root;
|
||||
|
||||
this._templates = {};
|
||||
this._orderMaps = {};
|
||||
this._buildColumns();
|
||||
},
|
||||
|
||||
properties: {
|
||||
order: null,
|
||||
},
|
||||
|
||||
members: {
|
||||
_controller: null,
|
||||
_phid: null,
|
||||
_root: null,
|
||||
_columns: null,
|
||||
_templates: null,
|
||||
_orderMaps: null,
|
||||
|
||||
getRoot: function() {
|
||||
return this._root;
|
||||
},
|
||||
|
||||
getColumns: function() {
|
||||
return this._columns;
|
||||
},
|
||||
|
||||
getColumn: function(k) {
|
||||
return this._columns[k];
|
||||
},
|
||||
|
||||
getPHID: function() {
|
||||
return this._phid;
|
||||
},
|
||||
|
||||
setCardTemplate: function(phid, template) {
|
||||
this._templates[phid] = template;
|
||||
return this;
|
||||
},
|
||||
|
||||
getCardTemplate: function(phid) {
|
||||
return this._templates[phid];
|
||||
},
|
||||
|
||||
getController: function() {
|
||||
return this._controller;
|
||||
},
|
||||
|
||||
setOrderMap: function(phid, map) {
|
||||
this._orderMaps[phid] = map;
|
||||
return this;
|
||||
},
|
||||
|
||||
getOrderVector: function(phid, key) {
|
||||
return this._orderMaps[phid][key];
|
||||
},
|
||||
|
||||
start: function() {
|
||||
this._setupDragHandlers();
|
||||
|
||||
for (var k in this._columns) {
|
||||
this._columns[k].redraw();
|
||||
}
|
||||
},
|
||||
|
||||
_buildColumns: function() {
|
||||
var nodes = JX.DOM.scry(this.getRoot(), 'ul', 'project-column');
|
||||
|
||||
this._columns = {};
|
||||
for (var ii = 0; ii < nodes.length; ii++) {
|
||||
var node = nodes[ii];
|
||||
var data = JX.Stratcom.getData(node);
|
||||
var phid = data.columnPHID;
|
||||
|
||||
this._columns[phid] = new JX.WorkboardColumn(this, phid, node);
|
||||
}
|
||||
},
|
||||
|
||||
_setupDragHandlers: function() {
|
||||
var columns = this.getColumns();
|
||||
|
||||
var lists = [];
|
||||
for (var k in columns) {
|
||||
var column = columns[k];
|
||||
|
||||
var list = new JX.DraggableList('project-card', column.getRoot())
|
||||
.setOuterContainer(this.getRoot())
|
||||
.setFindItemsHandler(JX.bind(column, column.getCardNodes))
|
||||
.setCanDragX(true)
|
||||
.setHasInfiniteHeight(true);
|
||||
|
||||
list.listen('didDrop', JX.bind(this, this._onmovecard, list));
|
||||
|
||||
lists.push(list);
|
||||
}
|
||||
|
||||
for (var ii = 0; ii < lists.length; ii++) {
|
||||
lists[ii].setGroup(lists);
|
||||
}
|
||||
},
|
||||
|
||||
_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()
|
||||
};
|
||||
|
||||
if (after_node) {
|
||||
data.afterPHID = JX.Stratcom.getData(after_node).objectPHID;
|
||||
}
|
||||
|
||||
var before_node = item.nextSibling;
|
||||
if (before_node) {
|
||||
var before_phid = JX.Stratcom.getData(before_node).objectPHID;
|
||||
if (before_phid) {
|
||||
data.beforePHID = before_phid;
|
||||
}
|
||||
}
|
||||
|
||||
var visible_phids = [];
|
||||
var column = this.getColumn(dst_phid);
|
||||
for (var object_phid in column.getCards()) {
|
||||
visible_phids.push(object_phid);
|
||||
}
|
||||
|
||||
data.visiblePHIDs = visible_phids.join(',');
|
||||
|
||||
var onupdate = JX.bind(
|
||||
this,
|
||||
this._oncardupdate,
|
||||
list,
|
||||
src_phid,
|
||||
dst_phid,
|
||||
data.afterPHID);
|
||||
|
||||
new JX.Workflow(this.getController().getMoveURI(), data)
|
||||
.setHandler(onupdate)
|
||||
.start();
|
||||
},
|
||||
|
||||
_oncardupdate: function(list, src_phid, dst_phid, after_phid, response) {
|
||||
var src_column = this.getColumn(src_phid);
|
||||
var dst_column = this.getColumn(dst_phid);
|
||||
|
||||
var card = src_column.removeCard(response.objectPHID);
|
||||
dst_column.addCard(card, after_phid);
|
||||
|
||||
this.updateCard(response);
|
||||
|
||||
list.unlock();
|
||||
},
|
||||
|
||||
updateCard: function(response) {
|
||||
var columns = this.getColumns();
|
||||
|
||||
var phid = response.objectPHID;
|
||||
|
||||
if (!this._templates[phid]) {
|
||||
for (var add_phid in response.columnMaps) {
|
||||
this.getColumn(add_phid).newCard(phid);
|
||||
}
|
||||
}
|
||||
|
||||
this.setCardTemplate(phid, response.cardHTML);
|
||||
|
||||
var order_maps = response.orderMaps;
|
||||
for (var order_phid in order_maps) {
|
||||
this.setOrderMap(order_phid, order_maps[order_phid]);
|
||||
}
|
||||
|
||||
var column_maps = response.columnMaps;
|
||||
for (var natural_phid in column_maps) {
|
||||
this.getColumn(natural_phid).setNaturalOrder(column_maps[natural_phid]);
|
||||
}
|
||||
|
||||
for (var column_phid in columns) {
|
||||
var cards = columns[column_phid].getCards();
|
||||
for (var object_phid in cards) {
|
||||
if (object_phid !== phid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var card = cards[object_phid];
|
||||
card.redraw();
|
||||
}
|
||||
columns[column_phid].redraw();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
56
webroot/rsrc/js/application/projects/WorkboardCard.js
Normal file
56
webroot/rsrc/js/application/projects/WorkboardCard.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* @provides javelin-workboard-card
|
||||
* @requires javelin-install
|
||||
* @javelin
|
||||
*/
|
||||
|
||||
JX.install('WorkboardCard', {
|
||||
|
||||
construct: function(column, phid) {
|
||||
this._column = column;
|
||||
this._phid = phid;
|
||||
},
|
||||
|
||||
members: {
|
||||
_column: null,
|
||||
_phid: null,
|
||||
_root: null,
|
||||
|
||||
getPHID: function() {
|
||||
return this._phid;
|
||||
},
|
||||
|
||||
getColumn: function() {
|
||||
return this._column;
|
||||
},
|
||||
|
||||
setColumn: function(column) {
|
||||
this._column = column;
|
||||
},
|
||||
|
||||
getNode: function() {
|
||||
if (!this._root) {
|
||||
var phid = this.getPHID();
|
||||
var template = this.getColumn().getBoard().getCardTemplate(phid);
|
||||
this._root = JX.$H(template).getFragment().firstChild;
|
||||
|
||||
JX.Stratcom.getData(this._root).objectPHID = this.getPHID();
|
||||
}
|
||||
return this._root;
|
||||
},
|
||||
|
||||
redraw: function() {
|
||||
var old_node = this._root;
|
||||
this._root = null;
|
||||
var new_node = this.getNode();
|
||||
|
||||
if (old_node && old_node.parentNode) {
|
||||
JX.DOM.replace(old_node, new_node);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
177
webroot/rsrc/js/application/projects/WorkboardColumn.js
Normal file
177
webroot/rsrc/js/application/projects/WorkboardColumn.js
Normal file
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
* @provides javelin-workboard-column
|
||||
* @requires javelin-install
|
||||
* javelin-workboard-card
|
||||
* @javelin
|
||||
*/
|
||||
|
||||
JX.install('WorkboardColumn', {
|
||||
|
||||
construct: function(board, phid, root) {
|
||||
this._board = board;
|
||||
this._phid = phid;
|
||||
this._root = root;
|
||||
|
||||
this._cards = {};
|
||||
this._naturalOrder = [];
|
||||
},
|
||||
|
||||
members: {
|
||||
_phid: null,
|
||||
_root: null,
|
||||
_board: null,
|
||||
_cards: null,
|
||||
_naturalOrder: null,
|
||||
|
||||
getPHID: function() {
|
||||
return this._phid;
|
||||
},
|
||||
|
||||
getRoot: function() {
|
||||
return this._root;
|
||||
},
|
||||
|
||||
getCards: function() {
|
||||
return this._cards;
|
||||
},
|
||||
|
||||
getCard: function(phid) {
|
||||
return this._cards[phid];
|
||||
},
|
||||
|
||||
getBoard: function() {
|
||||
return this._board;
|
||||
},
|
||||
|
||||
setNaturalOrder: function(order) {
|
||||
this._naturalOrder = order;
|
||||
return this;
|
||||
},
|
||||
|
||||
newCard: function(phid) {
|
||||
var card = new JX.WorkboardCard(this, phid);
|
||||
|
||||
this._cards[phid] = card;
|
||||
this._naturalOrder.push(phid);
|
||||
|
||||
return card;
|
||||
},
|
||||
|
||||
removeCard: function(phid) {
|
||||
var card = this._cards[phid];
|
||||
delete this._cards[phid];
|
||||
|
||||
for (var ii = 0; ii < this._naturalOrder.length; ii++) {
|
||||
if (this._naturalOrder[ii] == phid) {
|
||||
this._naturalOrder.splice(ii, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return card;
|
||||
},
|
||||
|
||||
addCard: function(card, after) {
|
||||
var phid = card.getPHID();
|
||||
|
||||
card.setColumn(this);
|
||||
this._cards[phid] = card;
|
||||
|
||||
var index = 0;
|
||||
|
||||
if (after) {
|
||||
for (var ii = 0; ii < this._naturalOrder.length; ii++) {
|
||||
if (this._naturalOrder[ii] == after) {
|
||||
index = ii + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (index > this._naturalOrder.length) {
|
||||
this._naturalOrder.push(phid);
|
||||
} else {
|
||||
this._naturalOrder.splice(index, 0, phid);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
getCardNodes: function() {
|
||||
var cards = this.getCards();
|
||||
|
||||
var nodes = [];
|
||||
for (var k in cards) {
|
||||
nodes.push(cards[k].getNode());
|
||||
}
|
||||
|
||||
return nodes;
|
||||
},
|
||||
|
||||
getCardPHIDs: function() {
|
||||
return JX.keys(this.getCards());
|
||||
},
|
||||
|
||||
redraw: function() {
|
||||
var order = this.getBoard().getOrder();
|
||||
|
||||
var list;
|
||||
if (order == 'natural') {
|
||||
list = this._getCardsSortedNaturally();
|
||||
} else {
|
||||
list = this._getCardsSortedByKey(order);
|
||||
}
|
||||
|
||||
var content = [];
|
||||
for (var ii = 0; ii < list.length; ii++) {
|
||||
var node = list[ii].getNode();
|
||||
content.push(node);
|
||||
}
|
||||
|
||||
JX.DOM.setContent(this.getRoot(), content);
|
||||
},
|
||||
|
||||
_getCardsSortedNaturally: function() {
|
||||
var list = [];
|
||||
|
||||
for (var ii = 0; ii < this._naturalOrder.length; ii++) {
|
||||
var phid = this._naturalOrder[ii];
|
||||
list.push(this.getCard(phid));
|
||||
}
|
||||
|
||||
return list;
|
||||
},
|
||||
|
||||
_getCardsSortedByKey: function(order) {
|
||||
var cards = this.getCards();
|
||||
|
||||
var list = [];
|
||||
for (var k in cards) {
|
||||
list.push(cards[k]);
|
||||
}
|
||||
|
||||
list.sort(JX.bind(this, this._sortCards, order));
|
||||
|
||||
return list;
|
||||
},
|
||||
|
||||
_sortCards: function(order, u, v) {
|
||||
var ud = this.getBoard().getOrderVector(u.getPHID(), order);
|
||||
var vd = this.getBoard().getOrderVector(v.getPHID(), order);
|
||||
|
||||
for (var ii = 0; ii < ud.length; ii++) {
|
||||
if (ud[ii] > vd[ii]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ud[ii] < vd[ii]) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
201
webroot/rsrc/js/application/projects/WorkboardController.js
Normal file
201
webroot/rsrc/js/application/projects/WorkboardController.js
Normal file
|
@ -0,0 +1,201 @@
|
|||
/**
|
||||
* @provides javelin-workboard-controller
|
||||
* @requires javelin-install
|
||||
* javelin-dom
|
||||
* javelin-util
|
||||
* javelin-vector
|
||||
* javelin-stratcom
|
||||
* javelin-workflow
|
||||
* phabricator-drag-and-drop-file-upload
|
||||
* javelin-workboard-board
|
||||
* @javelin
|
||||
*/
|
||||
|
||||
JX.install('WorkboardController', {
|
||||
|
||||
construct: function() {
|
||||
this._boards = {};
|
||||
},
|
||||
|
||||
properties: {
|
||||
uploadURI: null,
|
||||
coverURI: null,
|
||||
moveURI: null,
|
||||
createURI: null,
|
||||
chunkThreshold: null
|
||||
},
|
||||
|
||||
members: {
|
||||
_boards: null,
|
||||
|
||||
_panOrigin: null,
|
||||
_panNode: null,
|
||||
_panX: null,
|
||||
|
||||
start: function() {
|
||||
this._setupCoverImageHandlers();
|
||||
this._setupPanHandlers();
|
||||
this._setupEditHandlers();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
newBoard: function(phid, node) {
|
||||
var board = new JX.WorkboardBoard(this, phid, node);
|
||||
this._boards[phid] = board;
|
||||
return board;
|
||||
},
|
||||
|
||||
_getBoard: function(board_phid) {
|
||||
return this._boards[board_phid];
|
||||
},
|
||||
|
||||
_setupCoverImageHandlers: function() {
|
||||
if (!JX.PhabricatorDragAndDropFileUpload.isSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var drop = new JX.PhabricatorDragAndDropFileUpload('project-card')
|
||||
.setURI(this.getUploadURI())
|
||||
.setChunkThreshold(this.getChunkThreshold());
|
||||
|
||||
drop.listen('didBeginDrag', function(node) {
|
||||
JX.DOM.alterClass(node, 'phui-workcard-upload-target', true);
|
||||
});
|
||||
|
||||
drop.listen('didEndDrag', function(node) {
|
||||
JX.DOM.alterClass(node, 'phui-workcard-upload-target', false);
|
||||
});
|
||||
|
||||
drop.listen('didUpload', JX.bind(this, this._oncoverupload));
|
||||
|
||||
drop.start();
|
||||
},
|
||||
|
||||
_oncoverupload: function(file) {
|
||||
var node = file.getTargetNode();
|
||||
|
||||
var board = this._getBoardFromNode(node);
|
||||
|
||||
var column_node = JX.DOM.findAbove(node, 'ul', 'project-column');
|
||||
var column_phid = JX.Stratcom.getData(column_node).columnPHID;
|
||||
var column = board.getColumn(column_phid);
|
||||
|
||||
var data = {
|
||||
boardPHID: board.getPHID(),
|
||||
objectPHID: JX.Stratcom.getData(node).objectPHID,
|
||||
filePHID: file.getPHID(),
|
||||
visiblePHIDs: column.getCardPHIDs()
|
||||
};
|
||||
|
||||
new JX.Workflow(this.getCoverURI(), data)
|
||||
.setHandler(JX.bind(board, board.updateCard))
|
||||
.start();
|
||||
},
|
||||
|
||||
_getBoardFromNode: function(node) {
|
||||
var board_node = JX.DOM.findAbove(node, 'div', 'jx-workboard');
|
||||
var board_phid = JX.Stratcom.getData(board_node).boardPHID;
|
||||
return this._getBoard(board_phid);
|
||||
},
|
||||
|
||||
_setupPanHandlers: function() {
|
||||
var mousedown = JX.bind(this, this._onpanmousedown);
|
||||
var mousemove = JX.bind(this, this._onpanmousemove);
|
||||
var mouseup = JX.bind(this, this._onpanmouseup);
|
||||
|
||||
JX.Stratcom.listen('mousedown', 'workboard-shadow', mousedown);
|
||||
JX.Stratcom.listen('mousemove', null, mousemove);
|
||||
JX.Stratcom.listen('mouseup', null, mouseup);
|
||||
},
|
||||
|
||||
_onpanmousedown: function(e) {
|
||||
if (!JX.Device.isDesktop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.getNode('workpanel')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (JX.Stratcom.pass()) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.kill();
|
||||
|
||||
this._panOrigin = JX.$V(e);
|
||||
this._panNode = e.getNode('workboard-shadow');
|
||||
this._panX = this._panNode.scrollLeft;
|
||||
},
|
||||
|
||||
_onpanmousemove: function(e) {
|
||||
if (!this._panOrigin) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cursor = JX.$V(e);
|
||||
this._panNode.scrollLeft = this._panX + (this._panOrigin.x - cursor.x);
|
||||
},
|
||||
|
||||
_onpanmouseup: function() {
|
||||
this._panOrigin = null;
|
||||
},
|
||||
|
||||
_setupEditHandlers: function() {
|
||||
var onadd = JX.bind(this, this._onaddcard);
|
||||
var onedit = JX.bind(this, this._oneditcard);
|
||||
|
||||
JX.Stratcom.listen('click', 'column-add-task', onadd);
|
||||
JX.Stratcom.listen('click', 'edit-project-card', onedit);
|
||||
},
|
||||
|
||||
_onaddcard: function(e) {
|
||||
// We want the 'boards-dropdown-menu' behavior to see this event and
|
||||
// close the dropdown, but don't want to follow the link.
|
||||
e.prevent();
|
||||
|
||||
var column_data = e.getNodeData('column-add-task');
|
||||
var column_phid = column_data.columnPHID;
|
||||
|
||||
var board_phid = column_data.projectPHID;
|
||||
var board = this._getBoard(board_phid);
|
||||
var column = board.getColumn(column_phid);
|
||||
|
||||
var request_data = {
|
||||
responseType: 'card',
|
||||
columnPHID: column.getPHID(),
|
||||
projects: board.getPHID(),
|
||||
visiblePHIDs: column.getCardPHIDs(),
|
||||
order: board.getOrder()
|
||||
};
|
||||
|
||||
new JX.Workflow(this.getCreateURI(), request_data)
|
||||
.setHandler(JX.bind(board, board.updateCard))
|
||||
.start();
|
||||
},
|
||||
|
||||
_oneditcard: function(e) {
|
||||
e.kill();
|
||||
|
||||
var column_node = e.getNode('project-column');
|
||||
var column_phid = JX.Stratcom.getData(column_node).columnPHID;
|
||||
|
||||
var board = this._getBoardFromNode(column_node);
|
||||
var column = board.getColumn(column_phid);
|
||||
|
||||
var request_data = {
|
||||
responseType: 'card',
|
||||
columnPHID: column.getPHID(),
|
||||
visiblePHIDs: column.getCardPHIDs(),
|
||||
order: board.getOrder()
|
||||
};
|
||||
|
||||
new JX.Workflow(e.getNode('tag:a').href, request_data)
|
||||
.setHandler(JX.bind(board, board.updateCard))
|
||||
.start();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
|
@ -6,9 +6,7 @@
|
|||
* javelin-vector
|
||||
* javelin-stratcom
|
||||
* javelin-workflow
|
||||
* phabricator-draggable-list
|
||||
* phabricator-drag-and-drop-file-upload
|
||||
* javelin-workboard
|
||||
* javelin-workboard-controller
|
||||
*/
|
||||
|
||||
JX.behavior('project-boards', function(config, statics) {
|
||||
|
@ -61,66 +59,6 @@ JX.behavior('project-boards', function(config, statics) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function colsort(u, v) {
|
||||
var ud = JX.Stratcom.getData(u).sort || [];
|
||||
var vd = JX.Stratcom.getData(v).sort || [];
|
||||
|
||||
for (var ii = 0; ii < ud.length; ii++) {
|
||||
|
||||
if (parseInt(ud[ii]) < parseInt(vd[ii])) {
|
||||
return 1;
|
||||
}
|
||||
if (parseInt(ud[ii]) > parseInt(vd[ii])) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function onedit(column, r) {
|
||||
var new_card = JX.$H(r.tasks).getNode();
|
||||
var new_data = JX.Stratcom.getData(new_card);
|
||||
var items = finditems(column);
|
||||
var edited = false;
|
||||
var remove_index = null;
|
||||
|
||||
for (var ii = 0; ii < items.length; ii++) {
|
||||
var item = items[ii];
|
||||
|
||||
var data = JX.Stratcom.getData(item);
|
||||
var phid = data.objectPHID;
|
||||
|
||||
if (phid == new_data.objectPHID) {
|
||||
if (r.data.removeFromBoard) {
|
||||
remove_index = ii;
|
||||
}
|
||||
items[ii] = new_card;
|
||||
data = new_data;
|
||||
edited = true;
|
||||
}
|
||||
|
||||
data.sort = r.data.sortMap[data.objectPHID] || data.sort;
|
||||
}
|
||||
|
||||
// this is an add then...!
|
||||
if (!edited) {
|
||||
items[items.length + 1] = new_card;
|
||||
new_data.sort = r.data.sortMap[new_data.objectPHID] || new_data.sort;
|
||||
}
|
||||
|
||||
if (remove_index !== null) {
|
||||
items.splice(remove_index, 1);
|
||||
}
|
||||
|
||||
items.sort(colsort);
|
||||
|
||||
JX.DOM.setContent(column, items);
|
||||
|
||||
onupdate(column);
|
||||
};
|
||||
|
||||
function update_statics(update_config) {
|
||||
statics.boardID = update_config.boardID;
|
||||
statics.projectPHID = update_config.projectPHID;
|
||||
|
@ -130,56 +68,6 @@ JX.behavior('project-boards', function(config, statics) {
|
|||
}
|
||||
|
||||
function setup() {
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'click',
|
||||
['edit-project-card'],
|
||||
function(e) {
|
||||
e.kill();
|
||||
var column = e.getNode('project-column');
|
||||
var request_data = {
|
||||
responseType: 'card',
|
||||
columnPHID: JX.Stratcom.getData(column).columnPHID,
|
||||
order: statics.order
|
||||
};
|
||||
new JX.Workflow(e.getNode('tag:a').href, request_data)
|
||||
.setHandler(JX.bind(null, onedit, column))
|
||||
.start();
|
||||
});
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'click',
|
||||
['column-add-task'],
|
||||
function (e) {
|
||||
|
||||
// We want the 'boards-dropdown-menu' behavior to see this event and
|
||||
// close the dropdown, but don't want to follow the link.
|
||||
e.prevent();
|
||||
|
||||
var column_data = e.getNodeData('column-add-task');
|
||||
var column_phid = column_data.columnPHID;
|
||||
|
||||
var request_data = {
|
||||
responseType: 'card',
|
||||
columnPHID: column_phid,
|
||||
projects: column_data.projectPHID,
|
||||
order: statics.order
|
||||
};
|
||||
|
||||
var cols = getcolumns();
|
||||
var ii;
|
||||
var column;
|
||||
for (ii = 0; ii < cols.length; ii++) {
|
||||
if (JX.Stratcom.getData(cols[ii]).columnPHID == column_phid) {
|
||||
column = cols[ii];
|
||||
break;
|
||||
}
|
||||
}
|
||||
new JX.Workflow(statics.createURI, request_data)
|
||||
.setHandler(JX.bind(null, onedit, column))
|
||||
.start();
|
||||
});
|
||||
|
||||
JX.Stratcom.listen('click', 'boards-dropdown-menu', function(e) {
|
||||
var data = e.getNodeData('boards-dropdown-menu');
|
||||
if (data.menu) {
|
||||
|
@ -234,14 +122,40 @@ JX.behavior('project-boards', function(config, statics) {
|
|||
}
|
||||
|
||||
if (!statics.workboard) {
|
||||
statics.workboard = new JX.Workboard(config)
|
||||
statics.workboard = new JX.WorkboardController()
|
||||
.setUploadURI(config.uploadURI)
|
||||
.setCoverURI(config.coverURI)
|
||||
.setMoveURI(config.moveURI)
|
||||
.setCreateURI(config.createURI)
|
||||
.setChunkThreshold(config.chunkThreshold)
|
||||
.start();
|
||||
}
|
||||
|
||||
statics.workboard.addBoard(config.projectPHID, JX.$(config.boardID));
|
||||
var board_phid = config.projectPHID;
|
||||
var board_node = JX.$(config.boardID);
|
||||
|
||||
var board = statics.workboard.newBoard(board_phid, board_node)
|
||||
.setOrder(config.order);
|
||||
|
||||
var templates = config.templateMap;
|
||||
for (var k in templates) {
|
||||
board.setCardTemplate(k, templates[k]);
|
||||
}
|
||||
|
||||
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 (var ii = 0; ii < column_map.length; ii++) {
|
||||
column.newCard(column_map[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
var order_maps = config.orderMaps;
|
||||
for (var object_phid in order_maps) {
|
||||
board.setOrderMap(object_phid, order_maps[object_phid]);
|
||||
}
|
||||
|
||||
board.start();
|
||||
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue