ngx-vflow 1.11.0 → 1.12.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 +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/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/nodes.mjs +3 -3
- 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 +438 -48
- 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';
|
|
@@ -50,8 +50,8 @@ function nodeToBox(node) {
|
|
|
50
50
|
}
|
|
51
51
|
function nodeToRect(node) {
|
|
52
52
|
return {
|
|
53
|
-
x: node.
|
|
54
|
-
y: node.
|
|
53
|
+
x: node.globalPoint().x,
|
|
54
|
+
y: node.globalPoint().y,
|
|
55
55
|
width: node.width(),
|
|
56
56
|
height: node.height(),
|
|
57
57
|
};
|
|
@@ -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
|
}
|
|
@@ -1345,6 +1409,20 @@ function smoothStepPath({ sourcePoint, targetPoint, sourcePosition, targetPositi
|
|
|
1345
1409
|
};
|
|
1346
1410
|
}
|
|
1347
1411
|
|
|
1412
|
+
// MIT License
|
|
1413
|
+
/**
|
|
1414
|
+
* @todo Use `linkedSignal` after Angular update
|
|
1415
|
+
*/
|
|
1416
|
+
function extendedComputed(computedCallback, options) {
|
|
1417
|
+
if (!options) {
|
|
1418
|
+
options = { equal: Object.is };
|
|
1419
|
+
}
|
|
1420
|
+
let currentValue = undefined;
|
|
1421
|
+
return computed(() => {
|
|
1422
|
+
return (currentValue = computedCallback(currentValue));
|
|
1423
|
+
}, options);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1348
1426
|
class EdgeModel {
|
|
1349
1427
|
constructor(edge) {
|
|
1350
1428
|
this.edge = edge;
|
|
@@ -1405,25 +1483,51 @@ class EdgeModel {
|
|
|
1405
1483
|
return this.curve(params);
|
|
1406
1484
|
}
|
|
1407
1485
|
});
|
|
1408
|
-
this.sourceHandle =
|
|
1486
|
+
this.sourceHandle = extendedComputed((previousHandle) => {
|
|
1487
|
+
let handle = null;
|
|
1409
1488
|
if (this.edge.sourceHandle) {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1489
|
+
handle =
|
|
1490
|
+
this.source()
|
|
1491
|
+
?.handles()
|
|
1492
|
+
.find((handle) => handle.rawHandle.id === this.edge.sourceHandle) ?? null;
|
|
1413
1493
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1494
|
+
else {
|
|
1495
|
+
handle =
|
|
1496
|
+
this.source()
|
|
1497
|
+
?.handles()
|
|
1498
|
+
.find((handle) => handle.rawHandle.type === 'source') ?? null;
|
|
1499
|
+
}
|
|
1500
|
+
// In case of virtual scrolling, if the node is scrolled out of view the handle may disappear
|
|
1501
|
+
// which could lead to the edge not being rendered
|
|
1502
|
+
// so we return the previous handle if the current one is null
|
|
1503
|
+
// TODO: check if this breaks anything
|
|
1504
|
+
if (handle === null) {
|
|
1505
|
+
return previousHandle;
|
|
1506
|
+
}
|
|
1507
|
+
return handle;
|
|
1417
1508
|
});
|
|
1418
|
-
this.targetHandle =
|
|
1509
|
+
this.targetHandle = extendedComputed((previousHandle) => {
|
|
1510
|
+
let handle = null;
|
|
1419
1511
|
if (this.edge.targetHandle) {
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1512
|
+
handle =
|
|
1513
|
+
this.target()
|
|
1514
|
+
?.handles()
|
|
1515
|
+
.find((handle) => handle.rawHandle.id === this.edge.targetHandle) ?? null;
|
|
1423
1516
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1517
|
+
else {
|
|
1518
|
+
handle =
|
|
1519
|
+
this.target()
|
|
1520
|
+
?.handles()
|
|
1521
|
+
.find((handle) => handle.rawHandle.type === 'target') ?? null;
|
|
1522
|
+
}
|
|
1523
|
+
// In case of virtual scrolling, if the node is scrolled out of view the handle may disappear
|
|
1524
|
+
// which could lead to the edge not being rendered
|
|
1525
|
+
// so we return the previous handle if the current one is null
|
|
1526
|
+
// TODO: check if this breaks anything
|
|
1527
|
+
if (handle === null) {
|
|
1528
|
+
return previousHandle;
|
|
1529
|
+
}
|
|
1530
|
+
return handle;
|
|
1427
1531
|
});
|
|
1428
1532
|
/**
|
|
1429
1533
|
* TODO: not reactive
|
|
@@ -1737,11 +1841,74 @@ function isGroupNode(node) {
|
|
|
1737
1841
|
return node.rawNode.type === 'default-group' || node.rawNode.type === 'template-group';
|
|
1738
1842
|
}
|
|
1739
1843
|
|
|
1844
|
+
// MIT License
|
|
1845
|
+
// Copyright (c) 2023 Chau Tran
|
|
1846
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1847
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
1848
|
+
// in the Software without restriction, including without limitation the rights
|
|
1849
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
1850
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
1851
|
+
// furnished to do so, subject to the following conditions:
|
|
1852
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
1853
|
+
// copies or substantial portions of the Software.
|
|
1854
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
1855
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1856
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
1857
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
1858
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
1859
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1860
|
+
// SOFTWARE.
|
|
1861
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
1862
|
+
function assertInjector(fn, injector, runner) {
|
|
1863
|
+
!injector && assertInInjectionContext(fn);
|
|
1864
|
+
const assertedInjector = injector ?? inject(Injector);
|
|
1865
|
+
if (!runner)
|
|
1866
|
+
return assertedInjector;
|
|
1867
|
+
return runInInjectionContext(assertedInjector, runner);
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
// MIT License
|
|
1871
|
+
// Copyright (c) 2023 Chau Tran
|
|
1872
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1873
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
1874
|
+
// in the Software without restriction, including without limitation the rights
|
|
1875
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
1876
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
1877
|
+
// furnished to do so, subject to the following conditions:
|
|
1878
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
1879
|
+
// copies or substantial portions of the Software.
|
|
1880
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
1881
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1882
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
1883
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
1884
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
1885
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1886
|
+
// SOFTWARE.
|
|
1887
|
+
/**
|
|
1888
|
+
* Function `toLazySignal()` is a proxy function that will call the original
|
|
1889
|
+
* `toSignal()` function when the returned signal is read for the first time.
|
|
1890
|
+
*/
|
|
1891
|
+
function toLazySignal(source, options) {
|
|
1892
|
+
const injector = assertInjector(toLazySignal, options?.injector);
|
|
1893
|
+
let s;
|
|
1894
|
+
return computed(() => {
|
|
1895
|
+
if (!s) {
|
|
1896
|
+
s = untracked(() => toSignal(source, { ...options, injector }));
|
|
1897
|
+
}
|
|
1898
|
+
return s();
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1740
1902
|
class NodeRenderingService {
|
|
1741
1903
|
constructor() {
|
|
1742
1904
|
this.flowEntitiesService = inject(FlowEntitiesService);
|
|
1905
|
+
this.flowSettingsService = inject(FlowSettingsService);
|
|
1906
|
+
this.viewportService = inject(ViewportService);
|
|
1743
1907
|
this.nodes = computed(() => {
|
|
1744
|
-
|
|
1908
|
+
if (!this.flowSettingsService.optimization().virtualization) {
|
|
1909
|
+
return [...this.flowEntitiesService.nodes()].sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
|
|
1910
|
+
}
|
|
1911
|
+
return this.viewportNodesAfterInteraction().sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
|
|
1745
1912
|
});
|
|
1746
1913
|
this.groups = computed(() => {
|
|
1747
1914
|
return this.nodes().filter((n) => isGroupNode(n));
|
|
@@ -1749,6 +1916,28 @@ class NodeRenderingService {
|
|
|
1749
1916
|
this.nonGroups = computed(() => {
|
|
1750
1917
|
return this.nodes().filter((n) => !isGroupNode(n));
|
|
1751
1918
|
});
|
|
1919
|
+
this.viewportNodes = computed(() => {
|
|
1920
|
+
const nodes = this.flowEntitiesService.nodes();
|
|
1921
|
+
const viewport = this.viewportService.readableViewport();
|
|
1922
|
+
const flowWidth = this.flowSettingsService.computedFlowWidth();
|
|
1923
|
+
const flowHeight = this.flowSettingsService.computedFlowHeight();
|
|
1924
|
+
return nodes.filter((n) => {
|
|
1925
|
+
const { x, y } = n.globalPoint();
|
|
1926
|
+
const width = n.width();
|
|
1927
|
+
const height = n.height();
|
|
1928
|
+
return isRectInViewport({ x, y, width, height }, viewport, flowWidth, flowHeight);
|
|
1929
|
+
});
|
|
1930
|
+
});
|
|
1931
|
+
this.viewportNodesAfterInteraction = toLazySignal(merge(
|
|
1932
|
+
// TODO: maybe there is a better way wait when viewport is ready?
|
|
1933
|
+
// (to correctly calculate viewport nodes on first render)
|
|
1934
|
+
toObservable(this.flowEntitiesService.nodes).pipe(observeOn(asyncScheduler), filter((nodes) => !!nodes.length)), this.viewportService.viewportChangeEnd$.pipe(debounceTime(300))).pipe(map(() => {
|
|
1935
|
+
const viewport = this.viewportService.readableViewport();
|
|
1936
|
+
const zoomThreshold = this.flowSettingsService.optimization().virtualizationZoomThreshold;
|
|
1937
|
+
return viewport.zoom < zoomThreshold ? [] : this.viewportNodes();
|
|
1938
|
+
})), {
|
|
1939
|
+
initialValue: [],
|
|
1940
|
+
});
|
|
1752
1941
|
this.maxOrder = computed(() => {
|
|
1753
1942
|
return Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
|
|
1754
1943
|
});
|
|
@@ -1839,7 +2028,7 @@ class SpacePointContextDirective {
|
|
|
1839
2028
|
y: movement.y,
|
|
1840
2029
|
});
|
|
1841
2030
|
});
|
|
1842
|
-
this.pointerMovement =
|
|
2031
|
+
this.pointerMovement = toLazySignal(this.pointerMovementDirective.pointerMovement$);
|
|
1843
2032
|
}
|
|
1844
2033
|
documentPointToFlowPoint(documentPoint) {
|
|
1845
2034
|
const point = this.rootSvg.createSVGPoint();
|
|
@@ -2144,8 +2333,19 @@ function statusToConnection(status, isStrictMode) {
|
|
|
2144
2333
|
class EdgeRenderingService {
|
|
2145
2334
|
constructor() {
|
|
2146
2335
|
this.flowEntitiesService = inject(FlowEntitiesService);
|
|
2336
|
+
this.flowSettingsService = inject(FlowSettingsService);
|
|
2147
2337
|
this.edges = computed(() => {
|
|
2148
|
-
|
|
2338
|
+
if (!this.flowSettingsService.optimization().virtualization) {
|
|
2339
|
+
return [...this.flowEntitiesService.validEdges()].sort((aEdge, bEdge) => aEdge.renderOrder() - bEdge.renderOrder());
|
|
2340
|
+
}
|
|
2341
|
+
return this.viewportEdges().sort((aEdge, bEdge) => aEdge.renderOrder() - bEdge.renderOrder());
|
|
2342
|
+
});
|
|
2343
|
+
this.viewportEdges = computed(() => {
|
|
2344
|
+
return this.flowEntitiesService.validEdges().filter((e) => {
|
|
2345
|
+
const sourceHandle = e.sourceHandle();
|
|
2346
|
+
const targetHandle = e.targetHandle();
|
|
2347
|
+
return sourceHandle && targetHandle;
|
|
2348
|
+
});
|
|
2149
2349
|
});
|
|
2150
2350
|
this.maxOrder = computed(() => {
|
|
2151
2351
|
return Math.max(...this.flowEntitiesService.validEdges().map((n) => n.renderOrder()));
|
|
@@ -2645,9 +2845,11 @@ class HandleModel {
|
|
|
2645
2845
|
});
|
|
2646
2846
|
this.state = signal('idle');
|
|
2647
2847
|
this.updateHostSizeAndPosition$ = new Subject();
|
|
2848
|
+
// TODO: for some reason toLazySignal breaks unit tests, so we use toSignal here
|
|
2648
2849
|
this.hostSize = toSignal(this.updateHostSizeAndPosition$.pipe(map(() => this.getHostSize())), {
|
|
2649
2850
|
initialValue: { width: 0, height: 0 },
|
|
2650
2851
|
});
|
|
2852
|
+
// TODO: for some reason toLazySignal breaks unit tests, so we use toSignal here
|
|
2651
2853
|
this.hostPosition = toSignal(this.updateHostSizeAndPosition$.pipe(map(() => ({
|
|
2652
2854
|
x: this.hostReference instanceof HTMLElement ? this.hostReference.offsetLeft : 0, // for now just 0 for group nodes
|
|
2653
2855
|
y: this.hostReference instanceof HTMLElement ? this.hostReference.offsetTop : 0, // for now just 0 for group nodes
|
|
@@ -2846,6 +3048,7 @@ class NodeComponent {
|
|
|
2846
3048
|
this.toolbars = computed(() => this.overlaysService.nodeToolbarsMap().get(this.model()));
|
|
2847
3049
|
}
|
|
2848
3050
|
ngOnInit() {
|
|
3051
|
+
this.model().isVisible.set(true);
|
|
2849
3052
|
this.nodeAccessor.model.set(this.model());
|
|
2850
3053
|
this.handleService.node.set(this.model());
|
|
2851
3054
|
effect(() => {
|
|
@@ -2858,6 +3061,7 @@ class NodeComponent {
|
|
|
2858
3061
|
}, { injector: this.injector });
|
|
2859
3062
|
}
|
|
2860
3063
|
ngOnDestroy() {
|
|
3064
|
+
this.model().isVisible.set(false);
|
|
2861
3065
|
this.draggableService.destroy(this.hostRef.nativeElement);
|
|
2862
3066
|
}
|
|
2863
3067
|
startConnection(event, handle) {
|
|
@@ -3098,7 +3302,7 @@ class BackgroundComponent {
|
|
|
3098
3302
|
const background = this.backgroundSignal();
|
|
3099
3303
|
return background.type === 'image' ? background.src : '';
|
|
3100
3304
|
});
|
|
3101
|
-
this.imageSize =
|
|
3305
|
+
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
3306
|
this.scaledImageWidth = computed(() => {
|
|
3103
3307
|
const background = this.backgroundSignal();
|
|
3104
3308
|
if (background.type === 'image') {
|
|
@@ -3272,6 +3476,181 @@ function getSpacePoints(point, groups) {
|
|
|
3272
3476
|
return result;
|
|
3273
3477
|
}
|
|
3274
3478
|
|
|
3479
|
+
class PreviewFlowRenderStrategyService {
|
|
3480
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowRenderStrategyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3481
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowRenderStrategyService }); }
|
|
3482
|
+
}
|
|
3483
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowRenderStrategyService, decorators: [{
|
|
3484
|
+
type: Injectable
|
|
3485
|
+
}] });
|
|
3486
|
+
class ViewportPreviewFlowRenderStrategyService extends PreviewFlowRenderStrategyService {
|
|
3487
|
+
shouldRenderNode(node) {
|
|
3488
|
+
// Do not render preview node if the real node is visible
|
|
3489
|
+
return !node.isVisible();
|
|
3490
|
+
}
|
|
3491
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewportPreviewFlowRenderStrategyService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3492
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewportPreviewFlowRenderStrategyService }); }
|
|
3493
|
+
}
|
|
3494
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewportPreviewFlowRenderStrategyService, decorators: [{
|
|
3495
|
+
type: Injectable
|
|
3496
|
+
}] });
|
|
3497
|
+
|
|
3498
|
+
function drawNode(ctx, node) {
|
|
3499
|
+
if (Object.keys(node.preview().style).length) {
|
|
3500
|
+
drawStyledNode(ctx, node);
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
if (node.rawNode.type === 'default') {
|
|
3504
|
+
drawDefaultNode(ctx, node);
|
|
3505
|
+
return;
|
|
3506
|
+
}
|
|
3507
|
+
if (node.rawNode.type === 'default-group') {
|
|
3508
|
+
drawDefaultGroupNode(ctx, node);
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3511
|
+
drawUnknownNode(ctx, node);
|
|
3512
|
+
}
|
|
3513
|
+
function drawDefaultNode(ctx, node) {
|
|
3514
|
+
const point = node.globalPoint();
|
|
3515
|
+
const width = node.width();
|
|
3516
|
+
const height = node.height();
|
|
3517
|
+
borderRadius(ctx, node, 5);
|
|
3518
|
+
// Draw background (background-color: white)
|
|
3519
|
+
ctx.fillStyle = 'white';
|
|
3520
|
+
ctx.fill();
|
|
3521
|
+
// Draw border (border: 1.5px solid #1b262c)
|
|
3522
|
+
ctx.strokeStyle = '#1b262c';
|
|
3523
|
+
ctx.lineWidth = 1.5;
|
|
3524
|
+
ctx.stroke();
|
|
3525
|
+
// Draw centered text (color: black, justify-content: center)
|
|
3526
|
+
ctx.fillStyle = 'black';
|
|
3527
|
+
// TODO: use as in default node
|
|
3528
|
+
ctx.font = '14px Arial';
|
|
3529
|
+
ctx.textAlign = 'center';
|
|
3530
|
+
ctx.textBaseline = 'middle';
|
|
3531
|
+
const centerX = point.x + width / 2;
|
|
3532
|
+
const centerY = point.y + height / 2;
|
|
3533
|
+
ctx.fillText(node.text(), centerX, centerY);
|
|
3534
|
+
}
|
|
3535
|
+
function drawDefaultGroupNode(ctx, node) {
|
|
3536
|
+
const point = node.globalPoint();
|
|
3537
|
+
const width = node.width();
|
|
3538
|
+
const height = node.height();
|
|
3539
|
+
ctx.globalAlpha = 0.05;
|
|
3540
|
+
ctx.fillStyle = node.color();
|
|
3541
|
+
ctx.fillRect(point.x, point.y, width, height);
|
|
3542
|
+
ctx.globalAlpha = 1;
|
|
3543
|
+
ctx.strokeStyle = node.color();
|
|
3544
|
+
ctx.lineWidth = 1.5;
|
|
3545
|
+
ctx.strokeRect(point.x, point.y, width, height);
|
|
3546
|
+
}
|
|
3547
|
+
function drawStyledNode(ctx, node) {
|
|
3548
|
+
const point = node.globalPoint();
|
|
3549
|
+
const width = node.width();
|
|
3550
|
+
const height = node.height();
|
|
3551
|
+
const style = node.preview().style;
|
|
3552
|
+
if (style.borderRadius) {
|
|
3553
|
+
const radius = parseFloat(style.borderRadius);
|
|
3554
|
+
borderRadius(ctx, node, radius);
|
|
3555
|
+
}
|
|
3556
|
+
else {
|
|
3557
|
+
ctx.beginPath();
|
|
3558
|
+
ctx.rect(point.x, point.y, width, height);
|
|
3559
|
+
ctx.closePath();
|
|
3560
|
+
}
|
|
3561
|
+
if (style.backgroundColor) {
|
|
3562
|
+
ctx.fillStyle = style.backgroundColor;
|
|
3563
|
+
}
|
|
3564
|
+
if (style.borderColor) {
|
|
3565
|
+
ctx.strokeStyle = style.borderColor;
|
|
3566
|
+
}
|
|
3567
|
+
if (style.borderWidth) {
|
|
3568
|
+
ctx.lineWidth = parseFloat(style.borderWidth);
|
|
3569
|
+
}
|
|
3570
|
+
ctx.fill();
|
|
3571
|
+
ctx.stroke();
|
|
3572
|
+
}
|
|
3573
|
+
function drawUnknownNode(ctx, node) {
|
|
3574
|
+
const point = node.globalPoint();
|
|
3575
|
+
const width = node.width();
|
|
3576
|
+
const height = node.height();
|
|
3577
|
+
ctx.fillStyle = 'rgb(0 0 0 / 10%)';
|
|
3578
|
+
ctx.fillRect(point.x, point.y, width, height);
|
|
3579
|
+
}
|
|
3580
|
+
function borderRadius(ctx, node, radius) {
|
|
3581
|
+
const point = node.globalPoint();
|
|
3582
|
+
const width = node.width();
|
|
3583
|
+
const height = node.height();
|
|
3584
|
+
// Create rounded rectangle path
|
|
3585
|
+
ctx.beginPath();
|
|
3586
|
+
ctx.moveTo(point.x + radius, point.y);
|
|
3587
|
+
ctx.lineTo(point.x + width - radius, point.y);
|
|
3588
|
+
ctx.quadraticCurveTo(point.x + width, point.y, point.x + width, point.y + radius);
|
|
3589
|
+
ctx.lineTo(point.x + width, point.y + height - radius);
|
|
3590
|
+
ctx.quadraticCurveTo(point.x + width, point.y + height, point.x + width - radius, point.y + height);
|
|
3591
|
+
ctx.lineTo(point.x + radius, point.y + height);
|
|
3592
|
+
ctx.quadraticCurveTo(point.x, point.y + height, point.x, point.y + height - radius);
|
|
3593
|
+
ctx.lineTo(point.x, point.y + radius);
|
|
3594
|
+
ctx.quadraticCurveTo(point.x, point.y, point.x + radius, point.y);
|
|
3595
|
+
ctx.closePath();
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
class PreviewFlowComponent {
|
|
3599
|
+
constructor() {
|
|
3600
|
+
this.viewportService = inject(ViewportService);
|
|
3601
|
+
this.renderStrategy = inject(PreviewFlowRenderStrategyService);
|
|
3602
|
+
this.nodeRenderingService = inject(NodeRenderingService);
|
|
3603
|
+
this.renderer2 = inject(Renderer2);
|
|
3604
|
+
this.element = inject(ElementRef).nativeElement;
|
|
3605
|
+
this.ctx = this.element.getContext('2d');
|
|
3606
|
+
this.width = input(0);
|
|
3607
|
+
this.height = input(0);
|
|
3608
|
+
this.dpr = window.devicePixelRatio;
|
|
3609
|
+
effect(() => {
|
|
3610
|
+
// Set the "actual" size of the canvas
|
|
3611
|
+
this.renderer2.setProperty(this.element, 'width', this.width() * this.dpr);
|
|
3612
|
+
this.renderer2.setProperty(this.element, 'height', this.height() * this.dpr);
|
|
3613
|
+
// Set the "drawn" size of the canvas
|
|
3614
|
+
this.renderer2.setStyle(this.element, 'width', `${this.width()}px`);
|
|
3615
|
+
this.renderer2.setStyle(this.element, 'height', `${this.height()}px`);
|
|
3616
|
+
// Scale the context to match device pixel ratio
|
|
3617
|
+
this.ctx.scale(this.dpr, this.dpr);
|
|
3618
|
+
});
|
|
3619
|
+
effect(() => {
|
|
3620
|
+
const viewport = this.viewportService.readableViewport();
|
|
3621
|
+
this.ctx.clearRect(0, 0, this.width(), this.height());
|
|
3622
|
+
// Save the current context state
|
|
3623
|
+
this.ctx.save();
|
|
3624
|
+
// Apply viewport transformations (zoom and pan)
|
|
3625
|
+
this.ctx.setTransform(viewport.zoom * this.dpr, // horizontal scaling with DPR
|
|
3626
|
+
0, // horizontal skewing
|
|
3627
|
+
0, // vertical skewing
|
|
3628
|
+
viewport.zoom * this.dpr, // vertical scaling with DPR
|
|
3629
|
+
viewport.x * this.dpr, // horizontal translation with DPR
|
|
3630
|
+
viewport.y * this.dpr);
|
|
3631
|
+
for (let i = 0; i < this.nodeRenderingService.viewportNodes().length; i++) {
|
|
3632
|
+
const node = this.nodeRenderingService.viewportNodes()[i];
|
|
3633
|
+
if (this.renderStrategy.shouldRenderNode(node)) {
|
|
3634
|
+
drawNode(this.ctx, node);
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
// Restore the context state
|
|
3638
|
+
this.ctx.restore();
|
|
3639
|
+
});
|
|
3640
|
+
}
|
|
3641
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3642
|
+
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 }); }
|
|
3643
|
+
}
|
|
3644
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowComponent, decorators: [{
|
|
3645
|
+
type: Component,
|
|
3646
|
+
args: [{
|
|
3647
|
+
standalone: true,
|
|
3648
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3649
|
+
selector: 'canvas[previewFlow]',
|
|
3650
|
+
template: '',
|
|
3651
|
+
}]
|
|
3652
|
+
}], ctorParameters: () => [] });
|
|
3653
|
+
|
|
3275
3654
|
const changesControllerHostDirective = {
|
|
3276
3655
|
directive: ChangesControllerDirective,
|
|
3277
3656
|
outputs: [
|
|
@@ -3319,9 +3698,6 @@ class VflowComponent {
|
|
|
3319
3698
|
this.componentEventBusService = inject(ComponentEventBusService);
|
|
3320
3699
|
this.keyboardService = inject(KeyboardService);
|
|
3321
3700
|
this.injector = inject(Injector);
|
|
3322
|
-
this.optimization = input({
|
|
3323
|
-
detachedGroupsLayer: false,
|
|
3324
|
-
});
|
|
3325
3701
|
this.nodeModels = this.nodeRenderingService.nodes;
|
|
3326
3702
|
this.groups = this.nodeRenderingService.groups;
|
|
3327
3703
|
this.nonGroups = this.nodeRenderingService.nonGroups;
|
|
@@ -3355,13 +3731,13 @@ class VflowComponent {
|
|
|
3355
3731
|
/**
|
|
3356
3732
|
* Signal for reading nodes change
|
|
3357
3733
|
*/
|
|
3358
|
-
this.nodesChange =
|
|
3734
|
+
this.nodesChange = toLazySignal(this.nodesChangeService.changes$, {
|
|
3359
3735
|
initialValue: [],
|
|
3360
3736
|
});
|
|
3361
3737
|
/**
|
|
3362
3738
|
* Signal to reading edges change
|
|
3363
3739
|
*/
|
|
3364
|
-
this.edgesChange =
|
|
3740
|
+
this.edgesChange = toLazySignal(this.edgesChangeService.changes$, {
|
|
3365
3741
|
initialValue: [],
|
|
3366
3742
|
});
|
|
3367
3743
|
// #endregion
|
|
@@ -3381,6 +3757,9 @@ class VflowComponent {
|
|
|
3381
3757
|
// #endregion
|
|
3382
3758
|
this.markers = this.flowEntitiesService.markers;
|
|
3383
3759
|
this.minimap = this.flowEntitiesService.minimap;
|
|
3760
|
+
this.flowOptimization = this.flowSettingsService.optimization;
|
|
3761
|
+
this.flowWidth = this.flowSettingsService.computedFlowWidth;
|
|
3762
|
+
this.flowHeight = this.flowSettingsService.computedFlowHeight;
|
|
3384
3763
|
}
|
|
3385
3764
|
// #endregion
|
|
3386
3765
|
// #region SETTINGS
|
|
@@ -3412,6 +3791,12 @@ class VflowComponent {
|
|
|
3412
3791
|
set background(value) {
|
|
3413
3792
|
this.flowSettingsService.background.set(transformBackground(value));
|
|
3414
3793
|
}
|
|
3794
|
+
set optimization(newOptimization) {
|
|
3795
|
+
this.flowSettingsService.optimization.update((optimization) => ({
|
|
3796
|
+
...optimization,
|
|
3797
|
+
...newOptimization,
|
|
3798
|
+
}));
|
|
3799
|
+
}
|
|
3415
3800
|
/**
|
|
3416
3801
|
* Global rule if you can or can't select entities
|
|
3417
3802
|
*/
|
|
@@ -3469,7 +3854,7 @@ class VflowComponent {
|
|
|
3469
3854
|
set edges(newEdges) {
|
|
3470
3855
|
const newModels = runInInjectionContext(this.injector, () => ReferenceIdentityChecker.edges(newEdges, this.flowEntitiesService.edges()));
|
|
3471
3856
|
// quick and dirty binding nodes to edges
|
|
3472
|
-
addNodesToEdges(this.
|
|
3857
|
+
addNodesToEdges(this.flowEntitiesService.nodes(), newModels);
|
|
3473
3858
|
this.flowEntitiesService.edges.set(newModels);
|
|
3474
3859
|
}
|
|
3475
3860
|
// #region METHODS_API
|
|
@@ -3572,7 +3957,7 @@ class VflowComponent {
|
|
|
3572
3957
|
return edge;
|
|
3573
3958
|
}
|
|
3574
3959
|
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:
|
|
3960
|
+
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
3961
|
DraggableService,
|
|
3577
3962
|
ViewportService,
|
|
3578
3963
|
FlowStatusService,
|
|
@@ -3586,7 +3971,8 @@ class VflowComponent {
|
|
|
3586
3971
|
ComponentEventBusService,
|
|
3587
3972
|
KeyboardService,
|
|
3588
3973
|
OverlaysService,
|
|
3589
|
-
|
|
3974
|
+
{ provide: PreviewFlowRenderStrategyService, useClass: ViewportPreviewFlowRenderStrategyService },
|
|
3975
|
+
], 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
3976
|
}
|
|
3591
3977
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, decorators: [{
|
|
3592
3978
|
type: Component,
|
|
@@ -3604,6 +3990,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3604
3990
|
ComponentEventBusService,
|
|
3605
3991
|
KeyboardService,
|
|
3606
3992
|
OverlaysService,
|
|
3993
|
+
{ provide: PreviewFlowRenderStrategyService, useClass: ViewportPreviewFlowRenderStrategyService },
|
|
3607
3994
|
], hostDirectives: [changesControllerHostDirective], imports: [
|
|
3608
3995
|
RootSvgReferenceDirective,
|
|
3609
3996
|
RootSvgContextDirective,
|
|
@@ -3617,7 +4004,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3617
4004
|
NodeComponent,
|
|
3618
4005
|
EdgeComponent,
|
|
3619
4006
|
NgTemplateOutlet,
|
|
3620
|
-
|
|
4007
|
+
PreviewFlowComponent,
|
|
4008
|
+
], 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
4009
|
}], propDecorators: { view: [{
|
|
3622
4010
|
type: Input
|
|
3623
4011
|
}], minZoom: [{
|
|
@@ -3626,6 +4014,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3626
4014
|
type: Input
|
|
3627
4015
|
}], background: [{
|
|
3628
4016
|
type: Input
|
|
4017
|
+
}], optimization: [{
|
|
4018
|
+
type: Input
|
|
3629
4019
|
}], entitiesSelectable: [{
|
|
3630
4020
|
type: Input
|
|
3631
4021
|
}], keyboardShortcuts: [{
|
|
@@ -3966,5 +4356,5 @@ const Vflow = [
|
|
|
3966
4356
|
* Generated bundle index. Do not edit.
|
|
3967
4357
|
*/
|
|
3968
4358
|
|
|
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 };
|
|
4359
|
+
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
4360
|
//# sourceMappingURL=ngx-vflow.mjs.map
|