1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-14 10:52:41 +01:00
phorge-phorge/webroot/rsrc/js/application/files/behavior-document-engine.js
epriestley afc3099ee7 Add a view option to disable blame in Diffusion and fix some view transition bugs
Summary:
See PHI604. Ref T13130. Ref T13105. There's currently no way to turn blame off in Diffusion. Add a "Hide Blame" option to the "View Options" dropdown so it can be toggled off.

Also fix a couple of bugs around this: for example, if you loaded a Jupyter notebook and then switched to "Source" view, blame would incorrectly fail to activate because the original rendering of the "stage" used an asynchronous engine so `willRenderRef()` wasn't called to populate blame.

Test Plan:
  - Viewed a source file, toggled blame off/on, reloaded page to see state stick in URL.
  - Viewed a Jupyter notebook, toggled to "Source" view, saw blame.
  - Viewed stuff in Files (no blame UI options).
  - Tried to do some invalid stuff like toggle blame on a non-blame engine (options disable properly).

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13130, T13105

Differential Revision: https://secure.phabricator.com/D19414
2018-04-30 15:32:23 -07:00

479 lines
11 KiB
JavaScript

/**
* @provides javelin-behavior-document-engine
* @requires javelin-behavior
* javelin-dom
* javelin-stratcom
*/
JX.behavior('document-engine', function(config, statics) {
function onmenu(e) {
var node = e.getNode('document-engine-view-dropdown');
var data = JX.Stratcom.getData(node);
if (data.menu) {
return;
}
e.prevent();
var menu = new JX.PHUIXDropdownMenu(node);
var list = new JX.PHUIXActionListView();
var view;
var engines = [];
for (var ii = 0; ii < data.views.length; ii++) {
var spec = data.views[ii];
view = new JX.PHUIXActionView()
.setName(spec.name)
.setIcon(spec.icon)
.setIconColor(spec.color)
.setHref(spec.engineURI);
view.setHandler(JX.bind(null, function(spec, e) {
if (!e.isNormalClick()) {
return;
}
e.prevent();
menu.close();
onview(data, spec, false);
}, spec));
list.addItem(view);
engines.push({
spec: spec,
view: view
});
}
list.addItem(
new JX.PHUIXActionView()
.setDivider(true));
var encode_item = new JX.PHUIXActionView()
.setName(data.encode.name)
.setIcon(data.encode.icon);
var onencode = JX.bind(null, function(data, e) {
e.prevent();
if (encode_item.getDisabled()) {
return;
}
new JX.Workflow(data.encode.uri, {encoding: data.encode.value})
.setHandler(function(r) {
data.encode.value = r.encoding;
onview(data);
})
.start();
menu.close();
}, data);
encode_item.setHandler(onencode);
list.addItem(encode_item);
var highlight_item = new JX.PHUIXActionView()
.setName(data.highlight.name)
.setIcon(data.highlight.icon);
var onhighlight = JX.bind(null, function(data, e) {
e.prevent();
if (highlight_item.getDisabled()) {
return;
}
new JX.Workflow(data.highlight.uri, {highlight: data.highlight.value})
.setHandler(function(r) {
data.highlight.value = r.highlight;
onview(data);
})
.start();
menu.close();
}, data);
highlight_item.setHandler(onhighlight);
list.addItem(highlight_item);
var blame_item;
if (data.blame.uri) {
blame_item = new JX.PHUIXActionView()
.setIcon(data.blame.icon);
var onblame = JX.bind(null, function(data, e) {
e.prevent();
if (blame_item.getDisabled()) {
return;
}
data.blame.enabled = !data.blame.enabled;
onview(data);
menu.close();
}, data);
blame_item.setHandler(onblame);
list.addItem(blame_item);
}
menu.setContent(list.getNode());
menu.listen('open', function() {
for (var ii = 0; ii < engines.length; ii++) {
var engine = engines[ii];
// Highlight the current rendering engine.
var is_selected = (engine.spec.viewKey == data.viewKey);
engine.view.setSelected(is_selected);
if (is_selected) {
encode_item.setDisabled(!engine.spec.canEncode);
highlight_item.setDisabled(!engine.spec.canHighlight);
if (blame_item) {
blame_item.setDisabled(!engine.spec.canBlame);
}
}
}
if (blame_item) {
var blame_label;
if (data.blame.enabled) {
blame_label = data.blame.hide;
} else {
blame_label = data.blame.show;
}
blame_item.setName(blame_label);
}
});
data.menu = menu;
menu.open();
}
function add_params(uri, data) {
uri = JX.$U(uri);
if (data.highlight.value) {
uri.setQueryParam('highlight', data.highlight.value);
}
if (data.encode.value) {
uri.setQueryParam('encode', data.encode.value);
}
if (data.blame.enabled) {
uri.setQueryParam('blame', null);
} else {
uri.setQueryParam('blame', 'off');
}
return uri.toString();
}
function onview(data, spec, immediate) {
if (!spec) {
for (var ii = 0; ii < data.views.length; ii++) {
if (data.views[ii].viewKey == data.viewKey) {
spec = data.views[ii];
break;
}
}
}
data.sequence = (data.sequence || 0) + 1;
var handler = JX.bind(null, onrender, data, data.sequence, spec);
data.viewKey = spec.viewKey;
var uri = add_params(spec.engineURI, data);
new JX.Request(uri, handler)
.send();
if (data.loadingView) {
// If we're already showing "Loading...", immediately change it to
// show the new document type.
onloading(data, spec);
} else if (!immediate) {
// Otherwise, grey out the document and show "Loading..." after a
// short delay. This prevents the content from flickering when rendering
// is fast.
var viewport = JX.$(data.viewportID);
JX.DOM.alterClass(viewport, 'document-engine-in-flight', true);
var load = JX.bind(null, onloading, data, spec);
data.loadTimer = setTimeout(load, 333);
// Replace the URI with the URI for the specific rendering the user
// has selected.
var view_uri = add_params(spec.viewURI, data);
JX.History.replace(view_uri);
}
}
function onloading(data, spec) {
data.loadingView = true;
var viewport = JX.$(data.viewportID);
JX.DOM.alterClass(viewport, 'document-engine-in-flight', false);
JX.DOM.setContent(viewport, JX.$H(spec.loadingMarkup));
}
function onrender(data, sequence, spec, r) {
// If this isn't the most recent request we sent, throw it away. This can
// happen if the user makes multiple selections from the menu while we are
// still rendering the first view.
if (sequence != data.sequence) {
return;
}
if (data.loadTimer) {
clearTimeout(data.loadTimer);
data.loadTimer = null;
}
var viewport = JX.$(data.viewportID);
JX.DOM.alterClass(viewport, 'document-engine-in-flight', false);
data.loadingView = false;
JX.DOM.setContent(viewport, JX.$H(r.markup));
// If this engine supports rendering blame, populate or draw it.
if (spec.canBlame && data.blame.enabled) {
blame(data);
}
}
function blame(data) {
// If the rendering engine can't handle blame, bail.
if (!data.blame.uri) {
return;
}
// If we already have an outstanding request for blame data, bail.
if (data.blame.request) {
return;
}
// If we don't have blame data yet, request it and then try rendering
// again later.
if (!data.blame.value) {
var req = new JX.Request(data.blame.uri, JX.bind(null, onblame, data));
data.blame.request = req;
req.send();
return;
}
// We're ready to render.
var viewport = JX.$(data.viewportID);
var row_nodes = JX.DOM.scry(viewport, 'tr');
var row_list = [];
var ii;
for (ii = 0; ii < row_nodes.length; ii++) {
var row = {};
var keep = false;
var node = row_nodes[ii];
for (var jj = 0; jj < node.childNodes.length; jj++) {
var child = node.childNodes[jj];
if (!JX.DOM.isType(child, 'th')) {
continue;
}
var spec = child.getAttribute('data-blame');
if (spec) {
row[spec] = child;
keep = true;
}
if (spec === 'info') {
row.lines = child.getAttribute('data-blame-lines');
}
}
if (keep) {
row_list.push(row);
}
}
var last = null;
for (ii = 0; ii < row_list.length; ii++) {
var commit = data.blame.value.blame[row_list[ii].lines - 1];
row_list[ii].commit = commit;
row_list[ii].last = last;
last = commit;
}
for (ii = 0; ii < row_list.length; ii++) {
renderBlame(row_list[ii], data.blame.value);
}
}
function onblame(data, r) {
data.blame.request = null;
data.blame.value = r;
blame(data);
}
function renderBlame(row, blame) {
var spec = blame.map[row.commit];
var info = null;
var skip = null;
if (spec && (row.commit != row.last)) {
skip = JX.$H(spec.skip);
info = JX.$H(spec.info);
}
if (row.skip) {
JX.DOM.setContent(row.skip, skip);
}
if (row.info) {
JX.DOM.setContent(row.info, info);
}
var epoch_range = (blame.epoch.max - blame.epoch.min);
var epoch_value;
if (!epoch_range) {
epoch_value = 1;
} else {
epoch_value = (spec.epoch - blame.epoch.min) / epoch_range;
}
var h_min = 0.04;
var h_max = 0.44;
var h = h_min + ((h_max - h_min) * epoch_value);
var s = 0.25;
var v_min = 0.92;
var v_max = 1.00;
var v = v_min + ((v_max - v_min) * epoch_value);
row.info.style.background = getHSV(h, s, v);
}
function getHSV(h, s, v) {
var r, g, b, i, f, p, q, t;
i = Math.floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
r = Math.round(r * 255);
g = Math.round(g * 255);
b = Math.round(b * 255);
return 'rgb(' + r + ', ' + g + ', ' + b + ')';
}
function onhovercoverage(data, e) {
if (e.getType() === 'mouseout') {
redraw_coverage(data, null);
return;
}
var target = e.getNode('tag:th');
var coverage = target.getAttribute('data-coverage');
if (!coverage) {
return;
}
redraw_coverage(data, target);
}
var coverage_row = null;
function redraw_coverage(data, node) {
if (coverage_row) {
JX.DOM.alterClass(
coverage_row,
'phabricator-source-coverage-highlight',
false);
coverage_row = null;
}
if (!node) {
JX.Tooltip.hide();
return;
}
var coverage = node.getAttribute('data-coverage');
coverage = coverage.split('/');
var idx = parseInt(coverage[0], 10);
var chr = coverage[1];
var map = data.coverage.labels[idx];
if (map) {
var label = map[chr];
if (label) {
JX.Tooltip.show(node, 300, 'W', label);
coverage_row = JX.DOM.findAbove(node, 'tr');
JX.DOM.alterClass(
coverage_row,
'phabricator-source-coverage-highlight',
true);
}
}
}
if (!statics.initialized) {
JX.Stratcom.listen('click', 'document-engine-view-dropdown', onmenu);
statics.initialized = true;
}
if (config && config.controlID) {
var control = JX.$(config.controlID);
var data = JX.Stratcom.getData(control);
switch (config.next) {
case 'render':
onview(data, null, true);
break;
case 'blame':
blame(data);
break;
}
JX.DOM.listen(
JX.$(data.viewportID),
['mouseover', 'mouseout'],
'tag:th',
JX.bind(null, onhovercoverage, data));
}
});