mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-11 07:11:04 +01:00
Allow Portals to be edited, and improve empty/blank states
Summary: Depends on D20348. Ref T13275. Portals are mostly just a "ProfileMenuEngine" menu, and that code is already relatively modular/flexible, so set that up to start with. The stuff it gets wrong right now is mostly around empty/no-permission states, since the original use cases (project menus) didn't have any of these states: it's not possible to have a project menu with no content. Let the engine render an "empty" state (when there are no items that can render a content page) and try to make some of the empty behavior a little more user-friendly. This mostly makes portals work, more or less. Test Plan: {F6322284} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13275 Differential Revision: https://secure.phabricator.com/D20349
This commit is contained in:
parent
90df4b2bd1
commit
d0d49d1efd
8 changed files with 269 additions and 23 deletions
|
@ -2953,13 +2953,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDashboardPortalEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardPortalEditEngine.php',
|
||||
'PhabricatorDashboardPortalEditor' => 'applications/dashboard/editor/PhabricatorDashboardPortalEditor.php',
|
||||
'PhabricatorDashboardPortalListController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalListController.php',
|
||||
'PhabricatorDashboardPortalMenuItem' => 'applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php',
|
||||
'PhabricatorDashboardPortalNameTransaction' => 'applications/dashboard/xaction/portal/PhabricatorDashboardPortalNameTransaction.php',
|
||||
'PhabricatorDashboardPortalPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardPortalPHIDType.php',
|
||||
'PhabricatorDashboardPortalProfileMenuEngine' => 'applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php',
|
||||
'PhabricatorDashboardPortalQuery' => 'applications/dashboard/query/PhabricatorDashboardPortalQuery.php',
|
||||
'PhabricatorDashboardPortalSearchConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPortalSearchConduitAPIMethod.php',
|
||||
'PhabricatorDashboardPortalSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPortalSearchEngine.php',
|
||||
'PhabricatorDashboardPortalStatus' => 'applications/dashboard/constants/PhabricatorDashboardPortalStatus.php',
|
||||
'PhabricatorDashboardPortalTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPortalTransaction.php',
|
||||
'PhabricatorDashboardPortalTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPortalTransactionQuery.php',
|
||||
'PhabricatorDashboardPortalTransactionType' => 'applications/dashboard/xaction/portal/PhabricatorDashboardPortalTransactionType.php',
|
||||
'PhabricatorDashboardPortalViewController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalViewController.php',
|
||||
'PhabricatorDashboardProfileController' => 'applications/dashboard/controller/PhabricatorDashboardProfileController.php',
|
||||
|
@ -8924,13 +8927,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDashboardPortalEditEngine' => 'PhabricatorEditEngine',
|
||||
'PhabricatorDashboardPortalEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhabricatorDashboardPortalListController' => 'PhabricatorDashboardPortalController',
|
||||
'PhabricatorDashboardPortalMenuItem' => 'PhabricatorProfileMenuItem',
|
||||
'PhabricatorDashboardPortalNameTransaction' => 'PhabricatorDashboardPortalTransactionType',
|
||||
'PhabricatorDashboardPortalPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorDashboardPortalProfileMenuEngine' => 'PhabricatorProfileMenuEngine',
|
||||
'PhabricatorDashboardPortalQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorDashboardPortalSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||
'PhabricatorDashboardPortalSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorDashboardPortalStatus' => 'Phobject',
|
||||
'PhabricatorDashboardPortalTransaction' => 'PhabricatorModularTransaction',
|
||||
'PhabricatorDashboardPortalTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorDashboardPortalTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'PhabricatorDashboardPortalViewController' => 'PhabricatorDashboardPortalController',
|
||||
'PhabricatorDashboardProfileController' => 'PhabricatorController',
|
||||
|
|
|
@ -27,6 +27,9 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication {
|
|||
}
|
||||
|
||||
public function getRoutes() {
|
||||
$menu_rules = $this->getProfileMenuRouting(
|
||||
'PhabricatorDashboardPortalViewController');
|
||||
|
||||
return array(
|
||||
'/W(?P<id>\d+)' => 'PhabricatorDashboardPanelViewController',
|
||||
'/dashboard/' => array(
|
||||
|
@ -62,8 +65,10 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication {
|
|||
'PhabricatorDashboardPortalListController',
|
||||
$this->getEditRoutePattern('edit/') =>
|
||||
'PhabricatorDashboardPortalEditController',
|
||||
'view/(?P<id>\d)/' =>
|
||||
'PhabricatorDashboardPortalViewController',
|
||||
'view/(?P<portalID>\d)/' => array(
|
||||
'' => 'PhabricatorDashboardPortalViewController',
|
||||
) + $menu_rules,
|
||||
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,13 +3,24 @@
|
|||
final class PhabricatorDashboardPortalViewController
|
||||
extends PhabricatorDashboardPortalController {
|
||||
|
||||
private $portal;
|
||||
|
||||
public function setPortal(PhabricatorDashboardPortal $portal) {
|
||||
$this->portal = $portal;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPortal() {
|
||||
return $this->portal;
|
||||
}
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
$id = $request->getURIData('portalID');
|
||||
|
||||
$portal = id(new PhabricatorDashboardPortalQuery())
|
||||
->setViewer($viewer)
|
||||
|
@ -19,16 +30,30 @@ final class PhabricatorDashboardPortalViewController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$content = $portal->getObjectName();
|
||||
$this->setPortal($portal);
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle(
|
||||
array(
|
||||
pht('Portal'),
|
||||
$portal->getName(),
|
||||
))
|
||||
->setPageObjectPHIDs(array($portal->getPHID()))
|
||||
->appendChild($content);
|
||||
$engine = id(new PhabricatorDashboardPortalProfileMenuEngine())
|
||||
->setProfileObject($portal)
|
||||
->setController($this);
|
||||
|
||||
return $engine->buildResponse();
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
$crumbs = parent::buildApplicationCrumbs();
|
||||
|
||||
$portal = $this->getPortal();
|
||||
if ($portal) {
|
||||
$crumbs->addTextCrumb($portal->getName(), $portal->getURI());
|
||||
}
|
||||
|
||||
return $crumbs;
|
||||
}
|
||||
|
||||
public function newTimelineView() {
|
||||
return $this->buildTransactionTimeline(
|
||||
$this->getPortal(),
|
||||
new PhabricatorDashboardPortalTransactionQuery());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -58,7 +58,11 @@ final class PhabricatorDashboardPortalEditEngine
|
|||
}
|
||||
|
||||
protected function getObjectViewURI($object) {
|
||||
return $object->getURI();
|
||||
if ($this->getIsCreate()) {
|
||||
return $object->getURI();
|
||||
} else {
|
||||
return '/portal/view/'.$object->getID().'/view/manage/';
|
||||
}
|
||||
}
|
||||
|
||||
protected function getEditorURI() {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDashboardPortalProfileMenuEngine
|
||||
extends PhabricatorProfileMenuEngine {
|
||||
|
||||
protected function isMenuEngineConfigurable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isMenuEnginePersonalizable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getItemURI($path) {
|
||||
$portal = $this->getProfileObject();
|
||||
|
||||
return $portal->getURI().$path;
|
||||
}
|
||||
|
||||
protected function getBuiltinProfileItems($object) {
|
||||
$items = array();
|
||||
|
||||
$items[] = $this->newManageItem();
|
||||
|
||||
$items[] = $this->newItem()
|
||||
->setMenuItemKey(PhabricatorDashboardPortalMenuItem::MENUITEMKEY)
|
||||
->setBuiltinKey('manage');
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
protected function newNoMenuItemsView() {
|
||||
return $this->newEmptyView(
|
||||
pht('New Portal'),
|
||||
pht('Use "Edit Menu" to add menu items to this portal.'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDashboardPortalMenuItem
|
||||
extends PhabricatorProfileMenuItem {
|
||||
|
||||
const MENUITEMKEY = 'portal';
|
||||
|
||||
public function getMenuItemTypeIcon() {
|
||||
return 'fa-compass';
|
||||
}
|
||||
|
||||
public function getDefaultName() {
|
||||
return pht('Manage Portal');
|
||||
}
|
||||
|
||||
public function getMenuItemTypeName() {
|
||||
return pht('Manage Portal');
|
||||
}
|
||||
|
||||
public function canHideMenuItem(
|
||||
PhabricatorProfileMenuItemConfiguration $config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canMakeDefault(
|
||||
PhabricatorProfileMenuItemConfiguration $config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getDisplayName(
|
||||
PhabricatorProfileMenuItemConfiguration $config) {
|
||||
$name = $config->getMenuItemProperty('name');
|
||||
|
||||
if (strlen($name)) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
return $this->getDefaultName();
|
||||
}
|
||||
|
||||
public function buildEditEngineFields(
|
||||
PhabricatorProfileMenuItemConfiguration $config) {
|
||||
return array(
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('name')
|
||||
->setLabel(pht('Name'))
|
||||
->setPlaceholder($this->getDefaultName())
|
||||
->setValue($config->getMenuItemProperty('name')),
|
||||
);
|
||||
}
|
||||
|
||||
protected function newNavigationMenuItems(
|
||||
PhabricatorProfileMenuItemConfiguration $config) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
if (!$viewer->isLoggedIn()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$href = $this->getItemViewURI($config);
|
||||
$name = $this->getDisplayName($config);
|
||||
$icon = 'fa-pencil';
|
||||
|
||||
$item = $this->newItem()
|
||||
->setHref($href)
|
||||
->setName($name)
|
||||
->setIcon($icon);
|
||||
|
||||
return array(
|
||||
$item,
|
||||
);
|
||||
}
|
||||
|
||||
public function newPageContent(
|
||||
PhabricatorProfileMenuItemConfiguration $config) {
|
||||
$viewer = $this->getViewer();
|
||||
$engine = $this->getEngine();
|
||||
$portal = $engine->getProfileObject();
|
||||
$controller = $engine->getController();
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Manage Portal'));
|
||||
|
||||
$edit_uri = urisprintf(
|
||||
'/portal/edit/%d/',
|
||||
$portal->getID());
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$portal,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$curtain = $controller->newCurtainView($portal)
|
||||
->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Edit Portal'))
|
||||
->setIcon('fa-pencil')
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(!$can_edit)
|
||||
->setHref($edit_uri));
|
||||
|
||||
$timeline = $controller->newTimelineView()
|
||||
->setShouldTerminate(true);
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setCurtain($curtain)
|
||||
->setMainColumn(
|
||||
array(
|
||||
$timeline,
|
||||
));
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDashboardPortalTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new PhabricatorDashboardPortalTransaction();
|
||||
}
|
||||
|
||||
}
|
|
@ -181,6 +181,10 @@ abstract class PhabricatorProfileMenuEngine extends Phobject {
|
|||
|
||||
switch ($item_action) {
|
||||
case 'view':
|
||||
// If we were not able to select an item, we're still going to render
|
||||
// a page state. For example, this happens when you create a new
|
||||
// portal for the first time.
|
||||
break;
|
||||
case 'info':
|
||||
case 'hide':
|
||||
case 'default':
|
||||
|
@ -231,24 +235,35 @@ abstract class PhabricatorProfileMenuEngine extends Phobject {
|
|||
}
|
||||
$page_title = pht('Configure Menu');
|
||||
} else {
|
||||
$page_title = $selected_item->getDisplayName();
|
||||
if ($selected_item) {
|
||||
$page_title = $selected_item->getDisplayName();
|
||||
} else {
|
||||
$page_title = pht('Empty');
|
||||
}
|
||||
}
|
||||
|
||||
switch ($item_action) {
|
||||
case 'view':
|
||||
$navigation->selectFilter($selected_item->getDefaultMenuItemKey());
|
||||
if ($selected_item) {
|
||||
$navigation->selectFilter($selected_item->getDefaultMenuItemKey());
|
||||
|
||||
try {
|
||||
$content = $this->buildItemViewContent($selected_item);
|
||||
} catch (Exception $ex) {
|
||||
$content = id(new PHUIInfoView())
|
||||
->setTitle(pht('Unable to Render Dashboard'))
|
||||
->setErrors(array($ex->getMessage()));
|
||||
try {
|
||||
$content = $this->buildItemViewContent($selected_item);
|
||||
} catch (Exception $ex) {
|
||||
$content = id(new PHUIInfoView())
|
||||
->setTitle(pht('Unable to Render Dashboard'))
|
||||
->setErrors(array($ex->getMessage()));
|
||||
}
|
||||
|
||||
$crumbs->addTextCrumb($selected_item->getDisplayName());
|
||||
} else {
|
||||
$content = $this->newNoMenuItemsView();
|
||||
}
|
||||
|
||||
$crumbs->addTextCrumb($selected_item->getDisplayName());
|
||||
if (!$content) {
|
||||
return new Aphront404Response();
|
||||
$content = $this->newEmptyView(
|
||||
pht('Empty'),
|
||||
pht('There is nothing here.'));
|
||||
}
|
||||
break;
|
||||
case 'configure':
|
||||
|
@ -346,6 +361,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject {
|
|||
if ($this->navigation) {
|
||||
return $this->navigation;
|
||||
}
|
||||
|
||||
$nav = id(new AphrontSideNavFilterView())
|
||||
->setIsProfileMenu(true)
|
||||
->setBaseURI(new PhutilURI($this->getItemURI('')));
|
||||
|
@ -365,6 +381,7 @@ abstract class PhabricatorProfileMenuEngine extends Phobject {
|
|||
$first_item->willBuildNavigationItems($group);
|
||||
}
|
||||
|
||||
$has_items = false;
|
||||
foreach ($menu_items as $menu_item) {
|
||||
if ($menu_item->isDisabled()) {
|
||||
continue;
|
||||
|
@ -389,9 +406,18 @@ abstract class PhabricatorProfileMenuEngine extends Phobject {
|
|||
|
||||
foreach ($items as $item) {
|
||||
$nav->addMenuItem($item);
|
||||
$has_items = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$has_items) {
|
||||
// If the navigation menu has no items, add an empty label item to
|
||||
// force it to render something.
|
||||
$empty_item = id(new PHUIListItemView())
|
||||
->setType(PHUIListItemView::TYPE_LABEL);
|
||||
$nav->addMenuItem($empty_item);
|
||||
}
|
||||
|
||||
$nav->selectFilter(null);
|
||||
|
||||
$this->navigation = $nav;
|
||||
|
@ -1319,5 +1345,20 @@ abstract class PhabricatorProfileMenuEngine extends Phobject {
|
|||
return $items;
|
||||
}
|
||||
|
||||
final protected function newEmptyView($title, $message) {
|
||||
return id(new PHUIInfoView())
|
||||
->setTitle($title)
|
||||
->setSeverity(PHUIInfoView::SEVERITY_NODATA)
|
||||
->setErrors(
|
||||
array(
|
||||
$message,
|
||||
));
|
||||
}
|
||||
|
||||
protected function newNoMenuItemsView() {
|
||||
return $this->newEmptyView(
|
||||
pht('No Menu Items'),
|
||||
pht('There are no menu items.'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue