3 Commits

Author SHA1 Message Date
acf460772c the prompt now displays and works with unknown command 2025-02-06 20:51:25 -05:00
273f748ff1 create commands folder 2025-02-05 17:49:46 -05:00
0303bb733a the prompt now works properly 2025-02-05 17:49:00 -05:00
12 changed files with 176 additions and 108 deletions

View File

@ -7,7 +7,7 @@
"@astrojs/react": "^4.2.0", "@astrojs/react": "^4.2.0",
"@types/react": "^19.0.8", "@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3", "@types/react-dom": "^19.0.3",
"astro": "^5.2.3", "astro": "^5.2.5",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"sass": "^1.83.4", "sass": "^1.83.4",

View File

@ -1,21 +0,0 @@
import { working_dir } from "./fs"
function ls() {
}
function pwd() {
}
function cat() {
}
const commands = {
["ls"]: ls,
["pwd"]: pwd,
["cat"]: cat,
}
export { commands }

View File

@ -1,33 +0,0 @@
let working_dir = "user"
const enum EntryType {
Directory,
File
}
type File = string
type Entry<T> = {
readonly inner: T,
readonly type: EntryType
}
function Entry<T = File>(inner: T): Entry<T> {
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 }

View File

@ -0,0 +1,26 @@
import { working_dir } from "../fs"
type args = string[]
function ls(args: args) {
console.log(args)
}
function pwd(args: args) {
}
function cat(args: args) {
}
interface commands_list {
[index: string]: (args: args) => void
}
const commands: commands_list = {
["ls"]: ls,
["pwd"]: pwd,
["cat"]: cat,
}
export default commands

View File

@ -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<T> = React.Dispatch<React.SetStateAction<T>>
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 <p>{`sh: Unknown command: ${name}`}</p>
}
export default function run(stdin: string) {
const args = to_args(trim(stdin))
if (args[0] !== "" && !valid_command(args)) {
return unknown_command(args[0])
}
return <></>
}

View File

