@yuuvis/client-core 3.2.1 → 3.2.2

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.
@@ -5,11 +5,12 @@ import * as i1 from '@angular/common/http';
5
5
  import { HttpErrorResponse, HttpClient, HttpHeaders, HttpRequest, HttpParams, HttpResponse, HttpEventType, provideHttpClient, withInterceptors } from '@angular/common/http';
6
6
  import * as i0 from '@angular/core';
7
7
  import { inject, Injectable, InjectionToken, NgZone, DOCUMENT, Inject, signal, Directive, Pipe, provideEnvironmentInitializer, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';
8
- import { tap, finalize, shareReplay, catchError, filter, map, switchMap, first, scan, delay } from 'rxjs/operators';
9
- import { EMPTY, Subject, of, forkJoin, tap as tap$1, catchError as catchError$1, Observable, ReplaySubject, BehaviorSubject, map as map$1, fromEvent, throwError, switchMap as switchMap$1, merge, filter as filter$1, debounceTime, isObservable } from 'rxjs';
8
+ import { tap, finalize, shareReplay, catchError, map, switchMap, first, filter, scan, delay } from 'rxjs/operators';
9
+ import { EMPTY, Subject, of, forkJoin, Observable, tap as tap$1, catchError as catchError$1, ReplaySubject, BehaviorSubject, map as map$1, throwError, switchMap as switchMap$1, merge, fromEvent, filter as filter$1, debounceTime, isObservable } from 'rxjs';
10
10
  import { StorageMap } from '@ngx-pwa/local-storage';
11
11
  import { __decorate, __param, __metadata } from 'tslib';
12
12
  import { coerceBooleanProperty } from '@angular/cdk/coercion';
13
+ import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
13
14
  import { registerLocaleData, DecimalPipe, PercentPipe, CurrencyPipe } from '@angular/common';
14
15
  import localeAr from '@angular/common/locales/ar';
15
16
  import localeBn from '@angular/common/locales/bn';
@@ -59,7 +60,6 @@ import localeTr from '@angular/common/locales/tr';
59
60
  import localeUk from '@angular/common/locales/uk';
60
61
  import localeVi from '@angular/common/locales/vi';
61
62
  import localeZh from '@angular/common/locales/zh';
62
- import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
63
63
  import { DeviceDetectorService } from 'ngx-device-detector';
64
64
  import { FormGroup, FormControl } from '@angular/forms';
65
65
  import { MatTabGroup } from '@angular/material/tabs';
@@ -404,6 +404,28 @@ class YuvUser {
404
404
  }
405
405
  }
406
406
 
407
+ var AuditAction;
408
+ (function (AuditAction) {
409
+ AuditAction["CREATE_METADATA"] = "100";
410
+ AuditAction["CREATE_METADATA_WITH_CONTENT"] = "101";
411
+ AuditAction["CREATE_TAG"] = "110";
412
+ AuditAction["DELETE"] = "200";
413
+ AuditAction["DELETE_CONTENT"] = "201";
414
+ AuditAction["DELETE_MARKED"] = "202";
415
+ AuditAction["DELETE_TAG"] = "210";
416
+ AuditAction["UPDATE_METADATA"] = "300";
417
+ AuditAction["UPDATE_CONTENT"] = "301";
418
+ AuditAction["UPDATE_METADATA_WITH_CONTENT"] = "302";
419
+ AuditAction["UPDATE_MOVE_CONTENT"] = "303";
420
+ AuditAction["UPDATE_TAG"] = "310";
421
+ AuditAction["OBJECT_RESTORED_FROM_VERSION"] = "325";
422
+ AuditAction["GET_CONTENT"] = "400";
423
+ AuditAction["GET_METADATA"] = "401";
424
+ AuditAction["GET_RENDITION_TEXT"] = "402";
425
+ AuditAction["GET_RENDITION_PDF"] = "403";
426
+ AuditAction["GET_RENDITION_THUMBNAIL"] = "404";
427
+ })(AuditAction || (AuditAction = {}));
428
+
407
429
  var Sort;
408
430
  (function (Sort) {
409
431
  Sort["ASC"] = "asc";
@@ -1262,111 +1284,115 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
1262
1284
  args: [{ providedIn: 'root' }]
1263
1285
  }] });
1264
1286
 
1265
- var Direction;
1266
- (function (Direction) {
1267
- Direction["LTR"] = "ltr";
1268
- Direction["RTL"] = "rtl";
1269
- })(Direction || (Direction = {}));
1270
-
1271
- const CUSTOM_EVENTS = new InjectionToken('CUSTOM_EVENTS', { factory: () => [] });
1272
- const CUSTOM_EVENTS_TRUSTED_ORIGINS = new InjectionToken('CUSTOM_EVENTS_TRUSTED_ORIGINS', { factory: () => [] });
1273
-
1274
- /**
1275
- * Mandatory Custom event prefix for all custom YUV events
1276
- */
1277
- const CUSTOM_YUV_EVENT_PREFIX = 'yuv.';
1287
+ var Operator;
1288
+ (function (Operator) {
1289
+ Operator["EQUAL"] = "eq";
1290
+ Operator["EEQUAL"] = "eeq";
1291
+ Operator["IN"] = "in";
1292
+ Operator["GREATER_THAN"] = "gt";
1293
+ Operator["GREATER_OR_EQUAL"] = "gte";
1294
+ Operator["LESS_THAN"] = "lt";
1295
+ Operator["LESS_OR_EQUAL"] = "lte";
1296
+ Operator["INTERVAL"] = "gtlt";
1297
+ Operator["INTERVAL_INCLUDE_BOTH"] = "gtelte";
1298
+ Operator["INTERVAL_INCLUDE_TO"] = "gtlte";
1299
+ Operator["INTERVAL_INCLUDE_FROM"] = "gtelt";
1300
+ Operator["RANGE"] = "rg";
1301
+ Operator["LIKE"] = "like";
1302
+ Operator["CONTAINS"] = "contains"; // contains
1303
+ })(Operator || (Operator = {}));
1304
+ const OperatorLabel = {
1305
+ /** equal */
1306
+ EQUAL: '=',
1307
+ /** exact equal */
1308
+ EEQUAL: '==',
1309
+ /** match at least one of the provided values (value has to be an array) */
1310
+ IN: '~',
1311
+ /** greater than */
1312
+ GREATER_THAN: '>',
1313
+ /** greater than or equal */
1314
+ GREATER_OR_EQUAL: '≽', //
1315
+ LESS_THAN: '<', // less than
1316
+ LESS_OR_EQUAL: '≼', // less than or equal
1317
+ INTERVAL: '<>', // interval
1318
+ INTERVAL_INCLUDE_BOTH: '-', // interval include left and right
1319
+ INTERVAL_INCLUDE_TO: '>-', // interval include right
1320
+ INTERVAL_INCLUDE_FROM: '-<', // interval include left
1321
+ RANGE: '=', // aggegation ranges
1322
+ LIKE: '~', // like
1323
+ CONTAINS: '~' // contains
1324
+ };
1278
1325
 
1279
1326
  /**
1280
- * Service for providing triggered events
1327
+ * Service for saving or caching data on the users device. It uses the most efficient storage
1328
+ * available (IndexDB, localstorage, ...) on the device. Depending on the type of storage used,
1329
+ * its limitations apply.
1281
1330
  */
1282
- class EventService {
1283
- #customEvents;
1284
- #customEventsTrustedOrigins;
1285
- #ngZone;
1286
- #eventSource;
1287
- constructor() {
1288
- this.#customEvents = inject(CUSTOM_EVENTS);
1289
- this.#customEventsTrustedOrigins = inject(CUSTOM_EVENTS_TRUSTED_ORIGINS);
1290
- this.#ngZone = inject(NgZone);
1291
- this.#eventSource = new Subject();
1292
- this.event$ = this.#eventSource.asObservable();
1293
- this.#listenToWindowEvents();
1294
- }
1295
- #startsWithAllowed(value) {
1296
- return typeof value === 'string' && this.#customEvents.some((prefix) => value.startsWith(prefix));
1297
- }
1298
- #isValidExternalMessage(event) {
1299
- this.#customEvents.push(CUSTOM_YUV_EVENT_PREFIX);
1300
- if (!event.data || typeof event.data !== 'object')
1301
- return false;
1302
- if (!(this.#startsWithAllowed(event.data.source) || this.#startsWithAllowed(event.data.type)))
1303
- return false;
1304
- // Accept messages from trusted origins
1305
- this.#customEventsTrustedOrigins.push(window.location.origin);
1306
- const isFromTrustedOrigin = this.#customEventsTrustedOrigins.includes(event.origin);
1307
- const hasValidStructure = event.data && (event.data.type || event.data.eventType) && typeof event.data === 'object';
1308
- return isFromTrustedOrigin && hasValidStructure;
1331
+ class AppCacheService {
1332
+ #storage = inject(StorageMap);
1333
+ /**
1334
+ * Set item in storage.
1335
+ * @param key The key under which the value is stored.
1336
+ * @param value The value to store.
1337
+ * @returns An Observable that emits true when the operation is successful.
1338
+ */
1339
+ setItem(key, value) {
1340
+ return this.#storage.set(key, value).pipe(map(() => true));
1309
1341
  }
1310
- #listenToWindowEvents() {
1311
- window.addEventListener('message', (event) => {
1312
- this.#ngZone.run(() => this.#isValidExternalMessage(event) && this.trigger(event.data.type, event.data.data));
1313
- });
1342
+ /**
1343
+ * Get item from storage.
1344
+ * @param key The key of the item to retrieve.
1345
+ * @returns An Observable that emits the retrieved value.
1346
+ */
1347
+ getItem(key) {
1348
+ return this.#storage.get(key);
1314
1349
  }
1315
1350
  /**
1316
- * Triggers a postMessage event that will be sent to the yuuvis global event Trigger
1317
- * @param type Type/key of the event
1318
- * @param data Data to be sent along with the event
1351
+ * Remove item from storage.
1352
+ * @param key The key of the item to remove.
1353
+ * @returns An Observable that emits true when the operation is successful.
1319
1354
  */
1320
- triggerPostMessageEvent(type, data) {
1321
- const targetOrigin = window.location.origin;
1322
- const payload = {
1323
- type,
1324
- data,
1325
- timestamp: Date.now()
1326
- };
1327
- window.postMessage(payload, targetOrigin);
1355
+ removeItem(key) {
1356
+ return this.#storage.delete(key).pipe(map(() => true));
1328
1357
  }
1329
1358
  /**
1330
- * Trigger an global event
1331
- * @param type Type/key of the event
1332
- * @param data Data to be send along with the event
1359
+ * Clear storage, optionally filtered by a function.
1360
+ * @param filter A function to filter which keys to clear.
1361
+ * @returns An Observable that emits true when the operation is successful.
1333
1362
  */
1334
- trigger(type, ...args) {
1335
- this.#eventSource.next({ type: type, data: args[0] });
1363
+ clear(filter) {
1364
+ return filter
1365
+ ? this.getStorageKeys().pipe(switchMap((keys) => {
1366
+ const list = keys.filter((k) => filter(k)).map((k) => this.removeItem(k));
1367
+ return list.length ? forkJoin(list).pipe(map(() => true)) : of(true);
1368
+ }))
1369
+ : this.#storage.clear().pipe(map(() => true));
1336
1370
  }
1337
1371
  /**
1338
- * Listen on a triggered event
1339
- * @param types Type/key of the event
1372
+ * Get all storage keys.
1373
+ * @returns An Observable that emits an array of all storage keys.
1340
1374
  */
1341
- on(...types) {
1342
- return this.event$.pipe(filter((event) => event && !!types.find((t) => t === event.type)));
1375
+ getStorageKeys() {
1376
+ return new Observable((observer) => {
1377
+ const keys = [];
1378
+ this.#storage.keys().subscribe({
1379
+ next: (key) => keys.push(key),
1380
+ complete: () => observer.next(keys)
1381
+ });
1382
+ }).pipe(first());
1343
1383
  }
1344
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EventService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1345
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EventService, providedIn: 'root' }); }
1384
+ setStorage(options) {
1385
+ return forkJoin(Object.keys(options || {}).map((k) => this.setItem(k, options[k])));
1386
+ }
1387
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AppCacheService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1388
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AppCacheService, providedIn: 'root' }); }
1346
1389
  }
1347
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EventService, decorators: [{
1390
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AppCacheService, decorators: [{
1348
1391
  type: Injectable,
1349
1392
  args: [{
1350
1393
  providedIn: 'root'
1351
1394
  }]
1352
- }], ctorParameters: () => [] });
1353
-
1354
- /**
1355
- * Events emitted by parts of the application
1356
- */
1357
- var YuvEventType;
1358
- (function (YuvEventType) {
1359
- YuvEventType["LOGOUT"] = "yuv.user.logout";
1360
- YuvEventType["CLIENT_LOCALE_CHANGED"] = "yuv.user.locale.client.changed";
1361
- YuvEventType["DMS_OBJECT_LOADED"] = "yuv.dms.object.loaded";
1362
- YuvEventType["DMS_OBJECT_CREATED"] = "yuv.dms.object.created";
1363
- YuvEventType["DMS_OBJECT_DELETED"] = "yuv.dms.object.deleted";
1364
- YuvEventType["DMS_OBJECT_UPDATED"] = "yuv.dms.object.updated";
1365
- YuvEventType["DMS_OBJECT_CONTENT_UPDATED"] = "yuv.dms.object.content.updated";
1366
- YuvEventType["DMS_OBJECTS_MOVED"] = "yuv.dms.objects.moved";
1367
- YuvEventType["RELATIONSHIP_CREATED"] = "yuv.dms.relationship.created";
1368
- YuvEventType["RELATIONSHIP_DELETED"] = "yuv.dms.relationship.deleted";
1369
- })(YuvEventType || (YuvEventType = {}));
1395
+ }] });
1370
1396
 
1371
1397
  /**
1372
1398
  * Service managing localizations. The localizations are fetched
@@ -1432,77 +1458,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
1432
1458
  }]
1433
1459
  }] });
1434
1460
 
1435
- /**
1436
- * Service for saving or caching data on the users device. It uses the most efficient storage
1437
- * available (IndexDB, localstorage, ...) on the device. Depending on the type of storage used,
1438
- * its limitations apply.
1439
- */
1440
- class AppCacheService {
1441
- #storage = inject(StorageMap);
1442
- /**
1443
- * Set item in storage.
1444
- * @param key The key under which the value is stored.
1445
- * @param value The value to store.
1446
- * @returns An Observable that emits true when the operation is successful.
1447
- */
1448
- setItem(key, value) {
1449
- return this.#storage.set(key, value).pipe(map(() => true));
1450
- }
1451
- /**
1452
- * Get item from storage.
1453
- * @param key The key of the item to retrieve.
1454
- * @returns An Observable that emits the retrieved value.
1455
- */
1456
- getItem(key) {
1457
- return this.#storage.get(key);
1458
- }
1459
- /**
1460
- * Remove item from storage.
1461
- * @param key The key of the item to remove.
1462
- * @returns An Observable that emits true when the operation is successful.
1463
- */
1464
- removeItem(key) {
1465
- return this.#storage.delete(key).pipe(map(() => true));
1466
- }
1467
- /**
1468
- * Clear storage, optionally filtered by a function.
1469
- * @param filter A function to filter which keys to clear.
1470
- * @returns An Observable that emits true when the operation is successful.
1471
- */
1472
- clear(filter) {
1473
- return filter
1474
- ? this.getStorageKeys().pipe(switchMap((keys) => {
1475
- const list = keys.filter((k) => filter(k)).map((k) => this.removeItem(k));
1476
- return list.length ? forkJoin(list).pipe(map(() => true)) : of(true);
1477
- }))
1478
- : this.#storage.clear().pipe(map(() => true));
1479
- }
1480
- /**
1481
- * Get all storage keys.
1482
- * @returns An Observable that emits an array of all storage keys.
1483
- */
1484
- getStorageKeys() {
1485
- return new Observable((observer) => {
1486
- const keys = [];
1487
- this.#storage.keys().subscribe({
1488
- next: (key) => keys.push(key),
1489
- complete: () => observer.next(keys)
1490
- });
1491
- }).pipe(first());
1492
- }
1493
- setStorage(options) {
1494
- return forkJoin(Object.keys(options || {}).map((k) => this.setItem(k, options[k])));
1495
- }
1496
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AppCacheService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1497
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AppCacheService, providedIn: 'root' }); }
1498
- }
1499
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AppCacheService, decorators: [{
1500
- type: Injectable,
1501
- args: [{
1502
- providedIn: 'root'
1503
- }]
1504
- }] });
1505
-
1506
1461
  /**
1507
1462
  * Providing system definitions.
1508
1463
  */
@@ -2139,1268 +2094,1182 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
2139
2094
  }]
2140
2095
  }] });
2141
2096
 
