Changeset View
Standalone View
webroot/rsrc/js/application/projects/WorkboardBoard.js
| Show First 20 Lines • Show All 123 Lines • ▼ Show 20 Lines | compareVectors: function(u_vec, v_vec) { | ||||
| } | } | ||||
| return 0; | return 0; | ||||
| }, | }, | ||||
| start: function() { | start: function() { | ||||
| this._setupDragHandlers(); | this._setupDragHandlers(); | ||||
| // TODO: This is temporary code to make it easier to debug this workflow | |||||
| // by pressing the "R" key. | |||||
| var on_reload = JX.bind(this, this._reloadCards); | |||||
| new JX.KeyboardShortcut('R', 'Reload Card State (Prototype)') | |||||
| .setHandler(on_reload) | |||||
| .register(); | |||||
amckinley: We're definitely going to get rid of this, right? https://xkcd.com/1172/ | |||||
| for (var k in this._columns) { | for (var k in this._columns) { | ||||
| this._columns[k].redraw(); | this._columns[k].redraw(); | ||||
| } | } | ||||
| }, | }, | ||||
| _buildColumns: function() { | _buildColumns: function() { | ||||
| var nodes = JX.DOM.scry(this.getRoot(), 'ul', 'project-column'); | var nodes = JX.DOM.scry(this.getRoot(), 'ul', 'project-column'); | ||||
| ▲ Show 20 Lines • Show All 406 Lines • ▼ Show 20 Lines | _revertCard: function(list, item, src_phid, dst_phid) { | ||||
| src_column.markForRedraw(); | src_column.markForRedraw(); | ||||
| dst_column.markForRedraw(); | dst_column.markForRedraw(); | ||||
| this._redrawColumns(); | this._redrawColumns(); | ||||
| list.unlock(); | list.unlock(); | ||||
| }, | }, | ||||
| _oncardupdate: function(list, src_phid, dst_phid, after_phid, response) { | _oncardupdate: function(list, src_phid, dst_phid, after_phid, response) { | ||||
| var src_column = this.getColumn(src_phid); | |||||
| var dst_column = this.getColumn(dst_phid); | |||||
| var card = src_column.removeCard(response.objectPHID); | |||||
| dst_column.addCard(card, after_phid); | |||||
| src_column.markForRedraw(); | |||||
| dst_column.markForRedraw(); | |||||
| this.updateCard(response); | this.updateCard(response); | ||||
| var sounds = response.sounds || []; | var sounds = response.sounds || []; | ||||
| for (var ii = 0; ii < sounds.length; ii++) { | for (var ii = 0; ii < sounds.length; ii++) { | ||||
| JX.Sound.queue(sounds[ii]); | JX.Sound.queue(sounds[ii]); | ||||
| } | } | ||||
| list.unlock(); | list.unlock(); | ||||
| }, | }, | ||||
| updateCard: function(response) { | updateCard: function(response) { | ||||
| var columns = this.getColumns(); | var columns = this.getColumns(); | ||||
| var column_phid; | |||||
| var phid = response.objectPHID; | var card_phid; | ||||
| var card_data; | |||||
| for (var add_phid in response.columnMaps) { | |||||
| var target_column = this.getColumn(add_phid); | // The server may send us a full or partial update for a card. If we've | ||||
| // received a full update, we're going to redraw the entire card and may | |||||
| // need to change which columns it appears in. | |||||
amckinleyUnsubmitted Not Done Inline ActionsCan any of this interact weirdly with policy? If a card already rendered by my browser becomes not-visible to me, could I engage in silly buggers to disclose information about the now-restricted task? I'm guessing "no" since we're doing viewer checks server-side in PhabricatorProjectBoardReloadController, but stale information on the task will presumably stick around until I reload the page. I can't really call this an attack because at best it just means I can see the state of an object as of the last time I was legitimately able to view it. Actually, won't the hidden card just stick around until I reload the page? If I can't see it, PhabricatorProjectBoardReloadController just won't return it in the reload result set, and it should stay where it is. amckinley: Can any of this interact weirdly with policy? If a card already rendered by my browser becomes… | |||||
epriestleyAuthorUnsubmitted Done Inline ActionsIt definitely won't disclose any information, since the Query on the server side won't pull it. You're right that we do end up with a ghosting issue though, see below... epriestley: It definitely won't disclose any information, since the `Query` on the server side won't pull… | |||||
| // For a partial update, we've just received supplemental sorting or | |||||
| // property information and do not need to perform a full redraw. | |||||
| // When we reload card state, edit a card, or move a card, we get a full | |||||
| // update for the card. | |||||
| // We we move a card in a column, we may get a partial update for other | |||||
amckinleyUnsubmitted Not Done Inline ActionsOui oui! amckinley: Oui oui! | |||||
| // visible cards in the column. | |||||
| // Figure out which columns each card now appears in. For cards that | |||||
| // have received a full update, we'll use this map to move them into | |||||
| // the correct columns. | |||||
| var update_map = {}; | |||||
| for (column_phid in response.columnMaps) { | |||||
| var target_column = this.getColumn(column_phid); | |||||
| if (!target_column) { | if (!target_column) { | ||||
| // If the column isn't visible, don't try to add a card to it. | // If the column isn't visible, don't try to add a card to it. | ||||
| continue; | continue; | ||||
| } | } | ||||
| target_column.newCard(phid); | var column_map = response.columnMaps[column_phid]; | ||||
| } | |||||
| var column_maps = response.columnMaps; | for (var ii = 0; ii < column_map.length; ii++) { | ||||
| var natural_column; | card_phid = column_map[ii]; | ||||
| for (var natural_phid in column_maps) { | if (!update_map[card_phid]) { | ||||
| natural_column = this.getColumn(natural_phid); | update_map[card_phid] = {}; | ||||
| if (!natural_column) { | } | ||||
| // Our view of the board may be out of date, so we might get back | update_map[card_phid][column_phid] = true; | ||||
| // information about columns that aren't visible. Just ignore the | |||||
| // position information for any columns we aren't displaying on the | |||||
| // client. | |||||
| continue; | |||||
| } | } | ||||
| natural_column.setNaturalOrder(column_maps[natural_phid]); | |||||
| } | } | ||||
| for (var card_phid in response.cards) { | // Process partial updates for cards. This is supplemental data which | ||||
| var card_data = response.cards[card_phid]; | // we can just merge in without any special handling. | ||||
| for (card_phid in response.cards) { | |||||
| card_data = response.cards[card_phid]; | |||||
| var card_template = this.getCardTemplate(card_phid); | var card_template = this.getCardTemplate(card_phid); | ||||
| if (card_data.nodeHTMLTemplate) { | if (card_data.nodeHTMLTemplate) { | ||||
| card_template.setNodeHTMLTemplate(card_data.nodeHTMLTemplate); | card_template.setNodeHTMLTemplate(card_data.nodeHTMLTemplate); | ||||
| } | } | ||||
| var order; | var order; | ||||
| for (order in card_data.vectors) { | for (order in card_data.vectors) { | ||||
| card_template.setSortVector(order, card_data.vectors[order]); | card_template.setSortVector(order, card_data.vectors[order]); | ||||
| } | } | ||||
| for (order in card_data.headers) { | for (order in card_data.headers) { | ||||
| card_template.setHeaderKey(order, card_data.headers[order]); | card_template.setHeaderKey(order, card_data.headers[order]); | ||||
| } | } | ||||
| for (var key in card_data.properties) { | for (var key in card_data.properties) { | ||||
| card_template.setObjectProperty(key, card_data.properties[key]); | card_template.setObjectProperty(key, card_data.properties[key]); | ||||
| } | } | ||||
| } | } | ||||
| var headers = response.headers; | |||||
| for (var jj = 0; jj < headers.length; jj++) { | |||||
| var header = headers[jj]; | |||||
| this.getHeaderTemplate(header.key) | // Process full updates for cards which we have a full update for. This | ||||
| .setOrder(header.order) | // may involve moving them between columns. | ||||
| .setNodeHTMLTemplate(header.template) | for (card_phid in response.cards) { | ||||
| .setVector(header.vector) | card_data = response.cards[card_phid]; | ||||
| .setEditProperties(header.editProperties); | |||||
| if (!card_data.update) { | |||||
| continue; | |||||
| } | } | ||||
| for (var column_phid in columns) { | for (column_phid in columns) { | ||||
| var column = columns[column_phid]; | var column = columns[column_phid]; | ||||
| var card = column.getCard(card_phid); | |||||
| var cards = column.getCards(); | if (card) { | ||||
| for (var object_phid in cards) { | card.redraw(); | ||||
| if (object_phid !== phid) { | column.markForRedraw(); | ||||
| continue; | |||||
| } | } | ||||
| var card = cards[object_phid]; | // Compare the server state to the client state, and add or remove | ||||
| card.redraw(); | // cards on the client as necessary to synchronize them. | ||||
| if (update_map[card_phid][column_phid]) { | |||||
| if (!card) { | |||||
| column.newCard(card_phid); | |||||
| column.markForRedraw(); | |||||
| } | |||||
| } else { | |||||
| if (card) { | |||||
| column.removeCard(card_phid); | |||||
amckinleyUnsubmitted Not Done Inline ActionsOh, re: my above, this will presumably remove cards from the UI if they weren't present in the result set. amckinley: Oh, re: my above, this will presumably remove cards from the UI if they weren't present in the… | |||||
epriestleyAuthorUnsubmitted Done Inline ActionsWe don't actually make it here since we're iterating over card_phid in response.cards, which no longer includes the deleted/hidden card! I'll tweak things here to check both all the cards the client knows about and the cards the server gave us information about. This also has a slightly tricky interaction with edit flows if you move the card off the board by changing projects, or by dropping it into a column which kicks it off the board (which is silly, but I think currently allowed). epriestley: We don't actually make it here since we're iterating over `card_phid in response.cards`, which… | |||||
| column.markForRedraw(); | column.markForRedraw(); | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| } | |||||
| var column_maps = response.columnMaps; | |||||
| var natural_column; | |||||
| for (var natural_phid in column_maps) { | |||||
| natural_column = this.getColumn(natural_phid); | |||||
| if (!natural_column) { | |||||
| // Our view of the board may be out of date, so we might get back | |||||
| // information about columns that aren't visible. Just ignore the | |||||
| // position information for any columns we aren't displaying on the | |||||
| // client. | |||||
| continue; | |||||
| } | |||||
| natural_column.setNaturalOrder(column_maps[natural_phid]); | |||||
| } | |||||
| var headers = response.headers; | |||||
| for (var jj = 0; jj < headers.length; jj++) { | |||||
| var header = headers[jj]; | |||||
| this.getHeaderTemplate(header.key) | |||||
| .setOrder(header.order) | |||||
| .setNodeHTMLTemplate(header.template) | |||||
| .setVector(header.vector) | |||||
| .setEditProperties(header.editProperties); | |||||
| } | |||||
| this._redrawColumns(); | this._redrawColumns(); | ||||
| }, | }, | ||||
| _redrawColumns: function() { | _redrawColumns: function() { | ||||
| var columns = this.getColumns(); | var columns = this.getColumns(); | ||||
| for (var k in columns) { | for (var k in columns) { | ||||
| if (columns[k].isMarkedForRedraw()) { | if (columns[k].isMarkedForRedraw()) { | ||||
| columns[k].redraw(); | columns[k].redraw(); | ||||
| } | } | ||||
| } | } | ||||
| }, | |||||
| _reloadCards: function() { | |||||
| var data = {}; | |||||
| var on_reload = JX.bind(this, this._onReloadResponse); | |||||
| new JX.Request(this.getController().getReloadURI(), on_reload) | |||||
| .setData(data) | |||||
| .send(); | |||||
| }, | |||||
| _onReloadResponse: function(response) { | |||||
| this.updateCard(response); | |||||
| } | } | ||||
| } | } | ||||
| }); | }); | ||||
We're definitely going to get rid of this, right? https://xkcd.com/1172/