11import { useAppInfo } from "@/lib/hooks/use-app-info" ;
22import { useMenuActions } from "@/lib/hooks/use-menu-actions" ;
3+ import useWorkflow from "@/lib/hooks/use-workflow" ;
34import {
45 AppInfoModalContent ,
56 AppNodeData ,
67 CanvasViewConfig ,
78 MenuAction ,
9+ Workflow ,
810} from "@/lib/types" ;
911import { Button } from "@heroui/react" ;
1012import { Action } from "@pulse-editor/shared-utils" ;
@@ -21,6 +23,7 @@ import {
2123 Edge as ReactFlowEdge ,
2224 Node as ReactFlowNode ,
2325 reconnectEdge ,
26+ useNodes ,
2427 useReactFlow ,
2528 useViewport ,
2629} from "@xyflow/react" ;
@@ -52,36 +55,65 @@ export default function CanvasView({ config }: { config?: CanvasViewConfig }) {
5255 const { openAppInfoModal } = useAppInfo ( ) ;
5356 const { registerMenuAction, unregisterMenuAction } = useMenuActions ( ) ;
5457
58+ const [ workflow , setWorkflow ] = useState < Workflow | undefined > ( undefined ) ;
59+ const [ entryPoint , setEntryPoint ] = useState <
60+ ReactFlowNode < AppNodeData > | undefined
61+ > ( undefined ) ;
62+
63+ const { startWorkflow, runningNodes } = useWorkflow ( workflow , entryPoint ) ;
64+
5565 const viewport = useViewport ( ) ;
5666 const { screenToFlowPosition } = useReactFlow ( ) ;
67+ const nodes = useNodes < ReactFlowNode < AppNodeData > > ( ) ;
5768
5869 const containerRef = useRef < HTMLDivElement > ( null ) ;
5970
60- const [ nodes , setNodes ] = useState < ReactFlowNode < AppNodeData > [ ] > ( [ ] ) ;
61- const [ edges , setEdges ] = useState < ReactFlowEdge [ ] > ( [ ] ) ;
62-
6371 const onNodesChange = useCallback (
6472 ( changes : NodeChange < ReactFlowNode < AppNodeData > > [ ] ) => {
6573 console . log ( "Node changes:" , changes ) ;
66- setNodes ( ( nodesSnapshot ) => applyNodeChanges ( changes , nodesSnapshot ) ) ;
74+ setWorkflow ( ( prev ) => {
75+ if ( ! prev ) return undefined ;
76+ return {
77+ ...prev ,
78+ nodes : applyNodeChanges ( changes , prev . nodes ) ,
79+ } ;
80+ } ) ;
6781 } ,
6882 [ ] ,
6983 ) ;
7084 const onEdgesChange = useCallback (
7185 ( changes : EdgeChange < { id : string ; source : string ; target : string } > [ ] ) => {
7286 console . log ( "Edge changes:" , changes ) ;
73- setEdges ( ( edgesSnapshot ) => applyEdgeChanges ( changes , edgesSnapshot ) ) ;
87+ setWorkflow ( ( prev ) => {
88+ if ( ! prev ) return undefined ;
89+ return {
90+ ...prev ,
91+ edges : applyEdgeChanges ( changes , prev . edges ) ,
92+ } ;
93+ } ) ;
7494 } ,
7595 [ ] ,
7696 ) ;
7797 const onConnect = useCallback (
7898 ( params : any ) =>
79- setEdges ( ( edgesSnapshot ) => addEdge ( params , edgesSnapshot ) ) ,
99+ setWorkflow ( ( prev ) => {
100+ if ( ! prev ) return undefined ;
101+ return {
102+ ...prev ,
103+ edges : addEdge ( params , prev . edges ) ,
104+ } ;
105+ } ) ,
80106 [ ] ,
81107 ) ;
82108 const onReconnect = useCallback (
83109 ( oldEdge : ReactFlowEdge , newConnection : Connection ) =>
84- setEdges ( ( els ) => reconnectEdge ( oldEdge , newConnection , els ) ) ,
110+ setWorkflow ( ( prev ) => {
111+ if ( ! prev ) return undefined ;
112+ return {
113+ ...prev ,
114+ edges : reconnectEdge ( oldEdge , newConnection , prev . edges ) ,
115+ } ;
116+ } ) ,
85117 [ ] ,
86118 ) ;
87119
@@ -120,9 +152,8 @@ export default function CanvasView({ config }: { config?: CanvasViewConfig }) {
120152 reader . onload = ( event ) => {
121153 try {
122154 const workflow = JSON . parse ( event . target ?. result as string ) ;
123- if ( workflow . nodes && workflow . edges ) {
124- setNodes ( workflow . nodes ) ;
125- setEdges ( workflow . edges ) ;
155+ if ( workflow ) {
156+ setWorkflow ( workflow ) ;
126157 } else {
127158 alert ( "Invalid workflow file" ) ;
128159 }
@@ -150,12 +181,29 @@ export default function CanvasView({ config }: { config?: CanvasViewConfig }) {
150181 } ;
151182 } , [ ] ) ;
152183
184+ useEffect ( ( ) => {
185+ const action : MenuAction = {
186+ name : "Run Workflow" ,
187+ menuCategory : "view" ,
188+ description :
189+ "Run the current workflow from the selected or default entry point" ,
190+ shortcut : "Ctrl+Alt+R" ,
191+ actionFunc : async ( ) => {
192+ await startWorkflow ( ) ;
193+ } ,
194+ icon : "play_arrow" ,
195+ } ;
196+
197+ registerMenuAction ( action , true ) ;
198+ } , [ entryPoint ] ) ;
199+
153200 // Add or remove nodes when config changes
154201 useEffect ( ( ) => {
155202 if ( config ) {
156203 // Added nodes
157204 const newNodes = config . nodes ?. filter (
158- ( newNode ) => ! nodes . find ( ( node ) => node . id === newNode . viewId ) ,
205+ ( newNode ) =>
206+ ! workflow ?. nodes . find ( ( node ) => node . id === newNode . viewId ) ,
159207 ) ;
160208
161209 if ( newNodes && newNodes . length > 0 ) {
@@ -179,17 +227,15 @@ export default function CanvasView({ config }: { config?: CanvasViewConfig }) {
179227
180228 const flowCenter = screenToFlowPosition ( screenCenter ) ;
181229
182- return {
183- id : appConfig . viewId ,
184- position : flowCenter ,
185- data : {
186- label : appConfig . app ,
187- config : appConfig ,
188- selectedAction : undefined ,
189- setSelectedAction : async ( action : Action | undefined ) => {
190- // Update the node's data
191- setNodes ( ( nds ) =>
192- nds . map ( ( node ) => {
230+ const appNodeData : AppNodeData = {
231+ config : appConfig ,
232+ selectedAction : undefined ,
233+ setSelectedAction : async ( action : Action | undefined ) => {
234+ setWorkflow ( ( prev ) => {
235+ if ( ! prev ) return undefined ;
236+ return {
237+ ...prev ,
238+ nodes : prev . nodes . map ( ( node ) => {
193239 if ( node . id === appConfig . viewId ) {
194240 return {
195241 ...node ,
@@ -201,37 +247,69 @@ export default function CanvasView({ config }: { config?: CanvasViewConfig }) {
201247 }
202248 return node ;
203249 } ) ,
204- ) ;
205- } ,
206- isRunning : false ,
250+ } ;
251+ } ) ;
207252 } ,
253+ isRunning :
254+ runningNodes ?. find ( ( n ) => n . id === appConfig . viewId ) !==
255+ undefined ,
256+ } ;
257+
258+ return {
259+ id : appConfig . viewId ,
260+ position : flowCenter ,
261+ data : appNodeData ,
208262 type : "appNode" ,
209263 height : appConfig . recommendedHeight ?? 360 ,
210264 width : appConfig . recommendedWidth ?? 640 ,
211265 } ;
212266 } ) ?? [ ] ;
213267
214- setNodes ( ( nds ) => nds . concat ( newAppNodes ) ) ;
268+ // Add new nodes to the workflow
269+ setWorkflow ( ( prev ) => {
270+ if ( ! prev ) {
271+ return {
272+ nodes : newAppNodes ,
273+ edges : [ ] ,
274+ } ;
275+ }
276+ return {
277+ ...prev ,
278+ nodes : prev . nodes . concat ( newAppNodes ) ,
279+ } ;
280+ } ) ;
215281 }
216282
217283 // Removed nodes
218- const removedNodes = nodes . filter (
284+ const removedNodes = workflow ?. nodes . filter (
219285 ( node ) => ! config . nodes ?. find ( ( newNode ) => newNode . viewId === node . id ) ,
220286 ) ;
221287
222288 if ( removedNodes && removedNodes . length > 0 ) {
223- setNodes ( ( nds ) =>
224- nds . filter (
225- ( node ) =>
226- ! removedNodes . find ( ( removedNode ) => removedNode . id === node . id ) ,
227- ) ,
228- ) ;
289+ setWorkflow ( ( prev ) => {
290+ if ( ! prev ) return undefined ;
291+ return {
292+ ...prev ,
293+ nodes : prev . nodes . filter (
294+ ( node ) =>
295+ ! removedNodes . find ( ( removedNode ) => removedNode . id === node . id ) ,
296+ ) ,
297+ } ;
298+ } ) ;
229299 }
230300 }
231- } , [ config , viewport ] ) ;
301+ } , [ config , viewport , runningNodes ] ) ;
302+
303+ useEffect ( ( ) => {
304+ const selectedNodes = nodes . filter ( ( node ) => node . selected ) ;
305+ if ( selectedNodes . length > 0 ) {
306+ setEntryPoint ( selectedNodes [ 0 ] ) ;
307+ } else {
308+ setEntryPoint ( undefined ) ;
309+ }
310+ } , [ nodes ] ) ;
232311
233312 async function exportWorkflow ( ) {
234- const workflow = { nodes, edges } ;
235313 const blob = new Blob ( [ JSON . stringify ( workflow , null , 2 ) ] , {
236314 type : "application/json" ,
237315 } ) ;
@@ -250,8 +328,8 @@ export default function CanvasView({ config }: { config?: CanvasViewConfig }) {
250328 id = { `canvas-${ config ?. viewId } ` }
251329 >
252330 < ReactFlow
253- nodes = { nodes }
254- edges = { edges }
331+ nodes = { workflow ?. nodes }
332+ edges = { workflow ?. edges }
255333 onNodesChange = { onNodesChange }
256334 onEdgesChange = { onEdgesChange }
257335 onConnect = { onConnect }
0 commit comments