mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
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:
@@ -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 ({
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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: () => {
|
||||
|
||||
Reference in New Issue
Block a user