1
0
Fork 0
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:
epriestley 2019-03-29 11:00:23 -07:00
parent 90df4b2bd1
commit d0d49d1efd
8 changed files with 269 additions and 23 deletions

View file

@ -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',

View file

@ -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,
),
);
}

View file

@ -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());
}
}

View file

@ -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() {

View file

@ -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.'));
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,10 @@
<?php
final class PhabricatorDashboardPortalTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new PhabricatorDashboardPortalTransaction();
}
}

View file

@ -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.'));
}
}