diff --git a/src/components/client/shell/command/list.ts b/src/components/client/shell/command/list.ts index 4e06b88..c504b72 100644 --- a/src/components/client/shell/command/list.ts +++ b/src/components/client/shell/command/list.ts @@ -1,28 +1,47 @@ -import type { JSX } from "react" import { bold } from "../color" import { get_working_dir_name_full, set_working_dir, SetDirStatus } from "../fs/fn" -type args = string[] -type command = JSX.Element | boolean +import create from "../../elements/create" -function parse_ls(entries: JSX.Element[]) { - return
+type Term = HTMLElement +type Args = string[] -
+function strout(term: Term, s: string) { + const p = create("p") + p.innerText = s + term.appendChild(p) } -function cd(args: args): command { +function clear(term: Term, args: Args): boolean { + Array.from(term.children).forEach(node => { + if (node.tagName === "DIV") { + if (node.className === "shell-prompt") { + const input = node.getElementsByClassName("shell-ps1")[0] as HTMLInputElement + if (input.disabled || input.value === "clear") { + node.remove() + } + } else { + node.remove() + } + } else if (node.tagName === "P") { + node.remove() + } + }) + return true +} + +function cd(term: Term, args: Args): boolean { const new_dir_status = set_working_dir(args[1]) if (new_dir_status === SetDirStatus.NotADirectory) { - return

{"cd: \""}{bold(args[1])}{"\" is not a directory"}

+ // return

{"cd: \""}{bold(args[1])}{"\" is not a directory"}

} else if (new_dir_status === SetDirStatus.NotFound) { - return

{"cd: The directory \""}{bold(args[1])}{"\" does not exist"}

+ // return

{"cd: The directory \""}{bold(args[1])}{"\" does not exist"}

} return true } -function ls(args: args): command { +function ls(term: Term, args: Args): boolean { // if (args[1] === undefined) { // for (const dir_name in working_dir) { @@ -32,22 +51,26 @@ function ls(args: args): command { return true } -function pwd(args: args): command { - return

{`${get_working_dir_name_full()}`}

-} - -function cat(args: args): command { +function pwd(term: Term, args: Args): boolean { + strout(term, get_working_dir_name_full()) return true } -interface commands_list { - [index: string]: (args: args) => command +function cat(term: Term, args: Args): boolean { + return true } -const commands: commands_list = { + +type Command = (term: Term, args: Args) => boolean +interface CommandsList { + [index: string]: Command, +} +const commands: CommandsList = { + ["clear"]: clear, ["cd"]: cd, ["ls"]: ls, ["pwd"]: pwd, ["cat"]: cat, } -export default commands \ No newline at end of file +export default commands +export { type Command } \ No newline at end of file diff --git a/src/components/client/shell/command/parse.ts b/src/components/client/shell/command/parse.ts new file mode 100644 index 0000000..99a982d --- /dev/null +++ b/src/components/client/shell/command/parse.ts @@ -0,0 +1,14 @@ +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(" ") +} + +export { + trim, + to_args +} \ No newline at end of file diff --git a/src/components/client/shell/command/run.ts b/src/components/client/shell/command/run.ts index 69fa564..118339e 100644 --- a/src/components/client/shell/command/run.ts +++ b/src/components/client/shell/command/run.ts @@ -1,37 +1,37 @@ -import type { JSX } from "react"; -import commands from "./list"; import { bold } from "../color"; +import { to_args, trim } from "./parse"; -function trim(stdin: string): string { - const trimmed_str: string[] = [] - stdin.split(" ").forEach(s => { if (s !== "") { trimmed_str.push(s) } }) - return trimmed_str.join(" ") -} +import commands, { type Command } from "./list"; +import create from "../../elements/create"; +import history from "../history"; -function to_args(trimmed_str: string): string[] { - return trimmed_str.split(" ") -} +type Term = HTMLElement -function valid_command(args: string[]): JSX.Element | undefined { +function valid_command(term: Term, args: string[]) { for (const command_in_list in commands) { const command = args[0] if (command === command_in_list) { - return commands[command_in_list](args) + return (commands[command_in_list] as Command)(term, args) } } return } function unknown_command(cmd_name: string) { - return

{"shell: Unknown command: "}{bold(cmd_name)}

+ const unknown_element = create("p") + unknown_element.innerText = "shell: Unknown command: " + unknown_element.appendChild(bold(cmd_name)) + return unknown_element } -export default function run(stdin: string) { +export default function run(term: Term, stdin: string) { const args = to_args(trim(stdin)) - const command = valid_command(args) + const valid = valid_command(term, args) + const command = args[0] as string - if (args[0] !== "" && !command) { - return unknown_command(args[0]) + if (command !== "" && !valid) { + return unknown_command(command) } - return command ? command : <> + history.add(args.join(" ")) + return false } \ No newline at end of file diff --git a/src/components/client/shell/events.ts b/src/components/client/shell/events.ts deleted file mode 100644 index 0e66c52..0000000 --- a/src/components/client/shell/events.ts +++ /dev/null @@ -1,35 +0,0 @@ -import Display from "./prompt" -import run from "./command/run" - -const enum Key { - Enter = "Enter", - ArrowUp = "ArrowUp", - ArrowDown = "ArrowDown", - Tab = "Tab" -} - -function keyboard_events(terminal_window: HTMLElement, new_elements_f: newElement) { - const terminal_event = (keyboard_event: KeyboardEvent) => { - if (keyboard_event.key === Key.Enter) { - keyboard_event.preventDefault() - const current_prompt = get_current_prompt() - if (current_prompt) { - const prompt = new_prompt() - const output = run(current_prompt.value) - new_elements_f([output, prompt]) - terminal_window.removeEventListener("keydown", terminal_event) - } - } else if (keyboard_event.key === Key.ArrowUp) { - keyboard_event.preventDefault() - } else if (keyboard_event.key === Key.ArrowDown) { - keyboard_event.preventDefault() - } else if (keyboard_event.key === Key.Tab) { - keyboard_event.preventDefault() - } - } - terminal_window.addEventListener("keydown", terminal_event) -} - -export { - keyboard_events, -} \ No newline at end of file diff --git a/src/components/client/shell/fs/fn.ts b/src/components/client/shell/fs/fn.ts index 4b5ff92..a0b08f5 100644 --- a/src/components/client/shell/fs/fn.ts +++ b/src/components/client/shell/fs/fn.ts @@ -2,7 +2,7 @@ import { Entry, EntryType, fs, type FsEntrySignature } from "./fs" let working_dir = ["/", "home", "user"] -function get_working_dir_name(): string { +function get_working_dir_name() { return working_dir[working_dir.length-1] } @@ -29,7 +29,7 @@ function iter_fs_to_goal(w_dir_clone: string[]): FsIterEntry { for (const w_dir of w_dir_clone) { if (w_dir === "/") { continue } - if (next_iter.inner) { + if (next_iter && next_iter.inner) { const found = next_iter.inner.find(entry => entry.name === w_dir) if (!found) { diff --git a/src/components/client/shell/prompt.ts b/src/components/client/shell/prompt.ts deleted file mode 100644 index 11ca7aa..0000000 --- a/src/components/client/shell/prompt.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { get_working_dir_name } from "./fs/fn" -import { cyan, green } from "./color" - -const userAgent = navigator.userAgent -const browser_name_fallible = userAgent.match(/Firefox.\d+[\d.\d]+|Chrome.\d+[\d.\d]+/gm)?.map(f => f.split("/")[0]) - -let browser_name = "unknown" -if (browser_name_fallible) { - browser_name = browser_name_fallible[0] === "Firefox" ? "gecko" : "chromium" -} - -function working_dir() { - const name = get_working_dir_name() - return name === "user" ? "~" : name -} - -export default function Display() { - const user = cyan("user") - const dir = green(working_dir()) - return

{user}@{browser_name} {dir}{"> "}

-} - -export { userAgent } \ No newline at end of file diff --git a/src/components/client/terminal.ts b/src/components/client/terminal.ts index 10812e5..3ced967 100644 --- a/src/components/client/terminal.ts +++ b/src/components/client/terminal.ts @@ -1,42 +1,91 @@ -// import { red } from "../shell/color" -// import { display_prompt, keyboard_events } from "../shell/events" +import { history_list } from "./shell/history" import prompt from "./elements/prompt" -// import run from "./shell/command/run" +import run from "./shell/command/run" -const terminal_window = document.querySelector("main") +let history_index = 0 +const term_win_unsafe = document.querySelector("main") const enum Key { - Enter = "Enter", - ArrowUp = "ArrowUp", - ArrowDown = "ArrowDown", - Tab = "Tab" -} - -function spawnps1(terminal_window_safe: HTMLElement) { - const ps1prompt = prompt() - terminal_window_safe.appendChild(ps1prompt.body) - input_processor(ps1prompt.input) + Enter = "Enter", + ArrowRight = "ArrowRight", + ArrowUp = "ArrowUp", + ArrowDown = "ArrowDown", + Tab = "Tab" } type InputClosure = (key_event: KeyboardEvent) => void -function key_enter(ps1input: HTMLInputElement, key_event: KeyboardEvent, input_closure: InputClosure) { - key_event.preventDefault() - // run(ps1input.value) - ps1input.removeEventListener("keydown", input_closure) +interface EnterArgs { + readonly term_win_safe: HTMLElement, + readonly ps1input: HTMLInputElement, + readonly closure: InputClosure +} +function key_enter(input: EnterArgs) { + const unknown_command_msg = run(input.term_win_safe, input.ps1input.value) + + if (unknown_command_msg) { + input.term_win_safe.appendChild(unknown_command_msg) + } + input.ps1input.removeEventListener("keydown", input.closure) + spawnps1(input.term_win_safe) } -function input_processor(ps1input: HTMLInputElement) { - const input_closure = (key_event: KeyboardEvent) => { - if (key_event.key === Key.Enter) { - key_enter(ps1input, key_event, input_closure) +function key_up_arrow(ps1input: HTMLInputElement) { + const history_item = history_list[history_index] + if (history_item) { + ps1input.value = history_item + history_index+=1 + } +} + +function key_down_arrow(ps1input: HTMLInputElement) { + const history_item = history_list[history_index] + if (history_item) { + history_index-=1 + if (history_index === -1) { + history_index = 0 + } else { + ps1input.value = history_item } } - ps1input.addEventListener("keydown", input_closure) } -if (terminal_window) { - spawnps1(terminal_window) +function spawnps1(term_win_safe: HTMLElement) { + const ps1prompt = prompt() + term_win_safe.appendChild(ps1prompt.body) + bind_processor(term_win_safe, ps1prompt.input) + history_index = 0 + ps1prompt.input.focus() +} + +function bind_processor(term_win_safe: HTMLElement, ps1prompt_input: HTMLInputElement) { + const input_closure = (key_event: KeyboardEvent) => { + if (key_event.key === Key.Enter) { + key_event.preventDefault() + key_enter({ + term_win_safe: term_win_safe, + ps1input: ps1prompt_input, + closure: input_closure + }) + } else if (key_event.key === Key.Tab) { + key_event.preventDefault() + + } else if (key_event.key === Key.ArrowRight) { + key_event.preventDefault() + + } else if (key_event.key === Key.ArrowUp) { + key_event.preventDefault() + key_up_arrow(ps1prompt_input) + } else if (key_event.key === Key.ArrowDown) { + key_event.preventDefault() + key_down_arrow(ps1prompt_input) + } + } + ps1prompt_input.addEventListener("keydown", input_closure) +} + +if (term_win_unsafe) { + spawnps1(term_win_unsafe) } else { } \ No newline at end of file