@@ -37,14 +37,17 @@ import { hydrateComponentReference } from "@/services/componentService";
3737import {
3838 type ComponentSpec ,
3939 type InputSpec ,
40+ isGraphImplementation ,
4041 isNotMaterializedComponentReference ,
4142 type TaskSpec ,
4243} from "@/utils/componentSpec" ;
4344import { readTextFromFile } from "@/utils/dom" ;
4445import { deselectAllNodes } from "@/utils/flowUtils" ;
4546import createNodesFromComponentSpec from "@/utils/nodes/createNodesFromComponentSpec" ;
47+ import { extractPositionFromAnnotations } from "@/utils/nodes/extractPositionFromAnnotations" ;
4648import {
4749 getSubgraphComponentSpec ,
50+ isSubgraph ,
4851 updateSubgraphSpec ,
4952} from "@/utils/subgraphUtils" ;
5053
@@ -579,6 +582,90 @@ const FlowCanvas = ({
579582 }
580583 }
581584
585+ // Drop a subgraph onto an empty canvas
586+ if (
587+ nodes . length === 0 &&
588+ taskType === "task" &&
589+ droppedTask &&
590+ isSubgraph ( droppedTask ) &&
591+ droppedTask . componentRef . spec &&
592+ isGraphImplementation ( droppedTask . componentRef . spec . implementation )
593+ ) {
594+ const dialogDetails = {
595+ title : "Import Subgraph as Pipeline" ,
596+ description : `Dropping the subgraph "${ droppedTask . componentRef . spec . name } " onto an empty canvas will unpack its internal components. Do you want to proceed?` ,
597+ } ;
598+
599+ const confirmed = await triggerConfirmation ( dialogDetails ) ;
600+
601+ if ( ! confirmed ) {
602+ return ;
603+ }
604+
605+ // Todo: copy over IO values
606+ // Todo: output node connections
607+ // Todo: Move logic into a utility
608+ console . log ( "Unpacking subgraph onto empty canvas" ) ;
609+ console . log ( "Dropped Task:" , droppedTask ) ;
610+
611+ const graphSpec = droppedTask . componentRef . spec ?. implementation . graph ;
612+
613+ let updatedSubgraphSpec = { ...currentSubgraphSpec } ;
614+
615+ Object . entries ( graphSpec . tasks ) . forEach ( ( [ _ , task ] ) => {
616+ const { spec } = addTask (
617+ "task" ,
618+ task ,
619+ extractPositionFromAnnotations ( task . annotations ) ,
620+ updatedSubgraphSpec ,
621+ ) ;
622+
623+ updatedSubgraphSpec = spec ;
624+ } ) ;
625+
626+ droppedTask . componentRef . spec . inputs ?. forEach ( ( input ) => {
627+ const { spec } = addTask (
628+ "input" ,
629+ null ,
630+ extractPositionFromAnnotations ( input . annotations ) ,
631+ updatedSubgraphSpec ,
632+ {
633+ name : input . name ,
634+ type : input . type ,
635+ description : input . description ,
636+ } ,
637+ ) ;
638+
639+ updatedSubgraphSpec = spec ;
640+ } ) ;
641+
642+ droppedTask . componentRef . spec . outputs ?. forEach ( ( output ) => {
643+ const { spec } = addTask (
644+ "output" ,
645+ null ,
646+ extractPositionFromAnnotations ( output . annotations ) ,
647+ updatedSubgraphSpec ,
648+ {
649+ name : output . name ,
650+ type : output . type ,
651+ description : output . description ,
652+ } ,
653+ ) ;
654+
655+ updatedSubgraphSpec = spec ;
656+ } ) ;
657+
658+ const newRootSpec = updateSubgraphSpec (
659+ componentSpec ,
660+ currentSubgraphPath ,
661+ updatedSubgraphSpec ,
662+ ) ;
663+
664+ setComponentSpec ( newRootSpec ) ;
665+
666+ return ;
667+ }
668+
582669 // Replacing an existing node
583670 if ( replaceTarget ) {
584671 if ( ! droppedTask ) {
0 commit comments