diff --git a/api/js/etemplate/Et2TreeWidget/Et2Tree.ts b/api/js/etemplate/Et2TreeWidget/Et2Tree.ts new file mode 100644 index 0000000000..890428eb5b --- /dev/null +++ b/api/js/etemplate/Et2TreeWidget/Et2Tree.ts @@ -0,0 +1,256 @@ +import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; +import {SlTree} from "@shoelace-style/shoelace"; +import {Et2Link} from "../Et2Link/Et2Link"; +import {Et2widgetWithSelectMixin} from "../Et2Select/Et2WidgetWithSelectMixin"; +import {et2_no_init} from "../et2_core_common"; +import {egw, framework} from "../../jsapi/egw_global"; +import {SelectOption, find_select_options, cleanSelectOptions} from "../Et2Select/FindSelectOptions"; +import {html, TemplateResult} from "@lion/core"; +import {egwIsMobile} from "../../egw_action/egw_action_common"; + +export type TreeItem = { + child: Boolean | 1, + data?: Object,//{sieve:true,...} or {acl:true} or other + id: String, + im0: String, + im1: String, + im2: String, + item: TreeItem[], + checked?: Boolean, + nocheckbox: number | Boolean, + open: 0 | 1, + parent: String, + text: String, + tooltip: String +} + + +export class Et2Tree extends Et2widgetWithSelectMixin(SlTree) { + private input: any = null; + private div: JQuery; + private autoloading_url: any; + private selectOptions: TreeItem[]; + + constructor() { + super(); + } + + static get properties() { + return { + ...super.properties, + multiple: { + name: "", + type: Boolean, + default: false, + description: "Allow selecting multiple options" + }, + selectOptions: { + type: "any", + name: "Select options", + default: {}, + description: "Used to set the tree options." + }, + onClick: { + name: "onClick", + type: "js", + description: "JS code which gets executed when clicks on text of a node" + }, + onSelect: { + name: "onSelect", + type: "js", + default: et2_no_init, + description: "Javascript executed when user selects a node" + }, + onCheck: { + name: "onCheck", + type: "js", + default: et2_no_init, + description: "Javascript executed when user checks a node" + }, + // TODO do this : --> onChange event is mapped depending on multiple to onCheck or onSelect + onOpenStart: { + name: "onOpenStart", + type: "js", + default: et2_no_init, + description: "Javascript function executed when user opens a node: function(_id, _widget, _hasChildren) returning true to allow opening!" + }, + onOpenEnd: { + name: "onOpenEnd", + type: "js", + default: et2_no_init, + description: "Javascript function executed when opening a node is finished: function(_id, _widget, _hasChildren)" + }, + imagePath: { + name: "Image directory", + type: String, + default: egw().webserverUrl + "/api/templates/default/images/dhtmlxtree/",//TODO we will need a different path here! maybe just rename the path? + description: "Directory for tree structure images, set on server-side to 'dhtmlx' subdir of templates image-directory" + }, + value: { + type: "any", + default: {} + }, + actions: { + name: "Actions array", + type: "any", + default: et2_no_init, + description: "List of egw actions that can be done on the tree. This includes context menu, drag and drop. TODO: Link to action documentation" + }, + autoLoading: { + name: "Auto loading", + type: String, + default: "", + description: "JSON URL or menuaction to be called for nodes marked with child=1, but not having children, GET parameter selected contains node-id" + }, + stdImages: { + name: "Standard images", + type: String, + default: "", + description: "comma-separated names of icons for a leaf, closed and opened folder (default: leaf.png,folderClosed.png,folderOpen.png), images with extension get loaded from imagePath, just 'image' or 'appname/image' are allowed too" + }, + multiMarking: { + name: "multi marking", + type: "any", + default: false, + description: "Allow marking multiple nodes, default is false which means disabled multiselection, true or 'strict' activates it and 'strict' makes it strict to only same level marking" + }, + highlighting: { + name: "highlighting", + type: Boolean, + default: false, + description: "Add highlighting class on hovered over item, highlighting is disabled by default" + }, + } + }; + + public set onOpenStart(_handler: Function) { + this.installHandler("onOpenStart", _handler) + } + + public set onChange(_handler: Function) { + this.installHandler("onChange", _handler) + } + + public set onClick(_handler: Function) { + this.installHandler("onClick", _handler) + } + + public set onSelect(_handler: Function) { + this.installHandler("onSelect", _handler) + } + + public set onOpenEnd(_handler: Function) { + this.installHandler("onOpenEnd", _handler) + } + + _optionTemplate() { + // @ts-ignore + this.selectOptions= find_select_options(this)[1]; + //slot = expanded/collapsed instead of expand/collapse like it is in documentation + let result: TemplateResult<1> = html`` + for (const selectOption of this.selectOptions) { + result = html`${result} + + ${this.recursivelyAddChildren(selectOption)} + ` + } + const h = html`${result}` + return h + } + + /** + * @deprecated assign to onOpenStart + * @param _handler + */ + public set_onopenstart(_handler: Function) { + this.installHandler("onOpenStart", _handler) + } + + /** + * @deprecated assign to onChange + * @param _handler + */ + public set_onchange(_handler: Function) { + this.installHandler('onchange', _handler); + } + + /** + * @deprecated assign to onClick + * @param _handler + */ + public set_onclick(_handler: Function) { + this.installHandler('onclick', _handler); + } + + /** + * @deprecated assign to onSelect + * @param _handler + */ + public set_onselect(_handler: Function) { + this.installHandler('onselect', _handler); + } + + /** + * @deprecated assign to onOpenEnd + * @param _handler + */ + public set_onopenend(_handler: Function) { + this.installHandler('onOpenEnd', _handler); + } + + private recursivelyAddChildren(item: any): TemplateResult<1> { + let img:String =item.im0??item.im1??item.im2; + let attributes = "" + let res: TemplateResult<1> = html`${item.text}`; + if(img){ + img = "api/templates/default/images/dhtmlxtree/"+img + //sl-icon images need to be svgs if there is a png try to find the corresponding svg + if(img.endsWith(".png"))img = img.replace(".png",".svg"); + res = html`${res}` + } + if (item.item?.length > 0) // there are children available + { + for (const subItem of item.item) { + res = html` + ${res} + ${this.recursivelyAddChildren(subItem)}` + } + // }else if(item.child === 1){ + // res = html`` + // } + } + return res; + } + + private installHandler(_name: String, _handler: Function) { + if (this.input == null) this.createTree(this); + // automatic convert onChange event to oncheck or onSelect depending on multiple is used or not + // if (_name == "onchange") { + // _name = this.options.multiple ? "oncheck" : "onselect" + // } + // let handler = _handler; + // let widget = this; + // this.input.attachEvent(_name, function(_id){ + // let args = jQuery.makeArray(arguments); + // // splice in widget as 2. parameter, 1. is new node-id, now 3. is old node id + // args.splice(1, 0, widget); + // // try to close mobile sidemenu after clicking on node + // if (egwIsMobile() && typeof args[2] == 'string') framework.toggleMenu('on'); + // return handler.apply(this, args); + // }); + } + + private createTree(widget: this) { + widget.input = document.querySelector("et2-tree"); + // Allow controlling icon size by CSS + widget.input.def_img_x = ""; + widget.input.def_img_y = ""; + + // to allow "," in value, eg. folder-names, IF value is specified as array + widget.input.dlmtr = ':}-*('; + } +} + +customElements.define("et2-tree", Et2Tree); +const tree = new Et2Tree(); + diff --git a/api/js/etemplate/Et2TreeWidget/MailTree.ts b/api/js/etemplate/Et2TreeWidget/MailTree.ts new file mode 100644 index 0000000000..d12dd644ee --- /dev/null +++ b/api/js/etemplate/Et2TreeWidget/MailTree.ts @@ -0,0 +1,12 @@ +import {Et2Tree} from "./Et2Tree"; + +export function initMailTree(): Et2Tree { + const changeFunction = () => { + console.log("change"+tree) + } + const tree: Et2Tree = document.querySelector("et2-tree"); + tree.selection = "single"; + tree.addEventListener("sl-selection-change", (event)=>{console.log(event)}) + return tree; +} + diff --git a/api/js/etemplate/Et2TreeWidget/Tests/TreeTest.js b/api/js/etemplate/Et2TreeWidget/Tests/TreeTest.js new file mode 100644 index 0000000000..b658178ac1 --- /dev/null +++ b/api/js/etemplate/Et2TreeWidget/Tests/TreeTest.js @@ -0,0 +1,7 @@ +const selectionMode = document.querySelector('#selection-mode'); +const tree = document.querySelector('.tree-selectable'); + +selectionMode.addEventListener('sl-change', () => { + tree.querySelectorAll('sl-tree-item').forEach(item => (item.selected = false)); + tree.selection = selectionMode.value; +}); \ No newline at end of file diff --git a/api/js/etemplate/Et2TreeWidget/Tests/TreeTestSite.html b/api/js/etemplate/Et2TreeWidget/Tests/TreeTestSite.html new file mode 100644 index 0000000000..6e95ab2fe4 --- /dev/null +++ b/api/js/etemplate/Et2TreeWidget/Tests/TreeTestSite.html @@ -0,0 +1,26 @@ + + + + + TestSite + + + + + + Item 1 + + Item A + Item Z + Item Y + Item X + + Item B + Item C + + Item 2 + Item 3 + + + + \ No newline at end of file diff --git a/api/js/etemplate/etemplate2.ts b/api/js/etemplate/etemplate2.ts index daf92ff45d..8700118287 100644 --- a/api/js/etemplate/etemplate2.ts +++ b/api/js/etemplate/etemplate2.ts @@ -99,6 +99,7 @@ import "./Et2Vfs/Et2VfsMime"; import "./Et2Vfs/Et2VfsUid"; import "./Et2Textbox/Et2Password"; import './Et2Textbox/Et2Searchbox'; +import "./Et2TreeWidget/Et2Tree"; /* Include all widget classes here, we only care about them registering, not importing anything*/ import './et2_widget_vfs'; // Vfs must be first (before et2_widget_file) due to import cycle diff --git a/api/templates/default/images/dhtmlxtree/folderNoSelectClosed.svg b/api/templates/default/images/dhtmlxtree/folderNoSelectClosed.svg new file mode 100644 index 0000000000..23968cc12e --- /dev/null +++ b/api/templates/default/images/dhtmlxtree/folderNoSelectClosed.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + diff --git a/mail/js/app.js b/mail/js/app.js index fa164efb04..0d95606118 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -25,6 +25,9 @@ import { } from "../../api/js/egw_action/egw_keymanager"; import {Et2UrlEmailReadonly} from "../../api/js/etemplate/Et2Url/Et2UrlEmailReadonly"; import {Et2SelectEmail} from "../../api/js/etemplate/Et2Select/Select/Et2SelectEmail"; +import {Et2SelectEmail} from "../../api/js/etemplate/Et2Select/Et2SelectEmail"; +import {Et2Tree} from "../../api/js/etemplate/Et2TreeWidget/Et2Tree"; +import {initMailTree} from "../../api/js/etemplate/Et2TreeWidget/MailTree"; /* required dependency, commented out because no module, but egw:uses is no longer parsed */ @@ -253,6 +256,9 @@ app.classes.mail = AppJS.extend( var tree_wdg = this.et2.getWidgetById(this.nm_index+'[foldertree]'); if (tree_wdg) { + initMailTree(); + + tree_wdg.set_onopenstart(jQuery.proxy(this.openstart_tree, this)); tree_wdg.set_onopenend(jQuery.proxy(this.openend_tree, this)); } diff --git a/mail/templates/default/index.xet b/mail/templates/default/index.xet index 0b3dcd75c7..3fa186da8c 100644 --- a/mail/templates/default/index.xet +++ b/mail/templates/default/index.xet @@ -141,9 +141,17 @@