From cde5f34858984f19fb04ce8138cbc32919dd0ce8 Mon Sep 17 00:00:00 2001 From: JorySeverijnse Date: Sun, 21 Dec 2025 13:57:47 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=85=20Command=20History=20-=20Up/Down=20A?= =?UTF-8?q?rrow=20Navigation:=20Users=20can=20now=20press=20=E2=86=91/?= =?UTF-8?q?=E2=86=93=20to=20navigate=20through=20previously=20executed=20c?= =?UTF-8?q?ommands=20-=20History=20Storage:=20Commands=20are=20stored=20in?= =?UTF-8?q?=20state=20and=20persist=20during=20the=20session=20-=20Duplica?= =?UTF-8?q?te=20Prevention:=20Avoids=20adding=20the=20same=20command=20mul?= =?UTF-8?q?tiple=20times=20consecutively=20-=20Reset=20on=20Typing:=20Manu?= =?UTF-8?q?al=20typing=20resets=20the=20history=20navigation=20position=20?= =?UTF-8?q?=E2=9C=85=20Tab=20Completion=20-=20Auto-complete:=20Pressing=20?= =?UTF-8?q?Tab=20completes=20partial=20commands=20that=20match=20available?= =?UTF-8?q?=20commands=20-=20Single=20Match:=20If=20only=20one=20command?= =?UTF-8?q?=20matches,=20it=20completes=20the=20full=20command=20-=20Multi?= =?UTF-8?q?ple=20Matches:=20If=20multiple=20commands=20match,=20displays?= =?UTF-8?q?=20all=20possible=20completions=20in=20the=20terminal=20output?= =?UTF-8?q?=20-=20Case-insensitive:=20Works=20regardless=20of=20input=20ca?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TerminalCommand.tsx | 90 +++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/src/components/TerminalCommand.tsx b/src/components/TerminalCommand.tsx index 92deef0..b2073a8 100644 --- a/src/components/TerminalCommand.tsx +++ b/src/components/TerminalCommand.tsx @@ -65,6 +65,8 @@ const TerminalCommand = () => { const [isOpen, setIsOpen] = useState(false); const [input, setInput] = useState(''); const [output, setOutput] = useState(['Type /help for available commands']); + const [commandHistory, setCommandHistory] = useState([]); + const [historyIndex, setHistoryIndex] = useState(-1); const inputRef = useRef(null); const outputRef = useRef(null); const navigate = useNavigate(); @@ -116,37 +118,46 @@ const TerminalCommand = () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - const trimmedInput = input.trim().toLowerCase(); - + const trimmedInput = input.trim(); + if (!trimmedInput) return; playSound('click'); setOutput(prev => [...prev, `> ${input}`]); - + + // Add to command history (avoid duplicates of the last command) + setCommandHistory(prev => { + const newHistory = prev.filter(cmd => cmd !== trimmedInput); + return [...newHistory, trimmedInput]; + }); + setHistoryIndex(-1); + // Unlock terminal user achievement unlockAchievement('terminal_user'); - if (trimmedInput === '/help' || trimmedInput === '/h') { + const lowerInput = trimmedInput.toLowerCase(); + + if (lowerInput === '/help' || lowerInput === '/h') { setOutput(prev => [...prev, helpText, '---HINT---' + helpHint]); - } else if (trimmedInput === '/hint') { + } else if (lowerInput === '/hint') { unlockAchievement('hint_seeker'); - setOutput(prev => [...prev, + setOutput(prev => [...prev, 'Hidden feature detected in system...', 'Hint: Old-school gamers know a certain cheat code.', 'Think NES, 1986, Contra... 30 lives anyone?', 'The sequence uses arrow keys and two letters.' ]); - } else if (trimmedInput === '/clear' || trimmedInput === '/c') { + } else if (lowerInput === '/clear' || lowerInput === '/c') { setOutput(['Terminal cleared. Type /help for commands.']); - } else if (commands[trimmedInput]) { - setOutput(prev => [...prev, `Navigating to ${trimmedInput.slice(1)}...`]); + } else if (commands[lowerInput]) { + setOutput(prev => [...prev, `Navigating to ${lowerInput.slice(1)}...`]); playSound('beep'); setTimeout(() => { - navigate(commands[trimmedInput]); + navigate(commands[lowerInput]); setIsOpen(false); }, 300); } else { - setOutput(prev => [...prev, `Command not found: ${trimmedInput}`, 'Type /help for available commands']); + setOutput(prev => [...prev, `Command not found: ${lowerInput}`, 'Type /help for available commands']); } setInput(''); @@ -209,16 +220,53 @@ const TerminalCommand = () => {
{'>'} - setInput(e.target.value)} - className="flex-1 bg-transparent border-none outline-none font-mono text-primary placeholder-primary/40" - placeholder="Enter command..." - autoComplete="off" - spellCheck={false} - /> + { + setInput(e.target.value); + setHistoryIndex(-1); // Reset history navigation when typing + }} + onKeyDown={(e) => { + if (e.key === 'ArrowUp') { + e.preventDefault(); + if (commandHistory.length > 0) { + const newIndex = historyIndex === -1 ? commandHistory.length - 1 : Math.max(0, historyIndex - 1); + setHistoryIndex(newIndex); + setInput(commandHistory[newIndex]); + } + } else if (e.key === 'ArrowDown') { + e.preventDefault(); + if (historyIndex >= 0) { + const newIndex = historyIndex + 1; + if (newIndex >= commandHistory.length) { + setHistoryIndex(-1); + setInput(''); + } else { + setHistoryIndex(newIndex); + setInput(commandHistory[newIndex]); + } + } + } else if (e.key === 'Tab') { + e.preventDefault(); + const currentInput = input.trim().toLowerCase(); + if (currentInput) { + // Find commands that start with current input + const matches = Object.keys(commands).filter(cmd => cmd.startsWith(currentInput)); + if (matches.length === 1) { + setInput(matches[0]); + } else if (matches.length > 1) { + setOutput(prev => [...prev, `Possible completions: ${matches.join(', ')}`]); + } + } + } + }} + className="flex-1 bg-transparent border-none outline-none font-mono text-primary placeholder-primary/40" + placeholder="Enter command..." + autoComplete="off" + spellCheck={false} + /> _