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 {
let result = Self {
path: path.into_iter().map(|p| p.to_string()).collect(),
hash: None,
search: None,
title: None,
};
clean_url(result)
}
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
}
}
impl From<String> for Url {
fn from(s: String) -> Self {
let mut path: Vec<String> = s.split('/').map(|s2| s2.to_string()).collect();
path.remove(0); Self {
path,
hash: None,
search: None,
title: None,
}
}
}
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: '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
}
fn remove_first(s: &str) -> Option<&str> {
s.chars().next().map(|c| &s[c.len_utf8()..])
}
fn clean_url(mut url: Url) -> Url {
let mut cleaned_path = vec![];
for part in &url.path {
if let Some(first) = part.chars().next() {
if first == '/' {
cleaned_path.push(remove_first(part).unwrap().to_string());
} else {
cleaned_path.push(part.to_string());
}
}
}
url.path = cleaned_path;
url
}
pub fn push_route(mut url: Url) {
url = clean_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));
}
pub fn setup_popstate_listener<Ms, Mdl>(app: &App<Ms, Mdl>, routes: fn(&Url) -> Ms)
where
Ms: 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 deserializing 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 setup_link_listener<Ms: Clone, Mdl>(app: &App<Ms, Mdl>, routes: fn(&Url) -> Ms) {
let app_for_closure = app.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::Event| {
if let Some(et) = event.target() {
if let Some(el) = et.dyn_ref::<web_sys::Element>() {
let tag = el.tag_name();
if tag == "Base" || tag == "Link" {
return;
}
if let Some(href) = el.get_attribute("href") {
if let Some(first) = href.chars().next() {
if first != '/' {
return;
}
event.prevent_default(); let url = href.into();
app_for_closure.update(routes(&url));
push_route(url);
}
}
}
}
}) as Box<FnMut(web_sys::Event) + 'static>);
(util::document().as_ref() as &web_sys::EventTarget)
.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref())
.expect("Problem setting up link interceptor");
closure.forget(); }