@luxonis/depthai-pipeline-lib 1.9.0 → 1.11.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.
package/dist/panda.css CHANGED
@@ -1252,18 +1252,6 @@
1252
1252
 
1253
1253
  @layer utilities{
1254
1254
 
1255
- .w_custom\.handle\.dot\! {
1256
- width: var(--sizes-custom-handle-dot) !important;
1257
- }
1258
-
1259
- .h_custom\.handle\.dot\! {
1260
- height: var(--sizes-custom-handle-dot) !important;
1261
- }
1262
-
1263
- .bd_none\! {
1264
- border: var(--borders-none) !important;
1265
- }
1266
-
1267
1255
  .min-w_container\.smaller\.xxs {
1268
1256
  min-width: var(--sizes-container-smaller-xxs);
1269
1257
  }
@@ -1276,6 +1264,30 @@
1276
1264
  border-radius: var(--radii-common);
1277
1265
  }
1278
1266
 
1267
+ .p_0 {
1268
+ padding: var(--spacing-0);
1269
+ }
1270
+
1271
+ .bdr_0\%\! {
1272
+ border-radius: 0% !important;
1273
+ }
1274
+
1275
+ .bdr_100\%\! {
1276
+ border-radius: 100% !important;
1277
+ }
1278
+
1279
+ .w_custom\.handle\.dot\! {
1280
+ width: var(--sizes-custom-handle-dot) !important;
1281
+ }
1282
+
1283
+ .h_custom\.handle\.dot\! {
1284
+ height: var(--sizes-custom-handle-dot) !important;
1285
+ }
1286
+
1287
+ .bd_none\! {
1288
+ border: var(--borders-none) !important;
1289
+ }
1290
+
1279
1291
  .w_full {
1280
1292
  width: var(--sizes-full);
1281
1293
  }
@@ -1301,8 +1313,16 @@
1301
1313
  height: auto;
1302
1314
  }
1303
1315
 
1304
- .bg-c_dark\.warning\! {
1305
- background-color: var(--colors-dark-warning) !important;
1316
+ .border-style_dashed {
1317
+ border-style: dashed;
1318
+ }
1319
+
1320
+ .bg-c_rgba\(0\,0\,0\,0\.125\) {
1321
+ background-color: rgba(0,0,0,0.125);
1322
+ }
1323
+
1324
+ .bg-c_dark\.error\! {
1325
+ background-color: var(--colors-dark-error) !important;
1306
1326
  }
1307
1327
 
1308
1328
  .bg-c_dark\.success\! {
@@ -0,0 +1,5 @@
1
+ import type { NodeProps } from '@xyflow/react';
2
+ import type { ParsedNode } from '../services/pipeline';
3
+ type GroupNodeProps = NodeProps<ParsedNode>;
4
+ export declare const GroupNode: React.FC<GroupNodeProps>;
5
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Flex } from '@luxonis/common-fe-components';
3
+ import { css } from '../styled-system/css/index.mjs';
4
+ // eslint-disable-next-line no-warning-comments
5
+ // TODO: Add group node
6
+ export const GroupNode = (props) => {
7
+ const { data: _node } = props;
8
+ return (_jsx(Flex, { direction: "column", className: css({
9
+ 'minWidth': 'container.smaller.xxs',
10
+ 'border': 'base',
11
+ 'rounded': 'common',
12
+ '&:hover .node-help-icon': {
13
+ color: 'text.normal',
14
+ },
15
+ 'padding': 0,
16
+ 'borderStyle': 'dashed',
17
+ 'backgroundColor': 'rgba(0,0,0,0.125)',
18
+ }) }));
19
+ };
@@ -6,12 +6,18 @@ import { css } from '../styled-system/css/css.mjs';
6
6
  import { DOCS_BASE_URL, NodesWithLinks, } from '../services/pipeline.js';
7
7
  const NodeHandles = props => {
8
8
  const { handles, type } = props;
9
- return (_jsx(Flex, { full: true, direction: "column", align: type === 'input' ? 'start' : 'end', children: handles.map(({ type: handleType, id, blocking, queueSize, name, maxQueueSize, fps }) => (_jsxs(Flex, { position: "relative", align: "end", direction: handleType === 'input' ? 'row' : 'row-reverse', children: [_jsx(Handle, { type: handleType === 'input' ? 'target' : 'source', position: handleType === 'input' ? Position.Left : Position.Right, id: name, isConnectable: false, className: css({
10
- width: 'custom.handle.dot!',
11
- height: 'custom.handle.dot!',
12
- backgroundColor: blocking ? 'dark.warning!' : 'dark.success!',
13
- border: 'none!',
14
- }) }), _jsx(NodeHandlesSubLabel, { type: type, name: name, queueSize: queueSize, maxQueueSize: maxQueueSize, fps: fps })] }, id))) }));
9
+ return (_jsx(Flex, { full: true, direction: "column", align: type === 'input' ? 'start' : 'end', children: handles.map(({ type: handleType, id, blocking, queueSize, name, maxQueueSize, fps, connected }) => {
10
+ if (!connected) {
11
+ return;
12
+ }
13
+ return (_jsxs(Flex, { position: "relative", align: "end", direction: handleType === 'input' ? 'row' : 'row-reverse', children: [_jsx(Handle, { type: handleType === 'input' ? 'target' : 'source', position: handleType === 'input' ? Position.Left : Position.Right, id: name, isConnectable: false, className: css({
14
+ width: 'custom.handle.dot!',
15
+ height: 'custom.handle.dot!',
16
+ backgroundColor: blocking ? 'dark.error!' : 'dark.success!',
17
+ border: 'none!',
18
+ borderRadius: blocking ? '0% !important' : '100% !important',
19
+ }) }), _jsx(NodeHandlesSubLabel, { type: type, name: name, queueSize: queueSize, maxQueueSize: maxQueueSize, fps: fps })] }, id));
20
+ }) }));
15
21
  };
16
22
  const NodeHandlesSubLabel = props => {
17
23
  const { type, name, queueSize, maxQueueSize, fps } = props;
@@ -19,7 +25,9 @@ const NodeHandlesSubLabel = props => {
19
25
  ...(type === 'input' //
20
26
  ? { marginLeft: 'xs' }
21
27
  : { marginRight: 'xs' }),
22
- }), text: `[${fps ? `${fps.toFixed(1)} | ` : ''}${maxQueueSize ? `${maxQueueSize}/${queueSize}` : queueSize}] ${name}`, break: "none" }));
28
+ }), text: type === 'input'
29
+ ? `[${fps ? `${fps.toFixed(1)} | ` : ''}${`${maxQueueSize ?? 0}/${queueSize}`}] ${name}`
30
+ : `${fps ? `[${fps.toFixed(1)}]` : ''} ${name}`, break: "none" }));
23
31
  };
