diff --git a/src/components/node/nodeControls/Styled.ts b/src/components/node/nodeControls/Styled.ts index c8edc03..b975035 100644 --- a/src/components/node/nodeControls/Styled.ts +++ b/src/components/node/nodeControls/Styled.ts @@ -28,6 +28,7 @@ export const TerminalOutput = styled.pre` margin: 0; padding: 5px; font-size: 12px; + max-height: 500px; `; export const TerminalForm = styled.form` diff --git a/src/components/node/nodeControls/index.tsx b/src/components/node/nodeControls/index.tsx index 3692c78..e4cf96d 100644 --- a/src/components/node/nodeControls/index.tsx +++ b/src/components/node/nodeControls/index.tsx @@ -44,6 +44,9 @@ const NodeControls: React.FC = ({ ...props }) => { type: MessageType.INFO, }); const { handleGetNodeOutput } = useNodeManagement(); + const [suggestions, setSuggestions] = useState([]); + const [showSuggestions, setShowSuggestions] = useState(false); + const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1); useEffect(() => { if (props.action) { @@ -200,6 +203,105 @@ const NodeControls: React.FC = ({ ...props }) => { } }; + const handleKeyDown = (e: React.KeyboardEvent) => { + if (!showSuggestions) return; + + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + setSelectedSuggestionIndex(prev => + prev < suggestions.length - 1 ? prev + 1 : prev + ); + break; + case 'ArrowUp': + e.preventDefault(); + setSelectedSuggestionIndex(prev => + prev > 0 ? prev - 1 : prev + ); + break; + case 'Enter': + e.preventDefault(); + if (selectedSuggestionIndex >= 0) { + const suggestion = suggestions[selectedSuggestionIndex]; + const parts = suggestion.split(' '); + if (parts.length > 1 && !parts[1].startsWith('[')) { + // If it's a subcommand suggestion, preserve the main command + const mainCommand = input.split(' ')[0]; + setInput(mainCommand + ' ' + parts[0]); + } else { + // If it's a main command or has no subcommands, use only the first word + setInput(parts[0]); + } + setShowSuggestions(false); + setSelectedSuggestionIndex(-1); + } + break; + case 'Escape': + setShowSuggestions(false); + setSelectedSuggestionIndex(-1); + break; + } + }; + + const handleInputChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setInput(value); + + const commandStructure = { + 'call': [' '], + 'peers': [], + 'pool': [], + 'gc': [], + 'store': [], + 'context': ['ls', 'join', 'leave', 'invite', 'create', 'delete', 'state', 'identity'], + 'application': ['ls', 'install'] + }; + + // Handle both cases: with and without leading '/' + const parts = value.startsWith('/') ? value.slice(1).split(' ') : value.split(' '); + const mainCommand = parts[0].toLowerCase(); + + if (parts.length === 1) { + // Show main commands + const commands = Object.keys(commandStructure).map(cmd => { + const subcommands = commandStructure[cmd]; + return subcommands.length > 0 ? `${cmd} [${subcommands.join('|')}]` : cmd; + }); + + const filtered = commands.filter(cmd => + cmd.toLowerCase().startsWith(mainCommand) + ); + setSuggestions(filtered); + setShowSuggestions(filtered.length > 0); + } else if (parts.length === 2) { + // Get the base command without any parameters + const baseCommand = mainCommand.split(' ')[0]; + const subcommands = commandStructure[baseCommand] || []; + if (subcommands.length > 0) { + const subcommandTerm = parts[1].toLowerCase(); + const filtered = subcommands.filter(cmd => + cmd.toLowerCase().startsWith(subcommandTerm) + ); + setSuggestions(filtered); + setShowSuggestions(filtered.length > 0); + } + } + setSelectedSuggestionIndex(-1); + }; + + const handleSuggestionClick = (suggestion: string) => { + const parts = suggestion.split(' '); + if (parts.length > 1 && !parts[1].startsWith('[')) { + // If it's a subcommand suggestion, preserve the main command + const mainCommand = input.split(' ')[0]; + setInput(mainCommand + ' ' + parts[0]); + } else { + // If it's a main command or has no subcommands, use only the first word + setInput(parts[0]); + } + setShowSuggestions(false); + }; + return ( @@ -213,13 +315,49 @@ const NodeControls: React.FC = ({ ...props }) => { {output} - setInput(e.target.value)} - placeholder="Enter command..." - disabled={!isRunning} - /> +
+ + {showSuggestions && ( +
+ {suggestions.map((suggestion, index) => ( +
handleSuggestionClick(suggestion)} + style={{ + padding: '8px 12px', + cursor: 'pointer', + backgroundColor: index === selectedSuggestionIndex ? '#2c2c2c' : 'transparent', + '&:hover': { + backgroundColor: '#2c2c2c' + } + }} + > + {suggestion} +
+ ))} +
+ )} +