1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 00:32:42 +01:00

Add a breadcrumbs element

Summary:
Add a basic breadcrumbs element, and implement it in Paste.

This needs some polish but is most of the way there.

Test Plan:
{F26443}
{F26444}
{F26445}

(This element is not visible on devices.)

Reviewers: chad

Reviewed By: chad

CC: aran, btrahan

Maniphest Tasks: T1960

Differential Revision: https://secure.phabricator.com/D4087
This commit is contained in:
epriestley 2012-12-07 13:35:17 -08:00
parent f910e38ecc
commit 1c9a6be979
18 changed files with 354 additions and 179 deletions

View file

@ -22,8 +22,6 @@ $package_spec = array(
'javelin-typeahead-preloaded-source', 'javelin-typeahead-preloaded-source',
'javelin-typeahead-ondemand-source', 'javelin-typeahead-ondemand-source',
'javelin-tokenizer', 'javelin-tokenizer',
'javelin-fx',
'javelin-color',
), ),
'core.pkg.js' => array( 'core.pkg.js' => array(
'javelin-behavior-aphront-basic-tokenizer', 'javelin-behavior-aphront-basic-tokenizer',

View file

@ -656,6 +656,8 @@ phutil_register_library_map(array(
'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php', 'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php',
'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php', 'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php',
'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php', 'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php',
'PhabricatorCrumbView' => 'view/layout/PhabricatorCrumbView.php',
'PhabricatorCrumbsView' => 'view/layout/PhabricatorCrumbsView.php',
'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php', 'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php',
'PhabricatorDaemon' => 'infrastructure/daemon/PhabricatorDaemon.php', 'PhabricatorDaemon' => 'infrastructure/daemon/PhabricatorDaemon.php',
'PhabricatorDaemonCombinedLogController' => 'applications/daemon/controller/PhabricatorDaemonCombinedLogController.php', 'PhabricatorDaemonCombinedLogController' => 'applications/daemon/controller/PhabricatorDaemonCombinedLogController.php',
@ -1905,6 +1907,8 @@ phutil_register_library_map(array(
'PhabricatorCountdownEditController' => 'PhabricatorCountdownController', 'PhabricatorCountdownEditController' => 'PhabricatorCountdownController',
'PhabricatorCountdownListController' => 'PhabricatorCountdownController', 'PhabricatorCountdownListController' => 'PhabricatorCountdownController',
'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController',
'PhabricatorCrumbView' => 'AphrontView',
'PhabricatorCrumbsView' => 'AphrontView',
'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery', 'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery',
'PhabricatorDaemon' => 'PhutilDaemon', 'PhabricatorDaemon' => 'PhutilDaemon',
'PhabricatorDaemonCombinedLogController' => 'PhabricatorDaemonController', 'PhabricatorDaemonCombinedLogController' => 'PhabricatorDaemonController',

View file

@ -247,4 +247,28 @@ abstract class PhabricatorController extends AphrontController {
return null; 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;
}
} }

View file

@ -496,7 +496,7 @@ final class PhabricatorDirectoryMainController
'Share Files'); 'Share Files');
$nav_buttons[] = array( $nav_buttons[] = array(
'Create Paste', 'Create Paste',
'/paste/', '/paste/create/',
'create-paste', 'create-paste',
'Share Text'); 'Share Text');

View file

@ -22,8 +22,9 @@ final class PhabricatorApplicationPaste extends PhabricatorApplication {
return array( return array(
'/P(?P<id>[1-9]\d*)' => 'PhabricatorPasteViewController', '/P(?P<id>[1-9]\d*)' => 'PhabricatorPasteViewController',
'/paste/' => array( '/paste/' => array(
'' => 'PhabricatorPasteEditController', '' => 'PhabricatorPasteListController',
'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorPasteEditController', 'create/' => 'PhabricatorPasteEditController',
'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorPasteEditController',
'filter/(?P<filter>\w+)/' => 'PhabricatorPasteListController', 'filter/(?P<filter>\w+)/' => 'PhabricatorPasteListController',
), ),
); );

View file

@ -2,37 +2,41 @@
abstract class PhabricatorPasteController extends PhabricatorController { abstract class PhabricatorPasteController extends PhabricatorController {
public function buildSideNavView(PhabricatorPaste $paste = null) { public function buildSideNavView($filter = null, $for_app = false) {
$user = $this->getRequest()->getUser(); $user = $this->getRequest()->getUser();
$nav = new AphrontSideNavFilterView(); $nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI('filter/'))); $nav->setBaseURI(new PhutilURI($this->getApplicationURI('filter/')));
if ($paste) { if ($for_app) {
$nav->addFilter('paste', 'P'.$paste->getID(), '/P'.$paste->getID()); $nav->addFilter('', 'Create Paste', $this->getApplicationURI('/create/'));
$nav->addSpacer();
} }
$nav->addLabel('Create'); $nav->addLabel('Filters');
$nav->addFilter( $nav->addFilter('all', 'All Pastes');
'edit',
'New Paste',
$this->getApplicationURI(),
$relative = false,
$class = ($user->isLoggedIn() ? null : 'disabled'));
$nav->addSpacer();
$nav->addLabel('Pastes');
if ($user->isLoggedIn()) { if ($user->isLoggedIn()) {
$nav->addFilter('my', 'My Pastes'); $nav->addFilter('my', 'My Pastes');
} }
$nav->addFilter('all', 'All Pastes');
$nav->selectFilter($filter, 'all');
return $nav; return $nav;
} }
public function buildApplicationMenu() { 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;
} }
} }

