mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-05 14:20:40 +01:00
a731143be6
- Remember last used path, start there next time if path was not provided - Single click now opens sub-dir. Current path is used for select-dir mode - Fix Copy To in Et2LinkList context menu
242 lines
6.9 KiB
TypeScript
242 lines
6.9 KiB
TypeScript
/**
|
||
* 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 either return the selected path(s) as a value or take immediate
|
||
* action with them using the `method` property.
|
||
* @since 23.1
|
||
*
|
||
* @dependency et2-vfs-select-dialog
|
||
* @dependency et2-button
|
||
*
|
||
* @slot footer - Buttons are added to the dialog footer. Control their position with CSS `order` property.
|
||
*
|
||
* @event change - Emitted when the control's value changes.
|
||
*
|
||
* @csspart button - The button control
|
||
* @csspart dialog - The et2-vfs-select-dialog
|
||
*/
|
||
|
||
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 */
|
||
@property({type: String, reflect: true, attribute: "method-id"}) methodId : string;
|
||
|
||
protected readonly hasSlotController = new HasSlotController(this, '');
|
||
protected processingPromise : Promise<FileActionResult> = null;
|
||
|
||
get _dialog() : Et2VfsSelectDialog
|
||
{
|
||
return this.hasSlotController.test("[default]") ? <Et2VfsSelectDialog><unknown>this.querySelector("*") :
|
||
<Et2VfsSelectDialog><unknown>this.shadowRoot.querySelector("et2-vfs-select-dialog") ?? null
|
||
};
|
||
|
||
constructor()
|
||
{
|
||
super();
|
||
this.handleClick = this.handleClick.bind(this);
|
||
}
|
||
|
||
/** Programmatically trigger the dialog */
|
||
public click()
|
||
{
|
||
this.updateComplete.then(() =>
|
||
{
|
||
this.handleClick(new Event("click"));
|
||
})
|
||
}
|
||
|
||
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());
|
||
this.dispatchEvent(new Event("close", {bubbles: true}));
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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[]])
|
||
{
|
||
// Cancel or close do nothing
|
||
if(typeof button !== "undefined" && !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(button);
|
||
}
|
||
this.updateComplete.then(() =>
|
||
{
|
||
this.dispatchEvent(new Event("change", {bubbles: true}));
|
||
|
||
// Reset value after processing
|
||
if(this.method)
|
||
{
|
||
this.value = [];
|
||
this.requestUpdate("value");
|
||
}
|
||
})
|
||
}
|
||
|
||
protected sendFiles(button? : string | number)
|
||
{
|
||
// 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
|
||
this.processingPromise = this.egw().request(
|
||
this.method,
|
||
[this.methodId, value, button/*, 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
|
||
part="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}
|
||
>
|
||
<slot name="footer" slot="footer"></slot>
|
||
</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 part="button"
|
||
image=${image}
|
||
title=${this.title}
|
||
helptext=${this.helptext}
|
||
?disabled=${this.disabled}
|
||
?readonly=${this.readonly || processing}
|
||
.noSubmit=${true}
|
||
@click=${this.handleClick}
|
||
>
|
||
${processing ? html`
|
||
<sl-spinner></sl-spinner>` : nothing}
|
||
</et2-button>
|
||
${hasUserDialog ? html`
|
||
<slot></slot>` : this.dialogTemplate()}
|
||
`;
|
||
}
|
||
}
|
||
|
||
customElements.define("et2-vfs-select", Et2VfsSelectButton);
|
||
|
||
export interface FileActionResult
|
||
{
|
||
success : boolean,
|
||
message? : string
|
||
} |