From 12b92243874695d747c999ee46146420bd60c581 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 1 Apr 2019 10:25:24 -0700 Subject: [PATCH] Make the "Install Dashboard" flow smoother Summary: Depends on D20362. Ref T13272. Currently, Dashboards have an "Install Dashboard" flow which is pretty janky and only allows you to install things to the home page. Instead, allow users to install things to any valid target (home, favorites, portals, projects). This also provides URIs like `dashboard/install/1/home/personal/` which allow you to link users to an "install a dashboard" page; this may or may not get used. Test Plan: Installed dashboards on home, favorites, projects, and portals. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13272 Differential Revision: https://secure.phabricator.com/D20364 --- resources/celerity/map.php | 12 +- src/__phutil_library_map__.php | 16 ++ .../PhabricatorDashboardApplication.php | 5 +- .../PhabricatorDashboardInstallController.php | 153 +++++------------- .../PhabricatorDashboardViewController.php | 2 +- ...torDashboardApplicationInstallWorkflow.php | 58 +++++++ ...catorDashboardFavoritesInstallWorkflow.php | 85 ++++++++++ ...habricatorDashboardHomeInstallWorkflow.php | 83 ++++++++++ .../PhabricatorDashboardInstallWorkflow.php | 143 ++++++++++++++++ ...bricatorDashboardObjectInstallWorkflow.php | 99 ++++++++++++ ...bricatorDashboardPortalInstallWorkflow.php | 62 +++++++ ...ricatorDashboardProjectInstallWorkflow.php | 63 ++++++++ .../PhabricatorDashboardPortalDatasource.php | 47 ++++++ ...habricatorProjectPointsProfileMenuItem.php | 5 +- .../menuitem/PhabricatorProfileMenuItem.php | 1 - src/view/phui/PHUIObjectItemView.php | 2 + .../css/phui/object-item/phui-oi-big-ui.css | 20 ++- 17 files changed, 730 insertions(+), 126 deletions(-) create mode 100644 src/applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php create mode 100644 src/applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php create mode 100644 src/applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php create mode 100644 src/applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php create mode 100644 src/applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php create mode 100644 src/applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php create mode 100644 src/applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php create mode 100644 src/applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 12f464ce78..fb28c7448b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '3c8a0668', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => 'a1c2d49b', + 'core.pkg.css' => '3b565a84', 'core.pkg.js' => 'a568e834', 'differential.pkg.css' => '8d8360fb', 'differential.pkg.js' => '67e02996', @@ -128,7 +128,7 @@ return array( 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'ccd7e4e2', 'rsrc/css/phui/calendar/phui-calendar-month.css' => 'cb758c42', 'rsrc/css/phui/calendar/phui-calendar.css' => 'f11073aa', - 'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '534f1757', + 'rsrc/css/phui/object-item/phui-oi-big-ui.css' => 'fa74cc35', 'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e', @@ -849,7 +849,7 @@ return array( 'phui-lightbox-css' => '4ebf22da', 'phui-list-view-css' => '470b1adb', 'phui-object-box-css' => 'f434b6be', - 'phui-oi-big-ui-css' => '534f1757', + 'phui-oi-big-ui-css' => 'fa74cc35', 'phui-oi-color-css' => 'b517bfa0', 'phui-oi-drag-ui-css' => 'da15d3dc', 'phui-oi-flush-ui-css' => '490e2e2e', @@ -1379,9 +1379,6 @@ return array( 'javelin-dom', 'javelin-fx', ), - '534f1757' => array( - 'phui-oi-list-view-css', - ), '541f81c3' => array( 'javelin-install', ), @@ -2179,6 +2176,9 @@ return array( 'phabricator-keyboard-shortcut', 'conpherence-thread-manager', ), + 'fa74cc35' => array( + 'phui-oi-list-view-css', + ), 'fdc13e4e' => array( 'javelin-install', ), diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5c2d13d0d5..c77b7f820f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2905,6 +2905,7 @@ phutil_register_library_map(array( 'PhabricatorDashboard' => 'applications/dashboard/storage/PhabricatorDashboard.php', 'PhabricatorDashboardAddPanelController' => 'applications/dashboard/controller/PhabricatorDashboardAddPanelController.php', 'PhabricatorDashboardApplication' => 'applications/dashboard/application/PhabricatorDashboardApplication.php', + 'PhabricatorDashboardApplicationInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php', 'PhabricatorDashboardArchiveController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardArchiveController.php', 'PhabricatorDashboardConsoleController' => 'applications/dashboard/controller/PhabricatorDashboardConsoleController.php', 'PhabricatorDashboardController' => 'applications/dashboard/controller/PhabricatorDashboardController.php', @@ -2913,13 +2914,17 @@ phutil_register_library_map(array( 'PhabricatorDashboardDashboardPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardDashboardPHIDType.php', 'PhabricatorDashboardDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardDatasource.php', 'PhabricatorDashboardEditController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardEditController.php', + 'PhabricatorDashboardFavoritesInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php', + 'PhabricatorDashboardHomeInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php', 'PhabricatorDashboardIconSet' => 'applications/dashboard/icon/PhabricatorDashboardIconSet.php', 'PhabricatorDashboardInstall' => 'applications/dashboard/storage/PhabricatorDashboardInstall.php', 'PhabricatorDashboardInstallController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php', + 'PhabricatorDashboardInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php', 'PhabricatorDashboardLayoutConfig' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php', 'PhabricatorDashboardListController' => 'applications/dashboard/controller/PhabricatorDashboardListController.php', 'PhabricatorDashboardMovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardMovePanelController.php', 'PhabricatorDashboardNgrams' => 'applications/dashboard/storage/PhabricatorDashboardNgrams.php', + 'PhabricatorDashboardObjectInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php', 'PhabricatorDashboardPanel' => 'applications/dashboard/storage/PhabricatorDashboardPanel.php', 'PhabricatorDashboardPanelArchiveController' => 'applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php', 'PhabricatorDashboardPanelCoreCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php', @@ -2947,10 +2952,12 @@ phutil_register_library_map(array( 'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/PhabricatorDashboardPanelViewController.php', 'PhabricatorDashboardPortal' => 'applications/dashboard/storage/PhabricatorDashboardPortal.php', 'PhabricatorDashboardPortalController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalController.php', + 'PhabricatorDashboardPortalDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php', 'PhabricatorDashboardPortalEditConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPortalEditConduitAPIMethod.php', 'PhabricatorDashboardPortalEditController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalEditController.php', 'PhabricatorDashboardPortalEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardPortalEditEngine.php', 'PhabricatorDashboardPortalEditor' => 'applications/dashboard/editor/PhabricatorDashboardPortalEditor.php', + 'PhabricatorDashboardPortalInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php', 'PhabricatorDashboardPortalListController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalListController.php', 'PhabricatorDashboardPortalMenuItem' => 'applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php', 'PhabricatorDashboardPortalNameTransaction' => 'applications/dashboard/xaction/portal/PhabricatorDashboardPortalNameTransaction.php', @@ -2966,6 +2973,7 @@ phutil_register_library_map(array( 'PhabricatorDashboardPortalViewController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalViewController.php', 'PhabricatorDashboardProfileController' => 'applications/dashboard/controller/PhabricatorDashboardProfileController.php', 'PhabricatorDashboardProfileMenuItem' => 'applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php', + 'PhabricatorDashboardProjectInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php', 'PhabricatorDashboardQuery' => 'applications/dashboard/query/PhabricatorDashboardQuery.php', 'PhabricatorDashboardQueryPanelInstallController' => 'applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php', 'PhabricatorDashboardQueryPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php', @@ -8865,6 +8873,7 @@ phutil_register_library_map(array( ), 'PhabricatorDashboardAddPanelController' => 'PhabricatorDashboardController', 'PhabricatorDashboardApplication' => 'PhabricatorApplication', + 'PhabricatorDashboardApplicationInstallWorkflow' => 'PhabricatorDashboardInstallWorkflow', 'PhabricatorDashboardArchiveController' => 'PhabricatorDashboardController', 'PhabricatorDashboardConsoleController' => 'PhabricatorDashboardController', 'PhabricatorDashboardController' => 'PhabricatorController', @@ -8873,13 +8882,17 @@ phutil_register_library_map(array( 'PhabricatorDashboardDashboardPHIDType' => 'PhabricatorPHIDType', 'PhabricatorDashboardDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorDashboardEditController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardFavoritesInstallWorkflow' => 'PhabricatorDashboardApplicationInstallWorkflow', + 'PhabricatorDashboardHomeInstallWorkflow' => 'PhabricatorDashboardApplicationInstallWorkflow', 'PhabricatorDashboardIconSet' => 'PhabricatorIconSet', 'PhabricatorDashboardInstall' => 'PhabricatorDashboardDAO', 'PhabricatorDashboardInstallController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardInstallWorkflow' => 'Phobject', 'PhabricatorDashboardLayoutConfig' => 'Phobject', 'PhabricatorDashboardListController' => 'PhabricatorDashboardController', 'PhabricatorDashboardMovePanelController' => 'PhabricatorDashboardController', 'PhabricatorDashboardNgrams' => 'PhabricatorSearchNgrams', + 'PhabricatorDashboardObjectInstallWorkflow' => 'PhabricatorDashboardInstallWorkflow', 'PhabricatorDashboardPanel' => array( 'PhabricatorDashboardDAO', 'PhabricatorApplicationTransactionInterface', @@ -8923,10 +8936,12 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', ), 'PhabricatorDashboardPortalController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardPortalDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorDashboardPortalEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorDashboardPortalEditController' => 'PhabricatorDashboardPortalController', 'PhabricatorDashboardPortalEditEngine' => 'PhabricatorEditEngine', 'PhabricatorDashboardPortalEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorDashboardPortalInstallWorkflow' => 'PhabricatorDashboardObjectInstallWorkflow', 'PhabricatorDashboardPortalListController' => 'PhabricatorDashboardPortalController', 'PhabricatorDashboardPortalMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorDashboardPortalNameTransaction' => 'PhabricatorDashboardPortalTransactionType', @@ -8942,6 +8957,7 @@ phutil_register_library_map(array( 'PhabricatorDashboardPortalViewController' => 'PhabricatorDashboardPortalController', 'PhabricatorDashboardProfileController' => 'PhabricatorController', 'PhabricatorDashboardProfileMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorDashboardProjectInstallWorkflow' => 'PhabricatorDashboardObjectInstallWorkflow', 'PhabricatorDashboardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorDashboardQueryPanelInstallController' => 'PhabricatorDashboardController', 'PhabricatorDashboardQueryPanelType' => 'PhabricatorDashboardPanelType', diff --git a/src/applications/dashboard/application/PhabricatorDashboardApplication.php b/src/applications/dashboard/application/PhabricatorDashboardApplication.php index 1a594e1302..6201775f86 100644 --- a/src/applications/dashboard/application/PhabricatorDashboardApplication.php +++ b/src/applications/dashboard/application/PhabricatorDashboardApplication.php @@ -43,7 +43,10 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication { 'archive/(?P\d+)/' => 'PhabricatorDashboardArchiveController', 'create/' => 'PhabricatorDashboardEditController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardEditController', - 'install/(?:(?P\d+)/)?' => 'PhabricatorDashboardInstallController', + 'install/(?P\d+)/'. + '(?:(?P[^/]+)/'. + '(?:(?P[^/]+)/)?)?' => + 'PhabricatorDashboardInstallController', 'console/' => 'PhabricatorDashboardConsoleController', 'addpanel/(?P\d+)/' => 'PhabricatorDashboardAddPanelController', 'movepanel/(?P\d+)/' => 'PhabricatorDashboardMovePanelController', diff --git a/src/applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php index 5cc3e69541..417086b405 100644 --- a/src/applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php +++ b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php @@ -3,6 +3,17 @@ final class PhabricatorDashboardInstallController extends PhabricatorDashboardController { + private $dashboard; + + public function setDashboard(PhabricatorDashboard $dashboard) { + $this->dashboard = $dashboard; + return $this; + } + + public function getDashboard() { + return $this->dashboard; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); @@ -15,126 +26,50 @@ final class PhabricatorDashboardInstallController return new Aphront404Response(); } + $this->setDashboard($dashboard); $cancel_uri = $dashboard->getURI(); - $home_app = new PhabricatorHomeApplication(); + $workflow_key = $request->getURIData('workflowKey'); - $options = array(); - $options['home'] = array( - 'personal' => - array( - 'capability' => PhabricatorPolicyCapability::CAN_VIEW, - 'application' => $home_app, - 'name' => pht('Personal Dashboard'), - 'value' => 'personal', - 'description' => pht('Places this dashboard as a menu item on home '. - 'as a personal menu item. It will only be on your personal '. - 'home.'), - ), - 'global' => - array( - 'capability' => PhabricatorPolicyCapability::CAN_EDIT, - 'application' => $home_app, - 'name' => pht('Global Dashboard'), - 'value' => 'global', - 'description' => pht('Places this dashboard as a menu item on home '. - 'as a global menu item. It will be available to all users.'), - ), - ); - - - $errors = array(); - $v_name = null; - if ($request->isFormPost()) { - $menuitem = new PhabricatorDashboardProfileMenuItem(); - $dashboard_phid = $dashboard->getPHID(); - $home = new PhabricatorHomeApplication(); - $v_name = $request->getStr('name'); - $v_home = $request->getStr('home'); - - if ($v_home) { - $application = $options['home'][$v_home]['application']; - $capability = $options['home'][$v_home]['capability']; - - $can_edit_home = PhabricatorPolicyFilter::hasCapability( - $viewer, - $application, - $capability); - - if (!$can_edit_home) { - $errors[] = pht( - 'You do not have permission to install a dashboard on home.'); - } - } else { - $errors[] = pht( - 'You must select a destination to install this dashboard.'); - } - - $v_phid = $viewer->getPHID(); - if ($v_home == 'global') { - $v_phid = null; - } - - if (!$errors) { - $install = PhabricatorProfileMenuItemConfiguration::initializeNewItem( - $home, - $menuitem, - $v_phid); - - $install->setMenuItemProperty('dashboardPHID', $dashboard_phid); - $install->setMenuItemProperty('name', $v_name); - $install->setMenuItemOrder(1); - - $xactions = array(); - - $editor = id(new PhabricatorProfileMenuEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true) - ->setContentSourceFromRequest($request); - - $editor->applyTransactions($install, $xactions); - - $view_uri = '/home/menu/view/'.$install->getID().'/'; - - return id(new AphrontRedirectResponse())->setURI($view_uri); - } + $workflows = PhabricatorDashboardInstallWorkflow::getAllWorkflows(); + if (!isset($workflows[$workflow_key])) { + return $this->newWorkflowDialog($dashboard, $workflows); } - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Menu Label')) - ->setName('name') - ->setValue($v_name)); + return id(clone $workflows[$workflow_key]) + ->setRequest($request) + ->setViewer($viewer) + ->setDashboard($dashboard) + ->setMode($request->getURIData('modeKey')) + ->handleRequest($request); + } - $radio = id(new AphrontFormRadioButtonControl()) - ->setLabel(pht('Home Menu')) - ->setName('home'); + private function newWorkflowDialog( + PhabricatorDashboard $dashboard, + array $workflows) { + $viewer = $this->getViewer(); + $cancel_uri = $dashboard->getURI(); - foreach ($options['home'] as $type => $option) { - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $option['application'], - $option['capability']); - if ($can_edit) { - $radio->addButton( - $option['value'], - $option['name'], - $option['description']); - } + $menu = id(new PHUIObjectItemListView()) + ->setViewer($viewer) + ->setFlush(true) + ->setBig(true); + + foreach ($workflows as $key => $workflow) { + $item = $workflow->getWorkflowMenuItem(); + + $item_href = urisprintf('install/%d/%s/', $dashboard->getID(), $key); + $item_href = $this->getApplicationURI($item_href); + $item->setHref($item_href); + + $menu->addItem($item); } - $form->appendChild($radio); - return $this->newDialog() - ->setTitle(pht('Install Dashboard')) - ->setErrors($errors) + ->setTitle(pht('Add Dashboard to Menu')) ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($form->buildLayoutView()) - ->addCancelButton($cancel_uri) - ->addSubmitButton(pht('Install Dashboard')); + ->appendChild($menu) + ->addCancelButton($cancel_uri); } } diff --git a/src/applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php index d7dda43894..b0beb1d314 100644 --- a/src/applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php +++ b/src/applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php @@ -83,7 +83,7 @@ final class PhabricatorDashboardViewController $curtain->addAction( id(new PhabricatorActionView()) - ->setName(pht('Install Dashboard')) + ->setName(pht('Add Dashboard to Menu')) ->setIcon('fa-wrench') ->setHref($this->getApplicationURI("/install/{$id}/")) ->setWorkflow(true)); diff --git a/src/applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php new file mode 100644 index 0000000000..5c8ad5743f --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php @@ -0,0 +1,58 @@ +getViewer(), + $this->newApplication(), + PhabricatorPolicyCapability::CAN_EDIT); + } + + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $application = $this->newApplication(); + $can_global = $this->canInstallToGlobalMenu(); + + switch ($this->getMode()) { + case 'global': + if (!$can_global) { + return $this->newGlobalPermissionDialog(); + } else if ($request->isFormPost()) { + return $this->installDashboard($application, null); + } else { + return $this->newGlobalConfirmDialog(); + } + case 'personal': + if ($request->isFormPost()) { + return $this->installDashboard($application, $viewer->getPHID()); + } else { + return $this->newPersonalConfirmDialog(); + } + } + + $global_item = $this->newGlobalMenuItem() + ->setDisabled(!$can_global); + + $menu = $this->newMenuFromItemMap( + array( + 'personal' => $this->newPersonalMenuItem(), + 'global' => $global_item, + )); + + return $this->newApplicationModeDialog() + ->appendChild($menu); + } + + abstract protected function newGlobalPermissionDialog(); + abstract protected function newGlobalConfirmDialog(); + abstract protected function newPersonalConfirmDialog(); + + abstract protected function newPersonalMenuItem(); + abstract protected function newGlobalMenuItem(); + abstract protected function newApplicationModeDialog(); + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php new file mode 100644 index 0000000000..51cd45883b --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php @@ -0,0 +1,85 @@ +newMenuItem() + ->setHeader(pht('Add to Favorites Menu')) + ->setImageIcon('fa-bookmark') + ->addAttribute( + pht( + 'Add this dashboard to the favorites menu in the main '. + 'menu bar.')); + } + + protected function newProfileEngine() { + return new PhabricatorFavoritesProfileMenuEngine(); + } + + protected function newApplication() { + return new PhabricatorFavoritesApplication(); + } + + protected function newApplicationModeDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Favorites Menu')); + } + + protected function newPersonalMenuItem() { + return $this->newMenuItem() + ->setHeader(pht('Add to Personal Favorites')) + ->setImageIcon('fa-user') + ->addAttribute( + pht( + 'Add this dashboard to your list of personal favorite menu items, '. + 'visible to only you.')); + } + + protected function newGlobalMenuItem() { + return $this->newMenuItem() + ->setHeader(pht('Add to Global Favorites')) + ->setImageIcon('fa-globe') + ->addAttribute( + pht( + 'Add this dashboard to the global favorites menu, visible to all '. + 'users.')); + } + + protected function newGlobalPermissionDialog() { + return $this->newDialog() + ->setTitle(pht('No Permission')) + ->appendParagraph( + pht( + 'You do not have permission to install items on the global '. + 'favorites menu.')); + } + + protected function newGlobalConfirmDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Global Favorites')) + ->appendParagraph( + pht( + 'Add dashboard %s as a global menu item in the favorites menu?', + $this->getDashboardDisplayName())) + ->addSubmitButton(pht('Add to Favorites')); + } + + protected function newPersonalConfirmDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Personal Favorites')) + ->appendParagraph( + pht( + 'Add dashboard %s as a personal menu item in the favorites menu?', + $this->getDashboardDisplayName())) + ->addSubmitButton(pht('Add to Favorites')); + } + + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php new file mode 100644 index 0000000000..28731a601e --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php @@ -0,0 +1,83 @@ +newMenuItem() + ->setHeader(pht('Add to Home Page Menu')) + ->setImageIcon('fa-home') + ->addAttribute( + pht( + 'Add this dashboard to the menu on the home page.')); + } + + protected function newProfileEngine() { + return new PhabricatorHomeProfileMenuEngine(); + } + + protected function newApplication() { + return new PhabricatorHomeApplication(); + } + + protected function newApplicationModeDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Home Menu')); + } + + protected function newPersonalMenuItem() { + return $this->newMenuItem() + ->setHeader(pht('Add to Personal Home Menu')) + ->setImageIcon('fa-user') + ->addAttribute( + pht( + 'Add this dashboard to your list of personal home menu items, '. + 'visible to only you.')); + } + + protected function newGlobalMenuItem() { + return $this->newMenuItem() + ->setHeader(pht('Add to Global Home Menu')) + ->setImageIcon('fa-globe') + ->addAttribute( + pht( + 'Add this dashboard to the global home menu, visible to all '. + 'users.')); + } + + protected function newGlobalPermissionDialog() { + return $this->newDialog() + ->setTitle(pht('No Permission')) + ->appendParagraph( + pht( + 'You do not have permission to install items on the global home '. + 'menu.')); + } + + protected function newGlobalConfirmDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Global Home Page')) + ->appendParagraph( + pht( + 'Add dashboard %s as a global menu item on the home page?', + $this->getDashboardDisplayName())) + ->addSubmitButton(pht('Add to Home')); + } + + protected function newPersonalConfirmDialog() { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Personal Home Page')) + ->appendParagraph( + pht( + 'Add dashboard %s as a personal menu item on your home page?', + $this->getDashboardDisplayName())) + ->addSubmitButton(pht('Add to Home')); + } + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php new file mode 100644 index 0000000000..fb77cdebe3 --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php @@ -0,0 +1,143 @@ +viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + final public function setDashboard(PhabricatorDashboard $dashboard) { + $this->dashboard = $dashboard; + return $this; + } + + final public function getDashboard() { + return $this->dashboard; + } + + final public function setMode($mode) { + $this->mode = $mode; + return $this; + } + + final public function getMode() { + return $this->mode; + } + + final public function setRequest(AphrontRequest $request) { + $this->request = $request; + return $this; + } + + final public function getRequest() { + return $this->request; + } + + final public function getWorkflowKey() { + return $this->getPhobjectClassConstant('WORKFLOWKEY', 32); + } + + final public static function getAllWorkflows() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getWorkflowKey') + ->setSortMethod('getOrder') + ->execute(); + } + + final public function getWorkflowMenuItem() { + return $this->newWorkflowMenuItem(); + } + + abstract public function getOrder(); + abstract protected function newWorkflowMenuItem(); + + final protected function newMenuItem() { + return id(new PHUIObjectItemView()) + ->setClickable(true); + } + + abstract public function handleRequest(AphrontRequest $request); + + final protected function newDialog() { + $dashboard = $this->getDashboard(); + + return id(new AphrontDialogView()) + ->setViewer($this->getViewer()) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->addCancelButton($dashboard->getURI()); + } + + final protected function newMenuFromItemMap(array $map) { + $viewer = $this->getViewer(); + $dashboard = $this->getDashboard(); + + $menu = id(new PHUIObjectItemListView()) + ->setViewer($viewer) + ->setFlush(true) + ->setBig(true); + + foreach ($map as $key => $item) { + $item->setHref( + urisprintf( + '/dashboard/install/%d/%s/%s/', + $dashboard->getID(), + $this->getWorkflowKey(), + $key)); + + $menu->addItem($item); + } + + return $menu; + } + + abstract protected function newProfileEngine(); + + final protected function installDashboard($profile_object, $custom_phid) { + $dashboard = $this->getDashboard(); + $engine = $this->newProfileEngine() + ->setProfileObject($profile_object); + + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $config = PhabricatorProfileMenuItemConfiguration::initializeNewItem( + $profile_object, + new PhabricatorDashboardProfileMenuItem(), + $custom_phid); + + $config->setMenuItemProperty('dashboardPHID', $dashboard->getPHID()); + + $xactions = array(); + + $editor = id(new PhabricatorProfileMenuEditor()) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->setContentSourceFromRequest($request); + + $editor->applyTransactions($config, $xactions); + + $done_uri = $engine->getItemURI(urisprintf('view/%d/', $config->getID())); + + return id(new AphrontRedirectResponse()) + ->setURI($done_uri); + } + + final protected function getDashboardDisplayName() { + $dashboard = $this->getDashboard(); + return phutil_tag('strong', array(), $dashboard->getName()); + } + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php new file mode 100644 index 0000000000..eb1be21954 --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php @@ -0,0 +1,99 @@ +getViewer(); + + $target_identifier = null; + + $target_tokens = $request->getArr('target'); + if ($target_tokens) { + $target_identifier = head($target_tokens); + } + + if (!strlen($target_identifier)) { + $target_identifier = $request->getStr('target'); + } + + if (!strlen($target_identifier)) { + $target_identifier = $this->getMode(); + } + + $target = null; + if (strlen($target_identifier)) { + $targets = array(); + + if (ctype_digit($target_identifier)) { + $targets = $this->newQuery() + ->setViewer($viewer) + ->withIDs(array((int)$target_identifier)) + ->execute(); + } + + if (!$targets) { + $targets = $this->newQuery() + ->setViewer($viewer) + ->withPHIDs(array($target_identifier)) + ->execute(); + } + + if ($targets) { + $target = head($targets); + } + } + + if ($target) { + $target_phid = $target->getPHID(); + } else { + $target_phid = null; + } + + if ($target) { + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $target, + PhabricatorPolicyCapability::CAN_EDIT); + } else { + $can_edit = null; + } + + if ($request->isFormPost() && $target && $can_edit) { + if ($request->getBool('confirm')) { + return $this->installDashboard($target, null); + } else { + return $this->newConfirmDialog($target) + ->addHiddenInput('confirm', 1) + ->addHiddenInput('target', $target_phid); + } + } + + $errors = array(); + if (strlen($target_identifier)) { + if (!$target) { + $errors[] = pht('Choose a valid object.'); + } else if (!$can_edit) { + $errors[] = pht( + 'You do not have permission to edit the selected object. '. + 'You can only install dashboards on objects you can edit.'); + } + } else if ($request->getBool('pick')) { + $errors[] = pht( + 'Choose an object to install this dashboard on.'); + } + + $form = $this->newObjectSelectionForm($target) + ->addHiddenInput('pick', 1); + + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Project Menu')) + ->setErrors($errors) + ->appendForm($form) + ->addSubmitButton(pht('Continue')); + } +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php new file mode 100644 index 0000000000..b5f06684ec --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php @@ -0,0 +1,62 @@ +newMenuItem() + ->setHeader(pht('Add to Portal Menu')) + ->setImageIcon('fa-compass') + ->addAttribute( + pht('Add this dashboard to the menu on a portal.')); + } + + protected function newProfileEngine() { + return new PhabricatorDashboardPortalProfileMenuEngine(); + } + + protected function newQuery() { + return new PhabricatorDashboardPortalQuery(); + } + + protected function newConfirmDialog($object) { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Portal Menu')) + ->appendParagraph( + pht( + 'Add the dashboard %s to portal %s?', + $this->getDashboardDisplayName(), + phutil_tag('strong', array(), $object->getName()))) + ->addSubmitButton(pht('Add to Portal')); + } + protected function newObjectSelectionForm($object) { + $viewer = $this->getViewer(); + + if ($object) { + $tokenizer_value = array($object->getPHID()); + } else { + $tokenizer_value = array(); + } + + return id(new AphrontFormView()) + ->setViewer($viewer) + ->appendInstructions( + pht( + 'Select which portal you want to add the dashboard %s to.', + $this->getDashboardDisplayName())) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setName('target') + ->setLimit(1) + ->setLabel(pht('Add to Portal')) + ->setValue($tokenizer_value) + ->setDatasource(new PhabricatorDashboardPortalDatasource())); + } + +} diff --git a/src/applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php b/src/applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php new file mode 100644 index 0000000000..aab70af2cb --- /dev/null +++ b/src/applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php @@ -0,0 +1,63 @@ +newMenuItem() + ->setHeader(pht('Add to Project Menu')) + ->setImageIcon('fa-briefcase') + ->addAttribute( + pht('Add this dashboard to the menu for a project.')); + } + + protected function newProfileEngine() { + return new PhabricatorProjectProfileMenuEngine(); + } + + protected function newQuery() { + return new PhabricatorProjectQuery(); + } + + protected function newConfirmDialog($object) { + return $this->newDialog() + ->setTitle(pht('Add Dashboard to Project Menu')) + ->appendParagraph( + pht( + 'Add the dashboard %s to the menu for project %s?', + $this->getDashboardDisplayName(), + phutil_tag('strong', array(), $object->getName()))) + ->addSubmitButton(pht('Add to Project')); + } + + protected function newObjectSelectionForm($object) { + $viewer = $this->getViewer(); + + if ($object) { + $tokenizer_value = array($object->getPHID()); + } else { + $tokenizer_value = array(); + } + + return id(new AphrontFormView()) + ->setViewer($viewer) + ->appendInstructions( + pht( + 'Select which project menu you want to add the dashboard %s to.', + $this->getDashboardDisplayName())) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setName('target') + ->setLimit(1) + ->setLabel(pht('Add to Project')) + ->setValue($tokenizer_value) + ->setDatasource(new PhabricatorProjectDatasource())); + } + +} diff --git a/src/applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php b/src/applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php new file mode 100644 index 0000000000..898f9efa2f --- /dev/null +++ b/src/applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php @@ -0,0 +1,47 @@ +buildResults(); + return $this->filterResultsAgainstTokens($results); + } + + protected function renderSpecialTokens(array $values) { + return $this->renderTokensFromResults($this->buildResults(), $values); + } + + public function buildResults() { + $query = new PhabricatorDashboardPortalQuery(); + + // TODO: Actually query by name so this scales past 100 portals. + + $portals = $this->executeQuery($query); + + $results = array(); + foreach ($portals as $portal) { + $result = id(new PhabricatorTypeaheadResult()) + ->setName($portal->getObjectName().' '.$portal->getName()) + ->setPHID($portal->getPHID()) + ->setIcon('fa-compass'); + + $results[] = $result; + } + + return $results; + } + +} diff --git a/src/applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php index d8c7ee82b1..20c8d2985b 100644 --- a/src/applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php @@ -165,8 +165,9 @@ final class PhabricatorProjectPointsProfileMenuItem ), $bar); - $item = $this->newItemView() - ->newProgressBar($bar); + $item = $this->newItemView(); + + $item->newProgressBar($bar); return array( $item, diff --git a/src/applications/search/menuitem/PhabricatorProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorProfileMenuItem.php index 118815393d..5f378a6290 100644 --- a/src/applications/search/menuitem/PhabricatorProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorProfileMenuItem.php @@ -5,7 +5,6 @@ abstract class PhabricatorProfileMenuItem extends Phobject { private $viewer; private $engine; - public function getMenuItemTypeIcon() { return null; } diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index 40dd8f0a4b..05747c7ce6 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -299,6 +299,8 @@ final class PHUIObjectItemView extends AphrontTagView { if ($this->disabled) { $item_classes[] = 'phui-oi-disabled'; + } else { + $item_classes[] = 'phui-oi-enabled'; } switch ($this->effect) { diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css b/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css index 2d2163f9e9..3ad8f3a198 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-big-ui.css @@ -70,21 +70,29 @@ } .phui-oi-list-big .phui-oi-linked-container { - border: 1px solid {$lightblueborder}; + border-width: 1px; + border-style: solid; border-radius: 4px; - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.035); } -.phui-oi-list-big .phui-oi-disabled { - border-radius: 4px; - background: {$lightgreybackground}; +.phui-oi-list-big .phui-oi-enabled.phui-oi-linked-container { + border-color: {$lightblueborder}; + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.05); +} + +.phui-oi-list-big .phui-oi-disabled.phui-oi-linked-container { + border-color: {$greybackground}; +} + +.phui-oi-list-big .phui-oi-disabled .phui-oi-image-icon .phui-icon-view { + color: {$darkgreybackground}; } .device-desktop .phui-oi-linked-container { cursor: pointer; } -.device-desktop .phui-oi-linked-container:hover { +.device-desktop .phui-oi-enabled.phui-oi-linked-container:hover { background-color: {$hoverblue}; border-color: {$blueborder}; }