1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-23 07:12:41 +01:00

Make mobile navigation work properly by default in more cases

Summary:
Fixes T5752. This obsoletes a bunch of old patterns and I'll follow up on those with a big "go do a bunch of mechanical code changes" task. Major goals are:

  - Don't load named queries multiple times on search pages.
  - Don't require extra code to get standard navigation right on mobile.
  - Reduce the amount of boilerplate in ListControllers.
  - Reduce the amount of boilerplate around navigation/menus in all controllers.

Specifically, here's what this does:

  - The StandardPage is now a smarter/more structured object with `setNavigation()` and `setCrumbs()` methods. More rendering decisions are delayed until the last possible moment.
    - It uses this to automatically add crumb actions to the application menu.
    - It uses this to automatically reuse one SearchEngine instead of running queries multiple times.
  - The new preferred way to build responses is `$this->newPage()` (like `$this->newDialog()`), which has structured methods for adding stuff (`setTitle()`, etc).
  - SearchEngine exposes a new convenience method so you don't have to do all the controller delegation stuff.
  - Building menus is generally simpler.

Test Plan:
  - Tested paste list, view, edit, comment, raw controllers for functionality, mobile menu, crumbs, navigation menu.
  - Edited saved queries.
  - Tested Differential, Maniphest (no changes).
  - Verified the paste pages don't run any duplicate NamedQuery queries.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T5752

Differential Revision: https://secure.phabricator.com/D14382
This commit is contained in:
epriestley 2015-11-02 12:06:28 -08:00
parent 2ca269cb31
commit 300c74c49d
11 changed files with 430 additions and 201 deletions

View file

