mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
feat: batch audio/video file drops into single undo entry
This commit is contained in:
@@ -287,20 +287,21 @@ describe('pasteAudioNodes', () => {
|
||||
const file2 = createAudioFile('file2.wav', 'audio/wav')
|
||||
|
||||
const result = await pasteAudioNodes(mockCanvas, [file1, file2])
|
||||
await result.completion
|
||||
|
||||
expect(createNode).toHaveBeenCalledTimes(2)
|
||||
expect(createNode).toHaveBeenNthCalledWith(1, mockCanvas, 'LoadAudio')
|
||||
expect(createNode).toHaveBeenNthCalledWith(2, mockCanvas, 'LoadAudio')
|
||||
expect(mockNode1.pasteFile).toHaveBeenCalledWith(file1)
|
||||
expect(mockNode2.pasteFile).toHaveBeenCalledWith(file2)
|
||||
expect(result).toEqual([mockNode1, mockNode2])
|
||||
expect(result.nodes).toEqual([mockNode1, mockNode2])
|
||||
})
|
||||
|
||||
it('should handle empty file list', async () => {
|
||||
const result = await pasteAudioNodes(mockCanvas, [])
|
||||
|
||||
expect(createNode).not.toHaveBeenCalled()
|
||||
expect(result).toEqual([])
|
||||
expect(result.nodes).toEqual([])
|
||||
})
|
||||
|
||||
it('should handle single audio file', async () => {
|
||||
@@ -311,7 +312,7 @@ describe('pasteAudioNodes', () => {
|
||||
const result = await pasteAudioNodes(mockCanvas, [file])
|
||||
|
||||
expect(createNode).toHaveBeenCalledTimes(1)
|
||||
expect(result).toEqual([mockNode])
|
||||
expect(result.nodes).toEqual([mockNode])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -383,20 +384,21 @@ describe('pasteVideoNodes', () => {
|
||||
const file2 = createVideoFile('file2.webm', 'video/webm')
|
||||
|
||||
const result = await pasteVideoNodes(mockCanvas, [file1, file2])
|
||||
await result.completion
|
||||
|
||||
expect(createNode).toHaveBeenCalledTimes(2)
|
||||
expect(createNode).toHaveBeenNthCalledWith(1, mockCanvas, 'LoadVideo')
|
||||
expect(createNode).toHaveBeenNthCalledWith(2, mockCanvas, 'LoadVideo')
|
||||
expect(mockNode1.pasteFile).toHaveBeenCalledWith(file1)
|
||||
expect(mockNode2.pasteFile).toHaveBeenCalledWith(file2)
|
||||
expect(result).toEqual([mockNode1, mockNode2])
|
||||
expect(result.nodes).toEqual([mockNode1, mockNode2])
|
||||
})
|
||||
|
||||
it('should handle empty file list', async () => {
|
||||
const result = await pasteVideoNodes(mockCanvas, [])
|
||||
|
||||
expect(createNode).not.toHaveBeenCalled()
|
||||
expect(result).toEqual([])
|
||||
expect(result.nodes).toEqual([])
|
||||
})
|
||||
|
||||
it('should handle single video file', async () => {
|
||||
@@ -407,7 +409,7 @@ describe('pasteVideoNodes', () => {
|
||||
const result = await pasteVideoNodes(mockCanvas, [file])
|
||||
|
||||
expect(createNode).toHaveBeenCalledTimes(1)
|
||||
expect(result).toEqual([mockNode])
|
||||
expect(result.nodes).toEqual([mockNode])
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -139,20 +139,25 @@ export async function pasteAudioNode(
|
||||
export async function pasteAudioNodes(
|
||||
canvas: LGraphCanvas,
|
||||
fileList: File[]
|
||||
): Promise<LGraphNode[]> {
|
||||
): Promise<PasteNodesResult> {
|
||||
const nodes: LGraphNode[] = []
|
||||
const uploads: Promise<void>[] = []
|
||||
|
||||
for (const file of fileList) {
|
||||
const node = await createNode(canvas, 'LoadAudio')
|
||||
if (!node) continue
|
||||
|
||||
nodes.push(node)
|
||||
|
||||
const transfer = new DataTransfer()
|
||||
transfer.items.add(file)
|
||||
const node = await pasteAudioNode(canvas, transfer.items)
|
||||
|
||||
if (node) {
|
||||
nodes.push(node)
|
||||
}
|
||||
uploads.push(pasteItemsOnNode(transfer.items, node, 'audio'))
|
||||
}
|
||||
|
||||
return nodes
|
||||
return {
|
||||
nodes,
|
||||
completion: Promise.all(uploads).then(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
export async function pasteVideoNode(
|
||||
@@ -170,20 +175,25 @@ export async function pasteVideoNode(
|
||||
export async function pasteVideoNodes(
|
||||
canvas: LGraphCanvas,
|
||||
fileList: File[]
|
||||
): Promise<LGraphNode[]> {
|
||||
): Promise<PasteNodesResult> {
|
||||
const nodes: LGraphNode[] = []
|
||||
const uploads: Promise<void>[] = []
|
||||
|
||||
for (const file of fileList) {
|
||||
const node = await createNode(canvas, 'LoadVideo')
|
||||
if (!node) continue
|
||||
|
||||
nodes.push(node)
|
||||
|
||||
const transfer = new DataTransfer()
|
||||
transfer.items.add(file)
|
||||
const node = await pasteVideoNode(canvas, transfer.items)
|
||||
|
||||
if (node) {
|
||||
nodes.push(node)
|
||||
}
|
||||
uploads.push(pasteItemsOnNode(transfer.items, node, 'video'))
|
||||
}
|
||||
|
||||
return nodes
|
||||
return {
|
||||
nodes,
|
||||
completion: Promise.all(uploads).then(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -153,7 +153,10 @@ describe('ComfyApp', () => {
|
||||
it('should create audio nodes and select them', async () => {
|
||||
const mockNode1 = createMockNode({ id: 1, type: 'LoadAudio' })
|
||||
const mockNode2 = createMockNode({ id: 2, type: 'LoadAudio' })
|
||||
vi.mocked(pasteAudioNodes).mockResolvedValue([mockNode1, mockNode2])
|
||||
vi.mocked(pasteAudioNodes).mockResolvedValue({
|
||||
nodes: [mockNode1, mockNode2],
|
||||
completion: Promise.resolve()
|
||||
})
|
||||
|
||||
const file1 = createTestFile('test1.mp3', 'audio/mpeg')
|
||||
const file2 = createTestFile('test2.wav', 'audio/wav')
|
||||
@@ -165,14 +168,21 @@ describe('ComfyApp', () => {
|
||||
mockNode1,
|
||||
mockNode2
|
||||
])
|
||||
expect(mockCanvas.emitBeforeChange).toHaveBeenCalled()
|
||||
expect(mockCanvas.emitAfterChange).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not select when no nodes created', async () => {
|
||||
vi.mocked(pasteAudioNodes).mockResolvedValue([])
|
||||
vi.mocked(pasteAudioNodes).mockResolvedValue({
|
||||
nodes: [],
|
||||
completion: Promise.resolve()
|
||||
})
|
||||
|
||||
await app.handleAudioFileList([createTestFile('test.mp3', 'audio/mpeg')])
|
||||
|
||||
expect(mockCanvas.selectItems).not.toHaveBeenCalled()
|
||||
expect(mockCanvas.emitBeforeChange).toHaveBeenCalled()
|
||||
expect(mockCanvas.emitAfterChange).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -180,7 +190,10 @@ describe('ComfyApp', () => {
|
||||
it('should create video nodes and select them', async () => {
|
||||
const mockNode1 = createMockNode({ id: 1, type: 'LoadVideo' })
|
||||
const mockNode2 = createMockNode({ id: 2, type: 'LoadVideo' })
|
||||
vi.mocked(pasteVideoNodes).mockResolvedValue([mockNode1, mockNode2])
|
||||
vi.mocked(pasteVideoNodes).mockResolvedValue({
|
||||
nodes: [mockNode1, mockNode2],
|
||||
completion: Promise.resolve()
|
||||
})
|
||||
|
||||
const file1 = createTestFile('test1.mp4', 'video/mp4')
|
||||
const file2 = createTestFile('test2.webm', 'video/webm')
|
||||
@@ -192,14 +205,21 @@ describe('ComfyApp', () => {
|
||||
mockNode1,
|
||||
mockNode2
|
||||
])
|
||||
expect(mockCanvas.emitBeforeChange).toHaveBeenCalled()
|
||||
expect(mockCanvas.emitAfterChange).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not select when no nodes created', async () => {
|
||||
vi.mocked(pasteVideoNodes).mockResolvedValue([])
|
||||
vi.mocked(pasteVideoNodes).mockResolvedValue({
|
||||
nodes: [],
|
||||
completion: Promise.resolve()
|
||||
})
|
||||
|
||||
await app.handleVideoFileList([createTestFile('test.mp4', 'video/mp4')])
|
||||
|
||||
expect(mockCanvas.selectItems).not.toHaveBeenCalled()
|
||||
expect(mockCanvas.emitBeforeChange).toHaveBeenCalled()
|
||||
expect(mockCanvas.emitAfterChange).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1746,19 +1746,31 @@ export class ComfyApp {
|
||||
}
|
||||
|
||||
async handleAudioFileList(fileList: File[]) {
|
||||
const audioNodes = await pasteAudioNodes(this.canvas, fileList)
|
||||
if (audioNodes.length === 0) return
|
||||
this.canvas.emitBeforeChange()
|
||||
try {
|
||||
const { nodes, completion } = await pasteAudioNodes(this.canvas, fileList)
|
||||
if (nodes.length === 0) return
|
||||
|
||||
this.positionNodes(audioNodes)
|
||||
this.canvas.selectItems(audioNodes)
|
||||
this.positionNodes(nodes)
|
||||
this.canvas.selectItems(nodes)
|
||||
await completion
|
||||
} finally {
|
||||
this.canvas.emitAfterChange()
|
||||
}
|
||||
}
|
||||
|
||||
async handleVideoFileList(fileList: File[]) {
|
||||
const videoNodes = await pasteVideoNodes(this.canvas, fileList)
|
||||
if (videoNodes.length === 0) return
|
||||
this.canvas.emitBeforeChange()
|
||||
try {
|
||||
const { nodes, completion } = await pasteVideoNodes(this.canvas, fileList)
|
||||
if (nodes.length === 0) return
|
||||
|
||||
this.positionNodes(videoNodes)
|
||||
this.canvas.selectItems(videoNodes)
|
||||
this.positionNodes(nodes)
|
||||
this.canvas.selectItems(nodes)
|
||||
await completion
|
||||
} finally {
|
||||
this.canvas.emitAfterChange()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1769,7 +1781,7 @@ export class ComfyApp {
|
||||
positionNodes(nodes: LGraphNode[]): void {
|
||||
if (nodes.length <= 1) return
|
||||
|
||||
const [x, y] = nodes[0].getBounding()
|
||||
const [x, y] = nodes[0].pos
|
||||
const nodeHeight = 150
|
||||
|
||||
nodes.forEach((node, index) => {
|
||||
|
||||
Reference in New Issue
Block a user