diff --git a/src/components/react/fs.ts b/src/components/react/fs.ts deleted file mode 100644 index c2f8d43..0000000 --- a/src/components/react/fs.ts +++ /dev/null @@ -1,33 +0,0 @@ -let working_dir = "user" - -const enum EntryType { - Directory, - File -} - -type File = string -type Entry = { - readonly inner: T, - readonly type: EntryType -} -function Entry(inner: T): Entry { - const type = typeof inner == "object" ? EntryType.Directory : EntryType.File - return { inner: inner, type: type } -} - -const user = { - ["about_me.txt"]: Entry(""), - ["services.txt"]: Entry("") -} -const home = { - ["user"]: Entry(user) -} -const root = { - ["bin"]: Entry({}), - ["home"]: Entry(home) -} -const fs = { - ["/"]: Entry(root) -} - -export { fs, working_dir } \ No newline at end of file diff --git a/src/components/react/shell/command/list.ts b/src/components/react/shell/command/list.ts index 73dad36..a64ceb4 100644 --- a/src/components/react/shell/command/list.ts +++ b/src/components/react/shell/command/list.ts @@ -1,21 +1,26 @@ -import { working_dir } from "../../fs" +import { working_dir } from "../fs" -function ls() { +type args = string[] + +function ls(args: args) { + console.log(args) +} + +function pwd(args: args) { } -function pwd() { +function cat(args: args) { } -function cat() { - +interface commands_list { + [index: string]: (args: args) => void } - -const commands = { +const commands: commands_list = { ["ls"]: ls, ["pwd"]: pwd, ["cat"]: cat, } -export { commands } \ No newline at end of file +export default commands \ No newline at end of file diff --git a/src/components/react/shell/command/parse.ts b/src/components/react/shell/command/parse.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/react/shell/command/run.tsx b/src/components/react/shell/command/run.tsx new file mode 100644 index 0000000..52c0396 --- /dev/null +++ b/src/components/react/shell/command/run.tsx @@ -0,0 +1,41 @@ +import type { JSX } from "react"; +import type { Root } from "react-dom/client"; +import { type newElement } from "../../terminal/exec"; + +import commands from "./list"; + +type Renderer = React.Dispatch> + +function trim(stdin: string): string { + const trimmed_str: string[] = [] + stdin.split(" ").forEach(s => { if (s != "") { trimmed_str.push(s) } }) + return trimmed_str.join(" ") +} + +function to_args(trimmed_str: string): string[] { + return trimmed_str.split(" ") +} + +function valid_command(args: string[]): boolean { + for (const command_in_list in commands) { + const command = args[0] + if (command === command_in_list) { + commands[command_in_list](args) + return true + } + } + return false +} + +function unknown_command(name: string) { + return

{`sh: Unknown command: ${name}`}

+} + +export default function run(stdin: string) { + const args = to_args(trim(stdin)) + + if (args[0] !== "" && !valid_command(args)) { + return unknown_command(args[0]) + } + return <> +} \ No newline at end of file diff --git a/src/components/react/shell/events.tsx b/src/components/react/shell/events.tsx index 28251f6..9f013ed 100644 --- a/src/components/react/shell/events.tsx +++ b/src/components/react/shell/events.tsx @@ -1,9 +1,9 @@ -import { useState } from "react" -import type { JSX } from "react/jsx-dev-runtime" - import Display from "./prompt" +import run from "./command/run" + +import { type newElement } from "../terminal/exec"; +import type { JSX } from "react"; -type SetStateAction = React.Dispatch> const enum Key { Enter = "Enter", ArrowUp = "ArrowUp", @@ -12,32 +12,36 @@ const enum Key { } function display_prompt() { - return <> + return
- +
} function get_current_prompt(): HTMLInputElement | undefined { const shell_input = document.getElementsByClassName("shell-ps1") + return shell_input[shell_input.length-1] as HTMLInputElement } -function new_prompt([existingPrompts, setPrompt]: [JSX.Element[], SetStateAction]) { +function new_prompt(): JSX.Element { const shell_prompts = document.getElementsByClassName("shell-ps1") + Array.from(shell_prompts).forEach(shellps1 => { (shellps1 as HTMLInputElement).disabled = true }) - setPrompt([...existingPrompts, display_prompt()]) + return display_prompt() } -function keyboard_event(terminal: HTMLElement, existingPrompts: JSX.Element[], setPrompt: SetStateAction) { +function keyboard_events(terminal_window: HTMLElement, new_element_f: newElement) { const terminal_event = (keyboard_event: KeyboardEvent) => { if (keyboard_event.key === Key.Enter) { const current_prompt = get_current_prompt() if (current_prompt) { - new_prompt([existingPrompts, setPrompt]) - terminal.removeEventListener("keydown", terminal_event) + const prompt = new_prompt() + const output = run(current_prompt.value) + new_element_f([output, prompt]) + terminal_window.removeEventListener("keydown", terminal_event) } } else if (keyboard_event.key === Key.ArrowUp) { @@ -47,10 +51,10 @@ function keyboard_event(terminal: HTMLElement, existingPrompts: JSX.Element[], s } } - terminal.addEventListener("keydown", terminal_event) + terminal_window.addEventListener("keydown", terminal_event) } export { - keyboard_event, + keyboard_events, display_prompt } \ No newline at end of file diff --git a/src/components/react/shell/fs.ts b/src/components/react/shell/fs.ts new file mode 100644 index 0000000..56006b6 --- /dev/null +++ b/src/components/react/shell/fs.ts @@ -0,0 +1,46 @@ +let working_dir = "user" + +const enum EntryType { + Directory, + File +} +const enum Permissions { + r, + w, + rw +} + +type File = string +type Entry = { + readonly inner: T, + readonly type: EntryType, + readonly permissions: Permissions +} +function Entry(inner: T, permissions: Permissions): Entry { + const type = typeof inner == "object" ? EntryType.Directory : EntryType.File + return { + inner: inner, + type: type, + permissions: permissions + } +} + +const user = { + ["about_me.txt"]: Entry("", Permissions.rw), + ["services.txt"]: Entry("", Permissions.rw) +} +const home = { + ["user"]: Entry(user, Permissions.rw) +} +const root = { + ["bin"]: Entry({}, Permissions.r), + ["home"]: Entry(home, Permissions.r) +} +const fs = { + ["/"]: Entry(root, Permissions.r) +} + +export { + fs, + working_dir +} \ No newline at end of file diff --git a/src/components/react/shell/prompt.tsx b/src/components/react/shell/prompt.tsx index 4e43e82..cd2845a 100644 --- a/src/components/react/shell/prompt.tsx +++ b/src/components/react/shell/prompt.tsx @@ -1,4 +1,4 @@ -import { working_dir } from "../fs" +import { working_dir } from "./fs" import { cyan, green } from "./color" const userAgent = navigator.userAgent diff --git a/src/components/react/terminal/exec.tsx b/src/components/react/terminal/exec.tsx index c344e1c..5b4da0e 100644 --- a/src/components/react/terminal/exec.tsx +++ b/src/components/react/terminal/exec.tsx @@ -1,7 +1,8 @@ -import { useState } from "react" +import { useState, type JSX } from "react" import { red } from "../shell/color" +import { display_prompt, keyboard_events } from "../shell/events" -import { display_prompt, keyboard_event } from "../shell/events" +import React from "react" const terminal_window = document.querySelector("main") @@ -14,14 +15,21 @@ function panic(message: string) { } +type newElement = (elements: JSX.Element[]) => void + export default function Shell() { if (terminal_window) { - const [existingPrompts, setPrompt] = useState([display_prompt()]) - keyboard_event(terminal_window, existingPrompts, setPrompt) + const [renderedElements, renderElement] = useState([display_prompt()]) + const new_element_f = (elements: JSX.Element[]) => renderElement([...renderedElements, ...elements]) - return existingPrompts.map((ps1, k) =>
{ps1}
) + keyboard_events(terminal_window, new_element_f) + + return renderedElements.map((element, k) => {element}) } return panic("The
element is missing") } -export { panic } \ No newline at end of file +export { + panic, + type newElement, +} \ No newline at end of file diff --git a/src/pages/index.astro b/src/pages/index.astro index b464060..398a355 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -7,7 +7,7 @@ import Terminal from '../components/react/terminal/exec';
- +