2024-01-29 17:57:52 +01:00
|
|
|
|
/**
|
|
|
|
|
* 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";
|
2024-02-02 23:20:33 +01:00
|
|
|
|
import {Et2VfsSelectDialog, FileInfo} from "./Et2VfsSelectDialog";
|
|
|
|
|
import {waitForEvent} from "../Et2Widget/event";
|
2024-01-29 17:57:52 +01:00
|
|
|
|
|
|
|
|
|
/**
|
2024-02-21 17:11:45 +01:00
|
|
|
|
* @summary Button to open a file selection dialog, and either return the selected path(s) as a value or take immediate
|
|
|
|
|
* action with them using the `method` property.
|
2024-01-29 17:57:52 +01:00
|
|
|
|
* @since 23.1
|
|
|
|
|
*
|
|
|
|
|
* @dependency et2-vfs-select-dialog
|
|
|
|
|
* @dependency et2-button
|
|
|
|
|
*
|
2024-02-09 16:47:04 +01:00
|
|
|
|
* @slot footer - Buttons are added to the dialog footer. Control their position with CSS `order` property.
|
2024-01-29 17:57:52 +01:00
|
|
|
|
*
|
|
|
|
|
* @event change - Emitted when the control's value changes.
|
|
|
|
|
*
|
2024-02-09 16:47:04 +01:00
|
|
|
|
* @csspart button - The button control
|
|
|
|
|
* @csspart dialog - The et2-vfs-select-dialog
|
2024-01-29 17:57:52 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
export class Et2VfsSelectButton extends Et2InputWidget(LitElement)
|
|
|
|
|
{
|
|
|
|
|
/** Icon for the button */
|
|
|
|
|
@property() image : string;
|
|
|
|
|
|
|
|
|
|
/** Currently selected files */
|
|
|
|
|
@property() value : string[] | FileInfo[] = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The dialog’s 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 */
|
2024-02-09 16:47:04 +01:00
|
|
|
|
@property({type: String, reflect: true, attribute: "method-id"}) methodId : string;
|
2024-01-29 17:57:52 +01:00
|
|
|
|
|
|
|
|
|
protected readonly hasSlotController = new HasSlotController(this, '');
|
2024-02-02 23:20:33 +01:00
|
|
|
|
protected processingPromise : Promise<FileActionResult> = null;
|
2024-01-29 17:57:52 +01:00
|
|
|
|
|
2024-03-20 21:22:43 +01:00
|
|
|
|
get _dialog() : Et2VfsSelectDialog
|
|
|
|
|
{
|
|
|
|
|
return this.hasSlotController.test("[default]") ? <Et2VfsSelectDialog><unknown>this.querySelector("*") :
|
|
|
|
|
<Et2VfsSelectDialog><unknown>this.shadowRoot.querySelector("et2-vfs-select-dialog") ?? null
|
|
|
|
|
};
|
2024-01-29 17:57:52 +01:00
|
|
|
|
|
|
|
|
|
constructor()
|
|
|
|
|
{
|
|
|
|
|
super();
|
|
|
|
|
this.handleClick = this.handleClick.bind(this);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-09 16:47:04 +01:00
|
|
|
|
/** Programmatically trigger the dialog */
|
|
|
|
|
public click()
|
|
|
|
|
{
|
|
|
|
|
this.handleClick(new Event("click"));
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-29 17:57:52 +01:00
|
|
|
|
protected handleClick(event)
|
|
|
|
|
{
|
2024-02-02 23:20:33 +01:00
|
|
|
|
if(this._dialog && typeof this._dialog.show == "function")
|
2024-01-29 17:57:52 +01:00
|
|
|
|
{
|
2024-02-02 23:20:33 +01:00
|
|
|
|
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());
|
|
|
|
|
});
|
2024-01-29 17:57:52 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-09 16:47:04 +01:00
|
|
|
|
/**
|
|
|
|
|
* The select dialog has been closed, now deal with the provided paths
|
|
|
|
|
*
|
|
|
|
|
* @param {string | number} button
|
|
|
|
|
* @param {string[]} paths
|
|
|
|
|
* @protected
|
|
|
|
|
*/
|
|
|
|
|
protected processDialogComplete([button, paths] : [string | number, string[]])
|
2024-01-29 17:57:52 +01:00
|
|
|
|
{
|
2024-02-02 23:20:33 +01:00
|
|
|
|
// Cancel or close do nothing
|
2024-02-09 16:47:04 +01:00
|
|
|
|
if(typeof button !== "undefined" && !button)
|
2024-02-02 23:20:33 +01:00
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const oldValue = this.value;
|
|
|
|
|
this.value = paths ?? [];
|
|
|
|
|
this.requestUpdate("value", oldValue);
|
2024-01-29 17:57:52 +01:00
|
|
|
|
|
|
|
|
|
if(this.method && this.method == "download")
|
|
|
|
|
{
|
|
|
|
|
// download
|
2024-02-02 23:20:33 +01:00
|
|
|
|
this.value.forEach(path =>
|
|
|
|
|
{
|
|
|
|
|
this.egw().open_link(this._dialog.fileInfo(path)?.downloadUrl, "blank", "view", 'download');
|
|
|
|
|
});
|
2024-01-29 17:57:52 +01:00
|
|
|
|
}
|
|
|
|
|
else if(this.method)
|
|
|
|
|
{
|
2024-02-09 16:47:04 +01:00
|
|
|
|
this.sendFiles(button);
|
2024-01-29 17:57:52 +01:00
|
|
|
|
}
|
2024-02-02 23:20:33 +01:00
|
|
|
|
this.updateComplete.then(() =>
|
|
|
|
|
{
|
|
|
|
|
this.dispatchEvent(new Event("change", {bubbles: true}));
|
|
|
|
|
|
|
|
|
|
// Reset value after processing
|
|
|
|
|
if(this.method)
|
|
|
|
|
{
|
|
|
|
|
this.value = [];
|
|
|
|
|
this.requestUpdate("value");
|
|
|
|
|
}
|
|
|
|
|
})
|
2024-01-29 17:57:52 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-02-09 16:47:04 +01:00
|
|
|
|
protected sendFiles(button? : string | number)
|
2024-02-02 23:20:33 +01:00
|
|
|
|
{
|
2024-02-12 18:32:28 +01:00
|
|
|
|
// Some destinations expect only a single value when multiple=false
|
|
|
|
|
let value : string[] | FileInfo[] | string = this.value;
|
|
|
|
|
if(!this.multiple && this.value.length > 0)
|
|
|
|
|
{
|
|
|
|
|
// @ts-ignore This is the typecheck, no need to warn about it
|
|
|
|
|
value = (typeof this.value[0].path != "undefined") ? this.value[0].path : this.value[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send to server
|
2024-02-02 23:20:33 +01:00
|
|
|
|
this.processingPromise = this.egw().request(
|
|
|
|
|
this.method,
|
2024-02-12 18:32:28 +01:00
|
|
|
|
[this.methodId, value, button/*, savemode*/]
|
2024-02-02 23:20:33 +01:00
|
|
|
|
).then((data) =>
|
|
|
|
|
{
|
|
|
|
|
this.processingPromise = null;
|
|
|
|
|
|
|
|
|
|
// UI update now that we're done
|
|
|
|
|
this.requestUpdate();
|
|
|
|
|
return {success: true};
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// UI update, we're busy
|
|
|
|
|
this.requestUpdate();
|
|
|
|
|
}
|
2024-01-29 17:57:52 +01:00
|
|
|
|
|
|
|
|
|
protected dialogTemplate()
|
|
|
|
|
{
|
|
|
|
|
return html`
|
|
|
|
|
<et2-vfs-select-dialog
|
2024-02-09 16:47:04 +01:00
|
|
|
|
part="dialog"
|
2024-01-29 17:57:52 +01:00
|
|
|
|
.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}
|
|
|
|
|
>
|
2024-02-09 16:47:04 +01:00
|
|
|
|
<slot name="footer" slot="footer"></slot>
|
2024-01-29 17:57:52 +01:00
|
|
|
|
</et2-vfs-select-dialog>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
render()
|
|
|
|
|
{
|
|
|
|
|
const hasUserDialog = this.hasSlotController.test("[default]");
|
2024-02-02 23:20:33 +01:00
|
|
|
|
const processing = this.processingPromise !== null;
|
|
|
|
|
const image = processing ? "" : (this.image || "filemanager/navbar");
|
2024-01-29 17:57:52 +01:00
|
|
|
|
|
|
|
|
|
return html`
|
2024-02-09 16:47:04 +01:00
|
|
|
|
<et2-button part="button"
|
|
|
|
|
image=${image}
|
2024-03-27 22:25:15 +01:00
|
|
|
|
title=${this.title}
|
|
|
|
|
helptext=${this.helptext}
|
2024-01-29 17:57:52 +01:00
|
|
|
|
?disabled=${this.disabled}
|
2024-02-02 23:20:33 +01:00
|
|
|
|
?readonly=${this.readonly || processing}
|
2024-01-29 17:57:52 +01:00
|
|
|
|
.noSubmit=${true}
|
|
|
|
|
@click=${this.handleClick}
|
|
|
|
|
>
|
2024-02-02 23:20:33 +01:00
|
|
|
|
${processing ? html`
|
|
|
|
|
<sl-spinner></sl-spinner>` : nothing}
|
2024-01-29 17:57:52 +01:00
|
|
|
|
</et2-button>
|
2024-03-20 21:22:43 +01:00
|
|
|
|
${hasUserDialog ? html`
|
|
|
|
|
<slot></slot>` : this.dialogTemplate()}
|
2024-01-29 17:57:52 +01:00
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 23:20:33 +01:00
|
|
|
|
customElements.define("et2-vfs-select", Et2VfsSelectButton);
|
|
|
|
|
|
|
|
|
|
export interface FileActionResult
|
|
|
|
|
{
|
|
|
|
|
success : boolean,
|
|
|
|
|
message? : string
|
|
|
|
|
}
|