mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-09 16:32:39 +01:00
Begin rebuilding dropdown menus on ActionList
Summary: Dropdown menus are entirely dynamic right now and use custom CSS. Begin rebuilding them to use ActionList CSS. This introduces PHUIX components which are basically JS copy/pastes of the PHP PHUI components, just implemented in JS. We have two other dropdowns: policy controls and one in Conpherence. I'll convert those, then implement D8966. Test Plan: {F150418} Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D8973
This commit is contained in:
parent
707c5aec81
commit
56aa508f43
7 changed files with 479 additions and 108 deletions
|
@ -133,7 +133,8 @@ div.jx-typeahead-results {
|
|||
z-index: 20;
|
||||
}
|
||||
|
||||
.dropdown-menu-frame {
|
||||
.dropdown-menu-frame,
|
||||
.phuix-dropdown-menu {
|
||||
z-index: 32;
|
||||
}
|
||||
|
||||
|
|
|
@ -166,7 +166,8 @@ button.link:hover {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dropdown-menu-frame {
|
||||
.dropdown-menu-frame,
|
||||
.phuix-dropdown-menu {
|
||||
position: absolute;
|
||||
width: 240px;
|
||||
background: #fff;
|
||||
|
|
1
webroot/rsrc/externals/javelin/lib/DOM.js
vendored
1
webroot/rsrc/externals/javelin/lib/DOM.js
vendored
|
@ -944,6 +944,7 @@ JX.install('DOM', {
|
|||
try { node.focus(); } catch (lol_ie) {}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Scroll to the position of an element in the document.
|
||||
* @task view
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
* javelin-dom
|
||||
* javelin-util
|
||||
* javelin-stratcom
|
||||
* phabricator-dropdown-menu
|
||||
* phabricator-menu-item
|
||||
* phuix-dropdown-menu
|
||||
* phuix-action-list-view
|
||||
* phuix-action-view
|
||||
* phabricator-phtize
|
||||
*/
|
||||
|
||||
JX.behavior('differential-dropdown-menus', function(config) {
|
||||
|
||||
var pht = JX.phtize(config.pht);
|
||||
|
||||
function show_more(container) {
|
||||
|
@ -30,109 +30,6 @@ JX.behavior('differential-dropdown-menus', function(config) {
|
|||
}
|
||||
}
|
||||
|
||||
function build_menu(button, data) {
|
||||
|
||||
function link_to(name, uri) {
|
||||
var item = new JX.PhabricatorMenuItem(
|
||||
name,
|
||||
JX.bind(null, window.open, uri),
|
||||
uri);
|
||||
item.setDisabled(!uri);
|
||||
return item;
|
||||
}
|
||||
|
||||
var reveal_item = new JX.PhabricatorMenuItem('', function () {
|
||||
show_more(JX.$(data.containerID));
|
||||
});
|
||||
|
||||
var diffusion_item;
|
||||
if (data.diffusionURI) {
|
||||
// Show this only if we have a link, since when this appears in Diffusion
|
||||
// it is otherwise potentially confusing.
|
||||
diffusion_item = link_to(pht('Browse in Diffusion'), data.diffusionURI);
|
||||
}
|
||||
|
||||
var menu = new JX.PhabricatorDropdownMenu(buttons[ii])
|
||||
.addItem(reveal_item);
|
||||
|
||||
var visible_item = new JX.PhabricatorMenuItem('', function () {
|
||||
JX.Stratcom.invoke('differential-toggle-file', null, {
|
||||
diff: JX.DOM.scry(JX.$(data.containerID), 'table', 'differential-diff')
|
||||
});
|
||||
});
|
||||
menu.addItem(visible_item);
|
||||
|
||||
if (diffusion_item) {
|
||||
menu.addItem(diffusion_item);
|
||||
}
|
||||
|
||||
menu.addItem(link_to(pht('View Standalone'), data.standaloneURI));
|
||||
|
||||
if (data.leftURI) {
|
||||
menu.addItem(link_to(pht('Show Raw File (Left)'), data.leftURI));
|
||||
}
|
||||
|
||||
if (data.rightURI) {
|
||||
menu.addItem(link_to(pht('Show Raw File (Right)'), data.rightURI));
|
||||
}
|
||||
|
||||
if (data.editor) {
|
||||
menu.addItem(new JX.PhabricatorMenuItem(
|
||||
pht('Open in Editor'),
|
||||
// Open in the same window.
|
||||
JX.bind(location, location.assign, data.editor),
|
||||
data.editor));
|
||||
}
|
||||
|
||||
if (data.editorConfigure) {
|
||||
menu.addItem(link_to(pht('Configure Editor'), data.editorConfigure));
|
||||
}
|
||||
|
||||
menu.listen(
|
||||
'open',
|
||||
function() {
|
||||
|
||||
// When the user opens the menu, check if there are any "Show More"
|
||||
// links in the changeset body. If there aren't, disable the "Show
|
||||
// Entire File" menu item since it won't change anything.
|
||||
|
||||
var nodes = JX.DOM.scry(JX.$(data.containerID), 'a', 'show-more');
|
||||
if (nodes.length) {
|
||||
reveal_item.setDisabled(false);
|
||||
reveal_item.setName(pht('Show Entire File'));
|
||||
} else {
|
||||
reveal_item.setDisabled(true);
|
||||
reveal_item.setName(pht('Entire File Shown'));
|
||||
}
|
||||
|
||||
visible_item.setDisabled(true);
|
||||
visible_item.setName(pht("Can't Toggle Unloaded File"));
|
||||
var diffs = JX.DOM.scry(JX.$(data.containerID),
|
||||
'table', 'differential-diff');
|
||||
if (diffs.length > 1) {
|
||||
JX.$E(
|
||||
'More than one node with sigil "differential-diff" was found in "'+
|
||||
data.containerID+'."');
|
||||
} else if (diffs.length == 1) {
|
||||
diff = diffs[0];
|
||||
visible_item.setDisabled(false);
|
||||
if (JX.Stratcom.getData(diff).hidden) {
|
||||
visible_item.setName(pht('Expand File'));
|
||||
} else {
|
||||
visible_item.setName(pht('Collapse File'));
|
||||
}
|
||||
} else {
|
||||
// Do nothing when there is no diff shown in the table. For example,
|
||||
// the file is binary.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var buttons = JX.DOM.scry(window.document, 'a', 'differential-view-options');
|
||||
for (var ii = 0; ii < buttons.length; ii++) {
|
||||
build_menu(buttons[ii], JX.Stratcom.getData(buttons[ii]));
|
||||
}
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'click',
|
||||
'differential-reveal-all',
|
||||
|
@ -147,4 +44,124 @@ JX.behavior('differential-dropdown-menus', function(config) {
|
|||
e.kill();
|
||||
});
|
||||
|
||||
var buildmenu = function(e) {
|
||||
var button = e.getNode('differential-view-options');
|
||||
var data = JX.Stratcom.getData(button);
|
||||
|
||||
if (data.menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.prevent();
|
||||
|
||||
var menu = new JX.PHUIXDropdownMenu(button);
|
||||
var list = new JX.PHUIXActionListView();
|
||||
|
||||
var add_link = function(icon, name, href, local) {
|
||||
if (!href) {
|
||||
return;
|
||||
}
|
||||
|
||||
var link = new JX.PHUIXActionView()
|
||||
.setIcon(icon)
|
||||
.setName(name)
|
||||
.setHref(href)
|
||||
.setHandler(function(e) {
|
||||
if (local) {
|
||||
window.location.assign(href);
|
||||
} else {
|
||||
window.open(href);
|
||||
}
|
||||
menu.close();
|
||||
e.prevent();
|
||||
});
|
||||
|
||||
list.addItem(link);
|
||||
return link;
|
||||
};
|
||||
|
||||
var reveal_item = new JX.PHUIXActionView()
|
||||
.setIcon('preview');
|
||||
list.addItem(reveal_item);
|
||||
|
||||
var visible_item = new JX.PHUIXActionView()
|
||||
.setHandler(function(e) {
|
||||
var diff = JX.DOM.scry(
|
||||
JX.$(data.containerID),
|
||||
'table',
|
||||
'differential-diff');
|
||||
|
||||
JX.Stratcom.invoke('differential-toggle-file', null, {diff: diff});
|
||||
e.prevent();
|
||||
menu.close();
|
||||
});
|
||||
list.addItem(visible_item);
|
||||
|
||||
add_link('file', pht('Browse in Diffusion'), data.diffusionURI);
|
||||
add_link('transcript', pht('View Standalone'), data.standaloneURI);
|
||||
add_link('arrow_left', pht('Show Raw File (Left)'), data.leftURI);
|
||||
add_link('arrow_right', pht('Show Raw File (Right)'), data.rightURI);
|
||||
add_link('edit', pht('Open in Editor'), data.editor, true);
|
||||
add_link('wrench', pht('Configure Editor'), data.editorConfigure);
|
||||
|
||||
|
||||
menu.setContent(list.getNode());
|
||||
|
||||
menu.listen('open', function() {
|
||||
|
||||
// When the user opens the menu, check if there are any "Show More"
|
||||
// links in the changeset body. If there aren't, disable the "Show
|
||||
// Entire File" menu item since it won't change anything.
|
||||
|
||||
var nodes = JX.DOM.scry(JX.$(data.containerID), 'a', 'show-more');
|
||||
if (nodes.length) {
|
||||
reveal_item
|
||||
.setDisabled(false)
|
||||
.setName(pht('Show Entire File'))
|
||||
.setHandler(function(e) {
|
||||
show_more(JX.$(data.containerID));
|
||||
e.prevent();
|
||||
menu.close();
|
||||
});
|
||||
} else {
|
||||
reveal_item
|
||||
.setDisabled(true)
|
||||
.setName(pht('Entire File Shown'))
|
||||
.setHandler(function(e) { e.prevent(); });
|
||||
}
|
||||
|
||||
visible_item.setDisabled(true);
|
||||
visible_item.setName(pht("Can't Toggle Unloaded File"));
|
||||
var diffs = JX.DOM.scry(
|
||||
JX.$(data.containerID),
|
||||
'table',
|
||||
'differential-diff');
|
||||
|
||||
if (diffs.length > 1) {
|
||||
JX.$E(
|
||||
'More than one node with sigil "differential-diff" was found in "'+
|
||||
data.containerID+'."');
|
||||
} else if (diffs.length == 1) {
|
||||
diff = diffs[0];
|
||||
visible_item.setDisabled(false);
|
||||
if (JX.Stratcom.getData(diff).hidden) {
|
||||
visible_item
|
||||
.setName(pht('Expand File'))
|
||||
.setIcon('unmerge');
|
||||
} else {
|
||||
visible_item
|
||||
.setName(pht('Collapse File'))
|
||||
.setIcon('merge');
|
||||
}
|
||||
} else {
|
||||
// Do nothing when there is no diff shown in the table. For example,
|
||||
// the file is binary.
|
||||
}
|
||||
|
||||
});
|
||||
data.menu = menu;
|
||||
menu.open();
|
||||
};
|
||||
|
||||
JX.Stratcom.listen('click', 'differential-view-options', buildmenu);
|
||||
});
|
||||
|
|
36
webroot/rsrc/js/phuix/PHUIXActionListView.js
Normal file
36
webroot/rsrc/js/phuix/PHUIXActionListView.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @provides phuix-action-list-view
|
||||
* @requires javelin-install
|
||||
* javelin-dom
|
||||
*/
|
||||
|
||||
JX.install('PHUIXActionListView', {
|
||||
|
||||
construct: function() {
|
||||
this._items = [];
|
||||
},
|
||||
|
||||
members: {
|
||||
_items: null,
|
||||
_node: null,
|
||||
|
||||
addItem: function(item) {
|
||||
this._items.push(item);
|
||||
this.getNode().appendChild(item.getNode());
|
||||
return this;
|
||||
},
|
||||
|
||||
getNode: function() {
|
||||
if (!this._node) {
|
||||
var attrs = {
|
||||
className: 'phabricator-action-list-view'
|
||||
};
|
||||
|
||||
this._node = JX.$N('ul', attrs);
|
||||
}
|
||||
|
||||
return this._node;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
138
webroot/rsrc/js/phuix/PHUIXActionView.js
Normal file
138
webroot/rsrc/js/phuix/PHUIXActionView.js
Normal file
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* @provides phuix-action-view
|
||||
* @requires javelin-install
|
||||
* javelin-dom
|
||||
* javelin-util
|
||||
* @javelin
|
||||
*/
|
||||
|
||||
JX.install('PHUIXActionView', {
|
||||
|
||||
members: {
|
||||
_node: null,
|
||||
_name: null,
|
||||
_icon: 'none',
|
||||
_disabled: false,
|
||||
_handler: null,
|
||||
|
||||
_iconNode: null,
|
||||
_nameNode: null,
|
||||
|
||||
setDisabled: function(disabled) {
|
||||
this._disabled = disabled;
|
||||
JX.DOM.alterClass(
|
||||
this.getNode(),
|
||||
'phabricator-action-view-disabled',
|
||||
disabled);
|
||||
|
||||
this._buildIconNode(true);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
getDisabled: function() {
|
||||
return this._disabled;
|
||||
},
|
||||
|
||||
setName: function(name) {
|
||||
this._name = name;
|
||||
this._buildNameNode(true);
|
||||
return this;
|
||||
},
|
||||
|
||||
setHandler: function(handler) {
|
||||
this._handler = handler;
|
||||
this._buildNameNode(true);
|
||||
return this;
|
||||
},
|
||||
|
||||
setIcon: function(icon) {
|
||||
this._icon = icon;
|
||||
this._buildIconNode(true);
|
||||
return this;
|
||||
},
|
||||
|
||||
setHref: function(href) {
|
||||
this._href = href;
|
||||
this._buildNameNode(true);
|
||||
return this;
|
||||
},
|
||||
|
||||
getNode: function() {
|
||||
if (!this._node) {
|
||||
var attr = {
|
||||
className: 'phabricator-action-view'
|
||||
};
|
||||
|
||||
var content = [
|
||||
this._buildIconNode(),
|
||||
this._buildNameNode()
|
||||
];
|
||||
|
||||
this._node = JX.$N('li', attr, content);
|
||||
}
|
||||
|
||||
return this._node;
|
||||
},
|
||||
|
||||
_buildIconNode: function(dirty) {
|
||||
if (!this._iconNode || dirty) {
|
||||
var attr = {
|
||||
className: 'phui-icon-view sprite-icons phabricator-action-view-icon'
|
||||
};
|
||||
var node = JX.$N('span', attr);
|
||||
|
||||
var icon_class = 'icons-' + this._icon;
|
||||
if (this._disabled) {
|
||||
icon_class = icon_class + '-grey';
|
||||
}
|
||||
|
||||
JX.DOM.alterClass(node, icon_class, true);
|
||||
|
||||
if (this._iconNode && this._iconNode.parentNode) {
|
||||
JX.DOM.replace(this._iconNode, node);
|
||||
}
|
||||
this._iconNode = node;
|
||||
}
|
||||
|
||||
return this._iconNode;
|
||||
},
|
||||
|
||||
_buildNameNode: function(dirty) {
|
||||
if (!this._nameNode || dirty) {
|
||||
var attr = {
|
||||
className: 'phabricator-action-view-item'
|
||||
};
|
||||
|
||||
var href = this._href;
|
||||
if (!href && this._handler) {
|
||||
href = '#';
|
||||
}
|
||||
if (href) {
|
||||
attr.href = href;
|
||||
|
||||
}
|
||||
|
||||
var tag = href ? 'a' : 'span';
|
||||
|
||||
var node = JX.$N(tag, attr, this._name);
|
||||
JX.DOM.listen(node, 'click', null, JX.bind(this, this._onclick));
|
||||
|
||||
if (this._nameNode && this._nameNode.parentNode) {
|
||||
JX.DOM.replace(this._nameNode, node);
|
||||
}
|
||||
this._nameNode = node;
|
||||
}
|
||||
|
||||
return this._nameNode;
|
||||
},
|
||||
|
||||
_onclick: function(e) {
|
||||
if (this._handler) {
|
||||
this._handler(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
177
webroot/rsrc/js/phuix/PHUIXDropdownMenu.js
Normal file
177
webroot/rsrc/js/phuix/PHUIXDropdownMenu.js
Normal file
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
* @provides phuix-dropdown-menu
|
||||
* @requires javelin-install
|
||||
* javelin-util
|
||||
* javelin-dom
|
||||
* javelin-vector
|
||||
* javelin-stratcom
|
||||
* @javelin
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Basic interaction for a dropdown menu.
|
||||
*
|
||||
* The menu is unaware of the content inside it, so it can not close itself
|
||||
* when an item is selected. Callers must make a call to @{method:close} after
|
||||
* an item is chosen in order to close the menu.
|
||||
*/
|
||||
JX.install('PHUIXDropdownMenu', {
|
||||
|
||||
construct : function(node) {
|
||||
this._node = node;
|
||||
|
||||
JX.DOM.listen(
|
||||
this._node,
|
||||
'click',
|
||||
null,
|
||||
JX.bind(this, this._onclick));
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'mousedown',
|
||||
null,
|
||||
JX.bind(this, this._onanyclick));
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'resize',
|
||||
null,
|
||||
JX.bind(this, this._adjustposition));
|
||||
|
||||
JX.Stratcom.listen('phuix.dropdown.open', null, JX.bind(this, this.close));
|
||||
},
|
||||
|
||||
events: ['open'],
|
||||
|
||||
properties: {
|
||||
width: null,
|
||||
align: 'right'
|
||||
},
|
||||
|
||||
members: {
|
||||
_node: null,
|
||||
_menu: null,
|
||||
_open: false,
|
||||
_content: null,
|
||||
|
||||
setContent: function(content) {
|
||||
JX.DOM.setContent(this._getMenuNode(), content);
|
||||
return this;
|
||||
},
|
||||
|
||||
open: function() {
|
||||
if (this._open) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.invoke('open');
|
||||
JX.Stratcom.invoke('phuix.dropdown.open');
|
||||
|
||||
this._open = true;
|
||||
this._show();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
close: function() {
|
||||
if (!this._open) {
|
||||
return;
|
||||
}
|
||||
this._open = false;
|
||||
this._hide();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_getMenuNode: function() {
|
||||
if (!this._menu) {
|
||||
var attrs = {
|
||||
className: 'phuix-dropdown-menu',
|
||||
role: 'button'
|
||||
};
|
||||
|
||||
var menu = JX.$N('div', attrs);
|
||||
|
||||
this._node.setAttribute('aria-haspopup', 'true');
|
||||
this._node.setAttribute('aria-expanded', 'false');
|
||||
|
||||
this._menu = menu;
|
||||
}
|
||||
|
||||
return this._menu;
|
||||
},
|
||||
|
||||
_onclick : function(e) {
|
||||
if (this._open) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
e.prevent();
|
||||
},
|
||||
|
||||
_onanyclick : function(e) {
|
||||
if (!this._open) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (JX.Stratcom.pass(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var t = e.getTarget();
|
||||
while (t) {
|
||||
if (t == this._menu || t == this._node) {
|
||||
return;
|
||||
}
|
||||
t = t.parentNode;
|
||||
}
|
||||
|
||||
this.close();
|
||||
},
|
||||
|
||||
_show : function() {
|
||||
document.body.appendChild(this._menu);
|
||||
|
||||
if (this.getWidth()) {
|
||||
new JX.Vector(this.getWidth(), null).setDim(this._menu);
|
||||
}
|
||||
|
||||
this._adjustposition();
|
||||
|
||||
JX.DOM.alterClass(this._node, 'phuix-dropdown-open', true);
|
||||
|
||||
this._node.setAttribute('aria-expanded', 'true');
|
||||
},
|
||||
|
||||
_hide : function() {
|
||||
JX.DOM.remove(this._menu);
|
||||
|
||||
JX.DOM.alterClass(this._node, 'phuix-dropdown-open', false);
|
||||
|
||||
this._node.setAttribute('aria-expanded', 'false');
|
||||
},
|
||||
|
||||
_adjustposition : function() {
|
||||
if (!this._open) {
|
||||
return;
|
||||
}
|
||||
|
||||
var m = JX.Vector.getDim(this._menu);
|
||||
|
||||
var v = JX.$V(this._node);
|
||||
var d = JX.Vector.getDim(this._node);
|
||||
|
||||
switch (this.getAlign()) {
|
||||
case 'right':
|
||||
v = v.add(d)
|
||||
.add(JX.$V(-m.x, 0));
|
||||
break;
|
||||
default:
|
||||
v = v.add(0, d.y);
|
||||
break;
|
||||
}
|
||||
|
||||
v.setPos(this._menu);
|
||||
}
|
||||
}
|
||||
});
|
Loading…
Reference in a new issue