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

View file

@ -25,13 +25,10 @@ abstract class PhabricatorHomeController extends PhabricatorController {
->setViewer($user) ->setViewer($user)
->withInstalled(true) ->withInstalled(true)
->withUnlisted(false) ->withUnlisted(false)
->withLaunchable(true)
->execute(); ->execute();
foreach ($applications as $key => $application) { foreach ($applications as $key => $application) {
if (!$application->shouldAppearInLaunchView()) {
// Remove hidden applications (usually internal stuff).
unset($applications[$key]);
}
$invisible = PhabricatorApplication::TILE_INVISIBLE; $invisible = PhabricatorApplication::TILE_INVISIBLE;
if ($application->getDefaultTileDisplay($user) == $invisible) { if ($application->getDefaultTileDisplay($user) == $invisible) {
// Remove invisible applications (e.g., admin apps for non-admins). // Remove invisible applications (e.g., admin apps for non-admins).
@ -39,115 +36,45 @@ abstract class PhabricatorHomeController extends PhabricatorController {
} }
} }
$status = array(); $pinned = $user->loadPreferences()->getPinnedApplications(
foreach ($applications as $key => $application) { $applications,
$status[get_class($application)] = $application->loadStatus($user); $user);
}
$tile_groups = array(); // Put "Applications" at the bottom.
$prefs = $user->loadPreferences()->getPreference( $meta_app = 'PhabricatorApplicationApplications';
PhabricatorUserPreferences::PREFERENCE_APP_TILES, $pinned = array_fuse($pinned);
array()); unset($pinned[$meta_app]);
foreach ($applications as $key => $application) { $pinned[$meta_app] = $meta_app;
$display = idx(
$prefs,
get_class($application),
$application->getDefaultTileDisplay($user));
$tile_groups[$display][] = $application;
}
$tile_groups = array_select_keys( $tiles = array();
$tile_groups, foreach ($pinned as $pinned_application) {
array( if (empty($applications[$pinned_application])) {
PhabricatorApplication::TILE_FULL,
PhabricatorApplication::TILE_SHOW,
PhabricatorApplication::TILE_HIDE,
));
foreach ($tile_groups as $tile_display => $tile_group) {
if (!$tile_group) {
continue; continue;
} }
$is_small_tiles = ($tile_display == PhabricatorApplication::TILE_SHOW) || $application = $applications[$pinned_application];
($tile_display == PhabricatorApplication::TILE_HIDE);
if ($is_small_tiles) { $tile = id(new PhabricatorApplicationLaunchView())
$groups = PhabricatorApplication::getApplicationGroups(); ->setApplication($application)
$tile_group = mgroup($tile_group, 'getApplicationGroup'); ->setApplicationStatus($application->loadStatus($user))
$tile_group = array_select_keys($tile_group, array_keys($groups)); ->setUser($user);
} else {
$tile_group = array($tile_group);
}
$is_hide = ($tile_display == PhabricatorApplication::TILE_HIDE); $tiles[] = $tile;
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);
}
} }
$nav->addCustomBlock(
phutil_tag(
'div',
array(
'class' => 'application-tile-group',
),
$tiles));
$nav->addFilter( $nav->addFilter(
'', '',
pht('Customize Applications...'), pht('Customize Applications...'),
'/settings/panel/home/'); '/settings/panel/home/');
$nav->addClass('phabricator-side-menu-home'); $nav->addClass('phabricator-side-menu-home');
$nav->selectFilter(null); $nav->selectFilter(null);

View file

