From bf7d8d373c11cd7a6e1487175147c9a84e775a98 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 27 Feb 2026 10:09:16 -0800 Subject: map updates --- src/components/About.svelte | 2 +- src/components/Footer.svelte | 25 +++--- src/components/LandingHero.svelte | 4 +- src/components/LocationsMap.svelte | 168 ++++++++++++++++++++++++------------- 4 files changed, 124 insertions(+), 75 deletions(-) (limited to 'src/components') diff --git a/src/components/About.svelte b/src/components/About.svelte index 874ec8db..b2f376ed 100644 --- a/src/components/About.svelte +++ b/src/components/About.svelte @@ -60,7 +60,7 @@ -
+

{$t("about.locations.header")}

{@html $t("about.locations.description")} diff --git a/src/components/Footer.svelte b/src/components/Footer.svelte index 8e80151c..0bdee16e 100644 --- a/src/components/Footer.svelte +++ b/src/components/Footer.svelte @@ -9,7 +9,6 @@ titleID: "nostr", }, { url: "https://t.me/coinoswallet", titleID: "telegram" }, - { url: "https://linktr.ee/coinos.io", titleID: "linktree" }, ]; const companyLinks = [ @@ -20,21 +19,18 @@

diff --git a/src/components/LandingHero.svelte b/src/components/LandingHero.svelte index dae0bf2b..44def229 100644 --- a/src/components/LandingHero.svelte +++ b/src/components/LandingHero.svelte @@ -27,11 +27,11 @@ {$t("landing.subheader")} -
+
diff --git a/src/components/LocationsMap.svelte b/src/components/LocationsMap.svelte index 185a6ae5..dbc56375 100644 --- a/src/components/LocationsMap.svelte +++ b/src/components/LocationsMap.svelte @@ -4,11 +4,12 @@ import Popup from "$comp/Popup.svelte"; import { back } from "$lib/utils"; - let { locations } = $props(); let map: any; + let maplibreRef: any; + let mapReady = $state(false); let mapContainer: any = $state(), mapWrapper: any = $state(); - let markers: any[] = []; + let markers: any[] = $state([]); let search: string = $state(""); let clearSearch = (e: any) => (search = ""); @@ -20,13 +21,10 @@ if (mapWrapper.requestFullscreen) { mapWrapper.requestFullscreen(); } else if (mapWrapper.mozRequestFullScreen) { - /* Firefox */ mapWrapper.mozRequestFullScreen(); } else if (mapWrapper.webkitRequestFullscreen) { - /* Chrome, Safari & Opera */ mapWrapper.webkitRequestFullscreen(); } else if (mapWrapper.msRequestFullscreen) { - /* IE/Edge */ mapWrapper.msRequestFullscreen(); } @@ -36,13 +34,10 @@ if (doc.exitFullscreen) { doc.exitFullscreen(); } else if (doc.mozCancelFullScreen) { - /* Firefox */ doc.mozCancelFullScreen(); } else if (doc.webkitExitFullscreen) { - /* Chrome, Safari and Opera */ doc.webkitExitFullscreen(); } else if (doc.msExitFullscreen) { - /* IE/Edge */ doc.msExitFullscreen(); } } @@ -84,16 +79,6 @@ await new Promise((r) => setTimeout(r, 2500)); if (!timeout) return; - // - // map.flyTo({ - // zoom: 10.5, - // center: marker.getLngLat(), - // essential: true, - // easing: function (t) { - // return 1 - Math.pow(1 - t, 3); - // }, - // duration: 1000, - // }); } let toggle = () => { @@ -159,8 +144,6 @@ for (let marker of markers) { let isInView = bounds.contains(marker.getLngLat()); - let { tags } = marker; - if (isInView) { inview.push(marker); } @@ -170,10 +153,98 @@ }, 200); let locationMarkers: any = {}; + let addedIds = new Set(); let popups: any = {}; let counter = 0; let maplibrePromise: any; + function addMarkers(locations: any[]) { + let maplibre = maplibreRef; + if (!map || !maplibre) return; + + locations.forEach((location) => { + if (location["deleted_at"]) return; + if (addedIds.has(location.id)) return; + addedIds.add(location.id); + + let { lat, lon, tags } = location["osm_json"]; + if (!(lat && lon)) return; + + let popupContainer = document.createElement("div"); + + mount(Popup, { + target: popupContainer, + props: { tags }, + }); + + let marker = new maplibre.Marker({ color: "#F7931A", scale: 0.65 }) + .setLngLat([lon, lat]) + .setPopup(new maplibre.Popup().setDOMContent(popupContainer)) + .addTo(map); + + marker.id = counter++; + + marker.getElement().addEventListener("click", () => { + let p = marker.getPopup(); + popups[marker.id] = p; + p.setDOMContent(popupContainer); + setTimeout(() => { + selected = marker; + setTimeout(scroll, 10); + }, 50); + p.on("close", () => { + selected = undefined; + delete popups[marker.id]; + }); + }); + + marker.tags = tags; + locationMarkers[location.id] = marker; + + markers.push(marker); + }); + + updateLabelVisibility(); + } + + let fetchController: AbortController | null = null; + + async function fetchNearby(lat: number, lon: number, radius: number) { + if (fetchController) fetchController.abort(); + fetchController = new AbortController(); + + try { + let params = new URLSearchParams({ + lat: String(lat), + lon: String(lon), + radius: String(Math.ceil(radius)), + count: "100", + }); + let res = await fetch(`/locations?${params}`, { signal: fetchController.signal }); + let data = await res.json(); + if (data.locations) addMarkers(data.locations); + } catch (e: any) { + if (e.name !== "AbortError") console.log("fetch nearby failed", e); + } + } + + function getViewportRadius() { + let bounds = map.getBounds(); + let center = map.getCenter(); + let ne = bounds.getNorthEast(); + + let dlat = ne.lat - center.lat; + let dlng = ne.lng - center.lng; + let km = Math.sqrt(dlat * dlat + dlng * dlng) * 111; + return Math.max(km, 10); + } + + let fetchOnMove = debounce(() => { + if (!map) return; + let center = map.getCenter(); + fetchNearby(center.lat, center.lng, getViewportRadius()); + }, 500); + const loadMaplibre = async () => { if ((window as any).maplibregl) return (window as any).maplibregl; if (maplibrePromise) return maplibrePromise; @@ -210,6 +281,8 @@ await tick(); if (!mapContainer) return; loadMaplibre().then((maplibre) => { + maplibreRef = maplibre; + map = new maplibre.Map({ container: mapContainer, style: "https://tiles.stadiamaps.com/styles/stamen_toner.json", @@ -217,51 +290,26 @@ zoom: 10.5, }); - map.on("load", () => { - locations.forEach((location) => { - if (location["deleted_at"]) return; + map.scrollZoom.setWheelZoomRate(1 / 60); + map.scrollZoom.setZoomRate(1 / 30); - let { lat, lon, tags } = location["osm_json"]; - if (!(lat && lon)) return; + map.on("load", () => { + mapReady = true; - let element = document.createElement("div"); - element.className = "marker"; + // Initial fetch for default view + fetchNearby(49.26, -123.05, 50); - let popupContainer = document.createElement("div"); + // Fetch more on pan/zoom + map.on("moveend", fetchOnMove); - mount(Popup, { - target: popupContainer, - props: { tags }, + // Center on user if available + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition((position) => { + let { longitude: lng, latitude: lat } = position.coords; + map.flyTo({ center: [lng, lat], zoom: 11, duration: 1000 }); + fetchNearby(lat, lng, 50); }); - - let marker = new maplibre.Marker({ color: "#F7931A", scale: 0.65 }) - .setLngLat([lon, lat]) - .setPopup(new maplibre.Popup().setDOMContent(popupContainer)) - .addTo(map); - - marker.id = counter++; - - marker.getElement().addEventListener("click", () => { - let p = marker.getPopup(); - popups[marker.id] = p; - p.setDOMContent(popupContainer); - setTimeout(() => { - selected = marker; - setTimeout(scroll, 10); - }, 50); - p.on("close", () => { - selected = undefined; - delete popups[marker.id]; - }); - }); - - marker.tags = tags; - locationMarkers[location.id] = marker; - - markers.push(marker); - }); - - updateLabelVisibility(); + } }); map.on("mousedown", stopFlying); -- cgit v1.2.3