@franzzemen/brokenstock-shell 0.4.5 → 0.4.6

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.
@@ -2777,12 +2777,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
2777
2777
  args: [{ providedIn: 'root' }]
2778
2778
  }], ctorParameters: () => [] });
2779
2779
 
2780
+ /** Roomy enough for the scanner beside a chart, small enough for a laptop. */
2781
+ const POP_OUT_WIDTH = 1280;
2782
+ const POP_OUT_HEIGHT = 900;
2780
2783
  class StatusBar {
2781
2784
  constructor() {
2782
2785
  this.#authService = inject(AuthenticationService);
2783
2786
  this.#focusService = inject(TerminalFocusService);
2784
2787
  this.#commandService = inject(CommandService);
2788
+ this.#sharedSession = inject(SHELL_SHARED_SESSION, { optional: true }) ?? false;
2785
2789
  this.isAuthenticated = this.#authService.authenticated;
2790
+ /**
2791
+ * Whether a second window is offered at all (multiple-windows PRD D14).
2792
+ *
2793
+ * ⚠ GATED ON SHARED SESSIONS, and this is a correctness gate rather than a
2794
+ * tidiness one. Without a session shared across windows, the new window starts
2795
+ * with no token, goes to the login screen, and signing in there ends the
2796
+ * session THIS window is using. A pop-out button in an app without sharing is
2797
+ * a button that signs you out — the exact failure the multiple-windows work
2798
+ * exists to remove.
2799
+ */
2800
+ this.canPopOut = computed(() => this.#sharedSession && this.isAuthenticated(), /* @ts-ignore */
2801
+ ...(ngDevMode ? [{ debugName: "canPopOut" }] : /* istanbul ignore next */ []));
2786
2802
  this.loginStatus = computed(() => this.#authService.authenticating()
2787
2803
  ? 'Authenticating...'
2788
2804
  : this.#authService.authenticated()
@@ -2793,21 +2809,41 @@ class StatusBar {
2793
2809
  #authService;
2794
2810
  #focusService;
2795
2811
  #commandService;
2812
+ #sharedSession;
2796
2813
  logout() {
2797
2814
  this.#authService.logout();
2798
2815
  this.#commandService.echoCommand('logout');
2799
2816
  this.#focusService.requestFocus();
2800
2817
  }
2818
+ /**
2819
+ * Open the screen you are on in a second, sized window.
2820
+ *
2821
+ * There is no mechanism by which this differs from the browser's own
2822
+ * "Duplicate tab": every window is a separate copy of the app started at some
2823
+ * address. It exists for the sizing and because it is discoverable — Ctrl+T,
2824
+ * a bookmark and dragging a tab out all work equally well once the session is
2825
+ * shared.
2826
+ *
2827
+ * `noopener` is deliberate. The new window needs nothing from this one — it
2828
+ * reads the session from storage — and leaving the opener reachable would give
2829
+ * two windows script access to each other for no purpose.
2830
+ */
2831
+ popOut() {
2832
+ if (typeof window === 'undefined')
2833
+ return;
2834
+ window.open(window.location.href, '_blank', `noopener,noreferrer,width=${POP_OUT_WIDTH},height=${POP_OUT_HEIGHT}`);
2835
+ this.#focusService.requestFocus();
2836
+ }
2801
2837
  openFaq() {
2802
2838
  this.#commandService.execute('faq');
2803
2839
  this.#focusService.requestFocus();
2804
2840
  }
2805
2841
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StatusBar, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2806
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", 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 <button\n type=\"button\"\n class=\"faq-button text-interactive\"\n (click)=\"openFaq()\"\n title=\"Open FAQ (or type `faq`)\"\n aria-label=\"Open FAQ\"\n >?</button>\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}.faq-button{margin-left:auto;background:none;border:1px solid var(--color-surface-border);border-radius:50%;width:22px;height:22px;font-size:.85rem;font-weight:600;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}.faq-button:hover{opacity:.8}\n"], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
2842
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", 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 @if (canPopOut()) {\n <button\n type=\"button\"\n class=\"faq-button text-interactive\"\n (click)=\"popOut()\"\n title=\"Open this screen in a second window\"\n aria-label=\"Open this screen in a second window\"\n >\u29C9</button>\n }\n <button\n type=\"button\"\n class=\"faq-button text-interactive\"\n (click)=\"openFaq()\"\n title=\"Open FAQ (or type `faq`)\"\n aria-label=\"Open FAQ\"\n >?</button>\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}.faq-button{margin-left:auto;background:none;border:1px solid var(--color-surface-border);border-radius:50%;width:22px;height:22px;font-size:.85rem;font-weight:600;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}.faq-button:hover{opacity:.8}\n"], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
2807
2843
  }
2808
2844
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StatusBar, decorators: [{
2809
2845
  type: Component,
2810
- args: [{ selector: 'shell-status-bar', changeDetection: ChangeDetectionStrategy.Eager, 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 <button\n type=\"button\"\n class=\"faq-button text-interactive\"\n (click)=\"openFaq()\"\n title=\"Open FAQ (or type `faq`)\"\n aria-label=\"Open FAQ\"\n >?</button>\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}.faq-button{margin-left:auto;background:none;border:1px solid var(--color-surface-border);border-radius:50%;width:22px;height:22px;font-size:.85rem;font-weight:600;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}.faq-button:hover{opacity:.8}\n"] }]
2846
+ args: [{ selector: 'shell-status-bar', changeDetection: ChangeDetectionStrategy.Eager, 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 @if (canPopOut()) {\n <button\n type=\"button\"\n class=\"faq-button text-interactive\"\n (click)=\"popOut()\"\n title=\"Open this screen in a second window\"\n aria-label=\"Open this screen in a second window\"\n >\u29C9</button>\n }\n <button\n type=\"button\"\n class=\"faq-button text-interactive\"\n (click)=\"openFaq()\"\n title=\"Open FAQ (or type `faq`)\"\n aria-label=\"Open FAQ\"\n >?</button>\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}.faq-button{margin-left:auto;background:none;border:1px solid var(--color-surface-border);border-radius:50%;width:22px;height:22px;font-size:.85rem;font-weight:600;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}.faq-button:hover{opacity:.8}\n"] }]
2811
2847
  }] });
2812
2848
 
2813
2849
  class SideMenu {