@yuuvis/client-shell 2.3.4 → 2.3.6

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,34 +1,37 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, signal, Component, Injectable, ChangeDetectionStrategy, ElementRef, effect, HostListener, InjectionToken, input, computed, TemplateRef, Directive, NgModule } from '@angular/core';
2
+ import { inject, signal, Component, effect, untracked, Injectable, InjectionToken, computed, ChangeDetectionStrategy, ElementRef, HostListener, input, TemplateRef, Directive, NgModule } from '@angular/core';
3
3
  import { MAT_DIALOG_DATA, MatDialogRef, MatDialog } from '@angular/material/dialog';
4
- import * as i1$1 from '@yuuvis/client-core';
5
- import { DmsService, TranslateService, TranslateModule, SystemType, Utils, LocaleDatePipe, ConfigService, AuthService, UserService, DeviceService, SafeHtmlPipe, UserRoles } from '@yuuvis/client-core';
4
+ import * as i3 from '@yuuvis/client-core';
5
+ import { DmsService, TranslateService, TranslateModule, SystemType, DeviceService, ConfigService, SafeHtmlPipe, UserService, Utils, LocaleDatePipe, AuthService, UserRoles } from '@yuuvis/client-core';
6
6
  import { AbstractContextAction, ACTION_ICON, SelectionRange } from '@yuuvis/client-framework/actions';
7
7
  import { ShellService, ShellNotificationsService, CommandPaletteService } from '@yuuvis/client-shell-core';
8
- import { switchMap, of, tap, filter, debounceTime } from 'rxjs';
9
- import * as i1$2 from '@angular/common';
8
+ import { switchMap, of, tap, filter, debounceTime, map } from 'rxjs';
9
+ import * as i1$1 from '@angular/common';
10
10
  import { CommonModule, AsyncPipe } from '@angular/common';
11
- import * as i4 from '@angular/material/button';
11
+ import * as i3$1 from '@angular/material/button';
12
12
  import { MatButtonModule } from '@angular/material/button';
13
13
  import * as i1 from '@angular/material/icon';
14
- import { MatIconModule, MatIconRegistry, MatIcon } from '@angular/material/icon';
14
+ import { MatIconModule, MatIconRegistry } from '@angular/material/icon';
15
15
  import * as i2 from '@angular/material/tooltip';
16
16
  import { MatTooltipModule } from '@angular/material/tooltip';
17
17
  import { ConfirmService, BusyOverlayDirective, DialogComponent, LightDismissDirective, LayoutSettingsService } from '@yuuvis/client-framework/common';
18
- import * as i4$1 from '@yuuvis/client-framework/list';
18
+ import * as i4 from '@yuuvis/client-framework/list';
19
19
  import { ListItemDirective, YuvListModule } from '@yuuvis/client-framework/list';
20
20
  import { FlavorChipComponent } from '@yuuvis/client-framework/object-flavor';
21
21
  import { YmtButtonDirective, YmtIconButtonDirective } from '@yuuvis/material';
22
- import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
23
22
  import { MatProgressBar } from '@angular/material/progress-bar';
24
- import * as i2$1 from '@angular/router';
23
+ import * as i6 from '@angular/router';
25
24
  import { Router, NavigationEnd, RouterModule } from '@angular/router';
26
25
  import { SwPush } from '@angular/service-worker';
27
- import * as i3 from '@yuuvis/client-framework/metadata-form-defaults';
26
+ import * as i2$3 from '@yuuvis/client-framework/metadata-form-defaults';
28
27
  import { YuvMetadataFormDefaultsModule } from '@yuuvis/client-framework/metadata-form-defaults';
29
- import { tap as tap$1, map, filter as filter$1, catchError, switchMap as switchMap$1 } from 'rxjs/operators';
30
- import * as i2$2 from '@yuuvis/client-framework/overflow-hidden';
28
+ import { filter as filter$1, map as map$1, tap as tap$1, catchError, switchMap as switchMap$1 } from 'rxjs/operators';
29
+ import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
30
+ import * as i2$1 from '@yuuvis/client-framework/overflow-hidden';
31
31
  import { YuvOverflowHiddenModule } from '@yuuvis/client-framework/overflow-hidden';
32
+ import * as i2$2 from '@angular/material/menu';
33
+ import { MatMenuModule } from '@angular/material/menu';
34
+ import { MatDivider } from '@angular/material/divider';
32
35
  import { CdkTrapFocus } from '@angular/cdk/a11y';
33
36
  import { YUV_ICONS, YuvIconComponent } from '@yuuvis/client-framework/icons';
34
37
  import { WidgetGridRegistry } from '@yuuvis/client-framework/widget-grid';
@@ -96,7 +99,7 @@ class ManageFlavorsComponent {
96
99
  this.#refreshDmsObject();
97
100
  }
98
101
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ManageFlavorsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
99
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: ManageFlavorsComponent, isStandalone: true, selector: "yuv-manage-flavors", ngImport: i0, template: "<yuv-dialog [headertitel]=\"'yuv.shell.action.manage-flavors.title' | translate\">\n <main [yuvBusyOverlay]=\"busy()\">\n <p>{{ 'yuv.shell.action.manage-flavors.text' | translate }}</p>\n @if (appliedFlavors.length) {\n <h3>{{ 'yuv.shell.action.manage-flavors.applied.headline' | translate }}</h3>\n <ul>\n @for (f of appliedFlavors; track $index) {\n <li>\n <yuv-flavor-chip [flavor]=\"f\" yuvListItem></yuv-flavor-chip>\n <button mat-icon-button [matTooltip]=\"'yuv.shell.action.manage-flavors.applicable.button.remove.tooltip' | translate\" (click)=\"removeFlavor(f)\">\n <mat-icon>delete_forever</mat-icon>\n </button>\n </li>\n }\n </ul>\n }\n @if (applicableFlavors.length) {\n <h3>{{ 'yuv.shell.action.manage-flavors.applicable.headline' | translate }}</h3>\n <ul>\n @for (f of applicableFlavors; track $index) {\n <li>\n <yuv-flavor-chip [flavor]=\"f\" yuvListItem></yuv-flavor-chip>\n <button mat-icon-button [matTooltip]=\"'yuv.shell.action.manage-flavors.applied.button.apply.tooltip' | translate\" (click)=\"applyFlavor(f)\">\n <mat-icon>add</mat-icon>\n </button>\n </li>\n }\n </ul>\n }\n </main>\n <footer>\n <button ymtButton=\"secondary\" (click)=\"cancel()\">{{ 'yuv.shell.action.manage-flavors.button.cancel' | translate }}</button>\n </footer>\n</yuv-dialog>\n", styles: [":host main{display:flex;flex-flow:column;padding:var(--ymt-spacing-m);flex:1;overflow-y:auto}:host main yuv-flavor-chip{border-color:transparent;flex:1}:host main ul{padding:0;margin:0;list-style-type:none}:host main li{display:flex;gap:var(--ymt-spacing-xs);align-items:center;border:1px solid var(--ymt-outline-variant);margin-block-end:2px;padding:var(--ymt-spacing-2xs)}:host footer{flex:0 0 auto;margin-block-start:var(--ymt-spacing-m)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: BusyOverlayDirective, selector: "[yuvBusyOverlay]", inputs: ["yuvBusyOverlay", "yuvBusyOverlayConfig"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "directive", type: ListItemDirective, selector: "[yuvListItem]", inputs: ["disabled", "active", "selected"] }, { kind: "component", type: FlavorChipComponent, selector: "yuv-flavor-chip", inputs: ["flavor", "enableRemove", "enableDescription"], outputs: ["flavorRemove"] }, { kind: "component", type: DialogComponent, selector: "yuv-dialog", inputs: ["headertitel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: YmtButtonDirective, selector: "button[ymtButton], a[ymtButton]", inputs: ["ymtButton", "disabled", "aria-disabled", "disableRipple", "disabledInteractive", "button-size"] }] }); }
102
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: ManageFlavorsComponent, isStandalone: true, selector: "yuv-manage-flavors", ngImport: i0, template: "<yuv-dialog [headertitel]=\"'yuv.shell.action.manage-flavors.title' | translate\">\n <main [yuvBusyOverlay]=\"busy()\">\n <p>{{ 'yuv.shell.action.manage-flavors.text' | translate }}</p>\n @if (appliedFlavors.length) {\n <h3>{{ 'yuv.shell.action.manage-flavors.applied.headline' | translate }}</h3>\n <ul>\n @for (f of appliedFlavors; track $index) {\n <li>\n <yuv-flavor-chip [flavor]=\"f\" yuvListItem></yuv-flavor-chip>\n <button mat-icon-button [matTooltip]=\"'yuv.shell.action.manage-flavors.applicable.button.remove.tooltip' | translate\" (click)=\"removeFlavor(f)\">\n <mat-icon>delete_forever</mat-icon>\n </button>\n </li>\n }\n </ul>\n }\n @if (applicableFlavors.length) {\n <h3>{{ 'yuv.shell.action.manage-flavors.applicable.headline' | translate }}</h3>\n <ul>\n @for (f of applicableFlavors; track $index) {\n <li>\n <yuv-flavor-chip [flavor]=\"f\" yuvListItem></yuv-flavor-chip>\n <button mat-icon-button [matTooltip]=\"'yuv.shell.action.manage-flavors.applied.button.apply.tooltip' | translate\" (click)=\"applyFlavor(f)\">\n <mat-icon>add</mat-icon>\n </button>\n </li>\n }\n </ul>\n }\n </main>\n <footer>\n <button ymtButton=\"secondary\" (click)=\"cancel()\">{{ 'yuv.shell.action.manage-flavors.button.cancel' | translate }}</button>\n </footer>\n</yuv-dialog>\n", styles: [":host main{display:flex;flex-flow:column;padding:var(--ymt-spacing-m);flex:1;overflow-y:auto}:host main yuv-flavor-chip{border-color:transparent;flex:1}:host main ul{padding:0;margin:0;list-style-type:none}:host main li{display:flex;gap:var(--ymt-spacing-xs);align-items:center;border:1px solid var(--ymt-outline-variant);margin-block-end:2px;padding:var(--ymt-spacing-2xs)}:host footer{flex:0 0 auto;margin-block-start:var(--ymt-spacing-m)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: BusyOverlayDirective, selector: "[yuvBusyOverlay]", inputs: ["yuvBusyOverlay", "yuvBusyOverlayConfig"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }, { kind: "directive", type: ListItemDirective, selector: "[yuvListItem]", inputs: ["disabled", "active", "selected"] }, { kind: "component", type: FlavorChipComponent, selector: "yuv-flavor-chip", inputs: ["flavor", "enableRemove", "enableDescription"], outputs: ["flavorRemove"] }, { kind: "component", type: DialogComponent, selector: "yuv-dialog", inputs: ["headertitel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: YmtButtonDirective, selector: "button[ymtButton], a[ymtButton]", inputs: ["ymtButton", "disabled", "aria-disabled", "disableRipple", "disabledInteractive", "button-size"] }] }); }
100
103
  }