@ -19,194 +19,179 @@ final class PhabricatorSettingsPanelHomePreferences
$user = $request->getUser(); $user = $request->getUser();
$preferences = $user->loadPreferences(); $preferences = $user->loadPreferences();
require_celerity_resource('phabricator-settings-css');
$apps = id(new PhabricatorApplicationQuery()) $apps = id(new PhabricatorApplicationQuery())
->setViewer($user) ->setViewer($user)
->withInstalled(true) ->withInstalled(true)
->withUnlisted(false) ->withUnlisted(false)
->withLaunchable(true)
->execute(); ->execute();
$pref_tiles = PhabricatorUserPreferences::PREFERENCE_APP_TILES; $pinned = $preferences->getPinnedApplications($apps, $user);
$tiles = $preferences->getPreference($pref_tiles, array());
if ($request->isFormPost()) { $app_list = array();
$values = $request->getArr('tile'); foreach ($pinned as $app) {
if (isset($apps[$app])) {
$app_list[$app] = $apps[$app];
}
}
if ($request->getBool('add')) {
$options = array();
foreach ($apps as $app) { foreach ($apps as $app) {
$key = get_class($app); $options[get_class($app)] = $app->getName();
$value = idx($values, $key); }
switch ($value) { asort($options);
case PhabricatorApplication::TILE_FULL:
case PhabricatorApplication::TILE_SHOW: unset($options['PhabricatorApplicationApplications']);
case PhabricatorApplication::TILE_HIDE:
$tiles[$key] = $value; if ($request->isFormPost()) {
break; $pin = $request->getStr('pin');
default: if (isset($options[$pin]) && !in_array($pin, $pinned)) {
unset($tiles[$key]); $pinned[] = $pin;
break; $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(); $preferences->save();
return id(new AphrontRedirectResponse()) return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?saved=true')); ->setURI($this->getPanelURI());
} }
$form = id(new AphrontFormView()) $list_id = celerity_generate_unique_node_id();
->setUser($user);
$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'); foreach ($app_list as $key => $application) {
$app_groups = array_select_keys($app_groups, array_keys($group_map)); if ($key == 'PhabricatorApplicationApplications') {
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)) {
continue; continue;
} }
$table = new AphrontTableView($rows); $icon = $application->getIconName();
if (!$icon) {
$icon = 'application';
}
$table $icon_view = javelin_tag(
->setClassName('phabricator-settings-homepagetable') 'span',
->setHeaders( array(
array( 'class' => 'phui-icon-view '.
pht('Applications'), 'sprite-apps-large apps-'.$icon.'-dark-large',
pht('Default'), 'aural' => false,
pht('Hidden'), ),
pht('Small'), '');
pht('Large'),
))
->setColumnClasses(
array(
'',
'fixed',
'fixed',
'fixed',
'fixed',
));
$item = id(new PHUIObjectItemView())
->setHeader($application->getName())
->setImageIcon($icon_view)
->addAttribute($application->getShortDescription())
->setGrippable(true);
$panel = id(new PHUIObjectBoxView()) $item->addAction(
->setHeaderText($group_name) id(new PHUIListItemView())
->appendChild($table); ->setIcon('fa-times')
->setHref($this->getPanelURI().'?unpin='.$key)
->setWorkflow(true));
$output[] = $panel; $item->addSigil('pinned-application');
} $item->setMetadata(
array(
'applicationClass' => $key,
));
$save_button = $list->addItem($item);
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.')));
} }
$header = id(new PHUIHeaderView()) $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()) $box = id(new PHUIObjectBoxView())
->addClass('phabricator-settings-homepagetable-wrap') ->setHeader($header)
->appendChild($form); ->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_COLLAPSED = 'nav-collapsed';
const PREFERENCE_NAV_WIDTH = 'nav-width'; const PREFERENCE_NAV_WIDTH = 'nav-width';
const PREFERENCE_APP_TILES = 'app-tiles'; const PREFERENCE_APP_TILES = 'app-tiles';
const PREFERENCE_APP_PINNED = 'app-pinned';
const PREFERENCE_DIFF_FILETREE = 'diff-filetree'; const PREFERENCE_DIFF_FILETREE = 'diff-filetree';
@ -55,4 +56,31 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO {
return $this; 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 $options;
private $disabledOptions = array();
public function setOptions(array $options) { public function setOptions(array $options) {
$this->options = $options; $this->options = $options;
@ -17,6 +18,11 @@ final class AphrontFormSelectControl extends AphrontFormControl {
return $this->options; return $this->options;
} }
public function setDisabledOptions(array $disabled) {
$this->disabledOptions = $disabled;
return $this;
}
protected function renderInput() { protected function renderInput() {
return self::renderSelectTag( return self::renderSelectTag(
$this->getValue(), $this->getValue(),
@ -25,15 +31,17 @@ final class AphrontFormSelectControl extends AphrontFormControl {
'name' => $this->getName(), 'name' => $this->getName(),
'disabled' => $this->getDisabled() ? 'disabled' : null, 'disabled' => $this->getDisabled() ? 'disabled' : null,
'id' => $this->getID(), 'id' => $this->getID(),
)); ),
$this->disabledOptions);
} }
public static function renderSelectTag( public static function renderSelectTag(
$selected, $selected,
array $options, 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( return javelin_tag(
'select', 'select',
@ -41,7 +49,12 @@ final class AphrontFormSelectControl extends AphrontFormControl {
$option_tags); $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(); $tags = array();
foreach ($options as $value => $thing) { foreach ($options as $value => $thing) {
if (is_array($thing)) { if (is_array($thing)) {
@ -57,6 +70,7 @@ final class AphrontFormSelectControl extends AphrontFormControl {
array( array(
'selected' => ($value == $selected) ? 'selected' : null, 'selected' => ($value == $selected) ? 'selected' : null,
'value' => $value, 'value' => $value,
'disabled' => isset($disabled[$value]) ? 'disabled' : null,
), ),
$thing); $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();
});
});