Page MenuHomestyx hydra

No OneTemporary

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
<?php
require_once dirname(dirname(__FILE__)).'/__init_script__.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline('regenerate CSS sprite sheets');
$args->setSynopsis(<<<EOHELP
**sprites**
Rebuild CSS sprite sheets.
EOHELP
);
$args->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(<<<EOCSS
/**
* @provides autosprite-css
* {$at}generated
*/
.autosprite {
background-image: url(/rsrc/image/autosprite.png);
background-repeat: no-repeat;
}
EOCSS
);
$menu_normal_template = id(new PhutilSprite())
->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 <a> 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(
+ "<bg:red> WARNING </bg> `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 @@
<?php
final class CeleritySpriteGenerator {
public function buildIconSheet() {
$icons = $this->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(<<<EOCSS
/**
* @provides sprite-{$name}-css
* {$at}generated
*/
.sprite-{$name}{$extra_css} {
background-image: url(/rsrc/image/sprite-{$name}.png);
background-repeat: {$repeat_rule};
}
@media
only screen and (min-device-pixel-ratio: 1.5),
only screen and (-webkit-min-device-pixel-ratio: 1.5) {
.sprite-{$name}{$extra_css} {
background-image: url(/rsrc/image/sprite-{$name}-X2.png);
background-size: {X}px {Y}px;
}
}
EOCSS
);
return $sheet;
}
}
diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php
index a833960e4d..f9059dc28f 100644
--- a/src/view/page/menu/PhabricatorMainMenuView.php
+++ b/src/view/page/menu/PhabricatorMainMenuView.php
@@ -1,357 +1,385 @@
<?php
final class PhabricatorMainMenuView extends AphrontView {
private $user;
private $defaultSearchScope;
private $controller;
private $applicationMenu;
-
public function setApplicationMenu(PhabricatorMenuView $application_menu) {
$this->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 @@
-/**
+<<</**
* @provides phabricator-main-menu-view
*/
/* - Main Menu -----------------------------------------------------------------
Main menu at the top of every page that has chrome. It reacts to resolution
changes in order to behave reasonably on tablets and phones.
*/
.phabricator-main-menu {
background: #2d3236;
background-image: url(/rsrc/image/main_texture.png);
background-repeat: repeat-x;
position: relative;
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.25);
z-index: 6;
min-height: 44px;
}
.phabricator-main-menu a:hover {
text-decoration: none;
}
/* - Logo ----------------------------------------------------------------------
The "Phabricator" logo group in the main menu. On tablet and phone devices,
this shows a "reveal" button to expand/collapse the rest of the menu.
*/
.device-desktop .phabricator-main-menu-group-logo {
float: left;
}
.phabricator-main-menu-logo {
display: inline-block;
height: 44px;
width: 170px;
margin-right: 12px;
background: 6px 9px url(/rsrc/image/header_logo.png) no-repeat;
}
/* - Expand/Collapse Button ----------------------------------------------------
On phones, the menu switches to a vertical layout and uses a button to expand
or collapse the items.
*/
.phabricator-main-menu-expand-button {
position: absolute;
top: 7px;
display: block;
width: 40px;
height: 28px;
- text-align: center;
- background: #22292d url(/rsrc/image/lines.png) no-repeat 8px 6px;
+ background-color: #22292d;
border-radius: 6px;
border: 1px solid #111111;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1),
0 1px 0 rgba(255, 255, 255, 0.075);
}
+.phabricator-menu-button-icon {
+ position: absolute;
+ width: 24px;
+ height: 20px;
+ top: 5px;
+ left: 8px;
+}
+
.phabricator-expand-core-menu {
left: 10px;
}
.phabricator-expand-application-menu {
right: 10px;
}
.device-desktop .phabricator-main-menu-expand-button {
display: none;
}
.device-tablet .phabricator-main-menu-expand-button,
.device-phone .phabricator-main-menu-expand-button {
display: block;
}
.phabricator-core-menu-expanded .phabricator-expand-core-menu,
.phabricator-application-menu-expanded .phabricator-expand-application-menu {
background-color: #55595d;
}
/* - Search --------------------------------------------------------------------
The main search input in the menu bar.
*/
.device-desktop .phabricator-main-menu-search {
width: 220px;
}
.phabricator-main-menu-search-container {
padding: 10px 0;
position: relative;
height: 24px;
}
.phabricator-main-menu-search-target {
position: absolute;
top: 46px;
}
.device-desktop .phabricator-main-menu-search-target {
width: 320px;
margin-left: -150px;
}
.device-tablet .phabricator-main-menu-search-target,
.device-phone .phabricator-main-menu-search-target {
width: 100%;
margin-left: -25px;
}
.device-desktop .phabricator-main-menu-search-container {
margin: 0 8px 0 50px;
}
.device-tablet .phabricator-main-menu-search-container,
.device-phone .phabricator-main-menu-search-container {
margin: 0 18px 0 60px;
}
.phabricator-main-menu-search input {
outline: 0;
margin: 0;
width: 100%;
right: 0;
position: absolute;
border: 1px solid #333333;
border-radius: 12px;
background: #555555;
height: 12px;
line-height: 12px;
box-shadow: 0px 1px 1px rgba(128, 128, 128, 0.25);
padding: 6px 32px 6px 10px;
}
.phabricator-main-menu-search input:focus {
background: #c9c9c9;
}
.phabricator-main-menu-search input.jx-typeahead-placeholder {
color: #999999;
}
.phabricator-main-menu-search button {
position: absolute;
color: transparent;
background: transparent 5px 6px url(/rsrc/image/search.png) no-repeat;
border: none;
outline: none;
box-shadow: none;
text-shadow: none;
min-width: 0;
height: 20px;
width: 20px;
top: 11px;
right: 6px;
}
.phabricator-main-menu-search-target div.jx-typeahead-results {
border-radius: 4px;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.35);
border: 1px solid #33393d;
}
.phabricator-main-menu-search-target div.jx-typeahead-results a.jx-result {
border: 0;
}
.phabricator-main-menu-search-target div.jx-typeahead-results a.focused,
.phabricator-main-menu-search-target div.jx-typeahead-results a:hover {
background: #3875d7;
}
.phabricator-main-search-typeahead-result {
display: block;
padding: 4px 4px 4px 38px;
background-position: 4px 4px;
background-size: 25px 25px;
background-repeat: no-repeat;
}
.phabricator-main-search-typeahead-result .result-name {
display: block;
font-weight: bold;
color: #444444;
}
.focused .phabricator-main-search-typeahead-result .result-name,
a:hover .phabricator-main-search-typeahead-result .result-name {
color: #eeeeee;
}
.phabricator-main-search-typeahead-result .result-type {
color: #888888;
}
.focused .phabricator-main-search-typeahead-result .result-type,
a:hover .phabricator-main-search-typeahead-result .result-type {
color: #dddddd;
}
/* - Alert ---------------------------------------------------------------------
Alert menus are like icon menus but don't obey collapse rules.
*/
+.device-desktop .phabricator-main-menu-alerts {
+ display: inline-block;
+}
+
+.device-tablet .phabricator-main-menu-alerts,
+.device-phone .phabricator-main-menu-alerts {
+ position: absolute;
+ left: 60px;
+ right: 60px;
+ top: 10px;
+ height: 34px;
+ text-align: center;
+}
+
.phabricator-main-menu-alert-bubble {
display: inline-block;
position: relative;
width: 26px;
height: 26px;
margin-bottom: 9px;
}
.phabricator-main-menu-alert-bubble.alert-unread {
width: 46px;
}
.phabricator-main-menu-alert-icon {
position: absolute;
width: 14px;
height: 14px;
- top: 4px;
- left: 5px;
+ top: 5px;
+ left: 6px;
}
.phabricator-main-menu-alert-count {
position: absolute;
font-weight: bold;
line-height: 22px;
top: 1px;
right: 8px;
left: 20px;
color: #ffffff;
text-align: center;
display: none;
}
.phabricator-main-menu-alert-item {
width: 26px;
height: 26px;
margin: 9px;
display: block;
}
.alert-unread .phabricator-main-menu-alert-count {
display: block;
}
/* - Dark Menu -----------------------------------------------------------------
Styles shared between the "core" menu (left button on mobile) and
"application" menu (right button on mobile). These styles give the menu a
white-on-black appearance.
*/
.device-phone .phabricator-dark-menu,
.device-tablet .phabricator-dark-menu,
.device-phone .phabricator-dark-menu a.phabricator-menu-item-type-link,
.device-tablet .phabricator-dark-menu a.phabricator-menu-item-type-link {
color: #ffffff;
}
.device-phone .phabricator-dark-menu .phabricator-menu-item-view,
.device-tablet .phabricator-dark-menu .phabricator-menu-item-view {
display: block;
padding: 4px 0;
- border-width: 1px 0;
- border-style: solid;
- border-color: #35383b transparent #282b2d;
}
.device-phone .phabricator-dark-menu .phabricator-menu-item-type-label,
.device-tablet .phabricator-dark-menu .phabricator-menu-item-type-label {
text-transform: uppercase;
font-size: 11px;
- background: #151719;
- padding-left: 12px;
+ background-color: #151719;
+ padding: 0 0 0 12px;
+ height: 24px;
}
.device-phone .phabricator-dark-menu .phabricator-menu-item-type-spacer,
.device-tablet .phabricator-dark-menu .phabricator-menu-item-type-spacer {
display: none;
}
+.device-phone .phabricator-dark-menu .phabricator-menu-item-type-label
+ .phabricator-menu-item-name,
+.device-tablet .phabricator-dark-menu .phabricator-menu-item-type-label
+ .phabricator-menu-item-name {
+ display: block;
+ padding: 6px 0 0;
+}
+
+.device-phone .phabricator-dark-menu .phabricator-menu-item-type-link,
+.device-tablet .phabricator-dark-menu .phabricator-menu-item-type-link {
+ border-width: 1px 0;
+ border-style: solid;
+ border-color: #34373b transparent #282c2d;
+ background-image: url(/rsrc/image/texture/dark-menu.png);
+}
+
/* - Core Menu -----------------------------------------------------------------
Styles unique to the core menu (left button on mobile).
*/
.phabricator-core-menu-icon {
position: absolute;
display: block;
width: 26px;
height: 26px;
}
.phabricator-core-menu-profile-image {
background-size: 26px 26px;
}
.device-phone .phabricator-core-menu,
.device-tablet .phabricator-core-menu {
display: none;
}
.device-phone .phabricator-core-menu-expanded .phabricator-core-menu,
.device-tablet .phabricator-core-menu-expanded .phabricator-core-menu {
display: block;
}
.device-phone .phabricator-core-menu .phabricator-menu-item-type-link,
.device-tablet .phabricator-core-menu .phabricator-menu-item-type-link {
font-size: 15px;
min-height: 30px;
}
.device-phone .phabricator-core-menu
.phabricator-menu-item-type-link .phabricator-menu-item-name,
.device-tablet .phabricator-core-menu
.phabricator-menu-item-type-link .phabricator-menu-item-name {
margin-left: 40px;
- line-height: 26px;
+ line-height: 28px;
}
.device-desktop .phabricator-core-menu {
position: absolute;
right: 0;
top: 0;
vertical-align: top;
}
.device-desktop .phabricator-core-menu .phabricator-menu-item-name {
display: none;
}
.device-desktop .phabricator-core-menu .phabricator-menu-item-view {
display: block;
float: left;
position: relative;
min-width: 38px;
height: 44px;
}
.device-desktop .phabricator-core-menu .phabricator-core-item-device {
display: none;
}
.device-desktop .phabricator-core-menu-icon {
top: 9px;
left: 6px;
}
.device-phone .phabricator-core-menu-icon,
.device-tablet .phabricator-core-menu-icon {
left: 6px;
}
.device-desktop .phabricator-core-menu .phabricator-core-menu-item-profile {
border-width: 0 1px;
border-style: solid;
border-color: #44494d;
margin: 0 8px;
}
/* - Application Menu ----------------------------------------------------------
Styles unique to the application menu (right button on mobile).
*/
.device-phone .phabricator-application-menu-expanded
.phabricator-application-menu,
.device-tablet .phabricator-application-menu-expanded
.phabricator-application-menu {
display: block;
padding-top: 44px;
}
.phabricator-application-menu {
display: none;
}
.phabricator-application-menu .phabricator-menu-item-type-link
.phabricator-menu-item-name {
padding-left: 12px;
}
.device-phone .phabricator-application-menu-expanded
.phabricator-application-menu,
.device-tablet .phabricator-application-menu-expanded
.phabricator-application-menu {
display: block;
padding-top: 44px;
}
diff --git a/webroot/rsrc/css/sprite-gradient.css b/webroot/rsrc/css/sprite-gradient.css
index 19ae1ba676..8ea9376ee6 100644
--- a/webroot/rsrc/css/sprite-gradient.css
+++ b/webroot/rsrc/css/sprite-gradient.css
@@ -1,58 +1,58 @@
/**
* @provides sprite-gradient-css
* @generated
*/
-.sprite-gradient, button, a.button, a.button:visited, input.inputsubmit {
+.sprite-gradient, button, a.button, a.button:visited, input.inputsubmit, .phabricator-dark-menu .phabricator-menu-item-type-label {
background-image: url(/rsrc/image/sprite-gradient.png);
background-repeat: repeat-x;
}
@media
only screen and (min-device-pixel-ratio: 1.5),
only screen and (-webkit-min-device-pixel-ratio: 1.5) {
- .sprite-gradient, button, a.button, a.button:visited, input.inputsubmit {
+ .sprite-gradient, button, a.button, a.button:visited, input.inputsubmit, .phabricator-dark-menu .phabricator-menu-item-type-label {
background-image: url(/rsrc/image/sprite-gradient-X2.png);
background-size: 4px 274px;
}
}
.gradient-blue-dark, button, a.button, a.button:visited, input.inputsubmit {
background-position: 0px -26px;
}
.gradient-blue-light, button:active, a.button:active {
background-position: 0px -53px;
}
.gradient-black-dark, button.black, a.black, a.black:visited {
background-position: 0px -80px;
}
.gradient-black-light, button.black:active, a.black:active {
background-position: 0px -107px;
}
.gradient-breadcrumbs {
background-position: 0px -242px;
}
-.gradient-dark-menu-label {
+.gradient-dark-menu-label, .phabricator-dark-menu .phabricator-menu-item-type-label {
background-position: 0px 0px;
}
.gradient-green-dark, button.green, a.green, a.green:visited {
background-position: 0px -134px;
}
.gradient-green-light, button.green:active, a.green:active {
background-position: 0px -161px;
}
.gradient-grey-dark, button.grey, input.inputaux, a.grey, a.grey:visited, a.button.disabled, button[disabled], button.disabled {
background-position: 0px -188px;
}
.gradient-grey-light, button.grey:active, a.grey:active, button.grey_active, a.dropdown-open {
background-position: 0px -215px;
}
diff --git a/webroot/rsrc/css/sprite-menu.css b/webroot/rsrc/css/sprite-menu.css
index b87710bbe7..54427091c8 100644
--- a/webroot/rsrc/css/sprite-menu.css
+++ b/webroot/rsrc/css/sprite-menu.css
@@ -1,42 +1,50 @@
/**
* @provides sprite-menu-css
* @generated
*/
.sprite-menu {
background-image: url(/rsrc/image/sprite-menu.png);
background-repeat: no-repeat;
}
@media
only screen and (min-device-pixel-ratio: 1.5),
only screen and (-webkit-min-device-pixel-ratio: 1.5) {
.sprite-menu {
background-image: url(/rsrc/image/sprite-menu-X2.png);
- background-size: 47px 101px;
+ background-size: 72px 101px;
}
}
.phabricator-main-menu-alert-bubble {
background-position: 0px -42px;
}
.phabricator-main-menu-alert-bubble.alert-unread {
background-position: 0px -15px;
}
.alert-notifications .phabricator-main-menu-alert-icon {
background-position: 0px 0px;
}
.alert-notifications:hover .phabricator-main-menu-alert-icon {
background-position: -15px 0px;
}
.alert-notifications.alert-unread .phabricator-main-menu-alert-icon {
background-position: -27px -42px;
}
.phabricator-crumb-divider {
background-position: 0px -69px;
}
+
+.menu-icon-eye {
+ background-position: -47px -15px;
+}
+
+.menu-icon-app {
+ background-position: -42px -42px;
+}
diff --git a/webroot/rsrc/image/sprite-menu-X2.png b/webroot/rsrc/image/sprite-menu-X2.png
index cf4fdcdac5..7782d0684a 100644
Binary files a/webroot/rsrc/image/sprite-menu-X2.png and b/webroot/rsrc/image/sprite-menu-X2.png differ
diff --git a/webroot/rsrc/image/sprite-menu.png b/webroot/rsrc/image/sprite-menu.png
index 780efad474..7de4dc0106 100644
Binary files a/webroot/rsrc/image/sprite-menu.png and b/webroot/rsrc/image/sprite-menu.png differ
diff --git a/webroot/rsrc/image/texture/dark-menu.png b/webroot/rsrc/image/texture/dark-menu.png
new file mode 100644
index 0000000000..64982f5b9c
Binary files /dev/null and b/webroot/rsrc/image/texture/dark-menu.png differ

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 24, 10:23 AM (15 h, 48 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
182687
Default Alt Text
(48 KB)

Event Timeline