Speed up nextmatch context menus by creating them once & reuse them

This commit is contained in:
nathan 2025-01-06 13:30:17 -07:00
parent 2b5f15855b
commit 290dc59a09
3 changed files with 93 additions and 32 deletions

View File

@ -1,5 +1,5 @@
import {css, html, LitElement, nothing, PropertyValues} from "lit";
import {SlIcon, SlMenu, SlMenuItem} from "@shoelace-style/shoelace";
import {css, html, LitElement, nothing} from "lit";
import {SlMenu, SlMenuItem} from "@shoelace-style/shoelace";
import {egwMenuItem} from "./egw_menu";
import {customElement} from "lit/decorators/custom-element.js";
import {repeat} from "lit/directives/repeat.js";
@ -26,6 +26,12 @@ export class EgwMenuShoelace extends LitElement
box-shadow: var(--sl-shadow-x-large);
}
/* sl-menu-item:host overrides display */
sl-menu-item[hidden] {
display: none !important;
}
sl-menu-item::part(base) {
height: 1.7em;
line-height: var(--sl-line-height-dense);
@ -60,7 +66,7 @@ export class EgwMenuShoelace extends LitElement
private structure = [];
private popup = null;
private removeCallback = null;
private hideCallback = null;
private get menu() : SlMenu { return this.shadowRoot?.querySelector("sl-menu");}
@ -91,16 +97,16 @@ export class EgwMenuShoelace extends LitElement
this.popup.remove();
this.popup = null;
}
if(this.removeCallback)
if(this.hideCallback)
{
this.removeCallback.call();
this.hideCallback.call();
}
}
public showAt(_x, _y, _onHide)
{
this.removeCallback = _onHide;
this.hideCallback = _onHide;
if(this.popup == null)
{
this.popup = Object.assign(document.createElement("sl-popup"), {
@ -111,7 +117,10 @@ export class EgwMenuShoelace extends LitElement
});
this.popup.append(this);
this.popup.classList.add("egw_menu");
document.body.append(this.popup);
}
// Open where instructed
let menu = this;
this.popup.anchor = {
getBoundingClientRect()
@ -129,7 +138,6 @@ export class EgwMenuShoelace extends LitElement
}
};
this.popup.active = true;
document.body.append(this.popup);
Promise.all([this.updateComplete, this.popup.updateComplete]).then(() =>
{
// Causes scroll issues if we don't position
@ -138,15 +146,34 @@ export class EgwMenuShoelace extends LitElement
});
}
/**
* Update the menu items with current disabled / visible settings
*
* @param _links
*/
public applyContext(_links)
{
Object.keys(_links).forEach((actionId) =>
{
const menuItem = <SlMenuItem>this.shadowRoot.querySelector("[data-action-id='" + actionId + "']");
if(!menuItem)
{
return;
}
menuItem.disabled = !_links[actionId].enabled;
menuItem.hidden = !_links[actionId].visible;
});
}
public hide()
{
if(this.popup)
{
this.popup.active = false;
}
// egw_menu always creates a new menu
this.remove();
if(this.hideCallback)
{
this.hideCallback.call();
}
}
handleSelect(event)
@ -230,14 +257,16 @@ export class EgwMenuShoelace extends LitElement
{
item.iconUrl = item.checked ? "toggle-on" : "toggle-off";
}
const id = CSS.escape(item.id);
return html`
<sl-menu-item
class=${classMap({
"default-item": item.default
})}
id=${item.id}
id=${id}
type="${item.checkbox ? "checkbox" : "normal"}"
data-action-id="${item.id}"
?checked=${item.checkbox && item.checked}
?disabled=${!item.enabled}
.value=${item}

View File

@ -116,10 +116,24 @@ export class EgwPopupActionImplementation implements EgwActionImplementation {
_context = {"posx": x, "posy": y};
}
const menu = this._buildMenu(_links, _selected, _target);
menu.showAt(_context.posx, _context.posy);
let menu = null;
// Special handling for nextmatch context menu - reuse the same menu
if(!_target && !_context.menu && _selected[0].parent.manager.data.menu)
{
menu = _selected[0].parent.manager.data.menu
}
if(!menu)
{
menu = this._buildMenu(_links, _selected, _target);
}
else
{
menu.applyContext(_links, _selected, _target);
}
return true;
menu.showAt(_context.posx, _context.posy);
return true;
} else {
const defaultAction = this._getDefaultLink(_links);
if (defaultAction) {
@ -303,6 +317,13 @@ export class EgwPopupActionImplementation implements EgwActionImplementation {
*/
private _registerContext = (_node, _callback, _context) => {
// Special handling for nextmatch: only build the menu once and just re-use it.
if(!_context.menu && _context.actionLinks && _context.parent?.manager?.data?.nextmatch && !_context.parent.manager.data.menu)
{
_context.parent.manager.data.menu = this._buildMenu(_context.actionLinks.filter(l => l.actionObj.type == "popup"), [_context], null);
_context.parent.manager.data.menu.showAt(0, 0);
_context.parent.manager.data.menu.hide();
}
const contextHandler = (e) => {
const x = _node
//use different node and context for callback if event happens on parent
@ -526,7 +547,7 @@ export class EgwPopupActionImplementation implements EgwActionImplementation {
const tree = {"root": []};
// Automatically add in Drag & Drop actions
if(this.auto_paste && !window.egwIsMobile() && !this._context.event?.type.match(/touch/))
if(this.auto_paste && !window.egwIsMobile() && this._context?.event && !this._context.event?.type.match(/touch/))
{
this._addCopyPaste(_links, _selected);
}
@ -828,6 +849,7 @@ export class EgwPopupActionImplementation implements EgwActionImplementation {
}
};
private _context: any;
private menu : EgwMenu;
}

View File

@ -9,15 +9,6 @@
*
*/
import {EgwMenuShoelace} from "./EgwMenuShoelace";
import {egw_registeredShortcuts, egw_shortcutIdx} from './egw_keymanager';
import {
EGW_KEY_ARROW_DOWN,
EGW_KEY_ARROW_LEFT,
EGW_KEY_ARROW_RIGHT,
EGW_KEY_ARROW_UP,
EGW_KEY_ENTER,
EGW_KEY_ESCAPE
} from "./egw_action_constants";
//Global variable which is used to store the currently active menu so that it
//may be closed when another menu opens
@ -160,7 +151,6 @@ export class egwMenu
if (this.instance != null)
{
this.instance.hide();
this.instance = null;
}
}
@ -196,18 +186,38 @@ export class egwMenu
//Obtain a new egwMenuImpl object and pass this instance to it
this.instance = new EgwMenuShoelace(this.children);
_egw_active_menu = this;
this.instance.showAt(_x, _y, () => {
this.instance = null;
_egw_active_menu = null;
});
return true;
}
_egw_active_menu = this;
this.instance.showAt(_x, _y, () =>
{
_egw_active_menu = null;
});
return true;
return false;
}
/**
* Enable / disable menu items for the given selection & target
*
* @param _context
* @param _links
* @param _selected
* @param _target
* @private
*/
public applyContext(_links, _selected, _target)
{
if(!this.instance)
{
this.instance = new EgwMenuShoelace(this.children);
}
this.instance.applyContext(_links);
}
/**
* Adds a new menu item to the list and returns a reference to that object.
*