ngx-vflow 1.11.1 → 1.12.1
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 +4 -3
- package/esm2022/lib/vflow/components/node/node.component.mjs +3 -1
- package/esm2022/lib/vflow/components/preview-flow/draw-node.mjs +100 -0
- package/esm2022/lib/vflow/components/preview-flow/preview-flow.component.mjs +62 -0
- package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +26 -12
- package/esm2022/lib/vflow/directives/map-context.directive.mjs +28 -18
- package/esm2022/lib/vflow/directives/space-point-context.directive.mjs +3 -3
- package/esm2022/lib/vflow/interfaces/node-preview.interface.mjs +2 -0
- package/esm2022/lib/vflow/interfaces/node.interface.mjs +1 -1
- package/esm2022/lib/vflow/interfaces/optimization.interface.mjs +6 -2
- package/esm2022/lib/vflow/math/edge-path/smooth-step-path.mjs +60 -4
- package/esm2022/lib/vflow/models/edge.model.mjs +42 -15
- package/esm2022/lib/vflow/models/handle.model.mjs +3 -1
- package/esm2022/lib/vflow/models/node.model.mjs +6 -1
- package/esm2022/lib/vflow/services/edge-rendering.service.mjs +14 -2
- package/esm2022/lib/vflow/services/flow-settings.service.mjs +3 -1
- package/esm2022/lib/vflow/services/node-rendering.service.mjs +35 -2
- package/esm2022/lib/vflow/services/preview-flow-render-strategy.service.mjs +21 -0
- package/esm2022/lib/vflow/services/viewport.service.mjs +8 -1
- package/esm2022/lib/vflow/utils/assert-injector.mjs +27 -0
- package/esm2022/lib/vflow/utils/signals/extended-computed.mjs +15 -0
- package/esm2022/lib/vflow/utils/signals/to-lazy-signal.mjs +35 -0
- package/esm2022/lib/vflow/utils/viewport.mjs +37 -1
- package/esm2022/public-api.mjs +2 -1
- package/esm2022/testing/component-mocks/vflow-mock.component.mjs +7 -7
- package/fesm2022/ngx-vflow-testing.mjs +6 -6
- package/fesm2022/ngx-vflow-testing.mjs.map +1 -1
- package/fesm2022/ngx-vflow.mjs +495 -49
- package/fesm2022/ngx-vflow.mjs.map +1 -1
- package/lib/vflow/components/preview-flow/draw-node.d.ts +2 -0
- package/lib/vflow/components/preview-flow/preview-flow.component.d.ts +15 -0
- package/lib/vflow/components/vflow/vflow.component.d.ts +5 -2
- package/lib/vflow/directives/map-context.directive.d.ts +3 -2
- package/lib/vflow/interfaces/node-preview.interface.d.ts +3 -0
- package/lib/vflow/interfaces/node.interface.d.ts +3 -0
- package/lib/vflow/interfaces/optimization.interface.d.ts +17 -1
- package/lib/vflow/models/node.model.d.ts +3 -0
- package/lib/vflow/services/edge-rendering.service.d.ts +2 -0
- package/lib/vflow/services/flow-settings.service.d.ts +2 -0
- package/lib/vflow/services/node-rendering.service.d.ts +4 -0
- package/lib/vflow/services/preview-flow-render-strategy.service.d.ts +12 -0
- package/lib/vflow/services/viewport.service.d.ts +3 -0
- package/lib/vflow/utils/assert-injector.d.ts +44 -0
- package/lib/vflow/utils/signals/extended-computed.d.ts +5 -0
- package/lib/vflow/utils/signals/to-lazy-signal.d.ts +20 -0
- package/lib/vflow/utils/viewport.d.ts +19 -0
- package/package.json +1 -1
- package/public-api.d.ts +1 -0
- package/testing/component-mocks/vflow-mock.component.d.ts +3 -3
package/fesm2022/ngx-vflow.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, computed, Injectable, inject, ElementRef, Directive, effect, untracked, TemplateRef, DestroyRef, EventEmitter, OutputEmitterRef, input,
|
|
2
|
+
import { signal, computed, Injectable, inject, ElementRef, Directive, NgZone, effect, untracked, TemplateRef, DestroyRef, EventEmitter, OutputEmitterRef, input, assertInInjectionContext, Injector, runInInjectionContext, viewChild, Component, ChangeDetectionStrategy, output, HostListener, Renderer2, contentChild, Input, forwardRef } from '@angular/core';
|
|
3
3
|
import { select } from 'd3-selection';
|
|
4
4
|
import { zoomIdentity, zoom } from 'd3-zoom';
|
|
5
|
-
import { switchMap, merge, fromEvent, tap,
|
|
5
|
+
import { Subject, switchMap, merge, fromEvent, tap, Observable, skip, map, pairwise, filter, distinctUntilChanged, observeOn, asyncScheduler, zip, debounceTime, animationFrameScheduler, share, startWith } from 'rxjs';
|
|
6
6
|
import { toObservable, takeUntilDestroyed, outputFromObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
7
7
|
import { drag } from 'd3-drag';
|
|
8
8
|
import { __decorate } from 'tslib';
|
|
@@ -177,6 +177,48 @@ function getViewportForBounds(bounds, width, height, minZoom, maxZoom, padding)
|
|
|
177
177
|
function clamp(value, min = 0, max = 1) {
|
|
178
178
|
return Math.min(Math.max(value, min), max);
|
|
179
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Calculates the visible area bounds in world coordinates based on the current viewport state
|
|
182
|
+
*
|
|
183
|
+
* @param viewport Current viewport state (x, y, zoom)
|
|
184
|
+
* @param flowWidth Width of the flow container
|
|
185
|
+
* @param flowHeight Height of the flow container
|
|
186
|
+
* @returns Rect representing the visible area in world coordinates
|
|
187
|
+
*/
|
|
188
|
+
function getViewportBounds(viewport, flowWidth, flowHeight) {
|
|
189
|
+
const zoom = viewport.zoom;
|
|
190
|
+
return {
|
|
191
|
+
x: -viewport.x / zoom,
|
|
192
|
+
y: -viewport.y / zoom,
|
|
193
|
+
width: flowWidth / zoom,
|
|
194
|
+
height: flowHeight / zoom,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Checks if a rectangle intersects with the viewport's visible area
|
|
199
|
+
*
|
|
200
|
+
* @param rect Rectangle to check (in world coordinates)
|
|
201
|
+
* @param viewport Current viewport state
|
|
202
|
+
* @param flowWidth Width of the flow container
|
|
203
|
+
* @param flowHeight Height of the flow container
|
|
204
|
+
* @returns true if the rectangle intersects with the viewport, false otherwise
|
|
205
|
+
*/
|
|
206
|
+
function isRectInViewport(rect, viewport, flowWidth, flowHeight) {
|
|
207
|
+
const viewportBounds = getViewportBounds(viewport, flowWidth, flowHeight);
|
|
208
|
+
// Check if rectangles intersect using standard rectangle intersection test
|
|
209
|
+
// No intersection if: rect is completely to the left, right, above, or below the viewport
|
|
210
|
+
const isNotIntersecting = rect.x + rect.width < viewportBounds.x || // Rect is completely to the left
|
|
211
|
+
rect.x > viewportBounds.x + viewportBounds.width || // Rect is completely to the right
|
|
212
|
+
rect.y + rect.height < viewportBounds.y || // Rect is completely above
|
|
213
|
+
rect.y > viewportBounds.y + viewportBounds.height; // Rect is completely below
|
|
214
|
+
return !isNotIntersecting;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const DEFAULT_OPTIMIZATION = {
|
|
218
|
+
detachedGroupsLayer: false,
|
|
219
|
+
virtualization: false,
|
|
220
|
+
virtualizationZoomThreshold: 0.5,
|
|
221
|
+
};
|
|
180
222
|
|
|
181
223
|
class FlowSettingsService {
|
|
182
224
|
constructor() {
|
|
@@ -199,6 +241,7 @@ class FlowSettingsService {
|
|
|
199
241
|
this.maxZoom = signal(3);
|
|
200
242
|
this.background = signal({ type: 'solid', color: '#fff' });
|
|
201
243
|
this.snapGrid = signal([1, 1]);
|
|
244
|
+
this.optimization = signal(DEFAULT_OPTIMIZATION);
|
|
202
245
|
}
|
|
203
246
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowSettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
204
247
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowSettingsService }); }
|
|
@@ -226,6 +269,7 @@ class ViewportService {
|
|
|
226
269
|
* - writableViewport signal
|
|
227
270
|
*/
|
|
228
271
|
this.readableViewport = signal(ViewportService.getDefaultViewport());
|
|
272
|
+
this.viewportChangeEnd$ = new Subject();
|
|
229
273
|
}
|
|
230
274
|
/**
|
|
231
275
|
* The default value used by d3, just copy it here
|
|
@@ -242,6 +286,11 @@ class ViewportService {
|
|
|
242
286
|
const duration = options.duration ?? 0;
|
|
243
287
|
this.writableViewport.set({ changeType: 'absolute', state, duration });
|
|
244
288
|
}
|
|
289
|
+
triggerViewportChangeEvent(type) {
|
|
290
|
+
if (type === 'end') {
|
|
291
|
+
this.viewportChangeEnd$.next();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
245
294
|
getBoundsNodes(nodeIds) {
|
|
246
295
|
return !nodeIds?.length
|
|
247
296
|
? // If nodes option not passed or the list is empty, then get fit the whole view
|
|
@@ -398,8 +447,9 @@ class MapContextDirective {
|
|
|
398
447
|
this.selectionService = inject(SelectionService);
|
|
399
448
|
this.viewportService = inject(ViewportService);
|
|
400
449
|
this.flowSettingsService = inject(FlowSettingsService);
|
|
450
|
+
this.zone = inject(NgZone);
|
|
401
451
|
this.rootSvgSelection = select(this.rootSvg);
|
|
402
|
-
this.
|
|
452
|
+
this.transform = signal('');
|
|
403
453
|
this.viewportForSelection = {};
|
|
404
454
|
// under the hood this effect triggers handleZoom, so error throws without this flag
|
|
405
455
|
this.manualViewportChangeEffect = effect(() => {
|
|
@@ -435,7 +485,7 @@ class MapContextDirective {
|
|
|
435
485
|
this.handleZoom = ({ transform }) => {
|
|
436
486
|
// update public signal for user to read
|
|
437
487
|
this.viewportService.readableViewport.set(mapTransformToViewportState(transform));
|
|
438
|
-
this.
|
|
488
|
+
this.transform.set(transform.toString());
|
|
439
489
|
};
|
|
440
490
|
this.handleZoomStart = ({ transform }) => {
|
|
441
491
|
this.viewportForSelection = {
|
|
@@ -443,12 +493,16 @@ class MapContextDirective {
|
|
|
443
493
|
};
|
|
444
494
|
};
|
|
445
495
|
this.handleZoomEnd = ({ transform, sourceEvent }) => {
|
|
446
|
-
this.
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
496
|
+
this.zone.run(() => {
|
|
497
|
+
this.viewportForSelection = {
|
|
498
|
+
...this.viewportForSelection,
|
|
499
|
+
end: mapTransformToViewportState(transform),
|
|
500
|
+
target: evTarget(sourceEvent),
|
|
501
|
+
};
|
|
502
|
+
this.viewportService.triggerViewportChangeEvent('end');
|
|
503
|
+
// TODO: maybe use triggerViewportChangeEvent instead of this method?
|
|
504
|
+
this.selectionService.setViewport(this.viewportForSelection);
|
|
505
|
+
});
|
|
452
506
|
};
|
|
453
507
|
this.filterCondition = (event) => {
|
|
454
508
|
if (event.type === 'mousedown' || event.type === 'touchstart') {
|
|
@@ -458,22 +512,27 @@ class MapContextDirective {
|
|
|
458
512
|
};
|
|
459
513
|
}
|
|
460
514
|
ngOnInit() {
|
|
461
|
-
this.
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
515
|
+
this.zone.runOutsideAngular(() => {
|
|
516
|
+
this.zoomBehavior = zoom()
|
|
517
|
+
.scaleExtent([this.flowSettingsService.minZoom(), this.flowSettingsService.maxZoom()])
|
|
518
|
+
.filter(this.filterCondition)
|
|
519
|
+
.on('start', this.handleZoomStart)
|
|
520
|
+
.on('zoom', this.handleZoom)
|
|
521
|
+
.on('end', this.handleZoomEnd);
|
|
522
|
+
this.rootSvgSelection.call(this.zoomBehavior).on('dblclick.zoom', null);
|
|
523
|
+
});
|
|
468
524
|
}
|
|
469
525
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MapContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
470
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: MapContextDirective, isStandalone: true, selector: "g[mapContext]", ngImport: i0 }); }
|
|
526
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: MapContextDirective, isStandalone: true, selector: "g[mapContext]", host: { properties: { "attr.transform": "transform()" } }, ngImport: i0 }); }
|
|
471
527
|
}
|
|
472
528
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MapContextDirective, decorators: [{
|
|
473
529
|
type: Directive,
|
|
474
530
|
args: [{
|
|
475
531
|
standalone: true,
|
|
476
532
|
selector: 'g[mapContext]',
|
|
533
|
+
host: {
|
|
534
|
+
'[attr.transform]': 'transform()',
|
|
535
|
+
},
|
|
477
536
|
}]
|
|
478
537
|
}] });
|
|
479
538
|
const mapTransformToViewportState = (transform) => ({
|
|
@@ -966,6 +1025,7 @@ class NodeModel {
|
|
|
966
1025
|
constructor(rawNode) {
|
|
967
1026
|
this.rawNode = rawNode;
|
|
968
1027
|
this.entitiesService = inject(FlowEntitiesService);
|
|
1028
|
+
this.isVisible = signal(false);
|
|
969
1029
|
this.point = signal({ x: 0, y: 0 });
|
|
970
1030
|
this.width = signal(NodeModel.defaultWidth);
|
|
971
1031
|
this.height = signal(NodeModel.defaultHeight);
|
|
@@ -979,6 +1039,7 @@ class NodeModel {
|
|
|
979
1039
|
this.foHeight = computed(() => this.height() + MAGIC_NUMBER_TO_FIX_GLITCH_IN_CHROME);
|
|
980
1040
|
this.renderOrder = signal(0);
|
|
981
1041
|
this.selected = signal(false);
|
|
1042
|
+
this.preview = signal({ style: {} });
|
|
982
1043
|
this.globalPoint = computed(() => {
|
|
983
1044
|
let parent = this.parent();
|
|
984
1045
|
let x = this.point().x;
|
|
@@ -1030,6 +1091,9 @@ class NodeModel {
|
|
|
1030
1091
|
if (internalNode.parentId) {
|
|
1031
1092
|
this.parentId = internalNode.parentId;
|
|
1032
1093
|
}
|
|
1094
|
+
if (internalNode.preview) {
|
|
1095
|
+
this.preview = internalNode.preview;
|
|
1096
|
+
}
|
|
1033
1097
|
if (internalNode.type === 'default-group' && internalNode.color) {
|
|
1034
1098
|
this.color = internalNode.color;
|
|
1035
1099
|
}
|
|
@@ -1334,17 +1398,87 @@ function smoothStepPath({ sourcePoint, targetPoint, sourcePosition, targetPositi
|
|
|
1334
1398
|
res += segment;
|
|
1335
1399
|
return res;
|
|
1336
1400
|
}, '');
|
|
1401
|
+
// Performance optimization: Pre-calculate cumulative distances and use binary search
|
|
1402
|
+
const n = points.length;
|
|
1403
|
+
if (n < 2) {
|
|
1404
|
+
return {
|
|
1405
|
+
path,
|
|
1406
|
+
labelPoints: {
|
|
1407
|
+
start: { x: labelX, y: labelY },
|
|
1408
|
+
center: { x: labelX, y: labelY },
|
|
1409
|
+
end: { x: labelX, y: labelY },
|
|
1410
|
+
},
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
// Pre-calculate segment lengths and cumulative distances in a single loop
|
|
1414
|
+
const segmentLengths = new Array(n - 1);
|
|
1415
|
+
const cumulativeDistances = new Array(n);
|
|
1416
|
+
cumulativeDistances[0] = 0;
|
|
1417
|
+
let totalLength = 0;
|
|
1418
|
+
for (let i = 0; i < n - 1; i++) {
|
|
1419
|
+
const dx = points[i + 1].x - points[i].x;
|
|
1420
|
+
const dy = points[i + 1].y - points[i].y;
|
|
1421
|
+
const len = Math.sqrt(dx * dx + dy * dy);
|
|
1422
|
+
segmentLengths[i] = len;
|
|
1423
|
+
totalLength += len;
|
|
1424
|
+
cumulativeDistances[i + 1] = totalLength;
|
|
1425
|
+
}
|
|
1426
|
+
// Optimized helper function using binary search
|
|
1427
|
+
const getPointAtRatio = (ratio) => {
|
|
1428
|
+
const targetDistance = totalLength * ratio;
|
|
1429
|
+
// Edge cases
|
|
1430
|
+
if (targetDistance <= 0)
|
|
1431
|
+
return points[0];
|
|
1432
|
+
if (targetDistance >= totalLength)
|
|
1433
|
+
return points[n - 1];
|
|
1434
|
+
// Binary search for the correct segment
|
|
1435
|
+
let low = 0;
|
|
1436
|
+
let high = n - 1;
|
|
1437
|
+
while (low < high - 1) {
|
|
1438
|
+
const mid = (low + high) >>> 1; // Bitwise right shift is faster than Math.floor
|
|
1439
|
+
if (cumulativeDistances[mid] < targetDistance) {
|
|
1440
|
+
low = mid;
|
|
1441
|
+
}
|
|
1442
|
+
else {
|
|
1443
|
+
high = mid;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
// Calculate position within the segment
|
|
1447
|
+
const segmentStartDistance = cumulativeDistances[low];
|
|
1448
|
+
const localDistance = targetDistance - segmentStartDistance;
|
|
1449
|
+
const t = localDistance / segmentLengths[low];
|
|
1450
|
+
// Linear interpolation
|
|
1451
|
+
const start = points[low];
|
|
1452
|
+
const end = points[low + 1];
|
|
1453
|
+
return {
|
|
1454
|
+
x: start.x + (end.x - start.x) * t,
|
|
1455
|
+
y: start.y + (end.y - start.y) * t,
|
|
1456
|
+
};
|
|
1457
|
+
};
|
|
1337
1458
|
return {
|
|
1338
1459
|
path,
|
|
1339
1460
|
labelPoints: {
|
|
1340
|
-
|
|
1341
|
-
start: { x: labelX, y: labelY },
|
|
1461
|
+
start: getPointAtRatio(0.15),
|
|
1342
1462
|
center: { x: labelX, y: labelY },
|
|
1343
|
-
end:
|
|
1463
|
+
end: getPointAtRatio(0.85),
|
|
1344
1464
|
},
|
|
1345
1465
|
};
|
|
1346
1466
|
}
|
|
1347
1467
|
|
|
1468
|
+
// MIT License
|
|
1469
|
+
/**
|
|
1470
|
+
* @todo Use `linkedSignal` after Angular update
|
|
1471
|
+
*/
|
|
1472
|
+
function extendedComputed(computedCallback, options) {
|
|
1473
|
+
if (!options) {
|
|
1474
|
+
options = { equal: Object.is };
|
|
1475
|
+
}
|
|
1476
|
+
let currentValue = undefined;
|
|
1477
|
+
return computed(() => {
|
|
1478
|
+
return (currentValue = computedCallback(currentValue));
|
|
1479
|
+
}, options);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1348
1482
|
class EdgeModel {
|
|
1349
1483
|
constructor(edge) {
|
|
1350
1484
|
this.edge = edge;
|
|
@@ -1405,25 +1539,51 @@ class EdgeModel {
|
|
|
1405
1539
|
return this.curve(params);
|
|
1406
1540
|
}
|
|
1407
1541
|
});
|
|
1408
|
-
this.sourceHandle =
|
|
1542
|
+
this.sourceHandle = extendedComputed((previousHandle) => {
|
|
1543
|
+
let handle = null;
|
|
1409
1544
|
if (this.edge.sourceHandle) {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1545
|
+
handle =
|
|
1546
|
+
this.source()
|
|
1547
|
+
?.handles()
|
|
1548
|
+
.find((handle) => handle.rawHandle.id === this.edge.sourceHandle) ?? null;
|
|
1549
|
+
}
|
|
1550
|
+
else {
|
|
1551
|
+
handle =
|
|
1552
|
+
this.source()
|
|
1553
|
+
?.handles()
|
|
1554
|
+
.find((handle) => handle.rawHandle.type === 'source') ?? null;
|
|
1413
1555
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1556
|
+
// In case of virtual scrolling, if the node is scrolled out of view the handle may disappear
|
|
1557
|
+
// which could lead to the edge not being rendered
|
|
1558
|
+
// so we return the previous handle if the current one is null
|
|
1559
|
+
// TODO: check if this breaks anything
|
|
1560
|
+
if (handle === null) {
|
|
1561
|
+
return previousHandle;
|
|
1562
|
+
}
|
|
1563
|
+
return handle;
|
|
1417
1564
|
});
|
|
1418
|
-
this.targetHandle =
|
|
1565
|
+
this.targetHandle = extendedComputed((previousHandle) => {
|
|
1566
|
+
let handle = null;
|
|
1419
1567
|
if (this.edge.targetHandle) {
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1568
|
+
handle =
|
|
1569
|
+
this.target()
|
|
1570
|
+
?.handles()
|
|
1571
|
+
.find((handle) => handle.rawHandle.id === this.edge.targetHandle) ?? null;
|
|
1572
|
+
}
|
|
1573
|
+
else {
|
|
1574
|
+
handle =
|
|
1575
|
+
this.target()
|
|
1576
|
+
?.handles()
|
|
1577
|
+
.find((handle) => handle.rawHandle.type === 'target') ?? null;
|
|
1423
1578
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1579
|
+
// In case of virtual scrolling, if the node is scrolled out of view the handle may disappear
|
|
1580
|
+
// which could lead to the edge not being rendered
|
|
1581
|
+
// so we return the previous handle if the current one is null
|
|
1582
|
+
// TODO: check if this breaks anything
|
|
1583
|
+
if (handle === null) {
|
|
1584
|
+
return previousHandle;
|
|
1585
|
+
}
|
|
1586
|
+
return handle;
|
|
1427
1587
|
});
|
|
1428
1588
|
/**
|
|
1429
1589
|
* TODO: not reactive
|
|
@@ -1737,11 +1897,74 @@ function isGroupNode(node) {
|
|
|
1737
1897
|
return node.rawNode.type === 'default-group' || node.rawNode.type === 'template-group';
|
|
1738
1898
|
}
|
|
1739
1899
|
|
|
1900
|
+
// MIT License
|
|
1901
|
+
// Copyright (c) 2023 Chau Tran
|
|
1902
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1903
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
1904
|
+
// in the Software without restriction, including without limitation the rights
|
|
1905
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
1906
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
1907
|
+
// furnished to do so, subject to the following conditions:
|
|
1908
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
1909
|
+
// copies or substantial portions of the Software.
|
|
1910
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
1911
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1912
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
1913
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
1914
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
1915
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1916
|
+
// SOFTWARE.
|
|
1917
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
1918
|
+
function assertInjector(fn, injector, runner) {
|
|
1919
|
+
!injector && assertInInjectionContext(fn);
|
|
1920
|
+
const assertedInjector = injector ?? inject(Injector);
|
|
1921
|
+
if (!runner)
|
|
1922
|
+
return assertedInjector;
|
|
1923
|
+
return runInInjectionContext(assertedInjector, runner);
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
// MIT License
|
|
1927
|
+
// Copyright (c) 2023 Chau Tran
|
|
1928
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1929
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
1930
|
+
// in the Software without restriction, including without limitation the rights
|
|
1931
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
1932
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
1933
|
+
// furnished to do so, subject to the following conditions:
|
|
1934
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
1935
|
+
// copies or substantial portions of the Software.
|
|
1936
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
1937
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1938
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
1939
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
1940
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
1941
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1942
|
+
// SOFTWARE.
|
|
1943
|
+
/**
|
|
1944
|
+
* Function `toLazySignal()` is a proxy function that will call the original
|
|
1945
|
+
* `toSignal()` function when the returned signal is read for the first time.
|
|
1946
|
+
*/
|
|
1947
|
+
function toLazySignal(source, options) {
|
|
1948
|
+
const injector = assertInjector(toLazySignal, options?.injector);
|
|
1949
|
+
let s;
|
|
1950
|
+
return computed(() => {
|
|
1951
|
+
if (!s) {
|
|
1952
|
+
s = untracked(() => toSignal(source, { ...options, injector }));
|
|
1953
|
+
}
|
|
1954
|
+
return s();
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1740
1958
|
class NodeRenderingService {
|
|
1741
1959
|
constructor() {
|
|
1742
1960
|
this.flowEntitiesService = inject(FlowEntitiesService);
|
|
1961
|
+
this.flowSettingsService = inject(FlowSettingsService);
|
|
1962
|
+
this.viewportService = inject(ViewportService);
|
|
1743
1963
|
this.nodes = computed(() => {
|
|
1744
|
-
|
|
1964
|
+
if (!this.flowSettingsService.optimization().virtualization) {
|
|
1965
|
+
return [...this.flowEntitiesService.nodes()].sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
|
|
1966
|
+
}
|
|
1967
|
+
return this.viewportNodesAfterInteraction().sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
|
|
1745
1968
|
});
|
|
1746
1969
|
this.groups = computed(() => {
|
|
1747
1970
|
return this.nodes().filter((n) => isGroupNode(n));
|
|
@@ -1749,6 +1972,28 @@ class NodeRenderingService {
|
|
|
1749
1972
|
this.nonGroups = computed(() => {
|
|
1750
1973
|
return this.nodes().filter((n) => !isGroupNode(n));
|
|
1751
1974
|
});
|
|
1975
|
+
this.viewportNodes = computed(() => {
|
|
1976
|
+
const nodes = this.flowEntitiesService.nodes();
|
|
1977
|
+
const viewport = this.viewportService.readableViewport();
|
|
1978
|
+
const flowWidth = this.flowSettingsService.computedFlowWidth();
|
|
1979
|
+
const flowHeight = this.flowSettingsService.computedFlowHeight();
|
|
1980
|
+
return nodes.filter((n) => {
|
|
1981
|
+
const { x, y } = n.globalPoint();
|
|
1982
|
+
const width = n.width();
|
|
1983
|
+
const height = n.height();
|
|
1984
|
+
return isRectInViewport({ x, y, width, height }, viewport, flowWidth, flowHeight);
|
|
1985
|
+
});
|
|
1986
|
+
});
|
|
1987
|
+
this.viewportNodesAfterInteraction = toLazySignal(merge(
|
|
1988
|
+
// TODO: maybe there is a better way wait when viewport is ready?
|
|
1989
|
+
// (to correctly calculate viewport nodes on first render)
|
|
1990
|
+
toObservable(this.flowEntitiesService.nodes).pipe(observeOn(asyncScheduler), filter((nodes) => !!nodes.length)), this.viewportService.viewportChangeEnd$.pipe(debounceTime(300))).pipe(map(() => {
|
|
1991
|
+
const viewport = this.viewportService.readableViewport();
|
|
1992
|
+
const zoomThreshold = this.flowSettingsService.optimization().virtualizationZoomThreshold;
|
|
1993
|
+
return viewport.zoom < zoomThreshold ? [] : this.viewportNodes();
|
|
1994
|
+
})), {
|
|
1995
|
+
initialValue: [],
|
|
1996
|
+
});
|
|
1752
1997
|
this.maxOrder = computed(() => {
|
|
1753
1998
|
return Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
|
|
1754
1999
|
});
|
|
@@ -1839,7 +2084,7 @@ class SpacePointContextDirective {
|
|
|
1839
2084
|
y: movement.y,
|
|
1840
2085
|
});
|
|
1841
2086
|
});
|
|
1842
|
-
this.pointerMovement =
|
|
2087
|
+
this.pointerMovement = toLazySignal(this.pointerMovementDirective.pointerMovement$);
|
|
1843
2088
|
}
|
|
1844
2089
|
documentPointToFlowPoint(documentPoint) {
|
|
1845
2090
|
const point = this.rootSvg.createSVGPoint();
|
|
@@ -2144,8 +2389,19 @@ function statusToConnection(status, isStrictMode) {
|
|
|
2144
2389
|
class EdgeRenderingService {
|
|
2145
2390
|
constructor() {
|
|
2146
2391
|
this.flowEntitiesService = inject(FlowEntitiesService);
|
|
2392
|
+
this.flowSettingsService = inject(FlowSettingsService);
|
|
2147
2393
|
this.edges = computed(() => {
|
|
2148
|
-
|
|
2394
|
+
if (!this.flowSettingsService.optimization().virtualization) {
|
|
2395
|
+
return [...this.flowEntitiesService.validEdges()].sort((aEdge, bEdge) => aEdge.renderOrder() - bEdge.renderOrder());
|
|
2396
|
+
}
|
|
2397
|
+
return this.viewportEdges().sort((aEdge, bEdge) => aEdge.renderOrder() - bEdge.renderOrder());
|
|
2398
|
+
});
|
|
2399
|
+
this.viewportEdges = computed(() => {
|
|
2400
|
+
return this.flowEntitiesService.validEdges().filter((e) => {
|
|
2401
|
+
const sourceHandle = e.sourceHandle();
|
|
2402
|
+
const targetHandle = e.targetHandle();
|
|
2403
|
+
return sourceHandle && targetHandle;
|
|
2404
|
+
});
|
|
2149
2405
|
});
|
|
2150
2406
|
this.maxOrder = computed(() => {
|
|
2151
2407
|
return Math.max(...this.flowEntitiesService.validEdges().map((n) => n.renderOrder()));
|
|
@@ -2645,9 +2901,11 @@ class HandleModel {
|
|
|
2645
2901
|
});
|
|
2646
2902
|
this.state = signal('idle');
|
|
2647
2903
|
this.updateHostSizeAndPosition$ = new Subject();
|
|
2904
|
+
// TODO: for some reason toLazySignal breaks unit tests, so we use toSignal here
|
|
2648
2905
|
this.hostSize = toSignal(this.updateHostSizeAndPosition$.pipe(map(() => this.getHostSize())), {
|
|
2649
2906
|
initialValue: { width: 0, height: 0 },
|
|
2650
2907
|
});
|
|
2908
|
+
// TODO: for some reason toLazySignal breaks unit tests, so we use toSignal here
|
|
2651
2909
|
this.hostPosition = toSignal(this.updateHostSizeAndPosition$.pipe(map(() => ({
|
|
2652
2910
|
x: this.hostReference instanceof HTMLElement ? this.hostReference.offsetLeft : 0, // for now just 0 for group nodes
|
|
2653
2911
|
y: this.hostReference instanceof HTMLElement ? this.hostReference.offsetTop : 0, // for now just 0 for group nodes
|
|
@@ -2846,6 +3104,7 @@ class NodeComponent {
|
|
|
2846
3104
|
this.toolbars = computed(() => this.overlaysService.nodeToolbarsMap().get(this.model()));
|
|
2847
3105
|
}
|
|
2848
3106
|
ngOnInit() {
|
|
3107
|
+
this.model().isVisible.set(true);
|
|
2849
3108
|
this.nodeAccessor.model.set(this.model());
|
|
2850
3109
|
this.handleService.node.set(this.model());
|
|
2851
3110
|
effect(() => {
|
|
@@ -2858,6 +3117,7 @@ class NodeComponent {
|
|
|
2858
3117
|
}, { injector: this.injector });
|
|
2859
3118
|
}
|
|
2860
3119
|
ngOnDestroy() {
|
|
3120
|
+
this.model().isVisible.set(false);
|
|
2861
3121
|
this.draggableService.destroy(this.hostRef.nativeElement);
|
|
2862
3122
|
}
|
|
2863
3123
|
startConnection(event, handle) {
|
|
@@ -3098,7 +3358,7 @@ class BackgroundComponent {
|
|
|
3098
3358
|
const background = this.backgroundSignal();
|
|
3099
3359
|
return background.type === 'image' ? background.src : '';
|
|
3100
3360
|
});
|
|
3101
|
-
this.imageSize =
|
|
3361
|
+
this.imageSize = toLazySignal(toObservable(this.backgroundSignal).pipe(switchMap(() => createImage(this.bgImageSrc())), map((image) => ({ width: image.naturalWidth, height: image.naturalHeight }))), { initialValue: { width: 0, height: 0 } });
|
|
3102
3362
|
this.scaledImageWidth = computed(() => {
|
|
3103
3363
|
const background = this.backgroundSignal();
|
|
3104
3364
|
if (background.type === 'image') {
|
|
@@ -3272,6 +3532,181 @@ function getSpacePoints(point, groups) {
|
|
|
3272
3532
|
return result;
|
|
3273
3533
|
}
|
|
3274
3534
|
|
|
3535
|
+
class PreviewFlowRenderStrategyService {
|
|
3536
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowRenderStrategyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3537
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowRenderStrategyService }); }
|
|
3538
|
+
}
|
|
3539
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowRenderStrategyService, decorators: [{
|
|
3540
|
+
type: Injectable
|
|
3541
|
+
}] });
|
|
3542
|
+
class ViewportPreviewFlowRenderStrategyService extends PreviewFlowRenderStrategyService {
|
|
3543
|
+
shouldRenderNode(node) {
|
|
3544
|
+
// Do not render preview node if the real node is visible
|
|
3545
|
+
return !node.isVisible();
|
|
3546
|
+
}
|
|
3547
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewportPreviewFlowRenderStrategyService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3548
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewportPreviewFlowRenderStrategyService }); }
|
|
3549
|
+
}
|
|
3550
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewportPreviewFlowRenderStrategyService, decorators: [{
|
|
3551
|
+
type: Injectable
|
|
3552
|
+
}] });
|
|
3553
|
+
|
|
3554
|
+
function drawNode(ctx, node) {
|
|
3555
|
+
if (Object.keys(node.preview().style).length) {
|
|
3556
|
+
drawStyledNode(ctx, node);
|
|
3557
|
+
return;
|
|
3558
|
+
}
|
|
3559
|
+
if (node.rawNode.type === 'default') {
|
|
3560
|
+
drawDefaultNode(ctx, node);
|
|
3561
|
+
return;
|
|
3562
|
+
}
|
|
3563
|
+
if (node.rawNode.type === 'default-group') {
|
|
3564
|
+
drawDefaultGroupNode(ctx, node);
|
|
3565
|
+
return;
|
|
3566
|
+
}
|
|
3567
|
+
drawUnknownNode(ctx, node);
|
|
3568
|
+
}
|
|
3569
|
+
function drawDefaultNode(ctx, node) {
|
|
3570
|
+
const point = node.globalPoint();
|
|
3571
|
+
const width = node.width();
|
|
3572
|
+
const height = node.height();
|
|
3573
|
+
borderRadius(ctx, node, 5);
|
|
3574
|
+
// Draw background (background-color: white)
|
|
3575
|
+
ctx.fillStyle = 'white';
|
|
3576
|
+
ctx.fill();
|
|
3577
|
+
// Draw border (border: 1.5px solid #1b262c)
|
|
3578
|
+
ctx.strokeStyle = '#1b262c';
|
|
3579
|
+
ctx.lineWidth = 1.5;
|
|
3580
|
+
ctx.stroke();
|
|
3581
|
+
// Draw centered text (color: black, justify-content: center)
|
|
3582
|
+
ctx.fillStyle = 'black';
|
|
3583
|
+
// TODO: use as in default node
|
|
3584
|
+
ctx.font = '14px Arial';
|
|
3585
|
+
ctx.textAlign = 'center';
|
|
3586
|
+
ctx.textBaseline = 'middle';
|
|
3587
|
+
const centerX = point.x + width / 2;
|
|
3588
|
+
const centerY = point.y + height / 2;
|
|
3589
|
+
ctx.fillText(node.text(), centerX, centerY);
|
|
3590
|
+
}
|
|
3591
|
+
function drawDefaultGroupNode(ctx, node) {
|
|
3592
|
+
const point = node.globalPoint();
|
|
3593
|
+
const width = node.width();
|
|
3594
|
+
const height = node.height();
|
|
3595
|
+
ctx.globalAlpha = 0.05;
|
|
3596
|
+
ctx.fillStyle = node.color();
|
|
3597
|
+
ctx.fillRect(point.x, point.y, width, height);
|
|
3598
|
+
ctx.globalAlpha = 1;
|
|
3599
|
+
ctx.strokeStyle = node.color();
|
|
3600
|
+
ctx.lineWidth = 1.5;
|
|
3601
|
+
ctx.strokeRect(point.x, point.y, width, height);
|
|
3602
|
+
}
|
|
3603
|
+
function drawStyledNode(ctx, node) {
|
|
3604
|
+
const point = node.globalPoint();
|
|
3605
|
+
const width = node.width();
|
|
3606
|
+
const height = node.height();
|
|
3607
|
+
const style = node.preview().style;
|
|
3608
|
+
if (style.borderRadius) {
|
|
3609
|
+
const radius = parseFloat(style.borderRadius);
|
|
3610
|
+
borderRadius(ctx, node, radius);
|
|
3611
|
+
}
|
|
3612
|
+
else {
|
|
3613
|
+
ctx.beginPath();
|
|
3614
|
+
ctx.rect(point.x, point.y, width, height);
|
|
3615
|
+
ctx.closePath();
|
|
3616
|
+
}
|
|
3617
|
+
if (style.backgroundColor) {
|
|
3618
|
+
ctx.fillStyle = style.backgroundColor;
|
|
3619
|
+
}
|
|
3620
|
+
if (style.borderColor) {
|
|
3621
|
+
ctx.strokeStyle = style.borderColor;
|
|
3622
|
+
}
|
|
3623
|
+
if (style.borderWidth) {
|
|
3624
|
+
ctx.lineWidth = parseFloat(style.borderWidth);
|
|
3625
|
+
}
|
|
3626
|
+
ctx.fill();
|
|
3627
|
+
ctx.stroke();
|
|
3628
|
+
}
|
|
3629
|
+
function drawUnknownNode(ctx, node) {
|
|
3630
|
+
const point = node.globalPoint();
|
|
3631
|
+
const width = node.width();
|
|
3632
|
+
const height = node.height();
|
|
3633
|
+
ctx.fillStyle = 'rgb(0 0 0 / 10%)';
|
|
3634
|
+
ctx.fillRect(point.x, point.y, width, height);
|
|
3635
|
+
}
|
|
3636
|
+
function borderRadius(ctx, node, radius) {
|
|
3637
|
+
const point = node.globalPoint();
|
|
3638
|
+
const width = node.width();
|
|
3639
|
+
const height = node.height();
|
|
3640
|
+
// Create rounded rectangle path
|
|
3641
|
+
ctx.beginPath();
|
|
3642
|
+
ctx.moveTo(point.x + radius, point.y);
|
|
3643
|
+
ctx.lineTo(point.x + width - radius, point.y);
|
|
3644
|
+
ctx.quadraticCurveTo(point.x + width, point.y, point.x + width, point.y + radius);
|
|
3645
|
+
ctx.lineTo(point.x + width, point.y + height - radius);
|
|
3646
|
+
ctx.quadraticCurveTo(point.x + width, point.y + height, point.x + width - radius, point.y + height);
|
|
3647
|
+
ctx.lineTo(point.x + radius, point.y + height);
|
|
3648
|
+
ctx.quadraticCurveTo(point.x, point.y + height, point.x, point.y + height - radius);
|
|
3649
|
+
ctx.lineTo(point.x, point.y + radius);
|
|
3650
|
+
ctx.quadraticCurveTo(point.x, point.y, point.x + radius, point.y);
|
|
3651
|
+
ctx.closePath();
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
class PreviewFlowComponent {
|
|
3655
|
+
constructor() {
|
|
3656
|
+
this.viewportService = inject(ViewportService);
|
|
3657
|
+
this.renderStrategy = inject(PreviewFlowRenderStrategyService);
|
|
3658
|
+
this.nodeRenderingService = inject(NodeRenderingService);
|
|
3659
|
+
this.renderer2 = inject(Renderer2);
|
|
3660
|
+
this.element = inject(ElementRef).nativeElement;
|
|
3661
|
+
this.ctx = this.element.getContext('2d');
|
|
3662
|
+
this.width = input(0);
|
|
3663
|
+
this.height = input(0);
|
|
3664
|
+
this.dpr = window.devicePixelRatio;
|
|
3665
|
+
effect(() => {
|
|
3666
|
+
// Set the "actual" size of the canvas
|
|
3667
|
+
this.renderer2.setProperty(this.element, 'width', this.width() * this.dpr);
|
|
3668
|
+
this.renderer2.setProperty(this.element, 'height', this.height() * this.dpr);
|
|
3669
|
+
// Set the "drawn" size of the canvas
|
|
3670
|
+
this.renderer2.setStyle(this.element, 'width', `${this.width()}px`);
|
|
3671
|
+
this.renderer2.setStyle(this.element, 'height', `${this.height()}px`);
|
|
3672
|
+
// Scale the context to match device pixel ratio
|
|
3673
|
+
this.ctx.scale(this.dpr, this.dpr);
|
|
3674
|
+
});
|
|
3675
|
+
effect(() => {
|
|
3676
|
+
const viewport = this.viewportService.readableViewport();
|
|
3677
|
+
this.ctx.clearRect(0, 0, this.width(), this.height());
|
|
3678
|
+
// Save the current context state
|
|
3679
|
+
this.ctx.save();
|
|
3680
|
+
// Apply viewport transformations (zoom and pan)
|
|
3681
|
+
this.ctx.setTransform(viewport.zoom * this.dpr, // horizontal scaling with DPR
|
|
3682
|
+
0, // horizontal skewing
|
|
3683
|
+
0, // vertical skewing
|
|
3684
|
+
viewport.zoom * this.dpr, // vertical scaling with DPR
|
|
3685
|
+
viewport.x * this.dpr, // horizontal translation with DPR
|
|
3686
|
+
viewport.y * this.dpr);
|
|
3687
|
+
for (let i = 0; i < this.nodeRenderingService.viewportNodes().length; i++) {
|
|
3688
|
+
const node = this.nodeRenderingService.viewportNodes()[i];
|
|
3689
|
+
if (this.renderStrategy.shouldRenderNode(node)) {
|
|
3690
|
+
drawNode(this.ctx, node);
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
// Restore the context state
|
|
3694
|
+
this.ctx.restore();
|
|
3695
|
+
});
|
|
3696
|
+
}
|
|
3697
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3698
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "17.3.12", type: PreviewFlowComponent, isStandalone: true, selector: "canvas[previewFlow]", inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3699
|
+
}
|
|
3700
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowComponent, decorators: [{
|
|
3701
|
+
type: Component,
|
|
3702
|
+
args: [{
|
|
3703
|
+
standalone: true,
|
|
3704
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3705
|
+
selector: 'canvas[previewFlow]',
|
|
3706
|
+
template: '',
|
|
3707
|
+
}]
|
|
3708
|
+
}], ctorParameters: () => [] });
|
|
3709
|
+
|
|
3275
3710
|
const changesControllerHostDirective = {
|
|
3276
3711
|
directive: ChangesControllerDirective,
|
|
3277
3712
|
outputs: [
|
|
@@ -3319,9 +3754,6 @@ class VflowComponent {
|
|
|
3319
3754
|
this.componentEventBusService = inject(ComponentEventBusService);
|
|
3320
3755
|
this.keyboardService = inject(KeyboardService);
|
|
3321
3756
|
this.injector = inject(Injector);
|
|
3322
|
-
this.optimization = input({
|
|
3323
|
-
detachedGroupsLayer: false,
|
|
3324
|
-
});
|
|
3325
3757
|
this.nodeModels = this.nodeRenderingService.nodes;
|
|
3326
3758
|
this.groups = this.nodeRenderingService.groups;
|
|
3327
3759
|
this.nonGroups = this.nodeRenderingService.nonGroups;
|
|
@@ -3355,13 +3787,13 @@ class VflowComponent {
|
|
|
3355
3787
|
/**
|
|
3356
3788
|
* Signal for reading nodes change
|
|
3357
3789
|
*/
|
|
3358
|
-
this.nodesChange =
|
|
3790
|
+
this.nodesChange = toLazySignal(this.nodesChangeService.changes$, {
|
|
3359
3791
|
initialValue: [],
|
|
3360
3792
|
});
|
|
3361
3793
|
/**
|
|
3362
3794
|
* Signal to reading edges change
|
|
3363
3795
|
*/
|
|
3364
|
-
this.edgesChange =
|
|
3796
|
+
this.edgesChange = toLazySignal(this.edgesChangeService.changes$, {
|
|
3365
3797
|
initialValue: [],
|
|
3366
3798
|
});
|
|
3367
3799
|
// #endregion
|
|
@@ -3381,6 +3813,9 @@ class VflowComponent {
|
|
|
3381
3813
|
// #endregion
|
|
3382
3814
|
this.markers = this.flowEntitiesService.markers;
|
|
3383
3815
|
this.minimap = this.flowEntitiesService.minimap;
|
|
3816
|
+
this.flowOptimization = this.flowSettingsService.optimization;
|
|
3817
|
+
this.flowWidth = this.flowSettingsService.computedFlowWidth;
|
|
3818
|
+
this.flowHeight = this.flowSettingsService.computedFlowHeight;
|
|
3384
3819
|
}
|
|
3385
3820
|
// #endregion
|
|
3386
3821
|
// #region SETTINGS
|
|
@@ -3412,6 +3847,12 @@ class VflowComponent {
|
|
|
3412
3847
|
set background(value) {
|
|
3413
3848
|
this.flowSettingsService.background.set(transformBackground(value));
|
|
3414
3849
|
}
|
|
3850
|
+
set optimization(newOptimization) {
|
|
3851
|
+
this.flowSettingsService.optimization.update((optimization) => ({
|
|
3852
|
+
...optimization,
|
|
3853
|
+
...newOptimization,
|
|
3854
|
+
}));
|
|
3855
|
+
}
|
|
3415
3856
|
/**
|
|
3416
3857
|
* Global rule if you can or can't select entities
|
|
3417
3858
|
*/
|
|
@@ -3469,7 +3910,7 @@ class VflowComponent {
|
|
|
3469
3910
|
set edges(newEdges) {
|
|
3470
3911
|
const newModels = runInInjectionContext(this.injector, () => ReferenceIdentityChecker.edges(newEdges, this.flowEntitiesService.edges()));
|
|
3471
3912
|
// quick and dirty binding nodes to edges
|
|
3472
|
-
addNodesToEdges(this.
|
|
3913
|
+
addNodesToEdges(this.flowEntitiesService.nodes(), newModels);
|
|
3473
3914
|
this.flowEntitiesService.edges.set(newModels);
|
|
3474
3915
|
}
|
|
3475
3916
|
// #region METHODS_API
|
|
@@ -3572,7 +4013,7 @@ class VflowComponent {
|
|
|
3572
4013
|
return edge;
|
|
3573
4014
|
}
|
|
3574
4015
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3575
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: VflowComponent, isStandalone: true, selector: "vflow", inputs: { view:
|
|
4016
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: VflowComponent, isStandalone: true, selector: "vflow", inputs: { view: "view", minZoom: "minZoom", maxZoom: "maxZoom", background: "background", optimization: "optimization", entitiesSelectable: "entitiesSelectable", keyboardShortcuts: "keyboardShortcuts", connection: ["connection", "connection", (settings) => new ConnectionModel(settings)], snapGrid: "snapGrid", elevateNodesOnSelect: "elevateNodesOnSelect", elevateEdgesOnSelect: "elevateEdgesOnSelect", nodes: "nodes", edges: "edges" }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, providers: [
|
|
3576
4017
|
DraggableService,
|
|
3577
4018
|
ViewportService,
|
|
3578
4019
|
FlowStatusService,
|
|
@@ -3586,7 +4027,8 @@ class VflowComponent {
|
|
|
3586
4027
|
ComponentEventBusService,
|
|
3587
4028
|
KeyboardService,
|
|
3588
4029
|
OverlaysService,
|
|
3589
|
-
|
|
4030
|
+
{ provide: PreviewFlowRenderStrategyService, useClass: ViewportPreviewFlowRenderStrategyService },
|
|
4031
|
+
], queries: [{ propertyName: "nodeTemplateDirective", first: true, predicate: NodeHtmlTemplateDirective, descendants: true, isSignal: true }, { propertyName: "nodeSvgTemplateDirective", first: true, predicate: NodeSvgTemplateDirective, descendants: true, isSignal: true }, { propertyName: "groupNodeTemplateDirective", first: true, predicate: GroupNodeTemplateDirective, descendants: true, isSignal: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateDirective, descendants: true, isSignal: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateDirective, descendants: true, isSignal: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "mapContext", first: true, predicate: MapContextDirective, descendants: true, isSignal: true }, { propertyName: "spacePointContext", first: true, predicate: SpacePointContextDirective, descendants: true, isSignal: true }], hostDirectives: [{ 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 #flow rootSvgRef rootSvgContext rootPointer flowSizeController class=\"root-svg\">\n <defs flowDefs [markers]=\"markers()\" />\n\n <g background />\n\n <svg:g mapContext spacePointContext>\n <!-- Connection -->\n <svg:g connection [model]=\"connection\" [template]=\"connectionTemplateDirective()?.templateRef\" />\n\n @if (flowOptimization().detachedGroupsLayer) {\n <!-- Groups -->\n @for (model of groups(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n <!-- Nodes -->\n @for (model of nonGroups(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [nodeSvgTemplate]=\"nodeSvgTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n\n @if (!flowOptimization().detachedGroupsLayer) {\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n\n @for (model of nodeModels(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [nodeSvgTemplate]=\"nodeSvgTemplateDirective()?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n </svg:g>\n\n <!-- Minimap -->\n @if (minimap(); as minimap) {\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\n }\n</svg:svg>\n\n@if (flowOptimization().virtualization) {\n <canvas previewFlow class=\"preview-flow\" [width]=\"flowWidth()\" [height]=\"flowHeight()\"></canvas>\n}\n", styles: [":host{display:grid;grid-template-columns:1fr;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}.root-svg{grid-row-start:1;grid-column-start:1}.preview-flow{pointer-events:none;grid-row-start:1;grid-column-start:1}\n"], dependencies: [{ 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]" }, { kind: "component", type: DefsComponent, selector: "defs[flowDefs]", inputs: ["markers"] }, { kind: "component", type: BackgroundComponent, selector: "g[background]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]" }, { kind: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "component", type: ConnectionComponent, selector: "g[connection]", inputs: ["model", "template"] }, { kind: "component", type: NodeComponent, selector: "g[node]", inputs: ["model", "nodeTemplate", "nodeSvgTemplate", "groupNodeTemplate"] }, { kind: "component", type: EdgeComponent, selector: "g[edge]", inputs: ["model", "edgeTemplate", "edgeLabelHtmlTemplate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: PreviewFlowComponent, selector: "canvas[previewFlow]", inputs: ["width", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3590
4032
|
}
|
|
3591
4033
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, decorators: [{
|
|
3592
4034
|
type: Component,
|
|
@@ -3604,6 +4046,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3604
4046
|
ComponentEventBusService,
|
|
3605
4047
|
KeyboardService,
|
|
3606
4048
|
OverlaysService,
|
|
4049
|
+
{ provide: PreviewFlowRenderStrategyService, useClass: ViewportPreviewFlowRenderStrategyService },
|
|
3607
4050
|
], hostDirectives: [changesControllerHostDirective], imports: [
|
|
3608
4051
|
RootSvgReferenceDirective,
|
|
3609
4052
|
RootSvgContextDirective,
|
|
@@ -3617,7 +4060,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3617
4060
|
NodeComponent,
|
|
3618
4061
|
EdgeComponent,
|
|
3619
4062
|
NgTemplateOutlet,
|
|
3620
|
-
|
|
4063
|
+
PreviewFlowComponent,
|
|
4064
|
+
], template: "<svg:svg #flow rootSvgRef rootSvgContext rootPointer flowSizeController class=\"root-svg\">\n <defs flowDefs [markers]=\"markers()\" />\n\n <g background />\n\n <svg:g mapContext spacePointContext>\n <!-- Connection -->\n <svg:g connection [model]=\"connection\" [template]=\"connectionTemplateDirective()?.templateRef\" />\n\n @if (flowOptimization().detachedGroupsLayer) {\n <!-- Groups -->\n @for (model of groups(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n <!-- Nodes -->\n @for (model of nonGroups(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [nodeSvgTemplate]=\"nodeSvgTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n\n @if (!flowOptimization().detachedGroupsLayer) {\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n\n @for (model of nodeModels(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [nodeSvgTemplate]=\"nodeSvgTemplateDirective()?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n </svg:g>\n\n <!-- Minimap -->\n @if (minimap(); as minimap) {\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\n }\n</svg:svg>\n\n@if (flowOptimization().virtualization) {\n <canvas previewFlow class=\"preview-flow\" [width]=\"flowWidth()\" [height]=\"flowHeight()\"></canvas>\n}\n", styles: [":host{display:grid;grid-template-columns:1fr;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}.root-svg{grid-row-start:1;grid-column-start:1}.preview-flow{pointer-events:none;grid-row-start:1;grid-column-start:1}\n"] }]
|
|
3621
4065
|
}], propDecorators: { view: [{
|
|
3622
4066
|
type: Input
|
|
3623
4067
|
}], minZoom: [{
|
|
@@ -3626,6 +4070,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3626
4070
|
type: Input
|
|
3627
4071
|
}], background: [{
|
|
3628
4072
|
type: Input
|
|
4073
|
+
}], optimization: [{
|
|
4074
|
+
type: Input
|
|
3629
4075
|
}], entitiesSelectable: [{
|
|
3630
4076
|
type: Input
|
|
3631
4077
|
}], keyboardShortcuts: [{
|
|
@@ -3966,5 +4412,5 @@ const Vflow = [
|
|
|
3966
4412
|
* Generated bundle index. Do not edit.
|
|
3967
4413
|
*/
|
|
3968
4414
|
|
|
3969
|
-
export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, CustomDynamicNodeComponent, CustomNodeComponent, CustomTemplateEdgeComponent, DragHandleDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, GroupNodeTemplateDirective, HandleComponent, HandleTemplateDirective, MiniMapComponent, NodeHtmlTemplateDirective, NodeSvgTemplateDirective, NodeToolbarComponent, NodeToolbarWrapperDirective, ResizableComponent, SelectableDirective, Vflow, VflowComponent, isComponentDynamicNode, isComponentStaticNode, isDefaultDynamicGroupNode, isDefaultDynamicNode, isDefaultStaticGroupNode, isDefaultStaticNode, isDynamicNode, isStaticNode, isSvgTemplateDynamicNode, isSvgTemplateStaticNode, isTemplateDynamicGroupNode, isTemplateDynamicNode, isTemplateStaticGroupNode, isTemplateStaticNode, ComponentEventBusService as ɵComponentEventBusService, ConnectionModel as ɵConnectionModel, FlowEntitiesService as ɵFlowEntitiesService, FlowSettingsService as ɵFlowSettingsService, HandleModel as ɵHandleModel, HandleService as ɵHandleService, NodeAccessorService as ɵNodeAccessorService, NodeModel as ɵNodeModel, RootPointerDirective as ɵRootPointerDirective, SelectionService as ɵSelectionService, SpacePointContextDirective as ɵSpacePointContextDirective, ViewportService as ɵViewportService };
|
|
4415
|
+
export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, CustomDynamicNodeComponent, CustomNodeComponent, CustomTemplateEdgeComponent, DEFAULT_OPTIMIZATION, DragHandleDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, GroupNodeTemplateDirective, HandleComponent, HandleTemplateDirective, MiniMapComponent, NodeHtmlTemplateDirective, NodeSvgTemplateDirective, NodeToolbarComponent, NodeToolbarWrapperDirective, ResizableComponent, SelectableDirective, Vflow, VflowComponent, isComponentDynamicNode, isComponentStaticNode, isDefaultDynamicGroupNode, isDefaultDynamicNode, isDefaultStaticGroupNode, isDefaultStaticNode, isDynamicNode, isStaticNode, isSvgTemplateDynamicNode, isSvgTemplateStaticNode, isTemplateDynamicGroupNode, isTemplateDynamicNode, isTemplateStaticGroupNode, isTemplateStaticNode, ComponentEventBusService as ɵComponentEventBusService, ConnectionModel as ɵConnectionModel, FlowEntitiesService as ɵFlowEntitiesService, FlowSettingsService as ɵFlowSettingsService, HandleModel as ɵHandleModel, HandleService as ɵHandleService, NodeAccessorService as ɵNodeAccessorService, NodeModel as ɵNodeModel, RootPointerDirective as ɵRootPointerDirective, SelectionService as ɵSelectionService, SpacePointContextDirective as ɵSpacePointContextDirective, ViewportService as ɵViewportService };
|
|
3970
4416
|
//# sourceMappingURL=ngx-vflow.mjs.map
|