import { theme } from './themes' // Iframe cache - these never get recreated once loaded // Use a global to survive hot reloads const iframes: Map = (window as any).__toolIframes ??= new Map() // Track current state to avoid unnecessary DOM updates // Also stored on window to survive hot reloads let currentTool: string | null = (window as any).__currentTool ?? null // Get the stable container (outside Hono-managed DOM) const getContainer = () => document.getElementById('tool-iframes') // Initialize the container styles export function initToolIframes() { const container = getContainer() if (!container) return // Restore iframe cache from DOM if module was hot-reloaded if (iframes.size === 0) { const existingIframes = container.querySelectorAll('iframe') existingIframes.forEach(iframe => { const match = iframe.src.match(/localhost:(\d+)/) if (match && match[1]) { const port = parseInt(match[1], 10) const toolName = iframe.dataset.toolName const appName = iframe.dataset.appName if (toolName) { const cacheKey = appName ? `${toolName}:${appName}` : toolName iframes.set(cacheKey, { iframe, port }) } } }) } container.style.cssText = ` display: none; position: fixed; background: ${theme('colors-bg')}; ` } // Update which iframe is visible based on selected tab and tool state export function updateToolIframes( selectedTab: string, tools: Array<{ name: string; port?: number; state: string }>, selectedApp: string | null ) { const container = getContainer() if (!container) return // Find the selected tool const selectedTool = tools.find(t => t.name === selectedTab) const showIframe = selectedTool?.state === 'running' && selectedTool?.port if (!showIframe) { // Only update if state changed if (currentTool !== null) { container.style.display = 'none' currentTool = null ;(window as any).__currentTool = null } return } const tool = selectedTool! // Build cache key for tool + app combination const cacheKey = selectedApp ? `${tool.name}:${selectedApp}` : tool.name // Skip if nothing changed if (currentTool === cacheKey) { // Just update position in case of scroll/resize const tabContent = document.querySelector('[data-tool-target]') if (tabContent) { const rect = tabContent.getBoundingClientRect() container.style.top = `${rect.top}px` container.style.left = `${rect.left}px` container.style.width = `${rect.width}px` container.style.height = `${rect.height}px` } return } // Position container over the tab content area const tabContent = document.querySelector('[data-tool-target]') if (!tabContent) { container.style.display = 'none' currentTool = null return } const rect = tabContent.getBoundingClientRect() container.style.cssText = ` display: block; position: fixed; top: ${rect.top}px; left: ${rect.left}px; width: ${rect.width}px; height: ${rect.height}px; background: ${theme('colors-bg')}; z-index: 100; ` // Get or create the iframe for this tool + app combination let cached = iframes.get(cacheKey) if (!cached || cached.port !== tool.port) { // Create new iframe (first time or port changed) const iframe = document.createElement('iframe') const url = selectedApp ? `http://localhost:${tool.port}?app=${encodeURIComponent(selectedApp)}` : `http://localhost:${tool.port}` iframe.src = url iframe.dataset.toolName = tool.name // For hot reload recovery iframe.dataset.appName = selectedApp ?? '' // For hot reload recovery iframe.style.cssText = ` width: 100%; height: 100%; border: none; ` cached = { iframe, port: tool.port! } iframes.set(cacheKey, cached) // Add to container container.appendChild(iframe) } // Show only the selected iframe, hide others for (const [key, { iframe }] of iframes) { const shouldShow = key === cacheKey if (shouldShow && iframe.parentElement !== container) { container.appendChild(iframe) } iframe.style.display = shouldShow ? 'block' : 'none' } currentTool = cacheKey ;(window as any).__currentTool = cacheKey }