From af8433fb3d6649e7d3a510e775aa20774ec54a44 Mon Sep 17 00:00:00 2001 From: AustinMroz Date: Thu, 29 Jan 2026 11:38:41 -0800 Subject: [PATCH] Support widget specific contextmenu options in vue (#8431) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit image These options were defined in `litegraphService`. While the existing code for defining options is reused (to ensure there's no implementation drift) these extra widget options use the litegraph format for context menu options and do not belong in `useSelectionMenuOptions`. They have been moved out of `useLitegraphService` (good), but left in `litegraphService` (not great) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8431-Support-widget-specific-contextmenu-options-in-vue-2f76d73d3650814fb20fca352dc81e3b) by [Unito](https://www.unito.io) --- src/composables/graph/useMoreOptionsMenu.ts | 30 ++++++- src/core/graph/subgraph/proxyWidgetUtils.ts | 2 + .../vueNodes/components/NodeWidgets.vue | 1 + .../components/WidgetInputNumberInput.vue | 1 + src/services/litegraphService.ts | 88 ++++++++++--------- 5 files changed, 77 insertions(+), 45 deletions(-) diff --git a/src/composables/graph/useMoreOptionsMenu.ts b/src/composables/graph/useMoreOptionsMenu.ts index 96c677cd5..fb1055e13 100644 --- a/src/composables/graph/useMoreOptionsMenu.ts +++ b/src/composables/graph/useMoreOptionsMenu.ts @@ -1,8 +1,9 @@ import { computed, ref } from 'vue' import type { Ref } from 'vue' -import type { LGraphGroup } from '@/lib/litegraph/src/litegraph' +import type { LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' +import { getExtraOptionsForWidget } from '@/services/litegraphService' import { isLGraphGroup } from '@/utils/litegraphUtil' import { @@ -45,6 +46,8 @@ export enum BadgeVariant { // Global singleton for NodeOptions component reference let nodeOptionsInstance: null | NodeOptionsInstance = null +const hoveredWidgetName = ref() + /** * Toggle the node options popover * @param event - The trigger event @@ -61,6 +64,13 @@ export function toggleNodeOptions(event: Event) { * @param event - The trigger event (must be MouseEvent for position) */ export function showNodeOptions(event: MouseEvent) { + hoveredWidgetName.value = undefined + const target = event.target + if (target instanceof HTMLElement) { + const widgetEl = target.closest('.lg-node-widget') + if (widgetEl instanceof HTMLElement) + hoveredWidgetName.value = widgetEl.dataset.widgetName + } if (nodeOptionsInstance?.show) { nodeOptionsInstance.show(event) } @@ -133,8 +143,8 @@ export function useMoreOptionsMenu() { } = useGroupMenuOptions() const { getBasicSelectionOptions, - getSubgraphOptions, - getMultipleNodesOptions + getMultipleNodesOptions, + getSubgraphOptions } = useSelectionMenuOptions() const hasSubgraphs = hasSubgraphsComputed @@ -164,13 +174,13 @@ export function useMoreOptionsMenu() { // For single node selection, also get LiteGraph menu items to merge const litegraphOptions: MenuOption[] = [] + const node: LGraphNode | undefined = selectedNodes.value[0] if ( selectedNodes.value.length === 1 && !groupContext && canvasStore.canvas ) { try { - const node = selectedNodes.value[0] const rawItems = canvasStore.canvas.getNodeMenuOptions(node) // Don't apply structuring yet - we'll do it after merging with Vue options litegraphOptions.push( @@ -249,6 +259,18 @@ export function useMoreOptionsMenu() { options.push(...getImageMenuOptions(selectedNodes.value[0])) options.push({ type: 'divider' }) } + const rawName = hoveredWidgetName.value + const widget = node?.widgets?.find((w) => w.name === rawName) + if (widget) { + const widgetOptions = convertContextMenuToOptions( + getExtraOptionsForWidget(node, widget) + ) + if (widgetOptions) { + options.push(...widgetOptions) + options.push({ type: 'divider' }) + } + } + // Section 6 & 7: Extensions and Delete are handled by buildStructuredMenu // Mark all Vue options with source diff --git a/src/core/graph/subgraph/proxyWidgetUtils.ts b/src/core/graph/subgraph/proxyWidgetUtils.ts index eafb0f1dd..2cd8685a6 100644 --- a/src/core/graph/subgraph/proxyWidgetUtils.ts +++ b/src/core/graph/subgraph/proxyWidgetUtils.ts @@ -105,6 +105,7 @@ export function addWidgetPromotionOptions( content: `Promote Widget: ${widget.label ?? widget.name}`, callback: () => { promoteWidget(node, widget, promotableParents) + widget.callback?.(widget.value) } }) else { @@ -112,6 +113,7 @@ export function addWidgetPromotionOptions( content: `Un-Promote Widget: ${widget.label ?? widget.name}`, callback: () => { demoteWidget(node, widget, parents) + widget.callback?.(widget.value) } }) } diff --git a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue index c8d237cf6..37da74490 100644 --- a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue +++ b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue @@ -30,6 +30,7 @@ (!widget.simplified.options?.advanced || showAdvanced) " class="lg-node-widget group col-span-full grid grid-cols-subgrid items-stretch" + :data-widget-name="widget.name" >
() const dragDelta = ref(0) function handleMouseDown(e: PointerEvent) { + if (e.button > 0) return if (props.widget.options?.disabled) return const { target } = e if (!(target instanceof HTMLElement)) return diff --git a/src/services/litegraphService.ts b/src/services/litegraphService.ts index 268d7a0a7..42ecd1b43 100644 --- a/src/services/litegraphService.ts +++ b/src/services/litegraphService.ts @@ -29,6 +29,7 @@ import type { ISerialisableNodeOutput, ISerialisedNode } from '@/lib/litegraph/src/types/serialisation' +import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' import { useSettingStore } from '@/platform/settings/settingStore' import { useToastStore } from '@/platform/updates/common/toastStore' import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' @@ -71,6 +72,49 @@ export interface HasInitialMinSize { export const CONFIG = Symbol() export const GET_CONFIG = Symbol() +export function getExtraOptionsForWidget( + node: LGraphNode, + widget: IBaseWidget +) { + const options: IContextMenuValue[] = [] + const input = node.inputs.find((inp) => inp.widget?.name === widget.name) + + if (input) { + options.unshift({ + content: `${t('contextMenu.RenameWidget')}: ${widget.label ?? widget.name}`, + callback: async () => { + const newLabel = await useDialogService().prompt({ + title: t('g.rename'), + message: t('g.enterNewName') + ':', + defaultValue: widget.label, + placeholder: widget.name + }) + if (newLabel === null) return + widget.label = newLabel || undefined + input.label = newLabel || undefined + widget.callback?.(widget.value) + useCanvasStore().canvas?.setDirty(true) + } + }) + } + + const favoritedWidgetsStore = useFavoritedWidgetsStore() + const isFavorited = favoritedWidgetsStore.isFavorited(node, widget.name) + options.unshift({ + content: isFavorited + ? `${t('contextMenu.UnfavoriteWidget')}: ${widget.label ?? widget.name}` + : `${t('contextMenu.FavoriteWidget')}: ${widget.label ?? widget.name}`, + callback: () => { + favoritedWidgetsStore.toggleFavorite(node, widget.name) + } + }) + + if (node.graph && !node.graph.isRootGraph) { + addWidgetPromotionOptions(options, widget, node) + } + return options +} + /** * Service that augments litegraph with ComfyUI specific functionality. */ @@ -678,47 +722,8 @@ export const useLitegraphService = () => { } const [x, y] = canvas.graph_mouse const overWidget = this.getWidgetOnPos(x, y, true) - if (overWidget) { - const input = this.inputs.find( - (inp) => inp.widget?.name === overWidget.name - ) - - if (input) { - options.unshift({ - content: `${t('contextMenu.RenameWidget')}: ${overWidget.label ?? overWidget.name}`, - callback: async () => { - const newLabel = await useDialogService().prompt({ - title: t('g.rename'), - message: t('g.enterNewName') + ':', - defaultValue: overWidget.label, - placeholder: overWidget.name - }) - if (newLabel === null) return - overWidget.label = newLabel || undefined - input.label = newLabel || undefined - useCanvasStore().canvas?.setDirty(true) - } - }) - } - - const favoritedWidgetsStore = useFavoritedWidgetsStore() - const isFavorited = favoritedWidgetsStore.isFavorited( - this, - overWidget.name - ) - options.unshift({ - content: isFavorited - ? `${t('contextMenu.UnfavoriteWidget')}: ${overWidget.label ?? overWidget.name}` - : `${t('contextMenu.FavoriteWidget')}: ${overWidget.label ?? overWidget.name}`, - callback: () => { - favoritedWidgetsStore.toggleFavorite(this, overWidget.name) - } - }) - - if (this.graph && !this.graph.isRootGraph) { - addWidgetPromotionOptions(options, overWidget, this) - } - } + if (overWidget) + options.unshift(...getExtraOptionsForWidget(this, overWidget)) return [] } } @@ -934,6 +939,7 @@ export const useLitegraphService = () => { addNodeOnGraph, addNodeInput, getCanvasCenter, + getExtraOptionsForWidget, goToNode, resetView, fitView,