1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-28 00:10:57 +01:00

Remove application small/hidden tiles

Summary:
Ref T5176. This paves the way for the redesign by making the homepage editor thing a little more manageable/coherent.

Not perfect, but we can clean it up a bit after the new design.

Test Plan:
Home page:

{F162093}

New "Pinned Applications" settings panel (this supports drag-and-drop to reorder):

{F162094}

Pin an app:

{F162095}

Unpin an app:

{F162096}

Reviewers: btrahan, chad

Reviewed By: chad

Subscribers: epriestley

Maniphest Tasks: T5176

Differential Revision: https://secure.phabricator.com/D9332
This commit is contained in:
epriestley 2014-05-29 14:20:16 -07:00
parent 09a3506821
commit 24eacaa032
7 changed files with 263 additions and 289 deletions

View file

@ -100,7 +100,6 @@ return array(
'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd',
'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae',
'rsrc/css/application/search/search-results.css' => 'f240504c',
'rsrc/css/application/settings/settings.css' => 'ea8f5915',
'rsrc/css/application/slowvote/slowvote.css' => '266df6a1',
'rsrc/css/application/subscriptions/subscribers-list.css' => '5bb30c78',
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
@ -468,6 +467,7 @@ return array(
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'c021950a',
'rsrc/js/core/behavior-refresh-csrf.js' => '7814b593',
'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45',
'rsrc/js/core/behavior-reorder-applications.js' => 'a8e3795d',
'rsrc/js/core/behavior-reveal-content.js' => '8f24abfc',
'rsrc/js/core/behavior-search-typeahead.js' => '86549ee3',
'rsrc/js/core/behavior-select-on-click.js' => '0e34ca02',
@ -629,6 +629,7 @@ return array(
'javelin-behavior-releeph-request-state-change' => 'd259e7c9',
'javelin-behavior-releeph-request-typeahead' => 'cd9e7094',
'javelin-behavior-remarkup-preview' => 'f7379f45',
'javelin-behavior-reorder-applications' => 'a8e3795d',
'javelin-behavior-repository-crossreference' => '8ab282be',
'javelin-behavior-search-reorder-queries' => '37871df4',
'javelin-behavior-select-on-click' => '0e34ca02',
@ -720,7 +721,6 @@ return array(
'phabricator-project-tag-css' => '095c9404',
'phabricator-remarkup-css' => '80c3a48c',
'phabricator-search-results-css' => 'f240504c',
'phabricator-settings-css' => 'ea8f5915',
'phabricator-shaped-request' => '7cbe244b',
'phabricator-side-menu-view-css' => 'c1986b85',
'phabricator-slowvote-css' => '266df6a1',
@ -1612,6 +1612,14 @@ return array(
1 => 'javelin-dom',
2 => 'javelin-stratcom',
),
'a8e3795d' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-workflow',
3 => 'javelin-dom',
4 => 'phabricator-draggable-list',
),
'a9aaba0c' =>
array(
0 => 'javelin-behavior',

View file

@ -25,13 +25,10 @@ abstract class PhabricatorHomeController extends PhabricatorController {
->setViewer($user)
->withInstalled(true)
->withUnlisted(false)
->withLaunchable(true)
->execute();
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).
@ -39,115 +36,45 @@ abstract class PhabricatorHomeController extends PhabricatorController {
}
}
$status = array();
foreach ($applications as $key => $application) {
$status[get_class($application)] = $application->loadStatus($user);
}
$pinned = $user->loadPreferences()->getPinnedApplications(
$applications,
$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;
}
// Put "Applications" at the bottom.
$meta_app = 'PhabricatorApplicationApplications';
$pinned = array_fuse($pinned);
unset($pinned[$meta_app]);
$pinned[$meta_app] = $meta_app;
$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) {
$tiles = array();
foreach ($pinned as $pinned_application) {
if (empty($applications[$pinned_application])) {
continue;
}
$is_small_tiles = ($tile_display == PhabricatorApplication::TILE_SHOW) ||
($tile_display == PhabricatorApplication::TILE_HIDE);
$application = $applications[$pinned_application];
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);
}
$tile = id(new PhabricatorApplicationLaunchView())
->setApplication($application)
->setApplicationStatus($application->loadStatus($user))
->setUser($user);
$is_hide = ($tile_display == PhabricatorApplication::TILE_HIDE);
if ($is_hide) {
$show_item_id = celerity_generate_unique_node_id();
$hide_item_id = celerity_generate_unique_node_id();
$show_item = id(new PHUIListItemView())
->setName(pht('Show More Applications'))
->setHref('#')
->addSigil('reveal-content')
->setID($show_item_id);
$hide_item = id(new PHUIListItemView())
->setName(pht('Show Fewer Applications'))
->setHref('#')
->setStyle('display: none')
->setID($hide_item_id)
->addSigil('reveal-content');
$nav->addMenuItem($show_item);
$tile_ids = array($hide_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);
$tiles[] = $tile;
}
$group_id = celerity_generate_unique_node_id();
$tile_ids[] = $group_id;
$nav->addCustomBlock(
phutil_tag(
'div',
array(
'class' => 'application-tile-group',
'id' => $group_id,
'style' => ($is_hide ? 'display: none' : null),
),
mpull($tiles, 'render')));
}
if ($is_hide) {
Javelin::initBehavior('phabricator-reveal-content');
$show_item->setMetadata(
array(
'showIDs' => $tile_ids,
'hideIDs' => array($show_item_id),
));
$hide_item->setMetadata(
array(
'showIDs' => array($show_item_id),
'hideIDs' => $tile_ids,
));
$nav->addMenuItem($hide_item);
}
$tiles[] = $tile;
}
$nav->addCustomBlock(
phutil_tag(
'div',
array(
'class' => 'application-tile-group',
),
$tiles));
$nav->addFilter(
'',
pht('Customize Applications...'),
'/settings/panel/home/');
$nav->addClass('phabricator-side-menu-home');
$nav->selectFilter(null);

