Skip to content

Commit 7e431f3

Browse files
committed
feat: enhance Terminal component with connection management
- Added state management for terminal ID and connection info within the Terminal component. - Implemented UUID generation for unique terminal identification. - Updated customData handling to store and retrieve terminal connection information. - Improved terminal URL generation logic to include reconnect parameters based on terminal ID. - Enhanced initialization logic to ensure proper setup of terminal connection data.
1 parent 1e8760e commit 7e431f3

File tree

1 file changed

+148
-3
lines changed

1 file changed

+148
-3
lines changed

src/frontend/src/pad/containers/Terminal.tsx

Lines changed: 148 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useState, useEffect, useCallback, useRef } from 'react';
22
import { useWorkspaceState } from '../../api/hooks';
33
import type { NonDeleted, ExcalidrawEmbeddableElement } from '@atyrode/excalidraw/element/types';
44
import type { AppState } from '@atyrode/excalidraw/types';
@@ -10,19 +10,164 @@ interface TerminalProps {
1010
excalidrawAPI?: any;
1111
}
1212

13+
// Interface for terminal connection info stored in customData
14+
interface TerminalConnectionInfo {
15+
terminalId: string;
16+
baseUrl?: string;
17+
username?: string;
18+
workspaceId?: string;
19+
agent?: string;
20+
}
21+
1322
export const Terminal: React.FC<TerminalProps> = ({
1423
element,
1524
appState,
1625
excalidrawAPI
1726
}) => {
1827
const { data: workspaceState } = useWorkspaceState();
28+
const [terminalId, setTerminalId] = useState<string | null>(null);
29+
const elementIdRef = useRef(element?.id);
30+
const isInitializedRef = useRef(false);
31+
32+
// Generate a UUID for terminal ID
33+
const generateUUID = () => {
34+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
35+
const r = Math.random() * 16 | 0;
36+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
37+
return v.toString(16);
38+
});
39+
};
40+
41+
// Save terminal connection info to element's customData
42+
const saveConnectionInfoToCustomData = useCallback(() => {
43+
if (!element || !excalidrawAPI || !workspaceState || !terminalId) return;
44+
45+
try {
46+
// Get all elements from the scene
47+
const elements = excalidrawAPI.getSceneElements();
48+
49+
// Find and update the element
50+
const updatedElements = elements.map(el => {
51+
if (el.id === element.id) {
52+
// Create a new customData object with the terminal connection info
53+
const connectionInfo: TerminalConnectionInfo = {
54+
terminalId,
55+
baseUrl: workspaceState.base_url,
56+
username: workspaceState.username,
57+
workspaceId: workspaceState.workspace_id,
58+
agent: workspaceState.agent
59+
};
60+
61+
const customData = {
62+
...(el.customData || {}),
63+
terminalConnectionInfo: connectionInfo
64+
};
65+
66+
return { ...el, customData };
67+
}
68+
return el;
69+
});
70+
71+
// Update the scene with the modified elements
72+
excalidrawAPI.updateScene({
73+
elements: updatedElements
74+
});
75+
} catch (error) {
76+
console.error('Error saving terminal connection info:', error);
77+
}
78+
}, [element, excalidrawAPI, workspaceState, terminalId]);
79+
80+
// Generate a terminal ID if one doesn't exist
81+
useEffect(() => {
82+
if (terminalId) return;
83+
84+
// Generate a new terminal ID
85+
const newTerminalId = generateUUID();
86+
setTerminalId(newTerminalId);
87+
}, [terminalId]);
88+
89+
// Initialize terminal connection info
90+
useEffect(() => {
91+
if (!element || !workspaceState || !terminalId || isInitializedRef.current) return;
92+
93+
// Check if element ID has changed (indicating a new element)
94+
if (element.id !== elementIdRef.current) {
95+
elementIdRef.current = element.id;
96+
}
97+
98+
// Check if element already has terminal connection info
99+
if (element.customData?.terminalConnectionInfo) {
100+
const connectionInfo = element.customData.terminalConnectionInfo as TerminalConnectionInfo;
101+
setTerminalId(connectionInfo.terminalId);
102+
} else if (excalidrawAPI) {
103+
// Save the terminal ID to customData
104+
saveConnectionInfoToCustomData();
105+
}
106+
107+
isInitializedRef.current = true;
108+
}, [element, workspaceState, terminalId, saveConnectionInfoToCustomData, excalidrawAPI]);
109+
110+
// Update terminal connection info when element changes
111+
useEffect(() => {
112+
if (!element || !workspaceState) return;
113+
114+
// Check if element ID has changed (indicating a new element)
115+
if (element.id !== elementIdRef.current) {
116+
elementIdRef.current = element.id;
117+
isInitializedRef.current = false;
118+
119+
// Check if element already has terminal connection info
120+
if (element.customData?.terminalConnectionInfo) {
121+
const connectionInfo = element.customData.terminalConnectionInfo as TerminalConnectionInfo;
122+
setTerminalId(connectionInfo.terminalId);
123+
} else if (terminalId && excalidrawAPI) {
124+
// Save the existing terminal ID to customData
125+
saveConnectionInfoToCustomData();
126+
} else if (!terminalId) {
127+
// Generate a new terminal ID if one doesn't exist
128+
const newTerminalId = generateUUID();
129+
setTerminalId(newTerminalId);
130+
131+
// Save the new terminal ID to customData if excalidrawAPI is available
132+
if (excalidrawAPI) {
133+
setTimeout(() => {
134+
saveConnectionInfoToCustomData();
135+
}, 100);
136+
}
137+
}
138+
139+
isInitializedRef.current = true;
140+
} else if (!isInitializedRef.current && terminalId && excalidrawAPI && !element.customData?.terminalConnectionInfo) {
141+
// Handle the case where the element ID hasn't changed but we need to save the terminal ID
142+
saveConnectionInfoToCustomData();
143+
isInitializedRef.current = true;
144+
}
145+
}, [element, workspaceState, excalidrawAPI, terminalId, saveConnectionInfoToCustomData]);
19146

147+
// Effect to handle excalidrawAPI becoming available after component mount
148+
useEffect(() => {
149+
if (!excalidrawAPI || !element || !workspaceState || !terminalId) return;
150+
151+
// Check if element already has terminal connection info
152+
if (element.customData?.terminalConnectionInfo) return;
153+
154+
// Save the terminal ID to customData
155+
saveConnectionInfoToCustomData();
156+
}, [excalidrawAPI, element, workspaceState, terminalId, saveConnectionInfoToCustomData]);
157+
20158
const getTerminalUrl = () => {
21159
if (!workspaceState) {
22-
return 'https://terminal.example.dev';
160+
return '';
161+
}
162+
163+
const baseUrl = `${workspaceState.base_url}/@${workspaceState.username}/${workspaceState.workspace_id}.${workspaceState.agent}/terminal`;
164+
165+
// Add reconnect parameter if terminal ID exists
166+
if (terminalId) {
167+
return `${baseUrl}?reconnect=${terminalId}`;
23168
}
24169

25-
return `${workspaceState.base_url}/@${workspaceState.username}/${workspaceState.workspace_id}.${workspaceState.agent}/terminal`;
170+
return baseUrl;
26171
};
27172

28173
const terminalUrl = getTerminalUrl();

0 commit comments

Comments
 (0)