24
32
  export const PipelineNode = props => {
25
33
  const { data: node } = props;
@@ -6,85 +6,130 @@ import Dagre from '@dagrejs/dagre';
6
6
  import { PipelineNode } from './Node.js';
7
7
  const getLayoutedElements = (nodes, edges) => {
8
8
  const graph = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
9
- graph.setGraph({ rankdir: 'LR' });
9
+ graph.setGraph({ rankdir: 'LR', ranksep: 80, nodesep: 40 });
10
+ const childNodes = nodes.filter(node => node.type !== 'group');
10
11
  for (const edge of edges) {
11
12
  graph.setEdge(edge.source, edge.target);
12
13
  }
13
- for (const node of nodes) {
14
+ for (const node of childNodes) {
14
15
  graph.setNode(node.id, {
15
- ...node,
16
- width: node.measured?.width ?? 200,
17
- height: node.measured?.height ?? 100,
16
+ width: node.measured?.width ?? 300,
17
+ height: node.measured?.height ?? 150,
18
18
  });
19
19
  }
20
20
  Dagre.layout(graph);
21
21
  return {
22
- nodes: nodes.map((node) => {
23
- const position = graph.node(node.id);
24
- // Shift Dagre anchor (center) to React Flow anchor (top left)
25
- const x = position.x - (node.measured?.width ?? 200) / 2;
26
- const y = position.y - (node.measured?.height ?? 100) / 2;
27
- return { ...node, position: { x, y } };
22
+ nodes: nodes.map(node => {
23
+ const pos = graph.node(node.id);
24
+ if (!pos)
25
+ return node;
26
+ return {
27
+ ...node,
28
+ position: {
29
+ x: pos.x - (node.measured?.width ?? 300) / 2,
30
+ y: pos.y - (node.measured?.height ?? 150) / 2,
31
+ },
32
+ };
28
33
  }),
29
34
  edges,
30
35
  };
31
36
  };
32
- const adjustNodes = (nodes) => {
37
+ const adjustNodes = (nodes, edges) => {
33
38
  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;
39
+ const groupNodes = nodes.filter(node => node.type === 'group');
40
+ const childNodes = nodes.filter(node => node.type !== 'group');
41
+ const graph = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
42
+ graph.setGraph({ rankdir: 'LR', ranksep: 80, nodesep: 40 });
43
+ for (const edge of edges) {
44
+ graph.setEdge(edge.source, edge.target);
45
+ }
46
+ for (const node of childNodes) {
47
+ graph.setNode(node.id, {
48
+ width: node.measured?.width ?? 300,
49
+ height: node.measured?.height ?? 150,
50
+ });
51
+ }
52
+ Dagre.layout(graph);
53
+ const positionedChildren = childNodes.map(node => {
54
+ const pos = graph.node(node.id);
40
55
  return {
41
56
  ...node,
42
- position: { x, y },
57
+ position: {
58
+ x: pos.x - (node.measured?.width ?? 300) / 2,
59
+ y: pos.y - (node.measured?.height ?? 150) / 2,
60
+ },
43
61
  };
44
62
  });
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);
63
+ const positionedGroups = groupNodes.map(group => {
64
+ const children = positionedChildren.filter(n => n.parentId === group.id);
48
65
  if (children.length === 0) {
49
- return { ...parent, position: { x: 0, y: 0 } };
66
+ return { ...group, position: { x: 0, y: 0 } };
67
+ }
68
+ const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
69
+ g.setGraph({ rankdir: 'LR', ranksep: 60, nodesep: 20 });
70
+ for (const child of children) {
71
+ g.setNode(child.id, {
72
+ width: child.measured?.width ?? 300,
73
+ height: child.measured?.height ?? 150,
74
+ });
50
75
  }
76
+ const groupEdges = edges.filter(e => children.some(c => c.id === e.source) && children.some(c => c.id === e.target));
77
+ for (const edge of groupEdges) {
78
+ g.setEdge(edge.source, edge.target);
79
+ }
80
+ Dagre.layout(g);
51
81
  let minX = 9999;
52
82
  let minY = 9999;
53
83
  let maxX = -9999;
54
84
  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;
85
+ const locallyPositioned = children.map(child => {
86
+ const pos = g.node(child.id);
87
+ const x = pos.x - (child.measured?.width ?? 300) / 2;
88
+ const y = pos.y - (child.measured?.height ?? 150) / 2;
89
+ minX = Math.min(minX, x);
90
+ minY = Math.min(minY, y);
91
+ maxX = Math.max(maxX, x + (child.measured?.width ?? 300));
92
+ maxY = Math.max(maxY, y + (child.measured?.height ?? 150));
93
+ return { ...child, position: { x, y } };
70
94
  });
71
- return {
72
- ...parent,
73
- position: { x: parentX, y: parentY },
74
- measured: {
75
- width: maxX - minX + PADDING * 2,
76
- height: maxY - minY + PADDING * 2,
95
+ const relativeChildren = locallyPositioned.map(child => ({
96
+ ...child,
97
+ position: {
98
+ x: child.position.x - minX + PADDING,
99
+ y: child.position.y - minY + PADDING,
77
100
  },
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,
101
+ }));
102
+ const groupW = maxX - minX + PADDING * 2;
103
+ const groupH = maxY - minY + PADDING * 2;
104
+ let absMinX = 9999;
105
+ let absMinY = 9999;
106
+ for (const child of children) {
107
+ const pos = graph.node(child.id);
108
+ absMinX = Math.min(absMinX, pos.x - (child.measured?.width ?? 300) / 2);
109
+ absMinY = Math.min(absMinY, pos.y - (child.measured?.height ?? 150) / 2);
110
+ }
111
+ return {
112
+ nodes: relativeChildren,
113
+ group: {
114
+ ...group,
115
+ position: {
116
+ x: absMinX - PADDING,
117
+ y: absMinY - PADDING,
118
+ },
119
+ width: groupW + PADDING,
120
+ height: groupH,
121
+ style: { ...group.style, width: groupW + PADDING, height: groupH },
84
122
  },
85
123
  };
86
124
  });
87
- return [...positionedGroups, ...positionedNodes];
125
+ const rootChildren = positionedChildren.filter(n => !n.parentId);
126
+ const groupedChildren = positionedGroups.flatMap(g => g.nodes);
127
+ const groups = positionedGroups.map(g => g.group);
128
+ return [
129
+ ...groups.filter(g => g !== undefined),
130
+ ...groupedChildren.filter(c => c !== undefined),
131
+ ...rootChildren,
132
+ ];
88
133
  };
89
134
  export const updateNodesOnPipelineStateChange = (nodes, pipelineState) => {
90
135
  const parsedNodes = [];
@@ -144,7 +189,7 @@ const PipelineCanvasBody = ({ pipeline, pipelineState, action, ...flexProps }) =
144
189
  return;
145
190
  }
146
191
  const layouted = getLayoutedElements(pipeline?.nodes ?? [], pipeline?.edges ?? []);
147
- const adjustedNodes = adjustNodes([...layouted.nodes]);
192
+ const adjustedNodes = adjustNodes([...layouted.nodes], [...layouted.edges]);
148
193
  if (pipelineState) {
149
194
  const updatedNodes = updateNodesOnPipelineStateChange(adjustedNodes, pipelineState);
150
195
  setNodes([...updatedNodes]);
@@ -159,6 +204,12 @@ const PipelineCanvasBody = ({ pipeline, pipelineState, action, ...flexProps }) =
159
204
  if (pipelineState && nodes.length > 0) {
160
205
  const updatedNodes = updateNodesOnPipelineStateChange(nodes, pipelineState);
161
206
  setNodes([...updatedNodes]);
207
+ if (!autoArrangeRef.current) {
208
+ return;
209
+ }
210
+ else {
211
+ setShouldFitAndResize(true);
212
+ }
162
213
  }
163
214
  // eslint-disable-next-line react-hooks/exhaustive-deps
164
215
  }, [pipelineState]);
@@ -43,6 +43,7 @@ export type ParsedHandle = {
43
43
  type: 'input' | 'output';
44
44
  blocking: boolean;
45
45
  queueSize: number;
46
+ connected: boolean;
46
47
  maxQueueSize?: number;
47
48
  fps?: number;
48
49
  };
@@ -12,11 +12,34 @@ function parseHandles(handles) {
12
12
  type,
13
13
  blocking: handle.blocking,
14
14
  queueSize: handle.queueSize,
15
+ connected: true,
15
16
  };
16
17
  parsedHandles[type].push(parsed);
17
18
  }
18
19
  return parsedHandles;
19
20
  }
21
+ function filterNodesHandles(nodes, edges) {
22
+ const connectedHandleIds = new Set(edges.flatMap(edge => [
23
+ `${edge.source}-${edge.sourceHandle}`,
24
+ `${edge.target}-${edge.targetHandle}`,
25
+ ]));
26
+ return nodes.map(node => ({
27
+ ...node,
28
+ data: {
29
+ ...node.data,
30
+ handles: {
31
+ input: node.data.handles.input.map(handle => ({
32
+ ...handle,
33
+ connected: connectedHandleIds.has(`${node.id}-${handle.name}`),
34
+ })),
35
+ output: node.data.handles.output.map(handle => ({
36
+ ...handle,
37
+ connected: connectedHandleIds.has(`${node.id}-${handle.name}`),
38
+ })),
39
+ },
40
+ },
41
+ }));
42
+ }
20
43
  export function parsePipeline(rawPayload) {
21
44
  const { pipeline } = rawPayload;
22
45
  // Set all nodes as generic nodes
@@ -114,7 +137,8 @@ export function parsePipeline(rawPayload) {
114
137
  })) ?? [];
115
138
  // NOTE: Parent nodes should be rendered before child nodes
116
139
  const groupedNodes = [...newFormattedParentNodes, ...childrenNodes, ...filteredNodes];
117
- return { nodes: [...groupedNodes], edges: [...edges, ...bridges] };
140
+ const nodesWithFilteredHandles = filterNodesHandles(groupedNodes, edges);
141
+ return { nodes: [...nodesWithFilteredHandles], edges: [...edges, ...bridges] };
118
142
  }
119
143
  export const DOCS_BASE_URL = `https://docs.luxonis.com/software-v3/depthai/depthai-components/nodes`;
120
144
  export const NodesWithLinks = {
package/package.json CHANGED
@@ -1,64 +1,64 @@
1
1
  {
2
- "name": "@luxonis/depthai-pipeline-lib",
3
- "version": "1.9.0",
4
- "type": "module",
5
- "license": "UNLICENSED",
6
- "main": "./dist/src/index.js",
7
- "scripts": {
8
- "build": "./build.sh",
9
- "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10
- "gen:styles": "panda codegen",
11
- "prepublishOnly": "npm run build"
12
- },
13
- "dependencies": {
14
- "@dagrejs/dagre": "^1.1.3",
15
- "@luxonis/common-fe-components": "1.25.9",
16
- "@xyflow/react": "^12.0.4",
17
- "postcss-import": "^16.1.0",
18
- "postcss-nested": "^6.2.0",
19
- "postcss-preset-env": "^10.0.0",
20
- "rehype-sanitize": "^6.0.0"
21
- },
22
- "peerDependencies": {
23
- "react": "^18.3.1 || ^19.0.0",
24
- "react-dom": "^18.3.1 || ^19.0.0"
25
- },
26
- "devDependencies": {
27
- "@pandacss/dev": "^0.53.0",
28
- "@stylistic/eslint-plugin": "^2.6.1",
29
- "@types/react": "^18.3.1 || ^19.0.0",
30
- "@types/react-dom": "^18.3.1 || ^19.0.0",
31
- "@typescript-eslint/eslint-plugin": "^6.21.0",
32
- "@typescript-eslint/parser": "^6.21.0",
33
- "eslint": "^8.56.0",
34
- "eslint-config-prettier": "^9.1.0",
35
- "eslint-import-resolver-typescript": "^3.6.1",
36
- "eslint-interactive": "^10.8.0",
37
- "eslint-plugin-cypress": "^3.3.0",
38
- "eslint-plugin-functional": "^6.0.0",
39
- "eslint-plugin-github": "^4.10.1",
40
- "eslint-plugin-import": "^2.29.1",
41
- "eslint-plugin-prettier": "^5.1.3",
42
- "eslint-plugin-react": "^7.33.2",
43
- "eslint-plugin-react-hooks": "^4.6.0",
44
- "eslint-plugin-react-refresh": "^0.4.5",
45
- "postcss": "^8.4.31",
46
- "prettier": "^3.2.5",
47
- "prettier-eslint": "^16.3.0",
48
- "typescript": "^5.2.2"
49
- },
50
- "overrides": {
51
- "@radix-ui/react-alert-dialog": "1.0.5"
52
- },
53
- "files": [
54
- "dist/src/*",
55
- "dist/*.css"
56
- ],
57
- "exports": {
58
- ".": "./dist/src/index.js",
59
- "./styles": "./dist/index.css"
60
- },
61
- "publishConfig": {
62
- "access": "public"
63
- }
2
+ "name": "@luxonis/depthai-pipeline-lib",
3
+ "version": "1.11.0",
4
+ "type": "module",
5
+ "license": "UNLICENSED",
6
+ "main": "./dist/src/index.js",
7
+ "scripts": {
8
+ "build": "./build.sh",
9
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10
+ "gen:styles": "panda codegen",
11
+ "prepublishOnly": "npm run build"
12
+ },
13
+ "dependencies": {
14
+ "@dagrejs/dagre": "^1.1.3",
15
+ "@luxonis/common-fe-components": "1.25.9",
16
+ "@xyflow/react": "^12.0.4",
17
+ "postcss-import": "^16.1.0",
18
+ "postcss-nested": "^6.2.0",
19
+ "postcss-preset-env": "^10.0.0",
20
+ "rehype-sanitize": "^6.0.0"
21
+ },
22
+ "peerDependencies": {
23
+ "react": "^18.3.1 || ^19.0.0",
24
+ "react-dom": "^18.3.1 || ^19.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@pandacss/dev": "^0.53.0",
28
+ "@stylistic/eslint-plugin": "^2.6.1",
29
+ "@types/react": "^18.3.1 || ^19.0.0",
30
+ "@types/react-dom": "^18.3.1 || ^19.0.0",
31
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
32
+ "@typescript-eslint/parser": "^6.21.0",
33
+ "eslint": "^8.56.0",
34
+ "eslint-config-prettier": "^9.1.0",
35
+ "eslint-import-resolver-typescript": "^3.6.1",
36
+ "eslint-interactive": "^10.8.0",
37
+ "eslint-plugin-cypress": "^3.3.0",
38
+ "eslint-plugin-functional": "^6.0.0",
39
+ "eslint-plugin-github": "^4.10.1",
40
+ "eslint-plugin-import": "^2.29.1",
41
+ "eslint-plugin-prettier": "^5.1.3",
42
+ "eslint-plugin-react": "^7.33.2",
43
+ "eslint-plugin-react-hooks": "^4.6.0",
44
+ "eslint-plugin-react-refresh": "^0.4.5",
45
+ "postcss": "^8.4.31",
46
+ "prettier": "^3.2.5",
47
+ "prettier-eslint": "^16.3.0",
48
+ "typescript": "^5.2.2"
49
+ },
50
+ "overrides": {
51
+ "@radix-ui/react-alert-dialog": "1.0.5"
52
+ },
53
+ "files": [
54
+ "dist/src/*",
55
+ "dist/*.css"
56
+ ],
57
+ "exports": {
58
+ ".": "./dist/src/index.js",
59
+ "./styles": "./dist/index.css"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ }
64
64
  }