@memberjunction/ng-explorer-core 5.4.1 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.d.ts.map +1 -1
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js +7 -6
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js.map +1 -1
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js +2 -2
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.js +16 -5
- package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.js +13 -3
- package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
- package/dist/lib/shell/components/header/app-switcher.component.d.ts +4 -0
- package/dist/lib/shell/components/header/app-switcher.component.d.ts.map +1 -1
- package/dist/lib/shell/components/header/app-switcher.component.js +15 -6
- package/dist/lib/shell/components/header/app-switcher.component.js.map +1 -1
- package/dist/lib/shell/shell.component.d.ts +2 -0
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +28 -15
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/single-list-detail/single-list-detail.component.d.ts.map +1 -1
- package/dist/lib/single-list-detail/single-list-detail.component.js +4 -3
- package/dist/lib/single-list-detail/single-list-detail.component.js.map +1 -1
- package/dist/lib/single-record/single-record.component.d.ts +1 -0
- package/dist/lib/single-record/single-record.component.d.ts.map +1 -1
- package/dist/lib/single-record/single-record.component.js +3 -1
- package/dist/lib/single-record/single-record.component.js.map +1 -1
- package/dist/lib/user-notifications/user-notifications.component.js +3 -3
- package/dist/lib/user-notifications/user-notifications.component.js.map +1 -1
- package/dist/lib/user-profile/user-profile.component.d.ts.map +1 -1
- package/dist/lib/user-profile/user-profile.component.js +3 -2
- package/dist/lib/user-profile/user-profile.component.js.map +1 -1
- package/package.json +34 -34
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Component, Input, Output, EventEmitter, HostListener, ViewChild } from '@angular/core';
|
|
2
|
+
import { UUIDsEqual } from '@memberjunction/global';
|
|
2
3
|
import * as i0 from "@angular/core";
|
|
3
4
|
import * as i1 from "@memberjunction/ng-base-application";
|
|
4
5
|
import * as i2 from "@memberjunction/ng-explorer-settings";
|
|
@@ -35,11 +36,11 @@ function AppSwitcherComponent_Conditional_5_For_2_Template(rf, ctx) { if (rf & 1
|
|
|
35
36
|
const app_r5 = ctx.$implicit;
|
|
36
37
|
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
37
38
|
i0.ɵɵstyleProp("--item-color", app_r5.GetColor());
|
|
38
|
-
i0.ɵɵclassProp("active",
|
|
39
|
+
i0.ɵɵclassProp("active", ctx_r1.IsActiveApp(app_r5))("loading", ctx_r1.IsLoadingApp(app_r5));
|
|
39
40
|
i0.ɵɵadvance();
|
|
40
|
-
i0.ɵɵconditional(
|
|
41
|
+
i0.ɵɵconditional(ctx_r1.IsLoadingApp(app_r5) ? 1 : -1);
|
|
41
42
|
i0.ɵɵadvance();
|
|
42
|
-
i0.ɵɵconditional(
|
|
43
|
+
i0.ɵɵconditional(!ctx_r1.IsLoadingApp(app_r5) ? 2 : -1);
|
|
43
44
|
i0.ɵɵadvance(2);
|
|
44
45
|
i0.ɵɵtextInterpolate(app_r5.Name);
|
|
45
46
|
} }
|
|
@@ -101,10 +102,18 @@ export class AppSwitcherComponent {
|
|
|
101
102
|
*/
|
|
102
103
|
selectApp(app) {
|
|
103
104
|
this.showDropdown = false;
|
|
104
|
-
if (app.ID
|
|
105
|
+
if (!UUIDsEqual(app.ID, this.activeApp?.ID) || this.isViewingSystemTab) {
|
|
105
106
|
this.appSelected.emit(app.ID);
|
|
106
107
|
}
|
|
107
108
|
}
|
|
109
|
+
/** Case-insensitive UUID check whether an app is the currently active app. */
|
|
110
|
+
IsActiveApp(app) {
|
|
111
|
+
return UUIDsEqual(app.ID, this.activeApp?.ID);
|
|
112
|
+
}
|
|
113
|
+
/** Case-insensitive UUID check whether an app is the one currently loading. */
|
|
114
|
+
IsLoadingApp(app) {
|
|
115
|
+
return UUIDsEqual(app.ID, this.loadingAppId);
|
|
116
|
+
}
|
|
108
117
|
/**
|
|
109
118
|
* Close dropdown when clicking outside
|
|
110
119
|
*/
|
|
@@ -171,7 +180,7 @@ export class AppSwitcherComponent {
|
|
|
171
180
|
}
|
|
172
181
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AppSwitcherComponent, [{
|
|
173
182
|
type: Component,
|
|
174
|
-
args: [{ standalone: false, selector: 'mj-app-switcher', template: "<div class=\"app-switcher-container\" [class.loading]=\"isLoading\">\n <div class=\"app-switcher-button\"\n (click)=\"toggleDropdown()\">\n <!-- Show spinner when loading, otherwise show app icon -->\n @if (isLoading) {\n <i class=\"app-icon loading-spinner fa-solid fa-spinner fa-spin\"></i>\n }\n @if (!isLoading) {\n <i class=\"app-icon\" [class]=\"activeApp?.Icon || 'fa-solid fa-cube'\"\n [style.color]=\"activeApp?.GetColor()\"\n [style.--active-color]=\"activeApp?.GetColor()\"></i>\n }\n <i class=\"dropdown-arrow fa-solid fa-chevron-down\"></i>\n </div>\n\n @if (showDropdown) {\n <div class=\"app-switcher-dropdown\">\n @for (app of apps; track app) {\n <div\n class=\"app-switcher-item\"\n [class.active]=\"app
|
|
183
|
+
args: [{ standalone: false, selector: 'mj-app-switcher', template: "<div class=\"app-switcher-container\" [class.loading]=\"isLoading\">\n <div class=\"app-switcher-button\"\n (click)=\"toggleDropdown()\">\n <!-- Show spinner when loading, otherwise show app icon -->\n @if (isLoading) {\n <i class=\"app-icon loading-spinner fa-solid fa-spinner fa-spin\"></i>\n }\n @if (!isLoading) {\n <i class=\"app-icon\" [class]=\"activeApp?.Icon || 'fa-solid fa-cube'\"\n [style.color]=\"activeApp?.GetColor()\"\n [style.--active-color]=\"activeApp?.GetColor()\"></i>\n }\n <i class=\"dropdown-arrow fa-solid fa-chevron-down\"></i>\n </div>\n\n @if (showDropdown) {\n <div class=\"app-switcher-dropdown\">\n @for (app of apps; track app) {\n <div\n class=\"app-switcher-item\"\n [class.active]=\"IsActiveApp(app)\"\n [class.loading]=\"IsLoadingApp(app)\"\n [style.--item-color]=\"app.GetColor()\"\n (click)=\"selectApp(app)\">\n @if (IsLoadingApp(app)) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n @if (!IsLoadingApp(app)) {\n <i [class]=\"app.Icon || 'fa-solid fa-cube'\"></i>\n }\n <span>{{ app.Name }}</span>\n </div>\n }\n <!-- Divider -->\n <div class=\"app-switcher-divider\"></div>\n <!-- Configure option -->\n <div class=\"app-switcher-item configure-item\" (click)=\"openConfigDialog()\">\n <i class=\"fa-solid fa-gear\"></i>\n <span>Configure...</span>\n </div>\n </div>\n }\n</div>\n\n<!-- App Configuration Dialog -->\n<mj-user-app-config\n #appConfigDialog\n [(ShowDialog)]=\"showConfigDialog\"\n (ConfigSaved)=\"onConfigSaved()\">\n</mj-user-app-config>\n", styles: [".app-switcher-container {\n position: relative;\n margin-left: -2px; /* Fine-tune alignment when no nav-bar icons to the left */\n}\n\n.app-switcher-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.15s;\n user-select: none;\n position: relative;\n}\n.app-switcher-button .app-icon {\n font-size: 20px;\n width: 24px;\n text-align: center;\n position: relative;\n}\n/* Color underline under the icon only, not the caret */\n.app-switcher-button .app-icon::after {\n content: \"\";\n position: absolute;\n bottom: -6px;\n left: 2px;\n right: 2px;\n height: 3px;\n border-radius: 2px;\n background-color: var(--active-color, #757575);\n}\n.app-switcher-button:hover {\n background: var(--mj-bg-surface-hover);\n}\n.app-switcher-button .dropdown-arrow {\n font-size: 10px;\n color: var(--mj-text-disabled);\n}\n\n.app-switcher-dropdown {\n position: absolute;\n top: calc(100% + 8px);\n left: 0;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: var(--mj-shadow-lg);\n min-width: 220px;\n width: max-content;\n overflow: hidden;\n z-index: 10000;\n}\n.app-switcher-dropdown .app-switcher-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n cursor: pointer;\n transition: all 0.15s;\n font-weight: 500;\n color: var(--mj-text-secondary);\n position: relative;\n white-space: nowrap;\n}\n.app-switcher-dropdown .app-switcher-item::before {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 8px;\n bottom: 8px;\n width: 3px;\n border-radius: 2px;\n background-color: var(--item-color, #757575);\n opacity: 0.8;\n transition: all 0.15s;\n}\n.app-switcher-dropdown .app-switcher-item i {\n font-size: 18px;\n color: var(--item-color, #757575);\n width: 20px;\n text-align: center;\n opacity: 1;\n transition: all 0.15s;\n}\n.app-switcher-dropdown .app-switcher-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n.app-switcher-dropdown .app-switcher-item:hover::before {\n opacity: 1;\n}\n.app-switcher-dropdown .app-switcher-item.active {\n background: color-mix(in srgb, var(--item-color, #1976d2) 10%, var(--mj-bg-surface-elevated));\n color: var(--item-color, #1976d2);\n font-weight: 600;\n}\n.app-switcher-dropdown .app-switcher-item.active::before {\n width: 4px;\n left: 3px;\n opacity: 1;\n}\n/* Configure option - no color line, neutral gray styling */\n.app-switcher-dropdown .app-switcher-item.configure-item {\n color: var(--mj-text-secondary);\n}\n.app-switcher-dropdown .app-switcher-item.configure-item::before {\n display: none;\n}\n.app-switcher-dropdown .app-switcher-item.configure-item i {\n color: var(--mj-text-muted);\n}\n.app-switcher-dropdown .app-switcher-item.configure-item:hover {\n color: var(--mj-text-primary);\n}\n.app-switcher-dropdown .app-switcher-item.configure-item:hover i {\n color: var(--mj-text-primary);\n}\n.app-switcher-dropdown .app-switcher-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 12px;\n}\n\n/* Loading state for app switcher button */\n.app-switcher-button .loading-spinner {\n color: #757575;\n}\n.app-switcher-button .loading-spinner::after {\n /* Hide the underline when showing spinner */\n display: none;\n}\n\n/* Loading state for dropdown items */\n.app-switcher-dropdown .app-switcher-item.loading {\n pointer-events: none;\n opacity: 0.7;\n}\n.app-switcher-dropdown .app-switcher-item.loading i {\n color: var(--item-color, #757575);\n}\n\n/* Mobile: full-width dropdown with scroll */\n@media (max-width: 600px) {\n .app-switcher-dropdown {\n position: fixed;\n top: 60px;\n left: 0;\n right: 0;\n width: 100%;\n min-width: unset;\n height: calc(100vh - 60px);\n overflow-y: auto;\n border-radius: 0;\n border-top: none;\n padding: 0.25rem 0;\n }\n\n .app-switcher-dropdown .app-switcher-item {\n padding: 14px 20px;\n gap: 14px;\n min-height: 48px;\n font-size: 0.9375rem;\n }\n\n .app-switcher-dropdown .app-switcher-item i {\n font-size: 20px;\n width: 24px;\n }\n}\n"] }]
|
|
175
184
|
}], () => [{ type: i1.ApplicationManager }], { activeApp: [{
|
|
176
185
|
type: Input
|
|
177
186
|
}], isViewingSystemTab: [{
|
|
@@ -187,5 +196,5 @@ export class AppSwitcherComponent {
|
|
|
187
196
|
type: HostListener,
|
|
188
197
|
args: ['document:click', ['$event']]
|
|
189
198
|
}] }); })();
|
|
190
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AppSwitcherComponent, { className: "AppSwitcherComponent", filePath: "src/lib/shell/components/header/app-switcher.component.ts", lineNumber:
|
|
199
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AppSwitcherComponent, { className: "AppSwitcherComponent", filePath: "src/lib/shell/components/header/app-switcher.component.ts", lineNumber: 16 }); })();
|
|
191
200
|
//# sourceMappingURL=app-switcher.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-switcher.component.js","sourceRoot":"","sources":["../../../../../src/lib/shell/components/header/app-switcher.component.ts","../../../../../src/lib/shell/components/header/app-switcher.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;;;;;;
|
|
1
|
+
{"version":3,"file":"app-switcher.component.js","sourceRoot":"","sources":["../../../../../src/lib/shell/components/header/app-switcher.component.ts","../../../../../src/lib/shell/components/header/app-switcher.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, HostListener, ViewChild } from '@angular/core';\nimport { ApplicationManager, BaseApplication } from '@memberjunction/ng-base-application';\nimport { UserAppConfigComponent } from '@memberjunction/ng-explorer-settings';\nimport { UUIDsEqual } from '@memberjunction/global';\n\n/**\n * App switcher dropdown in the header.\n * Displays current app and allows switching between apps.\n */\n@Component({\n standalone: false,\n selector: 'mj-app-switcher',\n templateUrl: './app-switcher.component.html',\n styleUrls: ['./app-switcher.component.css']\n})\nexport class AppSwitcherComponent {\n @Input() activeApp: BaseApplication | null = null;\n @Input() isViewingSystemTab = false;\n /** ID of the app currently being loaded (shows loading indicator) */\n @Input() loadingAppId: string | null = null;\n @Output() appSelected = new EventEmitter<string>();\n\n @ViewChild('appConfigDialog') appConfigDialog!: UserAppConfigComponent;\n\n showDropdown = false;\n showConfigDialog = false;\n\n constructor(private appManager: ApplicationManager) {}\n\n /**\n * Check if the app switcher should show loading state\n */\n get isLoading(): boolean {\n return this.loadingAppId !== null;\n }\n\n /**\n * Get applications that should appear in the app switcher dropdown\n * (NavigationStyle = 'App Switcher' or 'Both')\n */\n get apps(): BaseApplication[] {\n return this.appManager.GetAppSwitcherApps();\n }\n\n /**\n * Toggle dropdown visibility\n */\n toggleDropdown(): void {\n this.showDropdown = !this.showDropdown;\n }\n\n /**\n * Select an application\n * When viewing a system tab, always emit to allow returning to the app\n */\n selectApp(app: BaseApplication): void {\n this.showDropdown = false;\n if (!UUIDsEqual(app.ID, this.activeApp?.ID) || this.isViewingSystemTab) {\n this.appSelected.emit(app.ID);\n }\n }\n\n /** Case-insensitive UUID check whether an app is the currently active app. */\n IsActiveApp(app: BaseApplication): boolean {\n return UUIDsEqual(app.ID, this.activeApp?.ID);\n }\n\n /** Case-insensitive UUID check whether an app is the one currently loading. */\n IsLoadingApp(app: BaseApplication): boolean {\n return UUIDsEqual(app.ID, this.loadingAppId);\n }\n\n /**\n * Close dropdown when clicking outside\n */\n @HostListener('document:click', ['$event'])\n onClickOutside(event: MouseEvent): void {\n const target = event.target as HTMLElement;\n if (!target.closest('.app-switcher-container') && this.showDropdown) {\n this.showDropdown = false;\n }\n }\n\n /**\n * Open the app configuration dialog\n */\n openConfigDialog(): void {\n this.showDropdown = false;\n this.showConfigDialog = true;\n // Use setTimeout to ensure ViewChild is available\n setTimeout(() => {\n if (this.appConfigDialog) {\n this.appConfigDialog.Open();\n }\n }, 0);\n }\n\n /**\n * Handle when config is saved - reload the app list\n */\n onConfigSaved(): void {\n // The ApplicationManager will be refreshed by the dialog\n // Force a re-render by triggering change detection\n this.showConfigDialog = false;\n }\n}\n","<div class=\"app-switcher-container\" [class.loading]=\"isLoading\">\n <div class=\"app-switcher-button\"\n (click)=\"toggleDropdown()\">\n <!-- Show spinner when loading, otherwise show app icon -->\n @if (isLoading) {\n <i class=\"app-icon loading-spinner fa-solid fa-spinner fa-spin\"></i>\n }\n @if (!isLoading) {\n <i class=\"app-icon\" [class]=\"activeApp?.Icon || 'fa-solid fa-cube'\"\n [style.color]=\"activeApp?.GetColor()\"\n [style.--active-color]=\"activeApp?.GetColor()\"></i>\n }\n <i class=\"dropdown-arrow fa-solid fa-chevron-down\"></i>\n </div>\n\n @if (showDropdown) {\n <div class=\"app-switcher-dropdown\">\n @for (app of apps; track app) {\n <div\n class=\"app-switcher-item\"\n [class.active]=\"IsActiveApp(app)\"\n [class.loading]=\"IsLoadingApp(app)\"\n [style.--item-color]=\"app.GetColor()\"\n (click)=\"selectApp(app)\">\n @if (IsLoadingApp(app)) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n @if (!IsLoadingApp(app)) {\n <i [class]=\"app.Icon || 'fa-solid fa-cube'\"></i>\n }\n <span>{{ app.Name }}</span>\n </div>\n }\n <!-- Divider -->\n <div class=\"app-switcher-divider\"></div>\n <!-- Configure option -->\n <div class=\"app-switcher-item configure-item\" (click)=\"openConfigDialog()\">\n <i class=\"fa-solid fa-gear\"></i>\n <span>Configure...</span>\n </div>\n </div>\n }\n</div>\n\n<!-- App Configuration Dialog -->\n<mj-user-app-config\n #appConfigDialog\n [(ShowDialog)]=\"showConfigDialog\"\n (ConfigSaved)=\"onConfigSaved()\">\n</mj-user-app-config>\n"]}
|
|
@@ -376,6 +376,8 @@ export declare class ShellComponent implements OnInit, OnDestroy, AfterViewInit
|
|
|
376
376
|
/**
|
|
377
377
|
* Redirect to the first available app (fallback)
|
|
378
378
|
*/
|
|
379
|
+
/** Case-insensitive UUID check whether an app is the currently active app. */
|
|
380
|
+
IsActiveApp(app: BaseApplication): boolean;
|
|
379
381
|
private redirectToFirstApp;
|
|
380
382
|
static ɵfac: i0.ɵɵFactoryDeclaration<ShellComponent, never>;
|
|
381
383
|
static ɵcmp: i0.ɵɵComponentDeclaration<ShellComponent, "mj-shell", never, {}, {}, never, never, false, never>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.component.d.ts","sourceRoot":"","sources":["../../../src/lib/shell/shell.component.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"shell.component.d.ts","sourceRoot":"","sources":["../../../src/lib/shell/shell.component.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { NavigationEnd } from '@angular/router';
|
|
|
3
3
|
import { combineLatest, Subject } from 'rxjs';
|
|
4
4
|
import { filter, takeUntil } from 'rxjs/operators';
|
|
5
5
|
import { Metadata, LogStatus, StartupManager, CompositeKey } from '@memberjunction/core';
|
|
6
|
-
import { MJEventType, MJGlobal, uuidv4 } from '@memberjunction/global';
|
|
6
|
+
import { MJEventType, MJGlobal, uuidv4, UUIDsEqual } from '@memberjunction/global';
|
|
7
7
|
import { EventCodes, SYSTEM_APP_ID } from '@memberjunction/ng-shared';
|
|
8
8
|
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
9
9
|
import { getActiveTheme } from './loading-themes';
|
|
@@ -37,7 +37,7 @@ function ShellComponent_Conditional_5_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
|
37
37
|
const app_r3 = ctx.$implicit;
|
|
38
38
|
const ctx_r3 = i0.ɵɵnextContext(2);
|
|
39
39
|
i0.ɵɵstyleProp("--app-color", app_r3.GetColor());
|
|
40
|
-
i0.ɵɵclassProp("active",
|
|
40
|
+
i0.ɵɵclassProp("active", ctx_r3.IsActiveApp(app_r3));
|
|
41
41
|
i0.ɵɵproperty("title", app_r3.Name);
|
|
42
42
|
i0.ɵɵadvance();
|
|
43
43
|
i0.ɵɵclassMap(app_r3.Icon);
|
|
@@ -79,7 +79,7 @@ function ShellComponent_Conditional_15_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
|
79
79
|
const app_r7 = ctx.$implicit;
|
|
80
80
|
const ctx_r3 = i0.ɵɵnextContext(2);
|
|
81
81
|
i0.ɵɵstyleProp("--app-color", app_r7.GetColor());
|
|
82
|
-
i0.ɵɵclassProp("active",
|
|
82
|
+
i0.ɵɵclassProp("active", ctx_r3.IsActiveApp(app_r7));
|
|
83
83
|
i0.ɵɵproperty("title", app_r7.Name);
|
|
84
84
|
i0.ɵɵadvance();
|
|
85
85
|
i0.ɵɵclassMap(app_r7.Icon);
|
|
@@ -359,7 +359,7 @@ export class ShellComponent {
|
|
|
359
359
|
*/
|
|
360
360
|
get leftOfSwitcherApps() {
|
|
361
361
|
return this.appManager.GetNavBarApps('Left of App Switcher')
|
|
362
|
-
.filter(app => !(app.HideNavBarIconWhenActive && app.ID
|
|
362
|
+
.filter(app => !(app.HideNavBarIconWhenActive && UUIDsEqual(app.ID, this.activeApp?.ID)));
|
|
363
363
|
}
|
|
364
364
|
/**
|
|
365
365
|
* Get Nav Bar apps positioned to the left of the user menu
|
|
@@ -367,7 +367,7 @@ export class ShellComponent {
|
|
|
367
367
|
*/
|
|
368
368
|
get leftOfUserMenuApps() {
|
|
369
369
|
return this.appManager.GetNavBarApps('Left of User Menu')
|
|
370
|
-
.filter(app => !(app.HideNavBarIconWhenActive && app.ID
|
|
370
|
+
.filter(app => !(app.HideNavBarIconWhenActive && UUIDsEqual(app.ID, this.activeApp?.ID)));
|
|
371
371
|
}
|
|
372
372
|
constructor(appManager, workspaceManager, layoutManager, tabService, navigationService, route, router, authBase, cdr, userAvatarService, settingsDialogService, viewContainerRef, titleService, developerModeService, commandPaletteService, themeService) {
|
|
373
373
|
this.appManager = appManager;
|
|
@@ -586,6 +586,15 @@ export class ShellComponent {
|
|
|
586
586
|
await this.loadSearchableEntities();
|
|
587
587
|
this.initialized = true;
|
|
588
588
|
this.waitingForFirstResource = true;
|
|
589
|
+
// Trigger initial URL sync: the Configuration BehaviorSubject already emitted
|
|
590
|
+
// its value when the shell subscribed (line above), but this.initialized was false
|
|
591
|
+
// at that point so syncUrlWithWorkspace() was skipped. Now that we're initialized,
|
|
592
|
+
// manually trigger the first URL sync to set the browser URL from workspace state.
|
|
593
|
+
const initConfig = this.workspaceManager.GetConfiguration();
|
|
594
|
+
if (initConfig && initConfig.activeTabId) {
|
|
595
|
+
await this.syncActiveAppWithTab(initConfig);
|
|
596
|
+
this.syncUrlWithWorkspace(initConfig);
|
|
597
|
+
}
|
|
589
598
|
// Force change detection to sync Angular's expected values after all async
|
|
590
599
|
// state changes (apps loaded, searchableEntities populated, etc.) to prevent
|
|
591
600
|
// NG0100 ExpressionChangedAfterItHasBeenCheckedError in dev mode
|
|
@@ -628,7 +637,7 @@ export class ShellComponent {
|
|
|
628
637
|
// 3. Either NOT in URL-based navigation mode, OR this IS a URL-based tab
|
|
629
638
|
const shouldSetActiveApp = this.initialized &&
|
|
630
639
|
app &&
|
|
631
|
-
currentActiveApp?.ID
|
|
640
|
+
!UUIDsEqual(currentActiveApp?.ID, request.ApplicationId) &&
|
|
632
641
|
(!this.urlBasedNavigation || isUrlBasedTab);
|
|
633
642
|
if (shouldSetActiveApp) {
|
|
634
643
|
await this.appManager.SetActiveApp(request.ApplicationId);
|
|
@@ -670,7 +679,7 @@ export class ShellComponent {
|
|
|
670
679
|
this.cdr.detectChanges();
|
|
671
680
|
// Check if active app needs to be updated
|
|
672
681
|
const currentActiveApp = this.appManager.GetActiveApp();
|
|
673
|
-
if (currentActiveApp?.ID
|
|
682
|
+
if (!UUIDsEqual(currentActiveApp?.ID, tabAppId)) {
|
|
674
683
|
// Update the active app to match the tab's application
|
|
675
684
|
await this.appManager.SetActiveApp(tabAppId);
|
|
676
685
|
}
|
|
@@ -871,7 +880,7 @@ export class ShellComponent {
|
|
|
871
880
|
if (!app)
|
|
872
881
|
return false;
|
|
873
882
|
const appMatches = tabAppName.toLowerCase() === app.Name.toLowerCase() ||
|
|
874
|
-
tab.applicationId
|
|
883
|
+
UUIDsEqual(tab.applicationId, app.ID);
|
|
875
884
|
const navMatches = tabNavItemName.toLowerCase() === navItemName.toLowerCase();
|
|
876
885
|
return appMatches && navMatches;
|
|
877
886
|
}) || null;
|
|
@@ -885,7 +894,7 @@ export class ShellComponent {
|
|
|
885
894
|
// First, try to find a tab with isAppDefault for this app
|
|
886
895
|
const defaultTab = tabs.find(tab => {
|
|
887
896
|
const tabConfig = tab.configuration || {};
|
|
888
|
-
return tab.applicationId
|
|
897
|
+
return UUIDsEqual(tab.applicationId, app.ID) && tabConfig['isAppDefault'] === true;
|
|
889
898
|
});
|
|
890
899
|
if (defaultTab) {
|
|
891
900
|
return defaultTab;
|
|
@@ -894,7 +903,7 @@ export class ShellComponent {
|
|
|
894
903
|
// This handles the case where the default tab was replaced when navigating away
|
|
895
904
|
const navItems = await app.GetNavItems();
|
|
896
905
|
if (navItems.length === 0) {
|
|
897
|
-
return tabs.find(tab => tab.applicationId
|
|
906
|
+
return tabs.find(tab => UUIDsEqual(tab.applicationId, app.ID)) || null;
|
|
898
907
|
}
|
|
899
908
|
return null;
|
|
900
909
|
}
|
|
@@ -1122,7 +1131,7 @@ export class ShellComponent {
|
|
|
1122
1131
|
}
|
|
1123
1132
|
// For non-Custom resources, match by ResourceType + RecordID
|
|
1124
1133
|
if (item.ResourceType && item.RecordID) {
|
|
1125
|
-
return item.ResourceType.toLowerCase() === resourceType && item.RecordID
|
|
1134
|
+
return item.ResourceType.toLowerCase() === resourceType && UUIDsEqual(item.RecordID, recordId);
|
|
1126
1135
|
}
|
|
1127
1136
|
return false;
|
|
1128
1137
|
});
|
|
@@ -1511,7 +1520,7 @@ export class ShellComponent {
|
|
|
1511
1520
|
const app = this.appManager.GetAppById(appId);
|
|
1512
1521
|
if (!app) {
|
|
1513
1522
|
// Get app info from all system apps to show the name
|
|
1514
|
-
const systemApp = this.appManager.GetAllSystemApps().find(a => a.ID
|
|
1523
|
+
const systemApp = this.appManager.GetAllSystemApps().find(a => UUIDsEqual(a.ID, appId));
|
|
1515
1524
|
const appName = systemApp?.Name || 'this application';
|
|
1516
1525
|
// Clear loading indicator before showing dialog
|
|
1517
1526
|
this.loadingAppId = null;
|
|
@@ -1625,7 +1634,7 @@ export class ShellComponent {
|
|
|
1625
1634
|
const tabRequest = await app.CreateDefaultTab();
|
|
1626
1635
|
if (tabRequest) {
|
|
1627
1636
|
// Set the app as active first if it isn't already
|
|
1628
|
-
if (this.activeApp?.ID
|
|
1637
|
+
if (!UUIDsEqual(this.activeApp?.ID, app.ID)) {
|
|
1629
1638
|
await this.appManager.SetActiveApp(app.ID);
|
|
1630
1639
|
}
|
|
1631
1640
|
// Open in new tab by adding to workspace
|
|
@@ -2294,7 +2303,7 @@ export class ShellComponent {
|
|
|
2294
2303
|
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
2295
2304
|
}
|
|
2296
2305
|
// Timeout - try to get from system apps as fallback
|
|
2297
|
-
const systemApp = this.appManager.GetAllSystemApps().find(a => a.ID
|
|
2306
|
+
const systemApp = this.appManager.GetAllSystemApps().find(a => UUIDsEqual(a.ID, appId));
|
|
2298
2307
|
if (systemApp) {
|
|
2299
2308
|
await this.navigateToApp(systemApp);
|
|
2300
2309
|
this.appAccessDialog?.completeProcessing();
|
|
@@ -2324,6 +2333,10 @@ export class ShellComponent {
|
|
|
2324
2333
|
/**
|
|
2325
2334
|
* Redirect to the first available app (fallback)
|
|
2326
2335
|
*/
|
|
2336
|
+
/** Case-insensitive UUID check whether an app is the currently active app. */
|
|
2337
|
+
IsActiveApp(app) {
|
|
2338
|
+
return UUIDsEqual(app.ID, this.activeApp?.ID);
|
|
2339
|
+
}
|
|
2327
2340
|
async redirectToFirstApp(apps) {
|
|
2328
2341
|
if (apps.length > 0) {
|
|
2329
2342
|
const firstApp = apps[0];
|
|
@@ -2459,7 +2472,7 @@ export class ShellComponent {
|
|
|
2459
2472
|
}
|
|
2460
2473
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ShellComponent, [{
|
|
2461
2474
|
type: Component,
|
|
2462
|
-
args: [{ standalone: false, selector: 'mj-shell', template: "<div class=\"shell-container\" [class.hidden]=\"loading\" [class.tabs-visible]=\"tabBarVisible\" kendoDialogContainer>\n <!-- Header -->\n <header class=\"shell-header\">\n <!-- MJ Logo -->\n <div class=\"mj-logo\" title=\"MemberJunction\"></div>\n\n <!-- Mobile Hamburger Button -->\n <button class=\"hamburger-btn\" (click)=\"toggleMobileNav()\" title=\"Menu\">\n <i class=\"fa-solid fa-bars\"></i>\n </button>\n\n <!-- Nav Bar Apps: Left of App Switcher -->\n @if (leftOfSwitcherApps.length > 0) {\n <div class=\"nav-bar-apps left-of-switcher\">\n @for (app of leftOfSwitcherApps; track app) {\n <button\n class=\"nav-bar-app-btn\"\n [class.active]=\"app.ID === activeApp?.ID\"\n [title]=\"app.Name\"\n [style.--app-color]=\"app.GetColor()\"\n (click)=\"onNavBarAppClick(app, $event)\"\n (dblclick)=\"onNavBarAppDblClick(app, $event)\">\n <i [class]=\"app.Icon\"></i>\n </button>\n }\n </div>\n }\n\n <!-- App Switcher -->\n <mj-app-switcher\n [activeApp]=\"activeApp\"\n [isViewingSystemTab]=\"isViewingSystemTab\"\n [loadingAppId]=\"loadingAppId\"\n (appSelected)=\"onAppSwitch($event)\">\n </mj-app-switcher>\n\n <!-- App Navigation (desktop only) -->\n @if (activeApp) {\n <mj-app-nav\n class=\"desktop-nav\"\n [app]=\"activeApp\"\n (navItemClick)=\"onNavItemClick($event)\"\n (navItemDismiss)=\"onNavItemDismiss($event)\">\n </mj-app-nav>\n }\n\n <!-- Spacer -->\n <div class=\"spacer\"></div>\n\n <!-- Actions (search, notifications, user menu) -->\n <div class=\"header-actions\">\n <button class=\"icon-btn desktop-only search-toggle-btn\" title=\"Search\" (click)=\"toggleSearch()\">\n <i class=\"fa-solid fa-search\"></i>\n </button>\n <button class=\"icon-btn desktop-only notification-btn\" title=\"Notifications\" (click)=\"showNotifications()\">\n <i class=\"fa-solid fa-bell\"></i>\n @if (unreadNotificationCount > 0) {\n <span class=\"notification-badge\">\n {{ unreadNotificationCount > 99 ? '99+' : unreadNotificationCount }}\n </span>\n }\n </button>\n\n <!-- Nav Bar Apps: Left of User Menu -->\n @if (leftOfUserMenuApps.length > 0) {\n <div class=\"nav-bar-apps left-of-user-menu\">\n @for (app of leftOfUserMenuApps; track app) {\n <button\n class=\"nav-bar-app-btn\"\n [class.active]=\"app.ID === activeApp?.ID\"\n [title]=\"app.Name\"\n [style.--app-color]=\"app.GetColor()\"\n (click)=\"onNavBarAppClick(app, $event)\"\n (dblclick)=\"onNavBarAppDblClick(app, $event)\">\n <i [class]=\"app.Icon\"></i>\n </button>\n }\n </div>\n }\n\n <div class=\"user-menu\">\n <button class=\"avatar-btn\" (click)=\"toggleUserMenu($event)\">\n @if (userImageURL) {\n <img [src]=\"userImageURL\" alt=\"User avatar\" class=\"avatar-img\" />\n } @else {\n <div class=\"icon-fallback\">\n <i [class]=\"userIconClass || 'fa-solid fa-user'\"></i>\n </div>\n }\n </button>\n <!-- User Context Menu (Dynamic) -->\n @if (userMenuVisible) {\n <div class=\"user-context-menu\"\n [class.menu-fade]=\"getUserMenuOptions()?.animationStyle === 'fade'\"\n [class.menu-slide]=\"getUserMenuOptions()?.animationStyle === 'slide'\">\n <!-- User Header -->\n @if (getUserMenuOptions()?.showUserName) {\n <div class=\"user-menu-header\">\n <div class=\"user-info\">\n <span class=\"user-name\">{{ getUserDisplayInfo()?.name || userName }}</span>\n @if (getUserMenuOptions()?.showUserEmail && getUserDisplayInfo()?.email) {\n <span class=\"user-email\">\n {{ getUserDisplayInfo()?.email }}\n </span>\n }\n </div>\n </div>\n <div class=\"user-menu-divider\"></div>\n }\n <!-- Dynamic Menu Items -->\n @for (element of userMenuElements; track element) {\n <!-- Divider -->\n @if (isMenuDivider(element)) {\n <div class=\"user-menu-divider\"></div>\n }\n <!-- Menu Item -->\n @if (!isMenuDivider(element)) {\n @if (asMenuItem(element); as item) {\n @if (item.id === 'toggle-theme') {\n <div class=\"user-menu-item theme-toggle-item\"\n [title]=\"item.tooltip || ''\"\n (click)=\"onUserMenuItemClick(item.id)\">\n <i [class]=\"item.icon\"></i>\n <span class=\"menu-label\">{{ item.label }}</span>\n <div class=\"theme-toggle-track\" [class.dark]=\"IsDarkMode\">\n <div class=\"theme-toggle-thumb\">\n <i [class]=\"IsDarkMode ? 'fa-solid fa-moon' : 'fa-solid fa-sun'\" class=\"theme-toggle-icon\"></i>\n </div>\n </div>\n </div>\n } @else {\n <div class=\"user-menu-item\"\n [class.disabled]=\"!item.enabled\"\n [class.danger]=\"item.cssClass === 'danger'\"\n [style.color]=\"item.color || null\"\n [title]=\"item.tooltip || ''\"\n (click)=\"item.enabled && onUserMenuItemClick(item.id)\">\n <i [class]=\"item.icon\"></i>\n <span class=\"menu-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"menu-shortcut\">{{ item.shortcut }}</span>\n }\n </div>\n }\n }\n }\n }\n </div>\n }\n </div>\n </div>\n </header>\n\n <!-- Search Popup -->\n @if (isSearchOpen) {\n <div class=\"search-popup-overlay\" (click)=\"closeSearch()\"></div>\n }\n <div class=\"search-popup\" [class.open]=\"isSearchOpen\">\n <div class=\"search-popup-content\" (click)=\"$event.stopPropagation()\">\n <kendo-dropdownlist\n [data]=\"searchableEntities\"\n textField=\"Name\"\n valueField=\"ID\"\n class=\"search-entity-dropdown\"\n [(ngModel)]=\"selectedEntity\">\n </kendo-dropdownlist>\n <input\n type=\"text\"\n #searchInput\n placeholder=\"Search...\"\n kendoTextBox\n class=\"search-input\"\n (keydown.enter)=\"onSearch($event)\"\n />\n <button class=\"search-submit-btn\" (click)=\"onSearch($event)\" title=\"Search\">\n <i class=\"fa-solid fa-search\"></i>\n </button>\n </div>\n </div>\n\n <!-- Mobile Navigation Drawer -->\n @if (mobileNavOpen) {\n <div class=\"mobile-nav-overlay\" (click)=\"closeMobileNav()\"></div>\n }\n <div class=\"mobile-nav-drawer\" [class.open]=\"mobileNavOpen\">\n <div class=\"mobile-nav-header\">\n <span>Navigation</span>\n <button class=\"close-btn\" (click)=\"closeMobileNav()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n @if (activeApp) {\n <div class=\"mobile-nav-content\">\n <div class=\"mobile-nav-section-title\">{{ activeApp.Name }}</div>\n <mj-app-nav\n [app]=\"activeApp\"\n (navItemClick)=\"onNavItemClick($event)\"\n (navItemDismiss)=\"onNavItemDismiss($event)\">\n </mj-app-nav>\n </div>\n }\n <div class=\"mobile-nav-footer\">\n <button class=\"mobile-nav-action\" title=\"Search\" (click)=\"toggleSearch(); closeMobileNav()\">\n <i class=\"fa-solid fa-search\"></i>\n <span>Search</span>\n </button>\n <button class=\"mobile-nav-action\" title=\"Notifications\" (click)=\"showNotifications(); closeMobileNav()\">\n <i class=\"fa-solid fa-bell\"></i>\n <span>Notifications</span>\n @if (unreadNotificationCount > 0) {\n <span class=\"notification-badge-mobile\">\n {{ unreadNotificationCount > 99 ? '99+' : unreadNotificationCount }}\n </span>\n }\n </button>\n </div>\n </div>\n\n <!-- Tab Container - with dynamic tab bar visibility -->\n <mj-tab-container\n [class.hide-tab-bar]=\"!tabBarVisible\"\n (firstResourceLoadComplete)=\"onFirstResourceLoadComplete()\"\n (layoutInitError)=\"handleLayoutError()\">\n </mj-tab-container>\n</div>\n\n<!-- Loading State -->\n@if (loading) {\n <div class=\"shell-loading\">\n <mj-loading\n [text]=\"currentLoadingText\"\n [textColor]=\"currentLoadingTextColor\"\n [logoColor]=\"currentLoadingColor\"\n [logoGradient]=\"currentLoadingGradient\"\n [animation]=\"currentLoadingAnimation\"\n size=\"large\">\n </mj-loading>\n </div>\n}\n\n<!-- App Access Error Dialog -->\n<mj-app-access-dialog\n #appAccessDialog\n (result)=\"onAppAccessDialogResult($event)\">\n</mj-app-access-dialog>\n\n<!-- Command Palette -->\n<mj-command-palette (AppSelected)=\"onAppSwitch($event)\"></mj-command-palette>\n", styles: [":host {\n display: block;\n height: 100vh;\n width: 100%;\n overflow: hidden;\n}\n\n/* MJ Logo - inline SVG as data URI from MJ-Mark-Only-Transparent.svg */\n.mj-logo {\n width: 32px;\n height: 18px;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='230' height='128' viewBox='0 0 230 128'%3E%3Cpath d='M0 0 C1.197 0.668 2.394 1.336 3.592 2.004 C12.234 6.837 20.792 11.805 29.327 16.824 C37.26 21.476 45.269 25.98 53.312 30.438 C53.91 30.769 54.507 31.1 55.122 31.441 C61.234 34.87 61.234 34.87 67.438 38.125 C69.709 37.16 69.709 37.16 72.258 35.652 C73.255 35.09 74.253 34.527 75.281 33.948 C76.364 33.325 77.447 32.703 78.562 32.062 C79.685 31.426 80.808 30.789 81.965 30.133 C90.435 25.322 98.84 20.401 107.239 15.469 C118.242 9.027 129.394 2.857 140.566 -3.286 C142.41 -4.306 144.248 -5.338 146.079 -6.382 C146.869 -6.832 147.659 -7.282 148.473 -7.746 C149.148 -8.136 149.824 -8.526 150.52 -8.927 C156.448 -11.856 163.036 -11.676 169.188 -9.625 C175.638 -6.152 178.915 -2.712 181.438 4.125 C181.951 7.259 181.946 10.308 181.892 13.479 C181.892 14.377 181.893 15.276 181.893 16.201 C181.891 19.145 181.86 22.088 181.828 25.031 C181.821 27.082 181.815 29.134 181.811 31.185 C181.796 36.564 181.757 41.943 181.712 47.322 C181.671 52.819 181.653 58.316 181.633 63.812 C181.59 74.584 181.522 85.354 181.438 96.125 C178.573 94.739 175.729 93.326 172.901 91.867 C170.644 90.732 168.349 89.671 166.018 88.7 C162.628 87.127 160.12 85.736 157.438 83.125 C155.741 78.082 155.893 73.244 156.047 68.004 C156.047 66.534 156.041 65.064 156.031 63.595 C156.018 59.745 156.076 55.899 156.15 52.049 C156.213 48.114 156.207 44.18 156.207 40.244 C156.217 32.536 156.303 24.832 156.438 17.125 C152.485 19.335 148.534 21.546 144.582 23.758 C143.476 24.376 142.37 24.995 141.231 25.632 C131.895 30.857 122.601 36.151 113.321 41.472 C107.737 44.673 102.15 47.868 96.562 51.062 C95.965 51.404 95.367 51.746 94.751 52.098 C86.826 56.629 78.889 61.14 70.938 65.625 C62.527 70.37 54.133 75.144 45.75 79.938 C45.166 80.271 44.581 80.605 43.979 80.95 C37.987 84.375 31.996 87.803 26.011 91.241 C14.366 97.93 2.707 104.594 -9.062 111.062 C-9.97 111.565 -10.878 112.067 -11.814 112.585 C-12.649 113.04 -13.484 113.496 -14.344 113.965 C-15.066 114.36 -15.789 114.756 -16.533 115.164 C-22.269 117.881 -28.602 117.949 -34.625 116.125 C-39.398 114.15 -42.881 110.491 -45.562 106.125 C-45.562 104.805 -45.562 103.485 -45.562 102.125 C-45.022 101.835 -44.481 101.545 -43.924 101.246 C-32.122 94.897 -20.456 88.363 -8.907 81.566 C-0.017 76.35 8.975 71.333 18.003 66.362 C24.427 62.82 30.777 59.186 37.065 55.408 C39.438 54.125 39.438 54.125 41.438 54.125 C41.438 53.465 41.438 52.805 41.438 52.125 C40.704 51.843 39.971 51.561 39.216 51.271 C36.702 50.234 34.368 49.093 31.98 47.797 C30.7 47.102 30.7 47.102 29.395 46.394 C28.481 45.892 27.567 45.391 26.625 44.875 C25.666 44.351 24.707 43.826 23.719 43.286 C14.477 38.202 5.37 32.897 -3.719 27.547 C-8.103 24.968 -12.509 22.431 -16.928 19.911 C-17.89 19.362 -17.89 19.362 -18.871 18.802 C-20.435 17.909 -21.999 17.017 -23.562 16.125 C-23.525 16.942 -23.487 17.759 -23.448 18.601 C-23.106 26.338 -22.848 34.071 -22.682 41.814 C-22.593 45.794 -22.474 49.768 -22.28 53.745 C-21.06 79.461 -21.06 79.461 -25.205 85.53 C-29.148 89.11 -33.517 90.996 -38.581 92.49 C-41.159 93.316 -43.282 94.688 -45.562 96.125 C-46.222 96.125 -46.882 96.125 -47.562 96.125 C-47.655 84.546 -47.726 72.967 -47.77 61.388 C-47.791 56.01 -47.819 50.633 -47.864 45.256 C-47.908 40.062 -47.932 34.869 -47.942 29.675 C-47.949 27.698 -47.964 25.721 -47.985 23.744 C-48.015 20.965 -48.018 18.187 -48.017 15.408 C-48.031 14.599 -48.045 13.79 -48.06 12.956 C-48.013 6.348 -46.391 0.196 -41.823 -4.764 C-27.895 -17.466 -13.817 -7.747 0 0 Z' fill='%23264FAF' transform='translate(47.5625,10.875)'/%3E%3Cpath d='M0 0 C2.051 0.961 2.051 0.961 4.098 2.16 C5.278 2.843 5.278 2.843 6.481 3.541 C7.333 4.043 8.185 4.545 9.062 5.062 C10.954 6.16 12.846 7.258 14.738 8.355 C16.255 9.24 16.255 9.24 17.803 10.142 C22.848 13.074 27.913 15.969 32.979 18.863 C34.753 19.877 36.525 20.892 38.297 21.907 C44.243 25.311 50.205 28.681 56.203 31.992 C57.347 32.625 58.491 33.258 59.669 33.911 C61.837 35.108 64.008 36.299 66.183 37.484 C67.154 38.02 68.125 38.557 69.125 39.109 C69.973 39.573 70.821 40.037 71.695 40.515 C74.149 42.096 76.015 43.87 78 46 C74.984 51.808 72.395 55.457 66.25 58.25 C54.159 61.04 44.857 55.778 34.625 49.75 C32.781 48.681 30.938 47.612 29.094 46.543 C28.114 45.972 27.134 45.402 26.125 44.814 C21.068 41.878 15.989 38.98 10.91 36.082 C9.13 35.065 7.35 34.048 5.57 33.03 C-5.598 26.648 -16.794 20.315 -28 14 C-28 13.34 -28 12.68 -28 12 C-24.284 9.789 -20.551 7.61 -16.812 5.438 C-15.757 4.809 -14.702 4.181 -13.615 3.533 C-12.083 2.649 -12.083 2.649 -10.52 1.746 C-9.582 1.196 -8.645 0.646 -7.679 0.08 C-4.518 -1.194 -3.196 -1.069 0 0 Z' fill='%23264FAF' transform='translate(150,69)'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n flex-shrink: 0;\n}\n\n.shell-container {\n display: flex;\n flex-direction: column;\n height: 100vh;\n width: 100%;\n overflow: hidden;\n}\n\n/* Hide shell container while loading - allows tab container to render and load\n first resource in background while shell loading indicator is visible */\n.shell-container.hidden {\n visibility: hidden;\n position: absolute;\n pointer-events: none;\n}\n\nmj-tab-container {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n transition: all 0.2s ease-in-out;\n}\n\n/* Hide Golden Layout tab headers when only one tab */\nmj-tab-container.hide-tab-bar ::ng-deep .lm_header {\n display: none;\n}\n\n/* Show tab headers when multiple tabs */\nmj-tab-container:not(.hide-tab-bar) ::ng-deep .lm_header {\n opacity: 1;\n max-height: 40px;\n transition: opacity 0.2s ease-in-out, max-height 0.2s ease-in-out;\n}\n\n/* Adjust content area height when tabs hidden */\nmj-tab-container.hide-tab-bar ::ng-deep .lm_content {\n height: 100% !important;\n}\n\n/* Ensure smooth transitions */\nmj-tab-container ::ng-deep .lm_stack {\n transition: all 0.2s ease-in-out;\n}\n\n.shell-header {\n height: 60px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n padding: 0 16px;\n gap: 16px;\n box-shadow: var(--mj-shadow-sm);\n flex-shrink: 0;\n}\n\n/* Nav Bar Apps - permanent app icons in the header */\n.nav-bar-apps {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.nav-bar-apps.left-of-switcher {\n /* No extra margin - header gap handles spacing */\n /* This prevents the app switcher from shifting when icons are hidden */\n}\n\n.nav-bar-apps.left-of-user-menu {\n margin-right: 8px;\n}\n\n.nav-bar-app-btn {\n --app-color: #757575;\n width: 40px;\n height: 40px;\n border-radius: 10px;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n font-size: 18px;\n transition: all 0.15s ease;\n position: relative;\n}\n\n.nav-bar-app-btn:hover {\n background: color-mix(in srgb, var(--app-color) 15%, transparent);\n color: var(--app-color);\n}\n\n.nav-bar-app-btn.active {\n background: color-mix(in srgb, var(--app-color) 20%, transparent);\n color: var(--app-color);\n}\n\n.nav-bar-app-btn.active::after {\n content: '';\n position: absolute;\n bottom: 6px;\n left: 50%;\n transform: translateX(-50%);\n width: 16px;\n height: 3px;\n background: var(--app-color);\n border-radius: 2px;\n}\n\n.nav-bar-app-btn i {\n transition: transform 0.15s ease;\n margin-top: 2px; /* Align with app switcher icon */\n}\n\n.nav-bar-app-btn:hover i {\n transform: scale(1.1);\n}\n\n.spacer {\n flex: 1;\n}\n\n.header-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.icon-btn {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n background: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n font-size: 18px;\n transition: background 0.15s;\n}\n\n.icon-btn:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Notification button with badge */\n.notification-btn {\n position: relative;\n}\n\n.notification-badge {\n position: absolute;\n top: 4px;\n right: 4px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n background: #e53935;\n color: white;\n font-size: 10px;\n font-weight: 600;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n.user-menu {\n position: relative;\n}\n\n.user-menu .avatar-btn {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: box-shadow 0.15s, transform 0.15s;\n padding: 0;\n overflow: hidden;\n}\n\n.user-menu .avatar-btn:hover {\n transform: scale(1.05);\n}\n\n/* Icon fallback styling - shows gray circle with icon */\n.user-menu .avatar-btn .icon-fallback {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 2px solid var(--mj-border-default);\n background: var(--mj-bg-surface-hover);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: border-color 0.15s;\n}\n\n.user-menu .avatar-btn:hover .icon-fallback {\n border-color: #1976d2;\n}\n\n.user-menu .avatar-btn .icon-fallback i {\n color: var(--mj-text-muted);\n font-size: 16px;\n}\n\n/* Avatar image - replaces the gray circle entirely */\n.user-menu .avatar-btn .avatar-img {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n object-fit: cover;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);\n}\n\n.user-menu .avatar-btn:hover .avatar-img {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);\n}\n\n/* User Menu Header */\n.user-menu-header {\n padding: 12px 16px;\n background: var(--mj-bg-surface-sunken);\n}\n\n.user-menu-header .user-info {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.user-menu-header .user-name {\n font-weight: 600;\n font-size: 14px;\n color: var(--mj-text-primary);\n}\n\n.user-menu-header .user-email {\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n/* User Context Menu */\n.user-context-menu {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 8px;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: var(--mj-shadow-lg);\n min-width: 180px;\n z-index: 10000;\n overflow: hidden;\n}\n\n.user-menu-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n cursor: pointer;\n font-size: 14px;\n color: var(--mj-text-primary);\n transition: background 0.15s;\n}\n\n.user-menu-item i {\n width: 18px;\n text-align: center;\n color: var(--mj-text-muted);\n font-size: 14px;\n}\n\n.user-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.user-menu-item.danger {\n color: #c62828;\n}\n\n.user-menu-item.danger i {\n color: #c62828;\n}\n\n.user-menu-item.danger:hover {\n background: color-mix(in srgb, #c62828 10%, var(--mj-bg-surface-elevated));\n}\n\n.user-menu-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n/* Menu item disabled state */\n.user-menu-item.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.user-menu-item.disabled:hover {\n background: transparent;\n}\n\n/* Theme Toggle Switch */\n.theme-toggle-item {\n cursor: pointer;\n}\n\n.theme-toggle-track {\n position: relative;\n width: 40px;\n height: 22px;\n border-radius: 11px;\n background: var(--mj-bg-surface-active);\n margin-left: auto;\n transition: background 0.2s ease;\n flex-shrink: 0;\n}\n\n.theme-toggle-track.dark {\n background: #5b6abf;\n}\n\n.theme-toggle-thumb {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--mj-bg-surface);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform 0.2s ease;\n}\n\n.theme-toggle-track.dark .theme-toggle-thumb {\n transform: translateX(18px);\n}\n\n.theme-toggle-icon {\n font-size: 10px;\n color: var(--mj-text-muted);\n}\n\n.theme-toggle-track.dark .theme-toggle-icon {\n color: #c4b5fd;\n}\n\n/* Menu shortcut hint */\n.user-menu-item .menu-shortcut {\n margin-left: auto;\n font-size: 11px;\n color: var(--mj-text-disabled);\n font-family: monospace;\n}\n\n/* Menu animations */\n.user-context-menu.menu-fade {\n animation: menuFadeIn 0.15s ease-out;\n}\n\n.user-context-menu.menu-slide {\n animation: menuSlideIn 0.2s ease-out;\n}\n\n@keyframes menuFadeIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes menuSlideIn {\n from {\n opacity: 0;\n transform: translateY(-16px) scale(0.95);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n.shell-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100vh;\n background: var(--mj-bg-page);\n}\n\n/* Hamburger button - hidden on desktop */\n.hamburger-btn {\n display: none;\n width: 40px;\n height: 40px;\n border-radius: 8px;\n border: none;\n background: none;\n cursor: pointer;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n font-size: 20px;\n transition: background 0.15s;\n}\n.hamburger-btn:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Mobile Navigation Overlay */\n.mobile-nav-overlay {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9998;\n}\n\n/* Mobile Navigation Drawer */\n.mobile-nav-drawer {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n width: 280px;\n max-width: 85vw;\n background: var(--mj-bg-surface);\n box-shadow: var(--mj-shadow-xl);\n z-index: 9999;\n flex-direction: column;\n transform: translateX(-100%);\n transition: transform 0.3s ease;\n}\n.mobile-nav-drawer.open {\n transform: translateX(0);\n}\n\n.mobile-nav-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n}\n.mobile-nav-header span {\n font-weight: 600;\n font-size: 16px;\n color: var(--mj-text-primary);\n}\n.mobile-nav-header .close-btn {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: none;\n background: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n font-size: 18px;\n transition: background 0.15s;\n}\n.mobile-nav-header .close-btn:hover {\n background: var(--mj-bg-surface-active);\n}\n\n.mobile-nav-content {\n flex: 1;\n overflow-y: auto;\n padding: 16px 0;\n}\n\n.mobile-nav-section-title {\n padding: 8px 20px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.mobile-nav-footer {\n border-top: 1px solid var(--mj-border-default);\n padding: 12px 16px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.mobile-nav-action {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n border: none;\n background: none;\n cursor: pointer;\n border-radius: 8px;\n color: var(--mj-text-secondary);\n font-size: 14px;\n font-weight: 500;\n transition: background 0.15s;\n width: 100%;\n text-align: left;\n}\n.mobile-nav-action:hover {\n background: var(--mj-bg-surface-hover);\n}\n.mobile-nav-action i {\n font-size: 16px;\n width: 20px;\n text-align: center;\n}\n\n/* Mobile Responsive Styles */\n@media (max-width: 768px) {\n .hamburger-btn {\n display: flex;\n }\n\n .desktop-nav {\n display: none !important;\n }\n\n .desktop-only {\n display: none !important;\n }\n\n .mobile-nav-overlay {\n display: block;\n }\n\n .mobile-nav-drawer {\n display: flex;\n }\n\n .shell-header {\n padding: 0 12px;\n gap: 8px;\n }\n}\n\n/* Settings Full-Screen Window Styles */\n::ng-deep .settings-fullscreen-window {\n /* Remove window chrome for full-screen feel */\n box-shadow: none !important;\n border: none !important;\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar {\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n padding: 12px 16px;\n}\n\n::ng-deep .settings-fullscreen-window .k-window-title {\n font-size: 18px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar-actions {\n gap: 4px;\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action {\n width: 32px;\n height: 32px;\n border-radius: 6px;\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-content {\n padding: 0;\n overflow: auto;\n background: var(--mj-bg-page);\n}\n\n/* Hide minimize/maximize buttons - only show close */\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action[title=\"Minimize\"],\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action[title=\"Maximize\"],\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action .k-i-window-minimize,\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action .k-i-window-maximize,\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action .k-i-window {\n display: none !important;\n}\n\n/* ========================================\n SEARCH POPUP\n ======================================== */\n\n.search-popup-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: 9999;\n animation: fadeIn 0.15s ease;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n.search-popup {\n position: fixed;\n top: 60px; /* Below header */\n right: 16px;\n width: 480px;\n max-width: calc(100vw - 32px);\n background: var(--mj-bg-surface-elevated);\n border-radius: 12px;\n box-shadow: var(--mj-shadow-xl);\n z-index: 10000;\n opacity: 0;\n transform: translateY(-10px);\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.search-popup.open {\n opacity: 1;\n transform: translateY(0);\n pointer-events: all;\n}\n\n.search-popup-content {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n}\n\n.search-entity-dropdown {\n min-width: 140px;\n max-width: 180px;\n flex-shrink: 0;\n}\n\n.search-input {\n flex: 1;\n min-width: 0;\n height: 40px;\n padding: 8px 12px;\n font-size: 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n outline: none;\n display: block !important;\n box-sizing: border-box;\n}\n\n.search-input:focus {\n border-color: #1976d2;\n box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1);\n}\n\n.search-submit-btn {\n width: 40px;\n height: 40px;\n border-radius: 8px;\n border: none;\n background: #1976d2;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n transition: background 0.15s;\n flex-shrink: 0;\n}\n\n.search-submit-btn:hover {\n background: #1565c0;\n}\n\n.search-submit-btn:active {\n background: #0d47a1;\n}\n\n/* Mobile notification badge */\n.notification-badge-mobile {\n position: absolute;\n top: 8px;\n right: 8px;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n background: #e53935;\n color: white;\n font-size: 11px;\n font-weight: 600;\n border-radius: 9px;\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n/* Mobile-specific search popup adjustments */\n@media (max-width: 768px) {\n .search-popup {\n top: 60px;\n left: 16px;\n right: 16px;\n width: auto;\n max-width: none;\n }\n\n .search-popup-content {\n flex-wrap: wrap;\n }\n\n .search-entity-dropdown {\n width: 100%;\n }\n\n .search-input {\n flex: 1;\n min-width: 0;\n }\n}"] }]
|
|
2475
|
+
args: [{ standalone: false, selector: 'mj-shell', template: "<div class=\"shell-container\" [class.hidden]=\"loading\" [class.tabs-visible]=\"tabBarVisible\" kendoDialogContainer>\n <!-- Header -->\n <header class=\"shell-header\">\n <!-- MJ Logo -->\n <div class=\"mj-logo\" title=\"MemberJunction\"></div>\n\n <!-- Mobile Hamburger Button -->\n <button class=\"hamburger-btn\" (click)=\"toggleMobileNav()\" title=\"Menu\">\n <i class=\"fa-solid fa-bars\"></i>\n </button>\n\n <!-- Nav Bar Apps: Left of App Switcher -->\n @if (leftOfSwitcherApps.length > 0) {\n <div class=\"nav-bar-apps left-of-switcher\">\n @for (app of leftOfSwitcherApps; track app) {\n <button\n class=\"nav-bar-app-btn\"\n [class.active]=\"IsActiveApp(app)\"\n [title]=\"app.Name\"\n [style.--app-color]=\"app.GetColor()\"\n (click)=\"onNavBarAppClick(app, $event)\"\n (dblclick)=\"onNavBarAppDblClick(app, $event)\">\n <i [class]=\"app.Icon\"></i>\n </button>\n }\n </div>\n }\n\n <!-- App Switcher -->\n <mj-app-switcher\n [activeApp]=\"activeApp\"\n [isViewingSystemTab]=\"isViewingSystemTab\"\n [loadingAppId]=\"loadingAppId\"\n (appSelected)=\"onAppSwitch($event)\">\n </mj-app-switcher>\n\n <!-- App Navigation (desktop only) -->\n @if (activeApp) {\n <mj-app-nav\n class=\"desktop-nav\"\n [app]=\"activeApp\"\n (navItemClick)=\"onNavItemClick($event)\"\n (navItemDismiss)=\"onNavItemDismiss($event)\">\n </mj-app-nav>\n }\n\n <!-- Spacer -->\n <div class=\"spacer\"></div>\n\n <!-- Actions (search, notifications, user menu) -->\n <div class=\"header-actions\">\n <button class=\"icon-btn desktop-only search-toggle-btn\" title=\"Search\" (click)=\"toggleSearch()\">\n <i class=\"fa-solid fa-search\"></i>\n </button>\n <button class=\"icon-btn desktop-only notification-btn\" title=\"Notifications\" (click)=\"showNotifications()\">\n <i class=\"fa-solid fa-bell\"></i>\n @if (unreadNotificationCount > 0) {\n <span class=\"notification-badge\">\n {{ unreadNotificationCount > 99 ? '99+' : unreadNotificationCount }}\n </span>\n }\n </button>\n\n <!-- Nav Bar Apps: Left of User Menu -->\n @if (leftOfUserMenuApps.length > 0) {\n <div class=\"nav-bar-apps left-of-user-menu\">\n @for (app of leftOfUserMenuApps; track app) {\n <button\n class=\"nav-bar-app-btn\"\n [class.active]=\"IsActiveApp(app)\"\n [title]=\"app.Name\"\n [style.--app-color]=\"app.GetColor()\"\n (click)=\"onNavBarAppClick(app, $event)\"\n (dblclick)=\"onNavBarAppDblClick(app, $event)\">\n <i [class]=\"app.Icon\"></i>\n </button>\n }\n </div>\n }\n\n <div class=\"user-menu\">\n <button class=\"avatar-btn\" (click)=\"toggleUserMenu($event)\">\n @if (userImageURL) {\n <img [src]=\"userImageURL\" alt=\"User avatar\" class=\"avatar-img\" />\n } @else {\n <div class=\"icon-fallback\">\n <i [class]=\"userIconClass || 'fa-solid fa-user'\"></i>\n </div>\n }\n </button>\n <!-- User Context Menu (Dynamic) -->\n @if (userMenuVisible) {\n <div class=\"user-context-menu\"\n [class.menu-fade]=\"getUserMenuOptions()?.animationStyle === 'fade'\"\n [class.menu-slide]=\"getUserMenuOptions()?.animationStyle === 'slide'\">\n <!-- User Header -->\n @if (getUserMenuOptions()?.showUserName) {\n <div class=\"user-menu-header\">\n <div class=\"user-info\">\n <span class=\"user-name\">{{ getUserDisplayInfo()?.name || userName }}</span>\n @if (getUserMenuOptions()?.showUserEmail && getUserDisplayInfo()?.email) {\n <span class=\"user-email\">\n {{ getUserDisplayInfo()?.email }}\n </span>\n }\n </div>\n </div>\n <div class=\"user-menu-divider\"></div>\n }\n <!-- Dynamic Menu Items -->\n @for (element of userMenuElements; track element) {\n <!-- Divider -->\n @if (isMenuDivider(element)) {\n <div class=\"user-menu-divider\"></div>\n }\n <!-- Menu Item -->\n @if (!isMenuDivider(element)) {\n @if (asMenuItem(element); as item) {\n @if (item.id === 'toggle-theme') {\n <div class=\"user-menu-item theme-toggle-item\"\n [title]=\"item.tooltip || ''\"\n (click)=\"onUserMenuItemClick(item.id)\">\n <i [class]=\"item.icon\"></i>\n <span class=\"menu-label\">{{ item.label }}</span>\n <div class=\"theme-toggle-track\" [class.dark]=\"IsDarkMode\">\n <div class=\"theme-toggle-thumb\">\n <i [class]=\"IsDarkMode ? 'fa-solid fa-moon' : 'fa-solid fa-sun'\" class=\"theme-toggle-icon\"></i>\n </div>\n </div>\n </div>\n } @else {\n <div class=\"user-menu-item\"\n [class.disabled]=\"!item.enabled\"\n [class.danger]=\"item.cssClass === 'danger'\"\n [style.color]=\"item.color || null\"\n [title]=\"item.tooltip || ''\"\n (click)=\"item.enabled && onUserMenuItemClick(item.id)\">\n <i [class]=\"item.icon\"></i>\n <span class=\"menu-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"menu-shortcut\">{{ item.shortcut }}</span>\n }\n </div>\n }\n }\n }\n }\n </div>\n }\n </div>\n </div>\n </header>\n\n <!-- Search Popup -->\n @if (isSearchOpen) {\n <div class=\"search-popup-overlay\" (click)=\"closeSearch()\"></div>\n }\n <div class=\"search-popup\" [class.open]=\"isSearchOpen\">\n <div class=\"search-popup-content\" (click)=\"$event.stopPropagation()\">\n <kendo-dropdownlist\n [data]=\"searchableEntities\"\n textField=\"Name\"\n valueField=\"ID\"\n class=\"search-entity-dropdown\"\n [(ngModel)]=\"selectedEntity\">\n </kendo-dropdownlist>\n <input\n type=\"text\"\n #searchInput\n placeholder=\"Search...\"\n kendoTextBox\n class=\"search-input\"\n (keydown.enter)=\"onSearch($event)\"\n />\n <button class=\"search-submit-btn\" (click)=\"onSearch($event)\" title=\"Search\">\n <i class=\"fa-solid fa-search\"></i>\n </button>\n </div>\n </div>\n\n <!-- Mobile Navigation Drawer -->\n @if (mobileNavOpen) {\n <div class=\"mobile-nav-overlay\" (click)=\"closeMobileNav()\"></div>\n }\n <div class=\"mobile-nav-drawer\" [class.open]=\"mobileNavOpen\">\n <div class=\"mobile-nav-header\">\n <span>Navigation</span>\n <button class=\"close-btn\" (click)=\"closeMobileNav()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n @if (activeApp) {\n <div class=\"mobile-nav-content\">\n <div class=\"mobile-nav-section-title\">{{ activeApp.Name }}</div>\n <mj-app-nav\n [app]=\"activeApp\"\n (navItemClick)=\"onNavItemClick($event)\"\n (navItemDismiss)=\"onNavItemDismiss($event)\">\n </mj-app-nav>\n </div>\n }\n <div class=\"mobile-nav-footer\">\n <button class=\"mobile-nav-action\" title=\"Search\" (click)=\"toggleSearch(); closeMobileNav()\">\n <i class=\"fa-solid fa-search\"></i>\n <span>Search</span>\n </button>\n <button class=\"mobile-nav-action\" title=\"Notifications\" (click)=\"showNotifications(); closeMobileNav()\">\n <i class=\"fa-solid fa-bell\"></i>\n <span>Notifications</span>\n @if (unreadNotificationCount > 0) {\n <span class=\"notification-badge-mobile\">\n {{ unreadNotificationCount > 99 ? '99+' : unreadNotificationCount }}\n </span>\n }\n </button>\n </div>\n </div>\n\n <!-- Tab Container - with dynamic tab bar visibility -->\n <mj-tab-container\n [class.hide-tab-bar]=\"!tabBarVisible\"\n (firstResourceLoadComplete)=\"onFirstResourceLoadComplete()\"\n (layoutInitError)=\"handleLayoutError()\">\n </mj-tab-container>\n</div>\n\n<!-- Loading State -->\n@if (loading) {\n <div class=\"shell-loading\">\n <mj-loading\n [text]=\"currentLoadingText\"\n [textColor]=\"currentLoadingTextColor\"\n [logoColor]=\"currentLoadingColor\"\n [logoGradient]=\"currentLoadingGradient\"\n [animation]=\"currentLoadingAnimation\"\n size=\"large\">\n </mj-loading>\n </div>\n}\n\n<!-- App Access Error Dialog -->\n<mj-app-access-dialog\n #appAccessDialog\n (result)=\"onAppAccessDialogResult($event)\">\n</mj-app-access-dialog>\n\n<!-- Command Palette -->\n<mj-command-palette (AppSelected)=\"onAppSwitch($event)\"></mj-command-palette>\n", styles: [":host {\n display: block;\n height: 100vh;\n width: 100%;\n overflow: hidden;\n}\n\n/* MJ Logo - inline SVG as data URI from MJ-Mark-Only-Transparent.svg */\n.mj-logo {\n width: 32px;\n height: 18px;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='230' height='128' viewBox='0 0 230 128'%3E%3Cpath d='M0 0 C1.197 0.668 2.394 1.336 3.592 2.004 C12.234 6.837 20.792 11.805 29.327 16.824 C37.26 21.476 45.269 25.98 53.312 30.438 C53.91 30.769 54.507 31.1 55.122 31.441 C61.234 34.87 61.234 34.87 67.438 38.125 C69.709 37.16 69.709 37.16 72.258 35.652 C73.255 35.09 74.253 34.527 75.281 33.948 C76.364 33.325 77.447 32.703 78.562 32.062 C79.685 31.426 80.808 30.789 81.965 30.133 C90.435 25.322 98.84 20.401 107.239 15.469 C118.242 9.027 129.394 2.857 140.566 -3.286 C142.41 -4.306 144.248 -5.338 146.079 -6.382 C146.869 -6.832 147.659 -7.282 148.473 -7.746 C149.148 -8.136 149.824 -8.526 150.52 -8.927 C156.448 -11.856 163.036 -11.676 169.188 -9.625 C175.638 -6.152 178.915 -2.712 181.438 4.125 C181.951 7.259 181.946 10.308 181.892 13.479 C181.892 14.377 181.893 15.276 181.893 16.201 C181.891 19.145 181.86 22.088 181.828 25.031 C181.821 27.082 181.815 29.134 181.811 31.185 C181.796 36.564 181.757 41.943 181.712 47.322 C181.671 52.819 181.653 58.316 181.633 63.812 C181.59 74.584 181.522 85.354 181.438 96.125 C178.573 94.739 175.729 93.326 172.901 91.867 C170.644 90.732 168.349 89.671 166.018 88.7 C162.628 87.127 160.12 85.736 157.438 83.125 C155.741 78.082 155.893 73.244 156.047 68.004 C156.047 66.534 156.041 65.064 156.031 63.595 C156.018 59.745 156.076 55.899 156.15 52.049 C156.213 48.114 156.207 44.18 156.207 40.244 C156.217 32.536 156.303 24.832 156.438 17.125 C152.485 19.335 148.534 21.546 144.582 23.758 C143.476 24.376 142.37 24.995 141.231 25.632 C131.895 30.857 122.601 36.151 113.321 41.472 C107.737 44.673 102.15 47.868 96.562 51.062 C95.965 51.404 95.367 51.746 94.751 52.098 C86.826 56.629 78.889 61.14 70.938 65.625 C62.527 70.37 54.133 75.144 45.75 79.938 C45.166 80.271 44.581 80.605 43.979 80.95 C37.987 84.375 31.996 87.803 26.011 91.241 C14.366 97.93 2.707 104.594 -9.062 111.062 C-9.97 111.565 -10.878 112.067 -11.814 112.585 C-12.649 113.04 -13.484 113.496 -14.344 113.965 C-15.066 114.36 -15.789 114.756 -16.533 115.164 C-22.269 117.881 -28.602 117.949 -34.625 116.125 C-39.398 114.15 -42.881 110.491 -45.562 106.125 C-45.562 104.805 -45.562 103.485 -45.562 102.125 C-45.022 101.835 -44.481 101.545 -43.924 101.246 C-32.122 94.897 -20.456 88.363 -8.907 81.566 C-0.017 76.35 8.975 71.333 18.003 66.362 C24.427 62.82 30.777 59.186 37.065 55.408 C39.438 54.125 39.438 54.125 41.438 54.125 C41.438 53.465 41.438 52.805 41.438 52.125 C40.704 51.843 39.971 51.561 39.216 51.271 C36.702 50.234 34.368 49.093 31.98 47.797 C30.7 47.102 30.7 47.102 29.395 46.394 C28.481 45.892 27.567 45.391 26.625 44.875 C25.666 44.351 24.707 43.826 23.719 43.286 C14.477 38.202 5.37 32.897 -3.719 27.547 C-8.103 24.968 -12.509 22.431 -16.928 19.911 C-17.89 19.362 -17.89 19.362 -18.871 18.802 C-20.435 17.909 -21.999 17.017 -23.562 16.125 C-23.525 16.942 -23.487 17.759 -23.448 18.601 C-23.106 26.338 -22.848 34.071 -22.682 41.814 C-22.593 45.794 -22.474 49.768 -22.28 53.745 C-21.06 79.461 -21.06 79.461 -25.205 85.53 C-29.148 89.11 -33.517 90.996 -38.581 92.49 C-41.159 93.316 -43.282 94.688 -45.562 96.125 C-46.222 96.125 -46.882 96.125 -47.562 96.125 C-47.655 84.546 -47.726 72.967 -47.77 61.388 C-47.791 56.01 -47.819 50.633 -47.864 45.256 C-47.908 40.062 -47.932 34.869 -47.942 29.675 C-47.949 27.698 -47.964 25.721 -47.985 23.744 C-48.015 20.965 -48.018 18.187 -48.017 15.408 C-48.031 14.599 -48.045 13.79 -48.06 12.956 C-48.013 6.348 -46.391 0.196 -41.823 -4.764 C-27.895 -17.466 -13.817 -7.747 0 0 Z' fill='%23264FAF' transform='translate(47.5625,10.875)'/%3E%3Cpath d='M0 0 C2.051 0.961 2.051 0.961 4.098 2.16 C5.278 2.843 5.278 2.843 6.481 3.541 C7.333 4.043 8.185 4.545 9.062 5.062 C10.954 6.16 12.846 7.258 14.738 8.355 C16.255 9.24 16.255 9.24 17.803 10.142 C22.848 13.074 27.913 15.969 32.979 18.863 C34.753 19.877 36.525 20.892 38.297 21.907 C44.243 25.311 50.205 28.681 56.203 31.992 C57.347 32.625 58.491 33.258 59.669 33.911 C61.837 35.108 64.008 36.299 66.183 37.484 C67.154 38.02 68.125 38.557 69.125 39.109 C69.973 39.573 70.821 40.037 71.695 40.515 C74.149 42.096 76.015 43.87 78 46 C74.984 51.808 72.395 55.457 66.25 58.25 C54.159 61.04 44.857 55.778 34.625 49.75 C32.781 48.681 30.938 47.612 29.094 46.543 C28.114 45.972 27.134 45.402 26.125 44.814 C21.068 41.878 15.989 38.98 10.91 36.082 C9.13 35.065 7.35 34.048 5.57 33.03 C-5.598 26.648 -16.794 20.315 -28 14 C-28 13.34 -28 12.68 -28 12 C-24.284 9.789 -20.551 7.61 -16.812 5.438 C-15.757 4.809 -14.702 4.181 -13.615 3.533 C-12.083 2.649 -12.083 2.649 -10.52 1.746 C-9.582 1.196 -8.645 0.646 -7.679 0.08 C-4.518 -1.194 -3.196 -1.069 0 0 Z' fill='%23264FAF' transform='translate(150,69)'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n flex-shrink: 0;\n}\n\n.shell-container {\n display: flex;\n flex-direction: column;\n height: 100vh;\n width: 100%;\n overflow: hidden;\n}\n\n/* Hide shell container while loading - allows tab container to render and load\n first resource in background while shell loading indicator is visible */\n.shell-container.hidden {\n visibility: hidden;\n position: absolute;\n pointer-events: none;\n}\n\nmj-tab-container {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n transition: all 0.2s ease-in-out;\n}\n\n/* Hide Golden Layout tab headers when only one tab */\nmj-tab-container.hide-tab-bar ::ng-deep .lm_header {\n display: none;\n}\n\n/* Show tab headers when multiple tabs */\nmj-tab-container:not(.hide-tab-bar) ::ng-deep .lm_header {\n opacity: 1;\n max-height: 40px;\n transition: opacity 0.2s ease-in-out, max-height 0.2s ease-in-out;\n}\n\n/* Adjust content area height when tabs hidden */\nmj-tab-container.hide-tab-bar ::ng-deep .lm_content {\n height: 100% !important;\n}\n\n/* Ensure smooth transitions */\nmj-tab-container ::ng-deep .lm_stack {\n transition: all 0.2s ease-in-out;\n}\n\n.shell-header {\n height: 60px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n padding: 0 16px;\n gap: 16px;\n box-shadow: var(--mj-shadow-sm);\n flex-shrink: 0;\n}\n\n/* Nav Bar Apps - permanent app icons in the header */\n.nav-bar-apps {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.nav-bar-apps.left-of-switcher {\n /* No extra margin - header gap handles spacing */\n /* This prevents the app switcher from shifting when icons are hidden */\n}\n\n.nav-bar-apps.left-of-user-menu {\n margin-right: 8px;\n}\n\n.nav-bar-app-btn {\n --app-color: #757575;\n width: 40px;\n height: 40px;\n border-radius: 10px;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n font-size: 18px;\n transition: all 0.15s ease;\n position: relative;\n}\n\n.nav-bar-app-btn:hover {\n background: color-mix(in srgb, var(--app-color) 15%, transparent);\n color: var(--app-color);\n}\n\n.nav-bar-app-btn.active {\n background: color-mix(in srgb, var(--app-color) 20%, transparent);\n color: var(--app-color);\n}\n\n.nav-bar-app-btn.active::after {\n content: '';\n position: absolute;\n bottom: 6px;\n left: 50%;\n transform: translateX(-50%);\n width: 16px;\n height: 3px;\n background: var(--app-color);\n border-radius: 2px;\n}\n\n.nav-bar-app-btn i {\n transition: transform 0.15s ease;\n margin-top: 2px; /* Align with app switcher icon */\n}\n\n.nav-bar-app-btn:hover i {\n transform: scale(1.1);\n}\n\n.spacer {\n flex: 1;\n}\n\n.header-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.icon-btn {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n background: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n font-size: 18px;\n transition: background 0.15s;\n}\n\n.icon-btn:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Notification button with badge */\n.notification-btn {\n position: relative;\n}\n\n.notification-badge {\n position: absolute;\n top: 4px;\n right: 4px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n background: #e53935;\n color: white;\n font-size: 10px;\n font-weight: 600;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n.user-menu {\n position: relative;\n}\n\n.user-menu .avatar-btn {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: box-shadow 0.15s, transform 0.15s;\n padding: 0;\n overflow: hidden;\n}\n\n.user-menu .avatar-btn:hover {\n transform: scale(1.05);\n}\n\n/* Icon fallback styling - shows gray circle with icon */\n.user-menu .avatar-btn .icon-fallback {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 2px solid var(--mj-border-default);\n background: var(--mj-bg-surface-hover);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: border-color 0.15s;\n}\n\n.user-menu .avatar-btn:hover .icon-fallback {\n border-color: #1976d2;\n}\n\n.user-menu .avatar-btn .icon-fallback i {\n color: var(--mj-text-muted);\n font-size: 16px;\n}\n\n/* Avatar image - replaces the gray circle entirely */\n.user-menu .avatar-btn .avatar-img {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n object-fit: cover;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);\n}\n\n.user-menu .avatar-btn:hover .avatar-img {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);\n}\n\n/* User Menu Header */\n.user-menu-header {\n padding: 12px 16px;\n background: var(--mj-bg-surface-sunken);\n}\n\n.user-menu-header .user-info {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.user-menu-header .user-name {\n font-weight: 600;\n font-size: 14px;\n color: var(--mj-text-primary);\n}\n\n.user-menu-header .user-email {\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n/* User Context Menu */\n.user-context-menu {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 8px;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: var(--mj-shadow-lg);\n min-width: 180px;\n z-index: 10000;\n overflow: hidden;\n}\n\n.user-menu-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n cursor: pointer;\n font-size: 14px;\n color: var(--mj-text-primary);\n transition: background 0.15s;\n}\n\n.user-menu-item i {\n width: 18px;\n text-align: center;\n color: var(--mj-text-muted);\n font-size: 14px;\n}\n\n.user-menu-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.user-menu-item.danger {\n color: #c62828;\n}\n\n.user-menu-item.danger i {\n color: #c62828;\n}\n\n.user-menu-item.danger:hover {\n background: color-mix(in srgb, #c62828 10%, var(--mj-bg-surface-elevated));\n}\n\n.user-menu-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n/* Menu item disabled state */\n.user-menu-item.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.user-menu-item.disabled:hover {\n background: transparent;\n}\n\n/* Theme Toggle Switch */\n.theme-toggle-item {\n cursor: pointer;\n}\n\n.theme-toggle-track {\n position: relative;\n width: 40px;\n height: 22px;\n border-radius: 11px;\n background: var(--mj-bg-surface-active);\n margin-left: auto;\n transition: background 0.2s ease;\n flex-shrink: 0;\n}\n\n.theme-toggle-track.dark {\n background: #5b6abf;\n}\n\n.theme-toggle-thumb {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--mj-bg-surface);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform 0.2s ease;\n}\n\n.theme-toggle-track.dark .theme-toggle-thumb {\n transform: translateX(18px);\n}\n\n.theme-toggle-icon {\n font-size: 10px;\n color: var(--mj-text-muted);\n}\n\n.theme-toggle-track.dark .theme-toggle-icon {\n color: #c4b5fd;\n}\n\n/* Menu shortcut hint */\n.user-menu-item .menu-shortcut {\n margin-left: auto;\n font-size: 11px;\n color: var(--mj-text-disabled);\n font-family: monospace;\n}\n\n/* Menu animations */\n.user-context-menu.menu-fade {\n animation: menuFadeIn 0.15s ease-out;\n}\n\n.user-context-menu.menu-slide {\n animation: menuSlideIn 0.2s ease-out;\n}\n\n@keyframes menuFadeIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes menuSlideIn {\n from {\n opacity: 0;\n transform: translateY(-16px) scale(0.95);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n.shell-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100vh;\n background: var(--mj-bg-page);\n}\n\n/* Hamburger button - hidden on desktop */\n.hamburger-btn {\n display: none;\n width: 40px;\n height: 40px;\n border-radius: 8px;\n border: none;\n background: none;\n cursor: pointer;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-secondary);\n font-size: 20px;\n transition: background 0.15s;\n}\n.hamburger-btn:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Mobile Navigation Overlay */\n.mobile-nav-overlay {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9998;\n}\n\n/* Mobile Navigation Drawer */\n.mobile-nav-drawer {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n width: 280px;\n max-width: 85vw;\n background: var(--mj-bg-surface);\n box-shadow: var(--mj-shadow-xl);\n z-index: 9999;\n flex-direction: column;\n transform: translateX(-100%);\n transition: transform 0.3s ease;\n}\n.mobile-nav-drawer.open {\n transform: translateX(0);\n}\n\n.mobile-nav-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n}\n.mobile-nav-header span {\n font-weight: 600;\n font-size: 16px;\n color: var(--mj-text-primary);\n}\n.mobile-nav-header .close-btn {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: none;\n background: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n font-size: 18px;\n transition: background 0.15s;\n}\n.mobile-nav-header .close-btn:hover {\n background: var(--mj-bg-surface-active);\n}\n\n.mobile-nav-content {\n flex: 1;\n overflow-y: auto;\n padding: 16px 0;\n}\n\n.mobile-nav-section-title {\n padding: 8px 20px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-disabled);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.mobile-nav-footer {\n border-top: 1px solid var(--mj-border-default);\n padding: 12px 16px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.mobile-nav-action {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n border: none;\n background: none;\n cursor: pointer;\n border-radius: 8px;\n color: var(--mj-text-secondary);\n font-size: 14px;\n font-weight: 500;\n transition: background 0.15s;\n width: 100%;\n text-align: left;\n}\n.mobile-nav-action:hover {\n background: var(--mj-bg-surface-hover);\n}\n.mobile-nav-action i {\n font-size: 16px;\n width: 20px;\n text-align: center;\n}\n\n/* Mobile Responsive Styles */\n@media (max-width: 768px) {\n .hamburger-btn {\n display: flex;\n }\n\n .desktop-nav {\n display: none !important;\n }\n\n .desktop-only {\n display: none !important;\n }\n\n .mobile-nav-overlay {\n display: block;\n }\n\n .mobile-nav-drawer {\n display: flex;\n }\n\n .shell-header {\n padding: 0 12px;\n gap: 8px;\n }\n}\n\n/* Settings Full-Screen Window Styles */\n::ng-deep .settings-fullscreen-window {\n /* Remove window chrome for full-screen feel */\n box-shadow: none !important;\n border: none !important;\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar {\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n padding: 12px 16px;\n}\n\n::ng-deep .settings-fullscreen-window .k-window-title {\n font-size: 18px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar-actions {\n gap: 4px;\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action {\n width: 32px;\n height: 32px;\n border-radius: 6px;\n}\n\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n::ng-deep .settings-fullscreen-window .k-window-content {\n padding: 0;\n overflow: auto;\n background: var(--mj-bg-page);\n}\n\n/* Hide minimize/maximize buttons - only show close */\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action[title=\"Minimize\"],\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action[title=\"Maximize\"],\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action .k-i-window-minimize,\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action .k-i-window-maximize,\n::ng-deep .settings-fullscreen-window .k-window-titlebar-action .k-i-window {\n display: none !important;\n}\n\n/* ========================================\n SEARCH POPUP\n ======================================== */\n\n.search-popup-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: 9999;\n animation: fadeIn 0.15s ease;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n.search-popup {\n position: fixed;\n top: 60px; /* Below header */\n right: 16px;\n width: 480px;\n max-width: calc(100vw - 32px);\n background: var(--mj-bg-surface-elevated);\n border-radius: 12px;\n box-shadow: var(--mj-shadow-xl);\n z-index: 10000;\n opacity: 0;\n transform: translateY(-10px);\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n}\n\n.search-popup.open {\n opacity: 1;\n transform: translateY(0);\n pointer-events: all;\n}\n\n.search-popup-content {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n}\n\n.search-entity-dropdown {\n min-width: 140px;\n max-width: 180px;\n flex-shrink: 0;\n}\n\n.search-input {\n flex: 1;\n min-width: 0;\n height: 40px;\n padding: 8px 12px;\n font-size: 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n outline: none;\n display: block !important;\n box-sizing: border-box;\n}\n\n.search-input:focus {\n border-color: #1976d2;\n box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1);\n}\n\n.search-submit-btn {\n width: 40px;\n height: 40px;\n border-radius: 8px;\n border: none;\n background: #1976d2;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n transition: background 0.15s;\n flex-shrink: 0;\n}\n\n.search-submit-btn:hover {\n background: #1565c0;\n}\n\n.search-submit-btn:active {\n background: #0d47a1;\n}\n\n/* Mobile notification badge */\n.notification-badge-mobile {\n position: absolute;\n top: 8px;\n right: 8px;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n background: #e53935;\n color: white;\n font-size: 11px;\n font-weight: 600;\n border-radius: 9px;\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n}\n\n/* Mobile-specific search popup adjustments */\n@media (max-width: 768px) {\n .search-popup {\n top: 60px;\n left: 16px;\n right: 16px;\n width: auto;\n max-width: none;\n }\n\n .search-popup-content {\n flex-wrap: wrap;\n }\n\n .search-entity-dropdown {\n width: 100%;\n }\n\n .search-input {\n flex: 1;\n min-width: 0;\n }\n}"] }]
|
|
2463
2476
|
}], () => [{ type: i1.ApplicationManager }, { type: i1.WorkspaceStateManager }, { type: i1.GoldenLayoutManager }, { type: i1.TabService }, { type: i2.NavigationService }, { type: i3.ActivatedRoute }, { type: i3.Router }, { type: i4.MJAuthBase }, { type: i0.ChangeDetectorRef }, { type: i5.UserAvatarService }, { type: i6.SettingsDialogService }, { type: i0.ViewContainerRef }, { type: i2.TitleService }, { type: i2.DeveloperModeService }, { type: i7.CommandPaletteService }, { type: i2.ThemeService }], { searchInput: [{
|
|
2464
2477
|
type: ViewChild,
|
|
2465
2478
|
args: ['searchInput']
|