101
104
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ManageFlavorsComponent, decorators: [{
102
105
  type: Component,
@@ -157,11 +160,22 @@ class ManageFlavorsAction extends AbstractContextAction {
157
160
  class ClientShellService {
158
161
  #router;
159
162
  #shell;
163
+ #appsEffect;
160
164
  constructor() {
161
165
  this.#router = inject(Router);
162
166
  this.#shell = inject(ShellService);
163
167
  this.appHeaderSlots = signal({});
164
168
  this.apps = this.#shell.apps;
169
+ this.#appsEffect = effect(() => {
170
+ const apps = this.apps();
171
+ // apps may change due to i18n, so we need to update current app
172
+ if (this.currentApp()) {
173
+ untracked(() => {
174
+ const app = apps.find((a) => a.path === this.currentApp().path);
175
+ this.currentApp.set(app);
176
+ });
177
+ }
178
+ });
165
179
  this.currentApp = signal(undefined);
166
180
  this.appHeader = signal(false);
167
181
  this.#router.events
@@ -208,7 +222,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
208
222
 
209
223
  class ShellLogoComponent {
210
224
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ShellLogoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
211
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: ShellLogoComponent, isStandalone: true, selector: "yuv-shell-logo", host: { classAttribute: "shell-logo" }, ngImport: i0, template: "<a class=\"shell-logo__to-root\" routerLink=\"/\" [attr.aria-label]=\"'yuv.shell.logo.aria.label' | translate\">\n <mat-icon class=\"shell-logo__icon\" svgIcon=\"shellIcons:app_logo\"></mat-icon>\n</a>\n", styles: [":host.shell-logo{display:block;aspect-ratio:1/1}.shell-logo__to-root{all:unset;cursor:pointer;display:block;height:100%}.shell-logo__icon{width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] }); }
225
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: ShellLogoComponent, isStandalone: true, selector: "yuv-shell-logo", host: { classAttribute: "shell-logo" }, ngImport: i0, template: "<a class=\"shell-logo__to-root\" routerLink=\"/\" [attr.aria-label]=\"'yuv.shell.logo.aria.label' | translate\">\n <mat-icon class=\"shell-logo__icon\" svgIcon=\"shellIcons:app_logo\"></mat-icon>\n</a>\n", styles: [":host.shell-logo{display:block;aspect-ratio:1/1}.shell-logo__to-root{all:unset;cursor:pointer;display:block;height:100%}.shell-logo__icon{width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i6.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] }); }
212
226
  }
213
227
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ShellLogoComponent, decorators: [{
214
228
  type: Component,
@@ -220,15 +234,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
220
234
  class YuvAppHeaderComponent {
221
235
  #clientShellService;
222
236
  #shell;
237
+ #device;
223
238
  #slots;
224
239
  constructor() {
225
240
  this.#clientShellService = inject(ClientShellService);
226
241
  this.#shell = inject(ShellService);
242
+ this.#device = inject(DeviceService);
227
243
  this.actionsSlot = null;
228
244
  this.searchSlot = null;
229
245
  this.notificationsSlot = null;
230
246
  this.app = this.#clientShellService.currentApp;
231
247
  this.#slots = this.#clientShellService.appHeaderSlots;
248
+ this.smallScreenLayout = this.#device.smallScreenLayout;
232
249
  this.busy$ = this.#shell.isBusy$;
233
250
  toObservable(this.#slots)
234
251
  .pipe(debounceTime(1), takeUntilDestroyed())
@@ -239,23 +256,130 @@ class YuvAppHeaderComponent {
239
256
  });
240
257
  }
241
258
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: YuvAppHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
242
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: YuvAppHeaderComponent, isStandalone: true, selector: "yuv-app-header", ngImport: i0, template: "@let a = app();\n@if (a) {\n <header\n class=\"app-header\"\n [ngClass]=\"{\n 'has-actions': !!actionsSlot,\n 'has-search': !!searchSlot,\n 'has-notifications': !!notificationsSlot\n }\"\n aria-labelledby=\"yuv-app-header\"\n >\n @if (busy$ | async) {\n <mat-progress-bar mode=\"indeterminate\" class=\"progress-bar\"></mat-progress-bar>\n }\n <yuv-shell-logo />\n <yuv-overflow-hidden>\n <ng-template #yuvDefaultSlot>\n <a class=\"name\" [routerLink]=\"app()?.path\">\n <span class=\"title\" id=\"yuv-app-header\">\n {{ a.title }}\n <span class=\"claim\">{{ a.options?.appClaim }}</span>\n </span>\n </a>\n </ng-template>\n </yuv-overflow-hidden>\n <div class=\"actions\">\n <ng-container *ngTemplateOutlet=\"actionsSlot\"></ng-container>\n </div>\n <div class=\"search\">\n <ng-container *ngTemplateOutlet=\"searchSlot\"></ng-container>\n </div>\n <div class=\"notifications\">\n <ng-container *ngTemplateOutlet=\"notificationsSlot\"></ng-container>\n </div>\n </header>\n}\n", styles: [":host .app-header{position:relative;display:grid;grid-template-columns:var(--yuv-app-header-height, var(--ymt-sizing-6xl)) auto 1fr auto auto;grid-template-rows:var(--yuv-app-header-height, var(--ymt-sizing-6xl));grid-template-areas:\"logo name actions search notifications\";align-items:center;background-color:var(--ymt-bar-surface);color:var(--ymt-on-bar-surface);box-shadow:0 0 0 1px var(--ymt-outline-variant);box-sizing:border-box;padding-inline-end:var(--ymt-spacing-xl)}:host mat-progress-bar{position:absolute;inset-block-end:0}:host .name{grid-area:name;display:inline-block;padding-inline-end:var(--ymt-spacing-3xl);padding-inline-start:var(--ymt-spacing-m);color:currentColor;text-decoration:none;white-space:nowrap;min-width:128px}:host .name .title{display:flex;flex-direction:column;align-items:flex-start;gap:var(--ymt-spacing-2xs);font:var(--ymt-font-app-name);letter-spacing:var(--ymt-font-app-name-tracking);margin:0;line-height:1}:host .name .claim{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle)}:host .actions{grid-area:actions}:host .search{grid-area:search}:host .notifications{grid-area:notifications}:host .app-header.has-actions .actions{padding-inline-start:var(--ymt-spacing-xl);padding-inline-end:var(--ymt-spacing-l);border-inline-start:var(--ymt-outline-width) solid var(--ymt-outline-variant);border-inline-end:var(--ymt-outline-width) solid var(--ymt-outline-variant)}:host .app-header.has-search .search{padding-inline-start:var(--ymt-spacing-m)}:host .app-header.has-notifications .notifications{padding-inline-start:var(--ymt-spacing-xl)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "component", type: MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: YuvOverflowHiddenModule }, { kind: "component", type: i2$2.OverflowHiddenComponent, selector: "yuv-overflow-hidden" }, { kind: "component", type: ShellLogoComponent, selector: "yuv-shell-logo" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
259
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: YuvAppHeaderComponent, isStandalone: true, selector: "yuv-app-header", host: { properties: { "class.small-screen": "smallScreenLayout()" } }, ngImport: i0, template: "@let a = app();\n@if (a) {\n <header\n class=\"app-header\"\n [ngClass]=\"{\n 'has-actions': !!actionsSlot,\n 'has-search': !!searchSlot,\n 'has-notifications': !!notificationsSlot\n }\"\n aria-labelledby=\"yuv-app-header\"\n >\n @if (busy$ | async) {\n <mat-progress-bar mode=\"indeterminate\" class=\"progress-bar\"></mat-progress-bar>\n }\n <yuv-shell-logo />\n <yuv-overflow-hidden>\n <ng-template #yuvDefaultSlot>\n <a class=\"name\" [routerLink]=\"app()?.path\">\n <span class=\"title\" id=\"yuv-app-header\">\n {{ a.title }}\n <span class=\"claim\">{{ a.options?.appClaim }}</span>\n </span>\n </a>\n </ng-template>\n </yuv-overflow-hidden>\n <div class=\"actions\">\n <ng-container *ngTemplateOutlet=\"actionsSlot\"></ng-container>\n </div>\n <div class=\"search\">\n <ng-container *ngTemplateOutlet=\"searchSlot\"></ng-container>\n </div>\n <div class=\"notifications\">\n <ng-container *ngTemplateOutlet=\"notificationsSlot\"></ng-container>\n </div>\n </header>\n}\n", styles: [":host.small-screen .app-header{grid-template-areas:\"actions actions actions search notifications\"}:host.small-screen yuv-shell-logo,:host.small-screen .name{display:none}:host .app-header{position:relative;display:grid;grid-template-columns:var(--yuv-app-header-height, var(--ymt-sizing-6xl)) auto 1fr auto auto;grid-template-rows:var(--yuv-app-header-height, var(--ymt-sizing-6xl));grid-template-areas:\"logo name actions search notifications\";align-items:center;background-color:var(--ymt-bar-surface);color:var(--ymt-on-bar-surface);box-shadow:0 0 0 1px var(--ymt-outline-variant);box-sizing:border-box;padding-inline-end:var(--ymt-spacing-xl)}:host mat-progress-bar{position:absolute;inset-block-end:0}:host .name{grid-area:name;display:inline-block;padding-inline-end:var(--ymt-spacing-3xl);padding-inline-start:var(--ymt-spacing-m);color:currentColor;text-decoration:none;white-space:nowrap;min-width:128px}:host .name .title{display:flex;flex-direction:column;align-items:flex-start;gap:var(--ymt-spacing-2xs);font:var(--ymt-font-app-name);letter-spacing:var(--ymt-font-app-name-tracking);margin:0;line-height:1}:host .name .claim{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle)}:host .actions{grid-area:actions}:host .search{grid-area:search}:host .notifications{grid-area:notifications}:host .app-header.has-actions .actions{padding-inline-start:var(--ymt-spacing-xl);padding-inline-end:var(--ymt-spacing-l);border-inline-start:var(--ymt-outline-width) solid var(--ymt-outline-variant);border-inline-end:var(--ymt-outline-width) solid var(--ymt-outline-variant)}:host .app-header.has-search .search{padding-inline-start:var(--ymt-spacing-m)}:host .app-header.has-notifications .notifications{padding-inline-start:var(--ymt-spacing-xl)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "component", type: MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: YuvOverflowHiddenModule }, { kind: "component", type: i2$1.OverflowHiddenComponent, selector: "yuv-overflow-hidden" }, { kind: "component", type: ShellLogoComponent, selector: "yuv-shell-logo" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i6.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
243
260
  }
244
261
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: YuvAppHeaderComponent, decorators: [{
245
262
  type: Component,
246
- args: [{ selector: 'yuv-app-header', imports: [CommonModule,
247
- MatProgressBar,
248
- MatIconModule, YuvOverflowHiddenModule, ShellLogoComponent, RouterModule], template: "@let a = app();\n@if (a) {\n <header\n class=\"app-header\"\n [ngClass]=\"{\n 'has-actions': !!actionsSlot,\n 'has-search': !!searchSlot,\n 'has-notifications': !!notificationsSlot\n }\"\n aria-labelledby=\"yuv-app-header\"\n >\n @if (busy$ | async) {\n <mat-progress-bar mode=\"indeterminate\" class=\"progress-bar\"></mat-progress-bar>\n }\n <yuv-shell-logo />\n <yuv-overflow-hidden>\n <ng-template #yuvDefaultSlot>\n <a class=\"name\" [routerLink]=\"app()?.path\">\n <span class=\"title\" id=\"yuv-app-header\">\n {{ a.title }}\n <span class=\"claim\">{{ a.options?.appClaim }}</span>\n </span>\n </a>\n </ng-template>\n </yuv-overflow-hidden>\n <div class=\"actions\">\n <ng-container *ngTemplateOutlet=\"actionsSlot\"></ng-container>\n </div>\n <div class=\"search\">\n <ng-container *ngTemplateOutlet=\"searchSlot\"></ng-container>\n </div>\n <div class=\"notifications\">\n <ng-container *ngTemplateOutlet=\"notificationsSlot\"></ng-container>\n </div>\n </header>\n}\n", styles: [":host .app-header{position:relative;display:grid;grid-template-columns:var(--yuv-app-header-height, var(--ymt-sizing-6xl)) auto 1fr auto auto;grid-template-rows:var(--yuv-app-header-height, var(--ymt-sizing-6xl));grid-template-areas:\"logo name actions search notifications\";align-items:center;background-color:var(--ymt-bar-surface);color:var(--ymt-on-bar-surface);box-shadow:0 0 0 1px var(--ymt-outline-variant);box-sizing:border-box;padding-inline-end:var(--ymt-spacing-xl)}:host mat-progress-bar{position:absolute;inset-block-end:0}:host .name{grid-area:name;display:inline-block;padding-inline-end:var(--ymt-spacing-3xl);padding-inline-start:var(--ymt-spacing-m);color:currentColor;text-decoration:none;white-space:nowrap;min-width:128px}:host .name .title{display:flex;flex-direction:column;align-items:flex-start;gap:var(--ymt-spacing-2xs);font:var(--ymt-font-app-name);letter-spacing:var(--ymt-font-app-name-tracking);margin:0;line-height:1}:host .name .claim{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle)}:host .actions{grid-area:actions}:host .search{grid-area:search}:host .notifications{grid-area:notifications}:host .app-header.has-actions .actions{padding-inline-start:var(--ymt-spacing-xl);padding-inline-end:var(--ymt-spacing-l);border-inline-start:var(--ymt-outline-width) solid var(--ymt-outline-variant);border-inline-end:var(--ymt-outline-width) solid var(--ymt-outline-variant)}:host .app-header.has-search .search{padding-inline-start:var(--ymt-spacing-m)}:host .app-header.has-notifications .notifications{padding-inline-start:var(--ymt-spacing-xl)}\n"] }]
263
+ args: [{ selector: 'yuv-app-header', imports: [CommonModule, MatProgressBar, MatIconModule, YuvOverflowHiddenModule, ShellLogoComponent, RouterModule], host: {
264
+ '[class.small-screen]': 'smallScreenLayout()'
265
+ }, template: "@let a = app();\n@if (a) {\n <header\n class=\"app-header\"\n [ngClass]=\"{\n 'has-actions': !!actionsSlot,\n 'has-search': !!searchSlot,\n 'has-notifications': !!notificationsSlot\n }\"\n aria-labelledby=\"yuv-app-header\"\n >\n @if (busy$ | async) {\n <mat-progress-bar mode=\"indeterminate\" class=\"progress-bar\"></mat-progress-bar>\n }\n <yuv-shell-logo />\n <yuv-overflow-hidden>\n <ng-template #yuvDefaultSlot>\n <a class=\"name\" [routerLink]=\"app()?.path\">\n <span class=\"title\" id=\"yuv-app-header\">\n {{ a.title }}\n <span class=\"claim\">{{ a.options?.appClaim }}</span>\n </span>\n </a>\n </ng-template>\n </yuv-overflow-hidden>\n <div class=\"actions\">\n <ng-container *ngTemplateOutlet=\"actionsSlot\"></ng-container>\n </div>\n <div class=\"search\">\n <ng-container *ngTemplateOutlet=\"searchSlot\"></ng-container>\n </div>\n <div class=\"notifications\">\n <ng-container *ngTemplateOutlet=\"notificationsSlot\"></ng-container>\n </div>\n </header>\n}\n", styles: [":host.small-screen .app-header{grid-template-areas:\"actions actions actions search notifications\"}:host.small-screen yuv-shell-logo,:host.small-screen .name{display:none}:host .app-header{position:relative;display:grid;grid-template-columns:var(--yuv-app-header-height, var(--ymt-sizing-6xl)) auto 1fr auto auto;grid-template-rows:var(--yuv-app-header-height, var(--ymt-sizing-6xl));grid-template-areas:\"logo name actions search notifications\";align-items:center;background-color:var(--ymt-bar-surface);color:var(--ymt-on-bar-surface);box-shadow:0 0 0 1px var(--ymt-outline-variant);box-sizing:border-box;padding-inline-end:var(--ymt-spacing-xl)}:host mat-progress-bar{position:absolute;inset-block-end:0}:host .name{grid-area:name;display:inline-block;padding-inline-end:var(--ymt-spacing-3xl);padding-inline-start:var(--ymt-spacing-m);color:currentColor;text-decoration:none;white-space:nowrap;min-width:128px}:host .name .title{display:flex;flex-direction:column;align-items:flex-start;gap:var(--ymt-spacing-2xs);font:var(--ymt-font-app-name);letter-spacing:var(--ymt-font-app-name-tracking);margin:0;line-height:1}:host .name .claim{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle)}:host .actions{grid-area:actions}:host .search{grid-area:search}:host .notifications{grid-area:notifications}:host .app-header.has-actions .actions{padding-inline-start:var(--ymt-spacing-xl);padding-inline-end:var(--ymt-spacing-l);border-inline-start:var(--ymt-outline-width) solid var(--ymt-outline-variant);border-inline-end:var(--ymt-outline-width) solid var(--ymt-outline-variant)}:host .app-header.has-search .search{padding-inline-start:var(--ymt-spacing-m)}:host .app-header.has-notifications .notifications{padding-inline-start:var(--ymt-spacing-xl)}\n"] }]
249
266
  }], ctorParameters: () => [] });
250
267
 
268
+ const SHELL_ACTION_BUTTONS_CONFIG = new InjectionToken('SHELL_ACTION_BUTTONS_CONFIG');
269
+
251
270
  class SidebarNavComponent {
271
+ #shell;
272
+ #config;
273
+ #shellNotifications;
274
+ #matIconRegistryService;
275
+ #router;
276
+ #userService;
277
+ #device;
278
+ #shellApps;
279
+ constructor() {
280
+ this.#shell = inject(ShellService);
281
+ this.css = inject(ClientShellService);
282
+ this.customActionButtons = inject(SHELL_ACTION_BUTTONS_CONFIG, { optional: true });
283
+ this.#config = inject(ConfigService);
284
+ this.#shellNotifications = inject(ShellNotificationsService);
285
+ this.#matIconRegistryService = inject(MatIconRegistry);
286
+ this.safeHtmlPipe = inject(SafeHtmlPipe);
287
+ this.commandPalette = inject(CommandPaletteService);
288
+ this.#router = inject(Router);
289
+ this.translate = inject(TranslateService);
290
+ this.#userService = inject(UserService);
291
+ this.#device = inject(DeviceService);
292
+ this.APP_LOGOUT_EVENT_KEY = 'yuv.app.event.logout';
293
+ this._levels = {
294
+ info: 0,
295
+ success: 1,
296
+ warning: 2,
297
+ alert: 3
298
+ };
299
+ this.smallScreenLayout = this.#device.smallScreenLayout;
300
+ this.app = this.css.currentApp;
301
+ this.showNotifications = signal(false);
302
+ this.newNotifications = toSignal(this.#shellNotifications.shellNotifications$.pipe(tap((n) => this.showNotifications.set(n.length > 0)), map((notifications) => {
303
+ let maxLevel = 'info';
304
+ const count = notifications.filter((n) => !n.seen).length;
305
+ notifications.forEach((n) => (maxLevel = n.level && this._levels[n.level] > this._levels[maxLevel] ? n.level : maxLevel));
306
+ return count > 0 ? { maxLevel, count } : undefined;
307
+ })));
308
+ this.#shellApps = this.#shell.apps;
309
+ this.navApps = computed(() => this.#shellApps().filter((a) => !a.options?.hideFromNav));
310
+ this.registerIcons = computed(() => {
311
+ const apps = this.#shellApps();
312
+ const config = this.#shell.shellConfig();
313
+ const namespace = config.shellIconNamespace;
314
+ // find svg-icons to register and put them in an array
315
+ const customSvgIconsToRegister = apps
316
+ .filter(({ svgIcon }) => !!svgIcon)
317
+ .map(({ svgIcon, iconName }) => ({ svgIcon, iconName }));
318
+ customSvgIconsToRegister.push(config.appIcon);
319
+ // register svg-icons
320
+ const allRegistered = customSvgIconsToRegister.every((icon) => {
321
+ return namespace && icon && icon.svgIcon && icon.iconName
322
+ ? !!this.#matIconRegistryService.addSvgIconLiteralInNamespace(namespace, icon.iconName, this.safeHtmlPipe.transform(icon.svgIcon))
323
+ : false;
324
+ });
325
+ return allRegistered;
326
+ });
327
+ this.translate.onLangChange.subscribe(() => this._setCommands(true));
328
+ window.addEventListener('storage', (evt) => {
329
+ if (evt.key === this.APP_LOGOUT_EVENT_KEY) {
330
+ this.appLogout(true);
331
+ }
332
+ });
333
+ }
334
+ getAppTitle(a) {
335
+ return a.title || '';
336
+ }
337
+ handleCustomActionButtonClick(button) {
338
+ button.onClick({ router: this.#router, config: this.#config });
339
+ }
340
+ appLogout(triggeredFromOtherTab) {
341
+ if (!triggeredFromOtherTab) {
342
+ // send storage event to logout all other open tabs
343
+ window.localStorage.setItem(this.APP_LOGOUT_EVENT_KEY, `${Date.now()}`);
344
+ }
345
+ this.#userService.logout('');
346
+ }
347
+ _setCommands(update) {
348
+ const commands = this.#shellApps().map((a, i) => ({
349
+ id: `nav.app.${i}`,
350
+ label: this.translate.instant('yuv.shell.cmd.app.open', { title: a.title }),
351
+ callback: () => this.#router.navigate([a.path])
352
+ }));
353
+ commands.push({
354
+ id: `nav.shell.settings`,
355
+ label: this.translate.instant('yuv.shell.cmd.open.settings'),
356
+ callback: () => this.#router.navigate(['settings'])
357
+ });
358
+ commands.push({
359
+ id: `nav.shell.logout`,
360
+ label: this.translate.instant('yuv.shell.cmd.logout'),
361
+ callback: () => this.appLogout()
362
+ });
363
+ if (update)
364
+ this.commandPalette.updateCommands(commands);
365
+ else
366
+ this.commandPalette.registerCommands(commands);
367
+ }
368
+ ngOnInit() {
369
+ this._setCommands();
370
+ }
252
371
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SidebarNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
253
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: SidebarNavComponent, isStandalone: true, selector: "yuv-sidebar-nav", ngImport: i0, template: "<nav class=\"sidenav\" [attr.aria-label]=\"('yuv.shell.side-nav.aria.label' | translate)\" >\n <div class=\"logo-container\">\n <ng-content select=\"[slot=shell-logo]\"></ng-content>\n </div>\n <div class=\"app-switcher-container\">\n <ng-content select=\"[slot=app-switcher]\"></ng-content>\n </div>\n <div class=\"actions-container\">\n <ng-content select=\"[slot=shell-actions]\"></ng-content>\n </div>\n</nav>\n", styles: [".sidenav{height:100%;width:var(--yuv-side-nav-width, var(--ymt-sizing-6xl));background:transparent;color:inherit;display:flex;flex-direction:column;justify-content:flex-start;align-items:center;gap:var(--ymt-spacing-s)}.sidenav .logo-container{width:100%}.sidenav .app-switcher-container{flex:1 1 0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--ymt-spacing-xs);scrollbar-width:none;-ms-overflow-style:none}.sidenav .app-switcher-container::-webkit-scrollbar{display:none}.sidenav .actions-container{justify-self:flex-end;border-top:var(--ymt-outline-width) solid var(--ymt-outline-variant);display:flex;flex-direction:column;gap:var(--ymt-spacing-s);padding:var(--ymt-spacing-m) 0}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
372
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SidebarNavComponent, isStandalone: true, selector: "yuv-sidebar-nav", host: { properties: { "class.small-screen": "smallScreenLayout()" }, classAttribute: "sidenav" }, ngImport: i0, template: "<nav class=\"sidenav\" [attr.aria-label]=\"'yuv.shell.side-nav.aria.label' | translate\">\n @let iconsRegistered = registerIcons();\n @let showAppHeader = css.appHeader();\n\n @let a = app();\n @if (smallScreenLayout()) {\n <div class=\"menu\">\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>menu</mat-icon>\n </button>\n @if (a) {\n {{ a.title }}\n }\n </div>\n\n <mat-menu #menu=\"matMenu\" class=\"sort-menu\" [ariaLabel]=\"'yuv.sort.tooltip' | translate\">\n @for (a of navApps(); track a.path) {\n <button mat-menu-item [routerLink]=\"a.path\">\n @if (a.svgIcon) {\n @if (iconsRegistered) {\n <mat-icon [svgIcon]=\"'shellIcons:' + a.iconName\"></mat-icon>\n }\n } @else {\n <mat-icon>{{ a.iconName }}</mat-icon>\n }\n <span>{{ getAppTitle(a) }}</span>\n </button>\n }\n @if (showNotifications()) {\n <mat-divider></mat-divider>\n <button mat-menu-item [routerLink]=\"[{ outlets: { aside: 'notifications' } }]\" routerLinkActive=\"active\">\n <mat-icon>notifications</mat-icon>\n @if (newNotifications(); as note) {\n <div class=\"badge {{ note.maxLevel }}\">{{ note.count }}</div>\n }\n <span>{{ 'yuv.shell.notifications.title' | translate }}</span>\n </button>\n }\n <mat-divider></mat-divider>\n\n @for (customButton of customActionButtons; track customButton.iconName) {\n <button mat-menu-item (click)=\"handleCustomActionButtonClick(customButton)\">\n <mat-icon>{{ customButton.iconName }}</mat-icon>\n <span>{{ customButton.tooltip ? (customButton.tooltip | translate) : '' }}</span>\n </button>\n }\n <button mat-menu-item [routerLink]=\"['/settings']\" routerLinkActive=\"active\">\n <mat-icon>settings</mat-icon>\n <span>{{ 'yuv.shell.settings.title' | translate }}</span>\n </button>\n <button mat-menu-item (click)=\"appLogout()\">\n <mat-icon>power_settings_new</mat-icon>\n <span>{{ 'yuv.shell.cmd.logout' | translate }}</span>\n </button>\n </mat-menu>\n } @else {\n <div class=\"logo-container\">\n @if (iconsRegistered && !showAppHeader) {\n <yuv-shell-logo slot=\"shell-logo\" class=\"shell-nav__shell-logo\" />\n }\n </div>\n <div class=\"app-switcher-container\">\n <ul class=\"shell-nav__app-switcher\">\n @for (a of navApps(); track a.path) {\n <li class=\"shell-nav__app-switcher-item\" routerLinkActive=\"shell-nav__app-switcher-item--active\">\n <button class=\"shell-nav__app-switcher-link\" ymt-icon-button [routerLink]=\"a.path\" matTooltipPosition=\"after\" [matTooltip]=\"getAppTitle(a)\">\n @if (a.svgIcon) {\n @if (iconsRegistered) {\n <mat-icon [svgIcon]=\"'shellIcons:' + a.iconName\"></mat-icon>\n }\n } @else {\n <mat-icon>{{ a.iconName }}</mat-icon>\n }\n <!--@if (getNotifications(a.id)) {\n <div [ngClass]=\"'badge ' + getNotifications(a.id).maxLevel\">{{ getNotifications(a.id).count }}</div>\n }-->\n </button>\n </li>\n }\n </ul>\n </div>\n <div class=\"actions-container\">\n @if (showNotifications()) {\n <button\n class=\"shell-nav__actions-button\"\n ymt-icon-button\n [routerLink]=\"[{ outlets: { aside: 'notifications' } }]\"\n routerLinkActive=\"active\"\n matTooltipPosition=\"after\"\n [matTooltip]=\"'yuv.shell.notifications.title' | translate\"\n >\n <mat-icon>notifications</mat-icon>\n @if (newNotifications(); as note) {\n <div class=\"badge {{ note.maxLevel }}\">{{ note.count }}</div>\n }\n </button>\n }\n @for (customButton of customActionButtons; track customButton.iconName) {\n <button\n class=\"shell-nav__actions-button\"\n ymt-icon-button\n (click)=\"handleCustomActionButtonClick(customButton)\"\n matTooltipPosition=\"after\"\n [matTooltip]=\"customButton.tooltip ? (customButton.tooltip | translate) : ''\"\n >\n <mat-icon>{{ customButton.iconName }}</mat-icon>\n </button>\n }\n <button\n class=\"shell-nav__actions-button\"\n ymt-icon-button\n [routerLink]=\"['/settings']\"\n routerLinkActive=\"active\"\n [matTooltip]=\"'yuv.shell.settings.title' | translate\"\n >\n <mat-icon>settings</mat-icon>\n </button>\n <button class=\"shell-nav__actions-button\" ymt-icon-button (click)=\"appLogout()\" [matTooltip]=\"'yuv.shell.cmd.logout' | translate\">\n <mat-icon>power_settings_new</mat-icon>\n </button>\n </div>\n }\n</nav>\n", styles: [":host{display:block}:host.small-screen nav .menu{display:flex;align-items:center;gap:var(--ymt-spacing-s);padding:var(--ymt-sizing-s) var(--ymt-sizing-m)}:host:not(.small-screen) .sidenav{height:100%;width:var(--yuv-side-nav-width, var(--ymt-sizing-6xl));background:transparent;color:inherit;display:flex;flex-direction:column;justify-content:flex-start;align-items:center;gap:var(--ymt-spacing-s)}:host:not(.small-screen) .sidenav .logo-container{width:100%}:host:not(.small-screen) .sidenav .app-switcher-container{flex:1 1 0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--ymt-spacing-xs);scrollbar-width:none;-ms-overflow-style:none}:host:not(.small-screen) .sidenav .app-switcher-container::-webkit-scrollbar{display:none}:host:not(.small-screen) .sidenav .actions-container{justify-self:flex-end;border-top:var(--ymt-outline-width) solid var(--ymt-outline-variant);display:flex;flex-direction:column;gap:var(--ymt-spacing-s);padding:var(--ymt-spacing-m) 0}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-wrapper{display:contents}:host:not(.small-screen) .sidenav .shell-nav__app-switcher{display:contents;list-style:none}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item{position:relative;padding:var(--ymt-spacing-2xs) 0}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item:after{content:\"\";position:absolute;inset:0;top:var(--ymt-spacing-2xs);aspect-ratio:1/1;background-color:var(--mat-icon-button-state-layer-color, var(--mat-sys-on-surface-variant));border-radius:var(--ymt-corner-full);visibility:hidden;opacity:0}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item:before{content:\"\";height:var(--ymt-sizing-3xs);width:var(--ymt-sizing-xs);border-radius:var(--ymt-corner-full);position:absolute;left:50%;bottom:0;transform:translate3d(-50%,0,0);background-color:transparent;transition:background-color .1s ease-in-out}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item--active:before{background-color:var(--ymt-primary)}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item--active:after{visibility:visible;opacity:.08;pointer-events:none}:host:not(.small-screen) .sidenav .shell-nav__actions{display:contents}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }, { kind: "component", type: ShellLogoComponent, selector: "yuv-shell-logo" }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i2$2.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i2$2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i2$2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i6.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i6.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
254
373
  }
255
374
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SidebarNavComponent, decorators: [{
256
375
  type: Component,
257
- args: [{ selector: 'yuv-sidebar-nav', changeDetection: ChangeDetectionStrategy.OnPush, imports: [TranslateModule], template: "<nav class=\"sidenav\" [attr.aria-label]=\"('yuv.shell.side-nav.aria.label' | translate)\" >\n <div class=\"logo-container\">\n <ng-content select=\"[slot=shell-logo]\"></ng-content>\n </div>\n <div class=\"app-switcher-container\">\n <ng-content select=\"[slot=app-switcher]\"></ng-content>\n </div>\n <div class=\"actions-container\">\n <ng-content select=\"[slot=shell-actions]\"></ng-content>\n </div>\n</nav>\n", styles: [".sidenav{height:100%;width:var(--yuv-side-nav-width, var(--ymt-sizing-6xl));background:transparent;color:inherit;display:flex;flex-direction:column;justify-content:flex-start;align-items:center;gap:var(--ymt-spacing-s)}.sidenav .logo-container{width:100%}.sidenav .app-switcher-container{flex:1 1 0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--ymt-spacing-xs);scrollbar-width:none;-ms-overflow-style:none}.sidenav .app-switcher-container::-webkit-scrollbar{display:none}.sidenav .actions-container{justify-self:flex-end;border-top:var(--ymt-outline-width) solid var(--ymt-outline-variant);display:flex;flex-direction:column;gap:var(--ymt-spacing-s);padding:var(--ymt-spacing-m) 0}\n"] }]
258
- }] });
376
+ args: [{ selector: 'yuv-sidebar-nav', changeDetection: ChangeDetectionStrategy.OnPush, imports: [TranslateModule, ShellLogoComponent, YmtIconButtonDirective,
377
+ MatMenuModule, MatButtonModule,
378
+ MatIconModule, MatTooltipModule, RouterModule, MatDivider], host: {
379
+ class: 'sidenav',
380
+ '[class.small-screen]': 'smallScreenLayout()'
381
+ }, template: "<nav class=\"sidenav\" [attr.aria-label]=\"'yuv.shell.side-nav.aria.label' | translate\">\n @let iconsRegistered = registerIcons();\n @let showAppHeader = css.appHeader();\n\n @let a = app();\n @if (smallScreenLayout()) {\n <div class=\"menu\">\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>menu</mat-icon>\n </button>\n @if (a) {\n {{ a.title }}\n }\n </div>\n\n <mat-menu #menu=\"matMenu\" class=\"sort-menu\" [ariaLabel]=\"'yuv.sort.tooltip' | translate\">\n @for (a of navApps(); track a.path) {\n <button mat-menu-item [routerLink]=\"a.path\">\n @if (a.svgIcon) {\n @if (iconsRegistered) {\n <mat-icon [svgIcon]=\"'shellIcons:' + a.iconName\"></mat-icon>\n }\n } @else {\n <mat-icon>{{ a.iconName }}</mat-icon>\n }\n <span>{{ getAppTitle(a) }}</span>\n </button>\n }\n @if (showNotifications()) {\n <mat-divider></mat-divider>\n <button mat-menu-item [routerLink]=\"[{ outlets: { aside: 'notifications' } }]\" routerLinkActive=\"active\">\n <mat-icon>notifications</mat-icon>\n @if (newNotifications(); as note) {\n <div class=\"badge {{ note.maxLevel }}\">{{ note.count }}</div>\n }\n <span>{{ 'yuv.shell.notifications.title' | translate }}</span>\n </button>\n }\n <mat-divider></mat-divider>\n\n @for (customButton of customActionButtons; track customButton.iconName) {\n <button mat-menu-item (click)=\"handleCustomActionButtonClick(customButton)\">\n <mat-icon>{{ customButton.iconName }}</mat-icon>\n <span>{{ customButton.tooltip ? (customButton.tooltip | translate) : '' }}</span>\n </button>\n }\n <button mat-menu-item [routerLink]=\"['/settings']\" routerLinkActive=\"active\">\n <mat-icon>settings</mat-icon>\n <span>{{ 'yuv.shell.settings.title' | translate }}</span>\n </button>\n <button mat-menu-item (click)=\"appLogout()\">\n <mat-icon>power_settings_new</mat-icon>\n <span>{{ 'yuv.shell.cmd.logout' | translate }}</span>\n </button>\n </mat-menu>\n } @else {\n <div class=\"logo-container\">\n @if (iconsRegistered && !showAppHeader) {\n <yuv-shell-logo slot=\"shell-logo\" class=\"shell-nav__shell-logo\" />\n }\n </div>\n <div class=\"app-switcher-container\">\n <ul class=\"shell-nav__app-switcher\">\n @for (a of navApps(); track a.path) {\n <li class=\"shell-nav__app-switcher-item\" routerLinkActive=\"shell-nav__app-switcher-item--active\">\n <button class=\"shell-nav__app-switcher-link\" ymt-icon-button [routerLink]=\"a.path\" matTooltipPosition=\"after\" [matTooltip]=\"getAppTitle(a)\">\n @if (a.svgIcon) {\n @if (iconsRegistered) {\n <mat-icon [svgIcon]=\"'shellIcons:' + a.iconName\"></mat-icon>\n }\n } @else {\n <mat-icon>{{ a.iconName }}</mat-icon>\n }\n <!--@if (getNotifications(a.id)) {\n <div [ngClass]=\"'badge ' + getNotifications(a.id).maxLevel\">{{ getNotifications(a.id).count }}</div>\n }-->\n </button>\n </li>\n }\n </ul>\n </div>\n <div class=\"actions-container\">\n @if (showNotifications()) {\n <button\n class=\"shell-nav__actions-button\"\n ymt-icon-button\n [routerLink]=\"[{ outlets: { aside: 'notifications' } }]\"\n routerLinkActive=\"active\"\n matTooltipPosition=\"after\"\n [matTooltip]=\"'yuv.shell.notifications.title' | translate\"\n >\n <mat-icon>notifications</mat-icon>\n @if (newNotifications(); as note) {\n <div class=\"badge {{ note.maxLevel }}\">{{ note.count }}</div>\n }\n </button>\n }\n @for (customButton of customActionButtons; track customButton.iconName) {\n <button\n class=\"shell-nav__actions-button\"\n ymt-icon-button\n (click)=\"handleCustomActionButtonClick(customButton)\"\n matTooltipPosition=\"after\"\n [matTooltip]=\"customButton.tooltip ? (customButton.tooltip | translate) : ''\"\n >\n <mat-icon>{{ customButton.iconName }}</mat-icon>\n </button>\n }\n <button\n class=\"shell-nav__actions-button\"\n ymt-icon-button\n [routerLink]=\"['/settings']\"\n routerLinkActive=\"active\"\n [matTooltip]=\"'yuv.shell.settings.title' | translate\"\n >\n <mat-icon>settings</mat-icon>\n </button>\n <button class=\"shell-nav__actions-button\" ymt-icon-button (click)=\"appLogout()\" [matTooltip]=\"'yuv.shell.cmd.logout' | translate\">\n <mat-icon>power_settings_new</mat-icon>\n </button>\n </div>\n }\n</nav>\n", styles: [":host{display:block}:host.small-screen nav .menu{display:flex;align-items:center;gap:var(--ymt-spacing-s);padding:var(--ymt-sizing-s) var(--ymt-sizing-m)}:host:not(.small-screen) .sidenav{height:100%;width:var(--yuv-side-nav-width, var(--ymt-sizing-6xl));background:transparent;color:inherit;display:flex;flex-direction:column;justify-content:flex-start;align-items:center;gap:var(--ymt-spacing-s)}:host:not(.small-screen) .sidenav .logo-container{width:100%}:host:not(.small-screen) .sidenav .app-switcher-container{flex:1 1 0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--ymt-spacing-xs);scrollbar-width:none;-ms-overflow-style:none}:host:not(.small-screen) .sidenav .app-switcher-container::-webkit-scrollbar{display:none}:host:not(.small-screen) .sidenav .actions-container{justify-self:flex-end;border-top:var(--ymt-outline-width) solid var(--ymt-outline-variant);display:flex;flex-direction:column;gap:var(--ymt-spacing-s);padding:var(--ymt-spacing-m) 0}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-wrapper{display:contents}:host:not(.small-screen) .sidenav .shell-nav__app-switcher{display:contents;list-style:none}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item{position:relative;padding:var(--ymt-spacing-2xs) 0}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item:after{content:\"\";position:absolute;inset:0;top:var(--ymt-spacing-2xs);aspect-ratio:1/1;background-color:var(--mat-icon-button-state-layer-color, var(--mat-sys-on-surface-variant));border-radius:var(--ymt-corner-full);visibility:hidden;opacity:0}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item:before{content:\"\";height:var(--ymt-sizing-3xs);width:var(--ymt-sizing-xs);border-radius:var(--ymt-corner-full);position:absolute;left:50%;bottom:0;transform:translate3d(-50%,0,0);background-color:transparent;transition:background-color .1s ease-in-out}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item--active:before{background-color:var(--ymt-primary)}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item--active:after{visibility:visible;opacity:.08;pointer-events:none}:host:not(.small-screen) .sidenav .shell-nav__actions{display:contents}\n"] }]
382
+ }], ctorParameters: () => [] });
259
383
 
