diff --git a/src/app/features/galaxy-system/galaxy-system-scene.component.ts b/src/app/features/galaxy-system/galaxy-system-scene.component.ts index 47d85fb..4b45aae 100644 --- a/src/app/features/galaxy-system/galaxy-system-scene.component.ts +++ b/src/app/features/galaxy-system/galaxy-system-scene.component.ts @@ -1,4 +1,13 @@ -import { AfterViewInit, Component, computed, effect, ElementRef, OnDestroy, signal, viewChild } from '@angular/core'; +import { + AfterViewInit, + Component, + computed, + effect, + ElementRef, + OnDestroy, + signal, + viewChild, +} from '@angular/core'; import { Router } from '@angular/router'; import * as THREE from 'three/webgpu'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; @@ -8,7 +17,7 @@ import { MILKY_WAY_ARMS, SUN_GALACTOCENTRIC_RADIUS_PC, galacticCentrePositionPc, - galacticToEquatorial + galacticToEquatorial, } from '../../shared/astro/galaxy'; import { DataLoaderService } from '../../core/data/data-loader.service'; import { EngineService, SceneCamera } from '../../core/engine/engine.service'; @@ -16,7 +25,11 @@ import { BodyRecord } from '../../shared/models/body.model'; import { DeepSkyRecord } from '../../shared/models/deepsky.model'; import { ExoplanetRecord } from '../../shared/models/exoplanet.model'; import { applyMilkyWaySkybox } from '../../shared/rendering/skybox'; -import { loadCachedTexture, MILKY_WAY_SKYBOX_PATH, SUN_TEXTURE_PATH } from '../../shared/rendering/texture-catalog'; +import { + loadCachedTexture, + MILKY_WAY_SKYBOX_PATH, + SUN_TEXTURE_PATH, +} from '../../shared/rendering/texture-catalog'; import { isDesignation } from '../../shared/models/star-catalog'; import { StarRecord } from '../../shared/models/star.model'; import { Bookmark } from '../../shared/state/bookmarks.store'; @@ -26,18 +39,41 @@ import { CameraRigController } from './camera-rig-controller'; import { DeepSkyRenderer } from './deep-sky-renderer'; import { galacticNormal, PolarGridPlane, TetherField } from './grid-plane'; import { MilkyWayRenderer } from './milky-way-renderer'; -import { starMarkerRadiusAu, SUN_RADIUS_AU, systemFrameRadiusAu, systemFramingDistanceAu, systemViewDirection } from './system-framing'; +import { + starMarkerRadiusAu, + SUN_RADIUS_AU, + systemFrameRadiusAu, + systemFramingDistanceAu, + systemViewDirection, +} from './system-framing'; import { formatAu, formatLuminosity, formatParsecs } from '../../shared/format/quantity'; -import { distanceRings, formatRoundLength, scaleBar, type LengthUnit, type ScaleBar } from '../../shared/format/scale-bar'; +import { + distanceRings, + formatRoundLength, + scaleBar, + type LengthUnit, + type ScaleBar, +} from '../../shared/format/scale-bar'; import { BodyDetailViewModel } from '../body-detail/body-detail.model'; import { buildBodyViewModel, luminosityOf } from '../body-detail/body-view-model'; -import { DEFAULT_HUD_DISPLAY, HudDisplay, HudDockComponent, HudReadout } from '../hud/hud-dock.component'; +import { + DEFAULT_HUD_DISPLAY, + HudDisplay, + HudDockComponent, + HudReadout, +} from '../hud/hud-dock.component'; import { RouteRequest, RouteResult, RouteStarOption } from '../hud/routes-panel.component'; import { buildSearchIndex, IndexedSearchEntry, rankSearchResults } from '../search/search-ranking'; import { StarmapHudComponent } from './starmap-hud.component'; import { SystemObjectCardComponent } from './system-object-card.component'; import { RoutingClient } from './routing-client'; -import { colorIndexToRgb, FOCUS_RADIUS_PC, StarFieldRenderer, starRenderBudgetFromUrl, VIEW_MARGIN } from './star-field-renderer'; +import { + colorIndexToRgb, + FOCUS_RADIUS_PC, + StarFieldRenderer, + starRenderBudgetFromUrl, + VIEW_MARGIN, +} from './star-field-renderer'; import { BrightnessIndex, brightestWithin, brightnessIndex } from '../../shared/astro/brightest'; import { LinkBudget } from '../../shared/astro/jump-links'; import { StarNeighbourhood } from '../../shared/astro/star-neighbourhood'; @@ -99,8 +135,14 @@ function servesTheSame(asked: LinkBudget | undefined, now: LinkBudget | undefine if (!asked || !now) { return asked === now; } - const moved = Math.hypot(now.centre.x - asked.centre.x, now.centre.y - asked.centre.y, now.centre.z - asked.centre.z); - return Math.abs(now.lengthPc / asked.lengthPc - 1) <= VIEW_MARGIN / 2 && moved <= STAR_FIELD_REFOCUS_PC; + const moved = Math.hypot( + now.centre.x - asked.centre.x, + now.centre.y - asked.centre.y, + now.centre.z - asked.centre.z, + ); + return ( + Math.abs(now.lengthPc / asked.lengthPc - 1) <= VIEW_MARGIN / 2 && moved <= STAR_FIELD_REFOCUS_PC + ); } /** @@ -263,7 +305,10 @@ function galacticOverviewPose(): { position: THREE.Vector3; target: THREE.Vector const centre = galacticCentrePositionPc(); const target = new THREE.Vector3(centre.x, centre.y, centre.z); const awayFromCentre = target.clone().negate().normalize(); - const position = target.clone().add(galacticNormal().multiplyScalar(GALACTIC_OVERVIEW_HEIGHT_PC)).add(awayFromCentre.multiplyScalar(GALACTIC_OVERVIEW_BACK_PC)); + const position = target + .clone() + .add(galacticNormal().multiplyScalar(GALACTIC_OVERVIEW_HEIGHT_PC)) + .add(awayFromCentre.multiplyScalar(GALACTIC_OVERVIEW_BACK_PC)); return { position, target }; } @@ -290,12 +335,33 @@ function galacticOverviewPose(): { position: THREE.Vector3; target: THREE.Vector
-

{{ eyebrow() }}

-

{{ title() }}

+

+ {{ title() }} +

@if (keepableStarId() !== null) { + type="button" + [attr.aria-label]=" + (bookmarks.has('star', keepableStarId()!) ? 'Forget ' : 'Keep ') + title() + " + [attr.aria-pressed]="bookmarks.has('star', keepableStarId()!)" + (click)=" + bookmarks.toggle({ kind: 'star', id: keepableStarId()!, name: title() }) + " + class="shrink-0 p-1 transition-colors focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-accent" + [class]=" + bookmarks.has('star', keepableStarId()!) + ? 'text-accent' + : 'text-muted hover:text-accent' + " + > + + }
@if (subtitle()) { @@ -118,30 +182,54 @@ function isWideViewport(): boolean {
@for (readout of readouts(); track readout.label) {
-
{{ readout.label }}@if (readout.derived) {}
+
+ {{ readout.label }} + @if (readout.derived) { + + } +
{{ readout.value }}
}
} @if (note() || hasDerived()) { -

@if (hasDerived()) {* Derived, not catalogued. }{{ note() }}

+

+ @if (hasDerived()) { + * Derived, not catalogued. + } + {{ note() }} +

} } @case ('bookmarks') { -
+
@if (bookmarks.bookmarks().length) { } @else {

- Nothing kept yet. The on a readout or a body keeps it here, in this browser. + Nothing kept yet. The + on a + readout or a body keeps it here, in this browser.

}
} @case ('display') { -
+

Layers

@for (layer of layers; track layer.key) { @@ -173,10 +276,18 @@ function isWideViewport(): boolean { [attr.aria-pressed]="isOn(layer.key)" (click)="toggleLayer(layer.key)" class="type-label flex items-center gap-2 border px-3 py-1.5 transition-colors focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-accent" - [class]="isOn(layer.key) ? 'border-accent/60 bg-accent/12 text-accent hover:bg-accent/18' : 'border-border/60 text-muted hover:border-border hover:text-text'" + [class]=" + isOn(layer.key) + ? 'border-accent/60 bg-accent/12 text-accent hover:bg-accent/18' + : 'border-border/60 text-muted hover:border-border hover:text-text' + " > - + {{ layer.label }} } @@ -187,11 +298,19 @@ function isWideViewport(): boolean {

Clock

-
+
@for (rate of timeRates; track rate.secondsPerSecond) {
- ` + `, }) export class HudDockComponent implements OnInit { /** Readout panel contents. An empty title means there is nothing to read out, and no tab for it. */ @@ -315,7 +450,7 @@ export class HudDockComponent implements OnInit { // Always offered, even with nothing in it: it is the only place that says the map can keep // anything at all, and a tab that appears once you already know is a tab that never taught. 'bookmarks', - ...(this.display() ? (['display'] as const) : []) + ...(this.display() ? (['display'] as const) : []), ]); readonly activeTab = signal(null); @@ -377,7 +512,10 @@ export class HudDockComponent implements OnInit { return; } const target = event.target as HTMLElement | null; - if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) { + if ( + target && + (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) + ) { return; } event.preventDefault(); @@ -389,7 +527,11 @@ export class HudDockComponent implements OnInit { /** On a narrow viewport the panel is a sheet over the scene: tapping the scene folds it away. */ @HostListener('document:pointerdown', ['$event']) onDocumentPointerDown(event: PointerEvent): void { - if (this.activeTab() && !isWideViewport() && !this.host.nativeElement.contains(event.target as Node)) { + if ( + this.activeTab() && + !isWideViewport() && + !this.host.nativeElement.contains(event.target as Node) + ) { this.activeTab.set(null); } } diff --git a/src/app/shared/state/time.store.ts b/src/app/shared/state/time.store.ts index d911b2a..3be5656 100644 --- a/src/app/shared/state/time.store.ts +++ b/src/app/shared/state/time.store.ts @@ -19,7 +19,7 @@ export const TIME_RATES = [ { label: 'Real time', secondsPerSecond: 1 }, { label: '1 h/s', secondsPerSecond: 3600 }, { label: '1 d/s', secondsPerSecond: 86_400 }, - { label: '1 mo/s', secondsPerSecond: 2_629_800 } + { label: '1 mo/s', secondsPerSecond: 2_629_800 }, ] as const; const MS_PER_DAY = 86_400_000;