mirror of
https://github.com/DioxusLabs/dioxus
synced 2025-02-17 06:08:26 +00:00
wip: updates to router
This commit is contained in:
parent
a5f05d73ac
commit
bab21a0aa1
8 changed files with 208 additions and 217 deletions
|
@ -3,8 +3,11 @@ This example is a simple iOS-style calculator. This particular example can run a
|
||||||
This calculator version uses React-style state management. All state is held as individual use_states.
|
This calculator version uses React-style state management. All state is held as individual use_states.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use dioxus::events::*;
|
use dioxus::events::*;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use separator::Separatable;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::launch(APP, |cfg| cfg);
|
dioxus::desktop::launch(APP, |cfg| cfg);
|
||||||
|
@ -15,48 +18,12 @@ const APP: FC<()> = |cx, _| {
|
||||||
let operator = use_state(cx, || None as Option<&'static str>);
|
let operator = use_state(cx, || None as Option<&'static str>);
|
||||||
let display_value = use_state(cx, || String::from(""));
|
let display_value = use_state(cx, || String::from(""));
|
||||||
|
|
||||||
let clear_display = display_value == "0";
|
let toggle_percent = move |_| todo!();
|
||||||
let clear_text = if clear_display { "C" } else { "AC" };
|
|
||||||
|
|
||||||
let input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str());
|
let input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str());
|
||||||
|
|
||||||
let input_dot = move || display_value.modify().push_str(".");
|
rsx!(cx, div {
|
||||||
|
class: "calculator",
|
||||||
let perform_operation = move || {
|
onkeydown: move |evt| match evt.key_code {
|
||||||
if let Some(op) = operator.as_ref() {
|
|
||||||
let rhs = display_value.parse::<f64>().unwrap();
|
|
||||||
let new_val = match *op {
|
|
||||||
"+" => *cur_val + rhs,
|
|
||||||
"-" => *cur_val - rhs,
|
|
||||||
"*" => *cur_val * rhs,
|
|
||||||
"/" => *cur_val / rhs,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
cur_val.set(new_val);
|
|
||||||
display_value.set(new_val.to_string());
|
|
||||||
operator.set(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let toggle_sign = move |_| {
|
|
||||||
if display_value.starts_with("-") {
|
|
||||||
display_value.set(display_value.trim_start_matches("-").to_string())
|
|
||||||
} else {
|
|
||||||
display_value.set(format!("-{}", *display_value))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let toggle_percent = move |_| todo!();
|
|
||||||
|
|
||||||
let clear_key = move |_| {
|
|
||||||
display_value.set("0".to_string());
|
|
||||||
if !clear_display {
|
|
||||||
operator.set(None);
|
|
||||||
cur_val.set(0.0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let keydownhandler = move |evt: KeyboardEvent| match evt.key_code {
|
|
||||||
KeyCode::Add => operator.set(Some("+")),
|
KeyCode::Add => operator.set(Some("+")),
|
||||||
KeyCode::Subtract => operator.set(Some("-")),
|
KeyCode::Subtract => operator.set(Some("-")),
|
||||||
KeyCode::Divide => operator.set(Some("/")),
|
KeyCode::Divide => operator.set(Some("/")),
|
||||||
|
@ -77,23 +44,41 @@ const APP: FC<()> = |cx, _| {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
}
|
||||||
|
div { class: "calculator-display", {[format_args!("{}", cur_val.separated_string())]} }
|
||||||
use separator::Separatable;
|
|
||||||
let formatted_display = cur_val.separated_string();
|
|
||||||
|
|
||||||
rsx!(cx, div {
|
|
||||||
class: "calculator", onkeydown: {keydownhandler}
|
|
||||||
div { class: "calculator-display", "{formatted_display}" }
|
|
||||||
div { class: "input-keys"
|
div { class: "input-keys"
|
||||||
div { class: "function-keys"
|
div { class: "function-keys"
|
||||||
CalculatorKey { name: "key-clear", onclick: {clear_key} "{clear_text}" }
|
CalculatorKey {
|
||||||
CalculatorKey { name: "key-sign", onclick: {toggle_sign}, "±"}
|
{[if display_value == "0" { "C" } else { "AC" }]}
|
||||||
CalculatorKey { name: "key-percent", onclick: {toggle_percent} "%"}
|
name: "key-clear",
|
||||||
|
onclick: move |_| {
|
||||||
|
display_value.set("0".to_string());
|
||||||
|
if display_value != "0" {
|
||||||
|
operator.set(None);
|
||||||
|
cur_val.set(0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CalculatorKey {
|
||||||
|
"±"
|
||||||
|
name: "key-sign",
|
||||||
|
onclick: move |_| {
|
||||||
|
if display_value.starts_with("-") {
|
||||||
|
display_value.set(display_value.trim_start_matches("-").to_string())
|
||||||
|
} else {
|
||||||
|
display_value.set(format!("-{}", *display_value))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
CalculatorKey {
|
||||||
|
"%"
|
||||||
|
onclick: {toggle_percent}
|
||||||
|
name: "key-percent",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
div { class: "digit-keys"
|
div { class: "digit-keys"
|
||||||
CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
|
CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
|
||||||
CalculatorKey { name: "key-dot", onclick: move |_| input_dot(), "●" }
|
CalculatorKey { name: "key-dot", onclick: move |_| display_value.modify().push_str("."), "●" }
|
||||||
|
|
||||||
{(1..9).map(|k| rsx!{
|
{(1..9).map(|k| rsx!{
|
||||||
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
|
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
|
||||||
|
@ -104,7 +89,25 @@ const APP: FC<()> = |cx, _| {
|
||||||
CalculatorKey { name: "key-multiply", onclick: move |_| operator.set(Some("*")) "×" }
|
CalculatorKey { name: "key-multiply", onclick: move |_| operator.set(Some("*")) "×" }
|
||||||
CalculatorKey { name: "key-subtract", onclick: move |_| operator.set(Some("-")) "−" }
|
CalculatorKey { name: "key-subtract", onclick: move |_| operator.set(Some("-")) "−" }
|
||||||
CalculatorKey { name: "key-add", onclick: move |_| operator.set(Some("+")) "+" }
|
CalculatorKey { name: "key-add", onclick: move |_| operator.set(Some("+")) "+" }
|
||||||
CalculatorKey { name: "key-equals", onclick: move |_| perform_operation() "=" }
|
CalculatorKey {
|
||||||
|
"="
|
||||||
|
name: "key-equals",
|
||||||
|
onclick: move |_| {
|
||||||
|
if let Some(op) = operator.as_ref() {
|
||||||
|
let rhs = display_value.parse::<f64>().unwrap();
|
||||||
|
let new_val = match *op {
|
||||||
|
"+" => *cur_val + rhs,
|
||||||
|
"-" => *cur_val - rhs,
|
||||||
|
"*" => *cur_val * rhs,
|
||||||
|
"/" => *cur_val / rhs,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
cur_val.set(new_val);
|
||||||
|
display_value.set(new_val.to_string());
|
||||||
|
operator.set(None);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -113,7 +116,7 @@ const APP: FC<()> = |cx, _| {
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
struct CalculatorKeyProps<'a> {
|
struct CalculatorKeyProps<'a> {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
onclick: &'a dyn Fn(MouseEvent),
|
onclick: &'a dyn Fn(Arc<MouseEvent>),
|
||||||
children: Element,
|
children: Element,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -206,43 +206,43 @@ pub fn routable_derive_impl(input: Routable) -> TokenStream {
|
||||||
// static #cache_thread_local_ident: ::std::cell::RefCell<::std::option::Option<#ident>> = ::std::cell::RefCell::new(::std::option::Option::None);
|
// static #cache_thread_local_ident: ::std::cell::RefCell<::std::option::Option<#ident>> = ::std::cell::RefCell::new(::std::option::Option::None);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[automatically_derived]
|
// #[automatically_derived]
|
||||||
impl ::dioxus::router::Routable for #ident {
|
// impl ::dioxus::router::Routable for #ident {
|
||||||
#from_path
|
// #from_path
|
||||||
#to_path
|
// #to_path
|
||||||
|
|
||||||
fn routes() -> ::std::vec::Vec<&'static str> {
|
// // fn routes() -> ::std::vec::Vec<&'static str> {
|
||||||
::std::vec![#(#ats),*]
|
// // ::std::vec![#(#ats),*]
|
||||||
}
|
// // }
|
||||||
|
|
||||||
fn not_found_route() -> ::std::option::Option<Self> {
|
// fn not_found_route() -> ::std::option::Option<Self> {
|
||||||
#not_found_route
|
// #not_found_route
|
||||||
}
|
|
||||||
|
|
||||||
// fn current_route() -> ::std::option::Option<Self> {
|
|
||||||
// #cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow()))
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fn recognize(pathname: &str) -> ::std::option::Option<Self> {
|
// // fn current_route() -> ::std::option::Option<Self> {
|
||||||
todo!()
|
// // #cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow()))
|
||||||
// ::std::thread_local! {
|
// // }
|
||||||
// static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>();
|
|
||||||
// }
|
|
||||||
// let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname));
|
|
||||||
// {
|
|
||||||
// let route = ::std::clone::Clone::clone(&route);
|
|
||||||
// #cache_thread_local_ident.with(move |val| {
|
|
||||||
// *val.borrow_mut() = route;
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// route
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn cleanup() {
|
// fn recognize(pathname: &str) -> ::std::option::Option<Self> {
|
||||||
// #cache_thread_local_ident.with(move |val| {
|
// todo!()
|
||||||
// *val.borrow_mut() = ::std::option::Option::None;
|
// // ::std::thread_local! {
|
||||||
// });
|
// // static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>();
|
||||||
|
// // }
|
||||||
|
// // let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname));
|
||||||
|
// // {
|
||||||
|
// // let route = ::std::clone::Clone::clone(&route);
|
||||||
|
// // #cache_thread_local_ident.with(move |val| {
|
||||||
|
// // *val.borrow_mut() = route;
|
||||||
|
// // });
|
||||||
|
// // }
|
||||||
|
// // route
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // fn cleanup() {
|
||||||
|
// // #cache_thread_local_ident.with(move |val| {
|
||||||
|
// // *val.borrow_mut() = ::std::option::Option::None;
|
||||||
|
// // });
|
||||||
|
// // }
|
||||||
// }
|
// }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,6 @@ pub struct SelfReferentialItems<'a> {
|
||||||
pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
|
pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
|
||||||
pub(crate) suspended_nodes: FxHashMap<u64, &'a VSuspended>,
|
pub(crate) suspended_nodes: FxHashMap<u64, &'a VSuspended>,
|
||||||
pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
|
pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
|
||||||
pub(crate) pending_effects: Vec<BumpBox<'a, dyn FnMut()>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A component's unique identifier.
|
/// A component's unique identifier.
|
||||||
|
@ -284,24 +283,6 @@ impl Scope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push an effect to be ran after the component has been successfully mounted to the dom
|
|
||||||
/// Returns the effect's position in the stack
|
|
||||||
pub fn push_effect<'src>(&'src self, effect: impl FnOnce() + 'src) -> usize {
|
|
||||||
// this is some tricker to get around not being able to actually call fnonces
|
|
||||||
let mut slot = Some(effect);
|
|
||||||
let fut: &mut dyn FnMut() = self.bump().alloc(move || slot.take().unwrap()());
|
|
||||||
|
|
||||||
// wrap it in a type that will actually drop the contents
|
|
||||||
let boxed_fut = unsafe { BumpBox::from_raw(fut) };
|
|
||||||
|
|
||||||
// erase the 'src lifetime for self-referential storage
|
|
||||||
let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
|
|
||||||
|
|
||||||
let mut items = self.items.borrow_mut();
|
|
||||||
items.pending_effects.push(self_ref_fut);
|
|
||||||
items.pending_effects.len() - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes the future onto the poll queue to be polled
|
/// Pushes the future onto the poll queue to be polled
|
||||||
/// The future is forcibly dropped if the component is not ready by the next render
|
/// The future is forcibly dropped if the component is not ready by the next render
|
||||||
pub fn push_task<'src, F: Future<Output = ()>>(
|
pub fn push_task<'src, F: Future<Output = ()>>(
|
||||||
|
@ -483,13 +464,6 @@ impl Scope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// run the list of effects
|
|
||||||
pub(crate) fn run_effects(&mut self) {
|
|
||||||
for mut effect in self.items.get_mut().pending_effects.drain(..) {
|
|
||||||
effect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn root_node(&self) -> &VNode {
|
pub fn root_node(&self) -> &VNode {
|
||||||
let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
|
let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
|
||||||
unsafe { std::mem::transmute(&*node) }
|
unsafe { std::mem::transmute(&*node) }
|
||||||
|
|
|
@ -188,7 +188,6 @@ impl ScopeArena {
|
||||||
borrowed_props: Default::default(),
|
borrowed_props: Default::default(),
|
||||||
suspended_nodes: Default::default(),
|
suspended_nodes: Default::default(),
|
||||||
tasks: Default::default(),
|
tasks: Default::default(),
|
||||||
pending_effects: Default::default(),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -226,14 +225,12 @@ impl ScopeArena {
|
||||||
let SelfReferentialItems {
|
let SelfReferentialItems {
|
||||||
borrowed_props,
|
borrowed_props,
|
||||||
listeners,
|
listeners,
|
||||||
pending_effects,
|
|
||||||
suspended_nodes,
|
suspended_nodes,
|
||||||
tasks,
|
tasks,
|
||||||
} = scope.items.get_mut();
|
} = scope.items.get_mut();
|
||||||
|
|
||||||
borrowed_props.clear();
|
borrowed_props.clear();
|
||||||
listeners.clear();
|
listeners.clear();
|
||||||
pending_effects.clear();
|
|
||||||
suspended_nodes.clear();
|
suspended_nodes.clear();
|
||||||
tasks.clear();
|
tasks.clear();
|
||||||
|
|
||||||
|
@ -328,14 +325,12 @@ impl ScopeArena {
|
||||||
// just forget about our suspended nodes while we're at it
|
// just forget about our suspended nodes while we're at it
|
||||||
items.suspended_nodes.clear();
|
items.suspended_nodes.clear();
|
||||||
items.tasks.clear();
|
items.tasks.clear();
|
||||||
items.pending_effects.clear();
|
|
||||||
|
|
||||||
// guarantee that we haven't screwed up - there should be no latent references anywhere
|
// guarantee that we haven't screwed up - there should be no latent references anywhere
|
||||||
debug_assert!(items.listeners.is_empty());
|
debug_assert!(items.listeners.is_empty());
|
||||||
debug_assert!(items.borrowed_props.is_empty());
|
debug_assert!(items.borrowed_props.is_empty());
|
||||||
debug_assert!(items.suspended_nodes.is_empty());
|
debug_assert!(items.suspended_nodes.is_empty());
|
||||||
debug_assert!(items.tasks.is_empty());
|
debug_assert!(items.tasks.is_empty());
|
||||||
debug_assert!(items.pending_effects.is_empty());
|
|
||||||
|
|
||||||
// Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
|
// Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
|
||||||
scope.wip_frame().nodes.borrow_mut().clear();
|
scope.wip_frame().nodes.borrow_mut().clear();
|
||||||
|
|
|
@ -409,7 +409,7 @@ class Interpreter {
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
let root = window.document.getElementById("_dioxusroot");
|
let root = window.document.getElementById("main");
|
||||||
window.interpreter = new Interpreter(root);
|
window.interpreter = new Interpreter(root);
|
||||||
console.log(window.interpreter);
|
console.log(window.interpreter);
|
||||||
|
|
||||||
|
|
|
@ -5,41 +5,35 @@ use dioxus_router::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||||
dioxus_web::launch(App, |c| c);
|
dioxus_web::launch(App, |c| c);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
static App: FC<()> = |cx, props| {
|
||||||
enum Route {
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
enum Route {
|
||||||
Home,
|
Home,
|
||||||
About,
|
About,
|
||||||
NotFound,
|
NotFound,
|
||||||
}
|
|
||||||
|
|
||||||
static App: FC<()> = |cx, props| {
|
|
||||||
let route = use_router(cx, Route::parse);
|
|
||||||
|
|
||||||
match route {
|
|
||||||
Route::Home => rsx!(cx, div { "Home" }),
|
|
||||||
Route::About => rsx!(cx, div { "About" }),
|
|
||||||
Route::NotFound => rsx!(cx, div { "NotFound" }),
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
impl ToString for Route {
|
let route = use_router(cx, |s| match s {
|
||||||
fn to_string(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Route::Home => "/".to_string(),
|
|
||||||
Route::About => "/about".to_string(),
|
|
||||||
Route::NotFound => "/404".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Route {
|
|
||||||
fn parse(s: &str) -> Self {
|
|
||||||
match s {
|
|
||||||
"/" => Route::Home,
|
"/" => Route::Home,
|
||||||
"/about" => Route::About,
|
"/about" => Route::About,
|
||||||
_ => Route::NotFound,
|
_ => Route::NotFound,
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
{match route {
|
||||||
|
Route::Home => rsx!(h1 { "Home" }),
|
||||||
|
Route::About => rsx!(h1 { "About" }),
|
||||||
|
Route::NotFound => rsx!(h1 { "NotFound" }),
|
||||||
|
}}
|
||||||
|
nav {
|
||||||
|
Link { to: Route::Home, href: |_| "/".to_string() }
|
||||||
|
Link { to: Route::About, href: |_| "/about".to_string() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
};
|
||||||
|
|
|
@ -6,23 +6,24 @@ use dioxus_core as dioxus;
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_core_macro::{format_args_f, rsx, Props};
|
use dioxus_core_macro::{format_args_f, rsx, Props};
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
use web_sys::Event;
|
use web_sys::{window, Event};
|
||||||
|
|
||||||
use crate::utils::fetch_base_url;
|
use crate::utils::strip_slash_suffix;
|
||||||
|
|
||||||
pub trait Routable: 'static + Send + Clone + ToString + PartialEq {}
|
pub trait Routable: 'static + Send + Clone + PartialEq {}
|
||||||
impl<T> Routable for T where T: 'static + Send + Clone + ToString + PartialEq {}
|
impl<T> Routable for T where T: 'static + Send + Clone + PartialEq {}
|
||||||
|
|
||||||
pub struct RouterService<R: Routable> {
|
pub struct RouterService<R: Routable> {
|
||||||
historic_routes: RefCell<Vec<R>>,
|
historic_routes: Vec<R>,
|
||||||
history_service: web_sys::History,
|
history_service: RefCell<web_sys::History>,
|
||||||
base_ur: RefCell<Option<String>>,
|
base_ur: RefCell<Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Routable> RouterService<R> {
|
impl<R: Routable> RouterService<R> {
|
||||||
fn push_route(&self, r: R) {
|
fn push_route(&self, r: R) {
|
||||||
self.historic_routes.borrow_mut().push(r);
|
todo!()
|
||||||
|
// self.historic_routes.borrow_mut().push(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_current_route(&self) -> &str {
|
fn get_current_route(&self) -> &str {
|
||||||
|
@ -65,41 +66,73 @@ impl<R: Routable> RouterService<R> {
|
||||||
/// This hould only be used once per app
|
/// 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
|
/// 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, parse: impl FnMut(&str) -> R) -> &R {
|
pub fn use_router<R: Routable>(cx: Context, mut parse: impl FnMut(&str) -> R + 'static) -> &R {
|
||||||
// for the web, attach to the history api
|
// for the web, attach to the history api
|
||||||
cx.use_hook(
|
cx.use_hook(
|
||||||
|f| {
|
|f| {
|
||||||
//
|
//
|
||||||
use gloo::events::EventListener;
|
use gloo::events::EventListener;
|
||||||
|
|
||||||
let base_url = fetch_base_url();
|
let base = window()
|
||||||
|
.unwrap()
|
||||||
|
.document()
|
||||||
|
.unwrap()
|
||||||
|
.query_selector("base[href]")
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.and_then(|base| {
|
||||||
|
let base = JsCast::unchecked_into::<web_sys::HtmlBaseElement>(base).href();
|
||||||
|
let url = web_sys::Url::new(&base).unwrap();
|
||||||
|
|
||||||
|
if url.pathname() != "/" {
|
||||||
|
Some(strip_slash_suffix(&base).to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let location = window().unwrap().location();
|
||||||
|
let pathname = location.pathname().unwrap();
|
||||||
|
let initial_route = parse(&pathname);
|
||||||
|
|
||||||
let service: RouterService<R> = RouterService {
|
let service: RouterService<R> = RouterService {
|
||||||
historic_routes: RefCell::new(vec![]),
|
historic_routes: vec![initial_route],
|
||||||
history_service: web_sys::window().unwrap().history().expect("no history"),
|
history_service: RefCell::new(
|
||||||
base_ur: RefCell::new(base_url),
|
web_sys::window().unwrap().history().expect("no history"),
|
||||||
|
),
|
||||||
|
base_ur: RefCell::new(base),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// let base = base_url();
|
||||||
|
// let url = route.to_path();
|
||||||
|
// pending_routes: RefCell::new(vec![]),
|
||||||
// service.history_service.push_state(data, title);
|
// service.history_service.push_state(data, title);
|
||||||
|
|
||||||
cx.provide_state(service);
|
// cx.provide_state(service);
|
||||||
|
|
||||||
let regenerate = cx.schedule_update();
|
let regenerate = cx.schedule_update();
|
||||||
|
|
||||||
// when "back" is called by the user, we want to to re-render the component
|
// // 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 |_| {
|
let listener = EventListener::new(&web_sys::window().unwrap(), "popstate", move |_| {
|
||||||
//
|
//
|
||||||
regenerate();
|
regenerate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
service
|
||||||
},
|
},
|
||||||
|f| {
|
|state| {
|
||||||
|
let base = state.base_ur.borrow();
|
||||||
|
if let Some(base) = base.as_ref() {
|
||||||
//
|
//
|
||||||
},
|
let path = format!("{}{}", base, state.get_current_route());
|
||||||
);
|
}
|
||||||
|
let history = state.history_service.borrow();
|
||||||
|
|
||||||
todo!()
|
todo!()
|
||||||
// let router = use_router_service::<R>(cx)?;
|
// history.state.historic_routes.last().unwrap()
|
||||||
// Some(cfg(router.get_current_route()))
|
//
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_router_service<R: Routable>(cx: Context) -> Option<&Rc<RouterService<R>>> {
|
pub fn use_router_service<R: Routable>(cx: Context) -> Option<&Rc<RouterService<R>>> {
|
||||||
|
@ -109,6 +142,23 @@ pub fn use_router_service<R: Routable>(cx: Context) -> Option<&Rc<RouterService<
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
pub struct LinkProps<R: Routable> {
|
pub struct LinkProps<R: Routable> {
|
||||||
to: R,
|
to: R,
|
||||||
|
|
||||||
|
/// The url that gets pushed to the history stack
|
||||||
|
///
|
||||||
|
/// You can either put it your own inline method or just autoderive the route using `derive(Routable)`
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
///
|
||||||
|
/// Link { to: Route::Home, href: |_| "home".to_string() }
|
||||||
|
///
|
||||||
|
/// // or
|
||||||
|
///
|
||||||
|
/// Link { to: Route::Home, href: Route::as_url }
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
href: fn(&R) -> String,
|
||||||
|
|
||||||
|
#[builder(default)]
|
||||||
children: Element,
|
children: Element,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +166,7 @@ pub fn Link<R: Routable>(cx: Context, props: &LinkProps<R>) -> Element {
|
||||||
let service = use_router_service::<R>(cx)?;
|
let service = use_router_service::<R>(cx)?;
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
a {
|
a {
|
||||||
href: format_args!("{}", props.to.to_string()),
|
href: format_args!("{}", (props.href)(&props.to)),
|
||||||
onclick: move |_| service.push_route(props.to.clone()),
|
onclick: move |_| service.push_route(props.to.clone()),
|
||||||
{&props.children},
|
{&props.children},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,6 @@
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use web_sys::window;
|
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 {
|
pub(crate) fn strip_slash_suffix(path: &str) -> &str {
|
||||||
path.strip_suffix('/').unwrap_or(path)
|
path.strip_suffix('/').unwrap_or(path)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue