Warning, /firebird/packages/firebird-ng/display/src/lib/pages/input-config/input-config.component.ts is written in an unsupported language. File is not indexed.
0001 import { Component, OnInit, ChangeDetectionStrategy, DestroyRef, inject, signal } from '@angular/core';
0002 import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
0003 import { FormControl, ReactiveFormsModule } from '@angular/forms';
0004 import { Router, RouterLink } from '@angular/router';
0005 import { NgIf } from '@angular/common';
0006 import { MatCard, MatCardContent, MatCardTitle } from '@angular/material/card';
0007 import { MatSlideToggle } from '@angular/material/slide-toggle';
0008 import { MatFormField } from '@angular/material/form-field';
0009 import { MatInput, MatLabel } from '@angular/material/input';
0010 import { MatAccordion, MatExpansionPanel, MatExpansionPanelTitle, MatExpansionPanelHeader } from '@angular/material/expansion';
0011 import { MatButton } from "@angular/material/button";
0012 import { MatSelect } from '@angular/material/select';
0013 import { MatOption } from '@angular/material/autocomplete';
0014 import { ConfigProperty, ConfigSchema, ConfigService, ServerConfigService, resolveRegistry } from '@dexvis/app-features';
0015 import {
0016 BACKEND_URL_CONFIG,
0017 BACKEND_USE_API_CONFIG,
0018 GEOMETRY_CUT_LIST_CONFIG,
0019 GEOMETRY_FAST_MATERIAL_CONFIG,
0020 GEOMETRY_ROOT_FILTER_CONFIG,
0021 GEOMETRY_THEME_CONFIG,
0022 GEOMETRY_THEMES,
0023 resolveRootGeometryRules,
0024 ROOT_GEOMETRY_RULES,
0025 ServerConfig,
0026 UrlService,
0027 USE_CONTROLLER_CONFIG,
0028 } from '@dexvis/firebird-ng/api';
0029 import { DataSelectorComponent } from '../../components/data-selector/data-selector.component';
0030 import { FirebirdShellComponent } from '../../components/firebird-shell/firebird-shell.component';
0031 import { GEOMETRY_PIPELINE_DIAGRAM_URL } from './geometry-pipeline-diagram';
0032
0033 /** One choice of a geometry pipeline select. */
0034 interface PipelineOption {
0035 value: string;
0036 label: string;
0037 }
0038
0039 /**
0040 * The configuration page: what to load (the data selector, shared with the
0041 * display's toolbar panel), the geometry pipeline options, controls and the
0042 * backend connection. Every control is bound to a config key, so the same
0043 * values are reachable from deep links, config.jsonc and yaml.
0044 */
0045 @Component({
0046 selector: 'app-input-config',
0047 standalone: true,
0048 imports: [
0049 ReactiveFormsModule,
0050 RouterLink,
0051 MatCard,
0052 MatCardContent,
0053 MatCardTitle,
0054 MatSlideToggle,
0055 MatFormField,
0056 MatInput,
0057 MatLabel,
0058 DataSelectorComponent,
0059 MatAccordion,
0060 MatExpansionPanel,
0061 MatExpansionPanelTitle,
0062 MatExpansionPanelHeader,
0063 FirebirdShellComponent,
0064 MatButton,
0065 MatSelect,
0066 MatOption,
0067 NgIf,
0068 ],
0069 templateUrl: './input-config.component.html',
0070 changeDetection: ChangeDetectionStrategy.OnPush,
0071 styleUrls: ['./input-config.component.scss']
0072 })
0073 export class InputConfigComponent implements OnInit {
0074 private readonly router = inject(Router);
0075 private readonly destroyRef = inject(DestroyRef);
0076 /** The backend the app talks to right now (shown under Backend Details). */
0077 protected readonly urlService = inject(UrlService);
0078 protected readonly pipelineDiagramUrl = GEOMETRY_PIPELINE_DIAGRAM_URL;
0079
0080 serverUseApi = new FormControl<boolean>(false);
0081 serverApiUrl = new FormControl<string>('http://localhost:5454');
0082
0083 geometryThemeName = new FormControl<string>('off');
0084 geometryCutListName = new FormControl<string>('off');
0085 geometryRootFilterName = new FormControl<string>('off');
0086
0087 /** The registered themes (withGeometryTheme); the template adds 'off'. */
0088 readonly themeOptions: PipelineOption[] = resolveRegistry(inject(GEOMETRY_THEMES, { optional: true }) ?? [], theme => theme.id)
0089 .map(theme => ({ value: theme.id, label: theme.label ?? theme.id }));
0090 /** Cut lists and edit rule sets (withRootGeometryRules), filled once the rule data loads; the template adds 'off'. */
0091 readonly cutListOptions = signal<PipelineOption[]>([]);
0092 readonly rootFilterOptions = signal<PipelineOption[]>([]);
0093 private readonly rootRuleSources = inject(ROOT_GEOMETRY_RULES, { optional: true }) ?? [];
0094 geometryFastAndUgly = new FormControl<boolean>(false);
0095 useController = new FormControl<boolean>(false);
0096
0097 /**
0098 * Server config for the template, bound through the service SIGNAL:
0099 * ServerConfigService replaces its config object when the async load
0100 * completes, so a by-reference snapshot taken in ngOnInit goes stale.
0101 */
0102 get firebirdConfig(): ServerConfig {
0103 return this.firebirdConfigService.configSignal();
0104 }
0105
0106 constructor(
0107 private userConfigService: ConfigService,
0108 private firebirdConfigService: ServerConfigService<ServerConfig>
0109 ) {}
0110
0111 /** The selector wrote the config keys; the display page loads from them on init. */
0112 onDataApplied(): void {
0113 void this.router.navigate(['/display']);
0114 }
0115
0116 /**
0117 * Two-way binds a form control to a config key. The key is declared from
0118 * the same schema its consumer declares (UrlService, GameControllerService,
0119 * GeometryService), so the control writes exactly what the app reads.
0120 * Both subscriptions end with the page.
0121 */
0122 private bind<T>(control: FormControl<T | null>, schema: ConfigSchema<T>): ConfigProperty<T> {
0123 const property = this.userConfigService.declare(schema);
0124 control.setValue(property.value, { emitEvent: false });
0125 property.changes$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => {
0126 control.setValue(value, { emitEvent: false });
0127 });
0128 control.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => {
0129 if (value !== null) {
0130 property.value = value;
0131 }
0132 });
0133 return property;
0134 }
0135
0136 ngOnInit(): void {
0137 this.bind(this.serverUseApi, BACKEND_USE_API_CONFIG);
0138 this.bind(this.serverApiUrl, BACKEND_URL_CONFIG);
0139 this.bind(this.geometryThemeName, GEOMETRY_THEME_CONFIG);
0140 this.bind(this.geometryCutListName, GEOMETRY_CUT_LIST_CONFIG);
0141 this.bind(this.geometryRootFilterName, GEOMETRY_ROOT_FILTER_CONFIG);
0142 this.bind(this.geometryFastAndUgly, GEOMETRY_FAST_MATERIAL_CONFIG);
0143 this.bind(this.useController, USE_CONTROLLER_CONFIG);
0144
0145 resolveRootGeometryRules(this.rootRuleSources).then(rules => {
0146 this.cutListOptions.set(Object.entries(rules.cutLists ?? {})
0147 .map(([name, cutList]) => ({ value: name, label: cutList.label ?? name })));
0148 this.rootFilterOptions.set(Object.entries(rules.editRules ?? {})
0149 .map(([name, ruleSet]) => ({ value: name, label: ruleSet.label ?? name })));
0150 }, error => console.error('[Config] Geometry rules failed to load:', error));
0151 }
0152
0153 /** Drops the saved pipeline choices: each falls back to the server value or its default. */
0154 resetGeometryToDefaults() {
0155 for (const schema of [GEOMETRY_THEME_CONFIG, GEOMETRY_CUT_LIST_CONFIG, GEOMETRY_ROOT_FILTER_CONFIG]) {
0156 this.userConfigService.declare<string>(schema).setDefault();
0157 }
0158 }
0159 }