@yuuvis/client-shell 0.6.5

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.
Files changed (41) hide show
  1. package/README.md +94 -0
  2. package/esm2022/index.mjs +6 -0
  3. package/esm2022/lib/actions/manage-flavors/manage-flavors.action.mjs +39 -0
  4. package/esm2022/lib/actions/manage-flavors/manage-flavors.component.mjs +69 -0
  5. package/esm2022/lib/client-shell.component.mjs +191 -0
  6. package/esm2022/lib/components/app-logo/app-logo.component.mjs +18 -0
  7. package/esm2022/lib/directives/inert.directive.mjs +26 -0
  8. package/esm2022/lib/lib.routes.mjs +15 -0
  9. package/esm2022/lib/pages/dashboard/dashboard.component.mjs +11 -0
  10. package/esm2022/lib/pages/notifications/notifications.component.mjs +82 -0
  11. package/esm2022/lib/pages/settings/settings.component.mjs +71 -0
  12. package/esm2022/lib/pages/web-share-target/web-share-target.component.mjs +18 -0
  13. package/esm2022/widget-dashboard/index.mjs +3 -0
  14. package/esm2022/widget-dashboard/lib/widget-dashboard.component.mjs +46 -0
  15. package/esm2022/widget-dashboard/lib/widget-dashboard.config.mjs +3 -0
  16. package/esm2022/widget-dashboard/lib/widget-dashboard.module.mjs +49 -0
  17. package/esm2022/widget-dashboard/yuuvis-client-shell-widget-dashboard.mjs +5 -0
  18. package/esm2022/yuuvis-client-shell.mjs +5 -0
  19. package/fesm2022/yuuvis-client-shell-widget-dashboard.mjs +98 -0
  20. package/fesm2022/yuuvis-client-shell-widget-dashboard.mjs.map +1 -0
  21. package/fesm2022/yuuvis-client-shell.mjs +496 -0
  22. package/fesm2022/yuuvis-client-shell.mjs.map +1 -0
  23. package/index.d.ts +5 -0
  24. package/lib/actions/manage-flavors/manage-flavors.action.d.ts +17 -0
  25. package/lib/actions/manage-flavors/manage-flavors.component.d.ts +17 -0
  26. package/lib/assets/i18n/de.json +30 -0
  27. package/lib/assets/i18n/en.json +30 -0
  28. package/lib/client-shell.component.d.ts +42 -0
  29. package/lib/components/app-logo/app-logo.component.d.ts +7 -0
  30. package/lib/directives/inert.directive.d.ts +8 -0
  31. package/lib/lib.routes.d.ts +2 -0
  32. package/lib/pages/dashboard/dashboard.component.d.ts +5 -0
  33. package/lib/pages/notifications/notifications.component.d.ts +29 -0
  34. package/lib/pages/settings/settings.component.d.ts +28 -0
  35. package/lib/pages/web-share-target/web-share-target.component.d.ts +6 -0
  36. package/package.json +40 -0
  37. package/widget-dashboard/README.md +19 -0
  38. package/widget-dashboard/index.d.ts +2 -0
  39. package/widget-dashboard/lib/widget-dashboard.component.d.ts +16 -0
  40. package/widget-dashboard/lib/widget-dashboard.config.d.ts +7 -0
  41. package/widget-dashboard/lib/widget-dashboard.module.d.ts +13 -0