2142
- /**
2143
- * Service providing user account configurations.
2144
- */
2145
- class UserService {
2146
- constructor() {
2147
- this.#backend = inject(BackendService);
2148
- this.#translate = inject(TranslateService);
2149
- this.#logger = inject(Logger);
2150
- this.#system = inject(SystemService);
2151
- this.#localization = inject(LocalizationService);
2152
- this.#eventService = inject(EventService);
2153
- this.#config = inject(ConfigService);
2154
- this.#document = inject(DOCUMENT);
2155
- this.#USERS_SETTINGS = '/users/settings/';
2156
- this.#DEFAULT_SETTINGS = '/users/settings';
2157
- this.#SETTINGS_SECTION_OBJECTCONFIG = 'object-config';
2158
- this.#userSource = new BehaviorSubject(this.#user);
2159
- this.user$ = this.#userSource.asObservable();
2160
- this.globalSettings = new Map();
2161
- this.canCreateObjects = false;
2162
- }
2163
- #backend;
2164
- #translate;
2165
- #logger;
2166
- #system;
2167
- #localization;
2168
- #eventService;
2169
- #config;
2170
- #document;
2171
- #USERS_SETTINGS;
2172
- #DEFAULT_SETTINGS;
2173
- #SETTINGS_SECTION_OBJECTCONFIG;
2174
- #user;
2175
- #userSource;
2097
+ class SearchService {
2098
+ #backend = inject(BackendService);
2099
+ #system = inject(SystemService);
2100
+ static { this.DEFAULT_QUERY_SIZE = 50; }
2176
2101
  /**
2177
- * Set a new current user
2178
- * @param user The user to be set as current user
2102
+ * Execute a search query ans transform the result to a SearchResult object
2103
+ * @param query The search query
2104
+ * @returns Observable of a SearchResult
2179
2105
  */
2180
- setCurrentUser(user) {
2181
- this.#user = user;
2182
- this.changeClientLocale('', false);
2183
- this.#userSource.next(this.#user);
2184
- }
2185
- getCurrentUser() {
2186
- return this.#user;
2187
- }
2188
- get hasAdminRole() {
2189
- return this.#user?.authorities?.includes(AdministrationRoles.ADMIN) || false;
2190
- }
2191
- get hasSystemRole() {
2192
- return this.#user?.authorities?.includes(AdministrationRoles.SYSTEM) || false;
2193
- }
2194
- get hasAdministrationRoles() {
2195
- return this.hasAdminRole || this.hasSystemRole;
2196
- }
2197
- get hasManageSettingsRole() {
2198
- const customRole = this.#config.get('core.permissions.manageSettingsRole');
2199
- const manageSettingsRole = customRole || AdministrationRoles.MANAGE_SETTINGS;
2200
- return this.#user?.authorities?.includes(manageSettingsRole) || false;
2201
- }
2202
- get isAdvancedUser() {
2203
- const customRole = this.#config.get('core.permissions.advancedUserRole');
2204
- const advancedUserRole = customRole || AdministrationRoles.MANAGE_SETTINGS;
2205
- return this.#user?.authorities?.includes(advancedUserRole) || false;
2106
+ search(query) {
2107
+ return this.searchRaw(query).pipe(map((res) => this.toSearchResult(res, query.size || SearchService.DEFAULT_QUERY_SIZE, query.from || 0)));
2206
2108
  }
2207
- get isRetentionManager() {
2208
- const customRole = this.#config.get('core.permissions.retentionManagerRole');
2209
- const retenetionManagerRole = customRole || AdministrationRoles.MANAGE_SETTINGS;
2210
- return this.#user?.authorities?.includes(retenetionManagerRole) || false;
2109
+ /**
2110
+ * Execute a raw search query and return the result as is.
2111
+ * @param query The search query
2112
+ * @returns Observable of the raw search result
2113
+ */
2114
+ searchRaw(query) {
2115
+ if (!query.size)
2116
+ query.size = SearchService.DEFAULT_QUERY_SIZE;
2117
+ return this.#backend.post(`/dms/objects/search`, query);
2211
2118
  }
2212
2119
  /**
2213
- * Change the users client locale
2214
- * @param iso ISO locale string to be set as new client locale
2120
+ * Search for objects in the dms using CMIS like SQL syntax.
2121
+ * @param statement The query statement
2122
+ * @param size The number of items to return
2123
+ * @returns Observable of a SearchResult
2215
2124
  */
2216
- changeClientLocale(iso, persist = true) {
2217
- if (this.#user) {
2218
- const languages = this.#config.getClientLocales().map((lang) => lang.iso);
2219
- iso = iso || this.#user.getClientLocale(this.#config.getDefaultClientLocale());
2220
- if (!languages.includes(iso)) {
2221
- iso = this.#config.getDefaultClientLocale();
2222
- }
2223
- this.#logger.debug("Changed client locale to '" + iso + "'");
2224
- this.#backend.setHeader('Accept-Language', iso);
2225
- this.#user.uiDirection = this.#getUiDirection(iso);
2226
- this.#document.body.dir = this.#user.uiDirection;
2227
- this.#document.documentElement.lang = iso;
2228
- this.#user.userSettings.locale = iso;
2229
- if (this.#translate.getCurrentLang() !== iso || this.#system.authData?.language !== iso) {
2230
- const obs = persist
2231
- ? forkJoin([
2232
- this.#translate.use(iso),
2233
- this.#system
2234
- .updateAuthData({ language: iso })
2235
- .pipe(switchMap(() => this.#localization.fetchLocalizations())),
2236
- this.saveUserSettings(this.#user.userSettings).pipe(tap(() => {
2237
- this.#logger.debug('Loading system definitions i18n resources for new locale.');
2238
- }))
2239
- ])
2240
- : this.#translate.use(iso);
2241
- obs.subscribe(() => this.#eventService.trigger(YuvEventType.CLIENT_LOCALE_CHANGED, iso));
2242
- }
2243
- }
2125
+ searchCmis(statement, size = SearchService.DEFAULT_QUERY_SIZE, includePermissions = false) {
2126
+ return this.#executeCmisSearch(statement, size, 0, includePermissions);
2244
2127
  }
2245
- saveUserSettings(settings) {
2246
- if (this.#user) {
2247
- //console.log(this.#user.userSettings);
2248
- this.#user.userSettings = { ...this.#user.userSettings, ...settings };
2249
- return this.#backend
2250
- .post(this.#DEFAULT_SETTINGS, this.#user.userSettings)
2251
- .pipe(tap(() => this.#userSource.next(this.#user)));
2128
+ #executeCmisSearch(statement, maxItems, skipCount = 0, includePermissions = false) {
2129
+ const payload = {
2130
+ query: {
2131
+ statement,
2132
+ skipCount,
2133
+ maxItems,
2134
+ handleDeletedDocuments: 'DELETED_DOCUMENTS_EXCLUDE'
2135
+ }
2136
+ };
2137
+ if (includePermissions) {
2138
+ payload.query.includePermissions = true;
2252
2139
  }
2253
- else
2254
- return of(null);
2255
- }
2256
- fetchUserSettings() {
2257
- return this.#backend.get('/dms/permissions').pipe(catchError(() => of(undefined)), switchMap((res) => {
2258
- this.#setUserPermissions(res);
2259
- return this.#backend.get(this.#DEFAULT_SETTINGS);
2260
- }));
2140
+ return this.#backend
2141
+ .post('/dms/objects/cmisSearch', payload
2142
+ // Using API-WEB because it enriches the response with additional information (resolved user names, etc.)
2143
+ // ApiBase.core
2144
+ )
2145
+ .pipe(map((res) => this.toSearchResult(res, maxItems, skipCount)));
2261
2146
  }
2262
2147
  /**
2263
- * Search for a user based on a search term
2264
- * @param term Search term
2265
- * @param excludeMe whether or not to exclude myself from the result list
2266
- * @param roles narrow down the search results by certain roles
2148
+ * Fetch aggragations for a given query.
2149
+ * @param q The query
2150
+ * @param aggregations List of aggregations to be fetched (e.g. `enaio:objectTypeId`
2151
+ * to get an aggregation of object types)
2267
2152
  */
2268
- queryUser(term, excludeMe, roles) {
2269
- let params = new HttpParams().set('search', term).set('excludeMe', `${!!excludeMe}`);
2270
- roles?.length && roles.map((r) => (params = params.append(`roles`, r)));
2271
- return this.#backend
2272
- .get(`/idm/users?${params}`)
2273
- .pipe(map((users) => (!users ? [] : users.map((u) => new YuvUser(u)))));
2274
- }
2275
- getUserById(id) {
2276
- return this.#backend.get(`/idm/users/${id}`).pipe(map((user) => new YuvUser(user, this.#user.userSettings)));
2277
- }
2278
- logout(redirRoute) {
2279
- const redir = redirRoute ? `?redir=${redirRoute}` : '';
2280
- window.location.href = `${this.#backend.getApiBase('logout')}${redir}`;
2281
- }
2282
- getSettings(section) {
2283
- return this.#user ? this.#backend.get(this.#USERS_SETTINGS + encodeURIComponent(section)) : of(null);
2284
- }
2285
- saveObjectConfig(objectConfigs) {
2286
- return this.#backend.post(this.#USERS_SETTINGS + encodeURIComponent(this.#SETTINGS_SECTION_OBJECTCONFIG), objectConfigs);
2287
- }
2288
- loadObjectConfig() {
2289
- return this.getSettings(this.#SETTINGS_SECTION_OBJECTCONFIG).pipe(catchError(() => of(undefined)));
2290
- }
2291
- #getUiDirection(iso) {
2292
- // languages that are read right to left
2293
- const rtlLanguages = ['ar', 'arc', 'dv', 'fa', 'ha', 'he', 'khw', 'ks', 'ku', 'ps', 'ur', 'yi'];
2294
- return !rtlLanguages.includes(iso) ? Direction.LTR : Direction.RTL;
2295
- }
2296
- #setUserPermissions(res) {
2297
- this.userPermissions = {
2298
- create: this.#mapPermissions('CREATE', res),
2299
- write: this.#mapPermissions('WRITE', res),
2300
- read: this.#mapPermissions('READ', res),
2301
- delete: this.#mapPermissions('DELETE', res)
2302
- };
2303
- const permissions = {
2304
- createableObjectTypes: [
2305
- ...this.userPermissions.create.folderTypes,
2306
- ...this.userPermissions.create.objectTypes,
2307
- ...this.userPermissions.create.secondaryObjectTypes
2308
- ],
2309
- searchableObjectTypes: [
2310
- ...this.userPermissions.read.folderTypes,
2311
- ...this.userPermissions.read.objectTypes,
2312
- ...this.userPermissions.read.secondaryObjectTypes
2313
- ]
2314
- };
2315
- this.#system.setPermissions(permissions);
2316
- this.canCreateObjects = permissions.createableObjectTypes.length > 0;
2153
+ aggregate(q, aggregations) {
2154
+ q.aggs = aggregations;
2155
+ return this.searchRaw(q).pipe(map((res) => this.#toAggregateResult(res, aggregations)));
2317
2156
  }
2318
- #mapPermissions(section, apiResponse) {
2319
- const res = apiResponse[section] || {};
2157
+ /**
2158
+ * Map search result from the backend to applications AggregateResult object
2159
+ * @param searchResponse The backend response
2160
+ * @param aggregations The aggregations to be fetched
2161
+ */
2162
+ #toAggregateResult(searchResponse, aggregations) {
2163
+ const agg = [];
2164
+ if (aggregations) {
2165
+ aggregations.forEach((a) => {
2166
+ const ag = {
2167
+ aggKey: a,
2168
+ entries: searchResponse.objects.map((o) => ({
2169
+ key: o.properties[a].value,
2170
+ count: o.properties['OBJECT_COUNT'].value
2171
+ }))
2172
+ };
2173
+ agg.push(ag);
2174
+ });
2175
+ }
2320
2176
  return {
2321
- folderTypes: res['folderTypeIds'] || [],
2322
- objectTypes: res['objectTypeIds'] || [],
2323
- secondaryObjectTypes: res['secondaryObjectTypeIds'] || []
2177
+ totalNumItems: searchResponse.totalNumItems,
2178
+ aggregations: agg
2324
2179
  };
2325
2180
  }
2326
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2327
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UserService, providedIn: 'root' }); }
2328
- }
2329
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UserService, decorators: [{
2330
- type: Injectable,
2331
- args: [{
2332
- providedIn: 'root'
2333
- }]
2334
- }] });
2335
-
2336
- /**
2337
- * @ignore
2338
- */
2339
- const CUSTOM_CONFIG = new InjectionToken('CUSTOM_CONFIG', {
2340
- factory: () => ({ main: ['assets/_yuuvis/config/main.json'], translations: ['assets/_yuuvis/i18n/'] })
2341
- });
2342
- const CORE_CONFIG = new InjectionToken('CORE_CONFIG');
2343
-
2344
- /**
2345
- * @ignore
2346
- */
2347
- let CoreConfig = class CoreConfig {
2348
- constructor(__config) {
2349
- this.main = ['assets/_yuuvis/config/main.json'];
2350
- this.translations = ['assets/_yuuvis/i18n/'];
2351
- this.environment = { production: true };
2352
- Object.assign(this, __config);
2353
- }
2354
- };
2355
- CoreConfig = __decorate([
2356
- __param(0, Inject(CUSTOM_CONFIG)),
2357
- __metadata("design:paramtypes", [CoreConfig])
2358
- ], CoreConfig);
2359
-
2360
- /**
2361
- * Service managing the configuarions for object types.
2362
- * Configuration means that you can pick certain fields
2363
- * of an object type and assign them to a config object.
2364
- */
2365
- class ObjectConfigService {
2366
- constructor() {
2367
- this.#system = inject(SystemService);
2368
- this.#user = inject(UserService);
2369
- this.OBJECT_CONFIG_STORAGE_KEY = 'yuv.framework.object-config';
2370
- this.#objectConfigsSource = new ReplaySubject();
2371
- this.#objectConfigs$ = this.#objectConfigsSource.asObservable();
2372
- this.#defaultObjectConfigs = {};
2373
- this.#registeredDefaultObjectConfigs = {
2374
- main: {},
2375
- buckets: []
2376
- };
2377
- this.#objectConfigs = {
2378
- main: {},
2379
- buckets: []
2380
- };
2381
- }
2382
- #system;
2383
- #user;
2384
- #objectConfigsSource;
2385
- #objectConfigs$;
2386
- #defaultObjectConfigs;
2387
- #registeredDefaultObjectConfigs;
2388
- #objectConfigs;
2389
- #isValidObjectConfig(soc) {
2390
- return coerceBooleanProperty(soc && Object.hasOwn(soc, 'main') && Object.hasOwn(soc, 'buckets'));
2391
- }
2392
- // called on core init (auth.service -> initApp)
2393
- init() {
2394
- return this.#user.loadObjectConfig().pipe(tap$1((res) => {
2395
- this.#getDefaultObjectConfig();
2396
- this.#objectConfigs = (this.#isValidObjectConfig(res) && res) || {
2397
- main: {},
2398
- buckets: []
2399
- };
2400
- this.#objectConfigsSource.next(this.#objectConfigs);
2401
- }));
2181
+ /**
2182
+ * Go to a page of a search result.
2183
+ * @param query SearchQuery or CMIS query statement
2184
+ * @param page The number of the page to go to
2185
+ */
2186
+ getPage(query, page, pageSize = SearchService.DEFAULT_QUERY_SIZE, includePermissions = false) {
2187
+ const isCmis = typeof query === 'string';
2188
+ if (isCmis) {
2189
+ return this.#executeCmisSearch(query, pageSize, (page - 1) * pageSize, includePermissions);
2190
+ }
2191
+ else {
2192
+ query.from = (page - 1) * (query.size || pageSize);
2193
+ return this.search(query);
2194
+ }
2402
2195
  }
