ProzillaOS/src/features/virtual-drive/folder/virtualFolder.ts
Prozilla 2ede410bec
Migration to TypeScript - Stage 3
Improved safe typing
2024-05-10 15:43:14 +02:00

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