@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+329 -42
+10 -3
resources/celerity/map.php
··· 7 7 */ 8 8 return array( 9 9 'names' => array( 10 - 'core.pkg.css' => 'c61091b0', 10 + 'core.pkg.css' => '7fce81fc', 11 11 'core.pkg.js' => '573e6664', 12 12 'darkconsole.pkg.js' => 'e7393ebb', 13 13 'differential.pkg.css' => '2de124c9', ··· 143 143 'rsrc/css/phui/phui-object-item-list-view.css' => '26c30d3f', 144 144 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 145 145 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 146 - 'rsrc/css/phui/phui-profile-menu.css' => 'a26fa598', 146 + 'rsrc/css/phui/phui-profile-menu.css' => '72d69773', 147 147 'rsrc/css/phui/phui-property-list-view.css' => '27b2849e', 148 148 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', 149 149 'rsrc/css/phui/phui-spacing.css' => '042804d6', ··· 500 500 'rsrc/js/core/phtize.js' => 'd254d646', 501 501 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '54733475', 502 502 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836', 503 + 'rsrc/js/phui/behavior-phui-profile-menu.js' => 'bf2c93d6', 503 504 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 504 505 'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262', 505 506 'rsrc/js/phuix/PHUIXAutocomplete.js' => '21dc9144', ··· 648 649 'javelin-behavior-pholio-mock-view' => 'fbe497e7', 649 650 'javelin-behavior-phui-dropdown-menu' => '54733475', 650 651 'javelin-behavior-phui-object-box-tabs' => '2bfa2836', 652 + 'javelin-behavior-phui-profile-menu' => 'bf2c93d6', 651 653 'javelin-behavior-policy-control' => 'ae45872f', 652 654 'javelin-behavior-policy-rule-editor' => '5e9f347c', 653 655 'javelin-behavior-project-boards' => 'ba4fa35c', ··· 817 819 'phui-object-item-list-view-css' => '26c30d3f', 818 820 'phui-pager-css' => 'bea33d23', 819 821 'phui-pinboard-view-css' => '2495140e', 820 - 'phui-profile-menu-css' => 'a26fa598', 822 + 'phui-profile-menu-css' => '72d69773', 821 823 'phui-property-list-view-css' => '27b2849e', 822 824 'phui-remarkup-preview-css' => '1a8f2591', 823 825 'phui-spacing-css' => '042804d6', ··· 1771 1773 'javelin-dom', 1772 1774 'javelin-util', 1773 1775 'javelin-request', 1776 + ), 1777 + 'bf2c93d6' => array( 1778 + 'javelin-behavior', 1779 + 'javelin-stratcom', 1780 + 'javelin-dom', 1774 1781 ), 1775 1782 'bff6884b' => array( 1776 1783 'javelin-install',
+16 -1
src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php
··· 191 191 // Background color for "dark" themes. 192 192 'page.background.dark' => '#ebecee', 193 193 194 + // NOTE: We can't just do these with an alpha channel because the 195 + // fixed items in the footer may render on top of other items, so the 196 + // backgrounds must be opaque. 197 + 198 + // This is the base background color. 194 199 'menu.profile.background' => '#525868', 200 + 201 + // This is premultiplied 7.5% alpha. 202 + 'menu.profile.background.hover' => '#4c5160', 203 + 204 + // This is premultiplied 15% alpha. 205 + 'menu.profile.background.selected' => '#464b59', 206 + 195 207 'menu.profile.text' => '#c6c7cb', 196 208 'menu.profile.text.selected' => '#ffffff', 197 - 'menu.profile.icon' => '#ffffff', 198 209 'menu.profile.icon.disabled' => '#b9bcc2', 199 210 200 211 'menu.main.height' => '44px', 212 + 213 + 'menu.profile.width' => '240px', 214 + 'menu.profile.width.collapsed' => '80px', 215 + 'menu.profile.item.height' => '46px', 201 216 202 217 ); 203 218 }
+23 -8
src/applications/project/controller/PhabricatorProjectController.php
··· 4 4 5 5 private $project; 6 6 private $profileMenu; 7 + private $profilePanelEngine; 7 8 8 9 protected function setProject(PhabricatorProject $project) { 9 10 $this->project = $project; ··· 98 99 99 100 protected function getProfileMenu() { 100 101 if (!$this->profileMenu) { 101 - $project = $this->getProject(); 102 - if ($project) { 103 - $viewer = $this->getViewer(); 104 - 105 - $engine = id(new PhabricatorProjectProfilePanelEngine()) 106 - ->setViewer($viewer) 107 - ->setProfileObject($project); 108 - 102 + $engine = $this->getProfilePanelEngine(); 103 + if ($engine) { 109 104 $this->profileMenu = $engine->buildNavigation(); 110 105 } 111 106 } ··· 129 124 } 130 125 131 126 return $crumbs; 127 + } 128 + 129 + protected function getProfilePanelEngine() { 130 + if (!$this->profilePanelEngine) { 131 + $viewer = $this->getViewer(); 132 + $project = $this->getProject(); 133 + if ($project) { 134 + $engine = id(new PhabricatorProjectProfilePanelEngine()) 135 + ->setViewer($viewer) 136 + ->setProfileObject($project); 137 + $this->profilePanelEngine = $engine; 138 + } 139 + } 140 + return $this->profilePanelEngine; 141 + } 142 + 143 + protected function setProfilePanelEngine( 144 + PhabricatorProjectProfilePanelEngine $engine) { 145 + $this->profilePanelEngine = $engine; 146 + return $this; 132 147 } 133 148 134 149 }
+6 -3
src/applications/project/controller/PhabricatorProjectPanelController.php
··· 12 12 $viewer = $this->getViewer(); 13 13 $project = $this->getProject(); 14 14 15 - return id(new PhabricatorProjectProfilePanelEngine()) 15 + $engine = id(new PhabricatorProjectProfilePanelEngine()) 16 16 ->setProfileObject($project) 17 - ->setController($this) 18 - ->buildResponse(); 17 + ->setController($this); 18 + 19 + $this->setProfilePanelEngine($engine); 20 + 21 + return $engine->buildResponse(); 19 22 } 20 23 21 24 }
+111 -19
src/applications/search/engine/PhabricatorProfilePanelEngine.php
··· 6 6 private $profileObject; 7 7 private $panels; 8 8 private $controller; 9 + private $navigation; 9 10 10 11 public function setViewer(PhabricatorUser $viewer) { 11 12 $this->viewer = $viewer; ··· 147 148 } 148 149 149 150 public function buildNavigation() { 151 + if ($this->navigation) { 152 + return $this->navigation; 153 + } 154 + 150 155 $nav = id(new AphrontSideNavFilterView()) 151 156 ->setIsProfileMenu(true) 152 157 ->setBaseURI(new PhutilURI($this->getPanelURI(''))); ··· 185 190 } 186 191 } 187 192 188 - $configure_item = $this->newConfigureMenuItem(); 189 - if ($configure_item) { 190 - $nav->addMenuItem($configure_item); 193 + $more_items = $this->newAutomaticMenuItems($nav); 194 + foreach ($more_items as $item) { 195 + $nav->addMenuItem($item); 191 196 } 192 197 193 198 $nav->selectFilter(null); 194 199 195 - return $nav; 200 + $this->navigation = $nav; 201 + return $this->navigation; 196 202 } 197 203 198 204 private function getPanels() { ··· 301 307 } 302 308 } 303 309 304 - private function newConfigureMenuItem() { 305 - if (!$this->isPanelEngineConfigurable()) { 306 - return null; 310 + private function newAutomaticMenuItems(AphrontSideNavFilterView $nav) { 311 + $items = array(); 312 + 313 + // NOTE: We're adding a spacer item for the fixed footer, so that if the 314 + // menu taller than the page content you can still scroll down the page far 315 + // enough to access the last item without the content being obscured by the 316 + // fixed items. 317 + $items[] = id(new PHUIListItemView()) 318 + ->setHideInApplicationMenu(true) 319 + ->addClass('phui-profile-menu-spacer'); 320 + 321 + if ($this->isPanelEngineConfigurable()) { 322 + $viewer = $this->getViewer(); 323 + $object = $this->getProfileObject(); 324 + 325 + $can_edit = PhabricatorPolicyFilter::hasCapability( 326 + $viewer, 327 + $object, 328 + PhabricatorPolicyCapability::CAN_EDIT); 329 + 330 + $expanded_edit_icon = id(new PHUIIconView()) 331 + ->addClass('phui-list-item-icon') 332 + ->addClass('phui-profile-menu-visible-when-expanded') 333 + ->setIconFont('fa-pencil'); 334 + 335 + $collapsed_edit_icon = id(new PHUIIconView()) 336 + ->addClass('phui-list-item-icon') 337 + ->addClass('phui-profile-menu-visible-when-collapsed') 338 + ->setIconFont('fa-pencil') 339 + ->addSigil('has-tooltip') 340 + ->setMetadata( 341 + array( 342 + 'tip' => pht('Edit Menu'), 343 + 'align' => 'E', 344 + )); 345 + 346 + $items[] = id(new PHUIListItemView()) 347 + ->setName('Edit Menu') 348 + ->setKey('panel.configure') 349 + ->addIcon($expanded_edit_icon) 350 + ->addIcon($collapsed_edit_icon) 351 + ->addClass('phui-profile-menu-footer') 352 + ->addClass('phui-profile-menu-footer-1') 353 + ->setHref($this->getPanelURI('configure/')) 354 + ->setDisabled(!$can_edit) 355 + ->setWorkflow(!$can_edit); 307 356 } 357 + 358 + $collapse_id = celerity_generate_unique_node_id(); 308 359 309 360 $viewer = $this->getViewer(); 310 - $object = $this->getProfileObject(); 361 + 362 + $collapse_key = 363 + PhabricatorUserPreferences::PREFERENCE_PROFILE_MENU_COLLAPSED; 364 + 365 + $preferences = $viewer->loadPreferences(); 366 + $is_collapsed = $preferences->getPreference($collapse_key, false); 367 + 368 + if ($is_collapsed) { 369 + $nav->addClass('phui-profile-menu-collapsed'); 370 + } else { 371 + $nav->addClass('phui-profile-menu-expanded'); 372 + } 373 + 374 + if ($viewer->isLoggedIn()) { 375 + $settings_uri = '/settings/adjust/?key='.$collapse_key; 376 + } else { 377 + $settings_uri = null; 378 + } 379 + 380 + Javelin::initBehavior( 381 + 'phui-profile-menu', 382 + array( 383 + 'menuID' => $nav->getMainID(), 384 + 'collapseID' => $collapse_id, 385 + 'isCollapsed' => $is_collapsed, 386 + 'settingsURI' => $settings_uri, 387 + )); 388 + 389 + $collapse_icon = id(new PHUIIconView()) 390 + ->addClass('phui-list-item-icon') 391 + ->addClass('phui-profile-menu-visible-when-expanded') 392 + ->setIconFont('fa-angle-left'); 393 + 394 + $expand_icon = id(new PHUIIconView()) 395 + ->addClass('phui-list-item-icon') 396 + ->addClass('phui-profile-menu-visible-when-collapsed') 397 + ->addSigil('has-tooltip') 398 + ->setMetadata( 399 + array( 400 + 'tip' => pht('Expand'), 401 + 'align' => 'E', 402 + )) 403 + ->setIconFont('fa-angle-right'); 311 404 312 - $can_edit = PhabricatorPolicyFilter::hasCapability( 313 - $viewer, 314 - $object, 315 - PhabricatorPolicyCapability::CAN_EDIT); 405 + $items[] = id(new PHUIListItemView()) 406 + ->setName('Collapse') 407 + ->addIcon($collapse_icon) 408 + ->addIcon($expand_icon) 409 + ->setID($collapse_id) 410 + ->addClass('phui-profile-menu-footer') 411 + ->addClass('phui-profile-menu-footer-2') 412 + ->setHideInApplicationMenu(true) 413 + ->setHref('#'); 316 414 317 - return id(new PHUIListItemView()) 318 - ->setName('Configure Menu') 319 - ->setKey('panel.configure') 320 - ->setIcon('fa-gear') 321 - ->setHref($this->getPanelURI('configure/')) 322 - ->setDisabled(!$can_edit) 323 - ->setWorkflow(!$can_edit); 415 + return $items; 324 416 } 325 417 326 418 public function getConfigureURI() {
+2
src/applications/settings/storage/PhabricatorUserPreferences.php
··· 41 41 const PREFERENCE_RESOURCE_POSTPROCESSOR = 'resource-postprocessor'; 42 42 const PREFERENCE_DESKTOP_NOTIFICATIONS = 'desktop-notifications'; 43 43 44 + const PREFERENCE_PROFILE_MENU_COLLAPSED = 'profile-menu.collapsed'; 45 + 44 46 // These are in an unusual order for historic reasons. 45 47 const MAILTAG_PREFERENCE_NOTIFY = 0; 46 48 const MAILTAG_PREFERENCE_EMAIL = 1;
+9 -1
src/view/layout/AphrontSideNavFilterView.php
··· 27 27 private $crumbs; 28 28 private $classes = array(); 29 29 private $menuID; 30 + private $mainID; 30 31 private $isProfileMenu; 31 32 private $footer = array(); 32 33 ··· 168 169 return $this; 169 170 } 170 171 172 + public function getMainID() { 173 + if (!$this->mainID) { 174 + $this->mainID = celerity_generate_unique_node_id(); 175 + } 176 + return $this->mainID; 177 + } 178 + 171 179 public function render() { 172 180 if ($this->menu->getItems()) { 173 181 if (!$this->baseURI) { ··· 212 220 $local_id = null; 213 221 $background_id = null; 214 222 $local_menu = null; 215 - $main_id = celerity_generate_unique_node_id(); 223 + $main_id = $this->getMainID(); 216 224 217 225 if ($this->flexible) { 218 226 $drag_id = celerity_generate_unique_node_id();
+4 -1
src/view/layout/PHUIApplicationMenuView.php
··· 75 75 $profile_menu = $this->getProfileMenu(); 76 76 if ($profile_menu) { 77 77 foreach ($profile_menu->getMenu()->getItems() as $item) { 78 + if ($item->getHideInApplicationMenu()) { 79 + continue; 80 + } 81 + 78 82 $item = clone $item; 79 - $item->setRenderNameAsTooltip(false); 80 83 $view->addMenuItem($item); 81 84 } 82 85 }
+23
src/view/phui/PHUIListItemView.php
··· 28 28 private $aural; 29 29 private $profileImage; 30 30 private $indented; 31 + private $hideInApplicationMenu; 32 + private $icons = array(); 33 + 34 + public function setHideInApplicationMenu($hide) { 35 + $this->hideInApplicationMenu = $hide; 36 + return $this; 37 + } 38 + 39 + public function getHideInApplicationMenu() { 40 + return $this->hideInApplicationMenu; 41 + } 31 42 32 43 public function setDropdownMenu(PhabricatorActionListView $actions) { 33 44 Javelin::initBehavior('phui-dropdown-menu'); ··· 150 161 return $this; 151 162 } 152 163 164 + public function addIcon(PHUIIconView $icon) { 165 + $this->icons[] = $icon; 166 + return $this; 167 + } 168 + 169 + public function getIcons() { 170 + return $this->icons; 171 + } 172 + 153 173 protected function getTagName() { 154 174 return 'li'; 155 175 } ··· 274 294 $classes[] = 'phui-list-item-indented'; 275 295 } 276 296 297 + $icons = $this->getIcons(); 298 + 277 299 return javelin_tag( 278 300 $this->href ? 'a' : 'div', 279 301 array( ··· 285 307 array( 286 308 $aural, 287 309 $icon, 310 + $icons, 288 311 $this->renderChildren(), 289 312 $name, 290 313 ));
+97 -6
webroot/rsrc/css/phui/phui-profile-menu.css
··· 16 16 display: table-cell; 17 17 position: relative; 18 18 vertical-align: top; 19 - width: 240px; 20 - max-width: 240px; 19 + width: {$menu.profile.width}; 20 + max-width: {$menu.profile.width}; 21 21 margin-top: 0; 22 22 overflow: hidden; 23 + } 24 + 25 + .device-desktop .phui-profile-menu-collapsed .phabricator-nav-local { 26 + width: {$menu.profile.width.collapsed}; 27 + max-width: {$menu.profile.width.collapsed}; 23 28 } 24 29 25 30 .device-desktop .phui-profile-menu .phabricator-nav-content { ··· 47 52 line-height: 22px; 48 53 overflow: hidden; 49 54 text-overflow: ellipsis; 55 + 56 + /* NOTE: We must have an opaque background on these items so the footer 57 + items appear opaque when the render over normal items. */ 58 + background: {$menu.profile.background}; 50 59 } 51 60 52 61 .phui-profile-menu .phabricator-side-menu .phui-list-item-icon, 53 62 .phui-profile-menu .phabricator-side-menu 54 63 .phui-list-item-href .phui-icon-view { 55 64 position: absolute; 56 - left: 13px; 57 65 top: 12px; 66 + left: 13px; 58 67 font-size: 20px; 59 68 width: 22px; 60 69 height: 22px; 61 70 line-height: 22px; 62 71 text-align: center; 63 - color: {$menu.profile.icon}; 72 + color: {$menu.profile.text}; 64 73 background-size: 100%; 65 74 } 66 75 76 + .phui-profile-menu .phui-profile-menu-collapsed .phui-list-item-href { 77 + text-align: center; 78 + padding: 42px 8px 12px; 79 + font-size: 11px; 80 + line-height: 13px; 81 + } 82 + 83 + .phui-profile-menu .phui-profile-menu-collapsed .phui-list-item-name { 84 + display: block; 85 + overflow: hidden; 86 + text-overflow: ellipsis; 87 + } 88 + 89 + .phui-profile-menu .phui-profile-menu-collapsed .phui-list-item-icon, 90 + .phui-profile-menu .phui-profile-menu-collapsed 91 + .phui-list-item-href .phui-icon-view { 92 + top: 10px; 93 + left: 29px; 94 + } 95 + 67 96 .phui-profile-menu .phabricator-side-menu 68 97 .phui-list-item-disabled 69 98 .phui-list-item-icon { ··· 76 105 77 106 .device-desktop .phui-profile-menu .phabricator-side-menu 78 107 .phui-list-item-href:hover { 79 - background-color: rgba(0, 0, 0, 0.075); 108 + background-color: {$menu.profile.background.hover}; 109 + color: {$menu.profile.text.selected}; 110 + } 111 + 112 + .phui-profile-menu .phabricator-side-menu 113 + .phui-list-item-selected 114 + .phui-list-item-icon, 115 + .device-desktop .phui-profile-menu .phabricator-side-menu 116 + .phui-list-item-href:hover 117 + .phui-list-item-icon { 80 118 color: {$menu.profile.text.selected}; 81 119 } 82 120 ··· 85 123 .device-desktop .phui-profile-menu .phabricator-side-menu 86 124 .phui-list-item-selected 87 125 .phui-list-item-href:hover { 88 - background-color: rgba(0, 0, 0, 0.150); 126 + background-color: {$menu.profile.background.selected}; 89 127 color: {$menu.profile.text.selected}; 90 128 } 91 129 ··· 107 145 font-size: 12px; 108 146 color: {$menu.profile.text}; 109 147 } 148 + 149 + .phui-profile-menu .phabricator-side-menu .phui-profile-menu-spacer { 150 + box-sizing: border-box; 151 + height: {$menu.profile.item.height}; 152 + } 153 + 154 + .phui-profile-menu .phabricator-side-menu .phui-profile-menu-footer { 155 + position: fixed; 156 + box-sizing: border-box; 157 + width: {$menu.profile.width}; 158 + bottom: 0px; 159 + } 160 + 161 + .phui-profile-menu .phabricator-side-menu .phui-profile-menu-footer-1 { 162 + left: 0; 163 + } 164 + 165 + .phui-profile-menu .phabricator-side-menu .phui-profile-menu-footer-2 { 166 + left: 120px; 167 + } 168 + 169 + .phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer { 170 + width: 40px; 171 + height: {$menu.profile.item.height}; 172 + bottom: 0px; 173 + } 174 + 175 + .phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer-1 { 176 + left: 0; 177 + } 178 + 179 + .phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer-2 { 180 + left: 40px; 181 + } 182 + 183 + 184 + .phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer 185 + .phui-list-item-name { 186 + display: none; 187 + } 188 + 189 + .phui-profile-menu .phui-profile-menu-collapsed .phui-profile-menu-footer 190 + .phui-list-item-icon { 191 + top: 10px; 192 + left: 10px; 193 + } 194 + 195 + .phui-profile-menu .phui-profile-menu-expanded 196 + .phui-profile-menu-visible-when-collapsed, 197 + .phui-profile-menu .phui-profile-menu-collapsed 198 + .phui-profile-menu-visible-when-expanded { 199 + display: none; 200 + }
+28
webroot/rsrc/js/phui/behavior-phui-profile-menu.js
··· 1 + /** 2 + * @provides javelin-behavior-phui-profile-menu 3 + * @requires javelin-behavior 4 + * javelin-stratcom 5 + * javelin-dom 6 + */ 7 + 8 + JX.behavior('phui-profile-menu', function(config) { 9 + var menu_node = JX.$(config.menuID); 10 + var collapse_node = JX.$(config.collapseID); 11 + 12 + var is_collapsed = config.isCollapsed; 13 + 14 + JX.DOM.listen(collapse_node, 'click', null, function(e) { 15 + is_collapsed = !is_collapsed; 16 + JX.DOM.alterClass(menu_node, 'phui-profile-menu-collapsed', is_collapsed); 17 + JX.DOM.alterClass(menu_node, 'phui-profile-menu-expanded', !is_collapsed); 18 + 19 + if (config.settingsURI) { 20 + new JX.Request(config.settingsURI) 21 + .setData({value: (is_collapsed ? 1 : 0)}) 22 + .send(); 23 + } 24 + 25 + e.kill(); 26 + }); 27 + 28 + });