View file

@ -19,194 +19,179 @@ final class PhabricatorSettingsPanelHomePreferences
$user = $request->getUser();
$preferences = $user->loadPreferences();
require_celerity_resource('phabricator-settings-css');
$apps = id(new PhabricatorApplicationQuery())
->setViewer($user)
->withInstalled(true)
->withUnlisted(false)
->withLaunchable(true)
->execute();
$pref_tiles = PhabricatorUserPreferences::PREFERENCE_APP_TILES;
$tiles = $preferences->getPreference($pref_tiles, array());
$pinned = $preferences->getPinnedApplications($apps, $user);
if ($request->isFormPost()) {
$values = $request->getArr('tile');
$app_list = array();
foreach ($pinned as $app) {
if (isset($apps[$app])) {
$app_list[$app] = $apps[$app];
}
}
if ($request->getBool('add')) {
$options = array();
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;
$options[get_class($app)] = $app->getName();
}
asort($options);
unset($options['PhabricatorApplicationApplications']);
if ($request->isFormPost()) {
$pin = $request->getStr('pin');
if (isset($options[$pin]) && !in_array($pin, $pinned)) {
$pinned[] = $pin;
$preferences->setPreference(
PhabricatorUserPreferences::PREFERENCE_APP_PINNED,
$pinned);
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI());
}
}
$preferences->setPreference($pref_tiles, $tiles);
$options_control = id(new AphrontFormSelectControl())
->setName('pin')
->setLabel(pht('Application'))
->setOptions($options)
->setDisabledOptions(array_keys($app_list));
$form = id(new AphrontFormView())
->setUser($user)
->addHiddenInput('add', 'true')
->appendRemarkupInstructions(
pht('Choose an application to pin to your home page.'))
->appendChild($options_control);
$dialog = id(new AphrontDialogView())
->setUser($user)
->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle(pht('Pin Application'))
->appendChild($form->buildLayoutView())
->addSubmitButton(pht('Pin Application'))
->addCancelButton($this->getPanelURI());
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$unpin = $request->getStr('unpin');
if ($unpin) {
$app = idx($apps, $unpin);
if ($app) {
if ($request->isFormPost()) {
$pinned = array_diff($pinned, array($unpin));
$preferences->setPreference(
PhabricatorUserPreferences::PREFERENCE_APP_PINNED,
$pinned);
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI());
}
$dialog = id(new AphrontDialogView())
->setUser($user)
->setTitle(pht('Unpin Application'))
->appendParagraph(
pht(
'Unpin the %s application from your home page?',
phutil_tag('strong', array(), $app->getName())))
->addSubmitButton(pht('Unpin Application'))
->addCanceLButton($this->getPanelURI());
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
$order = $request->getStrList('order');
if ($order && $request->validateCSRF()) {
$preferences->setPreference(
PhabricatorUserPreferences::PREFERENCE_APP_PINNED,
$order);
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?saved=true'));
->setURI($this->getPanelURI());
}
$form = id(new AphrontFormView())
->setUser($user);
$list_id = celerity_generate_unique_node_id();
$group_map = PhabricatorApplication::getApplicationGroups();
$list = id(new PHUIObjectItemListView())
->setUser($user)
->setID($list_id)
->setFlush(true);
$output = array();
Javelin::initBehavior(
'reorder-applications',
array(
'listID' => $list_id,
'panelURI' => $this->getPanelURI(),
));
$app_groups = mgroup($apps, 'getApplicationGroup');
$app_groups = array_select_keys($app_groups, array_keys($group_map));
foreach ($app_groups as $group => $apps) {
$group_name = $group_map[$group];
$rows = array();
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);
$default_radio_button_status =
(idx($tiles, $key, 'default') == 'default') ? 'checked' : null;
$hide_radio_button_status =
(idx($tiles, $key, 'default') == $hide) ? 'checked' : null;
$show_radio_button_status =
(idx($tiles, $key, 'default') == $show) ? 'checked' : null;
$full_radio_button_status =
(idx($tiles, $key, 'default') == $full) ? 'checked' : null;
$default_radio_button = phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'tile['.$key.']',
'value' => 'default',
'checked' => $default_radio_button_status,
));
$hide_radio_button = phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'tile['.$key.']',
'value' => $hide,
'checked' => $hide_radio_button_status,
));
$show_radio_button = phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'tile['.$key.']',
'value' => $show,
'checked' => $show_radio_button_status,
));
$full_radio_button = phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'tile['.$key.']',
'value' => $full,
'checked' => $full_radio_button_status,
));
$desc = $app->getShortDescription();
$app_column = hsprintf(
"<strong>%s</strong><br/ >%s, <em>Default: %s</em>",
$app->getName(), $desc, $default_name);
$rows[] = array(
$app_column,
$default_radio_button,
$hide_radio_button,
$show_radio_button,
$full_radio_button,
);
}
if (empty($rows)) {
foreach ($app_list as $key => $application) {
if ($key == 'PhabricatorApplicationApplications') {
continue;
}
$table = new AphrontTableView($rows);
$icon = $application->getIconName();
if (!$icon) {
$icon = 'application';
}
$table
->setClassName('phabricator-settings-homepagetable')
->setHeaders(
array(
pht('Applications'),
pht('Default'),
pht('Hidden'),
pht('Small'),
pht('Large'),
))
->setColumnClasses(
array(
'',
'fixed',
'fixed',
'fixed',
'fixed',
));
$icon_view = javelin_tag(
'span',
array(
'class' => 'phui-icon-view '.
'sprite-apps-large apps-'.$icon.'-dark-large',
'aural' => false,
),
'');
$item = id(new PHUIObjectItemView())
->setHeader($application->getName())
->setImageIcon($icon_view)
->addAttribute($application->getShortDescription())
->setGrippable(true);
$panel = id(new PHUIObjectBoxView())
->setHeaderText($group_name)
->appendChild($table);
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-times')
->setHref($this->getPanelURI().'?unpin='.$key)
->setWorkflow(true));
$output[] = $panel;
}
$item->addSigil('pinned-application');
$item->setMetadata(
array(
'applicationClass' => $key,
));
$save_button =
id(new AphrontFormSubmitControl())
->setValue(pht('Save Preferences'));
$output[] = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_LARGE)
->addClass('phabricator-settings-homepagetable-button')
->appendChild($save_button);
$form->appendChild($output);
$error_view = null;
if ($request->getStr('saved') === 'true') {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Preferences Saved'))
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setErrors(array(pht('Your preferences have been saved.')));
$list->addItem($item);
}
$header = id(new PHUIHeaderView())
->setHeader(pht('Home Page Preferences'));
->setHeader(pht('Pinned Applications'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setText(pht('Pin Application'))
->setHref($this->getPanelURI().'?add=true')
->setWorkflow(true)
->setIcon(
id(new PHUIIconView())
->setIconFont('fa-thumb-tack')));
$form = id(new PHUIBoxView())
->addClass('phabricator-settings-homepagetable-wrap')
->appendChild($form);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($list);
return array($header, $error_view, $form);
return $box;
}
}

