diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index d1351712a..718922a57 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -1,7 +1,10 @@ name: macOS tests on: - push: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: + - master paths: - packages/** - examples/** @@ -9,10 +12,6 @@ on: - .github/** - lib.rs - Cargo.toml - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - branches: - - master jobs: test: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3e1ec15c0..49ed00901 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,7 +1,10 @@ name: Rust CI on: - push: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: + - master paths: - packages/** - examples/** @@ -9,10 +12,6 @@ on: - .github/** - lib.rs - Cargo.toml - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - branches: - - master jobs: check: @@ -34,6 +33,7 @@ jobs: command: check test: + if: github.event.pull_request.draft == false name: Test Suite runs-on: ubuntu-latest steps: @@ -55,6 +55,7 @@ jobs: args: tests fmt: + if: github.event.pull_request.draft == false name: Rustfmt runs-on: ubuntu-latest steps: @@ -72,6 +73,7 @@ jobs: args: --all -- --check clippy: + if: github.event.pull_request.draft == false name: Clippy runs-on: ubuntu-latest steps: @@ -90,21 +92,23 @@ jobs: command: clippy args: -- -D warnings - coverage: - name: Coverage - runs-on: ubuntu-latest - container: - image: xd009642/tarpaulin:develop-nightly - options: --security-opt seccomp=unconfined - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - name: Generate code coverage - run: | - apt-get update &&\ - apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\ - cargo +nightly tarpaulin --verbose --tests --all-features --workspace --timeout 120 --out Xml - - name: Upload to codecov.io - uses: codecov/codecov-action@v2 - with: - fail_ci_if_error: false + # Coverage is disabled until we can fix it + # coverage: + # name: Coverage + # runs-on: ubuntu-latest + # container: + # image: xd009642/tarpaulin:develop-nightly + # options: --security-opt seccomp=unconfined + # steps: + # - name: Checkout repository + # uses: actions/checkout@v2 + # - name: Generate code coverage + # run: | + # apt-get update &&\ + # apt-get install build-essential &&\ + # apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\ + # cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml + # - name: Upload to codecov.io + # uses: codecov/codecov-action@v2 + # with: + # fail_ci_if_error: false diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 518da37b3..75953807a 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,7 +1,10 @@ name: windows on: - push: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: + - master paths: - packages/** - examples/** @@ -9,10 +12,6 @@ on: - .github/** - lib.rs - Cargo.toml - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - branches: - - master jobs: test: diff --git a/Cargo.toml b/Cargo.toml index 3f0d50187..d4e41af61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,19 +2,22 @@ name = "dioxus" version = "0.1.8" authors = ["Jonathan Kelley"] -edition = "2018" +edition = "2021" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" license = "MIT OR Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "wasm"] +rust-version = "1.56.0" [dependencies] dioxus-core = { path = "./packages/core", version = "^0.1.9" } dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true } dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true } dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true } +dioxus-rsx = { path = "./packages/rsx", optional = true } +fermi = { path = "./packages/fermi", version = "^0.1.0", optional = true } dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true } dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", optional = true } @@ -28,7 +31,7 @@ dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.0.0", o [features] default = ["macro", "hooks", "html"] -macro = ["dioxus-core-macro"] +macro = ["dioxus-core-macro", "dioxus-rsx"] hooks = ["dioxus-hooks"] html = ["dioxus-html"] ssr = ["dioxus-ssr"] @@ -36,24 +39,11 @@ web = ["dioxus-web"] desktop = ["dioxus-desktop"] router = ["dioxus-router"] -devtool = ["dioxus-desktop/devtool"] -fullscreen = ["dioxus-desktop/fullscreen"] -transparent = ["dioxus-desktop/transparent"] - -tray = ["dioxus-desktop/tray"] -ayatana = ["dioxus-desktop/ayatana"] - -# "dioxus-router/web" -# "dioxus-router/desktop" -# desktop = ["dioxus-desktop", "dioxus-router/desktop"] -# mobile = ["dioxus-mobile"] -# liveview = ["dioxus-liveview"] - - [workspace] members = [ "packages/core", "packages/core-macro", + "packages/rsx", "packages/html", "packages/hooks", "packages/web", @@ -61,6 +51,7 @@ members = [ "packages/desktop", "packages/mobile", "packages/interpreter", + "packages/fermi", ] [dev-dependencies] @@ -75,4 +66,4 @@ serde_json = "1.0.79" rand = { version = "0.8.4", features = ["small_rng"] } tokio = { version = "1.16.1", features = ["full"] } reqwest = { version = "0.11.9", features = ["json"] } -dioxus = { path = ".", features = ["desktop", "ssr", "router"] } +dioxus = { path = ".", features = ["desktop", "ssr", "router", "fermi"] } diff --git a/docs/fermi/.gitignore b/docs/fermi/.gitignore new file mode 100644 index 000000000..7585238ef --- /dev/null +++ b/docs/fermi/.gitignore @@ -0,0 +1 @@ +book diff --git a/docs/fermi/book.toml b/docs/fermi/book.toml new file mode 100644 index 000000000..dd6d2eacb --- /dev/null +++ b/docs/fermi/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Jonathan Kelley"] +language = "en" +multilingual = false +src = "src" +title = "Fermi Guide" diff --git a/docs/fermi/src/SUMMARY.md b/docs/fermi/src/SUMMARY.md new file mode 100644 index 000000000..7390c8289 --- /dev/null +++ b/docs/fermi/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +- [Chapter 1](./chapter_1.md) diff --git a/docs/fermi/src/chapter_1.md b/docs/fermi/src/chapter_1.md new file mode 100644 index 000000000..b743fda35 --- /dev/null +++ b/docs/fermi/src/chapter_1.md @@ -0,0 +1 @@ +# Chapter 1 diff --git a/docs/reference/src/platforms/ssr.md b/docs/reference/src/platforms/ssr.md index aef223dfd..09e32ba43 100644 --- a/docs/reference/src/platforms/ssr.md +++ b/docs/reference/src/platforms/ssr.md @@ -2,11 +2,8 @@ The Dioxus VirtualDom can be rendered to a string by traversing the Element Tree. This is implemented in the `ssr` crate where your Dioxus app can be directly rendered to HTML to be served by a web server. - - ## Setup - If you just want to render `rsx!` or a VirtualDom to HTML, check out the API docs. It's pretty simple: ```rust @@ -19,8 +16,7 @@ println!("{}", dioxus::ssr::render_vdom(&vdom)); println!( "{}", dioxus::ssr::render_lazy(rsx! { h1 { "Hello, world!" } } ); ``` - -However, for this guide, we're going to show how to use Dioxus SSR with `Axum`. +However, for this guide, we're going to show how to use Dioxus SSR with `Axum`. Make sure you have Rust and Cargo installed, and then create a new project: @@ -29,15 +25,16 @@ $ cargo new --bin demo $ cd app ``` -Add Dioxus with the `desktop` feature: +Add Dioxus with the `ssr` feature: ```shell $ cargo add dioxus --features ssr ``` Next, add all the Axum dependencies. This will be different if you're using a different Web Framework + ``` -$ cargo add dioxus tokio --features full +$ cargo add tokio --features full $ cargo add axum ``` @@ -45,12 +42,11 @@ Your dependencies should look roughly like this: ```toml [dependencies] -axum = "0.4.3" +axum = "0.4.5" dioxus = { version = "*", features = ["ssr"] } tokio = { version = "1.15.0", features = ["full"] } ``` - Now, setup your Axum app to respond on an endpoint. ```rust @@ -63,7 +59,11 @@ async fn main() { println!("listening on http://{}", addr); axum::Server::bind(&addr) - .serve(Router::new().route("/", get(app_endpoint))) + .serve( + Router::new() + .route("/", get(app_endpoint)) + .into_make_service(), + ) .await .unwrap(); } @@ -88,14 +88,14 @@ async fn app_endpoint() -> Html { } let mut app = VirtualDom::new(app); let _ = app.rebuild(); - + Html(dioxus::ssr::render_vdom(&app)) } ``` And that's it! -> You might notice that you cannot hold the VirtualDom across an await point. Dioxus is currently not ThreadSafe, so it *must* remain on the thread it started. We are working on loosening this requirement. +> You might notice that you cannot hold the VirtualDom across an await point. Dioxus is currently not ThreadSafe, so it _must_ remain on the thread it started. We are working on loosening this requirement. ## Future Steps diff --git a/examples/fermi.rs b/examples/fermi.rs new file mode 100644 index 000000000..2be01d019 --- /dev/null +++ b/examples/fermi.rs @@ -0,0 +1,30 @@ +#![allow(non_snake_case)] + +use dioxus::prelude::*; +use fermi::prelude::*; + +fn main() { + dioxus::desktop::launch(app) +} + +static NAME: Atom = |_| "world".to_string(); + +fn app(cx: Scope) -> Element { + let name = use_read(&cx, NAME); + + cx.render(rsx! { + div { "hello {name}!" } + Child {} + }) +} + +fn Child(cx: Scope) -> Element { + let set_name = use_set(&cx, NAME); + + cx.render(rsx! { + button { + onclick: move |_| set_name("dioxus".to_string()), + "reset name" + } + }) +} diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index 8026bcdaf..ed5d34e53 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -15,6 +15,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] proc-macro = true [dependencies] +dioxus-rsx = { path = "../rsx" } proc-macro-error = "1.0.4" proc-macro2 = { version = "1.0.6" } quote = "1.0" diff --git a/packages/core-macro/src/htm.rs b/packages/core-macro/src/htm.rs deleted file mode 100644 index ca53096f6..000000000 --- a/packages/core-macro/src/htm.rs +++ /dev/null @@ -1,443 +0,0 @@ -//! -//! TODO: -//! - [ ] Support for VComponents -//! - [ ] Support for inline format in text -//! - [ ] Support for expressions in attribute positions -//! - [ ] Support for iterators -//! - [ ] support for inline html! -//! -//! -//! -//! -//! -//! -//! - -use { - proc_macro2::TokenStream as TokenStream2, - quote::{quote, ToTokens, TokenStreamExt}, - syn::{ - ext::IdentExt, - parse::{Parse, ParseStream}, - token, Error, Expr, ExprClosure, Ident, LitStr, Result, Token, - }, -}; - -// ============================================== -// Parse any stream coming from the html! macro -// ============================================== -pub struct HtmlRender { - kind: NodeOrList, -} - -impl Parse for HtmlRender { - fn parse(input: ParseStream) -> Result { - if input.peek(LitStr) { - return input.parse::()?.parse::(); - } - - // let __cx: Ident = s.parse()?; - // s.parse::()?; - // if elements are in an array, return a bumpalo::collections::Vec rather than a Node. - let kind = if input.peek(token::Bracket) { - let nodes_toks; - syn::bracketed!(nodes_toks in input); - let mut nodes: Vec> = vec![nodes_toks.parse()?]; - while nodes_toks.peek(Token![,]) { - nodes_toks.parse::()?; - nodes.push(nodes_toks.parse()?); - } - NodeOrList::List(NodeList(nodes)) - } else { - NodeOrList::Node(input.parse()?) - }; - Ok(HtmlRender { kind }) - } -} - -impl ToTokens for HtmlRender { - fn to_tokens(&self, out_tokens: &mut TokenStream2) { - let new_toks = ToToksCtx::new(&self.kind).to_token_stream(); - - // create a lazy tree that accepts a bump allocator - let final_tokens = quote! { - dioxus::prelude::LazyNodes::new(move |__cx| { - let bump = __cx.bump(); - - #new_toks - }) - }; - - final_tokens.to_tokens(out_tokens); - } -} - -/// ============================================= -/// Parse any child as a node or list of nodes -/// ============================================= -/// - [ ] Allow iterators -/// -/// -enum NodeOrList { - Node(Node), - List(NodeList), -} - -impl ToTokens for ToToksCtx<&NodeOrList> { - fn to_tokens(&self, tokens: &mut TokenStream2) { - match self.inner { - NodeOrList::Node(node) => self.recurse(node).to_tokens(tokens), - NodeOrList::List(list) => self.recurse(list).to_tokens(tokens), - } - } -} - -struct NodeList(Vec>); - -impl ToTokens for ToToksCtx<&NodeList> { - fn to_tokens(&self, tokens: &mut TokenStream2) { - let nodes = self.inner.0.iter().map(|node| self.recurse(node)); - tokens.append_all(quote! { - dioxus::bumpalo::vec![in bump; - #(#nodes),* - ] - }); - } -} - -enum Node { - Element(Element), - Text(TextNode), -} - -impl ToTokens for ToToksCtx<&Node> { - fn to_tokens(&self, tokens: &mut TokenStream2) { - match &self.inner { - Node::Element(el) => self.recurse(el).to_tokens(tokens), - Node::Text(txt) => self.recurse(txt).to_tokens(tokens), - } - } -} - -impl Node { - fn _peek(s: ParseStream) -> bool { - (s.peek(Token![<]) && !s.peek2(Token![/])) || s.peek(token::Brace) || s.peek(LitStr) - } -} - -impl Parse for Node { - fn parse(s: ParseStream) -> Result { - Ok(if s.peek(Token![<]) { - Node::Element(s.parse()?) - } else { - Node::Text(s.parse()?) - }) - } -} - -/// ======================================= -/// Parse the VNode::Element type -/// ======================================= -/// - [ ] Allow VComponent -/// -/// -struct Element { - name: Ident, - attrs: Vec, - children: MaybeExpr>, -} - -impl ToTokens for ToToksCtx<&Element> { - fn to_tokens(&self, tokens: &mut TokenStream2) { - // let __cx = self.__cx; - let name = &self.inner.name; - // let name = &self.inner.name.to_string(); - tokens.append_all(quote! { - __cx.element(dioxus_elements::#name) - // dioxus::builder::ElementBuilder::new( #name) - }); - for attr in self.inner.attrs.iter() { - self.recurse(attr).to_tokens(tokens); - } - - // if is_valid_svg_tag(&name.to_string()) { - // tokens.append_all(quote! { - // .namespace(Some("http://www.w3.org/2000/svg")) - // }); - // } - - match &self.inner.children { - MaybeExpr::Expr(expr) => tokens.append_all(quote! { - .children(#expr) - }), - MaybeExpr::Literal(nodes) => { - let mut children = nodes.iter(); - if let Some(child) = children.next() { - let mut inner_toks = TokenStream2::new(); - self.recurse(child).to_tokens(&mut inner_toks); - for child in children { - quote!(,).to_tokens(&mut inner_toks); - self.recurse(child).to_tokens(&mut inner_toks); - } - tokens.append_all(quote! { - .children([#inner_toks]) - }); - } - } - } - tokens.append_all(quote! { - .finish() - }); - } -} - -impl Parse for Element { - fn parse(s: ParseStream) -> Result { - s.parse::()?; - let name = Ident::parse_any(s)?; - let mut attrs = vec![]; - let _children: Vec = vec![]; - - // keep looking for attributes - while !s.peek(Token![>]) { - // self-closing - if s.peek(Token![/]) { - s.parse::()?; - s.parse::]>()?; - return Ok(Self { - name, - attrs, - children: MaybeExpr::Literal(vec![]), - }); - } - attrs.push(s.parse()?); - } - s.parse::]>()?; - - // Contents of an element can either be a brace (in which case we just copy verbatim), or a - // sequence of nodes. - let children = if s.peek(token::Brace) { - // expr - let content; - syn::braced!(content in s); - MaybeExpr::Expr(content.parse()?) - } else { - // nodes - let mut children = vec![]; - while !(s.peek(Token![<]) && s.peek2(Token![/])) { - children.push(s.parse()?); - } - MaybeExpr::Literal(children) - }; - - // closing element - s.parse::()?; - s.parse::()?; - let close = Ident::parse_any(s)?; - if close != name { - return Err(Error::new_spanned( - close, - "closing element does not match opening", - )); - } - s.parse::]>()?; - - Ok(Self { - name, - attrs, - children, - }) - } -} - -/// ======================================= -/// Parse a VElement's Attributes -/// ======================================= -/// - [ ] Allow expressions as attribute -/// -/// -struct Attr { - name: Ident, - ty: AttrType, -} - -impl Parse for Attr { - fn parse(s: ParseStream) -> Result { - let mut name = Ident::parse_any(s)?; - let name_str = name.to_string(); - s.parse::()?; - - // Check if this is an event handler - // If so, parse into literal tokens - let ty = if name_str.starts_with("on") { - // remove the "on" bit - name = Ident::new(name_str.trim_start_matches("on"), name.span()); - let content; - syn::braced!(content in s); - // AttrType::Value(content.parse()?) - AttrType::Event(content.parse()?) - // AttrType::Event(content.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 - MaybeExpr::Expr(outer.parse()?) - // } - } else { - s.parse()? - }; - AttrType::Value(lit_str) - }; - Ok(Attr { name, ty }) - } -} - -impl ToTokens for ToToksCtx<&Attr> { - fn to_tokens(&self, tokens: &mut TokenStream2) { - let name = self.inner.name.to_string(); - let _attr_stream = TokenStream2::new(); - match &self.inner.ty { - AttrType::Value(value) => { - let value = self.recurse(value); - if name == "xmlns" { - tokens.append_all(quote! { - .namespace(Some(#value)) - }); - } else { - tokens.append_all(quote! { - .attr(#name, format_args_f!(#value)) - }); - } - } - AttrType::Event(event) => { - tokens.append_all(quote! { - .on(#name, #event) - }); - } - } - } -} - -enum AttrType { - Value(MaybeExpr), - Event(ExprClosure), - // todo Bool(MaybeExpr) -} - -/// ======================================= -/// Parse just plain text -/// ======================================= -/// - [ ] Perform formatting automatically -/// -/// -struct TextNode(MaybeExpr); - -impl Parse for TextNode { - fn parse(s: ParseStream) -> Result { - Ok(Self(s.parse()?)) - } -} - -impl ToTokens for ToToksCtx<&TextNode> { - fn to_tokens(&self, tokens: &mut TokenStream2) { - let mut token_stream = TokenStream2::new(); - self.recurse(&self.inner.0).to_tokens(&mut token_stream); - tokens.append_all(quote! { - __cx.text(format_args_f!(#token_stream)) - }); - } -} - -#[allow(clippy::large_enum_variant)] -enum MaybeExpr { - Literal(T), - Expr(Expr), -} - -impl Parse for MaybeExpr { - fn parse(s: ParseStream) -> Result { - if s.peek(token::Brace) { - let content; - syn::braced!(content in s); - Ok(MaybeExpr::Expr(content.parse()?)) - } else { - Ok(MaybeExpr::Literal(s.parse()?)) - } - } -} - -impl<'a, T> ToTokens for ToToksCtx<&'a MaybeExpr> -where - T: 'a, - ToToksCtx<&'a T>: ToTokens, -{ - fn to_tokens(&self, tokens: &mut TokenStream2) { - match &self.inner { - MaybeExpr::Literal(v) => self.recurse(v).to_tokens(tokens), - MaybeExpr::Expr(expr) => expr.to_tokens(tokens), - } - } -} - -/// ToTokens context -struct ToToksCtx { - inner: T, -} - -impl<'a, T> ToToksCtx { - fn new(inner: T) -> Self { - ToToksCtx { inner } - } - - fn recurse(&self, inner: U) -> ToToksCtx { - ToToksCtx { inner } - } -} - -impl ToTokens for ToToksCtx<&LitStr> { - fn to_tokens(&self, tokens: &mut TokenStream2) { - self.inner.to_tokens(tokens) - } -} - -#[cfg(test)] -mod test { - fn parse(input: &str) -> super::Result { - syn::parse_str(input) - } - - #[test] - fn div() { - parse("bump,
").unwrap(); - } - - #[test] - fn nested() { - parse("bump,
\"text\"
").unwrap(); - } - - #[test] - fn complex() { - parse( - "bump, -
{contact_details}
- ", - ) - .unwrap(); - } -} diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 7099311ce..96ea0ce87 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -2,11 +2,9 @@ use proc_macro::TokenStream; use quote::ToTokens; use syn::parse_macro_input; -pub(crate) mod ifmt; -pub(crate) mod inlineprops; -pub(crate) mod props; -pub(crate) mod router; -pub(crate) mod rsx; +mod ifmt; +mod inlineprops; +mod props; #[proc_macro] pub fn format_args_f(input: TokenStream) -> TokenStream { @@ -180,42 +178,12 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token #[proc_macro_error::proc_macro_error] #[proc_macro] pub fn rsx(s: TokenStream) -> TokenStream { - match syn::parse::(s) { + match syn::parse::(s) { Err(err) => err.to_compile_error().into(), Ok(stream) => stream.to_token_stream().into(), } } -/// Derive macro used to mark an enum as Routable. -/// -/// This macro can only be used on enums. Every varient of the macro needs to be marked -/// with the `at` attribute to specify the URL of the route. It generates an implementation of -/// `yew_router::Routable` trait and `const`s for the routes passed which are used with `Route` -/// component. -/// -/// # Example -/// -/// ``` -/// # use yew_router::Routable; -/// #[derive(Debug, Clone, Copy, PartialEq, Routable)] -/// enum Routes { -/// #[at("/")] -/// Home, -/// #[at("/secure")] -/// Secure, -/// #[at("/profile/{id}")] -/// Profile(u32), -/// #[at("/404")] -/// NotFound, -/// } -/// ``` -#[proc_macro_derive(Routable, attributes(at, not_found))] -pub fn routable_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - use router::{routable_derive_impl, Routable}; - let input = parse_macro_input!(input as Routable); - routable_derive_impl(input).into() -} - /// Derive props for a component within the component definition. /// /// This macro provides a simple transformation from `Scope<{}>` to `Scope

`, diff --git a/packages/core-macro/src/router.rs b/packages/core-macro/src/router.rs deleted file mode 100644 index 3edad75e3..000000000 --- a/packages/core-macro/src/router.rs +++ /dev/null @@ -1,214 +0,0 @@ -#![allow(dead_code)] - -use proc_macro2::TokenStream; -use quote::quote; -use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; -use syn::spanned::Spanned; -use syn::{Data, DeriveInput, Fields, Ident, LitStr, Variant}; - -const AT_ATTR_IDENT: &str = "at"; -const NOT_FOUND_ATTR_IDENT: &str = "not_found"; - -pub struct Routable { - ident: Ident, - ats: Vec, - variants: Punctuated, - not_found_route: Option, -} - -impl Parse for Routable { - fn parse(input: ParseStream) -> syn::Result { - let DeriveInput { ident, data, .. } = input.parse()?; - - let data = match data { - Data::Enum(data) => data, - Data::Struct(s) => { - return Err(syn::Error::new( - s.struct_token.span(), - "expected enum, found struct", - )) - } - Data::Union(u) => { - return Err(syn::Error::new( - u.union_token.span(), - "expected enum, found union", - )) - } - }; - - let (not_found_route, ats) = parse_variants_attributes(&data.variants)?; - - Ok(Self { - ident, - variants: data.variants, - ats, - not_found_route, - }) - } -} - -fn parse_variants_attributes( - variants: &Punctuated, -) -> syn::Result<(Option, Vec)> { - let mut not_founds = vec![]; - let mut ats: Vec = vec![]; - - let mut not_found_attrs = vec![]; - - for variant in variants.iter() { - if let Fields::Unnamed(ref field) = variant.fields { - return Err(syn::Error::new( - field.span(), - "only named fields are supported", - )); - } - - let attrs = &variant.attrs; - let at_attrs = attrs - .iter() - .filter(|attr| attr.path.is_ident(AT_ATTR_IDENT)) - .collect::>(); - - let attr = match at_attrs.len() { - 1 => *at_attrs.first().unwrap(), - 0 => { - return Err(syn::Error::new( - variant.span(), - format!( - "{} attribute must be present on every variant", - AT_ATTR_IDENT - ), - )) - } - _ => { - return Err(syn::Error::new_spanned( - quote! { #(#at_attrs)* }, - format!("only one {} attribute must be present", AT_ATTR_IDENT), - )) - } - }; - - let lit = attr.parse_args::()?; - ats.push(lit); - - for attr in attrs.iter() { - if attr.path.is_ident(NOT_FOUND_ATTR_IDENT) { - not_found_attrs.push(attr); - not_founds.push(variant.ident.clone()) - } - } - } - - if not_founds.len() > 1 { - return Err(syn::Error::new_spanned( - quote! { #(#not_found_attrs)* }, - format!("there can only be one {}", NOT_FOUND_ATTR_IDENT), - )); - } - - Ok((not_founds.into_iter().next(), ats)) -} - -impl Routable { - // fn build_from_path(&self) -> TokenStream { - // let from_path_matches = self.variants.iter().enumerate().map(|(i, variant)| { - // let ident = &variant.ident; - // let right = match &variant.fields { - // Fields::Unit => quote! { Self::#ident }, - // Fields::Named(field) => { - // let fields = field.named.iter().map(|it| { - // //named fields have idents - // it.ident.as_ref().unwrap() - // }); - // quote! { Self::#ident { #(#fields: params.get(stringify!(#fields))?.parse().ok()?,)* } } - // } - // Fields::Unnamed(_) => unreachable!(), // already checked - // }; - - // let left = self.ats.get(i).unwrap(); - // quote! { - // #left => ::std::option::Option::Some(#right) - // } - // }); - - // quote! { - // fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option { - // match path { - // #(#from_path_matches),*, - // _ => ::std::option::Option::None, - // } - // } - // } - // } - - // fn build_to_path(&self) -> TokenStream { - // let to_path_matches = self.variants.iter().enumerate().map(|(i, variant)| { - // let ident = &variant.ident; - // let mut right = self.ats.get(i).unwrap().value(); - - // match &variant.fields { - // Fields::Unit => quote! { Self::#ident => ::std::string::ToString::to_string(#right) }, - // Fields::Named(field) => { - // let fields = field - // .named - // .iter() - // .map(|it| it.ident.as_ref().unwrap()) - // .collect::>(); - - // for field in fields.iter() { - // // :param -> {param} - // // so we can pass it to `format!("...", param)` - // right = right.replace(&format!(":{}", field), &format!("{{{}}}", field)) - // } - - // quote! { - // Self::#ident { #(#fields),* } => ::std::format!(#right, #(#fields = #fields),*) - // } - // } - // Fields::Unnamed(_) => unreachable!(), // already checked - // } - // }); - - // quote! { - // fn to_path(&self) -> ::std::string::String { - // match self { - // #(#to_path_matches),*, - // } - // } - // } - // } -} - -pub fn routable_derive_impl(input: Routable) -> TokenStream { - let Routable { - // ats, - // not_found_route, - // ident, - .. - } = &input; - - // let from_path = input.build_from_path(); - // let to_path = input.build_to_path(); - - quote! { - // #[automatically_derived] - // impl ::dioxus::router::Routable for #ident { - - // fn recognize(pathname: &str) -> ::std::option::Option { - // todo!() - // // ::std::thread_local! { - // // static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>(); - // // } - // // let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname)); - // // { - // // let route = ::std::clone::Clone::clone(&route); - // // #cache_thread_local_ident.with(move |val| { - // // *val.borrow_mut() = route; - // // }); - // // } - // // route - // } - // } - } -} diff --git a/packages/core-macro/src/rsxtemplate.rs b/packages/core-macro/src/rsxtemplate.rs deleted file mode 100644 index 9222d7117..000000000 --- a/packages/core-macro/src/rsxtemplate.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::{rsx::RsxBody, util::is_valid_svg_tag}; - -use { - proc_macro::TokenStream, - proc_macro2::{Span, TokenStream as TokenStream2}, - quote::{quote, ToTokens, TokenStreamExt}, - syn::{ - ext::IdentExt, - parse::{Parse, ParseStream}, - token, Error, Expr, ExprClosure, Ident, LitBool, LitStr, Path, Result, Token, - }, -}; - -// ============================================== -// Parse any stream coming from the html! macro -// ============================================== -pub struct RsxTemplate { - inner: RsxBody, -} - -impl Parse for RsxTemplate { - fn parse(s: ParseStream) -> Result { - if s.peek(LitStr) { - use std::str::FromStr; - - let lit = s.parse::()?; - let g = lit.span(); - let mut value = lit.value(); - if value.ends_with('\n') { - value.pop(); - if value.ends_with('\r') { - value.pop(); - } - } - let lit = LitStr::new(&value, lit.span()); - - // panic!("{:#?}", lit); - match lit.parse::() { - Ok(r) => Ok(Self { inner: r }), - Err(e) => Err(e), - } - } else { - panic!("Not a str lit") - } - // let t = s.parse::()?; - - // let new_stream = TokenStream::from(t.to_s) - - // let cx: Ident = s.parse()?; - // s.parse::()?; - // if elements are in an array, return a bumpalo::collections::Vec rather than a Node. - // let kind = if s.peek(token::Bracket) { - // let nodes_toks; - // syn::bracketed!(nodes_toks in s); - // let mut nodes: Vec> = vec![nodes_toks.parse()?]; - // while nodes_toks.peek(Token![,]) { - // nodes_toks.parse::()?; - // nodes.push(nodes_toks.parse()?); - // } - // NodeOrList::List(NodeList(nodes)) - // } else { - // NodeOrList::Node(s.parse()?) - // }; - // Ok(HtmlRender { kind }) - } -} - -impl ToTokens for RsxTemplate { - fn to_tokens(&self, out_tokens: &mut TokenStream2) { - self.inner.to_tokens(out_tokens); - // let new_toks = ToToksCtx::new(&self.kind).to_token_stream(); - - // // create a lazy tree that accepts a bump allocator - // let final_tokens = quote! { - // dioxus::prelude::LazyNodes::new(move |cx| { - // let bump = &cx.bump(); - - // #new_toks - // }) - // }; - - // final_tokens.to_tokens(out_tokens); - } -} diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index 7be4273e9..44126dd63 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -26,7 +26,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option { match serde_json::from_str(payload) { Ok(message) => Some(message), Err(e) => { - log::error!("could not parse IPC message, error: {e}"); + log::error!("could not parse IPC message, error: {}", e); None } } diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 371adced5..197735ddc 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -173,6 +173,10 @@ pub fn launch_with_props( webview = webview.with_custom_protocol(name, handler) } + if cfg!(debug_assertions) { + webview = webview.with_dev_tool(true); + } + desktop.webviews.insert(window_id, webview.build().unwrap()); } diff --git a/packages/fermi/Cargo.toml b/packages/fermi/Cargo.toml new file mode 100644 index 000000000..48fe377c7 --- /dev/null +++ b/packages/fermi/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "fermi" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dioxus-core = { path = "../core" } +im-rc = { version = "15.0.0", features = ["serde"] } +log = "0.4.14" + +[dev-dependencies] +closure = "0.3.0" diff --git a/packages/fermi/README.md b/packages/fermi/README.md new file mode 100644 index 000000000..0923aa6f6 --- /dev/null +++ b/packages/fermi/README.md @@ -0,0 +1,92 @@ + +

+

Fermi ⚛

+

+ Atom-based global state management solution for Dioxus +

+
+ + +
+ + + Crates.io version + + + + Download + + + + docs.rs docs + + + + CI status + +
+ +----- + +Fermi is a global state management solution for Dioxus that's as easy as `use_state`. + +Inspired by atom-based state management solutions, all state in Fermi starts as an `atom`: + +```rust +static NAME: Atom<&str> = |_| "Dioxus"; +``` + +From anywhere in our app, we can read our the value of our atom: + +```rust +fn NameCard(cx: Scope) -> Element { + let name = use_read(&cx, NAME); + cx.render(rsx!{ h1 { "Hello, {name}"} }) +} +``` + +We can also set the value of our atom, also from anywhere in our app: + +```rust +fn NameCard(cx: Scope) -> Element { + let set_name = use_set(&cx, NAME); + cx.render(rsx!{ + button { + onclick: move |_| set_name("Fermi"), + "Set name to fermi" + } + }) +} +``` + +It's that simple! + +## Installation +Fermi is currently under construction, so you have to use the `master` branch to get started. + +```rust +[depdencies] +fermi = { git = "https://github.com/dioxuslabs/fermi" } +``` + + +## Running examples + +The examples here use Dioxus Desktop to showcase their functionality. To run an example, use +``` +$ cargo run --example EXAMPLE +``` + +## Features + +Broadly our feature set to required to be released includes: +- [x] Support for Atoms +- [x] Support for AtomRef (for values that aren't clone) +- [ ] Support for Atom Families +- [ ] Support for memoized Selectors +- [ ] Support for memoized SelectorFamilies +- [ ] Support for UseFermiCallback for access to fermi from async diff --git a/packages/fermi/src/atoms/atom.rs b/packages/fermi/src/atoms/atom.rs new file mode 100644 index 000000000..2b99174b5 --- /dev/null +++ b/packages/fermi/src/atoms/atom.rs @@ -0,0 +1,28 @@ +use crate::{AtomId, AtomRoot, Readable, Writable}; + +pub type Atom = fn(AtomBuilder) -> T; +pub struct AtomBuilder; + +impl Readable for Atom { + fn read(&self, _root: AtomRoot) -> Option { + todo!() + } + fn init(&self) -> V { + (*self)(AtomBuilder) + } + fn unique_id(&self) -> AtomId { + *self as *const () + } +} + +impl Writable for Atom { + fn write(&self, _root: AtomRoot, _value: V) { + todo!() + } +} + +#[test] +fn atom_compiles() { + static TEST_ATOM: Atom<&str> = |_| "hello"; + dbg!(TEST_ATOM.init()); +} diff --git a/packages/fermi/src/atoms/atomfamily.rs b/packages/fermi/src/atoms/atomfamily.rs new file mode 100644 index 000000000..60fc22bcb --- /dev/null +++ b/packages/fermi/src/atoms/atomfamily.rs @@ -0,0 +1,25 @@ +use crate::{AtomId, AtomRoot, Readable, Writable}; +use im_rc::HashMap as ImMap; + +pub struct AtomFamilyBuilder; +pub type AtomFamily = fn(AtomFamilyBuilder) -> ImMap; + +impl Readable> for AtomFamily { + fn read(&self, _root: AtomRoot) -> Option> { + todo!() + } + + fn init(&self) -> ImMap { + (*self)(AtomFamilyBuilder) + } + + fn unique_id(&self) -> AtomId { + *self as *const () + } +} + +impl Writable> for AtomFamily { + fn write(&self, _root: AtomRoot, _value: ImMap) { + todo!() + } +} diff --git a/packages/fermi/src/atoms/atomref.rs b/packages/fermi/src/atoms/atomref.rs new file mode 100644 index 000000000..59e412031 --- /dev/null +++ b/packages/fermi/src/atoms/atomref.rs @@ -0,0 +1,25 @@ +use crate::{AtomId, AtomRoot, Readable}; +use std::cell::RefCell; + +pub struct AtomRefBuilder; +pub type AtomRef = fn(AtomRefBuilder) -> T; + +impl Readable> for AtomRef { + fn read(&self, _root: AtomRoot) -> Option> { + todo!() + } + + fn init(&self) -> RefCell { + RefCell::new((*self)(AtomRefBuilder)) + } + + fn unique_id(&self) -> AtomId { + *self as *const () + } +} + +#[test] +fn atom_compiles() { + static TEST_ATOM: AtomRef> = |_| vec![]; + dbg!(TEST_ATOM.init()); +} diff --git a/packages/fermi/src/atoms/selector.rs b/packages/fermi/src/atoms/selector.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/packages/fermi/src/atoms/selector.rs @@ -0,0 +1 @@ + diff --git a/packages/fermi/src/atoms/selectorfamily.rs b/packages/fermi/src/atoms/selectorfamily.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/packages/fermi/src/atoms/selectorfamily.rs @@ -0,0 +1 @@ + diff --git a/packages/fermi/src/callback.rs b/packages/fermi/src/callback.rs new file mode 100644 index 000000000..a7ca9155f --- /dev/null +++ b/packages/fermi/src/callback.rs @@ -0,0 +1,53 @@ +#![allow(clippy::all, unused)] + +use std::rc::Rc; + +use dioxus_core::prelude::*; + +use crate::{AtomRoot, Readable, Writable}; + +#[derive(Clone)] +pub struct CallbackApi { + root: Rc, +} + +impl CallbackApi { + // get the current value of the atom + pub fn get(&self, atom: impl Readable) -> &V { + todo!() + } + + // get the current value of the atom in its RC container + pub fn get_rc(&self, atom: impl Readable) -> &Rc { + todo!() + } + + // set the current value of the atom + pub fn set(&self, atom: impl Writable, value: V) { + todo!() + } +} + +pub fn use_atom_context(cx: &ScopeState) -> &CallbackApi { + todo!() +} + +macro_rules! use_callback { + (&$cx:ident, [$($cap:ident),*], move || $body:expr) => { + move || { + $( + #[allow(unused_mut)] + let mut $cap = $cap.to_owned(); + )* + $cx.spawn($body); + } + }; +} + +#[macro_export] +macro_rules! to_owned { + ($($es:ident),+) => {$( + #[allow(unused_mut)] + let mut $es = $es.to_owned(); + )*} +} diff --git a/packages/fermi/src/hooks/atom_ref.rs b/packages/fermi/src/hooks/atom_ref.rs new file mode 100644 index 000000000..da9ad9367 --- /dev/null +++ b/packages/fermi/src/hooks/atom_ref.rs @@ -0,0 +1,62 @@ +use crate::{use_atom_root, AtomId, AtomRef, AtomRoot, Readable}; +use dioxus_core::{ScopeId, ScopeState}; +use std::{ + cell::{Ref, RefCell, RefMut}, + rc::Rc, +}; + +/// +/// +/// +/// +/// +/// +/// +/// +pub fn use_atom_ref(cx: &ScopeState, atom: AtomRef) -> &UseAtomRef { + let root = use_atom_root(cx); + + cx.use_hook(|_| { + root.initialize(atom); + UseAtomRef { + ptr: atom.unique_id(), + root: root.clone(), + scope_id: cx.scope_id(), + value: root.register(atom, cx.scope_id()), + } + }) +} + +pub struct UseAtomRef { + ptr: AtomId, + value: Rc>, + root: Rc, + scope_id: ScopeId, +} + +impl UseAtomRef { + pub fn read(&self) -> Ref { + self.value.borrow() + } + + pub fn write(&self) -> RefMut { + self.root.force_update(self.ptr); + self.value.borrow_mut() + } + + pub fn write_silent(&self) -> RefMut { + self.root.force_update(self.ptr); + self.value.borrow_mut() + } + + pub fn set(&self, new: T) { + self.root.force_update(self.ptr); + self.root.set(self.ptr, new); + } +} + +impl Drop for UseAtomRef { + fn drop(&mut self) { + self.root.unsubscribe(self.ptr, self.scope_id) + } +} diff --git a/packages/fermi/src/hooks/atom_root.rs b/packages/fermi/src/hooks/atom_root.rs new file mode 100644 index 000000000..d2945025b --- /dev/null +++ b/packages/fermi/src/hooks/atom_root.rs @@ -0,0 +1,11 @@ +use crate::AtomRoot; +use dioxus_core::ScopeState; +use std::rc::Rc; + +// Returns the atom root, initiaizing it at the root of the app if it does not exist. +pub fn use_atom_root(cx: &ScopeState) -> &Rc { + cx.use_hook(|_| match cx.consume_context::() { + Some(root) => root, + None => cx.provide_root_context(AtomRoot::new(cx.schedule_update_any())), + }) +} diff --git a/packages/fermi/src/hooks/init_atom_root.rs b/packages/fermi/src/hooks/init_atom_root.rs new file mode 100644 index 000000000..edb8aafb8 --- /dev/null +++ b/packages/fermi/src/hooks/init_atom_root.rs @@ -0,0 +1,11 @@ +use crate::AtomRoot; +use dioxus_core::ScopeState; +use std::rc::Rc; + +// Initializes the atom root and retuns it; +pub fn use_init_atom_root(cx: &ScopeState) -> &Rc { + cx.use_hook(|_| match cx.consume_context::() { + Some(ctx) => ctx, + None => cx.provide_context(AtomRoot::new(cx.schedule_update_any())), + }) +} diff --git a/packages/fermi/src/hooks/read.rs b/packages/fermi/src/hooks/read.rs new file mode 100644 index 000000000..b231c74d3 --- /dev/null +++ b/packages/fermi/src/hooks/read.rs @@ -0,0 +1,36 @@ +use crate::{use_atom_root, AtomId, AtomRoot, Readable}; +use dioxus_core::{ScopeId, ScopeState}; +use std::rc::Rc; + +pub fn use_read<'a, V: 'static>(cx: &'a ScopeState, f: impl Readable) -> &'a V { + use_read_rc(cx, f).as_ref() +} + +pub fn use_read_rc<'a, V: 'static>(cx: &'a ScopeState, f: impl Readable) -> &'a Rc { + let root = use_atom_root(cx); + + struct UseReadInner { + root: Rc, + id: AtomId, + scope_id: ScopeId, + value: Option>, + } + + impl Drop for UseReadInner { + fn drop(&mut self) { + self.root.unsubscribe(self.id, self.scope_id) + } + } + + let inner = cx.use_hook(|_| UseReadInner { + value: None, + root: root.clone(), + scope_id: cx.scope_id(), + id: f.unique_id(), + }); + + let value = inner.root.register(f, cx.scope_id()); + + inner.value = Some(value); + inner.value.as_ref().unwrap() +} diff --git a/packages/fermi/src/hooks/set.rs b/packages/fermi/src/hooks/set.rs new file mode 100644 index 000000000..6d77fbf8c --- /dev/null +++ b/packages/fermi/src/hooks/set.rs @@ -0,0 +1,13 @@ +use crate::{use_atom_root, Writable}; +use dioxus_core::ScopeState; +use std::rc::Rc; + +pub fn use_set<'a, T: 'static>(cx: &'a ScopeState, f: impl Writable) -> &'a Rc { + let root = use_atom_root(cx); + cx.use_hook(|_| { + let id = f.unique_id(); + let root = root.clone(); + root.initialize(f); + Rc::new(move |new| root.set(id, new)) as Rc + }) +} diff --git a/packages/fermi/src/lib.rs b/packages/fermi/src/lib.rs new file mode 100644 index 000000000..8ac4f1ccf --- /dev/null +++ b/packages/fermi/src/lib.rs @@ -0,0 +1,58 @@ +#![doc = include_str!("../README.md")] + +pub mod prelude { + pub use crate::*; +} + +mod callback; +mod root; + +pub use atoms::*; +pub use callback::*; +pub use hooks::*; +pub use root::*; + +mod atoms { + mod atom; + mod atomfamily; + mod atomref; + mod selector; + mod selectorfamily; + + pub use atom::*; + pub use atomfamily::*; + pub use atomref::*; + pub use selector::*; + pub use selectorfamily::*; +} + +pub mod hooks { + mod atom_ref; + mod atom_root; + mod init_atom_root; + mod read; + mod set; + pub use atom_ref::*; + pub use atom_root::*; + pub use init_atom_root::*; + pub use read::*; + pub use set::*; +} + +/// All Atoms are `Readable` - they support reading their value. +/// +/// This trait lets Dioxus abstract over Atoms, AtomFamilies, AtomRefs, and Selectors. +/// It is not very useful for your own code, but could be used to build new Atom primitives. +pub trait Readable { + fn read(&self, root: AtomRoot) -> Option; + fn init(&self) -> V; + fn unique_id(&self) -> AtomId; +} + +/// All Atoms are `Writable` - they support writing their value. +/// +/// This trait lets Dioxus abstract over Atoms, AtomFamilies, AtomRefs, and Selectors. +/// This trait lets Dioxus abstract over Atoms, AtomFamilies, AtomRefs, and Selectors +pub trait Writable: Readable { + fn write(&self, root: AtomRoot, value: V); +} diff --git a/packages/fermi/src/root.rs b/packages/fermi/src/root.rs new file mode 100644 index 000000000..5c07083be --- /dev/null +++ b/packages/fermi/src/root.rs @@ -0,0 +1,103 @@ +use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc}; + +use dioxus_core::ScopeId; +use im_rc::HashSet; + +use crate::Readable; + +pub type AtomId = *const (); + +pub struct AtomRoot { + pub atoms: RefCell>, + pub update_any: Rc, +} + +pub struct Slot { + pub value: Rc, + pub subscribers: HashSet, +} + +impl AtomRoot { + pub fn new(update_any: Rc) -> Self { + Self { + update_any, + atoms: RefCell::new(HashMap::new()), + } + } + + pub fn initialize(&self, f: impl Readable) { + let id = f.unique_id(); + if self.atoms.borrow().get(&id).is_none() { + self.atoms.borrow_mut().insert( + id, + Slot { + value: Rc::new(f.init()), + subscribers: HashSet::new(), + }, + ); + } + } + + pub fn register(&self, f: impl Readable, scope: ScopeId) -> Rc { + log::trace!("registering atom {:?}", f.unique_id()); + + let mut atoms = self.atoms.borrow_mut(); + + // initialize the value if it's not already initialized + if let Some(slot) = atoms.get_mut(&f.unique_id()) { + slot.subscribers.insert(scope); + slot.value.clone().downcast().unwrap() + } else { + let value = Rc::new(f.init()); + let mut subscribers = HashSet::new(); + subscribers.insert(scope); + + atoms.insert( + f.unique_id(), + Slot { + value: value.clone(), + subscribers, + }, + ); + value + } + } + + pub fn set(&self, ptr: AtomId, value: V) { + let mut atoms = self.atoms.borrow_mut(); + + if let Some(slot) = atoms.get_mut(&ptr) { + slot.value = Rc::new(value); + log::trace!("found item with subscribers {:?}", slot.subscribers); + + for scope in &slot.subscribers { + log::trace!("updating subcsriber"); + (self.update_any)(*scope); + } + } else { + log::trace!("no atoms found for {:?}", ptr); + } + } + + pub fn unsubscribe(&self, ptr: AtomId, scope: ScopeId) { + let mut atoms = self.atoms.borrow_mut(); + + if let Some(slot) = atoms.get_mut(&ptr) { + slot.subscribers.remove(&scope); + } + } + + // force update of all subscribers + pub fn force_update(&self, ptr: AtomId) { + if let Some(slot) = self.atoms.borrow_mut().get(&ptr) { + for scope in slot.subscribers.iter() { + log::trace!("updating subcsriber"); + (self.update_any)(*scope); + } + } + } + + pub fn read(&self, _f: impl Readable) -> &V { + todo!() + } +} diff --git a/packages/rsx/Cargo.toml b/packages/rsx/Cargo.toml new file mode 100644 index 000000000..4cad9064c --- /dev/null +++ b/packages/rsx/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dioxus-rsx" +version = "0.0.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +once_cell = "1.8" +proc-macro-error = "1.0.4" +proc-macro2 = { version = "1.0.6" } +quote = "1.0" +syn = { version = "1.0.11", features = ["full", "extra-traits"] } diff --git a/packages/core-macro/src/rsx/component.rs b/packages/rsx/src/component.rs similarity index 98% rename from packages/core-macro/src/rsx/component.rs rename to packages/rsx/src/component.rs index 4e9d449cf..17006e6ac 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/rsx/src/component.rs @@ -23,10 +23,10 @@ use syn::{ }; pub struct Component { - name: syn::Path, - body: Vec, - children: Vec, - manual_props: Option, + pub name: syn::Path, + pub body: Vec, + pub children: Vec, + pub manual_props: Option, } impl Parse for Component { diff --git a/packages/core-macro/src/rsx/element.rs b/packages/rsx/src/element.rs similarity index 92% rename from packages/core-macro/src/rsx/element.rs rename to packages/rsx/src/element.rs index ebc401b6e..fa8fc9f51 100644 --- a/packages/core-macro/src/rsx/element.rs +++ b/packages/rsx/src/element.rs @@ -11,12 +11,11 @@ use syn::{ // Parse the VNode::Element type // ======================================= pub struct Element { - name: Ident, - key: Option, - attributes: Vec, - listeners: Vec, - children: Vec, - _is_static: bool, + pub name: Ident, + pub key: Option, + pub attributes: Vec, + pub children: Vec, + pub _is_static: bool, } impl Parse for Element { @@ -28,7 +27,6 @@ impl Parse for Element { syn::braced!(content in stream); let mut attributes: Vec = vec![]; - let mut listeners: Vec = vec![]; let mut children: Vec = vec![]; let mut key = None; let mut _el_ref = None; @@ -54,6 +52,7 @@ impl Parse for Element { }); } else { let value = content.parse::()?; + attributes.push(ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr::CustomAttrExpression { name, value }, @@ -82,7 +81,7 @@ impl Parse for Element { content.parse::()?; if name_str.starts_with("on") { - listeners.push(ElementAttrNamed { + attributes.push(ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr::EventTokens { name, @@ -182,7 +181,6 @@ impl Parse for Element { name: el_name, attributes, children, - listeners, _is_static: false, }) } @@ -193,14 +191,21 @@ impl ToTokens for Element { let name = &self.name; let children = &self.children; - let listeners = &self.listeners; - let attr = &self.attributes; - let key = match &self.key { Some(ty) => quote! { Some(format_args_f!(#ty)) }, None => quote! { None }, }; + let listeners = self + .attributes + .iter() + .filter(|f| matches!(f.attr, ElementAttr::EventTokens { .. })); + + let attr = self + .attributes + .iter() + .filter(|f| !matches!(f.attr, ElementAttr::EventTokens { .. })); + tokens.append_all(quote! { __cx.element( dioxus_elements::#name, @@ -213,29 +218,28 @@ impl ToTokens for Element { } } -enum ElementAttr { - // attribute: "valuee {}" +pub enum ElementAttr { + /// attribute: "valuee {}" AttrText { name: Ident, value: LitStr }, - // attribute: true, + /// attribute: true, AttrExpression { name: Ident, value: Expr }, - // "attribute": "value {}" + /// "attribute": "value {}" CustomAttrText { name: LitStr, value: LitStr }, - // "attribute": true, + /// "attribute": true, CustomAttrExpression { name: LitStr, value: Expr }, - // // onclick: move |_| {} + // /// onclick: move |_| {} // EventClosure { name: Ident, closure: ExprClosure }, - - // onclick: {} + /// onclick: {} EventTokens { name: Ident, tokens: Expr }, } -struct ElementAttrNamed { - el_name: Ident, - attr: ElementAttr, +pub struct ElementAttrNamed { + pub el_name: Ident, + pub attr: ElementAttr, } impl ToTokens for ElementAttrNamed { diff --git a/packages/core-macro/src/rsx/mod.rs b/packages/rsx/src/lib.rs similarity index 96% rename from packages/core-macro/src/rsx/mod.rs rename to packages/rsx/src/lib.rs index 5ff5d85f7..e20d80dec 100644 --- a/packages/core-macro/src/rsx/mod.rs +++ b/packages/rsx/src/lib.rs @@ -15,6 +15,8 @@ mod component; mod element; mod node; +pub mod pretty; + // Re-export the namespaces into each other pub use component::*; pub use element::*; @@ -29,8 +31,8 @@ use syn::{ }; pub struct CallBody { - custom_context: Option, - roots: Vec, + pub custom_context: Option, + pub roots: Vec, } impl Parse for CallBody { diff --git a/packages/core-macro/src/rsx/node.rs b/packages/rsx/src/node.rs similarity index 95% rename from packages/core-macro/src/rsx/node.rs rename to packages/rsx/src/node.rs index d5d701767..eeee421b0 100644 --- a/packages/core-macro/src/rsx/node.rs +++ b/packages/rsx/src/node.rs @@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens, TokenStreamExt}; use syn::{ parse::{Parse, ParseStream}, - token, Expr, LitStr, Result, Token, + token, Attribute, Expr, LitStr, Result, Token, }; /* @@ -20,6 +20,7 @@ pub enum BodyNode { Component(Component), Text(LitStr), RawExpr(Expr), + Meta(Attribute), } impl Parse for BodyNode { @@ -79,6 +80,7 @@ impl ToTokens for BodyNode { BodyNode::RawExpr(exp) => tokens.append_all(quote! { __cx.fragment_from_iter(#exp) }), + BodyNode::Meta(_) => {} } } } diff --git a/packages/rsx/src/pretty.rs b/packages/rsx/src/pretty.rs new file mode 100644 index 000000000..26ad54006 --- /dev/null +++ b/packages/rsx/src/pretty.rs @@ -0,0 +1 @@ +//! pretty printer for rsx! diff --git a/src/lib.rs b/src/lib.rs index d7b479a07..488b1ee9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -340,6 +340,9 @@ pub use dioxus_web as web; #[cfg(feature = "desktop")] pub use dioxus_desktop as desktop; +#[cfg(feature = "fermi")] +pub use fermi; + // #[cfg(feature = "mobile")] // pub use dioxus_mobile as mobile; @@ -350,7 +353,7 @@ pub mod events { pub mod prelude { pub use dioxus_core::prelude::*; - pub use dioxus_core_macro::{format_args_f, inline_props, rsx, Props, Routable}; + pub use dioxus_core_macro::{format_args_f, inline_props, rsx, Props}; pub use dioxus_elements::{GlobalAttributes, SvgAttributes}; pub use dioxus_hooks::*; pub use dioxus_html as dioxus_elements; diff --git a/tests/fermi.rs b/tests/fermi.rs new file mode 100644 index 000000000..1149539f0 --- /dev/null +++ b/tests/fermi.rs @@ -0,0 +1,46 @@ +#![allow(non_snake_case)] + +use dioxus::prelude::*; +use fermi::*; + +#[test] +fn test_fermi() { + let mut app = VirtualDom::new(App); + app.rebuild(); +} + +static TITLE: Atom = |_| "".to_string(); +static USERS: AtomFamily = |_| Default::default(); + +fn App(cx: Scope) -> Element { + cx.render(rsx!( + Leaf { id: 0 } + Leaf { id: 1 } + Leaf { id: 2 } + )) +} + +#[derive(Debug, PartialEq, Props)] +struct LeafProps { + id: u32, +} + +fn Leaf(cx: Scope) -> Element { + let _user = use_read(&cx, TITLE); + let _user = use_read(&cx, USERS); + + rsx!(cx, div { + button { + onclick: move |_| {}, + "Start" + } + button { + onclick: move |_| {}, + "Stop" + } + button { + onclick: move |_| {}, + "Reverse" + } + }) +}