@meshmakers/octo-ui 3.5.100 → 3.5.110

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,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ChangeDetectionStrategy, Component, Injectable, Input, EventEmitter, Output, LOCALE_ID, ElementRef, forwardRef, ViewChild, signal, computed, input, model, output, effect, untracked, Directive, booleanAttribute, viewChild, afterEveryRender, InjectionToken, HostListener, makeEnvironmentProviders } from '@angular/core';
2
+ import { inject, ChangeDetectionStrategy, Component, Injectable, Input, EventEmitter, Output, Injector, LOCALE_ID, ElementRef, forwardRef, ViewChild, signal, computed, input, model, output, effect, untracked, Directive, booleanAttribute, viewChild, afterEveryRender, InjectionToken, HostListener, makeEnvironmentProviders } from '@angular/core';
3
3
  import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
4
- import { AttributeSelectorService, AttributeValueTypeDto as AttributeValueTypeDto$1, CkTypeSelectorService, CkTypeAttributeService, toSecretState, secretStateFromAttribute, isSecretValueType, isSecretStateObject, secretStatusOf, formatSecretStatus, GetEntitiesByCkTypeDtoGQL, RuntimeEntitySelectDataSource, RuntimeEntityDialogDataSource, SearchFilterTypesDto, SortOrdersDto, FieldFilterOperatorsDto, GetCkTypesDtoGQL, GetCkModelByIdDtoGQL, GraphDirectionDto, CkTypeMetaData, AssociationModOptionsDto, GraphQL, isSecretAttributeCandidate, toUniqueCamelCaseNames, toCamelCaseAttributeName, DeleteStrategiesDto, CommunicationService, provideOctoServices } from '@meshmakers/octo-services';
4
+ import { AttributeSelectorService, AttributeValueTypeDto as AttributeValueTypeDto$1, CkTypeSelectorService, CkTypeAttributeService, toSecretState, secretStateFromAttribute, isSecretValueType, FileDownloadService, isSecretStateObject, secretStatusOf, formatSecretStatus, GetEntitiesByCkTypeDtoGQL, RuntimeEntitySelectDataSource, RuntimeEntityDialogDataSource, SearchFilterTypesDto, SortOrdersDto, FieldFilterOperatorsDto, GetCkTypesDtoGQL, GetCkModelByIdDtoGQL, GraphDirectionDto, CkTypeMetaData, AssociationModOptionsDto, GraphQL, BinaryDownloadUnavailableError, isSecretAttributeCandidate, toUniqueCamelCaseNames, toCamelCaseAttributeName, DeleteStrategiesDto, CommunicationService, provideOctoServices } from '@meshmakers/octo-services';
5
5
  import { Roles, provideMmSharedAuth } from '@meshmakers/shared-auth';
6
6
  import { WindowStateService, EntitySelectInputComponent, DataSourceTyped, HierarchyDataSourceBase, NotificationDisplayService, TreeComponent, ListViewComponent, FetchResultTyped, DataSourceBase, MM_ACTION_ICONS, ConfirmationService, InputService, BaseTreeDetailComponent, provideMmSharedUi } from '@meshmakers/shared-ui';
7
7
  import * as i1$2 from '@angular/common';
@@ -2077,6 +2077,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
2077
2077
  type: Output
2078
2078
  }] } });
2079
2079
 
2080
+ /**
2081
+ * Hands a binary download request to the host when one listens on `emitter`, otherwise downloads
2082
+ * the clicked value itself with the user's bearer through `FileDownloadService` (AB#6179).
2083
+ *
2084
+ * The service is resolved lazily at click time so that merely rendering a property grid never
2085
+ * requires `HttpClient` / `CONFIGURATION_SERVICE` to be provided.
2086
+ */
2087
+ function emitOrDownloadBinary(emitter, event, injector) {
2088
+ if (emitter.observed) {
2089
+ emitter.emit(event);
2090
+ return;
2091
+ }
2092
+ let downloads;
2093
+ try {
2094
+ downloads = injector.get(FileDownloadService);
2095
+ }
2096
+ catch (error) {
2097
+ console.error('Cannot download binary: FileDownloadService is not available', error);
2098
+ return;
2099
+ }
2100
+ downloads.downloadBinary(event).catch(error => {
2101
+ console.error('Binary download failed:', error);
2102
+ });
2103
+ }
2104
+
2080
2105
  /**
2081
2106
  * A secret record member as projected by the server (AB#5528): the marker `{ isSet }` (optionally
2082
2107
  * with `keyMissing` / `setAt`), or a normalised `SecretState`. Nothing else has exactly these keys.
@@ -2097,6 +2122,7 @@ class PropertyValueDisplayComponent {
2097
2122
  // Expansion state
2098
2123
  isExpanded = false;
2099
2124
  windowService = inject(WindowService);
2125
+ injector = inject(Injector);
2100
2126
  locale = inject(LOCALE_ID);
2101
2127
  /** Non-null when the value is a secret state (SECRET value type or record member marker). */