View file

@ -24,6 +24,7 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO {
const PREFERENCE_NAV_COLLAPSED = 'nav-collapsed';
const PREFERENCE_NAV_WIDTH = 'nav-width';
const PREFERENCE_APP_TILES = 'app-tiles';
const PREFERENCE_APP_PINNED = 'app-pinned';
const PREFERENCE_DIFF_FILETREE = 'diff-filetree';
@ -55,4 +56,31 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO {
return $this;
}
public function getPinnedApplications(array $apps, PhabricatorUser $viewer) {
$pref_pinned = PhabricatorUserPreferences::PREFERENCE_APP_PINNED;
$pinned = $this->getPreference($pref_pinned);
if ($pinned) {
return $pinned;
}
$pref_tiles = PhabricatorUserPreferences::PREFERENCE_APP_TILES;
$tiles = $this->getPreference($pref_tiles, array());
$large = array();
foreach ($apps as $app) {
$tile = $app->getDefaultTileDisplay($viewer);
if (isset($tiles[get_class($app)])) {
$tile = $tiles[get_class($app)];
}
if ($tile == PhabricatorApplication::TILE_FULL) {
$large[] = get_class($app);
}
}
return $large;
}
}

View file

@ -7,6 +7,7 @@ final class AphrontFormSelectControl extends AphrontFormControl {
}
private $options;
private $disabledOptions = array();
public function setOptions(array $options) {
$this->options = $options;
@ -17,6 +18,11 @@ final class AphrontFormSelectControl extends AphrontFormControl {
return $this->options;
}
public function setDisabledOptions(array $disabled) {
$this->disabledOptions = $disabled;
return $this;
}
protected function renderInput() {
return self::renderSelectTag(
$this->getValue(),
@ -25,15 +31,17 @@ final class AphrontFormSelectControl extends AphrontFormControl {
'name' => $this->getName(),
'disabled' => $this->getDisabled() ? 'disabled' : null,
'id' => $this->getID(),
));
),
$this->disabledOptions);
}
public static function renderSelectTag(
$selected,
array $options,
array $attrs = array()) {
array $attrs = array(),
array $disabled = array()) {
$option_tags = self::renderOptions($selected, $options);
$option_tags = self::renderOptions($selected, $options, $disabled);
return javelin_tag(
'select',
@ -41,7 +49,12 @@ final class AphrontFormSelectControl extends AphrontFormControl {
$option_tags);
}
private static function renderOptions($selected, array $options) {
private static function renderOptions(
$selected,
array $options,
array $disabled = array()) {
$disabled = array_fuse($disabled);
$tags = array();
foreach ($options as $value => $thing) {
if (is_array($thing)) {
@ -57,6 +70,7 @@ final class AphrontFormSelectControl extends AphrontFormControl {
array(
'selected' => ($value == $selected) ? 'selected' : null,
'value' => $value,
'disabled' => isset($disabled[$value]) ? 'disabled' : null,
),
$thing);
}

View file

@ -1,25 +0,0 @@
/**
* @provides phabricator-settings-css
*/
.phabricator-settings-homepagetable .fixed {
width: 48px;
text-align: center;
}
.phabricator-settings-homepagetable td em {
color: {$lightgreytext};
}
.phabricator-settings-homepagetable-button .aphront-form-input {
margin: 0;
width: auto;
}
.phabricator-settings-homepagetable-button .aphront-form-control {
padding: 0;
}
.phabricator-settings-homepagetable-wrap .phui-form-view {
padding: 0;
}

View file

@ -0,0 +1,37 @@
/**
* @provides javelin-behavior-reorder-applications
* @requires javelin-behavior
* javelin-stratcom
* javelin-workflow
* javelin-dom
* phabricator-draggable-list
*/
JX.behavior('reorder-applications', function(config) {
var root = JX.$(config.listID);
var list = new JX.DraggableList('pinned-application', root)
.setFindItemsHandler(function() {
return JX.DOM.scry(root, 'li', 'pinned-application');
});
list.listen('didDrop', function(node, after) {
var nodes = list.findItems();
var order = [];
var key;
for (var ii = 0; ii < nodes.length; ii++) {
key = JX.Stratcom.getData(nodes[ii]).applicationClass;
if (key) {
order.push(key);
}
}
list.lock();
JX.DOM.alterClass(node, 'drag-sending', true);
new JX.Workflow(config.panelURI, {order: order.join()})
.start();
});
});