Feat: major overhaul to diffing
This commit is contained in:
parent
c809095124
commit
9810feebf5
19
Cargo.toml
19
Cargo.toml
|
@ -1,18 +1,28 @@
|
|||
[workspace]
|
||||
# members = ["packages/core-macro"]
|
||||
members = [
|
||||
"packages/dioxus",
|
||||
"packages/core-macro",
|
||||
"packages/core",
|
||||
"packages/web",
|
||||
"packages/docsite",
|
||||
"packages/ssr",
|
||||
"packages/webview",
|
||||
"packages/dioxus",
|
||||
]
|
||||
|
||||
# "packages/docsite",
|
||||
# "packages/ssr",
|
||||
# "packages/cli",
|
||||
# "packages/webview",
|
||||
# "packages/hooks",
|
||||
# "packages/ios",
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
|
||||
# "packages/liveview",
|
||||
# "packages/3d",
|
||||
|
||||
# Built-in
|
||||
# "packages/webview/client",
|
||||
# "packages/webview",
|
||||
# "packages/router",
|
||||
# "packages/webview",
|
||||
# "packages/livehost",
|
||||
|
@ -22,7 +32,6 @@ members = [
|
|||
# "packages/macro",
|
||||
# TODO @Jon, share the validation code
|
||||
# "packages/web",
|
||||
# "packages/hooks",
|
||||
# "packages/cli",
|
||||
# "examples",
|
||||
# "packages/html-macro",
|
||||
|
|
|
@ -24,7 +24,7 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
|
|||
> 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)
|
||||
- [ ] (Macro) Allow components to specify their props as function args
|
||||
|
||||
## Project: Hooks + Context + Subscriptions (TBD)
|
||||
> Implement the foundations for state management
|
||||
|
@ -80,20 +80,21 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
|
|||
- [x] Allow paths for components
|
||||
- [x] todo mvc
|
||||
- [ ] Make events lazy (use traits + Box<dyn>)
|
||||
- [ ] attrs on elements should implement format_args
|
||||
- [ ] Attributes on elements should implement format_args
|
||||
- [ ] Beef up the dioxus CLI tool
|
||||
- [ ] Tweak macro parsing for better errors
|
||||
- [ ] make ssr follow HTML spec (ish)
|
||||
- [ ] make SSR follow HTML spec
|
||||
- [ ] dirty tagging, compression
|
||||
- [ ] fix keys on elements
|
||||
- [ ] miri tests
|
||||
- [ ] MIRI tests
|
||||
- [ ] code health
|
||||
- [ ] all synthetic events filled out
|
||||
- [ ] doublecheck event targets and stuff
|
||||
- [ ] double check event targets and stuff
|
||||
- [ ] Documentation overhaul
|
||||
- [ ] Website
|
||||
= [ ] controlled components
|
||||
|
||||
lower priorty features
|
||||
lower priority features
|
||||
- [ ] fragments
|
||||
- [ ] node refs (postpone for future release?)
|
||||
- [ ] styling built-in (future release?)
|
||||
|
|
|
@ -12,7 +12,7 @@ description = "CLI tool for developing, testing, and publishing Dioxus apps"
|
|||
thiserror = "1.0.23"
|
||||
log = "0.4.13"
|
||||
fern = { version = "0.6.0", features = ["colored"] }
|
||||
wasm-bindgen-cli-support = "0.2.71"
|
||||
wasm-bindgen-cli-support = "0.2.73"
|
||||
anyhow = "1.0.38"
|
||||
argh = "0.1.4"
|
||||
serde = "1.0.120"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use syn::parse::{discouraged::Speculative, ParseBuffer};
|
||||
|
||||
use crate::util::is_valid_html_tag;
|
||||
|
||||
use {
|
||||
proc_macro::TokenStream,
|
||||
proc_macro2::{Span, TokenStream as TokenStream2},
|
||||
|
@ -15,77 +17,105 @@ use {
|
|||
// Parse any stream coming from the rsx! macro
|
||||
// ==============================================
|
||||
pub struct RsxRender {
|
||||
custom_context: Option<Ident>,
|
||||
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),
|
||||
}
|
||||
}
|
||||
// custom_context: Option<Ident>,
|
||||
root: AmbiguousElement,
|
||||
}
|
||||
|
||||
impl Parse for RsxRender {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let fork = input.fork();
|
||||
let root = { input.parse::<AmbiguousElement>() }?;
|
||||
if !input.is_empty() {
|
||||
return Err(Error::new(
|
||||
input.span(),
|
||||
"Currently only one element is allowed per component",
|
||||
));
|
||||
}
|
||||
|
||||
let custom_context = fork
|
||||
.parse::<Ident>()
|
||||
.and_then(|ident| {
|
||||
fork.parse::<Token![,]>().map(|_| {
|
||||
input.advance_to(&fork);
|
||||
ident
|
||||
})
|
||||
})
|
||||
.ok();
|
||||
|
||||
let forked = input.fork();
|
||||
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,
|
||||
})
|
||||
Ok(Self { root })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for RsxRender {
|
||||
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||
let new_toks = (&self.root).to_token_stream();
|
||||
|
||||
// create a lazy tree that accepts a bump allocator
|
||||
let final_tokens = match &self.custom_context {
|
||||
Some(ident) => quote! {
|
||||
#ident.render(dioxus::prelude::LazyNodes::new(move |ctx|{
|
||||
let bump = ctx.bump;
|
||||
#new_toks
|
||||
}))
|
||||
},
|
||||
None => quote! {
|
||||
// Currently disabled
|
||||
//
|
||||
// let final_tokens = match &self.custom_context {
|
||||
// Some(ident) => quote! {
|
||||
// #ident.render(dioxus::prelude::LazyNodes::new(move |ctx|{
|
||||
// let bump = ctx.bump;
|
||||
// #new_toks
|
||||
// }))
|
||||
// },
|
||||
|
||||
let inner = &self.root;
|
||||
let output = quote! {
|
||||
dioxus::prelude::LazyNodes::new(move |ctx|{
|
||||
let bump = ctx.bump;
|
||||
#new_toks
|
||||
#inner
|
||||
})
|
||||
},
|
||||
};
|
||||
output.to_tokens(out_tokens)
|
||||
}
|
||||
}
|
||||
|
||||
final_tokens.to_tokens(out_tokens);
|
||||
enum AmbiguousElement {
|
||||
Element(Element),
|
||||
Component(Component),
|
||||
}
|
||||
|
||||
impl Parse for AmbiguousElement {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
// Try to parse as an absolute path and immediately defer to the componetn
|
||||
if input.peek(Token![::]) {
|
||||
return input
|
||||
.parse::<Component>()
|
||||
.map(|c| AmbiguousElement::Component(c));
|
||||
}
|
||||
|
||||
// If not an absolute path, then parse the ident and check if it's a valid tag
|
||||
|
||||
if let Ok(pat) = input.fork().parse::<syn::Path>() {
|
||||
if pat.segments.len() > 1 {
|
||||
return input
|
||||
.parse::<Component>()
|
||||
.map(|c| AmbiguousElement::Component(c));
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(name) = input.fork().parse::<Ident>() {
|
||||
let name_str = name.to_string();
|
||||
|
||||
match is_valid_html_tag(&name_str) {
|
||||
true => input
|
||||
.parse::<Element>()
|
||||
.map(|c| AmbiguousElement::Element(c)),
|
||||
false => {
|
||||
let first_char = name_str.chars().next().unwrap();
|
||||
if first_char.is_ascii_uppercase() {
|
||||
input
|
||||
.parse::<Component>()
|
||||
.map(|c| AmbiguousElement::Component(c))
|
||||
} else {
|
||||
Err(Error::new(
|
||||
name.span(),
|
||||
"Components must be uppercased, perhaps you mispelled a html tag",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::new(input.span(), "Not a valid Html tag"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for AmbiguousElement {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match self {
|
||||
AmbiguousElement::Element(el) => el.to_tokens(tokens),
|
||||
AmbiguousElement::Component(comp) => comp.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,9 +123,8 @@ impl ToTokens for RsxRender {
|
|||
// Parse any div {} as a VElement
|
||||
// ==============================================
|
||||
enum Node {
|
||||
Element(Element),
|
||||
Element(AmbiguousElement),
|
||||
Text(TextNode),
|
||||
Component(Component),
|
||||
RawExpr(Expr),
|
||||
}
|
||||
|
||||
|
@ -104,7 +133,6 @@ impl ToTokens for &Node {
|
|||
match &self {
|
||||
Node::Element(el) => el.to_tokens(tokens),
|
||||
Node::Text(txt) => txt.to_tokens(tokens),
|
||||
Node::Component(c) => c.to_tokens(tokens),
|
||||
Node::RawExpr(exp) => exp.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
|
@ -115,31 +143,17 @@ impl Parse for Node {
|
|||
// Supposedly this approach is discouraged due to inability to return proper errors
|
||||
// TODO: Rework this to provide more informative errors
|
||||
|
||||
let fork = stream.fork();
|
||||
if let Ok(text) = fork.parse::<TextNode>() {
|
||||
stream.advance_to(&fork);
|
||||
return Ok(Self::Text(text));
|
||||
if stream.peek(token::Brace) {
|
||||
let content;
|
||||
syn::braced!(content in stream);
|
||||
return Ok(Node::RawExpr(content.parse::<Expr>()?));
|
||||
}
|
||||
|
||||
let fork = stream.fork();
|
||||
if let Ok(element) = fork.parse::<Element>() {
|
||||
stream.advance_to(&fork);
|
||||
return Ok(Self::Element(element));
|
||||
if stream.peek(LitStr) {
|
||||
return Ok(Node::Text(stream.parse::<TextNode>()?));
|
||||
}
|
||||
|
||||
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"));
|
||||
Ok(Node::Element(stream.parse::<AmbiguousElement>()?))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,6 +170,7 @@ impl Parse for Component {
|
|||
// todo: look into somehow getting the crate/super/etc
|
||||
|
||||
let name = syn::Path::parse_mod_style(s)?;
|
||||
|
||||
// parse the guts
|
||||
let content: ParseBuffer;
|
||||
syn::braced!(content in s);
|
||||
|
@ -169,9 +184,7 @@ impl Parse for Component {
|
|||
break 'parsing;
|
||||
}
|
||||
|
||||
if let Ok(field) = content.parse::<ComponentField>() {
|
||||
body.push(field);
|
||||
}
|
||||
body.push(content.parse::<ComponentField>()?);
|
||||
|
||||
// consume comma if it exists
|
||||
// we don't actually care if there *are* commas between attrs
|
||||
|
@ -228,6 +241,7 @@ impl ToTokens for &Component {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
// the struct's fields info
|
||||
pub struct ComponentField {
|
||||
name: Ident,
|
||||
|
@ -258,10 +272,56 @@ impl ToTokens for &ComponentField {
|
|||
// =======================================
|
||||
struct Element {
|
||||
name: Ident,
|
||||
attrs: Vec<Attr>,
|
||||
attrs: Vec<ElementAttr>,
|
||||
children: Vec<Node>,
|
||||
}
|
||||
|
||||
impl Parse for Element {
|
||||
fn parse(stream: ParseStream) -> Result<Self> {
|
||||
//
|
||||
let name = Ident::parse(stream)?;
|
||||
|
||||
if !crate::util::is_valid_html_tag(&name.to_string()) {
|
||||
return Err(Error::new(name.span(), "Not a valid Html tag"));
|
||||
}
|
||||
|
||||
// parse the guts
|
||||
let content: ParseBuffer;
|
||||
syn::braced!(content in stream);
|
||||
|
||||
let mut attrs: Vec<ElementAttr> = vec![];
|
||||
let mut children: Vec<Node> = vec![];
|
||||
'parsing: loop {
|
||||
// [1] Break if empty
|
||||
if content.is_empty() {
|
||||
break 'parsing;
|
||||
}
|
||||
|
||||
let forked = content.fork();
|
||||
if forked.call(Ident::parse_any).is_ok()
|
||||
&& forked.parse::<Token![:]>().is_ok()
|
||||
&& forked.parse::<Expr>().is_ok()
|
||||
{
|
||||
attrs.push(content.parse::<ElementAttr>()?);
|
||||
} else {
|
||||
children.push(content.parse::<Node>()?);
|
||||
}
|
||||
|
||||
// consume comma if it exists
|
||||
// we don't actually care if there *are* commas after elements/text
|
||||
if content.peek(Token![,]) {
|
||||
let _ = content.parse::<Token![,]>();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
attrs,
|
||||
children,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for &Element {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let name = &self.name.to_string();
|
||||
|
@ -288,86 +348,22 @@ impl ToTokens for &Element {
|
|||
}
|
||||
}
|
||||
|
||||
impl Parse for Element {
|
||||
fn parse(s: ParseStream) -> Result<Self> {
|
||||
let name = Ident::parse_any(s)?;
|
||||
|
||||
if !crate::util::is_valid_tag(&name.to_string()) {
|
||||
return Err(Error::new(name.span(), "Not a valid Html tag"));
|
||||
}
|
||||
|
||||
// parse the guts
|
||||
let content: ParseBuffer;
|
||||
syn::braced!(content in s);
|
||||
|
||||
let mut attrs: Vec<Attr> = vec![];
|
||||
let mut children: Vec<Node> = vec![];
|
||||
parse_element_content(content, &mut attrs, &mut children)?;
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
attrs,
|
||||
children,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// used by both vcomponet and velement to parse the contents of the elements into attras and children
|
||||
fn parse_element_content(
|
||||
stream: ParseBuffer,
|
||||
attrs: &mut Vec<Attr>,
|
||||
children: &mut Vec<Node>,
|
||||
) -> Result<()> {
|
||||
'parsing: loop {
|
||||
// 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() {
|
||||
break 'parsing;
|
||||
}
|
||||
|
||||
// [2] Try to consume an attr
|
||||
let fork = stream.fork();
|
||||
if let Ok(attr) = fork.parse::<Attr>() {
|
||||
// make sure to advance or your computer will become a space heater :)
|
||||
stream.advance_to(&fork);
|
||||
attrs.push(attr);
|
||||
continue 'parsing;
|
||||
}
|
||||
|
||||
// [3] Try to consume a child node
|
||||
let fork = stream.fork();
|
||||
if let Ok(node) = fork.parse::<Node>() {
|
||||
// make sure to advance or your computer will become a space heater :)
|
||||
stream.advance_to(&fork);
|
||||
children.push(node);
|
||||
continue 'parsing;
|
||||
}
|
||||
|
||||
// todo: pass a descriptive error onto the offending tokens
|
||||
panic!("Entry is not an attr or element\n {}", stream)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn try_parse_bracketed(stream: &ParseBuffer) -> Result<Expr> {
|
||||
let content;
|
||||
syn::braced!(content in stream);
|
||||
content.parse()
|
||||
}
|
||||
|
||||
/// =======================================
|
||||
/// Parse a VElement's Attributes
|
||||
/// =======================================
|
||||
struct Attr {
|
||||
struct ElementAttr {
|
||||
name: Ident,
|
||||
ty: AttrType,
|
||||
}
|
||||
|
||||
impl Parse for Attr {
|
||||
enum AttrType {
|
||||
Value(LitStr),
|
||||
FieldTokens(Expr),
|
||||
EventTokens(Expr),
|
||||
Event(ExprClosure),
|
||||
}
|
||||
|
||||
impl Parse for ElementAttr {
|
||||
fn parse(s: ParseStream) -> Result<Self> {
|
||||
let mut name = Ident::parse_any(s)?;
|
||||
let name_str = name.to_string();
|
||||
|
@ -389,32 +385,39 @@ impl Parse for Attr {
|
|||
content.advance_to(&fork);
|
||||
AttrType::Event(event)
|
||||
} else {
|
||||
AttrType::Tok(content.parse()?)
|
||||
AttrType::EventTokens(content.parse()?)
|
||||
}
|
||||
} else {
|
||||
AttrType::Event(s.parse()?)
|
||||
}
|
||||
} else {
|
||||
let lit_str = if name_str == "style" && s.peek(token::Brace) {
|
||||
// special-case to deal with literal styles.
|
||||
let outer;
|
||||
syn::braced!(outer in s);
|
||||
// double brace for inline style.
|
||||
// todo!("Style support not ready yet");
|
||||
|
||||
// if outer.peek(token::Brace) {
|
||||
// let inner;
|
||||
// syn::braced!(inner in outer);
|
||||
// let styles: Styles = inner.parse()?;
|
||||
// MaybeExpr::Literal(LitStr::new(&styles.to_string(), Span::call_site()))
|
||||
// } else {
|
||||
// just parse as an expression
|
||||
outer.parse()?
|
||||
// }
|
||||
let fork = s.fork();
|
||||
if let Ok(rawtext) = fork.parse::<LitStr>() {
|
||||
s.advance_to(&fork);
|
||||
AttrType::Value(rawtext)
|
||||
} else {
|
||||
s.parse()?
|
||||
};
|
||||
AttrType::Value(lit_str)
|
||||
let toks = s.parse::<Expr>()?;
|
||||
AttrType::FieldTokens(toks)
|
||||
}
|
||||
// let lit_str = if name_str == "style" && s.peek(token::Brace) {
|
||||
// // special-case to deal with literal styles.
|
||||
// let outer;
|
||||
// syn::braced!(outer in s);
|
||||
// // double brace for inline style.
|
||||
// // todo!("Style support not ready yet");
|
||||
|
||||
// // if outer.peek(token::Brace) {
|
||||
// // let inner;
|
||||
// // syn::braced!(inner in outer);
|
||||
// // let styles: Styles = inner.parse()?;
|
||||
// // MaybeExpr::Literal(LitStr::new(&styles.to_string(), Span::call_site()))
|
||||
// // } else {
|
||||
// // just parse as an expression
|
||||
// outer.parse()?
|
||||
// // }
|
||||
// } else {
|
||||
// s.parse()?
|
||||
// };
|
||||
};
|
||||
|
||||
// consume comma if it exists
|
||||
|
@ -423,11 +426,11 @@ impl Parse for Attr {
|
|||
let _ = s.parse::<Token![,]>();
|
||||
}
|
||||
|
||||
Ok(Attr { name, ty })
|
||||
Ok(ElementAttr { name, ty })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for &Attr {
|
||||
impl ToTokens for &ElementAttr {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let name = self.name.to_string();
|
||||
let nameident = &self.name;
|
||||
|
@ -435,7 +438,12 @@ impl ToTokens for &Attr {
|
|||
match &self.ty {
|
||||
AttrType::Value(value) => {
|
||||
tokens.append_all(quote! {
|
||||
.attr(#name, #value)
|
||||
.attr(#name, {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut s = bumpalo::collections::String::new_in(bump);
|
||||
s.write_fmt(format_args_f!(#value)).unwrap();
|
||||
s.into_bump_str()
|
||||
})
|
||||
});
|
||||
}
|
||||
AttrType::Event(event) => {
|
||||
|
@ -443,19 +451,19 @@ impl ToTokens for &Attr {
|
|||
.add_listener(dioxus::events::on::#nameident(ctx, #event))
|
||||
});
|
||||
}
|
||||
AttrType::Tok(exp) => {
|
||||
AttrType::FieldTokens(exp) => {
|
||||
tokens.append_all(quote! {
|
||||
.add_listener(dioxus::events::on::#nameident(ctx, #exp))
|
||||
.attr(#name, #exp)
|
||||
});
|
||||
}
|
||||
AttrType::EventTokens(event) => {
|
||||
//
|
||||
tokens.append_all(quote! {
|
||||
.add_listener(dioxus::events::on::#nameident(ctx, #event))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AttrType {
|
||||
Value(LitStr),
|
||||
Event(ExprClosure),
|
||||
Tok(Expr),
|
||||
}
|
||||
|
||||
// =======================================
|
||||
|
@ -483,3 +491,9 @@ impl ToTokens for TextNode {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn try_parse_bracketed(stream: &ParseBuffer) -> Result<Expr> {
|
||||
let content;
|
||||
syn::braced!(content in stream);
|
||||
content.parse()
|
||||
}
|
||||
|
|
|
@ -117,6 +117,9 @@ static VALID_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
|||
"var",
|
||||
"video",
|
||||
"wbr",
|
||||
// SVTG
|
||||
"svg",
|
||||
"path",
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
|
@ -132,6 +135,6 @@ static VALID_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
|||
///
|
||||
/// assert_eq!(is_valid_tag("random"), false);
|
||||
/// ```
|
||||
pub fn is_valid_tag(tag: &str) -> bool {
|
||||
pub fn is_valid_html_tag(tag: &str) -> bool {
|
||||
VALID_TAGS.contains(tag)
|
||||
}
|
||||
|
|
|
@ -34,4 +34,4 @@ log = "0.4.14"
|
|||
serde = { version = "1.0.123", features = ["derive"], optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["serde"]
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
fn main() {}
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::{borrow::Borrow, rc::Rc};
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
|
@ -36,7 +36,7 @@ fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
|
|||
// create the props with nothing but the fc<T>
|
||||
fc_to_builder(ChildItem)
|
||||
.item(child)
|
||||
.item_handler(set_val)
|
||||
.item_handler(set_val.clone())
|
||||
.build(),
|
||||
None,
|
||||
));
|
||||
|
@ -52,7 +52,7 @@ struct ChildProps<'a> {
|
|||
item: &'a ListItem,
|
||||
|
||||
// Even pass down handlers!
|
||||
item_handler: &'a dyn Fn(i32),
|
||||
item_handler: Rc<dyn Fn(i32)>,
|
||||
}
|
||||
|
||||
impl PartialEq for ChildProps<'_> {
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
// #![allow(unused, non_upper_case_globals)]
|
||||
// use bumpalo::Bump;
|
||||
// use dioxus_core::nodebuilder::*;
|
||||
// use dioxus_core::prelude::VNode;
|
||||
// use dioxus_core::prelude::*;
|
||||
// use once_cell::sync::{Lazy, OnceCell};
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
/*
|
||||
A guard over underlying T that provides access in callbacks via "Copy"
|
||||
*/
|
||||
|
||||
// #[derive(Clone)]
|
||||
struct ContextGuard2<T> {
|
||||
_val: std::marker::PhantomData<T>,
|
||||
}
|
||||
impl<T> Clone for ContextGuard2<T> {
|
||||
// we aren't cloning the underlying data so clone isn't necessary
|
||||
fn clone(&self) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
impl<T> Copy for ContextGuard2<T> {}
|
||||
|
||||
impl<T> ContextGuard2<T> {
|
||||
fn get<'a>(&'a self) -> ContextLock<'a, T> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
struct ContextLock<'a, T> {
|
||||
_val: std::marker::PhantomData<&'a T>,
|
||||
}
|
||||
impl<'a, T: 'a + 'static> Deref for ContextLock<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref<'b>(&'b self) -> &'b T {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The source of the data that gives out context guards
|
||||
*/
|
||||
struct Context<'a> {
|
||||
_p: std::marker::PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
fn use_context<'b, I, O: 'b>(&self, _f: fn(&'b I) -> O) -> ContextGuard2<O> {
|
||||
todo!()
|
||||
}
|
||||
fn add_listener(&self, _f: impl Fn(()) + 'a) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn render(self, _f: impl FnOnce(&'a String) + 'a) {}
|
||||
// fn view(self, f: impl for<'b> FnOnce(&'a String) + 'a) {}
|
||||
// fn view(self, f: impl for<'b> FnOnce(&'b String) + 'a) {}
|
||||
}
|
||||
|
||||
struct Example {
|
||||
value: String,
|
||||
}
|
||||
/*
|
||||
Example compiling
|
||||
*/
|
||||
fn t<'a>(ctx: Context<'a>) {
|
||||
let value = ctx.use_context(|b: &Example| &b.value);
|
||||
|
||||
// Works properly, value is moved by copy into the closure
|
||||
let refed = value.get();
|
||||
println!("Value is {}", refed.as_str());
|
||||
let r2 = refed.as_str();
|
||||
|
||||
ctx.add_listener(move |_| {
|
||||
// let val = value.get().as_str();
|
||||
let _val2 = r2.as_bytes();
|
||||
println!("v2 is {}", r2);
|
||||
// println!("refed is {}", refed);
|
||||
});
|
||||
|
||||
// let refed = value.deref();
|
||||
// returns &String
|
||||
|
||||
// returns &String
|
||||
// let refed = value.deref(); // returns &String
|
||||
// let refed = value.deref(); // returns &String
|
||||
|
||||
// Why does this work? This closure should be static but is holding a reference to refed
|
||||
// The context guard is meant to prevent any references moving into the closure
|
||||
// if the references move they might become invalid due to mutlithreading issues
|
||||
ctx.add_listener(move |_| {
|
||||
// let val = value.as_str();
|
||||
// let val2 = refed.as_bytes();
|
||||
});
|
||||
|
||||
ctx.render(move |_b| {});
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -1,63 +0,0 @@
|
|||
// #[macro_use]
|
||||
|
||||
// use dioxus_core::ifmt;
|
||||
// use fstrings::format_args_f;
|
||||
|
||||
fn main() {
|
||||
let bump = bumpalo::Bump::new();
|
||||
let _b = ≎
|
||||
let _world = "123";
|
||||
// dioxus_core::ifmt!(in b; "Hello {world}";);
|
||||
}
|
||||
|
||||
// let mut s = bumpalo::collections::String::new_in(b);
|
||||
// fstrings::write_f!(s, "Hello {world}");
|
||||
// dbg!(s);
|
||||
// let p = {
|
||||
// println!("hello, {}", &world);
|
||||
// ()
|
||||
// };
|
||||
// let g = format_args!("hello {world}", world = world);
|
||||
// let g = dioxus_core::ifmt!(in b, "Hello {world}");
|
||||
// let g = ifmt!(in b, "hhello {world}");
|
||||
// let g = ::core::fmt::Arguments::new_v1(
|
||||
// &["hello "],
|
||||
// &match (&world,) {
|
||||
// (arg0,) => [::core::fmt::ArgumentV1::new(
|
||||
// arg0,
|
||||
// ::core::fmt::Display::fmt,
|
||||
// )],
|
||||
// },
|
||||
// );
|
||||
// fn main() {
|
||||
// let bump = bumpalo::Bump::new();
|
||||
// let b = ≎
|
||||
// let world = "123";
|
||||
// let world = 123;
|
||||
// let g = {
|
||||
// use bumpalo::core_alloc::fmt::Write;
|
||||
// use ::dioxus_core::prelude::bumpalo;
|
||||
// use ::dioxus_core::prelude::format_args_f;
|
||||
// let bump = b;
|
||||
// let mut s = bumpalo::collections::String::new_in(bump);
|
||||
// let _ = (&mut s).write_fmt(::core::fmt::Arguments::new_v1(
|
||||
// &[""],
|
||||
// &match (&::core::fmt::Arguments::new_v1(
|
||||
// &["hhello "],
|
||||
// &match (&world,) {
|
||||
// (arg0,) => [::core::fmt::ArgumentV1::new(
|
||||
// arg0,
|
||||
// ::core::fmt::Display::fmt,
|
||||
// )],
|
||||
// },
|
||||
// ),)
|
||||
// {
|
||||
// (arg0,) => [::core::fmt::ArgumentV1::new(
|
||||
// arg0,
|
||||
// ::core::fmt::Display::fmt,
|
||||
// )],
|
||||
// },
|
||||
// ));
|
||||
// s
|
||||
// };
|
||||
// }
|
|
@ -0,0 +1,54 @@
|
|||
use baller::Baller;
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let g = rsx! {
|
||||
div {
|
||||
crate::baller::Baller {}
|
||||
baller::Baller {
|
||||
}
|
||||
Taller {
|
||||
a: "asd"
|
||||
}
|
||||
baller::Baller {}
|
||||
baller::Baller {}
|
||||
Baller {}
|
||||
div {
|
||||
a: "asd",
|
||||
a: "asd",
|
||||
a: "asd",
|
||||
a: "asd",
|
||||
div {
|
||||
"asdas",
|
||||
"asdas",
|
||||
"asdas",
|
||||
"asdas",
|
||||
div {
|
||||
|
||||
},
|
||||
div {
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod baller {
|
||||
use super::*;
|
||||
pub struct BallerProps {}
|
||||
|
||||
pub fn Baller(ctx: Context, props: &()) -> DomTree {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Props)]
|
||||
pub struct TallerProps {
|
||||
a: &'static str,
|
||||
}
|
||||
|
||||
pub fn Taller(ctx: Context, props: &TallerProps) -> DomTree {
|
||||
todo!()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{nodebuilder::IntoDomTree, prelude::*, scope::Scope};
|
||||
use crate::{innerlude::*, nodebuilder::IntoDomTree};
|
||||
use crate::{nodebuilder::LazyNodes, nodes::VNode};
|
||||
use bumpalo::Bump;
|
||||
use hooks::Hook;
|
||||
|
@ -97,9 +97,12 @@ impl<'a> Context<'a> {
|
|||
&self,
|
||||
lazy_nodes: LazyNodes<'a, F>,
|
||||
) -> DomTree {
|
||||
// let idx = self.idx.borrow();
|
||||
let ctx = NodeCtx {
|
||||
bump: &self.scope.cur_frame().bump,
|
||||
scope: self.scope.myidx,
|
||||
|
||||
// hmmmmmmmm not sure if this is right
|
||||
idx: 0.into(),
|
||||
listeners: &self.scope.listeners,
|
||||
};
|
||||
|
@ -151,11 +154,14 @@ pub mod hooks {
|
|||
let new_state = initializer();
|
||||
hooks.push(Hook::new(new_state));
|
||||
}
|
||||
*self.idx.borrow_mut() = 1;
|
||||
|
||||
*self.idx.borrow_mut() += 1;
|
||||
|
||||
let stable_ref = hooks.get_mut(idx).unwrap().0.as_mut();
|
||||
let v = unsafe { Pin::get_unchecked_mut(stable_ref) };
|
||||
let internal_state = v.downcast_mut::<InternalHookState>().unwrap();
|
||||
let internal_state = v
|
||||
.downcast_mut::<InternalHookState>()
|
||||
.expect("couldn't find the hook state");
|
||||
|
||||
// we extend the lifetime from borrowed in this scope to borrowed from self.
|
||||
// This is okay because the hook is pinned
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
//!
|
||||
//! More info on how to improve this diffing algorithm:
|
||||
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
||||
use crate::{innerlude::*, scope::Scope};
|
||||
use crate::innerlude::*;
|
||||
use bumpalo::Bump;
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use generational_arena::Arena;
|
||||
|
@ -66,26 +66,29 @@ pub struct DiffMachine<'a> {
|
|||
pub enum LifeCycleEvent<'a> {
|
||||
Mount {
|
||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||
scope: Weak<VCompAssociatedScope>,
|
||||
id: u32,
|
||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||
root_id: u32,
|
||||
},
|
||||
PropsChanged {
|
||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||
scope: Weak<VCompAssociatedScope>,
|
||||
id: u32,
|
||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||
root_id: u32,
|
||||
},
|
||||
SameProps {
|
||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||
scope: Weak<VCompAssociatedScope>,
|
||||
id: u32,
|
||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||
root_id: u32,
|
||||
},
|
||||
Replace {
|
||||
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
|
||||
old_scope: Weak<VCompAssociatedScope>,
|
||||
new_scope: Weak<VCompAssociatedScope>,
|
||||
id: u32,
|
||||
root_id: u32,
|
||||
},
|
||||
Remove {
|
||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
||||
root_id: u32,
|
||||
},
|
||||
Remove,
|
||||
}
|
||||
|
||||
static COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||
|
@ -159,7 +162,11 @@ impl<'a> DiffMachine<'a> {
|
|||
|
||||
let scope = Rc::downgrade(&cold.ass_scope);
|
||||
self.lifecycle_events
|
||||
.push_back(LifeCycleEvent::PropsChanged { caller, id, scope });
|
||||
.push_back(LifeCycleEvent::PropsChanged {
|
||||
caller,
|
||||
root_id: id,
|
||||
stable_scope_addr: scope,
|
||||
});
|
||||
} else {
|
||||
let caller = Rc::downgrade(&cnew.caller);
|
||||
let id = cold.stable_addr.borrow().unwrap();
|
||||
|
@ -168,7 +175,7 @@ impl<'a> DiffMachine<'a> {
|
|||
|
||||
self.lifecycle_events.push_back(LifeCycleEvent::Replace {
|
||||
caller,
|
||||
id,
|
||||
root_id: id,
|
||||
old_scope,
|
||||
new_scope,
|
||||
});
|
||||
|
@ -262,8 +269,8 @@ impl<'a> DiffMachine<'a> {
|
|||
let scope = Rc::downgrade(&component.ass_scope);
|
||||
self.lifecycle_events.push_back(LifeCycleEvent::Mount {
|
||||
caller: Rc::downgrade(&component.caller),
|
||||
id,
|
||||
scope,
|
||||
root_id: id,
|
||||
stable_scope_addr: scope,
|
||||
});
|
||||
}
|
||||
VNode::Suspended => {
|
||||
|
|
|
@ -21,7 +21,7 @@ mod use_state_def {
|
|||
struct UseState<T: 'static> {
|
||||
new_val: Rc<RefCell<Option<T>>>,
|
||||
current_val: T,
|
||||
caller: Box<dyn Fn(T) + 'static>,
|
||||
caller: Rc<dyn Fn(T) + 'static>,
|
||||
}
|
||||
|
||||
/// Store state between component renders!
|
||||
|
@ -49,12 +49,12 @@ 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 T, &'a Rc<dyn 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!")),
|
||||
caller: Rc::new(|_| println!("setter called!")),
|
||||
},
|
||||
move |hook| {
|
||||
log::debug!("Use_state set called");
|
||||
|
@ -69,7 +69,7 @@ mod use_state_def {
|
|||
}
|
||||
|
||||
// todo: swap out the caller with a subscription call and an internal update
|
||||
hook.caller = Box::new(move |new_val| {
|
||||
hook.caller = Rc::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);
|
||||
|
|
|
@ -68,18 +68,14 @@
|
|||
pub mod component; // Logic for extending FC
|
||||
pub mod context; // Logic for providing hook + context functionality to user components
|
||||
pub mod debug_renderer;
|
||||
pub mod patch; // An "edit phase" described by transitions and edit operations // Test harness for validating that lifecycles and diffs work appropriately
|
||||
// pub mod diff;
|
||||
// pub mod patch; // The diffing algorithm that builds the ChangeList
|
||||
pub mod diff;
|
||||
// the diffing algorithm that builds the ChangeList
|
||||
pub mod patch; // An "edit phase" described by transitions and edit operations // Test harness for validating that lifecycles and diffs work appropriately
|
||||
// the diffing algorithm that builds the ChangeList
|
||||
pub mod error; // Error type we expose to the renderers
|
||||
pub mod events; // Manages the synthetic event API
|
||||
pub mod hooks; // Built-in hooks
|
||||
pub mod nodebuilder; // Logic for building VNodes with a direct syntax
|
||||
pub mod nodes; // Logic for the VNodes
|
||||
pub mod scope; // Logic for single components
|
||||
// pub mod validation; // Logic for validating trees
|
||||
pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and suspense
|
||||
|
||||
pub mod builder {
|
||||
|
@ -88,37 +84,20 @@ pub mod builder {
|
|||
|
||||
// types used internally that are important
|
||||
pub(crate) mod innerlude {
|
||||
// pub(crate) use crate::component::Properties;
|
||||
pub(crate) use crate::component::Properties;
|
||||
pub(crate) use crate::context::Context;
|
||||
pub use crate::diff::LifeCycleEvent;
|
||||
pub(crate) use crate::error::Result;
|
||||
pub use crate::events::{EventTrigger, VirtualEvent};
|
||||
use crate::nodes;
|
||||
|
||||
pub use crate::component::ScopeIdx;
|
||||
pub use crate::context::hooks::Hook;
|
||||
pub use crate::nodes::VNode;
|
||||
|
||||
pub(crate) use nodes::*;
|
||||
|
||||
pub use crate::context::NodeCtx;
|
||||
pub use crate::diff::DiffMachine;
|
||||
pub use crate::patch::{EditList, EditMachine};
|
||||
// pub use crate::patchdx;
|
||||
// pub use crate::patchtList;
|
||||
// pub use nodes::iterables::IterableNodes;
|
||||
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
||||
pub use crate::component::*;
|
||||
pub use crate::context::*;
|
||||
pub use crate::debug_renderer::*;
|
||||
pub use crate::diff::*;
|
||||
pub use crate::error::*;
|
||||
pub use crate::events::*;
|
||||
pub use crate::hooks::*;
|
||||
pub use crate::nodebuilder::*;
|
||||
pub use crate::nodes::*;
|
||||
pub use crate::patch::*;
|
||||
pub use crate::virtual_dom::*;
|
||||
|
||||
pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
|
||||
|
||||
mod fc2 {}
|
||||
// pub type FC<'a, P: 'a> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
|
||||
// pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'scope P) -> DomTree;
|
||||
// pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'r P) -> VNode<'scope>;
|
||||
// pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'r P) -> VNode<'scope>;
|
||||
// pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
|
||||
|
||||
// TODO @Jon, fix this
|
||||
// hack the VNode type until VirtualNode is fixed in the macro crate
|
||||
pub type VirtualNode<'a> = VNode<'a>;
|
||||
|
@ -127,7 +106,6 @@ pub(crate) mod innerlude {
|
|||
pub use crate as dioxus;
|
||||
pub use crate::nodebuilder as builder;
|
||||
pub use dioxus_core_macro::{html, rsx};
|
||||
// pub use dioxus_core_macro::{fc, html, rsx};
|
||||
}
|
||||
|
||||
/// Re-export common types for ease of development use.
|
||||
|
|
|
@ -21,9 +21,14 @@
|
|||
//! ----
|
||||
//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom)
|
||||
|
||||
use std::cell::Ref;
|
||||
|
||||
use crate::innerlude::ScopeIdx;
|
||||
|
||||
pub type EditList<'src> = Vec<Edit<'src>>;
|
||||
// pub struct EditList<'src> {
|
||||
// edits: Vec<Edit<'src>>,
|
||||
// }
|
||||
|
||||
/// The `Edit` represents a single modifcation of the renderer tree.
|
||||
/// todo @jon, go through and make certain fields static. tag names should be known at compile time
|
||||
|
@ -338,7 +343,7 @@ impl<'a> EditMachine<'a> {
|
|||
}
|
||||
|
||||
pub fn create_element(&mut self, tag_name: &'a str) {
|
||||
debug_assert!(self.traversal_is_committed());;
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
self.emitter.push(Edit::CreateElement { tag_name });
|
||||
}
|
||||
|
||||
|
@ -389,7 +394,6 @@ impl<'a> EditMachine<'a> {
|
|||
// self.current_known = Some(id);
|
||||
self.emitter.push(Edit::TraverseToKnown { node: id })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Keeps track of where we are moving in a DOM tree, and shortens traversal
|
||||
|
|
|
@ -1,382 +0,0 @@
|
|||
use crate::{innerlude::*, virtual_dom::OpaqueComponent};
|
||||
use bumpalo::Bump;
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashSet,
|
||||
marker::PhantomData,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
/// Every component in Dioxus is represented by a `Scope`.
|
||||
///
|
||||
/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
|
||||
///
|
||||
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
|
||||
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
|
||||
pub struct Scope {
|
||||
// Map to the parent
|
||||
pub parent: Option<ScopeIdx>,
|
||||
|
||||
// our own index
|
||||
pub myidx: ScopeIdx,
|
||||
|
||||
//
|
||||
pub children: HashSet<ScopeIdx>,
|
||||
|
||||
pub caller: Weak<OpaqueComponent<'static>>,
|
||||
|
||||
// ==========================
|
||||
// slightly unsafe stuff
|
||||
// ==========================
|
||||
// an internal, highly efficient storage of vnodes
|
||||
pub frames: ActiveFrame,
|
||||
|
||||
// These hooks are actually references into the hook arena
|
||||
// These two could be combined with "OwningRef" to remove unsafe usage
|
||||
// or we could dedicate a tiny bump arena just for them
|
||||
// could also use ourborous
|
||||
pub hooks: RefCell<Vec<Hook>>,
|
||||
|
||||
pub hook_arena: Vec<Hook>,
|
||||
|
||||
// Unsafety:
|
||||
// - is self-refenrential and therefore needs to point into the bump
|
||||
// Stores references into the listeners attached to the vnodes
|
||||
// NEEDS TO BE PRIVATE
|
||||
pub(crate) listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
// we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
|
||||
// we are going to break this lifetime by force in order to save it on ourselves.
|
||||
// To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies.
|
||||
// This should never happen, but is a good check to keep around
|
||||
pub fn new<'creator_node>(
|
||||
caller: Weak<OpaqueComponent<'creator_node>>,
|
||||
myidx: ScopeIdx,
|
||||
parent: Option<ScopeIdx>,
|
||||
) -> Self {
|
||||
// Caller has been broken free
|
||||
// However, it's still weak, so if the original Rc gets killed, we can't touch it
|
||||
let broken_caller: Weak<OpaqueComponent<'static>> = unsafe { std::mem::transmute(caller) };
|
||||
|
||||
Self {
|
||||
caller: broken_caller,
|
||||
hook_arena: Vec::new(),
|
||||
hooks: RefCell::new(Vec::new()),
|
||||
frames: ActiveFrame::new(),
|
||||
children: HashSet::new(),
|
||||
listeners: Default::default(),
|
||||
parent,
|
||||
myidx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
|
||||
let broken_caller: Weak<OpaqueComponent<'static>> = unsafe { std::mem::transmute(caller) };
|
||||
|
||||
self.caller = broken_caller;
|
||||
}
|
||||
|
||||
/// Create a new context and run the component with references from the Virtual Dom
|
||||
/// This function downcasts the function pointer based on the stored props_type
|
||||
///
|
||||
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
|
||||
pub fn run_scope<'b>(&'b mut self) -> Result<()> {
|
||||
// cycle to the next frame and then reset it
|
||||
// this breaks any latent references
|
||||
self.frames.next().bump.reset();
|
||||
|
||||
let ctx = Context {
|
||||
idx: 0.into(),
|
||||
_p: PhantomData {},
|
||||
scope: self,
|
||||
};
|
||||
|
||||
let caller = self.caller.upgrade().expect("Failed to get caller");
|
||||
|
||||
/*
|
||||
SAFETY ALERT
|
||||
|
||||
DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
|
||||
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
|
||||
|
||||
Some things to note:
|
||||
- The VNode itself is bound to the lifetime, but it itself is owned by scope.
|
||||
- The VNode has a private API and can only be used from accessors.
|
||||
- Public API cannot drop or destructure VNode
|
||||
*/
|
||||
let new_head = unsafe {
|
||||
// use the same type, just manipulate the lifetime
|
||||
type ComComp<'c> = Rc<OpaqueComponent<'c>>;
|
||||
let caller = std::mem::transmute::<ComComp<'static>, ComComp<'b>>(caller);
|
||||
(caller.as_ref())(ctx)
|
||||
};
|
||||
|
||||
self.frames.cur_frame_mut().head_node = new_head.root;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// A safe wrapper around calling listeners
|
||||
// calling listeners will invalidate the list of listeners
|
||||
// The listener list will be completely drained because the next frame will write over previous listeners
|
||||
pub fn call_listener(&mut self, trigger: EventTrigger) {
|
||||
let EventTrigger {
|
||||
listener_id,
|
||||
event: source,
|
||||
..
|
||||
} = trigger;
|
||||
|
||||
unsafe {
|
||||
let listener = self
|
||||
.listeners
|
||||
.borrow()
|
||||
.get(listener_id as usize)
|
||||
.expect("Listener should exist if it was triggered")
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
|
||||
// Run the callback with the user event
|
||||
log::debug!("Running listener");
|
||||
listener(source);
|
||||
log::debug!("Listener finished");
|
||||
|
||||
// drain all the event listeners
|
||||
// if we don't, then they'll stick around and become invalid
|
||||
// big big big big safety issue
|
||||
self.listeners.borrow_mut().drain(..);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||
self.frames.current_head_node()
|
||||
}
|
||||
|
||||
pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||
self.frames.prev_head_node()
|
||||
}
|
||||
|
||||
pub fn cur_frame(&self) -> &BumpFrame {
|
||||
self.frames.cur_frame()
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================
|
||||
// Active-frame related code
|
||||
// ==========================
|
||||
// todo, do better with the active frame stuff
|
||||
// somehow build this vnode with a lifetime tied to self
|
||||
// This root node has "static" lifetime, but it's really not static.
|
||||
// It's goverened by the oldest of the two frames and is switched every time a new render occurs
|
||||
// Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref
|
||||
// ! do not copy this reference are things WILL break !
|
||||
pub struct ActiveFrame {
|
||||
pub idx: RefCell<usize>,
|
||||
pub frames: [BumpFrame; 2],
|
||||
}
|
||||
|
||||
pub struct BumpFrame {
|
||||
pub bump: Bump,
|
||||
pub head_node: VNode<'static>,
|
||||
}
|
||||
|
||||
impl ActiveFrame {
|
||||
pub fn new() -> Self {
|
||||
Self::from_frames(
|
||||
BumpFrame {
|
||||
bump: Bump::new(),
|
||||
head_node: VNode::text(""),
|
||||
},
|
||||
BumpFrame {
|
||||
bump: Bump::new(),
|
||||
head_node: VNode::text(""),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
|
||||
Self {
|
||||
idx: 0.into(),
|
||||
frames: [a, b],
|
||||
}
|
||||
}
|
||||
|
||||
fn cur_frame(&self) -> &BumpFrame {
|
||||
match *self.idx.borrow() & 1 == 0 {
|
||||
true => &self.frames[0],
|
||||
false => &self.frames[1],
|
||||
}
|
||||
}
|
||||
fn cur_frame_mut(&mut self) -> &mut BumpFrame {
|
||||
match *self.idx.borrow() & 1 == 0 {
|
||||
true => &mut self.frames[0],
|
||||
false => &mut self.frames[1],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||
let raw_node = match *self.idx.borrow() & 1 == 0 {
|
||||
true => &self.frames[0],
|
||||
false => &self.frames[1],
|
||||
};
|
||||
|
||||
// Give out our self-referential item with our own borrowed lifetime
|
||||
unsafe {
|
||||
let unsafe_head = &raw_node.head_node;
|
||||
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
|
||||
safe_node
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||
let raw_node = match *self.idx.borrow() & 1 != 0 {
|
||||
true => &self.frames[0],
|
||||
false => &self.frames[1],
|
||||
};
|
||||
|
||||
// Give out our self-referential item with our own borrowed lifetime
|
||||
unsafe {
|
||||
let unsafe_head = &raw_node.head_node;
|
||||
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
|
||||
safe_node
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self) -> &mut BumpFrame {
|
||||
*self.idx.borrow_mut() += 1;
|
||||
|
||||
if *self.idx.borrow() % 2 == 0 {
|
||||
&mut self.frames[0]
|
||||
} else {
|
||||
&mut self.frames[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(old)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
// static ListenerTest: FC<()> = |ctx, props| {
|
||||
// ctx.render(html! {
|
||||
// <div onclick={|_| println!("Hell owlrld")}>
|
||||
// "hello"
|
||||
// </div>
|
||||
// })
|
||||
// };
|
||||
|
||||
#[test]
|
||||
fn test_scope() {
|
||||
#[derive(PartialEq)]
|
||||
struct Example {}
|
||||
impl FC for Example {
|
||||
fn render(ctx: Context<'_>, _: &Self) -> DomTree {
|
||||
use crate::builder::*;
|
||||
ctx.render(|ctx| {
|
||||
builder::ElementBuilder::new(ctx, "div")
|
||||
.child(text("a"))
|
||||
.finish()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let mut nodes = generational_arena::Arena::new();
|
||||
nodes.insert_with(|myidx| {
|
||||
let scope = create_scoped(Example {}, myidx, None);
|
||||
});
|
||||
}
|
||||
|
||||
use crate::{builder::*, hooks::use_ref};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct EmptyProps<'src> {
|
||||
name: &'src String,
|
||||
}
|
||||
|
||||
impl FC for EmptyProps<'_> {
|
||||
fn render(ctx: Context, props: &Self) -> DomTree {
|
||||
let (content, _): (&String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string());
|
||||
|
||||
let childprops: ExampleProps<'_> = ExampleProps { name: content };
|
||||
todo!()
|
||||
// ctx.render(move |c| {
|
||||
// builder::ElementBuilder::new(c, "div")
|
||||
// .child(text(props.name))
|
||||
// .child(virtual_child(c, childprops))
|
||||
// .finish()
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct ExampleProps<'src> {
|
||||
name: &'src String,
|
||||
}
|
||||
|
||||
impl FC for ExampleProps<'_> {
|
||||
fn render(ctx: Context, props: &Self) -> DomTree {
|
||||
ctx.render(move |ctx| {
|
||||
builder::ElementBuilder::new(ctx, "div")
|
||||
.child(text(props.name))
|
||||
.finish()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_borrowed_scope() {
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Example {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl FC for Example {
|
||||
fn render(ctx: Context, props: &Self) -> DomTree {
|
||||
todo!()
|
||||
// ctx.render(move |c| {
|
||||
// builder::ElementBuilder::new(c, "div")
|
||||
// .child(virtual_child(c, ExampleProps { name: &props.name }))
|
||||
// .finish()
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
let source_text = "abcd123".to_string();
|
||||
let props = ExampleProps { name: &source_text };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(asd)]
|
||||
mod old {
|
||||
|
||||
/// The ComponentCaller struct is an opaque object that encapsultes the memoization and running functionality for FC
|
||||
///
|
||||
/// It's opaque because during the diffing mechanism, the type of props is sealed away in a closure. This makes it so
|
||||
/// scope doesn't need to be generic
|
||||
pub struct ComponentCaller {
|
||||
// used as a memoization strategy
|
||||
comparator: Box<dyn Fn(&Box<dyn Any>) -> bool>,
|
||||
|
||||
// used to actually run the component
|
||||
// encapsulates props
|
||||
runner: Box<dyn Fn(Context) -> DomTree>,
|
||||
|
||||
props_type: TypeId,
|
||||
|
||||
// the actual FC<T>
|
||||
raw: *const (),
|
||||
}
|
||||
|
||||
impl ComponentCaller {
|
||||
fn new<P>(props: P) -> Self {
|
||||
let comparator = Box::new(|f| false);
|
||||
todo!();
|
||||
// Self { comparator }
|
||||
}
|
||||
|
||||
fn update_props<P>(props: P) {}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
// use crate::{changelist::EditList, nodes::VNode};
|
||||
|
||||
use crate::{error::Error, innerlude::*};
|
||||
use crate::{patch::Edit, scope::Scope};
|
||||
use crate::{innerlude::hooks::Hook, patch::Edit};
|
||||
use bumpalo::Bump;
|
||||
use generational_arena::Arena;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
borrow::{Borrow, BorrowMut},
|
||||
cell::RefCell,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
cell::{Ref, RefCell, UnsafeCell},
|
||||
collections::{BTreeMap, BTreeSet, BinaryHeap, HashSet},
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
use thiserror::private::AsDynError;
|
||||
|
@ -21,23 +20,30 @@ pub(crate) type OpaqueComponent<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + '
|
|||
pub struct VirtualDom {
|
||||
/// 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.
|
||||
pub components: Arena<Scope>,
|
||||
///
|
||||
/// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
|
||||
/// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
|
||||
components: UnsafeCell<Arena<Scope>>,
|
||||
|
||||
/// The index of the root component.
|
||||
/// Will not be ready if the dom is fresh
|
||||
/// The index of the root component.\
|
||||
/// Should always be the first
|
||||
pub base_scope: ScopeIdx,
|
||||
|
||||
/// All components dump their updates into a queue to be processed
|
||||
pub(crate) update_schedule: UpdateFunnel,
|
||||
|
||||
// a strong allocation to the "caller" for the original props
|
||||
#[doc(hidden)]
|
||||
_root_caller: Rc<OpaqueComponent<'static>>,
|
||||
root_caller: Rc<OpaqueComponent<'static>>,
|
||||
|
||||
// Type of the original props. This is done so VirtualDom does not need to be generic.
|
||||
#[doc(hidden)]
|
||||
_root_prop_type: std::any::TypeId,
|
||||
}
|
||||
|
||||
// ======================================
|
||||
// Public Methods for the VirtualDOM
|
||||
// ======================================
|
||||
impl VirtualDom {
|
||||
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
|
||||
///
|
||||
|
@ -55,109 +61,50 @@ impl VirtualDom {
|
|||
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
||||
let mut components = Arena::new();
|
||||
|
||||
// let prr = Rc::new(root_props);
|
||||
// The user passes in a "root" component (IE the function)
|
||||
// When components are used in the rsx! syntax, the parent assumes ownership
|
||||
// Here, the virtual dom needs to own the function, wrapping it with a `context caller`
|
||||
// The RC holds this component with a hard allocation
|
||||
let root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| root(ctx, &root_props));
|
||||
|
||||
// the root is kept around with a "hard" allocation
|
||||
let root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| {
|
||||
//
|
||||
// let p2 = &root_props;
|
||||
// let p2 = prr.clone();
|
||||
root(ctx, &root_props)
|
||||
});
|
||||
|
||||
// we then expose this to the component with a weak allocation
|
||||
let weak_caller: Weak<OpaqueComponent> = Rc::downgrade(&root_caller);
|
||||
|
||||
let base_scope = components.insert_with(move |myidx| Scope::new(weak_caller, myidx, None));
|
||||
// To make it easier to pass the root around, we just leak it
|
||||
// When the virtualdom is dropped, we unleak it, so that unsafe isn't here, but it's important to remember
|
||||
let leaked_caller = Rc::downgrade(&root_caller);
|
||||
|
||||
Self {
|
||||
components,
|
||||
_root_caller: root_caller,
|
||||
base_scope,
|
||||
root_caller,
|
||||
base_scope: components
|
||||
.insert_with(move |myidx| Scope::new(leaked_caller, myidx, None, 0)),
|
||||
components: UnsafeCell::new(components),
|
||||
update_schedule: UpdateFunnel::default(),
|
||||
_root_prop_type: TypeId::of::<P>(),
|
||||
}
|
||||
}
|
||||
|
||||
// consume the top of the diff machine event cycle and dump edits into the edit list
|
||||
pub fn step(&mut self, event: LifeCycleEvent) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom.
|
||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom. from scratch
|
||||
pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
|
||||
// Diff from the top
|
||||
let mut diff_machine = DiffMachine::new();
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
let very_unsafe_components = &mut self.components as *mut generational_arena::Arena<Scope>;
|
||||
let component = self
|
||||
.components
|
||||
.get_mut(self.base_scope)
|
||||
.ok_or_else(|| Error::FatalInternal("Acquring base component should never fail"))?;
|
||||
component.run_scope()?;
|
||||
diff_machine.diff_node(component.old_frame(), component.new_frame());
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct HeightMarker {
|
||||
idx: ScopeIdx,
|
||||
height: u32,
|
||||
}
|
||||
impl Ord for HeightMarker {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.height.cmp(&other.height)
|
||||
}
|
||||
}
|
||||
|
||||
// log::debug!("New events generated: {:#?}", diff_machine.lifecycle_events);
|
||||
// chew down the the lifecycle events until all dirty nodes are computed
|
||||
while let Some(event) = diff_machine.lifecycle_events.pop_front() {
|
||||
match event {
|
||||
// A new component has been computed from the diffing algorithm
|
||||
// create a new component in the arena, run it, move the diffing machine to this new spot, and then diff it
|
||||
// this will flood the lifecycle queue with new updates
|
||||
LifeCycleEvent::Mount { caller, id, scope } => {
|
||||
log::debug!("Mounting a new component");
|
||||
|
||||
// We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
|
||||
// those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
|
||||
// However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
|
||||
unsafe {
|
||||
let p = &mut *(very_unsafe_components);
|
||||
|
||||
// todo, hook up the parent/child indexes properly
|
||||
let idx = p.insert_with(|f| Scope::new(caller, f, None));
|
||||
let c = p.get_mut(idx).unwrap();
|
||||
|
||||
let real_scope = scope.upgrade().unwrap();
|
||||
*real_scope.as_ref().borrow_mut() = Some(idx);
|
||||
c.run_scope()?;
|
||||
diff_machine.change_list.load_known_root(id);
|
||||
diff_machine.diff_node(c.old_frame(), c.new_frame());
|
||||
}
|
||||
}
|
||||
LifeCycleEvent::PropsChanged { caller, id, scope } => {
|
||||
log::debug!("PROPS CHANGED");
|
||||
let idx = scope.upgrade().unwrap().as_ref().borrow().unwrap();
|
||||
unsafe {
|
||||
let p = &mut *(very_unsafe_components);
|
||||
let c = p.get_mut(idx).unwrap();
|
||||
c.update_caller(caller);
|
||||
c.run_scope()?;
|
||||
diff_machine.change_list.load_known_root(id);
|
||||
diff_machine.diff_node(c.old_frame(), c.new_frame());
|
||||
}
|
||||
// break 'render;
|
||||
}
|
||||
LifeCycleEvent::SameProps { caller, id, scope } => {
|
||||
log::debug!("SAME PROPS RECEIVED");
|
||||
//
|
||||
// break 'render;
|
||||
}
|
||||
LifeCycleEvent::Remove => {
|
||||
//
|
||||
// break 'render;
|
||||
}
|
||||
LifeCycleEvent::Replace { caller, id, .. } => {}
|
||||
}
|
||||
|
||||
// } else {
|
||||
// break 'render;
|
||||
// }
|
||||
}
|
||||
|
||||
let edits: Vec<Edit<'s>> = diff_machine.consume();
|
||||
Ok(edits)
|
||||
}
|
||||
impl PartialOrd for HeightMarker {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
/// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
|
||||
///
|
||||
/// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual
|
||||
|
@ -165,7 +112,16 @@ impl VirtualDom {
|
|||
/// change list.
|
||||
///
|
||||
/// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
|
||||
/// listeners.
|
||||
/// listeners, something like this:
|
||||
///
|
||||
/// ```ignore
|
||||
/// while let Ok(event) = receiver.recv().await {
|
||||
/// let edits = self.internal_dom.progress_with_event(event)?;
|
||||
/// for edit in &edits {
|
||||
/// patch_machine.handle_edit(edit);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Note: this method is not async and does not provide suspense-like functionality. It is up to the renderer to provide the
|
||||
/// executor and handlers for suspense as show in the example.
|
||||
|
@ -182,67 +138,508 @@ impl VirtualDom {
|
|||
/// }
|
||||
///
|
||||
/// ```
|
||||
pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
|
||||
let component = self
|
||||
.components
|
||||
.get_mut(event.component_id)
|
||||
.expect("Borrowing should not fail");
|
||||
//
|
||||
// Developer notes:
|
||||
// ----
|
||||
// This method has some pretty complex safety guarantees to uphold.
|
||||
// We interact with bump arenas, raw pointers, and use UnsafeCell to get a partial borrow of the arena.
|
||||
// The final EditList has edits that pull directly from the Bump Arenas which add significant complexity
|
||||
// in crafting a 100% safe solution with traditional lifetimes. Consider this method to be internally unsafe
|
||||
// but the guarantees provide a safe, fast, and efficient abstraction for the VirtualDOM updating framework.
|
||||
pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList> {
|
||||
let id = event.component_id.clone();
|
||||
|
||||
component.call_listener(event);
|
||||
unsafe {
|
||||
(&mut *self.components.get())
|
||||
.get_mut(id)
|
||||
.expect("Borrowing should not fail")
|
||||
.call_listener(event)?;
|
||||
}
|
||||
|
||||
/*
|
||||
-> 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()?;
|
||||
// Add this component to the list of components that need to be difed
|
||||
let mut diff_machine = DiffMachine::new();
|
||||
let mut cur_height = 0;
|
||||
|
||||
// let mut diff_machine = DiffMachine::new();
|
||||
// let mut diff_machine = DiffMachine::new(&self.diff_bump);
|
||||
// Now, there are events in the queue
|
||||
let mut seen_nodes = HashSet::<ScopeIdx>::new();
|
||||
let mut updates = self.update_schedule.0.as_ref().borrow_mut();
|
||||
|
||||
// diff_machine.diff_node(component.old_frame(), component.new_frame());
|
||||
// Order the nodes by their height, we want the biggest nodes on the top
|
||||
// This prevents us from running the same component multiple times
|
||||
updates.sort_unstable();
|
||||
|
||||
// Ok(diff_machine.consume())
|
||||
self.rebuild()
|
||||
// Iterate through the triggered nodes (sorted by height) and begin to diff them
|
||||
for update in updates.drain(..) {
|
||||
// Make sure this isn't a node we've already seen, we don't want to double-render anything
|
||||
// If we double-renderer something, this would cause memory safety issues
|
||||
if seen_nodes.contains(&update.idx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now, all the "seen nodes" are nodes that got notified by running this listener
|
||||
seen_nodes.insert(update.idx.clone());
|
||||
|
||||
unsafe {
|
||||
// Start a new mutable borrow to components
|
||||
|
||||
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified
|
||||
let component = (&mut *self.components.get())
|
||||
.get_mut(update.idx)
|
||||
.expect("msg");
|
||||
|
||||
component.run_scope()?;
|
||||
|
||||
diff_machine.diff_node(component.old_frame(), component.next_frame());
|
||||
|
||||
cur_height = component.height + 1;
|
||||
}
|
||||
|
||||
// Now, the entire subtree has been invalidated. We need to descend depth-first and process
|
||||
// any updates that the diff machine has proprogated into the component lifecycle queue
|
||||
while let Some(event) = diff_machine.lifecycle_events.pop_front() {
|
||||
match event {
|
||||
// A new component has been computed from the diffing algorithm
|
||||
// create a new component in the arena, run it, move the diffing machine to this new spot, and then diff it
|
||||
// this will flood the lifecycle queue with new updates to build up the subtree
|
||||
LifeCycleEvent::Mount {
|
||||
caller,
|
||||
root_id: id,
|
||||
stable_scope_addr,
|
||||
} => {
|
||||
log::debug!("Mounting a new component");
|
||||
|
||||
// We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
|
||||
// those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
|
||||
// However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
|
||||
let components: &mut _ = unsafe { &mut *self.components.get() };
|
||||
|
||||
// Insert a new scope into our component list
|
||||
let idx =
|
||||
components.insert_with(|f| Scope::new(caller, f, None, cur_height));
|
||||
|
||||
// Grab out that component
|
||||
let component = components.get_mut(idx).unwrap();
|
||||
|
||||
// Actually initialize the caller's slot with the right address
|
||||
*stable_scope_addr.upgrade().unwrap().as_ref().borrow_mut() = Some(idx);
|
||||
|
||||
// Run the scope for one iteration to initialize it
|
||||
component.run_scope()?;
|
||||
|
||||
// Navigate the diff machine to the right point in the output dom
|
||||
diff_machine.change_list.load_known_root(id);
|
||||
|
||||
// And then run the diff algorithm
|
||||
diff_machine.diff_node(component.old_frame(), component.next_frame());
|
||||
|
||||
// Finally, insert this node as a seen node.
|
||||
seen_nodes.insert(idx);
|
||||
}
|
||||
|
||||
// A component has remained in the same location but its properties have changed
|
||||
// We need to process this component and then dump the output lifecycle events into the queue
|
||||
LifeCycleEvent::PropsChanged {
|
||||
caller,
|
||||
root_id,
|
||||
stable_scope_addr,
|
||||
} => {
|
||||
log::debug!("Updating a component after its props have changed");
|
||||
|
||||
let components: &mut _ = unsafe { &mut *self.components.get() };
|
||||
|
||||
// Get the stable index to the target component
|
||||
// This *should* exist due to guarantees in the diff algorithm
|
||||
let idx = stable_scope_addr
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.borrow()
|
||||
.unwrap();
|
||||
|
||||
// Grab out that component
|
||||
let component = components.get_mut(idx).unwrap();
|
||||
|
||||
// We have to move the caller over or running the scope will fail
|
||||
component.update_caller(caller);
|
||||
|
||||
// Run the scope
|
||||
component.run_scope()?;
|
||||
|
||||
// Navigate the diff machine to the right point in the output dom
|
||||
diff_machine.change_list.load_known_root(root_id);
|
||||
|
||||
// And then run the diff algorithm
|
||||
diff_machine.diff_node(component.old_frame(), component.next_frame());
|
||||
|
||||
// Finally, insert this node as a seen node.
|
||||
seen_nodes.insert(idx);
|
||||
}
|
||||
|
||||
// A component's parent has updated, but its properties did not change.
|
||||
// This means the caller ptr is invalidated and needs to be updated, but the component itself does not need to be re-ran
|
||||
LifeCycleEvent::SameProps {
|
||||
caller,
|
||||
root_id,
|
||||
stable_scope_addr,
|
||||
} => {
|
||||
// In this case, the parent made a new DomTree that resulted in the same props for us
|
||||
// However, since our caller is located in a Bump frame, we need to update the caller pointer (which is now invalid)
|
||||
log::debug!("Received the same props");
|
||||
|
||||
let components: &mut _ = unsafe { &mut *self.components.get() };
|
||||
|
||||
// Get the stable index to the target component
|
||||
// This *should* exist due to guarantees in the diff algorithm
|
||||
let idx = stable_scope_addr
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.borrow()
|
||||
.unwrap();
|
||||
|
||||
// Grab out that component
|
||||
let component = components.get_mut(idx).unwrap();
|
||||
|
||||
// We have to move the caller over or running the scope will fail
|
||||
component.update_caller(caller);
|
||||
|
||||
// This time, we will not add it to our seen nodes since we did not actually run it
|
||||
}
|
||||
|
||||
LifeCycleEvent::Remove {
|
||||
root_id,
|
||||
stable_scope_addr,
|
||||
} => {
|
||||
unimplemented!("This feature (Remove) is unimplemented")
|
||||
}
|
||||
LifeCycleEvent::Replace {
|
||||
caller,
|
||||
root_id: id,
|
||||
..
|
||||
} => {
|
||||
unimplemented!("This feature (Replace) is unimplemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(diff_machine.consume())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct HierarchyMarker {
|
||||
source: ScopeIdx,
|
||||
impl Drop for VirtualDom {
|
||||
fn drop(&mut self) {
|
||||
// Drop all the components first
|
||||
// self.components.drain();
|
||||
|
||||
// Finally, drop the root caller
|
||||
unsafe {
|
||||
// let root: Box<OpaqueComponent<'static>> =
|
||||
// Box::from_raw(self.root_caller as *const OpaqueComponent<'static> as *mut _);
|
||||
|
||||
// std::mem::drop(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct UpdateFunnel(Rc<RefCell<Vec<HierarchyMarker>>>);
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct UpdateFunnel(Rc<RefCell<Vec<HeightMarker>>>);
|
||||
|
||||
impl UpdateFunnel {
|
||||
fn schedule_update(&self, source: ScopeIdx) -> impl Fn() {
|
||||
fn schedule_update(&self, source: &Scope) -> impl Fn() {
|
||||
let inner = self.clone();
|
||||
move || {
|
||||
inner
|
||||
.0
|
||||
.as_ref()
|
||||
.borrow_mut()
|
||||
.push(HierarchyMarker { source })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! UpdateFunnel {
|
||||
(root: $root:expr) => {
|
||||
VirtualDom::new($root)
|
||||
let marker = HeightMarker {
|
||||
height: source.height,
|
||||
idx: source.myidx,
|
||||
};
|
||||
move || inner.0.as_ref().borrow_mut().push(marker)
|
||||
}
|
||||
}
|
||||
/// Every component in Dioxus is represented by a `Scope`.
|
||||
///
|
||||
/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
|
||||
///
|
||||
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
|
||||
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
|
||||
pub struct Scope {
|
||||
// The parent's scope ID
|
||||
pub parent: Option<ScopeIdx>,
|
||||
|
||||
// Our own ID accessible from the component map
|
||||
pub myidx: ScopeIdx,
|
||||
|
||||
pub height: u32,
|
||||
|
||||
// A list of children
|
||||
// TODO, repalce the hashset with a faster hash function
|
||||
pub children: HashSet<ScopeIdx>,
|
||||
|
||||
// caller: &'static OpaqueComponent<'static>,
|
||||
pub caller: Weak<OpaqueComponent<'static>>,
|
||||
|
||||
// ==========================
|
||||
// slightly unsafe stuff
|
||||
// ==========================
|
||||
// an internal, highly efficient storage of vnodes
|
||||
pub(crate) frames: ActiveFrame,
|
||||
|
||||
// These hooks are actually references into the hook arena
|
||||
// These two could be combined with "OwningRef" to remove unsafe usage
|
||||
// or we could dedicate a tiny bump arena just for them
|
||||
// could also use ourborous
|
||||
pub(crate) hooks: RefCell<Vec<Hook>>,
|
||||
|
||||
// Unsafety:
|
||||
// - is self-refenrential and therefore needs to point into the bump
|
||||
// Stores references into the listeners attached to the vnodes
|
||||
// NEEDS TO BE PRIVATE
|
||||
pub(crate) listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_new_vdom() {
|
||||
// let dom = UpdateFunnel! {
|
||||
// root: |ctx, props| {
|
||||
// ctx.render(rsx!{
|
||||
impl Scope {
|
||||
// we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
|
||||
// we are going to break this lifetime by force in order to save it on ourselves.
|
||||
// To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies.
|
||||
// This should never happen, but is a good check to keep around
|
||||
//
|
||||
// Scopes cannot be made anywhere else except for this file
|
||||
// Therefore, their lifetimes are connected exclusively to the virtual dom
|
||||
fn new<'creator_node>(
|
||||
caller: Weak<OpaqueComponent<'creator_node>>,
|
||||
myidx: ScopeIdx,
|
||||
parent: Option<ScopeIdx>,
|
||||
height: u32,
|
||||
) -> Self {
|
||||
log::debug!(
|
||||
"New scope created, height is {}, idx is {:?}",
|
||||
height,
|
||||
myidx
|
||||
);
|
||||
// Caller has been broken free
|
||||
// However, it's still weak, so if the original Rc gets killed, we can't touch it
|
||||
let broken_caller: Weak<OpaqueComponent<'static>> = unsafe { std::mem::transmute(caller) };
|
||||
|
||||
// })
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
Self {
|
||||
caller: broken_caller,
|
||||
hooks: RefCell::new(Vec::new()),
|
||||
frames: ActiveFrame::new(),
|
||||
children: HashSet::new(),
|
||||
listeners: Default::default(),
|
||||
parent,
|
||||
myidx,
|
||||
height,
|
||||
}
|
||||
}
|
||||
pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
|
||||
let broken_caller: Weak<OpaqueComponent<'static>> = unsafe { std::mem::transmute(caller) };
|
||||
|
||||
self.caller = broken_caller;
|
||||
}
|
||||
|
||||
/// Create a new context and run the component with references from the Virtual Dom
|
||||
/// This function downcasts the function pointer based on the stored props_type
|
||||
///
|
||||
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
|
||||
pub fn run_scope<'b>(&'b mut self) -> Result<()> {
|
||||
// cycle to the next frame and then reset it
|
||||
// this breaks any latent references
|
||||
self.frames.next().bump.reset();
|
||||
|
||||
let ctx = Context {
|
||||
idx: 0.into(),
|
||||
_p: std::marker::PhantomData {},
|
||||
scope: self,
|
||||
};
|
||||
|
||||
let caller = self.caller.upgrade().expect("Failed to get caller");
|
||||
|
||||
/*
|
||||
SAFETY ALERT
|
||||
|
||||
DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
|
||||
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
|
||||
|
||||
Some things to note:
|
||||
- The VNode itself is bound to the lifetime, but it itself is owned by scope.
|
||||
- The VNode has a private API and can only be used from accessors.
|
||||
- Public API cannot drop or destructure VNode
|
||||
*/
|
||||
let new_head = unsafe {
|
||||
// use the same type, just manipulate the lifetime
|
||||
type ComComp<'c> = Rc<OpaqueComponent<'c>>;
|
||||
let caller = std::mem::transmute::<ComComp<'static>, ComComp<'b>>(caller);
|
||||
(caller.as_ref())(ctx)
|
||||
};
|
||||
|
||||
self.frames.cur_frame_mut().head_node = new_head.root;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// A safe wrapper around calling listeners
|
||||
// calling listeners will invalidate the list of listeners
|
||||
// The listener list will be completely drained because the next frame will write over previous listeners
|
||||
pub fn call_listener(&mut self, trigger: EventTrigger) -> Result<()> {
|
||||
let EventTrigger {
|
||||
listener_id,
|
||||
event: source,
|
||||
..
|
||||
} = trigger;
|
||||
|
||||
unsafe {
|
||||
// Convert the raw ptr into an actual object
|
||||
// This operation is assumed to be safe
|
||||
|
||||
log::debug!("Running listener");
|
||||
|
||||
self.listeners
|
||||
.borrow()
|
||||
.get(listener_id as usize)
|
||||
.ok_or(Error::FatalInternal("Event should exist if it was triggered"))?
|
||||
.as_ref()
|
||||
.ok_or(Error::FatalInternal("Raw event ptr is invalid"))?
|
||||
// Run the callback with the user event
|
||||
(source);
|
||||
|
||||
log::debug!("Listener finished");
|
||||
|
||||
// drain all the event listeners
|
||||
// if we don't, then they'll stick around and become invalid
|
||||
// big big big big safety issue
|
||||
self.listeners.borrow_mut().drain(..);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||
self.frames.current_head_node()
|
||||
}
|
||||
|
||||
pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||
self.frames.prev_head_node()
|
||||
}
|
||||
|
||||
pub fn cur_frame(&self) -> &BumpFrame {
|
||||
self.frames.cur_frame()
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================
|
||||
// Active-frame related code
|
||||
// ==========================
|
||||
// todo, do better with the active frame stuff
|
||||
// somehow build this vnode with a lifetime tied to self
|
||||
// This root node has "static" lifetime, but it's really not static.
|
||||
// It's goverened by the oldest of the two frames and is switched every time a new render occurs
|
||||
// Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref
|
||||
// ! do not copy this reference are things WILL break !
|
||||
pub struct ActiveFrame {
|
||||
pub idx: RefCell<usize>,
|
||||
pub frames: [BumpFrame; 2],
|
||||
|
||||
// We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
|
||||
pub generation: u32,
|
||||
}
|
||||
|
||||
pub struct BumpFrame {
|
||||
pub bump: Bump,
|
||||
pub head_node: VNode<'static>,
|
||||
}
|
||||
|
||||
impl ActiveFrame {
|
||||
pub fn new() -> Self {
|
||||
Self::from_frames(
|
||||
BumpFrame {
|
||||
bump: Bump::new(),
|
||||
head_node: VNode::text(""),
|
||||
},
|
||||
BumpFrame {
|
||||
bump: Bump::new(),
|
||||
head_node: VNode::text(""),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
|
||||
Self {
|
||||
idx: 0.into(),
|
||||
frames: [a, b],
|
||||
generation: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn cur_frame(&self) -> &BumpFrame {
|
||||
match *self.idx.borrow() & 1 == 0 {
|
||||
true => &self.frames[0],
|
||||
false => &self.frames[1],
|
||||
}
|
||||
}
|
||||
fn cur_frame_mut(&mut self) -> &mut BumpFrame {
|
||||
match *self.idx.borrow() & 1 == 0 {
|
||||
true => &mut self.frames[0],
|
||||
false => &mut self.frames[1],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||
let raw_node = match *self.idx.borrow() & 1 == 0 {
|
||||
true => &self.frames[0],
|
||||
false => &self.frames[1],
|
||||
};
|
||||
|
||||
// Give out our self-referential item with our own borrowed lifetime
|
||||
unsafe {
|
||||
let unsafe_head = &raw_node.head_node;
|
||||
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
|
||||
safe_node
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||
let raw_node = match *self.idx.borrow() & 1 != 0 {
|
||||
true => &self.frames[0],
|
||||
false => &self.frames[1],
|
||||
};
|
||||
|
||||
// Give out our self-referential item with our own borrowed lifetime
|
||||
unsafe {
|
||||
let unsafe_head = &raw_node.head_node;
|
||||
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
|
||||
safe_node
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self) -> &mut BumpFrame {
|
||||
*self.idx.borrow_mut() += 1;
|
||||
|
||||
if *self.idx.borrow() % 2 == 0 {
|
||||
&mut self.frames[0]
|
||||
} else {
|
||||
&mut self.frames[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn simulate() {
|
||||
let dom = VirtualDom::new(|ctx, props| {
|
||||
//
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
|
||||
}
|
||||
})
|
||||
});
|
||||
// let root = dom.components.get(dom.base_scope).unwrap();
|
||||
}
|
||||
|
||||
// // This marker is designed to ensure resources shared from one bump to another are handled properly
|
||||
// // The underlying T may be already freed if there is an issue with our crate
|
||||
// pub(crate) struct BumpResource<T: 'static> {
|
||||
// resource: T,
|
||||
// scope: ScopeIdx,
|
||||
// gen: u32,
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -1,10 +1,119 @@
|
|||
// mod hooks;
|
||||
// pub use hooks::use_context;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// pub mod prelude {
|
||||
// use dioxus_core::prelude::Context;
|
||||
// pub fn use_state<T, G>(ctx: &Context<G>, init: impl Fn() -> T) -> (T, impl Fn(T)) {
|
||||
// let g = init();
|
||||
// (g, |_| {})
|
||||
// }
|
||||
// }
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
/*
|
||||
a form of use_state explicitly for map-style collections (BTreeMap, HashMap, etc).
|
||||
|
||||
Why?
|
||||
---
|
||||
Traditionally, it's possible to use the "use_state" hook for collections in the React world.
|
||||
Adding a new entry would look something similar to:
|
||||
|
||||
```js
|
||||
let (map, set_map) = useState({});
|
||||
set_map({ ...map, [key]: value });
|
||||
```
|
||||
The new value then causes the appropriate update when passed into children.
|
||||
|
||||
This is moderately efficient because the fields of the map are moved, but the data itself is not cloned.
|
||||
However, if you used similar approach with Dioxus:
|
||||
|
||||
```rust
|
||||
let (map, set_map) = use_state(ctx, || HashMap::new());
|
||||
set_map({
|
||||
let mut newmap = map.clone();
|
||||
newmap.set(key, value);
|
||||
newmap
|
||||
})
|
||||
```
|
||||
Unfortunately, you'd be cloning the entire state every time a value is changed. The obvious solution is to
|
||||
wrap every element in the HashMap with an Rc. That way, cloning the HashMap is on par with its JS equivalent.
|
||||
|
||||
Fortunately, we can make this operation even more efficient in Dioxus, leveraging the borrow rules of Rust.
|
||||
|
||||
This hook provides a memoized collection, memoized setters, and memoized getters. This particular hook is
|
||||
extremely powerful for implementing lists and supporting core state management needs for small apps.
|
||||
|
||||
If you need something even more powerful, check out the dedicated atomic state management Dioxus Dataflow, which
|
||||
uses the same memoization on top of the use_context API.
|
||||
|
||||
Here's a fully-functional todo app using the use_map API:
|
||||
```rust
|
||||
static TodoList: FC<()> = |ctx, props| {
|
||||
let todos = use_map(ctx, || HashMap::new());
|
||||
let input = use_ref(|| None);
|
||||
|
||||
ctx.render(rsx!{
|
||||
div {
|
||||
button {
|
||||
"Add todo"
|
||||
onclick: move |_| {
|
||||
let new_todo = TodoItem::new(input.contents());
|
||||
todos.insert(new_todo.id.clone(), new_todo);
|
||||
input.clear();
|
||||
}
|
||||
}
|
||||
button {
|
||||
"Clear todos"
|
||||
onclick: move |_| todos.clear()
|
||||
}
|
||||
input {
|
||||
placeholder: "What needs to be done?"
|
||||
ref: input
|
||||
}
|
||||
ul {
|
||||
{todos.iter().map(|todo| rsx!(
|
||||
li {
|
||||
key: todo.id
|
||||
span { "{todo.content}" }
|
||||
button {"x", onclick: move |_| todos.remove(todo.key.clone())}
|
||||
}
|
||||
))}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
*/
|
||||
fn use_map() {}
|
||||
|
||||
// a form of "use_state" that allows collection memoization
|
||||
// Elements are received as Rc<T> in case the underlying collection is shuffled around
|
||||
// Setters/getters can be generated
|
||||
fn use_collection<'a, T: Collection>(
|
||||
ctx: &Context<'a>,
|
||||
f: impl Fn() -> T,
|
||||
) -> CollectionHandle<'a, T> {
|
||||
ctx.use_hook(
|
||||
|| {},
|
||||
|h| {
|
||||
//
|
||||
CollectionHandle {
|
||||
_p: Default::default(),
|
||||
}
|
||||
},
|
||||
|h| {},
|
||||
)
|
||||
}
|
||||
|
||||
struct CollectionMemo {}
|
||||
|
||||
struct CollectionHandle<'a, T: Collection> {
|
||||
_p: std::marker::PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
trait Collection {}
|
||||
impl<K, V> Collection for std::collections::HashMap<K, V> {}
|
||||
|
||||
struct MapCollection<K, V> {
|
||||
inner: HashMap<K, V>,
|
||||
}
|
||||
|
||||
impl<K, V> MapCollection<K, V> {
|
||||
fn set(&self, key: K, val: V) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "dioxus-ios"
|
||||
version = "0.0.0"
|
||||
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cacao = { git = "https://github.com/ryanmcgrath/cacao" }
|
||||
dioxus-core = { path = "../core", version = "0.1.2" }
|
||||
log = "0.4.14"
|
|
@ -0,0 +1,3 @@
|
|||
# Dioxus for iOS
|
||||
|
||||
This crate implements a renderer of the Dioxus DomTree to an iOS app
|
|
@ -0,0 +1,70 @@
|
|||
use dioxus::virtual_dom::VirtualDom;
|
||||
pub use dioxus_core as dioxus;
|
||||
use dioxus_core::{events::EventTrigger, prelude::FC};
|
||||
|
||||
pub struct IosRenderer {
|
||||
internal_dom: VirtualDom,
|
||||
}
|
||||
|
||||
impl IosRenderer {
|
||||
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
|
||||
let (sender, mut receiver) = async_channel::unbounded::<EventTrigger>();
|
||||
|
||||
// let body_element = prepare_websys_dom();
|
||||
|
||||
let mut patch_machine = interpreter::PatchMachine::new(body_element.clone(), move |ev| {
|
||||
log::debug!("Event trigger! {:#?}", ev);
|
||||
let mut c = sender.clone();
|
||||
// wasm_bindgen_futures::spawn_local(async move {
|
||||
// c.send(ev).await.unwrap();
|
||||
// });
|
||||
});
|
||||
let root_node = body_element.first_child().unwrap();
|
||||
patch_machine.stack.push(root_node.clone());
|
||||
|
||||
// todo: initialize the event registry properly on the root
|
||||
|
||||
let edits = self.internal_dom.rebuild()?;
|
||||
log::debug!("Received edits: {:#?}", edits);
|
||||
edits.iter().for_each(|edit| {
|
||||
log::debug!("patching with {:?}", edit);
|
||||
patch_machine.handle_edit(edit);
|
||||
});
|
||||
|
||||
patch_machine.reset();
|
||||
let root_node = body_element.first_child().unwrap();
|
||||
patch_machine.stack.push(root_node.clone());
|
||||
|
||||
// log::debug!("patch stack size {:?}", patch_machine.stack);
|
||||
|
||||
// Event loop waits for the receiver to finish up
|
||||
// TODO! Connect the sender to the virtual dom's suspense system
|
||||
// Suspense is basically an external event that can force renders to specific nodes
|
||||
while let Ok(event) = receiver.recv().await {
|
||||
log::debug!("Stack before entrance {:#?}", patch_machine.stack.top());
|
||||
// log::debug!("patch stack size before {:#?}", patch_machine.stack);
|
||||
// patch_machine.reset();
|
||||
// patch_machine.stack.push(root_node.clone());
|
||||
let edits = self.internal_dom.progress_with_event(event)?;
|
||||
log::debug!("Received edits: {:#?}", edits);
|
||||
|
||||
for edit in &edits {
|
||||
log::debug!("edit stream {:?}", edit);
|
||||
// log::debug!("Stream stack {:#?}", patch_machine.stack.top());
|
||||
patch_machine.handle_edit(edit);
|
||||
}
|
||||
|
||||
// log::debug!("patch stack size after {:#?}", patch_machine.stack);
|
||||
patch_machine.reset();
|
||||
// our root node reference gets invalidated
|
||||
// not sure why
|
||||
// for now, just select the first child again.
|
||||
// eventually, we'll just make our own root element instead of using body
|
||||
// or just use body directly IDEK
|
||||
let root_node = body_element.first_child().unwrap();
|
||||
patch_machine.stack.push(root_node.clone());
|
||||
}
|
||||
|
||||
Ok(()) // should actually never return from this, should be an error, rustc just cant see it
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ async-channel = "1.6.1"
|
|||
# futures-lite = "1.11.3"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
version = "0.3.50"
|
||||
features = [
|
||||
"Comment",
|
||||
"Document",
|
||||
|
@ -71,7 +71,7 @@ crate-type = ["cdylib", "rlib"]
|
|||
|
||||
|
||||
[dev-dependencies]
|
||||
uuid = { version = "0.8.2", features = ["v4"] }
|
||||
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
|
||||
|
||||
[[example]]
|
||||
name = "todomvc"
|
||||
|
|
|
@ -7,8 +7,6 @@ fn main() {
|
|||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(CustomA))
|
||||
}
|
||||
|
||||
use components::CustomB;
|
||||
|
||||
fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
|
||||
let (val, set_val) = use_state(&ctx, || "abcdef".to_string());
|
||||
|
||||
|
@ -24,7 +22,7 @@ fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
|
|||
"Lower"
|
||||
onclick: move |_| set_val(val.to_ascii_lowercase())
|
||||
}
|
||||
CustomB {
|
||||
components::CustomB {
|
||||
val: val.as_ref()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
fn main() {}
|
|
@ -0,0 +1,86 @@
|
|||
use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
|
||||
|
||||
fn main() {
|
||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||
console_error_panic_hook::set_once();
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App))
|
||||
}
|
||||
|
||||
fn App(ctx: Context, props: &()) -> DomTree {
|
||||
ctx.render(rsx! {
|
||||
main { class: "dark:bg-gray-800 bg-white relative h-screen"
|
||||
NavBar {}
|
||||
{(0..10).map(|f| rsx!{ Landing {} })}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn NavBar(ctx: Context, props: &()) -> DomTree {
|
||||
ctx.render(rsx!{
|
||||
header { class: "h-24 sm:h-32 flex items-center z-30 w-full"
|
||||
div { class: "container mx-auto px-6 flex items-center justify-between"
|
||||
div { class: "uppercase text-gray-800 dark:text-white font-black text-3xl"
|
||||
svg { focusable:"false" width:"100" height:"100" viewBox: "0 0 512 309"
|
||||
path { fill: "#000"
|
||||
d: "M120.81 80.561h96.568v7.676h-87.716v57.767h82.486v7.675h-82.486v63.423h88.722v7.675H120.81V80.561zm105.22 0h10.26l45.467 63.423L328.23 80.56L391.441 0l-103.85 150.65l53.515 74.127h-10.663l-48.686-67.462l-48.888 67.462h-10.461l53.917-74.128l-50.296-70.088zm118.898 7.676V80.56h110.048v7.676h-50.699v136.54h-8.852V88.237h-50.497zM0 80.56h11.065l152.58 228.323l-63.053-84.107L9.254 91.468l-.402 133.31H0V80.56zm454.084 134.224c-1.809 0-3.165-1.4-3.165-3.212c0-1.81 1.356-3.212 3.165-3.212c1.83 0 3.165 1.401 3.165 3.212c0 1.811-1.335 3.212-3.165 3.212zm8.698-8.45h4.737c.064 2.565 1.937 4.29 4.693 4.29c3.079 0 4.823-1.854 4.823-5.325v-21.99h4.823v22.011c0 6.252-3.617 9.853-9.603 9.853c-5.62 0-9.473-3.493-9.473-8.84zm25.384-.28h4.78c.409 2.953 3.294 4.828 7.45 4.828c3.875 0 6.717-2.005 6.717-4.764c0-2.371-1.809-3.794-5.921-4.764l-4.005-.97c-5.62-1.316-8.181-4.032-8.181-8.602c0-5.54 4.521-9.227 11.303-9.227c6.308 0 10.916 3.686 11.196 8.925h-4.694c-.452-2.867-2.95-4.657-6.567-4.657c-3.81 0-6.35 1.833-6.35 4.635c0 2.22 1.635 3.493 5.683 4.441l3.423.841c6.373 1.488 9 4.075 9 8.753c0 5.95-4.607 9.68-11.97 9.68c-6.89 0-11.52-3.558-11.864-9.12z"
|
||||
}
|
||||
}
|
||||
}
|
||||
div { class:"flex items-center"
|
||||
nav { class: "font-sen text-gray-800 dark:text-white uppercase text-lg lg:flex items-center hidden"
|
||||
a { href: "#", class:"py-2 px-6 flex text-indigo-500 border-b-2 border-indigo-500"
|
||||
"Home"
|
||||
}
|
||||
a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
|
||||
"Watch"
|
||||
}
|
||||
a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
|
||||
"Product"
|
||||
}
|
||||
a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
|
||||
"Contact"
|
||||
}
|
||||
a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
|
||||
"Career"
|
||||
}
|
||||
}
|
||||
button { class: "lg:hidden flex flex-col ml-4"
|
||||
span { class: "w-6 h-1 bg-gray-800 dark:bg-white mb-1" }
|
||||
span { class: "w-6 h-1 bg-gray-800 dark:bg-white mb-1" }
|
||||
span { class: "w-6 h-1 bg-gray-800 dark:bg-white mb-1" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn Landing(ctx: Context, props: &()) -> DomTree {
|
||||
ctx.render(rsx!{
|
||||
div { class: "bg-white dark:bg-gray-800 flex relative z-20 items-center"
|
||||
div { class: "container mx-auto px-6 flex flex-col justify-between items-center relative py-8"
|
||||
div { class: "flex flex-col"
|
||||
h1 { class: "font-light w-full uppercase text-center text-4xl sm:text-5xl dark:text-white text-gray-800"
|
||||
"The React Framework for Production"
|
||||
}
|
||||
h2{ class: "font-light max-w-2xl mx-auto w-full text-xl dark:text-white text-gray-500 text-center py-8"
|
||||
"Next.js gives you the best developer experience with all the features you need for production: \n
|
||||
hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and \n
|
||||
more. No config needed."
|
||||
}
|
||||
div { class: "flex items-center justify-center mt-4"
|
||||
a { href: "#" class: "uppercase py-2 px-4 bg-gray-800 border-2 border-transparent text-white text-md mr-4 hover:bg-gray-900"
|
||||
"Get started"
|
||||
}
|
||||
a{ href: "#" class: "uppercase py-2 px-4 bg-transparent border-2 border-gray-800 text-gray-800 dark:text-white hover:bg-gray-800 hover:text-white text-md"
|
||||
"Documentation"
|
||||
}
|
||||
}
|
||||
}
|
||||
div { class: "block w-full mx-auto mt-6 md:mt-0 relative"
|
||||
img { src: "/images/object/12.svg" class: "max-w-xs md:max-w-2xl m-auto" }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
//! Basic example that renders a simple domtree to the browser.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_web::*;
|
||||
fn main() {
|
||||
// Setup logging
|
||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||
console_error_panic_hook::set_once();
|
||||
// Run the app
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
let (contents, set_contents) = use_state(&ctx, || "asd");
|
||||
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
class: "flex items-center justify-center flex-col"
|
||||
div {
|
||||
class: "flex items-center justify-center"
|
||||
div {
|
||||
class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
|
||||
div { class: "font-bold text-xl", "Example cloud app" }
|
||||
div { class: "text-sm text-gray-500", "This is running in the cloud!!" }
|
||||
div {
|
||||
class: "flex flex-row items-center justify-center mt-6"
|
||||
div { class: "font-medium text-6xl", "100%" }
|
||||
}
|
||||
div {
|
||||
class: "flex flex-row justify-between mt-6"
|
||||
a {
|
||||
href: "https://www.dioxuslabs.com"
|
||||
class: "underline"
|
||||
"Made with dioxus"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
//! Basic example that renders a simple domtree to the browser.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_web::*;
|
||||
fn main() {
|
||||
// Setup logging
|
||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||
console_error_panic_hook::set_once();
|
||||
// Run the app
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
let (contents, set_contents) = use_state(&ctx, || "asd");
|
||||
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
class: "flex items-center justify-center flex-col"
|
||||
div {
|
||||
class: "flex items-center justify-center"
|
||||
div {
|
||||
class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
|
||||
div { class: "font-bold text-xl", "Example Web app" }
|
||||
div { class: "text-sm text-gray-500", "This is running in your browser!" }
|
||||
div {
|
||||
class: "flex flex-row items-center justify-center mt-6"
|
||||
div { class: "font-medium text-6xl", "100%" }
|
||||
}
|
||||
div {
|
||||
class: "flex flex-row justify-between mt-6"
|
||||
a {
|
||||
href: "https://www.dioxuslabs.com"
|
||||
class: "underline"
|
||||
"Made with dioxus"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
|
@ -1,125 +1,185 @@
|
|||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
|
||||
use dioxus::{events::on::MouseEvent, prelude::*};
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_web::WebsysRenderer;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
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");
|
||||
});
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||
}
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
lazy_static! {
|
||||
static ref DummyData: BTreeMap<usize, String> = {
|
||||
let vals = vec![
|
||||
"abc123", //
|
||||
"abc124", //
|
||||
"abc125", //
|
||||
"abc126", //
|
||||
"abc127", //
|
||||
"abc128", //
|
||||
"abc129", //
|
||||
"abc1210", //
|
||||
"abc1211", //
|
||||
"abc1212", //
|
||||
"abc1213", //
|
||||
"abc1214", //
|
||||
"abc1215", //
|
||||
"abc1216", //
|
||||
"abc1217", //
|
||||
"abc1218", //
|
||||
"abc1219", //
|
||||
"abc1220", //
|
||||
"abc1221", //
|
||||
"abc1222", //
|
||||
];
|
||||
vals.into_iter()
|
||||
.map(ToString::to_string)
|
||||
.enumerate()
|
||||
.collect()
|
||||
};
|
||||
static APP_STYLE: &'static str = include_str!("./todomvc/style.css");
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum FilterState {
|
||||
All,
|
||||
Active,
|
||||
Completed,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct TodoItem {
|
||||
pub id: uuid::Uuid,
|
||||
pub checked: bool,
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
static App: FC<()> = |ctx, _| {
|
||||
let items = use_state_new(&ctx, || DummyData.clone());
|
||||
let (draft, set_draft) = use_state(&ctx, || "".to_string());
|
||||
let (filter, set_filter) = use_state(&ctx, || FilterState::All);
|
||||
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||
// let (todos, set_todos) = use_state(&ctx, || BTreeMap::<String, TodoItem>::new());
|
||||
|
||||
// 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, v);
|
||||
})
|
||||
};
|
||||
|
||||
let elements = items.iter().map(|(k, v)| {
|
||||
rsx! {
|
||||
ListHelper {
|
||||
name: k,
|
||||
value: v
|
||||
onclick: move |_| {
|
||||
let key = k.clone();
|
||||
items.modify(move |m| { m.remove(&key); } )
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// let todos = use_ref(&ctx, || BTreeMap::<String, TodoItem>::new());
|
||||
let todos = use_state_new(&ctx, || BTreeMap::<uuid::Uuid, TodoItem>::new());
|
||||
|
||||
// let blah = "{draft}"
|
||||
ctx.render(rsx!(
|
||||
div {
|
||||
h1 {"Some list"}
|
||||
id: "app"
|
||||
style { "{APP_STYLE}" }
|
||||
|
||||
div {
|
||||
header {
|
||||
class: "header"
|
||||
h1 {"todos"}
|
||||
button {
|
||||
"Remove all"
|
||||
onclick: move |_| items.set(BTreeMap::new())
|
||||
"press me"
|
||||
onclick: move |evt| {
|
||||
let contents = draft.clone();
|
||||
todos.modify(|f| {
|
||||
let id = uuid::Uuid::new_v4();
|
||||
f.insert(id.clone(), TodoItem {
|
||||
id,
|
||||
checked: false,
|
||||
contents
|
||||
});
|
||||
})
|
||||
}
|
||||
button {
|
||||
"add new"
|
||||
onclick: {add_new}
|
||||
}
|
||||
input {
|
||||
class: "new-todo"
|
||||
placeholder: "What needs to be done?"
|
||||
// value: "{draft}"
|
||||
oninput: move |evt| set_draft(evt.value)
|
||||
}
|
||||
}
|
||||
|
||||
{ // list
|
||||
todos
|
||||
.iter()
|
||||
.filter(|(id, item)| match filter {
|
||||
FilterState::All => true,
|
||||
FilterState::Active => !item.checked,
|
||||
FilterState::Completed => item.checked,
|
||||
})
|
||||
.map(|(id, todo)| {
|
||||
rsx!{
|
||||
li {
|
||||
"{todo.contents}"
|
||||
input {
|
||||
class: "toggle"
|
||||
type: "checkbox"
|
||||
"{todo.checked}"
|
||||
}
|
||||
// {is_editing.then(|| rsx!(
|
||||
// input {
|
||||
// value: "{contents}"
|
||||
// }
|
||||
// ))}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// filter toggle (show only if the list isn't empty)
|
||||
{(!todos.is_empty()).then(||
|
||||
rsx!{
|
||||
footer {
|
||||
span {
|
||||
strong {"10"}
|
||||
span {"0 items left"}
|
||||
}
|
||||
ul {
|
||||
{elements}
|
||||
class: "filters"
|
||||
{[
|
||||
("All", "", FilterState::All),
|
||||
("Active", "active", FilterState::Active),
|
||||
("Completed", "completed", FilterState::Completed),
|
||||
]
|
||||
.iter()
|
||||
.map(|(name, path, filter)| {
|
||||
rsx!(
|
||||
li {
|
||||
class: "{name}"
|
||||
a {
|
||||
href: "{path}"
|
||||
onclick: move |_| set_filter(filter.clone())
|
||||
"{name}"
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}}
|
||||
}
|
||||
}
|
||||
)}
|
||||
}
|
||||
|
||||
|
||||
footer {
|
||||
class: "info"
|
||||
p {"Double-click to edit a todo"}
|
||||
p {
|
||||
"Created by "
|
||||
a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" }
|
||||
}
|
||||
p {
|
||||
"Part of "
|
||||
a { "TodoMVC", href: "http://todomvc.com" }
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
};
|
||||
|
||||
#[derive(Props)]
|
||||
struct ListProps<'a, F: Fn(MouseEvent) + 'a> {
|
||||
name: &'a usize,
|
||||
value: &'a str,
|
||||
onclick: F,
|
||||
}
|
||||
pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
|
||||
// let reducer = recoil::use_callback(&ctx, || ());
|
||||
// let items_left = recoil::use_atom_family(&ctx, &TODOS, uuid::Uuid::new_v4());
|
||||
|
||||
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! {
|
||||
let toggles = [
|
||||
("All", "", FilterState::All),
|
||||
("Active", "active", FilterState::Active),
|
||||
("Completed", "completed", FilterState::Completed),
|
||||
]
|
||||
.iter()
|
||||
.map(|(name, path, _filter)| {
|
||||
rsx!(
|
||||
li {
|
||||
class: "flex items-center text-xl"
|
||||
key: "{k}"
|
||||
span { "{k}: {v}" }
|
||||
button {
|
||||
"__ Remove"
|
||||
onclick: {&props.onclick}
|
||||
class: "{name}"
|
||||
a {
|
||||
href: "{path}"
|
||||
// onclick: move |_| reducer.set_filter(&filter)
|
||||
"{name}"
|
||||
}
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
// todo
|
||||
let item_text = "";
|
||||
let items_left = "";
|
||||
|
||||
ctx.render(rsx! {
|
||||
footer {
|
||||
span {
|
||||
strong {"{items_left}"}
|
||||
span {"{item_text} left"}
|
||||
}
|
||||
ul {
|
||||
class: "filters"
|
||||
{toggles}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -13,16 +13,14 @@ pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
|
|||
]
|
||||
.iter()
|
||||
.map(|(name, path, filter)| {
|
||||
rsx!(
|
||||
li {
|
||||
rsx!(li {
|
||||
class: "{name}"
|
||||
a {
|
||||
"{name}"
|
||||
href: "{path}"
|
||||
onclick: move |_| reducer.set_filter(&filter)
|
||||
"{name}"
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
// todo
|
||||
|
|
|
@ -21,7 +21,7 @@ pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
|||
}
|
||||
{is_editing.then(|| rsx!(
|
||||
input {
|
||||
value: "{contents}"
|
||||
value: "{todo.contents}"
|
||||
}
|
||||
))}
|
||||
}
|
||||
|
|
|
@ -101,13 +101,6 @@ pub struct TodoEntryProps {
|
|||
item: Rc<TodoItem>,
|
||||
}
|
||||
|
||||
mod mac {
|
||||
#[macro_export]
|
||||
macro_rules! TodoEntry {
|
||||
() => {};
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
||||
// #[inline_props]
|
||||
pub fn TodoEntry(
|
||||
|
|
|
@ -115,6 +115,7 @@ pub struct TodoEntryProps {
|
|||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
|
||||
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||
let todo = use_atom_family(&ctx, &TODOS, props.id);
|
||||
let contents = "";
|
||||
|
||||
ctx.render(rsx! (
|
||||
li {
|
||||
|
|
|
@ -17,7 +17,7 @@ fn main() {
|
|||
<div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
|
||||
// Title
|
||||
<div class="font-bold text-xl">
|
||||
"Jon's awesome site!!11"
|
||||
"Jon's awesome site!!"
|
||||
</div>
|
||||
|
||||
// Subtext / description
|
||||
|
@ -45,4 +45,3 @@ fn main() {
|
|||
})
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -542,8 +542,14 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
|
|||
|
||||
"change" | "input" | "invalid" | "reset" | "submit" => {
|
||||
// is a special react events
|
||||
// let evt: web_sys::FormEvent = event.clone().dyn_into().unwrap();
|
||||
todo!()
|
||||
let evt: web_sys::InputEvent = event.clone().dyn_into().expect("wrong event type");
|
||||
let value: Option<String> = (&evt).data();
|
||||
let value = value.unwrap_or_default();
|
||||
// let value = (&evt).data().expect("No data to unwrap");
|
||||
|
||||
// todo - this needs to be a "controlled" event
|
||||
// these events won't carry the right data with them
|
||||
VirtualEvent::FormEvent(FormEvent { value })
|
||||
}
|
||||
|
||||
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
use dioxus::prelude::Properties;
|
||||
use fxhash::FxHashMap;
|
||||
use web_sys::{window, Document, Element, Event, Node};
|
||||
// use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
|
||||
use dioxus::virtual_dom::VirtualDom;
|
||||
pub use dioxus_core as dioxus;
|
||||
use dioxus_core::{events::EventTrigger, prelude::FC};
|
||||
// use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
|
||||
pub use dioxus_core::prelude;
|
||||
pub mod interpreter;
|
||||
|
|
|
@ -11,7 +11,7 @@ license = "MIT/Apache-2.0"
|
|||
[dependencies]
|
||||
# web-view = { git = "https://github.com/Boscop/web-view" }
|
||||
web-view = "0.7.3"
|
||||
dioxus-core = { path = "../core", version = "0.1.2" }
|
||||
dioxus-core = { path = "../core", version = "0.1.2", features = ["serde"] }
|
||||
anyhow = "1.0.38"
|
||||
argh = "0.1.4"
|
||||
serde = "1.0.120"
|
||||
|
|
|
@ -4,50 +4,43 @@ use dioxus_core::prelude::*;
|
|||
|
||||
fn main() {
|
||||
dioxus_webview::launch(
|
||||
// Customize the webview
|
||||
|builder| {
|
||||
builder
|
||||
.title("Test Dioxus App")
|
||||
.size(320, 480)
|
||||
.resizable(true)
|
||||
.resizable(false)
|
||||
.debug(true)
|
||||
},
|
||||
// Props
|
||||
(),
|
||||
// Draw the root component
|
||||
Example,
|
||||
)
|
||||
.expect("Webview finished");
|
||||
}
|
||||
|
||||
static Example: FC<()> = |ctx, _props| {
|
||||
ctx.render(html! {
|
||||
<div>
|
||||
<div class="flex items-center justify-center flex-col">
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
|
||||
// Title
|
||||
<div class="font-bold text-xl"> "Jon's awesome site!!11" </div>
|
||||
|
||||
// Subtext / description
|
||||
<div class="text-sm text-gray-500"> "He worked so hard on it :)" </div>
|
||||
|
||||
<div class="flex flex-row items-center justify-center mt-6">
|
||||
// Main number
|
||||
<div class="font-medium text-6xl">
|
||||
"1337"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// Try another
|
||||
<div class="flex flex-row justify-between mt-6">
|
||||
<a href="http://localhost:8080/fib/{}" class="underline">
|
||||
"Legit made my own React"
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
ctx.render(rsx! {
|
||||
div {
|
||||
class: "flex items-center justify-center flex-col"
|
||||
div {
|
||||
class: "flex items-center justify-center"
|
||||
div {
|
||||
class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
|
||||
div { class: "font-bold text-xl", "Example desktop app" }
|
||||
div { class: "text-sm text-gray-500", "This is running natively" }
|
||||
div {
|
||||
class: "flex flex-row items-center justify-center mt-6"
|
||||
div { class: "font-medium text-6xl", "100%" }
|
||||
}
|
||||
div {
|
||||
class: "flex flex-row justify-between mt-6"
|
||||
a {
|
||||
href: "https://www.dioxuslabs.com"
|
||||
class: "underline"
|
||||
"Made with dioxus"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue