import { useState, useEffect, useRef } from "preact/hooks"; import type { JSX } from "preact"; import type { Command } from "../lib/terminal/types"; import { buildFileSystem } from "../lib/terminal/fileSystem"; import { executeCommand, type CommandContext } from "../lib/terminal/commands"; import { getCompletions, formatOutput, saveCommandToHistory, loadCommandHistory, } from "../lib/terminal/utils"; const Terminal = () => { const [currentPath, setCurrentPath] = useState("/"); const [commandHistory, setCommandHistory] = useState([ { input: "", output: 'Welcome to Atridad\'s Shell!\nType "help" to see available commands.\n', timestamp: new Date(), path: "/", }, ]); const [currentInput, setCurrentInput] = useState(""); const [historyIndex, setHistoryIndex] = useState(-1); const [fileSystem, setFileSystem] = useState<{ [key: string]: any }>({}); const [isTrainRunning, setIsTrainRunning] = useState(false); const [trainPosition, setTrainPosition] = useState(100); const [persistentHistory, setPersistentHistory] = useState([]); const inputRef = useRef(null); const terminalRef = useRef(null); useEffect(() => { if (terminalRef.current && !isTrainRunning) { terminalRef.current.scrollTop = terminalRef.current.scrollHeight; } }, [commandHistory, isTrainRunning]); // Load command history from localStorage useEffect(() => { const history = loadCommandHistory(); setPersistentHistory(history); }, []); // Initialize file system useEffect(() => { buildFileSystem().then(setFileSystem); }, []); const handleSubmit = (e: JSX.TargetedEvent) => { e.preventDefault(); const commandContext: CommandContext = { currentPath, fileSystem, setCurrentPath, setIsTrainRunning, setTrainPosition, }; const output = executeCommand(currentInput, commandContext); const newCommand: Command = { input: currentInput, output, timestamp: new Date(), path: currentPath, }; // Save command to persistent history const updatedHistory = saveCommandToHistory( currentInput, persistentHistory, ); setPersistentHistory(updatedHistory); if (currentInput.trim().toLowerCase() === "clear") { setCommandHistory([]); } else { setCommandHistory((prev: Command[]) => [...prev, newCommand]); } setCurrentInput(""); setHistoryIndex(-1); }; const handleKeyDown = (e: JSX.TargetedKeyboardEvent) => { if (e.key === "Tab") { e.preventDefault(); const { completion, replaceFrom } = getCompletions( currentInput, currentPath, fileSystem, ); if (completion) { const beforeReplacement = currentInput.substring(0, replaceFrom); const newInput = beforeReplacement + completion; setCurrentInput(newInput + (completion.endsWith("/") ? "" : " ")); } } else if (e.key === "ArrowUp") { e.preventDefault(); if (persistentHistory.length > 0) { const newIndex = historyIndex === -1 ? persistentHistory.length - 1 : Math.max(0, historyIndex - 1); setHistoryIndex(newIndex); setCurrentInput(persistentHistory[newIndex]); } } else if (e.key === "ArrowDown") { e.preventDefault(); if (historyIndex !== -1) { const newIndex = Math.min( persistentHistory.length - 1, historyIndex + 1, ); if ( newIndex === persistentHistory.length - 1 && historyIndex === newIndex ) { setHistoryIndex(-1); setCurrentInput(""); } else { setHistoryIndex(newIndex); setCurrentInput(persistentHistory[newIndex]); } } } }; return (
guest@atri.dad: {currentPath}
!isTrainRunning && inputRef.current?.focus()} >
{commandHistory.map((command: Command, index: number) => (
{command.input && (
guest@atri.dad : {command.path} $ {command.input}
)} {command.output && (
)}
))} {!isTrainRunning && (
guest@atri.dad : {currentPath} $ setCurrentInput((e.target as HTMLInputElement).value) } onKeyDown={handleKeyDown} className="flex-1 bg-transparent border-none outline-none text-accent ml-1" spellcheck={false} />
)}
{/* Train animation overlay - positioned over the content area but outside the opacity div */} {isTrainRunning && (
{`
      ====        ________                ___________
  _D _|  |_______/        \\__I_I_____===__|_________|
   |(_)---  |   H\\________/ |   |        =|___ ___|      _________________
   /     |  |   H  |  |     |   |         ||_| |_||     _|                \\_____A
  |      |  |   H  |__--------------------| [___] |   =|                        |
  | ________|___H__/__|_____/[][]~\\_______|       |   -|                        |
  |/ |   |-----------I_____I [][] []  D   |=======|____|________________________|_
__/ =| o |=-O=====O=====O=====O \\ ____Y___________|__|__________________________|_
 |/-=|___|=    ||    ||    ||    |_____/~\\___/          |_D__D__D_|  |_D__D__D_|
  \\_/      \\__/  \\__/  \\__/  \\__/      \\_/               \\_/   \\_/    \\_/   \\_/`}
)}
); }; export default Terminal;