Merge remote-tracking branch 'origin/master' into upstream_master

This commit is contained in:
milan
2024-06-15 09:01:07 +02:00
9 changed files with 388 additions and 37 deletions

View File

@ -0,0 +1,176 @@
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";
import {classMap} from "lit/directives/class-map.js";
@customElement("egw-menu-shoelace")
export class EgwMenuShoelace extends LitElement
{
static get styles()
{
return [
css`
:host {
display: block;
/* Fit in popup, scroll if not enough height */
max-height: var(--auto-size-available-height, auto);
overflow-y: auto;
}
.default-item::part(label) {
font-weight: var(--sl-font-weight-bold, bold);
}
et2-image {
width: 1.5em;
}
`
]
}
private structure = [];
private popup = null;
private removeCallback = null;
private get menu() : SlMenu { return this.shadowRoot?.querySelector("sl-menu");}
constructor(_structure : egwMenuItem[])
{
super();
this.structure = _structure;
}
connectedCallback()
{
super.connectedCallback();
}
disconnectedCallback()
{
super.disconnectedCallback();
if(this.popup)
{
this.popup.remove();
this.popup = null;
}
if(this.removeCallback)
{
this.removeCallback.call();
}
}
public showAt(_x, _y, _onHide)
{
this.removeCallback = _onHide;
if(this.popup == null)
{
this.popup = document.createElement("sl-popup");
this.popup.placement = "bottom";
this.popup.autoSize = "vertical";
this.popup.flip = true;
this.popup.shift = true;
this.popup.classList.add("egw_menu")
document.body.append(this.popup);
this.popup.append(this);
}
this.popup.anchor = {
getBoundingClientRect()
{
return {
x: _x,
y: _y,
width: 0,
height: 0,
top: _y,
left: _x,
right: _x,
bottom: _y
}
}
};
this.popup.active = true;
Promise.all([this.updateComplete, this.popup.updateComplete]).then(() =>
{
// Causes scroll issues if we don't position
this.popup.popup.style = "top: 0px";
(<SlMenuItem>this.menu.querySelector('sl-menu-item')).focus();
});
}
public hide()
{
this.popup.active = false;
}
handleSelect(event)
{
if(!this.popup)
{
return;
}
if(event.detail.item.value)
{
const item = <egwMenuItem>event.detail.item.value;
if(item.checkbox || typeof item.checked !== "undefined")
{
item.checked = event.detail.item.checked;
return;
}
if(typeof item.onClick == "function")
{
this.hide();
item.onClick.call(event.detail.item, item, event);
}
}
}
private itemTemplate(item : egwMenuItem)
{
if(item.caption == "-")
{
return html`
<sl-divider></sl-divider>`;
}
return html`
<sl-menu-item
class=${classMap({
"default-item": item.default
})}
id=${item.id}
type="${item.checkbox ? "checkbox" : "normal"}"
?checked=${item.checkbox && item.checked}
?disabled=${!item.enabled}
.value=${item}
>
${item.iconUrl ? html`
<et2-image slot="prefix" src="${item.iconUrl}"></et2-image>` : nothing}
${item.caption}
${item.shortcutCaption ? html`<span slot="suffix"
class="keyboard_shortcut">
${item.shortcutCaption}
</span>` : nothing}
${item.children.length == 0 ? nothing : html`
<sl-menu slot="submenu">
${repeat(item.children, i => this.itemTemplate(i))}
</sl-menu>
`}
</sl-menu-item>
`;
}
render()
{
return html`
<sl-menu
@sl-select=${this.handleSelect}
>
${repeat(this.structure, i => this.itemTemplate(i))}
</sl-menu>`;
}
}

View File

@ -9,14 +9,18 @@
*
*/
import {egwMenuImpl} from './egw_menu_dhtmlx';
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_ARROW_UP,
EGW_KEY_ENTER,
EGW_KEY_ESCAPE
} from "./egw_action_constants";
import {EgwFramework} from "../../../kdots/js/EgwFramework";
//Global variable which is used to store the currently active menu so that it
//may be closed when another menu opens
export var _egw_active_menu: egwMenu = null;
@ -209,7 +213,14 @@ export class egwMenu
if (this.instance == null && this._checkImpl)
{
//Obtain a new egwMenuImpl object and pass this instance to it
this.instance = new egwMenuImpl(this.children);
if(window.framework instanceof EgwFramework)
{
this.instance = new EgwMenuShoelace(this.children);
}
else
{
this.instance = new egwMenuImpl(this.children);
}
_egw_active_menu = this;
@ -238,6 +249,12 @@ export class egwMenu
return false;
}
// Shoelace does its own keyboard navigation
if(!this.instance.dhtmlxmenu)
{
return false;
}
//TODO change with shoelace
let current = this.instance.dhtmlxmenu.menuSelected;
if (current !== -1)

