import { createSignal, createEffect, onMount, onCleanup, For, Show, JSX } from "solid-js"; import { CommandHistoryItem, createTerminalCommands, executeTerminalCommand, CommandContext } from "~/lib/terminal-commands"; import { Btop } from "~/components/Btop"; interface TerminalErrorPageProps { glitchText: string; glitchChars: string; glitchSpeed?: number; glitchThreshold?: number; glitchIntensity?: number; navigate: (path: string) => void; location: { pathname: string }; errorContent: JSX.Element; quickActions: JSX.Element; footer: JSX.Element; onGlitchTextChange?: (text: string) => void; commandContext?: Partial; } export function TerminalErrorPage(props: TerminalErrorPageProps) { const [glitchText, setGlitchText] = createSignal(props.glitchText); const [command, setCommand] = createSignal(""); const [history, setHistory] = createSignal([]); const [historyIndex, setHistoryIndex] = createSignal(-1); const [btopOpen, setBtopOpen] = createSignal(false); let inputRef: HTMLInputElement | undefined; let footerRef: HTMLDivElement | undefined; // Auto-scroll to bottom when history changes createEffect(() => { if (history().length > 0) { setTimeout(() => { footerRef?.scrollIntoView({ behavior: "smooth", block: "end" }); }, 0); } }); const addToHistory = ( cmd: string, output: string, type: "success" | "error" | "info" ) => { if (cmd === "clear") { setHistory([]); } else { setHistory([...history(), { command: cmd, output, type }]); } }; const commands = createTerminalCommands({ navigate: props.navigate, location: props.location, addToHistory, openBtop: () => setBtopOpen(true), ...props.commandContext }); const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Enter") { executeTerminalCommand(command(), commands, addToHistory); setCommand(""); setHistoryIndex(-1); } else if (e.key === "ArrowUp") { e.preventDefault(); const allCommands = history().map((h) => h.command); if (allCommands.length > 0) { const newIndex = historyIndex() === -1 ? allCommands.length - 1 : Math.max(0, historyIndex() - 1); setHistoryIndex(newIndex); setCommand(allCommands[newIndex]); } } else if (e.key === "ArrowDown") { e.preventDefault(); const allCommands = history().map((h) => h.command); if (historyIndex() !== -1) { const newIndex = Math.min(allCommands.length - 1, historyIndex() + 1); setHistoryIndex(newIndex); setCommand(allCommands[newIndex]); } } else if (e.key === "Tab") { e.preventDefault(); const typed = command().toLowerCase(); const matches = Object.keys(commands).filter((cmd) => cmd.startsWith(typed) ); if (matches.length === 1) { setCommand(matches[0]); } else if (matches.length > 1) { addToHistory(command(), matches.join(" "), "info"); } } else if (e.key === "l" && e.ctrlKey) { e.preventDefault(); setHistory([]); } }; onMount(() => { const originalText = props.glitchText; const glitchChars = props.glitchChars; const glitchSpeed = props.glitchSpeed || 300; const glitchThreshold = props.glitchThreshold || 0.85; const glitchIntensity = props.glitchIntensity || 0.7; const glitchInterval = setInterval(() => { if (Math.random() > glitchThreshold) { let glitched = ""; for (let i = 0; i < originalText.length; i++) { if (Math.random() > glitchIntensity) { glitched += glitchChars[Math.floor(Math.random() * glitchChars.length)]; } else { glitched += originalText[i]; } } setGlitchText(glitched); props.onGlitchTextChange?.(glitched); setTimeout(() => { setGlitchText(originalText); props.onGlitchTextChange?.(originalText); }, 100); } }, glitchSpeed); inputRef?.focus(); onCleanup(() => { clearInterval(glitchInterval); }); }); return (
inputRef?.focus()} > {/* Scanline effect */}
{/* Main content */}
{/* Terminal header */} {/* Error Content - passed as prop */} {props.errorContent} {/* Quick Actions - passed as prop */} {props.quickActions} {/* Command history */} 0}>
{(item) => (
freno@terminal : ~ $ {item.command}
{item.output}
)}
{/* Interactive input */}
freno@terminal : ~ $ setCommand(e.currentTarget.value)} onKeyDown={handleKeyDown} class="text-text caret-text ml-1 flex-1 border-none bg-transparent outline-none" autocomplete="off" autocapitalize="off" spellcheck={false} />
{/* Footer */}
{props.footer}
{/* Btop overlay */} setBtopOpen(false)} /> {/* Custom styles */}
); }