//! Tiny hash-based router: `#/r/{root_id}/{segment/...}` (files) or `#/` //! (root picker). The URL hash is the single source of truth; the //! `Location` signal in the app mirrors it. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Location { /// None = the top-level "folders" view (root picker). pub root_id: Option, /// Path segments relative to the current root. pub path: Vec, } impl Location { pub const fn root() -> Self { Self { root_id: None, path: Vec::new(), } } } pub fn parse_location() -> Location { let hash = web_sys::window() .and_then(|w| w.location().hash().ok()) .unwrap_or_default(); let h = hash.trim_start_matches('#'); let h = h.trim_start_matches('/'); let mut parts = h.split('/').filter(|s| !s.is_empty()); match parts.next() { Some("r") => match parts.next().and_then(|s| s.parse().ok()) { Some(root_id) => Location { root_id: Some(root_id), path: parts.map(String::from).collect(), }, None => Location::root(), }, _ => Location::root(), } } pub fn location_to_hash(loc: &Location) -> String { match loc.root_id { None => "#/".to_string(), Some(id) => { let mut s = format!("#/r/{id}"); for seg in &loc.path { s.push('/'); s.push_str(seg); } s } } } /// Change the app location (updates the hash; the app listens for hashchange). pub fn navigate(loc: &Location) { if let Some(w) = web_sys::window() { let _ = w.location().set_hash(&location_to_hash(loc)); } }