@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+128 -266
-2
src/__phutil_library_map__.php
··· 2204 2204 'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php', 2205 2205 'PhabricatorProjectEditDetailsController' => 'applications/project/controller/PhabricatorProjectEditDetailsController.php', 2206 2206 'PhabricatorProjectEditIconController' => 'applications/project/controller/PhabricatorProjectEditIconController.php', 2207 - 'PhabricatorProjectEditMainController' => 'applications/project/controller/PhabricatorProjectEditMainController.php', 2208 2207 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', 2209 2208 'PhabricatorProjectEditorTestCase' => 'applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php', 2210 2209 'PhabricatorProjectFeedController' => 'applications/project/controller/PhabricatorProjectFeedController.php', ··· 5444 5443 'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField', 5445 5444 'PhabricatorProjectEditDetailsController' => 'PhabricatorProjectController', 5446 5445 'PhabricatorProjectEditIconController' => 'PhabricatorProjectController', 5447 - 'PhabricatorProjectEditMainController' => 'PhabricatorProjectController', 5448 5446 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', 5449 5447 'PhabricatorProjectEditorTestCase' => 'PhabricatorTestCase', 5450 5448 'PhabricatorProjectFeedController' => 'PhabricatorProjectController',
+1 -1
src/applications/files/controller/PhabricatorFileComposeController.php
··· 72 72 )); 73 73 74 74 if ($project_phid) { 75 - $edit_uri = '/project/edit/'.$project->getID().'/'; 75 + $edit_uri = '/project/profile/'.$project->getID().'/'; 76 76 77 77 $xactions = array(); 78 78 $xactions[] = id(new PhabricatorProjectTransaction())
-1
src/applications/project/application/PhabricatorProjectApplication.php
··· 43 43 '/project/' => array( 44 44 '(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorProjectListController', 45 45 'filter/(?P<filter>[^/]+)/' => 'PhabricatorProjectListController', 46 - 'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectEditMainController', 47 46 'details/(?P<id>[1-9]\d*)/' 48 47 => 'PhabricatorProjectEditDetailsController', 49 48 'archive/(?P<id>[1-9]\d*)/'
+1 -1
src/applications/project/controller/PhabricatorProjectArchiveController.php
··· 26 26 return new Aphront404Response(); 27 27 } 28 28 29 - $edit_uri = $this->getApplicationURI('edit/'.$project->getID().'/'); 29 + $edit_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); 30 30 31 31 if ($request->isFormPost()) { 32 32 if ($project->isArchived()) {
+17 -7
src/applications/project/controller/PhabricatorProjectController.php
··· 7 7 } 8 8 9 9 public function buildSideNavView($for_app = false) { 10 - $user = $this->getRequest()->getUser(); 10 + $viewer = $this->getViewer(); 11 11 12 12 $nav = new AphrontSideNavFilterView(); 13 13 $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 14 14 15 15 $id = null; 16 16 if ($for_app) { 17 - $user = $this->getRequest()->getUser(); 18 17 $id = $this->getRequest()->getURIData('id'); 19 18 if ($id) { 20 19 $nav->addFilter("profile/{$id}/", pht('Profile')); 21 20 $nav->addFilter("board/{$id}/", pht('Workboard')); 22 21 $nav->addFilter("members/{$id}/", pht('Members')); 23 22 $nav->addFilter("feed/{$id}/", pht('Feed')); 24 - $nav->addFilter("edit/{$id}/", pht('Edit')); 23 + $nav->addFilter("details/{$id}/", pht('Edit Details')); 25 24 } 26 25 $nav->addFilter('create', pht('Create Project')); 27 26 } 28 27 29 28 if (!$id) { 30 29 id(new PhabricatorProjectSearchEngine()) 31 - ->setViewer($user) 30 + ->setViewer($viewer) 32 31 ->addNavigationItems($nav->getMenu()); 33 32 } 34 33 ··· 38 37 } 39 38 40 39 public function buildIconNavView(PhabricatorProject $project) { 41 - $user = $this->getRequest()->getUser(); 40 + $viewer = $this->getViewer(); 42 41 $id = $project->getID(); 43 42 $picture = $project->getProfileImageURI(); 44 43 $name = $project->getName(); 45 44 46 45 $columns = id(new PhabricatorProjectColumnQuery()) 47 - ->setViewer($user) 46 + ->setViewer($viewer) 48 47 ->withProjectPHIDs(array($project->getPHID())) 49 48 ->execute(); 50 49 if ($columns) { ··· 58 57 $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 59 58 $nav->addIcon("profile/{$id}/", $name, null, $picture); 60 59 $nav->addIcon("board/{$id}/", pht('Workboard'), $board_icon); 60 + 61 + $class = 'PhabricatorManiphestApplication'; 62 + if (PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) { 63 + $phid = $project->getPHID(); 64 + $query_uri = urisprintf( 65 + '/maniphest/?statuses=%s&allProjects=%s#R', 66 + implode(',', ManiphestTaskStatus::getOpenStatusConstants()), 67 + $phid); 68 + $nav->addIcon(null, pht('Open Tasks'), 'fa-anchor', null, $query_uri); 69 + } 70 + 61 71 $nav->addIcon("feed/{$id}/", pht('Feed'), 'fa-newspaper-o'); 62 72 $nav->addIcon("members/{$id}/", pht('Members'), 'fa-group'); 63 - $nav->addIcon("edit/{$id}/", pht('Edit'), 'fa-pencil'); 73 + $nav->addIcon("details/{$id}/", pht('Edit Details'), 'fa-pencil'); 64 74 65 75 return $nav; 66 76 }
+3 -8
src/applications/project/controller/PhabricatorProjectEditDetailsController.php
··· 149 149 )); 150 150 } 151 151 152 - if ($is_new) { 153 - $redirect_uri = 154 - $this->getApplicationURI('profile/'.$project->getID().'/'); 155 - } else { 156 - $redirect_uri = 157 - $this->getApplicationURI('edit/'.$project->getID().'/'); 158 - } 152 + $redirect_uri = 153 + $this->getApplicationURI('profile/'.$project->getID().'/'); 159 154 160 155 return id(new AphrontRedirectResponse())->setURI($redirect_uri); 161 156 } catch (PhabricatorApplicationTransactionValidationException $ex) { ··· 304 299 305 300 if (!$is_new) { 306 301 $nav = $this->buildIconNavView($project); 307 - $nav->selectFilter("edit/{$id}/"); 302 + $nav->selectFilter("details/{$id}/"); 308 303 $nav->appendChild($form_box); 309 304 } else { 310 305 $nav = array($form_box);
+1 -1
src/applications/project/controller/PhabricatorProjectEditIconController.php
··· 26 26 if (!$project) { 27 27 return new Aphront404Response(); 28 28 } 29 - $cancel_uri = $this->getApplicationURI('edit/'.$project->getID().'/'); 29 + $cancel_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); 30 30 $project_icon = $project->getIcon(); 31 31 } else { 32 32 $this->requireApplicationCapability(
-160
src/applications/project/controller/PhabricatorProjectEditMainController.php
··· 1 - <?php 2 - 3 - final class PhabricatorProjectEditMainController 4 - extends PhabricatorProjectController { 5 - 6 - private $id; 7 - 8 - public function shouldAllowPublic() { 9 - // This page shows project history and some detailed information, and 10 - // it's reasonable to allow public access to it. 11 - return true; 12 - } 13 - 14 - public function willProcessRequest(array $data) { 15 - $this->id = idx($data, 'id'); 16 - } 17 - 18 - public function processRequest() { 19 - $request = $this->getRequest(); 20 - $viewer = $request->getUser(); 21 - $id = $request->getURIData('id'); 22 - 23 - $project = id(new PhabricatorProjectQuery()) 24 - ->setViewer($viewer) 25 - ->withIDs(array($this->id)) 26 - ->needImages(true) 27 - ->executeOne(); 28 - if (!$project) { 29 - return new Aphront404Response(); 30 - } 31 - 32 - $header = id(new PHUIHeaderView()) 33 - ->setHeader(pht('Edit %s', $project->getName())) 34 - ->setUser($viewer) 35 - ->setPolicyObject($project); 36 - 37 - if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) { 38 - $header->setStatus('fa-check', 'bluegrey', pht('Active')); 39 - } else { 40 - $header->setStatus('fa-ban', 'red', pht('Archived')); 41 - } 42 - 43 - $actions = $this->buildActionListView($project); 44 - $properties = $this->buildPropertyListView($project, $actions); 45 - 46 - $object_box = id(new PHUIObjectBoxView()) 47 - ->setHeader($header) 48 - ->addPropertyList($properties); 49 - 50 - $timeline = $this->buildTransactionTimeline( 51 - $project, 52 - new PhabricatorProjectTransactionQuery()); 53 - $timeline->setShouldTerminate(true); 54 - 55 - $nav = $this->buildIconNavView($project); 56 - $nav->selectFilter("edit/{$id}/"); 57 - $nav->appendChild($object_box); 58 - $nav->appendChild($timeline); 59 - 60 - $mnav = $this->buildSideNavView(); 61 - 62 - return $this->buildApplicationPage( 63 - array( 64 - $nav, 65 - ), 66 - array( 67 - 'title' => $project->getName(), 68 - )); 69 - } 70 - 71 - private function buildActionListView(PhabricatorProject $project) { 72 - $request = $this->getRequest(); 73 - $viewer = $request->getUser(); 74 - 75 - $id = $project->getID(); 76 - 77 - $view = id(new PhabricatorActionListView()) 78 - ->setUser($viewer) 79 - ->setObjectURI($request->getRequestURI()); 80 - 81 - $can_edit = PhabricatorPolicyFilter::hasCapability( 82 - $viewer, 83 - $project, 84 - PhabricatorPolicyCapability::CAN_EDIT); 85 - 86 - $view->addAction( 87 - id(new PhabricatorActionView()) 88 - ->setName(pht('Edit Details')) 89 - ->setIcon('fa-pencil') 90 - ->setHref($this->getApplicationURI("details/{$id}/")) 91 - ->setDisabled(!$can_edit) 92 - ->setWorkflow(!$can_edit)); 93 - 94 - $view->addAction( 95 - id(new PhabricatorActionView()) 96 - ->setName(pht('Edit Picture')) 97 - ->setIcon('fa-picture-o') 98 - ->setHref($this->getApplicationURI("picture/{$id}/")) 99 - ->setDisabled(!$can_edit) 100 - ->setWorkflow(!$can_edit)); 101 - 102 - if ($project->isArchived()) { 103 - $view->addAction( 104 - id(new PhabricatorActionView()) 105 - ->setName(pht('Activate Project')) 106 - ->setIcon('fa-check') 107 - ->setHref($this->getApplicationURI("archive/{$id}/")) 108 - ->setDisabled(!$can_edit) 109 - ->setWorkflow(true)); 110 - } else { 111 - $view->addAction( 112 - id(new PhabricatorActionView()) 113 - ->setName(pht('Archive Project')) 114 - ->setIcon('fa-ban') 115 - ->setHref($this->getApplicationURI("archive/{$id}/")) 116 - ->setDisabled(!$can_edit) 117 - ->setWorkflow(true)); 118 - } 119 - 120 - return $view; 121 - } 122 - 123 - private function buildPropertyListView( 124 - PhabricatorProject $project, 125 - PhabricatorActionListView $actions) { 126 - $request = $this->getRequest(); 127 - $viewer = $request->getUser(); 128 - 129 - $view = id(new PHUIPropertyListView()) 130 - ->setUser($viewer) 131 - ->setObject($project) 132 - ->setActionList($actions); 133 - 134 - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( 135 - $viewer, 136 - $project); 137 - 138 - $this->loadHandles(array($project->getPHID())); 139 - 140 - $view->addProperty( 141 - pht('Looks Like'), 142 - $this->getHandle($project->getPHID())->renderTag()); 143 - 144 - $view->addProperty( 145 - pht('Visible To'), 146 - $descriptions[PhabricatorPolicyCapability::CAN_VIEW]); 147 - 148 - $view->addProperty( 149 - pht('Editable By'), 150 - $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); 151 - 152 - $view->addProperty( 153 - pht('Joinable By'), 154 - $descriptions[PhabricatorPolicyCapability::CAN_JOIN]); 155 - 156 - return $view; 157 - } 158 - 159 - 160 - }
+1 -1
src/applications/project/controller/PhabricatorProjectEditPictureController.php
··· 28 28 return new Aphront404Response(); 29 29 } 30 30 31 - $edit_uri = $this->getApplicationURI('edit/'.$project->getID().'/'); 31 + $edit_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); 32 32 $view_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); 33 33 34 34 $supported_formats = PhabricatorFile::getTransformableImageFormats();
+48 -78
src/applications/project/controller/PhabricatorProjectProfileController.php
··· 33 33 } 34 34 35 35 $picture = $project->getProfileImageURI(); 36 - require_celerity_resource('phabricator-profile-css'); 37 - $tasks = $this->renderTasksPage($project); 38 - $content = phutil_tag_div('phabricator-project-layout', $tasks); 39 - 40 - $phid = $project->getPHID(); 41 - $create_uri = '/maniphest/task/create/?projects='.$phid; 42 - $icon_new = id(new PHUIIconView()) 43 - ->setIconFont('fa-plus'); 44 - $button_add = id(new PHUIButtonView()) 45 - ->setTag('a') 46 - ->setText(pht('New Task')) 47 - ->setHref($create_uri) 48 - ->setIcon($icon_new); 49 36 50 37 $header = id(new PHUIHeaderView()) 51 38 ->setHeader($project->getName()) 52 39 ->setUser($user) 53 40 ->setPolicyObject($project) 54 - ->setImage($picture) 55 - ->addActionLink($button_add); 41 + ->setImage($picture); 56 42 57 43 if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) { 58 44 $header->setStatus('fa-check', 'bluegrey', pht('Active')); ··· 67 53 ->setHeader($header) 68 54 ->addPropertyList($properties); 69 55 56 + $timeline = $this->buildTransactionTimeline( 57 + $project, 58 + new PhabricatorProjectTransactionQuery()); 59 + $timeline->setShouldTerminate(true); 60 + 70 61 $nav = $this->buildIconNavView($project); 71 62 $nav->selectFilter("profile/{$id}/"); 72 63 $nav->appendChild($object_box); 73 - $nav->appendChild($content); 64 + $nav->appendChild($timeline); 74 65 75 66 return $this->buildApplicationPage( 76 67 array( ··· 81 72 )); 82 73 } 83 74 84 - private function renderTasksPage(PhabricatorProject $project) { 85 - 86 - $user = $this->getRequest()->getUser(); 87 - $limit = 50; 88 - 89 - $query = id(new ManiphestTaskQuery()) 90 - ->setViewer($user) 91 - ->withAnyProjects(array($project->getPHID())) 92 - ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants()) 93 - ->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY) 94 - ->needProjectPHIDs(true) 95 - ->setLimit(($limit + 1)); 96 - $tasks = $query->execute(); 97 - $count = count($tasks); 98 - if ($count == ($limit + 1)) { 99 - array_pop($tasks); 100 - } 101 - 102 - $phids = mpull($tasks, 'getOwnerPHID'); 103 - $phids = array_merge( 104 - $phids, 105 - array_mergev(mpull($tasks, 'getProjectPHIDs'))); 106 - $phids = array_filter($phids); 107 - $handles = $this->loadViewerHandles($phids); 108 - 109 - $task_list = new ManiphestTaskListView(); 110 - $task_list->setUser($user); 111 - $task_list->setTasks($tasks); 112 - $task_list->setHandles($handles); 113 - $task_list->setNoDataString(pht('This project has no open tasks.')); 114 - 115 - $phid = $project->getPHID(); 116 - $view_uri = urisprintf( 117 - '/maniphest/?statuses=%s&allProjects=%s#R', 118 - implode(',', ManiphestTaskStatus::getOpenStatusConstants()), 119 - $phid); 120 - $icon = id(new PHUIIconView()) 121 - ->setIconFont('fa-search'); 122 - $button_view = id(new PHUIButtonView()) 123 - ->setTag('a') 124 - ->setText(pht('View Query')) 125 - ->setHref($view_uri) 126 - ->setIcon($icon); 127 - 128 - $header = id(new PHUIHeaderView()) 129 - ->addActionLink($button_view); 130 - 131 - if ($count > $limit) { 132 - $header->setHeader(pht('Highest Priority (some)')); 133 - } else { 134 - $header->setHeader(pht('Highest Priority (all)')); 135 - } 136 - 137 - $content = id(new PHUIObjectBoxView()) 138 - ->setHeader($header) 139 - ->appendChild($task_list); 140 - 141 - return $content; 142 - } 143 - 144 75 private function buildActionListView(PhabricatorProject $project) { 145 76 $request = $this->getRequest(); 146 77 $viewer = $request->getUser(); ··· 159 90 160 91 $view->addAction( 161 92 id(new PhabricatorActionView()) 162 - ->setName(pht('Edit Project')) 93 + ->setName(pht('Edit Details')) 163 94 ->setIcon('fa-pencil') 164 - ->setHref($this->getApplicationURI("edit/{$id}/"))); 95 + ->setHref($this->getApplicationURI("details/{$id}/"))); 96 + 97 + $view->addAction( 98 + id(new PhabricatorActionView()) 99 + ->setName(pht('Edit Picture')) 100 + ->setIcon('fa-picture-o') 101 + ->setHref($this->getApplicationURI("picture/{$id}/")) 102 + ->setDisabled(!$can_edit) 103 + ->setWorkflow(!$can_edit)); 165 104 105 + if ($project->isArchived()) { 106 + $view->addAction( 107 + id(new PhabricatorActionView()) 108 + ->setName(pht('Activate Project')) 109 + ->setIcon('fa-check') 110 + ->setHref($this->getApplicationURI("archive/{$id}/")) 111 + ->setDisabled(!$can_edit) 112 + ->setWorkflow(true)); 113 + } else { 114 + $view->addAction( 115 + id(new PhabricatorActionView()) 116 + ->setName(pht('Archive Project')) 117 + ->setIcon('fa-ban') 118 + ->setHref($this->getApplicationURI("archive/{$id}/")) 119 + ->setDisabled(!$can_edit) 120 + ->setWorkflow(true)); 121 + } 166 122 167 123 $action = null; 168 124 if (!$project->isUserMember($viewer->getPHID())) { ··· 243 199 $project->getWatcherPHIDs() 244 200 ? $this->renderHandlesForPHIDs($project->getWatcherPHIDs(), ',') 245 201 : phutil_tag('em', array(), pht('None'))); 202 + 203 + $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( 204 + $viewer, 205 + $project); 206 + 207 + $this->loadHandles(array($project->getPHID())); 208 + 209 + $view->addProperty( 210 + pht('Looks Like'), 211 + $this->getHandle($project->getPHID())->renderTag()); 212 + 213 + $view->addProperty( 214 + pht('Joinable By'), 215 + $descriptions[PhabricatorPolicyCapability::CAN_JOIN]); 246 216 247 217 $field_list = PhabricatorCustomField::getObjectFields( 248 218 $project,
+48 -2
src/applications/project/storage/PhabricatorProjectTransaction.php
··· 42 42 return array_merge($req_phids, parent::getRequiredHandlePHIDs()); 43 43 } 44 44 45 + public function getColor() { 46 + 47 + $old = $this->getOldValue(); 48 + $new = $this->getNewValue(); 49 + 50 + switch ($this->getTransactionType()) { 51 + case PhabricatorProjectTransaction::TYPE_STATUS: 52 + if ($old == 0) { 53 + return 'red'; 54 + } else { 55 + return 'green'; 56 + } 57 + } 58 + return parent::getColor(); 59 + } 60 + 61 + public function getIcon() { 62 + 63 + $old = $this->getOldValue(); 64 + $new = $this->getNewValue(); 65 + 66 + switch ($this->getTransactionType()) { 67 + case PhabricatorProjectTransaction::TYPE_STATUS: 68 + if ($old == 0) { 69 + return 'fa-ban'; 70 + } else { 71 + return 'fa-check'; 72 + } 73 + case PhabricatorProjectTransaction::TYPE_LOCKED: 74 + if ($new) { 75 + return 'fa-lock'; 76 + } else { 77 + return 'fa-unlock'; 78 + } 79 + case PhabricatorProjectTransaction::TYPE_ICON: 80 + return $new; 81 + case PhabricatorProjectTransaction::TYPE_IMAGE: 82 + return 'fa-photo'; 83 + case PhabricatorProjectTransaction::TYPE_MEMBERS: 84 + return 'fa-user'; 85 + case PhabricatorProjectTransaction::TYPE_SLUGS: 86 + return 'fa-tag'; 87 + } 88 + return parent::getIcon(); 89 + } 90 + 45 91 public function getTitle() { 46 92 $old = $this->getOldValue(); 47 93 $new = $this->getNewValue(); ··· 63 109 case PhabricatorProjectTransaction::TYPE_STATUS: 64 110 if ($old == 0) { 65 111 return pht( 66 - '%s closed this project.', 112 + '%s archived this project.', 67 113 $author_handle); 68 114 } else { 69 115 return pht( 70 - '%s reopened this project.', 116 + '%s activated this project.', 71 117 $author_handle); 72 118 } 73 119 case PhabricatorProjectTransaction::TYPE_IMAGE:
+8 -4
src/view/layout/AphrontSideNavFilterView.php
··· 100 100 $key, $name, $uri, PHUIListItemView::TYPE_LINK); 101 101 } 102 102 103 - public function addIcon($key, $name, $icon, $image = null) { 104 - $href = clone $this->baseURI; 105 - $href->setPath(rtrim($href->getPath().$key, '/').'/'); 106 - $href = (string)$href; 103 + public function addIcon($key, $name, $icon, $image = null, $uri = null) { 104 + if (!$uri) { 105 + $href = clone $this->baseURI; 106 + $href->setPath(rtrim($href->getPath().$key, '/').'/'); 107 + $href = (string)$href; 108 + } else { 109 + $href = $uri; 110 + } 107 111 108 112 $item = id(new PHUIListItemView()) 109 113 ->setKey($key)