diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4c552dc8e3..2e8ae9af2b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -415,7 +415,7 @@ return array( 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => 'fe9a552f', 'rsrc/js/application/ponder/behavior-votebox.js' => '4e9b766b', 'rsrc/js/application/projects/behavior-boards-dropdown.js' => '0ec56e1d', - 'rsrc/js/application/projects/behavior-project-boards.js' => '21171a56', + 'rsrc/js/application/projects/behavior-project-boards.js' => 'f47fa23b', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', 'rsrc/js/application/projects/behavior-reorder-columns.js' => '09eee344', 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', @@ -639,7 +639,7 @@ return array( 'javelin-behavior-policy-control' => 'f3fef818', 'javelin-behavior-policy-rule-editor' => 'fe9a552f', 'javelin-behavior-ponder-votebox' => '4e9b766b', - 'javelin-behavior-project-boards' => '21171a56', + 'javelin-behavior-project-boards' => 'f47fa23b', 'javelin-behavior-project-create' => '065227cc', 'javelin-behavior-refresh-csrf' => '7814b593', 'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf', @@ -983,14 +983,6 @@ return array( 'javelin-util', 'javelin-magical-init', ), - '21171a56' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', - ), '2290aeef' => array( 'javelin-install', 'javelin-dom', @@ -1852,6 +1844,14 @@ return array( 'phuix-action-view', 'javelin-workflow', ), + 'f47fa23b' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', + ), 'f51afce0' => array( 'javelin-behavior', 'javelin-request', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8f48cb4128..09230da5ef 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3831,6 +3831,7 @@ phutil_register_library_map(array( 'PassphraseCredential' => array( 'PassphraseDAO', 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', ), 'PassphraseCredentialControl' => 'AphrontFormControl', 'PassphraseCredentialCreateController' => 'PassphraseController', diff --git a/src/applications/maniphest/controller/ManiphestTaskEditController.php b/src/applications/maniphest/controller/ManiphestTaskEditController.php index cac80bb77d..a4086281dd 100644 --- a/src/applications/maniphest/controller/ManiphestTaskEditController.php +++ b/src/applications/maniphest/controller/ManiphestTaskEditController.php @@ -11,7 +11,9 @@ final class ManiphestTaskEditController extends ManiphestController { public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); + $response_type = $request->getStr('responseType', 'task'); + $order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER); $can_edit_assign = $this->hasApplicationCapability( ManiphestEditAssignCapability::CAPABILITY); @@ -531,6 +533,7 @@ final class ManiphestTaskEditController extends ManiphestController { ->setUser($user) ->addHiddenInput('template', $template_id) ->addHiddenInput('responseType', $response_type) + ->addHiddenInput('order', $order) ->addHiddenInput('columnPHID', $request->getStr('columnPHID')); if ($parent_task) { diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 66120bbde4..25ffbeae37 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -8,6 +8,8 @@ final class PhabricatorProjectBoardViewController private $handles; private $queryKey; private $filter; + private $sortKey; + private $showHidden; public function shouldAllowPublic() { return true; @@ -25,6 +27,7 @@ final class PhabricatorProjectBoardViewController $viewer = $request->getUser(); $show_hidden = $request->getBool('hidden'); + $this->showHidden = $show_hidden; $project = id(new PhabricatorProjectQuery()) ->setViewer($viewer) @@ -42,6 +45,17 @@ final class PhabricatorProjectBoardViewController $this->setProject($project); $this->id = $project->getID(); + $sort_key = $request->getStr('order'); + switch ($sort_key) { + case PhabricatorProjectColumn::ORDER_NATURAL: + case PhabricatorProjectColumn::ORDER_PRIORITY: + break; + default: + $sort_key = PhabricatorProjectColumn::DEFAULT_ORDER; + break; + } + $this->sortKey = $sort_key; + $column_query = id(new PhabricatorProjectColumnQuery()) ->setViewer($viewer) ->withProjectPHIDs(array($project->getPHID())); @@ -90,13 +104,15 @@ final class PhabricatorProjectBoardViewController $saved = $engine->buildSavedQueryFromRequest($request); $engine->saveQuery($saved); return id(new AphrontRedirectResponse())->setURI( - $engine->getQueryResultsPageURI($saved->getQueryKey())); + $this->getURIWithState( + $engine->getQueryResultsPageURI($saved->getQueryKey()))); } $query_key = $this->queryKey; if (!$query_key) { $query_key = 'open'; } + $this->queryKey = $query_key; $custom_query = null; if ($engine->isBuiltinQuery($query_key)) { @@ -180,6 +196,7 @@ final class PhabricatorProjectBoardViewController 'projectPHID' => $project->getPHID(), 'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'), 'createURI' => '/maniphest/task/create/', + 'order' => $this->sortKey, )); $this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); @@ -235,6 +252,10 @@ final class PhabricatorProjectBoardViewController 'boards-dropdown', array()); + $sort_menu = $this->buildSortMenu( + $viewer, + $sort_key); + $filter_menu = $this->buildFilterMenu( $viewer, $custom_query, @@ -256,6 +277,7 @@ final class PhabricatorProjectBoardViewController ->setNoBackground(true) ->setImage($project->getProfileImageURI()) ->setImageURL($this->getApplicationURI('view/'.$project->getID().'/')) + ->addActionLink($sort_menu) ->addActionLink($filter_menu) ->addActionLink($manage_menu) ->setPolicyObject($project); @@ -274,6 +296,57 @@ final class PhabricatorProjectBoardViewController )); } + private function buildSortMenu( + PhabricatorUser $viewer, + $sort_key) { + + $sort_icon = id(new PHUIIconView()) + ->setIconFont('fa-sort-amount-asc bluegrey'); + + $named = array( + PhabricatorProjectColumn::ORDER_NATURAL => pht('Natural'), + PhabricatorProjectColumn::ORDER_PRIORITY => pht('Sort by Priority'), + ); + + $base_uri = $this->getURIWithState(); + + $items = array(); + foreach ($named as $key => $name) { + $is_selected = ($key == $sort_key); + if ($is_selected) { + $active_order = $name; + } + + $item = id(new PhabricatorActionView()) + ->setIcon('fa-sort-amount-asc') + ->setSelected($is_selected) + ->setName($name); + + $uri = $base_uri->alter('order', $key); + $item->setHref($uri); + + $items[] = $item; + } + + $sort_menu = id(new PhabricatorActionListView()) + ->setUser($viewer); + foreach ($items as $item) { + $sort_menu->addAction($item); + } + + $sort_button = id(new PHUIButtonView()) + ->setText(pht('Sort: %s', $active_order)) + ->setIcon($sort_icon) + ->setTag('a') + ->setHref('#') + ->addSigil('boards-dropdown-menu') + ->setMetadata( + array( + 'items' => hsprintf('%s', $sort_menu), + )); + + return $sort_button; + } private function buildFilterMenu( PhabricatorUser $viewer, $custom_query, @@ -314,14 +387,16 @@ final class PhabricatorProjectBoardViewController ->setName($name); if ($is_custom) { - $item->setHref( - $this->getApplicationURI( - 'board/'.$this->id.'/filter/query/'.$key.'/')); + $uri = $this->getApplicationURI( + 'board/'.$this->id.'/filter/query/'.$key.'/'); $item->setWorkflow(true); } else { - $item->setHref($engine->getQueryResultsPageURI($key)); + $uri = $engine->getQueryResultsPageURI($key); } + $uri = $this->getURIWithState($uri); + $item->setHref($uri); + $items[] = $item; } @@ -383,12 +458,12 @@ final class PhabricatorProjectBoardViewController ->setWorkflow(true); if ($show_hidden) { - $hidden_uri = $request->getRequestURI() + $hidden_uri = $this->getURIWithState() ->setQueryParam('hidden', null); $hidden_icon = 'fa-eye-slash'; $hidden_text = pht('Hide Hidden Columns'); } else { - $hidden_uri = $request->getRequestURI() + $hidden_uri = $this->getURIWithState() ->setQueryParam('hidden', 'true'); $hidden_icon = 'fa-eye'; $hidden_text = pht('Show Hidden Columns'); @@ -446,4 +521,34 @@ final class PhabricatorProjectBoardViewController ->setDialog($dialog); } + + /** + * Add current state parameters (like order and the visibility of hidden + * columns) to a URI. + * + * This allows actions which toggle or adjust one piece of state to keep + * the rest of the board state persistent. If no URI is provided, this method + * starts with the request URI. + * + * @param string|null URI to add state parameters to. + * @return PhutilURI URI with state parameters. + */ + private function getURIWithState($base = null) { + if ($base === null) { + $base = $this->getRequest()->getRequestURI(); + } + + $base = new PhutilURI($base); + + if ($this->sortKey != PhabricatorProjectColumn::DEFAULT_ORDER) { + $base->setQueryParam('order', $this->sortKey); + } else { + $base->setQueryParam('order', null); + } + + $base->setQueryParam('hidden', $this->showHidden ? 'true' : null); + + return $base; + } + } diff --git a/src/applications/project/storage/PhabricatorProjectColumn.php b/src/applications/project/storage/PhabricatorProjectColumn.php index 10b444154e..f602b7b217 100644 --- a/src/applications/project/storage/PhabricatorProjectColumn.php +++ b/src/applications/project/storage/PhabricatorProjectColumn.php @@ -9,6 +9,10 @@ final class PhabricatorProjectColumn const STATUS_ACTIVE = 0; const STATUS_HIDDEN = 1; + const DEFAULT_ORDER = 'natural'; + const ORDER_NATURAL = 'natural'; + const ORDER_PRIORITY = 'priority'; + protected $name; protected $status; protected $projectPHID; diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js index c050562878..89f4fd15d8 100644 --- a/webroot/rsrc/js/application/projects/behavior-project-boards.js +++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js @@ -81,6 +81,8 @@ JX.behavior('project-boards', function(config) { data.beforePHID = before_phid; } + data.order = config.order; + var workflow = new JX.Workflow(config.moveURI, data) .setHandler(function(response) { onresponse(response, item, list); @@ -148,11 +150,13 @@ JX.behavior('project-boards', function(config) { e.kill(); var column = e.getNode('project-column'); var request_data = { - 'responseType' : 'card', - 'columnPHID' : JX.Stratcom.getData(column).columnPHID }; + responseType: 'card', + columnPHID: JX.Stratcom.getData(column).columnPHID, + order: config.order + }; new JX.Workflow(e.getNode('tag:a').href, request_data) - .setHandler(JX.bind(null, onedit, column)) - .start(); + .setHandler(JX.bind(null, onedit, column)) + .start(); }); JX.Stratcom.listen( @@ -162,9 +166,11 @@ JX.behavior('project-boards', function(config) { e.kill(); var column_phid = e.getNodeData('column-add-task').columnPHID; var request_data = { - 'responseType' : 'card', - 'columnPHID' : column_phid, - 'projects' : config.projectPHID }; + responseType: 'card', + columnPHID: column_phid, + projects: config.projectPHID, + order: config.order + }; var cols = JX.DOM.scry(JX.$(config.boardID), 'ul', 'project-column'); var ii; var column; @@ -175,7 +181,7 @@ JX.behavior('project-boards', function(config) { } } new JX.Workflow(config.createURI, request_data) - .setHandler(JX.bind(null, onedit, column)) - .start(); + .setHandler(JX.bind(null, onedit, column)) + .start(); }); });