wip: add router

This commit is contained in:
Jonathan Kelley committed 2021-11-03 00:35:56 -04:00
1 parent 9874f342e2
commit d298b626d3
17 files changed
+617 -20

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
[package]
name = "dioxus-router"
version = "0.0.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = { path = "../core", version = "0.1.3", default-features = false }
dioxus-core-macro = { path = "../core-macro", version = "0.1" }
dioxus-html = { path = "../html", version = "0.0.0", default-features = false }
web-sys = { version = "0.3", features = [
"Attr",
"Document",
"History",
"HtmlBaseElement",
"Event",
"NamedNodeMap",
"Url",
"UrlSearchParams",
"Window",
], optional = true }
serde = "1"
serde_urlencoded = "0.7"
wasm-bindgen = "0.2"
js-sys = "0.3"
gloo = "0.4"
route-recognizer = "0.3.1"
[features]
default = ["web"]
web = ["web-sys"]
desktop = []
mobile = []
+28
View File
@@ -0,0 +1,28 @@
use dioxus::prelude::*;
fn main() {}
enum Routes {
Home,
Blog,
}
static App: FC<()> = |(cx, props)| {
let route = use_router::<Routes>(cx);
let content = match route {
Routes::Home => rsx!(Home {}),
Routes::Blog => rsx!(Blog {}),
};
cx.render(rsx! {
{content}
})
};
fn use_router<P>(cx: Context) -> &P {
todo!()
}
static Home: FC<()> = |(cx, props)| todo!();
static Blog: FC<()> = |(cx, props)| todo!();
+88
View File
@@ -0,0 +1,88 @@
use dioxus::prelude::*;
use dioxus_router::{use_router, Link, Routable};
fn main() {
dbg!(App as *const fn());
}
#[derive(PartialEq, Debug, Clone)]
pub enum Route {
// #[at("/")]
Home,
// #[at("/:id")]
AllUsers { page: u32 },
// #[at("/:id")]
User { id: u32 },
// #[at("/:id")]
BlogList { page: u32 },
// #[at("/:id")]
BlogPost { post_id: u32 },
// #[at("/404")]
// #[not_found]
NotFound,
}
static App: FC<()> = |(cx, props)| {
let route = use_router(cx, Route::parse)?;
cx.render(rsx! {
nav {
Link { to: Route::Home, "Go home!" }
Link { to: Route::AllUsers { page: 0 }, "List all users" }
Link { to: Route::BlogList { page: 0 }, "Blog posts" }
}
{match route {
Route::Home => rsx!("Home"),
Route::AllUsers { page } => rsx!("All users - page {page}"),
Route::User { id } => rsx!("User - id: {id}"),
Route::BlogList { page } => rsx!("Blog posts - page {page}"),
Route::BlogPost { post_id } => rsx!("Blog post - post {post_id}"),
Route::NotFound => rsx!("Not found"),
}}
footer {}
})
};
impl Route {
// Generate the appropriate route from the "tail" end of the URL
fn parse(url: &str) -> Self {
use Route::*;
match url {
"/" => Home,
"/users" => AllUsers { page: 1 },
"/users/:page" => AllUsers { page: 1 },
"/users/:page/:id" => User { id: 1 },
"/blog" => BlogList { page: 1 },
"/blog/:page" => BlogList { page: 1 },
"/blog/:page/:id" => BlogPost { post_id: 1 },
_ => NotFound,
}
}
}
impl Routable for Route {
fn from_path(path: &str, params: &std::collections::HashMap<&str, &str>) -> Option<Self> {
todo!()
}
fn to_path(&self) -> String {
todo!()
}
fn routes() -> Vec<&'static str> {
todo!()
}
fn not_found_route() -> Option<Self> {
todo!()
}
fn recognize(pathname: &str) -> Option<Self> {
todo!()
}
}
+140
View File
@@ -0,0 +1,140 @@
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::JsValue;
use web_sys::Event;
use crate::utils::fetch_base_url;
pub struct RouterService<R: Routable> {
history: RefCell<Vec<R>>,
base_ur: RefCell<Option<String>>,
}
impl<R: Routable> RouterService<R> {
fn push_route(&self, r: R) {
self.history.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<R: Routable>(cx: Context, cfg: impl FnOnce(&str) -> R) -> Option<R> {
// for the web, attach to the history api
cx.use_hook(
|f| {
//
use gloo::events::EventListener;
let base_url = fetch_base_url();
let service: RouterService<R> = RouterService {
history: RefCell::new(vec![]),
base_ur: RefCell::new(base_url),
};
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();
});
},
|f| {
//
},
|f| {
//
},
);
let router = use_router_service::<R>(cx)?;
Some(cfg(router.get_current_route()))
}
pub fn use_router_service<R: Routable>(cx: Context) -> Option<&Rc<RouterService<R>>> {
cx.use_hook(
|_| cx.consume_state::<RouterService<R>>(),
|f| f.as_ref(),
|f| {},
)
}
#[derive(Props)]
pub struct LinkProps<'a, R: Routable> {
to: R,
children: ScopeChildren<'a>,
}
pub fn Link<'a, R: Routable>((cx, props): Scope<'a, LinkProps<'a, R>>) -> Element {
let service = use_router_service::<R>(cx)?;
cx.render(rsx! {
a {
href: format_args!("{}", props.to.to_path()),
onclick: move |_| service.push_route(props.to.clone()),
{&props.children},
}
})
}
pub trait Routable: Sized + Clone + 'static {
/// Converts path to an instance of the routes enum.
fn from_path(path: &str, params: &HashMap<&str, &str>) -> Option<Self>;
/// Converts the route to a string that can passed to the history API.
fn to_path(&self) -> String;
/// Lists all the available routes
fn routes() -> Vec<&'static str>;
/// The route to redirect to on 404
fn not_found_route() -> Option<Self>;
/// Match a route based on the path
fn recognize(pathname: &str) -> Option<Self>;
}
+1
View File
@@ -0,0 +1 @@
Whitespace-only changes.
Whitespace-only changes.
View File
Whitespace-only changes.
View File
Whitespace-only changes.
+31
View File
@@ -0,0 +1,31 @@
use wasm_bindgen::JsCast;
use web_sys::window;
pub fn fetch_base_url() -> Option<String> {
match window()
.unwrap()
.document()
.unwrap()
.query_selector("base[href]")
{
Ok(Some(base)) => {
let base = JsCast::unchecked_into::<web_sys::HtmlBaseElement>(base).href();
let url = web_sys::Url::new(&base).unwrap();
let base = url.pathname();
let base = if base != "/" {
strip_slash_suffix(&base)
} else {
return None;
};
Some(base.to_string())
}
_ => None,
}
}
pub(crate) fn strip_slash_suffix(path: &str) -> &str {
path.strip_suffix('/').unwrap_or(path)
}