import {create} from 'zustand'; import { applyNodeChanges, applyEdgeChanges, addEdge, reconnectEdge, type Edge, type Connection } from '@xyflow/react'; import {type AppNode, type FlowState} from './VisProgTypes.tsx'; /** * contains the nodes that are created when the editor is loaded, * should contain at least a start and an end node */ const initialNodes = [ { id: 'start', type: 'start', position: {x: 0, y: 0}, data: {label: 'start'} }, { id: 'phase-1', 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'} } ]; /** * contains the initial edges that are created when the editor is loaded */ const initialEdges = [ { id: 'start-phase-1', source: 'start', target: 'phase-1', }, { id: 'phase-1-end', source: 'phase-1', target: 'end', } ]; /** * The useFlowStore hook contains the implementation for editor functionality and state * we can use this inside our editor component to access the current state * and use any implemented functionality */ const useFlowStore = 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) }); }, // handles connection of newly created 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 }); }, deleteNode: (nodeId: string) => { set({ nodes: get().nodes.filter((n) => n.id !== nodeId), edges: get().edges.filter((e) => e.source !== nodeId && e.target !== nodeId) }); }, setNodes: (nodes) => { set({nodes}); }, setEdges: (edges) => { set({edges}); }, /** * handles updating the data component of a node, * if the provided data object contains entries that aren't present in the updated node's data component * those entries are added to the data component, * entries that do exist within the node's data component, * are simply updated to contain the new value * * the data object * @param {string} nodeId * @param {object} data */ updateNodeData: (nodeId: string, data) => { set({ nodes: get().nodes.map((node) : AppNode => { if (node.id === nodeId) { return { ...node, data: { ...node.data, ...data } }; } else { return node; } }) }); } }), ); export default useFlowStore;