/* * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Menu Controller * * Handles context menu building for canvas and node right-click menus. * Uses slotMarkingCore for state queries and actions. * Follows the dependency rule: controllers → cores → stores → utils */ import { COMFYUI_INPUT_TYPE_MAP, COMFYUI_OUTPUT_TYPE_MAP, EVENTS } from "../utils/constants.js"; import { getPrimitiveTypeName } from "../utils/types.js"; import { isInputSlotMarked, isNodeOutputMarked, toggleInputSlotMark, toggleNodeOutputMark, } from "../cores/slotMarkingCore.js"; /** * Build menu items for input slots * @param {Object} node - LiteGraph node * @param {Object} app - ComfyUI app instance * @returns {Object[]} Array of menu item objects */ function buildInputSlotMenuItems(node, app) { const selectedNodes = Object.values(app.canvas.selected_nodes || {}); const multipleSelected = selectedNodes.length > 1; return node.inputs.map((slot, index) => { const isMarked = isInputSlotMarked(node, index); const primitiveType = getPrimitiveTypeName(slot.type); const isSupported = primitiveType !== null && COMFYUI_INPUT_TYPE_MAP[primitiveType]; let matchingCount = 0; if (multipleSelected) { matchingCount = selectedNodes.filter((n) => n.inputs?.some((s) => s.name === slot.name && s.type === slot.type) ).length; } const checkmark = isMarked ? "✔" : ""; const slotLabel = isSupported ? slot.name : `${slot.name} (not supported)`; const batchInfo = multipleSelected && matchingCount > 1 ? ` (${matchingCount} nodes)` : ""; const textColor = isSupported ? "inherit" : "#888"; const cursor = isSupported ? "inherit" : "not-allowed"; return { content: `${checkmark}${slotLabel}${batchInfo}`, callback: isSupported ? () => toggleInputSlotMark(node, index, app) : null, disabled: !isSupported, }; }); } /** * Build slot tagging submenu * @param {Object} node - LiteGraph node * @param {Object} app - ComfyUI app instance * @returns {Object} Submenu object with options */ function buildSlotTaggingMenu(node, app) { const submenu = { options: [] }; if (node.inputs && node.inputs.length > 0) { submenu.options.push({ content: `Inputs`, submenu: { options: buildInputSlotMenuItems(node, app) }, }); } const hasOutputSlots = node.outputs && node.outputs.length > 0; let isSupported = true; if (hasOutputSlots) { const firstOutput = node.outputs[0]; const primitiveType = getPrimitiveTypeName(firstOutput.type); isSupported = primitiveType !== null && COMFYUI_OUTPUT_TYPE_MAP[primitiveType]; } const isMarked = isNodeOutputMarked(node); const selectedNodes = Object.values(app.canvas.selected_nodes || {}); const multipleSelected = selectedNodes.length > 1; let outputNodeCount = 0; if (multipleSelected) { outputNodeCount = selectedNodes.filter((n) => { if (!n.outputs || n.outputs.length === 0) return true; const firstOut = n.outputs[0]; const primType = getPrimitiveTypeName(firstOut.type); return primType !== null && COMFYUI_OUTPUT_TYPE_MAP[primType]; }).length; } const checkmark = isMarked ? "✔ " : ""; const label = isSupported ? "Output" : "Output (not supported)"; const batchInfo = multipleSelected && outputNodeCount > 1 ? ` (${outputNodeCount} nodes)` : ""; const textColor = isSupported ? "inherit" : "#888"; const cursor = isSupported ? "inherit" : "not-allowed"; submenu.options.push({ content: `${checkmark}${label}${batchInfo}`, callback: isSupported ? () => toggleNodeOutputMark(node, app) : null, disabled: !isSupported, }); return submenu; } /** * Get canvas context menu items (background right-click) * @param {Object} app - ComfyUI app instance * @returns {Object[]} Array of menu item objects */ export function getCanvasMenuItems(app) { return [ null, // separator above { content: "Export Workflow for RTX Remix", className: "rtx-remix-menu-item", callback: () => { // Dispatch event - exportDialogController listens and handles export app.api.dispatchEvent(new CustomEvent(EVENTS.EXPORT_WORKFLOW_REQUESTED, { detail: { app } })); }, }, null, // separator below ]; } /** * Get node context menu items (node right-click) * @param {Object} node - LiteGraph node * @param {Object} app - ComfyUI app instance * @returns {Object[]} Array of menu item objects */ export function getNodeMenuItems(node, app) { return [ null, // separator above { content: "Tag for RTX Remix", className: "rtx-remix-menu-item", submenu: buildSlotTaggingMenu(node, app), }, null, // separator below ]; }