Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js
12242 views
1
/**
2
* @provides conpherence-thread-manager
3
* @requires javelin-dom
4
* javelin-util
5
* javelin-stratcom
6
* javelin-install
7
* javelin-aphlict
8
* javelin-workflow
9
* javelin-router
10
* javelin-behavior-device
11
* javelin-vector
12
*/
13
JX.install('ConpherenceThreadManager', {
14
15
construct : function() {
16
if (__DEV__) {
17
if (JX.ConpherenceThreadManager._instance) {
18
JX.$E('ConpherenceThreadManager object is a singleton.');
19
}
20
}
21
JX.ConpherenceThreadManager._instance = this;
22
return this;
23
},
24
25
members: {
26
_loadThreadURI: null,
27
_loadedThreadID: null,
28
_loadedThreadPHID: null,
29
_latestTransactionID: null,
30
_transactionIDMap: null,
31
_transactionCache: null,
32
_canEditLoadedThread: null,
33
_updating: null,
34
_messagesRootCallback: JX.bag,
35
_willLoadThreadCallback: JX.bag,
36
_didLoadThreadCallback: JX.bag,
37
_didUpdateThreadCallback: JX.bag,
38
_willSendMessageCallback: JX.bag,
39
_didSendMessageCallback: JX.bag,
40
_willUpdateWorkflowCallback: JX.bag,
41
_didUpdateWorkflowCallback: JX.bag,
42
43
setLoadThreadURI: function(uri) {
44
this._loadThreadURI = uri;
45
return this;
46
},
47
48
getLoadThreadURI: function() {
49
return this._loadThreadURI;
50
},
51
52
isThreadLoaded: function() {
53
return Boolean(this._loadedThreadID);
54
},
55
56
isThreadIDLoaded: function(thread_id) {
57
return this._loadedThreadID == thread_id;
58
},
59
60
getLoadedThreadID: function() {
61
return this._loadedThreadID;
62
},
63
64
setLoadedThreadID: function(id) {
65
this._loadedThreadID = id;
66
return this;
67
},
68
69
getLoadedThreadPHID: function() {
70
return this._loadedThreadPHID;
71
},
72
73
setLoadedThreadPHID: function(phid) {
74
this._loadedThreadPHID = phid;
75
return this;
76
},
77
78
getLatestTransactionID: function() {
79
return this._latestTransactionID;
80
},
81
82
setLatestTransactionID: function(id) {
83
this._latestTransactionID = id;
84
return this;
85
},
86
87
_updateTransactionIDMap: function(transactions) {
88
var loaded_id = this.getLoadedThreadID();
89
if (!this._transactionIDMap[loaded_id]) {
90
this._transactionIDMap[this._loadedThreadID] = {};
91
}
92
var loaded_transaction_ids = this._transactionIDMap[loaded_id];
93
var transaction;
94
for (var ii = 0; ii < transactions.length; ii++) {
95
transaction = transactions[ii];
96
loaded_transaction_ids[JX.Stratcom.getData(transaction).id] = 1;
97
}
98
this._transactionIDMap[this._loadedThreadID] = loaded_transaction_ids;
99
return this;
100
},
101
102
_updateTransactionCache: function(transactions) {
103
var transaction;
104
for (var ii = 0; ii < transactions.length; ii++) {
105
transaction = transactions[ii];
106
this._transactionCache[JX.Stratcom.getData(transaction).id] =
107
transaction;
108
}
109
return this;
110
},
111
112
_getLoadedTransactions: function() {
113
var loaded_id = this.getLoadedThreadID();
114
var loaded_tx_ids = JX.keys(this._transactionIDMap[loaded_id]);
115
loaded_tx_ids.sort(function (a, b) {
116
var x = parseFloat(a);
117
var y = parseFloat(b);
118
if (x > y) {
119
return 1;
120
}
121
if (x < y) {
122
return -1;
123
}
124
return 0;
125
});
126
var transactions = [];
127
for (var ii = 0; ii < loaded_tx_ids.length; ii++) {
128
transactions.push(this._transactionCache[loaded_tx_ids[ii]]);
129
}
130
return transactions;
131
},
132
133
_deleteTransactionCaches: function(id) {
134
delete this._transactionCache[id];
135
delete this._transactionIDMap[this._loadedThreadID][id];
136
137
return this;
138
},
139
140
setCanEditLoadedThread: function(bool) {
141
this._canEditLoadedThread = bool;
142
return this;
143
},
144
145
getCanEditLoadedThread: function() {
146
if (this._canEditLoadedThread === null) {
147
return false;
148
}
149
return this._canEditLoadedThread;
150
},
151
152
setMessagesRootCallback: function(callback) {
153
this._messagesRootCallback = callback;
154
return this;
155
},
156
157
setWillLoadThreadCallback: function(callback) {
158
this._willLoadThreadCallback = callback;
159
return this;
160
},
161
162
setDidLoadThreadCallback: function(callback) {
163
this._didLoadThreadCallback = callback;
164
return this;
165
},
166
167
setDidUpdateThreadCallback: function(callback) {
168
this._didUpdateThreadCallback = callback;
169
return this;
170
},
171
172
setWillSendMessageCallback: function(callback) {
173
this._willSendMessageCallback = callback;
174
return this;
175
},
176
177
setDidSendMessageCallback: function(callback) {
178
this._didSendMessageCallback = callback;
179
return this;
180
},
181
182
setWillUpdateWorkflowCallback: function(callback) {
183
this._willUpdateWorkflowCallback = callback;
184
return this;
185
},
186
187
setDidUpdateWorkflowCallback: function(callback) {
188
this._didUpdateWorkflowCallback = callback;
189
return this;
190
},
191
192
_getParams: function(base_params) {
193
if (this._latestTransactionID) {
194
base_params.latest_transaction_id = this._latestTransactionID;
195
}
196
return base_params;
197
},
198
199
start: function() {
200
201
this._transactionIDMap = {};
202
this._transactionCache = {};
203
204
JX.Stratcom.listen(
205
'aphlict-server-message',
206
null,
207
JX.bind(this, function(e) {
208
var message = e.getData();
209
210
if (message.type != 'message') {
211
// Not a message event.
212
return;
213
}
214
215
if (message.threadPHID != this._loadedThreadPHID) {
216
// Message event for some thread other than the visible one.
217
return;
218
}
219
220
if (message.messageID <= this._latestTransactionID) {
221
// Message event for something we already know about.
222
return;
223
}
224
225
// If this notification tells us about a message which is newer than
226
// the newest one we know to exist, update our latest knownID so we
227
// can properly update later.
228
if (this._updating &&
229
this._updating.threadPHID == this._loadedThreadPHID) {
230
if (message.messageID > this._updating.knownID) {
231
this._updating.knownID = message.messageID;
232
// We're currently updating, so wait for the update to complete.
233
// this.syncWorkflow has us covered in this case.
234
if (this._updating.active) {
235
return;
236
}
237
}
238
}
239
240
this._updateThread();
241
}));
242
243
// If we see a reconnect, always update the thread state.
244
JX.Stratcom.listen(
245
'aphlict-reconnect',
246
null,
247
JX.bind(this, function() {
248
if (!this._loadedThreadPHID) {
249
return;
250
}
251
252
this._updateThread();
253
}));
254
255
JX.Stratcom.listen(
256
'click',
257
'show-older-messages',
258
JX.bind(this, function(e) {
259
e.kill();
260
var data = e.getNodeData('show-older-messages');
261
262
var node = e.getNode('show-older-messages');
263
JX.DOM.setContent(node, 'Loading...');
264
JX.DOM.alterClass(
265
node,
266
'conpherence-show-more-messages-loading',
267
true);
268
269
new JX.Workflow(this._getMoreMessagesURI(), data)
270
.setHandler(JX.bind(this, function(r) {
271
this._deleteTransactionCaches(JX.Stratcom.getData(node).id);
272
JX.DOM.remove(node);
273
this._updateTransactions(r);
274
})).start();
275
}));
276
JX.Stratcom.listen(
277
'click',
278
'show-newer-messages',
279
JX.bind(this, function(e) {
280
e.kill();
281
var data = e.getNodeData('show-newer-messages');
282
var node = e.getNode('show-newer-messages');
283
JX.DOM.setContent(node, 'Loading...');
284
JX.DOM.alterClass(
285
node,
286
'conpherence-show-more-messages-loading',
287
true);
288
289
new JX.Workflow(this._getMoreMessagesURI(), data)
290
.setHandler(JX.bind(this, function(r) {
291
this._deleteTransactionCaches(JX.Stratcom.getData(node).id);
292
JX.DOM.remove(node);
293
this._updateTransactions(r);
294
})).start();
295
}));
296
},
297
298
_shouldUpdateDOM: function(r) {
299
if (this._updating &&
300
this._updating.threadPHID == this._loadedThreadPHID) {
301
302
if (r.non_update) {
303
return false;
304
}
305
306
// we have a different, more current update in progress so
307
// return early
308
if (r.latest_transaction_id < this._updating.knownID) {
309
return false;
310
}
311
}
312
return true;
313
},
314
315
_updateDOM: function(r) {
316
this._updateTransactions(r);
317
318
this._updating.knownID = r.latest_transaction_id;
319
this._latestTransactionID = r.latest_transaction_id;
320
321
JX.Leader.broadcast(
322
'conpherence.message.' + r.latest_transaction_id,
323
{
324
type: 'sound',
325
data: r.sound.receive
326
});
327
328
JX.Stratcom.invoke(
329
'conpherence-redraw-aphlict',
330
null,
331
r.aphlictDropdownData);
332
},
333
334
_updateTransactions: function(r) {
335
var new_transactions = JX.$H(r.transactions).getFragment().childNodes;
336
this._updateTransactionIDMap(new_transactions);
337
this._updateTransactionCache(new_transactions);
338
339
var transactions = this._getLoadedTransactions();
340
341
JX.DOM.setContent(this._messagesRootCallback(), transactions);
342
},
343
344
cacheCurrentTransactions: function() {
345
var root = this._messagesRootCallback();
346
var transactions = JX.DOM.scry(
347
root ,
348
'div',
349
'conpherence-transaction-view');
350
this._updateTransactionIDMap(transactions);
351
this._updateTransactionCache(transactions);
352
},
353
354
_updateThread: function() {
355
var params = this._getParams({
356
action: 'load',
357
});
358
359
var workflow = new JX.Workflow(this._getUpdateURI())
360
.setData(params)
361
.setHandler(JX.bind(this, function(r) {
362
if (this._shouldUpdateDOM(r)) {
363
this._updateDOM(r);
364
this._didUpdateThreadCallback(r);
365
}
366
}));
367
368
this.syncWorkflow(workflow, 'finally');
369
},
370
371
syncWorkflow: function(workflow, stage) {
372
this._updating = {
373
threadPHID: this._loadedThreadPHID,
374
knownID: this._latestTransactionID,
375
active: true
376
};
377
workflow.listen(stage, JX.bind(this, function() {
378
// TODO - do we need to handle if we switch threads somehow?
379
var need_sync = this._updating &&
380
(this._updating.knownID > this._latestTransactionID);
381
if (need_sync) {
382
return this._updateThread();
383
}
384
this._updating.active = false;
385
}));
386
workflow.start();
387
},
388
389
runUpdateWorkflowFromLink: function(link, params) {
390
params = this._getParams(params);
391
this._willUpdateWorkflowCallback();
392
var workflow = new JX.Workflow.newFromLink(link)
393
.setData(params)
394
.setHandler(JX.bind(this, function(r) {
395
if (this._shouldUpdateDOM(r)) {
396
this._updateDOM(r);
397
this._didUpdateWorkflowCallback(r);
398
}
399
}));
400
this.syncWorkflow(workflow, params.stage);
401
},
402
403
loadThreadByID: function(thread_id, force_reload) {
404
if (this.isThreadLoaded() &&
405
this.isThreadIDLoaded(thread_id) &&
406
!force_reload) {
407
return;
408
}
409
410
this._willLoadThreadCallback();
411
412
var params = {};
413
// We pick a thread from the server if not specified
414
if (thread_id) {
415
params.id = thread_id;
416
}
417
params = this._getParams(params);
418
419
var handler = JX.bind(this, function(r) {
420
var client = JX.Aphlict.getInstance();
421
if (client) {
422
var old_subs = client.getSubscriptions();
423
var new_subs = [];
424
for (var ii = 0; ii < old_subs.length; ii++) {
425
if (old_subs[ii] == this._loadedThreadPHID) {
426
continue;
427
} else {
428
new_subs.push(old_subs[ii]);
429
}
430
}
431
new_subs.push(r.threadPHID);
432
client.clearSubscriptions(client.getSubscriptions());
433
client.setSubscriptions(new_subs);
434
}
435
this._loadedThreadID = r.threadID;
436
this._loadedThreadPHID = r.threadPHID;
437
this._latestTransactionID = r.latestTransactionID;
438
this._canEditLoadedThread = r.canEdit;
439
440
JX.Stratcom.invoke(
441
'conpherence-redraw-aphlict',
442
null,
443
r.aphlictDropdownData);
444
445
this._didLoadThreadCallback(r);
446
this.cacheCurrentTransactions();
447
448
if (force_reload) {
449
JX.Stratcom.invoke('hashchange');
450
}
451
});
452
453
// should this be sync'd too?
454
new JX.Workflow(this.getLoadThreadURI())
455
.setData(params)
456
.setHandler(handler)
457
.start();
458
},
459
460
sendMessage: function(form, params) {
461
var inputs = JX.DOM.scry(form, 'input');
462
var block_empty = true;
463
for (var i = 0; i < inputs.length; i++) {
464
if (inputs[i].type != 'hidden') {
465
continue;
466
}
467
if (inputs[i].name == 'action' && inputs[i].value == 'join_room') {
468
block_empty = false;
469
continue;
470
}
471
}
472
// don't bother sending up text if there is nothing to submit
473
var textarea = JX.DOM.find(form, 'textarea');
474
if (block_empty && !textarea.value.length) {
475
return;
476
}
477
params = this._getParams(params);
478
479
var keep_enabled = true;
480
481
var workflow = JX.Workflow.newFromForm(form, params, keep_enabled)
482
.setHandler(JX.bind(this, function(r) {
483
if (this._shouldUpdateDOM(r)) {
484
this._updateDOM(r);
485
this._didSendMessageCallback(r);
486
} else if (r.non_update) {
487
this._didSendMessageCallback(r, true);
488
}
489
}));
490
this.syncWorkflow(workflow, 'finally');
491
textarea.value = '';
492
493
this._willSendMessageCallback();
494
},
495
496
handleDraftKeydown: function(e) {
497
var form = e.getNode('tag:form');
498
var data = e.getNodeData('tag:form');
499
500
if (!data.preview) {
501
data.preview = new JX.PhabricatorShapedRequest(
502
this._getUpdateURI(),
503
JX.bag,
504
JX.bind(this, function () {
505
var data = JX.DOM.convertFormToDictionary(form);
506
data.action = 'draft';
507
data = this._getParams(data);
508
return data;
509
}));
510
}
511
data.preview.trigger();
512
},
513
514
_getUpdateURI: function() {
515
return '/conpherence/update/' + this._loadedThreadID + '/';
516
},
517
518
_getMoreMessagesURI: function() {
519
return '/conpherence/' + this._loadedThreadID + '/';
520
}
521
},
522
523
statics: {
524
_instance: null,
525
526
getInstance: function() {
527
var self = JX.ConpherenceThreadManager;
528
if (!self._instance) {
529
return null;
530
}
531
return self._instance;
532
}
533
}
534
535
});
536
537