@luxonis/depthai-pipeline-lib 1.5.0 → 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.5.0",
3
+ "version": "1.6.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "main": "./dist/src/index.js",
@@ -1,196 +0,0 @@
1
- @layer utilities {
2
-
3
- .bg_dark\.warning\! {
4
- background-color: var(--colors-dark-warning) !important
5
- }
6
-
7
- .bg_dark\.success\! {
8
- background-color: var(--colors-dark-success) !important
9
- }
10
-
11
- .w_custom\.handle\.dot\! {
12
- width: var(--sizes-custom-handle-dot) !important
13
- }
14
-
15
- .h_custom\.handle\.dot\! {
16
- height: var(--sizes-custom-handle-dot) !important
17
- }
18
-
19
- .border_none\! {
20
- border: var(--borders-none) !important
21
- }
22
-
23
- .ml_xs {
24
- margin-left: var(--spacing-xs)
25
- }
26
-
27
- .mr_xs {
28
- margin-right: var(--spacing-xs)
29
- }
30
-
31
- .min-w_container\.smaller\.xxs {
32
- min-width: var(--sizes-container-smaller-xxs)
33
- }
34
-
35
- .border_base {
36
- border: var(--borders-base)
37
- }
38
-
39
- .rounded_common {
40
- border-radius: var(--radii-common)
41
- }
42
-
43
- .bg_white {
44
- background-color: var(--colors-white)
45
- }
46
-
47
- .w_full {
48
- width: var(--sizes-full)
49
- }
50
-
51
- .bg_light\.gray {
52
- background-color: var(--colors-light-gray)
53
- }
54
-
55
- .rounded-t_common {
56
- border-top-left-radius: var(--radii-common);
57
- border-top-right-radius: var(--radii-common)
58
- }
59
-
60
- .w_icon\.sm {
61
- width: var(--sizes-icon-sm)
62
- }
63
-
64
- .h_icon\.sm {
65
- height: var(--sizes-icon-sm)
66
- }
67
-
68
- .w_auto {
69
- width: auto
70
- }
71
-
72
- .h_auto {
73
- height: auto
74
- }
75
-
76
- .right_xs {
77
- right: var(--spacing-xs)
78
- }
79
-
80
- .text_unset {
81
- color: unset
82
- }
83
-
84
- .text_transparent {
85
- color: var(--colors-transparent)
86
- }
87
-
88
- .items_start {
89
- align-items: start
90
- }
91
-
92
- .flex_row {
93
- flex-direction: row
94
- }
95
-
96
- .flex_row-reverse {
97
- flex-direction: row-reverse
98
- }
99
-
100
- .items_end {
101
- align-items: end
102
- }
103
-
104
- .pos_relative {
105
- position: relative
106
- }
107
-
108
- .flex_column {
109
- flex-direction: column
110
- }
111
-
112
- .items_center {
113
- align-items: center
114
- }
115
-
116
- .justify_space-between {
117
- justify-content: space-between
118
- }
119
-
120
- .gap_xs {
121
- gap: var(--spacing-xs)
122
- }
123
-
124
- .d_flex {
125
- display: flex
126
- }
127
-
128
- .gap_sm {
129
- gap: var(--spacing-sm)
130
- }
131
-
132
- .py_xs {
133
- padding-block: var(--spacing-xs)
134
- }
135
- .\[\&\:hover_\.node-help-icon\]\:text_text\.normal:hover .node-help-icon {
136
- color: var(--colors-text-normal)
137
- }
138
- }
139
-
140
- @layer recipes {
141
- .button--variant_ghost:is(:hover, [data-hover]) {
142
- background: var(--colors-accent);
143
- color: var(--colors-accent-foreground)
144
- }
145
-
146
- .button--size_default {
147
- height: var(--sizes-10);
148
- padding-inline: var(--spacing-4);
149
- padding-block: var(--spacing-2)
150
- }
151
-
152
- @layer _base {
153
- .label {
154
- font-size: 0.875rem;
155
- line-height: var(--line-heights-none);
156
- font-weight: var(--font-weights-medium);
157
- }
158
- .peer:is(:disabled, [disabled], [data-disabled]) ~ .label {
159
- cursor: not-allowed;
160
- opacity: 0.7
161
- }
162
- }
163
-
164
- @layer _base {
165
- .button {
166
- display: inline-flex;
167
- align-items: center;
168
- justify-content: center;
169
- border-radius: var(--radii-md);
170
- font-size: 0.875rem;
171
- line-height: 1.25rem;
172
- font-weight: var(--font-weights-medium);
173
- transition-property: var(--transition-prop, color, background-color, border-color, outline-color, text-decoration-color, fill, stroke);
174
- transition-timing-function: var(--transition-easing, cubic-bezier(0.4, 0, 0.2, 1));
175
- transition-duration: var(--transition-duration, 150ms);
176
- cursor: pointer;
177
- --shadow-panda-ring-offset-color: var(--colors-background);
178
- gap: var(--spacing-2);
179
- }
180
- .button:is(:disabled, [disabled], [data-disabled]) {
181
- pointer-events: none;
182
- opacity: 50%
183
- }
184
- .button:is(:focus-visible, [data-focus-visible]) {
185
- outline: 2px solid transparent;
186
- outline-offset: 2px;
187
- --shadow-panda-ring-offset-shadow: var(--shadow-panda-ring-inset,) 0 0 0 var(--shadow-panda-ring-offset-width, 0px) var(--shadow-panda-ring-offset-color);
188
- --shadow-panda-ring-shadow: var(--shadow-panda-ring-inset,) 0 0 0 calc(2px + var(--shadow-panda-ring-offset-width, 0px)) var(--shadow-panda-ring-color);
189
- box-shadow: var(--shadow-panda-ring-offset-shadow),var(--shadow-panda-ring-shadow),var(--shadow-panda-base-shadow,0 0 #0000);
190
- --shadow-panda-ring-color: var(--colors-ring)
191
- ;
192
- --shadow-panda-ring-offset-width: 2px
193
-
194
- }
195
- }
196
- }
@@ -1,26 +0,0 @@
1
- @layer utilities {
2
-
3
- .d_flex {
4
- display: flex
5
- }
6
-
7
- .items_center {
8
- align-items: center
9
- }
10
-
11
- .justify_center {
12
- justify-content: center
13
- }
14
-
15
- .h_container\.xs {
16
- height: var(--sizes-container-xs)
17
- }
18
-
19
- .rounded_common {
20
- border-radius: var(--radii-common)
21
- }
22
-
23
- .border_base {
24
- border: var(--borders-base)
25
- }
26
- }
@@ -1,70 +0,0 @@
1
- @layer base {
2
- :root {
3
- --made-with-panda: '🐼'
4
- }
5
-
6
- *, *::before, *::after, ::backdrop {
7
- --blur: ;
8
- --brightness: ;
9
- --contrast: ;
10
- --grayscale: ;
11
- --hue-rotate: ;
12
- --invert: ;
13
- --saturate: ;
14
- --sepia: ;
15
- --drop-shadow: ;
16
- --backdrop-blur: ;
17
- --backdrop-brightness: ;
18
- --backdrop-contrast: ;
19
- --backdrop-grayscale: ;
20
- --backdrop-hue-rotate: ;
21
- --backdrop-invert: ;
22
- --backdrop-opacity: ;
23
- --backdrop-saturate: ;
24
- --backdrop-sepia: ;
25
- --scroll-snap-strictness: proximity;
26
- --border-spacing-x: 0;
27
- --border-spacing-y: 0;
28
- --translate-x: 0;
29
- --translate-y: 0;
30
- --rotate: 0;
31
- --skew-x: 0;
32
- --skew-y: 0;
33
- --scale-x: 1;
34
- --scale-y: 1
35
- }
36
-
37
- html {
38
- -moz-osx-font-smoothing: grayscale;
39
- text-rendering: optimizeLegibility;
40
- -webkit-font-smoothing: antialiased;
41
- -webkit-text-size-adjust: 100%
42
- }
43
-
44
- body {
45
- background: var(--colors-background);
46
- color: var(--colors-foreground);
47
- font-family: var(--fonts-sans)
48
- }
49
-
50
- button {
51
- color: inherit;
52
- outline: 2px solid transparent
53
- }
54
-
55
- * {
56
- box-sizing: border-box;
57
- font-family: var(--fonts-body)
58
- }
59
-
60
- html, body {
61
- margin: var(--spacing-0);
62
- padding: var(--spacing-0)
63
- }
64
-
65
- #root {
66
- display: flex;
67
- flex-direction: column;
68
- height: 100vh
69
- }
70
- }
@@ -1,21 +0,0 @@
1
- /* eslint-disable */
2
- import type { SystemStyleObject, ConditionalValue } from '../types/index.d.mts';
3
- import type { Properties } from '../types/csstype.d.mts';
4
- import type { PropertyValue } from '../types/prop-type.d.mts';
5
- import type { DistributiveOmit } from '../types/system-types.d.mts';
6
- import type { Tokens } from '../tokens/index.d.mts';
7
-
8
- export interface LinkBoxProperties {
9
-
10
- }
11
-
12
-
13
- interface LinkBoxStyles extends LinkBoxProperties, DistributiveOmit<SystemStyleObject, keyof LinkBoxProperties > {}
14
-
15
- interface LinkBoxPatternFn {
16
- (styles?: LinkBoxStyles): string
17
- raw: (styles?: LinkBoxStyles) => SystemStyleObject
18
- }
19
-
20
-
21
- export declare const linkBox: LinkBoxPatternFn;
@@ -1,19 +0,0 @@
1
- import { mapObject } from '../helpers.mjs';
2
- import { css } from '../css/index.mjs';
3
-
4
- const linkBoxConfig = {
5
- transform(props) {
6
- return {
7
- position: "relative",
8
- "& :where(a, abbr)": {
9
- position: "relative",
10
- zIndex: "1"
11
- },
12
- ...props
13
- };
14
- }}
15
-
16
- export const getLinkBoxStyle = (styles = {}) => linkBoxConfig.transform(styles, { map: mapObject })
17
-
18
- export const linkBox = (styles) => css(getLinkBoxStyle(styles))
19
- linkBox.raw = getLinkBoxStyle
@@ -1 +0,0 @@
1
- /* eslint-disable */