WIP Et2VfsSelect: Now files showing up in list

This commit is contained in:
nathan 2024-01-17 16:32:09 -07:00
parent e014487e86
commit faeee31155
6 changed files with 540 additions and 67 deletions

View File

@ -8,6 +8,7 @@ export default css`
et2-dialog::part(body) { et2-dialog::part(body) {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height: 40em;
} }
.vfs_select__listbox { .vfs_select__listbox {
@ -19,6 +20,7 @@ export default css`
.vfs_select__listbox .vfs_select__empty { .vfs_select__listbox .vfs_select__empty {
height: 100%; height: 100%;
min-height: 5em; min-height: 5em;
min-width: 20em;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@ -26,6 +28,18 @@ export default css`
user-select: none; 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 { .vfs_select__listbox .vfs_select__empty et2-image {
margin-top: auto; margin-top: auto;
} }

View File

@ -8,7 +8,7 @@
*/ */
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; 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 shoelace from "../Styles/shoelace";
import styles from "./Et2VfsSelect.styles"; import styles from "./Et2VfsSelect.styles";
import {property} from "lit/decorators/property.js"; import {property} from "lit/decorators/property.js";
@ -22,6 +22,7 @@ import {DialogButton, Et2Dialog} from "../Et2Dialog/Et2Dialog";
import {HasSlotController} from "../Et2Widget/slot"; import {HasSlotController} from "../Et2Widget/slot";
import {IegwAppLocal} from "../../jsapi/egw_global"; import {IegwAppLocal} from "../../jsapi/egw_global";
import {Et2Select} from "../Et2Select/Et2Select"; import {Et2Select} from "../Et2Select/Et2Select";
import {Et2VfsSelectRow} from "./Et2VfsSelectRow";
/** /**
* @summary Select files (including directories) from the VFS * @summary Select files (including directories) from the VFS
@ -29,6 +30,7 @@ import {Et2Select} from "../Et2Select/Et2Select";
* *
* @dependency et2-dialog * @dependency et2-dialog
* @dependency et2-select * @dependency et2-select
* @dependency et2-vfs-select-row
* *
* @slot title - Optional additions to title. Works best with `et2-button-icon`. * @slot title - Optional additions to title. Works best with `et2-button-icon`.
* @slot toolbar - Toolbar containing controls for search & navigation * @slot toolbar - Toolbar containing controls for search & navigation
@ -70,10 +72,10 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
* Dialog mode * Dialog mode
* Quickly sets button label, multiple, selection and for "select-dir", mime-type * 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 */ /** Button label */
@property() buttonLabel : string = "Select"; @property({type: String}) buttonLabel : string = "Select";
/** Provide a suggested filename for saving */ /** Provide a suggested filename for saving */
@property() filename : string = ""; @property() filename : string = "";
@ -95,7 +97,9 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
@state() searching = false; @state() searching = false;
@state() open : boolean = false; @state() open : boolean = false;
@state() currentFile; @state() currentFile : Et2VfsSelectRow;
@state() selectedFiles : Et2VfsSelectRow[] = [];
// SearchMixinInterface // // SearchMixinInterface //
@property() searchUrl : string = "EGroupware\\Api\\Etemplate\\Widget\\Vfs::ajax_vfsSelectFiles"; @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 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 // Internal accessors
get _dialog() : Et2Dialog { return this.shadowRoot.querySelector("et2-dialog");} get _dialog() : Et2Dialog { return this.shadowRoot.querySelector("et2-dialog");}
get _filenameNode() : HTMLInputElement { return this.shadowRoot.querySelector("#filename");} 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");} get _searchNode() : HTMLInputElement { return this.shadowRoot.querySelector("#search");}
@ -191,7 +197,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
if(this.path == "") if(this.path == "")
{ {
this.path = "~"; this.path = <string>this.egw()?.preference("startfolder", "filemanager") || "~";
} }
// Get file list // Get file list
this.startSearch(); this.startSearch();
@ -207,6 +213,27 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
return result; 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) public setPath(path)
{ {
if(path == '..') if(path == '..')
@ -244,6 +271,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
this.startSearch(); this.startSearch();
} }
return Promise.all([ return Promise.all([
this.updateComplete,
this._searchPromise, this._searchPromise,
this._dialog.show() this._dialog.show()
]); ]);
@ -258,6 +286,14 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
return this._dialog.hide(); return this._dialog.hide();
} }
getComplete() : Promise<string[]>
{
return this._dialog.getComplete().then(() =>
{
return this.value;
});
}
startSearch() : Promise<void> startSearch() : Promise<void>
{ {
// Stop timeout timer // 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 // Include a limit, even if options don't, to avoid massive lists breaking the UI
let sendOptions = { let sendOptions = {
path: this._pathNode?.value ?? this.path, path: this.path,
mime: this._mimeNode?.value ?? this.mime, mime: this.mime,
num_rows: 100, num_rows: 100,
...options ...options
} }
@ -298,10 +334,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
processRemoteResults(results) : FileInfo[] processRemoteResults(results) : FileInfo[]
{ {
if(results.message) this.helpText = results.message ?? "";
{
this.helpText = results.message;
}
this._fileList = results.files ?? []; this._fileList = results.files ?? [];
return this._fileList; 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) protected handleButtonClick(event : MouseEvent)
{ {
if(event.target.id !== "cancel")
{
throw new Error("Method not implemented.");
}
this.open = false; this.open = false;
this.requestUpdate("open", true); 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) handleSearchKeyDown(event)
{ {
clearTimeout(this._searchTimeout); clearTimeout(this._searchTimeout);
@ -362,7 +551,7 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
if(['ArrowDown', 'ArrowUp'].includes(event.key) && this._fileList.length) if(['ArrowDown', 'ArrowUp'].includes(event.key) && this._fileList.length)
{ {
event.stopPropagation(); event.stopPropagation();
this.setCurrentOption(this._fileNodes[0]); this.setCurrentFile(this._fileNodes[0]);
return; return;
} }
// Start search immediately // Start search immediately
@ -374,8 +563,10 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
} }
else if(event.key == "Escape") else if(event.key == "Escape")
{ {
event.stopPropagation();
event.preventDefault();
this.value = []; this.value = [];
this.close(); this.hide();
return; return;
} }
@ -408,7 +599,11 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
image="filemanager/fav_filter" noSubmit="true" image="filemanager/fav_filter" noSubmit="true"
@click=${() => this.setPath("/apps/favorites")} @click=${() => this.setPath("/apps/favorites")}
></et2-button> ></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" <et2-button statustext="Create directory" id="createdir" class="createDir"
arial-label=${this.egw().lang("Create directory")} arial-label=${this.egw().lang("Create directory")}
noSubmit="true" noSubmit="true"
@ -417,9 +612,10 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
@click=${this.handleCreateDirectory} @click=${this.handleCreateDirectory}
></et2-button> ></et2-button>
<file id="upload_file" statustext="upload file" progress_dropdownlist="true" multiple="true" <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" <et2-searchbox id="search"
@keydown=${this.handleSearchKeyDown} @keydown=${this.handleSearchKeyDown}
@sl-clear=${this.startSearch}
></et2-searchbox> ></et2-searchbox>
</et2-box> </et2-box>
`; `;
@ -428,28 +624,38 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
protected filesTemplate() protected filesTemplate()
{ {
const empty = this._fileList.length == 0; 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(() => const promise = this._searchPromise.then(() =>
{ {
return html` return html`
${empty ? noFilesTemplate : html` ${empty ? this.noFilesTemplate() : html`
${repeat(this._fileList, (file) => file.path, (file, index) => ${repeat(this._fileList, (file) => file.path, (file, index) =>
{ {
return html` return html`
<et2-vfs-mime <et2-vfs-select-row
.value=${file} ?disabled=${file.disabled || this.mode == "select-dir" && !file.isDir}
></et2-vfs-mime> .value=${file}
${file.name}`; @mouseup=${this.handleFileClick}
@dblclick=${this.handleFileDoubleClick}
></et2-vfs-select-row>`;
} }
)}` )}`
}`; }`;
}); });
return html` 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() protected mimeOptionsTemplate()
@ -502,8 +708,8 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
return html` return html`
<et2-dialog <et2-dialog
.isModal="true" .isModal=${true}
.destroyOnClose="false" .destroyOnClose=${false}
.title=${this.title} .title=${this.title}
.open=${this.open} .open=${this.open}
> >
@ -562,7 +768,6 @@ export class Et2VfsSelect extends Et2InputWidget(LitElement) implements SearchMi
</et2-dialog> </et2-dialog>
`; `;
} }
} }
customElements.define("et2-vfs-select", Et2VfsSelect); customElements.define("et2-vfs-select", Et2VfsSelect);
@ -573,4 +778,6 @@ export interface FileInfo
mime : string, mime : string,
isDir : boolean, isDir : boolean,
path? : string, path? : string,
// We want to show it, but not act with it. File is disabled for the UI
disabled? : boolean
} }

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

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

