egroupware/api/js/etemplate/Et2Vfs/Et2VfsSelectButton.ts
nathan 2e553911b0 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
2024-02-02 15:20:33 -07:00

209 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 {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
* @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({type: String, reflect: true}) methodId : string;
protected readonly hasSlotController = new HasSlotController(this, '');
protected processingPromise : Promise<FileActionResult> = null;
get _dialog() : Et2VfsSelectDialog {return this.shadowRoot.querySelector("et2-vfs-select-dialog") ?? null};
constructor()
{
super();
this.handleClick = this.handleClick.bind(this);
}
protected handleClick(event)
{
if(this._dialog && typeof this._dialog.show == "function")
{
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 processDialogComplete([button, paths])
{
// 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()
{
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}
>
</et2-vfs-select-dialog>
`;
}
render()
{
const hasUserDialog = this.hasSlotController.test("[default]");
const processing = this.processingPromise !== null;
const image = processing ? "" : (this.image || "filemanager/navbar");
return html`
<et2-button image=${image}
?disabled=${this.disabled}
?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);
export interface FileActionResult
{
success : boolean,
message? : string
}