Warning, /firebird/packages/firebird-ng/api/src/lib/firebird-features.ts is written in an unsupported language. File is not indexed.
0001 /**
0002 * The `with*()` feature functions: the composition API through which an
0003 * application (Firebird's own or an external experiment's) assembles its
0004 * event display. `provideFirebird()` (provide-firebird.ts) installs them.
0005 *
0006 * API shape: one contribution per `with*()` call. Plurality comes from
0007 * composition: `provideFirebird(...)` is variadic, and `firebirdFeatures(...)`
0008 * and `firebirdPack(id, ...)` bundle features (experiment packs).
0009 *
0010 * The composition mechanism (feature type and identity, multi-provider
0011 * plumbing, config layering, command bus, URL startup, server config
0012 * loading) comes from `@dexvis/app-features`; this file adds Firebird's own
0013 * extension points and its URL shorthand grammar. The registry precedence
0014 * rule is documented in tokens.ts.
0015 */
0016
0017 import { Type } from '@angular/core';
0018 import {
0019 AppFeature,
0020 AppFeatureInput,
0021 appFeaturePack,
0022 appFeatures,
0023 contributeClass,
0024 contributeValue,
0025 withConfigDefaults,
0026 withUrlShorthand,
0027 } from '@dexvis/app-features';
0028 import type {
0029 PiecePainterConstructor,
0030 EventPieceFactory,
0031 GeometryDataLoader,
0032 EventDataLoader,
0033 DataCatalog,
0034 } from '@dexvis/firebird-core';
0035 import {
0036 APP_VERSION,
0037 CAMERA_LIMITS,
0038 CAMERA_PRESETS,
0039 COLLISION_INTRO,
0040 CameraLimits,
0041 CameraPreset,
0042 CollisionIntroLoader,
0043 DATA_CATALOGS,
0044 DATA_SELECTOR_TABS,
0045 DataSelectorTabRegistration,
0046 EVENT_LOADERS,
0047 EVENT_PIECE_FACTORIES,
0048 FIREBIRD_WORKERS,
0049 FirebirdWorkers,
0050 GEOMETRY_LOADERS,
0051 GEOMETRY_POST_PROCESSORS,
0052 GEOMETRY_THEMES,
0053 LAZY_THREE_EXTENSIONS,
0054 PAINTERS,
0055 ROOT_GEOMETRY_RULES,
0056 THREE_EXTENSIONS,
0057 TOOLBAR_ACTIONS,
0058 ToolbarActionRegistration,
0059 } from './tokens';
0060 import type { LazyThreeExtensionLoader, ThreeExtension } from './three-extension';
0061 import type {
0062 GeometryPostProcessorRegistration,
0063 GeometryThemeRegistration,
0064 RootGeometryRulesSource,
0065 } from './geometry-pipeline';
0066
0067 /**
0068 * A Firebird feature: the providers contributed by one `with*()` call, or a
0069 * composition of features. The same type as `AppFeature` from
0070 * `@dexvis/app-features`, so features from both compose freely.
0071 */
0072 export type FirebirdFeature = AppFeature;
0073
0074 /**
0075 * Composes features into one feature. Falsy entries are skipped so packs can
0076 * include conditional features. The same function as `appFeatures()` from
0077 * `@dexvis/app-features`.
0078 */
0079 export const firebirdFeatures: (...features: AppFeatureInput[]) => FirebirdFeature = appFeatures;
0080
0081 /**
0082 * Composes features into a pack with an id: the mechanism behind experiment
0083 * packs. The id makes the pack replaceable and removable
0084 * (`withoutFeatures(id)`), a pack installed twice is installed once, and
0085 * development builds name the pack in collision warnings.
0086 *
0087 * ```ts
0088 * export function withMyExperiment(): FirebirdFeature {
0089 * return firebirdPack('my-experiment', withEventPiece(MyFactory), withDefaultGeometry(url));
0090 * }
0091 * ```
0092 *
0093 * The same function as `appFeaturePack()` from `@dexvis/app-features`.
0094 */
0095 export const firebirdPack: (id: string, ...features: AppFeatureInput[]) => FirebirdFeature = appFeaturePack;
0096
0097 /** Registers an event piece factory (DEX type decoder). The later factory for a type wins. */
0098 export function withEventPiece(factory: Type<EventPieceFactory>): FirebirdFeature {
0099 return contributeClass(EVENT_PIECE_FACTORIES, factory, { kind: 'event-piece' });
0100 }
0101
0102 /**
0103 * Registers a painter for a piece type. The type comes from
0104 * `opts.forPieceType`, or from the painter's static `meta.forPieceTypes`.
0105 * The first painter registered for a type is its default.
0106 */
0107 export function withPainter(
0108 painterClass: PiecePainterConstructor,
0109 opts?: { forPieceType?: string },
0110 ): FirebirdFeature {
0111 const meta = (painterClass as unknown as { meta?: { forPieceTypes?: string[] } }).meta;
0112 const types = opts?.forPieceType ? [opts.forPieceType] : (meta?.forPieceTypes ?? []);
0113 if (types.length === 0) {
0114 throw new Error(`withPainter(${painterClass.name}): pass { forPieceType } or declare static meta.forPieceTypes`);
0115 }
0116 return firebirdFeatures(...types.map(forPieceType =>
0117 contributeValue(PAINTERS, { forPieceType, painterClass }, { kind: 'painter' })));
0118 }
0119
0120 /**
0121 * Registers a painter through a dynamic import, keeping heavy painter code
0122 * (three.js materials etc.) out of the initial bundle. The class resolves
0123 * before the first event is painted; its place among the painters of the
0124 * type is its registration position, whenever its chunk arrives.
0125 */
0126 export function withLazyPainter(
0127 forPieceType: string,
0128 load: () => Promise<PiecePainterConstructor>,
0129 ): FirebirdFeature {
0130 return contributeValue(PAINTERS, { forPieceType, load }, { kind: 'painter' });
0131 }
0132
0133 /** Registers a rendering-machinery extension (instantiated through DI). */
0134 export function withThreeExtension(extension: Type<ThreeExtension>): FirebirdFeature {
0135 return contributeClass(THREE_EXTENSIONS, extension, { kind: 'three-extension' });
0136 }
0137
0138 /**
0139 * Registers a lazily-loaded extension. The dynamic import keeps it out of the
0140 * initial bundle; it is loaded and initialized after the scene is up.
0141 */
0142 export function withLazyThreeExtension(load: LazyThreeExtensionLoader): FirebirdFeature {
0143 return contributeValue(LAZY_THREE_EXTENSIONS, load, { kind: 'three-extension' });
0144 }
0145
0146 /** Registers a geometry format/scheme loader. A later loader with the same `meta.id` replaces it in place. */
0147 export function withGeometryLoader(loader: Type<GeometryDataLoader>): FirebirdFeature {
0148 return contributeClass(GEOMETRY_LOADERS, loader, { kind: 'geometry-loader' });
0149 }
0150
0151 /** Registers an event data format loader. A later loader with the same `meta.id` replaces it in place. */
0152 export function withEventLoader(loader: Type<EventDataLoader>): FirebirdFeature {
0153 return contributeClass(EVENT_LOADERS, loader, { kind: 'event-loader' });
0154 }
0155
0156 /**
0157 * Contributes datasets to the data selector: named presets (geometry + events),
0158 * tagged for the physics picker, and URL lists for manual pick. Several
0159 * contributions merge in registration order; the server's config.jsonc
0160 * `dataCatalog` and a remote `catalog.url` file add to them at runtime.
0161 *
0162 * ```ts
0163 * withDataCatalog({
0164 * facets: [{ key: 'process', label: 'Process', values: { 'dis-nc': { label: 'DIS NC' } } }],
0165 * entries: [{ name: 'DIS NC 10x100', geometry: 'exp://detector.root',
0166 * events: 'https://host/nc_10x100.firebird.zip', tags: { process: 'dis-nc', beam: '10x100' } }],
0167 * })
0168 * ```
0169 */
0170 export function withDataCatalog(catalog: DataCatalog): FirebirdFeature {
0171 return contributeValue(DATA_CATALOGS, catalog, { kind: 'data-catalog' });
0172 }
0173
0174 /**
0175 * Adds a tab to the data selector control. The component must be a dynamic
0176 * import (tab UI pulls Material/forms code that must stay out of the initial
0177 * bundle); it reads and writes the shared `DataSelectionService.draft`.
0178 * Feature id `data-selector-tab:<id>`: a later tab with the same id replaces
0179 * this one, and `withoutFeatures('data-selector-tab:<id>')` drops it.
0180 *
0181 * ```ts
0182 * withDataSelectorTab({ id: 'campaigns', label: 'Campaigns', order: 15,
0183 * load: () => import('./campaigns-tab.component').then(m => m.CampaignsTabComponent) })
0184 * ```
0185 */
0186 export function withDataSelectorTab(tab: DataSelectorTabRegistration): FirebirdFeature {
0187 return contributeValue(DATA_SELECTOR_TABS, tab, { kind: 'data-selector-tab', id: `data-selector-tab:${tab.id}` });
0188 }
0189
0190 /**
0191 * Sets the detector geometry the display loads when nothing else selects one.
0192 * Sugar for `withConfigDefaults({'geometry.selectedGeometry': url})`: the
0193 * same precedence applies, so server config, a saved user choice, and
0194 * `?geometry=` deep links all override it.
0195 */
0196 export function withDefaultGeometry(url: string): FirebirdFeature {
0197 return withConfigDefaults({ 'geometry.selectedGeometry': url });
0198 }
0199
0200 /**
0201 * Contributes pre-build TGeo rules: named edit rule sets (selected by
0202 * `geometry.rootFilterName`) and named cut lists (selected by
0203 * `geometry.cutListName`). The rules are data, posted to the geometry
0204 * worker with each load; pass a dynamic import to keep large rule data out
0205 * of the initial bundle. Contributions merge; a later rule set or cut list
0206 * with the same name replaces the earlier one.
0207 *
0208 * ```ts
0209 * withRootGeometryRules(() => import('./my-geometry-rules').then(m => m.myGeometryRules))
0210 * ```
0211 */
0212 export function withRootGeometryRules(rules: RootGeometryRulesSource): FirebirdFeature {
0213 return contributeValue(ROOT_GEOMETRY_RULES, rules, { kind: 'root-geometry-rules' });
0214 }
0215
0216 /**
0217 * Registers a geometry theme: subdetector rule sets (colors, merging,
0218 * outlines) applied after each load when config `geometry.themeName` is
0219 * `id`. Feature id `geometry-theme:<id>`.
0220 *
0221 * ```ts
0222 * withGeometryTheme({ id: 'my-colors', label: 'My colors',
0223 * load: () => import('./my-theme').then(m => m.myRuleSets) })
0224 * ```
0225 */
0226 export function withGeometryTheme(theme: GeometryThemeRegistration): FirebirdFeature {
0227 return contributeValue(GEOMETRY_THEMES, theme, { kind: 'geometry-theme', id: `geometry-theme:${theme.id}` });
0228 }
0229
0230 /**
0231 * Registers main-thread code that runs after each geometry load, once the
0232 * geometry sits in the scene and before the first frame shows it.
0233 * Processors run in registration order, each after the ids in its `after`.
0234 * Feature id `geometry-post-processor:<id>`.
0235 *
0236 * ```ts
0237 * withGeometryPostProcessor({ id: 'my-mirrors', after: ['my-arranger'],
0238 * load: () => import('./my-mirrors').then(m => m.MirrorProcessor) })
0239 * ```
0240 */
0241 export function withGeometryPostProcessor(processor: GeometryPostProcessorRegistration): FirebirdFeature {
0242 return contributeValue(GEOMETRY_POST_PROCESSORS, processor,
0243 { kind: 'geometry-post-processor', id: `geometry-post-processor:${processor.id}` });
0244 }
0245
0246 /**
0247 * Registers a named camera view for the `camera-preset` command
0248 * (`?cmd=camera-preset:<name>`), the navigation cube and the camera debug
0249 * panel. Feature id `camera-preset:<name>`: a later preset with the same name
0250 * replaces this one, so a pack pins `home` by registering it.
0251 *
0252 * ```ts
0253 * withCameraPreset({ name: 'endcap', position: [0, 2000, 9000], target: [0, 0, 3000] })
0254 * ```
0255 */
0256 export function withCameraPreset(preset: CameraPreset): FirebirdFeature {
0257 return contributeValue(CAMERA_PRESETS, preset, { kind: 'camera-preset', id: `camera-preset:${preset.name}` });
0258 }
0259
0260 /**
0261 * Pins the orbit distance limits [mm] of every view. Without this feature,
0262 * each geometry load derives them from the geometry's bounding sphere.
0263 * One setting per application (feature id `camera-limits`).
0264 */
0265 export function withCameraLimits(limits: CameraLimits): FirebirdFeature {
0266 return { kind: 'camera-limits', id: 'camera-limits', providers: [{ provide: CAMERA_LIMITS, useValue: limits }] };
0267 }
0268
0269 /**
0270 * Sets the collision intro: the animation played before an event's time
0271 * animation (the `animate-collision` command, offline recordings). A dynamic
0272 * import: intros pull three.js. Without it, events animate with no intro.
0273 * One per application (feature id `collision-intro`).
0274 *
0275 * ```ts
0276 * withCollisionIntro(() => import('./beams-intro').then(m => m.BeamsIntro))
0277 * ```
0278 */
0279 export function withCollisionIntro(load: CollisionIntroLoader): FirebirdFeature {
0280 return { kind: 'collision-intro', id: 'collision-intro', providers: [{ provide: COLLISION_INTRO, useValue: load }] };
0281 }
0282
0283 /**
0284 * Adds a button to the display's time toolbar that dispatches a command.
0285 * Feature id `toolbar-action:<id>`.
0286 *
0287 * ```ts
0288 * withToolbarAction({ id: 'collision', icon: 'close_fullscreen', label: 'Collision',
0289 * tooltip: 'Animate with beam particles collision', command: { type: 'animate-collision' } })
0290 * ```
0291 */
0292 export function withToolbarAction(action: ToolbarActionRegistration): FirebirdFeature {
0293 return contributeValue(TOOLBAR_ACTIONS, action, { kind: 'toolbar-action', id: `toolbar-action:${action.id}` });
0294 }
0295
0296 /**
0297 * Starts Firebird's web workers from entry modules of the application, which
0298 * its bundler builds (feature id `workers`). Required for ROOT geometry and
0299 * for converting ROOT event files in the browser.
0300 *
0301 * ```ts
0302 * withWorkers({
0303 * geometry: () => new Worker(new URL('./workers/geometry.worker', import.meta.url), { type: 'module' }),
0304 * rootFile: () => new Worker(new URL('./workers/root-file.worker', import.meta.url), { type: 'module' }),
0305 * })
0306 * ```
0307 *
0308 * with `geometry.worker.ts` holding `runGeometryWorker(self)` (from
0309 * `@dexvis/firebird-ng/workers/geometry`) and `root-file.worker.ts` holding
0310 * `runRootFileWorker(self)` (from `@dexvis/firebird-ng/workers/root-file`).
0311 * The `new Worker(new URL(...))` expressions
0312 * must stay literal in application code: that is the pattern the Angular
0313 * build recognizes and bundles.
0314 */
0315 export function withWorkers(workers: FirebirdWorkers): FirebirdFeature {
0316 return { kind: 'workers', id: 'workers', providers: [{ provide: FIREBIRD_WORKERS, useValue: workers }] };
0317 }
0318
0319 /**
0320 * Sets the version the display chrome shows in its logo menu: the
0321 * application's own release, which the library cannot know. One per
0322 * application (feature id `app-version`).
0323 *
0324 * ```ts
0325 * import { version } from '../../package.json';
0326 * provideFirebird(withAppVersion(version))
0327 * ```
0328 */
0329 export function withAppVersion(version: string): FirebirdFeature {
0330 return { kind: 'app-version', id: 'app-version', providers: [{ provide: APP_VERSION, useValue: version }] };
0331 }
0332
0333 /**
0334 * Firebird's URL shorthand grammar: `?geometry=<url>`, `?dex=<url>` and
0335 * `?event=<N>` stand for `?cmd=open-geometry:<url>`, `?cmd=open-dex:<url>`
0336 * and `?cmd=show-event:<N>`, queued in this order before the generic `?cmd=`
0337 * list. The built-in command handlers build the commands. Part of the
0338 * built-ins (`withFirebirdBuiltins()`).
0339 */
0340 export function withFirebirdUrlShorthands(): FirebirdFeature {
0341 return firebirdPack('firebird.url-shorthands',
0342 withUrlShorthand('geometry', 'open-geometry'),
0343 withUrlShorthand('dex', 'open-dex'),
0344 withUrlShorthand('event', 'show-event'),
0345 );
0346 }