View File

@ -98,6 +98,7 @@ import "./Layout/Et2Split/Et2Split";
import "./Layout/RowLimitedMixin"; import "./Layout/RowLimitedMixin";
import "./Et2Vfs/Et2VfsMime"; import "./Et2Vfs/Et2VfsMime";
import "./Et2Vfs/Et2VfsSelect"; import "./Et2Vfs/Et2VfsSelect";
import "./Et2Vfs/Et2VfsSelectRow";
import "./Et2Vfs/Et2VfsUid"; import "./Et2Vfs/Et2VfsUid";
import "./Et2Textbox/Et2Password"; import "./Et2Textbox/Et2Password";
import './Et2Textbox/Et2Searchbox'; import './Et2Textbox/Et2Searchbox';

View File

@ -612,49 +612,129 @@ class Vfs extends File
*/ */
public static function ajax_vfsSelectFiles($search, $content) public static function ajax_vfsSelectFiles($search, $content)
{ {
$pathIn = $options['path'] ?? '~'; $response = [];
if($pathIn == '~') $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, 'dirsontop' => true,
'order' => 'name', 'order' => 'name',
'sort' => 'ASC', 'sort' => 'ASC',
'maxdepth' => 1, '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; $vfs_options['limit'] = (int)$params['num_rows'];
foreach($files as $path) }
{ if(!($files = Api\Vfs::find($params['path'], $vfs_options)))
if($path == $pathIn) {
{ return lang("Can't open directory %1!", $params['path']);
continue; }
} // remove directory itself return $files;
}
$name = Api\Vfs::basename($path); /**
$is_dir = Api\Vfs::is_dir($path); * Get favorites as if they were folders
$mime = Api\Vfs::mime_content_type($path); *
if($content['mime'] && !$is_dir && $mime != $content['mime']) * @return array
{ */
continue; // does not match mime-filter --> ignore private static function filesFromFavorites($search, $params)
} {
$content['files'][$n] = array(
'name' => $name, // Display favorites as if they were folders
'path' => $path, $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, '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;
} }
/** /**