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,