Răsfoiți Sursa

wip: updates to router

Jonathan Kelley 3 ani în urmă
părinte
comite
bab21a0aa1

+ 75 - 72
examples/calculator.rs

@@ -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.
 */
 
+use std::sync::Arc;
+
 use dioxus::events::*;
 use dioxus::prelude::*;
+use separator::Separatable;
 
 fn main() {
     dioxus::desktop::launch(APP, |cfg| cfg);
@@ -15,85 +18,67 @@ const APP: FC<()> = |cx, _| {
     let operator = use_state(cx, || None as Option<&'static str>);
     let display_value = use_state(cx, || String::from(""));
 
-    let clear_display = display_value == "0";
-    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_dot = move || display_value.modify().push_str(".");
-
-    let perform_operation = 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);
-        }
-    };
-
-    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 input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str());
 
-    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::Subtract => operator.set(Some("-")),
-        KeyCode::Divide => operator.set(Some("/")),
-        KeyCode::Multiply => operator.set(Some("*")),
-        KeyCode::Num0 => input_digit(0),
-        KeyCode::Num1 => input_digit(1),
-        KeyCode::Num2 => input_digit(2),
-        KeyCode::Num3 => input_digit(3),
-        KeyCode::Num4 => input_digit(4),
-        KeyCode::Num5 => input_digit(5),
-        KeyCode::Num6 => input_digit(6),
-        KeyCode::Num7 => input_digit(7),
-        KeyCode::Num8 => input_digit(8),
-        KeyCode::Num9 => input_digit(9),
-        KeyCode::Backspace => {
-            if !display_value.as_str().eq("0") {
-                display_value.modify().pop();
+    rsx!(cx, div {
+        class: "calculator",
+        onkeydown: move |evt| match evt.key_code {
+            KeyCode::Add => operator.set(Some("+")),
+            KeyCode::Subtract => operator.set(Some("-")),
+            KeyCode::Divide => operator.set(Some("/")),
+            KeyCode::Multiply => operator.set(Some("*")),
+            KeyCode::Num0 => input_digit(0),
+            KeyCode::Num1 => input_digit(1),
+            KeyCode::Num2 => input_digit(2),
+            KeyCode::Num3 => input_digit(3),
+            KeyCode::Num4 => input_digit(4),
+            KeyCode::Num5 => input_digit(5),
+            KeyCode::Num6 => input_digit(6),
+            KeyCode::Num7 => input_digit(7),
+            KeyCode::Num8 => input_digit(8),
+            KeyCode::Num9 => input_digit(9),
+            KeyCode::Backspace => {
+                if !display_value.as_str().eq("0") {
+                    display_value.modify().pop();
+                }
             }
+            _ => {}
         }
-        _ => {}
-    };
-
-    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: "calculator-display", {[format_args!("{}", cur_val.separated_string())]} }
         div { class: "input-keys"
             div { class: "function-keys"
-                CalculatorKey { name: "key-clear", onclick: {clear_key} "{clear_text}" }
-                CalculatorKey { name: "key-sign", onclick: {toggle_sign}, "±"}
-                CalculatorKey { name: "key-percent", onclick: {toggle_percent} "%"}
+                CalculatorKey {
+                    {[if display_value == "0" { "C" } else { "AC" }]}
+                    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"
                 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!{
                     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-subtract", 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)]
 struct CalculatorKeyProps<'a> {
     name: &'static str,
-    onclick: &'a dyn Fn(MouseEvent),
+    onclick: &'a dyn Fn(Arc<MouseEvent>),
     children: Element,
 }
 

+ 38 - 38
packages/core-macro/src/router.rs

@@ -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);
         // }
 
-        #[automatically_derived]
-        impl ::dioxus::router::Routable for #ident {
-            #from_path
-            #to_path
-
-            fn routes() -> ::std::vec::Vec<&'static str> {
-                ::std::vec![#(#ats),*]
-            }
-
-            fn not_found_route() -> ::std::option::Option<Self> {
-                #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> {
-                todo!()
-                // ::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;
-            //     });
-            // }
-        }
+        // #[automatically_derived]
+        // impl ::dioxus::router::Routable for #ident {
+        //     #from_path
+        //     #to_path
+
+        //     // fn routes() -> ::std::vec::Vec<&'static str> {
+        //     //     ::std::vec![#(#ats),*]
+        //     // }
+
+        //     fn not_found_route() -> ::std::option::Option<Self> {
+        //         #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> {
+        //         todo!()
+        //         // ::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;
+        //     //     });
+        //     // }
+        // }
     }
 }

+ 0 - 26
packages/core/src/scope.rs

@@ -74,7 +74,6 @@ pub struct SelfReferentialItems<'a> {
     pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
     pub(crate) suspended_nodes: FxHashMap<u64, &'a VSuspended>,
     pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
-    pub(crate) pending_effects: Vec<BumpBox<'a, dyn FnMut()>>,
 }
 
 /// 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
     /// The future is forcibly dropped if the component is not ready by the next render
     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 {
         let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
         unsafe { std::mem::transmute(&*node) }

+ 0 - 5
packages/core/src/scopearena.rs

@@ -188,7 +188,6 @@ impl ScopeArena {
                     borrowed_props: Default::default(),
                     suspended_nodes: Default::default(),
                     tasks: Default::default(),
-                    pending_effects: Default::default(),
                 }),
             });
 
