Refactoring all nodes functionality into their own files, create a modular framework for the visual programming. #21
@@ -141,4 +141,4 @@ function VisProgPage() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VisProgPage
|
export default VisProgPage
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const NodeTypes = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The default functions of the nodes we have registered.
|
* The default functions of the nodes we have registered.
|
||||||
|
* These are defined in the <node>.default.ts files.
|
||||||
*/
|
*/
|
||||||
export const NodeDefaults = {
|
export const NodeDefaults = {
|
||||||
start: StartNodeDefaults,
|
start: StartNodeDefaults,
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ const initialEdges: Edge[] = [
|
|||||||
{ id: 'phase-1-end', source: 'phase-1', target: 'end' },
|
{ id: 'phase-1-end', source: 'phase-1', target: 'end' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How we have defined the functions for our FlowState.
|
||||||
|
* We have the normal functionality of a default FlowState with some exceptions to account for extra functionality.
|
||||||
|
* The biggest changes are in onConnect and onDelete, which we have given extra functionality based on the nodes defined functions.
|
||||||
|
*/
|
||||||
const useFlowStore = create<FlowState>((set, get) => ({
|
const useFlowStore = create<FlowState>((set, get) => ({
|
||||||
nodes: initialNodes,
|
nodes: initialNodes,
|
||||||
edges: initialEdges,
|
edges: initialEdges,
|
||||||
@@ -56,7 +62,6 @@ const useFlowStore = create<FlowState>((set, get) => ({
|
|||||||
set({nodes: applyNodeChanges(changes, get().nodes)}),
|
set({nodes: applyNodeChanges(changes, get().nodes)}),
|
||||||
onEdgesChange: (changes) => set({ edges: applyEdgeChanges(changes, get().edges) }),
|
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) => {
|
onConnect: (connection) => {
|
||||||
const edges = addEdge(connection, get().edges);
|
const edges = addEdge(connection, get().edges);
|
||||||
const nodes = get().nodes;
|
const nodes = get().nodes;
|
||||||
|
|||||||
@@ -47,12 +47,13 @@ function DraggableNode({ className, children, nodeType, onDrop }: DraggableNodeP
|
|||||||
* addNode — adds a new node to the flow using the unified class-based system.
|
* addNode — adds a new node to the flow using the unified class-based system.
|
||||||
* Keeps numbering logic for phase/norm nodes.
|
* Keeps numbering logic for phase/norm nodes.
|
||||||
*/
|
*/
|
||||||
function addNode(nodeType: keyof typeof NodeTypes, position: XYPosition) {
|
function addNode(nodeType: keyof typeof NodeTypes, position: XYPosition) {
|
||||||
const { nodes, setNodes } = useFlowStore.getState();
|
const { nodes, setNodes } = useFlowStore.getState();
|
||||||
const defaultData = NodeDefaults[nodeType]
|
|
||||||
|
|
||||||
if (!defaultData) throw new Error(`Node type '${nodeType}' not found in registry`);
|
|
||||||
|
|
||||||
|
// Find out if there's any default data about our ndoe
|
||||||
|
const defaultData = NodeDefaults[nodeType] ?? {}
|
||||||
|
|
||||||
|
// Currently, we find out what the Id is by checking the last node and adding one
|
||||||
const sameTypeNodes = nodes.filter((node) => node.type === nodeType);
|
const sameTypeNodes = nodes.filter((node) => node.type === nodeType);
|
||||||
const nextNumber =
|
const nextNumber =
|
||||||
sameTypeNodes.length > 0
|
sameTypeNodes.length > 0
|
||||||
@@ -63,9 +64,9 @@ function DraggableNode({ className, children, nodeType, onDrop }: DraggableNodeP
|
|||||||
return Number.isNaN(lastNum) ? sameTypeNodes.length + 1 : lastNum + 1;
|
return Number.isNaN(lastNum) ? sameTypeNodes.length + 1 : lastNum + 1;
|
||||||
})()
|
})()
|
||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
const id = `${nodeType}-${nextNumber}`;
|
const id = `${nodeType}-${nextNumber}`;
|
||||||
|
|
||||||
|
// Create new node
|
||||||
const newNode = {
|
const newNode = {
|
||||||
id: id,
|
id: id,
|
||||||
type: nodeType,
|
type: nodeType,
|
||||||
@@ -104,6 +105,7 @@ export function DndToolbar() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// Map over our default settings to see which of them have their droppable data set to true
|
||||||
const droppableNodes = Object.entries(NodeDefaults)
|
const droppableNodes = Object.entries(NodeDefaults)
|
||||||
.filter(([, data]) => data.droppable)
|
.filter(([, data]) => data.droppable)
|
||||||
.map(([type, data]) => ({
|
.map(([type, data]) => ({
|
||||||
@@ -111,20 +113,16 @@ export function DndToolbar() {
|
|||||||
data
|
data
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex-col gap-lg padding-md ${styles.innerDndPanel}`}>
|
<div className={`flex-col gap-lg padding-md ${styles.innerDndPanel}`}>
|
||||||
<div className="description">
|
<div className="description">
|
||||||
You can drag these nodes to the pane to create new nodes.
|
You can drag these nodes to the pane to create new nodes.
|
||||||
</div>
|
</div>
|
||||||
<div className={`flex-row gap-lg ${styles.dndNodeContainer}`}>
|
<div className={`flex-row gap-lg ${styles.dndNodeContainer}`}>
|
||||||
{
|
{/* Maps over all the nodes that are droppable, and puts them in the panel */}
|
||||||
// Maps over all the nodes that are droppable, and puts them in the panel
|
|
||||||
}
|
|
||||||
{droppableNodes.map(({type, data}) => (
|
{droppableNodes.map(({type, data}) => (
|
||||||
<DraggableNode
|
<DraggableNode
|
||||||
className={styles[`draggable-node-${type}`]}
|
className={styles[`draggable-node-${type}`]} // Our current style signature for nodes
|
||||||
nodeType={type}
|
nodeType={type}
|
||||||
onDrop={handleNodeDrop}
|
onDrop={handleNodeDrop}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function EndReduce(node: Node, nodes: Node[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any connection functionality that should get called when a connection is made to this 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 thisNode the node of which the functionality gets called
|
||||||
* @param otherNode the other node which has connected
|
* @param otherNode the other node which has connected
|
||||||
* @param isThisSource whether this node is the one that is the source of the connection
|
* @param isThisSource whether this node is the one that is the source of the connection
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import {
|
|||||||
Handle,
|
Handle,
|
||||||
type NodeProps,
|
type NodeProps,
|
||||||
Position,
|
Position,
|
||||||
type Connection,
|
|
||||||
type Edge,
|
|
||||||
type Node,
|
type Node,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import { Toolbar } from '../components/NodeComponents';
|
import { Toolbar } from '../components/NodeComponents';
|
||||||
@@ -26,15 +24,9 @@ export type GoalNodeData = {
|
|||||||
hasReduce: boolean;
|
hasReduce: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export type GoalNode = Node<GoalNodeData>
|
export type GoalNode = Node<GoalNodeData>
|
||||||
|
|
||||||
|
|
||||||
export function GoalNodeCanConnect(connection: Connection | Edge): boolean {
|
|
||||||
return (connection != undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines how a Goal node should be rendered
|
* Defines how a Goal node should be rendered
|
||||||
* @param props NodeProps, like id, label, children
|
* @param props NodeProps, like id, label, children
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import {
|
|||||||
Handle,
|
Handle,
|
||||||
type NodeProps,
|
type NodeProps,
|
||||||
Position,
|
Position,
|
||||||
type Connection,
|
|
||||||
type Edge,
|
|
||||||
type Node,
|
type Node,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import { Toolbar } from '../components/NodeComponents';
|
import { Toolbar } from '../components/NodeComponents';
|
||||||
@@ -25,15 +23,8 @@ export type NormNodeData = {
|
|||||||
hasReduce: boolean;
|
hasReduce: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export type NormNode = Node<NormNodeData>
|
export type NormNode = Node<NormNodeData>
|
||||||
|
|
||||||
|
|
||||||
export function NormNodeCanConnect(connection: Connection | Edge): boolean {
|
|
||||||
return (connection != undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines how a Norm node should be rendered
|
* Defines how a Norm node should be rendered
|
||||||
* @param props NodeProps, like id, label, children
|
* @param props NodeProps, like id, label, children
|
||||||
|
|||||||
@@ -24,10 +24,8 @@ export type PhaseNodeData = {
|
|||||||
hasReduce: boolean;
|
hasReduce: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type PhaseNode = Node<PhaseNodeData>
|
export type PhaseNode = Node<PhaseNodeData>
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines how a phase node should be rendered
|
* Defines how a phase node should be rendered
|
||||||
* @param props NodeProps, like id, label, children
|
* @param props NodeProps, like id, label, children
|
||||||
@@ -36,9 +34,7 @@ export type PhaseNode = Node<PhaseNodeData>
|
|||||||
export default function PhaseNode(props: NodeProps<Node>) {
|
export default function PhaseNode(props: NodeProps<Node>) {
|
||||||
const data = props.data as PhaseNodeData;
|
const data = props.data as PhaseNodeData;
|
||||||
const {updateNodeData} = useFlowStore();
|
const {updateNodeData} = useFlowStore();
|
||||||
|
|
||||||
const updateLabel = (value: string) => updateNodeData(props.id, {...data, label: value});
|
const updateLabel = (value: string) => updateNodeData(props.id, {...data, label: value});
|
||||||
|
|
||||||
const label_input_id = `phase_${props.id}_label_input`;
|
const label_input_id = `phase_${props.id}_label_input`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -62,10 +58,11 @@ export default function PhaseNode(props: NodeProps<Node>) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reduces each phase, including its children down into its relevant data.
|
* Reduces each phase, including its children down into its relevant data.
|
||||||
* @param props: The Node Properties of this node.
|
* @param node the node which is being reduced
|
||||||
|
* @param nodes all the nodes currently in the flow.
|
||||||
|
* @returns A collection of all reduced nodes in this phase, starting with this phases' reduced data.
|
||||||
*/
|
*/
|
||||||
export function PhaseReduce(node: Node, nodes: Node[]) {
|
export function PhaseReduce(node: Node, nodes: Node[]) {
|
||||||
const thisnode = node as PhaseNode;
|
const thisnode = node as PhaseNode;
|
||||||
@@ -104,7 +101,12 @@ export function PhaseReduce(node: Node, nodes: Node[]) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
export function PhaseConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
|
export function PhaseConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
|
||||||
console.log("Connect functionality called.")
|
console.log("Connect functionality called.")
|
||||||
const node = thisNode as PhaseNode
|
const node = thisNode as PhaseNode
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ export function StartReduce(node: Node, nodes: Node[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is made with this node type (start)
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
export function StartConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
|
export function StartConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
|
||||||
// Replace this for connection logic
|
// Replace this for connection logic
|
||||||
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
|
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
|
||||||
|
|||||||
@@ -81,6 +81,12 @@ export function TriggerReduce(node: Node, nodes: Node[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is made with this node type (trigger)
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
export function TriggerConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
|
export function TriggerConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
|
||||||
// Replace this for connection logic
|
// Replace this for connection logic
|
||||||
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
|
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
|
||||||
@@ -88,14 +94,13 @@ export function TriggerConnects(thisNode: Node, otherNode: Node, isThisSource: b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Definitions for the possible triggers, being keywords and emotions
|
||||||
|
type Keyword = { id: string, keyword: string };
|
||||||
|
|
||||||
export type EmotionTriggerNodeProps = {
|
export type EmotionTriggerNodeProps = {
|
||||||
type: "emotion";
|
type: "emotion";
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Keyword = { id: string, keyword: string };
|
|
||||||
|
|
||||||
export type KeywordTriggerNodeProps = {
|
export type KeywordTriggerNodeProps = {
|
||||||
type: "keywords";
|
type: "keywords";
|
||||||
value: Keyword[];
|
value: Keyword[];
|
||||||
@@ -103,6 +108,11 @@ export type KeywordTriggerNodeProps = {
|
|||||||
|
|
||||||
export type TriggerNodeProps = EmotionTriggerNodeProps | KeywordTriggerNodeProps;
|
export type TriggerNodeProps = EmotionTriggerNodeProps | KeywordTriggerNodeProps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The JSX element that is responsible for updating the field and showing the text
|
||||||
|
* @param param0 the function that updates the field
|
||||||
|
* @returns React.JSX.Element that handles adding keywords
|
||||||
|
*/
|
||||||
function KeywordAdder({ addKeyword }: { addKeyword: (keyword: string) => void }) {
|
function KeywordAdder({ addKeyword }: { addKeyword: (keyword: string) => void }) {
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
describe('not yet implemented', () => {
|
||||||
|
test('nothing yet', () => {
|
||||||
|
expect(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,33 +1,5 @@
|
|||||||
// import { mockReactFlow } from '../../../../setupFlowTests.ts';
|
describe('Not implemented', () => {
|
||||||
// import {act} from "@testing-library/react";
|
test('nothing yet', () => {
|
||||||
// import useFlowStore from "../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx";
|
expect(true)
|
||||||
// import {addNode} from "../../../../../src/pages/VisProgPage/visualProgrammingUI/components/DragDropSidebar.tsx";
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// beforeAll(() => {
|
|
||||||
// mockReactFlow();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// describe('Drag-and-Drop sidebar', () => {
|
|
||||||
// test.each(['phase', 'phase'])('new nodes get added correctly', (nodeType: string) => {
|
|
||||||
// act(()=> {
|
|
||||||
// addNode(nodeType, {x:100, y:100});
|
|
||||||
// })
|
|
||||||
// const updatedState = useFlowStore.getState();
|
|
||||||
// expect(updatedState.nodes.length).toBe(1);
|
|
||||||
// expect(updatedState.nodes[0].type).toBe(nodeType);
|
|
||||||
// });
|
|
||||||
// test.each(['phase', 'norm'])('new nodes get correct Id', (nodeType) => {
|
|
||||||
// act(()=> {
|
|
||||||
// addNode(nodeType, {x:100, y:100});
|
|
||||||
// addNode(nodeType, {x:100, y:100});
|
|
||||||
// })
|
|
||||||
// const updatedState = useFlowStore.getState();
|
|
||||||
// expect(updatedState.nodes.length).toBe(2);
|
|
||||||
// expect(updatedState.nodes[0].id).toBe(`${nodeType}-1`);
|
|
||||||
// expect(updatedState.nodes[1].id).toBe(`${nodeType}-2`);
|
|
||||||
// });
|
|
||||||
// test('throws error on unexpected node type', () => {
|
|
||||||
// expect(() => addNode('I do not Exist', {x:100, y:100})).toThrow("Node I do not Exist not found");
|
|
||||||
// })
|
|
||||||
// });
|
|
||||||
|
|||||||
Reference in New Issue
Block a user