Add searchable context menus

Add Move to group menu, closes #1244
This commit is contained in:
JannisX11 2022-02-27 20:03:07 +01:00
parent 6b1de30bf8
commit ebea154699
12 changed files with 144 additions and 45 deletions

View File

@ -592,6 +592,27 @@
background-color: transparent;
}
}
.contextMenu .menu_search_bar {
padding: 0;
display: flex;
border: 1px solid var(--color-border);
margin: 2px;
}
.contextMenu .menu_search_bar input {
color: inherit;
padding: 6px;
flex-grow: 1;
height: auto;
}
.contextMenu .menu_search_bar div {
width: 30px;
text-align: center;
padding-top: 2px;
}
.contextMenu .menu_search_bar div > * {
pointer-events: none;
vertical-align: middle;
}
.keybinding_label {
pointer-events: none;

View File

@ -210,6 +210,7 @@ class Action extends BarItem {
}
if (data.condition) this.condition = data.condition
this.children = data.children;
this.searchable = data.searchable;
//Node
if (!this.click) this.click = data.click

View File

@ -17,12 +17,16 @@ function handleMenuOverflow(node) {
})
}
class Menu {
constructor(id, structure) {
if (!structure) structure = id;
constructor(id, structure, options) {
if (typeof id !== 'string') {
options = structure;
structure = id;
}
this.id = typeof id == 'string' ? id : '';
this.children = [];
this.node = $('<ul class="contextMenu"></ul>')[0]
this.structure = structure
this.structure = structure;
this.options = options || {};
}
hover(node, event, expand) {
if (event) event.stopPropagation()
@ -146,13 +150,9 @@ class Menu {
node.find('ul.contextMenu.sub').detach();
if (list.length) {
var childlist = $('<ul class="contextMenu sub"></ul>')
list.forEach(function(s2, i) {
getEntry(s2, childlist)
})
var last = childlist.children().last()
if (last.length && last.hasClass('menu_separator')) {
last.remove()
}
populateList(list, childlist, object.searchable);
if (typeof object.click == 'function' && object instanceof Action == false) {
node.addClass('hybrid_parent');
let more_button = Interface.createElement('div', {class: 'menu_more_button'}, Blockbench.getIconNode('more_horiz'));
@ -168,6 +168,59 @@ class Menu {
}
return 0;
}
function populateList(list, menu_node, searchable) {
if (searchable) {
let input = Interface.createElement('input', {type: 'text', placeholder: tl('generic.search')});
let search_button = Interface.createElement('div', {}, Blockbench.getIconNode('search'));
let search_bar = Interface.createElement('li', {class: 'menu_search_bar'}, [input, search_button]);
menu_node.append(search_bar);
let object_list = [];
list.forEach(function(s2, i) {
let jq_node = getEntry(s2, menu_node);
if (!jq_node) return;
object_list.push({
object: s2,
node: jq_node[0] || jq_node,
id: s2.id,
name: s2.name,
description: s2.description,
})
})
search_button.onclick = (e) => {
input.value = '';
}
input.oninput = (e) => {
let search_term = input.value.toUpperCase();
search_button.firstElementChild.replaceWith(Blockbench.getIconNode(search_term ? 'clear' : 'search'));
object_list.forEach(item => {
$(item.node).detach();
})
object_list.forEach(item => {
if (
typeof item.object == 'string' ||
item.object.always_show ||
(item.id && item.id.toUpperCase().includes(search_term)) ||
(item.name && item.name.toUpperCase().includes(search_term)) ||
(item.description && item.description.toUpperCase().includes(search_term))
) {
menu_node.append(item.node);
}
})
}
} else {
list.forEach((object) => {
getEntry(object, menu_node);
})
}
var last = menu_node.children().last();
if (last.length && last.hasClass('menu_separator')) {
last.remove()
}
}
function getEntry(s, parent) {
@ -178,7 +231,7 @@ class Menu {
if (last.length && !last.hasClass('menu_separator')) {
parent.append(entry)
}
return;
return entry;
}
if (typeof s == 'string' && BarItems[s]) {
s = BarItems[s];
@ -291,15 +344,10 @@ class Menu {
obj = obj.parent().parent();
}
}
return entry;
}
scope.structure.forEach(function(s, i) {
getEntry(s, ctxmenu)
})
var last = ctxmenu.children().last()
if (last.length && last.hasClass('menu_separator')) {
last.remove()
}
populateList(scope.structure, ctxmenu, this.options.searchable);
var el_width = ctxmenu.width()
var el_height = ctxmenu.height()
@ -350,7 +398,9 @@ class Menu {
$(scope.node).on('click', (ev) => {
if (
ev.target.className.includes('parent') ||
(ev.target.parentNode && ev.target.parentNode.className.includes('parent'))
(ev.target.parentNode && ev.target.parentNode.className.includes('parent')) ||
ev.target.classList.contains('menu_search_bar') ||
(ev.target.parentNode && ev.target.parentNode.classList.contains('menu_search_bar'))
) {} else {
scope.hide()
}
@ -530,6 +580,7 @@ const MenuBar = {
},
{name: 'menu.file.recent', id: 'recent', icon: 'history',
condition() {return isApp && recent_projects.length},
searchable: true,
children() {
var arr = []
let redact = settings.streamer_mode.value;
@ -551,6 +602,7 @@ const MenuBar = {
arr.push('_', {
name: 'menu.file.recent.more',
icon: 'read_more',
always_show: true,
click(c, event) {
ActionControl.select('recent: ');
}
@ -560,6 +612,7 @@ const MenuBar = {
arr.push('_', {
name: 'menu.file.recent.clear',
icon: 'clear',
always_show: true,
click(c, event) {
recent_projects.empty();
updateRecentProjects();

View File

@ -721,7 +721,7 @@ class Cube extends OutlinerElement {
Cube.prototype.rotatable = true;
Cube.prototype.needsUniqueName = false;
Cube.prototype.menu = new Menu([
'group_elements',
...Outliner.control_menu_group,
'_',
'copy',
'paste',

View File

@ -433,9 +433,7 @@ class Group extends OutlinerNode {
Undo.finishEdit('Change group marker color')
}
Group.prototype.menu = new Menu([
'copy',
'paste',
'duplicate',
...Outliner.control_menu_group,
'_',
'add_locator',
'_',
@ -566,7 +564,7 @@ BARS.defineActions(function() {
}
})
new Action('group_elements', {
icon: 'drive_file_move',
icon: 'drive_folder_upload',
category: 'edit',
condition: () => Modes.edit && (selected.length || Group.selected),
keybind: new Keybind({key: 'g', ctrl: true, shift: true}),

View File

@ -104,7 +104,7 @@ class Locator extends OutlinerElement {
}
},
'_',
'group_elements',
...Outliner.control_menu_group,
'_',
'copy',
'paste',

View File

@ -616,11 +616,7 @@ class Mesh extends OutlinerElement {
'_',
'split_mesh',
'merge_meshes',
'group_elements',
'_',
'copy',
'paste',
'duplicate',
...Outliner.control_menu_group,
'_',
'rename',
{name: 'menu.cube.color', icon: 'color_lens', children: markerColors.map((color, i) => {return {

View File

@ -126,11 +126,7 @@ class NullObject extends OutlinerElement {
}
},
'_',
'group_elements',
'_',
'copy',
'paste',
'duplicate',
Outliner.control_menu_group,
'_',
'rename',
'delete'

View File

@ -609,6 +609,13 @@ class NodePreviewController {
}
}
}
Outliner.control_menu_group = [
'copy',
'paste',
'duplicate',
'group_elements',
'move_to_group',
]
OutlinerElement.registerType = function(constructor, id) {
OutlinerElement.types[id] = constructor;
@ -735,7 +742,7 @@ function parseGroups(array, import_reference, startIndex) {
}
// Dropping
function dropOutlinerObjects(item, target, event, order) {
function moveOutlinerSelectionTo(item, target, event, order) {
let duplicate = event.altKey || Pressing.overrides.alt;
if (item.type === 'group' && target && target.parent) {
var is_parent = false;
@ -811,7 +818,7 @@ function dropOutlinerObjects(item, target, event, order) {
updateSelection()
Undo.finishEdit('Duplicate selection', {elements: selected, outliner: true, selection: true})
} else {
Undo.finishEdit('Drag elements in outliner')
Undo.finishEdit('Move elements in outliner')
}
}
@ -957,6 +964,35 @@ BARS.defineActions(function() {
}
})
new Action('move_to_group', {
icon: 'drive_file_move',
category: 'edit',
searchable: true,
children(element) {
let groups = getAllGroups();
let root = {
name: 'Root',
icon: 'list_alt',
click(event) {
moveOutlinerSelectionTo(element, undefined, event);
}
};
return [root, ...groups.map(group => {
return {
name: group.name,
icon: 'folder',
color: markerColors[group.color] && markerColors[group.color].standard,
click(event) {
moveOutlinerSelectionTo(element, group, event);
element.showInOutliner();
}
}
})]
},
click(event) {
new Menu('move_to_group', this.children(this), {searchable: true}).open(event.target, this)
}
})
new Action('sort_outliner', {
icon: 'sort_by_alpha',
category: 'edit',
@ -1444,9 +1480,9 @@ Interface.definePanels(function() {
let target = document.elementFromPoint(e2.clientX, e2.clientY);
[drop_target] = eventTargetToNode(target);
if (drop_target) {
dropOutlinerObjects(item, drop_target, e2, order);
moveOutlinerSelectionTo(item, drop_target, e2, order);
} else if ($('#cubes_list').is(':hover')) {
dropOutlinerObjects(item, undefined, e2);
moveOutlinerSelectionTo(item, undefined, e2);
}
}
}

View File

@ -75,11 +75,7 @@ class TextureMesh extends OutlinerElement {
TextureMesh.prototype.rotatable = true;
TextureMesh.prototype.needsUniqueName = false;
TextureMesh.prototype.menu = new Menu([
'group_elements',
'_',
'copy',
'paste',
'duplicate',
...Outliner.control_menu_group,
'_',
'rename',
{name: 'menu.texture_mesh.texture_name', icon: 'collections', condition: () => !Project.single_texture, click(context) {

File diff suppressed because one or more lines are too long

View File

@ -1001,6 +1001,8 @@
"action.duplicate.desc": "Duplicates the selected cubes or group",
"action.delete": "Delete",
"action.delete.desc": "Deletes the selected cubes or group",
"action.move_to_group": "Move to Group",
"action.move_to_group.desc": "Move the selected elements to a different outliner group",
"action.sort_outliner": "Sort Outliner",
"action.sort_outliner.desc": "Sort the outliner alphabetically",
"action.unlock_everything": "Unlock All",