Jonathan Kelley 4 роки тому
батько
коміт
b843dbd

+ 48 - 19
packages/core-macro/src/rsxt.rs

@@ -17,12 +17,31 @@ use {
 // Parse any stream coming from the rsx! macro
 // ==============================================
 pub struct RsxRender {
-    // custom_context: Option<Ident>,
+    custom_context: Option<Ident>,
     root: AmbiguousElement,
 }
 
 impl Parse for RsxRender {
     fn parse(input: ParseStream) -> Result<Self> {
+        // try to parse the first ident and comma
+        let custom_context =
+            if input.peek(Token![in]) && input.peek2(Ident) && input.peek3(Token![,]) {
+                let _ = input.parse::<Token![in]>()?;
+                let name = input.parse::<Ident>()?;
+                if is_valid_html_tag(&name.to_string()) {
+                    return Err(Error::new(
+                        input.span(),
+                        "Custom context cannot be an html element name",
+                    ));
+                } else {
+                    input.parse::<Token![,]>().unwrap();
+                    Some(name)
+                }
+            } else {
+                // todo!("NOT WORKIGN");
+                None
+            };
+
         let root = { input.parse::<AmbiguousElement>() }?;
         if !input.is_empty() {
             return Err(Error::new(
@@ -31,7 +50,10 @@ impl Parse for RsxRender {
             ));
         }
 
-        Ok(Self { root })
+        Ok(Self {
+            root,
+            custom_context,
+        })
     }
 }
 
@@ -39,22 +61,29 @@ impl ToTokens for RsxRender {
     fn to_tokens(&self, out_tokens: &mut TokenStream2) {
         // create a lazy tree that accepts a bump allocator
         // Currently disabled
-        //
-        // let final_tokens = match &self.custom_context {
-        // Some(ident) => quote! {
-        //     #ident.render(dioxus::prelude::LazyNodes::new(move |ctx|{
-        //         let bump = ctx.bump;
-        //         #new_toks
-        //     }))
-        // },
 
         let inner = &self.root;
-        let output = quote! {
-            dioxus::prelude::LazyNodes::new(move |ctx|{
-                let bump = &ctx.bump();
-                #inner
-             })
+
+        let output = match &self.custom_context {
+            Some(ident) => {
+                //
+                quote! {
+                    #ident.render(dioxus::prelude::LazyNodes::new(move |__ctx|{
+                        let bump = &__ctx.bump();
+                        #inner
+                    }))
+                }
+            }
+            None => {
+                quote! {
+                    dioxus::prelude::LazyNodes::new(move |__ctx|{
+                        let bump = &__ctx.bump();
+                        #inner
+                     })
+                }
+            }
         };
+
         output.to_tokens(out_tokens)
     }
 }
@@ -237,7 +266,7 @@ impl ToTokens for &Component {
         };
 
         let _toks = tokens.append_all(quote! {
-            dioxus::builder::virtual_child(ctx, #name, #builder, #key_token)
+            dioxus::builder::virtual_child(__ctx, #name, #builder, #key_token)
         });
     }
 }
