diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 840a4c1455..412e0e63b2 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -35,6 +35,13 @@ celerity_register_resource_map(array( 'disk' => '/rsrc/image/credit_cards.png', 'type' => 'png', ), + '/rsrc/image/divot.png' => + array( + 'hash' => '3be267bd11ea375bf68e808893718e0e', + 'uri' => '/res/3be267bd/rsrc/image/divot.png', + 'disk' => '/rsrc/image/divot.png', + 'type' => 'png', + ), '/rsrc/image/glyph_sprite.png' => array( 'hash' => '0a1ea7c048be9f0b76ab2c807a9a1c0d', @@ -1402,6 +1409,20 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/application/core/behavior-keyboard-shortcuts.js', ), + 'javelin-behavior-phabricator-nav' => + array( + 'uri' => '/res/adaae8ae/rsrc/js/application/core/behavior-phabricator-nav.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-stratcom', + 2 => 'javelin-dom', + 3 => 'javelin-magical-init', + 4 => 'javelin-vector', + ), + 'disk' => '/rsrc/js/application/core/behavior-phabricator-nav.js', + ), 'javelin-behavior-phabricator-notification-example' => array( 'uri' => '/res/df97e4b3/rsrc/js/application/uiexample/notification-example.js', @@ -2285,6 +2306,15 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/application/core/DropdownMenuItem.js', ), + 'phabricator-nav-view-css' => + array( + 'uri' => '/res/3443576d/rsrc/css/aphront/phabricator-nav-view.css', + 'type' => 'css', + 'requires' => + array( + ), + 'disk' => '/rsrc/css/aphront/phabricator-nav-view.css', + ), 'phabricator-notification' => array( 'uri' => '/res/cacd79f1/rsrc/js/application/core/Notification.js', diff --git a/src/view/layout/AphrontSideNavFilterView.php b/src/view/layout/AphrontSideNavFilterView.php index aa36c9a450..c7f247599a 100644 --- a/src/view/layout/AphrontSideNavFilterView.php +++ b/src/view/layout/AphrontSideNavFilterView.php @@ -40,6 +40,18 @@ final class AphrontSideNavFilterView extends AphrontView { private $items = array(); private $baseURI; private $selectedFilter = false; + private $flexNav; + private $flexible; + + public function setFlexNav($flex_nav) { + $this->flexNav = $flex_nav; + return $this; + } + + public function setFlexible($flexible) { + $this->flexible = $flexible; + return $this; + } public function addFilter($key, $name, $uri = null, $relative = false) { $this->items[] = array( @@ -102,6 +114,8 @@ final class AphrontSideNavFilterView extends AphrontView { } $view = new AphrontSideNavView(); + $view->setFlexNav($this->flexNav); + $view->setFlexible($this->flexible); foreach ($this->items as $item) { list($type, $key, $name) = $item; switch ($type) { diff --git a/src/view/layout/AphrontSideNavView.php b/src/view/layout/AphrontSideNavView.php index 6a2d52573a..6a43eee791 100644 --- a/src/view/layout/AphrontSideNavView.php +++ b/src/view/layout/AphrontSideNavView.php @@ -1,7 +1,7 @@ items[] = $item; return $this; } + public function setFlexNav($flex) { + $this->flexNav = $flex; + return $this; + } + + public function setFlexible($flexible) { + $this->flexible = $flexible; + return $this; + } + public function render() { $view = new AphrontNullView(); $view->appendChild($this->items); - require_celerity_resource('aphront-side-nav-view-css'); + if ($this->flexNav) { + require_celerity_resource('phabricator-nav-view-css'); - return - ''. - ''. - ''. - ''. - ''. - '
'. - $view->render(). - ''. - $this->renderChildren(). - '
'; + $nav_id = celerity_generate_unique_node_id(); + $drag_id = celerity_generate_unique_node_id(); + $content_id = celerity_generate_unique_node_id(); + + if ($this->flexible) { + Javelin::initBehavior( + 'phabricator-nav', + array( + 'navID' => $nav_id, + 'dragID' => $drag_id, + 'contentID' => $content_id, + )); + $flex_bar = phutil_render_tag( + 'div', + array( + 'class' => 'phabricator-nav-drag', + 'id' => $drag_id, + ), + ''); + } else { + $flex_bar = null; + } + + return + '
'. + 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()). + '
'; + } else { + + require_celerity_resource('aphront-side-nav-view-css'); + + return + ''. + ''. + ''. + ''. + ''. + '
'. + $view->render(). + ''. + $this->renderChildren(). + '
'; + } } } diff --git a/webroot/rsrc/css/aphront/phabricator-nav-view.css b/webroot/rsrc/css/aphront/phabricator-nav-view.css new file mode 100644 index 0000000000..fecb298970 --- /dev/null +++ b/webroot/rsrc/css/aphront/phabricator-nav-view.css @@ -0,0 +1,83 @@ +/** + * @provides phabricator-nav-view-css + */ + +.jx-drag-col { + cursor: col-resize; +} + +.phabricator-nav-col { + position: fixed; + 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); + + overflow-y: auto; + overflow-x: hidden; + + white-space: nowrap; +} + +.phabricator-nav-drag { + position: fixed; + top: 44px; + left: 177px; + width: 7px; + bottom: 0; + z-index: 5; + + cursor: col-resize; + background: #f5f5f5; + border-style: solid; + border-width: 0 1px 0 1px; + border-color: #fff #999c9e #fff #999c9e; + + box-shadow: inset -1px 0px 2px rgba(0, 0, 0, 0.15); + + background-image: url(/rsrc/image/divot.png); + background-position: center; + background-repeat: no-repeat; +} + +.device-tablet .phabricator-nav-drag, +.device-phone .phabricator-nav-drag { + display: none; +} + +.phabricator-nav-col a, +.phabricator-nav-col span { + display: block; +} + +.phabricator-nav-content { + margin-left: 180px; +} + + +.phabricator-nav-col span { + display: block; + font-weight: bold; + padding: 6px 6px 6px 12px; + color: #222222; +} + +.phabricator-nav-col a { + display: block; + padding: 3px 6px 3px 24px; + font-weight: bold; + text-decoration: none; +} + +.phabricator-nav-col a.aphront-side-nav-selected { + background: #a1bbe5; +} + +.phabricator-nav-col a:hover { + background: #3875d7; + color: #ffffff; +} diff --git a/webroot/rsrc/image/divot.png b/webroot/rsrc/image/divot.png new file mode 100644 index 0000000000..d3c885483e Binary files /dev/null and b/webroot/rsrc/image/divot.png differ diff --git a/webroot/rsrc/js/application/core/behavior-phabricator-nav.js b/webroot/rsrc/js/application/core/behavior-phabricator-nav.js new file mode 100644 index 0000000000..c1c182f122 --- /dev/null +++ b/webroot/rsrc/js/application/core/behavior-phabricator-nav.js @@ -0,0 +1,97 @@ +/** + * @provides javelin-behavior-phabricator-nav + * @requires javelin-behavior + * javelin-stratcom + * javelin-dom + * javelin-magical-init + * javelin-vector + * @javelin + */ + +JX.behavior('phabricator-nav', function(config) { + + var dragging; + var track; + + var nav = JX.$(config.navID); + var drag = JX.$(config.dragID); + var content = JX.$(config.contentID); + + 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: nav, + parameter: 'width', + start: JX.Vector.getDim(nav).x, + scale: 1, + + 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, + + 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; + }); + +});