Jonathan Kelley před 4 roky
rodič
revize
8c541f6

+ 3 - 6
README.md

@@ -29,9 +29,10 @@ Dioxus can be used to deliver webapps, desktop apps, static pages, liveview apps
 ### **Things you'll love ❤️:**
 - Ergonomic design
 - Minimal boilerplate
-- Simple build, test, and deploy
+- Simple build, test, and deploy 
 - Support for html! and rsx! templating
 - SSR, WASM, desktop, and mobile support
+- Powerful and simple integrated state management
 - Rust! (enums, static types, modules, efficiency)
   
 
@@ -47,10 +48,6 @@ Dioxus can be used to deliver webapps, desktop apps, static pages, liveview apps
     <tr>
 </table>
 
-
-
-
-
 ## Explore
 - [**HTML Templates**: Drop in existing HTML5 templates with html! macro](docs/guides/00-index.md)
 - [**RSX Templates**: Clean component design with rsx! macro](docs/guides/00-index.md)
@@ -81,7 +78,7 @@ Liveview is an architecture for building web applications where the final page i
 
 In other frameworks, the DOM will be updated after events from the page are sent to the server and new html is generated. The html is then sent back to the page over websockets (ie html over websockets).
 
-In Dioxus, the user's bundle will link individual components on the page to the Liveview server. This ensures local events propogate through the page virtual dom if the server is not needed, keeping interactive latency low. This ensures the server load stays low, enablinmg a single server to handle tens of thousands of simultaneous clients.
+In Dioxus, the user's bundle will link individual components on the page to the Liveview server. This ensures local events propogate through the page virtual dom if the server is not needed, keeping interactive latency low. This ensures the server load stays low, enabling a single server to handle tens of thousands of simultaneous clients.
 
 <!-- ## Dioxus LiveHost
 Dioxus LiveHost is a paid service that accelerates the deployment of Dioxus Apps. It provides CI/CD, testing, monitoring, scaling, and deployment specifically for Dioxus apps. 

+ 17 - 11
notes/CHANGELOG.md

@@ -76,20 +76,26 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
 
 ## Outstanding todos:
 > anything missed so far
+- [x] keys on components
+- [x] Allow paths for components
+- [x] todo mvc
+- [ ] Make events lazy (use traits + Box<dyn>)
+- [ ] attrs on elements should implement format_args
+- [ ] Beef up the dioxus CLI tool
+- [ ] Tweak macro parsing for better errors
+- [ ] make ssr follow HTML spec (ish)
 - [ ] dirty tagging, compression
-- [ ] fragments
-- [ ] make ssr follow HTML spec
-- [ ] code health
+- [ ] fix keys on elements
 - [ ] miri tests
-- [ ] todo mvc
-- [ ] fix
+- [ ] code health
+- [ ] all synthetic events filled out
+- [ ] doublecheck event targets and stuff
+- [ ] Documentation overhaul
+- [ ] Website
+
+lower priorty features
+- [ ] fragments
 - [ ] node refs (postpone for future release?)
 - [ ] styling built-in (future release?)
 - [ ] key handler?
 - [ ] FC macro
-- [ ] Documentation overhaul
-- [ ] Website
-- [x] keys on components
-- [ ] fix keys on elements
-- [ ] all synthetic events filed out
-- [ ] doublecheck event targets and stuff

+ 42 - 47
packages/core-macro/src/rsxt.rs

@@ -144,60 +144,18 @@ impl Parse for Node {
 }
 
 struct Component {
-    name: Ident,
+    // accept any path-like argument
+    name: syn::Path,
     body: Vec<ComponentField>,
     children: Vec<Node>,
 }
 
