@yuuvis/client-framework 3.4.2 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,163 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, input, Component, computed, effect, ChangeDetectionStrategy, signal, Injectable, ViewContainerRef, Directive } from '@angular/core';
3
- import { SystemService, LocaleDatePipe, FileSizePipe, IdmService, AppCacheService, TranslateService, TranslatePipe, InternalFieldType, ContentStreamField } from '@yuuvis/client-core';
2
+ import { input, Component, inject, computed, ChangeDetectionStrategy, signal, Injectable, effect, ViewContainerRef, Directive } from '@angular/core';
3
+ import { TranslateService, LocalizationService, SystemService, LocaleDatePipe, FileSizePipe, IdmService, AppCacheService, TranslatePipe, InternalFieldType, ContentStreamField } from '@yuuvis/client-core';
4
4
  import { MatIcon, MatIconRegistry } from '@angular/material/icon';
5
5
  import { DomSanitizer } from '@angular/platform-browser';
6
6
  import { ObjectTypeIconComponent } from '@yuuvis/client-framework/icons';
7
7
  import { rxResource } from '@angular/core/rxjs-interop';
8
- import { switchMap, of, map, forkJoin } from 'rxjs';
8
+ import { of, switchMap, map, catchError, forkJoin } from 'rxjs';
9
9
  import { DecimalPipe, DatePipe } from '@angular/common';
10
10
 
