diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 417abce462..8ee18b9598 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -73,6 +73,7 @@ phutil_register_library_map(array( 'AphrontRequestFailureView' => 'view/page/failure', 'AphrontResponse' => 'aphront/response/base', 'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/scopeguard', + 'AphrontSideNavFilterView' => 'view/layout/sidenavfilter', 'AphrontSideNavView' => 'view/layout/sidenav', 'AphrontTableView' => 'view/control/table', 'AphrontTokenizerTemplateView' => 'view/control/tokenizer', @@ -568,6 +569,7 @@ phutil_register_library_map(array( 'PhabricatorProjectProfile' => 'applications/project/storage/profile', 'PhabricatorProjectProfileController' => 'applications/project/controller/profile', 'PhabricatorProjectProfileEditController' => 'applications/project/controller/profileedit', + 'PhabricatorProjectQuery' => 'applications/project/query/project', 'PhabricatorProjectStatus' => 'applications/project/constants/status', 'PhabricatorProjectSubproject' => 'applications/project/storage/subproject', 'PhabricatorRedirectController' => 'applications/base/controller/redirect', @@ -815,6 +817,7 @@ phutil_register_library_map(array( 'AphrontRedirectResponse' => 'AphrontResponse', 'AphrontReloadResponse' => 'AphrontRedirectResponse', 'AphrontRequestFailureView' => 'AphrontView', + 'AphrontSideNavFilterView' => 'AphrontView', 'AphrontSideNavView' => 'AphrontView', 'AphrontTableView' => 'AphrontView', 'AphrontTokenizerTemplateView' => 'AphrontView', diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index b149f519dc..c172bca5c9 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -199,6 +199,7 @@ class AphrontDefaultApplicationConfiguration '/project/' => array( '$' => 'PhabricatorProjectListController', + 'filter/(?P[^/]+)/$' => 'PhabricatorProjectListController', 'edit/(?P\d+)/$' => 'PhabricatorProjectProfileEditController', 'view/(?P\d+)/(?:(?P\w+)/)?$' => 'PhabricatorProjectProfileController', diff --git a/src/applications/project/controller/list/PhabricatorProjectListController.php b/src/applications/project/controller/list/PhabricatorProjectListController.php index 25eaf353c7..ecb829dc1c 100644 --- a/src/applications/project/controller/list/PhabricatorProjectListController.php +++ b/src/applications/project/controller/list/PhabricatorProjectListController.php @@ -19,10 +19,54 @@ class PhabricatorProjectListController extends PhabricatorProjectController { - public function processRequest() { + private $filter; + + public function willProcessRequest(array $data) { + $this->filter = idx($data, 'filter'); + } + + public function processRequest() { + $request = $this->getRequest(); + + $nav = new AphrontSideNavFilterView(); + $nav + ->setBaseURI(new PhutilURI('/project/filter/')) + ->addLabel('User') + ->addFilter('active', 'Active') + ->addFilter('owned', 'Owned') + ->addSpacer() + ->addLabel('All') + ->addFilter('all', 'All Projects'); + $this->filter = $nav->selectFilter($this->filter, 'active'); + + $pager = new AphrontPagerView(); + $pager->setPageSize(250); + $pager->setURI($request->getRequestURI(), 'page'); + $pager->setOffset($request->getInt('page')); + + $query = new PhabricatorProjectQuery(); + $query->setOffset($pager->getOffset()); + $query->setLimit($pager->getPageSize() + 1); + + $view_phid = $request->getUser()->getPHID(); + + switch ($this->filter) { + case 'active': + $table_header = 'Active Projects'; + $query->setMembers(array($view_phid)); + break; + case 'owned': + $table_header = 'Owned Projects'; + $query->setOwners(array($view_phid)); + break; + case 'all': + $table_header = 'All Projects'; + break; + } + + $projects = $query->execute(); + $projects = $pager->sliceResults($projects); - $projects = id(new PhabricatorProject())->loadAllWhere( - '1 = 1 ORDER BY id DESC limit 100'); $project_phids = mpull($projects, 'getPHID'); $profiles = array(); @@ -121,12 +165,15 @@ class PhabricatorProjectListController )); $panel = new AphrontPanelView(); - $panel->appendChild($table); - $panel->setHeader('Project'); + $panel->setHeader($table_header); $panel->setCreateButton('Create New Project', '/project/create/'); + $panel->appendChild($table); + $panel->appendChild($pager); + + $nav->appendChild($panel); return $this->buildStandardPageResponse( - $panel, + $nav, array( 'title' => 'Projects', )); diff --git a/src/applications/project/controller/list/__init__.php b/src/applications/project/controller/list/__init__.php index c87483a57d..27aedf24f7 100644 --- a/src/applications/project/controller/list/__init__.php +++ b/src/applications/project/controller/list/__init__.php @@ -10,13 +10,16 @@ phutil_require_module('phabricator', 'applications/maniphest/query'); phutil_require_module('phabricator', 'applications/phid/handle/data'); phutil_require_module('phabricator', 'applications/project/constants/status'); phutil_require_module('phabricator', 'applications/project/controller/base'); +phutil_require_module('phabricator', 'applications/project/query/project'); phutil_require_module('phabricator', 'applications/project/storage/affiliation'); phutil_require_module('phabricator', 'applications/project/storage/profile'); -phutil_require_module('phabricator', 'applications/project/storage/project'); +phutil_require_module('phabricator', 'view/control/pager'); phutil_require_module('phabricator', 'view/control/table'); phutil_require_module('phabricator', 'view/layout/panel'); +phutil_require_module('phabricator', 'view/layout/sidenavfilter'); phutil_require_module('phutil', 'markup'); +phutil_require_module('phutil', 'parser/uri'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/project/query/project/PhabricatorProjectQuery.php b/src/applications/project/query/project/PhabricatorProjectQuery.php new file mode 100644 index 0000000000..cd49a2426e --- /dev/null +++ b/src/applications/project/query/project/PhabricatorProjectQuery.php @@ -0,0 +1,103 @@ +limit = $limit; + return $this; + } + + public function setOffset($offset) { + $this->offset = $offset; + return $this; + } + + public function setOwners(array $owners) { + $this->owners = $owners; + return $this; + } + + public function setMembers(array $members) { + $this->members = $members; + return $this; + } + + public function execute() { + $table = id(new PhabricatorProject()); + $conn_r = $table->establishConnection('r'); + + $joins = $this->buildJoinsClause($conn_r); + + $limit = null; + if ($this->limit) { + $limit = qsprintf( + $conn_r, + 'LIMIT %d, %d', + $this->offset, + $this->limit); + } else if ($this->offset) { + $limit = qsprintf( + $conn_r, + 'LIMIT %d, %d', + $this->offset, + PHP_INT_MAX); + } + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T p %Q %Q', + $table->getTableName(), + $joins, + $limit); + + return $table->loadAllFromArray($data); + } + + private function buildJoinsClause($conn_r) { + $affil_table = new PhabricatorProjectAffiliation(); + + $joins = array(); + if ($this->owners) { + $joins[] = qsprintf( + $conn_r, + 'JOIN %T owner ON owner.projectPHID = p.phid AND owner.isOwner = 1 + AND owner.userPHID in (%Ls)', + $affil_table->getTableName(), + $this->owners); + } + + if ($this->members) { + $joins[] = qsprintf( + $conn_r, + 'JOIN %T member ON member.projectPHID = p.phid + AND member.userPHID in (%Ls)', + $affil_table->getTableName(), + $this->members); + } + + return implode(' ', $joins); + } + +} diff --git a/src/applications/project/query/project/__init__.php b/src/applications/project/query/project/__init__.php new file mode 100644 index 0000000000..0fb198a377 --- /dev/null +++ b/src/applications/project/query/project/__init__.php @@ -0,0 +1,17 @@ +setBaseURI($some_uri) + * ->addLabel('Cats') + * ->addFilter('meow', 'Meow') + * ->addFilter('purr', 'Purr') + * ->addSpacer() + * ->addLabel('Dogs') + * ->addFilter('woof', 'Woof') + * ->addFilter('bark', 'Bark'); + * $valid_filter = $nav->selectFilter($user_selection, $default = 'meow'); + * + */ +final class AphrontSideNavFilterView extends AphrontView { + + private $items = array(); + private $baseURI; + private $selectedFilter = false; + + public function addFilter($key, $name) { + $this->items[] = array('filter', $key, $name); + return $this; + } + + public function addLabel($name) { + $this->items[] = array('label', null, $name); + return $this; + } + + public function addSpacer() { + $this->items[] = array('spacer', null, null); + return $this; + } + + public function setBaseURI(PhutilURI $uri) { + $this->baseURI = $uri; + return $this; + } + + public function selectFilter($key, $default) { + $this->selectedFilter = $default; + foreach ($this->items as $item) { + if ($item[0] == 'filter') { + if ($item[1] == $key) { + $this->selectedFilter = $key; + break; + } + } + } + return $this->selectedFilter; + } + + public function render() { + if (!$this->baseURI) { + throw new Exception("Call setBaseURI() before render()!"); + } + if ($this->selectedFilter === false) { + throw new Exception("Call selectFilter() before render()!"); + } + + $view = new AphrontSideNavView(); + foreach ($this->items as $item) { + list($type, $key, $name) = $item; + switch ($type) { + case 'spacer': + $view->addNavItem('
'); + break; + case 'label': + $view->addNavItem( + phutil_render_tag( + 'span', + array(), + phutil_escape_html($name))); + break; + case 'filter': + $class = ($key == $this->selectedFilter) + ? 'aphront-side-nav-selected' + : null; + + $href = clone $this->baseURI; + $href->setPath($href->getPath().$key.'/'); + $href = (string)$href; + + $view->addNavItem( + phutil_render_tag( + 'a', + array( + 'href' => $href, + 'class' => $class, + ), + phutil_escape_html($name))); + break; + default: + throw new Exception("Unknown item type '{$type}'."); + } + } + $view->appendChild($this->renderChildren()); + + return $view->render(); + } + +} diff --git a/src/view/layout/sidenavfilter/__init__.php b/src/view/layout/sidenavfilter/__init__.php new file mode 100644 index 0000000000..e7e974c414 --- /dev/null +++ b/src/view/layout/sidenavfilter/__init__.php @@ -0,0 +1,15 @@ +