@yuuvis/client-core 2.13.0 → 2.15.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.
@@ -2368,7 +2368,6 @@ class EventService {
2368
2368
  }
2369
2369
  #listenToWindowEvents() {
2370
2370
  window.addEventListener('message', (event) => {
2371
- console.log(event);
2372
2371
  this.#ngZone.run(() => this.#isValidExternalMessage(event) && this.trigger(event.data.type, event.data.data));
2373
2372
  });
2374
2373
  }
@@ -3606,35 +3605,81 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
3606
3605
  }]
3607
3606
  }], ctorParameters: () => [] });
3608
3607
 
3609
- const transformResponse = () => map((res) => (res && res.body ? res.body.objects.map((val) => val) : null));
3608
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
3609
+ const transformResponse = () => map((res) => (res.body ? res.body.objects.map((val) => val) : null));
3610
3610
  /**
3611
3611
  * Service for providing upload of different object types into a client.
3612
3612
  */
3613
3613
  class UploadService {
3614
3614
  constructor() {
3615
+ // #region Dependencies
3615
3616
  this.#backend = inject(BackendService);
3616
3617
  this.#http = inject(HttpClient);
3617
3618
  this.#logger = inject(Logger);
3619
+ //#endregion
3620
+ //#region Properties
3618
3621
  this.#status = { err: 0, items: [] };
3619
3622
  this.#statusSource = new ReplaySubject();
3620
- this.status$ = this.#statusSource.pipe(scan((acc, newVal) => ({ ...acc, ...newVal }), this.#status));
3621
3623
  this.#uploadStatus = new BehaviorSubject(null);
3624
+ this.status$ = this.#statusSource.pipe(scan((acc, newVal) => ({ ...acc, ...newVal }), this.#status));
3622
3625
  this.uploadStatus$ = this.#uploadStatus.asObservable();
3623
3626
  }
3627
+ // #region Dependencies
3624
3628
  #backend;
3625
3629
  #http;
3626
3630
  #logger;
3631
+ //#endregion
3632
+ //#region Properties
3627
3633
  #status;
3628
3634
  #statusSource;
3629
3635
  #uploadStatus;
3636
+ //#endregion
3637
+ //#region Public Methods
3630
3638
  /**
3631
3639
  * Upload a file.
3632
3640
  * @param url The URL to upload the file to
3633
3641
  * @param file The file to be uploaded
3634
3642
  * @param label A label that will show up in the upload overlay dialog while uploading
3643
+ *
3644
+ * @deprecated use uploadFile instead. `label`, `silent` as well as `scope` can be provided through `options`.
3635
3645
  */
3636
3646
  upload(url, file, label, silent) {
3637
- return this.#executeUpload(url, file, label || file.name, silent);
3647
+ return this.#executeUpload(url, file, { label: label || file.name, silent: silent });
3648
+ }
3649
+ /**
3650
+ * Uploads a single file to the specified URL using the structured `FileUploadOptions` object.
3651
+ *
3652
+ * This is the preferred alternative to the legacy `upload()` method. It accepts a typed
3653
+ * options object instead of individual parameters, which makes it easier to pass optional
3654
+ * settings such as `silent` mode and `scope` without relying on positional arguments.
3655
+ *
3656
+ * **Behavior:**
3657
+ * - If `options.silent` is `false` (default), the upload appears in the upload overlay dialog
3658
+ * and progress is tracked via `status$`
3659
+ * - If `options.silent` is `true`, the upload runs quietly in the background with no UI feedback
3660
+ * - `options.label` is shown in the overlay dialog during upload; falls back to `file.name` if omitted
3661
+ * - `options.scope` tags the upload with a named scope, enabling an `UploadProgressComponent`
3662
+ * configured with the same scope to display only the uploads relevant to its section of the UI
3663
+ *
3664
+ * @param url - The backend URL to POST the file to
3665
+ * @param file - The `File` object to be uploaded
3666
+ * @param options - Upload configuration: label, silent mode, and optional scope
3667
+ * @returns An `Observable` that emits the server response on completion
3668
+ *
3669
+ * @example
3670
+ * ```typescript
3671
+ * // Standard upload with progress indicator
3672
+ * this.uploadService.uploadFile(url, file, { label: 'Uploading contract.pdf' }).subscribe();
3673
+ *
3674
+ * // Silent upload (no UI, runs in background)
3675
+ * this.uploadService.uploadFile(url, file, { label: 'document.pdf', silent: true }).subscribe();
3676
+ *
3677
+ * // Scoped upload — only the UploadProgressComponent with scope 'profile-editor' will show this
3678
+ * this.uploadService.uploadFile(url, file, { label: 'photo.jpg', scope: 'profile-editor' }).subscribe();
3679
+ * ```
3680
+ */
3681
+ uploadFile(url, file, options) {
3682
+ return this.#executeUpload(url, file, options);
3638
3683
  }
3639
3684
  /**
3640
3685
  * Upload files using multipart upload.
@@ -3642,18 +3687,110 @@ class UploadService {
3642
3687
  * @param files The files to be uploaded
3643
3688
  * @param data Data to be send along with the files
3644
3689
  * @param label A label that will show up in the upload overlay dialog while uploading
3690
+ *
3691
+ * @deprecated Use multipartUpload(url, files, options, data) instead. Provide scope through options.
3645
3692
  */
3646
3693
  uploadMultipart(url, files, data, label, silent) {
3647
- return this.#executeMultipartUpload(url, files, label || 'Upload', data, silent);
3694
+ return this.#executeMultipartUpload(url, files, { label, silent }, data);
3695
+ }
3696
+ /**
3697
+ * Uploads multiple files to the specified URL using a multipart/form-data request.
3698
+ *
3699
+ * This is the preferred method for multi-file uploads. It accepts a typed `FileUploadOptions`
3700
+ * object for configuration, making it easier to pass `scope` and other options compared
3701
+ * to the legacy `uploadMultipart()` overload.
3702
+ *
3703
+ * **Behavior:**
3704
+ * - All files are bundled into a single `FormData` payload under the `files` field
3705
+ * - Optional `data` is appended as a JSON blob under the `data` field
3706
+ * - If `options.silent` is `false` (default), upload progress is shown in the overlay dialog
3707
+ * - If `options.silent` is `true`, the upload runs in the background with no UI feedback
3708
+ * - `options.scope` tags the upload with a named scope, enabling an `UploadProgressComponent`
3709
+ * configured with the same scope to display only the uploads relevant to its section of the UI
3710
+ *
3711
+ * @param url - The backend URL to POST the multipart request to
3712
+ * @param files - Array of `File` objects to upload
3713
+ * @param options - Upload configuration: label (required), silent mode, and optional scope
3714
+ * @param data - Optional metadata object to send alongside the files (serialized as JSON)
3715
+ * @returns An `Observable` that emits the server response on completion
3716
+ *
3717
+ * @example
3718
+ * ```typescript
3719
+ * // Upload files with a visible progress indicator
3720
+ * this.uploadService.multipartUpload(url, files, { label: 'Uploading 3 files' }).subscribe();
3721
+ *
3722
+ * // Upload files with additional metadata
3723
+ * this.uploadService
3724
+ * .multipartUpload(url, files, { label: 'Batch upload' }, { folderId: 'abc123' })
3725
+ * .subscribe();
3726
+ *
3727
+ * // Scoped upload — only the UploadProgressComponent with scope 'mail-editor' will show this
3728
+ * this.uploadService
3729
+ * .multipartUpload(url, files, { label: 'attachments', scope: 'mail-editor' })
3730
+ * .subscribe();
3731
+ * ```
3732
+ */
3733
+ multipartUpload(url, files, options, data) {
3734
+ return this.#executeMultipartUpload(url, files, options, data);
3648
3735
  }
3736
+ /**
3737
+ * Creates a document on the server by sending structured metadata without a file attachment.
3738
+ *
3739
+ * Unlike `uploadFile()` and `multipartUpload()`, this method does not attach any file content.
3740
+ * It is intended for creating document objects from metadata alone — for example, creating
3741
+ * a folder, a placeholder record, or a document entry where the content stream will be
3742
+ * provided separately.
3743
+ *
3744
+ * **Behavior:**
3745
+ * - Sends the `data` object as a JSON blob inside a `FormData` payload
3746
+ * - Progress is NOT tracked — this method does not appear in `status$`
3747
+ * - Errors are re-thrown so the caller can handle them via the returned `Observable`
3748
+ *
3749
+ * @param url - The backend URL to POST the document creation request to
3750
+ * @param data - Metadata object describing the document to be created
3751
+ * @returns An `Observable` that emits the created object(s) on success
3752
+ *
3753
+ * @example
3754
+ * ```typescript
3755
+ * const metadata = {
3756
+ * objectTypeId: 'yuv:document',
3757
+ * properties: { 'yuv:title': 'My Document' }
3758
+ * };
3759
+ *
3760
+ * this.uploadService.createDocument(url, metadata).subscribe((result) => {
3761
+ * console.log('Created:', result);
3762
+ * });
3763
+ * ```
3764
+ */
3649
3765
  createDocument(url, data) {
3650
3766
  const formData = this.#createFormData({ data });
3651
3767
  const request = this.#createHttpRequest(url, { formData }, false);
3652
- return this.#http.request(request).pipe(filter((obj) => obj && obj.body), transformResponse(), catchError((err) => throwError(() => err)));
3768
+ return this.#http.request(request).pipe(filter((obj) => obj?.body), transformResponse(), catchError((err) => throwError(() => err)));
3653
3769
  }
3654
3770
  /**
3655
- * Cancels an upload request and removes it from the list of files being uploaded.
3656
- * @param id ID of the UploadItem to be canceled
3771
+ * Cancels one or all active upload requests and removes them from the tracked upload list.
3772
+ *
3773
+ * This method unsubscribes from the underlying HTTP request, effectively aborting the upload,
3774
+ * and removes the corresponding item(s) from the internal status list. The updated status
3775
+ * is then emitted via `status$` so that any subscribed UI components (e.g. the upload overlay)
3776
+ * can reflect the change immediately.
3777
+ *
3778
+ * **Behavior:**
3779
+ * - If `id` is provided, only the matching upload item is cancelled
3780
+ * - If `id` is omitted, **all** active uploads are cancelled at once
3781
+ * - If the provided `id` does not match any active item, the call is a no-op
3782
+ * - After cancellation, the error count on `status$` is recalculated
3783
+ *
3784
+ * @param id - Optional ID of the upload item to cancel. Omit to cancel all active uploads.
3785
+ *
3786
+ * @example
3787
+ * ```typescript
3788
+ * // Cancel a specific upload by ID
3789
+ * this.uploadService.cancelItem(uploadId);
3790
+ *
3791
+ * // Cancel all ongoing uploads (e.g. on component destroy or user confirmation)
3792
+ * this.uploadService.cancelItem();
3793
+ * ```
3657
3794
  */
3658
3795
  cancelItem(id) {
3659
3796
  if (id) {
@@ -3670,6 +3807,8 @@ class UploadService {
3670
3807
  this.#status.err = this.#status.items.filter((i) => i.err).length;
3671
3808
  this.#statusSource.next(this.#status);
3672
3809
  }
3810
+ //#endregion
3811
+ //#region Utilities
3673
3812
  /**
3674
3813
  * Prepares Formdata for multipart upload.
3675
3814
  * @param from contains form and or file
@@ -3677,6 +3816,7 @@ class UploadService {
3677
3816
  #createFormData({ file, data }) {
3678
3817
  const formData = new FormData();
3679
3818
  (file || []).forEach((f) => formData.append('files', f, f.name));
3819
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
3680
3820
  data ? formData.append('data', new Blob([JSON.stringify(data)], { type: 'application/json' })) : null;
3681
3821
  return formData;
3682
3822
  }
@@ -3689,8 +3829,8 @@ class UploadService {
3689
3829
  */
3690
3830
  #createHttpRequest(url, content, reportProgress, method = 'POST') {
3691
3831
  const { formData, file } = content;
3692
- // add request param to bypass the serviceworker
3693
- url += `${url.indexOf('?') === -1 ? '?' : '&'}ngsw-bypass=1`;
3832
+ // add request param to bypass the service-worker
3833
+ url += `${!url.includes('?') ? '?' : '&'}ngsw-bypass=1`;
3694
3834
  let headers = this.#backend.getAuthHeaders();
3695
3835
  if (file) {
3696
3836
  headers = headers.set('Content-Disposition', `attachment; filename*=utf-8''${encodeURIComponent(file.name)}`);
@@ -3703,25 +3843,29 @@ class UploadService {
3703
3843
  * @param file The file to be uploaded
3704
3844
  * @param label A label that will show up in the upload overlay dialog while uploading
3705
3845
  */
3706
- #executeUpload(url, file, label, silent = false) {
3846
+ #executeUpload(url, file, options) {
3847
+ const silent = options.silent === true;
3848
+ const label = options.label || file.name;
3707
3849
  const request = this.#createHttpRequest(url, { file }, !silent);
3708
3850
  return silent
3709
3851
  ? this.#http.request(request).pipe(filter((res) => res instanceof HttpResponse))
3710
- : this.#startUploadWithFile(request, label).pipe(transformResponse());
3852
+ : this.#startUploadWithFile(request, label, options.scope).pipe(transformResponse());
3711
3853
  }
3712
3854
  /**
3713
3855
  * Prepare multipart upload.
3714
3856
  * @param url The URL to upload the file to
3715
- * @param file Array of files to be uploaded
3857
+ * @param files Array of files to be uploaded
3716
3858
  * @param label A label that will show up in the upload overlay dialog while uploading
3717
3859
  * @param data Data to be send along with the files
3718
3860
  */
3719
- #executeMultipartUpload(url, file, label, data, silent = false) {
3720
- const formData = this.#createFormData({ file, data });
3861
+ #executeMultipartUpload(url, files, options, data) {
3862
+ const label = options.label ?? 'Upload';
3863
+ const silent = options.silent === true;
3864
+ const formData = this.#createFormData({ file: files, data });
3721
3865
  const request = this.#createHttpRequest(url, { formData }, !silent);
3722
3866
  return silent
3723
3867
  ? this.#http.request(request).pipe(filter((res) => res instanceof HttpResponse))
3724
- : this.#startUploadWithFile(request, label).pipe(transformResponse());
3868
+ : this.#startUploadWithFile(request, label, options.scope).pipe(transformResponse());
3725
3869
  }
3726
3870
  #generateResult(result) {
3727
3871
  const objects = result.body?.objects;
@@ -3753,14 +3897,15 @@ class UploadService {
3753
3897
  }
3754
3898
  #createProgressStatus(event, progress, id) {
3755
3899
  if (event.type === HttpEventType.UploadProgress) {
3756
- const percentDone = Math.round((100 * event.loaded) / event.total);
3900
+ const fullPercentage = 100;
3901
+ const percentDone = Math.round((fullPercentage * event.loaded) / event.total);
3757
3902
  progress.next(percentDone);
3758
3903
  }
3759
3904
  else if (event instanceof HttpResponse) {
3760
3905
  progress.complete();
3761
3906
  // add upload response
3762
3907
  // this.status.items = this.status.items.filter(s => s.id !== id);
3763
- const idx = this.#status.items.findIndex((s) => s.id === id);
3908
+ const idx = this.#status.items.findIndex((item) => item.id === id);
3764
3909
  if (idx !== -1) {
3765
3910
  this.#status.items[idx].result = this.#generateResult(event);
3766
3911
  this.#statusSource.next(this.#status);
@@ -3768,7 +3913,7 @@ class UploadService {
3768
3913
  }
3769
3914
  }
3770
3915
  #createUploadError(err, progress, id) {
3771
- const statusItem = this.#status.items.find((s) => s.id === id);
3916
+ const statusItem = this.#status.items.find((item) => item.id === id);
3772
3917
  statusItem.err = {
3773
3918
  code: err.status,
3774
3919
  message: err.error ? err.error.errorMessage : err.message
@@ -3784,8 +3929,8 @@ class UploadService {
3784
3929
  * @param request Request to be executed
3785
3930
  * @param label A label that will show up in the upload overlay dialog while uploading
3786
3931
  */
3787
- #startUploadWithFile(request, label) {
3788
- return new Observable((o) => {
3932
+ #startUploadWithFile(request, label, scope) {
3933
+ return new Observable((uploadObserver) => {
3789
3934
  const id = Utils.uuid();
3790
3935
  const progress = new Subject();
3791
3936
  let result;
@@ -3799,14 +3944,14 @@ class UploadService {
3799
3944
  .subscribe({
3800
3945
  next: (res) => (res.status ? (result = res) : null),
3801
3946
  error: (err) => {
3802
- o.error(err);
3947
+ uploadObserver.error(err);
3803
3948
  this.#uploadStatus.next(true);
3804
- o.complete();
3949
+ uploadObserver.complete();
3805
3950
  },
3806
3951
  complete: () => {
3807
- o.next(result);
3952
+ uploadObserver.next(result);
3808
3953
  this.#uploadStatus.next(true);
3809
- o.complete();
3954
+ uploadObserver.complete();
3810
3955
  }
3811
3956
  });
3812
3957
  this.#status.items.push({
@@ -3814,6 +3959,7 @@ class UploadService {
3814
3959
  filename: label,
3815
3960
  progress: progress.asObservable(),
3816
3961
  subscription,
3962
+ scope: scope,
3817
3963
  err: undefined
3818
3964
  });
3819
3965
  this.#statusSource.next(this.#status);
@@ -3837,26 +3983,6 @@ class DmsService {
3837
3983
  #backend = inject(BackendService);
3838
3984
  #eventService = inject(EventService);
3839
3985
  #uploadService = inject(UploadService);
3840
- // general trigger operator to handle all dms events
3841
- triggerEvent(event, id, silent = false) {
3842
- return (stream) => stream.pipe(
3843
- // update does not return permissions, so we need to re-load the whole dms object
3844
- // TODO: Remove once permissions are provided
3845
- switchMap((res) => (!id ? of(res) : this.getDmsObject(id))),
3846
- // TODO: enable once permissions are provided
3847
- // map((res) => this.searchResultToDmsObject(this.searchService.toSearchResult(res).items[0])),
3848
- tap((res) => !silent && this.#eventService.trigger(event, res)));
3849
- }
3850
- // general trigger operator to handle all dms events
3851
- #triggerEvents(event, ids, silent = false) {
3852
- return (stream) => stream.pipe(
3853
- // update does not return permissions, so we need to re-load the whole dms object
3854
- // TODO: Remove once permissions are provided
3855
- switchMap((res) => (!ids ? of(res) : this.getDmsObjects(ids))),
3856
- // TODO: enable once permissions are provided
3857
- // map((_res: any[]) => _res.map((res, i) => res?._error ? { ...res, id: ids?[i] } : this.searchResultToDmsObject(this.searchService.toSearchResult(res).items[0]))),
3858
- map((_res) => _res.map((res, i) => (res?._error && ids ? { ...res, id: ids[i] } : res))), tap((res) => !silent && res.forEach((o) => o && this.#eventService.trigger(event, o))));
3859
- }
3860
3986
  /**
3861
3987
  * Create new dms object(s). Providing an array of files here instead of one will create
3862
3988
  * a new dms object for every file. In this case indexdata will shared across all files.
@@ -3868,12 +3994,15 @@ class DmsService {
3868
3994
  * @returns Array of IDs of the objects that have been created
3869
3995
  */
3870
3996
  createDmsObject(objectTypeId, indexdata, files, label, silent = false, options = { waitForSearchConsistency: true }) {
3871
- const url = `${this.#backend.getApiBase(ApiBase.apiWeb)}/dms/objects${options.waitForSearchConsistency ? '?waitForSearchConsistency=true' : ''}`;
3997
+ const url = `${this.#backend.getApiBase(ApiBase.apiWeb)}/dms/objects` +
3998
+ `${options.waitForSearchConsistency ? '?waitForSearchConsistency=true' : ''}`;
3872
3999
  const data = indexdata;
3873
4000
  data[BaseObjectTypeField.OBJECT_TYPE_ID] = objectTypeId;
3874
- const upload = files.length ? this.#uploadService.uploadMultipart(url, files, data, label, silent) : this.#uploadService.createDocument(url, data);
4001
+ const upload = files.length
4002
+ ? this.#uploadService.multipartUpload(url, files, { label: label ?? '', silent, scope: options.scope }, data)
4003
+ : this.#uploadService.createDocument(url, data);
3875
4004
  return upload
3876
- .pipe(map((res) => res.map((r) => r.properties[BaseObjectTypeField.OBJECT_ID].value)),
4005
+ .pipe(map((res) => res.map((response) => response.properties[BaseObjectTypeField.OBJECT_ID].value)),
3877
4006
  // TODO: Replace by proper solution
3878
4007
  // Right now there is a gap between when the object was
3879
4008
  // created and when it is indexed. So delaying here will
@@ -3898,17 +4027,66 @@ class DmsService {
3898
4027
  * @param version version of the object to be restored
3899
4028
  */
3900
4029
  restoreDmsObject(id, version, silent = false) {
4030
+ // eslint-disable-next-line max-len
3901
4031
  const url = `/dms/objects/${id}/versions/${version}/actions/restore?waitForSearchConsistency=true&restoreParentId=false`;
3902
- return this.#backend.post(url, {}, ApiBase.apiWeb).pipe(this.triggerEvent(YuvEventType.DMS_OBJECT_UPDATED, id, silent));
4032
+ return this.#backend
4033
+ .post(url, {}, ApiBase.apiWeb)
4034
+ .pipe(this.triggerEvent(YuvEventType.DMS_OBJECT_UPDATED, id, silent));
3903
4035
  }
3904
4036
  /**
3905
4037
  * Upload (add/replace) content to a dms object.
3906
4038
  * @param objectId ID of the dms object to upload the file to
3907
4039
  * @param file The file to be uploaded
3908
4040
  * @param label A label that will show up in the upload overlay dialog while uploading
4041
+ *
4042
+ * @deprecated use uploadFileContent instead. Provide label and silent through `options`
3909
4043
  */
3910
4044
  uploadContent(objectId, file, label, silent) {
3911
- return this.#uploadService.upload(this.getContentPath(objectId), file, label, silent).pipe(this.triggerEvent(YuvEventType.DMS_OBJECT_UPDATED, objectId));
4045
+ return this.#uploadService
4046
+ .upload(this.getContentPath(objectId), file, label, silent)
4047
+ .pipe(this.triggerEvent(YuvEventType.DMS_OBJECT_UPDATED, objectId));
4048
+ }
4049
+ /**
4050
+ * Uploads a file as the content of an existing DMS object, replacing or setting its content stream.
4051
+ *
4052
+ * This is the preferred alternative to the legacy `uploadContent()` method. It accepts a typed
4053
+ * `FileUploadOptions` object instead of individual parameters, which enables full control over
4054
+ * the upload label, silent mode, and scope-based progress visibility.
4055
+ *
4056
+ * After a successful upload, a `DMS_OBJECT_UPDATED` event is triggered automatically so that
4057
+ * any subscribed parts of the application can react to the change (e.g. refreshing a preview).
4058
+ *
4059
+ * **Behavior:**
4060
+ * - Targets the content endpoint of the given DMS object: `/dms/objects/{objectId}/contents/file`
4061
+ * - `options.label` is shown in the upload progress indicator; falls back to `file.name` if omitted
4062
+ * - If `options.silent` is `true`, the upload runs in the background with no UI feedback
4063
+ * and no `DMS_OBJECT_UPDATED` event is emitted
4064
+ * - `options.scope` tags the upload so that only the `UploadProgressComponent` instance
4065
+ * configured with the same scope will display progress for this upload
4066
+ *
4067
+ * @param objectId - ID of the DMS object whose content should be uploaded or replaced
4068
+ * @param file - The `File` object to upload as the new content stream
4069
+ * @param options - Upload configuration: label, silent mode, and optional scope
4070
+ * @returns An `Observable` that emits the updated DMS object on completion
4071
+ *
4072
+ * @example
4073
+ * ```typescript
4074
+ * // Replace content with a visible progress indicator
4075
+ * this.dmsService.uploadFileContent(objectId, file, { label: 'Uploading contract.pdf' }).subscribe();
4076
+ *
4077
+ * // Silent replacement (no UI feedback, no event emitted)
4078
+ * this.dmsService.uploadFileContent(objectId, file, { label: 'report.pdf', silent: true }).subscribe();
4079
+ *
4080
+ * // Scoped upload — only UploadProgressComponent with scope 'detail-panel' will show this
4081
+ * this.dmsService
4082
+ * .uploadFileContent(objectId, file, { label: 'photo.jpg', scope: 'detail-panel' })
4083
+ * .subscribe();
4084
+ * ```
4085
+ */
4086
+ uploadFileContent(objectId, file, options) {
4087
+ return this.#uploadService
4088
+ .uploadFile(this.getContentPath(objectId), file, { ...options, label: options.label ?? file.name })
4089
+ .pipe(this.triggerEvent(YuvEventType.DMS_OBJECT_UPDATED, objectId));
3912
4090
  }
3913
4091
  /**
3914
4092
  * Path of dms object content file.
@@ -3916,7 +4094,8 @@ class DmsService {
3916
4094
  * @param version version number of the dms object
3917
4095
  */
3918
4096
  getContentPath(objectId, version) {
3919
- return `${this.#backend.getApiBase(ApiBase.apiWeb)}/dms/objects/${objectId}/contents/file${version ? '?version=' + version : ''}`;
4097
+ return (`${this.#backend.getApiBase(ApiBase.apiWeb)}/dms/objects/` +
4098
+ `${objectId}/contents/file${version ? '?version=' + version : ''}`);
3920
4099
  }
3921
4100
  /**
3922
4101
  * Original API Path of dms object content file.
@@ -3925,7 +4104,8 @@ class DmsService {
3925
4104
  * @param rendition should return rendition path of the dms object
3926
4105
  */
3927
4106
  getFullContentPath(objectId, version, rendition = false) {
3928
- return `${this.#backend.getApiBase(ApiBase.core, true)}/dms/objects/${objectId}${version ? '/versions/' + version : ''}/contents/${rendition ? 'renditions/pdf' : 'file'}`;
4107
+ return (`${this.#backend.getApiBase(ApiBase.core, true)}/dms/objects/` +
4108
+ `${objectId}${version ? '/versions/' + version : ''}/contents/${rendition ? 'renditions/pdf' : 'file'}`);
3929
4109
  }
3930
4110
  getSlideURI(objectId, mimeType) {
3931
4111
  const supportedMimeTypes = ['*/*'];
@@ -3934,7 +4114,9 @@ class DmsService {
3934
4114
  // check if mime type supports slides
3935
4115
  supportedMimeTypes.forEach((p) => (supported = supported || Utils.patternToRegExp(p).test(mimeType)));
3936
4116
  }
3937
- return !mimeType || supported ? `${this.#backend.getApiBase(ApiBase.core, true)}/dms/objects/${objectId}/contents/renditions/slide` : undefined;
4117
+ return !mimeType || supported
4118
+ ? `${this.#backend.getApiBase(ApiBase.core, true)}/dms/objects/${objectId}/contents/renditions/slide`
4119
+ : undefined;
3938
4120
  }
3939
4121
  /**
3940
4122
  * Downloads the content of dms objects.
@@ -3992,7 +4174,9 @@ class DmsService {
3992
4174
  * @param tag The tag to be deleted
3993
4175
  */
3994
4176
  deleteDmsObjectTag(id, tag, silent = false) {
3995
- return this.#backend.delete(`/dms/objects/${id}/tags/${tag}`, ApiBase.core).pipe(this.triggerEvent(YuvEventType.DMS_OBJECT_UPDATED, id, silent));
4177
+ return this.#backend
4178
+ .delete(`/dms/objects/${id}/tags/${tag}`, ApiBase.core)
4179
+ .pipe(this.triggerEvent(YuvEventType.DMS_OBJECT_UPDATED, id, silent));
3996
4180
  }
3997
4181
  /**
3998
4182
  * Update indexdata of a dms object.
@@ -4093,7 +4277,9 @@ class DmsService {
4093
4277
  */
4094
4278
  getDmsObjects(ids, silent = false) {
4095
4279
  return this.batchGet(ids)
4096
- .pipe(map((_res) => _res.map((res, i) => (res?._error ? { ...res, id: ids[i] } : this.#searchResultToDmsObject(this.#searchService.toSearchResult(res).items[0])))))
4280
+ .pipe(map((_res) => _res.map((res, i) => res?._error
4281
+ ? { ...res, id: ids[i] }
4282
+ : this.#searchResultToDmsObject(this.#searchService.toSearchResult(res).items[0]))))
4097
4283
  .pipe(this.#triggerEvents(YuvEventType.DMS_OBJECT_LOADED, undefined, silent));
4098
4284
  }
4099
4285
  /**
@@ -4158,9 +4344,6 @@ class DmsService {
4158
4344
  content: res.contentStreams?.[0]
4159
4345
  });
4160
4346
  }
4161
- #searchResultToDmsObject(resItem) {
4162
- return new DmsObject(resItem);
4163
- }
4164
4347
  /**
4165
4348
  * Transforms a plain data object to a DmsObject.
4166
4349
  * @param data The plain data object
@@ -4224,7 +4407,9 @@ class DmsService {
4224
4407
  if (o.properties[key].resolvedValues) {
4225
4408
  value.forEach((v) => {
4226
4409
  Object.keys(v).forEach((k) => {
4227
- const resValue = Array.isArray(v[k]) ? v[k].map((i) => o.properties[key].resolvedValues[i]) : o.properties[key].resolvedValues[v[k]];
4410
+ const resValue = Array.isArray(v[k])
4411
+ ? v[k].map((i) => o.properties[key].resolvedValues[i])
4412
+ : o.properties[key].resolvedValues[v[k]];
4228
4413
  if (resValue) {
4229
4414
  v[`${k}_title`] = resValue;
4230
4415
  }
@@ -4241,10 +4426,10 @@ class DmsService {
4241
4426
  // Objects that don't have files attached won't have this section
4242
4427
  let content;
4243
4428
  if (o.contentStreams && o.contentStreams.length > 0) {
4244
- // we assume that each result object only has ONE file attached, altough
4429
+ // we assume that each result object only has ONE file attached, although
4245
4430
  // this is an array and there may be more
4246
4431
  const contentStream = o.contentStreams[0];
4247
- // also add contentstream related fields to the result fields
4432
+ // also add content-stream related fields to the result fields
4248
4433
  fields.set(ContentStreamField.LENGTH, contentStream.length);
4249
4434
  fields.set(ContentStreamField.MIME_TYPE, contentStream.mimeType);
4250
4435
  fields.set(ContentStreamField.FILENAME, contentStream.fileName);
@@ -4264,8 +4449,10 @@ class DmsService {
4264
4449
  size: contentStream.length
4265
4450
  };
4266
4451
  }
4267
- const objectTypeId = o.properties[BaseObjectTypeField.OBJECT_TYPE_ID] ? o.properties[BaseObjectTypeField.OBJECT_TYPE_ID].value : null;
4268
- if (objectTypes.indexOf(objectTypeId) === -1) {
4452
+ const objectTypeId = o.properties[BaseObjectTypeField.OBJECT_TYPE_ID]
4453
+ ? o.properties[BaseObjectTypeField.OBJECT_TYPE_ID].value
4454
+ : null;
4455
+ if (!objectTypes.includes(objectTypeId)) {
4269
4456
  objectTypes.push(objectTypeId);
4270
4457
  }
4271
4458
  resultListItems.push({
@@ -4283,6 +4470,30 @@ class DmsService {
4283
4470
  };
4284
4471
  return result;
4285
4472
  }
4473
+ // general trigger operator to handle all dms events
4474
+ triggerEvent(event, id, silent = false) {
4475
+ return (stream) => stream.pipe(
4476
+ // update does not return permissions, so we need to re-load the whole dms object
4477
+ // TODO: Remove once permissions are provided
4478
+ switchMap((res) => (!id ? of(res) : this.getDmsObject(id))),
4479
+ // TODO: enable once permissions are provided
4480
+ // map((res) => this.searchResultToDmsObject(this.searchService.toSearchResult(res).items[0])),
4481
+ tap((res) => !silent && this.#eventService.trigger(event, res)));
4482
+ }
4483
+ // general trigger operator to handle all dms events
4484
+ #triggerEvents(event, ids, silent = false) {
4485
+ return (stream) => stream.pipe(
4486
+ // update does not return permissions, so we need to re-load the whole dms object
4487
+ // TODO: Remove once permissions are provided
4488
+ switchMap((res) => (!ids ? of(res) : this.getDmsObjects(ids))),
4489
+ // TODO: enable once permissions are provided
4490
+ // map((_res: any[]) => _res.map((res, i) => res?._error ?
4491
+ // { ...res, id: ids?[i] } : this.searchResultToDmsObject(this.searchService.toSearchResult(res).items[0]))),
4492
+ map((_res) => _res.map((res, i) => (res?._error && ids ? { ...res, id: ids[i] } : res))), tap((res) => !silent && res.forEach((o) => o && this.#eventService.trigger(event, o))));
4493
+ }
4494
+ #searchResultToDmsObject(resItem) {
4495
+ return new DmsObject(resItem);
4496
+ }
4286
4497
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DmsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4287
4498
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DmsService, providedIn: 'root' }); }
4288
4499
  }
@@ -5747,7 +5958,9 @@ let EoxTranslateJsonLoader = class EoxTranslateJsonLoader {
5747
5958
  return forkJoin(t).pipe(map((res) => res.reduce((acc, x) => Object.assign(acc, x), {})));
5748
5959
  }
5749
5960
  loadTranslationFile(path, lang) {
5750
- return this.http.get(`${Utils.getBaseHref()}${path}${lang}.json`).pipe(catchError(() => {
5961
+ const version = document.body.dataset['bt'] ?? document.body.dataset['version'];
5962
+ const cacheBuster = version ? `?v=${version}` : '';
5963
+ return this.http.get(`${Utils.getBaseHref()}${path}${lang}.json${cacheBuster}`).pipe(catchError(() => {
5751
5964
  // ISO codes with more than 2 characters are sub-languages like de-CH.
5752
5965
  // If there is no translation file for that sub-language we'll try to load
5753
5966
  // the file for the base language (in this case de).