11
+ /**
12
+ * Abstract class to be extended by audit-entry renderers. The renderer controls the
13
+ * inner content of an audit timeline entry; the surrounding framing (date column,
14
+ * timeline line, version badge, creator) stays with the host component.
15
+ */
16
+ class AbstractAuditRendererComponent {
17
+ constructor() {
18
+ this.auditEntry = input.required(...(ngDevMode ? [{ debugName: "auditEntry" }] : /* istanbul ignore next */ []));
19
+ }
20
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AbstractAuditRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
21
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: AbstractAuditRendererComponent, isStandalone: true, selector: "yuv-abstract-audit-renderer", inputs: { auditEntry: { classPropertyName: "auditEntry", publicName: "auditEntry", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: '', isInline: true }); }
22
+ }
23
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AbstractAuditRendererComponent, decorators: [{
24
+ type: Component,
25
+ args: [{
26
+ selector: 'yuv-abstract-audit-renderer',
27
+ template: ''
28
+ }]
29
+ }], propDecorators: { auditEntry: [{ type: i0.Input, args: [{ isSignal: true, alias: "auditEntry", required: true }] }] } });
30
+
31
+ class DefaultAuditRendererComponent extends AbstractAuditRendererComponent {
32
+ constructor() {
33
+ super(...arguments);
34
+ this.#translate = inject(TranslateService);
35
+ this.#localization = inject(LocalizationService);
36
+ this.#auditLabels = {
37
+ a100: this.#translate.instant('yuv.audit.label.create.metadata'),
38
+ a101: this.#translate.instant('yuv.audit.label.create.metadata.withcontent'),
39
+ a110: this.#translate.instant('yuv.audit.label.create.tag'),
40
+ a200: this.#translate.instant('yuv.audit.label.delete'),
41
+ a201: this.#translate.instant('yuv.audit.label.delete.content'),
42
+ a202: this.#translate.instant('yuv.audit.label.delete.marked'),
43
+ a210: this.#translate.instant('yuv.audit.label.delete.tag'),
44
+ a220: this.#translate.instant('yuv.audit.label.delete.version'),
45
+ a300: this.#translate.instant('yuv.audit.label.update.metadata'),
46
+ a301: this.#translate.instant('yuv.audit.label.update.content'),
47
+ a302: this.#translate.instant('yuv.audit.label.update.metadata.withcontent'),
48
+ a303: this.#translate.instant('yuv.audit.label.update.move.content'),
49
+ a310: this.#translate.instant('yuv.audit.label.update.tag'),
50
+ a325: this.#translate.instant('yuv.audit.label.update.restore'),
51
+ a340: this.#translate.instant('yuv.audit.label.update.move'),
52
+ a400: this.#translate.instant('yuv.audit.label.get.content'),
53
+ a401: this.#translate.instant('yuv.audit.label.get.metadata'),
54
+ a402: this.#translate.instant('yuv.audit.label.get.rendition.text'),
55
+ a403: this.#translate.instant('yuv.audit.label.get.rendition.pdf'),
56
+ a404: this.#translate.instant('yuv.audit.label.get.rendition.thumbnail'),
57
+ a10000: this.#translate.instant('yuv.audit.label.get.custom')
58
+ };
59
+ this.resolved = computed(() => this.#resolve(this.auditEntry()), ...(ngDevMode ? [{ debugName: "resolved" }] : /* istanbul ignore next */ []));
60
+ }
61
+ #translate;
62
+ #localization;
63
+ #auditLabels;
64
+ #resolve(entry) {
65
+ let label = this.#auditLabels[`a${entry.action}`];
66
+ let more;
67
+ if ([110, 210, 310].includes(entry.action)) {
68
+ const params = this.#getParams(entry);
69
+ if (params.length) {
70
+ const localizedLabel = `${params[0]}:${params[1]}`;
71
+ more = `${this.#localization.getLocalizedLabel(params[0]) ?? params[0]}: ${this.#localization.getLocalizedLabel(localizedLabel) ?? localizedLabel}`;
72
+ }
73
+ }
74
+ else if (entry.action === 325) {
75
+ const params = this.#getParams(entry);
76
+ more = this.#translate.instant('yuv.audit.label.update.restore.more', {
77
+ version: params[0] || '[?]'
78
+ });
79
+ }
80
+ else if (entry.action === 10000) {
81
+ label = this.#localization.getLocalizedLabel(`audit:custom:${entry.subaction}`) || `${entry.subaction}`;
82
+ }
83
+ return { label, more };
84
+ }
85
+ #getParams(entry) {
86
+ const m = entry.detail.match(/\[(.*?)\]/);
87
+ return m?.[1]?.split(',').map((i) => i.trim()) ?? [];
88
+ }
89
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DefaultAuditRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
90
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: DefaultAuditRendererComponent, isStandalone: true, selector: "yuv-default-audit-renderer", usesInheritance: true, ngImport: i0, template: `
91
+ @let r = resolved();
92
+ <span class="title">{{ r.label }}</span>
93
+ @if (r.more) {
94
+ <div class="more meta">{{ r.more }}</div>
95
+ }
96
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
97
+ }
98
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DefaultAuditRendererComponent, decorators: [{
99
+ type: Component,
100
+ args: [{
101
+ selector: 'yuv-default-audit-renderer',
102
+ standalone: true,
103
+ template: `
104
+ @let r = resolved();
105
+ <span class="title">{{ r.label }}</span>
106
+ @if (r.more) {
107
+ <div class="more meta">{{ r.more }}</div>
108
+ }
109
+ `,
110
+ changeDetection: ChangeDetectionStrategy.OnPush
111
+ }]
112
+ }] });
113
+
114
+ /**
115
+ * Service for managing audit-entry renderers. Renderers are components that render the
116
+ * inner content of an audit entry in the timeline view of `ObjectAuditComponent`.
117
+ *
118
+ * Register a renderer for an `action` to override the default rendering of all audit
119
+ * entries with that action, or for an `action` + `subaction` pair to override a specific
120
+ * variant. Lookup prefers the subaction-specific renderer over the action-only renderer;
121
+ * if neither is registered, `DefaultAuditRendererComponent` is used.
122
+ */
123
+ class AuditRendererService {
124
+ #renderers = signal({}, ...(ngDevMode ? [{ debugName: "#renderers" }] : /* istanbul ignore next */ []));
125
+ /**
126
+ * Register a renderer for a specific audit `action`. Pass `subaction` to scope the
127
+ * renderer to a particular (action, subaction) pair.
128
+ */
129
+ registerAuditRenderer(cmp, action, subaction) {
130
+ this.#renderers.update((curr) => ({ ...curr, [this.#getKey(action, subaction)]: cmp }));
131
+ }
132
+ /**
133
+ * Resolve the renderer for an audit entry. Tries `(action, subaction)` first, falls
134
+ * back to `(action)`, then to `DefaultAuditRendererComponent`.
135
+ */
136
+ getAuditRenderer(action, subaction) {
137
+ const r = this.#renderers();
138
+ if (subaction !== undefined) {
139
+ const scoped = r[this.#getKey(action, subaction)];
140
+ if (scoped)
141
+ return scoped;
142
+ }
143
+ return r[this.#getKey(action)] || DefaultAuditRendererComponent;
144
+ }
145
+ #getKey(action, subaction) {
146
+ const k = [String(action)];
147
+ if (subaction !== undefined)
148
+ k.push(String(subaction));
149
+ return k.join('-');
150
+ }
151
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuditRendererService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
152
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuditRendererService, providedIn: 'root' }); }
153
+ }
154
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuditRendererService, decorators: [{
155
+ type: Injectable,
156
+ args: [{
157
+ providedIn: 'root'
158
+ }]
159
+ }] });
160
+
11
161
  /**
12
162
  * Abstract class to be extended by property renderers
13
163
  */
@@ -92,8 +242,9 @@ class IconRendererComponent extends AbstractRendererComponent {
92
242
  this.customId = crypto.randomUUID();
93
243
  this.#registerIconsEffect = effect(async () => {
94
244
  const meta = this.meta();
245
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
95
246
  this.propertyName() === 'custom' &&
96
- !(meta && meta['isFontIcon']) &&
247
+ !meta?.['isFontIcon'] &&
97
248
  this.#iconRegistry.addSvgIconLiteral(this.customId, this.#sanitizer.bypassSecurityTrustHtml(this.value()));
98
249
  }, ...(ngDevMode ? [{ debugName: "#registerIconsEffect" }] : /* istanbul ignore next */ []));
99
250
  }
@@ -101,31 +252,11 @@ class IconRendererComponent extends AbstractRendererComponent {
101
252
  #sanitizer;
102
253
  #registerIconsEffect;
103
254
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: IconRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
104
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: IconRendererComponent, isStandalone: true, selector: "yuv-icon-renderer", usesInheritance: true, ngImport: i0, template: ` @let icon = value();
105
- @if (propertyName() !== 'custom') {
106
- <yuv-object-type-icon [objectTypeId]="icon || ''"></yuv-object-type-icon>
107
- } @else if (icon !== null) {
108
- @let metaData = meta();
109
- @if (metaData && metaData['isFontIcon']) {
110
- <mat-icon>{{ icon }}</mat-icon>
111
- } @else {
112
- <mat-icon [svgIcon]="customId"></mat-icon>
113
- }
114
- }`, isInline: true, styles: [":host{display:flex;align-items:center;justify-content:center;padding:var(--tile-slot-padding)}:host yuv-icon,:host yuv-object-type-icon{--icon-size: var(--icon-renderer-icon-size);width:var(--icon-size);height:var(--icon-size)}\n"], dependencies: [{ kind: "component", type: ObjectTypeIconComponent, selector: "yuv-object-type-icon", inputs: ["objectTypeId"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] }); }
255
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: IconRendererComponent, isStandalone: true, selector: "yuv-icon-renderer", usesInheritance: true, ngImport: i0, template: "@let icon = value();\n@if (propertyName() !== 'custom') {\n <yuv-object-type-icon [objectTypeId]=\"icon || ''\"></yuv-object-type-icon>\n} @else if (icon !== null) {\n @let metaData = meta();\n @if (metaData && metaData['isFontIcon']) {\n <mat-icon>{{ icon }}</mat-icon>\n } @else {\n <mat-icon [svgIcon]=\"customId\"></mat-icon>\n }\n}\n", styles: [":host{display:flex;align-items:center;justify-content:center;padding:var(--tile-slot-padding)}:host yuv-icon,:host yuv-object-type-icon{--icon-size: var(--icon-renderer-icon-size);width:var(--icon-size);height:var(--icon-size)}\n"], dependencies: [{ kind: "component", type: ObjectTypeIconComponent, selector: "yuv-object-type-icon", inputs: ["objectTypeId"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] }); }
115
256
  }
116
257
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: IconRendererComponent, decorators: [{
117
258
  type: Component,
118
- args: [{ selector: 'yuv-icon-renderer', imports: [ObjectTypeIconComponent, MatIcon], template: ` @let icon = value();
119
- @if (propertyName() !== 'custom') {
120
- <yuv-object-type-icon [objectTypeId]="icon || ''"></yuv-object-type-icon>
121
- } @else if (icon !== null) {
122
- @let metaData = meta();
123
- @if (metaData && metaData['isFontIcon']) {
124
- <mat-icon>{{ icon }}</mat-icon>
125
- } @else {
126
- <mat-icon [svgIcon]="customId"></mat-icon>
127
- }
128
- }`, styles: [":host{display:flex;align-items:center;justify-content:center;padding:var(--tile-slot-padding)}:host yuv-icon,:host yuv-object-type-icon{--icon-size: var(--icon-renderer-icon-size);width:var(--icon-size);height:var(--icon-size)}\n"] }]
259
+ args: [{ selector: 'yuv-icon-renderer', imports: [ObjectTypeIconComponent, MatIcon], template: "@let icon = value();\n@if (propertyName() !== 'custom') {\n <yuv-object-type-icon [objectTypeId]=\"icon || ''\"></yuv-object-type-icon>\n} @else if (icon !== null) {\n @let metaData = meta();\n @if (metaData && metaData['isFontIcon']) {\n <mat-icon>{{ icon }}</mat-icon>\n } @else {\n <mat-icon [svgIcon]=\"customId\"></mat-icon>\n }\n}\n", styles: [":host{display:flex;align-items:center;justify-content:center;padding:var(--tile-slot-padding)}:host yuv-icon,:host yuv-object-type-icon{--icon-size: var(--icon-renderer-icon-size);width:var(--icon-size);height:var(--icon-size)}\n"] }]
129
260
  }] });
130
261
 
131
262
  class IntegerRendererComponent extends AbstractRendererComponent {
@@ -144,55 +275,91 @@ class OrganizationRendererComponent extends AbstractRendererComponent {
144
275
  this.#appCache = inject(AppCacheService);
145
276
  this.#STORAGE_USER_KEY = 'yuv.core.users.storage';
146
277
  this.#STORAGE_ROLES_KEY = 'yuv.core.roles.storage';
278
+ // Input may be a single id/role-name or an array of them; normalize to string[] so the resolver
279
+ // pipeline always works on a uniform shape.
147
280
  this.resolvedValue = computed(() => {
148
- const m = this.value();
149
- return m === null ? [] : Array.isArray(m) ? m : [m];
281
+ const value = this.value();
282
+ return value === null ? [] : Array.isArray(value) ? value : [value];
150
283
  }, ...(ngDevMode ? [{ debugName: "resolvedValue" }] : /* istanbul ignore next */ []));
151
284
  this.#userRoleResolver = rxResource({
152
285
  params: this.resolvedValue,
153
- stream: ({ params }) =>
154
- // TODO: Move get Roles to App init or somewhere else to avoid multiple calls
155
- this.#appCache.getItem(this.#STORAGE_ROLES_KEY).pipe(switchMap((cachedRoles) => (cachedRoles
156
- ? of(cachedRoles)
157
- : this.#idmService
158
- .getRoles()
159
- .pipe(switchMap((roles) => roles.length
160
- ? this.#appCache.setItem(this.#STORAGE_ROLES_KEY, roles).pipe(map(() => roles))
161
- : of([])))).pipe(map((roles) => params.reduce((acc, value) => {
162
- const matchingRole = roles.find((r) => r.name === value);
163
- matchingRole ? acc.roles.push(matchingRole.name) : acc.users.push(value);
164
- return acc;
165
- }, { roles: [], users: [] })))), switchMap(({ roles, users }) => this.#appCache
166
- .getItem(this.#STORAGE_USER_KEY)
167
- .pipe(map((cache) => cache ? { roles, users, cache: cache.filter((c) => c !== null) } : { roles, users, cache: null }))), switchMap(({ roles, users, cache }) => {
168
- let missingUsers = users;
169
- let filteredUsers = [];
170
- if (cache) {
171
- filteredUsers = cache.filter((u) => u && users.includes(u.id));
172
- missingUsers = users.filter((uid) => !filteredUsers.some((u) => u.id === uid));
286
+ stream: ({ params }) => {
287
+ // Nothing to resolve short-circuit so we don't show the loading indicator or hit the
288
+ // backend for an empty input.
289
+ if (params.length === 0) {
290
+ return of([]);
173
291
  }
174
- const userRequests = missingUsers
175
- .map((uid) => this.#idmService.getUserById(uid).pipe(map((user) => (user ? user : null))))
176
- .filter((obs) => obs !== null);
177
- if (userRequests.length > 0) {
178
- return forkJoin(userRequests).pipe(switchMap((users) => this.#appCache
179
- .setItem(this.#STORAGE_USER_KEY, [...(cache || []), ...users, ...filteredUsers])
180
- .pipe(map(() => users))), map((users) => ({ roles, users: [...users, ...filteredUsers] })));
181
- }
182
- else {
183
- return of({ roles, users: [...filteredUsers] });
184
- }
185
- }), map(({ roles, users }) => {
186
- const userNodes = users.map((u) => ({
187
- type: 'user',
188
- label: u.title || u.displayName
189
- }));
190
- const roleNodes = roles.map((r) => ({
191
- type: 'role',
192
- label: r
292
+ // Step 1 — load the role catalog (cached) and use it to partition the input into role
293
+ // names vs. user IDs. Roles are a closed set; anything that doesn't match is treated as
294
+ // a user ID and resolved in step 2.
295
+ // TODO: Move get Roles to App init or somewhere else to avoid multiple calls
296
+ return this.#appCache.getItem(this.#STORAGE_ROLES_KEY).pipe(switchMap((cachedRoles) => (cachedRoles
297
+ ? of(cachedRoles)
298
+ : this.#idmService.getRoles().pipe(
299
+ // Only persist the role list when the backend actually returned something —
300
+ // caching an empty array would mask later successful fetches.
301
+ switchMap((roles) => roles.length ? this.#appCache.setItem(this.#STORAGE_ROLES_KEY, roles).pipe(map(() => roles)) : of([])))).pipe(map((roles) => params.reduce((acc, value) => {
302
+ const matchingRole = roles.find((role) => role.name === value);
303
+ // Role-name match → roles bucket; everything else is assumed to be a user ID.
304
+ if (matchingRole) {
305
+ acc.roles.push(matchingRole.name);
306
+ }
307
+ else {
308
+ acc.users.push(value);
309
+ }
310
+ return acc;
311
+ }, { roles: [], users: [] })))),
312
+ // Step 2a — pull the persisted user cache. Defensive filter against null entries that
313
+ // may linger from earlier buggy writes (see commit 2f3ff694f).
314
+ switchMap(({ roles, users }) => this.#appCache
315
+ .getItem(this.#STORAGE_USER_KEY)
316
+ .pipe(map((cache) => cache ? { roles, users, cache: cache.filter((entry) => entry !== null) } : { roles, users, cache: null }))),
317
+ // Step 2b — resolve users: serve from cache where possible, fetch the rest from IDM.
318
+ switchMap(({ roles, users, cache }) => {
319
+ let missingUsers = users;
320
+ let filteredUsers = [];
321
+ if (cache) {
322
+ filteredUsers = cache.filter((entry) => entry && users.includes(entry.id));
323
+ missingUsers = users.filter((uid) => !filteredUsers.some((user) => user.id === uid));
324
+ }
325
+ // Pair every request with its uid so we can tell which IDs came back unresolved.
326
+ // catchError keeps a single failing lookup from killing the whole forkJoin.
327
+ const userRequests = missingUsers.map((uid) => this.#idmService.getUserById(uid).pipe(catchError(() => of(null)), map((user) => ({ uid, user }))));
328
+ if (userRequests.length > 0) {
329
+ return forkJoin(userRequests).pipe(switchMap((results) => {
330
+ // Resolved users are real YuvUsers — safe to cache and render.
331
+ // Unresolved IDs render as a synthetic { id, title: id } so the user still
332
+ // sees the raw ID, but they are NOT written to the cache (a 404 today might
333
+ // resolve tomorrow, and caching it would block that).
334
+ const resolved = results.filter((res) => res.user != null).map((res) => res.user);
335
+ const unresolved = results
336
+ .filter((res) => res.user == null)
337
+ .map((res) => ({ id: res.uid, title: res.uid }));
338
+ return this.#appCache
339
+ .setItem(this.#STORAGE_USER_KEY, [...(cache || []), ...resolved, ...filteredUsers])
340
+ .pipe(map(() => [...resolved, ...unresolved]));
341
+ }), map((users) => ({ roles, users: [...users, ...filteredUsers] })));
342
+ }
343
+ else {
344
+ // Every requested user was already in the cache — no network needed.
345
+ return of({ roles, users: [...filteredUsers] });
346
+ }
347
+ }),
348
+ // Step 3 — project to the flat render-model the template iterates over. data-type
349
+ // drives the chip styling (user vs role).
350
+ map(({ roles, users }) => {
351
+ const userNodes = users.map((user) => ({
352
+ type: 'user',
353
+ label: user.title || user.displayName
354
+ }));
355
+ const roleNodes = roles.map((role) => ({
356
+ type: 'role',
357
+ label: role
358
+ }));
359
+ return [...userNodes, ...roleNodes];
193
360
  }));
194
- return [...userNodes, ...roleNodes];
195
- }))
361
+ },
362
+ defaultValue: []
196
363
  });
197
364
  this.userAndRole = this.#userRoleResolver.value;
198
365
  this.userAndRoleLoading = this.#userRoleResolver.isLoading;
@@ -203,27 +370,11 @@ class OrganizationRendererComponent extends AbstractRendererComponent {
203
370
  #STORAGE_ROLES_KEY;
204
371
  #userRoleResolver;
205
372
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: OrganizationRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
206
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: OrganizationRendererComponent, isStandalone: true, selector: "yuv-organization-renderer", usesInheritance: true, ngImport: i0, template: `
207
- @if (userAndRoleLoading()) {
208
- <span class="loading"><i>.</i><i>.</i><i>.</i></span>
209
- } @else {
210
- @for (node of userAndRole(); track $index) {
211
- <span class="node" [attr.data-type]="node.type">{{ node.label }}</span>
212
- }
213
- }
214
- `, isInline: true, styles: [":host{padding:var(--tile-slot-padding);display:var(--yuv-renderer-display, flex);gap:var(--ymt-spacing-xs);align-items:center;@keyframes pulse{0%,80%,to{opacity:.3;transform:scale(1)}40%{opacity:1;transform:scale(1.2)}}}:host .node{display:flex;flex-flow:row nowrap;align-items:center;border:1px solid var(--ymt-outline);border-radius:var(--ymt-corner-xs);padding:0 var(--ymt-spacing-xs);font:var(--ymt-font-body-subtle);white-space:nowrap}:host .loading i{animation:pulse 1.4s ease-in-out infinite both}:host .loading i:nth-child(1){animation-delay:-.32s}:host .loading i:nth-child(2){animation-delay:-.16s}:host .loading i:nth-child(3){animation-delay:0s}\n"] }); }
373
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: OrganizationRendererComponent, isStandalone: true, selector: "yuv-organization-renderer", usesInheritance: true, ngImport: i0, template: "@if (userAndRoleLoading()) {\n <span class=\"loading\"><i>.</i><i>.</i><i>.</i></span>\n} @else {\n @for (node of userAndRole(); track $index) {\n <span class=\"node\" [attr.data-type]=\"node.type\">{{ node.label }}</span>\n }\n}\n", styles: [":host{padding:var(--tile-slot-padding);display:var(--yuv-renderer-display, flex);gap:var(--ymt-spacing-xs);align-items:center}:host .node{display:flex;flex-flow:row nowrap;align-items:center;border:1px solid var(--ymt-outline);border-radius:var(--ymt-corner-xs);padding:0 var(--ymt-spacing-xs);font:var(--ymt-font-body-subtle);white-space:nowrap}:host .loading i{animation:pulse 1.4s ease-in-out infinite both}:host .loading i:nth-child(1){animation-delay:-.32s}:host .loading i:nth-child(2){animation-delay:-.16s}:host .loading i:nth-child(3){animation-delay:0s}@keyframes pulse{0%,80%,to{opacity:.3;transform:scale(1)}40%{opacity:1;transform:scale(1.2)}}\n"] }); }
215
374
  }
216
375
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: OrganizationRendererComponent, decorators: [{
217
376
  type: Component,
218
- args: [{ selector: 'yuv-organization-renderer', template: `
219
- @if (userAndRoleLoading()) {
220
- <span class="loading"><i>.</i><i>.</i><i>.</i></span>
221
- } @else {
222
- @for (node of userAndRole(); track $index) {
223
- <span class="node" [attr.data-type]="node.type">{{ node.label }}</span>
224
- }
225
- }
226
- `, styles: [":host{padding:var(--tile-slot-padding);display:var(--yuv-renderer-display, flex);gap:var(--ymt-spacing-xs);align-items:center;@keyframes pulse{0%,80%,to{opacity:.3;transform:scale(1)}40%{opacity:1;transform:scale(1.2)}}}:host .node{display:flex;flex-flow:row nowrap;align-items:center;border:1px solid var(--ymt-outline);border-radius:var(--ymt-corner-xs);padding:0 var(--ymt-spacing-xs);font:var(--ymt-font-body-subtle);white-space:nowrap}:host .loading i{animation:pulse 1.4s ease-in-out infinite both}:host .loading i:nth-child(1){animation-delay:-.32s}:host .loading i:nth-child(2){animation-delay:-.16s}:host .loading i:nth-child(3){animation-delay:0s}\n"] }]
377
+ args: [{ selector: 'yuv-organization-renderer', template: "@if (userAndRoleLoading()) {\n <span class=\"loading\"><i>.</i><i>.</i><i>.</i></span>\n} @else {\n @for (node of userAndRole(); track $index) {\n <span class=\"node\" [attr.data-type]=\"node.type\">{{ node.label }}</span>\n }\n}\n", styles: [":host{padding:var(--tile-slot-padding);display:var(--yuv-renderer-display, flex);gap:var(--ymt-spacing-xs);align-items:center}:host .node{display:flex;flex-flow:row nowrap;align-items:center;border:1px solid var(--ymt-outline);border-radius:var(--ymt-corner-xs);padding:0 var(--ymt-spacing-xs);font:var(--ymt-font-body-subtle);white-space:nowrap}:host .loading i{animation:pulse 1.4s ease-in-out infinite both}:host .loading i:nth-child(1){animation-delay:-.32s}:host .loading i:nth-child(2){animation-delay:-.16s}:host .loading i:nth-child(3){animation-delay:0s}@keyframes pulse{0%,80%,to{opacity:.3;transform:scale(1)}40%{opacity:1;transform:scale(1.2)}}\n"] }]
227
378
  }] });
228
379
 
229
380
  class StringRendererComponent extends AbstractRendererComponent {
@@ -243,7 +394,7 @@ class TableRendererComponent extends AbstractRendererComponent {
243
394
  this.#decimalPipe = inject(DecimalPipe);
244
395
  this.#datePipe = inject(DatePipe);
245
396
  this.tableHeaders = computed(() => [
246
- ...new Set((this.value() || []).flat().map((item, index) => ({
397
+ ...new Set((this.value() || []).flat().map((item) => ({
247
398
  id: `header-${crypto.randomUUID()}`,
248
399
  key: item.propertyName,
249
400
  label: this.#system.getLocalizedLabel(`${item.propertyName}`) || item.propertyName
@@ -251,7 +402,7 @@ class TableRendererComponent extends AbstractRendererComponent {
251
402
  ], ...(ngDevMode ? [{ debugName: "tableHeaders" }] : /* istanbul ignore next */ []));
252
403
  this.tableData = computed(() => {
253
404
  const data = this.value() || [];
254
- return (data.length > 10 ? data.slice(0, 10) : data).map((row, rowIndex) => row.map((item, index) => ({
405
+ return (data.length > 10 ? data.slice(0, 10) : data).map((row, rowIndex) => row.map((item) => ({
255
406
  id: `row-${rowIndex}-cell-${crypto.randomUUID()}`,
256
407
  ...item,
257
408
  value: this.#cellValue(item)
@@ -265,6 +416,10 @@ class TableRendererComponent extends AbstractRendererComponent {
265
416
  #system;
266
417
  #decimalPipe;
267
418
  #datePipe;
419
+ getCellValue(row, key) {
420
+ const item = (row || []).find((i) => i.propertyName === key);
421
+ return item ? item.value : '';
422
+ }
268
423
  #cellValue(element) {
269
424
  switch (element.propertyType) {
270
425
  case 'datetime':
@@ -277,64 +432,12 @@ class TableRendererComponent extends AbstractRendererComponent {
277
432
  return element.value;
278
433
  }
279
434
  }
280
- getCellValue(row, key) {
281
- const item = (row || []).find((i) => i.propertyName === key);
282
- return item ? item.value : '';
283
- }
284
435
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: TableRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
285
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: TableRendererComponent, isStandalone: true, selector: "yuv-table-renderer", providers: [DecimalPipe, DatePipe], usesInheritance: true, ngImport: i0, template: `
286
- <div class="table-container">
287
- <table>
288
- <thead>
289
- <tr>
290
- @for (header of tableHeaders(); track header.id) {
291
- <th>{{ header.label }}</th>
292
- }
293
- </tr>
294
- </thead>
295
- <tbody>
296
- @for (row of tableData(); track $index) {
297
- <tr>
298
- @for (header of tableHeaders(); track header.id) {
299
- <td>{{ getCellValue(row, header.key) }}</td>
300
- }
301
- </tr>
302
- }
303
- </tbody>
304
- </table>
305
- @if (reducedData()) {
306
- <p>{{ 'yuv.table.renderer.moreEntries' | translate: { count: value()?.length } }}</p>
307
- }
308
- </div>
309
- `, isInline: true, styles: [":host{display:flex;padding:var(--tile-slot-padding);width:100%}.table-container{width:100%;overflow-x:auto;max-width:100%}table{width:100%;border-collapse:collapse;min-width:400px}th,td{padding:8px;text-align:left;border-bottom:1px solid var(--ymt-outline-variant);word-break:normal}th{background-color:var(--object-summary-section-background, var(--ymt-surface));font-weight:700}tr:hover{background-color:var(--ymt-hover-background)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
436
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: TableRendererComponent, isStandalone: true, selector: "yuv-table-renderer", providers: [DecimalPipe, DatePipe], usesInheritance: true, ngImport: i0, template: "<div class=\"table-container\">\n <table>\n <thead>\n <tr>\n @for (header of tableHeaders(); track header.id) {\n <th>{{ header.label }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of tableData(); track $index) {\n <tr>\n @for (header of tableHeaders(); track header.id) {\n <td>{{ getCellValue(row, header.key) }}</td>\n }\n </tr>\n }\n </tbody>\n </table>\n @if (reducedData()) {\n <p>{{ 'yuv.table.renderer.moreEntries' | translate: { count: value()?.length } }}</p>\n }\n</div>\n", styles: [":host{display:flex;padding:var(--tile-slot-padding);width:100%}.table-container{width:100%;overflow-x:auto;max-width:100%}table{width:100%;border-collapse:collapse;min-width:400px}th,td{padding:8px;text-align:left;border-bottom:1px solid var(--ymt-outline-variant);word-break:normal}th{background-color:var(--object-summary-section-background, var(--ymt-surface));font-weight:700}tr:hover{background-color:var(--ymt-hover-background)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
310
437
  }
311
438
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: TableRendererComponent, decorators: [{
312
439
  type: Component,
313
- args: [{ selector: 'yuv-table-renderer', standalone: true, imports: [TranslatePipe], template: `
314
- <div class="table-container">
315
- <table>
316
- <thead>
317
- <tr>
318
- @for (header of tableHeaders(); track header.id) {
319
- <th>{{ header.label }}</th>
320
- }
321
- </tr>
322
- </thead>
323
- <tbody>
324
- @for (row of tableData(); track $index) {
325
- <tr>
326
- @for (header of tableHeaders(); track header.id) {
327
- <td>{{ getCellValue(row, header.key) }}</td>
328
- }
329
- </tr>
330
- }
331
- </tbody>
332
- </table>
333
- @if (reducedData()) {
334
- <p>{{ 'yuv.table.renderer.moreEntries' | translate: { count: value()?.length } }}</p>
335
- }
336
- </div>
337
- `, providers: [DecimalPipe, DatePipe], styles: [":host{display:flex;padding:var(--tile-slot-padding);width:100%}.table-container{width:100%;overflow-x:auto;max-width:100%}table{width:100%;border-collapse:collapse;min-width:400px}th,td{padding:8px;text-align:left;border-bottom:1px solid var(--ymt-outline-variant);word-break:normal}th{background-color:var(--object-summary-section-background, var(--ymt-surface));font-weight:700}tr:hover{background-color:var(--ymt-hover-background)}\n"] }]
440
+ args: [{ selector: 'yuv-table-renderer', standalone: true, imports: [TranslatePipe], providers: [DecimalPipe, DatePipe], template: "<div class=\"table-container\">\n <table>\n <thead>\n <tr>\n @for (header of tableHeaders(); track header.id) {\n <th>{{ header.label }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of tableData(); track $index) {\n <tr>\n @for (header of tableHeaders(); track header.id) {\n <td>{{ getCellValue(row, header.key) }}</td>\n }\n </tr>\n }\n </tbody>\n </table>\n @if (reducedData()) {\n <p>{{ 'yuv.table.renderer.moreEntries' | translate: { count: value()?.length } }}</p>\n }\n</div>\n", styles: [":host{display:flex;padding:var(--tile-slot-padding);width:100%}.table-container{width:100%;overflow-x:auto;max-width:100%}table{width:100%;border-collapse:collapse;min-width:400px}th,td{padding:8px;text-align:left;border-bottom:1px solid var(--ymt-outline-variant);word-break:normal}th{background-color:var(--object-summary-section-background, var(--ymt-surface));font-weight:700}tr:hover{background-color:var(--ymt-hover-background)}\n"] }]
338
441
  }] });
339
442
 
340
443
  class UnknownRendererComponent extends AbstractRendererComponent {
@@ -422,6 +525,45 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
422
525
  }]
423
526
  }], ctorParameters: () => [] });
424
527
 
528
+ /**
529
+ * Attribute directive that renders an `AuditEntry` using the component registered with
530
+ * `AuditRendererService` for the entry's `action` (and optionally `subaction`). Falls
531
+ * back to `DefaultAuditRendererComponent` when no override is registered.
532
+ */
533
+ class AuditRendererDirective {
534
+ constructor() {
535
+ this.#service = inject(AuditRendererService);
536
+ this.#containerRef = inject(ViewContainerRef);
537
+ this.yuvAuditRenderer = input.required(...(ngDevMode ? [{ debugName: "yuvAuditRenderer" }] : /* istanbul ignore next */ []));
538
+ this.#renderEffect = effect(() => {
539
+ const entry = this.yuvAuditRenderer();
540
+ if (this.#alreadyRendered(entry))
541
+ return;
542
+ this.#current = entry;
543
+ this.#containerRef.clear();
544
+ const cmp = this.#service.getAuditRenderer(entry.action, entry.subaction);
545
+ this.component = this.#containerRef.createComponent(cmp);
546
+ this.component.setInput('auditEntry', entry);
547
+ }, ...(ngDevMode ? [{ debugName: "#renderEffect" }] : /* istanbul ignore next */ []));
548
+ }
549
+ #service;
550
+ #containerRef;
551
+ #current;
552
+ #renderEffect;
553
+ #alreadyRendered(entry) {
554
+ return !!this.#current && JSON.stringify(this.#current) === JSON.stringify(entry);
555
+ }
556
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuditRendererDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
557
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.12", type: AuditRendererDirective, isStandalone: true, selector: "[yuvAuditRenderer]", inputs: { yuvAuditRenderer: { classPropertyName: "yuvAuditRenderer", publicName: "yuvAuditRenderer", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); }
558
+ }
559
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuditRendererDirective, decorators: [{
560
+ type: Directive,
561
+ args: [{
562
+ selector: '[yuvAuditRenderer]',
563
+ standalone: true
564
+ }]
565
+ }], propDecorators: { yuvAuditRenderer: [{ type: i0.Input, args: [{ isSignal: true, alias: "yuvAuditRenderer", required: true }] }] } });
566
+
425
567
  /**
426
568
  * Structural directive for rendering an obect type property
427
569
  */
@@ -473,5 +615,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
473
615
  * Generated bundle index. Do not edit.
474
616
  */
475
617
 
476
- export { AbstractRendererComponent, DateTimeRendererComponent, DecimalRendererComponent, IconRendererComponent, IntegerRendererComponent, OrganizationRendererComponent, RendererDirective, RendererService, StringRendererComponent, UnknownRendererComponent };
618
+ export { AbstractAuditRendererComponent, AbstractRendererComponent, AuditRendererDirective, AuditRendererService, BooleanRendererComponent, DateTimeRendererComponent, DecimalRendererComponent, DefaultAuditRendererComponent, FileSizeRendererComponent, IconRendererComponent, IntegerRendererComponent, OrganizationRendererComponent, RendererDirective, RendererService, StringRendererComponent, TableRendererComponent, UnknownRendererComponent };
477
619
  //# sourceMappingURL=yuuvis-client-framework-renderer.mjs.map