2403
- getObjectConfigs$(bucket, includeDefaults) {
2404
- return this.#objectConfigs$.pipe(map$1((soc) => {
2405
- let ocr = soc.main;
2406
- const defaults = { ...this.#defaultObjectConfigs, ...this.#getRegisteredDefaults(bucket) };
2407
- if (bucket) {
2408
- const tileBucket = soc.buckets.find((b) => b.id === bucket);
2409
- if (tileBucket) {
2410
- ocr = tileBucket.configs;
2411
- if (includeDefaults)
2412
- Object.keys(ocr).forEach((k) => {
2413
- ocr[k] = { ...defaults[k], ...ocr[k] };
2196
+ /**
2197
+ * Map search result from the backend to applications SearchResult object
2198
+ * @param searchResponse The backend response
2199
+ */
2200
+ toSearchResult(searchResponse, pageSize = SearchService.DEFAULT_QUERY_SIZE, skipCount = 0) {
2201
+ const resultListItems = [];
2202
+ const objectTypes = [];
2203
+ searchResponse.objects.forEach((o) => {
2204
+ const fields = new Map();
2205
+ // process properties section of result
2206
+ Object.keys(o.properties).forEach((key) => {
2207
+ let value = o.properties[key].value;
2208
+ if (o.properties[key].clvalue) {
2209
+ // table fields will have a clientValue too ...
2210
+ value = o.properties[key].clvalue;
2211
+ // ... and also may contain values that need to be resolved
2212
+ if (o.properties[key].resolvedValues) {
2213
+ value.forEach((v) => {
2214
+ Object.keys(v).forEach((k) => {
2215
+ const resValue = Array.isArray(v[k]) ? v[k].map((i) => o.properties[key].resolvedValues[i]) : o.properties[key].resolvedValues[v[k]];
2216
+ if (resValue) {
2217
+ v[`${k}_title`] = resValue;
2218
+ }
2219
+ });
2414
2220
  });
2221
+ }
2222
+ }
2223
+ fields.set(key, value);
2224
+ if (o.properties[key].title) {
2225
+ fields.set(key + '_title', o.properties[key].title);
2415
2226
  }
2227
+ });
2228
+ // process contentStreams section of result if available.
2229
+ // Objects that don't have files attached won't have this section
2230
+ let content;
2231
+ if (o.contentStreams && o.contentStreams.length > 0) {
2232
+ // we assume that each result object only has ONE file attached, altough
2233
+ // this is an array and there may be more
2234
+ const contentStream = o.contentStreams[0];
2235
+ // also add contentstream related fields to the result fields
2236
+ fields.set(ContentStreamField.LENGTH, contentStream.length);
2237
+ fields.set(ContentStreamField.MIME_TYPE, contentStream.mimeType);
2238
+ fields.set(ContentStreamField.FILENAME, contentStream.fileName);
2239
+ fields.set(ContentStreamField.ID, contentStream.contentStreamId);
2240
+ fields.set(ContentStreamField.RANGE, contentStream.contentStreamRange);
2241
+ fields.set(ContentStreamField.REPOSITORY_ID, contentStream.repositoryId);
2242
+ fields.set(ContentStreamField.DIGEST, contentStream.digest);
2243
+ fields.set(ContentStreamField.ARCHIVE_PATH, contentStream.archivePath);
2244
+ content = {
2245
+ contentStreamId: contentStream.contentStreamId,
2246
+ repositoryId: contentStream.repositoryId,
2247
+ range: contentStream.range,
2248
+ digest: contentStream.digest,
2249
+ archivePath: contentStream.archivePath,
2250
+ fileName: contentStream.fileName,
2251
+ mimeType: contentStream.mimeType,
2252
+ size: contentStream.length
2253
+ };
2416
2254
  }
2417
- return includeDefaults ? { ...defaults, ...ocr } : { ...ocr };
2418
- }));
2255
+ const objectTypeId = o.properties[BaseObjectTypeField.OBJECT_TYPE_ID] ? o.properties[BaseObjectTypeField.OBJECT_TYPE_ID].value : null;
2256
+ if (objectTypes.indexOf(objectTypeId) === -1) {
2257
+ objectTypes.push(objectTypeId);
2258
+ }
2259
+ resultListItems.push({
2260
+ objectTypeId,
2261
+ content,
2262
+ fields,
2263
+ permissions: o.permissions
2264
+ });
2265
+ });
2266
+ const totalPages = Math.ceil(searchResponse.totalNumItems / pageSize);
2267
+ const page = (!skipCount ? 0 : skipCount / pageSize) + 1;
2268
+ const result = {
2269
+ hasMoreItems: searchResponse.hasMoreItems,
2270
+ totalNumItems: searchResponse.totalNumItems,
2271
+ items: resultListItems,
2272
+ objectTypes: objectTypes,
2273
+ paging: totalPages > 1 ? { page, totalPages } : undefined
2274
+ };
2275
+ return result;
2419
2276
  }
2420
2277
  /**
2421
- * Register default object configs. Apps can pre-define how their
2422
- * objects should be rendered.
2423
- * @param bucket Name of a bucket to bind configs to (usually the ID of the app) to
2424
- * separate the configs from other apps. If not provided configs will be saved for
2425
- * the global scope
2278
+ * Maps data extracted from a search form to search filters. Every key of the form data object
2279
+ * will be mapped to a search filter.
2280
+ * @param formData form data
2281
+ * @returns Array of search filters
2426
2282
  */
2427
- registerDefaults(configs, bucket) {
2428
- if (bucket) {
2429
- // does bucket exist?
2430
- const tileBucketIdx = this.#registeredDefaultObjectConfigs.buckets.findIndex((b) => b.id === bucket);
2431
- if (tileBucketIdx !== -1) {
2432
- this.#registeredDefaultObjectConfigs.buckets[tileBucketIdx].configs = {
2433
- ...this.#registeredDefaultObjectConfigs.buckets[tileBucketIdx].configs,
2434
- ...configs
2435
- };
2283
+ formDataToSearchFilter(formData) {
2284
+ const isRangeValue = (v) => {
2285
+ return typeof v === 'object' && v !== null && 'firstValue' in v && 'operator' in v;
2286
+ };
2287
+ const filters = [];
2288
+ Object.keys(formData).forEach((key) => {
2289
+ const value = formData[key];
2290
+ if (isRangeValue(value)) {
2291
+ const f = this.rangeValueToSearchFilter(value, key);
2292
+ if (f)
2293
+ filters.push(f);
2436
2294
  }
2437
2295
  else {
2438
- this.#registeredDefaultObjectConfigs.buckets.push({
2439
- id: bucket,
2440
- configs
2296
+ filters.push({
2297
+ f: key,
2298
+ o: Array.isArray(value) ? Operator.IN : Operator.EQUAL,
2299
+ v1: value
2441
2300
  });
2442
2301
  }
2443
- }
2444
- else {
2445
- this.#registeredDefaultObjectConfigs.main = { ...this.#registeredDefaultObjectConfigs.main, ...configs };
2446
- }
2302
+ });
2303
+ return filters;
2447
2304
  }
2448
- unregisterDefaults(configs, bucket) {
2449
- if (bucket) {
2450
- // does bucket exist?
2451
- const tileBucketIdx = this.#registeredDefaultObjectConfigs.buckets.findIndex((b) => b.id === bucket);
2452
- if (tileBucketIdx !== -1) {
2453
- this.#registeredDefaultObjectConfigs.buckets.splice(tileBucketIdx, 1);
2305
+ rangeValueToSearchFilter(value, property) {
2306
+ return this.#system.getObjectTypeField(property)?.propertyType === 'datetime'
2307
+ ? this.#dateRangeValueToSearchFilter(value, property)
2308
+ : {
2309
+ f: property,
2310
+ o: value.operator,
2311
+ v1: value.firstValue,
2312
+ v2: value.secondValue
2313
+ };
2314
+ }
2315
+ #dateRangeValueToSearchFilter(rv, property) {
2316
+ const v1 = rv.firstValue;
2317
+ const v2 = rv.secondValue;
2318
+ switch (rv.operator) {
2319
+ case Operator.EQUAL: {
2320
+ return {
2321
+ f: property,
2322
+ o: Operator.INTERVAL_INCLUDE_BOTH,
2323
+ v1: this.#dateToISOString(v1, 'start'),
2324
+ v2: this.#dateToISOString(v1, 'end')
2325
+ };
2454
2326
  }
2455
- }
2456
- else {
2457
- Object.keys(configs).forEach((key) => {
2458
- delete this.#registeredDefaultObjectConfigs.main[key];
2459
- });
2327
+ case Operator.GREATER_OR_EQUAL: {
2328
+ return {
2329
+ f: property,
2330
+ o: rv.operator,
2331
+ v1: this.#dateToISOString(v1)
2332
+ };
2333
+ }
2334
+ case Operator.LESS_OR_EQUAL: {
2335
+ return {
2336
+ f: property,
2337
+ o: rv.operator,
2338
+ v1: this.#dateToISOString(v1, 'end')
2339
+ };
2340
+ }
2341
+ case Operator.INTERVAL_INCLUDE_BOTH: {
2342
+ return {
2343
+ f: property,
2344
+ o: rv.operator,
2345
+ v1: this.#dateToISOString(v1, 'start'),
2346
+ v2: v2 ? this.#dateToISOString(v2, 'end') : ''
2347
+ };
2348
+ }
2349
+ default:
2350
+ return undefined;
2460
2351
  }
2461
2352
  }
2462
- getRegisteredDefault(objectTypeId, bucket) {
2463
- if (bucket) {
2464
- const b = this.#registeredDefaultObjectConfigs.buckets.find((b) => b.id === bucket);
2465
- return b ? b.configs[objectTypeId] : undefined;
2466
- }
2467
- else {
2468
- return this.#registeredDefaultObjectConfigs.main[objectTypeId];
2469
- }
2470
- }
2471
- #getRegisteredDefaults(bucket) {
2472
- if (bucket) {
2473
- const defaultTileBucket = this.#registeredDefaultObjectConfigs.buckets.find((b) => b.id === bucket);
2474
- return defaultTileBucket?.configs || {};
2475
- }
2476
- else
2477
- return this.#registeredDefaultObjectConfigs.main;
2478
- }
2479
2353
  /**
2480
- * Get object config for an object type.
2481
- * @param objectTypeId ID of the object type to get the config for
2482
- * @param bucket Optional bucket ID to fetch the config from. Buckets
2483
- * define separated areas to store/receive those configs. This way
2484
- * components/apps can store different object configs than the ones
2485
- * used accross the whole client application.
2354
+ * Checks if value is a date and retrieves an ISOString representation. Optionally you can determine, if it should be the beginning or the end of a day.
2355
+ *
2356
+ * @param value
2357
+ * @param range
2358
+ * @private
2486
2359
  */
2487
- getObjectConfig(type, bucket) {
2488
- const b = bucket ? this.#objectConfigs.buckets.find((b) => b.id === bucket) : undefined;
2489
- const otr = b ? b.configs : this.#objectConfigs.main;
2490
- const oc = otr[type.id];
2491
- // return oc || this.#getRegisteredDefaults(bucket)[type.id];
2492
- // override icons by APP_SCHEMA-icons: temporary solution. Todo: remove when handling of custom svg-icons is clear!
2493
- const result = oc || this.#getRegisteredDefaults(bucket)[type.id] || this.#defaultObjectConfigs[type.id];
2494
- if (result?.icon)
2495
- result.icon = type.icon ? { svg: type.icon } : result.icon;
2496
- return result;
2360
+ #dateToISOString(value, range = 'start') {
2361
+ const date = typeof value === 'string' ? new Date(value) : value;
2362
+ const offset = Utils.DEFAULT_TIME_OFFSET_V2; // T+23:59:59:999
2363
+ if (!Utils.isValidDate(date))
2364
+ return '';
2365
+ const isoDateString = date.toISOString();
2366
+ // const isoDateStringWithOffset = new Date(date.getTime() + offset).toISOString();
2367
+ switch (range) {
2368
+ case 'start':
2369
+ return isoDateString;
2370
+ case 'end':
2371
+ return isoDateString;
2372
+ default:
2373
+ return isoDateString;
2374
+ }
2497
2375
  }
2498
- saveObjectConfig(type, config, bucket) {
2499
- return this.saveObjectConfigs({ [type.id]: config }, bucket);
2376
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SearchService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2377
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SearchService, providedIn: 'root' }); }
2378
+ }
2379
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SearchService, decorators: [{
2380
+ type: Injectable,
2381
+ args: [{
2382
+ providedIn: 'root'
2383
+ }]
2384
+ }] });
2385
+
2386
+ var Direction;
2387
+ (function (Direction) {
2388
+ Direction["LTR"] = "ltr";
2389
+ Direction["RTL"] = "rtl";
2390
+ })(Direction || (Direction = {}));
2391
+
2392
+ const CUSTOM_EVENTS = new InjectionToken('CUSTOM_EVENTS', { factory: () => [] });
2393
+ const CUSTOM_EVENTS_TRUSTED_ORIGINS = new InjectionToken('CUSTOM_EVENTS_TRUSTED_ORIGINS', { factory: () => [] });
2394
+
2395
+ /**
2396
+ * Mandatory Custom event prefix for all custom YUV events
2397
+ */
2398
+ const CUSTOM_YUV_EVENT_PREFIX = 'yuv.';
2399
+
2400
+ /**
2401
+ * Service for providing triggered events
2402
+ */
2403
+ class EventService {
2404
+ #customEvents;
2405
+ #customEventsTrustedOrigins;
2406
+ #ngZone;
2407
+ #eventSource;
2408
+ constructor() {
2409
+ this.#customEvents = inject(CUSTOM_EVENTS);
2410
+ this.#customEventsTrustedOrigins = inject(CUSTOM_EVENTS_TRUSTED_ORIGINS);
2411
+ this.#ngZone = inject(NgZone);
2412
+ this.#eventSource = new Subject();
2413
+ this.event$ = this.#eventSource.asObservable();
2414
+ this.#listenToWindowEvents();
2500
2415
  }
2501
- saveObjectConfigs(configs, bucket) {
2502
- this.#updateObjectConfig(configs, bucket);
2503
- return this.#user.saveObjectConfig(this.#objectConfigs);
2416
+ #startsWithAllowed(value) {
2417
+ return typeof value === 'string' && this.#customEvents.some((prefix) => value.startsWith(prefix));
2504
2418
  }
2505
- #updateObjectConfig(configs, bucket) {
2506
- if (!this.#objectConfigs) {
2507
- this.#objectConfigs = {
2508
- main: {},
2509
- buckets: []
2510
- };
2511
- }
2512
- if (bucket) {
2513
- // does bucket exist?
2514
- const tileBucketIdx = this.#objectConfigs.buckets.findIndex((b) => b.id === bucket);
2515
- if (tileBucketIdx !== -1) {
2516
- Object.keys(configs).forEach((objectTypeId) => {
2517
- const config = configs[objectTypeId];
2518
- if (config) {
2519
- this.#objectConfigs.buckets[tileBucketIdx].configs[objectTypeId] = config;
2520
- }
2521
- else {
2522
- delete this.#objectConfigs.buckets[tileBucketIdx].configs[objectTypeId];
2523
- }
2524
- });
2525
- }
2526
- else {
2527
- const newBucket = { id: bucket, configs: {} };
2528
- Object.keys(configs).forEach((objectTypeId) => {
2529
- const config = configs[objectTypeId];
2530
- if (config) {
2531
- newBucket.configs[objectTypeId] = config;
2532
- }
2533
- });
2534
- this.#objectConfigs.buckets.push(newBucket);
2535
- }
2536
- }
2537
- else {
2538
- Object.keys(configs).forEach((objectTypeId) => {
2539
- const config = configs[objectTypeId];
2540
- if (config) {
2541
- this.#objectConfigs.main[objectTypeId] = config;
2542
- }
2543
- else {
2544
- delete this.#objectConfigs.main[objectTypeId];
2545
- }
2546
- });
2547
- }
2548
- this.#objectConfigsSource.next(this.#objectConfigs);
2419
+ #isValidExternalMessage(event) {
2420
+ this.#customEvents.push(CUSTOM_YUV_EVENT_PREFIX);
2421
+ if (!event.data || typeof event.data !== 'object')
2422
+ return false;
2423
+ if (!(this.#startsWithAllowed(event.data.source) || this.#startsWithAllowed(event.data.type)))
2424
+ return false;
2425
+ // Accept messages from trusted origins
2426
+ this.#customEventsTrustedOrigins.push(window.location.origin);
2427
+ const isFromTrustedOrigin = this.#customEventsTrustedOrigins.includes(event.origin);
2428
+ const hasValidStructure = event.data && (event.data.type || event.data.eventType) && typeof event.data === 'object';
2429
+ return isFromTrustedOrigin && hasValidStructure;
2549
2430
  }
2550
- getDefaultConfig(objectTypeID) {
2551
- return this.#defaultObjectConfigs[objectTypeID];
2431
+ #listenToWindowEvents() {
2432
+ window.addEventListener('message', (event) => {
2433
+ this.#ngZone.run(() => this.#isValidExternalMessage(event) && this.trigger(event.data.type, event.data.data));
2434
+ });
2552
2435
  }
2553
- getResolvedObjectConfig(data, type, bucket, includeDefaults) {
2554
- const defaultCfg = this.#defaultObjectConfigs[data[BaseObjectTypeField.OBJECT_TYPE_ID]];
2555
- let oc = this.getObjectConfig(type, bucket) || defaultCfg;
2556
- if (includeDefaults)
2557
- oc = { ...defaultCfg, ...oc };
2558
- const res = {
2559
- id: data[BaseObjectTypeField.OBJECT_ID],
2560
- objectTypeId: data[BaseObjectTypeField.OBJECT_TYPE_ID],
2561
- actions: oc.actions,
2562
- badges: oc.badges,
2563
- instanceData: data
2436
+ /**
2437
+ * Triggers a postMessage event that will be sent to the yuuvis global event Trigger
2438
+ * @param type Type/key of the event
2439
+ * @param data Data to be sent along with the event
2440
+ */
2441
+ triggerPostMessageEvent(type, data) {
2442
+ const targetOrigin = window.location.origin;
2443
+ const payload = {
2444
+ type,
2445
+ data,
2446
+ timestamp: Date.now()
2564
2447
  };
2565
- if (oc.title)
2566
- res.title = {
2567
- propertyName: oc.title.propertyName,
2568
- value: data[oc.title.propertyName]
2569
- };
2570
- if (oc.description)
2571
- res.description = {
2572
- propertyName: oc.description.propertyName,
2573
- value: data[oc.description.propertyName]
2574
- };
2575
- if (oc.icon)
2576
- res.icon = {
2577
- propertyName: 'custom',
2578
- value: oc.icon.svg
2579
- };
2580
- if (oc.meta)
2581
- res.meta = {
2582
- propertyName: oc.meta.propertyName,
2583
- value: data[oc.meta.propertyName]
2584
- };
2585
- if (oc.aside)
2586
- res.aside = {
2587
- propertyName: oc.aside.propertyName,
2588
- value: data[oc.aside.propertyName]
2589
- };
2590
- return res;
2448
+ window.postMessage(payload, targetOrigin);
2591
2449
  }
2592
2450
  /**
2593
- * Load default/fallbac configs based on the object types and
2594
- * their first properties
2451
+ * Trigger an global event
2452
+ * @param type Type/key of the event
2453
+ * @param data Data to be send along with the event
2595
2454
  */
2596
- #getDefaultObjectConfig() {
2597
- this.#system.getObjectTypes(true).forEach((ot) => {
2598
- this.#defaultObjectConfigs[ot.id] = {
2599
- objectTypeId: ot.id,
2600
- title: this.#toObjectProperty(ot.fields[0]),
2601
- description: this.#toObjectProperty(ot.fields[1]),
2602
- meta: this.#toObjectProperty(ot.fields[2]),
2603
- aside: this.#toObjectProperty(ot.fields[3])
2604
- };
2605
- });
2455
+ trigger(type, ...args) {
2456
+ this.#eventSource.next({ type: type, data: args[0] });
2606
2457
  }
