oip-common 0.4.0 → 0.5.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,6 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, inject, signal, computed, effect, DestroyRef, ChangeDetectorRef, Component, Input, InjectionToken, PLATFORM_ID, ViewChild, HostBinding, EventEmitter, Output, Renderer2, SecurityContext, ChangeDetectionStrategy, makeEnvironmentProviders, importProvidersFrom } from '@angular/core';
2
+ import { Injectable, inject, signal, computed, effect, DestroyRef, ChangeDetectorRef, Component, Input, InjectionToken, PLATFORM_ID, ViewChild, HostBinding, EventEmitter, Output, Renderer2, SecurityContext, ChangeDetectionStrategy, Injector, EnvironmentInjector, ViewContainerRef, makeEnvironmentProviders, importProvidersFrom } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { Subject, BehaviorSubject, of, tap, shareReplay, ReplaySubject, firstValueFrom, from, filter as filter$1 } from 'rxjs';
4
5
  import * as i2$2 from 'primeng/api';
5
6
  import { MessageService, ConfirmationService, PrimeIcons, SharedModule } from 'primeng/api';
6
7
  import { HttpErrorResponse, HttpClient as HttpClient$1, provideHttpClient, withInterceptors, withFetch } from '@angular/common/http';
@@ -8,7 +9,6 @@ import * as i2$3 from '@ngx-translate/core';
8
9
  import { TranslateService, TranslatePipe, TranslateModule, TranslateLoader } from '@ngx-translate/core';
9
10
  import * as i1$5 from '@angular/router';
10
11
  import { ActivatedRoute, Router, RouterModule, NavigationEnd, RouterLinkActive, RouterLink } from '@angular/router';
11
- import { BehaviorSubject, Subject, of, tap, shareReplay, ReplaySubject, firstValueFrom, from, filter as filter$1 } from 'rxjs';
12
12
  import { tap as tap$1, catchError, filter, distinctUntilChanged, switchMap, map, take } from 'rxjs/operators';
13
13
  import { Title, DomSanitizer } from '@angular/platform-browser';
14
14
  import { PrimeNG } from 'primeng/config';
@@ -24,7 +24,7 @@ import * as i1$3 from '@angular/common';
24
24
  import { isPlatformBrowser, CommonModule, NgComponentOutlet, NgClass, DatePipe, PathLocationStrategy, LocationStrategy } from '@angular/common';
25
25
  import * as i3$3 from 'primeng/styleclass';
26
26
  import { StyleClassModule } from 'primeng/styleclass';
27
- import { updatePreset, updateSurfacePalette, $t } from '@primeng/themes';
27
+ import { updatePreset, updateSurfacePalette, $t, definePreset } from '@primeng/themes';
28
28
  import Aura from '@primeng/themes/aura';
29
29
  import Lara from '@primeng/themes/lara';
30
30
  import Nora from '@primeng/themes/nora';
@@ -65,13 +65,15 @@ import { TableModule } from 'primeng/table';
65
65
  import * as i2$4 from 'primeng/toggleswitch';
66
66
  import { ToggleSwitchModule } from 'primeng/toggleswitch';
67
67
  import { TextareaModule, Textarea } from 'primeng/textarea';
68
- import * as i4$2 from 'primeng/toolbar';
68
+ import * as i5$1 from 'primeng/toolbar';
69
69
  import { ToolbarModule } from 'primeng/toolbar';
70
- import * as i5$1 from 'primeng/card';
70
+ import * as i5$2 from 'primeng/card';
71
71
  import { CardModule } from 'primeng/card';
72
72
  import { DividerModule } from 'primeng/divider';
73
73
  import * as i6 from 'primeng/panel';
74
74
  import { PanelModule } from 'primeng/panel';
75
+ import { loadRemoteModule } from '@angular-architects/module-federation';
76
+ import { primitive } from '@primeng/themes/aura/base';
75
77
  import { TranslateHttpLoader } from '@ngx-translate/http-loader';
76
78
 
77
79
  class TopBarService {
@@ -89,22 +91,24 @@ class TopBarService {
89
91
  }
90
92
  set activeId(value) {
91
93
  this._activeId = value;
92
- if (this.activeTopBarItem?.click)
93
- this.activeTopBarItem.click();
94
+ const activeTopBarItem = this.activeTopBarItem;
95
+ if (activeTopBarItem?.click)
96
+ activeTopBarItem.click();
97
+ this.activeIdSubject.next(value);
94
98
  }
95
99
  constructor() {
96
100
  this.topBarItems = [];
101
+ this.activeIdSubject = new Subject();
102
+ this.activeId$ = this.activeIdSubject.asObservable();
97
103
  }
98
104
  // Set tob bar items
99
105
  setTopBarItems(items) {
100
106
  this.topBarItems = items;
101
107
  }
102
108
  checkId(id) {
103
- if (this.activeTopBarItem === undefined && id === 'content')
104
- return true;
105
- if (this.activeTopBarItem === undefined)
106
- return false;
107
- return this.activeTopBarItem.id == id;
109
+ const activeTopBarItem = this.activeTopBarItem;
110
+ const result = activeTopBarItem === undefined ? id === 'content' : activeTopBarItem.id == id;
111
+ return result;
108
112
  }
109
113
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: TopBarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
110
114
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: TopBarService, providedIn: 'root' }); }
@@ -150,18 +154,17 @@ class MsgService {
150
154
  });
151
155
  }
