The frontend wraps canvas.onDrawForeground again whenever its DOM widgets mount, and the wrapper calls the highlighter's hook, which then called the wrapper. The hook now keeps every function it replaced and goes one level down per call, so each runs once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
131 lines
5.1 KiB
JavaScript
131 lines
5.1 KiB
JavaScript
var __defProp = Object.defineProperty;
|
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
class CanvasHighlighter {
|
|
constructor(settings) {
|
|
// Each onDrawForeground this hook replaced, oldest first. The frontend wraps the hook again whenever its
|
|
// DOM widgets mount (e.g. on a Nodes 2.0 toggle), so a later one calls back here, and each call goes a level down.
|
|
__publicField(this, "originals", []);
|
|
__publicField(this, "depth", 0);
|
|
__publicField(this, "highlightedNodeId", null);
|
|
__publicField(this, "highlightEl", null);
|
|
// Cache for DOM updates to minimize thrashing
|
|
__publicField(this, "lastStyleState", "");
|
|
// The panel's settings cache, which the setting's onChange keeps current
|
|
__publicField(this, "settings");
|
|
// Bound method to preserve 'this' and allow equality check
|
|
__publicField(this, "boundOnDrawForeground", (ctx, visible_nodes) => {
|
|
const original = this.originals[this.originals.length - 1 - this.depth];
|
|
if (original) {
|
|
this.depth++;
|
|
try {
|
|
original.call(window.app.canvas, ctx, visible_nodes);
|
|
} finally {
|
|
this.depth--;
|
|
}
|
|
}
|
|
if (this.depth > 0) return;
|
|
this.updateHighlightPosition();
|
|
});
|
|
this.settings = settings;
|
|
this.createHighlightElement();
|
|
}
|
|
createHighlightElement() {
|
|
this.highlightEl = document.createElement("div");
|
|
this.highlightEl.id = "magnify-glass-node-highlight";
|
|
this.highlightEl.style.display = "none";
|
|
document.body.appendChild(this.highlightEl);
|
|
}
|
|
/**
|
|
* Set the node ID to highlight.
|
|
*/
|
|
setHighlightedNode(nodeId) {
|
|
this.ensureHook();
|
|
if (this.highlightedNodeId === nodeId) return;
|
|
this.highlightedNodeId = nodeId;
|
|
const app = window.app;
|
|
if (app && app.canvas) {
|
|
app.canvas.setDirty(true, true);
|
|
}
|
|
if (nodeId === null && this.highlightEl) {
|
|
this.highlightEl.style.display = "none";
|
|
}
|
|
}
|
|
/**
|
|
* Ensure the canvas hook is active.
|
|
*/
|
|
ensureHook() {
|
|
const app = window.app;
|
|
if (!app || !app.canvas) return;
|
|
if (app.canvas.onDrawForeground !== this.boundOnDrawForeground) {
|
|
this.originals.push(app.canvas.onDrawForeground);
|
|
app.canvas.onDrawForeground = this.boundOnDrawForeground;
|
|
}
|
|
}
|
|
/**
|
|
* Update the position of the DOM highlight element.
|
|
*/
|
|
updateHighlightPosition() {
|
|
var _a;
|
|
if (!this.highlightEl) return;
|
|
const app = window.app;
|
|
if (!((_a = app == null ? void 0 : app.canvas) == null ? void 0 : _a.graph)) return;
|
|
if (!this.settings["🔍MagnifyGlass.NodeHighlightEnabled"] || this.highlightedNodeId === null) {
|
|
if (this.highlightEl.style.display !== "none") {
|
|
this.highlightEl.style.display = "none";
|
|
}
|
|
return;
|
|
}
|
|
const node = app.canvas.graph.getNodeById(this.highlightedNodeId);
|
|
if (!node) {
|
|
if (this.highlightEl.style.display !== "none") {
|
|
this.highlightEl.style.display = "none";
|
|
}
|
|
return;
|
|
}
|
|
const canvasEl = app.canvas.canvas;
|
|
if (!canvasEl) return;
|
|
const canvasRect = canvasEl.getBoundingClientRect();
|
|
const ds = app.canvas.ds;
|
|
const scale = ds.scale;
|
|
const offset = ds.offset;
|
|
const padding = 6;
|
|
const LiteGraph = window.LiteGraph;
|
|
const titleHeight = (LiteGraph == null ? void 0 : LiteGraph.NODE_TITLE_HEIGHT) ?? 30;
|
|
const [graphX, graphY, graphW, graphH] = (LiteGraph == null ? void 0 : LiteGraph.vueNodesMode) ? [node.pos[0], node.pos[1] - titleHeight, node.size[0], node.size[1] + titleHeight] : node.getBounding();
|
|
const pageX = canvasRect.left + (graphX + offset[0]) * scale - padding;
|
|
const pageY = canvasRect.top + (graphY + offset[1]) * scale - padding;
|
|
const finalW = graphW * scale + padding * 2;
|
|
const finalH = graphH * scale + padding * 2;
|
|
const clip = `inset(${canvasRect.top - pageY}px ${pageX + finalW - canvasRect.right}px ${pageY + finalH - canvasRect.bottom}px ${canvasRect.left - pageX}px)`;
|
|
const styleState = `${pageX.toFixed(1)},${pageY.toFixed(1)},${finalW.toFixed(1)},${finalH.toFixed(1)},${clip}`;
|
|
if (this.lastStyleState !== styleState || this.highlightEl.style.display === "none") {
|
|
this.highlightEl.style.display = "block";
|
|
this.highlightEl.style.left = `${pageX}px`;
|
|
this.highlightEl.style.top = `${pageY}px`;
|
|
this.highlightEl.style.width = `${finalW}px`;
|
|
this.highlightEl.style.height = `${finalH}px`;
|
|
this.highlightEl.style.clipPath = clip;
|
|
const borderWidth = Math.max(2, Math.min(6, 3 * scale));
|
|
this.highlightEl.style.borderWidth = `${borderWidth}px`;
|
|
this.lastStyleState = styleState;
|
|
}
|
|
}
|
|
/**
|
|
* Clean up hooks.
|
|
*/
|
|
cleanup() {
|
|
const app = window.app;
|
|
if (app && app.canvas && app.canvas.onDrawForeground === this.boundOnDrawForeground) {
|
|
app.canvas.onDrawForeground = this.originals.pop();
|
|
}
|
|
if (this.highlightEl) {
|
|
this.highlightEl.remove();
|
|
this.highlightEl = null;
|
|
}
|
|
}
|
|
}
|
|
export {
|
|
CanvasHighlighter
|
|
};
|