From db69686927bdf1a68e5e0bd9f62a3779661810f8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 2 Jul 2019 10:33:13 -0700 Subject: [PATCH] Make pressing "R" on your keyboard reload the card state on workboards Summary: Depends on D20638. Ref T4900. This is an incremental step toward proper workboard updates. Currently, the client can mostly update its view because we do updates when you edit or move a card, and the client and server know how to send lists of card updates, so a lot of the work is already done. However, the code assumes we're only updating/redrawing one card at a time. Make the client accept and process multiple card updates. In future changes, I'll add versioning (so we only update cards that have actually changed), fix the "TODO" around ordering, and move toward actual Aphlict-based real-time updates. Test Plan: - Opened the same workboard in two windows. - Edited cards in one window, pressed "R" (capital letter, with no modifier keys) to reload the second window. - Saw edits and moves reflected accurately after sync, except for some special cases of header/order interaction (see "TODO"). Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T4900 Differential Revision: https://secure.phabricator.com/D20639 --- resources/celerity/map.php | 76 ++++----- src/__phutil_library_map__.php | 2 + .../PhabricatorProjectApplication.php | 2 + ...habricatorProjectBoardReloadController.php | 38 +++++ .../PhabricatorProjectBoardViewController.php | 1 + .../engine/PhabricatorBoardResponseEngine.php | 143 +++++++++++------ .../js/application/projects/WorkboardBoard.js | 148 ++++++++++++------ .../projects/WorkboardController.js | 1 + .../projects/behavior-project-boards.js | 1 + 9 files changed, 283 insertions(+), 129 deletions(-) create mode 100644 src/applications/project/controller/PhabricatorProjectBoardReloadController.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 775414b101..87c5ad5b85 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -412,16 +412,16 @@ return array( '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' => '34c2f539', '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 @@ return array( '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 @@ return array( 'javelin-view-renderer' => '9aae2b66', 'javelin-view-visitor' => '308f9fe4', 'javelin-websocket' => 'fdc13e4e', - 'javelin-workboard-board' => '44f71637', + 'javelin-workboard-board' => '34c2f539', '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', @@ -1202,6 +1202,18 @@ return array( 'javelin-install', 'javelin-util', ), + '34c2f539' => 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', + ), '34c53422' => array( 'javelin-behavior', 'javelin-dom', @@ -1264,16 +1276,6 @@ return array( '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', @@ -1294,18 +1296,6 @@ return array( '43bc9360' => array( 'javelin-install', ), - '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', @@ -1424,6 +1414,16 @@ return array( '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 +1852,6 @@ return array( '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 +1942,16 @@ return array( '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', + ), 'bde53589' => array( 'phui-inline-comment-view-css', ), diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 963666fe82..095e7231fb 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4163,6 +4163,7 @@ phutil_register_library_map(array( '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 @@ phutil_register_library_map(array( '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 index 1c04e0c2f9..af4a06fcc4 100644 --- a/src/applications/project/application/PhabricatorProjectApplication.php +++ b/src/applications/project/application/PhabricatorProjectApplication.php @@ -99,6 +99,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { => 'PhabricatorProjectBoardDefaultController', 'filter/(?:query/(?P[^/]+)/)?' => 'PhabricatorProjectBoardFilterController', + 'reload/' + => 'PhabricatorProjectBoardReloadController', ), 'column/' => array( 'remove/(?P\d+)/' => diff --git a/src/applications/project/controller/PhabricatorProjectBoardReloadController.php b/src/applications/project/controller/PhabricatorProjectBoardReloadController.php new file mode 100644 index 0000000000..43752e0cd5 --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectBoardReloadController.php @@ -0,0 +1,38 @@ +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 index 4979f9e78e..13a75c5a73 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -286,6 +286,7 @@ final class PhabricatorProjectBoardViewController '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 index f22254e43a..dbd2e31a3d 100644 --- a/src/applications/project/engine/PhabricatorBoardResponseEngine.php +++ b/src/applications/project/engine/PhabricatorBoardResponseEngine.php @@ -6,6 +6,7 @@ final class PhabricatorBoardResponseEngine extends Phobject { private $boardPHID; private $objectPHID; private $visiblePHIDs; + private $updatePHIDs = array(); private $ordering; private $sounds; @@ -45,6 +46,15 @@ final class PhabricatorBoardResponseEngine extends Phobject { 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 @@ final class PhabricatorBoardResponseEngine extends Phobject { // 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 @@ final class PhabricatorBoardResponseEngine extends Phobject { $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,11 @@ final class PhabricatorBoardResponseEngine extends Phobject { $card['properties'] = self::newTaskProperties($object); } - if ($card_phid === $object_phid) { - $card['nodeHTMLTemplate'] = hsprintf('%s', $template); + if (isset($templates[$card_phid])) { + $card['nodeHTMLTemplate'] = hsprintf('%s', $templates[$card_phid]); + $card['update'] = true; + } else { + $card['update'] = false; } $card['vectors'] = (object)$card['vectors']; @@ -156,7 +165,6 @@ final class PhabricatorBoardResponseEngine extends Phobject { } $payload = array( - 'objectPHID' => $object_phid, 'columnMaps' => $natural, 'cards' => $cards, 'headers' => $headers, @@ -176,22 +184,6 @@ final class PhabricatorBoardResponseEngine extends Phobject { ); } - 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 +203,67 @@ final class PhabricatorBoardResponseEngine extends Phobject { } 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 index 64a5d64b7e..ba015f592d 100644 --- a/webroot/rsrc/js/application/projects/WorkboardBoard.js +++ b/webroot/rsrc/js/application/projects/WorkboardBoard.js @@ -129,6 +129,13 @@ JX.install('WorkboardBoard', { 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 @@ JX.install('WorkboardBoard', { }, _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 @@ JX.install('WorkboardBoard', { updateCard: function(response) { var columns = this.getColumns(); + var column_phid; + var card_phid; + var card_data; - var phid = response.objectPHID; + // 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. - for (var add_phid in response.columnMaps) { - var target_column = this.getColumn(add_phid); + // 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. + + // Ween 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 @@ JX.install('WorkboardBoard', { } } + + // 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 @@ JX.install('WorkboardBoard', { .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 @@ JX.install('WorkboardBoard', { 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 index 8fe88eb50c..da5d177bb9 100644 --- a/webroot/rsrc/js/application/projects/WorkboardController.js +++ b/webroot/rsrc/js/application/projects/WorkboardController.js @@ -21,6 +21,7 @@ JX.install('WorkboardController', { 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 index bba6db7a49..26e5d90f8e 100644 --- a/webroot/rsrc/js/application/projects/behavior-project-boards.js +++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js @@ -71,6 +71,7 @@ JX.behavior('project-boards', function(config, statics) { .setUploadURI(config.uploadURI) .setCoverURI(config.coverURI) .setMoveURI(config.moveURI) + .setReloadURI(config.reloadURI) .setChunkThreshold(config.chunkThreshold) .start(); }