1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 03:20:59 +01:00

Allow installs to customize project icons

Summary:
Ref T10010. Ref T5819. General alignment of the stars:

  - There were some hacks in Conduit around stripping `fa-...` off icons when reading and writing that I wanted to get rid of.
  - We probably have room for a subtitle in the new heavy nav, and using the icon name is a good starting point (and maybe good enough on its own?)
  - The project list was real bad looking with redundant tag/names, now it is very slightly less bad looking with non-redundant types?
  - Some installs will want to call Milestones something else, and this gets us a big part of the way there.
  - This may slightly help to reinforce "tag" vs "policy" vs "group" stuff?

---

I'm letting installs have enough rope to shoot themselves in the foot (e.g., define 100 icons). It isn't the end of the world if they reuse icons, and is clearly their fault.

I think the cases where 100 icons will break down are:

  - Icon selector dialog may get very unwieldy.
  - Query UI will be pretty iffy/huge with 100 icons.

We could improve these fairly easily if an install comes up with a reasonable use case for having 100 icons.

---

The UI on the icon itself in the list views is a little iffy -- mostly, it's too saturated/bold.

I'd ideally like to try either:

  - rendering a "shade" version (i.e. lighter, less-saturated color); or
  - rendering a "shade" tag with just the icon in it.

However, there didn't seem to be a way to do the first one right now (`fa-example sh-blue` doesn't work) and the second one had weird margins/padding, so I left it like this for now. I figure we can clean it up once we build the thick nav, since that will probably also want an identical element.

(I don't want to render a full tag with the icon + name since I think that's confusing -- it looks like a project/object tag, but is not.)

Test Plan:
{F1049905}

{F1049906}

Reviewers: chad

Reviewed By: chad

Subscribers: 20after4, Luke081515.2

Maniphest Tasks: T5819, T10010

Differential Revision: https://secure.phabricator.com/D14918
This commit is contained in:
epriestley 2015-12-30 04:36:48 -08:00
parent c7520cd9f2
commit 9ab22e21b3
19 changed files with 452 additions and 96 deletions

View file

@ -37,7 +37,6 @@ return array(
'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 @@ return array(
'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 @@ return array(
'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 @@ return array(
'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 @@ return array(
'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 @@ return array(
'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 @@ return array(
'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 @@ return array(
'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 @@ return array(
'2f670a96' => array(
'phui-theme-css',
),
'327a00d1' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-workflow',
),
'331b1611' => array(
'javelin-install',
),
@ -1522,12 +1526,6 @@ return array(
'javelin-install',
'javelin-dom',
),
'8fee767e' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-workflow',
),
'901935ef' => array(
'javelin-behavior',
'javelin-dom',

View file

@ -0,0 +1,34 @@
<?php
$icon_map = array(
'fa-briefcase' => '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);
}

View file

@ -2902,6 +2902,7 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorProjectTypeConfigOptionType' => 'PhabricatorConfigJSONOptionType',
'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener',
'PhabricatorProjectUpdateController' => 'PhabricatorProjectController',
'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',

View file

@ -24,8 +24,7 @@ abstract class PhabricatorConfigOptionType extends Phobject {
$value) {
if (is_array($value)) {
$json = new PhutilJSON();
return $json->encodeFormatted($value);
return PhabricatorConfigJSON::prettyPrintJSON($value);
} else {
return $value;
}

View file

@ -26,7 +26,7 @@ final class PhabricatorFileIconSetSelectController
}
}
require_celerity_resource('project-icon-css');
require_celerity_resource('phui-icon-set-selector-css');
Javelin::initBehavior('phabricator-tooltips');
$ii = 0;
@ -37,6 +37,20 @@ final class PhabricatorFileIconSetSelectController
$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 @@ final class PhabricatorFileIconSetSelectController
),
pht('Choose "%s" Icon', $label));
$classes = array();
$classes[] = 'icon-button';
if ($icon->getKey() == $v_icon) {
$classes[] = 'selected';
}
$buttons[] = javelin_tag(
'button',
array(

View file

@ -6,6 +6,7 @@ final class PhabricatorIconSetIcon
private $key;
private $icon;
private $label;
private $isDisabled;
public function setKey($key) {
$this->key = $key;
@ -28,6 +29,15 @@ final class PhabricatorIconSetIcon
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;

View file

@ -26,7 +26,7 @@ abstract class ProjectConduitAPIMethod extends ConduitAPIMethod {
$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(),

View file

@ -76,11 +76,6 @@ final class ProjectQueryConduitAPIMethod extends ProjectConduitAPIMethod {
$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);
}

View file

@ -20,6 +20,34 @@ final class PhabricatorProjectConfigOptions
}
public function getOptions() {
$default_icons = PhabricatorProjectIconSet::getDefaultConfiguration();
$icons_type = 'custom:PhabricatorProjectTypeConfigOptionType';
$icons_description = $this->deformat(pht(<<<EOTEXT
Allows you to change and customize the available project icons.
You can find a list of available icons in {nav UIExamples > 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 @@ final class PhabricatorProjectConfigOptions
$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),
);
}

View file

@ -0,0 +1,10 @@
<?php
final class PhabricatorProjectTypeConfigOptionType
extends PhabricatorConfigJSONOptionType {
public function validateOption(PhabricatorConfigOption $option, $value) {
PhabricatorProjectIconSet::validateConfiguration($value);
}
}

View file

@ -156,10 +156,10 @@ abstract class PhabricatorProjectController extends PhabricatorController {
$subprojects_icon = 'fa-sitemap grey';
}
if ($project->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(

View file

@ -5,38 +5,121 @@ final class PhabricatorProjectIconSet
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 @@ final class PhabricatorProjectIconSet
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));
}
}
}

