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:
@@ -0,0 +1,197 @@
|
||||
<!--
|
||||
Phase 13 read-only planet inspector. Renders the documented field
|
||||
set for the planet kind in question:
|
||||
|
||||
- `local` / `other` carry the full economy: name, owner (other only),
|
||||
coordinates, size, population, colonists, industry, both stockpiles,
|
||||
natural resources, current production, free production potential.
|
||||
- `uninhabited` keeps name, coordinates, size, both stockpiles, and
|
||||
natural resources — the engine does not project industry or
|
||||
population for unowned planets.
|
||||
- `unidentified` is reduced to coordinates plus a no-data hint.
|
||||
|
||||
The component is purely presentational: the parent supplies a
|
||||
`ReportPlanet` snapshot resolved from `GameStateStore`, no store
|
||||
lookups happen here. Phase 14 will extend the same component with a
|
||||
`Rename` action; the read-only layout stays the structural baseline.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { ReportPlanet } from "../../api/game-state";
|
||||
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
|
||||
|
||||
type Props = {
|
||||
planet: ReportPlanet;
|
||||
};
|
||||
let { planet }: Props = $props();
|
||||
|
||||
const kindKeyMap: Record<ReportPlanet["kind"], TranslationKey> = {
|
||||
local: "game.inspector.planet.kind.local",
|
||||
other: "game.inspector.planet.kind.other",
|
||||
uninhabited: "game.inspector.planet.kind.uninhabited",
|
||||
unidentified: "game.inspector.planet.kind.unidentified",
|
||||
};
|
||||
|
||||
const kindLabel = $derived(i18n.t(kindKeyMap[planet.kind]));
|
||||
const coordinates = $derived(
|
||||
`(${formatNumber(planet.x)}, ${formatNumber(planet.y)})`,
|
||||
);
|
||||
const productionLabel = $derived(productionDisplay(planet.production));
|
||||
|
||||
function formatNumber(value: number): string {
|
||||
return value.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
function productionDisplay(value: string | null): string {
|
||||
if (value === null || value === "") {
|
||||
return i18n.t("game.inspector.planet.production_none");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<section
|
||||
class="inspector"
|
||||
data-testid="inspector-planet"
|
||||
data-planet-id={planet.number}
|
||||
data-planet-kind={planet.kind}
|
||||
>
|
||||
<header>
|
||||
<p class="kind" data-testid="inspector-planet-kind">{kindLabel}</p>
|
||||
{#if planet.kind !== "unidentified"}
|
||||
<h3 class="name" data-testid="inspector-planet-name">{planet.name}</h3>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
<dl class="fields">
|
||||
{#if planet.kind === "other" && planet.owner !== null}
|
||||
<div class="field" data-testid="inspector-planet-field-owner">
|
||||
<dt>{i18n.t("game.inspector.planet.field.owner")}</dt>
|
||||
<dd>{planet.owner}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="field" data-testid="inspector-planet-field-coordinates">
|
||||
<dt>{i18n.t("game.inspector.planet.field.coordinates")}</dt>
|
||||
<dd>{coordinates}</dd>
|
||||
</div>
|
||||
|
||||
{#if planet.size !== null}
|
||||
<div class="field" data-testid="inspector-planet-field-size">
|
||||
<dt>{i18n.t("game.inspector.planet.field.size")}</dt>
|
||||
<dd>{formatNumber(planet.size)}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if planet.resources !== null}
|
||||
<div class="field" data-testid="inspector-planet-field-natural_resources">
|
||||
<dt>{i18n.t("game.inspector.planet.field.natural_resources")}</dt>
|
||||
<dd>{formatNumber(planet.resources)}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if planet.population !== null}
|
||||
<div class="field" data-testid="inspector-planet-field-population">
|
||||
<dt>{i18n.t("game.inspector.planet.field.population")}</dt>
|
||||
<dd>{formatNumber(planet.population)}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if planet.colonists !== null}
|
||||
<div class="field" data-testid="inspector-planet-field-colonists">
|
||||
<dt>{i18n.t("game.inspector.planet.field.colonists")}</dt>
|
||||
<dd>{formatNumber(planet.colonists)}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if planet.industry !== null}
|
||||
<div class="field" data-testid="inspector-planet-field-industry">
|
||||
<dt>{i18n.t("game.inspector.planet.field.industry")}</dt>
|
||||
<dd>{formatNumber(planet.industry)}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if planet.industryStockpile !== null}
|
||||
<div class="field" data-testid="inspector-planet-field-industry_stockpile">
|
||||
<dt>{i18n.t("game.inspector.planet.field.industry_stockpile")}</dt>
|
||||
<dd>{formatNumber(planet.industryStockpile)}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if planet.materialsStockpile !== null}
|
||||
<div class="field" data-testid="inspector-planet-field-materials_stockpile">
|
||||
<dt>{i18n.t("game.inspector.planet.field.materials_stockpile")}</dt>
|
||||
<dd>{formatNumber(planet.materialsStockpile)}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if planet.production !== null}
|
||||
<div class="field" data-testid="inspector-planet-field-production">
|
||||
<dt>{i18n.t("game.inspector.planet.field.production")}</dt>
|
||||
<dd>{productionLabel}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if planet.freeIndustry !== null}
|
||||
<div class="field" data-testid="inspector-planet-field-free_industry">
|
||||
<dt>{i18n.t("game.inspector.planet.field.free_industry")}</dt>
|
||||
<dd>{formatNumber(planet.freeIndustry)}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
</dl>
|
||||
|
||||
{#if planet.kind === "unidentified"}
|
||||
<p class="hint" data-testid="inspector-planet-no-data">
|
||||
{i18n.t("game.inspector.planet.unidentified_no_data")}
|
||||
</p>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.inspector {
|
||||
padding: 1rem;
|
||||
font-family: system-ui, sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
.kind {
|
||||
margin: 0;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #aab;
|
||||
}
|
||||
.name {
|
||||
margin: 0;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.fields {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
row-gap: 0.25rem;
|
||||
column-gap: 0.75rem;
|
||||
}
|
||||
.field {
|
||||
display: contents;
|
||||
}
|
||||
.field dt {
|
||||
color: #aab;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.field dd {
|
||||
margin: 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.hint {
|
||||
margin: 0;
|
||||
color: #888;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user