File size: 4,916 Bytes
f91a684 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | import { useRef, useEffect, useState, type KeyboardEvent } from 'react';
interface TerminalLine {
type: 'prompt' | 'stdout' | 'stderr' | 'info' | 'dim';
text: string;
}
interface TerminalPanelProps {
lines: TerminalLine[];
onCommand: (cmd: string) => void;
isRunning: boolean;
onSendInput: (text: string) => void;
}
export type { TerminalLine };
export default function TerminalPanel({ lines, onCommand, isRunning, onSendInput }: TerminalPanelProps) {
const [commandInput, setCommandInput] = useState('');
const [activeTab, setActiveTab] = useState<'terminal' | 'problems' | 'output'>('terminal');
const scrollRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [lines, commandInput, isRunning]);
useEffect(() => {
if (activeTab === 'terminal' && inputRef.current) {
inputRef.current.focus();
}
}, [isRunning, activeTab, lines]);
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
if (isRunning) {
onSendInput(commandInput);
} else if (commandInput.trim()) {
onCommand(commandInput.trim());
}
setCommandInput('');
}
};
return (
<div className="flex flex-col h-full" style={{ background: '#000000' }}>
{/* Tab bar */}
<div
className="flex items-center justify-between px-3"
style={{ borderBottom: '1px solid #222233', background: '#000000', minHeight: 36 }}
>
<div className="flex items-center gap-1">
{(['terminal', 'problems', 'output'] as const).map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className="px-3 py-1.5 text-xs font-bold uppercase tracking-wide transition-colors rounded"
style={{
color: activeTab === tab ? '#e2e2f0' : '#55556a',
background: activeTab === tab ? '#111111' : 'transparent',
border: 'none',
cursor: 'pointer',
}}
>
{tab}
</button>
))}
</div>
<button
onClick={() => onCommand('clear')}
className="text-xs font-semibold transition-colors"
style={{ color: '#55556a', background: 'none', border: 'none', cursor: 'pointer' }}
onMouseEnter={(e) => (e.currentTarget.style.color = '#e2e2f0')}
onMouseLeave={(e) => (e.currentTarget.style.color = '#55556a')}
>
Clear
</button>
</div>
{/* Terminal body — unified output + inline input */}
<div
ref={scrollRef}
onClick={() => inputRef.current?.focus()}
className={`flex-1 overflow-auto min-h-0 p-3 ryp-compiler-scroll ryp-terminal ${activeTab !== 'terminal' ? 'hidden' : ''}`}
>
{lines.map((line, i) => (
<div key={i} className="flex gap-0 whitespace-pre-wrap" style={{ minHeight: '1.7em' }}>
{line.type === 'prompt' && <span className="ryp-terminal__prompt">{line.text}</span>}
{line.type === 'stdout' && <span className="ryp-terminal__stdout">{line.text}</span>}
{line.type === 'stderr' && <span className="ryp-terminal__stderr">{line.text}</span>}
{line.type === 'info' && <span className="ryp-terminal__info">{line.text}</span>}
{line.type === 'dim' && <span className="ryp-terminal__dim">{line.text}</span>}
</div>
))}
{/* Inline input line — last line of the terminal, scrolls with output */}
<div className="flex items-center whitespace-pre-wrap" style={{ minHeight: '1.7em' }}>
{!isRunning && <span className="ryp-terminal__prompt mr-1">~/ryp $</span>}
<input
ref={inputRef}
value={commandInput}
onChange={(e) => setCommandInput(e.target.value)}
onKeyDown={handleKeyDown}
className="flex-1 bg-transparent border-none outline-none p-0 m-0 font-mono text-sm"
style={{
color: '#e2e2f0',
caretColor: '#e2e2f0',
fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace",
}}
spellCheck={false}
autoComplete="off"
autoFocus
/>
</div>
</div>
{/* Problems Tab Content */}
<div className={`flex-1 flex items-center justify-center text-[#55556a] text-xs ${activeTab !== 'problems' ? 'hidden' : ''}`}>
No problems detected.
</div>
{/* Output Tab Content */}
<div className={`flex-1 flex items-center justify-center text-[#55556a] text-xs ${activeTab !== 'output' ? 'hidden' : ''}`}>
Execution output will appear here.
</div>
</div>
);
}
|