/** * TerminalCard - Reusable terminal container with header and controls */ import React, { useCallback, type JSX } from 'react'; import type { TerminalInfo, TerminalSettings, PtySession } from '#types'; import type { TerminalEngineId } from './views/types.js'; import { VirtualTerminal } from './VirtualTerminal.js'; import { HeadlessTerminal } from './HeadlessTerminal.js'; export interface TerminalCardProps { terminal: TerminalInfo; session: PtySession | undefined; index: number; settings: TerminalSettings; onSettingsChange: (updates: Partial) => void; onClose: () => void; onFocus: () => void; onBlur: () => void; engine?: TerminalEngineId; } export function TerminalCard({ terminal, session, index, settings, onSettingsChange, onClose, onFocus, onBlur, engine = 'xterm', }: TerminalCardProps): JSX.Element { const isUdsSession = session?.source === 'ipc'; const hasCustomSize = settings.width !== undefined && settings.height !== undefined; const terminalAutoResize = settings.autoResize && !(isUdsSession && terminal.mode !== 'active'); const handleResizeStart = useCallback( (e: React.MouseEvent, direction: 'e' | 's' | 'se') => { e.preventDefault(); e.stopPropagation(); const startX = e.clientX; const startY = e.clientY; const containerEl = (e.target as HTMLElement).closest('[data-terminal-container]') as HTMLElement; if (!containerEl) return; const startWidth = containerEl.offsetWidth; const startHeight = containerEl.offsetHeight; const handleMouseMove = (moveEvent: MouseEvent): void => { const deltaX = moveEvent.clientX - startX; const deltaY = moveEvent.clientY - startY; const newWidth = direction === 's' ? startWidth : Math.max(200, startWidth + deltaX); const newHeight = direction === 'e' ? startHeight : Math.max(100, startHeight + deltaY); onSettingsChange({ width: newWidth, height: newHeight }); }; const handleMouseUp = (): void => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); document.body.style.cursor = ''; document.body.style.userSelect = ''; }; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); document.body.style.cursor = direction === 'se' ? 'nwse-resize' : direction === 'e' ? 'ew-resize' : 'ns-resize'; document.body.style.userSelect = 'none'; }, [onSettingsChange] ); return (
{index + 1} {terminal.type === 'virtual' ? 'VIR' : 'HDL'}
{terminal.mode === 'active' ? 'ACT' : 'PAS'} {terminal.type === 'virtual' && ()} {terminal.cols}x{terminal.rows}
{terminal.type === 'virtual' ? ( ) : ( )}
handleResizeStart(e, 'e')} style={{ position: 'absolute', right: 0, top: 0, bottom: 0, width: '6px', cursor: 'ew-resize', backgroundColor: 'transparent' }} />
handleResizeStart(e, 's')} style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: '6px', cursor: 'ns-resize', backgroundColor: 'transparent' }} />
handleResizeStart(e, 'se')} style={{ position: 'absolute', right: 0, bottom: 0, width: '12px', height: '12px', cursor: 'nwse-resize', backgroundColor: 'transparent' }} />
); } export default TerminalCard;