257 lines
9.8 KiB
JavaScript
257 lines
9.8 KiB
JavaScript
var __defProp = Object.defineProperty;
|
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
import { Logger } from "../../shared/logger.js";
|
|
class WidgetSyncManager {
|
|
/**
|
|
* Update a widget value on a node
|
|
* @param nodeId - The node ID
|
|
* @param widgetName - The widget name
|
|
* @param value - The new value to set
|
|
* @returns SyncResult indicating success or failure
|
|
*/
|
|
static syncWidgetValue(nodeId, widgetName, value) {
|
|
try {
|
|
if (!(app == null ? void 0 : app.graph)) {
|
|
return { success: false, error: "ComfyUI app not available" };
|
|
}
|
|
const node = app.graph.getNodeById(nodeId);
|
|
if (!node) {
|
|
return { success: false, error: `Node ${nodeId} not found` };
|
|
}
|
|
if (!node.widgets || node.widgets.length === 0) {
|
|
return { success: false, error: `Node ${nodeId} has no widgets` };
|
|
}
|
|
const widget = node.widgets.find((w) => w.name === widgetName);
|
|
if (!widget) {
|
|
return { success: false, error: `Widget "${widgetName}" not found on node ${nodeId}` };
|
|
}
|
|
const previousValue = widget.value;
|
|
const constrainedValue = this.applyConstraints(value, widget);
|
|
widget.value = constrainedValue;
|
|
if (typeof widget.callback === "function") {
|
|
try {
|
|
widget.callback(constrainedValue, app.canvas, node, [0, 0], null);
|
|
} catch (callbackError) {
|
|
Logger.warn(`Widget callback failed for ${widgetName}:`, callbackError);
|
|
}
|
|
}
|
|
if (typeof node.setDirtyCanvas === "function") {
|
|
node.setDirtyCanvas(true, true);
|
|
}
|
|
this.triggerCanvasRedraw();
|
|
Logger.debug(`[WidgetSync] Updated ${widgetName} on node ${nodeId}: ${previousValue} → ${constrainedValue}`);
|
|
return {
|
|
success: true,
|
|
previousValue,
|
|
newValue: constrainedValue
|
|
};
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
Logger.error(`[WidgetSync] Error syncing widget:`, error);
|
|
return { success: false, error: message };
|
|
}
|
|
}
|
|
/**
|
|
* Debounced version of syncWidgetValue for text input
|
|
* @param nodeId - The node ID
|
|
* @param widgetName - The widget name
|
|
* @param value - The new value to set
|
|
* @param debounceMs - Debounce delay in milliseconds (default 50ms)
|
|
*/
|
|
static syncWidgetValueDebounced(nodeId, widgetName, value, debounceMs = this.DEBOUNCE_MS) {
|
|
const key = `${nodeId}:${widgetName}`;
|
|
const existingTimer = this.debounceTimers.get(key);
|
|
if (existingTimer) {
|
|
window.clearTimeout(existingTimer);
|
|
}
|
|
const timer = window.setTimeout(() => {
|
|
this.syncWidgetValue(nodeId, widgetName, value);
|
|
this.debounceTimers.delete(key);
|
|
}, debounceMs);
|
|
this.debounceTimers.set(key, timer);
|
|
}
|
|
/**
|
|
* Get widget constraints for validation
|
|
* @param nodeId - The node ID
|
|
* @param widgetName - The widget name
|
|
* @returns WidgetConstraints or null if widget not found
|
|
*/
|
|
static getWidgetConstraints(nodeId, widgetName) {
|
|
try {
|
|
if (!(app == null ? void 0 : app.graph)) return null;
|
|
const node = app.graph.getNodeById(nodeId);
|
|
if (!(node == null ? void 0 : node.widgets)) return null;
|
|
const widget = node.widgets.find((w) => w.name === widgetName);
|
|
if (!widget) return null;
|
|
return this.extractConstraints(widget);
|
|
} catch (error) {
|
|
Logger.warn(`[WidgetSync] Error getting constraints:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
/**
|
|
* Extract constraints from a widget object
|
|
*/
|
|
static extractConstraints(widget) {
|
|
const constraints = {};
|
|
if (typeof widget.min === "number") constraints.min = widget.min;
|
|
if (typeof widget.max === "number") constraints.max = widget.max;
|
|
if (typeof widget.step === "number") constraints.step = widget.step;
|
|
if (widget.options) {
|
|
if (typeof widget.options.min === "number") constraints.min = widget.options.min;
|
|
if (typeof widget.options.max === "number") constraints.max = widget.options.max;
|
|
if (typeof widget.options.step === "number") constraints.step = widget.options.step;
|
|
if (typeof widget.options.precision === "number") constraints.precision = widget.options.precision;
|
|
if (Array.isArray(widget.options.values)) {
|
|
constraints.options = widget.options.values;
|
|
}
|
|
}
|
|
if (Array.isArray(widget.options)) {
|
|
constraints.options = widget.options;
|
|
}
|
|
return constraints;
|
|
}
|
|
/**
|
|
* Apply constraints to a value
|
|
*/
|
|
static applyConstraints(value, widget) {
|
|
const constraints = this.extractConstraints(widget);
|
|
const widgetType = this.getWidgetType(widget);
|
|
if (widgetType === "number" || widgetType === "INT" || widgetType === "FLOAT" || widgetType === "slider") {
|
|
let numValue = typeof value === "number" ? value : parseFloat(String(value));
|
|
if (isNaN(numValue)) {
|
|
return widget.value;
|
|
}
|
|
if (typeof constraints.min === "number") {
|
|
numValue = Math.max(constraints.min, numValue);
|
|
}
|
|
if (typeof constraints.max === "number") {
|
|
numValue = Math.min(constraints.max, numValue);
|
|
}
|
|
if (typeof constraints.precision === "number") {
|
|
numValue = parseFloat(numValue.toFixed(constraints.precision));
|
|
}
|
|
return numValue;
|
|
}
|
|
if (widgetType === "combo" && constraints.options) {
|
|
const valueStr = String(value);
|
|
const hasMatch = constraints.options.some((opt) => String(opt) === valueStr);
|
|
if (!hasMatch) {
|
|
Logger.warn(`[WidgetSync] Invalid combo value "${value}", keeping current`);
|
|
return widget.value;
|
|
}
|
|
const matchedOption = constraints.options.find((opt) => String(opt) === valueStr);
|
|
if (matchedOption !== void 0) {
|
|
return matchedOption;
|
|
}
|
|
}
|
|
if (widgetType === "toggle" || widgetType === "boolean") {
|
|
return Boolean(value);
|
|
}
|
|
return value;
|
|
}
|
|
/**
|
|
* Determine widget type from widget object
|
|
*/
|
|
static getWidgetType(widget) {
|
|
if (!widget) return "unknown";
|
|
const type = (widget.type || "").toLowerCase();
|
|
if (type === "button") return "button";
|
|
if (type === "number" || type === "int" || type === "float") return "number";
|
|
if (type === "combo" || type === "string" && Array.isArray(widget.options)) return "combo";
|
|
if (type === "toggle" || type === "boolean") return "boolean";
|
|
if (type === "slider") return "slider";
|
|
if (type === "text" || type === "string" || type === "customtext") return "text";
|
|
if (Array.isArray(widget.options) && widget.options.length > 0) {
|
|
return "combo";
|
|
}
|
|
if (typeof widget.callback === "function" && widget.name) {
|
|
const name = widget.name.toLowerCase();
|
|
const buttonPatterns = ["show", "save", "open", "load", "export", "import", "reset", "clear", "apply", "run", "execute", "preview", "refresh", "update"];
|
|
const hasButtonName = buttonPatterns.some((pattern) => name.includes(pattern));
|
|
const hasNonEditableValue = widget.value === void 0 || widget.value === null || typeof widget.value === "boolean";
|
|
if (hasButtonName && hasNonEditableValue) {
|
|
return "button";
|
|
}
|
|
}
|
|
if (typeof widget.callback === "function" && widget.value === void 0 && !Array.isArray(widget.options)) {
|
|
return "button";
|
|
}
|
|
return "text";
|
|
}
|
|
/**
|
|
* Check if a widget is editable
|
|
*/
|
|
static isWidgetEditable(widget) {
|
|
if (!widget) return false;
|
|
if (widget.hidden) return false;
|
|
if (widget.readonly) return false;
|
|
const type = this.getWidgetType(widget);
|
|
if (type === "button") return false;
|
|
const editableTypes = ["number", "text", "combo", "boolean", "slider", "INT", "FLOAT"];
|
|
return editableTypes.includes(type) || editableTypes.includes(type.toUpperCase());
|
|
}
|
|
/**
|
|
* Force canvas redraw after widget update
|
|
*/
|
|
static triggerCanvasRedraw() {
|
|
try {
|
|
if (app == null ? void 0 : app.canvas) {
|
|
if (typeof app.canvas.setDirty === "function") {
|
|
app.canvas.setDirty(true, true);
|
|
}
|
|
if (typeof app.canvas.draw === "function") {
|
|
app.canvas.draw(true, true);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
Logger.warn(`[WidgetSync] Canvas redraw failed:`, error);
|
|
}
|
|
}
|
|
/**
|
|
* Get current value of a widget
|
|
*/
|
|
static getWidgetValue(nodeId, widgetName) {
|
|
try {
|
|
if (!(app == null ? void 0 : app.graph)) return null;
|
|
const node = app.graph.getNodeById(nodeId);
|
|
if (!(node == null ? void 0 : node.widgets)) return null;
|
|
const widget = node.widgets.find((w) => w.name === widgetName);
|
|
return (widget == null ? void 0 : widget.value) ?? null;
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
/**
|
|
* Validate a value against widget constraints without applying
|
|
*/
|
|
static validateValue(nodeId, widgetName, value) {
|
|
const constraints = this.getWidgetConstraints(nodeId, widgetName);
|
|
if (!constraints) {
|
|
return { valid: true };
|
|
}
|
|
if (typeof value === "number") {
|
|
if (typeof constraints.min === "number" && value < constraints.min) {
|
|
return { valid: false, error: `Value must be at least ${constraints.min}` };
|
|
}
|
|
if (typeof constraints.max === "number" && value > constraints.max) {
|
|
return { valid: false, error: `Value must be at most ${constraints.max}` };
|
|
}
|
|
}
|
|
if (constraints.options) {
|
|
const valueStr = String(value);
|
|
const hasMatch = constraints.options.some((opt) => String(opt) === valueStr);
|
|
if (!hasMatch) {
|
|
return { valid: false, error: `Invalid option selected` };
|
|
}
|
|
}
|
|
return { valid: true };
|
|
}
|
|
}
|
|
__publicField(WidgetSyncManager, "debounceTimers", /* @__PURE__ */ new Map());
|
|
__publicField(WidgetSyncManager, "DEBOUNCE_MS", 50);
|
|
export {
|
|
WidgetSyncManager
|
|
};
|