@yuuvis/client-framework 3.5.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,8 +6,8 @@ import * as i1$2 from '@angular/material/icon';
6
6
  import { MatIconModule } from '@angular/material/icon';
7
7
  import * as i2 from '@angular/material/tooltip';
8
8
  import { MatTooltipModule } from '@angular/material/tooltip';
9
- import { SearchService, BaseObjectTypeField, Operator, TranslatePipe, SystemService, DmsService, EventService, TranslateService, RelationshipTypeField, YuvEventType, SystemType, DmsObject } from '@yuuvis/client-core';
10
- import { DialogComponent, BusyOverlayDirective, ConfirmService, LayoutSettingsService } from '@yuuvis/client-framework/common';
9
+ import { SearchService, BaseObjectTypeField, Operator, TranslatePipe, SystemService, DmsService, EventService, TranslateService, RelationshipTypeField, YuvEventType, SystemType, DmsObject, LocalizationService } from '@yuuvis/client-core';
10
+ import { DialogComponent, BusyOverlayDirective, ConfirmService, ThemeService } from '@yuuvis/client-framework/common';
11
11
  import { YmtIconButtonDirective, YmtButtonDirective } from '@yuuvis/material';
12
12
  import * as i3 from '@yuuvis/material/panes';
13
13
  import { YmtPanesModule } from '@yuuvis/material/panes';
@@ -522,14 +522,15 @@ class ObjectRelationshipGraphComponent {
522
522
  constructor() {
523
523
  this.#dialog = inject(MatDialog);
524
524
  this.#system = inject(SystemService);
525
- this.#layoutSettings = inject(LayoutSettingsService);
525
+ this.#localization = inject(LocalizationService);
526
+ this.#theme = inject(ThemeService);
526
527
  this.#objectRelationshipService = inject(ObjectRelationshipService);
527
528
  this.#snack = inject(SnackBarService);
528
- this.translate = inject(TranslateService);
529
+ this.#translate = inject(TranslateService);
529
530
  this.container = viewChild.required('graphContainer');
530
531
  this.#mode = computed(() => {
531
- const m = this.#layoutSettings.mode();
532
- switch (m) {
532
+ const mode = this.#theme.mode();
533
+ switch (mode) {
533
534
  case 'dark': {
534
535
  return 'dark';
535
536
  }
@@ -546,7 +547,7 @@ class ObjectRelationshipGraphComponent {
546
547
  const rel = this.#relations();
547
548
  if (!rel)
548
549
  return;
549
- this.#_updateGraph(rel.originId, rel.relations, rel.objects);
550
+ this.#updateGraph(rel.originId, rel.relations, rel.objects);
550
551
  }, ...(ngDevMode ? [{ debugName: "#relationsEffect" }] : /* istanbul ignore next */ []));
551
552
  /**
552
553
  * Configuration for the relations component.
@@ -559,6 +560,8 @@ class ObjectRelationshipGraphComponent {
559
560
  this.#relationQA = {};
560
561
  this.#nodes = new DataSet([]);
561
562
  this.#edges = new DataSet([]);
563
+ // tracks what each node's expansion added, so a second double-click can collapse it again
564
+ this.#expansions = new Map();
562
565
  this.busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
563
566
  this.selectedRelation = signal(null, ...(ngDevMode ? [{ debugName: "selectedRelation" }] : /* istanbul ignore next */ []));
564
567
  this.#selectedRelationEffect = effect(() => {
@@ -566,10 +569,10 @@ class ObjectRelationshipGraphComponent {
566
569
  }, ...(ngDevMode ? [{ debugName: "#selectedRelationEffect" }] : /* istanbul ignore next */ []));
567
570
  this.selectedObject = signal(null, ...(ngDevMode ? [{ debugName: "selectedObject" }] : /* istanbul ignore next */ []));
568
571
  this.#selectedObjectEffect = effect(() => {
569
- const so = this.selectedObject();
570
- if (so)
572
+ const selectedObj = this.selectedObject();
573
+ if (selectedObj)
571
574
  this.selectedRelation.set(null);
572
- this.objectSelected.emit(so);
575
+ this.objectSelected.emit(selectedObj);
573
576
  }, ...(ngDevMode ? [{ debugName: "#selectedObjectEffect" }] : /* istanbul ignore next */ []));
