1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-14 00:31:05 +01:00

Replace home directory list with a dashboard

Summary:
Rough cut that still needs a lot of polish, but replace the directory list with
more of a dashboard type thing:

  - Show "Unbreak Now", triage-in-your-projects, and other stuff that you're
supposed to deal with, then feed.
  - Move tools a click a way behind nav -- this also lets us put more stuff
there and subtools, etc., later.
  - Remove tabs.
  - Merge the category/item editing views.
  - I also added a light blue wash to the side nav, not sure if I like that or
not.

Test Plan:
  - Viewed all elements in empty and nonempty states.
  - Viewed applications, edited items/categories.

Reviewers: btrahan, aran

Reviewed By: btrahan

CC: aran, epriestley, davidreuss

Maniphest Tasks: T21, T631

Differential Revision: https://secure.phabricator.com/D1574
This commit is contained in:
epriestley 2012-02-07 16:04:48 -08:00
parent 3e3f73235c
commit 3f46d30e8f
23 changed files with 546 additions and 258 deletions

View file

@ -108,7 +108,7 @@ celerity_register_resource_map(array(
),
'aphront-panel-view-css' =>
array(
'uri' => '/res/58da9c70/rsrc/css/aphront/panel-view.css',
'uri' => '/res/f4157618/rsrc/css/aphront/panel-view.css',
'type' => 'css',
'requires' =>
array(
@ -126,7 +126,7 @@ celerity_register_resource_map(array(
),
'aphront-side-nav-view-css' =>
array(
'uri' => '/res/42f70a69/rsrc/css/aphront/side-nav-view.css',
'uri' => '/res/ba0e18dd/rsrc/css/aphront/side-nav-view.css',
'type' => 'css',
'requires' =>
array(
@ -1368,7 +1368,7 @@ celerity_register_resource_map(array(
),
'phabricator-directory-css' =>
array(
'uri' => '/res/a3d307c5/rsrc/css/application/directory/phabricator-directory.css',
'uri' => '/res/61afca2b/rsrc/css/application/directory/phabricator-directory.css',
'type' => 'css',
'requires' =>
array(
@ -1787,28 +1787,7 @@ celerity_register_resource_map(array(
'uri' => '/res/pkg/540effd7/typeahead.pkg.js',
'type' => 'js',
),
'80580cea' =>
array(
'name' => 'differential.pkg.css',
'symbols' =>
array(
0 => 'differential-core-view-css',
1 => 'differential-changeset-view-css',
2 => 'differential-revision-detail-css',
3 => 'differential-revision-history-css',
4 => 'differential-table-of-contents-css',
5 => 'differential-revision-comment-css',
6 => 'differential-revision-add-comment-css',
7 => 'differential-revision-comment-list-css',
8 => 'phabricator-object-selector-css',
9 => 'aphront-headsup-action-list-view-css',
10 => 'phabricator-content-source-view-css',
11 => 'differential-local-commits-view-css',
),
'uri' => '/res/pkg/80580cea/differential.pkg.css',
'type' => 'css',
),
'9fd6210b' =>
'775f5bae' =>
array(
'name' => 'core.pkg.css',
'symbols' =>
@ -1829,7 +1808,28 @@ celerity_register_resource_map(array(
13 => 'phabricator-remarkup-css',
14 => 'syntax-highlighting-css',
),
'uri' => '/res/pkg/9fd6210b/core.pkg.css',
'uri' => '/res/pkg/775f5bae/core.pkg.css',
'type' => 'css',
),
'80580cea' =>
array(
'name' => 'differential.pkg.css',
'symbols' =>
array(
0 => 'differential-core-view-css',
1 => 'differential-changeset-view-css',
2 => 'differential-revision-detail-css',
3 => 'differential-revision-history-css',
4 => 'differential-table-of-contents-css',
5 => 'differential-revision-comment-css',
6 => 'differential-revision-add-comment-css',
7 => 'differential-revision-comment-list-css',
8 => 'phabricator-object-selector-css',
9 => 'aphront-headsup-action-list-view-css',
10 => 'phabricator-content-source-view-css',
11 => 'differential-local-commits-view-css',
),
'uri' => '/res/pkg/80580cea/differential.pkg.css',
'type' => 'css',
),
'b164acea' =>
@ -1854,16 +1854,16 @@ celerity_register_resource_map(array(
),
'reverse' =>
array(
'aphront-crumbs-view-css' => '9fd6210b',
'aphront-dialog-view-css' => '9fd6210b',
'aphront-form-view-css' => '9fd6210b',
'aphront-crumbs-view-css' => '775f5bae',
'aphront-dialog-view-css' => '775f5bae',
'aphront-form-view-css' => '775f5bae',
'aphront-headsup-action-list-view-css' => '80580cea',
'aphront-list-filter-view-css' => '9fd6210b',
'aphront-panel-view-css' => '9fd6210b',
'aphront-side-nav-view-css' => '9fd6210b',
'aphront-table-view-css' => '9fd6210b',
'aphront-tokenizer-control-css' => '9fd6210b',
'aphront-typeahead-control-css' => '9fd6210b',
'aphront-list-filter-view-css' => '775f5bae',
'aphront-panel-view-css' => '775f5bae',
'aphront-side-nav-view-css' => '775f5bae',
'aphront-table-view-css' => '775f5bae',
'aphront-tokenizer-control-css' => '775f5bae',
'aphront-typeahead-control-css' => '775f5bae',
'differential-changeset-view-css' => '80580cea',
'differential-core-view-css' => '80580cea',
'differential-inline-comment-editor' => '15c4a6dd',
@ -1912,16 +1912,16 @@ celerity_register_resource_map(array(
'javelin-vector' => 'b164acea',
'javelin-workflow' => '46547a92',
'phabricator-content-source-view-css' => '80580cea',
'phabricator-core-buttons-css' => '9fd6210b',
'phabricator-core-css' => '9fd6210b',
'phabricator-directory-css' => '9fd6210b',
'phabricator-core-buttons-css' => '775f5bae',
'phabricator-core-css' => '775f5bae',
'phabricator-directory-css' => '775f5bae',
'phabricator-drag-and-drop-file-upload' => '15c4a6dd',
'phabricator-keyboard-shortcut' => '46547a92',
'phabricator-keyboard-shortcut-manager' => '46547a92',
'phabricator-object-selector-css' => '80580cea',
'phabricator-remarkup-css' => '9fd6210b',
'phabricator-remarkup-css' => '775f5bae',
'phabricator-shaped-request' => '15c4a6dd',
'phabricator-standard-page-view' => '9fd6210b',
'syntax-highlighting-css' => '9fd6210b',
'phabricator-standard-page-view' => '775f5bae',
'syntax-highlighting-css' => '775f5bae',
),
));

View file

@ -472,13 +472,13 @@ phutil_register_library_map(array(
'PhabricatorDirectoryCategory' => 'applications/directory/storage/category',
'PhabricatorDirectoryCategoryDeleteController' => 'applications/directory/controller/categorydelete',
'PhabricatorDirectoryCategoryEditController' => 'applications/directory/controller/categoryedit',
'PhabricatorDirectoryCategoryListController' => 'applications/directory/controller/categorylist',
'PhabricatorDirectoryCategoryViewController' => 'applications/directory/controller/categoryview',
'PhabricatorDirectoryController' => 'applications/directory/controller/base',
'PhabricatorDirectoryDAO' => 'applications/directory/storage/base',
'PhabricatorDirectoryEditController' => 'applications/directory/controller/edit',
'PhabricatorDirectoryItem' => 'applications/directory/storage/item',
'PhabricatorDirectoryItemDeleteController' => 'applications/directory/controller/itemdelete',
'PhabricatorDirectoryItemEditController' => 'applications/directory/controller/itemedit',
'PhabricatorDirectoryItemListController' => 'applications/directory/controller/itemlist',
'PhabricatorDirectoryMainController' => 'applications/directory/controller/main',
'PhabricatorDisabledUserController' => 'applications/auth/controller/disabled',
'PhabricatorDraft' => 'applications/draft/storage/draft',
@ -1204,13 +1204,13 @@ phutil_register_library_map(array(
'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO',
'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryCategoryListController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryCategoryViewController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryController' => 'PhabricatorController',
'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO',
'PhabricatorDirectoryEditController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO',
'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController',
'PhabricatorDisabledUserController' => 'PhabricatorAuthController',
'PhabricatorDraft' => 'PhabricatorDraftDAO',

View file

@ -36,14 +36,14 @@ class AphrontDefaultApplicationConfiguration
'$' => 'PhabricatorDirectoryMainController',
),
'/directory/' => array(
'item/$'
=> 'PhabricatorDirectoryItemListController',
'(?P<id>\d+)/$'
=> 'PhabricatorDirectoryCategoryViewController',
'edit/$'
=> 'PhabricatorDirectoryEditController',
'item/edit/(?:(?P<id>\d+)/)?$'
=> 'PhabricatorDirectoryItemEditController',
'item/delete/(?P<id>\d+)/'
=> 'PhabricatorDirectoryItemDeleteController',
'category/$'
=> 'PhabricatorDirectoryCategoryListController',
'category/edit/(?:(?P<id>\d+)/)?$'
=> 'PhabricatorDirectoryCategoryEditController',
'category/delete/(?P<id>\d+)/'

View file

@ -369,26 +369,9 @@ class DifferentialRevisionListController extends DifferentialController {
$views = array();
switch ($filter) {
case 'active':
$active = array();
$waiting = array();
// Bucket revisions into $active (revisions you need to do something
// about) and $waiting (revisions you're waiting on someone else to do
// something about).
foreach ($revisions as $revision) {
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
$needs_review = ($revision->getStatus() == $status_review);
$filter_is_author = ($revision->getAuthorPHID() == $user_phid);
// If exactly one of "needs review" and "the user is the author" is
// true, the user needs to act on it. Otherwise, they're waiting on
// it.
if ($needs_review ^ $filter_is_author) {
$active[] = $revision;
} else {
$waiting[] = $revision;
}
}
list($active, $waiting) = DifferentialRevisionQuery::splitResponsible(
$revisions,
$user_phid);
$view = id(new DifferentialRevisionListView())
->setRevisions($active)

View file

@ -6,8 +6,6 @@
phutil_require_module('arcanist', 'differential/constants/revisionstatus');
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/differential/controller/base');
phutil_require_module('phabricator', 'applications/differential/query/revision');

View file

@ -785,5 +785,30 @@ final class DifferentialRevisionQuery {
}
}
public static function splitResponsible(array $revisions, $user_phid) {
$active = array();
$waiting = array();
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
// Bucket revisions into $active (revisions you need to do something
// about) and $waiting (revisions you're waiting on someone else to do
// something about).
foreach ($revisions as $revision) {
$needs_review = ($revision->getStatus() == $status_review);
$filter_is_author = ($revision->getAuthorPHID() == $user_phid);
// If exactly one of "needs review" and "the user is the author" is
// true, the user needs to act on it. Otherwise, they're waiting on
// it.
if ($needs_review ^ $filter_is_author) {
$active[] = $revision;
} else {
$waiting[] = $revision;
}
}
return array($active, $waiting);
}
}

View file

@ -1,7 +1,7 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,28 +26,9 @@ abstract class PhabricatorDirectoryController extends PhabricatorController {
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
$page->setApplicationName('Directory');
$page->setBaseURI('/');
$page->setTitle(idx($data, 'title'));
if ($this->getRequest()->getUser()->getIsAdmin()) {
$tabs = array(
'categories' => array(
'href' => '/directory/category/',
'name' => 'Categories',
),
'items' => array(
'href' => '/directory/item/',
'name' => 'Items',
),
);
} else {
$tabs = array();
}
$page->setTabs(
$tabs,
idx($data, 'tab'));
$page->setGlyph("\xE2\x9A\x92");
$page->appendChild($view);
@ -55,4 +36,37 @@ abstract class PhabricatorDirectoryController extends PhabricatorController {
return $response->setContent($page->render());
}
public function buildNav() {
$user = $this->getRequest()->getUser();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI('/'));
$nav->addLabel('Phabricator');
$nav->addFilter('home', 'Tactical Command', '/');
$nav->addSpacer();
$nav->addLabel('Applications');
$categories = $this->loadDirectoryCategories();
foreach ($categories as $category) {
$nav->addFilter(
'directory/'.$category->getID(),
$category->getName());
}
if ($user->getIsAdmin()) {
$nav->addSpacer();
$nav->addFilter('directory/edit', 'Edit Applications...');
}
return $nav;
}
protected function loadDirectoryCategories() {
$categories = id(new PhabricatorDirectoryCategory())->loadAll();
$categories = msort($categories, 'getSequence');
return $categories;
}
}

View file

@ -8,7 +8,10 @@
phutil_require_module('phabricator', 'aphront/response/webpage');
phutil_require_module('phabricator', 'applications/base/controller/base');
phutil_require_module('phabricator', 'applications/directory/storage/category');
phutil_require_module('phabricator', 'view/layout/sidenavfilter');
phutil_require_module('phutil', 'parser/uri');
phutil_require_module('phutil', 'utils');

View file

@ -1,7 +1,7 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -35,7 +35,7 @@ class PhabricatorDirectoryCategoryDeleteController
if ($request->isFormPost()) {
$category->delete();
return id(new AphrontRedirectResponse())
->setURI('/directory/category/');
->setURI('/directory/edit/');
}
$dialog = new AphrontDialogView();
@ -43,7 +43,7 @@ class PhabricatorDirectoryCategoryDeleteController
$dialog->setTitle('Really delete this category?');
$dialog->appendChild("Are you sure you want to delete this category?");
$dialog->addSubmitButton('Delete');
$dialog->addCancelButton('/directory/category/');
$dialog->addCancelButton('/directory/edit/');
$dialog->setSubmitURI($request->getPath());
return id(new AphrontDialogResponse())->setDialog($dialog);

View file

@ -1,7 +1,7 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -52,7 +52,7 @@ class PhabricatorDirectoryCategoryEditController
if (!$errors) {
$category->save();
return id(new AphrontRedirectResponse())
->setURI('/directory/category/');
->setURI('/directory/edit/');
}
}
@ -89,7 +89,7 @@ class PhabricatorDirectoryCategoryEditController
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Save')
->addCancelButton('/directory/category/'));
->addCancelButton('/directory/edit/'));
$panel = new AphrontPanelView();
if ($category->getID()) {

View file

@ -1,73 +0,0 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class PhabricatorDirectoryCategoryListController
extends PhabricatorDirectoryController {
public function processRequest() {
$categories = id(new PhabricatorDirectoryCategory())->loadAll();
$categories = msort($categories, 'getSequence');
$rows = array();
foreach ($categories as $category) {
$rows[] = array(
$category->getID(),
phutil_render_tag(
'a',
array(
'href' => '/directory/category/edit/'.$category->getID().'/',
),
phutil_escape_html($category->getName())),
javelin_render_tag(
'a',
array(
'href' => '/directory/category/delete/'.$category->getID().'/',
'class' => 'button grey small',
'sigil' => 'workflow',
),
'Delete'),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'ID',
'Name',
'',
));
$table->setColumnClasses(
array(
null,
'wide',
'action',
));
$panel = new AphrontPanelView();
$panel->appendChild($table);
$panel->setHeader('Directory Categories');
$panel->setCreateButton('New Category', '/directory/category/edit/');
return $this->buildStandardPageResponse($panel, array(
'title' => 'Directory Category List',
'tab' => 'categories',
));
}
}

View file

@ -0,0 +1,78 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorDirectoryCategoryViewController
extends PhabricatorDirectoryController {
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function shouldRequireAdmin() {
return false;
}
public function processRequest() {
$category = id(new PhabricatorDirectoryCategory())->load($this->id);
if (!$category) {
return new Aphront404Response();
}
$items = id(new PhabricatorDirectoryItem())->loadAllWhere(
'categoryID = %d',
$category->getID());
$items = msort($items, 'getSortKey');
$nav = $this->buildNav();
$nav->selectFilter('directory/'.$this->id, 'directory/'.$this->id);
require_celerity_resource('phabricator-directory-css');
$item_markup = array();
foreach ($items as $item) {
$item_markup[] =
'<div class="aphront-directory-item">'.
'<h1>'.
phutil_render_tag(
'a',
array(
'href' => $item->getHref(),
),
phutil_escape_html($item->getName())).
'</h1>'.
'<p>'.phutil_escape_html($item->getDescription()).'</p>'.
'</div>';
}
$content =
'<div class="aphront-directory-list">'.
implode("\n", $item_markup).
'</div>';
$nav->appendChild($content);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Directory Category List',
'tab' => 'categories',
));
}
}

View file

@ -6,14 +6,14 @@
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'applications/directory/controller/base');
phutil_require_module('phabricator', 'applications/directory/storage/category');
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
phutil_require_module('phabricator', 'view/control/table');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'applications/directory/storage/item');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorDirectoryCategoryListController.php');
phutil_require_source('PhabricatorDirectoryCategoryViewController.php');

View file

@ -1,7 +1,7 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,10 +16,71 @@
* limitations under the License.
*/
class PhabricatorDirectoryItemListController
final class PhabricatorDirectoryEditController
extends PhabricatorDirectoryController {
public function processRequest() {
$nav = $this->buildNav();
$nav->selectFilter('directory/edit', 'directory/edit');
$nav->appendChild($this->buildCategoryList());
$nav->appendChild($this->buildItemList());
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Edit Applications',
));
}
private function buildCategoryList() {
$categories = id(new PhabricatorDirectoryCategory())->loadAll();
$categories = msort($categories, 'getSequence');
$rows = array();
foreach ($categories as $category) {
$rows[] = array(
$category->getID(),
phutil_render_tag(
'a',
array(
'href' => '/directory/category/edit/'.$category->getID().'/',
),
phutil_escape_html($category->getName())),
javelin_render_tag(
'a',
array(
'href' => '/directory/category/delete/'.$category->getID().'/',
'class' => 'button grey small',
'sigil' => 'workflow',
),
'Delete'),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'ID',
'Name',
'',
));
$table->setColumnClasses(
array(
null,
'wide',
'action',
));
$panel = new AphrontPanelView();
$panel->appendChild($table);
$panel->setHeader('Directory Categories');
$panel->setCreateButton('New Category', '/directory/category/edit/');
return $panel;
}
private function buildItemList() {
$items = id(new PhabricatorDirectoryItem())->loadAll();
$items = msort($items, 'getSortKey');
@ -70,10 +131,6 @@ class PhabricatorDirectoryItemListController
$panel->setHeader('Directory Items');
$panel->setCreateButton('New Item', '/directory/item/edit/');
return $this->buildStandardPageResponse($panel, array(
'title' => 'Directory Items',
'tab' => 'items',
));
return $panel;
}
}

View file

@ -17,4 +17,4 @@ phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorDirectoryItemListController.php');
phutil_require_source('PhabricatorDirectoryEditController.php');

View file

@ -1,7 +1,7 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -35,7 +35,7 @@ class PhabricatorDirectoryItemDeleteController
if ($request->isFormPost()) {
$item->delete();
return id(new AphrontRedirectResponse())
->setURI('/directory/item/');
->setURI('/directory/edit/');
}
$dialog = new AphrontDialogView();
@ -43,7 +43,7 @@ class PhabricatorDirectoryItemDeleteController
$dialog->setTitle('Really delete this item?');
$dialog->appendChild("Are you sure you want to delete this item?");
$dialog->addSubmitButton('Delete');
$dialog->addCancelButton('/directory/item/');
$dialog->addCancelButton('/directory/edit/');
$dialog->setSubmitURI($request->getPath());
return id(new AphrontDialogResponse())->setDialog($dialog);

View file

@ -67,7 +67,7 @@ class PhabricatorDirectoryItemEditController
if (!$errors) {
$item->save();
return id(new AphrontRedirectResponse())
->setURI('/directory/item/');
->setURI('/directory/edit/');
}
}
@ -124,7 +124,7 @@ class PhabricatorDirectoryItemEditController
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Save')
->addCancelButton('/directory/item/'));
->addCancelButton('/directory/edit/'));
$panel = new AphrontPanelView();
if ($item->getID()) {

View file

@ -1,7 +1,7 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,6 +19,8 @@
class PhabricatorDirectoryMainController
extends PhabricatorDirectoryController {
private $filter;
public function shouldRequireAdmin() {
// These controllers are admin-only by default, but this one is public,
// so allow non-admin users to view it.
@ -26,61 +28,242 @@ class PhabricatorDirectoryMainController
}
public function processRequest() {
$user = $this->getRequest()->getUser();
$project_query = new PhabricatorProjectQuery();
$project_query->setMembers(array($user->getPHID()));
$projects = $project_query->execute();
$items = id(new PhabricatorDirectoryItem())->loadAll();
$items = msort($items, 'getSortKey');
$unbreak_panel = $this->buildUnbreakNowPanel();
$triage_panel = $this->buildNeedsTriagePanel($projects);
$revision_panel = $this->buildRevisionPanel();
$tasks_panel = $this->buildTasksPanel();
$feed_view = $this->buildFeedView($projects);
$categories = id(new PhabricatorDirectoryCategory())->loadAll();
$categories = msort($categories, 'getSequence');
$nav = $this->buildNav();
$this->filter = $nav->selectFilter($this->filter, 'home');
$category_map = mpull($categories, 'getName', 'getID');
$category_map[0] = 'Free Radicals';
$items = mgroup($items, 'getCategoryID');
$content = array(
$unbreak_panel,
$triage_panel,
$revision_panel,
$tasks_panel,
$feed_view,
);
require_celerity_resource('phabricator-directory-css');
$nav->appendChild($content);
$content = array();
foreach ($category_map as $id => $category_name) {
$category_items = idx($items, $id);
if (!$category_items) {
continue;
}
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Directory',
'tab' => 'directory',
));
}
$item_markup = array();
foreach ($category_items as $item) {
$item_markup[] =
'<div>'.
'<h2>'.
phutil_render_tag(
'a',
array(
'href' => $item->getHref(),
),
phutil_escape_html($item->getName())).
'</h2>'.
'<p>'.phutil_escape_html($item->getDescription()).'</p>'.
'</div>';
}
private function buildUnbreakNowPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$content[] =
'<div class="aphront-directory-category">'.
'<h1>'.phutil_escape_html($category_name).'</h1>'.
'<div class="aphront-directory-group">'.
implode("\n", $item_markup).
'</div>'.
'</div>';
$panel = new AphrontPanelView();
$panel->setHeader('Unbreak Now!');
$panel->setCaption('Open tasks with "Unbreak Now!" priority.');
$task_query = new ManiphestTaskQuery();
$task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$task_query->withPriority(ManiphestTaskPriority::PRIORITY_UNBREAK_NOW);
$task_query->setLimit(10);
$task_query->setCalculateRows(true);
$tasks = $task_query->execute();
if ($tasks) {
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/maniphest/view/all/',
'class' => 'grey button',
),
'View All Unbreak Now ('.$task_query->getRowCount().") \xC2\xBB"));
$panel->appendChild($this->buildTaskListView($tasks));
} else {
$panel->appendChild(
'<p>Nothing appears to be critically broken right now.</p>');
}
$content =
'<div class="aphront-directory-list">'.
implode("\n", $content).
'</div>';
return $panel;
}
return $this->buildStandardPageResponse($content, array(
'title' => 'Directory',
'tab' => 'directory',
));
private function buildNeedsTriagePanel(array $projects) {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$panel = new AphrontPanelView();
$panel->setHeader('Needs Triage');
$panel->setCaption(
'Open tasks with "Needs Triage" priority in '.
'<a href="/project/">projects you are a member of</a>.');
$task_query = new ManiphestTaskQuery();
$task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$task_query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
$task_query->withProjects(mpull($projects, 'getPHID'));
$task_query->withAnyProject(true);
$task_query->setCalculateRows(true);
$task_query->setLimit(10);
$tasks = $task_query->execute();
if ($tasks) {
$panel->addButton(
phutil_render_tag(
'a',
array(
// TODO: This should filter to just your projects' need-triage
// tasks?
'href' => '/maniphest/view/alltriage/',
'class' => 'grey button',
),
'View All Triage ('.$task_query->getRowCount().") \xC2\xBB"));
$panel->appendChild($this->buildTaskListView($tasks));
} else {
$panel->appendChild('<p>No tasks in your projects need triage.</p>');
}
return $panel;
}
private function buildRevisionPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$revision_query = new DifferentialRevisionQuery();
$revision_query->withStatus(DifferentialRevisionQuery::STATUS_OPEN);
$revision_query->withResponsibleUsers(array($user_phid));
$revision_query->needRelationships(true);
// NOTE: We need to unlimit this query to hit the responsible user
// fast-path.
$revision_query->setLimit(null);
$revisions = $revision_query->execute();
list($active, $waiting) = DifferentialRevisionQuery::splitResponsible(
$revisions,
$user_phid);
$panel = new AphrontPanelView();
$panel->setHeader('Revisions Waiting on You');
$panel->setCaption('Revisions waiting for you for review or commit.');
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/differential/',
'class' => 'button grey',
),
"View Active Revisions \xC2\xBB"));
if ($active) {
$revision_view = id(new DifferentialRevisionListView())
->setRevisions($active)
->setUser($user);
$phids = array_merge(
array($user_phid),
$revision_view->getRequiredHandlePHIDs());
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$revision_view->setHandles($handles);
$panel->appendChild($revision_view);
} else {
$panel->appendChild('<p>No revisions are waiting on you.</p>');
}
return $panel;
}
private function buildTasksPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$task_query = new ManiphestTaskQuery();
$task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$task_query->withOwners(array($user_phid));
$task_query->setCalculateRows(true);
$task_query->setLimit(10);
$tasks = $task_query->execute();
$panel = new AphrontPanelView();
$panel->setHeader('Assigned Tasks');
$panel->setCaption('Tasks assigned to you.');
if ($tasks) {
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/maniphest/',
'class' => 'button grey',
),
"View All Assigned Tasks (".$task_query->getRowCount().") \xC2\xBB"));
$panel->appendChild($this->buildTaskListView($tasks));
} else {
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/maniphest/?users='.
ManiphestTaskOwner::OWNER_UP_FOR_GRABS,
'class' => 'button grey',
),
"View Unassigned Tasks \xC2\xBB"));
$panel->appendChild('<p>You have no assigned tasks.</p>');
}
return $panel;
}
private function buildTaskListView(array $tasks) {
$user = $this->getRequest()->getUser();
$phids = array_filter(mpull($tasks, 'getOwnerPHID'));
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$view = new ManiphestTaskListView();
$view->setTasks($tasks);
$view->setUser($user);
$view->setHandles($handles);
return $view;
}
private function buildFeedView(array $projects) {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$feed_query = new PhabricatorFeedQuery();
$feed_query->setFilterPHIDs(
array_merge(
array($user_phid),
mpull($projects, 'getPHID')));
$feed = $feed_query->execute();
$builder = new PhabricatorFeedBuilder($feed);
$builder->setUser($user);
$feed_view = $builder->buildView();
return
'<div style="padding: 1em 1em;">'.
'<h1 style="font-size: 18px; '.
'border-bottom: 1px solid #aaaaaa; '.
'margin: 0 1em;">Feed</h1>'.
$feed_view->render().
'</div>';
}
}

