Refine viewer status panel layout

This commit is contained in:
2026-03-12 20:39:13 -04:00
parent 62d1c158e0
commit 0340e1cc7d
2 changed files with 42 additions and 71 deletions

View File

@@ -1,5 +1,5 @@
import * as THREE from "three";
import { fetchWorldSnapshot, openWorldStream, resetWorld } from "./api";
import { fetchWorldSnapshot, openWorldStream } from "./api";
import type {
FactionDelta,
FactionSnapshot,
@@ -86,9 +86,7 @@ export class GameViewer {
private readonly detailBodyEl: HTMLDivElement;
private readonly factionStripEl: HTMLDivElement;
private readonly networkPanelEl: HTMLDivElement;
private readonly resetButton: HTMLButtonElement;
private readonly errorEl: HTMLDivElement;
private readonly streamEl: HTMLDivElement;
private world?: WorldState;
private stream?: EventSource;
private readonly networkStats: NetworkStats = {
@@ -126,15 +124,8 @@ export class GameViewer {
hud.className = "viewer-shell";
hud.innerHTML = `
<header class="topbar">
<div>
<p class="eyebrow">Frontend Viewer</p>
<h1>Space Game Observer</h1>
</div>
<div class="topbar-actions">
<div class="status-pill">Bootstrapping</div>
<div class="status-pill stream-pill">Stream Offline</div>
<button type="button" class="reset-button">Reset World</button>
</div>
<h2>Game</h2>
<div class="topbar-body">Bootstrapping</div>
</header>
<aside class="details-panel">
<h2>Selection</h2>
@@ -149,18 +140,15 @@ export class GameViewer {
<section class="faction-strip"></section>
`;
this.statusEl = hud.querySelector(".status-pill") as HTMLDivElement;
this.streamEl = hud.querySelector(".stream-pill") as HTMLDivElement;
this.statusEl = hud.querySelector(".topbar-body") as HTMLDivElement;
this.detailTitleEl = hud.querySelector(".detail-title") as HTMLHeadingElement;
this.detailBodyEl = hud.querySelector(".detail-body") as HTMLDivElement;
this.factionStripEl = hud.querySelector(".faction-strip") as HTMLDivElement;
this.networkPanelEl = hud.querySelector(".network-body") as HTMLDivElement;
this.resetButton = hud.querySelector(".reset-button") as HTMLButtonElement;
this.errorEl = hud.querySelector(".error-strip") as HTMLDivElement;
this.container.append(this.renderer.domElement, hud);
this.resetButton.addEventListener("click", () => void this.handleReset());
this.renderer.domElement.addEventListener("pointerdown", this.onPointerDown);
this.renderer.domElement.addEventListener("pointermove", this.onPointerMove);
this.renderer.domElement.addEventListener("pointerup", this.onPointerUp);
@@ -181,14 +169,13 @@ export class GameViewer {
const snapshot = await fetchWorldSnapshot();
this.world = this.createWorldState(snapshot);
this.networkStats.snapshotBytes = new Blob([JSON.stringify(snapshot)]).size;
this.statusEl.textContent = `Snapshot ${snapshot.sequence}`;
this.updateGamePanel("Bootstrapped");
this.errorEl.hidden = true;
this.applySnapshot(snapshot);
this.openDeltaStream(snapshot.sequence);
this.updatePanels();
} catch (error) {
this.statusEl.textContent = "Backend offline";
this.streamEl.textContent = "Stream Offline";
this.updateGamePanel("Backend offline");
this.errorEl.hidden = false;
this.errorEl.textContent = error instanceof Error ? error.message : "Unable to bootstrap the backend snapshot.";
}
@@ -200,12 +187,12 @@ export class GameViewer {
onOpen: () => {
this.networkStats.streamConnected = true;
this.networkStats.streamOpenedAtMs = performance.now();
this.streamEl.textContent = "Stream Live";
this.updateGamePanel("Stream live");
this.updateNetworkPanel();
},
onError: () => {
this.networkStats.streamConnected = false;
this.streamEl.textContent = "Stream Reconnecting";
this.updateGamePanel("Stream reconnecting");
this.updateNetworkPanel();
},
onDelta: (delta, rawBytes) => {
@@ -226,26 +213,11 @@ export class GameViewer {
this.applyDelta(delta);
this.recordDeltaStats(delta, rawBytes);
this.statusEl.textContent = `Seq ${delta.sequence} · ${new Date(delta.generatedAtUtc).toLocaleTimeString()}`;
this.updateGamePanel("Live");
this.updatePanels();
this.updateNetworkPanel();
}
private async handleReset() {
this.resetButton.disabled = true;
try {
const snapshot = await resetWorld();
this.world = this.createWorldState(snapshot);
this.networkStats.snapshotBytes = new Blob([JSON.stringify(snapshot)]).size;
this.applySnapshot(snapshot);
this.openDeltaStream(snapshot.sequence);
this.updatePanels();
this.updateNetworkPanel();
} finally {
this.resetButton.disabled = false;
}
}
private createWorldState(snapshot: WorldSnapshot): WorldState {
return {
label: snapshot.label,
@@ -701,6 +673,18 @@ export class GameViewer {
].join("\n");
}
private updateGamePanel(mode: string) {
const sequence = this.world?.sequence ?? 0;
const generatedAt = this.world?.generatedAtUtc
? new Date(this.world.generatedAtUtc).toLocaleTimeString()
: "n/a";
this.statusEl.textContent = [
`mode: ${mode}`,
`sequence: ${sequence}`,
`snapshot: ${generatedAt}`,
].join("\n");
}
private toThreeVector(vector: Vector3Dto) {
return new THREE.Vector3(vector.x, vector.y, vector.z);
}