574
577
  this.detailsNode = computed(() => {
575
578
  const object = this.selectedObject();
@@ -579,9 +582,11 @@ class ObjectRelationshipGraphComponent {
579
582
  }
580
583
  #dialog;
581
584
  #system;
582
- #layoutSettings;
585
+ #localization;
586
+ #theme;
583
587
  #objectRelationshipService;
584
588
  #snack;
589
+ #translate;
585
590
  #mode;
586
591
  #relations;
587
592
  #relationsEffect;
@@ -590,10 +595,62 @@ class ObjectRelationshipGraphComponent {
590
595
  #relationQA;
591
596
  #nodes;
592
597
  #edges;
598
+ // tracks what each node's expansion added, so a second double-click can collapse it again
599
+ #expansions;
593
600
  #selectedRelationEffect;
594
601
  #selectedObjectEffect;
602
+ // #region: Lifecycle Hooks */
603
+ ngOnDestroy() {
604
+ if (this.#network) {
605
+ this.#network.destroy();
606
+ }
607
+ }
608
+ ngAfterViewInit() {
609
+ const options = {
610
+ physics: {
611
+ enabled: true,
612
+ solver: 'forceAtlas2Based',
613
+ forceAtlas2Based: {
614
+ // gravitationalConstant: -50,
615
+ // centralGravity: 0.01,
616
+ springLength: 200
617
+ }
618
+ },
619
+ nodes: {
620
+ shape: 'dot',
621
+ size: 20,
622
+ font: { size: 12 }
623
+ }
624
+ };
625
+ this.#network = new Network(this.container().nativeElement, {
626
+ nodes: this.#nodes,
627
+ edges: this.#edges
628
+ }, options);
629
+ this.#network.on('selectNode', (params) => this.#setSelection(params));
630
+ this.#network.on('deselectNode', (params) => this.#setSelection(params));
631
+ this.#network.on('dragEnd', (params) => this.#setSelection(params));
632
+ this.#network.on('selectEdge', (params) => this.#setSelection(params));
633
+ this.#network.on('deselectEdge', (params) => this.#setSelection(params));
634
+ this.#network.on('click', (params) => this.#setSelection(params));
635
+ this.#network.on('doubleClick', (params) => {
636
+ const nodeId = params.nodes[0];
637
+ if (!nodeId)
638
+ return;
639
+ // the root node never collapses - a double-click on it just re-expands
640
+ const isRoot = nodeId === this.#relations()?.originId;
641
+ if (!isRoot && this.#expansions.has(nodeId)) {
642
+ this.#collapse(nodeId);
643
+ }
644
+ else {
645
+ this.#expand(nodeId);
646
+ }
647
+ });
648
+ }
649
+ // #endregion
595
650
  download() {
596
651
  const canvas = this.container().nativeElement.querySelector('canvas');
652
+ if (!canvas)
653
+ return;
597
654
  const image = canvas.toDataURL('image/png');
598
655
  const link = document.createElement('a');
599
656
  link.href = image;
@@ -611,7 +668,7 @@ class ObjectRelationshipGraphComponent {
611
668
  }
612
669
  })
613
670
  .afterClosed()
614
- .subscribe((result) => {
671
+ .subscribe(() => {
615
672
  this.expandSelected();
616
673
  });
617
674
  }
@@ -624,7 +681,8 @@ class ObjectRelationshipGraphComponent {
624
681
  const sourceNodeId = deletedEdge.from;
625
682
  const targetNodeId = deletedEdge.to;
626
683
  this.#edges.remove(this.selectedRelation().id);
627
- // check if the source/target node of the deleted edge has other connections to another node in the current graph
684
+ // check if the source/target node of the deleted edge has other connections to
685
+ // another node in the current graph
628
686
  const otherEdgesForSource = this.#edges
629
687
  .get()
630
688
  .filter((edge) => edge.from === sourceNodeId || edge.to === sourceNodeId);
@@ -639,7 +697,7 @@ class ObjectRelationshipGraphComponent {
639
697
  }
640
698
  }
641
699
  else {
642
- this.#snack.danger(this.translate.instant('yuv.object-relationship.delete-relation.error-message'));
700
+ this.#snack.danger(this.#translate.instant('yuv.object-relationship.delete-relation.error-message'));
643
701
  }
644
702
  });
645
703
  }
@@ -654,7 +712,11 @@ class ObjectRelationshipGraphComponent {
654
712
  this.busy.set(true);
655
713
  this.#objectRelationshipService.fetchRelations(nodeId).subscribe({
656
714
  next: (res) => {
657
- this.#_updateGraph(nodeId, res.relations, res.objects);
715
+ const added = this.#updateGraph(nodeId, res.relations, res.objects);
716
+ // record the expansion so it can be toggled closed again - but never make the root collapsible
717
+ if (nodeId !== this.#relations()?.originId) {
718
+ this.#expansions.set(nodeId, added);
719
+ }
658
720
  this.busy.set(false);
659
721
  },
660
722
  error: (err) => {
@@ -664,7 +726,24 @@ class ObjectRelationshipGraphComponent {
664
726
  });
665
727
  }
666
728
  }
