@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.
- package/fesm2022/yuuvis-client-core.mjs +277 -64
- package/fesm2022/yuuvis-client-core.mjs.map +1 -1
- package/lib/model/file-upload-options.model.d.ts +5 -0
- package/lib/service/dms/dms.service.d.ts +44 -3
- package/lib/service/dms/dms.service.interface.d.ts +1 -0
- package/lib/service/event/event.service.d.ts +1 -1
- package/lib/service/upload/upload.interface.d.ts +1 -0
- package/lib/service/upload/upload.service.d.ts +128 -2
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
|
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
|
|
3768
|
+
return this.#http.request(request).pipe(filter((obj) => obj?.body), transformResponse(), catchError((err) => throwError(() => err)));
|
|
3653
3769
|
}
|
|
3654
3770
|
/**
|
|
3655
|
-
* Cancels
|
|
3656
|
-
*
|
|
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
|
|
3693
|
-
url += `${url.
|
|
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,
|
|
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
|
|
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,
|
|
3720
|
-
const
|
|
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
|
|
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((
|
|
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((
|
|
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((
|
|
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
|
-
|
|
3947
|
+
uploadObserver.error(err);
|
|
3803
3948
|
this.#uploadStatus.next(true);
|
|
3804
|
-
|
|
3949
|
+
uploadObserver.complete();
|
|
3805
3950
|
},
|
|
3806
3951
|
complete: () => {
|
|
3807
|
-
|
|
3952
|
+
uploadObserver.next(result);
|
|
3808
3953
|
this.#uploadStatus.next(true);
|
|
3809
|
-
|
|
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
|
|
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
|
|
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((
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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) =>
|
|
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])
|
|
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,
|
|
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
|
|
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]
|
|
4268
|
-
|
|
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
|
-
|
|
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).
|