mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 00:42:41 +01:00
Allow profile menu items to be reordered
Summary: Ref T10054. Allows users to drag menu items to reorder them. Test Plan: Reordered a project menu. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10054 Differential Revision: https://secure.phabricator.com/D15011
This commit is contained in:
parent
f24318f308
commit
1c5167dc74
7 changed files with 173 additions and 1 deletions
|
@ -424,6 +424,7 @@ return array(
|
||||||
'rsrc/js/application/releeph/releeph-request-state-change.js' => 'a0b57eb8',
|
'rsrc/js/application/releeph/releeph-request-state-change.js' => 'a0b57eb8',
|
||||||
'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f',
|
'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f',
|
||||||
'rsrc/js/application/repository/repository-crossreference.js' => 'e5339c43',
|
'rsrc/js/application/repository/repository-crossreference.js' => 'e5339c43',
|
||||||
|
'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e2e0a072',
|
||||||
'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08',
|
'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08',
|
||||||
'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f',
|
'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f',
|
||||||
'rsrc/js/application/transactions/behavior-comment-actions.js' => '1f2fcaf8',
|
'rsrc/js/application/transactions/behavior-comment-actions.js' => '1f2fcaf8',
|
||||||
|
@ -663,6 +664,7 @@ return array(
|
||||||
'javelin-behavior-remarkup-preview' => '4b700e9e',
|
'javelin-behavior-remarkup-preview' => '4b700e9e',
|
||||||
'javelin-behavior-reorder-applications' => '76b9fc3e',
|
'javelin-behavior-reorder-applications' => '76b9fc3e',
|
||||||
'javelin-behavior-reorder-columns' => 'e1d25dfb',
|
'javelin-behavior-reorder-columns' => 'e1d25dfb',
|
||||||
|
'javelin-behavior-reorder-profile-menu-items' => 'e2e0a072',
|
||||||
'javelin-behavior-repository-crossreference' => 'e5339c43',
|
'javelin-behavior-repository-crossreference' => 'e5339c43',
|
||||||
'javelin-behavior-scrollbar' => '834a1173',
|
'javelin-behavior-scrollbar' => '834a1173',
|
||||||
'javelin-behavior-search-reorder-queries' => 'e9581f08',
|
'javelin-behavior-search-reorder-queries' => 'e9581f08',
|
||||||
|
@ -1933,6 +1935,13 @@ return array(
|
||||||
'e292eaf4' => array(
|
'e292eaf4' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
),
|
),
|
||||||
|
'e2e0a072' => array(
|
||||||
|
'javelin-behavior',
|
||||||
|
'javelin-stratcom',
|
||||||
|
'javelin-workflow',
|
||||||
|
'javelin-dom',
|
||||||
|
'phabricator-draggable-list',
|
||||||
|
),
|
||||||
'e379b58e' => array(
|
'e379b58e' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-stratcom',
|
'javelin-stratcom',
|
||||||
|
|
|
@ -642,6 +642,7 @@ abstract class PhabricatorApplication
|
||||||
'(?P<panelAction>view)/(?P<panelID>[^/]+)/' => $controller,
|
'(?P<panelAction>view)/(?P<panelID>[^/]+)/' => $controller,
|
||||||
'(?P<panelAction>hide)/(?P<panelID>[^/]+)/' => $controller,
|
'(?P<panelAction>hide)/(?P<panelID>[^/]+)/' => $controller,
|
||||||
'(?P<panelAction>configure)/' => $controller,
|
'(?P<panelAction>configure)/' => $controller,
|
||||||
|
'(?P<panelAction>reorder)/' => $controller,
|
||||||
'(?P<panelAction>edit)/'.$edit_route => $controller,
|
'(?P<panelAction>edit)/'.$edit_route => $controller,
|
||||||
'(?P<panelAction>new)/(?<panelKey>[^/]+)/'.$edit_route => $controller,
|
'(?P<panelAction>new)/(?<panelKey>[^/]+)/'.$edit_route => $controller,
|
||||||
'(?P<panelAction>builtin)/(?<panelID>[^/]+)/'.$edit_route
|
'(?P<panelAction>builtin)/(?<panelID>[^/]+)/'.$edit_route
|
||||||
|
|
|
@ -15,6 +15,7 @@ final class PhabricatorProfilePanelEditor
|
||||||
$types = parent::getTransactionTypes();
|
$types = parent::getTransactionTypes();
|
||||||
|
|
||||||
$types[] = PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY;
|
$types[] = PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY;
|
||||||
|
$types[] = PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER;
|
||||||
|
|
||||||
return $types;
|
return $types;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +28,8 @@ final class PhabricatorProfilePanelEditor
|
||||||
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
|
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
|
||||||
$key = $xaction->getMetadataValue('property.key');
|
$key = $xaction->getMetadataValue('property.key');
|
||||||
return $object->getPanelProperty($key, null);
|
return $object->getPanelProperty($key, null);
|
||||||
|
case PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER:
|
||||||
|
return $object->getPanelOrder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +40,8 @@ final class PhabricatorProfilePanelEditor
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
|
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
|
||||||
return $xaction->getNewValue();
|
return $xaction->getNewValue();
|
||||||
|
case PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER:
|
||||||
|
return (int)$xaction->getNewValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +55,9 @@ final class PhabricatorProfilePanelEditor
|
||||||
$value = $xaction->getNewValue();
|
$value = $xaction->getNewValue();
|
||||||
$object->setPanelProperty($key, $value);
|
$object->setPanelProperty($key, $value);
|
||||||
return;
|
return;
|
||||||
|
case PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER:
|
||||||
|
$object->setPanelOrder($xaction->getNewValue());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||||
|
@ -61,6 +69,7 @@ final class PhabricatorProfilePanelEditor
|
||||||
|
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
|
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
|
||||||
|
case PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,9 @@ final class PhabricatorProfilePanelEngine extends Phobject {
|
||||||
$content = $this->buildPanelConfigureContent($panel_list);
|
$content = $this->buildPanelConfigureContent($panel_list);
|
||||||
$crumbs->addTextCrumb(pht('Configure Menu'));
|
$crumbs->addTextCrumb(pht('Configure Menu'));
|
||||||
break;
|
break;
|
||||||
|
case 'reorder':
|
||||||
|
$content = $this->buildPanelReorderContent($panel_list);
|
||||||
|
break;
|
||||||
case 'new':
|
case 'new':
|
||||||
$panel_key = $request->getURIData('panelKey');
|
$panel_key = $request->getURIData('panelKey');
|
||||||
$content = $this->buildPanelNewContent($panel_key);
|
$content = $this->buildPanelNewContent($panel_key);
|
||||||
|
@ -204,6 +207,8 @@ final class PhabricatorProfilePanelEngine extends Phobject {
|
||||||
$impl->setViewer($viewer);
|
$impl->setViewer($viewer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$panels = msort($panels, 'getSortKey');
|
||||||
|
|
||||||
// Normalize keys since callers shouldn't rely on this array being
|
// Normalize keys since callers shouldn't rely on this array being
|
||||||
// partially keyed.
|
// partially keyed.
|
||||||
$panels = array_values($panels);
|
$panels = array_values($panels);
|
||||||
|
@ -305,6 +310,79 @@ final class PhabricatorProfilePanelEngine extends Phobject {
|
||||||
return "/project/{$id}/panel/{$path}";
|
return "/project/{$id}/panel/{$path}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildPanelReorderContent(array $panels) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$object = $this->getProfileObject();
|
||||||
|
|
||||||
|
PhabricatorPolicyFilter::requireCapability(
|
||||||
|
$viewer,
|
||||||
|
$object,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
|
$controller = $this->getController();
|
||||||
|
$request = $controller->getRequest();
|
||||||
|
|
||||||
|
$request->validateCSRF();
|
||||||
|
|
||||||
|
$order = $request->getStrList('order');
|
||||||
|
|
||||||
|
$by_builtin = array();
|
||||||
|
$by_id = array();
|
||||||
|
|
||||||
|
foreach ($panels as $key => $panel) {
|
||||||
|
$id = $panel->getID();
|
||||||
|
if ($id) {
|
||||||
|
$by_id[$id] = $key;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$builtin_key = $panel->getBuiltinKey();
|
||||||
|
if ($builtin_key) {
|
||||||
|
$by_builtin[$builtin_key] = $key;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$key_order = array();
|
||||||
|
foreach ($order as $order_item) {
|
||||||
|
if (isset($by_id[$order_item])) {
|
||||||
|
$key_order[] = $by_id[$order_item];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isset($by_builtin[$order_item])) {
|
||||||
|
$key_order[] = $by_builtin[$order_item];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$panels = array_select_keys($panels, $key_order) + $panels;
|
||||||
|
|
||||||
|
$type_order =
|
||||||
|
PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER;
|
||||||
|
|
||||||
|
$order = 1;
|
||||||
|
foreach ($panels as $panel) {
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction())
|
||||||
|
->setTransactionType($type_order)
|
||||||
|
->setNewValue($order);
|
||||||
|
|
||||||
|
$editor = id(new PhabricatorProfilePanelEditor())
|
||||||
|
->setContentSourceFromRequest($request)
|
||||||
|
->setActor($viewer)
|
||||||
|
->setContinueOnMissingFields(true)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
|
->applyTransactions($panel, $xactions);
|
||||||
|
|
||||||
|
$order++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())
|
||||||
|
->setURI($this->getConfigureURI());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private function buildPanelConfigureContent(array $panels) {
|
private function buildPanelConfigureContent(array $panels) {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
$object = $this->getProfileObject();
|
$object = $this->getProfileObject();
|
||||||
|
@ -314,7 +392,18 @@ final class PhabricatorProfilePanelEngine extends Phobject {
|
||||||
$object,
|
$object,
|
||||||
PhabricatorPolicyCapability::CAN_EDIT);
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
$list = new PHUIObjectItemListView();
|
$list_id = celerity_generate_unique_node_id();
|
||||||
|
|
||||||
|
Javelin::initBehavior(
|
||||||
|
'reorder-profile-menu-items',
|
||||||
|
array(
|
||||||
|
'listID' => $list_id,
|
||||||
|
'orderURI' => $this->getPanelURI('reorder/'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$list = id(new PHUIObjectItemListView())
|
||||||
|
->setID($list_id);
|
||||||
|
|
||||||
foreach ($panels as $panel) {
|
foreach ($panels as $panel) {
|
||||||
$id = $panel->getID();
|
$id = $panel->getID();
|
||||||
$builtin_key = $panel->getBuiltinKey();
|
$builtin_key = $panel->getBuiltinKey();
|
||||||
|
@ -336,6 +425,14 @@ final class PhabricatorProfilePanelEngine extends Phobject {
|
||||||
$item->addAttribute($type);
|
$item->addAttribute($type);
|
||||||
|
|
||||||
if ($can_edit) {
|
if ($can_edit) {
|
||||||
|
$item
|
||||||
|
->setGrippable(true)
|
||||||
|
->addSigil('profile-menu-item')
|
||||||
|
->setMetadata(
|
||||||
|
array(
|
||||||
|
'key' => nonempty($id, $builtin_key),
|
||||||
|
));
|
||||||
|
|
||||||
if ($id) {
|
if ($id) {
|
||||||
$item->setHref($this->getPanelURI("edit/{$id}/"));
|
$item->setHref($this->getPanelURI("edit/{$id}/"));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -101,6 +101,20 @@ final class PhabricatorProfilePanelConfiguration
|
||||||
return $this->getPanel()->getDisplayName($this);
|
return $this->getPanel()->getDisplayName($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSortKey() {
|
||||||
|
$order = $this->getPanelOrder();
|
||||||
|
if ($order === null) {
|
||||||
|
$order = 'Z';
|
||||||
|
} else {
|
||||||
|
$order = sprintf('%020d', $order);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'~%s%020d',
|
||||||
|
$order,
|
||||||
|
$this->getID());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ final class PhabricatorProfilePanelConfigurationTransaction
|
||||||
extends PhabricatorApplicationTransaction {
|
extends PhabricatorApplicationTransaction {
|
||||||
|
|
||||||
const TYPE_PROPERTY = 'profilepanel.property';
|
const TYPE_PROPERTY = 'profilepanel.property';
|
||||||
|
const TYPE_ORDER = 'profilepanel.order';
|
||||||
|
|
||||||
public function getApplicationName() {
|
public function getApplicationName() {
|
||||||
return 'search';
|
return 'search';
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* @provides javelin-behavior-reorder-profile-menu-items
|
||||||
|
* @requires javelin-behavior
|
||||||
|
* javelin-stratcom
|
||||||
|
* javelin-workflow
|
||||||
|
* javelin-dom
|
||||||
|
* phabricator-draggable-list
|
||||||
|
*/
|
||||||
|
|
||||||
|
JX.behavior('reorder-profile-menu-items', function(config) {
|
||||||
|
|
||||||
|
var root = JX.$(config.listID);
|
||||||
|
|
||||||
|
var list = new JX.DraggableList('profile-menu-item', root)
|
||||||
|
.setFindItemsHandler(function() {
|
||||||
|
return JX.DOM.scry(root, 'li', 'profile-menu-item');
|
||||||
|
});
|
||||||
|
|
||||||
|
list.listen('didDrop', function(node) {
|
||||||
|
var nodes = list.findItems();
|
||||||
|
var order = [];
|
||||||
|
var key;
|
||||||
|
for (var ii = 0; ii < nodes.length; ii++) {
|
||||||
|
key = JX.Stratcom.getData(nodes[ii]).key;
|
||||||
|
if (key) {
|
||||||
|
order.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.lock();
|
||||||
|
JX.DOM.alterClass(node, 'drag-sending', true);
|
||||||
|
|
||||||
|
new JX.Workflow(config.orderURI, {order: order.join()})
|
||||||
|
.setHandler(function() {
|
||||||
|
JX.DOM.alterClass(node, 'drag-sending', false);
|
||||||
|
list.unlock();
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in a new issue