Fix live list updates: install graph hooks in onAdded + title-hash rename polling (new FE compat)
This commit is contained in:
+85
-17
@@ -74,6 +74,44 @@ function applyValue(targetNode, mode, value) {
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
|
||||
// ── rename polling (title hash) ─────────────────────────
|
||||
// The new frontend renames nodes by assigning node.title directly (no
|
||||
// graph.change() / onPropertyChanged fired), so renames are detected by
|
||||
// polling a title hash (cg-use-everywhere approach). One shared 500ms
|
||||
// interval: started when the first panel installs its hooks, stopped when
|
||||
// the last panel is removed.
|
||||
const panels = new Set();
|
||||
let titlesPollTimer = null;
|
||||
let lastTitlesKey = "";
|
||||
|
||||
function titlesKey() {
|
||||
return (app.graph?._nodes || [])
|
||||
.map((n) => (n.title || "") + "\u0000" + (n.type || ""))
|
||||
.join("|");
|
||||
}
|
||||
|
||||
function ensurePolling() {
|
||||
if (titlesPollTimer == null && panels.size > 0) {
|
||||
lastTitlesKey = titlesKey();
|
||||
titlesPollTimer = setInterval(() => {
|
||||
const k = titlesKey();
|
||||
if (k !== lastTitlesKey) {
|
||||
lastTitlesKey = k;
|
||||
panels.forEach((p) => {
|
||||
if (typeof p.__scheduleRefresh === "function") p.__scheduleRefresh();
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
function stopPollingIfEmpty() {
|
||||
if (panels.size === 0 && titlesPollTimer != null) {
|
||||
clearInterval(titlesPollTimer);
|
||||
titlesPollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── panel widget builder ─────────────────────────────────
|
||||
|
||||
function buildPanel(panelNode) {
|
||||
@@ -261,21 +299,50 @@ function buildPanel(panelNode) {
|
||||
|
||||
// ── event hooks ───────────────────────────────────────
|
||||
|
||||
// Graph changes (add/remove/rename nodes) — LiteGraph has no graph.on("change")
|
||||
// event; chain the on_change property hook instead (works on old and new litegraph).
|
||||
const graph = panelNode.graph;
|
||||
const prevOnChange = graph ? graph.on_change : null;
|
||||
const onGraphChangeWrapper = (g) => {
|
||||
if (prevOnChange) prevOnChange(g);
|
||||
scheduleRefresh();
|
||||
};
|
||||
if (graph) graph.on_change = onGraphChangeWrapper;
|
||||
// Expose scheduleRefresh so the module-level rename poller can trigger
|
||||
// refreshes on all live panels.
|
||||
panelNode.__scheduleRefresh = scheduleRefresh;
|
||||
|
||||
// Title / property changes
|
||||
const origOnPropertyChanged = panelNode.onPropertyChanged;
|
||||
panelNode.onPropertyChanged = function (prop, value) {
|
||||
if (prop === "title") scheduleRefresh();
|
||||
if (origOnPropertyChanged) origOnPropertyChanged.call(this, prop, value);
|
||||
// nodeCreated runs inside the ComfyNode constructor, where node.graph is
|
||||
// always null — so graph hooks must be installed in onAdded (fires with the
|
||||
// graph argument; node.graph is set by then). Add/remove both fire
|
||||
// graph.change(), so chaining graph.on_change plus onNodeAdded/onNodeRemoved
|
||||
// (belt & suspenders) covers structural changes.
|
||||
let prevChange = null;
|
||||
let prevNodeAdded = null;
|
||||
let prevNodeRemoved = null;
|
||||
const onChangeWrapper = (g) => { prevChange?.(g); scheduleRefresh(); };
|
||||
const onNodeAddedWrapper = (n) => { prevNodeAdded?.(n); scheduleRefresh(); };
|
||||
const onNodeRemovedWrapper = (n) => { prevNodeRemoved?.(n); scheduleRefresh(); };
|
||||
|
||||
function installGraphHooks(g) {
|
||||
if (!g || panelNode.__hooksInstalled) return;
|
||||
panelNode.__hooksInstalled = true;
|
||||
prevChange = g.on_change;
|
||||
prevNodeAdded = g.onNodeAdded;
|
||||
prevNodeRemoved = g.onNodeRemoved;
|
||||
g.on_change = onChangeWrapper;
|
||||
g.onNodeAdded = onNodeAddedWrapper;
|
||||
g.onNodeRemoved = onNodeRemovedWrapper;
|
||||
}
|
||||
|
||||
function uninstallGraphHooks() {
|
||||
const g = panelNode.graph;
|
||||
if (!panelNode.__hooksInstalled || !g) return;
|
||||
// Restore only when our wrapper is still the current handler.
|
||||
if (g.on_change === onChangeWrapper) g.on_change = prevChange;
|
||||
if (g.onNodeAdded === onNodeAddedWrapper) g.onNodeAdded = prevNodeAdded;
|
||||
if (g.onNodeRemoved === onNodeRemovedWrapper) g.onNodeRemoved = prevNodeRemoved;
|
||||
panelNode.__hooksInstalled = false;
|
||||
prevChange = prevNodeAdded = prevNodeRemoved = null;
|
||||
}
|
||||
|
||||
const origOnAdded = panelNode.onAdded;
|
||||
panelNode.onAdded = function (graph) {
|
||||
installGraphHooks(graph || panelNode.graph);
|
||||
panels.add(panelNode);
|
||||
ensurePolling();
|
||||
if (origOnAdded) origOnAdded.call(this, graph);
|
||||
};
|
||||
|
||||
// Mode widget changes — re-evaluate enabled/disabled rows
|
||||
@@ -294,9 +361,10 @@ function buildPanel(panelNode) {
|
||||
// Cleanup on removal
|
||||
const origOnRemoved = panelNode.onRemoved;
|
||||
panelNode.onRemoved = function () {
|
||||
if (panelNode.graph && panelNode.graph.on_change === onGraphChangeWrapper) {
|
||||
panelNode.graph.on_change = prevOnChange;
|
||||
}
|
||||
uninstallGraphHooks();
|
||||
panels.delete(panelNode);
|
||||
panelNode.__scheduleRefresh = null;
|
||||
stopPollingIfEmpty();
|
||||
if (origOnRemoved) origOnRemoved.call(this);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user