ngx-vflow 0.15.0 → 0.16.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.
- package/esm2022/lib/vflow/components/background/background.component.mjs +72 -4
- package/esm2022/lib/vflow/components/connection/connection.component.mjs +6 -1
- package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +7 -4
- package/esm2022/lib/vflow/interfaces/edge.interface.mjs +1 -1
- package/esm2022/lib/vflow/interfaces/optimization.interface.mjs +1 -1
- package/esm2022/lib/vflow/math/edge-path/smooth-step-path.mjs +170 -0
- package/esm2022/lib/vflow/models/edge.model.mjs +6 -1
- package/esm2022/lib/vflow/public-components/minimap/minimap.component.mjs +8 -2
- package/esm2022/lib/vflow/services/node-rendering.service.mjs +8 -1
- package/esm2022/lib/vflow/types/background.type.mjs +1 -1
- package/esm2022/lib/vflow/utils/is-group-node.mjs +4 -0
- package/fesm2022/ngx-vflow.mjs +270 -7
- package/fesm2022/ngx-vflow.mjs.map +1 -1
- package/lib/vflow/components/background/background.component.d.ts +13 -0
- package/lib/vflow/components/vflow/vflow.component.d.ts +2 -0
- package/lib/vflow/interfaces/edge.interface.d.ts +1 -1
- package/lib/vflow/interfaces/optimization.interface.d.ts +13 -0
- package/lib/vflow/math/edge-path/smooth-step-path.d.ts +5 -0
- package/lib/vflow/services/node-rendering.service.d.ts +2 -0
- package/lib/vflow/types/background.type.d.ts +24 -1
- package/lib/vflow/utils/is-group-node.d.ts +2 -0
- package/package.json +3 -3
package/fesm2022/ngx-vflow.mjs
CHANGED
|
@@ -1213,6 +1213,176 @@ function getPointOnBezier(sourcePoint, targetPoint, sourceControl, targetControl
|
|
|
1213
1213
|
return getPointOnLineByRatio(getPointOnLineByRatio(fromSourceToFirstControl, fromFirstControlToSecond, ratio), getPointOnLineByRatio(fromFirstControlToSecond, fromSecondControlToTarget, ratio), ratio);
|
|
1214
1214
|
}
|
|
1215
1215
|
|
|
1216
|
+
const handleDirections = {
|
|
1217
|
+
left: { x: -1, y: 0 },
|
|
1218
|
+
right: { x: 1, y: 0 },
|
|
1219
|
+
top: { x: 0, y: -1 },
|
|
1220
|
+
bottom: { x: 0, y: 1 },
|
|
1221
|
+
};
|
|
1222
|
+
function getEdgeCenter(source, target) {
|
|
1223
|
+
const xOffset = Math.abs(target.x - source.x) / 2;
|
|
1224
|
+
const centerX = target.x < source.x ? target.x + xOffset : target.x - xOffset;
|
|
1225
|
+
const yOffset = Math.abs(target.y - source.y) / 2;
|
|
1226
|
+
const centerY = target.y < source.y ? target.y + yOffset : target.y - yOffset;
|
|
1227
|
+
return [centerX, centerY, xOffset, yOffset];
|
|
1228
|
+
}
|
|
1229
|
+
const getDirection = ({ source, sourcePosition = 'bottom', target, }) => {
|
|
1230
|
+
if (sourcePosition === 'left' || sourcePosition === 'right') {
|
|
1231
|
+
return source.x < target.x ? { x: 1, y: 0 } : { x: -1, y: 0 };
|
|
1232
|
+
}
|
|
1233
|
+
return source.y < target.y ? { x: 0, y: 1 } : { x: 0, y: -1 };
|
|
1234
|
+
};
|
|
1235
|
+
const distance = (a, b) => Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
|
|
1236
|
+
// ith this function we try to mimic a orthogonal edge routing behaviour
|
|
1237
|
+
// It's not as good as a real orthogonal edge routing but it's faster and good enough as a default for step and smooth step edges
|
|
1238
|
+
function getPoints({ source, sourcePosition = 'bottom', target, targetPosition = 'top', offset, }) {
|
|
1239
|
+
const sourceDir = handleDirections[sourcePosition];
|
|
1240
|
+
const targetDir = handleDirections[targetPosition];
|
|
1241
|
+
const sourceGapped = { x: source.x + sourceDir.x * offset, y: source.y + sourceDir.y * offset };
|
|
1242
|
+
const targetGapped = { x: target.x + targetDir.x * offset, y: target.y + targetDir.y * offset };
|
|
1243
|
+
const dir = getDirection({
|
|
1244
|
+
source: sourceGapped,
|
|
1245
|
+
sourcePosition,
|
|
1246
|
+
target: targetGapped,
|
|
1247
|
+
});
|
|
1248
|
+
const dirAccessor = dir.x !== 0 ? 'x' : 'y';
|
|
1249
|
+
const currDir = dir[dirAccessor];
|
|
1250
|
+
let points = [];
|
|
1251
|
+
let centerX, centerY;
|
|
1252
|
+
const sourceGapOffset = { x: 0, y: 0 };
|
|
1253
|
+
const targetGapOffset = { x: 0, y: 0 };
|
|
1254
|
+
const [defaultCenterX, defaultCenterY] = getEdgeCenter(source, target);
|
|
1255
|
+
// opposite handle positions, default case
|
|
1256
|
+
if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) {
|
|
1257
|
+
centerX = defaultCenterX;
|
|
1258
|
+
centerY = defaultCenterY;
|
|
1259
|
+
// --->
|
|
1260
|
+
// |
|
|
1261
|
+
// >---
|
|
1262
|
+
const verticalSplit = [
|
|
1263
|
+
{ x: centerX, y: sourceGapped.y },
|
|
1264
|
+
{ x: centerX, y: targetGapped.y },
|
|
1265
|
+
];
|
|
1266
|
+
// |
|
|
1267
|
+
// ---
|
|
1268
|
+
// |
|
|
1269
|
+
const horizontalSplit = [
|
|
1270
|
+
{ x: sourceGapped.x, y: centerY },
|
|
1271
|
+
{ x: targetGapped.x, y: centerY },
|
|
1272
|
+
];
|
|
1273
|
+
if (sourceDir[dirAccessor] === currDir) {
|
|
1274
|
+
points = dirAccessor === 'x' ? verticalSplit : horizontalSplit;
|
|
1275
|
+
}
|
|
1276
|
+
else {
|
|
1277
|
+
points = dirAccessor === 'x' ? horizontalSplit : verticalSplit;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
else {
|
|
1281
|
+
// sourceTarget means we take x from source and y from target, targetSource is the opposite
|
|
1282
|
+
const sourceTarget = [{ x: sourceGapped.x, y: targetGapped.y }];
|
|
1283
|
+
const targetSource = [{ x: targetGapped.x, y: sourceGapped.y }];
|
|
1284
|
+
// this handles edges with same handle positions
|
|
1285
|
+
if (dirAccessor === 'x') {
|
|
1286
|
+
points = sourceDir.x === currDir ? targetSource : sourceTarget;
|
|
1287
|
+
}
|
|
1288
|
+
else {
|
|
1289
|
+
points = sourceDir.y === currDir ? sourceTarget : targetSource;
|
|
1290
|
+
}
|
|
1291
|
+
if (sourcePosition === targetPosition) {
|
|
1292
|
+
const diff = Math.abs(source[dirAccessor] - target[dirAccessor]);
|
|
1293
|
+
// if an edge goes from right to right for example (sourcePosition === targetPosition) and the distance between source.x and target.x is less than the offset, the added point and the gapped source/target will overlap. This leads to a weird edge path. To avoid this we add a gapOffset to the source/target
|
|
1294
|
+
if (diff <= offset) {
|
|
1295
|
+
const gapOffset = Math.min(offset - 1, offset - diff);
|
|
1296
|
+
if (sourceDir[dirAccessor] === currDir) {
|
|
1297
|
+
sourceGapOffset[dirAccessor] = (sourceGapped[dirAccessor] > source[dirAccessor] ? -1 : 1) * gapOffset;
|
|
1298
|
+
}
|
|
1299
|
+
else {
|
|
1300
|
+
targetGapOffset[dirAccessor] = (targetGapped[dirAccessor] > target[dirAccessor] ? -1 : 1) * gapOffset;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
// these are conditions for handling mixed handle positions like Right -> Bottom for example
|
|
1305
|
+
if (sourcePosition !== targetPosition) {
|
|
1306
|
+
const dirAccessorOpposite = dirAccessor === 'x' ? 'y' : 'x';
|
|
1307
|
+
const isSameDir = sourceDir[dirAccessor] === targetDir[dirAccessorOpposite];
|
|
1308
|
+
const sourceGtTargetOppo = sourceGapped[dirAccessorOpposite] > targetGapped[dirAccessorOpposite];
|
|
1309
|
+
const sourceLtTargetOppo = sourceGapped[dirAccessorOpposite] < targetGapped[dirAccessorOpposite];
|
|
1310
|
+
const flipSourceTarget = (sourceDir[dirAccessor] === 1 && ((!isSameDir && sourceGtTargetOppo) || (isSameDir && sourceLtTargetOppo))) ||
|
|
1311
|
+
(sourceDir[dirAccessor] !== 1 && ((!isSameDir && sourceLtTargetOppo) || (isSameDir && sourceGtTargetOppo)));
|
|
1312
|
+
if (flipSourceTarget) {
|
|
1313
|
+
points = dirAccessor === 'x' ? sourceTarget : targetSource;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
const sourceGapPoint = { x: sourceGapped.x + sourceGapOffset.x, y: sourceGapped.y + sourceGapOffset.y };
|
|
1317
|
+
const targetGapPoint = { x: targetGapped.x + targetGapOffset.x, y: targetGapped.y + targetGapOffset.y };
|
|
1318
|
+
const maxXDistance = Math.max(Math.abs(sourceGapPoint.x - points[0].x), Math.abs(targetGapPoint.x - points[0].x));
|
|
1319
|
+
const maxYDistance = Math.max(Math.abs(sourceGapPoint.y - points[0].y), Math.abs(targetGapPoint.y - points[0].y));
|
|
1320
|
+
// we want to place the label on the longest segment of the edge
|
|
1321
|
+
if (maxXDistance >= maxYDistance) {
|
|
1322
|
+
centerX = (sourceGapPoint.x + targetGapPoint.x) / 2;
|
|
1323
|
+
centerY = points[0].y;
|
|
1324
|
+
}
|
|
1325
|
+
else {
|
|
1326
|
+
centerX = points[0].x;
|
|
1327
|
+
centerY = (sourceGapPoint.y + targetGapPoint.y) / 2;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
const pathPoints = [
|
|
1331
|
+
source,
|
|
1332
|
+
{ x: sourceGapped.x + sourceGapOffset.x, y: sourceGapped.y + sourceGapOffset.y },
|
|
1333
|
+
...points,
|
|
1334
|
+
{ x: targetGapped.x + targetGapOffset.x, y: targetGapped.y + targetGapOffset.y },
|
|
1335
|
+
target,
|
|
1336
|
+
];
|
|
1337
|
+
return [pathPoints, centerX, centerY];
|
|
1338
|
+
}
|
|
1339
|
+
function getBend(a, b, c, size) {
|
|
1340
|
+
const bendSize = Math.min(distance(a, b) / 2, distance(b, c) / 2, size);
|
|
1341
|
+
const { x, y } = b;
|
|
1342
|
+
// no bend
|
|
1343
|
+
if ((a.x === x && x === c.x) || (a.y === y && y === c.y)) {
|
|
1344
|
+
return `L${x} ${y}`;
|
|
1345
|
+
}
|
|
1346
|
+
// first segment is horizontal
|
|
1347
|
+
if (a.y === y) {
|
|
1348
|
+
const xDir = a.x < c.x ? -1 : 1;
|
|
1349
|
+
const yDir = a.y < c.y ? 1 : -1;
|
|
1350
|
+
return `L ${x + bendSize * xDir},${y}Q ${x},${y} ${x},${y + bendSize * yDir}`;
|
|
1351
|
+
}
|
|
1352
|
+
const xDir = a.x < c.x ? 1 : -1;
|
|
1353
|
+
const yDir = a.y < c.y ? -1 : 1;
|
|
1354
|
+
return `L ${x},${y + bendSize * yDir}Q ${x},${y} ${x + bendSize * xDir},${y}`;
|
|
1355
|
+
}
|
|
1356
|
+
function smoothStepPath(source, target, sourcePosition, targetPosition, borderRadius = 5) {
|
|
1357
|
+
const [points, labelX, labelY] = getPoints({
|
|
1358
|
+
source,
|
|
1359
|
+
sourcePosition,
|
|
1360
|
+
target,
|
|
1361
|
+
targetPosition,
|
|
1362
|
+
offset: 20
|
|
1363
|
+
});
|
|
1364
|
+
const path = points.reduce((res, p, i) => {
|
|
1365
|
+
let segment = '';
|
|
1366
|
+
if (i > 0 && i < points.length - 1) {
|
|
1367
|
+
segment = getBend(points[i - 1], p, points[i + 1], borderRadius);
|
|
1368
|
+
}
|
|
1369
|
+
else {
|
|
1370
|
+
segment = `${i === 0 ? 'M' : 'L'}${p.x} ${p.y}`;
|
|
1371
|
+
}
|
|
1372
|
+
res += segment;
|
|
1373
|
+
return res;
|
|
1374
|
+
}, '');
|
|
1375
|
+
return {
|
|
1376
|
+
path,
|
|
1377
|
+
points: {
|
|
1378
|
+
// TODO start and end points temporary unavailable for this path
|
|
1379
|
+
start: { x: labelX, y: labelY },
|
|
1380
|
+
center: { x: labelX, y: labelY },
|
|
1381
|
+
end: { x: labelX, y: labelY },
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1216
1386
|
class EdgeModel {
|
|
1217
1387
|
constructor(edge) {
|
|
1218
1388
|
this.edge = edge;
|
|
@@ -1282,6 +1452,10 @@ class EdgeModel {
|
|
|
1282
1452
|
return straightPath(source.pointAbsolute(), target.pointAbsolute(), this.usingPoints);
|
|
1283
1453
|
case 'bezier':
|
|
1284
1454
|
return bezierPath(source.pointAbsolute(), target.pointAbsolute(), source.rawHandle.position, target.rawHandle.position, this.usingPoints);
|
|
1455
|
+
case 'smooth-step':
|
|
1456
|
+
return smoothStepPath(source.pointAbsolute(), target.pointAbsolute(), source.rawHandle.position, target.rawHandle.position);
|
|
1457
|
+
case 'step':
|
|
1458
|
+
return smoothStepPath(source.pointAbsolute(), target.pointAbsolute(), source.rawHandle.position, target.rawHandle.position, 0);
|
|
1285
1459
|
}
|
|
1286
1460
|
});
|
|
1287
1461
|
this.edgeLabels = {};
|
|
@@ -1558,6 +1732,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
|
|
|
1558
1732
|
args: ['onEdgesChange.select.many']
|
|
1559
1733
|
}] } });
|
|
1560
1734
|
|
|
1735
|
+
function isGroupNode(node) {
|
|
1736
|
+
return node.node.type === 'default-group' || node.node.type === 'template-group';
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1561
1739
|
class NodeRenderingService {
|
|
1562
1740
|
constructor() {
|
|
1563
1741
|
this.flowEntitiesService = inject(FlowEntitiesService);
|
|
@@ -1565,6 +1743,12 @@ class NodeRenderingService {
|
|
|
1565
1743
|
return this.flowEntitiesService.nodes()
|
|
1566
1744
|
.sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
|
|
1567
1745
|
});
|
|
1746
|
+
this.groups = computed(() => {
|
|
1747
|
+
return this.nodes().filter(n => isGroupNode(n));
|
|
1748
|
+
});
|
|
1749
|
+
this.nonGroups = computed(() => {
|
|
1750
|
+
return this.nodes().filter(n => !isGroupNode(n));
|
|
1751
|
+
});
|
|
1568
1752
|
this.maxOrder = computed(() => {
|
|
1569
1753
|
return Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
|
|
1570
1754
|
});
|
|
@@ -2470,6 +2654,8 @@ class ConnectionComponent {
|
|
|
2470
2654
|
switch (this.model.curve) {
|
|
2471
2655
|
case 'straight': return straightPath(sourcePoint, targetPoint).path;
|
|
2472
2656
|
case 'bezier': return bezierPath(sourcePoint, targetPoint, sourcePosition, targetPosition).path;
|
|
2657
|
+
case 'smooth-step': return smoothStepPath(sourcePoint, targetPoint, sourcePosition, targetPosition).path;
|
|
2658
|
+
case 'step': return smoothStepPath(sourcePoint, targetPoint, sourcePosition, targetPosition, 0).path;
|
|
2473
2659
|
}
|
|
2474
2660
|
}
|
|
2475
2661
|
if (status.state === 'connection-validation') {
|
|
@@ -2487,6 +2673,8 @@ class ConnectionComponent {
|
|
|
2487
2673
|
switch (this.model.curve) {
|
|
2488
2674
|
case 'straight': return straightPath(sourcePoint, targetPoint).path;
|
|
2489
2675
|
case 'bezier': return bezierPath(sourcePoint, targetPoint, sourcePosition, targetPosition).path;
|
|
2676
|
+
case 'smooth-step': return smoothStepPath(sourcePoint, targetPoint, sourcePosition, targetPosition).path;
|
|
2677
|
+
case 'step': return smoothStepPath(sourcePoint, targetPoint, sourcePosition, targetPosition, 0).path;
|
|
2490
2678
|
}
|
|
2491
2679
|
}
|
|
2492
2680
|
return null;
|
|
@@ -2592,12 +2780,15 @@ const defaultBg = '#fff';
|
|
|
2592
2780
|
const defaultGap = 20;
|
|
2593
2781
|
const defaultDotSize = 2;
|
|
2594
2782
|
const defaultDotColor = 'rgb(177, 177, 183)';
|
|
2783
|
+
const defaultImageScale = 0.1;
|
|
2784
|
+
const defaultRepeated = true;
|
|
2595
2785
|
class BackgroundComponent {
|
|
2596
2786
|
constructor() {
|
|
2597
2787
|
this.viewportService = inject(ViewportService);
|
|
2598
2788
|
this.rootSvg = inject(RootSvgReferenceDirective).element;
|
|
2599
2789
|
this.settingsService = inject(FlowSettingsService);
|
|
2600
2790
|
this.backgroundSignal = this.settingsService.background;
|
|
2791
|
+
// DOTS PATTERN
|
|
2601
2792
|
this.scaledGap = computed(() => {
|
|
2602
2793
|
const background = this.backgroundSignal();
|
|
2603
2794
|
if (background.type === 'dots') {
|
|
@@ -2608,7 +2799,13 @@ class BackgroundComponent {
|
|
|
2608
2799
|
});
|
|
2609
2800
|
this.x = computed(() => this.viewportService.readableViewport().x % this.scaledGap());
|
|
2610
2801
|
this.y = computed(() => this.viewportService.readableViewport().y % this.scaledGap());
|
|
2611
|
-
this.patternColor = computed(() =>
|
|
2802
|
+
this.patternColor = computed(() => {
|
|
2803
|
+
const bg = this.backgroundSignal();
|
|
2804
|
+
if (bg.type === 'dots') {
|
|
2805
|
+
return bg.color ?? defaultDotColor;
|
|
2806
|
+
}
|
|
2807
|
+
return defaultDotColor;
|
|
2808
|
+
});
|
|
2612
2809
|
this.patternSize = computed(() => {
|
|
2613
2810
|
const background = this.backgroundSignal();
|
|
2614
2811
|
if (background.type === 'dots') {
|
|
@@ -2616,6 +2813,56 @@ class BackgroundComponent {
|
|
|
2616
2813
|
}
|
|
2617
2814
|
return 0;
|
|
2618
2815
|
});
|
|
2816
|
+
// IMAGE PATTERN
|
|
2817
|
+
this.bgImageSrc = computed(() => {
|
|
2818
|
+
const background = this.backgroundSignal();
|
|
2819
|
+
return background.type === 'image' ? background.src : '';
|
|
2820
|
+
});
|
|
2821
|
+
this.imageSize = toSignal(toObservable(this.backgroundSignal).pipe(switchMap(() => createImage(this.bgImageSrc())), map((image) => ({ width: image.naturalWidth, height: image.naturalHeight }))), { initialValue: { width: 0, height: 0 } });
|
|
2822
|
+
this.scaledImageWidth = computed(() => {
|
|
2823
|
+
const background = this.backgroundSignal();
|
|
2824
|
+
if (background.type === 'image') {
|
|
2825
|
+
const zoom = background.fixed ? 1 : this.viewportService.readableViewport().zoom;
|
|
2826
|
+
return this.imageSize().width * zoom * (background.scale ?? defaultImageScale);
|
|
2827
|
+
}
|
|
2828
|
+
return 0;
|
|
2829
|
+
});
|
|
2830
|
+
this.scaledImageHeight = computed(() => {
|
|
2831
|
+
const background = this.backgroundSignal();
|
|
2832
|
+
if (background.type === 'image') {
|
|
2833
|
+
const zoom = background.fixed ? 1 : this.viewportService.readableViewport().zoom;
|
|
2834
|
+
return this.imageSize().height * zoom * (background.scale ?? defaultImageScale);
|
|
2835
|
+
}
|
|
2836
|
+
return 0;
|
|
2837
|
+
});
|
|
2838
|
+
this.imageX = computed(() => {
|
|
2839
|
+
const background = this.backgroundSignal();
|
|
2840
|
+
if (background.type === 'image') {
|
|
2841
|
+
if (!background.repeat) {
|
|
2842
|
+
return background.fixed ? 0 : this.viewportService.readableViewport().x;
|
|
2843
|
+
}
|
|
2844
|
+
return background.fixed
|
|
2845
|
+
? 0
|
|
2846
|
+
: this.viewportService.readableViewport().x % this.scaledImageWidth();
|
|
2847
|
+
}
|
|
2848
|
+
return 0;
|
|
2849
|
+
});
|
|
2850
|
+
this.imageY = computed(() => {
|
|
2851
|
+
const background = this.backgroundSignal();
|
|
2852
|
+
if (background.type === 'image') {
|
|
2853
|
+
if (!background.repeat) {
|
|
2854
|
+
return background.fixed ? 0 : this.viewportService.readableViewport().y;
|
|
2855
|
+
}
|
|
2856
|
+
return background.fixed
|
|
2857
|
+
? 0
|
|
2858
|
+
: this.viewportService.readableViewport().y % this.scaledImageHeight();
|
|
2859
|
+
}
|
|
2860
|
+
return 0;
|
|
2861
|
+
});
|
|
2862
|
+
this.repeated = computed(() => {
|
|
2863
|
+
const background = this.backgroundSignal();
|
|
2864
|
+
return background.type === 'image' && (background.repeat ?? defaultRepeated);
|
|
2865
|
+
});
|
|
2619
2866
|
// Without ID there will be pattern collision for several flows on the page
|
|
2620
2867
|
// Later pattern ID may be exposed to API
|
|
2621
2868
|
this.patternId = id();
|
|
@@ -2631,12 +2878,19 @@ class BackgroundComponent {
|
|
|
2631
2878
|
});
|
|
2632
2879
|
}
|
|
2633
2880
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: BackgroundComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2634
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: BackgroundComponent, selector: "g[background]", ngImport: i0, template: "<ng-container *ngIf=\"backgroundSignal().type === 'dots'\">\n <svg:pattern\n [attr.id]=\"patternId\"\n [attr.x]=\"x()\"\n [attr.y]=\"y()\"\n [attr.width]=\"scaledGap()\"\n [attr.height]=\"scaledGap()\"\n patternUnits=\"userSpaceOnUse\"\n >\n <svg:circle\n [attr.cx]=\"patternSize()\"\n [attr.cy]=\"patternSize()\"\n [attr.r]=\"patternSize()\"\n [attr.fill]=\"patternColor()\"\n />\n </svg:pattern>\n\n <svg:rect\n x=\"0\"\n y=\"0\"\n width=\"100%\"\n height=\"100%\"\n [attr.fill]=\"patternUrl\"\n />\n</ng-container>\n", dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2881
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: BackgroundComponent, selector: "g[background]", ngImport: i0, template: "<ng-container *ngIf=\"backgroundSignal().type === 'dots'\">\n <svg:pattern\n [attr.id]=\"patternId\"\n [attr.x]=\"x()\"\n [attr.y]=\"y()\"\n [attr.width]=\"scaledGap()\"\n [attr.height]=\"scaledGap()\"\n patternUnits=\"userSpaceOnUse\"\n >\n <svg:circle\n [attr.cx]=\"patternSize()\"\n [attr.cy]=\"patternSize()\"\n [attr.r]=\"patternSize()\"\n [attr.fill]=\"patternColor()\"\n />\n </svg:pattern>\n\n <svg:rect\n x=\"0\"\n y=\"0\"\n width=\"100%\"\n height=\"100%\"\n [attr.fill]=\"patternUrl\"\n />\n</ng-container>\n\n<ng-container *ngIf=\"backgroundSignal().type === 'image'\">\n <ng-container *ngIf=\"repeated()\">\n <svg:pattern\n [attr.id]=\"patternId\"\n [attr.x]=\"imageX()\"\n [attr.y]=\"imageY()\"\n [attr.width]=\"scaledImageWidth()\"\n [attr.height]=\"scaledImageHeight()\"\n patternUnits=\"userSpaceOnUse\"\n >\n <svg:image\n [attr.href]=\"bgImageSrc()\"\n [attr.width]=\"scaledImageWidth()\"\n [attr.height]=\"scaledImageHeight()\"\n />\n </svg:pattern>\n\n <svg:rect\n x=\"0\"\n y=\"0\"\n width=\"100%\"\n height=\"100%\"\n [attr.fill]=\"patternUrl\"\n />\n </ng-container>\n\n <ng-container *ngIf=\"!repeated()\">\n <svg:image\n [attr.x]=\"imageX()\"\n [attr.y]=\"imageY()\"\n [attr.width]=\"scaledImageWidth()\"\n [attr.height]=\"scaledImageHeight()\"\n [attr.href]=\"bgImageSrc()\"\n />\n </ng-container>\n</ng-container>\n", dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2635
2882
|
}
|
|
2636
2883
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: BackgroundComponent, decorators: [{
|
|
2637
2884
|
type: Component,
|
|
2638
|
-
args: [{ selector: 'g[background]', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *ngIf=\"backgroundSignal().type === 'dots'\">\n <svg:pattern\n [attr.id]=\"patternId\"\n [attr.x]=\"x()\"\n [attr.y]=\"y()\"\n [attr.width]=\"scaledGap()\"\n [attr.height]=\"scaledGap()\"\n patternUnits=\"userSpaceOnUse\"\n >\n <svg:circle\n [attr.cx]=\"patternSize()\"\n [attr.cy]=\"patternSize()\"\n [attr.r]=\"patternSize()\"\n [attr.fill]=\"patternColor()\"\n />\n </svg:pattern>\n\n <svg:rect\n x=\"0\"\n y=\"0\"\n width=\"100%\"\n height=\"100%\"\n [attr.fill]=\"patternUrl\"\n />\n</ng-container>\n" }]
|
|
2885
|
+
args: [{ selector: 'g[background]', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *ngIf=\"backgroundSignal().type === 'dots'\">\n <svg:pattern\n [attr.id]=\"patternId\"\n [attr.x]=\"x()\"\n [attr.y]=\"y()\"\n [attr.width]=\"scaledGap()\"\n [attr.height]=\"scaledGap()\"\n patternUnits=\"userSpaceOnUse\"\n >\n <svg:circle\n [attr.cx]=\"patternSize()\"\n [attr.cy]=\"patternSize()\"\n [attr.r]=\"patternSize()\"\n [attr.fill]=\"patternColor()\"\n />\n </svg:pattern>\n\n <svg:rect\n x=\"0\"\n y=\"0\"\n width=\"100%\"\n height=\"100%\"\n [attr.fill]=\"patternUrl\"\n />\n</ng-container>\n\n<ng-container *ngIf=\"backgroundSignal().type === 'image'\">\n <ng-container *ngIf=\"repeated()\">\n <svg:pattern\n [attr.id]=\"patternId\"\n [attr.x]=\"imageX()\"\n [attr.y]=\"imageY()\"\n [attr.width]=\"scaledImageWidth()\"\n [attr.height]=\"scaledImageHeight()\"\n patternUnits=\"userSpaceOnUse\"\n >\n <svg:image\n [attr.href]=\"bgImageSrc()\"\n [attr.width]=\"scaledImageWidth()\"\n [attr.height]=\"scaledImageHeight()\"\n />\n </svg:pattern>\n\n <svg:rect\n x=\"0\"\n y=\"0\"\n width=\"100%\"\n height=\"100%\"\n [attr.fill]=\"patternUrl\"\n />\n </ng-container>\n\n <ng-container *ngIf=\"!repeated()\">\n <svg:image\n [attr.x]=\"imageX()\"\n [attr.y]=\"imageY()\"\n [attr.width]=\"scaledImageWidth()\"\n [attr.height]=\"scaledImageHeight()\"\n [attr.href]=\"bgImageSrc()\"\n />\n </ng-container>\n</ng-container>\n" }]
|
|
2639
2886
|
}], ctorParameters: function () { return []; } });
|
|
2887
|
+
function createImage(url) {
|
|
2888
|
+
const image = new Image();
|
|
2889
|
+
image.src = url;
|
|
2890
|
+
return new Promise(resolve => {
|
|
2891
|
+
image.onload = () => resolve(image);
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2640
2894
|
|
|
2641
2895
|
// TODO: too general purpose nane
|
|
2642
2896
|
class RootSvgContextDirective {
|
|
@@ -2746,9 +3000,12 @@ class VflowComponent {
|
|
|
2746
3000
|
this.keyboardService = inject(KeyboardService);
|
|
2747
3001
|
this.injector = inject(Injector);
|
|
2748
3002
|
this.optimization = {
|
|
2749
|
-
computeLayersOnInit: true
|
|
3003
|
+
computeLayersOnInit: true,
|
|
3004
|
+
detachedGroupsLayer: false
|
|
2750
3005
|
};
|
|
2751
3006
|
this.nodeModels = computed(() => this.nodeRenderingService.nodes());
|
|
3007
|
+
this.groups = computed(() => this.nodeRenderingService.groups());
|
|
3008
|
+
this.nonGroups = computed(() => this.nodeRenderingService.nonGroups());
|
|
2752
3009
|
this.edgeModels = computed(() => this.flowEntitiesService.validEdges());
|
|
2753
3010
|
// #endregion
|
|
2754
3011
|
// #region OUTPUTS
|
|
@@ -2952,7 +3209,7 @@ class VflowComponent {
|
|
|
2952
3209
|
ComponentEventBusService,
|
|
2953
3210
|
KeyboardService,
|
|
2954
3211
|
OverlaysService
|
|
2955
|
-
], queries: [{ propertyName: "nodeTemplateDirective", first: true, predicate: NodeHtmlTemplateDirective, descendants: true }, { propertyName: "groupNodeTemplateDirective", first: true, predicate: GroupNodeTemplateDirective, descendants: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateDirective, descendants: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateDirective, descendants: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "mapContext", first: true, predicate: MapContextDirective, descendants: true }, { propertyName: "spacePointContext", first: true, predicate: SpacePointContextDirective, descendants: true }], hostDirectives: [{ directive: ConnectionControllerDirective, outputs: ["onConnect", "onConnect"] }, { directive: ChangesControllerDirective, outputs: ["onNodesChange", "onNodesChange", "onNodesChange.position", "onNodesChange.position", "onNodesChange.position.single", "onNodesChange.position.single", "onNodesChange.position.many", "onNodesChange.position.many", "onNodesChange.size", "onNodesChange.size", "onNodesChange.size.single", "onNodesChange.size.single", "onNodesChange.size.many", "onNodesChange.size.many", "onNodesChange.add", "onNodesChange.add", "onNodesChange.add.single", "onNodesChange.add.single", "onNodesChange.add.many", "onNodesChange.add.many", "onNodesChange.remove", "onNodesChange.remove", "onNodesChange.remove.single", "onNodesChange.remove.single", "onNodesChange.remove.many", "onNodesChange.remove.many", "onNodesChange.select", "onNodesChange.select", "onNodesChange.select.single", "onNodesChange.select.single", "onNodesChange.select.many", "onNodesChange.select.many", "onEdgesChange", "onEdgesChange", "onEdgesChange.detached", "onEdgesChange.detached", "onEdgesChange.detached.single", "onEdgesChange.detached.single", "onEdgesChange.detached.many", "onEdgesChange.detached.many", "onEdgesChange.add", "onEdgesChange.add", "onEdgesChange.add.single", "onEdgesChange.add.single", "onEdgesChange.add.many", "onEdgesChange.add.many", "onEdgesChange.remove", "onEdgesChange.remove", "onEdgesChange.remove.single", "onEdgesChange.remove.single", "onEdgesChange.remove.many", "onEdgesChange.remove.many", "onEdgesChange.select", "onEdgesChange.select", "onEdgesChange.select.single", "onEdgesChange.select.single", "onEdgesChange.select.many", "onEdgesChange.select.many"] }], ngImport: i0, template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\n flowSizeController\n class=\"root-svg\"\n #flow\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g background />\n\n <svg:g\n mapContext\n spacePointContext\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n
|
|
3212
|
+
], queries: [{ propertyName: "nodeTemplateDirective", first: true, predicate: NodeHtmlTemplateDirective, descendants: true }, { propertyName: "groupNodeTemplateDirective", first: true, predicate: GroupNodeTemplateDirective, descendants: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateDirective, descendants: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateDirective, descendants: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "mapContext", first: true, predicate: MapContextDirective, descendants: true }, { propertyName: "spacePointContext", first: true, predicate: SpacePointContextDirective, descendants: true }], hostDirectives: [{ directive: ConnectionControllerDirective, outputs: ["onConnect", "onConnect"] }, { directive: ChangesControllerDirective, outputs: ["onNodesChange", "onNodesChange", "onNodesChange.position", "onNodesChange.position", "onNodesChange.position.single", "onNodesChange.position.single", "onNodesChange.position.many", "onNodesChange.position.many", "onNodesChange.size", "onNodesChange.size", "onNodesChange.size.single", "onNodesChange.size.single", "onNodesChange.size.many", "onNodesChange.size.many", "onNodesChange.add", "onNodesChange.add", "onNodesChange.add.single", "onNodesChange.add.single", "onNodesChange.add.many", "onNodesChange.add.many", "onNodesChange.remove", "onNodesChange.remove", "onNodesChange.remove.single", "onNodesChange.remove.single", "onNodesChange.remove.many", "onNodesChange.remove.many", "onNodesChange.select", "onNodesChange.select", "onNodesChange.select.single", "onNodesChange.select.single", "onNodesChange.select.many", "onNodesChange.select.many", "onEdgesChange", "onEdgesChange", "onEdgesChange.detached", "onEdgesChange.detached", "onEdgesChange.detached.single", "onEdgesChange.detached.single", "onEdgesChange.detached.many", "onEdgesChange.detached.many", "onEdgesChange.add", "onEdgesChange.add", "onEdgesChange.add.single", "onEdgesChange.add.single", "onEdgesChange.add.many", "onEdgesChange.add.many", "onEdgesChange.remove", "onEdgesChange.remove", "onEdgesChange.remove.single", "onEdgesChange.remove.single", "onEdgesChange.remove.many", "onEdgesChange.remove.many", "onEdgesChange.select", "onEdgesChange.select", "onEdgesChange.select.single", "onEdgesChange.select.single", "onEdgesChange.select.many", "onEdgesChange.select.many"] }], ngImport: i0, template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\n flowSizeController\n class=\"root-svg\"\n #flow\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g background />\n\n <svg:g\n mapContext\n spacePointContext\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <ng-container *ngIf=\"optimization.detachedGroupsLayer\">\n <!-- Groups -->\n <svg:g\n *ngFor=\"let model of groups(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nonGroups(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </ng-container>\n\n <ng-container *ngIf=\"!optimization.detachedGroupsLayer\">\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </ng-container>\n\n </svg:g>\n\n <!-- Minimap -->\n <ng-container *ngIf=\"minimap() as minimap\">\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\n </ng-container>\n</svg:svg>\n\n", styles: [":host{display:block;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NodeComponent, selector: "g[node]", inputs: ["nodeModel", "nodeTemplate", "groupNodeTemplate"] }, { kind: "component", type: EdgeComponent, selector: "g[edge]", inputs: ["model", "edgeTemplate", "edgeLabelHtmlTemplate"] }, { kind: "component", type: ConnectionComponent, selector: "g[connection]", inputs: ["model", "template"] }, { kind: "component", type: DefsComponent, selector: "defs[flowDefs]", inputs: ["markers"] }, { kind: "component", type: BackgroundComponent, selector: "g[background]" }, { kind: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]" }, { kind: "directive", type: RootSvgReferenceDirective, selector: "svg[rootSvgRef]" }, { kind: "directive", type: RootSvgContextDirective, selector: "svg[rootSvgContext]" }, { kind: "directive", type: RootPointerDirective, selector: "svg[rootPointer]" }, { kind: "directive", type: FlowSizeControllerDirective, selector: "svg[flowSizeController]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2956
3213
|
}
|
|
2957
3214
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, decorators: [{
|
|
2958
3215
|
type: Component,
|
|
@@ -2972,7 +3229,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
|
|
|
2972
3229
|
], hostDirectives: [
|
|
2973
3230
|
connectionControllerHostDirective,
|
|
2974
3231
|
changesControllerHostDirective
|
|
2975
|
-
], template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\n flowSizeController\n class=\"root-svg\"\n #flow\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g background />\n\n <svg:g\n mapContext\n spacePointContext\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n
|
|
3232
|
+
], template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\n flowSizeController\n class=\"root-svg\"\n #flow\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g background />\n\n <svg:g\n mapContext\n spacePointContext\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <ng-container *ngIf=\"optimization.detachedGroupsLayer\">\n <!-- Groups -->\n <svg:g\n *ngFor=\"let model of groups(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nonGroups(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </ng-container>\n\n <ng-container *ngIf=\"!optimization.detachedGroupsLayer\">\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </ng-container>\n\n </svg:g>\n\n <!-- Minimap -->\n <ng-container *ngIf=\"minimap() as minimap\">\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\n </ng-container>\n</svg:svg>\n\n", styles: [":host{display:block;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}\n"] }]
|
|
2976
3233
|
}], propDecorators: { view: [{
|
|
2977
3234
|
type: Input
|
|
2978
3235
|
}], minZoom: [{
|
|
@@ -3085,7 +3342,13 @@ class MiniMapComponent {
|
|
|
3085
3342
|
}
|
|
3086
3343
|
return 0.2;
|
|
3087
3344
|
});
|
|
3088
|
-
this.viewportColor = computed(() =>
|
|
3345
|
+
this.viewportColor = computed(() => {
|
|
3346
|
+
const bg = this.flowSettingsService.background();
|
|
3347
|
+
if (bg.type === 'dots' || bg.type === 'solid') {
|
|
3348
|
+
return bg.color ?? '#fff';
|
|
3349
|
+
}
|
|
3350
|
+
return '#fff';
|
|
3351
|
+
});
|
|
3089
3352
|
this.hovered = signal(false);
|
|
3090
3353
|
this.minimapPoint = computed(() => {
|
|
3091
3354
|
switch (this.minimapPosition()) {
|