Skip to content

Commit b7a9705

Browse files
author
epriestley
committed
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
1 parent 2101c3b commit b7a9705

File tree

6 files changed

+247
-7
lines changed

6 files changed

+247
-7
lines changed

resources/celerity/map.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@
413413
'rsrc/js/application/projects/behavior-boards-dropdown.js' => '0ec56e1d',
414414
'rsrc/js/application/projects/behavior-project-boards.js' => 'c6b95cbd',
415415
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
416+
'rsrc/js/application/projects/behavior-reorder-columns.js' => '09eee344',
416417
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
417418
'rsrc/js/application/releeph/releeph-request-state-change.js' => 'ab836011',
418419
'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f',
@@ -645,6 +646,7 @@
645646
'javelin-behavior-releeph-request-typeahead' => 'de2e896f',
646647
'javelin-behavior-remarkup-preview' => 'f7379f45',
647648
'javelin-behavior-reorder-applications' => '76b9fc3e',
649+
'javelin-behavior-reorder-columns' => '09eee344',
648650
'javelin-behavior-repository-crossreference' => 'f9539603',
649651
'javelin-behavior-search-reorder-queries' => 'e9581f08',
650652
'javelin-behavior-select-on-click' => '4e3e79a6',
@@ -872,6 +874,14 @@
872874
4 => 'javelin-util',
873875
5 => 'phabricator-busy',
874876
),
877+
'09eee344' =>
878+
array(
879+
0 => 'javelin-behavior',
880+
1 => 'javelin-stratcom',
881+
2 => 'javelin-workflow',
882+
3 => 'javelin-dom',
883+
4 => 'phabricator-draggable-list',
884+
),
875885
'0a3f3021' =>
876886
array(
877887
0 => 'javelin-behavior',

src/__phutil_library_map__.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,6 +1971,7 @@
19711971
'PhabricatorProjectBoardController' => 'applications/project/controller/PhabricatorProjectBoardController.php',
19721972
'PhabricatorProjectBoardDeleteController' => 'applications/project/controller/PhabricatorProjectBoardDeleteController.php',
19731973
'PhabricatorProjectBoardEditController' => 'applications/project/controller/PhabricatorProjectBoardEditController.php',
1974+
'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php',
19741975
'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php',
19751976
'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php',
19761977
'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php',
@@ -4842,6 +4843,7 @@
48424843
'PhabricatorProjectBoardController' => 'PhabricatorProjectController',
48434844
'PhabricatorProjectBoardDeleteController' => 'PhabricatorProjectBoardController',
48444845
'PhabricatorProjectBoardEditController' => 'PhabricatorProjectBoardController',
4846+
'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController',
48454847
'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController',
48464848
'PhabricatorProjectColumn' =>
48474849
array(

src/applications/project/application/PhabricatorApplicationProject.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,16 @@ public function getRoutes() {
6464
'(?:query/(?P<queryKey>[^/]+)/)?' =>
6565
'PhabricatorProjectBoardViewController',
6666
'move/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectMoveController',
67-
'board/(?P<projectID>[1-9]\d*)/edit/(?:(?P<id>\d+)/)?'
68-
=> 'PhabricatorProjectBoardEditController',
69-
'board/(?P<projectID>[1-9]\d*)/delete/(?:(?P<id>\d+)/)?'
70-
=> 'PhabricatorProjectBoardDeleteController',
71-
'board/(?P<projectID>[1-9]\d*)/column/(?:(?P<id>\d+)/)?'
72-
=> 'PhabricatorProjectColumnDetailController',
67+
'board/(?P<projectID>[1-9]\d*)/' => array(
68+
'edit/(?:(?P<id>\d+)/)?'
69+
=> 'PhabricatorProjectBoardEditController',
70+
'delete/(?:(?P<id>\d+)/)?'
71+
=> 'PhabricatorProjectBoardDeleteController',
72+
'column/(?:(?P<id>\d+)/)?'
73+
=> 'PhabricatorProjectColumnDetailController',
74+
'reorder/'
75+
=> 'PhabricatorProjectBoardReorderController',
76+
),
7377
'update/(?P<id>[1-9]\d*)/(?P<action>[^/]+)/'
7478
=> 'PhabricatorProjectUpdateController',
7579
'history/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectHistoryController',
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
<?php
2+
3+
final class PhabricatorProjectBoardReorderController
4+
extends PhabricatorProjectBoardController {
5+
6+
private $projectID;
7+
8+
public function willProcessRequest(array $data) {
9+
$this->projectID = $data['projectID'];
10+
}
11+
12+
public function processRequest() {
13+
$request = $this->getRequest();
14+
$viewer = $request->getUser();
15+
16+
$project = id(new PhabricatorProjectQuery())
17+
->setViewer($viewer)
18+
->requireCapabilities(
19+
array(
20+
PhabricatorPolicyCapability::CAN_VIEW,
21+
PhabricatorPolicyCapability::CAN_EDIT,
22+
))
23+
->withIDs(array($this->projectID))
24+
->executeOne();
25+
if (!$project) {
26+
return new Aphront404Response();
27+
}
28+
29+
$this->setProject($project);
30+
31+
32+
$project_id = $project->getID();
33+
34+
$board_uri = $this->getApplicationURI("board/{$project_id}/");
35+
$reorder_uri = $this->getApplicationURI("board/{$project_id}/reorder/");
36+
37+
if ($request->isFormPost()) {
38+
// User clicked "Done", make sure the page reloads to show the new
39+
// column order.
40+
return id(new AphrontRedirectResponse())->setURI($board_uri);
41+
}
42+
43+
$columns = id(new PhabricatorProjectColumnQuery())
44+
->setViewer($viewer)
45+
->withProjectPHIDs(array($project->getPHID()))
46+
->execute();
47+
$columns = msort($columns, 'getSequence');
48+
49+
$column_phid = $request->getStr('columnPHID');
50+
if ($column_phid && $request->validateCSRF()) {
51+
52+
$columns = mpull($columns, null, 'getPHID');
53+
if (empty($columns[$column_phid])) {
54+
return new Aphront404Response();
55+
}
56+
57+
// TODO: We could let you move the backlog column around if you really
58+
// want, but for now we use sequence position 0 as magic.
59+
$target_column = $columns[$column_phid];
60+
$new_sequence = $request->getInt('sequence');
61+
if ($target_column->isDefaultColumn() || $new_sequence < 1) {
62+
return new Aphront404Response();
63+
}
64+
65+
// TODO: For now, we're not recording any transactions here. We probably
66+
// should, but this sort of edit is extremely trivial.
67+
68+
// Resequence the columns so that the moved column has the correct
69+
// sequence number. Move columns after it up one place in the sequence.
70+
$new_map = array();
71+
foreach ($columns as $phid => $column) {
72+
$value = $column->getSequence();
73+
if ($column->getPHID() == $column_phid) {
74+
$value = $new_sequence;
75+
} else if ($column->getSequence() >= $new_sequence) {
76+
$value = $value + 1;
77+
}
78+
$new_map[$phid] = $value;
79+
}
80+
81+
// Sort the columns into their new ordering.
82+
asort($new_map);
83+
84+
// Now, compact the ordering and adjust any columns that need changes.
85+
$project->openTransaction();
86+
$sequence = 0;
87+
foreach ($new_map as $phid => $ignored) {
88+
$new_value = $sequence++;
89+
$cur_value = $columns[$phid]->getSequence();
90+
if ($new_value != $cur_value) {
91+
$columns[$phid]->setSequence($new_value)->save();
92+
}
93+
}
94+
$project->saveTransaction();
95+
96+
return id(new AphrontAjaxResponse())->setContent(
97+
array(
98+
'sequenceMap' => mpull($columns, 'getSequence', 'getPHID'),
99+
));
100+
}
101+
102+
$list_id = celerity_generate_unique_node_id();
103+
104+
$static_list = id(new PHUIObjectItemListView())
105+
->setUser($viewer)
106+
->setFlush(true)
107+
->setStackable(true);
108+
109+
$list = id(new PHUIObjectItemListView())
110+
->setUser($viewer)
111+
->setID($list_id)
112+
->setFlush(true)
113+
->setStackable(true);
114+
115+
foreach ($columns as $column) {
116+
$item = id(new PHUIObjectItemView())
117+
->setHeader($column->getDisplayName());
118+
119+
if ($column->isHidden()) {
120+
$item->setDisabled(true);
121+
}
122+
123+
if ($column->isDefaultColumn()) {
124+
$item->setDisabled(true);
125+
$static_list->addItem($item);
126+
} else {
127+
$item->setGrippable(true);
128+
$item->addSigil('board-column');
129+
$item->setMetadata(
130+
array(
131+
'columnPHID' => $column->getPHID(),
132+
'columnSequence' => $column->getSequence(),
133+
));
134+
135+
$list->addItem($item);
136+
}
137+
138+
}
139+
140+
Javelin::initBehavior(
141+
'reorder-columns',
142+
array(
143+
'listID' => $list_id,
144+
'reorderURI' => $reorder_uri,
145+
));
146+
147+
return $this->newDialog()
148+
->setTitle(pht('Reorder Columns'))
149+
->setWidth(AphrontDialogView::WIDTH_FORM)
150+
->appendParagraph(pht('This column can not be moved:'))
151+
->appendChild($static_list)
152+
->appendParagraph(pht('Drag and drop these columns to reorder them:'))
153+
->appendChild($list)
154+
->addSubmitButton(pht('Done'));
155+
}
156+
157+
}

src/applications/project/controller/PhabricatorProjectBoardViewController.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,16 @@ private function buildManageMenu(
357357
$manage_items[] = id(new PhabricatorActionView())
358358
->setIcon('fa-plus')
359359
->setName(pht('Add Column'))
360-
->setHref($this->getApplicationURI('board/'.$this->id.'/edit/'));
360+
->setHref($this->getApplicationURI('board/'.$this->id.'/edit/'))
361+
->setDisabled(!$can_edit)
362+
->setWorkflow(!$can_edit);
363+
364+
$manage_items[] = id(new PhabricatorActionView())
365+
->setIcon('fa-exchange')
366+
->setName(pht('Reorder Columns'))
367+
->setHref($this->getApplicationURI('board/'.$this->id.'/reorder/'))
368+
->setDisabled(!$can_edit)
369+
->setWorkflow(true);
361370

362371
if ($show_hidden) {
363372
$hidden_uri = $request->getRequestURI()
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* @provides javelin-behavior-reorder-columns
3+
* @requires javelin-behavior
4+
* javelin-stratcom
5+
* javelin-workflow
6+
* javelin-dom
7+
* phabricator-draggable-list
8+
*/
9+
10+
JX.behavior('reorder-columns', function(config) {
11+
12+
var root = JX.$(config.listID);
13+
14+
var list = new JX.DraggableList('board-column', root)
15+
.setFindItemsHandler(function() {
16+
return JX.DOM.scry(root, 'li', 'board-column');
17+
});
18+
19+
list.listen('didDrop', function(node) {
20+
var nodes = list.findItems();
21+
22+
var node_data = JX.Stratcom.getData(node);
23+
24+
// Find the column sequence of the previous node.
25+
var sequence = null;
26+
var data;
27+
for (var ii = 0; ii < nodes.length; ii++) {
28+
data = JX.Stratcom.getData(nodes[ii]);
29+
if (data.columnPHID === node_data.columnPHID) {
30+
break;
31+
}
32+
sequence = data.columnSequence;
33+
}
34+
35+
list.lock();
36+
JX.DOM.alterClass(node, 'drag-sending', true);
37+
38+
var parameters = {
39+
columnPHID: node_data.columnPHID,
40+
sequence: (sequence === null) ? 1 : (parseInt(sequence, 10) + 1)
41+
};
42+
43+
new JX.Workflow(config.reorderURI, parameters)
44+
.setHandler(function(r) {
45+
46+
// Adjust metadata for the new sequence numbers.
47+
for (var ii = 0; ii < nodes.length; ii++) {
48+
var data = JX.Stratcom.getData(nodes[ii]);
49+
data.columnSequence = r.sequenceMap[data.columnPHID];
50+
}
51+
52+
list.unlock();
53+
JX.DOM.alterClass(node, 'drag-sending', false);
54+
})
55+
.start();
56+
});
57+
58+
});

0 commit comments

Comments
 (0)