Files
ComfyUI_frontend/src/composables/graph/useNodeMenuOptions.ts
jaeone94 a3106c4d53 fix: open node info panel from context menu (#12205)
## Summary

Replaces #12164.

Right-clicking a Vue node, using the selection toolbox More Options
menu, or clicking the selection toolbox Node Info button now opens the
right-side Info tab only when the new-menu UI makes that panel
available. Legacy-menu contexts hide the no-op action even when the
legacy node library design is selected; node-library help remains
isolated to the node library itself. The existing
`selection_toolbox_node_info_opened` telemetry fires only after the
toolbox button successfully opens node info. No new context-menu
telemetry event is added in this PR.

## Changes

- **What**: Share the node-info availability/action path across the
context menu and selection toolbox, keep legacy-menu state out of the
right-side panel public store API, tighten node-info settings tests, and
add unit plus E2E regression coverage for new-menu and legacy-menu
modes.
- **Dependencies**: None

## Review Focus

Confirm the node context menu, selection toolbox direct Info button, and
selection toolbox More Options entry all respect right-side panel
availability, including legacy menu + legacy node library mode, while
node-library help behavior remains isolated to the node library.

## Validation

- Self-review: checked production path, unit mocks, and Playwright
coverage; only gap found was weak E2E coverage for the toolbox direct
Info path, now strengthened.
- `pnpm test:unit -- src/composables/graph/useSelectionState.test.ts
src/components/graph/SelectionToolbox.test.ts
src/components/graph/selectionToolbox/InfoButton.test.ts`
- `pnpm test:browser:local -- --project=chromium
browser_tests/tests/selectionToolboxActions.spec.ts
browser_tests/tests/selectionToolboxSubmenus.spec.ts
browser_tests/tests/vueNodes/interactions/node/contextMenu.spec.ts
--grep "info button opens the right-side info tab|info button is
hidden|hides Node Info|should open node info"`
- `pnpm typecheck:browser`
- `pnpm exec oxlint --type-aware
browser_tests/tests/selectionToolboxActions.spec.ts`
- `pnpm exec eslint --cache --no-warn-ignored
browser_tests/tests/selectionToolboxActions.spec.ts`
- `pnpm exec oxfmt --check
browser_tests/tests/selectionToolboxActions.spec.ts`
- `git diff --check`
- Commit hooks: lint-staged + `pnpm typecheck` + `pnpm
typecheck:browser`
- Push hook: `knip --cache` (existing tag hint only)

## Screenshots (if applicable)
Before 


https://github.com/user-attachments/assets/4b1f6ddb-a01c-4958-81ab-36167f434e59


https://github.com/user-attachments/assets/83433f0d-24f1-46b7-a81d-f0f065812496

After 


https://github.com/user-attachments/assets/30bd61e5-f8d4-48b7-97e0-26c93e3cb362


https://github.com/user-attachments/assets/afce9f51-a43d-434f-a006-6b357a61ac8f

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-05-14 02:26:11 +00:00

130 lines
3.2 KiB
TypeScript

import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import type { MenuOption } from './useMoreOptionsMenu'
import { useNodeCustomization } from './useNodeCustomization'
import { useSelectedNodeActions } from './useSelectedNodeActions'
import type { NodeSelectionState } from './useSelectionState'
/**
* Composable for node-related menu operations
*/
export function useNodeMenuOptions() {
const { t } = useI18n()
const { shapeOptions, applyShape, applyColor, colorOptions, isLightTheme } =
useNodeCustomization()
const {
adjustNodeSize,
toggleNodeCollapse,
toggleNodePin,
toggleNodeBypass,
runBranch
} = useSelectedNodeActions()
const shapeSubmenu = computed(() =>
shapeOptions.map((shape) => ({
label: shape.localizedName,
action: () => applyShape(shape)
}))
)
const colorSubmenu = computed(() => {
return colorOptions.map((colorOption) => ({
label: colorOption.localizedName,
color: isLightTheme.value
? colorOption.value.light
: colorOption.value.dark,
action: () =>
applyColor(colorOption.name === 'noColor' ? null : colorOption)
}))
})
const getAdjustSizeOption = (): MenuOption => ({
label: t('contextMenu.Adjust Size'),
icon: 'icon-[lucide--move-diagonal-2]',
action: adjustNodeSize
})
const getNodeVisualOptions = (
states: NodeSelectionState,
bump: () => void
): MenuOption[] => [
{
label: states.collapsed
? t('contextMenu.Expand Node')
: t('contextMenu.Minimize Node'),
icon: states.collapsed
? 'icon-[lucide--maximize-2]'
: 'icon-[lucide--minimize-2]',
action: () => {
toggleNodeCollapse()
bump()
}
},
{
label: t('contextMenu.Shape'),
icon: 'icon-[lucide--box]',
hasSubmenu: true,
submenu: shapeSubmenu.value,
action: () => {}
},
{
label: t('contextMenu.Color'),
icon: 'icon-[lucide--palette]',
hasSubmenu: true,
submenu: colorSubmenu.value,
isColorPicker: true,
action: () => {}
}
]
const getPinOption = (
states: NodeSelectionState,
bump: () => void
): MenuOption => ({
label: states.pinned ? t('contextMenu.Unpin') : t('contextMenu.Pin'),
icon: states.pinned ? 'icon-[lucide--pin-off]' : 'icon-[lucide--pin]',
action: () => {
toggleNodePin()
bump()
}
})
const getBypassOption = (
states: NodeSelectionState,
bump: () => void
): MenuOption => ({
label: states.bypassed
? t('contextMenu.Remove Bypass')
: t('contextMenu.Bypass'),
icon: 'icon-[lucide--redo-dot]',
shortcut: 'Ctrl+B',
action: () => {
toggleNodeBypass()
bump()
}
})
const getRunBranchOption = (): MenuOption => ({
label: t('contextMenu.Run Branch'),
icon: 'icon-[lucide--play]',
action: runBranch
})
const getNodeInfoOption = (openNodeInfo: () => boolean): MenuOption => ({
label: t('contextMenu.Node Info'),
icon: 'icon-[lucide--info]',
action: openNodeInfo
})
return {
getNodeInfoOption,
getAdjustSizeOption,
getNodeVisualOptions,
getPinOption,
getBypassOption,
getRunBranchOption,
colorSubmenu
}
}