2607
- #toObjectProperty(field) {
2608
- return field
2609
- ? {
2610
- label: this.#system.getLocalizedLabel(field.id),
2611
- propertyName: field.id
2612
- }
2613
- : undefined;
2458
+ /**
2459
+ * Listen on a triggered event
2460
+ * @param types Type/key of the event
2461
+ */
2462
+ on(...types) {
2463
+ return this.event$.pipe(filter((event) => event && !!types.find((t) => t === event.type)));
2614
2464
  }
2615
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ObjectConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2616
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ObjectConfigService, providedIn: 'root' }); }
2465
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EventService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2466
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EventService, providedIn: 'root' }); }
2617
2467
  }
2618
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ObjectConfigService, decorators: [{
2468
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EventService, decorators: [{
2619
2469
  type: Injectable,
2620
2470
  args: [{
2621
2471
  providedIn: 'root'
2622
2472
  }]
2623
- }] });
2624
-
2625
- const YUV_USER = new InjectionToken('Currently logged in user', {
2626
- factory: () => undefined
2627
- });
2628
- const provideUser = (user) => {
2629
- return { provide: YUV_USER, useValue: user };
2630
- };
2473
+ }], ctorParameters: () => [] });
2631
2474
 
2632
2475
  /**
2633
- * Service handling authentication related issues.
2476
+ * Events emitted by parts of the application
2634
2477
  */
2635
- class AuthService {
2636
- // user may have been fetched already in bootstrapping process
2637
- #userToken;
2638
- #eventService;
2639
- #userService;
2640
- #objectConfigService;
2641
- #appCache;
2642
- #systemService;
2643
- #localizationService;
2644
- #backend;
2645
- #INITIAL_REQUEST_STORAGE_KEY;
2646
- #USER_FETCH_URI;
2647
- #authenticated;
2648
- #authSource;
2649
- #authData;
2650
- constructor(coreConfig) {
2651
- this.coreConfig = coreConfig;
2652
- // user may have been fetched already in bootstrapping process
2653
- this.#userToken = inject(YUV_USER);
2654
- this.#eventService = inject(EventService);
2655
- this.#userService = inject(UserService);
2656
- this.#objectConfigService = inject(ObjectConfigService);
2657
- this.#appCache = inject(AppCacheService);
2658
- this.#systemService = inject(SystemService);
2659
- this.#localizationService = inject(LocalizationService);
2478
+ var YuvEventType;
2479
+ (function (YuvEventType) {
2480
+ YuvEventType["LOGOUT"] = "yuv.user.logout";
2481
+ YuvEventType["CLIENT_LOCALE_CHANGED"] = "yuv.user.locale.client.changed";
2482
+ YuvEventType["DMS_OBJECT_LOADED"] = "yuv.dms.object.loaded";
2483
+ YuvEventType["DMS_OBJECT_CREATED"] = "yuv.dms.object.created";
2484
+ YuvEventType["DMS_OBJECT_DELETED"] = "yuv.dms.object.deleted";
2485
+ YuvEventType["DMS_OBJECT_UPDATED"] = "yuv.dms.object.updated";
2486
+ YuvEventType["DMS_OBJECT_CONTENT_UPDATED"] = "yuv.dms.object.content.updated";
2487
+ YuvEventType["DMS_OBJECTS_MOVED"] = "yuv.dms.objects.moved";
2488
+ YuvEventType["RELATIONSHIP_CREATED"] = "yuv.dms.relationship.created";
2489
+ YuvEventType["RELATIONSHIP_DELETED"] = "yuv.dms.relationship.deleted";
2490
+ })(YuvEventType || (YuvEventType = {}));
2491
+
2492
+ /**
2493
+ * Service providing user account configurations.
2494
+ */
2495
+ class UserService {
2496
+ constructor() {
2660
2497
  this.#backend = inject(BackendService);
2661
- this.#INITIAL_REQUEST_STORAGE_KEY = 'yuv.core.auth.initialrequest';
2662
- this.#USER_FETCH_URI = '/idm/whoami';
2663
- this.#authenticated = false;
2664
- this.#authSource = new BehaviorSubject(false);
2665
- this.authenticated$ = this.#authSource.asObservable();
2666
- }
2667
- isLoggedIn() {
2668
- return this.#authenticated;
2498
+ this.#translate = inject(TranslateService);
2499
+ this.#logger = inject(Logger);
2500
+ this.#system = inject(SystemService);
2501
+ this.#localization = inject(LocalizationService);
2502
+ this.#eventService = inject(EventService);
2503
+ this.#config = inject(ConfigService);
2504
+ this.#document = inject(DOCUMENT);
2505
+ this.#USERS_SETTINGS = '/users/settings/';
2506
+ this.#DEFAULT_SETTINGS = '/users/settings';
2507
+ this.#SETTINGS_SECTION_OBJECTCONFIG = 'object-config';
2508
+ this.#userSource = new BehaviorSubject(this.#user);
2509
+ this.user$ = this.#userSource.asObservable();
2510
+ this.globalSettings = new Map();
2511
+ this.canCreateObjects = false;
2669
2512
  }
2513
+ #backend;
2514
+ #translate;
2515
+ #logger;
2516
+ #system;
2517
+ #localization;
2518
+ #eventService;
2519
+ #config;
2520
+ #document;
2521
+ #USERS_SETTINGS;
2522
+ #DEFAULT_SETTINGS;
2523
+ #SETTINGS_SECTION_OBJECTCONFIG;
2524
+ #user;
2525
+ #userSource;
2670
2526
  /**
2671
- * Called while app/core is initialized (APP_INITIALIZER)
2672
- * @ignore
2527
+ * Set a new current user
2528
+ * @param user The user to be set as current user
2673
2529
  */
2674
- initUser() {
2675
- return this.fetchUser();
2530
+ setCurrentUser(user) {
2531
+ this.#user = user;
2532
+ this.changeClientLocale('', false);
2533
+ this.#userSource.next(this.#user);
2676
2534
  }
2677
- /**
2678
- * Get the current tenant or the previous one persisted locally
2679
- */
2680
- getTenant() {
2681
- return this.#authData?.tenant;
2535
+ getCurrentUser() {
2536
+ return this.#user;
2682
2537
  }
2683
- /**
2684
- * Fetch information about the user currently logged in
2685
- */
2686
- fetchUser() {
2687
- return (this.#userToken ? of(this.#userToken) : this.#backend.get(this.#USER_FETCH_URI)).pipe(tap(() => {
2688
- this.#authenticated = true;
2689
- this.#authSource.next(this.#authenticated);
2690
- }), switchMap((userJson) => this.#initApp(userJson)));
2538
+ get hasAdminRole() {
2539
+ return this.#user?.authorities?.includes(AdministrationRoles.ADMIN) || false;
2691
2540
  }
2692
- /**
2693
- * Logs out the current user.
2694
- */
2695
- logout() {
2696
- this.#authenticated = false;
2697
- this.#authSource.next(this.#authenticated);
2698
- this.#eventService.trigger(YuvEventType.LOGOUT);
2541
+ get hasSystemRole() {
2542
+ return this.#user?.authorities?.includes(AdministrationRoles.SYSTEM) || false;
2543
+ }
2544
+ get hasAdministrationRoles() {
2545
+ return this.hasAdminRole || this.hasSystemRole;
2546
+ }
2547
+ get hasManageSettingsRole() {
2548
+ const customRole = this.#config.get('core.permissions.manageSettingsRole');
2549
+ const manageSettingsRole = customRole || AdministrationRoles.MANAGE_SETTINGS;
2550
+ return this.#user?.authorities?.includes(manageSettingsRole) || false;
2551
+ }
2552
+ get isAdvancedUser() {
2553
+ const customRole = this.#config.get('core.permissions.advancedUserRole');
2554
+ const advancedUserRole = customRole || AdministrationRoles.MANAGE_SETTINGS;
2555
+ return this.#user?.authorities?.includes(advancedUserRole) || false;
2556
+ }
2557
+ get isRetentionManager() {
2558
+ const customRole = this.#config.get('core.permissions.retentionManagerRole');
2559
+ const retenetionManagerRole = customRole || AdministrationRoles.MANAGE_SETTINGS;
2560
+ return this.#user?.authorities?.includes(retenetionManagerRole) || false;
2699
2561
  }
2700
2562
  /**
2701
- * Persists the initial request URI. This is nessesary to be able to
2702
- * redirect the user to the requested page after authentication.
2703
- * This is done on app initialization (core-init).
2563
+ * Change the users client locale
2564
+ * @param iso ISO locale string to be set as new client locale
2704
2565
  */
2705
- setInitialRequestUri() {
2706
- const ignore = ['/', '/index.html'];
2707
- const baseHref = Utils.getBaseHref();
2708
- // Check only the pathname against the ignore list so that auth callback
2709
- // query params (e.g. ?session_state=…) don't bypass the check.
2710
- let path = location.pathname.replace(baseHref, '');
2711
- path = !path.startsWith('/') ? `/${path}` : path;
2712
- if (!ignore.includes(path)) {
2713
- let uri = `${location.pathname}${location.search}`.replace(baseHref, '');
2714
- uri = !uri.startsWith('/') ? `/${uri}` : uri;
2715
- return this.#appCache.setItem(this.#INITIAL_REQUEST_STORAGE_KEY, {
2716
- uri: uri,
2717
- timestamp: Date.now()
2718
- });
2566
+ changeClientLocale(iso, persist = true) {
2567
+ if (this.#user) {
2568
+ const languages = this.#config.getClientLocales().map((lang) => lang.iso);
2569
+ iso = iso || this.#user.getClientLocale(this.#config.getDefaultClientLocale());
2570
+ if (!languages.includes(iso)) {
2571
+ iso = this.#config.getDefaultClientLocale();
2572
+ }
2573
+ this.#logger.debug("Changed client locale to '" + iso + "'");
2574
+ this.#backend.setHeader('Accept-Language', iso);
2575
+ this.#user.uiDirection = this.#getUiDirection(iso);
2576
+ this.#document.body.dir = this.#user.uiDirection;
2577
+ this.#document.documentElement.lang = iso;
2578
+ this.#user.userSettings.locale = iso;
2579
+ if (this.#translate.getCurrentLang() !== iso || this.#system.authData?.language !== iso) {
2580
+ const obs = persist
2581
+ ? forkJoin([
2582
+ this.#translate.use(iso),
2583
+ this.#system
2584
+ .updateAuthData({ language: iso })
2585
+ .pipe(switchMap(() => this.#localization.fetchLocalizations())),
2586
+ this.saveUserSettings(this.#user.userSettings).pipe(tap(() => {
2587
+ this.#logger.debug('Loading system definitions i18n resources for new locale.');
2588
+ }))
2589
+ ])
2590
+ : this.#translate.use(iso);
2591
+ obs.subscribe(() => this.#eventService.trigger(YuvEventType.CLIENT_LOCALE_CHANGED, iso));
2592
+ }
2719
2593
  }
2720
- return of(false);
2721
2594
  }
2722
- /**
2723
- * Get the URL that entered the app. May be a deep link that could then be
2724
- * picked up again after user has been authenticated.
2725
- */
2726
- getInitialRequestUri() {
2727
- return this.#appCache.getItem(this.#INITIAL_REQUEST_STORAGE_KEY);
2595
+ saveUserSettings(settings) {
2596
+ if (this.#user) {
2597
+ //console.log(this.#user.userSettings);
2598
+ this.#user.userSettings = { ...this.#user.userSettings, ...settings };
2599
+ return this.#backend
2600
+ .post(this.#DEFAULT_SETTINGS, this.#user.userSettings)
2601
+ .pipe(tap(() => this.#userSource.next(this.#user)));
2602
+ }
2603
+ else
2604
+ return of(null);
2728
2605
  }
2729
- resetInitialRequestUri() {
2730
- return this.#appCache.removeItem(this.#INITIAL_REQUEST_STORAGE_KEY);
2606
+ fetchUserSettings() {
2607
+ return this.#backend.get('/dms/permissions').pipe(catchError(() => of(undefined)), switchMap((res) => {
2608
+ this.#setUserPermissions(res);
2609
+ return this.#backend.get(this.#DEFAULT_SETTINGS);
2610
+ }));
2731
2611
  }
2732
2612
  /**
2733
- * Initialize/setup the application for a given user. This involves fetching
2734
- * settings and schema information.
2735
- * @param userJson Data retrieved from the backend
2613
+ * Search for a user based on a search term
2614
+ * @param term Search term
2615
+ * @param excludeMe whether or not to exclude myself from the result list
2616
+ * @param roles narrow down the search results by certain roles
2736
2617
  */
2737
- #initApp(userJson) {
2738
- return this.#userService.fetchUserSettings().pipe(switchMap((userSettings) => {
2739
- const currentUser = new YuvUser(userJson, userSettings);
2740
- this.#userService.setCurrentUser(currentUser);
2741
- this.#authData = {
2742
- tenant: currentUser.tenant,
2743
- language: currentUser.getClientLocale()
2744
- };
2745
- return forkJoin([
2746
- this.#localizationService.fetchLocalizations(),
2747
- this.#systemService.getSystemDefinition(this.#authData)
2748
- ]).pipe(switchMap(() => this.#objectConfigService.init()), map(() => currentUser));
2749
- }));
2618
+ queryUser(term, excludeMe, roles) {
2619
+ let params = new HttpParams().set('search', term).set('excludeMe', `${!!excludeMe}`);
2620
+ roles?.length && roles.map((r) => (params = params.append(`roles`, r)));
2621
+ return this.#backend
2622
+ .get(`/idm/users?${params}`)
2623
+ .pipe(map((users) => (!users ? [] : users.map((u) => new YuvUser(u)))));
2750
2624
  }
2751
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthService, deps: [{ token: CORE_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); }
2752
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthService, providedIn: 'root' }); }
2753
- }
2754
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthService, decorators: [{
2755
- type: Injectable,
2756
- args: [{
2757
- providedIn: 'root'
2758
- }]
2759
- }], ctorParameters: () => [{ type: CoreConfig, decorators: [{
2760
- type: Inject,
2761
- args: [CORE_CONFIG]
2762
- }] }] });
2763
-
2764
- /**
2765
- * Prevent app from running into 401 issues related to gateway timeouts.
2766
- */
2767
- function AuthInterceptorFnc(req, next) {
2768
- const userService = inject(UserService);
2769
- const auth = inject(AuthService);
2770
- return next(req).pipe(tap({
2771
- next: (event) => {
2772
- if (event instanceof HttpResponse) {
2773
- // do stuff with response if you want
2774
- }
2775
- },
2776
- error: (error) => {
2777
- if (error instanceof HttpErrorResponse || error.isHttpErrorResponse) {
2778
- if (error.status === 401) {
2779
- auth.logout();
2780
- userService.logout();
2781
- }
2782
- }
2783
- }
2784
- }));
2625
+ getUserById(id) {
2626
+ return this.#backend.get(`/idm/users/${id}`).pipe(map((user) => new YuvUser(user, this.#user.userSettings)));
2627
+ }
2628
+ logout(redirRoute) {
2629
+ const redir = redirRoute ? `?redir=${redirRoute}` : '';
2630
+ window.location.href = `${this.#backend.getApiBase('logout')}${redir}`;
2631
+ }
2632
+ getSettings(section) {
2633
+ return this.#user ? this.#backend.get(this.#USERS_SETTINGS + encodeURIComponent(section)) : of(null);
2634
+ }
2635
+ saveObjectConfig(objectConfigs) {
2636
+ return this.#backend.post(this.#USERS_SETTINGS + encodeURIComponent(this.#SETTINGS_SECTION_OBJECTCONFIG), objectConfigs);
2637
+ }
2638
+ loadObjectConfig() {
2639
+ return this.getSettings(this.#SETTINGS_SECTION_OBJECTCONFIG).pipe(catchError(() => of(undefined)));
2640
+ }
2641
+ #getUiDirection(iso) {
2642
+ // languages that are read right to left
2643
+ const rtlLanguages = ['ar', 'arc', 'dv', 'fa', 'ha', 'he', 'khw', 'ks', 'ku', 'ps', 'ur', 'yi'];
2644
+ return !rtlLanguages.includes(iso) ? Direction.LTR : Direction.RTL;
2645
+ }
2646
+ #setUserPermissions(res) {
2647
+ this.userPermissions = {
2648
+ create: this.#mapPermissions('CREATE', res),
2649
+ write: this.#mapPermissions('WRITE', res),
2650
+ read: this.#mapPermissions('READ', res),
2651
+ delete: this.#mapPermissions('DELETE', res)
2652
+ };
2653
+ const permissions = {
2654
+ createableObjectTypes: [
2655
+ ...this.userPermissions.create.folderTypes,
2656
+ ...this.userPermissions.create.objectTypes,
2657
+ ...this.userPermissions.create.secondaryObjectTypes
2658
+ ],
2659
+ searchableObjectTypes: [
2660
+ ...this.userPermissions.read.folderTypes,
2661
+ ...this.userPermissions.read.objectTypes,
2662
+ ...this.userPermissions.read.secondaryObjectTypes
2663
+ ]
2664
+ };
2665
+ this.#system.setPermissions(permissions);
2666
+ this.canCreateObjects = permissions.createableObjectTypes.length > 0;
2667
+ }
2668
+ #mapPermissions(section, apiResponse) {
2669
+ const res = apiResponse[section] || {};
2670
+ return {
2671
+ folderTypes: res['folderTypeIds'] || [],
2672
+ objectTypes: res['objectTypeIds'] || [],
2673
+ secondaryObjectTypes: res['secondaryObjectTypeIds'] || []
2674
+ };
2675
+ }
2676
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2677
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UserService, providedIn: 'root' }); }
2785
2678
  }
