From d726e2659dde34566c8e61ea2b256d1cdfe56ade Mon Sep 17 00:00:00 2001 From: MrAlders0n Date: Sun, 31 May 2026 18:20:38 -0400 Subject: [PATCH] add RegionUrlSync to manage shareable region state in URL --- src/App.tsx | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 01d7fec..2dc323c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -46,6 +46,32 @@ function RegionWatcher({ wsManager: mgr }: { wsManager: WsManager }) { return null; } +// Mirror the active region into the URL (?iata=) so the address bar is always shareable — on a +// dropdown change and on load (including a region restored from localStorage, which users wouldn't +// otherwise know was shareable). "*" (All) clears it; any legacy ?region is folded into ?iata. The +// guard skips redundant writes (and any setSearchParams feedback loop); replace keeps it out of history. +function RegionUrlSync() { + const region = useRegion(); + const [searchParams, setSearchParams] = useSearchParams(); + + useEffect(() => { + const desired = region === "*" ? null : region; + if (searchParams.get("iata") === desired && !searchParams.has("region")) return; + setSearchParams( + (prev) => { + const next = new URLSearchParams(prev); + if (desired === null) next.delete("iata"); + else next.set("iata", desired); + next.delete("region"); + return next; + }, + { replace: true }, + ); + }, [region, searchParams, setSearchParams]); + + return null; +} + // Drop the shared node selection when the region changes, so the detail panel doesn't keep showing a // node that's no longer in the re-queried map/table. Lives inside RegionProvider so it can read useRegion. function SelectionResetOnRegion({ onRegionChange }: { onRegionChange: () => void }) { @@ -70,7 +96,14 @@ const DRAWER_STORAGE_KEY = "beacon-analyzer-open"; function AppInner() { const [searchParams, setSearchParams] = useSearchParams(); const [activeTab, setActiveTab] = useState(() => searchParams.get("tab") ?? "Packets"); - const initialRegion = searchParams.get("region") ?? localStorage.getItem("beacon-region") ?? "*"; + // ?iata is the canonical, shareable param; ?region is honored as a legacy fallback. || (not ??) + // so an empty ?iata= falls through instead of sticking as "". URL codes are upper-cased to match + // the dropdown's IATA codes (so a shared/typed ?iata=yyz still resolves). + const initialRegion = + searchParams.get("iata")?.toUpperCase() || + searchParams.get("region")?.toUpperCase() || + localStorage.getItem("beacon-region") || + "*"; const [analyzerHash, setAnalyzerHash] = useState(() => searchParams.get("hash")); const [selectedObservationId, setSelectedObservationId] = useState(null); @@ -133,6 +166,7 @@ function AppInner() { return ( +