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; }; } } });