View file

@ -6,10 +6,18 @@
phutil_require_module('phabricator', 'applications/differential/query/revision');
phutil_require_module('phabricator', 'applications/differential/view/revisionlist');
phutil_require_module('phabricator', 'applications/directory/controller/base');
phutil_require_module('phabricator', 'applications/directory/storage/category');
phutil_require_module('phabricator', 'applications/directory/storage/item');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'applications/feed/builder/feed');
phutil_require_module('phabricator', 'applications/feed/query');
phutil_require_module('phabricator', 'applications/maniphest/constants/owner');
phutil_require_module('phabricator', 'applications/maniphest/constants/priority');
phutil_require_module('phabricator', 'applications/maniphest/query');
phutil_require_module('phabricator', 'applications/maniphest/view/tasklist');
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'applications/project/query/project');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');

View file

@ -1,7 +1,7 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ final class AphrontPanelView extends AphrontView {
private $buttons = array();
private $header;
private $caption;
private $width;
private $classes = array();
private $id;
@ -66,6 +67,11 @@ final class AphrontPanelView extends AphrontView {
return $this;
}
public function setCaption($caption) {
$this->caption = $caption;
return $this;
}
public function render() {
if ($this->header !== null) {
$header = '<h1>'.$this->header.'</h1>';
@ -73,6 +79,15 @@ final class AphrontPanelView extends AphrontView {
$header = null;
}
if ($this->caption !== null) {
$caption =
'<div class="aphront-panel-view-caption">'.
$this->caption.
'</div>';
} else {
$caption = null;
}
$buttons = null;
if ($this->buttons) {
$buttons =
@ -97,7 +112,7 @@ final class AphrontPanelView extends AphrontView {
'class' => implode(' ', $classes),
'id' => $this->id,
),
$buttons.$header.$table);
$buttons.$header.$caption.$table);
}
}