@@ -226,14 +225,12 @@ impl ScopeArena {
         let SelfReferentialItems {
             borrowed_props,
             listeners,
-            pending_effects,
             suspended_nodes,
             tasks,
         } = scope.items.get_mut();
 
         borrowed_props.clear();
         listeners.clear();
-        pending_effects.clear();
         suspended_nodes.clear();
         tasks.clear();
 
@@ -328,14 +325,12 @@ impl ScopeArena {
             // just forget about our suspended nodes while we're at it
             items.suspended_nodes.clear();
             items.tasks.clear();
-            items.pending_effects.clear();
 
             // guarantee that we haven't screwed up - there should be no latent references anywhere
             debug_assert!(items.listeners.is_empty());
             debug_assert!(items.borrowed_props.is_empty());
             debug_assert!(items.suspended_nodes.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.
             scope.wip_frame().nodes.borrow_mut().clear();

+ 1 - 1
packages/desktop/src/index.js

@@ -409,7 +409,7 @@ class Interpreter {
 }
 
 function main() {
-  let root = window.document.getElementById("_dioxusroot");
+  let root = window.document.getElementById("main");
   window.interpreter = new Interpreter(root);
   console.log(window.interpreter);
 

+ 25 - 31
packages/router/examples/simple.rs

@@ -5,41 +5,35 @@ use dioxus_router::*;
 
 fn main() {
     console_error_panic_hook::set_once();
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
     dioxus_web::launch(App, |c| c);
 }
 
-#[derive(Clone, Debug, PartialEq)]
-enum Route {
-    Home,
-    About,
-    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" }),
+    #[derive(Clone, Debug, PartialEq)]
+    enum Route {
+        Home,
+        About,
+        NotFound,
     }
-};
 
-impl ToString for Route {
-    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,
-            "/about" => Route::About,
-            _ => Route::NotFound,
+    let route = use_router(cx, |s| match s {
+        "/" => Route::Home,
+        "/about" => Route::About,
+        _ => 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() }
+            }
         }
-    }
-}
+    })
+};

+ 72 - 22
packages/router/src/lib.rs

@@ -6,23 +6,24 @@ 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 wasm_bindgen::{JsCast, JsValue};
+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 {}
-impl<T> Routable for T where T: 'static + Send + Clone + ToString + PartialEq {}
+pub trait Routable: 'static + Send + Clone + PartialEq {}
+impl<T> Routable for T where T: 'static + Send + Clone + PartialEq {}
 
 pub struct RouterService<R: Routable> {
-    historic_routes: RefCell<Vec<R>>,
-    history_service: web_sys::History,
+    historic_routes: Vec<R>,
+    history_service: RefCell<web_sys::History>,
     base_ur: RefCell<Option<String>>,
 }
 
 impl<R: Routable> RouterService<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 {
@@ -65,41 +66,73 @@ impl<R: Routable> RouterService<R> {
 /// 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, 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
     cx.use_hook(
         |f| {
             //
             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 {
-                historic_routes: RefCell::new(vec![]),
-                history_service: web_sys::window().unwrap().history().expect("no history"),
-                base_ur: RefCell::new(base_url),
+                historic_routes: vec![initial_route],
+                history_service: RefCell::new(
+                    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);
 
-            cx.provide_state(service);
+            // cx.provide_state(service);
 
             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 |_| {
                 //
                 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!()
+            // history.state.historic_routes.last().unwrap()
             //
         },
-    );
-
-    todo!()
-    // 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>>> {
@@ -109,6 +142,23 @@ pub fn use_router_service<R: Routable>(cx: Context) -> Option<&Rc<RouterService<
 #[derive(Props)]
 pub struct LinkProps<R: Routable> {
     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,
 }
 
@@ -116,7 +166,7 @@ pub fn Link<R: Routable>(cx: Context, props: &LinkProps<R>) -> Element {
     let service = use_router_service::<R>(cx)?;
     cx.render(rsx! {
         a {
-            href: format_args!("{}", props.to.to_string()),
+            href: format_args!("{}", (props.href)(&props.to)),
             onclick: move |_| service.push_route(props.to.clone()),
             {&props.children},
         }

+ 0 - 25
packages/router/src/utils.rs

@@ -1,31 +1,6 @@
 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)
 }