Fix live preview media refresh

This commit is contained in:
Ewald
2026-05-01 20:03:25 +02:00
parent ad937b4873
commit c2f25dcd41
6 changed files with 92 additions and 7 deletions
+1 -1
View File
@@ -623,7 +623,7 @@ All nodes process batches natively:
**Author**: Majoor
**Category**: `image/imageops`
**Version**: 0.1.2
**Version**: 0.1.4
---
+18 -2
View File
@@ -70,6 +70,18 @@ def _field_factory(kind: str):
return getattr(_node20_io, fallback_map.get(kind, "String"), None)
def _custom_field_factory(type_name: str):
if _node20_io is None:
return None
custom = getattr(_node20_io, "Custom", None)
if callable(custom):
try:
return custom(type_name)
except Exception:
return None
return None
def _make_schema_input(name: str, spec, optional: bool = False):
if _node20_io is None:
return None
@@ -104,7 +116,9 @@ def _make_schema_input(name: str, spec, optional: bool = False):
return factory.Input(name, options=list(raw_type), **kwargs)
type_name = str(raw_type or "STRING").upper()
if type_name == "COLOR" or ("COLOR" in name.lower() and type_name == "STRING"):
if "," in type_name or type_name == "VIDEO":
factory = _custom_field_factory(type_name) or _field_factory("Image")
elif type_name == "COLOR" or ("COLOR" in name.lower() and type_name == "STRING"):
factory = _field_factory("Color")
elif type_name in {"BOOLEAN", "BOOL"}:
factory = _field_factory("Boolean")
@@ -144,7 +158,9 @@ def _make_schema_output(name: str, output_type: str):
if _node20_io is None:
return None
kind = str(output_type or "STRING").upper()
if kind == "MASK":
if "," in kind or kind == "VIDEO":
factory = _custom_field_factory(kind) or _field_factory("Image")
elif kind == "MASK":
factory = _field_factory("Mask")
elif "IMAGE" in kind or "VIDEO" in kind:
factory = _field_factory("Image")
+37 -1
View File
@@ -417,6 +417,18 @@ function registerImageOpsLivePreview() {
startLoopIfVideo(n);
}
}
function refreshDependentsSoon(changedNode) {
refreshDependents(changedNode);
for (const delayMs of [50, 250]) {
setTimeout(() => {
try {
refreshDependents(changedNode);
} catch (e) {
console.warn("[ImageOps] delayed refreshDependents threw", e);
}
}, delayMs);
}
}
function hookNode(node) {
if (!IMAGEOPS_CLASSES.has(node.comfyClass)) {
if (node.__imageops_hooked_ext) return;
@@ -430,12 +442,36 @@ function registerImageOpsLivePreview() {
console.warn("[ImageOps] upstream onExecuted threw", e);
}
try {
refreshDependents(node);
refreshDependentsSoon(node);
} catch (e) {
console.warn("[ImageOps] refreshDependents threw", e);
}
return r;
};
for (const w of node.widgets ?? []) {
if (typeof w.callback !== "function" && typeof w.callback !== "undefined") continue;
const orig = w.callback;
w.callback = function() {
const r = orig?.apply(this, arguments);
try {
refreshDependentsSoon(node);
} catch (e) {
console.warn("[ImageOps] widget refreshDependents threw", e);
}
return r;
};
const element = w.element;
if (element && !w.__imageops_dom_refresh_hooked) {
w.__imageops_dom_refresh_hooked = true;
element.addEventListener?.("change", () => {
try {
refreshDependentsSoon(node);
} catch (e) {
console.warn("[ImageOps] widget change refreshDependents threw", e);
}
});
}
}
return;
}
const st = ensureState(node);
+9 -1
View File
@@ -41,6 +41,14 @@ class ImageOpsChannel:
return build_node_preview_result(source, (source, output_mask), prefix="imageops_channel")
extracted = _extract_channel_mask(source, channel)
result = _channel_mask_to_image(extracted, source)
if str(_scalar(channel, str)).strip().lower() == "alpha":
result = source.clone()
if result.shape[-1] < 4:
result = _channel_mask_to_image(extracted, source)
else:
result[..., :3] = 1.0
result[..., 3] = extracted.to(device=result.device, dtype=result.dtype)
else:
result = _channel_mask_to_image(extracted, source)
progress.finish()
return build_node_preview_result(result, (result, extracted), prefix="imageops_channel")
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "comfyui-majoor-imageops",
"version": "0.1.3",
"version": "0.1.4",
"description": "WORK IN PROGRESS",
"scripts": {
"build": "node esbuild.mjs",
+26 -1
View File
@@ -473,6 +473,15 @@ export function registerImageOpsLivePreview(): void {
}
}
function refreshDependentsSoon(changedNode: ComfyNode): void {
refreshDependents(changedNode);
for (const delayMs of [50, 250]) {
setTimeout(() => {
try { refreshDependents(changedNode); } catch (e) { console.warn("[ImageOps] delayed refreshDependents threw", e); }
}, delayMs);
}
}
function hookNode(node: ComfyNode): void {
// Non-ImageOps nodes: only hook onExecuted to refresh downstream ImageOps nodes.
// Avoid creating heavy state objects or wrapping widgets on every node in the graph.
@@ -483,9 +492,25 @@ export function registerImageOpsLivePreview(): void {
node.onExecuted = function (this: any, message: any) {
let r: any;
try { r = origOnExecuted0?.apply(this, arguments as any); } catch (e) { console.warn("[ImageOps] upstream onExecuted threw", e); }
try { refreshDependents(node); } catch (e) { console.warn("[ImageOps] refreshDependents threw", e); }
try { refreshDependentsSoon(node); } catch (e) { console.warn("[ImageOps] refreshDependents threw", e); }
return r;
};
for (const w of (node.widgets ?? [])) {
if (typeof w.callback !== "function" && typeof w.callback !== "undefined") continue;
const orig = w.callback;
w.callback = function (this: any) {
const r = orig?.apply(this, arguments as any);
try { refreshDependentsSoon(node); } catch (e) { console.warn("[ImageOps] widget refreshDependents threw", e); }
return r;
};
const element = w.element as HTMLElement | null | undefined;
if (element && !(w as any).__imageops_dom_refresh_hooked) {
(w as any).__imageops_dom_refresh_hooked = true;
element.addEventListener?.("change", () => {
try { refreshDependentsSoon(node); } catch (e) { console.warn("[ImageOps] widget change refreshDependents threw", e); }
});
}
}
return;
}