ui/phase-13: planet inspector — read-only
Plumbs the map → inspector pathway: a click on a planet selects it through the new SelectionStore, the sidebar Inspector tab swaps its empty-state copy for a per-kind read-only field set, and a mobile-only bottom-sheet mirrors the same content over the map. Field projection in api/game-state.ts now surfaces every documented planet field.
This commit is contained in:
@@ -11,18 +11,36 @@ layout owns:
|
||||
so navigating to any other view through the More drawer or the
|
||||
header view-menu naturally drops the overlay even if `mobileTool`
|
||||
was set on a previous tap.
|
||||
- `activeTab` — current sidebar tool (`calculator` / `inspector` /
|
||||
`order`). Held here, bound into the sidebar so a planet click on
|
||||
the map can flip it to `inspector` from the outside (Phase 13).
|
||||
- Per-game stores: `GameStateStore`, `OrderDraftStore`, and the
|
||||
Phase 13 `SelectionStore`. All three are exposed to descendants
|
||||
via Svelte context; their lifetimes match the layout instance,
|
||||
which itself stays mounted across active-view switches inside
|
||||
`/games/:id/*`.
|
||||
|
||||
Phase 11 adds the per-game `GameStateStore` instance owned by this
|
||||
Phase 11 added the per-game `GameStateStore` instance owned by this
|
||||
layout: it constructs the `GalaxyClient`, fetches the matching lobby
|
||||
record to discover `current_turn`, then loads the report. The store
|
||||
is shared with descendants via `setContext("gameState", ...)` so the
|
||||
header turn counter, the map view, and later inspector tabs all read
|
||||
header turn counter, the map view, and the inspector tab all read
|
||||
from the same snapshot.
|
||||
|
||||
Phase 13 adds the planet inspector. The layout watches the selection
|
||||
store and, on the null → planet transition, flips `activeTab` to
|
||||
`inspector` and `sidebarOpen` to `true` so the inspector becomes
|
||||
visible regardless of breakpoint (desktop already has the sidebar
|
||||
pinned; tablet needs the drawer to surface). On mobile the
|
||||
`<PlanetSheet />` overlay reads the same selection and displays a
|
||||
read-only sheet over the map; closing the sheet clears the
|
||||
selection.
|
||||
|
||||
State preservation across active-view switches works for free
|
||||
because SvelteKit keeps this layout instance mounted while children
|
||||
swap; navigating between games unmounts and remounts the layout, so
|
||||
the next game's snapshot is loaded fresh.
|
||||
the next game's snapshot — and the next game's selection — start
|
||||
fresh.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount, setContext } from "svelte";
|
||||
@@ -32,8 +50,13 @@ the next game's snapshot is loaded fresh.
|
||||
import BottomTabs from "$lib/sidebar/bottom-tabs.svelte";
|
||||
import Calculator from "$lib/sidebar/calculator-tab.svelte";
|
||||
import Order from "$lib/sidebar/order-tab.svelte";
|
||||
import type { MobileTool } from "$lib/sidebar/types";
|
||||
import PlanetSheet from "$lib/inspectors/planet-sheet.svelte";
|
||||
import type { MobileTool, SidebarTab } from "$lib/sidebar/types";
|
||||
import { GameStateStore, GAME_STATE_CONTEXT_KEY } from "$lib/game-state.svelte";
|
||||
import {
|
||||
SelectionStore,
|
||||
SELECTION_CONTEXT_KEY,
|
||||
} from "$lib/selection.svelte";
|
||||
import {
|
||||
ORDER_DRAFT_CONTEXT_KEY,
|
||||
OrderDraftStore,
|
||||
@@ -49,6 +72,7 @@ the next game's snapshot is loaded fresh.
|
||||
|
||||
let sidebarOpen = $state(false);
|
||||
let mobileTool: MobileTool = $state("map");
|
||||
let activeTab: SidebarTab = $state("inspector");
|
||||
// Phase 12 ships the prop wiring; Phase 26 replaces this constant
|
||||
// with the real history-mode signal from `lib/history-mode.ts`.
|
||||
const historyMode = false;
|
||||
@@ -63,6 +87,33 @@ the next game's snapshot is loaded fresh.
|
||||
setContext(GAME_STATE_CONTEXT_KEY, gameState);
|
||||
const orderDraft = new OrderDraftStore();
|
||||
setContext(ORDER_DRAFT_CONTEXT_KEY, orderDraft);
|
||||
const selection = new SelectionStore();
|
||||
setContext(SELECTION_CONTEXT_KEY, selection);
|
||||
|
||||
// selectedPlanet resolves the current selection against the live
|
||||
// report so both the desktop sidebar and the mobile sheet display
|
||||
// the same snapshot. A selection that points at a planet missing
|
||||
// from the current report (e.g. visibility lost between turns)
|
||||
// reads as `null` here, which collapses the inspector and the
|
||||
// sheet without surfacing a stale row.
|
||||
const selectedPlanet = $derived.by(() => {
|
||||
const sel = selection.selected;
|
||||
if (sel === null || sel.kind !== "planet") return null;
|
||||
const report = gameState.report;
|
||||
if (report === null) return null;
|
||||
return report.planets.find((p) => p.number === sel.id) ?? null;
|
||||
});
|
||||
|
||||
// Reveal the inspector whenever a new planet selection lands.
|
||||
// Reading `selection.selected` once outside the effect keeps the
|
||||
// effect dependent on the rune transition and not on the derived
|
||||
// `selectedPlanet`, which can flicker as the report refreshes.
|
||||
$effect(() => {
|
||||
const sel = selection.selected;
|
||||
if (sel === null) return;
|
||||
activeTab = "inspector";
|
||||
sidebarOpen = true;
|
||||
});
|
||||
|
||||
function toggleSidebar(): void {
|
||||
sidebarOpen = !sidebarOpen;
|
||||
@@ -107,6 +158,7 @@ the next game's snapshot is loaded fresh.
|
||||
onDestroy(() => {
|
||||
gameState.dispose();
|
||||
orderDraft.dispose();
|
||||
selection.dispose();
|
||||
});
|
||||
|
||||
function describeBootstrapError(err: unknown): string {
|
||||
@@ -135,6 +187,7 @@ the next game's snapshot is loaded fresh.
|
||||
open={sidebarOpen}
|
||||
onClose={() => (sidebarOpen = false)}
|
||||
{historyMode}
|
||||
bind:activeTab
|
||||
/>
|
||||
</div>
|
||||
<BottomTabs
|
||||
@@ -143,6 +196,11 @@ the next game's snapshot is loaded fresh.
|
||||
onSelectTool={(tool) => (mobileTool = tool)}
|
||||
hideOrder={historyMode}
|
||||
/>
|
||||
<PlanetSheet
|
||||
planet={selectedPlanet}
|
||||
onMap={effectiveTool === "map"}
|
||||
onClose={() => selection.clear()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user