diff --git a/examples/calculator.rs b/examples/calculator.rs index 0b1b4f1e3..1a918879c 100644 --- a/examples/calculator.rs +++ b/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 toggle_percent = move |_| todo!(); 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::().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::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}" } + 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(); + } + } + _ => {} + } + 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::().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), children: Element, } diff --git a/packages/core-macro/src/router.rs b/packages/core-macro/src/router.rs index 2ab9f3a17..e28a9e9a6 100644 --- a/packages/core-macro/src/router.rs +++ b/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 + // #[automatically_derived] + // impl ::dioxus::router::Routable for #ident { + // #from_path + // #to_path - fn routes() -> ::std::vec::Vec<&'static str> { - ::std::vec![#(#ats),*] - } + // // fn routes() -> ::std::vec::Vec<&'static str> { + // // ::std::vec![#(#ats),*] + // // } - fn not_found_route() -> ::std::option::Option { - #not_found_route - } + // fn not_found_route() -> ::std::option::Option { + // #not_found_route + // } - // fn current_route() -> ::std::option::Option { - // #cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow())) - // } + // // fn current_route() -> ::std::option::Option { + // // #cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow())) + // // } - fn recognize(pathname: &str) -> ::std::option::Option { - 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 recognize(pathname: &str) -> ::std::option::Option { + // 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; - // }); - // } - } + // // fn cleanup() { + // // #cache_thread_local_ident.with(move |val| { + // // *val.borrow_mut() = ::std::option::Option::None; + // // }); + // // } + // } } } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 398c8f203..3eaf44f1c 100644 --- a/packages/core/src/scope.rs +++ b/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, pub(crate) tasks: Vec>>, - pub(crate) pending_effects: Vec>, } /// 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>( @@ -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) } diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index ea6f0c5be..e577d8d1e 100644 --- a/packages/core/src/scopearena.rs +++ b/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(); diff --git a/packages/desktop/src/index.js b/packages/desktop/src/index.js index aca3d3139..5d93052c7 100644 --- a/packages/desktop/src/index.js +++ b/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); diff --git a/packages/router/examples/simple.rs b/packages/router/examples/simple.rs index 43ddaa2d2..4a317dd5c 100644 --- a/packages/router/examples/simple.rs +++ b/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, } + + 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() } + } + } + }) }; - -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, - } - } -} diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index 948cb4eff..24daf8359 100644 --- a/packages/router/src/lib.rs +++ b/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 Routable for T where T: 'static + Send + Clone + ToString + PartialEq {} +pub trait Routable: 'static + Send + Clone + PartialEq {} +impl Routable for T where T: 'static + Send + Clone + PartialEq {} pub struct RouterService { - historic_routes: RefCell>, - history_service: web_sys::History, + historic_routes: Vec, + history_service: RefCell, base_ur: RefCell>, } impl RouterService { 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 RouterService { /// 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(cx: Context, parse: impl FnMut(&str) -> R) -> &R { +pub fn use_router(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::(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 = 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::(cx)?; - // Some(cfg(router.get_current_route())) + ) } pub fn use_router_service(cx: Context) -> Option<&Rc>> { @@ -109,6 +142,23 @@ pub fn use_router_service(cx: Context) -> Option<&Rc { 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(cx: Context, props: &LinkProps) -> Element { let service = use_router_service::(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}, } diff --git a/packages/router/src/utils.rs b/packages/router/src/utils.rs index 46764ebab..a8e9a2c35 100644 --- a/packages/router/src/utils.rs +++ b/packages/router/src/utils.rs @@ -1,31 +1,6 @@ use wasm_bindgen::JsCast; use web_sys::window; -pub fn fetch_base_url() -> Option { - match window() - .unwrap() - .document() - .unwrap() - .query_selector("base[href]") - { - Ok(Some(base)) => { - let base = JsCast::unchecked_into::(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) }