Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify Router #97

Merged
merged 2 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 8 additions & 21 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions crates/kobold_router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ documentation = "https://docs.rs/kobold"
[dependencies]
kobold = { path = "../kobold" }
matchit = "0.8.0"
serde = { version = "1.0.197", features = ["derive"] }
serde-wasm-bindgen = "0.4"
wasm-bindgen = "0.2.84"
wasm-bindgen = "0.2.92"

[dependencies.web-sys]
version = "0.3"
Expand Down
96 changes: 34 additions & 62 deletions crates/kobold_router/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
use std::str::FromStr;

use kobold::dom::Mountable;
use kobold::internal::In;
use kobold::prelude::*;

use matchit::Match;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use wasm_bindgen::{closure::Closure, JsCast, JsValue, UnwrapThrowExt};

mod internal;

/// Routes type
type Routes = matchit::Router<Box<dyn Fn()>>;
type Params = HashMap<String, String>;
type Routes = matchit::Router<Box<dyn Fn(Params)>>;

/// A web router for Kobold
pub struct Router {
router: Rc<RefCell<Routes>>,
router: Routes,
}

/// Get the current path via web_sys
Expand All @@ -32,71 +31,56 @@ pub fn get_path() -> String {
pub enum ParamError {
CouldNotFindParam,
CouldNotParseParam,
ParamsNotSet,
}

/// Implement of [Router]
impl Router {
pub fn new() -> Self {
let router = Rc::new(RefCell::new(matchit::Router::new()));
let router = matchit::Router::new();
Router { router }
}

/// Add a route to the router
pub fn add_route<F>(&mut self, route: &str, view: F)
pub fn add_route<F, V>(&mut self, route: &str, render: F)
where
F: Fn() + 'static,
F: Fn(Params) -> V + 'static,
V: View,
{
self.router
.borrow_mut()
.insert(route, Box::new(move || view()))
.insert(
route,
Box::new(move |params| {
let view = render(params);

start_route(view)
}),
)
.expect_throw("Failed to insert route");
}

/// Starts and hosts your web app with a router
pub fn start(&mut self) {
pub fn start(self) {
kobold::start(view! {
<div id="routerView"></div>
});

let local_router = Rc::clone(&self.router);

let window = web_sys::window().expect_throw("no window");
//This is what decides what is render and triggered by pushState
let conditonal_router_render: Closure<dyn FnMut()> = Closure::new(
move || match local_router.borrow().at(get_path().as_str()) {
let conditonal_router_render: Closure<dyn FnMut()> =
Closure::new(move || match self.router.at(get_path().as_str()) {
Ok(Match {
value: render_view_fn,
value: render,
params,
}) => {
let history = web_sys::window()
.expect_throw("no window")
.history()
.expect_throw("no history");

let params = params
.iter()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect::<Params>();

match serde_wasm_bindgen::to_value(&params) {
Ok(new_state) => {
history
.replace_state(&new_state, "")
.expect_throw("failed to replace state");
}
Err(_) => {}
}

//Runs the Fn() to render out the view
render_view_fn();
render(Params(params));
}
//TODO add ability to load your own 404 page. Possibly view a macro, or raw html
Err(_) => start_route(view! {
<h1> "404" </h1>
}),
},
);
});

let window = web_sys::window().expect_throw("no window");

//Sets up a listener for pushState events
internal::setup_push_state_event();
Expand All @@ -116,7 +100,7 @@ impl Router {
}

/// Start a route with a view
pub fn start_route(view: impl View) {
fn start_route(view: impl View) {
use std::mem::MaybeUninit;
use std::pin::pin;

Expand All @@ -136,24 +120,20 @@ pub fn navigate(path: &str) {
.expect_throw("failed to push state");
}

/// Get the value of a parameter from the current route
pub fn get_param<T: std::str::FromStr>(key: &str) -> Result<T, ParamError> {
let history_state = web_sys::window()
.expect_throw("no window")
.history()
.expect_throw("no history")
.state()
.expect_throw("no state");
pub struct Params<'p>(matchit::Params<'p, 'p>);

match serde_wasm_bindgen::from_value::<Params>(history_state) {
Ok(params) => match params.get(key) {
impl Params<'_> {
pub fn get<T>(&self, key: &str) -> Result<T, ParamError>
where
T: FromStr,
{
match self.0.get(key) {
Some(value) => match value.parse::<T>() {
Ok(value) => Ok(value),
Err(_) => Err(ParamError::CouldNotParseParam),
},
None => Err(ParamError::CouldNotFindParam),
},
Err(_) => Err(ParamError::ParamsNotSet),
}
}
}

Expand All @@ -173,11 +153,3 @@ pub fn link<'a>(route: &'a str, class: &'a str, children: impl View + 'a) -> imp
<a href={href} {class} {onclick}>{children}</a>
}
}

/// Allows short hand for creating a fn
#[macro_export]
macro_rules! route_view {
($view:expr) => {
|| crate::start_route($view)
};
}
33 changes: 12 additions & 21 deletions examples/router/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
use kobold::prelude::*;
use kobold::View;
use kobold_router::get_param;
use kobold_router::link;
use kobold_router::route_view;
use kobold_router::start_route;
use kobold_router::Router;
use kobold_router::{link, Params, Router};
use wasm_bindgen::JsValue;
use web_sys::console::error_1;
use web_sys::HtmlInputElement;

#[component]
fn inventory() -> impl View + 'static {
let attempt_to_get_id = get_param::<usize>("id");
fn inventory(params: Params) -> impl View + 'static {
let attempt_to_get_id = params.get("id");

let id = match attempt_to_get_id {
Ok(id) => Some(id),
Expand Down Expand Up @@ -76,26 +72,21 @@ fn route_two(state: &Hook<State>) -> impl View + '_ {
}
}

fn get_router() -> Router {
fn main() {
let mut router = Router::new();
router.add_route(
"/",
route_view!(view! {

router.add_route("/", |_| {
view! {
<h1>{"Welcome to the router example!"}</h1>
<!link route={"/one"}>"View your first route here!"</!link>

}),
);

router.add_route("/one", route_view!(stateful(State::default, route_one)));
router.add_route("/two", route_view!(stateful(State::default, route_two)));
router.add_route("/inventory/{id}", route_view!(view!(<!inventory>)));
}
});

router
}
router.add_route("/one", |_| stateful(State::default, route_one));
router.add_route("/two", |_| stateful(State::default, route_two));
router.add_route("/inventory/{id}", |params| view!(<!inventory {params}>));

fn main() {
let mut router = get_router();
router.start();
}

Expand Down
Loading