Feat: more ergonomics, more examples

This commit is contained in:
Jonathan Kelley 2021-03-26 15:50:28 -04:00
parent 44aad2746c
commit 0bcff1f88e
15 changed files with 488 additions and 469 deletions

View file

@ -12,13 +12,13 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla
```rust
static Example: FC<()> = |ctx, props| {
let (selection, set_selection) = use_state(&ctx, || "...?");
let selection = use_state(&ctx, || "...?");
ctx.render(rsx! {
div {
h1 { "Hello, {selection}" }
button { "?", onclick: move |_| set_selection("world!")}
button { "?", onclick: move |_| set_selection("Dioxus 🎉")}
button { "?", onclick: move |_| selection.set("world!")}
button { "?", onclick: move |_| selection.set("Dioxus 🎉")}
}
})
};

View file

@ -1,42 +1,3 @@
/*
https://github.com/chinedufn/percy/issues/37
An example usage of rsx! would look like this:
```ignore
ctx.render(rsx!{
div {
h1 { "Example" },
p {
tag: "type",
abc: 123,
enabled: true,
class: "big small wide short",
a { "abcder" },
h2 { "whatsup", class: "abc-123" },
CustomComponent { a: 123, b: 456, key: "1" },
{ 0..3.map(|i| rsx!{ h1 {"{:i}"} }) },
{expr}
// expr can take:
// - iterator
// - |bump| { }
// - value (gets formatted as &str)
// - ... more as we upgrade it
}
}
})
```
optionally, include the allocator directly
rsx!(ctx, div { "hello"} )
each element is given by tag { [Attr] }
*/
use syn::parse::{discouraged::Speculative, ParseBuffer};
use {
@ -72,11 +33,6 @@ impl Parse for RsxRender {
})
.ok();
// cannot accept multiple elements
// can only accept one root per component
// fragments can be used as
// todo
// enable fragements by autocoerrcing into list
let el: Element = input.parse()?;
Ok(Self { el, custom_context })
}
@ -85,7 +41,6 @@ impl Parse for RsxRender {
impl ToTokens for RsxRender {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let new_toks = (&self.el).to_token_stream();
// let new_toks = ToToksCtx::new(&self.kind).to_token_stream();
// create a lazy tree that accepts a bump allocator
let final_tokens = match &self.custom_context {
@ -130,42 +85,40 @@ impl ToTokens for &Node {
impl Parse for Node {
fn parse(stream: ParseStream) -> Result<Self> {
let fork1 = stream.fork();
let fork2 = stream.fork();
let fork3 = stream.fork();
// Supposedly this approach is discouraged due to inability to return proper errors
// TODO: Rework this to provide more informative errors
// todo: map issues onto the second fork if any arise
// it's unlikely that textnodes or components would fail?
let ret = if let Ok(text) = fork1.parse::<TextNode>() {
stream.advance_to(&fork1);
Self::Text(text)
} else if let Ok(element) = fork2.parse::<Element>() {
stream.advance_to(&fork2);
Self::Element(element)
} else if let Ok(comp) = fork3.parse::<Component>() {
stream.advance_to(&fork3);
Self::Component(comp)
} else {
return Err(Error::new(
stream.span(),
"Failed to parse as a valid child",
));
};
// consume comma if it exists
// we don't actually care if there *are* commas after elements/text
if stream.peek(Token![,]) {
let _ = stream.parse::<Token![,]>();
let fork = stream.fork();
if let Ok(text) = fork.parse::<TextNode>() {
stream.advance_to(&fork);
return Ok(Self::Text(text));
}
Ok(ret)
let fork = stream.fork();
if let Ok(element) = fork.parse::<Element>() {
stream.advance_to(&fork);
return Ok(Self::Element(element));
}
let fork = stream.fork();
if let Ok(comp) = fork.parse::<Component>() {
stream.advance_to(&fork);
return Ok(Self::Component(comp));
}
let fork = stream.fork();
if let Ok(tok) = try_parse_bracketed(&fork) {
stream.advance_to(&fork);
return Ok(Node::RawExpr(tok));
}
return Err(Error::new(stream.span(), "Failed to parse as a valid node"));
}
}
struct Component {
name: Ident,
body: Vec<ComponentField>,
// attrs: Vec<Attr>,
children: Vec<Node>,
}
@ -185,38 +138,6 @@ impl ToTokens for &Component {
.build()
});
// let mut toks = quote! {};
// for attr in self.inner.attrs.iter() {
// self.recurse(attr).to_tokens(&mut toks);
// }
// panic!("tokens are {:#?}", toks);
// no children right now
// match &self.inner.children {
// MaybeExpr::Expr(expr) => tokens.append_all(quote! {
// .children(#expr)
// }),
// MaybeExpr::Literal(nodes) => {
// let mut children = nodes.iter();
// if let Some(child) = children.next() {
// let mut inner_toks = TokenStream2::new();
// self.recurse(child).to_tokens(&mut inner_toks);
// while let Some(child) = children.next() {
// quote!(,).to_tokens(&mut inner_toks);
// self.recurse(child).to_tokens(&mut inner_toks);
// }
// tokens.append_all(quote! {
// .children([#inner_toks])
// });
// }
// }
// }
// tokens.append_all(quote! {
// .finish()
// });
let _toks = tokens.append_all(quote! {
dioxus::builder::virtual_child(ctx, #name, #builder)
});
@ -225,8 +146,6 @@ impl ToTokens for &Component {
impl Parse for Component {
fn parse(s: ParseStream) -> Result<Self> {
// TODO: reject anything weird/nonstandard
// we want names *only*
let name = Ident::parse_any(s)?;
if crate::util::is_valid_tag(&name.to_string()) {
@ -252,11 +171,16 @@ impl Parse for Component {
if let Ok(field) = content.parse::<ComponentField>() {
body.push(field);
}
// consume comma if it exists
// we don't actually care if there *are* commas between attrs
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
// todo: add support for children
let children: Vec<Node> = vec![];
// let children = MaybeExpr::Literal(children);
Ok(Self {
name,
@ -275,25 +199,16 @@ pub struct ComponentField {
impl Parse for ComponentField {
fn parse(input: ParseStream) -> Result<Self> {
let name = Ident::parse_any(input)?;
let _name_str = name.to_string();
input.parse::<Token![:]>()?;
let content = input.parse()?;
// consume comma if it exists
// we don't actually care if there *are* commas between attrs
if input.peek(Token![,]) {
let _ = input.parse::<Token![,]>();
}
Ok(Self { name, content })
}
}
impl ToTokens for &ComponentField {
// impl ToTokens for ToToksCtx<&ComponentField> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
let content = &self.content;
let ComponentField { name, content, .. } = self;
tokens.append_all(quote! {
.#name(#content)
})
@ -307,7 +222,6 @@ struct Element {
name: Ident,
attrs: Vec<Attr>,
children: Vec<Node>,
// children: MaybeExpr<Vec<Node>>,
}
impl ToTokens for &Element {
@ -321,46 +235,15 @@ impl ToTokens for &Element {
for attr in self.attrs.iter() {
attr.to_tokens(tokens);
}
// match &self.children {
// // MaybeExpr::Expr(expr) => tokens.append_all(quote! {
// // .iter_child(#expr)
// // }),
// MaybeExpr::Literal(nodes) => {
// let mut children = nodes.iter();
let mut children = self.children.iter();
while let Some(child) = children.next() {
// if let Some(child) = children.next() {
// let mut inner_toks = TokenStream2::new();
// child.to_tokens(&mut inner_toks);
// while let Some(child) = children.next() {
match child {
// raw exprs need to be wrapped in a once type?
Node::RawExpr(_) => {
let inner_toks = child.to_token_stream();
tokens.append_all(quote! {
.iter_child(std::iter::once(#inner_toks))
})
}
_ => {
let inner_toks = child.to_token_stream();
tokens.append_all(quote! {
.iter_child(#inner_toks)
})
}
}
// quote!(,).to_tokens(&mut inner_toks);
// child.to_tokens(&mut inner_toks);
// }
// while let Some(child) = children.next() {
// quote!(,).to_tokens(&mut inner_toks);
// child.to_tokens(&mut inner_toks);
// }
// tokens.append_all(quote! {
// .iter_child([#inner_toks])
// });
let inner_toks = child.to_token_stream();
tokens.append_all(quote! {
.iter_child(#inner_toks)
})
}
// }
// }
tokens.append_all(quote! {
.finish()
});
@ -383,8 +266,6 @@ impl Parse for Element {
let mut children: Vec<Node> = vec![];
parse_element_content(content, &mut attrs, &mut children)?;
// let children = MaybeExpr::Literal(children);
Ok(Self {
name,
attrs,
@ -400,12 +281,11 @@ fn parse_element_content(
children: &mut Vec<Node>,
) -> Result<()> {
'parsing: loop {
// todo move this around into a more functional style
// [1] Break if empty
// [2] Try to consume an attr (with comma)
// [3] Try to consume a child node (with comma)
// [4] Try to consume brackets as anything thats Into<Node>
// [last] Fail if none worked
// consume comma if it exists
// we don't actually care if there *are* commas after elements/text
if stream.peek(Token![,]) {
let _ = stream.parse::<Token![,]>();
}
// [1] Break if empty
if stream.is_empty() {
@ -430,33 +310,6 @@ fn parse_element_content(
continue 'parsing;
}
// [4] TODO: Parsing brackets
let fork = stream.fork();
if fork.peek(token::Brace) {
// todo!("Add support");
// this can fail (mismatched brackets)
// let content;
// syn::braced!(content in &stream);
match try_parse_bracketed(&fork) {
Ok(tok) => {
children.push(Node::RawExpr(tok))
// todo!("succeeded")
}
Err(e) => {
todo!("failed {}", e)
}
}
stream.advance_to(&fork);
continue 'parsing;
// let fork = content.fork();
// stream.advance_to(fork)
}
// if let Ok(el) = fork.parse() {
// children.push(el);
// continue 'parsing;
// }
// todo: pass a descriptive error onto the offending tokens
panic!("Entry is not an attr or element\n {}", stream)
}
@ -471,9 +324,6 @@ fn try_parse_bracketed(stream: &ParseBuffer) -> Result<Expr> {
/// =======================================
/// Parse a VElement's Attributes
/// =======================================
/// - [ ] Allow expressions as attribute
///
///
struct Attr {
name: Ident,
ty: AttrType,
@ -540,7 +390,6 @@ impl Parse for Attr {
}
impl ToTokens for &Attr {
// impl ToTokens for ToToksCtx<&Attr> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = self.name.to_string();
let nameident = &self.name;
@ -555,13 +404,11 @@ impl ToTokens for &Attr {
tokens.append_all(quote! {
.add_listener(dioxus::events::on::#nameident(ctx, #event))
});
// .on(#name, #event)
}
AttrType::Tok(exp) => {
tokens.append_all(quote! {
.add_listener(dioxus::events::on::#nameident(ctx, #exp))
});
// .on(#name, #exp)
}
}
}
@ -571,17 +418,12 @@ enum AttrType {
Value(LitStr),
Event(ExprClosure),
Tok(Expr),
// todo Bool(MaybeExpr<LitBool>)
}
// =======================================
// Parse just plain text
// =======================================
// - [ ] Perform formatting automatically
//
//
struct TextNode(LitStr);
// struct TextNode(MaybeExpr<LitStr>);
impl Parse for TextNode {
fn parse(s: ParseStream) -> Result<Self> {
@ -590,9 +432,8 @@ impl Parse for TextNode {
}
impl ToTokens for TextNode {
// impl ToTokens for ToToksCtx<&TextNode> {
// self.recurse(&self.inner.0).to_tokens(&mut token_stream);
fn to_tokens(&self, tokens: &mut TokenStream2) {
// todo: use heuristics to see if we can promote to &static str
let token_stream = &self.0.to_token_stream();
tokens.append_all(quote! {
{
@ -604,29 +445,3 @@ impl ToTokens for TextNode {
});
}
}
// enum MaybeExpr<T> {
// Literal(T),
// Expr(Expr),
// }
// impl<T: Parse> Parse for MaybeExpr<T> {
// fn parse(s: ParseStream) -> Result<Self> {
// if s.peek(token::Brace) {
// let content;
// syn::braced!(content in s);
// Ok(MaybeExpr::Expr(content.parse()?))
// } else {
// Ok(MaybeExpr::Literal(s.parse()?))
// }
// }
// }
// impl<'a, T: 'a + ToTokens> ToTokens for &'a MaybeExpr<T> {
// fn to_tokens(&self, tokens: &mut TokenStream2) {
// match &self {
// MaybeExpr::Literal(v) => v.to_tokens(tokens),
// MaybeExpr::Expr(expr) => expr.to_tokens(tokens),
// }
// }
// }

View file

@ -7,6 +7,8 @@
fn main() {}
use std::borrow::Borrow;
use dioxus_core::prelude::*;
struct Props {
@ -19,8 +21,8 @@ struct ListItem {
age: u32,
}
fn app(ctx: Context, props: &Props) -> DomTree {
let (_f, setter) = use_state(&ctx, || 0);
fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
let val = use_state(&ctx, || 0);
ctx.render(dioxus::prelude::LazyNodes::new(move |c| {
let mut root = builder::ElementBuilder::new(c, "div");
@ -34,7 +36,7 @@ fn app(ctx: Context, props: &Props) -> DomTree {
// create the props with nothing but the fc<T>
fc_to_builder(ChildItem)
.item(child)
.item_handler(setter)
.item_handler(val.setter())
.build(),
));
}
@ -42,15 +44,6 @@ fn app(ctx: Context, props: &Props) -> DomTree {
}))
}
type StateSetter<T> = dyn Fn(T);
// struct StateSetter<T>(dyn Fn(T));
// impl<T> PartialEq for StateSetter<T> {
// fn eq(&self, other: &Self) -> bool {
// self as *const _ == other as *const _
// }
// }
// props should derive a partialeq implementation automatically, but implement ptr compare for & fields
#[derive(Props)]
struct ChildProps<'a> {
@ -58,153 +51,26 @@ struct ChildProps<'a> {
item: &'a ListItem,
// Even pass down handlers!
item_handler: &'a StateSetter<i32>,
item_handler: &'a dyn Fn(i32),
}
impl PartialEq for ChildProps<'_> {
fn eq(&self, _other: &Self) -> bool {
// assume the dyn fn is never stable -
// wrap with use_callback if it's an issue for you
false
}
}
fn ChildItem(_ctx: Context, _props: &ChildProps) -> DomTree {
todo!()
// ctx.render(rsx! {
// div {
// item: child,
// handler: setter,
// abc: 123,
// onclick: props.item_handler,
// h1 { "abcd123" }
// h2 { "abcd123" }
// div {
// "abcd123"
// h2 { }
// p { }
// },
// }
// })
}
/*
rsx! {
ChildItem {
// props
item: child, handler: setter,
// children
div { class:"abcd", abc: 123 },
div { class:"abcd", abc: 123 },
// Auto-text coercion
"eyo matie {abc}",
// Anything that accepts Into<VChild>
{},
}
}
// dreaming of this syntax
#[derive(Properties)]
struct ChildProps<'a> {
username: &'a str,
item_handler: &'a dyn Fn(i32),
}
fn child_item(ctx: Context, props: &ChildProps) -> DomTree {
fn ChildItem<'a>(ctx: Context<'a>, props: &ChildProps) -> DomTree {
ctx.render(rsx! {
div {
class: "abc123",
abc: 123,
onclick: props.item_handler,
h1 { "Hello, {props.username}!" },
h2 { "Welcome the RSX syntax" },
onclick: move |evt| (props.item_handler)(10)
h1 { "abcd123" }
h2 { "abcd123" }
div {
h3 { "This is a subheader" }
button {
onclick: props.handler,
"This is a button"
}
"This is child text"
},
"abcd123"
h2 { }
p { }
}
}
})
}
// This is also nice
#[dioxus::component]
static CHILD: FC = |ctx, username: &str, handler: &dyn Fn(i32)| {
ctx.render(rsx! {
div {
class: "abc123",
abc: 123,
onclick: handler,
h1 { "Hello, {username}!" },
h2 { "Welcome the RSX syntax" },
div {
h3 { "This is a subheader" }
button {
onclick: props.handler,
"This is a button"
}
"This is child text"
},
}
})
}
Menlo, Monaco, 'Courier New', monospace
struct Item {
name: String,
content: String,
}
#[dioxus::live_component]
static CHILD: FC = |ctx, username: &str, handler: &dyn Fn(i32)| {
// return lazy nodes or
let ssr = ctx.suspend(async {
let data = fetch("https://google.com")
.await?
.json::<Item>()
.await?;
rsx! {
div {
h1 { "Welcome: {data.name}" }
p { "Content: \n {data.content}" }
}
}
});
ctx.render(rsx! {
div {
class: "abc123",
abc: 123,
onclick: handler,
h1 { "Hello, {username}!" },
h2 { "Welcome the RSX syntax" },
div {
h3 { "This is a subheader" }
button {
onclick: props.handler,
"This is a button"
}
{ssr}
},
}
})
}
*/

View file

@ -11,13 +11,13 @@ fn main() {
}
static Example: FC<()> = |ctx, props| {
let (name, set_name) = use_state(&ctx, || "...?");
let name = use_state(&ctx, || "...?");
ctx.render(html! {
<div>
<h1> "Hello, {name}" </h1>
<button onclick={move |_| set_name("jack")}> "jack!" </button>
<button onclick={move |_| set_name("jill")}> "jill!" </button>
<button onclick={move |_| name.set("jack")}> "jack!" </button>
<button onclick={move |_| name.set("jill")}> "jill!" </button>
</div>
})
};

View file

@ -61,9 +61,9 @@ impl<'a> Context<'a> {
/// Create a suspended component from a future.
///
/// When the future completes, the component will be renderered
pub fn suspend(
pub fn suspend<F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
&self,
_fut: impl Future<Output = impl FnOnce(&'a NodeCtx<'a>) -> VNode<'a>>,
_fut: impl Future<Output = LazyNodes<'a, F>>,
) -> VNode<'a> {
todo!()
}

View file

@ -40,6 +40,33 @@ impl DebugRenderer {
pub fn step(&mut self, machine: &mut DiffMachine) -> Result<()> {
Ok(())
}
// this does a "holy" compare - if something is missing in the rhs, it doesn't complain.
// it only complains if something shows up that's not in the lhs, *or* if a value is different.
// This lets you exclude various fields if you just want to drill in to a specific prop
// It leverages the internal diffing mechanism.
// If you have a list or "nth" child, you do need to list those children, but you don't need to
// fill in their children/attrs/etc
// Does not handle children or lifecycles and will always fail the test if they show up in the rhs
pub fn compare<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()>
where
F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
{
Ok(())
}
// Do a full compare - everything must match
// Ignores listeners and children components
pub fn compare_full<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()>
where
F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
{
Ok(())
}
pub fn trigger_listener(&mut self, id: usize) -> Result<()> {
Ok(())
}
}
#[cfg(test)]

View file

@ -5,6 +5,7 @@
//! - [ ] use_reducer
//! - [ ] use_effect
pub use use_reducer_def::use_reducer;
pub use use_ref_def::use_ref;
pub use use_state_def::use_state;
@ -12,29 +13,52 @@ mod use_state_def {
use crate::innerlude::*;
use std::{
cell::RefCell,
fmt::Display,
ops::{Deref, DerefMut},
rc::Rc,
};
struct UseState<T: 'static> {
new_val: Rc<RefCell<Option<T>>>,
pub struct UseState<T: 'static> {
modifier: Rc<RefCell<Option<Box<dyn FnOnce(&mut T)>>>>,
current_val: T,
caller: Box<dyn Fn(T) + 'static>,
update: Box<dyn Fn() + 'static>,
}
struct UseStateHandle<'a, T> {
inner: &'a T,
}
impl<'a, T> UseStateHandle<'a, T> {
fn set(&self, new_val: T) {}
fn modify(&self, f: impl FnOnce(&mut T)) {}
}
// #[derive(Clone, Copy)]
// pub struct UseStateHandle<'a, T: 'static> {
// inner: &'a UseState<T>,
// // pub setter: &'a dyn Fn(T),
// // pub modifier: &'a dyn Fn(&mut T),
// }
impl<'a, T> Deref for UseStateHandle<'a, T> {
impl<'a, T: 'static> UseState<T> {
pub fn setter(&self) -> &dyn Fn(T) {
todo!()
}
pub fn set(&self, new_val: T) {
self.modify(|f| *f = new_val);
}
// signal that we need to be updated
// save the modifier
pub fn modify(&self, f: impl FnOnce(&mut T) + 'static) {
let mut slot = self.modifier.as_ref().borrow_mut();
*slot = Some(Box::new(f));
(self.update)();
}
}
impl<'a, T: 'static> std::ops::Deref for UseState<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner
&self.current_val
}
}
// enable displaty for the handle
impl<'a, T: 'static + Display> std::fmt::Display for UseState<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.current_val)
}
}
@ -63,37 +87,26 @@ mod use_state_def {
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)) {
) -> &'a UseState<T> {
ctx.use_hook(
move || UseState {
new_val: Rc::new(RefCell::new(None)),
modifier: Rc::new(RefCell::new(None)),
current_val: initial_state_fn(),
caller: Box::new(|_| println!("setter called!")),
update: Box::new(|| {}),
},
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;
if let Some(new_val) = hook.modifier.as_ref().borrow_mut().deref_mut().take() {
(new_val)(&mut hook.current_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);
hook.update = Box::new(move || scheduled_update());
// 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)
&*hook
},
|_| {},
)

View file

@ -496,6 +496,17 @@ pub trait IntoDomTree<'a> {
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a>;
}
pub trait DomTreeBuilder<'a, G>: IntoIterator<Item = G>
where
G: IntoDomTree<'a>,
{
}
impl<'a, F> DomTreeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a
{
}
// Cover the cases where nodes are pre-rendered.
// Likely used by enums.
// ----
@ -547,7 +558,7 @@ where
// Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator
impl<'a, G> IntoIterator for LazyNodes<'a, G>
where
G: for<'b, 'c> FnOnce(&'b NodeCtx<'c>) -> VNode<'c> + 'a,
G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
{
type Item = Self;
type IntoIter = std::iter::Once<Self::Item>;

View file

@ -0,0 +1,152 @@
/*
This example shows how to encapsulate sate in dioxus components with the reducer pattern.
This pattern is very useful when a single component can handle many types of input that can
be represented by an enum. This particular pattern is very powerful in rust where ADTs can simplify
much of the traditional reducer boilerplate.
*/
#![allow(unused)]
use std::future::Future;
use dioxus::hooks::use_reducer;
use dioxus_ssr::prelude::*;
enum Actions {
Pause,
Play,
}
struct SomeState {
is_playing: bool,
}
impl SomeState {
fn new() -> Self {
Self { is_playing: false }
}
fn reduce(&mut self, action: Actions) {
match action {
Actions::Pause => self.is_playing = false,
Actions::Play => self.is_playing = true,
}
}
fn is_playing(&self) -> &'static str {
match self.is_playing {
true => "currently playing!",
false => "not currently playing",
}
}
}
pub static ExampleReducer: FC<()> = |ctx, props| {
let (state, reduce) = use_reducer(&ctx, SomeState::new, SomeState::reduce);
let is_playing = state.is_playing();
ctx.render(rsx! {
div {
h1 {"Select an option"}
h3 {"The radio is... {is_playing}!"}
button {
"Pause"
onclick: move |_| reduce(Actions::Pause)
}
button {
"Play"
onclick: move |_| reduce(Actions::Play)
}
}
})
};
/*
*/
struct AppContext {
name: String,
}
enum KindaState {
Ready,
Complete,
Erred,
}
static EnumReducer: FC<()> = |ctx, props| {
let (state, reduce) = use_reducer(&ctx, || KindaState::Ready, |cur, new| *cur = new);
let contents = helper(&ctx);
let status = match state {
KindaState::Ready => "Ready",
KindaState::Complete => "Complete",
KindaState::Erred => "Erred",
};
ctx.render(rsx! {
div {
h1 {"{status}"}
{contents}
button {
"Set Ready"
onclick: move |_| reduce(KindaState::Ready)
}
button {
"Set Complete"
onclick: move |_| reduce(KindaState::Complete)
}
button {
"Set Erred"
onclick: move |_| reduce(KindaState::Erred)
}
ul {
{(0..10).map(|f| {
rsx!{
li {
"hello there!"
}
}
})}
}
}
})
};
fn helper(ctx: &Context) -> DomTree {
ctx.render(rsx! {
div {}
})
}
/// Demonstrate how the DebugRenderer can be used to unit test components without needing a browser
/// These tests can run locally.
/// They use the "compare" method of the debug renderer to do partial tree compares for succint
#[test]
fn ensure_it_works_properly() -> dioxus::error::Result<()> {
let mut test = DebugRenderer::new(EnumReducer);
test.compare(rsx! { div { h1 {"Ready"} } })?;
test.trigger_listener(1)?;
test.compare(rsx! { div { h1 {"Ready"} } })?;
test.trigger_listener(2)?;
test.compare(rsx! { div { h1 {"Complete"} } })?;
test.trigger_listener(3)?;
test.compare(rsx! { div { h1 {"Erred"} } })?;
Ok(())
}

View file

@ -1,43 +1,52 @@
use dioxus_ssr::{prelude::*, TextRenderer};
use dioxus_ssr::{
prelude::{builder::IntoDomTree, dioxus::events::on::MouseEvent, *},
TextRenderer,
};
mod components {
mod app;
}
fn main() {
TextRenderer::new(App);
}
#[derive(Debug, PartialEq)]
enum Routes {
enum Route {
Homepage,
ExampleList,
Examples,
}
#[derive(Debug, PartialEq, Props)]
struct AppProps {
route: Routes,
route: Route,
}
trait Blah {}
impl<'a, G> Blah for LazyNodes<'a, G> where G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a {}
static App: FC<AppProps> = |ctx, props| {
//
let body = rsx! {
div {}
};
let body = match props.route {
Route::Homepage => ctx.render(rsx! {
div {
"Some Content"
}
}),
let top = rsx! {
div {}
Route::Examples => ctx.render(rsx! {
div {
"Other Content"
}
}),
};
ctx.render(rsx!(
div {
Header {}
{body}
{top}
{rsx!{
div {
"you ugl"
}
}}
ul {
{(0..10).map(|f| rsx!{
li {
"this is list item {f}"
}
})}
}
}
))
};
@ -49,3 +58,48 @@ static Header: FC<()> = |ctx, _| {
}
})
};
mod example {
use super::*;
static ExampleUsage: FC<()> = |ctx, props| {
// direct rsx!
let body = rsx! {
div {}
};
// rendered rsx!
let top = ctx.render(rsx! {
div {
"ack!"
}
});
// listy rsx
let list2 = (0..10).map(|f| {
rsx! {
div {}
}
});
// rendered list rsx
let list = (0..10).map(|f| {
ctx.render(rsx! {
div {}
})
});
ctx.render(rsx!(
div {
Header {}
{body}
{top}
{list}
{list2}
// inline rsx
{rsx!{
div { "hello" }
}}
}
))
};
}

View file

@ -10,27 +10,27 @@ fn main() {
use components::CustomB;
fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
let (val, set_val) = use_state(&ctx, || "abcdef".to_string());
let val = use_state(&ctx, || "abcdef".to_string());
ctx.render(rsx! {
div {
class: "m-8"
"CustomA {val}"
button {
"Upper"
onclick: move |_| set_val(val.to_ascii_uppercase())
onclick: move |_| val.set(val.to_ascii_uppercase())
}
button {
"Lower"
onclick: move |_| set_val(val.to_ascii_lowercase())
onclick: move |_| val.set(val.to_ascii_lowercase())
}
CustomB {
val: val
val: val.as_ref()
}
}
})
}
mod components {
use super::*;

View file

@ -12,11 +12,10 @@ fn main() {
// this is a component
static Example: FC<()> = |ctx, _props| {
let (event, set_event) = use_state(&ctx, || None);
let event = format!("{:#?}", event);
let event = use_state(&ctx, || None);
let handler = move |evt: MouseEvent| {
set_event(Some(evt));
event.set(Some(evt));
};
log::info!("hello world");
@ -36,9 +35,9 @@ static Example: FC<()> = |ctx, _props| {
pre {
onmousemove: {handler}
id: "json"
"{event}"
"Hello world"
}
Example2 { name: event }
Example2 { name: "Blah".into() }
}
})
};

View file

@ -10,19 +10,11 @@ fn main() {
}
static Example: FC<()> = |ctx, props| {
let (name, set_name) = use_state(&ctx, || "...?");
let name = use_state(&ctx, || "...?");
log::debug!("Running component....");
ctx.render(html! {
// <div>
// <h1> "Hello, {name}" </h1>
// <button onclick={move |_| set_name("jack")}> "jack!" </button>
// <button
// onclick={move |_| set_name("jill")}
// onclick={move |_| set_name("jill")}
// > "jill!" </button>
// </div>
<div>
<section class="py-12 px-4 text-center">
<div class="w-full max-w-2xl mx-auto">
@ -40,15 +32,14 @@ static Example: FC<()> = |ctx, props| {
<div>
<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"
// onclick={move |_| log::debug!("set jack")}>
onclick={move |_| set_name("jack")}>
onclick={move |_| name.set("jack")}>
"Jack!"
</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"
onclick={move |_| set_name("jill")}
onclick={move |_| set_name("jill")}>
onclick={move |_| name.set("jill")}
onclick={move |_| name.set("jill")}>
"Jill!"
</button>
</div>
@ -57,9 +48,3 @@ static Example: FC<()> = |ctx, props| {
</div>
})
};
// <div>
// <h1> "Hello, {name}" </h1>
// <button onclick={move |_| set_name("jack .")}> "jack!" </button>
// <button onclick={move |_| set_name("jill .")}> "jill!" </button>
// </div>

View file

@ -0,0 +1,97 @@
use std::collections::HashMap;
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_web::WebsysRenderer;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
wasm_bindgen_futures::spawn_local(async move {
WebsysRenderer::new_with_props(App, ())
.run()
.await
.expect("major crash");
});
}
use lazy_static::lazy_static;
lazy_static! {
static ref DummyData: HashMap<String, String> = {
let vals = vec![
("0 ", "abc123"),
("1 ", "abc124"),
("2 ", "abc125"),
("3 ", "abc126"),
("4 ", "abc127"),
("5 ", "abc128"),
("6 ", "abc129"),
("7 ", "abc1210"),
("8 ", "abc1211"),
("9 ", "abc1212"),
("10 ", "abc1213"),
("11 ", "abc1214"),
("12 ", "abc1215"),
("13 ", "abc1216"),
("14 ", "abc1217"),
("15 ", "abc1218"),
("16 ", "abc1219"),
("17 ", "abc1220"),
("18 ", "abc1221"),
("19 ", "abc1222"),
];
vals.into_iter()
.map(|(a, b)| (a.to_string(), b.to_string()))
.collect()
};
}
static App: FC<()> = |ctx, _| {
let items = use_state(&ctx, || DummyData.clone());
// handle new elements
let add_new = move |_| {
items.modify(|m| {
let k = m.len();
let v = match (k % 3, k % 5) {
(0, 0) => "FizzBuzz".to_string(),
(0, _) => "Fizz".to_string(),
(_, 0) => "Buzz".to_string(),
_ => k.to_string(),
};
m.insert(k.to_string(), v);
})
};
let elements = items.iter().map(|(k, v)| {
rsx! {
li {
span {"{k}: {v}"}
button {
"Remove"
onclick: move |_| {
let key_to_remove = k.clone();
items.modify(move |m| { m.remove(&key_to_remove); } )
}
}
}
}
});
ctx.render(rsx!(
div {
h1 {"Some list"}
// button to add new item
button {
"add new"
onclick: {add_new}
}
// list elements
ul {
{elements}
}
}
))
};

View file

@ -25,7 +25,7 @@ struct ExampleProps {
}
static Example: FC<ExampleProps> = |ctx, props| {
let (name, set_name) = use_state(&ctx, move || props.initial_name.to_string());
let name = use_state(&ctx, move || props.initial_name.to_string());
ctx.render(rsx! {
div {
@ -39,9 +39,9 @@ static Example: FC<ExampleProps> = |ctx, props| {
"Hello, {name}"
}
CustomButton { name: "Jack!", set_name: set_name }
CustomButton { name: "Jill!", set_name: set_name }
CustomButton { name: "Bob!", set_name: set_name }
CustomButton { name: "Jack!", set_name: name.setter() }
CustomButton { name: "Jill!", set_name: name.setter() }
CustomButton { name: "Bob!", set_name: name.setter() }
}
})
};