import { app } from "../../../scripts/app.js"; import { api } from "../../../scripts/api.js"; app.registerExtension({ name: "UltimateGrid.Dashboard", setup(app) { // 1. INJECT CSS (once only) if (!document.getElementById('ultimate-grid-css')) { const style = document.createElement('style'); style.id = 'ultimate-grid-css'; style.innerHTML = ` .dashboard-fullscreen { position: fixed !important; top: 0; left: 0; width: 100vw !important; height: 100vh !important; z-index: 99999 !important; background: #0b0b0b; border: none !important; } `; document.head.appendChild(style); } // 2. LIVE UPDATE LISTENER (SINGLETON - only register once) if (!window.__ultimateGridUpdateListenerInstalled) { api.addEventListener("ultimate_grid.update", (event) => { const payload = event.detail; const { session_name, node } = payload; console.log(`[UltimateGrid] Update received for: ${session_name}`); // Find all dashboard nodes const dashboardNodes = app.graph._nodes.filter(n => n.type === "UltimateGridDashboard"); dashboardNodes.forEach(dashboardNode => { const sessionWidget = dashboardNode.widgets.find(w => w.name === "session_name"); if (!sessionWidget) { console.warn(`[UltimateGrid] Dashboard ${dashboardNode.id} missing session_name widget`); return; } const currentSessionName = sessionWidget.value; // CASE 1: Session matches and dashboard is loaded โ send live update if (currentSessionName === session_name && dashboardNode.loaded_session === session_name && dashboardNode.iframe?.contentWindow) { console.log(`[UltimateGrid] ๐ค Live update โ Dashboard ${dashboardNode.id}`); try { dashboardNode.iframe.contentWindow.postMessage({ type: 'update_data', data: payload }, '*'); } catch (e) { console.error('[UltimateGrid] Failed to send message:', e); } } // CASE 2: Session matches but not loaded yet โ auto-load ONLY if no session is loaded // Don't force-load if the user is manually browsing a different session in another tab else if (currentSessionName === session_name && !dashboardNode.loaded_session) { console.log(`[UltimateGrid] ๐ Auto-loading session: ${session_name}`); if (dashboardNode.forceLoadSession) { dashboardNode.forceLoadSession(session_name); } } // CASE 3: Dashboard connected to sampler but different session โ update else if (currentSessionName !== session_name) { const isConnected = dashboardNode.inputs?.some(input => { const link = app.graph.links[input.link]; return link && link.origin_id === parseInt(node); }); if (isConnected) { console.log(`[UltimateGrid] ๐ Connected dashboard โ Update to: ${session_name}`); sessionWidget.value = session_name; if (dashboardNode.forceLoadSession) { dashboardNode.forceLoadSession(session_name); } } } }); }); window.__ultimateGridUpdateListenerInstalled = true; console.log('[UltimateGrid] โ Update listener installed'); } // PROGRESS/ETA LISTENER (SINGLETON - forward ETA to dashboard iframe) if (!window.__ultimateGridProgressListenerInstalled) { api.addEventListener("ultimate_grid.progress", (event) => { const payload = event.detail; const { session_name } = payload; const dashboardNodes = app.graph._nodes.filter(n => n.type === "UltimateGridDashboard"); dashboardNodes.forEach(dashboardNode => { const sessionWidget = dashboardNode.widgets.find(w => w.name === "session_name"); if (!sessionWidget) return; if (sessionWidget.value === session_name && dashboardNode.loaded_session === session_name && dashboardNode.iframe?.contentWindow) { try { dashboardNode.iframe.contentWindow.postMessage({ type: 'progress_update', data: payload }, '*'); } catch (e) { /* ignore */ } } }); }); window.__ultimateGridProgressListenerInstalled = true; } // DISTRIBUTION STATUS LISTENER (SINGLETON - forward distribution status to dashboard iframe) if (!window.__ultimateGridDistributionStatusListenerInstalled) { api.addEventListener("ultimate_grid.distribution_status", (event) => { const payload = event.detail; const { session_name } = payload; const dashboardNodes = app.graph._nodes.filter(n => n.type === "UltimateGridDashboard"); dashboardNodes.forEach(dashboardNode => { const sessionWidget = dashboardNode.widgets.find(w => w.name === "session_name"); if (!sessionWidget) return; if (sessionWidget.value === session_name && dashboardNode.loaded_session === session_name && dashboardNode.iframe?.contentWindow) { try { dashboardNode.iframe.contentWindow.postMessage({ type: 'distribution_status', data: payload }, '*'); } catch (e) { /* ignore */ } } }); }); window.__ultimateGridDistributionStatusListenerInstalled = true; } // 3. FULLSCREEN + CTRL+S LISTENER (SINGLETON - only register once) if (!window.__ultimateGridFullscreenListenerInstalled) { window.addEventListener("message", (event) => { // Forward Ctrl+S from dashboard iframe to ComfyUI's save workflow if (event.data?.type === 'forward_ctrl_s') { document.dispatchEvent(new KeyboardEvent('keydown', { key: 's', code: 'KeyS', ctrlKey: true, bubbles: true, cancelable: true })); return; } if (event.data?.type === 'toggle_fullscreen') { const nodeId = parseInt(event.data.node_id); const graphNode = app.graph.getNodeById(nodeId); if (!graphNode?.iframe) { console.warn('[UltimateGrid] Fullscreen toggle failed: node or iframe not found'); return; } const el = graphNode.iframe; // Save viewport position before fullscreen toggle // so it can be restored after the iframe reloads try { if (el.contentWindow?.saveViewportPosition) { el.contentWindow.saveViewportPosition(); } } catch (e) { /* cross-origin or not loaded yet */ } el.addEventListener('load', () => { console.log('[UltimateGrid] Iframe reloaded after fullscreen toggle, restoring session...'); // Get the session name โ prefer the one from the iframe (user may have // loaded a different session via the picker), fall back to node widget const sessionName = event.data.session_name || graphNode.widgets?.find(w => w.name === "session_name")?.value || null; if (sessionName && graphNode.forceLoadSession) { graphNode.forceLoadSession(sessionName); } }, { once: true }); // Exit fullscreen if (el.classList.contains('dashboard-fullscreen')) { el.classList.remove('dashboard-fullscreen'); // Restore to original position if (graphNode.fs_placeholder?.parentNode) { graphNode.fs_placeholder.parentNode.insertBefore(el, graphNode.fs_placeholder); graphNode.fs_placeholder.remove(); } graphNode.fs_placeholder = null; el.style.width = (graphNode.size[0] - 20) + "px"; el.style.height = "100%"; console.log('[UltimateGrid] โ๏ธ Exited fullscreen'); } // Enter fullscreen else { // Create placeholder const placeholder = document.createElement("div"); placeholder.style.display = "none"; graphNode.fs_placeholder = placeholder; if (el.parentNode) { el.parentNode.insertBefore(placeholder, el); } // Move to body document.body.appendChild(el); el.classList.add('dashboard-fullscreen'); console.log('[UltimateGrid] โ๏ธ Entered fullscreen'); } } }); window.__ultimateGridFullscreenListenerInstalled = true; console.log('[UltimateGrid] โ Fullscreen listener installed'); } }, async beforeRegisterNodeDef(nodeType, nodeData, app) { if (nodeData.name === "UltimateGridDashboard") { const onNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function () { const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; this.resizable = true; const node = this; node.loaded_session = null; node.is_fetching = false; // Helper: Get current session name const getSessionName = () => { const widget = node.widgets.find(w => w.name === "session_name"); return widget ? widget.value : null; }; /** * Force load session - FIXED (no 404 call!) */ this.forceLoadSession = async (sessionName) => { if (node.is_fetching) return; node.is_fetching = true; try { const response = await fetch("/config_tester/get_session_html", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_name: sessionName, node_id: node.id }) }); if (response.ok) { const html = await response.text(); if (node.iframe) { node.iframe.srcdoc = html; node.loaded_session = sessionName; } } } catch (e) { console.error(e); } finally { node.is_fetching = false; } };; // Button: Reload/Show Session this.addWidget("button", "RELOAD / SHOW SESSION", null, () => { const sessionName = getSessionName(); if (sessionName) { this.forceLoadSession(sessionName); } else { alert("No session name set."); } }); // Button: Delete Session this.addWidget("button", "DELETE SESSION (Files)", null, async () => { const sessionName = getSessionName(); if (!sessionName) { alert("No session name set."); return; } if (!confirm(`Are you sure you want to delete session "${sessionName}"?\n\nThis will delete all images and data.`)) { return; } try { const response = await fetch("/config_tester/delete_session", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_name: sessionName }) }); if (response.ok) { node.iframe.srcdoc = "