|
|
|
|
|
import { ComfyWidgets } from "../../../../scripts/widgets.js"; |
|
|
|
import { api } from "../../../../scripts/api.js"; |
|
|
|
import { app } from "../../../../scripts/app.js"; |
|
|
|
const PathHelper = { |
|
get(obj, path) { |
|
if (typeof path !== "string") { |
|
|
|
return path; |
|
} |
|
|
|
if (path[0] === '"' && path[path.length - 1] === '"') { |
|
|
|
return JSON.parse(path); |
|
} |
|
|
|
|
|
path = path.split(".").filter(Boolean); |
|
for (const p of path) { |
|
const k = isNaN(+p) ? p : +p; |
|
obj = obj[k]; |
|
} |
|
|
|
return obj; |
|
}, |
|
set(obj, path, value) { |
|
|
|
if (Object(obj) !== obj) return obj; |
|
|
|
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || []; |
|
path.slice(0, -1).reduce( |
|
( |
|
a, |
|
c, |
|
i |
|
) => |
|
Object(a[c]) === a[c] |
|
? |
|
a[c] |
|
: |
|
(a[c] = |
|
Math.abs(path[i + 1]) >> 0 === +path[i + 1] |
|
? [] |
|
: {}), |
|
obj |
|
)[path[path.length - 1]] = value; |
|
return obj; |
|
}, |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function evaluateCondition(condition, state) { |
|
const left = PathHelper.get(state, condition.left); |
|
const right = PathHelper.get(state, condition.right); |
|
|
|
let r; |
|
if (condition.op === "eq") { |
|
r = left === right; |
|
} else { |
|
r = left !== right; |
|
} |
|
|
|
return r; |
|
} |
|
|
|
|
|
|
|
|
|
const callbacks = { |
|
|
|
|
|
|
|
async if(cb, state) { |
|
|
|
let success = true; |
|
for (const condition of cb.condition) { |
|
const r = evaluateCondition(condition, state); |
|
if (!r) { |
|
success = false; |
|
break; |
|
} |
|
} |
|
|
|
for (const m of cb[success + ""] ?? []) { |
|
await invokeCallback(m, state); |
|
} |
|
}, |
|
|
|
|
|
|
|
async fetch(cb, state) { |
|
const url = cb.url.replace(/\{([^\}]+)\}/g, (m, v) => { |
|
return PathHelper.get(state, v); |
|
}); |
|
const res = await (await api.fetchApi(url)).json(); |
|
state["$result"] = res; |
|
for (const m of cb.then) { |
|
await invokeCallback(m, state); |
|
} |
|
}, |
|
|
|
|
|
|
|
async set(cb, state) { |
|
const value = PathHelper.get(state, cb.value); |
|
PathHelper.set(state, cb.target, value); |
|
}, |
|
async "validate-combo"(cb, state) { |
|
const w = state["$this"]; |
|
const valid = w.options.values.includes(w.value); |
|
if (!valid) { |
|
w.value = w.options.values[0]; |
|
} |
|
}, |
|
}; |
|
|
|
async function invokeCallback(callback, state) { |
|
if (callback.type in callbacks) { |
|
|
|
await callbacks[callback.type](callback, state); |
|
} else { |
|
console.warn( |
|
"%c[π pysssss]", |
|
"color: limegreen", |
|
`[binding ${state.$node.comfyClass}.${state.$this.name}]`, |
|
"unsupported binding callback type:", |
|
callback.type |
|
); |
|
} |
|
} |
|
|
|
app.registerExtension({ |
|
name: "pysssss.Binding", |
|
beforeRegisterNodeDef(node, nodeData) { |
|
const hasBinding = (v) => { |
|
if (!v) return false; |
|
return Object.values(v).find((c) => c[1]?.["pysssss.binding"]); |
|
}; |
|
const inputs = { ...nodeData.input?.required, ...nodeData.input?.optional }; |
|
if (hasBinding(inputs)) { |
|
const onAdded = node.prototype.onAdded; |
|
node.prototype.onAdded = function () { |
|
const r = onAdded?.apply(this, arguments); |
|
|
|
for (const widget of this.widgets || []) { |
|
const bindings = inputs[widget.name][1]?.["pysssss.binding"]; |
|
if (!bindings) continue; |
|
|
|
for (const binding of bindings) { |
|
|
|
|
|
|
|
const source = this.widgets.find((w) => w.name === binding.source); |
|
if (!source) { |
|
console.warn( |
|
"%c[π pysssss]", |
|
"color: limegreen", |
|
`[binding ${node.comfyClass}.${widget.name}]`, |
|
"unable to find source binding widget:", |
|
binding.source, |
|
binding |
|
); |
|
continue; |
|
} |
|
|
|
let lastValue; |
|
async function valueChanged() { |
|
const state = { |
|
$this: widget, |
|
$source: source, |
|
$node: node, |
|
}; |
|
|
|
for (const callback of binding.callback) { |
|
await invokeCallback(callback, state); |
|
} |
|
|
|
app.graph.setDirtyCanvas(true, false); |
|
} |
|
|
|
const cb = source.callback; |
|
source.callback = function () { |
|
const v = cb?.apply(this, arguments) ?? source.value; |
|
if (v !== lastValue) { |
|
lastValue = v; |
|
valueChanged(); |
|
} |
|
return v; |
|
}; |
|
|
|
lastValue = source.value; |
|
valueChanged(); |
|
} |
|
} |
|
|
|
return r; |
|
}; |
|
} |
|
}, |
|
}); |
|
|