@ -1403,6 +1403,7 @@ phutil_register_library_map(array(
'PHUI' => 'view/phui/PHUI.php',
'PHUIActionPanelExample' => 'applications/uiexample/examples/PHUIActionPanelExample.php',
'PHUIActionPanelView' => 'view/phui/PHUIActionPanelView.php',
'PHUIApplicationMenuView' => 'view/layout/PHUIApplicationMenuView.php',
'PHUIBadgeBoxView' => 'view/phui/PHUIBadgeBoxView.php',
'PHUIBadgeExample' => 'applications/uiexample/examples/PHUIBadgeExample.php',
'PHUIBadgeMiniView' => 'view/phui/PHUIBadgeMiniView.php',
@ -5310,6 +5311,7 @@ phutil_register_library_map(array(
'PHUI' => 'Phobject',
'PHUIActionPanelExample' => 'PhabricatorUIExample',
'PHUIActionPanelView' => 'AphrontTagView',
'PHUIApplicationMenuView' => 'Phobject',
'PHUIBadgeBoxView' => 'AphrontTagView',
'PHUIBadgeExample' => 'PhabricatorUIExample',
'PHUIBadgeMiniView' => 'AphrontTagView',
@ -7182,7 +7184,10 @@ phutil_register_library_map(array(
'PhabricatorStandardCustomFieldText' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldTokenizer' => 'PhabricatorStandardCustomFieldPHIDs',
'PhabricatorStandardCustomFieldUsers' => 'PhabricatorStandardCustomFieldTokenizer',
'PhabricatorStandardPageView' => 'PhabricatorBarePageView',
'PhabricatorStandardPageView' => array(
'PhabricatorBarePageView',
'AphrontResponseProducerInterface',
),
'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorStatusController' => 'PhabricatorController',
'PhabricatorStatusUIExample' => 'PhabricatorUIExample',

View file

@ -3,7 +3,6 @@
abstract class PhabricatorController extends AphrontController {
private $handles;
private $extraQuicksandConfig = array();
public function shouldRequireLogin() {
return true;
@ -62,15 +61,6 @@ abstract class PhabricatorController extends AphrontController {
return false;
}
public function addExtraQuicksandConfig($config) {
$this->extraQuicksandConfig += $config;
return $this;
}
private function getExtraQuicksandConfig() {
return $this->extraQuicksandConfig;
}
public function willBeginExecution() {
$request = $this->getRequest();
@ -285,32 +275,6 @@ abstract class PhabricatorController extends AphrontController {
}
}
public function buildStandardPageView() {
$view = new PhabricatorStandardPageView();
$view->setRequest($this->getRequest());
$view->setController($this);
return $view;
}
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
$page->appendChild($view);
return $this->buildPageResponse($page);
}
private function buildPageResponse($page) {
if ($this->getRequest()->isQuicksand()) {
$response = id(new AphrontAjaxResponse())
->setContent($page->renderForQuicksand(
$this->getExtraQuicksandConfig()));
} else {
$response = id(new AphrontWebpageResponse())
->setContent($page->render());
}
return $response;
}
public function getApplicationURI($path = '') {
if (!$this->getCurrentApplication()) {
throw new Exception(pht('No application!'));
@ -318,62 +282,6 @@ abstract class PhabricatorController extends AphrontController {
return $this->getCurrentApplication()->getApplicationURI($path);
}
public function buildApplicationPage($view, array $options) {
$page = $this->buildStandardPageView();
$title = PhabricatorEnv::getEnvConfig('phabricator.serious-business') ?
'Phabricator' :
pht('Bacon Ice Cream for Breakfast');
$application = $this->getCurrentApplication();
$page->setTitle(idx($options, 'title', $title));
if ($application) {
$page->setApplicationName($application->getName());
if ($application->getTitleGlyph()) {
$page->setGlyph($application->getTitleGlyph());
}
}
if (idx($options, 'class')) {
$page->addClass($options['class']);
}
if (!($view instanceof AphrontSideNavFilterView)) {
$nav = new AphrontSideNavFilterView();
$nav->appendChild($view);
$view = $nav;
}
$user = $this->getRequest()->getUser();
$view->setUser($user);
$page->appendChild($view);
$object_phids = idx($options, 'pageObjects', array());
if ($object_phids) {
$page->appendPageObjects($object_phids);
foreach ($object_phids as $object_phid) {
PhabricatorFeedStoryNotification::updateObjectNotificationViews(
$user,
$object_phid);
}
}
if (idx($options, 'device', true)) {
$page->setDeviceReady(true);
}
$page->setShowFooter(idx($options, 'showFooter', true));
$page->setShowChrome(idx($options, 'chrome', true));
$application_menu = $this->buildApplicationMenu();
if ($application_menu) {
$page->setApplicationMenu($application_menu);
}
return $this->buildPageResponse($page);
}
public function willSendResponse(AphrontResponse $response) {
$request = $this->getRequest();
@ -536,6 +444,35 @@ abstract class PhabricatorController extends AphrontController {
->setSubmitURI($submit_uri);
}
public function newPage() {
$page = id(new PhabricatorStandardPageView())
->setRequest($this->getRequest())
->setController($this);
$application = $this->getCurrentApplication();
if ($application) {
$page->setApplicationName($application->getName());
if ($application->getTitleGlyph()) {
$page->setGlyph($application->getTitleGlyph());
}
}
$viewer = $this->getRequest()->getUser();
if ($viewer) {
$page->setUser($viewer);
}
// TODO: Remove after removing callsites to addExtraQuicksandConfig().
$page->addQuicksandConfig($this->extraQuicksandConfig);
return $page;
}
public function newApplicationMenu() {
return id(new PHUIApplicationMenuView())
->setViewer($this->getRequest()->getUser());
}
protected function buildTransactionTimeline(
PhabricatorApplicationTransactionInterface $object,
PhabricatorApplicationTransactionQuery $query,
@ -583,4 +520,81 @@ abstract class PhabricatorController extends AphrontController {
return $timeline;
}
/* -( Deprecated )--------------------------------------------------------- */
/**
* DEPRECATED.
*/
private $extraQuicksandConfig = array();
/**
* DEPRECATED. Use @{method:newPage} and call addQuicksandConfig().
*/
public function addExtraQuicksandConfig($config) {
// TODO: When this method is removed,
$this->extraQuicksandConfig += $config;
return $this;
}
/**
* DEPRECATED. Use @{method:newPage}.
*/
public function buildStandardPageView() {
return $this->newPage();
}
/**
* DEPRECATED. Use @{method:newPage}.
*/
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
$page->appendChild($view);
return $page->produceAphrontResponse();
}
/**
* DEPRECATED. Use @{method:newPage}.
*/
public function buildApplicationPage($view, array $options) {
$page = $this->newPage();
$title = PhabricatorEnv::getEnvConfig('phabricator.serious-business') ?
'Phabricator' :
pht('Bacon Ice Cream for Breakfast');
$page->setTitle(idx($options, 'title', $title));
if (idx($options, 'class')) {
$page->addClass($options['class']);
}
if (!($view instanceof AphrontSideNavFilterView)) {
$nav = new AphrontSideNavFilterView();
$nav->appendChild($view);
$view = $nav;
}
$page->appendChild($view);
$object_phids = idx($options, 'pageObjects', array());
if ($object_phids) {
$page->setPageObjectPHIDs($object_phids);
}
if (idx($options, 'device', true)) {
$page->setDeviceReady(true);
}
$page->setShowFooter(idx($options, 'showFooter', true));
$page->setShowChrome(idx($options, 'chrome', true));
return $page->produceAphrontResponse();
}
}

View file

@ -2,39 +2,9 @@
abstract class PhabricatorPasteController extends PhabricatorController {
public function buildSideNavView($for_app = false) {
$user = $this->getRequest()->getUser();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
if ($for_app) {
$nav->addFilter('create', pht('Create Paste'));
}
id(new PhabricatorPasteSearchEngine())
->setViewer($user)
->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
return $nav;
}
public function buildApplicationMenu() {
return $this->buildSideNavView(true)->getMenu();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Create Paste'))
->setHref($this->getApplicationURI('create/'))
->setIcon('fa-plus-square'));
return $crumbs;
return $this->newApplicationMenu()
->setSearchEngine(new PhabricatorPasteSearchEngine());
}
public function buildSourceCodeView(

View file

@ -231,7 +231,7 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
$form_box->setValidationException($validation_exception);
}
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
$crumbs = $this->buildApplicationCrumbs();
if (!$is_create) {
$crumbs->addTextCrumb('P'.$paste->getID(), '/P'.$paste->getID());
}

View file

@ -7,15 +7,21 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
}
public function handleRequest(AphrontRequest $request) {
$querykey = $request->getURIData('queryKey');
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($querykey)
->setSearchEngine(new PhabricatorPasteSearchEngine())
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
return id(new PhabricatorPasteSearchEngine())
->setController($this)
->buildResponse();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Create Paste'))
->setHref($this->getApplicationURI('create/'))
->setIcon('fa-plus-square'));
return $crumbs;
}
}

View file

@ -66,7 +66,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController {
),
$source_code);
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView())
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb('P'.$paste->getID(), '/P'.$paste->getID());
$timeline = $this->buildTransactionTimeline(
@ -89,17 +89,19 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController {
->setAction($this->getApplicationURI('/comment/'.$paste->getID().'/'))
->setSubmitButtonName(pht('Add Comment'));
return $this->buildApplicationPage(
return $this->newPage()
->setTitle($paste->getFullName())
->setCrumbs($crumbs)
->setPageObjectPHIDs(
array(
$paste->getPHID(),
))
->appendChild(
array(
$crumbs,
$object_box,
$source_code,
$timeline,
$add_comment_form,
),
array(
'title' => $paste->getFullName(),
'pageObjects' => array($paste->getPHID()),
));
}

View file

@ -58,11 +58,6 @@ final class PhabricatorApplicationSearchController
throw new PhutilInvalidStateException('setEngine');
}
$nav = $this->getNavigation();
if (!$nav) {
throw new PhutilInvalidStateException('setNavigation');
}
$engine->setViewer($this->getRequest()->getUser());
$parent = $this->getDelegatingController();
@ -85,6 +80,9 @@ final class PhabricatorApplicationSearchController
$user = $request->getUser();
$engine = $this->getSearchEngine();
$nav = $this->getNavigation();
if (!$nav) {
$nav = $this->buildNavigation();
}
if ($request->isFormPost()) {
$saved_query = $engine->buildSavedQueryFromRequest($request);
@ -174,9 +172,10 @@ final class PhabricatorApplicationSearchController
// we sort out T5307.
$form->appendChild($submit);
$body = array();
if ($this->getPreface()) {
$nav->appendChild($this->getPreface());
$body[] = $this->getPreface();
}
if ($named_query) {
@ -202,7 +201,7 @@ final class PhabricatorApplicationSearchController
$box->setForm($form);
}
$nav->appendChild($box);
$body[] = $box;
if ($run_query) {
$box->setAnchor(
@ -257,7 +256,7 @@ final class PhabricatorApplicationSearchController
->addMargin(PHUI::MARGIN_LARGE)
->setBorder(true)
->appendChild($pager);
$nav->appendChild($pager_box);
$body[] = $pager_box;
}
} catch (PhabricatorTypeaheadInvalidTokenException $ex) {
@ -275,13 +274,12 @@ final class PhabricatorApplicationSearchController
->buildApplicationCrumbs()
->addTextCrumb($title);
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Query: %s', $title),
));
return $this->newPage()
->setApplicationMenu($this->buildApplicationMenu())
->setTitle(pht('Query: %s', $title))
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($body);
}
private function processEditRequest() {
@ -289,7 +287,11 @@ final class PhabricatorApplicationSearchController
$request = $this->getRequest();
$user = $request->getUser();
$engine = $this->getSearchEngine();
$nav = $this->getNavigation();
if (!$nav) {
$nav = $this->buildNavigation();
}
$named_queries = $engine->loadAllNamedQueries();
@ -357,23 +359,41 @@ final class PhabricatorApplicationSearchController
->addTextCrumb(pht('Saved Queries'), $engine->getQueryManagementURI());
$nav->selectFilter('query/edit');
$nav->setCrumbs($crumbs);
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Saved Queries'))
->setObjectList($list);
$nav->appendChild($box);
return $parent->buildApplicationPage(
$nav,
array(
'title' => pht('Saved Queries'),
));
return $this->newPage()
->setApplicationMenu($this->buildApplicationMenu())
->setTitle(pht('Saved Queries'))
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($box);
}
public function buildApplicationMenu() {
return $this->getDelegatingController()->buildApplicationMenu();
$menu = $this->getDelegatingController()
->buildApplicationMenu();
if ($menu instanceof PHUIApplicationMenuView) {
$menu->setSearchEngine($this->getSearchEngine());
}
return $menu;
}
private function buildNavigation() {
$viewer = $this->getViewer();
$engine = $this->getSearchEngine();
$nav = id(new AphrontSideNavFilterView())
->setUser($viewer)
->setBaseURI(new PhutilURI($this->getApplicationURI()));
$engine->addNavigationItems($nav->getMenu());
return $nav;
}
}

View file

@ -22,10 +22,32 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
private $customFields = false;
private $request;
private $context;
private $controller;
private $namedQueries;
const CONTEXT_LIST = 'list';
const CONTEXT_PANEL = 'panel';
public function setController(PhabricatorController $controller) {
$this->controller = $controller;
return $this;
}
public function getController() {
return $this->controller;
}
public function buildResponse() {
$controller = $this->getController();
$request = $controller->getRequest();
$search = id(new PhabricatorApplicationSearchController())
->setQueryKey($request->getURIData('queryKey'))
->setSearchEngine($this);
return $controller->delegateToController($search);
}
public function newResultObject() {
// We may be able to get this automatically if newQuery() is implemented.
$query = $this->newQuery();
@ -459,7 +481,9 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
public function loadAllNamedQueries() {
$viewer = $this->requireViewer();
$builtin = $this->getBuiltinQueries($viewer);
if ($this->namedQueries === null) {
$named_queries = id(new PhabricatorNamedQueryQuery())
->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
@ -467,7 +491,6 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
->execute();
$named_queries = mpull($named_queries, null, 'getQueryKey');
$builtin = $this->getBuiltinQueries($viewer);
$builtin = mpull($builtin, null, 'getQueryKey');
foreach ($named_queries as $key => $named_query) {
@ -484,8 +507,10 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
}
$named_queries = msort($named_queries, 'getSortKey');
$this->namedQueries = $named_queries;
}
return $named_queries + $builtin;
return $this->namedQueries + $builtin;
}
public function loadEnabledNamedQueries() {

View file

@ -0,0 +1,89 @@
<?php
final class PHUIApplicationMenuView extends Phobject {
private $viewer;
private $crumbs;
private $searchEngine;
private $items = array();
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function addLabel($name) {
$item = id(new PHUIListItemView())
->setName($name);
return $this->addItem($item);
}
public function addLink($name, $href) {
$item = id(new PHUIListItemView())
->setName($name)
->setHref($href);
return $this->addItem($item);
}
public function addItem(PHUIListItemView $item) {
$this->items[] = $item;
return $this;
}
public function setSearchEngine(PhabricatorApplicationSearchEngine $engine) {
$this->searchEngine = $engine;
return $this;
}
public function getSearchEngine() {
return $this->searchEngine;
}
public function setCrumbs(PHUICrumbsView $crumbs) {
$this->crumbs = $crumbs;
return $this;
}
public function getCrumbs() {
return $this->crumbs;
}
public function buildListView() {
$viewer = $this->getViewer();
$view = id(new PHUIListView())
->setUser($viewer);
$crumbs = $this->getCrumbs();
if ($crumbs) {
$actions = $crumbs->getActions();
if ($actions) {
$view->newLabel(pht('Create'));
foreach ($crumbs->getActions() as $action) {
$view->addMenuItem($action);
}
}
}
$engine = $this->getSearchEngine();
if ($engine) {
$engine
->setViewer($viewer)
->addNavigationItems($view);
}
foreach ($this->items as $item) {
$view->addMenuItem($item);
}
return $view;
}
}

View file

@ -4,7 +4,8 @@
* This is a standard Phabricator page with menus, Javelin, DarkConsole, and
* basic styles.
*/
final class PhabricatorStandardPageView extends PhabricatorBarePageView {
final class PhabricatorStandardPageView extends PhabricatorBarePageView
implements AphrontResponseProducerInterface {
private $baseURI;
private $applicationName;
@ -17,6 +18,9 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
private $applicationMenu;
private $showFooter = true;
private $showDurableColumn = true;
private $quicksandConfig = array();
private $crumbs;
private $navigation;
public function setShowFooter($show_footer) {
$this->showFooter = $show_footer;
@ -27,7 +31,10 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
return $this->showFooter;
}
public function setApplicationMenu(PHUIListView $application_menu) {
public function setApplicationMenu($application_menu) {
// NOTE: For now, this can either be a PHUIListView or a
// PHUIApplicationMenuView.
$this->applicationMenu = $application_menu;
return $this;
}
@ -73,10 +80,9 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
return $this;
}
public function appendPageObjects(array $objs) {
foreach ($objs as $obj) {
$this->pageObjects[] = $obj;
}
public function setPageObjectPHIDs(array $phids) {
$this->pageObjects = $phids;
return $this;
}
public function setShowDurableColumn($show) {
@ -130,6 +136,32 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
return (bool)$this->getUserPreference($column_key, 0);
}
public function addQuicksandConfig(array $config) {
$this->quicksandConfig = $config + $this->quicksandConfig;
return $this;
}
public function getQuicksandConfig() {
return $this->quicksandConfig;
}
public function setCrumbs(PHUICrumbsView $crumbs) {
$this->crumbs = $crumbs;
return $this;
}
public function getCrumbs() {
return $this->crumbs;
}
public function setNavigation(AphrontSideNavFilterView $navigation) {
$this->navigation = $navigation;
return $this;
}
public function getNavigation() {
return $this->navigation;
}
public function getTitle() {
$glyph_key = PhabricatorUserPreferences::PREFERENCE_TITLES;
@ -271,8 +303,18 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
$menu->setController($this->getController());
}
if ($this->getApplicationMenu()) {
$menu->setApplicationMenu($this->getApplicationMenu());
$application_menu = $this->getApplicationMenu();
if ($application_menu) {
if ($application_menu instanceof PHUIApplicationMenuView) {
$crumbs = $this->getCrumbs();
if ($crumbs) {
$application_menu->setCrumbs($crumbs);
}
$application_menu = $application_menu->buildListView();
}
$menu->setApplicationMenu($application_menu);
}
$this->menuContent = $menu->render();
@ -433,9 +475,26 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
private function renderPageBodyContent() {
$console = $this->getConsole();
$body = parent::getBody();
$nav = $this->getNavigation();
if ($nav) {
$crumbs = $this->getCrumbs();
if ($crumbs) {
$nav->setCrumbs($crumbs);
}
$nav->appendChild($body);
$body = phutil_implode_html('', array($nav->render()));
} else {
$crumbs = $this->getCrumbs();
if ($crumbs) {
$body = phutil_implode_html('', array($crumbs, $body));
}
}
return array(
($console ? hsprintf('<darkconsole />') : null),
parent::getBody(),
$body,
$this->renderFooter(),
);
}
@ -619,11 +678,13 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
$foot);
}
public function renderForQuicksand(array $extra_config) {
public function renderForQuicksand() {
parent::willRenderPage();
$response = $this->renderPageBodyContent();
$response = $this->willSendResponse($response);
$extra_config = $this->getQuicksandConfig();
return array(
'content' => hsprintf('%s', $response),
) + $this->buildQuicksandConfig()
@ -732,4 +793,37 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
return $user->loadPreferences()->getPreference($key, $default);
}
public function produceAphrontResponse() {
$controller = $this->getController();
if (!$this->getApplicationMenu()) {
$application_menu = $controller->buildApplicationMenu();
if ($application_menu) {
$this->setApplicationMenu($application_menu);
}
}
$viewer = $this->getUser();
if ($viewer && $viewer->getPHID()) {
$object_phids = $this->pageObjects;
foreach ($object_phids as $object_phid) {
PhabricatorFeedStoryNotification::updateObjectNotificationViews(
$viewer,
$object_phid);
}
}
if ($this->getRequest()->isQuicksand()) {
$content = $this->renderForQuicksand();
$response = id(new AphrontAjaxResponse())
->setContent($content);
} else {
$content = $this->render();
$response = id(new AphrontWebpageResponse())
->setContent($content);
}
return $response;
}
}

View file

@ -41,6 +41,10 @@ final class PHUICrumbsView extends AphrontView {
return $this;
}
public function getActions() {
return $this->actions;
}
public function render() {
require_celerity_resource('phui-crumbs-view-css');