Path: blob/master/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js
12242 views
/**1* @provides conpherence-thread-manager2* @requires javelin-dom3* javelin-util4* javelin-stratcom5* javelin-install6* javelin-aphlict7* javelin-workflow8* javelin-router9* javelin-behavior-device10* javelin-vector11*/12JX.install('ConpherenceThreadManager', {1314construct : function() {15if (__DEV__) {16if (JX.ConpherenceThreadManager._instance) {17JX.$E('ConpherenceThreadManager object is a singleton.');18}19}20JX.ConpherenceThreadManager._instance = this;21return this;22},2324members: {25_loadThreadURI: null,26_loadedThreadID: null,27_loadedThreadPHID: null,28_latestTransactionID: null,29_transactionIDMap: null,30_transactionCache: null,31_canEditLoadedThread: null,32_updating: null,33_messagesRootCallback: JX.bag,34_willLoadThreadCallback: JX.bag,35_didLoadThreadCallback: JX.bag,36_didUpdateThreadCallback: JX.bag,37_willSendMessageCallback: JX.bag,38_didSendMessageCallback: JX.bag,39_willUpdateWorkflowCallback: JX.bag,40_didUpdateWorkflowCallback: JX.bag,4142setLoadThreadURI: function(uri) {43this._loadThreadURI = uri;44return this;45},4647getLoadThreadURI: function() {48return this._loadThreadURI;49},5051isThreadLoaded: function() {52return Boolean(this._loadedThreadID);53},5455isThreadIDLoaded: function(thread_id) {56return this._loadedThreadID == thread_id;57},5859getLoadedThreadID: function() {60return this._loadedThreadID;61},6263setLoadedThreadID: function(id) {64this._loadedThreadID = id;65return this;66},6768getLoadedThreadPHID: function() {69return this._loadedThreadPHID;70},7172setLoadedThreadPHID: function(phid) {73this._loadedThreadPHID = phid;74return this;75},7677getLatestTransactionID: function() {78return this._latestTransactionID;79},8081setLatestTransactionID: function(id) {82this._latestTransactionID = id;83return this;84},8586_updateTransactionIDMap: function(transactions) {87var loaded_id = this.getLoadedThreadID();88if (!this._transactionIDMap[loaded_id]) {89this._transactionIDMap[this._loadedThreadID] = {};90}91var loaded_transaction_ids = this._transactionIDMap[loaded_id];92var transaction;93for (var ii = 0; ii < transactions.length; ii++) {94transaction = transactions[ii];95loaded_transaction_ids[JX.Stratcom.getData(transaction).id] = 1;96}97this._transactionIDMap[this._loadedThreadID] = loaded_transaction_ids;98return this;99},100101_updateTransactionCache: function(transactions) {102var transaction;103for (var ii = 0; ii < transactions.length; ii++) {104transaction = transactions[ii];105this._transactionCache[JX.Stratcom.getData(transaction).id] =106transaction;107}108return this;109},110111_getLoadedTransactions: function() {112var loaded_id = this.getLoadedThreadID();113var loaded_tx_ids = JX.keys(this._transactionIDMap[loaded_id]);114loaded_tx_ids.sort(function (a, b) {115var x = parseFloat(a);116var y = parseFloat(b);117if (x > y) {118return 1;119}120if (x < y) {121return -1;122}123return 0;124});125var transactions = [];126for (var ii = 0; ii < loaded_tx_ids.length; ii++) {127transactions.push(this._transactionCache[loaded_tx_ids[ii]]);128}129return transactions;130},131132_deleteTransactionCaches: function(id) {133delete this._transactionCache[id];134delete this._transactionIDMap[this._loadedThreadID][id];135136return this;137},138139setCanEditLoadedThread: function(bool) {140this._canEditLoadedThread = bool;141return this;142},143144getCanEditLoadedThread: function() {145if (this._canEditLoadedThread === null) {146return false;147}148return this._canEditLoadedThread;149},150151setMessagesRootCallback: function(callback) {152this._messagesRootCallback = callback;153return this;154},155156setWillLoadThreadCallback: function(callback) {157this._willLoadThreadCallback = callback;158return this;159},160161setDidLoadThreadCallback: function(callback) {162this._didLoadThreadCallback = callback;163return this;164},165166setDidUpdateThreadCallback: function(callback) {167this._didUpdateThreadCallback = callback;168return this;169},170171setWillSendMessageCallback: function(callback) {172this._willSendMessageCallback = callback;173return this;174},175176setDidSendMessageCallback: function(callback) {177this._didSendMessageCallback = callback;178return this;179},180181setWillUpdateWorkflowCallback: function(callback) {182this._willUpdateWorkflowCallback = callback;183return this;184},185186setDidUpdateWorkflowCallback: function(callback) {187this._didUpdateWorkflowCallback = callback;188return this;189},190191_getParams: function(base_params) {192if (this._latestTransactionID) {193base_params.latest_transaction_id = this._latestTransactionID;194}195return base_params;196},197198start: function() {199200this._transactionIDMap = {};201this._transactionCache = {};202203JX.Stratcom.listen(204'aphlict-server-message',205null,206JX.bind(this, function(e) {207var message = e.getData();208209if (message.type != 'message') {210// Not a message event.211return;212}213214if (message.threadPHID != this._loadedThreadPHID) {215// Message event for some thread other than the visible one.216return;217}218219if (message.messageID <= this._latestTransactionID) {220// Message event for something we already know about.221return;222}223224// If this notification tells us about a message which is newer than225// the newest one we know to exist, update our latest knownID so we226// can properly update later.227if (this._updating &&228this._updating.threadPHID == this._loadedThreadPHID) {229if (message.messageID > this._updating.knownID) {230this._updating.knownID = message.messageID;231// We're currently updating, so wait for the update to complete.232// this.syncWorkflow has us covered in this case.233if (this._updating.active) {234return;235}236}237}238239this._updateThread();240}));241242// If we see a reconnect, always update the thread state.243JX.Stratcom.listen(244'aphlict-reconnect',245null,246JX.bind(this, function() {247if (!this._loadedThreadPHID) {248return;249}250251this._updateThread();252}));253254JX.Stratcom.listen(255'click',256'show-older-messages',257JX.bind(this, function(e) {258e.kill();259var data = e.getNodeData('show-older-messages');260261var node = e.getNode('show-older-messages');262JX.DOM.setContent(node, 'Loading...');263JX.DOM.alterClass(264node,265'conpherence-show-more-messages-loading',266true);267268new JX.Workflow(this._getMoreMessagesURI(), data)269.setHandler(JX.bind(this, function(r) {270this._deleteTransactionCaches(JX.Stratcom.getData(node).id);271JX.DOM.remove(node);272this._updateTransactions(r);273})).start();274}));275JX.Stratcom.listen(276'click',277'show-newer-messages',278JX.bind(this, function(e) {279e.kill();280var data = e.getNodeData('show-newer-messages');281var node = e.getNode('show-newer-messages');282JX.DOM.setContent(node, 'Loading...');283JX.DOM.alterClass(284node,285'conpherence-show-more-messages-loading',286true);287288new JX.Workflow(this._getMoreMessagesURI(), data)289.setHandler(JX.bind(this, function(r) {290this._deleteTransactionCaches(JX.Stratcom.getData(node).id);291JX.DOM.remove(node);292this._updateTransactions(r);293})).start();294}));295},296297_shouldUpdateDOM: function(r) {298if (this._updating &&299this._updating.threadPHID == this._loadedThreadPHID) {300301if (r.non_update) {302return false;303}304305// we have a different, more current update in progress so306// return early307if (r.latest_transaction_id < this._updating.knownID) {308return false;309}310}311return true;312},313314_updateDOM: function(r) {315this._updateTransactions(r);316317this._updating.knownID = r.latest_transaction_id;318this._latestTransactionID = r.latest_transaction_id;319320JX.Leader.broadcast(321'conpherence.message.' + r.latest_transaction_id,322{323type: 'sound',324data: r.sound.receive325});326327JX.Stratcom.invoke(328'conpherence-redraw-aphlict',329null,330r.aphlictDropdownData);331},332333_updateTransactions: function(r) {334var new_transactions = JX.$H(r.transactions).getFragment().childNodes;335this._updateTransactionIDMap(new_transactions);336this._updateTransactionCache(new_transactions);337338var transactions = this._getLoadedTransactions();339340JX.DOM.setContent(this._messagesRootCallback(), transactions);341},342343cacheCurrentTransactions: function() {344var root = this._messagesRootCallback();345var transactions = JX.DOM.scry(346root ,347'div',348'conpherence-transaction-view');349this._updateTransactionIDMap(transactions);350this._updateTransactionCache(transactions);351},352353_updateThread: function() {354var params = this._getParams({355action: 'load',356});357358var workflow = new JX.Workflow(this._getUpdateURI())359.setData(params)360.setHandler(JX.bind(this, function(r) {361if (this._shouldUpdateDOM(r)) {362this._updateDOM(r);363this._didUpdateThreadCallback(r);364}365}));366367this.syncWorkflow(workflow, 'finally');368},369370syncWorkflow: function(workflow, stage) {371this._updating = {372threadPHID: this._loadedThreadPHID,373knownID: this._latestTransactionID,374active: true375};376workflow.listen(stage, JX.bind(this, function() {377// TODO - do we need to handle if we switch threads somehow?378var need_sync = this._updating &&379(this._updating.knownID > this._latestTransactionID);380if (need_sync) {381return this._updateThread();382}383this._updating.active = false;384}));385workflow.start();386},387388runUpdateWorkflowFromLink: function(link, params) {389params = this._getParams(params);390this._willUpdateWorkflowCallback();391var workflow = new JX.Workflow.newFromLink(link)392.setData(params)393.setHandler(JX.bind(this, function(r) {394if (this._shouldUpdateDOM(r)) {395this._updateDOM(r);396this._didUpdateWorkflowCallback(r);397}398}));399this.syncWorkflow(workflow, params.stage);400},401402loadThreadByID: function(thread_id, force_reload) {403if (this.isThreadLoaded() &&404this.isThreadIDLoaded(thread_id) &&405!force_reload) {406return;407}408409this._willLoadThreadCallback();410411var params = {};412// We pick a thread from the server if not specified413if (thread_id) {414params.id = thread_id;415}416params = this._getParams(params);417418var handler = JX.bind(this, function(r) {419var client = JX.Aphlict.getInstance();420if (client) {421var old_subs = client.getSubscriptions();422var new_subs = [];423for (var ii = 0; ii < old_subs.length; ii++) {424if (old_subs[ii] == this._loadedThreadPHID) {425continue;426} else {427new_subs.push(old_subs[ii]);428}429}430new_subs.push(r.threadPHID);431client.clearSubscriptions(client.getSubscriptions());432client.setSubscriptions(new_subs);433}434this._loadedThreadID = r.threadID;435this._loadedThreadPHID = r.threadPHID;436this._latestTransactionID = r.latestTransactionID;437this._canEditLoadedThread = r.canEdit;438439JX.Stratcom.invoke(440'conpherence-redraw-aphlict',441null,442r.aphlictDropdownData);443444this._didLoadThreadCallback(r);445this.cacheCurrentTransactions();446447if (force_reload) {448JX.Stratcom.invoke('hashchange');449}450});451452// should this be sync'd too?453new JX.Workflow(this.getLoadThreadURI())454.setData(params)455.setHandler(handler)456.start();457},458459sendMessage: function(form, params) {460var inputs = JX.DOM.scry(form, 'input');461var block_empty = true;462for (var i = 0; i < inputs.length; i++) {463if (inputs[i].type != 'hidden') {464continue;465}466if (inputs[i].name == 'action' && inputs[i].value == 'join_room') {467block_empty = false;468continue;469}470}471// don't bother sending up text if there is nothing to submit472var textarea = JX.DOM.find(form, 'textarea');473if (block_empty && !textarea.value.length) {474return;475}476params = this._getParams(params);477478var keep_enabled = true;479480var workflow = JX.Workflow.newFromForm(form, params, keep_enabled)481.setHandler(JX.bind(this, function(r) {482if (this._shouldUpdateDOM(r)) {483this._updateDOM(r);484this._didSendMessageCallback(r);485} else if (r.non_update) {486this._didSendMessageCallback(r, true);487}488}));489this.syncWorkflow(workflow, 'finally');490textarea.value = '';491492this._willSendMessageCallback();493},494495handleDraftKeydown: function(e) {496var form = e.getNode('tag:form');497var data = e.getNodeData('tag:form');498499if (!data.preview) {500data.preview = new JX.PhabricatorShapedRequest(501this._getUpdateURI(),502JX.bag,503JX.bind(this, function () {504var data = JX.DOM.convertFormToDictionary(form);505data.action = 'draft';506data = this._getParams(data);507return data;508}));509}510data.preview.trigger();511},512513_getUpdateURI: function() {514return '/conpherence/update/' + this._loadedThreadID + '/';515},516517_getMoreMessagesURI: function() {518return '/conpherence/' + this._loadedThreadID + '/';519}520},521522statics: {523_instance: null,524525getInstance: function() {526var self = JX.ConpherenceThreadManager;527if (!self._instance) {528return null;529}530return self._instance;531}532}533534});535536537