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 let update_position =
101 Closure::wrap(Box::new(update_position) as Box<dyn Fn(web_sys::Position)>);
102 let on_error =
103 Closure::wrap(Box::new(on_error) as Box<dyn Fn(web_sys::PositionError)>);
104
105 *watch_handle.lock().unwrap() =
106 Some(geolocation.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
112 update_position.forget();
113 on_error.forget();
114 }
115 })
116 };
117
118 if options.immediate {
119 resume();
120 }
121
122 pause = {
123 let watch_handle = Arc::clone(&watch_handle);
124
125 sendwrap_fn!(move || {
126 let navigator = use_window().navigator();
127 if let Some(navigator) = navigator
128 && let Some(handle) = *watch_handle.lock().unwrap()
129 && let Ok(geolocation) = navigator.geolocation()
130 {
131 geolocation.clear_watch(handle);
132 }
133 })
134 };
135
136 on_cleanup({
137 let pause = pause.clone();
138
139 move || {
140 pause();
141 }
142 });
143 }
144
145 UseGeolocationReturn {
146 coords: coords.read_only(),
147 located_at: located_at.into(),
148 error: error.read_only(),
149 resume,
150 pause,
151 }
152}
153
154/// Options for [`use_geolocation_with_options`].
155#[derive(DefaultBuilder, Clone)]
156#[allow(dead_code)]
157pub struct UseGeolocationOptions {
158 /// If `true` the geolocation watch is started when this function is called.
159 /// If `false` you have to call `resume` manually to start it. Defaults to `true`.
160 immediate: bool,
161
162 /// A boolean value that indicates the application would like to receive the best
163 /// possible results. If `true` and if the device is able to provide a more accurate
164 /// position, it will do so. Note that this can result in slower response times or
165 /// increased power consumption (with a GPS chip on a mobile device for example).
166 /// On the other hand, if `false`, the device can take the liberty to save
167 /// resources by responding more quickly and/or using less power. Default: `false`.
168 enable_high_accuracy: bool,
169
170 /// A positive value indicating the maximum age in milliseconds of a possible cached position that is acceptable to return.
171 /// If set to `0`, it means that the device cannot use a cached position and must attempt to retrieve the real current position.
172 /// Default: 30000.
173 maximum_age: u32,
174
175 /// A positive value representing the maximum length of time (in milliseconds)
176 /// the device is allowed to take in order to return a position.
177 /// The default value is 27000.
178 timeout: u32,
179}
180
181impl Default for UseGeolocationOptions {
182 fn default() -> Self {
183 Self {
184 enable_high_accuracy: false,
185 maximum_age: 30000,
186 timeout: 27000,
187 immediate: true,
188 }
189 }
190}
191
192#[cfg(not(feature = "ssr"))]
193impl UseGeolocationOptions {
194 fn as_position_options(&self) -> web_sys::PositionOptions {
195 let UseGeolocationOptions {
196 enable_high_accuracy,
197 maximum_age,
198 timeout,
199 ..
200 } = self;
201
202 let options = web_sys::PositionOptions::new();
203 options.set_enable_high_accuracy(*enable_high_accuracy);
204 options.set_maximum_age(*maximum_age);
205 options.set_timeout(*timeout);
206
207 options
208 }
209}
210
211/// Return type of [`use_geolocation`].
212pub struct UseGeolocationReturn<ResumeFn, PauseFn>
213where
214 ResumeFn: Fn() + Clone + Send + Sync,
215 PauseFn: Fn() + Clone + Send + Sync,
216{
217 /// The coordinates of the current device like latitude and longitude.
218 /// See [`GeolocationCoordinates`](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates)..
219 pub coords: OptionLocalSignal<web_sys::Coordinates>,
220
221 /// The timestamp of the current coordinates.
222 pub located_at: Signal<Option<f64>>,
223
224 /// The last error received from `navigator.geolocation`.
225 pub error: OptionLocalSignal<web_sys::PositionError>,
226
227 /// Resume the geolocation watch.
228 pub resume: ResumeFn,
229
230 /// Pause the geolocation watch.
231 pub pause: PauseFn,
232}