use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; use leptos::*; /// Reactive [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API). /// It allows the user to provide their location to web applications if they so desire. For privacy reasons, /// the user is asked for permission to report location information. /// /// ## Demo /// /// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_geolocation) /// /// ## Usage /// /// ``` /// # use leptos::*; /// # use leptos_use::{use_geolocation, UseGeolocationReturn}; /// # /// # #[component] /// # fn Demo() -> impl IntoView { /// let UseGeolocationReturn { /// coords, /// located_at, /// error, /// resume, /// pause, /// } = use_geolocation(); /// # /// # view! { } /// # } /// ``` /// /// ## Server-Side Rendering /// /// On the server all signals returns will always contain `None` and the functions do nothing. pub fn use_geolocation() -> UseGeolocationReturn { use_geolocation_with_options(UseGeolocationOptions::default()) } /// Version of [`use_geolocation`] that takes a `UseGeolocationOptions`. See [`use_geolocation`] for how to use. pub fn use_geolocation_with_options( options: UseGeolocationOptions, ) -> UseGeolocationReturn { let (located_at, set_located_at) = create_signal(None::); let (error, set_error) = create_signal(None::); let (coords, set_coords) = create_signal(None::); cfg_if! { if #[cfg(feature = "ssr")] { let resume = || (); let pause = || (); let _ = options; let _ = set_located_at; let _ = set_error; let _ = set_coords; } else { use crate::use_window; use std::cell::Cell; use std::rc::Rc; use wasm_bindgen::prelude::*; let update_position = move |position: web_sys::Position| { set_located_at.set(Some(position.timestamp())); set_coords.set(Some(position.coords())); set_error.set(None); }; let on_error = move |err: web_sys::PositionError| { set_error.set(Some(err)); }; let watch_handle = Rc::new(Cell::new(None::)); let resume = { let watch_handle = Rc::clone(&watch_handle); let position_options = options.as_position_options(); move || { let navigator = use_window().navigator(); if let Some(navigator) = navigator { if let Ok(geolocation) = navigator.geolocation() { let update_position = Closure::wrap(Box::new(update_position) as Box); let on_error = Closure::wrap(Box::new(on_error) as Box); watch_handle.replace( geolocation .watch_position_with_error_callback_and_options( update_position.as_ref().unchecked_ref(), Some(on_error.as_ref().unchecked_ref()), &position_options, ) .ok(), ); update_position.forget(); on_error.forget(); } } } }; if options.immediate { resume(); } let pause = { let watch_handle = Rc::clone(&watch_handle); move || { let navigator = use_window().navigator(); if let Some(navigator) = navigator { if let Some(handle) = watch_handle.take() { if let Ok(geolocation) = navigator.geolocation() { geolocation.clear_watch(handle); } } } } }; on_cleanup({ let pause = pause.clone(); move || { pause(); } }); }} UseGeolocationReturn { coords: coords.into(), located_at: located_at.into(), error: error.into(), resume, pause, } } /// Options for [`use_geolocation_with_options`]. #[derive(DefaultBuilder, Clone)] #[allow(dead_code)] pub struct UseGeolocationOptions { /// If `true` the geolocation watch is started when this function is called. /// If `false` you have to call `resume` manually to start it. Defaults to `true`. immediate: bool, /// A boolean value that indicates the application would like to receive the best /// possible results. If `true` and if the device is able to provide a more accurate /// position, it will do so. Note that this can result in slower response times or /// increased power consumption (with a GPS chip on a mobile device for example). /// On the other hand, if `false`, the device can take the liberty to save /// resources by responding more quickly and/or using less power. Default: `false`. enable_high_accuracy: bool, /// A positive value indicating the maximum age in milliseconds of a possible cached position that is acceptable to return. /// If set to `0`, it means that the device cannot use a cached position and must attempt to retrieve the real current position. /// Default: 30000. maximum_age: u32, /// A positive value representing the maximum length of time (in milliseconds) /// the device is allowed to take in order to return a position. /// The default value is 27000. timeout: u32, } impl Default for UseGeolocationOptions { fn default() -> Self { Self { enable_high_accuracy: false, maximum_age: 30000, timeout: 27000, immediate: true, } } } #[cfg(not(feature = "ssr"))] impl UseGeolocationOptions { fn as_position_options(&self) -> web_sys::PositionOptions { let UseGeolocationOptions { enable_high_accuracy, maximum_age, timeout, .. } = self; let mut options = web_sys::PositionOptions::new(); options.enable_high_accuracy(*enable_high_accuracy); options.maximum_age(*maximum_age); options.timeout(*timeout); options } } /// Return type of [`use_geolocation`]. pub struct UseGeolocationReturn where ResumeFn: Fn() + Clone, PauseFn: Fn() + Clone, { /// The coordinates of the current device like latitude and longitude. /// See [`GeolocationCoordinates`](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates).. pub coords: Signal>, /// The timestamp of the current coordinates. pub located_at: Signal>, /// The last error received from `navigator.geolocation`. pub error: Signal>, /// Resume the geolocation watch. pub resume: ResumeFn, /// Pause the geolocation watch. pub pause: PauseFn, }