From a4d99d9d284c199305907f0dae0c7c2573e30991 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Wed, 5 Feb 2025 17:09:01 -0500 Subject: [PATCH] Support insert workflow by dragging from workflow sidebar to canvas (#2440) --- browser_tests/menu.spec.ts | 30 +++++++++++++++++++ package-lock.json | 8 ++--- package.json | 2 +- .../sidebar/tabs/WorkflowsSidebarTab.vue | 3 +- src/composables/useCanvasDrop.ts | 10 +++++++ src/services/workflowService.ts | 8 +++-- 6 files changed, 53 insertions(+), 8 deletions(-) diff --git a/browser_tests/menu.spec.ts b/browser_tests/menu.spec.ts index ba479572ed..a05ee69953 100644 --- a/browser_tests/menu.spec.ts +++ b/browser_tests/menu.spec.ts @@ -707,6 +707,36 @@ test.describe('Menu', () => { '*Unsaved Workflow.json' ]) }) + + test('Can drop workflow from workflows sidebar', async ({ comfyPage }) => { + await comfyPage.setupWorkflowsDirectory({ + 'workflow1.json': 'default.json' + }) + await comfyPage.setup() + await comfyPage.menu.workflowsTab.open() + + const nodeCount = await comfyPage.getGraphNodesCount() + + // Get the bounding box of the canvas element + const canvasBoundingBox = (await comfyPage.page + .locator('#graph-canvas') + .boundingBox())! + + // Calculate the center position of the canvas + const targetPosition = { + x: canvasBoundingBox.x + canvasBoundingBox.width / 2, + y: canvasBoundingBox.y + canvasBoundingBox.height / 2 + } + + await comfyPage.page.dragAndDrop( + '.comfyui-workflows-browse .node-label:has-text("workflow1.json")', + '#graph-canvas', + { targetPosition } + ) + // Wait for the workflow to be inserted + await comfyPage.page.waitForTimeout(200) + expect(await comfyPage.getGraphNodesCount()).toBe(nodeCount * 2) + }) }) test.describe('Workflows topbar tabs', () => { diff --git a/package-lock.json b/package-lock.json index 25a3c4063f..58d0eba901 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.3.1", "@comfyorg/comfyui-electron-types": "^0.4.16", - "@comfyorg/litegraph": "^0.8.67", + "@comfyorg/litegraph": "^0.8.68", "@primevue/forms": "^4.2.5", "@primevue/themes": "^4.2.5", "@sentry/vue": "^8.48.0", @@ -1944,9 +1944,9 @@ "license": "GPL-3.0-only" }, "node_modules/@comfyorg/litegraph": { - "version": "0.8.67", - "resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.67.tgz", - "integrity": "sha512-yGytBGJHpEpesZ+2eusepbHlUlztYBu25h4eSIlMildceMFI/oEVTDj3gdmVgaReEs3uAZVnfSBqIPwqthHGJA==", + "version": "0.8.68", + "resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.68.tgz", + "integrity": "sha512-/c4PH2/vNE2DezdZYdXapCq4jGvSHJqrCT1S9WL3uJ5XYjRcanztLYUGkXgYXNEHfvUV/1oerF3p9o3Kvglwhg==", "license": "MIT" }, "node_modules/@cspotcode/source-map-support": { diff --git a/package.json b/package.json index 15e75cbcb9..51b7163439 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.3.1", "@comfyorg/comfyui-electron-types": "^0.4.16", - "@comfyorg/litegraph": "^0.8.67", + "@comfyorg/litegraph": "^0.8.68", "@primevue/forms": "^4.2.5", "@primevue/themes": "^4.2.5", "@sentry/vue": "^8.48.0", diff --git a/src/components/sidebar/tabs/WorkflowsSidebarTab.vue b/src/components/sidebar/tabs/WorkflowsSidebarTab.vue index 6ef2bc7f8d..3a8a94190d 100644 --- a/src/components/sidebar/tabs/WorkflowsSidebarTab.vue +++ b/src/components/sidebar/tabs/WorkflowsSidebarTab.vue @@ -280,7 +280,8 @@ const renderTreeNode = ( } } ] - } + }, + draggable: true } : { handleClick } diff --git a/src/composables/useCanvasDrop.ts b/src/composables/useCanvasDrop.ts index d5ea8e0b9f..a3313f9f57 100644 --- a/src/composables/useCanvasDrop.ts +++ b/src/composables/useCanvasDrop.ts @@ -5,15 +5,18 @@ import { Ref } from 'vue' import { usePragmaticDroppable } from '@/composables/usePragmaticDroppable' import { app as comfyApp } from '@/scripts/app' import { useLitegraphService } from '@/services/litegraphService' +import { useWorkflowService } from '@/services/workflowService' import { ComfyModelDef } from '@/stores/modelStore' import { ModelNodeProvider } from '@/stores/modelToNodeStore' import { useModelToNodeStore } from '@/stores/modelToNodeStore' import { ComfyNodeDefImpl } from '@/stores/nodeDefStore' +import { ComfyWorkflow } from '@/stores/workflowStore' import { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes' export const useCanvasDrop = (canvasRef: Ref) => { const modelToNodeStore = useModelToNodeStore() const litegraphService = useLitegraphService() + const workflowService = useWorkflowService() usePragmaticDroppable(() => canvasRef.value, { getDropEffect: (args): Exclude => @@ -70,6 +73,13 @@ export const useCanvasDrop = (canvasRef: Ref) => { widget.value = model.file_name } } + } else if (node.data instanceof ComfyWorkflow) { + const workflow = node.data + const position = comfyApp.clientPosToCanvasPos([ + loc.clientX, + loc.clientY + ]) + workflowService.insertWorkflow(workflow, { position }) } } } diff --git a/src/services/workflowService.ts b/src/services/workflowService.ts index 35c79d39b5..12eea62df8 100644 --- a/src/services/workflowService.ts +++ b/src/services/workflowService.ts @@ -1,4 +1,5 @@ import { LGraphCanvas } from '@comfyorg/litegraph' +import type { Vector2 } from '@comfyorg/litegraph' import { toRaw } from 'vue' import { t } from '@/i18n' @@ -327,7 +328,10 @@ export const useWorkflowService = () => { /** * Insert the given workflow into the current graph editor. */ - const insertWorkflow = async (workflow: ComfyWorkflow) => { + const insertWorkflow = async ( + workflow: ComfyWorkflow, + options: { position?: Vector2 } = {} + ) => { const loadedWorkflow = await workflow.load() const data = loadedWorkflow.initialState const old = localStorage.getItem('litegrapheditor_clipboard') @@ -341,7 +345,7 @@ export const useWorkflowService = () => { canvas.reroutesEnabled = app.canvas.reroutesEnabled canvas.selectItems() canvas.copyToClipboard() - app.canvas.pasteFromClipboard() + app.canvas.pasteFromClipboard(options) if (old !== null) { localStorage.setItem('litegrapheditor_clipboard', old) }