diff --git a/src/visualProgrammingUI/VisProgStores.tsx b/src/visualProgrammingUI/VisProgStores.tsx new file mode 100644 index 0000000..594d188 --- /dev/null +++ b/src/visualProgrammingUI/VisProgStores.tsx @@ -0,0 +1,92 @@ +import { create } from 'zustand'; +import { + applyNodeChanges, + applyEdgeChanges, + addEdge, + reconnectEdge, type Edge, type Connection +} from '@xyflow/react'; + +import { type FlowState } from './VisProgTypes.tsx'; + +const initialNodes = [ + { + id: 'start', + type: 'start', + position: {x: 0, y: 0}, + data: {label: 'start'} + }, + { + id: 'genericPhase', + type: 'phase', + position: {x: 0, y: 150}, + data: {label: 'Generic Phase', number: 1}, + }, + { + id: 'end', + type: 'end', + position: {x: 0, y: 300}, + data: {label: 'End'} + } +]; + +const initialEdges = [ + { + id: 'start-end', + source: 'start', + target: 'end' + } +]; + +// this is our useStore hook that we can use in our components to get parts of the store and call actions +const useStore = create((set, get) => ({ + nodes: initialNodes, + edges: initialEdges, + edgeReconnectSuccessful: true, + onNodesChange: (changes) => { + set({ + nodes: applyNodeChanges(changes, get().nodes) + }); + }, + onEdgesChange: (changes) => { + set({ + edges: applyEdgeChanges(changes, get().edges) + }); + }, + onConnect: (connection) => { + set({ + edges: addEdge(connection, get().edges) + }); + }, + // handles attempted reconnections of a previously disconnected edge + onReconnect: (oldEdge: Edge, newConnection: Connection) => { + get().edgeReconnectSuccessful = true; + set({ + edges: reconnectEdge(oldEdge, newConnection, get().edges) + }); + }, + // Handles initiation of reconnection of edges that are manually disconnected from a node + onReconnectStart: () => { + set({ + edgeReconnectSuccessful: false + }); + }, + // Drops the edge from the set of edges, removing it from the flow, if no successful reconnection occurred + onReconnectEnd: (_: unknown, edge: { id: string; }) => { + if (!get().edgeReconnectSuccessful) { + set({ + edges: get().edges.filter((e) => e.id !== edge.id), + }); + } + set({ + edgeReconnectSuccessful: true + }); + }, + setNodes: (nodes) => { + set({ nodes }); + }, + setEdges: (edges) => { + set({ edges }); + }, +})); + +export default useStore; \ No newline at end of file diff --git a/src/visualProgrammingUI/VisProgTypes.tsx b/src/visualProgrammingUI/VisProgTypes.tsx new file mode 100644 index 0000000..a298f6d --- /dev/null +++ b/src/visualProgrammingUI/VisProgTypes.tsx @@ -0,0 +1,25 @@ +import { + type Edge, + type Node, + type OnNodesChange, + type OnEdgesChange, + type OnConnect, + type OnReconnect, +} from '@xyflow/react'; + + +export type AppNode = Node; + +export type FlowState = { + nodes: Node[]; + edges: Edge[]; + edgeReconnectSuccessful: boolean; + onNodesChange: OnNodesChange; + onEdgesChange: OnEdgesChange; + onConnect: OnConnect; + onReconnect: OnReconnect; + onReconnectStart: () => void; + onReconnectEnd: (_: unknown, edge: { id: string }) => void; + setNodes: (nodes: AppNode[]) => void; + setEdges: (edges: Edge[]) => void; +}; \ No newline at end of file diff --git a/src/visualProgrammingUI/VisProgUI.tsx b/src/visualProgrammingUI/VisProgUI.tsx index 3fb2d52..afcea42 100644 --- a/src/visualProgrammingUI/VisProgUI.tsx +++ b/src/visualProgrammingUI/VisProgUI.tsx @@ -1,21 +1,13 @@ import './VisProgUI.css' -import { - useCallback, - useRef -} from 'react'; + import { Background, Controls, ReactFlow, ReactFlowProvider, - useNodesState, - useEdgesState, - reconnectEdge, - addEdge, MarkerType, - type Edge, - type Connection, Panel, + Panel, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import { @@ -27,6 +19,10 @@ import { import { Sidebar } from './components/DragDropSidebar.tsx'; +import useStore from "./VisProgStores.tsx"; +import {useShallow} from "zustand/react/shallow"; +import type {FlowState} from "./VisProgTypes.tsx"; + // --| config starting params for flow |-- @@ -39,28 +35,7 @@ const nodeTypes = { -const initialNodes = [ - { - id: 'start', - type: 'start', - position: {x: 0, y: 0}, - data: {label: 'start'} - }, - { - id: 'genericPhase', - type: 'phase', - position: {x: 0, y: 150}, - data: {label: 'Generic Phase', number: 1}, - }, - { - id: 'end', - type: 'end', - position: {x: 0, y: 300}, - data: {label: 'End'} - } -]; -const initialEdges = [{id: 'start-end', source: 'start', target: 'end'}]; const defaultEdgeOptions = { type: 'default', @@ -70,38 +45,27 @@ const defaultEdgeOptions = { }, }; +const selector = (state: FlowState) => ({ + nodes: state.nodes, + edges: state.edges, + onNodesChange: state.onNodesChange, + onEdgesChange: state.onEdgesChange, + onConnect: state.onConnect, + onReconnectStart: state.onReconnectStart, + onReconnectEnd: state.onReconnectEnd, + onReconnect: state.onReconnect +}); + // --| define ReactFlow editor |-- const VisProgUI = ()=> { - const edgeReconnectSuccessful = useRef(true); - const [nodes, , onNodesChange] = useNodesState(initialNodes); - const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); - // handles connection of newly created edges - const onConnect = useCallback( - (params: Edge | Connection) => setEdges((els) => addEdge(params, els)), - [setEdges], + const { nodes, edges, onNodesChange, onEdgesChange, onConnect, onReconnect, onReconnectStart, onReconnectEnd } = useStore( + useShallow(selector), ); + // handles connection of newly created edges - // Handles initiation of reconnection of edges that are manually disconnected from a node - const onReconnectStart = useCallback(() => { - edgeReconnectSuccessful.current = false; - }, []); - - // handles attempted reconnections of a previously disconnected edge - const onReconnect = useCallback((oldEdge: Edge, newConnection: Connection) => { - edgeReconnectSuccessful.current = true; - setEdges((els) => reconnectEdge(oldEdge, newConnection, els)); - }, [setEdges]); - - // Drops the edge from the set of edges, removing it from the flow, if no successful reconnection occurred - const onReconnectEnd = useCallback((_: unknown, edge: { id: string; }) => { - if (!edgeReconnectSuccessful.current) { - setEdges((eds) => eds.filter((e) => e.id !== edge.id)); - } - edgeReconnectSuccessful.current = true; - }, [setEdges]); return (
@@ -113,11 +77,11 @@ const VisProgUI = ()=> { onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} nodeTypes={nodeTypes} - snapToGrid onReconnect={onReconnect} onReconnectStart={onReconnectStart} onReconnectEnd={onReconnectEnd} onConnect={onConnect} + snapToGrid fitView proOptions={{hideAttribution: true }} >