fix: show Paste Image in Node 2.0 menu without requiring existing image

- Decouple Paste Image from image-dependent menu items so it appears
  even when no image is loaded yet (e.g. fresh LoadImage node)
- Remove Paste Image from legacy litegraph menu (Node 2.0 only)
- Simplify e2e test to not require image drag-drop setup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dante01yoon
2026-03-16 17:41:08 +09:00
parent fe63d6747c
commit 014d1427b6
4 changed files with 48 additions and 59 deletions

View File

@@ -3,11 +3,7 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.describe('Paste Image context menu option', { tag: ['@node'] }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test('shows Paste Image in LoadImage node context menu after image is loaded', async ({
test('shows Paste Image in LoadImage node context menu', async ({
comfyPage
}) => {
await comfyPage.workflow.loadWorkflow('widgets/load_image_widget')
@@ -15,22 +11,10 @@ test.describe('Paste Image context menu option', { tag: ['@node'] }, () => {
const loadImageNode = (
await comfyPage.nodeOps.getNodeRefsByType('LoadImage')
)[0]
const { x, y } = await loadImageNode.getPosition()
await comfyPage.dragDrop.dragAndDropFile('image64x64.webp', {
dropPosition: { x, y },
waitForUpload: true
})
const menuOptions = await loadImageNode.getContextMenuOptionNames()
expect(menuOptions).toContain('Paste Image')
const copyIdx = menuOptions.indexOf('Copy Image')
const pasteIdx = menuOptions.indexOf('Paste Image')
const saveIdx = menuOptions.indexOf('Save Image')
expect(copyIdx).toBeLessThan(pasteIdx)
expect(pasteIdx).toBeLessThan(saveIdx)
})
test('does not show Paste Image on output-only image nodes', async ({

View File

@@ -67,13 +67,22 @@ describe('useImageMenuOptions', () => {
expect(labels).not.toContain('Paste Image')
})
it('returns empty array when node has no images', () => {
it('returns empty array when node has no images and no pasteFiles', () => {
const node = createMockLGraphNode({ imgs: [] })
const { getImageMenuOptions } = useImageMenuOptions()
expect(getImageMenuOptions(node)).toEqual([])
})
it('returns only Paste Image when node has no images but supports paste', () => {
const node = createMockLGraphNode({ imgs: [], pasteFiles: vi.fn() })
const { getImageMenuOptions } = useImageMenuOptions()
const options = getImageMenuOptions(node)
const labels = options.map((o) => o.label)
expect(labels).toEqual(['Paste Image'])
})
it('places Paste Image between Copy Image and Save Image', () => {
const node = createImageNode()
const { getImageMenuOptions } = useImageMenuOptions()

View File

@@ -83,38 +83,47 @@ export function useImageMenuOptions() {
}
const getImageMenuOptions = (node: LGraphNode): MenuOption[] => {
if (!node?.imgs?.length) return []
const hasImages = !!node?.imgs?.length
if (!hasImages && !canPasteImage(node)) return []
return [
{
label: t('contextMenu.Open in Mask Editor'),
action: () => openMaskEditor()
},
{
label: t('contextMenu.Open Image'),
icon: 'icon-[lucide--external-link]',
action: () => openImage(node)
},
{
label: t('contextMenu.Copy Image'),
icon: 'icon-[lucide--copy]',
action: () => copyImage(node)
},
...(canPasteImage(node)
? [
{
label: t('contextMenu.Paste Image'),
icon: 'icon-[lucide--clipboard-paste]',
action: () => pasteClipboardImageToNode(node)
}
]
: []),
{
const options: MenuOption[] = []
if (hasImages) {
options.push(
{
label: t('contextMenu.Open in Mask Editor'),
action: () => openMaskEditor()
},
{
label: t('contextMenu.Open Image'),
icon: 'icon-[lucide--external-link]',
action: () => openImage(node)
},
{
label: t('contextMenu.Copy Image'),
icon: 'icon-[lucide--copy]',
action: () => copyImage(node)
}
)
}
if (canPasteImage(node)) {
options.push({
label: t('contextMenu.Paste Image'),
icon: 'icon-[lucide--clipboard-paste]',
action: () => pasteClipboardImageToNode(node)
})
}
if (hasImages) {
options.push({
label: t('contextMenu.Save Image'),
icon: 'icon-[lucide--download]',
action: () => saveImage(node)
}
]
})
}
return options
}
return {

View File

@@ -1,7 +1,6 @@
import _ from 'es-toolkit/compat'
import { downloadFile, openFileInNewTab } from '@/base/common/downloadUtil'
import { pasteClipboardImageToNode } from '@/utils/clipboardUtil'
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
import { useSubgraphOperations } from '@/composables/graph/useSubgraphOperations'
import { useNodeAnimatedImage } from '@/composables/node/useNodeAnimatedImage'
@@ -687,17 +686,6 @@ export const useLitegraphService = () => {
img = this.imgs[this.overIndex]
}
if (img) {
const node = this
const pasteImageOption: IContextMenuValue[] =
typeof node.pasteFiles === 'function'
? [
{
content: 'Paste Image',
callback: () => pasteClipboardImageToNode(node)
}
]
: []
options.unshift(
{
content: 'Open Image',
@@ -708,7 +696,6 @@ export const useLitegraphService = () => {
}
},
...getCopyImageOption(img),
...pasteImageOption,
{
content: 'Save Image',
callback: () => {