@adatechnology/conversations-ui 0.1.0-rc.29 → 0.1.0-rc.30
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/flows/index.d.ts +104 -8
- package/dist/flows/index.js +188 -157
- package/package.json +1 -1
- package/src/flows/FlowsWorkspace.tsx +94 -312
- package/src/flows/flowCanvasModel.test.ts +293 -0
- package/src/flows/flowCanvasModel.ts +342 -0
- package/src/flows/index.ts +21 -0
package/dist/flows/index.js
CHANGED
|
@@ -1347,61 +1347,60 @@ function isGraphDirty(working, published) {
|
|
|
1347
1347
|
return JSON.stringify(working) !== JSON.stringify(published);
|
|
1348
1348
|
}
|
|
1349
1349
|
|
|
1350
|
-
// src/flows/
|
|
1351
|
-
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1352
|
-
var RF_NODE_TYPES = {
|
|
1353
|
-
...flowNodeTypes,
|
|
1354
|
-
...flowPortalNodeTypes,
|
|
1355
|
-
...flowGroupHeaderNodeTypes,
|
|
1356
|
-
...flowGroupFrameNodeTypes
|
|
1357
|
-
};
|
|
1358
|
-
var CHAIN_FRAME_PADDING = 36;
|
|
1350
|
+
// src/flows/flowCanvasModel.ts
|
|
1359
1351
|
var COLUMN_GAP = 360;
|
|
1360
1352
|
var ROW_GAP = 40;
|
|
1361
|
-
var
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
var EDGE_COLOR_FALLBACK = "#cbd5e1";
|
|
1365
|
-
var EDGE_COLOR_LIVE = "#3b82f6";
|
|
1366
|
-
var EDGE_COLOR_CROSS_FLOW = "#06b6d4";
|
|
1367
|
-
var BACKGROUND_COLOR_LIGHT2 = "#cbd5e1";
|
|
1368
|
-
var BACKGROUND_COLOR_DARK2 = "#334155";
|
|
1369
|
-
function portalNodeId(sourceId, target) {
|
|
1370
|
-
return `__portal__${sourceId}__${target}`;
|
|
1353
|
+
var CHAIN_LABEL_ROOM = 24;
|
|
1354
|
+
function portalNodeId(sourceNodeId, target) {
|
|
1355
|
+
return `__portal__${sourceNodeId}__${target}`;
|
|
1371
1356
|
}
|
|
1372
|
-
function
|
|
1373
|
-
return
|
|
1357
|
+
function chainFrameNodeId(actionNodeId) {
|
|
1358
|
+
return `__chain__${actionNodeId}`;
|
|
1374
1359
|
}
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
const
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1360
|
+
var GROUP_HEADER_NODE_ID = "__group_header__";
|
|
1361
|
+
function countLiveByNode(params) {
|
|
1362
|
+
const { flowKey, rootFlowKey, positions } = params;
|
|
1363
|
+
const isRoot = flowKey === rootFlowKey;
|
|
1364
|
+
const counts = {};
|
|
1365
|
+
for (const position of positions ?? []) {
|
|
1366
|
+
if (position.flow !== flowKey && !isRoot) continue;
|
|
1367
|
+
const nodeId = isRoot ? position.menuNodeId : position.nodeId;
|
|
1368
|
+
if (!nodeId) continue;
|
|
1369
|
+
counts[nodeId] = (counts[nodeId] ?? 0) + 1;
|
|
1370
|
+
}
|
|
1371
|
+
return counts;
|
|
1372
|
+
}
|
|
1373
|
+
function computeMergedLayout(params) {
|
|
1374
|
+
const { openKeys, graphs, primaryFlowKey } = params;
|
|
1375
|
+
const open = new Set(openKeys);
|
|
1376
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
1377
|
+
for (const key of openKeys) {
|
|
1378
|
+
for (const node of Object.values(graphs[key]?.nodes ?? {})) {
|
|
1379
|
+
nodeById.set(namespaceNodeId(key, node.id), node);
|
|
1380
|
+
}
|
|
1382
1381
|
}
|
|
1383
1382
|
function forwardEdges(flowKey, node) {
|
|
1384
1383
|
const result = [];
|
|
1385
1384
|
for (const { target } of targetsOf(node)) {
|
|
1386
1385
|
if (isCrossFlowTarget(target)) {
|
|
1387
|
-
const
|
|
1388
|
-
const targetGraph =
|
|
1389
|
-
if (targetGraph) result.push(namespaceNodeId(
|
|
1390
|
-
} else if (
|
|
1386
|
+
const targetKey = crossFlowKey(target);
|
|
1387
|
+
const targetGraph = open.has(targetKey) ? graphs[targetKey] : void 0;
|
|
1388
|
+
if (targetGraph) result.push(namespaceNodeId(targetKey, targetGraph.startNodeId));
|
|
1389
|
+
} else if (graphs[flowKey]?.nodes[target]) {
|
|
1391
1390
|
result.push(namespaceNodeId(flowKey, target));
|
|
1392
1391
|
}
|
|
1393
1392
|
}
|
|
1394
1393
|
return result;
|
|
1395
1394
|
}
|
|
1396
1395
|
const rank = /* @__PURE__ */ new Map();
|
|
1397
|
-
const primaryGraph =
|
|
1396
|
+
const primaryGraph = graphs[primaryFlowKey];
|
|
1398
1397
|
const rootId = primaryGraph ? namespaceNodeId(primaryFlowKey, primaryGraph.startNodeId) : void 0;
|
|
1399
|
-
if (rootId &&
|
|
1398
|
+
if (rootId && nodeById.has(rootId)) {
|
|
1400
1399
|
rank.set(rootId, 0);
|
|
1401
1400
|
const queue = [rootId];
|
|
1402
1401
|
while (queue.length > 0) {
|
|
1403
1402
|
const id = queue.shift();
|
|
1404
|
-
const node =
|
|
1403
|
+
const node = nodeById.get(id);
|
|
1405
1404
|
if (!node) continue;
|
|
1406
1405
|
for (const nextId of forwardEdges(parseNamespacedId(id).flowKey, node)) {
|
|
1407
1406
|
if (!rank.has(nextId)) {
|
|
@@ -1412,100 +1411,111 @@ function computeMergedLayout(openFlows, workingGraphs, primaryFlowKey) {
|
|
|
1412
1411
|
}
|
|
1413
1412
|
}
|
|
1414
1413
|
const strayRank = Math.max(0, ...rank.values()) + 1;
|
|
1415
|
-
for (const
|
|
1416
|
-
if (!rank.has(
|
|
1414
|
+
for (const id of nodeById.keys()) {
|
|
1415
|
+
if (!rank.has(id)) rank.set(id, strayRank);
|
|
1417
1416
|
}
|
|
1418
1417
|
const layers = /* @__PURE__ */ new Map();
|
|
1419
|
-
for (const [
|
|
1420
|
-
|
|
1421
|
-
|
|
1418
|
+
for (const [id, value] of rank) {
|
|
1419
|
+
const layer = layers.get(value);
|
|
1420
|
+
if (layer) layer.push(id);
|
|
1421
|
+
else layers.set(value, [id]);
|
|
1422
1422
|
}
|
|
1423
1423
|
const positions = /* @__PURE__ */ new Map();
|
|
1424
|
-
for (const
|
|
1424
|
+
for (const value of [...layers.keys()].sort((a, b) => a - b)) {
|
|
1425
1425
|
let cursorY = 0;
|
|
1426
|
-
for (const
|
|
1427
|
-
positions.set(
|
|
1428
|
-
cursorY += estimateNodeHeight(
|
|
1426
|
+
for (const id of layers.get(value)) {
|
|
1427
|
+
positions.set(id, { x: value * COLUMN_GAP, y: cursorY });
|
|
1428
|
+
cursorY += estimateNodeHeight(nodeById.get(id)) + ROW_GAP;
|
|
1429
1429
|
}
|
|
1430
1430
|
}
|
|
1431
1431
|
return positions;
|
|
1432
1432
|
}
|
|
1433
|
-
function
|
|
1434
|
-
const
|
|
1435
|
-
|
|
1436
|
-
if (position.flow !== flowKey && flowKey !== rootFlowKey) continue;
|
|
1437
|
-
const nodeId = flowKey === rootFlowKey ? position.menuNodeId : position.nodeId;
|
|
1438
|
-
if (!nodeId) continue;
|
|
1439
|
-
counts[nodeId] = (counts[nodeId] ?? 0) + 1;
|
|
1440
|
-
}
|
|
1441
|
-
return counts;
|
|
1442
|
-
}
|
|
1443
|
-
function buildAllEdges(openFlows, workingGraphs, rootFlowKey, livePositions) {
|
|
1444
|
-
const openKeys = new Set(openFlows.map((flow) => flow.key));
|
|
1433
|
+
function buildFlowEdges(params) {
|
|
1434
|
+
const { openKeys, graphs, rootFlowKey, livePositions } = params;
|
|
1435
|
+
const open = new Set(openKeys);
|
|
1445
1436
|
const edges = [];
|
|
1446
|
-
for (const
|
|
1447
|
-
const graph =
|
|
1437
|
+
for (const flowKey of openKeys) {
|
|
1438
|
+
const graph = graphs[flowKey];
|
|
1448
1439
|
if (!graph) continue;
|
|
1449
|
-
const liveCounts =
|
|
1450
|
-
for (const [
|
|
1451
|
-
const
|
|
1440
|
+
const liveCounts = countLiveByNode({ flowKey, rootFlowKey, positions: livePositions });
|
|
1441
|
+
for (const [nodeId, node] of Object.entries(graph.nodes)) {
|
|
1442
|
+
const live = (liveCounts[nodeId] ?? 0) > 0;
|
|
1452
1443
|
for (const { target, optionId, isDefault } of targetsOf(node)) {
|
|
1453
1444
|
if (!target) continue;
|
|
1454
1445
|
const crossFlow = isCrossFlowTarget(target);
|
|
1455
1446
|
let targetFlowKey = flowKey;
|
|
1456
|
-
let
|
|
1447
|
+
let targetNodeId = target;
|
|
1457
1448
|
if (crossFlow) {
|
|
1458
1449
|
const wantedKey = crossFlowKey(target);
|
|
1459
|
-
const targetGraph =
|
|
1450
|
+
const targetGraph = open.has(wantedKey) ? graphs[wantedKey] : void 0;
|
|
1460
1451
|
if (targetGraph) {
|
|
1461
1452
|
targetFlowKey = wantedKey;
|
|
1462
|
-
|
|
1453
|
+
targetNodeId = targetGraph.startNodeId;
|
|
1463
1454
|
} else {
|
|
1464
|
-
|
|
1455
|
+
targetNodeId = portalNodeId(nodeId, target);
|
|
1465
1456
|
}
|
|
1466
1457
|
}
|
|
1467
|
-
const source = namespaceNodeId(flowKey,
|
|
1468
|
-
const edgeTarget = namespaceNodeId(targetFlowKey,
|
|
1469
|
-
if (
|
|
1470
|
-
const color = crossFlow ? EDGE_COLOR_CROSS_FLOW : isLive ? EDGE_COLOR_LIVE : EDGE_COLOR_LINEAR;
|
|
1471
|
-
edges.push({
|
|
1472
|
-
id: `${source}->${edgeTarget}`,
|
|
1473
|
-
source,
|
|
1474
|
-
target: edgeTarget,
|
|
1475
|
-
type: "bezier",
|
|
1476
|
-
animated: isLive,
|
|
1477
|
-
style: crossFlow ? { stroke: color, strokeWidth: 1.5, strokeDasharray: "3 3" } : { stroke: color, strokeWidth: isLive ? 2.5 : 1.5 },
|
|
1478
|
-
markerEnd: { type: MarkerType2.ArrowClosed, color, width: 18, height: 18 }
|
|
1479
|
-
});
|
|
1480
|
-
} else if (isDefault) {
|
|
1481
|
-
const color = crossFlow ? EDGE_COLOR_CROSS_FLOW : EDGE_COLOR_FALLBACK;
|
|
1458
|
+
const source = namespaceNodeId(flowKey, nodeId);
|
|
1459
|
+
const edgeTarget = namespaceNodeId(targetFlowKey, targetNodeId);
|
|
1460
|
+
if (isDefault) {
|
|
1482
1461
|
edges.push({
|
|
1483
1462
|
id: `${source}->${edgeTarget}-default`,
|
|
1484
1463
|
source,
|
|
1485
|
-
sourceHandle: "__default",
|
|
1486
|
-
target: edgeTarget,
|
|
1487
|
-
type: "bezier",
|
|
1488
|
-
style: { stroke: color, strokeWidth: 1.5, strokeDasharray: "5 4" },
|
|
1489
|
-
markerEnd: { type: MarkerType2.ArrowClosed, color, width: 16, height: 16 }
|
|
1490
|
-
});
|
|
1491
|
-
} else {
|
|
1492
|
-
const color = crossFlow ? EDGE_COLOR_CROSS_FLOW : isLive ? EDGE_COLOR_LIVE : EDGE_COLOR_BRANCH;
|
|
1493
|
-
edges.push({
|
|
1494
|
-
id: `${source}->${edgeTarget}-${optionId}`,
|
|
1495
|
-
source,
|
|
1496
|
-
sourceHandle: optionId,
|
|
1497
1464
|
target: edgeTarget,
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1465
|
+
sourceHandle: "__default",
|
|
1466
|
+
kind: "fallback",
|
|
1467
|
+
crossFlow,
|
|
1468
|
+
// Fallback não anima: é o caminho que ninguém escolheu, e piscar sugeria que a conversa
|
|
1469
|
+
// estava passando por ali.
|
|
1470
|
+
live: false
|
|
1502
1471
|
});
|
|
1472
|
+
continue;
|
|
1473
|
+
}
|
|
1474
|
+
if (optionId === void 0) {
|
|
1475
|
+
edges.push({ id: `${source}->${edgeTarget}`, source, target: edgeTarget, kind: "linear", crossFlow, live });
|
|
1476
|
+
continue;
|
|
1503
1477
|
}
|
|
1478
|
+
edges.push({
|
|
1479
|
+
id: `${source}->${edgeTarget}-${optionId}`,
|
|
1480
|
+
source,
|
|
1481
|
+
target: edgeTarget,
|
|
1482
|
+
sourceHandle: optionId,
|
|
1483
|
+
kind: "branch",
|
|
1484
|
+
crossFlow,
|
|
1485
|
+
live
|
|
1486
|
+
});
|
|
1504
1487
|
}
|
|
1505
1488
|
}
|
|
1506
1489
|
}
|
|
1507
1490
|
return edges;
|
|
1508
1491
|
}
|
|
1492
|
+
function detachedNodeIds(graph) {
|
|
1493
|
+
const connected = /* @__PURE__ */ new Set();
|
|
1494
|
+
for (const node of Object.values(graph.nodes)) {
|
|
1495
|
+
for (const { target } of targetsOf(node)) {
|
|
1496
|
+
if (!isCrossFlowTarget(target)) connected.add(target);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
const detached = /* @__PURE__ */ new Set();
|
|
1500
|
+
for (const nodeId of Object.keys(graph.nodes)) {
|
|
1501
|
+
if (nodeId !== graph.startNodeId && !connected.has(nodeId)) detached.add(nodeId);
|
|
1502
|
+
}
|
|
1503
|
+
return detached;
|
|
1504
|
+
}
|
|
1505
|
+
function chainFrameBounds(params) {
|
|
1506
|
+
const { nodeIds, graph, positionOf, padding } = params;
|
|
1507
|
+
const positions = nodeIds.map(positionOf);
|
|
1508
|
+
const minX = Math.min(...positions.map((each) => each.x));
|
|
1509
|
+
const maxX = Math.max(...positions.map((each) => each.x)) + NODE_CARD_WIDTH;
|
|
1510
|
+
const minY = Math.min(...positions.map((each) => each.y));
|
|
1511
|
+
const maxY = Math.max(...nodeIds.map((id, index) => positions[index].y + estimateNodeHeight(graph.nodes[id])));
|
|
1512
|
+
return {
|
|
1513
|
+
x: minX - padding,
|
|
1514
|
+
y: minY - padding - CHAIN_LABEL_ROOM,
|
|
1515
|
+
width: maxX - minX + padding * 2,
|
|
1516
|
+
height: maxY - minY + padding * 2 + CHAIN_LABEL_ROOM
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1509
1519
|
function newNodeFromSpec(spec, existingIds) {
|
|
1510
1520
|
if (spec.kind === "question") {
|
|
1511
1521
|
const id2 = slugifyNodeId("nova_pergunta", existingIds);
|
|
@@ -1532,6 +1542,44 @@ function newNodeFromSpec(spec, existingIds) {
|
|
|
1532
1542
|
const id = slugifyNodeId("nova_acao", existingIds);
|
|
1533
1543
|
return { id, type: "action", actionKind: spec.actionKind };
|
|
1534
1544
|
}
|
|
1545
|
+
|
|
1546
|
+
// src/flows/FlowsWorkspace.tsx
|
|
1547
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1548
|
+
var RF_NODE_TYPES = {
|
|
1549
|
+
...flowNodeTypes,
|
|
1550
|
+
...flowPortalNodeTypes,
|
|
1551
|
+
...flowGroupHeaderNodeTypes,
|
|
1552
|
+
...flowGroupFrameNodeTypes
|
|
1553
|
+
};
|
|
1554
|
+
var CHAIN_FRAME_PADDING = 36;
|
|
1555
|
+
var FLOW_KEY_PATTERN = /^[a-z0-9_]{2,40}$/;
|
|
1556
|
+
var EDGE_COLOR_LINEAR = "#94a3b8";
|
|
1557
|
+
var EDGE_COLOR_BRANCH = "#8b5cf6";
|
|
1558
|
+
var EDGE_COLOR_FALLBACK = "#cbd5e1";
|
|
1559
|
+
var EDGE_COLOR_LIVE = "#3b82f6";
|
|
1560
|
+
var EDGE_COLOR_CROSS_FLOW = "#06b6d4";
|
|
1561
|
+
var BACKGROUND_COLOR_LIGHT2 = "#cbd5e1";
|
|
1562
|
+
var BACKGROUND_COLOR_DARK2 = "#334155";
|
|
1563
|
+
function styleEdge(spec) {
|
|
1564
|
+
const color = spec.crossFlow ? EDGE_COLOR_CROSS_FLOW : spec.kind === "fallback" ? EDGE_COLOR_FALLBACK : spec.live ? EDGE_COLOR_LIVE : spec.kind === "branch" ? EDGE_COLOR_BRANCH : EDGE_COLOR_LINEAR;
|
|
1565
|
+
const baseWidth = spec.kind === "branch" ? 1.75 : 1.5;
|
|
1566
|
+
const dash = spec.crossFlow ? "3 3" : spec.kind === "fallback" ? "5 4" : void 0;
|
|
1567
|
+
const markerSize = spec.kind === "fallback" ? 16 : 18;
|
|
1568
|
+
return {
|
|
1569
|
+
id: spec.id,
|
|
1570
|
+
source: spec.source,
|
|
1571
|
+
target: spec.target,
|
|
1572
|
+
...spec.sourceHandle === void 0 ? {} : { sourceHandle: spec.sourceHandle },
|
|
1573
|
+
type: "bezier",
|
|
1574
|
+
animated: spec.live,
|
|
1575
|
+
style: {
|
|
1576
|
+
stroke: color,
|
|
1577
|
+
strokeWidth: spec.live && !spec.crossFlow ? 2.5 : baseWidth,
|
|
1578
|
+
...dash === void 0 ? {} : { strokeDasharray: dash }
|
|
1579
|
+
},
|
|
1580
|
+
markerEnd: { type: MarkerType2.ArrowClosed, color, width: markerSize, height: markerSize }
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1535
1583
|
function extractErrorMessage(error) {
|
|
1536
1584
|
if (error instanceof Error) return error.message;
|
|
1537
1585
|
return void 0;
|
|
@@ -1554,7 +1602,7 @@ function FlowsWorkspace({
|
|
|
1554
1602
|
const [loadState, setLoadState] = useState3("loading");
|
|
1555
1603
|
const [livePositions, setLivePositions] = useState3(void 0);
|
|
1556
1604
|
const [viewMode, setViewMode] = useState3("detail");
|
|
1557
|
-
const [
|
|
1605
|
+
const [openFlowKeys, setOpenFlowKeys] = useState3([rootFlowKey]);
|
|
1558
1606
|
const [hasAutoMerged, setHasAutoMerged] = useState3(false);
|
|
1559
1607
|
const [workingGraphs, setWorkingGraphs] = useState3({});
|
|
1560
1608
|
const [editingRef, setEditingRef] = useState3(null);
|
|
@@ -1599,11 +1647,11 @@ function FlowsWorkspace({
|
|
|
1599
1647
|
clearInterval(timer);
|
|
1600
1648
|
};
|
|
1601
1649
|
}, [api, livePollIntervalMs]);
|
|
1602
|
-
const primaryFlowKey =
|
|
1650
|
+
const primaryFlowKey = openFlowKeys[0] ?? rootFlowKey;
|
|
1603
1651
|
const primaryGraph = workingGraphs[primaryFlowKey];
|
|
1604
1652
|
useEffect2(() => {
|
|
1605
1653
|
if (!graphs || hasAutoMerged) return;
|
|
1606
|
-
|
|
1654
|
+
setOpenFlowKeys(mergedFlowKeysFrom(rootFlowKey, graphs));
|
|
1607
1655
|
setHasAutoMerged(true);
|
|
1608
1656
|
}, [graphs, hasAutoMerged, rootFlowKey]);
|
|
1609
1657
|
useEffect2(() => {
|
|
@@ -1611,7 +1659,7 @@ function FlowsWorkspace({
|
|
|
1611
1659
|
setWorkingGraphs((prev) => {
|
|
1612
1660
|
let changed = false;
|
|
1613
1661
|
const next = { ...prev };
|
|
1614
|
-
for (const
|
|
1662
|
+
for (const key of openFlowKeys) {
|
|
1615
1663
|
if (!next[key] && graphs[key]) {
|
|
1616
1664
|
next[key] = graphs[key];
|
|
1617
1665
|
changed = true;
|
|
@@ -1619,7 +1667,7 @@ function FlowsWorkspace({
|
|
|
1619
1667
|
}
|
|
1620
1668
|
return changed ? next : prev;
|
|
1621
1669
|
});
|
|
1622
|
-
}, [graphs,
|
|
1670
|
+
}, [graphs, openFlowKeys]);
|
|
1623
1671
|
const isFlowDirty = useCallback(
|
|
1624
1672
|
(key) => {
|
|
1625
1673
|
const working = workingGraphs[key];
|
|
@@ -1631,12 +1679,12 @@ function FlowsWorkspace({
|
|
|
1631
1679
|
);
|
|
1632
1680
|
const issuesByFlow = useMemo2(() => {
|
|
1633
1681
|
const map = {};
|
|
1634
|
-
for (const
|
|
1682
|
+
for (const key of openFlowKeys) {
|
|
1635
1683
|
const graph = workingGraphs[key];
|
|
1636
1684
|
if (graph) map[key] = validateGraph(graph, labels.validation);
|
|
1637
1685
|
}
|
|
1638
1686
|
return map;
|
|
1639
|
-
}, [
|
|
1687
|
+
}, [openFlowKeys, workingGraphs, labels]);
|
|
1640
1688
|
const errorCount = Object.values(issuesByFlow).reduce(
|
|
1641
1689
|
(sum, list) => sum + list.filter((issue) => issue.severity === "error").length,
|
|
1642
1690
|
0
|
|
@@ -1645,7 +1693,7 @@ function FlowsWorkspace({
|
|
|
1645
1693
|
(sum, list) => sum + list.filter((issue) => issue.severity === "warning").length,
|
|
1646
1694
|
0
|
|
1647
1695
|
);
|
|
1648
|
-
const dirtyKeys =
|
|
1696
|
+
const dirtyKeys = openFlowKeys.filter(isFlowDirty);
|
|
1649
1697
|
const isDirty = dirtyKeys.length > 0;
|
|
1650
1698
|
const updateFlow = useCallback((flowKey, updater) => {
|
|
1651
1699
|
setWorkingGraphs((prev) => {
|
|
@@ -1656,17 +1704,17 @@ function FlowsWorkspace({
|
|
|
1656
1704
|
}, []);
|
|
1657
1705
|
const focusFlow = useCallback(
|
|
1658
1706
|
(key) => {
|
|
1659
|
-
const others =
|
|
1660
|
-
if (others.some(
|
|
1661
|
-
|
|
1707
|
+
const others = openFlowKeys.filter((each) => each !== key);
|
|
1708
|
+
if (others.some(isFlowDirty) && !window.confirm(labels.workspace.unsavedChangesConfirm)) return;
|
|
1709
|
+
setOpenFlowKeys(graphs ? mergedFlowKeysFrom(key, graphs) : [key]);
|
|
1662
1710
|
if (editingRef && editingRef.flowKey !== key) setEditingRef(null);
|
|
1663
1711
|
},
|
|
1664
|
-
[
|
|
1712
|
+
[openFlowKeys, isFlowDirty, editingRef, graphs, labels]
|
|
1665
1713
|
);
|
|
1666
1714
|
const closeFlow = useCallback(
|
|
1667
1715
|
(key) => {
|
|
1668
1716
|
if (isFlowDirty(key) && !window.confirm(labels.workspace.unsavedChangesConfirm)) return;
|
|
1669
|
-
|
|
1717
|
+
setOpenFlowKeys((prev) => prev.filter((each) => each !== key));
|
|
1670
1718
|
setWorkingGraphs((prev) => {
|
|
1671
1719
|
const { [key]: _removed, ...rest } = prev;
|
|
1672
1720
|
return rest;
|
|
@@ -1675,50 +1723,30 @@ function FlowsWorkspace({
|
|
|
1675
1723
|
},
|
|
1676
1724
|
[isFlowDirty, editingRef, labels]
|
|
1677
1725
|
);
|
|
1678
|
-
const mergeFlow = useCallback(
|
|
1679
|
-
(targetFlowKey,
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
const sourceOpen = prev.find((flow) => flow.key === source.flowKey);
|
|
1683
|
-
const sourceGraph = workingGraphs[source.flowKey];
|
|
1684
|
-
const sourceLocalPosition = sourceGraph?.nodes[source.nodeId]?.position ?? (sourceGraph ? computeAutoLayout(sourceGraph)[source.nodeId] : void 0) ?? { x: 0, y: 0 };
|
|
1685
|
-
const baseOffset = sourceOpen?.offset ?? { x: 0, y: 0 };
|
|
1686
|
-
return [
|
|
1687
|
-
...prev,
|
|
1688
|
-
{
|
|
1689
|
-
key: targetFlowKey,
|
|
1690
|
-
offset: {
|
|
1691
|
-
x: baseOffset.x + sourceLocalPosition.x + 400,
|
|
1692
|
-
y: baseOffset.y + sourceLocalPosition.y
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
];
|
|
1696
|
-
});
|
|
1697
|
-
},
|
|
1698
|
-
[workingGraphs]
|
|
1699
|
-
);
|
|
1700
|
-
const isMerged = openFlows.length > 1;
|
|
1726
|
+
const mergeFlow = useCallback((targetFlowKey) => {
|
|
1727
|
+
setOpenFlowKeys((prev) => prev.includes(targetFlowKey) ? prev : [...prev, targetFlowKey]);
|
|
1728
|
+
}, []);
|
|
1729
|
+
const isMerged = openFlowKeys.length > 1;
|
|
1701
1730
|
const mergedPositions = useMemo2(
|
|
1702
|
-
() => isMerged ? computeMergedLayout(
|
|
1703
|
-
[isMerged,
|
|
1731
|
+
() => isMerged ? computeMergedLayout({ openKeys: openFlowKeys, graphs: workingGraphs, primaryFlowKey }) : null,
|
|
1732
|
+
[isMerged, openFlowKeys, workingGraphs, primaryFlowKey]
|
|
1704
1733
|
);
|
|
1705
1734
|
const renderedPositionsRef = useRef2(/* @__PURE__ */ new Map());
|
|
1706
1735
|
const derivedNodes = useMemo2(() => {
|
|
1707
1736
|
const allNodes = [];
|
|
1708
|
-
for (const
|
|
1737
|
+
for (const flowKey of openFlowKeys) {
|
|
1709
1738
|
let resolvePosition2 = function(nodeId) {
|
|
1710
1739
|
if (mergedPositions) {
|
|
1711
1740
|
const nsId = namespaceNodeId(flowKey, nodeId);
|
|
1712
1741
|
return renderedPositionsRef.current.get(nsId) ?? mergedPositions.get(nsId) ?? { x: 0, y: 0 };
|
|
1713
1742
|
}
|
|
1714
|
-
|
|
1715
|
-
return { x: local.x + offset.x, y: local.y + offset.y };
|
|
1743
|
+
return graph.nodes[nodeId]?.position ?? fallbackPositions[nodeId] ?? { x: 0, y: 0 };
|
|
1716
1744
|
};
|
|
1717
1745
|
var resolvePosition = resolvePosition2;
|
|
1718
1746
|
const graph = workingGraphs[flowKey];
|
|
1719
1747
|
if (!graph) continue;
|
|
1720
1748
|
const fallbackPositions = mergedPositions ? {} : computeAutoLayout(graph);
|
|
1721
|
-
const liveCounts =
|
|
1749
|
+
const liveCounts = countLiveByNode({ flowKey, rootFlowKey, positions: livePositions });
|
|
1722
1750
|
const flowIssues = issuesByFlow[flowKey] ?? [];
|
|
1723
1751
|
const isPrimary = flowKey === primaryFlowKey;
|
|
1724
1752
|
const connectedTargets = /* @__PURE__ */ new Set();
|
|
@@ -1748,7 +1776,7 @@ function FlowsWorkspace({
|
|
|
1748
1776
|
const crossFlowTargets = [...new Set(targetsOf(node).map((edge) => edge.target).filter(isCrossFlowTarget))];
|
|
1749
1777
|
crossFlowTargets.forEach((target, index) => {
|
|
1750
1778
|
const targetFlowKey = crossFlowKey(target);
|
|
1751
|
-
if (
|
|
1779
|
+
if (openFlowKeys.includes(targetFlowKey)) return;
|
|
1752
1780
|
allNodes.push({
|
|
1753
1781
|
id: namespaceNodeId(flowKey, portalNodeId(node.id, target)),
|
|
1754
1782
|
type: "flowPortal",
|
|
@@ -1757,7 +1785,7 @@ function FlowsWorkspace({
|
|
|
1757
1785
|
position: { x: position.x + 320, y: position.y + index * 70 },
|
|
1758
1786
|
data: {
|
|
1759
1787
|
label: graphs?.[targetFlowKey]?.label ?? targetFlowKey,
|
|
1760
|
-
onNavigate: () => mergeFlow(targetFlowKey
|
|
1788
|
+
onNavigate: () => mergeFlow(targetFlowKey)
|
|
1761
1789
|
}
|
|
1762
1790
|
});
|
|
1763
1791
|
});
|
|
@@ -1803,7 +1831,7 @@ function FlowsWorkspace({
|
|
|
1803
1831
|
}
|
|
1804
1832
|
return allNodes;
|
|
1805
1833
|
}, [
|
|
1806
|
-
|
|
1834
|
+
openFlowKeys,
|
|
1807
1835
|
workingGraphs,
|
|
1808
1836
|
mergedPositions,
|
|
1809
1837
|
livePositions,
|
|
@@ -1818,8 +1846,8 @@ function FlowsWorkspace({
|
|
|
1818
1846
|
labels
|
|
1819
1847
|
]);
|
|
1820
1848
|
const edges = useMemo2(
|
|
1821
|
-
() =>
|
|
1822
|
-
[
|
|
1849
|
+
() => buildFlowEdges({ openKeys: openFlowKeys, graphs: workingGraphs, rootFlowKey, livePositions }).map(styleEdge),
|
|
1850
|
+
[openFlowKeys, workingGraphs, rootFlowKey, livePositions]
|
|
1823
1851
|
);
|
|
1824
1852
|
useEffect2(() => {
|
|
1825
1853
|
setRfNodes(derivedNodes);
|
|
@@ -1841,16 +1869,13 @@ function FlowsWorkspace({
|
|
|
1841
1869
|
(_event, node) => {
|
|
1842
1870
|
renderedPositionsRef.current.set(node.id, node.position);
|
|
1843
1871
|
const { flowKey, nodeId } = parseNamespacedId(node.id);
|
|
1844
|
-
|
|
1845
|
-
if (!openFlow) return;
|
|
1846
|
-
const drawnWithOffset = openFlows.length === 1;
|
|
1847
|
-
const localPosition = drawnWithOffset ? { x: node.position.x - openFlow.offset.x, y: node.position.y - openFlow.offset.y } : node.position;
|
|
1872
|
+
if (!openFlowKeys.includes(flowKey)) return;
|
|
1848
1873
|
updateFlow(
|
|
1849
1874
|
flowKey,
|
|
1850
|
-
(graph) => graph.nodes[nodeId] ? { ...graph, nodes: { ...graph.nodes, [nodeId]: { ...graph.nodes[nodeId], position:
|
|
1875
|
+
(graph) => graph.nodes[nodeId] ? { ...graph, nodes: { ...graph.nodes, [nodeId]: { ...graph.nodes[nodeId], position: node.position } } } : graph
|
|
1851
1876
|
);
|
|
1852
1877
|
},
|
|
1853
|
-
[
|
|
1878
|
+
[openFlowKeys, updateFlow]
|
|
1854
1879
|
);
|
|
1855
1880
|
const onConnect = useCallback(
|
|
1856
1881
|
(connection) => {
|
|
@@ -1890,17 +1915,14 @@ function FlowsWorkspace({
|
|
|
1890
1915
|
if (!primaryGraph) return;
|
|
1891
1916
|
renderedPositionsRef.current.clear();
|
|
1892
1917
|
if (isMerged) {
|
|
1893
|
-
const positions2 = computeMergedLayout(
|
|
1894
|
-
for (const
|
|
1918
|
+
const positions2 = computeMergedLayout({ openKeys: openFlowKeys, graphs: workingGraphs, primaryFlowKey });
|
|
1919
|
+
for (const key of openFlowKeys) {
|
|
1895
1920
|
updateFlow(key, (graph) => ({
|
|
1896
1921
|
...graph,
|
|
1897
1922
|
nodes: Object.fromEntries(
|
|
1898
1923
|
Object.entries(graph.nodes).map(([id, node]) => {
|
|
1899
1924
|
const position = positions2.get(namespaceNodeId(key, id));
|
|
1900
|
-
return [
|
|
1901
|
-
id,
|
|
1902
|
-
position ? { ...node, position: { x: position.x - offset.x, y: position.y - offset.y } } : node
|
|
1903
|
-
];
|
|
1925
|
+
return [id, position ? { ...node, position } : node];
|
|
1904
1926
|
})
|
|
1905
1927
|
)
|
|
1906
1928
|
}));
|
|
@@ -1955,7 +1977,7 @@ function FlowsWorkspace({
|
|
|
1955
1977
|
...newFlow.showInMenu ? { menuOptionLabel: newFlow.menuOptionLabel || newFlow.label } : {}
|
|
1956
1978
|
});
|
|
1957
1979
|
await reloadGraphs();
|
|
1958
|
-
|
|
1980
|
+
setOpenFlowKeys([newFlow.key]);
|
|
1959
1981
|
setShowCreateDialog(false);
|
|
1960
1982
|
setNewFlow({ key: "", label: "", showInMenu: false, menuOptionLabel: "" });
|
|
1961
1983
|
setFlowMutationState({ pending: false });
|
|
@@ -1970,7 +1992,7 @@ function FlowsWorkspace({
|
|
|
1970
1992
|
try {
|
|
1971
1993
|
await deleteFlow(primaryGraph.key);
|
|
1972
1994
|
const reloaded = await reloadGraphs();
|
|
1973
|
-
|
|
1995
|
+
setOpenFlowKeys(reloaded ? mergedFlowKeysFrom(rootFlowKey, reloaded) : [rootFlowKey]);
|
|
1974
1996
|
setShowDeleteDialog(false);
|
|
1975
1997
|
setFlowMutationState({ pending: false });
|
|
1976
1998
|
} catch (error) {
|
|
@@ -2318,13 +2340,20 @@ export {
|
|
|
2318
2340
|
FlowPortalNode,
|
|
2319
2341
|
FlowWhatsAppPreview,
|
|
2320
2342
|
FlowsWorkspace,
|
|
2343
|
+
GROUP_HEADER_NODE_ID,
|
|
2321
2344
|
NODE_CARD_WIDTH,
|
|
2322
2345
|
WHATSAPP_LIMITS,
|
|
2323
2346
|
applyConnection,
|
|
2347
|
+
buildFlowEdges,
|
|
2348
|
+
chainFrameBounds,
|
|
2349
|
+
chainFrameNodeId,
|
|
2324
2350
|
computeAutoLayout,
|
|
2325
2351
|
computeFlowMapLayout,
|
|
2352
|
+
computeMergedLayout,
|
|
2353
|
+
countLiveByNode,
|
|
2326
2354
|
crossFlowKey,
|
|
2327
2355
|
crossFlowTargetsOf,
|
|
2356
|
+
detachedNodeIds,
|
|
2328
2357
|
estimateNodeHeight,
|
|
2329
2358
|
findCollectionChains,
|
|
2330
2359
|
flowGroupFrameNodeTypes,
|
|
@@ -2337,8 +2366,10 @@ export {
|
|
|
2337
2366
|
mergeFlowEditorLabels,
|
|
2338
2367
|
mergedFlowKeysFrom,
|
|
2339
2368
|
namespaceNodeId,
|
|
2369
|
+
newNodeFromSpec,
|
|
2340
2370
|
nodeLabel,
|
|
2341
2371
|
parseNamespacedId,
|
|
2372
|
+
portalNodeId,
|
|
2342
2373
|
removeNodeAndCleanRefs,
|
|
2343
2374
|
rendersAsButtons,
|
|
2344
2375
|
resolveConnection,
|
package/package.json
CHANGED