otomato-sdk 2.0.200 → 2.0.201
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/src/constants/WorkflowTemplates.js +209 -3
- package/dist/src/constants/version.js +1 -1
- package/dist/src/utils/WorkflowNodePositioner.js +160 -50
- package/dist/src/utils/helpers.js +0 -1
- package/dist/types/examples/Core/create-workflow-with-multiple-triggers.d.ts +2 -0
- package/dist/types/src/constants/WorkflowTemplates.d.ts +2 -0
- package/dist/types/src/constants/version.d.ts +1 -1
- package/dist/types/src/utils/WorkflowNodePositioner.d.ts +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Workflow, Trigger, Action, Edge, TRIGGERS, ACTIONS, CHAINS, getTokenFromSymbol, convertToTokenUnitsFromSymbol, convertToTokenUnits } from '../index.js';
|
|
1
|
+
import { Workflow, Trigger, Action, Edge, TRIGGERS, ACTIONS, CHAINS, getTokenFromSymbol, convertToTokenUnitsFromSymbol, convertToTokenUnits, WORKFLOW_LOOPING_TYPES } from '../index.js';
|
|
2
2
|
export const WORKFLOW_TEMPLATES_TAGS = {
|
|
3
3
|
NFTS: 'NFTs',
|
|
4
4
|
SOCIALS: 'Socials',
|
|
@@ -6,7 +6,9 @@ export const WORKFLOW_TEMPLATES_TAGS = {
|
|
|
6
6
|
ON_CHAIN_MONITORING: 'On-chain monitoring',
|
|
7
7
|
YIELD: 'Yield',
|
|
8
8
|
NOTIFICATIONS: 'notifications',
|
|
9
|
-
ABSTRACT: 'Abstract'
|
|
9
|
+
ABSTRACT: 'Abstract',
|
|
10
|
+
DEXES: 'Dexes',
|
|
11
|
+
LENDING: 'Lending'
|
|
10
12
|
};
|
|
11
13
|
const createModeTransferNotificationWorkflow = () => {
|
|
12
14
|
const modeTransferTrigger = new Trigger(TRIGGERS.TOKENS.TRANSFER.TRANSFER);
|
|
@@ -192,9 +194,123 @@ const abstractGetNotifiedWhenStreamerIsLive = async () => {
|
|
|
192
194
|
const edge = new Edge({ source: trigger, target: telegramAction });
|
|
193
195
|
return new Workflow('Get notified when a given streamer goes live', [trigger, telegramAction], [edge]);
|
|
194
196
|
};
|
|
197
|
+
// notify me when I can unstake my stakestone
|
|
198
|
+
const createStakestoneUnstakeNotificationWorkflow = async () => {
|
|
199
|
+
const trigger = new Trigger(TRIGGERS.YIELD.STAKESTONE.LATEST_ROUND_ID);
|
|
200
|
+
trigger.setParams('chainId', CHAINS.ETHEREUM);
|
|
201
|
+
trigger.setParams('contractAddress', "0x8f88ae3798e8ff3d0e0de7465a0863c9bbb577f0");
|
|
202
|
+
trigger.setCondition('neq');
|
|
203
|
+
trigger.setComparisonValue('{{history.0.value}}');
|
|
204
|
+
trigger.setPosition(400, 120);
|
|
205
|
+
const notificationAction = new Action(ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE);
|
|
206
|
+
notificationAction.setParams("message", "You can now unstake your Stakestone position!");
|
|
207
|
+
notificationAction.setPosition(400, 240);
|
|
208
|
+
const edge = new Edge({ source: trigger, target: notificationAction });
|
|
209
|
+
return new Workflow('Get notified when you can unstake your Stakestone position', [trigger, notificationAction], [edge]);
|
|
210
|
+
};
|
|
211
|
+
// notify me when a given uniswap position is out of range [looping enabled - 5 times]
|
|
212
|
+
const createUniswapPositionOutOfRangeNotificationWorkflow = async () => {
|
|
213
|
+
const trigger = new Trigger(TRIGGERS.DEXES.UNISWAP.IS_IN_RANGE);
|
|
214
|
+
trigger.setParams('chainId', CHAINS.ETHEREUM);
|
|
215
|
+
trigger.setCondition('eq');
|
|
216
|
+
trigger.setParams('comparisonValue', false);
|
|
217
|
+
trigger.setPosition(400, 120);
|
|
218
|
+
const notificationAction = new Action(ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE);
|
|
219
|
+
notificationAction.setParams("message", "Your Uniswap position is out of range! You can obtain your position Id at https://app.uniswap.org/positions");
|
|
220
|
+
notificationAction.setPosition(400, 240);
|
|
221
|
+
const edge = new Edge({ source: trigger, target: notificationAction });
|
|
222
|
+
const workflow = new Workflow('Get notified when a given uniswap position is out of range', [trigger, notificationAction], [edge]);
|
|
223
|
+
workflow.setSettings({
|
|
224
|
+
loopingType: WORKFLOW_LOOPING_TYPES.POLLING,
|
|
225
|
+
period: 600000,
|
|
226
|
+
limit: 5,
|
|
227
|
+
});
|
|
228
|
+
return workflow;
|
|
229
|
+
};
|
|
230
|
+
// notify me when Hyperlend raise their deposit cap for stHype [looping enabled - 10 times]
|
|
231
|
+
const createHyperLendDepositCapNotificationWorkflow = async () => {
|
|
232
|
+
const trigger = new Trigger(TRIGGERS.LENDING.HYPERLEND.SUPPLY_CAP);
|
|
233
|
+
trigger.setParams('chainId', CHAINS.HYPER_EVM);
|
|
234
|
+
trigger.setParams('asset', getTokenFromSymbol(CHAINS.HYPER_EVM, 'wstHYPE').contractAddress);
|
|
235
|
+
trigger.setCondition('gt');
|
|
236
|
+
trigger.setComparisonValue('{{history.0.value}}');
|
|
237
|
+
trigger.setPosition(400, 120);
|
|
238
|
+
const notificationAction = new Action(ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE);
|
|
239
|
+
notificationAction.setParams("message", "Hyperlend has raised their deposit cap for stHype!");
|
|
240
|
+
notificationAction.setPosition(400, 240);
|
|
241
|
+
const edge = new Edge({ source: trigger, target: notificationAction });
|
|
242
|
+
const workflow = new Workflow('Get notified when Hyperlend raise their deposit cap for stHype', [trigger, notificationAction], [edge]);
|
|
243
|
+
workflow.setSettings({
|
|
244
|
+
loopingType: WORKFLOW_LOOPING_TYPES.POLLING,
|
|
245
|
+
period: 600000,
|
|
246
|
+
limit: 10,
|
|
247
|
+
});
|
|
248
|
+
return workflow;
|
|
249
|
+
};
|
|
250
|
+
// Save all the current yields for USDC on base (AAVE, Compound, Moonwell & top 5 USDC morpho vault) every hour [repeat 100 times, every hour]
|
|
251
|
+
const createUSDCYieldsStorageWorkflow = async () => {
|
|
252
|
+
const trigger = new Trigger(TRIGGERS.CORE.EVERY_PERIOD.EVERY_PERIOD);
|
|
253
|
+
trigger.setParams('period', 3600000);
|
|
254
|
+
trigger.setParams('limit', 100);
|
|
255
|
+
trigger.setPosition(400, 120);
|
|
256
|
+
const notificationAction = new Action(ACTIONS.OTHERS.GSHEET.GSHEET);
|
|
257
|
+
notificationAction.setParams("data", [
|
|
258
|
+
["aave", "moonwell", "compound", "Spark USDC Vault", "Moonwell Flagship USDC", "Seamless USDC Vault", "Steakhouse USDC", "Gauntlet USDC Prime"],
|
|
259
|
+
[
|
|
260
|
+
"{{external.functions.aaveLendingRate(8453,0x833589fcd6edb6e08f4c7c32d4f71b54bda02913,,)}}",
|
|
261
|
+
"{{external.functions.moonwellLendingRate(8453,0x833589fcd6edb6e08f4c7c32d4f71b54bda02913,,)}}",
|
|
262
|
+
"{{external.functions.compoundLendingRate(8453,0x833589fcd6edb6e08f4c7c32d4f71b54bda02913,,,)}}",
|
|
263
|
+
"{{external.functions.morphoLendingRate(8453,0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A)}}",
|
|
264
|
+
"{{external.functions.morphoLendingRate(8453,0xc1256Ae5FF1cf2719D4937adb3bbCCab2E00A2Ca)}}",
|
|
265
|
+
"{{external.functions.morphoLendingRate(8453,0x616a4E1db48e22028f6bbf20444Cd3b8e3273738)}}",
|
|
266
|
+
"{{external.functions.morphoLendingRate(8453,0xbeeF010f9cb27031ad51e3333f9aF9C6B1228183)}}",
|
|
267
|
+
"{{external.functions.morphoLendingRate(8453,0xeE8F4eC5672F09119b96Ab6fB59C27E1b7e44b61)}}"
|
|
268
|
+
]
|
|
269
|
+
]);
|
|
270
|
+
// notificationAction.setParams("mode", "append");
|
|
271
|
+
// notificationAction.setParams("role", "writer");
|
|
272
|
+
// notificationAction.setParams("sheetId", "0");
|
|
273
|
+
// notificationAction.setParams("spreadsheetId", "1NxqGqgtUQkojBOl9g7CBkbqc7bB6mBkZxHWMPsu1uQY");
|
|
274
|
+
notificationAction.setPosition(400, 240);
|
|
275
|
+
const edge = new Edge({ source: trigger, target: notificationAction });
|
|
276
|
+
return new Workflow('Save all the current yields for USDC on base (AAVE, Compound, Moonwell, Spark USDC Vault, Moonwell Flagship USDC, Seamless USDC Vault, Steakhouse USDC, Gauntlet USDC Prime) every hour', [trigger, notificationAction], [edge]);
|
|
277
|
+
};
|
|
278
|
+
// notify me when there are more than 50 ETH in available liquidity for instant withdrawal on Stakestone
|
|
279
|
+
const createStakestoneInstantWithdrawalNotificationWorkflow = async () => {
|
|
280
|
+
const trigger = new Trigger(TRIGGERS.YIELD.STAKESTONE.STAKESTONE_VAULT_LIQUIDITY);
|
|
281
|
+
trigger.setParams('chainId', CHAINS.ETHEREUM);
|
|
282
|
+
trigger.setCondition('gt');
|
|
283
|
+
trigger.setComparisonValue('50');
|
|
284
|
+
trigger.setPosition(400, 120);
|
|
285
|
+
const notificationAction = new Action(ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE);
|
|
286
|
+
notificationAction.setParams("message", "There are more than 50 ETH in available liquidity for instant withdrawal on Stakestone!");
|
|
287
|
+
notificationAction.setPosition(400, 240);
|
|
288
|
+
const edge = new Edge({ source: trigger, target: notificationAction });
|
|
289
|
+
return new Workflow('Get notified when there are more than 50 ETH in available liquidity for instant withdrawal on Stakestone', [trigger, notificationAction], [edge]);
|
|
290
|
+
};
|
|
291
|
+
// notify me when I receive USDC [looping enabled - 30 times]
|
|
292
|
+
const createUSDCReceiveNotificationWorkflow = async () => {
|
|
293
|
+
const trigger = new Trigger(TRIGGERS.TOKENS.TRANSFER.TRANSFER);
|
|
294
|
+
trigger.setParams('chainId', CHAINS.BASE);
|
|
295
|
+
trigger.setParams('contractAddress', getTokenFromSymbol(CHAINS.BASE, 'USDC').contractAddress);
|
|
296
|
+
// TODO: add smart account address
|
|
297
|
+
trigger.setParams('abiParams.to', '{{smartAccountAddress}}');
|
|
298
|
+
trigger.setPosition(400, 120);
|
|
299
|
+
const notificationAction = new Action(ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE);
|
|
300
|
+
notificationAction.setParams("message", "You received USDC!");
|
|
301
|
+
notificationAction.setPosition(400, 240);
|
|
302
|
+
const edge = new Edge({ source: trigger, target: notificationAction });
|
|
303
|
+
const workflow = new Workflow('Get notified when I receive USDC', [trigger, notificationAction], [edge]);
|
|
304
|
+
workflow.setSettings({
|
|
305
|
+
loopingType: WORKFLOW_LOOPING_TYPES.SUBSCRIPTION,
|
|
306
|
+
timeout: 31536000000,
|
|
307
|
+
limit: 30,
|
|
308
|
+
});
|
|
309
|
+
return workflow;
|
|
310
|
+
};
|
|
195
311
|
const createEthereumFoundationTransferNotificationWorkflow = () => {
|
|
196
312
|
const ethTransferTrigger = new Trigger(TRIGGERS.TOKENS.TRANSFER.NATIVE_TRANSFER);
|
|
197
|
-
ethTransferTrigger.setChainId(CHAINS.
|
|
313
|
+
ethTransferTrigger.setChainId(CHAINS.ETHEREUM);
|
|
198
314
|
ethTransferTrigger.setParams('wallet', '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe');
|
|
199
315
|
ethTransferTrigger.setParams('threshold', 0.004);
|
|
200
316
|
ethTransferTrigger.setPosition(400, 120);
|
|
@@ -407,4 +523,94 @@ export const WORKFLOW_TEMPLATES = [
|
|
|
407
523
|
],
|
|
408
524
|
createWorkflow: copyTradeVitalikOdos
|
|
409
525
|
},*/
|
|
526
|
+
{
|
|
527
|
+
'name': 'Get notified when you can unstake your Stakestone position',
|
|
528
|
+
'description': 'Notify me when you can unstake your Stakestone position',
|
|
529
|
+
'tags': [WORKFLOW_TEMPLATES_TAGS.YIELD, WORKFLOW_TEMPLATES_TAGS.NOTIFICATIONS],
|
|
530
|
+
'thumbnail': 'https://otomato-sdk-images.s3.eu-west-1.amazonaws.com/templates/dailyYieldUpdates.jpg',
|
|
531
|
+
'image': [
|
|
532
|
+
TRIGGERS.YIELD.STAKESTONE.STAKESTONE_VAULT_LIQUIDITY.image,
|
|
533
|
+
ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE.image
|
|
534
|
+
],
|
|
535
|
+
'blockIDs': [
|
|
536
|
+
TRIGGERS.YIELD.STAKESTONE.STAKESTONE_VAULT_LIQUIDITY.blockId,
|
|
537
|
+
ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE.blockId
|
|
538
|
+
],
|
|
539
|
+
createWorkflow: createStakestoneUnstakeNotificationWorkflow
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
'name': 'Get notified when a given uniswap position is out of range',
|
|
543
|
+
'description': 'Notify me when a given uniswap position is out of range. Get your tokenId from https://app.uniswap.org/positions',
|
|
544
|
+
'tags': [WORKFLOW_TEMPLATES_TAGS.DEXES, WORKFLOW_TEMPLATES_TAGS.NOTIFICATIONS],
|
|
545
|
+
'thumbnail': 'https://otomato-sdk-images.s3.eu-west-1.amazonaws.com/templates/dailyYieldUpdates.jpg',
|
|
546
|
+
'image': [
|
|
547
|
+
TRIGGERS.DEXES.UNISWAP.IS_IN_RANGE.image,
|
|
548
|
+
ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE.image
|
|
549
|
+
],
|
|
550
|
+
'blockIDs': [
|
|
551
|
+
TRIGGERS.DEXES.UNISWAP.IS_IN_RANGE.blockId,
|
|
552
|
+
ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE.blockId
|
|
553
|
+
],
|
|
554
|
+
createWorkflow: createUniswapPositionOutOfRangeNotificationWorkflow
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
'name': 'Get notified when Hyperlend raise their deposit cap for stHype',
|
|
558
|
+
'description': 'Notify me when Hyperlend raise their deposit cap for stHype',
|
|
559
|
+
'tags': [WORKFLOW_TEMPLATES_TAGS.LENDING, WORKFLOW_TEMPLATES_TAGS.NOTIFICATIONS],
|
|
560
|
+
'thumbnail': 'https://otomato-sdk-images.s3.eu-west-1.amazonaws.com/templates/dailyYieldUpdates.jpg',
|
|
561
|
+
'image': [
|
|
562
|
+
TRIGGERS.LENDING.HYPERLEND.SUPPLY_CAP.image,
|
|
563
|
+
ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE.image
|
|
564
|
+
],
|
|
565
|
+
'blockIDs': [
|
|
566
|
+
TRIGGERS.LENDING.HYPERLEND.SUPPLY_CAP.blockId,
|
|
567
|
+
ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE.blockId
|
|
568
|
+
],
|
|
569
|
+
createWorkflow: createHyperLendDepositCapNotificationWorkflow
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
'name': 'Get notified when there are more than 50 ETH in available liquidity for instant withdrawal on Stakestone',
|
|
573
|
+
'description': 'Notify me when there are more than 50 ETH in available liquidity for instant withdrawal on Stakestone',
|
|
574
|
+
'tags': [WORKFLOW_TEMPLATES_TAGS.YIELD, WORKFLOW_TEMPLATES_TAGS.NOTIFICATIONS],
|
|
575
|
+
'thumbnail': 'https://otomato-sdk-images.s3.eu-west-1.amazonaws.com/templates/dailyYieldUpdates.jpg',
|
|
576
|
+
'image': [
|
|
577
|
+
TRIGGERS.YIELD.STAKESTONE.STAKESTONE_VAULT_LIQUIDITY.image,
|
|
578
|
+
ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE.image
|
|
579
|
+
],
|
|
580
|
+
'blockIDs': [
|
|
581
|
+
TRIGGERS.YIELD.STAKESTONE.STAKESTONE_VAULT_LIQUIDITY.blockId,
|
|
582
|
+
ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE.blockId
|
|
583
|
+
],
|
|
584
|
+
createWorkflow: createStakestoneInstantWithdrawalNotificationWorkflow
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
'name': 'Get notified when I receive USDC',
|
|
588
|
+
'description': 'Notify me when I receive USDC',
|
|
589
|
+
'tags': [WORKFLOW_TEMPLATES_TAGS.TRADING, WORKFLOW_TEMPLATES_TAGS.NOTIFICATIONS],
|
|
590
|
+
'thumbnail': 'https://otomato-sdk-images.s3.eu-west-1.amazonaws.com/templates/dailyYieldUpdates.jpg',
|
|
591
|
+
'image': [
|
|
592
|
+
TRIGGERS.TOKENS.TRANSFER.TRANSFER.image,
|
|
593
|
+
ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE.image
|
|
594
|
+
],
|
|
595
|
+
'blockIDs': [
|
|
596
|
+
TRIGGERS.TOKENS.TRANSFER.TRANSFER.blockId,
|
|
597
|
+
ACTIONS.NOTIFICATIONS.TELEGRAM.SEND_MESSAGE.blockId
|
|
598
|
+
],
|
|
599
|
+
createWorkflow: createUSDCReceiveNotificationWorkflow
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
'name': 'Save all the current yields for USDC on base (AAVE, Compound, Moonwell, Spark USDC Vault, Moonwell Flagship USDC, Seamless USDC Vault, Steakhouse USDC, Gauntlet USDC Prime) every hour',
|
|
603
|
+
'description': 'Save all the current yields for USDC on base (AAVE, Compound, Moonwell, Spark USDC Vault, Moonwell Flagship USDC, Seamless USDC Vault, Steakhouse USDC, Gauntlet USDC Prime) every hour',
|
|
604
|
+
'tags': [WORKFLOW_TEMPLATES_TAGS.YIELD, WORKFLOW_TEMPLATES_TAGS.NOTIFICATIONS],
|
|
605
|
+
'thumbnail': 'https://otomato-sdk-images.s3.eu-west-1.amazonaws.com/templates/dailyYieldUpdates.jpg',
|
|
606
|
+
'image': [
|
|
607
|
+
TRIGGERS.CORE.EVERY_PERIOD.EVERY_PERIOD.image,
|
|
608
|
+
ACTIONS.OTHERS.GSHEET.GSHEET.image
|
|
609
|
+
],
|
|
610
|
+
'blockIDs': [
|
|
611
|
+
TRIGGERS.CORE.EVERY_PERIOD.EVERY_PERIOD.blockId,
|
|
612
|
+
ACTIONS.OTHERS.GSHEET.GSHEET.blockId
|
|
613
|
+
],
|
|
614
|
+
createWorkflow: createUSDCYieldsStorageWorkflow
|
|
615
|
+
},
|
|
410
616
|
];
|
|
@@ -3,6 +3,7 @@ export const xSpacing = 500;
|
|
|
3
3
|
export const ySpacing = 120;
|
|
4
4
|
export const ROOT_X = 400;
|
|
5
5
|
export const ROOT_Y = 120;
|
|
6
|
+
export const TRIGGER_X_SPACING = 363;
|
|
6
7
|
/**
|
|
7
8
|
* Helper: Returns a group key for a node based on its primary parent.
|
|
8
9
|
* If a node has multiple parents, we sort them numerically (by their ref)
|
|
@@ -70,64 +71,123 @@ export function positionWorkflowNodes(workflow) {
|
|
|
70
71
|
const sortedLayers = Array.from(layers.keys()).sort((a, b) => a - b);
|
|
71
72
|
for (const layer of sortedLayers) {
|
|
72
73
|
const nodesInLayer = layers.get(layer);
|
|
73
|
-
// Group nodes by primary parent.
|
|
74
|
-
const groups = new Map();
|
|
75
|
-
for (const node of nodesInLayer) {
|
|
76
|
-
const groupKey = getGroupKey(node, workflow.edges);
|
|
77
|
-
if (!groups.has(groupKey)) {
|
|
78
|
-
groups.set(groupKey, []);
|
|
79
|
-
}
|
|
80
|
-
groups.get(groupKey).push(node);
|
|
81
|
-
}
|
|
82
74
|
// Determine the Y position for this layer.
|
|
83
75
|
const yPos = (layer * ySpacing) + ROOT_Y;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
76
|
+
// Special handling for layer 0 (triggers)
|
|
77
|
+
if (layer === 0) {
|
|
78
|
+
const startingNodes = nodesInLayer.filter(node => getParents(node, workflow.edges).length === 0);
|
|
79
|
+
startingNodes.sort((a, b) => Number(a.getRef()) - Number(b.getRef())); // Consistent ordering
|
|
80
|
+
const numStartingNodes = startingNodes.length;
|
|
81
|
+
let totalWidth = 0; // Initialize totalWidth
|
|
82
|
+
if (numStartingNodes > 0) {
|
|
83
|
+
totalWidth = (numStartingNodes - 1) * TRIGGER_X_SPACING; // Assign value to totalWidth
|
|
84
|
+
let currentX = ROOT_X - totalWidth / 2;
|
|
85
|
+
for (let i = 0; i < numStartingNodes; i++) {
|
|
86
|
+
startingNodes[i].setPosition(currentX, yPos);
|
|
87
|
+
currentX += TRIGGER_X_SPACING;
|
|
88
|
+
}
|
|
93
89
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
// Handle non-starting nodes in layer 0 if any (should be rare for triggers)
|
|
91
|
+
const otherNodesInLayer0 = nodesInLayer.filter(node => getParents(node, workflow.edges).length > 0);
|
|
92
|
+
if (otherNodesInLayer0.length > 0) {
|
|
93
|
+
// Fallback to default group positioning for these nodes
|
|
94
|
+
// This part reuses the existing grouping logic but only for these specific nodes.
|
|
95
|
+
const groups = new Map();
|
|
96
|
+
for (const node of otherNodesInLayer0) {
|
|
97
|
+
const groupKey = getGroupKey(node, workflow.edges);
|
|
98
|
+
if (!groups.has(groupKey)) {
|
|
99
|
+
groups.set(groupKey, []);
|
|
100
|
+
}
|
|
101
|
+
groups.get(groupKey).push(node);
|
|
102
|
+
}
|
|
103
|
+
// Simplified group processing for these non-starting nodes in layer 0
|
|
104
|
+
// Calculate currentXOffset based on whether startingNodes were present or not
|
|
105
|
+
const startingNodesOffset = numStartingNodes > 0 ? totalWidth / 2 + TRIGGER_X_SPACING : 0;
|
|
106
|
+
let currentXOffset = ROOT_X + startingNodesOffset;
|
|
107
|
+
// If there were no starting nodes, and we only have otherNodesInLayer0,
|
|
108
|
+
// they should probably start near ROOT_X, not offset by totalWidth/2.
|
|
109
|
+
// If startingNodes were there, offset by totalWidth/2 + some spacing.
|
|
110
|
+
// If numStartingNodes is 0, totalWidth is 0, so currentXOffset starts at ROOT_X.
|
|
111
|
+
// If numStartingNodes > 0, currentXOffset starts at ROOT_X + totalWidth/2 + TRIGGER_X_SPACING
|
|
112
|
+
// (using TRIGGER_X_SPACING as a gap, or xSpacing could be used too)
|
|
113
|
+
if (numStartingNodes > 0) {
|
|
114
|
+
currentXOffset = ROOT_X + totalWidth / 2 + xSpacing; // Place them after triggers with xSpacing
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
currentXOffset = ROOT_X; // If no triggers, start these at ROOT_X
|
|
118
|
+
}
|
|
119
|
+
const sortedGroupKeys = Array.from(groups.keys()).sort((a, b) => Number(a) - Number(b));
|
|
120
|
+
for (const key of sortedGroupKeys) {
|
|
121
|
+
const groupNodes = groups.get(key);
|
|
122
|
+
groupNodes.sort((a, b) => Number(a.getRef()) - Number(b.getRef()));
|
|
123
|
+
for (let i = 0; i < groupNodes.length; i++) {
|
|
124
|
+
groupNodes[i].setPosition(currentXOffset + i * xSpacing, yPos);
|
|
125
|
+
}
|
|
126
|
+
currentXOffset += groupNodes.length * xSpacing + xSpacing; // Add spacing between groups
|
|
127
|
+
}
|
|
97
128
|
}
|
|
98
|
-
const groupSize = groupNodes.length;
|
|
99
|
-
const width = (groupSize - 1) * xSpacing;
|
|
100
|
-
const desiredLeft = desiredCenter - width / 2;
|
|
101
|
-
groupInfos.push({
|
|
102
|
-
groupKey: key,
|
|
103
|
-
nodes: groupNodes,
|
|
104
|
-
desiredCenter,
|
|
105
|
-
desiredLeft,
|
|
106
|
-
width
|
|
107
|
-
});
|
|
108
129
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
130
|
+
else {
|
|
131
|
+
// Original logic for layers other than 0
|
|
132
|
+
// Group nodes by primary parent.
|
|
133
|
+
const groups = new Map();
|
|
134
|
+
for (const node of nodesInLayer) {
|
|
135
|
+
const groupKey = getGroupKey(node, workflow.edges);
|
|
136
|
+
if (!groups.has(groupKey)) {
|
|
137
|
+
groups.set(groupKey, []);
|
|
138
|
+
}
|
|
139
|
+
groups.get(groupKey).push(node);
|
|
117
140
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
141
|
+
const groupInfos = [];
|
|
142
|
+
// Sort group keys numerically.
|
|
143
|
+
const sortedGroupKeys = Array.from(groups.keys()).sort((a, b) => Number(a) - Number(b));
|
|
144
|
+
for (const key of sortedGroupKeys) {
|
|
145
|
+
const groupNodes = groups.get(key);
|
|
146
|
+
groupNodes.sort((a, b) => Number(a.getRef()) - Number(b.getRef()));
|
|
147
|
+
let desiredCenter;
|
|
148
|
+
if (key === "none") { // Should not happen for layer > 0 if graph is connected
|
|
149
|
+
desiredCenter = ROOT_X;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
const parent = workflow.nodes.find(n => n.getRef() === key);
|
|
153
|
+
desiredCenter = parent && parent.position ? parent.position.x : ROOT_X;
|
|
154
|
+
}
|
|
155
|
+
const groupSize = groupNodes.length;
|
|
156
|
+
const width = (groupSize - 1) * xSpacing;
|
|
157
|
+
const desiredLeft = desiredCenter - width / 2;
|
|
158
|
+
groupInfos.push({
|
|
159
|
+
groupKey: key,
|
|
160
|
+
nodes: groupNodes,
|
|
161
|
+
desiredCenter,
|
|
162
|
+
desiredLeft,
|
|
163
|
+
width
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// Adjust groups so that adjacent groups do not overlap.
|
|
167
|
+
// We require that the left edge of a group is at least xSpacing to the right of the previous group's right edge.
|
|
168
|
+
groupInfos.sort((a, b) => a.desiredLeft - b.desiredLeft);
|
|
169
|
+
let prevRight = -Infinity;
|
|
170
|
+
for (const group of groupInfos) {
|
|
171
|
+
let newLeft = group.desiredLeft;
|
|
172
|
+
if (newLeft < prevRight + xSpacing) {
|
|
173
|
+
newLeft = prevRight + xSpacing;
|
|
174
|
+
}
|
|
175
|
+
group.newLeft = newLeft;
|
|
176
|
+
prevRight = newLeft + group.width;
|
|
177
|
+
}
|
|
178
|
+
// Now assign positions for nodes in each group using the new left.
|
|
179
|
+
for (const group of groupInfos) {
|
|
180
|
+
const { nodes, newLeft } = group;
|
|
181
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
182
|
+
const nodeX = newLeft + i * xSpacing;
|
|
183
|
+
nodes[i].setPosition(nodeX, yPos);
|
|
184
|
+
}
|
|
127
185
|
}
|
|
128
186
|
}
|
|
129
187
|
}
|
|
130
188
|
// Step 3: Resolve overlaps within each group in each layer.
|
|
189
|
+
// This step might need adjustment if TRIGGER_X_SPACING causes issues with xSpacing based overlap.
|
|
190
|
+
// For now, we assume xSpacing is the minimum desired gap after initial placement.
|
|
131
191
|
layers.forEach((nodes, layer) => {
|
|
132
192
|
const groups = new Map();
|
|
133
193
|
for (const node of nodes) {
|
|
@@ -152,8 +212,19 @@ export function positionWorkflowNodes(workflow) {
|
|
|
152
212
|
const prev = groupNodes[i - 1];
|
|
153
213
|
const current = groupNodes[i];
|
|
154
214
|
const diff = current.position.x - prev.position.x;
|
|
155
|
-
|
|
156
|
-
|
|
215
|
+
// Determine the target spacing for overlap resolution
|
|
216
|
+
let targetSpacing = xSpacing;
|
|
217
|
+
if (layer === 0) {
|
|
218
|
+
// More direct check: if all nodes currently being processed together in this group are starting nodes.
|
|
219
|
+
const allNodesInGroupAreStartingNodes = groupNodes.every(gn => getParents(gn, workflow.edges).length === 0);
|
|
220
|
+
if (allNodesInGroupAreStartingNodes) {
|
|
221
|
+
// This condition implies that the group being processed should indeed be the "none" group,
|
|
222
|
+
// consisting only of starting nodes.
|
|
223
|
+
targetSpacing = TRIGGER_X_SPACING;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (diff < targetSpacing) {
|
|
227
|
+
const shift = targetSpacing - diff;
|
|
157
228
|
moveNodeAndChildren(current, shift, workflow.edges);
|
|
158
229
|
changed = true;
|
|
159
230
|
}
|
|
@@ -161,6 +232,32 @@ export function positionWorkflowNodes(workflow) {
|
|
|
161
232
|
}
|
|
162
233
|
});
|
|
163
234
|
});
|
|
235
|
+
// Additional step: Ensure common children of multiple layer 0 triggers are centered at ROOT_X.
|
|
236
|
+
// This runs after initial layer placement and before final overlap resolution specific to children groups might occur,
|
|
237
|
+
// and crucially before parent centering which might pull triggers away.
|
|
238
|
+
for (const node of workflow.nodes) {
|
|
239
|
+
const nodeLayer = node.layer;
|
|
240
|
+
if (nodeLayer === 1) { // Check nodes in the layer immediately following triggers
|
|
241
|
+
const parents = getParents(node, workflow.edges);
|
|
242
|
+
if (parents.length > 1) { // Only if it has multiple parents
|
|
243
|
+
const allParentsAreLayer0Triggers = parents.every(p => {
|
|
244
|
+
const parentLayer = p.layer;
|
|
245
|
+
const parentIsStartingNode = getParents(p, workflow.edges).length === 0;
|
|
246
|
+
return parentLayer === 0 && parentIsStartingNode;
|
|
247
|
+
});
|
|
248
|
+
if (allParentsAreLayer0Triggers) {
|
|
249
|
+
if (node.position) { // Ensure node.position exists before trying to read node.position.y
|
|
250
|
+
node.setPosition(ROOT_X, node.position.y);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
// This case should ideally not happen if nodes in layer 1 are already positioned by the main loop.
|
|
254
|
+
// If it does, assign a default Y based on its layer.
|
|
255
|
+
node.setPosition(ROOT_X, (nodeLayer * ySpacing) + ROOT_Y);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
164
261
|
// Step 4: Bottom-up Parent Centering.
|
|
165
262
|
// Now, we simply set each parent's x to the exact average of its children.
|
|
166
263
|
centerParentXPositions(workflow);
|
|
@@ -193,6 +290,19 @@ function centerParentXPositions(workflow) {
|
|
|
193
290
|
const child = queue.shift();
|
|
194
291
|
const parents = getParents(child, workflow.edges);
|
|
195
292
|
for (const parent of parents) {
|
|
293
|
+
// Check if the parent is a layer 0 starting node.
|
|
294
|
+
// Layer information should be available on the node from the assignLayers step.
|
|
295
|
+
const parentLayer = parent.layer;
|
|
296
|
+
const parentIsStartingNode = getParents(parent, workflow.edges).length === 0;
|
|
297
|
+
if (parentLayer === 0 && parentIsStartingNode) {
|
|
298
|
+
// Do not change the X position of layer 0 starting nodes.
|
|
299
|
+
// Still add to queue if not already present, for processing its own parents if it had any (though starting nodes don't).
|
|
300
|
+
// More importantly, it ensures the loop continues correctly for other parents of the current 'child'.
|
|
301
|
+
if (!queue.includes(parent)) {
|
|
302
|
+
queue.push(parent);
|
|
303
|
+
}
|
|
304
|
+
continue; // Skip X-position adjustment for this parent
|
|
305
|
+
}
|
|
196
306
|
const children = getChildren(parent, workflow.edges);
|
|
197
307
|
if (children.length) {
|
|
198
308
|
const xs = children.map(c => c.position.x);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "2.0.
|
|
1
|
+
export declare const SDK_VERSION = "2.0.201";
|
|
2
2
|
export declare function compareVersions(v1: string, v2: string): number;
|
|
@@ -5,6 +5,7 @@ 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 const TRIGGER_X_SPACING = 363;
|
|
8
9
|
/**
|
|
9
10
|
* Main function that positions workflow nodes using a hierarchical layout.
|
|
10
11
|
* Nodes are grouped by their primary parent.
|