refactor: Initial working framework of node encapsulation works- polymorphic implementation of nodes in creating and connecting calls correct functions
ref: N25B-294
This commit is contained in:
@@ -1,142 +1,127 @@
|
||||
import {create} from 'zustand';
|
||||
import { create } from 'zustand';
|
||||
import {
|
||||
applyNodeChanges,
|
||||
applyEdgeChanges,
|
||||
addEdge,
|
||||
reconnectEdge, type Edge, type Connection
|
||||
reconnectEdge,
|
||||
type Node,
|
||||
type Edge,
|
||||
type NodeChange,
|
||||
type XYPosition,
|
||||
} from '@xyflow/react';
|
||||
import type { FlowState, AppNode } from './VisProgTypes';
|
||||
import { NodeDefaults, NodeConnects } from './NodeRegistry';
|
||||
|
||||
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
|
||||
* Create a node given the correct data
|
||||
* @param type
|
||||
* @param id
|
||||
* @param position
|
||||
* @param data
|
||||
* @constructor
|
||||
*/
|
||||
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'}
|
||||
function createNode(id: string, type: string, position: XYPosition, data: any) {
|
||||
|
||||
const defaultData = Object.entries(NodeDefaults).find(([t, _]) => t == type)?.[1]
|
||||
const newData = {
|
||||
id: id,
|
||||
type: type,
|
||||
position: position,
|
||||
data: data,
|
||||
}
|
||||
|
||||
return (defaultData == undefined) ? newData : ({...defaultData, ...newData})
|
||||
}
|
||||
|
||||
//* Initial nodes, created by using createNodeInstance. */
|
||||
const initialNodes : Node[] = [
|
||||
createNode('start', 'start', {x: 100, y: 100}, {label: "Start"}),
|
||||
createNode('end', 'end', {x: 370, y: 100}, {label: "End"}),
|
||||
createNode('phase-1', 'phase', {x:200, y:100}, {label: "Phase 1", children: ['end', 'start']}),
|
||||
createNode('norms-1', 'norm', {x:-200, y:100}, {label: "Initial Norms", normList: ["Be a robot", "get good"]}),
|
||||
];
|
||||
|
||||
/**
|
||||
* 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',
|
||||
}
|
||||
// * Initial edges * /
|
||||
const initialEdges: Edge[] = [
|
||||
{ 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<FlowState>((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
|
||||
|
||||
onNodesChange: (changes) =>
|
||||
set({nodes: applyNodeChanges(changes, get().nodes)}),
|
||||
onEdgesChange: (changes) => set({ edges: applyEdgeChanges(changes, get().edges) }),
|
||||
|
||||
// Let's make sure we tell the nodes when they're connected, and how it matters.
|
||||
onConnect: (connection) => {
|
||||
set({
|
||||
edges: addEdge(connection, get().edges)
|
||||
});
|
||||
},
|
||||
// handles attempted reconnections of a previously disconnected edge
|
||||
onReconnect: (oldEdge: Edge, newConnection: Connection) => {
|
||||
const edges = addEdge(connection, get().edges);
|
||||
const nodes = get().nodes;
|
||||
// connection has: { source, sourceHandle, target, targetHandle }
|
||||
// Let's find the source and target ID's.
|
||||
let sourceNode = nodes.find((n) => n.id == connection.source);
|
||||
let targetNode = nodes.find((n) => n.id == connection.target);
|
||||
|
||||
// In case the nodes weren't found, return basic functionality.
|
||||
if (sourceNode == undefined || targetNode == undefined || sourceNode.type == undefined || targetNode.type == undefined) {
|
||||
set({ nodes, edges });
|
||||
return;
|
||||
}
|
||||
|
||||
// We should find out how their data changes by calling their respective functions.
|
||||
let sourceConnectFunction = Object.entries(NodeConnects).find(([t, _]) => t == sourceNode.type)?.[1]
|
||||
let targetConnectFunction = Object.entries(NodeConnects).find(([t, _]) => t == targetNode.type)?.[1]
|
||||
if (sourceConnectFunction == undefined || targetConnectFunction == undefined) {
|
||||
set({ nodes, edges });
|
||||
return;
|
||||
}
|
||||
|
||||
// We're going to have to update their data based on how they want to update it.
|
||||
sourceConnectFunction(sourceNode, targetNode, true)
|
||||
targetConnectFunction(targetNode, sourceNode, false)
|
||||
set({ nodes, edges });
|
||||
},
|
||||
|
||||
onReconnect: (oldEdge, newConnection) => {
|
||||
get().edgeReconnectSuccessful = true;
|
||||
set({
|
||||
edges: reconnectEdge(oldEdge, newConnection, get().edges)
|
||||
});
|
||||
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; }) => {
|
||||
|
||||
onReconnectStart: () => set({ edgeReconnectSuccessful: false }),
|
||||
onReconnectEnd: (_evt, edge) => {
|
||||
if (!get().edgeReconnectSuccessful) {
|
||||
set({
|
||||
edges: get().edges.filter((e) => e.id !== edge.id),
|
||||
});
|
||||
set({ edges: get().edges.filter((e) => e.id !== edge.id) });
|
||||
}
|
||||
set({
|
||||
edgeReconnectSuccessful: true
|
||||
});
|
||||
set({ edgeReconnectSuccessful: true });
|
||||
},
|
||||
deleteNode: (nodeId: string) => {
|
||||
|
||||
deleteNode: (nodeId) =>
|
||||
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; }
|
||||
})
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
edges: get().edges.filter((e) => e.source !== nodeId && e.target !== nodeId),
|
||||
}),
|
||||
|
||||
export default useFlowStore;
|
||||
setNodes: (nodes) => set({ nodes }),
|
||||
setEdges: (edges) => set({ edges }),
|
||||
|
||||
updateNodeData: (nodeId, data) => {
|
||||
set({
|
||||
nodes: get().nodes.map((node) => {
|
||||
if (node.id === nodeId) {
|
||||
node.data = { ...node.data, ...data };
|
||||
}
|
||||
return node;
|
||||
}),
|
||||
});
|
||||
},
|
||||
|
||||
addNode: (node: Node) => {
|
||||
set({ nodes: [...get().nodes, node] });
|
||||
},
|
||||
}));
|
||||
|
||||
export default useFlowStore;
|
||||
|
||||
Reference in New Issue
Block a user