mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-01-13 09:28:29 +01:00
WIP Et2VfsSelect: Now files showing up in list
This commit is contained in:
parent
e014487e86
commit
faeee31155
@ -8,6 +8,7 @@ export default css`
|
||||
et2-dialog::part(body) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 40em;
|
||||
}
|
||||
|
||||
.vfs_select__listbox {
|
||||
@ -19,6 +20,7 @@ export default css`
|
||||
.vfs_select__listbox .vfs_select__empty {
|
||||
height: 100%;
|
||||
min-height: 5em;
|
||||
min-width: 20em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@ -26,6 +28,18 @@ export default css`
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.vfs_select__file_row {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.vfs_select__listbox .vfs_select__loading {
|
||||
text-align: center;
|
||||
line-height: 15em; // 3 * listbox min height
|
||||
}
|
||||
|
||||
.vfs_select__listbox sl-spinner {
|
||||
font-size: 4rem;
|
||||
}
|
||||
.vfs_select__listbox .vfs_select__empty et2-image {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
|
||||
import {html, LitElement, nothing, TemplateResult} from "lit";
|
||||
import {html, LitElement, nothing, PropertyValues, TemplateResult} from "lit";
|
||||
import shoelace from "../Styles/shoelace";
|
||||
import styles from "./Et2VfsSelect.styles";
|
||||
import {property} from "lit/decorators/property.js";
|
||||
@ -22,6 +22,7 @@ import {DialogButton, Et2Dialog} from "../Et2Dialog/Et2Dialog";
|
||||
import {HasSlotController} from "../Et2Widget/slot";
|
||||
import {IegwAppLocal} from "../../jsapi/egw_global";
|
||||
import {Et2Select} from "../Et2Select/Et2Select";
|
||||
import {Et2VfsSelectRow} from "./Et2VfsSelectRow";
|
||||
|
||||
/**
|
||||
* @summary Select files (including directories) from the VFS
|
||||
@ -29,6 +30,7 @@ import {Et2Select} from "../Et2Select/Et2Select";
|
||||
*
|
||||
* @dependency et2-dialog
|
||||
* @dependency et2-select
|
||||
* @dependency et2-vfs-select-row
|
||||
*
|
||||
* @slot title - Optional additions to title. Works best with `et2-button-icon`.
|
||||
* @slot toolbar - Toolbar containing controls for search & navigation
|
||||
@ -70,10 +72,10 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
* Dialog mode
|
||||
* Quickly sets button label, multiple, selection and for "select-dir", mime-type
|
||||
**/
|
||||
@property() mode : "open" | "open-multiple" | "saveas" | "select-dir" = "open";
|
||||
@property({type: String}) mode : "open" | "open-multiple" | "saveas" | "select-dir";
|
||||
|
||||
/** Button label */
|
||||
@property() buttonLabel : string = "Select";
|
||||
@property({type: String}) buttonLabel : string = "Select";
|
||||
|
||||
/** Provide a suggested filename for saving */
|
||||
@property() filename : string = "";
|
||||
@ -95,7 +97,9 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
|
||||
@state() searching = false;
|
||||
@state() open : boolean = false;
|
||||
@state() currentFile;
|
||||
@state() currentFile : Et2VfsSelectRow;
|
||||
@state() selectedFiles : Et2VfsSelectRow[] = [];
|
||||
|
||||
|
||||
// SearchMixinInterface //
|
||||
@property() searchUrl : string = "EGroupware\\Api\\Etemplate\\Widget\\Vfs::ajax_vfsSelectFiles";
|
||||
@ -117,14 +121,16 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
|
||||
protected readonly hasSlotController = new HasSlotController(this, 'help-text', 'toolbar', 'footer');
|
||||
|
||||
protected _fileList = [];
|
||||
protected _fileList : FileInfo[] = [];
|
||||
// @ts-ignore different types
|
||||
protected _appList : SelectOption[] = this.egw().link_app_list("query") ?? [];
|
||||
|
||||
// Internal accessors
|
||||
get _dialog() : Et2Dialog { return this.shadowRoot.querySelector("et2-dialog");}
|
||||
|
||||
get _filenameNode() : HTMLInputElement { return this.shadowRoot.querySelector("#filename");}
|
||||
|
||||
get _fileNodes() : HTMLElement[] { return Array.from(this.shadowRoot.querySelectorAll(".vfs_select__file"));}
|
||||
get _fileNodes() : Et2VfsSelectRow[] { return Array.from(this.shadowRoot.querySelectorAll("et2-vfs-select-row"));}
|
||||
|
||||
get _searchNode() : HTMLInputElement { return this.shadowRoot.querySelector("#search");}
|
||||
|
||||
@ -191,7 +197,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
|
||||
if(this.path == "")
|
||||
{
|
||||
this.path = "~";
|
||||
this.path = <string>this.egw()?.preference("startfolder", "filemanager") || "~";
|
||||
}
|
||||
// Get file list
|
||||
this.startSearch();
|
||||
@ -207,6 +213,27 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
return result;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties : PropertyValues)
|
||||
{
|
||||
super.firstUpdated(changedProperties);
|
||||
debugger;
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties : PropertyValues)
|
||||
{
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if(changedProperties.has("mode"))
|
||||
{
|
||||
this.multiple = this.mode == "open-multiple";
|
||||
}
|
||||
|
||||
if(changedProperties.has("path"))
|
||||
{
|
||||
this.startSearch();
|
||||
}
|
||||
}
|
||||
|
||||
public setPath(path)
|
||||
{
|
||||
if(path == '..')
|
||||
@ -244,6 +271,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
this.startSearch();
|
||||
}
|
||||
return Promise.all([
|
||||
this.updateComplete,
|
||||
this._searchPromise,
|
||||
this._dialog.show()
|
||||
]);
|
||||
@ -258,6 +286,14 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
return this._dialog.hide();
|
||||
}
|
||||
|
||||
getComplete() : Promise<string[]>
|
||||
{
|
||||
return this._dialog.getComplete().then(() =>
|
||||
{
|
||||
return this.value;
|
||||
});
|
||||
}
|
||||
|
||||
startSearch() : Promise<void>
|
||||
{
|
||||
// Stop timeout timer
|
||||
@ -285,8 +321,8 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
{
|
||||
// Include a limit, even if options don't, to avoid massive lists breaking the UI
|
||||
let sendOptions = {
|
||||
path: this._pathNode?.value ?? this.path,
|
||||
mime: this._mimeNode?.value ?? this.mime,
|
||||
path: this.path,
|
||||
mime: this.mime,
|
||||
num_rows: 100,
|
||||
...options
|
||||
}
|
||||
@ -298,10 +334,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
|
||||
processRemoteResults(results) : FileInfo[]
|
||||
{
|
||||
if(results.message)
|
||||
{
|
||||
this.helpText = results.message;
|
||||
}
|
||||
this.helpText = results.message ?? "";
|
||||
this._fileList = results.files ?? [];
|
||||
|
||||
return this._fileList;
|
||||
@ -338,22 +371,178 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected files
|
||||
* @param {Et2VfsSelectRow | Et2VfsSelectRow[]} file
|
||||
* @private
|
||||
*/
|
||||
private setSelectedFiles(file : Et2VfsSelectRow | Et2VfsSelectRow[])
|
||||
{
|
||||
const newSelectedOptions = Array.isArray(file) ? file : [file];
|
||||
|
||||
// Clear existing selection
|
||||
this._fileNodes.forEach(el =>
|
||||
{
|
||||
el.selected = false;
|
||||
el.requestUpdate("selected");
|
||||
});
|
||||
|
||||
// Set the new selection
|
||||
if(newSelectedOptions.length)
|
||||
{
|
||||
newSelectedOptions.forEach(el =>
|
||||
{
|
||||
el.selected = true;
|
||||
el.requestUpdate("selected");
|
||||
});
|
||||
}
|
||||
|
||||
// Update selection, value, and display label
|
||||
this.selectionChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles a file's selected state
|
||||
*/
|
||||
private toggleFileSelection(file : Et2VfsSelectRow, force? : boolean)
|
||||
{
|
||||
if(force === true || force === false)
|
||||
{
|
||||
file.selected = force;
|
||||
}
|
||||
else
|
||||
{
|
||||
file.selected = !file.selected;
|
||||
}
|
||||
|
||||
file.requestUpdate("selected");
|
||||
this.selectionChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must be called whenever the selection changes. It will update the selected file cache, the current
|
||||
* value, and the display value
|
||||
*/
|
||||
private selectionChanged()
|
||||
{
|
||||
// Update selected files cache
|
||||
this.selectedFiles = this._fileNodes.filter(el => el.selected);
|
||||
|
||||
// Update the value
|
||||
if(this.multiple)
|
||||
{
|
||||
this.value = this.selectedFiles.map(el => el.value.path);
|
||||
|
||||
// TODO - show how many are selected?
|
||||
/*
|
||||
if(this.value.length === 0)
|
||||
{
|
||||
// When no items are selected, keep the value empty so the placeholder shows
|
||||
this.displayLabel = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
this.displayLabel = this.localize.term('numOptionsSelected', this.selectedFiles.length);
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
this.value = [this.selectedFiles[0]?.value.path] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
protected handleButtonClick(event : MouseEvent)
|
||||
{
|
||||
if(event.target.id !== "cancel")
|
||||
{
|
||||
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
this.open = false;
|
||||
this.requestUpdate("open", true);
|
||||
}
|
||||
|
||||
protected handleCreateDirectory(event : MouseEvent | KeyboardEvent)
|
||||
/**
|
||||
* Create a new directory in the current one
|
||||
* @param {MouseEvent | KeyboardEvent} event
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
protected async handleCreateDirectory(event : MouseEvent | KeyboardEvent)
|
||||
{
|
||||
throw new Error("Method not implemented.");
|
||||
// Get new directory name
|
||||
let [button, value] = await Et2Dialog.show_prompt(
|
||||
null, this.egw().lang('New directory'), this.egw().lang('Create directory')
|
||||
).getComplete();
|
||||
let dir = value.value;
|
||||
|
||||
if(button && dir)
|
||||
{
|
||||
this.egw().request('EGroupware\\Api\\Etemplate\\Widget\\Vfs::ajax_create_dir', [dir, this.path])
|
||||
.then((msg) =>
|
||||
{
|
||||
this.egw().message(msg);
|
||||
this.setPath(this.path + '/' + dir);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleFileClick(event : MouseEvent)
|
||||
{
|
||||
const target = event.target as HTMLElement;
|
||||
const file : Et2VfsSelectRow = target.closest('et2-vfs-select-row');
|
||||
const oldValue = this.value;
|
||||
|
||||
if(file && !file.disabled)
|
||||
{
|
||||
// Can't select a directory normally
|
||||
if(file.value.isDir && this.mode != "select-dir")
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(this.multiple)
|
||||
{
|
||||
this.toggleFileSelection(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.setSelectedFiles(file);
|
||||
}
|
||||
|
||||
// Set focus after updating so the value is announced by screen readers
|
||||
//this.updateComplete.then(() => this.displayInput.focus({ preventScroll: true }));
|
||||
|
||||
if(this.value !== oldValue)
|
||||
{
|
||||
// Emit after updating
|
||||
this.updateComplete.then(() =>
|
||||
{
|
||||
this.dispatchEvent(new Event('change', {bubbles: true}));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleFileDoubleClick(event : MouseEvent)
|
||||
{
|
||||
const target = event.target as HTMLElement;
|
||||
const file : Et2VfsSelectRow = target.closest('et2-vfs-select-row');
|
||||
|
||||
if(file.value.isDir)
|
||||
{
|
||||
this.toggleFileSelection(file, false);
|
||||
const oldPath = this.path;
|
||||
this.setPath(file.value.path);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a dir, just select it
|
||||
this.handleFileClick(event);
|
||||
|
||||
// If we only want one, we've got it. Close.
|
||||
if(!this.multiple)
|
||||
{
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
handleSearchKeyDown(event)
|
||||
{
|
||||
clearTimeout(this._searchTimeout);
|
||||
@ -362,7 +551,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
if(['ArrowDown', 'ArrowUp'].includes(event.key) && this._fileList.length)
|
||||
{
|
||||
event.stopPropagation();
|
||||
this.setCurrentOption(this._fileNodes[0]);
|
||||
this.setCurrentFile(this._fileNodes[0]);
|
||||
return;
|
||||
}
|
||||
// Start search immediately
|
||||
@ -374,8 +563,10 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
}
|
||||
else if(event.key == "Escape")
|
||||
{
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.value = [];
|
||||
this.close();
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -408,7 +599,11 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
image="filemanager/fav_filter" noSubmit="true"
|
||||
@click=${() => this.setPath("/apps/favorites")}
|
||||
></et2-button>
|
||||
<et2-select-app id="app" emptyLabel="Applications" noLang="1"></et2-select-app>
|
||||
<et2-select id="app" emptyLabel="Applications" noLang="1"
|
||||
.select_options=${this.appList}
|
||||
@change=${(e) => this.setPath("/apps/" + e.target.value)}
|
||||
>
|
||||
</et2-select>
|
||||
<et2-button statustext="Create directory" id="createdir" class="createDir"
|
||||
arial-label=${this.egw().lang("Create directory")}
|
||||
noSubmit="true"
|
||||
@ -417,9 +612,10 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
@click=${this.handleCreateDirectory}
|
||||
></et2-button>
|
||||
<file id="upload_file" statustext="upload file" progress_dropdownlist="true" multiple="true"
|
||||
onFinish="app.vfsSelectUI.storeFile"/>
|
||||
onFinish="app.vfsSelectUI.storeFile"></file>
|
||||
<et2-searchbox id="search"
|
||||
@keydown=${this.handleSearchKeyDown}
|
||||
@sl-clear=${this.startSearch}
|
||||
></et2-searchbox>
|
||||
</et2-box>
|
||||
`;
|
||||
@ -428,28 +624,38 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
protected filesTemplate()
|
||||
{
|
||||
const empty = this._fileList.length == 0;
|
||||
const noFilesTemplate = html`
|
||||
<div class="vfs_select__empty">
|
||||
<et2-image src="filemanager"></et2-image>
|
||||
${this.egw().lang("no files in this directory.")}
|
||||
</div>`;
|
||||
|
||||
const promise = this._searchPromise.then(() =>
|
||||
{
|
||||
return html`
|
||||
${empty ? noFilesTemplate : html`
|
||||
${empty ? this.noFilesTemplate() : html`
|
||||
${repeat(this._fileList, (file) => file.path, (file, index) =>
|
||||
{
|
||||
return html`
|
||||
<et2-vfs-mime
|
||||
.value=${file}
|
||||
></et2-vfs-mime>
|
||||
${file.name}`;
|
||||
<et2-vfs-select-row
|
||||
?disabled=${file.disabled || this.mode == "select-dir" && !file.isDir}
|
||||
.value=${file}
|
||||
@mouseup=${this.handleFileClick}
|
||||
@dblclick=${this.handleFileDoubleClick}
|
||||
></et2-vfs-select-row>`;
|
||||
}
|
||||
)}`
|
||||
}`;
|
||||
});
|
||||
return html`
|
||||
${until(promise, html`<sl-spinner></sl-spinner>`)}`;
|
||||
${until(promise, html`
|
||||
<div class="vfs_select__loading">
|
||||
<sl-spinner></sl-spinner>
|
||||
</div>`)}`;
|
||||
}
|
||||
|
||||
protected noFilesTemplate() : TemplateResult
|
||||
{
|
||||
return html`
|
||||
<div class="vfs_select__empty">
|
||||
<et2-image src="filemanager"></et2-image>
|
||||
${this.egw().lang("no files in this directory.")}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected mimeOptionsTemplate()
|
||||
@ -502,8 +708,8 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
|
||||
return html`
|
||||
<et2-dialog
|
||||
.isModal="true"
|
||||
.destroyOnClose="false"
|
||||
.isModal=${true}
|
||||
.destroyOnClose=${false}
|
||||
.title=${this.title}
|
||||
.open=${this.open}
|
||||
>
|
||||
@ -562,7 +768,6 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
|
||||
</et2-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
customElements.define("et2-vfs-select", Et2VfsSelect);
|
||||
@ -573,4 +778,6 @@ export interface FileInfo
|
||||
mime : string,
|
||||
isDir : boolean,
|
||||
path? : string,
|
||||
// We want to show it, but not act with it. File is disabled for the UI
|
||||
disabled? : boolean
|
||||
}
|
96
api/js/etemplate/Et2Vfs/Et2VfsSelectRow.styles.ts
Normal file
96
api/js/etemplate/Et2Vfs/Et2VfsSelectRow.styles.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import {css} from 'lit';
|
||||
|
||||
export default css`
|
||||
:host {
|
||||
display: block;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:host([disabled]) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.file {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: var(--sl-font-sans);
|
||||
font-size: var(--sl-font-size-medium);
|
||||
font-weight: var(--sl-font-weight-normal);
|
||||
line-height: var(--sl-line-height-normal);
|
||||
letter-spacing: var(--sl-letter-spacing-normal);
|
||||
color: var(--sl-color-neutral-700);
|
||||
padding: var(--sl-spacing-2x-small) var(--sl-spacing-medium) var(--sl-spacing-2x-small) var(--sl-spacing-x-small);
|
||||
transition: var(--sl-transition-fast) fill;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file--hover:not(.file--current):not(.file--disabled) {
|
||||
background-color: var(--sl-color-neutral-100);
|
||||
color: var(--sl-color-neutral-1000);
|
||||
}
|
||||
|
||||
.file--current,
|
||||
.file--current.file--disabled {
|
||||
background-color: var(--sl-color-primary-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.file--disabled {
|
||||
outline: none;
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.file__label {
|
||||
flex: 1 1 auto;
|
||||
display: inline-block;
|
||||
line-height: var(--sl-line-height-dense);
|
||||
}
|
||||
|
||||
.file .file__check {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
visibility: hidden;
|
||||
padding-inline-end: var(--sl-spacing-2x-small);
|
||||
}
|
||||
|
||||
.file et2-vfs-mime {
|
||||
/* line-height-normal has no unit */
|
||||
height: calc(var(--sl-line-height-normal) * 1em);
|
||||
padding-inline-end: var(--sl-spacing-medium);
|
||||
}
|
||||
|
||||
.file--selected .file__check {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.file__prefix,
|
||||
.file__suffix {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.file__prefix::slotted(*) {
|
||||
margin-inline-end: var(--sl-spacing-x-small);
|
||||
}
|
||||
|
||||
.file__suffix::slotted(*) {
|
||||
margin-inline-start: var(--sl-spacing-x-small);
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
:host(:hover:not([aria-disabled='true'])) .file {
|
||||
outline: dashed 1px SelectedItem;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
`;
|
75
api/js/etemplate/Et2Vfs/Et2VfsSelectRow.ts
Normal file
75
api/js/etemplate/Et2Vfs/Et2VfsSelectRow.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import {html, LitElement} from "lit";
|
||||
import {Et2Widget} from "../Et2Widget/Et2Widget";
|
||||
import {FileInfo} from "./Et2VfsSelect";
|
||||
import {property} from "lit/decorators/property.js";
|
||||
import {state} from "lit/decorators/state.js";
|
||||
import {classMap} from "lit/directives/class-map.js";
|
||||
import shoelace from "../Styles/shoelace";
|
||||
import styles from "./Et2VfsSelectRow.styles";
|
||||
|
||||
export class Et2VfsSelectRow extends Et2Widget(LitElement)
|
||||
{
|
||||
static get styles()
|
||||
{
|
||||
return [
|
||||
shoelace,
|
||||
...super.styles,
|
||||
styles
|
||||
];
|
||||
}
|
||||
|
||||
@property({type: Object}) value : FileInfo;
|
||||
|
||||
/** Draws the file in a disabled state, preventing selection. */
|
||||
@property({type: Boolean, reflect: true}) disabled = false;
|
||||
|
||||
@state() current = false; // the user has keyed into the file, but hasn't selected it yet (shows a highlight)
|
||||
@state() selected = false; // the file is selected and has aria-selected="true"
|
||||
@state() hasHover = false; // we need this because Safari doesn't honor :hover styles while dragging
|
||||
|
||||
connectedCallback()
|
||||
{
|
||||
super.connectedCallback();
|
||||
this.setAttribute('role', 'option');
|
||||
this.setAttribute('aria-selected', 'false');
|
||||
}
|
||||
|
||||
private handleMouseEnter()
|
||||
{
|
||||
this.hasHover = true;
|
||||
this.requestUpdate("hasHover", false);
|
||||
}
|
||||
|
||||
private handleMouseLeave()
|
||||
{
|
||||
this.hasHover = false;
|
||||
this.requestUpdate("hasHover", true);
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
return html`
|
||||
<div
|
||||
part="base"
|
||||
class=${classMap({
|
||||
file: true,
|
||||
'file--current': this.current,
|
||||
'file--disabled': this.disabled,
|
||||
'file--selected': this.selected,
|
||||
'file--hover': this.hasHover
|
||||
})}
|
||||
@mouseenter=${this.handleMouseEnter}
|
||||
@mouseleave=${this.handleMouseLeave}
|
||||
>
|
||||
<sl-icon part="checked-icon" class="file__check" name="check" library="system"
|
||||
aria-hidden="true"></sl-icon>
|
||||
<slot part="prefix" name="prefix" class="file__prefix"></slot>
|
||||
<et2-vfs-mime .value=${this.value}></et2-vfs-mime>
|
||||
${this.value.name}
|
||||
<slot part="suffix" name="suffix" class="file__suffix"></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("et2-vfs-select-row", Et2VfsSelectRow);
|
@ -98,6 +98,7 @@ import "./Layout/Et2Split/Et2Split";
|
||||
import "./Layout/RowLimitedMixin";
|
||||
import "./Et2Vfs/Et2VfsMime";
|
||||
import "./Et2Vfs/Et2VfsSelect";
|
||||
import "./Et2Vfs/Et2VfsSelectRow";
|
||||
import "./Et2Vfs/Et2VfsUid";
|
||||
import "./Et2Textbox/Et2Password";
|
||||
import './Et2Textbox/Et2Searchbox';
|
||||
|
@ -612,49 +612,129 @@ class Vfs extends File
|
||||
*/
|
||||
public static function ajax_vfsSelectFiles($search, $content)
|
||||
{
|
||||
$pathIn = $options['path'] ?? '~';
|
||||
if($pathIn == '~')
|
||||
$response = [];
|
||||
$content['path'] = $content['path'] ?? '~';
|
||||
if($content['path'] == '~')
|
||||
{
|
||||
$pathIn = Api\Vfs::get_home_dir();
|
||||
$content['path'] = Api\Vfs::get_home_dir();
|
||||
}
|
||||
$content = [];
|
||||
if(!($files = Api\Vfs::find($pathIn, array(
|
||||
|
||||
// Filemanager favorites as directories
|
||||
if(substr($content['path'], 0, strlen('/apps/favorites')) == '/apps/favorites')
|
||||
{
|
||||
$files = static::filesFromFavorites($search, $content);
|
||||
}
|
||||
else
|
||||
{
|
||||
$files = static::filesFromVfs($search, $content);
|
||||
if(is_string($files))
|
||||
{
|
||||
$response['message'] = $files;
|
||||
$files = [];
|
||||
}
|
||||
}
|
||||
foreach($files as $path)
|
||||
{
|
||||
if(is_string($path) && $path == $content['path'] || is_array($path) && $path['path'] == $content['path'])
|
||||
{
|
||||
// remove directory itself
|
||||
continue;
|
||||
}
|
||||
$name = $path['name'] ?? Api\Vfs::basename($path);
|
||||
$is_dir = $path['isDir'] ?? Api\Vfs::is_dir($path);
|
||||
$mime = $path['mime'] ?? Api\Vfs::mime_content_type($path);
|
||||
if($content['mime'] && !$is_dir && $mime != $content['mime'])
|
||||
{
|
||||
continue; // does not match mime-filter --> ignore
|
||||
}
|
||||
$response['files'][] = array(
|
||||
'name' => $name,
|
||||
'path' => $path,
|
||||
'mime' => $mime,
|
||||
'isDir' => $is_dir
|
||||
);
|
||||
}
|
||||
Json\Response::get()->data($response);
|
||||
}
|
||||
|
||||
private static function filesFromVfs($search, $params)
|
||||
{
|
||||
$vfs_options = array(
|
||||
'dirsontop' => true,
|
||||
'order' => 'name',
|
||||
'sort' => 'ASC',
|
||||
'maxdepth' => 1,
|
||||
))))
|
||||
);
|
||||
if($search)
|
||||
{
|
||||
$content['message'] = lang("Can't open directory %1!", $pathIn);
|
||||
$vfs_options['name_preg'] = '/' . str_replace(array('\\?', '\\*'),
|
||||
array('.{1}', '.*'),
|
||||
preg_quote($search)) . '/i';
|
||||
}
|
||||
else
|
||||
if($params['num_rows'])
|
||||
{
|
||||
$n = 0;
|
||||
foreach($files as $path)
|
||||
{
|
||||
if($path == $pathIn)
|
||||
{
|
||||
continue;
|
||||
} // remove directory itself
|
||||
$vfs_options['limit'] = (int)$params['num_rows'];
|
||||
}
|
||||
if(!($files = Api\Vfs::find($params['path'], $vfs_options)))
|
||||
{
|
||||
return lang("Can't open directory %1!", $params['path']);
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
$name = Api\Vfs::basename($path);
|
||||
$is_dir = Api\Vfs::is_dir($path);
|
||||
$mime = Api\Vfs::mime_content_type($path);
|
||||
if($content['mime'] && !$is_dir && $mime != $content['mime'])
|
||||
{
|
||||
continue; // does not match mime-filter --> ignore
|
||||
}
|
||||
$content['files'][$n] = array(
|
||||
'name' => $name,
|
||||
'path' => $path,
|
||||
/**
|
||||
* Get favorites as if they were folders
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function filesFromFavorites($search, $params)
|
||||
{
|
||||
|
||||
// Display favorites as if they were folders
|
||||
$files = array();
|
||||
$favorites = Api\Framework\Favorites::get_favorites('filemanager');
|
||||
|
||||
//check for recent paths and add them to the top of favorites list
|
||||
if(is_array($params['recentPaths']))
|
||||
{
|
||||
foreach($params['recentPaths'] as $p)
|
||||
{
|
||||
$mime = Api\Vfs::mime_content_type($p);
|
||||
$files[] = array(
|
||||
'name' => $p,
|
||||
'path' => $p,
|
||||
'mime' => $mime,
|
||||
'is_dir' => $is_dir
|
||||
'is_dir' => true
|
||||
);
|
||||
++$n;
|
||||
}
|
||||
}
|
||||
$response = Json\Response::get();
|
||||
$response->data($content);
|
||||
|
||||
foreach($favorites as $favorite)
|
||||
{
|
||||
$path = $favorite['state']['path'];
|
||||
if(!$path)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Search
|
||||
if($search && !(str_contains($favorite['name'], $search) || str_contains($path, $search)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(!Api\Vfs::is_readable($path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$mime = Api\Vfs::mime_content_type($path);
|
||||
$files[] = array(
|
||||
'name' => $favorite['name'],
|
||||
'path' => $path,
|
||||
'mime' => $mime,
|
||||
'isDir' => true
|
||||
);
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user