diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ return array( 'names' => array( - 'core.pkg.css' => 'c00e58c8', + 'core.pkg.css' => '9dc1daac', 'core.pkg.js' => 'b2ed04a2', 'darkconsole.pkg.js' => 'ca8671ce', 'differential.pkg.css' => '4b8686e3', @@ -105,7 +105,7 @@ 'rsrc/css/application/subscriptions/subscribers-list.css' => '5bb30c78', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '40151074', + 'rsrc/css/core/core.css' => '7c767604', 'rsrc/css/core/remarkup.css' => '80c3a48c', 'rsrc/css/core/syntax.css' => '3c18c1cb', 'rsrc/css/core/z-index.css' => 'efb673ac', @@ -115,7 +115,7 @@ 'rsrc/css/font/font-source-sans-pro.css' => '91d53463', 'rsrc/css/font/phui-font-icon-base.css' => 'cd92ff25', 'rsrc/css/layout/phabricator-action-header-view.css' => 'c14dfc57', - 'rsrc/css/layout/phabricator-action-list-view.css' => '6f7ef696', + 'rsrc/css/layout/phabricator-action-list-view.css' => '64c0d17c', 'rsrc/css/layout/phabricator-crumbs-view.css' => '0222cbe0', 'rsrc/css/layout/phabricator-filetree-view.css' => 'a8c86ace', 'rsrc/css/layout/phabricator-hovercard-view.css' => '46a13cf0', @@ -139,7 +139,7 @@ 'rsrc/css/phui/phui-object-box.css' => 'ce92d8ec', 'rsrc/css/phui/phui-object-item-list-view.css' => '7cf6ccf9', 'rsrc/css/phui/phui-pinboard-view.css' => 'e7d3b05e', - 'rsrc/css/phui/phui-property-list-view.css' => 'af4b381f', + 'rsrc/css/phui/phui-property-list-view.css' => 'ddb13477', 'rsrc/css/phui/phui-remarkup-preview.css' => '19ad512b', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => '2f562399', @@ -688,12 +688,12 @@ 'path-typeahead' => 'f7fc67ec', 'people-profile-css' => 'ba7b2762', 'phabricator-action-header-view-css' => 'c14dfc57', - 'phabricator-action-list-view-css' => '6f7ef696', + 'phabricator-action-list-view-css' => '64c0d17c', 'phabricator-application-launch-view-css' => 'd290ba21', 'phabricator-busy' => '6453c869', 'phabricator-chatlog-css' => '852140ff', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '40151074', + 'phabricator-core-css' => '7c767604', 'phabricator-countdown-css' => '86b7b0a0', 'phabricator-crumbs-view-css' => '0222cbe0', 'phabricator-drag-and-drop-file-upload' => 'ae6abfba', @@ -768,7 +768,7 @@ 'phui-object-box-css' => 'ce92d8ec', 'phui-object-item-list-view-css' => '7cf6ccf9', 'phui-pinboard-view-css' => 'e7d3b05e', - 'phui-property-list-view-css' => 'af4b381f', + 'phui-property-list-view-css' => 'ddb13477', 'phui-remarkup-preview-css' => '19ad512b', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => '2f562399', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3821,7 +3821,7 @@ 'PhabricatorActionHeaderView' => 'AphrontView', 'PhabricatorActionListExample' => 'PhabricatorUIExample', 'PhabricatorActionListView' => 'AphrontView', - 'PhabricatorActionView' => 'AphrontView', + 'PhabricatorActionView' => 'AphrontTagView', 'PhabricatorAllCapsTranslation' => 'PhabricatorTranslation', 'PhabricatorAnchorView' => 'AphrontView', 'PhabricatorAphrontBarExample' => 'PhabricatorUIExample', diff --git a/src/applications/people/controller/PhabricatorPeopleProfileController.php b/src/applications/people/controller/PhabricatorPeopleProfileController.php --- a/src/applications/people/controller/PhabricatorPeopleProfileController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileController.php @@ -64,24 +64,35 @@ ->setWorkflow(!$can_edit)); if ($viewer->getIsAdmin()) { + $dd = phutil_tag('span', array('class' => 'action-view-caret'), ''); + $admin_text = pht('Admisistrate User %s', $dd); $actions->addAction( id(new PhabricatorActionView()) - ->setIcon('wrench') + ->setIsContainer(true) + ->setIcon('user') + ->setGroupKey('user.admin') + ->setName($admin_text)); + + $actions->addAction( + id(new PhabricatorActionView()) + ->setGroupKey('user.admin') + ->setIcon('none') ->setName(pht('Edit Settings')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setHref('/settings/'.$user->getID().'/')); if ($user->getIsAdmin()) { - $empower_icon = 'lower-priority'; + $empower_icon = 'none'; $empower_name = pht('Remove Administrator'); } else { - $empower_icon = 'raise-priority'; + $empower_icon = 'none'; $empower_name = pht('Make Administrator'); } $actions->addAction( id(new PhabricatorActionView()) + ->setGroupKey('user.admin') ->setIcon($empower_icon) ->setName($empower_name) ->setDisabled(($user->getPHID() == $viewer->getPHID())) @@ -90,21 +101,23 @@ $actions->addAction( id(new PhabricatorActionView()) - ->setIcon('tag') + ->setGroupKey('user.admin') + ->setIcon('none') ->setName(pht('Change Username')) ->setWorkflow(true) ->setHref($this->getApplicationURI('rename/'.$user->getID().'/'))); if ($user->getIsDisabled()) { - $disable_icon = 'enable'; + $disable_icon = 'none'; $disable_name = pht('Enable User'); } else { - $disable_icon = 'disable'; + $disable_icon = 'none'; $disable_name = pht('Disable User'); } $actions->addAction( id(new PhabricatorActionView()) + ->setGroupKey('user.admin') ->setIcon($disable_icon) ->setName($disable_name) ->setDisabled(($user->getPHID() == $viewer->getPHID())) @@ -113,7 +126,8 @@ $actions->addAction( id(new PhabricatorActionView()) - ->setIcon('delete') + ->setGroupKey('user.admin') + ->setIcon('none') ->setName(pht('Delete User')) ->setDisabled(($user->getPHID() == $viewer->getPHID())) ->setWorkflow(true) @@ -121,7 +135,8 @@ $actions->addAction( id(new PhabricatorActionView()) - ->setIcon('message') + ->setGroupKey('user.admin') + ->setIcon('none') ->setName(pht('Send Welcome Email')) ->setWorkflow(true) ->setHref($this->getApplicationURI('welcome/'.$user->getID().'/'))); @@ -131,6 +146,7 @@ $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($user->getUsername()); + $crumbs->setActionList($actions); $feed = $this->renderUserFeed($user); $calendar = $this->renderUserCalendar($user); $activity = phutil_tag( diff --git a/src/view/AphrontTagView.php b/src/view/AphrontTagView.php --- a/src/view/AphrontTagView.php +++ b/src/view/AphrontTagView.php @@ -41,6 +41,11 @@ return $this->metadata; } + final protected function clearMetadata() { + $this->metadata = null; + return $this; + } + final public function setStyle($style) { $this->style = $style; return $this; @@ -59,6 +64,11 @@ return $this->sigils; } + final protected function setSigils(array $sigils) { + $this->sigils = $sigils; + return $this; + } + public function addClass($class) { $this->classes[] = $class; return $this; diff --git a/src/view/layout/PhabricatorActionListView.php b/src/view/layout/PhabricatorActionListView.php --- a/src/view/layout/PhabricatorActionListView.php +++ b/src/view/layout/PhabricatorActionListView.php @@ -51,6 +51,69 @@ $action->setUser($this->user); } + $groups = array(); + foreach ($actions as $key => $action) { + if (!$action->getIsContainer()) { + if ($action->getGroupKey()) { + $groups[$action->getGroupKey()][] = $action; + unset($actions[$key]); + } + } + } + + $output = array(); + foreach ($actions as $key => $action) { + if (!$action->getIsContainer()) { + $output[] = $action; + continue; + } + + $group = idx($groups, $action->getGroupKey(), array()); + if (!$group) { + continue; + } + + Javelin::initBehavior('phabricator-reveal-content'); + + $closed_id = celerity_generate_unique_node_id(); + + $open_id = celerity_generate_unique_node_id(); + $item_ids = array($open_id); + + foreach ($group as $item) { + if (!$item->getID()) { + $item->setID(celerity_generate_unique_node_id()); + } + $item_ids[] = $item->getID(); + $item->setStyle('display: none'); + $item->addClass('phabricator-action-list-subitem'); + } + + $output[] = id(clone $action) + ->setHref('#') + ->setID($closed_id) + ->addSigil('reveal-content') + ->setMetadata( + array( + 'hideIDs' => array($closed_id), + 'showIDs' => $item_ids, + )); + + $output[] = id(clone $action) + ->setHref('#') + ->setID($open_id) + ->addSigil('reveal-content') + ->setStyle('display: none') + ->addClass('phabricator-action-list-group-header') + ->setMetadata( + array( + 'hideIDs' => $item_ids, + 'showIDs' => array($closed_id), + )); + + $output[] = $group; + } + require_celerity_resource('phabricator-action-list-view-css'); return phutil_tag( @@ -59,7 +122,7 @@ 'class' => 'phabricator-action-list-view', 'id' => $this->id ), - $actions); + $output); } diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php --- a/src/view/layout/PhabricatorActionView.php +++ b/src/view/layout/PhabricatorActionView.php @@ -1,6 +1,6 @@ <?php -final class PhabricatorActionView extends AphrontView { +final class PhabricatorActionView extends AphrontTagView { private $name; private $icon; @@ -11,22 +11,36 @@ private $renderAsForm; private $download; private $objectURI; - private $sigils = array(); - private $metadata; + private $isContainer; + private $groupKey; - public function setMetadata($metadata) { - $this->metadata = $metadata; + private $actionSigils; + private $actionMetadata; + private $actionWorkflow; + + public function setGroupKey($group_key) { + $this->groupKey = $group_key; + return $this; + } + + public function getGroupKey() { + return $this->groupKey; + } + + public function setIsContainer($is_container) { + $this->isContainer = $is_container; return $this; } - public function getMetadata() { - return $this->metadata; + public function getIsContainer() { + return $this->isContainer; } public function setObjectURI($object_uri) { $this->objectURI = $object_uri; return $this; } + public function getObjectURI() { return $this->objectURI; } @@ -45,11 +59,6 @@ return $this; } - public function addSigil($sigil) { - $this->sigils[] = $sigil; - return $this; - } - /** * If the user is not logged in and the action is relatively complicated, * give them a generic login link that will re-direct to the page they're @@ -86,17 +95,42 @@ return $this; } - public function setWorkflow($workflow) { - $this->workflow = $workflow; - return $this; - } - public function setRenderAsForm($form) { $this->renderAsForm = $form; return $this; } - public function render() { + public function getTagName() { + return 'li'; + } + + protected function willRender() { + // Before rendering, move attribute that we want to put on the actual link + // off this object. This is kind of sketchy, but we don't have any other + // similar use cases yet. + $this->actionSigils = $this->getSigils(); + $this->setSigils(array()); + + $this->actionWorkflow = $this->getWorkflow(); + $this->setWorkflow(false); + + $this->actionMetadata = $this->getMetadata(); + $this->clearMetadata(); + } + + public function getTagAttributes() { + $classes = array(); + $classes[] = 'phabricator-action-view'; + if ($this->disabled) { + $classes[] = 'phabricator-action-view-disabled'; + } + + return array( + 'class' => $classes, + ); + } + + public function getTagContent() { $icon = null; if ($this->icon) { @@ -115,18 +149,16 @@ if ($this->href) { - $sigils = array(); - if ($this->workflow) { + $sigils = $this->actionSigils; + + if ($this->actionWorkflow) { $sigils[] = 'workflow'; } + if ($this->download) { $sigils[] = 'download'; } - if ($this->sigils) { - $sigils = array_merge($sigils, $this->sigils); - } - $sigils = $sigils ? implode(' ', $sigils) : null; if ($this->renderAsForm) { @@ -148,7 +180,7 @@ 'action' => $this->getHref(), 'method' => 'POST', 'sigil' => $sigils, - 'meta' => $this->metadata, + 'meta' => $this->actionMetadata, ), $item); } else { @@ -158,7 +190,7 @@ 'href' => $this->getHref(), 'class' => 'phabricator-action-view-item', 'sigil' => $sigils, - 'meta' => $this->metadata, + 'meta' => $this->actionMetadata, ), $this->name); } @@ -171,18 +203,7 @@ $this->name); } - $classes = array(); - $classes[] = 'phabricator-action-view'; - if ($this->disabled) { - $classes[] = 'phabricator-action-view-disabled'; - } - - return phutil_tag( - 'li', - array( - 'class' => implode(' ', $classes), - ), - array($icon, $item)); + return array($icon, $item); } public static function getAvailableIcons() { diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -93,7 +93,7 @@ cursor: pointer; } -a:hover { +.device-desktop a:hover { text-decoration: underline; } diff --git a/webroot/rsrc/css/layout/phabricator-action-list-view.css b/webroot/rsrc/css/layout/phabricator-action-list-view.css --- a/webroot/rsrc/css/layout/phabricator-action-list-view.css +++ b/webroot/rsrc/css/layout/phabricator-action-list-view.css @@ -70,6 +70,14 @@ left: 9px; } +.phabricator-action-list-group-header { + background-color: #f7f7f7; +} + +.phabricator-action-list-subitem .phabricator-action-view-icon { + left: 17px; +} + .device-desktop .phabricator-action-view:hover .phabricator-action-view-item { text-decoration: none; background-color: {$blue}; @@ -96,3 +104,39 @@ background-color: #dfdfdf; color: {$lightgreytext}; } + +.phabricator-action-list-group-header .action-view-caret { + display: inline-block; + width: 0; + height: 0; + vertical-align: top; + margin: 7px 0 0 3px; + border-top: 5px solid {$darkgreytext}; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + border-bottom: none; + content: ""; +} + +.action-view-caret { + display: inline-block; + width: 0; + height: 0; + vertical-align: middle; + border-left: 6px solid {$darkgreytext}; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + content: ""; + margin: 0 0 2px 3px; +} + +.device-desktop .phabricator-action-view:hover .action-view-caret { + border-left-color: #fff; +} + +.device-desktop + .phabricator-action-view:hover.phabricator-action-list-group-header + .action-view-caret { + border-left-color: transparent; + border-top-color: #fff; +} diff --git a/webroot/rsrc/css/phui/phui-property-list-view.css b/webroot/rsrc/css/phui/phui-property-list-view.css --- a/webroot/rsrc/css/phui/phui-property-list-view.css +++ b/webroot/rsrc/css/phui/phui-property-list-view.css @@ -171,6 +171,7 @@ float: none; width: auto; margin: -12px 0 12px 0; + border: none; } .phui-property-list-image-content img {