View file

@ -17,6 +17,13 @@
padding: 2px 0 8px;
}
.aphront-panel-view-caption {
font-size: 11px;
color: #666666;
margin-top: -0.75em;
margin-bottom: 0.75em;
}
.aphront-panel-view-buttons {
float: right;
}

View file

@ -12,15 +12,16 @@ td.aphront-side-nav-content {
}
th.aphront-side-nav-navigation {
border-right: 1px solid #bbbbbb;
border-right: 1px solid #99c4d7;
padding-bottom: 8em;
background: #fbfbff;
}
th.aphront-side-nav-navigation a,
th.aphront-side-nav-navigation span {
display: block;
margin: 0 0 2px;
min-width: 150px;
min-width: 165px;
padding: 3px 8px 3px 24px;
font-weight: bold;
white-space: nowrap;
@ -30,11 +31,12 @@ th.aphront-side-nav-navigation span {
th.aphront-side-nav-navigation span {
padding-left: 12px;
padding-top: 6px;
color: #333333;
}
th.aphront-side-nav-navigation a:hover {
text-decoration: none;
background: #f3f3f9;
background: #e9e9f6;
}
th.aphront-side-nav-navigation hr {
@ -46,5 +48,5 @@ th.aphront-side-nav-navigation hr {
th.aphront-side-nav-navigation a.aphront-side-nav-selected,
th.aphront-side-nav-navigation a.aphront-side-nav-selected:hover {
background: #d8dfea;
background: #d8d8e6;
}

View file

@ -5,17 +5,11 @@
.aphront-directory-list {
margin: 1em 3% 8em;
margin: 2em;
}
.aphront-directory-category h1 {
border-bottom: 1px solid #cccccc;
margin-bottom: .5em;
padding-bottom: .1em;
}
.aphront-directory-list h2 {
font-size: 14px;
.aphront-directory-list h1 {
font-size: 16px;
font-weight: bold;
padding: 0;
margin: 0;
@ -27,12 +21,6 @@
padding: .05em .5em .5em;
}
.aphront-directory-category {
padding: 10px;
width: 300px;
float: left;
}
.aphront-directory-group {
padding: 0 .5em 3em;
.aphront-directory-item {
margin: 1em 0;
}