390 lines
No EOL
9 KiB
TypeScript
390 lines
No EOL
9 KiB
TypeScript
import { APPS } from "../../../config/apps.config";
|
|
import AppsManager from "../../apps/appsManager";
|
|
import { removeFromArray } from "../../_utils/array.utils";
|
|
import WindowsManager from "../../windows/windowsManager";
|
|
import { VirtualFileLink } from "../file/virtualFileLink";
|
|
import { VirtualBase, VirtualBaseJson } from "../virtualBase";
|
|
import { VirtualFile, VirtualFileJson } from "../file/virtualFile";
|
|
import { VirtualFolderLink } from ".";
|
|
|
|
export interface VirtualFolderJson extends VirtualBaseJson {
|
|
fls?: VirtualFileJson[];
|
|
fds?: VirtualFolderJson[];
|
|
}
|
|
|
|
/**
|
|
* A virtual folder that can contains files and sub-folders
|
|
*/
|
|
export class VirtualFolder extends VirtualBase {
|
|
subFolders: (VirtualFolder | VirtualFolderLink)[];
|
|
files: (VirtualFile | VirtualFileLink)[];
|
|
type: number | undefined;
|
|
|
|
static TYPE = {
|
|
GENERAL: 0,
|
|
MEDIA: 1,
|
|
};
|
|
|
|
constructor(name: string, type?: number) {
|
|
super(name);
|
|
this.subFolders = [];
|
|
this.files = [];
|
|
this.type = type ?? VirtualFolder.TYPE.GENERAL;
|
|
}
|
|
|
|
setAlias(alias: string) {
|
|
return super.setAlias(alias);
|
|
}
|
|
|
|
/**
|
|
* Returns true if this folder contains a file matching a name and extension
|
|
*/
|
|
hasFile(name: string, extension?: string) {
|
|
return this.findFile(name, extension) !== null;
|
|
}
|
|
|
|
/**
|
|
* Returns true if this folder contains a folder matching a name
|
|
*/
|
|
hasFolder(name: string) {
|
|
return this.findSubFolder(name) !== null;
|
|
}
|
|
|
|
/**
|
|
* Finds and returns a file inside this folder matching a name and extension
|
|
*/
|
|
findFile(name: string, extension?: string): VirtualFile | VirtualFileLink {
|
|
let resultFile: VirtualFile | VirtualFileLink = null;
|
|
|
|
this.files.forEach((file) => {
|
|
const matchingName = (file.name === name || (file.alias && file.alias === name));
|
|
const matchingExtension = (extension == null || file.extension === extension);
|
|
if (matchingName && matchingExtension) {
|
|
return resultFile = file;
|
|
}
|
|
});
|
|
|
|
return resultFile;
|
|
}
|
|
|
|
/**
|
|
* Finds and returns a folder inside this folder matching a name
|
|
*/
|
|
findSubFolder(name: string): VirtualFolder | VirtualFolderLink {
|
|
let resultFolder: VirtualFolder | VirtualFolderLink = null;
|
|
|
|
this.subFolders.forEach((folder) => {
|
|
if (folder.name === name || (folder.alias && folder.alias === name)) {
|
|
return resultFolder = folder;
|
|
}
|
|
});
|
|
|
|
return resultFolder;
|
|
}
|
|
|
|
/**
|
|
* Creates a file with a name and extension
|
|
*/
|
|
createFile(name: string, extension: string, callback?: (newFile: VirtualFile | VirtualFileLink) => void) {
|
|
if (!this.canBeEdited)
|
|
return this;
|
|
|
|
let newFile = this.findFile(name, extension);
|
|
if (newFile == null) {
|
|
newFile = new VirtualFile(name, extension);
|
|
this.files.push(newFile);
|
|
newFile.parent = this;
|
|
}
|
|
callback?.(newFile);
|
|
|
|
newFile.confirmChanges();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates files based on an array of objects with file names and extensions
|
|
*/
|
|
createFiles(files: { name: string; extension: string; }[]) {
|
|
if (!this.canBeEdited)
|
|
return this;
|
|
|
|
files.forEach(({ name, extension }) => {
|
|
this.createFile(name, extension);
|
|
});
|
|
|
|
this.confirmChanges();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a file link with a name
|
|
*/
|
|
createFileLink(name: string, callback?: (newFileLink: VirtualFileLink | VirtualFile) => void) {
|
|
if (!this.canBeEdited)
|
|
return this;
|
|
|
|
let newFile = this.findFile(name);
|
|
if (newFile == null) {
|
|
newFile = new VirtualFileLink(name);
|
|
this.files.push(newFile);
|
|
newFile.parent = this;
|
|
}
|
|
callback?.(newFile);
|
|
|
|
newFile.confirmChanges();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates files based on an array of objects with file names and extensions
|
|
*/
|
|
createFileLinks(files: { name: string; }[]): VirtualFolder {
|
|
if (!this.canBeEdited)
|
|
return;
|
|
|
|
files.forEach(({ name }) => {
|
|
this.createFileLink(name);
|
|
});
|
|
|
|
this.confirmChanges();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a folder with a name
|
|
*/
|
|
createFolder(name: string, callback?: (newFolder: VirtualFolder) => void): VirtualFolder {
|
|
if (!this.canBeEdited)
|
|
return;
|
|
|
|
let newFolder = this.findSubFolder(name);
|
|
if (newFolder == null) {
|
|
newFolder = new VirtualFolder(name);
|
|
this.subFolders.push(newFolder);
|
|
newFolder.parent = this;
|
|
}
|
|
callback?.(newFolder);
|
|
|
|
newFolder.confirmChanges();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates folders based on an array of folder names
|
|
*/
|
|
createFolders(names: string[]): VirtualFolder {
|
|
if (!this.canBeEdited)
|
|
return;
|
|
|
|
names.forEach((name) => {
|
|
this.createFolder(name);
|
|
});
|
|
|
|
this.confirmChanges();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a folder link with a name
|
|
*/
|
|
createFolderLink(name: string, callback?: (newFolderLink: VirtualFolderLink | VirtualFolder) => void) {
|
|
if (!this.canBeEdited)
|
|
return this;
|
|
|
|
let newFolder = this.findSubFolder(name);
|
|
if (newFolder == null) {
|
|
newFolder = new VirtualFolderLink(name);
|
|
this.subFolders.push(newFolder);
|
|
newFolder.parent = this;
|
|
}
|
|
callback?.(newFolder);
|
|
|
|
newFolder.confirmChanges();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates folder links based on an array of folder names
|
|
*/
|
|
createFolderLinks(names: string[]): VirtualFolder {
|
|
if (!this.canBeEdited)
|
|
return;
|
|
|
|
names.forEach((name) => {
|
|
this.createFolder(name);
|
|
});
|
|
|
|
this.confirmChanges();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Removes a file or folder from this folder
|
|
*/
|
|
remove(child: VirtualFile | VirtualFileLink | VirtualFolder | VirtualFolderLink) {
|
|
if (!this.canBeEdited)
|
|
return;
|
|
|
|
child.parent = null;
|
|
|
|
if (child.isFile()) {
|
|
removeFromArray(child, this.files);
|
|
} else if (child.isFolder()) {
|
|
removeFromArray(child, this.subFolders);
|
|
}
|
|
|
|
child.confirmChanges();
|
|
}
|
|
|
|
/**
|
|
* Returns the file or folder at a relative path or null if it doesn't exist
|
|
*/
|
|
navigate(relativePath: string): VirtualFile | VirtualFolder | null {
|
|
const segments = relativePath.split("/");
|
|
let currentDirectory: VirtualFile | VirtualFolder = this as VirtualFolder;
|
|
|
|
const getDirectory = (path: string, isStart: boolean) => {
|
|
if (isStart && path === "") {
|
|
return this.getRoot();
|
|
} else if (isStart && Object.keys(this.getRoot().shortcuts).includes(path)) {
|
|
return this.getRoot().shortcuts[path];
|
|
} else if (path === ".") {
|
|
return this;
|
|
} else if (path === "..") {
|
|
return currentDirectory?.parent;
|
|
} else {
|
|
return (currentDirectory as VirtualFolder)?.findSubFolder(path);
|
|
}
|
|
};
|
|
|
|
if (segments.length === 1) {
|
|
const directory = getDirectory(segments[0], true);
|
|
if (directory !== null)
|
|
return directory;
|
|
}
|
|
|
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
const segment = segments[i];
|
|
currentDirectory = getDirectory(segment, i === 0);
|
|
}
|
|
|
|
const lastSegment = segments[segments.length - 1];
|
|
|
|
if (lastSegment === "") {
|
|
return currentDirectory;
|
|
} else if (currentDirectory != null) {
|
|
const folder = (currentDirectory as VirtualFolder).findSubFolder(lastSegment);
|
|
|
|
if (folder != null)
|
|
return folder;
|
|
|
|
const { name, extension } = VirtualFile.convertId(lastSegment);
|
|
return (currentDirectory as VirtualFolder).findFile(name, extension);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Opens this folder in file explorer
|
|
*/
|
|
open(windowsManager: WindowsManager) {
|
|
return windowsManager.open(APPS.FILE_EXPLORER, { startPath: this.path });
|
|
}
|
|
|
|
/**
|
|
* Deletes this folder and all its files and sub-folders recursively
|
|
*/
|
|
delete() {
|
|
if (!this.canBeEdited)
|
|
return;
|
|
|
|
super.delete();
|
|
|
|
const items = [
|
|
...this.files,
|
|
...this.subFolders
|
|
];
|
|
|
|
items.forEach((item) => {
|
|
item.delete();
|
|
});
|
|
|
|
this.confirmChanges();
|
|
}
|
|
|
|
/**
|
|
* Returns all files inside this folder
|
|
*/
|
|
getFiles(showHidden = false): VirtualFile[] {
|
|
if (showHidden)
|
|
return this.files;
|
|
|
|
return this.files.filter(({ name }) =>
|
|
!name.startsWith(".")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns all sub-folders inside this folder
|
|
*/
|
|
getSubFolders(showHidden = false): VirtualFolder[] {
|
|
if (showHidden)
|
|
return this.subFolders;
|
|
|
|
return this.subFolders.filter(({ name }) =>
|
|
!name.startsWith(".")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns the amount of files and sub-folders inside this folder
|
|
*/
|
|
getItemCount(showHidden = false): number {
|
|
const filesCount = this.getFiles(showHidden)?.length ?? 0;
|
|
const foldersCount = this.getSubFolders(showHidden)?.length ?? 0;
|
|
|
|
return filesCount + foldersCount;
|
|
}
|
|
|
|
isFolder(): boolean {
|
|
return true;
|
|
}
|
|
|
|
getIconUrl(): string {
|
|
if (this.iconUrl != null)
|
|
return this.iconUrl;
|
|
|
|
return AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "folder");
|
|
}
|
|
|
|
toJSON(): VirtualFolderJson | null {
|
|
const object = super.toJSON() as VirtualFolderJson;
|
|
|
|
if (object == null)
|
|
return null;
|
|
|
|
if (this.files.length > 0) {
|
|
const files = this.files
|
|
.map((file) => file.toJSON())
|
|
.filter((file) => file != null);
|
|
|
|
if (files.length > 0)
|
|
object.fls = files;
|
|
}
|
|
if (this.subFolders.length > 0) {
|
|
const folders = this.subFolders
|
|
.map((folder) => folder.toJSON())
|
|
.filter((folder) => folder != null);
|
|
|
|
if (folders.length > 0)
|
|
object.fds = folders;
|
|
}
|
|
|
|
// Don't store folder if it's empty and untouched
|
|
if (!this.editedByUser && (!object.fls || object.fls.length === 0) && (!object.fds || object.fds.length === 0))
|
|
return null;
|
|
|
|
return object;
|
|
}
|
|
} |