View file

@ -51,7 +51,7 @@ final class PhabricatorProjectProjectPHIDType extends PhabricatorPHIDType {
}
$handle->setImageURI($project->getProfileImageURI());
$handle->setIcon($project->getDisplayIcon());
$handle->setIcon($project->getDisplayIconIcon());
$handle->setTagColor($project->getDisplayColor());
if ($project->isArchived()) {

View file

@ -131,6 +131,10 @@ protected function buildQueryFromParameters(array $map) {
$set = new PhabricatorProjectIconSet();
foreach ($set->getIcons() as $icon) {
if ($icon->getIsDisabled()) {
continue;
}
$options[$icon->getKey()] = array(
id(new PHUIIconView())
->setIconFont($icon->getIcon()),

View file

@ -45,7 +45,6 @@ final class PhabricatorProject extends PhabricatorProjectDAO
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 @@ final class PhabricatorProject extends PhabricatorProjectDAO
$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 @@ final class PhabricatorProject extends PhabricatorProjectDAO
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 @@ final class PhabricatorProject extends PhabricatorProjectDAO
->setKey('slug')
->setType('string')
->setDescription(pht('Primary slug/hashtag.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('icon')
->setType('map<string, wild>')
->setDescription(pht('Information about the project icon.')),
);
}
@ -615,6 +632,11 @@ final class PhabricatorProject extends PhabricatorProjectDAO
return array(
'name' => $this->getName(),
'slug' => $this->getPrimarySlug(),
'icon' => array(
'key' => $this->getDisplayIconKey(),
'name' => $this->getDisplayIconName(),
'icon' => $this->getDisplayIconIcon(),
),
);
}

View file

@ -25,15 +25,24 @@ final class PhabricatorProjectListView extends AphrontView {
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'));

View file

@ -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};
}

View file

@ -1,5 +1,5 @@
/**
* @provides project-icon-css
* @provides phui-icon-set-selector-css
*/
button.icon-button {
@ -25,4 +25,5 @@ button.icon-button {
button.icon-button.selected {
background: {$bluebackground};
border: 1px solid {$blueborder};
}

View file

@ -22,7 +22,7 @@ JX.behavior('choose-control', function() {
}
var params = {
value: input.value
icon: input.value
};
new JX.Workflow(data.uri, params)