UNPKG

@hhoangphuoc/escape-room-cli

Version:

A CLI for playing AI-generated escape room games. Install globally with: npm install -g @hhoangphuoc/escape-room-cli

84 lines (83 loc) 4.59 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useState, useEffect } from 'react'; import { Box, Text, useInput, useStdin } from 'ink'; import { COMMANDS } from '../utils/constants.js'; import { applyTabCompletion, getCompletionDisplayInfo } from '../utils/tabCompletion.js'; const CommandInput = ({ value, onChange, onSubmit, mode = 'standard', currentRoomObjects = [], }) => { const { isRawModeSupported } = useStdin(); const [cursorVisible, setCursorVisible] = useState(true); const [showSuggestions, setShowSuggestions] = useState(false); const [filteredCommands, setFilteredCommands] = useState({}); const [completionInfo, setCompletionInfo] = useState(null); // Blink cursor effect useEffect(() => { if (!isRawModeSupported) return; const timer = setInterval(() => { setCursorVisible(prev => !prev); }, 500); return () => clearInterval(timer); }, [isRawModeSupported]); // Update suggestions based on input and context useEffect(() => { const context = { currentRoomObjects, availableCommands: Object.keys(COMMANDS), }; // Get completion info for display const info = getCompletionDisplayInfo(value, context); setCompletionInfo(info); // Show suggestions when there are completions available if (info && info.suggestions.length > 0) { setShowSuggestions(true); // Convert to the format expected by the existing display logic const filtered = info.suggestions.reduce((acc, suggestion) => { const cmd = suggestion.startsWith('/') ? suggestion : '/' + suggestion; const isObjectName = info.title === 'Available Objects'; acc[cmd] = { description: COMMANDS[cmd]?.description || (isObjectName ? `Room object: ${suggestion}` : `${info.title}: ${suggestion}`), usage: COMMANDS[cmd]?.usage || suggestion, }; return acc; }, {}); setFilteredCommands(filtered); } else { setShowSuggestions(false); setFilteredCommands({}); } }, [value, mode, currentRoomObjects]); // Handle keyboard input only if raw mode is supported useInput((input, key) => { if (!isRawModeSupported) return; if (key.return) { if (value.trim() !== '') { onSubmit(value); onChange(''); } } else if (key.tab) { // Handle tab completion const context = { currentRoomObjects, availableCommands: Object.keys(COMMANDS), }; const completed = applyTabCompletion(value, context); onChange(completed); } else if (key.backspace || key.delete) { onChange(value.slice(0, -1)); } else if (!key.ctrl && !key.meta) { onChange(value + input); } }); return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "> " }), _jsx(Text, { children: value }), isRawModeSupported ? (_jsx(Text, { color: "gray", children: cursorVisible ? '█' : ' ' })) : (_jsx(Text, { color: "red", children: "[Input disabled - run in interactive terminal]" }))] }), showSuggestions && Object.keys(filteredCommands).length > 0 && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: completionInfo?.title || 'Available Commands' }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", children: "(Press Tab to complete)" }) })] }), Object.entries(filteredCommands).map(([cmd, details]) => { const isCurrentMatch = completionInfo?.currentMatch && (cmd.toLowerCase().startsWith(completionInfo.currentMatch.toLowerCase()) || details.usage.toLowerCase().startsWith(completionInfo.currentMatch.toLowerCase())); return (_jsxs(Box, { marginBottom: 0, children: [_jsx(Text, { color: isCurrentMatch ? 'cyan' : 'white', bold: isCurrentMatch, children: details.usage.padEnd(30) }), _jsx(Text, { color: 'gray', children: details.description })] }, cmd)); })] }))] })); }; export default CommandInput;