@meshmakers/octo-meshboard 3.3.480 → 3.3.500

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,5 +1,5 @@
1
1
  import { firstValueFrom, from, map, Observable, of, catchError, interval } from 'rxjs';
2
- import { FieldFilterOperatorsDto, CkTypeSelectorService, DeleteStrategiesDto, AssociationModOptionsDto, CkModelService, AttributeSelectorService, GraphQL, GetCkTypeAvailableQueryColumnsDtoGQL, SortOrdersDto, HealthService, TENANT_ID_PROVIDER, AssetRepoService, JobManagementService } from '@meshmakers/octo-services';
2
+ import { FieldFilterOperatorsDto, CkTypeSelectorService, DeleteStrategiesDto, AssociationModOptionsDto, CkModelService, GraphDirectionDto, AttributeSelectorService, GraphQL, GetCkTypeAvailableQueryColumnsDtoGQL, SortOrdersDto, HealthService, TENANT_ID_PROVIDER, AssetRepoService, JobManagementService } from '@meshmakers/octo-services';
3
3
  export { TENANT_ID_PROVIDER } from '@meshmakers/octo-services';
4
4
  import * as i0 from '@angular/core';
5
5
  import { Injectable, inject, EventEmitter, forwardRef, Output, Input, ViewChild, Component, Injector, EnvironmentInjector, ApplicationRef, signal, computed, Directive, makeEnvironmentProviders, provideAppInitializer, InjectionToken, effect } from '@angular/core';
@@ -2586,6 +2586,84 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
2586
2586
  }]
2587
2587
  }], ctorParameters: () => [{ type: i1.Apollo }] });
2588
2588
 
2589
+ const GetAssociationTargetsDocumentDto = gql `
2590
+ query getAssociationTargets($rtId: OctoObjectId!, $ckTypeId: String!, $targetCkTypeId: String!, $roleId: String!, $direction: GraphDirection!, $first: Int, $attributeNames: [String]) {
2591
+ runtime {
2592
+ runtimeEntities(rtId: $rtId, ckId: $ckTypeId, first: 1) {
2593
+ items {
2594
+ associations {
2595
+ targets(
2596
+ ckId: $targetCkTypeId
2597
+ roleId: $roleId
2598
+ direction: $direction
2599
+ first: $first
2600
+ ) {
2601
+ totalCount
2602
+ items {
2603
+ rtId
2604
+ ckTypeId
2605
+ rtWellKnownName
2606
+ attributes(attributeNames: $attributeNames, resolveEnumValuesToNames: true) {
2607
+ items {
2608
+ attributeName
2609
+ value
2610
+ }
2611
+ }
2612
+ }
2613
+ }
2614
+ }
2615
+ }
2616
+ }
2617
+ }
2618
+ }
2619
+ `;
2620
+ class GetAssociationTargetsDtoGQL extends i1.Query {
2621
+ document = GetAssociationTargetsDocumentDto;
2622
+ constructor(apollo) {
2623
+ super(apollo);
2624
+ }
2625
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: GetAssociationTargetsDtoGQL, deps: [{ token: i1.Apollo }], target: i0.ɵɵFactoryTarget.Injectable });
2626
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: GetAssociationTargetsDtoGQL, providedIn: 'root' });
2627
+ }
2628
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: GetAssociationTargetsDtoGQL, decorators: [{
2629
+ type: Injectable,
2630
+ args: [{
2631
+ providedIn: 'root'
2632
+ }]
2633
+ }], ctorParameters: () => [{ type: i1.Apollo }] });
2634
+
2635
+ const GetCkTypeAttributesForMeshboardDocumentDto = gql `
2636
+ query getCkTypeAttributesForMeshboard($ckTypeId: String!, $first: Int) {
2637
+ constructionKit {
2638
+ types(rtCkId: $ckTypeId, first: 1) {
2639
+ items {
2640
+ rtCkTypeId
2641
+ attributes(first: $first) {
2642
+ items {
2643
+ attributeName
2644
+ attributeValueType
2645
+ }
2646
+ }
2647
+ }
2648
+ }
2649
+ }
2650
+ }
2651
+ `;
2652
+ class GetCkTypeAttributesForMeshboardDtoGQL extends i1.Query {
2653
+ document = GetCkTypeAttributesForMeshboardDocumentDto;
2654
+ constructor(apollo) {
2655
+ super(apollo);
2656
+ }
2657
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: GetCkTypeAttributesForMeshboardDtoGQL, deps: [{ token: i1.Apollo }], target: i0.ɵɵFactoryTarget.Injectable });
2658
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: GetCkTypeAttributesForMeshboardDtoGQL, providedIn: 'root' });
2659
+ }
2660
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: GetCkTypeAttributesForMeshboardDtoGQL, decorators: [{
2661
+ type: Injectable,
2662
+ args: [{
2663
+ providedIn: 'root'
2664
+ }]
2665
+ }], ctorParameters: () => [{ type: i1.Apollo }] });
2666
+
2589
2667
  /**
2590
2668
  * Service for resolving MeshBoard variables in filter values.
2591
2669
  * Variables use the syntax $variableName and are replaced at query execution time.
@@ -2734,6 +2812,8 @@ class MeshBoardDataService {
2734
2812
  getCkModelsWithStateGQL = inject(GetCkModelsWithStateDtoGQL);
2735
2813
  getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
2736
2814
  executeRuntimeQueryGQL = inject(ExecuteRuntimeQueryDtoGQL);
2815
+ getAssociationTargetsGQL = inject(GetAssociationTargetsDtoGQL);
2816
+ getCkTypeAttributesGQL = inject(GetCkTypeAttributesForMeshboardDtoGQL);
2737
2817
  apollo = inject(Apollo);
2738
2818
  stateService = inject(MeshBoardStateService);
2739
2819
  variableService = inject(MeshBoardVariableService);
@@ -3050,6 +3130,63 @@ class MeshBoardDataService {
3050
3130
  }
3051
3131
  }
3052
3132
  // ============================================================================
3133
+ // Association Target Queries
3134
+ // ============================================================================
3135
+ /**
3136
+ * Fetches target entities of an association group with their attributes.
3137
+ * Uses the associations.targets() GraphQL endpoint.
3138
+ */
3139
+ fetchAssociationTargets(sourceRtId, sourceCkTypeId, targetCkTypeId, roleId, direction, attributeNames, first) {
3140
+ const graphDirection = direction === 'out' ? GraphDirectionDto.OutboundDto : GraphDirectionDto.InboundDto;
3141
+ return this.getAssociationTargetsGQL.fetch({
3142
+ variables: {
3143
+ rtId: sourceRtId,
3144
+ ckTypeId: sourceCkTypeId,
3145
+ targetCkTypeId,
3146
+ roleId,
3147
+ direction: graphDirection,
3148
+ first: first ?? 100,
3149
+ attributeNames: attributeNames ?? null
3150
+ }
3151
+ }).pipe(map(result => {
3152
+ const entity = result.data?.runtime?.runtimeEntities?.items?.[0];
3153
+ const targets = entity?.associations?.targets?.items ?? [];
3154
+ return targets
3155
+ .filter((t) => t !== null)
3156
+ .map(t => ({
3157
+ rtId: t.rtId,
3158
+ ckTypeId: t.ckTypeId,
3159
+ rtWellKnownName: t.rtWellKnownName ?? undefined,
3160
+ attributes: (t.attributes?.items ?? [])
3161
+ .filter((a) => a !== null && a.attributeName !== null)
3162
+ .map(a => ({
3163
+ attributeName: a.attributeName,
3164
+ value: a.value
3165
+ }))
3166
+ }));
3167
+ }));
3168
+ }
3169
+ /**
3170
+ * Fetches CK type attribute definitions (name + type) for config dialog.
3171
+ */
3172
+ fetchCkTypeAttributes(ckTypeId) {
3173
+ return this.getCkTypeAttributesGQL.fetch({
3174
+ variables: {
3175
+ ckTypeId,
3176
+ first: 200
3177
+ }
3178
+ }).pipe(map(result => {
3179
+ const ckType = result.data?.constructionKit?.types?.items?.[0];
3180
+ const attrs = ckType?.attributes?.items ?? [];
3181
+ return attrs
3182
+ .filter((a) => a !== null)
3183
+ .map(a => ({
3184
+ attributeName: a.attributeName,
3185
+ attributeValueType: a.attributeValueType
3186
+ }));
3187
+ }));
3188
+ }
3189
+ // ============================================================================
3053
3190
  // Private Helper Methods