2786
-
2787
- var LoginStateName;
2788
- (function (LoginStateName) {
2789
- LoginStateName["STATE_LOGIN_URI"] = "login.uri";
2790
- LoginStateName["STATE_DONE"] = "login.done";
2791
- LoginStateName["STATE_CANCELED"] = "login.canceled";
2792
- })(LoginStateName || (LoginStateName = {}));
2679
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UserService, decorators: [{
2680
+ type: Injectable,
2681
+ args: [{
2682
+ providedIn: 'root'
2683
+ }]
2684
+ }] });
2793
2685
 
2794
2686
  /**
2795
- * Service to monitor the online/offline state of the application.
2796
- * It listens to the browser's online and offline events and provides an observable
2797
- * to track the current connection state.
2798
- *
2799
- * An observable `connection$` is provided which emits the current connection state
2800
- * whenever the online or offline state changes. The initial state is determined by
2801
- * the `window.navigator.onLine` property.
2687
+ * Service providing access to the systems audit entries. Audits can be seen as the history of
2688
+ * an object. Actions perormed on an object (eg. read, write, delete, ...) will be recorded during
2689
+ * the objects lifecycle. Audits are provided based on a users permissions. Beside the audit entries
2690
+ * visible to regular users there are more technical ones that will only be shown to users that
2691
+ * have administrative role.
2802
2692
  */
2803
- class ConnectionService {
2693
+ class AuditService {
2694
+ constructor() {
2695
+ this.#searchService = inject(SearchService);
2696
+ this.#userService = inject(UserService);
2697
+ // default number of items to be fetched
2698
+ this.DEFAULT_RES_SIZE = 20;
2699
+ // audit action codes that should be visible to regular users
2700
+ this.userAuditActions = [
2701
+ 100, // metadata created
2702
+ 101, // metadata created (with content)
2703
+ 110, // tag created
2704
+ 201, // content deleted
2705
+ 210, // tag deleted
2706
+ 300, // metadata updated
2707
+ 301, // content updated
2708
+ 302, // metadata and content updated
2709
+ 303, // content moved
2710
+ 310, // tag updated
2711
+ 325, // object restored form version
2712
+ 340, // object moved
2713
+ 10000 // custom audit entries
2714
+ ];
2715
+ // audit action codes that should be visible to admin users
2716
+ this.adminAuditActions = [
2717
+ 202, // marked for delete
2718
+ 220, // version deleted
2719
+ 400, // content read
2720
+ 401, // metadata read
2721
+ 402, // rendition read (text)
2722
+ 403, // rendition read (pdf)
2723
+ 404 // rendition read (thumbnail)
2724
+ ];
2725
+ }
2726
+ #searchService;
2727
+ #userService;
2804
2728
  /**
2805
- * @ignore
2729
+ * Get audit entries of a dms object
2730
+ * @param id The id of the object to get the audit entries for
2731
+ * @param options Options
2806
2732
  */
2807
- constructor() {
2808
- this.currentState = {
2809
- isOnline: window.navigator.onLine
2733
+ getAuditEntries(id, options = {}) {
2734
+ const auditActions = this.getAuditActions(!!options.allActions, options?.skipActions);
2735
+ const q = {
2736
+ size: this.DEFAULT_RES_SIZE,
2737
+ types: [SystemType.AUDIT],
2738
+ filters: [
2739
+ {
2740
+ f: AuditField.REFERRED_OBJECT_ID,
2741
+ o: Operator.EQUAL,
2742
+ v1: id
2743
+ },
2744
+ {
2745
+ f: AuditField.ACTION,
2746
+ o: Operator.IN,
2747
+ v1: auditActions
2748
+ }
2749
+ ],
2750
+ sort: [
2751
+ {
2752
+ field: AuditField.CREATION_DATE,
2753
+ order: 'desc'
2754
+ }
2755
+ ]
2810
2756
  };
2811
- this.connectionStateSource = new ReplaySubject();
2812
- this.connection$ = this.connectionStateSource.asObservable();
2813
- this.connectionStateSource.next(this.currentState);
2814
- fromEvent(window, 'online').subscribe(() => {
2815
- this.currentState.isOnline = true;
2816
- this.connectionStateSource.next(this.currentState);
2817
- });
2818
- fromEvent(window, 'offline').subscribe(() => {
2819
- this.currentState.isOnline = false;
2820
- this.connectionStateSource.next(this.currentState);
2821
- });
2757
+ return this.#fetchAudits(q);
2822
2758
  }
2823
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ConnectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2824
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ConnectionService, providedIn: 'root' }); }
2759
+ /**
2760
+ * Get an array of action codes that are provided by the service. Based on
2761
+ * whether or not the user has admin permissions you'll get a different
2762
+ * set of actions.
2763
+ * @param skipActions codes of actions that should not be fetched
2764
+ */
2765
+ getAuditActions(allActions, skipActions) {
2766
+ const actions = allActions || this.#userService.isAdvancedUser ? [...this.userAuditActions, ...this.adminAuditActions] : this.userAuditActions;
2767
+ return actions.filter((a) => !skipActions || !skipActions.includes(a));
2768
+ }
2769
+ /**
2770
+ * Get a certain page for a former audits query.
2771
+ * @param auditsResult The result object of a former audits query
2772
+ * @param page The page to load
2773
+ */
2774
+ getPage(auditsResult, page) {
2775
+ const q = auditsResult.query;
2776
+ q.from = (page - 1) * (q.size || this.DEFAULT_RES_SIZE);
2777
+ return this.#fetchAudits(q);
2778
+ }
2779
+ #fetchAudits(q) {
2780
+ return this.#searchService.searchRaw(q).pipe(map((res) => ({
2781
+ query: q,
2782
+ items: res.objects.map((o) => ({
2783
+ action: o.properties[AuditField.ACTION].value,
2784
+ actionGroup: this.#getActionGroup(o.properties[AuditField.ACTION].value),
2785
+ detail: o.properties[AuditField.DETAIL].value,
2786
+ subaction: o.properties[AuditField.SUBACTION] ? o.properties[AuditField.SUBACTION].value : null,
2787
+ version: o.properties[AuditField.VERSION].value,
2788
+ creationDate: o.properties[AuditField.CREATION_DATE].value,
2789
+ createdBy: {
2790
+ id: o.properties[AuditField.CREATED_BY].value,
2791
+ title: o.properties[AuditField.CREATED_BY].title
2792
+ }
2793
+ })),
2794
+ hasMoreItems: res.hasMoreItems,
2795
+ page: !q.from ? 1 : q.from / q.size + 1
2796
+ })));
2797
+ }
2798
+ #getActionGroup(action) {
2799
+ try {
2800
+ return parseInt(`${action}`.substr(0, 1));
2801
+ }
2802
+ catch {
2803
+ return -1;
2804
+ }
2805
+ }
2806
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuditService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2807
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuditService, providedIn: 'root' }); }
2825
2808
  }
2826
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ConnectionService, decorators: [{
2809
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuditService, decorators: [{
2827
2810
  type: Injectable,
2828
2811
  args: [{
2829
2812
  providedIn: 'root'
2830
2813
  }]
2831
- }], ctorParameters: () => [] });
2832
-
2833
- /**
2834
- * Http Offline interceptor trys to serving offline content for method/url
2835
- */
2836
- function OfflineInterceptorFnc(req, next) {
2837
- return next(req);
2838
- }
2814
+ }] });
2839
2815
 
2840
2816
  /**
2841
- * Providing functions,that are are injected at application startup and executed during app initialization.
2817
+ * @ignore
2842
2818
  */
2843
- const init_moduleFnc = () => {
2844
- const coreConfig = inject(CORE_CONFIG);
2845
- const logger = inject(Logger);
2846
- const http = inject(HttpClient);
2847
- const configService = inject(ConfigService);
2848
- const authService = inject(AuthService);
2849
- return authService.setInitialRequestUri().pipe(switchMap(() => coreConfig.main
2850
- ? !Array.isArray(coreConfig.main)
2851
- ? of([coreConfig.main])
2852
- : forkJoin([...coreConfig.main].map((uri) => http.get(`${Utils.getBaseHref()}${uri}`).pipe(catchError((e) => {
2853
- logger.error('failed to catch config file', e);
2854
- return of({});
2855
- }), map((res) => res)))).pipe(
2856
- // switchMap((configs: YuvConfig[]) => configService.extendConfig(configs)),
2857
- tap((configs) => configService.extendConfig(configs)), switchMap(() => authService.initUser().pipe(catchError((e) => {
2858
- authService.initError = {
2859
- status: e.status,
2860
- key: e.error.error
2861
- };
2862
- return of(true);
2863
- }))))
2864
- : of(false)));
2865
- };
2819
+ const CUSTOM_CONFIG = new InjectionToken('CUSTOM_CONFIG', {
2820
+ factory: () => ({ main: ['assets/_yuuvis/config/main.json'], translations: ['assets/_yuuvis/i18n/'] })
2821
+ });
2822
+ const CORE_CONFIG = new InjectionToken('CORE_CONFIG');
2866
2823
 
2867
2824
  /**
2868
- * Handles missing translations
2869
2825
  * @ignore
2870
2826
  */
2871
- class EoxMissingTranslationHandler {
2872
- handle(params) {
2873
- return '!missing key: ' + params.key;
2827
+ let CoreConfig = class CoreConfig {
2828
+ constructor(__config) {
2829
+ this.main = ['assets/_yuuvis/config/main.json'];
2830
+ this.translations = ['assets/_yuuvis/i18n/'];
2831
+ this.environment = { production: true };
2832
+ Object.assign(this, __config);
2874
2833
  }
2875
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EoxMissingTranslationHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2876
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EoxMissingTranslationHandler }); }
2877
- }
2878
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EoxMissingTranslationHandler, decorators: [{
2879
- type: Injectable
2880
- }] });
2834
+ };
2835
+ CoreConfig = __decorate([
2836
+ __param(0, Inject(CUSTOM_CONFIG)),
2837
+ __metadata("design:paramtypes", [CoreConfig])
2838
+ ], CoreConfig);
2881
2839
 
2882
2840
  /**
2883
- * i18n packages
2884
- */
2885
- /**
2886
- * Loader that fetches translations based on the configured locations
2887
- * @ignore
2841
+ * Service managing the configuarions for object types.
2842
+ * Configuration means that you can pick certain fields
2843
+ * of an object type and assign them to a config object.
2888
2844
  */
2889
- class EoxTranslateJsonLoader {
2890
- constructor(http, config) {
2891
- this.http = http;
2892
- this.config = config;
2893
- registerLocaleData(localeDe, 'de', localeExtraDe); // German
2894
- registerLocaleData(localeAr, 'ar', localeExtraAr); // Arabic
2895
- registerLocaleData(localeEs, 'es', localeExtraEs); // Spanish
2896
- registerLocaleData(localePt, 'pt', localeExtraPt); // Portuguese
2897
- registerLocaleData(localeFr, 'fr', localeExtraFr); // French
2898
- registerLocaleData(localeZh, 'zh', localeExtraZh); // Chinese
2899
- registerLocaleData(localeLv, 'lv', localeExtraLv); // Latvian
2900
- registerLocaleData(localeRu, 'ru', localeExtraRu); // Russian
2901
- registerLocaleData(localeIt, 'it', localeExtraIt); // Italian
2902
- registerLocaleData(localeSk, 'sk', localeExtraSk); // Slovak
2903
- registerLocaleData(localePl, 'pl', localeExtraPl); // Polish
2904
- registerLocaleData(localeUk, 'uk', localeExtraUk); // Ukrainian
2905
- registerLocaleData(localeJa, 'ja', localeExtraJa); // Japanese
2906
- registerLocaleData(localeKo, 'ko', localeExtraKo); // Korean
2907
- registerLocaleData(localeHi, 'hi', localeExtraHi); // Hindi
2908
- registerLocaleData(localeBn, 'bn', localeExtraBn); // Bengalese
2909
- registerLocaleData(localeVi, 'vi', localeExtraVi); // Vietnamese
2910
- registerLocaleData(localeTr, 'tr', localeExtraTr); // Turkish
2911
- registerLocaleData(localeNl, 'nl', localeExtraNl); // Dutch
2912
- registerLocaleData(localeNb, 'nb', localeExtraNb); // Norwegian
2913
- registerLocaleData(localeTh, 'th', localeExtraTh); // Thai
2914
- registerLocaleData(localeFi, 'fi', localeExtraFi); // Finnish
2915
- registerLocaleData(localeSv, 'sv', localeExtraSv); // Swedish
2916
- registerLocaleData(localeDeCh, 'de-CH', localeExtraDeCh); // German Swiss
2845
+ class ObjectConfigService {
2846
+ constructor() {
2847
+ this.#system = inject(SystemService);
2848
+ this.#user = inject(UserService);
2849
+ this.OBJECT_CONFIG_STORAGE_KEY = 'yuv.framework.object-config';
2850
+ this.#objectConfigsSource = new ReplaySubject();
2851
+ this.#objectConfigs$ = this.#objectConfigsSource.asObservable();
2852
+ this.#defaultObjectConfigs = {};
2853
+ this.#registeredDefaultObjectConfigs = {
2854
+ main: {},
2855
+ buckets: []
2856
+ };
2857
+ this.#objectConfigs = {
2858
+ main: {},
2859
+ buckets: []
2860
+ };
2917
2861
  }
2918
- /**
2919
- *
2920
- * @param string lang
2921
- * @returns Observable<Object>
2922
- */
2923
- getTranslation(lang) {
2924
- const t = (this.config.translations || []).map((path) => this.loadTranslationFile(path, lang));
2925
- return forkJoin(t).pipe(map((res) => res.reduce((acc, x) => Object.assign(acc, x), {})));
2862
+ #system;
2863
+ #user;
2864
+ #objectConfigsSource;
2865
+ #objectConfigs$;
2866
+ #defaultObjectConfigs;
2867
+ #registeredDefaultObjectConfigs;
2868
+ #objectConfigs;
2869
+ #isValidObjectConfig(soc) {
2870
+ return coerceBooleanProperty(soc && Object.hasOwn(soc, 'main') && Object.hasOwn(soc, 'buckets'));
2926
2871
  }
2927
- loadTranslationFile(path, lang) {
2928
- const version = document.body.dataset['bt'] ?? document.body.dataset['version'];
2929
- const cacheBuster = version ? `?v=${version}` : '';
2930
- return this.http.get(`${Utils.getBaseHref()}${path}${lang}.json${cacheBuster}`).pipe(catchError(() => {
2931
- // ISO codes with more than 2 characters are sub-languages like de-CH.
2932
- // If there is no translation file for that sub-language we'll try to load
2933
- // the file for the base language (in this case de).
2934
- return lang.length > 2 ? this.loadTranslationFile(path, lang.substring(0, 2)) : of({});
2872
+ // called on core init (auth.service -> initApp)
2873
+ init() {
2874
+ return this.#user.loadObjectConfig().pipe(tap$1((res) => {
2875
+ this.#getDefaultObjectConfig();
2876
+ this.#objectConfigs = (this.#isValidObjectConfig(res) && res) || {
2877
+ main: {},
2878
+ buckets: []
2879
+ };
2880
+ this.#objectConfigsSource.next(this.#objectConfigs);
2935
2881
  }));
2936
2882
  }
2937
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EoxTranslateJsonLoader, deps: [{ token: i1.HttpClient }, { token: CORE_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); }
2938
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EoxTranslateJsonLoader }); }
2939
- }
2940
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EoxTranslateJsonLoader, decorators: [{
2941
- type: Injectable
2942
- }], ctorParameters: () => [{ type: i1.HttpClient }, { type: CoreConfig, decorators: [{
2943
- type: Inject,
2944
- args: [CORE_CONFIG]
2945
- }] }] });
2946
-
2947
- var Operator;
2948
- (function (Operator) {
2949
- Operator["EQUAL"] = "eq";
2950
- Operator["EEQUAL"] = "eeq";
2951
- Operator["IN"] = "in";
2952
- Operator["GREATER_THAN"] = "gt";
2953
- Operator["GREATER_OR_EQUAL"] = "gte";
2954
- Operator["LESS_THAN"] = "lt";
2955
- Operator["LESS_OR_EQUAL"] = "lte";
2956
- Operator["INTERVAL"] = "gtlt";
2957
- Operator["INTERVAL_INCLUDE_BOTH"] = "gtelte";
2958
- Operator["INTERVAL_INCLUDE_TO"] = "gtlte";
2959
- Operator["INTERVAL_INCLUDE_FROM"] = "gtelt";
2960
- Operator["RANGE"] = "rg";
2961
- Operator["LIKE"] = "like";
2962
- Operator["CONTAINS"] = "contains"; // contains
2963
- })(Operator || (Operator = {}));
2964
- const OperatorLabel = {
2965
- /** equal */
2966
- EQUAL: '=',
2967
- /** exact equal */
2968
- EEQUAL: '==',
2969
- /** match at least one of the provided values (value has to be an array) */
2970
- IN: '~',
2971
- /** greater than */
2972
- GREATER_THAN: '>',
2973
- /** greater than or equal */
2974
- GREATER_OR_EQUAL: '≽', //
2975
- LESS_THAN: '<', // less than
2976
- LESS_OR_EQUAL: '≼', // less than or equal
2977
- INTERVAL: '<>', // interval
2978
- INTERVAL_INCLUDE_BOTH: '-', // interval include left and right
2979
- INTERVAL_INCLUDE_TO: '>-', // interval include right
2980
- INTERVAL_INCLUDE_FROM: '-<', // interval include left
2981
- RANGE: '=', // aggegation ranges
2982
- LIKE: '~', // like
2983
- CONTAINS: '~' // contains
2984
- };
2985
-
2986
- class SearchService {
2987
- #backend = inject(BackendService);
2988
- #system = inject(SystemService);
2989
- static { this.DEFAULT_QUERY_SIZE = 50; }
2990
- /**
2991
- * Execute a search query ans transform the result to a SearchResult object
2992
- * @param query The search query
2993
- * @returns Observable of a SearchResult
2994
- */
2995
- search(query) {
2996
- return this.searchRaw(query).pipe(map((res) => this.toSearchResult(res, query.size || SearchService.DEFAULT_QUERY_SIZE, query.from || 0)));
2997
- }
2998
- /**
2999
- * Execute a raw search query and return the result as is.
3000
- * @param query The search query
3001
- * @returns Observable of the raw search result
3002
- */
3003
- searchRaw(query) {
3004
- if (!query.size)
3005
- query.size = SearchService.DEFAULT_QUERY_SIZE;
3006
- return this.#backend.post(`/dms/objects/search`, query);
2883
+ getObjectConfigs$(bucket, includeDefaults) {
2884
+ return this.#objectConfigs$.pipe(map$1((soc) => {
2885
+ let ocr = soc.main;
2886
+ const defaults = { ...this.#defaultObjectConfigs, ...this.#getRegisteredDefaults(bucket) };
2887
+ if (bucket) {
2888
+ const tileBucket = soc.buckets.find((b) => b.id === bucket);
2889
+ if (tileBucket) {
2890
+ ocr = tileBucket.configs;
2891
+ if (includeDefaults)
2892
+ Object.keys(ocr).forEach((k) => {
2893
+ ocr[k] = { ...defaults[k], ...ocr[k] };
2894
+ });
2895
+ }
2896
+ }
2897
+ return includeDefaults ? { ...defaults, ...ocr } : { ...ocr };
2898
+ }));
3007
2899
  }
