@memberjunction/ng-explorer-core 5.49.0 → 5.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/omnibar/omnibar-palette.component.js +98 -96
- package/dist/lib/omnibar/omnibar-palette.component.js.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.d.ts +19 -16
- package/dist/lib/resource-wrappers/view-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.js +127 -121
- package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts +84 -9
- package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.js +380 -41
- package/dist/lib/shell/components/header/app-nav.component.js.map +1 -1
- package/dist/lib/shell/components/header/app-switcher.component.d.ts +159 -29
- package/dist/lib/shell/components/header/app-switcher.component.d.ts.map +1 -1
- package/dist/lib/shell/components/header/app-switcher.component.js +822 -116
- package/dist/lib/shell/components/header/app-switcher.component.js.map +1 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.d.ts +10 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.d.ts.map +1 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.js +30 -4
- package/dist/lib/shell/services/sharing-center-dialog-host.component.js.map +1 -1
- package/dist/lib/shell/shell.component.d.ts +3 -0
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +22 -12
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/shell/shell.module.d.ts +1 -1
- package/dist/lib/shell/shell.module.d.ts.map +1 -1
- package/dist/lib/shell/shell.module.js +3 -1
- package/dist/lib/shell/shell.module.js.map +1 -1
- package/package.json +51 -51
|
@@ -1,85 +1,433 @@
|
|
|
1
|
-
import { Component, Input, Output, EventEmitter,
|
|
2
|
-
import {
|
|
1
|
+
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
|
|
2
|
+
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
3
|
+
import { UserAppConfigContentComponent } from '@memberjunction/ng-explorer-settings';
|
|
4
|
+
import { UUIDsEqual, NormalizeUUID } from '@memberjunction/global';
|
|
5
|
+
import { UserInfoEngine } from '@memberjunction/core-entities';
|
|
6
|
+
import { Subject, takeUntil } from 'rxjs';
|
|
3
7
|
import * as i0 from "@angular/core";
|
|
4
8
|
import * as i1 from "@memberjunction/ng-base-application";
|
|
5
|
-
import * as i2 from "@
|
|
6
|
-
import * as i3 from "@memberjunction/ng-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
import * as i2 from "@angular/common";
|
|
10
|
+
import * as i3 from "@memberjunction/ng-ui-components";
|
|
11
|
+
import * as i4 from "@memberjunction/ng-explorer-settings";
|
|
12
|
+
const _c0 = ["filterInput"];
|
|
13
|
+
const _c1 = ["trigger"];
|
|
14
|
+
const _c2 = ["panel"];
|
|
15
|
+
const _c3 = (a0, a1) => ({ $implicit: a0, idx: a1 });
|
|
16
|
+
const _forTrack0 = ($index, $item) => $item.ID;
|
|
11
17
|
function AppSwitcherComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
12
|
-
i0.ɵɵelement(0, "i",
|
|
18
|
+
i0.ɵɵelement(0, "i", 6);
|
|
19
|
+
} }
|
|
20
|
+
function AppSwitcherComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
21
|
+
i0.ɵɵelement(0, "i", 11);
|
|
13
22
|
} if (rf & 2) {
|
|
14
23
|
const ctx_r1 = i0.ɵɵnextContext();
|
|
15
24
|
i0.ɵɵclassMap((ctx_r1.activeApp == null ? null : ctx_r1.activeApp.Icon) || "fa-solid fa-cube");
|
|
16
|
-
i0.ɵɵstyleProp("color", ctx_r1.activeApp == null ? null : ctx_r1.activeApp.GetColor())
|
|
17
|
-
} }
|
|
18
|
-
function AppSwitcherComponent_Conditional_5_For_3_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
19
|
-
i0.ɵɵelement(0, "i", 15);
|
|
25
|
+
i0.ɵɵstyleProp("color", ctx_r1.activeApp == null ? null : ctx_r1.activeApp.GetColor());
|
|
20
26
|
} }
|
|
21
|
-
function
|
|
22
|
-
i0.ɵɵ
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
i0.ɵɵ
|
|
27
|
+
function AppSwitcherComponent_Conditional_8_Conditional_2_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
28
|
+
const _r5 = i0.ɵɵgetCurrentView();
|
|
29
|
+
i0.ɵɵelementStart(0, "div", 19);
|
|
30
|
+
i0.ɵɵelement(1, "i", 21);
|
|
31
|
+
i0.ɵɵelementStart(2, "span", 22);
|
|
32
|
+
i0.ɵɵtext(3, "You have unsaved changes.");
|
|
33
|
+
i0.ɵɵelementEnd();
|
|
34
|
+
i0.ɵɵelementStart(4, "button", 23);
|
|
35
|
+
i0.ɵɵlistener("click", function AppSwitcherComponent_Conditional_8_Conditional_2_Conditional_7_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.ConfirmDiscard()); });
|
|
36
|
+
i0.ɵɵtext(5, "Discard");
|
|
37
|
+
i0.ɵɵelementEnd();
|
|
38
|
+
i0.ɵɵelementStart(6, "button", 24);
|
|
39
|
+
i0.ɵɵlistener("click", function AppSwitcherComponent_Conditional_8_Conditional_2_Conditional_7_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.CancelDiscard()); });
|
|
40
|
+
i0.ɵɵtext(7, "Keep editing");
|
|
41
|
+
i0.ɵɵelementEnd()();
|
|
26
42
|
} }
|
|
27
|
-
function
|
|
43
|
+
function AppSwitcherComponent_Conditional_8_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
28
44
|
const _r4 = i0.ɵɵgetCurrentView();
|
|
29
|
-
i0.ɵɵelementStart(0, "div", 14);
|
|
30
|
-
i0.ɵɵlistener("click", function
|
|
31
|
-
i0.ɵɵ
|
|
32
|
-
i0.ɵɵ
|
|
33
|
-
i0.ɵɵelementStart(3, "
|
|
34
|
-
i0.ɵɵtext(4);
|
|
45
|
+
i0.ɵɵelementStart(0, "div", 13)(1, "button", 14);
|
|
46
|
+
i0.ɵɵlistener("click", function AppSwitcherComponent_Conditional_8_Conditional_2_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.RequestExitConfigMode()); });
|
|
47
|
+
i0.ɵɵelement(2, "i", 15);
|
|
48
|
+
i0.ɵɵelementEnd();
|
|
49
|
+
i0.ɵɵelementStart(3, "h2", 16);
|
|
50
|
+
i0.ɵɵtext(4, "Configure apps");
|
|
51
|
+
i0.ɵɵelementEnd();
|
|
52
|
+
i0.ɵɵelementStart(5, "button", 17);
|
|
53
|
+
i0.ɵɵlistener("click", function AppSwitcherComponent_Conditional_8_Conditional_2_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.CloseLauncher()); });
|
|
54
|
+
i0.ɵɵelement(6, "i", 18);
|
|
35
55
|
i0.ɵɵelementEnd()();
|
|
56
|
+
i0.ɵɵconditionalCreate(7, AppSwitcherComponent_Conditional_8_Conditional_2_Conditional_7_Template, 8, 0, "div", 19);
|
|
57
|
+
i0.ɵɵelementStart(8, "mj-user-app-config-content", 20);
|
|
58
|
+
i0.ɵɵlistener("Saved", function AppSwitcherComponent_Conditional_8_Conditional_2_Template_mj_user_app_config_content_Saved_8_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.ExitConfigMode()); })("Cancelled", function AppSwitcherComponent_Conditional_8_Conditional_2_Template_mj_user_app_config_content_Cancelled_8_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.RequestExitConfigMode()); });
|
|
59
|
+
i0.ɵɵelementEnd();
|
|
36
60
|
} if (rf & 2) {
|
|
37
|
-
const app_r5 = ctx.$implicit;
|
|
38
61
|
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
39
|
-
i0.ɵɵ
|
|
40
|
-
i0.ɵɵ
|
|
41
|
-
i0.ɵɵproperty("mjClickable", app_r5.Name)("testId", "app-switcher-item-" + app_r5.Name);
|
|
42
|
-
i0.ɵɵattribute("aria-current", ctx_r1.IsActiveApp(app_r5) ? "true" : null);
|
|
62
|
+
i0.ɵɵadvance(7);
|
|
63
|
+
i0.ɵɵconditional(ctx_r1.PendingDiscard ? 7 : -1);
|
|
43
64
|
i0.ɵɵadvance();
|
|
44
|
-
i0.ɵɵ
|
|
65
|
+
i0.ɵɵproperty("Provider", ctx_r1.Provider);
|
|
66
|
+
} }
|
|
67
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
68
|
+
const _r7 = i0.ɵɵgetCurrentView();
|
|
69
|
+
i0.ɵɵelementStart(0, "div", 25)(1, "div", 32);
|
|
70
|
+
i0.ɵɵelement(2, "i", 33);
|
|
71
|
+
i0.ɵɵelementStart(3, "input", 34, 3);
|
|
72
|
+
i0.ɵɵlistener("input", function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_0_Template_input_input_3_listener($event) { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.OnFilterInput($event)); })("keydown", function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_0_Template_input_keydown_3_listener($event) { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.OnFilterKeydown($event)); });
|
|
73
|
+
i0.ɵɵelementEnd()();
|
|
74
|
+
i0.ɵɵelementStart(5, "button", 17);
|
|
75
|
+
i0.ɵɵlistener("click", function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_0_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.CloseLauncher()); });
|
|
76
|
+
i0.ɵɵelement(6, "i", 18);
|
|
77
|
+
i0.ɵɵelementEnd()();
|
|
78
|
+
} if (rf & 2) {
|
|
79
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
80
|
+
i0.ɵɵadvance(3);
|
|
81
|
+
i0.ɵɵproperty("value", ctx_r1.FilterText);
|
|
82
|
+
} }
|
|
83
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_2_For_2_ng_container_0_Template(rf, ctx) { if (rf & 1) {
|
|
84
|
+
i0.ɵɵelementContainer(0);
|
|
85
|
+
} }
|
|
86
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_2_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
87
|
+
i0.ɵɵtemplate(0, AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_2_For_2_ng_container_0_Template, 1, 0, "ng-container", 37);
|
|
88
|
+
} if (rf & 2) {
|
|
89
|
+
const app_r8 = ctx.$implicit;
|
|
90
|
+
const ɵ$index_68_r9 = ctx.$index;
|
|
91
|
+
i0.ɵɵnextContext(4);
|
|
92
|
+
const appCard_r10 = i0.ɵɵreference(10);
|
|
93
|
+
i0.ɵɵproperty("ngTemplateOutlet", appCard_r10)("ngTemplateOutletContext", i0.ɵɵpureFunction2(2, _c3, app_r8, ɵ$index_68_r9));
|
|
94
|
+
} }
|
|
95
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_2_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
96
|
+
i0.ɵɵelementStart(0, "div", 36);
|
|
97
|
+
i0.ɵɵtext(1);
|
|
98
|
+
i0.ɵɵelementEnd();
|
|
99
|
+
} if (rf & 2) {
|
|
100
|
+
const ctx_r1 = i0.ɵɵnextContext(4);
|
|
45
101
|
i0.ɵɵadvance();
|
|
46
|
-
i0.ɵɵ
|
|
102
|
+
i0.ɵɵtextInterpolate1("No apps match \"", ctx_r1.FilterText, "\"");
|
|
103
|
+
} }
|
|
104
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
105
|
+
i0.ɵɵelementStart(0, "div", 35);
|
|
106
|
+
i0.ɵɵrepeaterCreate(1, AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_2_For_2_Template, 1, 5, "ng-container", null, _forTrack0);
|
|
107
|
+
i0.ɵɵelementEnd();
|
|
108
|
+
i0.ɵɵconditionalCreate(3, AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_2_Conditional_3_Template, 2, 1, "div", 36);
|
|
109
|
+
} if (rf & 2) {
|
|
110
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
111
|
+
i0.ɵɵadvance();
|
|
112
|
+
i0.ɵɵrepeater(ctx_r1.VisibleApps);
|
|
47
113
|
i0.ɵɵadvance(2);
|
|
48
|
-
i0.ɵɵ
|
|
114
|
+
i0.ɵɵconditional(ctx_r1.VisibleApps.length === 0 ? 3 : -1);
|
|
49
115
|
} }
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
i0.ɵɵ
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
i0.ɵɵ
|
|
59
|
-
i0.ɵɵ
|
|
60
|
-
i0.ɵɵ
|
|
116
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_Conditional_0_For_4_ng_container_0_Template(rf, ctx) { if (rf & 1) {
|
|
117
|
+
i0.ɵɵelementContainer(0);
|
|
118
|
+
} }
|
|
119
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_Conditional_0_For_4_Template(rf, ctx) { if (rf & 1) {
|
|
120
|
+
i0.ɵɵtemplate(0, AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_Conditional_0_For_4_ng_container_0_Template, 1, 0, "ng-container", 37);
|
|
121
|
+
} if (rf & 2) {
|
|
122
|
+
const app_r12 = ctx.$implicit;
|
|
123
|
+
const ɵ$index_84_r13 = ctx.$index;
|
|
124
|
+
i0.ɵɵnextContext(5);
|
|
125
|
+
const appCard_r10 = i0.ɵɵreference(10);
|
|
126
|
+
i0.ɵɵproperty("ngTemplateOutlet", appCard_r10)("ngTemplateOutletContext", i0.ɵɵpureFunction2(2, _c3, app_r12, ɵ$index_84_r13));
|
|
127
|
+
} }
|
|
128
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
129
|
+
i0.ɵɵelementStart(0, "div", 42);
|
|
130
|
+
i0.ɵɵtext(1, "Recent");
|
|
131
|
+
i0.ɵɵelementEnd();
|
|
132
|
+
i0.ɵɵelementStart(2, "div", 43);
|
|
133
|
+
i0.ɵɵrepeaterCreate(3, AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_Conditional_0_For_4_Template, 1, 5, "ng-container", null, _forTrack0);
|
|
134
|
+
i0.ɵɵelementEnd();
|
|
135
|
+
} if (rf & 2) {
|
|
136
|
+
const ctx_r1 = i0.ɵɵnextContext(4);
|
|
137
|
+
i0.ɵɵadvance(3);
|
|
138
|
+
i0.ɵɵrepeater(ctx_r1.RecentApps);
|
|
139
|
+
} }
|
|
140
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_For_8_ng_container_0_Template(rf, ctx) { if (rf & 1) {
|
|
141
|
+
i0.ɵɵelementContainer(0);
|
|
142
|
+
} }
|
|
143
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_For_8_Template(rf, ctx) { if (rf & 1) {
|
|
144
|
+
i0.ɵɵtemplate(0, AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_For_8_ng_container_0_Template, 1, 0, "ng-container", 37);
|
|
145
|
+
} if (rf & 2) {
|
|
146
|
+
const app_r14 = ctx.$implicit;
|
|
147
|
+
const ɵ$index_100_r15 = ctx.$index;
|
|
148
|
+
const ctx_r1 = i0.ɵɵnextContext(4);
|
|
149
|
+
const appCard_r10 = i0.ɵɵreference(10);
|
|
150
|
+
i0.ɵɵproperty("ngTemplateOutlet", appCard_r10)("ngTemplateOutletContext", i0.ɵɵpureFunction2(2, _c3, app_r14, ɵ$index_100_r15 + ctx_r1.RecentApps.length));
|
|
151
|
+
} }
|
|
152
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_Conditional_9_Template(rf, ctx) { if (rf & 1) {
|
|
153
|
+
i0.ɵɵelementStart(0, "div", 36);
|
|
154
|
+
i0.ɵɵtext(1, " No applications are enabled for you yet \u2014 use Configure\u2026 below to add some. ");
|
|
155
|
+
i0.ɵɵelementEnd();
|
|
156
|
+
} }
|
|
157
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
158
|
+
const _r11 = i0.ɵɵgetCurrentView();
|
|
159
|
+
i0.ɵɵconditionalCreate(0, AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_Conditional_0_Template, 5, 0);
|
|
160
|
+
i0.ɵɵelementStart(1, "div", 38)(2, "span");
|
|
161
|
+
i0.ɵɵtext(3, "All apps");
|
|
162
|
+
i0.ɵɵelementEnd();
|
|
163
|
+
i0.ɵɵelementStart(4, "button", 39);
|
|
164
|
+
i0.ɵɵlistener("click", function AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r11); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.ToggleSortMode()); });
|
|
165
|
+
i0.ɵɵelement(5, "i", 40);
|
|
166
|
+
i0.ɵɵelementEnd()();
|
|
167
|
+
i0.ɵɵelementStart(6, "div", 41);
|
|
168
|
+
i0.ɵɵrepeaterCreate(7, AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_For_8_Template, 1, 5, "ng-container", null, _forTrack0);
|
|
169
|
+
i0.ɵɵelementEnd();
|
|
170
|
+
i0.ɵɵconditionalCreate(9, AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_Conditional_9_Template, 2, 0, "div", 36);
|
|
171
|
+
} if (rf & 2) {
|
|
172
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
173
|
+
i0.ɵɵconditional(ctx_r1.RecentApps.length > 0 ? 0 : -1);
|
|
174
|
+
i0.ɵɵadvance(4);
|
|
175
|
+
i0.ɵɵproperty("title", ctx_r1.SortMode === "alpha" ? "Sorted A\u2013Z \u2014 click for your custom order" : "Custom order \u2014 click for A\u2013Z");
|
|
176
|
+
i0.ɵɵattribute("aria-pressed", ctx_r1.SortMode === "alpha")("aria-label", ctx_r1.SortMode === "alpha" ? "Sorted alphabetically \u2014 switch to your custom order" : "Sorted by your custom order \u2014 switch to alphabetical");
|
|
177
|
+
i0.ɵɵadvance();
|
|
178
|
+
i0.ɵɵclassMap(ctx_r1.SortMode === "alpha" ? "fa-solid fa-arrow-down-a-z" : "fa-solid fa-bars-staggered");
|
|
179
|
+
i0.ɵɵadvance(2);
|
|
180
|
+
i0.ɵɵrepeater(ctx_r1.OtherApps);
|
|
181
|
+
i0.ɵɵadvance(2);
|
|
182
|
+
i0.ɵɵconditional(ctx_r1.VisibleApps.length === 0 ? 9 : -1);
|
|
183
|
+
} }
|
|
184
|
+
function AppSwitcherComponent_Conditional_8_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
185
|
+
const _r6 = i0.ɵɵgetCurrentView();
|
|
186
|
+
i0.ɵɵconditionalCreate(0, AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_0_Template, 7, 1, "div", 25);
|
|
187
|
+
i0.ɵɵelementStart(1, "div", 26);
|
|
188
|
+
i0.ɵɵconditionalCreate(2, AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_2_Template, 4, 1)(3, AppSwitcherComponent_Conditional_8_Conditional_3_Conditional_3_Template, 10, 7);
|
|
189
|
+
i0.ɵɵelementEnd();
|
|
190
|
+
i0.ɵɵelementStart(4, "div", 27)(5, "span", 28)(6, "span")(7, "kbd");
|
|
191
|
+
i0.ɵɵtext(8, "\u2191");
|
|
192
|
+
i0.ɵɵelementEnd();
|
|
193
|
+
i0.ɵɵelementStart(9, "kbd");
|
|
194
|
+
i0.ɵɵtext(10, "\u2193");
|
|
195
|
+
i0.ɵɵelementEnd();
|
|
196
|
+
i0.ɵɵtext(11, " navigate");
|
|
197
|
+
i0.ɵɵelementEnd();
|
|
198
|
+
i0.ɵɵelementStart(12, "span")(13, "kbd");
|
|
199
|
+
i0.ɵɵtext(14, "\u21B5");
|
|
200
|
+
i0.ɵɵelementEnd();
|
|
201
|
+
i0.ɵɵtext(15, " open");
|
|
202
|
+
i0.ɵɵelementEnd();
|
|
203
|
+
i0.ɵɵelementStart(16, "span")(17, "kbd");
|
|
204
|
+
i0.ɵɵtext(18, "esc");
|
|
205
|
+
i0.ɵɵelementEnd();
|
|
206
|
+
i0.ɵɵtext(19, " close");
|
|
207
|
+
i0.ɵɵelementEnd()();
|
|
208
|
+
i0.ɵɵelementStart(20, "button", 29);
|
|
209
|
+
i0.ɵɵlistener("click", function AppSwitcherComponent_Conditional_8_Conditional_3_Template_button_click_20_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.EnterConfigMode()); });
|
|
210
|
+
i0.ɵɵelement(21, "i", 30);
|
|
211
|
+
i0.ɵɵelementStart(22, "span");
|
|
212
|
+
i0.ɵɵtext(23, "Configure\u2026");
|
|
61
213
|
i0.ɵɵelementEnd()()();
|
|
214
|
+
i0.ɵɵelementStart(24, "span", 31);
|
|
215
|
+
i0.ɵɵtext(25);
|
|
216
|
+
i0.ɵɵelementEnd();
|
|
217
|
+
} if (rf & 2) {
|
|
218
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
219
|
+
i0.ɵɵconditional(ctx_r1.ShowFilter ? 0 : -1);
|
|
220
|
+
i0.ɵɵadvance(2);
|
|
221
|
+
i0.ɵɵconditional(ctx_r1.IsFiltering ? 2 : 3);
|
|
222
|
+
i0.ɵɵadvance(23);
|
|
223
|
+
i0.ɵɵtextInterpolate1("", ctx_r1.AnnouncedAppCount, " applications available");
|
|
224
|
+
} }
|
|
225
|
+
function AppSwitcherComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
226
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
227
|
+
i0.ɵɵelementStart(0, "dialog", 12, 2);
|
|
228
|
+
i0.ɵɵlistener("click", function AppSwitcherComponent_Conditional_8_Template_dialog_click_0_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDialogClick($event)); })("cancel", function AppSwitcherComponent_Conditional_8_Template_dialog_cancel_0_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDialogCancel($event)); })("keydown", function AppSwitcherComponent_Conditional_8_Template_dialog_keydown_0_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnPanelKeydown($event)); });
|
|
229
|
+
i0.ɵɵconditionalCreate(2, AppSwitcherComponent_Conditional_8_Conditional_2_Template, 9, 2)(3, AppSwitcherComponent_Conditional_8_Conditional_3_Template, 26, 3);
|
|
230
|
+
i0.ɵɵelementEnd();
|
|
62
231
|
} if (rf & 2) {
|
|
63
232
|
const ctx_r1 = i0.ɵɵnextContext();
|
|
233
|
+
i0.ɵɵstyleProp("--anchor-left", ctx_r1.AnchorLeft + "px")("--anchor-top", ctx_r1.AnchorTop + "px");
|
|
234
|
+
i0.ɵɵclassProp("launcher-panel--compact", ctx_r1.CompactMode && !ctx_r1.ConfigMode);
|
|
235
|
+
i0.ɵɵattribute("aria-label", ctx_r1.ConfigMode ? null : "Application launcher")("aria-labelledby", ctx_r1.ConfigMode ? "mj-launcher-config-title" : null);
|
|
64
236
|
i0.ɵɵadvance(2);
|
|
65
|
-
i0.ɵɵ
|
|
237
|
+
i0.ɵɵconditional(ctx_r1.ConfigMode ? 2 : 3);
|
|
238
|
+
} }
|
|
239
|
+
function AppSwitcherComponent_ng_template_9_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
240
|
+
i0.ɵɵelement(0, "i", 46);
|
|
241
|
+
} }
|
|
242
|
+
function AppSwitcherComponent_ng_template_9_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
243
|
+
i0.ɵɵelement(0, "i", 40);
|
|
244
|
+
} if (rf & 2) {
|
|
245
|
+
const app_r17 = i0.ɵɵnextContext().$implicit;
|
|
246
|
+
i0.ɵɵclassMap(app_r17.Icon || "fa-solid fa-cube");
|
|
247
|
+
} }
|
|
248
|
+
function AppSwitcherComponent_ng_template_9_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
249
|
+
i0.ɵɵelementStart(0, "div", 51);
|
|
250
|
+
i0.ɵɵtext(1);
|
|
251
|
+
i0.ɵɵelementEnd();
|
|
252
|
+
} if (rf & 2) {
|
|
253
|
+
const ctx_r18 = i0.ɵɵnextContext();
|
|
254
|
+
const app_r17 = ctx_r18.$implicit;
|
|
255
|
+
const idx_r18 = ctx_r18.idx;
|
|
256
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
257
|
+
i0.ɵɵproperty("id", ctx_r1.OptionId(idx_r18) + "-desc");
|
|
258
|
+
i0.ɵɵadvance();
|
|
259
|
+
i0.ɵɵtextInterpolate(app_r17.Description);
|
|
66
260
|
} }
|
|
261
|
+
function AppSwitcherComponent_ng_template_9_Template(rf, ctx) { if (rf & 1) {
|
|
262
|
+
const _r16 = i0.ɵɵgetCurrentView();
|
|
263
|
+
i0.ɵɵelementStart(0, "div", 44);
|
|
264
|
+
i0.ɵɵlistener("click", function AppSwitcherComponent_ng_template_9_Template_div_click_0_listener() { const app_r17 = i0.ɵɵrestoreView(_r16).$implicit; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.selectApp(app_r17)); })("keydown", function AppSwitcherComponent_ng_template_9_Template_div_keydown_0_listener($event) { const idx_r18 = i0.ɵɵrestoreView(_r16).idx; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnCardKeydown(idx_r18, $event)); });
|
|
265
|
+
i0.ɵɵelementStart(1, "div", 45);
|
|
266
|
+
i0.ɵɵconditionalCreate(2, AppSwitcherComponent_ng_template_9_Conditional_2_Template, 1, 0, "i", 46)(3, AppSwitcherComponent_ng_template_9_Conditional_3_Template, 1, 2, "i", 47);
|
|
267
|
+
i0.ɵɵelementEnd();
|
|
268
|
+
i0.ɵɵelementStart(4, "div", 48)(5, "div", 49)(6, "span", 50);
|
|
269
|
+
i0.ɵɵtext(7);
|
|
270
|
+
i0.ɵɵelementEnd()();
|
|
271
|
+
i0.ɵɵconditionalCreate(8, AppSwitcherComponent_ng_template_9_Conditional_8_Template, 2, 2, "div", 51);
|
|
272
|
+
i0.ɵɵelementEnd()();
|
|
273
|
+
} if (rf & 2) {
|
|
274
|
+
const app_r17 = ctx.$implicit;
|
|
275
|
+
const idx_r18 = ctx.idx;
|
|
276
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
277
|
+
i0.ɵɵstyleProp("--app-color", app_r17.GetColor());
|
|
278
|
+
i0.ɵɵclassProp("active", ctx_r1.IsActiveApp(app_r17))("loading", ctx_r1.IsLoadingApp(app_r17));
|
|
279
|
+
i0.ɵɵproperty("id", ctx_r1.OptionId(idx_r18))("mjClickable", app_r17.Name)("testId", "app-switcher-item-" + app_r17.Name);
|
|
280
|
+
i0.ɵɵattribute("aria-current", ctx_r1.IsActiveApp(app_r17) ? "true" : null)("aria-describedby", app_r17.Description ? ctx_r1.OptionId(idx_r18) + "-desc" : null);
|
|
281
|
+
i0.ɵɵadvance(2);
|
|
282
|
+
i0.ɵɵconditional(ctx_r1.IsLoadingApp(app_r17) ? 2 : 3);
|
|
283
|
+
i0.ɵɵadvance(5);
|
|
284
|
+
i0.ɵɵtextInterpolate(app_r17.Name);
|
|
285
|
+
i0.ɵɵadvance();
|
|
286
|
+
i0.ɵɵconditional(app_r17.Description ? 8 : -1);
|
|
287
|
+
} }
|
|
288
|
+
/** UserInfoEngine setting key holding the recently-switched-to apps */
|
|
289
|
+
const RECENT_APPS_KEY = 'mj.shell.recentApps.v1';
|
|
290
|
+
/** UserInfoEngine setting key for the launcher's All-apps sort preference */
|
|
291
|
+
const LAUNCHER_SORT_KEY = 'mj.shell.launcherSort.v1';
|
|
292
|
+
/** How many recent entries to persist (display shows fewer) */
|
|
293
|
+
const RECENT_STORE_MAX = 8;
|
|
294
|
+
/** How many recent cards to display */
|
|
295
|
+
const RECENT_DISPLAY_MAX = 3;
|
|
296
|
+
/** 'auto' style: below this many apps the compact anchored panel is used */
|
|
297
|
+
const COMPACT_AUTO_THRESHOLD = 8;
|
|
298
|
+
/** Compact mode: the filter input appears only at or above this many apps */
|
|
299
|
+
const COMPACT_FILTER_THRESHOLD = 10;
|
|
67
300
|
/**
|
|
68
|
-
* App switcher
|
|
69
|
-
*
|
|
301
|
+
* App switcher in the header: the trigger shows the active app's identity
|
|
302
|
+
* glyph; activating it opens a centered launcher overlay — a filterable grid
|
|
303
|
+
* of app cards (identity glyph + name + Description summary), with the user's
|
|
304
|
+
* most recently used apps surfaced first.
|
|
305
|
+
*
|
|
306
|
+
* Rendering model: the panel is a native <dialog> opened with showModal(),
|
|
307
|
+
* so it lives in the browser's TOP LAYER — above every stacking context in
|
|
308
|
+
* the app (the shell header's z-index:500 context, chat FAB, toasts). The
|
|
309
|
+
* modal state also makes the rest of the document inert (focus cannot
|
|
310
|
+
* escape) and the browser restores focus to the trigger on close.
|
|
311
|
+
*
|
|
312
|
+
* Accessibility model: every app card is a real focusable control (via
|
|
313
|
+
* mjClickable), so Tab walks filter → close → cards → Configure, and
|
|
314
|
+
* Enter/Space activate the focused card. Arrow keys are the fast path:
|
|
315
|
+
* ArrowDown from the filter focuses the first card; arrows move between
|
|
316
|
+
* cards; ArrowUp from the first card returns to the filter. Escape layers:
|
|
317
|
+
* discard-bar → config view → filter → close. Exiting the config view with
|
|
318
|
+
* unsaved changes shows an inline discard bar (a body-appended confirm
|
|
319
|
+
* dialog would be inert/beneath the top layer).
|
|
70
320
|
*/
|
|
71
|
-
export class AppSwitcherComponent {
|
|
321
|
+
export class AppSwitcherComponent extends BaseAngularComponent {
|
|
72
322
|
appManager;
|
|
323
|
+
cdr;
|
|
324
|
+
destroy$ = new Subject();
|
|
73
325
|
activeApp = null;
|
|
74
326
|
isViewingSystemTab = false;
|
|
75
327
|
/** ID of the app currently being loaded (shows loading indicator) */
|
|
76
328
|
loadingAppId = null;
|
|
329
|
+
/** Presentation style (from Shell.AppSwitcher.Style instance config) */
|
|
330
|
+
SwitcherStyle = 'auto';
|
|
77
331
|
appSelected = new EventEmitter();
|
|
78
|
-
|
|
332
|
+
filterInputRef;
|
|
333
|
+
triggerRef;
|
|
334
|
+
panelRef;
|
|
335
|
+
configContent;
|
|
79
336
|
showDropdown = false;
|
|
80
|
-
|
|
81
|
-
|
|
337
|
+
/** When true, the launcher body shows the app-configuration view instead of the card grid */
|
|
338
|
+
ConfigMode = false;
|
|
339
|
+
/** Pending action awaiting the inline unsaved-changes discard bar ('exit' = back to grid, 'close' = close launcher) */
|
|
340
|
+
PendingDiscard = null;
|
|
341
|
+
/** True when the panel renders as the compact anchored dropdown (few apps) */
|
|
342
|
+
CompactMode = false;
|
|
343
|
+
/** All-apps sort: the user's configured order ('custom') or A–Z ('alpha').
|
|
344
|
+
* Per-user preference, persisted via UserInfoEngine. */
|
|
345
|
+
SortMode = 'custom';
|
|
346
|
+
/** Anchor position for the compact panel (px, from the trigger's rect) */
|
|
347
|
+
AnchorLeft = 0;
|
|
348
|
+
AnchorTop = 0;
|
|
349
|
+
/** Live filter text — matches app names AND Description summaries */
|
|
350
|
+
FilterText = '';
|
|
351
|
+
recentEntries = [];
|
|
352
|
+
constructor(appManager, cdr) {
|
|
353
|
+
super();
|
|
82
354
|
this.appManager = appManager;
|
|
355
|
+
this.cdr = cdr;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* UserInfoEngine scoped to this component's provider (multi-provider rule:
|
|
359
|
+
* never reach for the global singleton in per-provider code paths). For the
|
|
360
|
+
* default single-provider shell this resolves to the already-loaded engine
|
|
361
|
+
* registered under the default connection. When no provider is available at
|
|
362
|
+
* all (test environments), fall back to the global singleton rather than
|
|
363
|
+
* letting GetProviderInstance construct a fresh unloaded engine.
|
|
364
|
+
*/
|
|
365
|
+
get userInfoEngine() {
|
|
366
|
+
const provider = this.ProviderToUse;
|
|
367
|
+
return provider
|
|
368
|
+
? UserInfoEngine.GetProviderInstance(provider, UserInfoEngine)
|
|
369
|
+
: UserInfoEngine.Instance;
|
|
370
|
+
}
|
|
371
|
+
ngOnInit() {
|
|
372
|
+
// The launcher's presentation (compact vs full, filter visibility) is
|
|
373
|
+
// resolved from the app COUNT — which can change while the panel is open
|
|
374
|
+
// (the user saves config changes that cross the auto threshold). React to
|
|
375
|
+
// the same stream the app list uses so the presentation can't go stale.
|
|
376
|
+
// Optional-chained: lightweight test stubs may not provide the observable.
|
|
377
|
+
this.appManager.Applications?.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
378
|
+
if (this.showDropdown) {
|
|
379
|
+
this.RefreshPresentation();
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
ngOnDestroy() {
|
|
384
|
+
this.destroy$.next();
|
|
385
|
+
this.destroy$.complete();
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Re-resolve the presentation for the CURRENT app count. No-op while the
|
|
389
|
+
* config view is open (the panel is promoted to full size there anyway);
|
|
390
|
+
* called when the app stream emits and when the config view exits.
|
|
391
|
+
*/
|
|
392
|
+
RefreshPresentation() {
|
|
393
|
+
if (this.ConfigMode) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const wasCompact = this.CompactMode;
|
|
397
|
+
this.CompactMode = this.resolveCompactMode();
|
|
398
|
+
if (this.CompactMode && !wasCompact) {
|
|
399
|
+
this.computeCompactAnchor();
|
|
400
|
+
}
|
|
401
|
+
// Mode flips can add/remove the filter input — if the focused element got
|
|
402
|
+
// torn out of the DOM, focus would drop into the inert background. Re-home
|
|
403
|
+
// it after the view settles.
|
|
404
|
+
// Zoneless: an RxJS-driven mutation needs an explicit CD kick (same
|
|
405
|
+
// pattern as AppNavComponent's workspace subscription).
|
|
406
|
+
this.cdr.markForCheck();
|
|
407
|
+
try {
|
|
408
|
+
this.cdr.detectChanges();
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// Re-entrant CD — harmless.
|
|
412
|
+
}
|
|
413
|
+
if (wasCompact !== this.CompactMode) {
|
|
414
|
+
requestAnimationFrame(() => {
|
|
415
|
+
const panel = this.panelRef?.nativeElement;
|
|
416
|
+
if (panel && !panel.contains(document.activeElement)) {
|
|
417
|
+
if (this.ShowFilter) {
|
|
418
|
+
this.filterInputRef?.nativeElement?.focus();
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
panel.querySelector('.app-card')?.focus();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/** Template handler for the filter input's input event */
|
|
428
|
+
OnFilterInput(event) {
|
|
429
|
+
this.FilterText = event.target.value;
|
|
430
|
+
this.OnFilterChange();
|
|
83
431
|
}
|
|
84
432
|
/**
|
|
85
433
|
* Check if the app switcher should show loading state
|
|
@@ -88,24 +436,331 @@ export class AppSwitcherComponent {
|
|
|
88
436
|
return this.loadingAppId !== null;
|
|
89
437
|
}
|
|
90
438
|
/**
|
|
91
|
-
*
|
|
439
|
+
* Visible trigger label: the current app's name, or "Apps" when there's no
|
|
440
|
+
* app context (system tabs, nothing active yet).
|
|
441
|
+
*/
|
|
442
|
+
get TriggerLabel() {
|
|
443
|
+
return this.activeApp && !this.isViewingSystemTab ? this.activeApp.Name : 'Apps';
|
|
444
|
+
}
|
|
445
|
+
/** Accessible name for the trigger — names the control AND the current app */
|
|
446
|
+
get TriggerAriaLabel() {
|
|
447
|
+
return this.activeApp && !this.isViewingSystemTab
|
|
448
|
+
? `Switch application — current: ${this.activeApp.Name}`
|
|
449
|
+
: 'Switch application';
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Get applications that should appear in the app switcher
|
|
92
453
|
* (NavigationStyle = 'App Switcher' or 'Both')
|
|
93
454
|
*/
|
|
94
455
|
get apps() {
|
|
95
456
|
return this.appManager.GetAppSwitcherApps();
|
|
96
457
|
}
|
|
97
|
-
/**
|
|
98
|
-
*
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
458
|
+
/** Recently-used apps (persisted per user), newest first, capped for display.
|
|
459
|
+
* Compact mode skips the Recent section — with a handful of apps it's noise. */
|
|
460
|
+
get RecentApps() {
|
|
461
|
+
if (this.CompactMode) {
|
|
462
|
+
return [];
|
|
463
|
+
}
|
|
464
|
+
const byId = new Map(this.apps.map(a => [NormalizeUUID(a.ID), a]));
|
|
465
|
+
const result = [];
|
|
466
|
+
for (const entry of this.recentEntries) {
|
|
467
|
+
const app = byId.get(NormalizeUUID(entry.id));
|
|
468
|
+
if (app) {
|
|
469
|
+
result.push(app);
|
|
470
|
+
if (result.length >= RECENT_DISPLAY_MAX) {
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return this.applyFilter(result);
|
|
476
|
+
}
|
|
477
|
+
/** All switcher apps in the user's chosen sort (custom Sequence order or A–Z) */
|
|
478
|
+
get sortedApps() {
|
|
479
|
+
return this.SortMode === 'alpha'
|
|
480
|
+
? [...this.apps].sort((a, b) => a.Name.localeCompare(b.Name))
|
|
481
|
+
: this.apps;
|
|
482
|
+
}
|
|
483
|
+
/** The complete "All apps" section, in the user's chosen sort. Apps shown in
|
|
484
|
+
* Recent appear here TOO — being recently used must not make an app vanish
|
|
485
|
+
* from its expected place in the full list. */
|
|
486
|
+
get OtherApps() {
|
|
487
|
+
return this.applyFilter(this.sortedApps);
|
|
488
|
+
}
|
|
489
|
+
/** Flat visible list in RENDERED card order — the keyboard-navigation index
|
|
490
|
+
* space. Filtering collapses the sections into one deduplicated list;
|
|
491
|
+
* otherwise it's Recent followed by the full All-apps list (a recent app
|
|
492
|
+
* intentionally appears twice, matching the two cards on screen). */
|
|
493
|
+
get VisibleApps() {
|
|
494
|
+
if (this.IsFiltering) {
|
|
495
|
+
return this.applyFilter(this.sortedApps);
|
|
496
|
+
}
|
|
497
|
+
return [...this.RecentApps, ...this.OtherApps];
|
|
498
|
+
}
|
|
499
|
+
/** Distinct app count for the screen-reader status (VisibleApps can contain
|
|
500
|
+
* the same app twice when it's in both Recent and All apps) */
|
|
501
|
+
get AnnouncedAppCount() {
|
|
502
|
+
return this.IsFiltering ? this.VisibleApps.length : this.applyFilter(this.apps).length;
|
|
503
|
+
}
|
|
504
|
+
/** True when a filter is active (sections collapse into one result list) */
|
|
505
|
+
get IsFiltering() {
|
|
506
|
+
return this.FilterText.trim().length > 0;
|
|
507
|
+
}
|
|
508
|
+
/** Compact mode hides the filter under a handful of apps — it's noise there */
|
|
509
|
+
get ShowFilter() {
|
|
510
|
+
return !this.CompactMode || this.apps.length >= COMPACT_FILTER_THRESHOLD;
|
|
511
|
+
}
|
|
512
|
+
applyFilter(apps) {
|
|
513
|
+
const q = this.FilterText.trim().toLowerCase();
|
|
514
|
+
if (!q) {
|
|
515
|
+
return apps;
|
|
516
|
+
}
|
|
517
|
+
return apps.filter(a => a.Name.toLowerCase().includes(q) || (a.Description || '').toLowerCase().includes(q));
|
|
518
|
+
}
|
|
519
|
+
/** Stable DOM id for a card (arrow-key focus target) */
|
|
520
|
+
OptionId(index) {
|
|
521
|
+
return `mj-launcher-opt-${index}`;
|
|
522
|
+
}
|
|
523
|
+
/** Trigger handler: open the launcher panel, or close it if already open */
|
|
524
|
+
ToggleLauncher() {
|
|
525
|
+
if (this.showDropdown) {
|
|
526
|
+
this.closeLauncher();
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
this.openLauncher();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
/** Flip the All-apps sort and persist it (fail-silent, like recents) */
|
|
533
|
+
ToggleSortMode() {
|
|
534
|
+
this.SortMode = this.SortMode === 'alpha' ? 'custom' : 'alpha';
|
|
535
|
+
try {
|
|
536
|
+
this.userInfoEngine.SetSettingDebounced(LAUNCHER_SORT_KEY, this.SortMode);
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
// Preference persistence must never break the launcher
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
loadSortMode() {
|
|
543
|
+
try {
|
|
544
|
+
this.SortMode = this.userInfoEngine.GetSetting(LAUNCHER_SORT_KEY) === 'alpha' ? 'alpha' : 'custom';
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
this.SortMode = 'custom';
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
openLauncher() {
|
|
551
|
+
this.loadRecents();
|
|
552
|
+
this.loadSortMode();
|
|
553
|
+
this.FilterText = '';
|
|
554
|
+
this.ConfigMode = false;
|
|
555
|
+
this.PendingDiscard = null;
|
|
556
|
+
this.CompactMode = this.resolveCompactMode();
|
|
557
|
+
if (this.CompactMode) {
|
|
558
|
+
this.computeCompactAnchor();
|
|
559
|
+
}
|
|
560
|
+
this.showDropdown = true;
|
|
561
|
+
// The dialog element renders under @if next frame: put it in the top
|
|
562
|
+
// layer via showModal() (jsdom fallback: plain open attribute), then
|
|
563
|
+
// focus the filter (or the first card when compact mode hides it).
|
|
564
|
+
requestAnimationFrame(() => {
|
|
565
|
+
const dialog = this.panelRef?.nativeElement;
|
|
566
|
+
if (dialog && !dialog.open) {
|
|
567
|
+
if (typeof dialog.showModal === 'function') {
|
|
568
|
+
dialog.showModal();
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
dialog.setAttribute('open', '');
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (this.ShowFilter) {
|
|
575
|
+
this.filterInputRef?.nativeElement?.focus();
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
dialog?.querySelector('.app-card')?.focus();
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
/** Resolve the presentation for this open (auto = compact under the threshold) */
|
|
583
|
+
resolveCompactMode() {
|
|
584
|
+
if (this.SwitcherStyle === 'compact') {
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
if (this.SwitcherStyle === 'launcher') {
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
return this.apps.length < COMPACT_AUTO_THRESHOLD;
|
|
591
|
+
}
|
|
592
|
+
/** Anchor the compact panel under the trigger, clamped to the viewport */
|
|
593
|
+
computeCompactAnchor() {
|
|
594
|
+
const rect = this.triggerRef?.nativeElement?.getBoundingClientRect();
|
|
595
|
+
if (!rect) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const panelWidth = 320; // Keep in sync with .launcher-panel--compact width
|
|
599
|
+
const margin = 8;
|
|
600
|
+
this.AnchorLeft = Math.max(margin, Math.min(rect.left, window.innerWidth - panelWidth - margin));
|
|
601
|
+
this.AnchorTop = rect.bottom + margin;
|
|
602
|
+
}
|
|
603
|
+
closeLauncher(restoreFocus = true) {
|
|
604
|
+
const dialog = this.panelRef?.nativeElement;
|
|
605
|
+
if (dialog?.open && typeof dialog.close === 'function') {
|
|
606
|
+
dialog.close(); // Browser restores focus to the trigger automatically
|
|
607
|
+
}
|
|
608
|
+
this.showDropdown = false;
|
|
609
|
+
this.FilterText = '';
|
|
610
|
+
this.ConfigMode = false;
|
|
611
|
+
this.PendingDiscard = null;
|
|
612
|
+
if (restoreFocus) {
|
|
613
|
+
requestAnimationFrame(() => {
|
|
614
|
+
this.triggerRef?.nativeElement?.focus();
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/** Guarded close for template paths (backdrop / close button): unsaved
|
|
619
|
+
* config changes surface the inline discard bar instead of closing. */
|
|
620
|
+
CloseLauncher() {
|
|
621
|
+
if (this.ConfigMode && this.configContent?.HasChanges()) {
|
|
622
|
+
this.PendingDiscard = 'close';
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
this.closeLauncher();
|
|
626
|
+
}
|
|
627
|
+
/** Backdrop clicks arrive as clicks on the <dialog> element itself */
|
|
628
|
+
OnDialogClick(event) {
|
|
629
|
+
if (event.target === this.panelRef?.nativeElement) {
|
|
630
|
+
this.CloseLauncher();
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
/** Native cancel (Esc while the browser owns the dialog) — route through
|
|
634
|
+
* our layered Escape handling instead of closing unconditionally. */
|
|
635
|
+
OnDialogCancel(event) {
|
|
636
|
+
event.preventDefault();
|
|
637
|
+
this.handleEscape();
|
|
638
|
+
}
|
|
639
|
+
/** Inline discard bar: confirm — perform the pending action, dropping changes */
|
|
640
|
+
ConfirmDiscard() {
|
|
641
|
+
const action = this.PendingDiscard;
|
|
642
|
+
this.PendingDiscard = null;
|
|
643
|
+
if (action === 'close') {
|
|
644
|
+
this.closeLauncher();
|
|
645
|
+
}
|
|
646
|
+
else if (action === 'exit') {
|
|
647
|
+
this.ExitConfigMode();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
/** Inline discard bar: keep editing */
|
|
651
|
+
CancelDiscard() {
|
|
652
|
+
this.PendingDiscard = null;
|
|
653
|
+
}
|
|
654
|
+
OnFilterChange() {
|
|
655
|
+
// Filtering re-renders the card set; nothing else to sync — focus stays
|
|
656
|
+
// in the input and Tab/arrows reach the new result set naturally.
|
|
657
|
+
}
|
|
658
|
+
/** Keyboard model on the filter input */
|
|
659
|
+
OnFilterKeydown(event) {
|
|
660
|
+
const visible = this.VisibleApps;
|
|
661
|
+
switch (event.key) {
|
|
662
|
+
case 'ArrowDown':
|
|
663
|
+
event.preventDefault();
|
|
664
|
+
this.focusCard(0);
|
|
665
|
+
break;
|
|
666
|
+
case 'Enter':
|
|
667
|
+
event.preventDefault();
|
|
668
|
+
// While filtering, Enter opens the top result (omnibar semantics —
|
|
669
|
+
// the footer advertises "↵ open"); unfiltered it needs an unambiguous
|
|
670
|
+
// single app.
|
|
671
|
+
if (this.IsFiltering && visible.length > 0) {
|
|
672
|
+
this.selectApp(visible[0]);
|
|
673
|
+
}
|
|
674
|
+
else if (visible.length === 1) {
|
|
675
|
+
this.selectApp(visible[0]);
|
|
676
|
+
}
|
|
677
|
+
break;
|
|
678
|
+
case 'Escape':
|
|
679
|
+
event.preventDefault();
|
|
680
|
+
// Stop the bubble: the panel's own Escape handler would otherwise
|
|
681
|
+
// re-evaluate AFTER we've mutated state and double-handle the key.
|
|
682
|
+
event.stopPropagation();
|
|
683
|
+
this.handleEscape();
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
/** Arrow-key navigation between focused cards */
|
|
688
|
+
OnCardKeydown(index, event) {
|
|
689
|
+
switch (event.key) {
|
|
690
|
+
case 'ArrowDown':
|
|
691
|
+
case 'ArrowRight':
|
|
692
|
+
event.preventDefault();
|
|
693
|
+
this.focusCard(index + 1);
|
|
694
|
+
break;
|
|
695
|
+
case 'ArrowUp':
|
|
696
|
+
case 'ArrowLeft':
|
|
697
|
+
event.preventDefault();
|
|
698
|
+
if (index === 0) {
|
|
699
|
+
this.filterInputRef?.nativeElement?.focus();
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
this.focusCard(index - 1);
|
|
703
|
+
}
|
|
704
|
+
break;
|
|
705
|
+
case 'Home':
|
|
706
|
+
event.preventDefault();
|
|
707
|
+
this.focusCard(0);
|
|
708
|
+
break;
|
|
709
|
+
case 'End':
|
|
710
|
+
event.preventDefault();
|
|
711
|
+
this.focusCard(this.VisibleApps.length - 1);
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/** Move DOM focus to the card at the given VisibleApps index (clamped) */
|
|
716
|
+
focusCard(index) {
|
|
717
|
+
const count = this.VisibleApps.length;
|
|
718
|
+
if (count === 0) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
const clamped = Math.min(count - 1, Math.max(0, index));
|
|
722
|
+
const el = document.getElementById(this.OptionId(clamped));
|
|
723
|
+
if (el) {
|
|
724
|
+
el.focus();
|
|
725
|
+
// jsdom (unit tests) doesn't implement scrollIntoView — guard it.
|
|
726
|
+
if (typeof el.scrollIntoView === 'function') {
|
|
727
|
+
el.scrollIntoView({ block: 'nearest' });
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
/** Keep Tab cycling within the launcher's real focusables (filter, close, Configure) */
|
|
732
|
+
/** Escape layering: discard bar → config view (guarded) → filter → close.
|
|
733
|
+
* Tab is NOT manually trapped — showModal() makes the background inert,
|
|
734
|
+
* which is a real trap (no leak when focus sits on a non-focusable). */
|
|
735
|
+
OnPanelKeydown(event) {
|
|
736
|
+
if (event.key === 'Escape') {
|
|
737
|
+
event.preventDefault();
|
|
738
|
+
event.stopPropagation();
|
|
739
|
+
this.handleEscape();
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
handleEscape() {
|
|
743
|
+
if (this.PendingDiscard) {
|
|
744
|
+
this.CancelDiscard();
|
|
745
|
+
}
|
|
746
|
+
else if (this.ConfigMode) {
|
|
747
|
+
this.RequestExitConfigMode();
|
|
748
|
+
}
|
|
749
|
+
else if (this.IsFiltering) {
|
|
750
|
+
this.FilterText = '';
|
|
751
|
+
this.filterInputRef?.nativeElement?.focus();
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
this.closeLauncher();
|
|
755
|
+
}
|
|
102
756
|
}
|
|
103
757
|
/**
|
|
104
|
-
* Select an application
|
|
105
|
-
* When viewing a system tab, always emit to allow returning to the app
|
|
758
|
+
* Select an application.
|
|
759
|
+
* When viewing a system tab, always emit to allow returning to the app.
|
|
106
760
|
*/
|
|
107
761
|
selectApp(app) {
|
|
108
|
-
this.
|
|
762
|
+
this.recordRecent(app);
|
|
763
|
+
this.closeLauncher(false); // Focus moves into the app, not back to the trigger
|
|
109
764
|
if (!UUIDsEqual(app.ID, this.activeApp?.ID) || this.isViewingSystemTab) {
|
|
110
765
|
this.appSelected.emit(app.ID);
|
|
111
766
|
}
|
|
@@ -118,92 +773,143 @@ export class AppSwitcherComponent {
|
|
|
118
773
|
IsLoadingApp(app) {
|
|
119
774
|
return UUIDsEqual(app.ID, this.loadingAppId);
|
|
120
775
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
776
|
+
// ---- Recents persistence (MJ: User Settings via UserInfoEngine) ----
|
|
777
|
+
loadRecents() {
|
|
778
|
+
try {
|
|
779
|
+
const raw = this.userInfoEngine.GetSetting(RECENT_APPS_KEY);
|
|
780
|
+
const parsed = raw ? JSON.parse(raw) : [];
|
|
781
|
+
this.recentEntries = Array.isArray(parsed)
|
|
782
|
+
? parsed.filter((e) => e && typeof e.id === 'string' && typeof e.ts === 'number')
|
|
783
|
+
: [];
|
|
784
|
+
}
|
|
785
|
+
catch {
|
|
786
|
+
this.recentEntries = [];
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
recordRecent(app) {
|
|
790
|
+
const id = NormalizeUUID(app.ID);
|
|
791
|
+
this.recentEntries = [
|
|
792
|
+
{ id, ts: Date.now() },
|
|
793
|
+
...this.recentEntries.filter(e => NormalizeUUID(e.id) !== id)
|
|
794
|
+
].slice(0, RECENT_STORE_MAX);
|
|
795
|
+
try {
|
|
796
|
+
this.userInfoEngine.SetSettingDebounced(RECENT_APPS_KEY, JSON.stringify(this.recentEntries));
|
|
797
|
+
}
|
|
798
|
+
catch {
|
|
799
|
+
// Settings persistence must never block app switching (e.g. engine not
|
|
800
|
+
// configured in tests or permission-constrained sessions).
|
|
128
801
|
}
|
|
129
802
|
}
|
|
130
803
|
/**
|
|
131
|
-
*
|
|
804
|
+
* Swap the launcher body to the app-configuration view (in-panel, seamless —
|
|
805
|
+
* no separate dialog). The embedded mj-user-app-config-content loads on
|
|
806
|
+
* creation.
|
|
132
807
|
*/
|
|
133
|
-
|
|
134
|
-
this.
|
|
135
|
-
this.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
808
|
+
EnterConfigMode() {
|
|
809
|
+
this.ConfigMode = true;
|
|
810
|
+
this.FilterText = '';
|
|
811
|
+
this.PendingDiscard = null;
|
|
812
|
+
// Move focus into the config view (the back button is its first focusable)
|
|
813
|
+
requestAnimationFrame(() => {
|
|
814
|
+
this.panelRef?.nativeElement
|
|
815
|
+
?.querySelector('.launcher-back')?.focus();
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
/** Guarded exit from the config view: unsaved changes surface the inline
|
|
819
|
+
* discard bar instead of silently dropping the user's edits. */
|
|
820
|
+
RequestExitConfigMode() {
|
|
821
|
+
if (this.configContent?.HasChanges()) {
|
|
822
|
+
this.PendingDiscard = 'exit';
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
this.ExitConfigMode();
|
|
142
826
|
}
|
|
143
827
|
/**
|
|
144
|
-
*
|
|
145
|
-
* The app list refreshes reactively: each UserApplication save
|
|
146
|
-
* event, UserInfoEngine's debounced refresh
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* which the `apps` getter reads. No explicit reload needed here.
|
|
828
|
+
* Return from the configuration view to the app grid.
|
|
829
|
+
* The app list refreshes reactively after a save: each UserApplication save
|
|
830
|
+
* fires a BaseEntity event, UserInfoEngine's debounced refresh emits
|
|
831
|
+
* DataChange$, and ApplicationManager.syncFromEngine() pushes the new list
|
|
832
|
+
* to applications$, which the `apps` getter reads. No explicit reload needed.
|
|
150
833
|
*/
|
|
151
|
-
|
|
152
|
-
this.
|
|
834
|
+
ExitConfigMode() {
|
|
835
|
+
this.ConfigMode = false;
|
|
836
|
+
// The config session may have changed the app count across the compact
|
|
837
|
+
// threshold — re-resolve the presentation for the view we're returning to.
|
|
838
|
+
// (The Applications stream also triggers this when the post-save engine
|
|
839
|
+
// refresh lands, whichever comes last wins with the freshest count.)
|
|
840
|
+
this.RefreshPresentation();
|
|
841
|
+
requestAnimationFrame(() => {
|
|
842
|
+
if (this.ShowFilter) {
|
|
843
|
+
this.filterInputRef?.nativeElement?.focus();
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
this.panelRef?.nativeElement?.querySelector('.app-card')?.focus();
|
|
847
|
+
}
|
|
848
|
+
});
|
|
153
849
|
}
|
|
154
|
-
static ɵfac = function AppSwitcherComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AppSwitcherComponent)(i0.ɵɵdirectiveInject(i1.ApplicationManager)); };
|
|
850
|
+
static ɵfac = function AppSwitcherComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AppSwitcherComponent)(i0.ɵɵdirectiveInject(i1.ApplicationManager), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
|
|
155
851
|
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AppSwitcherComponent, selectors: [["mj-app-switcher"]], viewQuery: function AppSwitcherComponent_Query(rf, ctx) { if (rf & 1) {
|
|
156
|
-
i0.ɵɵviewQuery(_c0, 5);
|
|
852
|
+
i0.ɵɵviewQuery(_c0, 5)(_c1, 5)(_c2, 5)(UserAppConfigContentComponent, 5);
|
|
157
853
|
} if (rf & 2) {
|
|
158
854
|
let _t;
|
|
159
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.
|
|
160
|
-
|
|
161
|
-
i0.ɵɵ
|
|
162
|
-
|
|
855
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.filterInputRef = _t.first);
|
|
856
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.triggerRef = _t.first);
|
|
857
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.panelRef = _t.first);
|
|
858
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.configContent = _t.first);
|
|
859
|
+
} }, inputs: { activeApp: "activeApp", isViewingSystemTab: "isViewingSystemTab", loadingAppId: "loadingAppId", SwitcherStyle: "SwitcherStyle" }, outputs: { appSelected: "appSelected" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 11, vars: 9, consts: [["trigger", ""], ["appCard", ""], ["panel", ""], ["filterInput", ""], [1, "app-switcher-container"], ["testId", "app-switcher-trigger", 1, "app-switcher-button", 3, "click", "mjClickable"], ["aria-hidden", "true", 1, "app-icon", "loading-spinner", "fa-solid", "fa-spinner", "fa-spin"], ["aria-hidden", "true", 1, "app-icon", 3, "class", "color"], [1, "app-label"], ["aria-hidden", "true", 1, "dropdown-arrow", "fa-solid", "fa-chevron-down"], ["role", "dialog", "aria-modal", "true", 1, "launcher-panel", 3, "launcher-panel--compact", "--anchor-left", "--anchor-top"], ["aria-hidden", "true", 1, "app-icon"], ["role", "dialog", "aria-modal", "true", 1, "launcher-panel", 3, "click", "cancel", "keydown"], [1, "launcher-head", "launcher-head--config"], ["type", "button", "aria-label", "Back to apps", 1, "launcher-back", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-arrow-left"], ["id", "mj-launcher-config-title", 1, "launcher-title"], ["type", "button", "aria-label", "Close application launcher", 1, "launcher-close", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-xmark"], ["role", "alert", 1, "launcher-discard-bar"], [1, "launcher-config-body", 3, "Saved", "Cancelled", "Provider"], ["aria-hidden", "true", 1, "fa-solid", "fa-triangle-exclamation"], [1, "discard-msg"], ["mjButton", "", "variant", "danger", "size", "sm", 3, "click"], ["mjButton", "", "size", "sm", 3, "click"], [1, "launcher-head"], [1, "launcher-scroll"], [1, "launcher-foot"], ["aria-hidden", "true", 1, "launcher-kbd-hints"], ["type", "button", "testId", "app-switcher-configure", 1, "launcher-configure", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-gear"], ["role", "status", 1, "sr-only"], [1, "launcher-filter"], ["aria-hidden", "true", 1, "fa-solid", "fa-magnifying-glass"], ["type", "text", "placeholder", "Find an app\u2026", "aria-label", "Find an app", "autocomplete", "off", "spellcheck", "false", 3, "input", "keydown", "value"], [1, "launcher-grid"], [1, "launcher-empty"], [4, "ngTemplateOutlet", "ngTemplateOutletContext"], [1, "launcher-sect", "launcher-sect--row"], ["type", "button", 1, "launcher-sort-toggle", 3, "click", "title"], ["aria-hidden", "true"], ["role", "group", "aria-label", "All applications", 1, "launcher-grid"], [1, "launcher-sect"], ["role", "group", "aria-label", "Recent applications", 1, "launcher-grid"], ["role", "link", 1, "app-card", 3, "click", "keydown", "id", "mjClickable", "testId"], [1, "app-card-glyph"], ["aria-hidden", "true", 1, "fa-solid", "fa-spinner", "fa-spin"], ["aria-hidden", "true", 3, "class"], [1, "app-card-text"], [1, "app-card-name"], [1, "app-card-name-label"], [1, "app-card-desc", 3, "id"]], template: function AppSwitcherComponent_Template(rf, ctx) { if (rf & 1) {
|
|
163
860
|
const _r1 = i0.ɵɵgetCurrentView();
|
|
164
|
-
i0.ɵɵelementStart(0, "div",
|
|
165
|
-
i0.ɵɵlistener("click", function AppSwitcherComponent_Template_div_click_1_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.
|
|
166
|
-
i0.ɵɵconditionalCreate(
|
|
167
|
-
i0.ɵɵconditionalCreate(
|
|
168
|
-
i0.ɵɵ
|
|
861
|
+
i0.ɵɵelementStart(0, "div", 4)(1, "div", 5, 0);
|
|
862
|
+
i0.ɵɵlistener("click", function AppSwitcherComponent_Template_div_click_1_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.ToggleLauncher()); });
|
|
863
|
+
i0.ɵɵconditionalCreate(3, AppSwitcherComponent_Conditional_3_Template, 1, 0, "i", 6);
|
|
864
|
+
i0.ɵɵconditionalCreate(4, AppSwitcherComponent_Conditional_4_Template, 1, 4, "i", 7);
|
|
865
|
+
i0.ɵɵelementStart(5, "span", 8);
|
|
866
|
+
i0.ɵɵtext(6);
|
|
169
867
|
i0.ɵɵelementEnd();
|
|
170
|
-
i0.ɵɵ
|
|
868
|
+
i0.ɵɵelement(7, "i", 9);
|
|
171
869
|
i0.ɵɵelementEnd();
|
|
172
|
-
i0.ɵɵ
|
|
173
|
-
i0.ɵɵtwoWayListener("ShowDialogChange", function AppSwitcherComponent_Template_mj_user_app_config_ShowDialogChange_6_listener($event) { i0.ɵɵrestoreView(_r1); i0.ɵɵtwoWayBindingSet(ctx.showConfigDialog, $event) || (ctx.showConfigDialog = $event); return i0.ɵɵresetView($event); });
|
|
174
|
-
i0.ɵɵlistener("ConfigSaved", function AppSwitcherComponent_Template_mj_user_app_config_ConfigSaved_6_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onConfigSaved()); });
|
|
870
|
+
i0.ɵɵconditionalCreate(8, AppSwitcherComponent_Conditional_8_Template, 4, 9, "dialog", 10);
|
|
175
871
|
i0.ɵɵelementEnd();
|
|
872
|
+
i0.ɵɵtemplate(9, AppSwitcherComponent_ng_template_9_Template, 9, 14, "ng-template", null, 1, i0.ɵɵtemplateRefExtractor);
|
|
176
873
|
} if (rf & 2) {
|
|
177
874
|
i0.ɵɵclassProp("loading", ctx.isLoading);
|
|
178
875
|
i0.ɵɵadvance();
|
|
179
|
-
i0.ɵɵ
|
|
180
|
-
i0.ɵɵ
|
|
181
|
-
i0.ɵɵconditional(ctx.isLoading ? 2 : -1);
|
|
182
|
-
i0.ɵɵadvance();
|
|
183
|
-
i0.ɵɵconditional(!ctx.isLoading ? 3 : -1);
|
|
876
|
+
i0.ɵɵproperty("mjClickable", ctx.TriggerAriaLabel);
|
|
877
|
+
i0.ɵɵattribute("aria-haspopup", "dialog")("aria-expanded", ctx.showDropdown);
|
|
184
878
|
i0.ɵɵadvance(2);
|
|
185
|
-
i0.ɵɵconditional(ctx.
|
|
879
|
+
i0.ɵɵconditional(ctx.isLoading ? 3 : -1);
|
|
186
880
|
i0.ɵɵadvance();
|
|
187
|
-
i0.ɵɵ
|
|
188
|
-
|
|
881
|
+
i0.ɵɵconditional(!ctx.isLoading ? 4 : -1);
|
|
882
|
+
i0.ɵɵadvance(2);
|
|
883
|
+
i0.ɵɵtextInterpolate(ctx.TriggerLabel);
|
|
884
|
+
i0.ɵɵadvance(2);
|
|
885
|
+
i0.ɵɵconditional(ctx.showDropdown ? 8 : -1);
|
|
886
|
+
} }, dependencies: [i2.NgTemplateOutlet, i3.MJClickableDirective, i3.MJButtonDirective, i4.UserAppConfigContentComponent], styles: [".app-switcher-container[_ngcontent-%COMP%] {\n position: relative;\n margin-left: calc(-1 * var(--mj-space-0-5)); \n\n}\n\n\n\n.app-switcher-button[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-1-5);\n padding: var(--mj-space-2) var(--mj-space-2-5);\n border-radius: var(--mj-radius-md);\n cursor: pointer;\n transition: background var(--mj-transition-fast);\n user-select: none;\n position: relative;\n}\n.app-switcher-button[_ngcontent-%COMP%] .app-icon[_ngcontent-%COMP%] {\n font-size: var(--mj-text-xl);\n width: 24px;\n text-align: center;\n position: relative;\n}\n.app-switcher-button[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n.app-switcher-button[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n\n\n\n.app-switcher-button[_ngcontent-%COMP%] .app-label[_ngcontent-%COMP%] {\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n max-width: 160px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.app-switcher-button[_ngcontent-%COMP%] .dropdown-arrow[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-muted);\n transition: color var(--mj-transition-fast);\n}\n\n\n\n.app-switcher-button[_ngcontent-%COMP%]:hover .dropdown-arrow[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n}\n.app-switcher-button[_ngcontent-%COMP%] .loading-spinner[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n}\n\n\n\n\n\n\n\n.launcher-panel[_ngcontent-%COMP%] {\n \n\n padding: 0;\n margin: 0;\n inset: unset;\n max-width: none;\n position: fixed;\n top: calc(60px + var(--mj-connectivity-banner-height, 0px) + var(--mj-space-6));\n left: 50%;\n transform: translateX(-50%);\n width: min(880px, calc(100vw - var(--mj-space-12)));\n max-height: calc(100vh - 60px - var(--mj-connectivity-banner-height, 0px) - var(--mj-space-12));\n background: var(--mj-bg-surface-elevated);\n color: inherit;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-xl);\n flex-direction: column;\n overflow: hidden;\n animation: _ngcontent-%COMP%_launcher-pop .13s ease-out;\n}\n.launcher-panel[open][_ngcontent-%COMP%] {\n display: flex;\n}\n.launcher-panel[_ngcontent-%COMP%]::backdrop {\n background: var(--mj-bg-overlay);\n animation: _ngcontent-%COMP%_launcher-fade-in var(--mj-transition-fast);\n}\n\n\n\n\n\n\n\n@media (min-width: 769px) {\n .launcher-panel--compact[_ngcontent-%COMP%] {\n left: var(--anchor-left);\n top: var(--anchor-top);\n transform: none;\n width: 320px;\n max-height: calc(100vh - var(--anchor-top) - var(--mj-space-6));\n box-shadow: var(--mj-shadow-lg);\n \n\n animation: _ngcontent-%COMP%_launcher-fade-in var(--mj-transition-fast);\n }\n .launcher-panel--compact[_ngcontent-%COMP%]::backdrop {\n background: transparent;\n animation: none;\n }\n .launcher-panel--compact[_ngcontent-%COMP%] .launcher-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n gap: var(--mj-space-1);\n }\n .launcher-panel--compact[_ngcontent-%COMP%] .launcher-scroll[_ngcontent-%COMP%] {\n padding: var(--mj-space-2);\n }\n .launcher-panel--compact[_ngcontent-%COMP%] .launcher-sect[_ngcontent-%COMP%] {\n display: none; \n\n }\n \n\n\n .launcher-panel--compact[_ngcontent-%COMP%] .app-card[_ngcontent-%COMP%] {\n padding: var(--mj-space-2) var(--mj-space-2-5);\n align-items: center;\n background: transparent;\n border-color: transparent;\n }\n .launcher-panel--compact[_ngcontent-%COMP%] .app-card[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n border-color: transparent;\n transform: none;\n }\n .launcher-panel--compact[_ngcontent-%COMP%] .app-card.active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n border-color: transparent;\n }\n .launcher-panel--compact[_ngcontent-%COMP%] .app-card[_ngcontent-%COMP%]:hover {\n transform: none;\n }\n .launcher-panel--compact[_ngcontent-%COMP%] .app-card-glyph[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n font-size: var(--mj-text-base);\n }\n .launcher-panel--compact[_ngcontent-%COMP%] .app-card-desc[_ngcontent-%COMP%] {\n display: none;\n }\n .launcher-panel--compact[_ngcontent-%COMP%] .launcher-kbd-hints[_ngcontent-%COMP%] {\n display: none;\n }\n}\n\n@keyframes _ngcontent-%COMP%_launcher-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n\n\n\n\n@keyframes _ngcontent-%COMP%_launcher-pop {\n from { opacity: 0; transform: translateX(-50%) scale(.985) translateY(-4px); }\n to { opacity: 1; transform: translateX(-50%) scale(1); }\n}\n\n\n\n.launcher-head[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n padding: var(--mj-space-4) var(--mj-space-4) var(--mj-space-2);\n flex-shrink: 0;\n}\n.launcher-filter[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2-5);\n flex: 1;\n height: 46px;\n padding: 0 var(--mj-space-3-5);\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n transition: border-color var(--mj-transition-fast);\n}\n.launcher-filter[_ngcontent-%COMP%]:focus-within {\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n.launcher-filter[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: var(--mj-text-sm);\n flex-shrink: 0;\n}\n.launcher-filter[_ngcontent-%COMP%] input[_ngcontent-%COMP%] {\n border: none;\n outline: none;\n background: transparent;\n width: 100%;\n font-family: inherit;\n font-size: var(--mj-text-base);\n color: var(--mj-text-primary);\n}\n.launcher-filter[_ngcontent-%COMP%] input[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-muted);\n}\n.launcher-close[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n flex-shrink: 0;\n border: none;\n border-radius: var(--mj-radius-md);\n background: transparent;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-base);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background var(--mj-transition-fast);\n}\n.launcher-close[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.launcher-close[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n\n\n\n.launcher-head--config[_ngcontent-%COMP%] {\n align-items: center;\n gap: var(--mj-space-2-5);\n padding-bottom: var(--mj-space-2);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.launcher-back[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n flex-shrink: 0;\n border: none;\n border-radius: var(--mj-radius-md);\n background: transparent;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-base);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background var(--mj-transition-fast);\n}\n.launcher-back[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.launcher-back[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n.launcher-title[_ngcontent-%COMP%] {\n flex: 1;\n margin: 0;\n font-size: var(--mj-text-base);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n}\n.launcher-config-body[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-height: 0;\n overflow: hidden;\n}\n\n\n\n.launcher-discard-bar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2-5);\n margin: var(--mj-space-2) var(--mj-space-4) 0;\n padding: var(--mj-space-2) var(--mj-space-3);\n background: var(--mj-status-warning-bg);\n border: 1px solid var(--mj-status-warning-border);\n border-radius: var(--mj-radius-md);\n flex-shrink: 0;\n}\n.launcher-discard-bar[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n}\n.launcher-discard-bar[_ngcontent-%COMP%] .discard-msg[_ngcontent-%COMP%] {\n flex: 1;\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-medium);\n color: var(--mj-status-warning-text);\n}\n\n\n\n.launcher-scroll[_ngcontent-%COMP%] {\n overflow-y: auto;\n padding: 0 var(--mj-space-4) var(--mj-space-4);\n flex: 1 1 auto;\n min-height: 0;\n}\n\n\n.launcher-sect[_ngcontent-%COMP%] {\n font-size: 10.5px;\n font-weight: var(--mj-font-bold);\n letter-spacing: .6px;\n text-transform: uppercase;\n color: var(--mj-text-muted);\n padding: var(--mj-space-3) var(--mj-space-1-5) var(--mj-space-1-5);\n}\n.launcher-sect--row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-right: var(--mj-space-1);\n}\n\n\n.launcher-sort-toggle[_ngcontent-%COMP%] {\n width: 28px;\n height: 28px;\n border: none;\n border-radius: var(--mj-radius-sm);\n background: transparent;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-xs);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background var(--mj-transition-fast), color var(--mj-transition-fast);\n}\n.launcher-sort-toggle[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.launcher-sort-toggle[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n.launcher-sort-toggle[aria-pressed=\"true\"][_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n.launcher-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: var(--mj-space-2-5);\n}\n.launcher-empty[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: var(--mj-text-sm);\n text-align: center;\n padding: var(--mj-space-6) var(--mj-space-2);\n}\n\n\n\n.app-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: var(--mj-space-3);\n \n\n\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n padding: var(--mj-space-3) var(--mj-space-3-5);\n cursor: pointer;\n min-width: 0;\n transition: border-color var(--mj-transition-fast), background var(--mj-transition-fast), transform var(--mj-transition-fast);\n}\n.app-card[_ngcontent-%COMP%]:hover {\n border-color: color-mix(in srgb, var(--mj-brand-primary) 45%, var(--mj-border-default));\n background: var(--mj-bg-surface-hover);\n transform: translateY(-1px);\n}\n.app-card[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -1px;\n}\n.app-card.active[_ngcontent-%COMP%] {\n \n\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n border-color: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent);\n}\n.app-card.loading[_ngcontent-%COMP%] {\n pointer-events: none;\n opacity: 0.7;\n}\n.app-card-glyph[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: var(--mj-radius-md);\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: var(--mj-text-lg);\n color: var(--app-color, var(--mj-text-muted));\n background: color-mix(in srgb, var(--app-color, var(--mj-text-muted)) 14%, var(--mj-bg-surface-card));\n}\n.app-card-text[_ngcontent-%COMP%] {\n min-width: 0;\n flex: 1;\n}\n.app-card-name[_ngcontent-%COMP%] {\n display: flex;\n align-items: baseline;\n gap: var(--mj-space-2);\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-bold);\n color: var(--mj-text-primary);\n}\n.app-card-name-label[_ngcontent-%COMP%] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.app-card.active[_ngcontent-%COMP%] .app-card-name[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n.app-card-desc[_ngcontent-%COMP%] {\n margin-top: var(--mj-space-0-5);\n font-size: var(--mj-text-xs);\n line-height: var(--mj-leading-snug);\n color: var(--mj-text-muted);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n\n\n\n\n.launcher-foot[_ngcontent-%COMP%] {\n border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-card);\n padding: var(--mj-space-2-5) var(--mj-space-4);\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n\n\n.launcher-configure[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n margin-left: auto;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-family: inherit;\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-semibold);\n padding: var(--mj-space-1-5) var(--mj-space-2);\n border-radius: var(--mj-radius-md);\n cursor: pointer;\n transition: background var(--mj-transition-fast), color var(--mj-transition-fast);\n}\n.launcher-configure[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.launcher-configure[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n.launcher-kbd-hints[_ngcontent-%COMP%] {\n display: flex;\n gap: var(--mj-space-4);\n font-size: 11.5px;\n color: var(--mj-text-muted);\n}\n\n\n\n.launcher-kbd-hints[_ngcontent-%COMP%] kbd[_ngcontent-%COMP%] {\n font-family: inherit;\n font-size: 10.5px;\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-bottom-width: 2px;\n border-radius: 5px;\n padding: 1.5px var(--mj-space-1-5);\n}\n\n\n\n.sr-only[_ngcontent-%COMP%] {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n\n\n@media (max-width: 900px) {\n .launcher-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n\n\n\n@media (max-width: 768px) {\n \n\n\n .launcher-panel[_ngcontent-%COMP%] {\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n transform: none;\n width: 100%;\n max-height: none;\n height: 100dvh;\n border-radius: 0;\n border: none;\n animation: none;\n }\n .launcher-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n .app-card[_ngcontent-%COMP%] {\n min-height: 48px;\n align-items: center;\n }\n .launcher-close[_ngcontent-%COMP%] {\n width: 46px;\n height: 46px;\n }\n .launcher-kbd-hints[_ngcontent-%COMP%] {\n display: none; \n\n }\n .app-switcher-button[_ngcontent-%COMP%] .app-label[_ngcontent-%COMP%] {\n display: none; \n\n }\n}\n\n\n\n@media (prefers-reduced-motion: reduce) {\n .launcher-panel[_ngcontent-%COMP%], \n .launcher-panel[_ngcontent-%COMP%]::backdrop {\n animation: none;\n }\n .app-card[_ngcontent-%COMP%], \n .app-card[_ngcontent-%COMP%]:hover {\n transition: none;\n transform: none;\n }\n}"] });
|
|
189
887
|
}
|
|
190
888
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AppSwitcherComponent, [{
|
|
191
889
|
type: Component,
|
|
192
|
-
args: [{ standalone: false, selector: 'mj-app-switcher', template: "<div class=\"app-switcher-container\" [class.loading]=\"isLoading\">\n <div class=\"app-switcher-button\"\n mjClickable=\"Switch application\"\n testId=\"app-switcher-trigger\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-expanded]=\"showDropdown\"\n (click)=\"toggleDropdown()\">\n <!-- Show spinner when loading, otherwise show app icon -->\n @if (isLoading) {\n <i class=\"app-icon loading-spinner fa-solid fa-spinner fa-spin\"></i>\n }\n @if (!isLoading) {\n <i class=\"app-icon\" [class]=\"activeApp?.Icon || 'fa-solid fa-cube'\"\n [style.color]=\"activeApp?.GetColor()\"\n [style.--active-color]=\"activeApp?.GetColor()\"></i>\n }\n <i class=\"dropdown-arrow fa-solid fa-chevron-down\"></i>\n </div>\n\n @if (showDropdown) {\n <div class=\"app-switcher-dropdown\">\n <div class=\"app-switcher-list\">\n @for (app of apps; track app) {\n <div\n class=\"app-switcher-item\"\n [mjClickable]=\"app.Name\"\n [testId]=\"'app-switcher-item-' + app.Name\"\n [attr.aria-current]=\"IsActiveApp(app) ? 'true' : null\"\n [class.active]=\"IsActiveApp(app)\"\n [class.loading]=\"IsLoadingApp(app)\"\n [style.--item-color]=\"app.GetColor()\"\n (click)=\"selectApp(app)\">\n @if (IsLoadingApp(app)) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n }\n @if (!IsLoadingApp(app)) {\n <i [class]=\"app.Icon || 'fa-solid fa-cube'\"></i>\n }\n <span>{{ app.Name }}</span>\n </div>\n }\n </div>\n <!-- Divider + Configure pinned at bottom -->\n <div class=\"app-switcher-divider\"></div>\n <div class=\"app-switcher-item configure-item\" mjClickable=\"Configure applications\" testId=\"app-switcher-configure\" (click)=\"openConfigDialog()\">\n <i class=\"fa-solid fa-gear\"></i>\n <span>Configure...</span>\n </div>\n </div>\n }\n</div>\n\n<!-- App Configuration Dialog -->\n<mj-user-app-config\n #appConfigDialog\n [(ShowDialog)]=\"showConfigDialog\"\n (ConfigSaved)=\"onConfigSaved()\">\n</mj-user-app-config>\n", styles: [".app-switcher-container {\n position: relative;\n margin-left: -2px; /* Fine-tune alignment when no nav-bar icons to the left */\n}\n\n.app-switcher-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.15s;\n user-select: none;\n position: relative;\n}\n.app-switcher-button .app-icon {\n font-size: 20px;\n width: 24px;\n text-align: center;\n position: relative;\n}\n/* Color underline under the icon only, not the caret */\n.app-switcher-button .app-icon::after {\n content: \"\";\n position: absolute;\n bottom: -6px;\n left: 2px;\n right: 2px;\n height: 3px;\n border-radius: 2px;\n background-color: var(--active-color, var(--mj-text-muted));\n}\n.app-switcher-button:hover {\n background: var(--mj-bg-surface-hover);\n}\n.app-switcher-button .dropdown-arrow {\n font-size: 10px;\n color: var(--mj-text-disabled);\n}\n\n.app-switcher-dropdown {\n position: absolute;\n top: calc(100% + 8px);\n left: 0;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: var(--mj-shadow-lg);\n min-width: 220px;\n width: max-content;\n overflow: hidden;\n z-index: 10000;\n max-height: calc(100vh - 80px - var(--mj-connectivity-banner-height, 0px));\n display: flex;\n flex-direction: column;\n}\n.app-switcher-list {\n overflow-y: auto;\n flex: 1 1 auto;\n min-height: 0;\n}\n.app-switcher-dropdown .app-switcher-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n cursor: pointer;\n transition: all 0.15s;\n font-weight: 500;\n color: var(--mj-text-secondary);\n position: relative;\n white-space: nowrap;\n}\n.app-switcher-dropdown .app-switcher-item::before {\n content: \"\";\n position: absolute;\n left: 4px;\n top: 8px;\n bottom: 8px;\n width: 3px;\n border-radius: 2px;\n background-color: var(--item-color, var(--mj-text-muted));\n opacity: 0.8;\n transition: all 0.15s;\n}\n.app-switcher-dropdown .app-switcher-item i {\n font-size: 18px;\n color: var(--item-color, var(--mj-text-muted));\n width: 20px;\n text-align: center;\n opacity: 1;\n transition: all 0.15s;\n}\n.app-switcher-dropdown .app-switcher-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n.app-switcher-dropdown .app-switcher-item:hover::before {\n opacity: 1;\n}\n.app-switcher-dropdown .app-switcher-item.active {\n background: color-mix(in srgb, var(--item-color, var(--mj-brand-primary)) 10%, var(--mj-bg-surface-elevated));\n color: var(--item-color, var(--mj-brand-primary));\n font-weight: 600;\n}\n.app-switcher-dropdown .app-switcher-item.active::before {\n width: 4px;\n left: 3px;\n opacity: 1;\n}\n/* Configure option - no color line, neutral gray styling */\n.app-switcher-dropdown .app-switcher-item.configure-item {\n color: var(--mj-text-secondary);\n}\n.app-switcher-dropdown .app-switcher-item.configure-item::before {\n display: none;\n}\n.app-switcher-dropdown .app-switcher-item.configure-item i {\n color: var(--mj-text-muted);\n}\n.app-switcher-dropdown .app-switcher-item.configure-item:hover {\n color: var(--mj-text-primary);\n}\n.app-switcher-dropdown .app-switcher-item.configure-item:hover i {\n color: var(--mj-text-primary);\n}\n.app-switcher-dropdown .app-switcher-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 12px;\n}\n\n/* Loading state for app switcher button */\n.app-switcher-button .loading-spinner {\n color: var(--mj-text-muted);\n}\n.app-switcher-button .loading-spinner::after {\n /* Hide the underline when showing spinner */\n display: none;\n}\n\n/* Loading state for dropdown items */\n.app-switcher-dropdown .app-switcher-item.loading {\n pointer-events: none;\n opacity: 0.7;\n}\n.app-switcher-dropdown .app-switcher-item.loading i {\n color: var(--item-color, var(--mj-text-muted));\n}\n\n/* Mobile: full-width dropdown with scroll */\n@media (max-width: 600px) {\n .app-switcher-dropdown {\n position: fixed;\n top: calc(60px + var(--mj-connectivity-banner-height, 0px));\n left: 0;\n right: 0;\n width: 100%;\n min-width: unset;\n height: calc(100vh - 60px - var(--mj-connectivity-banner-height, 0px));\n overflow-y: auto;\n border-radius: 0;\n border-top: none;\n padding: 0.25rem 0;\n }\n\n .app-switcher-dropdown .app-switcher-item {\n padding: 14px 20px;\n gap: 14px;\n min-height: 48px;\n font-size: 0.9375rem;\n }\n\n .app-switcher-dropdown .app-switcher-item i {\n font-size: 20px;\n width: 24px;\n }\n\n .app-switcher-list {\n overflow-y: visible;\n flex: none;\n }\n}\n"] }]
|
|
193
|
-
}], () => [{ type: i1.ApplicationManager }], { activeApp: [{
|
|
890
|
+
args: [{ standalone: false, selector: 'mj-app-switcher', template: "<div class=\"app-switcher-container\" [class.loading]=\"isLoading\">\n <div class=\"app-switcher-button\"\n #trigger\n [mjClickable]=\"TriggerAriaLabel\"\n testId=\"app-switcher-trigger\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"showDropdown\"\n (click)=\"ToggleLauncher()\">\n <!-- Show spinner when loading, otherwise show app icon -->\n @if (isLoading) {\n <i class=\"app-icon loading-spinner fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n }\n @if (!isLoading) {\n <i class=\"app-icon\" [class]=\"activeApp?.Icon || 'fa-solid fa-cube'\"\n [style.color]=\"activeApp?.GetColor()\" aria-hidden=\"true\"></i>\n }\n <!-- Current app name; hidden at the mobile breakpoint (icon-only trigger) -->\n <span class=\"app-label\">{{ TriggerLabel }}</span>\n <i class=\"dropdown-arrow fa-solid fa-chevron-down\" aria-hidden=\"true\"></i>\n </div>\n\n @if (showDropdown) {\n <!-- Native <dialog> + showModal(): lives in the browser TOP LAYER (above\n every app stacking context \u2014 chat FAB, toasts), background is inert\n (real focus trap), and focus restores to the trigger on close. The\n backdrop is ::backdrop; clicks on it arrive as clicks on the dialog\n element itself (OnDialogClick). -->\n <dialog class=\"launcher-panel\"\n #panel\n role=\"dialog\"\n aria-modal=\"true\"\n [class.launcher-panel--compact]=\"CompactMode && !ConfigMode\"\n [style.--anchor-left]=\"AnchorLeft + 'px'\"\n [style.--anchor-top]=\"AnchorTop + 'px'\"\n [attr.aria-label]=\"ConfigMode ? null : 'Application launcher'\"\n [attr.aria-labelledby]=\"ConfigMode ? 'mj-launcher-config-title' : null\"\n (click)=\"OnDialogClick($event)\"\n (cancel)=\"OnDialogCancel($event)\"\n (keydown)=\"OnPanelKeydown($event)\">\n\n @if (ConfigMode) {\n <!-- Configuration view: same panel, body swapped -->\n <div class=\"launcher-head launcher-head--config\">\n <button class=\"launcher-back\" type=\"button\" aria-label=\"Back to apps\" (click)=\"RequestExitConfigMode()\">\n <i class=\"fa-solid fa-arrow-left\" aria-hidden=\"true\"></i>\n </button>\n <h2 class=\"launcher-title\" id=\"mj-launcher-config-title\">Configure apps</h2>\n <button class=\"launcher-close\" type=\"button\" aria-label=\"Close application launcher\" (click)=\"CloseLauncher()\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n @if (PendingDiscard) {\n <div class=\"launcher-discard-bar\" role=\"alert\">\n <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n <span class=\"discard-msg\">You have unsaved changes.</span>\n <button mjButton variant=\"danger\" size=\"sm\" (click)=\"ConfirmDiscard()\">Discard</button>\n <button mjButton size=\"sm\" (click)=\"CancelDiscard()\">Keep editing</button>\n </div>\n }\n <mj-user-app-config-content\n class=\"launcher-config-body\"\n [Provider]=\"Provider\"\n (Saved)=\"ExitConfigMode()\"\n (Cancelled)=\"RequestExitConfigMode()\">\n </mj-user-app-config-content>\n } @else {\n @if (ShowFilter) {\n <div class=\"launcher-head\">\n <div class=\"launcher-filter\">\n <i class=\"fa-solid fa-magnifying-glass\" aria-hidden=\"true\"></i>\n <input\n #filterInput\n type=\"text\"\n placeholder=\"Find an app\u2026\"\n aria-label=\"Find an app\"\n [value]=\"FilterText\"\n (input)=\"OnFilterInput($event)\"\n (keydown)=\"OnFilterKeydown($event)\"\n autocomplete=\"off\"\n spellcheck=\"false\" />\n </div>\n <button class=\"launcher-close\" type=\"button\" aria-label=\"Close application launcher\" (click)=\"CloseLauncher()\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n <div class=\"launcher-scroll\">\n @if (IsFiltering) {\n <div class=\"launcher-grid\">\n @for (app of VisibleApps; track app.ID; let i = $index) {\n <ng-container *ngTemplateOutlet=\"appCard; context: { $implicit: app, idx: i }\"></ng-container>\n }\n </div>\n @if (VisibleApps.length === 0) {\n <div class=\"launcher-empty\">No apps match \"{{ FilterText }}\"</div>\n }\n } @else {\n @if (RecentApps.length > 0) {\n <div class=\"launcher-sect\">Recent</div>\n <div class=\"launcher-grid\" role=\"group\" aria-label=\"Recent applications\">\n @for (app of RecentApps; track app.ID; let i = $index) {\n <ng-container *ngTemplateOutlet=\"appCard; context: { $implicit: app, idx: i }\"></ng-container>\n }\n </div>\n }\n <div class=\"launcher-sect launcher-sect--row\">\n <span>All apps</span>\n <button class=\"launcher-sort-toggle\" type=\"button\"\n [attr.aria-pressed]=\"SortMode === 'alpha'\"\n [attr.aria-label]=\"SortMode === 'alpha' ? 'Sorted alphabetically \u2014 switch to your custom order' : 'Sorted by your custom order \u2014 switch to alphabetical'\"\n [title]=\"SortMode === 'alpha' ? 'Sorted A\u2013Z \u2014 click for your custom order' : 'Custom order \u2014 click for A\u2013Z'\"\n (click)=\"ToggleSortMode()\">\n <i [class]=\"SortMode === 'alpha' ? 'fa-solid fa-arrow-down-a-z' : 'fa-solid fa-bars-staggered'\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div class=\"launcher-grid\" role=\"group\" aria-label=\"All applications\">\n @for (app of OtherApps; track app.ID; let i = $index) {\n <ng-container *ngTemplateOutlet=\"appCard; context: { $implicit: app, idx: i + RecentApps.length }\"></ng-container>\n }\n </div>\n @if (VisibleApps.length === 0) {\n <div class=\"launcher-empty\">\n No applications are enabled for you yet \u2014 use Configure\u2026 below to add some.\n </div>\n }\n }\n </div>\n\n <div class=\"launcher-foot\">\n <span class=\"launcher-kbd-hints\" aria-hidden=\"true\">\n <span><kbd>\u2191</kbd><kbd>\u2193</kbd> navigate</span>\n <span><kbd>\u21B5</kbd> open</span>\n <span><kbd>esc</kbd> close</span>\n </span>\n <button class=\"launcher-configure\" type=\"button\" testId=\"app-switcher-configure\" (click)=\"EnterConfigMode()\">\n <i class=\"fa-solid fa-gear\" aria-hidden=\"true\"></i>\n <span>Configure\u2026</span>\n </button>\n </div>\n\n <span class=\"sr-only\" role=\"status\">{{ AnnouncedAppCount }} applications available</span>\n }\n </dialog>\n }\n</div>\n\n<!-- Shared app-card template: each card is a real focusable control (mjClickable\n adds role, tabindex 0, aria-label, and Enter/Space activation), so Tab walks\n the cards and arrow keys are the fast path. -->\n<ng-template #appCard let-app let-idx=\"idx\">\n <div class=\"app-card\"\n [id]=\"OptionId(idx)\"\n [mjClickable]=\"app.Name\"\n role=\"link\"\n [testId]=\"'app-switcher-item-' + app.Name\"\n [attr.aria-current]=\"IsActiveApp(app) ? 'true' : null\"\n [attr.aria-describedby]=\"app.Description ? OptionId(idx) + '-desc' : null\"\n [class.active]=\"IsActiveApp(app)\"\n [class.loading]=\"IsLoadingApp(app)\"\n [style.--app-color]=\"app.GetColor()\"\n (click)=\"selectApp(app)\"\n (keydown)=\"OnCardKeydown(idx, $event)\">\n <div class=\"app-card-glyph\">\n @if (IsLoadingApp(app)) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i [class]=\"app.Icon || 'fa-solid fa-cube'\" aria-hidden=\"true\"></i>\n }\n </div>\n <div class=\"app-card-text\">\n <div class=\"app-card-name\">\n <span class=\"app-card-name-label\">{{ app.Name }}</span>\n </div>\n @if (app.Description) {\n <div class=\"app-card-desc\" [id]=\"OptionId(idx) + '-desc'\">{{ app.Description }}</div>\n }\n </div>\n </div>\n</ng-template>\n\n", styles: [".app-switcher-container {\n position: relative;\n margin-left: calc(-1 * var(--mj-space-0-5)); /* Fine-tune alignment when no nav-bar icons to the left */\n}\n\n/* ---- Trigger button (header) ---- */\n.app-switcher-button {\n display: flex;\n align-items: center;\n gap: var(--mj-space-1-5);\n padding: var(--mj-space-2) var(--mj-space-2-5);\n border-radius: var(--mj-radius-md);\n cursor: pointer;\n transition: background var(--mj-transition-fast);\n user-select: none;\n position: relative;\n}\n.app-switcher-button .app-icon {\n font-size: var(--mj-text-xl);\n width: 24px;\n text-align: center;\n position: relative;\n}\n.app-switcher-button:hover {\n background: var(--mj-bg-surface-hover);\n}\n.app-switcher-button:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n/* Current-app name in the trigger. Capped at 160px: no current catalog name\n truncates at this width \u2014 the ellipsis only guards future/client apps. */\n.app-switcher-button .app-label {\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n max-width: 160px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.app-switcher-button .dropdown-arrow {\n font-size: 10px;\n color: var(--mj-text-muted);\n transition: color var(--mj-transition-fast);\n}\n/* text-disabled sat nearly on top of --mj-bg-surface-hover in dark mode \u2014\n the chevron vanished on hover. Muted at rest, secondary on hover. */\n.app-switcher-button:hover .dropdown-arrow {\n color: var(--mj-text-secondary);\n}\n.app-switcher-button .loading-spinner {\n color: var(--mj-text-muted);\n}\n\n/* ---- Launcher overlay ----\n The panel is a native <dialog> opened with showModal(): it renders in the\n browser TOP LAYER (above every app stacking context, incl. the header's\n z-index:500 context, the chat FAB, and toasts), the page behind it is\n inert, and the whole-screen scrim is the dialog's ::backdrop. */\n.launcher-panel {\n /* Reset dialog UA defaults, then apply the command-surface spec */\n padding: 0;\n margin: 0;\n inset: unset;\n max-width: none;\n position: fixed;\n top: calc(60px + var(--mj-connectivity-banner-height, 0px) + var(--mj-space-6));\n left: 50%;\n transform: translateX(-50%);\n width: min(880px, calc(100vw - var(--mj-space-12)));\n max-height: calc(100vh - 60px - var(--mj-connectivity-banner-height, 0px) - var(--mj-space-12));\n background: var(--mj-bg-surface-elevated);\n color: inherit;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-xl);\n flex-direction: column;\n overflow: hidden;\n animation: launcher-pop .13s ease-out;\n}\n.launcher-panel[open] {\n display: flex;\n}\n.launcher-panel::backdrop {\n background: var(--mj-bg-overlay);\n animation: launcher-fade-in var(--mj-transition-fast);\n}\n\n/* ---- Compact mode (few apps / Shell.AppSwitcher.Style) ----\n Anchored under the trigger like a dropdown: no dim, single-column rows,\n no recents/filter. Entering the Configure view drops this class, promoting\n the panel back to the centered command surface (the config layout needs\n the width). Desktop-only \u2014 the mobile sheet below overrides regardless. */\n@media (min-width: 769px) {\n .launcher-panel--compact {\n left: var(--anchor-left);\n top: var(--anchor-top);\n transform: none;\n width: 320px;\n max-height: calc(100vh - var(--anchor-top) - var(--mj-space-6));\n box-shadow: var(--mj-shadow-lg);\n /* The pop animation animates translateX(-50%), which compact doesn't use */\n animation: launcher-fade-in var(--mj-transition-fast);\n }\n .launcher-panel--compact::backdrop {\n background: transparent;\n animation: none;\n }\n .launcher-panel--compact .launcher-grid {\n grid-template-columns: 1fr;\n gap: var(--mj-space-1);\n }\n .launcher-panel--compact .launcher-scroll {\n padding: var(--mj-space-2);\n }\n .launcher-panel--compact .launcher-sect {\n display: none; /* Single flat list \u2014 \"All apps\" header is noise here */\n }\n /* Compact rows are flat menu rows (not card blocks) with an explicit\n hover fill; active keeps the shared brand tint. */\n .launcher-panel--compact .app-card {\n padding: var(--mj-space-2) var(--mj-space-2-5);\n align-items: center;\n background: transparent;\n border-color: transparent;\n }\n .launcher-panel--compact .app-card:hover {\n background: var(--mj-bg-surface-hover);\n border-color: transparent;\n transform: none;\n }\n .launcher-panel--compact .app-card.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n border-color: transparent;\n }\n .launcher-panel--compact .app-card:hover {\n transform: none;\n }\n .launcher-panel--compact .app-card-glyph {\n width: 32px;\n height: 32px;\n font-size: var(--mj-text-base);\n }\n .launcher-panel--compact .app-card-desc {\n display: none;\n }\n .launcher-panel--compact .launcher-kbd-hints {\n display: none;\n }\n}\n\n@keyframes launcher-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n/* Desktop pop matches the omnibar's ob-pop exactly. The from-state keeps the\n layout translateX(-50%); on the mobile sheet (base transform: none) this\n animation is disabled rather than redefined \u2014 redefining keyframes inside\n the media query is what caused the jump-in bug. */\n@keyframes launcher-pop {\n from { opacity: 0; transform: translateX(-50%) scale(.985) translateY(-4px); }\n to { opacity: 1; transform: translateX(-50%) scale(1); }\n}\n\n/* ---- Head: filter + close ---- */\n.launcher-head {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n padding: var(--mj-space-4) var(--mj-space-4) var(--mj-space-2);\n flex-shrink: 0;\n}\n.launcher-filter {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2-5);\n flex: 1;\n height: 46px;\n padding: 0 var(--mj-space-3-5);\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n transition: border-color var(--mj-transition-fast);\n}\n.launcher-filter:focus-within {\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n.launcher-filter i {\n color: var(--mj-text-muted);\n font-size: var(--mj-text-sm);\n flex-shrink: 0;\n}\n.launcher-filter input {\n border: none;\n outline: none;\n background: transparent;\n width: 100%;\n font-family: inherit;\n font-size: var(--mj-text-base);\n color: var(--mj-text-primary);\n}\n.launcher-filter input::placeholder {\n color: var(--mj-text-muted);\n}\n.launcher-close {\n width: 40px;\n height: 40px;\n flex-shrink: 0;\n border: none;\n border-radius: var(--mj-radius-md);\n background: transparent;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-base);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background var(--mj-transition-fast);\n}\n.launcher-close:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.launcher-close:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n\n/* ---- In-panel configuration view (view swap) ---- */\n.launcher-head--config {\n align-items: center;\n gap: var(--mj-space-2-5);\n padding-bottom: var(--mj-space-2);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.launcher-back {\n width: 40px;\n height: 40px;\n flex-shrink: 0;\n border: none;\n border-radius: var(--mj-radius-md);\n background: transparent;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-base);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background var(--mj-transition-fast);\n}\n.launcher-back:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.launcher-back:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n.launcher-title {\n flex: 1;\n margin: 0;\n font-size: var(--mj-text-base);\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n}\n.launcher-config-body {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-height: 0;\n overflow: hidden;\n}\n/* Inline unsaved-changes bar: lives inside the panel because a body-appended\n confirm dialog would sit beneath (and inert to) the top layer. */\n.launcher-discard-bar {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2-5);\n margin: var(--mj-space-2) var(--mj-space-4) 0;\n padding: var(--mj-space-2) var(--mj-space-3);\n background: var(--mj-status-warning-bg);\n border: 1px solid var(--mj-status-warning-border);\n border-radius: var(--mj-radius-md);\n flex-shrink: 0;\n}\n.launcher-discard-bar i {\n color: var(--mj-status-warning);\n}\n.launcher-discard-bar .discard-msg {\n flex: 1;\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-medium);\n color: var(--mj-status-warning-text);\n}\n\n/* ---- Scrollable card area ---- */\n.launcher-scroll {\n overflow-y: auto;\n padding: 0 var(--mj-space-4) var(--mj-space-4);\n flex: 1 1 auto;\n min-height: 0;\n}\n/* Typography matches the omnibar's .ob-group-label */\n.launcher-sect {\n font-size: 10.5px;\n font-weight: var(--mj-font-bold);\n letter-spacing: .6px;\n text-transform: uppercase;\n color: var(--mj-text-muted);\n padding: var(--mj-space-3) var(--mj-space-1-5) var(--mj-space-1-5);\n}\n.launcher-sect--row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-right: var(--mj-space-1);\n}\n/* All-apps sort toggle: quiet icon button, brand-tinted when non-default */\n.launcher-sort-toggle {\n width: 28px;\n height: 28px;\n border: none;\n border-radius: var(--mj-radius-sm);\n background: transparent;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-xs);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background var(--mj-transition-fast), color var(--mj-transition-fast);\n}\n.launcher-sort-toggle:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.launcher-sort-toggle:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n.launcher-sort-toggle[aria-pressed=\"true\"] {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n.launcher-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: var(--mj-space-2-5);\n}\n.launcher-empty {\n color: var(--mj-text-muted);\n font-size: var(--mj-text-sm);\n text-align: center;\n padding: var(--mj-space-6) var(--mj-space-2);\n}\n\n/* ---- App cards ---- */\n.app-card {\n display: flex;\n align-items: flex-start;\n gap: var(--mj-space-3);\n /* surface-card, NOT surface: on the elevated panel, plain surface is the\n same white in light mode and the cards visually vanish */\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n padding: var(--mj-space-3) var(--mj-space-3-5);\n cursor: pointer;\n min-width: 0;\n transition: border-color var(--mj-transition-fast), background var(--mj-transition-fast), transform var(--mj-transition-fast);\n}\n.app-card:hover {\n border-color: color-mix(in srgb, var(--mj-brand-primary) 45%, var(--mj-border-default));\n background: var(--mj-bg-surface-hover);\n transform: translateY(-1px);\n}\n.app-card:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: -1px;\n}\n.app-card.active {\n /* Shared active-tint recipe */\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n border-color: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent);\n}\n.app-card.loading {\n pointer-events: none;\n opacity: 0.7;\n}\n.app-card-glyph {\n width: 40px;\n height: 40px;\n border-radius: var(--mj-radius-md);\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: var(--mj-text-lg);\n color: var(--app-color, var(--mj-text-muted));\n background: color-mix(in srgb, var(--app-color, var(--mj-text-muted)) 14%, var(--mj-bg-surface-card));\n}\n.app-card-text {\n min-width: 0;\n flex: 1;\n}\n.app-card-name {\n display: flex;\n align-items: baseline;\n gap: var(--mj-space-2);\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-bold);\n color: var(--mj-text-primary);\n}\n.app-card-name-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.app-card.active .app-card-name {\n color: var(--mj-brand-primary);\n}\n.app-card-desc {\n margin-top: var(--mj-space-0-5);\n font-size: var(--mj-text-xs);\n line-height: var(--mj-leading-snug);\n color: var(--mj-text-muted);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* ---- Footer ---- */\n/* Footer tone matches the omnibar's .ob-foot */\n.launcher-foot {\n border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-card);\n padding: var(--mj-space-2-5) var(--mj-space-4);\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n/* Footer options button \u2014 shared spec with the omnibar's Settings button:\n quiet labeled gear, right-aligned. */\n.launcher-configure {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n margin-left: auto;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-family: inherit;\n font-size: var(--mj-text-sm);\n font-weight: var(--mj-font-semibold);\n padding: var(--mj-space-1-5) var(--mj-space-2);\n border-radius: var(--mj-radius-md);\n cursor: pointer;\n transition: background var(--mj-transition-fast), color var(--mj-transition-fast);\n}\n.launcher-configure:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.launcher-configure:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n}\n.launcher-kbd-hints {\n display: flex;\n gap: var(--mj-space-4);\n font-size: 11.5px;\n color: var(--mj-text-muted);\n}\n/* kbd chips: the canonical ob-kbd chrome (10.5px / 5px radius / bottom-heavy\n border) \u2014 same spec the omnibar and the header search button use. */\n.launcher-kbd-hints kbd {\n font-family: inherit;\n font-size: 10.5px;\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-bottom-width: 2px;\n border-radius: 5px;\n padding: 1.5px var(--mj-space-1-5);\n}\n\n/* ---- Screen-reader-only live region ---- */\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n/* ---- Responsive ---- */\n@media (max-width: 900px) {\n .launcher-grid {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n/* Mobile: full sheet below the header, single-column cards, 48px+ targets.\n Breakpoint matches the shell's 768px mobile breakpoint. */\n@media (max-width: 768px) {\n /* Full-screen sheet, matching the omnibar's mobile treatment. The pop\n animation is disabled (its from-state translateX no longer applies). */\n .launcher-panel {\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n transform: none;\n width: 100%;\n max-height: none;\n height: 100dvh;\n border-radius: 0;\n border: none;\n animation: none;\n }\n .launcher-grid {\n grid-template-columns: 1fr;\n }\n .app-card {\n min-height: 48px;\n align-items: center;\n }\n .launcher-close {\n width: 46px;\n height: 46px;\n }\n .launcher-kbd-hints {\n display: none; /* No hardware-keyboard hints on touch */\n }\n .app-switcher-button .app-label {\n display: none; /* Icon-only trigger \u2014 the 390px header has no room for a name */\n }\n}\n\n/* ---- Reduced motion ---- */\n@media (prefers-reduced-motion: reduce) {\n .launcher-panel,\n .launcher-panel::backdrop {\n animation: none;\n }\n .app-card,\n .app-card:hover {\n transition: none;\n transform: none;\n }\n}\n"] }]
|
|
891
|
+
}], () => [{ type: i1.ApplicationManager }, { type: i0.ChangeDetectorRef }], { activeApp: [{
|
|
194
892
|
type: Input
|
|
195
893
|
}], isViewingSystemTab: [{
|
|
196
894
|
type: Input
|
|
197
895
|
}], loadingAppId: [{
|
|
198
896
|
type: Input
|
|
897
|
+
}], SwitcherStyle: [{
|
|
898
|
+
type: Input
|
|
199
899
|
}], appSelected: [{
|
|
200
900
|
type: Output
|
|
201
|
-
}],
|
|
901
|
+
}], filterInputRef: [{
|
|
902
|
+
type: ViewChild,
|
|
903
|
+
args: ['filterInput']
|
|
904
|
+
}], triggerRef: [{
|
|
905
|
+
type: ViewChild,
|
|
906
|
+
args: ['trigger']
|
|
907
|
+
}], panelRef: [{
|
|
908
|
+
type: ViewChild,
|
|
909
|
+
args: ['panel']
|
|
910
|
+
}], configContent: [{
|
|
202
911
|
type: ViewChild,
|
|
203
|
-
args: [
|
|
204
|
-
}], onClickOutside: [{
|
|
205
|
-
type: HostListener,
|
|
206
|
-
args: ['document:click', ['$event']]
|
|
912
|
+
args: [UserAppConfigContentComponent]
|
|
207
913
|
}] }); })();
|
|
208
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AppSwitcherComponent, { className: "AppSwitcherComponent", filePath: "src/lib/shell/components/header/app-switcher.component.ts", lineNumber:
|
|
914
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AppSwitcherComponent, { className: "AppSwitcherComponent", filePath: "src/lib/shell/components/header/app-switcher.component.ts", lineNumber: 63 }); })();
|
|
209
915
|
//# sourceMappingURL=app-switcher.component.js.map
|