Files
ComfyUI_frontend/src/composables/node/useNodeDragAndDrop.ts
AustinMroz c16f10b49e Long workflow name cleanup (#13180)
When loading a workflow by dragging and dropping an output from the
assets sidebar, the very long and unhelpful url would be used as the
workflow name. This is fixed by instead using the asset display name
| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/5c68ae48-1fa6-40e1-b2fb-6188ccd60391"/>
| <img width="360" alt="after"
src="https://github.com/user-attachments/assets/29770c35-da48-4be9-943e-8ee69eb25e6a"
/>|


Additionally, a max width is added to the breadcrumb items to avoid
extremely long names.
| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/508155ec-81d7-4ca5-8910-f42a70c9cb4b"/>
| <img width="360" alt="after"
src="https://github.com/user-attachments/assets/d335ceb7-bfeb-481f-a132-c700e017ee0c"
/>|
2026-06-26 23:11:40 +00:00

95 lines
2.9 KiB
TypeScript

import { useChainCallback } from '@/composables/functional/useChainCallback'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { parseAssetInfo } from '@/platform/assets/schemas/mediaAssetSchema'
import type { ResultItem } from '@/schemas/apiSchema'
type DragHandler = (e: DragEvent) => boolean
type DropHandler<T> = (files: File[]) => Promise<T[]>
interface DragAndDropOptions<T> {
onDragOver?: DragHandler
onDrop: DropHandler<T>
onResultItemDrop?: (item: ResultItem) => void
fileFilter?: (file: File) => boolean
}
/**
* Adds drag and drop file handling to a node
* Will also resolve 'text/uri-list' to a file before passing
*/
export const useNodeDragAndDrop = <T>(
node: LGraphNode,
options: DragAndDropOptions<T>
) => {
const { onDragOver, onDrop, fileFilter = () => true } = options
const hasFiles = (items: DataTransferItemList) =>
!!Array.from(items).find((f) => f.kind === 'file')
const filterFiles = (files: FileList | File[]) =>
Array.from(files).filter(fileFilter)
const hasValidFiles = (files: FileList) => filterFiles(files).length > 0
const isDraggingFiles = (e: DragEvent | undefined) => {
if (!e?.dataTransfer?.items) return false
return (
onDragOver?.(e) ??
(hasFiles(e.dataTransfer.items) ||
e?.dataTransfer?.types?.includes('text/uri-list'))
)
}
const isDraggingValidFiles = (e: DragEvent | undefined) => {
if (e?.dataTransfer?.files?.length)
return hasValidFiles(e.dataTransfer.files)
return !!e?.dataTransfer?.getData('text/uri-list')
}
const installedDragOver = isDraggingFiles
node.onDragOver = installedDragOver
const installedDragDrop = async function (e: DragEvent) {
if (!isDraggingValidFiles(e)) return false
const files = filterFiles(e.dataTransfer!.files)
if (files.length) {
await onDrop(files)
return true
}
const asset = parseAssetInfo(e.dataTransfer!)
if (asset?.filename && options.onResultItemDrop) {
await options.onResultItemDrop(asset)
return true
}
const baseUri = e?.dataTransfer?.getData('text/uri-list') ?? ''
const uri = URL.parse(baseUri, location.href)
if (!uri || uri.origin !== location.origin) return false
try {
const resp = await fetch(uri)
const fileName =
uri?.searchParams?.get('filename') ?? baseUri.split('/').at(-1)
if (!fileName || !resp.ok) return false
const blob = await resp.blob()
const file = new File([blob], fileName, { type: blob.type })
const uriFiles = filterFiles([file])
if (!uriFiles.length) return false
await onDrop(uriFiles)
} catch {
return false
}
return true
}
node.onDragDrop = installedDragDrop
node.onRemoved = useChainCallback(node.onRemoved, () => {
if (node.onDragOver === installedDragOver) node.onDragOver = undefined
if (node.onDragDrop === installedDragDrop) node.onDragDrop = undefined
})
}