148 lines
5.3 KiB
TypeScript
148 lines
5.3 KiB
TypeScript
import {
|
|
Handle,
|
|
type NodeProps,
|
|
Position,
|
|
type Node
|
|
} from '@xyflow/react';
|
|
import { Toolbar } from '../components/NodeComponents';
|
|
import styles from '../../VisProg.module.css';
|
|
import { NodeReduces, NodesInPhase, NodeTypes} from '../NodeRegistry';
|
|
import useFlowStore from '../VisProgStores';
|
|
import { TextField } from '../../../../components/TextField';
|
|
|
|
/**
|
|
* The default data dot a phase node
|
|
* @param label: the label of this phase
|
|
* @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)
|
|
*/
|
|
export type PhaseNodeData = {
|
|
label: string;
|
|
droppable: boolean;
|
|
children: string[];
|
|
hasReduce: boolean;
|
|
};
|
|
|
|
export type PhaseNode = Node<PhaseNodeData>
|
|
|
|
/**
|
|
* Defines how a phase node should be rendered
|
|
* @param props NodeProps, like id, label, children
|
|
* @returns React.JSX.Element
|
|
*/
|
|
export default function PhaseNode(props: NodeProps<PhaseNode>) {
|
|
const data = props.data;
|
|
const {updateNodeData} = useFlowStore();
|
|
const updateLabel = (value: string) => updateNodeData(props.id, {...data, label: value});
|
|
const label_input_id = `phase_${props.id}_label_input`;
|
|
|
|
return (
|
|
<>
|
|
<Toolbar nodeId={props.id} allowDelete={true}/>
|
|
<div className={`${styles.defaultNode} ${styles.nodePhase}`}>
|
|
<div className={"flex-row gap-sm"}>
|
|
<label htmlFor={label_input_id}>Name:</label>
|
|
<TextField
|
|
id={label_input_id}
|
|
value={data.label}
|
|
setValue={updateLabel}
|
|
placeholder={"Phase ..."}
|
|
/>
|
|
</div>
|
|
<Handle type="target" position={Position.Left} id="target"/>
|
|
<Handle type="target" position={Position.Bottom} id="norms"/>
|
|
<Handle type="source" position={Position.Right} id="source"/>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Reduces each phase, including its children down into its relevant data.
|
|
* @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[]) {
|
|
const thisnode = node as PhaseNode;
|
|
const data = thisnode.data as PhaseNodeData;
|
|
|
|
// node typings that are not in phase
|
|
const nodesNotInPhase: string[] = Object.entries(NodesInPhase)
|
|
.filter(([, f]) => !f())
|
|
.map(([t]) => t);
|
|
|
|
// node typings that then are in phase
|
|
const nodesInPhase: string[] = Object.entries(NodeTypes)
|
|
.filter(([t]) => !nodesNotInPhase.includes(t))
|
|
.map(([t]) => t);
|
|
|
|
// children nodes - make sure to check for empty arrays
|
|
let childrenNodes: Node[] = [];
|
|
if (data.children)
|
|
childrenNodes = nodes.filter((node) => data.children.includes(node.id));
|
|
|
|
// Build the result object
|
|
const result: Record<string, unknown> = {
|
|
id: thisnode.id,
|
|
label: data.label,
|
|
};
|
|
|
|
nodesInPhase.forEach((type) => {
|
|
const typedChildren = childrenNodes.filter((child) => child.type == type);
|
|
const reducer = NodeReduces[type as keyof typeof NodeReduces];
|
|
if (!reducer) {
|
|
console.warn(`No reducer found for node type ${type}`);
|
|
result[type + "s"] = [];
|
|
} else {
|
|
result[type + "s"] = typedChildren.map((child) => reducer(child, nodes));
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 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 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)
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
} |