diff --git a/src/composables/useCoreCommands.test.ts b/src/composables/useCoreCommands.test.ts index 925b0aac4..f5df599ae 100644 --- a/src/composables/useCoreCommands.test.ts +++ b/src/composables/useCoreCommands.test.ts @@ -23,7 +23,13 @@ vi.mock('vue-i18n', async () => { vi.mock('@/scripts/app', () => { const mockGraphClear = vi.fn() - const mockCanvas = { subgraph: undefined } + const mockCanvas = { + subgraph: undefined, + selectedItems: new Set(), + copyToClipboard: vi.fn(), + pasteFromClipboard: vi.fn(), + selectItems: vi.fn() + } return { app: { @@ -105,7 +111,8 @@ vi.mock('@/stores/subgraphStore', () => ({ vi.mock('@/renderer/core/canvas/canvasStore', () => ({ useCanvasStore: vi.fn(() => ({ - getCanvas: () => app.canvas + getCanvas: () => app.canvas, + canvas: app.canvas })), useTitleEditorStore: vi.fn(() => ({ titleEditorTarget: null @@ -300,6 +307,48 @@ describe('useCoreCommands', () => { }) }) + describe('Canvas clipboard commands', () => { + function findCommand(id: string) { + return useCoreCommands().find((cmd) => cmd.id === id)! + } + + beforeEach(() => { + app.canvas.selectedItems = new Set() + vi.mocked(app.canvas.copyToClipboard).mockClear() + vi.mocked(app.canvas.pasteFromClipboard).mockClear() + vi.mocked(app.canvas.selectItems).mockClear() + }) + + it('should copy selected items when selection exists', async () => { + app.canvas.selectedItems = new Set([ + {} + ]) as typeof app.canvas.selectedItems + + await findCommand('Comfy.Canvas.CopySelected').function() + + expect(app.canvas.copyToClipboard).toHaveBeenCalledWith() + }) + + it('should not copy when no items are selected', async () => { + await findCommand('Comfy.Canvas.CopySelected').function() + + expect(app.canvas.copyToClipboard).not.toHaveBeenCalled() + }) + + it('should paste from clipboard', async () => { + await findCommand('Comfy.Canvas.PasteFromClipboard').function() + + expect(app.canvas.pasteFromClipboard).toHaveBeenCalledWith() + }) + + it('should select all items', async () => { + await findCommand('Comfy.Canvas.SelectAll').function() + + // No arguments means "select all items on canvas" + expect(app.canvas.selectItems).toHaveBeenCalledWith() + }) + }) + describe('Subgraph metadata commands', () => { beforeEach(() => { mockSubgraph.extra = {} diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 347aada4c..caab6316a 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -884,6 +884,32 @@ export function useCoreCommands(): ComfyCommand[] { window.open(staticUrls.forum, '_blank') } }, + { + id: 'Comfy.Canvas.CopySelected', + icon: 'icon-[lucide--copy]', + label: 'Copy', + function: () => { + if (app.canvas.selectedItems?.size) { + app.canvas.copyToClipboard() + } + } + }, + { + id: 'Comfy.Canvas.PasteFromClipboard', + icon: 'icon-[lucide--clipboard-paste]', + label: 'Paste', + function: () => { + app.canvas.pasteFromClipboard() + } + }, + { + id: 'Comfy.Canvas.SelectAll', + icon: 'icon-[lucide--lasso-select]', + label: 'Select All', + function: () => { + app.canvas.selectItems() + } + }, { id: 'Comfy.Canvas.DeleteSelectedItems', icon: 'pi pi-trash', diff --git a/src/constants/coreMenuCommands.ts b/src/constants/coreMenuCommands.ts index 6444b8309..1b1c52cbc 100644 --- a/src/constants/coreMenuCommands.ts +++ b/src/constants/coreMenuCommands.ts @@ -14,6 +14,14 @@ export const CORE_MENU_COMMANDS = [ ] ], [['Edit'], ['Comfy.Undo', 'Comfy.Redo']], + [ + ['Edit'], + [ + 'Comfy.Canvas.CopySelected', + 'Comfy.Canvas.PasteFromClipboard', + 'Comfy.Canvas.SelectAll' + ] + ], [['Edit'], ['Comfy.ClearWorkflow']], [['Edit'], ['Comfy.OpenClipspace']], [