diff --git a/src/application/routes/app/mod.rs b/src/application/routes/app/mod.rs index 9402d0e..4b42f78 100644 --- a/src/application/routes/app/mod.rs +++ b/src/application/routes/app/mod.rs @@ -55,6 +55,7 @@ pub(super) fn router() -> axum::Router { get(searchable_select_js), ) .route("/static/js/components/chip-scroll.js", get(chip_scroll_js)) + .route("/static/js/location.js", get(location_js)) .route("/static/js/components/world-map.js", get(world_map_js)) .route("/static/js/components/donut-chart.js", get(donut_chart_js)) .route("/static/favicon-light.svg", get(favicon_light)) @@ -87,6 +88,16 @@ async fn webauthn_js() -> impl IntoResponse { ) } +async fn location_js() -> impl IntoResponse { + ( + [ + ("content-type", "application/javascript; charset=utf-8"), + ("cache-control", "public, max-age=604800"), + ], + include_str!("../../../../static/js/location.js"), + ) +} + async fn photo_capture_js() -> impl IntoResponse { ( [ diff --git a/static/js/location.js b/static/js/location.js new file mode 100644 index 0000000..8f2ce4c --- /dev/null +++ b/static/js/location.js @@ -0,0 +1,62 @@ +const nearbyKeydown = (e, el) => { + const root = el.closest("[data-location-root]"); + const results = root?.querySelector("#nearby-results"); + if (!results) return; + const buttons = [...results.querySelectorAll("button")]; + if (!buttons.length) return; + + const active = results.querySelector(".ss-active"); + let idx = active ? buttons.indexOf(active) : -1; + + if (e.key === "ArrowDown") { + e.preventDefault(); + if (active) active.classList.remove("ss-active"); + idx = (idx + 1) % buttons.length; + buttons[idx].classList.add("ss-active"); + buttons[idx].scrollIntoView({ block: "nearest" }); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + if (active) active.classList.remove("ss-active"); + idx = idx <= 0 ? buttons.length - 1 : idx - 1; + buttons[idx].classList.add("ss-active"); + buttons[idx].scrollIntoView({ block: "nearest" }); + } else if (e.key === "Enter" && active) { + e.preventDefault(); + active.click(); + } else if (e.key === "Escape") { + results.classList.add("hidden"); + } +}; + +const locateUser = (triggerEl) => { + const root = triggerEl.closest("[data-location-root]"); + const emit = (name, detail) => + root.dispatchEvent(new CustomEvent(name, { detail, bubbles: true })); + if (!navigator.geolocation) { + emit("location-error", { + message: "Geolocation not supported by this browser.", + }); + return; + } + emit("location-start"); + navigator.geolocation.getCurrentPosition( + (pos) => { + emit("location-found", { + lat: pos.coords.latitude, + lng: pos.coords.longitude, + }); + }, + (err) => { + if (err.code === 1) { + emit("location-error", { + message: "Location access denied. Search by name instead.", + }); + } else { + emit("location-error", { + message: "Could not determine location. Search by name instead.", + }); + } + }, + { enableHighAccuracy: true, timeout: 15000 }, + ); +}; diff --git a/templates/partials/location_search.html b/templates/partials/location_search.html index b0db897..f3cee08 100644 --- a/templates/partials/location_search.html +++ b/templates/partials/location_search.html @@ -1,19 +1,5 @@ {% import "partials/icons.html" as icons %} -{% macro location_search_js() %} - const locateUser = (triggerEl) => { const root = - triggerEl.closest('[data-location-root]'); const emit = (name, detail) => - root.dispatchEvent(new CustomEvent(name, { detail, bubbles: true })); if - (!navigator.geolocation) { emit('location-error', { message: 'Geolocation not - supported by this browser.' }); return; } emit('location-start'); - navigator.geolocation.getCurrentPosition( (pos) => { emit('location-found', { - lat: pos.coords.latitude, lng: pos.coords.longitude }); }, (err) => { if - (err.code === 1) { emit('location-error', { message: 'Location access denied. - Search by name instead.' }); } else { emit('location-error', { message: 'Could - not determine location. Search by name instead.' }); } }, { - enableHighAccuracy: true, timeout: 15000 }, ); }; -{% endmacro %} - {% macro location_search(error_signal) %}