2014-01-13 21:23:57 +01:00
|
|
|
/**
|
|
|
|
* @provides javelin-behavior-project-boards
|
|
|
|
* @requires javelin-behavior
|
|
|
|
* javelin-dom
|
|
|
|
* javelin-util
|
2014-09-09 23:20:27 +02:00
|
|
|
* javelin-vector
|
2014-01-13 21:24:36 +01:00
|
|
|
* javelin-stratcom
|
|
|
|
* javelin-workflow
|
2014-01-13 21:23:57 +01:00
|
|
|
* phabricator-draggable-list
|
|
|
|
*/
|
|
|
|
|
2015-04-28 23:51:49 +02:00
|
|
|
JX.behavior('project-boards', function(config, statics) {
|
2014-01-13 21:23:57 +01:00
|
|
|
|
|
|
|
function finditems(col) {
|
|
|
|
return JX.DOM.scry(col, 'li', 'project-card');
|
|
|
|
}
|
|
|
|
|
2014-08-15 18:28:08 +02:00
|
|
|
function onupdate(col) {
|
|
|
|
var data = JX.Stratcom.getData(col);
|
|
|
|
var cards = finditems(col);
|
|
|
|
|
|
|
|
// Update the count of tasks in the column header.
|
|
|
|
if (!data.countTagNode) {
|
|
|
|
data.countTagNode = JX.$(data.countTagID);
|
|
|
|
JX.DOM.show(data.countTagNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
var sum = 0;
|
|
|
|
for (var ii = 0; ii < cards.length; ii++) {
|
|
|
|
// TODO: Allow this to be computed in some more clever way.
|
|
|
|
sum += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: This is a little bit hacky, but we don't have a PHUIX version of
|
|
|
|
// this element yet.
|
|
|
|
|
Allow columns to have a point limit
Summary:
Fixes T5885. This implements optional soft point limits for workboard columns, per traditional Kanban.
- Allow columns to have a point limit set.
- When a column has a point limit, show it in the header.
- If a column has too many points in it, show the column and point count in red.
@chad, this could probably use some design tweaks. In particular:
- I changed the color of "hidden" columns to avoid confusion with "overfull" columns. We might be able to find a better color.
- UI hints for overfull columns might need adjustment.
(After T4427, we'll let you sum some custom field instead of total number of tasks, which is why this is called "points" rather than "number of tasks".)
Test Plan:
{F190914}
Note that:
- "Pre-planning" has a limit, so it shows "4/12".
- "Planning" has a limit and is overfull, so it shows "5 / 4".
- Other columns do not have limits.
- "Post-planning" is a hidden column. This might be too muted now.
Transactions:
{F190915}
Error messages / edit screen:
{F190916}
Reviewers: btrahan, chad
Reviewed By: btrahan
Subscribers: chad, epriestley
Maniphest Tasks: T5885
Differential Revision: https://secure.phabricator.com/D10276
2014-08-15 20:16:08 +02:00
|
|
|
var over_limit = (data.pointLimit && (sum > data.pointLimit));
|
|
|
|
|
|
|
|
var display_value = sum;
|
|
|
|
if (data.pointLimit) {
|
|
|
|
display_value = sum + ' / ' + data.pointLimit;
|
|
|
|
}
|
|
|
|
JX.DOM.setContent(JX.$(data.countTagContentID), display_value);
|
|
|
|
|
|
|
|
|
|
|
|
var panel_map = {
|
|
|
|
'project-panel-empty': !cards.length,
|
|
|
|
'project-panel-over-limit': over_limit
|
|
|
|
};
|
|
|
|
var panel = JX.DOM.findAbove(col, 'div', 'workpanel');
|
2014-12-30 11:56:11 +01:00
|
|
|
for (var p in panel_map) {
|
|
|
|
JX.DOM.alterClass(panel, p, !!panel_map[p]);
|
Allow columns to have a point limit
Summary:
Fixes T5885. This implements optional soft point limits for workboard columns, per traditional Kanban.
- Allow columns to have a point limit set.
- When a column has a point limit, show it in the header.
- If a column has too many points in it, show the column and point count in red.
@chad, this could probably use some design tweaks. In particular:
- I changed the color of "hidden" columns to avoid confusion with "overfull" columns. We might be able to find a better color.
- UI hints for overfull columns might need adjustment.
(After T4427, we'll let you sum some custom field instead of total number of tasks, which is why this is called "points" rather than "number of tasks".)
Test Plan:
{F190914}
Note that:
- "Pre-planning" has a limit, so it shows "4/12".
- "Planning" has a limit and is overfull, so it shows "5 / 4".
- Other columns do not have limits.
- "Post-planning" is a hidden column. This might be too muted now.
Transactions:
{F190915}
Error messages / edit screen:
{F190916}
Reviewers: btrahan, chad
Reviewed By: btrahan
Subscribers: chad, epriestley
Maniphest Tasks: T5885
Differential Revision: https://secure.phabricator.com/D10276
2014-08-15 20:16:08 +02:00
|
|
|
}
|
|
|
|
|
2014-08-15 18:28:08 +02:00
|
|
|
var color_map = {
|
|
|
|
'phui-tag-shade-disabled': (sum === 0),
|
Allow columns to have a point limit
Summary:
Fixes T5885. This implements optional soft point limits for workboard columns, per traditional Kanban.
- Allow columns to have a point limit set.
- When a column has a point limit, show it in the header.
- If a column has too many points in it, show the column and point count in red.
@chad, this could probably use some design tweaks. In particular:
- I changed the color of "hidden" columns to avoid confusion with "overfull" columns. We might be able to find a better color.
- UI hints for overfull columns might need adjustment.
(After T4427, we'll let you sum some custom field instead of total number of tasks, which is why this is called "points" rather than "number of tasks".)
Test Plan:
{F190914}
Note that:
- "Pre-planning" has a limit, so it shows "4/12".
- "Planning" has a limit and is overfull, so it shows "5 / 4".
- Other columns do not have limits.
- "Post-planning" is a hidden column. This might be too muted now.
Transactions:
{F190915}
Error messages / edit screen:
{F190916}
Reviewers: btrahan, chad
Reviewed By: btrahan
Subscribers: chad, epriestley
Maniphest Tasks: T5885
Differential Revision: https://secure.phabricator.com/D10276
2014-08-15 20:16:08 +02:00
|
|
|
'phui-tag-shade-blue': (sum > 0 && !over_limit),
|
|
|
|
'phui-tag-shade-red': (over_limit)
|
2014-08-15 18:28:08 +02:00
|
|
|
};
|
2014-12-30 11:56:11 +01:00
|
|
|
for (var c in color_map) {
|
|
|
|
JX.DOM.alterClass(data.countTagNode, c, !!color_map[c]);
|
2014-08-15 18:28:08 +02:00
|
|
|
}
|
2014-01-13 21:24:13 +01:00
|
|
|
}
|
|
|
|
|
2014-03-05 02:01:33 +01:00
|
|
|
function onresponse(response, item, list) {
|
|
|
|
list.unlock();
|
|
|
|
JX.DOM.alterClass(item, 'drag-sending', false);
|
|
|
|
JX.DOM.replace(item, JX.$H(response.task));
|
2014-01-13 21:24:36 +01:00
|
|
|
}
|
|
|
|
|
2014-08-15 18:28:08 +02:00
|
|
|
function getcolumns() {
|
2015-04-28 23:51:49 +02:00
|
|
|
return JX.DOM.scry(JX.$(statics.boardID), 'ul', 'project-column');
|
2014-08-15 18:28:08 +02:00
|
|
|
}
|
|
|
|
|
2014-07-10 19:19:03 +02:00
|
|
|
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++) {
|
2014-07-30 22:09:09 +02:00
|
|
|
|
|
|
|
if (parseInt(ud[ii]) < parseInt(vd[ii])) {
|
2014-07-10 19:19:03 +02:00
|
|
|
return 1;
|
|
|
|
}
|
2014-07-30 22:09:09 +02:00
|
|
|
if (parseInt(ud[ii]) > parseInt(vd[ii])) {
|
2014-07-10 19:19:03 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-09-09 23:20:27 +02:00
|
|
|
function getcontainer() {
|
|
|
|
return JX.DOM.find(
|
2015-04-28 23:51:49 +02:00
|
|
|
JX.$(statics.boardID),
|
2014-09-09 23:20:27 +02:00
|
|
|
'div',
|
|
|
|
'aphront-multi-column-view');
|
|
|
|
}
|
|
|
|
|
|
|
|
function onbegindrag(item) {
|
|
|
|
// If the longest column on the board is taller than the window, the board
|
|
|
|
// will scroll vertically. Dragging an item to the longest column may
|
|
|
|
// make it longer, by the total height of the board, plus the height of
|
|
|
|
// the drop target.
|
|
|
|
|
|
|
|
// If this happens, the scrollbar will jump around and the scroll position
|
|
|
|
// can be adjusted in a disorienting way. To reproduce this, drag a task
|
|
|
|
// to the bottom of the longest column on a scrolling board and wave the
|
|
|
|
// task in and out of the column. The scroll bar will jump around and
|
|
|
|
// it will be hard to lock onto a target.
|
|
|
|
|
|
|
|
// To fix this, set the minimum board height to the current board height
|
|
|
|
// plus the size of the drop target (which is the size of the item plus
|
|
|
|
// a bit of margin). This makes sure the scroll bar never needs to
|
|
|
|
// recalculate.
|
|
|
|
|
|
|
|
var item_size = JX.Vector.getDim(item);
|
|
|
|
var container = getcontainer();
|
|
|
|
var container_size = JX.Vector.getDim(container);
|
|
|
|
|
|
|
|
container.style.minHeight = (item_size.y + container_size.y + 12) + 'px';
|
|
|
|
}
|
|
|
|
|
|
|
|
function onenddrag() {
|
|
|
|
getcontainer().style.minHeight = '';
|
|
|
|
}
|
|
|
|
|
2014-06-23 19:27:47 +02:00
|
|
|
function ondrop(list, item, after) {
|
2014-01-13 21:24:36 +01:00
|
|
|
list.lock();
|
|
|
|
JX.DOM.alterClass(item, 'drag-sending', true);
|
|
|
|
|
2014-03-27 18:50:54 +01:00
|
|
|
var item_phid = JX.Stratcom.getData(item).objectPHID;
|
2014-01-13 21:24:36 +01:00
|
|
|
var data = {
|
2014-03-27 18:50:54 +01:00
|
|
|
objectPHID: item_phid,
|
2014-03-04 00:58:00 +01:00
|
|
|
columnPHID: JX.Stratcom.getData(list.getRootNode()).columnPHID
|
2014-01-13 21:24:36 +01:00
|
|
|
};
|
|
|
|
|
2014-03-27 18:50:54 +01:00
|
|
|
var after_phid = null;
|
|
|
|
var items = finditems(list.getRootNode());
|
2014-03-04 00:58:00 +01:00
|
|
|
if (after) {
|
2014-03-27 18:50:54 +01:00
|
|
|
after_phid = JX.Stratcom.getData(after).objectPHID;
|
|
|
|
data.afterPHID = after_phid;
|
|
|
|
}
|
|
|
|
var ii;
|
|
|
|
var ii_item;
|
|
|
|
var ii_item_phid;
|
|
|
|
var ii_prev_item_phid = null;
|
|
|
|
var before_phid = null;
|
|
|
|
for (ii = 0; ii < items.length; ii++) {
|
|
|
|
ii_item = items[ii];
|
|
|
|
ii_item_phid = JX.Stratcom.getData(ii_item).objectPHID;
|
|
|
|
if (ii_item_phid == item_phid) {
|
|
|
|
// skip the item we just dropped
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// note this handles when there is no after phid - we are at the top of
|
|
|
|
// the list - quite nicely
|
|
|
|
if (ii_prev_item_phid == after_phid) {
|
|
|
|
before_phid = ii_item_phid;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ii_prev_item_phid = ii_item_phid;
|
|
|
|
}
|
|
|
|
if (before_phid) {
|
|
|
|
data.beforePHID = before_phid;
|
2014-03-04 00:58:00 +01:00
|
|
|
}
|
|
|
|
|
2015-04-28 23:51:49 +02:00
|
|
|
data.order = statics.order;
|
2014-08-08 17:10:29 +02:00
|
|
|
|
2015-04-28 23:51:49 +02:00
|
|
|
var workflow = new JX.Workflow(statics.moveURI, data)
|
2014-01-13 21:24:36 +01:00
|
|
|
.setHandler(function(response) {
|
2014-03-05 02:01:33 +01:00
|
|
|
onresponse(response, item, list);
|
2014-01-13 21:24:36 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
workflow.start();
|
|
|
|
}
|
|
|
|
|
2015-04-28 23:51:49 +02:00
|
|
|
function onedit(column, r) {
|
2014-07-10 19:19:03 +02:00
|
|
|
var new_card = JX.$H(r.tasks).getNode();
|
|
|
|
var new_data = JX.Stratcom.getData(new_card);
|
2014-03-06 03:40:28 +01:00
|
|
|
var items = finditems(column);
|
2014-07-30 22:09:09 +02:00
|
|
|
var edited = false;
|
2015-01-06 22:28:35 +01:00
|
|
|
var remove_index = null;
|
2014-07-10 19:19:03 +02:00
|
|
|
|
|
|
|
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) {
|
2015-01-06 22:28:35 +01:00
|
|
|
if (r.data.removeFromBoard) {
|
|
|
|
remove_index = ii;
|
|
|
|
}
|
2014-07-10 19:19:03 +02:00
|
|
|
items[ii] = new_card;
|
|
|
|
data = new_data;
|
2014-07-30 22:09:09 +02:00
|
|
|
edited = true;
|
2014-03-06 03:40:28 +01:00
|
|
|
}
|
2014-07-10 19:19:03 +02:00
|
|
|
|
|
|
|
data.sort = r.data.sortMap[data.objectPHID] || data.sort;
|
2014-03-06 03:40:28 +01:00
|
|
|
}
|
2014-07-10 19:19:03 +02:00
|
|
|
|
2014-07-30 22:09:09 +02:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2015-01-06 22:28:35 +01:00
|
|
|
if (remove_index !== null) {
|
|
|
|
items.splice(remove_index, 1);
|
|
|
|
}
|
|
|
|
|
2014-07-10 19:19:03 +02:00
|
|
|
items.sort(colsort);
|
|
|
|
|
|
|
|
JX.DOM.setContent(column, items);
|
2014-08-15 18:28:08 +02:00
|
|
|
|
|
|
|
onupdate(column);
|
2014-03-04 20:50:44 +01:00
|
|
|
};
|
|
|
|
|
2015-04-28 23:51:49 +02:00
|
|
|
function update_statics(update_config) {
|
|
|
|
statics.boardID = update_config.boardID;
|
|
|
|
statics.projectPHID = update_config.projectPHID;
|
|
|
|
statics.order = update_config.order;
|
|
|
|
statics.moveURI = update_config.moveURI;
|
|
|
|
statics.createURI = update_config.createURI;
|
|
|
|
}
|
|
|
|
|
|
|
|
function init_board() {
|
|
|
|
var lists = [];
|
|
|
|
var ii;
|
|
|
|
var cols = getcolumns();
|
|
|
|
|
|
|
|
for (ii = 0; ii < cols.length; ii++) {
|
|
|
|
var list = new JX.DraggableList('project-card', cols[ii])
|
|
|
|
.setFindItemsHandler(JX.bind(null, finditems, cols[ii]));
|
|
|
|
|
|
|
|
list.listen('didSend', JX.bind(list, onupdate, cols[ii]));
|
|
|
|
list.listen('didReceive', JX.bind(list, onupdate, cols[ii]));
|
|
|
|
|
|
|
|
list.listen('didDrop', JX.bind(null, ondrop, list));
|
|
|
|
|
|
|
|
list.listen('didBeginDrag', JX.bind(null, onbegindrag));
|
|
|
|
list.listen('didEndDrag', JX.bind(null, onenddrag));
|
|
|
|
|
|
|
|
lists.push(list);
|
|
|
|
|
|
|
|
onupdate(cols[ii]);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (ii = 0; ii < lists.length; ii++) {
|
|
|
|
lists[ii].setGroup(lists);
|
|
|
|
}
|
2014-03-04 20:50:44 +01:00
|
|
|
|
2015-04-28 23:51:49 +02:00
|
|
|
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_phid = e.getNodeData('column-add-task').columnPHID;
|
|
|
|
var request_data = {
|
|
|
|
responseType: 'card',
|
|
|
|
columnPHID: column_phid,
|
|
|
|
projects: statics.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;
|
|
|
|
}
|
2014-03-06 03:40:28 +01:00
|
|
|
}
|
2015-04-28 23:51:49 +02:00
|
|
|
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) {
|
|
|
|
return;
|
2014-03-06 03:40:28 +01:00
|
|
|
}
|
2015-04-28 23:51:49 +02:00
|
|
|
|
|
|
|
e.kill();
|
|
|
|
|
|
|
|
var list = JX.$H(data.items).getFragment().firstChild;
|
|
|
|
|
|
|
|
var button = e.getNode('boards-dropdown-menu');
|
|
|
|
data.menu = new JX.PHUIXDropdownMenu(button);
|
|
|
|
data.menu.setContent(list);
|
|
|
|
data.menu.open();
|
|
|
|
|
|
|
|
JX.DOM.listen(list, 'click', 'tag:a', function(e) {
|
|
|
|
if (!e.isNormalClick()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
data.menu.close();
|
|
|
|
});
|
2014-03-06 03:40:28 +01:00
|
|
|
});
|
2014-08-15 18:28:08 +02:00
|
|
|
|
2015-04-28 23:51:49 +02:00
|
|
|
JX.Stratcom.listen(
|
|
|
|
'quicksand-redraw',
|
|
|
|
null,
|
|
|
|
function (e) {
|
|
|
|
var data = e.getData();
|
|
|
|
if (!data.newResponse.boardConfig) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var new_config;
|
|
|
|
if (data.fromServer) {
|
|
|
|
new_config = data.newResponse.boardConfig;
|
|
|
|
statics.boardConfigCache[data.newResponseID] = new_config;
|
|
|
|
} else {
|
|
|
|
new_config = statics.boardConfigCache[data.newResponseID];
|
|
|
|
statics.boardID = new_config.boardID;
|
|
|
|
}
|
|
|
|
update_statics(new_config);
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!statics.setup) {
|
|
|
|
update_statics(config);
|
|
|
|
var current_page_id = JX.Quicksand.getCurrentPageID();
|
|
|
|
statics.boardConfigCache = {};
|
|
|
|
statics.boardConfigCache[current_page_id] = config;
|
|
|
|
statics.setup = init_board();
|
|
|
|
}
|
|
|
|
|
2014-01-13 21:23:57 +01:00
|
|
|
});
|