diff --git a/resources/sprite/conpher_1x/calendar_off.png b/resources/sprite/conpher_1x/calendar_off.png new file mode 100644 index 0000000000..d4bbae5dfe Binary files /dev/null and b/resources/sprite/conpher_1x/calendar_off.png differ diff --git a/resources/sprite/conpher_1x/calendar_on.png b/resources/sprite/conpher_1x/calendar_on.png new file mode 100644 index 0000000000..9759fc0546 Binary files /dev/null and b/resources/sprite/conpher_1x/calendar_on.png differ diff --git a/resources/sprite/conpher_1x/files_off.png b/resources/sprite/conpher_1x/files_off.png new file mode 100644 index 0000000000..0eb7e3996d Binary files /dev/null and b/resources/sprite/conpher_1x/files_off.png differ diff --git a/resources/sprite/conpher_1x/files_on.png b/resources/sprite/conpher_1x/files_on.png new file mode 100644 index 0000000000..5699ec5f00 Binary files /dev/null and b/resources/sprite/conpher_1x/files_on.png differ diff --git a/resources/sprite/conpher_1x/list_off.png b/resources/sprite/conpher_1x/list_off.png new file mode 100644 index 0000000000..5f64e19b74 Binary files /dev/null and b/resources/sprite/conpher_1x/list_off.png differ diff --git a/resources/sprite/conpher_1x/list_on.png b/resources/sprite/conpher_1x/list_on.png new file mode 100644 index 0000000000..5f104f217d Binary files /dev/null and b/resources/sprite/conpher_1x/list_on.png differ diff --git a/resources/sprite/conpher_1x/more_off.png b/resources/sprite/conpher_1x/more_off.png new file mode 100644 index 0000000000..bd65726e00 Binary files /dev/null and b/resources/sprite/conpher_1x/more_off.png differ diff --git a/resources/sprite/conpher_1x/more_on.png b/resources/sprite/conpher_1x/more_on.png new file mode 100644 index 0000000000..d6c1c6b12a Binary files /dev/null and b/resources/sprite/conpher_1x/more_on.png differ diff --git a/resources/sprite/conpher_1x/people_off.png b/resources/sprite/conpher_1x/people_off.png new file mode 100644 index 0000000000..372f5ecd05 Binary files /dev/null and b/resources/sprite/conpher_1x/people_off.png differ diff --git a/resources/sprite/conpher_1x/people_on.png b/resources/sprite/conpher_1x/people_on.png new file mode 100644 index 0000000000..87fd885951 Binary files /dev/null and b/resources/sprite/conpher_1x/people_on.png differ diff --git a/resources/sprite/conpher_1x/settings_off.png b/resources/sprite/conpher_1x/settings_off.png new file mode 100644 index 0000000000..d75b0f1fb7 Binary files /dev/null and b/resources/sprite/conpher_1x/settings_off.png differ diff --git a/resources/sprite/conpher_1x/settings_on.png b/resources/sprite/conpher_1x/settings_on.png new file mode 100644 index 0000000000..f90ca7820d Binary files /dev/null and b/resources/sprite/conpher_1x/settings_on.png differ diff --git a/resources/sprite/conpher_2x/calendar_off.png b/resources/sprite/conpher_2x/calendar_off.png new file mode 100644 index 0000000000..b807c6457e Binary files /dev/null and b/resources/sprite/conpher_2x/calendar_off.png differ diff --git a/resources/sprite/conpher_2x/calendar_on.png b/resources/sprite/conpher_2x/calendar_on.png new file mode 100644 index 0000000000..b60e94dc02 Binary files /dev/null and b/resources/sprite/conpher_2x/calendar_on.png differ diff --git a/resources/sprite/conpher_2x/conversation_off.png b/resources/sprite/conpher_2x/conversation_off.png new file mode 100644 index 0000000000..5c50e5ccfd Binary files /dev/null and b/resources/sprite/conpher_2x/conversation_off.png differ diff --git a/resources/sprite/conpher_2x/conversation_on.png b/resources/sprite/conpher_2x/conversation_on.png new file mode 100644 index 0000000000..8d1bf1435d Binary files /dev/null and b/resources/sprite/conpher_2x/conversation_on.png differ diff --git a/resources/sprite/conpher_2x/files_off.png b/resources/sprite/conpher_2x/files_off.png new file mode 100644 index 0000000000..b593b1437f Binary files /dev/null and b/resources/sprite/conpher_2x/files_off.png differ diff --git a/resources/sprite/conpher_2x/files_on.png b/resources/sprite/conpher_2x/files_on.png new file mode 100644 index 0000000000..a31547d8d9 Binary files /dev/null and b/resources/sprite/conpher_2x/files_on.png differ diff --git a/resources/sprite/conpher_2x/list_off.png b/resources/sprite/conpher_2x/list_off.png new file mode 100644 index 0000000000..a9add168df Binary files /dev/null and b/resources/sprite/conpher_2x/list_off.png differ diff --git a/resources/sprite/conpher_2x/list_on.png b/resources/sprite/conpher_2x/list_on.png new file mode 100644 index 0000000000..0214b6b0ca Binary files /dev/null and b/resources/sprite/conpher_2x/list_on.png differ diff --git a/resources/sprite/conpher_2x/more_off.png b/resources/sprite/conpher_2x/more_off.png new file mode 100644 index 0000000000..ce90ebc722 Binary files /dev/null and b/resources/sprite/conpher_2x/more_off.png differ diff --git a/resources/sprite/conpher_2x/more_on.png b/resources/sprite/conpher_2x/more_on.png new file mode 100644 index 0000000000..9b2b0f4321 Binary files /dev/null and b/resources/sprite/conpher_2x/more_on.png differ diff --git a/resources/sprite/conpher_2x/people_off.png b/resources/sprite/conpher_2x/people_off.png new file mode 100644 index 0000000000..ad51a2427d Binary files /dev/null and b/resources/sprite/conpher_2x/people_off.png differ diff --git a/resources/sprite/conpher_2x/people_on.png b/resources/sprite/conpher_2x/people_on.png new file mode 100644 index 0000000000..ff42ad8f95 Binary files /dev/null and b/resources/sprite/conpher_2x/people_on.png differ diff --git a/resources/sprite/conpher_2x/settings_off.png b/resources/sprite/conpher_2x/settings_off.png new file mode 100644 index 0000000000..c25d7905ac Binary files /dev/null and b/resources/sprite/conpher_2x/settings_off.png differ diff --git a/resources/sprite/conpher_2x/settings_on.png b/resources/sprite/conpher_2x/settings_on.png new file mode 100644 index 0000000000..2aa6501876 Binary files /dev/null and b/resources/sprite/conpher_2x/settings_on.png differ diff --git a/resources/sprite/manifest/conph.json b/resources/sprite/manifest/conph.json new file mode 100644 index 0000000000..f6cdb6dbb8 --- /dev/null +++ b/resources/sprite/manifest/conph.json @@ -0,0 +1,71 @@ +{ + "version" : 1, + "sprites" : { + "conpher_calendar_off" : { + "name" : "conpher_calendar_off", + "rule" : "conpher_calendar_off", + "hash" : "a8228ab90fd90f4c2500d9285179bf26" + }, + "conpher_calendar_on" : { + "name" : "conpher_calendar_on", + "rule" : "conpher_calendar_on", + "hash" : "931243bc3c414782ddb2d1d9607908ba" + }, + "conpher_files_off" : { + "name" : "conpher_files_off", + "rule" : "conpher_files_off", + "hash" : "de1aee01b9b47b354e6ac280ae68bae1" + }, + "conpher_files_on" : { + "name" : "conpher_files_on", + "rule" : "conpher_files_on", + "hash" : "9ccbbd5e86fd4ec87a11aee0c9ec8c60" + }, + "conpher_list_off" : { + "name" : "conpher_list_off", + "rule" : "conpher_list_off", + "hash" : "2611311d0c2aec04416433be74d3a30e" + }, + "conpher_list_on" : { + "name" : "conpher_list_on", + "rule" : "conpher_list_on", + "hash" : "cee6de0301c84b0d195282642642afa0" + }, + "conpher_more_off" : { + "name" : "conpher_more_off", + "rule" : "conpher_more_off", + "hash" : "3b7099bdde20a13864b48552b11e92c3" + }, + "conpher_more_on" : { + "name" : "conpher_more_on", + "rule" : "conpher_more_on", + "hash" : "b146f0cff9c2e5f0b57f7ebcfe0704d3" + }, + "conpher_people_off" : { + "name" : "conpher_people_off", + "rule" : "conpher_people_off", + "hash" : "641a6a21aa32a12416e85caf8a22e340" + }, + "conpher_people_on" : { + "name" : "conpher_people_on", + "rule" : "conpher_people_on", + "hash" : "f13745fd7036564eefb1c0ebc3502a92" + }, + "conpher_settings_off" : { + "name" : "conpher_settings_off", + "rule" : "conpher_settings_off", + "hash" : "aa9ab000d9e33e3c50c2fe70367f30b4" + }, + "conpher_settings_on" : { + "name" : "conpher_settings_on", + "rule" : "conpher_settings_on", + "hash" : "a5fe22965997f9559800ca7db5ea32c8" + } + }, + "scales" : [ + 1, + 2 + ], + "header" : "\/**\n * @provides sprite-conpher-css\n * @generated\n *\/\n\n.sprite-conpher {\n background-image: url(\/rsrc\/image\/sprite-conpher.png);\n background-repeat: no-repeat;\n}\n\n@media\nonly screen and (min-device-pixel-ratio: 1.5),\nonly screen and (-webkit-min-device-pixel-ratio: 1.5) {\n .sprite-conpher {\n background-image: url(\/rsrc\/image\/sprite-conpher-X2.png);\n background-size: {X}px {Y}px;\n }\n}\n", + "type" : "standard" +} diff --git a/scripts/celerity/generate_sprites.php b/scripts/celerity/generate_sprites.php index 1a337e2c63..1ac4d0fc2c 100755 --- a/scripts/celerity/generate_sprites.php +++ b/scripts/celerity/generate_sprites.php @@ -1,82 +1,83 @@ #!/usr/bin/env php setTagline('regenerate CSS sprite sheets'); $args->setSynopsis(<<parseStandardArguments(); $args->parse( array( array( 'name' => 'force', 'help' => 'Force regeneration even if sources have not changed.', ), )); $root = dirname(phutil_get_library_root('phabricator')); $webroot = $root.'/webroot/rsrc'; $webroot = Filesystem::readablePath($webroot); $generator = new CeleritySpriteGenerator(); $sheets = array( 'icon' => $generator->buildIconSheet(), 'menu' => $generator->buildMenuSheet(), 'apps' => $generator->buildAppsSheet(), + 'conph' => $generator->buildConpherenceSheet(), 'apps-large' => $generator->buildAppsLargeSheet(), // TODO: @chad: should we actually remove this? // 'apps-xlarge' => $generator->buildAppsXLargeSheet(), 'gradient' => $generator->buildGradientSheet(), ); list($err) = exec_manual('optipng'); if ($err) { $have_optipng = false; echo phutil_console_format( " WARNING `optipng` not found in PATH.\n". "Sprites will not be optimized! Install `optipng`!\n"); } else { $have_optipng = true; } foreach ($sheets as $name => $sheet) { $manifest_path = $root.'/resources/sprite/manifest/'.$name.'.json'; if (!$args->getArg('force')) { if (Filesystem::pathExists($manifest_path)) { $data = Filesystem::readFile($manifest_path); $data = json_decode($data, true); if (!$sheet->needsRegeneration($data)) { continue; } } } $sheet ->generateCSS($webroot."/css/sprite-{$name}.css") ->generateManifest($root."/resources/sprite/manifest/{$name}.json"); foreach ($sheet->getScales() as $scale) { if ($scale == 1) { $sheet_name = "sprite-{$name}.png"; } else { $sheet_name = "sprite-{$name}-X{$scale}.png"; } $full_path = "{$webroot}/image/{$sheet_name}"; $sheet->generateImage($full_path, $scale); if ($have_optipng) { echo "Optimizing...\n"; phutil_passthru('optipng -o7 -clobber %s', $full_path); } } } echo "Done.\n"; diff --git a/src/infrastructure/celerity/CeleritySpriteGenerator.php b/src/infrastructure/celerity/CeleritySpriteGenerator.php index c6b2ac23d0..bc25743b83 100644 --- a/src/infrastructure/celerity/CeleritySpriteGenerator.php +++ b/src/infrastructure/celerity/CeleritySpriteGenerator.php @@ -1,453 +1,489 @@ getDirectoryList('icons_1x'); $colors = array( '', 'grey', 'white', ); $scales = array( '1x' => 1, '2x' => 2, ); $template = id(new PhutilSprite()) ->setSourceSize(14, 14); $sprites = array(); foreach ($colors as $color) { foreach ($icons as $icon) { $prefix = 'icons_'; if (strlen($color)) { $prefix .= $color.'_'; } $suffix = ''; if (strlen($color)) { $suffix = '-'.$color; } $sprite = id(clone $template) ->setName('action-'.$icon.$suffix); $tcss = array(); $tcss[] = '.action-'.$icon.$suffix; if ($color == 'white') { $tcss[] = '.device-desktop .phabricator-action-view:hover '. '.action-'.$icon; if ($icon == 'new') { // Hover state for the "+" icons on homepage tiles. $tcss[] = '.phabricator-application-launch-create:hover '. '.phabricator-application-create-icon.action-new-grey'; } } $sprite->setTargetCSS(implode(', ', $tcss)); foreach ($scales as $scale_key => $scale) { $path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png'); $sprite->setSourceFile($path, $scale); } $sprites[] = $sprite; } } $remarkup_icons = $this->getDirectoryList('remarkup_1x'); foreach ($remarkup_icons as $icon) { $prefix = 'remarkup_'; // Strip 'text_' from these file names. $class_name = substr($icon, 5); $sprite = id(clone $template) ->setName('remarkup-assist-'.$icon) ->setTargetCSS('.remarkup-assist-'.$class_name); foreach ($scales as $scale_key => $scale) { $path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png'); $sprite->setSourceFile($path, $scale); } $sprites[] = $sprite; } $sheet = $this->buildSheet('icon', true); $sheet->setScales($scales); foreach ($sprites as $sprite) { $sheet->addSprite($sprite); } return $sheet; } public function buildMenuSheet() { $sprites = array(); $sources = array( 'seen_read_all' => array( 'x' => 14, 'y' => 14, 'css' => '.alert-notifications .phabricator-main-menu-alert-icon', ), 'seen_have_unread' => array( 'x' => 14, 'y' => 14, 'css' => '.alert-notifications:hover .phabricator-main-menu-alert-icon', ), 'unseen_any' => array( 'x' => 14, 'y' => 14, 'css' => '.alert-notifications.alert-unread .phabricator-main-menu-alert-icon', ), 'arrow-right' => array( 'x' => 9, 'y' => 31, 'css' => '.phabricator-crumb-divider', ), 'eye' => array( 'x' => 24, 'y' => 20, 'css' => '.menu-icon-eye', ), 'app' => array( 'x' => 24, 'y' => 20, 'css' => '.menu-icon-app', ), 'logo' => array( 'x' => 139, 'y' => 25, 'css' => '.phabricator-main-menu-logo-image', ), 'conf-off' => array( 'x' => 14, 'y' => 14, 'css' => '.alert-notifications .phabricator-main-menu-message-icon', ), 'conf-hover' => array( 'x' => 14, 'y' => 14, 'css' => '.alert-notifications:hover .phabricator-main-menu-message-icon', ), 'conf-unseen' => array( 'x' => 14, 'y' => 14, 'css' => '.alert-notifications.message-unread '. '.phabricator-main-menu-message-icon', ), ); $scales = array( '1x' => 1, '2x' => 2, ); $template = new PhutilSprite(); foreach ($sources as $name => $spec) { $sprite = id(clone $template) ->setName($name) ->setSourceSize($spec['x'], $spec['y']) ->setTargetCSS($spec['css']); foreach ($scales as $scale_name => $scale) { $path = 'menu_'.$scale_name.'/'.$name.'.png'; $path = $this->getPath($path); $sprite->setSourceFile($path, $scale); } $sprites[] = $sprite; } $sheet = $this->buildSheet('menu', true); $sheet->setScales($scales); foreach ($sprites as $sprite) { $sheet->addSprite($sprite); } return $sheet; } + public function buildConpherenceSheet() { + $icons = $this->getDirectoryList('conpher_1x'); + $scales = array( + '1x' => 1, + '2x' => 2, + ); + $template = id(new PhutilSprite()) + ->setSourceSize(32, 32); + + $sprites = array(); + foreach ($icons as $icon) { + $color = preg_match('/_on/', $icon) ? 'on' : 'off'; + + $prefix = 'conpher_'; + + $sprite = id(clone $template) + ->setName($prefix.$icon); + + $sprite->setTargetCSS($prefix.$icon); + + foreach ($scales as $scale_key => $scale) { + $path = $this->getPath($prefix.$scale_key.'/'.$icon.'.png'); + $sprite->setSourceFile($path, $scale); + } + $sprites[] = $sprite; + } + + $sheet = $this->buildSheet('conpher', true); + $sheet->setScales($scales); + foreach ($sprites as $sprite) { + $sheet->addSprite($sprite); + } + + return $sheet; + } + public function buildGradientSheet() { $gradients = $this->getDirectoryList('gradients'); $template = new PhutilSprite(); $unusual_heights = array( 'dark-menu-label' => 25, 'breadcrumbs' => 31, 'menu-hover' => 28, 'menu-label' => 24, 'menu-selected' => 28, ); // Reorder the sprites so less-specific rules generate earlier in the sheet. // Otherwise we end up with blue "a.black" buttons because the blue rules // have the same specificity but appear later. $gradients = array_fuse($gradients); $gradients = array_select_keys( $gradients, array( 'blue-dark', 'blue-light', )) + $gradients; $extra_css = array( 'black-dark' => ', button.black, a.black, a.black:visited', 'black-light' => ', button.black:active, a.black:active', 'blue-dark' => ', button, a.button, a.button:visited, input.inputsubmit', 'blue-light' => ', button:active, a.button:active', 'grey-dark' => ', button.grey, input.inputaux, a.grey, a.grey:visited, '. 'a.button.disabled, button[disabled], button.disabled', 'grey-light' => ', button.grey:active, a.grey:active, '. 'button.grey_active, a.dropdown-open', 'green-dark' => ', button.green, a.green, a.green:visited', 'green-light' => ', button.green:active, a.green:active', 'dark-menu-label' => ', .phabricator-dark-menu .phabricator-menu-item-type-label', 'menu-label' => ', .phabricator-side-menu .phabricator-menu-item-type-label', 'menu-hover' => ', .device-desktop .phabricator-side-menu '. 'a.phabricator-menu-item-type-link:hover, '. '.phabricator-filetree a.phabricator-filetree-item:hover', 'menu-selected' => ', .phabricator-side-menu .phabricator-menu-item-selected, '. '.device-desktop .phabricator-side-menu '. 'a.phabricator-menu-item-selected:hover, '. '.phabricator-nav-local a.phabricator-active-nav-focus', ); $sprites = array(); foreach ($gradients as $gradient) { $path = $this->getPath('gradients/'.$gradient.'.png'); $sprite = id(clone $template) ->setName('gradient-'.$gradient) ->setSourceFile($path) ->setTargetCSS('.gradient-'.$gradient.idx($extra_css, $gradient)); $sprite->setSourceSize(4, idx($unusual_heights, $gradient, 26)); $sprites[] = $sprite; } $sheet = $this->buildSheet( 'gradient', false, PhutilSpriteSheet::TYPE_REPEAT_X, ', button, a.button, a.button:visited, input.inputsubmit, '. '.phabricator-dark-menu .phabricator-menu-item-type-label, '. '.phabricator-side-menu .phabricator-menu-item-type-label, '. '.device-desktop .phabricator-side-menu '. 'a.phabricator-menu-item-type-link:hover, '. '.phabricator-side-menu .phabricator-menu-item-selected, '. '.device-desktop .phabricator-side-menu '. 'a.phabricator-menu-item-selected:hover, '. '.phabricator-filetree a.phabricator-filetree-item:hover, '. '.phabricator-filetree a.phabricator-active-nav-focus'); foreach ($sprites as $sprite) { $sheet->addSprite($sprite); } return $sheet; } public function buildAppsSheet() { return $this->buildAppsSheetVariant(1); } public function buildAppsLargeSheet() { return $this->buildAppsSheetVariant(2); } public function buildAppsXLargeSheet() { return $this->buildAppsSheetVariant(3); } private function buildAppsSheetVariant($variant) { if ($variant == 1) { $scales = array( '1x' => 1, '2x' => 2, ); $variant_name = 'apps'; $variant_short = ''; $size_x = 14; $size_y = 14; $colors = array( 'dark' => 'dark', ); } else if ($variant == 2) { $scales = array( '2x' => 1, '4x' => 2, ); $variant_name = 'apps-large'; $variant_short = '-large'; $size_x = 28; $size_y = 28; $colors = array( 'light' => 'lb', 'dark' => 'dark', 'blue' => 'blue', 'glow' => 'glow', ); } else { $scales = array( '4x' => 1, ); $variant_name = 'apps-xlarge'; $variant_short = '-xlarge'; $size_x = 56; $size_y = 56; $colors = array( 'dark' => 'dark', /* TODO: These are available but not currently used. 'blue' => 'blue', 'light' => 'lb', 'glow' => 'glow', */ ); } $apps = $this->getDirectoryList('apps_dark_1x'); $template = id(new PhutilSprite()) ->setSourceSize($size_x, $size_y); $sprites = array(); foreach ($apps as $app) { foreach ($colors as $color => $color_path) { $css = '.app-'.$app.'-'.$color.$variant_short; if ($color == 'blue' && $variant_name == 'apps-large') { $css .= ', .phabricator-crumb-view:hover .app-'.$app.'-dark-large'; } if ($color == 'glow' && $variant_name == 'apps-large') { $css .= ', .device-desktop .phabricator-dark-menu a:hover '. '.app-'.$app.'-light-large'; } $sprite = id(clone $template) ->setName('app-'.$app.'-'.$color.$variant_short) ->setTargetCSS($css); foreach ($scales as $scale_name => $scale) { $path = $this->getPath( 'apps_'.$color_path.'_'.$scale_name.'/'.$app.'.png'); $sprite->setSourceFile($path, $scale); } $sprites[] = $sprite; } } $sheet = $this->buildSheet($variant_name, count($scales) > 1); $sheet->setScales($scales); foreach ($sprites as $sprite) { $sheet->addSprite($sprite); } return $sheet; } private function getPath($to_path = null) { $root = dirname(phutil_get_library_root('phabricator')); return $root.'/resources/sprite/'.$to_path; } private function getDirectoryList($dir) { $path = $this->getPath($dir); $result = array(); $images = Filesystem::listDirectory($path, $include_hidden = false); foreach ($images as $image) { if (!preg_match('/\.png$/', $image)) { throw new Exception( "Expected file '{$image}' in '{$path}' to be a sprite source ". "ending in '.png'."); } $result[] = substr($image, 0, -4); } return $result; } private function buildSheet( $name, $has_retina, $type = null, $extra_css = '') { $sheet = new PhutilSpriteSheet(); $at = '@'; switch ($type) { case PhutilSpriteSheet::TYPE_STANDARD: default: $type = PhutilSpriteSheet::TYPE_STANDARD; $repeat_rule = 'no-repeat'; break; case PhutilSpriteSheet::TYPE_REPEAT_X: $repeat_rule = 'repeat-x'; break; case PhutilSpriteSheet::TYPE_REPEAT_Y: $repeat_rule = 'repeat-y'; break; } $retina_rules = null; if ($has_retina) { $retina_rules = <<setSheetType($type); $sheet->setCSSHeader(<<