mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-11 02:20:08 +00:00
Support widget specific contextmenu options in vue (#8431)
<img width="614" height="485" alt="image" src="https://github.com/user-attachments/assets/2a635dec-8bed-4fab-9881-5e6057d482e1" /> 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)
This commit is contained in:
@@ -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<string>()
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<!-- Widget Input Slot Dot -->
|
||||
<div
|
||||
|
||||
@@ -138,6 +138,7 @@ function updateValueBy(delta: number) {
|
||||
const dragValue = ref<number>()
|
||||
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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user