wip: todomvc

This commit is contained in:
Jonathan Kelley committed 2021-03-29 12:31:47 -04:00
1 parent 0bcff1f88e
commit ce33031519
22 files changed
+753 -203

No files matched your search

+3 -3
View File
@@ -4,16 +4,16 @@ members = [
"packages/core-macro", "packages/core-macro",
"packages/core", "packages/core",
"packages/web", "packages/web",
"packages/liveview",
"packages/3d",
"packages/docsite", "packages/docsite",
"packages/ssr",
] ]
# "packages/liveview",
# "packages/3d",
# Built-in # Built-in
# "packages/webview/client", # "packages/webview/client",
# "packages/webview", # "packages/webview",
# "packages/router", # "packages/router",
# "packages/ssr",
# "packages/webview", # "packages/webview",
# "packages/livehost", # "packages/livehost",
# "packages/vscode-ext", # "packages/vscode-ext",
+1 -1
View File
@@ -38,7 +38,7 @@ Dioxus can be used to deliver webapps, desktop apps, static pages, liveview apps
## Get Started with... ## Get Started with...
<table style="width:100%" align="center"> <table style="width:100%" align="center">
<tr > <tr >
<th><a href="http://github.com/jkelleyrtp/dioxus">WebApps</a></th> <th><a href="http://github.com/jkelleyrtp/dioxus">Web</a></th>
<th><a href="http://github.com/jkelleyrtp/dioxus">Desktop</a></th> <th><a href="http://github.com/jkelleyrtp/dioxus">Desktop</a></th>
<th><a href="http://github.com/jkelleyrtp/dioxus">Mobile</a></th> <th><a href="http://github.com/jkelleyrtp/dioxus">Mobile</a></th>
<th><a href="http://github.com/jkelleyrtp/dioxus">State Management</a></th> <th><a href="http://github.com/jkelleyrtp/dioxus">State Management</a></th>
+81 -52
View File
@@ -1,55 +1,14 @@
# Dioxus v0.1.0
Welcome to the first iteration of the Dioxus Virtual DOM! This release brings support for:
- Web via WASM
# Project: Live-View 🤲 🍨 - Desktop via webview integration
> Combine the server and client into a single file :) - Server-rendering with custom Display Impl
- Liveview (experimental)
- Mobile (experimental)
# Project: Sanitization (TBD) - State management
> Improve code health - Build CLI
- [ ] (Macro) Clippy sanity for html macro ----
- [ ] (Macro) Error sanitization ## Project: Initial VDOM support (TBD)
# Project: Examples
> Get *all* the examples
- [ ] (Examples) Tide example with templating
# Project: State management
> Get some global state management installed with the hooks API
# Project: Concurrency (TBD)
> Ensure the concurrency model works well, play with lifetimes to check if it can be multithreaded + halted
# Project: Web-View 🤲 🍨
> Proof of concept: stream render edits from server to client
- [x] Prove that the diffing and patching framework can support patch streaming
# Project: Web_sys renderer (TBD)
- [x] WebSys edit interpreter
- [x] Event system using async channels
- [ ] Implement conversion of all event types into synthetic events
# Project: String Render (TBD)
> Implement a light-weight string renderer with basic caching
- [x] (Macro) Make VText nodes automatically capture and format IE allow "Text is {blah}"
- [ ] (SSR) Implement stateful 3rd party string renderer
# Project: Hooks + Context + Subscriptions (TBD)
> Implement the foundations for state management
- [x] Implement context object
- [x] Implement use_state (rewrite to use the use_reducer api like rei)
- [x] Implement use_ref
- [x] Implement use_context (only the API, not the state management solution)
- [ ] Implement use_reducer (WIP)
# Project: QOL
> Make it easier to write components
- [x] (Macro) Tweak event syntax to not be dependent on wasm32 target (just return regular closures which get boxed/alloced)
- [x] (Macro) Tweak component syntax to accept a new custom element
- [ ] (Macro) Allow components to specify their props as function args
# Project: Initial VDOM support (TBD)
> Get the initial VDom + Event System + Patching + Diffing + Component framework up and running > Get the initial VDom + Event System + Patching + Diffing + Component framework up and running
> Get a demo working using just the web > Get a demo working using just the web
- [x] (Core) Migrate virtual node into new VNode type - [x] (Core) Migrate virtual node into new VNode type
@@ -61,3 +20,73 @@
- [x] (Core) Implement child nodes, scope creation - [x] (Core) Implement child nodes, scope creation
- [ ] (Core) Implement dirty tagging and compression - [ ] (Core) Implement dirty tagging and compression
## Project: QOL
> Make it easier to write components
- [x] (Macro) Tweak event syntax to not be dependent on wasm32 target (just return regular closures which get boxed/alloced)
- [x] (Macro) Tweak component syntax to accept a new custom element
- [ ] (Macro) Allow components to specify their props as function args (not going to do)
## Project: Hooks + Context + Subscriptions (TBD)
> Implement the foundations for state management
- [x] Implement context object
- [x] Implement use_state (rewrite to use the use_reducer api like rei)
- [x] Implement use_ref
- [x] Implement use_context (only the API, not the state management solution)
- [ ] Implement use_reducer (WIP)
## Project: String Render (TBD)
> Implement a light-weight string renderer with basic caching
- [x] (Macro) Make VText nodes automatically capture and format IE allow "Text is {blah}"
- [x] (SSR) Implement stateful 3rd party string renderer
## Project: Web_sys renderer (TBD)
- [x] WebSys edit interpreter
- [x] Event system using async channels
- [ ] Implement conversion of all event types into synthetic events
## Project: Web-View 🤲 🍨
> Proof of concept: stream render edits from server to client
- [x] Prove that the diffing and patching framework can support patch streaming
## Project: Examples
> Get *all* the examples
- [ ] (Examples) Tide example with templating
## Project: State management
> Get some global state management installed with the hooks + context API
## Project: Concurrency (TBD)
> Ensure the concurrency model works well, play with lifetimes to check if it can be multithreaded + halted
?
## Project: Mobile exploration
## Project: Live-View 🤲 🍨
> Combine the server and client into a single file :)
## Project: Sanitization (TBD)
> Improve code health
- [ ] (Macro) Clippy sanity for html macro
- [ ] (Macro) Error sanitization
## Outstanding todos:
> anything missed so far
- dirty tagging, compression
- fragments
- make ssr follow HTML spec
- code health
- miri tests
- todo mvc
- fix
- node refs (postpone for future release?)
- styling built-in (future release?)
- key handler?
- FC macro
- Documentation overhaul
- Website
- keys on components
+31 -4
View File
@@ -16,7 +16,24 @@ use {
// ============================================== // ==============================================
pub struct RsxRender { pub struct RsxRender {
custom_context: Option<Ident>, custom_context: Option<Ident>,
el: Element, root: RootOption,
}
enum RootOption {
// for lists of components
Fragment(),
Element(Element),
Component(Component),
}
impl ToTokens for RootOption {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
RootOption::Fragment() => todo!(),
RootOption::Element(el) => el.to_tokens(tokens),
RootOption::Component(comp) => comp.to_tokens(tokens),
}
}
} }
impl Parse for RsxRender { impl Parse for RsxRender {
@@ -33,14 +50,24 @@ impl Parse for RsxRender {
}) })
.ok(); .ok();
let el: Element = input.parse()?; let forked = input.fork();
Ok(Self { el, custom_context }) let name = forked.parse::<Ident>()?;
let root = match crate::util::is_valid_tag(&name.to_string()) {
true => input.parse::<Element>().map(|el| RootOption::Element(el)),
false => input.parse::<Component>().map(|c| RootOption::Component(c)),
}?;
Ok(Self {
root,
custom_context,
})
} }
} }
impl ToTokens for RsxRender { impl ToTokens for RsxRender {
fn to_tokens(&self, out_tokens: &mut TokenStream2) { fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let new_toks = (&self.el).to_token_stream(); let new_toks = (&self.root).to_token_stream();
// create a lazy tree that accepts a bump allocator // create a lazy tree that accepts a bump allocator
let final_tokens = match &self.custom_context { let final_tokens = match &self.custom_context {
+2 -5
View File
@@ -17,10 +17,7 @@ dioxus-core-macro = { path = "../core-macro", version = "0.1.1" }
generational-arena = { version = "0.2.8", features = ["serde"] } generational-arena = { version = "0.2.8", features = ["serde"] }
# Bumpalo backs the VNode creation # Bumpalo backs the VNode creation
bumpalo = { version = "3.6.0", features = ["collections"] } bumpalo = { version = "3.6.0", features = ["collections", "boxed"] }
# Backs hook data - might move away from this arena
typed-arena = "2.0.1"
# custom error type # custom error type
thiserror = "1.0.23" thiserror = "1.0.23"
@@ -34,7 +31,7 @@ longest-increasing-subsequence = "0.1.0"
# internall used # internall used
log = "0.4.14" log = "0.4.14"
serde = { version = "1.0.123", features = ["derive"], optional=true } serde = { version = "1.0.123", features = ["derive"], optional = true }
[features] [features]
default = [] default = []
+2 -2
View File
@@ -22,7 +22,7 @@ struct ListItem {
} }
fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree { fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
let val = use_state(&ctx, || 0); let (val, set_val) = use_state(&ctx, || 0);
ctx.render(dioxus::prelude::LazyNodes::new(move |c| { ctx.render(dioxus::prelude::LazyNodes::new(move |c| {
let mut root = builder::ElementBuilder::new(c, "div"); let mut root = builder::ElementBuilder::new(c, "div");
@@ -36,7 +36,7 @@ fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
// create the props with nothing but the fc<T> // create the props with nothing but the fc<T>
fc_to_builder(ChildItem) fc_to_builder(ChildItem)
.item(child) .item(child)
.item_handler(val.setter()) .item_handler(set_val)
.build(), .build(),
)); ));
} }
+3 -3
View File
@@ -11,13 +11,13 @@ fn main() {
} }
static Example: FC<()> = |ctx, props| { static Example: FC<()> = |ctx, props| {
let name = use_state(&ctx, || "...?"); let (name, set_name) = use_state(&ctx, || "...?");
ctx.render(html! { ctx.render(html! {
<div> <div>
<h1> "Hello, {name}" </h1> <h1> "Hello, {name}" </h1>
<button onclick={move |_| name.set("jack")}> "jack!" </button> <button onclick={move |_| set_name("jack")}> "jack!" </button>
<button onclick={move |_| name.set("jill")}> "jill!" </button> <button onclick={move |_| set_name("jill")}> "jill!" </button>
</div> </div>
}) })
}; };
+38 -28
View File
@@ -2,7 +2,7 @@ use crate::{nodebuilder::IntoDomTree, prelude::*};
use crate::{nodebuilder::LazyNodes, nodes::VNode}; use crate::{nodebuilder::LazyNodes, nodes::VNode};
use bumpalo::Bump; use bumpalo::Bump;
use hooks::Hook; use hooks::Hook;
use std::{cell::RefCell, future::Future, ops::Deref, rc::Rc, sync::atomic::AtomicUsize}; use std::{cell::RefCell, future::Future, ops::Deref, pin::Pin, rc::Rc, sync::atomic::AtomicUsize};
/// Components in Dioxus use the "Context" object to interact with their lifecycle. /// Components in Dioxus use the "Context" object to interact with their lifecycle.
/// This lets components schedule updates, integrate hooks, and expose their context via the context api. /// This lets components schedule updates, integrate hooks, and expose their context via the context api.
@@ -25,13 +25,14 @@ use std::{cell::RefCell, future::Future, ops::Deref, rc::Rc, sync::atomic::Atomi
// todo: force lifetime of source into T as a valid lifetime too // todo: force lifetime of source into T as a valid lifetime too
// it's definitely possible, just needs some more messing around // it's definitely possible, just needs some more messing around
pub struct Context<'src> { pub struct Context<'src> {
pub idx: AtomicUsize, pub idx: RefCell<usize>,
pub scope: ScopeIdx, pub scope: ScopeIdx,
// Borrowed from scope // Borrowed from scope
pub(crate) arena: &'src typed_arena::Arena<Hook>, // pub(crate) arena: &'src typed_arena::Arena<Hook>,
pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>, pub(crate) hooks: &'src RefCell<Vec<Hook>>,
// pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
pub(crate) bump: &'src Bump, pub(crate) bump: &'src Bump,
pub listeners: &'src RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>, pub listeners: &'src RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
@@ -114,14 +115,16 @@ impl<'a> Context<'a> {
/// This module provides internal state management functionality for Dioxus components /// This module provides internal state management functionality for Dioxus components
pub mod hooks { pub mod hooks {
use std::any::Any;
use super::*; use super::*;
#[derive(Debug)] #[derive(Debug)]
pub struct Hook(pub Box<dyn std::any::Any>); pub struct Hook(pub Pin<Box<dyn std::any::Any>>);
impl Hook { impl Hook {
pub fn new(state: Box<dyn std::any::Any>) -> Self { pub fn new<T: 'static>(state: T) -> Self {
Self(state) Self(Box::pin(state))
} }
} }
@@ -139,28 +142,27 @@ pub mod hooks {
// TODO: add this to the "clean up" group for when the component is dropped // TODO: add this to the "clean up" group for when the component is dropped
_cleanup: impl FnOnce(InternalHookState), _cleanup: impl FnOnce(InternalHookState),
) -> Output { ) -> Output {
let raw_hook = { let idx = *self.idx.borrow();
let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed);
// Mutate hook list if necessary // Mutate hook list if necessary
let mut hooks = self.hooks.borrow_mut(); let mut hooks = self.hooks.borrow_mut();
// Initialize the hook by allocating it in the typed arena. // Initialize the hook by allocating it in the typed arena.
// We get a reference from the arena which is owned by the component scope // We get a reference from the arena which is owned by the component scope
// This is valid because "Context" is only valid while the scope is borrowed // This is valid because "Context" is only valid while the scope is borrowed
if idx >= hooks.len() { if idx >= hooks.len() {
let new_state = initializer(); let new_state = initializer();
let boxed_state: Box<dyn std::any::Any> = Box::new(new_state); hooks.push(Hook::new(new_state));
let hook = self.arena.alloc(Hook::new(boxed_state)); }
*self.idx.borrow_mut() = 1;
// Push the raw pointer instead of the &mut let stable_ref = hooks.get_mut(idx).unwrap().0.as_mut();
// A "poor man's OwningRef" let v = unsafe { Pin::get_unchecked_mut(stable_ref) };
hooks.push(hook); let internal_state = v.downcast_mut::<InternalHookState>().unwrap();
}
self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
*hooks.get(idx).unwrap() // we extend the lifetime from borrowed in this scope to borrowed from self.
}; // This is okay because the hook is pinned
runner(unsafe { &mut *(internal_state as *mut _) })
/* /*
** UNSAFETY ALERT ** ** UNSAFETY ALERT **
@@ -178,11 +180,17 @@ pub mod hooks {
- We don't expose the raw hook pointer outside of the scope of use_hook - We don't expose the raw hook pointer outside of the scope of use_hook
- The reference is tied to context, meaning it can only be used while ctx is around to free it - The reference is tied to context, meaning it can only be used while ctx is around to free it
*/ */
let borrowed_hook: &'a mut _ = unsafe { raw_hook.as_mut().unwrap() }; // let raw_hook: &'scope mut _ = unsafe { &mut *raw_hook };
// let p = raw_hook.0.downcast_mut::<InternalHookState>();
// let r = p.unwrap();
// let v = unsafe { Pin::get_unchecked_mut(raw_hook) };
// // let carreied_ref: &'scope mut dyn Any = unsafe { &mut *v };
// let internal_state = v.downcast_mut::<InternalHookState>().unwrap();
let internal_state = borrowed_hook.0.downcast_mut::<InternalHookState>().unwrap(); // let real_internal = unsafe { internal_state as *mut _ };
runner(internal_state) // runner(unsafe { &mut *real_internal })
// runner(internal_state)
} }
} }
} }
@@ -247,6 +255,8 @@ Context should *never* be dangling!. If a Context is torn down, so should anythi
pub fn use_context<I, O>(&'a self, _narrow: impl Fn(&'_ I) -> &'_ O) -> RemoteState<O> { pub fn use_context<I, O>(&'a self, _narrow: impl Fn(&'_ I) -> &'_ O) -> RemoteState<O> {
todo!() todo!()
} }
pub fn create_context<T: 'static>(&self, creator: impl FnOnce() -> T) {}
} }
/// # SAFETY ALERT /// # SAFETY ALERT
+99 -3
View File
@@ -5,6 +5,7 @@
//! - [ ] use_reducer //! - [ ] use_reducer
//! - [ ] use_effect //! - [ ] use_effect
pub use new_use_state_def::use_state_new;
pub use use_reducer_def::use_reducer; pub use use_reducer_def::use_reducer;
pub use use_ref_def::use_ref; pub use use_ref_def::use_ref;
pub use use_state_def::use_state; pub use use_state_def::use_state;
@@ -13,15 +14,94 @@ mod use_state_def {
use crate::innerlude::*; use crate::innerlude::*;
use std::{ use std::{
cell::RefCell, cell::RefCell,
fmt::Display,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
rc::Rc, rc::Rc,
}; };
struct UseState<T: 'static> {
new_val: Rc<RefCell<Option<T>>>,
current_val: T,
caller: Box<dyn Fn(T) + 'static>,
}
/// Store state between component renders!
/// When called, this hook retrives a stored value and provides a setter to update that value.
/// When the setter is called, the component is re-ran with the new value.
///
/// This is behaves almost exactly the same way as React's "use_state".
///
/// Usage:
/// ```ignore
/// static Example: FC<()> = |ctx| {
/// let (counter, set_counter) = use_state(ctx, || 0);
/// let increment = |_| set_couter(counter + 1);
/// let decrement = |_| set_couter(counter + 1);
///
/// html! {
/// <div>
/// <h1>"Counter: {counter}" </h1>
/// <button onclick={increment}> "Increment" </button>
/// <button onclick={decrement}> "Decrement" </button>
/// </div>
/// }
/// }
/// ```
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
ctx: &'c Context<'a>,
initial_state_fn: F,
) -> (&'a T, &'a impl Fn(T)) {
ctx.use_hook(
move || UseState {
new_val: Rc::new(RefCell::new(None)),
current_val: initial_state_fn(),
caller: Box::new(|_| println!("setter called!")),
},
move |hook| {
log::debug!("Use_state set called");
let inner = hook.new_val.clone();
let scheduled_update = ctx.schedule_update();
// get ownership of the new val and replace the current with the new
// -> as_ref -> borrow_mut -> deref_mut -> take
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T
if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
hook.current_val = new_val;
}
// todo: swap out the caller with a subscription call and an internal update
hook.caller = Box::new(move |new_val| {
// update the setter with the new value
let mut new_inner = inner.as_ref().borrow_mut();
*new_inner = Some(new_val);
// Ensure the component gets updated
scheduled_update();
});
// box gets derefed into a ref which is then taken as ref with the hook
(&hook.current_val, &hook.caller)
},
|_| {},
)
}
}
mod new_use_state_def {
use crate::innerlude::*;
use std::{
cell::RefCell,
fmt::{Debug, Display},
ops::{Deref, DerefMut},
pin::Pin,
rc::Rc,
};
pub struct UseState<T: 'static> { pub struct UseState<T: 'static> {
modifier: Rc<RefCell<Option<Box<dyn FnOnce(&mut T)>>>>, modifier: Rc<RefCell<Option<Box<dyn FnOnce(&mut T)>>>>,
current_val: T, current_val: T,
update: Box<dyn Fn() + 'static>, update: Box<dyn Fn() + 'static>,
setter: Box<dyn Fn(T) + 'static>,
// setter: Box<dyn Fn(T) + 'static>,
} }
// #[derive(Clone, Copy)] // #[derive(Clone, Copy)]
@@ -33,8 +113,11 @@ mod use_state_def {
impl<'a, T: 'static> UseState<T> { impl<'a, T: 'static> UseState<T> {
pub fn setter(&self) -> &dyn Fn(T) { pub fn setter(&self) -> &dyn Fn(T) {
todo!() &self.setter
// let r = self.setter.as_mut();
// unsafe { Pin::get_unchecked_mut(r) }
} }
pub fn set(&self, new_val: T) { pub fn set(&self, new_val: T) {
self.modify(|f| *f = new_val); self.modify(|f| *f = new_val);
} }
@@ -84,7 +167,7 @@ mod use_state_def {
/// } /// }
/// } /// }
/// ``` /// ```
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>( pub fn use_state_new<'a, 'c, T: 'static, F: FnOnce() -> T>(
ctx: &'c Context<'a>, ctx: &'c Context<'a>,
initial_state_fn: F, initial_state_fn: F,
) -> &'a UseState<T> { ) -> &'a UseState<T> {
@@ -93,19 +176,32 @@ mod use_state_def {
modifier: Rc::new(RefCell::new(None)), modifier: Rc::new(RefCell::new(None)),
current_val: initial_state_fn(), current_val: initial_state_fn(),
update: Box::new(|| {}), update: Box::new(|| {}),
setter: Box::new(|_| {}),
}, },
move |hook| { move |hook| {
log::debug!("addr of hook: {:#?}", hook as *const _);
let scheduled_update = ctx.schedule_update(); let scheduled_update = ctx.schedule_update();
// log::debug!("Checking if new value {:#?}", &hook.current_val);
// get ownership of the new val and replace the current with the new // get ownership of the new val and replace the current with the new
// -> as_ref -> borrow_mut -> deref_mut -> take // -> as_ref -> borrow_mut -> deref_mut -> take
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T // -> rc -> &RefCell -> RefMut -> &Option<T> -> T
if let Some(new_val) = hook.modifier.as_ref().borrow_mut().deref_mut().take() { if let Some(new_val) = hook.modifier.as_ref().borrow_mut().deref_mut().take() {
// log::debug!("setting prev {:#?}", &hook.current_val);
(new_val)(&mut hook.current_val); (new_val)(&mut hook.current_val);
// log::debug!("setting new value {:#?}", &hook.current_val);
} }
hook.update = Box::new(move || scheduled_update()); hook.update = Box::new(move || scheduled_update());
let modifier = hook.modifier.clone();
hook.setter = Box::new(move |new_val: T| {
let mut slot = modifier.as_ref().borrow_mut();
let slot2 = slot.deref_mut();
*slot2 = Some(Box::new(move |old: &mut T| *old = new_val));
});
&*hook &*hook
}, },
|_| {}, |_| {},
+7 -31
View File
@@ -566,6 +566,11 @@ where
std::iter::once(self) std::iter::once(self)
} }
} }
impl<'a> IntoDomTree<'a> for () {
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
VNode::Suspended
}
}
#[test] #[test]
fn test_iterator_of_nodes<'b>() { fn test_iterator_of_nodes<'b>() {
@@ -646,9 +651,6 @@ pub fn text2<'a>(contents: bumpalo::collections::String<'a>) -> VNode<'a> {
let f: &'a str = contents.into_bump_str(); let f: &'a str = contents.into_bump_str();
VNode::text(f) VNode::text(f)
} }
// pub fn text<'a>(contents: &'a str) -> VNode<'a> {
// VNode::text(contents)
// }
/// Construct an attribute for an element. /// Construct an attribute for an element.
/// ///
@@ -666,35 +668,9 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
Attribute { name, value } Attribute { name, value }
} }
// /// Create an event listener.
// ///
// /// `event` is the type of event to listen for, e.g. `"click"`. The `callback`
// /// is the function that will be invoked when the event occurs.
// ///
// /// # Example
// ///
// /// ```no_run
// /// use dioxus::{builder::*, bumpalo::Bump};
// ///
// /// let b = Bump::new();
// ///
// /// let listener = on(&b, "click", |root, vdom, event| {
// /// // do something when a click happens...
// /// });
// /// ```
// pub fn on<'a, 'b>(
// // pub fn on<'a, F: 'static>(
// bump: &'a Bump,
// event: &'static str,
// callback: impl Fn(VirtualEvent) + 'a,
// ) -> Listener<'a> {
// Listener {
// event,
// callback: bump.alloc(callback),
// }
// }
pub fn virtual_child<'a, T: Properties + 'a>(ctx: &NodeCtx<'a>, f: FC<T>, p: T) -> VNode<'a> { pub fn virtual_child<'a, T: Properties + 'a>(ctx: &NodeCtx<'a>, f: FC<T>, p: T) -> VNode<'a> {
// currently concerned about if props have a custom drop implementation
// might override it with the props macro
let propsd: &'a mut _ = ctx.bump.alloc(p); let propsd: &'a mut _ = ctx.bump.alloc(p);
VNode::Component(crate::nodes::VComponent::new(f, propsd)) VNode::Component(crate::nodes::VComponent::new(f, propsd))
} }
+7 -7
View File
@@ -36,9 +36,9 @@ pub struct Scope {
// These two could be combined with "OwningRef" to remove unsafe usage // These two could be combined with "OwningRef" to remove unsafe usage
// or we could dedicate a tiny bump arena just for them // or we could dedicate a tiny bump arena just for them
// could also use ourborous // could also use ourborous
pub hooks: RefCell<Vec<*mut Hook>>, pub hooks: RefCell<Vec<Hook>>,
pub hook_arena: typed_arena::Arena<Hook>, pub hook_arena: Vec<Hook>,
// Unsafety: // Unsafety:
// - is self-refenrential and therefore needs to point into the bump // - is self-refenrential and therefore needs to point into the bump
@@ -63,7 +63,7 @@ impl Scope {
Self { Self {
caller: broken_caller, caller: broken_caller,
hook_arena: typed_arena::Arena::new(), hook_arena: Vec::new(),
hooks: RefCell::new(Vec::new()), hooks: RefCell::new(Vec::new()),
frames: ActiveFrame::new(), frames: ActiveFrame::new(),
children: HashSet::new(), children: HashSet::new(),
@@ -92,7 +92,7 @@ impl Scope {
}; };
let ctx: Context<'b> = Context { let ctx: Context<'b> = Context {
arena: &self.hook_arena, // arena: &self.hook_arena,
hooks: &self.hooks, hooks: &self.hooks,
bump: &frame.bump, bump: &frame.bump,
idx: 0.into(), idx: 0.into(),
@@ -145,7 +145,7 @@ impl Scope {
// Run the callback with the user event // Run the callback with the user event
log::debug!("Running listener"); log::debug!("Running listener");
listener(source); listener(source);
log::debug!("Running listener"); log::debug!("Listener finished");
// drain all the event listeners // drain all the event listeners
// if we don't, then they'll stick around and become invalid // if we don't, then they'll stick around and become invalid
@@ -203,7 +203,7 @@ impl ActiveFrame {
} }
} }
fn current_head_node<'b>(&'b self) -> &'b VNode<'b> { pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
let raw_node = match *self.idx.borrow() & 1 == 0 { let raw_node = match *self.idx.borrow() & 1 == 0 {
true => &self.frames[0], true => &self.frames[0],
false => &self.frames[1], false => &self.frames[1],
@@ -217,7 +217,7 @@ impl ActiveFrame {
} }
} }
fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> { pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
let raw_node = match *self.idx.borrow() & 1 != 0 { let raw_node = match *self.idx.borrow() & 1 != 0 {
true => &self.frames[0], true => &self.frames[0],
false => &self.frames[1], false => &self.frames[1],
+25 -4
View File
@@ -6,6 +6,8 @@ use generational_arena::Arena;
use std::{ use std::{
any::TypeId, any::TypeId,
borrow::{Borrow, BorrowMut}, borrow::{Borrow, BorrowMut},
cell::RefCell,
collections::{BTreeMap, BTreeSet},
rc::{Rc, Weak}, rc::{Rc, Weak},
}; };
use thiserror::private::AsDynError; use thiserror::private::AsDynError;
@@ -19,11 +21,13 @@ pub(crate) type OpaqueComponent<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + '
pub struct VirtualDom { pub struct VirtualDom {
/// All mounted components are arena allocated to make additions, removals, and references easy to work with /// All mounted components are arena allocated to make additions, removals, and references easy to work with
/// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena. /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
components: Arena<Scope>, pub components: Arena<Scope>,
/// The index of the root component. /// The index of the root component.
/// Will not be ready if the dom is fresh /// Will not be ready if the dom is fresh
base_scope: ScopeIdx, pub base_scope: ScopeIdx,
pub(crate) update_schedule: Rc<RefCell<Vec<HierarchyMarker>>>,
// a strong allocation to the "caller" for the original props // a strong allocation to the "caller" for the original props
#[doc(hidden)] #[doc(hidden)]
@@ -70,6 +74,7 @@ impl VirtualDom {
components, components,
_root_caller: root_caller, _root_caller: root_caller,
base_scope, base_scope,
update_schedule: Rc::new(RefCell::new(Vec::new())),
_root_prop_type: TypeId::of::<P>(), _root_prop_type: TypeId::of::<P>(),
} }
} }
@@ -85,13 +90,14 @@ impl VirtualDom {
let mut diff_machine = DiffMachine::new(); let mut diff_machine = DiffMachine::new();
let very_unsafe_components = &mut self.components as *mut generational_arena::Arena<Scope>; let very_unsafe_components = &mut self.components as *mut generational_arena::Arena<Scope>;
let mut component = self let component = self
.components .components
.get_mut(self.base_scope) .get_mut(self.base_scope)
.ok_or_else(|| Error::FatalInternal("Acquring base component should never fail"))?; .ok_or_else(|| Error::FatalInternal("Acquring base component should never fail"))?;
component.run_scope()?; component.run_scope()?;
diff_machine.diff_node(component.old_frame(), component.new_frame()); diff_machine.diff_node(component.old_frame(), component.new_frame());
// log::debug!("New events generated: {:#?}", diff_machine.lifecycle_events);
// chew down the the lifecycle events until all dirty nodes are computed // chew down the the lifecycle events until all dirty nodes are computed
while let Some(event) = diff_machine.lifecycle_events.pop_front() { while let Some(event) = diff_machine.lifecycle_events.pop_front() {
match event { match event {
@@ -119,6 +125,7 @@ impl VirtualDom {
} }
} }
LifeCycleEvent::PropsChanged { caller, id, scope } => { LifeCycleEvent::PropsChanged { caller, id, scope } => {
log::debug!("PROPS CHANGED");
let idx = scope.upgrade().unwrap().as_ref().borrow().unwrap(); let idx = scope.upgrade().unwrap().as_ref().borrow().unwrap();
unsafe { unsafe {
let p = &mut *(very_unsafe_components); let p = &mut *(very_unsafe_components);
@@ -131,6 +138,7 @@ impl VirtualDom {
// break 'render; // break 'render;
} }
LifeCycleEvent::SameProps { caller, id, scope } => { LifeCycleEvent::SameProps { caller, id, scope } => {
log::debug!("SAME PROPS RECEIVED");
// //
// break 'render; // break 'render;
} }
@@ -181,7 +189,15 @@ impl VirtualDom {
.expect("Borrowing should not fail"); .expect("Borrowing should not fail");
component.call_listener(event); component.call_listener(event);
component.run_scope()?;
/*
-> call listener
-> sort through accumulated queue by the top
-> run each component, running its children
-> add new updates to the tree of updates (these are dirty)
->
*/
// component.run_scope()?;
// let mut diff_machine = DiffMachine::new(); // let mut diff_machine = DiffMachine::new();
// let mut diff_machine = DiffMachine::new(&self.diff_bump); // let mut diff_machine = DiffMachine::new(&self.diff_bump);
@@ -192,3 +208,8 @@ impl VirtualDom {
self.rebuild() self.rebuild()
} }
} }
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct HierarchyMarker {
height: u32,
}
+102
View File
@@ -0,0 +1,102 @@
<div title="About W3Schools">
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 0
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 1
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 2
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 3
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 4
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 5
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 6
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 7
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 8
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 9
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 10
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 11
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 12
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 13
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 14
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 15
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 16
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 17
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 18
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 19
</p>
</div>
</div>
+1
View File
@@ -20,6 +20,7 @@
//! //!
use dioxus_core::prelude::{VNode, FC}; use dioxus_core::prelude::{VNode, FC};
pub mod tostring;
pub mod prelude { pub mod prelude {
pub use dioxus_core::prelude::*; pub use dioxus_core::prelude::*;
+84
View File
@@ -0,0 +1,84 @@
use std::fmt::Display;
use dioxus_core::prelude::*;
use dioxus_core::{nodes::VNode, prelude::ScopeIdx, virtual_dom::VirtualDom};
struct SsrRenderer {
dom: VirtualDom,
}
impl Display for SsrRenderer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let node = self
.dom
.components
.get(self.dom.base_scope)
.unwrap()
.frames
.current_head_node();
html_render(&self.dom, node, f)
}
}
// recursively walk the tree
fn html_render(
dom: &VirtualDom,
node: &VNode,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
match node {
VNode::Element(el) => {
write!(f, "<{}", el.tag_name)?;
for attr in el.attributes {
write!(f, " {}=\"{}\"", attr.name, attr.value)?;
}
write!(f, ">\n")?;
for child in el.children {
html_render(dom, child, f)?;
}
write!(f, "\n</{}>", el.tag_name)?;
Ok(())
}
VNode::Text(t) => write!(f, "{}", t.text),
VNode::Suspended => todo!(),
VNode::Component(vcomp) => {
let id = vcomp.ass_scope.as_ref().borrow().unwrap();
let new_node = dom.components.get(id).unwrap().frames.current_head_node();
html_render(&dom, new_node, f)
}
}
}
#[test]
fn test_serialize() {
let mut dom = VirtualDom::new(|ctx, props| {
ctx.render(rsx! {
div {
title: "About W3Schools"
{(0..20).map(|f| rsx!{
div {
title: "About W3Schools"
style: "color:blue;text-align:center"
class: "About W3Schools"
p {
title: "About W3Schools"
"Hello world!: {f}"
}
}
})}
}
})
});
dom.rebuild();
let renderer = SsrRenderer { dom };
use std::fs::File;
use std::io::prelude::*;
let mut file = File::create("index.html").unwrap();
let buf = renderer.to_string();
// dbg!(buf);
file.write(buf.as_bytes());
// dbg!(renderer.to_string());
}
+4
View File
@@ -55,3 +55,7 @@ debug = true
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[dev-dependencies]
uuid = { version = "0.8.2", features = ["v4"] }
+3 -3
View File
@@ -10,7 +10,7 @@ fn main() {
use components::CustomB; use components::CustomB;
fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree { fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
let val = use_state(&ctx, || "abcdef".to_string()); let (val, set_val) = use_state(&ctx, || "abcdef".to_string());
ctx.render(rsx! { ctx.render(rsx! {
div { div {
@@ -18,11 +18,11 @@ fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
"CustomA {val}" "CustomA {val}"
button { button {
"Upper" "Upper"
onclick: move |_| val.set(val.to_ascii_uppercase()) onclick: move |_| set_val(val.to_ascii_uppercase())
} }
button { button {
"Lower" "Lower"
onclick: move |_| val.set(val.to_ascii_lowercase()) onclick: move |_| set_val(val.to_ascii_lowercase())
} }
CustomB { CustomB {
val: val.as_ref() val: val.as_ref()
+2 -2
View File
@@ -12,10 +12,10 @@ fn main() {
// this is a component // this is a component
static Example: FC<()> = |ctx, _props| { static Example: FC<()> = |ctx, _props| {
let event = use_state(&ctx, || None); let (event, set_event) = use_state(&ctx, || None);
let handler = move |evt: MouseEvent| { let handler = move |evt: MouseEvent| {
event.set(Some(evt)); set_event(Some(evt));
}; };
log::info!("hello world"); log::info!("hello world");
+4 -4
View File
@@ -10,7 +10,7 @@ fn main() {
} }
static Example: FC<()> = |ctx, props| { static Example: FC<()> = |ctx, props| {
let name = use_state(&ctx, || "...?"); let (name, set_name) = use_state(&ctx, || "...?");
log::debug!("Running component...."); log::debug!("Running component....");
@@ -32,14 +32,14 @@ static Example: FC<()> = |ctx, props| {
<div> <div>
<button <button
class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow" class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick={move |_| name.set("jack")}> onclick={move |_| set_name("jack")}>
"Jack!" "Jack!"
</button> </button>
<button <button
class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow" class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick={move |_| name.set("jill")} onclick={move |_| set_name("jill")}
onclick={move |_| name.set("jill")}> onclick={move |_| set_name("jill")}>
"Jill!" "Jill!"
</button> </button>
</div> </div>
+67 -38
View File
@@ -1,6 +1,6 @@
use std::collections::HashMap; use std::collections::{BTreeMap, BTreeSet, HashMap};
use dioxus::prelude::*; use dioxus::{events::on::MouseEvent, prelude::*};
use dioxus_core as dioxus; use dioxus_core as dioxus;
use dioxus_web::WebsysRenderer; use dioxus_web::WebsysRenderer;
@@ -17,37 +17,38 @@ fn main() {
use lazy_static::lazy_static; use lazy_static::lazy_static;
lazy_static! { lazy_static! {
static ref DummyData: HashMap<String, String> = { static ref DummyData: BTreeMap<usize, String> = {
let vals = vec![ let vals = vec![
("0 ", "abc123"), "abc123", //
("1 ", "abc124"), "abc124", //
("2 ", "abc125"), "abc125", //
("3 ", "abc126"), "abc126", //
("4 ", "abc127"), "abc127", //
("5 ", "abc128"), "abc128", //
("6 ", "abc129"), "abc129", //
("7 ", "abc1210"), "abc1210", //
("8 ", "abc1211"), "abc1211", //
("9 ", "abc1212"), "abc1212", //
("10 ", "abc1213"), "abc1213", //
("11 ", "abc1214"), "abc1214", //
("12 ", "abc1215"), "abc1215", //
("13 ", "abc1216"), "abc1216", //
("14 ", "abc1217"), "abc1217", //
("15 ", "abc1218"), "abc1218", //
("16 ", "abc1219"), "abc1219", //
("17 ", "abc1220"), "abc1220", //
("18 ", "abc1221"), "abc1221", //
("19 ", "abc1222"), "abc1222", //
]; ];
vals.into_iter() vals.into_iter()
.map(|(a, b)| (a.to_string(), b.to_string())) .map(ToString::to_string)
.enumerate()
.collect() .collect()
}; };
} }
static App: FC<()> = |ctx, _| { static App: FC<()> = |ctx, _| {
let items = use_state(&ctx, || DummyData.clone()); let items = use_state_new(&ctx, || DummyData.clone());
// handle new elements // handle new elements
let add_new = move |_| { let add_new = move |_| {
@@ -59,20 +60,18 @@ static App: FC<()> = |ctx, _| {
(_, 0) => "Buzz".to_string(), (_, 0) => "Buzz".to_string(),
_ => k.to_string(), _ => k.to_string(),
}; };
m.insert(k.to_string(), v); m.insert(k, v);
}) })
}; };
let elements = items.iter().map(|(k, v)| { let elements = items.iter().map(|(k, v)| {
rsx! { rsx! {
li { ListHelper {
span {"{k}: {v}"} name: k,
button { value: v
"Remove" onclick: move |_| {
onclick: move |_| { let key = k.clone();
let key_to_remove = k.clone(); items.modify(move |m| { m.remove(&key); } )
items.modify(move |m| { m.remove(&key_to_remove); } )
}
} }
} }
} }
@@ -81,17 +80,47 @@ static App: FC<()> = |ctx, _| {
ctx.render(rsx!( ctx.render(rsx!(
div { div {
h1 {"Some list"} h1 {"Some list"}
button {
// button to add new item "Remove all"
onclick: move |_| items.set(BTreeMap::new())
}
button { button {
"add new" "add new"
onclick: {add_new} onclick: {add_new}
} }
// list elements
ul { ul {
{elements} {elements}
} }
} }
)) ))
}; };
#[derive(Props)]
struct ListProps<'a, F: Fn(MouseEvent) + 'a> {
name: &'a usize,
value: &'a str,
onclick: F,
}
impl<F: Fn(MouseEvent)> PartialEq for ListProps<'_, F> {
fn eq(&self, other: &Self) -> bool {
// no references are ever the same
false
}
}
fn ListHelper<F: Fn(MouseEvent)>(ctx: Context, props: &ListProps<F>) -> DomTree {
let k = props.name;
let v = props.value;
ctx.render(rsx! {
li {
class: "flex items-center text-xl"
key: "{k}"
span { "{k}: {v}" }
button {
"__ Remove"
onclick: {&props.onclick}
}
}
})
}
+9 -13
View File
@@ -16,16 +16,13 @@ fn main() {
}); });
} }
#[derive(PartialEq, Props)] #[derive(PartialEq, Props)]
struct ExampleProps { struct ExampleProps {
initial_name: &'static str, initial_name: &'static str,
} }
static Example: FC<ExampleProps> = |ctx, props| { static Example: FC<ExampleProps> = |ctx, props| {
let name = use_state(&ctx, move || props.initial_name.to_string()); let name = use_state_new(&ctx, move || props.initial_name.to_string());
ctx.render(rsx! { ctx.render(rsx! {
div { div {
@@ -39,9 +36,9 @@ static Example: FC<ExampleProps> = |ctx, props| {
"Hello, {name}" "Hello, {name}"
} }
CustomButton { name: "Jack!", set_name: name.setter() } CustomButton { name: "Jack!", handler: move |evt| name.set("Jack".to_string()) }
CustomButton { name: "Jill!", set_name: name.setter() } CustomButton { name: "Jill!", handler: move |evt| name.set("Jill".to_string()) }
CustomButton { name: "Bob!", set_name: name.setter() } CustomButton { name: "Bob!", handler: move |evt| name.set("Bob".to_string())}
} }
}) })
}; };
@@ -49,23 +46,22 @@ static Example: FC<ExampleProps> = |ctx, props| {
#[derive(Props)] #[derive(Props)]
struct ButtonProps<'src> { struct ButtonProps<'src, F: Fn(MouseEvent)> {
name: &'src str, name: &'src str,
set_name: &'src dyn Fn(String) handler: F
} }
fn CustomButton<'a>(ctx: Context<'a>, props: &'a ButtonProps<'a>) -> DomTree { fn CustomButton<'b, 'a, F: Fn(MouseEvent)>(ctx: Context<'a>, props: &'b ButtonProps<'b, F>) -> DomTree {
ctx.render(rsx!{ ctx.render(rsx!{
button { button {
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow" class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onmouseover: move |evt| (props.set_name)(props.name.to_string()) onmouseover: {&props.handler}
"{props.name}" "{props.name}"
} }
}) })
} }
impl<F: Fn(MouseEvent)> PartialEq for ButtonProps<'_, F> {
impl PartialEq for ButtonProps<'_> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
false false
} }
+178
View File
@@ -0,0 +1,178 @@
use std::{
collections::{BTreeMap, BTreeSet},
sync::atomic::AtomicUsize,
};
use dioxus_core::prelude::*;
use dioxus_web::WebsysRenderer;
use uuid::Uuid;
// Entry point
fn main() {
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, props| {
ctx.create_context(|| model::TodoManager::new());
ctx.render(rsx! {
div {
TodoList {}
Footer {}
}
})
}))
}
static TodoList: FC<()> = |ctx, props| {
let todos = use_state_new(&ctx, || BTreeMap::<usize, model::TodoItem>::new());
let items = todos.iter().map(|(order, item)| {
rsx!(TodoItem {
// key: "{}",
todo: item
})
});
ctx.render(rsx! {
div {
{items}
}
})
};
#[derive(Debug, PartialEq, Props)]
struct TodoItemsProp<'a> {
todo: &'a model::TodoItem,
}
fn TodoItem(ctx: Context, props: &TodoItemsProp) -> DomTree {
let (editing, set_editing) = use_state(&ctx, || false);
let id = props.todo.id;
ctx.render(rsx! (
li {
div {
"{id}"
}
// {input}
}
))
}
static Footer: FC<()> = |ctx, props| {
ctx.render(html! {
<footer className="info">
<p>"Double-click to edit a todo"</p>
<p>
"Created by "<a href="http://github.com/jkelleyrtp/">"jkelleyrtp"</a>
</p>
<p>
"Part of "<a href="http://todomvc.com">"TodoMVC"</a>
</p>
</footer>
})
};
// The data model that the todo mvc uses
mod model {
use std::{borrow::BorrowMut, future::Future};
use super::*;
#[derive(Debug, PartialEq, Clone)]
pub struct TodoItem {
pub id: Uuid,
pub checked: bool,
pub contents: String,
}
struct Dispatcher {}
struct AppContext<T: Clone> {
_t: std::rc::Rc<T>,
}
impl<T: Clone> AppContext<T> {
fn dispatch(&self, f: impl FnOnce(&mut T)) {}
fn async_dispatch(&self, f: impl Future<Output = ()>) {}
fn get<G>(&self, f: impl Fn(&T) -> &G) -> &G {
f(&self._t)
}
fn set(&self, orig: &mut std::borrow::Cow<T>) {
let r = orig.to_mut();
}
}
// use im-rc if your contexts are too large to clone!
// or, dangerously mutate and update subscriptions manually
#[derive(Clone)]
pub struct TodoManager {
items: Vec<u32>,
}
impl AppContext<TodoManager> {
fn remove_todo(&self, id: Uuid) {
self.dispatch(|f| {})
}
async fn push_todo(&self, todo: TodoItem) {
self.dispatch(|f| {
//
f.items.push(10);
});
}
fn add_todo(&self) {
// self.dispatch(|f| {});
// let items = self.get(|f| &f.items);
}
}
impl TodoManager {
pub fn new() -> Self {
todo!()
}
pub fn get_todo(&self) -> &TodoItem {
todo!()
}
}
pub struct TodoHandle {}
impl TodoHandle {
fn get_todo(&self, id: Uuid) -> &TodoItem {
todo!()
}
fn add_todo(&self, todo: TodoItem) {}
}
// use_reducer, but exposes the reducer and context to children
fn use_reducer_context() {}
fn use_context_selector() {}
fn use_context<'b, 'c, Root: 'static, Item: 'c>(
ctx: &'b Context<'c>,
f: impl Fn(Root) -> &'c Item,
) -> &'c Item {
todo!()
}
pub fn use_todo_item<'b, 'c>(ctx: &'b Context<'c>, item: Uuid) -> &'c TodoItem {
todo!()
// ctx.use_hook(|| TodoManager::new(), |hook| {}, cleanup)
}
fn use_todos(ctx: &Context) -> TodoHandle {
todo!()
}
fn use_todo_context(ctx: &Context) -> AppContext<TodoManager> {
todo!()
}
fn test(ctx: Context) {
let todos = use_todos(&ctx);
let todo = todos.get_todo(Uuid::new_v4());
let c = use_todo_context(&ctx);
// todos.add_todo();
}
}