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
use js_sys::Function; use plaster::callback::Callback; use route_recognizer::{Params, Router as RecRouter}; use std::sync::{Arc, Mutex}; use wasm_bindgen::prelude::*; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{window, Event}; use log::trace; pub use plaster_router_macro::Routes; pub struct Router<T> { routes: Vec<fn(Params) -> T>, index_router: RecRouter<usize>, current_path: Arc<Mutex<String>>, listener: Closure<dyn FnMut(JsValue)>, callback: Callback<()>, } impl<T> Router<T> { pub fn new(callback: Callback<()>) -> Router<T> { let win = window().expect("need a window context"); let path = win.location().pathname().unwrap_or("/".to_string()); trace!("initial route: {}", &path); let current_path = Arc::new(Mutex::new(path)); let current_path_c = current_path.clone(); let callback_c = callback.clone(); let listener_callback = Closure::wrap(Box::new(move |_: JsValue| { let path = window() .expect("need a window context") .location() .pathname() .unwrap_or("/".to_string()); trace!("route change: {}", &path); *current_path_c.lock().unwrap() = path; callback_c.emit(()); }) as Box<dyn FnMut(_)>); let listener_function: &Function = listener_callback.as_ref().unchecked_ref(); win.add_event_listener_with_callback("plasterroutechange", listener_function) .expect("could not attach global event listener"); win.add_event_listener_with_callback("popstate", listener_function) .expect("could not attach popstate event listener"); Router { routes: Vec::new(), index_router: RecRouter::new(), current_path: current_path, listener: listener_callback, callback: callback, } } pub fn add_route(&mut self, route: &str, closure: fn(Params) -> T) { trace!("added route: {}", route); let index = self.routes.len(); self.routes.push(closure); self.index_router.add(route, index); } pub fn navigate(&mut self, path: &str) { *self.current_path.lock().unwrap() = path.to_string(); self.push_state(); self.callback.emit(()); } pub fn resolve(&self) -> Option<T> { let route_match = self .index_router .recognize(&self.current_path.lock().unwrap()) .ok(); route_match.map(|m| self.routes.get(m.handler.clone()).unwrap()(m.params)) } fn push_state(&self) { match window().expect("need a window context").history() { Ok(history) => { history .push_state_with_url( &JsValue::NULL, "", Some(&self.current_path.lock().unwrap()), ) .expect("could not pushState"); } Err(_) => (), } } } impl<T> Drop for Router<T> { fn drop(&mut self) { window() .expect("need window context") .remove_event_listener_with_callback( "plasterroutechange", self.listener.as_ref().unchecked_ref(), ) .expect("could not remove event listener"); } } pub trait Routes<T> { fn router(callback: Callback<()>) -> Router<T>; } pub fn route_to(path: &str) { let win = window().expect("need window context"); win.history() .expect("history API unavailable") .push_state_with_url(&JsValue::NULL, "", Some(path)) .expect("could not pushState"); win.dispatch_event(&Event::new("plasterroutechange").expect("could not create event")) .expect("could not dispatch event"); }