260
384
  class NotificationsPageComponent {
261
385
  constructor() {
@@ -312,7 +436,7 @@ class NotificationsPageComponent {
312
436
  });
313
437
  }
314
438
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NotificationsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
315
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: NotificationsPageComponent, isStandalone: true, selector: "yuv-notifications", host: { listeners: { "keydown": "onKeydown($event)" } }, ngImport: i0, template: "<div class=\"notifications\" (yuvLightDismiss)=\"close()\" cdkTrapFocus>\n <h2>{{ 'yuv.shell.notifications.title' | translate }}</h2>\n\n @if (notifications()?.length) {\n <yuv-list (itemSelect)=\"itemSelected($event)\" (itemFocus)=\"itemFocused($event)\">\n @for (n of notifications(); track n.id) {\n <div class=\"note {{ n.level }}\" [ngClass]=\"{ withRoute: n.targetRoute }\" yuvListItem>\n <div class=\"icon\"><yuv-icon [svg]=\"n.icon || icons.note\"></yuv-icon></div>\n <div class=\"received\">{{ n.timestamp | localeDate }}</div>\n <div class=\"title\">{{ n.title }}</div>\n <div class=\"description\">{{ n.description }}</div>\n <div class=\"meta\"></div>\n\n <div class=\"actions\">\n <button mat-icon-button (click)=\"remove(n.id)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n </div>\n }\n </yuv-list>\n\n <div class=\"actions\">\n <button mat-icon-button [hidden]=\"!notifications()?.length\" class=\"icon secondary\" (click)=\"removeAll()\">\n {{ 'yuv.shell.notifications.button.remove.all' | translate }}<mat-icon>delete</mat-icon>\n </button>\n </div>\n } @else {\n <div class=\"empty\">\n <p>{{ 'yuv.shell.notifications.empty' | translate }}</p>\n </div>\n }\n\n <button mat-icon-button class=\"icon close\" (click)=\"close()\">\n <mat-icon>close</mat-icon>\n </button>\n</div>\n", styles: [":host{height:100%;display:flex}:host .notifications{background-color:var(--ymt-surface-panel);border-inline-end:1px solid var(--ymt-outline-variant);height:100%;overflow:hidden;box-sizing:border-box;padding:var(--ymt-spacing-m);display:grid;grid-template-rows:auto auto 1fr;grid-template-columns:1fr auto;grid-template-areas:\"title close\" \"actions actions\" \"list list\";gap:var(--ymt-spacing-m);max-width:30vw;min-width:300px;box-shadow:8px 0 8px #0000001a;animation:dialogAppear .2s ease-in-out}:host .notifications h2{color:var(--ymt-text-color-subtle);grid-area:title;margin:0;padding:0;align-self:center;text-overflow:ellipsis;overflow:hidden}:host .notifications .actions{grid-area:actions;display:flex;justify-content:end;--icon-size: 18px}:host .notifications .actions button{border-radius:.4em}:host .notifications .close{grid-area:close;--icon-size: 18px}:host .notifications yuv-list,:host .notifications .empty{grid-area:list}:host .notifications .empty{display:grid;align-items:center;justify-content:center;color:var(--ymt-text-color-subtle)}:host .notifications .note{--level-color: transparent;display:grid;grid-template-rows:auto auto auto auto;grid-template-columns:auto 1fr auto;grid-template-areas:\"icon received actions\" \"icon title title\" \"icon description description\" \"icon meta meta\";row-gap:var(--ymt-spacing-2xs);column-gap:var(--ymt-spacing-xs);align-items:center;padding:var(--ymt-spacing-xs);padding-inline-start:var(--ymt-spacing-m);margin-block-end:var(--ymt-spacing-xs);background-color:var(--ymt-surface-panel);outline:1px solid var(--ymt-outline-variant);outline-offset:-1px;position:relative;cursor:default}:host .notifications .note.withRoute{cursor:pointer}:host .notifications .note:before{content:\"\";width:4px;position:absolute;left:2px;top:2px;bottom:2px;border-radius:2px;background-color:var(--level-color)}:host .notifications .note.alert{--level-color: var(--ymt-danger)}:host .notifications .note.warning{--level-color: var(--ymt-warning)}:host .notifications .note.success{--level-color: var(--ymt-success)}:host .notifications .note:hover,:host .notifications .note[aria-current=true]{background-color:var(--ymt-focus-background)}:host .notifications .note:hover button,:host .notifications .note[aria-current=true] button{opacity:1}:host .notifications .note .icon{grid-area:icon;color:var(--ymt-text-color-subtle)}:host .notifications .note .title{overflow:hidden;text-overflow:ellipsis;font-weight:700;grid-area:title;color:var(--ymt-text-color)}:host .notifications .note .description{grid-area:description;overflow:hidden;text-overflow:ellipsis}:host .notifications .note .meta{grid-area:meta}:host .notifications .note .received{grid-area:received;color:var(--ymt-text-color-subtle)}:host .notifications .note button{grid-area:actions;padding:0;opacity:0}@keyframes dialogAppear{0%{opacity:0;transform:translate(calc(var(--ymt-spacing-m) * -1))}to{opacity:1;transform:translate(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: LightDismissDirective, selector: "[yuvLightDismiss]", outputs: ["yuvLightDismiss"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "pipe", type: LocaleDatePipe, name: "localeDate" }, { kind: "component", type: YuvIconComponent, selector: "yuv-icon", inputs: ["label", "svg", "svgSrc"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "ngmodule", type: YuvListModule }, { kind: "component", type: i4$1.ListComponent, selector: "yuv-list", inputs: ["multiselect", "selfHandleSelection", "autoSelect", "disableSelection"], outputs: ["itemSelect", "itemFocus"] }, { kind: "directive", type: i4$1.ListItemDirective, selector: "[yuvListItem]", inputs: ["disabled", "active", "selected"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] }); }
439
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: NotificationsPageComponent, isStandalone: true, selector: "yuv-notifications", host: { listeners: { "keydown": "onKeydown($event)" } }, ngImport: i0, template: "<div class=\"notifications\" (yuvLightDismiss)=\"close()\" cdkTrapFocus>\n <h2>{{ 'yuv.shell.notifications.title' | translate }}</h2>\n\n @if (notifications()?.length) {\n <yuv-list (itemSelect)=\"itemSelected($event)\" (itemFocus)=\"itemFocused($event)\">\n @for (n of notifications(); track n.id) {\n <div class=\"note {{ n.level }}\" [ngClass]=\"{ withRoute: n.targetRoute }\" yuvListItem>\n <div class=\"icon\"><yuv-icon [svg]=\"n.icon || icons.note\"></yuv-icon></div>\n <div class=\"received\">{{ n.timestamp | localeDate }}</div>\n <div class=\"title\">{{ n.title }}</div>\n <div class=\"description\">{{ n.description }}</div>\n <div class=\"meta\"></div>\n\n <div class=\"actions\">\n <button mat-icon-button (click)=\"remove(n.id)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n </div>\n }\n </yuv-list>\n\n <div class=\"actions\">\n <button mat-icon-button [hidden]=\"!notifications()?.length\" class=\"icon secondary\" (click)=\"removeAll()\">\n {{ 'yuv.shell.notifications.button.remove.all' | translate }}<mat-icon>delete</mat-icon>\n </button>\n </div>\n } @else {\n <div class=\"empty\">\n <p>{{ 'yuv.shell.notifications.empty' | translate }}</p>\n </div>\n }\n\n <button mat-icon-button class=\"icon close\" (click)=\"close()\">\n <mat-icon>close</mat-icon>\n </button>\n</div>\n", styles: [":host{height:100%;display:flex}:host .notifications{background-color:var(--ymt-surface-panel);border-inline-end:1px solid var(--ymt-outline-variant);height:100%;overflow:hidden;box-sizing:border-box;padding:var(--ymt-spacing-m);display:grid;grid-template-rows:auto auto 1fr;grid-template-columns:1fr auto;grid-template-areas:\"title close\" \"actions actions\" \"list list\";gap:var(--ymt-spacing-m);max-width:30vw;min-width:300px;box-shadow:8px 0 8px #0000001a;animation:dialogAppear .2s ease-in-out}:host .notifications h2{color:var(--ymt-text-color-subtle);grid-area:title;margin:0;padding:0;align-self:center;text-overflow:ellipsis;overflow:hidden}:host .notifications .actions{grid-area:actions;display:flex;justify-content:end;--icon-size: 18px}:host .notifications .actions button{border-radius:.4em}:host .notifications .close{grid-area:close;--icon-size: 18px}:host .notifications yuv-list,:host .notifications .empty{grid-area:list}:host .notifications .empty{display:grid;align-items:center;justify-content:center;color:var(--ymt-text-color-subtle)}:host .notifications .note{--level-color: transparent;display:grid;grid-template-rows:auto auto auto auto;grid-template-columns:auto 1fr auto;grid-template-areas:\"icon received actions\" \"icon title title\" \"icon description description\" \"icon meta meta\";row-gap:var(--ymt-spacing-2xs);column-gap:var(--ymt-spacing-xs);align-items:center;padding:var(--ymt-spacing-xs);padding-inline-start:var(--ymt-spacing-m);margin-block-end:var(--ymt-spacing-xs);background-color:var(--ymt-surface-panel);outline:1px solid var(--ymt-outline-variant);outline-offset:-1px;position:relative;cursor:default}:host .notifications .note.withRoute{cursor:pointer}:host .notifications .note:before{content:\"\";width:4px;position:absolute;left:2px;top:2px;bottom:2px;border-radius:2px;background-color:var(--level-color)}:host .notifications .note.alert{--level-color: var(--ymt-danger)}:host .notifications .note.warning{--level-color: var(--ymt-warning)}:host .notifications .note.success{--level-color: var(--ymt-success)}:host .notifications .note:hover,:host .notifications .note[aria-current=true]{background-color:var(--ymt-focus-background)}:host .notifications .note:hover button,:host .notifications .note[aria-current=true] button{opacity:1}:host .notifications .note .icon{grid-area:icon;color:var(--ymt-text-color-subtle)}:host .notifications .note .title{overflow:hidden;text-overflow:ellipsis;font-weight:700;grid-area:title;color:var(--ymt-text-color)}:host .notifications .note .description{grid-area:description;overflow:hidden;text-overflow:ellipsis}:host .notifications .note .meta{grid-area:meta}:host .notifications .note .received{grid-area:received;color:var(--ymt-text-color-subtle)}:host .notifications .note button{grid-area:actions;padding:0;opacity:0}@keyframes dialogAppear{0%{opacity:0;transform:translate(calc(var(--ymt-spacing-m) * -1))}to{opacity:1;transform:translate(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: LightDismissDirective, selector: "[yuvLightDismiss]", outputs: ["yuvLightDismiss"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "pipe", type: LocaleDatePipe, name: "localeDate" }, { kind: "component", type: YuvIconComponent, selector: "yuv-icon", inputs: ["label", "svg", "svgSrc"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "ngmodule", type: YuvListModule }, { kind: "component", type: i4.ListComponent, selector: "yuv-list", inputs: ["preventChangeUntil", "multiselect", "selfHandleSelection", "autoSelect", "disableSelection"], outputs: ["itemSelect", "itemFocus"] }, { kind: "directive", type: i4.ListItemDirective, selector: "[yuvListItem]", inputs: ["disabled", "active", "selected"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }] }); }
316
440
  }
317
441
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NotificationsPageComponent, decorators: [{
318
442
  type: Component,
@@ -335,15 +459,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
335
459
  const clientShellRoutes = [
336
460
  { path: 'dashboard', loadComponent: () => import('./yuuvis-client-shell-dashboard.component-DNpk_qVe.mjs').then((comp) => comp.DashboardPageComponent) },
337
461
  { path: 'notifications', component: NotificationsPageComponent, outlet: 'aside' },
338
- { path: 'settings', loadComponent: () => import('./yuuvis-client-shell-settings.component-OPDWz2_-.mjs').then((comp) => comp.SettingsPageComponent) },
462
+ { path: 'settings', loadComponent: () => import('./yuuvis-client-shell-settings.component-DWoamL5p.mjs').then((comp) => comp.SettingsPageComponent) },
339
463
  // default route
340
464
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
341
465
  // redirecting route
342
466
  { path: '**', redirectTo: '/' }
343
467
  ];
344
468
 
345
- const SHELL_ACTION_BUTTONS_CONFIG = new InjectionToken('SHELL_ACTION_BUTTONS_CONFIG');
346
-
347
469
  /**
348
470
  * Base component for the client shell application. In your apps
349
471
  * app.component.ts you can use this component as a base component
@@ -368,17 +490,14 @@ class ClientShellComponent {
368
490
  #shell;
369
491
  #device;
370
492
  #swPush;
371
- #matIconRegistryService;
372
493
  #layoutSettings;
373
- onFocusChange(event) {
374
- // console.log('focused: ', document.activeElement);
375
- }
376
494
  onDragOver(event) {
377
495
  event.stopPropagation();
378
496
  event.preventDefault();
379
497
  this.showUploadOverlay = !!this._dragContainsFiles(event);
380
498
  }
381
- #appsEffect;
499
+ // set shell apps from component input
500
+ #appsInputEffect;
382
501
  #shellConfigEffect;
383
502
  constructor() {
384
503
  this.router = inject(Router);
@@ -387,71 +506,35 @@ class ClientShellComponent {
387
506
  this.userService = inject(UserService);
388
507
  this.#shell = inject(ShellService);
389
508
  this.css = inject(ClientShellService);
390
- this.shellNotifications = inject(ShellNotificationsService);
391
- this.translate = inject(TranslateService);
392
- this.commandPalette = inject(CommandPaletteService);
393
509
  this.#device = inject(DeviceService);
394
510
  this.#swPush = inject(SwPush);
395
- this.#matIconRegistryService = inject(MatIconRegistry);
396
511
  this.#layoutSettings = inject(LayoutSettingsService);
397
- this.APP_LOGOUT_EVENT_KEY = 'yuv.app.event.logout';
398
- this._levels = {
399
- info: 0,
400
- success: 1,
401
- warning: 2,
402
- alert: 3
403
- };
404
512
  this.safeHtmlPipe = inject(SafeHtmlPipe);
405
513
  this.showUploadOverlay = false;
406
514
  this.busy$ = this.#shell.isBusy$;
407
- this.customActionButtons = inject(SHELL_ACTION_BUTTONS_CONFIG, { optional: true });
408
- this.showNotifications = signal(false);
409
- this.newNotifications = toSignal(this.shellNotifications.shellNotifications$.pipe(tap$1((n) => this.showNotifications.set(n.length > 0)), map((notifications) => {
410
- let maxLevel = 'info';
411
- const count = notifications.filter((n) => !n.seen).length;
412
- notifications.forEach((n) => (maxLevel = n.level && this._levels[n.level] > this._levels[maxLevel] ? n.level : maxLevel));
413
- return count > 0 ? { maxLevel, count } : undefined;
414
- })));
415
515
  this.apps = input.required();
416
- this.navApps = computed(() => this.apps().filter((a) => !a.options?.hideFromNav));
417
- this.#appsEffect = effect(() => this.#shell.setApps(this.apps()));
516
+ // set shell apps from component input
517
+ this.#appsInputEffect = effect(() => this.#shell.setApps(this.apps()));
418
518
  this.checkedForInitialRoute = false;
419
519
  this.enableTenantSwitch = false;
520
+ this.smallScreenLayout = this.#device.smallScreenLayout;
420
521
  this.config = input();
522
+ /**
523
+ * If set to true, the shell will adapt to small screen sizes (e.g. mobile phones).
524
+ * It also will propagate this setting to other components that might act aupon it.
525
+ * To do so, you can use the `DeviceService` and check for `smallScreenLayout` signal changes.
526
+ */
527
+ this.supportsSmallScreens = input(false);
421
528
  this.#shellConfigEffect = effect(() => {
422
529
  const cfg = this.config();
423
530
  if (cfg) {
424
531
  this.#shell.setShellConfig(cfg);
425
532
  }
426
533
  });
427
- this.registerIcons = computed(() => {
428
- const apps = this.apps();
429
- const config = this.#shell.shellConfig();
430
- const namespace = config.shellIconNamespace;
431
- // find svg-icons to register and put them in an array
432
- const customSvgIconsToRegister = apps
433
- .filter(({ svgIcon }) => !!svgIcon)
434
- .map(({ svgIcon, iconName }) => ({ svgIcon, iconName }));
435
- customSvgIconsToRegister.push(config.appIcon);
436
- // register svg-icons
437
- const allRegistered = customSvgIconsToRegister.every((icon) => {
438
- return namespace && icon && icon.svgIcon && icon.iconName
439
- ? !!this.#matIconRegistryService.addSvgIconLiteralInNamespace(namespace, icon.iconName, this.safeHtmlPipe.transform(icon.svgIcon))
440
- : false;
441
- });
442
- return allRegistered;
443
- });
444
534
  this.#layoutSettings.init();
445
- this.translate.onLangChange.subscribe(() => this._setCommands(true));
446
- // this.router.events.pipe(
447
- // // filter(e => e instanceof NavigationStart)
448
- // ).subscribe((e) => {
449
- // console.log(e);
450
- // });
451
535
  this.router.events
452
- .pipe(filter$1((e) => e instanceof NavigationEnd), map((e) => e))
536
+ .pipe(filter$1((e) => e instanceof NavigationEnd), map$1((e) => e))
453
537
  .subscribe((e) => this._processRouterNavigationEnd(e));
454
- this.#device.init();
455
538
  this.userService.user$.subscribe((user) => {
456
539
  if (user) {
457
540
  this.checkedForInitialRoute = !(!this.user || this.user.id !== user.id);
@@ -459,14 +542,6 @@ class ClientShellComponent {
459
542
  }
460
543
  this.user = user;
461
544
  });
462
- window.addEventListener('storage', (evt) => {
463
- if (evt.key === this.APP_LOGOUT_EVENT_KEY) {
464
- this.appLogout(true);
465
- }
466
- });
467
- }
468
- getAppTitle(a) {
469
- return a.title || '';
470
545
  }
471
546
  handleCustomActionButtonClick(button) {
472
547
  button.onClick({ router: this.router, config: this.#config });
@@ -483,27 +558,6 @@ class ClientShellComponent {
483
558
  return 0;
484
559
  return event.dataTransfer ? Array.from(event.dataTransfer.items || []).filter((i) => i.kind === 'file' && i.type).length : 0;
485
560
  }
486
- _setCommands(update) {
487
- const commands = this.apps().map((a, i) => ({
488
- id: `nav.app.${i}`,
489
- label: this.translate.instant('yuv.shell.cmd.app.open', { title: a.title }),
490
- callback: () => this.router.navigate([a.path])
491
- }));
492
- commands.push({
493
- id: `nav.shell.settings`,
494
- label: this.translate.instant('yuv.shell.cmd.open.settings'),
495
- callback: () => this.router.navigate(['settings'])
496
- });
497
- commands.push({
498
- id: `nav.shell.logout`,
499
- label: this.translate.instant('yuv.shell.cmd.logout'),
500
- callback: () => this.appLogout()
501
- });
502
- if (update)
503
- this.commandPalette.updateCommands(commands);
504
- else
505
- this.commandPalette.registerCommands(commands);
506
- }
507
561
  requestSubscription() {
508
562
  if (!this.#swPush.isEnabled) {
509
563
  return;
@@ -515,13 +569,6 @@ class ClientShellComponent {
515
569
  }))
516
570
  .subscribe();
517
571
  }
518
- appLogout(triggeredFromOtherTab) {
519
- if (!triggeredFromOtherTab) {
520
- // send storage event to logout all other open tabs
521
- window.localStorage.setItem(this.APP_LOGOUT_EVENT_KEY, `${Date.now()}`);
522
- }
523
- this.userService.logout('');
524
- }
525
572
  _processRouterNavigationEnd(e) {
526
573
  if (!this.checkedForInitialRoute) {
527
574
  this.checkedForInitialRoute = true;
@@ -533,7 +580,7 @@ class ClientShellComponent {
533
580
  // get persisted routes to decide where to redirect the logged in user to
534
581
  this.auth
535
582
  .getInitialRequestUri()
536
- .pipe(switchMap$1((res) => this.auth.resetInitialRequestUri().pipe(map((_) => res))))
583
+ .pipe(switchMap$1((res) => this.auth.resetInitialRequestUri().pipe(map$1((_) => res))))
537
584
  .subscribe((res) => {
538
585
  const loginRes = res && !ignoreRoutes.includes(res.uri) ? res : null;
539
586
  if (loginRes)
@@ -546,16 +593,16 @@ class ClientShellComponent {
546
593
  return `${Utils.getBaseHref()}${r}`.replace('//', '/');
547
594
  }
548
595
  ngOnInit() {
596
+ this.#device.init(this.supportsSmallScreens());
549
597
  this.#shell._init();
550
598
  clientShellRoutes.forEach((route) => {
551
599
  this.router.config.push(route);
552
600
  });
553
601
  this.router.resetConfig(this.router.config);
554
- this._setCommands();
555
602
  this.requestSubscription();
556
603
  }
557
604
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ClientShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
558
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: ClientShellComponent, isStandalone: true, selector: "yuv-client-shell", inputs: { apps: { classPropertyName: "apps", publicName: "apps", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:focusin": "onFocusChange($event)", "dragover": "onDragOver($event)" } }, providers: [SafeHtmlPipe], ngImport: i0, template: "<yuv-metadata-default-templates></yuv-metadata-default-templates>\n\n@let iconsRegistered = registerIcons();\n@let showAppHeader = css.appHeader();\n\n<!-- gloabl busy indicator if app header is present, it will deal with busy indication -->\n@if (!showAppHeader && (busy$ | async)) {\n <mat-progress-bar mode=\"indeterminate\" class=\"progress-bar\"></mat-progress-bar>\n}\n@if (showAppHeader) {\n <yuv-app-header> </yuv-app-header>\n}\n\n<yuv-sidebar-nav class=\"shell-nav\">\n @if (iconsRegistered && !showAppHeader) {\n <yuv-shell-logo slot=\"shell-logo\" class=\"shell-nav__shell-logo\" />\n }\n <section slot=\"app-switcher\" class=\"shell-nav__app-switcher-wrapper\" [attr.aria-label]=\"'yuv.shell.app-switcher.aria.label' | translate\">\n <ul class=\"shell-nav__app-switcher\">\n @for (a of navApps(); track a.path) {\n <li class=\"shell-nav__app-switcher-item\" routerLinkActive=\"shell-nav__app-switcher-item--active\">\n <button class=\"shell-nav__app-switcher-link\" ymt-icon-button [routerLink]=\"a.path\" matTooltipPosition=\"after\" [matTooltip]=\"getAppTitle(a)\">\n @if (a.svgIcon) {\n @if (iconsRegistered) {\n <mat-icon [svgIcon]=\"'shellIcons:' + a.iconName\"></mat-icon>\n }\n } @else {\n <mat-icon>{{ a.iconName }}</mat-icon>\n }\n <!--@if (getNotifications(a.id)) {\n <div [ngClass]=\"'badge ' + getNotifications(a.id).maxLevel\">{{ getNotifications(a.id).count }}</div>\n }-->\n </button>\n </li>\n }\n </ul>\n </section>\n\n <section slot=\"shell-actions\" class=\"shell-nav__actions\" [attr.aria-label]=\"'yuv.shell.shell-actions.aria.label' | translate\">\n @if (showNotifications()) {\n <button\n class=\"shell-nav__actions-button\"\n ymt-icon-button\n [routerLink]=\"[{ outlets: { aside: 'notifications' } }]\"\n routerLinkActive=\"active\"\n matTooltipPosition=\"after\"\n [matTooltip]=\"'yuv.shell.notifications.title' | translate\"\n >\n <mat-icon>notifications</mat-icon>\n @if (newNotifications(); as note) {\n <div class=\"badge {{ note.maxLevel }}\">{{ note.count }}</div>\n }\n </button>\n }\n @for (customButton of customActionButtons; track customButton.iconName) {\n <button\n class=\"shell-nav__actions-button\"\n ymt-icon-button\n (click)=\"handleCustomActionButtonClick(customButton)\"\n matTooltipPosition=\"after\"\n [matTooltip]=\"customButton.tooltip ? (customButton.tooltip | translate) : ''\"\n >\n <mat-icon>{{ customButton.iconName }}</mat-icon>\n </button>\n }\n <button\n class=\"shell-nav__actions-button\"\n ymt-icon-button\n [routerLink]=\"['/settings']\"\n routerLinkActive=\"active\"\n [matTooltip]=\"'yuv.shell.settings.title' | translate\"\n >\n <mat-icon>settings</mat-icon>\n </button>\n <button class=\"shell-nav__actions-button\" ymt-icon-button (click)=\"appLogout()\" [matTooltip]=\"'yuv.shell.cmd.logout' | translate\">\n <mat-icon>power_settings_new</mat-icon>\n </button>\n </section>\n</yuv-sidebar-nav>\n\n<main id=\"main-shell_content\" aria-label=\"main shell content\" [inert]=\"asideOutlet.isActivated\">\n <router-outlet></router-outlet>\n</main>\n\n<!-- outlet for aside modals like notifications -->\n<div class=\"asideOutlet\" [hidden]=\"!asideOutlet.isActivated\">\n <router-outlet name=\"aside\" #asideOutlet=\"outlet\"></router-outlet>\n</div>\n\n<div id=\"fi\" inert></div>\n", styles: [".sidenav{height:100%;width:var(--yuv-side-nav-width, var(--ymt-sizing-6xl));background:transparent;color:inherit;display:flex;flex-direction:column;justify-content:flex-start;align-items:center;gap:var(--ymt-spacing-s)}.sidenav .logo-container{width:100%}.sidenav .app-switcher-container{flex:1 1 0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--ymt-spacing-xs);scrollbar-width:none;-ms-overflow-style:none}.sidenav .app-switcher-container::-webkit-scrollbar{display:none}.sidenav .actions-container{justify-self:flex-end;border-top:var(--ymt-outline-width) solid var(--ymt-outline-variant);display:flex;flex-direction:column;gap:var(--ymt-spacing-s);padding:var(--ymt-spacing-m) 0}:host{position:absolute;inset:0;overflow:hidden;background-color:var(--ymt-surface-app);display:grid;grid-template-rows:auto 1fr;grid-template-columns:auto 1fr;grid-template-areas:\"appheader appheader\" \"nav main\"}:host .progress-bar{position:absolute}:host yuv-app-header{grid-area:appheader}:host .shell-nav{grid-area:nav}:host .shell-nav__app-switcher-wrapper{display:contents}:host .shell-nav__app-switcher{display:contents;list-style:none}:host .shell-nav__app-switcher-item{position:relative;padding:var(--ymt-spacing-2xs) 0}:host .shell-nav__app-switcher-item:after{content:\"\";position:absolute;inset:0;top:var(--ymt-spacing-2xs);aspect-ratio:1/1;background-color:var(--mat-icon-button-state-layer-color, var(--mat-sys-on-surface-variant));border-radius:var(--ymt-corner-full);visibility:hidden;opacity:0}:host .shell-nav__app-switcher-item:before{content:\"\";height:var(--ymt-sizing-3xs);width:var(--ymt-sizing-xs);border-radius:var(--ymt-corner-full);position:absolute;left:50%;bottom:0;transform:translate3d(-50%,0,0);background-color:transparent;transition:background-color .1s ease-in-out}:host .shell-nav__app-switcher-item--active:before{background-color:var(--ymt-primary)}:host .shell-nav__app-switcher-item--active:after{visibility:visible;opacity:.08;pointer-events:none}:host .shell-nav__actions{display:contents}:host main{grid-area:main;overflow:hidden;color:var(--ymt-text-color)}:host .asideOutlet{position:absolute;inset:0;padding-inline-start:var(--yuv-side-nav-width, var(--ymt-sizing-6xl))}:host-context([data-screen-size=s][data-screen-orientation=portrait]){flex-flow:column-reverse}:host-context([data-screen-size=s][data-screen-orientation=portrait]) main{overflow:hidden}:host-context([data-screen-size=s][data-screen-orientation=portrait]) .asideOutlet{padding-inline-start:0}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i2$1.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: YuvMetadataFormDefaultsModule }, { kind: "component", type: i3.MetadataDefaultTemplatesComponent, selector: "yuv-metadata-default-templates" }, { kind: "component", type: YuvAppHeaderComponent, selector: "yuv-app-header" }, { kind: "component", type: SidebarNavComponent, selector: "yuv-sidebar-nav" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ShellLogoComponent, selector: "yuv-shell-logo" }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }] }); }
605
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: ClientShellComponent, isStandalone: true, selector: "yuv-client-shell", inputs: { apps: { classPropertyName: "apps", publicName: "apps", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, supportsSmallScreens: { classPropertyName: "supportsSmallScreens", publicName: "supportsSmallScreens", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "dragover": "onDragOver($event)" }, properties: { "class.small-screen": "smallScreenLayout()" } }, providers: [SafeHtmlPipe], ngImport: i0, template: "<yuv-metadata-default-templates></yuv-metadata-default-templates>\n\n<!-- @let iconsRegistered = registerIcons(); -->\n@let showAppHeader = css.appHeader();\n\n<!-- gloabl busy indicator if app header is present, it will deal with busy indication -->\n@if (!showAppHeader && (busy$ | async)) {\n <mat-progress-bar mode=\"indeterminate\" class=\"progress-bar\"></mat-progress-bar>\n}\n@if (showAppHeader) {\n <yuv-app-header> </yuv-app-header>\n}\n<yuv-sidebar-nav></yuv-sidebar-nav>\n\n<main id=\"main-shell_content\" aria-label=\"main shell content\" [inert]=\"asideOutlet.isActivated\">\n <router-outlet></router-outlet>\n</main>\n\n<!-- outlet for aside modals like notifications -->\n<div class=\"asideOutlet\" [hidden]=\"!asideOutlet.isActivated\">\n <router-outlet name=\"aside\" #asideOutlet=\"outlet\"></router-outlet>\n</div>\n\n<div id=\"fi\" inert></div>\n", styles: [":host{display:block}:host.small-screen nav .menu{display:flex;align-items:center;gap:var(--ymt-spacing-s);padding:var(--ymt-sizing-s) var(--ymt-sizing-m)}:host:not(.small-screen) .sidenav{height:100%;width:var(--yuv-side-nav-width, var(--ymt-sizing-6xl));background:transparent;color:inherit;display:flex;flex-direction:column;justify-content:flex-start;align-items:center;gap:var(--ymt-spacing-s)}:host:not(.small-screen) .sidenav .logo-container{width:100%}:host:not(.small-screen) .sidenav .app-switcher-container{flex:1 1 0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--ymt-spacing-xs);scrollbar-width:none;-ms-overflow-style:none}:host:not(.small-screen) .sidenav .app-switcher-container::-webkit-scrollbar{display:none}:host:not(.small-screen) .sidenav .actions-container{justify-self:flex-end;border-top:var(--ymt-outline-width) solid var(--ymt-outline-variant);display:flex;flex-direction:column;gap:var(--ymt-spacing-s);padding:var(--ymt-spacing-m) 0}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-wrapper{display:contents}:host:not(.small-screen) .sidenav .shell-nav__app-switcher{display:contents;list-style:none}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item{position:relative;padding:var(--ymt-spacing-2xs) 0}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item:after{content:\"\";position:absolute;inset:0;top:var(--ymt-spacing-2xs);aspect-ratio:1/1;background-color:var(--mat-icon-button-state-layer-color, var(--mat-sys-on-surface-variant));border-radius:var(--ymt-corner-full);visibility:hidden;opacity:0}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item:before{content:\"\";height:var(--ymt-sizing-3xs);width:var(--ymt-sizing-xs);border-radius:var(--ymt-corner-full);position:absolute;left:50%;bottom:0;transform:translate3d(-50%,0,0);background-color:transparent;transition:background-color .1s ease-in-out}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item--active:before{background-color:var(--ymt-primary)}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item--active:after{visibility:visible;opacity:.08;pointer-events:none}:host:not(.small-screen) .sidenav .shell-nav__actions{display:contents}:host{position:absolute;inset:0;overflow:hidden;background-color:var(--ymt-surface-app);display:grid;grid-template-rows:auto 1fr;grid-template-columns:auto 1fr;grid-template-areas:\"appheader appheader\" \"nav main\"}:host.small-screen{grid-template-rows:auto 1fr auto;grid-template-columns:1fr;grid-template-areas:\"appheader\" \"main\" \"nav\"}:host .progress-bar{position:absolute}:host yuv-app-header{grid-area:appheader}:host yuv-sidebar-nav{grid-area:nav}:host main{grid-area:main;overflow:hidden;color:var(--ymt-text-color)}:host .asideOutlet{position:absolute;inset:0;padding-inline-start:var(--yuv-side-nav-width, var(--ymt-sizing-6xl))}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i6.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "ngmodule", type: YuvMetadataFormDefaultsModule }, { kind: "component", type: i2$3.MetadataDefaultTemplatesComponent, selector: "yuv-metadata-default-templates" }, { kind: "component", type: YuvAppHeaderComponent, selector: "yuv-app-header" }, { kind: "component", type: SidebarNavComponent, selector: "yuv-sidebar-nav" }, { kind: "component", type: MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatTooltipModule }] }); }
559
606
  }
560
607
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ClientShellComponent, decorators: [{
561
608
  type: Component,
@@ -566,16 +613,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
566
613
  YuvMetadataFormDefaultsModule,
567
614
  YuvAppHeaderComponent,
568
615
  SidebarNavComponent,
569
- MatIcon,
570
616
  MatProgressBar,
571
- MatTooltipModule,
572
- ShellLogoComponent,
573
- YmtIconButtonDirective
574
- ], providers: [SafeHtmlPipe], template: "<yuv-metadata-default-templates></yuv-metadata-default-templates>\n\n@let iconsRegistered = registerIcons();\n@let showAppHeader = css.appHeader();\n\n<!-- gloabl busy indicator if app header is present, it will deal with busy indication -->\n@if (!showAppHeader && (busy$ | async)) {\n <mat-progress-bar mode=\"indeterminate\" class=\"progress-bar\"></mat-progress-bar>\n}\n@if (showAppHeader) {\n <yuv-app-header> </yuv-app-header>\n}\n\n<yuv-sidebar-nav class=\"shell-nav\">\n @if (iconsRegistered && !showAppHeader) {\n <yuv-shell-logo slot=\"shell-logo\" class=\"shell-nav__shell-logo\" />\n }\n <section slot=\"app-switcher\" class=\"shell-nav__app-switcher-wrapper\" [attr.aria-label]=\"'yuv.shell.app-switcher.aria.label' | translate\">\n <ul class=\"shell-nav__app-switcher\">\n @for (a of navApps(); track a.path) {\n <li class=\"shell-nav__app-switcher-item\" routerLinkActive=\"shell-nav__app-switcher-item--active\">\n <button class=\"shell-nav__app-switcher-link\" ymt-icon-button [routerLink]=\"a.path\" matTooltipPosition=\"after\" [matTooltip]=\"getAppTitle(a)\">\n @if (a.svgIcon) {\n @if (iconsRegistered) {\n <mat-icon [svgIcon]=\"'shellIcons:' + a.iconName\"></mat-icon>\n }\n } @else {\n <mat-icon>{{ a.iconName }}</mat-icon>\n }\n <!--@if (getNotifications(a.id)) {\n <div [ngClass]=\"'badge ' + getNotifications(a.id).maxLevel\">{{ getNotifications(a.id).count }}</div>\n }-->\n </button>\n </li>\n }\n </ul>\n </section>\n\n <section slot=\"shell-actions\" class=\"shell-nav__actions\" [attr.aria-label]=\"'yuv.shell.shell-actions.aria.label' | translate\">\n @if (showNotifications()) {\n <button\n class=\"shell-nav__actions-button\"\n ymt-icon-button\n [routerLink]=\"[{ outlets: { aside: 'notifications' } }]\"\n routerLinkActive=\"active\"\n matTooltipPosition=\"after\"\n [matTooltip]=\"'yuv.shell.notifications.title' | translate\"\n >\n <mat-icon>notifications</mat-icon>\n @if (newNotifications(); as note) {\n <div class=\"badge {{ note.maxLevel }}\">{{ note.count }}</div>\n }\n </button>\n }\n @for (customButton of customActionButtons; track customButton.iconName) {\n <button\n class=\"shell-nav__actions-button\"\n ymt-icon-button\n (click)=\"handleCustomActionButtonClick(customButton)\"\n matTooltipPosition=\"after\"\n [matTooltip]=\"customButton.tooltip ? (customButton.tooltip | translate) : ''\"\n >\n <mat-icon>{{ customButton.iconName }}</mat-icon>\n </button>\n }\n <button\n class=\"shell-nav__actions-button\"\n ymt-icon-button\n [routerLink]=\"['/settings']\"\n routerLinkActive=\"active\"\n [matTooltip]=\"'yuv.shell.settings.title' | translate\"\n >\n <mat-icon>settings</mat-icon>\n </button>\n <button class=\"shell-nav__actions-button\" ymt-icon-button (click)=\"appLogout()\" [matTooltip]=\"'yuv.shell.cmd.logout' | translate\">\n <mat-icon>power_settings_new</mat-icon>\n </button>\n </section>\n</yuv-sidebar-nav>\n\n<main id=\"main-shell_content\" aria-label=\"main shell content\" [inert]=\"asideOutlet.isActivated\">\n <router-outlet></router-outlet>\n</main>\n\n<!-- outlet for aside modals like notifications -->\n<div class=\"asideOutlet\" [hidden]=\"!asideOutlet.isActivated\">\n <router-outlet name=\"aside\" #asideOutlet=\"outlet\"></router-outlet>\n</div>\n\n<div id=\"fi\" inert></div>\n", styles: [".sidenav{height:100%;width:var(--yuv-side-nav-width, var(--ymt-sizing-6xl));background:transparent;color:inherit;display:flex;flex-direction:column;justify-content:flex-start;align-items:center;gap:var(--ymt-spacing-s)}.sidenav .logo-container{width:100%}.sidenav .app-switcher-container{flex:1 1 0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--ymt-spacing-xs);scrollbar-width:none;-ms-overflow-style:none}.sidenav .app-switcher-container::-webkit-scrollbar{display:none}.sidenav .actions-container{justify-self:flex-end;border-top:var(--ymt-outline-width) solid var(--ymt-outline-variant);display:flex;flex-direction:column;gap:var(--ymt-spacing-s);padding:var(--ymt-spacing-m) 0}:host{position:absolute;inset:0;overflow:hidden;background-color:var(--ymt-surface-app);display:grid;grid-template-rows:auto 1fr;grid-template-columns:auto 1fr;grid-template-areas:\"appheader appheader\" \"nav main\"}:host .progress-bar{position:absolute}:host yuv-app-header{grid-area:appheader}:host .shell-nav{grid-area:nav}:host .shell-nav__app-switcher-wrapper{display:contents}:host .shell-nav__app-switcher{display:contents;list-style:none}:host .shell-nav__app-switcher-item{position:relative;padding:var(--ymt-spacing-2xs) 0}:host .shell-nav__app-switcher-item:after{content:\"\";position:absolute;inset:0;top:var(--ymt-spacing-2xs);aspect-ratio:1/1;background-color:var(--mat-icon-button-state-layer-color, var(--mat-sys-on-surface-variant));border-radius:var(--ymt-corner-full);visibility:hidden;opacity:0}:host .shell-nav__app-switcher-item:before{content:\"\";height:var(--ymt-sizing-3xs);width:var(--ymt-sizing-xs);border-radius:var(--ymt-corner-full);position:absolute;left:50%;bottom:0;transform:translate3d(-50%,0,0);background-color:transparent;transition:background-color .1s ease-in-out}:host .shell-nav__app-switcher-item--active:before{background-color:var(--ymt-primary)}:host .shell-nav__app-switcher-item--active:after{visibility:visible;opacity:.08;pointer-events:none}:host .shell-nav__actions{display:contents}:host main{grid-area:main;overflow:hidden;color:var(--ymt-text-color)}:host .asideOutlet{position:absolute;inset:0;padding-inline-start:var(--yuv-side-nav-width, var(--ymt-sizing-6xl))}:host-context([data-screen-size=s][data-screen-orientation=portrait]){flex-flow:column-reverse}:host-context([data-screen-size=s][data-screen-orientation=portrait]) main{overflow:hidden}:host-context([data-screen-size=s][data-screen-orientation=portrait]) .asideOutlet{padding-inline-start:0}\n"] }]
575
- }], ctorParameters: () => [], propDecorators: { onFocusChange: [{
576
- type: HostListener,
577
- args: ['document:focusin', ['$event']]
578
- }], onDragOver: [{
617
+ MatTooltipModule
618
+ ], providers: [SafeHtmlPipe], host: {
619
+ '[class.small-screen]': 'smallScreenLayout()'
620
+ }, template: "<yuv-metadata-default-templates></yuv-metadata-default-templates>\n\n<!-- @let iconsRegistered = registerIcons(); -->\n@let showAppHeader = css.appHeader();\n\n<!-- gloabl busy indicator if app header is present, it will deal with busy indication -->\n@if (!showAppHeader && (busy$ | async)) {\n <mat-progress-bar mode=\"indeterminate\" class=\"progress-bar\"></mat-progress-bar>\n}\n@if (showAppHeader) {\n <yuv-app-header> </yuv-app-header>\n}\n<yuv-sidebar-nav></yuv-sidebar-nav>\n\n<main id=\"main-shell_content\" aria-label=\"main shell content\" [inert]=\"asideOutlet.isActivated\">\n <router-outlet></router-outlet>\n</main>\n\n<!-- outlet for aside modals like notifications -->\n<div class=\"asideOutlet\" [hidden]=\"!asideOutlet.isActivated\">\n <router-outlet name=\"aside\" #asideOutlet=\"outlet\"></router-outlet>\n</div>\n\n<div id=\"fi\" inert></div>\n", styles: [":host{display:block}:host.small-screen nav .menu{display:flex;align-items:center;gap:var(--ymt-spacing-s);padding:var(--ymt-sizing-s) var(--ymt-sizing-m)}:host:not(.small-screen) .sidenav{height:100%;width:var(--yuv-side-nav-width, var(--ymt-sizing-6xl));background:transparent;color:inherit;display:flex;flex-direction:column;justify-content:flex-start;align-items:center;gap:var(--ymt-spacing-s)}:host:not(.small-screen) .sidenav .logo-container{width:100%}:host:not(.small-screen) .sidenav .app-switcher-container{flex:1 1 0;overflow-y:auto;display:flex;flex-direction:column;gap:var(--ymt-spacing-xs);scrollbar-width:none;-ms-overflow-style:none}:host:not(.small-screen) .sidenav .app-switcher-container::-webkit-scrollbar{display:none}:host:not(.small-screen) .sidenav .actions-container{justify-self:flex-end;border-top:var(--ymt-outline-width) solid var(--ymt-outline-variant);display:flex;flex-direction:column;gap:var(--ymt-spacing-s);padding:var(--ymt-spacing-m) 0}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-wrapper{display:contents}:host:not(.small-screen) .sidenav .shell-nav__app-switcher{display:contents;list-style:none}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item{position:relative;padding:var(--ymt-spacing-2xs) 0}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item:after{content:\"\";position:absolute;inset:0;top:var(--ymt-spacing-2xs);aspect-ratio:1/1;background-color:var(--mat-icon-button-state-layer-color, var(--mat-sys-on-surface-variant));border-radius:var(--ymt-corner-full);visibility:hidden;opacity:0}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item:before{content:\"\";height:var(--ymt-sizing-3xs);width:var(--ymt-sizing-xs);border-radius:var(--ymt-corner-full);position:absolute;left:50%;bottom:0;transform:translate3d(-50%,0,0);background-color:transparent;transition:background-color .1s ease-in-out}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item--active:before{background-color:var(--ymt-primary)}:host:not(.small-screen) .sidenav .shell-nav__app-switcher-item--active:after{visibility:visible;opacity:.08;pointer-events:none}:host:not(.small-screen) .sidenav .shell-nav__actions{display:contents}:host{position:absolute;inset:0;overflow:hidden;background-color:var(--ymt-surface-app);display:grid;grid-template-rows:auto 1fr;grid-template-columns:auto 1fr;grid-template-areas:\"appheader appheader\" \"nav main\"}:host.small-screen{grid-template-rows:auto 1fr auto;grid-template-columns:1fr;grid-template-areas:\"appheader\" \"main\" \"nav\"}:host .progress-bar{position:absolute}:host yuv-app-header{grid-area:appheader}:host yuv-sidebar-nav{grid-area:nav}:host main{grid-area:main;overflow:hidden;color:var(--ymt-text-color)}:host .asideOutlet{position:absolute;inset:0;padding-inline-start:var(--yuv-side-nav-width, var(--ymt-sizing-6xl))}\n"] }]
621
+ }], ctorParameters: () => [], propDecorators: { onDragOver: [{
579
622
  type: HostListener,
580
623
  args: ['dragover', ['$event']]
581
624
  }] } });