/ src / terminal / Terminal.jsx
Terminal.jsx
  1  import {
  2    useState,
  3    useRef,
  4    useEffect
  5  } from 'react';
  6  import PropTypes from 'prop-types';
  7  
  8  import {
  9    handleMkdirCommand,
 10    handleListCommand,
 11    handleCdCommand,
 12    handleTreeCommand,
 13    handleRmCommand
 14  } from './terminalUtils';
 15  import './Terminal.css';
 16  import TerminalPrompt from './TerminalPrompt';
 17  import TerminalOutput from './TerminalOutput';
 18  import TerminalInput from './TerminalInput';
 19  
 20  const Terminal = ({ onOpenNewChat }) => {
 21    
 22    const [username] = useState(import.meta.env.VITE_USERNAME); // Set the username
 23    const [port] = useState(parseInt(import.meta.env.VITE_PORT)); // Set the port
 24    const [currentPath, setCurrentPath] = useState('home'); // Set the current path
 25    const [commandHistory, setCommandHistory] = useState([]); // History of entered commands and their outputs
 26    const [inputValue, setInputValue] = useState(''); // Current input value
 27    const [inputHistory, setInputHistory] = useState([]); // History of entered commands for hotkeys
 28    const [inputHistoryIndex, setInputHistoryIndex] = useState(-1); // Track input history index
 29  
 30    const terminalRef = useRef(null);
 31    const inputRef = useRef(null);
 32  
 33    // Autoscroll on Terminal div overflow
 34    useEffect(() => {
 35      inputRef.current.focus();
 36    
 37      // Scroll to the bottom of the terminal
 38      const terminalElement = terminalRef.current;
 39      if (terminalElement) {
 40        terminalElement.scrollTop = terminalElement.scrollHeight;
 41      }
 42    }, [commandHistory]);
 43  
 44    useEffect(() => {
 45      inputRef.current.focus();
 46    }, []);
 47  
 48    const handleCommandInput = (command) => {
 49      const [cmd, arg] = command.trim().split(' ');
 50      const promptline = `${username}@${port} ${currentPath.split('/').pop()}`;
 51      let output;
 52  
 53      switch (cmd) {
 54        case 'mkdir':
 55          try {
 56            output = handleMkdirCommand(currentPath, arg) + '\n';
 57          } catch (e) {
 58            output = 'mkdir: ' + e.message + '\n';
 59          }
 60          break;
 61        case 'rm':
 62          try {
 63            output = handleRmCommand(currentPath, arg) + '\n';
 64          } catch (e) {
 65            output = 'rm: ' + e.message + '\n';
 66          }
 67          break;
 68        case 'ls':
 69          try {
 70            output = handleListCommand(currentPath, arg) + '\n';
 71          } catch (e) {
 72            output = 'ls: ' + e.message + '\n';
 73          }
 74          break;
 75        case 'cd':
 76          try {
 77            setCurrentPath(handleCdCommand(currentPath, arg));
 78            output = '';
 79          } catch (e) {
 80            output = 'cd: ' + e.message + '\n';
 81          }
 82          break;
 83        case 'tree':
 84          output = handleTreeCommand(currentPath) + '\n';
 85          break;
 86        case 'run':
 87          if (arg === 'new_chat.sh') {
 88            onOpenNewChat(); // Call the prop function
 89            output = 'Opening new chat interface...\n';
 90          } else {
 91            output = 'Invalid command: run ' + arg + '\n';
 92          }
 93          break;
 94        case 'clear':
 95          setCommandHistory([]);
 96          setInputHistory(prevInputHistory => [command, ...prevInputHistory]);
 97          setInputValue('');
 98          return;
 99        case 'pwd':
100          output = currentPath + '\n';
101          break;
102        case 'help':
103          output = ('help command text') // TODO
104          break;
105        default:
106          output = `Command not found: ${cmd}\n`;
107      }
108  
109      setCommandHistory(prevHistory => [...prevHistory, { promptline, command, output }]);
110      setInputValue('');
111      setInputHistory(prevInputHistory => [command, ...prevInputHistory]);
112      setInputHistoryIndex(-1); // Reset input history index
113    };
114  
115    const handleKeyDown = (e) => {
116      e.preventDefault();
117      const newIndex = e.key === 'ArrowUp' ? inputHistoryIndex + 1 : inputHistoryIndex - 1;
118      if (newIndex >= 0 && newIndex < inputHistory.length) {
119        setInputValue(inputHistory[newIndex]);
120        setInputHistoryIndex(newIndex);
121      } else {
122        setInputValue('');
123        setInputHistoryIndex(-1);
124      }
125    };
126  
127    return (
128      <div className="terminal" ref={terminalRef}>
129        <TerminalOutput commandHistory={commandHistory} />
130        <div className="input-line">
131          <TerminalPrompt username={username} port={port} currentPath={currentPath} />
132          <div className="input-spacer"></div>
133          <TerminalInput
134            inputRef={inputRef}
135            inputValue={inputValue}
136            setInputValue={setInputValue}
137            handleCommandInput={handleCommandInput}
138            handleKeyDown={handleKeyDown}
139          />
140        </div>
141      </div>
142    );
143  };
144  
145  Terminal.propTypes = {
146    onOpenNewChat: PropTypes.func.isRequired
147  }
148  
149  export default Terminal;