@dotglitch/ngx-common
Version:
Angular components and utilities that are commonly used.
629 lines • 132 kB
JavaScript
import { ScrollingModule } from '@angular/cdk/scrolling';
import { DatePipe, NgForOf, NgIf } from '@angular/common';
import { Component, EventEmitter, Input, Output, SecurityContext, TemplateRef, ViewChild } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatTabsModule } from '@angular/material/tabs';
import { MenuDirective, openMenu } from '@dotglitch/ngx-common/core';
import { NgScrollbarModule } from 'ngx-scrollbar';
import { TabulatorComponent, TabulatorModule } from 'ngx-tabulator-tables';
import { uploadFile } from '../helpers';
import { IconResolver } from '../icon-resolver';
import * as i0 from "@angular/core";
import * as i1 from "@dotglitch/ngx-common/core";
import * as i2 from "@angular/material/dialog";
import * as i3 from "../filemanager.component";
import * as i4 from "@angular/platform-browser";
import * as i5 from "ngx-scrollbar";
import * as i6 from "@angular/material/checkbox";
import * as i7 from "@angular/material/progress-bar";
import * as i8 from "@angular/material/icon";
import * as i9 from "@angular/material/button";
import * as i10 from "ngx-tabulator-tables";
import * as i11 from "@angular/cdk/scrolling";
const itemWidth = (80 + 20);
export class FileGridComponent {
set path(value) {
if (!value)
return;
if (this._path && this.config.navigateOnlyToDescendants) {
if (!value.startsWith('/'))
value = '/' + value;
if (!value.startsWith(this.config.path))
return;
}
let prev = this._path;
this._path = value;
if (prev != value) {
this.pathChange.next(this.path);
if (this.config.apiSettings)
this.loadFolder();
}
}
get path() { return this._path; }
performChecksum(path, digest) {
// this.windowManager.openWindow({
// appId: "checksum",
// data: { digest, path },
// workspace: this.windowRef.workspace,
// width: 600,
// height: 250
// });
}
get libConfig() { return this.fileManager.libConfig; }
constructor(fetch, keyboard, dialog, matDialog, fileManager, changeDetector, sanitizer) {
this.fetch = fetch;
this.keyboard = keyboard;
this.dialog = dialog;
this.matDialog = matDialog;
this.fileManager = fileManager;
this.changeDetector = changeDetector;
this.sanitizer = sanitizer;
this.pathChange = new EventEmitter();
this.config = {};
this.showHiddenFiles = false;
this.viewMode = "grid";
this.gridSize = "normal";
this.fileSelect = new EventEmitter();
this.fileDblClick = new EventEmitter();
this.folderSelect = new EventEmitter();
this.folderDblClick = new EventEmitter();
this.newTab = new EventEmitter();
this.loadFiles = new EventEmitter();
this.directoryContents = [];
this.selection = [];
this.selectionChange = new EventEmitter();
this.value = [];
this.valueChange = new EventEmitter();
this.sortedFolders = [];
this.sorters = {
"a-z": (a, b) => a.name > b.name ? 1 : -1,
"z-a": (a, b) => b.name > a.name ? 1 : -1,
"lastmod": (a, b) => b.stats.mtimeMs - a.stats.mtimeMs,
"firstmod": (a, b) => a.stats.mtimeMs - b.stats.mtimeMs,
"size": (a, b) => b.stats.size - a.stats.size,
"type": (a, b) => a.path.split('.').splice(-1, 1)[0] > b.path.split('.').splice(-1, 1)[0] ? 1 : -1
};
this.sortOrder = "a-z";
this.itemsPerRow = 6;
// If the current directory is inside of an archive
this.isArchive = true;
this.userIsDraggingFile = false;
this.draggingOver = false;
this.showLoader = false;
this.hideLoader = false;
this.failedLoad = false;
this.columns = [
{ id: "name", label: "Name" },
{ id: "size", label: "Size" },
{ id: "type", label: "Type" },
{ id: "owner", label: "Owner" },
{ id: "group", label: "Group" },
{ id: "permissions", label: "Permissions" },
{ id: "location", label: "Location" },
{ id: "modified", label: "Modified" },
{ id: "modified--time", label: "Modified - Time" },
{ id: "accessed", label: "Accessed" },
{ id: "created", label: "Created" },
{ id: "recency", label: "Recency" },
{ id: "star", label: "Star" },
{ id: "detailed-type", label: "Detailed Type" },
];
this.cols = [
{ id: "name", label: "Name" },
{ id: "size", label: "Size" },
{ id: "modified", label: "Modified" },
{ id: "star", label: "Star" }
];
this.folderContextMenu = [
{
label: "New Folder",
// shortcutLabel: "Shift+Ctrl+N",
icon: "create_new_folder",
action: (data) => this.onCreateFolder(data)
},
{
label: "Upload file",
// shortcutLabel: "Ctrl+D",
icon: "file_upload",
action: (evt) => this.onUploadFile(evt)
},
"separator",
// {
// isDisabled: (data) => true,
// label: "_P_aste",
// icon: "content_paste",
// action: (evt) => {
// }
// },
{
label: "Select _A_ll",
shortcutLabel: "Ctrl+A",
icon: "select_all",
action: (evt) => {
this.selection = this._sortFilter();
this.selectionText = this.getSelectionText();
this.selectionChange.next(this.selection);
}
},
// "separator",
// {
// label: "P_r_operties",
// icon: "find_in_page",
// action: (evt) => {
// }
// }
];
this.fileContextMenu = [];
this.nameCellFormatter = ((cell, formatterParams, onRendered) => {
// TODO: Sanitize?
const item = cell.getData();
return `
<span style="display: flex; align-items: center">
<img style="height: 24px; margin-right: 6px" src="${item['_icon'].path}"/>
<p style="margin: 0">${this.sanitizer.sanitize(SecurityContext.HTML, item['vanityName'] || item.name)}</p>
</span>
`;
}).bind(this);
this.iconResolver = new IconResolver(this.libConfig.assetPath);
// ctrl + a => select all
keyboard.onKeyCommand({
key: "a",
ctrl: true,
}).subscribe(evt => {
this.selection = this._sortFilter();
this.selectionText = this.getSelectionText();
this.selectionChange.next(this.selection);
});
// ctrl + c => copy file names to clipboard
keyboard.onKeyCommand({
key: "c",
ctrl: true,
}).subscribe(evt => {
});
// ctrl + h => toggle hidden files
keyboard.onKeyCommand({
key: "h",
ctrl: true,
interrupt: true
}).subscribe(evt => {
this.showHiddenFiles = !this.showHiddenFiles;
});
// F2 => Rename selected files
keyboard.onKeyCommand({
key: "f2",
}).subscribe(evt => {
// Rename selected file(s)
});
// Enter => Open selected files
keyboard.onKeyCommand({
key: "Enter",
}).subscribe(evt => {
const files = this.directoryContents.filter(dc => this.selection.find(i => i.name == dc.name));
// this.windowManager.openFiles(files as any);
});
// Delete => delete selected files
keyboard.onKeyCommand({
key: "delete",
}).subscribe(evt => {
const files = this.directoryContents.filter(dc => this.selection.find(i => i.name == dc.name));
});
}
async ngOnInit() {
// this.loadFolder();
}
ngAfterViewInit() {
this.fileContextMenu = [
{
label: "Download",
icon: "download",
action: (file) => this.fileManager.downloadFile(file)
},
{
label: "Open in new Tab",
icon: "open_in_new",
isVisible: (data) => data.kind == "directory",
action: (data) => {
this.fileManager.initTab(data.path + data.name);
}
},
// {
// label: "Open with Application...",
// isVisible: (data) => data.kind == "file",
// shortcutLabel: "Ctrl+D",
// action: (evt) => {
// },
// },
"separator",
// {
// label: "Cut",
// icon: "content_cut",
// isDisabled: data => true,
// action: (evt) => {
// },
// },
// {
// label: "Copy",
// icon: "file_copy",
// isDisabled: data => true,
// childrenResolver: () => new Promise(r => setTimeout(r, 500000))
// },
// {
// label: "Move To...",
// icon: "drive_file_move",
// shortcutLabel: "Ctrl+A",
// action: (evt) => {
// },
// },
// {
// label: "Copy To...",
// icon: "folder_copy",
// shortcutLabel: "Ctrl+A",
// action: (evt) => {
// },
// },
{
label: "Delete",
icon: "delete",
// shortcutLabel: "Del",
isVisible: data => !data.path.includes("#/"), // omit files in compressed dirs
action: (evt) => {
const path = evt.path + evt.name;
const url = this.config.apiSettings.deleteEntryUrlTemplate
? this.config.apiSettings.deleteEntryUrlTemplate(path)
: this.config.apiSettings.deleteEntryUrl;
this.fetch.post(url, { path: evt.path + evt.name })
.then(() => this.loadFolder());
},
},
// {
// label: "Shred file",
// icon: "delete_forever",
// isVisible: data => !data.path.includes("#/"), // omit files in compressed dirs
// action: (evt) => {
// this.fetch.post(`/api/filesystem/delete?wipe=true`, { files: [evt.path + evt.name]})
// .then(() => this.loadFolder())
// },
// },
{
label: "Rename",
icon: "drive_file_rename_outline",
isVisible: data => !data.path.includes("#/"), // omit files in compressed dirs
// shortcutLabel: "F2",
action: (data) => {
this.dialog.open("folder-rename", "@dotglitch/ngx-web-components", {
inputs: { path: data?.path || this.path, name: data?.name || '', config: this.config }
}).then(r => this.loadFolder());
}
},
// Extract Here
// Extract To...
// {
// label: "Extract Here",
// icon: "folder_zip",
// shortcutLabel: "Ctrl+A",
// isDisabled: (data) => !(data.kind == "file" && data.ext != ".zip" && isArchive(data)),
// action: (evt) => {
// // TODO
// },
// },
// {
// label: "Extract to...",
// icon: "folder_zip",
// shortcutLabel: "Ctrl+A",
// isDisabled: (data) => !(data.kind == "file" && data.ext != ".zip" && isArchive(data)),
// action: (evt) => {
// // TODO
// },
// },
// {
// label: "Compress...",
// icon: "folder_zip",
// shortcutLabel: "Ctrl+A",
// isDisabled: (data) => data.kind == "file",
// action: (evt) => {
// // TODO
// },
// },
{
label: "Checksum",
icon: "manage_search",
isDisabled: (data) => data.kind != "file",
children: [
{
label: "MD5",
action: (evt) => this.performChecksum(evt.path + evt.name, "md5"),
},
{
label: "SHA1",
action: (evt) => this.performChecksum(evt.path + evt.name, "sha1"),
},
{
label: "SHA256",
action: (evt) => this.performChecksum(evt.path + evt.name, "sha256"),
},
{
label: "SHA512",
action: (evt) => this.performChecksum(evt.path + evt.name, "sha512"),
},
],
isVisible: (data) => {
return false;
return !this.isArchive || data.kind == "file";
},
},
// {
// label: "Star",
// icon: "star",
// shortcutLabel: "Ctrl+A",
// action: (evt) => {
// },
// },
// "separator",
// {
// label: "P_r_operties",
// icon: "find_in_page",
// action: (evt) => {
// },
// }
];
}
async loadFolder() {
this.showLoader = true;
this.hideLoader = false;
this.failedLoad = false;
const url = this.config.apiSettings.listEntriesUrlTemplate
? this.config.apiSettings.listEntriesUrlTemplate(this.path)
: this.config.apiSettings.listEntriesUrl;
this.fetch.post(url, { path: this.path, showHidden: this.showHiddenFiles }, {}, true)
.then((data) => {
const files = data?.files || [];
const dirs = data?.dirs || [];
const descriptors = files.concat(dirs);
descriptors.forEach(f => {
f['_icon'] = this.iconResolver.resolveIcon(f);
if (f.kind == "file") {
f['_ctime'] = new Date(f.stats?.ctimeMs)?.toLocaleString();
f['_mtime'] = new Date(f.stats?.mtimeMs)?.toLocaleString();
f['_size'] = this.bytesToString(f.stats?.size);
}
});
this.directoryContents = descriptors;
this._sortFilter();
this.resize();
this.loadFiles.next(descriptors);
if (this.sortedFolders.length > 0)
this.flowRows();
setTimeout(() => this.resize(), 250);
setTimeout(() => this.resize(), 500);
setTimeout(() => this.resize(), 1000);
setTimeout(() => this.resize(), 2500);
setTimeout(() => this.resize(), 5000);
})
.catch(e => {
this.failedLoad = true;
this.error = e;
console.error(e);
})
.finally(() => {
this.hideLoader = true;
setTimeout(() => {
this.showLoader = false;
}, 200);
});
}
flowRows() {
let filtered = this._sortFilter();
this.sortedFolders = [];
const num = Math.ceil(filtered.length / this.itemsPerRow);
const iterations = Math.min(num, 100);
for (let row = 0; row < iterations; row++) {
if (!this.sortedFolders[row])
this.sortedFolders[row] = [];
for (let i = row * this.itemsPerRow; i < (row + 1) * this.itemsPerRow && i < filtered.length; i++) {
this.sortedFolders[row].push(filtered[i]);
}
}
}
onSelect(item, evt) {
evt.stopPropagation();
if (this.keyboard.isShiftPressed) {
let start = this.directoryContents.findIndex(i => i.name == this.selection.slice(-1, 1)[0].name);
let end = this.directoryContents.indexOf(item);
if (start == -1)
start = end;
let items = start > end
? this.directoryContents.slice(end, start + 1)
: this.directoryContents.slice(start, end + 1);
this.selection = items;
}
else if (this.keyboard.isCtrlPressed) {
if (!this.selection.includes(item))
this.selection.push(item);
else // Case that we selected the same item twice
this.selection.splice(this.selection.indexOf(item), 1);
}
else
this.selection = [item];
if (this.selection.length == 1) {
if (this.selection[0].kind == "directory")
this.folderSelect.next(this.selection[0]);
else
this.fileSelect.next(this.selection[0]);
}
this.selectionChange.next(this.selection);
this.selectionText = this.getSelectionText();
}
onItemClick(file) {
console.log(file, this);
if (file.kind == "directory") {
this.folderDblClick.next(file);
this.path = file.path + file.name;
}
// else if (file.ext == "zip") {
// this.fileDblClick.next(file);
// this.path = file.path + file.name;
// }
else {
this.fileDblClick.next(file);
this.fileSelect.next(file);
}
}
onToggle(item, state) {
item['_value'] = state.checked;
// TODO: What causes this to be null when initialized with an array?
if (!this.value) {
this.value = [];
}
if (state.checked) {
this.value.push(item);
}
else {
const i = this.value.findIndex(v => v == item);
if (i >= 0)
this.value.splice(i, 1);
}
this.valueChange.next(this.value);
}
async clearSelection() {
this.value = [];
this.valueChange.next(this.value);
this.tabulator?.table?.getRows().forEach(r => r.getElement().classList.remove('selected'));
}
_sortFilter() {
return this.directoryContents = this.directoryContents?.filter(d => d.kind == 'directory')
.concat(this.directoryContents?.filter(d => d.kind == 'file')
.sort(this.sorters[this.sortOrder]));
}
getSelectionText() {
const dirCount = this.selection.filter(s => s.kind == "directory").length;
const fileCount = this.selection.filter(s => s.kind == "file").length;
if (dirCount + fileCount == 0)
return "";
const totalSize = this.directoryContents
.filter(d => d.kind == "file")
.filter(d => this.selection?.find(i => i.name == d.name))
.map(d => d['stats'].size).reduce((a, b) => a + b, 0);
if (dirCount + fileCount == 1)
return `"${this.selection[0].name}" selected (${this.bytesToString(totalSize)})`;
if (dirCount > 0 && fileCount == 0)
return `"${dirCount}" folders selected`;
if (fileCount > 0 && dirCount == 0)
return `${fileCount} items selected (${this.bytesToString(totalSize)})`;
return `${dirCount} folder${dirCount == 1 ? "" : "s"} selected, ${fileCount} other item${fileCount == 1 ? "" : "s"} selected (${this.bytesToString(totalSize)})`;
}
bytesToString(bytes, decimals = 2) {
if (!+bytes)
return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
}
resize() {
if (!this.filesRef) {
setTimeout(() => this.resize(), 25);
return;
}
;
const bounds = this.filesRef.nativeElement.getBoundingClientRect();
const newColCount = Math.floor(bounds.width / itemWidth);
if (newColCount != this.itemsPerRow) {
this.itemsPerRow = Math.floor(bounds.width / itemWidth);
if (this.itemsPerRow > 100)
this.itemsPerRow = 1;
this.flowRows();
}
if (this.sortedFolders?.length == 0)
this.flowRows();
}
onDragStart(evt, item) {
const target = `${window.origin}/api/filesystem/download?dir=${item.path}&file=${item.name}`;
evt.dataTransfer.clearData();
// evt.dataTransfer.setData('text/uri-list', target);
// evt.dataTransfer.setData('DownloadURL', `text/uri-list:${target}`);
evt.dataTransfer.setData('text/plain', item.name);
}
onDrop(ev) {
ev.preventDefault();
if (ev.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
[...ev.dataTransfer.items].forEach((item, i) => {
// If dropped items aren't files, reject them
if (item.kind === "file") {
const file = item.getAsFile();
console.log(`… file[${i}].name = ${file.name}`);
}
});
}
else {
// Use DataTransfer interface to access the file(s)
[...ev.dataTransfer.files].forEach((file, i) => {
console.log(`… file[${i}].name = ${file.name}`);
});
}
}
onRowCtx([event, row] = []) {
openMenu(this.matDialog, this.fileContextMenu, row.getData(), event);
}
onRowClick(row) {
const data = row.getData();
// $event.data['_value'] = $event.data['_value'] == true ? false : true
// console.log(event, row, data, this.value);
const rowEl = row.getElement();
let state = rowEl.classList.contains('selected');
data['_value'] = !state;
if (!this.value) {
this.value = [];
}
if (!state) {
rowEl.classList.add('selected');
this.value.push(data);
}
else {
rowEl.classList.remove('selected');
const i = this.value.findIndex(v => v == data);
if (i >= 0)
this.value.splice(i, 1);
}
this.valueChange.next(this.value);
}
sort() {
this._sortFilter();
}
onUploadFile(evt) {
uploadFile(this.fetch, this.config, this._path, evt ? (evt.path + evt.name) : null, this.fileManager.contextTags)
.then(res => {
// Refresh folder contents
this.loadFolder();
});
}
onCreateFolder(data) {
this.dialog.open("folder-rename", "@dotglitch/ngx-web-components", {
inputs: { path: data?.path || this.path, name: data?.name || '', config: this.config }
}).then(r => this.loadFolder());
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FileGridComponent, deps: [{ token: i1.Fetch }, { token: i1.KeyboardService }, { token: i1.DialogService }, { token: i2.MatDialog }, { token: i3.FilemanagerComponent }, { token: i0.ChangeDetectorRef }, { token: i4.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: FileGridComponent, isStandalone: true, selector: "app-file-grid", inputs: { path: "path", config: "config", showHiddenFiles: "showHiddenFiles", viewMode: "viewMode", gridSize: "gridSize", tab: "tab", selection: "selection", value: "value", sortOrder: "sortOrder" }, outputs: { pathChange: "pathChange", fileSelect: "fileSelect", fileDblClick: "fileDblClick", folderSelect: "folderSelect", folderDblClick: "folderDblClick", newTab: "newTab", loadFiles: "loadFiles", selectionChange: "selectionChange", valueChange: "valueChange" }, viewQueries: [{ propertyName: "filesRef", first: true, predicate: ["fileViewport"], descendants: true }, { propertyName: "tabulator", first: true, predicate: TabulatorComponent, descendants: true }, { propertyName: "renameTemplate", first: true, predicate: ["renameTemplate"], descendants: true, read: TemplateRef }], ngImport: i0, template: "@if (showLoader) {\n<mat-progress-bar [class.hide]=\"hideLoader\" mode=\"query\" />\n}\n\n<div style=\"display: contents\"\n [style.--filemanager-fileicon-backdrop]=\"'url(' + iconResolver.path + '/pop/generic.svg)'\"\n (dragstart)=\"userIsDraggingFile = true\" (dragend)=\"userIsDraggingFile = false\" (dragover)=\"draggingOver = true\"\n (dragleave)=\"draggingOver = false\" (ondrop)=\"onDrop($event)\">\n\n @if (failedLoad) {\n <div style=\"display: flex; align-items: center; justify-content: center; height: 100%\">\n <div\n style=\"max-width: 400px; display: flex; flex-direction: column; align-items: center; justify-content: center;\">\n <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->\n <svg fill=\"var(--text-color)\" width=\"200px\" viewBox=\"0 0 600.525 600.525\">\n <path\n d=\"M57.375,138.656L43.031,95.146c-23.428,8.128-40.162,29.166-42.553,54.028l45.9,3.825C46.856,146.306,51.16,140.568,57.375,138.656z\" />\n <rect y=\"288.309\" width=\"45.9\" height=\"45.901\" />\n <path\n d=\"M554.625,446.091c0,3.346-0.956,6.215-2.868,9.084l38.25,25.34c6.693-10.039,10.04-21.992,10.04-34.424V423.14h-45.899v22.951H554.625z\" />\n <rect x=\"456.609\" y=\"146.306\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"485.297\" y=\"462.825\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"393.497\" y=\"462.825\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"364.81\" y=\"146.306\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"118.097\" y=\"462.825\" width=\"45.9\" height=\"45.9\" />\n <rect y=\"380.108\" width=\"45.9\" height=\"45.9\" />\n <rect y=\"196.509\" width=\"45.9\" height=\"45.9\" />\n <path\n d=\"M330.385,143.437c-2.391-1.434-3.825-2.391-4.304-2.869l-28.209-29.166c-1.913-1.913-4.303-3.825-6.694-5.737l-27.253,36.815c0.478,0.478,0.956,0.956,0.956,0.956s37.772,34.425,44.465,41.119L330.385,143.437z\" />\n <rect x=\"554.625\" y=\"239.541\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"301.697\" y=\"462.825\" width=\"45.899\" height=\"45.9\" />\n <path\n d=\"M559.885,146.306h-10.997v47.812h5.737h45.9v-7.172C600.525,164.475,582.356,146.306,559.885,146.306z\" />\n <rect x=\"187.425\" y=\"91.8\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"554.625\" y=\"331.341\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"95.625\" y=\"91.8\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"209.897\" y=\"462.825\" width=\"45.9\" height=\"45.9\" />\n <path\n d=\"M49.247,456.132l-36.337,27.73c11.953,15.777,30.122,24.863,49.725,24.863h9.562v-45.9h-9.562C57.375,462.825,52.594,460.435,49.247,456.132z\" />\n </svg>\n\n <h3>Sorry about that.</h3>\n <p>\n Our servers aren't doing their thing right now.\n You can try again later or contact an administrator about this.\n </p>\n <hr style=\"width: 100%; opacity: .5;\" />\n <p>\n Error:\n <span style=\"color: var(--mat-tab-header-active-focus-label-text-color)\">{{error.status}}</span>\n <br />\n @if (error.error?.message) {\n Message: {{error.error?.message}}\n }\n </p>\n </div>\n </div>\n }\n @else {\n <!-- <ng-container *ngIf=\"draggingOver\"></ng-container> -->\n <!-- Grid mode -->\n @if (viewMode == 'grid') {\n <ng-scrollbar class=\"grid content-area {{gridSize}} {{config.imageSize || 'normal'}}\"\n [class.selectionMode]=\"config.mode == 'focusFiles'\" [class.showDropArea]=\"draggingOver\"\n style=\"height: 100%; width: 100%\" track=\"vertical\" pointerEventsMethod=\"scrollbar\"\n [ngx-contextmenu]=\"folderContextMenu\">\n <div class=\"resize-observer\" #fileViewport></div>\n <cdk-virtual-scroll-viewport itemSize=\"150\" scrollViewport (click)=\"selection = []; selectionText = ''\">\n <div class=\"row\" *cdkVirtualFor=\"let row of sortedFolders\">\n @for (item of row; track item) {\n <div class=\"file\" [class.selected]=\"selection.includes(item)\"\n [class.generic]=\"item['_icon'].needsBackdrop\" [ngx-contextmenu]=\"fileContextMenu\"\n [ngx-menu-context]=\"item\">\n @if (config.mode == 'focusFiles' && item.kind == 'file') {\n <mat-checkbox #checkbox [checked]=\"item['_value']\" (change)=\"onToggle(item, $event)\" />\n }\n <div style=\"display: contents\" (click)=\"onSelect(item, $event)\" (dblclick)=\"onItemClick(item)\"\n (dragstart)=\"onDragStart($event, item)\">\n <img [src]=\"item['_icon'].path\" />\n <p>{{item['vanityName'] || item.name}}</p>\n </div>\n </div>\n }\n </div>\n </cdk-virtual-scroll-viewport>\n </ng-scrollbar>\n }\n\n <!-- List mode -->\n @if (viewMode == 'list') {\n <div class=\"content-area\" style=\"width: 100%; height: 100%\" [class.showDropArea]=\"draggingOver\"\n [ngx-contextmenu]=\"folderContextMenu\">\n <ngx-tabulator [dataSource]=\"directoryContents\" (rowClick)=\"onRowClick($event?.[1])\"\n (rowDblClick)=\"onItemClick($any($event?.[1]?.getData()))\" (rowContext)=\"onRowCtx($event)\">\n <ngx-tabulator-column field=\"name\" title=\"Name\" [formatter]=\"nameCellFormatter\" />\n <ngx-tabulator-column field=\"_size\" title=\"Size\" />\n <ngx-tabulator-column field=\"_ctime\" title=\"Created\" />\n <ngx-tabulator-column field=\"_mtime\" title=\"Modified\" />\n </ngx-tabulator>\n </div>\n }\n }\n</div>\n\n@if (selectionText?.trim()?.length > 0) {\n<div class=\"select-hint\">\n {{selectionText}}\n</div>\n}\n\n<div class=\"controls\">\n <button mat-flat-button class=\"upload-button\" (click)=\"onCreateFolder()\">\n <mat-icon>create_new_folder</mat-icon>\n New Folder\n </button>\n <button mat-flat-button class=\"upload-button\" (click)=\"onUploadFile()\">\n <mat-icon>upload_file</mat-icon>\n Upload\n </button>\n</div>", styles: [":host{display:block;height:100%;width:100%;overflow:hidden}.resize-observer{position:absolute;inset:0}.grid .row{flex:1;display:grid;grid-template-columns:repeat(auto-fill,80px);justify-content:space-between;grid-gap:20px;grid-auto-rows:min-content;padding:10px;margin-right:10px}.grid .file{width:80px;z-index:1;position:relative;transition:opacity 50ms ease-in-out;display:flex;flex-direction:column;align-items:center;text-align:center}.grid .file.selected p{background-color:#8ad9d9;color:#000}.grid .file.generic:before{content:\" \";position:absolute;background:var(--filemanager-fileicon-backdrop);background-repeat:no-repeat;width:100%;height:100%;z-index:-1;left:0}.grid .file.generic img{height:44px;width:44px;margin-top:28px;margin-bottom:8px}.grid .file img{-webkit-user-select:none;user-select:none}.grid .file p{height:50px;width:100%;margin:0;padding:2px;font-size:14px;line-height:16px;border-radius:4px;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;text-overflow:ellipsis;overflow-wrap:break-word;transition:background-color 50ms ease-in-out,color 50ms ease-in-out}.grid .file mat-checkbox{position:absolute;left:-32px;top:-16px}:host ::ng-deep .small .file{width:46px}:host ::ng-deep .grid.selectionMode .row{padding-left:30px}:host ::ng-deep .grid.selectionMode .cdk-virtual-scroll-content-wrapper{padding-top:10px}:host ::ng-deep .tabulator .tabulator-row .tabulator-cell:not(:first-of-type){padding-top:9px}.list .row{flex:1;display:flex;align-items:center;padding:5px 10px;height:42px}.list .row p{margin:0}.list .row:hover{background-color:var(--filemanager-row-hover-background-color, #343434)}.list .row.selected p{background-color:#8ad9d9;color:#000;border-radius:5px;padding:0 4px}.list .row.odd{background-color:var(--filemanager-row-alt-background-color, #323232)}.controls{position:absolute;bottom:12px;right:12px;display:flex;gap:12px}.select-hint{position:absolute;font-size:14px;bottom:0;right:0;background-color:#000a;padding:2px 10px;border:1px solid black;border-top-right-radius:5px;border-bottom:none;border-right:none;z-index:9999}.select-hint:hover{display:none}.content-area{border:4px dashed rgba(0,0,0,0)}.showDropArea{border:4px dashed var(--theme-primary, rgba(128, 128, 128, .8));transition:border-color .25s ease}:host ::ng-deep ng-scrollbar.ng-scrollbar{box-sizing:border-box!important;--scrollbar-thumb-color: #666;min-height:200px;min-width:200px}mat-progress-bar{--mdc-linear-progress-track-height: 2px;position:absolute;top:0;left:0;width:100%;animation:dropIn .2s ease;transition:position .2s ease;z-index:100}mat-progress-bar.hide{top:-2px}@keyframes dropIn{0%{top:-2px}to{top:0}}\n"], dependencies: [{ kind: "ngmodule", type: MatTabsModule }, { kind: "ngmodule", type: NgScrollbarModule }, { kind: "component", type: i5.NgScrollbar, selector: "ng-scrollbar", inputs: ["disabled", "sensorDisabled", "pointerEventsDisabled", "viewportPropagateMouseMove", "autoHeightDisabled", "autoWidthDisabled", "viewClass", "trackClass", "thumbClass", "minThumbSize", "trackClickScrollDuration", "pointerEventsMethod", "track", "visibility", "appearance", "position", "sensorDebounce", "scrollAuditTime"], outputs: ["updated"], exportAs: ["ngScrollbar"] }, { kind: "directive", type: i5.ScrollViewport, selector: "[scrollViewport]" }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i6.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i7.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i9.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "ngmodule", type: TabulatorModule }, { kind: "component", type: i10.TabulatorComponent, selector: "ngx-tabulator", inputs: ["dataSource", "options", "height", "maxHeight", "minHeight", "renderVertical", "renderHorizontal", "rowHeight", "renderVerticalBuffer", "placeholder", "placeholderHeaderFilter", "footerElement", "keybindings", "reactiveData", "autoResize", "invalidOptionWarnings", "validationMode", "textDirection", "rowHeader", "editorEmptyValue", "editorEmptyValueFunc", "rowContextMenu", "rowClickMenu", "rowDblClickMenu", "groupClickMenu", "groupDblClickMenu", "groupContextMenu", "popupContainer", "groupClickPopup", "groupContextPopup", "groupDblPopup", "groupDblClickPopup", "rowClickPopup", "rowContextPopup", "rowDblClickPopup", "history", "locale", "langs", "downloadEncoder", "downloadConfig", "downloadRowRange", "downloadDataFormatter", "downloadReady", "autoColumns", "autoColumnsDefinitions", "layout", "layoutColumnsOnNewData", "responsiveLayout", "responsiveLayoutCollapseStartOpen", "responsiveLayoutCollapseUseFormatters", "responsiveLayoutCollapseFormatter", "movableColumns", "columnHeaderVertAlign", "scrollToColumnPosition", "scrollToColumnIfVisible", "columnCalcs", "nestedFieldSeparator", "columnHeaderSortMulti", "headerVisible", "headerSort", "headerSortElement", "columnDefaults", "resizableColumnFit", "rowFormatter", "rowFormatterPrint", "rowFormatterHtmlOutput", "rowFormatterClipboard", "addRowPos", "selectable", "selectableRows", "selectableRange", "selectableRangeColumns", "selectableRangeRows", "selectableRangeClearCells", "selectableRangeClearCellsValue", "selectableRangeMode", "selectableRowsRangeMode", "selectableRollingSelection", "selectableRowsRollingSelection", "selectablePersistence", "selectableRowsPersistence", "selectableCheck", "movableRows", "movableRowsConnectedTables", "movableRowsSender", "movableRowsReceiver", "movableRowsConnectedElements", "resizableRows", "resizableRowGuide", "resizableColumnGuide", "scrollToRowPosition", "scrollToRowIfVisible", "tabEndNewRow", "frozenRowsField", "frozenRows", "editTriggerEvent", "index", "data", "importFormat", "importReader", "autoTables", "ajaxURL", "ajaxParams", "ajaxConfig", "ajaxContentType", "ajaxURLGenerator", "ajaxRequestFunc", "ajaxFiltering", "ajaxSorting", "progressiveLoad", "progressiveLoadDelay", "progressiveLoadScrollMargin", "ajaxLoader", "ajaxLoaderLoading", "ajaxLoaderError", "ajaxRequesting", "ajaxResponse", "dataLoader", "dataLoaderLoading", "dataLoaderError", "dataLoaderErrorTimeout", "sortMode", "filterMode", "initialSort", "sortOrderReverse", "headerSortClickElement", "initialFilter", "initialHeaderFilter", "headerFilterLiveFilterDelay", "groupBy", "groupValues", "groupHeader", "groupHeaderPrint", "groupStartOpen", "groupToggleElement", "groupClosedShowCalcs", "groupUpdateOnCellEdit", "pagination", "paginationMode", "paginationSize", "paginationSizeSelector", "paginationElement", "dataReceiveParams", "dataSendParams", "paginationAddRow", "paginationCounter", "paginationCounterElement", "paginationButtonCount", "paginationInitialPage", "persistenceID", "persistenceMode", "persistentLayout", "persistentSort", "persistentFilter", "persistence", "persistenceWriterFunc", "persistenceReaderFunc", "clipboard", "clipboardCopyRowRange", "clipboardCopyFormatter", "clipboardCopyHeader", "clipboardPasteParser", "clipboardPasteAction", "clipboardCopyStyled", "clipboardCopyConfig", "groupHeaderClipboard", "groupHeaderHtmlOutput", "dataTree", "dataTreeElementColumn", "dataTreeBranchElement", "dataTreeChildIndent", "dataTreeChildField", "dataTreeCollapseElement", "dataTreeExpandElement", "dataTreeStartExpanded", "dataTreeSelectPropagate", "dataTreeFilter", "dataTreeSort", "dataTreeChildColumnCalcs", "invalidOptionWarning", "debugInvalidOptions", "debugInitialization", "debugEventsExternal", "debugEventsInternal", "debugInvalidComponentFuncs", "debugDeprecation", "htmlOutputConfig", "printAsHtml", "printConfig", "printStyled", "printRowRange", "printHeader", "printFooter", "printFormatter", "groupHeaderDownload", "spreadsheet", "spreadsheetRows", "spreadsheetColumns", "spreadsheetColumnDefinition", "spreadsheetSheets", "spreadsheetSheetTabs", "spreadsheetOutputFull"], outputs: ["validationFailed", "scrollHorizontal", "scrollVertical", "rowAdded", "rowDeleted", "rowMoving", "rowMoved", "rowMoveCancelled", "rowUpdated", "rowSelectionChanged", "rowSelected", "rowDeselected", "rowResized", "rowClick", "rowDblClick", "rowContext", "rowTap", "rowDblTap", "rowTapHold", "rowMouseEnter", "rowMouseLeave", "rowMouseOver", "rowMouseDown", "rowMouseUp", "rowMouseOut", "rowMouseMove", "htmlImporting", "htmlImported", "ajaxError", "clipboardCopied", "clipboardPasted", "clipboardPasteError", "downloadComplete", "dataTreeRowExpanded", "dataTreeRowCollapsed", "pageLoaded", "pageSizeChanged", "headerClick", "headerDblClick", "headerContext", "headerTap", "headerDblTap", "headerTapHold", "headerMouseUp", "headerMouseDown", "groupClick", "groupDblClick", "groupContext", "groupTap", "groupDblTap", "groupTapHold", "groupMouseDown", "groupMouseUp", "tableBuilding", "tableBuilt", "tableDestroyed", "dataChanged", "dataLoading", "dataLoaded", "dataLoadError", "dataProcessing", "dataProcessed", "dataFiltering", "dataFiltered", "dataSorting", "dataSorted", "movableRowsSendingStart", "movableRowsSent", "movableRowsSentFailed", "movableRowsSendingStop", "movableRowsReceivingStart", "movableRowsReceived", "movableRowsReceivedFailed", "movableRowsReceivingStop", "movableRowsElementDrop", "dataGrouping", "dataGrouped", "groupVisibilityChanged", "localized", "renderStarted", "renderComplete", "columnMoved", "columnResized", "columnTitleChanged", "columnVisibilityChanged", "historyUndo", "historyRedo", "cellEditing", "cellEdited", "cellEditCancelled", "cellClick", "cellDblClick", "cellContext", "cellMouseDown", "cellMouseUp", "cellTap", "cellDblTap", "cellTapHold", "cellMouseEnter", "cellMouseLeave", "cellMouseOver", "cellMouseOut", "cellMouseMove", "popupOpen", "popupClosed", "menuClosed", "menuOpened", "TooltipClosed", "TooltipOpened", "rangeAdded", "rangeChanged", "rangeRemoved", "rowHeightChange", "rowResizing", "columnWidth", "columnResizing", "sheetAdded", "sheetLoaded", "sheetUpdated", "sheetRemoved", "columnsLoaded", "importChoose", "importImporting", "importError", "importImported"] }, { kind: "directive", type: i10.ColumnDirective, selector: "ngx-tabulator>ngx-tabulator-column", inputs: ["accessorClipboard", "accessor", "accessorClipboardParams", "accessorDownload", "accessorDownloadParams", "accessorHtmlOutput", "accessorHtmlOutputParams", "accessorParams", "accessorPrint", "accessorPrintParams", "bottomCalc", "bottomCalcFormatter", "bottomCalcFormatterParams", "bottomCalcParams", "cellPopup", "clickMenu", "clipboard", "columns", "contextMenu", "cssClass", "dblClickMenu", "dblClickPopup", "download", "editable", "editableTitle", "editor", "editorEmptyValue", "editorEmptyValueFunc", "editorParams", "field", "formatter", "formatterClipboard", "formatterClipboardParams", "formatterHtmlOutput", "formatterHtmlOutputParams", "formatterParams", "formatterPrint", "formatterPrintParams", "frozen", "headerContextMenu", "headerDblClickMenu", "headerDblClickPopup", "headerFilter", "headerFilterEmptyCheck", "headerFilterFunc", "headerFilterFuncParams", "headerFilterLiveFilter", "headerFilterParams", "headerFilterPlaceholder", "headerHozAlign", "headerMenu", "headerMenuIcon", "headerSort", "headerSortStartingDir", "headerSortTristate", "headerTooltip", "headerVertical", "headerWordWrap", "hideInHtml", "hozAlign", "htmlOutput", "maxWidth", "minWidth", "mutator", "mutatorClipboard", "mutatorClipboardParams", "mutatorData", "mutatorDataParams", "mutatorEdit", "mutatorEditParams", "mutatorParams", "print", "resizable", "responsive", "rowHandle", "sorter", "sorterParams", "title", "titleClipboard", "titleDownload", "titleFormatter", "titleFormatterParams", "titleHtmlOutput", "titlePrint", "tooltip", "topCalc", "topCalcFormatter", "topCalcFormatterParams", "topCalcParams", "validator", "variableHeight", "vertAlign", "visible", "width", "widthGrow", "widthShrink"], outputs: ["headerClick", "headerDblClick", "headerMouseDown", "headerMouseUp", "headerContext", "headerTap", "headerDblTap", "headerTapHold", "cellClick", "cellDblClick", "cellContext", "cellTap", "cellDblTap", "cellTapHold", "cellMouseEnter", "cellMouseLeave", "cellMouseOver", "cellMouseOut", "cellMouseMove", "cellEditing", "cellEdited", "cellEditCancelled", "cellMouseDown", "cellMouseUp"] }, { kind: "directive", type: MenuDirective, selector: "[ngx-contextmenu],[ngx-menu]", inputs: ["ngx-menu-context", "ngx-contextmenu", "ngx-menu", "ngx-menu-config"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i11.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i11.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i11.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FileGridComponent, decorators: [{
type: Component,
args: [{ selector: 'app-file-grid', imports: [
NgIf,
NgForOf,
DatePipe,
MatTabsModule,
NgScrollbarModule,
MatInputModule,
MatCheckboxModule,
MatProgressBarModule,
MatIconModule,
MatButtonModule,
TabulatorModule,
MenuDirective,
ScrollingModule
], standalone: true, template: "@if (showLoader) {\n<mat-progress-bar [class.hide]=\"hideLoader\" mode=\"query\" />\n}\n\n<div style=\"display: contents\"\n [style.--filemanager-fileicon-backdrop]=\"'url(' + iconResolver.path + '/pop/generic.svg)'\"\n (dragstart)=\"userIsDraggingFile = true\" (dragend)=\"userIsDraggingFile = false\" (dragover)=\"draggingOver = true\"\n (dragleave)=\"draggingOver = false\" (ondrop)=\"onDrop($event)\">\n\n @if (failedLoad) {\n <div style=\"display: flex; align-items: center; justify-content: center; height: 100%\">\n <div\n style=\"max-width: 400px; display: flex; flex-direction: column; align-items: center; justify-content: center;\">\n <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->\n <svg fill=\"var(--text-color)\" width=\"200px\" viewBox=\"0 0 600.525 600.525\">\n <path\n d=\"M57.375,138.656L43.031,95.146c-23.428,8.128-40.162,29.166-42.553,54.028l45.9,3.825C46.856,146.306,51.16,140.568,57.375,138.656z\" />\n <rect y=\"288.309\" width=\"45.9\" height=\"45.901\" />\n <path\n d=\"M554.625,446.091c0,3.346-0.956,6.215-2.868,9.084l38.25,25.34c6.693-10.039,10.04-21.992,10.04-34.424V423.14h-45.899v22.951H554.625z\" />\n <rect x=\"456.609\" y=\"146.306\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"485.297\" y=\"462.825\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"393.497\" y=\"462.825\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"364.81\" y=\"146.306\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"118.097\" y=\"462.825\" width=\"45.9\" height=\"45.9\" />\n <rect y=\"380.108\" width=\"45.9\" height=\"45.9\" />\n <rect y=\"196.509\" width=\"45.9\" height=\"45.9\" />\n <path\n d=\"M330.385,143.437c-2.391-1.434-3.825-2.391-4.304-2.869l-28.209-29.166c-1.913-1.913-4.303-3.825-6.694-5.737l-27.253,36.815c0.478,0.478,0.956,0.956,0.956,0.956s37.772,34.425,44.465,41.119L330.385,143.437z\" />\n <rect x=\"554.625\" y=\"239.541\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"301.697\" y=\"462.825\" width=\"45.899\" height=\"45.9\" />\n <path\n d=\"M559.885,146.306h-10.997v47.812h5.737h45.9v-7.172C600.525,164.475,582.356,146.306,559.885,146.306z\" />\n <rect x=\"187.425\" y=\"91.8\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"554.625\" y=\"331.341\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"95.625\" y=\"91.8\" width=\"45.9\" height=\"45.9\" />\n <rect x=\"209.897\" y=\"462.825\" width=\"45.9\" height=\"45.9\" />\n <path\n d=\"M49.247,456.132l-36.337,27.73c11.953,15.777,30.122,24.863,49.725,24.863h9.562v-45.9h-9.562C57.375,462.825,52.594,460.435,49.247,456.132z\" />\n </svg>\n\n <h3>Sorry about that.</h3>\n <p>\n Our servers aren't doing their thing right now.\n You can try again later or contact an administrator about this.\n </p>\n <hr style=\"width: 100%; opacity: .5;\" />\n <p>\n Error:\n <span style=\"color: var(--mat-tab-header-active-focus-label-text-color)\">{{error.status}}</span>\n <br />\n @if (error.error?.message) {\n