1- import React from 'react' ;
1+ import React , { useState , useEffect , useCallback , useRef } from 'react' ;
22import { useWorkspaceState } from '../../api/hooks' ;
33import type { NonDeleted , ExcalidrawEmbeddableElement } from '@atyrode/excalidraw/element/types' ;
44import 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+
1322export 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 ( / [ x y ] / 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