diff --git a/src/App.js b/src/App.js index 7775cc8..07549bb 100644 --- a/src/App.js +++ b/src/App.js @@ -2,15 +2,18 @@ import "./App.css"; import { Taskbar } from "./components/TaskBar.js"; import { WindowsManagerProvider } from "./hooks/WindowsManagerContext.js"; import { WindowsView } from "./components/WindowsView.js"; +import { VirtualRootProvider } from "./hooks/VirtualRootContext.js"; function App() { return ( - -
- - -
-
+ + +
+ + +
+
+
); } diff --git a/src/components/TaskBar.js b/src/components/TaskBar.js index 00f1c48..1c54842 100644 --- a/src/components/TaskBar.js +++ b/src/components/TaskBar.js @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import "./TaskBar.css"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faBatteryFull, faSearch, faVolumeHigh, faWifi } from "@fortawesome/free-solid-svg-icons"; -import ApplicationsManager from "../modules/applications/applications.js"; +import ApplicationsManager from "../features/applications/applications.js"; import { AppButton } from "./task-bar/AppButton.js"; export function Taskbar() { diff --git a/src/components/applications/terminal/Terminal.js b/src/components/applications/terminal/Terminal.js index 3ab9e19..97b485b 100644 --- a/src/components/applications/terminal/Terminal.js +++ b/src/components/applications/terminal/Terminal.js @@ -1,8 +1,7 @@ import { useState } from "react"; import styles from "./Terminal.module.css"; import { Command } from "./commands.js"; - -const PREFIX = "$ "; +import { useVirtualRoot } from "../../../hooks/VirtualRootContext.js"; function OutputLine({ text }) { return ( @@ -14,7 +13,7 @@ function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown }) { return ( {prefix &&

{prefix}

} -
@@ -31,6 +31,10 @@ function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown }) { export function Terminal() { const [inputValue, setInputValue] = useState(""); const [history, setHistory] = useState([]); + const virtualRoot = useVirtualRoot(); + const [currentDirectory, setCurrentDirectory] = useState(virtualRoot); + + const prefix = `user@prozilla-os:${currentDirectory.formattedPath}$ `; const updatedHistory = history; const pushHistory = (entry) => { @@ -51,7 +55,7 @@ export function Terminal() { return; pushHistory({ - text: PREFIX + value, + text: prefix + value, isInput: true }); @@ -65,7 +69,7 @@ export function Terminal() { const command = Command.find(commandName); if (!command) { - return promptOutput("Command not found: " + commandName); + return promptOutput(`${commandName}: Command not found`); } let response = null; @@ -73,17 +77,20 @@ export function Terminal() { try { response = command.execute(args, { promptOutput, - pushHistory + pushHistory, + virtualRoot, + currentDirectory, + setCurrentDirectory }); if (response == null) - return promptOutput("Command failed."); + return promptOutput(`${commandName}: Command failed`); if (!response.blank) promptOutput(response); } catch (error) { - promptOutput("Command failed."); console.error(error); + promptOutput(`${commandName}: Command failed`); } }; @@ -120,7 +127,7 @@ export function Terminal() { {displayHistory()} diff --git a/src/components/applications/terminal/Terminal.module.css b/src/components/applications/terminal/Terminal.module.css index 7991854..01ab882 100644 --- a/src/components/applications/terminal/Terminal.module.css +++ b/src/components/applications/terminal/Terminal.module.css @@ -16,6 +16,7 @@ .Prefix { width: max-content; + white-space: nowrap; } .Input, .Output { diff --git a/src/components/applications/terminal/commands.js b/src/components/applications/terminal/commands.js index 44df198..e554782 100644 --- a/src/components/applications/terminal/commands.js +++ b/src/components/applications/terminal/commands.js @@ -38,5 +38,35 @@ export class Command { return { blank: true }; }), + new Command("ls", (args, { currentDirectory }) => { + let directory = currentDirectory; + + if (args.length > 0) { + directory = currentDirectory.navigate(args[0]); + } + + if (!directory) + return `ls: Cannot access '${args[0]}': No such file or directory`; + + const fodlerNames = directory.subFolders.map((folder) => folder.id); + const fileNames = directory.files.map((file) => file.id); + + const contents = fodlerNames.concat(fileNames); + + if (contents.length === 0) + return { blank: true }; + + return contents.sort((nameA, nameB) => nameA.localeCompare(nameB)).join(" "); + }), + new Command("cd", (args, { currentDirectory, setCurrentDirectory }) => { + const destination = currentDirectory.navigate(args[0]); + + if (!destination) + return `cd: ${args[0]}: No such file or directory`; + + console.log(destination); + setCurrentDirectory(destination); + return { blank: true }; + }) ] } \ No newline at end of file diff --git a/src/modules/applications/application.js b/src/features/applications/application.js similarity index 100% rename from src/modules/applications/application.js rename to src/features/applications/application.js diff --git a/src/modules/applications/applications.js b/src/features/applications/applications.js similarity index 100% rename from src/modules/applications/applications.js rename to src/features/applications/applications.js diff --git a/src/modules/math/random.js b/src/features/math/random.js similarity index 100% rename from src/modules/math/random.js rename to src/features/math/random.js diff --git a/src/modules/math/vector2.js b/src/features/math/vector2.js similarity index 100% rename from src/modules/math/vector2.js rename to src/features/math/vector2.js diff --git a/src/features/virtual-drive/virtual-base.js b/src/features/virtual-drive/virtual-base.js new file mode 100644 index 0000000..e256de6 --- /dev/null +++ b/src/features/virtual-drive/virtual-base.js @@ -0,0 +1,61 @@ + +export class VirtualBase { + /** + * @param {String} name + */ + constructor(name) { + this.name = name; + } + + get id() { + return this.name; + } + + /** + * @param {String} name + */ + setName(name) { + this.name = name; + return this; + } + + setAlias(alias) { + this.alias = alias; + this.getRoot().addShortcut(alias, this); + return this; + } + + /** + * @param {VirtualBase} parent + */ + setParent(parent) { + this.parent = parent; + return this; + } + + delete() { + this.parent.remove?.(this); + } + + open() { + + } + + get path() { + return this.parent?.path + "/" + this.id; + } + + get formattedPath() { + return this.alias ?? this.path; + } + + getRoot() { + const root = this.root ?? this.parent.getRoot(); + + if (root === null) { + throw new Error("Root not found"); + } + + return root; + } +} \ No newline at end of file diff --git a/src/features/virtual-drive/virtual-file.js b/src/features/virtual-drive/virtual-file.js new file mode 100644 index 0000000..fe5192f --- /dev/null +++ b/src/features/virtual-drive/virtual-file.js @@ -0,0 +1,12 @@ +import { VirtualBase } from "./virtual-base.js"; + +export class VirtualFile extends VirtualBase { + constructor(name, extension) { + super(name); + this.extension = extension; + } + + get id() { + return this.name + this.extension; + } +} \ No newline at end of file diff --git a/src/features/virtual-drive/virtual-folder.js b/src/features/virtual-drive/virtual-folder.js new file mode 100644 index 0000000..ba62a63 --- /dev/null +++ b/src/features/virtual-drive/virtual-folder.js @@ -0,0 +1,159 @@ +import { VirtualBase } from "./virtual-base.js"; +import { VirtualFile } from "./virtual-file.js"; + +export class VirtualFolder extends VirtualBase { + static TYPE = { + GENERAL: 0, + MEDIA: 1, + } + + /** + * @param {String} name + * @param {Number} type + */ + constructor(name, type) { + super(name); + this.subFolders = []; + this.files = []; + this.type = type ?? VirtualFolder.TYPE.GENERAL; + } + + // add(element) { + + // } + + hasFile(name, extension) { + return this.findFile(name, extension) !== null; + } + + hasFolder(name) { + return this.findSubFolder(name) !== null; + } + + findFile(name, extension) { + let resultFile = null; + + this.files.forEach((file) => { + if ((file.name === name || (file.alias && file.alias === name)) && file.extension === extension) { + return resultFile = file; + } + }); + + return resultFile; + } + + findSubFolder(name) { + let resultFolder = null; + + this.subFolders.forEach((folder) => { + if (folder.name === name || (folder.alias && folder.alias === name)) { + return resultFolder = folder; + } + }); + + return resultFolder; + } + + createFile(name, extension) { + const newFile = new VirtualFile(name, extension); + this.files.push(newFile); + newFile.parent = this; + return newFile; + } + + createFolder(name) { + const newFolder = new VirtualFolder(name); + this.subFolders.push(newFolder); + newFolder.parent = this; + return newFolder; + } + + /** + * @param {String} destination + */ + addFile(destination) { + const folderNames = destination.split("/"); + const fileExtendedName = folderNames.pop().split(); + const fileExtendedNameSegments = fileExtendedName.split("."); + + const fileName = fileExtendedNameSegments[0]; + const fileExtension = fileExtendedNameSegments.length > 1 ? fileExtendedNameSegments[1] : "txt"; + + // To do: check if file already exists + + const file = new VirtualFile(fileName, fileExtension); + + const folders = this.addFolder(folderNames.join("/")); + + const parent = folders[folders.length - 1]; + parent.files.push(file); + file.parent = parent; + } + + /** + * @param {String} destination + */ + addFolder(destination) { + if (destination.endsWith("/")) + destination = destination.slice(0, -1); + + const folderNames = destination.split("/"); + + let currentFolder = this; + folderNames.forEach((folderName) => { + if (!currentFolder.hasFolder(folderName)) { + currentFolder.createFolder(folderName); + } + }) + } + + remove(child) { + if (child instanceof VirtualFile) { + // Remove file by id + } else if (child instanceof VirtualFolder) { + // Remove folder by id + } + } + + navigate(relativePath) { + const segments = relativePath.split("/"); + let currentDirectory = this; + + const getDirectory = (path, isStart) => { + 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?.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) { + // To do: add support for file names with dots + const [name, extension] = lastSegment.split("."); + return currentDirectory.findFile(name, extension); + } else { + return null; + } + } +} \ No newline at end of file diff --git a/src/features/virtual-drive/virtual-root.js b/src/features/virtual-drive/virtual-root.js new file mode 100644 index 0000000..75abc74 --- /dev/null +++ b/src/features/virtual-drive/virtual-root.js @@ -0,0 +1,21 @@ +import { VirtualFolder } from "./virtual-folder.js"; + +/** + * A virtual folder that serves as the root folder + */ +export class VirtualRoot extends VirtualFolder { + constructor() { + super("root"); + this.root = this; + this.shortcuts = {}; + } + + addShortcut(name, destination) { + this.shortcuts[name] = destination; + return this; + } + + get path() { + return ""; + } +} \ No newline at end of file diff --git a/src/modules/windows/windows.js b/src/features/windows/windows.js similarity index 100% rename from src/modules/windows/windows.js rename to src/features/windows/windows.js diff --git a/src/hooks/VirtualDriveContext.js b/src/hooks/VirtualDriveContext.js deleted file mode 100644 index e292de2..0000000 --- a/src/hooks/VirtualDriveContext.js +++ /dev/null @@ -1,24 +0,0 @@ -import { createContext, useContext } from "react"; -import { VirtualDrive } from "../modules/virtual-drive/virtual-drive.js"; - -const VirtualDriveContext = createContext(); - -/** - * @returns {React.Provider} - */ -export function VirtualDriveProvider({ children }) { - const virtualDrive = new VirtualDrive(); - - return ( - - {children} - - ); -} - -/** - * @returns {VirtualDrive} - */ -export function useWindowsManager() { - return useContext(VirtualDriveContext); -} \ No newline at end of file diff --git a/src/hooks/VirtualRootContext.js b/src/hooks/VirtualRootContext.js new file mode 100644 index 0000000..31a05e6 --- /dev/null +++ b/src/hooks/VirtualRootContext.js @@ -0,0 +1,36 @@ +import { createContext, useContext } from "react"; +import { VirtualRoot } from "../features/virtual-drive/virtual-root.js"; + +const VirtualRootContext = createContext(); + +/** + * @returns {React.Provider} + */ +export function VirtualRootProvider({ children }) { + const virtualRoot = new VirtualRoot().setAlias("/"); + + virtualRoot.createFolder("bin"); + virtualRoot.createFolder("dev"); + virtualRoot.createFolder("etc"); + virtualRoot.createFolder("usr"); + virtualRoot.createFolder("home").createFolder("prozilla-os").setAlias("~"); + virtualRoot.createFolder("lib"); + virtualRoot.createFolder("sbin"); + virtualRoot.createFolder("tmp"); + virtualRoot.createFolder("var"); + + console.log(virtualRoot.subFolders); + + return ( + + {children} + + ); +} + +/** + * @returns {VirtualRoot} + */ +export function useVirtualRoot() { + return useContext(VirtualRootContext); +} \ No newline at end of file diff --git a/src/hooks/WindowsManagerContext.js b/src/hooks/WindowsManagerContext.js index e8a90d6..3736437 100644 --- a/src/hooks/WindowsManagerContext.js +++ b/src/hooks/WindowsManagerContext.js @@ -1,5 +1,5 @@ import { createContext, useContext } from "react"; -import WindowsManager from "../modules/windows/windows.js"; +import WindowsManager from "../features/windows/windows.js"; import { WindowsProvider } from "./WindowsContext.js"; const WindowsManagerContext = createContext(); diff --git a/src/modules/virtual-drive/virtual-drive.js b/src/modules/virtual-drive/virtual-drive.js deleted file mode 100644 index f824517..0000000 --- a/src/modules/virtual-drive/virtual-drive.js +++ /dev/null @@ -1,7 +0,0 @@ -import { VirtualFolder } from "./virtual-folder.js"; - -export class VirtualDrive extends VirtualFolder { - constructor() { - - } -} \ No newline at end of file diff --git a/src/modules/virtual-drive/virtual-file.js b/src/modules/virtual-drive/virtual-file.js deleted file mode 100644 index 4e17c4f..0000000 --- a/src/modules/virtual-drive/virtual-file.js +++ /dev/null @@ -1,6 +0,0 @@ -export class VirtualFile { - constructor(name, extension) { - this.name = name; - this.extension = extension; - } -} \ No newline at end of file diff --git a/src/modules/virtual-drive/virtual-folder.js b/src/modules/virtual-drive/virtual-folder.js deleted file mode 100644 index 1806e5f..0000000 --- a/src/modules/virtual-drive/virtual-folder.js +++ /dev/null @@ -1,98 +0,0 @@ -import { VirtualFile } from "./virtual-file.js"; - -export class VirtualFolder { - static TYPE = { - GENERAL: 0, - MEDIA: 1, - } - - /** - * @param {String} name - * @param {Number} type - */ - constructor(name, type) { - this.name = name; - this.subFolders = []; - this.files = []; - this.type = type ?? this.TYPE.GENERAL; - } - - // add(element) { - - // } - - hasFile(name, extension) { - let exists = false; - - this.files.forEach(({ name: fileName, extension: fileExtension }) => { - if (fileName === name && fileExtension === extension) { - return exists = true; - } - }); - - return exists; - } - - hasFolder(name) { - let exists = false; - - this.folders.forEach(({ name: folderName }) => { - if (folderName === name) { - return exists = true; - } - }); - - return exists; - } - - createFile(name, extension) { - const file = new VirtualFile(name, extension); - this.files.push(file); - file.parent = this; - } - - createFolder(name) { - const newFolder = new VirtualFolder(name); - this.subFolders.push(newFolder); - newFolder.parent = this; - } - - /** - * @param {String} destination - */ - addFile(destination) { - const folderNames = destination.split("/"); - const fileExtendedName = folderNames.pop().split(); - const fileExtendedNameSegments = fileExtendedName.split("."); - - const fileName = fileExtendedNameSegments[0]; - const fileExtension = fileExtendedNameSegments.length > 1 ? fileExtendedNameSegments[1] : "txt"; - - // To do: check if file already exists - - const file = new VirtualFile(fileName, fileExtension); - - const folders = this.addFolder(folderNames.join("/")); - - const parent = folders[folders.length - 1]; - parent.files.push(file); - file.parent = parent; - } - - /** - * @param {String} destination - */ - addFolder(destination) { - if (destination.endsWith("/")) - destination = destination.slice(0, -1); - - const folderNames = destination.split("/"); - - let currentFolder = this; - folderNames.forEach((folderName) => { - if (!currentFolder.hasFolder(folderName)) { - currentFolder.createFolder(folderName); - } - }) - } -} \ No newline at end of file