2013-01-22 19:32:26 +01:00
|
|
|
/**
|
|
|
|
* @requires javelin-stratcom
|
|
|
|
* javelin-request
|
|
|
|
* javelin-dom
|
|
|
|
* javelin-vector
|
|
|
|
* javelin-install
|
|
|
|
* javelin-util
|
|
|
|
* javelin-mask
|
|
|
|
* javelin-uri
|
Provide a global router for Ajax requests
Summary:
Fixes T430. Fixes T4834. Obsoletes D7641. Currently, we do some things less-well than we could:
- We just let the browser queue and prioritize requests, so if you load a revision with 50 changes and then click "Award Token", the action blocks until the changes load in most/all browsers. It would be better to prioritize this action and queue it immediately.
- Similarly, changes tend to load in order, even if the user has clicked to a specific file. When the user expresses a preference for a specific file, we should prioritize it.
- We show a spinning GIF when waiting on requests. This is appropriate for some types of reuqests, but distracting for others.
To fix this:
- Queue all (or, at least, most) requests into a new queue in JX.Router.
- JX.Router handles prioritizing the requests. Principally:
- You can submit a request with a specific priority (500 = general content loading, 1000 = default, 2000 = explicit user action) and JX.Router will get the higher stuff fired off sooner.
- You can name requests and then adjust their prorities later, if the user expresses an interest in specific results.
- Only use the spinner gif for "workflow" requests, which is bascially when the user clicked something and we're waiting on the server. I think it's useful and not-annoying in this case.
- Don't show any status for draft requests.
- For content requests, show a subtle hipster-style top loading bar.
Test Plan:
- Viewed a diff with 93 changes, and clicked award token.
- Prior to this patch, the action took many many seconds to resolve.
- After this patch, it resolves quickly.
- Viewed a diff with 93 changes and saw a pleasant subtle hipster-style loading bar.
- Viewed a diff with 93 changes and typed some draft text. Previews populated fairly quickly and there was no spinner.
- Viewed a diff with 93 changes and clicked something with workflow, saw a spinner after a moment.
- Viewed a diff with 93 changes and clicked a file in the table of contents near the end of the list.
- Prior to this patch, it took a long time to show up.
- After this patch, it loads directly.
Reviewers: chad, btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T430, T4834
Differential Revision: https://secure.phabricator.com/D8979
2014-05-05 19:57:42 +02:00
|
|
|
* javelin-routable
|
2013-01-22 19:32:26 +01:00
|
|
|
* @provides javelin-workflow
|
|
|
|
* @javelin
|
|
|
|
*/
|
|
|
|
|
|
|
|
JX.install('Workflow', {
|
|
|
|
construct : function(uri, data) {
|
|
|
|
if (__DEV__) {
|
|
|
|
if (!uri || uri == '#') {
|
|
|
|
JX.$E(
|
|
|
|
'new JX.Workflow(<?>, ...): '+
|
|
|
|
'bogus URI provided when creating workflow.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.setURI(uri);
|
|
|
|
this.setData(data || {});
|
|
|
|
},
|
|
|
|
|
2016-05-20 15:20:35 +02:00
|
|
|
events : ['error', 'finally', 'submit', 'start'],
|
2013-01-22 19:32:26 +01:00
|
|
|
|
|
|
|
statics : {
|
|
|
|
_stack : [],
|
2015-03-14 20:00:17 +01:00
|
|
|
newFromForm : function(form, data, keep_enabled) {
|
2013-01-22 19:32:26 +01:00
|
|
|
var pairs = JX.DOM.convertFormToListOfPairs(form);
|
|
|
|
for (var k in data) {
|
|
|
|
pairs.push([k, data[k]]);
|
|
|
|
}
|
|
|
|
|
2015-03-14 20:00:17 +01:00
|
|
|
var inputs;
|
|
|
|
if (keep_enabled) {
|
|
|
|
inputs = [];
|
|
|
|
} else {
|
|
|
|
// Disable form elements during the request
|
|
|
|
inputs = [].concat(
|
|
|
|
JX.DOM.scry(form, 'input'),
|
|
|
|
JX.DOM.scry(form, 'button'),
|
|
|
|
JX.DOM.scry(form, 'textarea'));
|
|
|
|
for (var ii = 0; ii < inputs.length; ii++) {
|
|
|
|
if (inputs[ii].disabled) {
|
|
|
|
delete inputs[ii];
|
|
|
|
} else {
|
|
|
|
inputs[ii].disabled = true;
|
|
|
|
}
|
2013-01-22 19:32:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var workflow = new JX.Workflow(form.getAttribute('action'), {});
|
2016-05-20 15:20:35 +02:00
|
|
|
|
|
|
|
workflow._form = form;
|
|
|
|
|
2013-01-22 19:32:26 +01:00
|
|
|
workflow.setDataWithListOfPairs(pairs);
|
|
|
|
workflow.setMethod(form.getAttribute('method'));
|
2018-03-21 19:42:58 +01:00
|
|
|
|
|
|
|
var onfinally = JX.bind(workflow, function() {
|
|
|
|
if (!this._keepControlsDisabled) {
|
|
|
|
for (var ii = 0; ii < inputs.length; ii++) {
|
|
|
|
inputs[ii] && (inputs[ii].disabled = false);
|
|
|
|
}
|
2013-01-22 19:32:26 +01:00
|
|
|
}
|
|
|
|
});
|
2018-03-21 19:42:58 +01:00
|
|
|
workflow.listen('finally', onfinally);
|
2015-03-14 20:00:17 +01:00
|
|
|
|
2013-01-22 19:32:26 +01:00
|
|
|
return workflow;
|
|
|
|
},
|
|
|
|
newFromLink : function(link) {
|
|
|
|
var workflow = new JX.Workflow(link.href);
|
|
|
|
return workflow;
|
|
|
|
},
|
|
|
|
_push : function(workflow) {
|
|
|
|
JX.Mask.show();
|
|
|
|
JX.Workflow._stack.push(workflow);
|
|
|
|
},
|
|
|
|
_pop : function() {
|
|
|
|
var dialog = JX.Workflow._stack.pop();
|
|
|
|
(dialog.getCloseHandler() || JX.bag)();
|
|
|
|
dialog._destroy();
|
|
|
|
JX.Mask.hide();
|
|
|
|
},
|
|
|
|
disable : function() {
|
|
|
|
JX.Workflow._disabled = true;
|
|
|
|
},
|
|
|
|
_onbutton : function(event) {
|
|
|
|
|
|
|
|
if (JX.Stratcom.pass()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (JX.Workflow._disabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-02-15 16:47:14 +01:00
|
|
|
// Get the button (which is sometimes actually another tag, like an <a />)
|
|
|
|
// which triggered the event. In particular, this makes sure we get the
|
|
|
|
// right node if there is a <button> with an <img /> inside it or
|
|
|
|
// or something similar.
|
|
|
|
var t = event.getNode('jx-workflow-button') ||
|
|
|
|
event.getNode('tag:button');
|
2014-03-13 02:17:11 +01:00
|
|
|
|
|
|
|
// If this button disables workflow (normally, because it is a file
|
|
|
|
// download button) let the event through without modification.
|
|
|
|
if (JX.Stratcom.getData(t).disableWorkflow) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
event.prevent();
|
|
|
|
|
2013-01-22 19:32:26 +01:00
|
|
|
if (t.name == '__cancel__' || t.name == '__close__') {
|
|
|
|
JX.Workflow._pop();
|
|
|
|
} else {
|
|
|
|
var form = event.getNode('jx-dialog');
|
2014-05-03 03:01:16 +02:00
|
|
|
JX.Workflow._dosubmit(form, t);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_onsyntheticsubmit : function(e) {
|
|
|
|
if (JX.Stratcom.pass()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (JX.Workflow._disabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
e.prevent();
|
|
|
|
var form = e.getNode('jx-dialog');
|
|
|
|
var button = JX.DOM.find(form, 'button', '__default__');
|
|
|
|
JX.Workflow._dosubmit(form, button);
|
|
|
|
},
|
|
|
|
_dosubmit : function(form, button) {
|
|
|
|
// Issue a DOM event first, so form-oriented handlers can act.
|
|
|
|
var dom_event = JX.DOM.invoke(form, 'didWorkflowSubmit');
|
|
|
|
if (dom_event.getPrevented()) {
|
|
|
|
return;
|
|
|
|
}
|
2013-10-14 20:58:38 +02:00
|
|
|
|
2014-05-03 03:01:16 +02:00
|
|
|
var data = JX.DOM.convertFormToListOfPairs(form);
|
|
|
|
data.push([button.name, button.value || true]);
|
|
|
|
|
|
|
|
var active = JX.Workflow._getActiveWorkflow();
|
2016-05-20 15:20:35 +02:00
|
|
|
|
|
|
|
active._form = form;
|
|
|
|
|
2014-05-03 03:01:16 +02:00
|
|
|
var e = active.invoke('submit', {form: form, data: data});
|
|
|
|
if (!e.getStopped()) {
|
2016-05-20 15:20:35 +02:00
|
|
|
// NOTE: Don't remove the current dialog yet because additional
|
|
|
|
// handlers may still want to access the nodes.
|
|
|
|
|
2018-03-21 19:51:12 +01:00
|
|
|
// Disable whatever button the user clicked to prevent duplicate
|
2018-03-22 18:58:56 +01:00
|
|
|
// submission mistakes when you accidentally click a button multiple
|
|
|
|
// times. See T11145.
|
2018-03-21 19:51:12 +01:00
|
|
|
button.disabled = true;
|
|
|
|
|
2014-05-03 03:01:16 +02:00
|
|
|
active
|
|
|
|
.setURI(form.getAttribute('action') || active.getURI())
|
|
|
|
.setDataWithListOfPairs(data)
|
|
|
|
.start();
|
2013-01-22 19:32:26 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
_getActiveWorkflow : function() {
|
|
|
|
var stack = JX.Workflow._stack;
|
|
|
|
return stack[stack.length - 1];
|
2016-06-21 02:29:56 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
_onresizestart: function(e) {
|
|
|
|
var self = JX.Workflow;
|
|
|
|
if (self._resizing) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var workflow = self._getActiveWorkflow();
|
|
|
|
if (!workflow) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
e.kill();
|
|
|
|
|
|
|
|
var form = JX.DOM.find(workflow._root, 'div', 'jx-dialog');
|
|
|
|
var resize = e.getNodeData('jx-dialog-resize');
|
|
|
|
var node_y = JX.$(resize.resizeY);
|
|
|
|
|
|
|
|
var dim = JX.Vector.getDim(form);
|
|
|
|
dim.y = JX.Vector.getDim(node_y).y;
|
|
|
|
|
|
|
|
if (!form._minimumSize) {
|
|
|
|
form._minimumSize = dim;
|
|
|
|
}
|
|
|
|
|
|
|
|
self._resizing = {
|
|
|
|
min: form._minimumSize,
|
|
|
|
form: form,
|
|
|
|
startPos: JX.$V(e),
|
|
|
|
startDim: dim,
|
|
|
|
resizeY: node_y,
|
|
|
|
resizeX: resize.resizeX
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
_onmousemove: function(e) {
|
|
|
|
var self = JX.Workflow;
|
|
|
|
if (!self._resizing) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var spec = self._resizing;
|
|
|
|
var form = spec.form;
|
|
|
|
var min = spec.min;
|
|
|
|
|
|
|
|
var delta = JX.$V(e).add(-spec.startPos.x, -spec.startPos.y);
|
|
|
|
var src_dim = spec.startDim;
|
|
|
|
var dst_dim = JX.$V(src_dim.x + delta.x, src_dim.y + delta.y);
|
|
|
|
|
|
|
|
if (dst_dim.x < min.x) {
|
|
|
|
dst_dim.x = min.x;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dst_dim.y < min.y) {
|
|
|
|
dst_dim.y = min.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (spec.resizeX) {
|
|
|
|
JX.$V(dst_dim.x, null).setDim(form);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (spec.resizeY) {
|
|
|
|
JX.$V(null, dst_dim.y).setDim(spec.resizeY);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onmouseup: function() {
|
|
|
|
var self = JX.Workflow;
|
|
|
|
if (!self._resizing) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
self._resizing = false;
|
2013-01-22 19:32:26 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
members : {
|
|
|
|
_root : null,
|
|
|
|
_pushed : false,
|
|
|
|
_data : null,
|
2016-05-20 15:20:35 +02:00
|
|
|
|
|
|
|
_form: null,
|
|
|
|
_paused: 0,
|
|
|
|
_nextCallback: null,
|
2018-03-21 19:42:58 +01:00
|
|
|
_keepControlsDisabled: false,
|
2016-05-20 15:20:35 +02:00
|
|
|
|
|
|
|
getSourceForm: function() {
|
|
|
|
return this._form;
|
|
|
|
},
|
|
|
|
|
|
|
|
pause: function() {
|
|
|
|
this._paused++;
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
resume: function() {
|
|
|
|
if (!this._paused) {
|
|
|
|
JX.$E('Resuming a workflow which is not paused!');
|
|
|
|
}
|
|
|
|
|
|
|
|
this._paused--;
|
|
|
|
|
|
|
|
if (!this._paused) {
|
|
|
|
var next = this._nextCallback;
|
|
|
|
this._nextCallback = null;
|
|
|
|
if (next) {
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2013-01-22 19:32:26 +01:00
|
|
|
_onload : function(r) {
|
2016-05-20 15:20:35 +02:00
|
|
|
this._destroy();
|
|
|
|
|
2013-01-22 19:32:26 +01:00
|
|
|
// It is permissible to send back a falsey redirect to force a page
|
|
|
|
// reload, so we need to take this branch if the key is present.
|
|
|
|
if (r && (typeof r.redirect != 'undefined')) {
|
Never generate file download forms which point to the CDN domain, tighten "form-action" CSP
Summary:
Depends on D19155. Ref T13094. Ref T4340.
We can't currently implement a strict `form-action 'self'` content security policy because some file downloads rely on a `<form />` which sometimes POSTs to the CDN domain.
Broadly, stop generating these forms. We just redirect instead, and show an interstitial confirm dialog if no CDN domain is configured. This makes the UX for installs with no CDN domain a little worse and the UX for everyone else better.
Then, implement the stricter Content-Security-Policy.
This also removes extra confirm dialogs for downloading Harbormaster build logs and data exports.
Test Plan:
- Went through the plain data export, data export with bulk jobs, ssh key generation, calendar ICS download, Diffusion data, Paste data, Harbormaster log data, and normal file data download workflows with a CDN domain.
- Went through all those workflows again without a CDN domain.
- Grepped for affected symbols (`getCDNURI()`, `getDownloadURI()`).
- Added an evil form to a page, tried to submit it, was rejected.
- Went through the ReCaptcha and Stripe flows again to see if they're submitting any forms.
Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam
Maniphest Tasks: T13094, T4340
Differential Revision: https://secure.phabricator.com/D19156
2018-03-01 00:22:10 +01:00
|
|
|
// Before we redirect to file downloads, we close the dialog. These
|
|
|
|
// redirects aren't real navigation events so we end up stuck in the
|
|
|
|
// dialog otherwise.
|
|
|
|
if (r.close) {
|
|
|
|
this._pop();
|
|
|
|
}
|
|
|
|
|
2018-03-21 19:42:58 +01:00
|
|
|
// If we're redirecting, don't re-enable for controls.
|
|
|
|
this._keepControlsDisabled = true;
|
|
|
|
|
2013-01-22 19:32:26 +01:00
|
|
|
JX.$U(r.redirect).go();
|
|
|
|
} else if (r && r.dialog) {
|
|
|
|
this._push();
|
|
|
|
this._root = JX.$N(
|
|
|
|
'div',
|
|
|
|
{className: 'jx-client-dialog'},
|
|
|
|
JX.$H(r.dialog));
|
|
|
|
JX.DOM.listen(
|
|
|
|
this._root,
|
|
|
|
'click',
|
|
|
|
[['jx-workflow-button'], ['tag:button']],
|
|
|
|
JX.Workflow._onbutton);
|
2014-05-03 03:01:16 +02:00
|
|
|
JX.DOM.listen(
|
|
|
|
this._root,
|
|
|
|
'didSyntheticSubmit',
|
|
|
|
[],
|
|
|
|
JX.Workflow._onsyntheticsubmit);
|
2015-01-27 16:11:20 +01:00
|
|
|
|
2016-06-21 02:29:56 +02:00
|
|
|
JX.DOM.listen(
|
|
|
|
this._root,
|
|
|
|
'mousedown',
|
|
|
|
'jx-dialog-resize',
|
|
|
|
JX.Workflow._onresizestart);
|
|
|
|
|
2015-01-29 16:10:14 +01:00
|
|
|
// Note that even in the presence of a content frame, we're doing
|
|
|
|
// everything here at top level: dialogs are fully modal and cover
|
|
|
|
// the entire window.
|
|
|
|
|
|
|
|
document.body.appendChild(this._root);
|
|
|
|
|
2013-01-22 19:32:26 +01:00
|
|
|
var d = JX.Vector.getDim(this._root);
|
2015-01-29 16:10:14 +01:00
|
|
|
var v = JX.Vector.getViewport();
|
|
|
|
var s = JX.Vector.getScroll();
|
2013-05-31 03:55:25 +02:00
|
|
|
|
|
|
|
// Normally, we position dialogs 100px from the top of the screen.
|
|
|
|
// Use more space if the dialog is large (at least roughly the size
|
|
|
|
// of the viewport).
|
|
|
|
var offset = Math.min(Math.max(20, (v.y - d.y) / 2), 100);
|
2016-03-26 20:23:07 +01:00
|
|
|
JX.$V(0, s.y + offset).setPos(this._root);
|
2013-05-31 03:55:25 +02:00
|
|
|
|
2013-01-22 19:32:26 +01:00
|
|
|
try {
|
|
|
|
JX.DOM.focus(JX.DOM.find(this._root, 'button', '__default__'));
|
|
|
|
var inputs = JX.DOM.scry(this._root, 'input')
|
|
|
|
.concat(JX.DOM.scry(this._root, 'textarea'));
|
|
|
|
var miny = Number.POSITIVE_INFINITY;
|
|
|
|
var target = null;
|
|
|
|
for (var ii = 0; ii < inputs.length; ++ii) {
|
|
|
|
if (inputs[ii].type != 'hidden') {
|
|
|
|
// Find the topleft-most displayed element.
|
|
|
|
var p = JX.$V(inputs[ii]);
|
|
|
|
if (p.y < miny) {
|
|
|
|
miny = p.y;
|
|
|
|
target = inputs[ii];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
target && JX.DOM.focus(target);
|
|
|
|
} catch (_ignored) {}
|
2013-05-31 03:55:25 +02:00
|
|
|
|
|
|
|
// The `focus()` call may have scrolled the window. Scroll it back to
|
|
|
|
// where it was before -- we want to focus the control, but not adjust
|
|
|
|
// the scroll position.
|
2015-01-29 16:10:14 +01:00
|
|
|
|
|
|
|
// Dialogs are window-level, so scroll the window explicitly.
|
|
|
|
window.scrollTo(s.x, s.y);
|
2013-05-31 03:55:25 +02:00
|
|
|
|
2013-01-22 19:32:26 +01:00
|
|
|
} else if (this.getHandler()) {
|
|
|
|
this.getHandler()(r);
|
|
|
|
this._pop();
|
|
|
|
} else if (r) {
|
|
|
|
if (__DEV__) {
|
|
|
|
JX.$E('Response to workflow request went unhandled.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_push : function() {
|
|
|
|
if (!this._pushed) {
|
|
|
|
this._pushed = true;
|
|
|
|
JX.Workflow._push(this);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_pop : function() {
|
|
|
|
if (this._pushed) {
|
|
|
|
this._pushed = false;
|
|
|
|
JX.Workflow._pop();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_destroy : function() {
|
|
|
|
if (this._root) {
|
|
|
|
JX.DOM.remove(this._root);
|
|
|
|
this._root = null;
|
|
|
|
}
|
|
|
|
},
|
2016-05-20 15:20:35 +02:00
|
|
|
|
2013-01-22 19:32:26 +01:00
|
|
|
start : function() {
|
2016-05-20 15:20:35 +02:00
|
|
|
var next = JX.bind(this, this._send);
|
|
|
|
|
|
|
|
this.pause();
|
|
|
|
this._nextCallback = next;
|
|
|
|
|
|
|
|
this.invoke('start', this);
|
|
|
|
|
|
|
|
this.resume();
|
|
|
|
},
|
|
|
|
|
|
|
|
_send: function() {
|
2013-01-22 19:32:26 +01:00
|
|
|
var uri = this.getURI();
|
|
|
|
var method = this.getMethod();
|
|
|
|
var r = new JX.Request(uri, JX.bind(this, this._onload));
|
|
|
|
var list_of_pairs = this._data;
|
|
|
|
list_of_pairs.push(['__wflow__', true]);
|
|
|
|
r.setDataWithListOfPairs(list_of_pairs);
|
|
|
|
r.setDataSerializer(this.getDataSerializer());
|
|
|
|
if (method) {
|
|
|
|
r.setMethod(method);
|
|
|
|
}
|
|
|
|
r.listen('finally', JX.bind(this, this.invoke, 'finally'));
|
|
|
|
r.listen('error', JX.bind(this, function(error) {
|
|
|
|
var e = this.invoke('error', error);
|
|
|
|
if (e.getStopped()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// TODO: Default error behavior? On Facebook Lite, we just shipped the
|
|
|
|
// user to "/error/". We could emit a blanket 'workflow-failed' type
|
|
|
|
// event instead.
|
|
|
|
}));
|
|
|
|
r.send();
|
|
|
|
},
|
|
|
|
|
Provide a global router for Ajax requests
Summary:
Fixes T430. Fixes T4834. Obsoletes D7641. Currently, we do some things less-well than we could:
- We just let the browser queue and prioritize requests, so if you load a revision with 50 changes and then click "Award Token", the action blocks until the changes load in most/all browsers. It would be better to prioritize this action and queue it immediately.
- Similarly, changes tend to load in order, even if the user has clicked to a specific file. When the user expresses a preference for a specific file, we should prioritize it.
- We show a spinning GIF when waiting on requests. This is appropriate for some types of reuqests, but distracting for others.
To fix this:
- Queue all (or, at least, most) requests into a new queue in JX.Router.
- JX.Router handles prioritizing the requests. Principally:
- You can submit a request with a specific priority (500 = general content loading, 1000 = default, 2000 = explicit user action) and JX.Router will get the higher stuff fired off sooner.
- You can name requests and then adjust their prorities later, if the user expresses an interest in specific results.
- Only use the spinner gif for "workflow" requests, which is bascially when the user clicked something and we're waiting on the server. I think it's useful and not-annoying in this case.
- Don't show any status for draft requests.
- For content requests, show a subtle hipster-style top loading bar.
Test Plan:
- Viewed a diff with 93 changes, and clicked award token.
- Prior to this patch, the action took many many seconds to resolve.
- After this patch, it resolves quickly.
- Viewed a diff with 93 changes and saw a pleasant subtle hipster-style loading bar.
- Viewed a diff with 93 changes and typed some draft text. Previews populated fairly quickly and there was no spinner.
- Viewed a diff with 93 changes and clicked something with workflow, saw a spinner after a moment.
- Viewed a diff with 93 changes and clicked a file in the table of contents near the end of the list.
- Prior to this patch, it took a long time to show up.
- After this patch, it loads directly.
Reviewers: chad, btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T430, T4834
Differential Revision: https://secure.phabricator.com/D8979
2014-05-05 19:57:42 +02:00
|
|
|
getRoutable: function() {
|
|
|
|
var routable = new JX.Routable();
|
|
|
|
routable.listen('start', JX.bind(this, function() {
|
|
|
|
// Pass the event to allow other listeners to "start" to configure this
|
|
|
|
// workflow before it fires.
|
|
|
|
JX.Stratcom.pass(JX.Stratcom.context());
|
|
|
|
this.start();
|
|
|
|
}));
|
|
|
|
this.listen('finally', JX.bind(routable, routable.done));
|
|
|
|
return routable;
|
|
|
|
},
|
|
|
|
|
2013-01-22 19:32:26 +01:00
|
|
|
setData : function(dictionary) {
|
|
|
|
this._data = [];
|
|
|
|
for (var k in dictionary) {
|
|
|
|
this._data.push([k, dictionary[k]]);
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2016-05-20 15:20:35 +02:00
|
|
|
addData: function(key, value) {
|
|
|
|
this._data.push([key, value]);
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2013-01-22 19:32:26 +01:00
|
|
|
setDataWithListOfPairs : function(list_of_pairs) {
|
|
|
|
this._data = list_of_pairs;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
properties : {
|
|
|
|
handler : null,
|
|
|
|
closeHandler : null,
|
|
|
|
dataSerializer : null,
|
|
|
|
method : null,
|
|
|
|
URI : null
|
|
|
|
},
|
|
|
|
|
|
|
|
initialize : function() {
|
|
|
|
|
|
|
|
function close_dialog_when_user_presses_escape(e) {
|
|
|
|
if (e.getSpecialKey() != 'esc') {
|
|
|
|
// Some key other than escape.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (JX.Workflow._disabled) {
|
|
|
|
// Workflows are disabled on this page.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (JX.Stratcom.pass()) {
|
|
|
|
// Something else swallowed the event.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var active = JX.Workflow._getActiveWorkflow();
|
|
|
|
if (!active) {
|
|
|
|
// No active workflow.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: the cancel button is actually an <a /> tag.
|
|
|
|
var buttons = JX.DOM.scry(active._root, 'a', 'jx-workflow-button');
|
|
|
|
if (!buttons.length) {
|
|
|
|
// No buttons in the dialog.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var cancel = null;
|
|
|
|
for (var ii = 0; ii < buttons.length; ii++) {
|
|
|
|
if (buttons[ii].name == '__cancel__') {
|
|
|
|
cancel = buttons[ii];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cancel) {
|
|
|
|
// No 'Cancel' button.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JX.Workflow._pop();
|
|
|
|
e.prevent();
|
2013-05-19 02:04:22 +02:00
|
|
|
}
|
2013-01-22 19:32:26 +01:00
|
|
|
|
|
|
|
JX.Stratcom.listen('keydown', null, close_dialog_when_user_presses_escape);
|
2016-06-21 02:29:56 +02:00
|
|
|
|
|
|
|
JX.Stratcom.listen('mousemove', null, JX.Workflow._onmousemove);
|
|
|
|
JX.Stratcom.listen('mouseup', null, JX.Workflow._onmouseup);
|
2013-01-22 19:32:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
});
|