mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 23:02:42 +01:00
Add basic support for new navigation menu
Summary: Add a new left-side application menu. This menu shows which application you're in and provides a quick way to get to other applications. On desktops, menus are always shown but the app menu can be collapsed to be very small. On tablets, navigation buttons allow you to choose between the menus and the content. On phones, navigation buttons allow you to choose between the app menu, the local menu, and the content. This needs some code and UI cleanup, but has no effect yet so I think it's okay to land as-is, I'll clean it up a bit as I start integrating it. I want to play around with it a bit and see if it's good/useful or horrible anyway. Test Plan: Will include screenshots. Reviewers: vrana, btrahan, chad Reviewed By: btrahan CC: aran, alanh Maniphest Tasks: T1569 Differential Revision: https://secure.phabricator.com/D3223
This commit is contained in:
parent
7b068d3e46
commit
d3fd790574
23 changed files with 705 additions and 111 deletions
|
@ -7,6 +7,13 @@
|
|||
*/
|
||||
|
||||
celerity_register_resource_map(array(
|
||||
'/rsrc/image/app/app_applications.png' =>
|
||||
array(
|
||||
'hash' => '0e83b7bea93bf92777e546ae6c7ac1cb',
|
||||
'uri' => '/res/0e83b7be/rsrc/image/app/app_applications.png',
|
||||
'disk' => '/rsrc/image/app/app_applications.png',
|
||||
'type' => 'png',
|
||||
),
|
||||
'/rsrc/image/app/app_audit.png' =>
|
||||
array(
|
||||
'hash' => '53340003d1daf306b64ed5ebb08bc204',
|
||||
|
@ -140,6 +147,27 @@ celerity_register_resource_map(array(
|
|||
'disk' => '/rsrc/image/bolt.png',
|
||||
'type' => 'png',
|
||||
),
|
||||
'/rsrc/image/button_apps.png' =>
|
||||
array(
|
||||
'hash' => 'cc29f793afd01b15af613562225118f3',
|
||||
'uri' => '/res/cc29f793/rsrc/image/button_apps.png',
|
||||
'disk' => '/rsrc/image/button_apps.png',
|
||||
'type' => 'png',
|
||||
),
|
||||
'/rsrc/image/button_content.png' =>
|
||||
array(
|
||||
'hash' => '87cc5797352097b4b3d7541e6c46f032',
|
||||
'uri' => '/res/87cc5797/rsrc/image/button_content.png',
|
||||
'disk' => '/rsrc/image/button_content.png',
|
||||
'type' => 'png',
|
||||
),
|
||||
'/rsrc/image/button_menu.png' =>
|
||||
array(
|
||||
'hash' => '5742857c7734d9d25be1125f5737fe0e',
|
||||
'uri' => '/res/5742857c/rsrc/image/button_menu.png',
|
||||
'disk' => '/rsrc/image/button_menu.png',
|
||||
'type' => 'png',
|
||||
),
|
||||
'/rsrc/image/credit_cards.png' =>
|
||||
array(
|
||||
'hash' => '681448de424ea159b6ea68af04c046ae',
|
||||
|
@ -1153,7 +1181,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-device' =>
|
||||
array(
|
||||
'uri' => '/res/bec84986/rsrc/js/application/core/behavior-device.js',
|
||||
'uri' => '/res/37669d1a/rsrc/js/application/core/behavior-device.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1614,7 +1642,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-phabricator-nav' =>
|
||||
array(
|
||||
'uri' => '/res/adaae8ae/rsrc/js/application/core/behavior-phabricator-nav.js',
|
||||
'uri' => '/res/cb8979b2/rsrc/js/application/core/behavior-phabricator-nav.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1623,6 +1651,9 @@ celerity_register_resource_map(array(
|
|||
2 => 'javelin-dom',
|
||||
3 => 'javelin-magical-init',
|
||||
4 => 'javelin-vector',
|
||||
5 => 'javelin-request',
|
||||
6 => 'javelin-util',
|
||||
7 => 'javelin-fx',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-phabricator-nav.js',
|
||||
),
|
||||
|
@ -1819,7 +1850,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-toggle-class' =>
|
||||
array(
|
||||
'uri' => '/res/35b86b96/rsrc/js/application/core/behavior-toggle-class.js',
|
||||
'uri' => '/res/fa818e0f/rsrc/js/application/core/behavior-toggle-class.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -2533,7 +2564,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'phabricator-nav-view-css' =>
|
||||
array(
|
||||
'uri' => '/res/3443576d/rsrc/css/aphront/phabricator-nav-view.css',
|
||||
'uri' => '/res/82636b80/rsrc/css/aphront/phabricator-nav-view.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
|
|
@ -882,6 +882,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php',
|
||||
'PhabricatorPasteQuery' => 'applications/paste/query/PhabricatorPasteQuery.php',
|
||||
'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php',
|
||||
'PhabricatorPeopleAdjustSettingController' => 'applications/people/controller/PhabricatorPeopleAdjustSettingController.php',
|
||||
'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php',
|
||||
'PhabricatorPeopleEditController' => 'applications/people/controller/PhabricatorPeopleEditController.php',
|
||||
'PhabricatorPeopleLdapController' => 'applications/people/controller/PhabricatorPeopleLdapController.php',
|
||||
|
@ -1964,6 +1965,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPasteListController' => 'PhabricatorPasteController',
|
||||
'PhabricatorPasteQuery' => 'PhabricatorCursorPagedPolicyQuery',
|
||||
'PhabricatorPasteViewController' => 'PhabricatorPasteController',
|
||||
'PhabricatorPeopleAdjustSettingController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleController' => 'PhabricatorController',
|
||||
'PhabricatorPeopleEditController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPeopleLdapController' => 'PhabricatorPeopleController',
|
||||
|
|
|
@ -42,6 +42,10 @@ final class PhabricatorApplicationAudit extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getCoreApplicationOrder() {
|
||||
return 0.130;
|
||||
}
|
||||
|
||||
public function loadStatus(PhabricatorUser $user) {
|
||||
$status = array();
|
||||
|
||||
|
|
|
@ -62,6 +62,10 @@ abstract class PhabricatorApplication {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function getCoreApplicationOrder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/* -( URI Routing )-------------------------------------------------------- */
|
||||
|
||||
|
|
|
@ -66,6 +66,10 @@ final class PhabricatorApplicationDifferential extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getCoreApplicationOrder() {
|
||||
return 0.100;
|
||||
}
|
||||
|
||||
public function loadStatus(PhabricatorUser $user) {
|
||||
$revisions = id(new DifferentialRevisionQuery())
|
||||
->withResponsibleUsers(array($user->getPHID()))
|
||||
|
|
|
@ -72,5 +72,9 @@ final class PhabricatorApplicationDiffusion extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getCoreApplicationOrder() {
|
||||
return 0.120;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,10 @@ final class PhabricatorApplicationManiphest extends PhabricatorApplication {
|
|||
return celerity_get_resource_uri('/rsrc/image/app/app_maniphest.png');
|
||||
}
|
||||
|
||||
public function getCoreApplicationOrder() {
|
||||
return 0.110;
|
||||
}
|
||||
|
||||
public function getFactObjectsForAnalysis() {
|
||||
return array(
|
||||
new ManiphestTask(),
|
||||
|
|
|
@ -84,6 +84,11 @@ abstract class ManiphestController extends PhabricatorController {
|
|||
$nav->addLabel('Reports');
|
||||
$nav->addFilter('report', 'Reports', '/maniphest/report/');
|
||||
|
||||
$nav->setFlexNav(true);
|
||||
$nav->setShowApplicationMenu(true);
|
||||
$nav->setCurrentApplication($this->getCurrentApplication());
|
||||
$nav->setUser($this->getRequest()->getUser());
|
||||
|
||||
return $nav;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,10 @@ final class PhabricatorApplicationApplications extends PhabricatorApplication {
|
|||
return 'Manage Applications';
|
||||
}
|
||||
|
||||
public function getIconURI() {
|
||||
return celerity_get_resource_uri('/rsrc/image/app/app_applications.png');
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/applications/' => array(
|
||||
|
|
|
@ -34,6 +34,7 @@ final class PhabricatorApplicationSettings extends PhabricatorApplication {
|
|||
return array(
|
||||
'/settings/' => array(
|
||||
'(?:page/(?P<page>[^/]+)/)?' => 'PhabricatorUserSettingsController',
|
||||
'adjust/' => 'PhabricatorPeopleAdjustSettingController',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorPeopleAdjustSettingController
|
||||
extends PhabricatorPeopleController {
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$prefs = $user->loadPreferences();
|
||||
$prefs->setPreference(
|
||||
$request->getStr('key'),
|
||||
$request->getStr('value'));
|
||||
$prefs->save();
|
||||
|
||||
return id(new AphrontAjaxResponse())->setContent(array());
|
||||
}
|
||||
}
|
|
@ -33,6 +33,9 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO {
|
|||
const PREFERENCE_DIFFUSION_VIEW = 'diffusion-view';
|
||||
const PREFERENCE_DIFFUSION_SYMBOLS = 'diffusion-symbols';
|
||||
|
||||
const PREFERENCE_NAV_COLLAPSED = 'nav-collapsed';
|
||||
const PREFERENCE_NAV_WIDTH = 'nav-width';
|
||||
|
||||
protected $userPHID;
|
||||
protected $preferences = array();
|
||||
|
||||
|
|
|
@ -53,5 +53,9 @@ final class PhabricatorApplicationPhriction extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getCoreApplicationOrder() {
|
||||
return 0.140;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,24 @@ final class AphrontSideNavFilterView extends AphrontView {
|
|||
private $selectedFilter = false;
|
||||
private $flexNav;
|
||||
private $flexible;
|
||||
private $showApplicationMenu;
|
||||
private $user;
|
||||
private $currentApplication;
|
||||
|
||||
public function setCurrentApplication(PhabricatorApplication $current) {
|
||||
$this->currentApplication = $current;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUser(PhabricatorUser $user) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setShowApplicationMenu($show_application_menu) {
|
||||
$this->showApplicationMenu = $show_application_menu;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFlexNav($flex_nav) {
|
||||
$this->flexNav = $flex_nav;
|
||||
|
@ -129,6 +147,13 @@ final class AphrontSideNavFilterView extends AphrontView {
|
|||
$view = new AphrontSideNavView();
|
||||
$view->setFlexNav($this->flexNav);
|
||||
$view->setFlexible($this->flexible);
|
||||
$view->setShowApplicationMenu($this->showApplicationMenu);
|
||||
if ($this->user) {
|
||||
$view->setUser($this->user);
|
||||
}
|
||||
if ($this->currentApplication) {
|
||||
$view->setCurrentApplication($this->currentApplication);
|
||||
}
|
||||
foreach ($this->items as $item) {
|
||||
list($type, $key, $name) = $item;
|
||||
switch ($type) {
|
||||
|
|
|
@ -18,9 +18,27 @@
|
|||
|
||||
final class AphrontSideNavView extends AphrontView {
|
||||
|
||||
protected $items = array();
|
||||
protected $flexNav;
|
||||
protected $isFlexible;
|
||||
private $items = array();
|
||||
private $flexNav;
|
||||
private $isFlexible;
|
||||
private $showApplicationMenu;
|
||||
private $user;
|
||||
private $currentApplication;
|
||||
|
||||
public function setUser(PhabricatorUser $user) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setShowApplicationMenu($show_application_menu) {
|
||||
$this->showApplicationMenu = $show_application_menu;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCurrentApplication(PhabricatorApplication $current) {
|
||||
$this->currentApplication = $current;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addNavItem($item) {
|
||||
$this->items[] = $item;
|
||||
|
@ -42,20 +60,60 @@ final class AphrontSideNavView extends AphrontView {
|
|||
$view->appendChild($this->items);
|
||||
|
||||
if ($this->flexNav) {
|
||||
$user = $this->user;
|
||||
|
||||
require_celerity_resource('phabricator-nav-view-css');
|
||||
|
||||
$nav_id = celerity_generate_unique_node_id();
|
||||
$drag_id = celerity_generate_unique_node_id();
|
||||
$nav_classes = array();
|
||||
$nav_classes[] = 'phabricator-nav';
|
||||
|
||||
$app_id = celerity_generate_unique_node_id();
|
||||
$nav_id = null;
|
||||
$drag_id = null;
|
||||
$content_id = celerity_generate_unique_node_id();
|
||||
$collapse_id = null;
|
||||
$expand_id = null;
|
||||
$main_id = celerity_generate_unique_node_id();
|
||||
|
||||
$apps = $this->renderApplications();
|
||||
|
||||
$key = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED;
|
||||
if ($user->loadPreferences()->getPreference($key)) {
|
||||
$nav_classes[] = 'phabricator-nav-app-collapsed';
|
||||
}
|
||||
|
||||
$collapse_id = celerity_generate_unique_node_id();
|
||||
$expand_id = celerity_generate_unique_node_id();
|
||||
|
||||
$collapse_button = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '#',
|
||||
'class' => 'phabricator-nav-app-button-collapse',
|
||||
'id' => $collapse_id,
|
||||
),
|
||||
'« Collapse');
|
||||
$expand_button = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '#',
|
||||
'class' => 'phabricator-nav-app-button-expand',
|
||||
'id' => $expand_id,
|
||||
),
|
||||
'»');
|
||||
|
||||
$app_menu = phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-nav-col phabricator-nav-app',
|
||||
'id' => $app_id,
|
||||
),
|
||||
$apps->render()).
|
||||
$expand_button.
|
||||
$collapse_button;
|
||||
|
||||
if ($this->flexible) {
|
||||
Javelin::initBehavior(
|
||||
'phabricator-nav',
|
||||
array(
|
||||
'navID' => $nav_id,
|
||||
'dragID' => $drag_id,
|
||||
'contentID' => $content_id,
|
||||
));
|
||||
$drag_id = celerity_generate_unique_node_id();
|
||||
$flex_bar = phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
|
@ -67,24 +125,66 @@ final class AphrontSideNavView extends AphrontView {
|
|||
$flex_bar = null;
|
||||
}
|
||||
|
||||
return
|
||||
'<div class="phabricator-nav">'.
|
||||
phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-nav-col',
|
||||
'id' => $nav_id,
|
||||
),
|
||||
$view->render()).
|
||||
$flex_bar.
|
||||
phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-nav-content',
|
||||
'id' => $content_id,
|
||||
),
|
||||
$this->renderChildren()).
|
||||
$nav_menu = null;
|
||||
if ($this->items) {
|
||||
$local_id = celerity_generate_unique_node_id();
|
||||
$nav_classes[] = 'has-local-nav';
|
||||
$local_menu = phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-nav-col phabricator-nav-local',
|
||||
'id' => $local_id,
|
||||
),
|
||||
$view->render());
|
||||
}
|
||||
|
||||
Javelin::initBehavior(
|
||||
'phabricator-nav',
|
||||
array(
|
||||
'mainID' => $main_id,
|
||||
'appID' => $app_id,
|
||||
'localID' => $local_id,
|
||||
'dragID' => $drag_id,
|
||||
'contentID' => $content_id,
|
||||
'collapseID' => $collapse_id,
|
||||
'expandID' => $expand_id,
|
||||
'collapseKey' => $key,
|
||||
));
|
||||
|
||||
$header_part =
|
||||
'<div class="phabricator-nav-head">'.
|
||||
'<div class="phabricator-nav-head-tablet">'.
|
||||
'<a href="#" class="nav-button nav-button-w nav-button-menu" '.
|
||||
'id="tablet-menu1"></a>'.
|
||||
'<a href="#" class="nav-button nav-button-e nav-button-content '.
|
||||
'nav-button-selected" id="tablet-menu2"></a>'.
|
||||
'</div>'.
|
||||
'<div class="phabricator-nav-head-phone">'.
|
||||
'<a href="#" class="nav-button nav-button-w nav-button-apps" '.
|
||||
'id="phone-menu1"></button>'.
|
||||
'<a href="#" class="nav-button nav-button-menu" '.
|
||||
'id="phone-menu2"></button>'.
|
||||
'<a href="#" class="nav-button nav-button-e nav-button-content '.
|
||||
'nav-button-selected" id="phone-menu3"></button>'.
|
||||
'</div>'.
|
||||
'</div>';
|
||||
|
||||
return $header_part.phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => implode(' ', $nav_classes),
|
||||
'id' => $main_id,
|
||||
),
|
||||
$app_menu.
|
||||
$local_menu.
|
||||
$flex_bar.
|
||||
phutil_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-nav-content',
|
||||
'id' => $content_id,
|
||||
),
|
||||
$this->renderChildren()));
|
||||
} else {
|
||||
|
||||
require_celerity_resource('aphront-side-nav-view-css');
|
||||
|
@ -103,4 +203,52 @@ final class AphrontSideNavView extends AphrontView {
|
|||
}
|
||||
}
|
||||
|
||||
private function renderApplications() {
|
||||
$core = array();
|
||||
$current = $this->currentApplication;
|
||||
|
||||
$meta = null;
|
||||
|
||||
$applications = PhabricatorApplication::getAllInstalledApplications();
|
||||
foreach ($applications as $application) {
|
||||
if ($application instanceof PhabricatorApplicationApplications) {
|
||||
$meta = $application;
|
||||
continue;
|
||||
}
|
||||
if ($application->getCoreApplicationOrder() !== null) {
|
||||
$core[] = $application;
|
||||
}
|
||||
}
|
||||
|
||||
$core = msort($core, 'getCoreApplicationOrder');
|
||||
if ($meta) {
|
||||
$core[] = $meta;
|
||||
}
|
||||
$core = mpull($core, null, 'getPHID');
|
||||
|
||||
if ($current && empty($core[$current->getPHID()])) {
|
||||
array_unshift($core, $this->current);
|
||||
}
|
||||
|
||||
$apps = array();
|
||||
foreach ($core as $phid => $application) {
|
||||
$classes = array();
|
||||
$classes[] = 'phabricator-nav-app-item';
|
||||
if ($current && $phid == $current->getPHID()) {
|
||||
$classes[] = 'phabricator-nav-app-item-selected';
|
||||
}
|
||||
|
||||
$apps[] = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'class' => implode(' ', $classes),
|
||||
'href' => $application->getBaseURI(),
|
||||
'style' => 'background-image: url('.$application->getIconURI().')',
|
||||
),
|
||||
phutil_escape_html($application->getName()));
|
||||
}
|
||||
|
||||
return id(new AphrontNullView())->appendChild($apps);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,11 +11,9 @@
|
|||
top: 44px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 179px;
|
||||
|
||||
background: #e3e3e3;
|
||||
border-right: 1px solid #999c9e;
|
||||
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.20);
|
||||
box-shadow: inset -3px 0 4px rgba(0, 0, 0, 0.05);
|
||||
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
@ -23,6 +21,21 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.phabricator-nav-app {
|
||||
width: 149px;
|
||||
background: #d2d2d2;
|
||||
}
|
||||
|
||||
.phabricator-nav-local {
|
||||
width: 179px;
|
||||
background: #ececec;
|
||||
}
|
||||
|
||||
.device-tablet .phabricator-nav-local,
|
||||
.device-phone .phabricator-nav-local {
|
||||
width: 299px;
|
||||
}
|
||||
|
||||
.phabricator-nav-drag {
|
||||
position: fixed;
|
||||
top: 44px;
|
||||
|
@ -37,7 +50,7 @@
|
|||
border-width: 0 1px 0 1px;
|
||||
border-color: #fff #999c9e #fff #999c9e;
|
||||
|
||||
box-shadow: inset -1px 0px 2px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: inset -1px 0px 1px rgba(0, 0, 0, 0.15);
|
||||
|
||||
background-image: url(/rsrc/image/divot.png);
|
||||
background-position: center;
|
||||
|
@ -54,10 +67,30 @@
|
|||
display: block;
|
||||
}
|
||||
|
||||
.phabricator-nav-content {
|
||||
margin-left: 180px;
|
||||
.phabricator-nav-local {
|
||||
left: 150px;
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-nav-app-collapsed .phabricator-nav-local {
|
||||
left: 34px;
|
||||
}
|
||||
|
||||
.phabricator-nav-content {
|
||||
margin-left: 150px;
|
||||
}
|
||||
|
||||
.has-local-nav .phabricator-nav-content {
|
||||
margin-left: 330px;
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-nav-app-collapsed .phabricator-nav-content {
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
.device-desktop .has-local-nav.phabricator-nav-app-collapsed
|
||||
.phabricator-nav-content {
|
||||
margin-left: 212px;
|
||||
}
|
||||
|
||||
.phabricator-nav-col span {
|
||||
display: block;
|
||||
|
@ -74,10 +107,183 @@
|
|||
}
|
||||
|
||||
.phabricator-nav-col a.aphront-side-nav-selected {
|
||||
background: #a1bbe5;
|
||||
background-color: #a1bbe5;
|
||||
}
|
||||
|
||||
.phabricator-nav-col a:hover {
|
||||
background: #3875d7;
|
||||
.device-desktop .phabricator-nav-col a:hover {
|
||||
background-color: #3875d7;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
a.phabricator-nav-app-item {
|
||||
color: #222222;
|
||||
font-weight: normal;
|
||||
padding: 4px;
|
||||
padding-left: 37px;
|
||||
vertical-align: middle;
|
||||
line-height: 25px;
|
||||
|
||||
border-width: 1px 0px;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
|
||||
background-size: auto 25px;
|
||||
background-position: 4px 4px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
a.phabricator-nav-app-item-selected {
|
||||
background-color: #f3f3f3;
|
||||
box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.20);
|
||||
border-color: #b0b0b0;
|
||||
}
|
||||
|
||||
|
||||
a.phabricator-nav-app-button-expand,
|
||||
a.phabricator-nav-app-button-collapse {
|
||||
position: fixed;
|
||||
display: none;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
padding: 4px;
|
||||
z-index: 2;
|
||||
background: #d9d9d9;
|
||||
line-height: 14px;
|
||||
border-top: 1px solid #a9a9a9;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
box-shadow: inset -1px -1px 3px rgba(0, 0, 0, 0.1);
|
||||
color: #696969;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.phabricator-nav-app-button-collapse {
|
||||
width: 141px;
|
||||
}
|
||||
|
||||
.phabricator-nav-app-button-expand {
|
||||
width: 25px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-nav-app-button-expand,
|
||||
.device-desktop .phabricator-nav-app-button-collapse {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-nav-app-collapsed .phabricator-nav-app {
|
||||
width: 33px;
|
||||
}
|
||||
|
||||
.phabricator-nav-app-collapsed .phabricator-nav-app-button-collapse {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-nav-app-collapsed
|
||||
.phabricator-nav-app-button-expand {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-nav-head {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.device-tablet .phabricator-nav-col,
|
||||
.device-phone .phabricator-nav-col {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.device-tablet .phabricator-nav-app,
|
||||
.device-phone .phabricator-nav-app {
|
||||
left: -450px;
|
||||
}
|
||||
|
||||
.device-tablet .phabricator-nav-local,
|
||||
.device-phone .phabricator-nav-local {
|
||||
left: -300px;
|
||||
}
|
||||
|
||||
.device-phone .phabricator-nav-head-tablet {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.device-tablet .phabricator-nav-head-phone {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.device-tablet .phabricator-nav,
|
||||
.device-phone .phabricator-nav {
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.device-tablet .phabricator-nav-content,
|
||||
.device-phone .phabricator-nav-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.device-tablet .phabricator-nav-content,
|
||||
.device-phone .phabricator-nav-content {
|
||||
margin-left: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.phabricator-nav-head {
|
||||
display: block;
|
||||
position: relative;
|
||||
height: 43px;
|
||||
background: #e6e6e6;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid #9d9d9d;
|
||||
text-align: center;
|
||||
box-shadow: inset 0 0px 3px rgba(0, 0, 0, 0.30),
|
||||
0px 1px 2px rgba(0, 0, 0, 0.10);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
background-color: #f3f3f3;
|
||||
height: 32px;
|
||||
width: 40px;
|
||||
margin: 5px 0px;
|
||||
display: inline-block;
|
||||
border: 1px solid #999999;
|
||||
box-shadow: inset -1px -1px 3px rgba(0, 0, 0, 0.10);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.nav-button-selected {
|
||||
background-color: #c9c9c9;
|
||||
box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.20);
|
||||
}
|
||||
|
||||
.nav-button + .nav-button {
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.nav-button-w {
|
||||
border-radius: 6px 0 0 6px;
|
||||
}
|
||||
|
||||
.nav-button-e {
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
|
||||
.nav-button-apps {
|
||||
background-image: url(/rsrc/image/button_apps.png);
|
||||
background-size: 24px auto;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.nav-button-menu {
|
||||
background-image: url(/rsrc/image/button_menu.png);
|
||||
background-size: 24px auto;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.nav-button-content {
|
||||
background-image: url(/rsrc/image/button_content.png);
|
||||
background-size: 24px auto;
|
||||
background-position: center;
|
||||
}
|
||||
|
|
BIN
webroot/rsrc/image/app/app_applications.png
Normal file
BIN
webroot/rsrc/image/app/app_applications.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 463 B |
BIN
webroot/rsrc/image/button_apps.png
Normal file
BIN
webroot/rsrc/image/button_apps.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 463 B |
BIN
webroot/rsrc/image/button_content.png
Executable file
BIN
webroot/rsrc/image/button_content.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 326 B |
BIN
webroot/rsrc/image/button_menu.png
Executable file
BIN
webroot/rsrc/image/button_menu.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 147 B |
|
@ -31,6 +31,8 @@ JX.behavior('device', function(config) {
|
|||
JX.DOM.alterClass(e, 'device-phone', (device == 'phone'));
|
||||
JX.DOM.alterClass(e, 'device-tablet', (device == 'tablet'));
|
||||
JX.DOM.alterClass(e, 'device-desktop', (device == 'desktop'));
|
||||
|
||||
JX.Stratcom.invoke('phabricator-device-change', device);
|
||||
}
|
||||
|
||||
JX.Stratcom.listen('resize', null, onresize);
|
||||
|
|
|
@ -5,93 +5,202 @@
|
|||
* javelin-dom
|
||||
* javelin-magical-init
|
||||
* javelin-vector
|
||||
* javelin-request
|
||||
* javelin-util
|
||||
* javelin-fx
|
||||
* @javelin
|
||||
*/
|
||||
|
||||
JX.behavior('phabricator-nav', function(config) {
|
||||
|
||||
var dragging;
|
||||
var track;
|
||||
|
||||
var nav = JX.$(config.navID);
|
||||
var drag = JX.$(config.dragID);
|
||||
var app = JX.$(config.appID);
|
||||
var content = JX.$(config.contentID);
|
||||
var local = config.localID ? JX.$(config.localID) : null;
|
||||
|
||||
JX.enableDispatch(document.body, 'mousemove');
|
||||
|
||||
JX.DOM.listen(drag, 'mousedown', null, function(e) {
|
||||
dragging = JX.$V(e);
|
||||
// - Sliding Menu Animations ---------------------------------------------------
|
||||
|
||||
// Show the "col-resize" cursor on the whole document while we're
|
||||
// dragging, since the mouse will slip off the actual bar fairly often and
|
||||
// we don't want it to flicker.
|
||||
JX.DOM.alterClass(document.body, 'jx-drag-col', true);
|
||||
var animations = [];
|
||||
function slide_menu(position) {
|
||||
var app_width = 150;
|
||||
var local_width = local ? 300 : 0;
|
||||
|
||||
track = [
|
||||
{
|
||||
element: nav,
|
||||
parameter: 'width',
|
||||
start: JX.Vector.getDim(nav).x,
|
||||
scale: 1,
|
||||
var shifts = {
|
||||
0: 0,
|
||||
1: app_width - 10,
|
||||
2: app_width + local_width
|
||||
};
|
||||
var shift = shifts[position];
|
||||
|
||||
width: JX.Vector.getDim(nav).x,
|
||||
minWidth: 150,
|
||||
minScale: 1
|
||||
},
|
||||
{
|
||||
element: drag,
|
||||
parameter: 'left',
|
||||
start: JX.$V(drag).x,
|
||||
scale: 1
|
||||
},
|
||||
{
|
||||
element: content,
|
||||
parameter: 'marginLeft',
|
||||
start: parseInt(getComputedStyle(content).marginLeft, 10),
|
||||
scale: 1,
|
||||
while (animations.length) {
|
||||
animations.pop().stop();
|
||||
}
|
||||
animations.push(build_animation(app, -shift));
|
||||
local && animations.push(build_animation(local, -shift + app_width));
|
||||
animations.push(build_animation(content, -shift + app_width + local_width));
|
||||
|
||||
width: JX.Vector.getDim(content).x,
|
||||
minWidth: 300,
|
||||
minScale: -1
|
||||
select_button(position);
|
||||
}
|
||||
|
||||
function build_animation(element, target) {
|
||||
return new JX.FX(element)
|
||||
.setDuration(100)
|
||||
.start({left: [JX.$V(element).x, target]});
|
||||
}
|
||||
|
||||
|
||||
// - Sliding Menu Buttons ------------------------------------------------------
|
||||
|
||||
var button_positions = {
|
||||
0: [JX.$('phone-menu1'), JX.$('tablet-menu1')],
|
||||
1: [JX.$('phone-menu2')],
|
||||
2: [JX.$('phone-menu3'), JX.$('tablet-menu2')]
|
||||
};
|
||||
|
||||
for (var k in button_positions) {
|
||||
for (var ii = 0; ii < button_positions[k].length; ii++) {
|
||||
var onclick = function(p, e) {
|
||||
e.kill();
|
||||
slide_menu(p);
|
||||
};
|
||||
onclick = JX.bind(null, onclick, k);
|
||||
JX.DOM.listen(
|
||||
button_positions[k][ii],
|
||||
['touchstart', 'mousedown'],
|
||||
null,
|
||||
onclick);
|
||||
}
|
||||
}
|
||||
|
||||
function select_button(position) {
|
||||
for (var k in button_positions) {
|
||||
for (var ii = 0; ii < button_positions[k].length; ii++) {
|
||||
JX.DOM.alterClass(
|
||||
button_positions[k][ii],
|
||||
'nav-button-selected',
|
||||
(k == position));
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
e.kill();
|
||||
|
||||
// - Application Menu Collapse/Expand ------------------------------------------
|
||||
|
||||
function collapse(state, skip_save) {
|
||||
JX.DOM.alterClass(
|
||||
JX.$(config.mainID),
|
||||
'phabricator-nav-app-collapsed',
|
||||
state);
|
||||
|
||||
if (!skip_save) {
|
||||
new JX.Request('/settings/adjust/', JX.bag)
|
||||
.setData({key: config.collapseKey, value: state ? 1 : ''})
|
||||
.send();
|
||||
}
|
||||
}
|
||||
|
||||
JX.DOM.listen(JX.$(config.collapseID), 'click', null, function(e) {
|
||||
collapse(true);
|
||||
});
|
||||
|
||||
JX.Stratcom.listen('mousemove', null, function(e) {
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
var dx = JX.$V(e).x - dragging.x;
|
||||
var panel;
|
||||
|
||||
for (var k = 0; k < track.length; k++) {
|
||||
panel = track[k];
|
||||
if (!panel.minWidth) {
|
||||
continue;
|
||||
}
|
||||
var new_width = panel.width + (dx * panel.minScale);
|
||||
if (new_width < panel.minWidth) {
|
||||
dx = (panel.minWidth - panel.width) * panel.minScale;
|
||||
}
|
||||
}
|
||||
|
||||
for (var k = 0; k < track.length; k++) {
|
||||
panel = track[k];
|
||||
var v = (panel.start + (dx * panel.scale));
|
||||
panel.element.style[panel.parameter] = v + 'px';
|
||||
}
|
||||
JX.DOM.listen(JX.$(config.expandID), 'click', null, function(e) {
|
||||
collapse(false);
|
||||
});
|
||||
|
||||
JX.Stratcom.listen('mouseup', null, function(e) {
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
JX.DOM.alterClass(document.body, 'jx-drag-col', false);
|
||||
dragging = false;
|
||||
// - Flexible Navigation Column ------------------------------------------------
|
||||
|
||||
if (config.dragID) {
|
||||
var dragging;
|
||||
var track;
|
||||
|
||||
var drag = JX.$(config.dragID);
|
||||
JX.enableDispatch(document.body, 'mousemove');
|
||||
|
||||
JX.DOM.listen(drag, 'mousedown', null, function(e) {
|
||||
dragging = JX.$V(e);
|
||||
|
||||
// Show the "col-resize" cursor on the whole document while we're
|
||||
// dragging, since the mouse will slip off the actual bar fairly often and
|
||||
// we don't want it to flicker.
|
||||
JX.DOM.alterClass(document.body, 'jx-drag-col', true);
|
||||
|
||||
track = [
|
||||
{
|
||||
element: local,
|
||||
parameter: 'width',
|
||||
start: JX.Vector.getDim(local).x,
|
||||
scale: 1,
|
||||
|
||||
width: JX.Vector.getDim(local).x,
|
||||
minWidth: 150,
|
||||
minScale: 1
|
||||
},
|
||||
{
|
||||
element: drag,
|
||||
parameter: 'left',
|
||||
start: JX.$V(drag).x,
|
||||
scale: 1
|
||||
},
|
||||
{
|
||||
element: content,
|
||||
parameter: 'marginLeft',
|
||||
start: parseInt(getComputedStyle(content).marginLeft, 10),
|
||||
scale: 1,
|
||||
|
||||
width: JX.Vector.getDim(content).x,
|
||||
minWidth: 300,
|
||||
minScale: -1
|
||||
}
|
||||
];
|
||||
|
||||
e.kill();
|
||||
});
|
||||
|
||||
JX.Stratcom.listen('mousemove', null, function(e) {
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
var dx = JX.$V(e).x - dragging.x;
|
||||
var panel;
|
||||
|
||||
for (var k = 0; k < track.length; k++) {
|
||||
panel = track[k];
|
||||
if (!panel.minWidth) {
|
||||
continue;
|
||||
}
|
||||
var new_width = panel.width + (dx * panel.minScale);
|
||||
if (new_width < panel.minWidth) {
|
||||
dx = (panel.minWidth - panel.width) * panel.minScale;
|
||||
}
|
||||
}
|
||||
|
||||
for (var k = 0; k < track.length; k++) {
|
||||
panel = track[k];
|
||||
var v = (panel.start + (dx * panel.scale));
|
||||
panel.element.style[panel.parameter] = v + 'px';
|
||||
}
|
||||
});
|
||||
|
||||
JX.Stratcom.listen('mouseup', null, function(e) {
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
JX.DOM.alterClass(document.body, 'jx-drag-col', false);
|
||||
dragging = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// - Navigation Reset ----------------------------------------------------------
|
||||
|
||||
JX.Stratcom.listen('phabricator-device-change', null, function(device) {
|
||||
app.style.left = '';
|
||||
local && (local.style.left = '');
|
||||
content.style.left = '';
|
||||
|
||||
select_button(2);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
JX.behavior('toggle-class', function() {
|
||||
JX.Stratcom.listen(
|
||||
'click',
|
||||
['touchstart', 'mousedown'],
|
||||
'jx-toggle-class',
|
||||
function(e) {
|
||||
e.kill();
|
||||
|
|
Loading…
Reference in a new issue