leptos_use/
use_geolocation.rs

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