diff --git a/resources/sprite/manifest/gradient.json b/resources/sprite/manifest/gradient.json index ab96d12c96..c050f77f36 100644 --- a/resources/sprite/manifest/gradient.json +++ b/resources/sprite/manifest/gradient.json @@ -1,60 +1,60 @@ { "version" : 1, "sprites" : { "gradient-black-dark" : { "name" : "gradient-black-dark", "rule" : ".gradient-black-dark, button.black, a.black, a.black:visited", "hash" : "b9faf8505427bb14a8c51324e13e2f81" }, "gradient-black-light" : { "name" : "gradient-black-light", "rule" : ".gradient-black-light, button.black:active, a.black:active", "hash" : "812c3375d00ccc93c36c6df2eb565df4" }, "gradient-blue-dark" : { "name" : "gradient-blue-dark", "rule" : ".gradient-blue-dark, button, a.button, a.button:visited, input.inputsubmit", "hash" : "adc2d0f7397374936384014c5d78550d" }, "gradient-blue-light" : { "name" : "gradient-blue-light", "rule" : ".gradient-blue-light, button:active, a.button:active", "hash" : "91b841067a63e543a4dcd9d41e0a2e5c" }, "gradient-breadcrumbs" : { "name" : "gradient-breadcrumbs", "rule" : ".gradient-breadcrumbs", "hash" : "688ab77f50cfbc17ec30d798efb1b39e" }, "gradient-dark-menu-label" : { "name" : "gradient-dark-menu-label", - "rule" : ".gradient-dark-menu-label", + "rule" : ".gradient-dark-menu-label, .phabricator-dark-menu .phabricator-menu-item-type-label", "hash" : "89a908596142d38fbe61a706694cd321" }, "gradient-green-dark" : { "name" : "gradient-green-dark", "rule" : ".gradient-green-dark, button.green, a.green, a.green:visited", "hash" : "0d19ab593772b6b406c0db309b3a14fb" }, "gradient-green-light" : { "name" : "gradient-green-light", "rule" : ".gradient-green-light, button.green:active, a.green:active", "hash" : "71c67916327ec7fc65149ab72c3a2924" }, "gradient-grey-dark" : { "name" : "gradient-grey-dark", "rule" : ".gradient-grey-dark, button.grey, input.inputaux, a.grey, a.grey:visited, a.button.disabled, button[disabled], button.disabled", "hash" : "32651902d28d37dca01cf067072c39c0" }, "gradient-grey-light" : { "name" : "gradient-grey-light", "rule" : ".gradient-grey-light, button.grey:active, a.grey:active, button.grey_active, a.dropdown-open", "hash" : "c3c9ee1ed6f800a4ac86910b94687877" } }, "scales" : [ 1 ], - "header" : "\/**\n * @provides sprite-gradient-css\n * @generated\n *\/\n\n.sprite-gradient, button, a.button, a.button:visited, input.inputsubmit {\n background-image: url(\/rsrc\/image\/sprite-gradient.png);\n background-repeat: repeat-x;\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-gradient, button, a.button, a.button:visited, input.inputsubmit {\n background-image: url(\/rsrc\/image\/sprite-gradient-X2.png);\n background-size: {X}px {Y}px;\n }\n}", + "header" : "\/**\n * @provides sprite-gradient-css\n * @generated\n *\/\n\n.sprite-gradient, button, a.button, a.button:visited, input.inputsubmit, .phabricator-dark-menu .phabricator-menu-item-type-label {\n background-image: url(\/rsrc\/image\/sprite-gradient.png);\n background-repeat: repeat-x;\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-gradient, button, a.button, a.button:visited, input.inputsubmit, .phabricator-dark-menu .phabricator-menu-item-type-label {\n background-image: url(\/rsrc\/image\/sprite-gradient-X2.png);\n background-size: {X}px {Y}px;\n }\n}", "type" : "repeat-x" } diff --git a/resources/sprite/manifest/menu.json b/resources/sprite/manifest/menu.json index 75c4747934..e008dc5386 100644 --- a/resources/sprite/manifest/menu.json +++ b/resources/sprite/manifest/menu.json @@ -1,41 +1,51 @@ { "version" : 1, "sprites" : { + "app" : { + "name" : "app", + "rule" : ".menu-icon-app", + "hash" : "a389f99d9c00f688e625da71579ee90a" + }, "arrow-right" : { "name" : "arrow-right", "rule" : ".phabricator-crumb-divider", "hash" : "a994209450dc73a80841cdd66bb59925" }, "bubble" : { "name" : "bubble", "rule" : ".phabricator-main-menu-alert-bubble.alert-unread", "hash" : "1145ac8a137a2a22517c1945fe22c517" }, + "eye" : { + "name" : "eye", + "rule" : ".menu-icon-eye", + "hash" : "d598b1acb1933a86eaed3dea3347f7b0" + }, "round_bubble" : { "name" : "round_bubble", "rule" : ".phabricator-main-menu-alert-bubble", "hash" : "9be91cc0128997992e9001baf32c8ab8" }, "seen_have_unread" : { "name" : "seen_have_unread", "rule" : ".alert-notifications:hover .phabricator-main-menu-alert-icon", "hash" : "a8a7f07caa726d7e61ef3d41f959d94d" }, "seen_read_all" : { "name" : "seen_read_all", "rule" : ".alert-notifications .phabricator-main-menu-alert-icon", "hash" : "4fdd4807d0c41bd4179dd43150c7fc44" }, "unseen_any" : { "name" : "unseen_any", "rule" : ".alert-notifications.alert-unread .phabricator-main-menu-alert-icon", "hash" : "50d946952c73028b34e0c3378ca36b17" } }, "scales" : [ 1, 2 ], "header" : "\/**\n * @provides sprite-menu-css\n * @generated\n *\/\n\n.sprite-menu {\n background-image: url(\/rsrc\/image\/sprite-menu.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-menu {\n background-image: url(\/rsrc\/image\/sprite-menu-X2.png);\n background-size: {X}px {Y}px;\n }\n}", "type" : "standard" } diff --git a/resources/sprite/menu_1x/app.png b/resources/sprite/menu_1x/app.png new file mode 100644 index 0000000000..c395e306af Binary files /dev/null and b/resources/sprite/menu_1x/app.png differ diff --git a/resources/sprite/menu_1x/eye.png b/resources/sprite/menu_1x/eye.png new file mode 100644 index 0000000000..a1ccfe2bfd Binary files /dev/null and b/resources/sprite/menu_1x/eye.png differ diff --git a/resources/sprite/menu_2x/app.png b/resources/sprite/menu_2x/app.png new file mode 100644 index 0000000000..f59dc10f14 Binary files /dev/null and b/resources/sprite/menu_2x/app.png differ diff --git a/resources/sprite/menu_2x/eye.png b/resources/sprite/menu_2x/eye.png new file mode 100644 index 0000000000..702d2ca457 Binary files /dev/null and b/resources/sprite/menu_2x/eye.png differ diff --git a/scripts/celerity/generate_sprites.php b/scripts/celerity/generate_sprites.php index a395d5b76e..38b027a2fb 100755 --- a/scripts/celerity/generate_sprites.php +++ b/scripts/celerity/generate_sprites.php @@ -1,233 +1,250 @@ #!/usr/bin/env php setTagline('regenerate CSS sprite sheets'); $args->setSynopsis(<<parseStandardArguments(); $args->parse( array( array( 'name' => 'source', 'param' => 'directory', 'help' => 'Directory with sprite sources.', ), array( 'name' => 'force', 'help' => 'Force regeneration even if sources have not changed.', ), )); $srcroot = $args->getArg('source'); if (!$srcroot) { throw new Exception( "You must specify a source directory with '--source'."); } $root = dirname(phutil_get_library_root('phabricator')); $webroot = $root.'/webroot/rsrc'; $webroot = Filesystem::readablePath($webroot); function glx($x) { return (60 + (48 * $x)); } function gly($y) { return (110 + (48 * $y)); } $sheet = new PhutilSpriteSheet(); $at = '@'; $sheet->setCSSHeader(<<setSourceFile($srcroot.'/menu_normal_1x.png') ->setSourceSize(30, 30); $menu_hover_template = id(new PhutilSprite()) ->setSourceFile($srcroot.'/menu_hover_1x.png') ->setSourceSize(30, 30); $menu_selected_template = id(new PhutilSprite()) ->setSourceFile($srcroot.'/menu_selected_1x.png') ->setSourceSize(30, 30); $menu_map = array( '' => $menu_normal_template, '-selected' => $menu_selected_template, ':hover' => $menu_hover_template, ); $icon_map = array( 'help' => array(4, 19), 'settings' => array(0, 28), 'logout' => array(3, 6), 'task' => array(1, 15), ); foreach ($icon_map as $icon => $coords) { list($x, $y) = $coords; foreach ($menu_map as $suffix => $template) { $sheet->addSprite( id(clone $template) ->setName('menu-item-'.$icon.'-'.$suffix) ->setSourcePosition(glx($x), gly($y)) ->setTargetCSS('.main-menu-item-icon-'.$icon.$suffix)); } } $app_template_large = id(new PhutilSprite()) ->setSourceFile($srcroot.'/application_large_1x.png') ->setSourceSize(60, 60); $app_template_large_hover = id(new PhutilSprite()) ->setSourceFile($srcroot.'/application_large_hover_1x.png') ->setSourceSize(60, 60); $app_template_small = id(new PhutilSprite()) ->setSourceFile($srcroot.'/menu_normal_1x.png') ->setSourceSize(30, 30); $app_template_small_hover = id(new PhutilSprite()) ->setSourceFile($srcroot.'/menu_hover_1x.png') ->setSourceSize(30, 30); $app_template_small_selected = id(new PhutilSprite()) ->setSourceFile($srcroot.'/menu_selected_1x.png') ->setSourceSize(30, 30); $app_source_map = array( '-large' => array($app_template_large, 2), // For the application launch view, we only show hover state on the desktop // because it looks glitchy on touch devices. We show the hover state when // the surrounding is hovered, not the icon itself. '-large /* hover */' => array( $app_template_large_hover, 2, '.device-desktop .phabricator-application-launch-container:hover '), '' => array($app_template_small, 1), // Show hover state only for the desktop. ':hover' => array( $app_template_small_hover, 1, '.device-desktop ', ), '-selected' => array($app_template_small_selected, 1), ); $app_map = array( 'differential' => array(9, 1), 'fact' => array(2, 4), 'mail' => array(0, 1), 'diffusion' => array(7, 13), 'slowvote' => array(1, 4), 'phriction' => array(1, 7), 'maniphest' => array(3, 24), 'flags' => array(6, 26), 'settings' => array(9, 11), 'applications' => array(0, 34), 'default' => array(9, 9), 'people' => array(3, 0), 'ponder' => array(4, 35), 'calendar' => array(5, 4), 'files' => array(6, 3), 'projects' => array(7, 35), 'daemons' => array(7, 6), 'herald' => array(1, 5), 'countdown' => array(7, 5), 'conduit' => array(7, 30), 'feed' => array(3, 11), 'paste' => array(9, 2), 'audit' => array(8, 19), 'uiexample' => array(7, 28), 'phpast' => array(6, 31), 'owners' => array(5, 32), 'phid' => array(9, 25), 'diviner' => array(1, 35), 'repositories' => array(8, 13), 'phame' => array(8, 4), 'macro' => array(0, 31), 'releeph' => array(5, 18), 'drydock' => array(5, 25), ); $xadj = -1; foreach ($app_map as $icon => $coords) { list($x, $y) = $coords; foreach ($app_source_map as $suffix => $spec) { list($template, $scale) = $spec; if (isset($spec[2])) { $prefix = $spec[2]; } else { $prefix = ''; } $sheet->addSprite( id(clone $template) ->setName('app-'.$icon.'-'.$suffix) ->setSourcePosition(($xadj + glx($x)) * $scale, gly($y) * $scale) ->setTargetCSS($prefix.'.app-'.$icon.$suffix)); } } $sheet->generateImage($webroot.'/image/autosprite.png'); $sheet->generateCSS($webroot.'/css/autosprite.css'); /* -( Icons Sheet )-------------------------------------------------------- */ $generator = new CeleritySpriteGenerator(); $sheets = array( 'icon' => $generator->buildIconSheet(), 'menu' => $generator->buildMenuSheet(), '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"; } - $sheet->generateImage("{$webroot}/image/{$sheet_name}", $scale); + + $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 4d88b69c99..fe2033efae 100644 --- a/src/infrastructure/celerity/CeleritySpriteGenerator.php +++ b/src/infrastructure/celerity/CeleritySpriteGenerator.php @@ -1,276 +1,289 @@ 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); if ($color == 'white') { $sprite->setTargetCSS( '.device-desktop .phabricator-action-view:hover .action-'.$icon); } else { $sprite->setTargetCSS('.action-'.$icon.$suffix); } 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'); $sheet->setScales($scales); foreach ($sprites as $sprite) { $sheet->addSprite($sprite); } return $sheet; } public function buildMenuSheet() { $sprites = array(); $sources = array( 'round_bubble' => array( 'x' => 26, 'y' => 26, 'css' => '.phabricator-main-menu-alert-bubble' ), 'bubble' => array( 'x' => 46, 'y' => 26, 'css' => '.phabricator-main-menu-alert-bubble.alert-unread' ), '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', + ), ); $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'); $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, ); // 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_combine($gradients, $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', ); $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', PhutilSpriteSheet::TYPE_REPEAT_X, - ', button, a.button, a.button:visited, input.inputsubmit'); + ', button, a.button, a.button:visited, input.inputsubmit, '. + '.phabricator-dark-menu .phabricator-menu-item-type-label'); 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, $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; } $sheet->setSheetType($type); $sheet->setCSSHeader(<<applicationMenu = $application_menu; return $this; } public function getApplicationMenu() { return $this->applicationMenu; } public function setController(PhabricatorController $controller) { $this->controller = $controller; return $this; } public function getController() { return $this->controller; } public function setDefaultSearchScope($default_search_scope) { $this->defaultSearchScope = $default_search_scope; return $this; } public function getDefaultSearchScope() { return $this->defaultSearchScope; } public function setUser(PhabricatorUser $user) { $this->user = $user; return $this; } public function getUser() { return $this->user; } public function render() { $user = $this->user; require_celerity_resource('phabricator-main-menu-view'); $header_id = celerity_generate_unique_node_id(); $menus = array(); $group = new PhabricatorMainMenuGroupView(); $group->addClass('phabricator-main-menu-group-logo'); $group->setCollapsible(false); $group->appendChild( phutil_render_tag( 'a', array( 'class' => 'phabricator-main-menu-logo', 'href' => '/', ), '')); if (PhabricatorEnv::getEnvConfig('notification.enabled') && $user->isLoggedIn()) { list($menu, $dropdown) = $this->renderNotificationMenu(); $group->appendChild($menu); $menus[] = $dropdown; } $group->appendChild( javelin_render_tag( 'a', array( 'class' => 'phabricator-main-menu-expand-button', 'sigil' => 'jx-toggle-class', 'meta' => array( 'map' => array( $header_id => 'phabricator-core-menu-expand', ), ), ), '')); $logo = $group->render(); $phabricator_menu = $this->renderPhabricatorMenu(); // $menus[] = $this->renderApplicationMenu(); $actions = ''; $application_menu = $this->getApplicationMenu(); if ($application_menu) { $application_menu->addClass('phabricator-dark-menu'); $application_menu->addClass('phabricator-application-menu'); } return phutil_render_tag( 'div', array( 'class' => 'phabricator-main-menu', 'id' => $header_id, ), self::renderSingleView( array( $this->renderPhabricatorMenuButton($header_id), - $this->renderApplicationMenuButton($header_id), + $application_menu + ? $this->renderApplicationMenuButton($header_id) + : null, $this->renderPhabricatorLogo(), $alerts, $phabricator_menu, $application_menu, ))). self::renderSingleView($menus); } private function renderSearch() { $user = $this->user; $result = null; $keyboard_config = array( 'helpURI' => '/help/keyboardshortcut/', ); if ($user->isLoggedIn()) { $search = new PhabricatorMainMenuSearchView(); $search->setUser($user); $search->setScope($this->getDefaultSearchScope()); $result = $search; $pref_shortcut = PhabricatorUserPreferences::PREFERENCE_SEARCH_SHORTCUT; if ($user->loadPreferences()->getPreference($pref_shortcut, true)) { $keyboard_config['searchID'] = $search->getID(); } } Javelin::initBehavior('phabricator-keyboard-shortcuts', $keyboard_config); if ($result) { $result = id(new PhabricatorMenuItemView()) ->addClass('phabricator-main-menu-search') ->appendChild($result); } return $result; } private function renderPhabricatorMenuButton($header_id) { return javelin_render_tag( 'a', array( 'class' => 'phabricator-main-menu-expand-button '. 'phabricator-expand-core-menu', 'sigil' => 'jx-toggle-class', 'meta' => array( 'map' => array( $header_id => 'phabricator-core-menu-expanded', ), ), ), - ''); + phutil_render_tag( + 'span', + array( + 'class' => 'phabricator-menu-button-icon sprite-menu menu-icon-eye', + ), + '')); } public function renderApplicationMenuButton($header_id) { return javelin_render_tag( 'a', array( 'class' => 'phabricator-main-menu-expand-button '. 'phabricator-expand-application-menu', 'sigil' => 'jx-toggle-class', 'meta' => array( 'map' => array( $header_id => 'phabricator-application-menu-expanded', ), ), ), - ''); + phutil_render_tag( + 'span', + array( + 'class' => 'phabricator-menu-button-icon sprite-menu menu-icon-app', + ), + '')); } private function renderPhabricatorMenu() { $user = $this->getUser(); $controller = $this->getController(); $applications = PhabricatorApplication::getAllInstalledApplications(); $applications = msort($applications, 'getName'); $core = array(); $more = array(); $actions = array(); $group_core = PhabricatorApplication::GROUP_CORE; foreach ($applications as $application) { if ($application->shouldAppearInLaunchView()) { $item = id(new PhabricatorMenuItemView()) ->setName($application->getName()) ->setHref($application->getBaseURI()); if ($application->getApplicationGroup() == $group_core) { $core[] = $item; } else { $more[] = $item; } } $app_actions = $application->buildMainMenuItems($user, $controller); foreach ($app_actions as $action) { $actions[] = $action; } } $view = new PhabricatorMenuView(); $view->addClass('phabricator-dark-menu'); $view->addClass('phabricator-core-menu'); $search = $this->renderSearch(); $view->appendChild($search); + $view + ->newLabel(pht('Home')) + ->addClass('phabricator-core-item-device'); + $view->addMenuItem( + id(new PhabricatorMenuItemView()) + ->addClass('phabricator-core-item-device') + ->setName(pht('Phabricator Home')) + ->setHref('/')); + if ($controller->getCurrentApplication()) { + $application = $controller->getCurrentApplication(); + $view->addMenuItem( + id(new PhabricatorMenuItemView()) + ->addClass('phabricator-core-item-device') + ->setName(pht('%s Home', $application->getName())) + ->setHref($controller->getApplicationURI())); + } + if ($core) { $view->addMenuItem( id(new PhabricatorMenuItemView()) ->addClass('phabricator-core-item-device') ->setType(PhabricatorMenuItemView::TYPE_LABEL) ->setName(pht('Core Applications'))); foreach ($core as $item) { $item->addClass('phabricator-core-item-device'); $view->addMenuItem($item); } } if ($actions) { $actions = msort($actions, 'getSortOrder'); $view->addMenuItem( id(new PhabricatorMenuItemView()) ->addClass('phabricator-core-item-device') ->setType(PhabricatorMenuItemView::TYPE_LABEL) ->setName(pht('Actions'))); foreach ($actions as $action) { $icon = $action->getIcon(); if ($icon) { $classes = array( 'phabricator-core-menu-icon', 'autosprite', ); if ($action->getSelected()) { $classes[] = 'main-menu-item-icon-'.$icon.'-selected'; } else { $classes[] = 'main-menu-item-icon-'.$icon; } $action->appendChild( phutil_render_tag( 'span', array( 'class' => implode(' ', $classes), ), '')); } $view->addMenuItem($action); } } if ($more) { $view->addMenuItem( id(new PhabricatorMenuItemView()) ->addClass('phabricator-core-item-device') ->setType(PhabricatorMenuItemView::TYPE_LABEL) ->setName(pht('More Applications'))); foreach ($more as $item) { $item->addClass('phabricator-core-item-device'); $view->addMenuItem($item); } } return $view; } private function renderNotificationMenu() { $user = $this->user; require_celerity_resource('phabricator-notification-css'); require_celerity_resource('phabricator-notification-menu-css'); require_celerity_resource('sprite-menu-css'); $count_id = celerity_generate_unique_node_id(); $dropdown_id = celerity_generate_unique_node_id(); $bubble_id = celerity_generate_unique_node_id(); $count_number = id(new PhabricatorFeedStoryNotification()) ->countUnread($user); if ($count_number > 999) { $count_number = "\xE2\x88\x9E"; } $count_tag = phutil_render_tag( 'span', array( 'id' => $count_id, 'class' => 'phabricator-main-menu-alert-count' ), phutil_escape_html($count_number)); $icon_tag = phutil_render_tag( 'span', array( 'class' => 'sprite-menu phabricator-main-menu-alert-icon', ), ''); $container_classes = array( 'phabricator-main-menu-alert-bubble', 'sprite-menu', 'alert-notifications', ); if ($count_number) { $container_classes[] = 'alert-unread'; } $bubble_tag = phutil_render_tag( 'a', array( 'href' => '/notification/', 'class' => implode(' ', $container_classes), 'id' => $bubble_id, ), $icon_tag.$count_tag); Javelin::initBehavior( 'aphlict-dropdown', array( 'bubbleID' => $bubble_id, 'countID' => $count_id, 'dropdownID' => $dropdown_id, )); $notification_dropdown = javelin_render_tag( 'div', array( 'id' => $dropdown_id, 'class' => 'phabricator-notification-menu', 'sigil' => 'phabricator-notification-menu', 'style' => 'display: none;', ), ''); return array($bubble_tag, $notification_dropdown); } } diff --git a/webroot/rsrc/css/application/base/main-menu-view.css b/webroot/rsrc/css/application/base/main-menu-view.css index 2e7de7a188..035eb5636c 100644 --- a/webroot/rsrc/css/application/base/main-menu-view.css +++ b/webroot/rsrc/css/application/base/main-menu-view.css @@ -1,420 +1,455 @@ -/** +<<