mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-04 13:12:10 +00:00
- When in app mode, workflows can be loaded by dragging and dropping as elsewhere. - Dragging a file which is supported by a selected app input to the center panel will apply drop effects on the specific input - This overrides the loading of workflows - There's not currently an indicator for where the image will go. This is being considered for a followup PR - Outputs can be dragged from the assets panel onto nodes - This fixes behaviour outside of app mode as well - Has some thorny implementation specifics - Non-core nodes may not be able to accept these inputs without an update - Node DragOver filtering has reduced functionality when dragging from the assets pane. Nodes may have the blue border without being able to accept a drag operation. - When dropped onto the canvas, the workflow will load (a fix), but the workflow name will be the url of the image preview - The entire card is used for the drag preview <img width="329" height="380" alt="image" src="https://github.com/user-attachments/assets/2945f9a3-3e77-4e14-a812-4a361976390d" /> - Adds a new scroll-shadows tailwind util as an indicator that more content is available by scrolling. - Since a primary goal was preventing API costs overflowing, I've made the indicator fairly strong. This can be tuned later if needed  - Initial support for text outputs in App Mode - Also causes jobs with text outputs to incorrectly display in the assets panel with a generic 'check' icon instead of a text specific icon. This will need a dedicated pass, but shouldn't be overly onerous in the interim. <img width="1209" height="735" alt="text output" src="https://github.com/user-attachments/assets/fcd1cf9f-5d5c-434c-acd0-58d248237b99" /> NOTE: Displaying text outputs conflicted with the changes in #9622. I'll leave text output still disabled in this PR and open a new PR for reconciling text as an output so it can go through dedicated review. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10122-App-Mode-dragAndDrop-text-output-and-scroll-shadows-3256d73d3650810caaf8d75de94388c9) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com>
77 lines
2.1 KiB
TypeScript
77 lines
2.1 KiB
TypeScript
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
|
|
type DragHandler = (e: DragEvent) => boolean
|
|
type DropHandler<T> = (files: File[]) => Promise<T[]>
|
|
|
|
interface DragAndDropOptions<T> {
|
|
onDragOver?: DragHandler
|
|
onDrop: DropHandler<T>
|
|
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')
|
|
}
|
|
|
|
node.onDragOver = isDraggingFiles
|
|
|
|
node.onDragDrop = async function (e: DragEvent) {
|
|
if (!isDraggingValidFiles(e)) return false
|
|
|
|
const files = filterFiles(e.dataTransfer!.files)
|
|
if (files.length) {
|
|
await onDrop(files)
|
|
return true
|
|
}
|
|
|
|
const uri = URL.parse(e?.dataTransfer?.getData('text/uri-list') ?? '')
|
|
if (!uri || uri.origin !== location.origin) return false
|
|
|
|
try {
|
|
const resp = await fetch(uri)
|
|
const fileName = uri?.searchParams?.get('filename')
|
|
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
|
|
}
|
|
}
|