diff --git a/api/js/etemplate/Et2Vfs/Et2VfsSelect.styles.ts b/api/js/etemplate/Et2Vfs/Et2VfsSelect.styles.ts index 0c03ce0438..a75ff301da 100644 --- a/api/js/etemplate/Et2Vfs/Et2VfsSelect.styles.ts +++ b/api/js/etemplate/Et2Vfs/Et2VfsSelect.styles.ts @@ -14,7 +14,7 @@ export default css` } .vfs_select__listbox { - flex: 1 1 auto; + flex: 2 1 auto; min-height: 15em; overflow-y: auto; } @@ -49,4 +49,8 @@ export default css` .vfs_select__mimefilter { flex: 0 0; } + + :host::part(form-control-help-text) { + flex-basis: min-content !important; + } `; \ No newline at end of file diff --git a/api/js/etemplate/Et2Vfs/Et2VfsSelectButton.ts b/api/js/etemplate/Et2Vfs/Et2VfsSelectButton.ts index 20f29e1692..4690be5175 100644 --- a/api/js/etemplate/Et2Vfs/Et2VfsSelectButton.ts +++ b/api/js/etemplate/Et2Vfs/Et2VfsSelectButton.ts @@ -11,7 +11,8 @@ 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"; +import {Et2VfsSelectDialog, FileInfo} from "./Et2VfsSelectDialog"; +import {waitForEvent} from "../Et2Widget/event"; /** * @summary Button to open a file selection dialog, and return the selected path(s) as a value @@ -75,44 +76,90 @@ export class Et2VfsSelectButton extends Et2InputWidget(LitElement) */ @property() method : string = ""; /** ID passed to method */ - @property() method_id : string; + @property({type: String, reflect: true}) methodId : string; protected readonly hasSlotController = new HasSlotController(this, ''); + protected processingPromise : Promise = null; - //private _dialog : Et2VfsSelectDialog = this.shadowRoot.querySelector("et2-vfs-select-dialog") ?? null; + get _dialog() : Et2VfsSelectDialog {return 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") + if(this._dialog && typeof this._dialog.show == "function") { - dialog.show(); + this._dialog.show(); + // Avoids dialog showing old value if reused + this._dialog.requestUpdate("value"); + + // This is were we bind to get informed when user closes the dialog + waitForEvent(this._dialog, "sl-after-show").then(async() => + { + this.processDialogComplete(await this._dialog.getComplete()); + }); } } - protected handleDialogClose(event) + protected processDialogComplete([button, paths]) { - debugger; - this.value = dialog.value ?? []; + // Cancel or close do nothing + if(!button) + { + return; + } + + const oldValue = this.value; + this.value = paths ?? []; + this.requestUpdate("value", oldValue); if(this.method && this.method == "download") { // download + this.value.forEach(path => + { + this.egw().open_link(this._dialog.fileInfo(path)?.downloadUrl, "blank", "view", 'download'); + }); } else if(this.method) { this.sendFiles(); } + this.updateComplete.then(() => + { + this.dispatchEvent(new Event("change", {bubbles: true})); + + // Reset value after processing + if(this.method) + { + this.value = []; + this.requestUpdate("value"); + } + }) } protected sendFiles() + { + this.processingPromise = this.egw().request( + this.method, + [this.methodId, this.value/*, submit_button_id, savemode*/] + ).then((data) => + { + this.processingPromise = null; + + // UI update now that we're done + this.requestUpdate(); + return {success: true}; + } + ); + + // UI update, we're busy + this.requestUpdate(); + } protected dialogTemplate() { @@ -126,9 +173,7 @@ export class Et2VfsSelectButton extends Et2InputWidget(LitElement) .filename=${this.filename ?? nothing} .mime=${this.mime ?? nothing} .buttonLabel=${this.buttonLabel ?? nothing} - @close=${this.handleDialogClose} > - `; } @@ -137,18 +182,28 @@ export class Et2VfsSelectButton extends Et2InputWidget(LitElement) render() { const hasUserDialog = this.hasSlotController.test("[default]"); + const processing = this.processingPromise !== null; + const image = processing ? "" : (this.image || "filemanager/navbar"); return html` - + ${processing ? html` + ` : nothing} ${hasUserDialog ? nothing : this.dialogTemplate()} `; } } -customElements.define("et2-vfs-select", Et2VfsSelectButton); \ No newline at end of file +customElements.define("et2-vfs-select", Et2VfsSelectButton); + +export interface FileActionResult +{ + success : boolean, + message? : string +} \ No newline at end of file diff --git a/api/js/etemplate/Et2Vfs/Et2VfsSelectDialog.ts b/api/js/etemplate/Et2Vfs/Et2VfsSelectDialog.ts index 124a97d543..abd02ffd76 100644 --- a/api/js/etemplate/Et2Vfs/Et2VfsSelectDialog.ts +++ b/api/js/etemplate/Et2Vfs/Et2VfsSelectDialog.ts @@ -177,7 +177,6 @@ export class Et2VfsSelectDialog extends Et2InputWidget(LitElement) implements Se // Use filemanager translations this.egw().langRequireApp(this.egw().window, "filemanager", () => {this.requestUpdate()}); - this.handleButtonClick = this.handleButtonClick.bind(this); this.handleCreateDirectory = this.handleCreateDirectory.bind(this); this.handleSearchKeyDown = this.handleSearchKeyDown.bind(this); } @@ -266,6 +265,17 @@ export class Et2VfsSelectDialog extends Et2InputWidget(LitElement) implements Se return parts.join('/') || '/'; } + /** + * Get file information of currently displayed paths + * + * Returns null if the path is not currently displayed + * @param _path + */ + public fileInfo(_path) + { + return this._fileList.find(f => f.path == _path); + } + /** * Shows the dialog. */ @@ -292,11 +302,13 @@ export class Et2VfsSelectDialog extends Et2InputWidget(LitElement) implements Se return this._dialog.hide(); } - getComplete() : Promise + getComplete() : Promise<[number, Object]> { - return this._dialog.getComplete().then(() => + return this._dialog.getComplete().then((value) => { - return this.value; + // Overwrite dialog's value with what we say + value[1] = this.value; + return value }); } @@ -459,12 +471,6 @@ export class Et2VfsSelectDialog extends Et2InputWidget(LitElement) implements Se } } - protected handleButtonClick(event : MouseEvent) - { - this.open = false; - this.requestUpdate("open", true); - } - /** * Create a new directory in the current one * @param {MouseEvent | KeyboardEvent} event @@ -680,8 +686,8 @@ export class Et2VfsSelectDialog extends Et2InputWidget(LitElement) implements Se } const buttons = [ - {id: "ok", label: this.buttonLabel, image: image}, - {id: "cancel", label: "cancel", image: "cancel"} + {id: "ok", label: this.buttonLabel, image: image, button_id: Et2Dialog.OK_BUTTON}, + {id: "cancel", label: "cancel", image: "cancel", button_id: Et2Dialog.CANCEL_BUTTON} ]; return html` @@ -689,13 +695,13 @@ export class Et2VfsSelectDialog extends Et2InputWidget(LitElement) implements Se { return html` ${button.label} ` @@ -794,5 +800,7 @@ export interface FileInfo isDir : boolean, path? : string, // We want to show it, but not act with it. File is disabled for the UI - disabled? : boolean + disabled? : boolean, + // Direct download link + downloadUrl? : string } \ No newline at end of file diff --git a/api/src/Etemplate/Widget/Vfs.php b/api/src/Etemplate/Widget/Vfs.php index ba83299661..595f18397f 100644 --- a/api/src/Etemplate/Widget/Vfs.php +++ b/api/src/Etemplate/Widget/Vfs.php @@ -644,12 +644,14 @@ class Vfs extends File $path = $path['path'] ?? $path; $is_dir = $path['isDir'] ?? Api\Vfs::is_dir($path); $mime = $path['mime'] ?? Api\Vfs::mime_content_type($path); + $download = $path['download_url'] ?? Api\Vfs::download_url($path); $response['files'][] = array( 'name' => $name, 'path' => $path, 'mime' => $mime, - 'isDir' => $is_dir + 'isDir' => $is_dir, + 'downloadUrl' => $download ); } Json\Response::get()->data($response);