Files
PozzettiAndrea-ComfyUI-Geom…/web/js/dynamic_widgets.js
T

145 lines
4.3 KiB
JavaScript

/**
* Dynamic Widget Visibility for ComfyUI
*
* Shows/hides widgets based on selector widget values.
* Mappings are loaded from mappings.json (generated by prestartup_script.py).
*
* Usage in node definitions:
* "voxel_size": ("FLOAT", {
* "default": 0.1,
* "visible_when": {"backend": ["blender_voxel"]},
* })
*/
import { app } from "../../../scripts/app.js";
console.log("[DynamicWidgets] Loading extension...");
// Will be populated from JSON
let MAPPINGS = null;
/**
* Fetch mappings from generated JSON
*/
async function loadMappings() {
const paths = [
"/extensions/ComfyUI-GeometryPack/js/mappings.json",
"/extensions/comfyui-geometrypack/js/mappings.json",
];
for (const path of paths) {
try {
const response = await fetch(path);
if (response.ok) {
const data = await response.json();
MAPPINGS = data;
const nodeCount = Object.keys(data.nodes || {}).length;
console.log(`[DynamicWidgets] Loaded mappings for ${nodeCount} nodes from ${path}`);
return true;
}
} catch (e) {
// Try next path
}
}
console.warn("[DynamicWidgets] Could not load mappings.json - no dynamic widgets configured");
return false;
}
// Load mappings on startup
loadMappings();
/**
* Get the node configuration from mappings
*/
function getNodeConfig(nodeClass) {
if (!MAPPINGS || !MAPPINGS.nodes) return null;
return MAPPINGS.nodes[nodeClass] || null;
}
/**
* Update widget visibility based on selector value
*/
function updateWidgetVisibility(node, selectorName, selectedValue) {
const nodeConfig = getNodeConfig(node.comfyClass);
if (!nodeConfig || !nodeConfig.selectors) return;
const selectorConfig = nodeConfig.selectors[selectorName];
if (!selectorConfig) return;
// Get widgets that should be visible for this selector value
const visibleWidgets = selectorConfig[selectedValue] || [];
// Get all widgets controlled by this selector
const allControlledWidgets = new Set();
for (const widgets of Object.values(selectorConfig)) {
for (const w of widgets) {
allControlledWidgets.add(w);
}
}
console.log(`[DynamicWidgets] ${node.comfyClass}: ${selectorName}="${selectedValue}", visible=[${visibleWidgets.join(', ')}]`);
let visibilityChanged = false;
for (const widget of node.widgets || []) {
// Only control widgets that are in our mapping
if (!allControlledWidgets.has(widget.name)) continue;
const shouldShow = visibleWidgets.includes(widget.name);
const wasHidden = widget.hidden;
widget.hidden = !shouldShow;
if (wasHidden !== widget.hidden) {
visibilityChanged = true;
console.log(`[DynamicWidgets] Widget "${widget.name}": hidden=${widget.hidden}`);
}
}
if (visibilityChanged) {
node.setSize(node.computeSize());
app.graph.setDirtyCanvas(true, true);
}
}
/**
* Set up visibility control for a selector widget
*/
function setupSelectorWidget(node, selectorName) {
const selectorWidget = node.widgets?.find(w => w.name === selectorName);
if (!selectorWidget) {
console.log(`[DynamicWidgets] Selector widget "${selectorName}" not found on ${node.comfyClass}`);
return;
}
// Store original callback
const originalCallback = selectorWidget.callback;
// Override callback to update visibility on change
selectorWidget.callback = function(value) {
if (originalCallback) originalCallback.call(this, value);
updateWidgetVisibility(node, selectorName, value);
};
// Initial visibility update (with delay to ensure widgets are created)
setTimeout(() => {
updateWidgetVisibility(node, selectorName, selectorWidget.value);
}, 50);
}
app.registerExtension({
name: "Comfy.DynamicWidgets",
nodeCreated(node) {
const nodeConfig = getNodeConfig(node.comfyClass);
if (!nodeConfig || !nodeConfig.selectors) return;
console.log(`[DynamicWidgets] Setting up node: ${node.comfyClass}`);
// Set up each selector
for (const selectorName of Object.keys(nodeConfig.selectors)) {
setupSelectorWidget(node, selectorName);
}
}
});
console.log("[DynamicWidgets] Extension registered");