refactor: Initial working framework of node encapsulation works- polymorphic implementation of nodes in creating and connecting calls correct functions
ref: N25B-294
This commit is contained in:
@@ -1,19 +1,10 @@
|
||||
import {useDraggable} from '@neodrag/react';
|
||||
import {
|
||||
useReactFlow,
|
||||
type XYPosition
|
||||
} from '@xyflow/react';
|
||||
import {
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import useFlowStore from "../VisProgStores.tsx";
|
||||
import styles from "../../VisProg.module.css"
|
||||
import type {AppNode, PhaseNode, NormNode} from "../VisProgTypes.tsx";
|
||||
|
||||
|
||||
import { useDraggable } from '@neodrag/react';
|
||||
import { useReactFlow, type XYPosition } from '@xyflow/react';
|
||||
import { type ReactNode, useCallback, useRef, useState } from 'react';
|
||||
import useFlowStore from '../VisProgStores';
|
||||
import styles from '../../VisProg.module.css';
|
||||
import type { AppNode } from '../VisProgTypes';
|
||||
import { NodeDefaults, type NodeTypes } from '../NodeRegistry'
|
||||
|
||||
/**
|
||||
* DraggableNodeProps dictates the type properties of a DraggableNode
|
||||
@@ -21,41 +12,28 @@ import type {AppNode, PhaseNode, NormNode} from "../VisProgTypes.tsx";
|
||||
interface DraggableNodeProps {
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
nodeType: string;
|
||||
onDrop: (nodeType: string, position: XYPosition) => void;
|
||||
nodeType: keyof typeof NodeTypes;
|
||||
onDrop: (nodeType: keyof typeof NodeTypes, position: XYPosition) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition of a node inside the drag and drop toolbar,
|
||||
* these nodes require an onDrop function to be supplied
|
||||
* that dictates how the node is created in the graph.
|
||||
*
|
||||
* @param className
|
||||
* @param children
|
||||
* @param nodeType
|
||||
* @param onDrop
|
||||
* @constructor
|
||||
* 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.
|
||||
*/
|
||||
function DraggableNode({className, children, nodeType, onDrop}: DraggableNodeProps) {
|
||||
function DraggableNode({ className, children, nodeType, onDrop }: DraggableNodeProps) {
|
||||
const draggableRef = useRef<HTMLDivElement>(null);
|
||||
const [position, setPosition] = useState<XYPosition>({x: 0, y: 0});
|
||||
const [position, setPosition] = useState<XYPosition>({ x: 0, y: 0 });
|
||||
|
||||
// @ts-expect-error comes from a package and doesn't appear to play nicely with strict typescript typing
|
||||
// @ts-expect-error from the neodrag package — safe to ignore
|
||||
useDraggable(draggableRef, {
|
||||
position: position,
|
||||
onDrag: ({offsetX, offsetY}) => {
|
||||
// Calculate position relative to the viewport
|
||||
setPosition({
|
||||
x: offsetX,
|
||||
y: offsetY,
|
||||
});
|
||||
position,
|
||||
onDrag: ({ offsetX, offsetY }) => {
|
||||
setPosition({ x: offsetX, y: offsetY });
|
||||
},
|
||||
onDragEnd: ({event}) => {
|
||||
setPosition({x: 0, y: 0});
|
||||
onDrop(nodeType, {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
onDragEnd: ({ event }) => {
|
||||
setPosition({ x: 0, y: 0 });
|
||||
onDrop(nodeType, { x: event.clientX, y: event.clientY });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -66,71 +44,49 @@ function DraggableNode({className, children, nodeType, onDrop}: DraggableNodePro
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* addNode — adds a new node to the flow using the unified class-based system.
|
||||
* Keeps numbering logic for phase/norm nodes.
|
||||
*/
|
||||
export function addNode(nodeType: keyof typeof NodeTypes, position: XYPosition) {
|
||||
const { nodes, setNodes } = useFlowStore.getState();
|
||||
const defaultData = NodeDefaults[nodeType]
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export function addNode(nodeType: string, position: XYPosition) {
|
||||
const {setNodes} = useFlowStore.getState();
|
||||
const nds : AppNode[] = useFlowStore.getState().nodes;
|
||||
const newNode = () => {
|
||||
switch (nodeType) {
|
||||
case "phase":
|
||||
{
|
||||
const phaseNodes= nds.filter((node) => node.type === 'phase');
|
||||
let phaseNumber;
|
||||
if (phaseNodes.length > 0) {
|
||||
const finalPhaseId : number = +(phaseNodes[phaseNodes.length - 1].id.split('-')[1]);
|
||||
phaseNumber = finalPhaseId + 1;
|
||||
} else {
|
||||
phaseNumber = 1;
|
||||
}
|
||||
const phaseNode : PhaseNode = {
|
||||
id: `phase-${phaseNumber}`,
|
||||
type: nodeType,
|
||||
position,
|
||||
data: {label: 'new', number: phaseNumber},
|
||||
}
|
||||
return phaseNode;
|
||||
}
|
||||
case "norm":
|
||||
{
|
||||
const normNodes= nds.filter((node) => node.type === 'norm');
|
||||
let normNumber
|
||||
if (normNodes.length > 0) {
|
||||
const finalNormId : number = +(normNodes[normNodes.length - 1].id.split('-')[1]);
|
||||
normNumber = finalNormId + 1;
|
||||
} else {
|
||||
normNumber = 1;
|
||||
}
|
||||
if (!defaultData) throw new Error(`Node type '${nodeType}' not found in registry`);
|
||||
|
||||
const normNode : NormNode = {
|
||||
id: `norm-${normNumber}`,
|
||||
type: nodeType,
|
||||
position,
|
||||
data: {label: `new norm node`, value: "Pepper should be formal"},
|
||||
}
|
||||
return normNode;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Node ${nodeType} not found`);
|
||||
}
|
||||
}
|
||||
const sameTypeNodes = nodes.filter((node) => node.type === nodeType);
|
||||
const nextNumber =
|
||||
sameTypeNodes.length > 0
|
||||
? (() => {
|
||||
const lastNode = sameTypeNodes[sameTypeNodes.length - 1];
|
||||
const parts = lastNode.id.split('-');
|
||||
const lastNum = Number(parts[1]);
|
||||
return Number.isNaN(lastNum) ? sameTypeNodes.length + 1 : lastNum + 1;
|
||||
})()
|
||||
: 1;
|
||||
|
||||
const id = `${nodeType}-${nextNumber}`;
|
||||
|
||||
let newNode = {
|
||||
id: id,
|
||||
type: nodeType,
|
||||
position,
|
||||
data: {...defaultData}
|
||||
}
|
||||
|
||||
setNodes(nds.concat(newNode()));
|
||||
|
||||
console.log("Tried to add node");
|
||||
setNodes([...nodes, newNode]);
|
||||
}
|
||||
|
||||
/**
|
||||
* the DndToolbar defines how the drag and drop toolbar component works
|
||||
* and includes the default onDrop behavior through handleNodeDrop
|
||||
* @constructor
|
||||
* DndToolbar defines how the drag and drop toolbar component works
|
||||
* and includes the default onDrop behavior.
|
||||
*/
|
||||
export function DndToolbar() {
|
||||
const {screenToFlowPosition} = useReactFlow();
|
||||
/**
|
||||
* handleNodeDrop implements the default onDrop behavior
|
||||
*/
|
||||
const { screenToFlowPosition } = useReactFlow();
|
||||
|
||||
const handleNodeDrop = useCallback(
|
||||
(nodeType: string, screenPosition: XYPosition) => {
|
||||
(nodeType: keyof typeof NodeTypes, screenPosition: XYPosition) => {
|
||||
const flow = document.querySelector('.react-flow');
|
||||
const flowRect = flow?.getBoundingClientRect();
|
||||
const isInFlow =
|
||||
@@ -140,7 +96,6 @@ export function DndToolbar() {
|
||||
screenPosition.y >= flowRect.top &&
|
||||
screenPosition.y <= flowRect.bottom;
|
||||
|
||||
// Create a new node and add it to the flow
|
||||
if (isInFlow) {
|
||||
const position = screenToFlowPosition(screenPosition);
|
||||
addNode(nodeType, position);
|
||||
@@ -149,19 +104,35 @@ export function DndToolbar() {
|
||||
[screenToFlowPosition],
|
||||
);
|
||||
|
||||
|
||||
const droppableNodes = Object.entries(NodeDefaults)
|
||||
.filter(([_, data]) => data.droppable)
|
||||
.map(([type, data]) => ({
|
||||
type: type as DraggableNodeProps['nodeType'],
|
||||
data
|
||||
}));
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className={`flex-col gap-lg padding-md ${styles.innerDndPanel}`}>
|
||||
<div className="description">
|
||||
You can drag these nodes to the pane to create new nodes.
|
||||
</div>
|
||||
<div className={`flex-row gap-lg ${styles.dndNodeContainer}`}>
|
||||
<DraggableNode className={styles.draggableNodePhase} nodeType="phase" onDrop={handleNodeDrop}>
|
||||
phase Node
|
||||
</DraggableNode>
|
||||
<DraggableNode className={styles.draggableNodeNorm} nodeType="norm" onDrop={handleNodeDrop}>
|
||||
norm Node
|
||||
</DraggableNode>
|
||||
{
|
||||
// Maps over all the nodes that are droppable, and puts them in the panel
|
||||
}
|
||||
{droppableNodes.map(({type, data}) => (
|
||||
<DraggableNode
|
||||
className={styles.nodeType}
|
||||
nodeType={type}
|
||||
onDrop={handleNodeDrop}
|
||||
>
|
||||
{data.label}
|
||||
</DraggableNode>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user