@ -1,9 +1,9 @@
import { useState } from "react"
import type { JSX } from "react/jsx-dev-runtime"
import Display from "./prompt" import Display from "./prompt"
import run from "./command/run"
import { type newElement } from "../terminal/exec";
import type { JSX } from "react";
type SetStateAction<T> = React.Dispatch<React.SetStateAction<T>>
const enum Key { const enum Key {
Enter = "Enter", Enter = "Enter",
ArrowUp = "ArrowUp", ArrowUp = "ArrowUp",
@ -11,25 +11,38 @@ const enum Key {
Tab = "Tab" Tab = "Tab"
} }
function DisplayPrompt() { function display_prompt() {
return <> return <div className="shell-prompt">
<Display/> <Display/>
<input className="shell-ps1" type="text" spellCheck={false}/> <input className="shell-ps1" type="text" spellCheck={false}/>
</> </div>
} }
function Prompt([existingPrompts, setPrompt]: [JSX.Element[], SetStateAction<JSX.Element[]>]) { 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(): JSX.Element {
const shell_prompts = document.getElementsByClassName("shell-ps1") const shell_prompts = document.getElementsByClassName("shell-ps1")
Array.from(shell_prompts).forEach((shellps1) => {
Array.from(shell_prompts).forEach(shellps1 => {
(shellps1 as HTMLInputElement).disabled = true (shellps1 as HTMLInputElement).disabled = true
}) })
setPrompt([...existingPrompts, DisplayPrompt()]) return display_prompt()
} }
function keyboard_stream(terminal_window: HTMLElement, existingPrompts: JSX.Element[], setPrompt: SetStateAction<JSX.Element[]>) { function keyboard_events(terminal_window: HTMLElement, new_element_f: newElement) {
terminal_window.addEventListener("keydown", keyboard_event => { const terminal_event = (keyboard_event: KeyboardEvent) => {
if (keyboard_event.key === Key.Enter) { if (keyboard_event.key === Key.Enter) {
Prompt([existingPrompts, setPrompt]) const current_prompt = get_current_prompt()
if (current_prompt) {
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) { } else if (keyboard_event.key === Key.ArrowUp) {
} else if (keyboard_event.key === Key.ArrowDown) { } else if (keyboard_event.key === Key.ArrowDown) {
@ -37,18 +50,11 @@ function keyboard_stream(terminal_window: HTMLElement, existingPrompts: JSX.Elem
} else if (keyboard_event.key === Key.Tab) { } else if (keyboard_event.key === Key.Tab) {
} }
})
}
function Events() {
const terminal_window = document.querySelector("main")
if (terminal_window) {
const [existingPrompts, setPrompt] = useState([DisplayPrompt()])
keyboard_stream(terminal_window, existingPrompts, setPrompt)
return existingPrompts
} }
return terminal_window.addEventListener("keydown", terminal_event)
} }
export default Events export {
keyboard_events,
display_prompt
}

View File

@ -0,0 +1,46 @@
let working_dir = "user"
const enum EntryType {
Directory,
File
}
const enum Permissions {
r,
w,
rw
}
type File = string
type Entry<T> = {
readonly inner: T,
readonly type: EntryType,
readonly permissions: Permissions
}
function Entry<T = File>(inner: T, permissions: Permissions): Entry<T> {
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
}

View File

@ -1,9 +1,13 @@
import { working_dir } from "../fs" import { working_dir } from "./fs"
import { cyan, green } from "./color" import { cyan, green } from "./color"
const userAgent = navigator.userAgent const userAgent = navigator.userAgent
const browser_name_fallible = userAgent.match(/Firefox.\d+[\d.\d]+|Chrome.\d+[\d.\d]+/gm)?.map(f => f.split("/")[0]) const browser_name_fallible = userAgent.match(/Firefox.\d+[\d.\d]+|Chrome.\d+[\d.\d]+/gm)?.map(f => f.split("/")[0])
const browser_name = browser_name_fallible ? browser_name_fallible[0].toLowerCase() : "unknown"
let browser_name = "unknown"
if (browser_name_fallible) {
browser_name = browser_name_fallible[0] === "Firefox" ? "gecko" : "chromium"
}
function GetWorkingDir() { function GetWorkingDir() {
return working_dir === "user" ? "~" : working_dir return working_dir === "user" ? "~" : working_dir

View File

@ -1,10 +0,0 @@
const terminal_window = document.querySelector("main");
export function TermEvents() {
if (terminal_window) {
// terminal_window.addEventListener("click", (_event) => {
// const shell_input = document.getElementById("shell-input")
// if (shell_input) { shell_input.focus() }
// })
}
}

View File

@ -1,9 +1,12 @@
import { TermEvents } from "./events" import { useState, type JSX } from "react"
import { red } from "../shell/color" import { red } from "../shell/color"
import { display_prompt, keyboard_events } from "../shell/events"
import Events from "../shell/events" import React from "react"
function Panic(message: string) { const terminal_window = document.querySelector("main")
function panic(message: string) {
return <> return <>
<p>{red("=================================================")}</p> <p>{red("=================================================")}</p>
<p>{red("An unexpected JavaScript error occured:")}</p> <p>{red("An unexpected JavaScript error occured:")}</p>
@ -12,10 +15,21 @@ function Panic(message: string) {
</> </>
} }
type newElement = (elements: JSX.Element[]) => void
export default function Shell() { export default function Shell() {
const existingPrompts = Events() if (terminal_window) {
if (existingPrompts) { const [renderedElements, renderElement] = useState([display_prompt()])
return existingPrompts.map((ps1, k) => <div className="shell-prompt" key={k}>{ps1}</div>) const new_element_f = (elements: JSX.Element[]) => renderElement([...renderedElements, ...elements])
keyboard_events(terminal_window, new_element_f)
return renderedElements.map((element, k) => <React.Fragment key={k}>{element}</React.Fragment>)
} }
return Panic("The <main> element is missing") return panic("The <main> element is missing")
}
export {
panic,
type newElement,
} }

View File

@ -2,11 +2,6 @@
import Metas from "../components/metas.astro" import Metas from "../components/metas.astro"
import Header from "../components/header.astro" import Header from "../components/header.astro"
import Footer from "../components/footer.astro" import Footer from "../components/footer.astro"
interface Props {
title: string,
}
const {title} = Astro.props
--- ---
<!doctype html> <!doctype html>
@ -14,10 +9,10 @@ const {title} = Astro.props
<head> <head>
<Metas/> <Metas/>
<link rel="icon" type="image/png" href="/logo.png"/> <link rel="icon" type="image/png" href="/logo.png"/>
<title>{`rhpidfyre.io | ${title}`}</title> <title>rhpidfyre.io</title>
</head> </head>
<body> <body>
<Header title={title}/> <Header/>
<slot/> <slot/>
<Footer/> <Footer/>
</body> </body>

View File

@ -4,10 +4,10 @@ import Motd from '../components/terminal/motd.astro';
import Terminal from '../components/react/terminal/exec'; import Terminal from '../components/react/terminal/exec';
--- ---
<Webpage title="Home"> <Webpage>
<main> <main>
<Motd/> <Motd/>
<Terminal client:only/> <Terminal client:only="react"/>
</main> </main>
</Webpage> </Webpage>