1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-20 09:18:48 +02:00

Allow profile menus to be collapsed and expanded

Summary:
Ref T10054. I think this gets everything except:

  - circles on icons;
  - I spent ~15 minutes poking at animations but wasn't able to get anything that looked reasonable whatsoever.

Test Plan:
  - Collapsed menus.
  - Expanded menus.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10054

Differential Revision: https://secure.phabricator.com/D15056
This commit is contained in:
epriestley 2016-01-19 10:35:32 -08:00
parent bf18b59f5f
commit 0a554c2ed5
11 changed files with 329 additions and 42 deletions

View file

@ -7,7 +7,7 @@
*/ */
return array( return array(
'names' => array( 'names' => array(
'core.pkg.css' => 'c61091b0', 'core.pkg.css' => '7fce81fc',
'core.pkg.js' => '573e6664', 'core.pkg.js' => '573e6664',
'darkconsole.pkg.js' => 'e7393ebb', 'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9', 'differential.pkg.css' => '2de124c9',
@ -143,7 +143,7 @@ return array(
'rsrc/css/phui/phui-object-item-list-view.css' => '26c30d3f', 'rsrc/css/phui/phui-object-item-list-view.css' => '26c30d3f',
'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
'rsrc/css/phui/phui-profile-menu.css' => 'a26fa598', 'rsrc/css/phui/phui-profile-menu.css' => '72d69773',
'rsrc/css/phui/phui-property-list-view.css' => '27b2849e', 'rsrc/css/phui/phui-property-list-view.css' => '27b2849e',
'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591',
'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-spacing.css' => '042804d6',
@ -500,6 +500,7 @@ return array(
'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/core/phtize.js' => 'd254d646',
'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '54733475', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '54733475',
'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836', 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836',
'rsrc/js/phui/behavior-phui-profile-menu.js' => 'bf2c93d6',
'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262', 'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '21dc9144', 'rsrc/js/phuix/PHUIXAutocomplete.js' => '21dc9144',
@ -648,6 +649,7 @@ return array(
'javelin-behavior-pholio-mock-view' => 'fbe497e7', 'javelin-behavior-pholio-mock-view' => 'fbe497e7',
'javelin-behavior-phui-dropdown-menu' => '54733475', 'javelin-behavior-phui-dropdown-menu' => '54733475',
'javelin-behavior-phui-object-box-tabs' => '2bfa2836', 'javelin-behavior-phui-object-box-tabs' => '2bfa2836',
'javelin-behavior-phui-profile-menu' => 'bf2c93d6',
'javelin-behavior-policy-control' => 'ae45872f', 'javelin-behavior-policy-control' => 'ae45872f',
'javelin-behavior-policy-rule-editor' => '5e9f347c', 'javelin-behavior-policy-rule-editor' => '5e9f347c',
'javelin-behavior-project-boards' => 'ba4fa35c', 'javelin-behavior-project-boards' => 'ba4fa35c',
@ -817,7 +819,7 @@ return array(
'phui-object-item-list-view-css' => '26c30d3f', 'phui-object-item-list-view-css' => '26c30d3f',
'phui-pager-css' => 'bea33d23', 'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e', 'phui-pinboard-view-css' => '2495140e',
'phui-profile-menu-css' => 'a26fa598', 'phui-profile-menu-css' => '72d69773',
'phui-property-list-view-css' => '27b2849e', 'phui-property-list-view-css' => '27b2849e',
'phui-remarkup-preview-css' => '1a8f2591', 'phui-remarkup-preview-css' => '1a8f2591',
'phui-spacing-css' => '042804d6', 'phui-spacing-css' => '042804d6',
@ -1772,6 +1774,11 @@ return array(
'javelin-util', 'javelin-util',
'javelin-request', 'javelin-request',
), ),
'bf2c93d6' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'bff6884b' => array( 'bff6884b' => array(
'javelin-install', 'javelin-install',
'javelin-dom', 'javelin-dom',

View file

@ -191,14 +191,29 @@ final class CelerityDefaultPostprocessor
// Background color for "dark" themes. // Background color for "dark" themes.
'page.background.dark' => '#ebecee', 'page.background.dark' => '#ebecee',
// NOTE: We can't just do these with an alpha channel because the
// fixed items in the footer may render on top of other items, so the
// backgrounds must be opaque.
// This is the base background color.
'menu.profile.background' => '#525868', 'menu.profile.background' => '#525868',
// This is premultiplied 7.5% alpha.
'menu.profile.background.hover' => '#4c5160',
// This is premultiplied 15% alpha.
'menu.profile.background.selected' => '#464b59',
'menu.profile.text' => '#c6c7cb', 'menu.profile.text' => '#c6c7cb',
'menu.profile.text.selected' => '#ffffff', 'menu.profile.text.selected' => '#ffffff',
'menu.profile.icon' => '#ffffff',
'menu.profile.icon.disabled' => '#b9bcc2', 'menu.profile.icon.disabled' => '#b9bcc2',
'menu.main.height' => '44px', 'menu.main.height' => '44px',
'menu.profile.width' => '240px',
'menu.profile.width.collapsed' => '80px',
'menu.profile.item.height' => '46px',
); );
} }

View file

@ -4,6 +4,7 @@ abstract class PhabricatorProjectController extends PhabricatorController {
private $project; private $project;
private $profileMenu; private $profileMenu;
private $profilePanelEngine;
protected function setProject(PhabricatorProject $project) { protected function setProject(PhabricatorProject $project) {
$this->project = $project; $this->project = $project;
@ -98,14 +99,8 @@ abstract class PhabricatorProjectController extends PhabricatorController {
protected function getProfileMenu() { protected function getProfileMenu() {
if (!$this->profileMenu) { if (!$this->profileMenu) {
$project = $this->getProject(); $engine = $this->getProfilePanelEngine();
if ($project) { if ($engine) {
$viewer = $this->getViewer();
$engine = id(new PhabricatorProjectProfilePanelEngine())
->setViewer($viewer)
->setProfileObject($project);
$this->profileMenu = $engine->buildNavigation(); $this->profileMenu = $engine->buildNavigation();
} }
} }
@ -131,4 +126,24 @@ abstract class PhabricatorProjectController extends PhabricatorController {
return $crumbs; return $crumbs;
} }
protected function getProfilePanelEngine() {
if (!$this->profilePanelEngine) {
$viewer = $this->getViewer();
$project = $this->getProject();
if ($project) {
$engine = id(new PhabricatorProjectProfilePanelEngine())
->setViewer($viewer)
->setProfileObject($project);
$this->profilePanelEngine = $engine;
}
}
return $this->profilePanelEngine;
}
protected function setProfilePanelEngine(
PhabricatorProjectProfilePanelEngine $engine) {
$this->profilePanelEngine = $engine;
return $this;
}
} }

View file

@ -12,10 +12,13 @@ final class PhabricatorProjectPanelController
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$project = $this->getProject(); $project = $this->getProject();
return id(new PhabricatorProjectProfilePanelEngine()) $engine = id(new PhabricatorProjectProfilePanelEngine())
->setProfileObject($project) ->setProfileObject($project)
->setController($this) ->setController($this);
->buildResponse();
$this->setProfilePanelEngine($engine);
return $engine->buildResponse();
} }
} }

View file

@ -6,6 +6,7 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
private $profileObject; private $profileObject;
private $panels; private $panels;
private $controller; private $controller;
private $navigation;
public function setViewer(PhabricatorUser $viewer) { public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer; $this->viewer = $viewer;
@ -147,6 +148,10 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
} }
public function buildNavigation() { public function buildNavigation() {
if ($this->navigation) {
return $this->navigation;
}
$nav = id(new AphrontSideNavFilterView()) $nav = id(new AphrontSideNavFilterView())
->setIsProfileMenu(true) ->setIsProfileMenu(true)
->setBaseURI(new PhutilURI($this->getPanelURI(''))); ->setBaseURI(new PhutilURI($this->getPanelURI('')));
@ -185,14 +190,15 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
} }
} }
$configure_item = $this->newConfigureMenuItem(); $more_items = $this->newAutomaticMenuItems($nav);
if ($configure_item) { foreach ($more_items as $item) {
$nav->addMenuItem($configure_item); $nav->addMenuItem($item);
} }
$nav->selectFilter(null); $nav->selectFilter(null);
return $nav; $this->navigation = $nav;
return $this->navigation;
} }
private function getPanels() { private function getPanels() {
@ -301,26 +307,112 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
} }
} }
private function newConfigureMenuItem() { private function newAutomaticMenuItems(AphrontSideNavFilterView $nav) {
if (!$this->isPanelEngineConfigurable()) { $items = array();
return null;
// NOTE: We're adding a spacer item for the fixed footer, so that if the
// menu taller than the page content you can still scroll down the page far
// enough to access the last item without the content being obscured by the
// fixed items.
$items[] = id(new PHUIListItemView())
->setHideInApplicationMenu(true)
->addClass('phui-profile-menu-spacer');
if ($this->isPanelEngineConfigurable()) {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$expanded_edit_icon = id(new PHUIIconView())
->addClass('phui-list-item-icon')
->addClass('phui-profile-menu-visible-when-expanded')
->setIconFont('fa-pencil');
$collapsed_edit_icon = id(new PHUIIconView())
->addClass('phui-list-item-icon')
->addClass('phui-profile-menu-visible-when-collapsed')
->setIconFont('fa-pencil')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Edit Menu'),
'align' => 'E',
));
$items[] = id(new PHUIListItemView())
->setName('Edit Menu')
->setKey('panel.configure')
->addIcon($expanded_edit_icon)
->addIcon($collapsed_edit_icon)
->addClass('phui-profile-menu-footer')
->addClass('phui-profile-menu-footer-1')
->setHref($this->getPanelURI('configure/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit);
} }
$collapse_id = celerity_generate_unique_node_id();
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$object = $this->getProfileObject();
$can_edit = PhabricatorPolicyFilter::hasCapability( $collapse_key =
$viewer, PhabricatorUserPreferences::PREFERENCE_PROFILE_MENU_COLLAPSED;
$object,
PhabricatorPolicyCapability::CAN_EDIT);
return id(new PHUIListItemView()) $preferences = $viewer->loadPreferences();
->setName('Configure Menu') $is_collapsed = $preferences->getPreference($collapse_key, false);
->setKey('panel.configure')
->setIcon('fa-gear') if ($is_collapsed) {
->setHref($this->getPanelURI('configure/')) $nav->addClass('phui-profile-menu-collapsed');
->setDisabled(!$can_edit) } else {
->setWorkflow(!$can_edit); $nav->addClass('phui-profile-menu-expanded');
}
if ($viewer->isLoggedIn()) {
$settings_uri = '/settings/adjust/?key='.$collapse_key;
} else {
$settings_uri = null;
}
Javelin::initBehavior(
'phui-profile-menu',
array(
'menuID' => $nav->getMainID(),
'collapseID' => $collapse_id,
'isCollapsed' => $is_collapsed,
'settingsURI' => $settings_uri,
));
$collapse_icon = id(new PHUIIconView())
->addClass('phui-list-item-icon')
->addClass('phui-profile-menu-visible-when-expanded')
->setIconFont('fa-angle-left');
$expand_icon = id(new PHUIIconView())
->addClass('phui-list-item-icon')
->addClass('phui-profile-menu-visible-when-collapsed')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Expand'),
'align' => 'E',
))
->setIconFont('fa-angle-right');
$items[] = id(new PHUIListItemView())
->setName('Collapse')
->addIcon($collapse_icon)
->addIcon($expand_icon)
->setID($collapse_id)
->addClass('phui-profile-menu-footer')
->addClass('phui-profile-menu-footer-2')
->setHideInApplicationMenu(true)
->setHref('#');
return $items;
} }
public function getConfigureURI() { public function getConfigureURI() {

View file

@ -41,6 +41,8 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO {
const PREFERENCE_RESOURCE_POSTPROCESSOR = 'resource-postprocessor'; const PREFERENCE_RESOURCE_POSTPROCESSOR = 'resource-postprocessor';
const PREFERENCE_DESKTOP_NOTIFICATIONS = 'desktop-notifications'; const PREFERENCE_DESKTOP_NOTIFICATIONS = 'desktop-notifications';
const PREFERENCE_PROFILE_MENU_COLLAPSED = 'profile-menu.collapsed';
// These are in an unusual order for historic reasons. // These are in an unusual order for historic reasons.
const MAILTAG_PREFERENCE_NOTIFY = 0; const MAILTAG_PREFERENCE_NOTIFY = 0;
const MAILTAG_PREFERENCE_EMAIL = 1; const MAILTAG_PREFERENCE_EMAIL = 1;

View file

@ -27,6 +27,7 @@ final class AphrontSideNavFilterView extends AphrontView {
private $crumbs; private $crumbs;
private $classes = array(); private $classes = array();
private $menuID; private $menuID;
private $mainID;
private $isProfileMenu; private $isProfileMenu;
private $footer = array(); private $footer = array();
@ -168,6 +169,13 @@ final class AphrontSideNavFilterView extends AphrontView {
return $this; return $this;
} }
public function getMainID() {
if (!$this->mainID) {
$this->mainID = celerity_generate_unique_node_id();
}
return $this->mainID;
}
public function render() { public function render() {
if ($this->menu->getItems()) { if ($this->menu->getItems()) {
if (!$this->baseURI) { if (!$this->baseURI) {
@ -212,7 +220,7 @@ final class AphrontSideNavFilterView extends AphrontView {
$local_id = null; $local_id = null;
$background_id = null; $background_id = null;
$local_menu = null; $local_menu = null;
$main_id = celerity_generate_unique_node_id(); $main_id = $this->getMainID();
if ($this->flexible) { if ($this->flexible) {
$drag_id = celerity_generate_unique_node_id(); $drag_id = celerity_generate_unique_node_id();

View file

@ -75,8 +75,11 @@ final class PHUIApplicationMenuView extends Phobject {
$profile_menu = $this->getProfileMenu(); $profile_menu = $this->getProfileMenu();
if ($profile_menu) { if ($profile_menu) {
foreach ($profile_menu->getMenu()->getItems() as $item) { foreach ($profile_menu->getMenu()->getItems() as $item) {
if ($item->getHideInApplicationMenu()) {
continue;
}
$item = clone $item; $item = clone $item;
$item->setRenderNameAsTooltip(false);
$view->addMenuItem($item); $view->addMenuItem($item);
} }
} }

View file

@ -28,6 +28,17 @@ final class PHUIListItemView extends AphrontTagView {
private $aural; private $aural;
private $profileImage; private $profileImage;
private $indented; private $indented;
private $hideInApplicationMenu;
private $icons = array();
public function setHideInApplicationMenu($hide) {
$this->hideInApplicationMenu = $hide;
return $this;
}
public function getHideInApplicationMenu() {
return $this->hideInApplicationMenu;
}
public function setDropdownMenu(PhabricatorActionListView $actions) { public function setDropdownMenu(PhabricatorActionListView $actions) {
Javelin::initBehavior('phui-dropdown-menu'); Javelin::initBehavior('phui-dropdown-menu');
@ -150,6 +161,15 @@ final class PHUIListItemView extends AphrontTagView {
return $this; return $this;
} }
public function addIcon(PHUIIconView $icon) {
$this->icons[] = $icon;
return $this;
}
public function getIcons() {
return $this->icons;
}
protected function getTagName() { protected function getTagName() {
return 'li'; return 'li';
} }
@ -274,6 +294,8 @@ final class PHUIListItemView extends AphrontTagView {
$classes[] = 'phui-list-item-indented'; $classes[] = 'phui-list-item-indented';
} }
$icons = $this->getIcons();
return javelin_tag( return javelin_tag(
$this->href ? 'a' : 'div', $this->href ? 'a' : 'div',
array( array(
@ -285,6 +307,7 @@ final class PHUIListItemView extends AphrontTagView {
array( array(
$aural, $aural,
$icon, $icon,
$icons,
$this->renderChildren(), $this->renderChildren(),
$name, $name,
)); ));

View file

@ -16,12 +16,17 @@
display: table-cell; display: table-cell;
position: relative; position: relative;
vertical-align: top; vertical-align: top;
width: 240px; width: {$menu.profile.width};
max-width: 240px; max-width: {$menu.profile.width};
margin-top: 0; margin-top: 0;
overflow: hidden; overflow: hidden;
} }
.device-desktop .phui-profile-menu-collapsed .phabricator-nav-local {
width: {$menu.profile.width.collapsed};
max-width: {$menu.profile.width.collapsed};
}
.device-desktop .phui-profile-menu .phabricator-nav-content { .device-desktop .phui-profile-menu .phabricator-nav-content {
display: table-cell; display: table-cell;
margin-left: 0; margin-left: 0;
@ -47,23 +52,47 @@
line-height: 22px; line-height: 22px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
/* NOTE: We must have an opaque background on these items so the footer
items appear opaque when the render over normal items. */
background: {$menu.profile.background};
} }
.phui-profile-menu .phabricator-side-menu .phui-list-item-icon, .phui-profile-menu .phabricator-side-menu .phui-list-item-icon,
.phui-profile-menu .phabricator-side-menu .phui-profile-menu .phabricator-side-menu
.phui-list-item-href .phui-icon-view { .phui-list-item-href .phui-icon-view {
position: absolute; position: absolute;
left: 13px;
top: 12px; top: 12px;
left: 13px;
font-size: 20px; font-size: 20px;
width: 22px; width: 22px;
height: 22px; height: 22px;
line-height: 22px; line-height: 22px;
text-align: center; text-align: center;
color: {$menu.profile.icon}; color: {$menu.profile.text};
background-size: 100%; background-size: 100%;
} }
.phui-profile-menu .phui-profile-menu-collapsed .phui-list-item-href {
text-align: center;
padding: 42px 8px 12px;
font-size: 11px;
line-height: 13px;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-list-item-name {
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-list-item-icon,
.phui-profile-menu .phui-profile-menu-collapsed
.phui-list-item-href .phui-icon-view {
top: 10px;
left: 29px;
}
.phui-profile-menu .phabricator-side-menu .phui-profile-menu .phabricator-side-menu
.phui-list-item-disabled .phui-list-item-disabled
.phui-list-item-icon { .phui-list-item-icon {
@ -76,7 +105,16 @@
.device-desktop .phui-profile-menu .phabricator-side-menu .device-desktop .phui-profile-menu .phabricator-side-menu
.phui-list-item-href:hover { .phui-list-item-href:hover {
background-color: rgba(0, 0, 0, 0.075); background-color: {$menu.profile.background.hover};
color: {$menu.profile.text.selected};
}
.phui-profile-menu .phabricator-side-menu
.phui-list-item-selected
.phui-list-item-icon,
.device-desktop .phui-profile-menu .phabricator-side-menu
.phui-list-item-href:hover
.phui-list-item-icon {
color: {$menu.profile.text.selected}; color: {$menu.profile.text.selected};
} }
@ -85,7 +123,7 @@
.device-desktop .phui-profile-menu .phabricator-side-menu .device-desktop .phui-profile-menu .phabricator-side-menu
.phui-list-item-selected .phui-list-item-selected
.phui-list-item-href:hover { .phui-list-item-href:hover {
background-color: rgba(0, 0, 0, 0.150); background-color: {$menu.profile.background.selected};
color: {$menu.profile.text.selected}; color: {$menu.profile.text.selected};
} }
@ -107,3 +145,56 @@
font-size: 12px; font-size: 12px;
color: {$menu.profile.text}; color: {$menu.profile.text};
} }
.phui-profile-menu .phabricator-side-menu .phui-profile-menu-spacer {
box-sizing: border-box;
height: {$menu.profile.item.height};
}
.phui-profile-menu .phabricator-side-menu .phui-profile-menu-footer {
position: fixed;
box-sizing: border-box;
width: {$menu.profile.width};
bottom: 0px;
}
.phui-profile-menu .phabricator-side-menu .phui-profile-menu-footer-1 {
left: 0;
}
.phui-profile-menu .phabricator-side-menu .phui-profile-menu-footer-2 {
left: 120px;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer {
width: 40px;
height: {$menu.profile.item.height};
bottom: 0px;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer-1 {
left: 0;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer-2 {
left: 40px;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer
.phui-list-item-name {
display: none;
}
.phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer
.phui-list-item-icon {
top: 10px;
left: 10px;
}
.phui-profile-menu .phui-profile-menu-expanded
.phui-profile-menu-visible-when-collapsed,
.phui-profile-menu .phui-profile-menu-collapsed
.phui-profile-menu-visible-when-expanded {
display: none;
}

View file

@ -0,0 +1,28 @@
/**
* @provides javelin-behavior-phui-profile-menu
* @requires javelin-behavior
* javelin-stratcom
* javelin-dom
*/
JX.behavior('phui-profile-menu', function(config) {
var menu_node = JX.$(config.menuID);
var collapse_node = JX.$(config.collapseID);
var is_collapsed = config.isCollapsed;
JX.DOM.listen(collapse_node, 'click', null, function(e) {
is_collapsed = !is_collapsed;
JX.DOM.alterClass(menu_node, 'phui-profile-menu-collapsed', is_collapsed);
JX.DOM.alterClass(menu_node, 'phui-profile-menu-expanded', !is_collapsed);
if (config.settingsURI) {
new JX.Request(config.settingsURI)
.setData({value: (is_collapsed ? 1 : 0)})
.send();
}
e.kill();
});
});