@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
- ? { 'marginLeft': 'xs' }
17
- : { 'marginRight': 'xs' }),
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
- })) })] }), _jsxs(Flex, { gap: "sm", paddingY: "xs", children: [_jsx(NodeHandles, { type: "input", handles: node.handles.input }), _jsx(NodeHandles, { type: "output", handles: node.handles.output })] })] }));
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 { Edge, Node } from '@xyflow/react';
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
- return { nodes, edges };
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.4.14",
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.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
+ }