Files
JasonHoku-ComfyUI-Ultimate-…/web/dashboard.js
T
Jason HokuandClaude Opus 4.6 beacb02101 fix: fullscreen toggle loads correct session instead of default
When toggling fullscreen, the iframe reload was reading the session name
from the node's widget (default "my_session") instead of the session
the user had actually selected in the Dashboard picker.

Now the Dashboard sends its current session name in the fullscreen
postMessage, and the handler prefers that over the widget default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 10:32:45 -07:00

451 lines
20 KiB
JavaScript

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 = "<h3 style='color:#888; text-align:center; padding:20px;'>Session Deleted.</h3>";
node.loaded_session = null;
alert(`Session "${sessionName}" deleted successfully.`);
} else {
const errorText = await response.text();
alert(`Error deleting session: ${errorText}`);
}
} catch (error) {
console.error('[UltimateGrid] Delete failed:', error);
alert(`Connection error: ${error.message}`);
}
});
/**
* onExecuted - Handle dashboard HTML from server
*/
this.onExecuted = function (message) {
if (message?.html) {
const currentSession = getSessionName();
// Optimization: If session already loaded, try incremental update
if (node.loaded_session === currentSession) {
const newHtml = message.html[0];
// Try to extract just the manifest data
const pattern = /\/\*__JSON_START__\*\/\s*let fullManifest = (\{[\s\S]*?\});\s*\/\*__JSON_END__\*\//;
const match = newHtml.match(pattern);
if (match && match[1]) {
try {
const newManifest = JSON.parse(match[1]);
// Send incremental update instead of full reload
if (node.iframe?.contentWindow) {
node.iframe.contentWindow.postMessage({
type: 'update_data',
data: {
manifest: newManifest,
meta: newManifest.meta || {}
}
}, '*');
console.log('[UltimateGrid] 📊 Incremental update sent');
return; // Skip full HTML reload
}
} catch (error) {
console.warn('[UltimateGrid] Incremental update failed, doing full reload:', error);
}
}
}
// Full reload (when incremental fails or first load)
node.iframe.srcdoc = message.html[0];
node.loaded_session = currentSession;
console.log('[UltimateGrid] 🔄 Full reload');
}
// Update session name widget if server sent new name
if (message?.update_session_name && message.update_session_name[0]) {
const widget = node.widgets.find(w => w.name === "session_name");
if (widget) {
widget.value = message.update_session_name[0];
node.setDirtyCanvas(true, true);
}
}
};
/**
* Create iframe widget
*/
const iframe = document.createElement("iframe");
Object.assign(iframe.style, {
border: "none",
background: "#0b0b0b",
width: "100%",
height: "100%",
display: "block",
borderRadius: "0 0 4px 4px"
});
this.iframe = iframe;
this.addDOMWidget("dashboard_viewer", "iframe", iframe);
// Handle resize
this.onResize = function (size) {
node.setDirtyCanvas(true, true);
};
// Set default size
this.setSize([900, 750]);
// Auto-load: show landing page for new nodes, or saved session
// for workflows loaded from disk. onConfigure fires synchronously
// before the setTimeout, so we can use a flag to distinguish.
node._configured_from_workflow = false;
setTimeout(() => {
if (node._configured_from_workflow) {
// Workflow loaded — show the saved session
const savedSession = getSessionName();
if (savedSession) {
this.forceLoadSession(savedSession);
} else {
this.forceLoadSession("");
}
} else {
// Fresh node — show landing page with session cards
this.forceLoadSession("");
}
}, 500);
// onConfigure fires when loading a saved workflow (before setTimeout)
const origOnConfigure = this.onConfigure;
this.onConfigure = function (config) {
if (origOnConfigure) origOnConfigure.apply(this, arguments);
node._configured_from_workflow = true;
};
// Cleanup on node removal
const originalOnRemoved = this.onRemoved;
this.onRemoved = function () {
// Clean up iframe
if (node.iframe) {
node.iframe.remove();
node.iframe = null;
}
// Clean up placeholder
if (node.fs_placeholder) {
node.fs_placeholder.remove();
node.fs_placeholder = null;
}
console.log('[UltimateGrid] 🧹 Node cleanup complete');
if (originalOnRemoved) {
originalOnRemoved.apply(this, arguments);
}
};
return r;
};
}
},
});
console.log('[UltimateGrid] ✅ Dashboard extension loaded');