import { createSignal, createEffect, onMount, For, Show, JSX } from "solid-js"; import { CommandHistoryItem, createTerminalCommands, executeTerminalCommand, CommandContext } from "~/lib/terminal-commands"; import { Btop } from "~/components/Btop"; import { useLocation, useNavigate } from "@solidjs/router"; import { useDarkMode } from "~/context/darkMode"; interface TerminalErrorPageProps { errorContent: JSX.Element; quickActions: JSX.Element; footer: JSX.Element; commandContext?: Partial; disableTerminal?: boolean; } // Safe router hook wrappers that return undefined if outside Route context function useSafeNavigate() { try { return useNavigate(); } catch { return undefined; } } function useSafeLocation() { try { return useLocation(); } catch { return undefined; } } export function TerminalErrorPage(props: TerminalErrorPageProps) { 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; const navigate = useSafeNavigate(); const location = useSafeLocation(); const { isDark } = useDarkMode(); 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, location, addToHistory, openBtop: () => setBtopOpen(true), isDark: isDark, ...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(() => { inputRef?.focus(); }); const quickActions = (
Quick actions:
{props.quickActions}
); return (
inputRef?.focus()} >
{props.errorContent} {quickActions} 0}>
{(item) => (
freno@terminal : ~ $ {item.command}
{item.output}
)}
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 */}
); }