fix: unified fullscreen terminal header
- Add showControls prop to TerminalComponent to optionally hide internal header - Add reset() method to TerminalRef for external reset control - TerminalPage now renders a unified fullscreen header bar combining: - Session tabs (TerminalSessionTabs) - Terminal controls (status dot, A-, A+, Reset, Exit Fullscreen) - Unified header is always visible in fullscreen (no hover-to-reveal) - TerminalComponent internal header hidden when in fullscreen mode - Remove old CSS that hid session tabs with opacity:0 until hover Quality gates: pytest 188 passed, frontend typecheck clean Fixes: terminal-fullscreen-unified-header
This commit is contained in:
@@ -17,6 +17,8 @@ const SESSIONS_TO_INFO = (sessions: TerminalSession[]): TerminalSessionInfo[] =>
|
||||
status: s.status as TerminalSessionInfo["status"],
|
||||
}));
|
||||
|
||||
type TerminalStatus = "connecting" | "connected" | "disconnected" | "error" | "resetting";
|
||||
|
||||
export const TerminalPage: React.FC = () => {
|
||||
const { instanceId } = useParams<{
|
||||
instanceId: string;
|
||||
@@ -27,6 +29,11 @@ export const TerminalPage: React.FC = () => {
|
||||
const terminalRefs = useRef<Record<string, React.RefObject<TerminalRef>>>({});
|
||||
const headerAutoHide = useAutoHide({ timeout: 3000, enabled: isMobile });
|
||||
|
||||
// Track terminal status and callbacks for unified fullscreen header
|
||||
const [terminalStatuses, setTerminalStatuses] = useState<Record<string, TerminalStatus>>({});
|
||||
const changeFontSizeRef = useRef<((delta: number) => void) | null>(null);
|
||||
const [showResetConfirm, setShowResetConfirm] = useState(false);
|
||||
|
||||
const {
|
||||
sessions,
|
||||
activeSessionId,
|
||||
@@ -178,6 +185,29 @@ export const TerminalPage: React.FC = () => {
|
||||
[renameSession],
|
||||
);
|
||||
|
||||
const handleTerminalReady = useCallback(
|
||||
(
|
||||
_sendData: (data: string) => void,
|
||||
status: TerminalStatus,
|
||||
_focusInput: () => void,
|
||||
changeFontSize: (delta: number) => void,
|
||||
) => {
|
||||
setTerminalStatuses((prev) => ({ ...prev, [activeSessionId ?? "default"]: status }));
|
||||
changeFontSizeRef.current = changeFontSize;
|
||||
},
|
||||
[activeSessionId],
|
||||
);
|
||||
|
||||
const handleFontSizeChange = useCallback((delta: number) => {
|
||||
changeFontSizeRef.current?.(delta);
|
||||
}, []);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
if (activeSessionId && terminalRefs.current[activeSessionId]) {
|
||||
terminalRefs.current[activeSessionId].current?.reset();
|
||||
}
|
||||
}, [activeSessionId]);
|
||||
|
||||
if (!instanceId) {
|
||||
return (
|
||||
<section className="stack">
|
||||
@@ -240,6 +270,7 @@ export const TerminalPage: React.FC = () => {
|
||||
sessionId={session.id}
|
||||
onClose={() => handleClose(session.id)}
|
||||
isMobile={true}
|
||||
onTerminalReady={handleTerminalReady}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
@@ -275,15 +306,98 @@ export const TerminalPage: React.FC = () => {
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<TerminalSessionTabs
|
||||
sessions={sessionInfos}
|
||||
activeSessionId={activeSessionId ?? ""}
|
||||
onSelect={handleSelect}
|
||||
onClose={handleClose}
|
||||
onCreate={handleCreate}
|
||||
onRename={handleRename}
|
||||
isMobile={false}
|
||||
/>
|
||||
{isFullscreen ? (
|
||||
<div className="terminal-fullscreen-header">
|
||||
<div className="terminal-fullscreen-header-tabs">
|
||||
<TerminalSessionTabs
|
||||
sessions={sessionInfos}
|
||||
activeSessionId={activeSessionId ?? ""}
|
||||
onSelect={handleSelect}
|
||||
onClose={handleClose}
|
||||
onCreate={handleCreate}
|
||||
onRename={handleRename}
|
||||
isMobile={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="terminal-fullscreen-header-controls">
|
||||
<span
|
||||
className={`terminal-fullscreen-status status-dot ${terminalStatuses[activeSessionId ?? "default"] ?? "connecting"}`}
|
||||
aria-label={`Terminal status: ${terminalStatuses[activeSessionId ?? "default"] ?? "connecting"}`}
|
||||
/>
|
||||
<button
|
||||
className="terminal-header-button"
|
||||
onClick={() => handleFontSizeChange(-1)}
|
||||
type="button"
|
||||
aria-label="Decrease font size"
|
||||
>
|
||||
A-
|
||||
</button>
|
||||
<button
|
||||
className="terminal-header-button"
|
||||
onClick={() => handleFontSizeChange(1)}
|
||||
type="button"
|
||||
aria-label="Increase font size"
|
||||
>
|
||||
A+
|
||||
</button>
|
||||
<button
|
||||
className="terminal-header-button"
|
||||
onClick={() => setShowResetConfirm(true)}
|
||||
type="button"
|
||||
aria-label="Reset terminal"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
className="terminal-close"
|
||||
onClick={() => setIsFullscreen(false)}
|
||||
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={() => setShowResetConfirm(false)}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="terminal-reset-confirm-button confirm"
|
||||
onClick={() => {
|
||||
setShowResetConfirm(false);
|
||||
handleReset();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<TerminalSessionTabs
|
||||
sessions={sessionInfos}
|
||||
activeSessionId={activeSessionId ?? ""}
|
||||
onSelect={handleSelect}
|
||||
onClose={handleClose}
|
||||
onCreate={handleCreate}
|
||||
onRename={handleRename}
|
||||
isMobile={false}
|
||||
/>
|
||||
)}
|
||||
<div className="terminal-page-content">
|
||||
{error && <div className="terminal-error-banner">{error}</div>}
|
||||
{sessions
|
||||
@@ -296,6 +410,8 @@ export const TerminalPage: React.FC = () => {
|
||||
sessionId={session.id}
|
||||
onClose={() => handleClose(session.id)}
|
||||
isMobile={false}
|
||||
showControls={!isFullscreen}
|
||||
onTerminalReady={handleTerminalReady}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user