@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Update project profile image composer for new IconSet code

Summary:
Fixes T6856. Fixes T10164.

- Make the profile image composer code use the underlying icon name instead of the top-level icon key, so it works instead of 404'ing.
- Change the button to show a preview of the profile icon instead of the text "Use Icon and Color".
- When creating a new non-milestone project, automatically set the profile image to the icon + color image.

Test Plan:
- Created several new projects, saw appropriate default icons.
- Edited projects, saw icon previews.
- Clicked icon buttons to set icons.
- Poked around other applications which use builtins (Pholio, user profiles) to look for anything I broke, but everything seemed fine.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T6856, T10164

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

+496 -226
+6
src/__phutil_library_map__.php
··· 2314 2314 'PhabricatorFileinfoSetupCheck' => 'applications/config/check/PhabricatorFileinfoSetupCheck.php', 2315 2315 'PhabricatorFilesApplication' => 'applications/files/application/PhabricatorFilesApplication.php', 2316 2316 'PhabricatorFilesApplicationStorageEnginePanel' => 'applications/files/applicationpanel/PhabricatorFilesApplicationStorageEnginePanel.php', 2317 + 'PhabricatorFilesBuiltinFile' => 'applications/files/builtin/PhabricatorFilesBuiltinFile.php', 2318 + 'PhabricatorFilesComposeIconBuiltinFile' => 'applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php', 2317 2319 'PhabricatorFilesConfigOptions' => 'applications/files/config/PhabricatorFilesConfigOptions.php', 2318 2320 'PhabricatorFilesManagementCatWorkflow' => 'applications/files/management/PhabricatorFilesManagementCatWorkflow.php', 2319 2321 'PhabricatorFilesManagementCompactWorkflow' => 'applications/files/management/PhabricatorFilesManagementCompactWorkflow.php', ··· 2322 2324 'PhabricatorFilesManagementPurgeWorkflow' => 'applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php', 2323 2325 'PhabricatorFilesManagementRebuildWorkflow' => 'applications/files/management/PhabricatorFilesManagementRebuildWorkflow.php', 2324 2326 'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php', 2327 + 'PhabricatorFilesOnDiskBuiltinFile' => 'applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php', 2325 2328 'PhabricatorFilesOutboundRequestAction' => 'applications/files/action/PhabricatorFilesOutboundRequestAction.php', 2326 2329 'PhabricatorFlag' => 'applications/flag/storage/PhabricatorFlag.php', 2327 2330 'PhabricatorFlagAddFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php', ··· 6610 6613 'PhabricatorFileinfoSetupCheck' => 'PhabricatorSetupCheck', 6611 6614 'PhabricatorFilesApplication' => 'PhabricatorApplication', 6612 6615 'PhabricatorFilesApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel', 6616 + 'PhabricatorFilesBuiltinFile' => 'Phobject', 6617 + 'PhabricatorFilesComposeIconBuiltinFile' => 'PhabricatorFilesBuiltinFile', 6613 6618 'PhabricatorFilesConfigOptions' => 'PhabricatorApplicationConfigOptions', 6614 6619 'PhabricatorFilesManagementCatWorkflow' => 'PhabricatorFilesManagementWorkflow', 6615 6620 'PhabricatorFilesManagementCompactWorkflow' => 'PhabricatorFilesManagementWorkflow', ··· 6618 6623 'PhabricatorFilesManagementPurgeWorkflow' => 'PhabricatorFilesManagementWorkflow', 6619 6624 'PhabricatorFilesManagementRebuildWorkflow' => 'PhabricatorFilesManagementWorkflow', 6620 6625 'PhabricatorFilesManagementWorkflow' => 'PhabricatorManagementWorkflow', 6626 + 'PhabricatorFilesOnDiskBuiltinFile' => 'PhabricatorFilesBuiltinFile', 6621 6627 'PhabricatorFilesOutboundRequestAction' => 'PhabricatorSystemAction', 6622 6628 'PhabricatorFlag' => array( 6623 6629 'PhabricatorFlagDAO',
+9
src/applications/files/builtin/PhabricatorFilesBuiltinFile.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorFilesBuiltinFile extends Phobject { 4 + 5 + abstract public function getBuiltinFileKey(); 6 + abstract public function getBuiltinDisplayName(); 7 + abstract public function loadBuiltinFileData(); 8 + 9 + }
+235
src/applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php
··· 1 + <?php 2 + 3 + final class PhabricatorFilesComposeIconBuiltinFile 4 + extends PhabricatorFilesBuiltinFile { 5 + 6 + private $icon; 7 + private $color; 8 + 9 + public function setIcon($icon) { 10 + $this->icon = $icon; 11 + return $this; 12 + } 13 + 14 + public function getIcon() { 15 + return $this->icon; 16 + } 17 + 18 + public function setColor($color) { 19 + $this->color = $color; 20 + return $this; 21 + } 22 + 23 + public function getColor() { 24 + return $this->color; 25 + } 26 + 27 + public function getBuiltinFileKey() { 28 + $icon = $this->getIcon(); 29 + $color = $this->getColor(); 30 + $desc = "compose(icon={$icon}, color={$color})"; 31 + $hash = PhabricatorHash::digestToLength($desc, 40); 32 + return "builtin:{$hash}"; 33 + } 34 + 35 + public function getBuiltinDisplayName() { 36 + $icon = $this->getIcon(); 37 + $color = $this->getColor(); 38 + return "{$icon}-{$color}.png"; 39 + } 40 + 41 + public function loadBuiltinFileData() { 42 + return $this->composeImage($this->getColor(), $this->getIcon()); 43 + } 44 + 45 + public static function getAllIcons() { 46 + $root = dirname(phutil_get_library_root('phabricator')); 47 + $root = $root.'/resources/sprite/projects_2x/'; 48 + 49 + $quips = self::getIconQuips(); 50 + 51 + $map = array(); 52 + $list = Filesystem::listDirectory($root, $include_hidden = false); 53 + foreach ($list as $file) { 54 + $short = preg_replace('/\.png$/', '', $file); 55 + 56 + $map[$short] = array( 57 + 'path' => $root.$file, 58 + 'quip' => idx($quips, $short, $short), 59 + ); 60 + } 61 + 62 + return $map; 63 + } 64 + 65 + public static function getAllColors() { 66 + $colors = id(new CelerityResourceTransformer()) 67 + ->getCSSVariableMap(); 68 + 69 + $colors = array_select_keys( 70 + $colors, 71 + array( 72 + 'red', 73 + 'orange', 74 + 'yellow', 75 + 'green', 76 + 'blue', 77 + 'sky', 78 + 'indigo', 79 + 'violet', 80 + 'pink', 81 + 'charcoal', 82 + 'backdrop', 83 + )); 84 + 85 + $quips = self::getColorQuips(); 86 + 87 + $map = array(); 88 + foreach ($colors as $name => $color) { 89 + $map[$name] = array( 90 + 'color' => $color, 91 + 'quip' => idx($quips, $name, $name), 92 + ); 93 + } 94 + 95 + return $map; 96 + } 97 + 98 + private function composeImage($color, $icon) { 99 + $color_map = self::getAllColors(); 100 + $color = idx($color_map, $color); 101 + if (!$color) { 102 + $fallback = 'backdrop'; 103 + $color = idx($color_map, $fallback); 104 + if (!$color) { 105 + throw new Exception( 106 + pht( 107 + 'Fallback compose color ("%s") does not exist!', 108 + $fallback)); 109 + } 110 + } 111 + 112 + $color_hex = idx($color, 'color'); 113 + $color_const = hexdec(trim($color_hex, '#')); 114 + 115 + $icon_map = self::getAllIcons(); 116 + $icon = idx($icon_map, $icon); 117 + if (!$icon) { 118 + $fallback = 'fa-umbrella'; 119 + $icon = idx($icon_map, $fallback); 120 + if (!$icon) { 121 + throw new Exception( 122 + pht( 123 + 'Fallback compose icon ("%s") does not exist!', 124 + $fallback)); 125 + } 126 + } 127 + 128 + $path = idx($icon, 'path'); 129 + $data = Filesystem::readFile($path); 130 + 131 + $icon_img = imagecreatefromstring($data); 132 + 133 + $canvas = imagecreatetruecolor(100, 100); 134 + imagefill($canvas, 0, 0, $color_const); 135 + imagecopy($canvas, $icon_img, 0, 0, 0, 0, 100, 100); 136 + 137 + return PhabricatorImageTransformer::saveImageDataInAnyFormat( 138 + $canvas, 139 + 'image/png'); 140 + } 141 + 142 + private static function getIconQuips() { 143 + return array( 144 + '8ball' => pht('Take a Risk'), 145 + 'alien' => pht('Foreign Interface'), 146 + 'announce' => pht('Louder is Better'), 147 + 'art' => pht('Unique Snowflake'), 148 + 'award' => pht('Shooting Star'), 149 + 'bacon' => pht('Healthy Vegetables'), 150 + 'bandaid' => pht('Durable Infrastructure'), 151 + 'beer' => pht('Healthy Vegetable Juice'), 152 + 'bomb' => pht('Imminent Success'), 153 + 'briefcase' => pht('Adventure Pack'), 154 + 'bug' => pht('Costumed Egg'), 155 + 'calendar' => pht('Everyone Loves Meetings'), 156 + 'cloud' => pht('Water Cycle'), 157 + 'coffee' => pht('Half-Whip Nonfat Soy Latte'), 158 + 'creditcard' => pht('Expense It'), 159 + 'death' => pht('Calcium Promotes Bone Health'), 160 + 'desktop' => pht('Magical Portal'), 161 + 'dropbox' => pht('Cardboard Box'), 162 + 'education' => pht('Debt'), 163 + 'experimental' => pht('CAUTION: Dangerous Chemicals'), 164 + 'facebook' => pht('Popular Social Network'), 165 + 'facility' => pht('Pollution Solves Problems'), 166 + 'film' => pht('Actual Physical Film'), 167 + 'forked' => pht('You Can\'t Eat Soup'), 168 + 'games' => pht('Serious Business'), 169 + 'ghost' => pht('Haunted'), 170 + 'gift' => pht('Surprise!'), 171 + 'globe' => pht('Scanner Sweep'), 172 + 'golf' => pht('Business Meeting'), 173 + 'heart' => pht('Undergoing a Major Surgery'), 174 + 'intergalactic' => pht('Jupiter'), 175 + 'lock' => pht('Extremely Secret'), 176 + 'mail' => pht('Oragami'), 177 + 'martini' => pht('Healthy Olive Drink'), 178 + 'medical' => pht('Medic!'), 179 + 'mobile' => pht('Cellular Telephone'), 180 + 'music' => pht("\xE2\x99\xAB"), 181 + 'news' => pht('Actual Physical Newspaper'), 182 + 'orgchart' => pht('It\'s Good to be King'), 183 + 'peoples' => pht('Angel and Devil'), 184 + 'piechart' => pht('Actual Physical Pie'), 185 + 'poison' => pht('Healthy Bone Juice'), 186 + 'putabirdonit' => pht('Put a Bird On It'), 187 + 'radiate' => pht('Radiant Beauty'), 188 + 'savings' => pht('Oink Oink'), 189 + 'search' => pht('Sleuthing'), 190 + 'shield' => pht('Royal Crest'), 191 + 'speed' => pht('Slow and Steady'), 192 + 'sprint' => pht('Fire Exit'), 193 + 'star' => pht('The More You Know'), 194 + 'storage' => pht('Stack of Pancakes'), 195 + 'tablet' => pht('Cellular Telephone For Giants'), 196 + 'travel' => pht('Pretty Clearly an Airplane'), 197 + 'twitter' => pht('Bird Stencil'), 198 + 'warning' => pht('No Caution Required, Everything Looks Safe'), 199 + 'whale' => pht('Friendly Walrus'), 200 + 'fa-flask' => pht('Experimental'), 201 + 'fa-briefcase' => pht('Briefcase'), 202 + 'fa-bug' => pht('Bug'), 203 + 'fa-building' => pht('Company'), 204 + 'fa-calendar' => pht('Deadline'), 205 + 'fa-cloud' => pht('The Cloud'), 206 + 'fa-credit-card' => pht('Accounting'), 207 + 'fa-envelope' => pht('Communication'), 208 + 'fa-flag-checkered' => pht('Goal'), 209 + 'fa-folder' => pht('Folder'), 210 + 'fa-group' => pht('Team'), 211 + 'fa-lock' => pht('Policy'), 212 + 'fa-tags' => pht('Tag'), 213 + 'fa-trash-o' => pht('Garbage'), 214 + 'fa-truck' => pht('Release'), 215 + 'fa-umbrella' => pht('An Umbrella'), 216 + ); 217 + } 218 + 219 + private static function getColorQuips() { 220 + return array( 221 + 'red' => pht('Verbillion'), 222 + 'orange' => pht('Navel Orange'), 223 + 'yellow' => pht('Prim Goldenrod'), 224 + 'green' => pht('Lustrous Verdant'), 225 + 'blue' => pht('Tropical Deep'), 226 + 'sky' => pht('Wide Open Sky'), 227 + 'indigo' => pht('Pleated Khaki'), 228 + 'violet' => pht('Aged Merlot'), 229 + 'pink' => pht('Easter Bunny'), 230 + 'charcoal' => pht('Gemstone'), 231 + 'backdrop' => pht('Driven Snow'), 232 + ); 233 + } 234 + 235 + }
+56
src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php
··· 1 + <?php 2 + 3 + final class PhabricatorFilesOnDiskBuiltinFile 4 + extends PhabricatorFilesBuiltinFile { 5 + 6 + private $name; 7 + 8 + public function setName($name) { 9 + $this->name = $name; 10 + return $this; 11 + } 12 + 13 + public function getName() { 14 + if ($this->name === null) { 15 + throw new PhutilInvalidStateException('setName'); 16 + } 17 + 18 + return $this->name; 19 + } 20 + 21 + public function getBuiltinDisplayName() { 22 + return $this->getName(); 23 + } 24 + 25 + public function getBuiltinFileKey() { 26 + $name = $this->getName(); 27 + $desc = "disk(name={$name})"; 28 + $hash = PhabricatorHash::digestToLength($desc, 40); 29 + return "builtin:{$hash}"; 30 + } 31 + 32 + public function loadBuiltinFileData() { 33 + $name = $this->getName(); 34 + 35 + $available = $this->getAllBuiltinFiles(); 36 + if (empty($available[$name])) { 37 + throw new Exception(pht('Builtin "%s" does not exist!', $name)); 38 + } 39 + 40 + return Filesystem::readFile($available[$name]); 41 + } 42 + 43 + private function getAllBuiltinFiles() { 44 + $root = dirname(phutil_get_library_root('phabricator')); 45 + $root = $root.'/resources/builtin/'; 46 + 47 + $map = array(); 48 + $list = Filesystem::listDirectory($root, $include_hidden = false); 49 + foreach ($list as $file) { 50 + $map[$file] = $root.$file; 51 + } 52 + 53 + return $map; 54 + } 55 + 56 + }
+40 -158
src/applications/files/controller/PhabricatorFileComposeController.php
··· 6 6 public function handleRequest(AphrontRequest $request) { 7 7 $viewer = $request->getViewer(); 8 8 9 - $colors = array( 10 - 'red' => pht('Verbillion'), 11 - 'orange' => pht('Navel Orange'), 12 - 'yellow' => pht('Prim Goldenrod'), 13 - 'green' => pht('Lustrous Verdant'), 14 - 'blue' => pht('Tropical Deep'), 15 - 'sky' => pht('Wide Open Sky'), 16 - 'indigo' => pht('Pleated Khaki'), 17 - 'violet' => pht('Aged Merlot'), 18 - 'pink' => pht('Easter Bunny'), 19 - 'charcoal' => pht('Gemstone'), 20 - 'backdrop' => pht('Driven Snow'), 21 - ); 22 - 23 - $manifest = PHUIIconView::getSheetManifest(PHUIIconView::SPRITE_PROJECTS); 9 + $color_map = PhabricatorFilesComposeIconBuiltinFile::getAllColors(); 10 + $icon_map = $this->getIconMap(); 24 11 25 12 if ($request->isFormPost()) { 26 13 $project_phid = $request->getStr('projectPHID'); ··· 37 24 if (!$project) { 38 25 return new Aphront404Response(); 39 26 } 40 - $icon = $project->getIcon(); 41 - $color = $project->getColor(); 42 - switch ($color) { 43 - case 'grey': 44 - $color = 'charcoal'; 45 - break; 46 - case 'checkered': 47 - $color = 'backdrop'; 48 - break; 49 - } 50 - } else { 51 - $icon = $request->getStr('icon'); 52 - $color = $request->getStr('color'); 53 27 } 54 28 55 - if (!isset($colors[$color]) || !isset($manifest['projects-'.$icon])) { 56 - return new Aphront404Response(); 57 - } 29 + $icon = $request->getStr('icon'); 30 + $color = $request->getStr('color'); 58 31 59 - $root = dirname(phutil_get_library_root('phabricator')); 60 - $icon_file = $root.'/resources/sprite/projects_2x/'.$icon.'.png'; 61 - $icon_data = Filesystem::readFile($icon_file); 62 - 32 + $composer = id(new PhabricatorFilesComposeIconBuiltinFile()) 33 + ->setIcon($icon) 34 + ->setColor($color); 63 35 64 - $data = $this->composeImage($color, $icon_data); 36 + $data = $composer->loadBuiltinFileData(); 65 37 66 38 $file = PhabricatorFile::buildFromFileDataOrHash( 67 39 $data, 68 40 array( 69 - 'name' => 'project.png', 41 + 'name' => $composer->getBuiltinDisplayName(), 70 42 'profile' => true, 71 43 'canCDN' => true, 72 44 )); ··· 97 69 } 98 70 } 99 71 100 - $value_color = head_key($colors); 101 - $value_icon = head_key($manifest); 102 - $value_icon = substr($value_icon, strlen('projects-')); 72 + $value_color = head_key($color_map); 73 + $value_icon = head_key($icon_map); 103 74 104 75 require_celerity_resource('people-profile-css'); 105 76 106 77 $buttons = array(); 107 - foreach ($colors as $color => $name) { 78 + foreach ($color_map as $color => $info) { 79 + $quip = idx($info, 'quip'); 80 + 108 81 $buttons[] = javelin_tag( 109 82 'button', 110 83 array( ··· 113 86 'style' => 'margin: 0 8px 8px 0', 114 87 'meta' => array( 115 88 'color' => $color, 116 - 'tip' => $name, 89 + 'tip' => $quip, 117 90 ), 118 91 ), 119 92 id(new PHUIIconView()) 120 93 ->addClass('compose-background-'.$color)); 121 94 } 122 95 123 - $sort_these_first = array( 124 - 'projects-fa-briefcase', 125 - 'projects-fa-tags', 126 - 'projects-fa-folder', 127 - 'projects-fa-group', 128 - 'projects-fa-bug', 129 - 'projects-fa-trash-o', 130 - 'projects-fa-calendar', 131 - 'projects-fa-flag-checkered', 132 - 'projects-fa-envelope', 133 - 'projects-fa-truck', 134 - 'projects-fa-lock', 135 - 'projects-fa-umbrella', 136 - 'projects-fa-cloud', 137 - 'projects-fa-building', 138 - 'projects-fa-credit-card', 139 - 'projects-fa-flask', 140 - ); 141 - 142 - $manifest = array_select_keys( 143 - $manifest, 144 - $sort_these_first) 145 - + $manifest; 146 96 147 97 $icons = array(); 148 - 149 - $icon_quips = array( 150 - '8ball' => pht('Take a Risk'), 151 - 'alien' => pht('Foreign Interface'), 152 - 'announce' => pht('Louder is Better'), 153 - 'art' => pht('Unique Snowflake'), 154 - 'award' => pht('Shooting Star'), 155 - 'bacon' => pht('Healthy Vegetables'), 156 - 'bandaid' => pht('Durable Infrastructure'), 157 - 'beer' => pht('Healthy Vegetable Juice'), 158 - 'bomb' => pht('Imminent Success'), 159 - 'briefcase' => pht('Adventure Pack'), 160 - 'bug' => pht('Costumed Egg'), 161 - 'calendar' => pht('Everyone Loves Meetings'), 162 - 'cloud' => pht('Water Cycle'), 163 - 'coffee' => pht('Half-Whip Nonfat Soy Latte'), 164 - 'creditcard' => pht('Expense It'), 165 - 'death' => pht('Calcium Promotes Bone Health'), 166 - 'desktop' => pht('Magical Portal'), 167 - 'dropbox' => pht('Cardboard Box'), 168 - 'education' => pht('Debt'), 169 - 'experimental' => pht('CAUTION: Dangerous Chemicals'), 170 - 'facebook' => pht('Popular Social Network'), 171 - 'facility' => pht('Pollution Solves Problems'), 172 - 'film' => pht('Actual Physical Film'), 173 - 'forked' => pht('You Can\'t Eat Soup'), 174 - 'games' => pht('Serious Business'), 175 - 'ghost' => pht('Haunted'), 176 - 'gift' => pht('Surprise!'), 177 - 'globe' => pht('Scanner Sweep'), 178 - 'golf' => pht('Business Meeting'), 179 - 'heart' => pht('Undergoing a Major Surgery'), 180 - 'intergalactic' => pht('Jupiter'), 181 - 'lock' => pht('Extremely Secret'), 182 - 'mail' => pht('Oragami'), 183 - 'martini' => pht('Healthy Olive Drink'), 184 - 'medical' => pht('Medic!'), 185 - 'mobile' => pht('Cellular Telephone'), 186 - 'music' => pht("\xE2\x99\xAB"), 187 - 'news' => pht('Actual Physical Newspaper'), 188 - 'orgchart' => pht('It\'s Good to be King'), 189 - 'peoples' => pht('Angel and Devil'), 190 - 'piechart' => pht('Actual Physical Pie'), 191 - 'poison' => pht('Healthy Bone Juice'), 192 - 'putabirdonit' => pht('Put a Bird On It'), 193 - 'radiate' => pht('Radiant Beauty'), 194 - 'savings' => pht('Oink Oink'), 195 - 'search' => pht('Sleuthing'), 196 - 'shield' => pht('Royal Crest'), 197 - 'speed' => pht('Slow and Steady'), 198 - 'sprint' => pht('Fire Exit'), 199 - 'star' => pht('The More You Know'), 200 - 'storage' => pht('Stack of Pancakes'), 201 - 'tablet' => pht('Cellular Telephone For Giants'), 202 - 'travel' => pht('Pretty Clearly an Airplane'), 203 - 'twitter' => pht('Bird Stencil'), 204 - 'warning' => pht('No Caution Required, Everything Looks Safe'), 205 - 'whale' => pht('Friendly Walrus'), 206 - 'fa-flask' => pht('Experimental'), 207 - 'fa-briefcase' => pht('Briefcase'), 208 - 'fa-bug' => pht('Bug'), 209 - 'fa-building' => pht('Company'), 210 - 'fa-calendar' => pht('Deadline'), 211 - 'fa-cloud' => pht('The Cloud'), 212 - 'fa-credit-card' => pht('Accounting'), 213 - 'fa-envelope' => pht('Communication'), 214 - 'fa-flag-checkered' => pht('Goal'), 215 - 'fa-folder' => pht('Folder'), 216 - 'fa-group' => pht('Team'), 217 - 'fa-lock' => pht('Policy'), 218 - 'fa-tags' => pht('Tag'), 219 - 'fa-trash-o' => pht('Garbage'), 220 - 'fa-truck' => pht('Release'), 221 - 'fa-umbrella' => pht('An Umbrella'), 222 - ); 223 - 224 - foreach ($manifest as $icon => $spec) { 225 - $icon = substr($icon, strlen('projects-')); 98 + foreach ($icon_map as $icon => $spec) { 99 + $quip = idx($spec, 'quip'); 226 100 227 101 $icons[] = javelin_tag( 228 102 'button', ··· 232 106 'style' => 'margin: 0 8px 8px 0', 233 107 'meta' => array( 234 108 'icon' => $icon, 235 - 'tip' => idx($icon_quips, $icon, $icon), 109 + 'tip' => $quip, 236 110 ), 237 111 ), 238 112 id(new PHUIIconView()) ··· 318 192 return id(new AphrontDialogResponse())->setDialog($dialog); 319 193 } 320 194 321 - private function composeImage($color, $icon_data) { 322 - $icon_img = imagecreatefromstring($icon_data); 323 - 324 - $map = id(new CelerityResourceTransformer()) 325 - ->getCSSVariableMap(); 326 - 327 - $color_string = idx($map, $color, '#ff00ff'); 328 - $color_const = hexdec(trim($color_string, '#')); 195 + private function getIconMap() { 196 + $icon_map = PhabricatorFilesComposeIconBuiltinFile::getAllIcons(); 329 197 330 - $canvas = imagecreatetruecolor(100, 100); 331 - imagefill($canvas, 0, 0, $color_const); 198 + $first = array( 199 + 'fa-briefcase', 200 + 'fa-tags', 201 + 'fa-folder', 202 + 'fa-group', 203 + 'fa-bug', 204 + 'fa-trash-o', 205 + 'fa-calendar', 206 + 'fa-flag-checkered', 207 + 'fa-envelope', 208 + 'fa-truck', 209 + 'fa-lock', 210 + 'fa-umbrella', 211 + 'fa-cloud', 212 + 'fa-building', 213 + 'fa-credit-card', 214 + 'fa-flask', 215 + ); 332 216 333 - imagecopy($canvas, $icon_img, 0, 0, 0, 0, 100, 100); 217 + $icon_map = array_select_keys($icon_map, $first) + $icon_map; 334 218 335 - return PhabricatorImageTransformer::saveImageDataInAnyFormat( 336 - $canvas, 337 - 'image/png'); 219 + return $icon_map; 338 220 } 339 221 340 222 }
+29 -29
src/applications/files/storage/PhabricatorFile.php
··· 961 961 * Builtins are located in `resources/builtin/` and identified by their 962 962 * name. 963 963 * 964 - * @param PhabricatorUser Viewing user. 965 - * @param list<string> List of builtin file names. 966 - * @return dict<string, PhabricatorFile> Dictionary of named builtins. 964 + * @param PhabricatorUser Viewing user. 965 + * @param list<PhabricatorFilesBuiltinFile> List of builtin file specs. 966 + * @return dict<string, PhabricatorFile> Dictionary of named builtins. 967 967 */ 968 - public static function loadBuiltins(PhabricatorUser $user, array $names) { 968 + public static function loadBuiltins(PhabricatorUser $user, array $builtins) { 969 + $builtins = mpull($builtins, null, 'getBuiltinFileKey'); 970 + 969 971 $specs = array(); 970 - foreach ($names as $name) { 972 + foreach ($builtins as $key => $buitin) { 971 973 $specs[] = array( 972 974 'originalPHID' => PhabricatorPHIDConstants::PHID_VOID, 973 - 'transform' => 'builtin:'.$name, 975 + 'transform' => $key, 974 976 ); 975 977 } 976 978 ··· 981 983 ->withTransforms($specs) 982 984 ->execute(); 983 985 984 - $files = mpull($files, null, 'getName'); 985 - 986 - $root = dirname(phutil_get_library_root('phabricator')); 987 - $root = $root.'/resources/builtin/'; 986 + $results = array(); 987 + foreach ($files as $file) { 988 + $builtin_key = $file->getBuiltinName(); 989 + if ($builtin_key !== null) { 990 + $results[$builtin_key] = $file; 991 + } 992 + } 988 993 989 994 $build = array(); 990 - foreach ($names as $name) { 991 - if (isset($files[$name])) { 995 + foreach ($builtins as $key => $builtin) { 996 + if (isset($results[$key])) { 992 997 continue; 993 998 } 994 999 995 - // This is just a sanity check to prevent loading arbitrary files. 996 - if (basename($name) != $name) { 997 - throw new Exception(pht("Invalid builtin name '%s'!", $name)); 998 - } 999 - 1000 - $path = $root.$name; 1000 + $data = $builtin->loadBuiltinFileData(); 1001 1001 1002 - if (!Filesystem::pathExists($path)) { 1003 - throw new Exception(pht("Builtin '%s' does not exist!", $path)); 1004 - } 1005 - 1006 - $data = Filesystem::readFile($path); 1007 1002 $params = array( 1008 - 'name' => $name, 1003 + 'name' => $builtin->getBuiltinDisplayName(), 1009 1004 'ttl' => time() + (60 * 60 * 24 * 7), 1010 1005 'canCDN' => true, 1011 - 'builtin' => $name, 1006 + 'builtin' => $key, 1012 1007 ); 1013 1008 1014 1009 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 1015 1010 $file = self::newFromFileData($data, $params); 1016 1011 $xform = id(new PhabricatorTransformedFile()) 1017 1012 ->setOriginalPHID(PhabricatorPHIDConstants::PHID_VOID) 1018 - ->setTransform('builtin:'.$name) 1013 + ->setTransform($key) 1019 1014 ->setTransformedPHID($file->getPHID()) 1020 1015 ->save(); 1021 1016 unset($unguarded); ··· 1023 1018 $file->attachObjectPHIDs(array()); 1024 1019 $file->attachObjects(array()); 1025 1020 1026 - $files[$name] = $file; 1021 + $results[$key] = $file; 1027 1022 } 1028 1023 1029 - return $files; 1024 + return $results; 1030 1025 } 1031 1026 1032 1027 ··· 1038 1033 * @return PhabricatorFile Corresponding builtin file. 1039 1034 */ 1040 1035 public static function loadBuiltin(PhabricatorUser $user, $name) { 1041 - return idx(self::loadBuiltins($user, array($name)), $name); 1036 + $builtin = id(new PhabricatorFilesOnDiskBuiltinFile()) 1037 + ->setName($name); 1038 + 1039 + $key = $builtin->getBuiltinFileKey(); 1040 + 1041 + return idx(self::loadBuiltins($user, array($builtin)), $key); 1042 1042 } 1043 1043 1044 1044 public function getObjects() {
+3 -5
src/applications/pholio/view/PholioMockImagesView.php
··· 68 68 69 69 // TODO: We could maybe do a better job with tailoring this, which is the 70 70 // image shown on the review stage. 71 - $default_name = 'image-100x100.png'; 72 - $builtins = PhabricatorFile::loadBuiltins( 73 - $this->getUser(), 74 - array($default_name)); 75 - $default = $builtins[$default_name]; 71 + $viewer = $this->getUser(); 72 + 73 + $default = PhabricatorFile::loadBuiltin($viewer, 'image-100x100.png'); 76 74 77 75 $engine = id(new PhabricatorMarkupEngine()) 78 76 ->setViewer($this->getUser());
+71 -34
src/applications/project/controller/PhabricatorProjectEditPictureController.php
··· 123 123 124 124 $images[PhabricatorPHIDConstants::PHID_VOID] = array( 125 125 'uri' => $default_image->getBestURI(), 126 - 'tip' => pht('Default Picture'), 126 + 'tip' => pht('No Picture'), 127 127 ); 128 128 129 129 require_celerity_resource('people-profile-css'); ··· 181 181 $form->appendChild( 182 182 id(new AphrontFormMarkupControl()) 183 183 ->setLabel(pht('Use Picture')) 184 - ->setValue($buttons)); 184 + ->setValue( 185 + array( 186 + $this->renderDefaultForm($project), 187 + $buttons, 188 + ))); 185 189 186 190 $launch_id = celerity_generate_unique_node_id(); 187 191 $input_id = celerity_generate_unique_node_id(); ··· 226 230 ->setLabel(pht('Quick Create')) 227 231 ->setValue($compose_form)); 228 232 229 - $default_button = javelin_tag( 230 - 'button', 231 - array( 232 - 'class' => 'grey', 233 - ), 234 - pht('Use Project Icon')); 235 - 236 - $default_input = javelin_tag( 237 - 'input', 238 - array( 239 - 'type' => 'hidden', 240 - 'name' => 'projectPHID', 241 - 'value' => $project->getPHID(), 242 - )); 243 - 244 - $default_form = phabricator_form( 245 - $viewer, 246 - array( 247 - 'class' => 'profile-image-form', 248 - 'method' => 'POST', 249 - 'action' => '/file/compose/', 250 - ), 251 - array( 252 - $default_input, 253 - $default_button, 254 - )); 255 - 256 - $form->appendChild( 257 - id(new AphrontFormMarkupControl()) 258 - ->setLabel(pht('Use Default')) 259 - ->setValue($default_form)); 260 - 261 233 $upload_form = id(new AphrontFormView()) 262 234 ->setUser($viewer) 263 235 ->setEncType('multipart/form-data') ··· 294 266 $upload_box, 295 267 )); 296 268 } 269 + 270 + private function renderDefaultForm(PhabricatorProject $project) { 271 + $viewer = $this->getViewer(); 272 + $compose_color = $project->getDisplayIconComposeColor(); 273 + $compose_icon = $project->getDisplayIconComposeIcon(); 274 + 275 + $default_builtin = id(new PhabricatorFilesComposeIconBuiltinFile()) 276 + ->setColor($compose_color) 277 + ->setIcon($compose_icon); 278 + 279 + $file_builtins = PhabricatorFile::loadBuiltins( 280 + $viewer, 281 + array($default_builtin)); 282 + 283 + $file_builtin = head($file_builtins); 284 + 285 + $default_button = javelin_tag( 286 + 'button', 287 + array( 288 + 'class' => 'grey profile-image-button', 289 + 'sigil' => 'has-tooltip', 290 + 'meta' => array( 291 + 'tip' => pht('Use Icon and Color'), 292 + 'size' => 300, 293 + ), 294 + ), 295 + phutil_tag( 296 + 'img', 297 + array( 298 + 'height' => 50, 299 + 'width' => 50, 300 + 'src' => $file_builtin->getBestURI(), 301 + ))); 302 + 303 + $inputs = array( 304 + 'projectPHID' => $project->getPHID(), 305 + 'icon' => $compose_icon, 306 + 'color' => $compose_color, 307 + ); 308 + 309 + foreach ($inputs as $key => $value) { 310 + $inputs[$key] = javelin_tag( 311 + 'input', 312 + array( 313 + 'type' => 'hidden', 314 + 'name' => $key, 315 + 'value' => $value, 316 + )); 317 + } 318 + 319 + $default_form = phabricator_form( 320 + $viewer, 321 + array( 322 + 'class' => 'profile-image-form', 323 + 'method' => 'POST', 324 + 'action' => '/file/compose/', 325 + ), 326 + array( 327 + $inputs, 328 + $default_button, 329 + )); 330 + 331 + return $default_form; 332 + } 333 + 297 334 }
+30
src/applications/project/editor/PhabricatorProjectTransactionEditor.php
··· 713 713 } 714 714 } 715 715 716 + if ($this->getIsNewObject()) { 717 + $this->setDefaultProfilePicture($object); 718 + } 719 + 716 720 // TODO: We should dump an informational transaction onto the parent 717 721 // project to show that we created the sub-thing. 718 722 ··· 886 890 return $results; 887 891 } 888 892 893 + private function setDefaultProfilePicture(PhabricatorProject $project) { 894 + if ($project->isMilestone()) { 895 + return; 896 + } 897 + 898 + $compose_color = $project->getDisplayIconComposeColor(); 899 + $compose_icon = $project->getDisplayIconComposeIcon(); 900 + 901 + $builtin = id(new PhabricatorFilesComposeIconBuiltinFile()) 902 + ->setColor($compose_color) 903 + ->setIcon($compose_icon); 904 + 905 + $data = $builtin->loadBuiltinFileData(); 906 + 907 + $file = PhabricatorFile::newFromFileData( 908 + $data, 909 + array( 910 + 'name' => $builtin->getBuiltinDisplayName(), 911 + 'profile' => true, 912 + 'canCDN' => true, 913 + )); 914 + 915 + $project 916 + ->setProfileImagePHID($file->getPHID()) 917 + ->save(); 918 + } 889 919 890 920 }
+17
src/applications/project/storage/PhabricatorProject.php
··· 519 519 return $this->getColor(); 520 520 } 521 521 522 + public function getDisplayIconComposeIcon() { 523 + $icon = $this->getDisplayIconIcon(); 524 + return $icon; 525 + } 526 + 527 + public function getDisplayIconComposeColor() { 528 + $color = $this->getDisplayColor(); 529 + 530 + $map = array( 531 + 'grey' => 'charcoal', 532 + 'checkered' => 'backdrop', 533 + ); 534 + 535 + return idx($map, $color, $color); 536 + } 537 + 538 + 522 539 523 540 /* -( PhabricatorSubscribableInterface )----------------------------------- */ 524 541