Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/firebird-ng/display/src/lib/components/data-selector/data-selector.component.ts is written in an unsupported language. File is not indexed.

0001 import {
0002   ChangeDetectionStrategy,
0003   Component,
0004   Type,
0005   computed,
0006   effect,
0007   inject,
0008   input,
0009   output,
0010   signal,
0011   untracked,
0012 } from '@angular/core';
0013 import { NgComponentOutlet } from '@angular/common';
0014 import { MatButton } from '@angular/material/button';
0015 import { MatIcon } from '@angular/material/icon';
0016 import { resolveRegistry } from '@dexvis/app-features';
0017 import {
0018   DATA_SELECTOR_TABS,
0019   DataCatalogService,
0020   DataSelection,
0021   DataSelectionService,
0022   DataSelectorTabRegistration,
0023 } from '@dexvis/firebird-ng/api';
0024 
0025 /**
0026  * The data selector: one control to choose what the display shows — a named
0027  * preset, a physics case picked by tags, or geometry and event files by URL
0028  * or from disk. The display's "open" toolbar panel and the config page both
0029  * embed it; they differ only in the button caption and in what happens after
0030  * Show (`applied`): the panel closes, the config page navigates to /display.
0031  *
0032  * Tabs come from DI (`withDataSelectorTab`), so an experiment pack can add,
0033  * replace (same tab id) or drop them (`withoutFeatures('data-selector-tab:<id>')`). A tab reads and writes `DataSelectionService.draft`;
0034  * this host owns the Show/Cancel buttons and applies the draft through the
0035  * one shared path (config keys, then a live display reloads).
0036  */
0037 @Component({
0038   selector: 'firebird-data-selector',
0039   templateUrl: './data-selector.component.html',
0040   styleUrl: './data-selector.component.scss',
0041   changeDetection: ChangeDetectionStrategy.OnPush,
0042   imports: [NgComponentOutlet, MatButton, MatIcon],
0043 })
0044 export class DataSelectorComponent {
0045   /** Caption of the apply button: 'Show' in the display panel, 'Display' on the config page. */
0046   readonly applyLabel = input('Show');
0047   /** When false, the apply button is enabled with an unchanged draft (the config page navigates anyway). */
0048   readonly requireChanges = input(true);
0049   readonly showCancel = input(true);
0050   /** The selection that was applied. */
0051   readonly applied = output<DataSelection>();
0052   readonly cancelled = output<void>();
0053 
0054   readonly catalog = inject(DataCatalogService);
0055   readonly selection = inject(DataSelectionService);
0056   /** One tab per id: a later registration with the id of an earlier one replaces it (tokens.ts). */
0057   private readonly registeredTabs = resolveRegistry(inject(DATA_SELECTOR_TABS, { optional: true }) ?? [], tab => tab.id);
0058 
0059   /** Registered tabs by `order` (ties: registration order), minus catalog-only tabs when there is no catalog. */
0060   readonly visibleTabs = computed<DataSelectorTabRegistration[]>(() => {
0061     const catalogIsEmpty = this.catalog.isEmpty();
0062     return this.registeredTabs
0063       .filter(tab => tab.needsCatalog === false || !catalogIsEmpty)
0064       .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
0065   });
0066 
0067   readonly activeTabId = signal<string | null>(null);
0068   readonly tabComponent = signal<Type<unknown> | null>(null);
0069   readonly tabError = signal<string | null>(null);
0070 
0071   readonly applyEnabled = computed(() => !this.requireChanges() || this.selection.draftHasChanges());
0072 
0073   constructor() {
0074     void this.catalog.ensureRemoteLoaded();
0075     // Keep a valid tab selected as the tab set changes (the remote catalog
0076     // arriving can reveal Presets/Physics). First choice: Presets when a
0077     // preset is what is loaded, otherwise Manual, which shows the URLs as is.
0078     effect(() => {
0079       const tabs = this.visibleTabs();
0080       const current = untracked(this.activeTabId);
0081       if (tabs.some(tab => tab.id === current)) return;
0082       const preferred = untracked(this.selection.activeEntry)
0083         ? tabs[0]
0084         : (tabs.find(tab => tab.id === 'manual') ?? tabs[0]);
0085       if (preferred) this.selectTab(preferred.id);
0086     });
0087   }
0088 
0089   selectTab(id: string): void {
0090     const tab = this.visibleTabs().find(candidate => candidate.id === id);
0091     if (!tab || this.activeTabId() === id) return;
0092     // Each tab starts from the configured state; a half-made choice on
0093     // another tab must not leak into this one
0094     this.selection.clearDraft();
0095     this.activeTabId.set(id);
0096     this.tabComponent.set(null);
0097     this.tabError.set(null);
0098     tab.load().then(
0099       component => {
0100         if (this.activeTabId() === id) this.tabComponent.set(component);
0101       },
0102       error => {
0103         console.error(`[DataSelector] tab '${id}' failed to load`, error);
0104         this.tabError.set(`Tab '${tab.label}' failed to load: ${error instanceof Error ? error.message : error}`);
0105       },
0106     );
0107   }
0108 
0109   apply(): void {
0110     const draft = this.selection.draft();
0111     this.selection.apply(draft);
0112     this.applied.emit(draft);
0113   }
0114 
0115   cancel(): void {
0116     this.selection.clearDraft();
0117     this.cancelled.emit();
0118   }
0119 }