mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-20 23:04:06 +00:00
Batch Drag & Drop Images (#8282)
## Summary <!-- One sentence describing what changed and why. --> Added feature to drag and drop multiple images into the UI and connect them with a Batch Images node with tests to add convenience for users. Only works with a group of images, mixing files not supported. ## Review Focus <!-- Critical design decisions or edge cases that need attention --> I've updated our usage of Litegraph.createNode, honestly, that method is pretty bad, onNodeCreated option method doesn't even return the node created. I think I will probably go check out their repo to do a PR over there. Anyways, I made a createNode method to avoid race conditions when creating nodes for the paste actions. Will allow us to better programmatically create nodes that do not have workflows that also need to be connected to other nodes. <!-- If this PR fixes an issue, uncomment and update the line below --> https://www.notion.so/comfy-org/Implement-Multi-image-drag-and-drop-to-canvas-2eb6d73d36508195ad8addfc4367db10 ## Screenshots (if applicable) https://github.com/user-attachments/assets/d4155807-56e2-4e39-8ab1-16eda90f6a53 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8282-Batch-Drag-Drop-Images-2f16d73d365081c1ab31ce9da47a7be5) by [Unito](https://www.unito.io) --------- Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: Austin Mroz <austin@comfy.org>
This commit is contained in:
@@ -84,6 +84,7 @@ import {
|
||||
} from '@/utils/graphTraversalUtil'
|
||||
import {
|
||||
executeWidgetsCallback,
|
||||
createNode,
|
||||
fixLinkInputSlots,
|
||||
isImageNode
|
||||
} from '@/utils/litegraphUtil'
|
||||
@@ -108,7 +109,7 @@ import { type ComfyWidgetConstructor } from './widgets'
|
||||
import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale'
|
||||
import { extractFileFromDragEvent } from '@/utils/eventUtils'
|
||||
import { getWorkflowDataFromFile } from '@/scripts/metadata/parser'
|
||||
import { pasteImageNode } from '@/composables/usePaste'
|
||||
import { pasteImageNode, pasteImageNodes } from '@/composables/usePaste'
|
||||
|
||||
export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
|
||||
|
||||
@@ -553,7 +554,13 @@ export class ComfyApp {
|
||||
const workspace = useWorkspaceStore()
|
||||
try {
|
||||
workspace.spinner = true
|
||||
await this.handleFile(fileMaybe, 'file_drop')
|
||||
if (fileMaybe instanceof File) {
|
||||
await this.handleFile(fileMaybe, 'file_drop')
|
||||
}
|
||||
|
||||
if (fileMaybe instanceof FileList) {
|
||||
await this.handleFileList(fileMaybe)
|
||||
}
|
||||
} finally {
|
||||
workspace.spinner = false
|
||||
}
|
||||
@@ -1488,7 +1495,8 @@ export class ComfyApp {
|
||||
if (file.type.startsWith('image')) {
|
||||
const transfer = new DataTransfer()
|
||||
transfer.items.add(file)
|
||||
pasteImageNode(this.canvas, transfer.items)
|
||||
const imageNode = await createNode(this.canvas, 'LoadImage')
|
||||
await pasteImageNode(this.canvas, transfer.items, imageNode)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1567,6 +1575,50 @@ export class ComfyApp {
|
||||
this.showErrorOnFileLoad(file)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads multiple files, connects to a batch node, and selects them
|
||||
* @param {FileList} fileList
|
||||
*/
|
||||
async handleFileList(fileList: FileList) {
|
||||
if (fileList[0].type.startsWith('image')) {
|
||||
const imageNodes = await pasteImageNodes(this.canvas, fileList)
|
||||
const batchImagesNode = await createNode(this.canvas, 'BatchImagesNode')
|
||||
if (!batchImagesNode) return
|
||||
|
||||
this.positionBatchNodes(imageNodes, batchImagesNode)
|
||||
this.canvas.selectItems([...imageNodes, batchImagesNode])
|
||||
|
||||
Array.from(imageNodes).forEach((imageNode, index) => {
|
||||
imageNode.connect(0, batchImagesNode, index)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions batched nodes in drag and drop
|
||||
* @param nodes
|
||||
* @param batchNode
|
||||
*/
|
||||
positionBatchNodes(nodes: LGraphNode[], batchNode: LGraphNode): void {
|
||||
const [x, y, width] = nodes[0].getBounding()
|
||||
batchNode.pos = [ x + width + 100, y + 30 ]
|
||||
|
||||
// Retrieving Node Height is inconsistent
|
||||
let height = 0;
|
||||
if (nodes[0].type === 'LoadImage') {
|
||||
height = 344
|
||||
}
|
||||
|
||||
nodes.forEach((node, index) => {
|
||||
if (index > 0) {
|
||||
node.pos = [ x, y + (height * index) + (25 * (index + 1)) ]
|
||||
}
|
||||
});
|
||||
|
||||
this.canvas.graph?.change()
|
||||
}
|
||||
|
||||
// @deprecated
|
||||
isApiJson(data: unknown): data is ComfyApiWorkflow {
|
||||
if (!_.isObject(data) || Array.isArray(data)) {
|
||||
|
||||
Reference in New Issue
Block a user