From c1b3c97ae5c3b3359d650c5d5f036585a10ca458 Mon Sep 17 00:00:00 2001 From: LuqP2 Date: Tue, 23 Jun 2026 22:17:41 -0300 Subject: [PATCH 1/2] Add Save Node CTA visibility setting --- js/metahub_save_cta.js | 77 ++++++++++++++++++++++++++++++++++-- metadata_utils.py | 4 +- metadata_utils_impl.py | 2 +- pyproject.toml | 2 +- tests/test_metadata_utils.py | 2 +- 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/js/metahub_save_cta.js b/js/metahub_save_cta.js index 141a45f..bd0ca5d 100644 --- a/js/metahub_save_cta.js +++ b/js/metahub_save_cta.js @@ -8,6 +8,11 @@ const METAHUB_SAVE_NODE_CLASSES = new Set([ const IMAGE_METAHUB_URL = "https://www.imagemetahub.com/"; const IMAGE_METAHUB_DEEPLINK = "imagemetahub://open"; +const HIDE_CTA_SETTING_ID = "ImageMetaHub.SaveNode.hideCTAs"; +const CTA_WIDGET_NAMES = new Set([ + "Open in Image MetaHub", + "Get Image MetaHub", +]); function buildDeepLink(node) { const savedPath = node.__imageMetaHubLastSavedPath; @@ -24,6 +29,18 @@ function openUrl(url) { window.open(url, "_blank", "noopener,noreferrer"); } +function shouldHideCTA() { + if (app.extensionManager?.setting?.get) { + return app.extensionManager.setting.get(HIDE_CTA_SETTING_ID) === true; + } + + if (app.ui?.settings?.getSettingValue) { + return app.ui.settings.getSettingValue(HIDE_CTA_SETTING_ID) === true; + } + + return false; +} + function getSavedPaths(message) { const candidates = [ message?.imagemetahub_files, @@ -55,11 +72,14 @@ function addMetaHubCTA(node) { return; } - if (node.__imageMetaHubCTAAdded) { + if (shouldHideCTA()) { + removeMetaHubCTA(node); return; } - node.__imageMetaHubCTAAdded = true; + if (hasMetaHubCTA(node)) { + return; + } node.addWidget( "button", @@ -82,8 +102,59 @@ function addMetaHubCTA(node) { ); } +function hasMetaHubCTA(node) { + return node.widgets?.some((widget) => CTA_WIDGET_NAMES.has(widget.name)) === true; +} + +function removeMetaHubCTA(node) { + if (!Array.isArray(node.widgets)) { + return; + } + + const nextWidgets = node.widgets.filter( + (widget) => !CTA_WIDGET_NAMES.has(widget.name) + ); + + if (nextWidgets.length !== node.widgets.length) { + node.widgets = nextWidgets; + node.setSize?.(node.computeSize?.() ?? node.size); + } +} + +function syncMetaHubCTA(node) { + if (!METAHUB_SAVE_NODE_CLASSES.has(node.comfyClass)) { + return; + } + + if (shouldHideCTA()) { + removeMetaHubCTA(node); + } else { + addMetaHubCTA(node); + } +} + +function syncAllMetaHubCTAs() { + for (const node of app.graph?._nodes ?? []) { + syncMetaHubCTA(node); + } + + app.graph?.setDirtyCanvas?.(true, true); +} + app.registerExtension({ name: "ImageMetaHub.SaveNodeCTA", + settings: [ + { + id: HIDE_CTA_SETTING_ID, + name: "Hide Open/Get Image MetaHub buttons", + type: "boolean", + defaultValue: false, + category: ["Image MetaHub", "Save Node", "Hide Open/Get Image MetaHub buttons"], + tooltip: + 'Hide the "Open in Image MetaHub" and "Get Image MetaHub" buttons on MetaHub Save nodes.', + onChange: syncAllMetaHubCTAs, + }, + ], async beforeRegisterNodeDef(nodeType, nodeData) { if (!METAHUB_SAVE_NODE_CLASSES.has(nodeData.name)) { @@ -98,6 +169,6 @@ app.registerExtension({ }, async nodeCreated(node) { - addMetaHubCTA(node); + syncMetaHubCTA(node); }, }); diff --git a/metadata_utils.py b/metadata_utils.py index df817dd..15c9114 100644 --- a/metadata_utils.py +++ b/metadata_utils.py @@ -3,7 +3,7 @@ Compatibility shim for MetaHub Save Node metadata helpers. The implementation is kept in metadata_utils_impl.py; this module exposes the same public API expected by the save nodes while pinning the published node -version to 1.1.7. +version to 1.1.8. """ try: @@ -11,7 +11,7 @@ try: except ImportError: import metadata_utils_impl as _impl -METAHUB_SAVE_NODE_VERSION = "1.1.7" +METAHUB_SAVE_NODE_VERSION = "1.1.8" _impl.METAHUB_SAVE_NODE_VERSION = METAHUB_SAVE_NODE_VERSION for _name in dir(_impl): diff --git a/metadata_utils_impl.py b/metadata_utils_impl.py index 0533357..f277109 100644 --- a/metadata_utils_impl.py +++ b/metadata_utils_impl.py @@ -14,7 +14,7 @@ from xml.sax.saxutils import escape as xml_escape import numpy as np from PIL import Image, PngImagePlugin -METAHUB_SAVE_NODE_VERSION = "1.1.7" +METAHUB_SAVE_NODE_VERSION = "1.1.8" try: import piexif diff --git a/pyproject.toml b/pyproject.toml index 6e1a264..515bffd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "imagemetahub-comfyui-save" description = "Official companion node for Image MetaHub. Saves A1111/Civitai-compatible parameters plus extended Image MetaHub metadata (workflow JSON, SHA256 model hashes, VRAM peak/total, GPU device, generation time, and steps/sec) for reproducibility and benchmarking." -version = "1.1.7" +version = "1.1.8" license = {file = "LICENSE"} # classifiers = [ # # For OS-independent nodes (works on all operating systems) diff --git a/tests/test_metadata_utils.py b/tests/test_metadata_utils.py index 280f739..34279c9 100644 --- a/tests/test_metadata_utils.py +++ b/tests/test_metadata_utils.py @@ -334,7 +334,7 @@ def test_extract_workflow_attribution_from_node_properties(): assert attribution["token"] == "imhcrt_br_creator_workflow_v1_random" assert attribution["source"] == "metahub_save_node" - assert attribution["node_version"] == "1.1.7" + assert attribution["node_version"] == "1.1.8" def test_build_imh_metadata_includes_attribution_without_a1111_parameters(): From 686bd63836bf26e5e25fc9451189b1feb8788c19 Mon Sep 17 00:00:00 2001 From: LuqP2 Date: Sat, 27 Jun 2026 14:33:34 -0300 Subject: [PATCH 2/2] fix: sync save node ctas in subgraphs --- js/metahub_save_cta.js | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/js/metahub_save_cta.js b/js/metahub_save_cta.js index bd0ca5d..43f897e 100644 --- a/js/metahub_save_cta.js +++ b/js/metahub_save_cta.js @@ -133,11 +133,34 @@ function syncMetaHubCTA(node) { } } -function syncAllMetaHubCTAs() { - for (const node of app.graph?._nodes ?? []) { - syncMetaHubCTA(node); +function getGraphNodes(graph) { + if (Array.isArray(graph?._nodes)) { + return graph._nodes; } + if (Array.isArray(graph?.nodes)) { + return graph.nodes; + } + + return []; +} + +function walkGraphNodes(graph, visitor, visitedGraphs = new Set()) { + if (!graph || visitedGraphs.has(graph)) { + return; + } + + visitedGraphs.add(graph); + + for (const node of getGraphNodes(graph)) { + visitor(node); + walkGraphNodes(node?.subgraph, visitor, visitedGraphs); + } +} + +function syncAllMetaHubCTAs() { + walkGraphNodes(app.graph, syncMetaHubCTA); + app.graph?.setDirtyCanvas?.(true, true); }