From fffa7ffb6c525722483f0514aa4f15a4a2ceae37 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 16 Jan 2013 09:00:11 -0800 Subject: [PATCH] Allow users to customize applicaiton tile sizes Summary: See discussion in D4438. Allows users to customize application tiles, and implements generally reasonable defaults so they hopefully won't. Sizes are "invisible" (internal only, used to hide admin apps from non-admins), "hidden" (hide by default, show after clicking "Show More Applications"), "show" (show a small square tile) and "full" (show a full-width tile with subtitle). Test Plan: Default view for a non-admin: {F29375} Adjusted settings, hidden: {F29373} Adjusted settings, shown: {F29374} Reviewers: chad, btrahan Reviewed By: chad CC: aran Differential Revision: https://secure.phabricator.com/D4439 --- scripts/celerity_mapper.php | 1 + src/__phutil_library_map__.php | 2 + .../base/PhabricatorApplication.php | 35 ++++ .../PhabricatorDirectoryController.php | 156 +++++++++++++----- ...habricatorSettingsPanelHomePreferences.php | 108 ++++++++++++ .../storage/PhabricatorUserPreferences.php | 1 + src/view/layout/PhabricatorMenuItemView.php | 45 ++++- .../core/behavior-home-reveal-tiles.js | 29 ++++ 8 files changed, 335 insertions(+), 42 deletions(-) create mode 100644 src/applications/settings/panel/PhabricatorSettingsPanelHomePreferences.php create mode 100644 webroot/rsrc/js/application/core/behavior-home-reveal-tiles.js diff --git a/scripts/celerity_mapper.php b/scripts/celerity_mapper.php index eb57ecc97e..fd24b0eaa4 100755 --- a/scripts/celerity_mapper.php +++ b/scripts/celerity_mapper.php @@ -57,6 +57,7 @@ $package_spec = array( 'phabricator-textareautils', 'phabricator-file-upload', 'javelin-behavior-global-drag-and-drop', + 'javelin-behavior-phabricator-home-reveal-tiles', ), 'core.pkg.css' => array( 'phabricator-core-css', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7cb1d89ef7..8fecd377ae 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1185,6 +1185,7 @@ phutil_register_library_map(array( 'PhabricatorSettingsPanelDisplayPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php', 'PhabricatorSettingsPanelEmailAddresses' => 'applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php', 'PhabricatorSettingsPanelEmailPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelEmailPreferences.php', + 'PhabricatorSettingsPanelHomePreferences' => 'applications/settings/panel/PhabricatorSettingsPanelHomePreferences.php', 'PhabricatorSettingsPanelLDAP' => 'applications/settings/panel/PhabricatorSettingsPanelLDAP.php', 'PhabricatorSettingsPanelOAuth' => 'applications/settings/panel/PhabricatorSettingsPanelOAuth.php', 'PhabricatorSettingsPanelPassword' => 'applications/settings/panel/PhabricatorSettingsPanelPassword.php', @@ -2525,6 +2526,7 @@ phutil_register_library_map(array( 'PhabricatorSettingsPanelDisplayPreferences' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelEmailAddresses' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelEmailPreferences' => 'PhabricatorSettingsPanel', + 'PhabricatorSettingsPanelHomePreferences' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelLDAP' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelOAuth' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelPassword' => 'PhabricatorSettingsPanel', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 68c1ed74ff..3f1a352f0b 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -18,6 +18,11 @@ abstract class PhabricatorApplication { const GROUP_DEVELOPER = 'developer'; const GROUP_MISC = 'misc'; + const TILE_INVISIBLE = 'invisible'; + const TILE_HIDE = 'hide'; + const TILE_SHOW = 'show'; + const TILE_FULL = 'full'; + public static function getApplicationGroups() { return array( self::GROUP_CORE => pht('Core Applications'), @@ -30,6 +35,17 @@ abstract class PhabricatorApplication { ); } + public static function getTileDisplayName($constant) { + $names = array( + self::TILE_INVISIBLE => pht('Invisible'), + self::TILE_HIDE => pht('Hidden'), + self::TILE_SHOW => pht('Show Small Tile'), + self::TILE_FULL => pht('Show Large Tile'), + ); + return idx($names, $constant); + } + + /* -( Application Information )-------------------------------------------- */ @@ -96,6 +112,25 @@ abstract class PhabricatorApplication { return array(); } + public function getDefaultTileDisplay(PhabricatorUser $user) { + switch ($this->getApplicationGroup()) { + case self::GROUP_CORE: + return self::TILE_FULL; + case self::GROUP_UTILITIES: + case self::GROUP_DEVELOPER: + return self::TILE_HIDE; + case self::GROUP_ADMIN: + if ($user->getIsAdmin()) { + return self::TILE_SHOW; + } else { + return self::TILE_INVISIBLE; + } + break; + default: + return self::TILE_SHOW; + } + } + /* -( URI Routing )-------------------------------------------------------- */ diff --git a/src/applications/directory/controller/PhabricatorDirectoryController.php b/src/applications/directory/controller/PhabricatorDirectoryController.php index 2e517a333d..81ca7225da 100644 --- a/src/applications/directory/controller/PhabricatorDirectoryController.php +++ b/src/applications/directory/controller/PhabricatorDirectoryController.php @@ -25,51 +25,129 @@ abstract class PhabricatorDirectoryController extends PhabricatorController { foreach ($applications as $key => $application) { if (!$application->shouldAppearInLaunchView()) { + // Remove hidden applications (usually internal stuff). + unset($applications[$key]); + } + $invisible = PhabricatorApplication::TILE_INVISIBLE; + if ($application->getDefaultTileDisplay($user) == $invisible) { + // Remove invisible applications (e.g., admin apps for non-admins). unset($applications[$key]); } } - $groups = PhabricatorApplication::getApplicationGroups(); - - $applications = msort($applications, 'getApplicationOrder'); - $applications = mgroup($applications, 'getApplicationGroup'); - $applications = array_select_keys($applications, array_keys($groups)); - - $view = array(); - foreach ($applications as $group => $application_list) { - $status = array(); - foreach ($application_list as $key => $application) { - $status[$key] = $application->loadStatus($user); - } - - $views = array(); - foreach ($application_list as $key => $application) { - $tile = id(new PhabricatorApplicationLaunchView()) - ->setApplication($application) - ->setApplicationStatus(idx($status, $key, array())) - ->setUser($user); - - if ($group == PhabricatorApplication::GROUP_CORE) { - $tile->setFullWidth(true); - } - - $views[] = $tile; - } - - while (count($views) % 4) { - $views[] = id(new PhabricatorApplicationLaunchView()); - } - - $nav->addLabel($groups[$group]); - $nav->addCustomBlock( - phutil_render_tag( - 'div', - array( - 'class' => 'application-tile-group', - ), - id(new AphrontNullView())->appendChild($views)->render())); + $status = array(); + foreach ($applications as $key => $application) { + $status[get_class($application)] = $application->loadStatus($user); } + $tile_groups = array(); + $prefs = $user->loadPreferences()->getPreference( + PhabricatorUserPreferences::PREFERENCE_APP_TILES, + array()); + foreach ($applications as $key => $application) { + $display = idx( + $prefs, + get_class($application), + $application->getDefaultTileDisplay($user)); + $tile_groups[$display][] = $application; + } + + $tile_groups = array_select_keys( + $tile_groups, + array( + PhabricatorApplication::TILE_FULL, + PhabricatorApplication::TILE_SHOW, + PhabricatorApplication::TILE_HIDE, + )); + + foreach ($tile_groups as $tile_display => $tile_group) { + if (!$tile_group) { + continue; + } + + $tile_group = msort($tile_group, 'getApplicationOrder'); + + $is_small_tiles = ($tile_display == PhabricatorApplication::TILE_SHOW) || + ($tile_display == PhabricatorApplication::TILE_HIDE); + + if ($is_small_tiles) { + $groups = PhabricatorApplication::getApplicationGroups(); + $tile_group = mgroup($tile_group, 'getApplicationGroup'); + $tile_group = array_select_keys($tile_group, array_keys($groups)); + } else { + $tile_group = array($tile_group); + } + + $is_hide = ($tile_display == PhabricatorApplication::TILE_HIDE); + if ($is_hide) { + $show_item_id = celerity_generate_unique_node_id(); + $show_tiles_id = celerity_generate_unique_node_id(); + + $show_item = id(new PhabricatorMenuItemView()) + ->setName(pht('Show More Applications')) + ->setHref('#') + ->addSigil('home-show-applications') + ->setID($show_item_id); + + $hide_item = id(new PhabricatorMenuItemView()) + ->setName(pht('Show Fewer Applications')) + ->setHref('#') + ->addSigil('home-hide-applications'); + + $nav->addMenuItem($show_item); + $nav->addCustomBlock( + '
'); + + Javelin::initBehavior('phabricator-home-reveal-tiles', array( + 'tilesID' => $show_tiles_id, + 'showID' => $show_item_id, + )); + } + + foreach ($tile_group as $group => $application_list) { + $tiles = array(); + foreach ($application_list as $key => $application) { + $tile = id(new PhabricatorApplicationLaunchView()) + ->setApplication($application) + ->setApplicationStatus( + idx($status, get_class($application), array())) + ->setUser($user); + + if ($tile_display == PhabricatorApplication::TILE_FULL) { + $tile->setFullWidth(true); + } + + $tiles[] = $tile; + } + + if ($is_small_tiles) { + while (count($tiles) % 4) { + $tiles[] = id(new PhabricatorApplicationLaunchView()); + } + $nav->addLabel($groups[$group]); + } + $nav->addCustomBlock( + phutil_render_tag( + 'div', + array( + 'class' => 'application-tile-group', + ), + id(new AphrontNullView())->appendChild($tiles)->render())); + } + + $is_hide = ($tile_display == PhabricatorApplication::TILE_HIDE); + if ($is_hide) { + $nav->addMenuItem($hide_item); + $nav->addCustomBlock('
'); + } + } + + $nav->addFilter( + '', + pht('Customize Applications...'), + '/settings/panel/home/'); $nav->addClass('phabricator-side-menu-home'); $nav->selectFilter(null); diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelHomePreferences.php b/src/applications/settings/panel/PhabricatorSettingsPanelHomePreferences.php new file mode 100644 index 0000000000..6aaa00e819 --- /dev/null +++ b/src/applications/settings/panel/PhabricatorSettingsPanelHomePreferences.php @@ -0,0 +1,108 @@ +getUser(); + $preferences = $user->loadPreferences(); + + $apps = PhabricatorApplication::getAllInstalledApplications(); + $pref_tiles = PhabricatorUserPreferences::PREFERENCE_APP_TILES; + $tiles = $preferences->getPreference($pref_tiles, array()); + + if ($request->isFormPost()) { + $values = $request->getArr('tile'); + foreach ($apps as $app) { + $key = get_class($app); + $value = idx($values, $key); + switch ($value) { + case PhabricatorApplication::TILE_FULL: + case PhabricatorApplication::TILE_SHOW: + case PhabricatorApplication::TILE_HIDE: + $tiles[$key] = $value; + break; + default: + unset($tiles[$key]); + break; + } + } + $preferences->setPreference($pref_tiles, $tiles); + $preferences->save(); + + return id(new AphrontRedirectResponse()) + ->setURI($this->getPanelURI('?saved=true')); + } + + $header = id(new PhabricatorHeaderView()) + ->setHeader(pht('Home Page Preferences')); + + $form = id(new AphrontFormView()) + ->setFlexible(true) + ->setUser($user); + + $apps = msort($apps, 'getName'); + foreach ($apps as $app) { + if (!$app->shouldAppearInLaunchView()) { + continue; + } + + $default = $app->getDefaultTileDisplay($user); + if ($default == PhabricatorApplication::TILE_INVISIBLE) { + continue; + } + + $default_name = PhabricatorApplication::getTileDisplayName($default); + + $hide = PhabricatorApplication::TILE_HIDE; + $show = PhabricatorApplication::TILE_SHOW; + $full = PhabricatorApplication::TILE_FULL; + + $key = get_class($app); + $form->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel($app->getName()) + ->setName('tile['.$key.']') + ->setOptions( + array( + $hide => PhabricatorApplication::getTileDisplayName($hide), + 'default' => pht('Use Default (%s)', $default_name), + $show => PhabricatorApplication::getTileDisplayName($show), + $full => PhabricatorApplication::getTileDisplayName($full), + )) + ->setValue(idx($tiles, $key, 'default'))); + } + + $form + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Save Preferences')); + + $error_view = null; + if ($request->getStr('saved') === 'true') { + $error_view = id(new AphrontErrorView()) + ->setTitle('Preferences Saved') + ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) + ->setErrors(array('Your preferences have been saved.')); + } + + return array( + $header, + $error_view, + $form, + ); + } +} + diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php index 28bd1fbf9e..ebea7374f8 100644 --- a/src/applications/settings/storage/PhabricatorUserPreferences.php +++ b/src/applications/settings/storage/PhabricatorUserPreferences.php @@ -20,6 +20,7 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO { const PREFERENCE_DIFFUSION_SYMBOLS = 'diffusion-symbols'; const PREFERENCE_NAV_WIDTH = 'nav-width'; + const PREFERENCE_APP_TILES = 'app-tiles'; protected $userPHID; protected $preferences = array(); diff --git a/src/view/layout/PhabricatorMenuItemView.php b/src/view/layout/PhabricatorMenuItemView.php index 14620bea04..7224543975 100644 --- a/src/view/layout/PhabricatorMenuItemView.php +++ b/src/view/layout/PhabricatorMenuItemView.php @@ -16,6 +16,33 @@ final class PhabricatorMenuItemView extends AphrontView { private $sortOrder = 1.0; private $icon; private $selected; + private $sigils = array(); + private $metadata; + private $id; + + public function setID($id) { + $this->id = $id; + return $this; + } + + public function setProperty($property) { + $this->property = $property; + return $this; + } + + public function getProperty() { + return $this->property; + } + + public function setMetadata($metadata) { + $this->metadata = $metadata; + return $this; + } + + public function addSigil($sigil) { + $this->sigils[] = $sigil; + return $this; + } public function setSelected($selected) { $this->selected = $selected; @@ -125,12 +152,24 @@ final class PhabricatorMenuItemView extends AphrontView { phutil_escape_html($this->name.$external)); } + $sigils = $this->sigils; + if ($this->workflow) { + $sigils[] = 'workflow'; + } + if ($sigils) { + $sigils = implode(' ', $sigils); + } else { + $sigils = null; + } + return javelin_render_tag( $this->href ? 'a' : 'div', array( - 'class' => implode(' ', $classes), - 'href' => $this->href, - 'sigil' => $this->workflow ? 'workflow' : null, + 'class' => implode(' ', $classes), + 'href' => $this->href, + 'sigil' => $sigils, + 'meta' => $this->metadata, + 'id' => $this->id, ), $this->renderChildren(). $name); diff --git a/webroot/rsrc/js/application/core/behavior-home-reveal-tiles.js b/webroot/rsrc/js/application/core/behavior-home-reveal-tiles.js new file mode 100644 index 0000000000..14d77841e8 --- /dev/null +++ b/webroot/rsrc/js/application/core/behavior-home-reveal-tiles.js @@ -0,0 +1,29 @@ +/** + * @provides javelin-behavior-phabricator-home-reveal-tiles + * @requires javelin-behavior + * javelin-stratcom + * javelin-dom + * @javelin + */ + +JX.behavior('phabricator-home-reveal-tiles', function(config) { + JX.Stratcom.listen( + 'click', + 'home-show-applications', + function(e) { + e.kill(); + + JX.DOM.show(JX.$(config.tilesID)); + JX.DOM.hide(JX.$(config.showID)); + }); + + JX.Stratcom.listen( + 'click', + 'home-hide-applications', + function(e) { + e.kill(); + + JX.DOM.hide(JX.$(config.tilesID)); + JX.DOM.show(JX.$(config.showID)); + }); +});