Feat: more ergonomics, more examples
This commit is contained in:
1 parent
44aad2746c
commit
0bcff1f88e
15 files changed
+488
-469
No files matched your search
@@ -12,13 +12,13 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
static Example: FC<()> = |ctx, props| {
|
static Example: FC<()> = |ctx, props| {
|
||||||
let (selection, set_selection) = use_state(&ctx, || "...?");
|
let selection = use_state(&ctx, || "...?");
|
||||||
|
|
||||||
ctx.render(rsx! {
|
ctx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 { "Hello, {selection}" }
|
h1 { "Hello, {selection}" }
|
||||||
button { "?", onclick: move |_| set_selection("world!")}
|
button { "?", onclick: move |_| selection.set("world!")}
|
||||||
button { "?", onclick: move |_| set_selection("Dioxus 🎉")}
|
button { "?", onclick: move |_| selection.set("Dioxus 🎉")}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|||||||
+45
-230
@@ -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 syn::parse::{discouraged::Speculative, ParseBuffer};
|
||||||
|
|
||||||
use {
|
use {
|
||||||
@@ -72,11 +33,6 @@ impl Parse for RsxRender {
|
|||||||
})
|
})
|
||||||
.ok();
|
.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()?;
|
let el: Element = input.parse()?;
|
||||||
Ok(Self { el, custom_context })
|
Ok(Self { el, custom_context })
|
||||||
}
|
}
|
||||||
@@ -85,7 +41,6 @@ impl Parse for RsxRender {
|
|||||||
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.el).to_token_stream();
|
||||||
// let new_toks = ToToksCtx::new(&self.kind).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 {
|
||||||
@@ -130,42 +85,40 @@ impl ToTokens for &Node {
|
|||||||
|
|
||||||
impl Parse for Node {
|
impl Parse for Node {
|
||||||
fn parse(stream: ParseStream) -> Result<Self> {
|
fn parse(stream: ParseStream) -> Result<Self> {
|
||||||
let fork1 = stream.fork();
|
// Supposedly this approach is discouraged due to inability to return proper errors
|
||||||
let fork2 = stream.fork();
|
// TODO: Rework this to provide more informative errors
|
||||||
let fork3 = stream.fork();
|
|
||||||
|
|
||||||
// todo: map issues onto the second fork if any arise
|
let fork = stream.fork();
|
||||||
// it's unlikely that textnodes or components would fail?
|
if let Ok(text) = fork.parse::<TextNode>() {
|
||||||
|
stream.advance_to(&fork);
|
||||||
let ret = if let Ok(text) = fork1.parse::<TextNode>() {
|
return Ok(Self::Text(text));
|
||||||
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![,]>();
|
|
||||||
}
|
}
|
||||||
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 {
|
struct Component {
|
||||||
name: Ident,
|
name: Ident,
|
||||||
body: Vec<ComponentField>,
|
body: Vec<ComponentField>,
|
||||||
// attrs: Vec<Attr>,
|
|
||||||
children: Vec<Node>,
|
children: Vec<Node>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,38 +138,6 @@ impl ToTokens for &Component {
|
|||||||
.build()
|
.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! {
|
let _toks = tokens.append_all(quote! {
|
||||||
dioxus::builder::virtual_child(ctx, #name, #builder)
|
dioxus::builder::virtual_child(ctx, #name, #builder)
|
||||||
});
|
});
|
||||||
@@ -225,8 +146,6 @@ impl ToTokens for &Component {
|
|||||||
|
|
||||||
impl Parse for Component {
|
impl Parse for Component {
|
||||||
fn parse(s: ParseStream) -> Result<Self> {
|
fn parse(s: ParseStream) -> Result<Self> {
|
||||||
// TODO: reject anything weird/nonstandard
|
|
||||||
// we want names *only*
|
|
||||||
let name = Ident::parse_any(s)?;
|
let name = Ident::parse_any(s)?;
|
||||||
|
|
||||||
if crate::util::is_valid_tag(&name.to_string()) {
|
if crate::util::is_valid_tag(&name.to_string()) {
|
||||||
@@ -252,11 +171,16 @@ impl Parse for Component {
|
|||||||
if let Ok(field) = content.parse::<ComponentField>() {
|
if let Ok(field) = content.parse::<ComponentField>() {
|
||||||
body.push(field);
|
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
|
// todo: add support for children
|
||||||
let children: Vec<Node> = vec![];
|
let children: Vec<Node> = vec![];
|
||||||
// let children = MaybeExpr::Literal(children);
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name,
|
name,
|
||||||
@@ -275,25 +199,16 @@ pub struct ComponentField {
|
|||||||
impl Parse for ComponentField {
|
impl Parse for ComponentField {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let name = Ident::parse_any(input)?;
|
let name = Ident::parse_any(input)?;
|
||||||
let _name_str = name.to_string();
|
|
||||||
input.parse::<Token![:]>()?;
|
input.parse::<Token![:]>()?;
|
||||||
let content = input.parse()?;
|
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 })
|
Ok(Self { name, content })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for &ComponentField {
|
impl ToTokens for &ComponentField {
|
||||||
// impl ToTokens for ToToksCtx<&ComponentField> {
|
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let name = &self.name;
|
let ComponentField { name, content, .. } = self;
|
||||||
let content = &self.content;
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
.#name(#content)
|
.#name(#content)
|
||||||
})
|
})
|
||||||
@@ -307,7 +222,6 @@ struct Element {
|
|||||||
name: Ident,
|
name: Ident,
|
||||||
attrs: Vec<Attr>,
|
attrs: Vec<Attr>,
|
||||||
children: Vec<Node>,
|
children: Vec<Node>,
|
||||||
// children: MaybeExpr<Vec<Node>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for &Element {
|
impl ToTokens for &Element {
|
||||||
@@ -321,46 +235,15 @@ impl ToTokens for &Element {
|
|||||||
for attr in self.attrs.iter() {
|
for attr in self.attrs.iter() {
|
||||||
attr.to_tokens(tokens);
|
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();
|
let mut children = self.children.iter();
|
||||||
while let Some(child) = children.next() {
|
while let Some(child) = children.next() {
|
||||||
// if let Some(child) = children.next() {
|
let inner_toks = child.to_token_stream();
|
||||||
// let mut inner_toks = TokenStream2::new();
|
tokens.append_all(quote! {
|
||||||
// child.to_tokens(&mut inner_toks);
|
.iter_child(#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])
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
// }
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
.finish()
|
.finish()
|
||||||
});
|
});
|
||||||
@@ -383,8 +266,6 @@ impl Parse for Element {
|
|||||||
let mut children: Vec<Node> = vec![];
|
let mut children: Vec<Node> = vec![];
|
||||||
parse_element_content(content, &mut attrs, &mut children)?;
|
parse_element_content(content, &mut attrs, &mut children)?;
|
||||||
|
|
||||||
// let children = MaybeExpr::Literal(children);
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name,
|
name,
|
||||||
attrs,
|
attrs,
|
||||||
@@ -400,12 +281,11 @@ fn parse_element_content(
|
|||||||
children: &mut Vec<Node>,
|
children: &mut Vec<Node>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
'parsing: loop {
|
'parsing: loop {
|
||||||
// todo move this around into a more functional style
|
// consume comma if it exists
|
||||||
// [1] Break if empty
|
// we don't actually care if there *are* commas after elements/text
|
||||||
// [2] Try to consume an attr (with comma)
|
if stream.peek(Token![,]) {
|
||||||
// [3] Try to consume a child node (with comma)
|
let _ = stream.parse::<Token![,]>();
|
||||||
// [4] Try to consume brackets as anything thats Into<Node>
|
}
|
||||||
// [last] Fail if none worked
|
|
||||||
|
|
||||||
// [1] Break if empty
|
// [1] Break if empty
|
||||||
if stream.is_empty() {
|
if stream.is_empty() {
|
||||||
@@ -430,33 +310,6 @@ fn parse_element_content(
|
|||||||
continue 'parsing;
|
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
|
// todo: pass a descriptive error onto the offending tokens
|
||||||
panic!("Entry is not an attr or element\n {}", stream)
|
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
|
/// Parse a VElement's Attributes
|
||||||
/// =======================================
|
/// =======================================
|
||||||
/// - [ ] Allow expressions as attribute
|
|
||||||
///
|
|
||||||
///
|
|
||||||
struct Attr {
|
struct Attr {
|
||||||
name: Ident,
|
name: Ident,
|
||||||
ty: AttrType,
|
ty: AttrType,
|
||||||
@@ -540,7 +390,6 @@ impl Parse for Attr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for &Attr {
|
impl ToTokens for &Attr {
|
||||||
// impl ToTokens for ToToksCtx<&Attr> {
|
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let name = self.name.to_string();
|
let name = self.name.to_string();
|
||||||
let nameident = &self.name;
|
let nameident = &self.name;
|
||||||
@@ -555,13 +404,11 @@ impl ToTokens for &Attr {
|
|||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
.add_listener(dioxus::events::on::#nameident(ctx, #event))
|
.add_listener(dioxus::events::on::#nameident(ctx, #event))
|
||||||
});
|
});
|
||||||
// .on(#name, #event)
|
|
||||||
}
|
}
|
||||||
AttrType::Tok(exp) => {
|
AttrType::Tok(exp) => {
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
.add_listener(dioxus::events::on::#nameident(ctx, #exp))
|
.add_listener(dioxus::events::on::#nameident(ctx, #exp))
|
||||||
});
|
});
|
||||||
// .on(#name, #exp)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -571,17 +418,12 @@ enum AttrType {
|
|||||||
Value(LitStr),
|
Value(LitStr),
|
||||||
Event(ExprClosure),
|
Event(ExprClosure),
|
||||||
Tok(Expr),
|
Tok(Expr),
|
||||||
// todo Bool(MaybeExpr<LitBool>)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
// Parse just plain text
|
// Parse just plain text
|
||||||
// =======================================
|
// =======================================
|
||||||
// - [ ] Perform formatting automatically
|
|
||||||
//
|
|
||||||
//
|
|
||||||
struct TextNode(LitStr);
|
struct TextNode(LitStr);
|
||||||
// struct TextNode(MaybeExpr<LitStr>);
|
|
||||||
|
|
||||||
impl Parse for TextNode {
|
impl Parse for TextNode {
|
||||||
fn parse(s: ParseStream) -> Result<Self> {
|
fn parse(s: ParseStream) -> Result<Self> {
|
||||||
@@ -590,9 +432,8 @@ impl Parse for TextNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens 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) {
|
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();
|
let token_stream = &self.0.to_token_stream();
|
||||||
tokens.append_all(quote! {
|
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),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
|
|
||||||
struct Props {
|
struct Props {
|
||||||
@@ -19,8 +21,8 @@ struct ListItem {
|
|||||||
age: u32,
|
age: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(ctx: Context, props: &Props) -> DomTree {
|
fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
|
||||||
let (_f, setter) = use_state(&ctx, || 0);
|
let 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");
|
||||||
@@ -34,7 +36,7 @@ fn app(ctx: Context, 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(setter)
|
.item_handler(val.setter())
|
||||||
.build(),
|
.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
|
// props should derive a partialeq implementation automatically, but implement ptr compare for & fields
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
struct ChildProps<'a> {
|
struct ChildProps<'a> {
|
||||||
@@ -58,153 +51,26 @@ struct ChildProps<'a> {
|
|||||||
item: &'a ListItem,
|
item: &'a ListItem,
|
||||||
|
|
||||||
// Even pass down handlers!
|
// Even pass down handlers!
|
||||||
item_handler: &'a StateSetter<i32>,
|
item_handler: &'a dyn Fn(i32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for ChildProps<'_> {
|
impl PartialEq for ChildProps<'_> {
|
||||||
fn eq(&self, _other: &Self) -> bool {
|
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
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ChildItem(_ctx: Context, _props: &ChildProps) -> DomTree {
|
fn ChildItem<'a>(ctx: Context<'a>, 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 {
|
|
||||||
ctx.render(rsx! {
|
ctx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
class: "abc123",
|
onclick: move |evt| (props.item_handler)(10)
|
||||||
abc: 123,
|
h1 { "abcd123" }
|
||||||
onclick: props.item_handler,
|
h2 { "abcd123" }
|
||||||
|
|
||||||
h1 { "Hello, {props.username}!" },
|
|
||||||
h2 { "Welcome the RSX syntax" },
|
|
||||||
div {
|
div {
|
||||||
h3 { "This is a subheader" }
|
"abcd123"
|
||||||
button {
|
h2 { }
|
||||||
onclick: props.handler,
|
p { }
|
||||||
"This is a button"
|
}
|
||||||
}
|
|
||||||
"This is child text"
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
@@ -11,13 +11,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Example: FC<()> = |ctx, props| {
|
static Example: FC<()> = |ctx, props| {
|
||||||
let (name, set_name) = use_state(&ctx, || "...?");
|
let name = use_state(&ctx, || "...?");
|
||||||
|
|
||||||
ctx.render(html! {
|
ctx.render(html! {
|
||||||
<div>
|
<div>
|
||||||
<h1> "Hello, {name}" </h1>
|
<h1> "Hello, {name}" </h1>
|
||||||
<button onclick={move |_| set_name("jack")}> "jack!" </button>
|
<button onclick={move |_| name.set("jack")}> "jack!" </button>
|
||||||
<button onclick={move |_| set_name("jill")}> "jill!" </button>
|
<button onclick={move |_| name.set("jill")}> "jill!" </button>
|
||||||
</div>
|
</div>
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -61,9 +61,9 @@ impl<'a> Context<'a> {
|
|||||||
/// Create a suspended component from a future.
|
/// Create a suspended component from a future.
|
||||||
///
|
///
|
||||||
/// When the future completes, the component will be renderered
|
/// 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,
|
&self,
|
||||||
_fut: impl Future<Output = impl FnOnce(&'a NodeCtx<'a>) -> VNode<'a>>,
|
_fut: impl Future<Output = LazyNodes<'a, F>>,
|
||||||
) -> VNode<'a> {
|
) -> VNode<'a> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,33 @@ impl DebugRenderer {
|
|||||||
pub fn step(&mut self, machine: &mut DiffMachine) -> Result<()> {
|
pub fn step(&mut self, machine: &mut DiffMachine) -> Result<()> {
|
||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
|
|||||||
+43
-30
@@ -5,6 +5,7 @@
|
|||||||
//! - [ ] use_reducer
|
//! - [ ] use_reducer
|
||||||
//! - [ ] use_effect
|
//! - [ ] use_effect
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
@@ -12,29 +13,52 @@ 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> {
|
pub struct UseState<T: 'static> {
|
||||||
new_val: Rc<RefCell<Option<T>>>,
|
modifier: Rc<RefCell<Option<Box<dyn FnOnce(&mut T)>>>>,
|
||||||
current_val: T,
|
current_val: T,
|
||||||
caller: Box<dyn Fn(T) + 'static>,
|
update: Box<dyn Fn() + 'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UseStateHandle<'a, T> {
|
// #[derive(Clone, Copy)]
|
||||||
inner: &'a T,
|
// pub struct UseStateHandle<'a, T: 'static> {
|
||||||
}
|
// inner: &'a UseState<T>,
|
||||||
impl<'a, T> UseStateHandle<'a, T> {
|
// // pub setter: &'a dyn Fn(T),
|
||||||
fn set(&self, new_val: T) {}
|
// // pub modifier: &'a dyn Fn(&mut T),
|
||||||
fn modify(&self, f: impl FnOnce(&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;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
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>(
|
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||||
ctx: &'c Context<'a>,
|
ctx: &'c Context<'a>,
|
||||||
initial_state_fn: F,
|
initial_state_fn: F,
|
||||||
) -> (&'a T, &'a impl Fn(T)) {
|
) -> &'a UseState<T> {
|
||||||
ctx.use_hook(
|
ctx.use_hook(
|
||||||
move || UseState {
|
move || UseState {
|
||||||
new_val: Rc::new(RefCell::new(None)),
|
modifier: Rc::new(RefCell::new(None)),
|
||||||
current_val: initial_state_fn(),
|
current_val: initial_state_fn(),
|
||||||
caller: Box::new(|_| println!("setter called!")),
|
update: Box::new(|| {}),
|
||||||
},
|
},
|
||||||
move |hook| {
|
move |hook| {
|
||||||
log::debug!("Use_state set called");
|
|
||||||
let inner = hook.new_val.clone();
|
|
||||||
let scheduled_update = ctx.schedule_update();
|
let scheduled_update = ctx.schedule_update();
|
||||||
|
|
||||||
// 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.new_val.as_ref().borrow_mut().deref_mut().take() {
|
if let Some(new_val) = hook.modifier.as_ref().borrow_mut().deref_mut().take() {
|
||||||
hook.current_val = new_val;
|
(new_val)(&mut hook.current_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: swap out the caller with a subscription call and an internal update
|
hook.update = Box::new(move || scheduled_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
|
&*hook
|
||||||
scheduled_update();
|
|
||||||
});
|
|
||||||
|
|
||||||
// box gets derefed into a ref which is then taken as ref with the hook
|
|
||||||
(&hook.current_val, &hook.caller)
|
|
||||||
},
|
},
|
||||||
|_| {},
|
|_| {},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -496,6 +496,17 @@ pub trait IntoDomTree<'a> {
|
|||||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'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.
|
// Cover the cases where nodes are pre-rendered.
|
||||||
// Likely used by enums.
|
// Likely used by enums.
|
||||||
// ----
|
// ----
|
||||||
@@ -547,7 +558,7 @@ where
|
|||||||
// Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator
|
// Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator
|
||||||
impl<'a, G> IntoIterator for LazyNodes<'a, G>
|
impl<'a, G> IntoIterator for LazyNodes<'a, G>
|
||||||
where
|
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 Item = Self;
|
||||||
type IntoIter = std::iter::Once<Self::Item>;
|
type IntoIter = std::iter::Once<Self::Item>;
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
@@ -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() {
|
fn main() {
|
||||||
TextRenderer::new(App);
|
TextRenderer::new(App);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum Routes {
|
enum Route {
|
||||||
Homepage,
|
Homepage,
|
||||||
ExampleList,
|
Examples,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Props)]
|
#[derive(Debug, PartialEq, Props)]
|
||||||
struct AppProps {
|
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| {
|
static App: FC<AppProps> = |ctx, props| {
|
||||||
//
|
let body = match props.route {
|
||||||
let body = rsx! {
|
Route::Homepage => ctx.render(rsx! {
|
||||||
div {}
|
div {
|
||||||
};
|
"Some Content"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
let top = rsx! {
|
Route::Examples => ctx.render(rsx! {
|
||||||
div {}
|
div {
|
||||||
|
"Other Content"
|
||||||
|
}
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.render(rsx!(
|
ctx.render(rsx!(
|
||||||
div {
|
div {
|
||||||
Header {}
|
Header {}
|
||||||
{body}
|
{body}
|
||||||
{top}
|
ul {
|
||||||
{rsx!{
|
{(0..10).map(|f| rsx!{
|
||||||
div {
|
li {
|
||||||
"you ugl"
|
"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" }
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -10,27 +10,27 @@ 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, set_val) = use_state(&ctx, || "abcdef".to_string());
|
let val = use_state(&ctx, || "abcdef".to_string());
|
||||||
|
|
||||||
ctx.render(rsx! {
|
ctx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
class: "m-8"
|
class: "m-8"
|
||||||
"CustomA {val}"
|
"CustomA {val}"
|
||||||
button {
|
button {
|
||||||
"Upper"
|
"Upper"
|
||||||
onclick: move |_| set_val(val.to_ascii_uppercase())
|
onclick: move |_| val.set(val.to_ascii_uppercase())
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
"Lower"
|
"Lower"
|
||||||
onclick: move |_| set_val(val.to_ascii_lowercase())
|
onclick: move |_| val.set(val.to_ascii_lowercase())
|
||||||
}
|
}
|
||||||
CustomB {
|
CustomB {
|
||||||
val: val
|
val: val.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
mod components {
|
mod components {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ fn main() {
|
|||||||
|
|
||||||
// this is a component
|
// this is a component
|
||||||
static Example: FC<()> = |ctx, _props| {
|
static Example: FC<()> = |ctx, _props| {
|
||||||
let (event, set_event) = use_state(&ctx, || None);
|
let event = use_state(&ctx, || None);
|
||||||
let event = format!("{:#?}", event);
|
|
||||||
|
|
||||||
let handler = move |evt: MouseEvent| {
|
let handler = move |evt: MouseEvent| {
|
||||||
set_event(Some(evt));
|
event.set(Some(evt));
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!("hello world");
|
log::info!("hello world");
|
||||||
@@ -36,9 +35,9 @@ static Example: FC<()> = |ctx, _props| {
|
|||||||
pre {
|
pre {
|
||||||
onmousemove: {handler}
|
onmousemove: {handler}
|
||||||
id: "json"
|
id: "json"
|
||||||
"{event}"
|
"Hello world"
|
||||||
}
|
}
|
||||||
Example2 { name: event }
|
Example2 { name: "Blah".into() }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,19 +10,11 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Example: FC<()> = |ctx, props| {
|
static Example: FC<()> = |ctx, props| {
|
||||||
let (name, set_name) = use_state(&ctx, || "...?");
|
let name = use_state(&ctx, || "...?");
|
||||||
|
|
||||||
log::debug!("Running component....");
|
log::debug!("Running component....");
|
||||||
|
|
||||||
ctx.render(html! {
|
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>
|
<div>
|
||||||
<section class="py-12 px-4 text-center">
|
<section class="py-12 px-4 text-center">
|
||||||
<div class="w-full max-w-2xl mx-auto">
|
<div class="w-full max-w-2xl mx-auto">
|
||||||
@@ -40,15 +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 |_| log::debug!("set jack")}>
|
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 |_| set_name("jill")}
|
onclick={move |_| name.set("jill")}
|
||||||
onclick={move |_| set_name("jill")}>
|
onclick={move |_| name.set("jill")}>
|
||||||
"Jill!"
|
"Jill!"
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,9 +48,3 @@ static Example: FC<()> = |ctx, props| {
|
|||||||
</div>
|
</div>
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// <div>
|
|
||||||
// <h1> "Hello, {name}" </h1>
|
|
||||||
// <button onclick={move |_| set_name("jack .")}> "jack!" </button>
|
|
||||||
// <button onclick={move |_| set_name("jill .")}> "jill!" </button>
|
|
||||||
// </div>
|
|
||||||
@@ -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}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
))
|
||||||
|
};
|
||||||
@@ -25,7 +25,7 @@ struct ExampleProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Example: FC<ExampleProps> = |ctx, props| {
|
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! {
|
ctx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
@@ -39,9 +39,9 @@ static Example: FC<ExampleProps> = |ctx, props| {
|
|||||||
"Hello, {name}"
|
"Hello, {name}"
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomButton { name: "Jack!", set_name: set_name }
|
CustomButton { name: "Jack!", set_name: name.setter() }
|
||||||
CustomButton { name: "Jill!", set_name: set_name }
|
CustomButton { name: "Jill!", set_name: name.setter() }
|
||||||
CustomButton { name: "Bob!", set_name: set_name }
|
CustomButton { name: "Bob!", set_name: name.setter() }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user