-impl ToTokens for &Component {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let name = &self.name;
-
-        let mut builder = quote! {
-            fc_to_builder(#name)
-        };
-
-        let mut has_key = None;
-
-        for field in &self.body {
-            if field.name.to_string() == "key" {
-                has_key = Some(field);
-            } else {
-                builder.append_all(quote! {#field});
-            }
-        }
-
-        builder.append_all(quote! {
-            .build()
-        });
-
-        let key_token = match has_key {
-            Some(field) => {
-                let inners = field.content.to_token_stream();
-                quote! {
-                    Some(#inners)
-                }
-            }
-            None => quote! {None},
-        };
-
-        let _toks = tokens.append_all(quote! {
-            dioxus::builder::virtual_child(ctx, #name, #builder, #key_token)
-        });
-    }
-}
-
 impl Parse for Component {
     fn parse(s: ParseStream) -> Result<Self> {
-        let name = Ident::parse_any(s)?;
-
-        if crate::util::is_valid_tag(&name.to_string()) {
-            return Err(Error::new(
-                name.span(),
-                "Components cannot share names with valid HTML tags",
-            ));
-        }
+        // let name = s.parse::<syn::ExprPath>()?;
+        // todo: look into somehow getting the crate/super/etc
 
+        let name = syn::Path::parse_mod_style(s)?;
         // parse the guts
         let content: ParseBuffer;
         syn::braced!(content in s);
@@ -233,6 +191,43 @@ impl Parse for Component {
     }
 }
 
+impl ToTokens for &Component {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        let name = &self.name;
+
+        let mut builder = quote! {
+            fc_to_builder(#name)
+        };
+
+        let mut has_key = None;
+
+        for field in &self.body {
+            if field.name.to_string() == "key" {
+                has_key = Some(field);
+            } else {
+                builder.append_all(quote! {#field});
+            }
+        }
+
+        builder.append_all(quote! {
+            .build()
+        });
+
+        let key_token = match has_key {
+            Some(field) => {
+                let inners = field.content.to_token_stream();
+                quote! {
+                    Some(#inners)
+                }
+            }
+            None => quote! {None},
+        };
+
+        let _toks = tokens.append_all(quote! {
+            dioxus::builder::virtual_child(ctx, #name, #builder, #key_token)
+        });
+    }
+}
 // the struct's fields info
 pub struct ComponentField {
     name: Ident,

+ 24 - 1
packages/core/src/events.rs

@@ -115,7 +115,7 @@ pub mod on {
         data: String,
     }
     event_builder! {
-        ClipboardEvent;
+        CompositionEvent;
         compositionend compositionstart compositionupdate
     }
 
@@ -133,6 +133,29 @@ pub mod on {
         which: usize,
         get_modifier_state: GetModifierKey,
     }
+    pub struct KeyboardEvent2(pub Box<dyn KeyboardEventT>);
+    impl std::ops::Deref for KeyboardEvent2 {
+        type Target = Box<dyn KeyboardEventT>;
+
+        fn deref(&self) -> &Self::Target {
+            &self.0
+        }
+    }
+
+    pub trait KeyboardEventT {
+        fn char_code(&self) -> usize;
+        fn ctrl_key(&self) -> bool;
+        fn key(&self) -> String;
+        fn key_code(&self) -> usize;
+        fn locale(&self) -> String;
+        fn location(&self) -> usize;
+        fn meta_key(&self) -> bool;
+        fn repeat(&self) -> bool;
+        fn shift_key(&self) -> bool;
+        fn which(&self) -> usize;
+        fn get_modifier_state(&self) -> GetModifierKey;
+    }
+
     event_builder! {
         KeyboardEvent;
         keydown keypress keyup

+ 14 - 1
packages/web/Cargo.toml

@@ -45,10 +45,23 @@ features = [
     "Event",
     "MouseEvent",
     "InputEvent",
+    "ClipboardEvent",
+    "KeyboardEvent",
+    "TouchEvent",
+    "WheelEvent",
+    "AnimationEvent",
+    "TransitionEvent",
+    "PointerEvent",
+    "FocusEvent",
+    "CompositionEvent",
     "DocumentType",
     "CharacterData",
     "HtmlOptionElement",
-]
+] # "FormEvent",
+# "UIEvent",
+# "ToggleEvent",
+# "MediaEvent",
+# "SelectionEvent",
 
 [profile.release]
 debug = true

+ 3 - 3
packages/web/examples/rsxt.rs

@@ -36,9 +36,9 @@ static Example: FC<ExampleProps> = |ctx, props| {
                 "Hello, {name}"
             }
             
-            CustomButton { name: "Jack!", handler: move |evt| name.set("Jack".to_string()) }
-            CustomButton { name: "Jill!", handler: move |evt| name.set("Jill".to_string()) }
-            CustomButton { name: "Bob!", handler: move |evt| name.set("Bob".to_string())}
+            CustomButton { name: "Jack!", handler: move |_| name.set("Jack".to_string()) }
+            CustomButton { name: "Jill!", handler: move |_| name.set("Jill".to_string()) }
+            CustomButton { name: "Bob!", handler: move |_| name.set("Bob".to_string())}
         }
     })
 };

+ 2 - 2
packages/web/examples/todomvc/filtertoggles.rs

@@ -13,7 +13,7 @@ pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
     ]
     .iter()
     .map(|(name, path, filter)| {
-        rsx! {
+        rsx!(
             li {
                 class: "{name}"
                 a {
@@ -22,7 +22,7 @@ pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
                     "{name}"
                 }
             }
-        }
+        )
     });
 
     // todo

+ 2 - 5
packages/web/examples/todomvc/main.rs

@@ -1,5 +1,4 @@
-use dioxus_core::prelude::*;
-use dioxus_web::WebsysRenderer;
+use dioxus_web::{prelude::*, WebsysRenderer};
 
 mod filtertoggles;
 mod recoil;
@@ -7,8 +6,6 @@ mod state;
 mod todoitem;
 mod todolist;
 
-use todolist::TodoList;
-
 static APP_STYLE: &'static str = include_str!("./style.css");
 
 fn main() {
@@ -19,7 +16,7 @@ fn main() {
                 style { "{APP_STYLE}" }
 
                 // list
-                TodoList {}
+                todolist::TodoList {}
 
                 // footer
                 footer {

+ 14 - 0
packages/web/examples/todomvc/recoil.rs

@@ -85,3 +85,17 @@ mod atoms {
     pub struct atom_family<K: PartialEq, V: PartialEq>(pub fn(&mut AtomFamilyBuilder<K, V>));
     pub type AtomFamily<K: PartialEq, V: PartialEq> = atom_family<K, V>;
 }
+
+pub use selectors::selector;
+mod selectors {
+    pub struct SelectorBuilder<Out, const Built: bool> {
+        _p: std::marker::PhantomData<Out>,
+    }
+    impl<O> SelectorBuilder<O, false> {
+        pub fn getter(self, f: impl Fn(()) -> O) -> SelectorBuilder<O, true> {
+            std::rc::Rc::pin(value)
+            todo!()
+        }
+    }
+    pub struct selector<O>(pub fn(SelectorBuilder<O, false>) -> SelectorBuilder<O, true>);
+}

+ 31 - 7
packages/web/examples/todomvc/state.rs

@@ -1,7 +1,12 @@
 use crate::recoil::*;
+use dioxus_core::prelude::Context;
 
 pub static TODOS: AtomFamily<uuid::Uuid, TodoItem> = atom_family(|_| {});
 pub static FILTER: Atom<FilterState> = atom(|_| FilterState::All);
+pub static SHOW_ALL_TODOS: selector<bool> = selector(|g| g.getter(|f| false));
+
+// an atomfamily is just a HashMap<K, Pin<Rc<V>>> that pins the Rc and exposes the values by reference
+// we could do a more advanced management, but this is fine too
 
 #[derive(PartialEq)]
 pub enum FilterState {
@@ -17,11 +22,30 @@ pub struct TodoItem {
     pub contents: String,
 }
 
-impl crate::recoil::RecoilContext<()> {
-    pub fn add_todo(&self, contents: String) {}
-    pub fn remove_todo(&self) {}
-    pub fn select_all_todos(&self) {}
-    pub fn toggle_todo(&self, id: uuid::Uuid) {}
-    pub fn clear_completed(&self) {}
-    pub fn set_filter(&self, filter: &FilterState) {}
+pub fn add_todo(ctx: &Context, contents: String) {}
+
+pub fn remove_todo(ctx: &Context, id: &uuid::Uuid) {
+    TODOS.with(&ctx).remove(id)
+}
+
+pub fn select_all_todos(ctx: &Context) {}
+
+pub fn toggle_todo(ctx: &Context, id: &uuid::Uuid) {}
+
+pub fn clear_completed(ctx: &Context) {
+    let (set, get) = (self.set, self.get);
+
+    TOODS
+        .get(&ctx)
+        .iter()
+        .filter(|(k, v)| v.checked)
+        .map(|(k, v)| TODOS.remove(&ctx, k));
 }
+
+pub fn set_filter(ctx: &Context, filter: &FilterState) {}
+
+struct TodoManager<'a> {}
+fn use_todos(ctx: &Context) {}
+
+#[test]
+fn test() {}

+ 5 - 12
packages/web/examples/todomvc/todoitem.rs

@@ -1,11 +1,10 @@
 use super::state::TODOS;
 use crate::recoil::use_atom_family;
 use dioxus_core::prelude::*;
-use uuid::Uuid;
 
 #[derive(PartialEq, Props)]
 pub struct TodoEntryProps {
-    id: Uuid,
+    id: uuid::Uuid,
 }
 
 pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
@@ -20,17 +19,11 @@ pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
                 type: "checkbox"
                 "{todo.checked}"
             }
-            {is_editing.then(|| {
-                rsx!(input {
+            {is_editing.then(|| rsx!(
+                input {
                     value: "{contents}"
-                })
-            })}
+                }
+            ))}
         }
     ))
 }
-
-pub fn Example(ctx: Context, id: Uuid, name: String) -> DomTree {
-    ctx.render(rsx! {
-        div {}
-    })
-}

+ 10 - 12
packages/web/examples/todomvc/todolist.rs

@@ -1,20 +1,20 @@
 use super::state::{FilterState, TodoItem, FILTER, TODOS};
-use crate::filtertoggles::FilterToggles;
+use crate::filtertoggles;
 use crate::recoil::use_atom;
 use crate::todoitem::TodoEntry;
 use dioxus_core::prelude::*;
 
 pub fn TodoList(ctx: Context, props: &()) -> DomTree {
-    let (entry, set_entry) = use_state(&ctx, || "".to_string());
-    let todos: &Vec<TodoItem> = todo!();
+    let (draft, set_draft) = use_state(&ctx, || "".to_string());
+    let (todos, _) = use_state(&ctx, || Vec::<TodoItem>::new());
     let filter = use_atom(&ctx, &FILTER);
 
     let list = todos
         .iter()
-        .filter(|f| match filter {
+        .filter(|item| match filter {
             FilterState::All => true,
-            FilterState::Active => !f.checked,
-            FilterState::Completed => f.checked,
+            FilterState::Active => !item.checked,
+            FilterState::Completed => item.checked,
         })
         .map(|item| {
             rsx!(TodoEntry {
@@ -25,25 +25,23 @@ pub fn TodoList(ctx: Context, props: &()) -> DomTree {
 
     ctx.render(rsx! {
         div {
-            // header
             header {
                 class: "header"
                 h1 {"todos"}
                 input {
                     class: "new-todo"
                     placeholder: "What needs to be done?"
-                    value: "{entry}"
-                    oninput: move |evt| set_entry(evt.value)
+                    value: "{draft}"
+                    oninput: move |evt| set_draft(evt.value)
                 }
             }
 
-            // list
             {list}
 
             // filter toggle (show only if the list isn't empty)
             {(!todos.is_empty()).then(||
-                rsx!{ FilterToggles {}
-            })}
+                rsx!( filtertoggles::FilterToggles {})
+            )}
         }
     })
 }

+ 88 - 1
packages/web/src/interpreter.rs

@@ -97,7 +97,12 @@ impl EventDelegater {
 
                 if let (Some(gi_id), Some(gi_gen), Some(li_idx)) = (gi_id, gi_gen, li_idx) {
                     // Call the trigger
-                    log::debug!("decoded gi_id: {},  gi_gen: {},  li_idx: {}", gi_id,gi_gen,li_idx);
+                    log::debug!(
+                        "decoded gi_id: {},  gi_gen: {},  li_idx: {}",
+                        gi_id,
+                        gi_gen,
+                        li_idx
+                    );
 
                     let triggered_scope = ScopeIdx::from_raw_parts(gi_id, gi_gen);
                     trigger.0.as_ref()(EventTrigger::new(
@@ -514,6 +519,33 @@ impl PatchMachine {
 fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
     use dioxus_core::events::on::*;
     match event.type_().as_str() {
+        "copy" | "cut" | "paste" => {
+            // let evt: web_sys::ClipboardEvent = event.clone().dyn_into().unwrap();
+
+            todo!()
+        }
+
+        "compositionend" | "compositionstart" | "compositionupdate" => {
+            let evt: web_sys::CompositionEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "keydown" | "keypress" | "keyup" => {
+            let evt: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "focus" | "blur" => {
+            let evt: web_sys::FocusEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "change" | "input" | "invalid" | "reset" | "submit" => {
+            // is a special react events
+            // let evt: web_sys::FormEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
         "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
         | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
         | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
@@ -537,6 +569,61 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
                 })),
             })))
         }
+
+        "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
+        | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
+            let evt: web_sys::PointerEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "select" => {
+            // let evt: web_sys::SelectionEvent = event.clone().dyn_into().unwrap();
+            // not required to construct anything special beyond standard event stuff
+            todo!()
+        }
+
+        "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
+            let evt: web_sys::TouchEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "scroll" => {
+            // let evt: web_sys::UIEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "wheel" => {
+            let evt: web_sys::WheelEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
+        | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
+        | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
+        | "timeupdate" | "volumechange" | "waiting" => {
+            // not required to construct anything special beyond standard event stuff
+
+            // let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap();
+            // let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "animationstart" | "animationend" | "animationiteration" => {
+            let evt: web_sys::AnimationEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "transitionend" => {
+            let evt: web_sys::TransitionEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "toggle" => {
+            // not required to construct anything special beyond standard event stuff (target)
+
+            // let evt: web_sys::ToggleEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
         _ => VirtualEvent::OtherEvent,
     }
 }

+ 1 - 0
packages/web/src/lib.rs

@@ -11,6 +11,7 @@ pub use dioxus_core as dioxus;
 use dioxus_core::{events::EventTrigger, prelude::FC};
 // use futures::{channel::mpsc, SinkExt, StreamExt};
 
+pub use dioxus_core::prelude;
 pub mod interpreter;
 
 /// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM.