From a9a5991f010ddcc07dd5697bf61b46410b4f6861 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Jan 2016 12:58:19 -0800 Subject: [PATCH] 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 --- src/__phutil_library_map__.php | 6 + .../builtin/PhabricatorFilesBuiltinFile.php | 9 + ...PhabricatorFilesComposeIconBuiltinFile.php | 235 ++++++++++++++++++ .../PhabricatorFilesOnDiskBuiltinFile.php | 56 +++++ .../PhabricatorFileComposeController.php | 198 +++------------ .../files/storage/PhabricatorFile.php | 58 ++--- .../pholio/view/PholioMockImagesView.php | 8 +- ...habricatorProjectEditPictureController.php | 105 +++++--- .../PhabricatorProjectTransactionEditor.php | 30 +++ .../project/storage/PhabricatorProject.php | 17 ++ 10 files changed, 496 insertions(+), 226 deletions(-) create mode 100644 src/applications/files/builtin/PhabricatorFilesBuiltinFile.php create mode 100644 src/applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php create mode 100644 src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 37781a12a5..1a4684f464 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2314,6 +2314,8 @@ phutil_register_library_map(array( 'PhabricatorFileinfoSetupCheck' => 'applications/config/check/PhabricatorFileinfoSetupCheck.php', 'PhabricatorFilesApplication' => 'applications/files/application/PhabricatorFilesApplication.php', 'PhabricatorFilesApplicationStorageEnginePanel' => 'applications/files/applicationpanel/PhabricatorFilesApplicationStorageEnginePanel.php', + 'PhabricatorFilesBuiltinFile' => 'applications/files/builtin/PhabricatorFilesBuiltinFile.php', + 'PhabricatorFilesComposeIconBuiltinFile' => 'applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php', 'PhabricatorFilesConfigOptions' => 'applications/files/config/PhabricatorFilesConfigOptions.php', 'PhabricatorFilesManagementCatWorkflow' => 'applications/files/management/PhabricatorFilesManagementCatWorkflow.php', 'PhabricatorFilesManagementCompactWorkflow' => 'applications/files/management/PhabricatorFilesManagementCompactWorkflow.php', @@ -2322,6 +2324,7 @@ phutil_register_library_map(array( 'PhabricatorFilesManagementPurgeWorkflow' => 'applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php', 'PhabricatorFilesManagementRebuildWorkflow' => 'applications/files/management/PhabricatorFilesManagementRebuildWorkflow.php', 'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php', + 'PhabricatorFilesOnDiskBuiltinFile' => 'applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php', 'PhabricatorFilesOutboundRequestAction' => 'applications/files/action/PhabricatorFilesOutboundRequestAction.php', 'PhabricatorFlag' => 'applications/flag/storage/PhabricatorFlag.php', 'PhabricatorFlagAddFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php', @@ -6610,6 +6613,8 @@ phutil_register_library_map(array( 'PhabricatorFileinfoSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorFilesApplication' => 'PhabricatorApplication', 'PhabricatorFilesApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel', + 'PhabricatorFilesBuiltinFile' => 'Phobject', + 'PhabricatorFilesComposeIconBuiltinFile' => 'PhabricatorFilesBuiltinFile', 'PhabricatorFilesConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorFilesManagementCatWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementCompactWorkflow' => 'PhabricatorFilesManagementWorkflow', @@ -6618,6 +6623,7 @@ phutil_register_library_map(array( 'PhabricatorFilesManagementPurgeWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementRebuildWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'PhabricatorFilesOnDiskBuiltinFile' => 'PhabricatorFilesBuiltinFile', 'PhabricatorFilesOutboundRequestAction' => 'PhabricatorSystemAction', 'PhabricatorFlag' => array( 'PhabricatorFlagDAO', diff --git a/src/applications/files/builtin/PhabricatorFilesBuiltinFile.php b/src/applications/files/builtin/PhabricatorFilesBuiltinFile.php new file mode 100644 index 0000000000..f1ad3da197 --- /dev/null +++ b/src/applications/files/builtin/PhabricatorFilesBuiltinFile.php @@ -0,0 +1,9 @@ +icon = $icon; + return $this; + } + + public function getIcon() { + return $this->icon; + } + + public function setColor($color) { + $this->color = $color; + return $this; + } + + public function getColor() { + return $this->color; + } + + public function getBuiltinFileKey() { + $icon = $this->getIcon(); + $color = $this->getColor(); + $desc = "compose(icon={$icon}, color={$color})"; + $hash = PhabricatorHash::digestToLength($desc, 40); + return "builtin:{$hash}"; + } + + public function getBuiltinDisplayName() { + $icon = $this->getIcon(); + $color = $this->getColor(); + return "{$icon}-{$color}.png"; + } + + public function loadBuiltinFileData() { + return $this->composeImage($this->getColor(), $this->getIcon()); + } + + public static function getAllIcons() { + $root = dirname(phutil_get_library_root('phabricator')); + $root = $root.'/resources/sprite/projects_2x/'; + + $quips = self::getIconQuips(); + + $map = array(); + $list = Filesystem::listDirectory($root, $include_hidden = false); + foreach ($list as $file) { + $short = preg_replace('/\.png$/', '', $file); + + $map[$short] = array( + 'path' => $root.$file, + 'quip' => idx($quips, $short, $short), + ); + } + + return $map; + } + + public static function getAllColors() { + $colors = id(new CelerityResourceTransformer()) + ->getCSSVariableMap(); + + $colors = array_select_keys( + $colors, + array( + 'red', + 'orange', + 'yellow', + 'green', + 'blue', + 'sky', + 'indigo', + 'violet', + 'pink', + 'charcoal', + 'backdrop', + )); + + $quips = self::getColorQuips(); + + $map = array(); + foreach ($colors as $name => $color) { + $map[$name] = array( + 'color' => $color, + 'quip' => idx($quips, $name, $name), + ); + } + + return $map; + } + + private function composeImage($color, $icon) { + $color_map = self::getAllColors(); + $color = idx($color_map, $color); + if (!$color) { + $fallback = 'backdrop'; + $color = idx($color_map, $fallback); + if (!$color) { + throw new Exception( + pht( + 'Fallback compose color ("%s") does not exist!', + $fallback)); + } + } + + $color_hex = idx($color, 'color'); + $color_const = hexdec(trim($color_hex, '#')); + + $icon_map = self::getAllIcons(); + $icon = idx($icon_map, $icon); + if (!$icon) { + $fallback = 'fa-umbrella'; + $icon = idx($icon_map, $fallback); + if (!$icon) { + throw new Exception( + pht( + 'Fallback compose icon ("%s") does not exist!', + $fallback)); + } + } + + $path = idx($icon, 'path'); + $data = Filesystem::readFile($path); + + $icon_img = imagecreatefromstring($data); + + $canvas = imagecreatetruecolor(100, 100); + imagefill($canvas, 0, 0, $color_const); + imagecopy($canvas, $icon_img, 0, 0, 0, 0, 100, 100); + + return PhabricatorImageTransformer::saveImageDataInAnyFormat( + $canvas, + 'image/png'); + } + + private static function getIconQuips() { + return array( + '8ball' => pht('Take a Risk'), + 'alien' => pht('Foreign Interface'), + 'announce' => pht('Louder is Better'), + 'art' => pht('Unique Snowflake'), + 'award' => pht('Shooting Star'), + 'bacon' => pht('Healthy Vegetables'), + 'bandaid' => pht('Durable Infrastructure'), + 'beer' => pht('Healthy Vegetable Juice'), + 'bomb' => pht('Imminent Success'), + 'briefcase' => pht('Adventure Pack'), + 'bug' => pht('Costumed Egg'), + 'calendar' => pht('Everyone Loves Meetings'), + 'cloud' => pht('Water Cycle'), + 'coffee' => pht('Half-Whip Nonfat Soy Latte'), + 'creditcard' => pht('Expense It'), + 'death' => pht('Calcium Promotes Bone Health'), + 'desktop' => pht('Magical Portal'), + 'dropbox' => pht('Cardboard Box'), + 'education' => pht('Debt'), + 'experimental' => pht('CAUTION: Dangerous Chemicals'), + 'facebook' => pht('Popular Social Network'), + 'facility' => pht('Pollution Solves Problems'), + 'film' => pht('Actual Physical Film'), + 'forked' => pht('You Can\'t Eat Soup'), + 'games' => pht('Serious Business'), + 'ghost' => pht('Haunted'), + 'gift' => pht('Surprise!'), + 'globe' => pht('Scanner Sweep'), + 'golf' => pht('Business Meeting'), + 'heart' => pht('Undergoing a Major Surgery'), + 'intergalactic' => pht('Jupiter'), + 'lock' => pht('Extremely Secret'), + 'mail' => pht('Oragami'), + 'martini' => pht('Healthy Olive Drink'), + 'medical' => pht('Medic!'), + 'mobile' => pht('Cellular Telephone'), + 'music' => pht("\xE2\x99\xAB"), + 'news' => pht('Actual Physical Newspaper'), + 'orgchart' => pht('It\'s Good to be King'), + 'peoples' => pht('Angel and Devil'), + 'piechart' => pht('Actual Physical Pie'), + 'poison' => pht('Healthy Bone Juice'), + 'putabirdonit' => pht('Put a Bird On It'), + 'radiate' => pht('Radiant Beauty'), + 'savings' => pht('Oink Oink'), + 'search' => pht('Sleuthing'), + 'shield' => pht('Royal Crest'), + 'speed' => pht('Slow and Steady'), + 'sprint' => pht('Fire Exit'), + 'star' => pht('The More You Know'), + 'storage' => pht('Stack of Pancakes'), + 'tablet' => pht('Cellular Telephone For Giants'), + 'travel' => pht('Pretty Clearly an Airplane'), + 'twitter' => pht('Bird Stencil'), + 'warning' => pht('No Caution Required, Everything Looks Safe'), + 'whale' => pht('Friendly Walrus'), + 'fa-flask' => pht('Experimental'), + 'fa-briefcase' => pht('Briefcase'), + 'fa-bug' => pht('Bug'), + 'fa-building' => pht('Company'), + 'fa-calendar' => pht('Deadline'), + 'fa-cloud' => pht('The Cloud'), + 'fa-credit-card' => pht('Accounting'), + 'fa-envelope' => pht('Communication'), + 'fa-flag-checkered' => pht('Goal'), + 'fa-folder' => pht('Folder'), + 'fa-group' => pht('Team'), + 'fa-lock' => pht('Policy'), + 'fa-tags' => pht('Tag'), + 'fa-trash-o' => pht('Garbage'), + 'fa-truck' => pht('Release'), + 'fa-umbrella' => pht('An Umbrella'), + ); + } + + private static function getColorQuips() { + return array( + 'red' => pht('Verbillion'), + 'orange' => pht('Navel Orange'), + 'yellow' => pht('Prim Goldenrod'), + 'green' => pht('Lustrous Verdant'), + 'blue' => pht('Tropical Deep'), + 'sky' => pht('Wide Open Sky'), + 'indigo' => pht('Pleated Khaki'), + 'violet' => pht('Aged Merlot'), + 'pink' => pht('Easter Bunny'), + 'charcoal' => pht('Gemstone'), + 'backdrop' => pht('Driven Snow'), + ); + } + +} diff --git a/src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php b/src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php new file mode 100644 index 0000000000..b32ea6d39e --- /dev/null +++ b/src/applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php @@ -0,0 +1,56 @@ +name = $name; + return $this; + } + + public function getName() { + if ($this->name === null) { + throw new PhutilInvalidStateException('setName'); + } + + return $this->name; + } + + public function getBuiltinDisplayName() { + return $this->getName(); + } + + public function getBuiltinFileKey() { + $name = $this->getName(); + $desc = "disk(name={$name})"; + $hash = PhabricatorHash::digestToLength($desc, 40); + return "builtin:{$hash}"; + } + + public function loadBuiltinFileData() { + $name = $this->getName(); + + $available = $this->getAllBuiltinFiles(); + if (empty($available[$name])) { + throw new Exception(pht('Builtin "%s" does not exist!', $name)); + } + + return Filesystem::readFile($available[$name]); + } + + private function getAllBuiltinFiles() { + $root = dirname(phutil_get_library_root('phabricator')); + $root = $root.'/resources/builtin/'; + + $map = array(); + $list = Filesystem::listDirectory($root, $include_hidden = false); + foreach ($list as $file) { + $map[$file] = $root.$file; + } + + return $map; + } + +} diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index de5a6757a4..3f2667b9d1 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -6,21 +6,8 @@ final class PhabricatorFileComposeController public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $colors = array( - 'red' => pht('Verbillion'), - 'orange' => pht('Navel Orange'), - 'yellow' => pht('Prim Goldenrod'), - 'green' => pht('Lustrous Verdant'), - 'blue' => pht('Tropical Deep'), - 'sky' => pht('Wide Open Sky'), - 'indigo' => pht('Pleated Khaki'), - 'violet' => pht('Aged Merlot'), - 'pink' => pht('Easter Bunny'), - 'charcoal' => pht('Gemstone'), - 'backdrop' => pht('Driven Snow'), - ); - - $manifest = PHUIIconView::getSheetManifest(PHUIIconView::SPRITE_PROJECTS); + $color_map = PhabricatorFilesComposeIconBuiltinFile::getAllColors(); + $icon_map = $this->getIconMap(); if ($request->isFormPost()) { $project_phid = $request->getStr('projectPHID'); @@ -37,36 +24,21 @@ final class PhabricatorFileComposeController if (!$project) { return new Aphront404Response(); } - $icon = $project->getIcon(); - $color = $project->getColor(); - switch ($color) { - case 'grey': - $color = 'charcoal'; - break; - case 'checkered': - $color = 'backdrop'; - break; - } - } else { - $icon = $request->getStr('icon'); - $color = $request->getStr('color'); } - if (!isset($colors[$color]) || !isset($manifest['projects-'.$icon])) { - return new Aphront404Response(); - } + $icon = $request->getStr('icon'); + $color = $request->getStr('color'); - $root = dirname(phutil_get_library_root('phabricator')); - $icon_file = $root.'/resources/sprite/projects_2x/'.$icon.'.png'; - $icon_data = Filesystem::readFile($icon_file); + $composer = id(new PhabricatorFilesComposeIconBuiltinFile()) + ->setIcon($icon) + ->setColor($color); - - $data = $this->composeImage($color, $icon_data); + $data = $composer->loadBuiltinFileData(); $file = PhabricatorFile::buildFromFileDataOrHash( $data, array( - 'name' => 'project.png', + 'name' => $composer->getBuiltinDisplayName(), 'profile' => true, 'canCDN' => true, )); @@ -97,14 +69,15 @@ final class PhabricatorFileComposeController } } - $value_color = head_key($colors); - $value_icon = head_key($manifest); - $value_icon = substr($value_icon, strlen('projects-')); + $value_color = head_key($color_map); + $value_icon = head_key($icon_map); require_celerity_resource('people-profile-css'); $buttons = array(); - foreach ($colors as $color => $name) { + foreach ($color_map as $color => $info) { + $quip = idx($info, 'quip'); + $buttons[] = javelin_tag( 'button', array( @@ -113,116 +86,17 @@ final class PhabricatorFileComposeController 'style' => 'margin: 0 8px 8px 0', 'meta' => array( 'color' => $color, - 'tip' => $name, + 'tip' => $quip, ), ), id(new PHUIIconView()) ->addClass('compose-background-'.$color)); } - $sort_these_first = array( - 'projects-fa-briefcase', - 'projects-fa-tags', - 'projects-fa-folder', - 'projects-fa-group', - 'projects-fa-bug', - 'projects-fa-trash-o', - 'projects-fa-calendar', - 'projects-fa-flag-checkered', - 'projects-fa-envelope', - 'projects-fa-truck', - 'projects-fa-lock', - 'projects-fa-umbrella', - 'projects-fa-cloud', - 'projects-fa-building', - 'projects-fa-credit-card', - 'projects-fa-flask', - ); - - $manifest = array_select_keys( - $manifest, - $sort_these_first) - + $manifest; $icons = array(); - - $icon_quips = array( - '8ball' => pht('Take a Risk'), - 'alien' => pht('Foreign Interface'), - 'announce' => pht('Louder is Better'), - 'art' => pht('Unique Snowflake'), - 'award' => pht('Shooting Star'), - 'bacon' => pht('Healthy Vegetables'), - 'bandaid' => pht('Durable Infrastructure'), - 'beer' => pht('Healthy Vegetable Juice'), - 'bomb' => pht('Imminent Success'), - 'briefcase' => pht('Adventure Pack'), - 'bug' => pht('Costumed Egg'), - 'calendar' => pht('Everyone Loves Meetings'), - 'cloud' => pht('Water Cycle'), - 'coffee' => pht('Half-Whip Nonfat Soy Latte'), - 'creditcard' => pht('Expense It'), - 'death' => pht('Calcium Promotes Bone Health'), - 'desktop' => pht('Magical Portal'), - 'dropbox' => pht('Cardboard Box'), - 'education' => pht('Debt'), - 'experimental' => pht('CAUTION: Dangerous Chemicals'), - 'facebook' => pht('Popular Social Network'), - 'facility' => pht('Pollution Solves Problems'), - 'film' => pht('Actual Physical Film'), - 'forked' => pht('You Can\'t Eat Soup'), - 'games' => pht('Serious Business'), - 'ghost' => pht('Haunted'), - 'gift' => pht('Surprise!'), - 'globe' => pht('Scanner Sweep'), - 'golf' => pht('Business Meeting'), - 'heart' => pht('Undergoing a Major Surgery'), - 'intergalactic' => pht('Jupiter'), - 'lock' => pht('Extremely Secret'), - 'mail' => pht('Oragami'), - 'martini' => pht('Healthy Olive Drink'), - 'medical' => pht('Medic!'), - 'mobile' => pht('Cellular Telephone'), - 'music' => pht("\xE2\x99\xAB"), - 'news' => pht('Actual Physical Newspaper'), - 'orgchart' => pht('It\'s Good to be King'), - 'peoples' => pht('Angel and Devil'), - 'piechart' => pht('Actual Physical Pie'), - 'poison' => pht('Healthy Bone Juice'), - 'putabirdonit' => pht('Put a Bird On It'), - 'radiate' => pht('Radiant Beauty'), - 'savings' => pht('Oink Oink'), - 'search' => pht('Sleuthing'), - 'shield' => pht('Royal Crest'), - 'speed' => pht('Slow and Steady'), - 'sprint' => pht('Fire Exit'), - 'star' => pht('The More You Know'), - 'storage' => pht('Stack of Pancakes'), - 'tablet' => pht('Cellular Telephone For Giants'), - 'travel' => pht('Pretty Clearly an Airplane'), - 'twitter' => pht('Bird Stencil'), - 'warning' => pht('No Caution Required, Everything Looks Safe'), - 'whale' => pht('Friendly Walrus'), - 'fa-flask' => pht('Experimental'), - 'fa-briefcase' => pht('Briefcase'), - 'fa-bug' => pht('Bug'), - 'fa-building' => pht('Company'), - 'fa-calendar' => pht('Deadline'), - 'fa-cloud' => pht('The Cloud'), - 'fa-credit-card' => pht('Accounting'), - 'fa-envelope' => pht('Communication'), - 'fa-flag-checkered' => pht('Goal'), - 'fa-folder' => pht('Folder'), - 'fa-group' => pht('Team'), - 'fa-lock' => pht('Policy'), - 'fa-tags' => pht('Tag'), - 'fa-trash-o' => pht('Garbage'), - 'fa-truck' => pht('Release'), - 'fa-umbrella' => pht('An Umbrella'), - ); - - foreach ($manifest as $icon => $spec) { - $icon = substr($icon, strlen('projects-')); + foreach ($icon_map as $icon => $spec) { + $quip = idx($spec, 'quip'); $icons[] = javelin_tag( 'button', @@ -232,7 +106,7 @@ final class PhabricatorFileComposeController 'style' => 'margin: 0 8px 8px 0', 'meta' => array( 'icon' => $icon, - 'tip' => idx($icon_quips, $icon, $icon), + 'tip' => $quip, ), ), id(new PHUIIconView()) @@ -318,23 +192,31 @@ final class PhabricatorFileComposeController return id(new AphrontDialogResponse())->setDialog($dialog); } - private function composeImage($color, $icon_data) { - $icon_img = imagecreatefromstring($icon_data); + private function getIconMap() { + $icon_map = PhabricatorFilesComposeIconBuiltinFile::getAllIcons(); - $map = id(new CelerityResourceTransformer()) - ->getCSSVariableMap(); + $first = array( + 'fa-briefcase', + 'fa-tags', + 'fa-folder', + 'fa-group', + 'fa-bug', + 'fa-trash-o', + 'fa-calendar', + 'fa-flag-checkered', + 'fa-envelope', + 'fa-truck', + 'fa-lock', + 'fa-umbrella', + 'fa-cloud', + 'fa-building', + 'fa-credit-card', + 'fa-flask', + ); - $color_string = idx($map, $color, '#ff00ff'); - $color_const = hexdec(trim($color_string, '#')); + $icon_map = array_select_keys($icon_map, $first) + $icon_map; - $canvas = imagecreatetruecolor(100, 100); - imagefill($canvas, 0, 0, $color_const); - - imagecopy($canvas, $icon_img, 0, 0, 0, 0, 100, 100); - - return PhabricatorImageTransformer::saveImageDataInAnyFormat( - $canvas, - 'image/png'); + return $icon_map; } } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index f0a5f228e3..92dd0bcf88 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -961,16 +961,18 @@ final class PhabricatorFile extends PhabricatorFileDAO * Builtins are located in `resources/builtin/` and identified by their * name. * - * @param PhabricatorUser Viewing user. - * @param list List of builtin file names. - * @return dict Dictionary of named builtins. + * @param PhabricatorUser Viewing user. + * @param list List of builtin file specs. + * @return dict Dictionary of named builtins. */ - public static function loadBuiltins(PhabricatorUser $user, array $names) { + public static function loadBuiltins(PhabricatorUser $user, array $builtins) { + $builtins = mpull($builtins, null, 'getBuiltinFileKey'); + $specs = array(); - foreach ($names as $name) { + foreach ($builtins as $key => $buitin) { $specs[] = array( 'originalPHID' => PhabricatorPHIDConstants::PHID_VOID, - 'transform' => 'builtin:'.$name, + 'transform' => $key, ); } @@ -981,41 +983,34 @@ final class PhabricatorFile extends PhabricatorFileDAO ->withTransforms($specs) ->execute(); - $files = mpull($files, null, 'getName'); - - $root = dirname(phutil_get_library_root('phabricator')); - $root = $root.'/resources/builtin/'; + $results = array(); + foreach ($files as $file) { + $builtin_key = $file->getBuiltinName(); + if ($builtin_key !== null) { + $results[$builtin_key] = $file; + } + } $build = array(); - foreach ($names as $name) { - if (isset($files[$name])) { + foreach ($builtins as $key => $builtin) { + if (isset($results[$key])) { continue; } - // This is just a sanity check to prevent loading arbitrary files. - if (basename($name) != $name) { - throw new Exception(pht("Invalid builtin name '%s'!", $name)); - } + $data = $builtin->loadBuiltinFileData(); - $path = $root.$name; - - if (!Filesystem::pathExists($path)) { - throw new Exception(pht("Builtin '%s' does not exist!", $path)); - } - - $data = Filesystem::readFile($path); $params = array( - 'name' => $name, + 'name' => $builtin->getBuiltinDisplayName(), 'ttl' => time() + (60 * 60 * 24 * 7), 'canCDN' => true, - 'builtin' => $name, + 'builtin' => $key, ); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $file = self::newFromFileData($data, $params); $xform = id(new PhabricatorTransformedFile()) ->setOriginalPHID(PhabricatorPHIDConstants::PHID_VOID) - ->setTransform('builtin:'.$name) + ->setTransform($key) ->setTransformedPHID($file->getPHID()) ->save(); unset($unguarded); @@ -1023,10 +1018,10 @@ final class PhabricatorFile extends PhabricatorFileDAO $file->attachObjectPHIDs(array()); $file->attachObjects(array()); - $files[$name] = $file; + $results[$key] = $file; } - return $files; + return $results; } @@ -1038,7 +1033,12 @@ final class PhabricatorFile extends PhabricatorFileDAO * @return PhabricatorFile Corresponding builtin file. */ public static function loadBuiltin(PhabricatorUser $user, $name) { - return idx(self::loadBuiltins($user, array($name)), $name); + $builtin = id(new PhabricatorFilesOnDiskBuiltinFile()) + ->setName($name); + + $key = $builtin->getBuiltinFileKey(); + + return idx(self::loadBuiltins($user, array($builtin)), $key); } public function getObjects() { diff --git a/src/applications/pholio/view/PholioMockImagesView.php b/src/applications/pholio/view/PholioMockImagesView.php index cf8d0c6143..8430ada3d4 100644 --- a/src/applications/pholio/view/PholioMockImagesView.php +++ b/src/applications/pholio/view/PholioMockImagesView.php @@ -68,11 +68,9 @@ final class PholioMockImagesView extends AphrontView { // TODO: We could maybe do a better job with tailoring this, which is the // image shown on the review stage. - $default_name = 'image-100x100.png'; - $builtins = PhabricatorFile::loadBuiltins( - $this->getUser(), - array($default_name)); - $default = $builtins[$default_name]; + $viewer = $this->getUser(); + + $default = PhabricatorFile::loadBuiltin($viewer, 'image-100x100.png'); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($this->getUser()); diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index 8828bfae57..41ccb94eb3 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -123,7 +123,7 @@ final class PhabricatorProjectEditPictureController $images[PhabricatorPHIDConstants::PHID_VOID] = array( 'uri' => $default_image->getBestURI(), - 'tip' => pht('Default Picture'), + 'tip' => pht('No Picture'), ); require_celerity_resource('people-profile-css'); @@ -181,7 +181,11 @@ final class PhabricatorProjectEditPictureController $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Use Picture')) - ->setValue($buttons)); + ->setValue( + array( + $this->renderDefaultForm($project), + $buttons, + ))); $launch_id = celerity_generate_unique_node_id(); $input_id = celerity_generate_unique_node_id(); @@ -226,38 +230,6 @@ final class PhabricatorProjectEditPictureController ->setLabel(pht('Quick Create')) ->setValue($compose_form)); - $default_button = javelin_tag( - 'button', - array( - 'class' => 'grey', - ), - pht('Use Project Icon')); - - $default_input = javelin_tag( - 'input', - array( - 'type' => 'hidden', - 'name' => 'projectPHID', - 'value' => $project->getPHID(), - )); - - $default_form = phabricator_form( - $viewer, - array( - 'class' => 'profile-image-form', - 'method' => 'POST', - 'action' => '/file/compose/', - ), - array( - $default_input, - $default_button, - )); - - $form->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Use Default')) - ->setValue($default_form)); - $upload_form = id(new AphrontFormView()) ->setUser($viewer) ->setEncType('multipart/form-data') @@ -294,4 +266,69 @@ final class PhabricatorProjectEditPictureController $upload_box, )); } + + private function renderDefaultForm(PhabricatorProject $project) { + $viewer = $this->getViewer(); + $compose_color = $project->getDisplayIconComposeColor(); + $compose_icon = $project->getDisplayIconComposeIcon(); + + $default_builtin = id(new PhabricatorFilesComposeIconBuiltinFile()) + ->setColor($compose_color) + ->setIcon($compose_icon); + + $file_builtins = PhabricatorFile::loadBuiltins( + $viewer, + array($default_builtin)); + + $file_builtin = head($file_builtins); + + $default_button = javelin_tag( + 'button', + array( + 'class' => 'grey profile-image-button', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => pht('Use Icon and Color'), + 'size' => 300, + ), + ), + phutil_tag( + 'img', + array( + 'height' => 50, + 'width' => 50, + 'src' => $file_builtin->getBestURI(), + ))); + + $inputs = array( + 'projectPHID' => $project->getPHID(), + 'icon' => $compose_icon, + 'color' => $compose_color, + ); + + foreach ($inputs as $key => $value) { + $inputs[$key] = javelin_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => $key, + 'value' => $value, + )); + } + + $default_form = phabricator_form( + $viewer, + array( + 'class' => 'profile-image-form', + 'method' => 'POST', + 'action' => '/file/compose/', + ), + array( + $inputs, + $default_button, + )); + + return $default_form; + } + } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index c8175e6cd7..26cc3af86e 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -713,6 +713,10 @@ final class PhabricatorProjectTransactionEditor } } + if ($this->getIsNewObject()) { + $this->setDefaultProfilePicture($object); + } + // TODO: We should dump an informational transaction onto the parent // project to show that we created the sub-thing. @@ -886,5 +890,31 @@ final class PhabricatorProjectTransactionEditor return $results; } + private function setDefaultProfilePicture(PhabricatorProject $project) { + if ($project->isMilestone()) { + return; + } + + $compose_color = $project->getDisplayIconComposeColor(); + $compose_icon = $project->getDisplayIconComposeIcon(); + + $builtin = id(new PhabricatorFilesComposeIconBuiltinFile()) + ->setColor($compose_color) + ->setIcon($compose_icon); + + $data = $builtin->loadBuiltinFileData(); + + $file = PhabricatorFile::newFromFileData( + $data, + array( + 'name' => $builtin->getBuiltinDisplayName(), + 'profile' => true, + 'canCDN' => true, + )); + + $project + ->setProfileImagePHID($file->getPHID()) + ->save(); + } } diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 387882df2c..ceaac1a16d 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -519,6 +519,23 @@ final class PhabricatorProject extends PhabricatorProjectDAO return $this->getColor(); } + public function getDisplayIconComposeIcon() { + $icon = $this->getDisplayIconIcon(); + return $icon; + } + + public function getDisplayIconComposeColor() { + $color = $this->getDisplayColor(); + + $map = array( + 'grey' => 'charcoal', + 'checkered' => 'backdrop', + ); + + return idx($map, $color, $color); + } + + /* -( PhabricatorSubscribableInterface )----------------------------------- */