otomato-sdk 2.0.66 → 2.0.67

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.
@@ -1,4 +1,4 @@
1
- export const SDK_VERSION = '2.0.66';
1
+ export const SDK_VERSION = '2.0.67';
2
2
  export function compareVersions(v1, v2) {
3
3
  // Split the version strings into parts
4
4
  const v1Parts = v1.split('.').map(Number);
@@ -1,48 +1,11 @@
1
+ // workflowNodePositioner.ts
1
2
  export const xSpacing = 500;
2
3
  export const ySpacing = 120;
3
4
  export const ROOT_X = 400;
4
5
  export const ROOT_Y = 120;
5
- export function positionWorkflowNodesAvoidOverlap(workflow) {
6
- // 1) Lay out nodes using existing logic
7
- positionWorkflowNodes(workflow);
8
- // 2) Group nodes by 'level' based on vertical position
9
- const levels = new Map();
10
- workflow.nodes.forEach((node) => {
11
- if (node.position) {
12
- const level = Math.round(node.position.y / ySpacing);
13
- if (!levels.has(level)) {
14
- levels.set(level, []);
15
- }
16
- levels.get(level).push(node);
17
- }
18
- });
19
- // 3) Resolve horizontal overlaps among *all* nodes in each level
20
- levels.forEach((levelNodes) => {
21
- // Sort the nodes in this level by X
22
- levelNodes.sort((a, b) => { var _a, _b; return ((_a = a.position.x) !== null && _a !== void 0 ? _a : 0) - ((_b = b.position.x) !== null && _b !== void 0 ? _b : 0); });
23
- // Walk left-to-right, shifting nodes that would collide with the previous one
24
- for (let i = 1; i < levelNodes.length; i++) {
25
- const prev = levelNodes[i - 1];
26
- const current = levelNodes[i];
27
- // Compute how close they are
28
- const dx = current.position.x - prev.position.x;
29
- // If they are too close, shift the current node (and its children) to the right
30
- if (dx < xSpacing) {
31
- const shift = xSpacing - dx;
32
- moveNodeAndChildren(current, shift, workflow.edges);
33
- }
34
- }
35
- });
36
- // 4) Re-center parents above their children in a bottom-up pass
37
- centerParentXPositions(workflow);
38
- }
39
- /**
40
- * Lays out the workflow nodes in a rough top-down manner,
41
- * then positions each node relative to its parent.
42
- */
43
6
  export function positionWorkflowNodes(workflow) {
44
7
  try {
45
- // Step 1: Find the starting nodes
8
+ // Step 1: Find the starting nodes using identityStartingNodes function
46
9
  const startingNodes = identityStartingNodes(workflow);
47
10
  // Step 2: Place the starting nodes
48
11
  let xPosition = ROOT_X;
@@ -50,7 +13,7 @@ export function positionWorkflowNodes(workflow) {
50
13
  startNode.setPosition(xPosition, ROOT_Y);
51
14
  xPosition += xSpacing;
52
15
  });
53
- // Step 3: For all other nodes, position them based on their parent(s)
16
+ // Step 3: Place all other nodes relative to their parents
54
17
  const nodesToPosition = workflow.nodes.filter((node) => !startingNodes.includes(node));
55
18
  nodesToPosition.forEach((node) => positionNode(node, workflow.edges, xSpacing, ySpacing, workflow));
56
19
  }
@@ -60,13 +23,12 @@ export function positionWorkflowNodes(workflow) {
60
23
  }
61
24
  export function positionNode(node, edges, xSpacing, ySpacing, workflow) {
62
25
  const parents = getParents(node, edges);
63
- if (!parents.length)
64
- return; // Edge case: no parents?
65
- // Sort children of the first parent by edge labels (true/false) so "true" is left, "false" is right
66
- const children = getChildren(parents[0], edges).sort((a, b) => {
26
+ // todo: what if we have multiple parents?
27
+ const children = getChildren(parents[0], edges);
28
+ const sortedChildren = children.sort((a, b) => {
67
29
  var _a, _b;
68
- const edgeA = edges.find((edge) => edge.source === parents[0] && edge.target === a);
69
- const edgeB = edges.find((edge) => edge.source === parents[0] && edge.target === b);
30
+ const edgeA = edges.find(edge => edge.source === parents[0] && edge.target === a);
31
+ const edgeB = edges.find(edge => edge.source === parents[0] && edge.target === b);
70
32
  const labelA = (_a = edgeA === null || edgeA === void 0 ? void 0 : edgeA.label) !== null && _a !== void 0 ? _a : "";
71
33
  const labelB = (_b = edgeB === null || edgeB === void 0 ? void 0 : edgeB.label) !== null && _b !== void 0 ? _b : "";
72
34
  if (labelA === "true" && labelB !== "true")
@@ -79,25 +41,78 @@ export function positionNode(node, edges, xSpacing, ySpacing, workflow) {
79
41
  return -1;
80
42
  return 0;
81
43
  });
82
- const parentX = parents.reduce((sum, p) => { var _a, _b; return sum + ((_b = (_a = p.position) === null || _a === void 0 ? void 0 : _a.x) !== null && _b !== void 0 ? _b : ROOT_X); }, 0) /
83
- parents.length;
84
- const parentY = Math.max(...parents.map((p) => { var _a, _b; return (_b = (_a = p.position) === null || _a === void 0 ? void 0 : _a.y) !== null && _b !== void 0 ? _b : ROOT_Y; }));
85
- if (children.length <= 1) {
44
+ const childrenCountOfParent = sortedChildren.length;
45
+ const parentX = parents.reduce((sum, parent) => { var _a, _b; return sum + ((_b = (_a = parent.position) === null || _a === void 0 ? void 0 : _a.x) !== null && _b !== void 0 ? _b : ROOT_X); }, 0) / parents.length;
46
+ const parentY = Math.max(...parents.map(parent => { var _a, _b; return (_b = (_a = parent.position) === null || _a === void 0 ? void 0 : _a.y) !== null && _b !== void 0 ? _b : ROOT_Y; }));
47
+ if (childrenCountOfParent === 1) {
86
48
  node.setPosition(parentX, parentY + ySpacing);
87
49
  }
88
50
  else {
89
- // This node’s index among siblings
90
- const index = children.indexOf(node);
91
- const offset = index - (children.length - 1) / 2;
51
+ const index = sortedChildren.indexOf(node); // Get the position of this node among its siblings
52
+ const totalChildren = sortedChildren.length;
53
+ // Compute the x position for this node
54
+ const offset = index - (totalChildren - 1) / 2; // Center the children around the parent
92
55
  node.setPosition(parentX + offset * xSpacing, parentY + ySpacing);
93
56
  }
94
57
  }
58
+ export function positionWorkflowNodesAvoidOverlap(workflow) {
59
+ const levels = new Map();
60
+ function addToLevel(node) {
61
+ const level = Math.round(node.position.y / ySpacing);
62
+ if (!levels.has(level)) {
63
+ levels.set(level, []);
64
+ }
65
+ levels.get(level).push(node);
66
+ }
67
+ // 1) Lay out nodes using existing logic
68
+ positionWorkflowNodes(workflow);
69
+ // 2) Fill the `levels` map
70
+ workflow.nodes.forEach((node) => {
71
+ if (node.position) {
72
+ addToLevel(node);
73
+ }
74
+ });
75
+ // 3) Resolve horizontal overlaps and enforce parent grouping
76
+ levels.forEach((nodes, level) => {
77
+ // Group nodes by their parent
78
+ const parentGroups = new Map();
79
+ nodes.forEach((node) => {
80
+ const parents = getParents(node, workflow.edges);
81
+ if (parents.length > 0) {
82
+ const parent = parents[0]; // Assuming single parent for simplicity
83
+ if (!parentGroups.has(parent)) {
84
+ parentGroups.set(parent, []);
85
+ }
86
+ parentGroups.get(parent).push(node);
87
+ }
88
+ });
89
+ // Flatten the groups back into a sorted array
90
+ const groupedNodes = [];
91
+ parentGroups.forEach((group) => {
92
+ groupedNodes.push(...group);
93
+ });
94
+ // Sort nodes by x within each group to detect overlaps
95
+ groupedNodes.sort((a, b) => { var _a, _b; return ((_a = a.position.x) !== null && _a !== void 0 ? _a : 0) - ((_b = b.position.x) !== null && _b !== void 0 ? _b : 0); });
96
+ // Shift nodes within groups to resolve overlaps
97
+ for (let i = 1; i < groupedNodes.length; i++) {
98
+ const prev = groupedNodes[i - 1];
99
+ const current = groupedNodes[i];
100
+ if (current.position.x - prev.position.x < xSpacing) {
101
+ const shift = xSpacing - (current.position.x - prev.position.x);
102
+ moveNodeAndChildren(current, shift, workflow.edges);
103
+ }
104
+ }
105
+ });
106
+ // 4) **Center each parent over its children** (the existing step)
107
+ centerParentXPositions(workflow);
108
+ }
95
109
  /**
96
- * Repositions each parent node so that its X = average of its children’s X.
110
+ * Repositions each parent node so that its X is the average of the children’s X.
111
+ * We do a simple bottom-up pass: start with all leaves, then move upward to parents.
97
112
  */
98
113
  function centerParentXPositions(workflow) {
99
114
  var _a, _b;
100
- // Identify the leaf nodes to start a bottom-up pass
115
+ // Identify the leaf nodes
101
116
  const queue = identifyLeafNodes(workflow);
102
117
  while (queue.length > 0) {
103
118
  const child = queue.shift();
@@ -105,59 +120,57 @@ function centerParentXPositions(workflow) {
105
120
  for (const parent of parents) {
106
121
  const children = getChildren(parent, workflow.edges);
107
122
  if (children.length) {
123
+ // Average x of all children
108
124
  const sumX = children.reduce((acc, c) => { var _a, _b; return acc + ((_b = (_a = c.position) === null || _a === void 0 ? void 0 : _a.x) !== null && _b !== void 0 ? _b : 0); }, 0);
109
125
  const avgX = sumX / children.length;
126
+ // Move parent to that average, keep same y
110
127
  parent.setPosition(avgX, (_b = (_a = parent.position) === null || _a === void 0 ? void 0 : _a.y) !== null && _b !== void 0 ? _b : 0);
111
128
  }
112
- // Push this parent upward in the queue to continue up the chain
129
+ // Add this parent to the queue so we can recurse upward
113
130
  if (!queue.includes(parent)) {
114
131
  queue.push(parent);
115
132
  }
116
133
  }
117
134
  }
118
135
  }
119
- /**
120
- * Recursively shifts a node and all its descendants by `shift` in the x-direction.
121
- */
122
136
  function moveNodeAndChildren(node, shift, edges) {
137
+ // Move the node
123
138
  node.setPosition(node.position.x + shift, node.position.y);
139
+ // Propagate to children
124
140
  edges
125
141
  .filter((edge) => edge.source === node)
126
142
  .forEach((edge) => {
127
143
  moveNodeAndChildren(edge.target, shift, edges);
128
144
  });
129
145
  }
130
- /**
131
- * A "leaf" node is one that has no children (it’s never the source of an edge).
132
- */
133
146
  export function identifyLeafNodes(workflow) {
134
- const sources = new Set(workflow.edges.map((edge) => edge.source.getRef()));
135
- return workflow.nodes.filter((node) => !sources.has(node.getRef()));
147
+ const nonLeafNodes = new Set(workflow.edges.map(edge => edge.source.getRef()));
148
+ return workflow.nodes.filter(node => !nonLeafNodes.has(node.getRef()));
136
149
  }
137
150
  /**
138
- * Identify starting nodes (no incoming edges).
151
+ * Identifies starting nodes (nodes with no incoming edges).
152
+ * A starting node is defined as one that is not a target of any edge.
153
+ *
154
+ * @param workflow The workflow to analyze.
155
+ * @returns An array of nodes that have no incoming edges.
139
156
  */
140
157
  export function identityStartingNodes(workflow) {
141
158
  const childRefs = new Set(workflow.edges.map((edge) => edge.target.getRef()));
142
159
  return workflow.nodes.filter((node) => !childRefs.has(node.getRef()));
143
160
  }
144
161
  export function getChildren(node, edges) {
145
- return edges
146
- .filter((edge) => edge.source === node)
147
- .map((edge) => edge.target);
162
+ return edges.filter(edge => edge.source === node).map(edge => edge.target);
148
163
  }
149
164
  export function getParents(node, edges) {
150
- return edges
151
- .filter((edge) => edge.target === node)
152
- .map((edge) => edge.source);
165
+ return edges.filter(edge => edge.target === node).map(edge => edge.source);
153
166
  }
154
167
  export function getEdges(node, edges) {
155
- return edges.filter((edge) => edge.source === node || edge.target === node);
168
+ return edges.filter(edge => edge.source === node || edge.target === node);
156
169
  }
157
170
  export function getEndNodePositions(workflow) {
158
171
  return workflow.nodes
159
- .filter((node) => getChildren(node, workflow.edges).length === 0)
160
- .map((node) => {
172
+ .filter(node => getChildren(node, workflow.edges).length === 0) // node with no children
173
+ .map(node => {
161
174
  var _a, _b, _c, _d;
162
175
  return ({
163
176
  x: (_b = (_a = node.position) === null || _a === void 0 ? void 0 : _a.x) !== null && _b !== void 0 ? _b : 0,
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,2 +1,2 @@
1
- export declare const SDK_VERSION = "2.0.66";
1
+ export declare const SDK_VERSION = "2.0.67";
2
2
  export declare function compareVersions(v1: string, v2: string): number;
@@ -5,19 +5,16 @@ export declare const xSpacing = 500;
5
5
  export declare const ySpacing = 120;
6
6
  export declare const ROOT_X = 400;
7
7
  export declare const ROOT_Y = 120;
8
- export declare function positionWorkflowNodesAvoidOverlap(workflow: Workflow): void;
9
- /**
10
- * Lays out the workflow nodes in a rough top-down manner,
11
- * then positions each node relative to its parent.
12
- */
13
8
  export declare function positionWorkflowNodes(workflow: Workflow): void;
14
9
  export declare function positionNode(node: Node, edges: Edge[], xSpacing: number, ySpacing: number, workflow: Workflow): void;
15
- /**
16
- * A "leaf" node is one that has no children (it’s never the source of an edge).
17
- */
10
+ export declare function positionWorkflowNodesAvoidOverlap(workflow: Workflow): void;
18
11
  export declare function identifyLeafNodes(workflow: Workflow): Node[];
19
12
  /**
20
- * Identify starting nodes (no incoming edges).
13
+ * Identifies starting nodes (nodes with no incoming edges).
14
+ * A starting node is defined as one that is not a target of any edge.
15
+ *
16
+ * @param workflow The workflow to analyze.
17
+ * @returns An array of nodes that have no incoming edges.
21
18
  */
22
19
  export declare function identityStartingNodes(workflow: Workflow): Node[];
23
20
  export declare function getChildren(node: Node, edges: Edge[]): Node[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "otomato-sdk",
3
- "version": "2.0.66",
3
+ "version": "2.0.67",
4
4
  "description": "An SDK for building and managing automations on Otomato",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/types/src/index.d.ts",