diff --git a/scripts/celerity_mapper.php b/scripts/celerity_mapper.php index 5febbfee4d..ff32a7a967 100755 --- a/scripts/celerity_mapper.php +++ b/scripts/celerity_mapper.php @@ -22,8 +22,6 @@ $package_spec = array( 'javelin-typeahead-preloaded-source', 'javelin-typeahead-ondemand-source', 'javelin-tokenizer', - 'javelin-fx', - 'javelin-color', ), 'core.pkg.js' => array( 'javelin-behavior-aphront-basic-tokenizer', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7345a8c7a3..377dae0d33 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -656,6 +656,8 @@ phutil_register_library_map(array( 'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php', 'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php', 'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php', + 'PhabricatorCrumbView' => 'view/layout/PhabricatorCrumbView.php', + 'PhabricatorCrumbsView' => 'view/layout/PhabricatorCrumbsView.php', 'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php', 'PhabricatorDaemon' => 'infrastructure/daemon/PhabricatorDaemon.php', 'PhabricatorDaemonCombinedLogController' => 'applications/daemon/controller/PhabricatorDaemonCombinedLogController.php', @@ -1905,6 +1907,8 @@ phutil_register_library_map(array( 'PhabricatorCountdownEditController' => 'PhabricatorCountdownController', 'PhabricatorCountdownListController' => 'PhabricatorCountdownController', 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', + 'PhabricatorCrumbView' => 'AphrontView', + 'PhabricatorCrumbsView' => 'AphrontView', 'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery', 'PhabricatorDaemon' => 'PhutilDaemon', 'PhabricatorDaemonCombinedLogController' => 'PhabricatorDaemonController', diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index b53c8c22ea..48ab19e4fe 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -247,4 +247,28 @@ abstract class PhabricatorController extends AphrontController { return null; } + protected function buildApplicationCrumbs() { + + $crumbs = array(); + + $application = $this->getCurrentApplication(); + if ($application) { + $sprite = $application->getAutospriteName(); + if (!$sprite) { + $sprite = 'default'; + } + + $crumbs[] = id(new PhabricatorCrumbView()) + ->setHref($this->getApplicationURI()) + ->setIcon('temporary-icon-apps'); + } + + $view = new PhabricatorCrumbsView(); + foreach ($crumbs as $crumb) { + $view->addCrumb($crumb); + } + + return $view; + } + } diff --git a/src/applications/directory/controller/PhabricatorDirectoryMainController.php b/src/applications/directory/controller/PhabricatorDirectoryMainController.php index 4ec012006d..1aa2a86819 100644 --- a/src/applications/directory/controller/PhabricatorDirectoryMainController.php +++ b/src/applications/directory/controller/PhabricatorDirectoryMainController.php @@ -496,7 +496,7 @@ final class PhabricatorDirectoryMainController 'Share Files'); $nav_buttons[] = array( 'Create Paste', - '/paste/', + '/paste/create/', 'create-paste', 'Share Text'); diff --git a/src/applications/paste/application/PhabricatorApplicationPaste.php b/src/applications/paste/application/PhabricatorApplicationPaste.php index d38b99fe1a..87cb9d7980 100644 --- a/src/applications/paste/application/PhabricatorApplicationPaste.php +++ b/src/applications/paste/application/PhabricatorApplicationPaste.php @@ -22,8 +22,9 @@ final class PhabricatorApplicationPaste extends PhabricatorApplication { return array( '/P(?P[1-9]\d*)' => 'PhabricatorPasteViewController', '/paste/' => array( - '' => 'PhabricatorPasteEditController', - 'edit/(?P[1-9]\d*)/' => 'PhabricatorPasteEditController', + '' => 'PhabricatorPasteListController', + 'create/' => 'PhabricatorPasteEditController', + 'edit/(?P[1-9]\d*)/' => 'PhabricatorPasteEditController', 'filter/(?P\w+)/' => 'PhabricatorPasteListController', ), ); diff --git a/src/applications/paste/controller/PhabricatorPasteController.php b/src/applications/paste/controller/PhabricatorPasteController.php index 8237f7c694..4063282aa0 100644 --- a/src/applications/paste/controller/PhabricatorPasteController.php +++ b/src/applications/paste/controller/PhabricatorPasteController.php @@ -2,37 +2,41 @@ abstract class PhabricatorPasteController extends PhabricatorController { - public function buildSideNavView(PhabricatorPaste $paste = null) { + public function buildSideNavView($filter = null, $for_app = false) { $user = $this->getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI('filter/'))); - if ($paste) { - $nav->addFilter('paste', 'P'.$paste->getID(), '/P'.$paste->getID()); - $nav->addSpacer(); + if ($for_app) { + $nav->addFilter('', 'Create Paste', $this->getApplicationURI('/create/')); } - $nav->addLabel('Create'); - $nav->addFilter( - 'edit', - 'New Paste', - $this->getApplicationURI(), - $relative = false, - $class = ($user->isLoggedIn() ? null : 'disabled')); - - $nav->addSpacer(); - $nav->addLabel('Pastes'); + $nav->addLabel('Filters'); + $nav->addFilter('all', 'All Pastes'); if ($user->isLoggedIn()) { $nav->addFilter('my', 'My Pastes'); } - $nav->addFilter('all', 'All Pastes'); + + $nav->selectFilter($filter, 'all'); return $nav; } public function buildApplicationMenu() { - return $this->buildSideNavView(null)->getMenu(); + return $this->buildSideNavView(null, true)->getMenu(); + } + + public function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $crumbs->addAction( + id(new PhabricatorMenuItemView()) + ->setName(pht('Create Paste')) + ->setHref($this->getApplicationURI('create/')) + ->setIcon('create')); + + return $crumbs; } } diff --git a/src/applications/paste/controller/PhabricatorPasteEditController.php b/src/applications/paste/controller/PhabricatorPasteEditController.php index 4b83dc3d99..f5ff7ed17b 100644 --- a/src/applications/paste/controller/PhabricatorPasteEditController.php +++ b/src/applications/paste/controller/PhabricatorPasteEditController.php @@ -173,27 +173,35 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController { if (!$is_create) { $submit->addCancelButton($paste->getURI()); - $submit->setValue('Save Paste'); - $title = 'Edit '.$paste->getFullName(); + $submit->setValue(pht('Save Paste')); + $title = pht('Edit %s', $paste->getFullName()); + $short = pht('Edit'); } else { - $submit->setValue('Create Paste'); - $title = 'Create Paste'; + $submit->setValue(pht('Create Paste')); + $title = pht('Create Paste'); + $short = pht('Create'); } $form ->appendChild($submit); - $nav = $this->buildSideNavView(); - $nav->selectFilter('edit'); - $nav->appendChild( + $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); + if (!$is_create) { + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName('P'.$paste->getID()) + ->setHref('/P'.$paste->getID())); + } + $crumbs->addCrumb( + id(new PhabricatorCrumbView())->setName($short)); + + return $this->buildApplicationPage( array( + $crumbs, id(new PhabricatorHeaderView())->setHeader($title), $error_view, $form, - )); - - return $this->buildApplicationPage( - $nav, + ), array( 'title' => $title, 'device' => true, diff --git a/src/applications/paste/controller/PhabricatorPasteListController.php b/src/applications/paste/controller/PhabricatorPasteListController.php index 3c2d43714e..eab5750ba8 100644 --- a/src/applications/paste/controller/PhabricatorPasteListController.php +++ b/src/applications/paste/controller/PhabricatorPasteListController.php @@ -19,8 +19,8 @@ final class PhabricatorPasteListController extends PhabricatorPasteController { $query = new PhabricatorPasteQuery(); $query->setViewer($user); - $nav = $this->buildSideNavView(); - $filter = $nav->selectFilter($this->filter, 'my'); + $nav = $this->buildSideNavView($this->filter); + $filter = $nav->getSelectedFilter(); switch ($filter) { case 'my': @@ -45,6 +45,15 @@ final class PhabricatorPasteListController extends PhabricatorPasteController { $nav->appendChild($list); + $crumbs = $this + ->buildApplicationCrumbs($nav) + ->addCrumb( + id(new PhabricatorCrumbView()) + ->setName($title) + ->setHref($this->getApplicationURI('filter/'.$filter.'/'))); + + $nav->setCrumbs($crumbs); + return $this->buildApplicationPage( $nav, array( diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index ce5f577c2c..be4083e050 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -51,19 +51,20 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { $properties = $this->buildPropertyView($paste, $fork_phids); $source_code = $this->buildSourceCodeView($paste, $file); - $nav = $this->buildSideNavView($paste); - $nav->selectFilter('paste'); + $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()) + ->addCrumb( + id(new PhabricatorCrumbView()) + ->setName('P'.$paste->getID()) + ->setHref('/P'.$paste->getID())); - $nav->appendChild( + return $this->buildApplicationPage( array( + $crumbs, $header, $actions, $properties, $source_code, - )); - - return $this->buildApplicationPage( - $nav, + ), array( 'title' => $paste->getFullName(), 'device' => true, @@ -87,6 +88,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { PhabricatorPolicyCapability::CAN_EDIT); $can_fork = $user->isLoggedIn(); + $fork_uri = $this->getApplicationURI('/create/?parent='.$paste->getID()); return id(new PhabricatorActionListView()) ->setUser($user) @@ -97,7 +99,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { ->setIcon('fork') ->setDisabled(!$can_fork) ->setWorkflow(!$can_fork) - ->setHref($this->getApplicationURI('?parent='.$paste->getID()))) + ->setHref($fork_uri)) ->addAction( id(new PhabricatorActionView()) ->setName(pht('View Raw File')) diff --git a/src/view/layout/AphrontSideNavFilterView.php b/src/view/layout/AphrontSideNavFilterView.php index 0da3ecad0a..e58ea5fbee 100644 --- a/src/view/layout/AphrontSideNavFilterView.php +++ b/src/view/layout/AphrontSideNavFilterView.php @@ -29,11 +29,21 @@ final class AphrontSideNavFilterView extends AphrontView { private $user; private $active; private $menu; + private $crumbs; public function __construct() { $this->menu = new PhabricatorMenuView(); } + public function setCrumbs(PhabricatorCrumbsView $crumbs) { + $this->crumbs = $crumbs; + return $this; + } + + public function getCrumbs() { + return $this->crumbs; + } + public function setActive($active) { $this->active = $active; return $this; @@ -54,6 +64,10 @@ final class AphrontSideNavFilterView extends AphrontView { return $this; } + public function getMenuView() { + return $this->menu; + } + public function addMenuItem(PhabricatorMenuItemView $item) { $this->menu->addMenuItem($item); return $this; @@ -120,6 +134,10 @@ final class AphrontSideNavFilterView extends AphrontView { return $this->selectedFilter; } + public function getSelectedFilter() { + return $this->selectedFilter; + } + public function render() { if ($this->menu->getItems()) { if (!$this->baseURI) { @@ -187,6 +205,12 @@ final class AphrontSideNavFilterView extends AphrontView { self::renderSingleView($this->menu)); } + $crumbs = null; + if ($this->crumbs) { + $crumbs = $this->crumbs->render(); + $nav_classes[] = 'has-crumbs'; + } + Javelin::initBehavior( 'phabricator-nav', array( @@ -194,6 +218,7 @@ final class AphrontSideNavFilterView extends AphrontView { 'localID' => $local_id, 'dragID' => $drag_id, 'contentID' => $content_id, + 'menuSize' => ($crumbs ? 78 : 44), )); if ($this->active && $local_id) { @@ -204,17 +229,7 @@ final class AphrontSideNavFilterView extends AphrontView { )); } - $header_part = - '
'. - '
'. - ''. - ''. - '
'. - '
'; - - return $header_part.phutil_render_tag( + return $crumbs.phutil_render_tag( 'div', array( 'class' => implode(' ', $nav_classes), diff --git a/src/view/layout/PhabricatorCrumbView.php b/src/view/layout/PhabricatorCrumbView.php new file mode 100644 index 0000000000..20d7a888d8 --- /dev/null +++ b/src/view/layout/PhabricatorCrumbView.php @@ -0,0 +1,77 @@ +name = $name; + return $this; + } + + public function setHref($href) { + $this->href = $href; + return $this; + } + + public function setIcon($icon) { + $this->icon = $icon; + return $this; + } + + protected function canAppendChild() { + return false; + } + + public function setIsLastCrumb($is_last_crumb) { + $this->isLastCrumb = $is_last_crumb; + return $this; + } + + public function render() { + $classes = array( + 'phabricator-crumb-view', + ); + + $icon = null; + if ($this->icon) { + $classes[] = 'phabricator-crumb-has-icon'; + $icon = phutil_render_tag( + 'span', + array( + 'class' => 'phabricator-crumb-icon '.$this->icon, + ), + ''); + } + + $name = phutil_render_tag( + 'span', + array( + 'class' => 'phabricator-crumb-name', + ), + phutil_escape_html($this->name)); + + $divider = null; + if (!$this->isLastCrumb) { + $divider = phutil_render_tag( + 'span', + array( + 'class' => 'phabricator-crumb-divider', + ), + ''); + } + + return phutil_render_tag( + $this->href ? 'a' : 'span', + array( + 'href' => $this->href, + 'class' => implode(' ', $classes), + ), + $icon.$name.$divider); + } + + +} diff --git a/src/view/layout/PhabricatorCrumbsView.php b/src/view/layout/PhabricatorCrumbsView.php new file mode 100644 index 0000000000..7d4ca345c5 --- /dev/null +++ b/src/view/layout/PhabricatorCrumbsView.php @@ -0,0 +1,70 @@ +crumbs[] = $crumb; + return $this; + } + + + public function addAction(PhabricatorMenuItemView $action) { + $this->actions[] = $action; + + return $this; + } + + public function render() { + require_celerity_resource('phabricator-crumbs-view-css'); + + $action_view = null; + if ($this->actions) { + $actions = array(); + foreach ($this->actions as $action) { + $icon = null; + if ($action->getIcon()) { + $icon = phutil_render_tag( + 'span', + array( + 'class' => 'sprite-icon action-'.$action->getIcon(), + ), + ''); + } + $actions[] = phutil_render_tag( + 'a', + array( + 'href' => $action->getHref(), + 'class' => 'phabricator-crumbs-action', + ), + $icon.phutil_escape_html($action->getName())); + } + + $action_view = phutil_render_tag( + 'div', + array( + 'class' => 'phabricator-crumbs-actions', + ), + self::renderSingleView($actions)); + } + + if ($this->crumbs) { + last($this->crumbs)->setIsLastCrumb(true); + } + + return phutil_render_tag( + 'div', + array( + 'class' => 'phabricator-crumbs-view', + ), + $action_view. + self::renderSingleView($this->crumbs)); + } + +} diff --git a/src/view/layout/PhabricatorMenuItemView.php b/src/view/layout/PhabricatorMenuItemView.php index d30f02a0b1..d53079e904 100644 --- a/src/view/layout/PhabricatorMenuItemView.php +++ b/src/view/layout/PhabricatorMenuItemView.php @@ -36,7 +36,7 @@ final class PhabricatorMenuItemView extends AphrontView { } public function setKey($key) { - $this->key = $key; + $this->key = (string)$key; return $this; } diff --git a/src/view/layout/PhabricatorMenuView.php b/src/view/layout/PhabricatorMenuView.php index d2517e60be..eaaa04520c 100644 --- a/src/view/layout/PhabricatorMenuView.php +++ b/src/view/layout/PhabricatorMenuView.php @@ -49,7 +49,7 @@ final class PhabricatorMenuView extends AphrontView { } public function getItem($key) { - return idx($this->map, $key); + return idx($this->map, (string)$key); } public function getItems() { diff --git a/webroot/rsrc/css/aphront/phabricator-nav-view.css b/webroot/rsrc/css/aphront/phabricator-nav-view.css index 0988936a44..53ee74673b 100644 --- a/webroot/rsrc/css/aphront/phabricator-nav-view.css +++ b/webroot/rsrc/css/aphront/phabricator-nav-view.css @@ -19,6 +19,10 @@ z-index: 3; } +.has-crumbs .phabricator-nav-col { + top: 78px; +} + .phabricator-nav-local { width: 179px; background: #ececec; @@ -30,7 +34,7 @@ .device-tablet .phabricator-nav-local, .device-phone .phabricator-nav-local { - width: 299px; + display: none; } .phabricator-nav-drag { @@ -79,10 +83,6 @@ margin-left: 2.5em !important; } -.device-desktop .phabricator-nav-head { - display: none; -} - .device-tablet .phabricator-nav-col, .device-phone .phabricator-nav-col { position: absolute; @@ -110,55 +110,3 @@ margin-left: 0; position: relative; } - -.phabricator-nav-head { - display: block; - position: relative; - height: 43px; - background: #fafafa; - overflow: hidden; - border-bottom: 1px solid #5d5d5d; - text-align: center; - box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.10), - 0px 1px 2px rgba(0, 0, 0, 0.10); -} - -.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-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; -} diff --git a/webroot/rsrc/css/layout/phabricator-crumbs-view.css b/webroot/rsrc/css/layout/phabricator-crumbs-view.css new file mode 100644 index 0000000000..167445b7b4 --- /dev/null +++ b/webroot/rsrc/css/layout/phabricator-crumbs-view.css @@ -0,0 +1,81 @@ +/** + * @provides phabricator-crumbs-view-css + */ + +.device-phone .phabricator-crumbs-view, +.device-tablet .phabricator-crumbs-view { + display: none; +} + +.phabricator-crumbs-view { + background: #e1e5eb; + border-bottom: 1px solid #c5c9ce; + height: 33px; + overflow: hidden; + vertical-align: top; +} + +.phabricator-crumbs-view, +.phabricator-crumbs-view a.phabricator-crumb-view, +.phabricator-crumbs-view a.phabricator-crumbs-action { + color: #3d3d3d; + font-weight: bold; + text-decoration: none; + text-shadow: 0 1px 2px rgba(255, 255, 255, 0.9); +} + +.phabricator-crumb-view { + display: inline-block; + height: 33px; + line-height: 33px; + overflow: hidden; +} + +.phabricator-crumb-icon { + display: inline-block; + width: 30px; + height: 30px; + margin: 2px 2px 1px 8px; + vertical-align: top; +} + +.phabricator-crumbs-actions { + float: right; +} + +.phabricator-crumbs-action { + display: inline-block; + height: 17px; + padding: 8px 14px 8px 24px; + position: relative; +} + +.phabricator-crumbs-action .sprite-icon { + width: 14px; + height: 14px; + left: 4px; + top: 9px; + position: absolute; +} + +.phabricator-crumb-divider { + display: inline-block; + width: 9px; + height: 33px; + background-image: url(/rsrc/image/tab_divider.png); + vertical-align: top; + margin: 0 9px; +} + +.temporary-icon-apps { + background-image: url(/rsrc/image/button_apps.png); + background-position: center center; + background-repeat: no-repeat; +} + +.temporary-icon-list { + background-image: url(/rsrc/image/button_menu.png); + background-position: center center; + background-repeat: no-repeat; + background-size: 24px 15px; +} diff --git a/webroot/rsrc/image/tab_divider.png b/webroot/rsrc/image/tab_divider.png new file mode 100644 index 0000000000..7a49d55556 Binary files /dev/null and b/webroot/rsrc/image/tab_divider.png differ diff --git a/webroot/rsrc/js/application/core/behavior-phabricator-nav.js b/webroot/rsrc/js/application/core/behavior-phabricator-nav.js index 4a6279e71e..9292e9c9a7 100644 --- a/webroot/rsrc/js/application/core/behavior-phabricator-nav.js +++ b/webroot/rsrc/js/application/core/behavior-phabricator-nav.js @@ -6,8 +6,6 @@ * javelin-dom * javelin-magical-init * javelin-vector - * javelin-util - * javelin-fx * @javelin */ @@ -18,70 +16,6 @@ JX.behavior('phabricator-nav', function(config) { var main = JX.$(config.mainID); -// - Sliding Menu Animations --------------------------------------------------- - - var animations = []; - function slide_menu(position) { - var local_width = local ? 300 : 0; - - var shifts = { - 0: 0, - 1: -10, - 2: local_width - }; - var shift = shifts[position]; - - while (animations.length) { - animations.pop().stop(); - } - local && animations.push(build_animation(local, -shift)); - animations.push(build_animation(content, -shift + local_width)); - - 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.$('tablet-menu1')], - 1: [], - 2: [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)); - } - } - } - - // - Flexible Navigation Column ------------------------------------------------ if (config.dragID) { @@ -183,7 +117,7 @@ JX.behavior('phabricator-nav', function(config) { return; } - var y = Math.max(0, 44 - JX.Vector.getScroll().y); + var y = Math.max(0, config.menuSize - JX.Vector.getScroll().y); local.style.top = y + 'px'; });