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:
@@ -56,6 +56,20 @@ export const SELECTION_CONTEXT_KEY = Symbol("selection");
|
||||
export class SelectionStore {
|
||||
selected: Selected | null = $state(null);
|
||||
|
||||
// pendingFocus is a transient one-shot request to centre the map
|
||||
// camera on a selection. F8-10 tables raise it together with the
|
||||
// view switch to map; map.svelte consumes it once on mount (see
|
||||
// `consumePendingFocus`) and clears it. Held in memory only — not
|
||||
// persisted, so an F5 after a click-through does not re-centre.
|
||||
#pendingFocus: Selected | null = $state(null);
|
||||
|
||||
// pendingCenter is the coord-only sibling of pendingFocus: it
|
||||
// asks map.svelte to centre on a world point without touching
|
||||
// the selection. F8-10 uses it for in-space fleet rows (the
|
||||
// `Selected` union has no "fleet" variant, but the user still
|
||||
// expects the camera to find them). Also one-shot and transient.
|
||||
#pendingCenter: { x: number; y: number } | null = $state(null);
|
||||
|
||||
private destroyed = false;
|
||||
|
||||
/**
|
||||
@@ -77,10 +91,57 @@ export class SelectionStore {
|
||||
this.selected = { kind: "shipGroup", ref };
|
||||
}
|
||||
|
||||
/**
|
||||
* focus sets the active selection to `target` and queues a one-shot
|
||||
* camera-centre request for whichever next mount of the map view
|
||||
* picks it up via `consumePendingFocus`. Used by the F8-10 tables to
|
||||
* navigate from a row click to the map. A no-op once the store has
|
||||
* been disposed.
|
||||
*/
|
||||
focus(target: Selected): void {
|
||||
if (this.destroyed) return;
|
||||
this.selected = target;
|
||||
this.#pendingFocus = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* consumePendingFocus returns the queued focus target (if any) and
|
||||
* clears it in the same call. Designed for `map.svelte` to invoke
|
||||
* once after the renderer mounts.
|
||||
*/
|
||||
consumePendingFocus(): Selected | null {
|
||||
const target = this.#pendingFocus;
|
||||
this.#pendingFocus = null;
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* focusPoint queues a one-shot camera-centre on a free-form world
|
||||
* coordinate, without altering selection. Used by the fleets table
|
||||
* when the user clicks an in-space fleet (there is no `"fleet"`
|
||||
* variant in `Selected`, but the camera should still find it).
|
||||
* A no-op once the store has been disposed.
|
||||
*/
|
||||
focusPoint(x: number, y: number): void {
|
||||
if (this.destroyed) return;
|
||||
this.#pendingCenter = { x, y };
|
||||
}
|
||||
|
||||
/**
|
||||
* consumePendingCenter returns the queued centre point (if any)
|
||||
* and clears it.
|
||||
*/
|
||||
consumePendingCenter(): { x: number; y: number } | null {
|
||||
const point = this.#pendingCenter;
|
||||
this.#pendingCenter = null;
|
||||
return point;
|
||||
}
|
||||
|
||||
/**
|
||||
* clear drops the current selection. The mobile sheet's close
|
||||
* button calls this; otherwise selection persists across active-
|
||||
* view switches.
|
||||
* view switches. Does not affect any queued pending focus — that
|
||||
* is a transient delivery channel, not selection state.
|
||||
*/
|
||||
clear(): void {
|
||||
if (this.destroyed) return;
|
||||
@@ -90,5 +151,7 @@ export class SelectionStore {
|
||||
dispose(): void {
|
||||
this.destroyed = true;
|
||||
this.selected = null;
|
||||
this.#pendingFocus = null;
|
||||
this.#pendingCenter = null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user