refactor: defaults should be in their own file, respecting eslint/ react standards. all tests fail, obviously.

ref: N25B-294
This commit is contained in:
Björn Otgaar
2025-11-17 16:00:36 +01:00
parent c5dc825ca3
commit 35ff58eca8
16 changed files with 1134 additions and 1201 deletions

View File

@@ -0,0 +1,10 @@
import type { EndNodeData } from "./EndNode";
/**
* Default data for this node.
*/
export const EndNodeDefaults: EndNodeData = {
label: "End Node",
droppable: false,
hasReduce: true
};

View File

@@ -2,51 +2,20 @@ import {
Handle,
type NodeProps,
Position,
type Connection,
type Edge,
useReactFlow,
type Node,
} from '@xyflow/react';
import { Toolbar } from './NodeDefinitions';
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
export type EndNodeData = {
label: string;
droppable: Boolean;
hasReduce: Boolean;
};
export const EndNodeDefaults: EndNodeData = {
label: "End Node",
droppable: false,
hasReduce: true
droppable: boolean;
hasReduce: boolean;
};
export type EndNode = Node<EndNodeData>
export function EndNodeCanConnect(connection: Connection | Edge): boolean {
// connection has: { source, sourceHandle, target, targetHandle }
// Example rules:
if (connection.source === connection.target) return false;
if (connection.targetHandle && !["a", "b"].includes(connection.targetHandle)) {
return false;
}
if (connection.sourceHandle && connection.sourceHandle !== "result") {
return false;
}
// If all rules pass
return true;
}
export default function EndNode(props: NodeProps<Node>) {
const reactFlow = useReactFlow();
const label_input_id = `phase_${props.id}_label_input`;
return (
<>
<Toolbar nodeId={props.id} allowDelete={true}/>
@@ -54,7 +23,6 @@ export default function EndNode(props: NodeProps<Node>) {
<div className={"flex-row gap-sm"}>
End
</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>
@@ -63,11 +31,18 @@ export default function EndNode(props: NodeProps<Node>) {
}
export function EndReduce(node: Node, nodes: Node[]) {
return {
// Replace this for nodes functionality
if (nodes.length <= -1) {
console.warn("Impossible nodes length in EndReduce")
}
return {
id: node.id
}
}
}
export function EndConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
// Replace this for connection logic
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
console.warn("Impossible node connection called in EndConnects")
}
}

View File

@@ -1,84 +0,0 @@
import {
NodeToolbar} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import styles from '../../VisProg.module.css';
import useFlowStore from "../VisProgStores.tsx";
//Toolbar definitions
type ToolbarProps = {
nodeId: string;
allowDelete: boolean;
};
/**
* Node Toolbar definition:
* handles: node deleting functionality
* can be added to any custom node component as a React component
*
* @param {string} nodeId
* @param {boolean} allowDelete
* @returns {React.JSX.Element}
* @constructor
*/
export function Toolbar({nodeId, allowDelete}: ToolbarProps) {
const {deleteNode} = useFlowStore();
const deleteParentNode = ()=> {
deleteNode(nodeId);
}
return (
<NodeToolbar>
<button className="Node-toolbar__deletebutton" onClick={deleteParentNode} disabled={!allowDelete}>delete</button>
</NodeToolbar>);
}
// Renaming component
/**
* Adds a component that can be used to edit a node's label entry inside its Data
* can be added to any custom node that has a label inside its Data
*
* @param {string} nodeLabel
* @param {string} nodeId
* @returns {React.JSX.Element}
* @constructor
*/
export function EditableName({nodeLabel = "new node", nodeId} : { nodeLabel : string, nodeId: string}) {
const {updateNodeData} = useFlowStore();
const updateData = (event: React.FocusEvent<HTMLInputElement>) => {
const input = event.target.value;
updateNodeData(nodeId, {label: input});
event.currentTarget.setAttribute("readOnly", "true");
window.getSelection()?.empty();
event.currentTarget.classList.replace("nodrag", "drag"); // enable dragging of the node with cursor on the input box
};
const updateOnEnter = (event: React.KeyboardEvent<HTMLInputElement>) => { if (event.key === "Enter") (event.target as HTMLInputElement).blur(); };
const enableEditing = (event: React.MouseEvent<HTMLInputElement>) => {
if(event.currentTarget.hasAttribute("readOnly")) {
event.currentTarget.removeAttribute("readOnly"); // enable editing
event.currentTarget.select(); // select the text input
window.getSelection()?.collapseToEnd(); // move the caret to the end of the current value
event.currentTarget.classList.replace("drag", "nodrag"); // disable dragging using input box
}
}
return (
<div className={styles.NodeTextBar }>
<label>name: </label>
<input
className={`drag ${styles.nodeTextInput}`} // prevents dragging the component when user has focused the text input
type={"text"}
defaultValue={nodeLabel}
onKeyDown={updateOnEnter}
onBlur={updateData}
onClick={enableEditing}
maxLength={25}
readOnly
/>
</div>
)
}

View File

@@ -0,0 +1,11 @@
import type { NormNodeData } from "./NormNode";
/**
* Default data for this node
*/
export const NormNodeDefaults: NormNodeData = {
label: "Norm Node",
droppable: true,
normList: [],
hasReduce: true,
};

View File

@@ -4,13 +4,10 @@ import {
Position,
type Connection,
type Edge,
useReactFlow,
type Node,
} from '@xyflow/react';
import { Toolbar } from './NodeDefinitions';
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
import { NodeDefaults, NodeReduces } from '../NodeRegistry';
import type { FlowState } from '../VisProgTypes';
/**
* The default data dot a Norm node
@@ -25,25 +22,13 @@ export type NormNodeData = {
hasReduce: boolean;
};
/**
* Default data for this node
*/
export const NormNodeDefaults: NormNodeData = {
label: "Norm Node",
droppable: true,
normList: [],
hasReduce: true,
};
export type NormNode = Node<NormNodeData>
/**
*
* @param connection
* @returns
*/
export function NormNodeCanConnect(connection: Connection | Edge): boolean {
return true;
return (connection != undefined);
}
/**
@@ -52,7 +37,6 @@ export function NormNodeCanConnect(connection: Connection | Edge): boolean {
* @returns React.JSX.Element
*/
export default function NormNode(props: NodeProps<Node>) {
const reactFlow = useReactFlow();
const label_input_id = `Norm_${props.id}_label_input`;
const data = props.data as NormNodeData;
return (
@@ -75,6 +59,10 @@ export default function NormNode(props: NodeProps<Node>) {
* @param props: The Node Properties of this node.
*/
export function NormReduce(node: Node, nodes: Node[]) {
// Replace this for nodes functionality
if (nodes.length <= -1) {
console.warn("Impossible nodes length in NormReduce")
}
const data = node.data as NormNodeData;
return {
label: data.label,
@@ -83,4 +71,8 @@ export function NormReduce(node: Node, nodes: Node[]) {
}
export function NormConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
// Replace this for connection logic
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
console.warn("Impossible node connection called in EndConnects")
}
}

View File

@@ -0,0 +1,11 @@
import type { PhaseNodeData } from "./PhaseNode";
/**
* Default data for this node
*/
export const PhaseNodeDefaults: PhaseNodeData = {
label: "Phase Node",
droppable: true,
children: [],
hasReduce: true,
};

View File

@@ -2,15 +2,11 @@ import {
Handle,
type NodeProps,
Position,
type Connection,
type Edge,
useReactFlow,
type Node,
} from '@xyflow/react';
import { Toolbar } from './NodeDefinitions';
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
import { NodeDefaults, NodeReduces } from '../NodeRegistry';
import type { FlowState } from '../VisProgTypes';
/**
* The default data dot a phase node
@@ -25,26 +21,9 @@ export type PhaseNodeData = {
hasReduce: boolean;
};
/**
* Default data for this node
*/
export const PhaseNodeDefaults: PhaseNodeData = {
label: "Phase Node",
droppable: true,
children: [],
hasReduce: true,
};
export type PhaseNode = Node<PhaseNodeData>
/**
*
* @param connection
* @returns
*/
export function PhaseNodeCanConnect(connection: Connection | Edge): boolean {
return true;
}
/**
* Defines how a phase node should be rendered
@@ -52,7 +31,6 @@ export function PhaseNodeCanConnect(connection: Connection | Edge): boolean {
* @returns React.JSX.Element
*/
export default function PhaseNode(props: NodeProps<Node>) {
const reactFlow = useReactFlow();
const label_input_id = `phase_${props.id}_label_input`;
return (
<>
@@ -65,6 +43,7 @@ export default function PhaseNode(props: NodeProps<Node>) {
<Handle type="target" position={Position.Left} id="target"/>
<Handle type="source" position={Position.Right} id="source"/>
<Handle type="source" position={Position.Bottom} id="norms"/>
<Handle type="source" position={Position.Top} id="norms"/>
</div>
</>
);
@@ -78,16 +57,16 @@ export function PhaseReduce(node: Node, nodes: Node[]) {
const thisnode = node as PhaseNode;
const data = thisnode.data as PhaseNodeData;
const reducableChildren = Object.entries(NodeDefaults)
.filter(([_, data]) => data.hasReduce)
.map(([type, _]) => (
.filter(([, data]) => data.hasReduce)
.map(([type]) => (
type
));
let childrenData: any = ""
let childrenData: unknown = ""
if (data.children != undefined) {
childrenData = data.children.map((childId) => {
// Reduce each of this phases' children.
let child = nodes.find((node) => node.id == childId);
const child = nodes.find((node) => node.id == childId);
// Make sure that we reduce only valid children nodes.
if (child == undefined || child.type == undefined || !reducableChildren.includes(child.type)) return ''
@@ -109,8 +88,8 @@ export function PhaseReduce(node: Node, nodes: Node[]) {
export function PhaseConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
console.log("Connect functionality called.")
let node = thisNode as PhaseNode
let data = node.data as PhaseNodeData
const node = thisNode as PhaseNode
const data = node.data as PhaseNodeData
if (isThisSource)
data.children.push(otherNode.id)
}

View File

@@ -0,0 +1,10 @@
import type { StartNodeData } from "./StartNode";
/**
* Default data for this node.
*/
export const StartNodeDefaults: StartNodeData = {
label: "Start Node",
droppable: false,
hasReduce: true
};

View File

@@ -2,71 +2,22 @@ import {
Handle,
type NodeProps,
Position,
type Connection,
type Edge,
useReactFlow,
type Node,
} from '@xyflow/react';
import { Toolbar } from './NodeDefinitions';
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
/* ---------------------------------------------------------
* 1. THE DATA SHAPE FOR THIS NODE TYPE
* -------------------------------------------------------*/
export type StartNodeData = {
label: string;
droppable: boolean;
hasReduce: boolean;
};
/* ---------------------------------------------------------
* 2. DEFAULT DATA FOR NEW INSTANCES OF THIS NODE
* -------------------------------------------------------*/
export const StartNodeDefaults: StartNodeData = {
label: "Start Node",
droppable: false,
hasReduce: true,
};
export type StartNode = Node<StartNodeData>
/* ---------------------------------------------------------
* 3. CUSTOM CONNECTION LOGIC FOR THIS NODE
* -------------------------------------------------------*/
export function startNodeCanConnect(connection: Connection | Edge): boolean {
// connection has: { source, sourceHandle, target, targetHandle }
// Example rules:
// ❌ Cannot connect to itself
if (connection.source === connection.target) return false;
// ❌ Only allow incoming connections on input slots "a" or "b"
if (connection.targetHandle && !["a", "b"].includes(connection.targetHandle)) {
return false;
}
// ❌ Only allow outgoing connections from "result"
if (connection.sourceHandle && connection.sourceHandle !== "result") {
return false;
}
// If all rules pass
return true;
}
/* ---------------------------------------------------------
* 4. OPTIONAL: Node execution logic
* If your system evaluates nodes, this is where that lives.
* -------------------------------------------------------*/
/* ---------------------------------------------------------
* 5. THE NODE COMPONENT (UI)
* -------------------------------------------------------*/
export default function StartNode(props: NodeProps<Node>) {
const reactFlow = useReactFlow();
const label_input_id = `phase_${props.id}_label_input`;
return (
<>
<Toolbar nodeId={props.id} allowDelete={true}/>
@@ -83,11 +34,18 @@ export default function StartNode(props: NodeProps<Node>) {
}
export function StartReduce(node: Node, nodes: Node[]) {
return {
// Replace this for nodes functionality
if (nodes.length <= -1) {
console.warn("Impossible nodes length in StartReduce")
}
return {
id: node.id
}
}
}
export function StartConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
// Replace this for connection logic
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
console.warn("Impossible node connection called in EndConnects")
}
}