1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
use gloo::utils::window;
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::{Position, PositionError};
use yew::prelude::*;
use super::use_effect_once;
pub use web_sys::PositionOptions as UseGeolocationOptions;
#[derive(PartialEq, Default, Clone)]
pub struct UseGeolocationState {
pub loading: bool,
pub accuracy: f64,
pub altitude: Option<f64>,
pub altitude_accuracy: Option<f64>,
pub heading: Option<f64>,
pub latitude: f64,
pub longitude: f64,
pub speed: Option<f64>,
pub timestamp: f64,
pub error: Option<PositionError>,
}
/// A sensor hook that tracks user's geographic location.
///
/// # Example
///
/// ```rust
/// # use yew::prelude::*;
/// #
/// use yew_hooks::prelude::*;
///
/// #[function_component(UseGeolocation)]
/// fn geolocation() -> Html {
/// let state = use_geolocation();
///
/// html! {
/// <>
/// <b>{ " loading: " }</b>
/// { state.loading }
/// <b>{ " latitude: " }</b>
/// { state.latitude }
/// <b>{ " longitude: " }</b>
/// { state.longitude }
/// <b>{ " altitude: " }</b>
/// { state.altitude.unwrap_or_default() }
/// <b>{ " altitude_accuracy: " }</b>
/// { state.altitude_accuracy.unwrap_or_default() }
/// <b>{ " heading: " }</b>
/// { state.heading.unwrap_or_default() }
/// <b>{ " speed: " }</b>
/// { state.speed.unwrap_or_default() }
/// <b>{ " timestamp: " }</b>
/// { state.timestamp }
/// </>
/// }
/// }
/// ```
#[hook]
pub fn use_geolocation() -> UseGeolocationState {
use_geolocation_with_options(UseGeolocationOptions::default())
}
/// A sensor hook that tracks user's geographic location.
/// See [`use_geolocation`]
#[hook]
pub fn use_geolocation_with_options(options: UseGeolocationOptions) -> UseGeolocationState {
let state = use_state(|| UseGeolocationState {
loading: true,
..Default::default()
});
{
let state = state.clone();
use_effect_once(move || {
let closure = {
let state = state.clone();
Closure::wrap(Box::new(move |position: Position| {
state.set(UseGeolocationState {
loading: false,
accuracy: position.coords().accuracy(),
altitude: position.coords().altitude(),
altitude_accuracy: position.coords().altitude_accuracy(),
heading: position.coords().heading(),
latitude: position.coords().latitude(),
longitude: position.coords().longitude(),
speed: position.coords().speed(),
timestamp: position.timestamp(),
error: None,
});
}) as Box<dyn Fn(Position)>)
};
let error_closure = {
let state = state.clone();
Closure::wrap(Box::new(move |error: PositionError| {
state.set(UseGeolocationState {
loading: false,
error: Some(error),
..*state
});
}) as Box<dyn Fn(PositionError)>)
};
window()
.navigator()
.geolocation()
.unwrap_throw()
.get_current_position_with_error_callback_and_options(
closure.as_ref().unchecked_ref(),
Some(error_closure.as_ref().unchecked_ref()),
&options,
)
.unwrap_throw();
let watch_id = window()
.navigator()
.geolocation()
.unwrap_throw()
.watch_position_with_error_callback_and_options(
closure.as_ref().unchecked_ref(),
Some(error_closure.as_ref().unchecked_ref()),
&options,
)
.unwrap_throw();
// Forget the closure to keep it alive
closure.forget();
error_closure.forget();
move || {
window()
.navigator()
.geolocation()
.unwrap_throw()
.clear_watch(watch_id);
}
});
}
(*state).clone()
}