667
- #_updateGraph(originId, relations, objects) {
729
+ // remove the nodes/edges that this node's expansion introduced (and any expanded sub-trees),
730
+ // keeping nodes that are still connected to the rest of the graph
731
+ #collapse(nodeId) {
732
+ const expansion = this.#expansions.get(nodeId);
733
+ if (!expansion)
734
+ return;
735
+ this.#expansions.delete(nodeId);
736
+ this.#edges.remove(expansion.edgeIds);
737
+ expansion.nodeIds.forEach((id) => {
738
+ // collapse the sub-tree first so its edges are gone before the orphan check
739
+ if (this.#expansions.has(id))
740
+ this.#collapse(id);
741
+ const stillConnected = this.#edges.get().some((edge) => edge.from === id || edge.to === id);
742
+ if (!stillConnected)
743
+ this.#nodes.remove(id);
744
+ });
745
+ }
746
+ #updateGraph(originId, relations, objects) {
668
747
  objects.items.forEach((item) => {
669
748
  const id = item.fields.get(BaseObjectTypeField.OBJECT_ID);
670
749
  this.#objectQA[id] = new DmsObject(item);
@@ -677,7 +756,7 @@ class ObjectRelationshipGraphComponent {
677
756
  const incoming = relations.items.filter((item) => item.fields.get(RelationshipTypeField.TARGET_ID) === originId);
678
757
  const outgoing = relations.items.filter((item) => item.fields.get(RelationshipTypeField.SOURCE_ID) === originId);
679
758
  const rootNode = this.#toNode(originId);
680
- const _nodes = originId === this.#relations()?.originId && this.#nodes.get([originId]).length === 0
759
+ const _nodes = rootNode && originId === this.#relations()?.originId && this.#nodes.get([originId]).length === 0
681
760
  ? [
682
761
  {
683
762
  ...rootNode,
@@ -693,52 +772,57 @@ class ObjectRelationshipGraphComponent {
693
772
  // add nodes and edges from search result
694
773
  outgoing.forEach((item) => {
695
774
  const relatedObjectId = item.fields.get(RelationshipTypeField.TARGET_ID);
696
- const n = this.#toNode(relatedObjectId);
697
- const e = this.#toEdge(item, originId, relatedObjectId, 'to');
698
- if (n) {
775
+ const node = this.#toNode(relatedObjectId);
776
+ const edge = this.#toEdge(item, originId, relatedObjectId, 'to');
777
+ if (node) {
699
778
  // check if the node is already in the rendered graph
700
- const nodeExists = _nodes.some((node) => node.id === n.id) || this.#nodes.get([n.id]).length > 0;
779
+ const nodeExists = _nodes.some((existing) => existing.id === node.id) || this.#nodes.get([node.id]).length > 0;
701
780
  if (!nodeExists)
702
- _nodes.push(n);
781
+ _nodes.push(node);
703
782
  // check if edge already exists
704
- const edgeExists = _edges.some((edge) => edge.id === e.id) || this.#edges.get([e.id]).length > 0;
783
+ const edgeExists = _edges.some((existing) => existing.id === edge.id) || this.#edges.get([edge.id]).length > 0;
705
784
  if (!edgeExists)
706
- _edges.push(e);
785
+ _edges.push(edge);
707
786
  }
708
787
  });
709
788
  incoming.forEach((item) => {
710
789
  const relatedObjectId = item.fields.get(RelationshipTypeField.SOURCE_ID);
711
- const n = this.#toNode(relatedObjectId);
712
- const e = this.#toEdge(item, relatedObjectId, originId, 'from');
713
- if (n) {
790
+ const toNode = this.#toNode(relatedObjectId);
791
+ const toEdge = this.#toEdge(item, relatedObjectId, originId, 'from');
792
+ if (toNode) {
714
793
  // check if the node is already in the rendered graph
715
- const nodeExists = _nodes.some((node) => node.id === n.id) || this.#nodes.get([n.id]).length > 0;
794
+ const nodeExists = _nodes.some((node) => node.id === toNode.id) || this.#nodes.get([toNode.id]).length > 0;
716
795
  if (!nodeExists)
717
- _nodes.push(n);
796
+ _nodes.push(toNode);
718
797
  // check if edge already exists
719
- const edgeExists = _edges.some((edge) => edge.id === e.id) || this.#edges.get([e.id]).length > 0;
798
+ const edgeExists = _edges.some((edge) => edge.id === toEdge.id) || this.#edges.get([toEdge.id]).length > 0;
720
799
  if (!edgeExists)
721
- _edges.push(e);
800
+ _edges.push(toEdge);
722
801
  }
723
802
  });
724
803
  this.#nodes.add(_nodes);
725
804
  this.#edges.add(_edges);
805
+ // report what was added so the caller can later collapse it - never the root node
806
+ const rootId = this.#relations()?.originId;
807
+ return {
808
+ nodeIds: _nodes.map((node) => node.id).filter((id) => id !== rootId),
809
+ edgeIds: _edges.map((edge) => edge.id)
810
+ };
726
811
  }
727
- #getNodeConfig(o) {
812
+ #getNodeConfig(dmsObject) {
728
813
  const cfg = this.config();
729
- if (!o)
814
+ if (!dmsObject)
730
815
  return undefined;
731
- const objectTypeIds = [...o.sots, o.objectTypeId];
732
- return [cfg.rootNode, ...cfg.nodes].find((n) => objectTypeIds.includes(n.objectType));
816
+ const objectTypeIds = [...dmsObject.sots, dmsObject.objectTypeId];
817
+ return [cfg.rootNode, ...cfg.nodes].find((node) => objectTypeIds.includes(node.objectType));
733
818
  }
734
819
  // convert search result item to vis node
735
820
  // will return NULL if the item object type is not configured in the component config
736
821
  #toNode(objectId) {
737
- const cfg = this.config();
738
- const o = this.#objectQA[objectId];
739
- if (!o)
822
+ const dmsObject = this.#objectQA[objectId];
823
+ if (!dmsObject)
740
824
  return null;
741
- const cfgNode = this.#getNodeConfig(o);
825
+ const cfgNode = this.#getNodeConfig(dmsObject);
742
826
  if (!cfgNode)
743
827
  return null;
744
828
  return {
@@ -746,7 +830,7 @@ class ObjectRelationshipGraphComponent {
746
830
  objectType: cfgNode.objectType,
747
831
  color: this.#getNodeColor(),
748
832
  id: objectId,
749
- label: this.#maxLength(o.data[cfgNode.labelField], 30),
833
+ label: this.#maxLength(dmsObject.data[cfgNode.labelField], 30),
750
834
  icon: {
751
835
  face: 'Material Symbols Sharp',
752
836
  code: cfgNode.icon,
@@ -759,7 +843,7 @@ class ObjectRelationshipGraphComponent {
759
843
  };
760
844
  }
761
845
  #toEdge(item, from, to, direction) {
762
- const label = this.#system.getLocalizedLabel(item.fields.get(BaseObjectTypeField.OBJECT_TYPE_ID)) ||
846
+ const label = this.#localization.getLocalizedLabel(item.fields.get(BaseObjectTypeField.OBJECT_TYPE_ID)) ||
763
847
  item.fields.get(BaseObjectTypeField.OBJECT_TYPE_ID);
764
848
  return {
765
849
  ...EDGE_SETTINGS,
@@ -836,48 +920,12 @@ class ObjectRelationshipGraphComponent {
836
920
  else
837
921
  return s;
838
922
  }
839
- ngAfterViewInit() {
840
- const options = {
841
- physics: {
842
- enabled: true,
843
- solver: 'forceAtlas2Based',
844
- forceAtlas2Based: {
845
- // gravitationalConstant: -50,
846
- // centralGravity: 0.01,
847
- springLength: 200
848
- }
849
- },
850
- nodes: {
851
- shape: 'dot',
852
- size: 20,
853
- font: { size: 12 }
854
- }
855
- };
856
- this.#network = new Network(this.container().nativeElement, {
857
- nodes: this.#nodes,
858
- edges: this.#edges
859
- }, options);
860
- this.#network.on('selectNode', (params) => this.#setSelection(params));
861
- this.#network.on('deselectNode', (params) => this.#setSelection(params));
862
- this.#network.on('dragEnd', (params) => this.#setSelection(params));
863
- this.#network.on('selectEdge', (params) => this.#setSelection(params));
864
- this.#network.on('deselectEdge', (params) => this.#setSelection(params));
865
- this.#network.on('click', (params) => this.#setSelection(params));
866
- this.#network.on('doubleClick', (params) => {
867
- this.#expand(params.nodes[0]);
868
- });
869
- }
870
923
  #setSelection(params) {
871
924
  this.selectedObject.set(params.nodes.length === 1 ? this.#objectQA[params.nodes[0]] : null);
872
925
  this.selectedRelation.set(params.edges.length === 1 && params.nodes.length === 0 ? this.#relationQA[params.edges[0]] : null);
873
926
  }
874
- ngOnDestroy() {
875
- if (this.#network) {
876
- this.#network.destroy();
877
- }
878
- }
879
927
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ObjectRelationshipGraphComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
880
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: ObjectRelationshipGraphComponent, isStandalone: true, selector: "yuv-object-relationship-graph", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { objectSelected: "objectSelected", relationSelected: "relationSelected" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["graphContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"relations\" [yuvBusyOverlay]=\"busy()\">\n <div class=\"toolbar\">\n <button ymtIconButton (click)=\"download()\" [matTooltip]=\"'yuv.object-relationship.download.tooltip' | translate\">\n <mat-icon>download</mat-icon>\n </button>\n @if (selectedRelation()) {\n <button ymtIconButton (click)=\"deleteRelationship()\" [matTooltip]=\"'yuv.object-relationship.delete-relation.tooltip' | translate\">\n <mat-icon>delete</mat-icon>\n </button>\n }\n </div>\n <div class=\"graph-container\" #graphContainer></div>\n\n @let dn = detailsNode();\n @if (dn) {\n <div class=\"selected\">\n <yuv-node-summary [object]=\"dn.object\" [nodeConfig]=\"dn.config\" [actions]=\"additionalSummaryActions\"></yuv-node-summary>\n </div>\n }\n</div>\n\n<ng-template #additionalSummaryActions>\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n [matTooltip]=\"'yuv.object-relationship.selected.add-relationship.tooltip' | translate\"\n (click)=\"addRelationship()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n <button ymt-icon-button icon-button-size=\"small\" [matTooltip]=\"'yuv.object-relationship.selected.expand.tooltip' | translate\" (click)=\"expandSelected()\">\n <mat-icon>open_with</mat-icon>\n </button>\n</ng-template>\n", styles: [":host{--_container-background-color: var(--container-background-color, var(--ymt-surface));--_node-background-color: var(--node-background-color, var(--ymt-surface-container));--_node-border-color: var(--node-border-color, var(--ymt-outline));--_node-font-color: var(--node-font-color, var(--ymt-text-color));--_node-background-color-highlight: var(--node-background-color-highlight, var(--ymt-primary-container));--_node-color-highlight: var(--node-color-highlight, var(--ymt-on-primary-container));--_edge-font-color: var(--edge-font-color, var(--ymt-text-color-subtle));--_edge-color: var(--edge-color, var(--ymt-outline));--_edge-color-highlight: var(--edge-color-highlight, var(--ymt-primary));--_edge-color-hover: var(--edge-color-hover, var(--ymt-primary))}:host .relations{position:relative;display:block;height:100%}:host .relations .toolbar{position:absolute;inset-block-start:var(--ymt-spacing-2xs);inset-inline-start:var(--ymt-spacing-s);z-index:10}:host .relations .graph-container{background-color:var(--_container-background-color);width:100%;height:100%}:host .relations .selected{position:absolute;inset-inline-end:var(--ymt-spacing-m);inset-block-start:var(--ymt-spacing-m);inset-block-end:var(--ymt-spacing-m);padding:var(--ymt-spacing-s) var(--ymt-spacing-m);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);background-color:rgb(from var(--ymt-surface) r g b/.8);border:1px solid var(--ymt-outline-variant);border-radius:var(--ymt-corner-s);max-width:33%;z-index:10;min-width:200px}:host .relations .selected yuv-node-summary{height:100%}\n"], dependencies: [{ kind: "component", type: NodeSummaryComponent, selector: "yuv-node-summary", inputs: ["object", "nodeConfig", "actions"] }, { kind: "directive", type: BusyOverlayDirective, selector: "[yuvBusyOverlay]", inputs: ["yuvBusyOverlay", "yuvBusyOverlayConfig", "yuvBusyError"], outputs: ["yuvBusyErrorDismiss"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
928
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: ObjectRelationshipGraphComponent, isStandalone: true, selector: "yuv-object-relationship-graph", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { objectSelected: "objectSelected", relationSelected: "relationSelected" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["graphContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"relations\" [yuvBusyOverlay]=\"busy()\">\n <div class=\"toolbar\">\n <button ymtIconButton (click)=\"download()\" [matTooltip]=\"'yuv.object-relationship.download.tooltip' | translate\">\n <mat-icon>download</mat-icon>\n </button>\n @if (selectedRelation()) {\n <button\n ymtIconButton\n (click)=\"deleteRelationship()\"\n [matTooltip]=\"'yuv.object-relationship.delete-relation.tooltip' | translate\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n }\n </div>\n <div class=\"graph-container\" #graphContainer></div>\n\n @let dn = detailsNode();\n @if (dn) {\n <div class=\"selected\">\n <yuv-node-summary [object]=\"dn.object\" [nodeConfig]=\"dn.config\" [actions]=\"additionalSummaryActions\" />\n </div>\n }\n</div>\n\n<ng-template #additionalSummaryActions>\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n [matTooltip]=\"'yuv.object-relationship.selected.add-relationship.tooltip' | translate\"\n (click)=\"addRelationship()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n [matTooltip]=\"'yuv.object-relationship.selected.expand.tooltip' | translate\"\n (click)=\"expandSelected()\"\n >\n <mat-icon>open_with</mat-icon>\n </button>\n</ng-template>\n", styles: [":host{--_container-background-color: var(--container-background-color, var(--ymt-surface));--_node-background-color: var(--node-background-color, var(--ymt-surface-container));--_node-border-color: var(--node-border-color, var(--ymt-outline));--_node-font-color: var(--node-font-color, var(--ymt-text-color));--_node-background-color-highlight: var(--node-background-color-highlight, var(--ymt-primary-container));--_node-color-highlight: var(--node-color-highlight, var(--ymt-on-primary-container));--_edge-font-color: var(--edge-font-color, var(--ymt-text-color-subtle));--_edge-color: var(--edge-color, var(--ymt-outline));--_edge-color-highlight: var(--edge-color-highlight, var(--ymt-primary));--_edge-color-hover: var(--edge-color-hover, var(--ymt-primary))}:host .relations{position:relative;display:block;height:100%}:host .relations .toolbar{position:absolute;inset-block-start:var(--ymt-spacing-2xs);inset-inline-start:var(--ymt-spacing-s);z-index:10}:host .relations .graph-container{background-color:var(--_container-background-color);width:100%;height:100%}:host .relations .selected{position:absolute;inset-inline-end:var(--ymt-spacing-m);inset-block-start:var(--ymt-spacing-m);inset-block-end:var(--ymt-spacing-m);padding:var(--ymt-spacing-s) var(--ymt-spacing-m);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);background-color:rgb(from var(--ymt-surface) r g b/.8);border:1px solid var(--ymt-outline-variant);border-radius:var(--ymt-corner-s);max-width:33%;z-index:10;min-width:200px}:host .relations .selected yuv-node-summary{height:100%}\n"], dependencies: [{ kind: "component", type: NodeSummaryComponent, selector: "yuv-node-summary", inputs: ["object", "nodeConfig", "actions"] }, { kind: "directive", type: BusyOverlayDirective, selector: "[yuvBusyOverlay]", inputs: ["yuvBusyOverlay", "yuvBusyOverlayConfig", "yuvBusyError"], outputs: ["yuvBusyErrorDismiss"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
881
929
  }
882
930
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ObjectRelationshipGraphComponent, decorators: [{
883
931
  type: Component,
@@ -888,7 +936,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
888
936
  MatTooltipModule,
889
937
  TranslatePipe,
890
938
  YmtIconButtonDirective
891
- ], template: "<div class=\"relations\" [yuvBusyOverlay]=\"busy()\">\n <div class=\"toolbar\">\n <button ymtIconButton (click)=\"download()\" [matTooltip]=\"'yuv.object-relationship.download.tooltip' | translate\">\n <mat-icon>download</mat-icon>\n </button>\n @if (selectedRelation()) {\n <button ymtIconButton (click)=\"deleteRelationship()\" [matTooltip]=\"'yuv.object-relationship.delete-relation.tooltip' | translate\">\n <mat-icon>delete</mat-icon>\n </button>\n }\n </div>\n <div class=\"graph-container\" #graphContainer></div>\n\n @let dn = detailsNode();\n @if (dn) {\n <div class=\"selected\">\n <yuv-node-summary [object]=\"dn.object\" [nodeConfig]=\"dn.config\" [actions]=\"additionalSummaryActions\"></yuv-node-summary>\n </div>\n }\n</div>\n\n<ng-template #additionalSummaryActions>\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n [matTooltip]=\"'yuv.object-relationship.selected.add-relationship.tooltip' | translate\"\n (click)=\"addRelationship()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n <button ymt-icon-button icon-button-size=\"small\" [matTooltip]=\"'yuv.object-relationship.selected.expand.tooltip' | translate\" (click)=\"expandSelected()\">\n <mat-icon>open_with</mat-icon>\n </button>\n</ng-template>\n", styles: [":host{--_container-background-color: var(--container-background-color, var(--ymt-surface));--_node-background-color: var(--node-background-color, var(--ymt-surface-container));--_node-border-color: var(--node-border-color, var(--ymt-outline));--_node-font-color: var(--node-font-color, var(--ymt-text-color));--_node-background-color-highlight: var(--node-background-color-highlight, var(--ymt-primary-container));--_node-color-highlight: var(--node-color-highlight, var(--ymt-on-primary-container));--_edge-font-color: var(--edge-font-color, var(--ymt-text-color-subtle));--_edge-color: var(--edge-color, var(--ymt-outline));--_edge-color-highlight: var(--edge-color-highlight, var(--ymt-primary));--_edge-color-hover: var(--edge-color-hover, var(--ymt-primary))}:host .relations{position:relative;display:block;height:100%}:host .relations .toolbar{position:absolute;inset-block-start:var(--ymt-spacing-2xs);inset-inline-start:var(--ymt-spacing-s);z-index:10}:host .relations .graph-container{background-color:var(--_container-background-color);width:100%;height:100%}:host .relations .selected{position:absolute;inset-inline-end:var(--ymt-spacing-m);inset-block-start:var(--ymt-spacing-m);inset-block-end:var(--ymt-spacing-m);padding:var(--ymt-spacing-s) var(--ymt-spacing-m);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);background-color:rgb(from var(--ymt-surface) r g b/.8);border:1px solid var(--ymt-outline-variant);border-radius:var(--ymt-corner-s);max-width:33%;z-index:10;min-width:200px}:host .relations .selected yuv-node-summary{height:100%}\n"] }]
939
+ ], template: "<div class=\"relations\" [yuvBusyOverlay]=\"busy()\">\n <div class=\"toolbar\">\n <button ymtIconButton (click)=\"download()\" [matTooltip]=\"'yuv.object-relationship.download.tooltip' | translate\">\n <mat-icon>download</mat-icon>\n </button>\n @if (selectedRelation()) {\n <button\n ymtIconButton\n (click)=\"deleteRelationship()\"\n [matTooltip]=\"'yuv.object-relationship.delete-relation.tooltip' | translate\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n }\n </div>\n <div class=\"graph-container\" #graphContainer></div>\n\n @let dn = detailsNode();\n @if (dn) {\n <div class=\"selected\">\n <yuv-node-summary [object]=\"dn.object\" [nodeConfig]=\"dn.config\" [actions]=\"additionalSummaryActions\" />\n </div>\n }\n</div>\n\n<ng-template #additionalSummaryActions>\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n [matTooltip]=\"'yuv.object-relationship.selected.add-relationship.tooltip' | translate\"\n (click)=\"addRelationship()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n [matTooltip]=\"'yuv.object-relationship.selected.expand.tooltip' | translate\"\n (click)=\"expandSelected()\"\n >\n <mat-icon>open_with</mat-icon>\n </button>\n</ng-template>\n", styles: [":host{--_container-background-color: var(--container-background-color, var(--ymt-surface));--_node-background-color: var(--node-background-color, var(--ymt-surface-container));--_node-border-color: var(--node-border-color, var(--ymt-outline));--_node-font-color: var(--node-font-color, var(--ymt-text-color));--_node-background-color-highlight: var(--node-background-color-highlight, var(--ymt-primary-container));--_node-color-highlight: var(--node-color-highlight, var(--ymt-on-primary-container));--_edge-font-color: var(--edge-font-color, var(--ymt-text-color-subtle));--_edge-color: var(--edge-color, var(--ymt-outline));--_edge-color-highlight: var(--edge-color-highlight, var(--ymt-primary));--_edge-color-hover: var(--edge-color-hover, var(--ymt-primary))}:host .relations{position:relative;display:block;height:100%}:host .relations .toolbar{position:absolute;inset-block-start:var(--ymt-spacing-2xs);inset-inline-start:var(--ymt-spacing-s);z-index:10}:host .relations .graph-container{background-color:var(--_container-background-color);width:100%;height:100%}:host .relations .selected{position:absolute;inset-inline-end:var(--ymt-spacing-m);inset-block-start:var(--ymt-spacing-m);inset-block-end:var(--ymt-spacing-m);padding:var(--ymt-spacing-s) var(--ymt-spacing-m);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);background-color:rgb(from var(--ymt-surface) r g b/.8);border:1px solid var(--ymt-outline-variant);border-radius:var(--ymt-corner-s);max-width:33%;z-index:10;min-width:200px}:host .relations .selected yuv-node-summary{height:100%}\n"] }]
892
940
  }], propDecorators: { container: [{ type: i0.ViewChild, args: ['graphContainer', { isSignal: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], objectSelected: [{ type: i0.Output, args: ["objectSelected"] }], relationSelected: [{ type: i0.Output, args: ["relationSelected"] }] } });
893
941
 
894
942
  class ObjectRelationshipListItemComponent {
@@ -896,7 +944,7 @@ class ObjectRelationshipListItemComponent {
896
944
  this.#router = inject(Router);
897
945
  this.#snack = inject(SnackBarService);
898
946
  this.#objectRelationshipService = inject(ObjectRelationshipService);
899
- this.translate = inject(TranslateService);
947
+ this.#translate = inject(TranslateService);
900
948
  this.link = input.required(...(ngDevMode ? [{ debugName: "link" }] : /* istanbul ignore next */ []));
901
949
  this.direction = input.required(...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
902
950
  this.enableSourceLink = input(false, ...(ngDevMode ? [{ debugName: "enableSourceLink" }] : /* istanbul ignore next */ []));
@@ -905,16 +953,17 @@ class ObjectRelationshipListItemComponent {
905
953
  #router;
906
954
  #snack;
907
955
  #objectRelationshipService;
956
+ #translate;
908
957
  deleteRelationship() {
909
958
  this.#objectRelationshipService.deleteRelationship(this.link().id, true).subscribe((success) => {
910
959
  if (!success) {
911
- this.#snack.danger(this.translate.instant('yuv.object-relationship.delete-relation.error-message'));
960
+ this.#snack.danger(this.#translate.instant('yuv.object-relationship.delete-relation.error-message'));
912
961
  }
913
962
  });
914
963
  }
915
- open(link, skip) {
964
+ async open(link, skip) {
916
965
  if (link && !skip)
917
- this.#router.navigateByUrl(link);
966
+ await this.#router.navigateByUrl(link);
918
967
  }
919
968
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ObjectRelationshipListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
920
969
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: ObjectRelationshipListItemComponent, isStandalone: true, selector: "yuv-object-relationship-list-item", inputs: { link: { classPropertyName: "link", publicName: "link", isSignal: true, isRequired: true, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: true, transformFunction: null }, enableSourceLink: { classPropertyName: "enableSourceLink", publicName: "enableSourceLink", isSignal: true, isRequired: false, transformFunction: null }, enableTargetLink: { classPropertyName: "enableTargetLink", publicName: "enableTargetLink", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@let lnk = link();\n\n<button\n class=\"node source\"\n (click)=\"open(lnk.source.objectLink, !enableSourceLink())\"\n [attr.aria-readonly]=\"!enableSourceLink() || !lnk.source.objectLink\"\n [class.self]=\"direction() === 'out'\"\n>\n @if (lnk.source.icon) {\n <mat-icon>{{ lnk.source.icon }}</mat-icon>\n }\n <span class=\"title\">{{ lnk.source.label }}</span>\n <span class=\"description\">{{ lnk.source.description }}</span>\n</button>\n\n<div class=\"line\" [class.arr]=\"direction() === 'in'\"></div>\n<div class=\"relation\">{{ lnk.type.label }}</div>\n<div class=\"line\" [class.arr]=\"direction() === 'out'\"></div>\n\n<button\n class=\"node target\"\n (click)=\"open(lnk.target.objectLink, !enableTargetLink())\"\n [attr.aria-readonly]=\"!enableTargetLink() || !lnk.target.objectLink\"\n [class.self]=\"direction() === 'in'\"\n>\n @if (lnk.target.icon) {\n <mat-icon>{{ lnk.target.icon }}</mat-icon>\n }\n <span class=\"title\">{{ lnk.target.label }}</span>\n <span class=\"description\">{{ lnk.target.description }}</span>\n</button>\n\n<div class=\"actions\"> \n <button ymtIconButton icon-button-size=\"small\" (click)=\"deleteRelationship()\"><mat-icon>delete</mat-icon></button>\n</div>\n", styles: [":host{margin-block-end:var(--ymt-spacing-xs);display:grid;grid-template-columns:auto 1fr auto 1fr auto auto;align-items:center;grid-template-areas:\"source line relation arrow target actions\";column-gap:var(--ymt-spacing-xs);background-color:var(--ymt-surface);border-radius:var(--ymt-corner-s);padding:var(--ymt-spacing-2xs);border:1px solid var(--ymt-outline-variant)}:host .relation{color:var(--ymt-text-color-subtle);text-align:center;line-height:1.1em}:host>button{grid-area:source;display:grid;grid-template-rows:auto auto;grid-template-columns:auto 1fr;align-items:center;grid-template-areas:\"icon label\" \"icon desc\";border-radius:var(--ymt-corner-xs);border:2px solid var(--ymt-outline-variant);padding:var(--ymt-spacing-xs);font:var(--ymt-font-title-smallest);background-color:transparent;text-align:start}:host>button mat-icon{grid-area:icon;scale:.6;margin-inline-end:var(--ymt-spacing-xs)}:host>button .title{grid-area:label}:host>button .description{grid-area:desc;font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle)}:host>button.self{background-color:var(--ymt-surface-container);color:var(--ymt-on-surface)}:host>button:not(.self):hover{background-color:var(--ymt-hover-background)}:host>button:not([aria-readonly=true]){cursor:pointer}:host>button.target{grid-area:target}:host>button:not([aria-readonly]){cursor:pointer}:host .line{grid-area:line;height:2px;position:relative;background-color:var(--ymt-outline);opacity:.5;min-width:1em}:host .line.arr{grid-area:arrow}:host .line.arr:before{content:\"\";position:absolute;transform:rotate(45deg);transform-origin:top right;width:7px;inset-inline-end:0;translate:2px 1px;height:7px;border:3.5px solid transparent;border-block-start-color:var(--ymt-outline);border-inline-end-color:var(--ymt-outline)}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }] }); }
@@ -927,7 +976,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
927
976
  class ObjectRelationshipListComponent {
928
977
  constructor() {
929
978
  this.#search = inject(SearchService);
930
- this.#system = inject(SystemService);
979
+ this.#localization = inject(LocalizationService);
931
980
  this.#objectRelationshipService = inject(ObjectRelationshipService);
932
981
  /**
933
982
  * Configuration for the relations component.
@@ -945,7 +994,7 @@ class ObjectRelationshipListComponent {
945
994
  this.#objectQA = {};
946
995
  }
947
996
  #search;
948
- #system;
997
+ #localization;
949
998
  #objectRelationshipService;
950
999
  #relations;
951
1000
  #relationsEffect;
@@ -957,8 +1006,8 @@ class ObjectRelationshipListComponent {
957
1006
  const id = item.fields.get(BaseObjectTypeField.OBJECT_ID);
958
1007
  this.#objectQA[id] = new DmsObject(item);
959
1008
  });
960
- this.incomingLinks.set(incoming.map((rel) => this.#map(rel)).filter((l) => l !== undefined));
961
- this.outgoingLinks.set(outgoing.map((rel) => this.#map(rel)).filter((l) => l !== undefined));
1009
+ this.incomingLinks.set(incoming.map((rel) => this.#map(rel)).filter((link) => link !== undefined));
1010
+ this.outgoingLinks.set(outgoing.map((rel) => this.#map(rel)).filter((link) => link !== undefined));
962
1011
  }
963
1012
  #map(relation) {
964
1013
  const sourceObject = this.#objectQA[relation.fields.get(RelationshipTypeField.SOURCE_ID)];
@@ -981,7 +1030,7 @@ class ObjectRelationshipListComponent {
981
1030
  id: relID,
982
1031
  type: {
983
1032
  id: relObjectTypeID,
984
- label: this.#system.getLocalizedLabel(relObjectTypeID) || relObjectTypeID
1033
+ label: this.#localization.getLocalizedLabel(relObjectTypeID) || relObjectTypeID
985
1034
  },
986
1035
  source: {
987
1036
  id: relation.fields.get(RelationshipTypeField.SOURCE_ID),
@@ -1003,7 +1052,7 @@ class ObjectRelationshipListComponent {
1003
1052
  const cfg = this.config();
1004
1053
  if (!object)
1005
1054
  return undefined;
1006
- return cfg.nodes.find((n) => object.data[BaseObjectTypeField.SECONDARY_OBJECT_TYPE_IDS].includes(n.objectType));
1055
+ return cfg.nodes.find((nodes) => object.data[BaseObjectTypeField.SECONDARY_OBJECT_TYPE_IDS].includes(nodes.objectType));
1007
1056
  }
1008
1057
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ObjectRelationshipListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1009
1058
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: ObjectRelationshipListComponent, isStandalone: true, selector: "yuv-object-relationship-list", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@let incoming = incomingLinks();\n@let outgoing = outgoingLinks();\n\n@if (incoming.length > 0) {\n <h3>{{ 'yuv.object-relationship.list.incoming' | translate }}</h3>\n @for (n of incoming; track n.id) {\n <yuv-object-relationship-list-item\n [enableSourceLink]=\"true\"\n [enableTargetLink]=\"false\"\n [link]=\"n\"\n direction=\"in\"\n ></yuv-object-relationship-list-item>\n }\n}\n@if (outgoing.length > 0) {\n <h3>{{ 'yuv.object-relationship.list.outgoing' | translate }}</h3>\n @for (n of outgoing; track n.id) {\n <yuv-object-relationship-list-item\n [enableSourceLink]=\"false\"\n [enableTargetLink]=\"true\"\n [link]=\"n\"\n direction=\"out\"\n ></yuv-object-relationship-list-item>\n }\n}\n", styles: [":host{display:block;padding:var(--ymt-spacing-m)}\n"], dependencies: [{ kind: "component", type: ObjectRelationshipListItemComponent, selector: "yuv-object-relationship-list-item", inputs: ["link", "direction", "enableSourceLink", "enableTargetLink"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
@@ -1024,7 +1073,7 @@ class ObjectRelationshipComponent {
1024
1073
  this.mode = model('graph', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
1025
1074
  this.empty = computed(() => {
1026
1075
  const rel = this.#relations();
1027
- return rel && rel.relations.totalNumItems === 0;
1076
+ return rel?.relations.totalNumItems === 0;
1028
1077
  }, ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
1029
1078
  /**
1030
1079
  * ID of the object to display relations for.