Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/webroot/rsrc/js/application/files/behavior-document-engine.js
12241 views
1
/**
2
* @provides javelin-behavior-document-engine
3
* @requires javelin-behavior
4
* javelin-dom
5
* javelin-stratcom
6
*/
7
8
JX.behavior('document-engine', function(config, statics) {
9
10
function onmenu(e) {
11
var node = e.getNode('document-engine-view-dropdown');
12
var data = JX.Stratcom.getData(node);
13
14
if (data.menu) {
15
return;
16
}
17
18
e.prevent();
19
20
var menu = new JX.PHUIXDropdownMenu(node);
21
var list = new JX.PHUIXActionListView();
22
23
var view;
24
var engines = [];
25
for (var ii = 0; ii < data.views.length; ii++) {
26
var spec = data.views[ii];
27
28
view = new JX.PHUIXActionView()
29
.setName(spec.name)
30
.setIcon(spec.icon)
31
.setIconColor(spec.color)
32
.setHref(spec.engineURI);
33
34
view.setHandler(JX.bind(null, function(spec, e) {
35
if (!e.isNormalClick()) {
36
return;
37
}
38
39
e.prevent();
40
menu.close();
41
42
onview(data, spec, false);
43
}, spec));
44
45
list.addItem(view);
46
47
engines.push({
48
spec: spec,
49
view: view
50
});
51
}
52
53
list.addItem(
54
new JX.PHUIXActionView()
55
.setDivider(true));
56
57
var encode_item = new JX.PHUIXActionView()
58
.setName(data.encode.name)
59
.setIcon(data.encode.icon);
60
61
var onencode = JX.bind(null, function(data, e) {
62
e.prevent();
63
64
if (encode_item.getDisabled()) {
65
return;
66
}
67
68
new JX.Workflow(data.encode.uri, {encoding: data.encode.value})
69
.setHandler(function(r) {
70
data.encode.value = r.encoding;
71
onview(data);
72
})
73
.start();
74
75
menu.close();
76
77
}, data);
78
79
encode_item.setHandler(onencode);
80
81
list.addItem(encode_item);
82
83
var highlight_item = new JX.PHUIXActionView()
84
.setName(data.highlight.name)
85
.setIcon(data.highlight.icon);
86
87
var onhighlight = JX.bind(null, function(data, e) {
88
e.prevent();
89
90
if (highlight_item.getDisabled()) {
91
return;
92
}
93
94
new JX.Workflow(data.highlight.uri, {highlight: data.highlight.value})
95
.setHandler(function(r) {
96
data.highlight.value = r.highlight;
97
onview(data);
98
})
99
.start();
100
101
menu.close();
102
}, data);
103
104
highlight_item.setHandler(onhighlight);
105
106
list.addItem(highlight_item);
107
108
var blame_item;
109
if (data.blame.uri) {
110
blame_item = new JX.PHUIXActionView()
111
.setIcon(data.blame.icon);
112
113
var onblame = JX.bind(null, function(data, e) {
114
e.prevent();
115
116
if (blame_item.getDisabled()) {
117
return;
118
}
119
120
data.blame.enabled = !data.blame.enabled;
121
onview(data);
122
123
menu.close();
124
}, data);
125
126
blame_item.setHandler(onblame);
127
128
list.addItem(blame_item);
129
}
130
131
menu.setContent(list.getNode());
132
133
menu.listen('open', function() {
134
for (var ii = 0; ii < engines.length; ii++) {
135
var engine = engines[ii];
136
137
// Highlight the current rendering engine.
138
var is_selected = (engine.spec.viewKey == data.viewKey);
139
engine.view.setSelected(is_selected);
140
141
if (is_selected) {
142
encode_item.setDisabled(!engine.spec.canEncode);
143
highlight_item.setDisabled(!engine.spec.canHighlight);
144
if (blame_item) {
145
blame_item.setDisabled(!engine.spec.canBlame);
146
}
147
}
148
}
149
150
if (blame_item) {
151
var blame_label;
152
if (data.blame.enabled) {
153
blame_label = data.blame.hide;
154
} else {
155
blame_label = data.blame.show;
156
}
157
158
blame_item.setName(blame_label);
159
}
160
});
161
162
data.menu = menu;
163
menu.open();
164
}
165
166
function add_params(uri, data) {
167
uri = JX.$U(uri);
168
169
if (data.highlight.value) {
170
uri.setQueryParam('highlight', data.highlight.value);
171
}
172
173
if (data.encode.value) {
174
uri.setQueryParam('encode', data.encode.value);
175
}
176
177
if (data.blame.enabled) {
178
uri.setQueryParam('blame', null);
179
} else {
180
uri.setQueryParam('blame', 'off');
181
}
182
183
return uri.toString();
184
}
185
186
function onview(data, spec, immediate) {
187
if (!spec) {
188
for (var ii = 0; ii < data.views.length; ii++) {
189
if (data.views[ii].viewKey == data.viewKey) {
190
spec = data.views[ii];
191
break;
192
}
193
}
194
}
195
196
data.sequence = (data.sequence || 0) + 1;
197
var handler = JX.bind(null, onrender, data, data.sequence, spec);
198
199
data.viewKey = spec.viewKey;
200
201
var uri = add_params(spec.engineURI, data);
202
203
new JX.Request(uri, handler)
204
.send();
205
206
if (data.loadingView) {
207
// If we're already showing "Loading...", immediately change it to
208
// show the new document type.
209
onloading(data, spec);
210
} else if (!immediate) {
211
// Otherwise, grey out the document and show "Loading..." after a
212
// short delay. This prevents the content from flickering when rendering
213
// is fast.
214
var viewport = JX.$(data.viewportID);
215
JX.DOM.alterClass(viewport, 'document-engine-in-flight', true);
216
217
var load = JX.bind(null, onloading, data, spec);
218
data.loadTimer = setTimeout(load, 333);
219
220
// Replace the URI with the URI for the specific rendering the user
221
// has selected.
222
223
var view_uri = add_params(spec.viewURI, data);
224
JX.History.replace(view_uri);
225
}
226
}
227
228
function onloading(data, spec) {
229
data.loadingView = true;
230
231
var viewport = JX.$(data.viewportID);
232
JX.DOM.alterClass(viewport, 'document-engine-in-flight', false);
233
JX.DOM.setContent(viewport, JX.$H(spec.loadingMarkup));
234
}
235
236
function onrender(data, sequence, spec, r) {
237
// If this isn't the most recent request we sent, throw it away. This can
238
// happen if the user makes multiple selections from the menu while we are
239
// still rendering the first view.
240
if (sequence != data.sequence) {
241
return;
242
}
243
244
if (data.loadTimer) {
245
clearTimeout(data.loadTimer);
246
data.loadTimer = null;
247
}
248
249
var viewport = JX.$(data.viewportID);
250
251
JX.DOM.alterClass(viewport, 'document-engine-in-flight', false);
252
data.loadingView = false;
253
254
JX.DOM.setContent(viewport, JX.$H(r.markup));
255
256
// If this engine supports rendering blame, populate or draw it.
257
if (spec.canBlame && data.blame.enabled) {
258
blame(data);
259
}
260
}
261
262
function blame(data) {
263
// If the rendering engine can't handle blame, bail.
264
if (!data.blame.uri) {
265
return;
266
}
267
268
// If we already have an outstanding request for blame data, bail.
269
if (data.blame.request) {
270
return;
271
}
272
273
// If we don't have blame data yet, request it and then try rendering
274
// again later.
275
if (!data.blame.value) {
276
var req = new JX.Request(data.blame.uri, JX.bind(null, onblame, data));
277
data.blame.request = req;
278
req.send();
279
return;
280
}
281
282
// We're ready to render.
283
var viewport = JX.$(data.viewportID);
284
285
var row_nodes = JX.DOM.scry(viewport, 'tr');
286
var row_list = [];
287
var ii;
288
289
for (ii = 0; ii < row_nodes.length; ii++) {
290
var row = {};
291
var keep = false;
292
var node = row_nodes[ii];
293
294
for (var jj = 0; jj < node.childNodes.length; jj++) {
295
var child = node.childNodes[jj];
296
297
if (!JX.DOM.isType(child, 'th')) {
298
continue;
299
}
300
301
var spec = child.getAttribute('data-blame');
302
if (spec) {
303
row[spec] = child;
304
keep = true;
305
}
306
307
if (spec === 'info') {
308
row.lines = child.getAttribute('data-blame-lines');
309
}
310
}
311
312
if (keep) {
313
row_list.push(row);
314
}
315
}
316
317
var last = null;
318
for (ii = 0; ii < row_list.length; ii++) {
319
var commit = data.blame.value.blame[row_list[ii].lines - 1];
320
row_list[ii].commit = commit;
321
row_list[ii].last = last;
322
last = commit;
323
}
324
325
for (ii = 0; ii < row_list.length; ii++) {
326
renderBlame(row_list[ii], data.blame.value);
327
}
328
}
329
330
function onblame(data, r) {
331
data.blame.request = null;
332
data.blame.value = r;
333
blame(data);
334
}
335
336
function renderBlame(row, blame) {
337
var spec = blame.map[row.commit];
338
339
var info = null;
340
var skip = null;
341
342
if (spec && (row.commit != row.last)) {
343
skip = JX.$H(spec.skip);
344
info = JX.$H(spec.info);
345
}
346
347
if (row.skip) {
348
JX.DOM.setContent(row.skip, skip);
349
}
350
351
if (row.info) {
352
JX.DOM.setContent(row.info, info);
353
}
354
355
var epoch_range = (blame.epoch.max - blame.epoch.min);
356
357
var epoch_value;
358
if (!epoch_range) {
359
epoch_value = 1;
360
} else {
361
epoch_value = (spec.epoch - blame.epoch.min) / epoch_range;
362
}
363
364
var h_min = 0.04;
365
var h_max = 0.44;
366
var h = h_min + ((h_max - h_min) * epoch_value);
367
368
var s = 0.25;
369
370
var v_min = 0.92;
371
var v_max = 1.00;
372
var v = v_min + ((v_max - v_min) * epoch_value);
373
374
row.info.style.background = getHSV(h, s, v);
375
}
376
377
function getHSV(h, s, v) {
378
var r, g, b, i, f, p, q, t;
379
380
i = Math.floor(h * 6);
381
f = h * 6 - i;
382
p = v * (1 - s);
383
q = v * (1 - f * s);
384
t = v * (1 - (1 - f) * s);
385
386
switch (i % 6) {
387
case 0: r = v, g = t, b = p; break;
388
case 1: r = q, g = v, b = p; break;
389
case 2: r = p, g = v, b = t; break;
390
case 3: r = p, g = q, b = v; break;
391
case 4: r = t, g = p, b = v; break;
392
case 5: r = v, g = p, b = q; break;
393
}
394
395
r = Math.round(r * 255);
396
g = Math.round(g * 255);
397
b = Math.round(b * 255);
398
399
400
return 'rgb(' + r + ', ' + g + ', ' + b + ')';
401
}
402
403
function onhovercoverage(data, e) {
404
if (e.getType() === 'mouseout') {
405
redraw_coverage(data, null);
406
return;
407
}
408
409
var target = e.getNode('tag:th');
410
var coverage = target.getAttribute('data-coverage');
411
if (!coverage) {
412
return;
413
}
414
415
redraw_coverage(data, target);
416
}
417
418
var coverage_row = null;
419
function redraw_coverage(data, node) {
420
if (coverage_row) {
421
JX.DOM.alterClass(
422
coverage_row,
423
'phabricator-source-coverage-highlight',
424
false);
425
coverage_row = null;
426
}
427
428
if (!node) {
429
JX.Tooltip.hide();
430
return;
431
}
432
433
var coverage = node.getAttribute('data-coverage');
434
coverage = coverage.split('/');
435
436
var idx = parseInt(coverage[0], 10);
437
var chr = coverage[1];
438
439
var map = data.coverage.labels[idx];
440
if (map) {
441
var label = map[chr];
442
if (label) {
443
JX.Tooltip.show(node, 300, 'W', label);
444
445
coverage_row = JX.DOM.findAbove(node, 'tr');
446
JX.DOM.alterClass(
447
coverage_row,
448
'phabricator-source-coverage-highlight',
449
true);
450
}
451
}
452
}
453
454
if (!statics.initialized) {
455
JX.Stratcom.listen('click', 'document-engine-view-dropdown', onmenu);
456
statics.initialized = true;
457
}
458
459
if (config && config.controlID) {
460
var control = JX.$(config.controlID);
461
var data = JX.Stratcom.getData(control);
462
463
switch (config.next) {
464
case 'render':
465
onview(data, null, true);
466
break;
467
case 'blame':
468
blame(data);
469
break;
470
}
471
472
JX.DOM.listen(
473
JX.$(data.viewportID),
474
['mouseover', 'mouseout'],
475
'tag:th',
476
JX.bind(null, onhovercoverage, data));
477
}
478
479
});
480
481