@@ -0,0 +1,496 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, ElementRef, input, effect, Directive, Component, signal, HostListener } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { DOCUMENT, CommonModule, NgIf, AsyncPipe } from '@angular/common';
5
+ import { toSignal } from '@angular/core/rxjs-interop';
6
+ import * as i2$2 from '@angular/router';
7
+ import { Router, NavigationEnd, RouterModule } from '@angular/router';
8
+ import { SwPush } from '@angular/service-worker';
9
+ import * as i2 from '@yuuvis/client-core';
10
+ import { UserService, ConfigService, TranslateService, TranslateModule, Utils, LocaleDatePipe, AuthService, DeviceService, UserRoles, DmsService } from '@yuuvis/client-core';
11
+ import { MetadataDefaultTemplatesComponent } from '@yuuvis/client-framework/metadata-form';
12
+ import { ShellService, ShellNotificationsService, CommandPaletteService } from '@yuuvis/client-shell-core';
13
+ import * as i2$1 from '@yuuvis/components/icon';
14
+ import { ICONS, YvcIconModule } from '@yuuvis/components/icon';
15
+ import { map, of, tap as tap$1 } from 'rxjs';
16
+ import { tap, map as map$1, filter, catchError, switchMap } from 'rxjs/operators';
17
+ import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
18
+ import { CdkTrapFocus } from '@angular/cdk/a11y';
19
+ import { LightDismissDirective, BusyOverlayDirective } from '@yuuvis/client-framework/common';
20
+ import { YUV_ICONS } from '@yuuvis/client-framework/icons';
21
+ import { ListComponent, ListItemDirective } from '@yuuvis/client-framework/list';
22
+ import { AbstractContextAction, ACTION_ICON, SelectionRange } from '@yuuvis/client-framework/actions';
23
+ import { YvcOverlayRef, YvcOverlayService } from '@yuuvis/components/overlay';
24
+ import { FlavorChipComponent } from '@yuuvis/client-framework/object-flavor';
25
+
26
+ class InertDirective {
27
+ constructor() {
28
+ this.elRef = inject(ElementRef);
29
+ this.inert = input(false);
30
+ effect(() => {
31
+ const el = this.elRef.nativeElement;
32
+ if (this.inert())
33
+ el.setAttribute('inert', 'true');
34
+ else
35
+ el.removeAttribute('inert');
36
+ });
37
+ }
38
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: InertDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
39
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.2.13", type: InertDirective, isStandalone: true, selector: "[inert]", inputs: { inert: { classPropertyName: "inert", publicName: "inert", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
40
+ }
41
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: InertDirective, decorators: [{
42
+ type: Directive,
43
+ args: [{
44
+ // eslint-disable-next-line @angular-eslint/directive-selector
45
+ selector: '[inert]',
46
+ standalone: true
47
+ }]
48
+ }], ctorParameters: () => [] });
49
+
50
+ class DashboardPageComponent {
51
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DashboardPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
52
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: DashboardPageComponent, selector: "yuv-dashboard", ngImport: i0, template: "yuuvis Momentum\n", styles: [":host{display:grid;align-items:center;justify-content:center;height:100%;overflow:hidden;color:var(--text-color-hint);font-size:var(--font-display)}\n"] }); }
53
+ }
54
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: DashboardPageComponent, decorators: [{
55
+ type: Component,
56
+ args: [{ selector: 'yuv-dashboard', template: "yuuvis Momentum\n", styles: [":host{display:grid;align-items:center;justify-content:center;height:100%;overflow:hidden;color:var(--text-color-hint);font-size:var(--font-display)}\n"] }]
57
+ }] });
58
+
59
+ class SettingsPageComponent {
60
+ constructor() {
61
+ this.userService = inject(UserService);
62
+ this.config = inject(ConfigService);
63
+ this.translate = inject(TranslateService);
64
+ this.shell = inject(ShellService);
65
+ this.document = inject(DOCUMENT);
66
+ this.#fb = inject(FormBuilder);
67
+ this.clientLocales = signal([]);
68
+ this.clientVersion = signal(undefined);
69
+ this.user = toSignal(this.userService.user$);
70
+ this.appSettingForms$ = this.shell.appSettings$.pipe(map((settings) => settings.map((e) => {
71
+ const x = {};
72
+ const fcn = e.properties.map((p) => ({
73
+ label: this.translate.instant(p.label) || p.label,
74
+ name: p.name,
75
+ type: p.type
76
+ }));
77
+ e.properties.forEach((p) => {
78
+ x[p.name] = [p.value];
79
+ });
80
+ return {
81
+ appID: e.appID,
82
+ label: e.label,
83
+ formControls: fcn,
84
+ form: this.#fb.group(x)
85
+ };
86
+ })));
87
+ }
88
+ #fb;
89
+ saveAppSettings(appID, form) {
90
+ this.userService
91
+ .saveUserSettings({
92
+ clientAppSettings: {
93
+ [appID]: form.value
94
+ }
95
+ })
96
+ .subscribe({
97
+ next: () => {
98
+ form.markAsPristine();
99
+ },
100
+ error: (err) => {
101
+ console.error('Error saving app settings', err);
102
+ }
103
+ });
104
+ }
105
+ changeClientLocale(iso) {
106
+ this.userService.changeClientLocale(iso);
107
+ }
108
+ ngOnInit() {
109
+ this.clientVersion.set(this.document.body.getAttribute('data-version') ?? 'dev');
110
+ this.clientLocales.set(this.config.getClientLocales());
111
+ }
112
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SettingsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
113
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: SettingsPageComponent, isStandalone: true, selector: "yuv-settings", ngImport: i0, template: "<header>\n <h1>{{ 'yuv.shell.settings.title' | translate }}</h1>\n <h4>{{ 'yuc.shell.settings.client.version' | translate }}: {{ clientVersion() }}</h4>\n</header>\n\n\n<main>@if (user()) {\n <section class=\"user\">\n <!-- <yuv-user-avatar class=\"background\" [user]=\"user()\"></yuv-user-avatar> -->\n <div class=\"user\">\n <div class=\"meta uname\">{{ user()!.username }}</div>\n <h2>{{ user()!.title }}</h2>\n <div class=\"meta uemail\">{{ user()!.email }}</div>\n <div class=\"meta utenant\">{{ 'yuv.shell.settings.tenant' | translate }}: {{ user()!.tenant }}</div>\n </div>\n </section>\n}\n <!-- language -->\n <section yuvOfflineDisabled>\n <div class=\"label\" translate>yuv.shell.settings.language</div>\n <div class=\"value buttons\">\n @for (locale of clientLocales(); track locale.iso) {\n <button class=\"toggle secondary\" (click)=\"changeClientLocale(locale.iso)\" [ngClass]=\"{ active: translate.currentLang === locale.iso }\">\n {{ locale.label }}\n </button>\n }\n </div>\n </section>\n\n <!-- app settings -->\n <!-- TODO: activate one feature is refined -->\n <!-- @for (c of appSettingForms$ | async; track $index) {\n <section>\n {{ c.label }}\n <form [formGroup]=\"c.form\" (ngSubmit)=\"saveAppSettings(c.appID, c.form)\">\n @for (n of c.formControls; track $index) {\n <label\n >{{ n.label }}\n\n @switch (n.type) {\n @case ('string') {\n <input type=\"text\" [formControlName]=\"n.name\" />\n }\n @case ('number') {\n <input type=\"number\" [formControlName]=\"n.name\" />\n }\n }\n </label>\n }\n <button [ngClass]=\"{ hideen: c.form.untouched }\" [disabled]=\"c.form.invalid\">Save</button>\n </form>\n </section>\n } -->\n</main>\n", styles: [":host{display:grid;grid-template-columns:1fr auto;grid-template-rows:auto 1fr;grid-template-areas:\"header\" \"settings\";height:100%;overflow:hidden;overflow-y:auto}:host header{grid-area:header;padding:calc(var(--app-pane-padding) * 2)}:host header h1,:host header h4{margin:0}:host header h1{font-size:var(--font-display);font-weight:400}:host header h4{font-size:var(--font-cation);font-weight:400;font-style:italic;color:var(--text-color-caption)}:host main{grid-area:settings;padding:var(--app-pane-padding);overflow-y:auto}:host main section{display:flex;flex-flow:column;padding:var(--app-pane-padding)}:host main section .label{margin-block-end:1em}:host main section .value{display:flex;flex-flow:row wrap;gap:calc(var(--app-pane-padding) / 4)}:host main section.user h2{margin:0 0 1rem;font-weight:400}:host button.active{background-color:var(--color-accent);color:var(--color-accent-tone);pointer-events:none}:host button.color.clear{padding:2px 8px}:host button.color.accent.active{outline:2px solid #fff;outline-offset:-2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }, { kind: "directive", type: i2.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "ngmodule", type: ReactiveFormsModule }] }); }
114
+ }
115
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SettingsPageComponent, decorators: [{
116
+ type: Component,
117
+ args: [{ selector: 'yuv-settings', standalone: true, imports: [CommonModule, TranslateModule, ReactiveFormsModule], template: "<header>\n <h1>{{ 'yuv.shell.settings.title' | translate }}</h1>\n <h4>{{ 'yuc.shell.settings.client.version' | translate }}: {{ clientVersion() }}</h4>\n</header>\n\n\n<main>@if (user()) {\n <section class=\"user\">\n <!-- <yuv-user-avatar class=\"background\" [user]=\"user()\"></yuv-user-avatar> -->\n <div class=\"user\">\n <div class=\"meta uname\">{{ user()!.username }}</div>\n <h2>{{ user()!.title }}</h2>\n <div class=\"meta uemail\">{{ user()!.email }}</div>\n <div class=\"meta utenant\">{{ 'yuv.shell.settings.tenant' | translate }}: {{ user()!.tenant }}</div>\n </div>\n </section>\n}\n <!-- language -->\n <section yuvOfflineDisabled>\n <div class=\"label\" translate>yuv.shell.settings.language</div>\n <div class=\"value buttons\">\n @for (locale of clientLocales(); track locale.iso) {\n <button class=\"toggle secondary\" (click)=\"changeClientLocale(locale.iso)\" [ngClass]=\"{ active: translate.currentLang === locale.iso }\">\n {{ locale.label }}\n </button>\n }\n </div>\n </section>\n\n <!-- app settings -->\n <!-- TODO: activate one feature is refined -->\n <!-- @for (c of appSettingForms$ | async; track $index) {\n <section>\n {{ c.label }}\n <form [formGroup]=\"c.form\" (ngSubmit)=\"saveAppSettings(c.appID, c.form)\">\n @for (n of c.formControls; track $index) {\n <label\n >{{ n.label }}\n\n @switch (n.type) {\n @case ('string') {\n <input type=\"text\" [formControlName]=\"n.name\" />\n }\n @case ('number') {\n <input type=\"number\" [formControlName]=\"n.name\" />\n }\n }\n </label>\n }\n <button [ngClass]=\"{ hideen: c.form.untouched }\" [disabled]=\"c.form.invalid\">Save</button>\n </form>\n </section>\n } -->\n</main>\n", styles: [":host{display:grid;grid-template-columns:1fr auto;grid-template-rows:auto 1fr;grid-template-areas:\"header\" \"settings\";height:100%;overflow:hidden;overflow-y:auto}:host header{grid-area:header;padding:calc(var(--app-pane-padding) * 2)}:host header h1,:host header h4{margin:0}:host header h1{font-size:var(--font-display);font-weight:400}:host header h4{font-size:var(--font-cation);font-weight:400;font-style:italic;color:var(--text-color-caption)}:host main{grid-area:settings;padding:var(--app-pane-padding);overflow-y:auto}:host main section{display:flex;flex-flow:column;padding:var(--app-pane-padding)}:host main section .label{margin-block-end:1em}:host main section .value{display:flex;flex-flow:row wrap;gap:calc(var(--app-pane-padding) / 4)}:host main section.user h2{margin:0 0 1rem;font-weight:400}:host button.active{background-color:var(--color-accent);color:var(--color-accent-tone);pointer-events:none}:host button.color.clear{padding:2px 8px}:host button.color.accent.active{outline:2px solid #fff;outline-offset:-2px}\n"] }]
118
+ }] });
119
+
120
+ class NotificationsPageComponent {
121
+ constructor() {
122
+ this.router = inject(Router);
123
+ this.shellNotifications = inject(ShellNotificationsService);
124
+ this.elRef = inject(ElementRef);
125
+ this._focusedIndex = -1;
126
+ this._notificationIDs = [];
127
+ this.notifications = toSignal(this.shellNotifications.shellNotifications$);
128
+ this.notificationsIdEffect = effect(() => (this._notificationIDs = (this.notifications() || []).map((n) => n.id)));
129
+ this.icons = {
130
+ close: ICONS.clear,
131
+ trash: YUV_ICONS.trash,
132
+ note: YUV_ICONS.notification
133
+ };
134
+ }
135
+ onKeydown(event) {
136
+ switch (event.code) {
137
+ case 'Delete': {
138
+ if (this._focusedIndex >= 0)
139
+ this.remove(this._notificationIDs[this._focusedIndex]);
140
+ break;
141
+ }
142
+ }
143
+ }
144
+ async itemSelected(idx) {
145
+ const n = this.shellNotifications.getNotificationById(this._notificationIDs[idx[0]]);
146
+ if (n?.targetRoute) {
147
+ await this.close();
148
+ this.router.navigateByUrl(n.targetRoute, { replaceUrl: true });
149
+ if (n.removeOnTargetRouteNavigated)
150
+ this.remove(n.id);
151
+ }
152
+ }
153
+ itemFocused(index) {
154
+ this._focusedIndex = index;
155
+ }
156
+ remove(id) {
157
+ if (id)
158
+ this.shellNotifications.remove(id);
159
+ }
160
+ removeAll() {
161
+ this.shellNotifications.removeAll();
162
+ }
163
+ close() {
164
+ return this.router.navigate([{ outlets: { aside: null } }], {
165
+ replaceUrl: true
166
+ });
167
+ }
168
+ ngOnInit() {
169
+ this.shellNotifications.markAllAsSeen();
170
+ setTimeout(() => {
171
+ const fc = Utils.getFocusableChildren(this.elRef.nativeElement);
172
+ if (fc.length)
173
+ fc[0].focus();
174
+ });
175
+ }
176
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NotificationsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
177
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", 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\"><yvc-icon [svg]=\"n.icon || icons.note\"></yvc-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 (click)=\"remove(n.id)\">\n <yvc-icon [svg]=\"icons.trash\"></yvc-icon>\n </button>\n </div>\n </div>\n }\n </yuv-list>\n\n <div class=\"actions\">\n <button [hidden]=\"!notifications()?.length\" class=\"icon secondary\" (click)=\"removeAll()\">\n {{ 'yuv.shell.notifications.button.remove.all' | translate }}<yvc-icon [svg]=\"icons.trash\"></yvc-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 class=\"icon close\" (click)=\"close()\">\n <yvc-icon [svg]=\"icons.close\"></yvc-icon>\n </button>\n</div>\n", styles: [":host{height:100%;display:flex}:host .notifications{background-color:var(--panel-background-lightgrey);border-inline-end:1px solid var(--panel-divider-color);height:100%;overflow:hidden;box-sizing:border-box;padding:var(--app-pane-padding);display:grid;grid-template-rows:auto auto 1fr;grid-template-columns:1fr auto;grid-template-areas:\"title close\" \"actions actions\" \"list list\";gap:var(--app-pane-padding);max-width:30vw;min-width:300px;box-shadow:8px 0 8px #0000001a;animation:dialogAppear .2s ease-in-out}:host .notifications h2{color:var(--text-color-caption);grid-area:title;margin:0;padding:0;align-self:center;font-size:var(--font-title);font-weight:400;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(--text-color-caption)}: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:calc(var(--app-pane-padding) / 4);column-gap:calc(var(--app-pane-padding) / 2);align-items:center;padding:calc(var(--app-pane-padding) / 2);padding-inline-start:var(--app-pane-padding);margin-block-end:calc(var(--app-pane-padding) / 2);background-color:var(--panel-background);outline:1px solid var(--panel-divider-color);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(--color-error)}:host .notifications .note.warning{--level-color: var(--color-warning)}:host .notifications .note.success{--level-color: var(--color-success)}:host .notifications .note:hover,:host .notifications .note[aria-current=true]{background-color:var(--item-focus-background-color)}:host .notifications .note:hover button,:host .notifications .note[aria-current=true] button{opacity:1}:host .notifications .note .icon{grid-area:icon;color:var(--text-color-caption)}:host .notifications .note .title{overflow:hidden;text-overflow:ellipsis;font-weight:700;grid-area:title;color:var(--text-color-body)}: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(--text-color-caption)}:host .notifications .note button{grid-area:actions;padding:0;opacity:0}:host .notifications .note button yvc-icon{width:18px;height:18px}@keyframes dialogAppear{0%{opacity:0;transform:translate(calc(var(--app-pane-padding) * -1))}to{opacity:1;transform:translate(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: LightDismissDirective, selector: "[yuvLightDismiss]", outputs: ["yuvLightDismiss"] }, { kind: "pipe", type: LocaleDatePipe, name: "localeDate" }, { kind: "ngmodule", type: YvcIconModule }, { kind: "component", type: i2$1.Icon, selector: "yvc-icon", inputs: ["label", "svg", "svgSrc"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: ListComponent, selector: "yuv-list", inputs: ["multiselect", "disableSelection"], outputs: ["itemSelect", "itemFocus"] }, { kind: "directive", type: ListItemDirective, selector: "[yuvListItem]", inputs: ["disabled", "active", "selected"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }] }); }
178
+ }
179
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NotificationsPageComponent, decorators: [{
180
+ type: Component,
181
+ args: [{ selector: 'yuv-notifications', standalone: true, imports: [CommonModule, LightDismissDirective, LocaleDatePipe, YvcIconModule, CdkTrapFocus, ListComponent, ListItemDirective, TranslateModule], 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\"><yvc-icon [svg]=\"n.icon || icons.note\"></yvc-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 (click)=\"remove(n.id)\">\n <yvc-icon [svg]=\"icons.trash\"></yvc-icon>\n </button>\n </div>\n </div>\n }\n </yuv-list>\n\n <div class=\"actions\">\n <button [hidden]=\"!notifications()?.length\" class=\"icon secondary\" (click)=\"removeAll()\">\n {{ 'yuv.shell.notifications.button.remove.all' | translate }}<yvc-icon [svg]=\"icons.trash\"></yvc-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 class=\"icon close\" (click)=\"close()\">\n <yvc-icon [svg]=\"icons.close\"></yvc-icon>\n </button>\n</div>\n", styles: [":host{height:100%;display:flex}:host .notifications{background-color:var(--panel-background-lightgrey);border-inline-end:1px solid var(--panel-divider-color);height:100%;overflow:hidden;box-sizing:border-box;padding:var(--app-pane-padding);display:grid;grid-template-rows:auto auto 1fr;grid-template-columns:1fr auto;grid-template-areas:\"title close\" \"actions actions\" \"list list\";gap:var(--app-pane-padding);max-width:30vw;min-width:300px;box-shadow:8px 0 8px #0000001a;animation:dialogAppear .2s ease-in-out}:host .notifications h2{color:var(--text-color-caption);grid-area:title;margin:0;padding:0;align-self:center;font-size:var(--font-title);font-weight:400;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(--text-color-caption)}: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:calc(var(--app-pane-padding) / 4);column-gap:calc(var(--app-pane-padding) / 2);align-items:center;padding:calc(var(--app-pane-padding) / 2);padding-inline-start:var(--app-pane-padding);margin-block-end:calc(var(--app-pane-padding) / 2);background-color:var(--panel-background);outline:1px solid var(--panel-divider-color);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(--color-error)}:host .notifications .note.warning{--level-color: var(--color-warning)}:host .notifications .note.success{--level-color: var(--color-success)}:host .notifications .note:hover,:host .notifications .note[aria-current=true]{background-color:var(--item-focus-background-color)}:host .notifications .note:hover button,:host .notifications .note[aria-current=true] button{opacity:1}:host .notifications .note .icon{grid-area:icon;color:var(--text-color-caption)}:host .notifications .note .title{overflow:hidden;text-overflow:ellipsis;font-weight:700;grid-area:title;color:var(--text-color-body)}: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(--text-color-caption)}:host .notifications .note button{grid-area:actions;padding:0;opacity:0}:host .notifications .note button yvc-icon{width:18px;height:18px}@keyframes dialogAppear{0%{opacity:0;transform:translate(calc(var(--app-pane-padding) * -1))}to{opacity:1;transform:translate(0)}}\n"] }]
182
+ }], propDecorators: { onKeydown: [{
183
+ type: HostListener,
184
+ args: ['keydown', ['$event']]
185
+ }] } });
186
+
187
+ class WebShareTargetPageComponent {
188
+ constructor() {
189
+ navigator.serviceWorker.onmessage = (e) => {
190
+ const file = e.data.file;
191
+ console.log(file);
192
+ };
193
+ }
194
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: WebShareTargetPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
195
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: WebShareTargetPageComponent, isStandalone: true, selector: "yuv-web-share-target", ngImport: i0, template: "<p>web-share-target works!</p>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] }); }
196
+ }
197
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: WebShareTargetPageComponent, decorators: [{
198
+ type: Component,
199
+ args: [{ selector: 'yuv-web-share-target', standalone: true, imports: [CommonModule], template: "<p>web-share-target works!</p>\n" }]
200
+ }], ctorParameters: () => [] });
201
+
202
+ const clientShellRoutes = [
203
+ { path: 'dashboard', component: DashboardPageComponent },
204
+ { path: 'notifications', component: NotificationsPageComponent, outlet: 'aside' },
205
+ { path: 'settings', component: SettingsPageComponent },
206
+ { path: 'web-share-target', component: WebShareTargetPageComponent },
207
+ // default route
208
+ { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
209
+ // redirecting route
210
+ { path: '**', redirectTo: '/' }
211
+ ];
212
+
213
+ class ClientShellComponent {
214
+ #shell;
215
+ #device;
216
+ #swPush;
217
+ onFocusChange(event) {
218
+ // console.log('focused: ', document.activeElement);
219
+ }
220
+ onDragOver(event) {
221
+ event.stopPropagation();
222
+ event.preventDefault();
223
+ this.showUploadOverlay = !!this._dragContainsFiles(event);
224
+ }
225
+ #appsEffect;
226
+ #shellConfigEffect;
227
+ constructor() {
228
+ this.router = inject(Router);
229
+ this.auth = inject(AuthService);
230
+ this.userService = inject(UserService);
231
+ this.#shell = inject(ShellService);
232
+ this.shellNotifications = inject(ShellNotificationsService);
233
+ this.translate = inject(TranslateService);
234
+ this.commandPalette = inject(CommandPaletteService);
235
+ this.#device = inject(DeviceService);
236
+ this.#swPush = inject(SwPush);
237
+ this.APP_LOGOUT_EVENT_KEY = 'yuv.app.event.logout';
238
+ this._levels = {
239
+ info: 0,
240
+ success: 1,
241
+ warning: 2,
242
+ alert: 3
243
+ };
244
+ this.showUploadOverlay = false;
245
+ this.busy$ = this.#shell.isBusy$;
246
+ this.showNotifications = signal(false);
247
+ this.newNotifications = toSignal(this.shellNotifications.shellNotifications$.pipe(tap((n) => this.showNotifications.set(n.length > 0)), map$1((notifications) => {
248
+ let maxLevel = 'info';
249
+ const count = notifications.filter((n) => !n.seen).length;
250
+ notifications.forEach((n) => (maxLevel = n.level && this._levels[n.level] > this._levels[maxLevel] ? n.level : maxLevel));
251
+ return count > 0 ? { maxLevel, count } : undefined;
252
+ })));
253
+ this.apps = input.required();
254
+ this.#appsEffect = effect(() => this.#shell.setAppBaseRoutes(this.apps()));
255
+ this.checkedForInitialRoute = false;
256
+ this.enableTenantSwitch = false;
257
+ this.config = input();
258
+ this.#shellConfigEffect = effect(() => {
259
+ const cfg = this.config();
260
+ if (cfg) {
261
+ this.#shell.setShellConfig(cfg);
262
+ }
263
+ }, {
264
+ allowSignalWrites: true
265
+ });
266
+ this.shellConfig = this.#shell.shellConfig;
267
+ this.translate.onLangChange.subscribe(() => this._setCommands(true));
268
+ // this.router.events.pipe(
269
+ // // filter(e => e instanceof NavigationStart)
270
+ // ).subscribe((e) => {
271
+ // console.log(e);
272
+ // });
273
+ this.router.events
274
+ .pipe(filter((e) => e instanceof NavigationEnd), map$1((e) => e))
275
+ .subscribe((e) => this._processRouterNavigationEnd(e));
276
+ this.#device.init();
277
+ this.userService.user$.subscribe((user) => {
278
+ if (user) {
279
+ this.checkedForInitialRoute = !(!this.user || this.user.id !== user.id);
280
+ this.enableTenantSwitch = user.authorities.includes(UserRoles.MULTI_TENANT);
281
+ }
282
+ this.user = user;
283
+ });
284
+ window.addEventListener('storage', (evt) => {
285
+ if (evt.key === this.APP_LOGOUT_EVENT_KEY) {
286
+ this.appLogout(true);
287
+ }
288
+ });
289
+ }
290
+ openNotifications() {
291
+ this.router.navigate([{}]);
292
+ }
293
+ // getNotifications(id: string) {
294
+ // return this.shellNotifications.getNotifications(id);
295
+ // }
296
+ _dragContainsFiles(event) {
297
+ // do not allow to drop files/images from iframes
298
+ if (event.relatedTarget?.tagName.toLowerCase() === 'iframe')
299
+ return 0;
300
+ return event.dataTransfer ? Array.from(event.dataTransfer.items || []).filter((i) => i.kind === 'file' && i.type).length : 0;
301
+ }
302
+ _setCommands(update) {
303
+ const commands = this.apps().map((a, i) => ({
304
+ id: `nav.app.${i}`,
305
+ label: this.translate.instant('yuv.shell.cmd.app.open', { title: a.title }),
306
+ callback: () => this.router.navigate([a.path])
307
+ }));
308
+ commands.push({
309
+ id: `nav.shell.settings`,
310
+ label: this.translate.instant('yuv.shell.cmd.open.settings'),
311
+ callback: () => this.router.navigate(['settings'])
312
+ });
313
+ commands.push({
314
+ id: `nav.shell.logout`,
315
+ label: this.translate.instant('yuv.shell.cmd.logout'),
316
+ callback: () => this.appLogout()
317
+ });
318
+ if (update)
319
+ this.commandPalette.updateCommands(commands);
320
+ else
321
+ this.commandPalette.registerCommands(commands);
322
+ }
323
+ requestSubscription() {
324
+ if (!this.#swPush.isEnabled) {
325
+ console.log('Notification is not enabled.');
326
+ return;
327
+ }
328
+ this.#swPush.messages
329
+ .pipe(tap((msg) => console.log({ msg })), catchError((error) => {
330
+ console.log({ error });
331
+ return of(null);
332
+ }))
333
+ .subscribe();
334
+ }
335
+ appLogout(triggeredFromOtherTab) {
336
+ if (!triggeredFromOtherTab) {
337
+ // send storage event to logout all other open tabs
338
+ window.localStorage.setItem(this.APP_LOGOUT_EVENT_KEY, `${Date.now()}`);
339
+ }
340
+ this.userService.logout('');
341
+ }
342
+ _processRouterNavigationEnd(e) {
343
+ if (!this.checkedForInitialRoute) {
344
+ this.checkedForInitialRoute = true;
345
+ // redirect to the page the user logged out from the last time
346
+ // but only if current route is not a deep link
347
+ const ignoreRoutes = ['', 'dashboard', 'index.html'].map((s) => `${Utils.getBaseHref()}${s}`.replace('//', '/'));
348
+ const currentRoute = this.routeWithBaseHref(this.router.routerState.snapshot.url);
349
+ if (this.userService.getCurrentUser() && !ignoreRoutes.includes(currentRoute)) {
350
+ // get persisted routes to decide where to redirect the logged in user to
351
+ this.auth
352
+ .getInitialRequestUri()
353
+ .pipe(switchMap((res) => this.auth.resetInitialRequestUri().pipe(map$1((_) => res))))
354
+ .subscribe((res) => {
355
+ const loginRes = res && !ignoreRoutes.includes(res.uri) ? res : null;
356
+ if (loginRes)
357
+ this.router.navigateByUrl(loginRes.uri);
358
+ });
359
+ }
360
+ }
361
+ }
362
+ routeWithBaseHref(r) {
363
+ return `${Utils.getBaseHref()}${r}`.replace('//', '/');
364
+ }
365
+ ngOnInit() {
366
+ this.#shell._init();
367
+ clientShellRoutes.forEach((route) => {
368
+ this.router.config.push(route);
369
+ });
370
+ this.router.resetConfig(this.router.config);
371
+ this._setCommands();
372
+ this.requestSubscription();
373
+ }
374
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ClientShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
375
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", 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)" } }, ngImport: i0, template: "<yuv-metadata-default-templates></yuv-metadata-default-templates>\n<!-- <yuv-focus-indicator /> -->\n\n<!-- gloabl busy indicator -->\n@if (busy$ | async) {\n <div class=\"yuv-loader-linear\"></div>\n}\n<nav class=\"apps\" [inert]=\"asideOutlet.isActivated\" [attr.aria-label]=\"'yuv.shell.naviagtion.aria.label' | translate\">\n <div class=\"app-icon\">\n <button routerLink=\"/\" routerLinkActive=\"active\" [attr.aria-label]=\"'yuv.shell.logo.aria.label' | translate\">\n <yvc-icon [svg]=\"shellConfig().icons!.appIcon!\"></yvc-icon>\n </button>\n </div>\n <ul class=\"apps\">\n @for (a of apps(); track a.path) {\n <li>\n <button routerLinkActive=\"active\" [routerLink]=\"a.path\" title=\"{{ a.title }}\">\n <yvc-icon [svg]=\"a.icon\" title=\"{{ a.title }}\" *ngIf=\"a.icon\"></yvc-icon>\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\n <section class=\"actions\">\n @if (showNotifications()) {\n <button [routerLink]=\"[{ outlets: { aside: 'notifications' } }]\" routerLinkActive=\"active\" title=\"{{ 'yuv.shell.notifications.title' | translate }}\">\n <yvc-icon [svg]=\"shellConfig().icons!.notifications!\"></yvc-icon>\n @if (newNotifications(); as note) {\n <div class=\"badge {{ note.maxLevel }}\">{{ note.count }}</div>\n }\n </button>\n }\n <button [routerLink]=\"['/settings']\" routerLinkActive=\"active\" title=\"{{ 'yuv.shell.settings.title' | translate }}\">\n <yvc-icon [svg]=\"shellConfig().icons!.settings!\"></yvc-icon>\n </button>\n <button (click)=\"appLogout()\" title=\"{{ 'yuv.shell.cmd.logout' | translate }}\"><yvc-icon [svg]=\"shellConfig().icons!.logout!\"></yvc-icon></button>\n </section>\n</nav>\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{--client-shell-background-color: var(--main-background);--client-shell-bar-background-color: var(--panel-background);--client-shell-bar-icon-size: 24px;--client-shell-bar-button-padding: calc(var(--app-pane-padding) * .75);--client-shell-bar-width: calc(var(--client-shell-bar-icon-size) + var(--client-shell-bar-button-padding) * 2 + 1px);position:absolute;inset:0;overflow:hidden;display:flex;background-color:var(--client-shell-background-color)}:host .yuv-loader-linear{position:absolute}:host nav{background-color:var(--client-shell-bar-background-color);height:100%;overflow-y:auto;border-inline-end:1px solid var(--panel-divider-color);flex:0 0 auto;display:var(--nav-display, grid);grid-template-rows:auto 1fr auto;grid-template-columns:1fr;grid-template-areas:\"appIcon\" \"apps\" \"actions\"}:host nav button{padding:var(--client-shell-bar-button-padding);border-radius:0;border:0}:host nav button yvc-icon{--icon-size: var(--client-shell-bar-icon-size)}:host nav button.active{color:var(--color-accent)}:host nav .app-icon{grid-area:appIcon}:host nav ul.apps{grid-area:apps;list-style:none;margin:0;padding:0;overflow-y:auto}:host nav ul.apps button{position:relative}:host nav ul.apps button .badge{--badge-color: var(--color-accent);--badge-color-tone: var(--color-accent-tone);position:absolute;background-color:var(--badge-color);color:var(--badge-color-tone);font-size:10px;display:block;padding:1px 2px;border-radius:.25em;line-height:1em;top:calc(var(--app-pane-padding) / 4);right:calc(var(--app-pane-padding) / 4)}:host nav ul.apps button .badge.success{--badge-color: var(--color-success);--badge-color-tone: #fff}:host nav ul.apps button .badge.warning{--badge-color: var(--color-warning);--badge-color-tone: #fff}:host nav ul.apps button .badge.alert{--badge-color: var(--color-error);--badge-color-tone: #fff}:host nav ul.apps li a{display:block;padding:var(--app-pane-padding)}:host nav section.actions{grid-area:actions}:host nav section.actions button{position:relative}:host nav section.actions button .badge{--badge-color: var(--color-accent);--badge-color-tone: var(--color-accent-tone);position:absolute;background-color:var(--badge-color);color:var(--badge-color-tone);font-size:10px;display:block;padding:1px 2px;border-radius:.25em;line-height:1em;top:calc(var(--app-pane-padding) / 4);right:calc(var(--app-pane-padding) / 4)}:host nav section.actions button .badge.success{--badge-color: var(--color-success);--badge-color-tone: #fff}:host nav section.actions button .badge.warning{--badge-color: var(--color-warning);--badge-color-tone: #fff}:host nav section.actions button .badge.alert{--badge-color: var(--color-error);--badge-color-tone: #fff}:host main{flex:1;color:var(--text-color-body)}:host .asideOutlet{position:absolute;inset:0;padding-inline-start:var(--client-shell-bar-width)}:host-context([data-screen-size=s][data-screen-orientation=portrait]){flex-flow:column-reverse}:host-context([data-screen-size=s][data-screen-orientation=portrait]) nav{height:auto;grid-template-rows:1fr;grid-template-columns:auto 1fr auto;grid-template-areas:\"appIcon apps actions\";border:0;border-block-start:1px solid var(--panel-divider-color)}:host-context([data-screen-size=s][data-screen-orientation=portrait]) nav .app-icon{display:none}:host-context([data-screen-size=s][data-screen-orientation=portrait]) nav ul.apps,:host-context([data-screen-size=s][data-screen-orientation=portrait]) nav section.actions{display:flex}: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: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$2.RouterOutlet, selector: "router-outlet", inputs: ["name"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: i2$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i2$2.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: YvcIconModule }, { kind: "component", type: i2$1.Icon, selector: "yvc-icon", inputs: ["label", "svg", "svgSrc"] }, { kind: "component", type: MetadataDefaultTemplatesComponent, selector: "yuv-metadata-default-templates" }] }); }
376
+ }
377
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ClientShellComponent, decorators: [{
378
+ type: Component,
379
+ args: [{ selector: 'yuv-client-shell', standalone: true, imports: [NgIf, AsyncPipe, TranslateModule, RouterModule, YvcIconModule, MetadataDefaultTemplatesComponent], template: "<yuv-metadata-default-templates></yuv-metadata-default-templates>\n<!-- <yuv-focus-indicator /> -->\n\n<!-- gloabl busy indicator -->\n@if (busy$ | async) {\n <div class=\"yuv-loader-linear\"></div>\n}\n<nav class=\"apps\" [inert]=\"asideOutlet.isActivated\" [attr.aria-label]=\"'yuv.shell.naviagtion.aria.label' | translate\">\n <div class=\"app-icon\">\n <button routerLink=\"/\" routerLinkActive=\"active\" [attr.aria-label]=\"'yuv.shell.logo.aria.label' | translate\">\n <yvc-icon [svg]=\"shellConfig().icons!.appIcon!\"></yvc-icon>\n </button>\n </div>\n <ul class=\"apps\">\n @for (a of apps(); track a.path) {\n <li>\n <button routerLinkActive=\"active\" [routerLink]=\"a.path\" title=\"{{ a.title }}\">\n <yvc-icon [svg]=\"a.icon\" title=\"{{ a.title }}\" *ngIf=\"a.icon\"></yvc-icon>\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\n <section class=\"actions\">\n @if (showNotifications()) {\n <button [routerLink]=\"[{ outlets: { aside: 'notifications' } }]\" routerLinkActive=\"active\" title=\"{{ 'yuv.shell.notifications.title' | translate }}\">\n <yvc-icon [svg]=\"shellConfig().icons!.notifications!\"></yvc-icon>\n @if (newNotifications(); as note) {\n <div class=\"badge {{ note.maxLevel }}\">{{ note.count }}</div>\n }\n </button>\n }\n <button [routerLink]=\"['/settings']\" routerLinkActive=\"active\" title=\"{{ 'yuv.shell.settings.title' | translate }}\">\n <yvc-icon [svg]=\"shellConfig().icons!.settings!\"></yvc-icon>\n </button>\n <button (click)=\"appLogout()\" title=\"{{ 'yuv.shell.cmd.logout' | translate }}\"><yvc-icon [svg]=\"shellConfig().icons!.logout!\"></yvc-icon></button>\n </section>\n</nav>\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{--client-shell-background-color: var(--main-background);--client-shell-bar-background-color: var(--panel-background);--client-shell-bar-icon-size: 24px;--client-shell-bar-button-padding: calc(var(--app-pane-padding) * .75);--client-shell-bar-width: calc(var(--client-shell-bar-icon-size) + var(--client-shell-bar-button-padding) * 2 + 1px);position:absolute;inset:0;overflow:hidden;display:flex;background-color:var(--client-shell-background-color)}:host .yuv-loader-linear{position:absolute}:host nav{background-color:var(--client-shell-bar-background-color);height:100%;overflow-y:auto;border-inline-end:1px solid var(--panel-divider-color);flex:0 0 auto;display:var(--nav-display, grid);grid-template-rows:auto 1fr auto;grid-template-columns:1fr;grid-template-areas:\"appIcon\" \"apps\" \"actions\"}:host nav button{padding:var(--client-shell-bar-button-padding);border-radius:0;border:0}:host nav button yvc-icon{--icon-size: var(--client-shell-bar-icon-size)}:host nav button.active{color:var(--color-accent)}:host nav .app-icon{grid-area:appIcon}:host nav ul.apps{grid-area:apps;list-style:none;margin:0;padding:0;overflow-y:auto}:host nav ul.apps button{position:relative}:host nav ul.apps button .badge{--badge-color: var(--color-accent);--badge-color-tone: var(--color-accent-tone);position:absolute;background-color:var(--badge-color);color:var(--badge-color-tone);font-size:10px;display:block;padding:1px 2px;border-radius:.25em;line-height:1em;top:calc(var(--app-pane-padding) / 4);right:calc(var(--app-pane-padding) / 4)}:host nav ul.apps button .badge.success{--badge-color: var(--color-success);--badge-color-tone: #fff}:host nav ul.apps button .badge.warning{--badge-color: var(--color-warning);--badge-color-tone: #fff}:host nav ul.apps button .badge.alert{--badge-color: var(--color-error);--badge-color-tone: #fff}:host nav ul.apps li a{display:block;padding:var(--app-pane-padding)}:host nav section.actions{grid-area:actions}:host nav section.actions button{position:relative}:host nav section.actions button .badge{--badge-color: var(--color-accent);--badge-color-tone: var(--color-accent-tone);position:absolute;background-color:var(--badge-color);color:var(--badge-color-tone);font-size:10px;display:block;padding:1px 2px;border-radius:.25em;line-height:1em;top:calc(var(--app-pane-padding) / 4);right:calc(var(--app-pane-padding) / 4)}:host nav section.actions button .badge.success{--badge-color: var(--color-success);--badge-color-tone: #fff}:host nav section.actions button .badge.warning{--badge-color: var(--color-warning);--badge-color-tone: #fff}:host nav section.actions button .badge.alert{--badge-color: var(--color-error);--badge-color-tone: #fff}:host main{flex:1;color:var(--text-color-body)}:host .asideOutlet{position:absolute;inset:0;padding-inline-start:var(--client-shell-bar-width)}:host-context([data-screen-size=s][data-screen-orientation=portrait]){flex-flow:column-reverse}:host-context([data-screen-size=s][data-screen-orientation=portrait]) nav{height:auto;grid-template-rows:1fr;grid-template-columns:auto 1fr auto;grid-template-areas:\"appIcon apps actions\";border:0;border-block-start:1px solid var(--panel-divider-color)}:host-context([data-screen-size=s][data-screen-orientation=portrait]) nav .app-icon{display:none}:host-context([data-screen-size=s][data-screen-orientation=portrait]) nav ul.apps,:host-context([data-screen-size=s][data-screen-orientation=portrait]) nav section.actions{display:flex}: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"] }]
380
+ }], ctorParameters: () => [], propDecorators: { onFocusChange: [{
381
+ type: HostListener,
382
+ args: ['document:focusin', ['$event']]
383
+ }], onDragOver: [{
384
+ type: HostListener,
385
+ args: ['dragover', ['$event']]
386
+ }] } });
387
+
388
+ class AppLogoComponent {
389
+ constructor() {
390
+ this.icon = input();
391
+ this.label = input.required();
392
+ }
393
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AppLogoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
394
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: AppLogoComponent, isStandalone: true, selector: "yuv-app-logo", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@let ico = icon();\n@if(ico){\n<yvc-icon [svg]=\"ico\"></yvc-icon>\n}<span class=\"label\">{{label()}}</span>", styles: [":host{--band-color: var(--text-color-body);--band-height: var(--client-shell-bar-icon-size);--band-icon-size: 32px;--text-color: var(--main-background);--icon-color: var(--text-color);--icon-background-color: transparent;display:flex;align-items:center;padding:0 calc(var(--app-pane-padding) / 1) 0 calc(var(--app-pane-padding) / 2);height:calc(var(--client-shell-bar-icon-size));font-size:var(--font-caption);line-height:1em;cursor:pointer;background-color:var(--band-color);color:var(--text-color)}:host yvc-icon{--icon-size: var(--band-icon-size);grid-column:2;grid-row:1;border-radius:4px;margin-inline-end:calc(var(--app-pane-padding) / 2);opacity:.5;color:var(--icon-color);background-color:var(--icon-background-color)}:host .label{line-height:1em}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: YvcIconModule }, { kind: "component", type: i2$1.Icon, selector: "yvc-icon", inputs: ["label", "svg", "svgSrc"] }] }); }
395
+ }
396
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AppLogoComponent, decorators: [{
397
+ type: Component,
398
+ args: [{ selector: 'yuv-app-logo', standalone: true, imports: [CommonModule, YvcIconModule], template: "@let ico = icon();\n@if(ico){\n<yvc-icon [svg]=\"ico\"></yvc-icon>\n}<span class=\"label\">{{label()}}</span>", styles: [":host{--band-color: var(--text-color-body);--band-height: var(--client-shell-bar-icon-size);--band-icon-size: 32px;--text-color: var(--main-background);--icon-color: var(--text-color);--icon-background-color: transparent;display:flex;align-items:center;padding:0 calc(var(--app-pane-padding) / 1) 0 calc(var(--app-pane-padding) / 2);height:calc(var(--client-shell-bar-icon-size));font-size:var(--font-caption);line-height:1em;cursor:pointer;background-color:var(--band-color);color:var(--text-color)}:host yvc-icon{--icon-size: var(--band-icon-size);grid-column:2;grid-row:1;border-radius:4px;margin-inline-end:calc(var(--app-pane-padding) / 2);opacity:.5;color:var(--icon-color);background-color:var(--icon-background-color)}:host .label{line-height:1em}\n"] }]
399
+ }] });
400
+
401
+ class ManageFlavorsComponent {
402
+ constructor() {
403
+ this.#overlayRef = inject(YvcOverlayRef);
404
+ this.#shell = inject(ShellService);
405
+ this.#dmsService = inject(DmsService);
406
+ this.item = this.#overlayRef.data;
407
+ this.busy = signal(false);
408
+ this.appliedFlavors = [];
409
+ this.applicableFlavors = [];
410
+ }
411
+ #overlayRef;
412
+ #shell;
413
+ #dmsService;
414
+ applyFlavor(flavor) {
415
+ this.busy.set(true);
416
+ this.#shell.triggerApplyObjectFlavor(this.item, flavor).subscribe(() => {
417
+ this.#overlayRef.close();
418
+ this.busy.set(false);
419
+ });
420
+ }
421
+ removeFlavor(flavor) {
422
+ this.busy.set(true);
423
+ this.#shell.removeObjectFlavor(this.item, flavor).subscribe(() => {
424
+ this.#overlayRef.close();
425
+ this.busy.set(false);
426
+ });
427
+ }
428
+ #refreshDmsObject() {
429
+ this.busy.set(true);
430
+ this.#dmsService
431
+ .getDmsObject(this.item.id)
432
+ .pipe(tap$1((dmsObject) => {
433
+ this.item = dmsObject;
434
+ this.#getAppliedFlavors(this.item);
435
+ }))
436
+ .subscribe()
437
+ .add(() => this.busy.set(false));
438
+ }
439
+ #getAppliedFlavors(dmsObject) {
440
+ const res = this.#shell.getAppliedObjectFlavors(dmsObject);
441
+ this.appliedFlavors = res.applied;
442
+ this.applicableFlavors = res.applicable;
443
+ }
444
+ cancel() {
445
+ if (this.#overlayRef)
446
+ this.#overlayRef.close();
447
+ }
448
+ ngOnInit() {
449
+ this.#refreshDmsObject();
450
+ }
451
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ManageFlavorsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
452
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: ManageFlavorsComponent, isStandalone: true, selector: "yuv-manage-flavors", ngImport: i0, template: "<header>\n <h2>{{ 'yuv.shell.action.manage-flavors.title' | translate }}</h2>\n <p>{{ 'yuv.shell.action.manage-flavors.text' | translate }}</p>\n</header>\n<main [yuvBusyOverlay]=\"busy()\">\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 class=\"secondary\" (click)=\"removeFlavor(f)\">{{ 'yuv.shell.action.manage-flavors.applicable.button.remove' | translate }}</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 class=\"secondary\" (click)=\"applyFlavor(f)\">{{ 'yuv.shell.action.manage-flavors.applied.button.apply' | translate }}</button>\n </li>\n }\n </ul>\n }\n</main>\n<footer>\n <button class=\"secondary\" (click)=\"cancel()\">{{ 'yuv.shell.action.manage-flavors.button.cancel' | translate }}</button>\n</footer>\n", styles: [":host{display:flex;flex-flow:column;padding:var(--app-pane-padding)}:host header{flex:0 0 auto}:host header h2{padding:0;font-weight:400;font-size:var(--font-headline);margin:0 0 var(--app-pane-padding) 0}:host header p{max-width:42ch;line-height:1.3em}:host main{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:calc(var(--app-pane-padding) / 2);align-items:center;border:1px solid var(--panel-divider-color);margin-block-end:2px;padding:calc(var(--app-pane-padding) / 4)}:host main li button{padding:calc(var(--app-pane-padding) / 4) calc(var(--app-pane-padding) / 2)}:host footer{flex:0 0 auto;margin-block-start:var(--app-pane-padding)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: BusyOverlayDirective, selector: "[yuvBusyOverlay]", inputs: ["yuvBusyOverlay"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i2.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"] }] }); }
453
+ }
454
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ManageFlavorsComponent, decorators: [{
455
+ type: Component,
456
+ args: [{ selector: 'yuv-manage-flavors', standalone: true, imports: [CommonModule, ListComponent, BusyOverlayDirective, TranslateModule, ListItemDirective, FlavorChipComponent], template: "<header>\n <h2>{{ 'yuv.shell.action.manage-flavors.title' | translate }}</h2>\n <p>{{ 'yuv.shell.action.manage-flavors.text' | translate }}</p>\n</header>\n<main [yuvBusyOverlay]=\"busy()\">\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 class=\"secondary\" (click)=\"removeFlavor(f)\">{{ 'yuv.shell.action.manage-flavors.applicable.button.remove' | translate }}</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 class=\"secondary\" (click)=\"applyFlavor(f)\">{{ 'yuv.shell.action.manage-flavors.applied.button.apply' | translate }}</button>\n </li>\n }\n </ul>\n }\n</main>\n<footer>\n <button class=\"secondary\" (click)=\"cancel()\">{{ 'yuv.shell.action.manage-flavors.button.cancel' | translate }}</button>\n</footer>\n", styles: [":host{display:flex;flex-flow:column;padding:var(--app-pane-padding)}:host header{flex:0 0 auto}:host header h2{padding:0;font-weight:400;font-size:var(--font-headline);margin:0 0 var(--app-pane-padding) 0}:host header p{max-width:42ch;line-height:1.3em}:host main{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:calc(var(--app-pane-padding) / 2);align-items:center;border:1px solid var(--panel-divider-color);margin-block-end:2px;padding:calc(var(--app-pane-padding) / 4)}:host main li button{padding:calc(var(--app-pane-padding) / 4) calc(var(--app-pane-padding) / 2)}:host footer{flex:0 0 auto;margin-block-start:var(--app-pane-padding)}\n"] }]
457
+ }] });
458
+
459
+ class ManageFlavorsAction extends AbstractContextAction {
460
+ constructor() {
461
+ super(...arguments);
462
+ this.#shell = inject(ShellService);
463
+ this.#overlay = inject(YvcOverlayService);
464
+ this.translate = inject(TranslateService);
465
+ this.id = 'yuv.base.manage-flavor';
466
+ this.label = this.translate.instant('yuv.shell.action.manage-flavor.label');
467
+ this.description = this.translate.instant('yuv.shell.action.manage-flavor.description');
468
+ this.priority = 8;
469
+ this.icon = ACTION_ICON.manageFlavor;
470
+ this.group = 'common';
471
+ this.range = SelectionRange.SINGLE_SELECT;
472
+ this.supports = {
473
+ pattern: '*'
474
+ };
475
+ }
476
+ #shell;
477
+ #overlay;
478
+ isExecutable(items) {
479
+ const item = items[0];
480
+ return of(item && !!item.permissions?.writeIndexData && !!item.content && this.#shell.getApplicableObjectFlavors(item.content.mimeType).length > 0);
481
+ }
482
+ run(items) {
483
+ this.#overlay.open(ManageFlavorsComponent, items[0], {
484
+ width: '400px',
485
+ maxWidth: '90vw',
486
+ });
487
+ return of(true);
488
+ }
489
+ }
490
+
491
+ /**
492
+ * Generated bundle index. Do not edit.
493
+ */
494
+
495
+ export { AppLogoComponent, ClientShellComponent, InertDirective, ManageFlavorsAction, clientShellRoutes };
496
+ //# sourceMappingURL=yuuvis-client-shell.mjs.map