3008
2900
  /**
3009
- * Search for objects in the dms using CMIS like SQL syntax.
3010
- * @param statement The query statement
3011
- * @param size The number of items to return
3012
- * @returns Observable of a SearchResult
2901
+ * Register default object configs. Apps can pre-define how their
2902
+ * objects should be rendered.
2903
+ * @param bucket Name of a bucket to bind configs to (usually the ID of the app) to
2904
+ * separate the configs from other apps. If not provided configs will be saved for
2905
+ * the global scope
3013
2906
  */
3014
- searchCmis(statement, size = SearchService.DEFAULT_QUERY_SIZE, includePermissions = false) {
3015
- return this.#executeCmisSearch(statement, size, 0, includePermissions);
2907
+ registerDefaults(configs, bucket) {
2908
+ if (bucket) {
2909
+ // does bucket exist?
2910
+ const tileBucketIdx = this.#registeredDefaultObjectConfigs.buckets.findIndex((b) => b.id === bucket);
2911
+ if (tileBucketIdx !== -1) {
2912
+ this.#registeredDefaultObjectConfigs.buckets[tileBucketIdx].configs = {
2913
+ ...this.#registeredDefaultObjectConfigs.buckets[tileBucketIdx].configs,
2914
+ ...configs
2915
+ };
2916
+ }
2917
+ else {
2918
+ this.#registeredDefaultObjectConfigs.buckets.push({
2919
+ id: bucket,
2920
+ configs
2921
+ });
2922
+ }
2923
+ }
2924
+ else {
2925
+ this.#registeredDefaultObjectConfigs.main = { ...this.#registeredDefaultObjectConfigs.main, ...configs };
2926
+ }
3016
2927
  }
3017
- #executeCmisSearch(statement, maxItems, skipCount = 0, includePermissions = false) {
3018
- const payload = {
3019
- query: {
3020
- statement,
3021
- skipCount,
3022
- maxItems,
3023
- handleDeletedDocuments: 'DELETED_DOCUMENTS_EXCLUDE'
2928
+ unregisterDefaults(configs, bucket) {
2929
+ if (bucket) {
2930
+ // does bucket exist?
2931
+ const tileBucketIdx = this.#registeredDefaultObjectConfigs.buckets.findIndex((b) => b.id === bucket);
2932
+ if (tileBucketIdx !== -1) {
2933
+ this.#registeredDefaultObjectConfigs.buckets.splice(tileBucketIdx, 1);
3024
2934
  }
3025
- };
3026
- if (includePermissions) {
3027
- payload.query.includePermissions = true;
3028
2935
  }
3029
- return this.#backend
3030
- .post('/dms/objects/cmisSearch', payload
3031
- // Using API-WEB because it enriches the response with additional information (resolved user names, etc.)
3032
- // ApiBase.core
3033
- )
3034
- .pipe(map((res) => this.toSearchResult(res, maxItems, skipCount)));
2936
+ else {
2937
+ Object.keys(configs).forEach((key) => {
2938
+ delete this.#registeredDefaultObjectConfigs.main[key];
2939
+ });
2940
+ }
3035
2941
  }
3036
- /**
3037
- * Fetch aggragations for a given query.
3038
- * @param q The query
3039
- * @param aggregations List of aggregations to be fetched (e.g. `enaio:objectTypeId`
3040
- * to get an aggregation of object types)
3041
- */
3042
- aggregate(q, aggregations) {
3043
- q.aggs = aggregations;
3044
- return this.searchRaw(q).pipe(map((res) => this.#toAggregateResult(res, aggregations)));
3045
- }
3046
- /**
3047
- * Map search result from the backend to applications AggregateResult object
3048
- * @param searchResponse The backend response
3049
- * @param aggregations The aggregations to be fetched
3050
- */
3051
- #toAggregateResult(searchResponse, aggregations) {
3052
- const agg = [];
3053
- if (aggregations) {
3054
- aggregations.forEach((a) => {
3055
- const ag = {
3056
- aggKey: a,
3057
- entries: searchResponse.objects.map((o) => ({
3058
- key: o.properties[a].value,
3059
- count: o.properties['OBJECT_COUNT'].value
3060
- }))
3061
- };
3062
- agg.push(ag);
3063
- });
3064
- }
3065
- return {
3066
- totalNumItems: searchResponse.totalNumItems,
3067
- aggregations: agg
3068
- };
3069
- }
3070
- /**
3071
- * Go to a page of a search result.
3072
- * @param query SearchQuery or CMIS query statement
3073
- * @param page The number of the page to go to
3074
- */
3075
- getPage(query, page, pageSize = SearchService.DEFAULT_QUERY_SIZE, includePermissions = false) {
3076
- const isCmis = typeof query === 'string';
3077
- if (isCmis) {
3078
- return this.#executeCmisSearch(query, pageSize, (page - 1) * pageSize, includePermissions);
2942
+ getRegisteredDefault(objectTypeId, bucket) {
2943
+ if (bucket) {
2944
+ const b = this.#registeredDefaultObjectConfigs.buckets.find((b) => b.id === bucket);
2945
+ return b ? b.configs[objectTypeId] : undefined;
3079
2946
  }
3080
2947
  else {
3081
- query.from = (page - 1) * (query.size || pageSize);
3082
- return this.search(query);
2948
+ return this.#registeredDefaultObjectConfigs.main[objectTypeId];
2949
+ }
2950
+ }
2951
+ #getRegisteredDefaults(bucket) {
2952
+ if (bucket) {
2953
+ const defaultTileBucket = this.#registeredDefaultObjectConfigs.buckets.find((b) => b.id === bucket);
2954
+ return defaultTileBucket?.configs || {};
3083
2955
  }
2956
+ else
2957
+ return this.#registeredDefaultObjectConfigs.main;
3084
2958
  }
3085
2959
  /**
3086
- * Map search result from the backend to applications SearchResult object
3087
- * @param searchResponse The backend response
2960
+ * Get object config for an object type.
2961
+ * @param objectTypeId ID of the object type to get the config for
2962
+ * @param bucket Optional bucket ID to fetch the config from. Buckets
2963
+ * define separated areas to store/receive those configs. This way
2964
+ * components/apps can store different object configs than the ones
2965
+ * used accross the whole client application.
3088
2966
  */
3089
- toSearchResult(searchResponse, pageSize = SearchService.DEFAULT_QUERY_SIZE, skipCount = 0) {
3090
- const resultListItems = [];
3091
- const objectTypes = [];
3092
- searchResponse.objects.forEach((o) => {
3093
- const fields = new Map();
3094
- // process properties section of result
3095
- Object.keys(o.properties).forEach((key) => {
3096
- let value = o.properties[key].value;
3097
- if (o.properties[key].clvalue) {
3098
- // table fields will have a clientValue too ...
3099
- value = o.properties[key].clvalue;
3100
- // ... and also may contain values that need to be resolved
3101
- if (o.properties[key].resolvedValues) {
3102
- value.forEach((v) => {
3103
- Object.keys(v).forEach((k) => {
3104
- const resValue = Array.isArray(v[k]) ? v[k].map((i) => o.properties[key].resolvedValues[i]) : o.properties[key].resolvedValues[v[k]];
3105
- if (resValue) {
3106
- v[`${k}_title`] = resValue;
3107
- }
3108
- });
3109
- });
3110
- }
3111
- }
3112
- fields.set(key, value);
3113
- if (o.properties[key].title) {
3114
- fields.set(key + '_title', o.properties[key].title);
3115
- }
3116
- });
3117
- // process contentStreams section of result if available.
3118
- // Objects that don't have files attached won't have this section
3119
- let content;
3120
- if (o.contentStreams && o.contentStreams.length > 0) {
3121
- // we assume that each result object only has ONE file attached, altough
3122
- // this is an array and there may be more
3123
- const contentStream = o.contentStreams[0];
3124
- // also add contentstream related fields to the result fields
3125
- fields.set(ContentStreamField.LENGTH, contentStream.length);
3126
- fields.set(ContentStreamField.MIME_TYPE, contentStream.mimeType);
3127
- fields.set(ContentStreamField.FILENAME, contentStream.fileName);
3128
- fields.set(ContentStreamField.ID, contentStream.contentStreamId);
3129
- fields.set(ContentStreamField.RANGE, contentStream.contentStreamRange);
3130
- fields.set(ContentStreamField.REPOSITORY_ID, contentStream.repositoryId);
3131
- fields.set(ContentStreamField.DIGEST, contentStream.digest);
3132
- fields.set(ContentStreamField.ARCHIVE_PATH, contentStream.archivePath);
3133
- content = {
3134
- contentStreamId: contentStream.contentStreamId,
3135
- repositoryId: contentStream.repositoryId,
3136
- range: contentStream.range,
3137
- digest: contentStream.digest,
3138
- archivePath: contentStream.archivePath,
3139
- fileName: contentStream.fileName,
3140
- mimeType: contentStream.mimeType,
3141
- size: contentStream.length
3142
- };
3143
- }
3144
- const objectTypeId = o.properties[BaseObjectTypeField.OBJECT_TYPE_ID] ? o.properties[BaseObjectTypeField.OBJECT_TYPE_ID].value : null;
3145
- if (objectTypes.indexOf(objectTypeId) === -1) {
3146
- objectTypes.push(objectTypeId);
3147
- }
3148
- resultListItems.push({
3149
- objectTypeId,
3150
- content,
3151
- fields,
3152
- permissions: o.permissions
3153
- });
3154
- });
3155
- const totalPages = Math.ceil(searchResponse.totalNumItems / pageSize);
3156
- const page = (!skipCount ? 0 : skipCount / pageSize) + 1;
3157
- const result = {
3158
- hasMoreItems: searchResponse.hasMoreItems,
3159
- totalNumItems: searchResponse.totalNumItems,
3160
- items: resultListItems,
3161
- objectTypes: objectTypes,
3162
- paging: totalPages > 1 ? { page, totalPages } : undefined
3163
- };
2967
+ getObjectConfig(type, bucket) {
2968
+ const b = bucket ? this.#objectConfigs.buckets.find((b) => b.id === bucket) : undefined;
2969
+ const otr = b ? b.configs : this.#objectConfigs.main;
2970
+ const oc = otr[type.id];
2971
+ // return oc || this.#getRegisteredDefaults(bucket)[type.id];
2972
+ // override icons by APP_SCHEMA-icons: temporary solution. Todo: remove when handling of custom svg-icons is clear!
2973
+ const result = oc || this.#getRegisteredDefaults(bucket)[type.id] || this.#defaultObjectConfigs[type.id];
2974
+ if (result?.icon)
2975
+ result.icon = type.icon ? { svg: type.icon } : result.icon;
3164
2976
  return result;
3165
2977
  }
3166
- /**
3167
- * Maps data extracted from a search form to search filters. Every key of the form data object
3168
- * will be mapped to a search filter.
3169
- * @param formData form data
3170
- * @returns Array of search filters
3171
- */
3172
- formDataToSearchFilter(formData) {
3173
- const isRangeValue = (v) => {
3174
- return typeof v === 'object' && v !== null && 'firstValue' in v && 'operator' in v;
3175
- };
3176
- const filters = [];
3177
- Object.keys(formData).forEach((key) => {
3178
- const value = formData[key];
3179
- if (isRangeValue(value)) {
3180
- const f = this.rangeValueToSearchFilter(value, key);
3181
- if (f)
3182
- filters.push(f);
2978
+ saveObjectConfig(type, config, bucket) {
2979
+ return this.saveObjectConfigs({ [type.id]: config }, bucket);
2980
+ }
2981
+ saveObjectConfigs(configs, bucket) {
2982
+ this.#updateObjectConfig(configs, bucket);
2983
+ return this.#user.saveObjectConfig(this.#objectConfigs);
2984
+ }
2985
+ #updateObjectConfig(configs, bucket) {
2986
+ if (!this.#objectConfigs) {
2987
+ this.#objectConfigs = {
2988
+ main: {},
2989
+ buckets: []
2990
+ };
2991
+ }
2992
+ if (bucket) {
2993
+ // does bucket exist?
2994
+ const tileBucketIdx = this.#objectConfigs.buckets.findIndex((b) => b.id === bucket);
2995
+ if (tileBucketIdx !== -1) {
2996
+ Object.keys(configs).forEach((objectTypeId) => {
2997
+ const config = configs[objectTypeId];
2998
+ if (config) {
2999
+ this.#objectConfigs.buckets[tileBucketIdx].configs[objectTypeId] = config;
3000
+ }
3001
+ else {
3002
+ delete this.#objectConfigs.buckets[tileBucketIdx].configs[objectTypeId];
3003
+ }
3004
+ });
3183
3005
  }
3184
3006
  else {
3185
- filters.push({
3186
- f: key,
3187
- o: Array.isArray(value) ? Operator.IN : Operator.EQUAL,
3188
- v1: value
3007
+ const newBucket = { id: bucket, configs: {} };
3008
+ Object.keys(configs).forEach((objectTypeId) => {
3009
+ const config = configs[objectTypeId];
3010
+ if (config) {
3011
+ newBucket.configs[objectTypeId] = config;
3012
+ }
3189
3013
  });
3014
+ this.#objectConfigs.buckets.push(newBucket);
3190
3015
  }
3191
- });
3192
- return filters;
3016
+ }
3017
+ else {
3018
+ Object.keys(configs).forEach((objectTypeId) => {
3019
+ const config = configs[objectTypeId];
3020
+ if (config) {
3021
+ this.#objectConfigs.main[objectTypeId] = config;
3022
+ }
3023
+ else {
3024
+ delete this.#objectConfigs.main[objectTypeId];
3025
+ }
3026
+ });
3027
+ }
3028
+ this.#objectConfigsSource.next(this.#objectConfigs);
3193
3029
  }
3194
- rangeValueToSearchFilter(value, property) {
3195
- return this.#system.getObjectTypeField(property)?.propertyType === 'datetime'
3196
- ? this.#dateRangeValueToSearchFilter(value, property)
3197
- : {
3198
- f: property,
3199
- o: value.operator,
3200
- v1: value.firstValue,
3201
- v2: value.secondValue
3202
- };
3030
+ getDefaultConfig(objectTypeID) {
3031
+ return this.#defaultObjectConfigs[objectTypeID];
3203
3032
  }
3204
- #dateRangeValueToSearchFilter(rv, property) {
3205
- const v1 = rv.firstValue;
3206
- const v2 = rv.secondValue;
3207
- switch (rv.operator) {
3208
- case Operator.EQUAL: {
3209
- return {
3210
- f: property,
3211
- o: Operator.INTERVAL_INCLUDE_BOTH,
3212
- v1: this.#dateToISOString(v1, 'start'),
3213
- v2: this.#dateToISOString(v1, 'end')
3214
- };
3215
- }
3216
- case Operator.GREATER_OR_EQUAL: {
3217
- return {
3218
- f: property,
3219
- o: rv.operator,
3220
- v1: this.#dateToISOString(v1)
3221
- };
3222
- }
3223
- case Operator.LESS_OR_EQUAL: {
3224
- return {
3225
- f: property,
3226
- o: rv.operator,
3227
- v1: this.#dateToISOString(v1, 'end')
3228
- };
3229
- }
3230
- case Operator.INTERVAL_INCLUDE_BOTH: {
3231
- return {
3232
- f: property,
3233
- o: rv.operator,
3234
- v1: this.#dateToISOString(v1, 'start'),
3235
- v2: v2 ? this.#dateToISOString(v2, 'end') : ''
3236
- };
3237
- }
3238
- default:
3239
- return undefined;
3240
- }
3033
+ getResolvedObjectConfig(data, type, bucket, includeDefaults) {
3034
+ const defaultCfg = this.#defaultObjectConfigs[data[BaseObjectTypeField.OBJECT_TYPE_ID]];
3035
+ let oc = this.getObjectConfig(type, bucket) || defaultCfg;
3036
+ if (includeDefaults)
3037
+ oc = { ...defaultCfg, ...oc };
3038
+ const res = {
3039
+ id: data[BaseObjectTypeField.OBJECT_ID],
3040
+ objectTypeId: data[BaseObjectTypeField.OBJECT_TYPE_ID],
3041
+ actions: oc.actions,
3042
+ badges: oc.badges,
3043
+ instanceData: data
3044
+ };
3045
+ if (oc.title)
3046
+ res.title = {
3047
+ propertyName: oc.title.propertyName,
3048
+ value: data[oc.title.propertyName]
3049
+ };
3050
+ if (oc.description)
3051
+ res.description = {
3052
+ propertyName: oc.description.propertyName,
3053
+ value: data[oc.description.propertyName]
3054
+ };
3055
+ if (oc.icon)
3056
+ res.icon = {
3057
+ propertyName: 'custom',
3058
+ value: oc.icon.svg
3059
+ };
3060
+ if (oc.meta)
3061
+ res.meta = {
3062
+ propertyName: oc.meta.propertyName,
3063
+ value: data[oc.meta.propertyName]
3064
+ };
3065
+ if (oc.aside)
3066
+ res.aside = {
3067
+ propertyName: oc.aside.propertyName,
3068
+ value: data[oc.aside.propertyName]
3069
+ };
3070
+ return res;
3241
3071
  }
3242
3072
  /**
3243
- * Checks if value is a date and retrieves an ISOString representation. Optionally you can determine, if it should be the beginning or the end of a day.
3244
- *
3245
- * @param value
3246
- * @param range
3247
- * @private
3073
+ * Load default/fallbac configs based on the object types and
3074
+ * their first properties
3248
3075
  */
3249
- #dateToISOString(value, range = 'start') {
3250
- const date = typeof value === 'string' ? new Date(value) : value;
3251
- const offset = Utils.DEFAULT_TIME_OFFSET_V2; // T+23:59:59:999
3252
- if (!Utils.isValidDate(date))
3253
- return '';
3254
- const isoDateString = date.toISOString();
3255
- // const isoDateStringWithOffset = new Date(date.getTime() + offset).toISOString();
3256
- switch (range) {
3257
- case 'start':
3258
- return isoDateString;
3259
- case 'end':
3260
- return isoDateString;
3261
- default:
3262
- return isoDateString;
3263
- }
3076
+ #getDefaultObjectConfig() {
3077
+ this.#system.getObjectTypes(true).forEach((ot) => {
3078
+ this.#defaultObjectConfigs[ot.id] = {
3079
+ objectTypeId: ot.id,
3080
+ title: this.#toObjectProperty(ot.fields[0]),
3081
+ description: this.#toObjectProperty(ot.fields[1]),
3082
+ meta: this.#toObjectProperty(ot.fields[2]),
3083
+ aside: this.#toObjectProperty(ot.fields[3])
3084
+ };
3085
+ });
3264
3086
  }
3265
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SearchService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3266
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SearchService, providedIn: 'root' }); }
3087
+ #toObjectProperty(field) {
3088
+ return field
3089
+ ? {
3090
+ label: this.#system.getLocalizedLabel(field.id),
3091
+ propertyName: field.id
3092
+ }
3093
+ : undefined;
3094
+ }
3095
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ObjectConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3096
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ObjectConfigService, providedIn: 'root' }); }
3267
3097
  }
