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)); + }); +});