2102
2128
  secretStatus = null;
@@ -2541,25 +2567,27 @@ class PropertyValueDisplayComponent {
2541
2567
  return `${size.toFixed(unitIndex > 0 ? 1 : 0)} ${units[unitIndex]}`;
2542
2568
  }
2543
2569
  /**
2544
- * Handle download button click
2570
+ * Handle download button click.
2571
+ *
2572
+ * Emits {@link binaryDownload} with the clicked value when a host listens. Without a listener the
2573
+ * component downloads the value itself through `FileDownloadService` — an authenticated blob
2574
+ * download with the user's bearer, never `window.open(rawUrl)`, which carries no token (AB#6179).
2545
2575
  */
2546
2576
  onDownload() {
2547
2577
  if (!this.value || typeof this.value !== 'object') {
2548
2578
  return;
2549
2579
  }
2550
2580
  const binaryValue = this.value;
2551
- if (binaryValue.downloadUri) {
2552
- window.open(binaryValue.downloadUri, '_blank', 'noopener,noreferrer');
2581
+ if (!binaryValue.downloadUri && !binaryValue.binaryId) {
2553
2582
  return;
2554
2583
  }
2555
- if (binaryValue.binaryId) {
2556
- const event = {
2557
- binaryId: binaryValue.binaryId,
2558
- filename: binaryValue.filename,
2559
- contentType: binaryValue.contentType
2560
- };
2561
- this.binaryDownload.emit(event);
2562
- }
2584
+ const event = {
2585
+ binaryId: binaryValue.binaryId,
2586
+ filename: binaryValue.filename,
2587
+ contentType: binaryValue.contentType,
2588
+ downloadUri: binaryValue.downloadUri
2589
+ };
2590
+ emitOrDownloadBinary(this.binaryDownload, event, this.injector);
2563
2591
  }
2564
2592
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: PropertyValueDisplayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2565
2593
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: PropertyValueDisplayComponent, isStandalone: true, selector: "mm-property-value-display", inputs: { value: "value", type: "type", displayMode: "displayMode", attributeName: "attributeName" }, outputs: { binaryDownload: "binaryDownload" }, usesOnChanges: true, ngImport: i0, template: `
@@ -2885,6 +2913,7 @@ class PropertyGridComponent {
2885
2913
  propertyChange = new EventEmitter();
2886
2914
  saveRequested = new EventEmitter();
2887
2915
  binaryDownload = new EventEmitter();
2916
+ injector = inject(Injector);
2888
2917
  // Component state
2889
2918
  filteredData = [];
2890
2919
  searchTerm = '';
@@ -3019,10 +3048,11 @@ class PropertyGridComponent {
3019
3048
  this.propertyChange.emit(changeEvent);
3020
3049
  }
3021
3050
  /**
3022
- * Handle binary download request from property value display
3051
+ * Handle binary download request from property value display: forwarded to the host when it binds
3052
+ * `(binaryDownload)`, otherwise downloaded here with the user's bearer (AB#6179).
3023
3053
  */
3024
3054
  onBinaryDownload(event) {
3025
- this.binaryDownload.emit(event);
3055
+ emitOrDownloadBinary(this.binaryDownload, event, this.injector);
3026
3056
  }
3027
3057
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: PropertyGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3028
3058
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: PropertyGridComponent, isStandalone: true, selector: "mm-property-grid", inputs: { data: "data", config: "config", showTypeColumn: "showTypeColumn", messages: "messages" }, outputs: { propertyChange: "propertyChange", saveRequested: "saveRequested", binaryDownload: "binaryDownload" }, usesOnChanges: true, ngImport: i0, template: `
@@ -9759,41 +9789,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
9759
9789
  }]
9760
9790
  }], ctorParameters: () => [{ type: i1$3.Apollo }] });
9761
9791
 
