Files

1046 lines
54 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { app } from "../../scripts/app.js";
const RS_BYPASS_INSTANCES = new Set();
function isNodeInGroup(node, group) {
if (!node || !group) return false;
const n = node.getBounding ? node.getBounding() : node._bounding;
const g = group.bounding || group._bounding;
if (!n || !g) return false;
return n[0] < g[0] + g[2] && n[0] + n[2] > g[0] &&
n[1] < g[1] + g[3] && n[1] + n[3] > g[1];
}
function setNodeBypass(targetNode, bypass) {
targetNode.mode = bypass ? 4 : 0;
targetNode.setDirtyCanvas(true, true);
}
app.registerExtension({
name: "RS.Bypass",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "RS_Bypass") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function() {
const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
const self = this;
RS_BYPASS_INSTANCES.add(self);
self.data = {
bypassedNodes: [],
bypassedGroups: [],
menuSearch: "",
expandedGroups: {},
menuOpen: false,
toggleAllOn: true
};
self.rowHeight = 28;
self.padding = 10;
self.clickZones = [];
self._lastBypassedCount = -1;
self._lastSyncTimestamp = 0;
self._rs_discoveryDone = false;
const stateWidget = self.widgets.find(w => w.name === "bypass_state");
const isNewNode = !stateWidget.value || stateWidget.value === "{}";
if (stateWidget) {
stateWidget.hidden = true;
stateWidget.tooltip = "";
stateWidget.type = "hidden";
if (stateWidget.element) {
stateWidget.element.style.display = "none";
stateWidget.element.style.pointerEvents = "none";
}
stateWidget.computeSize = () => [0, 0];
if (!isNewNode) {
try {
const savedData = JSON.parse(stateWidget.value || "{}");
if (savedData && typeof savedData === 'object') {
if (Array.isArray(savedData.nodes)) {
if (savedData.nodes.length && typeof savedData.nodes[0] === 'number') {
self.data.bypassedNodes = savedData.nodes.map(id => ({ id, enabled: true }));
} else {
self.data.bypassedNodes = savedData.nodes;
}
}
if (Array.isArray(savedData.groups)) {
if (savedData.groups.length && typeof savedData.groups[0] === 'number') {
self.data.bypassedGroups = savedData.groups.map(id => ({ id, enabled: true }));
} else {
self.data.bypassedGroups = savedData.groups;
}
}
if (savedData.toggleAllOn !== undefined) self.data.toggleAllOn = savedData.toggleAllOn;
}
} catch (e) {
console.error("[Rayko] Error loading saved data", e);
}
}
stateWidget.serializeValue = () => {
self._rs_syncData();
return JSON.stringify({
nodes: self.data.bypassedNodes,
groups: self.data.bypassedGroups,
toggleAllOn: self.data.toggleAllOn
});
};
}
self.setSize([240, 180]);
self.computeSize = function() {
const count = self.data.bypassedNodes.length + self.data.bypassedGroups.length;
const hasItems = count > 0;
const calculatedHeight = 43 + (hasItems ? 38 : 0) + (count * 28) + 8;
return [240, Math.max(80, calculatedHeight)];
};
self.drawLabel = function(ctx, text, x, y, w, h) {
ctx.fillStyle = "#aaa";
ctx.font = "11px sans-serif";
ctx.textAlign = "left";
ctx.fillText(text, x, y + h/2 + 4);
};
self.drawField = function(ctx, value, x, y, w, h, isActive = false) {
ctx.fillStyle = isActive ? "#333" : "#222";
ctx.fillRect(x, y, w, h);
ctx.strokeStyle = isActive ? "#ff9800" : "#444";
ctx.lineWidth = isActive ? 2 : 1;
ctx.strokeRect(x, y, w, h);
ctx.fillStyle = isActive ? "#ff9800" : "#fff";
ctx.font = "11px sans-serif";
ctx.textAlign = "left";
ctx.fillText(value, x + 8, y + h/2 + 4);
ctx.fillStyle = "#666";
ctx.beginPath();
ctx.moveTo(x + w - 15, y + h/2 - 3);
ctx.lineTo(x + w - 9, y + h/2 - 3);
ctx.lineTo(x + w - 12, y + h/2 + 3);
ctx.fill();
ctx.lineWidth = 1;
};
self.drawToggle = function(ctx, isOn, x, y, w, h) {
const toggleW = 28;
const toggleH = 15;
const toggleX = x;
const toggleY = y + (h - toggleH) / 2;
const radius = toggleH / 2;
const knobRadius = radius - 2;
const knobOffset = isOn
? toggleW - 2 - knobRadius
: 2 + knobRadius;
ctx.fillStyle = isOn ? "#4CAF50" : "#555";
ctx.beginPath();
ctx.roundRect(toggleX, toggleY, toggleW, toggleH, radius);
ctx.fill();
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.arc(toggleX + knobOffset, toggleY + radius, knobRadius, 0, Math.PI * 2);
ctx.fill();
return { x: toggleX, y: toggleY, w: toggleW, h: toggleH };
};
self.drawBypassedItem = function(ctx, title, x, y, w, h, index, state, isChild, isLastChild) {
const toggleOn = state !== "none";
const toggleZone = self.drawToggle(ctx, toggleOn, x, y, 36, h);
const pad = 8;
const removeW = 16;
const gap = 8;
const textX = x + 28 + pad;
const removeX = x + w - removeW + 4;
const textW = removeX - gap - textX;
const bgHeight = 20;
const bgRadius = 4;
const bgY = y + (h - bgHeight) / 2;
ctx.fillStyle = index % 2 === 0 ? "rgba(34,34,34,1)" : "rgba(34,34,34,0.5)";
ctx.beginPath();
ctx.roundRect(textX, bgY, textW, bgHeight, bgRadius);
ctx.fill();
let titleColor = "#bbb";
if (state === "all") titleColor = "#ff4444";
else if (state === "partial") titleColor = "#ff9800";
ctx.font = "11px sans-serif";
ctx.textAlign = "left";
let cursorX = textX + pad;
if (isChild) {
const prefix = isLastChild ? "└─ " : "├─ ";
ctx.fillStyle = "#eee";
ctx.fillText(prefix, cursorX, y + h/2 + 4);
cursorX += ctx.measureText(prefix).width + 2;
}
ctx.fillStyle = titleColor;
ctx.fillText(title, cursorX, y + h/2 + 4);
if (!isChild) {
ctx.fillStyle = "#ff6666";
ctx.textAlign = "center";
ctx.fillText("✕", removeX + removeW / 2, y + h/2 + 4);
}
return {
toggle: toggleZone,
remove: { x: removeX, y: y, w: removeW, h: h }
};
};
self._rs_syncToggleAllState = function() {
const allItems = [...self.data.bypassedGroups, ...self.data.bypassedNodes];
if (allItems.length === 0) return;
const allEnabled = allItems.every(item => item.enabled);
const allDisabled = allItems.every(item => !item.enabled);
if (allEnabled) {
self.data.toggleAllOn = true;
} else if (allDisabled) {
self.data.toggleAllOn = false;
}
};
self._rs_getGroupState = function(group, nodes) {
const groupNodes = nodes.filter(n => n.comfyClass !== "RS_Bypass" && isNodeInGroup(n, group));
if (groupNodes.length === 0) return "none";
const bypassed = groupNodes.filter(n => n.mode === 4).length;
if (bypassed === 0) return "none";
if (bypassed === groupNodes.length) return "all";
return "partial";
};
self._rs_broadcastRemove = function(itemType, itemId) {
RS_BYPASS_INSTANCES.forEach(instance => {
if (instance === self) return;
let touched = false;
if (itemType === "group") {
const before = instance.data.bypassedGroups.length;
instance.data.bypassedGroups = instance.data.bypassedGroups.filter(item => item.id !== itemId);
if (instance.data.bypassedGroups.length !== before) touched = true;
} else {
const before = instance.data.bypassedNodes.length;
instance.data.bypassedNodes = instance.data.bypassedNodes.filter(item => item.id !== itemId);
if (instance.data.bypassedNodes.length !== before) touched = true;
}
if (touched) {
if (instance.data.menuOpen) {
instance._rs_closeMenu();
}
instance._rs_syncToggleAllState();
instance._rs_syncData();
instance._rs_updateUI();
}
});
};
self._rs_syncFromExternal = function() {
if (!self._rs_discoveryDone) return;
const now = Date.now();
if (now - self._lastSyncTimestamp < 300) return;
self._lastSyncTimestamp = now;
const nodes = app.graph._nodes || [];
const groups = app.graph._groups || [];
let changed = false;
self.data.bypassedGroups.forEach(item => {
const group = groups.find(g => g.id === item.id);
if (!group) return;
const groupNodes = nodes.filter(n => n.comfyClass !== "RS_Bypass" && isNodeInGroup(n, group));
if (groupNodes.length === 0) return;
const allBypassed = groupNodes.every(n => n.mode === 4);
if (allBypassed && !item.enabled) {
item.enabled = true;
changed = true;
} else if (!allBypassed && item.enabled) {
item.enabled = false;
changed = true;
}
});
self.data.bypassedNodes.forEach(item => {
const node = nodes.find(n => n.id === item.id);
if (!node) return;
const coveredByGroup = self.data.bypassedGroups.some(gItem => {
if (!gItem.enabled) return false;
const group = groups.find(g => g.id === gItem.id);
if (!group || !isNodeInGroup(node, group)) return false;
const groupNodes = nodes.filter(nn => nn.comfyClass !== "RS_Bypass" && isNodeInGroup(nn, group));
return groupNodes.length > 0 && groupNodes.every(nn => nn.mode === 4);
});
if (coveredByGroup) return;
const isBypassed = node.mode === 4;
if (isBypassed !== item.enabled) {
item.enabled = isBypassed;
changed = true;
}
});
const beforeCovered = self.data.bypassedNodes.length;
self.data.bypassedNodes = self.data.bypassedNodes.filter(item => {
const node = nodes.find(n => n.id === item.id);
if (!node) return true;
const coveredByGroup = self.data.bypassedGroups.some(gItem => {
if (!gItem.enabled) return false;
const group = groups.find(g => g.id === gItem.id);
if (!group || !isNodeInGroup(node, group)) return false;
const groupNodes = nodes.filter(nn => nn.comfyClass !== "RS_Bypass" && isNodeInGroup(nn, group));
return groupNodes.length > 0 && groupNodes.every(nn => nn.mode === 4);
});
return !coveredByGroup;
});
if (self.data.bypassedNodes.length !== beforeCovered) changed = true;
groups.forEach(group => {
if (!group || (!group.bounding && !group._bounding)) return;
const groupNodes = nodes.filter(n => n.comfyClass !== "RS_Bypass" && isNodeInGroup(n, group));
if (groupNodes.length === 0) return;
const allBypassed = groupNodes.every(n => n.mode === 4);
if (allBypassed) {
if (!self.data.bypassedGroups.some(item => item.id === group.id)) {
self.data.bypassedGroups.push({ id: group.id, enabled: true });
changed = true;
}
}
});
nodes.forEach(n => {
if (n.comfyClass === "RS_Bypass") return;
const groupEntry = self.data.bypassedGroups.find(gItem => {
const group = groups.find(g => g.id === gItem.id);
return group && isNodeInGroup(n, group);
});
if (n.mode === 4) {
const managedByBypassedGroup = self.data.bypassedGroups.some(gItem => {
if (!gItem.enabled) return false;
const group = groups.find(g => g.id === gItem.id);
if (!group || !isNodeInGroup(n, group)) return false;
const groupNodes = nodes.filter(nn => nn.comfyClass !== "RS_Bypass" && isNodeInGroup(nn, group));
return groupNodes.length > 0 && groupNodes.every(nn => nn.mode === 4);
});
if (managedByBypassedGroup) return;
if (!self.data.bypassedNodes.some(item => item.id === n.id)) {
self.data.bypassedNodes.push({ id: n.id, enabled: true });
changed = true;
} else {
const entry = self.data.bypassedNodes.find(item => item.id === n.id);
if (entry && !entry.enabled) {
entry.enabled = true;
changed = true;
}
}
} else if (n.mode === 0 && groupEntry && !groupEntry.enabled) {
const group = groups.find(g => g.id === groupEntry.id);
if (!group) return;
const groupNodes = nodes.filter(nn => nn.comfyClass !== "RS_Bypass" && isNodeInGroup(nn, group));
const hasBypassedSibling = groupNodes.some(nn => nn.mode === 4);
if (hasBypassedSibling && !self.data.bypassedNodes.some(item => item.id === n.id)) {
self.data.bypassedNodes.push({ id: n.id, enabled: false });
changed = true;
}
}
});
const beforeGroups = self.data.bypassedGroups.length;
const beforeNodes = self.data.bypassedNodes.length;
self.data.bypassedNodes = self.data.bypassedNodes.filter(item =>
nodes.some(n => n.id === item.id)
);
self.data.bypassedGroups = self.data.bypassedGroups.filter(item =>
groups.some(g => g.id === item.id)
);
if (self.data.bypassedNodes.length !== beforeNodes ||
self.data.bypassedGroups.length !== beforeGroups) {
changed = true;
}
if (changed) {
self._rs_syncToggleAllState();
self._rs_syncData();
self._rs_updateUI();
self.graph?.setDirtyCanvas(true, true);
}
};
self.onDrawForeground = function(ctx, visibleRect) {
self._rs_syncFromExternal();
self._rs_syncToggleAllState();
self.clickZones = [];
const pad = self.padding;
const rowH = self.rowHeight;
let y = 5;
self.drawLabel(ctx, "BYPASS", pad, y, 50, rowH);
self.drawField(ctx, "SELECT ITEM", pad + 50, y, self.size[0] - pad*2 - 50, rowH, self.data.menuOpen);
self.clickZones.push({ type: "select", x: pad + 50, y: y, w: self.size[0] - pad*2 - 50, h: rowH });
y += rowH + 10;
const nodes = app.graph._nodes || [];
const groups = app.graph._groups || [];
const displayItems = [];
const nodesPlacedInGroups = new Set();
self.data.bypassedGroups.forEach(item => {
const group = groups.find(g => g.id === item.id);
if (!group) return;
const state = self._rs_getGroupState(group, nodes);
displayItems.push({
type: "group",
id: group.id,
title: "📁 " + (group.title || "Group"),
state: state
});
if (state === "partial") {
const childItems = [];
self.data.bypassedNodes.forEach(nItem => {
const targetNode = nodes.find(n => n.id === nItem.id);
if (!targetNode) return;
if (!isNodeInGroup(targetNode, group)) return;
if (nodesPlacedInGroups.has(targetNode.id)) return;
if (nItem.enabled) return;
childItems.push({ node: targetNode, entry: nItem });
});
childItems.forEach((child, ci) => {
displayItems.push({
type: "node",
id: child.node.id,
title: " ️ " + (child.node.title || child.node.type),
state: "none",
isChild: true,
isLastChild: ci === childItems.length - 1
});
nodesPlacedInGroups.add(child.node.id);
});
}
});
self.data.bypassedNodes.forEach(item => {
const targetNode = nodes.find(n => n.id === item.id);
if (!targetNode) return;
if (nodesPlacedInGroups.has(targetNode.id)) return;
const belongsToCachedGroup = self.data.bypassedGroups.some(gItem => {
const group = groups.find(g => g.id === gItem.id);
return group && isNodeInGroup(targetNode, group);
});
if (belongsToCachedGroup) return;
displayItems.push({
type: "node",
id: targetNode.id,
title: " ️ " + (targetNode.title || targetNode.type),
state: item.enabled ? "all" : "none"
});
});
if (displayItems.length > 0) {
self.drawLabel(ctx, "TOGGLE ALL", pad, y, 100, rowH);
const toggleZone = self.drawToggle(ctx, self.data.toggleAllOn, pad + 100, y, self.size[0] - pad*2 - 100, rowH);
self.clickZones.push({
type: "toggleAll",
x: toggleZone.x,
y: toggleZone.y,
w: toggleZone.w,
h: toggleZone.h
});
y += rowH + 10;
}
displayItems.forEach((item, index) => {
const itemY = y + (index * rowH);
const zones = self.drawBypassedItem(ctx, item.title, pad, itemY, self.size[0] - pad*2, rowH, index, item.state, item.isChild, item.isLastChild);
self.clickZones.push({
type: "itemToggle",
itemType: item.type,
id: item.id,
x: zones.toggle.x,
y: zones.toggle.y,
w: zones.toggle.w,
h: zones.toggle.h
});
if (!item.isChild) {
self.clickZones.push({
type: "remove",
itemType: item.type,
id: item.id,
x: zones.remove.x,
y: zones.remove.y,
w: zones.remove.w,
h: zones.remove.h
});
}
});
const currentCount = displayItems.length;
if (self._lastBypassedCount !== currentCount) {
self._lastBypassedCount = currentCount;
self._rs_updateUI();
}
};
self.showBypassMenu = function(clickEvent) {
if (self.data.menuOpen) {
self._rs_closeMenu();
return;
}
self._rs_syncFromExternal();
const existingMenu = document.getElementById("rs-bypass-menu");
if (existingMenu) existingMenu.remove();
const menu = document.createElement("div");
menu.id = "rs-bypass-menu";
menu.style.cssText = "position:fixed;background:#1a1a1a;border:2px solid #ff9800;border-radius:6px;max-height:500px;overflow-y:auto;z-index:10001;box-shadow:0 4px 20px rgba(255,152,0,0.3);min-width:350px;transition:box-shadow 0.2s;";
const searchInput = document.createElement("input");
searchInput.type = "text";
searchInput.placeholder = " Search nodes/groups...";
searchInput.style.cssText = "width:100%;padding:10px;background:#252525;color:#fff;border:none;border-bottom:1px solid #333;box-sizing:border-box;font-size:12px;outline:none;";
searchInput.value = self.data.menuSearch || "";
menu.appendChild(searchInput);
const contentDiv = document.createElement("div");
contentDiv.id = "rs-bypass-content";
menu.appendChild(contentDiv);
if (clickEvent) {
menu.style.left = (clickEvent.clientX + 8) + "px";
menu.style.top = clickEvent.clientY + "px";
}
const outsideHandler = (ev) => {
const path = typeof ev.composedPath === "function" ? ev.composedPath() : null;
if (path && path.length) {
if (path.includes(menu)) return;
} else {
if (menu.contains(ev.target)) return;
}
self._rs_closeMenu();
};
self._rs_outsideHandler = outsideHandler;
const escHandler = (ev) => {
if (ev.key === "Escape") self._rs_closeMenu();
};
self._rs_escHandler = escHandler;
function renderContent() {
contentDiv.innerHTML = "";
const query = searchInput.value.toLowerCase();
self.data.menuSearch = query;
const groups = app.graph._groups || [];
const nodes = app.graph._nodes || [];
const filteredNodes = nodes.filter(n => n.comfyClass !== "RS_Bypass");
let hasItems = false;
groups.forEach(group => {
if (!group || !group.bounding && !group._bounding) {
return;
}
const groupNodes = filteredNodes.filter(n => isNodeInGroup(n, group));
if (groupNodes.length === 0) return;
const title = group.title || "Group " + group.id;
if (query && !title.toLowerCase().includes(query)) return;
const isExpanded = self.data.expandedGroups[group.id] || false;
const bypassedCount = groupNodes.filter(n => n.mode === 4).length;
let groupColor = "#ddd";
if (bypassedCount === groupNodes.length) groupColor = "#ff4444";
else if (bypassedCount > 0) groupColor = "#ff9800";
const groupItem = document.createElement("div");
groupItem.style.cssText = "padding:10px 12px;cursor:pointer;color:" + groupColor + ";border-bottom:1px solid #333;font-size:12px;display:flex;align-items:center;transition:background-color 0.15s;";
const arrow = document.createElement("span");
arrow.textContent = isExpanded ? "▼ " : "▶ ";
arrow.style.cssText = "margin-right:8px;font-size:10px;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border:1px solid #555;border-radius:4px;";
arrow.onclick = (ev) => {
ev.stopPropagation();
self.data.expandedGroups[group.id] = !isExpanded;
renderContent();
};
groupItem.appendChild(arrow);
const groupText = document.createElement("span");
groupText.textContent = " " + title;
groupText.style.cssText = "flex:1;margin-left:4px;";
groupText.onclick = (ev) => {
ev.stopPropagation();
let entry = self.data.bypassedGroups.find(item => item.id === group.id);
if (entry) {
entry.enabled = !entry.enabled;
} else {
entry = { id: group.id, enabled: true };
self.data.bypassedGroups.push(entry);
}
groupNodes.forEach(n => setNodeBypass(n, entry.enabled));
if (entry.enabled) {
self.data.bypassedNodes = self.data.bypassedNodes.filter(
nItem => !groupNodes.some(n => n.id === nItem.id)
);
}
self._rs_syncToggleAllState();
self._rs_syncData();
self._rs_updateUI();
renderContent();
};
groupItem.appendChild(groupText);
groupItem.onmouseover = () => groupItem.style.background = "#333";
groupItem.onmouseout = () => groupItem.style.background = "#1a1a1a";
contentDiv.appendChild(groupItem);
hasItems = true;
if (isExpanded) {
groupNodes.forEach(n => {
const nodeTitle = n.title || n.type;
if (query && !nodeTitle.toLowerCase().includes(query)) return;
const isNodeBypassed = n.mode === 4;
const nodeColor = isNodeBypassed ? "#ff4444" : "#ddd";
const nodeItem = document.createElement("div");
nodeItem.textContent = " ️ " + nodeTitle;
nodeItem.style.cssText = "padding:10px 12px 10px 32px;cursor:pointer;color:" + nodeColor + ";border-bottom:1px solid #333;font-size:12px;transition:background-color 0.15s;";
nodeItem.onmouseover = () => nodeItem.style.background = "#333";
nodeItem.onmouseout = () => nodeItem.style.background = "#1a1a1a";
nodeItem.onclick = (ev) => {
ev.stopPropagation();
let entry = self.data.bypassedNodes.find(item => item.id === n.id);
const currentlyBypassed = entry ? entry.enabled : (n.mode === 4);
const newState = !currentlyBypassed;
if (entry) {
entry.enabled = newState;
} else {
entry = { id: n.id, enabled: newState };
self.data.bypassedNodes.push(entry);
}
setNodeBypass(n, newState);
self._rs_syncToggleAllState();
self._rs_syncData();
self._rs_updateUI();
renderContent();
};
contentDiv.appendChild(nodeItem);
});
}
});
const ungroupedNodes = filteredNodes.filter(n => !groups.some(g => g && (g.bounding || g._bounding) && isNodeInGroup(n, g)));
ungroupedNodes.forEach(n => {
const title = n.title || n.type;
if (query && !title.toLowerCase().includes(query)) return;
const isBypassed = n.mode === 4;
const nodeColor = isBypassed ? "#ff4444" : "#ddd";
const item = document.createElement("div");
item.textContent = "⚙️ " + title;
item.style.cssText = "padding:10px 12px;cursor:pointer;color:" + nodeColor + ";border-bottom:1px solid #333;font-size:12px;transition:background-color 0.15s;";
item.onmouseover = () => item.style.background = "#333";
item.onmouseout = () => item.style.background = "#1a1a1a";
item.onclick = (ev) => {
ev.stopPropagation();
let entry = self.data.bypassedNodes.find(item => item.id === n.id);
if (entry) {
entry.enabled = !entry.enabled;
} else {
entry = { id: n.id, enabled: true };
self.data.bypassedNodes.push(entry);
}
setNodeBypass(n, entry.enabled);
self._rs_syncToggleAllState();
self._rs_syncData();
self._rs_updateUI();
renderContent();
};
contentDiv.appendChild(item);
hasItems = true;
});
if (!hasItems) {
const emptyMsg = document.createElement("div");
emptyMsg.textContent = "No nodes/groups found";
emptyMsg.style.cssText = "padding:15px;text-align:center;color:#666;font-size:12px;";
contentDiv.appendChild(emptyMsg);
}
}
searchInput.addEventListener("input", renderContent);
renderContent();
document.body.appendChild(menu);
self.data.menuOpen = true;
self._rs_updateUI();
setTimeout(() => {
document.addEventListener("pointerdown", outsideHandler, true);
document.addEventListener("keydown", escHandler);
searchInput.focus();
}, 10);
};
self._rs_closeMenu = function() {
const menu = document.getElementById("rs-bypass-menu");
if (menu) {
menu.remove();
}
if (self._rs_escHandler) {
document.removeEventListener("keydown", self._rs_escHandler);
self._rs_escHandler = null;
}
if (self._rs_outsideHandler) {
document.removeEventListener("pointerdown", self._rs_outsideHandler, true);
self._rs_outsideHandler = null;
}
self.data.menuOpen = false;
self._rs_updateUI();
};
self.onMouseDown = function(e, pos, canvas) {
if (!self.clickZones.length) return false;
for (const zone of self.clickZones) {
const inX = pos[0] >= zone.x && pos[0] <= zone.x + zone.w;
const inY = pos[1] >= zone.y && pos[1] <= zone.y + zone.h;
if (inX && inY) {
if (zone.type === "select") {
self.showBypassMenu(e);
return true;
}
if (zone.type === "toggleAll") {
self.data.toggleAllOn = !self.data.toggleAllOn;
const newState = self.data.toggleAllOn;
self.data.bypassedGroups.forEach(item => item.enabled = newState);
self.data.bypassedNodes.forEach(item => item.enabled = newState);
const nodes = app.graph._nodes || [];
const groups = app.graph._groups || [];
self.data.bypassedGroups.forEach(item => {
const group = groups.find(g => g.id === item.id);
if (group) {
const groupNodes = nodes.filter(n => isNodeInGroup(n, group));
groupNodes.forEach(n => setNodeBypass(n, newState));
}
});
self.data.bypassedNodes.forEach(item => {
const targetNode = nodes.find(n => n.id === item.id);
if (targetNode) {
const inGroup = self.data.bypassedGroups.some(gItem => {
if (!gItem.enabled) return false;
const group = groups.find(g => g.id === gItem.id);
return group && isNodeInGroup(targetNode, group);
});
if (!inGroup) setNodeBypass(targetNode, newState);
}
});
self._rs_syncData();
self._rs_updateUI();
return true;
}
if (zone.type === "itemToggle") {
if (zone.itemType === "group") {
const group = app.graph._groups.find(g => g.id === zone.id);
if (group) {
const groupNodes = app.graph._nodes.filter(n => isNodeInGroup(n, group));
let entry = self.data.bypassedGroups.find(item => item.id === zone.id);
if (!entry) {
entry = { id: zone.id, enabled: false };
self.data.bypassedGroups.push(entry);
}
entry.enabled = !entry.enabled;
groupNodes.forEach(n => setNodeBypass(n, entry.enabled));
}
} else {
const targetNode = app.graph._nodes.find(n => n.id === zone.id);
if (targetNode) {
let entry = self.data.bypassedNodes.find(item => item.id === zone.id);
if (!entry) {
entry = { id: zone.id, enabled: false };
self.data.bypassedNodes.push(entry);
}
entry.enabled = !entry.enabled;
setNodeBypass(targetNode, entry.enabled);
}
}
self._rs_syncToggleAllState();
self._rs_syncData();
self._rs_updateUI();
return true;
}
if (zone.type === "remove") {
if (zone.itemType === "group") {
const group = app.graph._groups.find(g => g.id === zone.id);
if (group) {
const groupNodes = app.graph._nodes.filter(n => isNodeInGroup(n, group));
groupNodes.forEach(n => setNodeBypass(n, false));
}
self.data.bypassedGroups = self.data.bypassedGroups.filter(item => item.id !== zone.id);
self._rs_broadcastRemove("group", zone.id);
} else {
const targetNode = app.graph._nodes.find(n => n.id === zone.id);
if (targetNode) {
setNodeBypass(targetNode, false);
}
self.data.bypassedNodes = self.data.bypassedNodes.filter(item => item.id !== zone.id);
self._rs_broadcastRemove("node", zone.id);
}
self._rs_syncToggleAllState();
self._rs_syncData();
self._rs_updateUI();
return true;
}
}
}
return false;
};
self._rs_updateUI = function() {
const pad = self.padding;
const rowH = self.rowHeight;
const bypassedItemsCount = self._lastBypassedCount >= 0 ? self._lastBypassedCount : (self.data.bypassedGroups.length + self.data.bypassedNodes.length);
const hasItems = bypassedItemsCount > 0;
let y = 5;
y += rowH;
y += 10;
if (hasItems) {
y += rowH;
y += 10;
}
y += (bypassedItemsCount * rowH);
y += 8;
const minHeight = 80;
const minWidth = 220;
const newHeight = Math.max(minHeight, y);
const newWidth = Math.max(minWidth, self.size[0]);
if (self.size[1] !== newHeight || self.size[0] !== newWidth) {
self.setSize([newWidth, newHeight]);
}
self.graph?.setDirtyCanvas(true, true);
};
self._rs_applyBypass = function() {
const nodes = app.graph._nodes || [];
const groups = app.graph._groups || [];
self.data.bypassedGroups.forEach(item => {
if (!item.enabled) return;
const group = groups.find(g => g.id === item.id);
if (group) {
const groupNodes = nodes.filter(n => isNodeInGroup(n, group));
groupNodes.forEach(n => setNodeBypass(n, true));
}
});
self.data.bypassedNodes.forEach(item => {
if (!item.enabled) return;
const targetNode = nodes.find(n => n.id === item.id);
if (targetNode) {
const inBypassedGroup = self.data.bypassedGroups.some(gItem => {
if (!gItem.enabled) return false;
const group = groups.find(g => g.id === gItem.id);
return group && isNodeInGroup(targetNode, group);
});
if (!inBypassedGroup) {
setNodeBypass(targetNode, true);
}
}
});
};
self._rs_discoverExistingBypasses = function() {
const nodes = app.graph._nodes || [];
const groups = app.graph._groups || [];
RS_BYPASS_INSTANCES.forEach(instance => {
if (instance === self) return;
instance.data.bypassedGroups.forEach(item => {
if (!self.data.bypassedGroups.some(x => x.id === item.id)) {
self.data.bypassedGroups.push({ id: item.id, enabled: item.enabled });
}
});
instance.data.bypassedNodes.forEach(item => {
if (!self.data.bypassedNodes.some(x => x.id === item.id)) {
self.data.bypassedNodes.push({ id: item.id, enabled: item.enabled });
}
});
});
groups.forEach(group => {
if (!group || (!group.bounding && !group._bounding)) return;
const groupNodes = nodes.filter(n => n.comfyClass !== "RS_Bypass" && isNodeInGroup(n, group));
if (groupNodes.length > 0 && groupNodes.every(n => n.mode === 4)) {
if (!self.data.bypassedGroups.some(item => item.id === group.id)) {
self.data.bypassedGroups.push({ id: group.id, enabled: true });
} else {
const entry = self.data.bypassedGroups.find(item => item.id === group.id);
if (entry) entry.enabled = true;
}
}
});
nodes.forEach(n => {
if (n.comfyClass === "RS_Bypass") return;
if (n.mode !== 4) return;
const managedByBypassedGroup = self.data.bypassedGroups.some(gItem => {
if (!gItem.enabled) return false;
const group = groups.find(g => g.id === gItem.id);
if (!group || !isNodeInGroup(n, group)) return false;
const groupNodes = nodes.filter(nn => nn.comfyClass !== "RS_Bypass" && isNodeInGroup(nn, group));
return groupNodes.length > 0 && groupNodes.every(nn => nn.mode === 4);
});
if (managedByBypassedGroup) return;
if (!self.data.bypassedNodes.some(item => item.id === n.id)) {
self.data.bypassedNodes.push({ id: n.id, enabled: true });
} else {
const entry = self.data.bypassedNodes.find(item => item.id === n.id);
if (entry) entry.enabled = true;
}
});
};
self._rs_syncData = function() {
if (stateWidget) {
stateWidget.value = JSON.stringify({
nodes: self.data.bypassedNodes,
groups: self.data.bypassedGroups,
toggleAllOn: self.data.toggleAllOn
});
}
if (self.graph) {
self.graph.changeTracker?.dispatchEvent(new Event("change"));
}
};
const onSerialize = self.onSerialize;
self.onSerialize = function(o) {
self._rs_syncData();
if (onSerialize) {
return onSerialize.apply(this, arguments);
}
};
const onRemoved = self.onRemoved;
self.onRemoved = function() {
RS_BYPASS_INSTANCES.delete(self);
if (onRemoved) return onRemoved.apply(this, arguments);
};
const onConfigure = self.onConfigure;
self.onConfigure = function(o) {
if (onConfigure) {
onConfigure.apply(this, arguments);
}
if (stateWidget && stateWidget.value) {
try {
const restoredData = JSON.parse(stateWidget.value);
if (restoredData) {
if (Array.isArray(restoredData.nodes)) {
if (restoredData.nodes.length && typeof restoredData.nodes[0] === 'number') {
self.data.bypassedNodes = restoredData.nodes.map(id => ({ id, enabled: true }));
} else {
self.data.bypassedNodes = restoredData.nodes;
}
}
if (Array.isArray(restoredData.groups)) {
if (restoredData.groups.length && typeof restoredData.groups[0] === 'number') {
self.data.bypassedGroups = restoredData.groups.map(id => ({ id, enabled: true }));
} else {
self.data.bypassedGroups = restoredData.groups;
}
}
if (restoredData.toggleAllOn !== undefined) self.data.toggleAllOn = restoredData.toggleAllOn;
}
} catch (e) {
console.error("[Rayko] Error restoring data:", e);
}
}
self._rs_syncToggleAllState();
self._rs_applyBypass();
self._rs_updateUI();
setTimeout(() => {
self.graph?.setDirtyCanvas(true, true);
app.graph.setDirtyCanvas(true, true);
}, 10);
};
setTimeout(() => {
if (isNewNode) {
self._rs_discoverExistingBypasses();
self._rs_syncData();
} else {
self._rs_applyBypass();
}
self._rs_discoveryDone = true;
self._rs_syncToggleAllState();
self._rs_updateUI();
}, 100);
return result;
};
}
}
});