Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/webroot/rsrc/js/application/projects/WorkboardColumn.js
12242 views
1
/**
2
* @provides javelin-workboard-column
3
* @requires javelin-install
4
* javelin-workboard-card
5
* javelin-workboard-header
6
* @javelin
7
*/
8
9
JX.install('WorkboardColumn', {
10
11
construct: function(board, phid, root) {
12
this._board = board;
13
this._phid = phid;
14
this._root = root;
15
16
this._panel = JX.DOM.findAbove(root, 'div', 'workpanel');
17
this._pointsNode = JX.DOM.find(this._panel, 'span', 'column-points');
18
19
this._pointsContentNode = JX.DOM.find(
20
this._panel,
21
'span',
22
'column-points-content');
23
24
this._cards = {};
25
this._headers = {};
26
this._objects = [];
27
this._naturalOrder = [];
28
this._dropEffects = [];
29
},
30
31
properties: {
32
triggerPreviewEffect: null
33
},
34
35
members: {
36
_phid: null,
37
_root: null,
38
_board: null,
39
_cards: null,
40
_headers: null,
41
_naturalOrder: null,
42
_orderVectors: null,
43
_panel: null,
44
_pointsNode: null,
45
_pointsContentNode: null,
46
_dirty: true,
47
_objects: null,
48
_dropEffects: null,
49
50
getPHID: function() {
51
return this._phid;
52
},
53
54
getRoot: function() {
55
return this._root;
56
},
57
58
getCards: function() {
59
return this._cards;
60
},
61
62
_getObjects: function() {
63
return this._objects;
64
},
65
66
getCard: function(phid) {
67
return this._cards[phid];
68
},
69
70
getBoard: function() {
71
return this._board;
72
},
73
74
setNaturalOrder: function(order) {
75
this._naturalOrder = order;
76
this._orderVectors = null;
77
return this;
78
},
79
80
setDropEffects: function(effects) {
81
this._dropEffects = effects;
82
return this;
83
},
84
85
getDropEffects: function() {
86
return this._dropEffects;
87
},
88
89
getPointsNode: function() {
90
return this._pointsNode;
91
},
92
93
getPointsContentNode: function() {
94
return this._pointsContentNode;
95
},
96
97
getWorkpanelNode: function() {
98
return this._panel;
99
},
100
101
newCard: function(phid) {
102
var card = new JX.WorkboardCard(this, phid);
103
104
this._cards[phid] = card;
105
this._naturalOrder.push(phid);
106
this._orderVectors = null;
107
108
return card;
109
},
110
111
removeCard: function(phid) {
112
var card = this._cards[phid];
113
delete this._cards[phid];
114
115
for (var ii = 0; ii < this._naturalOrder.length; ii++) {
116
if (this._naturalOrder[ii] == phid) {
117
this._naturalOrder.splice(ii, 1);
118
this._orderVectors = null;
119
break;
120
}
121
}
122
123
return card;
124
},
125
126
addCard: function(card, after) {
127
var phid = card.getPHID();
128
129
card.setColumn(this);
130
this._cards[phid] = card;
131
132
var index = 0;
133
134
if (after) {
135
for (var ii = 0; ii < this._naturalOrder.length; ii++) {
136
if (this._naturalOrder[ii] == after) {
137
index = ii + 1;
138
break;
139
}
140
}
141
}
142
143
if (index > this._naturalOrder.length) {
144
this._naturalOrder.push(phid);
145
} else {
146
this._naturalOrder.splice(index, 0, phid);
147
}
148
149
this._orderVectors = null;
150
151
return this;
152
},
153
154
getDropTargetNodes: function() {
155
var objects = this._getObjects();
156
157
var nodes = [];
158
for (var ii = 0; ii < objects.length; ii++) {
159
var object = objects[ii];
160
nodes.push(object.getNode());
161
}
162
163
return nodes;
164
},
165
166
getCardPHIDs: function() {
167
return JX.keys(this.getCards());
168
},
169
170
getPointLimit: function() {
171
return JX.Stratcom.getData(this.getRoot()).pointLimit;
172
},
173
174
markForRedraw: function() {
175
this._dirty = true;
176
},
177
178
isMarkedForRedraw: function() {
179
return this._dirty;
180
},
181
182
getHeader: function(key) {
183
if (!this._headers[key]) {
184
this._headers[key] = new JX.WorkboardHeader(this, key);
185
}
186
return this._headers[key];
187
},
188
189
handleDragGhost: function(default_handler, ghost, node) {
190
// If the column has headers, don't let the user drag a card above
191
// the topmost header: for example, you can't change a task to have
192
// a priority higher than the highest possible priority.
193
194
if (this._hasColumnHeaders()) {
195
if (!node) {
196
return false;
197
}
198
}
199
200
return default_handler(ghost, node);
201
},
202
203
_hasColumnHeaders: function() {
204
var board = this.getBoard();
205
var order = board.getOrder();
206
207
return board.getOrderTemplate(order).getHasHeaders();
208
},
209
210
redraw: function() {
211
var board = this.getBoard();
212
var order = board.getOrder();
213
214
var list = this._getCardsSortedByKey(order);
215
216
var ii;
217
var objects = [];
218
219
var has_headers = this._hasColumnHeaders();
220
var header_keys = [];
221
var seen_headers = {};
222
if (has_headers) {
223
var header_templates = board.getHeaderTemplatesForOrder(order);
224
for (var k in header_templates) {
225
header_keys.push(header_templates[k].getHeaderKey());
226
}
227
header_keys.reverse();
228
}
229
230
var header_key;
231
var next;
232
for (ii = 0; ii < list.length; ii++) {
233
var card = list[ii];
234
235
// If a column has a "High" priority card and a "Low" priority card,
236
// we need to add the "Normal" header in between them. This allows
237
// you to change priority to "Normal" even if there are no "Normal"
238
// cards in a column.
239
240
if (has_headers) {
241
header_key = board.getCardTemplate(card.getPHID())
242
.getHeaderKey(order);
243
244
if (!seen_headers[header_key]) {
245
while (header_keys.length) {
246
next = header_keys.pop();
247
248
var header = this.getHeader(next);
249
objects.push(header);
250
seen_headers[header_key] = true;
251
252
if (next === header_key) {
253
break;
254
}
255
}
256
}
257
}
258
259
objects.push(card);
260
}
261
262
// Add any leftover headers at the bottom of the column which don't have
263
// any cards in them. In particular, empty columns don't have any cards
264
// but should still have headers.
265
266
while (header_keys.length) {
267
next = header_keys.pop();
268
269
if (seen_headers[next]) {
270
continue;
271
}
272
273
objects.push(this.getHeader(next));
274
}
275
276
this._objects = objects;
277
278
var content = [];
279
for (ii = 0; ii < this._objects.length; ii++) {
280
var object = this._objects[ii];
281
282
var node = object.getNode();
283
content.push(node);
284
}
285
286
JX.DOM.setContent(this.getRoot(), content);
287
288
this._redrawFrame();
289
290
this._dirty = false;
291
},
292
293
compareHandler: function(src_list, src_node, dst_list, dst_node) {
294
var board = this.getBoard();
295
var order = board.getOrder();
296
297
var u_vec = this._getNodeOrderVector(src_node, order);
298
var v_vec = this._getNodeOrderVector(dst_node, order);
299
300
return board.compareVectors(u_vec, v_vec);
301
},
302
303
_getNodeOrderVector: function(node, order) {
304
var board = this.getBoard();
305
var data = JX.Stratcom.getData(node);
306
307
if (data.objectPHID) {
308
return this._getOrderVector(data.objectPHID, order);
309
}
310
311
return board.getHeaderTemplate(data.headerKey).getVector();
312
},
313
314
setIsDropTarget: function(is_target) {
315
var node = this.getWorkpanelNode();
316
JX.DOM.alterClass(node, 'workboard-column-drop-target', is_target);
317
},
318
319
_getCardsSortedByKey: function(order) {
320
var cards = this.getCards();
321
322
var list = [];
323
for (var k in cards) {
324
list.push(cards[k]);
325
}
326
327
list.sort(JX.bind(this, this._sortCards, order));
328
329
return list;
330
},
331
332
_sortCards: function(order, u, v) {
333
var board = this.getBoard();
334
var u_vec = this._getOrderVector(u.getPHID(), order);
335
var v_vec = this._getOrderVector(v.getPHID(), order);
336
337
return board.compareVectors(u_vec, v_vec);
338
},
339
340
_getOrderVector: function(phid, order) {
341
var board = this.getBoard();
342
343
if (!this._orderVectors) {
344
this._orderVectors = {};
345
}
346
347
if (!this._orderVectors[order]) {
348
var cards = this.getCards();
349
var vectors = {};
350
351
for (var k in cards) {
352
var card_phid = cards[k].getPHID();
353
var vector = board.getCardTemplate(card_phid)
354
.getSortVector(order);
355
356
vectors[card_phid] = [].concat(vector);
357
358
// Push a "card" type, so cards always sort after headers; headers
359
// have a "0" in this position.
360
vectors[card_phid].push(1);
361
}
362
363
for (var ii = 0; ii < this._naturalOrder.length; ii++) {
364
var natural_phid = this._naturalOrder[ii];
365
if (vectors[natural_phid]) {
366
vectors[natural_phid].push(ii);
367
}
368
}
369
370
this._orderVectors[order] = vectors;
371
}
372
373
if (!this._orderVectors[order][phid]) {
374
// In this case, we're comparing a card being dragged in from another
375
// column to the cards already in this column. We're just going to
376
// build a temporary vector for it.
377
var incoming_vector = board.getCardTemplate(phid)
378
.getSortVector(order);
379
incoming_vector = [].concat(incoming_vector);
380
381
// Add a "card" type to sort this after headers.
382
incoming_vector.push(1);
383
384
// Add a "0" for the natural ordering to put this on top. A new card
385
// has no natural ordering on a column it isn't part of yet.
386
incoming_vector.push(0);
387
388
return incoming_vector;
389
}
390
391
return this._orderVectors[order][phid];
392
},
393
394
_redrawFrame: function() {
395
var cards = this.getCards();
396
var board = this.getBoard();
397
398
var points = {};
399
var count = 0;
400
var decimal_places = 0;
401
for (var phid in cards) {
402
var card = cards[phid];
403
404
var card_points;
405
if (board.getPointsEnabled()) {
406
card_points = card.getPoints();
407
} else {
408
card_points = 1;
409
}
410
411
if (card_points !== null) {
412
var status = card.getStatus();
413
if (!points[status]) {
414
points[status] = 0;
415
}
416
points[status] += card_points;
417
418
// Count the number of decimal places in the point value with the
419
// most decimal digits. We'll use the same precision when rendering
420
// the point sum. This avoids rounding errors and makes the display
421
// a little more consistent.
422
var parts = card_points.toString().split('.');
423
if (parts[1]) {
424
decimal_places = Math.max(decimal_places, parts[1].length);
425
}
426
}
427
428
count++;
429
}
430
431
var total_points = 0;
432
for (var k in points) {
433
total_points += points[k];
434
}
435
total_points = total_points.toFixed(decimal_places);
436
437
var limit = this.getPointLimit();
438
439
var display_value;
440
if (limit !== null && limit !== 0) {
441
display_value = total_points + ' / ' + limit;
442
} else {
443
display_value = total_points;
444
}
445
446
if (board.getPointsEnabled()) {
447
display_value = count + ' | ' + display_value;
448
}
449
450
var over_limit = ((limit !== null) && (total_points > limit));
451
452
var content_node = this.getPointsContentNode();
453
var points_node = this.getPointsNode();
454
455
JX.DOM.setContent(content_node, display_value);
456
457
// Only put the "empty" style on the column (which just adds some empty
458
// space so it's easier to drop cards into an empty column) if it has no
459
// cards and no headers.
460
461
var is_empty =
462
(!this.getCardPHIDs().length) &&
463
(!this._hasColumnHeaders());
464
465
var panel = JX.DOM.findAbove(this.getRoot(), 'div', 'workpanel');
466
JX.DOM.alterClass(panel, 'project-panel-empty', is_empty);
467
468
469
JX.DOM.alterClass(panel, 'project-panel-over-limit', over_limit);
470
471
var color_map = {
472
'phui-tag-disabled': (total_points === 0),
473
'phui-tag-blue': (total_points > 0 && !over_limit),
474
'phui-tag-red': (over_limit)
475
};
476
477
for (var c in color_map) {
478
JX.DOM.alterClass(points_node, c, !!color_map[c]);
479
}
480
481
JX.DOM.show(points_node);
482
}
483
484
}
485
486
});
487
488