@franzzemen/brokenstock-shell 0.0.3 → 0.0.5
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.
|
@@ -23,6 +23,8 @@ import * as i4 from '@angular/material/input';
|
|
|
23
23
|
import { MatInput, MatInputModule } from '@angular/material/input';
|
|
24
24
|
import * as i6 from '@angular/material/button';
|
|
25
25
|
import { MatButtonModule } from '@angular/material/button';
|
|
26
|
+
import * as i7 from '@angular/material/checkbox';
|
|
27
|
+
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
26
28
|
import * as i1$2 from '@angular/material/dialog';
|
|
27
29
|
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
|
|
28
30
|
|
|
@@ -45,8 +47,11 @@ function provideShellConfig(config) {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
class ThinkingService {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
constructor() {
|
|
51
|
+
this.#activeCount = signal(0, ...(ngDevMode ? [{ debugName: "#activeCount" }] : /* istanbul ignore next */ []));
|
|
52
|
+
this.isThinking = computed(() => this.#activeCount() > 0, ...(ngDevMode ? [{ debugName: "isThinking" }] : /* istanbul ignore next */ []));
|
|
53
|
+
}
|
|
54
|
+
#activeCount;
|
|
50
55
|
startThinking() {
|
|
51
56
|
this.#activeCount.update(c => c + 1);
|
|
52
57
|
}
|
|
@@ -56,8 +61,8 @@ class ThinkingService {
|
|
|
56
61
|
reset() {
|
|
57
62
|
this.#activeCount.set(0);
|
|
58
63
|
}
|
|
59
|
-
static
|
|
60
|
-
static
|
|
64
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThinkingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
65
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThinkingService, providedIn: 'root' }); }
|
|
61
66
|
}
|
|
62
67
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThinkingService, decorators: [{
|
|
63
68
|
type: Injectable,
|
|
@@ -65,17 +70,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
65
70
|
}] });
|
|
66
71
|
|
|
67
72
|
class AuthenticationService {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
constructor() {
|
|
74
|
+
this.authenticated = signal(false, ...(ngDevMode ? [{ debugName: "authenticated" }] : /* istanbul ignore next */ []));
|
|
75
|
+
this.authenticating = signal(false, ...(ngDevMode ? [{ debugName: "authenticating" }] : /* istanbul ignore next */ []));
|
|
76
|
+
this.userSession = signal(undefined, ...(ngDevMode ? [{ debugName: "userSession" }] : /* istanbul ignore next */ []));
|
|
77
|
+
this.effectivePermissions = signal({}, ...(ngDevMode ? [{ debugName: "effectivePermissions" }] : /* istanbul ignore next */ []));
|
|
78
|
+
this.conflictSessions = signal(undefined, ...(ngDevMode ? [{ debugName: "conflictSessions" }] : /* istanbul ignore next */ []));
|
|
79
|
+
this.pendingCredentials = signal(undefined, ...(ngDevMode ? [{ debugName: "pendingCredentials" }] : /* istanbul ignore next */ []));
|
|
80
|
+
this.#refreshing = null;
|
|
81
|
+
this.#auth = 'Bearer ..';
|
|
82
|
+
this.#httpClient = inject(HttpClient);
|
|
83
|
+
this.#thinkingService = inject(ThinkingService);
|
|
84
|
+
this.#router = inject(Router);
|
|
85
|
+
this.#authUrls = inject(SHELL_AUTH_URLS);
|
|
86
|
+
}
|
|
87
|
+
#refreshing;
|
|
88
|
+
#auth;
|
|
89
|
+
#httpClient;
|
|
90
|
+
#thinkingService;
|
|
91
|
+
#router;
|
|
92
|
+
#authUrls;
|
|
79
93
|
get auth() {
|
|
80
94
|
return this.#auth;
|
|
81
95
|
}
|
|
@@ -94,6 +108,10 @@ class AuthenticationService {
|
|
|
94
108
|
this.#refreshing = this.#doRefresh().finally(() => { this.#refreshing = null; });
|
|
95
109
|
return this.#refreshing;
|
|
96
110
|
}
|
|
111
|
+
clearConflict() {
|
|
112
|
+
this.conflictSessions.set(undefined);
|
|
113
|
+
this.pendingCredentials.set(undefined);
|
|
114
|
+
}
|
|
97
115
|
logout() {
|
|
98
116
|
const subscription = this.#httpClient
|
|
99
117
|
.get(this.#authUrls.logout, {
|
|
@@ -148,6 +166,7 @@ class AuthenticationService {
|
|
|
148
166
|
}
|
|
149
167
|
this.#auth = auth;
|
|
150
168
|
this.authenticated.set(true);
|
|
169
|
+
this.pendingCredentials.set(undefined);
|
|
151
170
|
resolve();
|
|
152
171
|
}
|
|
153
172
|
catch {
|
|
@@ -163,6 +182,7 @@ class AuthenticationService {
|
|
|
163
182
|
try {
|
|
164
183
|
if (err.status === 409) {
|
|
165
184
|
this.conflictSessions.set(err.error?.activeSessions ?? []);
|
|
185
|
+
this.pendingCredentials.set(credentials);
|
|
166
186
|
reject('conflict');
|
|
167
187
|
}
|
|
168
188
|
else {
|
|
@@ -208,11 +228,12 @@ class AuthenticationService {
|
|
|
208
228
|
this.userSession.set(undefined);
|
|
209
229
|
this.effectivePermissions.set({});
|
|
210
230
|
this.conflictSessions.set(undefined);
|
|
231
|
+
this.pendingCredentials.set(undefined);
|
|
211
232
|
this.#auth = 'Bearer ..';
|
|
212
233
|
this.#router.navigate(['/login']).catch(console.error);
|
|
213
234
|
}
|
|
214
|
-
static
|
|
215
|
-
static
|
|
235
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: AuthenticationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
236
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: AuthenticationService, providedIn: 'root' }); }
|
|
216
237
|
}
|
|
217
238
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: AuthenticationService, decorators: [{
|
|
218
239
|
type: Injectable,
|
|
@@ -267,15 +288,17 @@ const authInterceptor = (req, next) => {
|
|
|
267
288
|
};
|
|
268
289
|
|
|
269
290
|
class RegexValidatorDirective {
|
|
270
|
-
|
|
291
|
+
constructor() {
|
|
292
|
+
this.regex = input(/^[A-Z0-9._%+-@!?]{4,64}$/i, ...(ngDevMode ? [{ debugName: "regex" }] : /* istanbul ignore next */ []));
|
|
293
|
+
}
|
|
271
294
|
validate(control) {
|
|
272
295
|
if (!control.value || !this.regex().test(control.value)) {
|
|
273
296
|
return { regexValidator: { value: control.value } };
|
|
274
297
|
}
|
|
275
298
|
return null;
|
|
276
299
|
}
|
|
277
|
-
static
|
|
278
|
-
static
|
|
300
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: RegexValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
301
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.7", type: RegexValidatorDirective, isStandalone: true, selector: "[regexValidator]", inputs: { regex: { classPropertyName: "regex", publicName: "regex", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: NG_VALIDATORS, useExisting: RegexValidatorDirective, multi: true }], ngImport: i0 }); }
|
|
279
302
|
}
|
|
280
303
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: RegexValidatorDirective, decorators: [{
|
|
281
304
|
type: Directive,
|
|
@@ -286,10 +309,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
286
309
|
}], propDecorators: { regex: [{ type: i0.Input, args: [{ isSignal: true, alias: "regex", required: false }] }] } });
|
|
287
310
|
|
|
288
311
|
class ThemeService {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
312
|
+
constructor() {
|
|
313
|
+
this.mode = signal('dark', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
314
|
+
this.isDark = computed(() => this.mode() === 'dark', ...(ngDevMode ? [{ debugName: "isDark" }] : /* istanbul ignore next */ []));
|
|
315
|
+
}
|
|
316
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
317
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeService, providedIn: 'root' }); }
|
|
293
318
|
}
|
|
294
319
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeService, decorators: [{
|
|
295
320
|
type: Injectable,
|
|
@@ -297,12 +322,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
297
322
|
}] });
|
|
298
323
|
|
|
299
324
|
class TerminalFocusService {
|
|
300
|
-
|
|
325
|
+
constructor() {
|
|
326
|
+
this.focusRequested = signal(0, ...(ngDevMode ? [{ debugName: "focusRequested" }] : /* istanbul ignore next */ []));
|
|
327
|
+
}
|
|
301
328
|
requestFocus() {
|
|
302
329
|
this.focusRequested.update(v => v + 1);
|
|
303
330
|
}
|
|
304
|
-
static
|
|
305
|
-
static
|
|
331
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: TerminalFocusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
332
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: TerminalFocusService, providedIn: 'root' }); }
|
|
306
333
|
}
|
|
307
334
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: TerminalFocusService, decorators: [{
|
|
308
335
|
type: Injectable,
|
|
@@ -310,22 +337,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
310
337
|
}] });
|
|
311
338
|
|
|
312
339
|
class KeyboardShortcutService {
|
|
313
|
-
#groups
|
|
314
|
-
#activeGroupId
|
|
315
|
-
#document
|
|
316
|
-
#zone
|
|
317
|
-
#focusService
|
|
318
|
-
helpVisible = signal(false, ...(ngDevMode ? [{ debugName: "helpVisible" }] : /* istanbul ignore next */ []));
|
|
319
|
-
activeShortcuts = computed(() => {
|
|
320
|
-
const groupId = this.#activeGroupId();
|
|
321
|
-
if (!groupId)
|
|
322
|
-
return null;
|
|
323
|
-
const shortcuts = this.#groups.get(groupId);
|
|
324
|
-
if (!shortcuts)
|
|
325
|
-
return null;
|
|
326
|
-
return { groupId, shortcuts };
|
|
327
|
-
}, ...(ngDevMode ? [{ debugName: "activeShortcuts" }] : /* istanbul ignore next */ []));
|
|
340
|
+
#groups;
|
|
341
|
+
#activeGroupId;
|
|
342
|
+
#document;
|
|
343
|
+
#zone;
|
|
344
|
+
#focusService;
|
|
328
345
|
constructor() {
|
|
346
|
+
this.#groups = new Map();
|
|
347
|
+
this.#activeGroupId = signal(null, ...(ngDevMode ? [{ debugName: "#activeGroupId" }] : /* istanbul ignore next */ []));
|
|
348
|
+
this.#document = inject(DOCUMENT);
|
|
349
|
+
this.#zone = inject(NgZone);
|
|
350
|
+
this.#focusService = inject(TerminalFocusService);
|
|
351
|
+
this.helpVisible = signal(false, ...(ngDevMode ? [{ debugName: "helpVisible" }] : /* istanbul ignore next */ []));
|
|
352
|
+
this.activeShortcuts = computed(() => {
|
|
353
|
+
const groupId = this.#activeGroupId();
|
|
354
|
+
if (!groupId)
|
|
355
|
+
return null;
|
|
356
|
+
const shortcuts = this.#groups.get(groupId);
|
|
357
|
+
if (!shortcuts)
|
|
358
|
+
return null;
|
|
359
|
+
return { groupId, shortcuts };
|
|
360
|
+
}, ...(ngDevMode ? [{ debugName: "activeShortcuts" }] : /* istanbul ignore next */ []));
|
|
329
361
|
this.#zone.runOutsideAngular(() => {
|
|
330
362
|
this.#document.addEventListener('keydown', (event) => {
|
|
331
363
|
this.#handleKeydown(event);
|
|
@@ -380,8 +412,8 @@ class KeyboardShortcutService {
|
|
|
380
412
|
}
|
|
381
413
|
return false;
|
|
382
414
|
}
|
|
383
|
-
static
|
|
384
|
-
static
|
|
415
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KeyboardShortcutService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
416
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KeyboardShortcutService, providedIn: 'root' }); }
|
|
385
417
|
}
|
|
386
418
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KeyboardShortcutService, decorators: [{
|
|
387
419
|
type: Injectable,
|
|
@@ -389,8 +421,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
389
421
|
}], ctorParameters: () => [] });
|
|
390
422
|
|
|
391
423
|
class CommandRegistryService {
|
|
392
|
-
commands = new Map();
|
|
393
424
|
constructor() {
|
|
425
|
+
this.commands = new Map();
|
|
394
426
|
const appCommands = inject(SHELL_COMMANDS, { optional: true }) ?? [];
|
|
395
427
|
for (const cmd of appCommands) {
|
|
396
428
|
this.register(cmd);
|
|
@@ -421,8 +453,8 @@ class CommandRegistryService {
|
|
|
421
453
|
.filter(name => name.startsWith(prefix) && name !== prefix)
|
|
422
454
|
.sort();
|
|
423
455
|
}
|
|
424
|
-
static
|
|
425
|
-
static
|
|
456
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: CommandRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
457
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: CommandRegistryService, providedIn: 'root' }); }
|
|
426
458
|
}
|
|
427
459
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: CommandRegistryService, decorators: [{
|
|
428
460
|
type: Injectable,
|
|
@@ -446,22 +478,22 @@ const HELP_USAGE = 'help [command]\n\n' +
|
|
|
446
478
|
|
|
447
479
|
const CommandInputSchema = z.string().trim().min(1, 'Command cannot be empty');
|
|
448
480
|
class CommandService {
|
|
449
|
-
history = signal([], ...(ngDevMode ? [{ debugName: "history" }] : /* istanbul ignore next */ []));
|
|
450
|
-
currentInput = signal('', ...(ngDevMode ? [{ debugName: "currentInput" }] : /* istanbul ignore next */ []));
|
|
451
|
-
isProcessing = signal(false, ...(ngDevMode ? [{ debugName: "isProcessing" }] : /* istanbul ignore next */ []));
|
|
452
|
-
authService = inject(AuthenticationService);
|
|
453
|
-
registry = inject(CommandRegistryService);
|
|
454
481
|
constructor() {
|
|
482
|
+
this.history = signal([], ...(ngDevMode ? [{ debugName: "history" }] : /* istanbul ignore next */ []));
|
|
483
|
+
this.currentInput = signal('', ...(ngDevMode ? [{ debugName: "currentInput" }] : /* istanbul ignore next */ []));
|
|
484
|
+
this.isProcessing = signal(false, ...(ngDevMode ? [{ debugName: "isProcessing" }] : /* istanbul ignore next */ []));
|
|
485
|
+
this.authService = inject(AuthenticationService);
|
|
486
|
+
this.registry = inject(CommandRegistryService);
|
|
487
|
+
this.lastResult = computed(() => {
|
|
488
|
+
const h = this.history();
|
|
489
|
+
return h.length > 0 ? h[h.length - 1].result : null;
|
|
490
|
+
}, ...(ngDevMode ? [{ debugName: "lastResult" }] : /* istanbul ignore next */ []));
|
|
455
491
|
effect(() => {
|
|
456
492
|
if (!this.authService.authenticated()) {
|
|
457
493
|
this.history.set([]);
|
|
458
494
|
}
|
|
459
495
|
});
|
|
460
496
|
}
|
|
461
|
-
lastResult = computed(() => {
|
|
462
|
-
const h = this.history();
|
|
463
|
-
return h.length > 0 ? h[h.length - 1].result : null;
|
|
464
|
-
}, ...(ngDevMode ? [{ debugName: "lastResult" }] : /* istanbul ignore next */ []));
|
|
465
497
|
echoCommand(input) {
|
|
466
498
|
if (this.#coalesce(input))
|
|
467
499
|
return;
|
|
@@ -585,8 +617,8 @@ class CommandService {
|
|
|
585
617
|
return { success: false, message: `Unknown command: ${name}`, timestamp: new Date() };
|
|
586
618
|
return { success: true, message: def.usage, timestamp: new Date() };
|
|
587
619
|
}
|
|
588
|
-
static
|
|
589
|
-
static
|
|
620
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: CommandService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
621
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: CommandService, providedIn: 'root' }); }
|
|
590
622
|
}
|
|
591
623
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: CommandService, decorators: [{
|
|
592
624
|
type: Injectable,
|
|
@@ -594,22 +626,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
594
626
|
}], ctorParameters: () => [] });
|
|
595
627
|
|
|
596
628
|
class StatusBar {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
629
|
+
constructor() {
|
|
630
|
+
this.#authService = inject(AuthenticationService);
|
|
631
|
+
this.#focusService = inject(TerminalFocusService);
|
|
632
|
+
this.#commandService = inject(CommandService);
|
|
633
|
+
this.isAuthenticated = this.#authService.authenticated;
|
|
634
|
+
this.loginStatus = computed(() => this.#authService.authenticating()
|
|
635
|
+
? 'Authenticating...'
|
|
636
|
+
: this.#authService.authenticated()
|
|
637
|
+
? 'Logged in'
|
|
638
|
+
: 'Not logged in', ...(ngDevMode ? [{ debugName: "loginStatus" }] : /* istanbul ignore next */ []));
|
|
639
|
+
}
|
|
640
|
+
#authService;
|
|
641
|
+
#focusService;
|
|
642
|
+
#commandService;
|
|
606
643
|
logout() {
|
|
607
644
|
this.#authService.logout();
|
|
608
645
|
this.#commandService.echoCommand('logout');
|
|
609
646
|
this.#focusService.requestFocus();
|
|
610
647
|
}
|
|
611
|
-
static
|
|
612
|
-
static
|
|
648
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StatusBar, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
649
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: StatusBar, isStandalone: true, selector: "shell-status-bar", ngImport: i0, template: "<div class=\"status-bar\">\n <div class=\"status-item\">\n <span class=\"status-label text-secondary-info\">Status:</span>\n @if (isAuthenticated()) {\n <a class=\"status-value text-interactive logout-link\" (click)=\"logout()\">Log Out</a>\n } @else {\n <span class=\"status-value text-loss\">{{ loginStatus() }}</span>\n }\n </div>\n @if (isAuthenticated()) {\n <ng-content />\n }\n</div>\n", styles: [".status-bar{display:flex;align-items:center;gap:24px;padding:0 16px;height:100%;background-color:var(--color-surface);border-bottom:1px solid var(--color-surface-border);font-size:.8rem}.status-item{display:flex;align-items:baseline;gap:6px}.status-label{font-weight:500;text-transform:uppercase;letter-spacing:.05em;font-size:.7rem}.logout-link{cursor:pointer;text-decoration:underline}.logout-link:hover{opacity:.8}\n"] }); }
|
|
613
650
|
}
|
|
614
651
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: StatusBar, decorators: [{
|
|
615
652
|
type: Component,
|
|
@@ -617,12 +654,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
617
654
|
}] });
|
|
618
655
|
|
|
619
656
|
class SideMenu {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
657
|
+
constructor() {
|
|
658
|
+
this.#focusService = inject(TerminalFocusService);
|
|
659
|
+
this.#commandService = inject(CommandService);
|
|
660
|
+
this.#shortcutService = inject(KeyboardShortcutService);
|
|
661
|
+
this.hasShortcuts = this.#shortcutService.activeShortcuts;
|
|
662
|
+
this.#allMenuItems = inject(SHELL_MENU_ITEMS, { optional: true }) ?? [];
|
|
663
|
+
this.menuItems = computed(() => this.#allMenuItems.filter(item => !item.visible || item.visible()), ...(ngDevMode ? [{ debugName: "menuItems" }] : /* istanbul ignore next */ []));
|
|
664
|
+
}
|
|
665
|
+
#focusService;
|
|
666
|
+
#commandService;
|
|
667
|
+
#shortcutService;
|
|
668
|
+
#allMenuItems;
|
|
626
669
|
onMenuClick(item) {
|
|
627
670
|
if (item.command) {
|
|
628
671
|
this.#commandService.echoCommand(item.command);
|
|
@@ -632,8 +675,8 @@ class SideMenu {
|
|
|
632
675
|
toggleHelp() {
|
|
633
676
|
this.#shortcutService.toggleHelp();
|
|
634
677
|
}
|
|
635
|
-
static
|
|
636
|
-
static
|
|
678
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SideMenu, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
679
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: SideMenu, isStandalone: true, selector: "shell-side-menu", ngImport: i0, template: "<nav class=\"side-menu\">\n @for (item of menuItems(); track item.route) {\n <a\n class=\"menu-item\"\n [routerLink]=\"item.route\"\n routerLinkActive=\"active\"\n [matTooltip]=\"item.label\"\n matTooltipPosition=\"right\"\n (click)=\"onMenuClick(item)\"\n >\n <mat-icon>{{ item.icon }}</mat-icon>\n </a>\n }\n <div class=\"menu-spacer\"></div>\n @if (hasShortcuts()) {\n <button\n class=\"help-button\"\n matTooltip=\"Keyboard shortcuts\"\n matTooltipPosition=\"right\"\n (click)=\"toggleHelp()\"\n >\n <span class=\"help-modifier\">Alt+</span><span class=\"help-key\">?</span>\n </button>\n }\n</nav>\n", styles: [".side-menu{display:flex;flex-direction:column;align-items:center;padding-top:8px;gap:4px;height:100%;background-color:var(--color-surface);border-right:1px solid var(--color-surface-border)}.menu-item{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:8px;color:var(--color-secondary-info);text-decoration:none;transition:background-color .15s,color .15s}.menu-item:hover,.menu-item.active{background-color:var(--color-surface-light);color:var(--color-interactive)}.menu-spacer{flex:1}.help-button{margin-bottom:8px;padding:4px 6px;border:1px solid var(--color-surface-border);border-radius:6px;background:none;font-size:.7rem;font-family:Roboto Mono,monospace;cursor:pointer;color:var(--color-interactive);transition:background-color .15s}.help-button:hover{background-color:var(--color-surface-light)}.help-modifier{opacity:.6}.help-key{font-weight:700}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] }); }
|
|
637
680
|
}
|
|
638
681
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SideMenu, decorators: [{
|
|
639
682
|
type: Component,
|
|
@@ -641,11 +684,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
641
684
|
}] });
|
|
642
685
|
|
|
643
686
|
class MainContent {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
687
|
+
constructor() {
|
|
688
|
+
this.#thinkingService = inject(ThinkingService);
|
|
689
|
+
this.#shortcutService = inject(KeyboardShortcutService);
|
|
690
|
+
this.isThinking = this.#thinkingService.isThinking;
|
|
691
|
+
this.shortcutHelpVisible = this.#shortcutService.helpVisible;
|
|
692
|
+
this.activeShortcuts = this.#shortcutService.activeShortcuts;
|
|
693
|
+
}
|
|
694
|
+
#thinkingService;
|
|
695
|
+
#shortcutService;
|
|
649
696
|
formatKey(key) {
|
|
650
697
|
const keyMap = {
|
|
651
698
|
ArrowUp: '↑', ArrowDown: '↓', ArrowLeft: '←', ArrowRight: '→',
|
|
@@ -656,8 +703,8 @@ class MainContent {
|
|
|
656
703
|
closeShortcutHelp() {
|
|
657
704
|
this.#shortcutService.helpVisible.set(false);
|
|
658
705
|
}
|
|
659
|
-
static
|
|
660
|
-
static
|
|
706
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MainContent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
707
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: MainContent, isStandalone: true, selector: "shell-main-content", ngImport: i0, template: "<div class=\"main-content\">\n <div class=\"content-area\">\n <router-outlet />\n </div>\n @if (shortcutHelpVisible() && activeShortcuts()) {\n <div class=\"shortcut-overlay\" (click)=\"closeShortcutHelp()\">\n <div class=\"shortcut-panel\" (click)=\"$event.stopPropagation()\">\n <div class=\"shortcut-title text-secondary-info\">{{ activeShortcuts()!.groupId }} shortcuts</div>\n @for (s of activeShortcuts()!.shortcuts; track s.key) {\n <div class=\"shortcut-row\">\n <kbd class=\"shortcut-key\">Alt+{{ formatKey(s.key) }}</kbd>\n <span class=\"shortcut-desc text-text-primary\">{{ s.description }}</span>\n </div>\n }\n <div class=\"shortcut-row shortcut-dismiss\">\n <kbd class=\"shortcut-key\">Alt+?</kbd>\n <span class=\"shortcut-desc text-secondary-info\">or</span>\n <kbd class=\"shortcut-key\">Esc</kbd>\n <span class=\"shortcut-desc text-secondary-info\">dismiss</span>\n </div>\n </div>\n </div>\n }\n @if (isThinking()) {\n <div class=\"thinking-overlay\">\n <mat-spinner diameter=\"48\" />\n </div>\n }\n</div>\n", styles: [".main-content{position:relative;height:100%;overflow:hidden}.content-area{height:100%;overflow-y:auto;padding:16px}.shortcut-overlay{position:absolute;inset:0;z-index:15;display:flex;align-items:center;justify-content:center;background-color:#0a0e1480}.shortcut-panel{background-color:var(--color-terminal-bg);border:1px solid var(--color-surface-border);border-radius:6px;padding:14px 18px;font-size:.8rem;box-shadow:0 4px 16px #00000080;min-width:220px}.shortcut-title{font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px;font-weight:500}.shortcut-row{display:flex;align-items:center;gap:10px;padding:3px 0}.shortcut-row.shortcut-dismiss{margin-top:6px;border-top:1px solid var(--color-surface-border);padding-top:8px}.shortcut-key{font-family:Roboto Mono,monospace;font-size:.75rem;background-color:#ffffff14;border:1px solid var(--color-surface-border);border-radius:3px;padding:1px 6px;min-width:70px;text-align:center}.shortcut-desc{font-size:.78rem}.thinking-overlay{position:absolute;inset:0;z-index:20;background-color:#0a0e14b3;display:flex;align-items:center;justify-content:center;pointer-events:all;cursor:wait}\n"], dependencies: [{ kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i1$1.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }] }); }
|
|
661
708
|
}
|
|
662
709
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MainContent, decorators: [{
|
|
663
710
|
type: Component,
|
|
@@ -665,25 +712,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
665
712
|
}] });
|
|
666
713
|
|
|
667
714
|
class CommandTerminal {
|
|
668
|
-
commandService = inject(CommandService);
|
|
669
|
-
registry = inject(CommandRegistryService);
|
|
670
|
-
authService = inject(AuthenticationService);
|
|
671
|
-
terminalFocusService = inject(TerminalFocusService);
|
|
672
|
-
history = computed(() => [...this.commandService.history()].reverse(), ...(ngDevMode ? [{ debugName: "history" }] : /* istanbul ignore next */ []));
|
|
673
|
-
currentInput = this.commandService.currentInput;
|
|
674
|
-
isProcessing = this.commandService.isProcessing;
|
|
675
|
-
inputEl = viewChild('terminalInput', ...(ngDevMode ? [{ debugName: "inputEl" }] : /* istanbul ignore next */ []));
|
|
676
|
-
tabCycleIndex = -1;
|
|
677
|
-
lastTabPrefix = '';
|
|
678
|
-
ghostText = computed(() => {
|
|
679
|
-
const input = this.currentInput().trim();
|
|
680
|
-
if (!input || input.includes(' '))
|
|
681
|
-
return '';
|
|
682
|
-
const prefix = input.toLowerCase();
|
|
683
|
-
const suggestions = this.registry.getSuggestions(prefix, this.authService.authenticated());
|
|
684
|
-
return suggestions.length > 0 ? suggestions[0] : '';
|
|
685
|
-
}, ...(ngDevMode ? [{ debugName: "ghostText" }] : /* istanbul ignore next */ []));
|
|
686
715
|
constructor() {
|
|
716
|
+
this.commandService = inject(CommandService);
|
|
717
|
+
this.registry = inject(CommandRegistryService);
|
|
718
|
+
this.authService = inject(AuthenticationService);
|
|
719
|
+
this.terminalFocusService = inject(TerminalFocusService);
|
|
720
|
+
this.history = computed(() => [...this.commandService.history()].reverse(), ...(ngDevMode ? [{ debugName: "history" }] : /* istanbul ignore next */ []));
|
|
721
|
+
this.currentInput = this.commandService.currentInput;
|
|
722
|
+
this.isProcessing = this.commandService.isProcessing;
|
|
723
|
+
this.inputEl = viewChild('terminalInput', ...(ngDevMode ? [{ debugName: "inputEl" }] : /* istanbul ignore next */ []));
|
|
724
|
+
this.tabCycleIndex = -1;
|
|
725
|
+
this.lastTabPrefix = '';
|
|
726
|
+
this.ghostText = computed(() => {
|
|
727
|
+
const input = this.currentInput().trim();
|
|
728
|
+
if (!input || input.includes(' '))
|
|
729
|
+
return '';
|
|
730
|
+
const prefix = input.toLowerCase();
|
|
731
|
+
const suggestions = this.registry.getSuggestions(prefix, this.authService.authenticated());
|
|
732
|
+
return suggestions.length > 0 ? suggestions[0] : '';
|
|
733
|
+
}, ...(ngDevMode ? [{ debugName: "ghostText" }] : /* istanbul ignore next */ []));
|
|
687
734
|
afterNextRender(() => this.focusInput());
|
|
688
735
|
effect(() => {
|
|
689
736
|
if (!this.isProcessing()) {
|
|
@@ -748,8 +795,8 @@ class CommandTerminal {
|
|
|
748
795
|
focusInput() {
|
|
749
796
|
this.inputEl()?.nativeElement.focus();
|
|
750
797
|
}
|
|
751
|
-
static
|
|
752
|
-
static
|
|
798
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: CommandTerminal, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
799
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: CommandTerminal, isStandalone: true, selector: "shell-command-terminal", viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["terminalInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"terminal\">\n <div class=\"terminal-input-row\">\n <span class=\"prompt\">$</span>\n <div class=\"input-wrapper\">\n @if (ghostText()) {\n <input\n class=\"ghost-input\"\n type=\"text\"\n [value]=\"ghostText()\"\n disabled\n tabindex=\"-1\"\n aria-hidden=\"true\"\n />\n }\n <input\n #terminalInput\n class=\"terminal-input\"\n type=\"text\"\n [ngModel]=\"currentInput()\"\n (ngModelChange)=\"onInputChange($event)\"\n (keydown.enter)=\"onSubmit()\"\n (keydown.tab)=\"onTab($any($event))\"\n placeholder=\"Type a command...\"\n [disabled]=\"isProcessing()\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n />\n </div>\n </div>\n <div class=\"terminal-output\">\n @for (entry of history(); track entry.result.timestamp) {\n @if (entry.input) {\n <div class=\"terminal-line\">\n <span class=\"prompt\">$</span>\n <span class=\"command-text\">{{ entry.input }}@if ((entry.repeatCount ?? 1) > 1) { <span class=\"repeat-count\">(\u00D7{{ entry.repeatCount }})</span>}</span>\n </div>\n }\n @if (entry.result.message) {\n <div class=\"terminal-line\" [class]=\"getSeverityClass(entry.result)\">{{ entry.result.message }}</div>\n }\n }\n </div>\n</div>\n", styles: [".terminal{display:flex;flex-direction:column;height:100%;background-color:var(--color-terminal-bg);border-top:1px solid var(--color-surface-border);font-family:Roboto Mono,Courier New,monospace;font-size:.85rem}.terminal-output{flex:1;overflow-y:auto;padding:8px 12px;display:flex;flex-direction:column}.terminal-line{line-height:1.6;color:var(--color-text-primary);white-space:pre-wrap}.terminal-line.error{color:var(--color-loss)}.terminal-line.warning{color:var(--color-warning)}.terminal-line.info{color:var(--color-info)}.prompt{color:var(--color-terminal-prompt);margin-right:8px;font-weight:700}.command-text{color:var(--color-terminal-text)}.repeat-count{color:var(--color-secondary-info);opacity:.6;margin-left:4px}.terminal-input-row{display:flex;align-items:center;padding:8px 12px;border-bottom:1px solid var(--color-surface-border)}.input-wrapper{flex:1;display:grid}.ghost-input,.terminal-input{grid-area:1 / 1;font-family:inherit;font-size:inherit;line-height:normal;margin:0;padding:0;border:none;background:transparent;outline:none;-webkit-appearance:none;appearance:none;width:100%}.ghost-input{color:var(--color-secondary-info);opacity:.3;pointer-events:none}.terminal-input{color:var(--color-terminal-text);caret-color:var(--color-terminal-prompt)}.terminal-input::placeholder{color:var(--color-secondary-info);opacity:.5}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] }); }
|
|
753
800
|
}
|
|
754
801
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: CommandTerminal, decorators: [{
|
|
755
802
|
type: Component,
|
|
@@ -760,23 +807,30 @@ const MIN_TERMINAL_HEIGHT = 80;
|
|
|
760
807
|
const MAX_TERMINAL_RATIO = 0.6;
|
|
761
808
|
const DEFAULT_TERMINAL_HEIGHT = 160;
|
|
762
809
|
class Shell {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
this
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
810
|
+
constructor() {
|
|
811
|
+
this.#thinkingService = inject(ThinkingService);
|
|
812
|
+
this.isThinking = this.#thinkingService.isThinking;
|
|
813
|
+
this.terminalHeight = signal(`${DEFAULT_TERMINAL_HEIGHT}px`, ...(ngDevMode ? [{ debugName: "terminalHeight" }] : /* istanbul ignore next */ []));
|
|
814
|
+
this.isDragging = signal(false, ...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ []));
|
|
815
|
+
this.#startY = 0;
|
|
816
|
+
this.#startHeight = DEFAULT_TERMINAL_HEIGHT;
|
|
817
|
+
this.#onMouseMove = (event) => {
|
|
818
|
+
const deltaY = event.clientY - this.#startY;
|
|
819
|
+
const maxHeight = Math.floor(window.innerHeight * MAX_TERMINAL_RATIO);
|
|
820
|
+
const newHeight = Math.min(maxHeight, Math.max(MIN_TERMINAL_HEIGHT, this.#startHeight - deltaY));
|
|
821
|
+
this.terminalHeight.set(`${newHeight}px`);
|
|
822
|
+
};
|
|
823
|
+
this.#onMouseUp = () => {
|
|
824
|
+
this.isDragging.set(false);
|
|
825
|
+
document.removeEventListener('mousemove', this.#onMouseMove);
|
|
826
|
+
document.removeEventListener('mouseup', this.#onMouseUp);
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
#thinkingService;
|
|
830
|
+
#startY;
|
|
831
|
+
#startHeight;
|
|
832
|
+
#onMouseMove;
|
|
833
|
+
#onMouseUp;
|
|
780
834
|
onResizeStart(event) {
|
|
781
835
|
event.preventDefault();
|
|
782
836
|
this.#startY = event.clientY;
|
|
@@ -785,8 +839,8 @@ class Shell {
|
|
|
785
839
|
document.addEventListener('mousemove', this.#onMouseMove);
|
|
786
840
|
document.addEventListener('mouseup', this.#onMouseUp);
|
|
787
841
|
}
|
|
788
|
-
static
|
|
789
|
-
static
|
|
842
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: Shell, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
843
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.7", type: Shell, isStandalone: true, selector: "shell-root", ngImport: i0, template: "<div class=\"shell\" [style.--terminal-height]=\"terminalHeight()\" [class.is-dragging]=\"isDragging()\">\n <shell-status-bar class=\"shell-status\" [class.disabled-zone]=\"isThinking()\">\n <ng-content />\n </shell-status-bar>\n <shell-side-menu class=\"shell-menu\" [class.disabled-zone]=\"isThinking()\" />\n <shell-main-content class=\"shell-main\" />\n <div class=\"shell-resize\" [class.dragging]=\"isDragging()\" (mousedown)=\"onResizeStart($event)\"></div>\n <shell-command-terminal class=\"shell-terminal\" [class.disabled-zone]=\"isThinking()\" />\n</div>\n", styles: [".shell{display:grid;grid-template-areas:\"status status\" \"menu main\" \"resize resize\" \"terminal terminal\";grid-template-columns:56px 1fr;grid-template-rows:48px 1fr 4px var(--terminal-height, 160px);height:100vh;width:100vw;overflow:hidden;background-color:var(--color-surface-dark)}.shell-status{grid-area:status}.shell-menu{grid-area:menu}.shell-main{grid-area:main;overflow:hidden}.shell-resize{grid-area:resize;cursor:row-resize;background-color:var(--color-surface-border);transition:background-color .15s ease}.shell-resize:hover,.shell-resize.dragging{background-color:var(--color-interactive)}.shell-terminal{grid-area:terminal}.shell.is-dragging{user-select:none;-webkit-user-select:none}.disabled-zone{pointer-events:none;opacity:.4;transition:opacity .2s ease}\n"], dependencies: [{ kind: "component", type: StatusBar, selector: "shell-status-bar" }, { kind: "component", type: SideMenu, selector: "shell-side-menu" }, { kind: "component", type: MainContent, selector: "shell-main-content" }, { kind: "component", type: CommandTerminal, selector: "shell-command-terminal" }] }); }
|
|
790
844
|
}
|
|
791
845
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: Shell, decorators: [{
|
|
792
846
|
type: Component,
|
|
@@ -794,20 +848,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
794
848
|
}] });
|
|
795
849
|
|
|
796
850
|
class Login {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
model = { username: '', password: '' };
|
|
800
|
-
submitted = false;
|
|
801
|
-
hide = signal(true, ...(ngDevMode ? [{ debugName: "hide" }] : /* istanbul ignore next */ []));
|
|
802
|
-
errors = [];
|
|
803
|
-
USERNAME_REGEX = USERNAME_REGEX;
|
|
804
|
-
PASSWORD_REGEX = PASSWORD_REGEX;
|
|
805
|
-
appName = inject(SHELL_APP_NAME, { optional: true }) ?? 'Application';
|
|
806
|
-
#commandService = inject(CommandService);
|
|
807
|
-
#terminalFocusService = inject(TerminalFocusService);
|
|
851
|
+
#commandService;
|
|
852
|
+
#terminalFocusService;
|
|
808
853
|
constructor(authenticationService, router) {
|
|
809
854
|
this.authenticationService = authenticationService;
|
|
810
855
|
this.router = router;
|
|
856
|
+
this.model = { username: '', password: '' };
|
|
857
|
+
this.submitted = false;
|
|
858
|
+
this.hide = signal(true, ...(ngDevMode ? [{ debugName: "hide" }] : /* istanbul ignore next */ []));
|
|
859
|
+
this.takeover = signal(true, ...(ngDevMode ? [{ debugName: "takeover" }] : /* istanbul ignore next */ []));
|
|
860
|
+
this.errors = [];
|
|
861
|
+
this.USERNAME_REGEX = USERNAME_REGEX;
|
|
862
|
+
this.PASSWORD_REGEX = PASSWORD_REGEX;
|
|
863
|
+
this.appName = inject(SHELL_APP_NAME, { optional: true }) ?? 'Application';
|
|
864
|
+
this.#commandService = inject(CommandService);
|
|
865
|
+
this.#terminalFocusService = inject(TerminalFocusService);
|
|
811
866
|
}
|
|
812
867
|
onAutofill(event, inputEl) {
|
|
813
868
|
if (event.isAutofilled) {
|
|
@@ -827,7 +882,7 @@ class Login {
|
|
|
827
882
|
this.submitted = true;
|
|
828
883
|
const result = validateCredentials(this.model);
|
|
829
884
|
if (result === true) {
|
|
830
|
-
this.#doLogin(
|
|
885
|
+
this.#doLogin(this.takeover());
|
|
831
886
|
}
|
|
832
887
|
else {
|
|
833
888
|
for (let ndx = 0; ndx < result.length; ndx++) {
|
|
@@ -841,11 +896,10 @@ class Login {
|
|
|
841
896
|
}
|
|
842
897
|
onTakeOver() {
|
|
843
898
|
this.errors = [];
|
|
844
|
-
this.
|
|
845
|
-
this.#doLogin(true);
|
|
899
|
+
this.#commandService.execute('login --force');
|
|
846
900
|
}
|
|
847
901
|
onCancelConflict() {
|
|
848
|
-
this.authenticationService.
|
|
902
|
+
this.authenticationService.clearConflict();
|
|
849
903
|
this.submitted = false;
|
|
850
904
|
}
|
|
851
905
|
#doLogin(force) {
|
|
@@ -864,8 +918,8 @@ class Login {
|
|
|
864
918
|
this.submitted = false;
|
|
865
919
|
});
|
|
866
920
|
}
|
|
867
|
-
static
|
|
868
|
-
static
|
|
921
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: Login, deps: [{ token: AuthenticationService }, { token: i2$1.Router }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
922
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: Login, isStandalone: true, selector: "shell-login", ngImport: i0, template: "@let usernameRegex = USERNAME_REGEX;\n@let passwordRegex = PASSWORD_REGEX;\n<div class=\"login-container\">\n <div class=\"login-header\">\n <h2 class=\"text-text-primary\">{{ appName }}</h2>\n <p class=\"text-secondary-info\">Sign in to continue</p>\n </div>\n <form #loginForm=\"ngForm\" (ngSubmit)=\"onSubmit($event)\" class=\"login-form\">\n <mat-form-field appearance=\"outline\" class=\"login-field\">\n <mat-label>Username</mat-label>\n <input\n #usernameInput\n matInput\n placeholder=\"Enter username\"\n [(ngModel)]=\"model.username\"\n name=\"username\"\n [disabled]=\"submitted\"\n required\n regexValidator\n [regex]=\"usernameRegex\"\n (cdkAutofill)=\"onAutofill($event, usernameInput)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"login-field\">\n <mat-label>Password</mat-label>\n <input\n #passwordInput\n matInput\n placeholder=\"Enter password\"\n [type]=\"hide() ? 'password' : 'text'\"\n [(ngModel)]=\"model.password\"\n name=\"password\"\n [disabled]=\"submitted\"\n regexValidator\n [regex]=\"passwordRegex\"\n (cdkAutofill)=\"onAutofill($event, passwordInput)\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n (click)=\"clickEvent($event)\"\n [attr.aria-label]=\"'Hide password'\"\n [attr.aria-pressed]=\"hide()\"\n >\n <mat-icon>{{ hide() ? 'visibility_off' : 'visibility' }}</mat-icon>\n </button>\n </mat-form-field>\n <mat-checkbox\n class=\"login-takeover\"\n name=\"takeover\"\n [checked]=\"takeover()\"\n (change)=\"takeover.set($event.checked)\"\n [disabled]=\"submitted\"\n >\n Takeover session if logged in elsewhere\n </mat-checkbox>\n <button mat-flat-button type=\"submit\" [disabled]=\"submitted\" class=\"login-button\">\n Login\n </button>\n </form>\n @if (errors.length > 0) {\n <div class=\"login-errors\">\n @for (error of errors; track error.ndx) {\n <div class=\"login-error text-loss\">{{ error.message }}</div>\n }\n </div>\n }\n @if (authenticationService.conflictSessions(); as sessions) {\n <div class=\"login-conflict\">\n @for (session of sessions; track $index) {\n <p class=\"text-secondary-info\">\n Already logged in from <strong>{{ session.appContext }}</strong>\n since {{ session.startEpoch | date:'short' }}.\n </p>\n }\n <p class=\"text-secondary-info\">Log in here and end that session?</p>\n <div class=\"login-conflict-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"onCancelConflict()\">Cancel</button>\n <button mat-flat-button type=\"button\" (click)=\"onTakeOver()\">Take Over</button>\n </div>\n </div>\n }\n</div>\n", styles: [".login-container{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;text-align:center}.login-header{margin-bottom:32px}.login-header h2{font-size:1.5rem;font-weight:300;margin-bottom:8px}.login-form{display:flex;flex-direction:column;align-items:center;gap:8px;width:320px}.login-field{width:100%;text-align:left}.login-takeover{align-self:flex-start;margin-top:4px}.login-button{margin-top:8px;width:100%}.login-errors{margin-top:16px;max-width:320px}.login-conflict{margin-top:16px;max-width:320px;padding:12px 16px;border-radius:4px;background-color:var(--color-surface-light);font-size:.85rem;text-align:left}.login-conflict p{margin:0 0 8px}.login-conflict-actions{display:flex;gap:8px;margin-top:12px;justify-content:flex-end}.login-error{padding:8px 12px;margin-bottom:4px;font-size:.85rem;border-radius:4px;background-color:var(--color-surface-light)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i5.CdkAutofill, selector: "[cdkAutofill]", outputs: ["cdkAutofill"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i7.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: RegexValidatorDirective, selector: "[regexValidator]", inputs: ["regex"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "pipe", type: DatePipe, name: "date" }] }); }
|
|
869
923
|
}
|
|
870
924
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: Login, decorators: [{
|
|
871
925
|
type: Component,
|
|
@@ -876,18 +930,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
876
930
|
MatInput,
|
|
877
931
|
MatInputModule,
|
|
878
932
|
MatButtonModule,
|
|
933
|
+
MatCheckboxModule,
|
|
879
934
|
MatIconModule,
|
|
880
935
|
RegexValidatorDirective,
|
|
881
936
|
TextFieldModule,
|
|
882
937
|
DatePipe,
|
|
883
|
-
], template: "@let usernameRegex = USERNAME_REGEX;\n@let passwordRegex = PASSWORD_REGEX;\n<div class=\"login-container\">\n <div class=\"login-header\">\n <h2 class=\"text-text-primary\">{{ appName }}</h2>\n <p class=\"text-secondary-info\">Sign in to continue</p>\n </div>\n <form #loginForm=\"ngForm\" (ngSubmit)=\"onSubmit($event)\" class=\"login-form\">\n <mat-form-field appearance=\"outline\" class=\"login-field\">\n <mat-label>Username</mat-label>\n <input\n #usernameInput\n matInput\n placeholder=\"Enter username\"\n [(ngModel)]=\"model.username\"\n name=\"username\"\n [disabled]=\"submitted\"\n required\n regexValidator\n [regex]=\"usernameRegex\"\n (cdkAutofill)=\"onAutofill($event, usernameInput)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"login-field\">\n <mat-label>Password</mat-label>\n <input\n #passwordInput\n matInput\n placeholder=\"Enter password\"\n [type]=\"hide() ? 'password' : 'text'\"\n [(ngModel)]=\"model.password\"\n name=\"password\"\n [disabled]=\"submitted\"\n regexValidator\n [regex]=\"passwordRegex\"\n (cdkAutofill)=\"onAutofill($event, passwordInput)\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n (click)=\"clickEvent($event)\"\n [attr.aria-label]=\"'Hide password'\"\n [attr.aria-pressed]=\"hide()\"\n >\n <mat-icon>{{ hide() ? 'visibility_off' : 'visibility' }}</mat-icon>\n </button>\n </mat-form-field>\n <button mat-flat-button type=\"submit\" [disabled]=\"submitted\" class=\"login-button\">\n Login\n </button>\n </form>\n @if (errors.length > 0) {\n <div class=\"login-errors\">\n @for (error of errors; track error.ndx) {\n <div class=\"login-error text-loss\">{{ error.message }}</div>\n }\n </div>\n }\n @if (authenticationService.conflictSessions(); as sessions) {\n <div class=\"login-conflict\">\n @for (session of sessions; track $index) {\n <p class=\"text-secondary-info\">\n Already logged in from <strong>{{ session.appContext }}</strong>\n since {{ session.startEpoch | date:'short' }}.\n </p>\n }\n <p class=\"text-secondary-info\">Log in here and end that session?</p>\n <div class=\"login-conflict-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"onCancelConflict()\">Cancel</button>\n <button mat-flat-button type=\"button\" (click)=\"onTakeOver()\">Take Over</button>\n </div>\n </div>\n }\n</div>\n", styles: [".login-container{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;text-align:center}.login-header{margin-bottom:32px}.login-header h2{font-size:1.5rem;font-weight:300;margin-bottom:8px}.login-form{display:flex;flex-direction:column;align-items:center;gap:8px;width:320px}.login-field{width:100%;text-align:left}.login-button{margin-top:8px;width:100%}.login-errors{margin-top:16px;max-width:320px}.login-conflict{margin-top:16px;max-width:320px;padding:12px 16px;border-radius:4px;background-color:var(--color-surface-light);font-size:.85rem;text-align:left}.login-conflict p{margin:0 0 8px}.login-conflict-actions{display:flex;gap:8px;margin-top:12px;justify-content:flex-end}.login-error{padding:8px 12px;margin-bottom:4px;font-size:.85rem;border-radius:4px;background-color:var(--color-surface-light)}\n"] }]
|
|
938
|
+
], template: "@let usernameRegex = USERNAME_REGEX;\n@let passwordRegex = PASSWORD_REGEX;\n<div class=\"login-container\">\n <div class=\"login-header\">\n <h2 class=\"text-text-primary\">{{ appName }}</h2>\n <p class=\"text-secondary-info\">Sign in to continue</p>\n </div>\n <form #loginForm=\"ngForm\" (ngSubmit)=\"onSubmit($event)\" class=\"login-form\">\n <mat-form-field appearance=\"outline\" class=\"login-field\">\n <mat-label>Username</mat-label>\n <input\n #usernameInput\n matInput\n placeholder=\"Enter username\"\n [(ngModel)]=\"model.username\"\n name=\"username\"\n [disabled]=\"submitted\"\n required\n regexValidator\n [regex]=\"usernameRegex\"\n (cdkAutofill)=\"onAutofill($event, usernameInput)\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"login-field\">\n <mat-label>Password</mat-label>\n <input\n #passwordInput\n matInput\n placeholder=\"Enter password\"\n [type]=\"hide() ? 'password' : 'text'\"\n [(ngModel)]=\"model.password\"\n name=\"password\"\n [disabled]=\"submitted\"\n regexValidator\n [regex]=\"passwordRegex\"\n (cdkAutofill)=\"onAutofill($event, passwordInput)\"\n />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n (click)=\"clickEvent($event)\"\n [attr.aria-label]=\"'Hide password'\"\n [attr.aria-pressed]=\"hide()\"\n >\n <mat-icon>{{ hide() ? 'visibility_off' : 'visibility' }}</mat-icon>\n </button>\n </mat-form-field>\n <mat-checkbox\n class=\"login-takeover\"\n name=\"takeover\"\n [checked]=\"takeover()\"\n (change)=\"takeover.set($event.checked)\"\n [disabled]=\"submitted\"\n >\n Takeover session if logged in elsewhere\n </mat-checkbox>\n <button mat-flat-button type=\"submit\" [disabled]=\"submitted\" class=\"login-button\">\n Login\n </button>\n </form>\n @if (errors.length > 0) {\n <div class=\"login-errors\">\n @for (error of errors; track error.ndx) {\n <div class=\"login-error text-loss\">{{ error.message }}</div>\n }\n </div>\n }\n @if (authenticationService.conflictSessions(); as sessions) {\n <div class=\"login-conflict\">\n @for (session of sessions; track $index) {\n <p class=\"text-secondary-info\">\n Already logged in from <strong>{{ session.appContext }}</strong>\n since {{ session.startEpoch | date:'short' }}.\n </p>\n }\n <p class=\"text-secondary-info\">Log in here and end that session?</p>\n <div class=\"login-conflict-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"onCancelConflict()\">Cancel</button>\n <button mat-flat-button type=\"button\" (click)=\"onTakeOver()\">Take Over</button>\n </div>\n </div>\n }\n</div>\n", styles: [".login-container{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;text-align:center}.login-header{margin-bottom:32px}.login-header h2{font-size:1.5rem;font-weight:300;margin-bottom:8px}.login-form{display:flex;flex-direction:column;align-items:center;gap:8px;width:320px}.login-field{width:100%;text-align:left}.login-takeover{align-self:flex-start;margin-top:4px}.login-button{margin-top:8px;width:100%}.login-errors{margin-top:16px;max-width:320px}.login-conflict{margin-top:16px;max-width:320px;padding:12px 16px;border-radius:4px;background-color:var(--color-surface-light);font-size:.85rem;text-align:left}.login-conflict p{margin:0 0 8px}.login-conflict-actions{display:flex;gap:8px;margin-top:12px;justify-content:flex-end}.login-error{padding:8px 12px;margin-bottom:4px;font-size:.85rem;border-radius:4px;background-color:var(--color-surface-light)}\n"] }]
|
|
884
939
|
}], ctorParameters: () => [{ type: AuthenticationService }, { type: i2$1.Router }] });
|
|
885
940
|
|
|
886
941
|
class ConfirmDialog {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
942
|
+
constructor() {
|
|
943
|
+
this.data = inject(MAT_DIALOG_DATA);
|
|
944
|
+
this.dialogRef = inject((MatDialogRef));
|
|
945
|
+
}
|
|
946
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ConfirmDialog, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
947
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: ConfirmDialog, isStandalone: true, selector: "shell-confirm-dialog", ngImport: i0, template: `
|
|
891
948
|
<h2 mat-dialog-title>{{ data.title }}</h2>
|
|
892
949
|
<mat-dialog-content>
|
|
893
950
|
<p>{{ data.message }}</p>
|
|
@@ -899,7 +956,7 @@ class ConfirmDialog {
|
|
|
899
956
|
<button mat-button (click)="dialogRef.close(false)">{{ data.cancelLabel ?? 'Cancel' }}</button>
|
|
900
957
|
<button mat-flat-button color="warn" (click)="dialogRef.close(true)">{{ data.confirmLabel ?? 'Confirm' }}</button>
|
|
901
958
|
</mat-dialog-actions>
|
|
902
|
-
`, isInline: true, styles: [":host{display:block}.warning-message{color:var(--color-warning);margin-top:8px}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1$2.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1$2.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1$2.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }] });
|
|
959
|
+
`, isInline: true, styles: [":host{display:block}.warning-message{color:var(--color-warning);margin-top:8px}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1$2.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1$2.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1$2.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }] }); }
|
|
903
960
|
}
|
|
904
961
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ConfirmDialog, decorators: [{
|
|
905
962
|
type: Component,
|