Live wall: pinned/docked cameras stop when their provider is switched off; admin-only empty-state CTA
- LiveView: localStorage pins carry image URLs, so they rendered (without credit) after an admin disabled the provider. Pins now show only while their provider is serving (server-26#198 licence gating). - MapView: pinned/docked map players drop out once their provider leaves the serving list; credits are kept from the last answer so docked players keep attribution while the overlay is hidden. - WallPanels: the "no camera provider enabled" link to /admin was a dead CTA for non-admins (bounced to /). Link shown to admins only, deep-linked to /admin#cameras; the admin page now honours a tab hash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
af00139079
commit
e6b1581139
@@ -145,6 +145,10 @@ export function LiveView() {
|
||||
}, [pointsById, nodes]);
|
||||
const { data: cams, loaded: camsLoaded } = useCamerasInBbox(regionBox);
|
||||
const feeds = useMemo(() => new Map(cams.providers.map((p) => [p.id, p])), [cams.providers]);
|
||||
// Pins live in this browser and carry their image URLs, so a pin must not
|
||||
// outlive its provider: once an admin switches a provider off (licence
|
||||
// gating, server-26#198) its pinned cameras stop rendering until it is back.
|
||||
const servingPinned = useMemo(() => pinned.filter((c) => feeds.has(c.provider)), [pinned, feeds]);
|
||||
const nearById = useMemo(() => {
|
||||
const m = new Map<string, NearCamera[]>();
|
||||
pointsById.forEach((pts, id) => m.set(id, nearestCameras(pts, cams.cameras)));
|
||||
@@ -158,19 +162,19 @@ export function LiveView() {
|
||||
const selectedPoints = selected ? pointsById.get(selected.incident_id) ?? [] : [];
|
||||
const near = selected ? nearById.get(selected.incident_id) ?? [] : [];
|
||||
const drillCam: Camera | null = link.cam
|
||||
? cams.cameras.find((c) => c.id === link.cam) ?? pinned.find((c) => c.id === link.cam) ?? null
|
||||
? cams.cameras.find((c) => c.id === link.cam) ?? servingPinned.find((c) => c.id === link.cam) ?? null
|
||||
: null;
|
||||
|
||||
// Region wall: pinned first, then the nearest camera to each major/moderate incident.
|
||||
const wallCameras = useMemo(() => {
|
||||
const out: { cam: Camera; km?: number }[] = pinned.map((cam) => ({ cam }));
|
||||
const out: { cam: Camera; km?: number }[] = servingPinned.map((cam) => ({ cam }));
|
||||
for (const inc of sorted) {
|
||||
if (severityRank(inc.severity) < 2) continue;
|
||||
const first = nearById.get(inc.incident_id)?.[0];
|
||||
if (first && !out.some((o) => o.cam.id === first.cam.id)) out.push({ cam: first.cam, km: first.km });
|
||||
}
|
||||
return out.slice(0, 6);
|
||||
}, [pinned, sorted, nearById]);
|
||||
}, [servingPinned, sorted, nearById]);
|
||||
|
||||
// ── Navigation ────────────────────────────────────────────────────────────
|
||||
const selectIncident = useCallback((id: string) => setLink({ inc: id, cam: null, tour: false }), [setLink]);
|
||||
|
||||
Reference in New Issue
Block a user