Skip to main content

leptos_use/
use_geolocation.rs

1use crate::core::{OptionLocalRwSignal, OptionLocalSignal};
2use default_struct_builder::DefaultBuilder;
3use leptos::prelude::*;
4use leptos::reactive::wrappers::read::Signal;
5
6/// Reactive [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API).
7///
8/// It allows the user to provide their location to web applications if they so desire. For privacy reasons,
9/// the user is asked for permission to report location information.
10///
11/// ## Demo
12///
13/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_geolocation)
14///
15/// ## Usage
16///
17/// ```
18/// # use leptos::prelude::*;
19/// # use leptos_use::{use_geolocation, UseGeolocationReturn};
20/// #
21/// # #[component]
22/// # fn Demo() -> impl IntoView {
23/// let UseGeolocationReturn {
24///     coords,
25///     located_at,
26///     error,
27///     resume,
28///     pause,
29/// } = use_geolocation();
30/// #
31/// # view! { }
32/// # }
33/// ```
34///
35///
36/// ## SendWrapped Return
37///
38/// The returned closures `pause` and `resume` are sendwrapped functions. They can
39/// only be called from the same thread that called `use_geolocation`.
40///
41/// ## Server-Side Rendering
42///
43/// > Make sure you follow the [instructions in Server-Side Rendering](https://leptos-use.rs/server_side_rendering.html).
44///
45/// On the server all signals returns will always contain `None` and the functions do nothing.
46pub fn use_geolocation()
47-> UseGeolocationReturn<impl Fn() + Clone + Send + Sync, impl Fn() + Clone + Send + Sync> {
48    use_geolocation_with_options(UseGeolocationOptions::default())
49}
50
51/// Version of [`use_geolocation`] that takes a `UseGeolocationOptions`. See [`use_geolocation`] for how to use.
52pub fn use_geolocation_with_options(
53    options: UseGeolocationOptions,
54) -> UseGeolocationReturn<impl Fn() + Clone + Send + Sync, impl Fn() + Clone + Send + Sync> {
55    let (located_at, set_located_at) = signal(None::<f64>);
56    let error = OptionLocalRwSignal::<web_sys::PositionError>::new();
57    let coords = OptionLocalRwSignal::<web_sys::Coordinates>::new();
58
59    let resume;
60    let pause;
61
62    #[cfg(feature = "ssr")]
63    {
64        resume = || ();
65        pause = || ();
66
67        let _ = options;
68        let _ = set_located_at;
69        let _ = error;
70        let _ = coords;
71    }
72
73    #[cfg(not(feature = "ssr"))]
74    {
75        use crate::{sendwrap_fn, use_window};
76        use std::sync::{Arc, Mutex};
77        use wasm_bindgen::prelude::*;
78
79        let update_position = move |position: web_sys::Position| {
80            set_located_at.set(Some(position.timestamp()));
81            coords.set(Some(position.coords()));
82            error.set(None);
83        };
84
85        let on_error = move |err: web_sys::PositionError| {
86            error.set(Some(err));
87        };
88
89        let watch_handle = Arc::new(Mutex::new(None::<i32>));
90
91        resume = {
92            let watch_handle = Arc::clone(&watch_handle);
93            let position_options = options.as_position_options();
94
95            sendwrap_fn!(move || {
96                let navigator = use_window().navigator();
97                if let Some(navigator) = navigator
98                    && let Ok(geolocation) = navigator.geolocation()
99                {
100                    if let Some(handle) = watch_handle.lock().unwrap().take() {
101                        geolocation.clear_watch(handle);
102                    }
103
104                    let update_position =
105                        Closure::wrap(Box::new(update_position) as Box<dyn Fn(web_sys::Position)>);
106                    let on_error =
107                        Closure::wrap(Box::new(on_error) as Box<dyn Fn(web_sys::PositionError)>);
108
109                    let handle = geolocation.watch_position_with_error_callback_and_options(
110                        update_position.as_ref().unchecked_ref(),
111                        Some(on_error.as_ref().unchecked_ref()),
112                        &position_options,
113                    );
114
115                    #[cfg(web_sys_unstable_apis)]
116                    let handle = Some(handle);
117
118                    #[cfg(not(web_sys_unstable_apis))]
119                    let handle = handle.ok();
120
121                    *watch_handle.lock().unwrap() = handle;
122
123                    update_position.forget();
124                    on_error.forget();
125                }
126            })
127        };
128
129        if options.immediate {
130            resume();
131        }
132
133        pause = {
134            let watch_handle = Arc::clone(&watch_handle);
135
136            sendwrap_fn!(move || {
137                let navigator = use_window().navigator();
138                if let Some(navigator) = navigator
139                    && let Some(handle) = *watch_handle.lock().unwrap()
140                    && let Ok(geolocation) = navigator.geolocation()
141                {
142                    geolocation.clear_watch(handle);
143                }
144            })
145        };
146
147        on_cleanup({
148            let pause = pause.clone();
149
150            move || {
151                pause();
152            }
153        });
154    }
155
156    UseGeolocationReturn {
157        coords: coords.read_only(),
158        located_at: located_at.into(),
159        error: error.read_only(),
160        resume,
161        pause,
162    }
163}
164
165/// Options for [`use_geolocation_with_options`].
166#[derive(DefaultBuilder, Clone)]
167#[allow(dead_code)]
168pub struct UseGeolocationOptions {
169    /// If `true` the geolocation watch is started when this function is called.
170    /// If `false` you have to call `resume` manually to start it. Defaults to `true`.
171    immediate: bool,
172
173    /// A boolean value that indicates the application would like to receive the best
174    /// possible results. If `true` and if the device is able to provide a more accurate
175    /// position, it will do so. Note that this can result in slower response times or
176    /// increased power consumption (with a GPS chip on a mobile device for example).
177    /// On the other hand, if `false`, the device can take the liberty to save
178    /// resources by responding more quickly and/or using less power. Default: `false`.
179    enable_high_accuracy: bool,
180
181    /// A positive value indicating the maximum age in milliseconds of a possible cached position that is acceptable to return.
182    /// If set to `0`, it means that the device cannot use a cached position and must attempt to retrieve the real current position.
183    /// Default: 30000.
184    maximum_age: u32,
185
186    /// A positive value representing the maximum length of time (in milliseconds)
187    /// the device is allowed to take in order to return a position.
188    /// The default value is 27000.
189    timeout: u32,
190}
191
192impl Default for UseGeolocationOptions {
193    fn default() -> Self {
194        Self {
195            enable_high_accuracy: false,
196            maximum_age: 30000,
197            timeout: 27000,
198            immediate: true,
199        }
200    }
201}
202
203#[cfg(not(feature = "ssr"))]
204impl UseGeolocationOptions {
205    fn as_position_options(&self) -> web_sys::PositionOptions {
206        let UseGeolocationOptions {
207            enable_high_accuracy,
208            maximum_age,
209            timeout,
210            ..
211        } = self;
212
213        let options = web_sys::PositionOptions::new();
214        options.set_enable_high_accuracy(*enable_high_accuracy);
215        options.set_maximum_age(*maximum_age);
216        options.set_timeout(*timeout);
217
218        options
219    }
220}
221
222/// Return type of [`use_geolocation`].
223pub struct UseGeolocationReturn<ResumeFn, PauseFn>
224where
225    ResumeFn: Fn() + Clone + Send + Sync,
226    PauseFn: Fn() + Clone + Send + Sync,
227{
228    /// The coordinates of the current device like latitude and longitude.
229    /// See [`GeolocationCoordinates`](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates)..
230    pub coords: OptionLocalSignal<web_sys::Coordinates>,
231
232    /// The timestamp of the current coordinates.
233    pub located_at: Signal<Option<f64>>,
234
235    /// The last error received from `navigator.geolocation`.
236    pub error: OptionLocalSignal<web_sys::PositionError>,
237
238    /// Resume the geolocation watch.
239    pub resume: ResumeFn,
240
241    /// Pause the geolocation watch.
242    pub pause: PauseFn,
243}