1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 00:42:41 +01:00

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
This commit is contained in:
epriestley 2014-05-05 10:57:42 -07:00
parent bfc1ccfdf1
commit 2b4c551b0e
11 changed files with 469 additions and 104 deletions

View file

@ -7,14 +7,14 @@
return array(
'names' =>
array(
'core.pkg.css' => '4279f4bd',
'core.pkg.js' => 'f6616bcf',
'core.pkg.css' => '989eee69',
'core.pkg.js' => 'b2ed04a2',
'darkconsole.pkg.js' => 'ca8671ce',
'differential.pkg.css' => '4b8686e3',
'differential.pkg.js' => 'a2f45b5f',
'differential.pkg.js' => '05ad02d3',
'diffusion.pkg.css' => '3783278d',
'diffusion.pkg.js' => '5b4010f4',
'javelin.pkg.js' => 'c57fd32c',
'javelin.pkg.js' => 'dbef0389',
'maniphest.pkg.css' => 'f1887d71',
'maniphest.pkg.js' => '2fe8af22',
'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
@ -105,7 +105,7 @@ return array(
'rsrc/css/application/subscriptions/subscribers-list.css' => '5bb30c78',
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
'rsrc/css/application/uiexample/example.css' => '528b19de',
'rsrc/css/core/core.css' => 'ef1b7892',
'rsrc/css/core/core.css' => '40151074',
'rsrc/css/core/remarkup.css' => '80c3a48c',
'rsrc/css/core/syntax.css' => '3c18c1cb',
'rsrc/css/core/z-index.css' => 'efb673ac',
@ -204,11 +204,13 @@ return array(
'rsrc/externals/javelin/lib/History.js' => 'c60f4327',
'rsrc/externals/javelin/lib/JSON.js' => '08e56a4e',
'rsrc/externals/javelin/lib/Mask.js' => 'b9f26029',
'rsrc/externals/javelin/lib/Request.js' => '23f9bb8d',
'rsrc/externals/javelin/lib/Request.js' => '7bad574b',
'rsrc/externals/javelin/lib/Resource.js' => '356de121',
'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692',
'rsrc/externals/javelin/lib/Router.js' => '29274e2b',
'rsrc/externals/javelin/lib/URI.js' => 'd9a9b862',
'rsrc/externals/javelin/lib/Vector.js' => '039fb90d',
'rsrc/externals/javelin/lib/Workflow.js' => 'ff8091f7',
'rsrc/externals/javelin/lib/Workflow.js' => '09b15cf1',
'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8',
'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b',
'rsrc/externals/javelin/lib/__tests__/JSON.js' => '2295d074',
@ -349,7 +351,7 @@ return array(
'rsrc/image/texture/table_header_tall.png' => 'd56b434f',
'rsrc/js/application/aphlict/Aphlict.js' => '493665ee',
'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '2a2dba85',
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '845731b8',
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '0a6c2de6',
'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18',
'rsrc/js/application/config/behavior-reorder-fields.js' => '938aed89',
'rsrc/js/application/conpherence/behavior-menu.js' => '7ee23816',
@ -365,7 +367,7 @@ return array(
'rsrc/js/application/differential/behavior-dropdown-menus.js' => '7f93ef26',
'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '00861799',
'rsrc/js/application/differential/behavior-keyboard-nav.js' => '173ce7e7',
'rsrc/js/application/differential/behavior-populate.js' => 'ce0c217a',
'rsrc/js/application/differential/behavior-populate.js' => 'dfdf9f34',
'rsrc/js/application/differential/behavior-show-all-comments.js' => '7c273581',
'rsrc/js/application/differential/behavior-show-field-details.js' => '441f2137',
'rsrc/js/application/differential/behavior-show-more.js' => 'dd7e8ef5',
@ -438,7 +440,7 @@ return array(
'rsrc/js/core/MultirowRowManager.js' => '50395a1b',
'rsrc/js/core/Notification.js' => '0c6946e7',
'rsrc/js/core/Prefab.js' => '0326e5d0',
'rsrc/js/core/ShapedRequest.js' => 'dfa181a4',
'rsrc/js/core/ShapedRequest.js' => '7cbe244b',
'rsrc/js/core/TextAreaUtils.js' => 'b3ec3cfc',
'rsrc/js/core/ToolTip.js' => '3915d490',
'rsrc/js/core/behavior-active-nav.js' => 'c81bc98f',
@ -467,7 +469,7 @@ return array(
'rsrc/js/core/behavior-oncopy.js' => 'c3e218fe',
'rsrc/js/core/behavior-phabricator-nav.js' => 'b5842a5e',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'c021950a',
'rsrc/js/core/behavior-refresh-csrf.js' => 'c4b31646',
'rsrc/js/core/behavior-refresh-csrf.js' => '7814b593',
'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45',
'rsrc/js/core/behavior-reveal-content.js' => '8f24abfc',
'rsrc/js/core/behavior-search-typeahead.js' => 'd8469741',
@ -476,7 +478,7 @@ return array(
'rsrc/js/core/behavior-tokenizer.js' => 'b3a4b884',
'rsrc/js/core/behavior-tooltip.js' => '48db4145',
'rsrc/js/core/behavior-watch-anchor.js' => '06e05112',
'rsrc/js/core/behavior-workflow.js' => 'fee00761',
'rsrc/js/core/behavior-workflow.js' => '0a3f3021',
'rsrc/js/core/phtize.js' => 'd254d646',
'rsrc/js/phui/behavior-phui-object-box-tabs.js' => 'a3e2244e',
'rsrc/js/phui/behavior-phui-timeline-dropdown-menu.js' => '4d94d9c3',
@ -534,7 +536,7 @@ return array(
'javelin-aphlict' => '493665ee',
'javelin-behavior' => '8a3ed18b',
'javelin-behavior-aphlict-dropdown' => '2a2dba85',
'javelin-behavior-aphlict-listen' => '845731b8',
'javelin-behavior-aphlict-listen' => '0a6c2de6',
'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884',
'javelin-behavior-aphront-crop' => 'b98fc918',
'javelin-behavior-aphront-drag-and-drop-textarea' => '4a11ea9c',
@ -558,7 +560,7 @@ return array(
'javelin-behavior-differential-edit-inline-comments' => '00861799',
'javelin-behavior-differential-feedback-preview' => '127f2018',
'javelin-behavior-differential-keyboard-navigation' => '173ce7e7',
'javelin-behavior-differential-populate' => 'ce0c217a',
'javelin-behavior-differential-populate' => 'dfdf9f34',
'javelin-behavior-differential-show-field-details' => '441f2137',
'javelin-behavior-differential-show-more' => 'dd7e8ef5',
'javelin-behavior-differential-toggle-files' => 'ca3f91eb',
@ -623,7 +625,7 @@ return array(
'javelin-behavior-ponder-votebox' => '327dbe61',
'javelin-behavior-project-boards' => 'd8e135db',
'javelin-behavior-project-create' => '065227cc',
'javelin-behavior-refresh-csrf' => 'c4b31646',
'javelin-behavior-refresh-csrf' => '7814b593',
'javelin-behavior-releeph-preview-branch' => '9eb2cedb',
'javelin-behavior-releeph-request-state-change' => 'd259e7c9',
'javelin-behavior-releeph-request-typeahead' => 'cd9e7094',
@ -636,7 +638,7 @@ return array(
'javelin-behavior-test-payment-form' => 'b3e5ee60',
'javelin-behavior-toggle-class' => 'a82a7769',
'javelin-behavior-view-placeholder' => '2fa810fc',
'javelin-behavior-workflow' => 'fee00761',
'javelin-behavior-workflow' => '0a3f3021',
'javelin-color' => '7e41274a',
'javelin-cookie' => '6b3dcf44',
'javelin-dom' => '07d99a3d',
@ -652,8 +654,10 @@ return array(
'javelin-reactor-dom' => 'b6d401d6',
'javelin-reactor-node-calmer' => '76f4ebed',
'javelin-reactornode' => 'b4c30592',
'javelin-request' => '23f9bb8d',
'javelin-request' => '7bad574b',
'javelin-resource' => '356de121',
'javelin-routable' => 'b3e7d692',
'javelin-router' => '29274e2b',
'javelin-stratcom' => 'c293f7b9',
'javelin-tokenizer' => 'e7c21fb3',
'javelin-typeahead' => 'c54eeefb',
@ -671,7 +675,7 @@ return array(
'javelin-view-interpreter' => '0c33c1a0',
'javelin-view-renderer' => '6c2b09a2',
'javelin-view-visitor' => 'efe49472',
'javelin-workflow' => 'ff8091f7',
'javelin-workflow' => '09b15cf1',
'lightbox-attachment-css' => '7acac05d',
'maniphest-batch-editor' => '8f380ebc',
'maniphest-report-css' => '6fc16517',
@ -689,7 +693,7 @@ return array(
'phabricator-busy' => '6453c869',
'phabricator-chatlog-css' => '852140ff',
'phabricator-content-source-view-css' => '4b8b05d4',
'phabricator-core-css' => 'ef1b7892',
'phabricator-core-css' => '40151074',
'phabricator-countdown-css' => '86b7b0a0',
'phabricator-crumbs-view-css' => '0222cbe0',
'phabricator-drag-and-drop-file-upload' => 'ae6abfba',
@ -717,7 +721,7 @@ return array(
'phabricator-remarkup-css' => '80c3a48c',
'phabricator-search-results-css' => 'f240504c',
'phabricator-settings-css' => 'ea8f5915',
'phabricator-shaped-request' => 'dfa181a4',
'phabricator-shaped-request' => '7cbe244b',
'phabricator-side-menu-view-css' => '503699d0',
'phabricator-slowvote-css' => '266df6a1',
'phabricator-source-code-view-css' => '62a99814',
@ -879,6 +883,38 @@ return array(
array(
0 => 'javelin-install',
),
'09b15cf1' =>
array(
0 => 'javelin-stratcom',
1 => 'javelin-request',
2 => 'javelin-dom',
3 => 'javelin-vector',
4 => 'javelin-install',
5 => 'javelin-util',
6 => 'javelin-mask',
7 => 'javelin-uri',
8 => 'javelin-routable',
),
'0a3f3021' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-workflow',
3 => 'javelin-dom',
4 => 'javelin-router',
),
'0a6c2de6' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-aphlict',
2 => 'javelin-stratcom',
3 => 'javelin-request',
4 => 'javelin-uri',
5 => 'javelin-dom',
6 => 'javelin-json',
7 => 'javelin-router',
8 => 'phabricator-notification',
),
'0c33c1a0' =>
array(
0 => 'javelin-view',
@ -990,16 +1026,6 @@ return array(
5 => 'javelin-json',
6 => 'phabricator-prefab',
),
'23f9bb8d' =>
array(
0 => 'javelin-install',
1 => 'javelin-stratcom',
2 => 'javelin-util',
3 => 'javelin-behavior',
4 => 'javelin-json',
5 => 'javelin-dom',
6 => 'javelin-resource',
),
'263aeb8c' =>
array(
0 => 'javelin-behavior',
@ -1012,6 +1038,11 @@ return array(
7 => 'javelin-typeahead-preloaded-source',
8 => 'javelin-json',
),
'29274e2b' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
),
'2a2dba85' =>
array(
0 => 'javelin-behavior',
@ -1223,6 +1254,11 @@ return array(
2 => 'javelin-util',
3 => 'phabricator-shaped-request',
),
'7319e029' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
),
'62e18640' =>
array(
0 => 'javelin-install',
@ -1258,11 +1294,6 @@ return array(
1 => 'javelin-stratcom',
2 => 'javelin-dom',
),
'7319e029' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
),
'75903ee1' =>
array(
0 => 'javelin-behavior',
@ -1281,6 +1312,15 @@ return array(
0 => 'javelin-install',
1 => 'javelin-util',
),
'7814b593' =>
array(
0 => 'javelin-request',
1 => 'javelin-behavior',
2 => 'javelin-dom',
3 => 'javelin-router',
4 => 'javelin-util',
5 => 'phabricator-busy',
),
'79473b62' =>
array(
0 => 'javelin-install',
@ -1296,12 +1336,30 @@ return array(
1 => 'javelin-dom',
2 => 'javelin-reactor-dom',
),
'7bad574b' =>
array(
0 => 'javelin-install',
1 => 'javelin-stratcom',
2 => 'javelin-util',
3 => 'javelin-behavior',
4 => 'javelin-json',
5 => 'javelin-dom',
6 => 'javelin-resource',
7 => 'javelin-routable',
),
'7c273581' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-dom',
),
'7cbe244b' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-request',
3 => 'javelin-router',
),
'7e41274a' =>
array(
0 => 'javelin-install',
@ -1345,17 +1403,6 @@ return array(
1 => 'javelin-dom',
2 => 'javelin-reactor-dom',
),
'845731b8' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-aphlict',
2 => 'javelin-stratcom',
3 => 'javelin-request',
4 => 'javelin-uri',
5 => 'javelin-dom',
6 => 'javelin-json',
7 => 'phabricator-notification',
),
'84845b5b' =>
array(
0 => 'javelin-behavior',
@ -1555,6 +1602,10 @@ return array(
1 => 'javelin-dom',
2 => 'phortune-credit-card-form',
),
'b3e7d692' =>
array(
0 => 'javelin-install',
),
'b3ec3cfc' =>
array(
0 => 'javelin-install',
@ -1683,13 +1734,6 @@ return array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
),
'c4b31646' =>
array(
0 => 'javelin-request',
1 => 'javelin-behavior',
2 => 'javelin-dom',
3 => 'phabricator-busy',
),
'c51a6616' =>
array(
0 => 'phabricator-notification',
@ -1744,17 +1788,6 @@ return array(
0 => 'javelin-install',
1 => 'javelin-typeahead-source',
),
'ce0c217a' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-workflow',
2 => 'javelin-util',
3 => 'javelin-dom',
4 => 'javelin-stratcom',
5 => 'javelin-behavior-device',
6 => 'javelin-vector',
7 => 'phabricator-tooltip',
),
'cf656c84' =>
array(
0 => 'javelin-behavior',
@ -1848,11 +1881,17 @@ return array(
1 => 'javelin-dom',
2 => 'phabricator-prefab',
),
'dfa181a4' =>
'dfdf9f34' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-request',
0 => 'javelin-behavior',
1 => 'javelin-workflow',
2 => 'javelin-util',
3 => 'javelin-dom',
4 => 'javelin-stratcom',
5 => 'javelin-behavior-device',
6 => 'javelin-vector',
7 => 'javelin-router',
8 => 'phabricator-tooltip',
),
'e1ff79b1' =>
array(
@ -1987,24 +2026,6 @@ return array(
4 => 'multirow-row-manager',
5 => 'javelin-json',
),
'fee00761' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-workflow',
3 => 'javelin-dom',
),
'ff8091f7' =>
array(
0 => 'javelin-stratcom',
1 => 'javelin-request',
2 => 'javelin-dom',
3 => 'javelin-vector',
4 => 'javelin-install',
5 => 'javelin-util',
6 => 'javelin-mask',
7 => 'javelin-uri',
),
28497740 =>
array(
0 => 'javelin-behavior',

View file

@ -159,3 +159,21 @@ hr {
background: #990066;
opacity: 0.25;
}
.routing-bar {
position: fixed;
top: 0;
width: 100%;
height: 2px;
background: {$darkbluetext};
z-index: 80;
box-shadow: 0 2px 1px rgba(0, 128, 255, 0.25);
}
.routing-progress {
position: fixed;
top: 0;
left: 0;
height: 2px;
background: {$sky};
}

View file

@ -6,6 +6,7 @@
* javelin-json
* javelin-dom
* javelin-resource
* javelin-routable
* @provides javelin-request
* @javelin
*/
@ -69,6 +70,18 @@ JX.install('Request', {
return this._transport;
},
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
// request before it fires.
JX.Stratcom.pass(JX.Stratcom.context());
this.send();
}));
this.listen('finally', JX.bind(routable, routable.done));
return routable;
},
send : function() {
if (this._sent || this._finished) {
if (__DEV__) {

View file

@ -0,0 +1,41 @@
/**
* @provides javelin-routable
* @requires javelin-install
* @javelin
*/
JX.install('Routable', {
construct : function() {
this._id = (JX.Routable._nextID++);
},
properties: {
key: null,
priority: 1000,
type: 'default'
},
events: ['start', 'done'],
members: {
_id: null,
getID: function() {
return this._id;
},
start: function() {
this.invoke('start');
},
done: function() {
this.invoke('done');
}
},
statics: {
_nextID: 0
}
});

View file

@ -0,0 +1,115 @@
/**
* @provides javelin-router
* @requires javelin-install
* javelin-util
* @javelin
*/
/**
* Route requests. Primarily, this class provides a quality-of-service
* priority queue so large numbers of background loading tasks don't block
* interactive requests.
*/
JX.install('Router', {
construct: function() {
this._queue = [];
},
events: ['queue', 'start', 'done'],
members: {
_queue: null,
_active: 0,
_limit: 5,
queue: function(routable) {
this._queue.push(routable);
this.invoke('queue', routable);
this._update();
},
getRoutableByKey: function(key) {
for (var ii = 0; ii < this._queue.length; ii++) {
if (this._queue[ii].getKey() == key) {
return this._queue[ii];
}
}
return null;
},
/**
* Start new requests if we have slots free for them.
*/
_update: function() {
var active = this._active;
var limit = this._limit;
if (active >= limit) {
// If we're already at the request limit, we can't add any more
// requests.
return;
}
// If we only have one free slot, we reserve it for a request with
// at least priority 1000.
var minimum;
if ((active + 1) == limit) {
minimum = 1000;
} else {
minimum = 0;
}
var idx = this._getNextRoutable(minimum);
if (idx === null) {
return;
}
var routable = this._queue[idx];
this._queue.splice(idx, 1);
routable.listen('done', JX.bind(this, this._done, routable));
this._active++;
routable.start();
this.invoke('start', routable);
this._update();
},
_done: function(routable) {
this._active--;
this.invoke('done', routable);
this._update();
},
_getNextRoutable: function(minimum) {
var best = (minimum - 1);
var routable = null;
for (var ii = 0; ii < this._queue.length; ii++) {
var priority = this._queue[ii].getPriority();
if (priority > best) {
best = priority;
routable = ii;
}
}
return routable;
}
},
statics: {
_instance: null,
getInstance: function() {
if (!JX.Router._instance) {
JX.Router._instance = new JX.Router();
}
return JX.Router._instance;
}
}
});

View file

@ -7,6 +7,7 @@
* javelin-util
* javelin-mask
* javelin-uri
* javelin-routable
* @provides javelin-workflow
* @javelin
*/
@ -259,6 +260,18 @@ JX.install('Workflow', {
r.send();
},
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;
},
setData : function(dictionary) {
this._data = [];
for (var k in dictionary) {

View file

@ -7,6 +7,7 @@
* javelin-uri
* javelin-dom
* javelin-json
* javelin-router
* phabricator-notification
*/
@ -24,9 +25,15 @@ JX.behavior('aphlict-listen', function(config) {
// a request to Phabricator to get notification details.
function onaphlictmessage(type, message) {
if (type == 'receive') {
var request = new JX.Request('/notification/individual/', onnotification)
var routable = new JX.Request('/notification/individual/', onnotification)
.addData({key: message.key})
.send();
.getRoutable();
routable
.setType('notification')
.setPriority(250);
JX.Router.getInstance().queue(routable);
} else if (__DEV__) {
if (config.debug) {
var details = message ? JX.JSON.stringify(message) : '';

View file

@ -7,6 +7,7 @@
* javelin-stratcom
* javelin-behavior-device
* javelin-vector
* javelin-router
* phabricator-tooltip
*/
@ -74,6 +75,25 @@ JX.behavior('differential-populate', function(config) {
// TODO: Once 1up works better, figure out when to show it.
renderer = '2up';
var get_key = function(id) {
return 'differential-populate.' + id;
};
var load = function(id, data) {
var routable = new JX.Workflow(config.uri, data)
.setHandler(JX.bind(null, onresponse, id))
.getRoutable();
routable
.setPriority(500)
.setType('content')
.setKey(get_key(id));
JX.Router.getInstance().queue(routable);
return routable;
};
for (var k in config.registry) {
var data = {
ref : config.registry[k],
@ -81,9 +101,7 @@ JX.behavior('differential-populate', function(config) {
renderer: renderer
};
new JX.Workflow(config.uri, data)
.setHandler(JX.bind(null, onresponse, k))
.start();
load(k, data);
}
var highlighted = null;
@ -104,13 +122,24 @@ JX.behavior('differential-populate', function(config) {
JX.DOM.setContent(
diff,
JX.$H('<div class="differential-loading">Loading...</div>'));
var data = {
ref : meta.ref,
whitespace : config.whitespace
};
new JX.Workflow(config.uri, data)
.setHandler(JX.bind(null, onresponse, meta.id))
.start();
// When a user explicitly clicks "Load" (or clicks a link in the table
// of contents) prioritize this request if it already exists. If it
// doesn't, make a new high-priority request.
var key = get_key(meta.id);
var routable = JX.Router.getInstance().getRoutableByKey(key);
if (!routable) {
var data = {
ref : meta.ref,
whitespace : config.whitespace
};
routable = load(meta.id, data);
}
routable.setPriority(2000);
}
if (meta.kill) {
e.kill();

View file

@ -2,6 +2,7 @@
* @requires javelin-install
* javelin-util
* javelin-request
* javelin-router
* @provides phabricator-shaped-request
* @javelin
*/
@ -63,7 +64,14 @@ JX.install('PhabricatorShapedRequest', {
}));
this._request.setData(data);
this._request.setTimeout(this.getRequestTimeout());
this._request.send();
var routable = this._request.getRoutable();
routable
.setType('draft')
.setPriority(750);
JX.Router.getInstance().queue(routable);
} else {
this._defer = setTimeout(
JX.bind(this, this.trigger),

View file

@ -3,6 +3,8 @@
* @requires javelin-request
* javelin-behavior
* javelin-dom
* javelin-router
* javelin-util
* phabricator-busy
*/
@ -49,11 +51,97 @@ JX.behavior('refresh-csrf', function(config) {
// Additionally, add the CSRF token as an HTTP header to every AJAX request.
JX.Request.listen('open', function(r) {
r.getTransport().setRequestHeader(config.header, current_token);
JX.Busy.start();
});
JX.Request.listen('finally', function(r) {
JX.Busy.done();
// Does this type of routable show the "Busy" spinner?
var is_busy_type = function(type) {
switch (type) {
case 'workflow':
return true;
}
return false;
};
// Does this type of routable show the "Loading" bar?
var is_bar_type = function(type) {
switch (type) {
case 'content':
return true;
}
return false;
};
var queue = {};
var count = 0;
var node;
// Redraw the loading bar.
var redraw_bar = function() {
// If all requests have completed, hide the bar after a moment.
if (!count) {
if (node) {
node.firstChild.style.width = '100%';
setTimeout(JX.bind(null, JX.DOM.remove, node), 500);
}
node = null;
queue = {};
return;
}
// If we don't have the bar yet, draw it.
if (!node) {
node = JX.$N('div', {className: 'routing-bar'});
document.body.appendChild(node);
node.appendChild(JX.$N('div', {className: 'routing-progress'}));
}
// Update the bar progress.
var done = 0;
var total = 0;
for (var k in queue) {
total++;
if (queue[k]) {
done++;
}
}
node.firstChild.style.width = (100 * (done / total)) + '%';
};
// Listen for queued requests.
JX.Router.listen('queue', function(r) {
var type = r.getType();
if (is_bar_type(type)) {
queue[r.getID()] = false;
count++;
redraw_bar();
}
if (is_busy_type(r.getType())) {
JX.Busy.start();
}
});
// Listen for completed requests.
JX.Router.listen('done', function(r) {
var type = r.getType();
if (is_bar_type(type)) {
queue[r.getID()] = true;
count--;
redraw_bar();
}
if (is_busy_type(r.getType())) {
JX.Busy.done();
}
});
});

View file

@ -4,10 +4,21 @@
* javelin-stratcom
* javelin-workflow
* javelin-dom
* javelin-router
*/
JX.behavior('workflow', function() {
// Queue a workflow at elevated priority. The user just clicked or submitted
// something, so service this before loading background content.
var queue = function(workflow) {
var routable = workflow.getRoutable()
.setPriority(2000)
.setType('workflow');
JX.Router.getInstance().queue(routable);
};
// If a user clicks an alternate submit button, make sure it gets marshalled
// into the workflow.
JX.Stratcom.listen(
@ -40,7 +51,8 @@ JX.behavior('workflow', function() {
// not just the <form /> itself.
e.prevent();
JX.Workflow.newFromForm(e.getNode('tag:form'), extra).start();
queue(JX.Workflow.newFromForm(e.getNode('tag:form'), extra));
});
JX.Stratcom.listen(
@ -70,7 +82,7 @@ JX.behavior('workflow', function() {
}
e.prevent();
JX.Workflow.newFromLink(e.getNode('tag:a')).start();
queue(JX.Workflow.newFromLink(e.getNode('tag:a')));
});
});