3268
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SearchService, decorators: [{
3098
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ObjectConfigService, decorators: [{
3269
3099
  type: Injectable,
3270
3100
  args: [{
3271
3101
  providedIn: 'root'
3272
3102
  }]
3273
3103
  }] });
3274
3104
 
3105
+ const YUV_USER = new InjectionToken('Currently logged in user', {
3106
+ factory: () => undefined
3107
+ });
3108
+ const provideUser = (user) => {
3109
+ return { provide: YUV_USER, useValue: user };
3110
+ };
3111
+
3275
3112
  /**
3276
- * Service providing access to the systems audit entries. Audits can be seen as the history of
3277
- * an object. Actions perormed on an object (eg. read, write, delete, ...) will be recorded during
3278
- * the objects lifecycle. Audits are provided based on a users permissions. Beside the audit entries
3279
- * visible to regular users there are more technical ones that will only be shown to users that
3280
- * have administrative role.
3113
+ * Service handling authentication related issues.
3281
3114
  */
3282
- class AuditService {
3283
- constructor() {
3284
- this.#searchService = inject(SearchService);
3115
+ class AuthService {
3116
+ // user may have been fetched already in bootstrapping process
3117
+ #userToken;
3118
+ #eventService;
3119
+ #userService;
3120
+ #objectConfigService;
3121
+ #appCache;
3122
+ #systemService;
3123
+ #localizationService;
3124
+ #backend;
3125
+ #INITIAL_REQUEST_STORAGE_KEY;
3126
+ #USER_FETCH_URI;
3127
+ #authenticated;
3128
+ #authSource;
3129
+ #authData;
3130
+ constructor(coreConfig) {
3131
+ this.coreConfig = coreConfig;
3132
+ // user may have been fetched already in bootstrapping process
3133
+ this.#userToken = inject(YUV_USER);
3134
+ this.#eventService = inject(EventService);
3285
3135
  this.#userService = inject(UserService);
3286
- // default number of items to be fetched
3287
- this.DEFAULT_RES_SIZE = 20;
3288
- // audit action codes that should be visible to regular users
3289
- this.userAuditActions = [
3290
- 100, // metadata created
3291
- 101, // metadata created (with content)
3292
- 110, // tag created
3293
- 201, // content deleted
3294
- 210, // tag deleted
3295
- 300, // metadata updated
3296
- 301, // content updated
3297
- 302, // metadata and content updated
3298
- 303, // content moved
3299
- 310, // tag updated
3300
- 325, // object restored form version
3301
- 340, // object moved
3302
- 10000 // custom audit entries
3303
- ];
3304
- // audit action codes that should be visible to admin users
3305
- this.adminAuditActions = [
3306
- 202, // marked for delete
3307
- 220, // version deleted
3308
- 400, // content read
3309
- 401, // metadata read
3310
- 402, // rendition read (text)
3311
- 403, // rendition read (pdf)
3312
- 404 // rendition read (thumbnail)
3313
- ];
3136
+ this.#objectConfigService = inject(ObjectConfigService);
3137
+ this.#appCache = inject(AppCacheService);
3138
+ this.#systemService = inject(SystemService);
3139
+ this.#localizationService = inject(LocalizationService);
3140
+ this.#backend = inject(BackendService);
3141
+ this.#INITIAL_REQUEST_STORAGE_KEY = 'yuv.core.auth.initialrequest';
3142
+ this.#USER_FETCH_URI = '/idm/whoami';
3143
+ this.#authenticated = false;
3144
+ this.#authSource = new BehaviorSubject(false);
3145
+ this.authenticated$ = this.#authSource.asObservable();
3146
+ }
3147
+ isLoggedIn() {
3148
+ return this.#authenticated;
3314
3149
  }
3315
- #searchService;
3316
- #userService;
3317
3150
  /**
3318
- * Get audit entries of a dms object
3319
- * @param id The id of the object to get the audit entries for
3320
- * @param options Options
3151
+ * Called while app/core is initialized (APP_INITIALIZER)
3152
+ * @ignore
3321
3153
  */
3322
- getAuditEntries(id, options = {}) {
3323
- const auditActions = this.getAuditActions(!!options.allActions, options?.skipActions);
3324
- const q = {
3325
- size: this.DEFAULT_RES_SIZE,
3326
- types: [SystemType.AUDIT],
3327
- filters: [
3328
- {
3329
- f: AuditField.REFERRED_OBJECT_ID,
3330
- o: Operator.EQUAL,
3331
- v1: id
3332
- },
3333
- {
3334
- f: AuditField.ACTION,
3335
- o: Operator.IN,
3336
- v1: auditActions
3337
- }
3338
- ],
3339
- sort: [
3340
- {
3341
- field: AuditField.CREATION_DATE,
3342
- order: 'desc'
3343
- }
3344
- ]
3345
- };
3346
- return this.#fetchAudits(q);
3154
+ initUser() {
3155
+ return this.fetchUser();
3347
3156
  }
3348
3157
  /**
3349
- * Get an array of action codes that are provided by the service. Based on
3350
- * whether or not the user has admin permissions you'll get a different
3351
- * set of actions.
3352
- * @param skipActions codes of actions that should not be fetched
3158
+ * Get the current tenant or the previous one persisted locally
3353
3159
  */
3354
- getAuditActions(allActions, skipActions) {
3355
- const actions = allActions || this.#userService.isAdvancedUser ? [...this.userAuditActions, ...this.adminAuditActions] : this.userAuditActions;
3356
- return actions.filter((a) => !skipActions || !skipActions.includes(a));
3160
+ getTenant() {
3161
+ return this.#authData?.tenant;
3357
3162
  }
3358
3163
  /**
3359
- * Get a certain page for a former audits query.
3360
- * @param auditsResult The result object of a former audits query
3361
- * @param page The page to load
3164
+ * Fetch information about the user currently logged in
3362
3165
  */
3363
- getPage(auditsResult, page) {
3364
- const q = auditsResult.query;
3365
- q.from = (page - 1) * (q.size || this.DEFAULT_RES_SIZE);
3366
- return this.#fetchAudits(q);
3166
+ fetchUser() {
3167
+ return (this.#userToken ? of(this.#userToken) : this.#backend.get(this.#USER_FETCH_URI)).pipe(tap(() => {
3168
+ this.#authenticated = true;
3169
+ this.#authSource.next(this.#authenticated);
3170
+ }), switchMap((userJson) => this.#initApp(userJson)));
3367
3171
  }
3368
- #fetchAudits(q) {
3369
- return this.#searchService.searchRaw(q).pipe(map((res) => ({
3370
- query: q,
3371
- items: res.objects.map((o) => ({
3372
- action: o.properties[AuditField.ACTION].value,
3373
- actionGroup: this.#getActionGroup(o.properties[AuditField.ACTION].value),
3374
- detail: o.properties[AuditField.DETAIL].value,
3375
- subaction: o.properties[AuditField.SUBACTION] ? o.properties[AuditField.SUBACTION].value : null,
3376
- version: o.properties[AuditField.VERSION].value,
3377
- creationDate: o.properties[AuditField.CREATION_DATE].value,
3378
- createdBy: {
3379
- id: o.properties[AuditField.CREATED_BY].value,
3380
- title: o.properties[AuditField.CREATED_BY].title
3381
- }
3382
- })),
3383
- hasMoreItems: res.hasMoreItems,
3384
- page: !q.from ? 1 : q.from / q.size + 1
3385
- })));
3172
+ /**
3173
+ * Logs out the current user.
3174
+ */
3175
+ logout() {
3176
+ this.#authenticated = false;
3177
+ this.#authSource.next(this.#authenticated);
3178
+ this.#eventService.trigger(YuvEventType.LOGOUT);
3386
3179
  }
3387
- #getActionGroup(action) {
3388
- try {
3389
- return parseInt(`${action}`.substr(0, 1));
3390
- }
3391
- catch {
3392
- return -1;
3180
+ /**
3181
+ * Persists the initial request URI. This is nessesary to be able to
3182
+ * redirect the user to the requested page after authentication.
3183
+ * This is done on app initialization (core-init).
3184
+ */
3185
+ setInitialRequestUri() {
3186
+ const ignore = ['/', '/index.html'];
3187
+ const baseHref = Utils.getBaseHref();
3188
+ // Check only the pathname against the ignore list so that auth callback
3189
+ // query params (e.g. ?session_state=…) don't bypass the check.
3190
+ let path = location.pathname.replace(baseHref, '');
3191
+ path = !path.startsWith('/') ? `/${path}` : path;
3192
+ if (!ignore.includes(path)) {
3193
+ let uri = `${location.pathname}${location.search}`.replace(baseHref, '');
3194
+ uri = !uri.startsWith('/') ? `/${uri}` : uri;
3195
+ return this.#appCache.setItem(this.#INITIAL_REQUEST_STORAGE_KEY, {
3196
+ uri: uri,
3197
+ timestamp: Date.now()
3198
+ });
3393
3199
  }
3200
+ return of(false);
3201
+ }
3202
+ /**
3203
+ * Get the URL that entered the app. May be a deep link that could then be
3204
+ * picked up again after user has been authenticated.
3205
+ */
3206
+ getInitialRequestUri() {
3207
+ return this.#appCache.getItem(this.#INITIAL_REQUEST_STORAGE_KEY);
3208
+ }
3209
+ resetInitialRequestUri() {
3210
+ return this.#appCache.removeItem(this.#INITIAL_REQUEST_STORAGE_KEY);
3211
+ }
3212
+ /**
3213
+ * Initialize/setup the application for a given user. This involves fetching
3214
+ * settings and schema information.
3215
+ * @param userJson Data retrieved from the backend
3216
+ */
3217
+ #initApp(userJson) {
3218
+ return this.#userService.fetchUserSettings().pipe(switchMap((userSettings) => {
3219
+ const currentUser = new YuvUser(userJson, userSettings);
3220
+ this.#userService.setCurrentUser(currentUser);
3221
+ this.#authData = {
3222
+ tenant: currentUser.tenant,
3223
+ language: currentUser.getClientLocale()
3224
+ };
3225
+ return forkJoin([
3226
+ this.#localizationService.fetchLocalizations(),
3227
+ this.#systemService.getSystemDefinition(this.#authData)
3228
+ ]).pipe(switchMap(() => this.#objectConfigService.init()), map(() => currentUser));
3229
+ }));
3394
3230
  }
3395
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuditService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3396
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuditService, providedIn: 'root' }); }
3231
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthService, deps: [{ token: CORE_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); }
3232
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthService, providedIn: 'root' }); }
3397
3233
  }
3398
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuditService, decorators: [{
3234
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthService, decorators: [{
3399
3235
  type: Injectable,
3400
3236
  args: [{
3401
3237
  providedIn: 'root'
3402
3238
  }]
3403
- }] });
3239
+ }], ctorParameters: () => [{ type: CoreConfig, decorators: [{
3240
+ type: Inject,
3241
+ args: [CORE_CONFIG]
3242
+ }] }] });
3243
+
3244
+ /**
3245
+ * Prevent app from running into 401 issues related to gateway timeouts.
3246
+ */
3247
+ function AuthInterceptorFnc(req, next) {
3248
+ const userService = inject(UserService);
3249
+ const auth = inject(AuthService);
3250
+ return next(req).pipe(tap({
3251
+ next: (event) => {
3252
+ if (event instanceof HttpResponse) {
3253
+ // do stuff with response if you want
3254
+ }
3255
+ },
3256
+ error: (error) => {
3257
+ if (error instanceof HttpErrorResponse || error.isHttpErrorResponse) {
3258
+ if (error.status === 401) {
3259
+ auth.logout();
3260
+ userService.logout();
3261
+ }
3262
+ }
3263
+ }
3264
+ }));
3265
+ }
3266
+
3267
+ var LoginStateName;
3268
+ (function (LoginStateName) {
3269
+ LoginStateName["STATE_LOGIN_URI"] = "login.uri";
3270
+ LoginStateName["STATE_DONE"] = "login.done";
3271
+ LoginStateName["STATE_CANCELED"] = "login.canceled";
3272
+ })(LoginStateName || (LoginStateName = {}));
3404
3273
 
