mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-23 14:16:00 +00:00
## 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>
130 lines
4.0 KiB
TypeScript
130 lines
4.0 KiB
TypeScript
import { storeToRefs } from 'pinia'
|
|
import { computed } from 'vue'
|
|
|
|
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
import { LGraphEventMode, SubgraphNode } from '@/lib/litegraph/src/litegraph'
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
|
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
|
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
|
import { isImageNode, isLGraphNode, isLoad3dNode } from '@/utils/litegraphUtil'
|
|
import { filterOutputNodes } from '@/utils/nodeFilterUtil'
|
|
|
|
export interface NodeSelectionState {
|
|
collapsed: boolean
|
|
pinned: boolean
|
|
bypassed: boolean
|
|
}
|
|
|
|
/**
|
|
* Centralized computed selection state + shared helper actions to avoid duplication
|
|
* between selection toolbox, context menus, and other UI affordances.
|
|
*/
|
|
export function useSelectionState() {
|
|
const canvasStore = useCanvasStore()
|
|
const nodeDefStore = useNodeDefStore()
|
|
const settingStore = useSettingStore()
|
|
const rightSidePanelStore = useRightSidePanelStore()
|
|
|
|
const { selectedItems } = storeToRefs(canvasStore)
|
|
|
|
const selectedNodes = computed(() => {
|
|
return selectedItems.value.filter((i: unknown) =>
|
|
isLGraphNode(i)
|
|
) as LGraphNode[]
|
|
})
|
|
|
|
const nodeDef = computed(() => {
|
|
if (selectedNodes.value.length !== 1) return null
|
|
return nodeDefStore.fromLGraphNode(selectedNodes.value[0])
|
|
})
|
|
|
|
const hasAnySelection = computed(() => selectedItems.value.length > 0)
|
|
const hasSingleSelection = computed(() => selectedItems.value.length === 1)
|
|
const hasMultipleSelection = computed(() => selectedItems.value.length > 1)
|
|
|
|
const isSingleNode = computed(
|
|
() => hasSingleSelection.value && isLGraphNode(selectedItems.value[0])
|
|
)
|
|
const isSingleSubgraph = computed(
|
|
() =>
|
|
isSingleNode.value &&
|
|
(selectedItems.value[0] as LGraphNode)?.isSubgraphNode?.()
|
|
)
|
|
const isSingleImageNode = computed(
|
|
() =>
|
|
isSingleNode.value && isImageNode(selectedItems.value[0] as LGraphNode)
|
|
)
|
|
|
|
const hasSubgraphs = computed(() =>
|
|
selectedItems.value.some((i: unknown) => i instanceof SubgraphNode)
|
|
)
|
|
|
|
const hasAny3DNodeSelected = computed(() => {
|
|
const enable3DViewer = settingStore.get('Comfy.Load3D.3DViewerEnable')
|
|
return (
|
|
selectedNodes.value.length === 1 &&
|
|
selectedNodes.value.some(isLoad3dNode) &&
|
|
enable3DViewer
|
|
)
|
|
})
|
|
|
|
const hasImageNode = computed(() => isSingleImageNode.value)
|
|
const hasOutputNodesSelected = computed(
|
|
() => filterOutputNodes(selectedNodes.value).length > 0
|
|
)
|
|
|
|
// Helper function to compute selection flags (reused by both computed and function)
|
|
const computeSelectionStatesFromNodes = (
|
|
nodes: LGraphNode[]
|
|
): NodeSelectionState => {
|
|
if (!nodes.length)
|
|
return { collapsed: false, pinned: false, bypassed: false }
|
|
return {
|
|
collapsed: nodes.some((n) => n.flags?.collapsed),
|
|
pinned: nodes.some((n) => n.pinned),
|
|
bypassed: nodes.some((n) => n.mode === LGraphEventMode.BYPASS)
|
|
}
|
|
}
|
|
|
|
const selectedNodesStates = computed<NodeSelectionState>(() =>
|
|
computeSelectionStatesFromNodes(selectedNodes.value)
|
|
)
|
|
|
|
// On-demand computation (non-reactive) so callers can fetch fresh flags
|
|
const computeSelectionFlags = (): NodeSelectionState =>
|
|
computeSelectionStatesFromNodes(selectedNodes.value)
|
|
|
|
const canOpenNodeInfo = computed(
|
|
() =>
|
|
Boolean(nodeDef.value) &&
|
|
settingStore.get('Comfy.UseNewMenu') !== 'Disabled'
|
|
)
|
|
|
|
const openNodeInfo = () => {
|
|
if (!canOpenNodeInfo.value) return false
|
|
rightSidePanelStore.openPanel('info')
|
|
return true
|
|
}
|
|
|
|
return {
|
|
selectedItems,
|
|
selectedNodes,
|
|
nodeDef,
|
|
canOpenNodeInfo,
|
|
openNodeInfo,
|
|
hasAny3DNodeSelected,
|
|
hasAnySelection,
|
|
hasSingleSelection,
|
|
hasMultipleSelection,
|
|
isSingleNode,
|
|
isSingleSubgraph,
|
|
isSingleImageNode,
|
|
hasSubgraphs,
|
|
hasImageNode,
|
|
hasOutputNodesSelected,
|
|
selectedNodesStates,
|
|
computeSelectionFlags
|
|
}
|
|
}
|