feat(ui): F8-10 — tables planets / ship-groups / fleets, ship-classes delete guard (#53)
Lights up three previously-stubbed table active views and tightens the
existing one:
- table-planets: 4 kind checkboxes (own / foreign / uninhabited /
unknown) + race dropdown that filters the foreign slice; row click
selects + centres the planet on the map.
- table-ship-groups: local + foreign groups in one grid, owner
checkboxes, planet dropdown (destination OR origin), class
dropdown; on-planet click focuses the destination planet, in-space
click focuses the ship group itself (camera follows interpolated
position).
- table-fleets: own fleets only with the shared planet dropdown;
on-planet click focuses the planet, in-space click centres the
camera on the interpolated fleet position without altering the
selection (no fleet variant in Selected).
- table-ship-classes: per-row Delete is disabled with a count tooltip
while at least one local ship group references the class. The
engine refuses the removal anyway; the UI pre-empts the surface.
Wires the click → map flow through a transient `SelectionStore.focus`
/ `focusPoint` channel that `map.svelte` consumes once on mount —
in-memory only, so an F5 does not re-centre.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,21 @@ data fetching is performed here — the layout is responsible.
|
||||
);
|
||||
const reportLoaded = $derived(rendered?.report !== null && rendered?.report !== undefined);
|
||||
|
||||
// inUseCounts is a derived index from class name → number of player
|
||||
// ship groups currently referencing it. The engine refuses
|
||||
// `removeShipClass` while any such group exists, so the table
|
||||
// pre-emptively disables the per-row Delete affordance instead of
|
||||
// surfacing a server-side rejection. The map is rebuilt whenever
|
||||
// the rendered report changes; an empty report yields an empty map
|
||||
// and every Delete stays enabled.
|
||||
const inUseCounts = $derived.by(() => {
|
||||
const map = new Map<string, number>();
|
||||
for (const group of rendered?.report?.localShipGroups ?? []) {
|
||||
map.set(group.class, (map.get(group.class) ?? 0) + 1);
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
const filtered = $derived.by(() => {
|
||||
const needle = filter.trim().toLowerCase();
|
||||
if (needle === "") return localShipClass;
|
||||
@@ -123,6 +138,13 @@ data fetching is performed here — the layout is responsible.
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
function deleteTooltip(count: number): string {
|
||||
if (count === 0) return "";
|
||||
return i18n.t("game.table.ship_classes.action.delete.in_use", {
|
||||
count: String(count),
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<section
|
||||
@@ -189,6 +211,7 @@ data fetching is performed here — the layout is responsible.
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each sorted as cls (cls.name)}
|
||||
{@const inUse = inUseCounts.get(cls.name) ?? 0}
|
||||
<tr
|
||||
data-testid="ship-classes-row"
|
||||
data-name={cls.name}
|
||||
@@ -215,6 +238,9 @@ data fetching is performed here — the layout is responsible.
|
||||
type="button"
|
||||
class="delete"
|
||||
data-testid="ship-classes-delete"
|
||||
data-in-use={inUse}
|
||||
disabled={inUse > 0 || draft === undefined}
|
||||
title={deleteTooltip(inUse)}
|
||||
onclick={() => void deleteShipClass(cls.name)}
|
||||
>
|
||||
{i18n.t("game.table.ship_classes.action.delete")}
|
||||
|
||||
Reference in New Issue
Block a user