152
156
  error(detail, summary = this.translate.instant('msgService.error'), life = this.lifetime) {
153
- const validationMessage = this.extractValidationMessage(detail);
154
- if (validationMessage) {
155
- detail = validationMessage;
156
- }
157
- else if (detail instanceof HttpErrorResponse) {
157
+ if (detail instanceof HttpErrorResponse) {
158
158
  summary = `Error: ${detail.status} ${detail.statusText}`;
159
- detail = `${detail.name} \r\n ${detail.message}`;
159
+ detail = this.extractErrorMessage(detail, `${detail.name} \r\n ${detail.message}`);
160
+ }
161
+ else {
162
+ detail = this.extractErrorMessage(detail, detail?.toString?.() ?? this.translate.instant('msgService.error'));
160
163
  }
161
164
  this.messageService.add({
162
165
  severity: 'error',
163
166
  summary: summary,
164
- detail: detail.toString(),
167
+ detail: detail,
165
168
  life: life
166
169
  });
167
170
  }
@@ -170,14 +173,9 @@ class MsgService {
170
173
  if (validationMessage) {
171
174
  return validationMessage;
172
175
  }
173
- if (typeof error === 'object' &&
174
- error &&
175
- 'error' in error &&
176
- typeof error.error === 'object' &&
177
- error.error &&
178
- 'message' in error.error &&
179
- typeof error.error.message === 'string') {
180
- return error.error.message;
176
+ const apiExceptionMessage = this.extractApiExceptionMessage(error);
177
+ if (apiExceptionMessage) {
178
+ return apiExceptionMessage;
181
179
  }
182
180
  if (typeof error === 'object' && error && 'message' in error && typeof error.message === 'string') {
183
181
  return error.message;
@@ -195,6 +193,15 @@ class MsgService {
195
193
  .filter((message) => message.length > 0);
196
194
  return messages.length > 0 ? messages.join('\n') : null;
197
195
  }
196
+ extractApiExceptionMessage(error) {
197
+ const responseError = this.getObjectProperty(error, 'error');
198
+ const message = this.getStringProperty(responseError, 'message') ?? this.getStringProperty(error, 'message');
199
+ const title = this.getStringProperty(responseError, 'title') ?? this.getStringProperty(error, 'title');
200
+ if (message && title) {
201
+ return `${title}: ${message}`;
202
+ }
203
+ return message ?? title;
204
+ }
198
205
  toValidationMessages(field, value) {
199
206
  if (Array.isArray(value)) {
200
207
  return value
@@ -213,6 +220,13 @@ class MsgService {
213
220
  const value = source[property];
214
221
  return typeof value === 'object' && value !== null ? value : null;
215
222
  }
223
+ getStringProperty(source, property) {
224
+ if (typeof source !== 'object' || source === null || !(property in source)) {
225
+ return null;
226
+ }
227
+ const value = source[property];
228
+ return typeof value === 'string' && value.length > 0 ? value : null;
229
+ }
216
230
  errorFromException(error, fallback, summary = fallback, life = this.lifetime) {
217
231
  this.error(this.extractErrorMessage(error, fallback), summary, life);
218
232
  }
@@ -399,6 +413,7 @@ class LayoutService {
399
413
  return ngDateFormat;
400
414
  }, ...(ngDevMode ? [{ debugName: "monthFormat" }] : []));
401
415
  this.timeZone = computed(() => this.layoutConfig().timeZone, ...(ngDevMode ? [{ debugName: "timeZone" }] : []));
416
+ this.adminMode = computed(() => this.layoutConfig().adminMode ?? false, ...(ngDevMode ? [{ debugName: "adminMode" }] : []));
402
417
  this.transitionComplete = signal(false, ...(ngDevMode ? [{ debugName: "transitionComplete" }] : []));
403
418
  this.initialized = false;
404
419
  effect(() => {
@@ -437,7 +452,8 @@ class LayoutService {
437
452
  dateFormat: 'yyyy-MM-dd',
438
453
  timeFormat: 'HH:mm:ss',
439
454
  dateTimeFormat: 'yyyy-MM-dd HH:mm:ss',
440
- timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
455
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
456
+ adminMode: false
441
457
  };
442
458
  }
443
459
  handleDarkModeTransition(config) {
@@ -542,6 +558,36 @@ class L10nService {
542
558
  const lang = this.translateService.currentLang || this.layoutService.language() || 'en';
543
559
  return this.loadTranslations(component, lang);
544
560
  }
561
+ /**
562
+ * Loads translations from an explicit asset URL and merges them into the active language dictionary.
563
+ * Use this for extension modules whose assets are hosted outside the shell application.
564
+ */
565
+ loadTranslationsFromUrl(namespace, url, lang) {
566
+ const selectedLang = lang || this.translateService.currentLang || this.layoutService.language() || 'en';
567
+ const key = `${namespace}.${selectedLang}.${url}`;
568
+ if (this.loadedTranslations.has(key)) {
569
+ return of(null);
570
+ }
571
+ const loading = this.loadingTranslations.get(key);
572
+ if (loading) {
573
+ return loading;
574
+ }
575
+ const request = this.httpClient.get(url).pipe(tap((translations) => {
576
+ const current = this.translateService.translations[selectedLang] || {};
577
+ this.translateService.setTranslation(selectedLang, { ...current, ...translations }, true);
578
+ this.loadedTranslations.add(key);
579
+ this.loadingTranslations.delete(key);
580
+ }), shareReplay(1));
581
+ this.loadingTranslations.set(key, request);
582
+ request.subscribe({
583
+ error: (e) => {
584
+ this.loadingTranslations.delete(key);
585
+ console.error(`No translations found for ${namespace}.${selectedLang}.json at ${url}`);
586
+ console.error(e);
587
+ }
588
+ });
589
+ return request;
590
+ }
545
591
  /**
546
592
  * Gets the translated value of a key (or an array of keys)
547
593
  * @returns the translated key, or an object of translated keys
@@ -620,16 +666,21 @@ class BffSecurityService {
620
666
  this.csrfToken = new ReplaySubject(1);
621
667
  this.payload = new BehaviorSubject(null);
622
668
  }
669
+ static { this.authorizeAfterLogoutReturnUrlKey = 'oip.authorizeAfterLogoutReturnUrl'; }
623
670
  auth() {
624
- this.http.get(this.buildUrl('api/security/get-current-auth-session'), {
671
+ this.http
672
+ .get(this.buildUrl('api/security/get-current-auth-session'), {
625
673
  withCredentials: true
626
- }).pipe(tap$1((session) => this.applySession(session)), catchError(() => {
674
+ })
675
+ .pipe(tap$1((session) => this.applySession(session)), catchError(() => {
627
676
  this.applyAnonymousSession();
628
677
  return of(null);
629
- })).subscribe();
678
+ }))
679
+ .subscribe();
630
680
  }
631
681
  logout() {
632
682
  firstValueFrom(this.getCsrfToken()).then((csrfToken) => {
683
+ this.storeAuthorizeAfterLogoutReturnUrl();
633
684
  const form = this.createPostForm(this.buildUrl('api/security/delete-auth-session'));
634
685
  if (csrfToken?.token) {
635
686
  const tokenInput = document.createElement('input');
@@ -665,15 +716,20 @@ class BffSecurityService {
665
716
  });
666
717
  }
667
718
  getCsrfToken() {
668
- this.http.get(this.buildUrl('api/security/get-auth-csrf-token'), {
719
+ this.http
720
+ .get(this.buildUrl('api/security/get-auth-csrf-token'), {
669
721
  withCredentials: true
670
- }).pipe(catchError(() => of(null))).subscribe((token) => this.csrfToken.next(token));
722
+ })
723
+ .pipe(catchError(() => of(null)))
724
+ .subscribe((token) => this.csrfToken.next(token));
671
725
  return this.csrfToken.asObservable();
672
726
  }
673
727
  isAdmin() {
674
728
  return this.payload.getValue()?.realm_access?.roles?.includes('admin') ?? false;
675
729
  }
676
- authorize() {
730
+ authorize(returnUrl) {
731
+ const normalizedReturnUrl = this.normalizeReturnUrl(returnUrl);
732
+ this.replaceWithUnauthorizedUrl(normalizedReturnUrl);
677
733
  const form = this.createPostForm(this.buildUrl('api/security/create-auth-session'));
678
734
  document.body.appendChild(form);
679
735
  form.submit();
@@ -724,10 +780,40 @@ class BffSecurityService {
724
780
  form.style.display = 'none';
725
781
  return form;
726
782
  }
783
+ normalizeReturnUrl(returnUrl) {
784
+ const candidate = returnUrl?.trim();
785
+ if (!candidate || /[\u0000-\u001f\u007f\\]/.test(candidate)) {
786
+ return '/';
787
+ }
788
+ if (!candidate.startsWith('/') || candidate.startsWith('//')) {
789
+ return '/';
790
+ }
791
+ try {
792
+ const url = new URL(candidate, window.location.origin);
793
+ if (url.origin !== window.location.origin) {
794
+ return '/';
795
+ }
796
+ const normalizedUrl = `${url.pathname}${url.search}${url.hash}`;
797
+ return normalizedUrl.startsWith('/unauthorized') ? '/' : normalizedUrl;
798
+ }
799
+ catch {
800
+ return '/';
801
+ }
802
+ }
803
+ replaceWithUnauthorizedUrl(returnUrl) {
804
+ const unauthorizedUrl = new URL('unauthorized', this.getBaseUrl());
805
+ unauthorizedUrl.searchParams.set('returnUrl', returnUrl);
806
+ history.replaceState(history.state, '', `${unauthorizedUrl.pathname}${unauthorizedUrl.search}`);
807
+ }
808
+ storeAuthorizeAfterLogoutReturnUrl() {
809
+ sessionStorage.setItem(BffSecurityService.authorizeAfterLogoutReturnUrlKey, this.normalizeReturnUrl(`${window.location.pathname}${window.location.search}${window.location.hash}`));
810
+ }
727
811
  buildUrl(path) {
812
+ return `${this.getBaseUrl()}${path}`;
813
+ }
814
+ getBaseUrl() {
728
815
  const baseUrl = document.getElementsByTagName('base')[0].href;
729
- const normalizedBase = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
730
- return `${normalizedBase}${path}`;
816
+ return baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
731
817
  }
732
818
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: BffSecurityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
733
819
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: BffSecurityService }); }
@@ -1171,13 +1257,11 @@ class BaseModuleComponent {
1171
1257
  * Called whenever the module instance changes, including the first load.
1172
1258
  * Derived components can override this to refresh module-specific data.
1173
1259
  */
1174
- async onModuleInstanceChange() {
1175
- }
1260
+ async onModuleInstanceChange() { }
1176
1261
  /**
1177
1262
  * Called whenever current user rights for the active module instance are recalculated.
1178
1263
  */
1179
- onSecurityRightsChange() {
1180
- }
1264
+ onSecurityRightsChange() { }
1181
1265
  /**
1182
1266
  * Starts watching current token roles and maps them to module instance security settings.
1183
1267
  */
@@ -1219,9 +1303,7 @@ class BaseModuleComponent {
1219
1303
  this.onSecurityRightsChange();
1220
1304
  }
1221
1305
  hasSecurityRight(roles, securitySettings, code) {
1222
- return securitySettings
1223
- .find((security) => security.code === code)
1224
- ?.roles?.some((role) => roles.includes(role)) ?? false;
1306
+ return (securitySettings.find((security) => security.code === code)?.roles?.some((role) => roles.includes(role)) ?? false);
1225
1307
  }
1226
1308
  getSecurity(controller = this.controller, id = this.id) {
1227
1309
  if (!controller || id == null) {
@@ -1548,8 +1630,7 @@ class AppConfiguratorComponent {
1548
1630
  this.themePresets = this.getThemePresets();
1549
1631
  this.themePresetsMap = new Map(this.themePresets.map((theme) => [theme.id, theme]));
1550
1632
  this.defaultThemePreset = this.themePresets[0] ?? DEFAULT_THEME_PRESETS[0];
1551
- this.fallbackPrimaryColors = (DEFAULT_THEME_PRESETS[0].preset
1552
- .primitive ?? {});
1633
+ this.fallbackPrimaryColors = (DEFAULT_THEME_PRESETS[0].preset.primitive ?? {});
1553
1634
  this.presets = this.themePresets.map((theme) => ({ label: theme.label ?? theme.id, value: theme.id }));
1554
1635
  this.showMenuModeButton = signal(!this.router.url.includes('auth'), ...(ngDevMode ? [{ debugName: "showMenuModeButton" }] : []));
1555
1636
  this.menuModeOptions = [
@@ -1868,7 +1949,7 @@ class AppConfiguratorComponent {
1868
1949
  .filter((entry) => Boolean(entry[1]))
1869
1950
  .map(([name, palette]) => ({ name, palette }));
1870
1951
  }
1871
- const presetPalette = (activeThemePreset.preset.primitive ?? {});
1952
+ const presetPalette = activeThemePreset.preset.primitive ?? {};
1872
1953
  const palettes = [{ name: 'noir', palette: {} }];
1873
1954
  PRIMARY_COLORS.forEach((color) => {
1874
1955
  palettes.push({
@@ -1927,7 +2008,9 @@ class AppConfiguratorComponent {
1927
2008
  this.layoutService.layoutConfig.update((state) => ({
1928
2009
  ...state,
1929
2010
  preset: nextThemeId,
1930
- primary: primaryColors.some((color) => color.name === state.primary) ? state.primary : (primaryColors[0]?.name ?? state.primary),
2011
+ primary: primaryColors.some((color) => color.name === state.primary)
2012
+ ? state.primary
2013
+ : (primaryColors[0]?.name ?? state.primary),
1931
2014
  surface: nextTheme.surfaceColors && !surfaceColors.some((color) => color.name === state.surface)
1932
2015
  ? (surfaceColors[0]?.name ?? state.surface)
1933
2016
  : state.surface
@@ -1991,12 +2074,12 @@ class AppConfiguratorComponent {
1991
2074
  <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.presets' | translate }}</span>
1992
2075
  <p-selectButton
1993
2076
  id="oip-app-configurator-preset-select-button"
2077
+ optionLabel="label"
2078
+ optionValue="value"
1994
2079
  size="small"
1995
2080
  [allowEmpty]="false"
1996
2081
  [ngModel]="selectedPreset()"
1997
2082
  [options]="presets"
1998
- optionLabel="label"
1999
- optionValue="value"
2000
2083
  (ngModelChange)="onPresetChange($event)" />
2001
2084
  </div>
2002
2085
  @if (showMenuModeButton()) {
@@ -2068,12 +2151,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
2068
2151
  <span class="text-sm text-muted-color font-semibold">{{ 'app-configurator.presets' | translate }}</span>
2069
2152
  <p-selectButton
2070
2153
  id="oip-app-configurator-preset-select-button"
2154
+ optionLabel="label"
2155
+ optionValue="value"
2071
2156
  size="small"
2072
2157
  [allowEmpty]="false"
2073
2158
  [ngModel]="selectedPreset()"
2074
2159
  [options]="presets"
2075
- optionLabel="label"
2076
- optionValue="value"
2077
2160
  (ngModelChange)="onPresetChange($event)" />
2078
2161
  </div>
2079
2162
  @if (showMenuModeButton()) {
@@ -2102,10 +2185,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
2102
2185
  class UserProfileApi extends HttpClient {
2103
2186
  constructor() {
2104
2187
  super(...arguments);
2105
- this.getUserPhoto = (query, params = {}) => this.request({
2188
+ this.getUserPhoto = (params = {}) => this.request({
2106
2189
  path: `/api/user-profile/get-user-photo`,
2107
2190
  method: "GET",
2108
- query: query,
2191
+ secure: true,
2192
+ format: "blob",
2193
+ ...params,
2194
+ });
2195
+ this.getUserPhotoById = ({ userId, ...query }, params = {}) => this.request({
2196
+ path: `/api/user-profile/get-user-photo-by-id/${userId}`,
2197
+ method: "GET",
2109
2198
  secure: true,
2110
2199
  format: "blob",
2111
2200
  ...params,
@@ -2149,7 +2238,9 @@ class UserService {
2149
2238
  constructor() {
2150
2239
  this.securityService = inject(SecurityService);
2151
2240
  this.userProfileApi = inject(UserProfileApi);
2152
- this.requestedPhotoEmail = null;
2241
+ this.msgService = inject(MsgService);
2242
+ this.translateService = inject(TranslateService);
2243
+ this.requestedPhotoKey = null;
2153
2244
  /**
2154
2245
  * Stores the user's photo as a data URL or binary blob, depending on how it's processed.
2155
2246
  */
@@ -2160,7 +2251,7 @@ class UserService {
2160
2251
  this.photoLoaded = false;
2161
2252
  this.securityService
2162
2253
  .getCurrentUser$()
2163
- .pipe(map((user) => user?.email ?? null), filter((email) => !!email), distinctUntilChanged())
2254
+ .pipe(map((user) => this.getCurrentUserPhotoKey(user)), filter((photoKey) => !!photoKey), distinctUntilChanged())
2164
2255
  .subscribe(() => {
2165
2256
  this.getUserPhoto();
2166
2257
  });
@@ -2178,35 +2269,41 @@ class UserService {
2178
2269
  }
2179
2270
  get userName() {
2180
2271
  const data = this.securityService.getCurrentUser();
2181
- return [data?.given_name, data?.family_name].filter(Boolean).join(' ')
2182
- || data?.displayName
2183
- || data?.name
2184
- || data?.userName
2185
- || data?.preferred_username
2186
- || data?.email
2187
- || '';
2272
+ return ([data?.given_name, data?.family_name].filter(Boolean).join(' ') ||
2273
+ data?.displayName ||
2274
+ data?.name ||
2275
+ data?.userName ||
2276
+ data?.preferred_username ||
2277
+ data?.email ||
2278
+ '');
2188
2279
  }
2189
2280
  /**
2190
- * Initiates an HTTP request to fetch the user's photo based on their email,
2281
+ * Initiates an HTTP request to fetch the current user's photo,
2191
2282
  * and updates the `photo` and `photoLoaded` properties accordingly.
2192
2283
  */
2193
2284
  getUserPhoto() {
2194
- const email = this.securityService.getCurrentUser()?.email;
2195
- if (!email) {
2285
+ const photoKey = this.getCurrentUserPhotoKey(this.securityService.getCurrentUser());
2286
+ if (!photoKey) {
2196
2287
  return;
2197
2288
  }
2198
- if (this.requestedPhotoEmail === email && (this.photoLoaded || this.photo)) {
2289
+ if (this.requestedPhotoKey === photoKey && (this.photoLoaded || this.photo)) {
2199
2290
  return;
2200
2291
  }
2201
- this.requestedPhotoEmail = email;
2202
- this.userProfileApi.getUserPhoto({ email }, { format: 'blob' }).then((data) => {
2292
+ this.requestedPhotoKey = photoKey;
2293
+ this.userProfileApi.getUserPhoto({ format: 'blob' }).then((data) => {
2203
2294
  this.createImageFromBlob(data);
2204
2295
  this.photoLoaded = true;
2205
2296
  }, (error) => {
2206
- console.log(error);
2297
+ this.msgService.errorFromException(error, this.translateService.instant('profileComponent.failedToLoadPhoto'));
2207
2298
  this.photoLoaded = false;
2208
2299
  });
2209
2300
  }
2301
+ refreshUserPhoto() {
2302
+ this.requestedPhotoKey = null;
2303
+ this.photo = null;
2304
+ this.photoLoaded = false;
2305
+ this.getUserPhoto();
2306
+ }
2210
2307
  /**
2211
2308
  * Converts a Blob image into a Base64 data URL and stores it in the `photo` property.
2212
2309
  *
@@ -2222,14 +2319,17 @@ class UserService {
2222
2319
  }
2223
2320
  }
2224
2321
  getInitials(value) {
2225
- return value
2322
+ return (value
2226
2323
  ?.trim()
2227
2324
  .split(/\s+/)
2228
2325
  .filter(Boolean)
2229
2326
  .slice(0, 2)
2230
2327
  .map((part) => part[0])
2231
2328
  .join('')
2232
- .toUpperCase() ?? '';
2329
+ .toUpperCase() ?? '');
2330
+ }
2331
+ getCurrentUserPhotoKey(user) {
2332
+ return user?.sub || user?.userName || user?.preferred_username || user?.displayName || user?.name || null;
2233
2333
  }
2234
2334
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: UserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2235
2335
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: UserService }); }
@@ -2502,8 +2602,8 @@ class UserNotificationsComponent {
2502
2602
  class="layout-topbar-action relative"
2503
2603
  id="oip-app-topbar-notification-button"
2504
2604
  type="button"
2505
- (click)="toggle($event)"
2506
- [attr.aria-label]="'Notifications: ' + unreadNotificationCount()">
2605
+ [attr.aria-label]="'Notifications: ' + unreadNotificationCount()"
2606
+ (click)="toggle($event)">
2507
2607
  <i class="pi pi-bell"></i>
2508
2608
  @if (unreadNotificationCount() > 0) {
2509
2609
  <p-badge
@@ -2579,8 +2679,8 @@ class UserNotificationsComponent {
2579
2679
  styleClass="mt-3"
2580
2680
  [first]="skip"
2581
2681
  [rows]="take"
2582
- [totalRecords]="totalCount"
2583
2682
  [rowsPerPageOptions]="[5, 10, 20]"
2683
+ [totalRecords]="totalCount"
2584
2684
  (onPageChange)="onPageChange($event)"></p-paginator>
2585
2685
  }
2586
2686
  </p-popover>
@@ -2591,14 +2691,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
2591
2691
  args: [{
2592
2692
  selector: 'app-user-notifications',
2593
2693
  standalone: true,
2594
- imports: [CommonModule, BadgeModule, ButtonModule, PaginatorModule, Popover, ProgressSpinnerModule, Tag, Tooltip, TranslatePipe],
2694
+ imports: [
2695
+ CommonModule,
2696
+ BadgeModule,
2697
+ ButtonModule,
2698
+ PaginatorModule,
2699
+ Popover,
2700
+ ProgressSpinnerModule,
2701
+ Tag,
2702
+ Tooltip,
2703
+ TranslatePipe
2704
+ ],
2595
2705
  template: `
2596
2706
  <button
2597
2707
  class="layout-topbar-action relative"
2598
2708
  id="oip-app-topbar-notification-button"
2599
2709
  type="button"
2600
- (click)="toggle($event)"
2601
- [attr.aria-label]="'Notifications: ' + unreadNotificationCount()">
2710
+ [attr.aria-label]="'Notifications: ' + unreadNotificationCount()"
2711
+ (click)="toggle($event)">
2602
2712
  <i class="pi pi-bell"></i>
2603
2713
  @if (unreadNotificationCount() > 0) {
2604
2714
  <p-badge
@@ -2674,8 +2784,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
2674
2784
  styleClass="mt-3"
2675
2785
  [first]="skip"
2676
2786
  [rows]="take"
2677
- [totalRecords]="totalCount"
2678
2787
  [rowsPerPageOptions]="[5, 10, 20]"
2788
+ [totalRecords]="totalCount"
2679
2789
  (onPageChange)="onPageChange($event)"></p-paginator>
2680
2790
  }
2681
2791
  </p-popover>
@@ -2839,6 +2949,10 @@ class AppTopbar {
2839
2949
  this.confirmationService = inject(ConfirmationService);
2840
2950
  this.translateService = inject(TranslateService);
2841
2951
  }
2952
+ onActiveTabChange(value) {
2953
+ const nextActiveId = value == null ? undefined : String(value);
2954
+ this.topBarService.activeId = nextActiveId;
2955
+ }
2842
2956
  toggleDarkMode() {
2843
2957
  this.layoutService.layoutConfig.update((state) => ({
2844
2958
  ...state,
@@ -2871,106 +2985,108 @@ class AppTopbar {
2871
2985
  });
2872
2986
  }
2873
2987
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: AppTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2874
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: AppTopbar, isStandalone: true, selector: "app-topbar", providers: [ConfirmationService], ngImport: i0, template: `
2875
- <div class="layout-topbar">
2876
- <p-confirmDialog appendTo="body"/>
2877
- <div class="layout-topbar-logo-container">
2878
- <button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()">
2879
- <i class="pi pi-bars"></i>
2880
- </button>
2881
- <a class="layout-topbar-logo" id="oip-app-topbar-logo-link" routerLink="">
2882
- <ng-container
2883
- *ngComponentOutlet="logoService.getLogoComponent(); inputs: { width: 36, height: 36 }"></ng-container>
2884
- <span>{{ 'app-info.title' | translate }}</span>
2885
- </a>
2886
- </div>
2988
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: AppTopbar, isStandalone: true, selector: "app-topbar", providers: [ConfirmationService], ngImport: i0, template: ` <div class="layout-topbar">
2989
+ <p-confirmDialog appendTo="body" />
2990
+ <div class="layout-topbar-logo-container">
2991
+ <button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()">
2992
+ <i class="pi pi-bars"></i>
2993
+ </button>
2994
+ <a class="layout-topbar-logo" id="oip-app-topbar-logo-link" routerLink="">
2995
+ <ng-container
2996
+ *ngComponentOutlet="logoService.getLogoComponent(); inputs: { width: 36, height: 36 }"></ng-container>
2997
+ <span>{{ 'app-info.title' | translate }}</span>
2998
+ </a>
2999
+ </div>
2887
3000
 
2888
- @if (securityService.isAdmin() && topBarService.topBarItems.length > 0) {
2889
- <p-tabs class="layout-topbar-tabs ml-2" [(value)]="topBarService.activeId">
2890
- <p-tablist>
2891
- @for (tab of topBarService.availableTopBarItems; track tab.id) {
2892
- <p-tab id="oip-app-topbar-tab-{{ tab.id }}" [value]="tab.id">
2893
- <i class="pi {{ tab.icon }}"></i>
2894
- <span class="ml-2">{{ tab.caption }}</span>
2895
- </p-tab>
2896
- }
2897
- </p-tablist>
2898
- </p-tabs>
2899
- }
2900
- <div class="layout-topbar-actions">
2901
- <div class="layout-config-menu">
2902
- <app-topbar-application-switcher/>
2903
- <app-user-notifications/>
2904
- <p-button
2905
- class="layout-topbar-action"
2906
- id="oip-app-topbar-theme-button"
2907
- severity="secondary"
2908
- type="button"
2909
- [rounded]="true"
2910
- [text]="true"
2911
- (click)="toggleDarkMode()">
2912
- <i
2913
- class="pi"
2914
- [ngClass]="{
3001
+ @if (securityService.isAdmin() && topBarService.topBarItems.length > 0) {
3002
+ <p-tabs
3003
+ class="layout-topbar-tabs ml-2"
3004
+ [value]="topBarService.activeId"
3005
+ (valueChange)="onActiveTabChange($event)">
3006
+ <p-tablist>
3007
+ @for (tab of topBarService.availableTopBarItems; track tab.id) {
3008
+ <p-tab id="oip-app-topbar-tab-{{ tab.id }}" [value]="tab.id">
3009
+ <i class="pi {{ tab.icon }}"></i>
3010
+ <span class="ml-2">{{ tab.caption }}</span>
3011
+ </p-tab>
3012
+ }
3013
+ </p-tablist>
3014
+ </p-tabs>
3015
+ }
3016
+ <div class="layout-topbar-actions">
3017
+ <div class="layout-config-menu">
3018
+ <app-topbar-application-switcher />
3019
+ <app-user-notifications />
3020
+ <p-button
3021
+ class="layout-topbar-action"
3022
+ id="oip-app-topbar-theme-button"
3023
+ severity="secondary"
3024
+ type="button"
3025
+ [rounded]="true"
3026
+ [text]="true"
3027
+ (click)="toggleDarkMode()">
3028
+ <i
3029
+ class="pi"
3030
+ [ngClass]="{
2915
3031
  'pi-moon': layoutService.isDarkTheme(),
2916
3032
  'pi-sun': !layoutService.isDarkTheme()
2917
3033
  }"></i>
3034
+ </p-button>
3035
+ <div class="relative">
3036
+ <p-button
3037
+ class="layout-topbar-action layout-topbar-action-highlight"
3038
+ enterActiveClass="animate-scalein"
3039
+ enterFromClass="hidden"
3040
+ id="oip-app-topbar-palette-button"
3041
+ leaveActiveClass="animate-fadeout"
3042
+ leaveToClass="hidden"
3043
+ pStyleClass="@next"
3044
+ [hideOnOutsideClick]="true"
3045
+ [rounded]="true">
3046
+ <i class="pi pi-palette"></i>
2918
3047
  </p-button>
2919
- <div class="relative">
2920
- <p-button
2921
- class="layout-topbar-action layout-topbar-action-highlight"
2922
- enterActiveClass="animate-scalein"
2923
- enterFromClass="hidden"
2924
- id="oip-app-topbar-palette-button"
2925
- leaveActiveClass="animate-fadeout"
2926
- leaveToClass="hidden"
2927
- pStyleClass="@next"
2928
- [hideOnOutsideClick]="true"
2929
- [rounded]="true">
2930
- <i class="pi pi-palette"></i>
2931
- </p-button>
2932
- <app-configurator/>
2933
- </div>
3048
+ <app-configurator />
2934
3049
  </div>
3050
+ </div>
2935
3051
 
2936
- <button
2937
- class="layout-topbar-menu-button layout-topbar-action"
2938
- enterActiveClass="animate-scalein"
2939
- enterFromClass="hidden"
2940
- id="oip-app-topbar-menu-expand-button"
2941
- leaveActiveClass="animate-fadeout"
2942
- leaveToClass="hidden"
2943
- pStyleClass="@next"
2944
- [hideOnOutsideClick]="true">
2945
- <i class="pi pi-ellipsis-v"></i>
2946
- </button>
3052
+ <button
3053
+ class="layout-topbar-menu-button layout-topbar-action"
3054
+ enterActiveClass="animate-scalein"
3055
+ enterFromClass="hidden"
3056
+ id="oip-app-topbar-menu-expand-button"
3057
+ leaveActiveClass="animate-fadeout"
3058
+ leaveToClass="hidden"
3059
+ pStyleClass="@next"
3060
+ [hideOnOutsideClick]="true">
3061
+ <i class="pi pi-ellipsis-v"></i>
3062
+ </button>
2947
3063
 
2948
- <div class="layout-topbar-menu hidden lg:block">
2949
- <div class="layout-topbar-menu-content">
2950
- <button
2951
- class="layout-topbar-action"
2952
- id="oip-app-topbar-logout-button"
2953
- type="button"
2954
- (click)="confirmLogout()"
2955
- (keydown)="logoutKeyDown($event)">
2956
- <i class="pi pi-sign-out"></i>
2957
- <span>{{ 'topbar.logout' | translate }}</span>
2958
- </button>
2959
- <button class="layout-topbar-action" routerLink="config">
2960
- <p-avatar
2961
- class="p-link flex align-items-center"
2962
- id="oip-app-topbar-user-avatar"
2963
- shape="circle"
2964
- size="normal"
2965
- [image]="userService.photoLoaded ? userService.photo : null"
3064
+ <div class="layout-topbar-menu hidden lg:block">
3065
+ <div class="layout-topbar-menu-content">
3066
+ <button
3067
+ class="layout-topbar-action"
3068
+ id="oip-app-topbar-logout-button"
3069
+ type="button"
3070
+ (click)="confirmLogout()"
3071
+ (keydown)="logoutKeyDown($event)">
3072
+ <i class="pi pi-sign-out"></i>
3073
+ <span>{{ 'topbar.logout' | translate }}</span>
3074
+ </button>
3075
+ <button class="layout-topbar-action" routerLink="config">
3076
+ <p-avatar
3077
+ class="p-link flex align-items-center"
3078
+ id="oip-app-topbar-user-avatar"
3079
+ shape="circle"
3080
+ size="normal"
3081
+ [image]="userService.photoLoaded ? userService.photo : null"
2966
3082
  >{{ !userService.photoLoaded ? userService.shortLabel : null }}
2967
- </p-avatar>
2968
- <span class="ml-2">{{ 'topbar.profile' | translate }}</span>
2969
- </button>
2970
- </div>
3083
+ </p-avatar>
3084
+ <span class="ml-2">{{ 'topbar.profile' | translate }}</span>
3085
+ </button>
2971
3086
  </div>
2972
3087
  </div>
2973
- </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$5.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$3.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i3$3.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfiguratorComponent, selector: "app-configurator" }, { kind: "component", type: Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: TabList, selector: "p-tablist" }, { kind: "component", type: Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i2$1.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "component", type: UserNotificationsComponent, selector: "app-user-notifications" }, { kind: "component", type: AppTopbarApplicationSwitcherComponent, selector: "app-topbar-application-switcher" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
3088
+ </div>
3089
+ </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$5.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$3.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: StyleClassModule }, { kind: "directive", type: i3$3.StyleClass, selector: "[pStyleClass]", inputs: ["pStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass", "hideOnEscape", "hideOnResize", "resizeSelector"] }, { kind: "component", type: AppConfiguratorComponent, selector: "app-configurator" }, { kind: "component", type: Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: TabList, selector: "p-tablist" }, { kind: "component", type: Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i2$1.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "component", type: UserNotificationsComponent, selector: "app-user-notifications" }, { kind: "component", type: AppTopbarApplicationSwitcherComponent, selector: "app-topbar-application-switcher" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
2974
3090
  }
2975
3091
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: AppTopbar, decorators: [{
2976
3092
  type: Component,
@@ -2992,106 +3108,108 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
2992
3108
  TranslatePipe,
2993
3109
  AppTopbarApplicationSwitcherComponent
2994
3110
  ],
2995
- template: `
2996
- <div class="layout-topbar">
2997
- <p-confirmDialog appendTo="body"/>
2998
- <div class="layout-topbar-logo-container">
2999
- <button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()">
3000
- <i class="pi pi-bars"></i>
3001
- </button>
3002
- <a class="layout-topbar-logo" id="oip-app-topbar-logo-link" routerLink="">
3003
- <ng-container
3004
- *ngComponentOutlet="logoService.getLogoComponent(); inputs: { width: 36, height: 36 }"></ng-container>
3005
- <span>{{ 'app-info.title' | translate }}</span>
3006
- </a>
3007
- </div>
3111
+ template: ` <div class="layout-topbar">
3112
+ <p-confirmDialog appendTo="body" />
3113
+ <div class="layout-topbar-logo-container">
3114
+ <button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()">
3115
+ <i class="pi pi-bars"></i>
3116
+ </button>
3117
+ <a class="layout-topbar-logo" id="oip-app-topbar-logo-link" routerLink="">
3118
+ <ng-container
3119
+ *ngComponentOutlet="logoService.getLogoComponent(); inputs: { width: 36, height: 36 }"></ng-container>
3120
+ <span>{{ 'app-info.title' | translate }}</span>
3121
+ </a>
3122
+ </div>
3008
3123
 
3009
- @if (securityService.isAdmin() && topBarService.topBarItems.length > 0) {
3010
- <p-tabs class="layout-topbar-tabs ml-2" [(value)]="topBarService.activeId">
3011
- <p-tablist>
3012
- @for (tab of topBarService.availableTopBarItems; track tab.id) {
3013
- <p-tab id="oip-app-topbar-tab-{{ tab.id }}" [value]="tab.id">
3014
- <i class="pi {{ tab.icon }}"></i>
3015
- <span class="ml-2">{{ tab.caption }}</span>
3016
- </p-tab>
3017
- }
3018
- </p-tablist>
3019
- </p-tabs>
3020
- }
3021
- <div class="layout-topbar-actions">
3022
- <div class="layout-config-menu">
3023
- <app-topbar-application-switcher/>
3024
- <app-user-notifications/>
3025
- <p-button
3026
- class="layout-topbar-action"
3027
- id="oip-app-topbar-theme-button"
3028
- severity="secondary"
3029
- type="button"
3030
- [rounded]="true"
3031
- [text]="true"
3032
- (click)="toggleDarkMode()">
3033
- <i
3034
- class="pi"
3035
- [ngClass]="{
3124
+ @if (securityService.isAdmin() && topBarService.topBarItems.length > 0) {
3125
+ <p-tabs
3126
+ class="layout-topbar-tabs ml-2"
3127
+ [value]="topBarService.activeId"
3128
+ (valueChange)="onActiveTabChange($event)">
3129
+ <p-tablist>
3130
+ @for (tab of topBarService.availableTopBarItems; track tab.id) {
3131
+ <p-tab id="oip-app-topbar-tab-{{ tab.id }}" [value]="tab.id">
3132
+ <i class="pi {{ tab.icon }}"></i>
3133
+ <span class="ml-2">{{ tab.caption }}</span>
3134
+ </p-tab>
3135
+ }
3136
+ </p-tablist>
3137
+ </p-tabs>
3138
+ }
3139
+ <div class="layout-topbar-actions">
3140
+ <div class="layout-config-menu">
3141
+ <app-topbar-application-switcher />
3142
+ <app-user-notifications />
3143
+ <p-button
3144
+ class="layout-topbar-action"
3145
+ id="oip-app-topbar-theme-button"
3146
+ severity="secondary"
3147
+ type="button"
3148
+ [rounded]="true"
3149
+ [text]="true"
3150
+ (click)="toggleDarkMode()">
3151
+ <i
3152
+ class="pi"
3153
+ [ngClass]="{
3036
3154
  'pi-moon': layoutService.isDarkTheme(),
3037
3155
  'pi-sun': !layoutService.isDarkTheme()
3038
3156
  }"></i>
3157
+ </p-button>
3158
+ <div class="relative">
3159
+ <p-button
3160
+ class="layout-topbar-action layout-topbar-action-highlight"
3161
+ enterActiveClass="animate-scalein"
3162
+ enterFromClass="hidden"
3163
+ id="oip-app-topbar-palette-button"
3164
+ leaveActiveClass="animate-fadeout"
3165
+ leaveToClass="hidden"
3166
+ pStyleClass="@next"
3167
+ [hideOnOutsideClick]="true"
3168
+ [rounded]="true">
3169
+ <i class="pi pi-palette"></i>
3039
3170
  </p-button>
3040
- <div class="relative">
3041
- <p-button
3042
- class="layout-topbar-action layout-topbar-action-highlight"
3043
- enterActiveClass="animate-scalein"
3044
- enterFromClass="hidden"
3045
- id="oip-app-topbar-palette-button"
3046
- leaveActiveClass="animate-fadeout"
3047
- leaveToClass="hidden"
3048
- pStyleClass="@next"
3049
- [hideOnOutsideClick]="true"
3050
- [rounded]="true">
3051
- <i class="pi pi-palette"></i>
3052
- </p-button>
3053
- <app-configurator/>
3054
- </div>
3171
+ <app-configurator />
3055
3172
  </div>
3173
+ </div>
3056
3174
 
3057
- <button
3058
- class="layout-topbar-menu-button layout-topbar-action"
3059
- enterActiveClass="animate-scalein"
3060
- enterFromClass="hidden"
3061
- id="oip-app-topbar-menu-expand-button"
3062
- leaveActiveClass="animate-fadeout"
3063
- leaveToClass="hidden"
3064
- pStyleClass="@next"
3065
- [hideOnOutsideClick]="true">
3066
- <i class="pi pi-ellipsis-v"></i>
3067
- </button>
3175
+ <button
3176
+ class="layout-topbar-menu-button layout-topbar-action"
3177
+ enterActiveClass="animate-scalein"
3178
+ enterFromClass="hidden"
3179
+ id="oip-app-topbar-menu-expand-button"
3180
+ leaveActiveClass="animate-fadeout"
3181
+ leaveToClass="hidden"
3182
+ pStyleClass="@next"
3183
+ [hideOnOutsideClick]="true">
3184
+ <i class="pi pi-ellipsis-v"></i>
3185
+ </button>
3068
3186
 
3069
- <div class="layout-topbar-menu hidden lg:block">
3070
- <div class="layout-topbar-menu-content">
3071
- <button
3072
- class="layout-topbar-action"
3073
- id="oip-app-topbar-logout-button"
3074
- type="button"
3075
- (click)="confirmLogout()"
3076
- (keydown)="logoutKeyDown($event)">
3077
- <i class="pi pi-sign-out"></i>
3078
- <span>{{ 'topbar.logout' | translate }}</span>
3079
- </button>
3080
- <button class="layout-topbar-action" routerLink="config">
3081
- <p-avatar
3082
- class="p-link flex align-items-center"
3083
- id="oip-app-topbar-user-avatar"
3084
- shape="circle"
3085
- size="normal"
3086
- [image]="userService.photoLoaded ? userService.photo : null"
3187
+ <div class="layout-topbar-menu hidden lg:block">
3188
+ <div class="layout-topbar-menu-content">
3189
+ <button
3190
+ class="layout-topbar-action"
3191
+ id="oip-app-topbar-logout-button"
3192
+ type="button"
3193
+ (click)="confirmLogout()"
3194
+ (keydown)="logoutKeyDown($event)">
3195
+ <i class="pi pi-sign-out"></i>
3196
+ <span>{{ 'topbar.logout' | translate }}</span>
3197
+ </button>
3198
+ <button class="layout-topbar-action" routerLink="config">
3199
+ <p-avatar
3200
+ class="p-link flex align-items-center"
3201
+ id="oip-app-topbar-user-avatar"
3202
+ shape="circle"
3203
+ size="normal"
3204
+ [image]="userService.photoLoaded ? userService.photo : null"
3087
3205
  >{{ !userService.photoLoaded ? userService.shortLabel : null }}
3088
- </p-avatar>
3089
- <span class="ml-2">{{ 'topbar.profile' | translate }}</span>
3090
- </button>
3091
- </div>
3206
+ </p-avatar>
3207
+ <span class="ml-2">{{ 'topbar.profile' | translate }}</span>
3208
+ </button>
3092
3209
  </div>
3093
3210
  </div>
3094
- </div>`,
3211
+ </div>
3212
+ </div>`,
3095
3213
  providers: [ConfirmationService]
3096
3214
  }]
3097
3215
  }] });
@@ -3206,10 +3324,16 @@ class MenuService {
3206
3324
  this.resetSource = new Subject();
3207
3325
  this.titleService = inject(AppTitleService);
3208
3326
  this.menuDataService = inject(MenuApi);
3327
+ this.layoutService = inject(LayoutService);
3209
3328
  this.menuSource$ = this.menuSource.asObservable();
3210
3329
  this.resetSource$ = this.resetSource.asObservable();
3211
3330
  this.menu = [];
3212
- this.adminMode = false;
3331
+ }
3332
+ get adminMode() {
3333
+ return this.layoutService.adminMode();
3334
+ }
3335
+ set adminMode(value) {
3336
+ this.layoutService.layoutConfig.update((config) => ({ ...config, adminMode: value }));
3213
3337
  }
3214
3338
  async loadMenu() {
3215
3339
  this.menu = this.adminMode ? await this.getAdminMenu() : await this.getMenu();
@@ -4179,8 +4303,7 @@ class MenuComponent {
4179
4303
  ];
4180
4304
  }
4181
4305
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: MenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4182
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: MenuComponent, isStandalone: true, selector: "app-menu", providers: [MenuApi], viewQueries: [{ propertyName: "menuItemCreateDialogComponent", first: true, predicate: MenuItemCreateDialogComponent, descendants: true }, { propertyName: "menuItemEditDialogComponent", first: true, predicate: MenuItemEditDialogComponent, descendants: true }, { propertyName: "contextMenu", first: true, predicate: ContextMenu, descendants: true }], ngImport: i0, template: `
4183
- <div #empty class="layout-sidebar" (contextmenu)="onContextMenu($event)">
4306
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: MenuComponent, isStandalone: true, selector: "app-menu", providers: [MenuApi], viewQueries: [{ propertyName: "menuItemCreateDialogComponent", first: true, predicate: MenuItemCreateDialogComponent, descendants: true }, { propertyName: "menuItemEditDialogComponent", first: true, predicate: MenuItemEditDialogComponent, descendants: true }, { propertyName: "contextMenu", first: true, predicate: ContextMenu, descendants: true }], ngImport: i0, template: ` <div #empty class="layout-sidebar" (contextmenu)="onContextMenu($event)">
4184
4307
  <ul class="layout-menu">
4185
4308
  @for (item of menuService.menu; track item; let i = $index) {
4186
4309
  <ng-container>
@@ -4201,9 +4324,9 @@ class MenuComponent {
4201
4324
  </ul>
4202
4325
  </div>
4203
4326
  @if (securityService.isAdmin()) {
4204
- <p-contextMenu [target]="empty"/>
4205
- <menu-item-create-dialog/>
4206
- <menu-item-edit-dialog/>
4327
+ <p-contextMenu [target]="empty" />
4328
+ <menu-item-create-dialog />
4329
+ <menu-item-edit-dialog />
4207
4330
  }`, isInline: true, dependencies: [{ kind: "component", type: MenuItemComponent, selector: "[app-menuitem]", inputs: ["item", "index", "root", "parentKey", "menuItemCreateDialogComponent", "menuItemEditDialogComponent", "contextMenu"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i1$6.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "component", type: MenuItemCreateDialogComponent, selector: "menu-item-create-dialog", inputs: ["visible"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: MenuItemEditDialogComponent, selector: "menu-item-edit-dialog", inputs: ["visible"], outputs: ["visibleChange"] }] }); }
4208
4331
  }
4209
4332
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: MenuComponent, decorators: [{
@@ -4222,8 +4345,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
4222
4345
  providers: [MenuApi],
4223
4346
  selector: 'app-menu',
4224
4347
  standalone: true,
4225
- template: `
4226
- <div #empty class="layout-sidebar" (contextmenu)="onContextMenu($event)">
4348
+ template: ` <div #empty class="layout-sidebar" (contextmenu)="onContextMenu($event)">
4227
4349
  <ul class="layout-menu">
4228
4350
  @for (item of menuService.menu; track item; let i = $index) {
4229
4351
  <ng-container>
@@ -4244,9 +4366,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
4244
4366
  </ul>
4245
4367
  </div>
4246
4368
  @if (securityService.isAdmin()) {
4247
- <p-contextMenu [target]="empty"/>
4248
- <menu-item-create-dialog/>
4249
- <menu-item-edit-dialog/>
4369
+ <p-contextMenu [target]="empty" />
4370
+ <menu-item-create-dialog />
4371
+ <menu-item-edit-dialog />
4250
4372
  }`
4251
4373
  }]
4252
4374
  }], propDecorators: { menuItemCreateDialogComponent: [{
@@ -4528,14 +4650,18 @@ class UnauthorizedComponent {
4528
4650
  constructor() {
4529
4651
  this.securityService = inject(SecurityService);
4530
4652
  this.route = inject(ActivatedRoute);
4531
- this.router = inject(Router);
4653
+ }
4654
+ ngOnInit() {
4655
+ const returnUrl = sessionStorage.getItem(BffSecurityService.authorizeAfterLogoutReturnUrlKey);
4656
+ if (!returnUrl) {
4657
+ return;
4658
+ }
4659
+ sessionStorage.removeItem(BffSecurityService.authorizeAfterLogoutReturnUrlKey);
4660
+ this.securityService.authorize(returnUrl);
4532
4661
  }
4533
4662
  signIn() {
4534
4663
  const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl') || '/';
4535
- this.router.navigate(['/unauthorized'], {
4536
- queryParams: { returnUrl },
4537
- replaceUrl: true
4538
- }).then(() => this.securityService.authorize());
4664
+ this.securityService.authorize(returnUrl);
4539
4665
  }
4540
4666
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: UnauthorizedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4541
4667
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.24", type: UnauthorizedComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
@@ -4690,9 +4816,21 @@ class ProfileComponent {
4690
4816
  this.userService = inject(UserService);
4691
4817
  this.msgService = inject(MsgService);
4692
4818
  this.translateService = inject(TranslateService);
4819
+ this.userProfileApi = inject(UserProfileApi);
4693
4820
  }
4694
- onBasicUploadAuto($event) {
4695
- this.msgService.success(this.translateService.instant('profileComponent.successfullyUploaded'));
4821
+ async uploadPhoto(event) {
4822
+ const file = event.files?.[0];
4823
+ if (!file) {
4824
+ return;
4825
+ }
4826
+ try {
4827
+ await this.userProfileApi.postUserPhoto({ files: file });
4828
+ this.userService.refreshUserPhoto();
4829
+ this.msgService.success(this.translateService.instant('profileComponent.successfullyUploaded'));
4830
+ }
4831
+ catch (error) {
4832
+ this.msgService.error(error);
4833
+ }
4696
4834
  }
4697
4835
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ProfileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4698
4836
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.24", type: ProfileComponent, isStandalone: true, selector: "user-profile", ngImport: i0, template: `
@@ -4711,10 +4849,9 @@ class ProfileComponent {
4711
4849
  maxFileSize="1000000"
4712
4850
  mode="basic"
4713
4851
  name="files"
4714
- url="/api/user-profile/post-user-photo"
4715
- withCredentials="true"
4716
4852
  [auto]="true"
4717
- (onUpload)="onBasicUploadAuto($event)" />
4853
+ [customUpload]="true"
4854
+ (uploadHandler)="uploadPhoto($event)" />
4718
4855
  </div>
4719
4856
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: FileUploadModule }, { kind: "component", type: i1$7.FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "ngmodule", type: ImageModule }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i2$1.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
4720
4857
  }
@@ -4723,7 +4860,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
4723
4860
  args: [{
4724
4861
  selector: 'user-profile',
4725
4862
  standalone: true,
4726
- imports: [FileUploadModule, ImageModule, ImageModule, FileUploadModule, ImageModule, AvatarModule, TranslatePipe],
4863
+ imports: [FileUploadModule, ImageModule, AvatarModule, TranslatePipe],
4727
4864
  template: `
4728
4865
  <p-avatar
4729
4866
  class="mr-2"
@@ -4740,10 +4877,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
4740
4877
  maxFileSize="1000000"
4741
4878
  mode="basic"
4742
4879
  name="files"
4743
- url="/api/user-profile/post-user-photo"
4744
- withCredentials="true"
4745
4880
  [auto]="true"
4746
- (onUpload)="onBasicUploadAuto($event)" />
4881
+ [customUpload]="true"
4882
+ (uploadHandler)="uploadPhoto($event)" />
4747
4883
  </div>
4748
4884
  `
4749
4885
  }]
@@ -5045,7 +5181,7 @@ class DbMigrationComponent extends BaseModuleComponent {
5045
5181
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: DbMigrationComponent, isStandalone: true, selector: "db-migration", providers: [ConfirmationService], usesInheritance: true, ngImport: i0, template: `
5046
5182
  @if (isContent) {
5047
5183
  <div class="card" style="height: 100%">
5048
- <p-confirmDialog/>
5184
+ <p-confirmDialog />
5049
5185
  <div>
5050
5186
  <h5>{{ 'db-migration.migrationManager' | translate }}</h5>
5051
5187
  <div class="flex flex-row gap-2">
@@ -5055,14 +5191,14 @@ class DbMigrationComponent extends BaseModuleComponent {
5055
5191
  tooltipPosition="bottom"
5056
5192
  [outlined]="true"
5057
5193
  [pTooltip]="'db-migration.actions.refresh' | translate"
5058
- (click)="refreshAction()"/>
5194
+ (click)="refreshAction()" />
5059
5195
  <p-button
5060
5196
  icon="pi pi-filter-slash"
5061
5197
  severity="secondary"
5062
5198
  tooltipPosition="bottom"
5063
5199
  [outlined]="true"
5064
5200
  [pTooltip]="'db-migration.actions.cleanFilter' | translate"
5065
- (click)="dt.clear()"/>
5201
+ (click)="dt.clear()" />
5066
5202
  </div>
5067
5203
  <div>
5068
5204
  <p-table #dt dataKey="name" editMode="row" size="small" [scrollable]="true" [value]="data">
@@ -5070,7 +5206,7 @@ class DbMigrationComponent extends BaseModuleComponent {
5070
5206
  <tr>
5071
5207
  <th pSortableColumn="name" scope="col">
5072
5208
  {{ 'db-migration.columns.name' | translate }}
5073
- <p-columnFilter display="menu" field="name" type="text"/>
5209
+ <p-columnFilter display="menu" field="name" type="text" />
5074
5210
  </th>
5075
5211
  <th scope="col">{{ 'db-migration.columns.applied' | translate }}</th>
5076
5212
  <th scope="col">{{ 'db-migration.columns.exist' | translate }}</th>
@@ -5091,7 +5227,7 @@ class DbMigrationComponent extends BaseModuleComponent {
5091
5227
  </td>
5092
5228
  <td>
5093
5229
  @if (rowData.exist) {
5094
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"/>
5230
+ <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true" />
5095
5231
  }
5096
5232
  </td>
5097
5233
  <td>
@@ -5118,7 +5254,7 @@ class DbMigrationComponent extends BaseModuleComponent {
5118
5254
  </div>
5119
5255
  </div>
5120
5256
  } @else if (isSecurity) {
5121
- <security [controller]="controller" [id]="id"/>
5257
+ <security [controller]="controller" [id]="id" />
5122
5258
  }
5123
5259
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: TableModule }, { kind: "component", type: i1$8.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i2$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i1$8.SortableColumn, selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "directive", type: i1$8.EditableRow, selector: "[pEditableRow]", inputs: ["pEditableRow", "pEditableRowDisabled"] }, { kind: "directive", type: i1$8.CancelEditableRow, selector: "[pCancelEditableRow]" }, { kind: "component", type: i1$8.ColumnFilter, selector: "p-columnFilter, p-column-filter, p-columnfilter", inputs: ["field", "type", "display", "showMenu", "matchMode", "operator", "showOperator", "showClearButton", "showApplyButton", "showMatchModes", "showAddButton", "hideOnClear", "placeholder", "matchModeOptions", "maxConstraints", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "locale", "localeMatcher", "currency", "currencyDisplay", "filterOn", "useGrouping", "showButtons", "ariaLabel", "filterButtonProps"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: SharedModule }, { kind: "ngmodule", type: TagModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: TextareaModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "component", type: SecurityComponent, selector: "security", inputs: ["id", "controller"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
5124
5260
  }
@@ -5142,7 +5278,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
5142
5278
  template: `
5143
5279
  @if (isContent) {
5144
5280
  <div class="card" style="height: 100%">
5145
- <p-confirmDialog/>
5281
+ <p-confirmDialog />
5146
5282
  <div>
5147
5283
  <h5>{{ 'db-migration.migrationManager' | translate }}</h5>
5148
5284
  <div class="flex flex-row gap-2">
@@ -5152,14 +5288,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
5152
5288
  tooltipPosition="bottom"
5153
5289
  [outlined]="true"
5154
5290
  [pTooltip]="'db-migration.actions.refresh' | translate"
5155
- (click)="refreshAction()"/>
5291
+ (click)="refreshAction()" />
5156
5292
  <p-button
5157
5293
  icon="pi pi-filter-slash"
5158
5294
  severity="secondary"
5159
5295
  tooltipPosition="bottom"
5160
5296
  [outlined]="true"
5161
5297
  [pTooltip]="'db-migration.actions.cleanFilter' | translate"
5162
- (click)="dt.clear()"/>
5298
+ (click)="dt.clear()" />
5163
5299
  </div>
5164
5300
  <div>
5165
5301
  <p-table #dt dataKey="name" editMode="row" size="small" [scrollable]="true" [value]="data">
@@ -5167,7 +5303,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
5167
5303
  <tr>
5168
5304
  <th pSortableColumn="name" scope="col">
5169
5305
  {{ 'db-migration.columns.name' | translate }}
5170
- <p-columnFilter display="menu" field="name" type="text"/>
5306
+ <p-columnFilter display="menu" field="name" type="text" />
5171
5307
  </th>
5172
5308
  <th scope="col">{{ 'db-migration.columns.applied' | translate }}</th>
5173
5309
  <th scope="col">{{ 'db-migration.columns.exist' | translate }}</th>
@@ -5188,7 +5324,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
5188
5324
  </td>
5189
5325
  <td>
5190
5326
  @if (rowData.exist) {
5191
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"/>
5327
+ <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true" />
5192
5328
  }
5193
5329
  </td>
5194
5330
  <td>
@@ -5215,13 +5351,65 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
5215
5351
  </div>
5216
5352
  </div>
5217
5353
  } @else if (isSecurity) {
5218
- <security [controller]="controller" [id]="id"/>
5354
+ <security [controller]="controller" [id]="id" />
5219
5355
  }
5220
5356
  `,
5221
5357
  providers: [ConfirmationService]
5222
5358
  }]
5223
5359
  }], ctorParameters: () => [] });
5224
5360
 
5361
+ /* eslint-disable */
5362
+ /* tslint:disable */
5363
+ // @ts-nocheck
5364
+ class ExtensionModulesApi extends HttpClient {
5365
+ constructor() {
5366
+ super(...arguments);
5367
+ this.getExtensionModules = (params = {}) => this.request({
5368
+ path: `/api/extension-modules/get-extension-modules`,
5369
+ method: "GET",
5370
+ secure: true,
5371
+ format: "json",
5372
+ ...params,
5373
+ });
5374
+ this.getExtensionModuleByKey = ({ extensionKey, ...query }, params = {}) => this.request({
5375
+ path: `/api/extension-modules/get-extension-module-by-key/${extensionKey}`,
5376
+ method: "GET",
5377
+ secure: true,
5378
+ format: "json",
5379
+ ...params,
5380
+ });
5381
+ this.registerExtensionModule = (data, params = {}) => this.request({
5382
+ path: `/api/extension-modules/register-extension-module`,
5383
+ method: "POST",
5384
+ body: data,
5385
+ secure: true,
5386
+ type: ContentType.Json,
5387
+ format: "json",
5388
+ ...params,
5389
+ });
5390
+ this.updateExtensionModule = ({ id, ...query }, data, params = {}) => this.request({
5391
+ path: `/api/extension-modules/update-extension-module/${id}`,
5392
+ method: "PUT",
5393
+ body: data,
5394
+ secure: true,
5395
+ type: ContentType.Json,
5396
+ format: "json",
5397
+ ...params,
5398
+ });
5399
+ this.deleteExtensionModule = ({ id, ...query }, params = {}) => this.request({
5400
+ path: `/api/extension-modules/delete-extension-module/${id}`,
5401
+ method: "DELETE",
5402
+ secure: true,
5403
+ ...params,
5404
+ });
5405
+ }
5406
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ExtensionModulesApi, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
5407
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ExtensionModulesApi }); }
5408
+ }
5409
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ExtensionModulesApi, decorators: [{
5410
+ type: Injectable
5411
+ }] });
5412
+
5225
5413
  /* eslint-disable */
5226
5414
  /* tslint:disable */
5227
5415
  // @ts-nocheck
@@ -5273,7 +5461,10 @@ class AppModulesComponent {
5273
5461
  this.confirmationService = inject(ConfirmationService);
5274
5462
  this.l10nService = inject(L10nService);
5275
5463
  this.titleService = inject(AppTitleService);
5464
+ this.externalModuleManifestUrl = '';
5465
+ this.registeringExternalModule = false;
5276
5466
  this.moduleService = inject(ModuleApi);
5467
+ this.extensionModulesService = inject(ExtensionModulesApi);
5277
5468
  this.translationsReady = firstValueFrom(this.l10nService.loadComponentTranslations('app-modules'));
5278
5469
  }
5279
5470
  async ngOnInit() {
@@ -5284,6 +5475,30 @@ class AppModulesComponent {
5284
5475
  async refreshAction() {
5285
5476
  this.modules = await this.moduleService.getModulesWithLoadStatus();
5286
5477
  }
5478
+ get canRegisterExternalModule() {
5479
+ return !!this.externalModuleManifestUrl.trim() && !this.registeringExternalModule;
5480
+ }
5481
+ async registerExternalModule() {
5482
+ if (!this.canRegisterExternalModule) {
5483
+ return;
5484
+ }
5485
+ await this.translationsReady;
5486
+ this.registeringExternalModule = true;
5487
+ try {
5488
+ await this.extensionModulesService.registerExtensionModule({
5489
+ manifestUrl: this.externalModuleManifestUrl.trim()
5490
+ });
5491
+ this.externalModuleManifestUrl = '';
5492
+ await this.refreshAction();
5493
+ this.msgService.success(this.t('app-modules.messages.registerSuccess'));
5494
+ }
5495
+ catch (error) {
5496
+ this.msgService.errorFromException(error, this.t('app-modules.messages.registerError'), this.t('app-modules.messages.registerError'));
5497
+ }
5498
+ finally {
5499
+ this.registeringExternalModule = false;
5500
+ }
5501
+ }
5287
5502
  async deleteModule(module) {
5288
5503
  await this.translationsReady;
5289
5504
  this.confirmationService.confirm({
@@ -5308,7 +5523,7 @@ class AppModulesComponent {
5308
5523
  this.msgService.success(this.t('app-modules.messages.deleteSuccess'));
5309
5524
  }
5310
5525
  catch (error) {
5311
- this.msgService.error(error);
5526
+ this.msgService.errorFromException(error, this.t('app-modules.messages.deleteError'), this.t('app-modules.messages.deleteError'));
5312
5527
  }
5313
5528
  }
5314
5529
  });
@@ -5317,7 +5532,7 @@ class AppModulesComponent {
5317
5532
  return this.l10nService.instant(key);
5318
5533
  }
5319
5534
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: AppModulesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5320
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.24", type: AppModulesComponent, isStandalone: true, selector: "app-modules", providers: [ConfirmationService, ModuleApi], ngImport: i0, template: `
5535
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.24", type: AppModulesComponent, isStandalone: true, selector: "app-modules", providers: [ConfirmationService, ModuleApi, ExtensionModulesApi], ngImport: i0, template: `
5321
5536
  <p-confirmDialog></p-confirmDialog>
5322
5537
  <div class="flex flex-col md:flex-row gap-4">
5323
5538
  <div class="card w-full">
@@ -5326,14 +5541,33 @@ class AppModulesComponent {
5326
5541
  </div>
5327
5542
  <div class="mb-4">
5328
5543
  <p-toolbar>
5329
- <p-button
5330
- icon="pi pi-refresh"
5331
- rounded="true"
5332
- severity="secondary"
5333
- text="true"
5334
- tooltipPosition="bottom"
5335
- [pTooltip]="'app-modules.refreshTooltip' | translate"
5336
- (onClick)="refreshAction()"></p-button>
5544
+ <div class="flex flex-col md:flex-row md:items-center gap-2 w-full">
5545
+ <div class="flex flex-col sm:flex-row gap-2 flex-1">
5546
+ <input
5547
+ class="w-full"
5548
+ pInputText
5549
+ type="url"
5550
+ [disabled]="registeringExternalModule"
5551
+ [placeholder]="'app-modules.register.manifestUrlPlaceholder' | translate"
5552
+ [(ngModel)]="externalModuleManifestUrl"
5553
+ (keydown.enter)="registerExternalModule()" />
5554
+ <p-button
5555
+ icon="pi pi-plus"
5556
+ severity="success"
5557
+ [disabled]="!canRegisterExternalModule"
5558
+ [label]="'app-modules.register.button' | translate"
5559
+ [loading]="registeringExternalModule"
5560
+ (onClick)="registerExternalModule()"></p-button>
5561
+ </div>
5562
+ <p-button
5563
+ icon="pi pi-refresh"
5564
+ rounded="true"
5565
+ severity="secondary"
5566
+ text="true"
5567
+ tooltipPosition="bottom"
5568
+ [pTooltip]="'app-modules.refreshTooltip' | translate"
5569
+ (onClick)="refreshAction()"></p-button>
5570
+ </div>
5337
5571
  </p-toolbar>
5338
5572
  </div>
5339
5573
  <p-table class="mt-4" [paginator]="true" [rows]="100" [value]="modules">
@@ -5371,13 +5605,23 @@ class AppModulesComponent {
5371
5605
  </p-table>
5372
5606
  </div>
5373
5607
  </div>
5374
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i1$8.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i2$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ToolbarModule }, { kind: "component", type: i4$2.Toolbar, selector: "p-toolbar", inputs: ["styleClass", "ariaLabelledBy"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
5608
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i1$8.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i2$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ToolbarModule }, { kind: "component", type: i5$1.Toolbar, selector: "p-toolbar", inputs: ["styleClass", "ariaLabelledBy"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pSize", "variant", "fluid", "invalid"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
5375
5609
  }
5376
5610
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: AppModulesComponent, decorators: [{
5377
5611
  type: Component,
5378
5612
  args: [{
5379
- imports: [FormsModule, TableModule, Tag, ButtonModule, ToolbarModule, Tooltip, ConfirmDialog, TranslatePipe],
5380
- providers: [ConfirmationService, ModuleApi],
5613
+ imports: [
5614
+ FormsModule,
5615
+ TableModule,
5616
+ Tag,
5617
+ ButtonModule,
5618
+ ToolbarModule,
5619
+ Tooltip,
5620
+ ConfirmDialog,
5621
+ TranslatePipe,
5622
+ InputTextModule
5623
+ ],
5624
+ providers: [ConfirmationService, ModuleApi, ExtensionModulesApi],
5381
5625
  selector: 'app-modules',
5382
5626
  template: `
5383
5627
  <p-confirmDialog></p-confirmDialog>
@@ -5388,14 +5632,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
5388
5632
  </div>
5389
5633
  <div class="mb-4">
5390
5634
  <p-toolbar>
5391
- <p-button
5392
- icon="pi pi-refresh"
5393
- rounded="true"
5394
- severity="secondary"
5395
- text="true"
5396
- tooltipPosition="bottom"
5397
- [pTooltip]="'app-modules.refreshTooltip' | translate"
5398
- (onClick)="refreshAction()"></p-button>
5635
+ <div class="flex flex-col md:flex-row md:items-center gap-2 w-full">
5636
+ <div class="flex flex-col sm:flex-row gap-2 flex-1">
5637
+ <input
5638
+ class="w-full"
5639
+ pInputText
5640
+ type="url"
5641
+ [disabled]="registeringExternalModule"
5642
+ [placeholder]="'app-modules.register.manifestUrlPlaceholder' | translate"
5643
+ [(ngModel)]="externalModuleManifestUrl"
5644
+ (keydown.enter)="registerExternalModule()" />
5645
+ <p-button
5646
+ icon="pi pi-plus"
5647
+ severity="success"
5648
+ [disabled]="!canRegisterExternalModule"
5649
+ [label]="'app-modules.register.button' | translate"
5650
+ [loading]="registeringExternalModule"
5651
+ (onClick)="registerExternalModule()"></p-button>
5652
+ </div>
5653
+ <p-button
5654
+ icon="pi pi-refresh"
5655
+ rounded="true"
5656
+ severity="secondary"
5657
+ text="true"
5658
+ tooltipPosition="bottom"
5659
+ [pTooltip]="'app-modules.refreshTooltip' | translate"
5660
+ (onClick)="refreshAction()"></p-button>
5661
+ </div>
5399
5662
  </p-toolbar>
5400
5663
  </div>
5401
5664
  <p-table class="mt-4" [paginator]="true" [rows]="100" [value]="modules">
@@ -5649,9 +5912,7 @@ class ApplicationsComponent {
5649
5912
  this.visibleApplications = this.filterApplications(this.globalFilter);
5650
5913
  }
5651
5914
  filterApplications(filter) {
5652
- const source = this.draftApplication
5653
- ? [this.draftApplication, ...this.applications]
5654
- : [...this.applications];
5915
+ const source = this.draftApplication ? [this.draftApplication, ...this.applications] : [...this.applications];
5655
5916
  const normalizedFilter = filter.trim().toLocaleLowerCase();
5656
5917
  if (!normalizedFilter) {
5657
5918
  return source;
@@ -5750,7 +6011,7 @@ class ApplicationsComponent {
5750
6011
  {{ 'applications.table.icon' | translate }}
5751
6012
  <p-sortIcon field="icon"></p-sortIcon>
5752
6013
  </th>
5753
- <th pSortableColumn="order" class="text-right">
6014
+ <th class="text-right" pSortableColumn="order">
5754
6015
  {{ 'applications.table.order' | translate }}
5755
6016
  <p-sortIcon field="order"></p-sortIcon>
5756
6017
  </th>
@@ -5823,16 +6084,16 @@ class ApplicationsComponent {
5823
6084
  [severity]="application.enabled ? 'success' : 'danger'"
5824
6085
  [value]="
5825
6086
  (application.enabled ? 'applications.table.yes' : 'applications.table.no') | translate
5826
- "></p-tag>
6087
+ "></p-tag>
5827
6088
  }
5828
6089
  </td>
5829
6090
  <td>
5830
6091
  @if (application._isEditing && application._editModel; as editModel) {
5831
6092
  <p-select
6093
+ appendTo="body"
5832
6094
  class="w-full min-w-36"
5833
6095
  optionLabel="label"
5834
6096
  optionValue="value"
5835
- appendTo="body"
5836
6097
  [options]="serviceTypeOptions"
5837
6098
  [(ngModel)]="editModel.serviceType"></p-select>
5838
6099
  } @else {
@@ -5853,33 +6114,33 @@ class ApplicationsComponent {
5853
6114
  icon="pi pi-check"
5854
6115
  severity="success"
5855
6116
  text="true"
6117
+ tooltipPosition="bottom"
5856
6118
  [disabled]="activeRowAction"
5857
6119
  [pTooltip]="'applications.table.saveTooltip' | translate"
5858
- tooltipPosition="bottom"
5859
6120
  (onClick)="saveApplication(application)"></p-button>
5860
6121
  <p-button
5861
6122
  icon="pi pi-times"
5862
6123
  severity="secondary"
5863
6124
  text="true"
6125
+ tooltipPosition="bottom"
5864
6126
  [disabled]="activeRowAction"
5865
6127
  [pTooltip]="'applications.table.cancelTooltip' | translate"
5866
- tooltipPosition="bottom"
5867
6128
  (onClick)="cancelEdit(application)"></p-button>
5868
6129
  } @else {
5869
6130
  <p-button
5870
6131
  icon="pi pi-pencil"
5871
6132
  text="true"
6133
+ tooltipPosition="bottom"
5872
6134
  [disabled]="activeRowAction"
5873
6135
  [pTooltip]="'applications.table.editTooltip' | translate"
5874
- tooltipPosition="bottom"
5875
6136
  (onClick)="editApplication(application)"></p-button>
5876
6137
  <p-button
5877
6138
  icon="pi pi-trash"
5878
6139
  severity="danger"
5879
6140
  text="true"
6141
+ tooltipPosition="bottom"
5880
6142
  [disabled]="activeRowAction"
5881
6143
  [pTooltip]="'applications.table.deleteTooltip' | translate"
5882
- tooltipPosition="bottom"
5883
6144
  (onClick)="deleteApplication(application)"></p-button>
5884
6145
  }
5885
6146
  </div>
@@ -5889,7 +6150,7 @@ class ApplicationsComponent {
5889
6150
 
5890
6151
  <ng-template pTemplate="emptymessage">
5891
6152
  <tr>
5892
- <td colspan="10" class="py-8 text-center text-surface-500">
6153
+ <td class="py-8 text-center text-surface-500" colspan="10">
5893
6154
  {{ 'applications.empty' | translate }}
5894
6155
  </td>
5895
6156
  </tr>
@@ -5897,7 +6158,7 @@ class ApplicationsComponent {
5897
6158
  </p-table>
5898
6159
  </div>
5899
6160
  </div>
5900
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i1$8.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i2$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i1$8.SortableColumn, selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "component", type: i1$8.SortIcon, selector: "p-sortIcon", inputs: ["field"] }, { kind: "component", type: Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ToolbarModule }, { kind: "component", type: i4$2.Toolbar, selector: "p-toolbar", inputs: ["styleClass", "ariaLabelledBy"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "directive", type: InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i2$4.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
6161
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i1$8.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i2$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i1$8.SortableColumn, selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "component", type: i1$8.SortIcon, selector: "p-sortIcon", inputs: ["field"] }, { kind: "component", type: Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ToolbarModule }, { kind: "component", type: i5$1.Toolbar, selector: "p-toolbar", inputs: ["styleClass", "ariaLabelledBy"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "directive", type: InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i2$4.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
5901
6162
  }
5902
6163
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ApplicationsComponent, decorators: [{
5903
6164
  type: Component,
@@ -6000,7 +6261,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
6000
6261
  {{ 'applications.table.icon' | translate }}
6001
6262
  <p-sortIcon field="icon"></p-sortIcon>
6002
6263
  </th>
6003
- <th pSortableColumn="order" class="text-right">
6264
+ <th class="text-right" pSortableColumn="order">
6004
6265
  {{ 'applications.table.order' | translate }}
6005
6266
  <p-sortIcon field="order"></p-sortIcon>
6006
6267
  </th>
@@ -6073,16 +6334,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
6073
6334
  [severity]="application.enabled ? 'success' : 'danger'"
6074
6335
  [value]="
6075
6336
  (application.enabled ? 'applications.table.yes' : 'applications.table.no') | translate
6076
- "></p-tag>
6337
+ "></p-tag>
6077
6338
  }
6078
6339
  </td>
6079
6340
  <td>
6080
6341
  @if (application._isEditing && application._editModel; as editModel) {
6081
6342
  <p-select
6343
+ appendTo="body"
6082
6344
  class="w-full min-w-36"
6083
6345
  optionLabel="label"
6084
6346
  optionValue="value"
6085
- appendTo="body"
6086
6347
  [options]="serviceTypeOptions"
6087
6348
  [(ngModel)]="editModel.serviceType"></p-select>
6088
6349
  } @else {
@@ -6103,33 +6364,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
6103
6364
  icon="pi pi-check"
6104
6365
  severity="success"
6105
6366
  text="true"
6367
+ tooltipPosition="bottom"
6106
6368
  [disabled]="activeRowAction"
6107
6369
  [pTooltip]="'applications.table.saveTooltip' | translate"
6108
- tooltipPosition="bottom"
6109
6370
  (onClick)="saveApplication(application)"></p-button>
6110
6371
  <p-button
6111
6372
  icon="pi pi-times"
6112
6373
  severity="secondary"
6113
6374
  text="true"
6375
+ tooltipPosition="bottom"
6114
6376
  [disabled]="activeRowAction"
6115
6377
  [pTooltip]="'applications.table.cancelTooltip' | translate"
6116
- tooltipPosition="bottom"
6117
6378
  (onClick)="cancelEdit(application)"></p-button>
6118
6379
  } @else {
6119
6380
  <p-button
6120
6381
  icon="pi pi-pencil"
6121
6382
  text="true"
6383
+ tooltipPosition="bottom"
6122
6384
  [disabled]="activeRowAction"
6123
6385
  [pTooltip]="'applications.table.editTooltip' | translate"
6124
- tooltipPosition="bottom"
6125
6386
  (onClick)="editApplication(application)"></p-button>
6126
6387
  <p-button
6127
6388
  icon="pi pi-trash"
6128
6389
  severity="danger"
6129
6390
  text="true"
6391
+ tooltipPosition="bottom"
6130
6392
  [disabled]="activeRowAction"
6131
6393
  [pTooltip]="'applications.table.deleteTooltip' | translate"
6132
- tooltipPosition="bottom"
6133
6394
  (onClick)="deleteApplication(application)"></p-button>
6134
6395
  }
6135
6396
  </div>
@@ -6139,7 +6400,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
6139
6400
 
6140
6401
  <ng-template pTemplate="emptymessage">
6141
6402
  <tr>
6142
- <td colspan="10" class="py-8 text-center text-surface-500">
6403
+ <td class="py-8 text-center text-surface-500" colspan="10">
6143
6404
  {{ 'applications.empty' | translate }}
6144
6405
  </td>
6145
6406
  </tr>
@@ -6468,7 +6729,9 @@ class DiscussionComponent {
6468
6729
  deleteAttachment(comment, attachment) {
6469
6730
  this.confirmationService.confirm({
6470
6731
  header: this.translateService.instant('discussionComponent.warning'),
6471
- message: this.translateService.instant('discussionComponent.confirmDeleteFile', { fileName: attachment.fileName }),
6732
+ message: this.translateService.instant('discussionComponent.confirmDeleteFile', {
6733
+ fileName: attachment.fileName
6734
+ }),
6472
6735
  icon: 'pi pi-trash',
6473
6736
  rejectButtonProps: {
6474
6737
  label: this.translateService.instant('discussionComponent.cancel'),
@@ -6671,27 +6934,24 @@ class DiscussionComponent {
6671
6934
  }
6672
6935
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: DiscussionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6673
6936
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: DiscussionComponent, isStandalone: true, selector: "discussion", inputs: { objectTypeId: "objectTypeId", objectId: "objectId" }, providers: [ConfirmationService, DiscussionApi], usesOnChanges: true, ngImport: i0, template: `
6674
- <p-confirmDialog/>
6937
+ <p-confirmDialog />
6675
6938
 
6676
6939
  <section class="flex flex-col gap-4">
6677
-
6678
6940
  <p-card class="block">
6679
6941
  <div class="flex flex-col gap-4">
6680
6942
  @if (!previewMode) {
6681
6943
  <textarea
6682
- pTextarea
6683
6944
  class="min-h-28 w-full"
6684
- [autoResize]="true"
6945
+ pTextarea
6685
6946
  rows="5"
6686
- [(ngModel)]="newComment"
6687
- (ngModelChange)="onComposerInput()"
6947
+ [autoResize]="true"
6688
6948
  [placeholder]="'discussionComponent.writeCommentPlaceholder' | translate"
6689
- ></textarea>
6949
+ [(ngModel)]="newComment"
6950
+ (ngModelChange)="onComposerInput()"></textarea>
6690
6951
  } @else {
6691
6952
  <div
6692
6953
  class="markdown-preview min-h-28 rounded-2xl border border-surface-200 bg-surface-50 px-4 py-3 leading-7 dark:border-surface-700 dark:bg-surface-900/40"
6693
- [innerHTML]="renderMarkdown(newComment)"
6694
- ></div>
6954
+ [innerHTML]="renderMarkdown(newComment)"></div>
6695
6955
  }
6696
6956
 
6697
6957
  @if (mentionSuggestions.length > 0) {
@@ -6706,10 +6966,9 @@ class DiscussionComponent {
6706
6966
  <div class="flex flex-col gap-3">
6707
6967
  @for (candidate of mentionSuggestions; track candidate.userId) {
6708
6968
  <button
6709
- type="button"
6710
6969
  class="flex w-full items-center justify-between gap-4 rounded-xl border border-transparent px-3 py-3 text-left transition hover:border-primary-200 hover:bg-primary-50 dark:hover:border-primary-800 dark:hover:bg-primary-950/30"
6711
- (click)="insertMention(candidate)"
6712
- >
6970
+ type="button"
6971
+ (click)="insertMention(candidate)">
6713
6972
  <div class="min-w-0">
6714
6973
  <strong>{{ candidate.displayName }}</strong>
6715
6974
  <small class="mt-1 block text-surface-500">{{ candidate.email }}</small>
@@ -6729,15 +6988,14 @@ class DiscussionComponent {
6729
6988
  <div class="inline-flex items-center gap-2">
6730
6989
  <i class="pi pi-file"></i>
6731
6990
  <span class="break-all">{{ file.name }}</span>
6732
- <p-tag [value]="formatBytes(file.size)" severity="contrast"/>
6991
+ <p-tag severity="contrast" [value]="formatBytes(file.size)" />
6733
6992
  </div>
6734
6993
  <p-button
6735
6994
  icon="pi pi-times"
6736
6995
  severity="danger"
6737
6996
  [rounded]="true"
6738
6997
  [text]="true"
6739
- (onClick)="removePendingFile(file)"
6740
- />
6998
+ (onClick)="removePendingFile(file)" />
6741
6999
  </div>
6742
7000
  }
6743
7001
  </div>
@@ -6745,33 +7003,28 @@ class DiscussionComponent {
6745
7003
 
6746
7004
  <div class="flex flex-col gap-2 sm:flex-row sm:justify-end">
6747
7005
  <p-button
7006
+ severity="secondary"
7007
+ [icon]="previewMode ? 'pi pi-pencil' : 'pi pi-eye'"
6748
7008
  [label]="
6749
- previewMode
6750
- ? ('discussionComponent.edit' | translate)
6751
- : ('discussionComponent.preview' | translate)
7009
+ previewMode ? ('discussionComponent.edit' | translate) : ('discussionComponent.preview' | translate)
6752
7010
  "
6753
- [icon]="previewMode ? 'pi pi-pencil' : 'pi pi-eye'"
6754
- severity="secondary"
6755
7011
  [text]="true"
6756
- (onClick)="previewMode = !previewMode"
6757
- />
7012
+ (onClick)="previewMode = !previewMode" />
6758
7013
 
6759
7014
  <label class="file-trigger">
6760
7015
  <p-button
6761
- [label]="'discussionComponent.attachFiles' | translate"
6762
7016
  icon="pi pi-paperclip"
6763
7017
  severity="secondary"
6764
- [text]="true"
6765
- />
6766
- <input type="file" multiple (change)="onFilesSelected($event)"/>
7018
+ [label]="'discussionComponent.attachFiles' | translate"
7019
+ [text]="true" />
7020
+ <input multiple type="file" (change)="onFilesSelected($event)" />
6767
7021
  </label>
6768
7022
  <p-button
6769
- [label]="'discussionComponent.send' | translate"
6770
7023
  icon="pi pi-send"
6771
- [loading]="submitting"
6772
7024
  [disabled]="submitting || !newComment.trim()"
6773
- (onClick)="createComment()"
6774
- />
7025
+ [label]="'discussionComponent.send' | translate"
7026
+ [loading]="submitting"
7027
+ (onClick)="createComment()" />
6775
7028
  </div>
6776
7029
  </div>
6777
7030
  </p-card>
@@ -6779,7 +7032,7 @@ class DiscussionComponent {
6779
7032
  @if (loading) {
6780
7033
  <div
6781
7034
  class="flex min-h-20 flex-col items-center justify-center gap-3 rounded-2xl border border-surface-200 px-4 py-5 text-surface-500 dark:border-surface-700">
6782
- <p-progressSpinner strokeWidth="4"/>
7035
+ <p-progressSpinner strokeWidth="4" />
6783
7036
  <span>{{ 'discussionComponent.loadingComments' | translate }}</span>
6784
7037
  </div>
6785
7038
  } @else if (comments.length === 0) {
@@ -6795,24 +7048,25 @@ class DiscussionComponent {
6795
7048
  <ng-template pTemplate="content">
6796
7049
  <div class="grid gap-4 md:grid-cols-[3.2rem_minmax(0,1fr)]">
6797
7050
  <div>
6798
- <p-avatar
6799
- [label]="getInitialsFromString(comment.authorDisplayName)"
6800
- shape="circle"
6801
- />
7051
+ <p-avatar shape="circle" [label]="getInitialsFromString(comment.authorDisplayName)" />
6802
7052
  </div>
6803
7053
 
6804
7054
  <div class="flex flex-col gap-2">
6805
7055
  <div class="flex flex-row gap-3 justify-between">
6806
7056
  <div class="flex flex-wrap items-center gap-2">
6807
7057
  <strong>{{ comment.authorDisplayName }}</strong>
6808
- <span
6809
- class="text-sm text-surface-500">{{ comment.createdAt | date: layoutService.dateTimeFormat() }}</span>
7058
+ <span class="text-sm text-surface-500">{{
7059
+ comment.createdAt | date: layoutService.dateTimeFormat()
7060
+ }}</span>
6810
7061
  </div>
6811
7062
 
6812
7063
  <div class="flex flex-wrap items-center justify-end gap-2">
6813
7064
  <div class="flex flex-wrap items-center">
6814
7065
  @if (comment.isEdited) {
6815
- <p-tag [value]="'discussionComponent.edited' | translate" icon="pi pi-pencil" severity="warn"/>
7066
+ <p-tag
7067
+ icon="pi pi-pencil"
7068
+ severity="warn"
7069
+ [value]="'discussionComponent.edited' | translate" />
6816
7070
  }
6817
7071
  </div>
6818
7072
  @if (comment.canEdit) {
@@ -6821,8 +7075,7 @@ class DiscussionComponent {
6821
7075
  severity="secondary"
6822
7076
  [rounded]="true"
6823
7077
  [text]="true"
6824
- (onClick)="startEdit(comment)"
6825
- />
7078
+ (onClick)="startEdit(comment)" />
6826
7079
  }
6827
7080
  @if (comment.historyCount > 0) {
6828
7081
  <p-button
@@ -6830,8 +7083,7 @@ class DiscussionComponent {
6830
7083
  severity="secondary"
6831
7084
  [rounded]="true"
6832
7085
  [text]="true"
6833
- (onClick)="toggleHistory(comment)"
6834
- />
7086
+ (onClick)="toggleHistory(comment)" />
6835
7087
  }
6836
7088
  @if (comment.canDelete) {
6837
7089
  <p-button
@@ -6839,47 +7091,42 @@ class DiscussionComponent {
6839
7091
  severity="danger"
6840
7092
  [rounded]="true"
6841
7093
  [text]="true"
6842
- (onClick)="deleteComment(comment)"
6843
- />
7094
+ (onClick)="deleteComment(comment)" />
6844
7095
  }
6845
7096
  </div>
6846
7097
  </div>
6847
7098
  @if (editingCommentId === comment.commentId) {
6848
7099
  <div class="flex flex-col gap-4">
6849
7100
  <textarea
6850
- pTextarea
6851
7101
  class="min-h-24 w-full"
6852
- [autoResize]="true"
7102
+ pTextarea
6853
7103
  rows="4"
6854
- [(ngModel)]="editContent"
7104
+ [autoResize]="true"
6855
7105
  [placeholder]="'discussionComponent.editCommentPlaceholder' | translate"
6856
- ></textarea>
7106
+ [(ngModel)]="editContent"></textarea>
6857
7107
  <div class="flex flex-col gap-2 sm:flex-row sm:justify-end">
6858
7108
  <div>
6859
7109
  <label class="file-trigger">
6860
7110
  <p-button
6861
- [label]="'discussionComponent.addAttachment' | translate"
6862
7111
  icon="pi pi-paperclip"
6863
- [text]="true"
6864
7112
  severity="secondary"
6865
- />
6866
- <input type="file" multiple (change)="onInlineFilesSelected(comment, $event)"/>
7113
+ [label]="'discussionComponent.addAttachment' | translate"
7114
+ [text]="true" />
7115
+ <input multiple type="file" (change)="onInlineFilesSelected(comment, $event)" />
6867
7116
  </label>
6868
7117
  </div>
6869
7118
 
6870
7119
  <p-button
6871
- [label]="'discussionComponent.save' | translate"
6872
7120
  icon="pi pi-check"
6873
7121
  [disabled]="!editContent.trim()"
6874
- (onClick)="saveEdit(comment)"
6875
- />
7122
+ [label]="'discussionComponent.save' | translate"
7123
+ (onClick)="saveEdit(comment)" />
6876
7124
  <p-button
6877
- [label]="'discussionComponent.cancel' | translate"
6878
7125
  icon="pi pi-times"
6879
7126
  severity="secondary"
7127
+ [label]="'discussionComponent.cancel' | translate"
6880
7128
  [text]="true"
6881
- (onClick)="cancelEdit()"
6882
- />
7129
+ (onClick)="cancelEdit()" />
6883
7130
  </div>
6884
7131
  </div>
6885
7132
  } @else {
@@ -6894,13 +7141,12 @@ class DiscussionComponent {
6894
7141
  <div class="inline-flex items-center gap-2">
6895
7142
  <i class="pi pi-file"></i>
6896
7143
  <button
6897
- type="button"
6898
7144
  class="break-all bg-transparent p-0 text-left font-semibold text-primary-600 transition hover:text-primary-500"
6899
- (click)="downloadAttachment(attachment)"
6900
- >
7145
+ type="button"
7146
+ (click)="downloadAttachment(attachment)">
6901
7147
  {{ attachment.fileName }}
6902
7148
  </button>
6903
- <p-tag [value]="formatBytes(attachment.fileSize)" severity="contrast"/>
7149
+ <p-tag severity="contrast" [value]="formatBytes(attachment.fileSize)" />
6904
7150
  </div>
6905
7151
 
6906
7152
  @if (comment.canEdit) {
@@ -6909,8 +7155,7 @@ class DiscussionComponent {
6909
7155
  severity="danger"
6910
7156
  [rounded]="true"
6911
7157
  [text]="true"
6912
- (onClick)="deleteAttachment(comment, attachment)"
6913
- />
7158
+ (onClick)="deleteAttachment(comment, attachment)" />
6914
7159
  }
6915
7160
  </div>
6916
7161
  }
@@ -6921,11 +7166,10 @@ class DiscussionComponent {
6921
7166
  <div class="flex flex-wrap items-center gap-2">
6922
7167
  @for (reaction of getReactions(comment); track reaction.emojiCode) {
6923
7168
  <p-button
6924
- [label]="reaction.emojiCode + ' ' + reaction.count"
6925
7169
  severity="secondary"
7170
+ [label]="reaction.emojiCode + ' ' + reaction.count"
6926
7171
  [outlined]="false"
6927
- (onClick)="toggleReaction(comment, reaction)"
6928
- />
7172
+ (onClick)="toggleReaction(comment, reaction)" />
6929
7173
  }
6930
7174
 
6931
7175
  @if (!hasUserReaction(comment)) {
@@ -6934,16 +7178,14 @@ class DiscussionComponent {
6934
7178
  severity="secondary"
6935
7179
  [rounded]="true"
6936
7180
  [text]="true"
6937
- (onClick)="reactionPopover.toggle($event)"
6938
- />
7181
+ (onClick)="reactionPopover.toggle($event)" />
6939
7182
  <p-popover #reactionPopover>
6940
7183
  <div class="emoji-popover">
6941
7184
  @for (emoji of emojiPalette; track emoji) {
6942
7185
  <button
6943
- type="button"
6944
7186
  class="emoji-option"
6945
- (click)="reactWithEmoji(comment, emoji, reactionPopover)"
6946
- >
7187
+ type="button"
7188
+ (click)="reactWithEmoji(comment, emoji, reactionPopover)">
6947
7189
  {{ emoji }}
6948
7190
  </button>
6949
7191
  }
@@ -6953,7 +7195,7 @@ class DiscussionComponent {
6953
7195
  </div>
6954
7196
 
6955
7197
  @if (historyByComment[comment.commentId]?.opened) {
6956
- <p-panel class="block" [toggleable]="true" [collapsed]="false">
7198
+ <p-panel class="block" [collapsed]="false" [toggleable]="true">
6957
7199
  <ng-template pTemplate="header">
6958
7200
  <div class="inline-flex items-center gap-2 text-sm font-medium">
6959
7201
  <i class="pi pi-history"></i>
@@ -6963,12 +7205,15 @@ class DiscussionComponent {
6963
7205
 
6964
7206
  @if (historyByComment[comment.commentId]?.loading) {
6965
7207
  <div class="flex items-center gap-3 text-surface-500">
6966
- <p-progressSpinner strokeWidth="4"/>
7208
+ <p-progressSpinner strokeWidth="4" />
6967
7209
  <span>{{ 'discussionComponent.loadingHistory' | translate }}</span>
6968
7210
  </div>
6969
7211
  } @else {
6970
7212
  <div class="flex flex-col gap-4">
6971
- @for (item of historyByComment[comment.commentId]?.items ?? []; track item.commentEditHistoryId) {
7213
+ @for (
7214
+ item of historyByComment[comment.commentId]?.items ?? [];
7215
+ track item.commentEditHistoryId
7216
+ ) {
6972
7217
  <div class="rounded-2xl border border-surface-200 px-4 py-4 dark:border-surface-700">
6973
7218
  <div class="mb-3 inline-flex items-center gap-2 text-sm font-semibold">
6974
7219
  <i class="pi pi-clock"></i>
@@ -6976,18 +7221,16 @@ class DiscussionComponent {
6976
7221
  </div>
6977
7222
  <div class="grid gap-4 lg:grid-cols-2">
6978
7223
  <div class="flex flex-col gap-2">
6979
- <p-tag [value]="'discussionComponent.before' | translate" severity="secondary"/>
7224
+ <p-tag severity="secondary" [value]="'discussionComponent.before' | translate" />
6980
7225
  <div
6981
7226
  class="history-markdown rounded-2xl bg-surface-50 px-4 py-3 leading-7 dark:bg-surface-900/40"
6982
- [innerHTML]="renderMarkdown(item.oldContent)"
6983
- ></div>
7227
+ [innerHTML]="renderMarkdown(item.oldContent)"></div>
6984
7228
  </div>
6985
7229
  <div class="flex flex-col gap-2">
6986
- <p-tag [value]="'discussionComponent.after' | translate" severity="success"/>
7230
+ <p-tag severity="success" [value]="'discussionComponent.after' | translate" />
6987
7231
  <div
6988
7232
  class="history-markdown rounded-2xl bg-surface-50 px-4 py-3 leading-7 dark:bg-surface-900/40"
6989
- [innerHTML]="renderMarkdown(item.newContent)"
6990
- ></div>
7233
+ [innerHTML]="renderMarkdown(item.newContent)"></div>
6991
7234
  </div>
6992
7235
  </div>
6993
7236
  </div>
@@ -7004,7 +7247,7 @@ class DiscussionComponent {
7004
7247
  </div>
7005
7248
  }
7006
7249
  </section>
7007
- `, isInline: true, styles: [":host{display:block}.file-trigger{position:relative;display:inline-flex}.file-trigger input{position:absolute;inset:0;opacity:0;cursor:pointer}.markdown-preview,.history-markdown{word-break:break-word}.markdown-preview :is(h1,h2,h3),.history-markdown :is(h1,h2,h3){margin:0 0 .75rem;font-weight:600}.markdown-preview a,.history-markdown a{color:#2563eb;text-decoration:underline;text-underline-offset:.18em}.emoji-popover{display:flex;flex-wrap:wrap;gap:.5rem;max-width:16rem}.emoji-option{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;font-size:1.25rem;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i2$1.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "directive", type: i2$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i5$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "ngmodule", type: DividerModule }, { kind: "ngmodule", type: PanelModule }, { kind: "component", type: i6.Panel, selector: "p-panel", inputs: ["id", "toggleable", "header", "collapsed", "styleClass", "iconPos", "showHeader", "toggler", "transitionOptions", "toggleButtonProps"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i7.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i4.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i9.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "pipe", type: i1$3.DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7250
+ `, isInline: true, styles: [":host{display:block}.file-trigger{position:relative;display:inline-flex}.file-trigger input{position:absolute;inset:0;opacity:0;cursor:pointer}.markdown-preview,.history-markdown{word-break:break-word}.markdown-preview :is(h1,h2,h3),.history-markdown :is(h1,h2,h3){margin:0 0 .75rem;font-weight:600}.markdown-preview a,.history-markdown a{color:#2563eb;text-decoration:underline;text-underline-offset:.18em}.emoji-popover{display:flex;flex-wrap:wrap;gap:.5rem;max-width:16rem}.emoji-option{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;font-size:1.25rem;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i2$1.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "directive", type: i2$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i5$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "ngmodule", type: DividerModule }, { kind: "ngmodule", type: PanelModule }, { kind: "component", type: i6.Panel, selector: "p-panel", inputs: ["id", "toggleable", "header", "collapsed", "styleClass", "iconPos", "showHeader", "toggler", "transitionOptions", "toggleButtonProps"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i7.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i4.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i9.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "pipe", type: i1$3.DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7008
7251
  }
7009
7252
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: DiscussionComponent, decorators: [{
7010
7253
  type: Component,
@@ -7024,27 +7267,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7024
7267
  DatePipe,
7025
7268
  TranslatePipe
7026
7269
  ], template: `
7027
- <p-confirmDialog/>
7270
+ <p-confirmDialog />
7028
7271
 
7029
7272
  <section class="flex flex-col gap-4">
7030
-
7031
7273
  <p-card class="block">
7032
7274
  <div class="flex flex-col gap-4">
7033
7275
  @if (!previewMode) {
7034
7276
  <textarea
7035
- pTextarea
7036
7277
  class="min-h-28 w-full"
7037
- [autoResize]="true"
7278
+ pTextarea
7038
7279
  rows="5"
7039
- [(ngModel)]="newComment"
7040
- (ngModelChange)="onComposerInput()"
7280
+ [autoResize]="true"
7041
7281
  [placeholder]="'discussionComponent.writeCommentPlaceholder' | translate"
7042
- ></textarea>
7282
+ [(ngModel)]="newComment"
7283
+ (ngModelChange)="onComposerInput()"></textarea>
7043
7284
  } @else {
7044
7285
  <div
7045
7286
  class="markdown-preview min-h-28 rounded-2xl border border-surface-200 bg-surface-50 px-4 py-3 leading-7 dark:border-surface-700 dark:bg-surface-900/40"
7046
- [innerHTML]="renderMarkdown(newComment)"
7047
- ></div>
7287
+ [innerHTML]="renderMarkdown(newComment)"></div>
7048
7288
  }
7049
7289
 
7050
7290
  @if (mentionSuggestions.length > 0) {
@@ -7059,10 +7299,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7059
7299
  <div class="flex flex-col gap-3">
7060
7300
  @for (candidate of mentionSuggestions; track candidate.userId) {
7061
7301
  <button
7062
- type="button"
7063
7302
  class="flex w-full items-center justify-between gap-4 rounded-xl border border-transparent px-3 py-3 text-left transition hover:border-primary-200 hover:bg-primary-50 dark:hover:border-primary-800 dark:hover:bg-primary-950/30"
7064
- (click)="insertMention(candidate)"
7065
- >
7303
+ type="button"
7304
+ (click)="insertMention(candidate)">
7066
7305
  <div class="min-w-0">
7067
7306
  <strong>{{ candidate.displayName }}</strong>
7068
7307
  <small class="mt-1 block text-surface-500">{{ candidate.email }}</small>
@@ -7082,15 +7321,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7082
7321
  <div class="inline-flex items-center gap-2">
7083
7322
  <i class="pi pi-file"></i>
7084
7323
  <span class="break-all">{{ file.name }}</span>
7085
- <p-tag [value]="formatBytes(file.size)" severity="contrast"/>
7324
+ <p-tag severity="contrast" [value]="formatBytes(file.size)" />
7086
7325
  </div>
7087
7326
  <p-button
7088
7327
  icon="pi pi-times"
7089
7328
  severity="danger"
7090
7329
  [rounded]="true"
7091
7330
  [text]="true"
7092
- (onClick)="removePendingFile(file)"
7093
- />
7331
+ (onClick)="removePendingFile(file)" />
7094
7332
  </div>
7095
7333
  }
7096
7334
  </div>
@@ -7098,33 +7336,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7098
7336
 
7099
7337
  <div class="flex flex-col gap-2 sm:flex-row sm:justify-end">
7100
7338
  <p-button
7339
+ severity="secondary"
7340
+ [icon]="previewMode ? 'pi pi-pencil' : 'pi pi-eye'"
7101
7341
  [label]="
7102
- previewMode
7103
- ? ('discussionComponent.edit' | translate)
7104
- : ('discussionComponent.preview' | translate)
7342
+ previewMode ? ('discussionComponent.edit' | translate) : ('discussionComponent.preview' | translate)
7105
7343
  "
7106
- [icon]="previewMode ? 'pi pi-pencil' : 'pi pi-eye'"
7107
- severity="secondary"
7108
7344
  [text]="true"
7109
- (onClick)="previewMode = !previewMode"
7110
- />
7345
+ (onClick)="previewMode = !previewMode" />
7111
7346
 
7112
7347
  <label class="file-trigger">
7113
7348
  <p-button
7114
- [label]="'discussionComponent.attachFiles' | translate"
7115
7349
  icon="pi pi-paperclip"
7116
7350
  severity="secondary"
7117
- [text]="true"
7118
- />
7119
- <input type="file" multiple (change)="onFilesSelected($event)"/>
7351
+ [label]="'discussionComponent.attachFiles' | translate"
7352
+ [text]="true" />
7353
+ <input multiple type="file" (change)="onFilesSelected($event)" />
7120
7354
  </label>
7121
7355
  <p-button
7122
- [label]="'discussionComponent.send' | translate"
7123
7356
  icon="pi pi-send"
7124
- [loading]="submitting"
7125
7357
  [disabled]="submitting || !newComment.trim()"
7126
- (onClick)="createComment()"
7127
- />
7358
+ [label]="'discussionComponent.send' | translate"
7359
+ [loading]="submitting"
7360
+ (onClick)="createComment()" />
7128
7361
  </div>
7129
7362
  </div>
7130
7363
  </p-card>
@@ -7132,7 +7365,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7132
7365
  @if (loading) {
7133
7366
  <div
7134
7367
  class="flex min-h-20 flex-col items-center justify-center gap-3 rounded-2xl border border-surface-200 px-4 py-5 text-surface-500 dark:border-surface-700">
7135
- <p-progressSpinner strokeWidth="4"/>
7368
+ <p-progressSpinner strokeWidth="4" />
7136
7369
  <span>{{ 'discussionComponent.loadingComments' | translate }}</span>
7137
7370
  </div>
7138
7371
  } @else if (comments.length === 0) {
@@ -7148,24 +7381,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7148
7381
  <ng-template pTemplate="content">
7149
7382
  <div class="grid gap-4 md:grid-cols-[3.2rem_minmax(0,1fr)]">
7150
7383
  <div>
7151
- <p-avatar
7152
- [label]="getInitialsFromString(comment.authorDisplayName)"
7153
- shape="circle"
7154
- />
7384
+ <p-avatar shape="circle" [label]="getInitialsFromString(comment.authorDisplayName)" />
7155
7385
  </div>
7156
7386
 
7157
7387
  <div class="flex flex-col gap-2">
7158
7388
  <div class="flex flex-row gap-3 justify-between">
7159
7389
  <div class="flex flex-wrap items-center gap-2">
7160
7390
  <strong>{{ comment.authorDisplayName }}</strong>
7161
- <span
7162
- class="text-sm text-surface-500">{{ comment.createdAt | date: layoutService.dateTimeFormat() }}</span>
7391
+ <span class="text-sm text-surface-500">{{
7392
+ comment.createdAt | date: layoutService.dateTimeFormat()
7393
+ }}</span>
7163
7394
  </div>
7164
7395
 
7165
7396
  <div class="flex flex-wrap items-center justify-end gap-2">
7166
7397
  <div class="flex flex-wrap items-center">
7167
7398
  @if (comment.isEdited) {
7168
- <p-tag [value]="'discussionComponent.edited' | translate" icon="pi pi-pencil" severity="warn"/>
7399
+ <p-tag
7400
+ icon="pi pi-pencil"
7401
+ severity="warn"
7402
+ [value]="'discussionComponent.edited' | translate" />
7169
7403
  }
7170
7404
  </div>
7171
7405
  @if (comment.canEdit) {
@@ -7174,8 +7408,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7174
7408
  severity="secondary"
7175
7409
  [rounded]="true"
7176
7410
  [text]="true"
7177
- (onClick)="startEdit(comment)"
7178
- />
7411
+ (onClick)="startEdit(comment)" />
7179
7412
  }
7180
7413
  @if (comment.historyCount > 0) {
7181
7414
  <p-button
@@ -7183,8 +7416,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7183
7416
  severity="secondary"
7184
7417
  [rounded]="true"
7185
7418
  [text]="true"
7186
- (onClick)="toggleHistory(comment)"
7187
- />
7419
+ (onClick)="toggleHistory(comment)" />
7188
7420
  }
7189
7421
  @if (comment.canDelete) {
7190
7422
  <p-button
@@ -7192,47 +7424,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7192
7424
  severity="danger"
7193
7425
  [rounded]="true"
7194
7426
  [text]="true"
7195
- (onClick)="deleteComment(comment)"
7196
- />
7427
+ (onClick)="deleteComment(comment)" />
7197
7428
  }
7198
7429
  </div>
7199
7430
  </div>
7200
7431
  @if (editingCommentId === comment.commentId) {
7201
7432
  <div class="flex flex-col gap-4">
7202
7433
  <textarea
7203
- pTextarea
7204
7434
  class="min-h-24 w-full"
7205
- [autoResize]="true"
7435
+ pTextarea
7206
7436
  rows="4"
7207
- [(ngModel)]="editContent"
7437
+ [autoResize]="true"
7208
7438
  [placeholder]="'discussionComponent.editCommentPlaceholder' | translate"
7209
- ></textarea>
7439
+ [(ngModel)]="editContent"></textarea>
7210
7440
  <div class="flex flex-col gap-2 sm:flex-row sm:justify-end">
7211
7441
  <div>
7212
7442
  <label class="file-trigger">
7213
7443
  <p-button
7214
- [label]="'discussionComponent.addAttachment' | translate"
7215
7444
  icon="pi pi-paperclip"
7216
- [text]="true"
7217
7445
  severity="secondary"
7218
- />
7219
- <input type="file" multiple (change)="onInlineFilesSelected(comment, $event)"/>
7446
+ [label]="'discussionComponent.addAttachment' | translate"
7447
+ [text]="true" />
7448
+ <input multiple type="file" (change)="onInlineFilesSelected(comment, $event)" />
7220
7449
  </label>
7221
7450
  </div>
7222
7451
 
7223
7452
  <p-button
7224
- [label]="'discussionComponent.save' | translate"
7225
7453
  icon="pi pi-check"
7226
7454
  [disabled]="!editContent.trim()"
7227
- (onClick)="saveEdit(comment)"
7228
- />
7455
+ [label]="'discussionComponent.save' | translate"
7456
+ (onClick)="saveEdit(comment)" />
7229
7457
  <p-button
7230
- [label]="'discussionComponent.cancel' | translate"
7231
7458
  icon="pi pi-times"
7232
7459
  severity="secondary"
7460
+ [label]="'discussionComponent.cancel' | translate"
7233
7461
  [text]="true"
7234
- (onClick)="cancelEdit()"
7235
- />
7462
+ (onClick)="cancelEdit()" />
7236
7463
  </div>
7237
7464
  </div>
7238
7465
  } @else {
@@ -7247,13 +7474,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7247
7474
  <div class="inline-flex items-center gap-2">
7248
7475
  <i class="pi pi-file"></i>
7249
7476
  <button
7250
- type="button"
7251
7477
  class="break-all bg-transparent p-0 text-left font-semibold text-primary-600 transition hover:text-primary-500"
7252
- (click)="downloadAttachment(attachment)"
7253
- >
7478
+ type="button"
7479
+ (click)="downloadAttachment(attachment)">
7254
7480
  {{ attachment.fileName }}
7255
7481
  </button>
7256
- <p-tag [value]="formatBytes(attachment.fileSize)" severity="contrast"/>
7482
+ <p-tag severity="contrast" [value]="formatBytes(attachment.fileSize)" />
7257
7483
  </div>
7258
7484
 
7259
7485
  @if (comment.canEdit) {
@@ -7262,8 +7488,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7262
7488
  severity="danger"
7263
7489
  [rounded]="true"
7264
7490
  [text]="true"
7265
- (onClick)="deleteAttachment(comment, attachment)"
7266
- />
7491
+ (onClick)="deleteAttachment(comment, attachment)" />
7267
7492
  }
7268
7493
  </div>
7269
7494
  }
@@ -7274,11 +7499,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7274
7499
  <div class="flex flex-wrap items-center gap-2">
7275
7500
  @for (reaction of getReactions(comment); track reaction.emojiCode) {
7276
7501
  <p-button
7277
- [label]="reaction.emojiCode + ' ' + reaction.count"
7278
7502
  severity="secondary"
7503
+ [label]="reaction.emojiCode + ' ' + reaction.count"
7279
7504
  [outlined]="false"
7280
- (onClick)="toggleReaction(comment, reaction)"
7281
- />
7505
+ (onClick)="toggleReaction(comment, reaction)" />
7282
7506
  }
7283
7507
 
7284
7508
  @if (!hasUserReaction(comment)) {
@@ -7287,16 +7511,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7287
7511
  severity="secondary"
7288
7512
  [rounded]="true"
7289
7513
  [text]="true"
7290
- (onClick)="reactionPopover.toggle($event)"
7291
- />
7514
+ (onClick)="reactionPopover.toggle($event)" />
7292
7515
  <p-popover #reactionPopover>
7293
7516
  <div class="emoji-popover">
7294
7517
  @for (emoji of emojiPalette; track emoji) {
7295
7518
  <button
7296
- type="button"
7297
7519
  class="emoji-option"
7298
- (click)="reactWithEmoji(comment, emoji, reactionPopover)"
7299
- >
7520
+ type="button"
7521
+ (click)="reactWithEmoji(comment, emoji, reactionPopover)">
7300
7522
  {{ emoji }}
7301
7523
  </button>
7302
7524
  }
@@ -7306,7 +7528,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7306
7528
  </div>
7307
7529
 
7308
7530
  @if (historyByComment[comment.commentId]?.opened) {
7309
- <p-panel class="block" [toggleable]="true" [collapsed]="false">
7531
+ <p-panel class="block" [collapsed]="false" [toggleable]="true">
7310
7532
  <ng-template pTemplate="header">
7311
7533
  <div class="inline-flex items-center gap-2 text-sm font-medium">
7312
7534
  <i class="pi pi-history"></i>
@@ -7316,12 +7538,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7316
7538
 
7317
7539
  @if (historyByComment[comment.commentId]?.loading) {
7318
7540
  <div class="flex items-center gap-3 text-surface-500">
7319
- <p-progressSpinner strokeWidth="4"/>
7541
+ <p-progressSpinner strokeWidth="4" />
7320
7542
  <span>{{ 'discussionComponent.loadingHistory' | translate }}</span>
7321
7543
  </div>
7322
7544
  } @else {
7323
7545
  <div class="flex flex-col gap-4">
7324
- @for (item of historyByComment[comment.commentId]?.items ?? []; track item.commentEditHistoryId) {
7546
+ @for (
7547
+ item of historyByComment[comment.commentId]?.items ?? [];
7548
+ track item.commentEditHistoryId
7549
+ ) {
7325
7550
  <div class="rounded-2xl border border-surface-200 px-4 py-4 dark:border-surface-700">
7326
7551
  <div class="mb-3 inline-flex items-center gap-2 text-sm font-semibold">
7327
7552
  <i class="pi pi-clock"></i>
@@ -7329,18 +7554,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7329
7554
  </div>
7330
7555
  <div class="grid gap-4 lg:grid-cols-2">
7331
7556
  <div class="flex flex-col gap-2">
7332
- <p-tag [value]="'discussionComponent.before' | translate" severity="secondary"/>
7557
+ <p-tag severity="secondary" [value]="'discussionComponent.before' | translate" />
7333
7558
  <div
7334
7559
  class="history-markdown rounded-2xl bg-surface-50 px-4 py-3 leading-7 dark:bg-surface-900/40"
7335
- [innerHTML]="renderMarkdown(item.oldContent)"
7336
- ></div>
7560
+ [innerHTML]="renderMarkdown(item.oldContent)"></div>
7337
7561
  </div>
7338
7562
  <div class="flex flex-col gap-2">
7339
- <p-tag [value]="'discussionComponent.after' | translate" severity="success"/>
7563
+ <p-tag severity="success" [value]="'discussionComponent.after' | translate" />
7340
7564
  <div
7341
7565
  class="history-markdown rounded-2xl bg-surface-50 px-4 py-3 leading-7 dark:bg-surface-900/40"
7342
- [innerHTML]="renderMarkdown(item.newContent)"
7343
- ></div>
7566
+ [innerHTML]="renderMarkdown(item.newContent)"></div>
7344
7567
  </div>
7345
7568
  </div>
7346
7569
  </div>
@@ -7445,7 +7668,7 @@ class IframeModuleComponent extends BaseModuleComponent {
7445
7668
  placeholder="{{ 'iframe-module.iframeModule.urlPlaceholder' | translate }}"
7446
7669
  qa-id="iframe-module-settings-site-url-input"
7447
7670
  type="text"
7448
- [(ngModel)]="settings.url"/>
7671
+ [(ngModel)]="settings.url" />
7449
7672
  </div>
7450
7673
  <div class="flex justify-end">
7451
7674
  <p-button
@@ -7459,7 +7682,7 @@ class IframeModuleComponent extends BaseModuleComponent {
7459
7682
  </div>
7460
7683
  </div>
7461
7684
  } @else if (isSecurity) {
7462
- <security [controller]="controller" [id]="id"/>
7685
+ <security [controller]="controller" [id]="id" />
7463
7686
  }
7464
7687
  `, isInline: true, dependencies: [{ kind: "component", type: SecurityComponent, selector: "security", inputs: ["id", "controller"] }, { kind: "directive", type: InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
7465
7688
  }
@@ -7489,7 +7712,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7489
7712
  placeholder="{{ 'iframe-module.iframeModule.urlPlaceholder' | translate }}"
7490
7713
  qa-id="iframe-module-settings-site-url-input"
7491
7714
  type="text"
7492
- [(ngModel)]="settings.url"/>
7715
+ [(ngModel)]="settings.url" />
7493
7716
  </div>
7494
7717
  <div class="flex justify-end">
7495
7718
  <p-button
@@ -7503,7 +7726,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7503
7726
  </div>
7504
7727
  </div>
7505
7728
  } @else if (isSecurity) {
7506
- <security [controller]="controller" [id]="id"/>
7729
+ <security [controller]="controller" [id]="id" />
7507
7730
  }
7508
7731
  `
7509
7732
  }]
@@ -7512,6 +7735,563 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7512
7735
  args: ['iframe']
7513
7736
  }] } });
7514
7737
 
7738
+ const OIP_EXTENSION_EVENTS = {
7739
+ contextChange: 'oip:context-change',
7740
+ titleChange: 'oip:title-change',
7741
+ settingsChange: 'oip:settings-change',
7742
+ notify: 'oip:notify',
7743
+ navigate: 'oip:navigate',
7744
+ error: 'oip:error'
7745
+ };
7746
+ function emitOipTitleChange(element, title) {
7747
+ element.dispatchEvent(new CustomEvent(OIP_EXTENSION_EVENTS.titleChange, { detail: title, bubbles: true }));
7748
+ }
7749
+ function emitOipSettingsChange(element, settings) {
7750
+ element.dispatchEvent(new CustomEvent(OIP_EXTENSION_EVENTS.settingsChange, { detail: settings, bubbles: true }));
7751
+ }
7752
+ function emitOipNotify(element, notification) {
7753
+ element.dispatchEvent(new CustomEvent(OIP_EXTENSION_EVENTS.notify, { detail: notification, bubbles: true }));
7754
+ }
7755
+ function emitOipNavigate(element, navigation) {
7756
+ element.dispatchEvent(new CustomEvent(OIP_EXTENSION_EVENTS.navigate, { detail: navigation, bubbles: true }));
7757
+ }
7758
+ function emitOipError(element, error) {
7759
+ element.dispatchEvent(new CustomEvent(OIP_EXTENSION_EVENTS.error, { detail: error, bubbles: true }));
7760
+ }
7761
+ function emitOipContextChange(element, context) {
7762
+ element.dispatchEvent(new CustomEvent(OIP_EXTENSION_EVENTS.contextChange, { detail: context }));
7763
+ }
7764
+
7765
+ class ExtensionLoaderService {
7766
+ constructor() {
7767
+ this.loadedScripts = new Map();
7768
+ }
7769
+ loadScript(scriptUrl) {
7770
+ const existing = this.loadedScripts.get(scriptUrl);
7771
+ if (existing) {
7772
+ return existing;
7773
+ }
7774
+ const loadPromise = new Promise((resolve, reject) => {
7775
+ const script = document.createElement('script');
7776
+ script.type = 'module';
7777
+ script.src = scriptUrl;
7778
+ script.async = true;
7779
+ script.onload = () => resolve();
7780
+ script.onerror = () => {
7781
+ this.loadedScripts.delete(scriptUrl);
7782
+ reject(new Error(`Extension script could not be loaded: ${scriptUrl}`));
7783
+ };
7784
+ document.head.appendChild(script);
7785
+ });
7786
+ this.loadedScripts.set(scriptUrl, loadPromise);
7787
+ return loadPromise;
7788
+ }
7789
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ExtensionLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7790
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ExtensionLoaderService, providedIn: 'root' }); }
7791
+ }
7792
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ExtensionLoaderService, decorators: [{
7793
+ type: Injectable,
7794
+ args: [{ providedIn: 'root' }]
7795
+ }] });
7796
+
7797
+ class CustomElementExtensionModuleHostComponent extends BaseModuleComponent {
7798
+ set extensionContainer(element) {
7799
+ this.container = element;
7800
+ this.queueRenderExtension();
7801
+ }
7802
+ get showContent() {
7803
+ return this.isActiveTab('content');
7804
+ }
7805
+ get showSettings() {
7806
+ return this.isActiveTab('settings');
7807
+ }
7808
+ get showSecurity() {
7809
+ return this.isActiveTab('security');
7810
+ }
7811
+ get extensionKey() {
7812
+ return this.route.snapshot.paramMap.get('extensionKey') ?? '';
7813
+ }
7814
+ constructor() {
7815
+ super();
7816
+ this.extensionLoader = inject(ExtensionLoaderService);
7817
+ this.router = inject(Router);
7818
+ this.hostChangeDetectorRef = inject(ChangeDetectorRef);
7819
+ this.extensionModulesApi = inject(ExtensionModulesApi);
7820
+ this.removeListeners = [];
7821
+ this.renderQueued = false;
7822
+ this.activeTabSyncQueued = false;
7823
+ this.activeTabId = this.getCurrentActiveTabId();
7824
+ this.destroyed = false;
7825
+ this.loadError = null;
7826
+ this.subscriptions.push(this.topBarService.activeId$.subscribe(() => {
7827
+ this.queueActiveTabSync();
7828
+ }));
7829
+ }
7830
+ async onModuleInstanceChange() {
7831
+ await this.loadExtensionMetadata();
7832
+ await this.renderExtension();
7833
+ }
7834
+ onSecurityRightsChange() {
7835
+ this.updateExtensionContext();
7836
+ }
7837
+ ngOnDestroy() {
7838
+ this.destroyed = true;
7839
+ this.destroyExtensionElement();
7840
+ super.ngOnDestroy();
7841
+ }
7842
+ async reloadExtension() {
7843
+ this.loadError = null;
7844
+ this.extensionMetadata = undefined;
7845
+ await this.loadExtensionMetadata();
7846
+ await this.renderExtension();
7847
+ }
7848
+ async loadExtensionMetadata() {
7849
+ if (this.extensionMetadata) {
7850
+ return;
7851
+ }
7852
+ if (!this.extensionKey) {
7853
+ this.loadError = 'Extension key is missing.';
7854
+ return;
7855
+ }
7856
+ this.extensionMetadata = (await this.extensionModulesApi.getExtensionModuleByKey({
7857
+ extensionKey: this.extensionKey
7858
+ }));
7859
+ await this.loadExtensionTranslations(this.extensionMetadata);
7860
+ }
7861
+ async renderExtension() {
7862
+ if (this.destroyed || !this.container || !this.extensionMetadata || !this.showContent) {
7863
+ return;
7864
+ }
7865
+ try {
7866
+ this.loadError = null;
7867
+ this.destroyExtensionElement();
7868
+ await this.renderCustomElementExtension(this.extensionMetadata);
7869
+ }
7870
+ catch (error) {
7871
+ this.loadError = error instanceof Error ? error.message : 'Extension could not be loaded.';
7872
+ this.msgService.error(error);
7873
+ }
7874
+ }
7875
+ queueRenderExtension() {
7876
+ if (this.renderQueued) {
7877
+ return;
7878
+ }
7879
+ this.renderQueued = true;
7880
+ queueMicrotask(() => {
7881
+ this.renderQueued = false;
7882
+ void this.renderExtension();
7883
+ });
7884
+ }
7885
+ isActiveTab(id) {
7886
+ return this.activeTabId === id;
7887
+ }
7888
+ queueActiveTabSync() {
7889
+ const nextActiveTabId = this.getCurrentActiveTabId();
7890
+ if (this.activeTabId === nextActiveTabId || this.activeTabSyncQueued) {
7891
+ return;
7892
+ }
7893
+ this.activeTabSyncQueued = true;
7894
+ queueMicrotask(() => {
7895
+ this.activeTabSyncQueued = false;
7896
+ if (this.destroyed) {
7897
+ return;
7898
+ }
7899
+ this.activeTabId = this.getCurrentActiveTabId();
7900
+ this.hostChangeDetectorRef.detectChanges();
7901
+ if (this.showContent) {
7902
+ this.queueRenderExtension();
7903
+ }
7904
+ else {
7905
+ this.destroyExtensionElement();
7906
+ }
7907
+ });
7908
+ }
7909
+ getCurrentActiveTabId() {
7910
+ return this.topBarService.activeTopBarItem?.id ?? 'content';
7911
+ }
7912
+ updateExtensionContext() {
7913
+ if (!this.extensionElement || !this.extensionMetadata?.extensionKey) {
7914
+ return;
7915
+ }
7916
+ const context = {
7917
+ moduleInstanceId: this.id,
7918
+ extensionKey: this.extensionMetadata.extensionKey,
7919
+ apiBasePath: this.buildUrl(`api/extensions/${this.extensionMetadata.extensionKey}`),
7920
+ settings: this.settings,
7921
+ locale: this.layoutService.language(),
7922
+ theme: this.layoutService.layoutConfig(),
7923
+ user: this.securityService.getCurrentUser(),
7924
+ activeTabId: this.topBarService.activeId ?? 'content',
7925
+ permissions: {
7926
+ canRead: this.canRead,
7927
+ canEdit: this.canEdit,
7928
+ canDelete: this.canDelete
7929
+ }
7930
+ };
7931
+ Object.assign(this.extensionElement, { oipContext: context });
7932
+ emitOipContextChange(this.extensionElement, context);
7933
+ }
7934
+ attachListeners(element) {
7935
+ this.addListener(element, OIP_EXTENSION_EVENTS.titleChange, (event) => {
7936
+ const title = event.detail;
7937
+ if (title) {
7938
+ this.appTitleService.setTitle(title);
7939
+ }
7940
+ });
7941
+ this.addListener(element, OIP_EXTENSION_EVENTS.settingsChange, (event) => {
7942
+ this.settings = event.detail;
7943
+ void this.saveSettings(this.settings);
7944
+ });
7945
+ this.addListener(element, OIP_EXTENSION_EVENTS.notify, (event) => {
7946
+ const detail = event.detail;
7947
+ const severity = detail?.severity ?? 'info';
7948
+ const message = detail?.detail ?? detail?.summary ?? '';
7949
+ switch (severity) {
7950
+ case 'success':
7951
+ this.msgService.success(message, detail?.summary);
7952
+ break;
7953
+ case 'warn':
7954
+ this.msgService.warn(message, detail?.summary);
7955
+ break;
7956
+ case 'error':
7957
+ this.msgService.error(message, detail?.summary);
7958
+ break;
7959
+ default:
7960
+ this.msgService.info(message, detail?.summary);
7961
+ }
7962
+ });
7963
+ this.addListener(element, OIP_EXTENSION_EVENTS.navigate, (event) => {
7964
+ const detail = event.detail;
7965
+ if (detail?.commands) {
7966
+ void this.router.navigate(detail.commands);
7967
+ }
7968
+ else if (detail?.url) {
7969
+ void this.router.navigateByUrl(detail.url);
7970
+ }
7971
+ });
7972
+ this.addListener(element, OIP_EXTENSION_EVENTS.error, (event) => {
7973
+ this.msgService.error(event.detail);
7974
+ });
7975
+ }
7976
+ buildUrl(path) {
7977
+ const baseUrl = document.getElementsByTagName('base')[0]?.href ?? `${window.location.origin}/`;
7978
+ return `${baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`}${path}`;
7979
+ }
7980
+ async loadExtensionTranslations(metadata) {
7981
+ const extensionKey = metadata.extensionKey ?? this.extensionKey;
7982
+ const apiBaseUrl = metadata.apiBaseUrl;
7983
+ if (!extensionKey || !apiBaseUrl) {
7984
+ return;
7985
+ }
7986
+ const lang = this.translateService.currentLang || this.layoutService.language() || 'en';
7987
+ const url = this.buildExtensionI18nUrl(apiBaseUrl, extensionKey, lang);
7988
+ try {
7989
+ await firstValueFrom(this.l10nService.loadTranslationsFromUrl(extensionKey, url, lang));
7990
+ }
7991
+ catch (error) {
7992
+ console.error(`No translations found for extension ${extensionKey}.${lang}.json`, error);
7993
+ }
7994
+ }
7995
+ buildExtensionI18nUrl(apiBaseUrl, extensionKey, lang) {
7996
+ return `${apiBaseUrl.replace(/\/+$/, '')}/assets/i18n/${encodeURIComponent(extensionKey)}.${encodeURIComponent(lang)}.json`;
7997
+ }
7998
+ addListener(element, eventName, listener) {
7999
+ element.addEventListener(eventName, listener);
8000
+ this.removeListeners.push(() => element.removeEventListener(eventName, listener));
8001
+ }
8002
+ destroyExtensionElement() {
8003
+ this.removeListeners.forEach((remove) => remove());
8004
+ this.removeListeners = [];
8005
+ this.extensionElement?.remove();
8006
+ this.extensionElement = undefined;
8007
+ }
8008
+ async renderCustomElementExtension(metadata) {
8009
+ const { elementName, scriptUrl } = metadata;
8010
+ if (!this.container || !elementName || !scriptUrl) {
8011
+ this.loadError = 'Custom Element extension metadata is incomplete.';
8012
+ return;
8013
+ }
8014
+ await this.extensionLoader.loadScript(scriptUrl);
8015
+ await customElements.whenDefined(elementName);
8016
+ const element = document.createElement(elementName);
8017
+ this.extensionElement = element;
8018
+ this.attachListeners(element);
8019
+ this.container.nativeElement.appendChild(element);
8020
+ this.updateExtensionContext();
8021
+ }
8022
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: CustomElementExtensionModuleHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8023
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: CustomElementExtensionModuleHostComponent, isStandalone: true, selector: "ng-component", providers: [ExtensionModulesApi], viewQueries: [{ propertyName: "extensionContainer", first: true, predicate: ["extensionContainer"], descendants: true }], usesInheritance: true, ngImport: i0, template: `
8024
+ @if (showContent) {
8025
+ <div class="min-h-[calc(100vh-10rem)] w-full">
8026
+ @if (loadError) {
8027
+ <div class="card flex flex-col gap-4">
8028
+ <div class="font-semibold text-xl">{{ loadError }}</div>
8029
+ <p-button icon="pi pi-refresh" label="Retry" (onClick)="reloadExtension()"></p-button>
8030
+ </div>
8031
+ } @else {
8032
+ <div #extensionContainer class="w-full"></div>
8033
+ }
8034
+ </div>
8035
+ } @else if (showSettings) {
8036
+ <div class="flex flex-col md:flex-row gap-8">
8037
+ <div class="md:w-1/2">
8038
+ <div class="card flex flex-col gap-4">
8039
+ <div class="font-semibold text-xl">{{ 'baseComponent.settings' | translate }}</div>
8040
+ <p-button icon="pi pi-save" label="Save" (onClick)="saveSettings(settings)"></p-button>
8041
+ </div>
8042
+ </div>
8043
+ </div>
8044
+ } @else if (showSecurity) {
8045
+ <security [controller]="controller" [id]="id" />
8046
+ }
8047
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SecurityComponent, selector: "security", inputs: ["id", "controller"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
8048
+ }
8049
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: CustomElementExtensionModuleHostComponent, decorators: [{
8050
+ type: Component,
8051
+ args: [{
8052
+ standalone: true,
8053
+ imports: [CommonModule, SecurityComponent, TranslatePipe, Button],
8054
+ providers: [ExtensionModulesApi],
8055
+ template: `
8056
+ @if (showContent) {
8057
+ <div class="min-h-[calc(100vh-10rem)] w-full">
8058
+ @if (loadError) {
8059
+ <div class="card flex flex-col gap-4">
8060
+ <div class="font-semibold text-xl">{{ loadError }}</div>
8061
+ <p-button icon="pi pi-refresh" label="Retry" (onClick)="reloadExtension()"></p-button>
8062
+ </div>
8063
+ } @else {
8064
+ <div #extensionContainer class="w-full"></div>
8065
+ }
8066
+ </div>
8067
+ } @else if (showSettings) {
8068
+ <div class="flex flex-col md:flex-row gap-8">
8069
+ <div class="md:w-1/2">
8070
+ <div class="card flex flex-col gap-4">
8071
+ <div class="font-semibold text-xl">{{ 'baseComponent.settings' | translate }}</div>
8072
+ <p-button icon="pi pi-save" label="Save" (onClick)="saveSettings(settings)"></p-button>
8073
+ </div>
8074
+ </div>
8075
+ </div>
8076
+ } @else if (showSecurity) {
8077
+ <security [controller]="controller" [id]="id" />
8078
+ }
8079
+ `
8080
+ }]
8081
+ }], ctorParameters: () => [], propDecorators: { extensionContainer: [{
8082
+ type: ViewChild,
8083
+ args: ['extensionContainer']
8084
+ }] } });
8085
+
8086
+ class ExtensionModuleHostComponent extends BaseModuleComponent {
8087
+ set extensionViewContainer(viewContainer) {
8088
+ this.viewContainer = viewContainer;
8089
+ this.queueRenderExtension();
8090
+ }
8091
+ get extensionKey() {
8092
+ return this.route.snapshot.paramMap.get('extensionKey') ?? '';
8093
+ }
8094
+ constructor() {
8095
+ super();
8096
+ this.injector = inject(Injector);
8097
+ this.environmentInjector = inject(EnvironmentInjector);
8098
+ this.extensionModulesApi = inject(ExtensionModulesApi);
8099
+ this.renderQueued = false;
8100
+ this.destroyed = false;
8101
+ this.loadError = null;
8102
+ this.subscriptions.push(this.topBarService.activeId$.subscribe(() => {
8103
+ this.updateExtensionContext();
8104
+ }));
8105
+ }
8106
+ async reloadExtension() {
8107
+ this.loadError = null;
8108
+ this.extensionMetadata = undefined;
8109
+ await this.loadExtensionMetadata();
8110
+ await this.renderExtension();
8111
+ }
8112
+ async onModuleInstanceChange() {
8113
+ await this.loadExtensionMetadata();
8114
+ await this.renderExtension();
8115
+ }
8116
+ onSecurityRightsChange() {
8117
+ this.updateExtensionContext();
8118
+ }
8119
+ ngOnDestroy() {
8120
+ this.destroyed = true;
8121
+ this.destroyExtensionComponent();
8122
+ super.ngOnDestroy();
8123
+ }
8124
+ async loadExtensionMetadata() {
8125
+ if (this.extensionMetadata) {
8126
+ return;
8127
+ }
8128
+ if (!this.extensionKey) {
8129
+ this.loadError = 'Extension key is missing.';
8130
+ return;
8131
+ }
8132
+ this.extensionMetadata = (await this.extensionModulesApi.getExtensionModuleByKey({
8133
+ extensionKey: this.extensionKey
8134
+ }));
8135
+ await this.loadExtensionTranslations(this.extensionMetadata);
8136
+ }
8137
+ async renderExtension() {
8138
+ if (this.destroyed || !this.viewContainer) {
8139
+ return;
8140
+ }
8141
+ try {
8142
+ this.loadError = null;
8143
+ await this.loadExtensionMetadata();
8144
+ if (!this.extensionMetadata) {
8145
+ return;
8146
+ }
8147
+ this.destroyExtensionComponent();
8148
+ if (this.extensionMetadata.loadType === 'moduleFederation') {
8149
+ await this.renderFederatedExtension(this.extensionMetadata);
8150
+ }
8151
+ else {
8152
+ this.extensionComponent = this.viewContainer.createComponent(CustomElementExtensionModuleHostComponent, {
8153
+ injector: this.injector,
8154
+ environmentInjector: this.environmentInjector
8155
+ });
8156
+ }
8157
+ }
8158
+ catch (error) {
8159
+ this.loadError = error instanceof Error ? error.message : 'Extension could not be loaded.';
8160
+ console.error(error);
8161
+ }
8162
+ }
8163
+ queueRenderExtension() {
8164
+ if (this.renderQueued) {
8165
+ return;
8166
+ }
8167
+ this.renderQueued = true;
8168
+ queueMicrotask(() => {
8169
+ this.renderQueued = false;
8170
+ void this.renderExtension();
8171
+ });
8172
+ }
8173
+ destroyExtensionComponent() {
8174
+ this.extensionComponent?.destroy();
8175
+ this.extensionComponent = undefined;
8176
+ this.extensionElement = undefined;
8177
+ this.viewContainer?.clear();
8178
+ }
8179
+ async renderFederatedExtension(metadata) {
8180
+ const { remoteEntryUrl, exposedModule, componentName } = metadata;
8181
+ if (!remoteEntryUrl || !exposedModule || !componentName) {
8182
+ this.loadError = 'Module Federation extension metadata is incomplete.';
8183
+ return;
8184
+ }
8185
+ const remoteModule = await loadRemoteModule({
8186
+ type: 'module',
8187
+ remoteEntry: remoteEntryUrl,
8188
+ exposedModule
8189
+ });
8190
+ const component = remoteModule[componentName];
8191
+ if (!component) {
8192
+ throw new Error(`Remote component '${componentName}' was not exported by '${exposedModule}'.`);
8193
+ }
8194
+ this.extensionComponent = this.viewContainer?.createComponent(component, {
8195
+ environmentInjector: this.environmentInjector
8196
+ });
8197
+ this.extensionElement = this.extensionComponent?.location.nativeElement;
8198
+ this.updateExtensionContext();
8199
+ }
8200
+ async loadExtensionTranslations(metadata) {
8201
+ const extensionKey = metadata.extensionKey ?? this.extensionKey;
8202
+ const apiBaseUrl = metadata.apiBaseUrl;
8203
+ if (!extensionKey || !apiBaseUrl) {
8204
+ return;
8205
+ }
8206
+ const lang = this.translateService.currentLang || this.layoutService.language() || 'en';
8207
+ for (const namespace of this.getExtensionTranslationNamespaces(metadata, extensionKey)) {
8208
+ const url = this.buildExtensionI18nUrl(apiBaseUrl, namespace, lang);
8209
+ try {
8210
+ await firstValueFrom(this.l10nService.loadTranslationsFromUrl(namespace, url, lang));
8211
+ return;
8212
+ }
8213
+ catch (error) {
8214
+ console.error(`No translations found for extension ${namespace}.${lang}.json`, error);
8215
+ }
8216
+ }
8217
+ }
8218
+ buildExtensionI18nUrl(apiBaseUrl, extensionKey, lang) {
8219
+ return `${apiBaseUrl.replace(/\/+$/, '')}/assets/i18n/${encodeURIComponent(extensionKey)}.${encodeURIComponent(lang)}.json`;
8220
+ }
8221
+ getExtensionTranslationNamespaces(metadata, extensionKey) {
8222
+ const namespaces = [extensionKey];
8223
+ const componentNamespace = this.toKebabCase(metadata.componentName?.replace(/Component$/, '') ?? '');
8224
+ if (componentNamespace && !namespaces.includes(componentNamespace)) {
8225
+ namespaces.push(componentNamespace);
8226
+ }
8227
+ return namespaces;
8228
+ }
8229
+ toKebabCase(value) {
8230
+ return value
8231
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
8232
+ .replace(/[\s_]+/g, '-')
8233
+ .toLowerCase();
8234
+ }
8235
+ updateExtensionContext() {
8236
+ if (!this.extensionElement || !this.extensionMetadata?.extensionKey) {
8237
+ return;
8238
+ }
8239
+ const context = {
8240
+ moduleInstanceId: this.id,
8241
+ extensionKey: this.extensionMetadata.extensionKey,
8242
+ apiBasePath: this.buildUrl(`api/extensions/${this.extensionMetadata.extensionKey}`),
8243
+ settings: this.settings,
8244
+ locale: this.layoutService.language(),
8245
+ theme: this.layoutService.layoutConfig(),
8246
+ user: this.securityService.getCurrentUser(),
8247
+ activeTabId: this.topBarService.activeId ?? 'content',
8248
+ permissions: {
8249
+ canRead: this.canRead,
8250
+ canEdit: this.canEdit,
8251
+ canDelete: this.canDelete
8252
+ }
8253
+ };
8254
+ Object.assign(this.extensionElement, { oipContext: context });
8255
+ emitOipContextChange(this.extensionElement, context);
8256
+ }
8257
+ buildUrl(path) {
8258
+ const baseUrl = document.getElementsByTagName('base')[0]?.href ?? `${window.location.origin}/`;
8259
+ return `${baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`}${path}`;
8260
+ }
8261
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ExtensionModuleHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8262
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: ExtensionModuleHostComponent, isStandalone: true, selector: "ng-component", providers: [ExtensionModulesApi], viewQueries: [{ propertyName: "extensionViewContainer", first: true, predicate: ["extensionContainer"], descendants: true, read: ViewContainerRef }], usesInheritance: true, ngImport: i0, template: `
8263
+ @if (loadError) {
8264
+ <div class="card flex flex-col gap-4">
8265
+ <div class="font-semibold text-xl">{{ loadError }}</div>
8266
+ <p-button icon="pi pi-refresh" label="Retry" (onClick)="reloadExtension()"></p-button>
8267
+ </div>
8268
+ } @else {
8269
+ <ng-container #extensionContainer></ng-container>
8270
+ }
8271
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }] }); }
8272
+ }
8273
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: ExtensionModuleHostComponent, decorators: [{
8274
+ type: Component,
8275
+ args: [{
8276
+ standalone: true,
8277
+ imports: [CommonModule, Button],
8278
+ providers: [ExtensionModulesApi],
8279
+ template: `
8280
+ @if (loadError) {
8281
+ <div class="card flex flex-col gap-4">
8282
+ <div class="font-semibold text-xl">{{ loadError }}</div>
8283
+ <p-button icon="pi pi-refresh" label="Retry" (onClick)="reloadExtension()"></p-button>
8284
+ </div>
8285
+ } @else {
8286
+ <ng-container #extensionContainer></ng-container>
8287
+ }
8288
+ `
8289
+ }]
8290
+ }], ctorParameters: () => [], propDecorators: { extensionViewContainer: [{
8291
+ type: ViewChild,
8292
+ args: ['extensionContainer', { read: ViewContainerRef }]
8293
+ }] } });
8294
+
7515
8295
  /**
7516
8296
  * A route guard that ensures the user is authenticated and has a valid access token.
7517
8297
  * If the access token is expired, it attempts to refresh the session.
@@ -7520,24 +8300,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7520
8300
  class AuthGuardService {
7521
8301
  constructor() {
7522
8302
  this.oidcSecurityService = inject(SecurityService);
7523
- this.router = inject(Router);
7524
8303
  }
7525
8304
  /**
7526
8305
  * Checks whether the route can be activated.
7527
8306
  * - Returns `true` if the user is authenticated and the token is valid.
7528
8307
  * - Attempts to refresh the token if expired.
7529
- * - Redirects to `/unauthorized` if not authenticated or refresh fails.
8308
+ * - Starts the authorization flow if not authenticated or refresh fails.
7530
8309
  *
7531
- * @returns {Observable<boolean | UrlTree>} A stream resolving to true (allow), or UrlTree (redirect).
8310
+ * @returns {Observable<boolean>} A stream resolving to true (allow), or false after starting authorization.
7532
8311
  */
7533
8312
  canActivate(returnUrl = '/') {
7534
8313
  this.oidcSecurityService.auth();
7535
- return this.oidcSecurityService.isAuthenticated().pipe(map((authenticated) => authenticated
7536
- ? true
7537
- : this.router.createUrlTree(['/unauthorized'], { queryParams: { returnUrl: this.getReturnUrl(returnUrl) } })));
7538
- }
7539
- getReturnUrl(returnUrl) {
7540
- return returnUrl.startsWith('/unauthorized') ? '/' : returnUrl;
8314
+ return this.oidcSecurityService.isAuthenticated().pipe(map((authenticated) => {
8315
+ if (authenticated) {
8316
+ return true;
8317
+ }
8318
+ this.oidcSecurityService.authorize(returnUrl);
8319
+ return false;
8320
+ }));
7541
8321
  }
7542
8322
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: AuthGuardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7543
8323
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: AuthGuardService }); }
@@ -7618,13 +8398,55 @@ function replaceDefaults(themes) {
7618
8398
  return provideAppThemes(themes, { mode: 'replaceDefaults' });
7619
8399
  }
7620
8400
 
8401
+ const defaultTheme = [
8402
+ {
8403
+ id: 'Oip',
8404
+ label: 'Oip',
8405
+ preset: definePreset(Aura, {
8406
+ components: {
8407
+ toolbar: {
8408
+ colorScheme: {
8409
+ light: {
8410
+ root: {
8411
+ borderRadius: '0.7rem'
8412
+ }
8413
+ },
8414
+ dark: {
8415
+ root: {
8416
+ borderRadius: '0.7rem'
8417
+ }
8418
+ }
8419
+ }
8420
+ }
8421
+ }
8422
+ }),
8423
+ primaryColors: {
8424
+ rose: primitive['red']
8425
+ },
8426
+ surfaceColors: {
8427
+ zinc: {
8428
+ 0: '#ffffff',
8429
+ 50: '#fafafa',
8430
+ 100: '#f4f4f5',
8431
+ 200: '#e4e4e7',
8432
+ 300: '#d4d4d8',
8433
+ 400: '#a1a1aa',
8434
+ 500: '#71717a',
8435
+ 600: '#52525b',
8436
+ 700: '#3f3f46',
8437
+ 800: '#27272a',
8438
+ 900: '#18181b',
8439
+ 950: '#09090b'
8440
+ }
8441
+ }
8442
+ }
8443
+ ];
8444
+
7621
8445
  const langIntercept = (req, next) => {
7622
8446
  const layoutService = inject(LayoutService);
7623
8447
  const securityService = inject(SecurityService);
7624
8448
  const lang = layoutService.language() ? layoutService.language() : 'en';
7625
- const headers = req.headers
7626
- .set('Accept-language', lang)
7627
- .set('X-Timezone', layoutService.timeZone());
8449
+ const headers = req.headers.set('Accept-language', lang).set('X-Timezone', layoutService.timeZone());
7628
8450
  const reqWithCredentials = req.clone({
7629
8451
  headers,
7630
8452
  withCredentials: true
@@ -7717,5 +8539,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
7717
8539
  * Generated bundle index. Do not edit.
7718
8540
  */
7719
8541
 
7720
- export { APP_THEME_PRESETS, APP_THEME_PRESETS_MERGE_MODE, AppConfiguratorComponent, AppFloatingConfiguratorComponent, AppLayoutComponent, AppModulesComponent, AppTopbar, AppTopbarApplicationSwitcherComponent, ApplicationRegistryService, ApplicationsApi, ApplicationsComponent, AuthGuardService, BaseModuleComponent, BffSecurityService, ConfigComponent, ContentType, DbMigrationComponent, DiscussionComponent, ErrorComponent, FolderModuleApi, FooterComponent, HttpClient, IframeModuleApi, IframeModuleComponent, L10nService, LOGO_COMPONENT_TOKEN, LayoutService, LogoComponent, LogoService, MenuComponent, MenuService, MsgService, NotfoundComponent, NotificationApi, NotificationService, ProfileComponent, SecurityApi, SecurityComponent, SecurityService, SidebarComponent, TableFilterService, TopBarService, UnauthorizedComponent, UserNotificationsComponent, UserProfileApi, UserService, convertToPrimeNgDateFormat, langIntercept, mergeWithDefaults, provideAppThemes, provideLogoComponent, provideOip, replaceDefaults };
8542
+ export { APP_THEME_PRESETS, APP_THEME_PRESETS_MERGE_MODE, AppConfiguratorComponent, AppFloatingConfiguratorComponent, AppLayoutComponent, AppModulesComponent, AppTopbar, AppTopbarApplicationSwitcherComponent, ApplicationRegistryService, ApplicationsApi, ApplicationsComponent, AuthGuardService, BaseModuleComponent, BffSecurityService, ConfigComponent, ContentType, CustomElementExtensionModuleHostComponent, DbMigrationComponent, DiscussionComponent, ErrorComponent, ExtensionLoaderService, ExtensionModuleHostComponent, FolderModuleApi, FooterComponent, HttpClient, IframeModuleApi, IframeModuleComponent, L10nService, LOGO_COMPONENT_TOKEN, LayoutService, LogoComponent, LogoService, MenuComponent, MenuService, MsgService, NotfoundComponent, NotificationApi, NotificationService, OIP_EXTENSION_EVENTS, ProfileComponent, SecurityApi, SecurityComponent, SecurityService, SidebarComponent, TableFilterService, TopBarService, UnauthorizedComponent, UserNotificationsComponent, UserProfileApi, UserService, convertToPrimeNgDateFormat, defaultTheme, emitOipContextChange, emitOipError, emitOipNavigate, emitOipNotify, emitOipSettingsChange, emitOipTitleChange, langIntercept, mergeWithDefaults, provideAppThemes, provideLogoComponent, provideOip, replaceDefaults };
7721
8543
  //# sourceMappingURL=oip-common.mjs.map