feat(ui): error & state UX — error surface, view states, map selection, sheet gestures (F4)
- lib/error/: classify any caught error into a stable ErrorKind from the transport signal (HTTP status / Connect Code / fetch TypeError / navigator.onLine); map to translated error.* messages via reportError (sticky Retry toast for retryable kinds) or errorMessageKey (inline). Mail compose now surfaces the translated 403/error inline. - lib/ui/view-state.svelte: shared loading/empty/error placeholder with the right live-region role + optional action; entity tables (races/sciences/ship-classes) migrated, rest adopt incrementally. - map/selection-ring.ts: accent ring around the selected planet, fed into the map buildExtras alongside the reach circles. - lib/ui/sheet-dismiss.ts: tap-outside + drag-handle swipe-down dismissal for the planet/ship-group bottom-sheets (hand-rolled pointer events). Tests: error, view-state, selection-ring, sheet-dismiss (761 total). Docs: ui/docs/error-state-ux.md (+ index); F4 marked done. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
// Selected-planet marker. When the SelectionStore holds a planet, the
|
||||
// map draws one accent ring tight around it so the current selection is
|
||||
// visible on the canvas itself (the inspector/sheet show the detail).
|
||||
// Ship-group selection is intentionally not ringed here — groups are
|
||||
// addressed by report index and have no single stable map coordinate.
|
||||
|
||||
import type { CirclePrim } from "./world";
|
||||
|
||||
/** Planet marker radius in world units; mirrors `battle-markers.ts`. */
|
||||
const PLANET_RADIUS_WORLD = 6;
|
||||
/** The ring sits just outside the marker (and the bombing ring at +3). */
|
||||
const SELECTION_RING_RADIUS = PLANET_RADIUS_WORLD + 4;
|
||||
|
||||
export const SELECTION_RING_COLOR = 0x6d8cff;
|
||||
/** High-bit prefix so the ring id never collides with planet numbers,
|
||||
* route lines, reach rings (`0xb…`), or battle markers. */
|
||||
export const SELECTION_RING_ID = 0xc0000000;
|
||||
/** Below interactive primitives so it never wins a click. */
|
||||
const SELECTION_RING_PRIORITY = 0;
|
||||
|
||||
/**
|
||||
* computeSelectionRing returns one ring primitive centred on the selected
|
||||
* planet, or `null` when nothing (or a non-planet) is selected or the
|
||||
* planet is absent from the current report.
|
||||
*/
|
||||
export function computeSelectionRing(
|
||||
planets: ReadonlyArray<{ number: number; x: number; y: number }>,
|
||||
selectedPlanetId: number | null,
|
||||
): CirclePrim | null {
|
||||
if (selectedPlanetId === null) return null;
|
||||
const planet = planets.find((p) => p.number === selectedPlanetId);
|
||||
if (planet === undefined) return null;
|
||||
return {
|
||||
kind: "circle",
|
||||
id: SELECTION_RING_ID,
|
||||
priority: SELECTION_RING_PRIORITY,
|
||||
hitSlopPx: 0,
|
||||
x: planet.x,
|
||||
y: planet.y,
|
||||
radius: SELECTION_RING_RADIUS,
|
||||
style: {
|
||||
strokeColor: SELECTION_RING_COLOR,
|
||||
strokeAlpha: 0.95,
|
||||
strokeWidthPx: 1.5,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user