Merge branch 'dev' into fix/deep-clone-data
This commit is contained in:
@@ -12,7 +12,10 @@ import TriggerNode, { TriggerConnects, TriggerReduce } from "./nodes/TriggerNode
|
||||
import { TriggerNodeDefaults } from "./nodes/TriggerNode.default";
|
||||
|
||||
/**
|
||||
* The types of the nodes we have registered.
|
||||
* Registered node types in the visual programming system.
|
||||
*
|
||||
* Key: the node type string used in the flow graph.
|
||||
* Value: the corresponding React component for rendering the node.
|
||||
*/
|
||||
export const NodeTypes = {
|
||||
start: StartNode,
|
||||
@@ -24,8 +27,9 @@ export const NodeTypes = {
|
||||
};
|
||||
|
||||
/**
|
||||
* The default functions of the nodes we have registered.
|
||||
* Default data and settings for each node type.
|
||||
* These are defined in the <node>.default.ts files.
|
||||
* These defaults are used when a new node is created to initialize its properties.
|
||||
*/
|
||||
export const NodeDefaults = {
|
||||
start: StartNodeDefaults,
|
||||
@@ -38,7 +42,10 @@ export const NodeDefaults = {
|
||||
|
||||
|
||||
/**
|
||||
* The reduce functions of the nodes we have registered.
|
||||
* Reduce functions for each node type.
|
||||
*
|
||||
* A reduce function extracts the relevant data from a node and its children.
|
||||
* Used during graph evaluation or export.
|
||||
*/
|
||||
export const NodeReduces = {
|
||||
start: StartReduce,
|
||||
@@ -51,7 +58,9 @@ export const NodeReduces = {
|
||||
|
||||
|
||||
/**
|
||||
* The connection functionality of the nodes we have registered.
|
||||
* Connection functions for each node type.
|
||||
*
|
||||
* These functions define how nodes of a particular type can connect to other nodes.
|
||||
*/
|
||||
export const NodeConnects = {
|
||||
start: StartConnects,
|
||||
@@ -63,8 +72,9 @@ export const NodeConnects = {
|
||||
}
|
||||
|
||||
/**
|
||||
* Functions that define whether a node should be deleted, currently constant only for start and end.
|
||||
* Any node types that aren't mentioned are 'true', and can be deleted by default.
|
||||
* Defines whether a node type can be deleted.
|
||||
*
|
||||
* Returns a function per node type. Nodes not explicitly listed are deletable by default.
|
||||
*/
|
||||
export const NodeDeletes = {
|
||||
start: () => false,
|
||||
@@ -73,8 +83,10 @@ export const NodeDeletes = {
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines which types are variables in the phase node-
|
||||
* any node that is NOT mentioned here, is automatically seen as a variable of a phase.
|
||||
* Defines which node types are considered variables in a phase node.
|
||||
*
|
||||
* Any node type not listed here is automatically treated as part of a phase.
|
||||
* This allows the system to dynamically group nodes under a phase node.
|
||||
*/
|
||||
export const NodesInPhase = {
|
||||
start: () => false,
|
||||
|
||||
@@ -13,13 +13,14 @@ import { NodeDefaults, NodeConnects, NodeDeletes } from './NodeRegistry';
|
||||
|
||||
|
||||
/**
|
||||
* Create a node given the correct data
|
||||
* @param type the type of the node to create
|
||||
* @param id the id of the node to create
|
||||
* @param position the position of the node to create
|
||||
* @param data the data in the node to create
|
||||
* @param deletable if this node should be able to be deleted IN ANY WAY POSSIBLE
|
||||
* @constructor
|
||||
* A Function to create a new node with the correct default data and properties.
|
||||
*
|
||||
* @param id - The unique ID of the node.
|
||||
* @param type - The type of node to create (must exist in NodeDefaults).
|
||||
* @param position - The XY position of the node in the flow canvas.
|
||||
* @param data - The data object to initialize the node with.
|
||||
* @param deletable - Optional flag to indicate if the node can be deleted (can be deleted by default).
|
||||
* @returns A fully initialized Node object ready to be added to the flow.
|
||||
*/
|
||||
function createNode(id: string, type: string, position: XYPosition, data: Record<string, unknown>, deletable? : boolean) {
|
||||
const defaultData = NodeDefaults[type as keyof typeof NodeDefaults]
|
||||
@@ -33,7 +34,7 @@ function createNode(id: string, type: string, position: XYPosition, data: Record
|
||||
return {...defaultData, ...newData}
|
||||
}
|
||||
|
||||
//* Initial nodes, created by using createNode. */
|
||||
//* Initial nodes to populate the flow at startup.
|
||||
const initialNodes : Node[] = [
|
||||
createNode('start', 'start', {x: 100, y: 100}, {label: "Start"}, false),
|
||||
createNode('end', 'end', {x: 500, y: 100}, {label: "End"}, false),
|
||||
@@ -41,7 +42,7 @@ const initialNodes : Node[] = [
|
||||
createNode('norms-1', 'norm', {x:-200, y:100}, {label: "Initial Norms", normList: ["Be a robot", "get good"]}),
|
||||
];
|
||||
|
||||
// * Initial edges * /
|
||||
//* Initial edges to connect the startup nodes.
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'start-phase-1', source: 'start', target: 'phase-1' },
|
||||
{ id: 'phase-1-end', source: 'phase-1', target: 'end' },
|
||||
@@ -52,16 +53,33 @@ const initialEdges: Edge[] = [
|
||||
* 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.
|
||||
*
|
||||
* * Provides:
|
||||
* - Node and edge state management
|
||||
* - Node creation, deletion, and updates
|
||||
* - Custom connection handling via NodeConnects
|
||||
* - Edge reconnection handling
|
||||
*/
|
||||
const useFlowStore = create<FlowState>((set, get) => ({
|
||||
nodes: initialNodes,
|
||||
edges: initialEdges,
|
||||
edgeReconnectSuccessful: true,
|
||||
|
||||
/**
|
||||
* Handles changes to nodes triggered by ReactFlow.
|
||||
*/
|
||||
onNodesChange: (changes) =>
|
||||
set({nodes: applyNodeChanges(changes, get().nodes)}),
|
||||
|
||||
/**
|
||||
* Handles changes to edges triggered by ReactFlow.
|
||||
*/
|
||||
onEdgesChange: (changes) => set({ edges: applyEdgeChanges(changes, get().edges) }),
|
||||
|
||||
/**
|
||||
* Handles creating a new connection between nodes.
|
||||
* Updates edges and calls the node-specific connection functions.
|
||||
*/
|
||||
onConnect: (connection) => {
|
||||
const edges = addEdge(connection, get().edges);
|
||||
const nodes = get().nodes;
|
||||
@@ -86,6 +104,9 @@ const useFlowStore = create<FlowState>((set, get) => ({
|
||||
set({ nodes, edges });
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles reconnecting an edge between nodes.
|
||||
*/
|
||||
onReconnect: (oldEdge, newConnection) => {
|
||||
get().edgeReconnectSuccessful = true;
|
||||
set({ edges: reconnectEdge(oldEdge, newConnection, get().edges) });
|
||||
@@ -98,7 +119,11 @@ const useFlowStore = create<FlowState>((set, get) => ({
|
||||
}
|
||||
set({ edgeReconnectSuccessful: true });
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Deletes a node by ID, respecting NodeDeletes rules.
|
||||
* Also removes all edges connected to that node.
|
||||
*/
|
||||
deleteNode: (nodeId) => {
|
||||
// Let's find our node to check if they have a special deletion function
|
||||
const ourNode = get().nodes.find((n)=>n.id==nodeId);
|
||||
@@ -112,10 +137,19 @@ const useFlowStore = create<FlowState>((set, get) => ({
|
||||
})}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Replaces the entire nodes array in the store.
|
||||
*/
|
||||
setNodes: (nodes) => set({ nodes }),
|
||||
|
||||
/**
|
||||
* Replaces the entire edges array in the store.
|
||||
*/
|
||||
setEdges: (edges) => set({ edges }),
|
||||
|
||||
/**
|
||||
* Updates the data of a node by merging new data with existing data.
|
||||
*/
|
||||
updateNodeData: (nodeId, data) => {
|
||||
set({
|
||||
nodes: get().nodes.map((node) => {
|
||||
@@ -127,6 +161,9 @@ const useFlowStore = create<FlowState>((set, get) => ({
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new node to the flow store.
|
||||
*/
|
||||
addNode: (node: Node) => {
|
||||
set({ nodes: [...get().nodes, node] });
|
||||
},
|
||||
|
||||
@@ -2,23 +2,76 @@
|
||||
import type { Edge, OnNodesChange, OnEdgesChange, OnConnect, OnReconnect, Node } from '@xyflow/react';
|
||||
import type { NodeTypes } from './NodeRegistry';
|
||||
|
||||
export type AppNode = typeof NodeTypes
|
||||
/**
|
||||
* Type representing all registered node types.
|
||||
* This corresponds to the keys of NodeTypes in NodeRegistry.
|
||||
*/
|
||||
export type AppNode = typeof NodeTypes;
|
||||
|
||||
/**
|
||||
* The FlowState type defines the shape of the Zustand store used for managing the visual programming flow.
|
||||
*
|
||||
* Includes:
|
||||
* - Nodes and edges currently in the flow
|
||||
* - Callbacks for node and edge changes
|
||||
* - Node deletion and updates
|
||||
* - Edge reconnection handling
|
||||
*/
|
||||
export type FlowState = {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
edgeReconnectSuccessful: boolean;
|
||||
|
||||
/** Handler for changes to nodes triggered by ReactFlow */
|
||||
onNodesChange: OnNodesChange;
|
||||
|
||||
/** Handler for changes to edges triggered by ReactFlow */
|
||||
onEdgesChange: OnEdgesChange;
|
||||
|
||||
/** Handler for creating a new connection between nodes */
|
||||
onConnect: OnConnect;
|
||||
|
||||
/** Handler for reconnecting an existing edge */
|
||||
onReconnect: OnReconnect;
|
||||
|
||||
/** Called when an edge reconnect process starts */
|
||||
onReconnectStart: () => void;
|
||||
|
||||
/**
|
||||
* Called when an edge reconnect process ends.
|
||||
* @param _ - event or unused parameter
|
||||
* @param edge - the edge that finished reconnecting
|
||||
*/
|
||||
onReconnectEnd: (_: unknown, edge: { id: string }) => void;
|
||||
|
||||
/**
|
||||
* Deletes a node and any connected edges.
|
||||
* @param nodeId - the ID of the node to delete
|
||||
*/
|
||||
deleteNode: (nodeId: string) => void;
|
||||
|
||||
/**
|
||||
* Replaces the current nodes array in the store.
|
||||
* @param nodes - new array of nodes
|
||||
*/
|
||||
setNodes: (nodes: Node[]) => void;
|
||||
|
||||
/**
|
||||
* Replaces the current edges array in the store.
|
||||
* @param edges - new array of edges
|
||||
*/
|
||||
setEdges: (edges: Edge[]) => void;
|
||||
|
||||
/**
|
||||
* Updates the data of a node by merging new data with existing node data.
|
||||
* @param nodeId - the ID of the node to update
|
||||
* @param data - object containing new data fields to merge
|
||||
*/
|
||||
updateNodeData: (nodeId: string, data: object) => void;
|
||||
|
||||
/**
|
||||
* Adds a new node to the flow.
|
||||
* @param node - the Node object to add
|
||||
*/
|
||||
addNode: (node: Node) => void;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,7 +6,12 @@ import { NodeDefaults, type NodeTypes } from '../NodeRegistry'
|
||||
import addNode from '../utils/AddNode';
|
||||
|
||||
/**
|
||||
* DraggableNodeProps dictates the type properties of a DraggableNode
|
||||
* Props for a draggable node within the drag-and-drop toolbar.
|
||||
*
|
||||
* @property className - Optional custom CSS classes for styling.
|
||||
* @property children - The visual content or label rendered inside the draggable node.
|
||||
* @property nodeType - The type of node represented (key from `NodeTypes`).
|
||||
* @property onDrop - Function called when the node is dropped on the flow pane.
|
||||
*/
|
||||
interface DraggableNodeProps {
|
||||
className?: string;
|
||||
@@ -16,15 +21,20 @@ interface DraggableNodeProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition of a node inside the drag and drop toolbar.
|
||||
* These nodes require an onDrop function that dictates
|
||||
* how the node is created in the graph.
|
||||
* A draggable node element used in the drag-and-drop toolbar.
|
||||
*
|
||||
* Integrates with the NeoDrag library to handle drag events.
|
||||
* On drop, it calls the provided `onDrop` function with the node type and drop position.
|
||||
*
|
||||
* @param props - The draggable node configuration.
|
||||
* @returns A React element representing a draggable node.
|
||||
*/
|
||||
function DraggableNode({ className, children, nodeType, onDrop }: DraggableNodeProps) {
|
||||
const draggableRef = useRef<HTMLDivElement>(null);
|
||||
const [position, setPosition] = useState<XYPosition>({ x: 0, y: 0 });
|
||||
|
||||
// @ts-expect-error from the neodrag package — safe to ignore
|
||||
// The NeoDrag hook enables smooth drag functionality for this element.
|
||||
// @ts-expect-error: NeoDrag typing incompatibility — safe to ignore.
|
||||
useDraggable(draggableRef, {
|
||||
position,
|
||||
onDrag: ({ offsetX, offsetY }) => {
|
||||
@@ -48,16 +58,28 @@ function DraggableNode({ className, children, nodeType, onDrop }: DraggableNodeP
|
||||
}
|
||||
|
||||
/**
|
||||
* DndToolbar defines how the drag and drop toolbar component works
|
||||
* and includes the default onDrop behavior.
|
||||
* The drag-and-drop toolbar component for the visual programming interface.
|
||||
*
|
||||
* Displays draggable node templates based on entries in `NodeDefaults`.
|
||||
* Each droppable node can be dragged into the flow pane to instantiate it.
|
||||
*
|
||||
* Automatically filters nodes whose `droppable` flag is set to `true`.
|
||||
*
|
||||
* @returns A React element representing the drag-and-drop toolbar.
|
||||
*/
|
||||
export function DndToolbar() {
|
||||
const { screenToFlowPosition } = useReactFlow();
|
||||
|
||||
/**
|
||||
* Handles dropping a node onto the flow pane.
|
||||
* Translates screen coordinates into flow coordinates using React Flow utilities.
|
||||
*/
|
||||
const handleNodeDrop = useCallback(
|
||||
(nodeType: keyof typeof NodeTypes, screenPosition: XYPosition) => {
|
||||
const flow = document.querySelector('.react-flow');
|
||||
const flowRect = flow?.getBoundingClientRect();
|
||||
|
||||
// Only add the node if it is inside the flow canvas area.
|
||||
const isInFlow =
|
||||
flowRect &&
|
||||
screenPosition.x >= flowRect.left &&
|
||||
@@ -74,7 +96,7 @@ export function DndToolbar() {
|
||||
);
|
||||
|
||||
|
||||
// Map over our default settings to see which of them have their droppable data set to true
|
||||
// Map over the default nodes to get all nodes that can be dropped from the toolbar.
|
||||
const droppableNodes = Object.entries(NodeDefaults)
|
||||
.filter(([, data]) => data.droppable)
|
||||
.map(([type, data]) => ({
|
||||
|
||||
@@ -2,7 +2,12 @@ import { NodeToolbar } from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import useFlowStore from "../VisProgStores.tsx";
|
||||
|
||||
//Toolbar definitions
|
||||
/**
|
||||
* Props for the Toolbar component.
|
||||
*
|
||||
* @property nodeId - The ID of the node this toolbar is attached to.
|
||||
* @property allowDelete - If `true`, the delete button is enabled; otherwise disabled.
|
||||
*/
|
||||
type ToolbarProps = {
|
||||
nodeId: string;
|
||||
allowDelete: boolean;
|
||||
@@ -10,12 +15,12 @@ type ToolbarProps = {
|
||||
|
||||
/**
|
||||
* Node Toolbar definition:
|
||||
* handles: node deleting functionality
|
||||
* can be added to any custom node component as a React component
|
||||
* Handles: node deleting functionality
|
||||
* Can be integrated to any custom node component as a React component
|
||||
*
|
||||
* @param {string} nodeId
|
||||
* @param {boolean} allowDelete
|
||||
* @returns {React.JSX.Element}
|
||||
* @param {string} nodeId - The ID of the node for which the toolbar is rendered.
|
||||
* @param {boolean} allowDelete - Enables or disables the delete functionality.
|
||||
* @returns {React.JSX.Element} A JSX element representing the toolbar.
|
||||
* @constructor
|
||||
*/
|
||||
export function Toolbar({nodeId, allowDelete}: ToolbarProps) {
|
||||
|
||||
@@ -72,5 +72,11 @@ export function NormReduce(node: Node, _nodes: Node[]) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called whenever a connection is made with this node type (Norm)
|
||||
* @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 NormConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
|
||||
}
|
||||
@@ -14,10 +14,16 @@ import { RealtimeTextField, TextField } from '../../../../components/TextField';
|
||||
import duplicateIndices from '../../../../utils/duplicateIndices';
|
||||
|
||||
/**
|
||||
* The default data dot a Trigger node
|
||||
* @param label: the label of this Trigger
|
||||
* @param droppable: whether this node is droppable from the drop bar (initialized as true)
|
||||
* @param children: ID's of children of this node
|
||||
* The default data structure for a Trigger node
|
||||
*
|
||||
* Represents configuration for a node that activates when a specific condition is met,
|
||||
* such as keywords being spoken or emotions detected.
|
||||
*
|
||||
* @property label: the display label of this Trigger node.
|
||||
* @property droppable: Whether this node can be dropped from the toolbar (default: true).
|
||||
* @property triggerType - The type of trigger ("keywords" or a custom string).
|
||||
* @property triggers - The list of keyword triggers (if applicable).
|
||||
* @property hasReduce - Whether this node supports reduction logic.
|
||||
*/
|
||||
export type TriggerNodeData = {
|
||||
label: string;
|
||||
@@ -31,14 +37,20 @@ export type TriggerNodeData = {
|
||||
export type TriggerNode = Node<TriggerNodeData>
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether a Trigger node can connect to another node or edge.
|
||||
*
|
||||
* @param connection - The connection or edge being attempted to connect towards.
|
||||
* @returns `true` if the connection is defined; otherwise, `false`.
|
||||
*/
|
||||
export function TriggerNodeCanConnect(connection: Connection | Edge): boolean {
|
||||
return (connection != undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how a Trigger node should be rendered
|
||||
* @param props NodeProps, like id, label, children
|
||||
* @returns React.JSX.Element
|
||||
* @param props - Node properties provided by React Flow, including `id` and `data`.
|
||||
* @returns The rendered TriggerNode React element (React.JSX.Element).
|
||||
*/
|
||||
export default function TriggerNode(props: NodeProps<TriggerNode>) {
|
||||
const data = props.data;
|
||||
@@ -69,6 +81,7 @@ export default function TriggerNode(props: NodeProps<TriggerNode>) {
|
||||
* Reduces each Trigger, including its children down into its relevant data.
|
||||
* @param node: The Node Properties of this node.
|
||||
* @param _nodes: all the nodes in the graph.
|
||||
* @returns A simplified object containing the node label and its list of triggers.
|
||||
*/
|
||||
export function TriggerReduce(node: Node, _nodes: Node[]) {
|
||||
const data = node.data as TriggerNodeData;
|
||||
@@ -89,23 +102,33 @@ export function TriggerConnects(_thisNode: Node, _otherNode: Node, _isThisSource
|
||||
}
|
||||
|
||||
// Definitions for the possible triggers, being keywords and emotions
|
||||
|
||||
/** Represents a single keyword trigger entry. */
|
||||
type Keyword = { id: string, keyword: string };
|
||||
|
||||
/** Properties for an emotion-type trigger node. */
|
||||
export type EmotionTriggerNodeProps = {
|
||||
type: "emotion";
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** Props for a keyword-type trigger node. */
|
||||
export type KeywordTriggerNodeProps = {
|
||||
type: "keywords";
|
||||
value: Keyword[];
|
||||
}
|
||||
|
||||
/** Union type for all possible Trigger node configurations. */
|
||||
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
|
||||
* Renders an input element that allows users to add new keyword triggers.
|
||||
*
|
||||
* When the input is committed, the `addKeyword` callback is called with the new keyword.
|
||||
*
|
||||
* @param param0 - An object containing the `addKeyword` function.
|
||||
* @returns A React element(React.JSX.Element) providing an input for adding keywords.
|
||||
*/
|
||||
function KeywordAdder({ addKeyword }: { addKeyword: (keyword: string) => void }) {
|
||||
const [input, setInput] = useState("");
|
||||
@@ -129,6 +152,14 @@ function KeywordAdder({ addKeyword }: { addKeyword: (keyword: string) => void })
|
||||
</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays and manages a list of keyword triggers for a Trigger node.
|
||||
* Handles adding, editing, and removing keywords, as well as detecting duplicate entries.
|
||||
*
|
||||
* @param keywords - The current list of keyword triggers.
|
||||
* @param setKeywords - A callback to update the keyword list in the parent node.
|
||||
* @returns A React element(React.JSX.Element) for editing keyword triggers.
|
||||
*/
|
||||
function Keywords({
|
||||
keywords,
|
||||
setKeywords,
|
||||
|
||||
Reference in New Issue
Block a user