diff --git a/api/js/egw_action/EgwEt2WidgetObject.ts b/api/js/egw_action/EgwEt2WidgetObject.ts new file mode 100644 index 0000000000..ba6e0c9cd3 --- /dev/null +++ b/api/js/egw_action/EgwEt2WidgetObject.ts @@ -0,0 +1,81 @@ +import {EgwActionObjectInterface} from "./EgwActionObjectInterface"; +import {Element} from "parse5"; +import {Function} from "estree"; +import {EGW_AO_STATE_NORMAL, EGW_AO_STATE_VISIBLE} from "./egw_action_constants"; +import {Et2Widget} from "../etemplate/Et2Widget/Et2Widget"; + +/** + * Generic interface object so any webComponent can participate in action system. + * This interface can be extended if special handling is needed, but it should work + * for any widget. + */ +export class EgwEt2WidgetObject implements EgwActionObjectInterface +{ + node = null; + + constructor(node) + { + this.node = node; + } + + _state : number = EGW_AO_STATE_NORMAL || EGW_AO_STATE_VISIBLE; + handlers : { [p : string] : any }; + + reconnectActionsCallback(p0) + { + } + + reconnectActionsContext : any; + + stateChangeCallback(p0) + { + } + + stateChangeContext : any; + + // @ts-ignore + getDOMNode() : Element + { + return this.node; + } + + getWidget() : typeof Et2Widget + { + return this.node + } + + getState() : number + { + return this._state; + } + + makeVisible() : void + { + } + + reconnectActions() : void + { + } + + setReconnectActionsCallback(_callback : Function, _context : any) : void + { + } + + setState(_state : any) : void + { + this._state = _state + } + + setStateChangeCallback(_callback : Function, _context : any) : void + { + } + + triggerEvent(_event : any, _data : any) : boolean + { + return false; + } + + updateState(_stateBit : number, _set : boolean, _shiftState : boolean) : void + { + } +} \ No newline at end of file diff --git a/api/js/etemplate/Et2Widget/Et2Widget.ts b/api/js/etemplate/Et2Widget/Et2Widget.ts index 6be881474a..99159940c8 100644 --- a/api/js/etemplate/Et2Widget/Et2Widget.ts +++ b/api/js/etemplate/Et2Widget/Et2Widget.ts @@ -13,6 +13,12 @@ import {dedupeMixin} from "@open-wc/dedupe-mixin"; import type {et2_container} from "../et2_core_baseWidget"; import type {et2_DOMWidget} from "../et2_core_DOMWidget"; import bootstrapIcons from "../Styles/bootstrap-icons"; +import {EgwAction} from "../../egw_action/EgwAction"; +import {property} from "lit/decorators/property.js"; +import {egw_getActionManager, egw_getAppObjectManager} from "../../egw_action/egw_action"; +import {EgwEt2WidgetObject} from "../../egw_action/EgwEt2WidgetObject"; +import {EgwActionObject} from "../../egw_action/EgwActionObject"; +import {EgwActionObjectInterface} from "../../egw_action/EgwActionObjectInterface"; /** * This mixin will allow any LitElement to become an Et2Widget @@ -101,6 +107,8 @@ const Et2WidgetMixin = (superClass : T) => */ protected _deferred_properties : { [key : string] : string } = {}; + protected _actionManager : EgwAction = null; + /** WebComponent **/ static get styles() @@ -267,6 +275,10 @@ const Et2WidgetMixin = (superClass : T) => data: { type: String, reflect: false + }, + + actions: { + type: Object } }; } @@ -498,6 +510,148 @@ const Et2WidgetMixin = (superClass : T) => return data.join(","); } + /** + * Set Actions on the widget + * + * Each action is defined as an object: + * + * move: { + * type: "drop", + * acceptedTypes: "mail", + * icon: "move", + * caption: "Move to" + * onExecute: javascript:mail_move" + * } + * + * This will turn the widget into a drop target for "mail" drag types. When "mail" drag types are dropped, + * the global function mail_move(egwAction action, egwActionObject sender) will be called. The ID of the + * dragged "mail" will be in sender.id, some information about the sender will be in sender.context. The + * etemplate2 widget involved can typically be found in action.parent.data.widget, so your handler + * can operate in the widget context easily. The location varies depending on your action though. It + * might be action.parent.parent.data.widget + * + * To customise how the actions are handled for a particular widget, override _link_actions(). It handles + * the more widget-specific parts. + * + * @param {object} actions {ID: {attributes..}+} map of egw action information + * @see api/src/Etemplate/Widget/Nextmatch.php egw_actions() method + */ + @property({type: Object}) + set actions(actions : EgwAction[] | { [id : string] : object }) + { + if(this.id == "" || typeof this.id == "undefined") + { + this.egw().debug("warn", "Widget should have an ID if you want actions", this); + return; + } + + // Initialize the action manager and add some actions to it + if(this._actionManager == null) + { + // Find the apropriate parent action manager + let parent_am = null; + let widget = this; + while(widget.getParent() && !parent_am) + { + // @ts-ignore + if(widget._actionManager) + { + // @ts-ignore + parent_am = widget._actionManager; + } + widget = widget.getParent(); + } + if(!parent_am) + { + // Only look 1 level deep + parent_am = egw_getActionManager(this.egw().appName, true, 1); + } + if(parent_am.getActionById(this.getInstanceManager().uniqueId, 1) !== null) + { + parent_am = parent_am.getActionById(this.getInstanceManager().uniqueId, 1); + } + if(parent_am.getActionById(this.id, 1) != null) + { + this._actionManager = parent_am.getActionById(this.id, 1); + } + else + { + this._actionManager = parent_am.addAction("actionManager", this.id); + } + } + this._actionManager.updateActions(actions, this.egw().appName); + + // Put a reference to the widget into the action stuff, so we can + // easily get back to widget context from the action handler + this._actionManager.data = {widget: this}; + + // Link the actions to the DOM + this._link_actions(actions); + } + + get actions() + { + return this._actionManager?.children || {}; + } + + /** + * Get all action-links / id's of 1.-level actions from a given action object + * + * This can be overwritten to not allow all actions, by not returning them here. + * + * @param actions + * @returns {Array} + */ + protected _get_action_links(actions) + { + const action_links = []; + for(let i in actions) + { + let action = actions[i]; + action_links.push(typeof action.id != 'undefined' ? action.id : i); + } + return action_links; + } + + /** + * Link the actions to the DOM nodes / widget bits. + * + * @param {object} actions {ID: {attributes..}+} map of egw action information + */ + protected _link_actions(actions) + { + // Get the top level element for the tree + let objectManager = egw_getAppObjectManager(true); + let widget_object = objectManager.getObjectById(this.id); + + if(widget_object == null) + { + // Add a new container to the object manager which will hold the widget + // objects + widget_object = objectManager.insertObject(false, new EgwActionObject( + this.id, objectManager, this.createWidgetObjectInterface(), + this._actionManager || objectManager.manager.getActionById(this.id) || objectManager.manager + )); + } + else + { + widget_object.setAOI(this.createWidgetObjectInterface()); + } + + // Delete all old objects + widget_object.clear(); + widget_object.unregisterActions(); + + // Go over the widget & add links - this is where we decide which actions are + // 'allowed' for this widget at this time + widget_object.updateActionLinks(this._get_action_links(actions)); + } + + protected createWidgetObjectInterface() : EgwActionObjectInterface + { + return (new EgwEt2WidgetObject(this)); + } + /** * A property has changed, and we want to make adjustments to other things * based on that