mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 06:35:10 +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>
166 lines
5.8 KiB
Vue
166 lines
5.8 KiB
Vue
<template>
|
|
<div
|
|
ref="toolboxRef"
|
|
style="transform: translate(var(--tb-x), var(--tb-y))"
|
|
class="pointer-events-none fixed top-0 left-0 z-40"
|
|
>
|
|
<Transition name="slide-up">
|
|
<Panel
|
|
v-if="visible"
|
|
data-testid="selection-toolbox"
|
|
class="selection-toolbox pointer-events-auto rounded-lg border border-interface-stroke bg-interface-panel-surface"
|
|
:pt="{
|
|
header: 'hidden',
|
|
content: 'p-1 h-10 flex flex-row gap-1'
|
|
}"
|
|
@wheel="canvasInteractions.forwardEventToCanvas"
|
|
>
|
|
<DeleteButton v-if="showDelete" />
|
|
<VerticalDivider v-if="canOpenNodeInfo && showAnyPrimaryActions" />
|
|
<InfoButton v-if="canOpenNodeInfo" />
|
|
|
|
<ColorPickerButton v-if="showColorPicker" />
|
|
<FrameNodes v-if="showFrameNodes" />
|
|
<ConvertToSubgraphButton v-if="showConvertToSubgraph" />
|
|
<ConfigureSubgraph v-if="showSubgraphButtons" />
|
|
<PublishSubgraphButton v-if="showSubgraphButtons" />
|
|
<MaskEditorButton v-if="showMaskEditor" />
|
|
<VerticalDivider
|
|
v-if="showAnyPrimaryActions && showAnyControlActions"
|
|
/>
|
|
|
|
<BypassButton v-if="showBypass" />
|
|
<RefreshSelectionButton v-if="showRefresh" />
|
|
<Load3DViewerButton v-if="showLoad3DViewer" />
|
|
|
|
<ExtensionCommandButton
|
|
v-for="command in extensionToolboxCommands"
|
|
:key="command.id"
|
|
:command="command"
|
|
/>
|
|
<ExecuteButton v-if="showExecute" />
|
|
<NodeOptionsButton />
|
|
</Panel>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import Panel from 'primevue/panel'
|
|
import { computed, ref } from 'vue'
|
|
|
|
import BypassButton from '@/components/graph/selectionToolbox/BypassButton.vue'
|
|
import ColorPickerButton from '@/components/graph/selectionToolbox/ColorPickerButton.vue'
|
|
import ConfigureSubgraph from '@/components/graph/selectionToolbox/ConfigureSubgraph.vue'
|
|
import ConvertToSubgraphButton from '@/components/graph/selectionToolbox/ConvertToSubgraphButton.vue'
|
|
import DeleteButton from '@/components/graph/selectionToolbox/DeleteButton.vue'
|
|
import ExecuteButton from '@/components/graph/selectionToolbox/ExecuteButton.vue'
|
|
import ExtensionCommandButton from '@/components/graph/selectionToolbox/ExtensionCommandButton.vue'
|
|
import InfoButton from '@/components/graph/selectionToolbox/InfoButton.vue'
|
|
import Load3DViewerButton from '@/components/graph/selectionToolbox/Load3DViewerButton.vue'
|
|
import MaskEditorButton from '@/components/graph/selectionToolbox/MaskEditorButton.vue'
|
|
import RefreshSelectionButton from '@/components/graph/selectionToolbox/RefreshSelectionButton.vue'
|
|
import PublishSubgraphButton from '@/components/graph/selectionToolbox/SaveToSubgraphLibrary.vue'
|
|
import { useSelectionToolboxPosition } from '@/composables/canvas/useSelectionToolboxPosition'
|
|
import { useSelectionState } from '@/composables/graph/useSelectionState'
|
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
|
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
|
|
import { useExtensionService } from '@/services/extensionService'
|
|
import { useCommandStore } from '@/stores/commandStore'
|
|
import type { ComfyCommandImpl } from '@/stores/commandStore'
|
|
|
|
import FrameNodes from './selectionToolbox/FrameNodes.vue'
|
|
import NodeOptionsButton from './selectionToolbox/NodeOptionsButton.vue'
|
|
import VerticalDivider from './selectionToolbox/VerticalDivider.vue'
|
|
|
|
const commandStore = useCommandStore()
|
|
const canvasStore = useCanvasStore()
|
|
const extensionService = useExtensionService()
|
|
const canvasInteractions = useCanvasInteractions()
|
|
|
|
const toolboxRef = ref<HTMLElement | undefined>()
|
|
const { visible } = useSelectionToolboxPosition(toolboxRef)
|
|
|
|
const extensionToolboxCommands = computed<ComfyCommandImpl[]>(() => {
|
|
const commandIds = new Set<string>(
|
|
canvasStore.selectedItems
|
|
.map(
|
|
(item) =>
|
|
extensionService
|
|
.invokeExtensions('getSelectionToolboxCommands', item)
|
|
.flat() as string[]
|
|
)
|
|
.flat()
|
|
)
|
|
return Array.from(commandIds)
|
|
.map((commandId) => commandStore.getCommand(commandId))
|
|
.filter((command): command is ComfyCommandImpl => command !== undefined)
|
|
})
|
|
|
|
const {
|
|
hasAnySelection,
|
|
hasMultipleSelection,
|
|
isSingleNode,
|
|
isSingleSubgraph,
|
|
isSingleImageNode,
|
|
hasAny3DNodeSelected,
|
|
hasOutputNodesSelected,
|
|
canOpenNodeInfo
|
|
} = useSelectionState()
|
|
|
|
const showColorPicker = computed(() => hasAnySelection.value)
|
|
const showConvertToSubgraph = computed(() => hasAnySelection.value)
|
|
const showFrameNodes = computed(() => hasMultipleSelection.value)
|
|
const showSubgraphButtons = computed(() => isSingleSubgraph.value)
|
|
|
|
const showBypass = computed(
|
|
() =>
|
|
isSingleNode.value || isSingleSubgraph.value || hasMultipleSelection.value
|
|
)
|
|
const showLoad3DViewer = computed(() => hasAny3DNodeSelected.value)
|
|
const showMaskEditor = computed(() => isSingleImageNode.value)
|
|
|
|
const showDelete = computed(() => hasAnySelection.value)
|
|
const showRefresh = computed(() => hasAnySelection.value)
|
|
const showExecute = computed(() => hasOutputNodesSelected.value)
|
|
|
|
const showAnyPrimaryActions = computed(
|
|
() =>
|
|
showColorPicker.value ||
|
|
showConvertToSubgraph.value ||
|
|
showFrameNodes.value ||
|
|
showSubgraphButtons.value
|
|
)
|
|
|
|
const showAnyControlActions = computed(() => showBypass.value)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.selection-toolbox {
|
|
transform: translateX(-50%) translateY(-120%);
|
|
}
|
|
|
|
@keyframes slideUp {
|
|
0% {
|
|
transform: translateX(-50%) translateY(-100%);
|
|
opacity: 0;
|
|
}
|
|
50% {
|
|
transform: translateX(-50%) translateY(-125%);
|
|
opacity: 0.5;
|
|
}
|
|
100% {
|
|
transform: translateX(-50%) translateY(-120%);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.slide-up-enter-active {
|
|
animation: slideUp 125ms ease-out;
|
|
}
|
|
|
|
.slide-up-leave-active {
|
|
animation: slideUp 25ms ease-out reverse;
|
|
}
|
|
</style>
|