9762
- const GetBinaryInfoDocumentDto = gql `
9763
- query getBinaryInfo($rtId: OctoObjectId!) {
9764
- runtime {
9765
- systemReportingFileSystemItem(rtId: $rtId) {
9766
- items {
9767
- rtId
9768
- ckTypeId
9769
- name
9770
- content {
9771
- binaryId
9772
- size
9773
- contentType
9774
- filename
9775
- downloadUri
9776
- }
9777
- }
9778
- }
9779
- }
9780
- }
9781
- `;
9782
- class GetBinaryInfoDtoGQL extends i1$3.Query {
9783
- document = GetBinaryInfoDocumentDto;
9784
- constructor(apollo) {
9785
- super(apollo);
9786
- }
9787
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: GetBinaryInfoDtoGQL, deps: [{ token: i1$3.Apollo }], target: i0.ɵɵFactoryTarget.Injectable });
9788
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: GetBinaryInfoDtoGQL, providedIn: 'root' });
9789
- }
9790
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: GetBinaryInfoDtoGQL, decorators: [{
9791
- type: Injectable,
9792
- args: [{
9793
- providedIn: 'root'
9794
- }]
9795
- }], ctorParameters: () => [{ type: i1$3.Apollo }] });
9796
-
9797
9792
  class EntityAssociationsDataSourceDirective extends OctoGraphQlDataSource {
9798
9793
  getAssociationsGQL = inject(GetRuntimeEntityAssociationsByIdDtoGQL);
9799
9794
  rtId = null;
@@ -9983,7 +9978,7 @@ const DEFAULT_RUNTIME_BROWSER_MESSAGES = {
9983
9978
  copiedToClipboard: 'copied to clipboard',
9984
9979
  failedToCopyToClipboard: 'Failed to copy to clipboard',
9985
9980
  downloadNotAvailable: 'Download not available for this file',
9986
- failedToLoadDownloadInfo: 'Failed to load download information',
9981
+ failedToLoadDownloadInfo: 'Failed to download the file',
9987
9982
  missingRequiredIdentifiers: 'Missing required identifiers to create an entity.',
9988
9983
  failedToCreateEntity: 'Failed to create entity. Please try again.',
9989
9984
  failedToUpdateEntity: 'Failed to update entity. Please try again.',
@@ -10332,7 +10327,7 @@ class EntityDetailViewComponent {
10332
10327
  associationsDataSource;
10333
10328
  notificationService = inject(NotificationService);
10334
10329
  propertyConverter = inject(PropertyConverterService);
10335
- getBinaryInfoGQL = inject(GetBinaryInfoDtoGQL);
10330
+ fileDownloadService = inject(FileDownloadService);
10336
10331
  _messages = {
10337
10332
  ...DEFAULT_RUNTIME_BROWSER_MESSAGES,
10338
10333
  };
@@ -10547,25 +10542,20 @@ class EntityDetailViewComponent {
10547
10542
  }
10548
10543
  /**
10549
10544
  * Handle binary download request from property grid.
10550
- * Loads the full binary info including downloadUri and opens the download.
10545
+ *
10546
+ * Downloads the clicked attribute's own value — whatever CK type owns it (Basic File,
10547
+ * System.Communication binaries, FileSystemItem, …) and whether or not the tenant has
10548
+ * System.Reporting. The value is the stored binary info `{ binaryId, filename, contentType, size }`
10549
+ * (plus `downloadUri` when a typed query supplied one); `FileDownloadService` fetches it with the
10550
+ * user's bearer and saves it under its real file name (AB#6179, decision D9).
10551
10551
  */
10552
- async onBinaryDownload(_event) {
10553
- if (!this.entity?.rtId) {
10554
- console.warn('Cannot download: no entity rtId available');
10555
- return;
10556
- }
10552
+ async onBinaryDownload(event) {
10557
10553
  try {
10558
- // Load binary info with downloadUri using the entity's rtId
10559
- const result = await firstValueFrom(this.getBinaryInfoGQL.fetch({
10560
- variables: { rtId: this.entity.rtId },
10561
- }));
10562
- const item = result.data?.runtime?.systemReportingFileSystemItem?.items?.[0];
10563
- if (item?.content?.downloadUri) {
10564
- // Open download in new tab
10565
- window.open(item.content.downloadUri, '_blank', 'noopener,noreferrer');
10566
- }
10567
- else {
10568
- console.warn('No downloadUri found for binary');
10554
+ await this.fileDownloadService.downloadBinary(event);
10555
+ }
10556
+ catch (error) {
10557
+ if (error instanceof BinaryDownloadUnavailableError) {
10558
+ console.warn('Binary download not available:', error.message);
10569
10559
  this.notificationService.show({
10570
10560
  content: this._messages.downloadNotAvailable,
10571
10561
  type: { style: 'warning', icon: true },
@@ -10573,10 +10563,9 @@ class EntityDetailViewComponent {
10573
10563
  hideAfter: 3000,
10574
10564
  animation: { type: 'fade', duration: 400 },
10575
10565
  });
10566
+ return;
10576
10567
  }
10577
- }
10578
- catch (error) {
10579
- console.error('Failed to load binary info:', error);
10568
+ console.error('Binary download failed:', error);
10580
10569
  this.notificationService.show({
10581
10570
  content: this._messages.failedToLoadDownloadInfo,
10582
10571
  type: { style: 'error', icon: true },