mod utils; use std::{cell::RefCell, collections::HashMap, rc::Rc}; use dioxus_core as dioxus; use dioxus_core::prelude::*; use dioxus_core_macro::{format_args_f, rsx, Props}; use dioxus_html as dioxus_elements; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{window, Event}; use crate::utils::strip_slash_suffix; pub trait Routable: 'static + Send + Clone + PartialEq {} impl Routable for T where T: 'static + Send + Clone + PartialEq {} pub struct RouterService { historic_routes: Vec, history_service: RefCell, base_ur: RefCell>, } impl RouterService { fn push_route(&self, r: R) { todo!() // self.historic_routes.borrow_mut().push(r); } fn get_current_route(&self) -> &str { todo!() } fn update_route_impl(&self, url: String, push: bool) { let history = web_sys::window().unwrap().history().expect("no history"); let base = self.base_ur.borrow(); let path = match base.as_ref() { Some(base) => { let path = format!("{}{}", base, url); if path.is_empty() { "/".to_string() } else { path } } None => url, }; if push { history .push_state_with_url(&JsValue::NULL, "", Some(&path)) .expect("push history"); } else { history .replace_state_with_url(&JsValue::NULL, "", Some(&path)) .expect("replace history"); } let event = Event::new("popstate").unwrap(); web_sys::window() .unwrap() .dispatch_event(&event) .expect("dispatch"); } } /// This hould only be used once per app /// /// You can manually parse the route if you want, but the derived `parse` method on `Routable` will also work just fine pub fn use_router(cx: Context, mut parse: impl FnMut(&str) -> R + 'static) -> &R { // for the web, attach to the history api cx.use_hook( |f| { // use gloo::events::EventListener; let base = window() .unwrap() .document() .unwrap() .query_selector("base[href]") .ok() .flatten() .and_then(|base| { let base = JsCast::unchecked_into::(base).href(); let url = web_sys::Url::new(&base).unwrap(); if url.pathname() != "/" { Some(strip_slash_suffix(&base).to_string()) } else { None } }); let location = window().unwrap().location(); let pathname = location.pathname().unwrap(); let initial_route = parse(&pathname); let service: RouterService = RouterService { historic_routes: vec![initial_route], history_service: RefCell::new( web_sys::window().unwrap().history().expect("no history"), ), base_ur: RefCell::new(base), }; // let base = base_url(); // let url = route.to_path(); // pending_routes: RefCell::new(vec![]), // service.history_service.push_state(data, title); // cx.provide_state(service); let regenerate = cx.schedule_update(); // // when "back" is called by the user, we want to to re-render the component let listener = EventListener::new(&web_sys::window().unwrap(), "popstate", move |_| { // regenerate(); }); service }, |state| { let base = state.base_ur.borrow(); if let Some(base) = base.as_ref() { // let path = format!("{}{}", base, state.get_current_route()); } let history = state.history_service.borrow(); todo!() // history.state.historic_routes.last().unwrap() // }, ) } pub fn use_router_service(cx: Context) -> Option<&Rc>> { cx.use_hook(|_| cx.consume_state::>(), |f| f.as_ref()) } #[derive(Props)] pub struct LinkProps { to: R, /// The url that gets pushed to the history stack /// /// You can either put it your own inline method or just autoderive the route using `derive(Routable)` /// /// ```rust /// /// Link { to: Route::Home, href: |_| "home".to_string() } /// /// // or /// /// Link { to: Route::Home, href: Route::as_url } /// /// ``` href: fn(&R) -> String, #[builder(default)] children: Element, } pub fn Link(cx: Context, props: &LinkProps) -> Element { let service = use_router_service::(cx)?; cx.render(rsx! { a { href: format_args!("{}", (props.href)(&props.to)), onclick: move |_| service.push_route(props.to.clone()), {&props.children}, } }) }