diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -37,7 +37,6 @@ 'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601', 'rsrc/css/application/base/phui-theme.css' => '6b451f24', 'rsrc/css/application/base/standard-page-view.css' => '3c99cdf4', - 'rsrc/css/application/calendar/calendar-icon.css' => 'c69aa59f', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '0ede4c9b', @@ -94,7 +93,6 @@ 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/ponder/ponder-view.css' => '7b0df4da', - 'rsrc/css/application/projects/project-icon.css' => '4e3eaa5a', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', @@ -135,6 +133,7 @@ 'rsrc/css/phui/phui-form-view.css' => '4a1a0f5e', 'rsrc/css/phui/phui-form.css' => '0b98e572', 'rsrc/css/phui/phui-header-view.css' => '55bb32dd', + 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', 'rsrc/css/phui/phui-icon.css' => 'b0a6b1b6', 'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', @@ -465,7 +464,7 @@ 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', - 'rsrc/js/core/behavior-choose-control.js' => '8fee767e', + 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', 'rsrc/js/core/behavior-device.js' => 'a205cf28', @@ -524,7 +523,6 @@ 'aphront-typeahead-control-css' => '0e403212', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', - 'calendar-icon-css' => 'c69aa59f', 'changeset-view-manager' => '58562350', 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', @@ -571,7 +569,7 @@ 'javelin-behavior-audio-source' => '59b251eb', 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-bulk-job-reload' => 'edf8a145', - 'javelin-behavior-choose-control' => '8fee767e', + 'javelin-behavior-choose-control' => '327a00d1', 'javelin-behavior-comment-actions' => 'b65559c0', 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', @@ -809,6 +807,7 @@ 'phui-form-css' => '0b98e572', 'phui-form-view-css' => '4a1a0f5e', 'phui-header-view-css' => '55bb32dd', + 'phui-icon-set-selector-css' => '1ab67aad', 'phui-icon-view-css' => 'b0a6b1b6', 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', @@ -839,7 +838,6 @@ 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', 'ponder-view-css' => '7b0df4da', - 'project-icon-css' => '4e3eaa5a', 'raphael-core' => '51ee6b43', 'raphael-g' => '40dde778', 'raphael-g-line' => '40da039e', @@ -1044,6 +1042,12 @@ '2f670a96' => array( 'phui-theme-css', ), + '327a00d1' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-workflow', + ), '331b1611' => array( 'javelin-install', ), @@ -1522,12 +1526,6 @@ 'javelin-install', 'javelin-dom', ), - '8fee767e' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-workflow', - ), '901935ef' => array( 'javelin-behavior', 'javelin-dom', diff --git a/resources/sql/autopatches/20151231.proj.01.icon.php b/resources/sql/autopatches/20151231.proj.01.icon.php new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20151231.proj.01.icon.php @@ -0,0 +1,34 @@ + 'project', + 'fa-tags' => 'tag', + 'fa-lock' => 'policy', + 'fa-users' => 'group', + + 'fa-folder' => 'folder', + 'fa-calendar' => 'timeline', + 'fa-flag-checkered' => 'goal', + 'fa-truck' => 'release', + + 'fa-bug' => 'bugs', + 'fa-trash-o' => 'cleanup', + 'fa-umbrella' => 'umbrella', + 'fa-envelope' => 'communication', + + 'fa-building' => 'organization', + 'fa-cloud' => 'infrastructure', + 'fa-credit-card' => 'account', + 'fa-flask' => 'experimental', +); + +$table = new PhabricatorProject(); +$conn_w = $table->establishConnection('w'); +foreach ($icon_map as $old_icon => $new_key) { + queryfx( + $conn_w, + 'UPDATE %T SET icon = %s WHERE icon = %s', + $table->getTableName(), + $new_key, + $old_icon); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2902,6 +2902,7 @@ 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', 'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php', + 'PhabricatorProjectTypeConfigOptionType' => 'applications/project/config/PhabricatorProjectTypeConfigOptionType.php', 'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php', 'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php', 'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php', @@ -7268,6 +7269,7 @@ 'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorProjectTypeConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener', 'PhabricatorProjectUpdateController' => 'PhabricatorProjectController', 'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', diff --git a/src/applications/config/custom/PhabricatorConfigOptionType.php b/src/applications/config/custom/PhabricatorConfigOptionType.php --- a/src/applications/config/custom/PhabricatorConfigOptionType.php +++ b/src/applications/config/custom/PhabricatorConfigOptionType.php @@ -24,8 +24,7 @@ $value) { if (is_array($value)) { - $json = new PhutilJSON(); - return $json->encodeFormatted($value); + return PhabricatorConfigJSON::prettyPrintJSON($value); } else { return $value; } diff --git a/src/applications/files/controller/PhabricatorFileIconSetSelectController.php b/src/applications/files/controller/PhabricatorFileIconSetSelectController.php --- a/src/applications/files/controller/PhabricatorFileIconSetSelectController.php +++ b/src/applications/files/controller/PhabricatorFileIconSetSelectController.php @@ -26,7 +26,7 @@ } } - require_celerity_resource('project-icon-css'); + require_celerity_resource('phui-icon-set-selector-css'); Javelin::initBehavior('phabricator-tooltips'); $ii = 0; @@ -37,6 +37,20 @@ $view = id(new PHUIIconView()) ->setIconFont($icon->getIcon()); + $classes = array(); + $classes[] = 'icon-button'; + + $is_selected = ($icon->getKey() == $v_icon); + + if ($is_selected) { + $classes[] = 'selected'; + } + + $is_disabled = $icon->getIsDisabled(); + if ($is_disabled && !$is_selected) { + continue; + } + $aural = javelin_tag( 'span', array( @@ -44,13 +58,6 @@ ), pht('Choose "%s" Icon', $label)); - $classes = array(); - $classes[] = 'icon-button'; - - if ($icon->getKey() == $v_icon) { - $classes[] = 'selected'; - } - $buttons[] = javelin_tag( 'button', array( diff --git a/src/applications/files/iconset/PhabricatorIconSetIcon.php b/src/applications/files/iconset/PhabricatorIconSetIcon.php --- a/src/applications/files/iconset/PhabricatorIconSetIcon.php +++ b/src/applications/files/iconset/PhabricatorIconSetIcon.php @@ -6,6 +6,7 @@ private $key; private $icon; private $label; + private $isDisabled; public function setKey($key) { $this->key = $key; @@ -28,6 +29,15 @@ return $this->icon; } + public function setIsDisabled($is_disabled) { + $this->isDisabled = $is_disabled; + return $this; + } + + public function getIsDisabled() { + return $this->isDisabled; + } + public function setLabel($label) { $this->label = $label; return $this; diff --git a/src/applications/project/conduit/ProjectConduitAPIMethod.php b/src/applications/project/conduit/ProjectConduitAPIMethod.php --- a/src/applications/project/conduit/ProjectConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectConduitAPIMethod.php @@ -26,7 +26,7 @@ $project_slugs = $project->getSlugs(); $project_slugs = array_values(mpull($project_slugs, 'getSlug')); - $project_icon = substr($project->getIcon(), 3); + $project_icon = $project->getDisplayIconKey(); $result[$project->getPHID()] = array( 'id' => $project->getID(), diff --git a/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php b/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php --- a/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php @@ -76,11 +76,6 @@ $request->getValue('icons'); if ($request->getValue('icons')) { $icons = array(); - // the internal 'fa-' prefix is a detail hidden from api clients - // but needs to pre prepended to the values in the icons array: - foreach ($request->getValue('icons') as $value) { - $icons[] = 'fa-'.$value; - } $query->withIcons($icons); } diff --git a/src/applications/project/config/PhabricatorProjectConfigOptions.php b/src/applications/project/config/PhabricatorProjectConfigOptions.php --- a/src/applications/project/config/PhabricatorProjectConfigOptions.php +++ b/src/applications/project/config/PhabricatorProjectConfigOptions.php @@ -20,6 +20,34 @@ } public function getOptions() { + $default_icons = PhabricatorProjectIconSet::getDefaultConfiguration(); + $icons_type = 'custom:PhabricatorProjectTypeConfigOptionType'; + + $icons_description = $this->deformat(pht(<< Icons and Images}. + +Configure a list of icon specifications. Each icon specification should be +a dictionary, which may contain these keys: + + - `key` //Required string.// Internal key identifying the icon. + - `name` //Required string.// Human-readable icon name. + - `icon` //Required string.// Specifies which actual icon image to use. + - `default` //Optional bool.// Selects a default icon. Exactly one icon must + be selected as the default. + - `disabled` //Optional bool.// If true, this icon will no longer be + available for selection when creating or editing projects. + - `special` //Optional string.// Marks an icon as a special icon: + - `milestone` This is the icon for milestones. Exactly one icon must be + selected as the milestone icon. + +You can look at the default configuration below for an example of a valid +configuration. +EOTEXT + )); + + $default_fields = array( 'std:project:internal:description' => true, ); @@ -45,6 +73,9 @@ $this->newOption('projects.fields', $custom_field_type, $default_fields) ->setCustomData(id(new PhabricatorProject())->getCustomFieldBaseClass()) ->setDescription(pht('Select and reorder project fields.')), + $this->newOption('projects.icons', $icons_type, $default_icons) + ->setSummary(pht('Adjust project icons.')) + ->setDescription($icons_description), ); } diff --git a/src/applications/project/config/PhabricatorProjectTypeConfigOptionType.php b/src/applications/project/config/PhabricatorProjectTypeConfigOptionType.php new file mode 100644 --- /dev/null +++ b/src/applications/project/config/PhabricatorProjectTypeConfigOptionType.php @@ -0,0 +1,10 @@ +supportsMilestones()) { - $milestones_icon = 'fa-map-marker'; - } else { - $milestones_icon = 'fa-map-marker grey'; + $key = PhabricatorProjectIconSet::getMilestoneIconKey(); + $milestones_icon = PhabricatorProjectIconSet::getIconIcon($key); + if (!$project->supportsMilestones()) { + $milestones_icon = "{$milestones_icon} grey"; } $nav->addIcon( diff --git a/src/applications/project/icon/PhabricatorProjectIconSet.php b/src/applications/project/icon/PhabricatorProjectIconSet.php --- a/src/applications/project/icon/PhabricatorProjectIconSet.php +++ b/src/applications/project/icon/PhabricatorProjectIconSet.php @@ -5,38 +5,121 @@ const ICONSETKEY = 'projects'; + const SPECIAL_MILESTONE = 'milestone'; + public function getSelectIconTitleText() { return pht('Choose Project Icon'); } - protected function newIcons() { - $map = array( - 'fa-briefcase' => 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'), - - 'fa-cloud' => pht('The Cloud'), - 'fa-building' => pht('Company'), - 'fa-credit-card' => pht('Accounting'), - 'fa-flask' => pht('Experimental'), + public static function getDefaultConfiguration() { + return array( + array( + 'key' => 'project', + 'icon' => 'fa-briefcase', + 'name' => pht('Project'), + 'default' => true, + ), + array( + 'key' => 'tag', + 'icon' => 'fa-tags', + 'name' => pht('Tag'), + ), + array( + 'key' => 'policy', + 'icon' => 'fa-lock', + 'name' => pht('Policy'), + ), + array( + 'key' => 'group', + 'icon' => 'fa-users', + 'name' => pht('Group'), + ), + array( + 'key' => 'folder', + 'icon' => 'fa-folder', + 'name' => pht('Folder'), + ), + array( + 'key' => 'timeline', + 'icon' => 'fa-calendar', + 'name' => pht('Timeline'), + ), + array( + 'key' => 'goal', + 'icon' => 'fa-flag-checkered', + 'name' => pht('Goal'), + ), + array( + 'key' => 'release', + 'icon' => 'fa-truck', + 'name' => pht('Release'), + ), + array( + 'key' => 'bugs', + 'icon' => 'fa-bug', + 'name' => pht('Bugs'), + ), + array( + 'key' => 'cleanup', + 'icon' => 'fa-trash-o', + 'name' => pht('Cleanup'), + ), + array( + 'key' => 'umbrella', + 'icon' => 'fa-umbrella', + 'name' => pht('Umbrella'), + ), + array( + 'key' => 'communication', + 'icon' => 'fa-envelope', + 'name' => pht('Communication'), + ), + array( + 'key' => 'organization', + 'icon' => 'fa-building', + 'name' => pht('Organization'), + ), + array( + 'key' => 'infrastructure', + 'icon' => 'fa-cloud', + 'name' => pht('Infrastructure'), + ), + array( + 'key' => 'account', + 'icon' => 'fa-credit-card', + 'name' => pht('Account'), + ), + array( + 'key' => 'experimental', + 'icon' => 'fa-flask', + 'name' => pht('Experimental'), + ), + array( + 'key' => 'milestone', + 'icon' => 'fa-map-marker', + 'name' => pht('Milestone'), + 'special' => self::SPECIAL_MILESTONE, + ), ); + } + + + protected function newIcons() { + $map = self::getIconSpecifications(); $icons = array(); - foreach ($map as $key => $label) { + foreach ($map as $spec) { + $special = idx($spec, 'special'); + + if ($special === self::SPECIAL_MILESTONE) { + continue; + } + $icons[] = id(new PhabricatorIconSetIcon()) - ->setKey($key) - ->setLabel($label); + ->setKey($spec['key']) + ->setIsDisabled(idx($spec, 'disabled')) + ->setIcon($spec['icon']) + ->setLabel($spec['name']); } return $icons; @@ -52,4 +135,183 @@ return $shades; } + private static function getIconSpecifications() { + return PhabricatorEnv::getEnvConfig('projects.icons'); + } + + public static function getDefaultIconKey() { + $icons = self::getIconSpecifications(); + foreach ($icons as $icon) { + if (idx($icon, 'default')) { + return $icon['key']; + } + } + return null; + } + + public static function getIconIcon($key) { + $spec = self::getIconSpec($key); + return idx($spec, 'icon', null); + } + + public static function getIconName($key) { + $spec = self::getIconSpec($key); + return idx($spec, 'name', null); + } + + private static function getIconSpec($key) { + $icons = self::getIconSpecifications(); + foreach ($icons as $icon) { + if (idx($icon, 'key') === $key) { + return $icon; + } + } + + return array(); + } + + public static function getMilestoneIconKey() { + $icons = self::getIconSpecifications(); + foreach ($icons as $icon) { + if (idx($icon, 'special') === self::SPECIAL_MILESTONE) { + return idx($icon, 'key'); + } + } + return null; + } + + public static function validateConfiguration($config) { + if (!is_array($config)) { + throw new Exception( + pht('Configuration must be a list of project icon specifications.')); + } + + foreach ($config as $idx => $value) { + if (!is_array($value)) { + throw new Exception( + pht( + 'Value for index "%s" should be a dictionary.', + $idx)); + } + + PhutilTypeSpec::checkMap( + $value, + array( + 'key' => 'string', + 'name' => 'string', + 'icon' => 'string', + 'special' => 'optional string', + 'disabled' => 'optional bool', + 'default' => 'optional bool', + )); + + if (!preg_match('/^[a-z]{1,32}\z/', $value['key'])) { + throw new Exception( + pht( + 'Icon key "%s" is not a valid icon key. Icon keys must be 1-32 '. + 'characters long and contain only lowercase letters. For example, '. + '"%s" and "%s" are reasonable keys.', + 'tag', + 'group')); + } + + $special = idx($value, 'special'); + $valid = array( + self::SPECIAL_MILESTONE => true, + ); + + if ($special !== null) { + if (empty($valid[$special])) { + throw new Exception( + pht( + 'Icon special attribute "%s" is not valid. Recognized special '. + 'attributes are: %s.', + $special, + implode(', ', array_keys($valid)))); + } + } + } + + $default = null; + $milestone = null; + $keys = array(); + foreach ($config as $idx => $value) { + $key = $value['key']; + if (isset($keys[$key])) { + throw new Exception( + pht( + 'Project icons must have unique keys, but two icons share the '. + 'same key ("%s").', + $key)); + } else { + $keys[$key] = true; + } + + $is_disabled = idx($value, 'disabled'); + + if (idx($value, 'default')) { + if ($default === null) { + if ($is_disabled) { + throw new Exception( + pht( + 'The project icon marked as the default icon ("%s") must not '. + 'be disabled.', + $key)); + } + $default = $value; + } else { + $original_key = $default['key']; + throw new Exception( + pht( + 'Two different icons ("%s", "%s") are marked as the default '. + 'icon. Only one icon may be marked as the default.', + $key, + $original_key)); + } + } + + $special = idx($value, 'special'); + if ($special === self::SPECIAL_MILESTONE) { + if ($milestone === null) { + if ($is_disabled) { + throw new Exception( + pht( + 'The project icon ("%s") with special attribute "%s" must '. + 'not be disabled', + $key, + self::SPECIAL_MIILESTONE)); + } + $milestone = $value; + } else { + $original_key = $milestone['key']; + throw new Exception( + pht( + 'Two different icons ("%s", "%s") are marked with special '. + 'attribute "%s". Only one icon may be marked with this '. + 'attribute.', + $key, + $original_key, + self::SPECIAL_MILESTONE)); + } + } + } + + if ($default === null) { + throw new Exception( + pht( + 'Project icons must include one icon marked as the "%s" icon, '. + 'but no such icon exists.', + 'default')); + } + + if ($milestone === null) { + throw new Exception( + pht( + 'Project icons must include one icon marked with special attribute '. + '"%s", but no such icon exists.', + self::SPECIAL_MILESTONE)); + } + + } + } diff --git a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php --- a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php +++ b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php @@ -51,7 +51,7 @@ } $handle->setImageURI($project->getProfileImageURI()); - $handle->setIcon($project->getDisplayIcon()); + $handle->setIcon($project->getDisplayIconIcon()); $handle->setTagColor($project->getDisplayColor()); if ($project->isArchived()) { diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -131,6 +131,10 @@ $set = new PhabricatorProjectIconSet(); foreach ($set->getIcons() as $icon) { + if ($icon->getIsDisabled()) { + continue; + } + $options[$icon->getKey()] = array( id(new PHUIIconView()) ->setIconFont($icon->getIcon()), diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -45,7 +45,6 @@ private $slugs = self::ATTACHABLE; private $parentProject = self::ATTACHABLE; - const DEFAULT_ICON = 'fa-briefcase'; const DEFAULT_COLOR = 'blue'; const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken'; @@ -63,9 +62,11 @@ $join_policy = $app->getPolicy( ProjectDefaultJoinCapability::CAPABILITY); + $default_icon = PhabricatorProjectIconSet::getDefaultIconKey(); + return id(new PhabricatorProject()) ->setAuthorPHID($actor->getPHID()) - ->setIcon(self::DEFAULT_ICON) + ->setIcon($default_icon) ->setColor(self::DEFAULT_COLOR) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) @@ -484,12 +485,24 @@ return $number; } - public function getDisplayIcon() { + public function getDisplayIconKey() { if ($this->isMilestone()) { - return 'fa-map-marker'; + $key = PhabricatorProjectIconSet::getMilestoneIconKey(); + } else { + $key = $this->getIcon(); } - return $this->getIcon(); + return $key; + } + + public function getDisplayIconIcon() { + $key = $this->getDisplayIconKey(); + return PhabricatorProjectIconSet::getIconIcon($key); + } + + public function getDisplayIconName() { + $key = $this->getDisplayIconKey(); + return PhabricatorProjectIconSet::getIconName($key); } public function getDisplayColor() { @@ -608,6 +621,10 @@ ->setKey('slug') ->setType('string') ->setDescription(pht('Primary slug/hashtag.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('icon') + ->setType('map') + ->setDescription(pht('Information about the project icon.')), ); } @@ -615,6 +632,11 @@ return array( 'name' => $this->getName(), 'slug' => $this->getPrimarySlug(), + 'icon' => array( + 'key' => $this->getDisplayIconKey(), + 'name' => $this->getDisplayIconName(), + 'icon' => $this->getDisplayIconIcon(), + ), ); } diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php --- a/src/applications/project/view/PhabricatorProjectListView.php +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -25,15 +25,24 @@ foreach ($projects as $key => $project) { $id = $project->getID(); - $tag_list = id(new PHUIHandleTagListView()) - ->setSlim(true) - ->setHandles(array($handles[$project->getPHID()])); + $icon = $project->getDisplayIconIcon(); + $color = $project->getColor(); + + $icon_icon = id(new PHUIIconView()) + ->setIconFont("{$icon} {$color}"); + + $icon_name = $project->getDisplayIconName(); $item = id(new PHUIObjectItemView()) ->setHeader($project->getName()) ->setHref("/project/view/{$id}/") ->setImageURI($project->getProfileImageURI()) - ->addAttribute($tag_list); + ->addAttribute( + array( + $icon_icon, + ' ', + $icon_name, + )); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) { $item->addIcon('delete-grey', pht('Archived')); diff --git a/webroot/rsrc/css/application/calendar/calendar-icon.css b/webroot/rsrc/css/application/calendar/calendar-icon.css deleted file mode 100644 --- a/webroot/rsrc/css/application/calendar/calendar-icon.css +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @provides calendar-icon-css - */ - -button.icon-button { - background: {$lightgreybackground}; - 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}; -} diff --git a/webroot/rsrc/css/application/projects/project-icon.css b/webroot/rsrc/css/phui/phui-icon-set-selector.css rename from webroot/rsrc/css/application/projects/project-icon.css rename to webroot/rsrc/css/phui/phui-icon-set-selector.css --- a/webroot/rsrc/css/application/projects/project-icon.css +++ b/webroot/rsrc/css/phui/phui-icon-set-selector.css @@ -1,5 +1,5 @@ /** - * @provides project-icon-css + * @provides phui-icon-set-selector-css */ button.icon-button { @@ -25,4 +25,5 @@ button.icon-button.selected { background: {$bluebackground}; + border: 1px solid {$blueborder}; } diff --git a/webroot/rsrc/js/core/behavior-choose-control.js b/webroot/rsrc/js/core/behavior-choose-control.js --- a/webroot/rsrc/js/core/behavior-choose-control.js +++ b/webroot/rsrc/js/core/behavior-choose-control.js @@ -22,7 +22,7 @@ } var params = { - value: input.value + icon: input.value }; new JX.Workflow(data.uri, params)