use wasm_bindgen::{JsValue, UnwrapThrowExt}; use web_sys::{window, Url, EventTarget, HtmlElement}; use futures_signals::signal::{Mutable, Signal}; use gloo::events::EventListener; use crate::dom::{Dom, DomBuilder}; use crate::events; /*pub struct State { value: Mutable>, callback: Value, } impl State { pub fn new() -> Self { // TODO replace with stdweb function let value = Mutable::new(js!( return history.state; ).try_into().unwrap_throw()); let callback = |state: Option| { value.set(state); }; Self { value, callback: js!( var callback = @{callback}; addEventListener("popstate", function (e) { callback(e.state); }, true); return callback; ), } } pub fn set(&self, value: A) { window().history().replace_state(value, "", None).unwrap_throw(); self.value.set(value); } }*/ fn current_url_string() -> String { window().unwrap_throw().location().href().unwrap_throw() } // TODO inline ? fn change_url(mutable: &Mutable) { let mut lock = mutable.lock_mut(); let new_url = current_url_string(); // TODO test that this doesn't notify if the URLs are the same // TODO helper method for this // TODO can this be made more efficient ? if lock.href() != new_url { *lock = Url::new(&new_url).unwrap_throw(); } } struct CurrentUrl { value: Mutable, _listener: EventListener, } impl CurrentUrl { fn new() -> Self { // TODO can this be made more efficient ? let value = Mutable::new(Url::new(¤t_url_string()).unwrap_throw()); Self { _listener: EventListener::new(&window().unwrap_throw(), "popstate", { let value = value.clone(); move |_| { change_url(&value); } }), value, } } } // TODO somehow share this safely between threads ? thread_local! { static URL: CurrentUrl = CurrentUrl::new(); } #[inline] pub fn current_url() -> Url { URL.with(|url| url.value.get_cloned()) } #[inline] pub fn url() -> impl Signal { URL.with(|url| url.value.signal_cloned()) } // TODO if URL hasn't been created yet, don't create it #[inline] pub fn go_to_url(new_url: &str) { window() .unwrap_throw() .history() .unwrap_throw() // TODO is this the best state object to use ? .push_state_with_url(&JsValue::NULL, "", Some(new_url)) .unwrap_throw(); URL.with(|url| { change_url(&url.value); }); } // TODO somehow use &str rather than String, maybe Cow ? #[inline] pub fn on_click_go_to_url(new_url: String) -> impl FnOnce(DomBuilder) -> DomBuilder where A: AsRef { #[inline] move |dom| { dom.event_preventable(move |e: events::Click| { e.prevent_default(); go_to_url(&new_url); }) } } // TODO better type than HtmlElement #[inline] pub fn link(url: &str, f: F) -> Dom where F: FnOnce(DomBuilder) -> DomBuilder { html!("a", { .attribute("href", url) // TODO somehow avoid this allocation .apply(on_click_go_to_url(url.to_string())) .apply(f) }) }