View file

@ -173,27 +173,35 @@ final class PhabricatorPasteEditController extends PhabricatorPasteController {
if (!$is_create) { if (!$is_create) {
$submit->addCancelButton($paste->getURI()); $submit->addCancelButton($paste->getURI());
$submit->setValue('Save Paste'); $submit->setValue(pht('Save Paste'));
$title = 'Edit '.$paste->getFullName(); $title = pht('Edit %s', $paste->getFullName());
$short = pht('Edit');
} else { } else {
$submit->setValue('Create Paste'); $submit->setValue(pht('Create Paste'));
$title = 'Create Paste'; $title = pht('Create Paste');
$short = pht('Create');
} }
$form $form
->appendChild($submit); ->appendChild($submit);
$nav = $this->buildSideNavView(); $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
$nav->selectFilter('edit'); if (!$is_create) {
$nav->appendChild( $crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('P'.$paste->getID())
->setHref('/P'.$paste->getID()));
}
$crumbs->addCrumb(
id(new PhabricatorCrumbView())->setName($short));
return $this->buildApplicationPage(
array( array(
$crumbs,
id(new PhabricatorHeaderView())->setHeader($title), id(new PhabricatorHeaderView())->setHeader($title),
$error_view, $error_view,
$form, $form,
)); ),
return $this->buildApplicationPage(
$nav,
array( array(
'title' => $title, 'title' => $title,
'device' => true, 'device' => true,

View file

@ -19,8 +19,8 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
$query = new PhabricatorPasteQuery(); $query = new PhabricatorPasteQuery();
$query->setViewer($user); $query->setViewer($user);
$nav = $this->buildSideNavView(); $nav = $this->buildSideNavView($this->filter);
$filter = $nav->selectFilter($this->filter, 'my'); $filter = $nav->getSelectedFilter();
switch ($filter) { switch ($filter) {
case 'my': case 'my':
@ -45,6 +45,15 @@ final class PhabricatorPasteListController extends PhabricatorPasteController {
$nav->appendChild($list); $nav->appendChild($list);
$crumbs = $this
->buildApplicationCrumbs($nav)
->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($this->getApplicationURI('filter/'.$filter.'/')));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage( return $this->buildApplicationPage(
$nav, $nav,
array( array(

View file

@ -51,19 +51,20 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController {
$properties = $this->buildPropertyView($paste, $fork_phids); $properties = $this->buildPropertyView($paste, $fork_phids);
$source_code = $this->buildSourceCodeView($paste, $file); $source_code = $this->buildSourceCodeView($paste, $file);
$nav = $this->buildSideNavView($paste); $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView())
$nav->selectFilter('paste'); ->addCrumb(
id(new PhabricatorCrumbView())
->setName('P'.$paste->getID())
->setHref('/P'.$paste->getID()));
$nav->appendChild( return $this->buildApplicationPage(
array( array(
$crumbs,
$header, $header,
$actions, $actions,
$properties, $properties,
$source_code, $source_code,
)); ),
return $this->buildApplicationPage(
$nav,
array( array(
'title' => $paste->getFullName(), 'title' => $paste->getFullName(),
'device' => true, 'device' => true,
@ -87,6 +88,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController {
PhabricatorPolicyCapability::CAN_EDIT); PhabricatorPolicyCapability::CAN_EDIT);
$can_fork = $user->isLoggedIn(); $can_fork = $user->isLoggedIn();
$fork_uri = $this->getApplicationURI('/create/?parent='.$paste->getID());
return id(new PhabricatorActionListView()) return id(new PhabricatorActionListView())
->setUser($user) ->setUser($user)
@ -97,7 +99,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController {
->setIcon('fork') ->setIcon('fork')
->setDisabled(!$can_fork) ->setDisabled(!$can_fork)
->setWorkflow(!$can_fork) ->setWorkflow(!$can_fork)
->setHref($this->getApplicationURI('?parent='.$paste->getID()))) ->setHref($fork_uri))
->addAction( ->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('View Raw File')) ->setName(pht('View Raw File'))

View file

@ -29,11 +29,21 @@ final class AphrontSideNavFilterView extends AphrontView {
private $user; private $user;
private $active; private $active;
private $menu; private $menu;
private $crumbs;
public function __construct() { public function __construct() {
$this->menu = new PhabricatorMenuView(); $this->menu = new PhabricatorMenuView();
} }
public function setCrumbs(PhabricatorCrumbsView $crumbs) {
$this->crumbs = $crumbs;
return $this;
}
public function getCrumbs() {
return $this->crumbs;
}
public function setActive($active) { public function setActive($active) {
$this->active = $active; $this->active = $active;
return $this; return $this;
@ -54,6 +64,10 @@ final class AphrontSideNavFilterView extends AphrontView {
return $this; return $this;
} }
public function getMenuView() {
return $this->menu;
}
public function addMenuItem(PhabricatorMenuItemView $item) { public function addMenuItem(PhabricatorMenuItemView $item) {
$this->menu->addMenuItem($item); $this->menu->addMenuItem($item);
return $this; return $this;
@ -120,6 +134,10 @@ final class AphrontSideNavFilterView extends AphrontView {
return $this->selectedFilter; return $this->selectedFilter;
} }
public function getSelectedFilter() {
return $this->selectedFilter;
}
public function render() { public function render() {
if ($this->menu->getItems()) { if ($this->menu->getItems()) {
if (!$this->baseURI) { if (!$this->baseURI) {
@ -187,6 +205,12 @@ final class AphrontSideNavFilterView extends AphrontView {
self::renderSingleView($this->menu)); self::renderSingleView($this->menu));
} }
$crumbs = null;
if ($this->crumbs) {
$crumbs = $this->crumbs->render();
$nav_classes[] = 'has-crumbs';
}
Javelin::initBehavior( Javelin::initBehavior(
'phabricator-nav', 'phabricator-nav',
array( array(
@ -194,6 +218,7 @@ final class AphrontSideNavFilterView extends AphrontView {
'localID' => $local_id, 'localID' => $local_id,
'dragID' => $drag_id, 'dragID' => $drag_id,
'contentID' => $content_id, 'contentID' => $content_id,
'menuSize' => ($crumbs ? 78 : 44),
)); ));
if ($this->active && $local_id) { if ($this->active && $local_id) {
@ -204,17 +229,7 @@ final class AphrontSideNavFilterView extends AphrontView {
)); ));
} }
$header_part = return $crumbs.phutil_render_tag(
'<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>';
return $header_part.phutil_render_tag(
'div', 'div',
array( array(
'class' => implode(' ', $nav_classes), 'class' => implode(' ', $nav_classes),

View file

@ -0,0 +1,77 @@
<?php
final class PhabricatorCrumbView extends AphrontView {
private $name;
private $href;
private $icon;
private $isLastCrumb;
public function setName($name) {
$this->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);
}
}

View file

@ -0,0 +1,70 @@
<?php
final class PhabricatorCrumbsView extends AphrontView {
private $crumbs = array();
private $actions = array();
protected function canAppendChild() {
return false;
}
public function addCrumb(PhabricatorCrumbView $crumb) {
$this->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));
}
}

View file

@ -36,7 +36,7 @@ final class PhabricatorMenuItemView extends AphrontView {
} }
public function setKey($key) { public function setKey($key) {
$this->key = $key; $this->key = (string)$key;
return $this; return $this;
} }

View file

@ -49,7 +49,7 @@ final class PhabricatorMenuView extends AphrontView {
} }
public function getItem($key) { public function getItem($key) {
return idx($this->map, $key); return idx($this->map, (string)$key);
} }
public function getItems() { public function getItems() {

View file

@ -19,6 +19,10 @@
z-index: 3; z-index: 3;
} }
.has-crumbs .phabricator-nav-col {
top: 78px;
}
.phabricator-nav-local { .phabricator-nav-local {
width: 179px; width: 179px;
background: #ececec; background: #ececec;
@ -30,7 +34,7 @@
.device-tablet .phabricator-nav-local, .device-tablet .phabricator-nav-local,
.device-phone .phabricator-nav-local { .device-phone .phabricator-nav-local {
width: 299px; display: none;
} }
.phabricator-nav-drag { .phabricator-nav-drag {
@ -79,10 +83,6 @@
margin-left: 2.5em !important; margin-left: 2.5em !important;
} }
.device-desktop .phabricator-nav-head {
display: none;
}
.device-tablet .phabricator-nav-col, .device-tablet .phabricator-nav-col,
.device-phone .phabricator-nav-col { .device-phone .phabricator-nav-col {
position: absolute; position: absolute;
@ -110,55 +110,3 @@
margin-left: 0; margin-left: 0;
position: relative; 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;
}

View file

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

View file

@ -6,8 +6,6 @@
* javelin-dom * javelin-dom
* javelin-magical-init * javelin-magical-init
* javelin-vector * javelin-vector
* javelin-util
* javelin-fx
* @javelin * @javelin
*/ */
@ -18,70 +16,6 @@ JX.behavior('phabricator-nav', function(config) {
var main = JX.$(config.mainID); 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 ------------------------------------------------ // - Flexible Navigation Column ------------------------------------------------
if (config.dragID) { if (config.dragID) {
@ -183,7 +117,7 @@ JX.behavior('phabricator-nav', function(config) {
return; 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'; local.style.top = y + 'px';
}); });