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