Skip to content

Commit 4f05736

Browse files
author
epriestley
committed
Add an icon+background selector for project images
Summary: Makes it easy to choose distinctive icons for projects. Test Plan: {F71018} {F71020} {F71019} {F71021} Reviewers: btrahan, chad Reviewed By: chad CC: chad, aran Differential Revision: https://secure.phabricator.com/D7333
1 parent 70b7b9a commit 4f05736

21 files changed

+973
-287
lines changed

resources/builtin/project.png

2.98 KB
Loading

resources/sprite/manifest/projects.json

Lines changed: 169 additions & 169 deletions
Large diffs are not rendered by default.

src/__celerity_resource_map__.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,6 +1791,18 @@
17911791
),
17921792
'disk' => '/rsrc/js/core/behavior-history-install.js',
17931793
),
1794+
'javelin-behavior-icon-composer' =>
1795+
array(
1796+
'uri' => '/res/0be5c462/rsrc/js/application/files/behavior-icon-composer.js',
1797+
'type' => 'js',
1798+
'requires' =>
1799+
array(
1800+
0 => 'javelin-behavior',
1801+
1 => 'javelin-dom',
1802+
2 => 'javelin-stratcom',
1803+
),
1804+
'disk' => '/rsrc/js/application/files/behavior-icon-composer.js',
1805+
),
17941806
'javelin-behavior-konami' =>
17951807
array(
17961808
'uri' => '/res/b7bb7c24/rsrc/js/core/behavior-konami.js',
@@ -1802,6 +1814,18 @@
18021814
),
18031815
'disk' => '/rsrc/js/core/behavior-konami.js',
18041816
),
1817+
'javelin-behavior-launch-icon-composer' =>
1818+
array(
1819+
'uri' => '/res/202488ac/rsrc/js/application/files/behavior-launch-icon-composer.js',
1820+
'type' => 'js',
1821+
'requires' =>
1822+
array(
1823+
0 => 'javelin-behavior',
1824+
1 => 'javelin-dom',
1825+
2 => 'javelin-workflow',
1826+
),
1827+
'disk' => '/rsrc/js/application/files/behavior-launch-icon-composer.js',
1828+
),
18051829
'javelin-behavior-lightbox-attachments' =>
18061830
array(
18071831
'uri' => '/res/72b4d3a8/rsrc/js/core/behavior-lightbox-attachments.js',
@@ -3062,7 +3086,7 @@
30623086
),
30633087
'people-profile-css' =>
30643088
array(
3065-
'uri' => '/res/d50d9502/rsrc/css/application/people/people-profile.css',
3089+
'uri' => '/res/f1da102e/rsrc/css/application/people/people-profile.css',
30663090
'type' => 'css',
30673091
'requires' =>
30683092
array(
@@ -4227,7 +4251,7 @@
42274251
),
42284252
'sprite-projects-css' =>
42294253
array(
4230-
'uri' => '/res/3ff34b69/rsrc/css/sprite-projects.css',
4254+
'uri' => '/res/40eacbfb/rsrc/css/sprite-projects.css',
42314255
'type' => 'css',
42324256
'requires' =>
42334257
array(

src/__phutil_library_map__.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,7 @@
12091209
'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php',
12101210
'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php',
12111211
'PhabricatorFileCommentController' => 'applications/files/controller/PhabricatorFileCommentController.php',
1212+
'PhabricatorFileComposeController' => 'applications/files/controller/PhabricatorFileComposeController.php',
12121213
'PhabricatorFileController' => 'applications/files/controller/PhabricatorFileController.php',
12131214
'PhabricatorFileDAO' => 'applications/files/storage/PhabricatorFileDAO.php',
12141215
'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php',
@@ -1523,6 +1524,7 @@
15231524
'PhabricatorProjectProfile' => 'applications/project/storage/PhabricatorProjectProfile.php',
15241525
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
15251526
'PhabricatorProjectProfileEditController' => 'applications/project/controller/PhabricatorProjectProfileEditController.php',
1527+
'PhabricatorProjectProfilePictureController' => 'applications/project/controller/PhabricatorProjectProfilePictureController.php',
15261528
'PhabricatorProjectQuery' => 'applications/project/query/PhabricatorProjectQuery.php',
15271529
'PhabricatorProjectSearchEngine' => 'applications/project/query/PhabricatorProjectSearchEngine.php',
15281530
'PhabricatorProjectSearchIndexer' => 'applications/project/search/PhabricatorProjectSearchIndexer.php',
@@ -3397,6 +3399,7 @@
33973399
3 => 'PhabricatorPolicyInterface',
33983400
),
33993401
'PhabricatorFileCommentController' => 'PhabricatorFileController',
3402+
'PhabricatorFileComposeController' => 'PhabricatorFileController',
34003403
'PhabricatorFileController' => 'PhabricatorController',
34013404
'PhabricatorFileDAO' => 'PhabricatorLiskDAO',
34023405
'PhabricatorFileDataController' => 'PhabricatorFileController',
@@ -3739,6 +3742,7 @@
37393742
'PhabricatorProjectProfile' => 'PhabricatorProjectDAO',
37403743
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
37413744
'PhabricatorProjectProfileEditController' => 'PhabricatorProjectController',
3745+
'PhabricatorProjectProfilePictureController' => 'PhabricatorProjectController',
37423746
'PhabricatorProjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
37433747
'PhabricatorProjectSearchEngine' => 'PhabricatorApplicationSearchEngine',
37443748
'PhabricatorProjectSearchIndexer' => 'PhabricatorSearchDocumentIndexer',

src/applications/files/application/PhabricatorApplicationFiles.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public function getRoutes() {
5151
'(query/(?P<key>[^/]+)/)?' => 'PhabricatorFileListController',
5252
'upload/' => 'PhabricatorFileUploadController',
5353
'dropupload/' => 'PhabricatorFileDropUploadController',
54+
'compose/' => 'PhabricatorFileComposeController',
5455
'comment/(?P<id>[1-9]\d*)/' => 'PhabricatorFileCommentController',
5556
'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorFileDeleteController',
5657
'info/(?P<phid>[^/]+)/' => 'PhabricatorFileInfoController',
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
<?php
2+
3+
final class PhabricatorFileComposeController
4+
extends PhabricatorFileController {
5+
6+
public function processRequest() {
7+
$request = $this->getRequest();
8+
$viewer = $request->getUser();
9+
10+
$colors = array(
11+
'red' => pht('Verbillion'),
12+
'orange' => pht('Navel Orange'),
13+
'yellow' => pht('Prim Goldenrod'),
14+
'green' => pht('Lustrous Verdant'),
15+
'blue' => pht('Tropical Deep'),
16+
'sky' => pht('Wide Open Sky'),
17+
'indigo' => pht('Pleated Khaki'),
18+
'violet' => pht('Aged Merlot'),
19+
'charcoal' => pht('Gemstone'),
20+
'backdrop' => pht('Driven Snow'),
21+
);
22+
23+
$manifest = PHUIIconView::getSheetManifest(PHUIIconView::SPRITE_PROJECTS);
24+
25+
if ($request->isFormPost()) {
26+
$icon = $request->getStr('icon');
27+
$color = $request->getStr('color');
28+
29+
if (isset($colors[$color]) && isset($manifest['projects-'.$icon])) {
30+
$root = dirname(phutil_get_library_root('phabricator'));
31+
$icon_file = $root.'/resources/sprite/projects_1x/'.$icon.'.png';
32+
$icon_data = Filesystem::readFile($icon_file);
33+
34+
35+
$data = $this->composeImage($color, $icon_data);
36+
37+
$file = PhabricatorFile::buildFromFileDataOrHash(
38+
$data,
39+
array(
40+
'name' => 'project.png',
41+
));
42+
43+
$content = array(
44+
'phid' => $file->getPHID(),
45+
);
46+
47+
return id(new AphrontAjaxResponse())->setContent($content);
48+
}
49+
}
50+
51+
$value_color = head_key($colors);
52+
$value_icon = head_key($manifest);
53+
$value_icon = substr($value_icon, strlen('projects-'));
54+
55+
require_celerity_resource('people-profile-css');
56+
57+
$buttons = array();
58+
foreach ($colors as $color => $name) {
59+
$buttons[] = javelin_tag(
60+
'button',
61+
array(
62+
'class' => 'grey profile-image-button',
63+
'sigil' => 'has-tooltip compose-select-color',
64+
'style' => 'margin: 0 8px 8px 0',
65+
'meta' => array(
66+
'color' => $color,
67+
'tip' => $name,
68+
),
69+
),
70+
id(new PHUIIconView())
71+
->addClass('compose-background-'.$color));
72+
}
73+
74+
$icons = array();
75+
76+
$icon_quips = array(
77+
'8ball' => pht('Take a Risk'),
78+
'alien' => pht('Foreign Interface'),
79+
'announce' => pht('Louder is Better'),
80+
'art' => pht('Unique Snowflake'),
81+
'award' => pht('Shooting Star'),
82+
'bacon' => pht('Healthy Vegetables'),
83+
'bandaid' => pht('Durable Infrastructure'),
84+
'beer' => pht('Healthy Vegetable Juice'),
85+
'bomb' => pht('Imminent Success'),
86+
'briefcase' => pht('Adventure Pack'),
87+
'bug' => pht('Costumed Egg'),
88+
'calendar' => pht('Everyone Loves Meetings'),
89+
'cloud' => pht('Water Cycle'),
90+
'coffee' => pht('Half-Whip Nonfat Soy Latte'),
91+
'creditcard' => pht('Expense It'),
92+
'death' => pht('Calcium Promotes Bone Health'),
93+
'desktop' => pht('Magical Portal'),
94+
'dropbox' => pht('Cardboard Box'),
95+
'education' => pht('Debt'),
96+
'experimental' => pht('CAUTION: Dangerous Chemicals'),
97+
'facebook' => pht('Popular Social Network'),
98+
'facility' => pht('Pollution Solves Problems'),
99+
'film' => pht('Actual Physical Film'),
100+
'forked' => pht('You Can\'t Eat Soup'),
101+
'games' => pht('Serious Business'),
102+
'ghost' => pht('Haunted'),
103+
'gift' => pht('Surprise!'),
104+
'globe' => pht('Scanner Sweep'),
105+
'golf' => pht('Business Meeting'),
106+
'heart' => pht('Undergoing a Major Surgery'),
107+
'intergalactic' => pht('Jupiter'),
108+
'lock' => pht('Extremely Secret'),
109+
'mail' => pht('Oragami'),
110+
'martini' => pht('Healthy Olive Drink'),
111+
'medical' => pht('Medic!'),
112+
'mobile' => pht('Cellular Telephone'),
113+
'music' => pht("\xE2\x99\xAB"),
114+
'news' => pht('Actual Physical Newspaper'),
115+
'orgchart' => pht('It\'s Good to be King'),
116+
'peoples' => pht('Angel and Devil'),
117+
'piechart' => pht('Actual Physical Pie'),
118+
'poison' => pht('Healthy Bone Juice'),
119+
'putabirdonit' => pht('Put a Bird On It'),
120+
'radiate' => pht('Radiant Beauty'),
121+
'savings' => pht('Oink Oink'),
122+
'search' => pht('Sleuthing'),
123+
'shield' => pht('Royal Crest'),
124+
'speed' => pht('Slow and Steady'),
125+
'sprint' => pht('Fire Exit'),
126+
'star' => pht('The More You Know'),
127+
'storage' => pht('Stack of Pancakes'),
128+
'tablet' => pht('Cellular Telephone For Giants'),
129+
'travel' => pht('Pretty Clearly an Airplane'),
130+
'twitter' => pht('Bird Stencil'),
131+
'warning' => pht('No Caution Required, Everything Looks Safe'),
132+
'whale' => pht('Friendly Walrus'),
133+
);
134+
135+
foreach ($manifest as $icon => $spec) {
136+
$icon = substr($icon, strlen('projects-'));
137+
138+
$icons[] = javelin_tag(
139+
'button',
140+
array(
141+
'class' => 'grey profile-image-button',
142+
'sigil' => 'has-tooltip compose-select-icon',
143+
'style' => 'margin: 0 8px 8px 0',
144+
'meta' => array(
145+
'icon' => $icon,
146+
'tip' => idx($icon_quips, $icon, $icon),
147+
),
148+
),
149+
id(new PHUIIconView())
150+
->setSpriteIcon($icon)
151+
->setSpriteSheet(PHUIIconView::SPRITE_PROJECTS));
152+
}
153+
154+
$dialog_id = celerity_generate_unique_node_id();
155+
$color_input_id = celerity_generate_unique_node_id();;
156+
$icon_input_id = celerity_generate_unique_node_id();
157+
$preview_id = celerity_generate_unique_node_id();
158+
159+
$preview = id(new PHUIIconView())
160+
->setID($preview_id)
161+
->addClass('compose-background-'.$value_color)
162+
->setSpriteIcon($value_icon)
163+
->setSpriteSheet(PHUIIconView::SPRITE_PROJECTS);
164+
165+
$color_input = javelin_tag(
166+
'input',
167+
array(
168+
'type' => 'hidden',
169+
'name' => 'color',
170+
'value' => $value_color,
171+
'id' => $color_input_id,
172+
));
173+
174+
$icon_input = javelin_tag(
175+
'input',
176+
array(
177+
'type' => 'hidden',
178+
'name' => 'icon',
179+
'value' => $value_icon,
180+
'id' => $icon_input_id,
181+
));
182+
183+
Javelin::initBehavior('phabricator-tooltips');
184+
Javelin::initBehavior(
185+
'icon-composer',
186+
array(
187+
'dialogID' => $dialog_id,
188+
'colorInputID' => $color_input_id,
189+
'iconInputID' => $icon_input_id,
190+
'previewID' => $preview_id,
191+
'defaultColor' => $value_color,
192+
'defaultIcon' => $value_icon,
193+
));
194+
195+
$dialog = id(new AphrontDialogView())
196+
->setUser($viewer)
197+
->setFormID($dialog_id)
198+
->setClass('compose-dialog')
199+
->setTitle(pht('Compose Image'))
200+
->appendChild(
201+
phutil_tag(
202+
'div',
203+
array(
204+
'class' => 'compose-header',
205+
),
206+
pht('Choose Background Color')))
207+
->appendChild($buttons)
208+
->appendChild(
209+
phutil_tag(
210+
'div',
211+
array(
212+
'class' => 'compose-header',
213+
),
214+
pht('Choose Icon')))
215+
->appendChild($icons)
216+
->appendChild(
217+
phutil_tag(
218+
'div',
219+
array(
220+
'class' => 'compose-header',
221+
),
222+
pht('Preview')))
223+
->appendChild($preview)
224+
->appendChild($color_input)
225+
->appendChild($icon_input)
226+
->addCancelButton('/')
227+
->addSubmitButton(pht('Save Image'));
228+
229+
return id(new AphrontDialogResponse())->setDialog($dialog);
230+
}
231+
232+
private function composeImage($color, $icon_data) {
233+
$icon_img = imagecreatefromstring($icon_data);
234+
235+
$map = CelerityResourceTransformer::getCSSVariableMap();
236+
$color_string = idx($map, $color, '#ff00ff');
237+
$color_const = hexdec(trim($color_string, '#'));
238+
239+
$canvas = imagecreatetruecolor(50, 50);
240+
imagefill($canvas, 0, 0, $color_const);
241+
242+
imagecopy($canvas, $icon_img, 0, 0, 0, 0, 50, 50);
243+
244+
return PhabricatorImageTransformer::saveImageDataInAnyFormat(
245+
$canvas,
246+
'image/png');
247+
}
248+
249+
}

src/applications/project/application/PhabricatorApplicationProject.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public function getRoutes() {
4242
=> 'PhabricatorProjectMembersEditController',
4343
'view/(?P<id>[1-9]\d*)/(?:(?P<page>\w+)/)?'
4444
=> 'PhabricatorProjectProfileController',
45+
'picture/(?P<id>[1-9]\d*)/' =>
46+
'PhabricatorProjectProfilePictureController',
4547
'create/' => 'PhabricatorProjectCreateController',
4648
'update/(?P<id>[1-9]\d*)/(?P<action>[^/]+)/'
4749
=> 'PhabricatorProjectUpdateController',

src/applications/project/controller/PhabricatorProjectProfileController.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ private function buildActionListView(PhabricatorProject $project) {
216216
->setDisabled(!$can_edit)
217217
->setWorkflow(!$can_edit));
218218

219+
$view->addAction(
220+
id(new PhabricatorActionView())
221+
->setName(pht('Edit Picture'))
222+
->setIcon('image')
223+
->setHref($this->getApplicationURI("picture/{$id}/"))
224+
->setDisabled(!$can_edit)
225+
->setWorkflow(!$can_edit));
226+
219227

220228
$action = null;
221229
if (!$project->isUserMember($viewer->getPHID())) {

0 commit comments

Comments
 (0)