Files
electricty00-ComfyUI-Toggle…/js/privacy_toggle.js
T
GBrain 60665017a9 feat: broadcast switch on node top, selection-rect fix, ship missing sampler
- js/broadcast_toggle.js: per-node global broadcast toggle, drawn on the node top row next to the toggle switch (state kept in node.properties.tp_broadcast, serialize:false so old workflows are untouched)

- js/selection_rect_fix.js: work around a clipped .graph-canvas-panel so the drag-select rectangle follows the pointer (opt out via localStorage.tp_selrect_fix=off)

- js/canvas_menu.js, js/sampler_widgets.js, sampler.py: ship modules that were referenced but missing from the repo

- multi_ref_encode.py, js/multi_ref_encode.js, unified_loader.py, __init__.py: multi-reference encoding rework plus Krea2 CLIPType support

- example_workflows: rename Toggle-Pass-Test.json to 'All-in-One Demo.json', drop the three obsolete preview PNGs

- README: bilingual docs, example workflow table, changelog
2026-10-25 20:55:41 +08:00

169 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Privacy Toggle - 隐私遮罩(Toggle-Pass 图片节点)
*
* 适用于 SaveLoadImage、ToggleLoadImage、TogglePreviewImage
* 核心思路:用 Object.defineProperty 在实例上拦截 imgs 的读写
* 隐私开启时,任何代码读 this.imgs 都返回 [],写操作保存到 _hiddenImgs
* 标题栏按钮切换,不影响节点尺寸
*
* 按钮含义:
* 👁 = 显示图片(隐私关闭)
* 🫣 = 隐藏图片(隐私开启)
*/
import { app } from "../../scripts/app.js";
const PRIVACY_KEY = "privacy_hidden";
const TARGET_NODES = ["SaveLoadImage", "ToggleLoadImage", "TogglePreviewImage"];
const BTN_SIZE = 14;
const BTN_MARGIN = 6;
/** 开启隐私:拦截实例的 imgs 属性 */
function enablePrivacy(node) {
// 保存真实图片
if (node.imgs && node.imgs.length > 0) {
node._hiddenImgs = node.imgs;
}
// 在实例上定义 imgs,拦截所有读写
Object.defineProperty(node, 'imgs', {
get() { return []; },
set(val) {
// 写入时保存到 _hiddenImgs,但不让外部读到
if (val && val.length > 0) {
this._hiddenImgs = val;
}
},
configurable: true,
enumerable: true,
});
node[PRIVACY_KEY] = true;
}
/** 关闭隐私:删除实例拦截,恢复正常 */
function disablePrivacy(node) {
// 删除实例上的拦截,让 imgs 回到正常行为
delete node.imgs;
// 恢复图片
if (node._hiddenImgs && node._hiddenImgs.length > 0) {
node.imgs = node._hiddenImgs;
}
node[PRIVACY_KEY] = false;
node._hiddenImgs = null;
}
app.registerExtension({
name: "ComfyUI-Toggle-Pass-privacy",
beforeRegisterNodeDef(nodeType, nodeData, app) {
if (!TARGET_NODES.includes(nodeData.name)) return;
// ===== 新建节点时兜一个正常尺寸 =====
// TogglePreviewImage 只有 1 个 IMAGE 输入、没有 widget,computeSize() 只算出
// 约 26px 高 —— 添加后看起来像被"折叠"成一条。这里仅在「新建」时兜底;
// 加载已有工作流时 onConfigure 会把用户自己存的 size 恢复回来,不受影响。
if (nodeData.name === "TogglePreviewImage") {
const origCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
origCreated?.apply(this, arguments);
const cs = this.computeSize();
// 与 Toggle Load Image 的初始尺寸保持一致(实测 270×102)
const w = Math.max(cs[0], 270);
const h = Math.max(cs[1], 102);
const sw = Number(this.size?.[0]);
const sh = Number(this.size?.[1]);
if (!Number.isFinite(sw) || !Number.isFinite(sh) || sw < w - 0.5 || sh < h - 0.5) {
this.setSize([w, h]);
this.setDirtyCanvas?.(true, true);
}
};
}
// ===== 序列化 =====
const origSerialize = nodeType.prototype.serialize;
nodeType.prototype.serialize = function () {
const data = origSerialize ? origSerialize.apply(this, arguments) : {};
if (this[PRIVACY_KEY]) data[PRIVACY_KEY] = true;
return data;
};
// ===== 反序列化 =====
const origConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function (info) {
origConfigure?.apply(this, arguments);
if (info?.[PRIVACY_KEY]) {
this[PRIVACY_KEY] = true;
enablePrivacy(this);
}
};
// ===== onExecuted:执行后若隐私开启则拦截 =====
const origExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
origExecuted?.apply(this, arguments);
if (this[PRIVACY_KEY]) {
// 已经在 enablePrivacy 里拦截了,这里不需要再做任何事
// 因为 set imgs 会被拦截,图片自动保存到 _hiddenImgs
} else {
this._hiddenImgs = [];
}
app.graph.change();
};
// ===== onDrawForeground:只负责绘制标题栏图标 =====
const origDrawFg = nodeType.prototype.onDrawForeground;
nodeType.prototype.onDrawForeground = function (ctx) {
origDrawFg?.apply(this, arguments);
// ===== 绘制标题栏图标 =====
const [w] = this.size;
const titleH = LiteGraph.NODE_TITLE_HEIGHT || 30;
const icon = this[PRIVACY_KEY] ? "🫣" : "👁";
let iconX;
if (this.flags?.collapsed) {
const textW = ctx.measureText(this.title || "").width;
iconX = textW + 30 - BTN_MARGIN;
} else {
iconX = w - BTN_MARGIN;
}
this._privacyBtnRight = iconX;
ctx.save();
ctx.font = `${BTN_SIZE}px Arial`;
ctx.textAlign = "right";
ctx.textBaseline = "middle";
ctx.fillText(icon, iconX, -titleH / 2);
ctx.restore();
};
// ===== 点击标题栏图标切换 =====
const origMouseDown = nodeType.prototype.onMouseDown;
nodeType.prototype.onMouseDown = function (e, pos, canvas) {
const titleH = LiteGraph.NODE_TITLE_HEIGHT || 30;
const iconRight = this._privacyBtnRight || this.size[0];
if (
pos[1] >= -titleH &&
pos[1] <= 0 &&
pos[0] >= iconRight - 28 &&
pos[0] <= iconRight
) {
if (this[PRIVACY_KEY]) {
disablePrivacy(this);
} else {
enablePrivacy(this);
}
this.setDirtyCanvas(true, true);
app.graph.change();
return true;
}
return origMouseDown?.apply(this, arguments);
};
},
});