Added virtual drive + terminal directory commands
This commit is contained in:
parent
1f70f610f7
commit
f11820436b
20 changed files with 347 additions and 152 deletions
15
src/App.js
15
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 (
|
||||
<WindowsManagerProvider>
|
||||
<div className="App">
|
||||
<Taskbar/>
|
||||
<WindowsView/>
|
||||
</div>
|
||||
</WindowsManagerProvider>
|
||||
<VirtualRootProvider>
|
||||
<WindowsManagerProvider>
|
||||
<div className="App">
|
||||
<Taskbar/>
|
||||
<WindowsView/>
|
||||
</div>
|
||||
</WindowsManagerProvider>
|
||||
</VirtualRootProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<span className={styles.Input}>
|
||||
{prefix && <p className={[styles.Prefix]}>{prefix}</p>}
|
||||
<label for="input"/>
|
||||
<label htmlFor="input"/>
|
||||
<input
|
||||
id="input"
|
||||
value={value}
|
||||
|
|
@ -22,6 +21,7 @@ function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown }) {
|
|||
onKeyUp={onKeyUp}
|
||||
onKeyDown={onKeyDown}
|
||||
spellCheck={false}
|
||||
autoComplete={null}
|
||||
autoFocus
|
||||
/>
|
||||
</span>
|
||||
|
|
@ -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()}
|
||||
<InputLine
|
||||
value={inputValue}
|
||||
prefix={PREFIX}
|
||||
prefix={prefix}
|
||||
onKeyDown={onKeyDown}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
.Prefix {
|
||||
width: max-content;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.Input, .Output {
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
})
|
||||
]
|
||||
}
|
||||
61
src/features/virtual-drive/virtual-base.js
Normal file
61
src/features/virtual-drive/virtual-base.js
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
12
src/features/virtual-drive/virtual-file.js
Normal file
12
src/features/virtual-drive/virtual-file.js
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
159
src/features/virtual-drive/virtual-folder.js
Normal file
159
src/features/virtual-drive/virtual-folder.js
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/features/virtual-drive/virtual-root.js
Normal file
21
src/features/virtual-drive/virtual-root.js
Normal file
|
|
@ -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 "";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
import { createContext, useContext } from "react";
|
||||
import { VirtualDrive } from "../modules/virtual-drive/virtual-drive.js";
|
||||
|
||||
const VirtualDriveContext = createContext();
|
||||
|
||||
/**
|
||||
* @returns {React.Provider<any>}
|
||||
*/
|
||||
export function VirtualDriveProvider({ children }) {
|
||||
const virtualDrive = new VirtualDrive();
|
||||
|
||||
return (
|
||||
<VirtualDriveContext.Provider value={virtualDrive}>
|
||||
{children}
|
||||
</VirtualDriveContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {VirtualDrive}
|
||||
*/
|
||||
export function useWindowsManager() {
|
||||
return useContext(VirtualDriveContext);
|
||||
}
|
||||
36
src/hooks/VirtualRootContext.js
Normal file
36
src/hooks/VirtualRootContext.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { createContext, useContext } from "react";
|
||||
import { VirtualRoot } from "../features/virtual-drive/virtual-root.js";
|
||||
|
||||
const VirtualRootContext = createContext();
|
||||
|
||||
/**
|
||||
* @returns {React.Provider<VirtualRoot>}
|
||||
*/
|
||||
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 (
|
||||
<VirtualRootContext.Provider value={virtualRoot}>
|
||||
{children}
|
||||
</VirtualRootContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {VirtualRoot}
|
||||
*/
|
||||
export function useVirtualRoot() {
|
||||
return useContext(VirtualRootContext);
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import { VirtualFolder } from "./virtual-folder.js";
|
||||
|
||||
export class VirtualDrive extends VirtualFolder {
|
||||
constructor() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
export class VirtualFile {
|
||||
constructor(name, extension) {
|
||||
this.name = name;
|
||||
this.extension = extension;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue