Skip to content

Commit 5d59a00

Browse files
committed
feat: add group function card
1 parent 9e2d52c commit 5d59a00

File tree

5 files changed

+85
-21
lines changed

5 files changed

+85
-21
lines changed

src/components/d-flow/DFlow.nodes.hook.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,80 @@ import {useService} from "../../utils/contextStore";
22
import {DFlowReactiveService} from "./DFlow.service";
33
import {isNodeFunctionObject, NodeFunction, NodeFunctionObject} from "./DFlow.view";
44
import {Node} from "@xyflow/react";
5+
import {DFlowFunctionReactiveService} from "./function/DFlowFunction.service";
6+
import {DFlowDataTypeReactiveService} from "./data-type/DFlowDataType.service";
7+
import {EDataType} from "./data-type/DFlowDataType.view";
58

69
export const useFlowNodes = (flowId: string): Node[] => {
710
const flowService = useService(DFlowReactiveService);
11+
const functionService = useService(DFlowFunctionReactiveService);
12+
const dataTypeService = useService(DFlowDataTypeReactiveService);
813
const flow = flowService.getById(flowId);
914

1015
if (!flow) return [];
1116

1217
const nodes: Node[] = [];
1318
let idCounter = 0;
1419

15-
const traverse = (fn: NodeFunction, isParameter = false, parentId?: string) => {
20+
const traverse = (
21+
fn: NodeFunction,
22+
isParameter = false,
23+
parentId?: string,
24+
depth: number = 0,
25+
parentGroup?: string
26+
) => {
1627
const id = `${fn.runtime_id}-${idCounter++}`;
1728

1829
nodes.push({
1930
id,
2031
type: "default",
2132
position: {x: 0, y: 0},
2233
draggable: false,
34+
parentNode: parentGroup,
2335
data: {
2436
...fn.json,
2537
isParameter,
26-
parentId: isParameter ? parentId : undefined, // Nur für Parameter!
38+
parentId: isParameter ? parentId : undefined,
39+
depth
2740
},
2841
});
2942

43+
const definition = functionService.getFunctionDefinition(fn.id);
44+
3045
fn.parameters?.forEach((param, i) => {
31-
if (param.value && isNodeFunctionObject(param.value as NodeFunctionObject)) {
46+
const paramType = definition?.parameters!!.find(p => p.parameter_id == param.id)?.type;
47+
const paramDataType = paramType ? dataTypeService.getDataType(paramType) : undefined;
48+
49+
if (paramDataType?.type === EDataType.NODE) {
50+
if (param.value && isNodeFunctionObject(param.value as NodeFunctionObject)) {
51+
const groupId = `${id}-group-${idCounter++}`;
52+
nodes.push({
53+
id: groupId,
54+
type: "group",
55+
position: {x: 0, y: 0},
56+
draggable: false,
57+
parentNode: parentGroup,
58+
data: {
59+
isParameter: true,
60+
parentId: id,
61+
depth: depth + 1,
62+
},
63+
});
64+
65+
const subNode = new NodeFunction(param.value as NodeFunctionObject);
66+
traverse(subNode, false, undefined, depth + 1, groupId);
67+
}
68+
} else if (param.value && isNodeFunctionObject(param.value as NodeFunctionObject)) {
3269
const subNode = new NodeFunction(param.value as NodeFunctionObject);
33-
traverse(subNode, true, id); // Parameter: isParameter=true, parentId gesetzt
70+
traverse(subNode, true, id, depth, parentGroup);
3471
}
3572
});
3673

3774
if (fn.nextNode) {
38-
traverse(fn.nextNode, false); // nextNode: isParameter=false, parentId NICHT setzen
75+
traverse(fn.nextNode, false, undefined, depth, parentGroup);
3976
}
4077
};
78+
4179
traverse(flow.startingNode);
4280
return nodes;
4381
};

src/components/d-flow/DFlow.stories.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {Meta} from "@storybook/react";
22
import {Background, BackgroundVariant} from "@xyflow/react";
33
import React, {useEffect} from "react";
44
import {DFlowFunctionCard} from "./function/cards/DFlowFunctionCard";
5+
import {DFlowFunctionGroupCard} from "./function/cards/DFlowFunctionGroupCard";
56
import {DFlow} from "./DFlow";
67
import {ContextStoreProvider} from "../../utils/contextStore";
78
import {createReactiveArrayService} from "../../utils/reactiveArrayService";
@@ -79,6 +80,7 @@ const Test = () => {
7980

8081
const nodeTypes = {
8182
default: DFlowFunctionCard,
83+
group: DFlowFunctionGroupCard,
8284
}
8385

8486
const edgeTypes = {

src/components/d-flow/DFlow.tsx

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ const getLayoutedElements = (nodes: any[], edges: any[]) => {
2929
}
3030
}
3131

32-
// 2. Identify root/main nodes (nodes that are not parameters or have no parent).
32+
// 2. Identify root/main nodes (nodes that are not parameters or have no parent and are not inside a group).
3333
const roots = nodes.filter(
34-
n => !n.data?.isParameter || !n.data?.parentId
34+
n => (!n.data?.isParameter || !n.data?.parentId) && !n.parentNode
3535
);
3636

3737
// 3. Store calculated center positions for each node by id.
@@ -51,38 +51,47 @@ const getLayoutedElements = (nodes: any[], edges: any[]) => {
5151
// Store center position for this node.
5252
pos.set(node.id, {x: cx, y: cy});
5353

54-
// Find and sort direct parameter nodes for this node.
55-
const params = (children.get(node.id) ?? [])
54+
// Find direct children for this node.
55+
const allChildren = (children.get(node.id) ?? [])
5656
.sort((a, b) => (a.data?.paramIndex ?? 0) - (b.data?.paramIndex ?? 0));
57+
const sideParams = allChildren.filter((c: any) => c.type !== 'group');
58+
const downGroups = allChildren.filter((c: any) => c.type === 'group');
5759

58-
// Compute the total vertical height of all parameter nodes including spacing.
59-
const paramHeights = params.map(p => p.measured?.height ?? 80);
60+
// Compute the total vertical height of parameter nodes on the side including spacing.
61+
const paramHeights = sideParams.map(p => p.measured?.height ?? 80);
6062
const paramBlockHeight = paramHeights.length
6163
? paramHeights.reduce((sum, h, i) => sum + h + (i ? V_SPACING : 0), 0)
6264
: 0;
6365

64-
// The "block" height for the main node is the max of its own height or its parameter block.
65-
const blockHeight = Math.max(h, paramBlockHeight);
66-
67-
// Center parameters vertically relative to parent node.
66+
// Center side parameters vertically relative to parent node.
6867
let paramY = cy - paramBlockHeight / 2;
69-
params.forEach((param, i) => {
68+
sideParams.forEach((param: any, i: number) => {
7069
const pw = param.measured?.width ?? 200;
7170
const ph = paramHeights[i];
72-
// Place parameter node to the right of the parent.
7371
const px = cx + w / 2 + H_SPACING + pw / 2;
7472
const py = paramY + ph / 2;
7573

7674
pos.set(param.id, {x: px, y: py});
7775

78-
// Recursively layout any sub-parameters of this parameter node.
7976
layout(param, px, py);
8077

8178
paramY += ph + V_SPACING;
8279
});
8380

84-
// Return the bottom y-coordinate of this main node's "block".
85-
return cy + blockHeight / 2;
81+
// Place group parameters below the node.
82+
let blockBottom = cy + h / 2;
83+
downGroups.forEach((group: any) => {
84+
const gh = group.measured?.height ?? 80;
85+
const gx = cx;
86+
const gy = blockBottom + V_SPACING + gh / 2;
87+
pos.set(group.id, {x: gx, y: gy});
88+
layout(group, gx, gy);
89+
blockBottom = gy + gh / 2;
90+
});
91+
92+
// The block height is the max of node height, side params and groups below.
93+
const maxBottom = Math.max(blockBottom, cy + Math.max(h, paramBlockHeight) / 2);
94+
return maxBottom;
8695
}
8796

8897
// 4. Layout all root/main nodes vertically, keeping at least V_SPACING between each.
@@ -96,7 +105,7 @@ const getLayoutedElements = (nodes: any[], edges: any[]) => {
96105

97106
// 5. Generate new positioned nodes by shifting from center to top-left.
98107
const positionedNodes = nodes.map(node => {
99-
const {x, y} = pos.get(node.id)!;
108+
const {x, y} = pos.get(node.id) ?? {x: 0, y: 0};
100109
const w = node.measured?.width ?? 200;
101110
const h = node.measured?.height ?? 80;
102111
return {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.function-group {
2+
background: transparent;
3+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from "react";
2+
import {GroupNode, NodeProps, Node} from "@xyflow/react";
3+
import {FLOW_EDGE_RAINBOW} from "../../DFlow.edges.hook";
4+
import "./DFlowFunctionGroupCard.style.scss";
5+
6+
export interface DFlowFunctionGroupCardProps extends NodeProps<Node> {}
7+
8+
export const DFlowFunctionGroupCard: React.FC<DFlowFunctionGroupCardProps> = (props) => {
9+
const depth = (props.data as any)?.depth ?? 0;
10+
const color = FLOW_EDGE_RAINBOW[depth % FLOW_EDGE_RAINBOW.length];
11+
return <GroupNode {...props} className="function-group" style={{border: `2px solid ${color}`, background: "transparent"}} />;
12+
};

0 commit comments

Comments
 (0)