mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-07 23:17:37 +00:00
Refactor Settings, FileTree, GitPanel, Shell, and CodeEditor components (#402)
This commit is contained in:
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user