Page MenuHomePhabricator

D20639.id49225.diff
No OneTemporary

D20639.id49225.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -412,16 +412,16 @@
'rsrc/js/application/phortune/phortune-credit-card-form.js' => 'd12d214f',
'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '9347f172',
- 'rsrc/js/application/projects/WorkboardBoard.js' => '44f71637',
+ 'rsrc/js/application/projects/WorkboardBoard.js' => 'bdb99902',
'rsrc/js/application/projects/WorkboardCard.js' => '0392a5d8',
'rsrc/js/application/projects/WorkboardCardTemplate.js' => '2a61f8d4',
'rsrc/js/application/projects/WorkboardColumn.js' => 'c3d24e63',
- 'rsrc/js/application/projects/WorkboardController.js' => '42c7a5a7',
+ 'rsrc/js/application/projects/WorkboardController.js' => 'b9d0c2f3',
'rsrc/js/application/projects/WorkboardDropEffect.js' => '8e0aa661',
'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d',
'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b',
'rsrc/js/application/projects/WorkboardOrderTemplate.js' => '03e8891f',
- 'rsrc/js/application/projects/behavior-project-boards.js' => 'aad45445',
+ 'rsrc/js/application/projects/behavior-project-boards.js' => '58cb6a88',
'rsrc/js/application/projects/behavior-project-create.js' => '34c53422',
'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9',
'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68',
@@ -667,7 +667,7 @@
'javelin-behavior-phuix-example' => 'c2c500a7',
'javelin-behavior-policy-control' => '0eaa33a9',
'javelin-behavior-policy-rule-editor' => '9347f172',
- 'javelin-behavior-project-boards' => 'aad45445',
+ 'javelin-behavior-project-boards' => '58cb6a88',
'javelin-behavior-project-create' => '34c53422',
'javelin-behavior-quicksand-blacklist' => '5a6f6a06',
'javelin-behavior-read-only-warning' => 'b9109f8f',
@@ -743,11 +743,11 @@
'javelin-view-renderer' => '9aae2b66',
'javelin-view-visitor' => '308f9fe4',
'javelin-websocket' => 'fdc13e4e',
- 'javelin-workboard-board' => '44f71637',
+ 'javelin-workboard-board' => 'bdb99902',
'javelin-workboard-card' => '0392a5d8',
'javelin-workboard-card-template' => '2a61f8d4',
'javelin-workboard-column' => 'c3d24e63',
- 'javelin-workboard-controller' => '42c7a5a7',
+ 'javelin-workboard-controller' => 'b9d0c2f3',
'javelin-workboard-drop-effect' => '8e0aa661',
'javelin-workboard-header' => '111bfd2d',
'javelin-workboard-header-template' => 'ebe83a6b',
@@ -1264,16 +1264,6 @@
'4234f572' => array(
'syntax-default-css',
),
- '42c7a5a7' => array(
- 'javelin-install',
- 'javelin-dom',
- 'javelin-util',
- 'javelin-vector',
- 'javelin-stratcom',
- 'javelin-workflow',
- 'phabricator-drag-and-drop-file-upload',
- 'javelin-workboard-board',
- ),
'4370900d' => array(
'javelin-install',
'javelin-util',
@@ -1305,18 +1295,6 @@
'javelin-uri',
'javelin-routable',
),
- '44f71637' => array(
- 'javelin-install',
- 'javelin-dom',
- 'javelin-util',
- 'javelin-stratcom',
- 'javelin-workflow',
- 'phabricator-draggable-list',
- 'javelin-workboard-column',
- 'javelin-workboard-header-template',
- 'javelin-workboard-card-template',
- 'javelin-workboard-order-template',
- ),
'46116c01' => array(
'javelin-request',
'javelin-behavior',
@@ -1435,6 +1413,16 @@
'javelin-vector',
'javelin-typeahead-static-source',
),
+ '58cb6a88' => array(
+ 'javelin-behavior',
+ 'javelin-dom',
+ 'javelin-util',
+ 'javelin-vector',
+ 'javelin-stratcom',
+ 'javelin-workflow',
+ 'javelin-workboard-controller',
+ 'javelin-workboard-drop-effect',
+ ),
'5902260c' => array(
'javelin-util',
'javelin-magical-init',
@@ -1852,16 +1840,6 @@
'javelin-dom',
'javelin-util',
),
- 'aad45445' => array(
- 'javelin-behavior',
- 'javelin-dom',
- 'javelin-util',
- 'javelin-vector',
- 'javelin-stratcom',
- 'javelin-workflow',
- 'javelin-workboard-controller',
- 'javelin-workboard-drop-effect',
- ),
'ab85e184' => array(
'javelin-install',
'javelin-dom',
@@ -1952,6 +1930,28 @@
'javelin-uri',
'phabricator-notification',
),
+ 'b9d0c2f3' => array(
+ 'javelin-install',
+ 'javelin-dom',
+ 'javelin-util',
+ 'javelin-vector',
+ 'javelin-stratcom',
+ 'javelin-workflow',
+ 'phabricator-drag-and-drop-file-upload',
+ 'javelin-workboard-board',
+ ),
+ 'bdb99902' => array(
+ 'javelin-install',
+ 'javelin-dom',
+ 'javelin-util',
+ 'javelin-stratcom',
+ 'javelin-workflow',
+ 'phabricator-draggable-list',
+ 'javelin-workboard-column',
+ 'javelin-workboard-header-template',
+ 'javelin-workboard-card-template',
+ 'javelin-workboard-order-template',
+ ),
'bde53589' => array(
'phui-inline-comment-view-css',
),
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -4163,6 +4163,7 @@
'PhabricatorProjectBoardFilterController' => 'applications/project/controller/PhabricatorProjectBoardFilterController.php',
'PhabricatorProjectBoardImportController' => 'applications/project/controller/PhabricatorProjectBoardImportController.php',
'PhabricatorProjectBoardManageController' => 'applications/project/controller/PhabricatorProjectBoardManageController.php',
+ 'PhabricatorProjectBoardReloadController' => 'applications/project/controller/PhabricatorProjectBoardReloadController.php',
'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php',
'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php',
'PhabricatorProjectBuiltinsExample' => 'applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php',
@@ -10431,6 +10432,7 @@
'PhabricatorProjectBoardFilterController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardImportController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardManageController' => 'PhabricatorProjectBoardController',
+ 'PhabricatorProjectBoardReloadController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBuiltinsExample' => 'PhabricatorUIExample',
diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php
--- a/src/applications/project/application/PhabricatorProjectApplication.php
+++ b/src/applications/project/application/PhabricatorProjectApplication.php
@@ -99,6 +99,8 @@
=> 'PhabricatorProjectBoardDefaultController',
'filter/(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorProjectBoardFilterController',
+ 'reload/'
+ => 'PhabricatorProjectBoardReloadController',
),
'column/' => array(
'remove/(?P<id>\d+)/' =>
diff --git a/src/applications/project/controller/PhabricatorProjectBoardReloadController.php b/src/applications/project/controller/PhabricatorProjectBoardReloadController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/controller/PhabricatorProjectBoardReloadController.php
@@ -0,0 +1,38 @@
+<?php
+
+final class PhabricatorProjectBoardReloadController
+ extends PhabricatorProjectBoardController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+
+ $response = $this->loadProject();
+ if ($response) {
+ return $response;
+ }
+
+ $project = $this->getProject();
+ $state = $this->getViewState();
+ $board_uri = $state->newWorkboardURI();
+
+ $layout_engine = $state->getLayoutEngine();
+
+ $board_phid = $project->getPHID();
+
+ $objects = $state->getObjects();
+ $object_phids = mpull($objects, 'getPHID');
+
+ $engine = id(new PhabricatorBoardResponseEngine())
+ ->setViewer($viewer)
+ ->setBoardPHID($board_phid)
+ ->setUpdatePHIDs($object_phids);
+
+ // TODO: We don't currently process "order" properly. If a user is viewing
+ // a board grouped by "Owner", and another user changes a task to be owned
+ // by a user who currently owns nothing on the board, the new header won't
+ // generate correctly if the first user presses "R".
+
+ return $engine->buildResponse();
+ }
+
+}
diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
--- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php
+++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
@@ -286,6 +286,7 @@
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
'uploadURI' => '/file/dropupload/',
'coverURI' => $this->getApplicationURI('cover/'),
+ 'reloadURI' => phutil_string_cast($state->newWorkboardURI('reload/')),
'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(),
'pointsEnabled' => ManiphestTaskPoints::getIsEnabled(),
diff --git a/src/applications/project/engine/PhabricatorBoardResponseEngine.php b/src/applications/project/engine/PhabricatorBoardResponseEngine.php
--- a/src/applications/project/engine/PhabricatorBoardResponseEngine.php
+++ b/src/applications/project/engine/PhabricatorBoardResponseEngine.php
@@ -6,6 +6,7 @@
private $boardPHID;
private $objectPHID;
private $visiblePHIDs;
+ private $updatePHIDs = array();
private $ordering;
private $sounds;
@@ -45,6 +46,15 @@
return $this->visiblePHIDs;
}
+ public function setUpdatePHIDs(array $update_phids) {
+ $this->updatePHIDs = $update_phids;
+ return $this;
+ }
+
+ public function getUpdatePHIDs() {
+ return $this->updatePHIDs;
+ }
+
public function setOrdering(PhabricatorProjectColumnOrder $ordering) {
$this->ordering = $ordering;
return $this;
@@ -71,36 +81,41 @@
// Load all the other tasks that are visible in the affected columns and
// perform layout for them.
- $visible_phids = $this->getAllVisiblePHIDs();
+ $all_phids = $this->getAllVisiblePHIDs();
$layout_engine = id(new PhabricatorBoardLayoutEngine())
->setViewer($viewer)
->setBoardPHIDs(array($board_phid))
- ->setObjectPHIDs($visible_phids)
+ ->setObjectPHIDs($all_phids)
->executeLayout();
- $object_columns = $layout_engine->getObjectColumns(
- $board_phid,
- $object_phid);
-
$natural = array();
- foreach ($object_columns as $column_phid => $column) {
+
+ $update_phids = $this->getAllUpdatePHIDs();
+ $update_columns = array();
+ foreach ($update_phids as $update_phid) {
+ $update_columns += $layout_engine->getObjectColumns(
+ $board_phid,
+ $update_phid);
+ }
+
+ foreach ($update_columns as $column_phid => $column) {
$column_object_phids = $layout_engine->getColumnObjectPHIDs(
$board_phid,
$column_phid);
$natural[$column_phid] = array_values($column_object_phids);
}
- $all_visible = id(new ManiphestTaskQuery())
+ $all_objects = id(new ManiphestTaskQuery())
->setViewer($viewer)
- ->withPHIDs($visible_phids)
+ ->withPHIDs($all_phids)
->execute();
- $all_visible = mpull($all_visible, null, 'getPHID');
+ $all_objects = mpull($all_objects, null, 'getPHID');
if ($ordering) {
- $vectors = $ordering->getSortVectorsForObjects($all_visible);
- $header_keys = $ordering->getHeaderKeysForObjects($all_visible);
- $headers = $ordering->getHeadersForObjects($all_visible);
+ $vectors = $ordering->getSortVectorsForObjects($all_objects);
+ $header_keys = $ordering->getHeaderKeysForObjects($all_objects);
+ $headers = $ordering->getHeadersForObjects($all_objects);
$headers = mpull($headers, 'toDictionary');
} else {
$vectors = array();
@@ -108,19 +123,10 @@
$headers = array();
}
- $object = id(new ManiphestTaskQuery())
- ->setViewer($viewer)
- ->withPHIDs(array($object_phid))
- ->needProjectPHIDs(true)
- ->executeOne();
- if (!$object) {
- return new Aphront404Response();
- }
-
- $template = $this->buildTemplate($object);
+ $templates = $this->newCardTemplates();
$cards = array();
- foreach ($all_visible as $card_phid => $object) {
+ foreach ($all_objects as $card_phid => $object) {
$card = array(
'vectors' => array(),
'headers' => array(),
@@ -144,8 +150,13 @@
$card['properties'] = self::newTaskProperties($object);
}
- if ($card_phid === $object_phid) {
- $card['nodeHTMLTemplate'] = hsprintf('%s', $template);
+ if (isset($templates[$card_phid])) {
+ phlog("full up: $card_phid");
+
+ $card['nodeHTMLTemplate'] = hsprintf('%s', $templates[$card_phid]);
+ $card['update'] = true;
+ } else {
+ $card['update'] = false;
}
$card['vectors'] = (object)$card['vectors'];
@@ -156,7 +167,6 @@
}
$payload = array(
- 'objectPHID' => $object_phid,
'columnMaps' => $natural,
'cards' => $cards,
'headers' => $headers,
@@ -176,22 +186,6 @@
);
}
- private function buildTemplate($object) {
- $viewer = $this->getViewer();
- $object_phid = $this->getObjectPHID();
-
- $excluded_phids = $this->loadExcludedProjectPHIDs();
-
- $rendering_engine = id(new PhabricatorBoardRenderingEngine())
- ->setViewer($viewer)
- ->setObjects(array($object))
- ->setExcludedProjectPHIDs($excluded_phids);
-
- $card = $rendering_engine->renderCard($object_phid);
-
- return hsprintf('%s', $card->getItem());
- }
-
private function loadExcludedProjectPHIDs() {
$viewer = $this->getViewer();
$board_phid = $this->getBoardPHID();
@@ -211,10 +205,67 @@
}
private function getAllVisiblePHIDs() {
- $visible_phids = $this->getVisiblePHIDs();
- $visible_phids[] = $this->getObjectPHID();
- $visible_phids = array_fuse($visible_phids);
- return $visible_phids;
+ $phids = $this->getAllUpdatePHIDs();
+
+ foreach ($this->getVisiblePHIDs() as $phid) {
+ $phids[] = $phid;
+ }
+
+ $phids = array_fuse($phids);
+
+ return $phids;
+ }
+
+ private function getAllUpdatePHIDs() {
+ $phids = $this->getUpdatePHIDs();
+
+ $object_phid = $this->getObjectPHID();
+ if ($object_phid) {
+ $phids[] = $object_phid;
+ }
+
+ $phids = array_fuse($phids);
+
+ return $phids;
+ }
+
+ private function newCardTemplates() {
+ $viewer = $this->getViewer();
+
+ $update_phids = $this->getAllUpdatePHIDs();
+ if (!$update_phids) {
+ return array();
+ }
+
+ $objects = id(new ManiphestTaskQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($update_phids)
+ ->needProjectPHIDs(true)
+ ->execute();
+
+ if (!$objects) {
+ return array();
+ }
+
+ $excluded_phids = $this->loadExcludedProjectPHIDs();
+
+ $rendering_engine = id(new PhabricatorBoardRenderingEngine())
+ ->setViewer($viewer)
+ ->setObjects($objects)
+ ->setExcludedProjectPHIDs($excluded_phids);
+
+ $templates = array();
+ foreach ($objects as $object) {
+ $object_phid = $object->getPHID();
+
+ $card = $rendering_engine->renderCard($object_phid);
+ $item = $card->getItem();
+ $template = hsprintf('%s', $item);
+
+ $templates[$object_phid] = $template;
+ }
+
+ return $templates;
}
}
diff --git a/webroot/rsrc/js/application/projects/WorkboardBoard.js b/webroot/rsrc/js/application/projects/WorkboardBoard.js
--- a/webroot/rsrc/js/application/projects/WorkboardBoard.js
+++ b/webroot/rsrc/js/application/projects/WorkboardBoard.js
@@ -129,6 +129,13 @@
start: function() {
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();
+
for (var k in this._columns) {
this._columns[k].redraw();
}
@@ -551,15 +558,6 @@
},
_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);
var sounds = response.sounds || [];
@@ -572,37 +570,51 @@
updateCard: function(response) {
var columns = this.getColumns();
+ var column_phid;
+ var card_phid;
+ var card_data;
+
+ // 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.
- var phid = response.objectPHID;
+ // For a partial update, we've just received supplemental sorting or
+ // property information and do not need to perform a full redraw.
- for (var add_phid in response.columnMaps) {
- var target_column = this.getColumn(add_phid);
+ // 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
+ // 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 the column isn't visible, don't try to add a card to it.
continue;
}
- target_column.newCard(phid);
- }
+ var column_map = response.columnMaps[column_phid];
- 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;
+ for (var ii = 0; ii < column_map.length; ii++) {
+ card_phid = column_map[ii];
+ if (!update_map[card_phid]) {
+ update_map[card_phid] = {};
+ }
+ update_map[card_phid][column_phid] = true;
}
-
- natural_column.setNaturalOrder(column_maps[natural_phid]);
}
- for (var card_phid in response.cards) {
- var card_data = response.cards[card_phid];
+ // Process partial updates for cards. This is supplemental data which
+ // 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);
if (card_data.nodeHTMLTemplate) {
@@ -623,6 +635,57 @@
}
}
+
+ // Process full updates for cards which we have a full update for. This
+ // may involve moving them between columns.
+ for (card_phid in response.cards) {
+ card_data = response.cards[card_phid];
+
+ if (!card_data.update) {
+ continue;
+ }
+
+ for (column_phid in columns) {
+ var column = columns[column_phid];
+ var card = column.getCard(card_phid);
+
+ if (card) {
+ card.redraw();
+ column.markForRedraw();
+ }
+
+ // Compare the server state to the client state, and add or remove
+ // 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);
+ 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];
@@ -634,22 +697,6 @@
.setEditProperties(header.editProperties);
}
- for (var column_phid in columns) {
- var column = columns[column_phid];
-
- var cards = column.getCards();
- for (var object_phid in cards) {
- if (object_phid !== phid) {
- continue;
- }
-
- var card = cards[object_phid];
- card.redraw();
-
- column.markForRedraw();
- }
- }
-
this._redrawColumns();
},
@@ -660,6 +707,19 @@
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);
}
}
diff --git a/webroot/rsrc/js/application/projects/WorkboardController.js b/webroot/rsrc/js/application/projects/WorkboardController.js
--- a/webroot/rsrc/js/application/projects/WorkboardController.js
+++ b/webroot/rsrc/js/application/projects/WorkboardController.js
@@ -21,6 +21,7 @@
uploadURI: null,
coverURI: null,
moveURI: null,
+ reloadURI: null,
chunkThreshold: null
},
diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js
--- a/webroot/rsrc/js/application/projects/behavior-project-boards.js
+++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js
@@ -71,6 +71,7 @@
.setUploadURI(config.uploadURI)
.setCoverURI(config.coverURI)
.setMoveURI(config.moveURI)
+ .setReloadURI(config.reloadURI)
.setChunkThreshold(config.chunkThreshold)
.start();
}

File Metadata

Mime Type
text/plain
Expires
Mon, Mar 24, 3:54 PM (1 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7717223
Default Alt Text
D20639.id49225.diff (23 KB)

Event Timeline