Merge branch 'DioxusLabs:master' into master
This commit is contained in:
40 files changed
+741
-871
No files matched your search
@@ -1,7 +1,10 @@
|
|||||||
name: macOS tests
|
name: macOS tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, ready_for_review]
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
paths:
|
paths:
|
||||||
- packages/**
|
- packages/**
|
||||||
- examples/**
|
- examples/**
|
||||||
@@ -9,10 +12,6 @@ on:
|
|||||||
- .github/**
|
- .github/**
|
||||||
- lib.rs
|
- lib.rs
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened, ready_for_review]
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|||||||
+27
-23
@@ -1,7 +1,10 @@
|
|||||||
name: Rust CI
|
name: Rust CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, ready_for_review]
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
paths:
|
paths:
|
||||||
- packages/**
|
- packages/**
|
||||||
- examples/**
|
- examples/**
|
||||||
@@ -9,10 +12,6 @@ on:
|
|||||||
- .github/**
|
- .github/**
|
||||||
- lib.rs
|
- lib.rs
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened, ready_for_review]
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
@@ -34,6 +33,7 @@ jobs:
|
|||||||
command: check
|
command: check
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
name: Test Suite
|
name: Test Suite
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -55,6 +55,7 @@ jobs:
|
|||||||
args: tests
|
args: tests
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
name: Rustfmt
|
name: Rustfmt
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -72,6 +73,7 @@ jobs:
|
|||||||
args: --all -- --check
|
args: --all -- --check
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
name: Clippy
|
name: Clippy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -90,21 +92,23 @@ jobs:
|
|||||||
command: clippy
|
command: clippy
|
||||||
args: -- -D warnings
|
args: -- -D warnings
|
||||||
|
|
||||||
coverage:
|
# Coverage is disabled until we can fix it
|
||||||
name: Coverage
|
# coverage:
|
||||||
runs-on: ubuntu-latest
|
# name: Coverage
|
||||||
container:
|
# runs-on: ubuntu-latest
|
||||||
image: xd009642/tarpaulin:develop-nightly
|
# container:
|
||||||
options: --security-opt seccomp=unconfined
|
# image: xd009642/tarpaulin:develop-nightly
|
||||||
steps:
|
# options: --security-opt seccomp=unconfined
|
||||||
- name: Checkout repository
|
# steps:
|
||||||
uses: actions/checkout@v2
|
# - name: Checkout repository
|
||||||
- name: Generate code coverage
|
# uses: actions/checkout@v2
|
||||||
run: |
|
# - name: Generate code coverage
|
||||||
apt-get update &&\
|
# run: |
|
||||||
apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\
|
# apt-get update &&\
|
||||||
cargo +nightly tarpaulin --verbose --tests --all-features --workspace --timeout 120 --out Xml
|
# apt-get install build-essential &&\
|
||||||
- name: Upload to codecov.io
|
# apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\
|
||||||
uses: codecov/codecov-action@v2
|
# cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml
|
||||||
with:
|
# - name: Upload to codecov.io
|
||||||
fail_ci_if_error: false
|
# uses: codecov/codecov-action@v2
|
||||||
|
# with:
|
||||||
|
# fail_ci_if_error: false
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
name: windows
|
name: windows
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, ready_for_review]
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
paths:
|
paths:
|
||||||
- packages/**
|
- packages/**
|
||||||
- examples/**
|
- examples/**
|
||||||
@@ -9,10 +12,6 @@ on:
|
|||||||
- .github/**
|
- .github/**
|
||||||
- lib.rs
|
- lib.rs
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened, ready_for_review]
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|||||||
+8
-17
@@ -2,19 +2,22 @@
|
|||||||
name = "dioxus"
|
name = "dioxus"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||||
homepage = "https://dioxuslabs.com"
|
homepage = "https://dioxuslabs.com"
|
||||||
documentation = "https://dioxuslabs.com"
|
documentation = "https://dioxuslabs.com"
|
||||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||||
|
rust-version = "1.56.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-core = { path = "./packages/core", version = "^0.1.9" }
|
dioxus-core = { path = "./packages/core", version = "^0.1.9" }
|
||||||
dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true }
|
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-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true }
|
||||||
dioxus-hooks = { path = "./packages/hooks", 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-web = { path = "./packages/web", version = "^0.0.5", optional = true }
|
||||||
dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", 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]
|
[features]
|
||||||
default = ["macro", "hooks", "html"]
|
default = ["macro", "hooks", "html"]
|
||||||
|
|
||||||
macro = ["dioxus-core-macro"]
|
macro = ["dioxus-core-macro", "dioxus-rsx"]
|
||||||
hooks = ["dioxus-hooks"]
|
hooks = ["dioxus-hooks"]
|
||||||
html = ["dioxus-html"]
|
html = ["dioxus-html"]
|
||||||
ssr = ["dioxus-ssr"]
|
ssr = ["dioxus-ssr"]
|
||||||
@@ -36,24 +39,11 @@ web = ["dioxus-web"]
|
|||||||
desktop = ["dioxus-desktop"]
|
desktop = ["dioxus-desktop"]
|
||||||
router = ["dioxus-router"]
|
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]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"packages/core",
|
"packages/core",
|
||||||
"packages/core-macro",
|
"packages/core-macro",
|
||||||
|
"packages/rsx",
|
||||||
"packages/html",
|
"packages/html",
|
||||||
"packages/hooks",
|
"packages/hooks",
|
||||||
"packages/web",
|
"packages/web",
|
||||||
@@ -61,6 +51,7 @@ members = [
|
|||||||
"packages/desktop",
|
"packages/desktop",
|
||||||
"packages/mobile",
|
"packages/mobile",
|
||||||
"packages/interpreter",
|
"packages/interpreter",
|
||||||
|
"packages/fermi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
@@ -75,4 +66,4 @@ serde_json = "1.0.79"
|
|||||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||||
tokio = { version = "1.16.1", features = ["full"] }
|
tokio = { version = "1.16.1", features = ["full"] }
|
||||||
reqwest = { version = "0.11.9", features = ["json"] }
|
reqwest = { version = "0.11.9", features = ["json"] }
|
||||||
dioxus = { path = ".", features = ["desktop", "ssr", "router"] }
|
dioxus = { path = ".", features = ["desktop", "ssr", "router", "fermi"] }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
book
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[book]
|
||||||
|
authors = ["Jonathan Kelley"]
|
||||||
|
language = "en"
|
||||||
|
multilingual = false
|
||||||
|
src = "src"
|
||||||
|
title = "Fermi Guide"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Summary
|
||||||
|
|
||||||
|
- [Chapter 1](./chapter_1.md)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Chapter 1
|
||||||
@@ -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.
|
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
|
## Setup
|
||||||
|
|
||||||
|
|
||||||
If you just want to render `rsx!` or a VirtualDom to HTML, check out the API docs. It's pretty simple:
|
If you just want to render `rsx!` or a VirtualDom to HTML, check out the API docs. It's pretty simple:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -19,8 +16,7 @@ println!("{}", dioxus::ssr::render_vdom(&vdom));
|
|||||||
println!( "{}", dioxus::ssr::render_lazy(rsx! { h1 { "Hello, world!" } } );
|
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:
|
Make sure you have Rust and Cargo installed, and then create a new project:
|
||||||
|
|
||||||
@@ -29,15 +25,16 @@ $ cargo new --bin demo
|
|||||||
$ cd app
|
$ cd app
|
||||||
```
|
```
|
||||||
|
|
||||||
Add Dioxus with the `desktop` feature:
|
Add Dioxus with the `ssr` feature:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ cargo add dioxus --features ssr
|
$ cargo add dioxus --features ssr
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, add all the Axum dependencies. This will be different if you're using a different Web Framework
|
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
|
$ cargo add axum
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -45,12 +42,11 @@ Your dependencies should look roughly like this:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = "0.4.3"
|
axum = "0.4.5"
|
||||||
dioxus = { version = "*", features = ["ssr"] }
|
dioxus = { version = "*", features = ["ssr"] }
|
||||||
tokio = { version = "1.15.0", features = ["full"] }
|
tokio = { version = "1.15.0", features = ["full"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Now, setup your Axum app to respond on an endpoint.
|
Now, setup your Axum app to respond on an endpoint.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -63,7 +59,11 @@ async fn main() {
|
|||||||
println!("listening on http://{}", addr);
|
println!("listening on http://{}", addr);
|
||||||
|
|
||||||
axum::Server::bind(&addr)
|
axum::Server::bind(&addr)
|
||||||
.serve(Router::new().route("/", get(app_endpoint)))
|
.serve(
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(app_endpoint))
|
||||||
|
.into_make_service(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -88,14 +88,14 @@ async fn app_endpoint() -> Html<String> {
|
|||||||
}
|
}
|
||||||
let mut app = VirtualDom::new(app);
|
let mut app = VirtualDom::new(app);
|
||||||
let _ = app.rebuild();
|
let _ = app.rebuild();
|
||||||
|
|
||||||
Html(dioxus::ssr::render_vdom(&app))
|
Html(dioxus::ssr::render_vdom(&app))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
And that's it!
|
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
|
## Future Steps
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use fermi::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus::desktop::launch(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
static NAME: Atom<String> = |_| "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"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
dioxus-rsx = { path = "../rsx" }
|
||||||
proc-macro-error = "1.0.4"
|
proc-macro-error = "1.0.4"
|
||||||
proc-macro2 = { version = "1.0.6" }
|
proc-macro2 = { version = "1.0.6" }
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
|
|||||||
@@ -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<Self> {
|
|
||||||
if input.peek(LitStr) {
|
|
||||||
return input.parse::<LitStr>()?.parse::<HtmlRender>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// let __cx: Ident = s.parse()?;
|
|
||||||
// s.parse::<Token![,]>()?;
|
|
||||||
// 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<MaybeExpr<Node>> = vec![nodes_toks.parse()?];
|
|
||||||
while nodes_toks.peek(Token![,]) {
|
|
||||||
nodes_toks.parse::<Token![,]>()?;
|
|
||||||
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<MaybeExpr<Node>>);
|
|
||||||
|
|
||||||
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<Self> {
|
|
||||||
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<Attr>,
|
|
||||||
children: MaybeExpr<Vec<Node>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Self> {
|
|
||||||
s.parse::<Token![<]>()?;
|
|
||||||
let name = Ident::parse_any(s)?;
|
|
||||||
let mut attrs = vec![];
|
|
||||||
let _children: Vec<Node> = vec![];
|
|
||||||
|
|
||||||
// keep looking for attributes
|
|
||||||
while !s.peek(Token![>]) {
|
|
||||||
// self-closing
|
|
||||||
if s.peek(Token![/]) {
|
|
||||||
s.parse::<Token![/]>()?;
|
|
||||||
s.parse::<Token![>]>()?;
|
|
||||||
return Ok(Self {
|
|
||||||
name,
|
|
||||||
attrs,
|
|
||||||
children: MaybeExpr::Literal(vec![]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
attrs.push(s.parse()?);
|
|
||||||
}
|
|
||||||
s.parse::<Token![>]>()?;
|
|
||||||
|
|
||||||
// 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::<Token![<]>()?;
|
|
||||||
s.parse::<Token![/]>()?;
|
|
||||||
let close = Ident::parse_any(s)?;
|
|
||||||
if close != name {
|
|
||||||
return Err(Error::new_spanned(
|
|
||||||
close,
|
|
||||||
"closing element does not match opening",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
s.parse::<Token![>]>()?;
|
|
||||||
|
|
||||||
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<Self> {
|
|
||||||
let mut name = Ident::parse_any(s)?;
|
|
||||||
let name_str = name.to_string();
|
|
||||||
s.parse::<Token![=]>()?;
|
|
||||||
|
|
||||||
// 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<LitStr>),
|
|
||||||
Event(ExprClosure),
|
|
||||||
// todo Bool(MaybeExpr<LitBool>)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// =======================================
|
|
||||||
/// Parse just plain text
|
|
||||||
/// =======================================
|
|
||||||
/// - [ ] Perform formatting automatically
|
|
||||||
///
|
|
||||||
///
|
|
||||||
struct TextNode(MaybeExpr<LitStr>);
|
|
||||||
|
|
||||||
impl Parse for TextNode {
|
|
||||||
fn parse(s: ParseStream) -> Result<Self> {
|
|
||||||
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<T> {
|
|
||||||
Literal(T),
|
|
||||||
Expr(Expr),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Parse> Parse for MaybeExpr<T> {
|
|
||||||
fn parse(s: ParseStream) -> Result<Self> {
|
|
||||||
if s.peek(token::Brace) {
|
|
||||||
let content;
|
|
||||||
syn::braced!(content in s);
|
|
||||||
Ok(MaybeExpr::Expr(content.parse()?))
|
|
||||||
} else {
|
|
||||||
Ok(MaybeExpr::Literal(s.parse()?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> ToTokens for ToToksCtx<&'a MaybeExpr<T>>
|
|
||||||
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<T> {
|
|
||||||
inner: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> ToToksCtx<T> {
|
|
||||||
fn new(inner: T) -> Self {
|
|
||||||
ToToksCtx { inner }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recurse<U>(&self, inner: U) -> ToToksCtx<U> {
|
|
||||||
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<super::HtmlRender> {
|
|
||||||
syn::parse_str(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn div() {
|
|
||||||
parse("bump, <div class=\"test\"/>").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nested() {
|
|
||||||
parse("bump, <div class=\"test\"><div />\"text\"</div>").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn complex() {
|
|
||||||
parse(
|
|
||||||
"bump,
|
|
||||||
<section style={{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 95%;
|
|
||||||
}} class=\"map-panel\">{contact_details}</section>
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,9 @@ use proc_macro::TokenStream;
|
|||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use syn::parse_macro_input;
|
use syn::parse_macro_input;
|
||||||
|
|
||||||
pub(crate) mod ifmt;
|
mod ifmt;
|
||||||
pub(crate) mod inlineprops;
|
mod inlineprops;
|
||||||
pub(crate) mod props;
|
mod props;
|
||||||
pub(crate) mod router;
|
|
||||||
pub(crate) mod rsx;
|
|
||||||
|
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn format_args_f(input: TokenStream) -> TokenStream {
|
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_error::proc_macro_error]
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn rsx(s: TokenStream) -> TokenStream {
|
pub fn rsx(s: TokenStream) -> TokenStream {
|
||||||
match syn::parse::<rsx::CallBody>(s) {
|
match syn::parse::<dioxus_rsx::CallBody>(s) {
|
||||||
Err(err) => err.to_compile_error().into(),
|
Err(err) => err.to_compile_error().into(),
|
||||||
Ok(stream) => stream.to_token_stream().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.
|
/// Derive props for a component within the component definition.
|
||||||
///
|
///
|
||||||
/// This macro provides a simple transformation from `Scope<{}>` to `Scope<P>`,
|
/// This macro provides a simple transformation from `Scope<{}>` to `Scope<P>`,
|
||||||
|
|||||||
@@ -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<LitStr>,
|
|
||||||
variants: Punctuated<Variant, syn::token::Comma>,
|
|
||||||
not_found_route: Option<Ident>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Routable {
|
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
||||||
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<Variant, syn::token::Comma>,
|
|
||||||
) -> syn::Result<(Option<Ident>, Vec<LitStr>)> {
|
|
||||||
let mut not_founds = vec![];
|
|
||||||
let mut ats: Vec<LitStr> = 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::<Vec<_>>();
|
|
||||||
|
|
||||||
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::<LitStr>()?;
|
|
||||||
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<Self> {
|
|
||||||
// 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::<Vec<_>>();
|
|
||||||
|
|
||||||
// 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<Self> {
|
|
||||||
// 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
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<Self> {
|
|
||||||
if s.peek(LitStr) {
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
let lit = s.parse::<LitStr>()?;
|
|
||||||
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::<crate::rsx::RsxBody>() {
|
|
||||||
Ok(r) => Ok(Self { inner: r }),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("Not a str lit")
|
|
||||||
}
|
|
||||||
// let t = s.parse::<LitStr>()?;
|
|
||||||
|
|
||||||
// let new_stream = TokenStream::from(t.to_s)
|
|
||||||
|
|
||||||
// let cx: Ident = s.parse()?;
|
|
||||||
// s.parse::<Token![,]>()?;
|
|
||||||
// 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<MaybeExpr<Node>> = vec![nodes_toks.parse()?];
|
|
||||||
// while nodes_toks.peek(Token![,]) {
|
|
||||||
// nodes_toks.parse::<Token![,]>()?;
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,7 +26,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
|
|||||||
match serde_json::from_str(payload) {
|
match serde_json::from_str(payload) {
|
||||||
Ok(message) => Some(message),
|
Ok(message) => Some(message),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("could not parse IPC message, error: {e}");
|
log::error!("could not parse IPC message, error: {}", e);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,6 +173,10 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||||||
webview = webview.with_custom_protocol(name, handler)
|
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());
|
desktop.webviews.insert(window_id, webview.build().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<h1>Fermi ⚛</h1>
|
||||||
|
<p>
|
||||||
|
<strong>Atom-based global state management solution for Dioxus</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<!-- Crates version -->
|
||||||
|
<a href="https://crates.io/crates/dioxus">
|
||||||
|
<img src="https://img.shields.io/crates/v/dioxus.svg?style=flat-square"
|
||||||
|
alt="Crates.io version" />
|
||||||
|
</a>
|
||||||
|
<!-- Downloads -->
|
||||||
|
<a href="https://crates.io/crates/dioxus">
|
||||||
|
<img src="https://img.shields.io/crates/d/dioxus.svg?style=flat-square"
|
||||||
|
alt="Download" />
|
||||||
|
</a>
|
||||||
|
<!-- docs -->
|
||||||
|
<a href="https://docs.rs/dioxus">
|
||||||
|
<img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
|
||||||
|
alt="docs.rs docs" />
|
||||||
|
</a>
|
||||||
|
<!-- CI -->
|
||||||
|
<a href="https://github.com/jkelleyrtp/dioxus/actions">
|
||||||
|
<img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg"
|
||||||
|
alt="CI status" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
use crate::{AtomId, AtomRoot, Readable, Writable};
|
||||||
|
|
||||||
|
pub type Atom<T> = fn(AtomBuilder) -> T;
|
||||||
|
pub struct AtomBuilder;
|
||||||
|
|
||||||
|
impl<V> Readable<V> for Atom<V> {
|
||||||
|
fn read(&self, _root: AtomRoot) -> Option<V> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn init(&self) -> V {
|
||||||
|
(*self)(AtomBuilder)
|
||||||
|
}
|
||||||
|
fn unique_id(&self) -> AtomId {
|
||||||
|
*self as *const ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> Writable<V> for Atom<V> {
|
||||||
|
fn write(&self, _root: AtomRoot, _value: V) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn atom_compiles() {
|
||||||
|
static TEST_ATOM: Atom<&str> = |_| "hello";
|
||||||
|
dbg!(TEST_ATOM.init());
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
use crate::{AtomId, AtomRoot, Readable, Writable};
|
||||||
|
use im_rc::HashMap as ImMap;
|
||||||
|
|
||||||
|
pub struct AtomFamilyBuilder;
|
||||||
|
pub type AtomFamily<K, V> = fn(AtomFamilyBuilder) -> ImMap<K, V>;
|
||||||
|
|
||||||
|
impl<K, V> Readable<ImMap<K, V>> for AtomFamily<K, V> {
|
||||||
|
fn read(&self, _root: AtomRoot) -> Option<ImMap<K, V>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&self) -> ImMap<K, V> {
|
||||||
|
(*self)(AtomFamilyBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unique_id(&self) -> AtomId {
|
||||||
|
*self as *const ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, V> Writable<ImMap<K, V>> for AtomFamily<K, V> {
|
||||||
|
fn write(&self, _root: AtomRoot, _value: ImMap<K, V>) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
use crate::{AtomId, AtomRoot, Readable};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
pub struct AtomRefBuilder;
|
||||||
|
pub type AtomRef<T> = fn(AtomRefBuilder) -> T;
|
||||||
|
|
||||||
|
impl<V> Readable<RefCell<V>> for AtomRef<V> {
|
||||||
|
fn read(&self, _root: AtomRoot) -> Option<RefCell<V>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&self) -> RefCell<V> {
|
||||||
|
RefCell::new((*self)(AtomRefBuilder))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unique_id(&self) -> AtomId {
|
||||||
|
*self as *const ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn atom_compiles() {
|
||||||
|
static TEST_ATOM: AtomRef<Vec<String>> = |_| vec![];
|
||||||
|
dbg!(TEST_ATOM.init());
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -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<AtomRoot>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallbackApi {
|
||||||
|
// get the current value of the atom
|
||||||
|
pub fn get<V>(&self, atom: impl Readable<V>) -> &V {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the current value of the atom in its RC container
|
||||||
|
pub fn get_rc<V>(&self, atom: impl Readable<V>) -> &Rc<V> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the current value of the atom
|
||||||
|
pub fn set<V>(&self, atom: impl Writable<V>, 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();
|
||||||
|
)*}
|
||||||
|
}
|
||||||
@@ -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<T: 'static>(cx: &ScopeState, atom: AtomRef<T>) -> &UseAtomRef<T> {
|
||||||
|
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<T> {
|
||||||
|
ptr: AtomId,
|
||||||
|
value: Rc<RefCell<T>>,
|
||||||
|
root: Rc<AtomRoot>,
|
||||||
|
scope_id: ScopeId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> UseAtomRef<T> {
|
||||||
|
pub fn read(&self) -> Ref<T> {
|
||||||
|
self.value.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&self) -> RefMut<T> {
|
||||||
|
self.root.force_update(self.ptr);
|
||||||
|
self.value.borrow_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_silent(&self) -> RefMut<T> {
|
||||||
|
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<T> Drop for UseAtomRef<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.root.unsubscribe(self.ptr, self.scope_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<AtomRoot> {
|
||||||
|
cx.use_hook(|_| match cx.consume_context::<AtomRoot>() {
|
||||||
|
Some(root) => root,
|
||||||
|
None => cx.provide_root_context(AtomRoot::new(cx.schedule_update_any())),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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<AtomRoot> {
|
||||||
|
cx.use_hook(|_| match cx.consume_context::<AtomRoot>() {
|
||||||
|
Some(ctx) => ctx,
|
||||||
|
None => cx.provide_context(AtomRoot::new(cx.schedule_update_any())),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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<V>) -> &'a V {
|
||||||
|
use_read_rc(cx, f).as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn use_read_rc<'a, V: 'static>(cx: &'a ScopeState, f: impl Readable<V>) -> &'a Rc<V> {
|
||||||
|
let root = use_atom_root(cx);
|
||||||
|
|
||||||
|
struct UseReadInner<V> {
|
||||||
|
root: Rc<AtomRoot>,
|
||||||
|
id: AtomId,
|
||||||
|
scope_id: ScopeId,
|
||||||
|
value: Option<Rc<V>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> Drop for UseReadInner<V> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
@@ -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<T>) -> &'a Rc<dyn Fn(T)> {
|
||||||
|
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<dyn Fn(T)>
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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<V> {
|
||||||
|
fn read(&self, root: AtomRoot) -> Option<V>;
|
||||||
|
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<V>: Readable<V> {
|
||||||
|
fn write(&self, root: AtomRoot, value: V);
|
||||||
|
}
|
||||||
@@ -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<HashMap<AtomId, Slot>>,
|
||||||
|
pub update_any: Rc<dyn Fn(ScopeId)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Slot {
|
||||||
|
pub value: Rc<dyn Any>,
|
||||||
|
pub subscribers: HashSet<ScopeId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtomRoot {
|
||||||
|
pub fn new(update_any: Rc<dyn Fn(ScopeId)>) -> Self {
|
||||||
|
Self {
|
||||||
|
update_any,
|
||||||
|
atoms: RefCell::new(HashMap::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initialize<V: 'static>(&self, f: impl Readable<V>) {
|
||||||
|
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<V: 'static>(&self, f: impl Readable<V>, scope: ScopeId) -> Rc<V> {
|
||||||
|
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<V: 'static>(&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<V>(&self, _f: impl Readable<V>) -> &V {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"] }
|
||||||
@@ -23,10 +23,10 @@ use syn::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct Component {
|
pub struct Component {
|
||||||
name: syn::Path,
|
pub name: syn::Path,
|
||||||
body: Vec<ComponentField>,
|
pub body: Vec<ComponentField>,
|
||||||
children: Vec<BodyNode>,
|
pub children: Vec<BodyNode>,
|
||||||
manual_props: Option<Expr>,
|
pub manual_props: Option<Expr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Component {
|
impl Parse for Component {
|
||||||
@@ -11,12 +11,11 @@ use syn::{
|
|||||||
// Parse the VNode::Element type
|
// Parse the VNode::Element type
|
||||||
// =======================================
|
// =======================================
|
||||||
pub struct Element {
|
pub struct Element {
|
||||||
name: Ident,
|
pub name: Ident,
|
||||||
key: Option<LitStr>,
|
pub key: Option<LitStr>,
|
||||||
attributes: Vec<ElementAttrNamed>,
|
pub attributes: Vec<ElementAttrNamed>,
|
||||||
listeners: Vec<ElementAttrNamed>,
|
pub children: Vec<BodyNode>,
|
||||||
children: Vec<BodyNode>,
|
pub _is_static: bool,
|
||||||
_is_static: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Element {
|
impl Parse for Element {
|
||||||
@@ -28,7 +27,6 @@ impl Parse for Element {
|
|||||||
syn::braced!(content in stream);
|
syn::braced!(content in stream);
|
||||||
|
|
||||||
let mut attributes: Vec<ElementAttrNamed> = vec![];
|
let mut attributes: Vec<ElementAttrNamed> = vec![];
|
||||||
let mut listeners: Vec<ElementAttrNamed> = vec![];
|
|
||||||
let mut children: Vec<BodyNode> = vec![];
|
let mut children: Vec<BodyNode> = vec![];
|
||||||
let mut key = None;
|
let mut key = None;
|
||||||
let mut _el_ref = None;
|
let mut _el_ref = None;
|
||||||
@@ -54,6 +52,7 @@ impl Parse for Element {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let value = content.parse::<Expr>()?;
|
let value = content.parse::<Expr>()?;
|
||||||
|
|
||||||
attributes.push(ElementAttrNamed {
|
attributes.push(ElementAttrNamed {
|
||||||
el_name: el_name.clone(),
|
el_name: el_name.clone(),
|
||||||
attr: ElementAttr::CustomAttrExpression { name, value },
|
attr: ElementAttr::CustomAttrExpression { name, value },
|
||||||
@@ -82,7 +81,7 @@ impl Parse for Element {
|
|||||||
content.parse::<Token![:]>()?;
|
content.parse::<Token![:]>()?;
|
||||||
|
|
||||||
if name_str.starts_with("on") {
|
if name_str.starts_with("on") {
|
||||||
listeners.push(ElementAttrNamed {
|
attributes.push(ElementAttrNamed {
|
||||||
el_name: el_name.clone(),
|
el_name: el_name.clone(),
|
||||||
attr: ElementAttr::EventTokens {
|
attr: ElementAttr::EventTokens {
|
||||||
name,
|
name,
|
||||||
@@ -182,7 +181,6 @@ impl Parse for Element {
|
|||||||
name: el_name,
|
name: el_name,
|
||||||
attributes,
|
attributes,
|
||||||
children,
|
children,
|
||||||
listeners,
|
|
||||||
_is_static: false,
|
_is_static: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -193,14 +191,21 @@ impl ToTokens for Element {
|
|||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
let children = &self.children;
|
let children = &self.children;
|
||||||
|
|
||||||
let listeners = &self.listeners;
|
|
||||||
let attr = &self.attributes;
|
|
||||||
|
|
||||||
let key = match &self.key {
|
let key = match &self.key {
|
||||||
Some(ty) => quote! { Some(format_args_f!(#ty)) },
|
Some(ty) => quote! { Some(format_args_f!(#ty)) },
|
||||||
None => quote! { None },
|
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! {
|
tokens.append_all(quote! {
|
||||||
__cx.element(
|
__cx.element(
|
||||||
dioxus_elements::#name,
|
dioxus_elements::#name,
|
||||||
@@ -213,29 +218,28 @@ impl ToTokens for Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ElementAttr {
|
pub enum ElementAttr {
|
||||||
// attribute: "valuee {}"
|
/// attribute: "valuee {}"
|
||||||
AttrText { name: Ident, value: LitStr },
|
AttrText { name: Ident, value: LitStr },
|
||||||
|
|
||||||
// attribute: true,
|
/// attribute: true,
|
||||||
AttrExpression { name: Ident, value: Expr },
|
AttrExpression { name: Ident, value: Expr },
|
||||||
|
|
||||||
// "attribute": "value {}"
|
/// "attribute": "value {}"
|
||||||
CustomAttrText { name: LitStr, value: LitStr },
|
CustomAttrText { name: LitStr, value: LitStr },
|
||||||
|
|
||||||
// "attribute": true,
|
/// "attribute": true,
|
||||||
CustomAttrExpression { name: LitStr, value: Expr },
|
CustomAttrExpression { name: LitStr, value: Expr },
|
||||||
|
|
||||||
// // onclick: move |_| {}
|
// /// onclick: move |_| {}
|
||||||
// EventClosure { name: Ident, closure: ExprClosure },
|
// EventClosure { name: Ident, closure: ExprClosure },
|
||||||
|
/// onclick: {}
|
||||||
// onclick: {}
|
|
||||||
EventTokens { name: Ident, tokens: Expr },
|
EventTokens { name: Ident, tokens: Expr },
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ElementAttrNamed {
|
pub struct ElementAttrNamed {
|
||||||
el_name: Ident,
|
pub el_name: Ident,
|
||||||
attr: ElementAttr,
|
pub attr: ElementAttr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ElementAttrNamed {
|
impl ToTokens for ElementAttrNamed {
|
||||||
@@ -15,6 +15,8 @@ mod component;
|
|||||||
mod element;
|
mod element;
|
||||||
mod node;
|
mod node;
|
||||||
|
|
||||||
|
pub mod pretty;
|
||||||
|
|
||||||
// Re-export the namespaces into each other
|
// Re-export the namespaces into each other
|
||||||
pub use component::*;
|
pub use component::*;
|
||||||
pub use element::*;
|
pub use element::*;
|
||||||
@@ -29,8 +31,8 @@ use syn::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct CallBody {
|
pub struct CallBody {
|
||||||
custom_context: Option<Ident>,
|
pub custom_context: Option<Ident>,
|
||||||
roots: Vec<BodyNode>,
|
pub roots: Vec<BodyNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for CallBody {
|
impl Parse for CallBody {
|
||||||
@@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2;
|
|||||||
use quote::{quote, ToTokens, TokenStreamExt};
|
use quote::{quote, ToTokens, TokenStreamExt};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
token, Expr, LitStr, Result, Token,
|
token, Attribute, Expr, LitStr, Result, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -20,6 +20,7 @@ pub enum BodyNode {
|
|||||||
Component(Component),
|
Component(Component),
|
||||||
Text(LitStr),
|
Text(LitStr),
|
||||||
RawExpr(Expr),
|
RawExpr(Expr),
|
||||||
|
Meta(Attribute),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for BodyNode {
|
impl Parse for BodyNode {
|
||||||
@@ -79,6 +80,7 @@ impl ToTokens for BodyNode {
|
|||||||
BodyNode::RawExpr(exp) => tokens.append_all(quote! {
|
BodyNode::RawExpr(exp) => tokens.append_all(quote! {
|
||||||
__cx.fragment_from_iter(#exp)
|
__cx.fragment_from_iter(#exp)
|
||||||
}),
|
}),
|
||||||
|
BodyNode::Meta(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
//! pretty printer for rsx!
|
||||||
+4
-1
@@ -340,6 +340,9 @@ pub use dioxus_web as web;
|
|||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
pub use dioxus_desktop as desktop;
|
pub use dioxus_desktop as desktop;
|
||||||
|
|
||||||
|
#[cfg(feature = "fermi")]
|
||||||
|
pub use fermi;
|
||||||
|
|
||||||
// #[cfg(feature = "mobile")]
|
// #[cfg(feature = "mobile")]
|
||||||
// pub use dioxus_mobile as mobile;
|
// pub use dioxus_mobile as mobile;
|
||||||
|
|
||||||
@@ -350,7 +353,7 @@ pub mod events {
|
|||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use dioxus_core::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_elements::{GlobalAttributes, SvgAttributes};
|
||||||
pub use dioxus_hooks::*;
|
pub use dioxus_hooks::*;
|
||||||
pub use dioxus_html as dioxus_elements;
|
pub use dioxus_html as dioxus_elements;
|
||||||
|
|||||||
@@ -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<String> = |_| "".to_string();
|
||||||
|
static USERS: AtomFamily<u32, String> = |_| 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<LeafProps>) -> 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"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in new issue
Block a user