Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/firebird-ng/src/lib/builtin-command-handlers.ts is written in an unsupported language. File is not indexed.

0001 /**
0002  * Built-in command handlers, registered through the same COMMAND_HANDLERS
0003  * token user extensions get. Each handler owns one command type and its
0004  * `?cmd=type:arg` URL grammar.
0005  */
0006 
0007 import { Injectable, Injector, inject } from '@angular/core';
0008 import { AppCommand, CommandHandler, ConfigService, coerceConfigValue } from '@dexvis/app-features';
0009 import { injectCameraPresets } from '@dexvis/firebird-ng/api';
0010 
0011 // Handlers live in the initial bundle (referenced from app.config), so the
0012 // display-stack services (three.js and friends) are resolved through DYNAMIC
0013 // imports at execute time: a static import would defeat route code-splitting.
0014 
0015 /** Polls a condition (startup path only, never inside the frame loop). */
0016 async function waitFor(condition: () => boolean, timeoutMs: number, stepMs = 100): Promise<boolean> {
0017   const deadline = Date.now() + timeoutMs;
0018   while (Date.now() < deadline) {
0019     if (condition()) return true;
0020     await new Promise(resolve => setTimeout(resolve, stepMs));
0021   }
0022   return condition();
0023 }
0024 
0025 /** `open-geometry`: load detector geometry: `{ type, url }` / `?cmd=open-geometry:URL`. */
0026 @Injectable()
0027 export class OpenGeometryCommandHandler implements CommandHandler {
0028   readonly type = 'open-geometry';
0029   private injector = inject(Injector);
0030 
0031   fromUrlArg(arg: string): AppCommand {
0032     return { type: this.type, url: arg };
0033   }
0034 
0035   /** Loads through the first geometry loader that claims the URL; resolves when it is on screen. */
0036   async execute(command: AppCommand): Promise<void> {
0037     const url = command['url'] as string;
0038     if (!url) throw new Error(`open-geometry: 'url' argument is required`);
0039     const { EventDisplayService } = await import('@dexvis/firebird-ng/display');
0040     await this.injector.get(EventDisplayService).openGeometry(url);
0041   }
0042 }
0043 
0044 /** `open-dex`: load event data: `{ type, url }` / `?cmd=open-dex:URL` / `?dex=URL`. */
0045 @Injectable()
0046 export class OpenDexCommandHandler implements CommandHandler {
0047   readonly type = 'open-dex';
0048   private injector = inject(Injector);
0049 
0050   fromUrlArg(arg: string): AppCommand {
0051     return { type: this.type, url: arg };
0052   }
0053 
0054   /** Loads through the first event loader that claims the URL; resolves when the first event shows. */
0055   async execute(command: AppCommand): Promise<void> {
0056     const url = command['url'] as string;
0057     if (!url) throw new Error(`open-dex: 'url' argument is required`);
0058     const { EventDisplayService } = await import('@dexvis/firebird-ng/display');
0059     await this.injector.get(EventDisplayService).openEvents(url);
0060   }
0061 }
0062 
0063 /** `show-event`: select entry by index: `{ type, index }` / `?event=N`. */
0064 @Injectable()
0065 export class ShowEventCommandHandler implements CommandHandler {
0066   readonly type = 'show-event';
0067   private injector = inject(Injector);
0068 
0069   fromUrlArg(arg: string): AppCommand {
0070     return { type: this.type, index: parseInt(arg, 10) };
0071   }
0072 
0073   async execute(command: AppCommand): Promise<void> {
0074     const index = Number(command['index']);
0075     if (isNaN(index)) throw new Error(`show-event: numeric 'index' argument is required`);
0076 
0077     const { DataModelService } = await import('@dexvis/firebird-ng/display');
0078     const data = this.injector.get(DataModelService);
0079 
0080     // Events may still be loading (config-driven autoload runs in parallel).
0081     const loaded = await waitFor(() => data.entries().length > 0, 30_000);
0082     if (!loaded) {
0083       throw new Error('show-event: no events were loaded within 30 s');
0084     }
0085     const entries = data.entries();
0086     if (index < 0 || index >= entries.length) {
0087       throw new Error(`show-event: index ${index} out of range (0..${entries.length - 1})`);
0088     }
0089     data.setCurrentEntry(entries[index]);
0090   }
0091 }
0092 
0093 /**
0094  * `set-config`: set a config value: `{ type, key, value }` / `?cmd=set-config:key=value`.
0095  * URL/server/batch sources apply as SESSION values (never persisted: a link
0096  * or script cannot poison saved user preferences); ui/code sources persist.
0097  * A ui/code write to a key no code declared yet goes to a placeholder: the
0098  * owning code's later declaration still sets the key's default, type and
0099  * validator, and reads the written value with them.
0100  */
0101 @Injectable()
0102 export class SetConfigCommandHandler implements CommandHandler {
0103   readonly type = 'set-config';
0104   private config = inject(ConfigService);
0105 
0106   fromUrlArg(arg: string): AppCommand {
0107     const eq = arg.indexOf('=');
0108     if (eq < 0) return { type: this.type, key: arg, value: '' };
0109     return { type: this.type, key: arg.substring(0, eq), value: arg.substring(eq + 1) };
0110   }
0111 
0112   execute(command: AppCommand): void {
0113     const key = command['key'] as string;
0114     if (!key) throw new Error(`set-config: 'key' argument is required`);
0115     const value = command['value'];
0116     const transient = command.source === 'url' || command.source === 'server' || command.source === 'batch';
0117     if (transient) {
0118       this.config.applySessionValue(key, value);
0119     } else {
0120       const property = this.config.getConfigOrPlaceholder(key, value);
0121       // Text from a URL-style argument becomes the declared type ('7' -> 7)
0122       property.setValue(coerceConfigValue(value, property.codeDefault));
0123     }
0124   }
0125 }
0126 
0127 /**
0128  * `camera-preset`: move the camera to a named preset: `?cmd=camera-preset:top`.
0129  * The presets come from `withCameraPreset()` registrations; the built-in
0130  * ones are the face views and `home` (see `withStandardCameraPresets()`).
0131  */
0132 @Injectable()
0133 export class CameraPresetCommandHandler implements CommandHandler {
0134   readonly type = 'camera-preset';
0135   private injector = inject(Injector);
0136   private presets = injectCameraPresets();
0137 
0138   fromUrlArg(arg: string): AppCommand {
0139     return { type: this.type, name: arg };
0140   }
0141 
0142   async execute(command: AppCommand): Promise<void> {
0143     const name = command['name'] as string;
0144     const preset = this.presets.find(candidate => candidate.name === name);
0145     if (!preset) {
0146       throw new Error(`camera-preset: unknown preset '${name}'. Known: ${this.presets.map(known => known.name).join(', ')}`);
0147     }
0148     const { ThreeService } = await import('@dexvis/firebird-ng/display');
0149     this.injector.get(ThreeService).applyCameraPreset(preset);
0150   }
0151 }
0152 
0153 /**
0154  * `animate-collision`: play the collision intro (`withCollisionIntro()`),
0155  * then the event's time animation from the start: `?cmd=animate-collision`.
0156  * Without an intro the time animation starts at once.
0157  */
0158 @Injectable()
0159 export class AnimateCollisionCommandHandler implements CommandHandler {
0160   readonly type = 'animate-collision';
0161   private injector = inject(Injector);
0162 
0163   async execute(): Promise<void> {
0164     const { EventDisplayService } = await import('@dexvis/firebird-ng/display');
0165     await this.injector.get(EventDisplayService).animateWithCollision();
0166   }
0167 }