Fixed virtual drive saving and loading
This commit is contained in:
parent
334676084a
commit
9c65c7c1f8
10 changed files with 248 additions and 58 deletions
|
|
@ -59,7 +59,7 @@ export function FileExplorer({ startPath }) {
|
|||
|
||||
if (directory) {
|
||||
setCurrentDirectory(directory);
|
||||
setPath(directory.path);
|
||||
setPath(directory.root ? "/" : directory.path);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -68,10 +68,16 @@ export function FileExplorer({ startPath }) {
|
|||
};
|
||||
|
||||
const onKeyDown = (event) => {
|
||||
const value = event.target.value;
|
||||
let value = event.target.value;
|
||||
|
||||
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>
|
||||
</div>
|
||||
<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) =>
|
||||
<button key={index} tabIndex={0} className={styles["File-button"]}
|
||||
onClick={(event) => {
|
||||
|
|
@ -150,16 +166,6 @@ export function FileExplorer({ startPath }) {
|
|||
<p>{file.id}</p>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import styles from "./Settings.module.css";
|
|||
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext.js";
|
||||
import { useSettingsManager } from "../../../hooks/settings/settingsManagerContext.js";
|
||||
|
||||
const wallpapersPath = "~/Images/Wallpapers";
|
||||
|
||||
export function AppearanceSettings() {
|
||||
const virtualRoot = useVirtualRoot();
|
||||
const settingsManager = useSettingsManager();
|
||||
|
|
@ -23,7 +25,7 @@ export function AppearanceSettings() {
|
|||
<div className={styles["Option"]}>
|
||||
<p className={styles["Label"]}>Wallpaper</p>
|
||||
<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}>
|
||||
<input
|
||||
type="radio"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { useModals } from "../../hooks/modals/modals.js";
|
|||
import { ModalsView } from "../modals/ModalsView.jsx";
|
||||
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js";
|
||||
import { useContextMenu } from "../../hooks/modals/contextMenu.js";
|
||||
import { FALLBACK_WALLPAPER } from "../../constants/desktop.js";
|
||||
|
||||
export const Desktop = memo(() => {
|
||||
const settingsManager = useSettingsManager();
|
||||
|
|
@ -28,12 +29,20 @@ export const Desktop = memo(() => {
|
|||
})();
|
||||
}, [settingsManager]);
|
||||
|
||||
const onError = () => {
|
||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
||||
settings.set("wallpaper", FALLBACK_WALLPAPER);
|
||||
};
|
||||
|
||||
return (<>
|
||||
<div
|
||||
className={styles.Container}
|
||||
style={{ backgroundImage: wallpaper ? `url(${wallpaper})` : null }}
|
||||
onContextMenu={onContextMenu}
|
||||
>
|
||||
{wallpaper
|
||||
? <img src={wallpaper} className={styles.Wallpaper} alt="Desktop wallpaper" onError={onError}/>
|
||||
: null
|
||||
}
|
||||
<ModalsView modalsManager={modalsManager} modals={modals}/>
|
||||
</div>
|
||||
</>);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,14 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
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;
|
||||
}
|
||||
|
|
@ -7,4 +7,6 @@ export const WALLPAPERS = [
|
|||
"/media/wallpapers/vibrant-wallpaper-blue-purple-red.png",
|
||||
"/media/wallpapers/vibrant-wallpaper-purple-yellow.png",
|
||||
"/media/wallpapers/wave-abstract-wallpaper-teal.png",
|
||||
];
|
||||
];
|
||||
|
||||
export const FALLBACK_WALLPAPER = WALLPAPERS[4];
|
||||
|
|
@ -6,6 +6,9 @@ export class StorageManager {
|
|||
* @param {string} value
|
||||
*/
|
||||
static store(key, value) {
|
||||
if (key == null || value == null)
|
||||
return;
|
||||
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
|
||||
|
|
@ -14,6 +17,9 @@ export class StorageManager {
|
|||
* @returns {string | null}
|
||||
*/
|
||||
static load(key) {
|
||||
if (key == null)
|
||||
return null;
|
||||
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { EventEmitter } from "../utils/events.js";
|
||||
|
||||
import { VirtualRoot } from "./virtualRoot.js";
|
||||
|
||||
export class VirtualBase extends EventEmitter {
|
||||
|
|
@ -20,8 +19,12 @@ export class VirtualBase extends EventEmitter {
|
|||
* @returns {VirtualBase}
|
||||
*/
|
||||
setName(name) {
|
||||
if (this.name === name || !this.canBeEdited)
|
||||
return;
|
||||
|
||||
this.name = name;
|
||||
this.getRoot().saveData();
|
||||
|
||||
this.confirmChanges();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -30,12 +33,13 @@ export class VirtualBase extends EventEmitter {
|
|||
* @returns {ThisType}
|
||||
*/
|
||||
setAlias(alias) {
|
||||
if (this.alias === alias)
|
||||
if (this.alias === alias || !this.canBeEdited)
|
||||
return;
|
||||
|
||||
this.alias = alias;
|
||||
this.getRoot().addShortcut(alias, this);
|
||||
this.getRoot().saveData();
|
||||
|
||||
this.confirmChanges();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -44,34 +48,92 @@ export class VirtualBase extends EventEmitter {
|
|||
* @returns {VirtualBase}
|
||||
*/
|
||||
setParent(parent) {
|
||||
if (this.parent === parent)
|
||||
if (this.parent === parent || !this.canBeEdited)
|
||||
return;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
delete() {
|
||||
if (!this.canBeEdited)
|
||||
return;
|
||||
|
||||
const parent = this.parent;
|
||||
|
||||
if (parent == null)
|
||||
return;
|
||||
|
||||
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() {}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
|
|
@ -85,6 +147,9 @@ export class VirtualBase extends EventEmitter {
|
|||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object | null}
|
||||
*/
|
||||
toJSON() {
|
||||
const object = {
|
||||
nam: this.name
|
||||
|
|
|
|||
|
|
@ -35,15 +35,15 @@ export class VirtualFile extends VirtualBase {
|
|||
* @returns {VirtualFile}
|
||||
*/
|
||||
setSource(source) {
|
||||
if (this.source === source)
|
||||
if (this.source === source || !this.canBeEdited)
|
||||
return;
|
||||
|
||||
this.source = source;
|
||||
this.content = null;
|
||||
|
||||
this.emit(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, this);
|
||||
this.getRoot().saveData();
|
||||
|
||||
|
||||
this.confirmChanges();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -53,15 +53,15 @@ export class VirtualFile extends VirtualBase {
|
|||
* @returns {VirtualFile}
|
||||
*/
|
||||
setContent(content) {
|
||||
if (this.content === content)
|
||||
if (this.content === content || !this.canBeEdited)
|
||||
return;
|
||||
|
||||
this.content = content;
|
||||
this.source = null;
|
||||
|
||||
this.emit(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, this);
|
||||
this.getRoot().saveData();
|
||||
|
||||
this.confirmChanges();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -93,8 +93,18 @@ export class VirtualFile extends VirtualBase {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object | null}
|
||||
*/
|
||||
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();
|
||||
|
||||
if (object == null)
|
||||
return null;
|
||||
|
||||
if (this.extension != null) {
|
||||
object.ext = this.extension;
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ export class VirtualFolder extends VirtualBase {
|
|||
* @returns {VirtualFolder}
|
||||
*/
|
||||
createFile(name, extension, callback) {
|
||||
if (!this.canBeEdited)
|
||||
return;
|
||||
|
||||
let newFile = this.findFile(name, extension);
|
||||
if (newFile == null) {
|
||||
newFile = new VirtualFile(name, extension);
|
||||
|
|
@ -104,7 +107,8 @@ export class VirtualFolder extends VirtualBase {
|
|||
newFile.parent = this;
|
||||
}
|
||||
callback?.(newFile);
|
||||
this.getRoot().saveData();
|
||||
|
||||
newFile.confirmChanges();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -116,10 +120,14 @@ export class VirtualFolder extends VirtualBase {
|
|||
* @returns {VirtualFolder}
|
||||
*/
|
||||
createFiles(files) {
|
||||
if (!this.canBeEdited)
|
||||
return;
|
||||
|
||||
files.forEach(({name, extension}) => {
|
||||
this.createFile(name, extension);
|
||||
});
|
||||
this.getRoot().saveData();
|
||||
|
||||
this.confirmChanges();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +143,9 @@ export class VirtualFolder extends VirtualBase {
|
|||
* @param {createFolderCallback} callback
|
||||
*/
|
||||
createFolder(name, callback) {
|
||||
if (!this.canBeEdited)
|
||||
return;
|
||||
|
||||
let newFolder = this.findSubFolder(name);
|
||||
if (newFolder == null) {
|
||||
newFolder = new VirtualFolder(name);
|
||||
|
|
@ -142,7 +153,8 @@ export class VirtualFolder extends VirtualBase {
|
|||
newFolder.parent = this;
|
||||
}
|
||||
callback?.(newFolder);
|
||||
this.getRoot().saveData();
|
||||
|
||||
newFolder.confirmChanges();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -152,18 +164,25 @@ export class VirtualFolder extends VirtualBase {
|
|||
* @returns {VirtualFolder}
|
||||
*/
|
||||
createFolders(folders) {
|
||||
if (!this.canBeEdited)
|
||||
return;
|
||||
|
||||
folders.forEach((name) => {
|
||||
this.createFolder(name);
|
||||
});
|
||||
this.getRoot().saveData();
|
||||
|
||||
this.confirmChanges();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a file or folder from this folder
|
||||
* @param {VirtualFile|VirtualFolder} child
|
||||
* @param {VirtualFile | VirtualFolder} child
|
||||
*/
|
||||
remove(child) {
|
||||
if (!this.canBeEdited)
|
||||
return;
|
||||
|
||||
child.parent = null;
|
||||
|
||||
if (child instanceof VirtualFile) {
|
||||
|
|
@ -172,13 +191,13 @@ export class VirtualFolder extends VirtualBase {
|
|||
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
|
||||
* @param {string} relativePath
|
||||
* @returns {VirtualFile|VirtualFolder|null}
|
||||
* @returns {VirtualFile | VirtualFolder | null}
|
||||
*/
|
||||
navigate(relativePath) {
|
||||
const segments = relativePath.split("/");
|
||||
|
|
@ -231,16 +250,20 @@ export class VirtualFolder extends VirtualBase {
|
|||
* Deletes this folder and all its files and sub-folders recursively
|
||||
*/
|
||||
delete() {
|
||||
if (!this.canBeEdited)
|
||||
return;
|
||||
|
||||
super.delete();
|
||||
|
||||
this.files.concat(this.subFolders).forEach((item) => {
|
||||
item.delete();
|
||||
});
|
||||
|
||||
this.getRoot()?.saveData();
|
||||
this.confirmChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all files inside this folder
|
||||
* @param {Boolean=false} showHidden
|
||||
* @returns {Array<VirtualFile>}
|
||||
*/
|
||||
|
|
@ -254,6 +277,7 @@ export class VirtualFolder extends VirtualBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns all subfolders inside this folder
|
||||
* @param {Boolean=false} showHidden
|
||||
* @returns {Array<VirtualFolder>}
|
||||
*/
|
||||
|
|
@ -266,16 +290,36 @@ export class VirtualFolder extends VirtualBase {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object | null}
|
||||
*/
|
||||
toJSON() {
|
||||
const object = super.toJSON();
|
||||
|
||||
if (object == null)
|
||||
return null;
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,14 +7,16 @@ import { VirtualFolder } from "./virtualFolder.js";
|
|||
* A virtual folder that serves as the root folder
|
||||
*/
|
||||
export class VirtualRoot extends VirtualFolder {
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
/** @type {boolean} */
|
||||
initiated = false;
|
||||
|
||||
/** @type {boolean} */
|
||||
loadedDefaultData = false;
|
||||
|
||||
constructor() {
|
||||
super("root");
|
||||
this.root = this;
|
||||
this.isRoot = true;
|
||||
this.shortcuts = {};
|
||||
}
|
||||
|
||||
|
|
@ -51,18 +53,21 @@ export class VirtualRoot extends VirtualFolder {
|
|||
});
|
||||
})
|
||||
.createFolder("Images", (folder) => {
|
||||
for (let i = 0; i < WALLPAPERS.length; i++) {
|
||||
const source = WALLPAPERS[i];
|
||||
folder.createFile(`Wallpaper${i + 1}`, "png", (file) => {
|
||||
file.setSource(source);
|
||||
});
|
||||
}
|
||||
folder.createFolder("Wallpapers", (folder) => {
|
||||
folder.setProtected(true);
|
||||
for (let i = 0; i < WALLPAPERS.length; i++) {
|
||||
const source = WALLPAPERS[i];
|
||||
folder.createFile(`Wallpaper${i + 1}`, "png", (file) => {
|
||||
file.setSource(source);
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.createFolder("Documents", (folder) => {
|
||||
folder.createFile("text", "txt", (file) => {
|
||||
file.setContent("Hello world!");
|
||||
}).createFile("info", "md", (file) => {
|
||||
file.setSource("/documents/info.md");
|
||||
file.setProtected(true).setSource("/documents/info.md");
|
||||
});
|
||||
})
|
||||
.createFolder("Desktop");
|
||||
|
|
@ -117,10 +122,10 @@ export class VirtualRoot extends VirtualFolder {
|
|||
|
||||
const addFolder = ({ nam: name, fds: folders, fls: files }, parent = this) => {
|
||||
parent.createFolder(name, (folder) => {
|
||||
if (Object.values(shortcuts).includes(folder.absolutePath)) {
|
||||
if (Object.values(shortcuts).includes(folder.displayPath)) {
|
||||
let alias;
|
||||
for (const [key, value] of Object.entries(shortcuts)) {
|
||||
if (value === folder.absolutePath)
|
||||
if (value === folder.displayPath)
|
||||
alias = key;
|
||||
}
|
||||
folder.setAlias(alias);
|
||||
|
|
@ -148,23 +153,40 @@ export class VirtualRoot extends VirtualFolder {
|
|||
addFile(file);
|
||||
});
|
||||
}
|
||||
|
||||
console.log(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the storage manager's store function with this root's data as a string
|
||||
*/
|
||||
saveData() {
|
||||
if (!this.initiated)
|
||||
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() {
|
||||
this.initiated = false;
|
||||
|
||||
// Load default data
|
||||
this.loadedDefaultData = false;
|
||||
this.setAlias("/");
|
||||
this.loadDefaultData();
|
||||
this.loadedDefaultData = true;
|
||||
|
||||
// Load user's data
|
||||
this.loadData();
|
||||
this.initiated = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -179,6 +201,9 @@ export class VirtualRoot extends VirtualFolder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the storage manager to clear all data and reloads the window
|
||||
*/
|
||||
reset() {
|
||||
if (window.confirm("Are you sure you want to reset all your data?")) {
|
||||
StorageManager.clear();
|
||||
|
|
@ -202,13 +227,19 @@ export class VirtualRoot extends VirtualFolder {
|
|||
return "";
|
||||
}
|
||||
|
||||
get absolutePath() {
|
||||
get displayPath() {
|
||||
return "/";
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object | null}
|
||||
*/
|
||||
toJSON() {
|
||||
const object = super.toJSON();
|
||||
|
||||
if (object == null)
|
||||
return null;
|
||||
|
||||
if (Object.entries(this.shortcuts).length > 0) {
|
||||
object.scs = {};
|
||||
|
||||
|
|
@ -220,8 +251,15 @@ export class VirtualRoot extends VirtualFolder {
|
|||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string | null}
|
||||
*/
|
||||
toString() {
|
||||
const json = this.toJSON();
|
||||
|
||||
if (json == null)
|
||||
return null;
|
||||
|
||||
return JSON.stringify(json);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue