1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 08:52:39 +01:00

Project revamp part 2: Edit

Summary:
Taking a pass at revamping the edit pages in Projects. Specifically:

 - Remove EditMainController
 - Move actions from EditMain to Profile
 - Move properties from EditMain to Profile
 - Move timeline from EditMain to Profile
 - Move Open Tasks from Profile to sidenavicon
 - Add custom icons and colors to timeline

Feel free to bang on this a bit and give feedback, feels generally correct to me.

Test Plan: Edit everything I could on various projects. Check links, timelines, actions.

Reviewers: btrahan, epriestley

Reviewed By: epriestley

Subscribers: Korvin, epriestley

Differential Revision: https://secure.phabricator.com/D11421
This commit is contained in:
Chad Little 2015-01-19 10:14:27 -08:00
parent 9a7ad972cd
commit 3bc54c2041
12 changed files with 128 additions and 266 deletions

View file

@ -2204,7 +2204,6 @@ phutil_register_library_map(array(
'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php', 'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php',
'PhabricatorProjectEditDetailsController' => 'applications/project/controller/PhabricatorProjectEditDetailsController.php', 'PhabricatorProjectEditDetailsController' => 'applications/project/controller/PhabricatorProjectEditDetailsController.php',
'PhabricatorProjectEditIconController' => 'applications/project/controller/PhabricatorProjectEditIconController.php', 'PhabricatorProjectEditIconController' => 'applications/project/controller/PhabricatorProjectEditIconController.php',
'PhabricatorProjectEditMainController' => 'applications/project/controller/PhabricatorProjectEditMainController.php',
'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php',
'PhabricatorProjectEditorTestCase' => 'applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php', 'PhabricatorProjectEditorTestCase' => 'applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php',
'PhabricatorProjectFeedController' => 'applications/project/controller/PhabricatorProjectFeedController.php', 'PhabricatorProjectFeedController' => 'applications/project/controller/PhabricatorProjectFeedController.php',
@ -5444,7 +5443,6 @@ phutil_register_library_map(array(
'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField', 'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField',
'PhabricatorProjectEditDetailsController' => 'PhabricatorProjectController', 'PhabricatorProjectEditDetailsController' => 'PhabricatorProjectController',
'PhabricatorProjectEditIconController' => 'PhabricatorProjectController', 'PhabricatorProjectEditIconController' => 'PhabricatorProjectController',
'PhabricatorProjectEditMainController' => 'PhabricatorProjectController',
'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController',
'PhabricatorProjectEditorTestCase' => 'PhabricatorTestCase', 'PhabricatorProjectEditorTestCase' => 'PhabricatorTestCase',
'PhabricatorProjectFeedController' => 'PhabricatorProjectController', 'PhabricatorProjectFeedController' => 'PhabricatorProjectController',

View file

@ -72,7 +72,7 @@ final class PhabricatorFileComposeController
)); ));
if ($project_phid) { if ($project_phid) {
$edit_uri = '/project/edit/'.$project->getID().'/'; $edit_uri = '/project/profile/'.$project->getID().'/';
$xactions = array(); $xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction()) $xactions[] = id(new PhabricatorProjectTransaction())

View file

@ -43,7 +43,6 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
'/project/' => array( '/project/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorProjectListController', '(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorProjectListController',
'filter/(?P<filter>[^/]+)/' => 'PhabricatorProjectListController', 'filter/(?P<filter>[^/]+)/' => 'PhabricatorProjectListController',
'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectEditMainController',
'details/(?P<id>[1-9]\d*)/' 'details/(?P<id>[1-9]\d*)/'
=> 'PhabricatorProjectEditDetailsController', => 'PhabricatorProjectEditDetailsController',
'archive/(?P<id>[1-9]\d*)/' 'archive/(?P<id>[1-9]\d*)/'

View file

@ -26,7 +26,7 @@ final class PhabricatorProjectArchiveController
return new Aphront404Response(); return new Aphront404Response();
} }
$edit_uri = $this->getApplicationURI('edit/'.$project->getID().'/'); $edit_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
if ($request->isFormPost()) { if ($request->isFormPost()) {
if ($project->isArchived()) { if ($project->isArchived()) {

View file

@ -7,28 +7,27 @@ abstract class PhabricatorProjectController extends PhabricatorController {
} }
public function buildSideNavView($for_app = false) { public function buildSideNavView($for_app = false) {
$user = $this->getRequest()->getUser(); $viewer = $this->getViewer();
$nav = new AphrontSideNavFilterView(); $nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI())); $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
$id = null; $id = null;
if ($for_app) { if ($for_app) {
$user = $this->getRequest()->getUser();
$id = $this->getRequest()->getURIData('id'); $id = $this->getRequest()->getURIData('id');
if ($id) { if ($id) {
$nav->addFilter("profile/{$id}/", pht('Profile')); $nav->addFilter("profile/{$id}/", pht('Profile'));
$nav->addFilter("board/{$id}/", pht('Workboard')); $nav->addFilter("board/{$id}/", pht('Workboard'));
$nav->addFilter("members/{$id}/", pht('Members')); $nav->addFilter("members/{$id}/", pht('Members'));
$nav->addFilter("feed/{$id}/", pht('Feed')); $nav->addFilter("feed/{$id}/", pht('Feed'));
$nav->addFilter("edit/{$id}/", pht('Edit')); $nav->addFilter("details/{$id}/", pht('Edit Details'));
} }
$nav->addFilter('create', pht('Create Project')); $nav->addFilter('create', pht('Create Project'));
} }
if (!$id) { if (!$id) {
id(new PhabricatorProjectSearchEngine()) id(new PhabricatorProjectSearchEngine())
->setViewer($user) ->setViewer($viewer)
->addNavigationItems($nav->getMenu()); ->addNavigationItems($nav->getMenu());
} }
@ -38,13 +37,13 @@ abstract class PhabricatorProjectController extends PhabricatorController {
} }
public function buildIconNavView(PhabricatorProject $project) { public function buildIconNavView(PhabricatorProject $project) {
$user = $this->getRequest()->getUser(); $viewer = $this->getViewer();
$id = $project->getID(); $id = $project->getID();
$picture = $project->getProfileImageURI(); $picture = $project->getProfileImageURI();
$name = $project->getName(); $name = $project->getName();
$columns = id(new PhabricatorProjectColumnQuery()) $columns = id(new PhabricatorProjectColumnQuery())
->setViewer($user) ->setViewer($viewer)
->withProjectPHIDs(array($project->getPHID())) ->withProjectPHIDs(array($project->getPHID()))
->execute(); ->execute();
if ($columns) { if ($columns) {
@ -58,9 +57,20 @@ abstract class PhabricatorProjectController extends PhabricatorController {
$nav->setBaseURI(new PhutilURI($this->getApplicationURI())); $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
$nav->addIcon("profile/{$id}/", $name, null, $picture); $nav->addIcon("profile/{$id}/", $name, null, $picture);
$nav->addIcon("board/{$id}/", pht('Workboard'), $board_icon); $nav->addIcon("board/{$id}/", pht('Workboard'), $board_icon);
$class = 'PhabricatorManiphestApplication';
if (PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
$phid = $project->getPHID();
$query_uri = urisprintf(
'/maniphest/?statuses=%s&allProjects=%s#R',
implode(',', ManiphestTaskStatus::getOpenStatusConstants()),
$phid);
$nav->addIcon(null, pht('Open Tasks'), 'fa-anchor', null, $query_uri);
}
$nav->addIcon("feed/{$id}/", pht('Feed'), 'fa-newspaper-o'); $nav->addIcon("feed/{$id}/", pht('Feed'), 'fa-newspaper-o');
$nav->addIcon("members/{$id}/", pht('Members'), 'fa-group'); $nav->addIcon("members/{$id}/", pht('Members'), 'fa-group');
$nav->addIcon("edit/{$id}/", pht('Edit'), 'fa-pencil'); $nav->addIcon("details/{$id}/", pht('Edit Details'), 'fa-pencil');
return $nav; return $nav;
} }

View file

@ -149,13 +149,8 @@ final class PhabricatorProjectEditDetailsController
)); ));
} }
if ($is_new) { $redirect_uri =
$redirect_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
$this->getApplicationURI('profile/'.$project->getID().'/');
} else {
$redirect_uri =
$this->getApplicationURI('edit/'.$project->getID().'/');
}
return id(new AphrontRedirectResponse())->setURI($redirect_uri); return id(new AphrontRedirectResponse())->setURI($redirect_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) { } catch (PhabricatorApplicationTransactionValidationException $ex) {
@ -304,7 +299,7 @@ final class PhabricatorProjectEditDetailsController
if (!$is_new) { if (!$is_new) {
$nav = $this->buildIconNavView($project); $nav = $this->buildIconNavView($project);
$nav->selectFilter("edit/{$id}/"); $nav->selectFilter("details/{$id}/");
$nav->appendChild($form_box); $nav->appendChild($form_box);
} else { } else {
$nav = array($form_box); $nav = array($form_box);

View file

@ -26,7 +26,7 @@ final class PhabricatorProjectEditIconController
if (!$project) { if (!$project) {
return new Aphront404Response(); return new Aphront404Response();
} }
$cancel_uri = $this->getApplicationURI('edit/'.$project->getID().'/'); $cancel_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
$project_icon = $project->getIcon(); $project_icon = $project->getIcon();
} else { } else {
$this->requireApplicationCapability( $this->requireApplicationCapability(

View file

@ -1,160 +0,0 @@
<?php
final class PhabricatorProjectEditMainController
extends PhabricatorProjectController {
private $id;
public function shouldAllowPublic() {
// This page shows project history and some detailed information, and
// it's reasonable to allow public access to it.
return true;
}
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $request->getURIData('id');
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->needImages(true)
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$header = id(new PHUIHeaderView())
->setHeader(pht('Edit %s', $project->getName()))
->setUser($viewer)
->setPolicyObject($project);
if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) {
$header->setStatus('fa-check', 'bluegrey', pht('Active'));
} else {
$header->setStatus('fa-ban', 'red', pht('Archived'));
}
$actions = $this->buildActionListView($project);
$properties = $this->buildPropertyListView($project, $actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
$timeline = $this->buildTransactionTimeline(
$project,
new PhabricatorProjectTransactionQuery());
$timeline->setShouldTerminate(true);
$nav = $this->buildIconNavView($project);
$nav->selectFilter("edit/{$id}/");
$nav->appendChild($object_box);
$nav->appendChild($timeline);
$mnav = $this->buildSideNavView();
return $this->buildApplicationPage(
array(
$nav,
),
array(
'title' => $project->getName(),
));
}
private function buildActionListView(PhabricatorProject $project) {
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $project->getID();
$view = id(new PhabricatorActionListView())
->setUser($viewer)
->setObjectURI($request->getRequestURI());
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Details'))
->setIcon('fa-pencil')
->setHref($this->getApplicationURI("details/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Picture'))
->setIcon('fa-picture-o')
->setHref($this->getApplicationURI("picture/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
if ($project->isArchived()) {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Activate Project'))
->setIcon('fa-check')
->setHref($this->getApplicationURI("archive/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(true));
} else {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Archive Project'))
->setIcon('fa-ban')
->setHref($this->getApplicationURI("archive/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(true));
}
return $view;
}
private function buildPropertyListView(
PhabricatorProject $project,
PhabricatorActionListView $actions) {
$request = $this->getRequest();
$viewer = $request->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($project)
->setActionList($actions);
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$viewer,
$project);
$this->loadHandles(array($project->getPHID()));
$view->addProperty(
pht('Looks Like'),
$this->getHandle($project->getPHID())->renderTag());
$view->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
$view->addProperty(
pht('Editable By'),
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
$view->addProperty(
pht('Joinable By'),
$descriptions[PhabricatorPolicyCapability::CAN_JOIN]);
return $view;
}
}

View file

@ -28,7 +28,7 @@ final class PhabricatorProjectEditPictureController
return new Aphront404Response(); return new Aphront404Response();
} }
$edit_uri = $this->getApplicationURI('edit/'.$project->getID().'/'); $edit_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
$view_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); $view_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
$supported_formats = PhabricatorFile::getTransformableImageFormats(); $supported_formats = PhabricatorFile::getTransformableImageFormats();

View file

@ -33,26 +33,12 @@ final class PhabricatorProjectProfileController
} }
$picture = $project->getProfileImageURI(); $picture = $project->getProfileImageURI();
require_celerity_resource('phabricator-profile-css');
$tasks = $this->renderTasksPage($project);
$content = phutil_tag_div('phabricator-project-layout', $tasks);
$phid = $project->getPHID();
$create_uri = '/maniphest/task/create/?projects='.$phid;
$icon_new = id(new PHUIIconView())
->setIconFont('fa-plus');
$button_add = id(new PHUIButtonView())
->setTag('a')
->setText(pht('New Task'))
->setHref($create_uri)
->setIcon($icon_new);
$header = id(new PHUIHeaderView()) $header = id(new PHUIHeaderView())
->setHeader($project->getName()) ->setHeader($project->getName())
->setUser($user) ->setUser($user)
->setPolicyObject($project) ->setPolicyObject($project)
->setImage($picture) ->setImage($picture);
->addActionLink($button_add);
if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) { if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) {
$header->setStatus('fa-check', 'bluegrey', pht('Active')); $header->setStatus('fa-check', 'bluegrey', pht('Active'));
@ -67,10 +53,15 @@ final class PhabricatorProjectProfileController
->setHeader($header) ->setHeader($header)
->addPropertyList($properties); ->addPropertyList($properties);
$timeline = $this->buildTransactionTimeline(
$project,
new PhabricatorProjectTransactionQuery());
$timeline->setShouldTerminate(true);
$nav = $this->buildIconNavView($project); $nav = $this->buildIconNavView($project);
$nav->selectFilter("profile/{$id}/"); $nav->selectFilter("profile/{$id}/");
$nav->appendChild($object_box); $nav->appendChild($object_box);
$nav->appendChild($content); $nav->appendChild($timeline);
return $this->buildApplicationPage( return $this->buildApplicationPage(
array( array(
@ -81,66 +72,6 @@ final class PhabricatorProjectProfileController
)); ));
} }
private function renderTasksPage(PhabricatorProject $project) {
$user = $this->getRequest()->getUser();
$limit = 50;
$query = id(new ManiphestTaskQuery())
->setViewer($user)
->withAnyProjects(array($project->getPHID()))
->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
->needProjectPHIDs(true)
->setLimit(($limit + 1));
$tasks = $query->execute();
$count = count($tasks);
if ($count == ($limit + 1)) {
array_pop($tasks);
}
$phids = mpull($tasks, 'getOwnerPHID');
$phids = array_merge(
$phids,
array_mergev(mpull($tasks, 'getProjectPHIDs')));
$phids = array_filter($phids);
$handles = $this->loadViewerHandles($phids);
$task_list = new ManiphestTaskListView();
$task_list->setUser($user);
$task_list->setTasks($tasks);
$task_list->setHandles($handles);
$task_list->setNoDataString(pht('This project has no open tasks.'));
$phid = $project->getPHID();
$view_uri = urisprintf(
'/maniphest/?statuses=%s&allProjects=%s#R',
implode(',', ManiphestTaskStatus::getOpenStatusConstants()),
$phid);
$icon = id(new PHUIIconView())
->setIconFont('fa-search');
$button_view = id(new PHUIButtonView())
->setTag('a')
->setText(pht('View Query'))
->setHref($view_uri)
->setIcon($icon);
$header = id(new PHUIHeaderView())
->addActionLink($button_view);
if ($count > $limit) {
$header->setHeader(pht('Highest Priority (some)'));
} else {
$header->setHeader(pht('Highest Priority (all)'));
}
$content = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($task_list);
return $content;
}
private function buildActionListView(PhabricatorProject $project) { private function buildActionListView(PhabricatorProject $project) {
$request = $this->getRequest(); $request = $this->getRequest();
$viewer = $request->getUser(); $viewer = $request->getUser();
@ -159,10 +90,35 @@ final class PhabricatorProjectProfileController
$view->addAction( $view->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Edit Project')) ->setName(pht('Edit Details'))
->setIcon('fa-pencil') ->setIcon('fa-pencil')
->setHref($this->getApplicationURI("edit/{$id}/"))); ->setHref($this->getApplicationURI("details/{$id}/")));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Picture'))
->setIcon('fa-picture-o')
->setHref($this->getApplicationURI("picture/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
if ($project->isArchived()) {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Activate Project'))
->setIcon('fa-check')
->setHref($this->getApplicationURI("archive/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(true));
} else {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Archive Project'))
->setIcon('fa-ban')
->setHref($this->getApplicationURI("archive/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(true));
}
$action = null; $action = null;
if (!$project->isUserMember($viewer->getPHID())) { if (!$project->isUserMember($viewer->getPHID())) {
@ -244,6 +200,20 @@ final class PhabricatorProjectProfileController
? $this->renderHandlesForPHIDs($project->getWatcherPHIDs(), ',') ? $this->renderHandlesForPHIDs($project->getWatcherPHIDs(), ',')
: phutil_tag('em', array(), pht('None'))); : phutil_tag('em', array(), pht('None')));
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$viewer,
$project);
$this->loadHandles(array($project->getPHID()));
$view->addProperty(
pht('Looks Like'),
$this->getHandle($project->getPHID())->renderTag());
$view->addProperty(
pht('Joinable By'),
$descriptions[PhabricatorPolicyCapability::CAN_JOIN]);
$field_list = PhabricatorCustomField::getObjectFields( $field_list = PhabricatorCustomField::getObjectFields(
$project, $project,
PhabricatorCustomField::ROLE_VIEW); PhabricatorCustomField::ROLE_VIEW);

View file

@ -42,6 +42,52 @@ final class PhabricatorProjectTransaction
return array_merge($req_phids, parent::getRequiredHandlePHIDs()); return array_merge($req_phids, parent::getRequiredHandlePHIDs());
} }
public function getColor() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorProjectTransaction::TYPE_STATUS:
if ($old == 0) {
return 'red';
} else {
return 'green';
}
}
return parent::getColor();
}
public function getIcon() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorProjectTransaction::TYPE_STATUS:
if ($old == 0) {
return 'fa-ban';
} else {
return 'fa-check';
}
case PhabricatorProjectTransaction::TYPE_LOCKED:
if ($new) {
return 'fa-lock';
} else {
return 'fa-unlock';
}
case PhabricatorProjectTransaction::TYPE_ICON:
return $new;
case PhabricatorProjectTransaction::TYPE_IMAGE:
return 'fa-photo';
case PhabricatorProjectTransaction::TYPE_MEMBERS:
return 'fa-user';
case PhabricatorProjectTransaction::TYPE_SLUGS:
return 'fa-tag';
}
return parent::getIcon();
}
public function getTitle() { public function getTitle() {
$old = $this->getOldValue(); $old = $this->getOldValue();
$new = $this->getNewValue(); $new = $this->getNewValue();
@ -63,11 +109,11 @@ final class PhabricatorProjectTransaction
case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_STATUS:
if ($old == 0) { if ($old == 0) {
return pht( return pht(
'%s closed this project.', '%s archived this project.',
$author_handle); $author_handle);
} else { } else {
return pht( return pht(
'%s reopened this project.', '%s activated this project.',
$author_handle); $author_handle);
} }
case PhabricatorProjectTransaction::TYPE_IMAGE: case PhabricatorProjectTransaction::TYPE_IMAGE:

View file

@ -100,10 +100,14 @@ final class AphrontSideNavFilterView extends AphrontView {
$key, $name, $uri, PHUIListItemView::TYPE_LINK); $key, $name, $uri, PHUIListItemView::TYPE_LINK);
} }
public function addIcon($key, $name, $icon, $image = null) { public function addIcon($key, $name, $icon, $image = null, $uri = null) {
$href = clone $this->baseURI; if (!$uri) {
$href->setPath(rtrim($href->getPath().$key, '/').'/'); $href = clone $this->baseURI;
$href = (string)$href; $href->setPath(rtrim($href->getPath().$key, '/').'/');
$href = (string)$href;
} else {
$href = $uri;
}
$item = id(new PHUIListItemView()) $item = id(new PHUIListItemView())
->setKey($key) ->setKey($key)