updated to v2.0.0

added new features such as dynamic sum adjustment and sum-lock dynamics
This commit is contained in:
FunnyFinger
2025-04-19 20:22:11 +03:00
parent 218d28b435
commit a92aaa7159
29 changed files with 2562 additions and 269 deletions
+1043 -53
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -13,7 +13,7 @@ const COLOR_THEMES = {
// Updated NODE_COLORS map
const NODE_COLORS = {
"Slider Stacker": "purple",
"Slider Stacker": "purple",
"Slider Receiver": "purple", // Added receiver, same color
// Add other nodes here if needed
};
@@ -30,7 +30,6 @@ shuffleArray(colorKeys); // Shuffle the color themes initially
function setNodeColors(node, theme) {
if (!theme) {return;}
node.shape = "box"; // Apply box shape if desired
if(theme.nodeColor && theme.nodeBgColor) {
node.color = theme.nodeColor;
node.bgcolor = theme.nodeBgColor;
@@ -62,4 +61,7 @@ const ext = {
}
};
app.registerExtension(ext);
// Add a console log for loading confirmation
// console.log("Loaded Dynamic Sliders Stack Appearance Script");
app.registerExtension(ext);
+73 -48
View File
@@ -1,6 +1,6 @@
import { app } from "../../scripts/app.js";
console.log("Loading context.js for Slider Stacker");
// Removed console log
// --- Helper Functions (Copied from Dynamic.js for self-containment) ---
@@ -14,9 +14,28 @@ const getWidgetValue = (widget) => {
return isNaN(num) ? 0 : num;
};
// Function to check if node is our Slider Stacker using multiple identification methods
function isSliderStackerNode(node) {
// Check the display title
const nodeTitle = node.getTitle ? node.getTitle() : node.title;
if (nodeTitle === "Slider Stacker") return true;
// Check internal type name
if (node.type === "Slider Stacker (DSS)") return true;
// Check properties that are unique to our node
if (findWidgetByName(node, "sliders_count") &&
findWidgetByName(node, "sliders_max") &&
findWidgetByName(node, "slider_name_1")) {
return true;
}
return false;
}
// Gets the actual max strength based on current slider values and activity
function recalculateCurrentMaxStrength(node) {
const sliderCountWidget = findWidgetByName(node, "slider_count");
const sliderCountWidget = findWidgetByName(node, "sliders_count");
const count = getWidgetValue(sliderCountWidget);
let currentMaxValue = 0;
for (let i = 1; i <= count; i++) {
@@ -25,7 +44,7 @@ function recalculateCurrentMaxStrength(node) {
currentMaxValue = Math.max(currentMaxValue, getWidgetValue(wtWidget));
}
}
const maxStrengthWidget = findWidgetByName(node, "slider_max_strength");
const maxStrengthWidget = findWidgetByName(node, "sliders_max");
const overallMax = maxStrengthWidget?.options?.max ?? 2.0;
const overallMin = maxStrengthWidget?.options?.min ?? 0.0;
return Math.max(overallMin, Math.min(currentMaxValue, overallMax));
@@ -34,7 +53,7 @@ function recalculateCurrentMaxStrength(node) {
// Updates the stored normalized weights based on current displayed values and max strength
function updateNormalization(node, currentMaxStrength) {
if (!node.normalizedSliderWeights) node.normalizedSliderWeights = {}; // Ensure it exists
const sliderCountWidget = findWidgetByName(node, "slider_count");
const sliderCountWidget = findWidgetByName(node, "sliders_count");
const maxSliders = sliderCountWidget?.options?.max || 50;
for (let i = 1; i <= maxSliders; i++) {
const wtWidget = findWidgetByName(node, `(${i})`);
@@ -50,9 +69,9 @@ function updateNormalization(node, currentMaxStrength) {
// Updates the displayed values of individual sliders based on normalized weights and new max strength
// Note: Needs access to node._isUpdatingInternally if called directly, but here it's used post-operation
function updateAllSliderDisplaysFromContext(node, newMaxStrength) {
if (!node.normalizedSliderWeights) return;
if (!node.normalizedSliderWeights) return;
node._isUpdatingInternally = true; // Set flag for this operation
const count = getWidgetValue(findWidgetByName(node, "slider_count"));
const count = getWidgetValue(findWidgetByName(node, "sliders_count"));
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(node, `(${i})`);
if (wtWidget) {
@@ -60,17 +79,19 @@ function updateAllSliderDisplaysFromContext(node, newMaxStrength) {
let newDisplayValue = normalizedValue * newMaxStrength;
const widgetMax = wtWidget.options?.max ?? 2.0;
const widgetMin = wtWidget.options?.min ?? 0.0;
newDisplayValue = Math.max(widgetMin, Math.min(newDisplayValue, widgetMax, newMaxStrength));
newDisplayValue = Math.max(widgetMin, Math.min(newDisplayValue, widgetMax, newMaxStrength));
if (Math.abs(getWidgetValue(wtWidget) - newDisplayValue) > 0.001) {
wtWidget.value = newDisplayValue; // This should use the intercepted setter
}
}
}
node.graph.setDirtyCanvas(true, true);
if (node.graph) {
node.graph.setDirtyCanvas(true, true);
}
node._isUpdatingInternally = false;
}
// --- Function to add menu options ---
// --- Function to add menu options ---
function addContextMenuOptions(node) {
const originalGetExtraMenuOptions = node.getExtraMenuOptions;
@@ -83,7 +104,7 @@ function addContextMenuOptions(node) {
options.push({
content: "Average Slider Values",
callback: () => {
const count = getWidgetValue(findWidgetByName(this, "slider_count"));
const count = getWidgetValue(findWidgetByName(this, "sliders_count"));
let sum = 0;
let activeCount = 0;
for (let i = 1; i <= count; i++) {
@@ -94,27 +115,29 @@ function addContextMenuOptions(node) {
}
}
const average = activeCount > 0 ? sum / activeCount : 0;
this._isUpdatingInternally = true;
this._isUpdatingInternally = true;
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(this, `(${i})`);
if (wtWidget) { wtWidget.value = average; }
}
this._isUpdatingInternally = false;
// Recalculate max and update normalization *after* loop
const newMax = recalculateCurrentMaxStrength(this);
updateNormalization(this, newMax);
// Trigger update on master slider to sync everything
const masterWidget = findWidgetByName(this, "slider_max_strength");
const masterWidget = findWidgetByName(this, "sliders_max");
if (masterWidget && Math.abs(getWidgetValue(masterWidget) - newMax) > 0.001) {
masterWidget.value = newMax; // This uses the setter from Dynamic.js
} else {
// If max didn't change, still redraw displays based on new average
updateAllSliderDisplaysFromContext(this, newMax);
}
this.graph.setDirtyCanvas(true, true);
if (this.graph) {
this.graph.setDirtyCanvas(true, true);
}
}
});
@@ -122,25 +145,27 @@ function addContextMenuOptions(node) {
options.push({
content: "Reset Slider Values",
callback: () => {
const count = getWidgetValue(findWidgetByName(this, "slider_count"));
const defaultValue = 1.0;
const count = getWidgetValue(findWidgetByName(this, "sliders_count"));
const defaultValue = 1.0;
this._isUpdatingInternally = true;
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(this, `(${i})`);
if (wtWidget) { wtWidget.value = defaultValue; }
}
this._isUpdatingInternally = false;
const newMax = recalculateCurrentMaxStrength(this);
updateNormalization(this, newMax);
const masterWidget = findWidgetByName(this, "slider_max_strength");
updateNormalization(this, newMax);
const masterWidget = findWidgetByName(this, "sliders_max");
if (masterWidget && Math.abs(getWidgetValue(masterWidget) - newMax) > 0.001) {
masterWidget.value = newMax;
} else {
updateAllSliderDisplaysFromContext(this, newMax);
}
this.graph.setDirtyCanvas(true, true);
if (this.graph) {
this.graph.setDirtyCanvas(true, true);
}
}
});
@@ -148,7 +173,7 @@ function addContextMenuOptions(node) {
options.push({
content: "Set All to Max Value",
callback: () => {
const count = getWidgetValue(findWidgetByName(this, "slider_count"));
const count = getWidgetValue(findWidgetByName(this, "sliders_count"));
let maxValue = -Infinity; // Start low
// First pass: Find the maximum value among visible sliders
@@ -158,37 +183,36 @@ function addContextMenuOptions(node) {
maxValue = Math.max(maxValue, getWidgetValue(wtWidget));
}
}
// Ensure maxValue is within widget bounds if needed (though setters handle clamping)
// const widgetMaxBound = findWidgetByName(this, `(1)`)?.options?.max ?? 2.0;
// maxValue = Math.min(maxValue, widgetMaxBound);
// Second pass: Set all visible sliders to the found maximum value
this._isUpdatingInternally = true;
this._isUpdatingInternally = true;
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(this, `(${i})`);
if (wtWidget) { wtWidget.value = maxValue; } // Use setter
}
this._isUpdatingInternally = false;
// Recalculate max (should be == maxValue), normalize, and update master slider
const newMax = recalculateCurrentMaxStrength(this); // Will be maxValue
updateNormalization(this, newMax);
const masterWidget = findWidgetByName(this, "slider_max_strength");
const masterWidget = findWidgetByName(this, "sliders_max");
// Update master slider directly - its setter handles updating displays
if (masterWidget && Math.abs(getWidgetValue(masterWidget) - newMax) > 0.001) {
masterWidget.value = newMax;
masterWidget.value = newMax;
}
if (this.graph) {
this.graph.setDirtyCanvas(true, true);
}
this.graph.setDirtyCanvas(true, true);
}
});
// Option 4: Set All to Min Value
options.push({
content: "Set All to Min Value",
callback: () => {
const count = getWidgetValue(findWidgetByName(this, "slider_count"));
const count = getWidgetValue(findWidgetByName(this, "sliders_count"));
let minValue = Infinity; // Start high
// First pass: Find the minimum value among visible sliders
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(this, `(${i})`);
@@ -196,40 +220,41 @@ function addContextMenuOptions(node) {
minValue = Math.min(minValue, getWidgetValue(wtWidget));
}
}
// Ensure minValue isn't below widget bounds
// const widgetMinBound = findWidgetByName(this, `(1)`)?.options?.min ?? 0.0;
// minValue = Math.max(minValue, widgetMinBound);
// Second pass: Set all visible sliders to the found minimum value
this._isUpdatingInternally = true;
this._isUpdatingInternally = true;
for (let i = 1; i <= count; i++) {
const wtWidget = findWidgetByName(this, `(${i})`);
if (wtWidget) { wtWidget.value = minValue; } // Use setter
}
this._isUpdatingInternally = false;
// Recalculate max (will be == minValue), normalize, and update master slider
const newMax = recalculateCurrentMaxStrength(this); // Will be minValue
updateNormalization(this, newMax);
const masterWidget = findWidgetByName(this, "slider_max_strength");
updateNormalization(this, newMax);
const masterWidget = findWidgetByName(this, "sliders_max");
// Update master slider directly
if (masterWidget && Math.abs(getWidgetValue(masterWidget) - newMax) > 0.001) {
masterWidget.value = newMax;
}
this.graph.setDirtyCanvas(true, true);
if (this.graph) {
this.graph.setDirtyCanvas(true, true);
}
}
});
};
}
// --- ComfyUI Extension ---
// --- ComfyUI Extension ---
app.registerExtension({
name: "Comfy.DynamicSlidersStack.ContextMenu",
nodeCreated(node) {
const nodeTitle = node.getTitle ? node.getTitle() : node.title;
if (nodeTitle === "Slider Stacker") {
// Use our robust identification instead of title
if (isSliderStackerNode(node)) {
addContextMenuOptions(node);
}
}
});
});
// console.log("Loaded Dynamic Sliders Stack Context Menu Script");
+247
View File
@@ -0,0 +1,247 @@
/**
* Dynamic Sliders Stack - Modern Widget Hider
* Manages widget visibility based on slider count
*/
// REMOVE top-level import attempts for app
/*
// Import app with fallbacks
let app;
try { app = await import('/scripts/app.js'); } catch (e) {
try { app = await import('../../../scripts/app.js'); } catch (e) {
app = window.app;
}
}
*/
// REMOVE top-level check
// const isModernFrontend = app && app.extensionManager;
// Config
const NODE_CONFIG = {
controlWidgets: 3, // Number of control widgets
maxSliders: 50, // Maximum number of sliders
// nodeRadius removed as ComfyUI default is round
};
let domObserver = null; // DOM Mutation observer
// Helper to find node element
const findNodeElement = (nodeId) => document?.getElementById(nodeId) ||
document?.querySelector(`.litegraph-node[data-node-id="${nodeId}"]`) ||
document?.querySelector(`.comfy-node[data-node-id="${nodeId}"]`) ||
document?.querySelector(`.node_box[data-litegraph-id="${nodeId}"]`) ||
document?.querySelector(`.litegraph.litecontextualmenu .node_box[data-litegraph-id='${nodeId}']`);
// Extension definition
const extension = {
app: null, // Add property to store app instance
/** Set up the extension */
setup(app) { // Receive app as argument
// console.log("[DSS Modern Hider] setup() called.");
this.app = app; // Store app instance
// No need to check isModernFrontend here, this script should only load in modern
this.createGlobalStyles();
this.setupNodeTracking();
// console.log("[DSS Modern Hider] setup() completed.");
return true; // Indicate setup was successful (or based on actual success)
},
/** Update widget visibility (internal & DOM) */
updateWidgetVisibility(node, sliderCount) {
// console.log(`[DSS Modern Hider] updateWidgetVisibility called for node ${node.id}, sliderCount: ${sliderCount}`); // Log entry
if (!node?.widgets) {
// console.warn(`[DSS Modern Hider] No widgets found for node ${node.id}`);
return;
}
// Sanitize slider count
const originalSliderCount = sliderCount;
sliderCount = Math.max(1, Math.min(Math.floor(sliderCount), NODE_CONFIG.maxSliders));
if (originalSliderCount !== sliderCount) {
// console.log(`[DSS Modern Hider] Sanitized sliderCount from ${originalSliderCount} to ${sliderCount}`);
}
// Update internal widget visibility
let hiddenChangedCount = 0;
node.widgets.slice(NODE_CONFIG.controlWidgets).forEach((widget, i) => {
const shouldBeHidden = Math.floor(i / 2) + 1 > sliderCount;
if (widget.hidden !== shouldBeHidden) {
widget.hidden = shouldBeHidden;
hiddenChangedCount++;
// console.log(`[DSS Modern Hider] Set widget ${widget.name} (index ${i}) hidden: ${shouldBeHidden}`); // Optional detailed log
}
});
// console.log(`[DSS Modern Hider] Updated 'hidden' property for ${hiddenChangedCount} widgets.`);
// Remove the DOM manipulation block entirely
/*
// Update DOM widget visibility <-- REMOVED
const nodeElement = findNodeElement(node.id);
if (nodeElement) {
const widgetContainers = nodeElement.querySelectorAll('.slotcontainer .for_widget');
if (widgetContainers?.length > NODE_CONFIG.controlWidgets) {
widgetContainers.forEach((container, i) => {
if (i >= NODE_CONFIG.controlWidgets) {
container.style.display = Math.floor((i - NODE_CONFIG.controlWidgets) / 2) + 1 > sliderCount ? 'none' : '';
}
});
}
}
*/
},
/** Create global CSS */
createGlobalStyles() {
if (document.getElementById('dynamic-slider-stack-styles')) return;
const style = document.createElement('style');
style.id = 'dynamic-slider-stack-styles';
style.textContent = `
/* Hide disabled widgets */
.widget.disabled { display: none !important; }
`;
document.head.appendChild(style);
},
/** Setup node tracking */
setupNodeTracking() {
// console.log("[DSS Modern Hider] setupNodeTracking() called.");
// Use this.app instead of global app
if (!this.app?.graph) {
// console.warn("[DSS Modern Hider] this.app.graph not available during setupNodeTracking.");
return;
}
// Process existing nodes
// console.log(`[DSS Modern Hider] Processing ${this.app.graph.nodes?.length || 0} existing nodes.`);
this.app.graph.nodes?.forEach(node => {
// Add more comprehensive check including the suffixed name
if (node.type === "Slider Stacker (DSS)" || node.type === "SliderStacker" || node.title === "Slider Stacker") {
// console.log(`[DSS Modern Hider] Found existing Slider Stacker node (ID: ${node.id}, Type: ${node.type}, Title: ${node.title}). Processing...`);
this.processNewNode(node);
}
});
// Listen for new nodes
// console.log("[DSS Modern Hider] Adding nodeCreated listener.");
// Use this.app instead of global app
this.app.graph.addEventListener("nodeCreated", (node) => {
// console.log(`[DSS Modern Hider] nodeCreated event fired for node (ID: ${node.id}, Type: ${node.type}, Title: ${node.title})`);
// Add more comprehensive check including the suffixed name
if (node.type === "Slider Stacker (DSS)" || node.type === "SliderStacker" || node.title === "Slider Stacker") {
// console.log(`[DSS Modern Hider] nodeCreated is a Slider Stacker. Scheduling processNewNode...`);
// Pass 'this' context if processNewNode uses it, although it doesn't currently
setTimeout(() => this.processNewNode(node), 0);
} else if (node.title === "Slider Receiver") { // Also log receiver for context
// console.log(`[DSS Modern Hider] nodeCreated is a Slider Receiver.`);
// Potentially call processNewNode if receiver needs processing later
}
});
// Setup DOM observer
// Pass 'this' context if setupDOMObserver uses it (it doesn't currently)
this.setupDOMObserver();
// console.log("[DSS Modern Hider] setupNodeTracking() completed.");
},
/** Setup DOM observer */
setupDOMObserver() {
if (domObserver) domObserver.disconnect();
domObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes?.forEach(domNode => { // Rename to domNode to avoid confusion with LiteGraph node
if (domNode.classList?.contains('comfy-node') && // Check for modern class name
domNode.dataset?.nodeType?.includes('Slider Stacker')) { // Check data attribute if available
// console.log(`[DSS Modern Hider] DOM Observer detected node addition (ID: ${domNode.dataset?.nodeId}, Type: ${domNode.dataset?.nodeType})`);
// Attempt to find the LiteGraph node and re-process it
// Use this.app
const lgNode = this.app?.graph?.getNodeById(parseInt(domNode.dataset?.nodeId));
if (lgNode) {
// console.log(`[DSS Modern Hider] Found corresponding LiteGraph node ${lgNode.id}. Processing...`);
this.processNewNode(lgNode);
} else {
// console.warn(`[DSS Modern Hider] Could not find LiteGraph node for DOM node ID ${domNode.dataset?.nodeId}`);
}
}
});
});
});
// Assuming the main graph container might have a different ID or structure in modern
// Let's try a more generic approach or rely on setupNodeTracking initially
// const graphCanvas = document.getElementById('graph-canvas'); // This might be legacy
// For now, let's observe the body, which is less efficient but might catch additions
// Consider refining this later if needed
// console.log("[DSS Modern Hider] Setting up DOM Observer on document.body.");
domObserver.observe(document.body, { childList: true, subtree: true });
},
/** Process a new node */
processNewNode(node) {
// console.log(`[DSS Modern Hider] processNewNode called for node (ID: ${node.id}, Type: ${node.type}, Title: ${node.title})`); // Log process entry
if (!node) {
// console.warn("[DSS Modern Hider] processNewNode called with null node.");
return;
}
// Intercept widget and update visibility (only for Stacker)
// Use more robust check
if (node.type === "Slider Stacker (DSS)" || node.type === "SliderStacker" || node.title === "Slider Stacker") {
// console.log(`[DSS Modern Hider] Node ${node.id} confirmed as Slider Stacker, calling interceptAndUpdate.`);
this.interceptAndUpdate(node);
}
// No styling needed here, assuming ComfyUI default round shape
},
/** Intercept sliders_count widget setter and update visibility */
interceptAndUpdate(node) {
const widget = node.widgets?.find(w => w.name === "sliders_count");
if (!widget) {
// console.warn(`[DSS Modern Hider] 'sliders_count' widget not found for node ${node.id}`);
return;
}
if (widget._valueSetterOverridden) {
// console.log(`[DSS Modern Hider] Setter already overridden for node ${node.id}. Triggering initial update.`);
this.updateWidgetVisibility(node, widget.value);
return;
}
// console.log(`[DSS Modern Hider] Intercepting 'sliders_count' setter for node ${node.id}`);
const originalSetter = Object.getOwnPropertyDescriptor(widget, "value")?.set;
widget._valueSetterOverridden = true;
Object.defineProperty(widget, "value", {
get: function() { return this._value; },
set: function(v) {
// console.log(`[DSS Modern Hider] 'sliders_count' setter triggered for node ${node.id}. New value: ${v}, Old value: ${this._value}`);
const oldValue = this._value;
this._value = v;
if (originalSetter) originalSetter.call(this, v);
if (oldValue !== v) {
// console.log(`[DSS Modern Hider] Value changed, calling updateWidgetVisibility.`);
extension.updateWidgetVisibility(node, v);
} else {
// console.log(`[DSS Modern Hider] Value did not change, skipping update.`);
}
},
enumerable: true, configurable: true
});
// console.log(`[DSS Modern Hider] Triggering initial update for node ${node.id} with value ${widget.value}`);
this.updateWidgetVisibility(node, widget.value); // Initial update
},
/** Clean up resources */
cleanup() {
if (domObserver) { domObserver.disconnect(); domObserver = null; }
const style = document.getElementById('dynamic-slider-stack-styles');
if (style) style.remove();
}
};
export default extension;
+195 -136
View File
@@ -1,150 +1,209 @@
import { app } from "../../scripts/app.js";
// --- Start: Simplified code snippets ---
let origProps = {};
let initialized = false;
// Helper function to find a widget by name
const findWidgetByName = (node, name) => {
return node.widgets ? node.widgets.find((w) => w.name === name) : null;
};
const doesInputWithNameExist = (node, name) => {
return node.inputs ? node.inputs.some((input) => input.name === name) : false;
};
const HIDDEN_TAG = "tschide";
function toggleWidget(node, widget, show = false, suffix = "") {
// ... (toggleWidget function remains the same) ...
if (!widget || doesInputWithNameExist(node, widget.name)) return;
if (!origProps[widget.name]) {
origProps[widget.name] = { origType: widget.type, origComputeSize: widget.computeSize };
}
const origSize = node.size;
widget.type = show ? origProps[widget.name].origType : HIDDEN_TAG + suffix;
widget.computeSize = show ? origProps[widget.name].origComputeSize : () => [0, -4];
widget.linkedWidgets?.forEach(w => toggleWidget(node, w, ":" + widget.name, show));
let newHeight = node.size[1];
try {
if (node.computeSize) {
newHeight = node.computeSize()[1];
} else if (node.onResize) {
node.onResize(node.size);
newHeight = node.size[1];
}
} catch (e) {
console.error("Error computing node size for", node.getTitle(), e);
}
node.setSize([node.size[0], newHeight]);
}
function generateWidgetNames(baseName, count) {
return Array.from({ length: count }, (_, i) => `${baseName}_${i + 1}`);
}
function handleSliderVisibility(node, countValue) {
const node_type = "Slider Stacker";
const baseNamesMap = {
"Slider Stacker": ["slider_name", ""]
};
const baseNames = baseNamesMap[node_type];
if (!baseNames) return;
const sliderCountWidget = findWidgetByName(node, "slider_count");
const maxSliders = sliderCountWidget?.options?.max || 50;
for (let i = 1; i <= maxSliders; i++) {
const nameWidget = findWidgetByName(node, `${baseNames[0]}_${i}`);
const weightWidget = findWidgetByName(node, `(${i})`);
const currentCount = Number(countValue) || 0;
const show = i <= currentCount;
toggleWidget(node, nameWidget, show);
toggleWidget(node, weightWidget, show);
}
node.graph.setDirtyCanvas(true, true);
}
function handleSliderStackerSliderCount(node, widget) {
handleSliderVisibility(node, widget.value);
}
// Updated handler map key to match new display name
const nodeWidgetHandlers = {
"Slider Stacker": { // Updated node title check
'slider_count': handleSliderStackerSliderCount
}
};
function widgetLogic(node, widget) {
// Function to check if node is our Slider Stacker
function isSliderStackerNode(node) {
const nodeTitle = node.getTitle ? node.getTitle() : node.title;
// Use the potentially updated title for the handler check
const handler = nodeWidgetHandlers[nodeTitle]?.[widget.name];
if (handler) {
handler(node, widget);
}
if (nodeTitle === "Slider Stacker") return true;
if (node.type === "Slider Stacker (DSS)") return true;
return false;
}
// --- End: Simplified code snippets ---
// Constants for hiding
const HIDDEN_TAG = "_hidden_"; // Needs a unique suffix if other nodes use similar tags
app.registerExtension({
// Updated extension name
name: "Comfy.DynamicSlidersStack.WidgetHider",
nodeCreated(node) {
const nodeTitle = node.getTitle ? node.getTitle() : node.title;
// Check for the new display name
if (nodeTitle === "Slider Stacker") {
let hasSliderStackerHandler = false;
// Cache for original widget properties
let originalWidgetProps = {};
for (const w of node.widgets || []) {
// Use updated title for handler check
if (nodeWidgetHandlers[nodeTitle]?.[w.name]) {
hasSliderStackerHandler = true;
}
}
// Function to toggle widget visibility using standard JS properties
function toggleWidgetVisibility(node, widget, show) {
if (!widget) return false; // Return false if no widget
if(hasSliderStackerHandler) {
for (const w of node.widgets) {
// Use updated title for handler check
if (nodeWidgetHandlers[nodeTitle]?.[w.name]) {
// ... (value interception logic remains the same) ...
let widgetValue = w.value;
let originalDescriptor = Object.getOwnPropertyDescriptor(w, 'value');
if (!originalDescriptor) {
let proto = Object.getPrototypeOf(w);
while (proto && !originalDescriptor && proto !== Object.prototype) {
originalDescriptor = Object.getOwnPropertyDescriptor(proto, 'value');
proto = Object.getPrototypeOf(proto);
}
}
Object.defineProperty(w, 'value', {
configurable: true,
enumerable: true,
get() {
let valueToReturn = originalDescriptor?.get
? originalDescriptor.get.call(w)
: widgetValue;
return valueToReturn;
},
set(newVal) {
if (originalDescriptor?.set) {
originalDescriptor.set.call(w, newVal);
} else {
widgetValue = newVal;
}
widgetLogic(node, w);
}
});
widgetLogic(node, w);
}
}
}
// Trigger full node redraw after setup
setTimeout(() => {
const sliderCountWidget = findWidgetByName(node, "slider_count");
if (sliderCountWidget) {
handleSliderVisibility(node, sliderCountWidget.value);
} else {
node.graph.setDirtyCanvas(true, true);
}
}, 50);
// Store original properties if not already saved
const widgetKey = `${node.id}_${widget.name}`;
if (!originalWidgetProps[widgetKey]) {
originalWidgetProps[widgetKey] = {
origType: widget.type,
origComputeSize: widget.computeSize,
origHidden: widget.hidden // Store original hidden state if available
};
}
const origProps = originalWidgetProps[widgetKey];
// Check both hidden property and our custom type tag
const isCurrentlyHidden = widget.hidden || widget.type === HIDDEN_TAG;
// Only change if the state needs toggling
if (show && !isCurrentlyHidden) return false; // Return false: no change needed
if (!show && isCurrentlyHidden) return false; // Return false: no change needed
if (show) {
// Restore original properties
widget.type = origProps.origType;
widget.hidden = origProps.origHidden || false;
widget.computeSize = origProps.origComputeSize || null; // Restore original or set to null
} else {
// Hide the widget
widget.type = HIDDEN_TAG;
widget.hidden = true;
widget.computeSize = () => [0, -4]; // Standard way to hide widget space
}
// Mark the node as needing redraw
node.setDirtyCanvas(true, false); // Only redraw foreground (nodes)
return true; // Return true: change was made
}
// Function to handle the actual update of slider visibility
function updateSliderWidgetsVisibility(node) {
if (!isSliderStackerNode(node) || !node.widgets) return;
const countWidget = findWidgetByName(node, "sliders_count");
if (!countWidget) {
// Don't log error repeatedly if widget is just missing temporarily during load
return;
}
let currentCount = 1;
try {
currentCount = parseInt(countWidget.value) || 1;
currentCount = Math.max(1, Math.min(currentCount, 50)); // Clamp value (min 1)
} catch (e) {
console.error("Slider Stacker: Error parsing sliders_count value", e); // Keep error log
currentCount = 1; // Default to 1 on error
}
const maxSliders = 50; // Assuming max 50 sliders based on Python code
let visibilityChanged = false;
// Store current width to preserve it
const currentWidth = node.size[0];
for (let i = 1; i <= maxSliders; i++) {
const nameWidget = findWidgetByName(node, `slider_name_${i}`);
const sliderWidget = findWidgetByName(node, `(${i})`);
const shouldShow = i <= currentCount;
let nameChanged = false;
let sliderChanged = false;
if (nameWidget) {
nameChanged = toggleWidgetVisibility(node, nameWidget, shouldShow);
}
if (sliderWidget) {
sliderChanged = toggleWidgetVisibility(node, sliderWidget, shouldShow);
}
if (nameChanged || sliderChanged) {
visibilityChanged = true;
}
}
});
// Trigger a resize and redraw if visibility actually changed
if (visibilityChanged && node.graph) {
// Recalculate the required size based on visible widgets
let newHeight = node.size[1]; // Default to current height
if (node.computeSize) {
try {
const computedSize = node.computeSize();
if (computedSize && computedSize.length === 2) {
newHeight = computedSize[1]; // Get the computed height
}
} catch (e) {
// Keep error log
console.error("Slider Stacker: Error computing node size after visibility change", e);
}
}
// Set the size, preserving width and using the new height
node.setSize([currentWidth, newHeight]);
// Mark the whole graph canvas as dirty to ensure redraw and layout adjustment
node.graph.setDirtyCanvas(true, true);
}
}
// Main App Extension
app.registerExtension({
name: "Comfy.DynamicSlidersStack.WidgetHider.JS",
// Called once per node type before registration
beforeRegisterNodeDef(nodeType, nodeData, appInstance) {
if (nodeData.name === "Slider Stacker (DSS)") {
// Store original onConfigure if it exists
const originalOnConfigure = nodeType.prototype.onConfigure;
// Override onConfigure to apply initial visibility
nodeType.prototype.onConfigure = function(info) {
if (originalOnConfigure) {
originalOnConfigure.apply(this, arguments);
}
requestAnimationFrame(() => updateSliderWidgetsVisibility(this));
};
// Store original onConnectionsChange if it exists
const originalOnConnectionsChange = nodeType.prototype.onConnectionsChange;
// Override onConnectionsChange to potentially update visibility if needed
nodeType.prototype.onConnectionsChange = function(side, slot, connect, link_info, output) {
if (originalOnConnectionsChange) {
originalOnConnectionsChange.apply(this, arguments);
}
requestAnimationFrame(() => updateSliderWidgetsVisibility(this));
};
}
},
// Called every time a node instance is created
nodeCreated(node) {
if (!isSliderStackerNode(node)) return;
const countWidget = findWidgetByName(node, "sliders_count");
if (!countWidget) {
// Don't log error repeatedly
return;
}
// Intercept the 'value' property of the sliders_count widget
let widgetValue = countWidget.value;
const descriptor = Object.getOwnPropertyDescriptor(countWidget, 'value');
Object.defineProperty(countWidget, 'value', {
configurable: true,
enumerable: true,
get() {
return descriptor?.get ? descriptor.get.call(countWidget) : widgetValue;
},
set(newVal) {
let changedValue = newVal;
// Ensure value is at least 1
changedValue = Math.max(1, parseInt(changedValue) || 1);
if (descriptor?.set) {
descriptor.set.call(countWidget, changedValue);
} else {
widgetValue = changedValue;
}
// Update input element visually if it exists
if(countWidget.inputEl) {
countWidget.inputEl.value = changedValue;
}
requestAnimationFrame(() => updateSliderWidgetsVisibility(node));
}
});
// Initial setup - apply visibility based on the default/loaded value
requestAnimationFrame(() => updateSliderWidgetsVisibility(node));
},
// Called when node is added to the graph (might be redundant with nodeCreated)
nodeAdded(node) {
if (!isSliderStackerNode(node)) return;
requestAnimationFrame(() => updateSliderWidgetsVisibility(node));
}
});
// console.log("Loaded Dynamic Sliders Stack Widget Hider Script");