@flusys/ng-layout 1.1.0 → 1.2.0-rc.1
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.
- package/README.md +51 -664
- package/assets/layout/_core.scss +19 -0
- package/assets/layout/_footer.scss +18 -1
- package/assets/layout/_forms.scss +12 -32
- package/assets/layout/_menu.scss +5 -5
- package/assets/layout/_responsive.scss +18 -0
- package/assets/layout/_rtl.scss +482 -0
- package/assets/layout/_topbar.scss +177 -124
- package/assets/layout/_topbar_nav.scss +345 -0
- package/assets/layout/_typography.scss +25 -30
- package/assets/layout/_utils.scss +12 -16
- package/assets/layout/layout.scss +3 -1
- package/assets/layout/variables/_common.scss +17 -17
- package/assets/layout/variables/_dark.scss +3 -3
- package/assets/layout/variables/_light.scss +3 -3
- package/assets/style.scss +3 -3
- package/fesm2022/flusys-ng-layout.mjs +1703 -1155
- package/fesm2022/flusys-ng-layout.mjs.map +1 -1
- package/package.json +9 -10
- package/types/flusys-ng-layout.d.ts +118 -132
|
@@ -1,32 +1,19 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, PLATFORM_ID, Injectable, DOCUMENT, signal, computed, effect,
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
2
|
+
import { inject, PLATFORM_ID, Injectable, DOCUMENT, signal, DestroyRef, computed, effect, Component, InjectionToken, input, viewChild, ElementRef, Renderer2 } from '@angular/core';
|
|
3
|
+
import * as i3 from '@flusys/ng-shared';
|
|
4
|
+
import { evaluateLogicNode, PermissionValidatorService, InlineSvgComponent, TranslatePipe, TranslateService, IconComponent, AngularModule } from '@flusys/ng-shared';
|
|
5
|
+
import { isPlatformBrowser, NgComponentOutlet, DOCUMENT as DOCUMENT$1 } from '@angular/common';
|
|
6
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
|
+
import { APP_CONFIG, DEFAULT_APP_NAME, DEFAULT_AUTHOR, isCompanyFeatureEnabled, isSignUpEnabled } from '@flusys/ng-core';
|
|
7
8
|
import { Subject, fromEvent, filter as filter$1 } from 'rxjs';
|
|
8
|
-
import * as
|
|
9
|
+
import * as i2 from '@angular/forms';
|
|
9
10
|
import { FormsModule } from '@angular/forms';
|
|
10
|
-
import * as
|
|
11
|
-
import { Router,
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
import Nora from '@primeuix/themes/nora';
|
|
16
|
-
import * as i2 from 'primeng/selectbutton';
|
|
17
|
-
import { SelectButtonModule } from 'primeng/selectbutton';
|
|
18
|
-
import * as i1$1 from 'primeng/button';
|
|
19
|
-
import { ButtonModule } from 'primeng/button';
|
|
20
|
-
import * as i2$1 from 'primeng/styleclass';
|
|
21
|
-
import { StyleClassModule } from 'primeng/styleclass';
|
|
22
|
-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
11
|
+
import * as i2$1 from '@angular/router';
|
|
12
|
+
import { Router, NavigationEnd, isActive, RouterModule } from '@angular/router';
|
|
13
|
+
import * as i1 from '@flusys/ng-ui';
|
|
14
|
+
import { PRESET_NAMES, applyTheme, SURFACE_PALETTES, PRIMARY_PALETTES, applyPrimaryPalette, applySurfacePalette, UiModule, MessageService } from '@flusys/ng-ui';
|
|
15
|
+
export { applyTheme, buildBootstrapPreset } from '@flusys/ng-ui';
|
|
23
16
|
import { filter } from 'rxjs/operators';
|
|
24
|
-
import { MessageService } from 'primeng/api';
|
|
25
|
-
import * as i4 from 'primeng/select';
|
|
26
|
-
import { SelectModule } from 'primeng/select';
|
|
27
|
-
import * as i2$3 from 'primeng/ripple';
|
|
28
|
-
import { RippleModule } from 'primeng/ripple';
|
|
29
|
-
import Material from '@primeuix/themes/material';
|
|
30
17
|
|
|
31
18
|
/** Filter launcher apps based on user permission codes */
|
|
32
19
|
function filterAppsByPermissions(apps, permissionCodes) {
|
|
@@ -89,36 +76,11 @@ function filterMenuItem(item, permissionCode) {
|
|
|
89
76
|
|
|
90
77
|
const STORAGE_KEY = 'flusys.layout.config';
|
|
91
78
|
const STORAGE_VERSION = 1;
|
|
92
|
-
/**
|
|
93
|
-
* Service for persisting layout configuration to localStorage.
|
|
94
|
-
* Handles loading, saving, and validation of layout preferences with SSR-safe checks.
|
|
95
|
-
*
|
|
96
|
-
* @example
|
|
97
|
-
* ```typescript
|
|
98
|
-
* const persistence = inject(LayoutPersistenceService);
|
|
99
|
-
*
|
|
100
|
-
* // Load saved config
|
|
101
|
-
* const config = persistence.load();
|
|
102
|
-
*
|
|
103
|
-
* // Save config
|
|
104
|
-
* persistence.save({ darkTheme: true, preset: 'Aura' });
|
|
105
|
-
*
|
|
106
|
-
* // Clear saved config
|
|
107
|
-
* persistence.clear();
|
|
108
|
-
* ```
|
|
109
|
-
*/
|
|
110
79
|
class LayoutPersistenceService {
|
|
111
80
|
platformId = inject(PLATFORM_ID);
|
|
112
81
|
isBrowser = isPlatformBrowser(this.platformId);
|
|
113
82
|
validPresets = ['Aura', 'Lara', 'Nora'];
|
|
114
|
-
validMenuModes = ['static', 'overlay'];
|
|
115
|
-
/**
|
|
116
|
-
* Load configuration from localStorage.
|
|
117
|
-
* Returns null if no saved config or invalid data.
|
|
118
|
-
* SSR-safe (returns null on server).
|
|
119
|
-
*
|
|
120
|
-
* @returns Validated layout configuration or null
|
|
121
|
-
*/
|
|
83
|
+
validMenuModes = ['static', 'overlay', 'topbar'];
|
|
122
84
|
load() {
|
|
123
85
|
if (!this.isBrowser)
|
|
124
86
|
return null;
|
|
@@ -134,12 +96,6 @@ class LayoutPersistenceService {
|
|
|
134
96
|
return null;
|
|
135
97
|
}
|
|
136
98
|
}
|
|
137
|
-
/**
|
|
138
|
-
* Save configuration to localStorage.
|
|
139
|
-
* SSR-safe (no-op on server).
|
|
140
|
-
*
|
|
141
|
-
* @param config Layout configuration to save
|
|
142
|
-
*/
|
|
143
99
|
save(config) {
|
|
144
100
|
if (!this.isBrowser)
|
|
145
101
|
return;
|
|
@@ -154,64 +110,46 @@ class LayoutPersistenceService {
|
|
|
154
110
|
// Silent fail - localStorage may be full or disabled
|
|
155
111
|
}
|
|
156
112
|
}
|
|
157
|
-
/**
|
|
158
|
-
* Clear stored configuration.
|
|
159
|
-
* SSR-safe (no-op on server).
|
|
160
|
-
*/
|
|
161
113
|
clear() {
|
|
162
114
|
if (!this.isBrowser)
|
|
163
115
|
return;
|
|
164
116
|
localStorage.removeItem(STORAGE_KEY);
|
|
165
117
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Validate and sanitize configuration.
|
|
168
|
-
* Returns null if version mismatch.
|
|
169
|
-
*
|
|
170
|
-
* @param config Configuration to validate
|
|
171
|
-
* @returns Validated configuration or null
|
|
172
|
-
*/
|
|
173
118
|
validateConfig(config) {
|
|
174
|
-
// Version mismatch - clear and return null
|
|
175
119
|
if (config._version && config._version !== STORAGE_VERSION) {
|
|
176
120
|
this.clear();
|
|
177
121
|
return null;
|
|
178
122
|
}
|
|
179
|
-
// Validate preset - silently fix invalid values
|
|
180
123
|
if (config.preset && !this.validPresets.includes(config.preset)) {
|
|
181
124
|
config.preset = 'Aura';
|
|
182
125
|
}
|
|
183
|
-
// Validate menuMode - silently fix invalid values
|
|
184
126
|
if (config.menuMode && !this.validMenuModes.includes(config.menuMode)) {
|
|
185
127
|
config.menuMode = 'static';
|
|
186
128
|
}
|
|
187
|
-
// Validate darkTheme
|
|
188
129
|
if (typeof config.darkTheme !== 'boolean') {
|
|
189
130
|
config.darkTheme = false;
|
|
190
131
|
}
|
|
191
|
-
// Remove version field from returned config
|
|
192
132
|
const { _version, ...cleanConfig } = config;
|
|
193
133
|
return cleanConfig;
|
|
194
134
|
}
|
|
195
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
196
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
135
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LayoutPersistenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
136
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LayoutPersistenceService, providedIn: 'root' });
|
|
197
137
|
}
|
|
198
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
138
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LayoutPersistenceService, decorators: [{
|
|
199
139
|
type: Injectable,
|
|
200
|
-
args: [{
|
|
201
|
-
providedIn: 'root',
|
|
202
|
-
}]
|
|
140
|
+
args: [{ providedIn: 'root' }]
|
|
203
141
|
}] });
|
|
204
142
|
|
|
205
|
-
|
|
143
|
+
const DESKTOP_BREAKPOINT = 991;
|
|
206
144
|
class LayoutService {
|
|
207
145
|
document = inject(DOCUMENT);
|
|
208
146
|
platformId = inject(PLATFORM_ID);
|
|
209
147
|
isBrowser = isPlatformBrowser(this.platformId);
|
|
210
148
|
persistence = inject(LayoutPersistenceService);
|
|
211
149
|
appConfig = inject(APP_CONFIG, { optional: true });
|
|
212
|
-
DEFAULT_CONFIG = {
|
|
150
|
+
DEFAULT_CONFIG = this.appConfig?.layoutConfig ?? {
|
|
213
151
|
preset: 'Aura',
|
|
214
|
-
primary: '
|
|
152
|
+
primary: 'violet',
|
|
215
153
|
surface: null,
|
|
216
154
|
darkTheme: false,
|
|
217
155
|
menuMode: 'static',
|
|
@@ -222,66 +160,101 @@ class LayoutService {
|
|
|
222
160
|
configSidebarVisible: false,
|
|
223
161
|
staticMenuMobileActive: false,
|
|
224
162
|
menuHoverActive: false,
|
|
163
|
+
topbarMenuVisible: true,
|
|
225
164
|
};
|
|
165
|
+
memorable = this.DEFAULT_CONFIG.isMemorable !== false;
|
|
226
166
|
_layoutConfig = signal({
|
|
227
167
|
...this.DEFAULT_CONFIG,
|
|
228
|
-
...this.persistence.load(),
|
|
229
|
-
},
|
|
230
|
-
|
|
231
|
-
|
|
168
|
+
...(this.memorable ? this.persistence.load() : null),
|
|
169
|
+
}, /* @ts-ignore */
|
|
170
|
+
...(ngDevMode ? [{ debugName: "_layoutConfig" }] : /* istanbul ignore next */ []));
|
|
171
|
+
_layoutState = signal(this.DEFAULT_STATE, /* @ts-ignore */
|
|
172
|
+
...(ngDevMode ? [{ debugName: "_layoutState" }] : /* istanbul ignore next */ []));
|
|
173
|
+
_transitionComplete = signal(false, /* @ts-ignore */
|
|
174
|
+
...(ngDevMode ? [{ debugName: "_transitionComplete" }] : /* istanbul ignore next */ []));
|
|
232
175
|
layoutConfig = this._layoutConfig.asReadonly();
|
|
233
176
|
layoutState = this._layoutState.asReadonly();
|
|
234
177
|
transitionComplete = this._transitionComplete.asReadonly();
|
|
235
|
-
_userProfile = signal(null,
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
178
|
+
_userProfile = signal(null, /* @ts-ignore */
|
|
179
|
+
...(ngDevMode ? [{ debugName: "_userProfile" }] : /* istanbul ignore next */ []));
|
|
180
|
+
_companyProfile = signal(null, /* @ts-ignore */
|
|
181
|
+
...(ngDevMode ? [{ debugName: "_companyProfile" }] : /* istanbul ignore next */ []));
|
|
182
|
+
_rawMenu = signal([], /* @ts-ignore */
|
|
183
|
+
...(ngDevMode ? [{ debugName: "_rawMenu" }] : /* istanbul ignore next */ []));
|
|
184
|
+
_rawApps = signal([], /* @ts-ignore */
|
|
185
|
+
...(ngDevMode ? [{ debugName: "_rawApps" }] : /* istanbul ignore next */ []));
|
|
239
186
|
permissionValidator = inject(PermissionValidatorService);
|
|
187
|
+
destroyRef = inject(DestroyRef);
|
|
188
|
+
_windowWidth = signal(this.isBrowser ? window.innerWidth : DESKTOP_BREAKPOINT + 1, /* @ts-ignore */
|
|
189
|
+
...(ngDevMode ? [{ debugName: "_windowWidth" }] : /* istanbul ignore next */ []));
|
|
240
190
|
userProfile = this._userProfile.asReadonly();
|
|
241
191
|
companyProfile = this._companyProfile.asReadonly();
|
|
242
|
-
// Static app info from config
|
|
243
192
|
appName = this.appConfig?.appName ?? DEFAULT_APP_NAME;
|
|
193
|
+
appLogo = this.appConfig?.appLogo ?? '';
|
|
194
|
+
isAppLogoSvg = this.appLogo.toLowerCase().endsWith('.svg');
|
|
244
195
|
authorName = this.appConfig?.author?.name ?? DEFAULT_AUTHOR.name;
|
|
245
196
|
authorUrl = this.appConfig?.author?.url ?? DEFAULT_AUTHOR.url;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
apps = computed(() => filterAppsByPermissions(this._rawApps(), this.permissionValidator.permissions()),
|
|
249
|
-
|
|
250
|
-
|
|
197
|
+
menu = computed(() => filterMenuByPermissions(this._rawMenu(), this.permissionValidator.permissions()), /* @ts-ignore */
|
|
198
|
+
...(ngDevMode ? [{ debugName: "menu" }] : /* istanbul ignore next */ []));
|
|
199
|
+
apps = computed(() => filterAppsByPermissions(this._rawApps(), this.permissionValidator.permissions()), /* @ts-ignore */
|
|
200
|
+
...(ngDevMode ? [{ debugName: "apps" }] : /* istanbul ignore next */ []));
|
|
201
|
+
hasApps = computed(() => this.apps().length > 0, /* @ts-ignore */
|
|
202
|
+
...(ngDevMode ? [{ debugName: "hasApps" }] : /* istanbul ignore next */ []));
|
|
251
203
|
isSidebarActive = computed(() => this._layoutState().overlayMenuActive ||
|
|
252
|
-
this._layoutState().staticMenuMobileActive,
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
204
|
+
this._layoutState().staticMenuMobileActive, /* @ts-ignore */
|
|
205
|
+
...(ngDevMode ? [{ debugName: "isSidebarActive" }] : /* istanbul ignore next */ []));
|
|
206
|
+
isDesktop = computed(() => this._windowWidth() > DESKTOP_BREAKPOINT, /* @ts-ignore */
|
|
207
|
+
...(ngDevMode ? [{ debugName: "isDesktop" }] : /* istanbul ignore next */ []));
|
|
208
|
+
isMobile = computed(() => !this.isDesktop(), /* @ts-ignore */
|
|
209
|
+
...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
|
|
210
|
+
isDarkTheme = computed(() => this._layoutConfig().darkTheme, /* @ts-ignore */
|
|
211
|
+
...(ngDevMode ? [{ debugName: "isDarkTheme" }] : /* istanbul ignore next */ []));
|
|
212
|
+
isOverlay = computed(() => this._layoutConfig().menuMode === 'overlay', /* @ts-ignore */
|
|
213
|
+
...(ngDevMode ? [{ debugName: "isOverlay" }] : /* istanbul ignore next */ []));
|
|
214
|
+
isTopbar = computed(() => this._layoutConfig().menuMode === 'topbar', /* @ts-ignore */
|
|
215
|
+
...(ngDevMode ? [{ debugName: "isTopbar" }] : /* istanbul ignore next */ []));
|
|
216
|
+
userName = computed(() => this._userProfile()?.name ?? 'Guest', /* @ts-ignore */
|
|
217
|
+
...(ngDevMode ? [{ debugName: "userName" }] : /* istanbul ignore next */ []));
|
|
218
|
+
userEmail = computed(() => this._userProfile()?.email ?? '', /* @ts-ignore */
|
|
219
|
+
...(ngDevMode ? [{ debugName: "userEmail" }] : /* istanbul ignore next */ []));
|
|
220
|
+
userProfilePictureUrl = computed(() => this._userProfile()?.profilePictureUrl ?? null, /* @ts-ignore */
|
|
221
|
+
...(ngDevMode ? [{ debugName: "userProfilePictureUrl" }] : /* istanbul ignore next */ []));
|
|
222
|
+
companyLogoUrl = computed(() => this._companyProfile()?.logoUrl ?? null, /* @ts-ignore */
|
|
223
|
+
...(ngDevMode ? [{ debugName: "companyLogoUrl" }] : /* istanbul ignore next */ []));
|
|
224
|
+
isAuthenticated = computed(() => !!this._userProfile(), /* @ts-ignore */
|
|
225
|
+
...(ngDevMode ? [{ debugName: "isAuthenticated" }] : /* istanbul ignore next */ []));
|
|
263
226
|
companyName = computed(() => {
|
|
264
227
|
if (!this.appConfig || !isCompanyFeatureEnabled(this.appConfig))
|
|
265
228
|
return this.appName;
|
|
266
229
|
return this._companyProfile()?.name ?? this.appName;
|
|
267
|
-
},
|
|
268
|
-
|
|
269
|
-
|
|
230
|
+
}, /* @ts-ignore */
|
|
231
|
+
...(ngDevMode ? [{ debugName: "companyName" }] : /* istanbul ignore next */ []));
|
|
232
|
+
displayLogo = computed(() => this.companyLogoUrl() ?? this.appLogo, /* @ts-ignore */
|
|
233
|
+
...(ngDevMode ? [{ debugName: "displayLogo" }] : /* istanbul ignore next */ []));
|
|
234
|
+
isDisplayLogoSvg = computed(() => this.displayLogo().toLowerCase().endsWith('.svg'), /* @ts-ignore */
|
|
235
|
+
...(ngDevMode ? [{ debugName: "isDisplayLogoSvg" }] : /* istanbul ignore next */ []));
|
|
270
236
|
overlayOpen = new Subject();
|
|
271
237
|
menuSource = new Subject();
|
|
272
238
|
resetSource = new Subject();
|
|
273
239
|
menuSource$ = this.menuSource.asObservable();
|
|
274
240
|
resetSource$ = this.resetSource.asObservable();
|
|
275
|
-
configUpdate$ = this.configUpdate.asObservable();
|
|
276
241
|
overlayOpen$ = this.overlayOpen.asObservable();
|
|
277
242
|
initialized = false;
|
|
278
243
|
constructor() {
|
|
244
|
+
if (this.isBrowser) {
|
|
245
|
+
this.toggleDarkMode();
|
|
246
|
+
fromEvent(window, 'resize')
|
|
247
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
248
|
+
.subscribe(() => {
|
|
249
|
+
this._windowWidth.set(window.innerWidth);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
279
252
|
effect(() => {
|
|
280
253
|
const config = this._layoutConfig();
|
|
281
|
-
this.configUpdate.next(config);
|
|
282
254
|
if (this.initialized) {
|
|
283
255
|
this.handleDarkModeTransition(config);
|
|
284
|
-
this.
|
|
256
|
+
if (this.memorable)
|
|
257
|
+
this.persistence.save(config);
|
|
285
258
|
}
|
|
286
259
|
this.initialized = true;
|
|
287
260
|
});
|
|
@@ -292,7 +265,8 @@ class LayoutService {
|
|
|
292
265
|
}
|
|
293
266
|
handleDarkModeTransition(config) {
|
|
294
267
|
const doc = this.document;
|
|
295
|
-
if ('startViewTransition' in doc &&
|
|
268
|
+
if ('startViewTransition' in doc &&
|
|
269
|
+
typeof doc.startViewTransition === 'function') {
|
|
296
270
|
doc
|
|
297
271
|
.startViewTransition(() => this.toggleDarkMode(config))
|
|
298
272
|
.ready.then(() => this.onTransitionEnd())
|
|
@@ -309,31 +283,41 @@ class LayoutService {
|
|
|
309
283
|
}
|
|
310
284
|
onMenuToggle() {
|
|
311
285
|
const state = this._layoutState();
|
|
286
|
+
// Desktop topbar mode: toggle topbar nav visibility
|
|
287
|
+
if (this.isDesktop() && this.isTopbar()) {
|
|
288
|
+
this._layoutState.update((prev) => ({
|
|
289
|
+
...prev,
|
|
290
|
+
topbarMenuVisible: !prev.topbarMenuVisible,
|
|
291
|
+
}));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
// Mobile always uses staticMenuMobileActive regardless of menu mode
|
|
295
|
+
if (!this.isDesktop()) {
|
|
296
|
+
const newMobileActive = !state.staticMenuMobileActive;
|
|
297
|
+
this._layoutState.update((prev) => ({
|
|
298
|
+
...prev,
|
|
299
|
+
staticMenuMobileActive: newMobileActive,
|
|
300
|
+
}));
|
|
301
|
+
if (newMobileActive)
|
|
302
|
+
this.overlayOpen.next();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
// Desktop: use overlay or static mode
|
|
312
306
|
if (this.isOverlay()) {
|
|
313
307
|
const newOverlayActive = !state.overlayMenuActive;
|
|
314
|
-
this._layoutState.update((prev) => ({
|
|
308
|
+
this._layoutState.update((prev) => ({
|
|
309
|
+
...prev,
|
|
310
|
+
overlayMenuActive: newOverlayActive,
|
|
311
|
+
}));
|
|
315
312
|
if (newOverlayActive)
|
|
316
313
|
this.overlayOpen.next();
|
|
317
|
-
return;
|
|
318
314
|
}
|
|
319
|
-
|
|
315
|
+
else {
|
|
320
316
|
this._layoutState.update((prev) => ({
|
|
321
317
|
...prev,
|
|
322
318
|
staticMenuDesktopInactive: !state.staticMenuDesktopInactive,
|
|
323
319
|
}));
|
|
324
320
|
}
|
|
325
|
-
else {
|
|
326
|
-
const newMobileActive = !state.staticMenuMobileActive;
|
|
327
|
-
this._layoutState.update((prev) => ({ ...prev, staticMenuMobileActive: newMobileActive }));
|
|
328
|
-
if (newMobileActive)
|
|
329
|
-
this.overlayOpen.next();
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
isDesktop() {
|
|
333
|
-
return this.isBrowser ? window.innerWidth > 991 : true;
|
|
334
|
-
}
|
|
335
|
-
isMobile() {
|
|
336
|
-
return !this.isDesktop();
|
|
337
321
|
}
|
|
338
322
|
onMenuStateChange(event) {
|
|
339
323
|
this.menuSource.next(event);
|
|
@@ -341,21 +325,18 @@ class LayoutService {
|
|
|
341
325
|
reset() {
|
|
342
326
|
this.resetSource.next(true);
|
|
343
327
|
}
|
|
344
|
-
// Config & state updates
|
|
345
328
|
updateLayoutConfig(config) {
|
|
346
329
|
this._layoutConfig.update((prev) => ({ ...prev, ...config }));
|
|
347
330
|
}
|
|
348
331
|
updateLayoutState(state) {
|
|
349
332
|
this._layoutState.update((prev) => ({ ...prev, ...state }));
|
|
350
333
|
}
|
|
351
|
-
// Profile setters
|
|
352
334
|
setUserProfile(profile) {
|
|
353
335
|
this._userProfile.set(profile);
|
|
354
336
|
}
|
|
355
337
|
setCompanyProfile(profile) {
|
|
356
338
|
this._companyProfile.set(profile);
|
|
357
339
|
}
|
|
358
|
-
// Menu & apps
|
|
359
340
|
setMenu(items) {
|
|
360
341
|
this._rawMenu.set(items);
|
|
361
342
|
}
|
|
@@ -368,361 +349,128 @@ class LayoutService {
|
|
|
368
349
|
clearApps() {
|
|
369
350
|
this._rawApps.set([]);
|
|
370
351
|
}
|
|
371
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
372
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
352
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LayoutService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
353
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LayoutService, providedIn: 'root' });
|
|
373
354
|
}
|
|
374
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
355
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LayoutService, decorators: [{
|
|
375
356
|
type: Injectable,
|
|
376
357
|
args: [{ providedIn: 'root' }]
|
|
377
358
|
}], ctorParameters: () => [] });
|
|
378
359
|
|
|
379
360
|
class AppFooter {
|
|
380
361
|
layoutService = inject(LayoutService);
|
|
381
|
-
// Footer shows product branding (appName), not user's company
|
|
382
362
|
appName = this.layoutService.appName;
|
|
363
|
+
displayLogo = this.layoutService.appLogo;
|
|
364
|
+
isLogoSvg = this.layoutService.isAppLogoSvg;
|
|
383
365
|
authorName = this.layoutService.authorName;
|
|
384
366
|
authorUrl = this.layoutService.authorUrl;
|
|
385
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
386
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
367
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppFooter, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
368
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppFooter, isStandalone: true, selector: "app-footer", ngImport: i0, template: `<div class="layout-footer">
|
|
369
|
+
@if (displayLogo) {
|
|
370
|
+
@if (isLogoSvg) {
|
|
371
|
+
<inline-svg [src]="displayLogo" class="logo-image" />
|
|
372
|
+
} @else {
|
|
373
|
+
<img [src]="displayLogo" [alt]="appName" class="logo-image" />
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
<span class="hidden sm:inline">
|
|
377
|
+
{{ appName }} {{ 'layout.footer.by' | translate }}
|
|
378
|
+
<a
|
|
379
|
+
[href]="authorUrl"
|
|
380
|
+
target="_blank"
|
|
381
|
+
rel="noopener noreferrer"
|
|
382
|
+
class="text-primary font-bold hover:underline"
|
|
383
|
+
>{{ authorName }}</a
|
|
384
|
+
>
|
|
385
|
+
</span>
|
|
386
|
+
</div>`, isInline: true, dependencies: [{ kind: "component", type: InlineSvgComponent, selector: "inline-svg", inputs: ["src"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
390
387
|
}
|
|
391
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
388
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppFooter, decorators: [{
|
|
392
389
|
type: Component,
|
|
393
390
|
args: [{
|
|
394
391
|
selector: 'app-footer',
|
|
395
|
-
|
|
392
|
+
imports: [TranslatePipe, InlineSvgComponent],
|
|
396
393
|
template: `<div class="layout-footer">
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
394
|
+
@if (displayLogo) {
|
|
395
|
+
@if (isLogoSvg) {
|
|
396
|
+
<inline-svg [src]="displayLogo" class="logo-image" />
|
|
397
|
+
} @else {
|
|
398
|
+
<img [src]="displayLogo" [alt]="appName" class="logo-image" />
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
<span class="hidden sm:inline">
|
|
402
|
+
{{ appName }} {{ 'layout.footer.by' | translate }}
|
|
403
|
+
<a
|
|
404
|
+
[href]="authorUrl"
|
|
405
|
+
target="_blank"
|
|
406
|
+
rel="noopener noreferrer"
|
|
407
|
+
class="text-primary font-bold hover:underline"
|
|
408
|
+
>{{ authorName }}</a
|
|
409
|
+
>
|
|
410
|
+
</span>
|
|
411
|
+
</div>`,
|
|
400
412
|
}]
|
|
401
413
|
}] });
|
|
402
414
|
|
|
403
|
-
const presets = {
|
|
404
|
-
Aura,
|
|
405
|
-
Lara,
|
|
406
|
-
Nora,
|
|
407
|
-
};
|
|
408
415
|
class AppConfigurator {
|
|
409
416
|
router = inject(Router);
|
|
417
|
+
translateAdapter = inject(TranslateService, { optional: true });
|
|
418
|
+
platformId = inject(PLATFORM_ID);
|
|
410
419
|
layoutService = inject(LayoutService);
|
|
411
|
-
presets =
|
|
412
|
-
showMenuModeButton = signal(!this.router.url.includes('auth'),
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
420
|
+
presets = PRESET_NAMES;
|
|
421
|
+
showMenuModeButton = signal(!this.router.url.includes('auth'), /* @ts-ignore */
|
|
422
|
+
...(ngDevMode ? [{ debugName: "showMenuModeButton" }] : /* istanbul ignore next */ []));
|
|
423
|
+
menuModeOptions = computed(() => {
|
|
424
|
+
const options = [
|
|
425
|
+
{ label: this.translate('layout.configurator.menu.mode.static'), value: 'static' },
|
|
426
|
+
{ label: this.translate('layout.configurator.menu.mode.overlay'), value: 'overlay' },
|
|
427
|
+
];
|
|
428
|
+
if (this.layoutService.isDesktop()) {
|
|
429
|
+
options.push({ label: this.translate('layout.configurator.menu.mode.topbar'), value: 'topbar' });
|
|
430
|
+
}
|
|
431
|
+
return options;
|
|
432
|
+
}, /* @ts-ignore */
|
|
433
|
+
...(ngDevMode ? [{ debugName: "menuModeOptions" }] : /* istanbul ignore next */ []));
|
|
417
434
|
constructor() {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
435
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
436
|
+
effect(() => {
|
|
437
|
+
const config = this.layoutService.layoutConfig();
|
|
438
|
+
applyTheme({
|
|
439
|
+
preset: (config.preset ?? 'Aura'),
|
|
440
|
+
primary: config.primary ?? 'violet',
|
|
441
|
+
surface: config.surface ?? null,
|
|
442
|
+
darkTheme: config.darkTheme ?? false,
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
}
|
|
423
446
|
}
|
|
424
|
-
surfaces = [
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
name: 'gray',
|
|
444
|
-
palette: {
|
|
445
|
-
0: '#ffffff',
|
|
446
|
-
50: '#f9fafb',
|
|
447
|
-
100: '#f3f4f6',
|
|
448
|
-
200: '#e5e7eb',
|
|
449
|
-
300: '#d1d5db',
|
|
450
|
-
400: '#9ca3af',
|
|
451
|
-
500: '#6b7280',
|
|
452
|
-
600: '#4b5563',
|
|
453
|
-
700: '#374151',
|
|
454
|
-
800: '#1f2937',
|
|
455
|
-
900: '#111827',
|
|
456
|
-
950: '#030712',
|
|
457
|
-
},
|
|
458
|
-
},
|
|
459
|
-
{
|
|
460
|
-
name: 'zinc',
|
|
461
|
-
palette: {
|
|
462
|
-
0: '#ffffff',
|
|
463
|
-
50: '#fafafa',
|
|
464
|
-
100: '#f4f4f5',
|
|
465
|
-
200: '#e4e4e7',
|
|
466
|
-
300: '#d4d4d8',
|
|
467
|
-
400: '#a1a1aa',
|
|
468
|
-
500: '#71717a',
|
|
469
|
-
600: '#52525b',
|
|
470
|
-
700: '#3f3f46',
|
|
471
|
-
800: '#27272a',
|
|
472
|
-
900: '#18181b',
|
|
473
|
-
950: '#09090b',
|
|
474
|
-
},
|
|
475
|
-
},
|
|
476
|
-
{
|
|
477
|
-
name: 'neutral',
|
|
478
|
-
palette: {
|
|
479
|
-
0: '#ffffff',
|
|
480
|
-
50: '#fafafa',
|
|
481
|
-
100: '#f5f5f5',
|
|
482
|
-
200: '#e5e5e5',
|
|
483
|
-
300: '#d4d4d4',
|
|
484
|
-
400: '#a3a3a3',
|
|
485
|
-
500: '#737373',
|
|
486
|
-
600: '#525252',
|
|
487
|
-
700: '#404040',
|
|
488
|
-
800: '#262626',
|
|
489
|
-
900: '#171717',
|
|
490
|
-
950: '#0a0a0a',
|
|
491
|
-
},
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
name: 'stone',
|
|
495
|
-
palette: {
|
|
496
|
-
0: '#ffffff',
|
|
497
|
-
50: '#fafaf9',
|
|
498
|
-
100: '#f5f5f4',
|
|
499
|
-
200: '#e7e5e4',
|
|
500
|
-
300: '#d6d3d1',
|
|
501
|
-
400: '#a8a29e',
|
|
502
|
-
500: '#78716c',
|
|
503
|
-
600: '#57534e',
|
|
504
|
-
700: '#44403c',
|
|
505
|
-
800: '#292524',
|
|
506
|
-
900: '#1c1917',
|
|
507
|
-
950: '#0c0a09',
|
|
508
|
-
},
|
|
509
|
-
},
|
|
510
|
-
{
|
|
511
|
-
name: 'soho',
|
|
512
|
-
palette: {
|
|
513
|
-
0: '#ffffff',
|
|
514
|
-
50: '#ececec',
|
|
515
|
-
100: '#dedfdf',
|
|
516
|
-
200: '#c4c4c6',
|
|
517
|
-
300: '#adaeb0',
|
|
518
|
-
400: '#97979b',
|
|
519
|
-
500: '#7f8084',
|
|
520
|
-
600: '#6a6b70',
|
|
521
|
-
700: '#55565b',
|
|
522
|
-
800: '#3f4046',
|
|
523
|
-
900: '#2c2c34',
|
|
524
|
-
950: '#16161d',
|
|
525
|
-
},
|
|
526
|
-
},
|
|
527
|
-
{
|
|
528
|
-
name: 'viva',
|
|
529
|
-
palette: {
|
|
530
|
-
0: '#ffffff',
|
|
531
|
-
50: '#f3f3f3',
|
|
532
|
-
100: '#e7e7e8',
|
|
533
|
-
200: '#cfd0d0',
|
|
534
|
-
300: '#b7b8b9',
|
|
535
|
-
400: '#9fa1a1',
|
|
536
|
-
500: '#87898a',
|
|
537
|
-
600: '#6e7173',
|
|
538
|
-
700: '#565a5b',
|
|
539
|
-
800: '#3e4244',
|
|
540
|
-
900: '#262b2c',
|
|
541
|
-
950: '#0e1315',
|
|
542
|
-
},
|
|
543
|
-
},
|
|
544
|
-
{
|
|
545
|
-
name: 'ocean',
|
|
546
|
-
palette: {
|
|
547
|
-
0: '#ffffff',
|
|
548
|
-
50: '#fbfcfc',
|
|
549
|
-
100: '#F7F9F8',
|
|
550
|
-
200: '#EFF3F2',
|
|
551
|
-
300: '#DADEDD',
|
|
552
|
-
400: '#B1B7B6',
|
|
553
|
-
500: '#828787',
|
|
554
|
-
600: '#5F7274',
|
|
555
|
-
700: '#415B61',
|
|
556
|
-
800: '#29444E',
|
|
557
|
-
900: '#183240',
|
|
558
|
-
950: '#0c1920',
|
|
559
|
-
},
|
|
560
|
-
},
|
|
561
|
-
];
|
|
562
|
-
selectedPrimaryColor = computed(() => {
|
|
563
|
-
return this.layoutService.layoutConfig().primary;
|
|
564
|
-
}, ...(ngDevMode ? [{ debugName: "selectedPrimaryColor" }] : []));
|
|
565
|
-
selectedSurfaceColor = computed(() => this.layoutService.layoutConfig().surface, ...(ngDevMode ? [{ debugName: "selectedSurfaceColor" }] : []));
|
|
566
|
-
selectedPreset = computed(() => this.layoutService.layoutConfig().preset, ...(ngDevMode ? [{ debugName: "selectedPreset" }] : []));
|
|
567
|
-
menuMode = computed(() => this.layoutService.layoutConfig().menuMode, ...(ngDevMode ? [{ debugName: "menuMode" }] : []));
|
|
447
|
+
surfaces = Object.entries(SURFACE_PALETTES).map(([name, pal]) => ({
|
|
448
|
+
name,
|
|
449
|
+
palette: pal,
|
|
450
|
+
}));
|
|
451
|
+
selectedPrimaryColor = computed(() => this.layoutService.layoutConfig().primary, /* @ts-ignore */
|
|
452
|
+
...(ngDevMode ? [{ debugName: "selectedPrimaryColor" }] : /* istanbul ignore next */ []));
|
|
453
|
+
selectedSurfaceColor = computed(() => this.layoutService.layoutConfig().surface, /* @ts-ignore */
|
|
454
|
+
...(ngDevMode ? [{ debugName: "selectedSurfaceColor" }] : /* istanbul ignore next */ []));
|
|
455
|
+
isSurfaceSelected(surfaceName) {
|
|
456
|
+
const sel = this.selectedSurfaceColor();
|
|
457
|
+
if (sel)
|
|
458
|
+
return sel === surfaceName;
|
|
459
|
+
const isDark = this.layoutService.layoutConfig().darkTheme;
|
|
460
|
+
return isDark ? surfaceName === 'zinc' : surfaceName === 'slate';
|
|
461
|
+
}
|
|
462
|
+
selectedPreset = computed(() => this.layoutService.layoutConfig().preset, /* @ts-ignore */
|
|
463
|
+
...(ngDevMode ? [{ debugName: "selectedPreset" }] : /* istanbul ignore next */ []));
|
|
464
|
+
menuMode = computed(() => this.layoutService.layoutConfig().menuMode, /* @ts-ignore */
|
|
465
|
+
...(ngDevMode ? [{ debugName: "menuMode" }] : /* istanbul ignore next */ []));
|
|
568
466
|
primaryColors = computed(() => {
|
|
569
|
-
const presetPalette = presets[this.layoutService.layoutConfig().preset].primitive;
|
|
570
|
-
const colors = [
|
|
571
|
-
'emerald',
|
|
572
|
-
'green',
|
|
573
|
-
'lime',
|
|
574
|
-
'orange',
|
|
575
|
-
'amber',
|
|
576
|
-
'yellow',
|
|
577
|
-
'teal',
|
|
578
|
-
'cyan',
|
|
579
|
-
'sky',
|
|
580
|
-
'blue',
|
|
581
|
-
'indigo',
|
|
582
|
-
'violet',
|
|
583
|
-
'purple',
|
|
584
|
-
'fuchsia',
|
|
585
|
-
'pink',
|
|
586
|
-
'rose',
|
|
587
|
-
];
|
|
588
467
|
const palettes = [{ name: 'noir', palette: {} }];
|
|
589
|
-
|
|
590
|
-
palettes.push({
|
|
591
|
-
name: color,
|
|
592
|
-
palette: presetPalette?.[color],
|
|
593
|
-
});
|
|
594
|
-
});
|
|
595
|
-
return palettes;
|
|
596
|
-
}, ...(ngDevMode ? [{ debugName: "primaryColors" }] : []));
|
|
597
|
-
getPresetExt() {
|
|
598
|
-
const color = this.primaryColors().find((c) => c.name === this.selectedPrimaryColor()) || {};
|
|
599
|
-
const preset = this.layoutService.layoutConfig().preset;
|
|
600
|
-
if (color.name === 'noir') {
|
|
601
|
-
return {
|
|
602
|
-
semantic: {
|
|
603
|
-
primary: {
|
|
604
|
-
50: '{surface.50}',
|
|
605
|
-
100: '{surface.100}',
|
|
606
|
-
200: '{surface.200}',
|
|
607
|
-
300: '{surface.300}',
|
|
608
|
-
400: '{surface.400}',
|
|
609
|
-
500: '{surface.500}',
|
|
610
|
-
600: '{surface.600}',
|
|
611
|
-
700: '{surface.700}',
|
|
612
|
-
800: '{surface.800}',
|
|
613
|
-
900: '{surface.900}',
|
|
614
|
-
950: '{surface.950}',
|
|
615
|
-
},
|
|
616
|
-
colorScheme: {
|
|
617
|
-
light: {
|
|
618
|
-
primary: {
|
|
619
|
-
color: '{primary.950}',
|
|
620
|
-
contrastColor: '#ffffff',
|
|
621
|
-
hoverColor: '{primary.800}',
|
|
622
|
-
activeColor: '{primary.700}',
|
|
623
|
-
},
|
|
624
|
-
highlight: {
|
|
625
|
-
background: '{primary.950}',
|
|
626
|
-
focusBackground: '{primary.700}',
|
|
627
|
-
color: '#ffffff',
|
|
628
|
-
focusColor: '#ffffff',
|
|
629
|
-
},
|
|
630
|
-
},
|
|
631
|
-
dark: {
|
|
632
|
-
primary: {
|
|
633
|
-
color: '{primary.50}',
|
|
634
|
-
contrastColor: '{primary.950}',
|
|
635
|
-
hoverColor: '{primary.200}',
|
|
636
|
-
activeColor: '{primary.300}',
|
|
637
|
-
},
|
|
638
|
-
highlight: {
|
|
639
|
-
background: '{primary.50}',
|
|
640
|
-
focusBackground: '{primary.300}',
|
|
641
|
-
color: '{primary.950}',
|
|
642
|
-
focusColor: '{primary.950}',
|
|
643
|
-
},
|
|
644
|
-
},
|
|
645
|
-
},
|
|
646
|
-
},
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
else {
|
|
650
|
-
if (preset === 'Nora') {
|
|
651
|
-
return {
|
|
652
|
-
semantic: {
|
|
653
|
-
primary: color.palette,
|
|
654
|
-
colorScheme: {
|
|
655
|
-
light: {
|
|
656
|
-
primary: {
|
|
657
|
-
color: '{primary.600}',
|
|
658
|
-
contrastColor: '#ffffff',
|
|
659
|
-
hoverColor: '{primary.700}',
|
|
660
|
-
activeColor: '{primary.800}',
|
|
661
|
-
},
|
|
662
|
-
highlight: {
|
|
663
|
-
background: '{primary.600}',
|
|
664
|
-
focusBackground: '{primary.700}',
|
|
665
|
-
color: '#ffffff',
|
|
666
|
-
focusColor: '#ffffff',
|
|
667
|
-
},
|
|
668
|
-
},
|
|
669
|
-
dark: {
|
|
670
|
-
primary: {
|
|
671
|
-
color: '{primary.500}',
|
|
672
|
-
contrastColor: '{surface.900}',
|
|
673
|
-
hoverColor: '{primary.400}',
|
|
674
|
-
activeColor: '{primary.300}',
|
|
675
|
-
},
|
|
676
|
-
highlight: {
|
|
677
|
-
background: '{primary.500}',
|
|
678
|
-
focusBackground: '{primary.400}',
|
|
679
|
-
color: '{surface.900}',
|
|
680
|
-
focusColor: '{surface.900}',
|
|
681
|
-
},
|
|
682
|
-
},
|
|
683
|
-
},
|
|
684
|
-
},
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
else {
|
|
688
|
-
return {
|
|
689
|
-
semantic: {
|
|
690
|
-
primary: color.palette,
|
|
691
|
-
colorScheme: {
|
|
692
|
-
light: {
|
|
693
|
-
primary: {
|
|
694
|
-
color: '{primary.500}',
|
|
695
|
-
contrastColor: '#ffffff',
|
|
696
|
-
hoverColor: '{primary.600}',
|
|
697
|
-
activeColor: '{primary.700}',
|
|
698
|
-
},
|
|
699
|
-
highlight: {
|
|
700
|
-
background: '{primary.50}',
|
|
701
|
-
focusBackground: '{primary.100}',
|
|
702
|
-
color: '{primary.700}',
|
|
703
|
-
focusColor: '{primary.800}',
|
|
704
|
-
},
|
|
705
|
-
},
|
|
706
|
-
dark: {
|
|
707
|
-
primary: {
|
|
708
|
-
color: '{primary.400}',
|
|
709
|
-
contrastColor: '{surface.900}',
|
|
710
|
-
hoverColor: '{primary.300}',
|
|
711
|
-
activeColor: '{primary.200}',
|
|
712
|
-
},
|
|
713
|
-
highlight: {
|
|
714
|
-
background: 'color-mix(in srgb, {primary.400}, transparent 84%)',
|
|
715
|
-
focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)',
|
|
716
|
-
color: 'rgba(255,255,255,.87)',
|
|
717
|
-
focusColor: 'rgba(255,255,255,.87)',
|
|
718
|
-
},
|
|
719
|
-
},
|
|
720
|
-
},
|
|
721
|
-
},
|
|
722
|
-
};
|
|
723
|
-
}
|
|
468
|
+
for (const [name, pal] of Object.entries(PRIMARY_PALETTES)) {
|
|
469
|
+
palettes.push({ name, palette: pal });
|
|
724
470
|
}
|
|
725
|
-
|
|
471
|
+
return palettes;
|
|
472
|
+
}, /* @ts-ignore */
|
|
473
|
+
...(ngDevMode ? [{ debugName: "primaryColors" }] : /* istanbul ignore next */ []));
|
|
726
474
|
updateColors(event, type, color) {
|
|
727
475
|
if (type === 'primary') {
|
|
728
476
|
this.layoutService.updateLayoutConfig({ primary: color.name });
|
|
@@ -730,291 +478,727 @@ class AppConfigurator {
|
|
|
730
478
|
else if (type === 'surface') {
|
|
731
479
|
this.layoutService.updateLayoutConfig({ surface: color.name });
|
|
732
480
|
}
|
|
733
|
-
this.
|
|
481
|
+
this._applyColorChange(type, color);
|
|
734
482
|
event.stopPropagation();
|
|
735
483
|
}
|
|
736
|
-
|
|
484
|
+
_applyColorChange(type, color) {
|
|
485
|
+
const isDark = this.layoutService.layoutConfig().darkTheme ?? false;
|
|
737
486
|
if (type === 'primary') {
|
|
738
|
-
|
|
487
|
+
const primary = color.name ?? 'violet';
|
|
488
|
+
const pal = primary === 'noir'
|
|
489
|
+
? Object.fromEntries(Object.entries(SURFACE_PALETTES['slate']).map(([k, v]) => [k, v]))
|
|
490
|
+
: PRIMARY_PALETTES[primary];
|
|
491
|
+
if (pal)
|
|
492
|
+
applyPrimaryPalette(pal, isDark);
|
|
739
493
|
}
|
|
740
494
|
else if (type === 'surface') {
|
|
741
|
-
|
|
495
|
+
const surfacePal = SURFACE_PALETTES[color.name ?? 'slate'];
|
|
496
|
+
if (surfacePal)
|
|
497
|
+
applySurfacePalette(surfacePal);
|
|
742
498
|
}
|
|
743
499
|
}
|
|
744
500
|
onPresetChange(event) {
|
|
745
501
|
this.layoutService.updateLayoutConfig({ preset: event });
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
const surfacePalette = this.surfaces.find((s) => s.name === (selectedSurface || defaultSurface))?.palette;
|
|
751
|
-
$t()
|
|
752
|
-
.preset(preset)
|
|
753
|
-
.preset(this.getPresetExt())
|
|
754
|
-
.surfacePalette(surfacePalette)
|
|
755
|
-
.use({ useDefaultOptions: true });
|
|
502
|
+
const isDark = this.layoutService.layoutConfig().darkTheme ?? false;
|
|
503
|
+
const primary = this.layoutService.layoutConfig().primary ?? 'violet';
|
|
504
|
+
const surface = this.layoutService.layoutConfig().surface ?? null;
|
|
505
|
+
applyTheme({ preset: event, primary, surface, darkTheme: isDark });
|
|
756
506
|
}
|
|
757
507
|
onMenuModeChange(event) {
|
|
758
508
|
this.layoutService.updateLayoutConfig({ menuMode: event });
|
|
759
509
|
}
|
|
760
|
-
|
|
761
|
-
|
|
510
|
+
translate(key) {
|
|
511
|
+
return this.translateAdapter?.translate(key) ?? key;
|
|
512
|
+
}
|
|
513
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
514
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppConfigurator, isStandalone: true, selector: "app-configurator", host: { styleAttribute: "background-color: var(--surface-overlay); inset-inline-end: 0;", classAttribute: "hidden absolute top-[3.25rem] w-[calc(100vw-2rem)] sm:w-72 max-w-72 p-4 border border-surface rounded-border origin-top shadow-xl" }, ngImport: i0, template: `
|
|
762
515
|
<div class="flex flex-col gap-4">
|
|
763
516
|
<div>
|
|
764
|
-
<span class="text-sm text-muted-color font-semibold">
|
|
517
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
518
|
+
'layout.configurator.primary' | translate
|
|
519
|
+
}}</span>
|
|
765
520
|
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
766
521
|
@for (primaryColor of primaryColors(); track primaryColor.name) {
|
|
767
522
|
<button
|
|
768
523
|
type="button"
|
|
769
524
|
[title]="primaryColor.name"
|
|
770
525
|
(click)="updateColors($event, 'primary', primaryColor)"
|
|
771
|
-
|
|
772
|
-
'outline-primary': primaryColor.name === selectedPrimaryColor()
|
|
773
|
-
}"
|
|
526
|
+
[class.outline-primary]="primaryColor.name === selectedPrimaryColor()"
|
|
774
527
|
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
528
|
+
[style.background-color]="
|
|
529
|
+
primaryColor?.name === 'noir'
|
|
530
|
+
? 'var(--f-text-color)'
|
|
531
|
+
: primaryColor?.palette?.['500']
|
|
532
|
+
"
|
|
778
533
|
></button>
|
|
779
534
|
}
|
|
780
535
|
</div>
|
|
781
536
|
</div>
|
|
782
537
|
<div>
|
|
783
|
-
<span class="text-sm text-muted-color font-semibold">
|
|
538
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
539
|
+
'layout.configurator.surface' | translate
|
|
540
|
+
}}</span>
|
|
784
541
|
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
785
542
|
@for (surface of surfaces; track surface.name) {
|
|
786
543
|
<button
|
|
787
544
|
type="button"
|
|
788
545
|
[title]="surface.name"
|
|
789
546
|
(click)="updateColors($event, 'surface', surface)"
|
|
790
|
-
|
|
791
|
-
'outline-primary': selectedSurfaceColor()
|
|
792
|
-
? selectedSurfaceColor() === surface.name
|
|
793
|
-
: layoutService.layoutConfig().darkTheme
|
|
794
|
-
? surface.name === 'zinc'
|
|
795
|
-
: surface.name === 'slate'
|
|
796
|
-
}"
|
|
547
|
+
[class.outline-primary]="isSurfaceSelected(surface.name)"
|
|
797
548
|
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
549
|
+
[style.background-color]="
|
|
550
|
+
surface?.name === 'noir'
|
|
551
|
+
? 'var(--f-text-color)'
|
|
552
|
+
: surface?.palette?.['500']
|
|
553
|
+
"
|
|
801
554
|
></button>
|
|
802
555
|
}
|
|
803
556
|
</div>
|
|
804
557
|
</div>
|
|
805
558
|
<div class="flex flex-col gap-2">
|
|
806
|
-
<span class="text-sm text-muted-color font-semibold">
|
|
807
|
-
|
|
559
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
560
|
+
'layout.configurator.presets' | translate
|
|
561
|
+
}}</span>
|
|
562
|
+
<f-selectbutton
|
|
808
563
|
[options]="presets"
|
|
809
564
|
[ngModel]="selectedPreset()"
|
|
810
565
|
(ngModelChange)="onPresetChange($event)"
|
|
811
566
|
[allowEmpty]="false"
|
|
812
567
|
size="small"
|
|
813
|
-
|
|
568
|
+
/>
|
|
814
569
|
</div>
|
|
815
570
|
@if (showMenuModeButton()) {
|
|
816
571
|
<div class="flex flex-col gap-2">
|
|
817
|
-
<span class="text-sm text-muted-color font-semibold">
|
|
818
|
-
|
|
572
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
573
|
+
'layout.configurator.menu.mode' | translate
|
|
574
|
+
}}</span>
|
|
575
|
+
<f-selectbutton
|
|
819
576
|
[ngModel]="menuMode()"
|
|
820
577
|
(ngModelChange)="onMenuModeChange($event)"
|
|
821
|
-
[options]="menuModeOptions"
|
|
578
|
+
[options]="menuModeOptions()"
|
|
822
579
|
[allowEmpty]="false"
|
|
823
580
|
size="small"
|
|
824
|
-
|
|
581
|
+
/>
|
|
825
582
|
</div>
|
|
826
583
|
}
|
|
827
584
|
</div>
|
|
828
|
-
|
|
585
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.SelectButtonComponent, selector: "f-selectbutton", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "multiple", "disabled", "invalid", "allowEmpty", "size", "styleClass", "style"], outputs: ["onChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
829
586
|
}
|
|
830
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
587
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppConfigurator, decorators: [{
|
|
831
588
|
type: Component,
|
|
832
589
|
args: [{
|
|
833
590
|
selector: 'app-configurator',
|
|
834
|
-
|
|
835
|
-
imports: [NgClass, FormsModule, SelectButtonModule],
|
|
591
|
+
imports: [UiModule, FormsModule, TranslatePipe],
|
|
836
592
|
template: `
|
|
837
593
|
<div class="flex flex-col gap-4">
|
|
838
594
|
<div>
|
|
839
|
-
<span class="text-sm text-muted-color font-semibold">
|
|
595
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
596
|
+
'layout.configurator.primary' | translate
|
|
597
|
+
}}</span>
|
|
840
598
|
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
841
599
|
@for (primaryColor of primaryColors(); track primaryColor.name) {
|
|
842
600
|
<button
|
|
843
601
|
type="button"
|
|
844
602
|
[title]="primaryColor.name"
|
|
845
603
|
(click)="updateColors($event, 'primary', primaryColor)"
|
|
846
|
-
|
|
847
|
-
'outline-primary': primaryColor.name === selectedPrimaryColor()
|
|
848
|
-
}"
|
|
604
|
+
[class.outline-primary]="primaryColor.name === selectedPrimaryColor()"
|
|
849
605
|
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
606
|
+
[style.background-color]="
|
|
607
|
+
primaryColor?.name === 'noir'
|
|
608
|
+
? 'var(--f-text-color)'
|
|
609
|
+
: primaryColor?.palette?.['500']
|
|
610
|
+
"
|
|
853
611
|
></button>
|
|
854
612
|
}
|
|
855
613
|
</div>
|
|
856
614
|
</div>
|
|
857
615
|
<div>
|
|
858
|
-
<span class="text-sm text-muted-color font-semibold">
|
|
616
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
617
|
+
'layout.configurator.surface' | translate
|
|
618
|
+
}}</span>
|
|
859
619
|
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
|
860
620
|
@for (surface of surfaces; track surface.name) {
|
|
861
621
|
<button
|
|
862
622
|
type="button"
|
|
863
623
|
[title]="surface.name"
|
|
864
624
|
(click)="updateColors($event, 'surface', surface)"
|
|
865
|
-
|
|
866
|
-
'outline-primary': selectedSurfaceColor()
|
|
867
|
-
? selectedSurfaceColor() === surface.name
|
|
868
|
-
: layoutService.layoutConfig().darkTheme
|
|
869
|
-
? surface.name === 'zinc'
|
|
870
|
-
: surface.name === 'slate'
|
|
871
|
-
}"
|
|
625
|
+
[class.outline-primary]="isSurfaceSelected(surface.name)"
|
|
872
626
|
class="border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1"
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
627
|
+
[style.background-color]="
|
|
628
|
+
surface?.name === 'noir'
|
|
629
|
+
? 'var(--f-text-color)'
|
|
630
|
+
: surface?.palette?.['500']
|
|
631
|
+
"
|
|
876
632
|
></button>
|
|
877
633
|
}
|
|
878
634
|
</div>
|
|
879
635
|
</div>
|
|
880
636
|
<div class="flex flex-col gap-2">
|
|
881
|
-
<span class="text-sm text-muted-color font-semibold">
|
|
882
|
-
|
|
637
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
638
|
+
'layout.configurator.presets' | translate
|
|
639
|
+
}}</span>
|
|
640
|
+
<f-selectbutton
|
|
883
641
|
[options]="presets"
|
|
884
642
|
[ngModel]="selectedPreset()"
|
|
885
643
|
(ngModelChange)="onPresetChange($event)"
|
|
886
644
|
[allowEmpty]="false"
|
|
887
645
|
size="small"
|
|
888
|
-
|
|
646
|
+
/>
|
|
889
647
|
</div>
|
|
890
648
|
@if (showMenuModeButton()) {
|
|
891
649
|
<div class="flex flex-col gap-2">
|
|
892
|
-
<span class="text-sm text-muted-color font-semibold">
|
|
893
|
-
|
|
650
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
651
|
+
'layout.configurator.menu.mode' | translate
|
|
652
|
+
}}</span>
|
|
653
|
+
<f-selectbutton
|
|
894
654
|
[ngModel]="menuMode()"
|
|
895
655
|
(ngModelChange)="onMenuModeChange($event)"
|
|
896
|
-
[options]="menuModeOptions"
|
|
656
|
+
[options]="menuModeOptions()"
|
|
897
657
|
[allowEmpty]="false"
|
|
898
658
|
size="small"
|
|
899
|
-
|
|
659
|
+
/>
|
|
900
660
|
</div>
|
|
901
661
|
}
|
|
902
662
|
</div>
|
|
903
|
-
|
|
663
|
+
`,
|
|
904
664
|
host: {
|
|
905
|
-
class: 'hidden absolute top-[3.25rem]
|
|
906
|
-
style: 'background-color: var(--surface-overlay)',
|
|
665
|
+
class: 'hidden absolute top-[3.25rem] w-[calc(100vw-2rem)] sm:w-72 max-w-72 p-4 border border-surface rounded-border origin-top shadow-xl',
|
|
666
|
+
style: 'background-color: var(--surface-overlay); inset-inline-end: 0;',
|
|
907
667
|
},
|
|
908
668
|
}]
|
|
909
669
|
}], ctorParameters: () => [] });
|
|
910
670
|
|
|
671
|
+
const LAYOUT_AUTH_STATE = new InjectionToken('LAYOUT_AUTH_STATE');
|
|
672
|
+
const LAYOUT_AUTH_API = new InjectionToken('LAYOUT_AUTH_API');
|
|
673
|
+
const LAYOUT_NOTIFICATION_BELL = new InjectionToken('LAYOUT_NOTIFICATION_BELL');
|
|
674
|
+
const LAYOUT_LANGUAGE_SELECTOR = new InjectionToken('LAYOUT_LANGUAGE_SELECTOR');
|
|
675
|
+
const LAYOUT_LANGUAGE_SELECTOR_PANEL = new InjectionToken('LAYOUT_LANGUAGE_SELECTOR_PANEL');
|
|
676
|
+
const LAYOUT_SEARCHBAR = new InjectionToken('LAYOUT_SEARCHBAR');
|
|
677
|
+
const LAYOUT_IS_RTL = new InjectionToken('LAYOUT_IS_RTL');
|
|
678
|
+
const LAYOUT_SEARCH_ADAPTER = new InjectionToken('LAYOUT_SEARCH_ADAPTER');
|
|
679
|
+
|
|
911
680
|
class AppFloatingConfigurator {
|
|
912
681
|
layoutService = inject(LayoutService);
|
|
913
|
-
|
|
682
|
+
languageSelectorPanel = inject(LAYOUT_LANGUAGE_SELECTOR_PANEL, {
|
|
683
|
+
optional: true,
|
|
684
|
+
});
|
|
685
|
+
isDarkTheme = computed(() => this.layoutService.layoutConfig().darkTheme, /* @ts-ignore */
|
|
686
|
+
...(ngDevMode ? [{ debugName: "isDarkTheme" }] : /* istanbul ignore next */ []));
|
|
914
687
|
toggleDarkMode() {
|
|
915
688
|
const currentDarkTheme = this.layoutService.layoutConfig().darkTheme;
|
|
916
689
|
this.layoutService.updateLayoutConfig({ darkTheme: !currentDarkTheme });
|
|
917
690
|
}
|
|
918
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
919
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
691
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppFloatingConfigurator, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
692
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppFloatingConfigurator, isStandalone: true, selector: "app-floating-configurator", ngImport: i0, template: `
|
|
693
|
+
<div
|
|
694
|
+
class="floating-configurator fixed flex flex-col md:flex-row gap-2 md:gap-4 top-4 md:top-8 z-50"
|
|
695
|
+
>
|
|
696
|
+
<button
|
|
697
|
+
fButton
|
|
698
|
+
type="button"
|
|
699
|
+
(click)="toggleDarkMode()"
|
|
700
|
+
[rounded]="true"
|
|
701
|
+
severity="secondary"
|
|
702
|
+
>
|
|
703
|
+
<ui-icon pButtonIcon [name]="isDarkTheme() ? 'moon' : 'sun'"></ui-icon>
|
|
704
|
+
</button>
|
|
705
|
+
@if (languageSelectorPanel) {
|
|
706
|
+
<div class="relative">
|
|
707
|
+
<button
|
|
708
|
+
fButton
|
|
709
|
+
fStyleClass="@next"
|
|
710
|
+
enterFromClass="hidden"
|
|
711
|
+
enterActiveClass="animate-scalein"
|
|
712
|
+
leaveToClass="hidden"
|
|
713
|
+
leaveActiveClass="animate-fadeout"
|
|
714
|
+
[hideOnOutsideClick]="true"
|
|
715
|
+
type="button"
|
|
716
|
+
rounded
|
|
717
|
+
>
|
|
718
|
+
<ui-icon pButtonIcon name="globe"></ui-icon>
|
|
719
|
+
</button>
|
|
720
|
+
<ng-container *ngComponentOutlet="languageSelectorPanel" />
|
|
926
721
|
</div>
|
|
927
|
-
|
|
722
|
+
}
|
|
723
|
+
<div class="relative">
|
|
724
|
+
<button
|
|
725
|
+
fButton
|
|
726
|
+
fStyleClass="@next"
|
|
727
|
+
enterFromClass="hidden"
|
|
728
|
+
enterActiveClass="animate-scalein"
|
|
729
|
+
leaveToClass="hidden"
|
|
730
|
+
leaveActiveClass="animate-fadeout"
|
|
731
|
+
[hideOnOutsideClick]="true"
|
|
732
|
+
type="button"
|
|
733
|
+
rounded
|
|
734
|
+
>
|
|
735
|
+
<ui-icon pButtonIcon name="palette"></ui-icon>
|
|
736
|
+
</button>
|
|
737
|
+
<app-configurator />
|
|
738
|
+
</div>
|
|
739
|
+
</div>
|
|
740
|
+
`, isInline: true, styles: [".floating-configurator{inset-inline-end:.5rem}@media(min-width:768px){.floating-configurator{inset-inline-end:2rem}}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "directive", type: i1.StyleClassDirective, selector: "[fStyleClass]", inputs: ["fStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }] });
|
|
928
741
|
}
|
|
929
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
742
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppFloatingConfigurator, decorators: [{
|
|
930
743
|
type: Component,
|
|
931
|
-
args: [{
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
744
|
+
args: [{ selector: 'app-floating-configurator', imports: [NgComponentOutlet, UiModule, AppConfigurator], template: `
|
|
745
|
+
<div
|
|
746
|
+
class="floating-configurator fixed flex flex-col md:flex-row gap-2 md:gap-4 top-4 md:top-8 z-50"
|
|
747
|
+
>
|
|
748
|
+
<button
|
|
749
|
+
fButton
|
|
750
|
+
type="button"
|
|
751
|
+
(click)="toggleDarkMode()"
|
|
752
|
+
[rounded]="true"
|
|
753
|
+
severity="secondary"
|
|
754
|
+
>
|
|
755
|
+
<ui-icon pButtonIcon [name]="isDarkTheme() ? 'moon' : 'sun'"></ui-icon>
|
|
756
|
+
</button>
|
|
757
|
+
@if (languageSelectorPanel) {
|
|
758
|
+
<div class="relative">
|
|
759
|
+
<button
|
|
760
|
+
fButton
|
|
761
|
+
fStyleClass="@next"
|
|
762
|
+
enterFromClass="hidden"
|
|
763
|
+
enterActiveClass="animate-scalein"
|
|
764
|
+
leaveToClass="hidden"
|
|
765
|
+
leaveActiveClass="animate-fadeout"
|
|
766
|
+
[hideOnOutsideClick]="true"
|
|
767
|
+
type="button"
|
|
768
|
+
rounded
|
|
769
|
+
>
|
|
770
|
+
<ui-icon pButtonIcon name="globe"></ui-icon>
|
|
771
|
+
</button>
|
|
772
|
+
<ng-container *ngComponentOutlet="languageSelectorPanel" />
|
|
942
773
|
</div>
|
|
943
|
-
|
|
944
|
-
|
|
774
|
+
}
|
|
775
|
+
<div class="relative">
|
|
776
|
+
<button
|
|
777
|
+
fButton
|
|
778
|
+
fStyleClass="@next"
|
|
779
|
+
enterFromClass="hidden"
|
|
780
|
+
enterActiveClass="animate-scalein"
|
|
781
|
+
leaveToClass="hidden"
|
|
782
|
+
leaveActiveClass="animate-fadeout"
|
|
783
|
+
[hideOnOutsideClick]="true"
|
|
784
|
+
type="button"
|
|
785
|
+
rounded
|
|
786
|
+
>
|
|
787
|
+
<ui-icon pButtonIcon name="palette"></ui-icon>
|
|
788
|
+
</button>
|
|
789
|
+
<app-configurator />
|
|
790
|
+
</div>
|
|
791
|
+
</div>
|
|
792
|
+
`, styles: [".floating-configurator{inset-inline-end:.5rem}@media(min-width:768px){.floating-configurator{inset-inline-end:2rem}}\n"] }]
|
|
945
793
|
}] });
|
|
946
794
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
*/
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
795
|
+
class AppMenuitem {
|
|
796
|
+
// Signal inputs
|
|
797
|
+
item = input.required(/* @ts-ignore */
|
|
798
|
+
...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
799
|
+
index = input.required(/* @ts-ignore */
|
|
800
|
+
...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
801
|
+
parentKey = input('', /* @ts-ignore */
|
|
802
|
+
...(ngDevMode ? [{ debugName: "parentKey" }] : /* istanbul ignore next */ []));
|
|
803
|
+
// Injected services
|
|
804
|
+
router = inject(Router);
|
|
805
|
+
layoutService = inject(LayoutService);
|
|
957
806
|
destroyRef = inject(DestroyRef);
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
807
|
+
// View references
|
|
808
|
+
parentLink = viewChild('parentLink', /* @ts-ignore */
|
|
809
|
+
...(ngDevMode ? [{ debugName: "parentLink" }] : /* istanbul ignore next */ []));
|
|
810
|
+
// State signals - private writable + public readonly pattern
|
|
811
|
+
_active = signal(false, /* @ts-ignore */
|
|
812
|
+
...(ngDevMode ? [{ debugName: "_active" }] : /* istanbul ignore next */ []));
|
|
813
|
+
active = this._active.asReadonly();
|
|
814
|
+
// Computed signals
|
|
815
|
+
key = computed(() => {
|
|
816
|
+
const parent = this.parentKey();
|
|
817
|
+
return parent ? `${parent}-${this.index()}` : String(this.index());
|
|
818
|
+
}, /* @ts-ignore */
|
|
819
|
+
...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
|
|
820
|
+
routerLink = computed(() => {
|
|
821
|
+
const menuItem = this.item();
|
|
822
|
+
return menuItem.routerLink ?? [];
|
|
823
|
+
}, /* @ts-ignore */
|
|
824
|
+
...(ngDevMode ? [{ debugName: "routerLink" }] : /* istanbul ignore next */ []));
|
|
825
|
+
// Computed options for routerLinkActive - use 'exact' for root, 'subset' for others
|
|
826
|
+
routerLinkActiveOptions = computed(() => {
|
|
827
|
+
const link = this.routerLink();
|
|
828
|
+
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
829
|
+
return {
|
|
830
|
+
paths: pathsMatch,
|
|
831
|
+
queryParams: 'ignored',
|
|
832
|
+
matrixParams: 'ignored',
|
|
833
|
+
fragment: 'ignored',
|
|
834
|
+
};
|
|
835
|
+
}, /* @ts-ignore */
|
|
836
|
+
...(ngDevMode ? [{ debugName: "routerLinkActiveOptions" }] : /* istanbul ignore next */ []));
|
|
965
837
|
constructor() {
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
.
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
this.
|
|
838
|
+
// Subscribe to menu source changes
|
|
839
|
+
this.layoutService.menuSource$
|
|
840
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
841
|
+
.subscribe((value) => {
|
|
842
|
+
Promise.resolve(null).then(() => {
|
|
843
|
+
const currentKey = this.key();
|
|
844
|
+
if (value.routeEvent) {
|
|
845
|
+
this._active.set(value.key === currentKey ||
|
|
846
|
+
value.key?.startsWith(currentKey + '-'));
|
|
847
|
+
}
|
|
848
|
+
else if (value.key !== currentKey &&
|
|
849
|
+
!value.key?.startsWith(currentKey + '-')) {
|
|
850
|
+
this._active.set(false);
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
});
|
|
854
|
+
// Subscribe to menu reset
|
|
855
|
+
this.layoutService.resetSource$
|
|
856
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
857
|
+
.subscribe(() => this._active.set(false));
|
|
858
|
+
// Subscribe to navigation events
|
|
859
|
+
this.router.events
|
|
860
|
+
.pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
861
|
+
.subscribe(() => {
|
|
862
|
+
if (this.item().routerLink) {
|
|
863
|
+
this.updateActiveStateFromRoute();
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
// Effect to update active state on init when item has routerLink
|
|
867
|
+
effect(() => {
|
|
868
|
+
const menuItem = this.item();
|
|
869
|
+
if (menuItem.routerLink) {
|
|
870
|
+
this.updateActiveStateFromRoute();
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
// Effect to update submenu position when active in topbar mode
|
|
874
|
+
effect(() => {
|
|
875
|
+
if (this.active() && this.parentLink()) {
|
|
876
|
+
Promise.resolve().then(() => this.updateSubmenuPosition());
|
|
972
877
|
}
|
|
973
878
|
});
|
|
974
879
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
companies = this._companies.asReadonly();
|
|
979
|
-
_branches = signal([], ...(ngDevMode ? [{ debugName: "_branches" }] : []));
|
|
980
|
-
branches = this._branches.asReadonly();
|
|
981
|
-
_selectedCompanyId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedCompanyId" }] : []));
|
|
982
|
-
selectedCompanyId = this._selectedCompanyId.asReadonly();
|
|
983
|
-
_selectedBranchId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedBranchId" }] : []));
|
|
984
|
-
selectedBranchId = this._selectedBranchId.asReadonly();
|
|
985
|
-
_isLoadingCompanies = signal(false, ...(ngDevMode ? [{ debugName: "_isLoadingCompanies" }] : []));
|
|
986
|
-
isLoadingCompanies = this._isLoadingCompanies.asReadonly();
|
|
987
|
-
_isLoadingBranches = signal(false, ...(ngDevMode ? [{ debugName: "_isLoadingBranches" }] : []));
|
|
988
|
-
isLoadingBranches = this._isLoadingBranches.asReadonly();
|
|
989
|
-
_isSwitching = signal(false, ...(ngDevMode ? [{ debugName: "_isSwitching" }] : []));
|
|
990
|
-
isSwitching = this._isSwitching.asReadonly();
|
|
991
|
-
canSwitch = computed(() => {
|
|
992
|
-
const selectedCompany = this.selectedCompanyId();
|
|
993
|
-
if (!selectedCompany)
|
|
994
|
-
return false;
|
|
995
|
-
const currentCompanyId = this.authState?.currentCompanyInfo()?.id;
|
|
996
|
-
const currentBranchId = this.authState?.currentBranchInfo()?.id;
|
|
997
|
-
const selectedBranch = this.selectedBranchId();
|
|
998
|
-
return selectedCompany !== currentCompanyId || selectedBranch !== currentBranchId;
|
|
999
|
-
}, ...(ngDevMode ? [{ debugName: "canSwitch" }] : []));
|
|
1000
|
-
onPanelToggle() {
|
|
1001
|
-
this._isActive.update((v) => !v);
|
|
1002
|
-
if (this.isActive() && this.companies().length === 0) {
|
|
1003
|
-
this.loadCompanies();
|
|
880
|
+
onMouseEnter() {
|
|
881
|
+
if (this.layoutService.isTopbar() && this.item().children) {
|
|
882
|
+
this.updateSubmenuPosition();
|
|
1004
883
|
}
|
|
1005
884
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
885
|
+
updateSubmenuPosition() {
|
|
886
|
+
const linkEl = this.parentLink()?.nativeElement;
|
|
887
|
+
if (!linkEl || typeof linkEl.getBoundingClientRect !== 'function')
|
|
1008
888
|
return;
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
889
|
+
const rect = linkEl.getBoundingClientRect();
|
|
890
|
+
const hostEl = linkEl.closest('[app-menuitem]');
|
|
891
|
+
if (!hostEl)
|
|
892
|
+
return;
|
|
893
|
+
// Position submenu to the left of the parent link and above it
|
|
894
|
+
const left = rect.left;
|
|
895
|
+
const top = rect.top + 30; // 8px gap above the menu item
|
|
896
|
+
hostEl.style.setProperty('--submenu-left', `${left}px`);
|
|
897
|
+
hostEl.style.setProperty('--submenu-top', `${top}px`);
|
|
898
|
+
}
|
|
899
|
+
updateActiveStateFromRoute() {
|
|
900
|
+
const link = this.routerLink();
|
|
901
|
+
if (!link.length)
|
|
902
|
+
return;
|
|
903
|
+
// Use 'exact' for root path to avoid matching all routes
|
|
904
|
+
// Use 'subset' for all other paths to match child routes
|
|
905
|
+
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
906
|
+
const active = isActive(link[0], this.router, {
|
|
907
|
+
paths: pathsMatch,
|
|
908
|
+
queryParams: 'ignored',
|
|
909
|
+
matrixParams: 'ignored',
|
|
910
|
+
fragment: 'ignored',
|
|
911
|
+
})();
|
|
912
|
+
if (active) {
|
|
913
|
+
this.layoutService.onMenuStateChange({
|
|
914
|
+
key: this.key(),
|
|
915
|
+
routeEvent: true,
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
itemClick() {
|
|
920
|
+
if (this.item().children && !this.layoutService.isTopbar()) {
|
|
921
|
+
this._active.update((prev) => !prev);
|
|
922
|
+
}
|
|
923
|
+
this.layoutService.onMenuStateChange({ key: this.key() });
|
|
924
|
+
}
|
|
925
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenuitem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
926
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppMenuitem, isStandalone: true, selector: "[app-menuitem]", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, parentKey: { classPropertyName: "parentKey", publicName: "parentKey", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.active-menuitem": "active()" } }, viewQueries: [{ propertyName: "parentLink", first: true, predicate: ["parentLink"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
927
|
+
<ng-container>
|
|
928
|
+
@if (item().children?.length) {
|
|
929
|
+
<a
|
|
930
|
+
(click)="itemClick()"
|
|
931
|
+
(mouseenter)="onMouseEnter()"
|
|
932
|
+
#parentLink
|
|
933
|
+
tabindex="0"
|
|
934
|
+
>
|
|
935
|
+
@if (item().icon) {
|
|
936
|
+
<lib-icon
|
|
937
|
+
[icon]="item().icon!"
|
|
938
|
+
[iconType]="item().iconType"
|
|
939
|
+
class="layout-menuitem-icon"
|
|
940
|
+
/>
|
|
941
|
+
}
|
|
942
|
+
<span class="layout-menuitem-text">{{
|
|
943
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
944
|
+
}}</span>
|
|
945
|
+
@if (item().children) {
|
|
946
|
+
@if (layoutService.isTopbar()) {
|
|
947
|
+
<ui-icon name="chevron-right" class="layout-submenu-toggler"></ui-icon>
|
|
948
|
+
} @else {
|
|
949
|
+
<ui-icon name="angle-down" class="layout-submenu-toggler"></ui-icon>
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
</a>
|
|
953
|
+
}
|
|
954
|
+
@if (item().routerLink && !item().children?.length) {
|
|
955
|
+
<a
|
|
956
|
+
(click)="itemClick()"
|
|
957
|
+
[routerLink]="routerLink()"
|
|
958
|
+
routerLinkActive="active-route"
|
|
959
|
+
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
960
|
+
tabindex="0"
|
|
961
|
+
>
|
|
962
|
+
@if (item().icon) {
|
|
963
|
+
<lib-icon
|
|
964
|
+
[icon]="item().icon!"
|
|
965
|
+
[iconType]="item().iconType"
|
|
966
|
+
class="layout-menuitem-icon"
|
|
967
|
+
/>
|
|
968
|
+
}
|
|
969
|
+
<span class="layout-menuitem-text">{{
|
|
970
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
971
|
+
}}</span>
|
|
972
|
+
</a>
|
|
973
|
+
}
|
|
974
|
+
@if (item().children) {
|
|
975
|
+
<ul
|
|
976
|
+
(mouseenter)="onMouseEnter()"
|
|
977
|
+
[class.submenu-expanded]="active() && !layoutService.isTopbar()"
|
|
978
|
+
[class.submenu-collapsed]="!active() && !layoutService.isTopbar()"
|
|
979
|
+
>
|
|
980
|
+
@for (
|
|
981
|
+
child of item().children;
|
|
982
|
+
track 'menu' + child.id + '' + i;
|
|
983
|
+
let i = $index
|
|
984
|
+
) {
|
|
985
|
+
<li
|
|
986
|
+
app-menuitem
|
|
987
|
+
[item]="child"
|
|
988
|
+
[index]="i"
|
|
989
|
+
[parentKey]="key()"
|
|
990
|
+
></li>
|
|
991
|
+
}
|
|
992
|
+
</ul>
|
|
993
|
+
}
|
|
994
|
+
</ng-container>
|
|
995
|
+
`, isInline: true, styles: [":host ul{overflow:hidden;transition:max-height .4s cubic-bezier(.86,0,.07,1)}:host ul.submenu-collapsed{max-height:0}:host ul.submenu-expanded{max-height:1000px}\n"], dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["icon", "iconType"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: i2$1.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
996
|
+
}
|
|
997
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenuitem, decorators: [{
|
|
998
|
+
type: Component,
|
|
999
|
+
args: [{ selector: '[app-menuitem]', imports: [UiModule, IconComponent, RouterModule, TranslatePipe], template: `
|
|
1000
|
+
<ng-container>
|
|
1001
|
+
@if (item().children?.length) {
|
|
1002
|
+
<a
|
|
1003
|
+
(click)="itemClick()"
|
|
1004
|
+
(mouseenter)="onMouseEnter()"
|
|
1005
|
+
#parentLink
|
|
1006
|
+
tabindex="0"
|
|
1007
|
+
>
|
|
1008
|
+
@if (item().icon) {
|
|
1009
|
+
<lib-icon
|
|
1010
|
+
[icon]="item().icon!"
|
|
1011
|
+
[iconType]="item().iconType"
|
|
1012
|
+
class="layout-menuitem-icon"
|
|
1013
|
+
/>
|
|
1014
|
+
}
|
|
1015
|
+
<span class="layout-menuitem-text">{{
|
|
1016
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
1017
|
+
}}</span>
|
|
1018
|
+
@if (item().children) {
|
|
1019
|
+
@if (layoutService.isTopbar()) {
|
|
1020
|
+
<ui-icon name="chevron-right" class="layout-submenu-toggler"></ui-icon>
|
|
1021
|
+
} @else {
|
|
1022
|
+
<ui-icon name="angle-down" class="layout-submenu-toggler"></ui-icon>
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
</a>
|
|
1026
|
+
}
|
|
1027
|
+
@if (item().routerLink && !item().children?.length) {
|
|
1028
|
+
<a
|
|
1029
|
+
(click)="itemClick()"
|
|
1030
|
+
[routerLink]="routerLink()"
|
|
1031
|
+
routerLinkActive="active-route"
|
|
1032
|
+
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
1033
|
+
tabindex="0"
|
|
1034
|
+
>
|
|
1035
|
+
@if (item().icon) {
|
|
1036
|
+
<lib-icon
|
|
1037
|
+
[icon]="item().icon!"
|
|
1038
|
+
[iconType]="item().iconType"
|
|
1039
|
+
class="layout-menuitem-icon"
|
|
1040
|
+
/>
|
|
1041
|
+
}
|
|
1042
|
+
<span class="layout-menuitem-text">{{
|
|
1043
|
+
item().labelKey ? (item().labelKey! | translate) : item().label
|
|
1044
|
+
}}</span>
|
|
1045
|
+
</a>
|
|
1046
|
+
}
|
|
1047
|
+
@if (item().children) {
|
|
1048
|
+
<ul
|
|
1049
|
+
(mouseenter)="onMouseEnter()"
|
|
1050
|
+
[class.submenu-expanded]="active() && !layoutService.isTopbar()"
|
|
1051
|
+
[class.submenu-collapsed]="!active() && !layoutService.isTopbar()"
|
|
1052
|
+
>
|
|
1053
|
+
@for (
|
|
1054
|
+
child of item().children;
|
|
1055
|
+
track 'menu' + child.id + '' + i;
|
|
1056
|
+
let i = $index
|
|
1057
|
+
) {
|
|
1058
|
+
<li
|
|
1059
|
+
app-menuitem
|
|
1060
|
+
[item]="child"
|
|
1061
|
+
[index]="i"
|
|
1062
|
+
[parentKey]="key()"
|
|
1063
|
+
></li>
|
|
1064
|
+
}
|
|
1065
|
+
</ul>
|
|
1066
|
+
}
|
|
1067
|
+
</ng-container>
|
|
1068
|
+
`, host: {
|
|
1069
|
+
'[class.active-menuitem]': 'active()',
|
|
1070
|
+
}, styles: [":host ul{overflow:hidden;transition:max-height .4s cubic-bezier(.86,0,.07,1)}:host ul.submenu-collapsed{max-height:0}:host ul.submenu-expanded{max-height:1000px}\n"] }]
|
|
1071
|
+
}], ctorParameters: () => [], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: true }] }], parentKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentKey", required: false }] }], parentLink: [{ type: i0.ViewChild, args: ['parentLink', { isSignal: true }] }] } });
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Main menu component that displays filtered menu items.
|
|
1075
|
+
* Menu is automatically filtered based on user permissions using the permission checker (logicNode pattern).
|
|
1076
|
+
* The filtering happens internally in LayoutService via computed signal.
|
|
1077
|
+
*/
|
|
1078
|
+
class AppMenu {
|
|
1079
|
+
layoutService = inject(LayoutService);
|
|
1080
|
+
/**
|
|
1081
|
+
* Filtered menu items from layout service.
|
|
1082
|
+
* This signal is computed in LayoutService and automatically updates when:
|
|
1083
|
+
* - Raw menu changes (setMenu called)
|
|
1084
|
+
* - Permission state changes (user permissions updated)
|
|
1085
|
+
*/
|
|
1086
|
+
menuItems = this.layoutService.menu;
|
|
1087
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1088
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppMenu, isStandalone: true, selector: "app-menu", ngImport: i0, template: `<div class="layout-menu">
|
|
1089
|
+
<ul>
|
|
1090
|
+
@for (
|
|
1091
|
+
item of menuItems();
|
|
1092
|
+
track 'menu' + item.id + '' + i;
|
|
1093
|
+
let i = $index
|
|
1094
|
+
) {
|
|
1095
|
+
<li app-menuitem [item]="item" [index]="i"></li>
|
|
1096
|
+
}
|
|
1097
|
+
</ul>
|
|
1098
|
+
</div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }] });
|
|
1099
|
+
}
|
|
1100
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppMenu, decorators: [{
|
|
1101
|
+
type: Component,
|
|
1102
|
+
args: [{
|
|
1103
|
+
selector: 'app-menu',
|
|
1104
|
+
imports: [AppMenuitem, RouterModule],
|
|
1105
|
+
template: `<div class="layout-menu">
|
|
1106
|
+
<ul>
|
|
1107
|
+
@for (
|
|
1108
|
+
item of menuItems();
|
|
1109
|
+
track 'menu' + item.id + '' + i;
|
|
1110
|
+
let i = $index
|
|
1111
|
+
) {
|
|
1112
|
+
<li app-menuitem [item]="item" [index]="i"></li>
|
|
1113
|
+
}
|
|
1114
|
+
</ul>
|
|
1115
|
+
</div>`,
|
|
1116
|
+
}]
|
|
1117
|
+
}] });
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* View Transitions API type definitions
|
|
1121
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
|
|
1122
|
+
*/
|
|
1123
|
+
|
|
1124
|
+
/** Company/branch switcher displayed in top bar */
|
|
1125
|
+
class AppCompanyBranchSelector {
|
|
1126
|
+
destroyRef = inject(DestroyRef);
|
|
1127
|
+
authState = inject(LAYOUT_AUTH_STATE, { optional: true });
|
|
1128
|
+
authApi = inject(LAYOUT_AUTH_API, { optional: true });
|
|
1129
|
+
messageService = inject(MessageService);
|
|
1130
|
+
translateAdapter = inject(TranslateService, {
|
|
1131
|
+
optional: true,
|
|
1132
|
+
});
|
|
1133
|
+
document = inject(DOCUMENT$1);
|
|
1134
|
+
elementRef = inject(ElementRef);
|
|
1135
|
+
_isActive = signal(false, /* @ts-ignore */
|
|
1136
|
+
...(ngDevMode ? [{ debugName: "_isActive" }] : /* istanbul ignore next */ []));
|
|
1137
|
+
isActive = this._isActive.asReadonly();
|
|
1138
|
+
constructor() {
|
|
1139
|
+
fromEvent(this.document, 'click')
|
|
1140
|
+
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
|
|
1141
|
+
.subscribe((event) => {
|
|
1142
|
+
const target = event.target;
|
|
1143
|
+
if (!this.elementRef.nativeElement.contains(target)) {
|
|
1144
|
+
this._isActive.set(false);
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
currentCompanyName = computed(() => this.authState?.currentCompanyInfo()?.name ??
|
|
1149
|
+
this.translate('layout.company.branch.selector.no.company'), /* @ts-ignore */
|
|
1150
|
+
...(ngDevMode ? [{ debugName: "currentCompanyName" }] : /* istanbul ignore next */ []));
|
|
1151
|
+
currentBranchName = computed(() => this.authState?.currentBranchInfo()?.name ?? null, /* @ts-ignore */
|
|
1152
|
+
...(ngDevMode ? [{ debugName: "currentBranchName" }] : /* istanbul ignore next */ []));
|
|
1153
|
+
_companies = signal([], /* @ts-ignore */
|
|
1154
|
+
...(ngDevMode ? [{ debugName: "_companies" }] : /* istanbul ignore next */ []));
|
|
1155
|
+
companies = this._companies.asReadonly();
|
|
1156
|
+
_branches = signal([], /* @ts-ignore */
|
|
1157
|
+
...(ngDevMode ? [{ debugName: "_branches" }] : /* istanbul ignore next */ []));
|
|
1158
|
+
branches = this._branches.asReadonly();
|
|
1159
|
+
_selectedCompanyId = signal(null, /* @ts-ignore */
|
|
1160
|
+
...(ngDevMode ? [{ debugName: "_selectedCompanyId" }] : /* istanbul ignore next */ []));
|
|
1161
|
+
selectedCompanyId = this._selectedCompanyId.asReadonly();
|
|
1162
|
+
_selectedBranchId = signal(null, /* @ts-ignore */
|
|
1163
|
+
...(ngDevMode ? [{ debugName: "_selectedBranchId" }] : /* istanbul ignore next */ []));
|
|
1164
|
+
selectedBranchId = this._selectedBranchId.asReadonly();
|
|
1165
|
+
_isLoadingCompanies = signal(false, /* @ts-ignore */
|
|
1166
|
+
...(ngDevMode ? [{ debugName: "_isLoadingCompanies" }] : /* istanbul ignore next */ []));
|
|
1167
|
+
isLoadingCompanies = this._isLoadingCompanies.asReadonly();
|
|
1168
|
+
_isLoadingBranches = signal(false, /* @ts-ignore */
|
|
1169
|
+
...(ngDevMode ? [{ debugName: "_isLoadingBranches" }] : /* istanbul ignore next */ []));
|
|
1170
|
+
isLoadingBranches = this._isLoadingBranches.asReadonly();
|
|
1171
|
+
_isSwitching = signal(false, /* @ts-ignore */
|
|
1172
|
+
...(ngDevMode ? [{ debugName: "_isSwitching" }] : /* istanbul ignore next */ []));
|
|
1173
|
+
isSwitching = this._isSwitching.asReadonly();
|
|
1174
|
+
canSwitch = computed(() => {
|
|
1175
|
+
const selectedCompany = this.selectedCompanyId();
|
|
1176
|
+
if (!selectedCompany)
|
|
1177
|
+
return false;
|
|
1178
|
+
const currentCompanyId = this.authState?.currentCompanyInfo()?.id;
|
|
1179
|
+
const currentBranchId = this.authState?.currentBranchInfo()?.id;
|
|
1180
|
+
const selectedBranch = this.selectedBranchId();
|
|
1181
|
+
return (selectedCompany !== currentCompanyId || selectedBranch !== currentBranchId);
|
|
1182
|
+
}, /* @ts-ignore */
|
|
1183
|
+
...(ngDevMode ? [{ debugName: "canSwitch" }] : /* istanbul ignore next */ []));
|
|
1184
|
+
onPanelToggle() {
|
|
1185
|
+
this._isActive.update((v) => !v);
|
|
1186
|
+
if (this.isActive() && this.companies().length === 0) {
|
|
1187
|
+
this.loadCompanies();
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
loadCompanies() {
|
|
1191
|
+
if (!this.authApi)
|
|
1192
|
+
return;
|
|
1193
|
+
this._isLoadingCompanies.set(true);
|
|
1194
|
+
this.authApi
|
|
1195
|
+
.getUserCompanies()
|
|
1196
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1197
|
+
.subscribe({
|
|
1198
|
+
next: (companies) => {
|
|
1199
|
+
this._companies.set(companies);
|
|
1200
|
+
this._isLoadingCompanies.set(false);
|
|
1201
|
+
},
|
|
1018
1202
|
error: () => {
|
|
1019
1203
|
// Error toast handled by global interceptor
|
|
1020
1204
|
this._isLoadingCompanies.set(false);
|
|
@@ -1064,8 +1248,8 @@ class AppCompanyBranchSelector {
|
|
|
1064
1248
|
if (this.branches().length > 0 && !selectedBranch) {
|
|
1065
1249
|
this.messageService.add({
|
|
1066
1250
|
severity: 'warn',
|
|
1067
|
-
summary: '
|
|
1068
|
-
detail: '
|
|
1251
|
+
summary: this.translate('layout.company.branch.selector.branch.required'),
|
|
1252
|
+
detail: this.translate('layout.company.branch.selector.please.select.branch'),
|
|
1069
1253
|
});
|
|
1070
1254
|
return;
|
|
1071
1255
|
}
|
|
@@ -1081,13 +1265,16 @@ class AppCompanyBranchSelector {
|
|
|
1081
1265
|
},
|
|
1082
1266
|
});
|
|
1083
1267
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1268
|
+
translate(key) {
|
|
1269
|
+
return this.translateAdapter?.translate(key) ?? key;
|
|
1270
|
+
}
|
|
1271
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppCompanyBranchSelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1272
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppCompanyBranchSelector, isStandalone: true, selector: "app-company-branch-selector", host: { classAttribute: "relative" }, ngImport: i0, template: `
|
|
1086
1273
|
<button
|
|
1087
1274
|
type="button"
|
|
1088
1275
|
class="layout-topbar-action"
|
|
1089
1276
|
[class.layout-topbar-action-highlight]="isActive()"
|
|
1090
|
-
|
|
1277
|
+
fStyleClass="@next"
|
|
1091
1278
|
enterFromClass="hidden"
|
|
1092
1279
|
enterActiveClass="animate-scalein"
|
|
1093
1280
|
leaveToClass="hidden"
|
|
@@ -1095,31 +1282,35 @@ class AppCompanyBranchSelector {
|
|
|
1095
1282
|
[hideOnOutsideClick]="true"
|
|
1096
1283
|
(click)="onPanelToggle()"
|
|
1097
1284
|
>
|
|
1098
|
-
<
|
|
1285
|
+
<ui-icon name="building"></ui-icon>
|
|
1099
1286
|
<span>{{ currentCompanyName() }}</span>
|
|
1100
1287
|
@if (currentBranchName()) {
|
|
1101
|
-
<span class="text-xs opacity-70"
|
|
1288
|
+
<span class="text-xs opacity-70 ml-1">| {{ currentBranchName() }}</span>
|
|
1102
1289
|
}
|
|
1103
1290
|
</button>
|
|
1104
1291
|
<div
|
|
1105
|
-
class="hidden absolute top-[3.25rem]
|
|
1106
|
-
style="background-color: var(--surface-overlay)"
|
|
1292
|
+
class="hidden absolute top-[3.25rem] z-50 p-4 border border-surface rounded-border origin-top shadow-xl w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[300px] max-w-[360px]"
|
|
1293
|
+
style="background-color: var(--surface-overlay); inset-inline-end: 0;"
|
|
1107
1294
|
>
|
|
1108
1295
|
<div class="flex flex-col gap-4">
|
|
1109
|
-
<span class="text-sm text-muted-color font-semibold"
|
|
1110
|
-
|
|
1111
|
-
>
|
|
1296
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1297
|
+
'layout.company.branch.selector.title' | translate
|
|
1298
|
+
}}</span>
|
|
1112
1299
|
|
|
1113
1300
|
<!-- Company Selector -->
|
|
1114
1301
|
<div class="flex flex-col gap-2">
|
|
1115
|
-
<label class="text-sm text-muted-color font-semibold">
|
|
1116
|
-
|
|
1302
|
+
<label class="text-sm text-muted-color font-semibold">{{
|
|
1303
|
+
'shared.company' | translate
|
|
1304
|
+
}}</label>
|
|
1305
|
+
<f-select
|
|
1117
1306
|
[options]="companies()"
|
|
1118
1307
|
[ngModel]="selectedCompanyId()"
|
|
1119
1308
|
(ngModelChange)="onCompanyChange($event)"
|
|
1120
1309
|
optionLabel="name"
|
|
1121
1310
|
optionValue="id"
|
|
1122
|
-
placeholder="
|
|
1311
|
+
[placeholder]="
|
|
1312
|
+
'layout.company.branch.selector.select.company' | translate
|
|
1313
|
+
"
|
|
1123
1314
|
class="w-full"
|
|
1124
1315
|
[loading]="isLoadingCompanies()"
|
|
1125
1316
|
/>
|
|
@@ -1128,14 +1319,18 @@ class AppCompanyBranchSelector {
|
|
|
1128
1319
|
<!-- Branch Selector -->
|
|
1129
1320
|
@if (selectedCompanyId() && branches().length > 0) {
|
|
1130
1321
|
<div class="flex flex-col gap-2">
|
|
1131
|
-
<label class="text-sm text-muted-color font-semibold">
|
|
1132
|
-
|
|
1322
|
+
<label class="text-sm text-muted-color font-semibold">{{
|
|
1323
|
+
'layout.company.branch.selector.branch' | translate
|
|
1324
|
+
}}</label>
|
|
1325
|
+
<f-select
|
|
1133
1326
|
[options]="branches()"
|
|
1134
1327
|
[ngModel]="selectedBranchId()"
|
|
1135
1328
|
(ngModelChange)="onBranchChange($event)"
|
|
1136
1329
|
optionLabel="name"
|
|
1137
1330
|
optionValue="id"
|
|
1138
|
-
placeholder="
|
|
1331
|
+
[placeholder]="
|
|
1332
|
+
'layout.company.branch.selector.select.branch' | translate
|
|
1333
|
+
"
|
|
1139
1334
|
class="w-full"
|
|
1140
1335
|
[loading]="isLoadingBranches()"
|
|
1141
1336
|
/>
|
|
@@ -1144,32 +1339,37 @@ class AppCompanyBranchSelector {
|
|
|
1144
1339
|
|
|
1145
1340
|
<!-- Actions -->
|
|
1146
1341
|
<div class="flex gap-2 pt-2">
|
|
1147
|
-
<
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
(
|
|
1154
|
-
|
|
1342
|
+
<button
|
|
1343
|
+
fButton
|
|
1344
|
+
class="flex-1"
|
|
1345
|
+
[disabled]="!canSwitch() || isSwitching()"
|
|
1346
|
+
(click)="onSwitch()"
|
|
1347
|
+
>
|
|
1348
|
+
@if (isSwitching()) {
|
|
1349
|
+
<ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
|
|
1350
|
+
} @else {
|
|
1351
|
+
<ui-icon pButtonIcon name="sync"></ui-icon>
|
|
1352
|
+
}
|
|
1353
|
+
<span pButtonLabel>{{ 'layout.company.branch.selector.switch.button' | translate }}</span>
|
|
1354
|
+
</button>
|
|
1155
1355
|
</div>
|
|
1156
1356
|
</div>
|
|
1157
1357
|
</div>
|
|
1158
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type:
|
|
1358
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i1.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "directive", type: i1.StyleClassDirective, selector: "[fStyleClass]", inputs: ["fStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1159
1359
|
}
|
|
1160
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1360
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppCompanyBranchSelector, decorators: [{
|
|
1161
1361
|
type: Component,
|
|
1162
1362
|
args: [{
|
|
1163
1363
|
selector: 'app-company-branch-selector',
|
|
1164
|
-
|
|
1165
|
-
|
|
1364
|
+
imports: [UiModule, AngularModule,
|
|
1365
|
+
TranslatePipe,],
|
|
1166
1366
|
host: { class: 'relative' },
|
|
1167
1367
|
template: `
|
|
1168
1368
|
<button
|
|
1169
1369
|
type="button"
|
|
1170
1370
|
class="layout-topbar-action"
|
|
1171
1371
|
[class.layout-topbar-action-highlight]="isActive()"
|
|
1172
|
-
|
|
1372
|
+
fStyleClass="@next"
|
|
1173
1373
|
enterFromClass="hidden"
|
|
1174
1374
|
enterActiveClass="animate-scalein"
|
|
1175
1375
|
leaveToClass="hidden"
|
|
@@ -1177,31 +1377,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1177
1377
|
[hideOnOutsideClick]="true"
|
|
1178
1378
|
(click)="onPanelToggle()"
|
|
1179
1379
|
>
|
|
1180
|
-
<
|
|
1380
|
+
<ui-icon name="building"></ui-icon>
|
|
1181
1381
|
<span>{{ currentCompanyName() }}</span>
|
|
1182
1382
|
@if (currentBranchName()) {
|
|
1183
|
-
<span class="text-xs opacity-70"
|
|
1383
|
+
<span class="text-xs opacity-70 ml-1">| {{ currentBranchName() }}</span>
|
|
1184
1384
|
}
|
|
1185
1385
|
</button>
|
|
1186
1386
|
<div
|
|
1187
|
-
class="hidden absolute top-[3.25rem]
|
|
1188
|
-
style="background-color: var(--surface-overlay)"
|
|
1387
|
+
class="hidden absolute top-[3.25rem] z-50 p-4 border border-surface rounded-border origin-top shadow-xl w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[300px] max-w-[360px]"
|
|
1388
|
+
style="background-color: var(--surface-overlay); inset-inline-end: 0;"
|
|
1189
1389
|
>
|
|
1190
1390
|
<div class="flex flex-col gap-4">
|
|
1191
|
-
<span class="text-sm text-muted-color font-semibold"
|
|
1192
|
-
|
|
1193
|
-
>
|
|
1391
|
+
<span class="text-sm text-muted-color font-semibold">{{
|
|
1392
|
+
'layout.company.branch.selector.title' | translate
|
|
1393
|
+
}}</span>
|
|
1194
1394
|
|
|
1195
1395
|
<!-- Company Selector -->
|
|
1196
1396
|
<div class="flex flex-col gap-2">
|
|
1197
|
-
<label class="text-sm text-muted-color font-semibold">
|
|
1198
|
-
|
|
1397
|
+
<label class="text-sm text-muted-color font-semibold">{{
|
|
1398
|
+
'shared.company' | translate
|
|
1399
|
+
}}</label>
|
|
1400
|
+
<f-select
|
|
1199
1401
|
[options]="companies()"
|
|
1200
1402
|
[ngModel]="selectedCompanyId()"
|
|
1201
1403
|
(ngModelChange)="onCompanyChange($event)"
|
|
1202
1404
|
optionLabel="name"
|
|
1203
1405
|
optionValue="id"
|
|
1204
|
-
placeholder="
|
|
1406
|
+
[placeholder]="
|
|
1407
|
+
'layout.company.branch.selector.select.company' | translate
|
|
1408
|
+
"
|
|
1205
1409
|
class="w-full"
|
|
1206
1410
|
[loading]="isLoadingCompanies()"
|
|
1207
1411
|
/>
|
|
@@ -1210,14 +1414,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1210
1414
|
<!-- Branch Selector -->
|
|
1211
1415
|
@if (selectedCompanyId() && branches().length > 0) {
|
|
1212
1416
|
<div class="flex flex-col gap-2">
|
|
1213
|
-
<label class="text-sm text-muted-color font-semibold">
|
|
1214
|
-
|
|
1417
|
+
<label class="text-sm text-muted-color font-semibold">{{
|
|
1418
|
+
'layout.company.branch.selector.branch' | translate
|
|
1419
|
+
}}</label>
|
|
1420
|
+
<f-select
|
|
1215
1421
|
[options]="branches()"
|
|
1216
1422
|
[ngModel]="selectedBranchId()"
|
|
1217
1423
|
(ngModelChange)="onBranchChange($event)"
|
|
1218
1424
|
optionLabel="name"
|
|
1219
1425
|
optionValue="id"
|
|
1220
|
-
placeholder="
|
|
1426
|
+
[placeholder]="
|
|
1427
|
+
'layout.company.branch.selector.select.branch' | translate
|
|
1428
|
+
"
|
|
1221
1429
|
class="w-full"
|
|
1222
1430
|
[loading]="isLoadingBranches()"
|
|
1223
1431
|
/>
|
|
@@ -1226,14 +1434,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1226
1434
|
|
|
1227
1435
|
<!-- Actions -->
|
|
1228
1436
|
<div class="flex gap-2 pt-2">
|
|
1229
|
-
<
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
(
|
|
1236
|
-
|
|
1437
|
+
<button
|
|
1438
|
+
fButton
|
|
1439
|
+
class="flex-1"
|
|
1440
|
+
[disabled]="!canSwitch() || isSwitching()"
|
|
1441
|
+
(click)="onSwitch()"
|
|
1442
|
+
>
|
|
1443
|
+
@if (isSwitching()) {
|
|
1444
|
+
<ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
|
|
1445
|
+
} @else {
|
|
1446
|
+
<ui-icon pButtonIcon name="sync"></ui-icon>
|
|
1447
|
+
}
|
|
1448
|
+
<span pButtonLabel>{{ 'layout.company.branch.selector.switch.button' | translate }}</span>
|
|
1449
|
+
</button>
|
|
1237
1450
|
</div>
|
|
1238
1451
|
</div>
|
|
1239
1452
|
</div>
|
|
@@ -1242,15 +1455,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1242
1455
|
}], ctorParameters: () => [] });
|
|
1243
1456
|
|
|
1244
1457
|
class AppLauncher {
|
|
1458
|
+
layoutService = inject(LayoutService);
|
|
1245
1459
|
destroyRef = inject(DestroyRef);
|
|
1246
1460
|
document = inject(DOCUMENT$1);
|
|
1247
1461
|
elementRef = inject(ElementRef);
|
|
1248
|
-
|
|
1249
|
-
|
|
1462
|
+
_isActive = signal(false, /* @ts-ignore */
|
|
1463
|
+
...(ngDevMode ? [{ debugName: "_isActive" }] : /* istanbul ignore next */ []));
|
|
1250
1464
|
isActive = this._isActive.asReadonly();
|
|
1251
1465
|
constructor() {
|
|
1252
1466
|
fromEvent(this.document, 'click')
|
|
1253
|
-
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.
|
|
1467
|
+
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.isActive()))
|
|
1254
1468
|
.subscribe((event) => {
|
|
1255
1469
|
const target = event.target;
|
|
1256
1470
|
if (!this.elementRef.nativeElement.contains(target)) {
|
|
@@ -1258,114 +1472,129 @@ class AppLauncher {
|
|
|
1258
1472
|
}
|
|
1259
1473
|
});
|
|
1260
1474
|
}
|
|
1261
|
-
|
|
1475
|
+
toggleActive() {
|
|
1262
1476
|
this._isActive.update((v) => !v);
|
|
1263
1477
|
}
|
|
1264
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1265
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
1478
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppLauncher, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1479
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppLauncher, isStandalone: true, selector: "app-launcher", host: { classAttribute: "relative" }, ngImport: i0, template: `
|
|
1266
1480
|
<button
|
|
1267
1481
|
type="button"
|
|
1268
1482
|
class="layout-topbar-action"
|
|
1269
1483
|
[class.layout-topbar-action-highlight]="isActive()"
|
|
1270
|
-
|
|
1484
|
+
fStyleClass="@next"
|
|
1271
1485
|
enterFromClass="hidden"
|
|
1272
1486
|
enterActiveClass="animate-scalein"
|
|
1273
1487
|
leaveToClass="hidden"
|
|
1274
1488
|
leaveActiveClass="animate-fadeout"
|
|
1275
1489
|
[hideOnOutsideClick]="true"
|
|
1276
|
-
(click)="
|
|
1490
|
+
(click)="toggleActive()"
|
|
1277
1491
|
>
|
|
1278
|
-
<
|
|
1279
|
-
<span>
|
|
1492
|
+
<ui-icon name="th-large"></ui-icon>
|
|
1493
|
+
<span>{{ 'layout.launcher.apps' | translate }}</span>
|
|
1280
1494
|
</button>
|
|
1281
1495
|
<div
|
|
1282
|
-
class="hidden absolute top-[3.25rem]
|
|
1283
|
-
style="
|
|
1496
|
+
class="hidden absolute top-[3.25rem] z-50 border border-surface rounded-border origin-top surface-overlay"
|
|
1497
|
+
style="inset-inline-end: 0; min-width: 16rem; max-width: 22rem; width: calc(100vw - 2rem); box-shadow: var(--f-shadow-xl);"
|
|
1284
1498
|
>
|
|
1285
|
-
<div class="
|
|
1286
|
-
<span class="text-
|
|
1287
|
-
|
|
1499
|
+
<div class="px-4 py-3 border-b border-surface">
|
|
1500
|
+
<span class="text-xs font-semibold tracking-wide text-muted-color">{{
|
|
1501
|
+
'layout.launcher.applications' | translate
|
|
1502
|
+
}}</span>
|
|
1503
|
+
</div>
|
|
1504
|
+
@if (layoutService.apps().length === 0) {
|
|
1505
|
+
<div class="px-4 py-6 text-center text-muted-color text-sm">
|
|
1506
|
+
{{ 'layout.launcher.no.apps' | translate }}
|
|
1507
|
+
</div>
|
|
1508
|
+
} @else {
|
|
1509
|
+
<div class="p-3 grid grid-cols-3 gap-1">
|
|
1288
1510
|
@for (app of layoutService.apps(); track app.id) {
|
|
1289
1511
|
<a
|
|
1290
|
-
class="group flex flex-col items-center
|
|
1512
|
+
class="group flex flex-col items-center gap-2 p-3 rounded-border cursor-pointer no-underline transition-colors duration-150 hover:bg-emphasis"
|
|
1291
1513
|
[href]="app.url"
|
|
1292
1514
|
target="_blank"
|
|
1293
1515
|
rel="noopener noreferrer"
|
|
1294
1516
|
>
|
|
1295
1517
|
<div
|
|
1296
|
-
class="flex items-center justify-center w-
|
|
1518
|
+
class="flex items-center justify-center w-9 h-9 rounded-border bg-primary/10 group-hover:bg-primary transition-colors duration-150"
|
|
1297
1519
|
>
|
|
1298
|
-
<
|
|
1299
|
-
[
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
/>
|
|
1520
|
+
<ui-icon
|
|
1521
|
+
[name]="app.icon"
|
|
1522
|
+
class="text-base text-primary group-hover:text-primary-contrast transition-colors duration-150"
|
|
1523
|
+
></ui-icon>
|
|
1303
1524
|
</div>
|
|
1304
1525
|
<span
|
|
1305
|
-
class="text-xs
|
|
1306
|
-
>{{ app.name }}</span
|
|
1526
|
+
class="text-xs text-center text-muted-color group-hover:text-color transition-colors duration-150 truncate w-full text-center leading-tight"
|
|
1307
1527
|
>
|
|
1528
|
+
{{ app.nameKey ? (app.nameKey | translate) : app.name }}
|
|
1529
|
+
</span>
|
|
1308
1530
|
</a>
|
|
1309
1531
|
}
|
|
1310
1532
|
</div>
|
|
1311
|
-
|
|
1533
|
+
}
|
|
1312
1534
|
</div>
|
|
1313
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type:
|
|
1535
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.StyleClassDirective, selector: "[fStyleClass]", inputs: ["fStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1314
1536
|
}
|
|
1315
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1537
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppLauncher, decorators: [{
|
|
1316
1538
|
type: Component,
|
|
1317
1539
|
args: [{
|
|
1318
1540
|
selector: 'app-launcher',
|
|
1319
|
-
|
|
1320
|
-
imports: [StyleClassModule, IconComponent],
|
|
1541
|
+
imports: [UiModule, TranslatePipe],
|
|
1321
1542
|
host: { class: 'relative' },
|
|
1322
1543
|
template: `
|
|
1323
1544
|
<button
|
|
1324
1545
|
type="button"
|
|
1325
1546
|
class="layout-topbar-action"
|
|
1326
1547
|
[class.layout-topbar-action-highlight]="isActive()"
|
|
1327
|
-
|
|
1548
|
+
fStyleClass="@next"
|
|
1328
1549
|
enterFromClass="hidden"
|
|
1329
1550
|
enterActiveClass="animate-scalein"
|
|
1330
1551
|
leaveToClass="hidden"
|
|
1331
1552
|
leaveActiveClass="animate-fadeout"
|
|
1332
1553
|
[hideOnOutsideClick]="true"
|
|
1333
|
-
(click)="
|
|
1554
|
+
(click)="toggleActive()"
|
|
1334
1555
|
>
|
|
1335
|
-
<
|
|
1336
|
-
<span>
|
|
1556
|
+
<ui-icon name="th-large"></ui-icon>
|
|
1557
|
+
<span>{{ 'layout.launcher.apps' | translate }}</span>
|
|
1337
1558
|
</button>
|
|
1338
1559
|
<div
|
|
1339
|
-
class="hidden absolute top-[3.25rem]
|
|
1340
|
-
style="
|
|
1560
|
+
class="hidden absolute top-[3.25rem] z-50 border border-surface rounded-border origin-top surface-overlay"
|
|
1561
|
+
style="inset-inline-end: 0; min-width: 16rem; max-width: 22rem; width: calc(100vw - 2rem); box-shadow: var(--f-shadow-xl);"
|
|
1341
1562
|
>
|
|
1342
|
-
<div class="
|
|
1343
|
-
<span class="text-
|
|
1344
|
-
|
|
1563
|
+
<div class="px-4 py-3 border-b border-surface">
|
|
1564
|
+
<span class="text-xs font-semibold tracking-wide text-muted-color">{{
|
|
1565
|
+
'layout.launcher.applications' | translate
|
|
1566
|
+
}}</span>
|
|
1567
|
+
</div>
|
|
1568
|
+
@if (layoutService.apps().length === 0) {
|
|
1569
|
+
<div class="px-4 py-6 text-center text-muted-color text-sm">
|
|
1570
|
+
{{ 'layout.launcher.no.apps' | translate }}
|
|
1571
|
+
</div>
|
|
1572
|
+
} @else {
|
|
1573
|
+
<div class="p-3 grid grid-cols-3 gap-1">
|
|
1345
1574
|
@for (app of layoutService.apps(); track app.id) {
|
|
1346
1575
|
<a
|
|
1347
|
-
class="group flex flex-col items-center
|
|
1576
|
+
class="group flex flex-col items-center gap-2 p-3 rounded-border cursor-pointer no-underline transition-colors duration-150 hover:bg-emphasis"
|
|
1348
1577
|
[href]="app.url"
|
|
1349
1578
|
target="_blank"
|
|
1350
1579
|
rel="noopener noreferrer"
|
|
1351
1580
|
>
|
|
1352
1581
|
<div
|
|
1353
|
-
class="flex items-center justify-center w-
|
|
1582
|
+
class="flex items-center justify-center w-9 h-9 rounded-border bg-primary/10 group-hover:bg-primary transition-colors duration-150"
|
|
1354
1583
|
>
|
|
1355
|
-
<
|
|
1356
|
-
[
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
/>
|
|
1584
|
+
<ui-icon
|
|
1585
|
+
[name]="app.icon"
|
|
1586
|
+
class="text-base text-primary group-hover:text-primary-contrast transition-colors duration-150"
|
|
1587
|
+
></ui-icon>
|
|
1360
1588
|
</div>
|
|
1361
1589
|
<span
|
|
1362
|
-
class="text-xs
|
|
1363
|
-
>{{ app.name }}</span
|
|
1590
|
+
class="text-xs text-center text-muted-color group-hover:text-color transition-colors duration-150 truncate w-full text-center leading-tight"
|
|
1364
1591
|
>
|
|
1592
|
+
{{ app.nameKey ? (app.nameKey | translate) : app.name }}
|
|
1593
|
+
</span>
|
|
1365
1594
|
</a>
|
|
1366
1595
|
}
|
|
1367
1596
|
</div>
|
|
1368
|
-
|
|
1597
|
+
}
|
|
1369
1598
|
</div>
|
|
1370
1599
|
`,
|
|
1371
1600
|
}]
|
|
@@ -1377,9 +1606,14 @@ class AppProfile {
|
|
|
1377
1606
|
authApi = inject(LAYOUT_AUTH_API, { optional: true });
|
|
1378
1607
|
layoutService = inject(LayoutService);
|
|
1379
1608
|
messageService = inject(MessageService);
|
|
1609
|
+
translateAdapter = inject(TranslateService, {
|
|
1610
|
+
optional: true,
|
|
1611
|
+
});
|
|
1612
|
+
appConfig = inject(APP_CONFIG, { optional: true });
|
|
1380
1613
|
document = inject(DOCUMENT$1);
|
|
1381
1614
|
elementRef = inject(ElementRef);
|
|
1382
|
-
_isActive = signal(false,
|
|
1615
|
+
_isActive = signal(false, /* @ts-ignore */
|
|
1616
|
+
...(ngDevMode ? [{ debugName: "_isActive" }] : /* istanbul ignore next */ []));
|
|
1383
1617
|
isActive = this._isActive.asReadonly();
|
|
1384
1618
|
constructor() {
|
|
1385
1619
|
fromEvent(this.document, 'click')
|
|
@@ -1394,9 +1628,15 @@ class AppProfile {
|
|
|
1394
1628
|
togglePanel() {
|
|
1395
1629
|
this._isActive.update((v) => !v);
|
|
1396
1630
|
}
|
|
1397
|
-
userName = computed(() => this.authState?.loginUserData()?.name ??
|
|
1398
|
-
|
|
1399
|
-
|
|
1631
|
+
userName = computed(() => this.authState?.loginUserData()?.name ??
|
|
1632
|
+
this.translate('layout.profile.guest'), /* @ts-ignore */
|
|
1633
|
+
...(ngDevMode ? [{ debugName: "userName" }] : /* istanbul ignore next */ []));
|
|
1634
|
+
userEmail = computed(() => this.authState?.loginUserData()?.email ?? '', /* @ts-ignore */
|
|
1635
|
+
...(ngDevMode ? [{ debugName: "userEmail" }] : /* istanbul ignore next */ []));
|
|
1636
|
+
profilePicture = computed(() => this.layoutService.userProfilePictureUrl() ?? '', /* @ts-ignore */
|
|
1637
|
+
...(ngDevMode ? [{ debugName: "profilePicture" }] : /* istanbul ignore next */ []));
|
|
1638
|
+
signUpEnabled = computed(() => this.appConfig ? isSignUpEnabled(this.appConfig) : false, /* @ts-ignore */
|
|
1639
|
+
...(ngDevMode ? [{ debugName: "signUpEnabled" }] : /* istanbul ignore next */ []));
|
|
1400
1640
|
logout() {
|
|
1401
1641
|
if (!this.authApi)
|
|
1402
1642
|
return;
|
|
@@ -1405,10 +1645,13 @@ class AppProfile {
|
|
|
1405
1645
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1406
1646
|
.subscribe({
|
|
1407
1647
|
next: (result) => {
|
|
1648
|
+
const r = result;
|
|
1408
1649
|
this.messageService.add({
|
|
1409
1650
|
severity: 'success',
|
|
1410
|
-
summary: '
|
|
1411
|
-
detail:
|
|
1651
|
+
summary: this.translate('shared.success'),
|
|
1652
|
+
detail: r?.messageKey
|
|
1653
|
+
? this.translate(r.messageKey, r.messageVariables)
|
|
1654
|
+
: (r?.message ?? ''),
|
|
1412
1655
|
});
|
|
1413
1656
|
this.authApi?.navigateLogin(false);
|
|
1414
1657
|
},
|
|
@@ -1425,25 +1668,28 @@ class AppProfile {
|
|
|
1425
1668
|
.then(() => {
|
|
1426
1669
|
this.messageService.add({
|
|
1427
1670
|
severity: 'success',
|
|
1428
|
-
summary: '
|
|
1429
|
-
detail: '
|
|
1671
|
+
summary: this.translate('layout.profile.link.copied'),
|
|
1672
|
+
detail: this.translate('layout.profile.sign.up.link.copied'),
|
|
1430
1673
|
});
|
|
1431
1674
|
})
|
|
1432
1675
|
.catch(() => {
|
|
1433
1676
|
this.messageService.add({
|
|
1434
1677
|
severity: 'error',
|
|
1435
|
-
summary: '
|
|
1436
|
-
detail: '
|
|
1678
|
+
summary: this.translate('shared.error'),
|
|
1679
|
+
detail: this.translate('layout.profile.failed.copy.sign.up.link'),
|
|
1437
1680
|
});
|
|
1438
1681
|
});
|
|
1439
1682
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1683
|
+
translate(key, vars) {
|
|
1684
|
+
return this.translateAdapter?.translate(key, vars) ?? key;
|
|
1685
|
+
}
|
|
1686
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppProfile, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1687
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppProfile, isStandalone: true, selector: "app-profile", host: { classAttribute: "relative" }, ngImport: i0, template: `
|
|
1442
1688
|
<button
|
|
1443
1689
|
type="button"
|
|
1444
1690
|
class="layout-topbar-action"
|
|
1445
1691
|
[class.layout-topbar-action-highlight]="isActive()"
|
|
1446
|
-
|
|
1692
|
+
fStyleClass="@next"
|
|
1447
1693
|
enterFromClass="hidden"
|
|
1448
1694
|
enterActiveClass="animate-scalein"
|
|
1449
1695
|
leaveToClass="hidden"
|
|
@@ -1451,12 +1697,12 @@ class AppProfile {
|
|
|
1451
1697
|
[hideOnOutsideClick]="true"
|
|
1452
1698
|
(click)="togglePanel()"
|
|
1453
1699
|
>
|
|
1454
|
-
<
|
|
1455
|
-
<span>
|
|
1700
|
+
<ui-icon name="user"></ui-icon>
|
|
1701
|
+
<span>{{ 'layout.profile.title' | translate }}</span>
|
|
1456
1702
|
</button>
|
|
1457
1703
|
<div
|
|
1458
|
-
class="hidden absolute top-[3.25rem]
|
|
1459
|
-
style="background-color: var(--surface-overlay)"
|
|
1704
|
+
class="hidden absolute top-[3.25rem] z-50 p-4 border border-surface rounded-border origin-top shadow-xl w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]"
|
|
1705
|
+
style="background-color: var(--surface-overlay); inset-inline-end: 0;"
|
|
1460
1706
|
>
|
|
1461
1707
|
<div class="flex flex-col gap-3">
|
|
1462
1708
|
<!-- User Info -->
|
|
@@ -1468,18 +1714,22 @@ class AppProfile {
|
|
|
1468
1714
|
<img
|
|
1469
1715
|
[src]="profilePicture()"
|
|
1470
1716
|
class="w-12 h-12 rounded-border object-cover"
|
|
1471
|
-
alt="
|
|
1717
|
+
[alt]="'layout.profile.profile.picture.alt' | translate"
|
|
1472
1718
|
/>
|
|
1473
1719
|
} @else {
|
|
1474
1720
|
<div
|
|
1475
1721
|
class="w-12 h-12 rounded-border bg-primary flex items-center justify-center"
|
|
1476
1722
|
>
|
|
1477
|
-
<
|
|
1723
|
+
<ui-icon name="user" class="text-xl text-primary-contrast"></ui-icon>
|
|
1478
1724
|
</div>
|
|
1479
1725
|
}
|
|
1480
1726
|
<div class="flex flex-col min-w-0 flex-1">
|
|
1481
|
-
<span class="font-semibold text-color truncate">{{
|
|
1482
|
-
|
|
1727
|
+
<span class="font-semibold text-color truncate">{{
|
|
1728
|
+
userName()
|
|
1729
|
+
}}</span>
|
|
1730
|
+
<span class="text-sm text-muted-color truncate">{{
|
|
1731
|
+
userEmail()
|
|
1732
|
+
}}</span>
|
|
1483
1733
|
</div>
|
|
1484
1734
|
</a>
|
|
1485
1735
|
|
|
@@ -1488,38 +1738,39 @@ class AppProfile {
|
|
|
1488
1738
|
|
|
1489
1739
|
<!-- Menu Items -->
|
|
1490
1740
|
<div class="flex flex-col gap-1">
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1741
|
+
@if (signUpEnabled()) {
|
|
1742
|
+
<a
|
|
1743
|
+
class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline text-color hover:bg-emphasis transition-all duration-200"
|
|
1744
|
+
(click)="copySignUpLink()"
|
|
1745
|
+
>
|
|
1746
|
+
<ui-icon name="link" class="text-muted-color"></ui-icon>
|
|
1747
|
+
<span>{{ 'layout.profile.copy.sign.up.link' | translate }}</span>
|
|
1748
|
+
</a>
|
|
1749
|
+
}
|
|
1498
1750
|
<a
|
|
1499
1751
|
class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline text-color hover:bg-emphasis transition-all duration-200"
|
|
1500
1752
|
(click)="logout()"
|
|
1501
1753
|
>
|
|
1502
|
-
<
|
|
1503
|
-
<span>
|
|
1754
|
+
<ui-icon name="sign-out" class="text-muted-color"></ui-icon>
|
|
1755
|
+
<span>{{ 'layout.profile.logout' | translate }}</span>
|
|
1504
1756
|
</a>
|
|
1505
1757
|
</div>
|
|
1506
1758
|
</div>
|
|
1507
1759
|
</div>
|
|
1508
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type:
|
|
1760
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.StyleClassDirective, selector: "[fStyleClass]", inputs: ["fStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: i3.IsEmptyImageDirective, selector: "img", inputs: ["src"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1509
1761
|
}
|
|
1510
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1762
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppProfile, decorators: [{
|
|
1511
1763
|
type: Component,
|
|
1512
1764
|
args: [{
|
|
1513
1765
|
selector: 'app-profile',
|
|
1514
|
-
|
|
1515
|
-
imports: [AngularModule, StyleClassModule],
|
|
1766
|
+
imports: [UiModule, AngularModule, TranslatePipe],
|
|
1516
1767
|
host: { class: 'relative' },
|
|
1517
1768
|
template: `
|
|
1518
1769
|
<button
|
|
1519
1770
|
type="button"
|
|
1520
1771
|
class="layout-topbar-action"
|
|
1521
1772
|
[class.layout-topbar-action-highlight]="isActive()"
|
|
1522
|
-
|
|
1773
|
+
fStyleClass="@next"
|
|
1523
1774
|
enterFromClass="hidden"
|
|
1524
1775
|
enterActiveClass="animate-scalein"
|
|
1525
1776
|
leaveToClass="hidden"
|
|
@@ -1527,12 +1778,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1527
1778
|
[hideOnOutsideClick]="true"
|
|
1528
1779
|
(click)="togglePanel()"
|
|
1529
1780
|
>
|
|
1530
|
-
<
|
|
1531
|
-
<span>
|
|
1781
|
+
<ui-icon name="user"></ui-icon>
|
|
1782
|
+
<span>{{ 'layout.profile.title' | translate }}</span>
|
|
1532
1783
|
</button>
|
|
1533
1784
|
<div
|
|
1534
|
-
class="hidden absolute top-[3.25rem]
|
|
1535
|
-
style="background-color: var(--surface-overlay)"
|
|
1785
|
+
class="hidden absolute top-[3.25rem] z-50 p-4 border border-surface rounded-border origin-top shadow-xl w-[calc(100vw-2rem)] sm:w-auto sm:min-w-[280px] max-w-[320px]"
|
|
1786
|
+
style="background-color: var(--surface-overlay); inset-inline-end: 0;"
|
|
1536
1787
|
>
|
|
1537
1788
|
<div class="flex flex-col gap-3">
|
|
1538
1789
|
<!-- User Info -->
|
|
@@ -1544,18 +1795,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1544
1795
|
<img
|
|
1545
1796
|
[src]="profilePicture()"
|
|
1546
1797
|
class="w-12 h-12 rounded-border object-cover"
|
|
1547
|
-
alt="
|
|
1798
|
+
[alt]="'layout.profile.profile.picture.alt' | translate"
|
|
1548
1799
|
/>
|
|
1549
1800
|
} @else {
|
|
1550
1801
|
<div
|
|
1551
1802
|
class="w-12 h-12 rounded-border bg-primary flex items-center justify-center"
|
|
1552
1803
|
>
|
|
1553
|
-
<
|
|
1804
|
+
<ui-icon name="user" class="text-xl text-primary-contrast"></ui-icon>
|
|
1554
1805
|
</div>
|
|
1555
1806
|
}
|
|
1556
1807
|
<div class="flex flex-col min-w-0 flex-1">
|
|
1557
|
-
<span class="font-semibold text-color truncate">{{
|
|
1558
|
-
|
|
1808
|
+
<span class="font-semibold text-color truncate">{{
|
|
1809
|
+
userName()
|
|
1810
|
+
}}</span>
|
|
1811
|
+
<span class="text-sm text-muted-color truncate">{{
|
|
1812
|
+
userEmail()
|
|
1813
|
+
}}</span>
|
|
1559
1814
|
</div>
|
|
1560
1815
|
</a>
|
|
1561
1816
|
|
|
@@ -1564,19 +1819,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1564
1819
|
|
|
1565
1820
|
<!-- Menu Items -->
|
|
1566
1821
|
<div class="flex flex-col gap-1">
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1822
|
+
@if (signUpEnabled()) {
|
|
1823
|
+
<a
|
|
1824
|
+
class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline text-color hover:bg-emphasis transition-all duration-200"
|
|
1825
|
+
(click)="copySignUpLink()"
|
|
1826
|
+
>
|
|
1827
|
+
<ui-icon name="link" class="text-muted-color"></ui-icon>
|
|
1828
|
+
<span>{{ 'layout.profile.copy.sign.up.link' | translate }}</span>
|
|
1829
|
+
</a>
|
|
1830
|
+
}
|
|
1574
1831
|
<a
|
|
1575
1832
|
class="flex items-center gap-3 p-3 rounded-border cursor-pointer no-underline text-color hover:bg-emphasis transition-all duration-200"
|
|
1576
1833
|
(click)="logout()"
|
|
1577
1834
|
>
|
|
1578
|
-
<
|
|
1579
|
-
<span>
|
|
1835
|
+
<ui-icon name="sign-out" class="text-muted-color"></ui-icon>
|
|
1836
|
+
<span>{{ 'layout.profile.logout' | translate }}</span>
|
|
1580
1837
|
</a>
|
|
1581
1838
|
</div>
|
|
1582
1839
|
</div>
|
|
@@ -1585,24 +1842,401 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1585
1842
|
}]
|
|
1586
1843
|
}], ctorParameters: () => [] });
|
|
1587
1844
|
|
|
1845
|
+
class AppSearchbar {
|
|
1846
|
+
layoutService = inject(LayoutService);
|
|
1847
|
+
router = inject(Router);
|
|
1848
|
+
searchAdapter = inject(LAYOUT_SEARCH_ADAPTER, {
|
|
1849
|
+
optional: true,
|
|
1850
|
+
});
|
|
1851
|
+
destroyRef = inject(DestroyRef);
|
|
1852
|
+
document = inject(DOCUMENT$1);
|
|
1853
|
+
searchTerm = signal('', /* @ts-ignore */
|
|
1854
|
+
...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
|
|
1855
|
+
suggestionsOpen = signal(false, /* @ts-ignore */
|
|
1856
|
+
...(ngDevMode ? [{ debugName: "suggestionsOpen" }] : /* istanbul ignore next */ []));
|
|
1857
|
+
suggestions = signal([], /* @ts-ignore */
|
|
1858
|
+
...(ngDevMode ? [{ debugName: "suggestions" }] : /* istanbul ignore next */ []));
|
|
1859
|
+
focusedIndex = signal(-1, /* @ts-ignore */
|
|
1860
|
+
...(ngDevMode ? [{ debugName: "focusedIndex" }] : /* istanbul ignore next */ []));
|
|
1861
|
+
isSearchExpanded = signal(false, /* @ts-ignore */
|
|
1862
|
+
...(ngDevMode ? [{ debugName: "isSearchExpanded" }] : /* istanbul ignore next */ []));
|
|
1863
|
+
isResponsive = signal(typeof window !== 'undefined' ? window.innerWidth < 768 : false, /* @ts-ignore */
|
|
1864
|
+
...(ngDevMode ? [{ debugName: "isResponsive" }] : /* istanbul ignore next */ []));
|
|
1865
|
+
searchWrapper = viewChild('searchWrapper', /* @ts-ignore */
|
|
1866
|
+
...(ngDevMode ? [{ debugName: "searchWrapper" }] : /* istanbul ignore next */ []));
|
|
1867
|
+
searchInput = viewChild('searchInput', /* @ts-ignore */
|
|
1868
|
+
...(ngDevMode ? [{ debugName: "searchInput" }] : /* istanbul ignore next */ []));
|
|
1869
|
+
searchTimeout;
|
|
1870
|
+
constructor() {
|
|
1871
|
+
// Track responsive breakpoint (mobile < 768px)
|
|
1872
|
+
if (typeof window !== 'undefined') {
|
|
1873
|
+
fromEvent(window, 'resize')
|
|
1874
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1875
|
+
.subscribe(() => {
|
|
1876
|
+
this.isResponsive.set(window.innerWidth < 768);
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
// Close dropdown/search on outside click
|
|
1880
|
+
fromEvent(this.document, 'click')
|
|
1881
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1882
|
+
.subscribe((event) => {
|
|
1883
|
+
const wrapper = this.searchWrapper()?.nativeElement;
|
|
1884
|
+
const target = event.target;
|
|
1885
|
+
if (wrapper && !wrapper.contains(target)) {
|
|
1886
|
+
if (this.suggestionsOpen()) {
|
|
1887
|
+
this.closeSuggestions();
|
|
1888
|
+
}
|
|
1889
|
+
if (this.isSearchExpanded() &&
|
|
1890
|
+
(this.layoutService.isTopbar() || this.isResponsive())) {
|
|
1891
|
+
this.closeSearch();
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
// Debounced search for suggestions
|
|
1896
|
+
effect(() => {
|
|
1897
|
+
const query = this.searchTerm();
|
|
1898
|
+
clearTimeout(this.searchTimeout);
|
|
1899
|
+
if (!this.searchAdapter || !query.trim()) {
|
|
1900
|
+
this.suggestions.set([]);
|
|
1901
|
+
this.focusedIndex.set(-1);
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
this.searchTimeout = setTimeout(() => {
|
|
1905
|
+
this.searchAdapter.getSuggestions(query)
|
|
1906
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1907
|
+
.subscribe((results) => {
|
|
1908
|
+
this.suggestions.set(results);
|
|
1909
|
+
this.focusedIndex.set(-1);
|
|
1910
|
+
this.suggestionsOpen.set(results.length > 0);
|
|
1911
|
+
});
|
|
1912
|
+
}, 300);
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
onFocus() {
|
|
1916
|
+
if (this.suggestions().length > 0) {
|
|
1917
|
+
this.suggestionsOpen.set(true);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
handleKeydown(event) {
|
|
1921
|
+
const items = this.suggestions();
|
|
1922
|
+
const current = this.focusedIndex();
|
|
1923
|
+
switch (event.key) {
|
|
1924
|
+
case 'ArrowDown':
|
|
1925
|
+
event.preventDefault();
|
|
1926
|
+
this.focusedIndex.set(current < items.length - 1 ? current + 1 : 0);
|
|
1927
|
+
this.suggestionsOpen.set(items.length > 0);
|
|
1928
|
+
break;
|
|
1929
|
+
case 'ArrowUp':
|
|
1930
|
+
event.preventDefault();
|
|
1931
|
+
this.focusedIndex.set(current > 0 ? current - 1 : items.length - 1);
|
|
1932
|
+
break;
|
|
1933
|
+
case 'Escape':
|
|
1934
|
+
event.preventDefault();
|
|
1935
|
+
this.closeSuggestions();
|
|
1936
|
+
break;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
handleEnter() {
|
|
1940
|
+
const current = this.focusedIndex();
|
|
1941
|
+
const items = this.suggestions();
|
|
1942
|
+
if (current >= 0 && current < items.length) {
|
|
1943
|
+
this.selectSuggestion(items[current]);
|
|
1944
|
+
}
|
|
1945
|
+
else if (items.length > 0) {
|
|
1946
|
+
this.selectSuggestion(items[0]);
|
|
1947
|
+
}
|
|
1948
|
+
else {
|
|
1949
|
+
this.onSearch();
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
selectSuggestion(item) {
|
|
1953
|
+
if (item.routerLink) {
|
|
1954
|
+
this.router.navigate(item.routerLink);
|
|
1955
|
+
}
|
|
1956
|
+
this.searchTerm.set('');
|
|
1957
|
+
this.closeSuggestions();
|
|
1958
|
+
if (this.layoutService.isTopbar() || this.isResponsive()) {
|
|
1959
|
+
this.closeSearch();
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
closeSuggestions() {
|
|
1963
|
+
this.suggestionsOpen.set(false);
|
|
1964
|
+
this.focusedIndex.set(-1);
|
|
1965
|
+
}
|
|
1966
|
+
toggleSearch() {
|
|
1967
|
+
this.isSearchExpanded.set(!this.isSearchExpanded());
|
|
1968
|
+
if (this.isSearchExpanded()) {
|
|
1969
|
+
setTimeout(() => {
|
|
1970
|
+
this.searchInput()?.nativeElement?.focus();
|
|
1971
|
+
}, 100);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
closeSearch() {
|
|
1975
|
+
this.isSearchExpanded.set(false);
|
|
1976
|
+
this.closeSuggestions();
|
|
1977
|
+
}
|
|
1978
|
+
onSearch() {
|
|
1979
|
+
const query = this.searchTerm();
|
|
1980
|
+
if (this.searchAdapter?.onSearch && query.trim()) {
|
|
1981
|
+
this.searchAdapter.onSearch(query);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppSearchbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1985
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppSearchbar, isStandalone: true, selector: "app-searchbar", viewQueries: [{ propertyName: "searchWrapper", first: true, predicate: ["searchWrapper"], descendants: true, isSignal: true }, { propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1986
|
+
<div
|
|
1987
|
+
class="search-container h-full"
|
|
1988
|
+
#searchWrapper
|
|
1989
|
+
[class.topbar-mode]="layoutService.isTopbar() || isResponsive()"
|
|
1990
|
+
>
|
|
1991
|
+
@if (layoutService.isTopbar() || isResponsive()) {
|
|
1992
|
+
<!-- Topbar/Mobile Icon Mode -->
|
|
1993
|
+
<button
|
|
1994
|
+
type="button"
|
|
1995
|
+
class="search-toggle-btn layout-topbar-action"
|
|
1996
|
+
(click)="toggleSearch()"
|
|
1997
|
+
[attr.aria-label]="'layout.topbar.search' | translate"
|
|
1998
|
+
>
|
|
1999
|
+
<ui-icon name="search"></ui-icon>
|
|
2000
|
+
</button>
|
|
2001
|
+
|
|
2002
|
+
@if (isSearchExpanded()) {
|
|
2003
|
+
<div class="search-dropdown-panel">
|
|
2004
|
+
<f-iconfield class="search-iconfield">
|
|
2005
|
+
<f-inputicon class="search-icon"><ui-icon name="search" /></f-inputicon>
|
|
2006
|
+
<input
|
|
2007
|
+
type="text"
|
|
2008
|
+
fInputText
|
|
2009
|
+
class="search-input w-full"
|
|
2010
|
+
[placeholder]="'layout.topbar.search.placeholder' | translate"
|
|
2011
|
+
[(ngModel)]="searchTerm"
|
|
2012
|
+
(keyup.enter)="handleEnter()"
|
|
2013
|
+
(keydown)="handleKeydown($event)"
|
|
2014
|
+
(focus)="onFocus()"
|
|
2015
|
+
[attr.aria-label]="'layout.topbar.search' | translate"
|
|
2016
|
+
#searchInput
|
|
2017
|
+
autocomplete="off"
|
|
2018
|
+
/>
|
|
2019
|
+
</f-iconfield>
|
|
2020
|
+
|
|
2021
|
+
@if (suggestionsOpen() && suggestions().length > 0) {
|
|
2022
|
+
<div class="search-suggestions">
|
|
2023
|
+
<ul class="suggestions-list">
|
|
2024
|
+
@for (
|
|
2025
|
+
item of suggestions();
|
|
2026
|
+
track item.label;
|
|
2027
|
+
let idx = $index
|
|
2028
|
+
) {
|
|
2029
|
+
<li
|
|
2030
|
+
class="suggestion-item"
|
|
2031
|
+
[class.focused]="idx === focusedIndex()"
|
|
2032
|
+
(click)="selectSuggestion(item)"
|
|
2033
|
+
(mouseenter)="focusedIndex.set(idx)"
|
|
2034
|
+
role="option"
|
|
2035
|
+
[attr.aria-selected]="idx === focusedIndex()"
|
|
2036
|
+
>
|
|
2037
|
+
@if (item.icon) {
|
|
2038
|
+
<ui-icon [name]="item.icon" class="suggestion-icon"></ui-icon>
|
|
2039
|
+
}
|
|
2040
|
+
<span class="suggestion-label">{{ item.label }}</span>
|
|
2041
|
+
</li>
|
|
2042
|
+
}
|
|
2043
|
+
</ul>
|
|
2044
|
+
</div>
|
|
2045
|
+
}
|
|
2046
|
+
</div>
|
|
2047
|
+
}
|
|
2048
|
+
} @else {
|
|
2049
|
+
<!-- Desktop Full Input Mode -->
|
|
2050
|
+
<f-iconfield class="search-iconfield">
|
|
2051
|
+
<f-inputicon class="search-icon cursor-pointer" (click)="onSearch()"><ui-icon name="search" /></f-inputicon>
|
|
2052
|
+
<input
|
|
2053
|
+
type="text"
|
|
2054
|
+
fInputText
|
|
2055
|
+
class="search-input w-full"
|
|
2056
|
+
[placeholder]="'layout.topbar.search.placeholder' | translate"
|
|
2057
|
+
[(ngModel)]="searchTerm"
|
|
2058
|
+
(keyup.enter)="handleEnter()"
|
|
2059
|
+
(keydown)="handleKeydown($event)"
|
|
2060
|
+
(focus)="onFocus()"
|
|
2061
|
+
[attr.aria-label]="'layout.topbar.search' | translate"
|
|
2062
|
+
#searchInput
|
|
2063
|
+
/>
|
|
2064
|
+
</f-iconfield>
|
|
2065
|
+
|
|
2066
|
+
@if (suggestionsOpen() && suggestions().length > 0) {
|
|
2067
|
+
<div class="search-dropdown">
|
|
2068
|
+
<ul class="suggestions-list">
|
|
2069
|
+
@for (item of suggestions(); track item.label; let idx = $index) {
|
|
2070
|
+
<li
|
|
2071
|
+
class="suggestion-item"
|
|
2072
|
+
[class.focused]="idx === focusedIndex()"
|
|
2073
|
+
(click)="selectSuggestion(item)"
|
|
2074
|
+
(mouseenter)="focusedIndex.set(idx)"
|
|
2075
|
+
role="option"
|
|
2076
|
+
[attr.aria-selected]="idx === focusedIndex()"
|
|
2077
|
+
>
|
|
2078
|
+
@if (item.icon) {
|
|
2079
|
+
<ui-icon [name]="item.icon" class="suggestion-icon"></ui-icon>
|
|
2080
|
+
}
|
|
2081
|
+
<span class="suggestion-label">{{ item.label }}</span>
|
|
2082
|
+
</li>
|
|
2083
|
+
}
|
|
2084
|
+
</ul>
|
|
2085
|
+
</div>
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
</div>
|
|
2089
|
+
`, isInline: true, styles: [".search-container{display:flex;align-items:center;position:relative;width:100%;flex:0 1 auto}.search-container.topbar-mode{width:auto}.search-dropdown-panel{position:absolute;top:100%;right:0;margin-top:.5rem;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--f-content-border-radius);box-shadow:var(--f-shadow-xl);z-index:1100;padding:1rem .75rem .75rem;min-width:300px;max-width:450px;animation:slideDown .2s ease-out}.search-suggestions{margin-top:.5rem;border-top:1px solid var(--surface-border);padding-top:.5rem;background-color:inherit}.suggestions-list{list-style:none;margin:0;padding:0;max-height:360px;overflow-y:auto;background-color:inherit}.suggestion-item{padding:.75rem 1rem;cursor:pointer;display:flex;align-items:center;gap:.75rem;color:var(--text-color);border-bottom:1px solid var(--surface-border);transition:background-color .15s ease,color .15s ease}.suggestion-item:last-child{border-bottom:none}.suggestion-item:hover,.suggestion-item.focused{background-color:var(--surface-50);color:var(--primary-color)}.suggestion-icon{color:var(--text-color-secondary);font-size:1rem;flex-shrink:0;transition:color .15s ease}.suggestion-item:hover .suggestion-icon,.suggestion-item.focused .suggestion-icon{color:var(--primary-color)}.suggestion-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.search-dropdown{position:absolute;top:calc(100% + .5rem);left:0;right:0;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--f-content-border-radius);box-shadow:var(--f-shadow-xl);z-index:1000;overflow:hidden}.search-dropdown .suggestions-list{background-color:inherit}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@media(max-width:768px){.search-dropdown-panel{min-width:280px;max-width:400px}}@media(max-width:576px){.search-dropdown-panel{position:fixed;top:4rem;left:0;right:0;bottom:auto;width:100vw;min-width:100vw;max-width:100vw;margin-top:0;padding:1rem;border-radius:0;border-bottom:1px solid var(--surface-border);z-index:1200}.search-dropdown-panel .search-iconfield{margin-bottom:1rem}.search-dropdown{max-width:100%;left:0!important;right:0!important;transform:none;position:static;border-radius:var(--f-content-border-radius);margin-top:0}.suggestion-item{padding:.75rem}}\n"], dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.IconFieldComponent, selector: "f-iconfield", inputs: ["iconPosition", "styleClass"] }, { kind: "component", type: i1.InputIconComponent, selector: "f-inputicon", inputs: ["icon", "position", "styleClass", "style"] }, { kind: "directive", type: i1.InputTextDirective, selector: "[fInputText]", inputs: ["invalid", "touched", "variant", "size", "fluid"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2090
|
+
}
|
|
2091
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppSearchbar, decorators: [{
|
|
2092
|
+
type: Component,
|
|
2093
|
+
args: [{ selector: 'app-searchbar', imports: [UiModule, AngularModule, TranslatePipe], template: `
|
|
2094
|
+
<div
|
|
2095
|
+
class="search-container h-full"
|
|
2096
|
+
#searchWrapper
|
|
2097
|
+
[class.topbar-mode]="layoutService.isTopbar() || isResponsive()"
|
|
2098
|
+
>
|
|
2099
|
+
@if (layoutService.isTopbar() || isResponsive()) {
|
|
2100
|
+
<!-- Topbar/Mobile Icon Mode -->
|
|
2101
|
+
<button
|
|
2102
|
+
type="button"
|
|
2103
|
+
class="search-toggle-btn layout-topbar-action"
|
|
2104
|
+
(click)="toggleSearch()"
|
|
2105
|
+
[attr.aria-label]="'layout.topbar.search' | translate"
|
|
2106
|
+
>
|
|
2107
|
+
<ui-icon name="search"></ui-icon>
|
|
2108
|
+
</button>
|
|
2109
|
+
|
|
2110
|
+
@if (isSearchExpanded()) {
|
|
2111
|
+
<div class="search-dropdown-panel">
|
|
2112
|
+
<f-iconfield class="search-iconfield">
|
|
2113
|
+
<f-inputicon class="search-icon"><ui-icon name="search" /></f-inputicon>
|
|
2114
|
+
<input
|
|
2115
|
+
type="text"
|
|
2116
|
+
fInputText
|
|
2117
|
+
class="search-input w-full"
|
|
2118
|
+
[placeholder]="'layout.topbar.search.placeholder' | translate"
|
|
2119
|
+
[(ngModel)]="searchTerm"
|
|
2120
|
+
(keyup.enter)="handleEnter()"
|
|
2121
|
+
(keydown)="handleKeydown($event)"
|
|
2122
|
+
(focus)="onFocus()"
|
|
2123
|
+
[attr.aria-label]="'layout.topbar.search' | translate"
|
|
2124
|
+
#searchInput
|
|
2125
|
+
autocomplete="off"
|
|
2126
|
+
/>
|
|
2127
|
+
</f-iconfield>
|
|
2128
|
+
|
|
2129
|
+
@if (suggestionsOpen() && suggestions().length > 0) {
|
|
2130
|
+
<div class="search-suggestions">
|
|
2131
|
+
<ul class="suggestions-list">
|
|
2132
|
+
@for (
|
|
2133
|
+
item of suggestions();
|
|
2134
|
+
track item.label;
|
|
2135
|
+
let idx = $index
|
|
2136
|
+
) {
|
|
2137
|
+
<li
|
|
2138
|
+
class="suggestion-item"
|
|
2139
|
+
[class.focused]="idx === focusedIndex()"
|
|
2140
|
+
(click)="selectSuggestion(item)"
|
|
2141
|
+
(mouseenter)="focusedIndex.set(idx)"
|
|
2142
|
+
role="option"
|
|
2143
|
+
[attr.aria-selected]="idx === focusedIndex()"
|
|
2144
|
+
>
|
|
2145
|
+
@if (item.icon) {
|
|
2146
|
+
<ui-icon [name]="item.icon" class="suggestion-icon"></ui-icon>
|
|
2147
|
+
}
|
|
2148
|
+
<span class="suggestion-label">{{ item.label }}</span>
|
|
2149
|
+
</li>
|
|
2150
|
+
}
|
|
2151
|
+
</ul>
|
|
2152
|
+
</div>
|
|
2153
|
+
}
|
|
2154
|
+
</div>
|
|
2155
|
+
}
|
|
2156
|
+
} @else {
|
|
2157
|
+
<!-- Desktop Full Input Mode -->
|
|
2158
|
+
<f-iconfield class="search-iconfield">
|
|
2159
|
+
<f-inputicon class="search-icon cursor-pointer" (click)="onSearch()"><ui-icon name="search" /></f-inputicon>
|
|
2160
|
+
<input
|
|
2161
|
+
type="text"
|
|
2162
|
+
fInputText
|
|
2163
|
+
class="search-input w-full"
|
|
2164
|
+
[placeholder]="'layout.topbar.search.placeholder' | translate"
|
|
2165
|
+
[(ngModel)]="searchTerm"
|
|
2166
|
+
(keyup.enter)="handleEnter()"
|
|
2167
|
+
(keydown)="handleKeydown($event)"
|
|
2168
|
+
(focus)="onFocus()"
|
|
2169
|
+
[attr.aria-label]="'layout.topbar.search' | translate"
|
|
2170
|
+
#searchInput
|
|
2171
|
+
/>
|
|
2172
|
+
</f-iconfield>
|
|
2173
|
+
|
|
2174
|
+
@if (suggestionsOpen() && suggestions().length > 0) {
|
|
2175
|
+
<div class="search-dropdown">
|
|
2176
|
+
<ul class="suggestions-list">
|
|
2177
|
+
@for (item of suggestions(); track item.label; let idx = $index) {
|
|
2178
|
+
<li
|
|
2179
|
+
class="suggestion-item"
|
|
2180
|
+
[class.focused]="idx === focusedIndex()"
|
|
2181
|
+
(click)="selectSuggestion(item)"
|
|
2182
|
+
(mouseenter)="focusedIndex.set(idx)"
|
|
2183
|
+
role="option"
|
|
2184
|
+
[attr.aria-selected]="idx === focusedIndex()"
|
|
2185
|
+
>
|
|
2186
|
+
@if (item.icon) {
|
|
2187
|
+
<ui-icon [name]="item.icon" class="suggestion-icon"></ui-icon>
|
|
2188
|
+
}
|
|
2189
|
+
<span class="suggestion-label">{{ item.label }}</span>
|
|
2190
|
+
</li>
|
|
2191
|
+
}
|
|
2192
|
+
</ul>
|
|
2193
|
+
</div>
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
</div>
|
|
2197
|
+
`, styles: [".search-container{display:flex;align-items:center;position:relative;width:100%;flex:0 1 auto}.search-container.topbar-mode{width:auto}.search-dropdown-panel{position:absolute;top:100%;right:0;margin-top:.5rem;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--f-content-border-radius);box-shadow:var(--f-shadow-xl);z-index:1100;padding:1rem .75rem .75rem;min-width:300px;max-width:450px;animation:slideDown .2s ease-out}.search-suggestions{margin-top:.5rem;border-top:1px solid var(--surface-border);padding-top:.5rem;background-color:inherit}.suggestions-list{list-style:none;margin:0;padding:0;max-height:360px;overflow-y:auto;background-color:inherit}.suggestion-item{padding:.75rem 1rem;cursor:pointer;display:flex;align-items:center;gap:.75rem;color:var(--text-color);border-bottom:1px solid var(--surface-border);transition:background-color .15s ease,color .15s ease}.suggestion-item:last-child{border-bottom:none}.suggestion-item:hover,.suggestion-item.focused{background-color:var(--surface-50);color:var(--primary-color)}.suggestion-icon{color:var(--text-color-secondary);font-size:1rem;flex-shrink:0;transition:color .15s ease}.suggestion-item:hover .suggestion-icon,.suggestion-item.focused .suggestion-icon{color:var(--primary-color)}.suggestion-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.search-dropdown{position:absolute;top:calc(100% + .5rem);left:0;right:0;background-color:var(--surface-card);border:1px solid var(--surface-border);border-radius:var(--f-content-border-radius);box-shadow:var(--f-shadow-xl);z-index:1000;overflow:hidden}.search-dropdown .suggestions-list{background-color:inherit}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@media(max-width:768px){.search-dropdown-panel{min-width:280px;max-width:400px}}@media(max-width:576px){.search-dropdown-panel{position:fixed;top:4rem;left:0;right:0;bottom:auto;width:100vw;min-width:100vw;max-width:100vw;margin-top:0;padding:1rem;border-radius:0;border-bottom:1px solid var(--surface-border);z-index:1200}.search-dropdown-panel .search-iconfield{margin-bottom:1rem}.search-dropdown{max-width:100%;left:0!important;right:0!important;transform:none;position:static;border-radius:var(--f-content-border-radius);margin-top:0}.suggestion-item{padding:.75rem}}\n"] }]
|
|
2198
|
+
}], ctorParameters: () => [], propDecorators: { searchWrapper: [{ type: i0.ViewChild, args: ['searchWrapper', { isSignal: true }] }], searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
|
|
2199
|
+
|
|
1588
2200
|
class AppTopbar {
|
|
1589
2201
|
appConfig = inject(APP_CONFIG);
|
|
1590
2202
|
document = inject(DOCUMENT$1);
|
|
1591
2203
|
destroyRef = inject(DestroyRef);
|
|
1592
2204
|
layoutService = inject(LayoutService);
|
|
1593
|
-
|
|
1594
|
-
|
|
2205
|
+
notificationBellComponent = inject(LAYOUT_NOTIFICATION_BELL, {
|
|
2206
|
+
optional: true,
|
|
2207
|
+
});
|
|
2208
|
+
languageSelectorComponent = inject(LAYOUT_LANGUAGE_SELECTOR, {
|
|
2209
|
+
optional: true,
|
|
2210
|
+
});
|
|
2211
|
+
searchAdapter = inject(LAYOUT_SEARCH_ADAPTER, {
|
|
2212
|
+
optional: true,
|
|
2213
|
+
});
|
|
2214
|
+
hasSearchAdapter = computed(() => !!this.searchAdapter, /* @ts-ignore */
|
|
2215
|
+
...(ngDevMode ? [{ debugName: "hasSearchAdapter" }] : /* istanbul ignore next */ []));
|
|
2216
|
+
configContainer = viewChild('configContainer', /* @ts-ignore */
|
|
2217
|
+
...(ngDevMode ? [{ debugName: "configContainer" }] : /* istanbul ignore next */ []));
|
|
2218
|
+
navContainer = viewChild('navContainer', /* @ts-ignore */
|
|
2219
|
+
...(ngDevMode ? [{ debugName: "navContainer" }] : /* istanbul ignore next */ []));
|
|
2220
|
+
activePanel = signal(null, /* @ts-ignore */
|
|
2221
|
+
...(ngDevMode ? [{ debugName: "activePanel" }] : /* istanbul ignore next */ []));
|
|
1595
2222
|
constructor() {
|
|
1596
2223
|
fromEvent(this.document, 'click')
|
|
1597
2224
|
.pipe(takeUntilDestroyed(this.destroyRef), filter(() => this.activePanel() !== null))
|
|
1598
2225
|
.subscribe((event) => {
|
|
1599
2226
|
this.handleOutsideClick(event);
|
|
1600
2227
|
});
|
|
2228
|
+
effect(() => {
|
|
2229
|
+
const nav = this.navContainer();
|
|
2230
|
+
if (nav && this.layoutService.isTopbar()) {
|
|
2231
|
+
fromEvent(nav.nativeElement, 'scroll')
|
|
2232
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
2233
|
+
.subscribe(() => this.recalculateHoveredSubmenu(nav.nativeElement));
|
|
2234
|
+
}
|
|
2235
|
+
});
|
|
1601
2236
|
}
|
|
1602
2237
|
companyName = this.layoutService.companyName;
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
}, ...(ngDevMode ? [{ debugName: "enableCompanyFeature" }] : []));
|
|
2238
|
+
displayLogo = this.layoutService.displayLogo;
|
|
2239
|
+
enableCompanyFeature = isCompanyFeatureEnabled(this.appConfig);
|
|
1606
2240
|
toggleDarkMode() {
|
|
1607
2241
|
const currentDarkTheme = this.layoutService.layoutConfig().darkTheme;
|
|
1608
2242
|
this.layoutService.updateLayoutConfig({ darkTheme: !currentDarkTheme });
|
|
@@ -1617,38 +2251,90 @@ class AppTopbar {
|
|
|
1617
2251
|
this.activePanel.set(null);
|
|
1618
2252
|
}
|
|
1619
2253
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
2254
|
+
recalculateHoveredSubmenu(navEl) {
|
|
2255
|
+
const hoveredItems = navEl.querySelectorAll('li:hover');
|
|
2256
|
+
hoveredItems.forEach((li) => {
|
|
2257
|
+
const link = li.querySelector(':scope > a');
|
|
2258
|
+
if (!link)
|
|
2259
|
+
return;
|
|
2260
|
+
const rect = link.getBoundingClientRect();
|
|
2261
|
+
const top = rect.bottom + 12;
|
|
2262
|
+
const left = rect.left - 12;
|
|
2263
|
+
li.style.setProperty('--submenu-left', `${left}px`);
|
|
2264
|
+
li.style.setProperty('--submenu-top', `${top}px`);
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
2267
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2268
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: AppTopbar, isStandalone: true, selector: "app-topbar", viewQueries: [{ propertyName: "configContainer", first: true, predicate: ["configContainer"], descendants: true, isSignal: true }, { propertyName: "navContainer", first: true, predicate: ["navContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `<div class="layout-topbar">
|
|
2269
|
+
<div
|
|
2270
|
+
class="layout-topbar-logo-container"
|
|
2271
|
+
[class.layout-topbar-logo-container-desktop]="layoutService.isDesktop()"
|
|
2272
|
+
>
|
|
2273
|
+
@if (!layoutService.isTopbar() && layoutService.isMobile()) {
|
|
2274
|
+
<button
|
|
2275
|
+
class="layout-menu-button layout-topbar-action"
|
|
2276
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2277
|
+
(click)="layoutService.onMenuToggle()"
|
|
2278
|
+
>
|
|
2279
|
+
<ui-icon name="bars"></ui-icon>
|
|
2280
|
+
</button>
|
|
2281
|
+
}
|
|
1629
2282
|
<a class="layout-topbar-logo" routerLink="/">
|
|
1630
|
-
|
|
2283
|
+
@if (displayLogo()) {
|
|
2284
|
+
@if (layoutService.isDisplayLogoSvg()) {
|
|
2285
|
+
<inline-svg [src]="displayLogo()" class="logo-image" />
|
|
2286
|
+
} @else {
|
|
2287
|
+
<img
|
|
2288
|
+
[src]="displayLogo()"
|
|
2289
|
+
[alt]="companyName()"
|
|
2290
|
+
class="logo-image"
|
|
2291
|
+
/>
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
<span class="hidden sm:inline">{{ companyName() }}</span>
|
|
1631
2295
|
</a>
|
|
2296
|
+
@if (!layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2297
|
+
<button
|
|
2298
|
+
class="layout-menu-button layout-topbar-action layout-topbar-action-right"
|
|
2299
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2300
|
+
(click)="layoutService.onMenuToggle()"
|
|
2301
|
+
>
|
|
2302
|
+
<ui-icon name="bars"></ui-icon>
|
|
2303
|
+
</button>
|
|
2304
|
+
}
|
|
1632
2305
|
</div>
|
|
1633
2306
|
|
|
2307
|
+
@if (layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2308
|
+
<div
|
|
2309
|
+
#navContainer
|
|
2310
|
+
class="layout-topbar-nav-horizontal"
|
|
2311
|
+
[class.layout-topbar-nav-hidden]="
|
|
2312
|
+
!layoutService.layoutState().topbarMenuVisible
|
|
2313
|
+
"
|
|
2314
|
+
>
|
|
2315
|
+
<app-menu />
|
|
2316
|
+
</div>
|
|
2317
|
+
}
|
|
2318
|
+
|
|
1634
2319
|
<div class="layout-topbar-actions">
|
|
2320
|
+
@if (hasSearchAdapter()) {
|
|
2321
|
+
<app-searchbar />
|
|
2322
|
+
}
|
|
1635
2323
|
<div class="layout-config-menu">
|
|
1636
2324
|
<button
|
|
1637
2325
|
type="button"
|
|
1638
2326
|
class="layout-topbar-action"
|
|
2327
|
+
[attr.aria-label]="'layout.topbar.toggle.dark.mode' | translate"
|
|
1639
2328
|
(click)="toggleDarkMode()"
|
|
1640
2329
|
>
|
|
1641
|
-
<
|
|
1642
|
-
class="pi"
|
|
1643
|
-
[class.pi-moon]="layoutService.isDarkTheme()"
|
|
1644
|
-
[class.pi-sun]="!layoutService.isDarkTheme()"
|
|
1645
|
-
></i>
|
|
2330
|
+
<ui-icon [name]="layoutService.isDarkTheme() ? 'moon' : 'sun'"></ui-icon>
|
|
1646
2331
|
</button>
|
|
1647
2332
|
<div class="relative" #configContainer>
|
|
1648
2333
|
<button
|
|
1649
2334
|
class="layout-topbar-action"
|
|
1650
2335
|
[class.layout-topbar-action-highlight]="activePanel() === 'config'"
|
|
1651
|
-
|
|
2336
|
+
[attr.aria-label]="'layout.topbar.open.theme.settings' | translate"
|
|
2337
|
+
fStyleClass="@next"
|
|
1652
2338
|
enterFromClass="hidden"
|
|
1653
2339
|
enterActiveClass="animate-scalein"
|
|
1654
2340
|
leaveToClass="hidden"
|
|
@@ -1656,7 +2342,7 @@ class AppTopbar {
|
|
|
1656
2342
|
[hideOnOutsideClick]="true"
|
|
1657
2343
|
(click)="togglePanel('config')"
|
|
1658
2344
|
>
|
|
1659
|
-
<
|
|
2345
|
+
<ui-icon name="palette"></ui-icon>
|
|
1660
2346
|
</button>
|
|
1661
2347
|
<app-configurator />
|
|
1662
2348
|
</div>
|
|
@@ -1664,14 +2350,15 @@ class AppTopbar {
|
|
|
1664
2350
|
|
|
1665
2351
|
<button
|
|
1666
2352
|
class="layout-topbar-menu-button layout-topbar-action"
|
|
1667
|
-
|
|
2353
|
+
[attr.aria-label]="'layout.topbar.more.options' | translate"
|
|
2354
|
+
fStyleClass="@next"
|
|
1668
2355
|
enterFromClass="hidden"
|
|
1669
2356
|
enterActiveClass="animate-scalein"
|
|
1670
2357
|
leaveToClass="hidden"
|
|
1671
2358
|
leaveActiveClass="animate-fadeout"
|
|
1672
2359
|
[hideOnOutsideClick]="true"
|
|
1673
2360
|
>
|
|
1674
|
-
<
|
|
2361
|
+
<ui-icon name="ellipsis-v"></ui-icon>
|
|
1675
2362
|
</button>
|
|
1676
2363
|
|
|
1677
2364
|
<div class="layout-topbar-menu hidden lg:block">
|
|
@@ -1679,59 +2366,105 @@ class AppTopbar {
|
|
|
1679
2366
|
@if (layoutService.hasApps()) {
|
|
1680
2367
|
<app-launcher />
|
|
1681
2368
|
}
|
|
1682
|
-
@if (
|
|
2369
|
+
@if (languageSelectorComponent) {
|
|
2370
|
+
<ng-container *ngComponentOutlet="languageSelectorComponent" />
|
|
2371
|
+
}
|
|
2372
|
+
@if (enableCompanyFeature) {
|
|
1683
2373
|
<app-company-branch-selector />
|
|
1684
2374
|
}
|
|
2375
|
+
@if (notificationBellComponent) {
|
|
2376
|
+
<ng-container *ngComponentOutlet="notificationBellComponent" />
|
|
2377
|
+
}
|
|
1685
2378
|
<app-profile />
|
|
1686
2379
|
</div>
|
|
1687
2380
|
</div>
|
|
1688
2381
|
</div>
|
|
1689
|
-
</div>`, isInline: true, dependencies: [{ kind: "ngmodule", type:
|
|
2382
|
+
</div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.StyleClassDirective, selector: "[fStyleClass]", inputs: ["fStyleClass", "enterFromClass", "enterActiveClass", "enterToClass", "leaveFromClass", "leaveActiveClass", "leaveToClass", "hideOnOutsideClick", "toggleClass"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: InlineSvgComponent, selector: "inline-svg", inputs: ["src"] }, { kind: "component", type: AppConfigurator, selector: "app-configurator" }, { kind: "component", type: AppMenu, selector: "app-menu" }, { kind: "component", type: AppProfile, selector: "app-profile" }, { kind: "component", type: AppCompanyBranchSelector, selector: "app-company-branch-selector" }, { kind: "component", type: AppLauncher, selector: "app-launcher" }, { kind: "component", type: AppSearchbar, selector: "app-searchbar" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1690
2383
|
}
|
|
1691
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2384
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppTopbar, decorators: [{
|
|
1692
2385
|
type: Component,
|
|
1693
2386
|
args: [{
|
|
1694
2387
|
selector: 'app-topbar',
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
2388
|
+
imports: [UiModule, RouterModule,
|
|
2389
|
+
NgComponentOutlet,
|
|
2390
|
+
TranslatePipe,
|
|
2391
|
+
InlineSvgComponent,
|
|
1699
2392
|
AppConfigurator,
|
|
2393
|
+
AppMenu,
|
|
1700
2394
|
AppProfile,
|
|
1701
2395
|
AppCompanyBranchSelector,
|
|
1702
2396
|
AppLauncher,
|
|
1703
|
-
|
|
1704
|
-
template:
|
|
1705
|
-
<div
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
<
|
|
1711
|
-
|
|
2397
|
+
AppSearchbar,],
|
|
2398
|
+
template: `<div class="layout-topbar">
|
|
2399
|
+
<div
|
|
2400
|
+
class="layout-topbar-logo-container"
|
|
2401
|
+
[class.layout-topbar-logo-container-desktop]="layoutService.isDesktop()"
|
|
2402
|
+
>
|
|
2403
|
+
@if (!layoutService.isTopbar() && layoutService.isMobile()) {
|
|
2404
|
+
<button
|
|
2405
|
+
class="layout-menu-button layout-topbar-action"
|
|
2406
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2407
|
+
(click)="layoutService.onMenuToggle()"
|
|
2408
|
+
>
|
|
2409
|
+
<ui-icon name="bars"></ui-icon>
|
|
2410
|
+
</button>
|
|
2411
|
+
}
|
|
1712
2412
|
<a class="layout-topbar-logo" routerLink="/">
|
|
1713
|
-
|
|
2413
|
+
@if (displayLogo()) {
|
|
2414
|
+
@if (layoutService.isDisplayLogoSvg()) {
|
|
2415
|
+
<inline-svg [src]="displayLogo()" class="logo-image" />
|
|
2416
|
+
} @else {
|
|
2417
|
+
<img
|
|
2418
|
+
[src]="displayLogo()"
|
|
2419
|
+
[alt]="companyName()"
|
|
2420
|
+
class="logo-image"
|
|
2421
|
+
/>
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
<span class="hidden sm:inline">{{ companyName() }}</span>
|
|
1714
2425
|
</a>
|
|
2426
|
+
@if (!layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2427
|
+
<button
|
|
2428
|
+
class="layout-menu-button layout-topbar-action layout-topbar-action-right"
|
|
2429
|
+
[attr.aria-label]="'layout.topbar.toggle.menu' | translate"
|
|
2430
|
+
(click)="layoutService.onMenuToggle()"
|
|
2431
|
+
>
|
|
2432
|
+
<ui-icon name="bars"></ui-icon>
|
|
2433
|
+
</button>
|
|
2434
|
+
}
|
|
1715
2435
|
</div>
|
|
1716
2436
|
|
|
2437
|
+
@if (layoutService.isTopbar() && layoutService.isDesktop()) {
|
|
2438
|
+
<div
|
|
2439
|
+
#navContainer
|
|
2440
|
+
class="layout-topbar-nav-horizontal"
|
|
2441
|
+
[class.layout-topbar-nav-hidden]="
|
|
2442
|
+
!layoutService.layoutState().topbarMenuVisible
|
|
2443
|
+
"
|
|
2444
|
+
>
|
|
2445
|
+
<app-menu />
|
|
2446
|
+
</div>
|
|
2447
|
+
}
|
|
2448
|
+
|
|
1717
2449
|
<div class="layout-topbar-actions">
|
|
2450
|
+
@if (hasSearchAdapter()) {
|
|
2451
|
+
<app-searchbar />
|
|
2452
|
+
}
|
|
1718
2453
|
<div class="layout-config-menu">
|
|
1719
2454
|
<button
|
|
1720
2455
|
type="button"
|
|
1721
2456
|
class="layout-topbar-action"
|
|
2457
|
+
[attr.aria-label]="'layout.topbar.toggle.dark.mode' | translate"
|
|
1722
2458
|
(click)="toggleDarkMode()"
|
|
1723
2459
|
>
|
|
1724
|
-
<
|
|
1725
|
-
class="pi"
|
|
1726
|
-
[class.pi-moon]="layoutService.isDarkTheme()"
|
|
1727
|
-
[class.pi-sun]="!layoutService.isDarkTheme()"
|
|
1728
|
-
></i>
|
|
2460
|
+
<ui-icon [name]="layoutService.isDarkTheme() ? 'moon' : 'sun'"></ui-icon>
|
|
1729
2461
|
</button>
|
|
1730
2462
|
<div class="relative" #configContainer>
|
|
1731
2463
|
<button
|
|
1732
2464
|
class="layout-topbar-action"
|
|
1733
2465
|
[class.layout-topbar-action-highlight]="activePanel() === 'config'"
|
|
1734
|
-
|
|
2466
|
+
[attr.aria-label]="'layout.topbar.open.theme.settings' | translate"
|
|
2467
|
+
fStyleClass="@next"
|
|
1735
2468
|
enterFromClass="hidden"
|
|
1736
2469
|
enterActiveClass="animate-scalein"
|
|
1737
2470
|
leaveToClass="hidden"
|
|
@@ -1739,7 +2472,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1739
2472
|
[hideOnOutsideClick]="true"
|
|
1740
2473
|
(click)="togglePanel('config')"
|
|
1741
2474
|
>
|
|
1742
|
-
<
|
|
2475
|
+
<ui-icon name="palette"></ui-icon>
|
|
1743
2476
|
</button>
|
|
1744
2477
|
<app-configurator />
|
|
1745
2478
|
</div>
|
|
@@ -1747,14 +2480,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1747
2480
|
|
|
1748
2481
|
<button
|
|
1749
2482
|
class="layout-topbar-menu-button layout-topbar-action"
|
|
1750
|
-
|
|
2483
|
+
[attr.aria-label]="'layout.topbar.more.options' | translate"
|
|
2484
|
+
fStyleClass="@next"
|
|
1751
2485
|
enterFromClass="hidden"
|
|
1752
2486
|
enterActiveClass="animate-scalein"
|
|
1753
2487
|
leaveToClass="hidden"
|
|
1754
2488
|
leaveActiveClass="animate-fadeout"
|
|
1755
2489
|
[hideOnOutsideClick]="true"
|
|
1756
2490
|
>
|
|
1757
|
-
<
|
|
2491
|
+
<ui-icon name="ellipsis-v"></ui-icon>
|
|
1758
2492
|
</button>
|
|
1759
2493
|
|
|
1760
2494
|
<div class="layout-topbar-menu hidden lg:block">
|
|
@@ -1762,294 +2496,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1762
2496
|
@if (layoutService.hasApps()) {
|
|
1763
2497
|
<app-launcher />
|
|
1764
2498
|
}
|
|
1765
|
-
@if (
|
|
2499
|
+
@if (languageSelectorComponent) {
|
|
2500
|
+
<ng-container *ngComponentOutlet="languageSelectorComponent" />
|
|
2501
|
+
}
|
|
2502
|
+
@if (enableCompanyFeature) {
|
|
1766
2503
|
<app-company-branch-selector />
|
|
1767
2504
|
}
|
|
2505
|
+
@if (notificationBellComponent) {
|
|
2506
|
+
<ng-container *ngComponentOutlet="notificationBellComponent" />
|
|
2507
|
+
}
|
|
1768
2508
|
<app-profile />
|
|
1769
2509
|
</div>
|
|
1770
2510
|
</div>
|
|
1771
2511
|
</div>
|
|
1772
2512
|
</div>`,
|
|
1773
2513
|
}]
|
|
1774
|
-
}], ctorParameters: () => [], propDecorators: { configContainer: [{ type: i0.ViewChild, args: ['configContainer', { isSignal: true }] }] } });
|
|
1775
|
-
|
|
1776
|
-
class AppMenuitem {
|
|
1777
|
-
// Signal inputs
|
|
1778
|
-
item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
|
|
1779
|
-
index = input.required(...(ngDevMode ? [{ debugName: "index" }] : []));
|
|
1780
|
-
parentKey = input('', ...(ngDevMode ? [{ debugName: "parentKey" }] : []));
|
|
1781
|
-
// Injected services
|
|
1782
|
-
router = inject(Router);
|
|
1783
|
-
layoutService = inject(LayoutService);
|
|
1784
|
-
destroyRef = inject(DestroyRef);
|
|
1785
|
-
// State signals - private writable + public readonly pattern
|
|
1786
|
-
_active = signal(false, ...(ngDevMode ? [{ debugName: "_active" }] : []));
|
|
1787
|
-
active = this._active.asReadonly();
|
|
1788
|
-
// Computed signals
|
|
1789
|
-
key = computed(() => {
|
|
1790
|
-
const parent = this.parentKey();
|
|
1791
|
-
return parent ? `${parent}-${this.index()}` : String(this.index());
|
|
1792
|
-
}, ...(ngDevMode ? [{ debugName: "key" }] : []));
|
|
1793
|
-
routerLink = computed(() => {
|
|
1794
|
-
const menuItem = this.item();
|
|
1795
|
-
return menuItem.routerLink ?? [];
|
|
1796
|
-
}, ...(ngDevMode ? [{ debugName: "routerLink" }] : []));
|
|
1797
|
-
// Computed options for routerLinkActive - use 'exact' for root, 'subset' for others
|
|
1798
|
-
routerLinkActiveOptions = computed(() => {
|
|
1799
|
-
const link = this.routerLink();
|
|
1800
|
-
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
1801
|
-
return {
|
|
1802
|
-
paths: pathsMatch,
|
|
1803
|
-
queryParams: 'ignored',
|
|
1804
|
-
matrixParams: 'ignored',
|
|
1805
|
-
fragment: 'ignored',
|
|
1806
|
-
};
|
|
1807
|
-
}, ...(ngDevMode ? [{ debugName: "routerLinkActiveOptions" }] : []));
|
|
1808
|
-
constructor() {
|
|
1809
|
-
// Subscribe to menu source changes
|
|
1810
|
-
this.layoutService.menuSource$
|
|
1811
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1812
|
-
.subscribe((value) => {
|
|
1813
|
-
Promise.resolve(null).then(() => {
|
|
1814
|
-
const currentKey = this.key();
|
|
1815
|
-
if (value.routeEvent) {
|
|
1816
|
-
this._active.set(value.key === currentKey ||
|
|
1817
|
-
value.key?.startsWith(currentKey + '-'));
|
|
1818
|
-
}
|
|
1819
|
-
else if (value.key !== currentKey &&
|
|
1820
|
-
!value.key?.startsWith(currentKey + '-')) {
|
|
1821
|
-
this._active.set(false);
|
|
1822
|
-
}
|
|
1823
|
-
});
|
|
1824
|
-
});
|
|
1825
|
-
// Subscribe to menu reset
|
|
1826
|
-
this.layoutService.resetSource$
|
|
1827
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1828
|
-
.subscribe(() => this._active.set(false));
|
|
1829
|
-
// Subscribe to navigation events
|
|
1830
|
-
this.router.events
|
|
1831
|
-
.pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
|
|
1832
|
-
.subscribe(() => {
|
|
1833
|
-
if (this.item().routerLink) {
|
|
1834
|
-
this.updateActiveStateFromRoute();
|
|
1835
|
-
}
|
|
1836
|
-
});
|
|
1837
|
-
// Effect to update active state on init when item has routerLink
|
|
1838
|
-
effect(() => {
|
|
1839
|
-
const menuItem = this.item();
|
|
1840
|
-
if (menuItem.routerLink) {
|
|
1841
|
-
this.updateActiveStateFromRoute();
|
|
1842
|
-
}
|
|
1843
|
-
});
|
|
1844
|
-
}
|
|
1845
|
-
updateActiveStateFromRoute() {
|
|
1846
|
-
const link = this.routerLink();
|
|
1847
|
-
if (!link.length)
|
|
1848
|
-
return;
|
|
1849
|
-
// Use 'exact' for root path to avoid matching all routes
|
|
1850
|
-
// Use 'subset' for all other paths to match child routes
|
|
1851
|
-
const pathsMatch = link[0] === '/' ? 'exact' : 'subset';
|
|
1852
|
-
const isActive = this.router.isActive(link[0], {
|
|
1853
|
-
paths: pathsMatch,
|
|
1854
|
-
queryParams: 'ignored',
|
|
1855
|
-
matrixParams: 'ignored',
|
|
1856
|
-
fragment: 'ignored',
|
|
1857
|
-
});
|
|
1858
|
-
if (isActive) {
|
|
1859
|
-
this.layoutService.onMenuStateChange({
|
|
1860
|
-
key: this.key(),
|
|
1861
|
-
routeEvent: true,
|
|
1862
|
-
});
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
itemClick() {
|
|
1866
|
-
if (this.item().children) {
|
|
1867
|
-
this._active.update((prev) => !prev);
|
|
1868
|
-
}
|
|
1869
|
-
this.layoutService.onMenuStateChange({ key: this.key() });
|
|
1870
|
-
}
|
|
1871
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenuitem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1872
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppMenuitem, isStandalone: true, selector: "[app-menuitem]", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, parentKey: { classPropertyName: "parentKey", publicName: "parentKey", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.active-menuitem": "active()" } }, ngImport: i0, template: `
|
|
1873
|
-
<ng-container>
|
|
1874
|
-
@if (item().children?.length) {
|
|
1875
|
-
<a (click)="itemClick()" tabindex="0" pRipple>
|
|
1876
|
-
@if (item().icon) {
|
|
1877
|
-
<lib-icon
|
|
1878
|
-
[icon]="item().icon!"
|
|
1879
|
-
[iconType]="item().iconType"
|
|
1880
|
-
class="layout-menuitem-icon"
|
|
1881
|
-
/>
|
|
1882
|
-
}
|
|
1883
|
-
<span class="layout-menuitem-text">{{ item().label }}</span>
|
|
1884
|
-
@if (item().children) {
|
|
1885
|
-
<i class="pi pi-fw pi-angle-down layout-submenu-toggler"></i>
|
|
1886
|
-
}
|
|
1887
|
-
</a>
|
|
1888
|
-
}
|
|
1889
|
-
@if (item().routerLink && !item().children?.length) {
|
|
1890
|
-
<a
|
|
1891
|
-
(click)="itemClick()"
|
|
1892
|
-
[routerLink]="routerLink()"
|
|
1893
|
-
routerLinkActive="active-route"
|
|
1894
|
-
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
1895
|
-
tabindex="0"
|
|
1896
|
-
pRipple
|
|
1897
|
-
>
|
|
1898
|
-
@if (item().icon) {
|
|
1899
|
-
<lib-icon
|
|
1900
|
-
[icon]="item().icon!"
|
|
1901
|
-
[iconType]="item().iconType"
|
|
1902
|
-
class="layout-menuitem-icon"
|
|
1903
|
-
/>
|
|
1904
|
-
}
|
|
1905
|
-
<span class="layout-menuitem-text">{{ item().label }}</span>
|
|
1906
|
-
</a>
|
|
1907
|
-
}
|
|
1908
|
-
@if (item().children) {
|
|
1909
|
-
<ul
|
|
1910
|
-
[class.submenu-expanded]="active()"
|
|
1911
|
-
[class.submenu-collapsed]="!active()"
|
|
1912
|
-
>
|
|
1913
|
-
@for (
|
|
1914
|
-
child of item().children;
|
|
1915
|
-
track 'menu' + child.id + '' + i;
|
|
1916
|
-
let i = $index
|
|
1917
|
-
) {
|
|
1918
|
-
<li
|
|
1919
|
-
app-menuitem
|
|
1920
|
-
[item]="child"
|
|
1921
|
-
[index]="i"
|
|
1922
|
-
[parentKey]="key()"
|
|
1923
|
-
></li>
|
|
1924
|
-
}
|
|
1925
|
-
</ul>
|
|
1926
|
-
}
|
|
1927
|
-
</ng-container>
|
|
1928
|
-
`, isInline: true, styles: [":host ul{overflow:hidden;transition:max-height .4s cubic-bezier(.86,0,.07,1)}:host ul.submenu-collapsed{max-height:0}:host ul.submenu-expanded{max-height:1000px}\n"], dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["icon", "iconType"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1$2.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: RippleModule }, { kind: "directive", type: i2$3.Ripple, selector: "[pRipple]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1929
|
-
}
|
|
1930
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenuitem, decorators: [{
|
|
1931
|
-
type: Component,
|
|
1932
|
-
args: [{ selector: '[app-menuitem]', changeDetection: ChangeDetectionStrategy.OnPush, imports: [IconComponent, RouterModule, RippleModule], template: `
|
|
1933
|
-
<ng-container>
|
|
1934
|
-
@if (item().children?.length) {
|
|
1935
|
-
<a (click)="itemClick()" tabindex="0" pRipple>
|
|
1936
|
-
@if (item().icon) {
|
|
1937
|
-
<lib-icon
|
|
1938
|
-
[icon]="item().icon!"
|
|
1939
|
-
[iconType]="item().iconType"
|
|
1940
|
-
class="layout-menuitem-icon"
|
|
1941
|
-
/>
|
|
1942
|
-
}
|
|
1943
|
-
<span class="layout-menuitem-text">{{ item().label }}</span>
|
|
1944
|
-
@if (item().children) {
|
|
1945
|
-
<i class="pi pi-fw pi-angle-down layout-submenu-toggler"></i>
|
|
1946
|
-
}
|
|
1947
|
-
</a>
|
|
1948
|
-
}
|
|
1949
|
-
@if (item().routerLink && !item().children?.length) {
|
|
1950
|
-
<a
|
|
1951
|
-
(click)="itemClick()"
|
|
1952
|
-
[routerLink]="routerLink()"
|
|
1953
|
-
routerLinkActive="active-route"
|
|
1954
|
-
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
|
1955
|
-
tabindex="0"
|
|
1956
|
-
pRipple
|
|
1957
|
-
>
|
|
1958
|
-
@if (item().icon) {
|
|
1959
|
-
<lib-icon
|
|
1960
|
-
[icon]="item().icon!"
|
|
1961
|
-
[iconType]="item().iconType"
|
|
1962
|
-
class="layout-menuitem-icon"
|
|
1963
|
-
/>
|
|
1964
|
-
}
|
|
1965
|
-
<span class="layout-menuitem-text">{{ item().label }}</span>
|
|
1966
|
-
</a>
|
|
1967
|
-
}
|
|
1968
|
-
@if (item().children) {
|
|
1969
|
-
<ul
|
|
1970
|
-
[class.submenu-expanded]="active()"
|
|
1971
|
-
[class.submenu-collapsed]="!active()"
|
|
1972
|
-
>
|
|
1973
|
-
@for (
|
|
1974
|
-
child of item().children;
|
|
1975
|
-
track 'menu' + child.id + '' + i;
|
|
1976
|
-
let i = $index
|
|
1977
|
-
) {
|
|
1978
|
-
<li
|
|
1979
|
-
app-menuitem
|
|
1980
|
-
[item]="child"
|
|
1981
|
-
[index]="i"
|
|
1982
|
-
[parentKey]="key()"
|
|
1983
|
-
></li>
|
|
1984
|
-
}
|
|
1985
|
-
</ul>
|
|
1986
|
-
}
|
|
1987
|
-
</ng-container>
|
|
1988
|
-
`, host: {
|
|
1989
|
-
'[class.active-menuitem]': 'active()',
|
|
1990
|
-
}, styles: [":host ul{overflow:hidden;transition:max-height .4s cubic-bezier(.86,0,.07,1)}:host ul.submenu-collapsed{max-height:0}:host ul.submenu-expanded{max-height:1000px}\n"] }]
|
|
1991
|
-
}], ctorParameters: () => [], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: true }] }], parentKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentKey", required: false }] }] } });
|
|
1992
|
-
|
|
1993
|
-
/**
|
|
1994
|
-
* Main menu component that displays filtered menu items.
|
|
1995
|
-
* Menu is automatically filtered based on user permissions using the permission checker (logicNode pattern).
|
|
1996
|
-
* The filtering happens internally in LayoutService via computed signal.
|
|
1997
|
-
*/
|
|
1998
|
-
class AppMenu {
|
|
1999
|
-
layoutService = inject(LayoutService);
|
|
2000
|
-
/**
|
|
2001
|
-
* Filtered menu items from layout service.
|
|
2002
|
-
* This signal is computed in LayoutService and automatically updates when:
|
|
2003
|
-
* - Raw menu changes (setMenu called)
|
|
2004
|
-
* - Permission state changes (user permissions updated)
|
|
2005
|
-
*/
|
|
2006
|
-
menuItems = this.layoutService.menu;
|
|
2007
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2008
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: AppMenu, isStandalone: true, selector: "app-menu", ngImport: i0, template: `<div class="layout-menu">
|
|
2009
|
-
<ul>
|
|
2010
|
-
@for (
|
|
2011
|
-
item of menuItems();
|
|
2012
|
-
track 'menu' + item.id + '' + i;
|
|
2013
|
-
let i = $index
|
|
2014
|
-
) {
|
|
2015
|
-
<li app-menuitem [item]="item" [index]="i"></li>
|
|
2016
|
-
}
|
|
2017
|
-
</ul>
|
|
2018
|
-
</div>`, isInline: true, dependencies: [{ kind: "component", type: AppMenuitem, selector: "[app-menuitem]", inputs: ["item", "index", "parentKey"] }, { kind: "ngmodule", type: RouterModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2019
|
-
}
|
|
2020
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AppMenu, decorators: [{
|
|
2021
|
-
type: Component,
|
|
2022
|
-
args: [{
|
|
2023
|
-
selector: 'app-menu',
|
|
2024
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2025
|
-
imports: [AppMenuitem, RouterModule],
|
|
2026
|
-
template: `<div class="layout-menu">
|
|
2027
|
-
<ul>
|
|
2028
|
-
@for (
|
|
2029
|
-
item of menuItems();
|
|
2030
|
-
track 'menu' + item.id + '' + i;
|
|
2031
|
-
let i = $index
|
|
2032
|
-
) {
|
|
2033
|
-
<li app-menuitem [item]="item" [index]="i"></li>
|
|
2034
|
-
}
|
|
2035
|
-
</ul>
|
|
2036
|
-
</div>`,
|
|
2037
|
-
}]
|
|
2038
|
-
}] });
|
|
2514
|
+
}], ctorParameters: () => [], propDecorators: { configContainer: [{ type: i0.ViewChild, args: ['configContainer', { isSignal: true }] }], navContainer: [{ type: i0.ViewChild, args: ['navContainer', { isSignal: true }] }] } });
|
|
2039
2515
|
|
|
2040
2516
|
class AppSidebar {
|
|
2041
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2042
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
2517
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2518
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.6", type: AppSidebar, isStandalone: true, selector: "app-sidebar", ngImport: i0, template: `
|
|
2043
2519
|
<div class="layout-sidebar">
|
|
2044
2520
|
<app-menu />
|
|
2045
2521
|
</div>
|
|
2046
|
-
`, isInline: true, dependencies: [{ kind: "component", type: AppMenu, selector: "app-menu" }]
|
|
2522
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AppMenu, selector: "app-menu" }] });
|
|
2047
2523
|
}
|
|
2048
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2524
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppSidebar, decorators: [{
|
|
2049
2525
|
type: Component,
|
|
2050
2526
|
args: [{
|
|
2051
2527
|
selector: 'app-sidebar',
|
|
2052
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2053
2528
|
imports: [AppMenu],
|
|
2054
2529
|
template: `
|
|
2055
2530
|
<div class="layout-sidebar">
|
|
@@ -2088,11 +2563,14 @@ class AppLayout {
|
|
|
2088
2563
|
isOutsideClicked(event) {
|
|
2089
2564
|
const sidebarEl = this.document.querySelector('.layout-sidebar');
|
|
2090
2565
|
const topbarEl = this.document.querySelector('.layout-menu-button');
|
|
2566
|
+
const topbarNavEl = this.document.querySelector('.layout-topbar-nav');
|
|
2091
2567
|
const eventTarget = event.target;
|
|
2092
2568
|
return !(sidebarEl?.isSameNode(eventTarget) ||
|
|
2093
2569
|
sidebarEl?.contains(eventTarget) ||
|
|
2094
2570
|
topbarEl?.isSameNode(eventTarget) ||
|
|
2095
|
-
topbarEl?.contains(eventTarget)
|
|
2571
|
+
topbarEl?.contains(eventTarget) ||
|
|
2572
|
+
topbarNavEl?.isSameNode(eventTarget) ||
|
|
2573
|
+
topbarNavEl?.contains(eventTarget));
|
|
2096
2574
|
}
|
|
2097
2575
|
hideMenu() {
|
|
2098
2576
|
this.layoutService.updateLayoutState({
|
|
@@ -2112,20 +2590,32 @@ class AppLayout {
|
|
|
2112
2590
|
unblockBodyScroll() {
|
|
2113
2591
|
this.document.body.classList.remove('blocked-scroll');
|
|
2114
2592
|
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2593
|
+
isOverlay = computed(() => this.layoutService.layoutConfig().menuMode === 'overlay', /* @ts-ignore */
|
|
2594
|
+
...(ngDevMode ? [{ debugName: "isOverlay" }] : /* istanbul ignore next */ []));
|
|
2595
|
+
isStatic = computed(() => this.layoutService.layoutConfig().menuMode === 'static', /* @ts-ignore */
|
|
2596
|
+
...(ngDevMode ? [{ debugName: "isStatic" }] : /* istanbul ignore next */ []));
|
|
2597
|
+
isTopbarMode = computed(() => this.layoutService.layoutConfig().menuMode === 'topbar', /* @ts-ignore */
|
|
2598
|
+
...(ngDevMode ? [{ debugName: "isTopbarMode" }] : /* istanbul ignore next */ []));
|
|
2599
|
+
isStaticInactive = computed(() => this.layoutService.layoutState().staticMenuDesktopInactive && this.isStatic(), /* @ts-ignore */
|
|
2600
|
+
...(ngDevMode ? [{ debugName: "isStaticInactive" }] : /* istanbul ignore next */ []));
|
|
2601
|
+
isOverlayActive = computed(() => this.layoutService.layoutState().overlayMenuActive, /* @ts-ignore */
|
|
2602
|
+
...(ngDevMode ? [{ debugName: "isOverlayActive" }] : /* istanbul ignore next */ []));
|
|
2603
|
+
isMobileActive = computed(() => this.layoutService.layoutState().staticMenuMobileActive, /* @ts-ignore */
|
|
2604
|
+
...(ngDevMode ? [{ debugName: "isMobileActive" }] : /* istanbul ignore next */ []));
|
|
2605
|
+
isTopbarNavHidden = computed(() => !this.layoutService.layoutState().topbarMenuVisible && this.isTopbarMode(), /* @ts-ignore */
|
|
2606
|
+
...(ngDevMode ? [{ debugName: "isTopbarNavHidden" }] : /* istanbul ignore next */ []));
|
|
2607
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2608
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.6", type: AppLayout, isStandalone: true, selector: "app-layout", ngImport: i0, template: `
|
|
2609
|
+
<div
|
|
2610
|
+
class="layout-wrapper"
|
|
2611
|
+
[class.layout-overlay]="isOverlay()"
|
|
2612
|
+
[class.layout-static]="isStatic()"
|
|
2613
|
+
[class.layout-topbar-mode]="isTopbarMode()"
|
|
2614
|
+
[class.layout-static-inactive]="isStaticInactive()"
|
|
2615
|
+
[class.layout-overlay-active]="isOverlayActive()"
|
|
2616
|
+
[class.layout-mobile-active]="isMobileActive()"
|
|
2617
|
+
[class.layout-topbar-nav-hidden]="isTopbarNavHidden()"
|
|
2618
|
+
>
|
|
2129
2619
|
<app-topbar></app-topbar>
|
|
2130
2620
|
<app-sidebar></app-sidebar>
|
|
2131
2621
|
<div class="layout-main-container">
|
|
@@ -2136,16 +2626,24 @@ class AppLayout {
|
|
|
2136
2626
|
</div>
|
|
2137
2627
|
<div class="layout-mask animate-fadein"></div>
|
|
2138
2628
|
</div>
|
|
2139
|
-
`, isInline: true, dependencies: [{ kind: "
|
|
2629
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AppTopbar, selector: "app-topbar" }, { kind: "component", type: AppSidebar, selector: "app-sidebar" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: AppFooter, selector: "app-footer" }] });
|
|
2140
2630
|
}
|
|
2141
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2631
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: AppLayout, decorators: [{
|
|
2142
2632
|
type: Component,
|
|
2143
2633
|
args: [{
|
|
2144
2634
|
selector: 'app-layout',
|
|
2145
|
-
|
|
2146
|
-
imports: [NgClass, AppTopbar, AppSidebar, RouterModule, AppFooter],
|
|
2635
|
+
imports: [AppTopbar, AppSidebar, RouterModule, AppFooter],
|
|
2147
2636
|
template: `
|
|
2148
|
-
<div
|
|
2637
|
+
<div
|
|
2638
|
+
class="layout-wrapper"
|
|
2639
|
+
[class.layout-overlay]="isOverlay()"
|
|
2640
|
+
[class.layout-static]="isStatic()"
|
|
2641
|
+
[class.layout-topbar-mode]="isTopbarMode()"
|
|
2642
|
+
[class.layout-static-inactive]="isStaticInactive()"
|
|
2643
|
+
[class.layout-overlay-active]="isOverlayActive()"
|
|
2644
|
+
[class.layout-mobile-active]="isMobileActive()"
|
|
2645
|
+
[class.layout-topbar-nav-hidden]="isTopbarNavHidden()"
|
|
2646
|
+
>
|
|
2149
2647
|
<app-topbar></app-topbar>
|
|
2150
2648
|
<app-sidebar></app-sidebar>
|
|
2151
2649
|
<div class="layout-main-container">
|
|
@@ -2160,47 +2658,97 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2160
2658
|
}]
|
|
2161
2659
|
}], ctorParameters: () => [] });
|
|
2162
2660
|
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
light: {
|
|
2167
|
-
primary: {
|
|
2168
|
-
color: '#3535cd',
|
|
2169
|
-
inverseColor: '#0707a9',
|
|
2170
|
-
hoverColor: '#0707a9',
|
|
2171
|
-
activeColor: '#0707a9',
|
|
2172
|
-
},
|
|
2173
|
-
highlight: {
|
|
2174
|
-
background: '#e2e8f0',
|
|
2175
|
-
focusBackground: '#e2e8f0',
|
|
2176
|
-
color: '#3535cd',
|
|
2177
|
-
focusColor: '#3535cd',
|
|
2178
|
-
},
|
|
2179
|
-
},
|
|
2180
|
-
},
|
|
2181
|
-
},
|
|
2182
|
-
});
|
|
2661
|
+
function buildPreset(presetName, primary) {
|
|
2662
|
+
return { preset: presetName, primary };
|
|
2663
|
+
}
|
|
2183
2664
|
|
|
2184
|
-
const
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2665
|
+
const NavyBlueTheme = {
|
|
2666
|
+
preset: 'Aura',
|
|
2667
|
+
primary: 'indigo',
|
|
2668
|
+
};
|
|
2669
|
+
|
|
2670
|
+
const GreenTheme = {
|
|
2671
|
+
preset: 'Aura',
|
|
2672
|
+
primary: 'green',
|
|
2673
|
+
};
|
|
2674
|
+
|
|
2675
|
+
const LAYOUT_MESSAGES = {
|
|
2676
|
+
// components/top-bar/app.profile.ts
|
|
2677
|
+
'layout.profile.title': 'Profile',
|
|
2678
|
+
'layout.profile.profile.picture.alt': 'Profile picture',
|
|
2679
|
+
'layout.profile.copy.sign.up.link': 'Copy SignUp Link',
|
|
2680
|
+
'layout.profile.logout': 'Logout',
|
|
2681
|
+
'layout.profile.guest': 'Guest',
|
|
2682
|
+
'layout.profile.link.copied': 'Link Copied',
|
|
2683
|
+
'layout.profile.sign.up.link.copied': 'Sign Up Link copied.',
|
|
2684
|
+
'layout.profile.failed.copy.sign.up.link': 'Failed to copy signup link.',
|
|
2685
|
+
// components/top-bar/app.launcher.ts
|
|
2686
|
+
'layout.launcher.apps': 'Apps',
|
|
2687
|
+
'layout.launcher.applications': 'Applications',
|
|
2688
|
+
// components/top-bar/app.company-branch-selector.ts
|
|
2689
|
+
'layout.company.branch.selector.title': 'Switch Company & Branch',
|
|
2690
|
+
'layout.company.branch.selector.select.company': 'Select Company',
|
|
2691
|
+
'layout.company.branch.selector.branch': 'Branch',
|
|
2692
|
+
'layout.company.branch.selector.select.branch': 'Select Branch',
|
|
2693
|
+
'layout.company.branch.selector.switch.button': 'Switch',
|
|
2694
|
+
'layout.company.branch.selector.no.company': 'No Company',
|
|
2695
|
+
'layout.company.branch.selector.branch.required': 'Branch Required',
|
|
2696
|
+
'layout.company.branch.selector.please.select.branch': 'Please select a branch',
|
|
2697
|
+
// components/top-bar/app.searchbar.ts
|
|
2698
|
+
'layout.topbar.search': 'Search',
|
|
2699
|
+
'layout.topbar.search.placeholder': 'Search for content...',
|
|
2700
|
+
// components/app.topbar.ts
|
|
2701
|
+
'layout.topbar.toggle.menu': 'Toggle menu',
|
|
2702
|
+
'layout.topbar.toggle.dark.mode': 'Toggle dark mode',
|
|
2703
|
+
'layout.topbar.open.theme.settings': 'Open theme settings',
|
|
2704
|
+
'layout.topbar.more.options': 'More options',
|
|
2705
|
+
// components/app.configurator.ts
|
|
2706
|
+
'layout.configurator.primary': 'Primary',
|
|
2707
|
+
'layout.configurator.surface': 'Surface',
|
|
2708
|
+
'layout.configurator.presets': 'Presets',
|
|
2709
|
+
'layout.configurator.menu.mode': 'Menu Mode',
|
|
2710
|
+
'layout.configurator.menu.mode.static': 'Static',
|
|
2711
|
+
'layout.configurator.menu.mode.overlay': 'Overlay',
|
|
2712
|
+
'layout.configurator.menu.mode.topbar': 'Topbar',
|
|
2713
|
+
// components/app.footer.ts
|
|
2714
|
+
'layout.footer.by': 'by',
|
|
2715
|
+
// app-menu.config.ts (main app menu configuration)
|
|
2716
|
+
'menu.dashboard': 'Dashboard',
|
|
2717
|
+
'menu.administration': 'Administration',
|
|
2718
|
+
'menu.iam': 'IAM',
|
|
2719
|
+
'menu.storage': 'Storage',
|
|
2720
|
+
'menu.forms': 'Forms',
|
|
2721
|
+
'menu.email': 'Email',
|
|
2722
|
+
'menu.event.manager': 'Event Manager',
|
|
2723
|
+
'menu.task.manager': 'Task Manager',
|
|
2724
|
+
'menu.notifications': 'Notifications',
|
|
2725
|
+
'menu.localization': 'Localization',
|
|
2726
|
+
'menu.components': 'Components',
|
|
2727
|
+
'menu.components.display': 'Display',
|
|
2728
|
+
'menu.components.menu': 'Menu',
|
|
2729
|
+
'menu.components.form': 'Form',
|
|
2730
|
+
'menu.components.overlay': 'Overlay',
|
|
2731
|
+
'menu.components.data': 'Data',
|
|
2732
|
+
'menu.components.panel': 'Panel',
|
|
2733
|
+
'menu.components.media': 'Media',
|
|
2734
|
+
'menu.components.misc': 'Directives',
|
|
2735
|
+
'menu.projects': 'Projects',
|
|
2736
|
+
'menu.projects.active': 'Active Projects',
|
|
2737
|
+
'menu.projects.archived': 'Archived',
|
|
2738
|
+
'menu.projects.reports': 'Reports',
|
|
2739
|
+
// Those are layout part from notification module, but we put them here to avoid circular dependency between layout and notification module
|
|
2740
|
+
// components/notification-bell/notification-bell.component.ts
|
|
2741
|
+
'notification.title': 'Notifications',
|
|
2742
|
+
// components/notification-list/notification-list.component.ts
|
|
2743
|
+
'notification.mark.all.read': 'Mark all read',
|
|
2744
|
+
'notification.view.all': 'View all notifications',
|
|
2745
|
+
'notification.empty': 'No notifications',
|
|
2746
|
+
// components/notification-item/notification-item.component.ts
|
|
2747
|
+
'notification.time.just.now': 'Just now',
|
|
2748
|
+
'notification.time.minutes.ago': '{{count}}m ago',
|
|
2749
|
+
'notification.time.hours.ago': '{{count}}h ago',
|
|
2750
|
+
'notification.time.days.ago': '{{count}}d ago',
|
|
2751
|
+
};
|
|
2204
2752
|
|
|
2205
2753
|
// Components
|
|
2206
2754
|
|
|
@@ -2208,5 +2756,5 @@ const GreenTheme = definePreset(Material, {
|
|
|
2208
2756
|
* Generated bundle index. Do not edit.
|
|
2209
2757
|
*/
|
|
2210
2758
|
|
|
2211
|
-
export { AppCompanyBranchSelector, AppConfigurator, AppFloatingConfigurator, AppFooter, AppLauncher, AppLayout, AppMenu, AppMenuitem, AppProfile, AppSidebar, AppTopbar, GreenTheme, LAYOUT_AUTH_API, LAYOUT_AUTH_STATE, LayoutPersistenceService, LayoutService, NavyBlueTheme, filterAppsByPermissions, filterMenuByPermissions };
|
|
2759
|
+
export { AppCompanyBranchSelector, AppConfigurator, AppFloatingConfigurator, AppFooter, AppLauncher, AppLayout, AppMenu, AppMenuitem, AppProfile, AppSearchbar, AppSidebar, AppTopbar, GreenTheme, LAYOUT_AUTH_API, LAYOUT_AUTH_STATE, LAYOUT_IS_RTL, LAYOUT_LANGUAGE_SELECTOR, LAYOUT_LANGUAGE_SELECTOR_PANEL, LAYOUT_MESSAGES, LAYOUT_NOTIFICATION_BELL, LAYOUT_SEARCHBAR, LAYOUT_SEARCH_ADAPTER, LayoutPersistenceService, LayoutService, NavyBlueTheme, buildPreset, filterAppsByPermissions, filterMenuByPermissions };
|
|
2212
2760
|
//# sourceMappingURL=flusys-ng-layout.mjs.map
|