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 {
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;
}
`;

View File

@ -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<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()
{
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}
>
</et2-vfs-select-dialog>
`;
}
@ -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`
<et2-button image=${this.image || "filemanager/navbar"}
<et2-button image=${image}
?disabled=${this.disabled}
?readonly=${this.readonly}
?readonly=${this.readonly || processing}
.noSubmit=${true}
@click=${this.handleClick}
>
${processing ? html`
<sl-spinner></sl-spinner>` : nothing}
</et2-button>
<slot>${hasUserDialog ? nothing : this.dialogTemplate()}</slot>
`;
}
}
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
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<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
* @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`
<et2-button id=${button.id}
button_id=${button.button_id}
class="et2_button et2_vfs__button"
label=${button.label}
variant=${index == 0 ? "primary" : "default"}
slot="footer"
.image=${ifDefined(button.image)}
.noSubmit=${true}
@click=${this.handleButtonClick}
>${button.label}
</et2-button>
`
@ -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
}

View File

@ -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);