Fixed virtual drive saving and loading

This commit is contained in:
Prozilla 2023-08-17 15:06:48 +02:00
parent 334676084a
commit 9c65c7c1f8
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
10 changed files with 248 additions and 58 deletions

View file

@ -59,7 +59,7 @@ export function FileExplorer({ startPath }) {
if (directory) { if (directory) {
setCurrentDirectory(directory); setCurrentDirectory(directory);
setPath(directory.path); setPath(directory.root ? "/" : directory.path);
} }
}; };
@ -68,10 +68,16 @@ export function FileExplorer({ startPath }) {
}; };
const onKeyDown = (event) => { const onKeyDown = (event) => {
const value = event.target.value; let value = event.target.value;
if (event.key === "Enter") { if (event.key === "Enter") {
setCurrentDirectory(virtualRoot.navigate(value)); if (value === "")
value = "~";
const directory = virtualRoot.navigate(value);
setCurrentDirectory(directory);
setPath(directory.root ? "/" : directory.path);
} }
}; };
@ -139,6 +145,16 @@ export function FileExplorer({ startPath }) {
</button> </button>
</div> </div>
<div className={styles.Main}> <div className={styles.Main}>
{currentDirectory?.getSubFolders(showHidden)?.map(({ name }, index) =>
<button key={index} tabIndex={0} className={styles["Folder-button"]}
onClick={() => {
changeDirectory(name);
}}
>
<FontAwesomeIcon icon={faFolder}/>
<p>{name}</p>
</button>
)}
{currentDirectory?.getFiles(showHidden)?.map((file, index) => {currentDirectory?.getFiles(showHidden)?.map((file, index) =>
<button key={index} tabIndex={0} className={styles["File-button"]} <button key={index} tabIndex={0} className={styles["File-button"]}
onClick={(event) => { onClick={(event) => {
@ -150,16 +166,6 @@ export function FileExplorer({ startPath }) {
<p>{file.id}</p> <p>{file.id}</p>
</button> </button>
)} )}
{currentDirectory?.getSubFolders(showHidden)?.map(({ name }, index) =>
<button key={index} tabIndex={0} className={styles["Folder-button"]}
onClick={() => {
changeDirectory(name);
}}
>
<FontAwesomeIcon icon={faFolder}/>
<p>{name}</p>
</button>
)}
</div> </div>
</div> </div>
</div> </div>

View file

@ -4,6 +4,8 @@ import styles from "./Settings.module.css";
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js"; import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
import { useSettingsManager } from "../../../hooks/settings/settingsManagerContext.js"; import { useSettingsManager } from "../../../hooks/settings/settingsManagerContext.js";
const wallpapersPath = "~/Images/Wallpapers";
export function AppearanceSettings() { export function AppearanceSettings() {
const virtualRoot = useVirtualRoot(); const virtualRoot = useVirtualRoot();
const settingsManager = useSettingsManager(); const settingsManager = useSettingsManager();
@ -23,7 +25,7 @@ export function AppearanceSettings() {
<div className={styles["Option"]}> <div className={styles["Option"]}>
<p className={styles["Label"]}>Wallpaper</p> <p className={styles["Label"]}>Wallpaper</p>
<div className={styles["Input"]}> <div className={styles["Input"]}>
{virtualRoot.navigate("~/Images")?.getFiles()?.map(({ id, source }) => {virtualRoot.navigate(wallpapersPath)?.getFiles()?.toReversed().map(({ id, source }) =>
<label className={styles["Image-select"]} key={id}> <label className={styles["Image-select"]} key={id}>
<input <input
type="radio" type="radio"

View file

@ -7,6 +7,7 @@ import { useModals } from "../../hooks/modals/modals.js";
import { ModalsView } from "../modals/ModalsView.jsx"; import { ModalsView } from "../modals/ModalsView.jsx";
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js"; import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js";
import { useContextMenu } from "../../hooks/modals/contextMenu.js"; import { useContextMenu } from "../../hooks/modals/contextMenu.js";
import { FALLBACK_WALLPAPER } from "../../constants/desktop.js";
export const Desktop = memo(() => { export const Desktop = memo(() => {
const settingsManager = useSettingsManager(); const settingsManager = useSettingsManager();
@ -28,12 +29,20 @@ export const Desktop = memo(() => {
})(); })();
}, [settingsManager]); }, [settingsManager]);
const onError = () => {
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
settings.set("wallpaper", FALLBACK_WALLPAPER);
};
return (<> return (<>
<div <div
className={styles.Container} className={styles.Container}
style={{ backgroundImage: wallpaper ? `url(${wallpaper})` : null }}
onContextMenu={onContextMenu} onContextMenu={onContextMenu}
> >
{wallpaper
? <img src={wallpaper} className={styles.Wallpaper} alt="Desktop wallpaper" onError={onError}/>
: null
}
<ModalsView modalsManager={modalsManager} modals={modals}/> <ModalsView modalsManager={modalsManager} modals={modals}/>
</div> </div>
</>); </>);

View file

@ -5,6 +5,14 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: -1; z-index: -1;
background-size: cover; }
background-position: center;
.Wallpaper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
pointer-events: none;
} }

View file

@ -8,3 +8,5 @@ export const WALLPAPERS = [
"/media/wallpapers/vibrant-wallpaper-purple-yellow.png", "/media/wallpapers/vibrant-wallpaper-purple-yellow.png",
"/media/wallpapers/wave-abstract-wallpaper-teal.png", "/media/wallpapers/wave-abstract-wallpaper-teal.png",
]; ];
export const FALLBACK_WALLPAPER = WALLPAPERS[4];

View file

@ -6,6 +6,9 @@ export class StorageManager {
* @param {string} value * @param {string} value
*/ */
static store(key, value) { static store(key, value) {
if (key == null || value == null)
return;
localStorage.setItem(key, value); localStorage.setItem(key, value);
} }
@ -14,6 +17,9 @@ export class StorageManager {
* @returns {string | null} * @returns {string | null}
*/ */
static load(key) { static load(key) {
if (key == null)
return null;
return localStorage.getItem(key); return localStorage.getItem(key);
} }

View file

@ -1,5 +1,4 @@
import { EventEmitter } from "../utils/events.js"; import { EventEmitter } from "../utils/events.js";
import { VirtualRoot } from "./virtualRoot.js"; import { VirtualRoot } from "./virtualRoot.js";
export class VirtualBase extends EventEmitter { export class VirtualBase extends EventEmitter {
@ -20,8 +19,12 @@ export class VirtualBase extends EventEmitter {
* @returns {VirtualBase} * @returns {VirtualBase}
*/ */
setName(name) { setName(name) {
if (this.name === name || !this.canBeEdited)
return;
this.name = name; this.name = name;
this.getRoot().saveData();
this.confirmChanges();
return this; return this;
} }
@ -30,12 +33,13 @@ export class VirtualBase extends EventEmitter {
* @returns {ThisType} * @returns {ThisType}
*/ */
setAlias(alias) { setAlias(alias) {
if (this.alias === alias) if (this.alias === alias || !this.canBeEdited)
return; return;
this.alias = alias; this.alias = alias;
this.getRoot().addShortcut(alias, this); this.getRoot().addShortcut(alias, this);
this.getRoot().saveData();
this.confirmChanges();
return this; return this;
} }
@ -44,34 +48,92 @@ export class VirtualBase extends EventEmitter {
* @returns {VirtualBase} * @returns {VirtualBase}
*/ */
setParent(parent) { setParent(parent) {
if (this.parent === parent) if (this.parent === parent || !this.canBeEdited)
return; return;
this.parent = parent; this.parent = parent;
this.getRoot().saveData();
this.confirmChanges();
return this;
}
/**
* @param {boolean} value
* @returns {VirtualBase}
*/
setProtected(value) {
if (!this.canBeEdited)
return;
this.isProtected = value;
return this; return this;
} }
delete() { delete() {
if (!this.canBeEdited)
return;
const parent = this.parent; const parent = this.parent;
if (parent == null) if (parent == null)
return; return;
parent.remove?.(this); parent.remove?.(this);
parent.getRoot()?.saveData(); this.confirmChanges(parent.getRoot());
}
/**
* @param {VirtualRoot} [root]
*/
confirmChanges(root = null) {
if (this.getRoot().loadedDefaultData)
this.editedByUser = true;
if (root == null)
root = this.getRoot();
root?.saveData();
} }
open() {} open() {}
get path() { get path() {
return this.alias ?? this.absolutePath; return this.alias ?? this.displayPath;
} }
get absolutePath() { /**
* Returns path without using alias
*/
get displayPath() {
return this.parent?.path + "/" + this.id; return this.parent?.path + "/" + this.id;
} }
/**
* Returns path without using any aliases
* @returns {string}
*/
get absolutePath() {
if (this.parent?.isRoot) {
return "/" + this.id;
} else {
return this.parent?.absolutePath + "/" + this.id;
}
}
/**
* Returns whether this can be edited in its current state
* @returns {boolean}
*/
get canBeEdited() {
const isProtected = this.isProtected && this.getRoot().loadedDefaultData;
if (!isProtected && this.parent != null) {
return this.parent.canBeEdited;
} else {
return !isProtected;
}
}
/** /**
* @returns {VirtualRoot} * @returns {VirtualRoot}
*/ */
@ -85,6 +147,9 @@ export class VirtualBase extends EventEmitter {
return root; return root;
} }
/**
* @returns {object | null}
*/
toJSON() { toJSON() {
const object = { const object = {
nam: this.name nam: this.name

View file

@ -35,15 +35,15 @@ export class VirtualFile extends VirtualBase {
* @returns {VirtualFile} * @returns {VirtualFile}
*/ */
setSource(source) { setSource(source) {
if (this.source === source) if (this.source === source || !this.canBeEdited)
return; return;
this.source = source; this.source = source;
this.content = null; this.content = null;
this.emit(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, this); this.emit(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, this);
this.getRoot().saveData();
this.confirmChanges();
return this; return this;
} }
@ -53,15 +53,15 @@ export class VirtualFile extends VirtualBase {
* @returns {VirtualFile} * @returns {VirtualFile}
*/ */
setContent(content) { setContent(content) {
if (this.content === content) if (this.content === content || !this.canBeEdited)
return; return;
this.content = content; this.content = content;
this.source = null; this.source = null;
this.emit(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, this); this.emit(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, this);
this.getRoot().saveData();
this.confirmChanges();
return this; return this;
} }
@ -93,9 +93,19 @@ export class VirtualFile extends VirtualBase {
}); });
} }
/**
* @returns {object | null}
*/
toJSON() { toJSON() {
// Don't return file if it can't or hasn't been edited
if (!this.canBeEdited || (this.editedByUser == null || !this.editedByUser))
return null;
const object = super.toJSON(); const object = super.toJSON();
if (object == null)
return null;
if (this.extension != null) { if (this.extension != null) {
object.ext = this.extension; object.ext = this.extension;
} }

View file

@ -97,6 +97,9 @@ export class VirtualFolder extends VirtualBase {
* @returns {VirtualFolder} * @returns {VirtualFolder}
*/ */
createFile(name, extension, callback) { createFile(name, extension, callback) {
if (!this.canBeEdited)
return;
let newFile = this.findFile(name, extension); let newFile = this.findFile(name, extension);
if (newFile == null) { if (newFile == null) {
newFile = new VirtualFile(name, extension); newFile = new VirtualFile(name, extension);
@ -104,7 +107,8 @@ export class VirtualFolder extends VirtualBase {
newFile.parent = this; newFile.parent = this;
} }
callback?.(newFile); callback?.(newFile);
this.getRoot().saveData();
newFile.confirmChanges();
return this; return this;
} }
@ -116,10 +120,14 @@ export class VirtualFolder extends VirtualBase {
* @returns {VirtualFolder} * @returns {VirtualFolder}
*/ */
createFiles(files) { createFiles(files) {
if (!this.canBeEdited)
return;
files.forEach(({name, extension}) => { files.forEach(({name, extension}) => {
this.createFile(name, extension); this.createFile(name, extension);
}); });
this.getRoot().saveData();
this.confirmChanges();
return this; return this;
} }
@ -135,6 +143,9 @@ export class VirtualFolder extends VirtualBase {
* @param {createFolderCallback} callback * @param {createFolderCallback} callback
*/ */
createFolder(name, callback) { createFolder(name, callback) {
if (!this.canBeEdited)
return;
let newFolder = this.findSubFolder(name); let newFolder = this.findSubFolder(name);
if (newFolder == null) { if (newFolder == null) {
newFolder = new VirtualFolder(name); newFolder = new VirtualFolder(name);
@ -142,7 +153,8 @@ export class VirtualFolder extends VirtualBase {
newFolder.parent = this; newFolder.parent = this;
} }
callback?.(newFolder); callback?.(newFolder);
this.getRoot().saveData();
newFolder.confirmChanges();
return this; return this;
} }
@ -152,18 +164,25 @@ export class VirtualFolder extends VirtualBase {
* @returns {VirtualFolder} * @returns {VirtualFolder}
*/ */
createFolders(folders) { createFolders(folders) {
if (!this.canBeEdited)
return;
folders.forEach((name) => { folders.forEach((name) => {
this.createFolder(name); this.createFolder(name);
}); });
this.getRoot().saveData();
this.confirmChanges();
return this; return this;
} }
/** /**
* Removes a file or folder from this folder * Removes a file or folder from this folder
* @param {VirtualFile|VirtualFolder} child * @param {VirtualFile | VirtualFolder} child
*/ */
remove(child) { remove(child) {
if (!this.canBeEdited)
return;
child.parent = null; child.parent = null;
if (child instanceof VirtualFile) { if (child instanceof VirtualFile) {
@ -172,13 +191,13 @@ export class VirtualFolder extends VirtualBase {
removeFromArray(child, this.subFolders); removeFromArray(child, this.subFolders);
} }
this.getRoot()?.saveData(); child.confirmChanges();
} }
/** /**
* Returns the file or folder at a relative path or null if it doesn't exist * Returns the file or folder at a relative path or null if it doesn't exist
* @param {string} relativePath * @param {string} relativePath
* @returns {VirtualFile|VirtualFolder|null} * @returns {VirtualFile | VirtualFolder | null}
*/ */
navigate(relativePath) { navigate(relativePath) {
const segments = relativePath.split("/"); const segments = relativePath.split("/");
@ -231,16 +250,20 @@ export class VirtualFolder extends VirtualBase {
* Deletes this folder and all its files and sub-folders recursively * Deletes this folder and all its files and sub-folders recursively
*/ */
delete() { delete() {
if (!this.canBeEdited)
return;
super.delete(); super.delete();
this.files.concat(this.subFolders).forEach((item) => { this.files.concat(this.subFolders).forEach((item) => {
item.delete(); item.delete();
}); });
this.getRoot()?.saveData(); this.confirmChanges();
} }
/** /**
* Returns all files inside this folder
* @param {Boolean=false} showHidden * @param {Boolean=false} showHidden
* @returns {Array<VirtualFile>} * @returns {Array<VirtualFile>}
*/ */
@ -254,6 +277,7 @@ export class VirtualFolder extends VirtualBase {
} }
/** /**
* Returns all subfolders inside this folder
* @param {Boolean=false} showHidden * @param {Boolean=false} showHidden
* @returns {Array<VirtualFolder>} * @returns {Array<VirtualFolder>}
*/ */
@ -266,16 +290,36 @@ export class VirtualFolder extends VirtualBase {
); );
} }
/**
* @returns {object | null}
*/
toJSON() { toJSON() {
const object = super.toJSON(); const object = super.toJSON();
if (object == null)
return null;
if (this.files.length > 0) { if (this.files.length > 0) {
object.fls = this.files.map((file) => file.toJSON()); const files = this.files
.map((file) => file.toJSON())
.filter((file) => file != null);
if (files.length > 0)
object.fls = files;
} }
if (this.subFolders.length > 0) { if (this.subFolders.length > 0) {
object.fds = this.subFolders.map((folder) => folder.toJSON()); 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
if ((!object.fls || object.fls.length === 0) && (!object.fds || object.fds.length === 0))
return null;
return object; return object;
} }
} }

View file

@ -7,14 +7,16 @@ import { VirtualFolder } from "./virtualFolder.js";
* A virtual folder that serves as the root folder * A virtual folder that serves as the root folder
*/ */
export class VirtualRoot extends VirtualFolder { export class VirtualRoot extends VirtualFolder {
/** /** @type {boolean} */
* @type {boolean}
*/
initiated = false; initiated = false;
/** @type {boolean} */
loadedDefaultData = false;
constructor() { constructor() {
super("root"); super("root");
this.root = this; this.root = this;
this.isRoot = true;
this.shortcuts = {}; this.shortcuts = {};
} }
@ -51,18 +53,21 @@ export class VirtualRoot extends VirtualFolder {
}); });
}) })
.createFolder("Images", (folder) => { .createFolder("Images", (folder) => {
for (let i = 0; i < WALLPAPERS.length; i++) { folder.createFolder("Wallpapers", (folder) => {
const source = WALLPAPERS[i]; folder.setProtected(true);
folder.createFile(`Wallpaper${i + 1}`, "png", (file) => { for (let i = 0; i < WALLPAPERS.length; i++) {
file.setSource(source); const source = WALLPAPERS[i];
}); folder.createFile(`Wallpaper${i + 1}`, "png", (file) => {
} file.setSource(source);
});
}
});
}) })
.createFolder("Documents", (folder) => { .createFolder("Documents", (folder) => {
folder.createFile("text", "txt", (file) => { folder.createFile("text", "txt", (file) => {
file.setContent("Hello world!"); file.setContent("Hello world!");
}).createFile("info", "md", (file) => { }).createFile("info", "md", (file) => {
file.setSource("/documents/info.md"); file.setProtected(true).setSource("/documents/info.md");
}); });
}) })
.createFolder("Desktop"); .createFolder("Desktop");
@ -117,10 +122,10 @@ export class VirtualRoot extends VirtualFolder {
const addFolder = ({ nam: name, fds: folders, fls: files }, parent = this) => { const addFolder = ({ nam: name, fds: folders, fls: files }, parent = this) => {
parent.createFolder(name, (folder) => { parent.createFolder(name, (folder) => {
if (Object.values(shortcuts).includes(folder.absolutePath)) { if (Object.values(shortcuts).includes(folder.displayPath)) {
let alias; let alias;
for (const [key, value] of Object.entries(shortcuts)) { for (const [key, value] of Object.entries(shortcuts)) {
if (value === folder.absolutePath) if (value === folder.displayPath)
alias = key; alias = key;
} }
folder.setAlias(alias); folder.setAlias(alias);
@ -148,23 +153,40 @@ export class VirtualRoot extends VirtualFolder {
addFile(file); addFile(file);
}); });
} }
console.log(this);
} }
/**
* Calls the storage manager's store function with this root's data as a string
*/
saveData() { saveData() {
if (!this.initiated) if (!this.initiated)
return; return;
StorageManager.store("data", this.toString()); const data = this.toString();
if (data == null)
return;
StorageManager.store("data", data);
} }
/**
* Initiates this root by loading the default data and then the user's data on top
* @returns {VirtualRoot}
*/
init() { init() {
this.initiated = false; this.initiated = false;
// Load default data
this.loadedDefaultData = false;
this.setAlias("/"); this.setAlias("/");
this.loadDefaultData(); this.loadDefaultData();
this.loadedDefaultData = true;
// Load user's data
this.loadData(); this.loadData();
this.initiated = true; this.initiated = true;
return this; return this;
} }
@ -179,6 +201,9 @@ export class VirtualRoot extends VirtualFolder {
return this; return this;
} }
/**
* Tells the storage manager to clear all data and reloads the window
*/
reset() { reset() {
if (window.confirm("Are you sure you want to reset all your data?")) { if (window.confirm("Are you sure you want to reset all your data?")) {
StorageManager.clear(); StorageManager.clear();
@ -202,13 +227,19 @@ export class VirtualRoot extends VirtualFolder {
return ""; return "";
} }
get absolutePath() { get displayPath() {
return "/"; return "/";
} }
/**
* @returns {object | null}
*/
toJSON() { toJSON() {
const object = super.toJSON(); const object = super.toJSON();
if (object == null)
return null;
if (Object.entries(this.shortcuts).length > 0) { if (Object.entries(this.shortcuts).length > 0) {
object.scs = {}; object.scs = {};
@ -220,8 +251,15 @@ export class VirtualRoot extends VirtualFolder {
return object; return object;
} }
/**
* @returns {string | null}
*/
toString() { toString() {
const json = this.toJSON(); const json = this.toJSON();
if (json == null)
return null;
return JSON.stringify(json); return JSON.stringify(json);
} }
} }