mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-20 06:44:32 +00:00
Add support for dragging in multiple workflow files at once (#8757)
## Summary Allows users to drag in multiple files that are/have embedded workflows and loads each of them as tabs. Previously it would only load the first one. ## Changes - **What**: - process all files from drop event - add defered errors so you don't get errors for non-visible workflows ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8757-Add-support-for-dragging-in-multiple-workflow-files-at-once-3026d73d365081c096e9dfb18ba01253) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -1,39 +1,68 @@
|
||||
import { extractFileFromDragEvent } from '@/utils/eventUtils'
|
||||
import { extractFilesFromDragEvent } from '@/utils/eventUtils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
describe('eventUtils', () => {
|
||||
describe('extractFileFromDragEvent', () => {
|
||||
it('should handle drops with no data', async () => {
|
||||
const actual = await extractFileFromDragEvent(new FakeDragEvent('drop'))
|
||||
expect(actual).toBe(undefined)
|
||||
describe('extractFilesFromDragEvent', () => {
|
||||
it('should return empty array when no dataTransfer', async () => {
|
||||
const actual = await extractFilesFromDragEvent(new FakeDragEvent('drop'))
|
||||
expect(actual).toEqual([])
|
||||
})
|
||||
|
||||
it('should handle drops with dataTransfer but no files', async () => {
|
||||
const actual = await extractFileFromDragEvent(
|
||||
it('should return empty array when dataTransfer has no files', async () => {
|
||||
const actual = await extractFilesFromDragEvent(
|
||||
new FakeDragEvent('drop', { dataTransfer: new DataTransfer() })
|
||||
)
|
||||
expect(actual).toBe(undefined)
|
||||
expect(actual).toEqual([])
|
||||
})
|
||||
|
||||
it('should handle drops with dataTransfer with files', async () => {
|
||||
const fileWithWorkflowMaybeWhoKnows = new File(
|
||||
[new Uint8Array()],
|
||||
'fake_workflow.json',
|
||||
{
|
||||
type: 'application/json'
|
||||
}
|
||||
)
|
||||
|
||||
it('should return single file from dataTransfer', async () => {
|
||||
const file = new File([new Uint8Array()], 'workflow.json', {
|
||||
type: 'application/json'
|
||||
})
|
||||
const dataTransfer = new DataTransfer()
|
||||
dataTransfer.items.add(fileWithWorkflowMaybeWhoKnows)
|
||||
dataTransfer.items.add(file)
|
||||
|
||||
const event = new FakeDragEvent('drop', { dataTransfer })
|
||||
|
||||
const actual = await extractFileFromDragEvent(event)
|
||||
expect(actual).toBe(fileWithWorkflowMaybeWhoKnows)
|
||||
const actual = await extractFilesFromDragEvent(
|
||||
new FakeDragEvent('drop', { dataTransfer })
|
||||
)
|
||||
expect(actual).toEqual([file])
|
||||
})
|
||||
|
||||
it('should handle drops with multiple image files', async () => {
|
||||
it('should return multiple files from dataTransfer', async () => {
|
||||
const file1 = new File([new Uint8Array()], 'workflow1.json', {
|
||||
type: 'application/json'
|
||||
})
|
||||
const file2 = new File([new Uint8Array()], 'workflow2.json', {
|
||||
type: 'application/json'
|
||||
})
|
||||
const dataTransfer = new DataTransfer()
|
||||
dataTransfer.items.add(file1)
|
||||
dataTransfer.items.add(file2)
|
||||
|
||||
const actual = await extractFilesFromDragEvent(
|
||||
new FakeDragEvent('drop', { dataTransfer })
|
||||
)
|
||||
expect(actual).toEqual([file1, file2])
|
||||
})
|
||||
|
||||
it('should filter out bmp files', async () => {
|
||||
const jsonFile = new File([new Uint8Array()], 'workflow.json', {
|
||||
type: 'application/json'
|
||||
})
|
||||
const bmpFile = new File([new Uint8Array()], 'image.bmp', {
|
||||
type: 'image/bmp'
|
||||
})
|
||||
const dataTransfer = new DataTransfer()
|
||||
dataTransfer.items.add(jsonFile)
|
||||
dataTransfer.items.add(bmpFile)
|
||||
|
||||
const actual = await extractFilesFromDragEvent(
|
||||
new FakeDragEvent('drop', { dataTransfer })
|
||||
)
|
||||
expect(actual).toEqual([jsonFile])
|
||||
})
|
||||
|
||||
it('should return multiple image files from dataTransfer', async () => {
|
||||
const imageFile1 = new File([new Uint8Array()], 'image1.png', {
|
||||
type: 'image/png'
|
||||
})
|
||||
@@ -45,16 +74,13 @@ describe('eventUtils', () => {
|
||||
dataTransfer.items.add(imageFile1)
|
||||
dataTransfer.items.add(imageFile2)
|
||||
|
||||
const event = new FakeDragEvent('drop', { dataTransfer })
|
||||
|
||||
const actual = await extractFileFromDragEvent(event)
|
||||
expect(actual).toBeDefined()
|
||||
expect((actual as FileList).length).toBe(2)
|
||||
expect((actual as FileList)[0]).toBe(imageFile1)
|
||||
expect((actual as FileList)[1]).toBe(imageFile2)
|
||||
const actual = await extractFilesFromDragEvent(
|
||||
new FakeDragEvent('drop', { dataTransfer })
|
||||
)
|
||||
expect(actual).toEqual([imageFile1, imageFile2])
|
||||
})
|
||||
|
||||
it('should return undefined when dropping multiple non-image files', async () => {
|
||||
it('should return multiple non-image files from dataTransfer', async () => {
|
||||
const file1 = new File([new Uint8Array()], 'file1.txt', {
|
||||
type: 'text/plain'
|
||||
})
|
||||
@@ -66,10 +92,10 @@ describe('eventUtils', () => {
|
||||
dataTransfer.items.add(file1)
|
||||
dataTransfer.items.add(file2)
|
||||
|
||||
const event = new FakeDragEvent('drop', { dataTransfer })
|
||||
|
||||
const actual = await extractFileFromDragEvent(event)
|
||||
expect(actual).toBe(undefined)
|
||||
const actual = await extractFilesFromDragEvent(
|
||||
new FakeDragEvent('drop', { dataTransfer })
|
||||
)
|
||||
expect(actual).toEqual([file1, file2])
|
||||
})
|
||||
|
||||
// Skip until we can setup MSW
|
||||
@@ -77,14 +103,14 @@ describe('eventUtils', () => {
|
||||
const urlWithWorkflow = 'https://fakewebsite.notreal/fake_workflow.json'
|
||||
|
||||
const dataTransfer = new DataTransfer()
|
||||
|
||||
dataTransfer.setData('text/uri-list', urlWithWorkflow)
|
||||
dataTransfer.setData('text/x-moz-url', urlWithWorkflow)
|
||||
|
||||
const event = new FakeDragEvent('drop', { dataTransfer })
|
||||
|
||||
const actual = await extractFileFromDragEvent(event)
|
||||
expect(actual).toBeInstanceOf(File)
|
||||
const actual = await extractFilesFromDragEvent(
|
||||
new FakeDragEvent('drop', { dataTransfer })
|
||||
)
|
||||
expect(actual.length).toBe(1)
|
||||
expect(actual[0]).toBeInstanceOf(File)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
export async function extractFileFromDragEvent(
|
||||
export async function extractFilesFromDragEvent(
|
||||
event: DragEvent
|
||||
): Promise<File | FileList | undefined> {
|
||||
if (!event.dataTransfer) return
|
||||
): Promise<File[]> {
|
||||
if (!event.dataTransfer) return []
|
||||
|
||||
const { files } = event.dataTransfer
|
||||
// Dragging from Chrome->Firefox there is a file, but it's a bmp, so ignore it
|
||||
if (files.length === 1 && files[0].type !== 'image/bmp') {
|
||||
return files[0]
|
||||
} else if (files.length > 1 && Array.from(files).every(hasImageType)) {
|
||||
return files
|
||||
}
|
||||
// Dragging from Chrome->Firefox there is a file but its a bmp, so ignore that
|
||||
const files = Array.from(event.dataTransfer.files).filter(
|
||||
(file) => file.type !== 'image/bmp'
|
||||
)
|
||||
|
||||
if (files.length > 0) return files
|
||||
|
||||
// Try loading the first URI in the transfer list
|
||||
const validTypes = ['text/uri-list', 'text/x-moz-url']
|
||||
const match = [...event.dataTransfer.types].find((t) =>
|
||||
validTypes.includes(t)
|
||||
)
|
||||
if (!match) return
|
||||
if (!match) return []
|
||||
|
||||
const uri = event.dataTransfer.getData(match)?.split('\n')?.[0]
|
||||
if (!uri) return
|
||||
if (!uri) return []
|
||||
|
||||
const response = await fetch(uri)
|
||||
const blob = await response.blob()
|
||||
return new File([blob], uri, { type: blob.type })
|
||||
return [new File([blob], uri, { type: blob.type })]
|
||||
}
|
||||
|
||||
function hasImageType({ type }: File): boolean {
|
||||
export function hasImageType({ type }: File): boolean {
|
||||
return type.startsWith('image')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user