@ship-ui/core 0.15.18 → 0.15.22

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