Files
PozzettiAndrea-ComfyUI-Geom…/javascript/js/utils/uiComponents.js
T
PozzettiAndrea 6047a30e1b Dual/VTK/Multi preview bars: layout+mode selectors, opacity gating, shared fullscreen
Preview Mesh Dual:
- New control bar with Layout (Side by side | Overlay | Slider) and Mode
  (Fields | Texture), two-way synced with the node widgets.
- side_by_side <-> slider reuse the SAME exported files, so that switch is
  CLIENT-SIDE: onExecuted was refactored into render(message, layoutOverride)
  and the last message is stored -- switching re-renders instantly, no re-run.
  Overlay needs the combined export and mode changes the export format, so
  those queue a re-run (same cost as editing the widget).
- opacity_1/opacity_2 widgets are hidden unless layout is Overlay (the
  hidden-type trick from preview_mesh_batch_render.js), restored when it is.

Preview Mesh (VTK): new bar with Mode (Fields | Texture | Texture (PBR)),
two-way synced, re-runs on change.

Fullscreen: new createFullscreenButton(container) in utils/uiComponents.js
(fullscreens the whole widget so bar controls stay usable; listeners on the
container only, isolation-clean). Wired into Dual, VTK, and Multi bars (in
Multi it sits outside the collapsible group so it survives collapse). The
batch node keeps its own earlier implementation with arrow-key nav.

Note: plain Preview Mesh (three.js, GeomPackPreviewMesh) has no mode input on
the Python side, so there is nothing to select there; the VTK preview is the
one with modes. comfy-3d-viewers was checked for fullscreen prior art -- its
"FullScreen" hits are VTK class names, not browser fullscreen; this is new.
2026-08-14 14:01:37 +02:00

161 lines
5.3 KiB
JavaScript

/**
* UI component factory functions for ComfyUI 3D viewer widgets
*/
/**
* Create a container div for viewer + info panel
* @param {Object} options - Container options
* @param {string} options.backgroundColor - Background color (default: "#2a2a2a")
* @returns {HTMLDivElement} Container element
*/
export function createContainer(options = {}) {
const { backgroundColor = "#2a2a2a" } = options;
const container = document.createElement("div");
container.style.width = "100%";
container.style.height = "100%";
container.style.display = "flex";
container.style.flexDirection = "column";
container.style.backgroundColor = backgroundColor;
container.style.overflow = "hidden";
return container;
}
/**
* Create an iframe for embedding a viewer
* @param {string} src - Initial iframe source URL
* @param {Object} options - Iframe options
* @param {string} options.minHeight - Minimum height (default: "0")
* @param {string} options.backgroundColor - Background color (default: "#2a2a2a")
* @returns {HTMLIFrameElement} Iframe element
*/
export function createIframe(src, options = {}) {
const {
minHeight = "0",
backgroundColor = "#2a2a2a"
} = options;
const iframe = document.createElement("iframe");
iframe.style.width = "100%";
iframe.style.flex = "1 1 0";
iframe.style.minHeight = minHeight;
iframe.style.border = "none";
iframe.style.backgroundColor = backgroundColor;
iframe.src = src;
return iframe;
}
/**
* Create a fullscreen toggle button wired to a widget container.
*
* Fullscreens the WHOLE container (viewer iframe + any control bar), so the
* bar's buttons stay usable in fullscreen. All listeners are attached to the
* container itself (never document) to stay isolation-clean.
*
* @param {HTMLElement} container - The widget container to fullscreen
* @returns {HTMLButtonElement} The toggle button (append it to a bar)
*/
export function createFullscreenButton(container) {
const btn = document.createElement("button");
btn.textContent = "⛶";
btn.title = "Fullscreen";
btn.style.cssText = "padding:4px 10px;cursor:pointer;background:#333;color:#ccc;" +
"border:1px solid #555;border-radius:3px;font-size:13px;line-height:1;margin-left:auto;";
const isFs = () => (document.fullscreenElement || document.webkitFullscreenElement) === container;
btn.addEventListener("click", () => {
if (isFs()) {
(document.exitFullscreen || document.webkitExitFullscreen)?.call(document);
} else {
(container.requestFullscreen || container.webkitRequestFullscreen)?.call(container);
}
});
return btn;
}
/**
* Create an info panel for displaying mesh metadata
* @param {string} placeholder - Initial placeholder text
* @param {Object} options - Panel options
* @returns {HTMLDivElement} Info panel element
*/
export function createInfoPanel(placeholder = "Info will appear here after execution", options = {}) {
const {
backgroundColor = "#1a1a1a",
borderTop = "1px solid #444",
padding = "6px 12px",
fontSize = "10px",
fontFamily = "monospace",
color = "#ccc",
lineHeight = "1.3"
} = options;
const infoPanel = document.createElement("div");
infoPanel.style.backgroundColor = backgroundColor;
infoPanel.style.borderTop = borderTop;
infoPanel.style.padding = padding;
infoPanel.style.fontSize = fontSize;
infoPanel.style.fontFamily = fontFamily;
infoPanel.style.color = color;
infoPanel.style.lineHeight = lineHeight;
infoPanel.style.flexShrink = "0";
infoPanel.style.overflow = "hidden";
infoPanel.innerHTML = `<span style="color: #888;">${placeholder}</span>`;
return infoPanel;
}
/**
* Create an analysis info panel (for connected components, self-intersections, etc.)
* @param {string} placeholder - Initial placeholder text
* @param {Object} options - Panel options
* @returns {HTMLDivElement} Analysis panel element
*/
export function createAnalysisPanel(placeholder = "Run workflow to see results", options = {}) {
const {
backgroundColor = "#1a1a1a",
padding = "8px",
fontSize = "10px",
fontFamily = "monospace",
color = "#ccc",
lineHeight = "1.4",
maxHeight = "200px",
borderRadius = "4px"
} = options;
const panel = document.createElement("div");
panel.style.backgroundColor = backgroundColor;
panel.style.padding = padding;
panel.style.fontSize = fontSize;
panel.style.fontFamily = fontFamily;
panel.style.color = color;
panel.style.lineHeight = lineHeight;
panel.style.maxHeight = maxHeight;
panel.style.overflowY = "auto";
panel.style.borderRadius = borderRadius;
panel.innerHTML = `<span style="color: #666;">${placeholder}</span>`;
return panel;
}
/**
* Show error in an info panel
* @param {HTMLElement} panel - The info panel element
* @param {string} message - Error message
*/
export function showPanelError(panel, message) {
panel.innerHTML = `<div style="color: #ff6b6b; padding: 8px;">Error: ${message}</div>`;
}
/**
* Create default widget options
* @returns {Object} Widget options object
*/
export function createWidgetOptions() {
return {
getValue() { return ""; },
setValue(v) { }
};
}