WIP VFS Select: split off dialog and made button its own component for easier backward compatability

This commit is contained in:
nathan 2024-01-29 09:57:52 -07:00
parent 23ec5ee796
commit 379729daff
6 changed files with 172 additions and 15 deletions

View File

@ -90,8 +90,9 @@ export function inputBasicTests(before : Function, test_value : string, value_se
// Shows as empty / no value // Shows as empty / no value
let value = (<Element><unknown>element).querySelector(value_selector) || (<Element><unknown>element).shadowRoot.querySelector(value_selector); let value = (<Element><unknown>element).querySelector(value_selector) || (<Element><unknown>element).shadowRoot.querySelector(value_selector);
assert.isDefined(value, "Bad value selector '" + 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) if(element.multiple)
{ {
assert.isEmpty(element.get_value()); assert.isEmpty(element.get_value());

View File

@ -15,7 +15,7 @@ import {property} from "lit/decorators/property.js";
import {state} from "lit/decorators/state.js"; import {state} from "lit/decorators/state.js";
import {classMap} from "lit/directives/class-map.js"; import {classMap} from "lit/directives/class-map.js";
import {repeat} from "lit/directives/repeat.js"; import {repeat} from "lit/directives/repeat.js";
import {FileInfo} from "./Et2VfsSelect"; import {FileInfo} from "./Et2VfsSelectDialog";
import {SlBreadcrumbItem} from "@shoelace-style/shoelace"; import {SlBreadcrumbItem} from "@shoelace-style/shoelace";
import {HasSlotController} from "../Et2Widget/slot"; import {HasSlotController} from "../Et2Widget/slot";

View File

@ -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 dialogs 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`
<et2-vfs-select-dialog
.title=${this.title ?? nothing}
.value=${this.value ?? nothing}
.mode=${this.mode ?? nothing}
.multiple=${this.multiple ?? nothing}
.path=${this.path ?? nothing}
.filename=${this.filename ?? nothing}
.mime=${this.mime ?? nothing}
.buttonLabel=${this.buttonLabel ?? nothing}
@close=${this.handleDialogClose}
>
</et2-vfs-select-dialog>
`;
}
render()
{
const hasUserDialog = this.hasSlotController.test("[default]");
return html`
<et2-button image=${this.image || "filemanager/navbar"}
?disabled=${this.disabled}
?readonly=${this.readonly}
.noSubmit=${true}
@click=${this.handleClick}
>
</et2-button>
<slot>${hasUserDialog ? nothing : this.dialogTemplate()}</slot>
`;
}
}
customElements.define("et2-vfs-select", Et2VfsSelectButton);

View File

@ -23,6 +23,7 @@ import {HasSlotController} from "../Et2Widget/slot";
import {IegwAppLocal} from "../../jsapi/egw_global"; import {IegwAppLocal} from "../../jsapi/egw_global";
import {Et2Select} from "../Et2Select/Et2Select"; import {Et2Select} from "../Et2Select/Et2Select";
import {Et2VfsSelectRow} from "./Et2VfsSelectRow"; import {Et2VfsSelectRow} from "./Et2VfsSelectRow";
import {Et2VfsPath} from "./Et2VfsPath";
/** /**
* @summary Select files (including directories) from the VFS * @summary Select files (including directories) from the VFS
@ -32,7 +33,7 @@ import {Et2VfsSelectRow} from "./Et2VfsSelectRow";
* @dependency et2-select * @dependency et2-select
* @dependency et2-vfs-select-row * @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 toolbar - Toolbar containing controls for search & navigation
* @slot prefix - Before the toolbar * @slot prefix - Before the toolbar
* @slot suffix - Like prefix, but after * @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() static get styles()
{ {
@ -145,7 +146,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
get _searchNode() : HTMLInputElement { return this.shadowRoot.querySelector("#search");} 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");} get _listNode() : HTMLElement { return this.shadowRoot.querySelector("#listbox");}
@ -194,7 +195,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
mime: this.mime || null, mime: this.mime || null,
name: this.title 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) => [content, attrs]).then((results) =>
{ {
debugger; 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 // -1 because we're in keyDown handler, and value is from _before_ this key was pressed
if(this._searchNode.value.length - 1 > 0) 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")} @click=${() => this.setPath("/apps/favorites")}
></et2-button> ></et2-button>
<et2-select id="app" emptyLabel="Applications" noLang="1" <et2-select id="app" emptyLabel="Applications" noLang="1"
.select_options=${this.appList} .select_options=${this._appList}
@change=${(e) => this.setPath("/apps/" + e.target.value)} @change=${(e) => this.setPath("/apps/" + e.target.value)}
> >
</et2-select> </et2-select>
@ -733,10 +734,10 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
<div <div
part="path" part="path"
> >
<input id="path" <et2-vfs-path id="path"
value=${this.path} .value=${this.path}
@onchange=${this.startSearch} @change=${() => {this.setPath(this._pathNode.value)}}
/> ></et2-vfs-path>
</div> </div>
<div <div
id="listbox" id="listbox"
@ -784,7 +785,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
} }
} }
customElements.define("et2-vfs-select", Et2VfsSelect); customElements.define("et2-vfs-select-dialog", Et2VfsSelectDialog);
export interface FileInfo export interface FileInfo
{ {

View File

@ -1,6 +1,6 @@
import {html, LitElement} from "lit"; import {html, LitElement} from "lit";
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
import {FileInfo} from "./Et2VfsSelect"; import {FileInfo} from "./Et2VfsSelectDialog";
import {property} from "lit/decorators/property.js"; import {property} from "lit/decorators/property.js";
import {state} from "lit/decorators/state.js"; import {state} from "lit/decorators/state.js";
import {classMap} from "lit/directives/class-map.js"; import {classMap} from "lit/directives/class-map.js";

View File

@ -98,7 +98,8 @@ import "./Layout/Et2Split/Et2Split";
import "./Layout/RowLimitedMixin"; import "./Layout/RowLimitedMixin";
import "./Et2Vfs/Et2VfsMime"; import "./Et2Vfs/Et2VfsMime";
import "./Et2Vfs/Et2VfsPath"; import "./Et2Vfs/Et2VfsPath";
import "./Et2Vfs/Et2VfsSelect"; import "./Et2Vfs/Et2VfsSelectButton";
import "./Et2Vfs/Et2VfsSelectDialog";
import "./Et2Vfs/Et2VfsSelectRow"; import "./Et2Vfs/Et2VfsSelectRow";
import "./Et2Vfs/Et2VfsUid"; import "./Et2Vfs/Et2VfsUid";
import "./Et2Textbox/Et2Password"; import "./Et2Textbox/Et2Password";