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/// On the server all signals returns will always contain `None` and the functions do nothing.
43pub fn use_geolocation(
44) -> UseGeolocationReturn<impl Fn() + Clone + Send + Sync, impl Fn() + Clone + Send + Sync> {
45 use_geolocation_with_options(UseGeolocationOptions::default())
46}
47
48/// Version of [`use_geolocation`] that takes a `UseGeolocationOptions`. See [`use_geolocation`] for how to use.
49pub fn use_geolocation_with_options(
50 options: UseGeolocationOptions,
51) -> UseGeolocationReturn<impl Fn() + Clone + Send + Sync, impl Fn() + Clone + Send + Sync> {
52 let (located_at, set_located_at) = signal(None::<f64>);
53 let (error, set_error) = signal_local(None::<web_sys::PositionError>);
54 let (coords, set_coords) = signal_local(None::<web_sys::Coordinates>);
55
56 let resume;
57 let pause;
58
59 #[cfg(feature = "ssr")]
60 {
61 resume = || ();
62 pause = || ();
63
64 let _ = options;
65 let _ = set_located_at;
66 let _ = set_error;
67 let _ = set_coords;
68 }
69
70 #[cfg(not(feature = "ssr"))]
71 {
72 use crate::{sendwrap_fn, use_window};
73 use std::sync::{Arc, Mutex};
74 use wasm_bindgen::prelude::*;
75
76 let update_position = move |position: web_sys::Position| {
77 set_located_at.set(Some(position.timestamp()));
78 set_coords.set(Some(position.coords()));
79 set_error.set(None);
80 };
81
82 let on_error = move |err: web_sys::PositionError| {
83 set_error.set(Some(err));
84 };
85
86 let watch_handle = Arc::new(Mutex::new(None::<i32>));
87
88 resume = {
89 let watch_handle = Arc::clone(&watch_handle);
90 let position_options = options.as_position_options();
91
92 sendwrap_fn!(move || {
93 let navigator = use_window().navigator();
94 if let Some(navigator) = navigator {
95 if let Ok(geolocation) = navigator.geolocation() {
96 let update_position = Closure::wrap(
97 Box::new(update_position) as Box<dyn Fn(web_sys::Position)>
98 );
99 let on_error = Closure::wrap(
100 Box::new(on_error) as Box<dyn Fn(web_sys::PositionError)>
101 );
102
103 *watch_handle.lock().unwrap() = geolocation
104 .watch_position_with_error_callback_and_options(
105 update_position.as_ref().unchecked_ref(),
106 Some(on_error.as_ref().unchecked_ref()),
107 &position_options,
108 )
109 .ok();
110
111 update_position.forget();
112 on_error.forget();
113 }
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 if let Some(handle) = *watch_handle.lock().unwrap() {
129 if let Ok(geolocation) = navigator.geolocation() {
130 geolocation.clear_watch(handle);
131 }
132 }
133 }
134 })
135 };
136
137 on_cleanup({
138 let pause = pause.clone();
139
140 move || {
141 pause();
142 }
143 });
144 }
145
146 UseGeolocationReturn {
147 coords: coords.into(),
148 located_at: located_at.into(),
149 error: error.into(),
150 resume,
151 pause,
152 }
153}
154
155/// Options for [`use_geolocation_with_options`].
156#[derive(DefaultBuilder, Clone)]
157#[allow(dead_code)]
158pub struct UseGeolocationOptions {
159 /// If `true` the geolocation watch is started when this function is called.
160 /// If `false` you have to call `resume` manually to start it. Defaults to `true`.
161 immediate: bool,
162
163 /// A boolean value that indicates the application would like to receive the best
164 /// possible results. If `true` and if the device is able to provide a more accurate
165 /// position, it will do so. Note that this can result in slower response times or
166 /// increased power consumption (with a GPS chip on a mobile device for example).
167 /// On the other hand, if `false`, the device can take the liberty to save
168 /// resources by responding more quickly and/or using less power. Default: `false`.
169 enable_high_accuracy: bool,
170
171 /// A positive value indicating the maximum age in milliseconds of a possible cached position that is acceptable to return.
172 /// If set to `0`, it means that the device cannot use a cached position and must attempt to retrieve the real current position.
173 /// Default: 30000.
174 maximum_age: u32,
175
176 /// A positive value representing the maximum length of time (in milliseconds)
177 /// the device is allowed to take in order to return a position.
178 /// The default value is 27000.
179 timeout: u32,
180}
181
182impl Default for UseGeolocationOptions {
183 fn default() -> Self {
184 Self {
185 enable_high_accuracy: false,
186 maximum_age: 30000,
187 timeout: 27000,
188 immediate: true,
189 }
190 }
191}
192
193#[cfg(not(feature = "ssr"))]
194impl UseGeolocationOptions {
195 fn as_position_options(&self) -> web_sys::PositionOptions {
196 let UseGeolocationOptions {
197 enable_high_accuracy,
198 maximum_age,
199 timeout,
200 ..
201 } = self;
202
203 let options = web_sys::PositionOptions::new();
204 options.set_enable_high_accuracy(*enable_high_accuracy);
205 options.set_maximum_age(*maximum_age);
206 options.set_timeout(*timeout);
207
208 options
209 }
210}
211
212/// Return type of [`use_geolocation`].
213pub struct UseGeolocationReturn<ResumeFn, PauseFn>
214where
215 ResumeFn: Fn() + Clone + Send + Sync,
216 PauseFn: Fn() + Clone + Send + Sync,
217{
218 /// The coordinates of the current device like latitude and longitude.
219 /// See [`GeolocationCoordinates`](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates)..
220 pub coords: Signal<Option<web_sys::Coordinates>, LocalStorage>,
221
222 /// The timestamp of the current coordinates.
223 pub located_at: Signal<Option<f64>>,
224
225 /// The last error received from `navigator.geolocation`.
226 pub error: Signal<Option<web_sys::PositionError>, LocalStorage>,
227
228 /// Resume the geolocation watch.
229 pub resume: ResumeFn,
230
231 /// Pause the geolocation watch.
232 pub pause: PauseFn,
233}