@luxonis/depthai-pipeline-lib 1.4.14 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -13,8 +13,8 @@ const NodeHandles = props => {
|
|
|
13
13
|
border: 'none!',
|
|
14
14
|
}) }), _jsx(SubLabel, { className: css({
|
|
15
15
|
...(type === 'input' //
|
|
16
|
-
? {
|
|
17
|
-
: {
|
|
16
|
+
? { marginLeft: 'xs' }
|
|
17
|
+
: { marginRight: 'xs' }),
|
|
18
18
|
}), text: type === 'input' ? `[${queueSize}] ${name}` : name, break: "none" })] }, id))) }));
|
|
19
19
|
};
|
|
20
20
|
export const PipelineNode = props => {
|
|
@@ -38,9 +38,21 @@ export const PipelineNode = props => {
|
|
|
38
38
|
}), children: [_jsx("span", { className: css({
|
|
39
39
|
width: 'icon.sm',
|
|
40
40
|
height: 'icon.sm',
|
|
41
|
-
}) }), _jsx(Label, { text: node.name, color: "unset" }), _jsx(Button, { variant: "ghost", color: "transparent", icon: HelpIcon, onClick: () => window.open(link, '_blank'), className: clsx('node-help-icon', css({
|
|
41
|
+
}) }), _jsx(Label, { text: node.id ? `${node.name} (${node.id})` : node.name, color: "unset" }), _jsx(Button, { variant: "ghost", color: "transparent", icon: HelpIcon, onClick: () => window.open(link, '_blank'), className: clsx('node-help-icon', css({
|
|
42
42
|
width: 'auto',
|
|
43
43
|
height: 'auto',
|
|
44
44
|
right: 'xs',
|
|
45
|
-
})) })] }),
|
|
45
|
+
})) })] }), _jsx(Handle, { type: "target", position: Position.Top, id: "top", isConnectable: false, className: css({
|
|
46
|
+
width: 'custom.handle.dot!',
|
|
47
|
+
height: 'custom.handle.dot!',
|
|
48
|
+
}), style: {
|
|
49
|
+
border: 'none',
|
|
50
|
+
background: 'transparent',
|
|
51
|
+
} }), _jsxs(Flex, { gap: "sm", paddingY: "xs", children: [_jsx(NodeHandles, { type: "input", handles: node.handles.input }), _jsx(NodeHandles, { type: "output", handles: node.handles.output })] }), _jsx(Handle, { type: "source", position: Position.Bottom, id: "bottom", isConnectable: false, className: css({
|
|
52
|
+
width: 'custom.handle.dot!',
|
|
53
|
+
height: 'custom.handle.dot!',
|
|
54
|
+
}), style: {
|
|
55
|
+
border: 'none',
|
|
56
|
+
background: 'transparent',
|
|
57
|
+
} })] }));
|
|
46
58
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { ReactFlow, ReactFlowProvider, useEdgesState, useNodesState, useReactFlow, useStore, } from '@xyflow/react';
|
|
3
|
+
import { ReactFlow, ReactFlowProvider, useEdgesState, useNodesInitialized, useNodesState, useReactFlow, useStore, } from '@xyflow/react';
|
|
4
4
|
import { Flex, Header } from '@luxonis/common-fe-components';
|
|
5
5
|
import Dagre from '@dagrejs/dagre';
|
|
6
6
|
import { PipelineNode } from './Node.js';
|
|
@@ -29,6 +29,63 @@ const getLayoutedElements = (nodes, edges) => {
|
|
|
29
29
|
edges,
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
|
+
const adjustNodes = (nodes) => {
|
|
33
|
+
const PADDING = 16;
|
|
34
|
+
const positionedNodes = nodes
|
|
35
|
+
.filter(node => node.type !== 'group')
|
|
36
|
+
.map((node) => {
|
|
37
|
+
const position = node.position;
|
|
38
|
+
const x = position.x - (node.measured?.width ?? 200) / 2;
|
|
39
|
+
const y = position.y - (node.measured?.height ?? 100) / 2;
|
|
40
|
+
return {
|
|
41
|
+
...node,
|
|
42
|
+
position: { x, y },
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
const groupNodes = nodes.filter(node => node.type === 'group');
|
|
46
|
+
const positionedGroups = groupNodes.map(parent => {
|
|
47
|
+
const children = positionedNodes.filter(n => n.parentId === parent.id);
|
|
48
|
+
if (children.length === 0) {
|
|
49
|
+
return { ...parent, position: { x: 0, y: 0 } };
|
|
50
|
+
}
|
|
51
|
+
let minX = 9999;
|
|
52
|
+
let minY = 9999;
|
|
53
|
+
let maxX = -9999;
|
|
54
|
+
let maxY = -9999;
|
|
55
|
+
// eslint-disable-next-line github/array-foreach
|
|
56
|
+
children.forEach(child => {
|
|
57
|
+
const width = child.measured?.width ?? 200;
|
|
58
|
+
const height = child.measured?.height ?? 100;
|
|
59
|
+
minX = Math.min(minX, child.position.x);
|
|
60
|
+
minY = Math.min(minY, child.position.y);
|
|
61
|
+
maxX = Math.max(maxX, child.position.x + width);
|
|
62
|
+
maxY = Math.max(maxY, child.position.y + height);
|
|
63
|
+
});
|
|
64
|
+
const parentX = minX - PADDING;
|
|
65
|
+
const parentY = minY - PADDING;
|
|
66
|
+
// eslint-disable-next-line github/array-foreach
|
|
67
|
+
children.forEach(child => {
|
|
68
|
+
child.position.x -= parentX;
|
|
69
|
+
child.position.y -= parentY;
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
...parent,
|
|
73
|
+
position: { x: parentX, y: parentY },
|
|
74
|
+
measured: {
|
|
75
|
+
width: maxX - minX + PADDING * 2,
|
|
76
|
+
height: maxY - minY + PADDING * 2,
|
|
77
|
+
},
|
|
78
|
+
width: maxX - minX + PADDING * 2,
|
|
79
|
+
height: maxY - minY + PADDING * 2,
|
|
80
|
+
style: {
|
|
81
|
+
...parent.style,
|
|
82
|
+
width: maxX - minX + PADDING * 2,
|
|
83
|
+
height: maxY - minY + PADDING * 2,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
return [...positionedGroups, ...positionedNodes];
|
|
88
|
+
};
|
|
32
89
|
const PipelineCanvasBody = ({ pipeline, ...flexProps }) => {
|
|
33
90
|
const { fitView, setViewport, getViewport } = useReactFlow();
|
|
34
91
|
const autoArrangeRef = React.useRef(true);
|
|
@@ -36,6 +93,8 @@ const PipelineCanvasBody = ({ pipeline, ...flexProps }) => {
|
|
|
36
93
|
const heightSelector = (state) => state.height;
|
|
37
94
|
const reactFlowWidth = useStore(widthSelector);
|
|
38
95
|
const reactFlowHeight = useStore(heightSelector);
|
|
96
|
+
const nodesInitialized = useNodesInitialized();
|
|
97
|
+
const [isAutoLayoutingFinished, setIsAutoLayoutingFinished] = React.useState(false);
|
|
39
98
|
React.useEffect(() => {
|
|
40
99
|
void fitView();
|
|
41
100
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -50,6 +109,14 @@ const PipelineCanvasBody = ({ pipeline, ...flexProps }) => {
|
|
|
50
109
|
setNodes([...layouted.nodes]);
|
|
51
110
|
setEdges([...layouted.edges]);
|
|
52
111
|
}, [pipeline?.edges, pipeline?.nodes, setEdges, setNodes]);
|
|
112
|
+
React.useEffect(() => {
|
|
113
|
+
if (nodesInitialized && !isAutoLayoutingFinished) {
|
|
114
|
+
const adjustedNodes = adjustNodes(nodes);
|
|
115
|
+
setNodes([...adjustedNodes]);
|
|
116
|
+
setIsAutoLayoutingFinished(true);
|
|
117
|
+
}
|
|
118
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
119
|
+
}, [nodesInitialized, isAutoLayoutingFinished]);
|
|
53
120
|
const setViewportAndFit = async (x, y, zoom, nds) => {
|
|
54
121
|
await setViewport({ x, y, zoom });
|
|
55
122
|
await fitView({ nodes: nds });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Edge, type Node } from '@xyflow/react';
|
|
2
2
|
export type RawPipelineNodeIO = [
|
|
3
3
|
[
|
|
4
4
|
string,
|
|
@@ -16,7 +16,9 @@ export type RawPipelineNode = {
|
|
|
16
16
|
id: number;
|
|
17
17
|
ioInfo: RawPipelineNodeIO[];
|
|
18
18
|
name: string;
|
|
19
|
+
parentId?: number;
|
|
19
20
|
};
|
|
21
|
+
export type RawPipelineBridge = [number, number];
|
|
20
22
|
export type RawPipelineEdge = {
|
|
21
23
|
node1Id: number;
|
|
22
24
|
node1Output: string;
|
|
@@ -29,6 +31,7 @@ export type RawPipelinePayload = {
|
|
|
29
31
|
export type RawPipeline = {
|
|
30
32
|
connections: RawPipelineEdge[];
|
|
31
33
|
nodes: [number, RawPipelineNode][];
|
|
34
|
+
bridges?: [number, number][];
|
|
32
35
|
};
|
|
33
36
|
export type Pipeline = {
|
|
34
37
|
nodes: ParsedNode[];
|
|
@@ -42,6 +45,8 @@ export type ParsedHandle = {
|
|
|
42
45
|
queueSize: number;
|
|
43
46
|
};
|
|
44
47
|
export type ParsedNode = Node<{
|
|
48
|
+
id: string;
|
|
49
|
+
parentId?: string;
|
|
45
50
|
name: string;
|
|
46
51
|
handles: {
|
|
47
52
|
input: ParsedHandle[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MarkerType } from '@xyflow/react';
|
|
1
2
|
function parseHandles(handles) {
|
|
2
3
|
const parsedHandles = {
|
|
3
4
|
input: [],
|
|
@@ -24,19 +25,61 @@ export function parsePipeline(rawPayload) {
|
|
|
24
25
|
type: 'generic',
|
|
25
26
|
id: id.toString(),
|
|
26
27
|
position: { x: 0, y: 0 },
|
|
28
|
+
parentId: node.parentId?.toString() === '-1' ? undefined : node.parentId?.toString(),
|
|
29
|
+
extent: node.parentId?.toString() === '-1' ? undefined : 'parent',
|
|
27
30
|
data: {
|
|
31
|
+
id: id.toString(),
|
|
28
32
|
name: node.name,
|
|
29
33
|
handles: parseHandles(node.ioInfo),
|
|
30
34
|
},
|
|
31
35
|
})) ?? [];
|
|
36
|
+
const mappedParentNodes = nodes.map(node => node.parentId).filter(id => id !== undefined);
|
|
37
|
+
const filteredParentNodes = mappedParentNodes.filter(id => id !== '-1');
|
|
38
|
+
const parentNodes = [...new Set(filteredParentNodes)];
|
|
39
|
+
const parentNodesArr = parentNodes
|
|
40
|
+
.map(id => pipeline.nodes.find(([_, node]) => node.id.toString() === id))
|
|
41
|
+
.filter(node => node !== undefined);
|
|
42
|
+
const formattedParentNodes = parentNodesArr.map(([id, node]) => ({
|
|
43
|
+
type: 'group',
|
|
44
|
+
id: id.toString(),
|
|
45
|
+
position: { x: 0, y: 0 },
|
|
46
|
+
parentId: node.parentId?.toString() === '-1' ? undefined : node.parentId?.toString(),
|
|
47
|
+
data: {
|
|
48
|
+
id: id.toString(),
|
|
49
|
+
name: node.name,
|
|
50
|
+
handles: parseHandles(node.ioInfo),
|
|
51
|
+
},
|
|
52
|
+
style: {
|
|
53
|
+
padding: 0,
|
|
54
|
+
borderStyle: 'dashed',
|
|
55
|
+
background: 'rgba(0,0,0,0.125)',
|
|
56
|
+
},
|
|
57
|
+
})) ?? [];
|
|
32
58
|
const edges = pipeline.connections.map(connection => ({
|
|
33
|
-
id: `${connection.node1Id}-${connection.node2Id}`,
|
|
59
|
+
id: `${connection.node1Id}-${connection.node2Id}-${connection.node1Output}-${connection.node2Input}-edge`,
|
|
34
60
|
source: connection.node1Id.toString(),
|
|
35
61
|
target: connection.node2Id.toString(),
|
|
36
62
|
sourceHandle: connection.node1Output,
|
|
37
63
|
targetHandle: connection.node2Input,
|
|
38
64
|
})) ?? [];
|
|
39
|
-
|
|
65
|
+
const bridges = pipeline.bridges?.map(bridge => ({
|
|
66
|
+
id: `${bridge[0]}-${bridge[1]}-bridge`,
|
|
67
|
+
source: bridge[0].toString(),
|
|
68
|
+
target: bridge[1].toString(),
|
|
69
|
+
sourceHandle: 'bottom',
|
|
70
|
+
targetHandle: 'top',
|
|
71
|
+
animated: true,
|
|
72
|
+
markerStart: {
|
|
73
|
+
type: MarkerType.ArrowClosed,
|
|
74
|
+
},
|
|
75
|
+
markerEnd: {
|
|
76
|
+
type: MarkerType.ArrowClosed,
|
|
77
|
+
},
|
|
78
|
+
type: 'step',
|
|
79
|
+
})) ?? [];
|
|
80
|
+
const groupedNodes = [...formattedParentNodes, ...nodes];
|
|
81
|
+
// NOTE: Parent nodes should be rendered before child nodes
|
|
82
|
+
return { nodes: [...groupedNodes], edges: [...edges, ...bridges] };
|
|
40
83
|
}
|
|
41
84
|
export const DOCS_BASE_URL = `https://docs.luxonis.com/software-v3/depthai/depthai-components/nodes`;
|
|
42
85
|
export const NodesWithLinks = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luxonis/depthai-pipeline-lib",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
"rehype-sanitize": "^6.0.0"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"react": "^18.3.1",
|
|
24
|
-
"react-dom": "^18.3.1"
|
|
23
|
+
"react": "^18.3.1 || ^19.0.0",
|
|
24
|
+
"react-dom": "^18.3.1 || ^19.0.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@pandacss/dev": "^0.53.0",
|
|
28
28
|
"@stylistic/eslint-plugin": "^2.6.1",
|
|
29
|
-
"@types/react": "^18.3.
|
|
30
|
-
"@types/react-dom": "^18.3.0",
|
|
29
|
+
"@types/react": "^18.3.1 || ^19.0.0",
|
|
30
|
+
"@types/react-dom": "^18.3.1 || ^19.0.0",
|
|
31
31
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
32
32
|
"@typescript-eslint/parser": "^6.21.0",
|
|
33
33
|
"eslint": "^8.56.0",
|
|
@@ -61,4 +61,4 @@
|
|
|
61
61
|
"publishConfig": {
|
|
62
62
|
"access": "public"
|
|
63
63
|
}
|
|
64
|
-
}
|
|
64
|
+
}
|