3405
3274
  const ProcessAction = {
3406
3275
  complete: 'complete',
@@ -3453,6 +3322,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
3453
3322
  }]
3454
3323
  }] });
3455
3324
 
3325
+ const LOCALIZATION_COLUMNS = ['locale', 'label', 'description'];
3326
+
3456
3327
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
3457
3328
  const transformResponse = () => map((res) => (res.body ? res.body.objects.map((val) => val) : null));
3458
3329
  /**
@@ -4352,8 +4223,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
4352
4223
  }]
4353
4224
  }] });
4354
4225
 
4355
- const LOCALIZATION_COLUMNS = ['locale', 'label', 'description'];
4356
-
4357
4226
  class CatalogService {
4358
4227
  constructor() {
4359
4228
  this.#backend = inject(BackendService);
@@ -4743,6 +4612,159 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
4743
4612
  }]
4744
4613
  }] });
4745
4614
 
4615
+ /**
4616
+ * Service to monitor the online/offline state of the application.
4617
+ * It listens to the browser's online and offline events and provides an observable
4618
+ * to track the current connection state.
4619
+ *
4620
+ * An observable `connection$` is provided which emits the current connection state
4621
+ * whenever the online or offline state changes. The initial state is determined by
4622
+ * the `window.navigator.onLine` property.
4623
+ */
4624
+ class ConnectionService {
4625
+ /**
4626
+ * @ignore
4627
+ */
4628
+ constructor() {
4629
+ this.currentState = {
4630
+ isOnline: window.navigator.onLine
4631
+ };
4632
+ this.connectionStateSource = new ReplaySubject();
4633
+ this.connection$ = this.connectionStateSource.asObservable();
4634
+ this.connectionStateSource.next(this.currentState);
4635
+ fromEvent(window, 'online').subscribe(() => {
4636
+ this.currentState.isOnline = true;
4637
+ this.connectionStateSource.next(this.currentState);
4638
+ });
4639
+ fromEvent(window, 'offline').subscribe(() => {
4640
+ this.currentState.isOnline = false;
4641
+ this.connectionStateSource.next(this.currentState);
4642
+ });
4643
+ }
4644
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ConnectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4645
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ConnectionService, providedIn: 'root' }); }
4646
+ }
4647
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ConnectionService, decorators: [{
4648
+ type: Injectable,
4649
+ args: [{
4650
+ providedIn: 'root'
4651
+ }]
4652
+ }], ctorParameters: () => [] });
4653
+
4654
+ /**
4655
+ * Http Offline interceptor trys to serving offline content for method/url
4656
+ */
4657
+ function OfflineInterceptorFnc(req, next) {
4658
+ return next(req);
4659
+ }
4660
+
4661
+ /**
4662
+ * Providing functions,that are are injected at application startup and executed during app initialization.
4663
+ */
4664
+ const init_moduleFnc = () => {
4665
+ const coreConfig = inject(CORE_CONFIG);
4666
+ const logger = inject(Logger);
4667
+ const http = inject(HttpClient);
4668
+ const configService = inject(ConfigService);
4669
+ const authService = inject(AuthService);
4670
+ return authService.setInitialRequestUri().pipe(switchMap(() => coreConfig.main
4671
+ ? !Array.isArray(coreConfig.main)
4672
+ ? of([coreConfig.main])
4673
+ : forkJoin([...coreConfig.main].map((uri) => http.get(`${Utils.getBaseHref()}${uri}`).pipe(catchError((e) => {
4674
+ logger.error('failed to catch config file', e);
4675
+ return of({});
4676
+ }), map((res) => res)))).pipe(
4677
+ // switchMap((configs: YuvConfig[]) => configService.extendConfig(configs)),
4678
+ tap((configs) => configService.extendConfig(configs)), switchMap(() => authService.initUser().pipe(catchError((e) => {
4679
+ authService.initError = {
4680
+ status: e.status,
4681
+ key: e.error.error
4682
+ };
4683
+ return of(true);
4684
+ }))))
4685
+ : of(false)));
4686
+ };
4687
+
4688
+ /**
4689
+ * Handles missing translations
4690
+ * @ignore
4691
+ */
4692
+ class EoxMissingTranslationHandler {
4693
+ handle(params) {
4694
+ return '!missing key: ' + params.key;
4695
+ }
4696
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EoxMissingTranslationHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4697
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EoxMissingTranslationHandler }); }
4698
+ }
4699
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EoxMissingTranslationHandler, decorators: [{
4700
+ type: Injectable
4701
+ }] });
4702
+
4703
+ /**
4704
+ * i18n packages
4705
+ */
4706
+ /**
4707
+ * Loader that fetches translations based on the configured locations
4708
+ * @ignore
4709
+ */
4710
+ class EoxTranslateJsonLoader {
4711
+ constructor(http, config) {
4712
+ this.http = http;
4713
+ this.config = config;
4714
+ registerLocaleData(localeDe, 'de', localeExtraDe); // German
4715
+ registerLocaleData(localeAr, 'ar', localeExtraAr); // Arabic
4716
+ registerLocaleData(localeEs, 'es', localeExtraEs); // Spanish
4717
+ registerLocaleData(localePt, 'pt', localeExtraPt); // Portuguese
4718
+ registerLocaleData(localeFr, 'fr', localeExtraFr); // French
4719
+ registerLocaleData(localeZh, 'zh', localeExtraZh); // Chinese
4720
+ registerLocaleData(localeLv, 'lv', localeExtraLv); // Latvian
4721
+ registerLocaleData(localeRu, 'ru', localeExtraRu); // Russian
4722
+ registerLocaleData(localeIt, 'it', localeExtraIt); // Italian
4723
+ registerLocaleData(localeSk, 'sk', localeExtraSk); // Slovak
4724
+ registerLocaleData(localePl, 'pl', localeExtraPl); // Polish
4725
+ registerLocaleData(localeUk, 'uk', localeExtraUk); // Ukrainian
4726
+ registerLocaleData(localeJa, 'ja', localeExtraJa); // Japanese
4727
+ registerLocaleData(localeKo, 'ko', localeExtraKo); // Korean
4728
+ registerLocaleData(localeHi, 'hi', localeExtraHi); // Hindi
4729
+ registerLocaleData(localeBn, 'bn', localeExtraBn); // Bengalese
4730
+ registerLocaleData(localeVi, 'vi', localeExtraVi); // Vietnamese
4731
+ registerLocaleData(localeTr, 'tr', localeExtraTr); // Turkish
4732
+ registerLocaleData(localeNl, 'nl', localeExtraNl); // Dutch
4733
+ registerLocaleData(localeNb, 'nb', localeExtraNb); // Norwegian
4734
+ registerLocaleData(localeTh, 'th', localeExtraTh); // Thai
4735
+ registerLocaleData(localeFi, 'fi', localeExtraFi); // Finnish
4736
+ registerLocaleData(localeSv, 'sv', localeExtraSv); // Swedish
4737
+ registerLocaleData(localeDeCh, 'de-CH', localeExtraDeCh); // German Swiss
4738
+ }
4739
+ /**
4740
+ *
4741
+ * @param string lang
4742
+ * @returns Observable<Object>
4743
+ */
4744
+ getTranslation(lang) {
4745
+ const t = (this.config.translations || []).map((path) => this.loadTranslationFile(path, lang));
4746
+ return forkJoin(t).pipe(map((res) => res.reduce((acc, x) => Object.assign(acc, x), {})));
4747
+ }
4748
+ loadTranslationFile(path, lang) {
4749
+ const version = document.body.dataset['bt'] ?? document.body.dataset['version'];
4750
+ const cacheBuster = version ? `?v=${version}` : '';
4751
+ return this.http.get(`${Utils.getBaseHref()}${path}${lang}.json${cacheBuster}`).pipe(catchError(() => {
4752
+ // ISO codes with more than 2 characters are sub-languages like de-CH.
4753
+ // If there is no translation file for that sub-language we'll try to load
4754
+ // the file for the base language (in this case de).
4755
+ return lang.length > 2 ? this.loadTranslationFile(path, lang.substring(0, 2)) : of({});
4756
+ }));
4757
+ }
4758
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EoxTranslateJsonLoader, deps: [{ token: i1.HttpClient }, { token: CORE_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); }
4759
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EoxTranslateJsonLoader }); }
4760
+ }
4761
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EoxTranslateJsonLoader, decorators: [{
4762
+ type: Injectable
4763
+ }], ctorParameters: () => [{ type: i1.HttpClient }, { type: CoreConfig, decorators: [{
4764
+ type: Inject,
4765
+ args: [CORE_CONFIG]
4766
+ }] }] });
4767
+
4746
4768
  var DeviceScreenOrientation;
4747
4769
  (function (DeviceScreenOrientation) {
4748
4770
  DeviceScreenOrientation["PORTRAIT"] = "portrait";
@@ -5508,6 +5530,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
5508
5530
  }]
5509
5531
  }] });
5510
5532
 
5533
+ /**
5534
+ * Interface to be implemented by all components (state components) that are aware of pending changes
5535
+ */
5536
+
5511
5537
  /**
5512
5538
  * EditingObserver service is used to track changes made inside the application, that should prevent
5513
5539
  * doing something or going somewhere before the changes are persisted or ignored. For example when
@@ -5627,27 +5653,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
5627
5653
  }]
5628
5654
  }] });
5629
5655
 
5630
- /**
5631
- * Providing a `PendingChangesComponent`.
5632
- */
5633
- class PendingChangesGuard {
5634
- constructor(pendingChanges) {
5635
- this.pendingChanges = pendingChanges;
5636
- }
5637
- canDeactivate(component) {
5638
- // if there are no pending changes, just allow deactivation; else confirm first
5639
- return !this.pendingChanges.check(component);
5640
- }
5641
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PendingChangesGuard, deps: [{ token: PendingChangesService }], target: i0.ɵɵFactoryTarget.Injectable }); }
5642
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PendingChangesGuard, providedIn: 'root' }); }
5643
- }
5644
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PendingChangesGuard, decorators: [{
5645
- type: Injectable,
5646
- args: [{
5647
- providedIn: 'root'
5648
- }]
5649
- }], ctorParameters: () => [{ type: PendingChangesService }] });
5650
-
5651
5656
  /**
5652
5657
  * Service that provides guards for Material Dialog close operations.
5653
5658
  *
@@ -5803,6 +5808,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
5803
5808
  type: Injectable
5804
5809
  }] });
5805
5810
 
5811
+ /**
5812
+ * Providing a `PendingChangesComponent`.
5813
+ */
5814
+ class PendingChangesGuard {
5815
+ constructor(pendingChanges) {
5816
+ this.pendingChanges = pendingChanges;
5817
+ }
5818
+ canDeactivate(component) {
5819
+ // if there are no pending changes, just allow deactivation; else confirm first
5820
+ return !this.pendingChanges.check(component);
5821
+ }
5822
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PendingChangesGuard, deps: [{ token: PendingChangesService }], target: i0.ɵɵFactoryTarget.Injectable }); }
5823
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PendingChangesGuard, providedIn: 'root' }); }
5824
+ }
5825
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PendingChangesGuard, decorators: [{
5826
+ type: Injectable,
5827
+ args: [{
5828
+ providedIn: 'root'
5829
+ }]
5830
+ }], ctorParameters: () => [{ type: PendingChangesService }] });
5831
+
5806
5832
  class TabGuardDirective {
5807
5833
  #tabGroup = inject(MatTabGroup);
5808
5834
  #pending = inject(PendingChangesService);
@@ -6556,5 +6582,5 @@ const provideYuvClientCore = (options = { translations: [] }, customEvents, cust
6556
6582
  * Generated bundle index. Do not edit.
6557
6583
  */
6558
6584
 
6559
- export { AFO_STATE, AVAILABLE_BACKEND_APPS, AdministrationRoles, ApiBase, AppCacheService, AuditField, AuditService, AuthInterceptorFnc, AuthService, BackendService, BaseObjectTypeField, BpmService, CLIENT_APP_REQUIREMENTS, CORE_CONFIG, CUSTOM_CONFIG, CUSTOM_YUV_EVENT_PREFIX, CatalogService, CatalogTypeField, Classification, ClassificationPrefix, ClientCacheService, ClientDefaultsObjectTypeField, ClipboardService, ColumnConfigSkipFields, ConfigService, ConnectionService, ContentStreamAllowed, ContentStreamField, CoreConfig, DEFAULT_LOCK_OPTIONS, DeviceScreenOrientation, DeviceService, DialogCloseGuard, Direction, DmsObject, DmsService, EoxMissingTranslationHandler, EoxTranslateJsonLoader, EventService, FileSizePipe, IdmService, InternalFieldType, KeysPipe, LOCALIZATION_COLUMNS, LOCK_TAG_OWNER_INDEX, LocaleCurrencyPipe, LocaleDatePipe, LocaleDecimalPipe, LocaleNumberPipe, LocalePercentPipe, LocalizationService, LockField, Logger, LoginStateName, NativeNotificationService, NotificationService, ObjectConfigService, ObjectFormControl, ObjectFormControlWrapper, ObjectFormGroup, ObjectLockingService, ObjectTag, ObjectTypeClassification, ObjectTypePropertyClassification, OfflineInterceptorFnc, Operator, OperatorLabel, ParentField, PendingChangesGuard, PendingChangesService, PredictionService, ProcessAction, RelationshipTypeField, RetentionField, RetentionService, SafeHtmlPipe, SafeUrlPipe, SearchService, SearchUtils, SecondaryObjectTypeClassification, SessionStorageService, Situation, Sort, SystemResult, SystemSOT, SystemService, SystemType, TENANT_HEADER, TabGuardDirective, ToastService, UploadService, UserRoles, UserService, UserStorageService, Utils, YUV_USER, YuvError, YuvEventType, YuvUser, findLockTag, init_moduleFnc, provideAvailabilityManagement, provideBeforeUnloadProtection, provideNavigationProtection, providePopstateDialogProtection, provideRequirements, provideUser, provideYuvClientCore };
6585
+ export { AFO_STATE, AVAILABLE_BACKEND_APPS, AdministrationRoles, ApiBase, AppCacheService, AuditAction, AuditField, AuditService, AuthInterceptorFnc, AuthService, BackendService, BaseObjectTypeField, BpmService, CLIENT_APP_REQUIREMENTS, CORE_CONFIG, CUSTOM_CONFIG, CUSTOM_EVENTS, CUSTOM_EVENTS_TRUSTED_ORIGINS, CUSTOM_YUV_EVENT_PREFIX, CatalogService, CatalogTypeField, Classification, ClassificationPrefix, ClientCacheService, ClientDefaultsObjectTypeField, ClipboardService, ColumnConfigSkipFields, ConfigService, ConnectionService, ContentStreamAllowed, ContentStreamField, CoreConfig, DEFAULT_LOCK_OPTIONS, DeviceScreenOrientation, DeviceService, DialogCloseGuard, Direction, DmsObject, DmsService, EoxMissingTranslationHandler, EoxTranslateJsonLoader, EventService, FileSizePipe, IdmService, InternalFieldType, KeysPipe, LOCALIZATION_COLUMNS, LOCK_TAG_OWNER_INDEX, LocaleCurrencyPipe, LocaleDatePipe, LocaleDecimalPipe, LocaleNumberPipe, LocalePercentPipe, LocalizationService, LockField, Logger, LoginStateName, NativeNotificationService, NotificationService, ObjectConfigService, ObjectFormControl, ObjectFormControlWrapper, ObjectFormGroup, ObjectLockingService, ObjectTag, ObjectTypeClassification, ObjectTypePropertyClassification, OfflineInterceptorFnc, Operator, OperatorLabel, ParentField, PendingChangesGuard, PendingChangesService, PredictionService, ProcessAction, RelationshipTypeField, RetentionField, RetentionService, SafeHtmlPipe, SafeUrlPipe, SearchService, SearchUtils, SecondaryObjectTypeClassification, SessionStorageService, Situation, Sort, SystemResult, SystemSOT, SystemService, SystemType, TENANT_HEADER, TabGuardDirective, ToastService, UploadService, UserRoles, UserService, UserStorageService, Utils, YUV_USER, YuvError, YuvEventType, YuvToastStyles, YuvUser, findLockTag, init_moduleFnc, provideAvailabilityManagement, provideBeforeUnloadProtection, provideNavigationProtection, providePopstateDialogProtection, provideRequirements, provideUser, provideYuvClientCore };
6560
6586
  //# sourceMappingURL=yuuvis-client-core.mjs.map