Merge branch 'fix/terminal-session-callbacks' into dev

This commit is contained in:
Developer
2026-06-15 13:53:15 +00:00
5 changed files with 1216 additions and 1118 deletions
@@ -27,10 +27,17 @@ export const MobileTerminalWrapper: React.FC<MobileTerminalWrapperProps> = ({
const { isOpen: isKeyboardOpen, height: keyboardHeight } = const { isOpen: isKeyboardOpen, height: keyboardHeight } =
useVirtualKeyboard(); useVirtualKeyboard();
const [showPanel, setShowPanel] = useState(false); const [showPanel, setShowPanel] = useState(false);
const [activeModifier, setActiveModifier] = useState<ModifierKey | null>(null); const [activeModifier, setActiveModifier] = useState<ModifierKey | null>(
null,
);
const [terminalRef, setTerminalRef] = useState<{ const [terminalRef, setTerminalRef] = useState<{
sendData: (data: string) => void; sendData: (data: string) => void;
connectionStatus: "connecting" | "connected" | "disconnected" | "error" | "resetting"; connectionStatus:
| "connecting"
| "connected"
| "disconnected"
| "error"
| "resetting";
focusInput: () => void; focusInput: () => void;
changeFontSize: (delta: number) => void; changeFontSize: (delta: number) => void;
} | null>(null); } | null>(null);
@@ -42,17 +49,33 @@ export const MobileTerminalWrapper: React.FC<MobileTerminalWrapperProps> = ({
}, [headerAutoHide]); }, [headerAutoHide]);
const handleTerminalReady = useCallback( const handleTerminalReady = useCallback(
(sendData: (data: string) => void, connectionStatus: "connecting" | "connected" | "disconnected" | "error" | "resetting", focusInput: () => void, changeFontSize: (delta: number) => void) => { (
setTerminalRef({ sendData, connectionStatus, focusInput, changeFontSize }); _sessionId: string | undefined,
sendData: (data: string) => void,
connectionStatus:
| "connecting"
| "connected"
| "disconnected"
| "error"
| "resetting",
focusInput: () => void,
changeFontSize: (delta: number) => void,
) => {
setTerminalRef({
sendData,
connectionStatus,
focusInput,
changeFontSize,
});
}, },
[] [],
); );
const handleSendKey = useCallback( const handleSendKey = useCallback(
(data: string) => { (data: string) => {
terminalRef?.sendData(data); terminalRef?.sendData(data);
}, },
[terminalRef] [terminalRef],
); );
if (!isMobile) { if (!isMobile) {
@@ -1,6 +1,9 @@
import React from "react"; import React from "react";
import { TerminalComponent, type TerminalRef } from "./terminal"; import { TerminalComponent, type TerminalRef } from "./terminal";
import { TerminalSessionTabs, type TerminalSessionInfo } from "./terminal-session-tabs"; import {
TerminalSessionTabs,
type TerminalSessionInfo,
} from "./terminal-session-tabs";
import type { TerminalSession } from "../../../api/terminal"; import type { TerminalSession } from "../../../api/terminal";
interface Props { interface Props {
@@ -8,7 +11,9 @@ interface Props {
sessions: TerminalSession[]; sessions: TerminalSession[];
sessionInfos: TerminalSessionInfo[]; sessionInfos: TerminalSessionInfo[];
activeSessionId: string; activeSessionId: string;
terminalRefs: React.MutableRefObject<Record<string, React.RefObject<TerminalRef>>>; terminalRefs: React.MutableRefObject<
Record<string, React.RefObject<TerminalRef>>
>;
isFullscreen: boolean; isFullscreen: boolean;
status: string; status: string;
error: string | null; error: string | null;
@@ -26,6 +31,7 @@ interface Props {
onHideResetConfirm: () => void; onHideResetConfirm: () => void;
onReset: () => void; onReset: () => void;
onTerminalReady: ( onTerminalReady: (
sessionId: string | undefined,
sendData: (data: string) => void, sendData: (data: string) => void,
status: "connecting" | "connected" | "disconnected" | "error" | "resetting", status: "connecting" | "connected" | "disconnected" | "error" | "resetting",
focusInput: () => void, focusInput: () => void,
@@ -64,7 +70,11 @@ export const DesktopTerminalView: React.FC<Props> = ({
> >
{!isFullscreen && ( {!isFullscreen && (
<div className="terminal-page-header"> <div className="terminal-page-header">
<button className="secondary-button" onClick={onNavigateBack} type="button"> <button
className="secondary-button"
onClick={onNavigateBack}
type="button"
>
Back Back
</button> </button>
<h1>Terminal</h1> <h1>Terminal</h1>
@@ -1,6 +1,9 @@
import React from "react"; import React from "react";
import { TerminalComponent, type TerminalRef } from "./terminal"; import { TerminalComponent, type TerminalRef } from "./terminal";
import { TerminalSessionTabs, type TerminalSessionInfo } from "./terminal-session-tabs"; import {
TerminalSessionTabs,
type TerminalSessionInfo,
} from "./terminal-session-tabs";
import { Icon } from "../../icon"; import { Icon } from "../../icon";
import { SpecialKeysStrip } from "./special-keys-strip"; import { SpecialKeysStrip } from "./special-keys-strip";
import { SpecialKeysPanel } from "./special-keys-panel"; import { SpecialKeysPanel } from "./special-keys-panel";
@@ -12,7 +15,9 @@ interface Props {
sessions: TerminalSession[]; sessions: TerminalSession[];
sessionInfos: TerminalSessionInfo[]; sessionInfos: TerminalSessionInfo[];
activeSessionId: string; activeSessionId: string;
terminalRefs: React.MutableRefObject<Record<string, React.RefObject<TerminalRef>>>; terminalRefs: React.MutableRefObject<
Record<string, React.RefObject<TerminalRef>>
>;
status: string; status: string;
error: string | null; error: string | null;
loading: boolean; loading: boolean;
@@ -29,6 +34,7 @@ interface Props {
onCreate: () => void; onCreate: () => void;
onRename: (id: string, name: string) => void; onRename: (id: string, name: string) => void;
onTerminalReady: ( onTerminalReady: (
sessionId: string | undefined,
sendData: (data: string) => void, sendData: (data: string) => void,
status: "connecting" | "connected" | "disconnected" | "error" | "resetting", status: "connecting" | "connected" | "disconnected" | "error" | "resetting",
focusInput: () => void, focusInput: () => void,
@@ -73,25 +79,53 @@ export const MobileTerminalView: React.FC<Props> = ({
return ( return (
<section className="terminal-page mobile"> <section className="terminal-page mobile">
<div className={`mobile-terminal-overlay ${isVisible ? "visible" : "hidden"}`} onClick={(e) => e.stopPropagation()}> <div
className={`mobile-terminal-overlay ${isVisible ? "visible" : "hidden"}`}
onClick={(e) => e.stopPropagation()}
>
<div className="mobile-terminal-toolbar"> <div className="mobile-terminal-toolbar">
<div className="mobile-terminal-toolbar-left"> <div className="mobile-terminal-toolbar-left">
<button className="mobile-terminal-toolbtn" onClick={onNavigateBack} type="button" aria-label="Back"> <button
className="mobile-terminal-toolbtn"
onClick={onNavigateBack}
type="button"
aria-label="Back"
>
<Icon name="arrow-left" size="sm" /> <Icon name="arrow-left" size="sm" />
</button> </button>
</div> </div>
<div className="mobile-terminal-toolbar-center"> <div className="mobile-terminal-toolbar-center">
<span className="mobile-terminal-title">{activeSession?.name || "Terminal"}</span> <span className="mobile-terminal-title">
<span className={`mobile-terminal-status status-dot ${status}`} aria-label={`Connection status: ${status}`} /> {activeSession?.name || "Terminal"}
</span>
<span
className={`mobile-terminal-status status-dot ${status}`}
aria-label={`Connection status: ${status}`}
/>
</div> </div>
<div className="mobile-terminal-toolbar-right"> <div className="mobile-terminal-toolbar-right">
<button className="mobile-terminal-toolbtn" onClick={() => onFontSizeChange(-1)} type="button" aria-label="Decrease font size"> <button
className="mobile-terminal-toolbtn"
onClick={() => onFontSizeChange(-1)}
type="button"
aria-label="Decrease font size"
>
<span style={{ fontSize: "0.75rem" }}>A-</span> <span style={{ fontSize: "0.75rem" }}>A-</span>
</button> </button>
<button className="mobile-terminal-toolbtn" onClick={() => onFontSizeChange(1)} type="button" aria-label="Increase font size"> <button
className="mobile-terminal-toolbtn"
onClick={() => onFontSizeChange(1)}
type="button"
aria-label="Increase font size"
>
<span style={{ fontSize: "1rem" }}>A+</span> <span style={{ fontSize: "1rem" }}>A+</span>
</button> </button>
<button className="mobile-terminal-toolbtn" onClick={onNavigateBack} type="button" aria-label="Exit terminal"> <button
className="mobile-terminal-toolbtn"
onClick={onNavigateBack}
type="button"
aria-label="Exit terminal"
>
<Icon name="close" size="sm" /> <Icon name="close" size="sm" />
</button> </button>
</div> </div>
File diff suppressed because it is too large Load Diff
+328 -305
View File
@@ -10,336 +10,359 @@ import type { TerminalSession } from "../api/terminal";
import type { ModifierKey } from "./use-special-keys"; import type { ModifierKey } from "./use-special-keys";
const SESSIONS_TO_INFO = (sessions: TerminalSession[]): TerminalSessionInfo[] => const SESSIONS_TO_INFO = (sessions: TerminalSession[]): TerminalSessionInfo[] =>
sessions.map((s) => ({ sessions.map((s) => ({
id: s.id, id: s.id,
name: s.name, name: s.name,
status: s.status as TerminalSessionInfo["status"], status: s.status as TerminalSessionInfo["status"],
})); }));
type TerminalStatus = type TerminalStatus =
| "connecting" | "connecting"
| "connected" | "connected"
| "disconnected" | "disconnected"
| "error" | "error"
| "resetting"; | "resetting";
export const useTerminalPage = () => { export const useTerminalPage = () => {
const { instanceId } = useParams<{ instanceId: string }>(); const { instanceId } = useParams<{ instanceId: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const isMobile = useMobileViewport(); const isMobile = useMobileViewport();
const [isFullscreen, setIsFullscreen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false);
const terminalRefs = useRef<Record<string, React.RefObject<TerminalRef>>>({}); const terminalRefs = useRef<Record<string, React.RefObject<TerminalRef>>>({});
const headerAutoHide = useAutoHide({ timeout: 3000, enabled: isMobile }); const headerAutoHide = useAutoHide({ timeout: 3000, enabled: isMobile });
const [terminalStatuses, setTerminalStatuses] = useState< const [terminalStatuses, setTerminalStatuses] = useState<
Record<string, TerminalStatus> Record<string, TerminalStatus>
>({}); >({});
const changeFontSizeRef = useRef<((delta: number) => void) | null>(null); const changeFontSizeRefs = useRef<
const sendDataRef = useRef<((data: string) => void) | null>(null); Record<string, ((delta: number) => void) | null>
const focusInputRef = useRef<(() => void) | null>(null); >({});
const [showResetConfirm, setShowResetConfirm] = useState(false); const sendDataRefs = useRef<Record<string, ((data: string) => void) | null>>(
const [showSpecialKeysPanel, setShowSpecialKeysPanel] = useState(false); {},
const [activeModifier, setActiveModifier] = useState<ModifierKey | null>( );
null, const focusInputRefs = useRef<Record<string, (() => void) | null>>({});
); const [showResetConfirm, setShowResetConfirm] = useState(false);
const { isOpen: isKeyboardOpen, height: keyboardHeight } = const [showSpecialKeysPanel, setShowSpecialKeysPanel] = useState(false);
useVirtualKeyboard(); const [activeModifier, setActiveModifier] = useState<ModifierKey | null>(
null,
);
const { isOpen: isKeyboardOpen, height: keyboardHeight } =
useVirtualKeyboard();
const [instanceInfo, setInstanceInfo] = useState<{ const [instanceInfo, setInstanceInfo] = useState<{
display_name: string; display_name: string;
workspace_name?: string | null; workspace_name?: string | null;
tool_type_name: string; tool_type_name: string;
} | null>(null); } | null>(null);
const { const {
sessions, sessions,
activeSessionId, activeSessionId,
setActiveSessionId, setActiveSessionId,
createSession, createSession,
closeSession, closeSession,
renameSession, renameSession,
resetSession, resetSession,
loading, loading,
error, error,
} = useTerminalSessions(instanceId ?? ""); } = useTerminalSessions(instanceId ?? "");
// Fetch instance details for tab title // Fetch instance details for tab title
useEffect(() => { useEffect(() => {
if (!instanceId) return; if (!instanceId) return;
const load = async () => { const load = async () => {
try { try {
const { getUserSessions } = await import("../api/sessions"); const { getUserSessions } = await import("../api/sessions");
const allSessions = await getUserSessions(); const allSessions = await getUserSessions();
const match = allSessions.find((s) => s.id === instanceId); const match = allSessions.find((s) => s.id === instanceId);
if (match) { if (match) {
setInstanceInfo({ setInstanceInfo({
display_name: match.display_name, display_name: match.display_name,
workspace_name: match.workspace_name, workspace_name: match.workspace_name,
tool_type_name: match.tool_type_name, tool_type_name: match.tool_type_name,
}); });
} }
} catch { } catch {
// ignore // ignore
} }
}; };
void load(); void load();
}, [instanceId]); }, [instanceId]);
// Auto-create default session // Auto-create default session
useEffect(() => { useEffect(() => {
if (!loading && sessions.length === 0 && !error && instanceId) { if (!loading && sessions.length === 0 && !error && instanceId) {
void createSession("Session 1"); void createSession("Session 1");
} }
}, [loading, sessions.length, error, instanceId, createSession]); }, [loading, sessions.length, error, instanceId, createSession]);
// Update document title based on active terminal session // Update document title based on active terminal session
useEffect(() => { useEffect(() => {
if (!instanceId) { if (!instanceId) {
document.title = "Terminal"; document.title = "Terminal";
return; return;
} }
const active = sessions.find((s) => s.id === activeSessionId); const active = sessions.find((s) => s.id === activeSessionId);
const baseName = instanceInfo const baseName = instanceInfo
? `${instanceInfo.workspace_name ?? instanceInfo.display_name} · ${instanceInfo.tool_type_name}` ? `${instanceInfo.workspace_name ?? instanceInfo.display_name} · ${instanceInfo.tool_type_name}`
: `Instance ${instanceId.slice(0, 8)}`; : `Instance ${instanceId.slice(0, 8)}`;
if (sessions.length <= 1) { if (sessions.length <= 1) {
document.title = baseName; document.title = baseName;
} else { } else {
const sessionName = active?.name ?? "Session"; const sessionName = active?.name ?? "Session";
document.title = `${baseName} ${sessionName}`; document.title = `${baseName} ${sessionName}`;
} }
return () => { return () => {
document.title = "Headquarter"; document.title = "Headquarter";
}; };
}, [instanceId, activeSessionId, sessions, instanceInfo]); }, [instanceId, activeSessionId, sessions, instanceInfo]);
// Sync refs with sessions // Sync refs with sessions
useEffect(() => { useEffect(() => {
for (const session of sessions) { for (const session of sessions) {
if (!terminalRefs.current[session.id]) { if (!terminalRefs.current[session.id]) {
terminalRefs.current[session.id] = React.createRef<TerminalRef>(); terminalRefs.current[session.id] = React.createRef<TerminalRef>();
} }
} }
const currentIds = new Set(sessions.map((s) => s.id)); const currentIds = new Set(sessions.map((s) => s.id));
for (const id of Object.keys(terminalRefs.current)) { for (const id of Object.keys(terminalRefs.current)) {
if (!currentIds.has(id)) { if (!currentIds.has(id)) {
delete terminalRefs.current[id]; delete terminalRefs.current[id];
} delete sendDataRefs.current[id];
} delete focusInputRefs.current[id];
}, [sessions]); delete changeFontSizeRefs.current[id];
}
}
setTerminalStatuses((prev) => {
const next: Record<string, TerminalStatus> = {};
for (const id of currentIds) {
if (prev[id]) {
next[id] = prev[id];
}
}
return next;
});
}, [sessions]);
// Fit and focus active terminal // Fit and focus active terminal
useEffect(() => { useEffect(() => {
if (activeSessionId && terminalRefs.current[activeSessionId]) { if (activeSessionId && terminalRefs.current[activeSessionId]) {
const ref = terminalRefs.current[activeSessionId]; const ref = terminalRefs.current[activeSessionId];
let raf1 = 0; let raf1 = 0;
let raf2 = 0; let raf2 = 0;
raf1 = requestAnimationFrame(() => { raf1 = requestAnimationFrame(() => {
raf2 = requestAnimationFrame(() => { raf2 = requestAnimationFrame(() => {
ref.current?.fit(); ref.current?.fit();
ref.current?.focus(); ref.current?.focus();
}); });
}); });
return () => { return () => {
cancelAnimationFrame(raf1); cancelAnimationFrame(raf1);
cancelAnimationFrame(raf2); cancelAnimationFrame(raf2);
}; };
} }
}, [activeSessionId]); }, [activeSessionId]);
// Keyboard shortcuts // Keyboard shortcuts
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
const isAltShift = e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey; const isAltShift = e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey;
if (!isAltShift) return; if (!isAltShift) return;
switch (e.key.toLowerCase()) { switch (e.key.toLowerCase()) {
case "n": case "n":
e.preventDefault(); e.preventDefault();
if (sessions.length < 5) { if (sessions.length < 5) {
void createSession(`Session ${sessions.length + 1}`); void createSession(`Session ${sessions.length + 1}`);
} }
break; break;
case "w": case "w":
e.preventDefault(); e.preventDefault();
if ( if (
activeSessionId && activeSessionId &&
window.confirm("Close this terminal session?") window.confirm("Close this terminal session?")
) { ) {
void closeSession(activeSessionId); void closeSession(activeSessionId);
} }
break; break;
case "arrowleft": case "arrowleft":
e.preventDefault(); e.preventDefault();
if (activeSessionId) { if (activeSessionId) {
const idx = sessions.findIndex((s) => s.id === activeSessionId); const idx = sessions.findIndex((s) => s.id === activeSessionId);
if (idx > 0) setActiveSessionId(sessions[idx - 1].id); if (idx > 0) setActiveSessionId(sessions[idx - 1].id);
} }
break; break;
case "arrowright": case "arrowright":
e.preventDefault(); e.preventDefault();
if (activeSessionId) { if (activeSessionId) {
const idx = sessions.findIndex((s) => s.id === activeSessionId); const idx = sessions.findIndex((s) => s.id === activeSessionId);
if (idx < sessions.length - 1) if (idx < sessions.length - 1)
setActiveSessionId(sessions[idx + 1].id); setActiveSessionId(sessions[idx + 1].id);
} }
break; break;
case "r": case "r":
e.preventDefault(); e.preventDefault();
if (activeSessionId) void resetSession(activeSessionId); if (activeSessionId) void resetSession(activeSessionId);
break; break;
case "f": case "f":
e.preventDefault(); e.preventDefault();
setIsFullscreen((prev) => !prev); setIsFullscreen((prev) => !prev);
break; break;
} }
}; };
window.addEventListener("keydown", handleKeyDown); window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown);
}, [ }, [
sessions, sessions,
activeSessionId, activeSessionId,
createSession, createSession,
closeSession, closeSession,
resetSession, resetSession,
setActiveSessionId, setActiveSessionId,
]); ]);
// Keep screen awake // Keep screen awake
useEffect(() => { useEffect(() => {
let wakeLock: WakeLockSentinel | null = null; let wakeLock: WakeLockSentinel | null = null;
const requestWakeLock = async () => { const requestWakeLock = async () => {
try { try {
if ("wakeLock" in navigator) { if ("wakeLock" in navigator) {
wakeLock = await navigator.wakeLock.request("screen"); wakeLock = await navigator.wakeLock.request("screen");
} }
} catch { } catch {
// ignore // ignore
} }
}; };
void requestWakeLock(); void requestWakeLock();
const handleVisibilityChange = () => { const handleVisibilityChange = () => {
if (document.visibilityState === "visible") void requestWakeLock(); if (document.visibilityState === "visible") void requestWakeLock();
}; };
document.addEventListener("visibilitychange", handleVisibilityChange); document.addEventListener("visibilitychange", handleVisibilityChange);
return () => { return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange); document.removeEventListener("visibilitychange", handleVisibilityChange);
wakeLock?.release().catch(() => {}); wakeLock?.release().catch(() => {});
}; };
}, []); }, []);
// Lock page scroll on mobile // Lock page scroll on mobile
useEffect(() => { useEffect(() => {
if (!isMobile) return; if (!isMobile) return;
document.documentElement.classList.add("terminal-page-open"); document.documentElement.classList.add("terminal-page-open");
document.body.classList.add("terminal-page-open"); document.body.classList.add("terminal-page-open");
return () => { return () => {
document.documentElement.classList.remove("terminal-page-open"); document.documentElement.classList.remove("terminal-page-open");
document.body.classList.remove("terminal-page-open"); document.body.classList.remove("terminal-page-open");
}; };
}, [isMobile]); }, [isMobile]);
const handleFullscreenClick = useCallback( const handleFullscreenClick = useCallback(
(e: React.MouseEvent<HTMLElement>) => { (e: React.MouseEvent<HTMLElement>) => {
if (!isFullscreen) return; if (!isFullscreen) return;
const target = e.target as Node; const target = e.target as Node;
const current = e.currentTarget as HTMLElement; const current = e.currentTarget as HTMLElement;
const content = current.querySelector(".terminal-page-content"); const content = current.querySelector(".terminal-page-content");
const header = current.querySelector(".terminal-fullscreen-header"); const header = current.querySelector(".terminal-fullscreen-header");
if (content?.contains(target) || header?.contains(target)) return; if (content?.contains(target) || header?.contains(target)) return;
setIsFullscreen(false); setIsFullscreen(false);
}, },
[isFullscreen], [isFullscreen],
); );
const handleSelect = useCallback( const handleSelect = useCallback(
(sessionId: string) => setActiveSessionId(sessionId), (sessionId: string) => setActiveSessionId(sessionId),
[setActiveSessionId], [setActiveSessionId],
); );
const handleClose = useCallback( const handleClose = useCallback(
async (sessionId: string) => closeSession(sessionId), async (sessionId: string) => closeSession(sessionId),
[closeSession], [closeSession],
); );
const handleCreate = useCallback(() => { const handleCreate = useCallback(() => {
void createSession(`Session ${sessions.length + 1}`); void createSession(`Session ${sessions.length + 1}`);
}, [createSession, sessions.length]); }, [createSession, sessions.length]);
const handleRename = useCallback( const handleRename = useCallback(
(sessionId: string, newName: string) => { (sessionId: string, newName: string) => {
void renameSession(sessionId, newName); void renameSession(sessionId, newName);
}, },
[renameSession], [renameSession],
); );
const handleTerminalReady = useCallback( const handleTerminalReady = useCallback(
( (
sendData: (data: string) => void, sessionId: string | undefined,
status: TerminalStatus, sendData: (data: string) => void,
focusInput: () => void, status: TerminalStatus,
changeFontSize: (delta: number) => void, focusInput: () => void,
) => { changeFontSize: (delta: number) => void,
setTerminalStatuses((prev) => ({ ) => {
...prev, const key = sessionId ?? "default";
[activeSessionId ?? "default"]: status, setTerminalStatuses((prev) => ({
})); ...prev,
sendDataRef.current = sendData; [key]: status,
focusInputRef.current = focusInput; }));
changeFontSizeRef.current = changeFontSize; sendDataRefs.current[key] = sendData;
}, focusInputRefs.current[key] = focusInput;
[activeSessionId], changeFontSizeRefs.current[key] = changeFontSize;
); },
[],
);
const handleFontSizeChange = useCallback((delta: number) => { const handleFontSizeChange = useCallback(
changeFontSizeRef.current?.(delta); (delta: number) => {
}, []); const key = activeSessionId ?? "default";
changeFontSizeRefs.current[key]?.(delta);
},
[activeSessionId],
);
const handleSendKey = useCallback((data: string) => { const handleSendKey = useCallback(
sendDataRef.current?.(data); (data: string) => {
}, []); const key = activeSessionId ?? "default";
sendDataRefs.current[key]?.(data);
},
[activeSessionId],
);
const handleReset = useCallback(() => { const handleReset = useCallback(() => {
if (activeSessionId && terminalRefs.current[activeSessionId]) { if (activeSessionId && terminalRefs.current[activeSessionId]) {
terminalRefs.current[activeSessionId].current?.reset(); terminalRefs.current[activeSessionId].current?.reset();
} }
}, [activeSessionId]); }, [activeSessionId]);
return { return {
instanceId, instanceId,
navigate, navigate,
isMobile, isMobile,
isFullscreen, isFullscreen,
setIsFullscreen, setIsFullscreen,
terminalRefs, terminalRefs,
headerAutoHide, headerAutoHide,
terminalStatuses, terminalStatuses,
sendDataRef, showResetConfirm,
focusInputRef, setShowResetConfirm,
changeFontSizeRef, showSpecialKeysPanel,
showResetConfirm, setShowSpecialKeysPanel,
setShowResetConfirm, activeModifier,
showSpecialKeysPanel, setActiveModifier,
setShowSpecialKeysPanel, isKeyboardOpen,
activeModifier, keyboardHeight,
setActiveModifier, sessions,
isKeyboardOpen, activeSessionId,
keyboardHeight, setActiveSessionId,
sessions, loading,
activeSessionId, error,
setActiveSessionId, handleFullscreenClick,
loading, handleSelect,
error, handleClose,
handleFullscreenClick, handleCreate,
handleSelect, handleRename,
handleClose, handleTerminalReady,
handleCreate, handleFontSizeChange,
handleRename, handleSendKey,
handleTerminalReady, handleReset,
handleFontSizeChange, sessionInfos: SESSIONS_TO_INFO(sessions),
handleSendKey, };
handleReset,
sessionInfos: SESSIONS_TO_INFO(sessions),
};
}; };