diff --git a/Cargo.lock b/Cargo.lock index ba1b21b..ef51189 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1482,6 +1482,7 @@ dependencies = [ "egui_animation", "egui_inbox", "egui_suspense", + "form_urlencoded", "js-sys", "matchit", "thiserror", diff --git a/crates/egui_router/CHANGELOG.md b/crates/egui_router/CHANGELOG.md index 09330b6..4da5dab 100644 --- a/crates/egui_router/CHANGELOG.md +++ b/crates/egui_router/CHANGELOG.md @@ -1,5 +1,9 @@ # egui_router changelog +## Unreleased + +- Parse query parameters and expose them in `Request::query` and `OwnedRequest::query` + ## 0.1.2 - Make RouterBuilder public diff --git a/crates/egui_router/Cargo.toml b/crates/egui_router/Cargo.toml index 56c7a92..1ca30e3 100644 --- a/crates/egui_router/Cargo.toml +++ b/crates/egui_router/Cargo.toml @@ -29,6 +29,7 @@ egui_suspense = { workspace = true, optional = true } matchit = "0.8" thiserror = "1" +form_urlencoded = "1" [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = { version = "0.3", features = ["History", "PopStateEvent", "HtmlCollection"] } diff --git a/crates/egui_router/examples/router.rs b/crates/egui_router/examples/router.rs index af8c568..e57f0e2 100644 --- a/crates/egui_router/examples/router.rs +++ b/crates/egui_router/examples/router.rs @@ -2,6 +2,7 @@ use eframe::NativeOptions; use egui::{CentralPanel, Color32, Context, Frame, ScrollArea, Ui, Window}; use egui_inbox::type_inbox::TypeInbox; use egui_router::{EguiRouter, Request, Route, TransitionConfig}; +use std::borrow::Cow; #[derive(Debug, Clone)] struct AppState { @@ -104,6 +105,12 @@ fn home() -> impl Route { .send(RouterMessage::Navigate("/post/2".to_string())); } + if ui.link("Post With Query").clicked() { + state + .inbox + .send(RouterMessage::Navigate("/post/3?search=test".to_string())); + } + if ui.link("Invalid Post").clicked() { state .inbox @@ -132,9 +139,11 @@ fn edit_message() -> impl Route { } } -fn post(request: Request) -> impl Route { +fn post(mut request: Request) -> impl Route { let id = request.params.get("id").map(ToOwned::to_owned); + let search: Option = request.query.remove("search").map(Cow::into_owned); + move |ui: &mut Ui, state: &mut AppState| { background(ui, ui.style().visuals.extreme_bg_color, |ui| { ScrollArea::vertical().show(ui, |ui| { @@ -143,6 +152,10 @@ fn post(request: Request) -> impl Route { ui.label(format!("Id: {:?}", ui.next_auto_id())); + if let Some(search) = &search { + ui.label(format!("Search: {}", search)); + } + if ui.button("back").clicked() { state.inbox.send(RouterMessage::Back); } diff --git a/crates/egui_router/src/lib.rs b/crates/egui_router/src/lib.rs index 814251b..acf04af 100644 --- a/crates/egui_router/src/lib.rs +++ b/crates/egui_router/src/lib.rs @@ -17,6 +17,8 @@ use crate::history::HistoryError; use crate::transition::{ActiveTransition, SlideFadeTransition, SlideTransition, Transition}; use egui::emath::ease_in_ease_out; use egui::{Ui, Vec2}; +use std::borrow::Cow; +use std::collections::BTreeMap; use std::sync::atomic::AtomicUsize; pub use handler::{HandlerError, HandlerResult}; @@ -142,6 +144,8 @@ struct CurrentTransition { pub struct Request<'a, State = ()> { /// The parsed path params pub params: matchit::Params<'a, 'a>, + /// The parsed query params + pub query: BTreeMap, Cow<'a, str>>, /// The custom state pub state: &'a mut State, } @@ -150,7 +154,9 @@ pub struct Request<'a, State = ()> { /// Owned request, passed to [handler::AsyncMakeHandler] pub struct OwnedRequest { /// The parsed path params - pub params: std::collections::BTreeMap, + pub params: BTreeMap, + /// The parsed query params + pub query: BTreeMap, /// The custom state pub state: State, } diff --git a/crates/egui_router/src/router.rs b/crates/egui_router/src/router.rs index 6199610..99da75e 100644 --- a/crates/egui_router/src/router.rs +++ b/crates/egui_router/src/router.rs @@ -7,6 +7,8 @@ use crate::{ }; use egui::Ui; use matchit::MatchError; +use std::borrow::Cow; +use std::collections::BTreeMap; use std::sync::atomic::Ordering; /// A router instance @@ -52,7 +54,7 @@ impl EguiRouter { { router .navigate_impl(state, r, TransitionConfig::none(), state_index.unwrap_or(0)) - .ok(); + .unwrap(); } router @@ -63,6 +65,13 @@ impl EguiRouter { self.history.last().map(|r| r.path.as_str()) } + fn parse_query(path: &str) -> BTreeMap, Cow> { + let query = path.split_once('?').map(|(_, q)| q); + query + .map(|q| form_urlencoded::parse(q.as_bytes()).collect()) + .unwrap_or(BTreeMap::new()) + } + fn navigate_impl( &mut self, state: &mut State, @@ -70,6 +79,8 @@ impl EguiRouter { transition_config: TransitionConfig, new_state: u32, ) -> RouterResult { + let query = Self::parse_query(&path); + let mut redirect = None; let result = self.router.at_mut(&path); @@ -80,6 +91,7 @@ impl EguiRouter { let route = handler(Request { state, params: match_.params, + query, }); self.history.push(RouteState { path, @@ -171,6 +183,8 @@ impl EguiRouter { let current_state = self.history.last().map(|r| r.state).unwrap_or(0); let new_state = current_state; + let query = Self::parse_query(&path); + let result = match result { Ok(match_) => match match_.value { RouteKind::Route(handler) => { @@ -179,6 +193,7 @@ impl EguiRouter { let route = handler(Request { state, params: match_.params, + query, }); self.history.push(RouteState { path, diff --git a/crates/egui_router/src/router_builder.rs b/crates/egui_router/src/router_builder.rs index eb560ea..8702339 100644 --- a/crates/egui_router/src/router_builder.rs +++ b/crates/egui_router/src/router_builder.rs @@ -200,6 +200,11 @@ impl RouterBuilder { .iter() .map(|(k, v)| (k.to_string(), v.to_string())) .collect(), + query: req + .query + .into_iter() + .map(|(k, v)| (k.into_owned(), v.into_owned())) + .collect(), state: req.state.clone(), };