Enhance Panel component with additional tabs and keyboard input handling for terminal

This commit is contained in:
ishikabhoyar
2025-03-30 01:51:21 +05:30
parent 99e12a7355
commit 697c4b8460

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { X } from "lucide-react"; import { X, Maximize2, ChevronDown, Plus } from "lucide-react";
const Panel = ({ const Panel = ({
height, height,
@@ -11,125 +11,135 @@ const Panel = ({
onClose, onClose,
userInput = "", userInput = "",
onUserInputChange, onUserInputChange,
onInputSubmit onInputSubmit,
}) => { }) => {
const [activeTab, setActiveTab] = useState(initialTab); const [activeTab, setActiveTab] = useState(initialTab);
const terminalRef = useRef(null);
const [inputBuffer, setInputBuffer] = useState("");
// Set active tab when initialTab changes // Update active tab when initialTab changes
useEffect(() => { useEffect(() => {
setActiveTab(initialTab); setActiveTab(initialTab);
}, [initialTab]); }, [initialTab]);
// Update the renderTerminal function to create an interactive terminal // Auto-scroll terminal to the bottom when content changes
const renderTerminal = () => { useEffect(() => {
const terminalRef = useRef(null); if (terminalRef.current) {
const [inputBuffer, setInputBuffer] = useState(""); terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
}
}, [terminalOutput]);
// Auto-scroll terminal to bottom when content changes // Handle keyboard input for the terminal
useEffect(() => { useEffect(() => {
if (terminalRef.current) { const handleKeyDown = (e) => {
terminalRef.current.scrollTop = terminalRef.current.scrollHeight; if (!isRunning) return;
}
}, [terminalOutput]);
// Set up keyboard event listeners when terminal is focused if (e.key === "Enter") {
useEffect(() => { if (inputBuffer.trim() && onInputSubmit) {
const handleKeyDown = (e) => { e.preventDefault();
if (!isRunning) return; onUserInputChange(inputBuffer);
setTimeout(() => {
if (e.key === 'Enter') { onInputSubmit();
// Send current input buffer through WebSocket setInputBuffer("");
if (inputBuffer.trim() && onInputSubmit) { }, 10);
e.preventDefault(); // Prevent default Enter behavior
// Important: Set user input and THEN call submit in a sequence
onUserInputChange(inputBuffer);
// Add a small delay before submitting to ensure state update
setTimeout(() => {
onInputSubmit();
// Clear buffer after submission is processed
setInputBuffer("");
}, 10);
}
} else if (e.key === 'Backspace') {
// Handle backspace to remove characters
setInputBuffer(prev => prev.slice(0, -1));
} else if (e.key.length === 1) {
// Add regular characters to input buffer
setInputBuffer(prev => prev + e.key);
} }
}; } else if (e.key === "Backspace") {
setInputBuffer((prev) => prev.slice(0, -1));
// Add event listener } else if (e.key.length === 1) {
if (terminalRef.current) { setInputBuffer((prev) => prev + e.key);
terminalRef.current.addEventListener('keydown', handleKeyDown);
} }
};
// Clean up const terminalElement = terminalRef.current;
return () => { terminalElement?.addEventListener("keydown", handleKeyDown);
if (terminalRef.current) {
terminalRef.current.removeEventListener('keydown', handleKeyDown);
}
};
}, [isRunning, inputBuffer, onInputSubmit, onUserInputChange]);
return ( return () => {
<div terminalElement?.removeEventListener("keydown", handleKeyDown);
className="panel-terminal" };
ref={terminalRef} }, [isRunning, inputBuffer, onInputSubmit, onUserInputChange]);
tabIndex={0} // Make div focusable
onClick={() => terminalRef.current?.focus()} // Focus when clicked // Render the terminal tab
> const renderTerminal = () => (
{terminalOutput.length > 0 ? ( <div
// Render output from EditorArea when available className="panel-terminal"
<> ref={terminalRef}
{terminalOutput.map((line, index) => ( tabIndex={0} // Make div focusable
<div key={index} className={`terminal-line ${line.type === 'warning' ? 'terminal-warning' : 'terminal-output'}`}> onClick={() => terminalRef.current?.focus()} // Focus when clicked
{line.type === 'command' ? <span className="terminal-prompt">$</span> : ''} {line.content} >
{terminalOutput.length > 0 ? (
<>
{terminalOutput.map((line, index) => {
const typeClass =
line.type === "warning"
? "terminal-warning"
: line.type === "error"
? "terminal-error"
: "terminal-output";
return (
<div key={index} className={`terminal-line ${typeClass}`}>
{line.timestamp && (
<span className="terminal-timestamp">{line.timestamp} </span>
)}
{line.type === "command" && <span className="terminal-prompt">$</span>}
{line.content}
</div> </div>
))} );
})}
{/* Show current input with blinking cursor only when connection is active */} {isRunning && (
{isRunning && ( <div className="terminal-line terminal-input-line">
<div className="terminal-line terminal-input-line"> <span className="terminal-prompt">$</span> {inputBuffer}
<span className="terminal-prompt">$</span> {inputBuffer} <span className="terminal-cursor"></span>
<span className="terminal-cursor"></span>
</div>
)}
</>
) : (
// Default terminal content
<>
<div className="terminal-line">
<span className="terminal-prompt">$</span>
</div> </div>
</> )}
)} </>
</div> ) : (
); <div className="terminal-line">
}; <span className="terminal-prompt">$</span>
<span className="terminal-cursor"></span>
</div>
)}
</div>
);
const renderProblems = () => { // Render other tabs
return ( const renderProblems = () => (
<div className="panel-problems"> <div className="panel-problems">
<div className="panel-empty-message">No problems have been detected in the workspace.</div> <div className="panel-empty-message">No problems have been detected in the workspace.</div>
</div> </div>
); );
};
const renderOutput = () => { const renderOutput = () => (
return ( <div className="panel-output">
<div className="panel-output"> <div className="output-line">[Extension Host] Extension host started.</div>
<div className="output-line">[Extension Host] Extension host started.</div> <div className="output-line">[Language Server] Language server started.</div>
<div className="output-line">[Language Server] Language server started.</div> {activeRunningFile && (
{activeRunningFile && ( <div className="output-line">[Running] {activeRunningFile}</div>
<div className="output-line">[Running] {activeRunningFile}</div> )}
)} </div>
</div> );
);
};
const renderDebugConsole = () => (
<div className="panel-debug-console">
<div className="debug-line">Debug session not yet started.</div>
<div className="debug-line">Press F5 to start debugging.</div>
</div>
);
const renderPorts = () => (
<div className="panel-ports">
<div className="ports-line">No forwarded ports detected.</div>
</div>
);
const renderComments = () => (
<div className="panel-comments">
<div className="comments-line">No comments have been added to this workspace.</div>
</div>
);
// Get content for the active tab
const getTabContent = () => { const getTabContent = () => {
switch (activeTab) { switch (activeTab) {
case "terminal": case "terminal":
@@ -138,6 +148,12 @@ const Panel = ({
return renderProblems(); return renderProblems();
case "output": case "output":
return renderOutput(); return renderOutput();
case "debug":
return renderDebugConsole();
case "ports":
return renderPorts();
case "comments":
return renderComments();
default: default:
return <div>Unknown tab</div>; return <div>Unknown tab</div>;
} }
@@ -146,76 +162,29 @@ const Panel = ({
return ( return (
<div className="panel" style={{ height: `${height}px` }}> <div className="panel" style={{ height: `${height}px` }}>
<div className="panel-tabs"> <div className="panel-tabs">
<div {["problems", "output", "debug", "terminal", "ports", "comments"].map((tab) => (
className={`panel-tab ${activeTab === "problems" ? "active" : ""}`} <div
onClick={() => setActiveTab("problems")} key={tab}
> className={`panel-tab ${activeTab === tab ? "active" : ""}`}
<span className="tab-icon"> onClick={() => setActiveTab(tab)}
<svg >
xmlns="http://www.w3.org/2000/svg" <span className="tab-name">{tab.toUpperCase()}</span>
width="16" </div>
height="16" ))}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>
</span>
<span className="tab-name">Problems</span>
</div>
<div className={`panel-tab ${activeTab === "output" ? "active" : ""}`} onClick={() => setActiveTab("output")}>
<span className="tab-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
</span>
<span className="tab-name">Output</span>
</div>
<div
className={`panel-tab ${activeTab === "terminal" ? "active" : ""}`}
onClick={() => setActiveTab("terminal")}
>
<span className="tab-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="4 17 10 11 4 5"></polyline>
<line x1="12" y1="19" x2="20" y2="19"></line>
</svg>
</span>
<span className="tab-name">Terminal</span>
</div>
{/* Add close button */}
<div className="panel-actions"> <div className="panel-actions">
<button className="panel-action-btn">
<span className="current-terminal">node - frontend</span>
<ChevronDown size={16} />
</button>
<button className="panel-action-btn">
<Plus size={16} />
</button>
<button className="panel-action-btn">
<Maximize2 size={16} />
</button>
<button className="panel-close-btn" onClick={onClose}> <button className="panel-close-btn" onClick={onClose}>
<X size={14} /> <X size={16} />
</button> </button>
</div> </div>
</div> </div>
@@ -226,4 +195,3 @@ const Panel = ({
}; };
export default Panel; export default Panel;