Merge branch 'dev' into feat/basic-belief-nodes

This commit is contained in:
Björn Otgaar
2025-12-15 14:47:01 +01:00
15 changed files with 639 additions and 109 deletions

View File

@@ -39,6 +39,7 @@ const selector = (state: FlowState) => ({
nodes: state.nodes,
edges: state.edges,
onNodesChange: state.onNodesChange,
onEdgesDelete: state.onEdgesDelete,
onEdgesChange: state.onEdgesChange,
onConnect: state.onConnect,
onReconnectStart: state.onReconnectStart,
@@ -62,6 +63,7 @@ const VisProgUI = () => {
const {
nodes, edges,
onNodesChange,
onEdgesDelete,
onEdgesChange,
onConnect,
onReconnect,
@@ -91,6 +93,7 @@ const VisProgUI = () => {
defaultEdgeOptions={DEFAULT_EDGE_OPTIONS}
nodeTypes={NodeTypes}
onNodesChange={onNodesChange}
onEdgesDelete={onEdgesDelete}
onEdgesChange={onEdgesChange}
onReconnect={onReconnect}
onReconnectStart={onReconnectStart}

View File

@@ -1,14 +1,50 @@
import StartNode, { StartConnects, StartReduce } from "./nodes/StartNode";
import EndNode, { EndConnects, EndReduce } from "./nodes/EndNode";
import PhaseNode, { PhaseConnects, PhaseReduce } from "./nodes/PhaseNode";
import NormNode, { NormConnects, NormReduce } from "./nodes/NormNode";
import EndNode, {
EndConnectionTarget,
EndConnectionSource,
EndDisconnectionTarget,
EndDisconnectionSource,
EndReduce
} from "./nodes/EndNode";
import { EndNodeDefaults } from "./nodes/EndNode.default";
import StartNode, {
StartConnectionTarget,
StartConnectionSource,
StartDisconnectionTarget,
StartDisconnectionSource,
StartReduce
} from "./nodes/StartNode";
import { StartNodeDefaults } from "./nodes/StartNode.default";
import PhaseNode, {
PhaseConnectionTarget,
PhaseConnectionSource,
PhaseDisconnectionTarget,
PhaseDisconnectionSource,
PhaseReduce
} from "./nodes/PhaseNode";
import { PhaseNodeDefaults } from "./nodes/PhaseNode.default";
import NormNode, {
NormConnectionTarget,
NormConnectionSource,
NormDisconnectionTarget,
NormDisconnectionSource,
NormReduce
} from "./nodes/NormNode";
import { NormNodeDefaults } from "./nodes/NormNode.default";
import GoalNode, { GoalConnects, GoalReduce } from "./nodes/GoalNode";
import GoalNode, {
GoalConnectionTarget,
GoalConnectionSource,
GoalDisconnectionTarget,
GoalDisconnectionSource,
GoalReduce
} from "./nodes/GoalNode";
import { GoalNodeDefaults } from "./nodes/GoalNode.default";
import TriggerNode, { TriggerConnects, TriggerReduce } from "./nodes/TriggerNode";
import TriggerNode, {
TriggerConnectionTarget,
TriggerConnectionSource,
TriggerDisconnectionTarget,
TriggerDisconnectionSource,
TriggerReduce
} from "./nodes/TriggerNode";
import { TriggerNodeDefaults } from "./nodes/TriggerNode.default";
import BasicBeliefNode, { BasicBeliefConnects, BasicBeliefReduce } from "./nodes/BasicBeliefNode";
import { BasicBeliefNodeDefaults } from "./nodes/BasicBeliefNode.default";
@@ -65,16 +101,51 @@ export const NodeReduces = {
/**
* Connection functions for each node type.
*
* These functions define how nodes of a particular type can connect to other nodes.
* These functions define any additional actions a node may perform
* when a new connection is made
*/
export const NodeConnects = {
start: StartConnects,
end: EndConnects,
phase: PhaseConnects,
norm: NormConnects,
goal: GoalConnects,
trigger: TriggerConnects,
basic_belief: BasicBeliefConnects,
export const NodeConnections = {
Targets: {
start: StartConnectionTarget,
end: EndConnectionTarget,
phase: PhaseConnectionTarget,
norm: NormConnectionTarget,
goal: GoalConnectionTarget,
trigger: TriggerConnectionTarget,
},
Sources: {
start: StartConnectionSource,
end: EndConnectionSource,
phase: PhaseConnectionSource,
norm: NormConnectionSource,
goal: GoalConnectionSource,
trigger: TriggerConnectionSource,
}
}
/**
* Disconnection functions for each node type.
*
* These functions define any additional actions a node may perform
* when a connection is disconnected
*/
export const NodeDisconnections = {
Targets: {
start: StartDisconnectionTarget,
end: EndDisconnectionTarget,
phase: PhaseDisconnectionTarget,
norm: NormDisconnectionTarget,
goal: GoalDisconnectionTarget,
trigger: TriggerDisconnectionTarget,
},
Sources: {
start: StartDisconnectionSource,
end: EndDisconnectionSource,
phase: PhaseDisconnectionSource,
norm: NormDisconnectionSource,
goal: GoalDisconnectionSource,
trigger: TriggerDisconnectionSource,
},
}
/**

View File

@@ -9,7 +9,12 @@ import {
type XYPosition,
} from '@xyflow/react';
import type { FlowState } from './VisProgTypes';
import { NodeDefaults, NodeConnects, NodeDeletes } from './NodeRegistry';
import {
NodeDefaults,
NodeConnections as NodeCs,
NodeDisconnections as NodeDs,
NodeDeletes
} from './NodeRegistry';
import { UndoRedo } from "./EditorUndoRedo.ts";
@@ -73,10 +78,25 @@ const useFlowStore = create<FlowState>(UndoRedo((set, get) => ({
*/
onNodesChange: (changes) => set({nodes: applyNodeChanges(changes, get().nodes)}),
onEdgesDelete: (edges) => {
// we make sure any affected nodes get updated to reflect removal of edges
edges.forEach((edge) => {
const nodes = get().nodes;
const sourceNode = nodes.find((n) => n.id == edge.source);
const targetNode = nodes.find((n) => n.id == edge.target);
if (sourceNode) { NodeDs.Sources[sourceNode.type as keyof typeof NodeDs.Sources](sourceNode, edge.target); }
if (targetNode) { NodeDs.Targets[targetNode.type as keyof typeof NodeDs.Targets](targetNode, edge.source); }
});
},
/**
* Handles changes to edges triggered by ReactFlow.
*/
onEdgesChange: (changes) => set({ edges: applyEdgeChanges(changes, get().edges) }),
onEdgesChange: (changes) => {
set({ edges: applyEdgeChanges(changes, get().edges) })
},
/**
* Handles creating a new connection between nodes.
@@ -84,32 +104,16 @@ const useFlowStore = create<FlowState>(UndoRedo((set, get) => ({
*/
onConnect: (connection) => {
get().pushSnapshot();
set({edges: addEdge(connection, get().edges)});
const edges = addEdge(connection, get().edges);
// We make sure to perform any required data updates on the newly connected nodes
const nodes = get().nodes;
// connection has: { source, sourceHandle, target, targetHandle }
// Let's find the source and target ID's.
const sourceNode = nodes.find((n) => n.id == connection.source);
const 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.
const sourceConnectFunction = NodeConnects[sourceNode.type as keyof typeof NodeConnects]
const targetConnectFunction = NodeConnects[targetNode.type as keyof typeof NodeConnects]
// 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 });
if (sourceNode) { NodeCs.Sources[sourceNode.type as keyof typeof NodeCs.Sources](sourceNode, connection.target); }
if (targetNode) { NodeCs.Targets[targetNode.type as keyof typeof NodeCs.Targets](targetNode, connection.source); }
},
/**
@@ -118,6 +122,22 @@ const useFlowStore = create<FlowState>(UndoRedo((set, get) => ({
onReconnect: (oldEdge, newConnection) => {
get().edgeReconnectSuccessful = true;
set({ edges: reconnectEdge(oldEdge, newConnection, get().edges) });
// We make sure to perform any required data updates on the newly reconnected nodes
const nodes = get().nodes;
const oldSourceNode = nodes.find((n) => n.id == oldEdge.source)!;
const oldTargetNode = nodes.find((n) => n.id == oldEdge.target)!;
const newSourceNode = nodes.find((n) => n.id == newConnection.source)!;
const newTargetNode = nodes.find((n) => n.id == newConnection.target)!;
if (oldSourceNode === newSourceNode && oldTargetNode === newTargetNode) return;
NodeCs.Sources[newSourceNode.type as keyof typeof NodeCs.Sources](newSourceNode, newConnection.target);
NodeCs.Targets[newTargetNode.type as keyof typeof NodeCs.Targets](newTargetNode, newConnection.source);
NodeDs.Sources[oldSourceNode.type as keyof typeof NodeDs.Sources](oldSourceNode, oldEdge.target);
NodeDs.Targets[oldTargetNode.type as keyof typeof NodeDs.Targets](oldTargetNode, oldEdge.source);
},
onReconnectStart: () => {
@@ -130,11 +150,21 @@ const useFlowStore = create<FlowState>(UndoRedo((set, get) => ({
* if it is not reconnected to a node after detaching it
*
* @param _evt - the event
* @param {{id: string}} edge - the described edge
* @param edge - the described edge
*/
onReconnectEnd: (_evt, edge) => {
if (!get().edgeReconnectSuccessful) {
// delete the edge from the flowState
set({ edges: get().edges.filter((e) => e.id !== edge.id) });
// update node data to reflect the dropped edge
const nodes = get().nodes;
const sourceNode = nodes.find((n) => n.id == edge.source)!;
const targetNode = nodes.find((n) => n.id == edge.target)!;
NodeDs.Sources[sourceNode.type as keyof typeof NodeDs.Sources](sourceNode, edge.target);
NodeDs.Targets[targetNode.type as keyof typeof NodeDs.Targets](targetNode, edge.source);
}
set({ edgeReconnectSuccessful: true });
},

View File

@@ -1,5 +1,5 @@
// VisProgTypes.ts
import type { Edge, OnNodesChange, OnEdgesChange, OnConnect, OnReconnect, Node } from '@xyflow/react';
import type {Edge, OnNodesChange, OnEdgesChange, OnConnect, OnReconnect, Node, OnEdgesDelete} from '@xyflow/react';
import type { NodeTypes } from './NodeRegistry';
import type {FlowSnapshot} from "./EditorUndoRedo.ts";
@@ -27,6 +27,8 @@ export type FlowState = {
/** Handler for changes to nodes triggered by ReactFlow */
onNodesChange: OnNodesChange;
onEdgesDelete: OnEdgesDelete;
/** Handler for changes to edges triggered by ReactFlow */
onEdgesChange: OnEdgesChange;
@@ -44,7 +46,7 @@ export type FlowState = {
* @param _ - event or unused parameter
* @param edge - the edge that finished reconnecting
*/
onReconnectEnd: (_: unknown, edge: { id: string }) => void;
onReconnectEnd: (_: unknown, edge: Edge) => void;
/**
* Deletes a node and any connected edges.

View File

@@ -5,7 +5,8 @@ import {
type Node,
} from '@xyflow/react';
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
import styles from '../../VisProg.module.css';
/**
* The typing of this node's data
@@ -51,10 +52,37 @@ export function EndReduce(node: Node, _nodes: Node[]) {
}
/**
* Any connection functionality that should get called when a connection is made to this node type (end)
* @param _thisNode the node of which the functionality gets called
* @param _otherNode the other node which has connected
* @param _isThisSource whether this node is the one that is the source of the connection
* This function is called whenever a connection is made with this node type as the target
* @param _thisNode the node of this node type which function is called
* @param _sourceNodeId the source of the received connection
*/
export function EndConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
export function EndConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is made with this node type as the source
* @param _thisNode the node of this node type which function is called
* @param _targetNodeId the target of the created connection
*/
export function EndConnectionSource(_thisNode: Node, _targetNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is disconnected with this node type as the target
* @param _thisNode the node of this node type which function is called
* @param _sourceNodeId the source of the disconnected connection
*/
export function EndDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is disconnected with this node type as the source
* @param _thisNode the node of this node type which function is called
* @param _targetNodeId the target of the diconnected connection
*/
export function EndDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
// no additional connection logic exists yet
}

View File

@@ -75,8 +75,8 @@ export default function GoalNode({id, data}: NodeProps<GoalNode>) {
/**
* Reduces each Goal, including its children down into its relevant data.
* @param node: The Node Properties of this node.
* @param _nodes: all the nodes in the graph
* @param node The Node Properties of this node.
* @param _nodes all the nodes in the graph
*/
export function GoalReduce(node: Node, _nodes: Node[]) {
const data = node.data as GoalNodeData;
@@ -89,11 +89,37 @@ export function GoalReduce(node: Node, _nodes: Node[]) {
}
/**
* This function is called whenever a connection is made with this node type (Goal)
* This function is called whenever a connection is made with this node type as the target
* @param _thisNode the node of this node type which function is called
* @param _otherNode the other node which was part of the connection
* @param _isThisSource whether this instance of the node was the source in the connection, true = yes.
* @param _sourceNodeId the source of the received connection
*/
export function GoalConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
// Replace this for connection logic
export function GoalConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is made with this node type as the source
* @param _thisNode the node of this node type which function is called
* @param _targetNodeId the target of the created connection
*/
export function GoalConnectionSource(_thisNode: Node, _targetNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is disconnected with this node type as the target
* @param _thisNode the node of this node type which function is called
* @param _sourceNodeId the source of the disconnected connection
*/
export function GoalDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is disconnected with this node type as the source
* @param _thisNode the node of this node type which function is called
* @param _targetNodeId the target of the diconnected connection
*/
export function GoalDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
// no additional connection logic exists yet
}

View File

@@ -75,8 +75,8 @@ export default function NormNode(props: NodeProps<NormNode>) {
/**
* Reduces each Norm, including its children down into its relevant data.
* @param node: The Node Properties of this node.
* @param _nodes: all the nodes in the graph
* @param node The Node Properties of this node.
* @param _nodes all the nodes in the graph
*/
export function NormReduce(node: Node, _nodes: Node[]) {
const data = node.data as NormNodeData;
@@ -89,10 +89,37 @@ export function NormReduce(node: Node, _nodes: Node[]) {
}
/**
* This function is called whenever a connection is made with this node type (Norm)
* This function is called whenever a connection is made with this node type as the target
* @param _thisNode the node of this node type which function is called
* @param _otherNode the other node which was part of the connection
* @param _isThisSource whether this instance of the node was the source in the connection, true = yes.
* @param _sourceNodeId the source of the received connection
*/
export function NormConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
export function NormConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is made with this node type as the source
* @param _thisNode the node of this node type which function is called
* @param _targetNodeId the target of the created connection
*/
export function NormConnectionSource(_thisNode: Node, _targetNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is disconnected with this node type as the target
* @param _thisNode the node of this node type which function is called
* @param _sourceNodeId the source of the disconnected connection
*/
export function NormDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is disconnected with this node type as the source
* @param _thisNode the node of this node type which function is called
* @param _targetNodeId the target of the diconnected connection
*/
export function NormDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
// no additional connection logic exists yet
}

View File

@@ -2,11 +2,11 @@ import {
Handle,
type NodeProps,
Position,
type Node,
type Node
} from '@xyflow/react';
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
import { NodeReduces, NodesInPhase, NodeTypes } from '../NodeRegistry';
import { NodeReduces, NodesInPhase, NodeTypes} from '../NodeRegistry';
import useFlowStore from '../VisProgStores';
import { TextField } from '../../../../components/TextField';
@@ -104,14 +104,45 @@ export function PhaseReduce(node: Node, nodes: Node[]) {
}
/**
* This function is called whenever a connection is made with this node type (phase)
* @param thisNode the node of this node type which function is called
* @param otherNode the other node which was part of the connection
* @param isThisSource whether this instance of the node was the source in the connection, true = yes.
* This function is called whenever a connection is made with this node type as the target (phase)
* @param _thisNode the node of this node type which function is called
* @param _sourceNodeId the source of the received connection
*/
export function PhaseConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
const node = thisNode as PhaseNode
export function PhaseConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
const node = _thisNode as PhaseNode
const data = node.data as PhaseNodeData
if (!isThisSource)
data.children.push(otherNode.id)
// we only add none phase nodes to the children
if (!(useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && node.type === 'phase'))) {
data.children.push(_sourceNodeId)
}
}
/**
* This function is called whenever a connection is made with this node type as the source (phase)
* @param _thisNode the node of this node type which function is called
* @param _targetNodeId the target of the created connection
*/
export function PhaseConnectionSource(_thisNode: Node, _targetNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is disconnected with this node type as the target (phase)
* @param _thisNode the node of this node type which function is called
* @param _sourceNodeId the source of the disconnected connection
*/
export function PhaseDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
const node = _thisNode as PhaseNode
const data = node.data as PhaseNodeData
data.children = data.children.filter((child) => { if (child != _sourceNodeId) return child; });
}
/**
* This function is called whenever a connection is disconnected with this node type as the source (phase)
* @param _thisNode the node of this node type which function is called
* @param _targetNodeId the target of the diconnected connection
*/
export function PhaseDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
// no additional connection logic exists yet
}

View File

@@ -51,10 +51,37 @@ export function StartReduce(node: Node, _nodes: Node[]) {
}
/**
* This function is called whenever a connection is made with this node type (start)
* This function is called whenever a connection is made with this node type as the target
* @param _thisNode the node of this node type which function is called
* @param _otherNode the other node which was part of the connection
* @param _isThisSource whether this instance of the node was the source in the connection, true = yes.
* @param _sourceNodeId the source of the received connection
*/
export function StartConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
export function StartConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is made with this node type as the source
* @param _thisNode the node of this node type which function is called
* @param _targetNodeId the target of the created connection
*/
export function StartConnectionSource(_thisNode: Node, _targetNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is disconnected with this node type as the target
* @param _thisNode the node of this node type which function is called
* @param _sourceNodeId the source of the disconnected connection
*/
export function StartDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is disconnected with this node type as the source
* @param _thisNode the node of this node type which function is called
* @param _targetNodeId the target of the diconnected connection
*/
export function StartDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
// no additional connection logic exists yet
}

View File

@@ -102,13 +102,39 @@ export function TriggerReduce(node: Node, _nodes: Node[]) {
}
/**
* This function is called whenever a connection is made with this node type (trigger)
* This function is called whenever a connection is made with this node type as the target
* @param _thisNode the node of this node type which function is called
* @param _otherNode the other node which was part of the connection
* @param _isThisSource whether this instance of the node was the source in the connection, true = yes.
* @param _sourceNodeId the source of the received connection
*/
export function TriggerConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
export function TriggerConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is made with this node type as the source
* @param _thisNode the node of this node type which function is called
* @param _targetNodeId the target of the created connection
*/
export function TriggerConnectionSource(_thisNode: Node, _targetNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is disconnected with this node type as the target
* @param _thisNode the node of this node type which function is called
* @param _sourceNodeId the source of the disconnected connection
*/
export function TriggerDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
// no additional connection logic exists yet
}
/**
* This function is called whenever a connection is disconnected with this node type as the source
* @param _thisNode the node of this node type which function is called
* @param _targetNodeId the target of the diconnected connection
*/
export function TriggerDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
// no additional connection logic exists yet
}
// Definitions for the possible triggers, being keywords and emotions