1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-18 12:52:42 +01: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(
'names' => array(
'core.pkg.css' => 'c61091b0',
'core.pkg.css' => '7fce81fc',
'core.pkg.js' => '573e6664',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9',
@ -143,7 +143,7 @@ return array(
'rsrc/css/phui/phui-object-item-list-view.css' => '26c30d3f',
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'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-remarkup-preview.css' => '1a8f2591',
'rsrc/css/phui/phui-spacing.css' => '042804d6',
@ -500,6 +500,7 @@ return array(
'rsrc/js/core/phtize.js' => 'd254d646',
'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '54733475',
'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/PHUIXActionView.js' => '8cf6d262',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '21dc9144',
@ -648,6 +649,7 @@ return array(
'javelin-behavior-pholio-mock-view' => 'fbe497e7',
'javelin-behavior-phui-dropdown-menu' => '54733475',
'javelin-behavior-phui-object-box-tabs' => '2bfa2836',
'javelin-behavior-phui-profile-menu' => 'bf2c93d6',
'javelin-behavior-policy-control' => 'ae45872f',
'javelin-behavior-policy-rule-editor' => '5e9f347c',
'javelin-behavior-project-boards' => 'ba4fa35c',
@ -817,7 +819,7 @@ return array(
'phui-object-item-list-view-css' => '26c30d3f',
'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e',
'phui-profile-menu-css' => 'a26fa598',
'phui-profile-menu-css' => '72d69773',
'phui-property-list-view-css' => '27b2849e',
'phui-remarkup-preview-css' => '1a8f2591',
'phui-spacing-css' => '042804d6',
@ -1772,6 +1774,11 @@ return array(
'javelin-util',
'javelin-request',
),
'bf2c93d6' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'bff6884b' => array(
'javelin-install',
'javelin-dom',

View file

@ -191,14 +191,29 @@ final class CelerityDefaultPostprocessor
// Background color for "dark" themes.
'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',
// 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.selected' => '#ffffff',
'menu.profile.icon' => '#ffffff',
'menu.profile.icon.disabled' => '#b9bcc2',
'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 $profileMenu;
private $profilePanelEngine;
protected function setProject(PhabricatorProject $project) {
$this->project = $project;
@ -98,14 +99,8 @@ abstract class PhabricatorProjectController extends PhabricatorController {
protected function getProfileMenu() {
if (!$this->profileMenu) {
$project = $this->getProject();
if ($project) {
$viewer = $this->getViewer();
$engine = id(new PhabricatorProjectProfilePanelEngine())
->setViewer($viewer)
->setProfileObject($project);
$engine = $this->getProfilePanelEngine();
if ($engine) {
$this->profileMenu = $engine->buildNavigation();
}
}
@ -131,4 +126,24 @@ abstract class PhabricatorProjectController extends PhabricatorController {
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();
$project = $this->getProject();
return id(new PhabricatorProjectProfilePanelEngine())
$engine = id(new PhabricatorProjectProfilePanelEngine())
->setProfileObject($project)
->setController($this)
->buildResponse();
->setController($this);
$this->setProfilePanelEngine($engine);
return $engine->buildResponse();
}
}

View file

@ -6,6 +6,7 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
private $profileObject;
private $panels;
private $controller;
private $navigation;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
@ -147,6 +148,10 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
}
public function buildNavigation() {
if ($this->navigation) {
return $this->navigation;
}
$nav = id(new AphrontSideNavFilterView())
->setIsProfileMenu(true)
->setBaseURI(new PhutilURI($this->getPanelURI('')));
@ -185,14 +190,15 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
}
}
$configure_item = $this->newConfigureMenuItem();
if ($configure_item) {
$nav->addMenuItem($configure_item);
$more_items = $this->newAutomaticMenuItems($nav);
foreach ($more_items as $item) {
$nav->addMenuItem($item);
}
$nav->selectFilter(null);
return $nav;
$this->navigation = $nav;
return $this->navigation;
}
private function getPanels() {
@ -301,26 +307,112 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
}
}
private function newConfigureMenuItem() {
if (!$this->isPanelEngineConfigurable()) {
return null;
private function newAutomaticMenuItems(AphrontSideNavFilterView $nav) {
$items = array();
// 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();
$object = $this->getProfileObject();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$collapse_key =
PhabricatorUserPreferences::PREFERENCE_PROFILE_MENU_COLLAPSED;
return id(new PHUIListItemView())
->setName('Configure Menu')
->setKey('panel.configure')
->setIcon('fa-gear')
->setHref($this->getPanelURI('configure/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit);
$preferences = $viewer->loadPreferences();
$is_collapsed = $preferences->getPreference($collapse_key, false);
if ($is_collapsed) {
$nav->addClass('phui-profile-menu-collapsed');
} else {
$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() {

View file

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

View file

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

View file

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

View file

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

View file

@ -16,12 +16,17 @@
display: table-cell;
position: relative;
vertical-align: top;
width: 240px;
max-width: 240px;
width: {$menu.profile.width};
max-width: {$menu.profile.width};
margin-top: 0;
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 {
display: table-cell;
margin-left: 0;
@ -47,23 +52,47 @@
line-height: 22px;
overflow: hidden;
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-href .phui-icon-view {
position: absolute;
left: 13px;
top: 12px;
left: 13px;
font-size: 20px;
width: 22px;
height: 22px;
line-height: 22px;
text-align: center;
color: {$menu.profile.icon};
color: {$menu.profile.text};
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-list-item-disabled
.phui-list-item-icon {
@ -76,7 +105,16 @@
.device-desktop .phui-profile-menu .phabricator-side-menu
.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};
}
@ -85,7 +123,7 @@
.device-desktop .phui-profile-menu .phabricator-side-menu
.phui-list-item-selected
.phui-list-item-href:hover {
background-color: rgba(0, 0, 0, 0.150);
background-color: {$menu.profile.background.selected};
color: {$menu.profile.text.selected};
}
@ -107,3 +145,56 @@
font-size: 12px;
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();
});
});