allow copy and paste when minimap target

This commit is contained in:
bymyself
2025-10-14 11:46:54 -07:00
parent 9f046a11ea
commit 4d012f5fd9
9 changed files with 128 additions and 38 deletions

View File

@@ -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(() => {

View File

@@ -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<Locator['click']>[0]): Promise<void> {
await this.canvas.click(options)
}
async clickSettingsButton(): Promise<void> {
await this.settingsButton.click()
}
async close(): Promise<void> {
await this.closeButton.click()
}
}

View File

@@ -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)

View File

@@ -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()
})
})

View File

@@ -10,6 +10,7 @@
></div>
<ButtonGroup
id="graph-canvas-controls"
class="absolute right-2 bottom-2 z-[1200] flex-row gap-1 border-[1px] border-node-border bg-interface-panel-surface p-2"
:style="stringifiedMinimapStyles.buttonGroupStyles"
@wheel="canvasInteractions.handleWheel"

View File

@@ -20,10 +20,16 @@ export const useCopy = () => {
// Default system copy
return
}
// Check if target is graph canvas or within graph UI (minimap, controls, etc.)
const isTargetInGraph =
e.target.classList.contains('litegraph') ||
e.target.id === 'graph-canvas' ||
e.target.id === 'comfy-minimap' ||
e.target.id === 'graph-canvas-controls' ||
e.target.classList.contains('graph-canvas-container') ||
e.target.id === 'graph-canvas'
e.target.classList.contains('litegraph') ||
e.target.closest('#comfy-minimap') !== null ||
e.target.closest('#graph-canvas-controls') !== null ||
e.target.closest('#graph-canvas-container') !== null
// copy nodes and clear clipboard
const canvas = canvasStore.canvas

View File

@@ -38,11 +38,17 @@ export const usePaste = () => {
}
useEventListener(document, 'paste', async (e) => {
// Check if target is graph canvas or within graph UI (minimap, controls, etc.)
const isTargetInGraph =
e.target instanceof Element &&
(e.target.classList.contains('litegraph') ||
(e.target.id === 'graph-canvas' ||
e.target.id === 'comfy-minimap' ||
e.target.id === 'graph-canvas-controls' ||
e.target.classList.contains('graph-canvas-container') ||
e.target.id === 'graph-canvas')
e.target.classList.contains('litegraph') ||
e.target.closest('#comfy-minimap') !== null ||
e.target.closest('#graph-canvas-controls') !== null ||
e.target.closest('#graph-canvas-container') !== null)
// If the target is not in the graph, we don't want to handle the paste event
if (!isTargetInGraph) return

View File

@@ -1,6 +1,7 @@
<template>
<div
v-if="visible && initialized"
id="comfy-minimap"
ref="minimapRef"
class="minimap-main-container absolute right-2 bottom-[66px] z-1000 flex"
>

View File

@@ -15,6 +15,9 @@ export const useKeybindingService = () => {
const settingStore = useSettingStore()
const dialogStore = useDialogStore()
// Keys that LiteGraph handles but aren't in core keybindings
const canvasBindedKeys = ['Delete', 'Backspace']
// Helper function to determine if an event should be forwarded to canvas
const shouldForwardToCanvas = (event: KeyboardEvent): boolean => {
// Don't forward if modifier keys are pressed (except shift)
@@ -22,10 +25,7 @@ export const useKeybindingService = () => {
return false
}
// Keys that LiteGraph handles but aren't in core keybindings
const canvasKeys = ['Delete', 'Backspace']
return canvasKeys.includes(event.key)
return canvasBindedKeys.includes(event.key)
}
const keybindHandler = async function (event: KeyboardEvent) {