Files
server-26/drb-frontend/lib/wallPrefs.ts
Logan CusanoandClaude Opus 5.5 afd2bc463d Live wall: saved views in the URL hash, per-viewer memory, kiosk mode (#234)
- liveWall.ts: WallLink carries layers (short stable keys), z/lat/lon,
  base, tourMs, kiosk and an f.<name> filter pass-through for #235;
  parse/serialise stay pure and round-trip; old inc/cam/tour links parse.
- MapView: overlays/basemap/viewport come from initialLayers/initialBase/
  initialView (defaults unchanged); onViewStateChange reports layer, base
  and region-view changes (debounced). Initially-checked overlays now start
  "shown" (react-leaflet adds a checked overlay before the control can fire
  overlayadd). Closing an incident flies back to the pre-drill view (#137).
- wallPrefs.ts: per-browser last layers/base/view in localStorage, every
  access wrapped; precedence hash > remembered > defaults.
- LiveView: one effect mirrors wall + map state into the hash; hash edits
  remount the map with the new view; Copy view link; configurable tour.
- Kiosk (?kiosk=1 or #kiosk): no nav, fullscreen button (auto on first
  touch), Screen Wake Lock re-acquired on visibility, reconnect banner
  (offline / useCalls error) with backoff reload, 18-20 px type via a
  data-kiosk attribute and Tailwind arbitrary variants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:58:22 -04:00

52 lines
1.9 KiB
TypeScript

/**
* Per-viewer memory of the live wall's map view (server-26#234): the last
* overlays, basemap and viewport this browser used. A convenience only —
* every access is wrapped, and a blocked/empty store (private window, cleared
* site data) simply yields {} so the wall falls back to its defaults.
*
* Precedence when the wall opens: URL hash > this store > defaults
* (resolveWallView below). Nothing here is shared with other viewers.
*/
import {
DEFAULT_BASE, DEFAULT_LAYERS, isBaseKey, normaliseLayers, parseViewport,
type WallView,
} from "@/lib/liveWall";
const KEY = "drb.wall.view.v1";
export function loadWallPrefs(): WallView {
try {
const raw = window.localStorage.getItem(KEY);
if (!raw) return {};
const v: unknown = JSON.parse(raw);
if (!v || typeof v !== "object") return {};
const o = v as Record<string, unknown>;
const out: WallView = {};
if (Array.isArray(o.layers)) out.layers = normaliseLayers(o.layers.filter((x): x is string => typeof x === "string"));
if (typeof o.base === "string" && isBaseKey(o.base)) out.base = o.base;
const view = o.view && typeof o.view === "object" ? (o.view as Record<string, unknown>) : null;
const vp = view ? parseViewport(view.z, view.lat, view.lon) : undefined;
if (vp) out.view = vp;
return out;
} catch {
return {};
}
}
export function saveWallPrefs(v: WallView): void {
try {
window.localStorage.setItem(KEY, JSON.stringify({ layers: v.layers, base: v.base, view: v.view }));
} catch {
/* storage blocked or full — the URL hash still carries the view */
}
}
/** Field-by-field: hash wins, then what this browser remembers, then defaults. */
export function resolveWallView(hash: WallView, stored: WallView): Required<Pick<WallView, "layers" | "base">> & Pick<WallView, "view"> {
return {
layers: hash.layers ?? stored.layers ?? DEFAULT_LAYERS,
base: hash.base ?? stored.base ?? DEFAULT_BASE,
view: hash.view ?? stored.view,
};
}