Files
electricty00-ComfyUI-Toggle…/js/selection_rect_fix.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

120 lines
5.3 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.
/**
* 选择框位置修正 —— 绕过 ComfyUI 前端(1.57.x)的一个裁剪 bug。
*
* 现象:在画布**上半部分**按住鼠标拖框,框却从画面中下部出现;或者干脆整块不见了,
* 看起来像「鼠标在左上角拉、框跑到中间」。框的左右位置往往是对的,**只有上下不对**。
*
* 根因(读前端源码 + 实测确认,不是猜的):
* `SelectionRectangle` 组件(GraphView-*.js)拿 `pointer.eDown/eMove.safeOffsetX/Y`
* 算出框的矩形后,还要跟 `panelEl` 的矩形**求交集**再渲染:
* const { left, top, right, bottom } = i ? intersect(rect, i) : rect;
* 而 `panelEl` 绑的是 `.graph-canvas-panel`,实测这个容器并不等于整个画布:
* 画布 canvas = [0, 0, 1600, 852]
* .graph-canvas-panel = [64, 488, 1528, 356] ← 只覆盖画布右下部
* 于是在 y < 488 的位置拖框时,交集把框的 top 强行抬到 488、bottom 却还小于 488,
* 高度被压成 0 —— 框就「消失」或「跳到下面」了。
* (实测 1300x700 / 1600x900 / 1920x1080 / 2560x1400 四种窗口都一样;
* 拦截全部自定义扩展后也一样 ⇒ 与任何插件无关,是前端自身的问题。)
*
* 修法:不改任何布局,只在这条框元素上,把 left/top/width/height 按 `pointer.eDown/eMove`
* 的**真实按下点 / 当前点**重写一遍。纯显示层覆盖,不碰画布状态、不影响选中逻辑。
*
* ⚠️ 为什么用 MutationObserver 而不是逐帧 requestAnimationFrame:
* 前端也是在渲染帧/微任务里改这条框的 style。rAF 回调按**注册顺序**执行,
* 本文件的循环是最早注册的那批,永远排在 Vue 写 style **之前** ⇒ 写了也立刻被覆盖
* (实测:补丁写入 37 次/轮,框的位置一次都没变)。MutationObserver 的回调排在
* DOM 变更**之后**,才能真正盖住它。
*
* 停用:控制台执行 `localStorage.setItem("tp_selrect_fix", "off")` 后刷新;
* 恢复用 `localStorage.removeItem("tp_selrect_fix")`。
*/
const SEL_KEY = "tp_selrect_fix";
const SEL_SELECTOR = '[data-testid="selection-rectangle"]';
function selRectFixEnabled() {
try {
return localStorage.getItem(SEL_KEY) !== "off";
} catch (e) {
return true;
}
}
function installSelectionRectFix() {
if (typeof window === "undefined" || window.__tpSelRectFix) return;
if (!selRectFixEnabled()) return;
window.__tpSelRectFix = true;
let target = null;
let observer = null;
/** 按真实按下点/当前点写回框的位置;返回 true 表示写过 */
function apply() {
const cv = typeof app !== "undefined" ? app && app.canvas : window.app && window.app.canvas;
/* 非拖动期不介入(这一步也保证不会跟官方的显隐逻辑打架) */
if (!cv || !cv.dragging_rectangle || !target) return false;
const p = cv.pointer;
if (!p || !p.eDown || !p.eMove) return false;
const x0 = p.eDown.safeOffsetX;
const y0 = p.eDown.safeOffsetY;
const x1 = p.eMove.safeOffsetX;
const y1 = p.eMove.safeOffsetY;
if (!isFinite(x0) || !isFinite(y0) || !isFinite(x1) || !isFinite(y1)) return false;
const s = target.style;
const L = Math.min(x0, x1);
const T = Math.min(y0, y1);
const W = Math.abs(x1 - x0);
const H = Math.abs(y1 - y0);
/* 必须「值不同才写」:无条件写会和 MutationObserver 形成自激循环 */
if (s.left !== L + "px") s.left = L + "px";
if (s.top !== T + "px") s.top = T + "px";
if (s.width !== W + "px") s.width = W + "px";
if (s.height !== H + "px") s.height = H + "px";
/* 官方用 v-show 控制显隐;框本该显示却因被裁成 0 高而隐藏时,恢复可见 */
if (s.display === "none") s.display = "";
return true;
}
/** 盯住那条框元素(v-show 只切 display,元素本身常驻,通常一次就绑上) */
function attach() {
const el = document.querySelector(SEL_SELECTOR);
if (!el || el === target) return;
if (observer) observer.disconnect();
target = el;
observer = new MutationObserver(() => {
try {
apply();
} catch (e) {
/* 修正失败不影响任何功能 */
}
});
observer.observe(el, { attributes: true, attributeFilter: ["style"] });
}
attach();
/* 兜底:万一前端重建了这条元素(换工作流 / 重挂载),重新绑一次。
* 每 2 秒一次 `document.contains` 的开销可以忽略。 */
setInterval(() => {
if (!target || !document.contains(target)) attach();
}, 2000);
/* 拖动起手时也主动补一次:首帧的 MutationObserver 可能还没轮到,
* 用 setTimeout(0) 排在当前微任务之后执行(不要用 rAF,理由见文件头)。 */
document.addEventListener(
"pointerdown",
() => {
setTimeout(() => {
try {
apply();
} catch (e) {
/* ignore */
}
}, 0);
},
true,
);
}
installSelectionRectFix();