mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-20 20:40:56 +01:00
Allow board columns to be reordered
Summary: Fixes T4567. This isn't going to win design awards and we have some leaky CSS, but it works fine. Test Plan: {F176743} Reviewers: btrahan, chad Reviewed By: chad Subscribers: epriestley Maniphest Tasks: T4567 Differential Revision: https://secure.phabricator.com/D9905
This commit is contained in:
parent
2101c3b689
commit
b7a970598d
6 changed files with 247 additions and 7 deletions
|
@ -413,6 +413,7 @@ return array(
|
||||||
'rsrc/js/application/projects/behavior-boards-dropdown.js' => '0ec56e1d',
|
'rsrc/js/application/projects/behavior-boards-dropdown.js' => '0ec56e1d',
|
||||||
'rsrc/js/application/projects/behavior-project-boards.js' => 'c6b95cbd',
|
'rsrc/js/application/projects/behavior-project-boards.js' => 'c6b95cbd',
|
||||||
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
|
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
|
||||||
|
'rsrc/js/application/projects/behavior-reorder-columns.js' => '09eee344',
|
||||||
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
|
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
|
||||||
'rsrc/js/application/releeph/releeph-request-state-change.js' => 'ab836011',
|
'rsrc/js/application/releeph/releeph-request-state-change.js' => 'ab836011',
|
||||||
'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f',
|
'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f',
|
||||||
|
@ -645,6 +646,7 @@ return array(
|
||||||
'javelin-behavior-releeph-request-typeahead' => 'de2e896f',
|
'javelin-behavior-releeph-request-typeahead' => 'de2e896f',
|
||||||
'javelin-behavior-remarkup-preview' => 'f7379f45',
|
'javelin-behavior-remarkup-preview' => 'f7379f45',
|
||||||
'javelin-behavior-reorder-applications' => '76b9fc3e',
|
'javelin-behavior-reorder-applications' => '76b9fc3e',
|
||||||
|
'javelin-behavior-reorder-columns' => '09eee344',
|
||||||
'javelin-behavior-repository-crossreference' => 'f9539603',
|
'javelin-behavior-repository-crossreference' => 'f9539603',
|
||||||
'javelin-behavior-search-reorder-queries' => 'e9581f08',
|
'javelin-behavior-search-reorder-queries' => 'e9581f08',
|
||||||
'javelin-behavior-select-on-click' => '4e3e79a6',
|
'javelin-behavior-select-on-click' => '4e3e79a6',
|
||||||
|
@ -872,6 +874,14 @@ return array(
|
||||||
4 => 'javelin-util',
|
4 => 'javelin-util',
|
||||||
5 => 'phabricator-busy',
|
5 => 'phabricator-busy',
|
||||||
),
|
),
|
||||||
|
'09eee344' =>
|
||||||
|
array(
|
||||||
|
0 => 'javelin-behavior',
|
||||||
|
1 => 'javelin-stratcom',
|
||||||
|
2 => 'javelin-workflow',
|
||||||
|
3 => 'javelin-dom',
|
||||||
|
4 => 'phabricator-draggable-list',
|
||||||
|
),
|
||||||
'0a3f3021' =>
|
'0a3f3021' =>
|
||||||
array(
|
array(
|
||||||
0 => 'javelin-behavior',
|
0 => 'javelin-behavior',
|
||||||
|
|
|
@ -1971,6 +1971,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectBoardController' => 'applications/project/controller/PhabricatorProjectBoardController.php',
|
'PhabricatorProjectBoardController' => 'applications/project/controller/PhabricatorProjectBoardController.php',
|
||||||
'PhabricatorProjectBoardDeleteController' => 'applications/project/controller/PhabricatorProjectBoardDeleteController.php',
|
'PhabricatorProjectBoardDeleteController' => 'applications/project/controller/PhabricatorProjectBoardDeleteController.php',
|
||||||
'PhabricatorProjectBoardEditController' => 'applications/project/controller/PhabricatorProjectBoardEditController.php',
|
'PhabricatorProjectBoardEditController' => 'applications/project/controller/PhabricatorProjectBoardEditController.php',
|
||||||
|
'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php',
|
||||||
'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php',
|
'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php',
|
||||||
'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php',
|
'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php',
|
||||||
'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php',
|
'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php',
|
||||||
|
@ -4842,6 +4843,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectBoardController' => 'PhabricatorProjectController',
|
'PhabricatorProjectBoardController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorProjectBoardDeleteController' => 'PhabricatorProjectBoardController',
|
'PhabricatorProjectBoardDeleteController' => 'PhabricatorProjectBoardController',
|
||||||
'PhabricatorProjectBoardEditController' => 'PhabricatorProjectBoardController',
|
'PhabricatorProjectBoardEditController' => 'PhabricatorProjectBoardController',
|
||||||
|
'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController',
|
||||||
'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController',
|
'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController',
|
||||||
'PhabricatorProjectColumn' =>
|
'PhabricatorProjectColumn' =>
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -64,12 +64,16 @@ final class PhabricatorApplicationProject extends PhabricatorApplication {
|
||||||
'(?:query/(?P<queryKey>[^/]+)/)?' =>
|
'(?:query/(?P<queryKey>[^/]+)/)?' =>
|
||||||
'PhabricatorProjectBoardViewController',
|
'PhabricatorProjectBoardViewController',
|
||||||
'move/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectMoveController',
|
'move/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectMoveController',
|
||||||
'board/(?P<projectID>[1-9]\d*)/edit/(?:(?P<id>\d+)/)?'
|
'board/(?P<projectID>[1-9]\d*)/' => array(
|
||||||
|
'edit/(?:(?P<id>\d+)/)?'
|
||||||
=> 'PhabricatorProjectBoardEditController',
|
=> 'PhabricatorProjectBoardEditController',
|
||||||
'board/(?P<projectID>[1-9]\d*)/delete/(?:(?P<id>\d+)/)?'
|
'delete/(?:(?P<id>\d+)/)?'
|
||||||
=> 'PhabricatorProjectBoardDeleteController',
|
=> 'PhabricatorProjectBoardDeleteController',
|
||||||
'board/(?P<projectID>[1-9]\d*)/column/(?:(?P<id>\d+)/)?'
|
'column/(?:(?P<id>\d+)/)?'
|
||||||
=> 'PhabricatorProjectColumnDetailController',
|
=> 'PhabricatorProjectColumnDetailController',
|
||||||
|
'reorder/'
|
||||||
|
=> 'PhabricatorProjectBoardReorderController',
|
||||||
|
),
|
||||||
'update/(?P<id>[1-9]\d*)/(?P<action>[^/]+)/'
|
'update/(?P<id>[1-9]\d*)/(?P<action>[^/]+)/'
|
||||||
=> 'PhabricatorProjectUpdateController',
|
=> 'PhabricatorProjectUpdateController',
|
||||||
'history/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectHistoryController',
|
'history/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectHistoryController',
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorProjectBoardReorderController
|
||||||
|
extends PhabricatorProjectBoardController {
|
||||||
|
|
||||||
|
private $projectID;
|
||||||
|
|
||||||
|
public function willProcessRequest(array $data) {
|
||||||
|
$this->projectID = $data['projectID'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processRequest() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$viewer = $request->getUser();
|
||||||
|
|
||||||
|
$project = id(new PhabricatorProjectQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->withIDs(array($this->projectID))
|
||||||
|
->executeOne();
|
||||||
|
if (!$project) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setProject($project);
|
||||||
|
|
||||||
|
|
||||||
|
$project_id = $project->getID();
|
||||||
|
|
||||||
|
$board_uri = $this->getApplicationURI("board/{$project_id}/");
|
||||||
|
$reorder_uri = $this->getApplicationURI("board/{$project_id}/reorder/");
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
// User clicked "Done", make sure the page reloads to show the new
|
||||||
|
// column order.
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($board_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$columns = id(new PhabricatorProjectColumnQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withProjectPHIDs(array($project->getPHID()))
|
||||||
|
->execute();
|
||||||
|
$columns = msort($columns, 'getSequence');
|
||||||
|
|
||||||
|
$column_phid = $request->getStr('columnPHID');
|
||||||
|
if ($column_phid && $request->validateCSRF()) {
|
||||||
|
|
||||||
|
$columns = mpull($columns, null, 'getPHID');
|
||||||
|
if (empty($columns[$column_phid])) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: We could let you move the backlog column around if you really
|
||||||
|
// want, but for now we use sequence position 0 as magic.
|
||||||
|
$target_column = $columns[$column_phid];
|
||||||
|
$new_sequence = $request->getInt('sequence');
|
||||||
|
if ($target_column->isDefaultColumn() || $new_sequence < 1) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: For now, we're not recording any transactions here. We probably
|
||||||
|
// should, but this sort of edit is extremely trivial.
|
||||||
|
|
||||||
|
// Resequence the columns so that the moved column has the correct
|
||||||
|
// sequence number. Move columns after it up one place in the sequence.
|
||||||
|
$new_map = array();
|
||||||
|
foreach ($columns as $phid => $column) {
|
||||||
|
$value = $column->getSequence();
|
||||||
|
if ($column->getPHID() == $column_phid) {
|
||||||
|
$value = $new_sequence;
|
||||||
|
} else if ($column->getSequence() >= $new_sequence) {
|
||||||
|
$value = $value + 1;
|
||||||
|
}
|
||||||
|
$new_map[$phid] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the columns into their new ordering.
|
||||||
|
asort($new_map);
|
||||||
|
|
||||||
|
// Now, compact the ordering and adjust any columns that need changes.
|
||||||
|
$project->openTransaction();
|
||||||
|
$sequence = 0;
|
||||||
|
foreach ($new_map as $phid => $ignored) {
|
||||||
|
$new_value = $sequence++;
|
||||||
|
$cur_value = $columns[$phid]->getSequence();
|
||||||
|
if ($new_value != $cur_value) {
|
||||||
|
$columns[$phid]->setSequence($new_value)->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$project->saveTransaction();
|
||||||
|
|
||||||
|
return id(new AphrontAjaxResponse())->setContent(
|
||||||
|
array(
|
||||||
|
'sequenceMap' => mpull($columns, 'getSequence', 'getPHID'),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$list_id = celerity_generate_unique_node_id();
|
||||||
|
|
||||||
|
$static_list = id(new PHUIObjectItemListView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->setFlush(true)
|
||||||
|
->setStackable(true);
|
||||||
|
|
||||||
|
$list = id(new PHUIObjectItemListView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->setID($list_id)
|
||||||
|
->setFlush(true)
|
||||||
|
->setStackable(true);
|
||||||
|
|
||||||
|
foreach ($columns as $column) {
|
||||||
|
$item = id(new PHUIObjectItemView())
|
||||||
|
->setHeader($column->getDisplayName());
|
||||||
|
|
||||||
|
if ($column->isHidden()) {
|
||||||
|
$item->setDisabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($column->isDefaultColumn()) {
|
||||||
|
$item->setDisabled(true);
|
||||||
|
$static_list->addItem($item);
|
||||||
|
} else {
|
||||||
|
$item->setGrippable(true);
|
||||||
|
$item->addSigil('board-column');
|
||||||
|
$item->setMetadata(
|
||||||
|
array(
|
||||||
|
'columnPHID' => $column->getPHID(),
|
||||||
|
'columnSequence' => $column->getSequence(),
|
||||||
|
));
|
||||||
|
|
||||||
|
$list->addItem($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Javelin::initBehavior(
|
||||||
|
'reorder-columns',
|
||||||
|
array(
|
||||||
|
'listID' => $list_id,
|
||||||
|
'reorderURI' => $reorder_uri,
|
||||||
|
));
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Reorder Columns'))
|
||||||
|
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||||
|
->appendParagraph(pht('This column can not be moved:'))
|
||||||
|
->appendChild($static_list)
|
||||||
|
->appendParagraph(pht('Drag and drop these columns to reorder them:'))
|
||||||
|
->appendChild($list)
|
||||||
|
->addSubmitButton(pht('Done'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -357,7 +357,16 @@ final class PhabricatorProjectBoardViewController
|
||||||
$manage_items[] = id(new PhabricatorActionView())
|
$manage_items[] = id(new PhabricatorActionView())
|
||||||
->setIcon('fa-plus')
|
->setIcon('fa-plus')
|
||||||
->setName(pht('Add Column'))
|
->setName(pht('Add Column'))
|
||||||
->setHref($this->getApplicationURI('board/'.$this->id.'/edit/'));
|
->setHref($this->getApplicationURI('board/'.$this->id.'/edit/'))
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setWorkflow(!$can_edit);
|
||||||
|
|
||||||
|
$manage_items[] = id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-exchange')
|
||||||
|
->setName(pht('Reorder Columns'))
|
||||||
|
->setHref($this->getApplicationURI('board/'.$this->id.'/reorder/'))
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setWorkflow(true);
|
||||||
|
|
||||||
if ($show_hidden) {
|
if ($show_hidden) {
|
||||||
$hidden_uri = $request->getRequestURI()
|
$hidden_uri = $request->getRequestURI()
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* @provides javelin-behavior-reorder-columns
|
||||||
|
* @requires javelin-behavior
|
||||||
|
* javelin-stratcom
|
||||||
|
* javelin-workflow
|
||||||
|
* javelin-dom
|
||||||
|
* phabricator-draggable-list
|
||||||
|
*/
|
||||||
|
|
||||||
|
JX.behavior('reorder-columns', function(config) {
|
||||||
|
|
||||||
|
var root = JX.$(config.listID);
|
||||||
|
|
||||||
|
var list = new JX.DraggableList('board-column', root)
|
||||||
|
.setFindItemsHandler(function() {
|
||||||
|
return JX.DOM.scry(root, 'li', 'board-column');
|
||||||
|
});
|
||||||
|
|
||||||
|
list.listen('didDrop', function(node) {
|
||||||
|
var nodes = list.findItems();
|
||||||
|
|
||||||
|
var node_data = JX.Stratcom.getData(node);
|
||||||
|
|
||||||
|
// Find the column sequence of the previous node.
|
||||||
|
var sequence = null;
|
||||||
|
var data;
|
||||||
|
for (var ii = 0; ii < nodes.length; ii++) {
|
||||||
|
data = JX.Stratcom.getData(nodes[ii]);
|
||||||
|
if (data.columnPHID === node_data.columnPHID) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sequence = data.columnSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.lock();
|
||||||
|
JX.DOM.alterClass(node, 'drag-sending', true);
|
||||||
|
|
||||||
|
var parameters = {
|
||||||
|
columnPHID: node_data.columnPHID,
|
||||||
|
sequence: (sequence === null) ? 1 : (parseInt(sequence, 10) + 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
new JX.Workflow(config.reorderURI, parameters)
|
||||||
|
.setHandler(function(r) {
|
||||||
|
|
||||||
|
// Adjust metadata for the new sequence numbers.
|
||||||
|
for (var ii = 0; ii < nodes.length; ii++) {
|
||||||
|
var data = JX.Stratcom.getData(nodes[ii]);
|
||||||
|
data.columnSequence = r.sequenceMap[data.columnPHID];
|
||||||
|
}
|
||||||
|
|
||||||
|
list.unlock();
|
||||||
|
JX.DOM.alterClass(node, 'drag-sending', false);
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in a new issue