mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-07 13:55:38 +08:00
Refactor Settings, FileTree, GitPanel, Shell, and CodeEditor components (#402)
This commit is contained in:
229
src/components/shell/hooks/useShellConnection.ts
Normal file
229
src/components/shell/hooks/useShellConnection.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { MutableRefObject } from 'react';
|
||||
import type { FitAddon } from '@xterm/addon-fit';
|
||||
import type { Terminal } from '@xterm/xterm';
|
||||
import type { Project, ProjectSession } from '../../../types/app';
|
||||
import { TERMINAL_INIT_DELAY_MS } from '../constants/constants';
|
||||
import { getShellWebSocketUrl, parseShellMessage, sendSocketMessage } from '../utils/socket';
|
||||
|
||||
const ANSI_ESCAPE_REGEX =
|
||||
/(?:\u001B\[[0-?]*[ -/]*[@-~]|\u009B[0-?]*[ -/]*[@-~]|\u001B\][^\u0007\u001B]*(?:\u0007|\u001B\\)|\u009D[^\u0007\u009C]*(?:\u0007|\u009C)|\u001B[PX^_][^\u001B]*\u001B\\|[\u0090\u0098\u009E\u009F][^\u009C]*\u009C|\u001B[@-Z\\-_])/g;
|
||||
const PROCESS_EXIT_REGEX = /Process exited with code (\d+)/;
|
||||
|
||||
type UseShellConnectionOptions = {
|
||||
wsRef: MutableRefObject<WebSocket | null>;
|
||||
terminalRef: MutableRefObject<Terminal | null>;
|
||||
fitAddonRef: MutableRefObject<FitAddon | null>;
|
||||
selectedProjectRef: MutableRefObject<Project | null | undefined>;
|
||||
selectedSessionRef: MutableRefObject<ProjectSession | null | undefined>;
|
||||
initialCommandRef: MutableRefObject<string | null | undefined>;
|
||||
isPlainShellRef: MutableRefObject<boolean>;
|
||||
onProcessCompleteRef: MutableRefObject<((exitCode: number) => void) | null | undefined>;
|
||||
isInitialized: boolean;
|
||||
autoConnect: boolean;
|
||||
closeSocket: () => void;
|
||||
clearTerminalScreen: () => void;
|
||||
setAuthUrl: (nextAuthUrl: string) => void;
|
||||
};
|
||||
|
||||
type UseShellConnectionResult = {
|
||||
isConnected: boolean;
|
||||
isConnecting: boolean;
|
||||
closeSocket: () => void;
|
||||
connectToShell: () => void;
|
||||
disconnectFromShell: () => void;
|
||||
};
|
||||
|
||||
export function useShellConnection({
|
||||
wsRef,
|
||||
terminalRef,
|
||||
fitAddonRef,
|
||||
selectedProjectRef,
|
||||
selectedSessionRef,
|
||||
initialCommandRef,
|
||||
isPlainShellRef,
|
||||
onProcessCompleteRef,
|
||||
isInitialized,
|
||||
autoConnect,
|
||||
closeSocket,
|
||||
clearTerminalScreen,
|
||||
setAuthUrl,
|
||||
}: UseShellConnectionOptions): UseShellConnectionResult {
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
const connectingRef = useRef(false);
|
||||
|
||||
const handleProcessCompletion = useCallback(
|
||||
(output: string) => {
|
||||
if (!isPlainShellRef.current || !onProcessCompleteRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sanitizedOutput = output.replace(ANSI_ESCAPE_REGEX, '');
|
||||
const cleanOutput = sanitizedOutput;
|
||||
if (cleanOutput.includes('Process exited with code 0')) {
|
||||
onProcessCompleteRef.current(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const match = cleanOutput.match(PROCESS_EXIT_REGEX);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exitCode = Number.parseInt(match[1], 10);
|
||||
if (!Number.isNaN(exitCode) && exitCode !== 0) {
|
||||
onProcessCompleteRef.current(exitCode);
|
||||
}
|
||||
},
|
||||
[isPlainShellRef, onProcessCompleteRef],
|
||||
);
|
||||
|
||||
const handleSocketMessage = useCallback(
|
||||
(rawPayload: string) => {
|
||||
const message = parseShellMessage(rawPayload);
|
||||
if (!message) {
|
||||
console.error('[Shell] Error handling WebSocket message:', rawPayload);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === 'output') {
|
||||
const output = typeof message.data === 'string' ? message.data : '';
|
||||
handleProcessCompletion(output);
|
||||
terminalRef.current?.write(output);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === 'auth_url' || message.type === 'url_open') {
|
||||
const nextAuthUrl = typeof message.url === 'string' ? message.url : '';
|
||||
if (nextAuthUrl) {
|
||||
setAuthUrl(nextAuthUrl);
|
||||
}
|
||||
}
|
||||
},
|
||||
[handleProcessCompletion, setAuthUrl, terminalRef],
|
||||
);
|
||||
|
||||
const connectWebSocket = useCallback(
|
||||
(isConnectionLocked = false) => {
|
||||
if ((connectingRef.current && !isConnectionLocked) || isConnecting || isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const wsUrl = getShellWebSocketUrl();
|
||||
if (!wsUrl) {
|
||||
connectingRef.current = false;
|
||||
setIsConnecting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
connectingRef.current = true;
|
||||
|
||||
const socket = new WebSocket(wsUrl);
|
||||
wsRef.current = socket;
|
||||
|
||||
socket.onopen = () => {
|
||||
setIsConnected(true);
|
||||
setIsConnecting(false);
|
||||
connectingRef.current = false;
|
||||
setAuthUrl('');
|
||||
|
||||
window.setTimeout(() => {
|
||||
const currentTerminal = terminalRef.current;
|
||||
const currentFitAddon = fitAddonRef.current;
|
||||
const currentProject = selectedProjectRef.current;
|
||||
if (!currentTerminal || !currentFitAddon || !currentProject) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentFitAddon.fit();
|
||||
|
||||
sendSocketMessage(socket, {
|
||||
type: 'init',
|
||||
projectPath: currentProject.fullPath || currentProject.path || '',
|
||||
sessionId: isPlainShellRef.current ? null : selectedSessionRef.current?.id || null,
|
||||
hasSession: isPlainShellRef.current ? false : Boolean(selectedSessionRef.current),
|
||||
provider: isPlainShellRef.current ? 'plain-shell' : (selectedSessionRef.current?.__provider || localStorage.getItem('selected-provider') || 'claude'),
|
||||
cols: currentTerminal.cols,
|
||||
rows: currentTerminal.rows,
|
||||
initialCommand: initialCommandRef.current,
|
||||
isPlainShell: isPlainShellRef.current,
|
||||
});
|
||||
}, TERMINAL_INIT_DELAY_MS);
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
const rawPayload = typeof event.data === 'string' ? event.data : String(event.data ?? '');
|
||||
handleSocketMessage(rawPayload);
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
setIsConnected(false);
|
||||
setIsConnecting(false);
|
||||
connectingRef.current = false;
|
||||
clearTerminalScreen();
|
||||
};
|
||||
|
||||
socket.onerror = () => {
|
||||
setIsConnected(false);
|
||||
setIsConnecting(false);
|
||||
connectingRef.current = false;
|
||||
};
|
||||
} catch {
|
||||
setIsConnected(false);
|
||||
setIsConnecting(false);
|
||||
connectingRef.current = false;
|
||||
}
|
||||
},
|
||||
[
|
||||
clearTerminalScreen,
|
||||
fitAddonRef,
|
||||
handleSocketMessage,
|
||||
initialCommandRef,
|
||||
isConnected,
|
||||
isConnecting,
|
||||
isPlainShellRef,
|
||||
selectedProjectRef,
|
||||
selectedSessionRef,
|
||||
setAuthUrl,
|
||||
terminalRef,
|
||||
wsRef,
|
||||
],
|
||||
);
|
||||
|
||||
const connectToShell = useCallback(() => {
|
||||
if (!isInitialized || isConnected || isConnecting || connectingRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
connectingRef.current = true;
|
||||
setIsConnecting(true);
|
||||
connectWebSocket(true);
|
||||
}, [connectWebSocket, isConnected, isConnecting, isInitialized]);
|
||||
|
||||
const disconnectFromShell = useCallback(() => {
|
||||
closeSocket();
|
||||
clearTerminalScreen();
|
||||
setIsConnected(false);
|
||||
setIsConnecting(false);
|
||||
connectingRef.current = false;
|
||||
setAuthUrl('');
|
||||
}, [clearTerminalScreen, closeSocket, setAuthUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoConnect || !isInitialized || isConnecting || isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
connectToShell();
|
||||
}, [autoConnect, connectToShell, isConnected, isConnecting, isInitialized]);
|
||||
|
||||
return {
|
||||
isConnected,
|
||||
isConnecting,
|
||||
closeSocket,
|
||||
connectToShell,
|
||||
disconnectFromShell,
|
||||
};
|
||||
}
|
||||
162
src/components/shell/hooks/useShellRuntime.ts
Normal file
162
src/components/shell/hooks/useShellRuntime.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { FitAddon } from '@xterm/addon-fit';
|
||||
import type { Terminal } from '@xterm/xterm';
|
||||
import { useShellConnection } from './useShellConnection';
|
||||
import { useShellTerminal } from './useShellTerminal';
|
||||
import type { UseShellRuntimeOptions, UseShellRuntimeResult } from '../types/types';
|
||||
import { copyTextToClipboard } from '../../../utils/clipboard';
|
||||
|
||||
export function useShellRuntime({
|
||||
selectedProject,
|
||||
selectedSession,
|
||||
initialCommand,
|
||||
isPlainShell,
|
||||
minimal,
|
||||
autoConnect,
|
||||
isRestarting,
|
||||
onProcessComplete,
|
||||
}: UseShellRuntimeOptions): UseShellRuntimeResult {
|
||||
const terminalContainerRef = useRef<HTMLDivElement>(null);
|
||||
const terminalRef = useRef<Terminal | null>(null);
|
||||
const fitAddonRef = useRef<FitAddon | null>(null);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
|
||||
const [authUrl, setAuthUrl] = useState('');
|
||||
const [authUrlVersion, setAuthUrlVersion] = useState(0);
|
||||
|
||||
const selectedProjectRef = useRef(selectedProject);
|
||||
const selectedSessionRef = useRef(selectedSession);
|
||||
const initialCommandRef = useRef(initialCommand);
|
||||
const isPlainShellRef = useRef(isPlainShell);
|
||||
const onProcessCompleteRef = useRef(onProcessComplete);
|
||||
const authUrlRef = useRef('');
|
||||
const lastSessionIdRef = useRef<string | null>(selectedSession?.id ?? null);
|
||||
|
||||
// Keep mutable values in refs so websocket handlers always read current data.
|
||||
useEffect(() => {
|
||||
selectedProjectRef.current = selectedProject;
|
||||
selectedSessionRef.current = selectedSession;
|
||||
initialCommandRef.current = initialCommand;
|
||||
isPlainShellRef.current = isPlainShell;
|
||||
onProcessCompleteRef.current = onProcessComplete;
|
||||
}, [selectedProject, selectedSession, initialCommand, isPlainShell, onProcessComplete]);
|
||||
|
||||
const setCurrentAuthUrl = useCallback((nextAuthUrl: string) => {
|
||||
authUrlRef.current = nextAuthUrl;
|
||||
setAuthUrl(nextAuthUrl);
|
||||
setAuthUrlVersion((previous) => previous + 1);
|
||||
}, []);
|
||||
|
||||
const closeSocket = useCallback(() => {
|
||||
const activeSocket = wsRef.current;
|
||||
if (!activeSocket) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
activeSocket.readyState === WebSocket.OPEN ||
|
||||
activeSocket.readyState === WebSocket.CONNECTING
|
||||
) {
|
||||
activeSocket.close();
|
||||
}
|
||||
|
||||
wsRef.current = null;
|
||||
}, []);
|
||||
|
||||
const openAuthUrlInBrowser = useCallback((url = authUrlRef.current) => {
|
||||
if (!url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const popup = window.open(url, '_blank');
|
||||
if (popup) {
|
||||
try {
|
||||
popup.opener = null;
|
||||
} catch {
|
||||
// Ignore cross-origin restrictions when trying to null opener.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, []);
|
||||
|
||||
const copyAuthUrlToClipboard = useCallback(async (url = authUrlRef.current) => {
|
||||
if (!url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return copyTextToClipboard(url);
|
||||
}, []);
|
||||
|
||||
const { isInitialized, clearTerminalScreen, disposeTerminal } = useShellTerminal({
|
||||
terminalContainerRef,
|
||||
terminalRef,
|
||||
fitAddonRef,
|
||||
wsRef,
|
||||
selectedProject,
|
||||
minimal,
|
||||
isRestarting,
|
||||
initialCommandRef,
|
||||
isPlainShellRef,
|
||||
authUrlRef,
|
||||
copyAuthUrlToClipboard,
|
||||
closeSocket,
|
||||
});
|
||||
|
||||
const { isConnected, isConnecting, connectToShell, disconnectFromShell } = useShellConnection({
|
||||
wsRef,
|
||||
terminalRef,
|
||||
fitAddonRef,
|
||||
selectedProjectRef,
|
||||
selectedSessionRef,
|
||||
initialCommandRef,
|
||||
isPlainShellRef,
|
||||
onProcessCompleteRef,
|
||||
isInitialized,
|
||||
autoConnect,
|
||||
closeSocket,
|
||||
clearTerminalScreen,
|
||||
setAuthUrl: setCurrentAuthUrl,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isRestarting) {
|
||||
return;
|
||||
}
|
||||
|
||||
disconnectFromShell();
|
||||
disposeTerminal();
|
||||
}, [disconnectFromShell, disposeTerminal, isRestarting]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProject) {
|
||||
return;
|
||||
}
|
||||
|
||||
disconnectFromShell();
|
||||
disposeTerminal();
|
||||
}, [disconnectFromShell, disposeTerminal, selectedProject]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentSessionId = selectedSession?.id ?? null;
|
||||
if (lastSessionIdRef.current !== currentSessionId && isInitialized) {
|
||||
disconnectFromShell();
|
||||
}
|
||||
|
||||
lastSessionIdRef.current = currentSessionId;
|
||||
}, [disconnectFromShell, isInitialized, selectedSession?.id]);
|
||||
|
||||
return {
|
||||
terminalContainerRef,
|
||||
isConnected,
|
||||
isInitialized,
|
||||
isConnecting,
|
||||
authUrl,
|
||||
authUrlVersion,
|
||||
connectToShell,
|
||||
disconnectFromShell,
|
||||
openAuthUrlInBrowser,
|
||||
copyAuthUrlToClipboard,
|
||||
};
|
||||
}
|
||||
245
src/components/shell/hooks/useShellTerminal.ts
Normal file
245
src/components/shell/hooks/useShellTerminal.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { MutableRefObject, RefObject } from 'react';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||||
import { WebglAddon } from '@xterm/addon-webgl';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import type { Project } from '../../../types/app';
|
||||
import {
|
||||
CODEX_DEVICE_AUTH_URL,
|
||||
TERMINAL_INIT_DELAY_MS,
|
||||
TERMINAL_OPTIONS,
|
||||
TERMINAL_RESIZE_DELAY_MS,
|
||||
} from '../constants/constants';
|
||||
import { isCodexLoginCommand } from '../utils/auth';
|
||||
import { sendSocketMessage } from '../utils/socket';
|
||||
import { ensureXtermFocusStyles } from '../utils/terminalStyles';
|
||||
|
||||
type UseShellTerminalOptions = {
|
||||
terminalContainerRef: RefObject<HTMLDivElement>;
|
||||
terminalRef: MutableRefObject<Terminal | null>;
|
||||
fitAddonRef: MutableRefObject<FitAddon | null>;
|
||||
wsRef: MutableRefObject<WebSocket | null>;
|
||||
selectedProject: Project | null | undefined;
|
||||
minimal: boolean;
|
||||
isRestarting: boolean;
|
||||
initialCommandRef: MutableRefObject<string | null | undefined>;
|
||||
isPlainShellRef: MutableRefObject<boolean>;
|
||||
authUrlRef: MutableRefObject<string>;
|
||||
copyAuthUrlToClipboard: (url?: string) => Promise<boolean>;
|
||||
closeSocket: () => void;
|
||||
};
|
||||
|
||||
type UseShellTerminalResult = {
|
||||
isInitialized: boolean;
|
||||
clearTerminalScreen: () => void;
|
||||
disposeTerminal: () => void;
|
||||
};
|
||||
|
||||
export function useShellTerminal({
|
||||
terminalContainerRef,
|
||||
terminalRef,
|
||||
fitAddonRef,
|
||||
wsRef,
|
||||
selectedProject,
|
||||
minimal,
|
||||
isRestarting,
|
||||
initialCommandRef,
|
||||
isPlainShellRef,
|
||||
authUrlRef,
|
||||
copyAuthUrlToClipboard,
|
||||
closeSocket,
|
||||
}: UseShellTerminalOptions): UseShellTerminalResult {
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
const resizeTimeoutRef = useRef<number | null>(null);
|
||||
const selectedProjectKey = selectedProject?.fullPath || selectedProject?.path || '';
|
||||
const hasSelectedProject = Boolean(selectedProject);
|
||||
|
||||
useEffect(() => {
|
||||
ensureXtermFocusStyles();
|
||||
}, []);
|
||||
|
||||
const clearTerminalScreen = useCallback(() => {
|
||||
if (!terminalRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
terminalRef.current.clear();
|
||||
terminalRef.current.write('\x1b[2J\x1b[H');
|
||||
}, [terminalRef]);
|
||||
|
||||
const disposeTerminal = useCallback(() => {
|
||||
if (terminalRef.current) {
|
||||
terminalRef.current.dispose();
|
||||
terminalRef.current = null;
|
||||
}
|
||||
|
||||
fitAddonRef.current = null;
|
||||
setIsInitialized(false);
|
||||
}, [fitAddonRef, terminalRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!terminalContainerRef.current || !hasSelectedProject || isRestarting || terminalRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextTerminal = new Terminal(TERMINAL_OPTIONS);
|
||||
terminalRef.current = nextTerminal;
|
||||
|
||||
const nextFitAddon = new FitAddon();
|
||||
fitAddonRef.current = nextFitAddon;
|
||||
nextTerminal.loadAddon(nextFitAddon);
|
||||
|
||||
// Avoid wrapped partial links in compact login flows.
|
||||
if (!minimal) {
|
||||
nextTerminal.loadAddon(new WebLinksAddon());
|
||||
}
|
||||
|
||||
try {
|
||||
nextTerminal.loadAddon(new WebglAddon());
|
||||
} catch {
|
||||
console.warn('[Shell] WebGL renderer unavailable, using Canvas fallback');
|
||||
}
|
||||
|
||||
nextTerminal.open(terminalContainerRef.current);
|
||||
|
||||
nextTerminal.attachCustomKeyEventHandler((event) => {
|
||||
const activeAuthUrl = isCodexLoginCommand(initialCommandRef.current)
|
||||
? CODEX_DEVICE_AUTH_URL
|
||||
: authUrlRef.current;
|
||||
|
||||
if (
|
||||
event.type === 'keydown' &&
|
||||
minimal &&
|
||||
isPlainShellRef.current &&
|
||||
activeAuthUrl &&
|
||||
!event.ctrlKey &&
|
||||
!event.metaKey &&
|
||||
!event.altKey &&
|
||||
event.key?.toLowerCase() === 'c'
|
||||
) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
void copyAuthUrlToClipboard(activeAuthUrl);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === 'keydown' &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key?.toLowerCase() === 'c' &&
|
||||
nextTerminal.hasSelection()
|
||||
) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
document.execCommand('copy');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === 'keydown' &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key?.toLowerCase() === 'v'
|
||||
) {
|
||||
// Block native paste so data is only injected after clipboard-read resolves.
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (typeof navigator !== 'undefined' && navigator.clipboard?.readText) {
|
||||
navigator.clipboard
|
||||
.readText()
|
||||
.then((text) => {
|
||||
sendSocketMessage(wsRef.current, {
|
||||
type: 'input',
|
||||
data: text,
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
window.setTimeout(() => {
|
||||
const currentFitAddon = fitAddonRef.current;
|
||||
const currentTerminal = terminalRef.current;
|
||||
if (!currentFitAddon || !currentTerminal) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentFitAddon.fit();
|
||||
sendSocketMessage(wsRef.current, {
|
||||
type: 'resize',
|
||||
cols: currentTerminal.cols,
|
||||
rows: currentTerminal.rows,
|
||||
});
|
||||
}, TERMINAL_INIT_DELAY_MS);
|
||||
|
||||
setIsInitialized(true);
|
||||
|
||||
const dataSubscription = nextTerminal.onData((data) => {
|
||||
sendSocketMessage(wsRef.current, {
|
||||
type: 'input',
|
||||
data,
|
||||
});
|
||||
});
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
if (resizeTimeoutRef.current !== null) {
|
||||
window.clearTimeout(resizeTimeoutRef.current);
|
||||
}
|
||||
|
||||
resizeTimeoutRef.current = window.setTimeout(() => {
|
||||
const currentFitAddon = fitAddonRef.current;
|
||||
const currentTerminal = terminalRef.current;
|
||||
if (!currentFitAddon || !currentTerminal) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentFitAddon.fit();
|
||||
sendSocketMessage(wsRef.current, {
|
||||
type: 'resize',
|
||||
cols: currentTerminal.cols,
|
||||
rows: currentTerminal.rows,
|
||||
});
|
||||
}, TERMINAL_RESIZE_DELAY_MS);
|
||||
});
|
||||
|
||||
resizeObserver.observe(terminalContainerRef.current);
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
if (resizeTimeoutRef.current !== null) {
|
||||
window.clearTimeout(resizeTimeoutRef.current);
|
||||
resizeTimeoutRef.current = null;
|
||||
}
|
||||
dataSubscription.dispose();
|
||||
closeSocket();
|
||||
disposeTerminal();
|
||||
};
|
||||
}, [
|
||||
authUrlRef,
|
||||
closeSocket,
|
||||
copyAuthUrlToClipboard,
|
||||
disposeTerminal,
|
||||
fitAddonRef,
|
||||
initialCommandRef,
|
||||
isPlainShellRef,
|
||||
isRestarting,
|
||||
minimal,
|
||||
hasSelectedProject,
|
||||
selectedProjectKey,
|
||||
terminalContainerRef,
|
||||
terminalRef,
|
||||
wsRef,
|
||||
]);
|
||||
|
||||
return {
|
||||
isInitialized,
|
||||
clearTerminalScreen,
|
||||
disposeTerminal,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user