Allow changing views on fresh node creation

This commit is contained in:
Jordan Thompson
2026-02-17 13:54:13 -08:00
parent 3c63ff7f81
commit d5154a871e
3 changed files with 103 additions and 41 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "ComfyUI_Viewer"
version = "0.4.0"
version = "0.4.1"
description = "Extensible content viewer for ComfyUI: HTML, Markdown (LaTeX, Mermaid, etc), Code, Images, and more"
readme = "README.md"
requires-python = ">=3.10"
+35 -15
View File
@@ -608,7 +608,7 @@ function ensureElementsForNode(node) {
textarea: null,
typeLabel: null,
toggleAllBtn: null,
lastContentHash: "",
lastContentHash: null,
isEditing: false,
listEditContainer: null,
listTextareas: null,
@@ -896,20 +896,26 @@ function updateIframeContent(node, elements, forceView = null) {
if (elements.isEditing) return;
const content = getNodeContent(node, elements);
const contentHash = content ? content.length + "_" + content.slice(0, 100) : "";
const contentHash = content ? content.length + "_" + content.slice(0, 100) : "__empty__";
// Check for manual view selection from the toolbar dropdown
let manualViewType = null;
if (!content && elements.viewSelector && elements.viewSelector.value) {
manualViewType = elements.viewSelector.value;
}
// When content is empty but the upstream node is a UI view provider (e.g.
// OpenReel), load the view in standalone mode so the user can work in it
// before the workflow has been executed. We use a special hash so this
// only runs once (not every frame).
let upstreamViewType = null;
if (!content) {
if (!content && !manualViewType) {
const upstreamClass = getUpstreamNodeClass(node);
if (upstreamClass && upstreamClass.includes("OpenReel")) {
upstreamViewType = "openreel_video";
}
}
const effectiveHash = upstreamViewType ? ("_upstream:" + upstreamViewType) : contentHash;
const effectiveHash = (manualViewType || upstreamViewType) ? ("_manual:" + (manualViewType || upstreamViewType)) : contentHash;
if (effectiveHash === elements.lastContentHash && !forceView) return;
elements.lastContentHash = effectiveHash;
@@ -938,11 +944,19 @@ function updateIframeContent(node, elements, forceView = null) {
contentType = detectContentType(content);
}
} else {
contentType = upstreamViewType || detectContentType(content);
elements.currentView = null;
// Manual view_type only applies when there's no content (standalone mode)
// When content exists, always use normal detection
if (!content && manualViewType) {
contentType = manualViewType;
} else {
contentType = upstreamViewType || detectContentType(content);
}
elements.currentView = manualViewType || null;
elements.multiviewContent = null;
// Update view selector for empty content (shows all views)
if (elements.viewSelector) {
elements.viewSelector.style.display = "none";
updateViewSelector(elements.viewSelector, content, manualViewType);
}
}
@@ -984,10 +998,10 @@ function updateIframeContent(node, elements, forceView = null) {
let html;
const nodeId = String(node.id);
if (!displayContent) {
if (!displayContent && !manualViewType) {
html = buildIframeContent("<p style='opacity:0.5;text-align:center;margin-top:40px;'>No content. Click Edit to add content or connect a STRING input.</p>", "html", theme, [], nodeId);
} else {
html = buildIframeContent(finalContent, contentType, theme, excluded, nodeId);
html = buildIframeContent(finalContent || '', contentType, theme, excluded, nodeId);
}
// Check if the view provides a direct URL (e.g. OpenReel app served by its own endpoint).
@@ -999,7 +1013,7 @@ function updateIframeContent(node, elements, forceView = null) {
diag(`updateIframeContent: directUrl=${!!directUrl} directUrlLoaded=${!!elements.directUrlLoaded} nodeId=${String(node.id)} hasContent=${!!displayContent}`);
// For OpenReel: defer loading until we have actual content to avoid hard refresh race conditions
if (directUrl && !displayContent && !elements.directUrlLoaded) {
if (directUrl && !displayContent && !elements.directUrlLoaded && !manualViewType) {
diag(`updateIframeContent: OpenReel deferred - no content yet`);
// Show placeholder, don't load the app yet
const placeholderHtml = buildIframeContent("<p style='opacity:0.5;text-align:center;margin-top:40px;'>Waiting for video content...</p>", "html", theme, [], nodeId);
@@ -1022,9 +1036,9 @@ function updateIframeContent(node, elements, forceView = null) {
return;
}
const needsBlobUrl = !directUrl && displayContent && (
const needsBlobUrl = !directUrl && (displayContent || manualViewType) && (
viewNeedsBlobUrl(contentType) ||
(contentType === "html" && (
(contentType === "html" && displayContent && (
displayContent.includes("WebAssembly") ||
displayContent.includes("wasm") ||
displayContent.includes("createUnityInstance") ||
@@ -1033,7 +1047,7 @@ function updateIframeContent(node, elements, forceView = null) {
))
);
const scriptData = displayContent ? getViewScriptData(contentType, finalContent) : [];
const scriptData = (displayContent || manualViewType) ? getViewScriptData(contentType, finalContent || '') : [];
// Mark that a directUrl load has been queued.
if (directUrl) {
@@ -1053,9 +1067,15 @@ function updateIframeContent(node, elements, forceView = null) {
* @param {string} viewName - Name of view to switch to
*/
function handleViewChange(node, elements, viewName) {
if (!elements.multiviewContent) return;
if (elements.multiviewContent) {
elements.lastContentHash = "";
updateIframeContent(node, elements, viewName);
return;
}
// Manual view selection on empty nodes
elements.lastContentHash = "";
updateIframeContent(node, elements, viewName);
elements.directUrlLoaded = false;
updateIframeContent(node, elements);
}
window.addEventListener("message", (event) => {
+67 -25
View File
@@ -5,7 +5,7 @@
import { computeThemeTokens } from "../utils/theme.js";
import { createZipBlob } from "../utils/zip.js";
import { detectContentType, isMultiviewContent, getMultiviewOptions, parseMultiviewContent, stripContentMarker } from "../views/view_loader.js";
import { detectContentType, isMultiviewContent, getMultiviewOptions, parseMultiviewContent, stripContentMarker, getAllViews } from "../views/view_loader.js";
const CONTROLS_HEIGHT = 32;
const LIST_SEPARATOR = "\n---LIST_SEPARATOR---\n";
@@ -80,35 +80,77 @@ function createViewSelector(theme, node, elements, callbacks) {
* @param {string} content - Content to check for multi-view
* @param {string} currentView - Currently selected view name
*/
export function updateViewSelector(selector, content, currentView) {
export async function updateViewSelector(selector, content, currentView) {
if (!selector) return;
if (!isMultiviewContent(content)) {
selector.style.display = "none";
selector.innerHTML = "";
return;
}
const options = getMultiviewOptions(content);
if (options.length <= 1) {
selector.style.display = "none";
selector.innerHTML = "";
return;
}
// Build options
selector.innerHTML = "";
for (const opt of options) {
const option = document.createElement("option");
option.value = opt.name;
option.textContent = opt.displayName;
if (opt.name === currentView) {
option.selected = true;
// For multi-view content, show the multi-view selector
if (isMultiviewContent(content)) {
const options = getMultiviewOptions(content);
if (options.length <= 1) {
selector.style.display = "none";
selector.innerHTML = "";
return;
}
selector.appendChild(option);
// Build multi-view options
selector.innerHTML = "";
for (const opt of options) {
const option = document.createElement("option");
option.value = opt.name;
option.textContent = opt.displayName;
if (opt.name === currentView) {
option.selected = true;
}
selector.appendChild(option);
}
selector.style.display = "inline-block";
return;
}
selector.style.display = "inline-block";
// For empty content, show all available views for manual selection
if (!content || content.trim() === "") {
const { loadAllViews } = await import("../views/view_loader.js");
await loadAllViews();
const allViews = getAllViews();
if (allViews.size === 0) {
selector.style.display = "none";
selector.innerHTML = "";
return;
}
// Build options from all registered views
selector.innerHTML = "";
// Add "Auto" option as default
const autoOption = document.createElement("option");
autoOption.value = "";
autoOption.textContent = "Auto";
if (!currentView) {
autoOption.selected = true;
}
selector.appendChild(autoOption);
// Add all registered views
for (const [id, view] of allViews) {
const option = document.createElement("option");
option.value = id;
option.textContent = view.displayName || id;
if (id === currentView) {
option.selected = true;
}
selector.appendChild(option);
}
selector.style.display = "inline-block";
return;
}
// For normal content with a view, hide the selector
selector.style.display = "none";
selector.innerHTML = "";
}
/**