use serde::{Deserialize, Serialize};
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
use crate::{util, App};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Url {
pub path: Vec<String>,
pub hash: Option<String>,
pub search: Option<String>,
pub title: Option<String>,
}
impl Url {
pub fn new<T: ToString>(path: Vec<T>) -> Self {
Self {
path: path.into_iter().map(|p| p.to_string()).collect(),
hash: None,
search: None,
title: None,
}
}
pub fn hash(mut self, hash: &str) -> Self {
self.hash = Some(hash.into());
self
}
pub fn search(mut self, search: &str) -> Self {
self.search = Some(search.into());
self
}
pub fn title(mut self, title: &str) -> Self {
self.title = Some(title.into());
self
}
}
fn get_path() -> String {
let path = util::window()
.location()
.pathname()
.expect("Can't find pathname");
path[1..path.len()].to_string() }
fn get_hash() -> String {
let hash = util::window()
.location()
.hash()
.expect("Can't find hash");
hash.to_string().replace("#", "")
}
fn get_search() -> String {
let search = util::window()
.location()
.search()
.expect("Can't find search");
search.to_string().replace("?", "")
}
pub fn initial<Ms, Mdl>(app: App<Ms, Mdl>, routes: fn(&Url) -> Ms) -> App<Ms, Mdl>
where
Ms: Clone + 'static,
Mdl: Clone + 'static,
{
let raw_path = get_path();
let path_ref: Vec<&str> = raw_path.split('/').collect();
let path: Vec<String> = path_ref.into_iter().map(|p| p.to_string()).collect();
let raw_hash = get_hash();
let hash = match raw_hash.len() {
0 => None,
_ => Some(raw_hash)
};
let raw_search = get_search();
let search = match raw_search.len() {
0 => None,
_ => Some(raw_search)
};
let url = Url {
path,
hash,
search,
title: None,
};
app.update(routes(&url));
app
}
pub fn setup_popstate_listener<Ms, Mdl>(app: &App<Ms, Mdl>, routes: fn(&Url) -> Ms)
where
Ms: Clone,
Mdl: Clone,
{
let app_for_closure = app.clone();
let closure = Closure::wrap(Box::new(move |ev: web_sys::Event| {
let ev = ev
.dyn_ref::<web_sys::PopStateEvent>()
.expect("Problem casting as Popstate event");
let url: Url = match ev.state().as_string() {
Some(state_str) => serde_json::from_str(&state_str)
.expect("Problem deserialzing popstate state"),
None => {
let empty: Vec<String> = Vec::new();
Url::new(empty)
}
};
app_for_closure.update(routes(&url));
}) as Box<FnMut(web_sys::Event) + 'static>);
(util::window().as_ref() as &web_sys::EventTarget)
.add_event_listener_with_callback("popstate", closure.as_ref().unchecked_ref())
.expect("Problem adding popstate listener");
app.data.popstate_closure.replace(Some(closure));
}
pub fn push_route(url: Url) {
let data =
JsValue::from_serde(&serde_json::to_string(&url).expect("Problem serializing route data"))
.expect("Problem converting route data to JsValue");
let title = match url.title {
Some(t) => t,
None => "".into(),
};
let path = String::from("/") + &url.path.join("/");
util::window()
.history()
.expect("Can't find history")
.push_state_with_url(&data, &title, Some(&path))
.expect("Problem pushing state");
let location = util::window().location();
if let Some(hash) = url.hash {
location.set_hash(&hash).expect("Problem setting hash");
}
if let Some(search) = url.search {
location.set_search(&search).expect("Problem setting search");
}
}
pub fn push_path<T: ToString>(path: Vec<T>) {
push_route(Url::new(path));
}