Further work on Et2VfsSelect

- Button opens dialog & handles action
- Button shows feedback while processing
- Fix dialog flex spacing
- Fix dialog re-use was not clean
This commit is contained in:
nathan 2024-02-02 15:20:33 -07:00
parent d132609165
commit 2e553911b0
4 changed files with 100 additions and 31 deletions

View File

@ -14,7 +14,7 @@ export default css`
} }
.vfs_select__listbox { .vfs_select__listbox {
flex: 1 1 auto; flex: 2 1 auto;
min-height: 15em; min-height: 15em;
overflow-y: auto; overflow-y: auto;
} }
@ -49,4 +49,8 @@ export default css`
.vfs_select__mimefilter { .vfs_select__mimefilter {
flex: 0 0; flex: 0 0;
} }
:host::part(form-control-help-text) {
flex-basis: min-content !important;
}
`; `;

View File

@ -11,7 +11,8 @@ import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {html, LitElement, nothing} from "lit"; import {html, LitElement, nothing} from "lit";
import {HasSlotController} from "../Et2Widget/slot"; import {HasSlotController} from "../Et2Widget/slot";
import {property} from "lit/decorators/property.js"; 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 * @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 = ""; @property() method : string = "";
/** ID passed to method */ /** ID passed to method */
@property() method_id : string; @property({type: String, reflect: true}) methodId : string;
protected readonly hasSlotController = new HasSlotController(this, ''); protected readonly hasSlotController = new HasSlotController(this, '');
protected processingPromise : Promise<FileActionResult> = 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() constructor()
{ {
super(); super();
this.handleClick = this.handleClick.bind(this); this.handleClick = this.handleClick.bind(this);
this.handleDialogClose = this.handleDialogClose.bind(this);
} }
protected handleClick(event) protected handleClick(event)
{ {
const dialog : any = this.shadowRoot.querySelector("et2-vfs-select-dialog"); if(this._dialog && typeof this._dialog.show == "function")
if(dialog && typeof 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; // Cancel or close do nothing
this.value = dialog.value ?? []; if(!button)
{
return;
}
const oldValue = this.value;
this.value = paths ?? [];
this.requestUpdate("value", oldValue);
if(this.method && this.method == "download") if(this.method && this.method == "download")
{ {
// download // download
this.value.forEach(path =>
{
this.egw().open_link(this._dialog.fileInfo(path)?.downloadUrl, "blank", "view", 'download');
});
} }
else if(this.method) else if(this.method)
{ {
this.sendFiles(); 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() 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() protected dialogTemplate()
{ {
@ -126,9 +173,7 @@ export class Et2VfsSelectButton extends Et2InputWidget(LitElement)
.filename=${this.filename ?? nothing} .filename=${this.filename ?? nothing}
.mime=${this.mime ?? nothing} .mime=${this.mime ?? nothing}
.buttonLabel=${this.buttonLabel ?? nothing} .buttonLabel=${this.buttonLabel ?? nothing}
@close=${this.handleDialogClose}
> >
</et2-vfs-select-dialog> </et2-vfs-select-dialog>
`; `;
} }
@ -137,14 +182,18 @@ export class Et2VfsSelectButton extends Et2InputWidget(LitElement)
render() render()
{ {
const hasUserDialog = this.hasSlotController.test("[default]"); const hasUserDialog = this.hasSlotController.test("[default]");
const processing = this.processingPromise !== null;
const image = processing ? "" : (this.image || "filemanager/navbar");
return html` return html`
<et2-button image=${this.image || "filemanager/navbar"} <et2-button image=${image}
?disabled=${this.disabled} ?disabled=${this.disabled}
?readonly=${this.readonly} ?readonly=${this.readonly || processing}
.noSubmit=${true} .noSubmit=${true}
@click=${this.handleClick} @click=${this.handleClick}
> >
${processing ? html`
<sl-spinner></sl-spinner>` : nothing}
</et2-button> </et2-button>
<slot>${hasUserDialog ? nothing : this.dialogTemplate()}</slot> <slot>${hasUserDialog ? nothing : this.dialogTemplate()}</slot>
`; `;
@ -152,3 +201,9 @@ export class Et2VfsSelectButton extends Et2InputWidget(LitElement)
} }
customElements.define("et2-vfs-select", Et2VfsSelectButton); customElements.define("et2-vfs-select", Et2VfsSelectButton);
export interface FileActionResult
{
success : boolean,
message? : string
}

View File

@ -177,7 +177,6 @@ export class Et2VfsSelectDialog extends Et2InputWidget(LitElement) implements Se
// Use filemanager translations // Use filemanager translations
this.egw().langRequireApp(this.egw().window, "filemanager", () => {this.requestUpdate()}); this.egw().langRequireApp(this.egw().window, "filemanager", () => {this.requestUpdate()});
this.handleButtonClick = this.handleButtonClick.bind(this);
this.handleCreateDirectory = this.handleCreateDirectory.bind(this); this.handleCreateDirectory = this.handleCreateDirectory.bind(this);
this.handleSearchKeyDown = this.handleSearchKeyDown.bind(this); this.handleSearchKeyDown = this.handleSearchKeyDown.bind(this);
} }
@ -266,6 +265,17 @@ export class Et2VfsSelectDialog extends Et2InputWidget(LitElement) implements Se
return parts.join('/') || '/'; 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. * Shows the dialog.
*/ */
@ -292,11 +302,13 @@ export class Et2VfsSelectDialog extends Et2InputWidget(LitElement) implements Se
return this._dialog.hide(); return this._dialog.hide();
} }
getComplete() : Promise<string[]> 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 * Create a new directory in the current one
* @param {MouseEvent | KeyboardEvent} event * @param {MouseEvent | KeyboardEvent} event
@ -680,8 +686,8 @@ export class Et2VfsSelectDialog extends Et2InputWidget(LitElement) implements Se
} }
const buttons = [ const buttons = [
{id: "ok", label: this.buttonLabel, image: image}, {id: "ok", label: this.buttonLabel, image: image, button_id: Et2Dialog.OK_BUTTON},
{id: "cancel", label: "cancel", image: "cancel"} {id: "cancel", label: "cancel", image: "cancel", button_id: Et2Dialog.CANCEL_BUTTON}
]; ];
return html` return html`
@ -689,13 +695,13 @@ export class Et2VfsSelectDialog extends Et2InputWidget(LitElement) implements Se
{ {
return html` return html`
<et2-button id=${button.id} <et2-button id=${button.id}
button_id=${button.button_id}
class="et2_button et2_vfs__button" class="et2_button et2_vfs__button"
label=${button.label} label=${button.label}
variant=${index == 0 ? "primary" : "default"} variant=${index == 0 ? "primary" : "default"}
slot="footer" slot="footer"
.image=${ifDefined(button.image)} .image=${ifDefined(button.image)}
.noSubmit=${true} .noSubmit=${true}
@click=${this.handleButtonClick}
>${button.label} >${button.label}
</et2-button> </et2-button>
` `
@ -794,5 +800,7 @@ export interface FileInfo
isDir : boolean, isDir : boolean,
path? : string, path? : string,
// We want to show it, but not act with it. File is disabled for the UI // 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
} }

View File

@ -644,12 +644,14 @@ class Vfs extends File
$path = $path['path'] ?? $path; $path = $path['path'] ?? $path;
$is_dir = $path['isDir'] ?? Api\Vfs::is_dir($path); $is_dir = $path['isDir'] ?? Api\Vfs::is_dir($path);
$mime = $path['mime'] ?? Api\Vfs::mime_content_type($path); $mime = $path['mime'] ?? Api\Vfs::mime_content_type($path);
$download = $path['download_url'] ?? Api\Vfs::download_url($path);
$response['files'][] = array( $response['files'][] = array(
'name' => $name, 'name' => $name,
'path' => $path, 'path' => $path,
'mime' => $mime, 'mime' => $mime,
'isDir' => $is_dir 'isDir' => $is_dir,
'downloadUrl' => $download
); );
} }
Json\Response::get()->data($response); Json\Response::get()->data($response);