@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

Project - add ability to select an icon for typeaheads and such

Summary: Fixes T5090. Introduced getIcon into Handle stack which allows you to specify a per handle icon. getIcon falls back ot getTypeIcon.

Test Plan: changed the icon on a project a bunch. verified transactions showed up. verified icon showed up in typeahead. verified icon showed up in tokens that were pre-generated (not typed in). units test passed.

Reviewers: chad, epriestley

Reviewed By: epriestley

Subscribers: epriestley, Korvin

Maniphest Tasks: T5090

Differential Revision: https://secure.phabricator.com/D9264

+229 -13
+2
resources/celerity/map.php
··· 94 94 'rsrc/css/application/ponder/post.css' => 'ebab8a70', 95 95 'rsrc/css/application/ponder/vote.css' => '8ed6ed8b', 96 96 'rsrc/css/application/profile/profile-view.css' => '33e6f703', 97 + 'rsrc/css/application/projects/project-icon.css' => 'd80f48b0', 97 98 'rsrc/css/application/projects/project-tag.css' => '095c9404', 98 99 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 99 100 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', ··· 788 789 'ponder-feed-view-css' => 'e62615b6', 789 790 'ponder-post-css' => 'ebab8a70', 790 791 'ponder-vote-css' => '8ed6ed8b', 792 + 'project-icon-css' => 'd80f48b0', 791 793 'raphael-core' => '51ee6b43', 792 794 'raphael-g' => '40dde778', 793 795 'raphael-g-line' => '40da039e',
+5
resources/sql/autopatches/20140522.projecticon.sql
··· 1 + ALTER TABLE {$NAMESPACE}_project.project 2 + ADD COLUMN icon VARCHAR(32) NOT NULL COLLATE utf8_bin; 3 + 4 + UPDATE {$NAMESPACE}_project.project 5 + SET icon = "fa-briefcase" WHERE icon = "";
+4
src/__phutil_library_map__.php
··· 1950 1950 'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php', 1951 1951 'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php', 1952 1952 'PhabricatorProjectEditDetailsController' => 'applications/project/controller/PhabricatorProjectEditDetailsController.php', 1953 + 'PhabricatorProjectEditIconController' => 'applications/project/controller/PhabricatorProjectEditIconController.php', 1953 1954 'PhabricatorProjectEditMainController' => 'applications/project/controller/PhabricatorProjectEditMainController.php', 1954 1955 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', 1955 1956 'PhabricatorProjectEditorTestCase' => 'applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php', 1957 + 'PhabricatorProjectIcon' => 'applications/project/icon/PhabricatorProjectIcon.php', 1956 1958 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', 1957 1959 'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php', 1958 1960 'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php', ··· 4776 4778 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 4777 4779 'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField', 4778 4780 'PhabricatorProjectEditDetailsController' => 'PhabricatorProjectController', 4781 + 'PhabricatorProjectEditIconController' => 'PhabricatorProjectController', 4779 4782 'PhabricatorProjectEditMainController' => 'PhabricatorProjectController', 4780 4783 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', 4781 4784 'PhabricatorProjectEditorTestCase' => 'PhabricatorTestCase', 4785 + 'PhabricatorProjectIcon' => 'Phobject', 4782 4786 'PhabricatorProjectListController' => 'PhabricatorProjectController', 4783 4787 'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController', 4784 4788 'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController',
+13
src/applications/phid/PhabricatorObjectHandle.php
··· 10 10 private $fullName; 11 11 private $title; 12 12 private $imageURI; 13 + private $icon; 13 14 private $timestamp; 14 15 private $status = PhabricatorObjectHandleStatus::STATUS_OPEN; 15 16 private $complete; 16 17 private $disabled; 17 18 private $objectName; 18 19 private $policyFiltered; 20 + 21 + public function setIcon($icon) { 22 + $this->icon = $icon; 23 + return $this; 24 + } 25 + 26 + public function getIcon() { 27 + if ($this->icon) { 28 + return $this->icon; 29 + } 30 + return $this->getTypeIcon(); 31 + } 19 32 20 33 public function getTypeIcon() { 21 34 if ($this->getPHIDType()) {
+2
src/applications/project/application/PhabricatorApplicationProject.php
··· 50 50 => 'PhabricatorProjectProfileController', 51 51 'picture/(?P<id>[1-9]\d*)/' => 52 52 'PhabricatorProjectEditPictureController', 53 + 'icon/(?P<id>[1-9]\d*)/' => 54 + 'PhabricatorProjectEditIconController', 53 55 'create/' => 'PhabricatorProjectCreateController', 54 56 'board/(?P<id>[1-9]\d*)/'. 55 57 '(?P<filter>filter/)?'.
+111
src/applications/project/controller/PhabricatorProjectEditIconController.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectEditIconController 4 + extends PhabricatorProjectController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = $data['id']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $project = id(new PhabricatorProjectQuery()) 17 + ->setViewer($viewer) 18 + ->withIDs(array($this->id)) 19 + ->requireCapabilities( 20 + array( 21 + PhabricatorPolicyCapability::CAN_VIEW, 22 + PhabricatorPolicyCapability::CAN_EDIT, 23 + )) 24 + ->executeOne(); 25 + if (!$project) { 26 + return new Aphront404Response(); 27 + } 28 + 29 + $view_uri = '/tag/'.$project->getPrimarySlug().'/'; 30 + $edit_uri = $this->getApplicationURI('edit/'.$project->getID().'/'); 31 + 32 + if ($request->isFormPost()) { 33 + $v_icon = $request->getStr('icon'); 34 + $type_icon = PhabricatorProjectTransaction::TYPE_ICON; 35 + $xactions = array(id(new PhabricatorProjectTransaction()) 36 + ->setTransactionType($type_icon) 37 + ->setNewValue($v_icon)); 38 + 39 + $editor = id(new PhabricatorProjectTransactionEditor()) 40 + ->setActor($viewer) 41 + ->setContentSourceFromRequest($request) 42 + ->setContinueOnMissingFields(true) 43 + ->setContinueOnNoEffect(true); 44 + 45 + $editor->applyTransactions($project, $xactions); 46 + 47 + return id(new AphrontReloadResponse())->setURI($edit_uri); 48 + } 49 + 50 + require_celerity_resource('project-icon-css'); 51 + Javelin::initBehavior('phabricator-tooltips'); 52 + 53 + $project_icons = PhabricatorProjectIcon::getIconMap(); 54 + $ii = 0; 55 + $buttons = array(); 56 + foreach ($project_icons as $icon => $label) { 57 + $view = id(new PHUIIconView()) 58 + ->setIconFont($icon.' bluegrey'); 59 + 60 + $aural = javelin_tag( 61 + 'span', 62 + array( 63 + 'aural' => true, 64 + ), 65 + pht('Choose "%s" Icon', $label)); 66 + 67 + if ($icon == $project->getIcon()) { 68 + $class_extra = ' selected'; 69 + $tip = $label . pht(' - selected'); 70 + } else { 71 + $class_extra = null; 72 + $tip = $label; 73 + } 74 + 75 + $buttons[] = javelin_tag( 76 + 'button', 77 + array( 78 + 'class' => 'icon-button'.$class_extra, 79 + 'name' => 'icon', 80 + 'value' => $icon, 81 + 'type' => 'submit', 82 + 'sigil' => 'has-tooltip', 83 + 'meta' => array( 84 + 'tip' => $tip, 85 + ) 86 + ), 87 + array( 88 + $aural, 89 + $view, 90 + )); 91 + if ((++$ii % 4) == 0) { 92 + $buttons[] = phutil_tag('br'); 93 + } 94 + } 95 + 96 + $buttons = phutil_tag( 97 + 'div', 98 + array( 99 + 'class' => 'icon-grid', 100 + ), 101 + $buttons); 102 + 103 + $dialog = id(new AphrontDialogView()) 104 + ->setUser($viewer) 105 + ->setTitle(pht('Choose Project Icon')) 106 + ->appendChild($buttons) 107 + ->addCancelButton($edit_uri); 108 + 109 + return id(new AphrontDialogResponse())->setDialog($dialog); 110 + } 111 + }
+8
src/applications/project/controller/PhabricatorProjectEditMainController.php
··· 96 96 97 97 $view->addAction( 98 98 id(new PhabricatorActionView()) 99 + ->setName(pht('Edit Icon')) 100 + ->setIcon($project->getIcon()) 101 + ->setHref($this->getApplicationURI("icon/{$id}/")) 102 + ->setDisabled(!$can_edit) 103 + ->setWorkflow(true)); 104 + 105 + $view->addAction( 106 + id(new PhabricatorActionView()) 99 107 ->setName(pht('Edit Picture')) 100 108 ->setIcon('fa-picture-o') 101 109 ->setHref($this->getApplicationURI("picture/{$id}/"))
+9
src/applications/project/editor/PhabricatorProjectTransactionEditor.php
··· 15 15 $types[] = PhabricatorProjectTransaction::TYPE_SLUGS; 16 16 $types[] = PhabricatorProjectTransaction::TYPE_STATUS; 17 17 $types[] = PhabricatorProjectTransaction::TYPE_IMAGE; 18 + $types[] = PhabricatorProjectTransaction::TYPE_ICON; 18 19 19 20 return $types; 20 21 } ··· 35 36 return $object->getStatus(); 36 37 case PhabricatorProjectTransaction::TYPE_IMAGE: 37 38 return $object->getProfileImagePHID(); 39 + case PhabricatorProjectTransaction::TYPE_ICON: 40 + return $object->getIcon(); 38 41 } 39 42 40 43 return parent::getCustomTransactionOldValue($object, $xaction); ··· 49 52 case PhabricatorProjectTransaction::TYPE_SLUGS: 50 53 case PhabricatorProjectTransaction::TYPE_STATUS: 51 54 case PhabricatorProjectTransaction::TYPE_IMAGE: 55 + case PhabricatorProjectTransaction::TYPE_ICON: 52 56 return $xaction->getNewValue(); 53 57 } 54 58 ··· 71 75 return; 72 76 case PhabricatorProjectTransaction::TYPE_IMAGE: 73 77 $object->setProfileImagePHID($xaction->getNewValue()); 78 + return; 79 + case PhabricatorProjectTransaction::TYPE_ICON: 80 + $object->setIcon($xaction->getNewValue()); 74 81 return; 75 82 case PhabricatorTransactions::TYPE_EDGE: 76 83 return; ··· 173 180 case PhabricatorTransactions::TYPE_JOIN_POLICY: 174 181 case PhabricatorProjectTransaction::TYPE_STATUS: 175 182 case PhabricatorProjectTransaction::TYPE_IMAGE: 183 + case PhabricatorProjectTransaction::TYPE_ICON: 176 184 return; 177 185 case PhabricatorTransactions::TYPE_EDGE: 178 186 $edge_type = $xaction->getMetadataValue('edge:type'); ··· 342 350 case PhabricatorProjectTransaction::TYPE_NAME: 343 351 case PhabricatorProjectTransaction::TYPE_STATUS: 344 352 case PhabricatorProjectTransaction::TYPE_IMAGE: 353 + case PhabricatorProjectTransaction::TYPE_ICON: 345 354 PhabricatorPolicyFilter::requireCapability( 346 355 $this->requireActor(), 347 356 $object,
+6 -11
src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php
··· 15 15 $user2 = $this->createUser(); 16 16 $user2->save(); 17 17 18 - $proj = $this->createProject(); 19 - $proj->setAuthorPHID($user->getPHID()); 20 - $proj->save(); 18 + $proj = $this->createProject($user); 21 19 22 20 $proj = $this->refreshProject($proj, $user, true); 23 21 ··· 48 46 $user2 = $this->createUser(); 49 47 $user2->save(); 50 48 51 - $proj = $this->createProject(); 52 - $proj->setAuthorPHID($user->getPHID()); 53 - $proj->save(); 49 + $proj = $this->createProject($user); 54 50 55 51 56 52 // When edit and view policies are set to "user", anyone can edit. ··· 100 96 $user->save(); 101 97 102 98 $proj = $this->createProjectWithNewAuthor(); 103 - $proj->save(); 104 99 105 100 $proj = $this->refreshProject($proj, $user, true); 106 101 $this->assertTrue( ··· 228 223 } 229 224 } 230 225 231 - private function createProject() { 232 - $project = new PhabricatorProject(); 226 + private function createProject(PhabricatorUser $user) { 227 + $project = PhabricatorProject::initializeNewProject($user); 233 228 $project->setName('Test Project '.mt_rand()); 229 + $project->save(); 234 230 235 231 return $project; 236 232 } ··· 239 235 $author = $this->createUser(); 240 236 $author->save(); 241 237 242 - $project = $this->createProject(); 243 - $project->setAuthorPHID($author->getPHID()); 238 + $project = $this->createProject($author); 244 239 245 240 return $project; 246 241 }
+27
src/applications/project/icon/PhabricatorProjectIcon.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectIcon extends Phobject { 4 + 5 + public static function getIconMap() { 6 + return 7 + array( 8 + 'fa-briefcase' => pht('Briefcase'), 9 + 'fa-tags' => pht('Tag'), 10 + 'fa-folder' => pht('Folder'), 11 + 'fa-users' => pht('Team'), 12 + 'fa-bug' => pht('Bug'), 13 + 'fa-trash-o' => pht('Garbage'), 14 + 'fa-calendar' => pht('Deadline'), 15 + 'fa-flag-checkered' => pht('Goal'), 16 + 'fa-envelope' => pht('Communication'), 17 + 'fa-truck' => pht('Release'), 18 + 'fa-lock' => pht('Policy'), 19 + 'fa-umbrella' => pht('An Umbrella'), 20 + ); 21 + } 22 + 23 + public static function getLabel($key) { 24 + $map = self::getIconMap(); 25 + return $map[$key]; 26 + } 27 + }
+1
src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php
··· 49 49 $handle->setObjectName('#'.$slug); 50 50 $handle->setURI("/tag/{$slug}/"); 51 51 $handle->setImageURI($project->getProfileImageURI()); 52 + $handle->setIcon($project->getIcon()); 52 53 53 54 if ($project->isArchived()) { 54 55 $handle->setStatus(PhabricatorObjectHandleStatus::STATUS_CLOSED);
+4
src/applications/project/storage/PhabricatorProject.php
··· 13 13 protected $subprojectPHIDs = array(); 14 14 protected $phrictionSlug; 15 15 protected $profileImagePHID; 16 + protected $icon; 16 17 17 18 protected $viewPolicy; 18 19 protected $editPolicy; ··· 26 27 private $profileImageFile = self::ATTACHABLE; 27 28 private $slugs = self::ATTACHABLE; 28 29 30 + const DEFAULT_ICON = 'fa-briefcase'; 31 + 29 32 public static function initializeNewProject(PhabricatorUser $actor) { 30 33 return id(new PhabricatorProject()) 31 34 ->setName('') 32 35 ->setAuthorPHID($actor->getPHID()) 36 + ->setIcon(self::DEFAULT_ICON) 33 37 ->setViewPolicy(PhabricatorPolicies::POLICY_USER) 34 38 ->setEditPolicy(PhabricatorPolicies::POLICY_USER) 35 39 ->setJoinPolicy(PhabricatorPolicies::POLICY_USER)
+7
src/applications/project/storage/PhabricatorProjectTransaction.php
··· 7 7 const TYPE_SLUGS = 'project:slugs'; 8 8 const TYPE_STATUS = 'project:status'; 9 9 const TYPE_IMAGE = 'project:image'; 10 + const TYPE_ICON = 'project:icon'; 10 11 11 12 // NOTE: This is deprecated, members are just a normal edge now. 12 13 const TYPE_MEMBERS = 'project:members'; ··· 85 86 $this->renderHandleLink($old), 86 87 $this->renderHandleLink($new)); 87 88 } 89 + 90 + case PhabricatorProjectTransaction::TYPE_ICON: 91 + return pht( 92 + '%s set this project\'s icon to %s.', 93 + $author_handle, 94 + PhabricatorProjectIcon::getLabel($new)); 88 95 89 96 case PhabricatorProjectTransaction::TYPE_SLUGS: 90 97 $add = array_diff($new, $old);
+1 -1
src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php
··· 288 288 ->setDisplayType("Project") 289 289 ->setURI('/project/view/'.$proj->getID().'/') 290 290 ->setPHID($proj->getPHID()) 291 - ->setIcon('fa-briefcase bluegrey') 291 + ->setIcon($proj->getIcon().' bluegrey') 292 292 ->setClosed($closed); 293 293 294 294 $proj_result->setImageURI($proj->getProfileImageURI());
+1 -1
src/view/form/control/AphrontFormTokenizerControl.php
··· 62 62 'id' => $id, 63 63 'src' => $this->datasource, 64 64 'value' => mpull($values, 'getFullName', 'getPHID'), 65 - 'icons' => mpull($values, 'getTypeIcon', 'getPHID'), 65 + 'icons' => mpull($values, 'getIcon', 'getPHID'), 66 66 'limit' => $this->limit, 67 67 'username' => $username, 68 68 'placeholder' => $this->placeholder,
+28
webroot/rsrc/css/application/projects/project-icon.css
··· 1 + /** 2 + * @provides project-icon-css 3 + */ 4 + 5 + button.icon-button { 6 + background: #f7f7f7; 7 + border: 1px solid {$lightblueborder}; 8 + position: relative; 9 + width: 16px; 10 + height: 16px; 11 + padding: 12px; 12 + margin: 4px; 13 + text-shadow: none; 14 + box-shadow: none; 15 + box-sizing: content-box; 16 + } 17 + 18 + .icon-grid { 19 + text-align: center; 20 + } 21 + 22 + .icon-icon + .icon-icon { 23 + margin-left: 4px; 24 + } 25 + 26 + button.icon-button.selected { 27 + background: {$bluebackground}; 28 + }