From 102befdede3ae983c4e8f413944548a1cbba44c1 Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Fri, 23 May 2014 10:41:24 -0700 Subject: [PATCH] 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 --- resources/celerity/map.php | 2 + .../sql/autopatches/20140522.projecticon.sql | 5 + src/__phutil_library_map__.php | 4 + .../phid/PhabricatorObjectHandle.php | 13 ++ .../PhabricatorApplicationProject.php | 2 + .../PhabricatorProjectEditIconController.php | 111 ++++++++++++++++++ .../PhabricatorProjectEditMainController.php | 8 ++ .../PhabricatorProjectTransactionEditor.php | 9 ++ .../PhabricatorProjectEditorTestCase.php | 17 +-- .../project/icon/PhabricatorProjectIcon.php | 27 +++++ .../PhabricatorProjectPHIDTypeProject.php | 1 + .../project/storage/PhabricatorProject.php | 4 + .../storage/PhabricatorProjectTransaction.php | 7 ++ ...torTypeaheadCommonDatasourceController.php | 2 +- .../control/AphrontFormTokenizerControl.php | 2 +- .../css/application/projects/project-icon.css | 28 +++++ 16 files changed, 229 insertions(+), 13 deletions(-) create mode 100644 resources/sql/autopatches/20140522.projecticon.sql create mode 100644 src/applications/project/controller/PhabricatorProjectEditIconController.php create mode 100644 src/applications/project/icon/PhabricatorProjectIcon.php create mode 100644 webroot/rsrc/css/application/projects/project-icon.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e30ed96c13..1dd4d9412c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -94,6 +94,7 @@ return array( 'rsrc/css/application/ponder/post.css' => 'ebab8a70', 'rsrc/css/application/ponder/vote.css' => '8ed6ed8b', 'rsrc/css/application/profile/profile-view.css' => '33e6f703', + 'rsrc/css/application/projects/project-icon.css' => 'd80f48b0', 'rsrc/css/application/projects/project-tag.css' => '095c9404', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', @@ -788,6 +789,7 @@ return array( 'ponder-feed-view-css' => 'e62615b6', 'ponder-post-css' => 'ebab8a70', 'ponder-vote-css' => '8ed6ed8b', + 'project-icon-css' => 'd80f48b0', 'raphael-core' => '51ee6b43', 'raphael-g' => '40dde778', 'raphael-g-line' => '40da039e', diff --git a/resources/sql/autopatches/20140522.projecticon.sql b/resources/sql/autopatches/20140522.projecticon.sql new file mode 100644 index 0000000000..66cf71ed72 --- /dev/null +++ b/resources/sql/autopatches/20140522.projecticon.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_project.project + ADD COLUMN icon VARCHAR(32) NOT NULL COLLATE utf8_bin; + +UPDATE {$NAMESPACE}_project.project + SET icon = "fa-briefcase" WHERE icon = ""; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4af4552907..a401d35d1b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1950,9 +1950,11 @@ phutil_register_library_map(array( 'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php', 'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php', 'PhabricatorProjectEditDetailsController' => 'applications/project/controller/PhabricatorProjectEditDetailsController.php', + 'PhabricatorProjectEditIconController' => 'applications/project/controller/PhabricatorProjectEditIconController.php', 'PhabricatorProjectEditMainController' => 'applications/project/controller/PhabricatorProjectEditMainController.php', 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', 'PhabricatorProjectEditorTestCase' => 'applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php', + 'PhabricatorProjectIcon' => 'applications/project/icon/PhabricatorProjectIcon.php', 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', 'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php', 'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php', @@ -4776,9 +4778,11 @@ phutil_register_library_map(array( 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField', 'PhabricatorProjectEditDetailsController' => 'PhabricatorProjectController', + 'PhabricatorProjectEditIconController' => 'PhabricatorProjectController', 'PhabricatorProjectEditMainController' => 'PhabricatorProjectController', 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', 'PhabricatorProjectEditorTestCase' => 'PhabricatorTestCase', + 'PhabricatorProjectIcon' => 'Phobject', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController', 'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController', diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php index 0e6fcce971..be6b1c09e5 100644 --- a/src/applications/phid/PhabricatorObjectHandle.php +++ b/src/applications/phid/PhabricatorObjectHandle.php @@ -10,6 +10,7 @@ final class PhabricatorObjectHandle private $fullName; private $title; private $imageURI; + private $icon; private $timestamp; private $status = PhabricatorObjectHandleStatus::STATUS_OPEN; private $complete; @@ -17,6 +18,18 @@ final class PhabricatorObjectHandle private $objectName; private $policyFiltered; + public function setIcon($icon) { + $this->icon = $icon; + return $this; + } + + public function getIcon() { + if ($this->icon) { + return $this->icon; + } + return $this->getTypeIcon(); + } + public function getTypeIcon() { if ($this->getPHIDType()) { return $this->getPHIDType()->getTypeIcon(); diff --git a/src/applications/project/application/PhabricatorApplicationProject.php b/src/applications/project/application/PhabricatorApplicationProject.php index 0fe314f025..22e016c285 100644 --- a/src/applications/project/application/PhabricatorApplicationProject.php +++ b/src/applications/project/application/PhabricatorApplicationProject.php @@ -50,6 +50,8 @@ final class PhabricatorApplicationProject extends PhabricatorApplication { => 'PhabricatorProjectProfileController', 'picture/(?P[1-9]\d*)/' => 'PhabricatorProjectEditPictureController', + 'icon/(?P[1-9]\d*)/' => + 'PhabricatorProjectEditIconController', 'create/' => 'PhabricatorProjectCreateController', 'board/(?P[1-9]\d*)/'. '(?Pfilter/)?'. diff --git a/src/applications/project/controller/PhabricatorProjectEditIconController.php b/src/applications/project/controller/PhabricatorProjectEditIconController.php new file mode 100644 index 0000000000..c38baa510d --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectEditIconController.php @@ -0,0 +1,111 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $project = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$project) { + return new Aphront404Response(); + } + + $view_uri = '/tag/'.$project->getPrimarySlug().'/'; + $edit_uri = $this->getApplicationURI('edit/'.$project->getID().'/'); + + if ($request->isFormPost()) { + $v_icon = $request->getStr('icon'); + $type_icon = PhabricatorProjectTransaction::TYPE_ICON; + $xactions = array(id(new PhabricatorProjectTransaction()) + ->setTransactionType($type_icon) + ->setNewValue($v_icon)); + + $editor = id(new PhabricatorProjectTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($project, $xactions); + + return id(new AphrontReloadResponse())->setURI($edit_uri); + } + + require_celerity_resource('project-icon-css'); + Javelin::initBehavior('phabricator-tooltips'); + + $project_icons = PhabricatorProjectIcon::getIconMap(); + $ii = 0; + $buttons = array(); + foreach ($project_icons as $icon => $label) { + $view = id(new PHUIIconView()) + ->setIconFont($icon.' bluegrey'); + + $aural = javelin_tag( + 'span', + array( + 'aural' => true, + ), + pht('Choose "%s" Icon', $label)); + + if ($icon == $project->getIcon()) { + $class_extra = ' selected'; + $tip = $label . pht(' - selected'); + } else { + $class_extra = null; + $tip = $label; + } + + $buttons[] = javelin_tag( + 'button', + array( + 'class' => 'icon-button'.$class_extra, + 'name' => 'icon', + 'value' => $icon, + 'type' => 'submit', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tip, + ) + ), + array( + $aural, + $view, + )); + if ((++$ii % 4) == 0) { + $buttons[] = phutil_tag('br'); + } + } + + $buttons = phutil_tag( + 'div', + array( + 'class' => 'icon-grid', + ), + $buttons); + + $dialog = id(new AphrontDialogView()) + ->setUser($viewer) + ->setTitle(pht('Choose Project Icon')) + ->appendChild($buttons) + ->addCancelButton($edit_uri); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } +} diff --git a/src/applications/project/controller/PhabricatorProjectEditMainController.php b/src/applications/project/controller/PhabricatorProjectEditMainController.php index 948bb1bc2f..cade9c1398 100644 --- a/src/applications/project/controller/PhabricatorProjectEditMainController.php +++ b/src/applications/project/controller/PhabricatorProjectEditMainController.php @@ -94,6 +94,14 @@ final class PhabricatorProjectEditMainController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Icon')) + ->setIcon($project->getIcon()) + ->setHref($this->getApplicationURI("icon/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Picture')) diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index ce4f389cbd..27b0c4a13d 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -15,6 +15,7 @@ final class PhabricatorProjectTransactionEditor $types[] = PhabricatorProjectTransaction::TYPE_SLUGS; $types[] = PhabricatorProjectTransaction::TYPE_STATUS; $types[] = PhabricatorProjectTransaction::TYPE_IMAGE; + $types[] = PhabricatorProjectTransaction::TYPE_ICON; return $types; } @@ -35,6 +36,8 @@ final class PhabricatorProjectTransactionEditor return $object->getStatus(); case PhabricatorProjectTransaction::TYPE_IMAGE: return $object->getProfileImagePHID(); + case PhabricatorProjectTransaction::TYPE_ICON: + return $object->getIcon(); } return parent::getCustomTransactionOldValue($object, $xaction); @@ -49,6 +52,7 @@ final class PhabricatorProjectTransactionEditor case PhabricatorProjectTransaction::TYPE_SLUGS: case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: + case PhabricatorProjectTransaction::TYPE_ICON: return $xaction->getNewValue(); } @@ -72,6 +76,9 @@ final class PhabricatorProjectTransactionEditor case PhabricatorProjectTransaction::TYPE_IMAGE: $object->setProfileImagePHID($xaction->getNewValue()); return; + case PhabricatorProjectTransaction::TYPE_ICON: + $object->setIcon($xaction->getNewValue()); + return; case PhabricatorTransactions::TYPE_EDGE: return; case PhabricatorTransactions::TYPE_VIEW_POLICY: @@ -173,6 +180,7 @@ final class PhabricatorProjectTransactionEditor case PhabricatorTransactions::TYPE_JOIN_POLICY: case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: + case PhabricatorProjectTransaction::TYPE_ICON: return; case PhabricatorTransactions::TYPE_EDGE: $edge_type = $xaction->getMetadataValue('edge:type'); @@ -342,6 +350,7 @@ final class PhabricatorProjectTransactionEditor case PhabricatorProjectTransaction::TYPE_NAME: case PhabricatorProjectTransaction::TYPE_STATUS: case PhabricatorProjectTransaction::TYPE_IMAGE: + case PhabricatorProjectTransaction::TYPE_ICON: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, diff --git a/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php b/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php index 13f9527cd9..8dafce7bff 100644 --- a/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php +++ b/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php @@ -15,9 +15,7 @@ final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase { $user2 = $this->createUser(); $user2->save(); - $proj = $this->createProject(); - $proj->setAuthorPHID($user->getPHID()); - $proj->save(); + $proj = $this->createProject($user); $proj = $this->refreshProject($proj, $user, true); @@ -48,9 +46,7 @@ final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase { $user2 = $this->createUser(); $user2->save(); - $proj = $this->createProject(); - $proj->setAuthorPHID($user->getPHID()); - $proj->save(); + $proj = $this->createProject($user); // When edit and view policies are set to "user", anyone can edit. @@ -100,7 +96,6 @@ final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase { $user->save(); $proj = $this->createProjectWithNewAuthor(); - $proj->save(); $proj = $this->refreshProject($proj, $user, true); $this->assertTrue( @@ -228,9 +223,10 @@ final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase { } } - private function createProject() { - $project = new PhabricatorProject(); + private function createProject(PhabricatorUser $user) { + $project = PhabricatorProject::initializeNewProject($user); $project->setName('Test Project '.mt_rand()); + $project->save(); return $project; } @@ -239,8 +235,7 @@ final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase { $author = $this->createUser(); $author->save(); - $project = $this->createProject(); - $project->setAuthorPHID($author->getPHID()); + $project = $this->createProject($author); return $project; } diff --git a/src/applications/project/icon/PhabricatorProjectIcon.php b/src/applications/project/icon/PhabricatorProjectIcon.php new file mode 100644 index 0000000000..8729db9e4e --- /dev/null +++ b/src/applications/project/icon/PhabricatorProjectIcon.php @@ -0,0 +1,27 @@ + pht('Briefcase'), + 'fa-tags' => pht('Tag'), + 'fa-folder' => pht('Folder'), + 'fa-users' => pht('Team'), + 'fa-bug' => pht('Bug'), + 'fa-trash-o' => pht('Garbage'), + 'fa-calendar' => pht('Deadline'), + 'fa-flag-checkered' => pht('Goal'), + 'fa-envelope' => pht('Communication'), + 'fa-truck' => pht('Release'), + 'fa-lock' => pht('Policy'), + 'fa-umbrella' => pht('An Umbrella'), + ); + } + + public static function getLabel($key) { + $map = self::getIconMap(); + return $map[$key]; + } +} diff --git a/src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php b/src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php index bb7a5a1a07..9879b925db 100644 --- a/src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php +++ b/src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php @@ -49,6 +49,7 @@ final class PhabricatorProjectPHIDTypeProject extends PhabricatorPHIDType { $handle->setObjectName('#'.$slug); $handle->setURI("/tag/{$slug}/"); $handle->setImageURI($project->getProfileImageURI()); + $handle->setIcon($project->getIcon()); if ($project->isArchived()) { $handle->setStatus(PhabricatorObjectHandleStatus::STATUS_CLOSED); diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 156c9e506f..ecdd28f31c 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -13,6 +13,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO protected $subprojectPHIDs = array(); protected $phrictionSlug; protected $profileImagePHID; + protected $icon; protected $viewPolicy; protected $editPolicy; @@ -26,10 +27,13 @@ final class PhabricatorProject extends PhabricatorProjectDAO private $profileImageFile = self::ATTACHABLE; private $slugs = self::ATTACHABLE; + const DEFAULT_ICON = 'fa-briefcase'; + public static function initializeNewProject(PhabricatorUser $actor) { return id(new PhabricatorProject()) ->setName('') ->setAuthorPHID($actor->getPHID()) + ->setIcon(self::DEFAULT_ICON) ->setViewPolicy(PhabricatorPolicies::POLICY_USER) ->setEditPolicy(PhabricatorPolicies::POLICY_USER) ->setJoinPolicy(PhabricatorPolicies::POLICY_USER) diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index 25e2fd471b..e749c57468 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -7,6 +7,7 @@ final class PhabricatorProjectTransaction const TYPE_SLUGS = 'project:slugs'; const TYPE_STATUS = 'project:status'; const TYPE_IMAGE = 'project:image'; + const TYPE_ICON = 'project:icon'; // NOTE: This is deprecated, members are just a normal edge now. const TYPE_MEMBERS = 'project:members'; @@ -86,6 +87,12 @@ final class PhabricatorProjectTransaction $this->renderHandleLink($new)); } + case PhabricatorProjectTransaction::TYPE_ICON: + return pht( + '%s set this project\'s icon to %s.', + $author_handle, + PhabricatorProjectIcon::getLabel($new)); + case PhabricatorProjectTransaction::TYPE_SLUGS: $add = array_diff($new, $old); $rem = array_diff($old, $new); diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php index 8ed9e92fbf..5d2b22dd76 100644 --- a/src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php +++ b/src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php @@ -288,7 +288,7 @@ final class PhabricatorTypeaheadCommonDatasourceController ->setDisplayType("Project") ->setURI('/project/view/'.$proj->getID().'/') ->setPHID($proj->getPHID()) - ->setIcon('fa-briefcase bluegrey') + ->setIcon($proj->getIcon().' bluegrey') ->setClosed($closed); $proj_result->setImageURI($proj->getProfileImageURI()); diff --git a/src/view/form/control/AphrontFormTokenizerControl.php b/src/view/form/control/AphrontFormTokenizerControl.php index 8c706cca41..b01e199366 100644 --- a/src/view/form/control/AphrontFormTokenizerControl.php +++ b/src/view/form/control/AphrontFormTokenizerControl.php @@ -62,7 +62,7 @@ final class AphrontFormTokenizerControl extends AphrontFormControl { 'id' => $id, 'src' => $this->datasource, 'value' => mpull($values, 'getFullName', 'getPHID'), - 'icons' => mpull($values, 'getTypeIcon', 'getPHID'), + 'icons' => mpull($values, 'getIcon', 'getPHID'), 'limit' => $this->limit, 'username' => $username, 'placeholder' => $this->placeholder, diff --git a/webroot/rsrc/css/application/projects/project-icon.css b/webroot/rsrc/css/application/projects/project-icon.css new file mode 100644 index 0000000000..1af85b30d6 --- /dev/null +++ b/webroot/rsrc/css/application/projects/project-icon.css @@ -0,0 +1,28 @@ +/** + * @provides project-icon-css + */ + +button.icon-button { + background: #f7f7f7; + border: 1px solid {$lightblueborder}; + position: relative; + width: 16px; + height: 16px; + padding: 12px; + margin: 4px; + text-shadow: none; + box-shadow: none; + box-sizing: content-box; +} + +.icon-grid { + text-align: center; +} + +.icon-icon + .icon-icon { + margin-left: 4px; +} + +button.icon-button.selected { + background: {$bluebackground}; +}