View File

@ -345,20 +345,21 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
// Hide the trash
trash.remove();
// Delete preference server side
Favorite.remove(this.egw(), this.app, line.value).then(response =>
// Delete preference server side, returns boolean
Favorite.remove(this.egw(), this.app, line.value).then(result =>
{
line.classList.remove("loading");
let result = response.response.find(r => r.type == "data");
this.dispatchEvent(new CustomEvent("preferenceChange", {
bubbles: true,
composed: true,
detail: {
application: this.application,
preference: line.value
}
}));
// Could not find the result we want
if(!result || result.type !== "data")
{
return;
}
if(typeof result.data == 'boolean' && result.data)
if(result)
{
// Remove line from list
line.remove();

View File

@ -46,6 +46,9 @@ export class Et2FavoritesMenu extends Et2Widget(LitElement)
@property()
application : string;
@property()
noAdd : boolean = false;
private favorites : { [name : string] : Favorite } = {
'blank': {
name: typeof this.egw()?.lang == "function" ? this.egw().lang("No filters") : "No filters",
@ -55,24 +58,63 @@ export class Et2FavoritesMenu extends Et2Widget(LitElement)
};
private loadingPromise = Promise.resolve();
constructor()
{
super();
this.handlePreferenceChange = this.handlePreferenceChange.bind(this);
}
connectedCallback()
{
super.connectedCallback();
if(this.application)
{
this.loadingPromise = Favorite.load(this.egw(), this.application).then((favorites) =>
{
this.favorites = favorites;
});
this._load();
}
document.addEventListener("preferenceChange", this.handlePreferenceChange);
}
disconnectedCallback()
{
super.disconnectedCallback();
document.removeEventListener("preferenceChange", this.handlePreferenceChange);
}
private _load()
{
this.loadingPromise = Favorite.load(this.egw(), this.application).then((favorites) =>
{
this.favorites = favorites;
});
}
handlePreferenceChange(e)
{
if(e && e.detail?.application == this.application)
{
this._load();
this.requestUpdate();
}
}
handleSelect(event)
{
if(event.detail.item.value == Favorite.ADD_VALUE)
{
return this.handleAdd(event);
}
Favorite.applyFavorite(this.egw(), this.application, event.detail.item.value);
}
handleAdd(event)
{
event.stopPropagation();
if(this.egw().window && this.egw().window.app[this.application])
{
this.egw().window.app[this.application].add_favorite({});
}
}
handleDelete(event)
{
// Don't trigger click
@ -89,6 +131,18 @@ export class Et2FavoritesMenu extends Et2Widget(LitElement)
// Remove from widget
delete this.favorites[favoriteName];
this.requestUpdate();
this.updateComplete.then(() =>
{
this.dispatchEvent(new CustomEvent("preferenceChange", {
bubbles: true,
composed: true,
detail: {
application: this.application,
preference: favoriteName
}
}));
});
});
this.requestUpdate();
@ -123,12 +177,21 @@ export class Et2FavoritesMenu extends Et2Widget(LitElement)
{
return html`
<sl-menu
part="menu"
@sl-select=${this.handleSelect}
>
${this.label ? html`
<sl-menu-label>${this.label}</sl-menu-label>` : nothing}
${repeat(Object.keys(this.favorites), (i) => this.menuItemTemplate(i, this.favorites[i]))}
<slot></slot>
${this.noAdd ? nothing : html`
<sl-menu-item value=${Favorite.ADD_VALUE}
@sl-select=${this.handleAdd}
>
<sl-icon name="plus" slot="prefix"></sl-icon>
${this.egw().lang("Current view as favourite")}
</sl-menu-item>`
}
</sl-menu>
`;
});

View File

@ -1246,6 +1246,15 @@ export abstract class EgwApp
this.egw.set_preference(this.appname, favorite_pref, favorite);
}
// Trigger event so widgets can update
document.dispatchEvent(new CustomEvent("preferenceChange", {
bubbles: true,
detail: {
application: this.appname,
preference: favorite_pref
}
}));
// Add to list immediately
if(this.sidebox)
{