@ship-ui/core 0.15.17 → 0.15.19
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/fesm2022/ship-ui-core.mjs +1048 -12
- package/fesm2022/ship-ui-core.mjs.map +1 -1
- package/index.d.ts +97 -3
- package/package.json +4 -1
- package/styles/components/ship-blueprint.component.scss +238 -0
- package/styles/index.scss +4 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, ElementRef, Renderer2, ChangeDetectionStrategy, Component, signal, DestroyRef, InjectionToken, input, computed, viewChild, effect, HostListener, NgModule, Injectable, DOCUMENT, model, output, ApplicationRef, createComponent, isSignal, OutputEmitterRef, contentChild, contentChildren, afterNextRender, assertInInjectionContext, Injector, HostBinding, TemplateRef, runInInjectionContext, Directive, ChangeDetectorRef, viewChildren, ViewContainerRef, EnvironmentInjector } from '@angular/core';
|
|
3
|
-
import { DatePipe, NgTemplateOutlet } from '@angular/common';
|
|
2
|
+
import { inject, ElementRef, Renderer2, ChangeDetectionStrategy, Component, signal, DestroyRef, InjectionToken, input, computed, viewChild, effect, HostListener, NgModule, Injectable, DOCUMENT, model, output, ApplicationRef, createComponent, isSignal, OutputEmitterRef, PLATFORM_ID, ViewChild, contentChild, contentChildren, afterNextRender, assertInInjectionContext, Injector, HostBinding, TemplateRef, runInInjectionContext, Directive, ChangeDetectorRef, viewChildren, ViewContainerRef, EnvironmentInjector } from '@angular/core';
|
|
3
|
+
import { isPlatformBrowser, JsonPipe, DatePipe, NgTemplateOutlet } from '@angular/common';
|
|
4
|
+
import { ShipCardComponent as ShipCardComponent$1, ShipIconComponent as ShipIconComponent$1, ShipButtonComponent as ShipButtonComponent$1 } from 'ship-ui';
|
|
4
5
|
import { NgModel } from '@angular/forms';
|
|
5
6
|
import { SIGNAL } from '@angular/core/primitives/signals';
|
|
6
7
|
|
|
@@ -35,8 +36,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImpor
|
|
|
35
36
|
}]
|
|
36
37
|
}] });
|
|
37
38
|
|
|
38
|
-
function classMutationSignal() {
|
|
39
|
-
const element = inject(ElementRef).nativeElement;
|
|
39
|
+
function classMutationSignal(_element = null) {
|
|
40
|
+
const element = _element ?? inject(ElementRef).nativeElement;
|
|
40
41
|
if (!element)
|
|
41
42
|
return signal('');
|
|
42
43
|
const classListSignal = signal(element.className, ...(ngDevMode ? [{ debugName: "classListSignal" }] : []));
|
|
@@ -559,6 +560,1041 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImpor
|
|
|
559
560
|
}]
|
|
560
561
|
}] });
|
|
561
562
|
|
|
563
|
+
function layoutNodes(nodes) {
|
|
564
|
+
if (!nodes || nodes.length === 0)
|
|
565
|
+
return nodes;
|
|
566
|
+
const layoutedNodes = structuredClone(nodes);
|
|
567
|
+
const visited = new Set();
|
|
568
|
+
const unconnectedNodes = [];
|
|
569
|
+
const connectedGraphs = [];
|
|
570
|
+
const allToNodes = new Set(layoutedNodes.flatMap((node) => node.connections).map((conn) => conn.toNode));
|
|
571
|
+
const rootNodes = layoutedNodes.filter((node) => !allToNodes.has(node.id));
|
|
572
|
+
for (const node of layoutedNodes) {
|
|
573
|
+
if (!node.connections.length) {
|
|
574
|
+
unconnectedNodes.push(node);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
for (const root of rootNodes) {
|
|
578
|
+
if (!visited.has(root.id)) {
|
|
579
|
+
const graph = [];
|
|
580
|
+
traverseGraph(root, graph, visited, layoutedNodes);
|
|
581
|
+
if (graph.length > 0) {
|
|
582
|
+
connectedGraphs.push(graph);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
positionUnconnectedNodes(unconnectedNodes);
|
|
587
|
+
let currentX = 20;
|
|
588
|
+
let currentY = 20;
|
|
589
|
+
connectedGraphs.forEach((graph) => {
|
|
590
|
+
const layoutedGraph = traverseAndLayout(graph);
|
|
591
|
+
positionGraph(layoutedGraph, currentX, currentY);
|
|
592
|
+
currentY += getGraphHeight(layoutedGraph) + 200;
|
|
593
|
+
});
|
|
594
|
+
return layoutedNodes;
|
|
595
|
+
}
|
|
596
|
+
function traverseGraph(startNode, graph, visited, allNodes) {
|
|
597
|
+
const queue = [startNode];
|
|
598
|
+
visited.add(startNode.id);
|
|
599
|
+
let head = 0;
|
|
600
|
+
while (head < queue.length) {
|
|
601
|
+
const node = queue[head++];
|
|
602
|
+
graph.push(node);
|
|
603
|
+
node.connections.forEach((conn) => {
|
|
604
|
+
const toNode = allNodes.find((n) => n.id === conn.toNode);
|
|
605
|
+
if (toNode && !visited.has(toNode.id)) {
|
|
606
|
+
visited.add(toNode.id);
|
|
607
|
+
queue.push(toNode);
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function positionUnconnectedNodes(nodes) {
|
|
613
|
+
nodes.forEach((node, index) => {
|
|
614
|
+
node.coordinates[0] = 20 + index * 200;
|
|
615
|
+
node.coordinates[1] = 20;
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
function traverseAndLayout(graph) {
|
|
619
|
+
const graphNodeMap = new Map(graph.map((n) => [n.id, n]));
|
|
620
|
+
const incomingConnectionCount = new Map();
|
|
621
|
+
graph.forEach((node) => {
|
|
622
|
+
incomingConnectionCount.set(node.id, 0);
|
|
623
|
+
});
|
|
624
|
+
graph
|
|
625
|
+
.flatMap((n) => n.connections)
|
|
626
|
+
.forEach((conn) => {
|
|
627
|
+
if (graphNodeMap.has(conn.toNode)) {
|
|
628
|
+
incomingConnectionCount.set(conn.toNode, (incomingConnectionCount.get(conn.toNode) || 0) + 1);
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
const roots = graph.filter((node) => (incomingConnectionCount.get(node.id) || 0) === 0);
|
|
632
|
+
const layoutedNodes = new Set();
|
|
633
|
+
const queue = [...roots];
|
|
634
|
+
let head = 0;
|
|
635
|
+
while (head < queue.length) {
|
|
636
|
+
const currentNode = queue[head++];
|
|
637
|
+
layoutedNodes.add(currentNode.id);
|
|
638
|
+
let outputConnectionIndex = 0;
|
|
639
|
+
const connectedNodes = [];
|
|
640
|
+
currentNode.connections.forEach((conn) => {
|
|
641
|
+
const toNode = graphNodeMap.get(conn.toNode);
|
|
642
|
+
if (toNode && !layoutedNodes.has(toNode.id)) {
|
|
643
|
+
connectedNodes.push(toNode);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
connectedNodes.forEach((toNode) => {
|
|
647
|
+
toNode.coordinates[0] = currentNode.coordinates[0] + 200;
|
|
648
|
+
if (connectedNodes.length > 1) {
|
|
649
|
+
const verticalOffset = (outputConnectionIndex - (connectedNodes.length - 1) / 2) * 200;
|
|
650
|
+
toNode.coordinates[1] = currentNode.coordinates[1] + verticalOffset;
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
toNode.coordinates[1] = currentNode.coordinates[1];
|
|
654
|
+
}
|
|
655
|
+
queue.push(toNode);
|
|
656
|
+
outputConnectionIndex++;
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
return graph;
|
|
660
|
+
}
|
|
661
|
+
function positionGraph(graph, startX, startY) {
|
|
662
|
+
const minX = Math.min(...graph.map((n) => n.coordinates[0]));
|
|
663
|
+
const minY = Math.min(...graph.map((n) => n.coordinates[1]));
|
|
664
|
+
graph.forEach((node) => {
|
|
665
|
+
node.coordinates[0] += startX - minX;
|
|
666
|
+
node.coordinates[1] += startY - minY;
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
function getGraphHeight(graph) {
|
|
670
|
+
if (!graph.length)
|
|
671
|
+
return 0;
|
|
672
|
+
const maxY = Math.max(...graph.map((n) => n.coordinates[1]));
|
|
673
|
+
const minY = Math.min(...graph.map((n) => n.coordinates[1]));
|
|
674
|
+
return maxY - minY;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function findDuplicatePortIDs(nodes) {
|
|
678
|
+
const errors = [];
|
|
679
|
+
for (const node of nodes) {
|
|
680
|
+
const portIdCounts = new Map();
|
|
681
|
+
const allPorts = [...node.inputs, ...node.outputs];
|
|
682
|
+
for (const port of allPorts) {
|
|
683
|
+
const count = portIdCounts.get(port.id) || 0;
|
|
684
|
+
portIdCounts.set(port.id, count + 1);
|
|
685
|
+
}
|
|
686
|
+
const duplicatePortIds = [];
|
|
687
|
+
for (const [id, count] of portIdCounts.entries()) {
|
|
688
|
+
if (count > 1) {
|
|
689
|
+
duplicatePortIds.push(id);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (duplicatePortIds.length > 0) {
|
|
693
|
+
errors.push({
|
|
694
|
+
nodeId: node.id,
|
|
695
|
+
duplicatePortIds: duplicatePortIds,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return errors;
|
|
700
|
+
}
|
|
701
|
+
function findDuplicateNodeIDs(nodes) {
|
|
702
|
+
const nodeIdCounts = new Map();
|
|
703
|
+
for (const node of nodes) {
|
|
704
|
+
const count = nodeIdCounts.get(node.id) || 0;
|
|
705
|
+
nodeIdCounts.set(node.id, count + 1);
|
|
706
|
+
}
|
|
707
|
+
const duplicateNodeIds = [];
|
|
708
|
+
for (const [id, count] of nodeIdCounts.entries()) {
|
|
709
|
+
if (count > 1) {
|
|
710
|
+
duplicateNodeIds.push(id);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return duplicateNodeIds;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const TEST_NODES = [
|
|
717
|
+
{
|
|
718
|
+
id: 'a1',
|
|
719
|
+
coordinates: [0, 0],
|
|
720
|
+
inputs: [],
|
|
721
|
+
outputs: [{ id: 'out-1', name: 'Start Output' }],
|
|
722
|
+
connections: [{ fromNode: 'a1', fromPort: 'out-1', toNode: 'b1', toPort: 'in-1' }],
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
id: 'b1',
|
|
726
|
+
coordinates: [0, 0],
|
|
727
|
+
inputs: [
|
|
728
|
+
{ id: 'in-1', name: 'Input A' },
|
|
729
|
+
{ id: 'in-1', name: 'Another Input A' },
|
|
730
|
+
],
|
|
731
|
+
outputs: [{ id: 'out-1', name: 'Output B' }],
|
|
732
|
+
connections: [
|
|
733
|
+
{ fromNode: 'b1', fromPort: 'out-1', toNode: 'c6', toPort: 'in-1' },
|
|
734
|
+
{ fromNode: 'a1', fromPort: 'out-1', toNode: 'b1', toPort: 'in-1' },
|
|
735
|
+
],
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
id: 'c6',
|
|
739
|
+
coordinates: [0, 0],
|
|
740
|
+
inputs: [{ id: 'in-1', name: 'Input C' }],
|
|
741
|
+
outputs: [{ id: 'out-1', name: 'Output D' }],
|
|
742
|
+
connections: [{ fromNode: 'b1', fromPort: 'out-1', toNode: 'c6', toPort: 'in-1' }],
|
|
743
|
+
},
|
|
744
|
+
];
|
|
745
|
+
class ShipBlueprintComponent {
|
|
746
|
+
#ZOOM_SPEED;
|
|
747
|
+
#MAX_ZOOM;
|
|
748
|
+
#MIN_ZOOM;
|
|
749
|
+
#document;
|
|
750
|
+
#platformId;
|
|
751
|
+
#selfRef;
|
|
752
|
+
#currentClass;
|
|
753
|
+
#htmlClass;
|
|
754
|
+
#currentGridColor;
|
|
755
|
+
#draggingNodeCoordinates;
|
|
756
|
+
#isDragging;
|
|
757
|
+
#lastMouseX;
|
|
758
|
+
#lastMouseY;
|
|
759
|
+
#initialPinchDistance;
|
|
760
|
+
#isNodeDragging;
|
|
761
|
+
#draggedNodeId;
|
|
762
|
+
#dragOffset;
|
|
763
|
+
#connections;
|
|
764
|
+
#ctx;
|
|
765
|
+
#resizeObserver;
|
|
766
|
+
constructor() {
|
|
767
|
+
this.#ZOOM_SPEED = 0.01;
|
|
768
|
+
this.#MAX_ZOOM = 1.5;
|
|
769
|
+
this.#MIN_ZOOM = 0.5;
|
|
770
|
+
this.#document = inject(DOCUMENT);
|
|
771
|
+
this.#platformId = inject(PLATFORM_ID);
|
|
772
|
+
this.#selfRef = inject((ElementRef));
|
|
773
|
+
this.#currentClass = classMutationSignal();
|
|
774
|
+
this.#htmlClass = classMutationSignal(this.#document.documentElement);
|
|
775
|
+
this.asDots = computed(() => this.#currentClass().includes('dots'), ...(ngDevMode ? [{ debugName: "asDots" }] : []));
|
|
776
|
+
this.lightMode = computed(() => this.#htmlClass().includes('light'), ...(ngDevMode ? [{ debugName: "lightMode" }] : []));
|
|
777
|
+
this.forceUnique = input(true, ...(ngDevMode ? [{ debugName: "forceUnique" }] : []));
|
|
778
|
+
this.autoLayout = input(false, ...(ngDevMode ? [{ debugName: "autoLayout" }] : []));
|
|
779
|
+
this.gridSize = input(20, ...(ngDevMode ? [{ debugName: "gridSize" }] : []));
|
|
780
|
+
this.snapToGrid = input(true, ...(ngDevMode ? [{ debugName: "snapToGrid" }] : []));
|
|
781
|
+
this.gridColor = input(['#d8d8d8', '#2c2c2c'], ...(ngDevMode ? [{ debugName: "gridColor" }] : []));
|
|
782
|
+
this.nodes = model([], ...(ngDevMode ? [{ debugName: "nodes" }] : []));
|
|
783
|
+
this.#currentGridColor = computed(() => this.gridColor()[this.lightMode() ? 0 : 1], ...(ngDevMode ? [{ debugName: "#currentGridColor" }] : []));
|
|
784
|
+
this.panX = signal(0, ...(ngDevMode ? [{ debugName: "panX" }] : []));
|
|
785
|
+
this.panY = signal(0, ...(ngDevMode ? [{ debugName: "panY" }] : []));
|
|
786
|
+
this.zoomLevel = signal(1, ...(ngDevMode ? [{ debugName: "zoomLevel" }] : []));
|
|
787
|
+
this.gridSnapSize = signal(20, ...(ngDevMode ? [{ debugName: "gridSnapSize" }] : []));
|
|
788
|
+
this.isHoveringNode = signal(false, ...(ngDevMode ? [{ debugName: "isHoveringNode" }] : []));
|
|
789
|
+
this.midpointDivPosition = signal(null, ...(ngDevMode ? [{ debugName: "midpointDivPosition" }] : []));
|
|
790
|
+
this.showMidpointDiv = signal(false, ...(ngDevMode ? [{ debugName: "showMidpointDiv" }] : []));
|
|
791
|
+
this.isLocked = signal(false, ...(ngDevMode ? [{ debugName: "isLocked" }] : []));
|
|
792
|
+
this.draggingConnection = signal(null, ...(ngDevMode ? [{ debugName: "draggingConnection" }] : []));
|
|
793
|
+
this.validationErrors = signal(null, ...(ngDevMode ? [{ debugName: "validationErrors" }] : []));
|
|
794
|
+
this.highlightedConnection = signal(null, ...(ngDevMode ? [{ debugName: "highlightedConnection" }] : []));
|
|
795
|
+
this.#draggingNodeCoordinates = signal(null, ...(ngDevMode ? [{ debugName: "#draggingNodeCoordinates" }] : []));
|
|
796
|
+
this.#isDragging = signal(false, ...(ngDevMode ? [{ debugName: "#isDragging" }] : []));
|
|
797
|
+
this.#lastMouseX = signal(0, ...(ngDevMode ? [{ debugName: "#lastMouseX" }] : []));
|
|
798
|
+
this.#lastMouseY = signal(0, ...(ngDevMode ? [{ debugName: "#lastMouseY" }] : []));
|
|
799
|
+
this.#initialPinchDistance = signal(0, ...(ngDevMode ? [{ debugName: "#initialPinchDistance" }] : []));
|
|
800
|
+
this.#isNodeDragging = signal(false, ...(ngDevMode ? [{ debugName: "#isNodeDragging" }] : []));
|
|
801
|
+
this.#draggedNodeId = signal(null, ...(ngDevMode ? [{ debugName: "#draggedNodeId" }] : []));
|
|
802
|
+
this.#dragOffset = signal(null, ...(ngDevMode ? [{ debugName: "#dragOffset" }] : []));
|
|
803
|
+
this.#connections = signal([], ...(ngDevMode ? [{ debugName: "#connections" }] : []));
|
|
804
|
+
if (isPlatformBrowser(this.#platformId)) {
|
|
805
|
+
effect(() => {
|
|
806
|
+
this.asDots();
|
|
807
|
+
this.panX();
|
|
808
|
+
this.panY();
|
|
809
|
+
this.zoomLevel();
|
|
810
|
+
this.nodes();
|
|
811
|
+
this.#connections();
|
|
812
|
+
this.draggingConnection();
|
|
813
|
+
this.#currentGridColor();
|
|
814
|
+
this.highlightedConnection();
|
|
815
|
+
this.#draggingNodeCoordinates();
|
|
816
|
+
requestAnimationFrame(() => this.drawCanvas());
|
|
817
|
+
});
|
|
818
|
+
effect(() => {
|
|
819
|
+
const nodes = this.nodes();
|
|
820
|
+
const connectionsFromNodes = nodes.flatMap((node) => node.connections);
|
|
821
|
+
const uniqueConnections = connectionsFromNodes.filter((conn, index, self) => index ===
|
|
822
|
+
self.findIndex((c) => c.fromNode === conn.fromNode &&
|
|
823
|
+
c.fromPort === conn.fromPort &&
|
|
824
|
+
c.toNode === conn.toNode &&
|
|
825
|
+
c.toPort === conn.toPort));
|
|
826
|
+
this.#connections.set(uniqueConnections);
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
ngAfterViewInit() {
|
|
831
|
+
if (isPlatformBrowser(this.#platformId)) {
|
|
832
|
+
const canvas = this.canvasRef.nativeElement;
|
|
833
|
+
this.#ctx = canvas.getContext('2d');
|
|
834
|
+
this.#resizeObserver = new ResizeObserver(() => this.updateCanvasSize());
|
|
835
|
+
this.#resizeObserver.observe(this.#selfRef.nativeElement);
|
|
836
|
+
this.updateCanvasSize();
|
|
837
|
+
if (this.autoLayout()) {
|
|
838
|
+
this.applyAutolayout();
|
|
839
|
+
}
|
|
840
|
+
if (this.forceUnique()) {
|
|
841
|
+
this.#removeDuplicatesAndReinitiate();
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
this.#validateNodes();
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
ngOnDestroy() {
|
|
849
|
+
if (this.#resizeObserver) {
|
|
850
|
+
this.#resizeObserver.disconnect();
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
#validateNodes() {
|
|
854
|
+
const duplicatePortIds = findDuplicatePortIDs(this.nodes());
|
|
855
|
+
const duplicateNodeIds = findDuplicateNodeIDs(this.nodes());
|
|
856
|
+
this.validationErrors.set({
|
|
857
|
+
duplicateNodeIds,
|
|
858
|
+
duplicatePortIds,
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
applyAutolayout() {
|
|
862
|
+
const newNodes = layoutNodes(this.nodes());
|
|
863
|
+
this.nodes.set(newNodes);
|
|
864
|
+
}
|
|
865
|
+
updateCanvasSize() {
|
|
866
|
+
const canvas = this.canvasRef.nativeElement;
|
|
867
|
+
const rect = this.#selfRef.nativeElement.getBoundingClientRect();
|
|
868
|
+
const dpr = window.devicePixelRatio || 1;
|
|
869
|
+
canvas.width = rect.width * dpr;
|
|
870
|
+
canvas.height = rect.height * dpr;
|
|
871
|
+
this.#ctx.scale(dpr, dpr);
|
|
872
|
+
canvas.style.width = `${rect.width}px`;
|
|
873
|
+
canvas.style.height = `${rect.height}px`;
|
|
874
|
+
this.drawCanvas();
|
|
875
|
+
}
|
|
876
|
+
drawCanvas() {
|
|
877
|
+
if (!this.#ctx)
|
|
878
|
+
return;
|
|
879
|
+
const ctx = this.#ctx;
|
|
880
|
+
const { width, height } = this.canvasRef.nativeElement;
|
|
881
|
+
const dpr = window.devicePixelRatio || 1;
|
|
882
|
+
ctx.clearRect(0, 0, width / dpr, height / dpr);
|
|
883
|
+
ctx.save();
|
|
884
|
+
ctx.translate(this.panX(), this.panY());
|
|
885
|
+
ctx.scale(this.zoomLevel(), this.zoomLevel());
|
|
886
|
+
this.drawGrid(ctx);
|
|
887
|
+
this.drawConnections(ctx);
|
|
888
|
+
if (this.draggingConnection()) {
|
|
889
|
+
this.drawDraggingPath(ctx);
|
|
890
|
+
}
|
|
891
|
+
ctx.restore();
|
|
892
|
+
}
|
|
893
|
+
drawGrid(ctx) {
|
|
894
|
+
const { width, height } = this.canvasRef.nativeElement;
|
|
895
|
+
const dpr = window.devicePixelRatio || 1;
|
|
896
|
+
const zoom = this.zoomLevel();
|
|
897
|
+
const panX = this.panX();
|
|
898
|
+
const panY = this.panY();
|
|
899
|
+
const gridSize = this.gridSize();
|
|
900
|
+
const gridColor = this.#currentGridColor();
|
|
901
|
+
const scaledGridSize = gridSize * zoom;
|
|
902
|
+
const dynamicGridSize = scaledGridSize < 20 ? gridSize * 4 : gridSize;
|
|
903
|
+
const startX = Math.floor(-panX / zoom / dynamicGridSize) * dynamicGridSize;
|
|
904
|
+
const startY = Math.floor(-panY / zoom / dynamicGridSize) * dynamicGridSize;
|
|
905
|
+
const endX = startX + width / dpr / zoom + dynamicGridSize;
|
|
906
|
+
const endY = startY + height / dpr / zoom + dynamicGridSize;
|
|
907
|
+
if (this.asDots()) {
|
|
908
|
+
const dotRadius = 1 / zoom;
|
|
909
|
+
ctx.fillStyle = gridColor;
|
|
910
|
+
for (let x = startX; x < endX; x += dynamicGridSize) {
|
|
911
|
+
for (let y = startY; y < endY; y += dynamicGridSize) {
|
|
912
|
+
ctx.beginPath();
|
|
913
|
+
ctx.arc(x, y, dotRadius, 0, 2 * Math.PI);
|
|
914
|
+
ctx.fill();
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
else {
|
|
919
|
+
ctx.beginPath();
|
|
920
|
+
ctx.strokeStyle = gridColor;
|
|
921
|
+
ctx.lineWidth = 1 / zoom;
|
|
922
|
+
for (let x = startX; x < endX; x += dynamicGridSize) {
|
|
923
|
+
ctx.moveTo(x, startY);
|
|
924
|
+
ctx.lineTo(x, endY);
|
|
925
|
+
}
|
|
926
|
+
for (let y = startY; y < endY; y += dynamicGridSize) {
|
|
927
|
+
ctx.moveTo(startX, y);
|
|
928
|
+
ctx.lineTo(endX, y);
|
|
929
|
+
}
|
|
930
|
+
ctx.stroke();
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
drawConnections(ctx) {
|
|
934
|
+
const highlighted = this.highlightedConnection();
|
|
935
|
+
ctx.lineWidth = 2 / this.zoomLevel();
|
|
936
|
+
for (const conn of this.#connections()) {
|
|
937
|
+
const isHighlighted = highlighted?.fromNode === conn.fromNode &&
|
|
938
|
+
highlighted?.fromPort === conn.fromPort &&
|
|
939
|
+
highlighted?.toNode === conn.toNode &&
|
|
940
|
+
highlighted?.toPort === conn.toPort;
|
|
941
|
+
ctx.strokeStyle = isHighlighted ? '#ffc107' : '#888';
|
|
942
|
+
ctx.beginPath();
|
|
943
|
+
const startPos = this.getNodePortPosition(conn.fromNode, conn.fromPort);
|
|
944
|
+
const endPos = this.getNodePortPosition(conn.toNode, conn.toPort);
|
|
945
|
+
this.drawCurvedPath(ctx, startPos, endPos);
|
|
946
|
+
ctx.stroke();
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
drawDraggingPath(ctx) {
|
|
950
|
+
const conn = this.draggingConnection();
|
|
951
|
+
const startPos = this.getNodePortPosition(conn.fromNode, conn.fromPort);
|
|
952
|
+
const endPos = [conn.x2, conn.y2];
|
|
953
|
+
ctx.strokeStyle = '#5a9cf8';
|
|
954
|
+
ctx.lineWidth = 2 / this.zoomLevel();
|
|
955
|
+
ctx.beginPath();
|
|
956
|
+
this.drawCurvedPath(ctx, startPos, endPos);
|
|
957
|
+
ctx.stroke();
|
|
958
|
+
}
|
|
959
|
+
drawCurvedPath(ctx, start, end) {
|
|
960
|
+
const [x1, y1] = start;
|
|
961
|
+
const [x2, y2] = end;
|
|
962
|
+
const dx = Math.abs(x1 - x2) * 0.7;
|
|
963
|
+
ctx.moveTo(x1, y1);
|
|
964
|
+
ctx.bezierCurveTo(x1 + dx, y1, x2 - dx, y2, x2, y2);
|
|
965
|
+
}
|
|
966
|
+
onMouseUp(event) {
|
|
967
|
+
if (this.isLocked())
|
|
968
|
+
return;
|
|
969
|
+
this.endPan();
|
|
970
|
+
this.endNodeDrag();
|
|
971
|
+
}
|
|
972
|
+
onClick(event) {
|
|
973
|
+
const target = event.target;
|
|
974
|
+
if (this.draggingConnection()) {
|
|
975
|
+
if (!target.closest('.port')) {
|
|
976
|
+
this.cancelPortDrag();
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
else if (this.isLocked() && !target.closest('.midpoint-div')) {
|
|
980
|
+
this.closeMidpointDiv();
|
|
981
|
+
}
|
|
982
|
+
else {
|
|
983
|
+
this.#handleConnectionClick(event);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
onEscape(event) {
|
|
987
|
+
if (this.draggingConnection()) {
|
|
988
|
+
this.cancelPortDrag();
|
|
989
|
+
}
|
|
990
|
+
else if (this.isLocked()) {
|
|
991
|
+
this.closeMidpointDiv();
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
onMouseMove(event) {
|
|
995
|
+
if (this.isLocked())
|
|
996
|
+
return;
|
|
997
|
+
if (this.#isNodeDragging()) {
|
|
998
|
+
this.nodeDrag(event);
|
|
999
|
+
}
|
|
1000
|
+
else if (this.draggingConnection()) {
|
|
1001
|
+
this.updatePathOnMove(event);
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
this.pan(event);
|
|
1005
|
+
if (this.isHoveringNode()) {
|
|
1006
|
+
this.highlightedConnection.set(null);
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
this.#checkConnectionHover(event);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
onTouchMove(event) {
|
|
1014
|
+
if (this.isLocked())
|
|
1015
|
+
return;
|
|
1016
|
+
if (this.#isNodeDragging()) {
|
|
1017
|
+
this.nodeDrag(event.touches[0]);
|
|
1018
|
+
}
|
|
1019
|
+
else if (this.draggingConnection()) {
|
|
1020
|
+
this.updatePathOnMove(event.touches[0]);
|
|
1021
|
+
}
|
|
1022
|
+
else {
|
|
1023
|
+
this.handleTouchMove(event);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
startNodeDrag(event, nodeId) {
|
|
1027
|
+
event.stopPropagation();
|
|
1028
|
+
event.preventDefault();
|
|
1029
|
+
this.#isNodeDragging.set(true);
|
|
1030
|
+
this.#draggedNodeId.set(nodeId);
|
|
1031
|
+
const clientX = event instanceof MouseEvent ? event.clientX : event.touches[0].clientX;
|
|
1032
|
+
const clientY = event instanceof MouseEvent ? event.clientY : event.touches[0].clientY;
|
|
1033
|
+
const blueprintRect = this.#selfRef.nativeElement.getBoundingClientRect();
|
|
1034
|
+
const worldX = (clientX - blueprintRect.left - this.panX()) / this.zoomLevel();
|
|
1035
|
+
const worldY = (clientY - blueprintRect.top - this.panY()) / this.zoomLevel();
|
|
1036
|
+
const draggedNode = this.nodes().find((n) => n.id === nodeId);
|
|
1037
|
+
if (draggedNode) {
|
|
1038
|
+
this.#dragOffset.set([worldX - draggedNode.coordinates[0], worldY - draggedNode.coordinates[1]]);
|
|
1039
|
+
}
|
|
1040
|
+
this.#lastMouseX.set(clientX);
|
|
1041
|
+
this.#lastMouseY.set(clientY);
|
|
1042
|
+
}
|
|
1043
|
+
endNodeDrag() {
|
|
1044
|
+
const draggedId = this.#draggedNodeId();
|
|
1045
|
+
const finalCoords = this.#draggingNodeCoordinates();
|
|
1046
|
+
if (draggedId && finalCoords) {
|
|
1047
|
+
this.nodes.update((nodes) => {
|
|
1048
|
+
const node = nodes.find((n) => n.id === draggedId);
|
|
1049
|
+
if (node) {
|
|
1050
|
+
node.coordinates = finalCoords;
|
|
1051
|
+
}
|
|
1052
|
+
return [...nodes];
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
this.#isNodeDragging.set(false);
|
|
1056
|
+
this.#draggedNodeId.set(null);
|
|
1057
|
+
this.#dragOffset.set(null);
|
|
1058
|
+
this.#draggingNodeCoordinates.set(null);
|
|
1059
|
+
}
|
|
1060
|
+
nodeDrag(event) {
|
|
1061
|
+
const draggedId = this.#draggedNodeId();
|
|
1062
|
+
if (!this.#isNodeDragging() || !draggedId)
|
|
1063
|
+
return;
|
|
1064
|
+
const { clientX, clientY } = event;
|
|
1065
|
+
const blueprintRect = this.#selfRef.nativeElement.getBoundingClientRect();
|
|
1066
|
+
const worldX = (clientX - blueprintRect.left - this.panX()) / this.zoomLevel();
|
|
1067
|
+
const worldY = (clientY - blueprintRect.top - this.panY()) / this.zoomLevel();
|
|
1068
|
+
let newX = worldX - this.#dragOffset()[0];
|
|
1069
|
+
let newY = worldY - this.#dragOffset()[1];
|
|
1070
|
+
if (this.snapToGrid() || (event instanceof MouseEvent && event.shiftKey)) {
|
|
1071
|
+
const grid = this.gridSnapSize();
|
|
1072
|
+
newX = Math.round(newX / grid) * grid;
|
|
1073
|
+
newY = Math.round(newY / grid) * grid;
|
|
1074
|
+
}
|
|
1075
|
+
this.#draggingNodeCoordinates.set([newX, newY]);
|
|
1076
|
+
this.#lastMouseX.set(clientX);
|
|
1077
|
+
this.#lastMouseY.set(clientY);
|
|
1078
|
+
}
|
|
1079
|
+
startPortDrag(event, nodeId, portId) {
|
|
1080
|
+
event.stopPropagation();
|
|
1081
|
+
if (this.draggingConnection())
|
|
1082
|
+
this.cancelPortDrag();
|
|
1083
|
+
const node = this.nodes().find((n) => n.id === nodeId);
|
|
1084
|
+
if (node) {
|
|
1085
|
+
this.draggingConnection.set({
|
|
1086
|
+
fromNode: nodeId,
|
|
1087
|
+
fromPort: portId,
|
|
1088
|
+
x2: node.coordinates[0],
|
|
1089
|
+
y2: node.coordinates[1],
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
this.updatePathOnMove(event);
|
|
1093
|
+
}
|
|
1094
|
+
endPortDrag(_, toNodeId, toPortId) {
|
|
1095
|
+
if (!this.draggingConnection())
|
|
1096
|
+
return;
|
|
1097
|
+
const from = this.draggingConnection();
|
|
1098
|
+
if (from.fromNode === toNodeId) {
|
|
1099
|
+
this.cancelPortDrag();
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
const newConnection = {
|
|
1103
|
+
fromNode: from.fromNode,
|
|
1104
|
+
fromPort: from.fromPort,
|
|
1105
|
+
toNode: toNodeId,
|
|
1106
|
+
toPort: toPortId,
|
|
1107
|
+
};
|
|
1108
|
+
this.nodes.update((nodes) => {
|
|
1109
|
+
const fromNode = nodes.find((n) => n.id === newConnection.fromNode);
|
|
1110
|
+
const toNode = nodes.find((n) => n.id === newConnection.toNode);
|
|
1111
|
+
if (fromNode && toNode) {
|
|
1112
|
+
const isDuplicateFrom = fromNode.connections.some((c) => c.fromNode === newConnection.fromNode &&
|
|
1113
|
+
c.fromPort === newConnection.fromPort &&
|
|
1114
|
+
c.toNode === newConnection.toNode &&
|
|
1115
|
+
c.toPort === newConnection.toPort);
|
|
1116
|
+
if (!isDuplicateFrom) {
|
|
1117
|
+
fromNode.connections = [...fromNode.connections, newConnection];
|
|
1118
|
+
}
|
|
1119
|
+
const isDuplicateTo = toNode.connections.some((c) => c.fromNode === newConnection.fromNode &&
|
|
1120
|
+
c.fromPort === newConnection.fromPort &&
|
|
1121
|
+
c.toNode === newConnection.toNode &&
|
|
1122
|
+
c.toPort === newConnection.toPort);
|
|
1123
|
+
if (!isDuplicateTo) {
|
|
1124
|
+
toNode.connections = [...toNode.connections, newConnection];
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
return [...nodes];
|
|
1128
|
+
});
|
|
1129
|
+
this.cancelPortDrag();
|
|
1130
|
+
}
|
|
1131
|
+
cancelPortDrag() {
|
|
1132
|
+
this.draggingConnection.set(null);
|
|
1133
|
+
}
|
|
1134
|
+
updatePathOnMove(event) {
|
|
1135
|
+
if (!this.draggingConnection())
|
|
1136
|
+
return;
|
|
1137
|
+
const blueprintRect = this.#selfRef.nativeElement.getBoundingClientRect();
|
|
1138
|
+
const x2 = (event.clientX - blueprintRect.left - this.panX()) / this.zoomLevel();
|
|
1139
|
+
const y2 = (event.clientY - blueprintRect.top - this.panY()) / this.zoomLevel();
|
|
1140
|
+
this.draggingConnection.update((conn) => (conn ? { ...conn, x2, y2 } : conn));
|
|
1141
|
+
}
|
|
1142
|
+
getNodePortPosition(nodeId, portId) {
|
|
1143
|
+
const node = this.nodes().find((n) => n.id === nodeId);
|
|
1144
|
+
if (!node)
|
|
1145
|
+
return [0, 0];
|
|
1146
|
+
const portEl = this.#selfRef.nativeElement.querySelector(`[data-node-id="${nodeId}"][data-port-id="${portId}"]`);
|
|
1147
|
+
if (!portEl)
|
|
1148
|
+
return node.coordinates;
|
|
1149
|
+
const nodeWrapper = this.#selfRef.nativeElement.querySelector('.nodes-wrapper');
|
|
1150
|
+
const wrapperRect = nodeWrapper.getBoundingClientRect();
|
|
1151
|
+
const portRect = portEl.getBoundingClientRect();
|
|
1152
|
+
const portCenterX = portRect.left + portRect.width / 2;
|
|
1153
|
+
const portCenterY = portRect.top + portRect.height / 2;
|
|
1154
|
+
const worldX = (portCenterX - wrapperRect.left) / this.zoomLevel();
|
|
1155
|
+
const worldY = (portCenterY - wrapperRect.top) / this.zoomLevel();
|
|
1156
|
+
return [worldX, worldY];
|
|
1157
|
+
}
|
|
1158
|
+
startPan(event) {
|
|
1159
|
+
if (this.isLocked())
|
|
1160
|
+
return;
|
|
1161
|
+
if (event.target instanceof HTMLElement && event.target.closest('.node'))
|
|
1162
|
+
return;
|
|
1163
|
+
event.preventDefault();
|
|
1164
|
+
this.#isDragging.set(true);
|
|
1165
|
+
this.#lastMouseX.set(event.clientX);
|
|
1166
|
+
this.#lastMouseY.set(event.clientY);
|
|
1167
|
+
}
|
|
1168
|
+
endPan() {
|
|
1169
|
+
this.#isDragging.set(false);
|
|
1170
|
+
}
|
|
1171
|
+
pan(event) {
|
|
1172
|
+
if (!this.#isDragging() || this.isLocked())
|
|
1173
|
+
return;
|
|
1174
|
+
const dx = event.clientX - this.#lastMouseX();
|
|
1175
|
+
const dy = event.clientY - this.#lastMouseY();
|
|
1176
|
+
this.panX.update((x) => x + dx);
|
|
1177
|
+
this.panY.update((y) => y + dy);
|
|
1178
|
+
this.#lastMouseX.set(event.clientX);
|
|
1179
|
+
this.#lastMouseY.set(event.clientY);
|
|
1180
|
+
}
|
|
1181
|
+
zoom(event) {
|
|
1182
|
+
event.preventDefault();
|
|
1183
|
+
const oldZoom = this.zoomLevel();
|
|
1184
|
+
const newZoom = this.#clamp(oldZoom * (1 - event.deltaY * this.#ZOOM_SPEED), this.#MIN_ZOOM, this.#MAX_ZOOM);
|
|
1185
|
+
const panRatio = newZoom / oldZoom;
|
|
1186
|
+
const newPanX = event.clientX - (event.clientX - this.panX()) * panRatio;
|
|
1187
|
+
const newPanY = event.clientY - (event.clientY - this.panY()) * panRatio;
|
|
1188
|
+
this.zoomLevel.set(newZoom);
|
|
1189
|
+
this.panX.set(newPanX);
|
|
1190
|
+
this.panY.set(newPanY);
|
|
1191
|
+
}
|
|
1192
|
+
handleTouchStart(event) {
|
|
1193
|
+
if (event.target instanceof HTMLElement && event.target.closest('.node'))
|
|
1194
|
+
return;
|
|
1195
|
+
event.preventDefault();
|
|
1196
|
+
if (event.touches.length === 1) {
|
|
1197
|
+
this.#isDragging.set(true);
|
|
1198
|
+
this.#lastMouseX.set(event.touches[0].clientX);
|
|
1199
|
+
this.#lastMouseY.set(event.touches[0].clientY);
|
|
1200
|
+
}
|
|
1201
|
+
else if (event.touches.length === 2) {
|
|
1202
|
+
this.#isDragging.set(false);
|
|
1203
|
+
this.#initialPinchDistance.set(this.#getDistance(event.touches[0], event.touches[1]));
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
handleTouchMove(event) {
|
|
1207
|
+
if (event.touches.length === 1 && this.#isDragging()) {
|
|
1208
|
+
const dx = event.touches[0].clientX - this.#lastMouseX();
|
|
1209
|
+
const dy = event.touches[0].clientY - this.#lastMouseY();
|
|
1210
|
+
this.panX.update((x) => x + dx);
|
|
1211
|
+
this.panY.update((y) => y + dy);
|
|
1212
|
+
this.#lastMouseX.set(event.touches[0].clientX);
|
|
1213
|
+
this.#lastMouseY.set(event.touches[0].clientY);
|
|
1214
|
+
}
|
|
1215
|
+
else if (event.touches.length === 2) {
|
|
1216
|
+
const newPinchDistance = this.#getDistance(event.touches[0], event.touches[1]);
|
|
1217
|
+
const pinchRatio = newPinchDistance / this.#initialPinchDistance();
|
|
1218
|
+
const oldZoom = this.zoomLevel();
|
|
1219
|
+
const newZoom = this.#clamp(oldZoom * pinchRatio, this.#MIN_ZOOM, this.#MAX_ZOOM);
|
|
1220
|
+
const pinchCenterX = (event.touches[0].clientX + event.touches[1].clientX) / 2;
|
|
1221
|
+
const pinchCenterY = (event.touches[0].clientY + event.touches[1].clientY) / 2;
|
|
1222
|
+
const panRatio = newZoom / oldZoom;
|
|
1223
|
+
const newPanX = pinchCenterX - (pinchCenterX - this.panX()) * panRatio;
|
|
1224
|
+
const newPanY = pinchCenterY - (pinchCenterY - this.panY()) * panRatio;
|
|
1225
|
+
this.zoomLevel.set(newZoom);
|
|
1226
|
+
this.panX.set(newPanX);
|
|
1227
|
+
this.panY.set(newPanY);
|
|
1228
|
+
this.#initialPinchDistance.set(newPinchDistance);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
handleTouchEnd() {
|
|
1232
|
+
this.#isDragging.set(false);
|
|
1233
|
+
}
|
|
1234
|
+
closeMidpointDiv() {
|
|
1235
|
+
this.showMidpointDiv.set(false);
|
|
1236
|
+
this.midpointDivPosition.set(null);
|
|
1237
|
+
this.isLocked.set(false);
|
|
1238
|
+
}
|
|
1239
|
+
getDisplayCoordinates(node) {
|
|
1240
|
+
const draggedId = this.#draggedNodeId();
|
|
1241
|
+
const draggingCoords = this.#draggingNodeCoordinates();
|
|
1242
|
+
if (draggedId === node.id && draggingCoords) {
|
|
1243
|
+
return `translate(${draggingCoords[0]}px, ${draggingCoords[1]}px)`;
|
|
1244
|
+
}
|
|
1245
|
+
return `translate(${node.coordinates[0]}px, ${node.coordinates[1]}px)`;
|
|
1246
|
+
}
|
|
1247
|
+
removeConnection() {
|
|
1248
|
+
const conn = this.highlightedConnection();
|
|
1249
|
+
if (!conn)
|
|
1250
|
+
return;
|
|
1251
|
+
this.#connections.update((conns) => conns.filter((c) => !(c.fromNode === conn.fromNode &&
|
|
1252
|
+
c.fromPort === conn.fromPort &&
|
|
1253
|
+
c.toNode === conn.toNode &&
|
|
1254
|
+
c.toPort === conn.toPort)));
|
|
1255
|
+
this.nodes.update((nodes) => nodes.map((n) => ({
|
|
1256
|
+
...n,
|
|
1257
|
+
connections: n.connections.filter((c) => !(c.fromNode === conn.fromNode &&
|
|
1258
|
+
c.fromPort === conn.fromPort &&
|
|
1259
|
+
c.toNode === conn.toNode &&
|
|
1260
|
+
c.toPort === conn.toPort)),
|
|
1261
|
+
})));
|
|
1262
|
+
this.highlightedConnection.set(null);
|
|
1263
|
+
this.closeMidpointDiv();
|
|
1264
|
+
}
|
|
1265
|
+
#removeDuplicatesAndReinitiate() {
|
|
1266
|
+
const nodes = this.nodes();
|
|
1267
|
+
const uniqueNodesMap = new Map();
|
|
1268
|
+
const uniqueNodes = [];
|
|
1269
|
+
for (const node of nodes) {
|
|
1270
|
+
if (!uniqueNodesMap.has(node.id)) {
|
|
1271
|
+
uniqueNodesMap.set(node.id, node);
|
|
1272
|
+
uniqueNodes.push(node);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
const nodesWithUniquePorts = uniqueNodes.map((node) => {
|
|
1276
|
+
const uniqueInputs = new Map();
|
|
1277
|
+
const uniqueOutputs = new Map();
|
|
1278
|
+
const uniqueNodeInputs = node.inputs.filter((port) => {
|
|
1279
|
+
if (!uniqueInputs.has(port.id)) {
|
|
1280
|
+
uniqueInputs.set(port.id, port);
|
|
1281
|
+
return true;
|
|
1282
|
+
}
|
|
1283
|
+
return false;
|
|
1284
|
+
});
|
|
1285
|
+
const uniqueNodeOutputs = node.outputs.filter((port) => {
|
|
1286
|
+
if (!uniqueOutputs.has(port.id)) {
|
|
1287
|
+
uniqueOutputs.set(port.id, port);
|
|
1288
|
+
return true;
|
|
1289
|
+
}
|
|
1290
|
+
return false;
|
|
1291
|
+
});
|
|
1292
|
+
return {
|
|
1293
|
+
...node,
|
|
1294
|
+
inputs: uniqueNodeInputs,
|
|
1295
|
+
outputs: uniqueNodeOutputs,
|
|
1296
|
+
};
|
|
1297
|
+
});
|
|
1298
|
+
const finalNodes = nodesWithUniquePorts.map((node) => {
|
|
1299
|
+
const validConnections = node.connections.filter((conn) => {
|
|
1300
|
+
const fromNodeExists = nodesWithUniquePorts.some((n) => n.id === conn.fromNode);
|
|
1301
|
+
const toNodeExists = nodesWithUniquePorts.some((n) => n.id === conn.toNode);
|
|
1302
|
+
const fromPortExists = nodesWithUniquePorts
|
|
1303
|
+
.find((n) => n.id === conn.fromNode)
|
|
1304
|
+
?.outputs.some((p) => p.id === conn.fromPort);
|
|
1305
|
+
const toPortExists = nodesWithUniquePorts
|
|
1306
|
+
.find((n) => n.id === conn.toNode)
|
|
1307
|
+
?.inputs.some((p) => p.id === conn.toPort);
|
|
1308
|
+
return fromNodeExists && toNodeExists && fromPortExists && toPortExists;
|
|
1309
|
+
});
|
|
1310
|
+
return { ...node, connections: validConnections };
|
|
1311
|
+
});
|
|
1312
|
+
this.nodes.set(finalNodes);
|
|
1313
|
+
}
|
|
1314
|
+
#handleConnectionClick(event) {
|
|
1315
|
+
if (this.highlightedConnection()) {
|
|
1316
|
+
const conn = this.highlightedConnection();
|
|
1317
|
+
const startPos = this.getNodePortPosition(conn.fromNode, conn.fromPort);
|
|
1318
|
+
const endPos = this.getNodePortPosition(conn.toNode, conn.toPort);
|
|
1319
|
+
const midpoint = this.#getBezierMidpoint(startPos, endPos);
|
|
1320
|
+
this.midpointDivPosition.set({ x: midpoint[0], y: midpoint[1] });
|
|
1321
|
+
this.showMidpointDiv.set(true);
|
|
1322
|
+
this.isLocked.set(true);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
#getBezierMidpoint(start, end) {
|
|
1326
|
+
const t = 0.5;
|
|
1327
|
+
const [x1, y1] = start;
|
|
1328
|
+
const [x2, y2] = end;
|
|
1329
|
+
const dx = Math.abs(x1 - x2) * 0.7;
|
|
1330
|
+
const cp1x = x1 + dx;
|
|
1331
|
+
const cp1y = y1;
|
|
1332
|
+
const cp2x = x2 - dx;
|
|
1333
|
+
const cp2y = y2;
|
|
1334
|
+
const mx = Math.pow(1 - t, 3) * x1 +
|
|
1335
|
+
3 * Math.pow(1 - t, 2) * t * cp1x +
|
|
1336
|
+
3 * (1 - t) * Math.pow(t, 2) * cp2x +
|
|
1337
|
+
Math.pow(t, 3) * x2;
|
|
1338
|
+
const my = Math.pow(1 - t, 3) * y1 +
|
|
1339
|
+
3 * Math.pow(1 - t, 2) * t * cp1y +
|
|
1340
|
+
3 * (1 - t) * Math.pow(t, 2) * cp2y +
|
|
1341
|
+
Math.pow(t, 3) * y2;
|
|
1342
|
+
return [mx, my];
|
|
1343
|
+
}
|
|
1344
|
+
#checkConnectionHover(event) {
|
|
1345
|
+
const blueprintRect = this.#selfRef.nativeElement.getBoundingClientRect();
|
|
1346
|
+
const worldX = (event.clientX - blueprintRect.left - this.panX()) / this.zoomLevel();
|
|
1347
|
+
const worldY = (event.clientY - blueprintRect.top - this.panY()) / this.zoomLevel();
|
|
1348
|
+
const cursorPoint = [worldX, worldY];
|
|
1349
|
+
let hoveredConn = null;
|
|
1350
|
+
const tolerance = 10 / this.zoomLevel();
|
|
1351
|
+
for (const conn of this.#connections()) {
|
|
1352
|
+
const startPos = this.getNodePortPosition(conn.fromNode, conn.fromPort);
|
|
1353
|
+
const endPos = this.getNodePortPosition(conn.toNode, conn.toPort);
|
|
1354
|
+
if (this.#isPointNearBezierCurve(cursorPoint, startPos, endPos, tolerance)) {
|
|
1355
|
+
hoveredConn = conn;
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
this.highlightedConnection.set(hoveredConn);
|
|
1360
|
+
}
|
|
1361
|
+
#isPointNearBezierCurve(point, start, end, tolerance = 10) {
|
|
1362
|
+
const [x1, y1] = start;
|
|
1363
|
+
const [x2, y2] = end;
|
|
1364
|
+
const dx = Math.abs(x1 - x2) * 0.7;
|
|
1365
|
+
const cp1x = x1 + dx;
|
|
1366
|
+
const cp1y = y1;
|
|
1367
|
+
const cp2x = x2 - dx;
|
|
1368
|
+
const cp2y = y2;
|
|
1369
|
+
const steps = 20;
|
|
1370
|
+
for (let i = 0; i <= steps; i++) {
|
|
1371
|
+
const t = i / steps;
|
|
1372
|
+
const x = Math.pow(1 - t, 3) * x1 +
|
|
1373
|
+
3 * Math.pow(1 - t, 2) * t * cp1x +
|
|
1374
|
+
3 * (1 - t) * Math.pow(t, 2) * cp2x +
|
|
1375
|
+
Math.pow(t, 3) * x2;
|
|
1376
|
+
const y = Math.pow(1 - t, 3) * y1 +
|
|
1377
|
+
3 * Math.pow(1 - t, 2) * t * cp1y +
|
|
1378
|
+
3 * (1 - t) * Math.pow(t, 2) * cp2y +
|
|
1379
|
+
Math.pow(t, 3) * y2;
|
|
1380
|
+
const distance = Math.sqrt(Math.pow(point[0] - x, 2) + Math.pow(point[1] - y, 2));
|
|
1381
|
+
if (distance < tolerance) {
|
|
1382
|
+
return true;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1387
|
+
#getDistance(touch1, touch2) {
|
|
1388
|
+
const dx = touch1.clientX - touch2.clientX;
|
|
1389
|
+
const dy = touch1.clientY - touch2.clientY;
|
|
1390
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
1391
|
+
}
|
|
1392
|
+
#clamp(value, min, max) {
|
|
1393
|
+
return Math.max(min, Math.min(max, value));
|
|
1394
|
+
}
|
|
1395
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ShipBlueprintComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1396
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: ShipBlueprintComponent, isStandalone: true, selector: "sh-blueprint", inputs: { forceUnique: { classPropertyName: "forceUnique", publicName: "forceUnique", isSignal: true, isRequired: false, transformFunction: null }, autoLayout: { classPropertyName: "autoLayout", publicName: "autoLayout", isSignal: true, isRequired: false, transformFunction: null }, gridSize: { classPropertyName: "gridSize", publicName: "gridSize", isSignal: true, isRequired: false, transformFunction: null }, snapToGrid: { classPropertyName: "snapToGrid", publicName: "snapToGrid", isSignal: true, isRequired: false, transformFunction: null }, gridColor: { classPropertyName: "gridColor", publicName: "gridColor", isSignal: true, isRequired: false, transformFunction: null }, nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { nodes: "nodesChange" }, host: { listeners: { "document:mouseup": "onMouseUp($event)", "document:click": "onClick($event)", "document:keydown.escape": "onEscape($event)", "document:mousemove": "onMouseMove($event)", "document:touchmove": "onTouchMove($event)" } }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["blueprintCanvas"], descendants: true, static: true }], ngImport: i0, template: `
|
|
1397
|
+
<div
|
|
1398
|
+
class="canvas-container"
|
|
1399
|
+
[class.locked]="isLocked()"
|
|
1400
|
+
[class.hovering-connection]="highlightedConnection()"
|
|
1401
|
+
(mousedown)="startPan($event)"
|
|
1402
|
+
(mousemove)="pan($event)"
|
|
1403
|
+
(wheel)="zoom($event)"
|
|
1404
|
+
(touchstart)="handleTouchStart($event)"
|
|
1405
|
+
(touchmove)="handleTouchMove($event)"
|
|
1406
|
+
(touchend)="handleTouchEnd()">
|
|
1407
|
+
<div class="action-panel">
|
|
1408
|
+
<button shButton (click)="applyAutolayout()" class="small">
|
|
1409
|
+
Autolayout
|
|
1410
|
+
<sh-icon>tree-structure</sh-icon>
|
|
1411
|
+
</button>
|
|
1412
|
+
|
|
1413
|
+
<ng-content />
|
|
1414
|
+
</div>
|
|
1415
|
+
|
|
1416
|
+
<canvas #blueprintCanvas></canvas>
|
|
1417
|
+
|
|
1418
|
+
@if (validationErrors()) {
|
|
1419
|
+
<sh-card class="validation-errors type-c">
|
|
1420
|
+
Something went wrong you have
|
|
1421
|
+
<br />
|
|
1422
|
+
duplicate node IDs or port IDs
|
|
1423
|
+
<pre>{{ validationErrors() | json }}</pre>
|
|
1424
|
+
</sh-card>
|
|
1425
|
+
} @else {
|
|
1426
|
+
<div
|
|
1427
|
+
class="nodes-wrapper"
|
|
1428
|
+
[style.transform]="'translate(' + panX() + 'px, ' + panY() + 'px) scale(' + zoomLevel() + ')'"
|
|
1429
|
+
[style.transform-origin]="'0 0'">
|
|
1430
|
+
@for (node of nodes(); track node.id) {
|
|
1431
|
+
<sh-card
|
|
1432
|
+
class="node type-c"
|
|
1433
|
+
[style.transform]="getDisplayCoordinates(node)"
|
|
1434
|
+
(mouseenter)="isHoveringNode.set(true)"
|
|
1435
|
+
(mouseleave)="isHoveringNode.set(false)">
|
|
1436
|
+
<header (mousedown)="startNodeDrag($event, node.id)" (touchstart)="startNodeDrag($event, node.id)">
|
|
1437
|
+
<span class="node-title">{{ node.id }}</span>
|
|
1438
|
+
<sh-icon>list</sh-icon>
|
|
1439
|
+
</header>
|
|
1440
|
+
<div class="ports">
|
|
1441
|
+
<div class="inputs">
|
|
1442
|
+
@for (inputPort of node.inputs; track inputPort.id) {
|
|
1443
|
+
<div class="port-wrap">
|
|
1444
|
+
<div
|
|
1445
|
+
class="port input-port"
|
|
1446
|
+
[attr.data-node-id]="node.id"
|
|
1447
|
+
[attr.data-port-id]="inputPort.id"
|
|
1448
|
+
(click)="endPortDrag($event, node.id, inputPort.id)"></div>
|
|
1449
|
+
<span class="port-name">{{ inputPort.name }}</span>
|
|
1450
|
+
</div>
|
|
1451
|
+
}
|
|
1452
|
+
</div>
|
|
1453
|
+
<div class="outputs">
|
|
1454
|
+
@for (outputPort of node.outputs; track outputPort.id) {
|
|
1455
|
+
<div class="port-wrap">
|
|
1456
|
+
<span class="port-name">{{ outputPort.name }}</span>
|
|
1457
|
+
<div
|
|
1458
|
+
class="port output-port"
|
|
1459
|
+
[attr.data-node-id]="node.id"
|
|
1460
|
+
[attr.data-port-id]="outputPort.id"
|
|
1461
|
+
(click)="startPortDrag($event, node.id, outputPort.id)"></div>
|
|
1462
|
+
</div>
|
|
1463
|
+
}
|
|
1464
|
+
</div>
|
|
1465
|
+
</div>
|
|
1466
|
+
</sh-card>
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
@if (showMidpointDiv()) {
|
|
1470
|
+
<div
|
|
1471
|
+
class="midpoint-div"
|
|
1472
|
+
(click)="removeConnection()"
|
|
1473
|
+
[style.left.px]="midpointDivPosition()?.x"
|
|
1474
|
+
[style.top.px]="midpointDivPosition()?.y">
|
|
1475
|
+
Remove connection
|
|
1476
|
+
<sh-icon>trash</sh-icon>
|
|
1477
|
+
</div>
|
|
1478
|
+
}
|
|
1479
|
+
</div>
|
|
1480
|
+
}
|
|
1481
|
+
</div>
|
|
1482
|
+
`, isInline: true, dependencies: [{ kind: "component", type: ShipCardComponent$1, selector: "sh-card" }, { kind: "component", type: ShipIconComponent$1, selector: "sh-icon" }, { kind: "component", type: ShipButtonComponent$1, selector: "[shButton]" }, { kind: "pipe", type: JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1483
|
+
}
|
|
1484
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ShipBlueprintComponent, decorators: [{
|
|
1485
|
+
type: Component,
|
|
1486
|
+
args: [{
|
|
1487
|
+
selector: 'sh-blueprint',
|
|
1488
|
+
imports: [ShipCardComponent$1, ShipIconComponent$1, JsonPipe, ShipButtonComponent$1],
|
|
1489
|
+
template: `
|
|
1490
|
+
<div
|
|
1491
|
+
class="canvas-container"
|
|
1492
|
+
[class.locked]="isLocked()"
|
|
1493
|
+
[class.hovering-connection]="highlightedConnection()"
|
|
1494
|
+
(mousedown)="startPan($event)"
|
|
1495
|
+
(mousemove)="pan($event)"
|
|
1496
|
+
(wheel)="zoom($event)"
|
|
1497
|
+
(touchstart)="handleTouchStart($event)"
|
|
1498
|
+
(touchmove)="handleTouchMove($event)"
|
|
1499
|
+
(touchend)="handleTouchEnd()">
|
|
1500
|
+
<div class="action-panel">
|
|
1501
|
+
<button shButton (click)="applyAutolayout()" class="small">
|
|
1502
|
+
Autolayout
|
|
1503
|
+
<sh-icon>tree-structure</sh-icon>
|
|
1504
|
+
</button>
|
|
1505
|
+
|
|
1506
|
+
<ng-content />
|
|
1507
|
+
</div>
|
|
1508
|
+
|
|
1509
|
+
<canvas #blueprintCanvas></canvas>
|
|
1510
|
+
|
|
1511
|
+
@if (validationErrors()) {
|
|
1512
|
+
<sh-card class="validation-errors type-c">
|
|
1513
|
+
Something went wrong you have
|
|
1514
|
+
<br />
|
|
1515
|
+
duplicate node IDs or port IDs
|
|
1516
|
+
<pre>{{ validationErrors() | json }}</pre>
|
|
1517
|
+
</sh-card>
|
|
1518
|
+
} @else {
|
|
1519
|
+
<div
|
|
1520
|
+
class="nodes-wrapper"
|
|
1521
|
+
[style.transform]="'translate(' + panX() + 'px, ' + panY() + 'px) scale(' + zoomLevel() + ')'"
|
|
1522
|
+
[style.transform-origin]="'0 0'">
|
|
1523
|
+
@for (node of nodes(); track node.id) {
|
|
1524
|
+
<sh-card
|
|
1525
|
+
class="node type-c"
|
|
1526
|
+
[style.transform]="getDisplayCoordinates(node)"
|
|
1527
|
+
(mouseenter)="isHoveringNode.set(true)"
|
|
1528
|
+
(mouseleave)="isHoveringNode.set(false)">
|
|
1529
|
+
<header (mousedown)="startNodeDrag($event, node.id)" (touchstart)="startNodeDrag($event, node.id)">
|
|
1530
|
+
<span class="node-title">{{ node.id }}</span>
|
|
1531
|
+
<sh-icon>list</sh-icon>
|
|
1532
|
+
</header>
|
|
1533
|
+
<div class="ports">
|
|
1534
|
+
<div class="inputs">
|
|
1535
|
+
@for (inputPort of node.inputs; track inputPort.id) {
|
|
1536
|
+
<div class="port-wrap">
|
|
1537
|
+
<div
|
|
1538
|
+
class="port input-port"
|
|
1539
|
+
[attr.data-node-id]="node.id"
|
|
1540
|
+
[attr.data-port-id]="inputPort.id"
|
|
1541
|
+
(click)="endPortDrag($event, node.id, inputPort.id)"></div>
|
|
1542
|
+
<span class="port-name">{{ inputPort.name }}</span>
|
|
1543
|
+
</div>
|
|
1544
|
+
}
|
|
1545
|
+
</div>
|
|
1546
|
+
<div class="outputs">
|
|
1547
|
+
@for (outputPort of node.outputs; track outputPort.id) {
|
|
1548
|
+
<div class="port-wrap">
|
|
1549
|
+
<span class="port-name">{{ outputPort.name }}</span>
|
|
1550
|
+
<div
|
|
1551
|
+
class="port output-port"
|
|
1552
|
+
[attr.data-node-id]="node.id"
|
|
1553
|
+
[attr.data-port-id]="outputPort.id"
|
|
1554
|
+
(click)="startPortDrag($event, node.id, outputPort.id)"></div>
|
|
1555
|
+
</div>
|
|
1556
|
+
}
|
|
1557
|
+
</div>
|
|
1558
|
+
</div>
|
|
1559
|
+
</sh-card>
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
@if (showMidpointDiv()) {
|
|
1563
|
+
<div
|
|
1564
|
+
class="midpoint-div"
|
|
1565
|
+
(click)="removeConnection()"
|
|
1566
|
+
[style.left.px]="midpointDivPosition()?.x"
|
|
1567
|
+
[style.top.px]="midpointDivPosition()?.y">
|
|
1568
|
+
Remove connection
|
|
1569
|
+
<sh-icon>trash</sh-icon>
|
|
1570
|
+
</div>
|
|
1571
|
+
}
|
|
1572
|
+
</div>
|
|
1573
|
+
}
|
|
1574
|
+
</div>
|
|
1575
|
+
`,
|
|
1576
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1577
|
+
}]
|
|
1578
|
+
}], ctorParameters: () => [], propDecorators: { canvasRef: [{
|
|
1579
|
+
type: ViewChild,
|
|
1580
|
+
args: ['blueprintCanvas', { static: true }]
|
|
1581
|
+
}], onMouseUp: [{
|
|
1582
|
+
type: HostListener,
|
|
1583
|
+
args: ['document:mouseup', ['$event']]
|
|
1584
|
+
}], onClick: [{
|
|
1585
|
+
type: HostListener,
|
|
1586
|
+
args: ['document:click', ['$event']]
|
|
1587
|
+
}], onEscape: [{
|
|
1588
|
+
type: HostListener,
|
|
1589
|
+
args: ['document:keydown.escape', ['$event']]
|
|
1590
|
+
}], onMouseMove: [{
|
|
1591
|
+
type: HostListener,
|
|
1592
|
+
args: ['document:mousemove', ['$event']]
|
|
1593
|
+
}], onTouchMove: [{
|
|
1594
|
+
type: HostListener,
|
|
1595
|
+
args: ['document:touchmove', ['$event']]
|
|
1596
|
+
}] } });
|
|
1597
|
+
|
|
562
1598
|
class ShipButtonGroupComponent {
|
|
563
1599
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ShipButtonGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
564
1600
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.0", type: ShipButtonGroupComponent, isStandalone: true, selector: "sh-button-group", ngImport: i0, template: `
|
|
@@ -1113,7 +2149,7 @@ class ShipPopoverComponent {
|
|
|
1113
2149
|
}
|
|
1114
2150
|
return this.#document.documentElement;
|
|
1115
2151
|
}
|
|
1116
|
-
#alignLeftUnder(triggerRect) {
|
|
2152
|
+
#alignLeftUnder(triggerRect, menuRect) {
|
|
1117
2153
|
const newLeft = triggerRect.left;
|
|
1118
2154
|
const newTop = triggerRect.bottom + BASE_SPACE;
|
|
1119
2155
|
return {
|
|
@@ -1121,7 +2157,7 @@ class ShipPopoverComponent {
|
|
|
1121
2157
|
top: newTop,
|
|
1122
2158
|
};
|
|
1123
2159
|
}
|
|
1124
|
-
#alignTopRight(triggerRect) {
|
|
2160
|
+
#alignTopRight(triggerRect, menuRect) {
|
|
1125
2161
|
const newLeft = triggerRect.right + BASE_SPACE;
|
|
1126
2162
|
const newTop = triggerRect.top;
|
|
1127
2163
|
return {
|
|
@@ -1129,7 +2165,7 @@ class ShipPopoverComponent {
|
|
|
1129
2165
|
top: newTop,
|
|
1130
2166
|
};
|
|
1131
2167
|
}
|
|
1132
|
-
#alignBottomRight(triggerRect) {
|
|
2168
|
+
#alignBottomRight(triggerRect, menuRect) {
|
|
1133
2169
|
const newLeft = triggerRect.right + BASE_SPACE;
|
|
1134
2170
|
const newTop = triggerRect.bottom;
|
|
1135
2171
|
return {
|
|
@@ -1137,9 +2173,9 @@ class ShipPopoverComponent {
|
|
|
1137
2173
|
top: newTop,
|
|
1138
2174
|
};
|
|
1139
2175
|
}
|
|
1140
|
-
#alignLeftOver(triggerRect) {
|
|
2176
|
+
#alignLeftOver(triggerRect, menuRect) {
|
|
1141
2177
|
const newLeft = triggerRect.left;
|
|
1142
|
-
const newTop = triggerRect.bottom - triggerRect.height - BASE_SPACE;
|
|
2178
|
+
const newTop = triggerRect.bottom - triggerRect.height - menuRect.height - BASE_SPACE;
|
|
1143
2179
|
return {
|
|
1144
2180
|
left: newLeft,
|
|
1145
2181
|
top: newTop,
|
|
@@ -1152,7 +2188,7 @@ class ShipPopoverComponent {
|
|
|
1152
2188
|
const tryOrderDefault = [this.#alignLeftUnder, this.#alignLeftOver];
|
|
1153
2189
|
const tryOrder = this.asMultiLayer() ? tryOrderMultiLayer : tryOrderDefault;
|
|
1154
2190
|
for (let i = 0; i < tryOrder.length; i++) {
|
|
1155
|
-
const position = tryOrder[i](triggerRect);
|
|
2191
|
+
const position = tryOrder[i](triggerRect, menuRect);
|
|
1156
2192
|
const outOfBoundsRight = position.left + (menuRect?.width || 0) > window.innerWidth;
|
|
1157
2193
|
const outOfBoundsBottom = position.top + (menuRect?.height || 0) > window.innerHeight;
|
|
1158
2194
|
if (!outOfBoundsRight && !outOfBoundsBottom) {
|
|
@@ -1163,7 +2199,7 @@ class ShipPopoverComponent {
|
|
|
1163
2199
|
return;
|
|
1164
2200
|
}
|
|
1165
2201
|
}
|
|
1166
|
-
const fallbackPosition = tryOrder[0](triggerRect);
|
|
2202
|
+
const fallbackPosition = tryOrder[0](triggerRect, menuRect);
|
|
1167
2203
|
this.menuStyle.set({
|
|
1168
2204
|
left: fallbackPosition.left + 'px',
|
|
1169
2205
|
top: fallbackPosition.top + 'px',
|
|
@@ -5696,5 +6732,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImpor
|
|
|
5696
6732
|
* Generated bundle index. Do not edit.
|
|
5697
6733
|
*/
|
|
5698
6734
|
|
|
5699
|
-
export { GridSortableDirective, SHIP_CONFIG, ShipAlertComponent, ShipAlertContainerComponent, ShipAlertModule, ShipAlertService, ShipButtonComponent, ShipButtonGroupComponent, ShipCardComponent, ShipCheckboxComponent, ShipChipComponent, ShipColorPickerComponent, ShipDatepickerComponent, ShipDatepickerInputComponent, ShipDaterangeInputComponent, ShipDialogComponent, ShipDialogService, ShipDividerComponent, ShipEventCardComponent, ShipFileDragDropDirective, ShipFileUploadComponent, ShipFormFieldComponent, ShipIconComponent, ShipListComponent, ShipMenuComponent, ShipPopoverComponent, ShipPreventWheelDirective, ShipProgressBarComponent, ShipRadioComponent, ShipRangeSliderComponent, ShipResizeDirective, ShipSelectComponent, ShipSidenavComponent, ShipSortDirective, ShipSortableComponent, ShipSortableDirective, ShipSpinnerComponent, ShipStepperComponent, ShipStickyColumnsDirective, ShipTableComponent, ShipTabsComponent, ShipToggleCardComponent, ShipToggleComponent, ShipTooltipComponent, ShipTooltipDirective, ShipTooltipWrapper, ShipVirtualScrollComponent, moveIndex, watchHostClass };
|
|
6735
|
+
export { GridSortableDirective, SHIP_CONFIG, ShipAlertComponent, ShipAlertContainerComponent, ShipAlertModule, ShipAlertService, ShipBlueprintComponent, ShipButtonComponent, ShipButtonGroupComponent, ShipCardComponent, ShipCheckboxComponent, ShipChipComponent, ShipColorPickerComponent, ShipDatepickerComponent, ShipDatepickerInputComponent, ShipDaterangeInputComponent, ShipDialogComponent, ShipDialogService, ShipDividerComponent, ShipEventCardComponent, ShipFileDragDropDirective, ShipFileUploadComponent, ShipFormFieldComponent, ShipIconComponent, ShipListComponent, ShipMenuComponent, ShipPopoverComponent, ShipPreventWheelDirective, ShipProgressBarComponent, ShipRadioComponent, ShipRangeSliderComponent, ShipResizeDirective, ShipSelectComponent, ShipSidenavComponent, ShipSortDirective, ShipSortableComponent, ShipSortableDirective, ShipSpinnerComponent, ShipStepperComponent, ShipStickyColumnsDirective, ShipTableComponent, ShipTabsComponent, ShipToggleCardComponent, ShipToggleComponent, ShipTooltipComponent, ShipTooltipDirective, ShipTooltipWrapper, ShipVirtualScrollComponent, TEST_NODES, moveIndex, watchHostClass };
|
|
5700
6736
|
//# sourceMappingURL=ship-ui-core.mjs.map
|