diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 867845743a..5d4d298aa9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,8 +7,8 @@ return array( 'names' => array( - 'core.pkg.css' => '63255578', - 'core.pkg.js' => 'c907bd96', + 'core.pkg.css' => 'a2b5fd6a', + 'core.pkg.js' => 'c7854cc5', 'darkconsole.pkg.js' => 'ca8671ce', 'differential.pkg.css' => '5a65a762', 'differential.pkg.js' => '322ea941', @@ -39,7 +39,7 @@ return array( 'rsrc/css/aphront/two-column.css' => '16ab3ad2', 'rsrc/css/aphront/typeahead.css' => '00c9a200', 'rsrc/css/application/auth/auth.css' => '1e655982', - 'rsrc/css/application/base/main-menu-view.css' => 'aba0b7a6', + 'rsrc/css/application/base/main-menu-view.css' => 'aa18107a', 'rsrc/css/application/base/notification-menu.css' => 'fc9a363c', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '6f8453d9', 'rsrc/css/application/base/standard-page-view.css' => '517cdfb1', @@ -337,7 +337,7 @@ return array( 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/js/application/aphlict/Aphlict.js' => '493665ee', - 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'd4220f5b', + 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '2a2dba85', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '845731b8', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/config/behavior-reorder-fields.js' => '69bb5094', @@ -420,7 +420,7 @@ return array( 'rsrc/js/core/Busy.js' => '6453c869', 'rsrc/js/core/DragAndDropFileUpload.js' => 'ae6abfba', 'rsrc/js/core/DraggableList.js' => '1681c4d4', - 'rsrc/js/core/DropdownMenu.js' => '2f6f80f4', + 'rsrc/js/core/DropdownMenu.js' => 'fb342e18', 'rsrc/js/core/DropdownMenuItem.js' => '0f386ef4', 'rsrc/js/core/FileUpload.js' => '96713558', 'rsrc/js/core/Hovercard.js' => '4f344388', @@ -519,7 +519,7 @@ return array( 'inline-comment-summary-css' => '14a91639', 'javelin-aphlict' => '493665ee', 'javelin-behavior' => '8a3ed18b', - 'javelin-behavior-aphlict-dropdown' => 'd4220f5b', + 'javelin-behavior-aphlict-dropdown' => '2a2dba85', 'javelin-behavior-aphlict-listen' => '845731b8', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-crop' => 'b98fc918', @@ -679,7 +679,7 @@ return array( 'phabricator-crumbs-view-css' => '2d9db584', 'phabricator-drag-and-drop-file-upload' => 'ae6abfba', 'phabricator-draggable-list' => '1681c4d4', - 'phabricator-dropdown-menu' => '2f6f80f4', + 'phabricator-dropdown-menu' => 'fb342e18', 'phabricator-fatal-config-template-css' => '25d446d6', 'phabricator-feed-css' => '0d17c209', 'phabricator-file-upload' => '96713558', @@ -690,7 +690,7 @@ return array( 'phabricator-jump-nav' => 'f0c5e726', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'ad7a69ca', - 'phabricator-main-menu-view' => 'aba0b7a6', + 'phabricator-main-menu-view' => 'aa18107a', 'phabricator-menu-item' => '0f386ef4', 'phabricator-nav-view-css' => 'd0d4a509', 'phabricator-notification' => '95944043', @@ -972,6 +972,16 @@ return array( 1 => 'javelin-stratcom', 2 => 'javelin-dom', ), + '2a2dba85' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-request', + 2 => 'javelin-stratcom', + 3 => 'javelin-vector', + 4 => 'javelin-dom', + 5 => 'javelin-uri', + 6 => 'javelin-behavior-device', + ), '2f2e18aa' => array( 0 => 'javelin-behavior', @@ -979,15 +989,6 @@ return array( 2 => 'javelin-workflow', 3 => 'javelin-stratcom', ), - '2f6f80f4' => - array( - 0 => 'javelin-install', - 1 => 'javelin-util', - 2 => 'javelin-dom', - 3 => 'javelin-vector', - 4 => 'javelin-stratcom', - 5 => 'phabricator-menu-item', - ), '2fa810fc' => array( 0 => 'javelin-behavior', @@ -1710,15 +1711,6 @@ return array( array( 0 => 'javelin-util', ), - 'd4220f5b' => - array( - 0 => 'javelin-behavior', - 1 => 'javelin-request', - 2 => 'javelin-stratcom', - 3 => 'javelin-vector', - 4 => 'javelin-dom', - 5 => 'javelin-uri', - ), 'd4a14807' => array( 0 => 'javelin-install', @@ -1921,6 +1913,15 @@ return array( 4 => 'javelin-stratcom', 5 => 'phabricator-shaped-request', ), + 'fb342e18' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + 2 => 'javelin-dom', + 3 => 'javelin-vector', + 4 => 'javelin-stratcom', + 5 => 'phabricator-menu-item', + ), 'fbbce3bf' => array( 0 => 'phabricator-busy', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index b0febb168b..e26baeea1e 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -258,6 +258,23 @@ abstract class PhabricatorApplication } + /** + * Build extra items for the main menu. Generally, this is used to render + * static dropdowns. + * + * @param PhabricatorUser The viewing user. + * @param AphrontController The current controller. May be null for special + * pages like 404, exception handlers, etc. + * @return view List of menu items. + * @task ui + */ + public function buildMainMenuExtraNodes( + PhabricatorUser $viewer, + PhabricatorController $controller = null) { + return array(); + } + + /** * On the Phabricator homepage sidebar, this function returns the URL for * a quick create X link which is displayed in the wide button only. diff --git a/src/applications/home/application/PhabricatorApplicationHome.php b/src/applications/home/application/PhabricatorApplicationHome.php index 9393d24333..2f530b4b27 100644 --- a/src/applications/home/application/PhabricatorApplicationHome.php +++ b/src/applications/home/application/PhabricatorApplicationHome.php @@ -42,11 +42,24 @@ final class PhabricatorApplicationHome extends PhabricatorApplication { $items = array(); if ($user->isLoggedIn() && $user->isUserActivated()) { + $create_id = celerity_generate_unique_node_id(); + Javelin::initBehavior( + 'aphlict-dropdown', + array( + 'bubbleID' => $create_id, + 'dropdownID' => 'phabricator-quick-create-menu', + 'local' => true, + 'desktop' => true, + 'right' => true, + )); + $item = id(new PHUIListItemView()) ->setName(pht('Create New...')) ->setIcon('new') ->addClass('core-menu-item') ->setHref('/home/create/') + ->addSigil('quick-create-menu') + ->setID($create_id) ->setOrder(300); $items[] = $item; } @@ -54,4 +67,43 @@ final class PhabricatorApplicationHome extends PhabricatorApplication { return $items; } + public function loadAllQuickCreateItems(PhabricatorUser $viewer) { + $applications = id(new PhabricatorApplicationQuery()) + ->setViewer($viewer) + ->withInstalled(true) + ->execute(); + + $items = array(); + foreach ($applications as $application) { + $app_items = $application->getQuickCreateItems($viewer); + foreach ($app_items as $app_item) { + $items[] = $app_item; + } + } + + return $items; + } + + public function buildMainMenuExtraNodes( + PhabricatorUser $viewer, + PhabricatorController $controller = null) { + + $items = $this->loadAllQuickCreateItems($viewer); + + $view = new PHUIListView(); + $view->newLabel(pht('Create New...')); + foreach ($items as $item) { + $view->addMenuItem($item); + } + + return phutil_tag( + 'div', + array( + 'id' => 'phabricator-quick-create-menu', + 'class' => 'phabricator-main-menu-dropdown phui-list-sidenav', + 'style' => 'display: none', + ), + $view); + } + } diff --git a/src/applications/home/controller/PhabricatorHomeQuickCreateController.php b/src/applications/home/controller/PhabricatorHomeQuickCreateController.php index a93b7f1c06..82b39cb688 100644 --- a/src/applications/home/controller/PhabricatorHomeQuickCreateController.php +++ b/src/applications/home/controller/PhabricatorHomeQuickCreateController.php @@ -6,18 +6,7 @@ final class PhabricatorHomeQuickCreateController public function processRequest() { $viewer = $this->getRequest()->getUser(); - $applications = id(new PhabricatorApplicationQuery()) - ->setViewer($viewer) - ->withInstalled(true) - ->execute(); - - $items = array(); - foreach ($applications as $application) { - $app_items = $application->getQuickCreateItems($viewer); - foreach ($app_items as $app_item) { - $items[] = $app_item; - } - } + $items = $this->getCurrentApplication()->loadAllQuickCreateItems($viewer); $list = id(new PHUIObjectItemListView()) ->setUser($viewer); diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 94430e8ed4..9f656e2c78 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -395,6 +395,13 @@ final class PhabricatorMainMenuView extends AphrontView { $notification_dropdown, $message_notification_dropdown); + $applications = PhabricatorApplication::getAllInstalledApplications(); + foreach ($applications as $application) { + $dropdowns[] = $application->buildMainMenuExtraNodes( + $this->getUser(), + $this->getController()); + } + return array( hsprintf('%s%s', $bubble_tag, $message_tag), $dropdowns diff --git a/webroot/rsrc/css/application/base/main-menu-view.css b/webroot/rsrc/css/application/base/main-menu-view.css index c8fe37d541..d9830b89a7 100644 --- a/webroot/rsrc/css/application/base/main-menu-view.css +++ b/webroot/rsrc/css/application/base/main-menu-view.css @@ -389,6 +389,16 @@ height: 28px; } +.phabricator-main-menu-dropdown { + position: absolute; + background: #ffffff; + top: 44px; + padding: 2px; + border: 1px solid {$lightgreyborder}; + box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.25); +} + + /* - Application Menu ---------------------------------------------------------- Styles unique to the application menu (right button on mobile). diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js index 5dce1a9a4f..77317a54df 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-dropdown.js @@ -6,6 +6,7 @@ * javelin-vector * javelin-dom * javelin-uri + * javelin-behavior-device */ JX.behavior('aphlict-dropdown', function(config, statics) { @@ -13,10 +14,15 @@ JX.behavior('aphlict-dropdown', function(config, statics) { statics.visible = statics.visible || null; var dropdown = JX.$(config.dropdownID); - var count = JX.$(config.countID); var bubble = JX.$(config.bubbleID); + + var count; + if (config.countID) { + count = JX.$(config.countID); + } + var request = null; - var dirty = true; + var dirty = config.local ? false : true; function refresh() { if (dirty) { @@ -86,6 +92,10 @@ JX.behavior('aphlict-dropdown', function(config, statics) { return; } + if (config.desktop && JX.Device.getDevice() != 'desktop') { + return; + } + e.kill(); // If a menu is currently open, close it. @@ -108,16 +118,24 @@ JX.behavior('aphlict-dropdown', function(config, statics) { } var p = JX.$V(bubble); + JX.DOM.show(dropdown); + p.y = null; - p.x -= 6; + if (config.right) { + p.x -= (JX.Vector.getDim(dropdown).x - JX.Vector.getDim(bubble).x); + } else { + p.x -= 6; + } p.setPos(dropdown); - JX.DOM.show(dropdown); statics.visible = dropdown; } ); JX.Stratcom.listen('notification-panel-update', null, function() { + if (config.local) { + return; + } dirty = true; refresh(); }); diff --git a/webroot/rsrc/js/core/DropdownMenu.js b/webroot/rsrc/js/core/DropdownMenu.js index f298acd8c8..490e92e223 100644 --- a/webroot/rsrc/js/core/DropdownMenu.js +++ b/webroot/rsrc/js/core/DropdownMenu.js @@ -77,6 +77,8 @@ JX.install('PhabricatorDropdownMenu', { this._open = true; this._show(); + + return this; }, close : function() { @@ -85,6 +87,8 @@ JX.install('PhabricatorDropdownMenu', { } this._open = false; this._hide(); + + return this; }, clear : function() {