Merge branch 'fix/terminal-session-callbacks' into dev
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface TerminalProps {
|
|||||||
activeModifier?: ModifierKey | null;
|
activeModifier?: ModifierKey | null;
|
||||||
onModifierChange?: (modifier: ModifierKey | null) => void;
|
onModifierChange?: (modifier: ModifierKey | null) => void;
|
||||||
onTerminalReady?: (
|
onTerminalReady?: (
|
||||||
|
sessionId: string | undefined,
|
||||||
sendData: (data: string) => void,
|
sendData: (data: string) => void,
|
||||||
connectionStatus:
|
connectionStatus:
|
||||||
| "connecting"
|
| "connecting"
|
||||||
@@ -563,6 +564,7 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
|
|||||||
handleFontSizeChangeRef.current(delta);
|
handleFontSizeChangeRef.current(delta);
|
||||||
};
|
};
|
||||||
onTerminalReadyRef.current(
|
onTerminalReadyRef.current(
|
||||||
|
sessionId,
|
||||||
sendData,
|
sendData,
|
||||||
status,
|
status,
|
||||||
focusInput,
|
focusInput,
|
||||||
@@ -657,9 +659,15 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
|
|||||||
const changeFontSize = (delta: number) => {
|
const changeFontSize = (delta: number) => {
|
||||||
handleFontSizeChangeRef.current(delta);
|
handleFontSizeChangeRef.current(delta);
|
||||||
};
|
};
|
||||||
onTerminalReady(sendData, status, focusInput, changeFontSize);
|
onTerminalReady(
|
||||||
|
sessionId,
|
||||||
|
sendData,
|
||||||
|
status,
|
||||||
|
focusInput,
|
||||||
|
changeFontSize,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [status, onTerminalReady]);
|
}, [sessionId, status, onTerminalReady]);
|
||||||
|
|
||||||
const handleFontSizeChange = (delta: number) => {
|
const handleFontSizeChange = (delta: number) => {
|
||||||
const newSize = Math.max(
|
const newSize = Math.max(
|
||||||
|
|||||||
@@ -34,9 +34,13 @@ export const useTerminalPage = () => {
|
|||||||
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 sendDataRefs = useRef<Record<string, ((data: string) => void) | null>>(
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const focusInputRefs = useRef<Record<string, (() => void) | null>>({});
|
||||||
const [showResetConfirm, setShowResetConfirm] = useState(false);
|
const [showResetConfirm, setShowResetConfirm] = useState(false);
|
||||||
const [showSpecialKeysPanel, setShowSpecialKeysPanel] = useState(false);
|
const [showSpecialKeysPanel, setShowSpecialKeysPanel] = useState(false);
|
||||||
const [activeModifier, setActiveModifier] = useState<ModifierKey | null>(
|
const [activeModifier, setActiveModifier] = useState<ModifierKey | null>(
|
||||||
@@ -124,8 +128,20 @@ export const useTerminalPage = () => {
|
|||||||
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];
|
||||||
|
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]);
|
}, [sessions]);
|
||||||
|
|
||||||
// Fit and focus active terminal
|
// Fit and focus active terminal
|
||||||
@@ -276,29 +292,39 @@ export const useTerminalPage = () => {
|
|||||||
|
|
||||||
const handleTerminalReady = useCallback(
|
const handleTerminalReady = useCallback(
|
||||||
(
|
(
|
||||||
|
sessionId: string | undefined,
|
||||||
sendData: (data: string) => void,
|
sendData: (data: string) => void,
|
||||||
status: TerminalStatus,
|
status: TerminalStatus,
|
||||||
focusInput: () => void,
|
focusInput: () => void,
|
||||||
changeFontSize: (delta: number) => void,
|
changeFontSize: (delta: number) => void,
|
||||||
) => {
|
) => {
|
||||||
|
const key = sessionId ?? "default";
|
||||||
setTerminalStatuses((prev) => ({
|
setTerminalStatuses((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[activeSessionId ?? "default"]: status,
|
[key]: status,
|
||||||
}));
|
}));
|
||||||
sendDataRef.current = sendData;
|
sendDataRefs.current[key] = sendData;
|
||||||
focusInputRef.current = focusInput;
|
focusInputRefs.current[key] = focusInput;
|
||||||
changeFontSizeRef.current = changeFontSize;
|
changeFontSizeRefs.current[key] = changeFontSize;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFontSizeChange = useCallback(
|
||||||
|
(delta: number) => {
|
||||||
|
const key = activeSessionId ?? "default";
|
||||||
|
changeFontSizeRefs.current[key]?.(delta);
|
||||||
},
|
},
|
||||||
[activeSessionId],
|
[activeSessionId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFontSizeChange = useCallback((delta: number) => {
|
const handleSendKey = useCallback(
|
||||||
changeFontSizeRef.current?.(delta);
|
(data: string) => {
|
||||||
}, []);
|
const key = activeSessionId ?? "default";
|
||||||
|
sendDataRefs.current[key]?.(data);
|
||||||
const handleSendKey = useCallback((data: string) => {
|
},
|
||||||
sendDataRef.current?.(data);
|
[activeSessionId],
|
||||||
}, []);
|
);
|
||||||
|
|
||||||
const handleReset = useCallback(() => {
|
const handleReset = useCallback(() => {
|
||||||
if (activeSessionId && terminalRefs.current[activeSessionId]) {
|
if (activeSessionId && terminalRefs.current[activeSessionId]) {
|
||||||
@@ -315,9 +341,6 @@ export const useTerminalPage = () => {
|
|||||||
terminalRefs,
|
terminalRefs,
|
||||||
headerAutoHide,
|
headerAutoHide,
|
||||||
terminalStatuses,
|
terminalStatuses,
|
||||||
sendDataRef,
|
|
||||||
focusInputRef,
|
|
||||||
changeFontSizeRef,
|
|
||||||
showResetConfirm,
|
showResetConfirm,
|
||||||
setShowResetConfirm,
|
setShowResetConfirm,
|
||||||
showSpecialKeysPanel,
|
showSpecialKeysPanel,
|
||||||
|
|||||||
Reference in New Issue
Block a user