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,70 +11,55 @@ 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 = () => {
const terminalRef = useRef(null);
const [inputBuffer, setInputBuffer] = useState("");
// Auto-scroll terminal to bottom when content changes
useEffect(() => { useEffect(() => {
if (terminalRef.current) { if (terminalRef.current) {
terminalRef.current.scrollTop = terminalRef.current.scrollHeight; terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
} }
}, [terminalOutput]); }, [terminalOutput]);
// Set up keyboard event listeners when terminal is focused // Handle keyboard input for the terminal
useEffect(() => { useEffect(() => {
const handleKeyDown = (e) => { const handleKeyDown = (e) => {
if (!isRunning) return; if (!isRunning) return;
if (e.key === 'Enter') { if (e.key === "Enter") {
// Send current input buffer through WebSocket
if (inputBuffer.trim() && onInputSubmit) { if (inputBuffer.trim() && onInputSubmit) {
e.preventDefault(); // Prevent default Enter behavior e.preventDefault();
// Important: Set user input and THEN call submit in a sequence
onUserInputChange(inputBuffer); onUserInputChange(inputBuffer);
// Add a small delay before submitting to ensure state update
setTimeout(() => { setTimeout(() => {
onInputSubmit(); onInputSubmit();
// Clear buffer after submission is processed
setInputBuffer(""); setInputBuffer("");
}, 10); }, 10);
} }
} else if (e.key === 'Backspace') { } else if (e.key === "Backspace") {
// Handle backspace to remove characters setInputBuffer((prev) => prev.slice(0, -1));
setInputBuffer(prev => prev.slice(0, -1));
} else if (e.key.length === 1) { } else if (e.key.length === 1) {
// Add regular characters to input buffer setInputBuffer((prev) => prev + e.key);
setInputBuffer(prev => prev + e.key);
} }
}; };
// Add event listener const terminalElement = terminalRef.current;
if (terminalRef.current) { terminalElement?.addEventListener("keydown", handleKeyDown);
terminalRef.current.addEventListener('keydown', handleKeyDown);
}
// Clean up
return () => { return () => {
if (terminalRef.current) { terminalElement?.removeEventListener("keydown", handleKeyDown);
terminalRef.current.removeEventListener('keydown', handleKeyDown);
}
}; };
}, [isRunning, inputBuffer, onInputSubmit, onUserInputChange]); }, [isRunning, inputBuffer, onInputSubmit, onUserInputChange]);
return ( // Render the terminal tab
const renderTerminal = () => (
<div <div
className="panel-terminal" className="panel-terminal"
ref={terminalRef} ref={terminalRef}
@@ -82,15 +67,26 @@ const Panel = ({
onClick={() => terminalRef.current?.focus()} // Focus when clicked onClick={() => terminalRef.current?.focus()} // Focus when clicked
> >
{terminalOutput.length > 0 ? ( {terminalOutput.length > 0 ? (
// Render output from EditorArea when available
<> <>
{terminalOutput.map((line, index) => ( {terminalOutput.map((line, index) => {
<div key={index} className={`terminal-line ${line.type === 'warning' ? 'terminal-warning' : 'terminal-output'}`}> const typeClass =
{line.type === 'command' ? <span className="terminal-prompt">$</span> : ''} {line.content} line.type === "warning"
</div> ? "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>
);
})}
{/* 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}
@@ -99,27 +95,22 @@ const Panel = ({
)} )}
</> </>
) : ( ) : (
// Default terminal content
<>
<div className="terminal-line"> <div className="terminal-line">
<span className="terminal-prompt">$</span> <span className="terminal-prompt">$</span>
<span className="terminal-cursor"></span>
</div> </div>
</>
)} )}
</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>
@@ -128,8 +119,27 @@ const Panel = ({
)} )}
</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">
{["problems", "output", "debug", "terminal", "ports", "comments"].map((tab) => (
<div <div
className={`panel-tab ${activeTab === "problems" ? "active" : ""}`} key={tab}
onClick={() => setActiveTab("problems")} className={`panel-tab ${activeTab === tab ? "active" : ""}`}
onClick={() => setActiveTab(tab)}
> >
<span className="tab-icon"> <span className="tab-name">{tab.toUpperCase()}</span>
<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="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> </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;