fix(ui): F8-08 unified number format — mono, fixed 3-decimal, no separators #65
@@ -43,6 +43,7 @@ fractions is a Phase 21 decision documented in
|
|||||||
validateScience,
|
validateScience,
|
||||||
type ScienceInvalidReason,
|
type ScienceInvalidReason,
|
||||||
} from "$lib/util/science-validation";
|
} from "$lib/util/science-validation";
|
||||||
|
import { formatPercent } from "$lib/util/number-format";
|
||||||
|
|
||||||
const rendered = getContext<RenderedReportSource | undefined>(
|
const rendered = getContext<RenderedReportSource | undefined>(
|
||||||
RENDERED_REPORT_CONTEXT_KEY,
|
RENDERED_REPORT_CONTEXT_KEY,
|
||||||
@@ -106,12 +107,7 @@ fractions is a Phase 21 decision documented in
|
|||||||
const canSave = $derived(validation.ok && draft !== undefined);
|
const canSave = $derived(validation.ok && draft !== undefined);
|
||||||
|
|
||||||
const sumPercent = $derived(drive + weapons + shields + cargo);
|
const sumPercent = $derived(drive + weapons + shields + cargo);
|
||||||
const sumDisplay = $derived(
|
const sumDisplay = $derived(sumPercent.toFixed(1));
|
||||||
sumPercent.toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
maximumFractionDigits: 1,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!isViewMode) {
|
if (!isViewMode) {
|
||||||
@@ -119,13 +115,6 @@ fractions is a Phase 21 decision documented in
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function formatPercent(fraction: number): string {
|
|
||||||
return (fraction * 100).toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
maximumFractionDigits: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function backToTable(): void {
|
function backToTable(): void {
|
||||||
activeView.select("table", { tableEntity: "sciences" });
|
activeView.select("table", { tableEntity: "sciences" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +1,44 @@
|
|||||||
// Shared number / planet formatters for the Phase 23 Report View
|
// Number formatters and lookup helpers reused across the report-section
|
||||||
// sections. Inlined in 10+ components, so factoring keeps each
|
// components. The numeric formatters delegate to the project-wide
|
||||||
// section component focused on its data shape. The formatters
|
// `lib/util/number-format` so inspector, report tables, and the
|
||||||
// match the conventions of the per-entity tables (tabular numerals,
|
// calculator panel all render numbers identically.
|
||||||
// one-decimal percent without a `%` suffix — the header carries the
|
|
||||||
// unit) so the report's grids read the same way as the
|
|
||||||
// table-races / table-sciences views.
|
|
||||||
|
|
||||||
import type { ReportPlanet } from "../../../api/game-state";
|
import type { ReportPlanet } from "../../../api/game-state";
|
||||||
|
import {
|
||||||
|
formatFloat as formatFloatBase,
|
||||||
|
formatInt,
|
||||||
|
formatPercent as formatPercentBase,
|
||||||
|
} from "$lib/util/number-format";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* formatPercent renders a `[0, 1]` fraction as a one-decimal
|
* formatPercent renders a `[0, 1]` fraction as a one-decimal percent
|
||||||
* percent (without a `%` suffix — the column header carries the
|
* (without a `%` suffix — the column header carries the unit).
|
||||||
* unit). Matches the convention used by `table-races.svelte` and
|
* Re-exported from the shared util for backwards-compatible imports
|
||||||
* `table-sciences.svelte`.
|
* across the report sections.
|
||||||
*/
|
*/
|
||||||
export function formatPercent(fraction: number): string {
|
export const formatPercent = formatPercentBase;
|
||||||
return (fraction * 100).toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
maximumFractionDigits: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* formatCount renders an integer-ish value (population, industry,
|
* formatCount renders an integer-ish value (population, industry,
|
||||||
* planet count, …) without fractional digits and with locale-aware
|
* planet count, …) with zero fractional digits and no thousand
|
||||||
* thousand separators.
|
* separators. Alias of the shared `formatInt`.
|
||||||
*/
|
*/
|
||||||
export function formatCount(value: number): string {
|
export const formatCount = formatInt;
|
||||||
return value.toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
maximumFractionDigits: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* formatFloat renders a floating-point value with up to two
|
* formatFloat renders an engine `Float` (Fixed3-quantised) with three
|
||||||
* fractional digits. Used for stockpiles, distances, cost, mass —
|
* fractional digits and no thousand separators. Used for stockpiles,
|
||||||
* everything the engine emits as a `Float` that is not a fraction.
|
* distances, cost, mass, tech levels — every report payload that is
|
||||||
|
* neither an integer count nor a `[0, 1]` fraction.
|
||||||
*/
|
*/
|
||||||
export function formatFloat(value: number): string {
|
export const formatFloat = formatFloatBase;
|
||||||
return value.toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* formatVotes renders a vote weight with up to two decimal digits —
|
* formatVotes renders a vote weight. Votes travel as the same `Float`
|
||||||
* mirrors the races table's column convention so the cumulative
|
* shape as every other float field, so this is a semantic alias of
|
||||||
* vote totals line up across views.
|
* `formatFloat` kept for readability at the call site.
|
||||||
*/
|
*/
|
||||||
export function formatVotes(value: number): string {
|
export const formatVotes = formatFloatBase;
|
||||||
return value.toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* planetLabel renders a planet reference as `#<number> (<name>)` if
|
* planetLabel renders a planet reference as `#<number> (<name>)` if
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ class when the group lands and a battle roster forms.
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{i18n.t("game.report.section.approaching_groups.column.from")}</th>
|
<th>{i18n.t("game.report.section.approaching_groups.column.from")}</th>
|
||||||
<th>{i18n.t("game.report.section.approaching_groups.column.to")}</th>
|
<th>{i18n.t("game.report.section.approaching_groups.column.to")}</th>
|
||||||
<th>
|
<th class="numeric">
|
||||||
{i18n.t("game.report.section.approaching_groups.column.distance")}
|
{i18n.t("game.report.section.approaching_groups.column.distance")}
|
||||||
</th>
|
</th>
|
||||||
<th>{i18n.t("game.report.section.approaching_groups.column.speed")}</th>
|
<th class="numeric">{i18n.t("game.report.section.approaching_groups.column.speed")}</th>
|
||||||
<th>{i18n.t("game.report.section.approaching_groups.column.mass")}</th>
|
<th class="numeric">{i18n.t("game.report.section.approaching_groups.column.mass")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -54,9 +54,9 @@ class when the group lands and a battle roster forms.
|
|||||||
<tr data-testid="approaching-groups-row">
|
<tr data-testid="approaching-groups-row">
|
||||||
<td>{planetLabel(r.origin, planets)}</td>
|
<td>{planetLabel(r.origin, planets)}</td>
|
||||||
<td>{planetLabel(r.destination, planets)}</td>
|
<td>{planetLabel(r.destination, planets)}</td>
|
||||||
<td>{formatFloat(r.distance)}</td>
|
<td class="numeric">{formatFloat(r.distance)}</td>
|
||||||
<td>{formatFloat(r.speed)}</td>
|
<td class="numeric">{formatFloat(r.speed)}</td>
|
||||||
<td>{formatFloat(r.mass)}</td>
|
<td class="numeric">{formatFloat(r.mass)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -79,7 +79,7 @@ class when the group lands and a battle roster forms.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -93,6 +93,11 @@ class when the group lands and a battle roster forms.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,16 +42,16 @@ Decoder sorts by `planetNumber` already.
|
|||||||
<th>{i18n.t("game.report.section.bombings.column.owner")}</th>
|
<th>{i18n.t("game.report.section.bombings.column.owner")}</th>
|
||||||
<th>{i18n.t("game.report.section.bombings.column.attacker")}</th>
|
<th>{i18n.t("game.report.section.bombings.column.attacker")}</th>
|
||||||
<th>{i18n.t("game.report.section.bombings.column.production")}</th>
|
<th>{i18n.t("game.report.section.bombings.column.production")}</th>
|
||||||
<th>{i18n.t("game.report.section.bombings.column.industry")}</th>
|
<th class="numeric">{i18n.t("game.report.section.bombings.column.industry")}</th>
|
||||||
<th>{i18n.t("game.report.section.bombings.column.population")}</th>
|
<th class="numeric">{i18n.t("game.report.section.bombings.column.population")}</th>
|
||||||
<th>{i18n.t("game.report.section.bombings.column.colonists")}</th>
|
<th class="numeric">{i18n.t("game.report.section.bombings.column.colonists")}</th>
|
||||||
<th>
|
<th class="numeric">
|
||||||
{i18n.t("game.report.section.bombings.column.industry_stockpile")}
|
{i18n.t("game.report.section.bombings.column.industry_stockpile")}
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th class="numeric">
|
||||||
{i18n.t("game.report.section.bombings.column.materials_stockpile")}
|
{i18n.t("game.report.section.bombings.column.materials_stockpile")}
|
||||||
</th>
|
</th>
|
||||||
<th>{i18n.t("game.report.section.bombings.column.attack_power")}</th>
|
<th class="numeric">{i18n.t("game.report.section.bombings.column.attack_power")}</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -67,12 +67,12 @@ Decoder sorts by `planetNumber` already.
|
|||||||
<td>{b.owner}</td>
|
<td>{b.owner}</td>
|
||||||
<td>{b.attacker}</td>
|
<td>{b.attacker}</td>
|
||||||
<td>{b.production}</td>
|
<td>{b.production}</td>
|
||||||
<td>{formatFloat(b.industry)}</td>
|
<td class="numeric">{formatFloat(b.industry)}</td>
|
||||||
<td>{formatFloat(b.population)}</td>
|
<td class="numeric">{formatFloat(b.population)}</td>
|
||||||
<td>{formatFloat(b.colonists)}</td>
|
<td class="numeric">{formatFloat(b.colonists)}</td>
|
||||||
<td>{formatFloat(b.industryStockpile)}</td>
|
<td class="numeric">{formatFloat(b.industryStockpile)}</td>
|
||||||
<td>{formatFloat(b.materialsStockpile)}</td>
|
<td class="numeric">{formatFloat(b.materialsStockpile)}</td>
|
||||||
<td>{formatCount(b.attackPower)}</td>
|
<td class="numeric">{formatCount(b.attackPower)}</td>
|
||||||
<td>
|
<td>
|
||||||
{#if b.wiped}
|
{#if b.wiped}
|
||||||
<span
|
<span
|
||||||
@@ -105,7 +105,7 @@ Decoder sorts by `planetNumber` already.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -119,6 +119,11 @@ Decoder sorts by `planetNumber` already.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ has many routes.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
|
|||||||
@@ -42,20 +42,20 @@ as the local planets table plus an `owner` column.
|
|||||||
<th>{i18n.t("game.report.section.my_planets.column.number")}</th>
|
<th>{i18n.t("game.report.section.my_planets.column.number")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.name")}</th>
|
<th>{i18n.t("game.report.section.my_planets.column.name")}</th>
|
||||||
<th>{i18n.t("game.report.section.foreign_planets.column.owner")}</th>
|
<th>{i18n.t("game.report.section.foreign_planets.column.owner")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.size")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.size")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.resources")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.resources")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.population")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.population")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.industry")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.industry")}</th>
|
||||||
<th>
|
<th class="numeric">
|
||||||
{i18n.t("game.report.section.my_planets.column.industry_stockpile")}
|
{i18n.t("game.report.section.my_planets.column.industry_stockpile")}
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th class="numeric">
|
||||||
{i18n.t("game.report.section.my_planets.column.materials_stockpile")}
|
{i18n.t("game.report.section.my_planets.column.materials_stockpile")}
|
||||||
</th>
|
</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.colonists")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.colonists")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.production")}</th>
|
<th>{i18n.t("game.report.section.my_planets.column.production")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.free_industry")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.free_industry")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -64,16 +64,16 @@ as the local planets table plus an `owner` column.
|
|||||||
<td>{p.number}</td>
|
<td>{p.number}</td>
|
||||||
<td>{p.name}</td>
|
<td>{p.name}</td>
|
||||||
<td>{p.owner ?? ""}</td>
|
<td>{p.owner ?? ""}</td>
|
||||||
<td>{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
<td class="numeric">{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
||||||
<td>{formatFloat(p.size ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.size ?? 0)}</td>
|
||||||
<td>{formatFloat(p.resources ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.resources ?? 0)}</td>
|
||||||
<td>{formatFloat(p.population ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.population ?? 0)}</td>
|
||||||
<td>{formatFloat(p.industry ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.industry ?? 0)}</td>
|
||||||
<td>{formatFloat(p.industryStockpile ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.industryStockpile ?? 0)}</td>
|
||||||
<td>{formatFloat(p.materialsStockpile ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.materialsStockpile ?? 0)}</td>
|
||||||
<td>{formatFloat(p.colonists ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.colonists ?? 0)}</td>
|
||||||
<td>{p.production ?? "—"}</td>
|
<td>{p.production ?? "—"}</td>
|
||||||
<td>{formatFloat(p.freeIndustry ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.freeIndustry ?? 0)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -96,7 +96,7 @@ as the local planets table plus an `owner` column.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -110,6 +110,11 @@ as the local planets table plus an `owner` column.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,10 +67,10 @@ unit even when the section spans many races.
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{i18n.t("game.report.section.my_sciences.column.name")}</th>
|
<th>{i18n.t("game.report.section.my_sciences.column.name")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_sciences.column.drive")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.drive")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_sciences.column.weapons")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.weapons")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_sciences.column.shields")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.shields")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_sciences.column.cargo")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.cargo")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -81,10 +81,10 @@ unit even when the section spans many races.
|
|||||||
data-name={r.name}
|
data-name={r.name}
|
||||||
>
|
>
|
||||||
<td>{r.name}</td>
|
<td>{r.name}</td>
|
||||||
<td>{formatPercent(r.drive)}</td>
|
<td class="numeric">{formatPercent(r.drive)}</td>
|
||||||
<td>{formatPercent(r.weapons)}</td>
|
<td class="numeric">{formatPercent(r.weapons)}</td>
|
||||||
<td>{formatPercent(r.shields)}</td>
|
<td class="numeric">{formatPercent(r.shields)}</td>
|
||||||
<td>{formatPercent(r.cargo)}</td>
|
<td class="numeric">{formatPercent(r.cargo)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -115,7 +115,7 @@ unit even when the section spans many races.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -129,6 +129,11 @@ unit even when the section spans many races.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,12 +65,12 @@ incoming groups.
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{i18n.t("game.report.section.my_ship_classes.column.name")}</th>
|
<th>{i18n.t("game.report.section.my_ship_classes.column.name")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_classes.column.drive")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.drive")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_classes.column.armament")}</th>
|
<th>{i18n.t("game.report.section.my_ship_classes.column.armament")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_classes.column.weapons")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.weapons")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_classes.column.shields")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.shields")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_classes.column.cargo")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.cargo")}</th>
|
||||||
<th>{i18n.t("game.report.section.foreign_ship_classes.column.mass")}</th>
|
<th class="numeric">{i18n.t("game.report.section.foreign_ship_classes.column.mass")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -81,12 +81,12 @@ incoming groups.
|
|||||||
data-name={r.name}
|
data-name={r.name}
|
||||||
>
|
>
|
||||||
<td>{r.name}</td>
|
<td>{r.name}</td>
|
||||||
<td>{formatFloat(r.drive)}</td>
|
<td class="numeric">{formatFloat(r.drive)}</td>
|
||||||
<td>{r.armament}</td>
|
<td>{r.armament}</td>
|
||||||
<td>{formatFloat(r.weapons)}</td>
|
<td class="numeric">{formatFloat(r.weapons)}</td>
|
||||||
<td>{formatFloat(r.shields)}</td>
|
<td class="numeric">{formatFloat(r.shields)}</td>
|
||||||
<td>{formatFloat(r.cargo)}</td>
|
<td class="numeric">{formatFloat(r.cargo)}</td>
|
||||||
<td>{formatFloat(r.mass)}</td>
|
<td class="numeric">{formatFloat(r.mass)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -117,7 +117,7 @@ incoming groups.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -131,6 +131,11 @@ incoming groups.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ to groups the player doesn't own.
|
|||||||
<th>{i18n.t("game.report.section.my_ship_groups.column.destination")}</th>
|
<th>{i18n.t("game.report.section.my_ship_groups.column.destination")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_groups.column.origin")}</th>
|
<th>{i18n.t("game.report.section.my_ship_groups.column.origin")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_groups.column.range")}</th>
|
<th>{i18n.t("game.report.section.my_ship_groups.column.range")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_groups.column.speed")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_ship_groups.column.speed")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_groups.column.mass")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_ship_groups.column.mass")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -64,8 +64,8 @@ to groups the player doesn't own.
|
|||||||
{g.origin === null ? "—" : planetLabel(g.origin, planets)}
|
{g.origin === null ? "—" : planetLabel(g.origin, planets)}
|
||||||
</td>
|
</td>
|
||||||
<td>{g.range === null ? "—" : formatFloat(g.range)}</td>
|
<td>{g.range === null ? "—" : formatFloat(g.range)}</td>
|
||||||
<td>{formatFloat(g.speed)}</td>
|
<td class="numeric">{formatFloat(g.speed)}</td>
|
||||||
<td>{formatFloat(g.mass)}</td>
|
<td class="numeric">{formatFloat(g.mass)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -88,7 +88,7 @@ to groups the player doesn't own.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -102,6 +102,11 @@ to groups the player doesn't own.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,13 +31,13 @@ section is never empty as long as the report has loaded.
|
|||||||
{:else}
|
{:else}
|
||||||
<dl class="kv">
|
<dl class="kv">
|
||||||
<dt>{i18n.t("game.report.section.galaxy_summary.field.turn")}</dt>
|
<dt>{i18n.t("game.report.section.galaxy_summary.field.turn")}</dt>
|
||||||
<dd data-testid="galaxy-summary-field-turn">{report.turn}</dd>
|
<dd class="numeric" data-testid="galaxy-summary-field-turn">{report.turn}</dd>
|
||||||
<dt>{i18n.t("game.report.section.galaxy_summary.field.size")}</dt>
|
<dt>{i18n.t("game.report.section.galaxy_summary.field.size")}</dt>
|
||||||
<dd data-testid="galaxy-summary-field-size">
|
<dd class="numeric" data-testid="galaxy-summary-field-size">
|
||||||
{report.mapWidth} × {report.mapHeight}
|
{report.mapWidth} × {report.mapHeight}
|
||||||
</dd>
|
</dd>
|
||||||
<dt>{i18n.t("game.report.section.galaxy_summary.field.planets")}</dt>
|
<dt>{i18n.t("game.report.section.galaxy_summary.field.planets")}</dt>
|
||||||
<dd data-testid="galaxy-summary-field-planets">{report.planetCount}</dd>
|
<dd class="numeric" data-testid="galaxy-summary-field-planets">{report.planetCount}</dd>
|
||||||
<dt>{i18n.t("game.report.section.galaxy_summary.field.race")}</dt>
|
<dt>{i18n.t("game.report.section.galaxy_summary.field.race")}</dt>
|
||||||
<dd data-testid="galaxy-summary-field-race">{report.race}</dd>
|
<dd data-testid="galaxy-summary-field-race">{report.race}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
@@ -60,7 +60,7 @@ section is never empty as long as the report has loaded.
|
|||||||
grid-template-columns: max-content 1fr;
|
grid-template-columns: max-content 1fr;
|
||||||
gap: 0.3rem 1rem;
|
gap: 0.3rem 1rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.kv dt {
|
.kv dt {
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
@@ -73,4 +73,7 @@ section is never empty as long as the report has loaded.
|
|||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
.kv dd.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ in orbit has neither); empty cells in those columns are normal.
|
|||||||
<th>{i18n.t("game.report.section.my_fleets.column.destination")}</th>
|
<th>{i18n.t("game.report.section.my_fleets.column.destination")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_fleets.column.origin")}</th>
|
<th>{i18n.t("game.report.section.my_fleets.column.origin")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_fleets.column.range")}</th>
|
<th>{i18n.t("game.report.section.my_fleets.column.range")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_fleets.column.speed")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_fleets.column.speed")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -58,7 +58,7 @@ in orbit has neither); empty cells in those columns are normal.
|
|||||||
{f.origin === null ? "—" : planetLabel(f.origin, planets)}
|
{f.origin === null ? "—" : planetLabel(f.origin, planets)}
|
||||||
</td>
|
</td>
|
||||||
<td>{f.range === null ? "—" : formatFloat(f.range)}</td>
|
<td>{f.range === null ? "—" : formatFloat(f.range)}</td>
|
||||||
<td>{formatFloat(f.speed)}</td>
|
<td class="numeric">{formatFloat(f.speed)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -81,7 +81,7 @@ in orbit has neither); empty cells in those columns are normal.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -95,6 +95,11 @@ in orbit has neither); empty cells in those columns are normal.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,20 +41,20 @@ column set (matches `ReportPlanet` shape).
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.number")}</th>
|
<th>{i18n.t("game.report.section.my_planets.column.number")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.name")}</th>
|
<th>{i18n.t("game.report.section.my_planets.column.name")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.size")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.size")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.resources")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.resources")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.population")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.population")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.industry")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.industry")}</th>
|
||||||
<th>
|
<th class="numeric">
|
||||||
{i18n.t("game.report.section.my_planets.column.industry_stockpile")}
|
{i18n.t("game.report.section.my_planets.column.industry_stockpile")}
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th class="numeric">
|
||||||
{i18n.t("game.report.section.my_planets.column.materials_stockpile")}
|
{i18n.t("game.report.section.my_planets.column.materials_stockpile")}
|
||||||
</th>
|
</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.colonists")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.colonists")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.production")}</th>
|
<th>{i18n.t("game.report.section.my_planets.column.production")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.free_industry")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.free_industry")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -62,16 +62,16 @@ column set (matches `ReportPlanet` shape).
|
|||||||
<tr data-testid="my-planets-row" data-number={p.number}>
|
<tr data-testid="my-planets-row" data-number={p.number}>
|
||||||
<td>{p.number}</td>
|
<td>{p.number}</td>
|
||||||
<td>{p.name}</td>
|
<td>{p.name}</td>
|
||||||
<td>{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
<td class="numeric">{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
||||||
<td>{formatFloat(p.size ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.size ?? 0)}</td>
|
||||||
<td>{formatFloat(p.resources ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.resources ?? 0)}</td>
|
||||||
<td>{formatFloat(p.population ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.population ?? 0)}</td>
|
||||||
<td>{formatFloat(p.industry ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.industry ?? 0)}</td>
|
||||||
<td>{formatFloat(p.industryStockpile ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.industryStockpile ?? 0)}</td>
|
||||||
<td>{formatFloat(p.materialsStockpile ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.materialsStockpile ?? 0)}</td>
|
||||||
<td>{formatFloat(p.colonists ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.colonists ?? 0)}</td>
|
||||||
<td>{p.production ?? "—"}</td>
|
<td>{p.production ?? "—"}</td>
|
||||||
<td>{formatFloat(p.freeIndustry ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.freeIndustry ?? 0)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -94,7 +94,7 @@ column set (matches `ReportPlanet` shape).
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -108,6 +108,11 @@ column set (matches `ReportPlanet` shape).
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,20 +39,20 @@ table).
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{i18n.t("game.report.section.my_sciences.column.name")}</th>
|
<th>{i18n.t("game.report.section.my_sciences.column.name")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_sciences.column.drive")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.drive")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_sciences.column.weapons")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.weapons")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_sciences.column.shields")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.shields")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_sciences.column.cargo")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.cargo")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each rows as r (r.name)}
|
{#each rows as r (r.name)}
|
||||||
<tr data-testid="my-sciences-row" data-name={r.name}>
|
<tr data-testid="my-sciences-row" data-name={r.name}>
|
||||||
<td>{r.name}</td>
|
<td>{r.name}</td>
|
||||||
<td>{formatPercent(r.drive)}</td>
|
<td class="numeric">{formatPercent(r.drive)}</td>
|
||||||
<td>{formatPercent(r.weapons)}</td>
|
<td class="numeric">{formatPercent(r.weapons)}</td>
|
||||||
<td>{formatPercent(r.shields)}</td>
|
<td class="numeric">{formatPercent(r.shields)}</td>
|
||||||
<td>{formatPercent(r.cargo)}</td>
|
<td class="numeric">{formatPercent(r.cargo)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -75,7 +75,7 @@ table).
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -89,6 +89,11 @@ table).
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,22 +40,22 @@ drafts immediately, matching the ship-class designer's behaviour.
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{i18n.t("game.report.section.my_ship_classes.column.name")}</th>
|
<th>{i18n.t("game.report.section.my_ship_classes.column.name")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_classes.column.drive")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.drive")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_classes.column.armament")}</th>
|
<th>{i18n.t("game.report.section.my_ship_classes.column.armament")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_classes.column.weapons")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.weapons")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_classes.column.shields")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.shields")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_classes.column.cargo")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.cargo")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each rows as r (r.name)}
|
{#each rows as r (r.name)}
|
||||||
<tr data-testid="my-ship-classes-row" data-name={r.name}>
|
<tr data-testid="my-ship-classes-row" data-name={r.name}>
|
||||||
<td>{r.name}</td>
|
<td>{r.name}</td>
|
||||||
<td>{formatFloat(r.drive)}</td>
|
<td class="numeric">{formatFloat(r.drive)}</td>
|
||||||
<td>{r.armament}</td>
|
<td>{r.armament}</td>
|
||||||
<td>{formatFloat(r.weapons)}</td>
|
<td class="numeric">{formatFloat(r.weapons)}</td>
|
||||||
<td>{formatFloat(r.shields)}</td>
|
<td class="numeric">{formatFloat(r.shields)}</td>
|
||||||
<td>{formatFloat(r.cargo)}</td>
|
<td class="numeric">{formatFloat(r.cargo)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -78,7 +78,7 @@ drafts immediately, matching the ship-class designer's behaviour.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -92,6 +92,11 @@ drafts immediately, matching the ship-class designer's behaviour.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ shown together with `load` when carrying.
|
|||||||
<th>{i18n.t("game.report.section.my_ship_groups.column.destination")}</th>
|
<th>{i18n.t("game.report.section.my_ship_groups.column.destination")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_groups.column.origin")}</th>
|
<th>{i18n.t("game.report.section.my_ship_groups.column.origin")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_groups.column.range")}</th>
|
<th>{i18n.t("game.report.section.my_ship_groups.column.range")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_groups.column.speed")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_ship_groups.column.speed")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_groups.column.mass")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_ship_groups.column.mass")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_ship_groups.column.fleet")}</th>
|
<th>{i18n.t("game.report.section.my_ship_groups.column.fleet")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -77,8 +77,8 @@ shown together with `load` when carrying.
|
|||||||
{g.origin === null ? "—" : planetLabel(g.origin, planets)}
|
{g.origin === null ? "—" : planetLabel(g.origin, planets)}
|
||||||
</td>
|
</td>
|
||||||
<td>{g.range === null ? "—" : formatFloat(g.range)}</td>
|
<td>{g.range === null ? "—" : formatFloat(g.range)}</td>
|
||||||
<td>{formatFloat(g.speed)}</td>
|
<td class="numeric">{formatFloat(g.speed)}</td>
|
||||||
<td>{formatFloat(g.mass)}</td>
|
<td class="numeric">{formatFloat(g.mass)}</td>
|
||||||
<td>{g.fleet ?? "—"}</td>
|
<td>{g.fleet ?? "—"}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -102,7 +102,7 @@ shown together with `load` when carrying.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -116,6 +116,11 @@ shown together with `load` when carrying.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ highlight so the user can locate themselves quickly.
|
|||||||
RENDERED_REPORT_CONTEXT_KEY,
|
RENDERED_REPORT_CONTEXT_KEY,
|
||||||
type RenderedReportSource,
|
type RenderedReportSource,
|
||||||
} from "$lib/rendered-report.svelte";
|
} from "$lib/rendered-report.svelte";
|
||||||
import { formatCount, formatPercent, formatVotes } from "./format";
|
import { formatCount, formatFloat, formatVotes } from "./format";
|
||||||
|
|
||||||
const rendered = getContext<RenderedReportSource | undefined>(
|
const rendered = getContext<RenderedReportSource | undefined>(
|
||||||
RENDERED_REPORT_CONTEXT_KEY,
|
RENDERED_REPORT_CONTEXT_KEY,
|
||||||
@@ -37,14 +37,14 @@ highlight so the user can locate themselves quickly.
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{i18n.t("game.report.section.player_status.column.name")}</th>
|
<th>{i18n.t("game.report.section.player_status.column.name")}</th>
|
||||||
<th>{i18n.t("game.report.section.player_status.column.drive")}</th>
|
<th class="numeric">{i18n.t("game.report.section.player_status.column.drive")}</th>
|
||||||
<th>{i18n.t("game.report.section.player_status.column.weapons")}</th>
|
<th class="numeric">{i18n.t("game.report.section.player_status.column.weapons")}</th>
|
||||||
<th>{i18n.t("game.report.section.player_status.column.shields")}</th>
|
<th class="numeric">{i18n.t("game.report.section.player_status.column.shields")}</th>
|
||||||
<th>{i18n.t("game.report.section.player_status.column.cargo")}</th>
|
<th class="numeric">{i18n.t("game.report.section.player_status.column.cargo")}</th>
|
||||||
<th>{i18n.t("game.report.section.player_status.column.population")}</th>
|
<th class="numeric">{i18n.t("game.report.section.player_status.column.population")}</th>
|
||||||
<th>{i18n.t("game.report.section.player_status.column.industry")}</th>
|
<th class="numeric">{i18n.t("game.report.section.player_status.column.industry")}</th>
|
||||||
<th>{i18n.t("game.report.section.player_status.column.planets")}</th>
|
<th class="numeric">{i18n.t("game.report.section.player_status.column.planets")}</th>
|
||||||
<th>{i18n.t("game.report.section.player_status.column.votes")}</th>
|
<th class="numeric">{i18n.t("game.report.section.player_status.column.votes")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -73,14 +73,14 @@ highlight so the user can locate themselves quickly.
|
|||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td>{formatPercent(p.drive)}</td>
|
<td class="numeric">{formatFloat(p.drive)}</td>
|
||||||
<td>{formatPercent(p.weapons)}</td>
|
<td class="numeric">{formatFloat(p.weapons)}</td>
|
||||||
<td>{formatPercent(p.shields)}</td>
|
<td class="numeric">{formatFloat(p.shields)}</td>
|
||||||
<td>{formatPercent(p.cargo)}</td>
|
<td class="numeric">{formatFloat(p.cargo)}</td>
|
||||||
<td>{formatCount(p.population)}</td>
|
<td class="numeric">{formatCount(p.population)}</td>
|
||||||
<td>{formatCount(p.industry)}</td>
|
<td class="numeric">{formatCount(p.industry)}</td>
|
||||||
<td>{formatCount(p.planets)}</td>
|
<td class="numeric">{formatCount(p.planets)}</td>
|
||||||
<td>{formatVotes(p.votesReceived)}</td>
|
<td class="numeric">{formatVotes(p.votesReceived)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -103,7 +103,7 @@ highlight so the user can locate themselves quickly.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -117,6 +117,11 @@ highlight so the user can locate themselves quickly.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ reads `#17 (Castle)` rather than just `#17`.
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{i18n.t("game.report.section.ships_in_production.column.planet")}</th>
|
<th>{i18n.t("game.report.section.ships_in_production.column.planet")}</th>
|
||||||
<th>{i18n.t("game.report.section.ships_in_production.column.class")}</th>
|
<th>{i18n.t("game.report.section.ships_in_production.column.class")}</th>
|
||||||
<th>{i18n.t("game.report.section.ships_in_production.column.cost")}</th>
|
<th class="numeric">{i18n.t("game.report.section.ships_in_production.column.cost")}</th>
|
||||||
<th>
|
<th class="numeric">
|
||||||
{i18n.t("game.report.section.ships_in_production.column.prod_used")}
|
{i18n.t("game.report.section.ships_in_production.column.prod_used")}
|
||||||
</th>
|
</th>
|
||||||
<th>{i18n.t("game.report.section.ships_in_production.column.percent")}</th>
|
<th>{i18n.t("game.report.section.ships_in_production.column.percent")}</th>
|
||||||
<th>{i18n.t("game.report.section.ships_in_production.column.free")}</th>
|
<th class="numeric">{i18n.t("game.report.section.ships_in_production.column.free")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -58,10 +58,10 @@ reads `#17 (Castle)` rather than just `#17`.
|
|||||||
>
|
>
|
||||||
<td>{planetLabel(r.planetNumber, planets)}</td>
|
<td>{planetLabel(r.planetNumber, planets)}</td>
|
||||||
<td>{r.class}</td>
|
<td>{r.class}</td>
|
||||||
<td>{formatFloat(r.cost)}</td>
|
<td class="numeric">{formatFloat(r.cost)}</td>
|
||||||
<td>{formatFloat(r.prodUsed)}</td>
|
<td class="numeric">{formatFloat(r.prodUsed)}</td>
|
||||||
<td>{(r.percent * 100).toFixed(1)}</td>
|
<td>{(r.percent * 100).toFixed(1)}</td>
|
||||||
<td>{formatFloat(r.freeIndustry)}</td>
|
<td class="numeric">{formatFloat(r.freeIndustry)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -84,7 +84,7 @@ reads `#17 (Castle)` rather than just `#17`.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -98,6 +98,11 @@ reads `#17 (Castle)` rather than just `#17`.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,15 +37,15 @@ radar that doesn't even resolve to a planet.
|
|||||||
<table class="grid" data-testid="unidentified-groups-table">
|
<table class="grid" data-testid="unidentified-groups-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{i18n.t("game.report.section.unidentified_groups.column.x")}</th>
|
<th class="numeric">{i18n.t("game.report.section.unidentified_groups.column.x")}</th>
|
||||||
<th>{i18n.t("game.report.section.unidentified_groups.column.y")}</th>
|
<th class="numeric">{i18n.t("game.report.section.unidentified_groups.column.y")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each rows as g, i (i)}
|
{#each rows as g, i (i)}
|
||||||
<tr data-testid="unidentified-groups-row">
|
<tr data-testid="unidentified-groups-row">
|
||||||
<td>{formatFloat(g.x)}</td>
|
<td class="numeric">{formatFloat(g.x)}</td>
|
||||||
<td>{formatFloat(g.y)}</td>
|
<td class="numeric">{formatFloat(g.y)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -68,7 +68,7 @@ radar that doesn't even resolve to a planet.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -82,6 +82,11 @@ radar that doesn't even resolve to a planet.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,13 +42,13 @@ are intentionally omitted.
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.number")}</th>
|
<th>{i18n.t("game.report.section.my_planets.column.number")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.name")}</th>
|
<th>{i18n.t("game.report.section.my_planets.column.name")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.size")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.size")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.resources")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.resources")}</th>
|
||||||
<th>
|
<th class="numeric">
|
||||||
{i18n.t("game.report.section.my_planets.column.industry_stockpile")}
|
{i18n.t("game.report.section.my_planets.column.industry_stockpile")}
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th class="numeric">
|
||||||
{i18n.t("game.report.section.my_planets.column.materials_stockpile")}
|
{i18n.t("game.report.section.my_planets.column.materials_stockpile")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -58,11 +58,11 @@ are intentionally omitted.
|
|||||||
<tr data-testid="uninhabited-planets-row" data-number={p.number}>
|
<tr data-testid="uninhabited-planets-row" data-number={p.number}>
|
||||||
<td>{p.number}</td>
|
<td>{p.number}</td>
|
||||||
<td>{p.name}</td>
|
<td>{p.name}</td>
|
||||||
<td>{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
<td class="numeric">{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
||||||
<td>{formatFloat(p.size ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.size ?? 0)}</td>
|
||||||
<td>{formatFloat(p.resources ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.resources ?? 0)}</td>
|
||||||
<td>{formatFloat(p.industryStockpile ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.industryStockpile ?? 0)}</td>
|
||||||
<td>{formatFloat(p.materialsStockpile ?? 0)}</td>
|
<td class="numeric">{formatFloat(p.materialsStockpile ?? 0)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -85,7 +85,7 @@ are intentionally omitted.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -99,6 +99,11 @@ are intentionally omitted.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,14 +40,14 @@ else is known.
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.number")}</th>
|
<th>{i18n.t("game.report.section.my_planets.column.number")}</th>
|
||||||
<th>{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
<th class="numeric">{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each rows as p (p.number)}
|
{#each rows as p (p.number)}
|
||||||
<tr data-testid="unknown-planets-row" data-number={p.number}>
|
<tr data-testid="unknown-planets-row" data-number={p.number}>
|
||||||
<td>{p.number}</td>
|
<td>{p.number}</td>
|
||||||
<td>{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
<td class="numeric">{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -70,7 +70,7 @@ else is known.
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
@@ -84,6 +84,11 @@ else is known.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ explanatory text on the races table.
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{i18n.t("game.report.section.votes.column.race")}</th>
|
<th>{i18n.t("game.report.section.votes.column.race")}</th>
|
||||||
<th>{i18n.t("game.report.section.votes.column.votes")}</th>
|
<th class="numeric">{i18n.t("game.report.section.votes.column.votes")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each races as r (r.name)}
|
{#each races as r (r.name)}
|
||||||
<tr data-testid="votes-received-row" data-race={r.name}>
|
<tr data-testid="votes-received-row" data-race={r.name}>
|
||||||
<td>{r.name}</td>
|
<td>{r.name}</td>
|
||||||
<td>{formatVotes(r.votesReceived)}</td>
|
<td class="numeric">{formatVotes(r.votesReceived)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -113,7 +113,7 @@ explanatory text on the races table.
|
|||||||
.grid {
|
.grid {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th,
|
.grid th,
|
||||||
.grid td {
|
.grid td {
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ data fetching is performed here — the layout is responsible.
|
|||||||
} from "../../sync/order-draft.svelte";
|
} from "../../sync/order-draft.svelte";
|
||||||
import type { Relation } from "../../sync/order-types";
|
import type { Relation } from "../../sync/order-types";
|
||||||
import ViewState from "$lib/ui/view-state.svelte";
|
import ViewState from "$lib/ui/view-state.svelte";
|
||||||
|
import {
|
||||||
|
formatFloat,
|
||||||
|
formatInt,
|
||||||
|
} from "$lib/util/number-format";
|
||||||
|
|
||||||
type SortColumn =
|
type SortColumn =
|
||||||
| "name"
|
| "name"
|
||||||
@@ -122,31 +126,6 @@ data fetching is performed here — the layout is responsible.
|
|||||||
return sortDirection === "asc" ? "ascending" : "descending";
|
return sortDirection === "asc" ? "ascending" : "descending";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render a fraction in `[0, 1]` as a one-decimal percent
|
|
||||||
// (`0.225` → `"22.5"`). The conversion is value-only — no `%`
|
|
||||||
// suffix — so the column header carries the unit. Matches the
|
|
||||||
// sciences-table convention.
|
|
||||||
function formatPercent(fraction: number): string {
|
|
||||||
return (fraction * 100).toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
maximumFractionDigits: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatCount(value: number): string {
|
|
||||||
return value.toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
maximumFractionDigits: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatVotes(value: number): string {
|
|
||||||
return value.toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setStance(acceptor: string, relation: Relation): Promise<void> {
|
async function setStance(acceptor: string, relation: Relation): Promise<void> {
|
||||||
if (draft === undefined) return;
|
if (draft === undefined) return;
|
||||||
// No-op when the row already reflects the requested stance — the
|
// No-op when the row already reflects the requested stance — the
|
||||||
@@ -192,7 +171,7 @@ data fetching is performed here — the layout is responsible.
|
|||||||
<span class="summary-label">
|
<span class="summary-label">
|
||||||
{i18n.t("game.table.races.votes.mine")}:
|
{i18n.t("game.table.races.votes.mine")}:
|
||||||
</span>
|
</span>
|
||||||
<span class="summary-value">{formatVotes(myVotes)}</span>
|
<span class="summary-value numeric">{formatFloat(myVotes)}</span>
|
||||||
</span>
|
</span>
|
||||||
<label class="summary-cell vote-picker">
|
<label class="summary-cell vote-picker">
|
||||||
<span class="summary-label">
|
<span class="summary-label">
|
||||||
@@ -244,7 +223,7 @@ data fetching is performed here — the layout is responsible.
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{#each COLUMNS as column (column)}
|
{#each COLUMNS as column (column)}
|
||||||
<th aria-sort={ariaSort(column)}>
|
<th aria-sort={ariaSort(column)} class:numeric={column !== "name"}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="sort"
|
class="sort"
|
||||||
@@ -267,23 +246,29 @@ data fetching is performed here — the layout is responsible.
|
|||||||
{#each sorted as r (r.name)}
|
{#each sorted as r (r.name)}
|
||||||
<tr data-testid="races-row" data-name={r.name}>
|
<tr data-testid="races-row" data-name={r.name}>
|
||||||
<td data-testid="races-cell-name">{r.name}</td>
|
<td data-testid="races-cell-name">{r.name}</td>
|
||||||
<td data-testid="races-cell-drive">{formatPercent(r.drive)}</td>
|
<td class="numeric" data-testid="races-cell-drive">
|
||||||
<td data-testid="races-cell-weapons">
|
{formatFloat(r.drive)}
|
||||||
{formatPercent(r.weapons)}
|
|
||||||
</td>
|
</td>
|
||||||
<td data-testid="races-cell-shields">
|
<td class="numeric" data-testid="races-cell-weapons">
|
||||||
{formatPercent(r.shields)}
|
{formatFloat(r.weapons)}
|
||||||
</td>
|
</td>
|
||||||
<td data-testid="races-cell-cargo">{formatPercent(r.cargo)}</td>
|
<td class="numeric" data-testid="races-cell-shields">
|
||||||
<td data-testid="races-cell-population">
|
{formatFloat(r.shields)}
|
||||||
{formatCount(r.population)}
|
|
||||||
</td>
|
</td>
|
||||||
<td data-testid="races-cell-industry">
|
<td class="numeric" data-testid="races-cell-cargo">
|
||||||
{formatCount(r.industry)}
|
{formatFloat(r.cargo)}
|
||||||
</td>
|
</td>
|
||||||
<td data-testid="races-cell-planets">{formatCount(r.planets)}</td>
|
<td class="numeric" data-testid="races-cell-population">
|
||||||
<td data-testid="races-cell-votes">
|
{formatInt(r.population)}
|
||||||
{formatVotes(r.votesReceived)}
|
</td>
|
||||||
|
<td class="numeric" data-testid="races-cell-industry">
|
||||||
|
{formatInt(r.industry)}
|
||||||
|
</td>
|
||||||
|
<td class="numeric" data-testid="races-cell-planets">
|
||||||
|
{formatInt(r.planets)}
|
||||||
|
</td>
|
||||||
|
<td class="numeric" data-testid="races-cell-votes">
|
||||||
|
{formatFloat(r.votesReceived)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div
|
<div
|
||||||
@@ -359,6 +344,9 @@ data fetching is performed here — the layout is responsible.
|
|||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
.summary-value.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
.vote-picker select {
|
.vote-picker select {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
@@ -403,7 +391,7 @@ data fetching is performed here — the layout is responsible.
|
|||||||
padding: 0.4rem 0.6rem;
|
padding: 0.4rem 0.6rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 1px solid var(--color-border-subtle);
|
border-bottom: 1px solid var(--color-border-subtle);
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th {
|
.grid th {
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
@@ -411,6 +399,14 @@ data fetching is performed here — the layout is responsible.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.grid th.numeric .sort {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
.grid tbody tr:hover {
|
.grid tbody tr:hover {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ data fetching is performed here — the shell is responsible.
|
|||||||
ORDER_DRAFT_CONTEXT_KEY,
|
ORDER_DRAFT_CONTEXT_KEY,
|
||||||
OrderDraftStore,
|
OrderDraftStore,
|
||||||
} from "../../sync/order-draft.svelte";
|
} from "../../sync/order-draft.svelte";
|
||||||
|
import { formatPercent } from "$lib/util/number-format";
|
||||||
|
|
||||||
type SortColumn = "name" | "drive" | "weapons" | "shields" | "cargo";
|
type SortColumn = "name" | "drive" | "weapons" | "shields" | "cargo";
|
||||||
type SortDirection = "asc" | "desc";
|
type SortDirection = "asc" | "desc";
|
||||||
@@ -103,16 +104,6 @@ data fetching is performed here — the shell is responsible.
|
|||||||
return sortDirection === "asc" ? "ascending" : "descending";
|
return sortDirection === "asc" ? "ascending" : "descending";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render a fraction in `[0, 1]` as a one-decimal percent
|
|
||||||
// (`0.225` → `"22.5"`). The conversion is value-only — no `%`
|
|
||||||
// suffix — so callers can decorate independently.
|
|
||||||
function formatPercent(fraction: number): string {
|
|
||||||
return (fraction * 100).toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 0,
|
|
||||||
maximumFractionDigits: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function openDesigner(name: string): void {
|
function openDesigner(name: string): void {
|
||||||
activeView.select("designer-science", { scienceId: name });
|
activeView.select("designer-science", { scienceId: name });
|
||||||
}
|
}
|
||||||
@@ -174,7 +165,7 @@ data fetching is performed here — the shell is responsible.
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{#each COLUMNS as column (column)}
|
{#each COLUMNS as column (column)}
|
||||||
<th aria-sort={ariaSort(column)}>
|
<th aria-sort={ariaSort(column)} class:numeric={column !== "name"}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="sort"
|
class="sort"
|
||||||
@@ -201,14 +192,18 @@ data fetching is performed here — the shell is responsible.
|
|||||||
ondblclick={() => openDesigner(sci.name)}
|
ondblclick={() => openDesigner(sci.name)}
|
||||||
>
|
>
|
||||||
<td data-testid="sciences-cell-name">{sci.name}</td>
|
<td data-testid="sciences-cell-name">{sci.name}</td>
|
||||||
<td data-testid="sciences-cell-drive">{formatPercent(sci.drive)}</td>
|
<td class="numeric" data-testid="sciences-cell-drive">
|
||||||
<td data-testid="sciences-cell-weapons">
|
{formatPercent(sci.drive)}
|
||||||
|
</td>
|
||||||
|
<td class="numeric" data-testid="sciences-cell-weapons">
|
||||||
{formatPercent(sci.weapons)}
|
{formatPercent(sci.weapons)}
|
||||||
</td>
|
</td>
|
||||||
<td data-testid="sciences-cell-shields">
|
<td class="numeric" data-testid="sciences-cell-shields">
|
||||||
{formatPercent(sci.shields)}
|
{formatPercent(sci.shields)}
|
||||||
</td>
|
</td>
|
||||||
<td data-testid="sciences-cell-cargo">{formatPercent(sci.cargo)}</td>
|
<td class="numeric" data-testid="sciences-cell-cargo">
|
||||||
|
{formatPercent(sci.cargo)}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -283,7 +278,7 @@ data fetching is performed here — the shell is responsible.
|
|||||||
padding: 0.4rem 0.6rem;
|
padding: 0.4rem 0.6rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 1px solid var(--color-border-subtle);
|
border-bottom: 1px solid var(--color-border-subtle);
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th {
|
.grid th {
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
@@ -291,6 +286,14 @@ data fetching is performed here — the shell is responsible.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.grid th.numeric .sort {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
.grid tbody tr {
|
.grid tbody tr {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ data fetching is performed here — the layout is responsible.
|
|||||||
} from "../../sync/order-draft.svelte";
|
} from "../../sync/order-draft.svelte";
|
||||||
import { calculatorLoadRequest } from "$lib/calculator/load-request.svelte";
|
import { calculatorLoadRequest } from "$lib/calculator/load-request.svelte";
|
||||||
import ViewState from "$lib/ui/view-state.svelte";
|
import ViewState from "$lib/ui/view-state.svelte";
|
||||||
|
import { formatFloat } from "$lib/util/number-format";
|
||||||
|
|
||||||
type SortColumn =
|
type SortColumn =
|
||||||
| "name"
|
| "name"
|
||||||
@@ -106,10 +107,6 @@ data fetching is performed here — the layout is responsible.
|
|||||||
return sortDirection === "asc" ? "ascending" : "descending";
|
return sortDirection === "asc" ? "ascending" : "descending";
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatNumber(value: number): string {
|
|
||||||
return value.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
||||||
}
|
|
||||||
|
|
||||||
function openInCalculator(name: string): void {
|
function openInCalculator(name: string): void {
|
||||||
calculatorLoadRequest.request(name);
|
calculatorLoadRequest.request(name);
|
||||||
}
|
}
|
||||||
@@ -171,7 +168,7 @@ data fetching is performed here — the layout is responsible.
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{#each COLUMNS as column (column)}
|
{#each COLUMNS as column (column)}
|
||||||
<th aria-sort={ariaSort(column)}>
|
<th aria-sort={ariaSort(column)} class:numeric={column !== "name"}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="sort"
|
class="sort"
|
||||||
@@ -198,11 +195,21 @@ data fetching is performed here — the layout is responsible.
|
|||||||
ondblclick={() => openInCalculator(cls.name)}
|
ondblclick={() => openInCalculator(cls.name)}
|
||||||
>
|
>
|
||||||
<td data-testid="ship-classes-cell-name">{cls.name}</td>
|
<td data-testid="ship-classes-cell-name">{cls.name}</td>
|
||||||
<td data-testid="ship-classes-cell-drive">{formatNumber(cls.drive)}</td>
|
<td class="numeric" data-testid="ship-classes-cell-drive">
|
||||||
<td data-testid="ship-classes-cell-armament">{cls.armament}</td>
|
{formatFloat(cls.drive)}
|
||||||
<td data-testid="ship-classes-cell-weapons">{formatNumber(cls.weapons)}</td>
|
</td>
|
||||||
<td data-testid="ship-classes-cell-shields">{formatNumber(cls.shields)}</td>
|
<td class="numeric" data-testid="ship-classes-cell-armament">
|
||||||
<td data-testid="ship-classes-cell-cargo">{formatNumber(cls.cargo)}</td>
|
{cls.armament}
|
||||||
|
</td>
|
||||||
|
<td class="numeric" data-testid="ship-classes-cell-weapons">
|
||||||
|
{formatFloat(cls.weapons)}
|
||||||
|
</td>
|
||||||
|
<td class="numeric" data-testid="ship-classes-cell-shields">
|
||||||
|
{formatFloat(cls.shields)}
|
||||||
|
</td>
|
||||||
|
<td class="numeric" data-testid="ship-classes-cell-cargo">
|
||||||
|
{formatFloat(cls.cargo)}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -277,7 +284,7 @@ data fetching is performed here — the layout is responsible.
|
|||||||
padding: 0.4rem 0.6rem;
|
padding: 0.4rem 0.6rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 1px solid var(--color-border-subtle);
|
border-bottom: 1px solid var(--color-border-subtle);
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
.grid th {
|
.grid th {
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
@@ -285,6 +292,14 @@ data fetching is performed here — the layout is responsible.
|
|||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
.grid th.numeric,
|
||||||
|
.grid td.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.grid th.numeric .sort {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
.grid tbody tr {
|
.grid tbody tr {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,8 +282,8 @@ const en = {
|
|||||||
"game.inspector.planet.field.population": "population",
|
"game.inspector.planet.field.population": "population",
|
||||||
"game.inspector.planet.field.colonists": "colonists",
|
"game.inspector.planet.field.colonists": "colonists",
|
||||||
"game.inspector.planet.field.industry": "industry",
|
"game.inspector.planet.field.industry": "industry",
|
||||||
"game.inspector.planet.field.industry_stockpile": "industry stockpile ($)",
|
"game.inspector.planet.field.industry_stockpile": "industry ($)",
|
||||||
"game.inspector.planet.field.materials_stockpile": "materials stockpile (M)",
|
"game.inspector.planet.field.materials_stockpile": "materials (M)",
|
||||||
"game.inspector.planet.field.natural_resources": "natural resources",
|
"game.inspector.planet.field.natural_resources": "natural resources",
|
||||||
"game.inspector.planet.field.production": "current production",
|
"game.inspector.planet.field.production": "current production",
|
||||||
"game.inspector.planet.field.free_industry": "free production",
|
"game.inspector.planet.field.free_industry": "free production",
|
||||||
@@ -503,10 +503,10 @@ const en = {
|
|||||||
"game.table.races.empty": "no other races known yet",
|
"game.table.races.empty": "no other races known yet",
|
||||||
"game.table.races.filter.placeholder": "filter by name",
|
"game.table.races.filter.placeholder": "filter by name",
|
||||||
"game.table.races.column.name": "name",
|
"game.table.races.column.name": "name",
|
||||||
"game.table.races.column.drive": "drive %",
|
"game.table.races.column.drive": "drive",
|
||||||
"game.table.races.column.weapons": "weapons %",
|
"game.table.races.column.weapons": "weapons",
|
||||||
"game.table.races.column.shields": "shields %",
|
"game.table.races.column.shields": "shields",
|
||||||
"game.table.races.column.cargo": "cargo %",
|
"game.table.races.column.cargo": "cargo",
|
||||||
"game.table.races.column.population": "population",
|
"game.table.races.column.population": "population",
|
||||||
"game.table.races.column.industry": "production",
|
"game.table.races.column.industry": "production",
|
||||||
"game.table.races.column.planets": "planets",
|
"game.table.races.column.planets": "planets",
|
||||||
@@ -627,10 +627,10 @@ const en = {
|
|||||||
"game.report.section.votes.empty": "no votes cast yet",
|
"game.report.section.votes.empty": "no votes cast yet",
|
||||||
"game.report.section.player_status.title": "player status",
|
"game.report.section.player_status.title": "player status",
|
||||||
"game.report.section.player_status.column.name": "name",
|
"game.report.section.player_status.column.name": "name",
|
||||||
"game.report.section.player_status.column.drive": "drive %",
|
"game.report.section.player_status.column.drive": "drive",
|
||||||
"game.report.section.player_status.column.weapons": "weapons %",
|
"game.report.section.player_status.column.weapons": "weapons",
|
||||||
"game.report.section.player_status.column.shields": "shields %",
|
"game.report.section.player_status.column.shields": "shields",
|
||||||
"game.report.section.player_status.column.cargo": "cargo %",
|
"game.report.section.player_status.column.cargo": "cargo",
|
||||||
"game.report.section.player_status.column.population": "population",
|
"game.report.section.player_status.column.population": "population",
|
||||||
"game.report.section.player_status.column.industry": "production",
|
"game.report.section.player_status.column.industry": "production",
|
||||||
"game.report.section.player_status.column.planets": "planets",
|
"game.report.section.player_status.column.planets": "planets",
|
||||||
@@ -693,8 +693,8 @@ const en = {
|
|||||||
"game.report.section.bombings.column.industry": "industry",
|
"game.report.section.bombings.column.industry": "industry",
|
||||||
"game.report.section.bombings.column.population": "population",
|
"game.report.section.bombings.column.population": "population",
|
||||||
"game.report.section.bombings.column.colonists": "colonists",
|
"game.report.section.bombings.column.colonists": "colonists",
|
||||||
"game.report.section.bombings.column.industry_stockpile": "industry stockpile ($)",
|
"game.report.section.bombings.column.industry_stockpile": "industry ($)",
|
||||||
"game.report.section.bombings.column.materials_stockpile": "materials stockpile (M)",
|
"game.report.section.bombings.column.materials_stockpile": "materials (M)",
|
||||||
"game.report.section.bombings.column.attack_power": "attack power",
|
"game.report.section.bombings.column.attack_power": "attack power",
|
||||||
"game.report.section.bombings.wiped": "wiped",
|
"game.report.section.bombings.wiped": "wiped",
|
||||||
"game.report.section.approaching_groups.title": "approaching groups",
|
"game.report.section.approaching_groups.title": "approaching groups",
|
||||||
|
|||||||
@@ -283,8 +283,8 @@ const ru: Record<keyof typeof en, string> = {
|
|||||||
"game.inspector.planet.field.population": "население",
|
"game.inspector.planet.field.population": "население",
|
||||||
"game.inspector.planet.field.colonists": "колонисты",
|
"game.inspector.planet.field.colonists": "колонисты",
|
||||||
"game.inspector.planet.field.industry": "промышленность",
|
"game.inspector.planet.field.industry": "промышленность",
|
||||||
"game.inspector.planet.field.industry_stockpile": "запасы промышленности ($)",
|
"game.inspector.planet.field.industry_stockpile": "промышленность ($)",
|
||||||
"game.inspector.planet.field.materials_stockpile": "запасы сырья (M)",
|
"game.inspector.planet.field.materials_stockpile": "сырьё (M)",
|
||||||
"game.inspector.planet.field.natural_resources": "природные ресурсы",
|
"game.inspector.planet.field.natural_resources": "природные ресурсы",
|
||||||
"game.inspector.planet.field.production": "текущее производство",
|
"game.inspector.planet.field.production": "текущее производство",
|
||||||
"game.inspector.planet.field.free_industry": "свободные мощности",
|
"game.inspector.planet.field.free_industry": "свободные мощности",
|
||||||
@@ -504,10 +504,10 @@ const ru: Record<keyof typeof en, string> = {
|
|||||||
"game.table.races.empty": "других рас пока не видно",
|
"game.table.races.empty": "других рас пока не видно",
|
||||||
"game.table.races.filter.placeholder": "фильтр по имени",
|
"game.table.races.filter.placeholder": "фильтр по имени",
|
||||||
"game.table.races.column.name": "имя",
|
"game.table.races.column.name": "имя",
|
||||||
"game.table.races.column.drive": "двигатель %",
|
"game.table.races.column.drive": "двигатель",
|
||||||
"game.table.races.column.weapons": "оружие %",
|
"game.table.races.column.weapons": "оружие",
|
||||||
"game.table.races.column.shields": "защита %",
|
"game.table.races.column.shields": "защита",
|
||||||
"game.table.races.column.cargo": "трюм %",
|
"game.table.races.column.cargo": "трюм",
|
||||||
"game.table.races.column.population": "население",
|
"game.table.races.column.population": "население",
|
||||||
"game.table.races.column.industry": "производство",
|
"game.table.races.column.industry": "производство",
|
||||||
"game.table.races.column.planets": "планет",
|
"game.table.races.column.planets": "планет",
|
||||||
@@ -628,10 +628,10 @@ const ru: Record<keyof typeof en, string> = {
|
|||||||
"game.report.section.votes.empty": "голосов ещё нет",
|
"game.report.section.votes.empty": "голосов ещё нет",
|
||||||
"game.report.section.player_status.title": "статус игроков",
|
"game.report.section.player_status.title": "статус игроков",
|
||||||
"game.report.section.player_status.column.name": "имя",
|
"game.report.section.player_status.column.name": "имя",
|
||||||
"game.report.section.player_status.column.drive": "двигатель %",
|
"game.report.section.player_status.column.drive": "двигатель",
|
||||||
"game.report.section.player_status.column.weapons": "оружие %",
|
"game.report.section.player_status.column.weapons": "оружие",
|
||||||
"game.report.section.player_status.column.shields": "защита %",
|
"game.report.section.player_status.column.shields": "защита",
|
||||||
"game.report.section.player_status.column.cargo": "трюм %",
|
"game.report.section.player_status.column.cargo": "трюм",
|
||||||
"game.report.section.player_status.column.population": "население",
|
"game.report.section.player_status.column.population": "население",
|
||||||
"game.report.section.player_status.column.industry": "производство",
|
"game.report.section.player_status.column.industry": "производство",
|
||||||
"game.report.section.player_status.column.planets": "планет",
|
"game.report.section.player_status.column.planets": "планет",
|
||||||
@@ -694,8 +694,8 @@ const ru: Record<keyof typeof en, string> = {
|
|||||||
"game.report.section.bombings.column.industry": "промышленность",
|
"game.report.section.bombings.column.industry": "промышленность",
|
||||||
"game.report.section.bombings.column.population": "население",
|
"game.report.section.bombings.column.population": "население",
|
||||||
"game.report.section.bombings.column.colonists": "колонисты",
|
"game.report.section.bombings.column.colonists": "колонисты",
|
||||||
"game.report.section.bombings.column.industry_stockpile": "запас промышленности ($)",
|
"game.report.section.bombings.column.industry_stockpile": "промышленность ($)",
|
||||||
"game.report.section.bombings.column.materials_stockpile": "запас материалов (M)",
|
"game.report.section.bombings.column.materials_stockpile": "сырьё (M)",
|
||||||
"game.report.section.bombings.column.attack_power": "сила удара",
|
"game.report.section.bombings.column.attack_power": "сила удара",
|
||||||
"game.report.section.bombings.wiped": "уничтожена",
|
"game.report.section.bombings.wiped": "уничтожена",
|
||||||
"game.report.section.approaching_groups.title": "приближающиеся группы",
|
"game.report.section.approaching_groups.title": "приближающиеся группы",
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ field with five buttons.
|
|||||||
validateEntityName,
|
validateEntityName,
|
||||||
type EntityNameInvalidReason,
|
type EntityNameInvalidReason,
|
||||||
} from "$lib/util/entity-name";
|
} from "$lib/util/entity-name";
|
||||||
|
import { formatFloat } from "$lib/util/number-format";
|
||||||
import CargoRoutes from "./planet/cargo-routes.svelte";
|
import CargoRoutes from "./planet/cargo-routes.svelte";
|
||||||
import Production from "./planet/production.svelte";
|
import Production from "./planet/production.svelte";
|
||||||
import ShipGroups from "./planet/ship-groups.svelte";
|
import ShipGroups from "./planet/ship-groups.svelte";
|
||||||
@@ -99,14 +100,10 @@ field with five buttons.
|
|||||||
|
|
||||||
const kindLabel = $derived(i18n.t(kindKeyMap[planet.kind]));
|
const kindLabel = $derived(i18n.t(kindKeyMap[planet.kind]));
|
||||||
const coordinates = $derived(
|
const coordinates = $derived(
|
||||||
`(${formatNumber(planet.x)}, ${formatNumber(planet.y)})`,
|
`${formatFloat(planet.x)}, ${formatFloat(planet.y)}`,
|
||||||
);
|
);
|
||||||
const productionLabel = $derived(productionDisplay(planet.production));
|
const productionLabel = $derived(productionDisplay(planet.production));
|
||||||
|
|
||||||
function formatNumber(value: number): string {
|
|
||||||
return value.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
||||||
}
|
|
||||||
|
|
||||||
function productionDisplay(value: string | null): string {
|
function productionDisplay(value: string | null): string {
|
||||||
if (value === null || value === "") {
|
if (value === null || value === "") {
|
||||||
return i18n.t("game.inspector.planet.production_none");
|
return i18n.t("game.inspector.planet.production_none");
|
||||||
@@ -252,55 +249,55 @@ field with five buttons.
|
|||||||
|
|
||||||
<div class="field" data-testid="inspector-planet-field-coordinates">
|
<div class="field" data-testid="inspector-planet-field-coordinates">
|
||||||
<dt>{i18n.t("game.inspector.planet.field.coordinates")}</dt>
|
<dt>{i18n.t("game.inspector.planet.field.coordinates")}</dt>
|
||||||
<dd>{coordinates}</dd>
|
<dd class="numeric">{coordinates}</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if planet.size !== null}
|
{#if planet.size !== null}
|
||||||
<div class="field" data-testid="inspector-planet-field-size">
|
<div class="field" data-testid="inspector-planet-field-size">
|
||||||
<dt>{i18n.t("game.inspector.planet.field.size")}</dt>
|
<dt>{i18n.t("game.inspector.planet.field.size")}</dt>
|
||||||
<dd>{formatNumber(planet.size)}</dd>
|
<dd class="numeric">{formatFloat(planet.size)}</dd>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if planet.resources !== null}
|
{#if planet.resources !== null}
|
||||||
<div class="field" data-testid="inspector-planet-field-natural_resources">
|
<div class="field" data-testid="inspector-planet-field-natural_resources">
|
||||||
<dt>{i18n.t("game.inspector.planet.field.natural_resources")}</dt>
|
<dt>{i18n.t("game.inspector.planet.field.natural_resources")}</dt>
|
||||||
<dd>{formatNumber(planet.resources)}</dd>
|
<dd class="numeric">{formatFloat(planet.resources)}</dd>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if planet.population !== null}
|
{#if planet.population !== null}
|
||||||
<div class="field" data-testid="inspector-planet-field-population">
|
<div class="field" data-testid="inspector-planet-field-population">
|
||||||
<dt>{i18n.t("game.inspector.planet.field.population")}</dt>
|
<dt>{i18n.t("game.inspector.planet.field.population")}</dt>
|
||||||
<dd>{formatNumber(planet.population)}</dd>
|
<dd class="numeric">{formatFloat(planet.population)}</dd>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if planet.colonists !== null}
|
{#if planet.colonists !== null}
|
||||||
<div class="field" data-testid="inspector-planet-field-colonists">
|
<div class="field" data-testid="inspector-planet-field-colonists">
|
||||||
<dt>{i18n.t("game.inspector.planet.field.colonists")}</dt>
|
<dt>{i18n.t("game.inspector.planet.field.colonists")}</dt>
|
||||||
<dd>{formatNumber(planet.colonists)}</dd>
|
<dd class="numeric">{formatFloat(planet.colonists)}</dd>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if planet.industry !== null}
|
{#if planet.industry !== null}
|
||||||
<div class="field" data-testid="inspector-planet-field-industry">
|
<div class="field" data-testid="inspector-planet-field-industry">
|
||||||
<dt>{i18n.t("game.inspector.planet.field.industry")}</dt>
|
<dt>{i18n.t("game.inspector.planet.field.industry")}</dt>
|
||||||
<dd>{formatNumber(planet.industry)}</dd>
|
<dd class="numeric">{formatFloat(planet.industry)}</dd>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if planet.industryStockpile !== null}
|
{#if planet.industryStockpile !== null}
|
||||||
<div class="field" data-testid="inspector-planet-field-industry_stockpile">
|
<div class="field" data-testid="inspector-planet-field-industry_stockpile">
|
||||||
<dt>{i18n.t("game.inspector.planet.field.industry_stockpile")}</dt>
|
<dt>{i18n.t("game.inspector.planet.field.industry_stockpile")}</dt>
|
||||||
<dd>{formatNumber(planet.industryStockpile)}</dd>
|
<dd class="numeric">{formatFloat(planet.industryStockpile)}</dd>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if planet.materialsStockpile !== null}
|
{#if planet.materialsStockpile !== null}
|
||||||
<div class="field" data-testid="inspector-planet-field-materials_stockpile">
|
<div class="field" data-testid="inspector-planet-field-materials_stockpile">
|
||||||
<dt>{i18n.t("game.inspector.planet.field.materials_stockpile")}</dt>
|
<dt>{i18n.t("game.inspector.planet.field.materials_stockpile")}</dt>
|
||||||
<dd>{formatNumber(planet.materialsStockpile)}</dd>
|
<dd class="numeric">{formatFloat(planet.materialsStockpile)}</dd>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -314,7 +311,7 @@ field with five buttons.
|
|||||||
{#if planet.freeIndustry !== null}
|
{#if planet.freeIndustry !== null}
|
||||||
<div class="field" data-testid="inspector-planet-field-free_industry">
|
<div class="field" data-testid="inspector-planet-field-free_industry">
|
||||||
<dt>{i18n.t("game.inspector.planet.field.free_industry")}</dt>
|
<dt>{i18n.t("game.inspector.planet.field.free_industry")}</dt>
|
||||||
<dd>{formatNumber(planet.freeIndustry)}</dd>
|
<dd class="numeric">{formatFloat(planet.freeIndustry)}</dd>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</dl>
|
</dl>
|
||||||
@@ -362,17 +359,20 @@ field with five buttons.
|
|||||||
}
|
}
|
||||||
.field dt {
|
.field dt {
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
.field dd {
|
.field dd {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.field dd.numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
}
|
}
|
||||||
.hint {
|
.hint {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
.action {
|
.action {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ ship-groups table view with an additional `(planet, race)` filter.
|
|||||||
SELECTION_CONTEXT_KEY,
|
SELECTION_CONTEXT_KEY,
|
||||||
type SelectionStore,
|
type SelectionStore,
|
||||||
} from "$lib/selection.svelte";
|
} from "$lib/selection.svelte";
|
||||||
|
import { formatFloat } from "$lib/util/number-format";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
planet: ReportPlanet;
|
planet: ReportPlanet;
|
||||||
@@ -90,10 +91,6 @@ ship-groups table view with an additional `(planet, race)` filter.
|
|||||||
return rows;
|
return rows;
|
||||||
});
|
});
|
||||||
|
|
||||||
function formatNumber(value: number): string {
|
|
||||||
return value.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectLocalGroup(groupId: string): void {
|
function selectLocalGroup(groupId: string): void {
|
||||||
if (selection === undefined) return;
|
if (selection === undefined) return;
|
||||||
selection.selectShipGroup({ variant: "local", id: groupId });
|
selection.selectShipGroup({ variant: "local", id: groupId });
|
||||||
@@ -125,7 +122,7 @@ ship-groups table view with an additional `(planet, race)` filter.
|
|||||||
</span>
|
</span>
|
||||||
<span class="mass">
|
<span class="mass">
|
||||||
{i18n.t("game.inspector.planet.ship_groups.row.mass", {
|
{i18n.t("game.inspector.planet.ship_groups.row.mass", {
|
||||||
mass: formatNumber(row.mass),
|
mass: formatFloat(row.mass),
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -141,7 +138,7 @@ ship-groups table view with an additional `(planet, race)` filter.
|
|||||||
</span>
|
</span>
|
||||||
<span class="mass">
|
<span class="mass">
|
||||||
{i18n.t("game.inspector.planet.ship_groups.row.mass", {
|
{i18n.t("game.inspector.planet.ship_groups.row.mass", {
|
||||||
mass: formatNumber(row.mass),
|
mass: formatFloat(row.mass),
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ variant — for Phase 19 the inspector is intentionally read-only.
|
|||||||
ShipClassSummary,
|
ShipClassSummary,
|
||||||
} from "../../api/game-state";
|
} from "../../api/game-state";
|
||||||
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
|
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
|
||||||
|
import { formatFloat } from "$lib/util/number-format";
|
||||||
import Actions from "./ship-group/actions.svelte";
|
import Actions from "./ship-group/actions.svelte";
|
||||||
|
|
||||||
export type ShipGroupSelection =
|
export type ShipGroupSelection =
|
||||||
@@ -82,10 +83,6 @@ variant — for Phase 19 the inspector is intentionally read-only.
|
|||||||
return planet.name;
|
return planet.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatNumber(value: number): string {
|
|
||||||
return value.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
||||||
}
|
|
||||||
|
|
||||||
function cargoLabel(cargo: "NONE" | "COL" | "CAP" | "MAT" | "EMP"): string {
|
function cargoLabel(cargo: "NONE" | "COL" | "CAP" | "MAT" | "EMP"): string {
|
||||||
if (cargo === "NONE") {
|
if (cargo === "NONE") {
|
||||||
return i18n.t("game.inspector.ship_group.cargo.none");
|
return i18n.t("game.inspector.ship_group.cargo.none");
|
||||||
@@ -132,27 +129,27 @@ variant — for Phase 19 the inspector is intentionally read-only.
|
|||||||
<dl class="fields">
|
<dl class="fields">
|
||||||
<div class="field" data-testid="inspector-ship-group-field-count">
|
<div class="field" data-testid="inspector-ship-group-field-count">
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.count")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.count")}</dt>
|
||||||
<dd>{g.count}</dd>
|
<dd class="numeric">{g.count}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="field" data-testid="inspector-ship-group-field-drive">
|
<div class="field" data-testid="inspector-ship-group-field-drive">
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.drive")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.drive")}</dt>
|
||||||
<dd>{formatNumber(g.tech.drive)}</dd>
|
<dd class="numeric">{formatFloat(g.tech.drive)}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="field" data-testid="inspector-ship-group-field-weapons">
|
<div class="field" data-testid="inspector-ship-group-field-weapons">
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.weapons")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.weapons")}</dt>
|
||||||
<dd>{formatNumber(g.tech.weapons)}</dd>
|
<dd class="numeric">{formatFloat(g.tech.weapons)}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="field" data-testid="inspector-ship-group-field-shields">
|
<div class="field" data-testid="inspector-ship-group-field-shields">
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.shields")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.shields")}</dt>
|
||||||
<dd>{formatNumber(g.tech.shields)}</dd>
|
<dd class="numeric">{formatFloat(g.tech.shields)}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="field" data-testid="inspector-ship-group-field-cargo-tech">
|
<div class="field" data-testid="inspector-ship-group-field-cargo-tech">
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.cargo_tech")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.cargo_tech")}</dt>
|
||||||
<dd>{formatNumber(g.tech.cargo)}</dd>
|
<dd class="numeric">{formatFloat(g.tech.cargo)}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="field" data-testid="inspector-ship-group-field-mass">
|
<div class="field" data-testid="inspector-ship-group-field-mass">
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.mass")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.mass")}</dt>
|
||||||
<dd>{formatNumber(g.mass)}</dd>
|
<dd class="numeric">{formatFloat(g.mass)}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="field" data-testid="inspector-ship-group-field-cargo-load">
|
<div class="field" data-testid="inspector-ship-group-field-cargo-load">
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.cargo_load")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.cargo_load")}</dt>
|
||||||
@@ -160,7 +157,7 @@ variant — for Phase 19 the inspector is intentionally read-only.
|
|||||||
{#if g.cargo === "NONE"}
|
{#if g.cargo === "NONE"}
|
||||||
{cargoLabel(g.cargo)}
|
{cargoLabel(g.cargo)}
|
||||||
{:else}
|
{:else}
|
||||||
{cargoLabel(g.cargo)} × {formatNumber(g.load)}
|
{cargoLabel(g.cargo)} × <span class="numeric">{formatFloat(g.load)}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
@@ -181,7 +178,7 @@ variant — for Phase 19 the inspector is intentionally read-only.
|
|||||||
</div>
|
</div>
|
||||||
<div class="field" data-testid="inspector-ship-group-field-distance">
|
<div class="field" data-testid="inspector-ship-group-field-distance">
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.distance")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.distance")}</dt>
|
||||||
<dd>{formatNumber(g.range!)}</dd>
|
<dd class="numeric">{formatFloat(g.range!)}</dd>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -212,23 +209,25 @@ variant — for Phase 19 the inspector is intentionally read-only.
|
|||||||
</div>
|
</div>
|
||||||
<div class="field" data-testid="inspector-ship-group-field-distance">
|
<div class="field" data-testid="inspector-ship-group-field-distance">
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.distance")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.distance")}</dt>
|
||||||
<dd>{formatNumber(g.distance)}</dd>
|
<dd class="numeric">{formatFloat(g.distance)}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="field" data-testid="inspector-ship-group-field-speed">
|
<div class="field" data-testid="inspector-ship-group-field-speed">
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.speed")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.speed")}</dt>
|
||||||
<dd>{formatNumber(g.speed)}</dd>
|
<dd class="numeric">{formatFloat(g.speed)}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="field" data-testid="inspector-ship-group-field-eta">
|
<div class="field" data-testid="inspector-ship-group-field-eta">
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.eta")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.eta")}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{eta === null
|
{#if eta === null}
|
||||||
? i18n.t("game.designer.ship_class.preview.unavailable")
|
{i18n.t("game.designer.ship_class.preview.unavailable")}
|
||||||
: eta}
|
{:else}
|
||||||
|
<span class="numeric">{eta}</span>
|
||||||
|
{/if}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="field" data-testid="inspector-ship-group-field-mass">
|
<div class="field" data-testid="inspector-ship-group-field-mass">
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.mass")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.mass")}</dt>
|
||||||
<dd>{formatNumber(g.mass)}</dd>
|
<dd class="numeric">{formatFloat(g.mass)}</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -238,8 +237,8 @@ variant — for Phase 19 the inspector is intentionally read-only.
|
|||||||
data-testid="inspector-ship-group-field-coordinates"
|
data-testid="inspector-ship-group-field-coordinates"
|
||||||
>
|
>
|
||||||
<dt>{i18n.t("game.inspector.ship_group.field.coordinates")}</dt>
|
<dt>{i18n.t("game.inspector.ship_group.field.coordinates")}</dt>
|
||||||
<dd>
|
<dd class="numeric">
|
||||||
({formatNumber(selection.group.x)}, {formatNumber(selection.group.y)})
|
{formatFloat(selection.group.x)}, {formatFloat(selection.group.y)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
@@ -285,16 +284,20 @@ variant — for Phase 19 the inspector is intentionally read-only.
|
|||||||
}
|
}
|
||||||
.field dt {
|
.field dt {
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
.field dd {
|
.field dd {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.field dd.numeric,
|
||||||
|
.field dd .numeric {
|
||||||
|
font-family: var(--font-mono);
|
||||||
}
|
}
|
||||||
.hint {
|
.hint {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ modernize cost preview backed by `core.blockUpgradeCost`.
|
|||||||
type ShipGroupUpgradeTech,
|
type ShipGroupUpgradeTech,
|
||||||
} from "../../../sync/order-types";
|
} from "../../../sync/order-types";
|
||||||
import { validateEntityName } from "$lib/util/entity-name";
|
import { validateEntityName } from "$lib/util/entity-name";
|
||||||
|
import { formatFloat } from "$lib/util/number-format";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
group: ReportLocalShipGroup;
|
group: ReportLocalShipGroup;
|
||||||
@@ -675,9 +676,6 @@ modernize cost preview backed by `core.blockUpgradeCost`.
|
|||||||
CARGO: "game.inspector.ship_group.action.tech.cargo",
|
CARGO: "game.inspector.ship_group.action.tech.cargo",
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatNumber(value: number): string {
|
|
||||||
return value.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="actions" data-testid="inspector-ship-group-actions">
|
<section class="actions" data-testid="inspector-ship-group-actions">
|
||||||
@@ -973,7 +971,7 @@ modernize cost preview backed by `core.blockUpgradeCost`.
|
|||||||
{i18n.t("game.inspector.ship_group.action.modernize.cost_unavailable")}
|
{i18n.t("game.inspector.ship_group.action.modernize.cost_unavailable")}
|
||||||
{:else}
|
{:else}
|
||||||
{i18n.t("game.inspector.ship_group.action.modernize.cost", {
|
{i18n.t("game.inspector.ship_group.action.modernize.cost", {
|
||||||
cost: formatNumber(modernizeCostPreview),
|
cost: formatFloat(modernizeCostPreview),
|
||||||
})}
|
})}
|
||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
@@ -1148,7 +1146,7 @@ modernize cost preview backed by `core.blockUpgradeCost`.
|
|||||||
}
|
}
|
||||||
.action {
|
.action {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
padding: 0.2rem 0.55rem;
|
padding: 0.2rem 0.55rem;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
@@ -1177,7 +1175,7 @@ modernize cost preview backed by `core.blockUpgradeCost`.
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.2rem;
|
gap: 0.2rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
.form input[type="number"],
|
.form input[type="number"],
|
||||||
@@ -1196,7 +1194,7 @@ modernize cost preview backed by `core.blockUpgradeCost`.
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
.form .destination-readonly .label {
|
.form .destination-readonly .label {
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
@@ -1208,7 +1206,7 @@ modernize cost preview backed by `core.blockUpgradeCost`.
|
|||||||
}
|
}
|
||||||
.form-actions button {
|
.form-actions button {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
padding: 0.25rem 0.65rem;
|
padding: 0.25rem 0.65rem;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
@@ -1226,18 +1224,18 @@ modernize cost preview backed by `core.blockUpgradeCost`.
|
|||||||
}
|
}
|
||||||
.preview {
|
.preview {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
.warning {
|
.warning {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
color: var(--color-warning);
|
color: var(--color-warning);
|
||||||
}
|
}
|
||||||
.locked {
|
.locked {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.4rem 0.55rem;
|
padding: 0.4rem 0.55rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
background: var(--color-surface-overlay);
|
background: var(--color-surface-overlay);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// Shared numeric formatters for inspector and report views.
|
||||||
|
//
|
||||||
|
// Engine wire-format for floats is `Fixed3` quantised
|
||||||
|
// (pkg/model/report/report.go). The UI renders them as a fixed 3-decimal
|
||||||
|
// string with neither thousand separators nor locale-aware grouping —
|
||||||
|
// values stay column-aligned across rows and never reflow when the locale
|
||||||
|
// switches mid-session. Integer counts (planet count, ship `count`) are
|
||||||
|
// rendered with zero fractional digits to match their wire shape
|
||||||
|
// (uint16 / uint64). The convention mirrors the calculator-tab view
|
||||||
|
// (`useGrouping: false`, 3 decimals) so inspector and report tables read
|
||||||
|
// the same way as the calculator panel.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* formatFloat renders an engine-emitted `Float` with three fractional
|
||||||
|
* digits and no thousand separators. Used for tech levels, stockpiles,
|
||||||
|
* coordinates, range, mass, votes — every report payload that is not
|
||||||
|
* an integer count or a `[0, 1]` fraction.
|
||||||
|
*/
|
||||||
|
export function formatFloat(value: number): string {
|
||||||
|
return value.toFixed(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* formatInt renders an integer-ish count (planet count, ship count,
|
||||||
|
* etc.) with zero fractional digits and no thousand separators.
|
||||||
|
*/
|
||||||
|
export function formatInt(value: number): string {
|
||||||
|
return value.toFixed(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* formatPercent renders a `[0, 1]` fraction as a one-decimal percent
|
||||||
|
* without a `%` suffix — the column header carries the unit. 0.1 %
|
||||||
|
* precision (the third decimal of the underlying float) matches the
|
||||||
|
* project-wide "three decimal digits" convention used elsewhere by
|
||||||
|
* `formatFloat`.
|
||||||
|
*/
|
||||||
|
export function formatPercent(fraction: number): string {
|
||||||
|
return (fraction * 100).toFixed(1);
|
||||||
|
}
|
||||||
@@ -319,7 +319,7 @@ test("create / list / delete science via the table + designer", async ({
|
|||||||
await expect(page.getByTestId("sciences-table")).toBeVisible();
|
await expect(page.getByTestId("sciences-table")).toBeVisible();
|
||||||
const row = page.getByTestId("sciences-row");
|
const row = page.getByTestId("sciences-row");
|
||||||
await expect(row).toHaveAttribute("data-name", "FirstStep");
|
await expect(row).toHaveAttribute("data-name", "FirstStep");
|
||||||
await expect(page.getByTestId("sciences-cell-drive")).toHaveText("25");
|
await expect(page.getByTestId("sciences-cell-drive")).toHaveText("25.0");
|
||||||
|
|
||||||
// The auto-sync round-trip lands as applied.
|
// The auto-sync round-trip lands as applied.
|
||||||
await page.getByTestId("sidebar-tab-order").click();
|
await page.getByTestId("sidebar-tab-order").click();
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ test("create / list / delete ship class via the table + calculator", async ({
|
|||||||
await expect(page.getByTestId("ship-classes-table")).toBeVisible();
|
await expect(page.getByTestId("ship-classes-table")).toBeVisible();
|
||||||
const row = page.getByTestId("ship-classes-row");
|
const row = page.getByTestId("ship-classes-row");
|
||||||
await expect(row).toHaveAttribute("data-name", "Drone");
|
await expect(row).toHaveAttribute("data-name", "Drone");
|
||||||
await expect(page.getByTestId("ship-classes-cell-drive")).toHaveText("1");
|
await expect(page.getByTestId("ship-classes-cell-drive")).toHaveText("1.000");
|
||||||
|
|
||||||
// The auto-sync round-trip lands as applied.
|
// The auto-sync round-trip lands as applied.
|
||||||
await page.getByTestId("sidebar-tab-order").click();
|
await page.getByTestId("sidebar-tab-order").click();
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ describe("planet inspector", () => {
|
|||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
ui.getByTestId("inspector-planet-field-coordinates"),
|
ui.getByTestId("inspector-planet-field-coordinates"),
|
||||||
).toHaveTextContent("(100.25, 200)");
|
).toHaveTextContent("100.250, 200.000");
|
||||||
expect(ui.getByTestId("inspector-planet-field-size")).toHaveTextContent(
|
expect(ui.getByTestId("inspector-planet-field-size")).toHaveTextContent(
|
||||||
"size",
|
"size",
|
||||||
);
|
);
|
||||||
@@ -240,7 +240,7 @@ describe("planet inspector", () => {
|
|||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
ui.getByTestId("inspector-planet-field-coordinates"),
|
ui.getByTestId("inspector-planet-field-coordinates"),
|
||||||
).toHaveTextContent("(1,234, -5)");
|
).toHaveTextContent("1234.000, -5.000");
|
||||||
expect(ui.queryByTestId("inspector-planet-field-size")).toBeNull();
|
expect(ui.queryByTestId("inspector-planet-field-size")).toBeNull();
|
||||||
expect(ui.queryByTestId("inspector-planet-field-natural_resources")).toBeNull();
|
expect(ui.queryByTestId("inspector-planet-field-natural_resources")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -175,18 +175,16 @@ describe("races table", () => {
|
|||||||
expect(rows).toHaveLength(1);
|
expect(rows).toHaveLength(1);
|
||||||
expect(rows[0]).toHaveAttribute("data-name", "Andori");
|
expect(rows[0]).toHaveAttribute("data-name", "Andori");
|
||||||
expect(ui.getByTestId("races-cell-name")).toHaveTextContent("Andori");
|
expect(ui.getByTestId("races-cell-name")).toHaveTextContent("Andori");
|
||||||
expect(ui.getByTestId("races-cell-drive")).toHaveTextContent("25");
|
// drive/weapons/shields/cargo are tech LEVELS from the engine
|
||||||
expect(ui.getByTestId("races-cell-weapons")).toHaveTextContent("50");
|
// (see F8-08 bugfix); rendered as 3-decimal Floats, not percents.
|
||||||
expect(ui.getByTestId("races-cell-shields")).toHaveTextContent("75");
|
expect(ui.getByTestId("races-cell-drive")).toHaveTextContent("0.250");
|
||||||
expect(ui.getByTestId("races-cell-cargo")).toHaveTextContent("100");
|
expect(ui.getByTestId("races-cell-weapons")).toHaveTextContent("0.500");
|
||||||
expect(ui.getByTestId("races-cell-population")).toHaveTextContent(
|
expect(ui.getByTestId("races-cell-shields")).toHaveTextContent("0.750");
|
||||||
/12[,\s]345/,
|
expect(ui.getByTestId("races-cell-cargo")).toHaveTextContent("1.000");
|
||||||
);
|
expect(ui.getByTestId("races-cell-population")).toHaveTextContent("12345");
|
||||||
expect(ui.getByTestId("races-cell-industry")).toHaveTextContent(
|
expect(ui.getByTestId("races-cell-industry")).toHaveTextContent("6789");
|
||||||
/6[,\s]?789/,
|
|
||||||
);
|
|
||||||
expect(ui.getByTestId("races-cell-planets")).toHaveTextContent("4");
|
expect(ui.getByTestId("races-cell-planets")).toHaveTextContent("4");
|
||||||
expect(ui.getByTestId("races-cell-votes")).toHaveTextContent("3.5");
|
expect(ui.getByTestId("races-cell-votes")).toHaveTextContent("3.500");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("filters rows by case-insensitive name match", async () => {
|
test("filters rows by case-insensitive name match", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user