1- import { useResizeObserver } from '@vueuse/core'
1+ import { useEventListener , useResizeObserver } from '@vueuse/core'
22import _ from 'es-toolkit/compat'
33import type { ToastMessageOptions } from 'primevue/toast'
44import { reactive , unref } from 'vue'
@@ -42,12 +42,6 @@ import {
4242 isComboInputSpecV2
4343} from '@/schemas/nodeDefSchema'
4444import { type BaseDOMWidget , DOMWidgetImpl } from '@/scripts/domWidget'
45- import { getFromWebmFile } from '@/scripts/metadata/ebml'
46- import { getGltfBinaryMetadata } from '@/scripts/metadata/gltf'
47- import { getFromIsobmffFile } from '@/scripts/metadata/isobmff'
48- import { getMp3Metadata } from '@/scripts/metadata/mp3'
49- import { getOggMetadata } from '@/scripts/metadata/ogg'
50- import { getSvgMetadata } from '@/scripts/metadata/svg'
5145import { useDialogService } from '@/services/dialogService'
5246import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
5347import { useExtensionService } from '@/services/extensionService'
@@ -89,19 +83,14 @@ import { deserialiseAndCreate } from '@/utils/vintageClipboard'
8983
9084import { type ComfyApi , PromptExecutionError , api } from './api'
9185import { defaultGraph } from './defaultGraph'
92- import {
93- getAvifMetadata ,
94- getFlacMetadata ,
95- getLatentMetadata ,
96- getPngMetadata ,
97- getWebpMetadata ,
98- importA1111
99- } from './pnginfo'
86+ import { importA1111 } from './pnginfo'
10087import { $el , ComfyUI } from './ui'
10188import { ComfyAppMenu } from './ui/menu/index'
10289import { clone } from './utils'
10390import { type ComfyWidgetConstructor } from './widgets'
10491import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale'
92+ import { extractFileFromDragEvent } from '@/utils/eventUtils'
93+ import { getWorkflowDataFromFile } from '@/scripts/metadata/parser'
10594
10695export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
10796
@@ -534,7 +523,7 @@ export class ComfyApp {
534523 */
535524 private addDropHandler ( ) {
536525 // Get prompt from dropped PNG or json
537- document . addEventListener ( 'drop' , async ( event ) => {
526+ useEventListener ( document , 'drop' , async ( event : DragEvent ) => {
538527 try {
539528 event . preventDefault ( )
540529 event . stopPropagation ( )
@@ -543,66 +532,49 @@ export class ComfyApp {
543532 this . dragOverNode = null
544533 // Node handles file drop, we dont use the built in onDropFile handler as its buggy
545534 // If you drag multiple files it will call it multiple times with the same file
546- if ( n && n . onDragDrop && ( await n . onDragDrop ( event ) ) ) {
547- return
548- }
549- // Dragging from Chrome->Firefox there is a file but its a bmp, so ignore that
550- if ( ! event . dataTransfer ) return
551- if (
552- event . dataTransfer . files . length &&
553- event . dataTransfer . files [ 0 ] . type !== 'image/bmp'
554- ) {
555- await this . handleFile ( event . dataTransfer . files [ 0 ] , 'file_drop' )
556- } else {
557- // Try loading the first URI in the transfer list
558- const validTypes = [ 'text/uri-list' , 'text/x-moz-url' ]
559- const match = [ ...event . dataTransfer . types ] . find ( ( t ) =>
560- validTypes . find ( ( v ) => t === v )
561- )
562- if ( match ) {
563- const uri = event . dataTransfer . getData ( match ) ?. split ( '\n' ) ?. [ 0 ]
564- if ( uri ) {
565- const blob = await ( await fetch ( uri ) ) . blob ( )
566- await this . handleFile (
567- new File ( [ blob ] , uri , { type : blob . type } ) ,
568- 'file_drop'
569- )
570- }
571- }
535+ if ( await n ?. onDragDrop ?.( event ) ) return
536+
537+ const fileMaybe = await extractFileFromDragEvent ( event )
538+ if ( ! fileMaybe ) return
539+
540+ const workspace = useWorkspaceStore ( )
541+ try {
542+ workspace . spinner = true
543+ await this . handleFile ( fileMaybe , 'file_drop' )
544+ } finally {
545+ workspace . spinner = false
572546 }
573- } catch ( err : any ) {
574- useToastStore ( ) . addAlert (
575- t ( 'toastMessages.dropFileError' , { error : err } )
576- )
547+ } catch ( error : unknown ) {
548+ useToastStore ( ) . addAlert ( t ( 'toastMessages.dropFileError' , { error } ) )
577549 }
578550 } )
579551
580552 // Always clear over node on drag leave
581- this . canvasEl . addEventListener ( 'dragleave' , async ( ) => {
582- if ( this . dragOverNode ) {
583- this . dragOverNode = null
584- this . graph . setDirtyCanvas ( false , true )
585- }
553+ useEventListener ( this . canvasElRef , 'dragleave' , async ( ) => {
554+ if ( ! this . dragOverNode ) return
555+ this . dragOverNode = null
556+ this . graph . setDirtyCanvas ( false , true )
586557 } )
587558
588559 // Add handler for dropping onto a specific node
589- this . canvasEl . addEventListener (
560+ useEventListener (
561+ this . canvasElRef ,
590562 'dragover' ,
591- ( e ) => {
592- this . canvas . adjustMouseEvent ( e )
593- const node = this . graph . getNodeOnPos ( e . canvasX , e . canvasY )
594- if ( node ) {
595- if ( node . onDragOver && node . onDragOver ( e ) ) {
596- this . dragOverNode = node
597-
598- // dragover event is fired very frequently, run this on an animation frame
599- requestAnimationFrame ( ( ) => {
600- this . graph . setDirtyCanvas ( false , true )
601- } )
602- return
603- }
563+ ( event : DragEvent ) => {
564+ this . canvas . adjustMouseEvent ( event )
565+ const node = this . graph . getNodeOnPos ( event . canvasX , event . canvasY )
566+
567+ if ( ! node ?. onDragOver ?.( event ) ) {
568+ this . dragOverNode = null
569+ return
604570 }
605- this . dragOverNode = null
571+
572+ this . dragOverNode = node
573+
574+ // dragover event is fired very frequently, run this on an animation frame
575+ requestAnimationFrame ( ( ) => {
576+ this . graph . setDirtyCanvas ( false , true )
577+ } )
606578 } ,
607579 false
608580 )
@@ -1417,199 +1389,50 @@ export class ComfyApp {
14171389 * @param {File } file
14181390 */
14191391 async handleFile ( file : File , openSource ?: WorkflowOpenSource ) {
1420- const removeExt = ( f : string ) => {
1421- if ( ! f ) return f
1422- const p = f . lastIndexOf ( '.' )
1423- if ( p === - 1 ) return f
1424- return f . substring ( 0 , p )
1392+ const fileName = file . name . replace ( / \. \w + $ / , '' ) // Strip file extension
1393+ const workflowData = await getWorkflowDataFromFile ( file )
1394+ if ( ! workflowData ) {
1395+ this . showErrorOnFileLoad ( file )
1396+ return
14251397 }
1426- const fileName = removeExt ( file . name )
1427- if ( file . type === 'image/png' ) {
1428- const pngInfo = await getPngMetadata ( file )
1429- if ( pngInfo ?. workflow ) {
1430- await this . loadGraphData (
1431- JSON . parse ( pngInfo . workflow ) ,
1432- true ,
1433- true ,
1434- fileName ,
1435- { openSource }
1436- )
1437- } else if ( pngInfo ?. prompt ) {
1438- this . loadApiJson ( JSON . parse ( pngInfo . prompt ) , fileName )
1439- } else if ( pngInfo ?. parameters ) {
1440- // Note: Not putting this in `importA1111` as it is mostly not used
1441- // by external callers, and `importA1111` has no access to `app`.
1442- useWorkflowService ( ) . beforeLoadNewGraph ( )
1443- importA1111 ( this . graph , pngInfo . parameters )
1444- useWorkflowService ( ) . afterLoadNewGraph (
1445- fileName ,
1446- this . graph . serialize ( ) as unknown as ComfyWorkflowJSON
1447- )
1448- } else {
1449- this . showErrorOnFileLoad ( file )
1450- }
1451- } else if ( file . type === 'image/avif' ) {
1452- const { workflow, prompt } = await getAvifMetadata ( file )
14531398
1454- if ( workflow ) {
1455- this . loadGraphData ( JSON . parse ( workflow ) , true , true , fileName , {
1456- openSource
1457- } )
1458- } else if ( prompt ) {
1459- this . loadApiJson ( JSON . parse ( prompt ) , fileName )
1460- } else {
1461- this . showErrorOnFileLoad ( file )
1462- }
1463- } else if ( file . type === 'image/webp' ) {
1464- const pngInfo = await getWebpMetadata ( file )
1465- // Support loading workflows from that webp custom node.
1466- const workflow = pngInfo ?. workflow || pngInfo ?. Workflow
1467- const prompt = pngInfo ?. prompt || pngInfo ?. Prompt
1468-
1469- if ( workflow ) {
1470- this . loadGraphData ( JSON . parse ( workflow ) , true , true , fileName , {
1471- openSource
1472- } )
1473- } else if ( prompt ) {
1474- this . loadApiJson ( JSON . parse ( prompt ) , fileName )
1475- } else {
1476- this . showErrorOnFileLoad ( file )
1477- }
1478- } else if ( file . type === 'audio/mpeg' ) {
1479- const { workflow, prompt } = await getMp3Metadata ( file )
1480- if ( workflow ) {
1481- this . loadGraphData ( workflow , true , true , fileName , { openSource } )
1482- } else if ( prompt ) {
1483- this . loadApiJson ( prompt , fileName )
1484- } else {
1485- this . showErrorOnFileLoad ( file )
1486- }
1487- } else if ( file . type === 'audio/ogg' ) {
1488- const { workflow, prompt } = await getOggMetadata ( file )
1489- if ( workflow ) {
1490- this . loadGraphData ( workflow , true , true , fileName , { openSource } )
1491- } else if ( prompt ) {
1492- this . loadApiJson ( prompt , fileName )
1493- } else {
1494- this . showErrorOnFileLoad ( file )
1495- }
1496- } else if ( file . type === 'audio/flac' || file . type === 'audio/x-flac' ) {
1497- const pngInfo = await getFlacMetadata ( file )
1498- const workflow = pngInfo ?. workflow || pngInfo ?. Workflow
1499- const prompt = pngInfo ?. prompt || pngInfo ?. Prompt
1500-
1501- if ( workflow ) {
1502- this . loadGraphData ( JSON . parse ( workflow ) , true , true , fileName , {
1503- openSource
1504- } )
1505- } else if ( prompt ) {
1506- this . loadApiJson ( JSON . parse ( prompt ) , fileName )
1507- } else {
1508- this . showErrorOnFileLoad ( file )
1509- }
1510- } else if ( file . type === 'video/webm' ) {
1511- const webmInfo = await getFromWebmFile ( file )
1512- if ( webmInfo . workflow ) {
1513- this . loadGraphData ( webmInfo . workflow , true , true , fileName , {
1514- openSource
1515- } )
1516- } else if ( webmInfo . prompt ) {
1517- this . loadApiJson ( webmInfo . prompt , fileName )
1518- } else {
1519- this . showErrorOnFileLoad ( file )
1520- }
1521- } else if (
1522- file . type === 'video/mp4' ||
1523- file . name ?. endsWith ( '.mp4' ) ||
1524- file . name ?. endsWith ( '.mov' ) ||
1525- file . name ?. endsWith ( '.m4v' ) ||
1526- file . type === 'video/quicktime' ||
1527- file . type === 'video/x-m4v'
1528- ) {
1529- const mp4Info = await getFromIsobmffFile ( file )
1530- if ( mp4Info . workflow ) {
1531- this . loadGraphData ( mp4Info . workflow , true , true , fileName , {
1532- openSource
1533- } )
1534- } else if ( mp4Info . prompt ) {
1535- this . loadApiJson ( mp4Info . prompt , fileName )
1536- }
1537- } else if ( file . type === 'image/svg+xml' || file . name ?. endsWith ( '.svg' ) ) {
1538- const svgInfo = await getSvgMetadata ( file )
1539- if ( svgInfo . workflow ) {
1540- this . loadGraphData ( svgInfo . workflow , true , true , fileName , {
1541- openSource
1542- } )
1543- } else if ( svgInfo . prompt ) {
1544- this . loadApiJson ( svgInfo . prompt , fileName )
1545- } else {
1546- this . showErrorOnFileLoad ( file )
1547- }
1548- } else if (
1549- file . type === 'model/gltf-binary' ||
1550- file . name ?. endsWith ( '.glb' )
1551- ) {
1552- const gltfInfo = await getGltfBinaryMetadata ( file )
1553- if ( gltfInfo . workflow ) {
1554- this . loadGraphData ( gltfInfo . workflow , true , true , fileName , {
1555- openSource
1556- } )
1557- } else if ( gltfInfo . prompt ) {
1558- this . loadApiJson ( gltfInfo . prompt , fileName )
1559- } else {
1560- this . showErrorOnFileLoad ( file )
1561- }
1562- } else if (
1563- file . type === 'application/json' ||
1564- file . name ?. endsWith ( '.json' )
1565- ) {
1566- const reader = new FileReader ( )
1567- reader . onload = async ( ) => {
1568- const readerResult = reader . result as string
1569- const jsonContent = JSON . parse ( readerResult )
1570- if ( jsonContent ?. templates ) {
1571- this . loadTemplateData ( jsonContent )
1572- } else if ( this . isApiJson ( jsonContent ) ) {
1573- this . loadApiJson ( jsonContent , fileName )
1574- } else {
1575- await this . loadGraphData (
1576- JSON . parse ( readerResult ) ,
1577- true ,
1578- true ,
1579- fileName ,
1580- { openSource }
1581- )
1582- }
1583- }
1584- reader . readAsText ( file )
1585- } else if (
1586- file . name ?. endsWith ( '.latent' ) ||
1587- file . name ?. endsWith ( '.safetensors' )
1588- ) {
1589- const info = await getLatentMetadata ( file )
1590- // TODO define schema to LatentMetadata
1591- // @ts -expect-error
1592- if ( info . workflow ) {
1593- await this . loadGraphData (
1594- // @ts -expect-error
1595- JSON . parse ( info . workflow ) ,
1596- true ,
1597- true ,
1598- fileName ,
1599- { openSource }
1600- )
1601- // @ts -expect-error
1602- } else if ( info . prompt ) {
1603- // @ts -expect-error
1604- this . loadApiJson ( JSON . parse ( info . prompt ) )
1605- } else {
1606- this . showErrorOnFileLoad ( file )
1607- }
1608- } else {
1609- this . showErrorOnFileLoad ( file )
1399+ const { workflow, prompt, parameters, templates } = workflowData
1400+
1401+ if ( templates ) {
1402+ this . loadTemplateData ( { templates } )
16101403 }
1404+
1405+ if ( parameters ) {
1406+ // Note: Not putting this in `importA1111` as it is mostly not used
1407+ // by external callers, and `importA1111` has no access to `app`.
1408+ useWorkflowService ( ) . beforeLoadNewGraph ( )
1409+ importA1111 ( this . graph , parameters )
1410+ useWorkflowService ( ) . afterLoadNewGraph (
1411+ fileName ,
1412+ this . graph . serialize ( ) as unknown as ComfyWorkflowJSON
1413+ )
1414+ return
1415+ }
1416+
1417+ if ( workflow ) {
1418+ const workflowObj =
1419+ typeof workflow === 'string' ? JSON . parse ( workflow ) : workflow
1420+ await this . loadGraphData ( workflowObj , true , true , fileName , {
1421+ openSource
1422+ } )
1423+ return
1424+ }
1425+
1426+ if ( prompt ) {
1427+ const promptObj = typeof prompt === 'string' ? JSON . parse ( prompt ) : prompt
1428+ this . loadApiJson ( promptObj , fileName )
1429+ return
1430+ }
1431+
1432+ this . showErrorOnFileLoad ( file )
16111433 }
16121434
1435+ // @deprecated
16131436 isApiJson ( data : unknown ) {
16141437 return _ . isObject ( data ) && Object . values ( data ) . every ( ( v ) => v . class_type )
16151438 }
0 commit comments