diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -424,6 +424,7 @@ 'rsrc/js/application/releeph/releeph-request-state-change.js' => 'a0b57eb8', 'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f', 'rsrc/js/application/repository/repository-crossreference.js' => 'e5339c43', + 'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e2e0a072', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08', 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f', 'rsrc/js/application/transactions/behavior-comment-actions.js' => '1f2fcaf8', @@ -663,6 +664,7 @@ 'javelin-behavior-remarkup-preview' => '4b700e9e', 'javelin-behavior-reorder-applications' => '76b9fc3e', 'javelin-behavior-reorder-columns' => 'e1d25dfb', + 'javelin-behavior-reorder-profile-menu-items' => 'e2e0a072', 'javelin-behavior-repository-crossreference' => 'e5339c43', 'javelin-behavior-scrollbar' => '834a1173', 'javelin-behavior-search-reorder-queries' => 'e9581f08', @@ -1933,6 +1935,13 @@ 'e292eaf4' => array( 'javelin-install', ), + 'e2e0a072' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-dom', + 'phabricator-draggable-list', + ), 'e379b58e' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -642,6 +642,7 @@ '(?Pview)/(?P[^/]+)/' => $controller, '(?Phide)/(?P[^/]+)/' => $controller, '(?Pconfigure)/' => $controller, + '(?Preorder)/' => $controller, '(?Pedit)/'.$edit_route => $controller, '(?Pnew)/(?[^/]+)/'.$edit_route => $controller, '(?Pbuiltin)/(?[^/]+)/'.$edit_route diff --git a/src/applications/search/editor/PhabricatorProfilePanelEditor.php b/src/applications/search/editor/PhabricatorProfilePanelEditor.php --- a/src/applications/search/editor/PhabricatorProfilePanelEditor.php +++ b/src/applications/search/editor/PhabricatorProfilePanelEditor.php @@ -15,6 +15,7 @@ $types = parent::getTransactionTypes(); $types[] = PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY; + $types[] = PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER; return $types; } @@ -27,6 +28,8 @@ case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY: $key = $xaction->getMetadataValue('property.key'); return $object->getPanelProperty($key, null); + case PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER: + return $object->getPanelOrder(); } } @@ -37,6 +40,8 @@ switch ($xaction->getTransactionType()) { case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY: return $xaction->getNewValue(); + case PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER: + return (int)$xaction->getNewValue(); } } @@ -50,6 +55,9 @@ $value = $xaction->getNewValue(); $object->setPanelProperty($key, $value); return; + case PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER: + $object->setPanelOrder($xaction->getNewValue()); + return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -61,6 +69,7 @@ switch ($xaction->getTransactionType()) { case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY: + case PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER: return; } diff --git a/src/applications/search/engine/PhabricatorProfilePanelEngine.php b/src/applications/search/engine/PhabricatorProfilePanelEngine.php --- a/src/applications/search/engine/PhabricatorProfilePanelEngine.php +++ b/src/applications/search/engine/PhabricatorProfilePanelEngine.php @@ -91,6 +91,9 @@ $content = $this->buildPanelConfigureContent($panel_list); $crumbs->addTextCrumb(pht('Configure Menu')); break; + case 'reorder': + $content = $this->buildPanelReorderContent($panel_list); + break; case 'new': $panel_key = $request->getURIData('panelKey'); $content = $this->buildPanelNewContent($panel_key); @@ -204,6 +207,8 @@ $impl->setViewer($viewer); } + $panels = msort($panels, 'getSortKey'); + // Normalize keys since callers shouldn't rely on this array being // partially keyed. $panels = array_values($panels); @@ -305,6 +310,79 @@ return "/project/{$id}/panel/{$path}"; } + private function buildPanelReorderContent(array $panels) { + $viewer = $this->getViewer(); + $object = $this->getProfileObject(); + + PhabricatorPolicyFilter::requireCapability( + $viewer, + $object, + PhabricatorPolicyCapability::CAN_EDIT); + + $controller = $this->getController(); + $request = $controller->getRequest(); + + $request->validateCSRF(); + + $order = $request->getStrList('order'); + + $by_builtin = array(); + $by_id = array(); + + foreach ($panels as $key => $panel) { + $id = $panel->getID(); + if ($id) { + $by_id[$id] = $key; + continue; + } + + $builtin_key = $panel->getBuiltinKey(); + if ($builtin_key) { + $by_builtin[$builtin_key] = $key; + continue; + } + } + + $key_order = array(); + foreach ($order as $order_item) { + if (isset($by_id[$order_item])) { + $key_order[] = $by_id[$order_item]; + continue; + } + if (isset($by_builtin[$order_item])) { + $key_order[] = $by_builtin[$order_item]; + continue; + } + } + + $panels = array_select_keys($panels, $key_order) + $panels; + + $type_order = + PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER; + + $order = 1; + foreach ($panels as $panel) { + $xactions = array(); + + $xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction()) + ->setTransactionType($type_order) + ->setNewValue($order); + + $editor = id(new PhabricatorProfilePanelEditor()) + ->setContentSourceFromRequest($request) + ->setActor($viewer) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->applyTransactions($panel, $xactions); + + $order++; + } + + return id(new AphrontRedirectResponse()) + ->setURI($this->getConfigureURI()); + } + + private function buildPanelConfigureContent(array $panels) { $viewer = $this->getViewer(); $object = $this->getProfileObject(); @@ -314,7 +392,18 @@ $object, PhabricatorPolicyCapability::CAN_EDIT); - $list = new PHUIObjectItemListView(); + $list_id = celerity_generate_unique_node_id(); + + Javelin::initBehavior( + 'reorder-profile-menu-items', + array( + 'listID' => $list_id, + 'orderURI' => $this->getPanelURI('reorder/'), + )); + + $list = id(new PHUIObjectItemListView()) + ->setID($list_id); + foreach ($panels as $panel) { $id = $panel->getID(); $builtin_key = $panel->getBuiltinKey(); @@ -336,6 +425,14 @@ $item->addAttribute($type); if ($can_edit) { + $item + ->setGrippable(true) + ->addSigil('profile-menu-item') + ->setMetadata( + array( + 'key' => nonempty($id, $builtin_key), + )); + if ($id) { $item->setHref($this->getPanelURI("edit/{$id}/")); } else { diff --git a/src/applications/search/storage/PhabricatorProfilePanelConfiguration.php b/src/applications/search/storage/PhabricatorProfilePanelConfiguration.php --- a/src/applications/search/storage/PhabricatorProfilePanelConfiguration.php +++ b/src/applications/search/storage/PhabricatorProfilePanelConfiguration.php @@ -101,6 +101,20 @@ return $this->getPanel()->getDisplayName($this); } + public function getSortKey() { + $order = $this->getPanelOrder(); + if ($order === null) { + $order = 'Z'; + } else { + $order = sprintf('%020d', $order); + } + + return sprintf( + '~%s%020d', + $order, + $this->getID()); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/search/storage/PhabricatorProfilePanelConfigurationTransaction.php b/src/applications/search/storage/PhabricatorProfilePanelConfigurationTransaction.php --- a/src/applications/search/storage/PhabricatorProfilePanelConfigurationTransaction.php +++ b/src/applications/search/storage/PhabricatorProfilePanelConfigurationTransaction.php @@ -4,6 +4,7 @@ extends PhabricatorApplicationTransaction { const TYPE_PROPERTY = 'profilepanel.property'; + const TYPE_ORDER = 'profilepanel.order'; public function getApplicationName() { return 'search'; diff --git a/webroot/rsrc/js/application/search/behavior-reorder-profile-menu-items.js b/webroot/rsrc/js/application/search/behavior-reorder-profile-menu-items.js new file mode 100644 --- /dev/null +++ b/webroot/rsrc/js/application/search/behavior-reorder-profile-menu-items.js @@ -0,0 +1,41 @@ +/** + * @provides javelin-behavior-reorder-profile-menu-items + * @requires javelin-behavior + * javelin-stratcom + * javelin-workflow + * javelin-dom + * phabricator-draggable-list + */ + +JX.behavior('reorder-profile-menu-items', function(config) { + + var root = JX.$(config.listID); + + var list = new JX.DraggableList('profile-menu-item', root) + .setFindItemsHandler(function() { + return JX.DOM.scry(root, 'li', 'profile-menu-item'); + }); + + list.listen('didDrop', function(node) { + var nodes = list.findItems(); + var order = []; + var key; + for (var ii = 0; ii < nodes.length; ii++) { + key = JX.Stratcom.getData(nodes[ii]).key; + if (key) { + order.push(key); + } + } + + list.lock(); + JX.DOM.alterClass(node, 'drag-sending', true); + + new JX.Workflow(config.orderURI, {order: order.join()}) + .setHandler(function() { + JX.DOM.alterClass(node, 'drag-sending', false); + list.unlock(); + }) + .start(); + }); + +});