diff --git a/src/pages/VisProgPage/VisProg.tsx b/src/pages/VisProgPage/VisProg.tsx
index 8ccbc7d..486cc7f 100644
--- a/src/pages/VisProgPage/VisProg.tsx
+++ b/src/pages/VisProgPage/VisProg.tsx
@@ -9,8 +9,10 @@ import {
import '@xyflow/react/dist/style.css';
import {useEffect} from "react";
import {useShallow} from 'zustand/react/shallow';
+import orderPhaseNodeArray from "../../utils/orderPhaseNodes.ts";
import useProgramStore from "../../utils/programStore.ts";
import {DndToolbar} from './visualProgrammingUI/components/DragDropSidebar.tsx';
+import type {PhaseNode} from "./visualProgrammingUI/nodes/PhaseNode.tsx";
import useFlowStore from './visualProgrammingUI/VisProgStores.tsx';
import type {FlowState} from './visualProgrammingUI/VisProgTypes.tsx';
import styles from './VisProg.module.css'
@@ -168,14 +170,15 @@ function runProgram() {
*/
function graphReducer() {
const { nodes } = useFlowStore.getState();
- return nodes
- .filter((n) => n.type == 'phase')
+ return orderPhaseNodeArray(nodes.filter((n) => n.type == 'phase') as PhaseNode [])
.map((n) => {
const reducer = NodeReduces['phase'];
return reducer(n, nodes)
});
}
+
+
/**
* houses the entire page, so also UI elements
* that are not a part of the Visual Programming UI
diff --git a/src/pages/VisProgPage/visualProgrammingUI/EditorUndoRedo.ts b/src/pages/VisProgPage/visualProgrammingUI/EditorUndoRedo.ts
index 70c4c01..6ad705d 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/EditorUndoRedo.ts
+++ b/src/pages/VisProgPage/visualProgrammingUI/EditorUndoRedo.ts
@@ -39,10 +39,10 @@ export const UndoRedo = (
* @param {BaseFlowState} state - the current state of the editor
* @returns {FlowSnapshot} - returns a snapshot of the current editor state
*/
- const getSnapshot = (state : BaseFlowState) : FlowSnapshot => ({
+ const getSnapshot = (state : BaseFlowState) : FlowSnapshot => (structuredClone({
nodes: state.nodes,
edges: state.edges
- });
+ }));
const initialState = config(set, get, api);
diff --git a/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx b/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx
index ec85a97..ca746e5 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx
@@ -46,15 +46,12 @@ function createNode(id: string, type: string, position: XYPosition, data: Record
// Start and End don't need to apply the UUID, since they are technically never compiled into a program.
const startNode = createNode('start', 'start', {x: 100, y: 100}, {label: "Start"}, false)
const endNode = createNode('end', 'end', {x: 500, y: 100}, {label: "End"}, false)
- const initialPhaseNode = createNode(crypto.randomUUID(), 'phase', {x:200, y:100}, {label: "Phase 1", children : []})
+ const initialPhaseNode = createNode(crypto.randomUUID(), 'phase', {x:200, y:100}, {label: "Phase 1", children : [], isFirstPhase: false, nextPhaseId: null})
const initialNodes : Node[] = [startNode, endNode, initialPhaseNode,];
- // * Initial edges * /
- const initialEdges: Edge[] = [
- { id: 'start-phase-1', source: startNode.id, target: initialPhaseNode.id },
- { id: 'phase-1-end', source: initialPhaseNode.id, target: endNode.id },
- ];
+// * Initial edges * /
+const initialEdges: Edge[] = []; // no initial edges as edge connect events don't fire when using initial edges
/**
diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/CustomNodeHandles.tsx b/src/pages/VisProgPage/visualProgrammingUI/components/CustomNodeHandles.tsx
new file mode 100644
index 0000000..853c488
--- /dev/null
+++ b/src/pages/VisProgPage/visualProgrammingUI/components/CustomNodeHandles.tsx
@@ -0,0 +1,30 @@
+import {
+ Handle,
+ useNodeConnections,
+ type HandleType,
+ type Position
+} from '@xyflow/react';
+
+
+const LimitedConnectionCountHandle = (props: {
+ node_id: string,
+ type: HandleType,
+ position: Position,
+ connection_count: number,
+ id?: string
+}) => {
+ const connections = useNodeConnections({
+ id: props.node_id,
+ handleType: props.type,
+ handleId: props.id,
+ });
+
+ return (
+
+ );
+};
+
+export default LimitedConnectionCountHandle;
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/EndNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/EndNode.tsx
index 57db571..116dc01 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/EndNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/EndNode.tsx
@@ -1,9 +1,9 @@
import {
- Handle,
type NodeProps,
Position,
type Node,
} from '@xyflow/react';
+import LimitedConnectionCountHandle from "../components/CustomNodeHandles.tsx";
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
@@ -32,7 +32,13 @@ export default function EndNode(props: NodeProps) {
End
-
+
>
);
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts
index 4b4a3ed..8df25cc 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts
@@ -6,6 +6,7 @@ import type { NormNodeData } from "./NormNode";
export const NormNodeDefaults: NormNodeData = {
label: "Norm Node",
droppable: true,
+ conditions: [],
norm: "",
hasReduce: true,
critical: false,
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx
index fb7a251..32922c7 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx
@@ -20,7 +20,10 @@ import { BasicBeliefReduce } from './BasicBeliefNode';
export type NormNodeData = {
label: string;
droppable: boolean;
+<<<<<<< HEAD
condition?: string; // id of this node's belief.
+=======
+>>>>>>> demo
norm: string;
hasReduce: boolean;
critical: boolean;
@@ -70,12 +73,15 @@ export default function NormNode(props: NodeProps) {
/>
+<<<<<<< HEAD
{data.condition && (
)}
-
-
-
+=======
+
+
+
+>>>>>>> demo
>;
};
@@ -89,7 +95,9 @@ export default function NormNode(props: NodeProps) {
export function NormReduce(node: Node, nodes: Node[]) {
const data = node.data as NormNodeData;
- // Build the result object
+<<<<<<< HEAD
+=======
+ // conditions nodes - make sure to check for empty arrays
const result: Record = {
id: node.id,
label: data.label,
@@ -97,6 +105,7 @@ export function NormReduce(node: Node, nodes: Node[]) {
critical: data.critical,
};
+<<<<<<< HEAD
if (data.condition) {
const reducer = BasicBeliefReduce; // TODO: also add inferred.
const conditionNode = nodes.find((node) => node.id === data.condition);
@@ -104,7 +113,6 @@ export function NormReduce(node: Node, nodes: Node[]) {
if (conditionNode == undefined) return result;
result["condition"] = reducer(conditionNode, nodes)
}
-
return result
}
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.default.ts b/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.default.ts
index 0a96d6b..73697eb 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.default.ts
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.default.ts
@@ -8,4 +8,6 @@ export const PhaseNodeDefaults: PhaseNodeData = {
droppable: true,
children: [],
hasReduce: true,
+ nextPhaseId: null,
+ isFirstPhase: false,
};
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.tsx
index d12ad62..51cf6e7 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.tsx
@@ -9,6 +9,7 @@ import styles from '../../VisProg.module.css';
import { NodeReduces, NodesInPhase, NodeTypes} from '../NodeRegistry';
import useFlowStore from '../VisProgStores';
import { TextField } from '../../../../components/TextField';
+import LimitedConnectionCountHandle from "../components/CustomNodeHandles.tsx";
/**
* The default data dot a phase node
@@ -16,12 +17,15 @@ import { TextField } from '../../../../components/TextField';
* @param droppable: whether this node is droppable from the drop bar (initialized as true)
* @param children: ID's of children of this node
* @param hasReduce: whether this node has reducing functionality (true by default)
+ * @param nextPhaseId:
*/
export type PhaseNodeData = {
label: string;
droppable: boolean;
children: string[];
hasReduce: boolean;
+ nextPhaseId: string | "end" | null;
+ isFirstPhase: boolean;
};
export type PhaseNode = Node
@@ -50,9 +54,21 @@ export default function PhaseNode(props: NodeProps) {
placeholder={"Phase ..."}
/>
-
+
-
+
>
);
@@ -65,8 +81,8 @@ export default function PhaseNode(props: NodeProps) {
* @returns A collection of all reduced nodes in this phase, starting with this phases' reduced data.
*/
export function PhaseReduce(node: Node, nodes: Node[]) {
- const thisnode = node as PhaseNode;
- const data = thisnode.data as PhaseNodeData;
+ const thisNode = node as PhaseNode;
+ const data = thisNode.data as PhaseNodeData;
// node typings that are not in phase
const nodesNotInPhase: string[] = Object.entries(NodesInPhase)
@@ -109,13 +125,19 @@ export function PhaseReduce(node: Node, nodes: Node[]) {
* @param _sourceNodeId the source of the received connection
*/
export function PhaseConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
- const node = _thisNode as PhaseNode
- const data = node.data as PhaseNodeData
- // 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)
- }
+ const data = _thisNode.data as PhaseNodeData
+ const nodes = useFlowStore.getState().nodes;
+ const sourceNode = nodes.find((node) => node.id === _sourceNodeId)!
+ switch (sourceNode.type) {
+ case "phase": break;
+ case "start": data.isFirstPhase = true; break;
+ // we only add none phase or start nodes to the children
+ // endNodes cannot be the source of an outgoing connection
+ // so we don't need to cover them with a special case
+ // before handling the default behavior
+ default: data.children.push(_sourceNodeId); break;
+ }
}
/**
@@ -124,7 +146,19 @@ export function PhaseConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
* @param _targetNodeId the target of the created connection
*/
export function PhaseConnectionSource(_thisNode: Node, _targetNodeId: string) {
- // no additional connection logic exists yet
+ const data = _thisNode.data as PhaseNodeData
+ const nodes = useFlowStore.getState().nodes;
+
+ const targetNode = nodes.find((node) => node.id === _targetNodeId)
+ if (!targetNode) {throw new Error("Source node not found")}
+
+ // we set the nextPhaseId to the next target's id if the target is a phaseNode,
+ // or "end" if the target node is the end node
+ switch (targetNode.type) {
+ case 'phase': data.nextPhaseId = _targetNodeId; break;
+ case 'end': data.nextPhaseId = "end"; break;
+ default: break;
+ }
}
/**
@@ -133,9 +167,23 @@ export function PhaseConnectionSource(_thisNode: Node, _targetNodeId: string) {
* @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; });
+ const data = _thisNode.data as PhaseNodeData
+
+ const nodes = useFlowStore.getState().nodes;
+ const sourceNode = nodes.find((node) => node.id === _sourceNodeId)
+ const sourceType = sourceNode ? sourceNode.type : "deleted";
+ switch (sourceType) {
+ case "phase": break;
+ case "start": data.isFirstPhase = false; break;
+ // we only add none phase or start nodes to the children
+ // endNodes cannot be the source of an outgoing connection
+ // so we don't need to cover them with a special case
+ // before handling the default behavior
+ default:
+ data.children = data.children.filter((child) => { if (child != _sourceNodeId) return child; });
+ break;
+ }
+
}
/**
@@ -144,5 +192,12 @@ export function PhaseDisconnectionTarget(_thisNode: Node, _sourceNodeId: string)
* @param _targetNodeId the target of the diconnected connection
*/
export function PhaseDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
- // no additional connection logic exists yet
+ const data = _thisNode.data as PhaseNodeData
+ const nodes = useFlowStore.getState().nodes;
+
+ // if the target is a phase or end node set the nextPhaseId to null,
+ // as we are no longer connected to a subsequent phaseNode or to the endNode
+ if (nodes.some((node) => node.id === _targetNodeId && ['phase', 'end'].includes(node.type!))){
+ data.nextPhaseId = null;
+ }
}
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
index 92ca6ed..13f3fc8 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
@@ -1,9 +1,9 @@
import {
- Handle,
type NodeProps,
Position,
type Node,
} from '@xyflow/react';
+import LimitedConnectionCountHandle from "../components/CustomNodeHandles.tsx";
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
@@ -31,7 +31,13 @@ export default function StartNode(props: NodeProps) {