@memberjunction/ng-explorer-core 2.121.0 → 2.122.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/README.md +0 -1
- package/dist/app-routing.module.d.ts +6 -5
- package/dist/app-routing.module.d.ts.map +1 -1
- package/dist/app-routing.module.js +266 -132
- package/dist/app-routing.module.js.map +1 -1
- package/dist/lib/app-view/application-view.component.js +10 -10
- package/dist/lib/app-view/application-view.component.js.map +1 -1
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js +11 -12
- package/dist/lib/dashboard-preferences-dialog/dashboard-preferences-dialog.component.js.map +1 -1
- package/dist/lib/data-browser-component/data-browser.component.js +8 -8
- package/dist/lib/data-browser-component/data-browser.component.js.map +1 -1
- package/dist/lib/generic-browse-list/generic-browse-list.component.js +10 -7
- package/dist/lib/generic-browse-list/generic-browse-list.component.js.map +1 -1
- package/dist/lib/generic-browser-list/generic-browser-list.component.js +14 -11
- package/dist/lib/generic-browser-list/generic-browser-list.component.js.map +1 -1
- package/dist/lib/list-view/list-view.component.js +24 -21
- package/dist/lib/list-view/list-view.component.js.map +1 -1
- package/dist/lib/navigation/navigation.component.d.ts +0 -1
- package/dist/lib/navigation/navigation.component.d.ts.map +1 -1
- package/dist/lib/navigation/navigation.component.js +11 -19
- package/dist/lib/navigation/navigation.component.js.map +1 -1
- package/dist/lib/resource-browser/resource-browser.component.js +14 -11
- package/dist/lib/resource-browser/resource-browser.component.js.map +1 -1
- package/dist/lib/resource-wrappers/artifact-resource.component.d.ts +20 -0
- package/dist/lib/resource-wrappers/artifact-resource.component.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/artifact-resource.component.js +92 -0
- package/dist/lib/resource-wrappers/artifact-resource.component.js.map +1 -0
- package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts +119 -0
- package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/chat-collections-resource.component.js +500 -0
- package/dist/lib/resource-wrappers/chat-collections-resource.component.js.map +1 -0
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts +87 -0
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js +345 -0
- package/dist/lib/resource-wrappers/chat-conversations-resource.component.js.map +1 -0
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts +67 -0
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.d.ts.map +1 -0
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.js +244 -0
- package/dist/lib/resource-wrappers/chat-tasks-resource.component.js.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts +46 -3
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.js +298 -21
- package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/list-detail-resource.component.js +1 -1
- package/dist/lib/resource-wrappers/list-detail-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/query-resource.component.js +1 -1
- package/dist/lib/resource-wrappers/query-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/record-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/record-resource.component.js +22 -5
- package/dist/lib/resource-wrappers/record-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/report-resource.component.js +1 -1
- package/dist/lib/resource-wrappers/report-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/resource-wrappers-loader.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/resource-wrappers-loader.js +15 -0
- package/dist/lib/resource-wrappers/resource-wrappers-loader.js.map +1 -1
- package/dist/lib/resource-wrappers/search-results-resource.component.js +1 -1
- package/dist/lib/resource-wrappers/search-results-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.js +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts +45 -0
- package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -0
- package/dist/lib/shell/components/header/app-nav.component.js +127 -0
- package/dist/lib/shell/components/header/app-nav.component.js.map +1 -0
- package/dist/lib/shell/components/header/app-switcher.component.d.ts +53 -0
- package/dist/lib/shell/components/header/app-switcher.component.d.ts.map +1 -0
- package/dist/lib/shell/components/header/app-switcher.component.js +190 -0
- package/dist/lib/shell/components/header/app-switcher.component.js.map +1 -0
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts +83 -0
- package/dist/lib/shell/components/tabs/component-cache-manager.d.ts.map +1 -0
- package/dist/lib/shell/components/tabs/component-cache-manager.js +175 -0
- package/dist/lib/shell/components/tabs/component-cache-manager.js.map +1 -0
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts +138 -0
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -0
- package/dist/lib/shell/components/tabs/tab-container.component.js +920 -0
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -0
- package/dist/lib/shell/services/settings-dialog.service.d.ts +28 -0
- package/dist/lib/shell/services/settings-dialog.service.d.ts.map +1 -0
- package/dist/lib/shell/services/settings-dialog.service.js +67 -0
- package/dist/lib/shell/services/settings-dialog.service.js.map +1 -0
- package/dist/lib/shell/shell.component.d.ts +166 -0
- package/dist/lib/shell/shell.component.d.ts.map +1 -0
- package/dist/lib/shell/shell.component.js +1173 -0
- package/dist/lib/shell/shell.component.js.map +1 -0
- package/dist/lib/shell/shell.module.d.ts +14 -0
- package/dist/lib/shell/shell.module.d.ts.map +1 -0
- package/dist/lib/shell/shell.module.js +42 -0
- package/dist/lib/shell/shell.module.js.map +1 -0
- package/dist/lib/single-dashboard/Components/add-item/add-item.component.js +17 -13
- package/dist/lib/single-dashboard/Components/add-item/add-item.component.js.map +1 -1
- package/dist/lib/single-dashboard/single-dashboard.component.d.ts +2 -1
- package/dist/lib/single-dashboard/single-dashboard.component.d.ts.map +1 -1
- package/dist/lib/single-dashboard/single-dashboard.component.js +6 -1
- package/dist/lib/single-dashboard/single-dashboard.component.js.map +1 -1
- package/dist/lib/single-entity/single-entity.component.js +21 -17
- package/dist/lib/single-entity/single-entity.component.js.map +1 -1
- package/dist/lib/single-list-detail/single-list-detail.component.js +36 -30
- 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 +16 -9
- package/dist/lib/single-record/single-record.component.js.map +1 -1
- package/dist/lib/single-report/single-report.component.d.ts +1 -7
- package/dist/lib/single-report/single-report.component.d.ts.map +1 -1
- package/dist/lib/single-report/single-report.component.js +16 -37
- package/dist/lib/single-report/single-report.component.js.map +1 -1
- package/dist/lib/single-view/single-view.component.d.ts +1 -2
- package/dist/lib/single-view/single-view.component.d.ts.map +1 -1
- package/dist/lib/single-view/single-view.component.js +35 -43
- package/dist/lib/single-view/single-view.component.js.map +1 -1
- package/dist/lib/style-guide-test/style-guide-test.component.js +20 -13
- package/dist/lib/style-guide-test/style-guide-test.component.js.map +1 -1
- package/dist/lib/tabbed-dashboard/tabbed-dashboard.component.d.ts +1 -1
- package/dist/lib/tabbed-dashboard/tabbed-dashboard.component.d.ts.map +1 -1
- package/dist/lib/tabbed-dashboard/tabbed-dashboard.component.js +8 -11
- package/dist/lib/tabbed-dashboard/tabbed-dashboard.component.js.map +1 -1
- package/dist/lib/user-notifications/user-notifications.component.d.ts.map +1 -1
- package/dist/lib/user-notifications/user-notifications.component.js +0 -5
- package/dist/lib/user-notifications/user-notifications.component.js.map +1 -1
- package/dist/module.d.ts +50 -46
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +23 -11
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +4 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +5 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +36 -35
|
@@ -0,0 +1,1173 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { NavigationEnd } from '@angular/router';
|
|
3
|
+
import { firstValueFrom } from 'rxjs';
|
|
4
|
+
import { filter } from 'rxjs/operators';
|
|
5
|
+
import { Metadata } from '@memberjunction/core';
|
|
6
|
+
import { MJEventType, MJGlobal, uuidv4 } from '@memberjunction/global';
|
|
7
|
+
import { EventCodes, SYSTEM_APP_ID } from '@memberjunction/ng-shared';
|
|
8
|
+
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
9
|
+
import * as i0 from "@angular/core";
|
|
10
|
+
import * as i1 from "@memberjunction/ng-base-application";
|
|
11
|
+
import * as i2 from "@memberjunction/ng-shared";
|
|
12
|
+
import * as i3 from "@angular/router";
|
|
13
|
+
import * as i4 from "@memberjunction/ng-auth-services";
|
|
14
|
+
import * as i5 from "@memberjunction/ng-user-avatar";
|
|
15
|
+
import * as i6 from "./services/settings-dialog.service";
|
|
16
|
+
import * as i7 from "@angular/common";
|
|
17
|
+
import * as i8 from "@memberjunction/ng-shared-generic";
|
|
18
|
+
import * as i9 from "./components/header/app-switcher.component";
|
|
19
|
+
import * as i10 from "./components/header/app-nav.component";
|
|
20
|
+
import * as i11 from "./components/tabs/tab-container.component";
|
|
21
|
+
function ShellComponent_div_0_div_5_button_1_Template(rf, ctx) { if (rf & 1) {
|
|
22
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
23
|
+
i0.ɵɵelementStart(0, "button", 34);
|
|
24
|
+
i0.ɵɵlistener("click", function ShellComponent_div_0_div_5_button_1_Template_button_click_0_listener($event) { const app_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.onNavBarAppClick(app_r4, $event)); })("dblclick", function ShellComponent_div_0_div_5_button_1_Template_button_dblclick_0_listener($event) { const app_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.onNavBarAppDblClick(app_r4, $event)); });
|
|
25
|
+
i0.ɵɵelement(1, "i");
|
|
26
|
+
i0.ɵɵelementEnd();
|
|
27
|
+
} if (rf & 2) {
|
|
28
|
+
const app_r4 = ctx.$implicit;
|
|
29
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
30
|
+
i0.ɵɵstyleProp("--app-color", app_r4.GetColor());
|
|
31
|
+
i0.ɵɵclassProp("active", app_r4.ID === (ctx_r1.activeApp == null ? null : ctx_r1.activeApp.ID));
|
|
32
|
+
i0.ɵɵproperty("title", app_r4.Name);
|
|
33
|
+
i0.ɵɵadvance();
|
|
34
|
+
i0.ɵɵclassMap(app_r4.Icon);
|
|
35
|
+
} }
|
|
36
|
+
function ShellComponent_div_0_div_5_Template(rf, ctx) { if (rf & 1) {
|
|
37
|
+
i0.ɵɵelementStart(0, "div", 32);
|
|
38
|
+
i0.ɵɵtemplate(1, ShellComponent_div_0_div_5_button_1_Template, 2, 7, "button", 33);
|
|
39
|
+
i0.ɵɵelementEnd();
|
|
40
|
+
} if (rf & 2) {
|
|
41
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
42
|
+
i0.ɵɵadvance();
|
|
43
|
+
i0.ɵɵproperty("ngForOf", ctx_r1.leftOfSwitcherApps);
|
|
44
|
+
} }
|
|
45
|
+
function ShellComponent_div_0_mj_app_nav_7_Template(rf, ctx) { if (rf & 1) {
|
|
46
|
+
const _r5 = i0.ɵɵgetCurrentView();
|
|
47
|
+
i0.ɵɵelementStart(0, "mj-app-nav", 35);
|
|
48
|
+
i0.ɵɵlistener("navItemClick", function ShellComponent_div_0_mj_app_nav_7_Template_mj_app_nav_navItemClick_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onNavItemClick($event)); });
|
|
49
|
+
i0.ɵɵelementEnd();
|
|
50
|
+
} if (rf & 2) {
|
|
51
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
52
|
+
i0.ɵɵproperty("app", ctx_r1.activeApp);
|
|
53
|
+
} }
|
|
54
|
+
function ShellComponent_div_0_span_14_Template(rf, ctx) { if (rf & 1) {
|
|
55
|
+
i0.ɵɵelementStart(0, "span", 36);
|
|
56
|
+
i0.ɵɵtext(1);
|
|
57
|
+
i0.ɵɵelementEnd();
|
|
58
|
+
} if (rf & 2) {
|
|
59
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
60
|
+
i0.ɵɵadvance();
|
|
61
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.unreadNotificationCount > 99 ? "99+" : ctx_r1.unreadNotificationCount, " ");
|
|
62
|
+
} }
|
|
63
|
+
function ShellComponent_div_0_div_15_button_1_Template(rf, ctx) { if (rf & 1) {
|
|
64
|
+
const _r6 = i0.ɵɵgetCurrentView();
|
|
65
|
+
i0.ɵɵelementStart(0, "button", 34);
|
|
66
|
+
i0.ɵɵlistener("click", function ShellComponent_div_0_div_15_button_1_Template_button_click_0_listener($event) { const app_r7 = i0.ɵɵrestoreView(_r6).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.onNavBarAppClick(app_r7, $event)); })("dblclick", function ShellComponent_div_0_div_15_button_1_Template_button_dblclick_0_listener($event) { const app_r7 = i0.ɵɵrestoreView(_r6).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.onNavBarAppDblClick(app_r7, $event)); });
|
|
67
|
+
i0.ɵɵelement(1, "i");
|
|
68
|
+
i0.ɵɵelementEnd();
|
|
69
|
+
} if (rf & 2) {
|
|
70
|
+
const app_r7 = ctx.$implicit;
|
|
71
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
72
|
+
i0.ɵɵstyleProp("--app-color", app_r7.GetColor());
|
|
73
|
+
i0.ɵɵclassProp("active", app_r7.ID === (ctx_r1.activeApp == null ? null : ctx_r1.activeApp.ID));
|
|
74
|
+
i0.ɵɵproperty("title", app_r7.Name);
|
|
75
|
+
i0.ɵɵadvance();
|
|
76
|
+
i0.ɵɵclassMap(app_r7.Icon);
|
|
77
|
+
} }
|
|
78
|
+
function ShellComponent_div_0_div_15_Template(rf, ctx) { if (rf & 1) {
|
|
79
|
+
i0.ɵɵelementStart(0, "div", 37);
|
|
80
|
+
i0.ɵɵtemplate(1, ShellComponent_div_0_div_15_button_1_Template, 2, 7, "button", 33);
|
|
81
|
+
i0.ɵɵelementEnd();
|
|
82
|
+
} if (rf & 2) {
|
|
83
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
84
|
+
i0.ɵɵadvance();
|
|
85
|
+
i0.ɵɵproperty("ngForOf", ctx_r1.leftOfUserMenuApps);
|
|
86
|
+
} }
|
|
87
|
+
function ShellComponent_div_0_ng_container_18_Template(rf, ctx) { if (rf & 1) {
|
|
88
|
+
i0.ɵɵelementContainerStart(0);
|
|
89
|
+
i0.ɵɵelement(1, "img", 38);
|
|
90
|
+
i0.ɵɵelementContainerEnd();
|
|
91
|
+
} if (rf & 2) {
|
|
92
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
93
|
+
i0.ɵɵadvance();
|
|
94
|
+
i0.ɵɵproperty("src", ctx_r1.userImageURL, i0.ɵɵsanitizeUrl);
|
|
95
|
+
} }
|
|
96
|
+
function ShellComponent_div_0_ng_template_19_Template(rf, ctx) { if (rf & 1) {
|
|
97
|
+
i0.ɵɵelementStart(0, "div", 39);
|
|
98
|
+
i0.ɵɵelement(1, "i");
|
|
99
|
+
i0.ɵɵelementEnd();
|
|
100
|
+
} if (rf & 2) {
|
|
101
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
102
|
+
i0.ɵɵadvance();
|
|
103
|
+
i0.ɵɵclassMap(ctx_r1.userIconClass || "fa-solid fa-user");
|
|
104
|
+
} }
|
|
105
|
+
function ShellComponent_div_0_div_21_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
106
|
+
i0.ɵɵelementStart(0, "div", 49)(1, "span", 50);
|
|
107
|
+
i0.ɵɵtext(2);
|
|
108
|
+
i0.ɵɵelementEnd()();
|
|
109
|
+
} if (rf & 2) {
|
|
110
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
111
|
+
i0.ɵɵadvance(2);
|
|
112
|
+
i0.ɵɵtextInterpolate(ctx_r1.userName);
|
|
113
|
+
} }
|
|
114
|
+
function ShellComponent_div_0_div_21_div_2_Template(rf, ctx) { if (rf & 1) {
|
|
115
|
+
i0.ɵɵelement(0, "div", 45);
|
|
116
|
+
} }
|
|
117
|
+
function ShellComponent_div_0_div_21_Template(rf, ctx) { if (rf & 1) {
|
|
118
|
+
const _r8 = i0.ɵɵgetCurrentView();
|
|
119
|
+
i0.ɵɵelementStart(0, "div", 40);
|
|
120
|
+
i0.ɵɵtemplate(1, ShellComponent_div_0_div_21_div_1_Template, 3, 1, "div", 41)(2, ShellComponent_div_0_div_21_div_2_Template, 1, 0, "div", 42);
|
|
121
|
+
i0.ɵɵelementStart(3, "div", 43);
|
|
122
|
+
i0.ɵɵlistener("click", function ShellComponent_div_0_div_21_Template_div_click_3_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onSettings()); });
|
|
123
|
+
i0.ɵɵelement(4, "i", 44);
|
|
124
|
+
i0.ɵɵelementStart(5, "span");
|
|
125
|
+
i0.ɵɵtext(6, "Settings");
|
|
126
|
+
i0.ɵɵelementEnd()();
|
|
127
|
+
i0.ɵɵelement(7, "div", 45);
|
|
128
|
+
i0.ɵɵelementStart(8, "div", 43);
|
|
129
|
+
i0.ɵɵlistener("click", function ShellComponent_div_0_div_21_Template_div_click_8_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onLogLayout()); });
|
|
130
|
+
i0.ɵɵelement(9, "i", 46);
|
|
131
|
+
i0.ɵɵelementStart(10, "span");
|
|
132
|
+
i0.ɵɵtext(11, "Log Layout (Debug)");
|
|
133
|
+
i0.ɵɵelementEnd()();
|
|
134
|
+
i0.ɵɵelement(12, "div", 45);
|
|
135
|
+
i0.ɵɵelementStart(13, "div", 47);
|
|
136
|
+
i0.ɵɵlistener("click", function ShellComponent_div_0_div_21_Template_div_click_13_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onResetLayout()); });
|
|
137
|
+
i0.ɵɵelement(14, "i", 48);
|
|
138
|
+
i0.ɵɵelementStart(15, "span");
|
|
139
|
+
i0.ɵɵtext(16, "Reset Layout");
|
|
140
|
+
i0.ɵɵelementEnd()()();
|
|
141
|
+
} if (rf & 2) {
|
|
142
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
143
|
+
i0.ɵɵadvance();
|
|
144
|
+
i0.ɵɵproperty("ngIf", ctx_r1.userName);
|
|
145
|
+
i0.ɵɵadvance();
|
|
146
|
+
i0.ɵɵproperty("ngIf", ctx_r1.userName);
|
|
147
|
+
} }
|
|
148
|
+
function ShellComponent_div_0_div_22_Template(rf, ctx) { if (rf & 1) {
|
|
149
|
+
const _r9 = i0.ɵɵgetCurrentView();
|
|
150
|
+
i0.ɵɵelementStart(0, "div", 51);
|
|
151
|
+
i0.ɵɵlistener("click", function ShellComponent_div_0_div_22_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.closeMobileNav()); });
|
|
152
|
+
i0.ɵɵelementEnd();
|
|
153
|
+
} }
|
|
154
|
+
function ShellComponent_div_0_div_29_Template(rf, ctx) { if (rf & 1) {
|
|
155
|
+
const _r10 = i0.ɵɵgetCurrentView();
|
|
156
|
+
i0.ɵɵelementStart(0, "div", 52)(1, "div", 53);
|
|
157
|
+
i0.ɵɵtext(2);
|
|
158
|
+
i0.ɵɵelementEnd();
|
|
159
|
+
i0.ɵɵelementStart(3, "mj-app-nav", 54);
|
|
160
|
+
i0.ɵɵlistener("navItemClick", function ShellComponent_div_0_div_29_Template_mj_app_nav_navItemClick_3_listener($event) { i0.ɵɵrestoreView(_r10); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onNavItemClick($event)); });
|
|
161
|
+
i0.ɵɵelementEnd()();
|
|
162
|
+
} if (rf & 2) {
|
|
163
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
164
|
+
i0.ɵɵadvance(2);
|
|
165
|
+
i0.ɵɵtextInterpolate(ctx_r1.activeApp.Name);
|
|
166
|
+
i0.ɵɵadvance();
|
|
167
|
+
i0.ɵɵproperty("app", ctx_r1.activeApp);
|
|
168
|
+
} }
|
|
169
|
+
function ShellComponent_div_0_Template(rf, ctx) { if (rf & 1) {
|
|
170
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
171
|
+
i0.ɵɵelementStart(0, "div", 3)(1, "header", 4);
|
|
172
|
+
i0.ɵɵelement(2, "div", 5);
|
|
173
|
+
i0.ɵɵelementStart(3, "button", 6);
|
|
174
|
+
i0.ɵɵlistener("click", function ShellComponent_div_0_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.toggleMobileNav()); });
|
|
175
|
+
i0.ɵɵelement(4, "i", 7);
|
|
176
|
+
i0.ɵɵelementEnd();
|
|
177
|
+
i0.ɵɵtemplate(5, ShellComponent_div_0_div_5_Template, 2, 1, "div", 8);
|
|
178
|
+
i0.ɵɵelementStart(6, "mj-app-switcher", 9);
|
|
179
|
+
i0.ɵɵlistener("appSelected", function ShellComponent_div_0_Template_mj_app_switcher_appSelected_6_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onAppSwitch($event)); });
|
|
180
|
+
i0.ɵɵelementEnd();
|
|
181
|
+
i0.ɵɵtemplate(7, ShellComponent_div_0_mj_app_nav_7_Template, 1, 1, "mj-app-nav", 10);
|
|
182
|
+
i0.ɵɵelement(8, "div", 11);
|
|
183
|
+
i0.ɵɵelementStart(9, "div", 12)(10, "button", 13);
|
|
184
|
+
i0.ɵɵelement(11, "i", 14);
|
|
185
|
+
i0.ɵɵelementEnd();
|
|
186
|
+
i0.ɵɵelementStart(12, "button", 15);
|
|
187
|
+
i0.ɵɵelement(13, "i", 16);
|
|
188
|
+
i0.ɵɵtemplate(14, ShellComponent_div_0_span_14_Template, 2, 1, "span", 17);
|
|
189
|
+
i0.ɵɵelementEnd();
|
|
190
|
+
i0.ɵɵtemplate(15, ShellComponent_div_0_div_15_Template, 2, 1, "div", 18);
|
|
191
|
+
i0.ɵɵelementStart(16, "div", 19)(17, "button", 20);
|
|
192
|
+
i0.ɵɵlistener("click", function ShellComponent_div_0_Template_button_click_17_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.toggleUserMenu($event)); });
|
|
193
|
+
i0.ɵɵtemplate(18, ShellComponent_div_0_ng_container_18_Template, 2, 1, "ng-container", 21)(19, ShellComponent_div_0_ng_template_19_Template, 2, 2, "ng-template", null, 0, i0.ɵɵtemplateRefExtractor);
|
|
194
|
+
i0.ɵɵelementEnd();
|
|
195
|
+
i0.ɵɵtemplate(21, ShellComponent_div_0_div_21_Template, 17, 2, "div", 22);
|
|
196
|
+
i0.ɵɵelementEnd()()();
|
|
197
|
+
i0.ɵɵtemplate(22, ShellComponent_div_0_div_22_Template, 1, 0, "div", 23);
|
|
198
|
+
i0.ɵɵelementStart(23, "div", 24)(24, "div", 25)(25, "span");
|
|
199
|
+
i0.ɵɵtext(26, "Navigation");
|
|
200
|
+
i0.ɵɵelementEnd();
|
|
201
|
+
i0.ɵɵelementStart(27, "button", 26);
|
|
202
|
+
i0.ɵɵlistener("click", function ShellComponent_div_0_Template_button_click_27_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.closeMobileNav()); });
|
|
203
|
+
i0.ɵɵelement(28, "i", 27);
|
|
204
|
+
i0.ɵɵelementEnd()();
|
|
205
|
+
i0.ɵɵtemplate(29, ShellComponent_div_0_div_29_Template, 4, 2, "div", 28);
|
|
206
|
+
i0.ɵɵelementStart(30, "div", 29)(31, "button", 30);
|
|
207
|
+
i0.ɵɵelement(32, "i", 14);
|
|
208
|
+
i0.ɵɵelementStart(33, "span");
|
|
209
|
+
i0.ɵɵtext(34, "Search");
|
|
210
|
+
i0.ɵɵelementEnd()();
|
|
211
|
+
i0.ɵɵelementStart(35, "button", 31);
|
|
212
|
+
i0.ɵɵelement(36, "i", 16);
|
|
213
|
+
i0.ɵɵelementStart(37, "span");
|
|
214
|
+
i0.ɵɵtext(38, "Notifications");
|
|
215
|
+
i0.ɵɵelementEnd()()()();
|
|
216
|
+
i0.ɵɵelement(39, "mj-tab-container");
|
|
217
|
+
i0.ɵɵelementEnd();
|
|
218
|
+
} if (rf & 2) {
|
|
219
|
+
const iconAvatar_r11 = i0.ɵɵreference(20);
|
|
220
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
221
|
+
i0.ɵɵclassProp("tabs-visible", ctx_r1.tabBarVisible);
|
|
222
|
+
i0.ɵɵadvance(5);
|
|
223
|
+
i0.ɵɵproperty("ngIf", ctx_r1.leftOfSwitcherApps.length > 0);
|
|
224
|
+
i0.ɵɵadvance();
|
|
225
|
+
i0.ɵɵproperty("activeApp", ctx_r1.activeApp)("isViewingSystemTab", ctx_r1.isViewingSystemTab)("loadingAppId", ctx_r1.loadingAppId);
|
|
226
|
+
i0.ɵɵadvance();
|
|
227
|
+
i0.ɵɵproperty("ngIf", ctx_r1.activeApp);
|
|
228
|
+
i0.ɵɵadvance(7);
|
|
229
|
+
i0.ɵɵproperty("ngIf", ctx_r1.unreadNotificationCount > 0);
|
|
230
|
+
i0.ɵɵadvance();
|
|
231
|
+
i0.ɵɵproperty("ngIf", ctx_r1.leftOfUserMenuApps.length > 0);
|
|
232
|
+
i0.ɵɵadvance(3);
|
|
233
|
+
i0.ɵɵproperty("ngIf", ctx_r1.userImageURL)("ngIfElse", iconAvatar_r11);
|
|
234
|
+
i0.ɵɵadvance(3);
|
|
235
|
+
i0.ɵɵproperty("ngIf", ctx_r1.userMenuVisible);
|
|
236
|
+
i0.ɵɵadvance();
|
|
237
|
+
i0.ɵɵproperty("ngIf", ctx_r1.mobileNavOpen);
|
|
238
|
+
i0.ɵɵadvance();
|
|
239
|
+
i0.ɵɵclassProp("open", ctx_r1.mobileNavOpen);
|
|
240
|
+
i0.ɵɵadvance(6);
|
|
241
|
+
i0.ɵɵproperty("ngIf", ctx_r1.activeApp);
|
|
242
|
+
i0.ɵɵadvance(10);
|
|
243
|
+
i0.ɵɵclassProp("hide-tab-bar", !ctx_r1.tabBarVisible);
|
|
244
|
+
} }
|
|
245
|
+
function ShellComponent_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
246
|
+
i0.ɵɵelementStart(0, "div", 55);
|
|
247
|
+
i0.ɵɵelement(1, "mj-loading", 56);
|
|
248
|
+
i0.ɵɵelementEnd();
|
|
249
|
+
} }
|
|
250
|
+
/**
|
|
251
|
+
* Main shell component for the new Explorer UX.
|
|
252
|
+
*
|
|
253
|
+
* Provides:
|
|
254
|
+
* - App-centric header with app switcher and nav items
|
|
255
|
+
* - Golden Layout-based tab container
|
|
256
|
+
* - Unified workspace state management
|
|
257
|
+
*/
|
|
258
|
+
export class ShellComponent {
|
|
259
|
+
appManager;
|
|
260
|
+
workspaceManager;
|
|
261
|
+
layoutManager;
|
|
262
|
+
tabService;
|
|
263
|
+
navigationService;
|
|
264
|
+
route;
|
|
265
|
+
router;
|
|
266
|
+
authBase;
|
|
267
|
+
cdr;
|
|
268
|
+
userAvatarService;
|
|
269
|
+
settingsDialogService;
|
|
270
|
+
viewContainerRef;
|
|
271
|
+
titleService;
|
|
272
|
+
subscriptions = [];
|
|
273
|
+
urlBasedNavigation = false; // Track if we're loading from a URL
|
|
274
|
+
initialNavigationComplete = false; // Track if initial navigation has completed
|
|
275
|
+
firstUrlSync = true; // Track if this is the first URL sync (for replaceUrl behavior)
|
|
276
|
+
activeApp = null;
|
|
277
|
+
loading = true;
|
|
278
|
+
initialized = false;
|
|
279
|
+
tabBarVisible = true; // Controlled by workspace manager
|
|
280
|
+
userMenuVisible = false; // User avatar context menu
|
|
281
|
+
mobileNavOpen = false; // Mobile navigation drawer
|
|
282
|
+
unreadNotificationCount = 0; // Notification badge count
|
|
283
|
+
isViewingSystemTab = false; // True when viewing a resource tab (not associated with a registered app)
|
|
284
|
+
loadingAppId = null; // ID of app currently being loaded (for app switcher loading indicator)
|
|
285
|
+
// User avatar state
|
|
286
|
+
userImageURL = '';
|
|
287
|
+
userIconClass = null;
|
|
288
|
+
userName = '';
|
|
289
|
+
/**
|
|
290
|
+
* Get Nav Bar apps positioned to the left of the app switcher
|
|
291
|
+
* Filters out apps that have HideNavBarIconWhenActive=true and are currently active
|
|
292
|
+
*/
|
|
293
|
+
get leftOfSwitcherApps() {
|
|
294
|
+
return this.appManager.GetNavBarApps('Left of App Switcher')
|
|
295
|
+
.filter(app => !(app.HideNavBarIconWhenActive && app.ID === this.activeApp?.ID));
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Get Nav Bar apps positioned to the left of the user menu
|
|
299
|
+
* Filters out apps that have HideNavBarIconWhenActive=true and are currently active
|
|
300
|
+
*/
|
|
301
|
+
get leftOfUserMenuApps() {
|
|
302
|
+
return this.appManager.GetNavBarApps('Left of User Menu')
|
|
303
|
+
.filter(app => !(app.HideNavBarIconWhenActive && app.ID === this.activeApp?.ID));
|
|
304
|
+
}
|
|
305
|
+
constructor(appManager, workspaceManager, layoutManager, tabService, navigationService, route, router, authBase, cdr, userAvatarService, settingsDialogService, viewContainerRef, titleService) {
|
|
306
|
+
this.appManager = appManager;
|
|
307
|
+
this.workspaceManager = workspaceManager;
|
|
308
|
+
this.layoutManager = layoutManager;
|
|
309
|
+
this.tabService = tabService;
|
|
310
|
+
this.navigationService = navigationService;
|
|
311
|
+
this.route = route;
|
|
312
|
+
this.router = router;
|
|
313
|
+
this.authBase = authBase;
|
|
314
|
+
this.cdr = cdr;
|
|
315
|
+
this.userAvatarService = userAvatarService;
|
|
316
|
+
this.settingsDialogService = settingsDialogService;
|
|
317
|
+
this.viewContainerRef = viewContainerRef;
|
|
318
|
+
this.titleService = titleService;
|
|
319
|
+
}
|
|
320
|
+
async ngOnInit() {
|
|
321
|
+
try {
|
|
322
|
+
MJGlobal.Instance.GetEventListener(true).subscribe(async (loginEvent) => {
|
|
323
|
+
if (loginEvent.event === MJEventType.LoggedIn) {
|
|
324
|
+
if (this.authBase.initialPath === "/") {
|
|
325
|
+
// Base route - no need to wait for NavigationEnd
|
|
326
|
+
await this.initializeShell();
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
// Deep link route - wait for NavigationEnd to ensure router URL is correct
|
|
330
|
+
this.router.events.pipe(filter((event) => event instanceof NavigationEnd), filter(() => !this.initialNavigationComplete)).subscribe(async () => {
|
|
331
|
+
this.initialNavigationComplete = true;
|
|
332
|
+
await this.initializeShell();
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
console.error('Failed to initialize shell:', error);
|
|
340
|
+
this.loading = false;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async initializeShell() {
|
|
344
|
+
// Initialize application manager (subscribes to LoggedIn event)
|
|
345
|
+
this.appManager.Initialize();
|
|
346
|
+
// Get current user
|
|
347
|
+
const md = new Metadata();
|
|
348
|
+
const user = md.CurrentUser;
|
|
349
|
+
if (!user) {
|
|
350
|
+
throw new Error('No current user found');
|
|
351
|
+
}
|
|
352
|
+
// Check the current URL to determine if we're loading from a URL-based navigation
|
|
353
|
+
const currentUrl = this.router.url;
|
|
354
|
+
this.urlBasedNavigation = currentUrl.includes('/app/') || currentUrl.includes('/resource/');
|
|
355
|
+
// Wait for workspace initialization to complete before allowing any tab operations
|
|
356
|
+
await this.workspaceManager.Initialize(user.ID);
|
|
357
|
+
// Subscribe to tab bar visibility changes
|
|
358
|
+
this.subscriptions.push(this.workspaceManager.TabBarVisible.subscribe(visible => {
|
|
359
|
+
this.tabBarVisible = visible;
|
|
360
|
+
}));
|
|
361
|
+
// Subscribe to unread notification count changes
|
|
362
|
+
this.subscriptions.push(MJNotificationService.UnreadCount$.subscribe(count => {
|
|
363
|
+
this.unreadNotificationCount = count;
|
|
364
|
+
this.cdr.detectChanges();
|
|
365
|
+
}));
|
|
366
|
+
// Subscribe to active app changes
|
|
367
|
+
this.subscriptions.push(this.appManager.ActiveApp.subscribe(async (app) => {
|
|
368
|
+
this.activeApp = app;
|
|
369
|
+
// Create default tab when app is activated ONLY if:
|
|
370
|
+
// 1. App has no tabs yet
|
|
371
|
+
// 2. We're not loading from a URL that will create its own tab
|
|
372
|
+
if (app) {
|
|
373
|
+
const existingTabs = this.workspaceManager.GetAppTabs(app.ID);
|
|
374
|
+
if (existingTabs.length === 0) {
|
|
375
|
+
// Check if we're loading from a URL that will create a tab
|
|
376
|
+
const currentUrl = this.router.url;
|
|
377
|
+
const hasResourceUrl = currentUrl.includes('/app/') ||
|
|
378
|
+
currentUrl.includes('/resource/');
|
|
379
|
+
// Only create default tab if we're NOT loading from a resource URL
|
|
380
|
+
if (!hasResourceUrl) {
|
|
381
|
+
const tabRequest = await app.CreateDefaultTab();
|
|
382
|
+
if (tabRequest) {
|
|
383
|
+
this.tabService.OpenTab(tabRequest);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}));
|
|
389
|
+
// Subscribe to applications loading - set app based on URL or default to first
|
|
390
|
+
this.subscriptions.push(this.appManager.Applications.subscribe(async (apps) => {
|
|
391
|
+
if (apps.length > 0) {
|
|
392
|
+
// Check if URL specifies an app by parsing the browser URL
|
|
393
|
+
const currentUrl = this.router.url;
|
|
394
|
+
const appMatch = currentUrl.match(/\/app\/([^\/]+)/);
|
|
395
|
+
if (appMatch) {
|
|
396
|
+
const routeAppPath = decodeURIComponent(appMatch[1]);
|
|
397
|
+
// Find the app from the URL by Path (or Name for backwards compatibility)
|
|
398
|
+
const urlApp = this.appManager.GetAppByPath(routeAppPath);
|
|
399
|
+
if (urlApp) {
|
|
400
|
+
// Set the app from URL - takes precedence over workspace restoration
|
|
401
|
+
await this.appManager.SetActiveApp(urlApp.ID);
|
|
402
|
+
// If the URL is just /app/:appName (no nav item), create default tab
|
|
403
|
+
const hasNavItem = currentUrl.match(/\/app\/[^\/]+\/[^\/]+/);
|
|
404
|
+
if (!hasNavItem) {
|
|
405
|
+
const existingTabs = this.workspaceManager.GetAppTabs(urlApp.ID);
|
|
406
|
+
if (existingTabs.length === 0) {
|
|
407
|
+
const tabRequest = await urlApp.CreateDefaultTab();
|
|
408
|
+
if (tabRequest) {
|
|
409
|
+
this.tabService.OpenTab(tabRequest);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Set default app if URL doesn't specify one AND no app is active yet
|
|
417
|
+
const currentActiveApp = this.appManager.GetActiveApp();
|
|
418
|
+
if (!appMatch && !currentActiveApp) {
|
|
419
|
+
await this.appManager.SetActiveApp(apps[0].ID);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}));
|
|
423
|
+
// Subscribe to tab open requests from TabService
|
|
424
|
+
this.subscriptions.push(this.tabService.TabRequests.subscribe(async (request) => {
|
|
425
|
+
await this.processTabRequest(request);
|
|
426
|
+
}));
|
|
427
|
+
// Replay any tab requests that were queued before we subscribed
|
|
428
|
+
// This handles the case where ResourceResolver creates requests before shell is ready
|
|
429
|
+
const queuedRequests = this.tabService.GetQueuedRequests();
|
|
430
|
+
if (queuedRequests.length > 0) {
|
|
431
|
+
for (const request of queuedRequests) {
|
|
432
|
+
await this.processTabRequest(request);
|
|
433
|
+
}
|
|
434
|
+
this.tabService.ClearQueue();
|
|
435
|
+
}
|
|
436
|
+
// Clear urlBasedNavigation flag after initial setup completes
|
|
437
|
+
// This must happen regardless of whether there were queued requests,
|
|
438
|
+
// because apps with zero nav items create tabs directly (not via ResourceResolver)
|
|
439
|
+
// and we still need URL sync to work for subsequent navigation
|
|
440
|
+
if (this.urlBasedNavigation) {
|
|
441
|
+
this.urlBasedNavigation = false;
|
|
442
|
+
}
|
|
443
|
+
// Subscribe to workspace configuration changes to sync URL and active app
|
|
444
|
+
this.subscriptions.push(this.workspaceManager.Configuration.subscribe(async (config) => {
|
|
445
|
+
if (config && this.initialized) {
|
|
446
|
+
// Sync active app with active tab's application
|
|
447
|
+
await this.syncActiveAppWithTab(config);
|
|
448
|
+
this.syncUrlWithWorkspace(config);
|
|
449
|
+
// Update browser tab title
|
|
450
|
+
this.updateBrowserTitle(config);
|
|
451
|
+
}
|
|
452
|
+
}));
|
|
453
|
+
// Subscribe to router navigation events (for browser back/forward)
|
|
454
|
+
this.subscriptions.push(this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(event => {
|
|
455
|
+
if (this.initialized) {
|
|
456
|
+
this.syncWorkspaceWithUrl(event.urlAfterRedirects || event.url);
|
|
457
|
+
}
|
|
458
|
+
}));
|
|
459
|
+
// Check for deep link parameters on initialization
|
|
460
|
+
this.handleDeepLink();
|
|
461
|
+
// Load user avatar
|
|
462
|
+
await this.loadUserAvatar(user);
|
|
463
|
+
// Listen for avatar updates from settings page
|
|
464
|
+
this.subscriptions.push(MJGlobal.Instance.GetEventListener(false).subscribe(async (updateEvent) => {
|
|
465
|
+
if (updateEvent.eventCode === EventCodes.AvatarUpdated) {
|
|
466
|
+
const md = new Metadata();
|
|
467
|
+
const currentUserInfo = md.CurrentUser;
|
|
468
|
+
const userEntity = await md.GetEntityObject('Users');
|
|
469
|
+
await userEntity.Load(currentUserInfo.ID);
|
|
470
|
+
this.applyUserAvatar(userEntity);
|
|
471
|
+
}
|
|
472
|
+
}));
|
|
473
|
+
this.initialized = true;
|
|
474
|
+
this.loading = false;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Handle deep links from URL
|
|
478
|
+
* Resource URLs like /resource/record/Companies/123 are handled by ResourceResolver
|
|
479
|
+
* Legacy ?tab= params are supported for backward compatibility
|
|
480
|
+
*/
|
|
481
|
+
handleDeepLink() {
|
|
482
|
+
const queryParams = this.route.snapshot.queryParams;
|
|
483
|
+
const tabParam = queryParams['tab'];
|
|
484
|
+
// Legacy support for ?tab= parameters
|
|
485
|
+
if (tabParam) {
|
|
486
|
+
const tabIds = Array.isArray(tabParam) ? tabParam : [tabParam];
|
|
487
|
+
if (tabIds.length > 0) {
|
|
488
|
+
this.workspaceManager.SetActiveTab(tabIds[0]);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Note: Resource URLs like /resource/record/EntityName/123 are automatically
|
|
492
|
+
// handled by the ResourceResolver which raises MJ events that the workspace
|
|
493
|
+
// manager listens to. No additional handling needed here.
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Process a tab request (from subscription or replay)
|
|
497
|
+
*/
|
|
498
|
+
async processTabRequest(request) {
|
|
499
|
+
const app = this.appManager.GetAppById(request.ApplicationId);
|
|
500
|
+
const appColor = app?.GetColor() || '#757575';
|
|
501
|
+
// Determine if this is a URL-based tab request
|
|
502
|
+
// URL-based tabs have appName/resourceType BUT NOT isAppDefault
|
|
503
|
+
// (isAppDefault indicates workspace restoration, not URL navigation)
|
|
504
|
+
const isUrlBasedTab = (request.Configuration?.appName || request.Configuration?.resourceType) &&
|
|
505
|
+
!request.Configuration?.isAppDefault;
|
|
506
|
+
const currentActiveApp = this.appManager.GetActiveApp();
|
|
507
|
+
// Only set the app as active if:
|
|
508
|
+
// 1. We're initialized (past the startup phase)
|
|
509
|
+
// 2. App is different from current
|
|
510
|
+
// 3. Either NOT in URL-based navigation mode, OR this IS a URL-based tab
|
|
511
|
+
const shouldSetActiveApp = this.initialized &&
|
|
512
|
+
app &&
|
|
513
|
+
currentActiveApp?.ID !== request.ApplicationId &&
|
|
514
|
+
(!this.urlBasedNavigation || isUrlBasedTab);
|
|
515
|
+
if (shouldSetActiveApp) {
|
|
516
|
+
await this.appManager.SetActiveApp(request.ApplicationId);
|
|
517
|
+
}
|
|
518
|
+
this.workspaceManager.OpenTab(request, appColor);
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Sync active app with the active tab's application
|
|
522
|
+
* Called when workspace configuration changes (e.g., user clicks on a tab in Golden Layout)
|
|
523
|
+
*/
|
|
524
|
+
async syncActiveAppWithTab(config) {
|
|
525
|
+
if (!config.activeTabId) {
|
|
526
|
+
this.titleService.reset();
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
// Find the active tab
|
|
530
|
+
const activeTab = config.tabs?.find(tab => tab.id === config.activeTabId);
|
|
531
|
+
if (!activeTab) {
|
|
532
|
+
this.titleService.reset();
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
// Get the tab's application ID
|
|
536
|
+
const tabAppId = activeTab.applicationId;
|
|
537
|
+
if (!tabAppId) {
|
|
538
|
+
this.titleService.setResourceName(activeTab.title || null);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
// Check if this is a system tab (not associated with a registered app)
|
|
542
|
+
if (tabAppId === SYSTEM_APP_ID) {
|
|
543
|
+
this.isViewingSystemTab = true;
|
|
544
|
+
// Don't try to set active app - SYSTEM_APP_ID has no registered app
|
|
545
|
+
// Update browser title with just the tab title (no app context)
|
|
546
|
+
this.titleService.setContext(null, activeTab.title || 'Explorer');
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
// Not a system tab - clear the flag
|
|
550
|
+
this.isViewingSystemTab = false;
|
|
551
|
+
// Check if active app needs to be updated
|
|
552
|
+
const currentActiveApp = this.appManager.GetActiveApp();
|
|
553
|
+
if (currentActiveApp?.ID !== tabAppId) {
|
|
554
|
+
// Update the active app to match the tab's application
|
|
555
|
+
await this.appManager.SetActiveApp(tabAppId);
|
|
556
|
+
}
|
|
557
|
+
// Update browser title with app and tab context
|
|
558
|
+
const app = this.appManager.GetAppById(tabAppId);
|
|
559
|
+
const appName = app?.Name || null;
|
|
560
|
+
const tabTitle = activeTab.title || null;
|
|
561
|
+
this.titleService.setContext(appName, tabTitle);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Sync URL with active tab's resource
|
|
565
|
+
*/
|
|
566
|
+
syncUrlWithWorkspace(config) {
|
|
567
|
+
// Don't sync URL during URL-based navigation initialization
|
|
568
|
+
if (this.urlBasedNavigation) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (!config.activeTabId) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
// Find the active tab
|
|
575
|
+
const activeTab = config.tabs?.find(tab => tab.id === config.activeTabId);
|
|
576
|
+
if (!activeTab) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
// Build resource URL from tab configuration
|
|
580
|
+
const resourceUrl = this.buildResourceUrl(activeTab);
|
|
581
|
+
if (resourceUrl) {
|
|
582
|
+
// Only update URL if it's different from current URL to avoid navigation loops
|
|
583
|
+
const currentUrl = this.router.url.split('?')[0];
|
|
584
|
+
const newUrl = resourceUrl.split('?')[0];
|
|
585
|
+
if (currentUrl !== newUrl) {
|
|
586
|
+
// Replace URL on first sync (initialization), push new history entries after that
|
|
587
|
+
const replaceUrl = this.firstUrlSync;
|
|
588
|
+
this.firstUrlSync = false;
|
|
589
|
+
this.router.navigateByUrl(resourceUrl, { replaceUrl });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Sync workspace state with the current URL (for browser back/forward navigation).
|
|
595
|
+
* Finds and activates the tab that matches the URL.
|
|
596
|
+
*/
|
|
597
|
+
async syncWorkspaceWithUrl(url) {
|
|
598
|
+
const config = this.workspaceManager.GetConfiguration();
|
|
599
|
+
if (!config?.tabs?.length) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
// Find the tab that matches this URL
|
|
603
|
+
const matchingTab = this.findTabForUrl(url, config.tabs);
|
|
604
|
+
if (matchingTab && matchingTab.id !== config.activeTabId) {
|
|
605
|
+
// Activate the matching tab
|
|
606
|
+
this.workspaceManager.SetActiveTab(matchingTab.id);
|
|
607
|
+
}
|
|
608
|
+
else if (!matchingTab) {
|
|
609
|
+
// No matching tab found - check if this is an app-only URL for an app with zero nav items
|
|
610
|
+
// If so, we need to create a new tab for it (the old one was replaced when navigating away)
|
|
611
|
+
await this.handleMissingTabForUrl(url);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Handle the case where no tab matches the URL during back/forward navigation.
|
|
616
|
+
* For apps with zero nav items, creates a new default tab.
|
|
617
|
+
*/
|
|
618
|
+
async handleMissingTabForUrl(url) {
|
|
619
|
+
const urlPath = url.split('?')[0];
|
|
620
|
+
// Check for app-only URL: /app/:appName
|
|
621
|
+
const appOnlyMatch = urlPath.match(/^\/app\/([^\/]+)$/);
|
|
622
|
+
if (appOnlyMatch) {
|
|
623
|
+
const appPath = decodeURIComponent(appOnlyMatch[1]);
|
|
624
|
+
const app = this.appManager.GetAppByPath(appPath) || this.appManager.GetAppByName(appPath);
|
|
625
|
+
if (app) {
|
|
626
|
+
const navItems = app.GetNavItems();
|
|
627
|
+
// Only auto-create tabs for apps with zero nav items
|
|
628
|
+
// Apps with nav items should have had their tabs preserved
|
|
629
|
+
if (navItems.length === 0) {
|
|
630
|
+
// Set the app as active and create its default tab
|
|
631
|
+
// Use urlBasedNavigation flag to prevent syncUrlWithWorkspace from navigating again
|
|
632
|
+
this.urlBasedNavigation = true;
|
|
633
|
+
try {
|
|
634
|
+
await this.appManager.SetActiveApp(app.ID);
|
|
635
|
+
const defaultTab = await app.CreateDefaultTab();
|
|
636
|
+
if (defaultTab) {
|
|
637
|
+
this.workspaceManager.OpenTab(defaultTab, app.GetColor());
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
finally {
|
|
641
|
+
this.urlBasedNavigation = false;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Find the tab that matches a given URL
|
|
649
|
+
*/
|
|
650
|
+
findTabForUrl(url, tabs) {
|
|
651
|
+
// Parse the URL to extract resource info
|
|
652
|
+
const urlPath = url.split('?')[0];
|
|
653
|
+
// Check for app nav item URL: /app/:appName/:navItemName
|
|
654
|
+
const appNavMatch = urlPath.match(/^\/app\/([^\/]+)\/([^\/]+)$/);
|
|
655
|
+
if (appNavMatch) {
|
|
656
|
+
const appPath = decodeURIComponent(appNavMatch[1]);
|
|
657
|
+
const navItemName = decodeURIComponent(appNavMatch[2]);
|
|
658
|
+
return tabs.find(tab => {
|
|
659
|
+
const tabConfig = tab.configuration || {};
|
|
660
|
+
const tabAppName = tabConfig['appName'];
|
|
661
|
+
const tabNavItemName = tabConfig['navItemName'];
|
|
662
|
+
if (!tabAppName || !tabNavItemName)
|
|
663
|
+
return false;
|
|
664
|
+
// Match by app path/name and nav item name (case-insensitive)
|
|
665
|
+
const app = this.appManager.GetAppByPath(appPath) || this.appManager.GetAppByName(appPath);
|
|
666
|
+
if (!app)
|
|
667
|
+
return false;
|
|
668
|
+
const appMatches = tabAppName.toLowerCase() === app.Name.toLowerCase() ||
|
|
669
|
+
tab.applicationId === app.ID;
|
|
670
|
+
const navMatches = tabNavItemName.toLowerCase() === navItemName.toLowerCase();
|
|
671
|
+
return appMatches && navMatches;
|
|
672
|
+
}) || null;
|
|
673
|
+
}
|
|
674
|
+
// Check for app-only URL: /app/:appName
|
|
675
|
+
const appOnlyMatch = urlPath.match(/^\/app\/([^\/]+)$/);
|
|
676
|
+
if (appOnlyMatch) {
|
|
677
|
+
const appPath = decodeURIComponent(appOnlyMatch[1]);
|
|
678
|
+
const app = this.appManager.GetAppByPath(appPath) || this.appManager.GetAppByName(appPath);
|
|
679
|
+
if (app) {
|
|
680
|
+
// First, try to find a tab with isAppDefault for this app
|
|
681
|
+
const defaultTab = tabs.find(tab => {
|
|
682
|
+
const tabConfig = tab.configuration || {};
|
|
683
|
+
return tab.applicationId === app.ID && tabConfig['isAppDefault'] === true;
|
|
684
|
+
});
|
|
685
|
+
if (defaultTab) {
|
|
686
|
+
return defaultTab;
|
|
687
|
+
}
|
|
688
|
+
// Fallback for apps with zero nav items: match ANY tab belonging to this app
|
|
689
|
+
// This handles the case where the default tab was replaced when navigating away
|
|
690
|
+
const navItems = app.GetNavItems();
|
|
691
|
+
if (navItems.length === 0) {
|
|
692
|
+
return tabs.find(tab => tab.applicationId === app.ID) || null;
|
|
693
|
+
}
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
// Check for resource record URL: /resource/record/:entityName/:recordId
|
|
698
|
+
const recordMatch = urlPath.match(/^\/resource\/record\/([^\/]+)\/(.+)$/);
|
|
699
|
+
if (recordMatch) {
|
|
700
|
+
const entityName = decodeURIComponent(recordMatch[1]);
|
|
701
|
+
const recordId = recordMatch[2];
|
|
702
|
+
return tabs.find(tab => {
|
|
703
|
+
const tabConfig = tab.configuration || {};
|
|
704
|
+
const tabEntity = (tabConfig['Entity'] || tabConfig['entity']);
|
|
705
|
+
const tabRecordId = (tabConfig['recordId'] || tab.resourceRecordId);
|
|
706
|
+
return tabEntity?.toLowerCase() === entityName.toLowerCase() &&
|
|
707
|
+
tabRecordId === recordId;
|
|
708
|
+
}) || null;
|
|
709
|
+
}
|
|
710
|
+
// Check for view URL: /resource/view/:viewId
|
|
711
|
+
const viewMatch = urlPath.match(/^\/resource\/view\/([^\/]+)$/);
|
|
712
|
+
if (viewMatch) {
|
|
713
|
+
const viewId = viewMatch[1];
|
|
714
|
+
// Check if it's a dynamic view
|
|
715
|
+
if (viewId === 'dynamic') {
|
|
716
|
+
// Dynamic views include entity name in a different path
|
|
717
|
+
return null; // Let the resolver handle dynamic views
|
|
718
|
+
}
|
|
719
|
+
return tabs.find(tab => {
|
|
720
|
+
const tabConfig = tab.configuration || {};
|
|
721
|
+
const resourceType = tabConfig['resourceType']?.toLowerCase();
|
|
722
|
+
const tabViewId = (tabConfig['viewId'] || tabConfig['recordId'] || tab.resourceRecordId);
|
|
723
|
+
return resourceType === 'user views' && tabViewId === viewId;
|
|
724
|
+
}) || null;
|
|
725
|
+
}
|
|
726
|
+
// Check for dashboard URL: /resource/dashboard/:dashboardId
|
|
727
|
+
const dashboardMatch = urlPath.match(/^\/resource\/dashboard\/(.+)$/);
|
|
728
|
+
if (dashboardMatch) {
|
|
729
|
+
const dashboardId = dashboardMatch[1];
|
|
730
|
+
return tabs.find(tab => {
|
|
731
|
+
const tabConfig = tab.configuration || {};
|
|
732
|
+
const resourceType = tabConfig['resourceType']?.toLowerCase();
|
|
733
|
+
const tabDashboardId = (tabConfig['dashboardId'] || tabConfig['recordId'] || tab.resourceRecordId);
|
|
734
|
+
return resourceType === 'dashboards' && tabDashboardId === dashboardId;
|
|
735
|
+
}) || null;
|
|
736
|
+
}
|
|
737
|
+
// Check for artifact URL: /resource/artifact/:artifactId
|
|
738
|
+
const artifactMatch = urlPath.match(/^\/resource\/artifact\/(.+)$/);
|
|
739
|
+
if (artifactMatch) {
|
|
740
|
+
const artifactId = artifactMatch[1];
|
|
741
|
+
return tabs.find(tab => {
|
|
742
|
+
const tabConfig = tab.configuration || {};
|
|
743
|
+
const resourceType = tabConfig['resourceType']?.toLowerCase();
|
|
744
|
+
const tabArtifactId = (tabConfig['artifactId'] || tabConfig['recordId'] || tab.resourceRecordId);
|
|
745
|
+
return resourceType === 'artifacts' && tabArtifactId === artifactId;
|
|
746
|
+
}) || null;
|
|
747
|
+
}
|
|
748
|
+
// Check for query URL: /resource/query/:queryId
|
|
749
|
+
const queryMatch = urlPath.match(/^\/resource\/query\/(.+)$/);
|
|
750
|
+
if (queryMatch) {
|
|
751
|
+
const queryId = queryMatch[1];
|
|
752
|
+
return tabs.find(tab => {
|
|
753
|
+
const tabConfig = tab.configuration || {};
|
|
754
|
+
const resourceType = tabConfig['resourceType']?.toLowerCase();
|
|
755
|
+
const tabQueryId = (tabConfig['queryId'] || tabConfig['recordId'] || tab.resourceRecordId);
|
|
756
|
+
return resourceType === 'queries' && tabQueryId === queryId;
|
|
757
|
+
}) || null;
|
|
758
|
+
}
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Build a shareable resource URL from tab data.
|
|
763
|
+
* Uses app.Path for cleaner URLs (e.g., /app/data-explorer instead of /app/Data%20Explorer)
|
|
764
|
+
*/
|
|
765
|
+
buildResourceUrl(tab) {
|
|
766
|
+
const config = tab.configuration || {};
|
|
767
|
+
const resourceType = config['resourceType']?.toLowerCase();
|
|
768
|
+
const recordId = tab.resourceRecordId;
|
|
769
|
+
const appName = config['appName'];
|
|
770
|
+
const navItemName = config['navItemName'];
|
|
771
|
+
const queryParams = config['queryParams'];
|
|
772
|
+
const isAppDefault = config['isAppDefault'];
|
|
773
|
+
const tabAppId = tab.applicationId;
|
|
774
|
+
const tabTitle = tab.title;
|
|
775
|
+
// Helper function to get app path for URL
|
|
776
|
+
const getAppPath = (appIdOrName) => {
|
|
777
|
+
// First try by ID
|
|
778
|
+
let app = this.appManager.GetAppById(appIdOrName);
|
|
779
|
+
if (!app) {
|
|
780
|
+
// Try by name
|
|
781
|
+
app = this.appManager.GetAppByName(appIdOrName);
|
|
782
|
+
}
|
|
783
|
+
if (app) {
|
|
784
|
+
// Prefer Path, fall back to Name for backwards compatibility
|
|
785
|
+
return app.Path || app.Name;
|
|
786
|
+
}
|
|
787
|
+
return null;
|
|
788
|
+
};
|
|
789
|
+
// If this is an app nav item, build app-based URL
|
|
790
|
+
if (appName && navItemName) {
|
|
791
|
+
// Look up the app to get its Path
|
|
792
|
+
const appPath = getAppPath(appName) || appName;
|
|
793
|
+
let url = `/app/${encodeURIComponent(appPath)}/${encodeURIComponent(navItemName)}`;
|
|
794
|
+
// Add query params if present
|
|
795
|
+
if (queryParams && Object.keys(queryParams).length > 0) {
|
|
796
|
+
const params = new URLSearchParams(queryParams);
|
|
797
|
+
url += `?${params.toString()}`;
|
|
798
|
+
}
|
|
799
|
+
return url;
|
|
800
|
+
}
|
|
801
|
+
// If this is an app's default dashboard (no nav items), use app-level URL
|
|
802
|
+
if (isAppDefault && appName) {
|
|
803
|
+
const appPath = getAppPath(appName) || appName;
|
|
804
|
+
return `/app/${encodeURIComponent(appPath)}`;
|
|
805
|
+
}
|
|
806
|
+
// Fallback: If tab belongs to a non-system app but doesn't have appName/navItemName,
|
|
807
|
+
// try to reconstruct the URL from the ApplicationId and tab title
|
|
808
|
+
if (tabAppId && tabAppId !== '__explorer') {
|
|
809
|
+
const app = this.appManager.GetAppById(tabAppId);
|
|
810
|
+
if (app) {
|
|
811
|
+
// Prefer Path, fall back to Name
|
|
812
|
+
const appPath = app.Path || app.Name;
|
|
813
|
+
const navItems = app.GetNavItems();
|
|
814
|
+
// If app has nav items, try to find the matching one by title
|
|
815
|
+
if (navItems.length > 0) {
|
|
816
|
+
const navItem = navItems.find(item => item.Label?.trim().toLowerCase() === tabTitle?.trim().toLowerCase());
|
|
817
|
+
if (navItem) {
|
|
818
|
+
let url = `/app/${encodeURIComponent(appPath)}/${encodeURIComponent(navItem.Label)}`;
|
|
819
|
+
// Add query params if present
|
|
820
|
+
if (queryParams && Object.keys(queryParams).length > 0) {
|
|
821
|
+
const params = new URLSearchParams(queryParams);
|
|
822
|
+
url += `?${params.toString()}`;
|
|
823
|
+
}
|
|
824
|
+
return url;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
else {
|
|
828
|
+
// App has zero nav items - use app-level URL
|
|
829
|
+
// This handles apps that only have a default dashboard
|
|
830
|
+
return `/app/${encodeURIComponent(appPath)}`;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const entityName = (config['Entity'] || config['entity']);
|
|
835
|
+
const isDynamic = config['isDynamic'];
|
|
836
|
+
const extraFilter = (config['ExtraFilter'] || config['extraFilter']);
|
|
837
|
+
switch (resourceType) {
|
|
838
|
+
case 'records':
|
|
839
|
+
// /resource/record/:entityName/:recordId
|
|
840
|
+
if (entityName && recordId) {
|
|
841
|
+
return `/resource/record/${encodeURIComponent(entityName)}/${recordId}`;
|
|
842
|
+
}
|
|
843
|
+
break;
|
|
844
|
+
case 'user views':
|
|
845
|
+
// Check if it's a dynamic view
|
|
846
|
+
if (isDynamic || recordId === 'dynamic') {
|
|
847
|
+
// /resource/view/dynamic/:entityName?ExtraFilter=...
|
|
848
|
+
if (entityName) {
|
|
849
|
+
let url = `/resource/view/dynamic/${encodeURIComponent(entityName)}`;
|
|
850
|
+
if (extraFilter) {
|
|
851
|
+
url += `?ExtraFilter=${encodeURIComponent(extraFilter)}`;
|
|
852
|
+
}
|
|
853
|
+
return url;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
else if (recordId) {
|
|
857
|
+
// /resource/view/:viewId (saved view)
|
|
858
|
+
return `/resource/view/${recordId}`;
|
|
859
|
+
}
|
|
860
|
+
break;
|
|
861
|
+
case 'dashboards':
|
|
862
|
+
// /resource/dashboard/:dashboardId
|
|
863
|
+
if (recordId) {
|
|
864
|
+
return `/resource/dashboard/${recordId}`;
|
|
865
|
+
}
|
|
866
|
+
break;
|
|
867
|
+
case 'artifacts':
|
|
868
|
+
// /resource/artifact/:artifactId
|
|
869
|
+
if (recordId) {
|
|
870
|
+
return `/resource/artifact/${recordId}`;
|
|
871
|
+
}
|
|
872
|
+
break;
|
|
873
|
+
case 'queries':
|
|
874
|
+
// /resource/query/:queryId
|
|
875
|
+
if (recordId) {
|
|
876
|
+
return `/resource/query/${recordId}`;
|
|
877
|
+
}
|
|
878
|
+
break;
|
|
879
|
+
}
|
|
880
|
+
return null;
|
|
881
|
+
}
|
|
882
|
+
ngAfterViewInit() {
|
|
883
|
+
// Layout initialization happens in TabContainerComponent
|
|
884
|
+
}
|
|
885
|
+
ngOnDestroy() {
|
|
886
|
+
this.subscriptions.forEach(sub => sub.unsubscribe());
|
|
887
|
+
this.layoutManager.Destroy();
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Handle app switch from app switcher
|
|
891
|
+
*/
|
|
892
|
+
async onAppSwitch(appId) {
|
|
893
|
+
// Clear the system tab flag since we're switching to a real app
|
|
894
|
+
this.isViewingSystemTab = false;
|
|
895
|
+
// Show loading indicator in app switcher
|
|
896
|
+
this.loadingAppId = appId;
|
|
897
|
+
this.cdr.detectChanges();
|
|
898
|
+
try {
|
|
899
|
+
await this.appManager.SetActiveApp(appId);
|
|
900
|
+
// Check if app has any tabs
|
|
901
|
+
const appTabs = this.workspaceManager.GetAppTabs(appId);
|
|
902
|
+
if (appTabs.length === 0) {
|
|
903
|
+
// No tabs - create default tab (will trigger URL sync via workspace config subscription)
|
|
904
|
+
const app = this.appManager.GetAppById(appId);
|
|
905
|
+
if (app) {
|
|
906
|
+
const defaultTab = await app.CreateDefaultTab();
|
|
907
|
+
if (defaultTab) {
|
|
908
|
+
this.workspaceManager.OpenTab(defaultTab, app.GetColor());
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
// App has existing tabs - activate the first one and sync URL
|
|
914
|
+
const firstTab = appTabs[0];
|
|
915
|
+
this.workspaceManager.SetActiveTab(firstTab.id);
|
|
916
|
+
// The workspace configuration subscription will trigger URL sync
|
|
917
|
+
// but we can also manually trigger it here to ensure immediate update
|
|
918
|
+
const resourceUrl = this.buildResourceUrl(firstTab);
|
|
919
|
+
if (resourceUrl) {
|
|
920
|
+
this.router.navigateByUrl(resourceUrl, { replaceUrl: true });
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
finally {
|
|
925
|
+
// Clear loading indicator
|
|
926
|
+
this.loadingAppId = null;
|
|
927
|
+
this.cdr.detectChanges();
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Handle Nav Bar app icon click (single click switches to app)
|
|
932
|
+
*/
|
|
933
|
+
onNavBarAppClick(app, event) {
|
|
934
|
+
// If shift key is held, force new tab
|
|
935
|
+
if (event.shiftKey) {
|
|
936
|
+
this.openNavBarAppInNewTab(app);
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
this.onAppSwitch(app.ID);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Handle Nav Bar app icon double-click (opens in new tab)
|
|
944
|
+
*/
|
|
945
|
+
onNavBarAppDblClick(app, event) {
|
|
946
|
+
event.preventDefault();
|
|
947
|
+
this.openNavBarAppInNewTab(app);
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Open a nav bar app's default content in a new tab
|
|
951
|
+
*/
|
|
952
|
+
async openNavBarAppInNewTab(app) {
|
|
953
|
+
// Create the default tab for this app with forceNewTab
|
|
954
|
+
const tabRequest = await app.CreateDefaultTab();
|
|
955
|
+
if (tabRequest) {
|
|
956
|
+
// Set the app as active first if it isn't already
|
|
957
|
+
if (this.activeApp?.ID !== app.ID) {
|
|
958
|
+
await this.appManager.SetActiveApp(app.ID);
|
|
959
|
+
}
|
|
960
|
+
// Open in new tab by adding to workspace
|
|
961
|
+
this.workspaceManager.OpenTab(tabRequest, app.GetColor());
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Handle navigation item click with shift-key and double-click detection
|
|
966
|
+
*/
|
|
967
|
+
onNavItemClick(event) {
|
|
968
|
+
if (!this.activeApp) {
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
const { item, shiftKey, dblClick } = event;
|
|
972
|
+
// Close mobile nav if open
|
|
973
|
+
this.mobileNavOpen = false;
|
|
974
|
+
// Use NavigationService with forceNewTab option if shift was pressed or double-clicked
|
|
975
|
+
this.navigationService.OpenNavItem(this.activeApp.ID, item, this.activeApp.GetColor(), { forceNewTab: shiftKey || dblClick });
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Toggle mobile navigation drawer
|
|
979
|
+
*/
|
|
980
|
+
toggleMobileNav() {
|
|
981
|
+
this.mobileNavOpen = !this.mobileNavOpen;
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Close mobile navigation drawer
|
|
985
|
+
*/
|
|
986
|
+
closeMobileNav() {
|
|
987
|
+
this.mobileNavOpen = false;
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Toggle user menu visibility
|
|
991
|
+
*/
|
|
992
|
+
toggleUserMenu(event) {
|
|
993
|
+
event.stopPropagation();
|
|
994
|
+
this.userMenuVisible = !this.userMenuVisible;
|
|
995
|
+
if (this.userMenuVisible) {
|
|
996
|
+
// Close menu when clicking outside
|
|
997
|
+
const closeHandler = () => {
|
|
998
|
+
this.userMenuVisible = false;
|
|
999
|
+
document.removeEventListener('click', closeHandler);
|
|
1000
|
+
};
|
|
1001
|
+
setTimeout(() => {
|
|
1002
|
+
document.addEventListener('click', closeHandler);
|
|
1003
|
+
}, 0);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Open Settings in a full-screen modal dialog
|
|
1008
|
+
*/
|
|
1009
|
+
onSettings() {
|
|
1010
|
+
this.userMenuVisible = false;
|
|
1011
|
+
this.settingsDialogService.open(this.viewContainerRef);
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Log current workspace configuration to console (debug)
|
|
1015
|
+
*/
|
|
1016
|
+
onLogLayout() {
|
|
1017
|
+
const config = this.workspaceManager.GetConfiguration();
|
|
1018
|
+
console.log('📋 Workspace Configuration:', JSON.stringify(config, null, 2));
|
|
1019
|
+
console.log('📋 Workspace Configuration (object):', config);
|
|
1020
|
+
this.userMenuVisible = false;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Reset workspace layout - clears all tabs and switches to single-resource mode
|
|
1024
|
+
*/
|
|
1025
|
+
async onResetLayout() {
|
|
1026
|
+
this.userMenuVisible = false;
|
|
1027
|
+
// Get current active app to create a fresh default tab
|
|
1028
|
+
const currentApp = this.activeApp;
|
|
1029
|
+
if (!currentApp) {
|
|
1030
|
+
console.warn('No active app to reset to');
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
// Create a fresh configuration with just one default tab
|
|
1034
|
+
const defaultTabRequest = await currentApp.CreateDefaultTab();
|
|
1035
|
+
if (!defaultTabRequest) {
|
|
1036
|
+
console.warn('Could not create default tab for app');
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
// Generate a new tab ID
|
|
1040
|
+
const newTabId = uuidv4();
|
|
1041
|
+
// Create minimal configuration with single tab (will trigger single-resource mode)
|
|
1042
|
+
const freshConfig = {
|
|
1043
|
+
version: 1,
|
|
1044
|
+
layout: {
|
|
1045
|
+
root: {
|
|
1046
|
+
type: 'row',
|
|
1047
|
+
content: []
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
activeTabId: newTabId,
|
|
1051
|
+
theme: 'light',
|
|
1052
|
+
preferences: {
|
|
1053
|
+
tabPosition: 'top',
|
|
1054
|
+
showTabIcons: true,
|
|
1055
|
+
autoSaveInterval: 5000
|
|
1056
|
+
},
|
|
1057
|
+
tabs: [{
|
|
1058
|
+
id: newTabId,
|
|
1059
|
+
applicationId: defaultTabRequest.ApplicationId,
|
|
1060
|
+
title: defaultTabRequest.Title,
|
|
1061
|
+
resourceTypeId: defaultTabRequest.ResourceTypeId || '',
|
|
1062
|
+
resourceRecordId: defaultTabRequest.ResourceRecordId || '',
|
|
1063
|
+
isPinned: false,
|
|
1064
|
+
sequence: 0,
|
|
1065
|
+
lastAccessedAt: new Date().toISOString(),
|
|
1066
|
+
configuration: defaultTabRequest.Configuration || {}
|
|
1067
|
+
}]
|
|
1068
|
+
};
|
|
1069
|
+
console.log('🔄 Resetting layout to fresh state:', freshConfig);
|
|
1070
|
+
// Update workspace configuration
|
|
1071
|
+
this.workspaceManager.UpdateConfiguration(freshConfig);
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Load user avatar from database, auto-sync from auth provider if needed
|
|
1075
|
+
*/
|
|
1076
|
+
async loadUserAvatar(currentUserInfo) {
|
|
1077
|
+
try {
|
|
1078
|
+
const md = new Metadata();
|
|
1079
|
+
this.userName = currentUserInfo.FirstLast || currentUserInfo.Name || 'User';
|
|
1080
|
+
// Load the full UserEntity to access avatar fields
|
|
1081
|
+
const currentUserEntity = await md.GetEntityObject('Users');
|
|
1082
|
+
await currentUserEntity.Load(currentUserInfo.ID);
|
|
1083
|
+
// Auto-sync avatar from auth provider if user has no avatar settings in DB
|
|
1084
|
+
if (!currentUserEntity.UserImageURL && !currentUserEntity.UserImageIconClass) {
|
|
1085
|
+
const synced = await this.syncAvatarFromAuthProvider(currentUserEntity);
|
|
1086
|
+
if (synced) {
|
|
1087
|
+
// Reload user entity to get saved values
|
|
1088
|
+
await currentUserEntity.Load(currentUserInfo.ID);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
// Load avatar for display (always from DB after potential sync)
|
|
1092
|
+
this.applyUserAvatar(currentUserEntity);
|
|
1093
|
+
}
|
|
1094
|
+
catch (error) {
|
|
1095
|
+
console.warn('Could not load user avatar:', error);
|
|
1096
|
+
// Use fallback
|
|
1097
|
+
this.userImageURL = '';
|
|
1098
|
+
this.userIconClass = null;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Syncs avatar from auth provider (Microsoft, Google, etc.)
|
|
1103
|
+
*/
|
|
1104
|
+
async syncAvatarFromAuthProvider(user) {
|
|
1105
|
+
try {
|
|
1106
|
+
const claims = await firstValueFrom(await this.authBase.getUserClaims());
|
|
1107
|
+
// Check if Microsoft
|
|
1108
|
+
if (claims && claims.authority &&
|
|
1109
|
+
(claims.authority.includes('microsoftonline.com') || claims.authority.includes('microsoft.com'))) {
|
|
1110
|
+
// Microsoft Graph API photo endpoint
|
|
1111
|
+
const imageUrl = 'https://graph.microsoft.com/v1.0/me/photo/$value';
|
|
1112
|
+
const authHeaders = { 'Authorization': `Bearer ${claims.accessToken}` };
|
|
1113
|
+
return await this.userAvatarService.syncFromImageUrl(user, imageUrl, authHeaders);
|
|
1114
|
+
}
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1117
|
+
catch (error) {
|
|
1118
|
+
console.warn('Could not sync avatar from auth provider:', error);
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Apply user avatar to component state
|
|
1124
|
+
* Priority: UserImageURL > UserImageIconClass > default icon
|
|
1125
|
+
*/
|
|
1126
|
+
applyUserAvatar(user) {
|
|
1127
|
+
if (user.UserImageURL) {
|
|
1128
|
+
this.userImageURL = user.UserImageURL;
|
|
1129
|
+
this.userIconClass = null;
|
|
1130
|
+
}
|
|
1131
|
+
else if (user.UserImageIconClass) {
|
|
1132
|
+
this.userIconClass = user.UserImageIconClass;
|
|
1133
|
+
this.userImageURL = '';
|
|
1134
|
+
}
|
|
1135
|
+
else {
|
|
1136
|
+
// Default fallback - show icon
|
|
1137
|
+
this.userImageURL = '';
|
|
1138
|
+
this.userIconClass = null;
|
|
1139
|
+
}
|
|
1140
|
+
this.cdr.detectChanges();
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Update browser tab title based on current app and active tab.
|
|
1144
|
+
* Format: "Resource Name - App Name - MemberJunction"
|
|
1145
|
+
*/
|
|
1146
|
+
updateBrowserTitle(config) {
|
|
1147
|
+
// Find the active tab
|
|
1148
|
+
const activeTab = config.tabs?.find(tab => tab.id === config.activeTabId);
|
|
1149
|
+
// Get app name
|
|
1150
|
+
const appName = this.activeApp?.Name || null;
|
|
1151
|
+
// Get resource name from active tab
|
|
1152
|
+
let resourceName = null;
|
|
1153
|
+
if (activeTab) {
|
|
1154
|
+
resourceName = activeTab.title || null;
|
|
1155
|
+
}
|
|
1156
|
+
// Update title via TitleService
|
|
1157
|
+
this.titleService.setContext(appName, resourceName);
|
|
1158
|
+
}
|
|
1159
|
+
static ɵfac = function ShellComponent_Factory(t) { return new (t || ShellComponent)(i0.ɵɵdirectiveInject(i1.ApplicationManager), i0.ɵɵdirectiveInject(i1.WorkspaceStateManager), i0.ɵɵdirectiveInject(i1.GoldenLayoutManager), i0.ɵɵdirectiveInject(i1.TabService), i0.ɵɵdirectiveInject(i2.NavigationService), i0.ɵɵdirectiveInject(i3.ActivatedRoute), i0.ɵɵdirectiveInject(i3.Router), i0.ɵɵdirectiveInject(i4.MJAuthBase), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i5.UserAvatarService), i0.ɵɵdirectiveInject(i6.SettingsDialogService), i0.ɵɵdirectiveInject(i0.ViewContainerRef), i0.ɵɵdirectiveInject(i2.TitleService)); };
|
|
1160
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ShellComponent, selectors: [["mj-shell"]], decls: 2, vars: 2, consts: [["iconAvatar", ""], ["class", "shell-container", "kendoDialogContainer", "", 3, "tabs-visible", 4, "ngIf"], ["class", "shell-loading", 4, "ngIf"], ["kendoDialogContainer", "", 1, "shell-container"], [1, "shell-header"], ["title", "MemberJunction", 1, "mj-logo"], ["title", "Menu", 1, "hamburger-btn", 3, "click"], [1, "fa-solid", "fa-bars"], ["class", "nav-bar-apps left-of-switcher", 4, "ngIf"], [3, "appSelected", "activeApp", "isViewingSystemTab", "loadingAppId"], ["class", "desktop-nav", 3, "app", "navItemClick", 4, "ngIf"], [1, "spacer"], [1, "header-actions"], ["title", "Search", 1, "icon-btn", "desktop-only"], [1, "fa-solid", "fa-search"], ["title", "Notifications", 1, "icon-btn", "desktop-only", "notification-btn"], [1, "fa-solid", "fa-bell"], ["class", "notification-badge", 4, "ngIf"], ["class", "nav-bar-apps left-of-user-menu", 4, "ngIf"], [1, "user-menu"], [1, "avatar-btn", 3, "click"], [4, "ngIf", "ngIfElse"], ["class", "user-context-menu", 4, "ngIf"], ["class", "mobile-nav-overlay", 3, "click", 4, "ngIf"], [1, "mobile-nav-drawer"], [1, "mobile-nav-header"], [1, "close-btn", 3, "click"], [1, "fa-solid", "fa-xmark"], ["class", "mobile-nav-content", 4, "ngIf"], [1, "mobile-nav-footer"], ["title", "Search", 1, "mobile-nav-action"], ["title", "Notifications", 1, "mobile-nav-action"], [1, "nav-bar-apps", "left-of-switcher"], ["class", "nav-bar-app-btn", 3, "active", "title", "--app-color", "click", "dblclick", 4, "ngFor", "ngForOf"], [1, "nav-bar-app-btn", 3, "click", "dblclick", "title"], [1, "desktop-nav", 3, "navItemClick", "app"], [1, "notification-badge"], [1, "nav-bar-apps", "left-of-user-menu"], ["alt", "User avatar", 1, "avatar-img", 3, "src"], [1, "icon-fallback"], [1, "user-context-menu"], ["class", "user-menu-header", 4, "ngIf"], ["class", "user-menu-divider", 4, "ngIf"], [1, "user-menu-item", 3, "click"], [1, "fa-solid", "fa-gear"], [1, "user-menu-divider"], [1, "fa-solid", "fa-code"], [1, "user-menu-item", "danger", 3, "click"], [1, "fa-solid", "fa-rotate-left"], [1, "user-menu-header"], [1, "user-name"], [1, "mobile-nav-overlay", 3, "click"], [1, "mobile-nav-content"], [1, "mobile-nav-section-title"], [3, "navItemClick", "app"], [1, "shell-loading"], ["text", "Loading workspace...", "size", "large"]], template: function ShellComponent_Template(rf, ctx) { if (rf & 1) {
|
|
1161
|
+
i0.ɵɵtemplate(0, ShellComponent_div_0_Template, 40, 18, "div", 1)(1, ShellComponent_div_1_Template, 2, 0, "div", 2);
|
|
1162
|
+
} if (rf & 2) {
|
|
1163
|
+
i0.ɵɵproperty("ngIf", !ctx.loading);
|
|
1164
|
+
i0.ɵɵadvance();
|
|
1165
|
+
i0.ɵɵproperty("ngIf", ctx.loading);
|
|
1166
|
+
} }, dependencies: [i7.NgForOf, i7.NgIf, i8.LoadingComponent, i9.AppSwitcherComponent, i10.AppNavComponent, i11.TabContainerComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n height: 100vh;\n width: 100%;\n overflow: hidden;\n}\n\n\n\n.mj-logo[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100vh;\n width: 100%;\n overflow: hidden;\n}\n\nmj-tab-container[_ngcontent-%COMP%] {\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\n\nmj-tab-container.hide-tab-bar[_ngcontent-%COMP%] .lm_header {\n display: none;\n}\n\n\n\nmj-tab-container[_ngcontent-%COMP%]:not(.hide-tab-bar) .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\n\nmj-tab-container.hide-tab-bar[_ngcontent-%COMP%] .lm_content {\n height: 100% !important;\n}\n\n\n\nmj-tab-container[_ngcontent-%COMP%] .lm_stack {\n transition: all 0.2s ease-in-out;\n}\n\n.shell-header[_ngcontent-%COMP%] {\n height: 60px;\n background: white;\n border-bottom: 1px solid #e0e0e0;\n display: flex;\n align-items: center;\n padding: 0 16px;\n gap: 16px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);\n flex-shrink: 0;\n}\n\n\n\n.nav-bar-apps[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.nav-bar-apps.left-of-switcher[_ngcontent-%COMP%] {\n \n\n \n\n}\n\n.nav-bar-apps.left-of-user-menu[_ngcontent-%COMP%] {\n margin-right: 8px;\n}\n\n.nav-bar-app-btn[_ngcontent-%COMP%] {\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: #616161;\n font-size: 18px;\n transition: all 0.15s ease;\n position: relative;\n}\n\n.nav-bar-app-btn[_ngcontent-%COMP%]: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[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--app-color) 20%, transparent);\n color: var(--app-color);\n}\n\n.nav-bar-app-btn.active[_ngcontent-%COMP%]::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[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n transition: transform 0.15s ease;\n margin-top: 2px; \n\n}\n\n.nav-bar-app-btn[_ngcontent-%COMP%]:hover i[_ngcontent-%COMP%] {\n transform: scale(1.1);\n}\n\n.spacer[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.header-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.icon-btn[_ngcontent-%COMP%] {\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: #757575;\n font-size: 18px;\n transition: background 0.15s;\n}\n\n.icon-btn[_ngcontent-%COMP%]:hover {\n background: #f5f5f5;\n}\n\n\n\n.notification-btn[_ngcontent-%COMP%] {\n position: relative;\n}\n\n.notification-badge[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%] {\n position: relative;\n}\n\n.user-menu[_ngcontent-%COMP%] .avatar-btn[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%] .avatar-btn[_ngcontent-%COMP%]:hover {\n transform: scale(1.05);\n}\n\n\n\n.user-menu[_ngcontent-%COMP%] .avatar-btn[_ngcontent-%COMP%] .icon-fallback[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n border: 2px solid #e0e0e0;\n background: #f5f5f5;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: border-color 0.15s;\n}\n\n.user-menu[_ngcontent-%COMP%] .avatar-btn[_ngcontent-%COMP%]:hover .icon-fallback[_ngcontent-%COMP%] {\n border-color: #1976d2;\n}\n\n.user-menu[_ngcontent-%COMP%] .avatar-btn[_ngcontent-%COMP%] .icon-fallback[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #757575;\n font-size: 16px;\n}\n\n\n\n.user-menu[_ngcontent-%COMP%] .avatar-btn[_ngcontent-%COMP%] .avatar-img[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%] .avatar-btn[_ngcontent-%COMP%]:hover .avatar-img[_ngcontent-%COMP%] {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);\n}\n\n\n\n.user-menu-header[_ngcontent-%COMP%] {\n padding: 12px 16px;\n background: #fafafa;\n}\n\n.user-menu-header[_ngcontent-%COMP%] .user-name[_ngcontent-%COMP%] {\n font-weight: 600;\n font-size: 14px;\n color: #424242;\n}\n\n\n\n.user-context-menu[_ngcontent-%COMP%] {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 8px;\n background: white;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);\n min-width: 180px;\n z-index: 10000;\n overflow: hidden;\n}\n\n.user-menu-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n cursor: pointer;\n font-size: 14px;\n color: #424242;\n transition: background 0.15s;\n}\n\n.user-menu-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 18px;\n text-align: center;\n color: #757575;\n font-size: 14px;\n}\n\n.user-menu-item[_ngcontent-%COMP%]:hover {\n background: #f5f5f5;\n}\n\n.user-menu-item.danger[_ngcontent-%COMP%] {\n color: #c62828;\n}\n\n.user-menu-item.danger[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #c62828;\n}\n\n.user-menu-item.danger[_ngcontent-%COMP%]:hover {\n background: #ffebee;\n}\n\n.user-menu-divider[_ngcontent-%COMP%] {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 0;\n}\n\n.shell-loading[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100vh;\n background: #fafafa;\n}\n\n\n\n.hamburger-btn[_ngcontent-%COMP%] {\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: #616161;\n font-size: 20px;\n transition: background 0.15s;\n}\n.hamburger-btn[_ngcontent-%COMP%]:hover {\n background: #f5f5f5;\n}\n\n\n\n.mobile-nav-overlay[_ngcontent-%COMP%] {\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\n\n.mobile-nav-drawer[_ngcontent-%COMP%] {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n width: 280px;\n max-width: 85vw;\n background: white;\n box-shadow: 2px 0 12px rgba(0, 0, 0, 0.15);\n z-index: 9999;\n flex-direction: column;\n transform: translateX(-100%);\n transition: transform 0.3s ease;\n}\n.mobile-nav-drawer.open[_ngcontent-%COMP%] {\n transform: translateX(0);\n}\n\n.mobile-nav-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid #e0e0e0;\n background: #fafafa;\n}\n.mobile-nav-header[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-weight: 600;\n font-size: 16px;\n color: #424242;\n}\n.mobile-nav-header[_ngcontent-%COMP%] .close-btn[_ngcontent-%COMP%] {\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: #757575;\n font-size: 18px;\n transition: background 0.15s;\n}\n.mobile-nav-header[_ngcontent-%COMP%] .close-btn[_ngcontent-%COMP%]:hover {\n background: #e0e0e0;\n}\n\n.mobile-nav-content[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 16px 0;\n}\n\n.mobile-nav-section-title[_ngcontent-%COMP%] {\n padding: 8px 20px;\n font-size: 12px;\n font-weight: 600;\n color: #9e9e9e;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.mobile-nav-footer[_ngcontent-%COMP%] {\n border-top: 1px solid #e0e0e0;\n padding: 12px 16px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.mobile-nav-action[_ngcontent-%COMP%] {\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: #616161;\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[_ngcontent-%COMP%]:hover {\n background: #f5f5f5;\n}\n.mobile-nav-action[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n width: 20px;\n text-align: center;\n}\n\n\n\n@media (max-width: 768px) {\n .hamburger-btn[_ngcontent-%COMP%] {\n display: flex;\n }\n\n .desktop-nav[_ngcontent-%COMP%] {\n display: none !important;\n }\n\n .desktop-only[_ngcontent-%COMP%] {\n display: none !important;\n }\n\n .mobile-nav-overlay[_ngcontent-%COMP%] {\n display: block;\n }\n\n .mobile-nav-drawer[_ngcontent-%COMP%] {\n display: flex;\n }\n\n .shell-header[_ngcontent-%COMP%] {\n padding: 0 12px;\n gap: 8px;\n }\n}\n\n\n\n .settings-fullscreen-window {\n \n\n box-shadow: none !important;\n border: none !important;\n}\n\n .settings-fullscreen-window .k-window-titlebar {\n background: #ffffff;\n border-bottom: 1px solid #e0e0e0;\n padding: 12px 16px;\n}\n\n .settings-fullscreen-window .k-window-title {\n font-size: 18px;\n font-weight: 600;\n color: #212121;\n}\n\n .settings-fullscreen-window .k-window-titlebar-actions {\n gap: 4px;\n}\n\n .settings-fullscreen-window .k-window-titlebar-action {\n width: 32px;\n height: 32px;\n border-radius: 6px;\n}\n\n .settings-fullscreen-window .k-window-titlebar-action:hover {\n background: #f5f5f5;\n}\n\n .settings-fullscreen-window .k-window-content {\n padding: 0;\n overflow: auto;\n background: #fafafa;\n}\n\n\n\n .settings-fullscreen-window .k-window-titlebar-action[title=\"Minimize\"], \n .settings-fullscreen-window .k-window-titlebar-action[title=\"Maximize\"], \n .settings-fullscreen-window .k-window-titlebar-action .k-i-window-minimize, \n .settings-fullscreen-window .k-window-titlebar-action .k-i-window-maximize, \n .settings-fullscreen-window .k-window-titlebar-action .k-i-window {\n display: none !important;\n}"] });
|
|
1167
|
+
}
|
|
1168
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ShellComponent, [{
|
|
1169
|
+
type: Component,
|
|
1170
|
+
args: [{ selector: 'mj-shell', template: "<div class=\"shell-container\" *ngIf=\"!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 <div class=\"nav-bar-apps left-of-switcher\" *ngIf=\"leftOfSwitcherApps.length > 0\">\n <button *ngFor=\"let app of leftOfSwitcherApps\"\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 </div>\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 <mj-app-nav\n *ngIf=\"activeApp\"\n class=\"desktop-nav\"\n [app]=\"activeApp\"\n (navItemClick)=\"onNavItemClick($event)\">\n </mj-app-nav>\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\" title=\"Search\">\n <i class=\"fa-solid fa-search\"></i>\n </button>\n <button class=\"icon-btn desktop-only notification-btn\" title=\"Notifications\">\n <i class=\"fa-solid fa-bell\"></i>\n <span class=\"notification-badge\" *ngIf=\"unreadNotificationCount > 0\">\n {{ unreadNotificationCount > 99 ? '99+' : unreadNotificationCount }}\n </span>\n </button>\n\n <!-- Nav Bar Apps: Left of User Menu -->\n <div class=\"nav-bar-apps left-of-user-menu\" *ngIf=\"leftOfUserMenuApps.length > 0\">\n <button *ngFor=\"let app of leftOfUserMenuApps\"\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 </div>\n\n <div class=\"user-menu\">\n <button class=\"avatar-btn\" (click)=\"toggleUserMenu($event)\">\n <ng-container *ngIf=\"userImageURL; else iconAvatar\">\n <img [src]=\"userImageURL\" alt=\"User avatar\" class=\"avatar-img\" />\n </ng-container>\n <ng-template #iconAvatar>\n <div class=\"icon-fallback\">\n <i [class]=\"userIconClass || 'fa-solid fa-user'\"></i>\n </div>\n </ng-template>\n </button>\n <!-- User Context Menu -->\n <div class=\"user-context-menu\" *ngIf=\"userMenuVisible\">\n <div class=\"user-menu-header\" *ngIf=\"userName\">\n <span class=\"user-name\">{{ userName }}</span>\n </div>\n <div class=\"user-menu-divider\" *ngIf=\"userName\"></div>\n <div class=\"user-menu-item\" (click)=\"onSettings()\">\n <i class=\"fa-solid fa-gear\"></i>\n <span>Settings</span>\n </div>\n <div class=\"user-menu-divider\"></div>\n <div class=\"user-menu-item\" (click)=\"onLogLayout()\">\n <i class=\"fa-solid fa-code\"></i>\n <span>Log Layout (Debug)</span>\n </div>\n <div class=\"user-menu-divider\"></div>\n <div class=\"user-menu-item danger\" (click)=\"onResetLayout()\">\n <i class=\"fa-solid fa-rotate-left\"></i>\n <span>Reset Layout</span>\n </div>\n </div>\n </div>\n </div>\n </header>\n\n <!-- Mobile Navigation Drawer -->\n <div class=\"mobile-nav-overlay\" *ngIf=\"mobileNavOpen\" (click)=\"closeMobileNav()\"></div>\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 <div class=\"mobile-nav-content\" *ngIf=\"activeApp\">\n <div class=\"mobile-nav-section-title\">{{ activeApp.Name }}</div>\n <mj-app-nav\n [app]=\"activeApp\"\n (navItemClick)=\"onNavItemClick($event)\">\n </mj-app-nav>\n </div>\n <div class=\"mobile-nav-footer\">\n <button class=\"mobile-nav-action\" title=\"Search\">\n <i class=\"fa-solid fa-search\"></i>\n <span>Search</span>\n </button>\n <button class=\"mobile-nav-action\" title=\"Notifications\">\n <i class=\"fa-solid fa-bell\"></i>\n <span>Notifications</span>\n </button>\n </div>\n </div>\n\n <!-- Tab Container - with dynamic tab bar visibility -->\n <mj-tab-container [class.hide-tab-bar]=\"!tabBarVisible\"></mj-tab-container>\n</div>\n\n<!-- Loading State -->\n<div class=\"shell-loading\" *ngIf=\"loading\">\n <mj-loading text=\"Loading workspace...\" size=\"large\"></mj-loading>\n</div>\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\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: white;\n border-bottom: 1px solid #e0e0e0;\n display: flex;\n align-items: center;\n padding: 0 16px;\n gap: 16px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);\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: #616161;\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: #757575;\n font-size: 18px;\n transition: background 0.15s;\n}\n\n.icon-btn:hover {\n background: #f5f5f5;\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 #e0e0e0;\n background: #f5f5f5;\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: #757575;\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: #fafafa;\n}\n\n.user-menu-header .user-name {\n font-weight: 600;\n font-size: 14px;\n color: #424242;\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: white;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);\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: #424242;\n transition: background 0.15s;\n}\n\n.user-menu-item i {\n width: 18px;\n text-align: center;\n color: #757575;\n font-size: 14px;\n}\n\n.user-menu-item:hover {\n background: #f5f5f5;\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: #ffebee;\n}\n\n.user-menu-divider {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 0;\n}\n\n.shell-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100vh;\n background: #fafafa;\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: #616161;\n font-size: 20px;\n transition: background 0.15s;\n}\n.hamburger-btn:hover {\n background: #f5f5f5;\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: white;\n box-shadow: 2px 0 12px rgba(0, 0, 0, 0.15);\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 #e0e0e0;\n background: #fafafa;\n}\n.mobile-nav-header span {\n font-weight: 600;\n font-size: 16px;\n color: #424242;\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: #757575;\n font-size: 18px;\n transition: background 0.15s;\n}\n.mobile-nav-header .close-btn:hover {\n background: #e0e0e0;\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: #9e9e9e;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.mobile-nav-footer {\n border-top: 1px solid #e0e0e0;\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: #616161;\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: #f5f5f5;\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: #ffffff;\n border-bottom: 1px solid #e0e0e0;\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: #212121;\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: #f5f5f5;\n}\n\n::ng-deep .settings-fullscreen-window .k-window-content {\n padding: 0;\n overflow: auto;\n background: #fafafa;\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}"] }]
|
|
1171
|
+
}], () => [{ 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 }], null); })();
|
|
1172
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ShellComponent, { className: "ShellComponent", filePath: "src/lib/shell/shell.component.ts", lineNumber: 36 }); })();
|
|
1173
|
+
//# sourceMappingURL=shell.component.js.map
|