Shared base #1

Open
Senrokai wants to merge 11 commits from shared-base into main
6 changed files with 28 additions and 28 deletions
Showing only changes of commit bd90b4a21f - Show all commits
+3 -2
View File
@@ -1,4 +1,4 @@
import { NgZone } from '@angular/core'; import { Injector, NgZone, runInInjectionContext } from '@angular/core';
import * as THREE from 'three/webgpu'; import * as THREE from 'three/webgpu';
import { beforeEach, describe, expect, it } from 'vitest'; import { beforeEach, describe, expect, it } from 'vitest';
@@ -10,7 +10,8 @@ import { EngineService } from './engine.service';
* arithmetic that keeps them showing the same thing. * arithmetic that keeps them showing the same thing.
*/ */
function engineWithCameras(): { engine: EngineService; perspective: THREE.PerspectiveCamera; orthographic: THREE.OrthographicCamera } { function engineWithCameras(): { engine: EngineService; perspective: THREE.PerspectiveCamera; orthographic: THREE.OrthographicCamera } {
const engine = new EngineService({ runOutsideAngular: (fn: () => unknown) => fn() } as unknown as NgZone); const zone = { runOutsideAngular: (fn: () => unknown) => fn() } as unknown as NgZone;
const engine = runInInjectionContext(Injector.create({ providers: [{ provide: NgZone, useValue: zone }] }), () => new EngineService());
const perspective = new THREE.PerspectiveCamera(50, 16 / 9, 0.1, 1000); const perspective = new THREE.PerspectiveCamera(50, 16 / 9, 0.1, 1000);
const orthographic = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 1000); const orthographic = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 1000);
// The two cameras are private, because nothing outside should choose between them by hand. // The two cameras are private, because nothing outside should choose between them by hand.
+2 -3
View File
@@ -1,4 +1,4 @@
import { Injectable, NgZone } from '@angular/core'; import { inject, Injectable, NgZone } from '@angular/core';
import * as THREE from 'three/webgpu'; import * as THREE from 'three/webgpu';
export type EngineTickCallback = (deltaSeconds: number, elapsedSeconds: number) => void; export type EngineTickCallback = (deltaSeconds: number, elapsedSeconds: number) => void;
@@ -20,6 +20,7 @@ export type SceneCamera = THREE.PerspectiveCamera | THREE.OrthographicCamera;
*/ */
@Injectable() @Injectable()
export class EngineService { export class EngineService {
private readonly ngZone = inject(NgZone);
private readonly clock = new THREE.Clock(false); private readonly clock = new THREE.Clock(false);
private readonly tickCallbacks = new Set<EngineTickCallback>(); private readonly tickCallbacks = new Set<EngineTickCallback>();
@@ -36,8 +37,6 @@ export class EngineService {
private projection: Projection = 'perspective'; private projection: Projection = 'perspective';
private running = false; private running = false;
constructor(private readonly ngZone: NgZone) {}
get isInitialized(): boolean { get isInitialized(): boolean {
return !!this.renderer; return !!this.renderer;
} }
@@ -1,4 +1,4 @@
import { AfterViewInit, Component, ElementRef, OnDestroy, signal, viewChild } from '@angular/core'; import { AfterViewInit, Component, ElementRef, inject, OnDestroy, signal, viewChild } from '@angular/core';
import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import * as THREE from 'three/webgpu'; import * as THREE from 'three/webgpu';
@@ -65,6 +65,12 @@ const GLOW_SCALE = 2.6;
` `
}) })
export class BodyDetailSceneComponent implements AfterViewInit, OnDestroy { export class BodyDetailSceneComponent implements AfterViewInit, OnDestroy {
private readonly engine = inject(EngineService);
private readonly dataLoader = inject(DataLoaderService);
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly navigationStore = inject(NavigationStore);
/** A kept place, revisited from this page: a star means leaving it for the map. */ /** A kept place, revisited from this page: a star means leaving it for the map. */
goToBookmark(bookmark: Bookmark): void { goToBookmark(bookmark: Bookmark): void {
if (bookmark.kind === 'star') { if (bookmark.kind === 'star') {
@@ -95,14 +101,6 @@ export class BodyDetailSceneComponent implements AfterViewInit, OnDestroy {
readonly viewModel = signal<BodyDetailViewModel | undefined>(undefined); readonly viewModel = signal<BodyDetailViewModel | undefined>(undefined);
readonly notFound = signal(false); readonly notFound = signal(false);
constructor(
private readonly engine: EngineService,
private readonly dataLoader: DataLoaderService,
private readonly route: ActivatedRoute,
private readonly router: Router,
private readonly navigationStore: NavigationStore
) {}
ngAfterViewInit(): void { ngAfterViewInit(): void {
void this.bootstrap(); void this.bootstrap();
} }
@@ -98,6 +98,8 @@ import { ReadoutSectionsComponent } from './readout-sections.component';
export class InfoPanelComponent { export class InfoPanelComponent {
readonly body = input.required<BodyDetailViewModel>(); readonly body = input.required<BodyDetailViewModel>();
private readonly router = inject(Router);
readonly bookmarks = inject(BookmarksStore); readonly bookmarks = inject(BookmarksStore);
private readonly articles = inject(ArticleService); private readonly articles = inject(ArticleService);
@@ -105,7 +107,7 @@ export class InfoPanelComponent {
readonly article = signal<Article | null>(null); readonly article = signal<Article | null>(null);
readonly aboutState = signal<'idle' | 'loading' | 'none' | 'unavailable'>('idle'); readonly aboutState = signal<'idle' | 'loading' | 'none' | 'unavailable'>('idle');
constructor(private readonly router: Router) { constructor() {
// The panel is reused as the route's parameter changes, so what was asked about one body // The panel is reused as the route's parameter changes, so what was asked about one body
// must not still be showing under the next one's name. // must not still be showing under the next one's name.
effect(() => { effect(() => {
@@ -1,4 +1,4 @@
import { AfterViewInit, Component, computed, effect, ElementRef, OnDestroy, signal, viewChild } from '@angular/core'; import { AfterViewInit, Component, computed, effect, ElementRef, inject, OnDestroy, signal, viewChild } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import * as THREE from 'three/webgpu'; import * as THREE from 'three/webgpu';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
@@ -323,6 +323,11 @@ function galacticOverviewPose(): { position: THREE.Vector3; target: THREE.Vector
` `
}) })
export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy { export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
private readonly engine = inject(EngineService);
private readonly dataLoader = inject(DataLoaderService);
private readonly router = inject(Router);
readonly navigationStore = inject(NavigationStore);
private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas'); private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');
private readonly labelHostRef = viewChild.required<ElementRef<HTMLDivElement>>('labelHost'); private readonly labelHostRef = viewChild.required<ElementRef<HTMLDivElement>>('labelHost');
private readonly leaderRef = viewChild.required<ElementRef<SVGLineElement>>('leader'); private readonly leaderRef = viewChild.required<ElementRef<SVGLineElement>>('leader');
@@ -467,12 +472,7 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
private starMarker?: THREE.Mesh; private starMarker?: THREE.Mesh;
private starGlow?: THREE.Sprite; private starGlow?: THREE.Sprite;
constructor( constructor() {
private readonly engine: EngineService,
private readonly dataLoader: DataLoaderService,
private readonly router: Router,
readonly navigationStore: NavigationStore
) {
effect(() => { effect(() => {
const selectedStarId = this.navigationStore.selectedStarId(); const selectedStarId = this.navigationStore.selectedStarId();
if (this.ready) { if (this.ready) {
+6 -6
View File
@@ -1,4 +1,4 @@
import { Component, computed, ElementRef, output, signal, viewChild } from '@angular/core'; import { Component, computed, ElementRef, inject, output, signal, viewChild } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { DataLoaderService } from '../../core/data/data-loader.service'; import { DataLoaderService } from '../../core/data/data-loader.service';
@@ -80,6 +80,10 @@ const KIND_LABELS: Record<SearchResultKind, string> = {
` `
}) })
export class SearchComponent { export class SearchComponent {
private readonly dataLoader = inject(DataLoaderService);
private readonly navigationStore = inject(NavigationStore);
private readonly router = inject(Router);
readonly query = signal(''); readonly query = signal('');
/** Fires once a result has been chosen and navigation kicked off — the dock uses it to hand /** Fires once a result has been chosen and navigation kicked off — the dock uses it to hand
* the view back to the readout, since the thing to look at is now the scene, not the box. */ * the view back to the readout, since the thing to look at is now the scene, not the box. */
@@ -109,11 +113,7 @@ export class SearchComponent {
readonly matchTotal = computed(() => this.matches().length); readonly matchTotal = computed(() => this.matches().length);
readonly results = computed(() => this.matches().slice(0, MAX_RESULTS)); readonly results = computed(() => this.matches().slice(0, MAX_RESULTS));
constructor( constructor() {
private readonly dataLoader: DataLoaderService,
private readonly navigationStore: NavigationStore,
private readonly router: Router
) {
void this.buildIndex(); void this.buildIndex();
} }