From 379729daffbcb11d69098b0c415f11bf73665800 Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 29 Jan 2024 09:57:52 -0700 Subject: [PATCH] WIP VFS Select: split off dialog and made button its own component for easier backward compatability --- .../Et2InputWidget/test/InputBasicTests.ts | 3 +- api/js/etemplate/Et2Vfs/Et2VfsPath.ts | 2 +- api/js/etemplate/Et2Vfs/Et2VfsSelectButton.ts | 154 ++++++++++++++++++ ...{Et2VfsSelect.ts => Et2VfsSelectDialog.ts} | 23 +-- api/js/etemplate/Et2Vfs/Et2VfsSelectRow.ts | 2 +- api/js/etemplate/etemplate2.ts | 3 +- 6 files changed, 172 insertions(+), 15 deletions(-) create mode 100644 api/js/etemplate/Et2Vfs/Et2VfsSelectButton.ts rename api/js/etemplate/Et2Vfs/{Et2VfsSelect.ts => Et2VfsSelectDialog.ts} (96%) diff --git a/api/js/etemplate/Et2InputWidget/test/InputBasicTests.ts b/api/js/etemplate/Et2InputWidget/test/InputBasicTests.ts index 5850892a00..75c34a5174 100644 --- a/api/js/etemplate/Et2InputWidget/test/InputBasicTests.ts +++ b/api/js/etemplate/Et2InputWidget/test/InputBasicTests.ts @@ -90,8 +90,9 @@ export function inputBasicTests(before : Function, test_value : string, value_se // Shows as empty / no value let value = (element).querySelector(value_selector) || (element).shadowRoot.querySelector(value_selector); assert.isDefined(value, "Bad value selector '" + value_selector + "'"); + assert.isNotNull(value, "Bad value selector '" + value_selector + "'"); - assert.equal(value.textContent.trim(), "", "Displaying something when there is no value"); + assert.equal(value.innerText.trim(), "", "Displaying something when there is no value"); if(element.multiple) { assert.isEmpty(element.get_value()); diff --git a/api/js/etemplate/Et2Vfs/Et2VfsPath.ts b/api/js/etemplate/Et2Vfs/Et2VfsPath.ts index 2f9a7b4575..bc8e2a939f 100644 --- a/api/js/etemplate/Et2Vfs/Et2VfsPath.ts +++ b/api/js/etemplate/Et2Vfs/Et2VfsPath.ts @@ -15,7 +15,7 @@ import {property} from "lit/decorators/property.js"; import {state} from "lit/decorators/state.js"; import {classMap} from "lit/directives/class-map.js"; import {repeat} from "lit/directives/repeat.js"; -import {FileInfo} from "./Et2VfsSelect"; +import {FileInfo} from "./Et2VfsSelectDialog"; import {SlBreadcrumbItem} from "@shoelace-style/shoelace"; import {HasSlotController} from "../Et2Widget/slot"; diff --git a/api/js/etemplate/Et2Vfs/Et2VfsSelectButton.ts b/api/js/etemplate/Et2Vfs/Et2VfsSelectButton.ts new file mode 100644 index 0000000000..20f29e1692 --- /dev/null +++ b/api/js/etemplate/Et2Vfs/Et2VfsSelectButton.ts @@ -0,0 +1,154 @@ +/** + * EGroupware eTemplate2 - Button to open a vfs select dialog + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @link https://www.egroupware.org + * @author Nathan Gray + */ + +import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; +import {html, LitElement, nothing} from "lit"; +import {HasSlotController} from "../Et2Widget/slot"; +import {property} from "lit/decorators/property.js"; +import {FileInfo} from "./Et2VfsSelectDialog"; + +/** + * @summary Button to open a file selection dialog, and return the selected path(s) as a value + * @since 23.1 + * + * @dependency et2-vfs-select-dialog + * @dependency et2-button + * + * @slot prefix - Before the toolbar + * @slot suffix - Like prefix, but after + * + * @event change - Emitted when the control's value changes. + * + * @csspart form-control-input - The textbox's wrapper. + * @csspart form-control-help-text - The help text's wrapper. + * @csspart prefix - The container that wraps the prefix slot. + * @csspart suffix - The container that wraps the suffix slot. + * + */ + +export class Et2VfsSelectButton extends Et2InputWidget(LitElement) +{ + /** Icon for the button */ + @property() image : string; + + /** Currently selected files */ + @property() value : string[] | FileInfo[] = []; + + /** + * The dialog’s label as displayed in the header. + * You should always include a relevant label, as it is required for proper accessibility. + */ + @property() title : string = "Select"; + + /** + * Dialog mode + * Quickly sets button label, multiple, selection and for "select-dir", mime-type + **/ + @property({type: String}) mode : "open" | "open-multiple" | "saveas" | "select-dir"; + + /** Button label */ + @property({type: String}) buttonLabel : string = "Select"; + + /** Provide a suggested filename for saving */ + @property() filename : string = ""; + + /** Allow selecting multiple files */ + @property({type: Boolean}) multiple = false; + + /** Start path in VFS. Leave unset to use the last used path. */ + @property() path : string = ""; + + /** Limit display to the given mime-type */ + @property() mime : string | string[] | RegExp = ""; + + /** Server side callback to process selected value(s) in + * app.class.method or class::method format. The first parameter will + * be Method ID, the second the file list. 'download' is reserved and it + * means it should use download_baseUrl instead of path in value (no method + * will be actually executed). + */ + @property() method : string = ""; + /** ID passed to method */ + @property() method_id : string; + + protected readonly hasSlotController = new HasSlotController(this, ''); + + //private _dialog : Et2VfsSelectDialog = this.shadowRoot.querySelector("et2-vfs-select-dialog") ?? null; + + constructor() + { + super(); + this.handleClick = this.handleClick.bind(this); + this.handleDialogClose = this.handleDialogClose.bind(this); + } + + protected handleClick(event) + { + const dialog : any = this.shadowRoot.querySelector("et2-vfs-select-dialog"); + if(dialog && typeof dialog.show == "function") + { + dialog.show(); + } + } + + protected handleDialogClose(event) + { + debugger; + this.value = dialog.value ?? []; + + if(this.method && this.method == "download") + { + // download + } + else if(this.method) + { + this.sendFiles(); + } + } + + protected sendFiles() + + protected dialogTemplate() + { + return html` + + + + `; + } + + + render() + { + const hasUserDialog = this.hasSlotController.test("[default]"); + + return html` + + + ${hasUserDialog ? nothing : this.dialogTemplate()} + `; + } +} + +customElements.define("et2-vfs-select", Et2VfsSelectButton); \ No newline at end of file diff --git a/api/js/etemplate/Et2Vfs/Et2VfsSelect.ts b/api/js/etemplate/Et2Vfs/Et2VfsSelectDialog.ts similarity index 96% rename from api/js/etemplate/Et2Vfs/Et2VfsSelect.ts rename to api/js/etemplate/Et2Vfs/Et2VfsSelectDialog.ts index 71accba77d..124a97d543 100644 --- a/api/js/etemplate/Et2Vfs/Et2VfsSelect.ts +++ b/api/js/etemplate/Et2Vfs/Et2VfsSelectDialog.ts @@ -23,6 +23,7 @@ import {HasSlotController} from "../Et2Widget/slot"; import {IegwAppLocal} from "../../jsapi/egw_global"; import {Et2Select} from "../Et2Select/Et2Select"; import {Et2VfsSelectRow} from "./Et2VfsSelectRow"; +import {Et2VfsPath} from "./Et2VfsPath"; /** * @summary Select files (including directories) from the VFS @@ -32,7 +33,7 @@ import {Et2VfsSelectRow} from "./Et2VfsSelectRow"; * @dependency et2-select * @dependency et2-vfs-select-row * - * @slot title - Optional additions to title. Works best with `et2-button-icon`. + * @slot title - Optional additions to title. * @slot toolbar - Toolbar containing controls for search & navigation * @slot prefix - Before the toolbar * @slot suffix - Like prefix, but after @@ -48,7 +49,7 @@ import {Et2VfsSelectRow} from "./Et2VfsSelectRow"; * */ -export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMixinInterface +export class Et2VfsSelectDialog extends Et2InputWidget(LitElement) implements SearchMixinInterface { static get styles() { @@ -145,7 +146,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi get _searchNode() : HTMLInputElement { return this.shadowRoot.querySelector("#search");} - get _pathNode() : HTMLElement { return this.shadowRoot.querySelector("#path");} + get _pathNode() : Et2VfsPath { return this.shadowRoot.querySelector("#path");} get _listNode() : HTMLElement { return this.shadowRoot.querySelector("#listbox");} @@ -194,7 +195,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi mime: this.mime || null, name: this.title }; - return this.egw().request(this.egw().link(this.egw().ajaxUrl(this.egw().decodePath(Et2VfsSelect.SERVER_URL))), + return this.egw().request(this.egw().link(this.egw().ajaxUrl(this.egw().decodePath(Et2VfsSelectDialog.SERVER_URL))), [content, attrs]).then((results) => { debugger; @@ -579,7 +580,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi // -1 because we're in keyDown handler, and value is from _before_ this key was pressed if(this._searchNode.value.length - 1 > 0) { - this._searchTimeout = window.setTimeout(() => {this.startSearch()}, Et2VfsSelect.SEARCH_TIMEOUT); + this._searchTimeout = window.setTimeout(() => {this.startSearch()}, Et2VfsSelectDialog.SEARCH_TIMEOUT); } } @@ -605,7 +606,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi @click=${() => this.setPath("/apps/favorites")} > this.setPath("/apps/" + e.target.value)} > @@ -733,10 +734,10 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
- + {this.setPath(this._pathNode.value)}} + >