leptos-use/src/use_geolocation.rs

221 lines
7.2 KiB
Rust
Raw Normal View History

2024-01-16 11:24:52 +00:00
use cfg_if::cfg_if;
2023-09-12 23:53:43 +01:00
use default_struct_builder::DefaultBuilder;
use leptos::*;
/// Reactive [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API).
2024-08-28 02:59:31 +01:00
///
2023-09-12 23:53:43 +01:00
/// 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! { }
/// # }
/// ```
2024-01-16 11:24:52 +00:00
///
/// ## Server-Side Rendering
///
/// On the server all signals returns will always contain `None` and the functions do nothing.
2023-09-12 23:53:43 +01:00
pub fn use_geolocation() -> UseGeolocationReturn<impl Fn() + Clone, impl Fn() + Clone> {
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<impl Fn() + Clone, impl Fn() + Clone> {
let (located_at, set_located_at) = create_signal(None::<f64>);
let (error, set_error) = create_signal(None::<web_sys::PositionError>);
let (coords, set_coords) = create_signal(None::<web_sys::Coordinates>);
2024-01-16 11:24:52 +00:00
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::<i32>));
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<dyn Fn(web_sys::Position)>);
let on_error =
Closure::wrap(Box::new(on_error) as Box<dyn Fn(web_sys::PositionError)>);
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();
}
2023-09-12 23:53:43 +01:00
}
}
2024-01-16 11:24:52 +00:00
};
2023-09-12 23:53:43 +01:00
2024-01-16 11:24:52 +00:00
if options.immediate {
resume();
}
2023-09-12 23:53:43 +01:00
2024-01-16 11:24:52 +00:00
let pause = {
let watch_handle = Rc::clone(&watch_handle);
2023-09-12 23:53:43 +01:00
2024-01-16 11:24:52 +00:00
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);
}
2023-09-12 23:53:43 +01:00
}
}
}
2024-01-16 11:24:52 +00:00
};
2023-09-12 23:53:43 +01:00
2024-01-16 11:24:52 +00:00
on_cleanup({
let pause = pause.clone();
2023-09-12 23:53:43 +01:00
2024-01-16 11:24:52 +00:00
move || {
pause();
}
});
}}
2023-09-12 23:53:43 +01:00
UseGeolocationReturn {
coords: coords.into(),
located_at: located_at.into(),
error: error.into(),
resume,
pause,
}
}
/// Options for [`use_geolocation_with_options`].
2024-01-16 11:24:52 +00:00
#[derive(DefaultBuilder, Clone)]
#[allow(dead_code)]
2023-09-12 23:53:43 +01:00
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,
}
}
}
2024-01-16 11:24:52 +00:00
#[cfg(not(feature = "ssr"))]
2023-09-12 23:53:43 +01:00
impl UseGeolocationOptions {
fn as_position_options(&self) -> web_sys::PositionOptions {
let UseGeolocationOptions {
enable_high_accuracy,
maximum_age,
timeout,
..
} = self;
let options = web_sys::PositionOptions::new();
options.set_enable_high_accuracy(*enable_high_accuracy);
options.set_maximum_age(*maximum_age);
options.set_timeout(*timeout);
2023-09-12 23:53:43 +01:00
options
}
}
/// Return type of [`use_geolocation`].
pub struct UseGeolocationReturn<ResumeFn, PauseFn>
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<Option<web_sys::Coordinates>>,
/// The timestamp of the current coordinates.
pub located_at: Signal<Option<f64>>,
/// The last error received from `navigator.geolocation`.
pub error: Signal<Option<web_sys::PositionError>>,
/// Resume the geolocation watch.
pub resume: ResumeFn,
/// Pause the geolocation watch.
pub pause: PauseFn,
}