e7f219f7c3
- Extract use-terminal-page hook for terminal state and effects - Extract MobileTerminalView and DesktopTerminalView components - Slim TerminalPage from 571 to 112 lines Quality gates: tsc --noEmit passes, npm run build passes
199 lines
6.1 KiB
TypeScript
199 lines
6.1 KiB
TypeScript
import React from "react";
|
|
import { TerminalComponent, type TerminalRef } from "./terminal";
|
|
import { TerminalSessionTabs, type TerminalSessionInfo } from "./terminal-session-tabs";
|
|
import type { TerminalSession } from "../../../api/terminal";
|
|
|
|
interface Props {
|
|
instanceId: string;
|
|
sessions: TerminalSession[];
|
|
sessionInfos: TerminalSessionInfo[];
|
|
activeSessionId: string;
|
|
terminalRefs: React.MutableRefObject<Record<string, React.RefObject<TerminalRef>>>;
|
|
isFullscreen: boolean;
|
|
status: string;
|
|
error: string | null;
|
|
loading: boolean;
|
|
showResetConfirm: boolean;
|
|
onFullscreenClick: (e: React.MouseEvent<HTMLElement>) => void;
|
|
onSelect: (id: string) => void;
|
|
onClose: (id: string) => void;
|
|
onCreate: () => void;
|
|
onRename: (id: string, name: string) => void;
|
|
onNavigateBack: () => void;
|
|
onToggleFullscreen: () => void;
|
|
onFontSizeChange: (delta: number) => void;
|
|
onShowResetConfirm: () => void;
|
|
onHideResetConfirm: () => void;
|
|
onReset: () => void;
|
|
onTerminalReady: (
|
|
sendData: (data: string) => void,
|
|
status: "connecting" | "connected" | "disconnected" | "error" | "resetting",
|
|
focusInput: () => void,
|
|
changeFontSize: (delta: number) => void,
|
|
) => void;
|
|
}
|
|
|
|
export const DesktopTerminalView: React.FC<Props> = ({
|
|
instanceId,
|
|
sessions,
|
|
sessionInfos,
|
|
activeSessionId,
|
|
terminalRefs,
|
|
isFullscreen,
|
|
status,
|
|
error,
|
|
loading,
|
|
showResetConfirm,
|
|
onFullscreenClick,
|
|
onSelect,
|
|
onClose,
|
|
onCreate,
|
|
onRename,
|
|
onNavigateBack,
|
|
onToggleFullscreen,
|
|
onFontSizeChange,
|
|
onShowResetConfirm,
|
|
onHideResetConfirm,
|
|
onReset,
|
|
onTerminalReady,
|
|
}) => {
|
|
return (
|
|
<section
|
|
className={`terminal-page ${isFullscreen ? "fullscreen" : ""}`}
|
|
onClick={onFullscreenClick}
|
|
>
|
|
{!isFullscreen && (
|
|
<div className="terminal-page-header">
|
|
<button className="secondary-button" onClick={onNavigateBack} type="button">
|
|
Back
|
|
</button>
|
|
<h1>Terminal</h1>
|
|
<button
|
|
className="secondary-button"
|
|
onClick={onToggleFullscreen}
|
|
type="button"
|
|
title="Toggle fullscreen (Alt+Shift+F)"
|
|
>
|
|
{isFullscreen ? "Exit Fullscreen" : "Fullscreen"}
|
|
</button>
|
|
</div>
|
|
)}
|
|
{isFullscreen ? (
|
|
<div className="terminal-fullscreen-header">
|
|
<div className="terminal-fullscreen-header-tabs">
|
|
<TerminalSessionTabs
|
|
sessions={sessionInfos}
|
|
activeSessionId={activeSessionId}
|
|
onSelect={onSelect}
|
|
onClose={onClose}
|
|
onCreate={onCreate}
|
|
onRename={onRename}
|
|
isMobile={false}
|
|
/>
|
|
</div>
|
|
<div className="terminal-fullscreen-header-controls">
|
|
<span
|
|
className={`terminal-fullscreen-status status-dot ${status}`}
|
|
aria-label={`Terminal status: ${status}`}
|
|
/>
|
|
<button
|
|
className="terminal-header-button"
|
|
onClick={() => onFontSizeChange(-1)}
|
|
type="button"
|
|
aria-label="Decrease font size"
|
|
>
|
|
A-
|
|
</button>
|
|
<button
|
|
className="terminal-header-button"
|
|
onClick={() => onFontSizeChange(1)}
|
|
type="button"
|
|
aria-label="Increase font size"
|
|
>
|
|
A+
|
|
</button>
|
|
<button
|
|
className="terminal-header-button"
|
|
onClick={onShowResetConfirm}
|
|
type="button"
|
|
aria-label="Reset terminal"
|
|
>
|
|
Reset
|
|
</button>
|
|
<button
|
|
className="terminal-close"
|
|
onClick={onToggleFullscreen}
|
|
type="button"
|
|
title="Exit fullscreen (Esc)"
|
|
>
|
|
Exit
|
|
</button>
|
|
</div>
|
|
{showResetConfirm && (
|
|
<div className="terminal-reset-confirm">
|
|
<div className="terminal-reset-confirm-content">
|
|
<p>
|
|
Reset terminal? This will kill the current shell session and
|
|
start fresh.
|
|
</p>
|
|
<div className="terminal-reset-confirm-buttons">
|
|
<button
|
|
className="terminal-reset-confirm-button cancel"
|
|
onClick={onHideResetConfirm}
|
|
type="button"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
className="terminal-reset-confirm-button confirm"
|
|
onClick={() => {
|
|
onHideResetConfirm();
|
|
onReset();
|
|
}}
|
|
type="button"
|
|
>
|
|
Reset
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<TerminalSessionTabs
|
|
sessions={sessionInfos}
|
|
activeSessionId={activeSessionId}
|
|
onSelect={onSelect}
|
|
onClose={onClose}
|
|
onCreate={onCreate}
|
|
onRename={onRename}
|
|
isMobile={false}
|
|
/>
|
|
)}
|
|
<div className="terminal-page-content">
|
|
{error && <div className="terminal-error-banner">{error}</div>}
|
|
{sessions
|
|
.filter((session) => session.id === activeSessionId)
|
|
.map((session) => (
|
|
<div key={session.id} className="terminal-instance active">
|
|
<TerminalComponent
|
|
ref={terminalRefs.current[session.id]}
|
|
instanceId={instanceId}
|
|
sessionId={session.id}
|
|
onClose={() => onClose(session.id)}
|
|
isMobile={false}
|
|
showControls={!isFullscreen}
|
|
onTerminalReady={onTerminalReady}
|
|
/>
|
|
</div>
|
|
))}
|
|
{sessions.length === 0 && !loading && (
|
|
<div className="terminal-empty-state">
|
|
<p>No terminal sessions. Press Alt+Shift+N to create one.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|