import { useState, useEffect, useRef } from "react"; import { fileSystem } from "../data/filesystem"; export default function Terminal() { const [currentPath, setCurrentPath] = useState("/home/guest"); const [commandHistory, setCommandHistory] = useState([]); const [inputValue, setInputValue] = useState(""); const inputRef = useRef(null); const terminalRef = useRef(null); const [hasBeenCleared, setHasBeenCleared] = useState(false); // Focus the input when the component mounts and on click useEffect(() => { inputRef.current.focus(); }, []); useEffect(() => { // Make sure input is focused after render if (inputRef.current) { inputRef.current.focus(); } }, [commandHistory]); // Re-focus after command history changes const focusInput = () => { inputRef.current.focus(); }; const handleKeyDown = (e) => { console.log("Key pressed:", e.key, "Input value:", inputValue); // Add debugging if (e.key === "Enter") { e.preventDefault(); e.stopPropagation(); // Stop event propagation console.log("Enter pressed, executing command:", inputValue); executeCommand(); return false; // Ensure the event is fully handled } else if (e.key === "Tab") { e.preventDefault(); handleTabCompletion(); } else if (e.key === "l" && e.ctrlKey) { e.preventDefault(); handleClear(); setInputValue(""); return; } }; const handleContainerKeyDown = (e) => { if (e.key === "Enter" && document.activeElement === inputRef.current) { e.preventDefault(); executeCommand(); } }; const handleClear = () => { setCommandHistory([]); setHasBeenCleared(true); // Return focus to input setTimeout(() => { if (inputRef.current) { inputRef.current.focus(); } }, 10); return ""; }; // Path resolution functions const resolvePath = (path) => { // If it's an absolute path, start from root if (path.startsWith("/")) { return path; } // If it's ".", return current path if (path === ".") { return currentPath; } // If it's "..", go up one level if (path === "..") { const pathParts = currentPath.split("/").filter(Boolean); if (pathParts.length === 0) { return "/"; } pathParts.pop(); return "/" + pathParts.join("/"); } // Handle relative paths - append to current path if (currentPath.endsWith("/")) { return currentPath + path; } return currentPath + "/" + path; }; const getItemAtPath = (path) => { const normalizedPath = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path; if (normalizedPath === "/") { return fileSystem["/"]; } const pathParts = normalizedPath.split("/").filter(Boolean); let current = fileSystem["/"]; for (const part of pathParts) { if (!current || current.type !== "directory" || !current.children[part]) { return null; } current = current.children[part]; } return current; }; // Handle tab completion const handleTabCompletion = () => { if (!inputValue.trim()) return; const parts = inputValue.trim().split(" "); const command = parts[0].toLowerCase(); // If completing a command if (parts.length === 1) { const commands = [ "ls", "cd", "cat", "pwd", "date", "help", "clear", "view", ]; const matchingCommands = commands.filter((cmd) => cmd.startsWith(command) ); if (matchingCommands.length === 1) { setInputValue(matchingCommands[0]); } return; } // If completing a path/file/directory if (["cd", "cat", "ls", "view"].includes(command)) { const partialPath = parts[parts.length - 1]; const suggestions = getPathSuggestions(partialPath); if (suggestions.length === 1) { const completedParts = [...parts]; completedParts[completedParts.length - 1] = suggestions[0]; setInputValue(completedParts.join(" ")); } } }; // Get path suggestions based on current directory const getPathSuggestions = (partialPath) => { const dirPath = partialPath.includes("/") ? partialPath.substring(0, partialPath.lastIndexOf("/") + 1) : ""; const prefix = partialPath.includes("/") ? partialPath.substring(partialPath.lastIndexOf("/") + 1) : partialPath; const resolvedDirPath = resolvePath(dirPath || currentPath); const dir = getItemAtPath(resolvedDirPath); if (!dir || dir.type !== "directory") return []; return Object.keys(dir.children) .filter((name) => name.startsWith(prefix)) .map((name) => { const fullPath = dirPath + name; if (dir.children[name].type === "directory") { return fullPath + "/"; } return fullPath; }); }; // Process and execute commands const executeCommand = () => { if (!inputValue.trim()) return; // Create a new history entry with the CURRENT PATH at execution time const newHistoryEntry = { command: inputValue, output: "", path: currentPath, // Store the path at execution time }; const parts = inputValue.trim().split(/\s+/); const command = parts[0].toLowerCase(); const args = parts.slice(1); let output = ""; switch (command) { case "ls": output = handleLs(args); break; case "cd": output = handleCd(args); break; case "cat": output = handleCat(args); break; case "view": output = handleView(args); break; case "pwd": output = currentPath; break; case "date": output = new Date().toLocaleString(); break; case "help": output = "Available commands:\n" + " ls - List directory contents\n" + " cd [directory] - Change directory\n" + " cat [file] - Display file contents\n" + " view [image] - Display image files\n" + " pwd - Print working directory\n" + " clear - Clear the terminal\n" + " help - Display this help message\n\n" + "Shortcuts:\n" + " Tab - Autocomplete commands and paths\n" + " Ctrl+L - Clear the terminal"; break; case "clear": clearTerminal(); setInputValue(""); return; default: output = `Command not found: ${command}`; } newHistoryEntry.output = output; setCommandHistory([...commandHistory, newHistoryEntry]); setInputValue(""); // Auto-scroll to bottom and re-focus input after rendering setTimeout(() => { if (terminalRef.current) { terminalRef.current.scrollTop = terminalRef.current.scrollHeight; } if (inputRef.current) { inputRef.current.focus(); } }, 50); }; // Handle ls command const handleLs = (args) => { let targetPath = currentPath; if (args.length > 0) { // Handle path argument targetPath = resolvePath(args[0]); } const item = getItemAtPath(targetPath); if (!item) { return `ls: ${args[0]}: No such file or directory`; } if (item.type !== "directory") { return `ls: ${args[0]}: Not a directory`; } const children = Object.keys(item.children).sort(); if (children.length === 0) { return "Empty directory"; } return children .map((name) => { const child = item.children[name]; const itemClass = child.type === "directory" ? "directory" : child.type === "image" ? "image" : "file"; return `
{`
_ _ ____ ____ _ ____ ___ _ _ _____ __ __ _____
| \\ | | _ \\ / ___| / \\ | __ ) / _ \\| | | |_ _| | \\/ | ____|
| \\| | |_) | | / _ \\ | _ \\| | | | | | | | | | |\\/| | _|
| |\\ | __/| |___ / ___ \\| |_) | |_| | |_| | | | | | | | |___
|_| \\_|_| \\____| /_/ \\_\\____/ \\___/ \\___/ |_| |_| |_|_____|
`}