3054
3191
  // ============================================================================
3055
3192
  /**
@@ -5506,6 +5643,9 @@ class EntityAssociationsWidgetComponent {
5506
5643
  _data = signal(null, ...(ngDevMode ? [{ debugName: "_data" }] : []));
5507
5644
  _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : []));
5508
5645
  _expandedGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "_expandedGroups" }] : []));
5646
+ // Cache for target entity attributes per group (key = roleId_direction)
5647
+ _targetAttributesCache = signal(new Map(), ...(ngDevMode ? [{ debugName: "_targetAttributesCache" }] : []));
5648
+ _loadingTargetGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "_loadingTargetGroups" }] : []));
5509
5649
  // Detail dialog state
5510
5650
  showDetailDialog = false;
5511
5651
  detailEntityRtId = '';
@@ -5545,6 +5685,16 @@ class EntityAssociationsWidgetComponent {
5545
5685
  displayMode = computed(() => {
5546
5686
  return this.config?.displayMode ?? 'expandable';
5547
5687
  }, ...(ngDevMode ? [{ debugName: "displayMode" }] : []));
5688
+ /**
5689
+ * Filtered source entity attributes based on configured entityAttributePaths
5690
+ */
5691
+ filteredEntityAttributes = computed(() => {
5692
+ const data = this._data();
5693
+ const paths = this.config?.entityAttributePaths;
5694
+ if (!data?.attributes || !paths?.length)
5695
+ return [];
5696
+ return data.attributes.filter(a => paths.includes(a.attributeName));
5697
+ }, ...(ngDevMode ? [{ debugName: "filteredEntityAttributes" }] : []));
5548
5698
  groupedAssociations = computed(() => {
5549
5699
  const data = this._data();
5550
5700
  if (!data?.associations)
@@ -5622,6 +5772,8 @@ class EntityAssociationsWidgetComponent {
5622
5772
  }
5623
5773
  else {
5624
5774
  expanded.add(key);
5775
+ // Load target attributes when expanding, if configured
5776
+ this.loadTargetAttributes(group);
5625
5777
  }
5626
5778
  this._expandedGroups.set(expanded);
5627
5779
  // Update the group's expanded state
@@ -5632,6 +5784,18 @@ class EntityAssociationsWidgetComponent {
5632
5784
  return this._expandedGroups().has(key);
5633
5785
  }
5634
5786
  getTargetEntities(group) {
5787
+ const cacheKey = this.getGroupCacheKey(group);
5788
+ const cached = this._targetAttributesCache().get(cacheKey);
5789
+ if (cached) {
5790
+ // Return enriched targets from cache
5791
+ return cached.map(t => ({
5792
+ rtId: t.rtId,
5793
+ ckTypeId: t.ckTypeId,
5794
+ displayName: t.rtWellKnownName || t.rtId,
5795
+ attributes: t.attributes
5796
+ }));
5797
+ }
5798
+ // Fall back to basic association data
5635
5799
  const sourceRtId = this._data()?.rtId;
5636
5800
  return group.associations.map(assoc => {
5637
5801
  const isOutgoing = assoc.originRtId === sourceRtId;
@@ -5640,10 +5804,81 @@ class EntityAssociationsWidgetComponent {
5640
5804
  return {
5641
5805
  rtId,
5642
5806
  ckTypeId,
5643
- displayName: rtId // Could be enhanced with wellKnownName if available
5807
+ displayName: rtId
5644
5808
  };
5645
5809
  });
5646
5810
  }
5811
+ isLoadingTargets(group) {
5812
+ return this._loadingTargetGroups().has(this.getGroupCacheKey(group));
5813
+ }
5814
+ formatAttributeName(name) {
5815
+ // camelCase/PascalCase → "Title Case"
5816
+ return name
5817
+ .replace(/([A-Z])/g, ' $1')
5818
+ .replace(/^./, str => str.toUpperCase())
5819
+ .trim();
5820
+ }
5821
+ formatValue(value) {
5822
+ if (value === null || value === undefined)
5823
+ return '–';
5824
+ if (typeof value === 'boolean')
5825
+ return value ? 'Yes' : 'No';
5826
+ if (typeof value === 'number') {
5827
+ return value.toLocaleString('de-AT', {
5828
+ minimumFractionDigits: value % 1 !== 0 ? 1 : 0,
5829
+ maximumFractionDigits: 2
5830
+ });
5831
+ }
5832
+ if (value instanceof Date)
5833
+ return value.toLocaleDateString('de-AT');
5834
+ if (typeof value === 'string') {
5835
+ // Try to detect ISO date strings
5836
+ if (/^\d{4}-\d{2}-\d{2}T/.test(value)) {
5837
+ const date = new Date(value);
5838
+ if (!isNaN(date.getTime()))
5839
+ return date.toLocaleDateString('de-AT');
5840
+ }
5841
+ return value;
5842
+ }
5843
+ return String(value);
5844
+ }
5845
+ getGroupCacheKey(group) {
5846
+ return `${group.direction}-${group.roleId}-${group.targetType}`;
5847
+ }
5848
+ loadTargetAttributes(group) {
5849
+ const targetAttributePaths = this.config?.targetAttributePaths;
5850
+ if (!targetAttributePaths?.length)
5851
+ return;
5852
+ const cacheKey = this.getGroupCacheKey(group);
5853
+ // Skip if already cached or loading
5854
+ if (this._targetAttributesCache().has(cacheKey) || this._loadingTargetGroups().has(cacheKey))
5855
+ return;
5856
+ const data = this._data();
5857
+ if (!data)
5858
+ return;
5859
+ // Mark as loading
5860
+ const loading = new Set(this._loadingTargetGroups());
5861
+ loading.add(cacheKey);
5862
+ this._loadingTargetGroups.set(loading);
5863
+ // Determine target CK type from the group's associations
5864
+ const sourceRtId = data.rtId;
5865
+ const firstAssoc = group.associations[0];
5866
+ const isOutgoing = firstAssoc.originRtId === sourceRtId;
5867
+ const targetCkTypeId = isOutgoing ? firstAssoc.targetCkTypeId : firstAssoc.originCkTypeId;
5868
+ this.dataService.fetchAssociationTargets(data.rtId, data.ckTypeId, targetCkTypeId, group.roleId, group.direction, targetAttributePaths, group.count).pipe(catchError(err => {
5869
+ console.error('Error loading target attributes:', err);
5870
+ return of([]);
5871
+ })).subscribe(targets => {
5872
+ // Update cache
5873
+ const cache = new Map(this._targetAttributesCache());
5874
+ cache.set(cacheKey, targets);
5875
+ this._targetAttributesCache.set(cache);
5876
+ // Remove from loading
5877
+ const loadingState = new Set(this._loadingTargetGroups());
5878
+ loadingState.delete(cacheKey);
5879
+ this._loadingTargetGroups.set(loadingState);
5880
+ });
5881
+ }
5647
5882
  onTargetClick(target) {
5648
5883
  this.detailEntityRtId = target.rtId;
5649
5884
  this.detailEntityCkTypeId = target.ckTypeId;
@@ -5670,6 +5905,8 @@ class EntityAssociationsWidgetComponent {
5670
5905
  if (dataSource.type === 'runtimeEntity') {
5671
5906
  this._isLoading.set(true);
5672
5907
  this._error.set(null);
5908
+ this._targetAttributesCache.set(new Map());
5909
+ this._loadingTargetGroups.set(new Set());
5673
5910
  this.dataService.fetchEntityWithAssociations(dataSource.rtId, dataSource.ckTypeId)
5674
5911
  .pipe(catchError(err => {
5675
5912
  console.error('Error loading associations data:', err);
@@ -5679,6 +5916,14 @@ class EntityAssociationsWidgetComponent {
5679
5916
  .subscribe(entityData => {
5680
5917
  this._data.set(entityData);
5681
5918
  this._isLoading.set(false);
5919
+ // Re-load target attributes for any currently expanded groups
5920
+ if (entityData && this.config?.targetAttributePaths?.length) {
5921
+ for (const group of this.groupedAssociations()) {
5922
+ if (this.isGroupExpanded(group)) {
5923
+ this.loadTargetAttributes(group);
5924
+ }
5925
+ }
5926
+ }
5682
5927
  });
5683
5928
  }
5684
5929
  }
@@ -5693,11 +5938,11 @@ class EntityAssociationsWidgetComponent {
5693
5938
  .trim();
5694
5939
  }
5695
5940
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EntityAssociationsWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5696
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: EntityAssociationsWidgetComponent, isStandalone: true, selector: "mm-entity-associations-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"associations-widget\" [class.no-data]=\"!data()\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>Loading...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else if (data()) {\n <!-- Entity Info Header -->\n <div class=\"entity-header\">\n <div class=\"entity-box\">\n <span class=\"entity-name\">{{ entityName() }}</span>\n </div>\n <div class=\"entity-details\">\n <span class=\"entity-id\" title=\"{{ entityCkTypeId() }}\">{{ entityRtId() }}</span>\n </div>\n </div>\n\n <!-- Associations List -->\n <div class=\"associations-container\">\n @for (group of groupedAssociations(); track group.roleId + group.direction) {\n <div class=\"association-group\" [class.expanded]=\"isGroupExpanded(group)\">\n <!-- Group Header -->\n <div class=\"group-header\"\n [class.clickable]=\"displayMode() === 'expandable'\"\n (click)=\"toggleGroup(group)\">\n <div class=\"direction-indicator\">\n @if (group.direction === 'out') {\n <kendo-svg-icon [icon]=\"arrowRightIcon\" size=\"small\"></kendo-svg-icon>\n } @else {\n <kendo-svg-icon [icon]=\"arrowLeftIcon\" size=\"small\"></kendo-svg-icon>\n }\n </div>\n <div class=\"group-info\">\n <span class=\"role-name\">{{ group.roleName }}</span>\n <span class=\"target-type\">{{ group.targetType }}</span>\n <span class=\"count-badge\">{{ group.count }}</span>\n </div>\n @if (displayMode() === 'expandable') {\n <div class=\"expand-icon\">\n @if (isGroupExpanded(group)) {\n <kendo-svg-icon [icon]=\"chevronDownIcon\" size=\"small\"></kendo-svg-icon>\n } @else {\n <kendo-svg-icon [icon]=\"chevronRightIcon\" size=\"small\"></kendo-svg-icon>\n }\n </div>\n }\n </div>\n\n <!-- Expanded Target List -->\n @if (displayMode() === 'expandable' && isGroupExpanded(group)) {\n <div class=\"target-list\">\n @for (target of getTargetEntities(group); track target.rtId) {\n <div class=\"target-item\" (click)=\"onTargetClick(target)\">\n <span class=\"target-id\">{{ target.displayName }}</span>\n <span class=\"target-type-hint\">{{ target.ckTypeId | slice:-30 }}</span>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Summary Footer -->\n <div class=\"associations-summary\">\n <kendo-svg-icon [icon]=\"linkIcon\" size=\"small\"></kendo-svg-icon>\n <span>{{ totalAssociations() }} relationship(s)</span>\n </div>\n } @else {\n <mm-widget-not-configured></mm-widget-not-configured>\n }\n</div>\n\n<!-- Entity Detail Dialog -->\n@if (showDetailDialog) {\n <mm-entity-detail-dialog\n [rtId]=\"detailEntityRtId\"\n [ckTypeId]=\"detailEntityCkTypeId\"\n (closed)=\"onDetailDialogClosed()\">\n </mm-entity-detail-dialog>\n}\n", styles: [".associations-widget{display:flex;flex-direction:column;height:100%}.associations-widget.no-data{justify-content:center;align-items:center;color:var(--kendo-color-subtle, #6c757d)}.entity-header{display:flex;align-items:center;gap:12px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.entity-header .entity-box{display:flex;align-items:center;justify-content:center;padding:8px 16px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);border-radius:4px;font-weight:600;font-size:.875rem}.entity-header .entity-details{display:flex;flex-direction:column;gap:2px}.entity-header .entity-id{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);font-family:monospace}.associations-container{flex:1;overflow:auto;padding:8px;display:flex;flex-direction:column;gap:4px}.association-group{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;overflow:hidden}.association-group.expanded .group-header{border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.group-header{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa)}.group-header.clickable{cursor:pointer}.group-header.clickable:hover{background:var(--kendo-color-surface, #ffffff)}.direction-indicator{color:var(--kendo-color-subtle, #6c757d);display:flex;align-items:center;width:20px}.group-info{display:flex;align-items:center;gap:8px;flex:1;font-size:.8125rem}.group-info .role-name{color:var(--kendo-color-subtle, #6c757d);font-style:italic}.group-info .target-type{font-weight:500;color:var(--kendo-color-on-surface, #212529)}.group-info .count-badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);border-radius:10px;font-size:.6875rem;font-weight:600}.expand-icon{color:var(--kendo-color-subtle, #6c757d);display:flex;align-items:center}.target-list{padding:4px 0;max-height:200px;overflow-y:auto}.target-item{display:flex;align-items:center;justify-content:space-between;padding:6px 12px 6px 40px;cursor:pointer;font-size:.8125rem;transition:background .15s ease}.target-item:hover{background:var(--kendo-color-surface-alt, #f8f9fa)}.target-item .target-id{font-family:monospace;color:var(--kendo-color-on-surface, #212529)}.target-item .target-type-hint{font-size:.6875rem;color:var(--kendo-color-subtle, #6c757d);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:150px}.associations-summary{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-top:1px solid var(--kendo-color-border, #dee2e6);font-size:.75rem;color:var(--kendo-color-subtle, #6c757d)}.no-data-message{font-size:.875rem}.loading-indicator{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-subtle, #6c757d)}.error-message{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-error, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i2$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: EntityDetailDialogComponent, selector: "mm-entity-detail-dialog", inputs: ["rtId", "ckTypeId"], outputs: ["closed"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }, { kind: "pipe", type: i1$3.SlicePipe, name: "slice" }] });
5941
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: EntityAssociationsWidgetComponent, isStandalone: true, selector: "mm-entity-associations-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"associations-widget\" [class.no-data]=\"!data()\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>Loading...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else if (data()) {\n <!-- Entity Info Header -->\n <div class=\"entity-header\">\n <div class=\"entity-box\">\n <span class=\"entity-name\">{{ entityName() }}</span>\n </div>\n <div class=\"entity-details\">\n <span class=\"entity-id\" title=\"{{ entityCkTypeId() }}\">{{ entityRtId() }}</span>\n </div>\n </div>\n\n <!-- Entity Attributes (Source) -->\n @if (filteredEntityAttributes().length > 0) {\n <div class=\"entity-attributes\">\n @for (attr of filteredEntityAttributes(); track attr.attributeName) {\n <div class=\"attribute-row\">\n <span class=\"attribute-name\">{{ formatAttributeName(attr.attributeName) }}</span>\n <span class=\"attribute-value\">{{ formatValue(attr.value) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Associations List -->\n <div class=\"associations-container\">\n @for (group of groupedAssociations(); track group.roleId + group.direction) {\n <div class=\"association-group\" [class.expanded]=\"isGroupExpanded(group)\">\n <!-- Group Header -->\n <div class=\"group-header\"\n [class.clickable]=\"displayMode() === 'expandable'\"\n (click)=\"toggleGroup(group)\">\n <div class=\"direction-indicator\">\n @if (group.direction === 'out') {\n <kendo-svg-icon [icon]=\"arrowRightIcon\" size=\"small\"></kendo-svg-icon>\n } @else {\n <kendo-svg-icon [icon]=\"arrowLeftIcon\" size=\"small\"></kendo-svg-icon>\n }\n </div>\n <div class=\"group-info\">\n <span class=\"role-name\">{{ group.roleName }}</span>\n <span class=\"target-type\">{{ group.targetType }}</span>\n <span class=\"count-badge\">{{ group.count }}</span>\n </div>\n @if (displayMode() === 'expandable') {\n <div class=\"expand-icon\">\n @if (isGroupExpanded(group)) {\n <kendo-svg-icon [icon]=\"chevronDownIcon\" size=\"small\"></kendo-svg-icon>\n } @else {\n <kendo-svg-icon [icon]=\"chevronRightIcon\" size=\"small\"></kendo-svg-icon>\n }\n </div>\n }\n </div>\n\n <!-- Expanded Target List -->\n @if (displayMode() === 'expandable' && isGroupExpanded(group)) {\n <div class=\"target-list\">\n @if (isLoadingTargets(group)) {\n <div class=\"target-loading\">Loading attributes...</div>\n }\n @for (target of getTargetEntities(group); track target.rtId) {\n <div class=\"target-entry\">\n <div class=\"target-item\" (click)=\"onTargetClick(target)\">\n <span class=\"target-id\">{{ target.displayName }}</span>\n <span class=\"target-type-hint\" [title]=\"target.ckTypeId\">{{ target.ckTypeId }}</span>\n </div>\n @if (target.attributes?.length) {\n <div class=\"target-attributes\">\n @for (attr of target.attributes; track attr.attributeName) {\n <div class=\"target-attr-row\">\n <span class=\"target-attr-name\">{{ formatAttributeName(attr.attributeName) }}</span>\n <span class=\"target-attr-value\">{{ formatValue(attr.value) }}</span>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Summary Footer -->\n <div class=\"associations-summary\">\n <kendo-svg-icon [icon]=\"linkIcon\" size=\"small\"></kendo-svg-icon>\n <span>{{ totalAssociations() }} relationship(s)</span>\n </div>\n } @else {\n <mm-widget-not-configured></mm-widget-not-configured>\n }\n</div>\n\n<!-- Entity Detail Dialog -->\n@if (showDetailDialog) {\n <mm-entity-detail-dialog\n [rtId]=\"detailEntityRtId\"\n [ckTypeId]=\"detailEntityCkTypeId\"\n (closed)=\"onDetailDialogClosed()\">\n </mm-entity-detail-dialog>\n}\n", styles: [".associations-widget{display:flex;flex-direction:column;height:100%}.associations-widget.no-data{justify-content:center;align-items:center;color:var(--kendo-color-subtle, #6c757d)}.entity-header{display:flex;align-items:center;gap:12px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.entity-header .entity-box{display:flex;align-items:center;justify-content:center;padding:8px 16px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);border-radius:4px;font-weight:600;font-size:.875rem}.entity-header .entity-details{display:flex;flex-direction:column;gap:2px}.entity-header .entity-id{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);font-family:monospace}.entity-attributes{padding:4px 12px 8px;border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.entity-attributes .attribute-row{display:flex;justify-content:space-between;padding:2px 0;font-size:.8125rem}.entity-attributes .attribute-name{color:var(--kendo-color-subtle, #6c757d)}.entity-attributes .attribute-value{font-weight:500;color:var(--kendo-color-on-surface, #212529)}.associations-container{flex:1;overflow:auto;padding:8px;display:flex;flex-direction:column;gap:4px}.association-group{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;overflow:hidden}.association-group.expanded .group-header{border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.group-header{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa)}.group-header.clickable{cursor:pointer}.group-header.clickable:hover{background:var(--kendo-color-surface, #ffffff)}.direction-indicator{color:var(--kendo-color-subtle, #6c757d);display:flex;align-items:center;width:20px}.group-info{display:flex;align-items:center;gap:8px;flex:1;font-size:.8125rem}.group-info .role-name{color:var(--kendo-color-subtle, #6c757d);font-style:italic}.group-info .target-type{font-weight:500;color:var(--kendo-color-on-surface, #212529)}.group-info .count-badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);border-radius:10px;font-size:.6875rem;font-weight:600}.expand-icon{color:var(--kendo-color-subtle, #6c757d);display:flex;align-items:center}.target-list{padding:4px 0;max-height:200px;overflow-y:auto}.target-entry+.target-entry{border-top:1px solid var(--kendo-color-border, #dee2e6)}.target-item{display:flex;align-items:center;gap:8px;padding:6px 12px 6px 40px;cursor:pointer;font-size:.8125rem;transition:background .15s ease}.target-item:hover{background:var(--kendo-color-surface-alt, #f8f9fa)}.target-item .target-id{font-family:monospace;color:var(--kendo-color-on-surface, #212529);flex-shrink:0}.target-item .target-type-hint{font-size:.6875rem;color:var(--kendo-color-subtle, #6c757d);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex-shrink:1}.target-attributes{padding:2px 12px 6px 56px}.target-attributes .target-attr-row{display:flex;justify-content:space-between;padding:1px 0;font-size:.75rem}.target-attributes .target-attr-name{color:var(--kendo-color-subtle, #6c757d)}.target-attributes .target-attr-value{color:var(--kendo-color-on-surface, #212529);font-weight:500}.target-loading{padding:6px 12px 6px 40px;font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.associations-summary{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-top:1px solid var(--kendo-color-border, #dee2e6);font-size:.75rem;color:var(--kendo-color-subtle, #6c757d)}.no-data-message{font-size:.875rem}.loading-indicator{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-subtle, #6c757d)}.error-message{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-error, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i2$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: EntityDetailDialogComponent, selector: "mm-entity-detail-dialog", inputs: ["rtId", "ckTypeId"], outputs: ["closed"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
5697
5942
  }
5698
5943
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EntityAssociationsWidgetComponent, decorators: [{
5699
5944
  type: Component,
5700
- args: [{ selector: 'mm-entity-associations-widget', standalone: true, imports: [CommonModule, SVGIconModule, EntityDetailDialogComponent, WidgetNotConfiguredComponent], template: "<div class=\"associations-widget\" [class.no-data]=\"!data()\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>Loading...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else if (data()) {\n <!-- Entity Info Header -->\n <div class=\"entity-header\">\n <div class=\"entity-box\">\n <span class=\"entity-name\">{{ entityName() }}</span>\n </div>\n <div class=\"entity-details\">\n <span class=\"entity-id\" title=\"{{ entityCkTypeId() }}\">{{ entityRtId() }}</span>\n </div>\n </div>\n\n <!-- Associations List -->\n <div class=\"associations-container\">\n @for (group of groupedAssociations(); track group.roleId + group.direction) {\n <div class=\"association-group\" [class.expanded]=\"isGroupExpanded(group)\">\n <!-- Group Header -->\n <div class=\"group-header\"\n [class.clickable]=\"displayMode() === 'expandable'\"\n (click)=\"toggleGroup(group)\">\n <div class=\"direction-indicator\">\n @if (group.direction === 'out') {\n <kendo-svg-icon [icon]=\"arrowRightIcon\" size=\"small\"></kendo-svg-icon>\n } @else {\n <kendo-svg-icon [icon]=\"arrowLeftIcon\" size=\"small\"></kendo-svg-icon>\n }\n </div>\n <div class=\"group-info\">\n <span class=\"role-name\">{{ group.roleName }}</span>\n <span class=\"target-type\">{{ group.targetType }}</span>\n <span class=\"count-badge\">{{ group.count }}</span>\n </div>\n @if (displayMode() === 'expandable') {\n <div class=\"expand-icon\">\n @if (isGroupExpanded(group)) {\n <kendo-svg-icon [icon]=\"chevronDownIcon\" size=\"small\"></kendo-svg-icon>\n } @else {\n <kendo-svg-icon [icon]=\"chevronRightIcon\" size=\"small\"></kendo-svg-icon>\n }\n </div>\n }\n </div>\n\n <!-- Expanded Target List -->\n @if (displayMode() === 'expandable' && isGroupExpanded(group)) {\n <div class=\"target-list\">\n @for (target of getTargetEntities(group); track target.rtId) {\n <div class=\"target-item\" (click)=\"onTargetClick(target)\">\n <span class=\"target-id\">{{ target.displayName }}</span>\n <span class=\"target-type-hint\">{{ target.ckTypeId | slice:-30 }}</span>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Summary Footer -->\n <div class=\"associations-summary\">\n <kendo-svg-icon [icon]=\"linkIcon\" size=\"small\"></kendo-svg-icon>\n <span>{{ totalAssociations() }} relationship(s)</span>\n </div>\n } @else {\n <mm-widget-not-configured></mm-widget-not-configured>\n }\n</div>\n\n<!-- Entity Detail Dialog -->\n@if (showDetailDialog) {\n <mm-entity-detail-dialog\n [rtId]=\"detailEntityRtId\"\n [ckTypeId]=\"detailEntityCkTypeId\"\n (closed)=\"onDetailDialogClosed()\">\n </mm-entity-detail-dialog>\n}\n", styles: [".associations-widget{display:flex;flex-direction:column;height:100%}.associations-widget.no-data{justify-content:center;align-items:center;color:var(--kendo-color-subtle, #6c757d)}.entity-header{display:flex;align-items:center;gap:12px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.entity-header .entity-box{display:flex;align-items:center;justify-content:center;padding:8px 16px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);border-radius:4px;font-weight:600;font-size:.875rem}.entity-header .entity-details{display:flex;flex-direction:column;gap:2px}.entity-header .entity-id{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);font-family:monospace}.associations-container{flex:1;overflow:auto;padding:8px;display:flex;flex-direction:column;gap:4px}.association-group{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;overflow:hidden}.association-group.expanded .group-header{border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.group-header{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa)}.group-header.clickable{cursor:pointer}.group-header.clickable:hover{background:var(--kendo-color-surface, #ffffff)}.direction-indicator{color:var(--kendo-color-subtle, #6c757d);display:flex;align-items:center;width:20px}.group-info{display:flex;align-items:center;gap:8px;flex:1;font-size:.8125rem}.group-info .role-name{color:var(--kendo-color-subtle, #6c757d);font-style:italic}.group-info .target-type{font-weight:500;color:var(--kendo-color-on-surface, #212529)}.group-info .count-badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);border-radius:10px;font-size:.6875rem;font-weight:600}.expand-icon{color:var(--kendo-color-subtle, #6c757d);display:flex;align-items:center}.target-list{padding:4px 0;max-height:200px;overflow-y:auto}.target-item{display:flex;align-items:center;justify-content:space-between;padding:6px 12px 6px 40px;cursor:pointer;font-size:.8125rem;transition:background .15s ease}.target-item:hover{background:var(--kendo-color-surface-alt, #f8f9fa)}.target-item .target-id{font-family:monospace;color:var(--kendo-color-on-surface, #212529)}.target-item .target-type-hint{font-size:.6875rem;color:var(--kendo-color-subtle, #6c757d);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:150px}.associations-summary{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-top:1px solid var(--kendo-color-border, #dee2e6);font-size:.75rem;color:var(--kendo-color-subtle, #6c757d)}.no-data-message{font-size:.875rem}.loading-indicator{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-subtle, #6c757d)}.error-message{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-error, #dc3545)}\n"] }]
5945
+ args: [{ selector: 'mm-entity-associations-widget', standalone: true, imports: [CommonModule, SVGIconModule, EntityDetailDialogComponent, WidgetNotConfiguredComponent], template: "<div class=\"associations-widget\" [class.no-data]=\"!data()\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>Loading...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else if (data()) {\n <!-- Entity Info Header -->\n <div class=\"entity-header\">\n <div class=\"entity-box\">\n <span class=\"entity-name\">{{ entityName() }}</span>\n </div>\n <div class=\"entity-details\">\n <span class=\"entity-id\" title=\"{{ entityCkTypeId() }}\">{{ entityRtId() }}</span>\n </div>\n </div>\n\n <!-- Entity Attributes (Source) -->\n @if (filteredEntityAttributes().length > 0) {\n <div class=\"entity-attributes\">\n @for (attr of filteredEntityAttributes(); track attr.attributeName) {\n <div class=\"attribute-row\">\n <span class=\"attribute-name\">{{ formatAttributeName(attr.attributeName) }}</span>\n <span class=\"attribute-value\">{{ formatValue(attr.value) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Associations List -->\n <div class=\"associations-container\">\n @for (group of groupedAssociations(); track group.roleId + group.direction) {\n <div class=\"association-group\" [class.expanded]=\"isGroupExpanded(group)\">\n <!-- Group Header -->\n <div class=\"group-header\"\n [class.clickable]=\"displayMode() === 'expandable'\"\n (click)=\"toggleGroup(group)\">\n <div class=\"direction-indicator\">\n @if (group.direction === 'out') {\n <kendo-svg-icon [icon]=\"arrowRightIcon\" size=\"small\"></kendo-svg-icon>\n } @else {\n <kendo-svg-icon [icon]=\"arrowLeftIcon\" size=\"small\"></kendo-svg-icon>\n }\n </div>\n <div class=\"group-info\">\n <span class=\"role-name\">{{ group.roleName }}</span>\n <span class=\"target-type\">{{ group.targetType }}</span>\n <span class=\"count-badge\">{{ group.count }}</span>\n </div>\n @if (displayMode() === 'expandable') {\n <div class=\"expand-icon\">\n @if (isGroupExpanded(group)) {\n <kendo-svg-icon [icon]=\"chevronDownIcon\" size=\"small\"></kendo-svg-icon>\n } @else {\n <kendo-svg-icon [icon]=\"chevronRightIcon\" size=\"small\"></kendo-svg-icon>\n }\n </div>\n }\n </div>\n\n <!-- Expanded Target List -->\n @if (displayMode() === 'expandable' && isGroupExpanded(group)) {\n <div class=\"target-list\">\n @if (isLoadingTargets(group)) {\n <div class=\"target-loading\">Loading attributes...</div>\n }\n @for (target of getTargetEntities(group); track target.rtId) {\n <div class=\"target-entry\">\n <div class=\"target-item\" (click)=\"onTargetClick(target)\">\n <span class=\"target-id\">{{ target.displayName }}</span>\n <span class=\"target-type-hint\" [title]=\"target.ckTypeId\">{{ target.ckTypeId }}</span>\n </div>\n @if (target.attributes?.length) {\n <div class=\"target-attributes\">\n @for (attr of target.attributes; track attr.attributeName) {\n <div class=\"target-attr-row\">\n <span class=\"target-attr-name\">{{ formatAttributeName(attr.attributeName) }}</span>\n <span class=\"target-attr-value\">{{ formatValue(attr.value) }}</span>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Summary Footer -->\n <div class=\"associations-summary\">\n <kendo-svg-icon [icon]=\"linkIcon\" size=\"small\"></kendo-svg-icon>\n <span>{{ totalAssociations() }} relationship(s)</span>\n </div>\n } @else {\n <mm-widget-not-configured></mm-widget-not-configured>\n }\n</div>\n\n<!-- Entity Detail Dialog -->\n@if (showDetailDialog) {\n <mm-entity-detail-dialog\n [rtId]=\"detailEntityRtId\"\n [ckTypeId]=\"detailEntityCkTypeId\"\n (closed)=\"onDetailDialogClosed()\">\n </mm-entity-detail-dialog>\n}\n", styles: [".associations-widget{display:flex;flex-direction:column;height:100%}.associations-widget.no-data{justify-content:center;align-items:center;color:var(--kendo-color-subtle, #6c757d)}.entity-header{display:flex;align-items:center;gap:12px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.entity-header .entity-box{display:flex;align-items:center;justify-content:center;padding:8px 16px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);border-radius:4px;font-weight:600;font-size:.875rem}.entity-header .entity-details{display:flex;flex-direction:column;gap:2px}.entity-header .entity-id{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);font-family:monospace}.entity-attributes{padding:4px 12px 8px;border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.entity-attributes .attribute-row{display:flex;justify-content:space-between;padding:2px 0;font-size:.8125rem}.entity-attributes .attribute-name{color:var(--kendo-color-subtle, #6c757d)}.entity-attributes .attribute-value{font-weight:500;color:var(--kendo-color-on-surface, #212529)}.associations-container{flex:1;overflow:auto;padding:8px;display:flex;flex-direction:column;gap:4px}.association-group{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;overflow:hidden}.association-group.expanded .group-header{border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.group-header{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa)}.group-header.clickable{cursor:pointer}.group-header.clickable:hover{background:var(--kendo-color-surface, #ffffff)}.direction-indicator{color:var(--kendo-color-subtle, #6c757d);display:flex;align-items:center;width:20px}.group-info{display:flex;align-items:center;gap:8px;flex:1;font-size:.8125rem}.group-info .role-name{color:var(--kendo-color-subtle, #6c757d);font-style:italic}.group-info .target-type{font-weight:500;color:var(--kendo-color-on-surface, #212529)}.group-info .count-badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);border-radius:10px;font-size:.6875rem;font-weight:600}.expand-icon{color:var(--kendo-color-subtle, #6c757d);display:flex;align-items:center}.target-list{padding:4px 0;max-height:200px;overflow-y:auto}.target-entry+.target-entry{border-top:1px solid var(--kendo-color-border, #dee2e6)}.target-item{display:flex;align-items:center;gap:8px;padding:6px 12px 6px 40px;cursor:pointer;font-size:.8125rem;transition:background .15s ease}.target-item:hover{background:var(--kendo-color-surface-alt, #f8f9fa)}.target-item .target-id{font-family:monospace;color:var(--kendo-color-on-surface, #212529);flex-shrink:0}.target-item .target-type-hint{font-size:.6875rem;color:var(--kendo-color-subtle, #6c757d);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex-shrink:1}.target-attributes{padding:2px 12px 6px 56px}.target-attributes .target-attr-row{display:flex;justify-content:space-between;padding:1px 0;font-size:.75rem}.target-attributes .target-attr-name{color:var(--kendo-color-subtle, #6c757d)}.target-attributes .target-attr-value{color:var(--kendo-color-on-surface, #212529);font-weight:500}.target-loading{padding:6px 12px 6px 40px;font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.associations-summary{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-top:1px solid var(--kendo-color-border, #dee2e6);font-size:.75rem;color:var(--kendo-color-subtle, #6c757d)}.no-data-message{font-size:.875rem}.loading-indicator{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-subtle, #6c757d)}.error-message{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-error, #dc3545)}\n"] }]
5701
5946
  }], propDecorators: { config: [{
5702
5947
  type: Input
5703
5948
  }] } });
@@ -5715,11 +5960,13 @@ const GetCkTypeAssociationRolesDocumentDto = gql `
5715
5960
  fullName
5716
5961
  semanticVersionedFullName
5717
5962
  }
5963
+ rtRoleId
5718
5964
  navigationPropertyName
5719
5965
  multiplicity
5720
5966
  targetCkTypeId {
5721
5967
  fullName
5722
5968
  }
5969
+ rtTargetCkTypeId
5723
5970
  }
5724
5971
  }
5725
5972
  out {
@@ -5728,11 +5975,13 @@ const GetCkTypeAssociationRolesDocumentDto = gql `
5728
5975
  fullName
5729
5976
  semanticVersionedFullName
5730
5977
  }
5978
+ rtRoleId
5731
5979
  navigationPropertyName
5732
5980
  multiplicity
5733
5981
  targetCkTypeId {
5734
5982
  fullName
5735
5983
  }
5984
+ rtTargetCkTypeId
5736
5985
  }
5737
5986
  }
5738
5987
  }
@@ -5860,6 +6109,7 @@ class AssociationsConfigDialogComponent {
5860
6109
  getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
5861
6110
  getCkTypeAssociationRolesGQL = inject(GetCkTypeAssociationRolesDtoGQL);
5862
6111
  ckTypeSelectorService = inject(CkTypeSelectorService);
6112
+ dataService = inject(MeshBoardDataService);
5863
6113
  windowRef = inject(WindowRef);
5864
6114
  initialCkTypeId;
5865
6115
  initialRtId;
@@ -5867,6 +6117,8 @@ class AssociationsConfigDialogComponent {
5867
6117
  initialShowOutgoing;
5868
6118
  initialRoleFilter;
5869
6119
  initialDisplayMode;
6120
+ initialEntityAttributePaths;
6121
+ initialTargetAttributePaths;
5870
6122
  selectedCkType = null;
5871
6123
  selectedEntity = null;
5872
6124
  entityDataSource;
@@ -5875,11 +6127,21 @@ class AssociationsConfigDialogComponent {
5875
6127
  showOutgoing = true;
5876
6128
  selectedRoles = [];
5877
6129
  displayMode = 'expandable';
6130
+ selectedEntityAttributes = [];
6131
+ selectedTargetAttributes = [];
5878
6132
  isLoadingInitial = false;
5879
6133
  isLoadingRoles = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingRoles" }] : []));
5880
6134
  availableRoles = signal([], ...(ngDevMode ? [{ debugName: "availableRoles" }] : []));
5881
6135
  filteredRoles = signal([], ...(ngDevMode ? [{ debugName: "filteredRoles" }] : []));
6136
+ isLoadingEntityAttributes = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingEntityAttributes" }] : []));
6137
+ availableEntityAttributes = signal([], ...(ngDevMode ? [{ debugName: "availableEntityAttributes" }] : []));
6138
+ filteredEntityAttributes = signal([], ...(ngDevMode ? [{ debugName: "filteredEntityAttributes" }] : []));
6139
+ isLoadingTargetAttributes = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingTargetAttributes" }] : []));
6140
+ availableTargetAttributes = signal([], ...(ngDevMode ? [{ debugName: "availableTargetAttributes" }] : []));
6141
+ filteredTargetAttributes = signal([], ...(ngDevMode ? [{ debugName: "filteredTargetAttributes" }] : []));
5882
6142
  roleFilterText = '';
6143
+ entityAttrFilterText = '';
6144
+ targetAttrFilterText = '';
5883
6145
  get isValid() {
5884
6146
  return this.selectedCkType !== null &&
5885
6147
  this.selectedEntity !== null &&
@@ -5896,6 +6158,12 @@ class AssociationsConfigDialogComponent {
5896
6158
  if (this.initialDisplayMode) {
5897
6159
  this.displayMode = this.initialDisplayMode;
5898
6160
  }
6161
+ if (this.initialEntityAttributePaths) {
6162
+ this.selectedEntityAttributes = [...this.initialEntityAttributePaths];
6163
+ }
6164
+ if (this.initialTargetAttributePaths) {
6165
+ this.selectedTargetAttributes = [...this.initialTargetAttributePaths];
6166
+ }
5899
6167
  if (this.initialCkTypeId) {
5900
6168
  await this.loadInitialValues();
5901
6169
  }
@@ -5912,11 +6180,17 @@ class AssociationsConfigDialogComponent {
5912
6180
  if (this.initialRtId && this.entityDataSource) {
5913
6181
  await this.loadInitialEntity();
5914
6182
  }
5915
- // Set initial role filter after roles are loaded
6183
+ // Restore initial selections after onCkTypeSelected() (which resets them)
5916
6184
  if (this.initialRoleFilter && this.initialRoleFilter.length > 0) {
5917
6185
  const allRoles = this.availableRoles();
5918
6186
  this.selectedRoles = allRoles.filter(r => this.initialRoleFilter.includes(r.roleId));
5919
6187
  }
6188
+ if (this.initialEntityAttributePaths?.length) {
6189
+ this.selectedEntityAttributes = [...this.initialEntityAttributePaths];
6190
+ }
6191
+ if (this.initialTargetAttributePaths?.length) {
6192
+ this.selectedTargetAttributes = [...this.initialTargetAttributePaths];
6193
+ }
5920
6194
  }
5921
6195
  }
5922
6196
  catch (error) {
@@ -5958,11 +6232,15 @@ class AssociationsConfigDialogComponent {
5958
6232
  this.selectedCkType = ckType;
5959
6233
  this.selectedEntity = null;
5960
6234
  this.selectedRoles = [];
6235
+ this.selectedEntityAttributes = [];
5961
6236
  // Create data sources for entity selection
5962
6237
  this.entityDataSource = new RuntimeEntitySelectDataSource$1(this.getEntitiesByCkTypeGQL, ckType.rtCkTypeId);
5963
6238
  this.entityDialogDataSource = new RuntimeEntityDialogDataSource$1(this.getEntitiesByCkTypeGQL, ckType.rtCkTypeId);
5964
- // Load available association roles
5965
- await this.loadAssociationRoles(ckType.rtCkTypeId);
6239
+ // Load available association roles and entity attributes in parallel
6240
+ await Promise.all([
6241
+ this.loadAssociationRoles(ckType.rtCkTypeId),
6242
+ this.loadEntityAttributes(ckType.rtCkTypeId)
6243
+ ]);
5966
6244
  }
5967
6245
  onCkTypeCleared() {
5968
6246
  this.selectedCkType = null;
@@ -5972,6 +6250,12 @@ class AssociationsConfigDialogComponent {
5972
6250
  this.availableRoles.set([]);
5973
6251
  this.filteredRoles.set([]);
5974
6252
  this.selectedRoles = [];
6253
+ this.availableEntityAttributes.set([]);
6254
+ this.filteredEntityAttributes.set([]);
6255
+ this.selectedEntityAttributes = [];
6256
+ this.availableTargetAttributes.set([]);
6257
+ this.filteredTargetAttributes.set([]);
6258
+ this.selectedTargetAttributes = [];
5975
6259
  }
5976
6260
  onEntitySelected(entity) {
5977
6261
  this.selectedEntity = entity;
@@ -6011,12 +6295,12 @@ class AssociationsConfigDialogComponent {
6011
6295
  if (!assoc)
6012
6296
  continue;
6013
6297
  roles.push({
6014
- roleId: assoc.roleId.semanticVersionedFullName,
6298
+ roleId: assoc.rtRoleId,
6015
6299
  navigationPropertyName: assoc.navigationPropertyName,
6016
6300
  direction: 'in',
6017
- targetCkTypeId: assoc.targetCkTypeId.fullName,
6301
+ targetCkTypeId: assoc.rtTargetCkTypeId,
6018
6302
  multiplicity: assoc.multiplicity,
6019
- displayName: `[IN] ${assoc.navigationPropertyName} (${this.formatTypeName(assoc.targetCkTypeId.fullName)})`
6303
+ displayName: `[IN] ${assoc.rtRoleId} (${assoc.rtTargetCkTypeId})`
6020
6304
  });
6021
6305
  }
6022
6306
  // Process outgoing associations
@@ -6025,16 +6309,18 @@ class AssociationsConfigDialogComponent {
6025
6309
  if (!assoc)
6026
6310
  continue;
6027
6311
  roles.push({
6028
- roleId: assoc.roleId.semanticVersionedFullName,
6312
+ roleId: assoc.rtRoleId,
6029
6313
  navigationPropertyName: assoc.navigationPropertyName,
6030
6314
  direction: 'out',
6031
- targetCkTypeId: assoc.targetCkTypeId.fullName,
6315
+ targetCkTypeId: assoc.rtTargetCkTypeId,
6032
6316
  multiplicity: assoc.multiplicity,
6033
- displayName: `[OUT] ${assoc.navigationPropertyName} (${this.formatTypeName(assoc.targetCkTypeId.fullName)})`
6317
+ displayName: `[OUT] ${assoc.rtRoleId} (${assoc.rtTargetCkTypeId})`
6034
6318
  });
6035
6319
  }
6036
6320
  this.availableRoles.set(roles);
6037
6321
  this.filteredRoles.set(roles);
6322
+ // Load target attributes from unique target CK types
6323
+ await this.loadTargetAttributesFromRoles(roles);
6038
6324
  }
6039
6325
  catch (error) {
6040
6326
  console.error('Error loading association roles:', error);
@@ -6043,9 +6329,77 @@ class AssociationsConfigDialogComponent {
6043
6329
  this.isLoadingRoles.set(false);
6044
6330
  }
6045
6331
  }
6046
- formatTypeName(fullName) {
6047
- const parts = fullName.split('/');
6048
- return parts[parts.length - 1];
6332
+ async loadEntityAttributes(ckTypeId) {
6333
+ this.isLoadingEntityAttributes.set(true);
6334
+ this.availableEntityAttributes.set([]);
6335
+ this.filteredEntityAttributes.set([]);
6336
+ try {
6337
+ const attrs = await firstValueFrom(this.dataService.fetchCkTypeAttributes(ckTypeId));
6338
+ this.availableEntityAttributes.set(attrs);
6339
+ this.filteredEntityAttributes.set(attrs);
6340
+ }
6341
+ catch (error) {
6342
+ console.error('Error loading entity attributes:', error);
6343
+ }
6344
+ finally {
6345
+ this.isLoadingEntityAttributes.set(false);
6346
+ }
6347
+ }
6348
+ async loadTargetAttributesFromRoles(roles) {
6349
+ // Collect unique target CK type IDs from the roles
6350
+ const targetCkTypeIds = [...new Set(roles.map(r => r.targetCkTypeId))];
6351
+ if (targetCkTypeIds.length === 0)
6352
+ return;
6353
+ this.isLoadingTargetAttributes.set(true);
6354
+ this.availableTargetAttributes.set([]);
6355
+ this.filteredTargetAttributes.set([]);
6356
+ try {
6357
+ // Load attributes from all target types and merge (union)
6358
+ const allAttrs = new Map();
6359
+ for (const ckTypeId of targetCkTypeIds) {
6360
+ const attrs = await firstValueFrom(this.dataService.fetchCkTypeAttributes(ckTypeId));
6361
+ for (const attr of attrs) {
6362
+ if (!allAttrs.has(attr.attributeName)) {
6363
+ allAttrs.set(attr.attributeName, attr);
6364
+ }
6365
+ }
6366
+ }
6367
+ const merged = Array.from(allAttrs.values()).sort((a, b) => a.attributeName.localeCompare(b.attributeName));
6368
+ this.availableTargetAttributes.set(merged);
6369
+ this.filteredTargetAttributes.set(merged);
6370
+ }
6371
+ catch (error) {
6372
+ console.error('Error loading target attributes:', error);
6373
+ }
6374
+ finally {
6375
+ this.isLoadingTargetAttributes.set(false);
6376
+ }
6377
+ }
6378
+ onEntityAttrFilterChange(filter) {
6379
+ this.entityAttrFilterText = filter.toLowerCase();
6380
+ this.updateFilteredEntityAttributes();
6381
+ }
6382
+ onTargetAttrFilterChange(filter) {
6383
+ this.targetAttrFilterText = filter.toLowerCase();
6384
+ this.updateFilteredTargetAttributes();
6385
+ }
6386
+ updateFilteredEntityAttributes() {
6387
+ const all = this.availableEntityAttributes();
6388
+ if (!this.entityAttrFilterText) {
6389
+ this.filteredEntityAttributes.set(all);
6390
+ }
6391
+ else {
6392
+ this.filteredEntityAttributes.set(all.filter(a => a.attributeName.toLowerCase().includes(this.entityAttrFilterText)));
6393
+ }
6394
+ }
6395
+ updateFilteredTargetAttributes() {
6396
+ const all = this.availableTargetAttributes();
6397
+ if (!this.targetAttrFilterText) {
6398
+ this.filteredTargetAttributes.set(all);
6399
+ }
6400
+ else {
6401
+ this.filteredTargetAttributes.set(all.filter(a => a.attributeName.toLowerCase().includes(this.targetAttrFilterText)));
6402
+ }
6049
6403
  }
6050
6404
  onSave() {
6051
6405
  if (this.selectedCkType && this.selectedEntity) {
@@ -6055,7 +6409,9 @@ class AssociationsConfigDialogComponent {
6055
6409
  showIncoming: this.showIncoming,
6056
6410
  showOutgoing: this.showOutgoing,
6057
6411
  roleFilter: this.selectedRoles.map(r => r.roleId),
6058
- displayMode: this.displayMode
6412
+ displayMode: this.displayMode,
6413
+ entityAttributePaths: this.selectedEntityAttributes,
6414
+ targetAttributePaths: this.selectedTargetAttributes
6059
6415
  });
6060
6416
  }
6061
6417
  }
@@ -6063,7 +6419,7 @@ class AssociationsConfigDialogComponent {
6063
6419
  this.windowRef.close();
6064
6420
  }
6065
6421
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AssociationsConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6066
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: AssociationsConfigDialogComponent, isStandalone: true, selector: "mm-associations-config-dialog", inputs: { initialCkTypeId: "initialCkTypeId", initialRtId: "initialRtId", initialShowIncoming: "initialShowIncoming", initialShowOutgoing: "initialShowOutgoing", initialRoleFilter: "initialRoleFilter", initialDisplayMode: "initialDisplayMode" }, ngImport: i0, template: `
6422
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: AssociationsConfigDialogComponent, isStandalone: true, selector: "mm-associations-config-dialog", inputs: { initialCkTypeId: "initialCkTypeId", initialRtId: "initialRtId", initialShowIncoming: "initialShowIncoming", initialShowOutgoing: "initialShowOutgoing", initialRoleFilter: "initialRoleFilter", initialDisplayMode: "initialDisplayMode", initialEntityAttributePaths: "initialEntityAttributePaths", initialTargetAttributePaths: "initialTargetAttributePaths" }, ngImport: i0, template: `
6067
6423
  <div class="config-container">
6068
6424
 
6069
6425
  <div class="config-form" [class.loading]="isLoadingInitial">
@@ -6162,6 +6518,56 @@ class AssociationsConfigDialogComponent {
6162
6518
  <p class="field-hint">How to display association targets.</p>
6163
6519
  </div>
6164
6520
 
6521
+ <!-- Entity Attributes -->
6522
+ <div class="form-field" [class.disabled]="!selectedCkType || availableEntityAttributes().length === 0">
6523
+ <label>Entity Attributes (optional)</label>
6524
+ @if (isLoadingEntityAttributes()) {
6525
+ <div class="loading-roles">Loading attributes...</div>
6526
+ } @else if (availableEntityAttributes().length > 0) {
6527
+ <kendo-multiselect
6528
+ [data]="filteredEntityAttributes()"
6529
+ [textField]="'attributeName'"
6530
+ [valueField]="'attributeName'"
6531
+ [(ngModel)]="selectedEntityAttributes"
6532
+ [valuePrimitive]="true"
6533
+ placeholder="No attributes selected"
6534
+ [filterable]="true"
6535
+ (filterChange)="onEntityAttrFilterChange($event)">
6536
+ </kendo-multiselect>
6537
+ } @else {
6538
+ <kendo-textbox
6539
+ [disabled]="true"
6540
+ placeholder="No attributes available">
6541
+ </kendo-textbox>
6542
+ }
6543
+ <p class="field-hint">Attributes of the source entity to display below the header.</p>
6544
+ </div>
6545
+
6546
+ <!-- Target Attributes -->
6547
+ <div class="form-field" [class.disabled]="!selectedCkType || availableTargetAttributes().length === 0">
6548
+ <label>Target Attributes (optional)</label>
6549
+ @if (isLoadingTargetAttributes()) {
6550
+ <div class="loading-roles">Loading attributes...</div>
6551
+ } @else if (availableTargetAttributes().length > 0) {
6552
+ <kendo-multiselect
6553
+ [data]="filteredTargetAttributes()"
6554
+ [textField]="'attributeName'"
6555
+ [valueField]="'attributeName'"
6556
+ [(ngModel)]="selectedTargetAttributes"
6557
+ [valuePrimitive]="true"
6558
+ placeholder="No attributes selected"
6559
+ [filterable]="true"
6560
+ (filterChange)="onTargetAttrFilterChange($event)">
6561
+ </kendo-multiselect>
6562
+ } @else {
6563
+ <kendo-textbox
6564
+ [disabled]="true"
6565
+ placeholder="No target attributes available">
6566
+ </kendo-textbox>
6567
+ }
6568
+ <p class="field-hint">Attributes of target entities to display inline in the expanded list.</p>
6569
+ </div>
6570
+
6165
6571
  <!-- Selection Preview -->
6166
6572
  @if (selectedEntity) {
6167
6573
  <div class="selection-preview">
@@ -6301,6 +6707,56 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
6301
6707
  <p class="field-hint">How to display association targets.</p>
6302
6708
  </div>
6303
6709
 
6710
+ <!-- Entity Attributes -->
6711
+ <div class="form-field" [class.disabled]="!selectedCkType || availableEntityAttributes().length === 0">
6712
+ <label>Entity Attributes (optional)</label>
6713
+ @if (isLoadingEntityAttributes()) {
6714
+ <div class="loading-roles">Loading attributes...</div>
6715
+ } @else if (availableEntityAttributes().length > 0) {
6716
+ <kendo-multiselect
6717
+ [data]="filteredEntityAttributes()"
6718
+ [textField]="'attributeName'"
6719
+ [valueField]="'attributeName'"
6720
+ [(ngModel)]="selectedEntityAttributes"
6721
+ [valuePrimitive]="true"
6722
+ placeholder="No attributes selected"
6723
+ [filterable]="true"
6724
+ (filterChange)="onEntityAttrFilterChange($event)">
6725
+ </kendo-multiselect>
6726
+ } @else {
6727
+ <kendo-textbox
6728
+ [disabled]="true"
6729
+ placeholder="No attributes available">
6730
+ </kendo-textbox>
6731
+ }
6732
+ <p class="field-hint">Attributes of the source entity to display below the header.</p>
6733
+ </div>
6734
+
6735
+ <!-- Target Attributes -->
6736
+ <div class="form-field" [class.disabled]="!selectedCkType || availableTargetAttributes().length === 0">
6737
+ <label>Target Attributes (optional)</label>
6738
+ @if (isLoadingTargetAttributes()) {
6739
+ <div class="loading-roles">Loading attributes...</div>
6740
+ } @else if (availableTargetAttributes().length > 0) {
6741
+ <kendo-multiselect
6742
+ [data]="filteredTargetAttributes()"
6743
+ [textField]="'attributeName'"
6744
+ [valueField]="'attributeName'"
6745
+ [(ngModel)]="selectedTargetAttributes"
6746
+ [valuePrimitive]="true"
6747
+ placeholder="No attributes selected"
6748
+ [filterable]="true"
6749
+ (filterChange)="onTargetAttrFilterChange($event)">
6750
+ </kendo-multiselect>
6751
+ } @else {
6752
+ <kendo-textbox
6753
+ [disabled]="true"
6754
+ placeholder="No target attributes available">
6755
+ </kendo-textbox>
6756
+ }
6757
+ <p class="field-hint">Attributes of target entities to display inline in the expanded list.</p>
6758
+ </div>
6759
+
6304
6760
  <!-- Selection Preview -->
6305
6761
  @if (selectedEntity) {
6306
6762
  <div class="selection-preview">
@@ -6340,6 +6796,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
6340
6796
  type: Input
6341
6797
  }], initialDisplayMode: [{
6342
6798
  type: Input
6799
+ }], initialEntityAttributePaths: [{
6800
+ type: Input
6801
+ }], initialTargetAttributePaths: [{
6802
+ type: Input
6343
6803
  }] } });
6344
6804
 
6345
6805
  /**
@@ -20361,7 +20821,9 @@ function registerDefaultWidgets(registry) {
20361
20821
  initialShowIncoming: assocWidget.showIncoming,
20362
20822
  initialShowOutgoing: assocWidget.showOutgoing,
20363
20823
  initialRoleFilter: assocWidget.roleFilter,
20364
- initialDisplayMode: assocWidget.displayMode
20824
+ initialDisplayMode: assocWidget.displayMode,
20825
+ initialEntityAttributePaths: assocWidget.entityAttributePaths,
20826
+ initialTargetAttributePaths: assocWidget.targetAttributePaths
20365
20827
  };
20366
20828
  },
20367
20829
  applyConfigResult: (widget, result) => ({
@@ -20370,7 +20832,9 @@ function registerDefaultWidgets(registry) {
20370
20832
  showIncoming: result.showIncoming,
20371
20833
  showOutgoing: result.showOutgoing,
20372
20834
  roleFilter: result.roleFilter,
20373
- displayMode: result.displayMode
20835
+ displayMode: result.displayMode,
20836
+ entityAttributePaths: result.entityAttributePaths,
20837
+ targetAttributePaths: result.targetAttributePaths
20374
20838
  }),
20375
20839
  // SOLID: Factory function
20376
20840
  createDefaultConfig: (base) => ({
@@ -20393,7 +20857,9 @@ function registerDefaultWidgets(registry) {
20393
20857
  showOutgoing: widget.showOutgoing,
20394
20858
  maxAssociations: widget.maxAssociations,
20395
20859
  roleFilter: widget.roleFilter,
20396
- displayMode: widget.displayMode
20860
+ displayMode: widget.displayMode,
20861
+ entityAttributePaths: widget.entityAttributePaths ?? [],
20862
+ targetAttributePaths: widget.targetAttributePaths ?? []
20397
20863
  }
20398
20864
  }),
20399
20865
  // SOLID: Deserialization from persistence
@@ -20412,7 +20878,9 @@ function registerDefaultWidgets(registry) {
20412
20878
  showOutgoing: config['showOutgoing'] ?? true,
20413
20879
  maxAssociations: config['maxAssociations'] ?? 5,
20414
20880
  roleFilter: config['roleFilter'],
20415
- displayMode: config['displayMode']
20881
+ displayMode: config['displayMode'],
20882
+ entityAttributePaths: config['entityAttributePaths'] ?? [],
20883
+ targetAttributePaths: config['targetAttributePaths'] ?? []
20416
20884
  };
20417
20885
  }
20418
20886
  });
@@ -22598,14 +23066,15 @@ class MeshBoardViewComponent {
22598
23066
  */
22599
23067
  updateUrlWithRtId(rtId) {
22600
23068
  const currentUrl = this.router.url;
22601
- // Check if we're already on a meshboard/:rtId route or just /meshboard
22602
- if (currentUrl.includes('/meshboard/')) {
22603
- // Replace the rtId in the URL
22604
- const newUrl = currentUrl.replace(/\/meshboard\/[^/]+/, `/meshboard/${rtId}`);
23069
+ const hasRtIdParam = this.route.snapshot.paramMap.has('rtId');
23070
+ if (hasRtIdParam) {
23071
+ // Replace the last URL segment (the old rtId) with the new one
23072
+ const lastSlashIndex = currentUrl.lastIndexOf('/');
23073
+ const newUrl = currentUrl.substring(0, lastSlashIndex + 1) + rtId;
22605
23074
  this.router.navigateByUrl(newUrl, { replaceUrl: true });
22606
23075
  }
22607
- else if (currentUrl.endsWith('/meshboard')) {
22608
- // Append the rtId
23076
+ else {
23077
+ // Append the rtId to the current URL
22609
23078
  this.router.navigateByUrl(`${currentUrl}/${rtId}`, { replaceUrl: true });
22610
23079
  }
22611
23080
  }