diff --git a/browser_tests/fixtures/ComfyPage.ts b/browser_tests/fixtures/ComfyPage.ts index b01de101e5..3f960561c5 100644 --- a/browser_tests/fixtures/ComfyPage.ts +++ b/browser_tests/fixtures/ComfyPage.ts @@ -13,6 +13,7 @@ import { ComfyTemplates } from '../helpers/templates' import { ComfyMouse } from './ComfyMouse' import { VueNodeHelpers } from './VueNodeHelpers' import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox' +import { Minimap } from './components/Minimap' import { SettingDialog } from './components/SettingDialog' import { NodeLibrarySidebarTab, @@ -33,6 +34,7 @@ class ComfyMenu { private _workflowsTab: WorkflowsSidebarTab | null = null private _queueTab: QueueSidebarTab | null = null private _topbar: Topbar | null = null + private _minimap: Minimap | null = null public readonly sideToolbar: Locator public readonly themeToggleButton: Locator @@ -70,6 +72,11 @@ class ComfyMenu { return this._topbar } + get minimap() { + this._minimap ??= new Minimap(this.page) + return this._minimap + } + async toggleTheme() { await this.themeToggleButton.click() await this.page.evaluate(() => { diff --git a/browser_tests/fixtures/components/Minimap.ts b/browser_tests/fixtures/components/Minimap.ts new file mode 100644 index 0000000000..9554b008ff --- /dev/null +++ b/browser_tests/fixtures/components/Minimap.ts @@ -0,0 +1,41 @@ +import type { Locator, Page } from '@playwright/test' + +export class Minimap { + constructor(public readonly page: Page) {} + + get mainContainer(): Locator { + return this.page.locator('.minimap-main-container') + } + + get container(): Locator { + return this.page.locator('.litegraph-minimap') + } + + get canvas(): Locator { + return this.container.locator('.minimap-canvas') + } + + get viewport(): Locator { + return this.container.locator('.minimap-viewport') + } + + get settingsButton(): Locator { + return this.container.getByRole('button').first() + } + + get closeButton(): Locator { + return this.container.getByTestId('close-minmap-button') + } + + async clickCanvas(options?: Parameters[0]): Promise { + await this.canvas.click(options) + } + + async clickSettingsButton(): Promise { + await this.settingsButton.click() + } + + async close(): Promise { + await this.closeButton.click() + } +} diff --git a/browser_tests/tests/copyPaste.spec.ts b/browser_tests/tests/copyPaste.spec.ts index cabb849e80..877cab03a7 100644 --- a/browser_tests/tests/copyPaste.spec.ts +++ b/browser_tests/tests/copyPaste.spec.ts @@ -2,11 +2,11 @@ import { expect } from '@playwright/test' import { comfyPageFixture as test } from '../fixtures/ComfyPage' -test.beforeEach(async ({ comfyPage }) => { - await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled') -}) - test.describe('Copy Paste', () => { + test.beforeEach(async ({ comfyPage }) => { + await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled') + }) + test('Can copy and paste node', async ({ comfyPage }) => { await comfyPage.clickEmptyLatentNode() await comfyPage.page.mouse.move(10, 10) @@ -15,6 +15,29 @@ test.describe('Copy Paste', () => { await expect(comfyPage.canvas).toHaveScreenshot('copied-node.png') }) + test('Can copy and paste node after clicking minimap', async ({ + comfyPage + }) => { + await comfyPage.setSetting('Comfy.UseNewMenu', 'Top') + await comfyPage.setSetting('Comfy.Minimap.Visible', true) + + const latentNodeTitle = 'Empty Latent Image' + const initialLatentNodeCt = + await comfyPage.getNodeRefsByTitle(latentNodeTitle) + + await comfyPage.clickEmptyLatentNode() + await comfyPage.ctrlC() + + // Click minimap to lose focus. + await comfyPage.menu.minimap.clickCanvas({ force: true }) + + // Paste node. + await comfyPage.ctrlV() + const expectedNodeCt = initialLatentNodeCt.length + 1 + const latentNodeCt = await comfyPage.getNodeRefsByTitle(latentNodeTitle) + expect(latentNodeCt.length).toBe(expectedNodeCt) + }) + test('Can copy and paste node with link', async ({ comfyPage }) => { await comfyPage.clickTextEncodeNode1() await comfyPage.page.mouse.move(10, 10) diff --git a/browser_tests/tests/minimap.spec.ts b/browser_tests/tests/minimap.spec.ts index cea5fe9a80..3b8d8b26ce 100644 --- a/browser_tests/tests/minimap.spec.ts +++ b/browser_tests/tests/minimap.spec.ts @@ -14,65 +14,70 @@ test.describe('Minimap', () => { }) test('Validate minimap is visible by default', async ({ comfyPage }) => { - const minimapContainer = comfyPage.page.locator('.litegraph-minimap') + const minimap = comfyPage.menu.minimap - await expect(minimapContainer).toBeVisible() - - const minimapCanvas = minimapContainer.locator('.minimap-canvas') - await expect(minimapCanvas).toBeVisible() - - const minimapViewport = minimapContainer.locator('.minimap-viewport') - await expect(minimapViewport).toBeVisible() - - await expect(minimapContainer).toHaveCSS('position', 'relative') + await expect(minimap.container).toBeVisible() + await expect(minimap.canvas).toBeVisible() + await expect(minimap.viewport).toBeVisible() + await expect(minimap.container).toHaveCSS('position', 'relative') // position and z-index validation moved to the parent container of the minimap - const minimapMainContainer = comfyPage.page.locator( - '.minimap-main-container' - ) - await expect(minimapMainContainer).toHaveCSS('position', 'absolute') - await expect(minimapMainContainer).toHaveCSS('z-index', '1000') + await expect(minimap.mainContainer).toHaveCSS('position', 'absolute') + await expect(minimap.mainContainer).toHaveCSS('z-index', '1000') }) test('Validate minimap toggle button state', async ({ comfyPage }) => { + const minimap = comfyPage.menu.minimap + const toggleButton = comfyPage.page.getByTestId('toggle-minimap-button') await expect(toggleButton).toBeVisible() - - const minimapContainer = comfyPage.page.locator('.litegraph-minimap') - await expect(minimapContainer).toBeVisible() + await expect(minimap.container).toBeVisible() }) test('Validate minimap can be toggled off and on', async ({ comfyPage }) => { - const minimapContainer = comfyPage.page.locator('.litegraph-minimap') + const minimap = comfyPage.menu.minimap + + // Open zoom controls dropdown first + const zoomControlsButton = comfyPage.page.getByTestId( + 'zoom-controls-button' + ) + await zoomControlsButton.click() + const toggleButton = comfyPage.page.getByTestId('toggle-minimap-button') - await expect(minimapContainer).toBeVisible() + await expect(minimap.container).toBeVisible() await toggleButton.click() await comfyPage.nextFrame() - await expect(minimapContainer).not.toBeVisible() + await expect(minimap.container).not.toBeVisible() await toggleButton.click() await comfyPage.nextFrame() - await expect(minimapContainer).toBeVisible() + await expect(minimap.container).toBeVisible() + + // Open zoom controls dropdown again to verify button text + await zoomControlsButton.click() + await comfyPage.nextFrame() + + await expect(toggleButton).toContainText('Hide Minimap') }) test('Validate minimap keyboard shortcut Alt+M', async ({ comfyPage }) => { - const minimapContainer = comfyPage.page.locator('.litegraph-minimap') + const minimap = comfyPage.menu.minimap - await expect(minimapContainer).toBeVisible() + await expect(minimap.container).toBeVisible() await comfyPage.page.keyboard.press('Alt+KeyM') await comfyPage.nextFrame() - await expect(minimapContainer).not.toBeVisible() + await expect(minimap.container).not.toBeVisible() await comfyPage.page.keyboard.press('Alt+KeyM') await comfyPage.nextFrame() - await expect(minimapContainer).toBeVisible() + await expect(minimap.container).toBeVisible() }) }) diff --git a/src/components/graph/GraphCanvasMenu.vue b/src/components/graph/GraphCanvasMenu.vue index 0923db0064..568f161a1a 100644 --- a/src/components/graph/GraphCanvasMenu.vue +++ b/src/components/graph/GraphCanvasMenu.vue @@ -10,6 +10,7 @@ > { // Default system copy return } - const isTargetInGraph = - e.target.classList.contains('litegraph') || - e.target.classList.contains('graph-canvas-container') || - e.target.id === 'graph-canvas' + // Check if target is graph canvas or within graph UI (minimap, controls, etc.) + if (!isEventTargetInGraph(e.target)) { + return + } // copy nodes and clear clipboard const canvas = canvasStore.canvas - if (isTargetInGraph && canvas?.selectedItems) { + if (canvas?.selectedItems) { canvas.copyToClipboard() // clearData doesn't remove images from clipboard e.clipboardData?.setData('text', ' ') diff --git a/src/composables/usePaste.ts b/src/composables/usePaste.ts index c04901951a..c322d5940a 100644 --- a/src/composables/usePaste.ts +++ b/src/composables/usePaste.ts @@ -7,6 +7,7 @@ import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { app } from '@/scripts/app' import { useWorkspaceStore } from '@/stores/workspaceStore' import { isAudioNode, isImageNode, isVideoNode } from '@/utils/litegraphUtil' +import { isEventTargetInGraph } from '@/workbench/eventHelpers' /** * Adds a handler on paste that extracts and loads images or workflows from pasted JSON data @@ -38,14 +39,10 @@ export const usePaste = () => { } useEventListener(document, 'paste', async (e) => { - const isTargetInGraph = - e.target instanceof Element && - (e.target.classList.contains('litegraph') || - e.target.classList.contains('graph-canvas-container') || - e.target.id === 'graph-canvas') - - // If the target is not in the graph, we don't want to handle the paste event - if (!isTargetInGraph) return + // Check if target is graph canvas or within graph UI (minimap, controls, etc.) + if (!isEventTargetInGraph(e.target)) { + return + } // ctrl+shift+v is used to paste nodes with connections // this is handled by litegraph diff --git a/src/renderer/extensions/minimap/MiniMap.vue b/src/renderer/extensions/minimap/MiniMap.vue index 227379c049..7a3bca5bef 100644 --- a/src/renderer/extensions/minimap/MiniMap.vue +++ b/src/renderer/extensions/minimap/MiniMap.vue @@ -1,6 +1,7 @@