@@ -327,7 +356,7 @@ impl ToTokens for &Element {
         let name = &self.name.to_string();
 
         tokens.append_all(quote! {
-            dioxus::builder::ElementBuilder::new(ctx, #name)
+            dioxus::builder::ElementBuilder::new(__ctx, #name)
         });
 
         for attr in self.attrs.iter() {
@@ -448,7 +477,7 @@ impl ToTokens for &ElementAttr {
             }
             AttrType::Event(event) => {
                 tokens.append_all(quote! {
-                    .add_listener(dioxus::events::on::#nameident(ctx, #event))
+                    .add_listener(dioxus::events::on::#nameident(__ctx, #event))
                 });
             }
             AttrType::FieldTokens(exp) => {
@@ -459,7 +488,7 @@ impl ToTokens for &ElementAttr {
             AttrType::EventTokens(event) => {
                 //
                 tokens.append_all(quote! {
-                    .add_listener(dioxus::events::on::#nameident(ctx, #event))
+                    .add_listener(dioxus::events::on::#nameident(__ctx, #event))
                 })
             }
         }

+ 25 - 1
packages/core/src/diff.rs

@@ -887,6 +887,7 @@ impl<'a> DiffMachine<'a> {
         }
 
         match old.len().cmp(&new.len()) {
+            // old.len > new.len -> removing some nodes
             Ordering::Greater => {
                 // [... parent prev_child]
                 self.change_list.go_to_sibling(new.len());
@@ -896,6 +897,7 @@ impl<'a> DiffMachine<'a> {
                 self.remove_self_and_next_siblings(&old[new.len()..]);
                 // [... parent]
             }
+            // old.len < new.len -> adding some nodes
             Ordering::Less => {
                 // [... parent last_child]
                 self.change_list.go_up();
@@ -903,6 +905,7 @@ impl<'a> DiffMachine<'a> {
                 self.change_list.commit_traversal();
                 self.create_and_append_children(&new[old.len()..]);
             }
+            // old.len == new.len -> no nodes added/removed, but πerhaps changed
             Ordering::Equal => {
                 // [... parent child]
                 self.change_list.go_up();
@@ -924,6 +927,7 @@ impl<'a> DiffMachine<'a> {
     // When this function returns, the change list stack is in the same state.
     pub fn remove_all_children(&mut self, old: &[VNode<'a>]) {
         debug_assert!(self.change_list.traversal_is_committed());
+        log::debug!("REMOVING CHILDREN");
         for _child in old {
             // registry.remove_subtree(child);
         }
@@ -958,7 +962,27 @@ impl<'a> DiffMachine<'a> {
     //     [... parent]
     pub fn remove_self_and_next_siblings(&mut self, old: &[VNode<'a>]) {
         debug_assert!(self.change_list.traversal_is_committed());
-        for _child in old {
+        for child in old {
+            if let VNode::Component(vcomp) = child {
+                // self.change_list
+                //     .create_text_node("placeholder for vcomponent");
+
+                let root_id = vcomp.stable_addr.as_ref().borrow().unwrap();
+                self.lifecycle_events.push_back(LifeCycleEvent::Remove {
+                    root_id,
+                    stable_scope_addr: Rc::downgrade(&vcomp.ass_scope),
+                })
+                // let id = get_id();
+                // *component.stable_addr.as_ref().borrow_mut() = Some(id);
+                // self.change_list.save_known_root(id);
+                // let scope = Rc::downgrade(&component.ass_scope);
+                // self.lifecycle_events.push_back(LifeCycleEvent::Mount {
+                //     caller: Rc::downgrade(&component.caller),
+                //     root_id: id,
+                //     stable_scope_addr: scope,
+                // });
+            }
+
             // registry.remove_subtree(child);
         }
         self.change_list.remove_self_and_next_siblings();

+ 36 - 4
packages/core/src/virtual_dom.rs

@@ -26,7 +26,7 @@ use std::{
     any::{Any, TypeId},
     borrow::{Borrow, BorrowMut},
     cell::RefCell,
-    collections::{HashMap, HashSet},
+    collections::{HashMap, HashSet, VecDeque},
     fmt::Debug,
     future::Future,
     pin::Pin,
@@ -277,7 +277,7 @@ impl VirtualDom {
             // Start a new mutable borrow to components
             // We are guaranteeed that this scope is unique because we are tracking which nodes have modified
 
-            let mut cur_component = self.components.try_get_mut(update.idx).unwrap();
+            let cur_component = self.components.try_get_mut(update.idx).unwrap();
 
             cur_component.run_scope()?;
 
@@ -323,7 +323,12 @@ impl VirtualDom {
                             })
                         })?;
 
-                        cur_component.children.borrow_mut().insert(idx);
+                        {
+                            let cur_component = self.components.try_get_mut(update.idx).unwrap();
+                            let mut ch = cur_component.children.borrow_mut();
+                            ch.insert(idx);
+                            std::mem::drop(ch);
+                        }
 
                         // Grab out that component
                         let new_component = self.components.try_get_mut(idx).unwrap();
@@ -415,8 +420,33 @@ impl VirtualDom {
                         root_id,
                         stable_scope_addr,
                     } => {
-                        unimplemented!("This feature (Remove) is unimplemented")
+                        let id = stable_scope_addr
+                            .upgrade()
+                            .unwrap()
+                            .as_ref()
+                            .borrow()
+                            .unwrap();
+
+                        log::warn!("Removing node {:#?}", id);
+
+                        // This would normally be recursive but makes sense to do linear to
+                        let mut children_to_remove = VecDeque::new();
+                        children_to_remove.push_back(id);
+
+                        // Accumulate all the child components that need to be removed
+                        while let Some(child_id) = children_to_remove.pop_back() {
+                            let comp = self.components.try_get(child_id).unwrap();
+                            let children = comp.children.borrow();
+                            for child in children.iter() {
+                                children_to_remove.push_front(*child);
+                            }
+                            log::debug!("Removing component: {:#?}", child_id);
+                            self.components
+                                .with(|components| components.remove(child_id).unwrap())
+                                .unwrap();
+                        }
                     }
+
                     LifeCycleEvent::Replace {
                         caller,
                         root_id: id,
@@ -442,6 +472,8 @@ pub struct Scope {
     // The parent's scope ID
     pub parent: Option<ScopeIdx>,
 
+    // IDs of children that this scope has created
+    // This enables us to drop the children and their children when this scope is destroyed
     pub children: RefCell<HashSet<ScopeIdx>>,
 
     // A reference to the list of components.

+ 4 - 2
packages/recoil/Cargo.toml

@@ -1,9 +1,11 @@
 [package]
-name = "dioxus-recoil"
+name = "recoil"
 version = "0.0.0"
 authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
 edition = "2018"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
-[dependencies]
+[dependencies]
+dioxus-core = { path = "../core" }
+uuid = "0.8.2"

+ 8 - 4
packages/recoil/README.md

@@ -1,13 +1,17 @@
-# Recoil.rs
+# Recoil.rs - Official global state management solution for Dioxus Apps
+
 Recoil.rs provides a global state management API for Dioxus apps built on the concept of "atomic state." Instead of grouping state together into a single bundle ALA Redux, Recoil provides individual building blocks of state called Atoms. These atoms can be set/get anywhere in the app and combined to craft complex state. Recoil should be easier to learn and more efficient than Redux. Recoil.rs is modeled after the Recoil.JS project and pulls in 
 
+Recoil.rs is officially supported by the Dioxus team. By doing so, are are "planting our flag in the stand" for atomic state management instead of bundled (Redux-style) state management. Atomic state management fits well with the internals of Dioxus, meaning Recoil.rs state management will be faster, more efficient, and less sensitive to data races than Redux-style apps.
+
+Internally, Dioxus uses batching to speed up linear-style operations. Recoil.rs integrates with this batching optimization, making app-wide changes extremely fast. This way, Recoil.rs can be pushed significantly harder than Redux without the need to enable/disable debug flags to prevent performance slowdowns.
 
 ## Guide
 
-A simple atom of state is defined globally as a const:
+A simple atom of state is defined globally as a static:
 
 ```rust
-static Light: Atom<&'static str> = atom(|_| "Green");
+const Light: Atom<&'static str> = atom(|_| "Green");
 ```
 
 This atom of state is initialized with a value of `"Green"`. The atom that is returned does not actually contain any values. Instead, the atom's key - which is automatically generated in this instance - is used in the context of a Recoil App.  
@@ -16,7 +20,7 @@ This is then later used in components like so:
 
 ```rust
 fn App(ctx: Context, props: &()) -> DomTree {
-    // The recoil root must be initialized at the top of the application before any uses 
+    // The recoil root must be initialized at the top of the application before any use_recoils
     recoil::init_recoil_root(&ctx, |_| {});
 
     let color = use_recoil(&ctx, &TITLE);

+ 130 - 5
packages/recoil/src/lib.rs

@@ -1,7 +1,132 @@
-#[cfg(test)]
-mod tests {
-    #[test]
-    fn it_works() {
-        assert_eq!(2 + 2, 4);
+// ========================
+//   Important hooks
+// ========================
+
+pub fn init_recoil_root(ctx: Context) {}
+
+pub fn use_recoil_value() {}
+
+pub fn use_recoil() {}
+
+pub fn use_set_recoil() {}
+
+use dioxus_core::virtual_dom::Context;
+
+pub struct RecoilContext {}
+
+impl RecoilContext {
+    /// Get the value of an atom. Returns a reference to the underlying data.
+    pub fn get<T: PartialEq>(&self, t: &'static Atom<T>) -> &T {
+        todo!()
     }
+
+    /// Replace an existing value with a new value
+    ///
+    /// This does not replace the value instantly.
+    /// All calls to "get" will return the old value until the component is rendered.
+    pub fn set<T: PartialEq, O>(&self, t: &'static Atom<T>, new: T) {
+        self.modify(t, move |old| *old = new);
+    }
+
+    /// Modify lets you modify the value in place. However, because there's no previous value around to compare
+    /// the new one with, we are unable to memoize the change. As such, all downsteam users of this Atom will
+    /// be updated, causing all subsrcibed components to re-render.
+    ///
+    /// This is fine for most values, but might not be performant when dealing with collections. For collections,
+    /// use the "Family" variants as these will stay memoized for inserts, removals, and modifications.
+    ///
+    /// Note - like "set" this won't propogate instantly. Once all "gets" are dropped, only then will the update occur
+    pub fn modify<T: PartialEq, O>(&self, t: &'static Atom<T>, f: impl FnOnce(&mut T) -> O) -> O {
+        todo!()
+    }
+}
+
+pub fn use_recoil_context<T>(c: Context) -> &T {
+    todo!()
+}
+
+// pub fn use_callback<'a>(c: &Context<'a>, f: impl Fn() -> G) -> &'a RecoilContext {
+//     todo!()
+// }
+
+pub fn use_atom<'a, T: PartialEq>(c: Context<'a>, t: &'static Atom<T>) -> &'a T {
+    todo!()
+}
+
+pub trait Readable {}
+impl<T: PartialEq> Readable for &'static Atom<T> {}
+impl<K: PartialEq, V: PartialEq> Readable for &'static AtomFamily<K, V> {}
+
+pub fn use_atom_family<'a, K: PartialEq, V: PartialEq>(
+    c: &Context<'a>,
+    t: &'static AtomFamily<K, V>,
+    g: K,
+) -> &'a V {
+    todo!()
+}
+
+pub struct AtomBuilder<T: PartialEq> {
+    pub key: String,
+    pub manual_init: Option<Box<dyn Fn() -> T>>,
+    _never: std::marker::PhantomData<T>,
+}
+
+impl<T: PartialEq> AtomBuilder<T> {
+    pub fn new() -> Self {
+        Self {
+            key: "".to_string(),
+            manual_init: None,
+            _never: std::marker::PhantomData {},
+        }
+    }
+
+    pub fn init<A: Fn() -> T + 'static>(&mut self, f: A) {
+        self.manual_init = Some(Box::new(f));
+    }
+
+    pub fn set_key(&mut self, _key: &'static str) {}
+}
+
+// =====================================
+//    Atom
+// =====================================
+pub struct atom<T: PartialEq>(pub fn(&mut AtomBuilder<T>) -> T);
+pub type Atom<T: PartialEq> = atom<T>;
+
+// =====================================
+//    Atom Family
+// =====================================
+pub struct AtomFamilyBuilder<K, V> {
+    _never: std::marker::PhantomData<(K, V)>,
+}
+
+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>;
+
+// =====================================
+//    Selectors
+// =====================================
+pub struct SelectorApi {}
+impl SelectorApi {
+    pub fn get<T: PartialEq>(&self, t: &'static Atom<T>) -> &T {
+        todo!()
+    }
+}
+// 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> {
+//         todo!()
+//         // std::rc::Rc::pin(value)
+//         // todo!()
+//     }
+// }
+
+pub struct selector<O>(pub fn(&SelectorApi) -> O);
+// pub struct selector<O>(pub fn(SelectorBuilder<O, false>) -> SelectorBuilder<O, true>);
+pub type Selector<O> = selector<O>;
+
+pub fn use_selector<'a, O>(c: Context<'a>, s: &'static Selector<O>) -> &'a O {
+    todo!()
 }

+ 1 - 0
packages/web/Cargo.toml

@@ -21,6 +21,7 @@ console_error_panic_hook = "0.1.6"
 generational-arena = "0.2.8"
 wasm-bindgen-test = "0.3.21"
 once_cell = "1.7.2"
+recoil = { path = "../recoil" }
 
 # wasm-bindgen = "0.2.70"
 # futures = "0.3.12"

+ 43 - 1
packages/web/examples/derive.rs

@@ -1,4 +1,46 @@
 use dioxus::{events::on::MouseEvent, prelude::*};
 use dioxus_core as dioxus;
 use dioxus_web::WebsysRenderer;
-fn main() {}
+
+fn main() {
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    console_error_panic_hook::set_once();
+    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
+}
+
+fn App(ctx: Context, props: &()) -> DomTree {
+    let cansee = use_state_new(&ctx, || false);
+    rsx! { in ctx,
+        div {
+            "Shadow of the child:"
+            button {
+                "Gaze into the void"
+                onclick: move |_| cansee.set(!**cansee)
+            }
+            {cansee.then(|| rsx!{ Child {} })}
+        }
+    }
+}
+
+fn Child(ctx: Context, props: &()) -> DomTree {
+    rsx! { in ctx,
+        section { class: "py-6 bg-coolGray-100 text-coolGray-900"
+            div { class: "container mx-auto flex flex-col items-center justify-center p-4 space-y-8 md:p-10 md:px-24 xl:px-48"
+                h1 { class: "text-5xl font-bold leading-none text-center",
+                    "Sign up now"
+                }
+                p { class: "text-xl font-medium text-center",
+                    "At a assumenda quas cum earum ut itaque commodi saepe rem aspernatur quam natus quis nihil quod, hic explicabo doloribus magnam neque, exercitationem eius sunt!"
+                }
+                div { class: "flex flex-col space-y-4 sm:space-y-0 sm:flex-row sm:space-x-8"
+                    button { class: "px-8 py-3 text-lg font-semibold rounded bg-violet-600 text-coolGray-50",
+                        "Get started"
+                    }
+                    button { class: "px-8 py-3 text-lg font-normal border rounded bg-coolGray-800 text-coolGray-50 border-coolGray-700",
+                        "Learn more"
+                    }
+                }
+            }
+        }
+    }
+}

+ 120 - 182
packages/web/examples/todomvcsingle.rs

@@ -1,31 +1,114 @@
-#![allow(non_snake_case)]
-use dioxus_core::prelude::*;
-use dioxus_web::WebsysRenderer;
+//! Example: TODOVMC - One file
+//! ---------------------------
+//! This example shows how to build a one-file TODO MVC app with Dioxus and Recoil.
+//! This project is confined to a single file to showcase the suggested patterns
+//! for building a small but mighty UI with Dioxus without worrying about project structure.
+//!
+//! If you want an example on recommended project structure, check out the TodoMVC folder
+//!
+//! Here, we show to use Dioxus' Recoil state management solution to simplify app logic
 
-static APP_STYLE: &'static str = include_str!("./todomvc/style.css");
+#![allow(non_snake_case)]
+use dioxus_web::dioxus::prelude::*;
+use recoil::*;
+use std::collections::HashMap;
+use uuid::Uuid;
 
-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));
+const APP_STYLE: &'static str = include_str!("./todomvc/style.css");
 
 fn main() {
-    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, props| {
-        ctx.render(rsx! {
-            div {
-                id: "app",
-                style { "{APP_STYLE}" }
-                TodoList {}
-                Footer {}
+    wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App));
+}
+
+// Declare our global app state
+const TODO_LIST: Atom<HashMap<Uuid, TodoItem>> = atom(|_| Default::default());
+const FILTER: Atom<FilterState> = atom(|_| FilterState::All);
+const TODOS_LEFT: selector<usize> = selector(|api| api.get(&TODO_LIST).len());
+
+// Implement a simple abstraction over sets/gets of multiple atoms
+struct TodoManager(RecoilContext);
+impl TodoManager {
+    fn add_todo(&self, contents: String) {
+        let item = TodoItem {
+            checked: false,
+            contents,
+            id: Uuid::new_v4(),
+        };
+        self.0.modify(&TODO_LIST, move |list| {
+            list.insert(item.id, item);
+        });
+    }
+    fn remove_todo(&self, id: &Uuid) {
+        self.0.modify(&TODO_LIST, move |list| {
+            list.remove(id);
+        })
+    }
+    fn select_all_todos(&self) {
+        self.0.modify(&TODO_LIST, move |list| {
+            for item in list.values_mut() {
+                item.checked = true;
             }
         })
-    }));
+    }
+    fn toggle_todo(&self, id: &Uuid) {
+        self.0.modify(&TODO_LIST, move |list| {
+            list.get_mut(id).map(|item| item.checked = !item.checked)
+        });
+    }
+    fn clear_completed(&self) {
+        self.0.modify(&TODO_LIST, move |list| {
+            *list = list.drain().filter(|(_, item)| !item.checked).collect();
+        })
+    }
+    fn set_filter(&self, filter: &FilterState) {
+        self.0.modify(&FILTER, move |f| *f = *filter);
+    }
+}
+
+#[derive(PartialEq, Clone, Copy)]
+pub enum FilterState {
+    All,
+    Active,
+    Completed,
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct TodoItem {
+    pub id: Uuid,
+    pub checked: bool,
+    pub contents: String,
+}
+
+fn App(ctx: Context, props: &()) -> DomTree {
+    init_recoil_root(ctx);
+
+    ctx.render(rsx! {
+        div { id: "app", style { "{APP_STYLE}" }
+            TodoList {}
+            Footer {}
+        }
+    })
 }
 
 pub fn TodoList(ctx: Context, props: &()) -> DomTree {
-    let (draft, set_draft) = use_state(&ctx, || "".to_string());
-    let (todos, _) = use_state(&ctx, || Vec::<TodoItem>::new());
+    let draft = use_state_new(&ctx, || "".to_string());
+    let todos = use_atom(&ctx, &TODO_LIST);
     let filter = use_atom(&ctx, &FILTER);
 
+    let todolist = todos
+        .values()
+        .filter(|item| match filter {
+            FilterState::All => true,
+            FilterState::Active => !item.checked,
+            FilterState::Completed => item.checked,
+        })
+        .map(|item| {
+            rsx!(TodoEntry {
+                key: "{order}",
+                id: item.id,
+            })
+        });
+
     ctx.render(rsx! {
         div {
             header {
@@ -35,43 +118,26 @@ pub fn TodoList(ctx: Context, props: &()) -> DomTree {
                     class: "new-todo"
                     placeholder: "What needs to be done?"
                     value: "{draft}"
-                    oninput: move |evt| set_draft(evt.value)
+                    oninput: move |evt| draft.set(evt.value)
                 }
             }
+            {todolist}
 
-            { // list
-                todos
-                .iter()
-                .filter(|item| match filter {
-                    FilterState::All => true,
-                    FilterState::Active => !item.checked,
-                    FilterState::Completed => item.checked,
-                })
-                .map(|item| {
-                    rsx!(TodoEntry {
-                        key: "{order}",
-                        id: item.id,
-                    })
-                })
-            }
-
-            // filter toggle (show only if the list isn't empty)
-            {(!todos.is_empty()).then(||
-                rsx!( FilterToggles {})
-            )}
+            // rsx! accepts optionals, so we suggest the `then` method in place of ternary
+            {(!todos.is_empty()).then(|| rsx!( FilterToggles {}) )}
         }
     })
 }
 
 #[derive(PartialEq, Props)]
 pub struct TodoEntryProps {
-    id: uuid::Uuid,
+    id: Uuid,
 }
 
 pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
     let (is_editing, set_is_editing) = use_state(&ctx, || false);
-    let todo = use_atom_family(&ctx, &TODOS, props.id);
-    let contents = "";
+    let todo = use_atom(&ctx, &TODO_LIST).get(&props.id).unwrap();
+    // let todo = use_atom_family(&ctx, &TODOS, props.id);
 
     ctx.render(rsx! (
         li {
@@ -83,7 +149,7 @@ pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
             }
            {is_editing.then(|| rsx!(
                 input {
-                    value: "{contents}"
+                    value: "{todo.contents}"
                 }
            ))}
         }
@@ -91,8 +157,8 @@ pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
 }
 
 pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
-    let reducer = recoil::use_callback(&ctx, || ());
-    let items_left = recoil::use_atom_family(&ctx, &TODOS, uuid::Uuid::new_v4());
+    let reducer = use_recoil_context::<TodoManager>(ctx);
+    let items_left = use_selector(ctx, &TODOS_LEFT);
 
     let toggles = [
         ("All", "", FilterState::All),
@@ -105,19 +171,20 @@ pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
             li {
                 class: "{name}"
                 a {
-                    href: "{path}"
-                    onclick: move |_| reducer.set_filter(&filter)
+                    href: "{path}",
+                    onclick: move |_| reducer.set_filter(&filter),
                     "{name}"
                 }
             }
         )
     });
 
-    // todo
-    let item_text = "";
-    let items_left = "";
+    let item_text = match items_left {
+        1 => "item",
+        _ => "items",
+    };
 
-    ctx.render(rsx! {
+    rsx! { in ctx,
         footer {
             span {
                 strong {"{items_left}"}
@@ -128,11 +195,11 @@ pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
                 {toggles}
             }
         }
-    })
+    }
 }
 
 pub fn Footer(ctx: Context, props: &()) -> DomTree {
-    ctx.render(rsx! {
+    rsx!( in ctx,
         footer {
             class: "info"
             p {"Double-click to edit a todo"}
@@ -145,134 +212,5 @@ pub fn Footer(ctx: Context, props: &()) -> DomTree {
                 a { "TodoMVC", href: "http://todomvc.com" }
             }
         }
-    })
-}
-
-#[derive(PartialEq)]
-pub enum FilterState {
-    All,
-    Active,
-    Completed,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct TodoItem {
-    pub id: uuid::Uuid,
-    pub checked: bool,
-    pub contents: String,
-}
-
-impl RecoilContext<()> {
-    pub fn add_todo(&self, contents: String) {}
-    pub fn remove_todo(&self, id: &uuid::Uuid) {}
-    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 use recoil::*;
-mod recoil {
-    use dioxus_core::context::Context;
-
-    pub struct RecoilContext<T: 'static> {
-        _inner: T,
-    }
-
-    impl<T: 'static> RecoilContext<T> {
-        /// Get the value of an atom. Returns a reference to the underlying data.
-
-        pub fn get(&self) {}
-
-        /// Replace an existing value with a new value
-        ///
-        /// This does not replace the value instantly, and all calls to "get" within the current scope will return
-        pub fn set(&self) {}
-
-        // Modify lets you modify the value in place. However, because there's no previous value around to compare
-        // the new one with, we are unable to memoize the change. As such, all downsteam users of this Atom will
-        // be updated, causing all subsrcibed components to re-render.
-        //
-        // This is fine for most values, but might not be performant when dealing with collections. For collections,
-        // use the "Family" variants as these will stay memoized for inserts, removals, and modifications.
-        //
-        // Note - like "set" this won't propogate instantly. Once all "gets" are dropped, only then will we run the
-        pub fn modify(&self) {}
-    }
-
-    pub fn use_callback<'a, G>(c: &Context<'a>, f: impl Fn() -> G) -> &'a RecoilContext<G> {
-        todo!()
-    }
-
-    pub fn use_atom<T: PartialEq, O>(c: &Context, t: &'static Atom<T>) -> O {
-        todo!()
-    }
-    pub fn use_batom<T: PartialEq, O>(c: &Context, t: impl Readable) -> O {
-        todo!()
-    }
-
-    pub trait Readable {}
-    impl<T: PartialEq> Readable for &'static Atom<T> {}
-    impl<K: PartialEq, V: PartialEq> Readable for &'static AtomFamily<K, V> {}
-
-    pub fn use_atom_family<'a, K: PartialEq, V: PartialEq>(
-        c: &Context<'a>,
-        t: &'static AtomFamily<K, V>,
-        g: K,
-    ) -> &'a V {
-        todo!()
-    }
-
-    pub use atoms::{atom, Atom};
-    pub use atoms::{atom_family, AtomFamily};
-    mod atoms {
-
-        use super::*;
-        pub struct AtomBuilder<T: PartialEq> {
-            pub key: String,
-            pub manual_init: Option<Box<dyn Fn() -> T>>,
-            _never: std::marker::PhantomData<T>,
-        }
-
-        impl<T: PartialEq> AtomBuilder<T> {
-            pub fn new() -> Self {
-                Self {
-                    key: uuid::Uuid::new_v4().to_string(),
-                    manual_init: None,
-                    _never: std::marker::PhantomData {},
-                }
-            }
-
-            pub fn init<A: Fn() -> T + 'static>(&mut self, f: A) {
-                self.manual_init = Some(Box::new(f));
-            }
-
-            pub fn set_key(&mut self, _key: &'static str) {}
-        }
-
-        pub struct atom<T: PartialEq>(pub fn(&mut AtomBuilder<T>) -> T);
-        pub type Atom<T: PartialEq> = atom<T>;
-
-        pub struct AtomFamilyBuilder<K, V> {
-            _never: std::marker::PhantomData<(K, V)>,
-        }
-
-        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> {
-                todo!()
-                // std::rc::Rc::pin(value)
-                // todo!()
-            }
-        }
-        pub struct selector<O>(pub fn(SelectorBuilder<O, false>) -> SelectorBuilder<O, true>);
-    }
+    )
 }

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

@@ -100,7 +100,7 @@ impl WebsysRenderer {
             log::debug!("Received edits: {:#?}", edits);
 
             for edit in &edits {
-                log::debug!("edit stream {:?}", edit);
+                // log::debug!("edit stream {:?}", edit);
                 // log::debug!("Stream stack {:#?}", patch_machine.stack.top());
                 patch_machine.handle_edit(edit);
             }