diff --git a/deps/web-components b/deps/web-components index 1a6c4bdd..818fac81 160000 --- a/deps/web-components +++ b/deps/web-components @@ -1 +1 @@ -Subproject commit 1a6c4bdde37033993d1c8d42d81dca81cffa1005 +Subproject commit 818fac81cf8a7ca318701198ea380b64babad7ca diff --git a/package.json b/package.json index 3b49004b..7d03cc9a 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,7 @@ "jose": "^5.1.2", "mapbox-gl": "^2.15.0", "new-github-issue-url": "^1.0.0", + "part-regex": "^0.1.2", "react": "^18.3.0", "react-cookie": "^3.0.4", "react-dom": "^18.3.0", diff --git a/pages/dev/map/rockd-strabospot/+Page.client.ts b/pages/dev/map/rockd-strabospot/+Page.client.ts index ac7e74be..3624f15b 100644 --- a/pages/dev/map/rockd-strabospot/+Page.client.ts +++ b/pages/dev/map/rockd-strabospot/+Page.client.ts @@ -21,25 +21,66 @@ import styles from "./main.module.sass"; import { DetailsPanel } from "./details-panel"; import Legend from "./legend-text.mdx"; import { useInDarkMode } from "@macrostrat/ui-components"; +import { usePageContext } from "vike-react/usePageContext"; +import { navigate } from "vike/client/router"; +import { formatCoordForZoomLevel } from "@macrostrat/mapbox-utils"; const h = hyper.styled(styles); mapboxgl.accessToken = mapboxAccessToken; +const baseURL = "/dev/map/rockd-strabospot"; + export function Page() { + const { routeParams } = usePageContext(); + const { lng, lat } = routeParams; + + let initialPosition = null; + if (lng != null && lat != null) { + try { + // Try to get the zoom level from the hash + const q = new URLSearchParams(window.location.hash.slice(1)); + const zoom = q.has("z") ? parseInt(q.get("z")) : 7; + + initialPosition = { lng: parseFloat(lng), lat: parseFloat(lat), zoom }; + } catch { + console.error("Failed to parse initial position"); + } + } + const style = useRockdStraboSpotStyle(); const inDarkMode = useInDarkMode(); const [isOpen, setOpen] = useState(true); const [inspectPosition, setInspectPosition] = - useState(null); + useState(initialPosition); const [data, setData] = useState(null); - const onSelectPosition = useCallback((position: mapboxgl.LngLat) => { - setInspectPosition(position); - }, []); + const onSelectPosition = useCallback( + ( + position: mapboxgl.LngLat | null, + event: Event | undefined, + map: mapboxgl.Map | undefined + ) => { + setInspectPosition(position); + let newPathname = "/dev/map/rockd-strabospot"; + let currentPathname = window.location.pathname; + + if (map != null && position != null) { + const z = map.getZoom() ?? 7; + const lng = formatCoordForZoomLevel(position.lng, z); + const lat = formatCoordForZoomLevel(position.lat, z); + newPathname += `/loc/${lng}/${lat}`; + } + newPathname += buildHashParams(map, position); + if (currentPathname !== newPathname) { + window.history.pushState({}, "", newPathname); + } + }, + [] + ); return h( MapAreaContainer, @@ -61,7 +102,7 @@ export function Page() { detailPanel: h(DetailsPanel, { position: inspectPosition, onClose() { - setInspectPosition(null); + onSelectPosition(null, null, null); }, nearbyFeatures: data, }), @@ -73,8 +114,11 @@ export function Page() { style, projection: { name: "globe" }, mapboxToken: mapboxAccessToken, - mapPosition: null, // Have to set map position to null for bounds to work + mapPosition: initialPosition, bounds: [-125, 24, -66, 49], + onMapMoved(mapPosition, map) { + window.location.hash = buildHashParams(map, inspectPosition); + }, }, [ h(FeatureSelectionHandler, { @@ -90,3 +134,12 @@ export function Page() { ) ); } + +function buildHashParams(map, selectedPosition) { + if (selectedPosition == null) return ""; + const z = map.getZoom(); + // Parse hash and add zoom level + let q = new URLSearchParams(window.location.hash.slice(1)); + q.set("z", z.toFixed(0)); + return "#" + q.toString(); +} diff --git a/pages/dev/map/rockd-strabospot/+route.ts b/pages/dev/map/rockd-strabospot/+route.ts new file mode 100644 index 00000000..e6af392c --- /dev/null +++ b/pages/dev/map/rockd-strabospot/+route.ts @@ -0,0 +1,35 @@ +import { partRegex } from "part-regex"; +import { redirect } from "vike/abort"; + +const prefix = "/dev/map/rockd-strabospot"; + +const lngLatRegex = /(-?\d+\.\d+)/; + +const routeRegex = partRegex`/loc/${lngLatRegex}/${lngLatRegex}`; + +export function route(pageContext) { + const url = pageContext.urlPathname; + + if (!url.startsWith(prefix)) return false; + + const suffix = url.slice(prefix.length); + + if (suffix == "" || suffix == "/") { + return { + routeParams: {}, + }; + } + + if (suffix.match(routeRegex)) { + const [_, lng, lat] = suffix.match(routeRegex); + return { + routeParams: { + lng, + lat, + }, + }; + } + + // Redirect to the root of this route + throw redirect(prefix); +} diff --git a/pages/dev/map/rockd-strabospot/sidebar-data.ts b/pages/dev/map/rockd-strabospot/sidebar-data.ts index 98834f2c..32449f9a 100644 --- a/pages/dev/map/rockd-strabospot/sidebar-data.ts +++ b/pages/dev/map/rockd-strabospot/sidebar-data.ts @@ -30,6 +30,10 @@ async function processSpotsData(nearbyFeatures) { .filter((d) => d.source === "notableSpots") .slice(0, 5); + if (firstFewSpots.length == 0) { + return []; + } + const ids = firstFewSpots.map((d) => d.properties.id); const res = await pg.from("dataset").select("data").in("id", ids); const { data, error } = res; diff --git a/yarn.lock b/yarn.lock index 1540a579..450c327c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6438,6 +6438,7 @@ __metadata: jose: "npm:^5.1.2" mapbox-gl: "npm:^2.15.0" new-github-issue-url: "npm:^1.0.0" + part-regex: "npm:^0.1.2" prettier: "npm:^2.7.1" react: "npm:^18.3.0" react-arborist: "npm:^3.4.0" @@ -27994,6 +27995,13 @@ __metadata: languageName: node linkType: hard +"part-regex@npm:^0.1.2": + version: 0.1.2 + resolution: "part-regex@npm:0.1.2" + checksum: 10/e01c9532963914b8ac234ae4362a0914b24820635d8815014f745ce30fe7f035ab6618784e9a900127f8359fea8bc2c6c7cc65ddf429baa179621e56e7dd717e + languageName: node + linkType: hard + "pascal-case@npm:^3.1.2": version: 3.1.2 resolution: "pascal-case@npm:3.1.2"