mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-21 19:53:04 +00:00
Move the document trait into a separate crate (#3035)
* add a default head method through eval * remove the old document trait * implement document for each platform * pull out document into a dedicated crate to cut down on shared dependencies --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
parent
2b219c826c
commit
519ec9d294
91 changed files with 1113 additions and 1044 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -2380,6 +2380,7 @@ dependencies = [
|
|||
"dioxus-core-macro",
|
||||
"dioxus-desktop",
|
||||
"dioxus-devtools",
|
||||
"dioxus-document",
|
||||
"dioxus-fullstack",
|
||||
"dioxus-hooks",
|
||||
"dioxus-html",
|
||||
|
@ -2579,6 +2580,7 @@ dependencies = [
|
|||
"dioxus-cli-config",
|
||||
"dioxus-core",
|
||||
"dioxus-devtools",
|
||||
"dioxus-document",
|
||||
"dioxus-hooks",
|
||||
"dioxus-html",
|
||||
"dioxus-interpreter-js",
|
||||
|
@ -2638,6 +2640,24 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-document"
|
||||
version = "0.6.0-alpha.2"
|
||||
dependencies = [
|
||||
"dioxus",
|
||||
"dioxus-core",
|
||||
"dioxus-core-macro",
|
||||
"dioxus-core-types",
|
||||
"dioxus-html",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"generational-box",
|
||||
"lazy-js-bundle",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-examples"
|
||||
version = "0.6.0-alpha.2"
|
||||
|
@ -2815,6 +2835,7 @@ dependencies = [
|
|||
"dioxus-config-macro",
|
||||
"dioxus-core",
|
||||
"dioxus-core-macro",
|
||||
"dioxus-document",
|
||||
"dioxus-hooks",
|
||||
"dioxus-html",
|
||||
"dioxus-rsx",
|
||||
|
@ -2830,6 +2851,7 @@ dependencies = [
|
|||
"dioxus-cli-config",
|
||||
"dioxus-core",
|
||||
"dioxus-devtools",
|
||||
"dioxus-document",
|
||||
"dioxus-html",
|
||||
"dioxus-interpreter-js",
|
||||
"futures-channel",
|
||||
|
@ -3074,6 +3096,7 @@ dependencies = [
|
|||
"dioxus-core",
|
||||
"dioxus-core-types",
|
||||
"dioxus-devtools",
|
||||
"dioxus-document",
|
||||
"dioxus-html",
|
||||
"dioxus-interpreter-js",
|
||||
"dioxus-signals",
|
||||
|
|
56
Cargo.toml
56
Cargo.toml
|
@ -1,39 +1,40 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"packages/dioxus",
|
||||
"packages/dioxus-lib",
|
||||
"packages/core",
|
||||
"packages/core-types",
|
||||
"packages/cli",
|
||||
"packages/cli-config",
|
||||
"packages/core-macro",
|
||||
"packages/config-macro",
|
||||
"packages/router-macro",
|
||||
"packages/extension",
|
||||
"packages/router",
|
||||
"packages/html",
|
||||
"packages/html-internal-macro",
|
||||
"packages/hooks",
|
||||
"packages/web",
|
||||
"packages/ssr",
|
||||
"packages/desktop",
|
||||
"packages/mobile",
|
||||
"packages/interpreter",
|
||||
"packages/liveview",
|
||||
"packages/autofmt",
|
||||
"packages/check",
|
||||
"packages/rsx",
|
||||
"packages/cli-config",
|
||||
"packages/cli",
|
||||
"packages/config-macro",
|
||||
"packages/core-macro",
|
||||
"packages/core-types",
|
||||
"packages/core",
|
||||
"packages/desktop",
|
||||
"packages/devtools-types",
|
||||
"packages/devtools",
|
||||
"packages/dioxus-lib",
|
||||
"packages/dioxus",
|
||||
"packages/document",
|
||||
"packages/extension",
|
||||
"packages/fullstack",
|
||||
"packages/generational-box",
|
||||
"packages/hooks",
|
||||
"packages/html-internal-macro",
|
||||
"packages/html",
|
||||
"packages/interpreter",
|
||||
"packages/lazy-js-bundle",
|
||||
"packages/liveview",
|
||||
"packages/mobile",
|
||||
"packages/router-macro",
|
||||
"packages/router",
|
||||
"packages/rsx-hotreload",
|
||||
"packages/rsx-rosetta",
|
||||
"packages/generational-box",
|
||||
"packages/signals",
|
||||
"packages/devtools",
|
||||
"packages/devtools-types",
|
||||
"packages/fullstack",
|
||||
"packages/rsx",
|
||||
"packages/server-macro",
|
||||
"packages/signals",
|
||||
"packages/ssr",
|
||||
"packages/static-generation",
|
||||
"packages/lazy-js-bundle",
|
||||
"packages/web",
|
||||
|
||||
# Full project examples
|
||||
"example-projects/fullstack-hackernews",
|
||||
|
@ -76,6 +77,7 @@ dioxus-core-macro = { path = "packages/core-macro", version = "0.6.0-alpha.0" }
|
|||
dioxus-config-macro = { path = "packages/config-macro", version = "0.6.0-alpha.0" }
|
||||
dioxus-router = { path = "packages/router", version = "0.6.0-alpha.0" }
|
||||
dioxus-router-macro = { path = "packages/router-macro", version = "0.6.0-alpha.0" }
|
||||
dioxus-document = { path = "packages/document", version = "0.6.0-alpha.0", default-features = false }
|
||||
dioxus-html = { path = "packages/html", version = "0.6.0-alpha.0", default-features = false }
|
||||
dioxus-html-internal-macro = { path = "packages/html-internal-macro", version = "0.6.0-alpha.0" }
|
||||
dioxus-hooks = { path = "packages/hooks", version = "0.6.0-alpha.0" }
|
||||
|
|
|
@ -3,7 +3,7 @@ use dioxus::prelude::*;
|
|||
#[component]
|
||||
pub(crate) fn ChildrenOrLoading(children: Element) -> Element {
|
||||
rsx! {
|
||||
head::Link {
|
||||
document::Link {
|
||||
rel: "stylesheet",
|
||||
href: asset!("./public/loading.css")
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ mod api;
|
|||
fn main() {
|
||||
dioxus::launch(|| {
|
||||
rsx! {
|
||||
head::Link {
|
||||
document::Link {
|
||||
rel: "stylesheet",
|
||||
href: asset!("./public/tailwind.css")
|
||||
}
|
||||
|
|
|
@ -21,12 +21,12 @@ fn app() -> Element {
|
|||
let mut files = use_signal(Files::new);
|
||||
|
||||
rsx! {
|
||||
head::Link {
|
||||
document::Link {
|
||||
rel: "stylesheet",
|
||||
href: asset!("./assets/fileexplorer.css")
|
||||
}
|
||||
div {
|
||||
head::Link { href: "https://fonts.googleapis.com/icon?family=Material+Icons", rel: "stylesheet" }
|
||||
document::Link { href: "https://fonts.googleapis.com/icon?family=Material+Icons", rel: "stylesheet" }
|
||||
header {
|
||||
i { class: "material-icons icon-menu", "menu" }
|
||||
h1 { "Files: " {files.read().current()} }
|
||||
|
|
|
@ -36,7 +36,7 @@ pub fn App() -> Element {
|
|||
#[component]
|
||||
fn Homepage(story: ReadOnlySignal<PreviewState>) -> Element {
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: asset!("./assets/hackernews.css") }
|
||||
document::Link { rel: "stylesheet", href: asset!("./assets/hackernews.css") }
|
||||
div { display: "flex", flex_direction: "row", width: "100%",
|
||||
div {
|
||||
width: "50%",
|
||||
|
|
|
@ -26,7 +26,7 @@ fn app() -> Element {
|
|||
};
|
||||
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
div { id: "container",
|
||||
// focusing is necessary to catch keyboard events
|
||||
div { id: "receiver", tabindex: 0,
|
||||
|
|
|
@ -54,7 +54,7 @@ fn app() -> Element {
|
|||
};
|
||||
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
div { id: "wrapper",
|
||||
div { class: "app",
|
||||
div { class: "calculator", tabindex: "0", onkeydown: handle_key_down_event,
|
||||
|
|
|
@ -29,7 +29,7 @@ fn app() -> Element {
|
|||
let mut state = use_signal(Calculator::new);
|
||||
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: asset!("./examples/assets/calculator.css") }
|
||||
document::Link { rel: "stylesheet", href: asset!("./examples/assets/calculator.css") }
|
||||
div { id: "wrapper",
|
||||
div { class: "app",
|
||||
div {
|
||||
|
|
|
@ -36,7 +36,7 @@ fn app() -> Element {
|
|||
);
|
||||
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
div { id: "app",
|
||||
div { id: "title", "Carpe diem 🎉" }
|
||||
div { id: "clock-display", "{time}" }
|
||||
|
|
|
@ -40,7 +40,7 @@ fn app() -> Element {
|
|||
});
|
||||
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
h1 { "Input Roulette" }
|
||||
button { onclick: move |_| running.toggle(), "Toggle roulette" }
|
||||
div { id: "roulette-grid",
|
||||
|
|
|
@ -16,7 +16,7 @@ fn app() -> Element {
|
|||
let sum = use_memo(move || counters.read().iter().copied().sum::<i32>());
|
||||
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
|
||||
div { id: "controls",
|
||||
button { onclick: move |_| counters.write().push(0), "Add counter" }
|
||||
|
|
|
@ -20,13 +20,13 @@ fn main() {
|
|||
}))
|
||||
.launch(|| {
|
||||
rsx! {
|
||||
head::Link {
|
||||
document::Link {
|
||||
rel: "stylesheet",
|
||||
href: asset!("https://unpkg.com/purecss@2.0.6/build/pure-min.css"),
|
||||
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
|
||||
crossorigin: "anonymous"
|
||||
}
|
||||
head::Link { rel: "stylesheet", href: asset!("./examples/assets/crm.css") }
|
||||
document::Link { rel: "stylesheet", href: asset!("./examples/assets/crm.css") }
|
||||
h1 { "Dioxus CRM Example" }
|
||||
Router::<Route> {}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ fn app() -> Element {
|
|||
});
|
||||
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
h1 { "Dynamic Assets" }
|
||||
img { src: "/logos/logo.png" }
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ fn app() -> Element {
|
|||
// The `eval` is available in the prelude - and simply takes a block of JS.
|
||||
// Dioxus' eval is interesting since it allows sending messages to and from the JS code using the `await dioxus.recv()`
|
||||
// builtin function. This allows you to create a two-way communication channel between Rust and JS.
|
||||
let mut eval = eval(
|
||||
let mut eval = document::eval(
|
||||
r#"
|
||||
dioxus.send("Hi from JS!");
|
||||
let msg = await dioxus.recv();
|
||||
|
@ -29,10 +29,10 @@ fn app() -> Element {
|
|||
);
|
||||
|
||||
// Send a message to the JS code.
|
||||
eval.send("Hi from Rust!".into()).unwrap();
|
||||
eval.send("Hi from Rust!").unwrap();
|
||||
|
||||
// Our line on the JS side will log the message and then return "hello world".
|
||||
let res = eval.recv().await.unwrap();
|
||||
let res: String = eval.recv().await.unwrap();
|
||||
|
||||
// This will print "Hi from JS!" and "Hi from Rust!".
|
||||
println!("{:?}", eval.await);
|
||||
|
|
|
@ -43,7 +43,7 @@ fn app() -> Element {
|
|||
};
|
||||
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
|
||||
h1 { "File Upload Example" }
|
||||
p { "Drop a .txt, .rs, or .js file here to read it" }
|
||||
|
|
|
@ -14,7 +14,7 @@ const STYLE: &str = asset!("./examples/assets/flat_router.css");
|
|||
fn main() {
|
||||
dioxus::launch(|| {
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
Router::<Route> {}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -18,7 +18,7 @@ fn main() {
|
|||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
Increment {}
|
||||
Decrement {}
|
||||
Reset {}
|
||||
|
|
|
@ -36,7 +36,7 @@ fn app() -> Element {
|
|||
});
|
||||
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: "https://unpkg.com/bulma@0.9.0/css/bulma.min.css" }
|
||||
document::Link { rel: "stylesheet", href: "https://unpkg.com/bulma@0.9.0/css/bulma.min.css" }
|
||||
div { class: "container",
|
||||
div { class: "columns",
|
||||
div { class: "column",
|
||||
|
|
|
@ -16,7 +16,7 @@ fn main() {
|
|||
|
||||
fn app() -> Element {
|
||||
rsx! (
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
Router::<Route> {}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,23 +12,23 @@ fn app() -> Element {
|
|||
// You can use the Meta component to render a meta tag into the head of the page
|
||||
// Meta tags are useful to provide information about the page to search engines and social media sites
|
||||
// This example sets up meta tags for the open graph protocol for social media previews
|
||||
Meta {
|
||||
document::Meta {
|
||||
property: "og:title",
|
||||
content: "My Site",
|
||||
}
|
||||
Meta {
|
||||
document::Meta {
|
||||
property: "og:type",
|
||||
content: "website",
|
||||
}
|
||||
Meta {
|
||||
document::Meta {
|
||||
property: "og:url",
|
||||
content: "https://www.example.com",
|
||||
}
|
||||
Meta {
|
||||
document::Meta {
|
||||
property: "og:image",
|
||||
content: "https://example.com/image.jpg",
|
||||
}
|
||||
Meta {
|
||||
document::Meta {
|
||||
name: "description",
|
||||
content: "My Site is a site",
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ fn app() -> Element {
|
|||
_ = use_global_shortcut("cmd+g", move || show_overlay.toggle());
|
||||
|
||||
rsx! {
|
||||
head::Link {
|
||||
document::Link {
|
||||
rel: "stylesheet",
|
||||
href: asset!("./examples/assets/overlay.css"),
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ fn app() -> Element {
|
|||
};
|
||||
|
||||
rsx!(
|
||||
head::Link { rel: "stylesheet", href: asset!("./examples/assets/read_size.css") }
|
||||
document::Link { rel: "stylesheet", href: asset!("./examples/assets/read_size.css") }
|
||||
div {
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
|
|
|
@ -17,7 +17,7 @@ fn app() -> Element {
|
|||
let mut state = use_signal(|| PlayerState { is_playing: false });
|
||||
|
||||
rsx!(
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
h1 {"Select an option"}
|
||||
|
||||
// Add some cute animations if the radio is playing!
|
||||
|
|
|
@ -15,7 +15,7 @@ fn app() -> Element {
|
|||
let mut dimensions = use_signal(Size2D::zero);
|
||||
|
||||
rsx!(
|
||||
head::Link { rel: "stylesheet", href: asset!("./examples/assets/read_size.css") }
|
||||
document::Link { rel: "stylesheet", href: asset!("./examples/assets/read_size.css") }
|
||||
div {
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
|
|
|
@ -13,7 +13,7 @@ const STYLE: &str = asset!("./examples/assets/router.css");
|
|||
fn main() {
|
||||
dioxus::launch(|| {
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
Router::<Route> {}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ fn app() -> Element {
|
|||
div {
|
||||
// You can set the title of the page with the Title component
|
||||
// In web applications, this sets the title in the head. On desktop, it sets the window title
|
||||
Title { "My Application (Count {count})" }
|
||||
document::Title { "My Application (Count {count})" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ fn app() -> Element {
|
|||
};
|
||||
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
section { class: "todoapp",
|
||||
TodoHeader { todos }
|
||||
section { class: "main",
|
||||
|
|
|
@ -19,7 +19,7 @@ fn app() -> Element {
|
|||
let current_weather = use_resource(move || async move { get_weather(&country()).await });
|
||||
|
||||
rsx! {
|
||||
head::Link { rel: "stylesheet", href: "https://unpkg.com/tailwindcss@^2.0/dist/tailwind.min.css" }
|
||||
document::Link { rel: "stylesheet", href: "https://unpkg.com/tailwindcss@^2.0/dist/tailwind.min.css" }
|
||||
div { class: "mx-auto p-4 bg-gray-100 h-screen flex justify-center",
|
||||
div { class: "flex items-center justify-center flex-row",
|
||||
div { class: "flex items-start justify-center flex-row",
|
||||
|
|
|
@ -26,7 +26,7 @@ fn main() {
|
|||
|
||||
fn app() -> Element {
|
||||
rsx!(
|
||||
head::Link { href: "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel: "stylesheet" }
|
||||
document::Link { href: "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel: "stylesheet" }
|
||||
Header {}
|
||||
div { class: "container mx-auto",
|
||||
div { class: "grid grid-cols-5",
|
||||
|
|
|
@ -156,7 +156,7 @@ impl BuildRequest {
|
|||
};
|
||||
match variant {
|
||||
ResourceType::Style => format!(
|
||||
" head::Link {{ rel: \"stylesheet\", href: asset!(css(\"{}\")) }}",
|
||||
" document::Link {{ rel: \"stylesheet\", href: asset!(css(\"{}\")) }}",
|
||||
path.display()
|
||||
),
|
||||
ResourceType::Script => {
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
//! use_effect(move || {
|
||||
//! let id = id.read();
|
||||
//! // This will panic if the id is not written to the DOM before the effect is run
|
||||
//! eval(format!(r#"document.getElementById("{id}").innerHTML = "Hello World";"#));
|
||||
//! document::eval(format!(r#"document.getElementById("{id}").innerHTML = "Hello World";"#));
|
||||
//! });
|
||||
//!
|
||||
//! rsx! {
|
||||
|
|
|
@ -517,7 +517,7 @@ fn nested_suspense_resolves_client() {
|
|||
let title = use_resource(move || async_content(0)).suspend()?();
|
||||
|
||||
rsx! {
|
||||
Title { "{title.title}" }
|
||||
document::Title { "{title.title}" }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@ dioxus-html = { workspace = true, features = [
|
|||
"serialize",
|
||||
"mounted",
|
||||
"file_engine",
|
||||
"document",
|
||||
] }
|
||||
dioxus-document = { workspace = true }
|
||||
dioxus-signals = { workspace = true, optional = true }
|
||||
dioxus-interpreter-js = { workspace = true, features = ["binary-protocol", "serialize"] }
|
||||
dioxus-cli-config = { workspace = true }
|
||||
|
|
|
@ -16,23 +16,23 @@ static EVALS_RETURNED: GlobalSignal<usize> = Signal::global(|| 0);
|
|||
fn app() -> Element {
|
||||
// Double 100 values in the value
|
||||
use_future(|| async {
|
||||
let mut eval = eval(
|
||||
let mut eval = document::eval(
|
||||
r#"for (let i = 0; i < 100; i++) {
|
||||
let value = await dioxus.recv();
|
||||
dioxus.send(value*2);
|
||||
}"#,
|
||||
);
|
||||
for i in 0..100 {
|
||||
eval.send(serde_json::Value::from(i)).unwrap();
|
||||
let value = eval.recv().await.unwrap();
|
||||
assert_eq!(value, serde_json::Value::from(i * 2));
|
||||
eval.send(i).unwrap();
|
||||
let value: i32 = eval.recv().await.unwrap();
|
||||
assert_eq!(value, i * 2);
|
||||
EVALS_RECEIVED.with_mut(|x| *x += 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure returning no value resolves the future
|
||||
use_future(|| async {
|
||||
let eval = eval(r#"return;"#);
|
||||
let eval = document::eval(r#"return;"#);
|
||||
|
||||
eval.await.unwrap();
|
||||
EVALS_RETURNED.with_mut(|x| *x += 1);
|
||||
|
@ -40,7 +40,7 @@ fn app() -> Element {
|
|||
|
||||
// Return a value from the future
|
||||
use_future(|| async {
|
||||
let eval = eval(
|
||||
let eval = document::eval(
|
||||
r#"
|
||||
return [1, 2, 3];
|
||||
"#,
|
||||
|
|
|
@ -16,7 +16,7 @@ fn use_inner_html(id: &'static str) -> Option<String> {
|
|||
spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
|
||||
|
||||
let res = eval(&format!(
|
||||
let res = document::eval(&format!(
|
||||
r#"let element = document.getElementById('{}');
|
||||
return element.innerHTML"#,
|
||||
id
|
||||
|
|
|
@ -50,7 +50,7 @@ pub fn mock_event_with_extra(id: &'static str, value: &'static str, extra: &'sta
|
|||
"#
|
||||
);
|
||||
|
||||
eval(&js).await.unwrap();
|
||||
document::eval(&js).await.unwrap();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use dioxus_html::document::{Document, EvalError, Evaluator};
|
||||
use dioxus_document::{Document, Eval, EvalError, Evaluator};
|
||||
use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
|
||||
|
||||
use crate::{query::Query, DesktopContext};
|
||||
|
@ -18,17 +18,13 @@ impl DesktopDocument {
|
|||
}
|
||||
|
||||
impl Document for DesktopDocument {
|
||||
fn new_evaluator(&self, js: String) -> GenerationalBox<Box<dyn Evaluator>> {
|
||||
DesktopEvaluator::create(self.desktop_ctx.clone(), js)
|
||||
fn eval(&self, js: String) -> Eval {
|
||||
Eval::new(DesktopEvaluator::create(self.desktop_ctx.clone(), js))
|
||||
}
|
||||
|
||||
fn set_title(&self, title: String) {
|
||||
self.desktop_ctx.window.set_title(&title);
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a desktop-target's JavaScript evaluator.
|
||||
|
|
|
@ -1 +1 @@
|
|||
[11927251734412729446]
|
||||
[14101548031762241351]
|
|
@ -2,7 +2,7 @@ import {
|
|||
Channel,
|
||||
DioxusChannel,
|
||||
WeakDioxusChannel,
|
||||
} from "../../../html/src/ts/eval";
|
||||
} from "../../../document/src/ts/eval";
|
||||
|
||||
// In dioxus desktop, eval needs to use the window object to store global state because we evaluate separate snippets of javascript in the browser
|
||||
declare global {
|
||||
|
@ -86,5 +86,5 @@ export class NativeDioxusChannel extends DioxusChannel {
|
|||
}
|
||||
|
||||
// Receive data sent from javascript in rust. This is a no-op in the native interpreter because the rust code runs remotely
|
||||
async rustRecv(): Promise<any> {}
|
||||
async rustRecv(): Promise<any> { }
|
||||
}
|
||||
|
|
|
@ -13,8 +13,9 @@ use crate::{
|
|||
Config, DesktopContext, DesktopService,
|
||||
};
|
||||
use dioxus_core::{Runtime, ScopeId, VirtualDom};
|
||||
use dioxus_document::Document;
|
||||
use dioxus_hooks::to_owned;
|
||||
use dioxus_html::{prelude::Document, HasFileData, HtmlEvent, PlatformEventData};
|
||||
use dioxus_html::{HasFileData, HtmlEvent, PlatformEventData};
|
||||
use futures_util::{pin_mut, FutureExt};
|
||||
use std::cell::OnceCell;
|
||||
use std::sync::Arc;
|
||||
|
|
|
@ -13,6 +13,7 @@ rust-version = "1.79.0"
|
|||
[dependencies]
|
||||
dioxus-core = { workspace = true }
|
||||
dioxus-html = { workspace = true, optional = true }
|
||||
dioxus-document = { workspace = true, optional = true }
|
||||
dioxus-core-macro = { workspace = true, optional = true }
|
||||
dioxus-config-macro = { workspace = true, optional = true }
|
||||
dioxus-hooks = { workspace = true, optional = true }
|
||||
|
@ -26,7 +27,7 @@ dioxus = { workspace = true }
|
|||
default = ["macro", "html", "signals", "hooks"]
|
||||
signals = ["dep:dioxus-signals"]
|
||||
macro = ["dep:dioxus-core-macro", "dep:dioxus-rsx", "dep:dioxus-config-macro"]
|
||||
html = ["dep:dioxus-html"]
|
||||
html = ["dep:dioxus-html", "dep:dioxus-document"]
|
||||
hooks = ["dep:dioxus-hooks"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
|
|
@ -16,6 +16,9 @@ pub mod events {
|
|||
#[cfg(feature = "html")]
|
||||
pub use dioxus_html as html;
|
||||
|
||||
#[cfg(feature = "html")]
|
||||
pub use dioxus_document as document;
|
||||
|
||||
#[cfg(feature = "macro")]
|
||||
pub use dioxus_rsx as rsx;
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ rust-version = "1.79.0"
|
|||
[dependencies]
|
||||
dioxus-core = { workspace = true }
|
||||
dioxus-html = { workspace = true, default-features = false, optional = true }
|
||||
dioxus-document = { workspace = true, optional = true }
|
||||
dioxus-core-macro = { workspace = true, optional = true }
|
||||
dioxus-config-macro = { workspace = true, optional = true }
|
||||
dioxus-hooks = { workspace = true, optional = true }
|
||||
|
@ -44,7 +45,7 @@ devtools = ["dep:dioxus-devtools", "dioxus-web?/devtools", "dioxus-fullstack?/de
|
|||
mounted = ["dioxus-web?/mounted", "dioxus-html?/mounted"]
|
||||
file_engine = ["dioxus-web?/file_engine"]
|
||||
asset = ["dep:manganis", "dioxus-core/manganis"]
|
||||
document = ["dioxus-web?/document", "dioxus-html?/document"]
|
||||
document = ["dioxus-web?/document", "dioxus-document"]
|
||||
|
||||
launch = ["dep:dioxus-config-macro"]
|
||||
router = ["dep:dioxus-router"]
|
||||
|
|
|
@ -330,7 +330,7 @@ fn web_launch(
|
|||
#[cfg(all(feature = "static-generation", not(feature = "fullstack")))]
|
||||
use dioxus_static_site_generation::document;
|
||||
let document = std::rc::Rc::new(document::web::FullstackWebDocument)
|
||||
as std::rc::Rc<dyn crate::prelude::Document>;
|
||||
as std::rc::Rc<dyn crate::prelude::document::Document>;
|
||||
vdom.provide_root_context(document);
|
||||
}
|
||||
vdom
|
||||
|
|
|
@ -50,6 +50,10 @@ pub mod events {
|
|||
pub use dioxus_html::prelude::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "document")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "document")))]
|
||||
pub use dioxus_document as document;
|
||||
|
||||
#[cfg(feature = "html")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "html")))]
|
||||
pub use dioxus_html as html;
|
||||
|
@ -59,6 +63,13 @@ pub use dioxus_html as html;
|
|||
pub use dioxus_core_macro as core_macro;
|
||||
|
||||
pub mod prelude {
|
||||
#[cfg(feature = "document")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "document")))]
|
||||
pub use dioxus_document as document;
|
||||
|
||||
#[cfg(feature = "launch")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "launch")))]
|
||||
pub use crate::launch::*;
|
||||
|
||||
#[cfg(feature = "hooks")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "hooks")))]
|
||||
|
|
22
packages/document/Cargo.toml
Normal file
22
packages/document/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "dioxus-document"
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { workspace = true }
|
||||
dioxus-core-types = { workspace = true }
|
||||
dioxus-core-macro = { workspace = true }
|
||||
dioxus-html = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
futures-channel = { workspace = true }
|
||||
futures-util.workspace = true
|
||||
generational-box.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
lazy-js-bundle = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { workspace = true }
|
1
packages/document/assets/script.js
Normal file
1
packages/document/assets/script.js
Normal file
|
@ -0,0 +1 @@
|
|||
// this script is included as an asset!() for a test
|
1
packages/document/assets/style.css
Normal file
1
packages/document/assets/style.css
Normal file
|
@ -0,0 +1 @@
|
|||
/* this stylesheet is included as an asset!() for a test */
|
|
@ -1,6 +1,6 @@
|
|||
# Communicating with JavaScript
|
||||
|
||||
You can use the `eval` function to execute JavaScript code in your application with the desktop, mobile, web or liveview renderers. Eval takes a block of JavaScript code (that may be asynchronous) and returns a `UseEval` object that you can use to send data to the JavaScript code and receive data from it.
|
||||
You can use the `eval` function to execute JavaScript code in your application with the desktop, mobile, web or liveview renderers. Eval takes a block of JavaScript code (that may be asynchronous) and returns a `Eval` object that you can use to send data to the JavaScript code and receive data from it.
|
||||
|
||||
<div class="warning">
|
||||
|
||||
|
@ -18,7 +18,7 @@ fn App() -> Element {
|
|||
button {
|
||||
onclick: move |_| async move {
|
||||
// Eval is a global function you can use anywhere inside Dioxus. It will execute the given JavaScript code.
|
||||
let result = eval(r#"console.log("Hello World");
|
||||
let result = document::eval(r#"console.log("Hello World");
|
||||
return "Hello World";"#);
|
||||
|
||||
// You can use the `await` keyword to wait for the result of the JavaScript code.
|
||||
|
@ -32,7 +32,7 @@ fn App() -> Element {
|
|||
|
||||
## Sending data to JavaScript
|
||||
|
||||
When you execute JavaScript code with `eval`, you can pass data to it by formatting the value into the JavaScript code or sending values to the `UseEval` channel.
|
||||
When you execute JavaScript code with `eval`, you can pass data to it by formatting the value into the JavaScript code or sending values to the `Eval` channel.
|
||||
|
||||
```rust
|
||||
use dioxus::prelude::*;
|
||||
|
@ -43,7 +43,7 @@ fn app() -> Element {
|
|||
onclick: move |_| {
|
||||
// You can pass initial data to the eval function by formatting it into the JavaScript code.
|
||||
const LOOP_COUNT: usize = 10;
|
||||
let eval = eval(&format!(r#"for(let i = 0; i < {LOOP_COUNT}; i++) {{
|
||||
let eval = document::eval(&format!(r#"for(let i = 0; i < {LOOP_COUNT}; i++) {{
|
||||
// You can receive values asynchronously with the the `await dioxus.recv()` method.
|
||||
let value = await dioxus.recv();
|
||||
console.log("Received", value);
|
||||
|
@ -51,7 +51,7 @@ fn app() -> Element {
|
|||
|
||||
// You can send values from rust to the JavaScript code with the `send` method on the object returned by `eval`.
|
||||
for i in 0..LOOP_COUNT {
|
||||
eval.send(i.into()).unwrap();
|
||||
eval.send(i).unwrap();
|
||||
}
|
||||
},
|
||||
"Log Count"
|
||||
|
@ -62,7 +62,7 @@ fn app() -> Element {
|
|||
|
||||
## Sending data from JavaScript
|
||||
|
||||
The `UseEval` struct also contains methods for receiving values you send from JavaScript. You can use the `dioxus.send()` method to send values to the JavaScript code and the `UseEval::recv()` method to receive values from the JavaScript code.
|
||||
The `Eval` struct also contains methods for receiving values you send from JavaScript. You can use the `dioxus.send()` method to send values to the JavaScript code and the `Eval::recv()` method to receive values from the JavaScript code.
|
||||
|
||||
```rust
|
||||
use dioxus::prelude::*;
|
||||
|
@ -72,14 +72,14 @@ fn app() -> Element {
|
|||
button {
|
||||
onclick: move |_| async move {
|
||||
// You can send values from rust to the JavaScript code by using the `send` method on the object returned by `eval`.
|
||||
let mut eval = eval(r#"for(let i = 0; i < 10; i++) {
|
||||
let mut eval = document::eval(r#"for(let i = 0; i < 10; i++) {
|
||||
// You can send values asynchronously with the `dioxus.send()` method.
|
||||
dioxus.send(i);
|
||||
}"#);
|
||||
|
||||
// You can receive values from the JavaScript code with the `recv` method on the object returned by `eval`.
|
||||
for _ in 0..10 {
|
||||
let value = eval.recv().await.unwrap();
|
||||
let value: i32 = eval.recv().await.unwrap();
|
||||
println!("Received {}", value);
|
||||
}
|
||||
},
|
||||
|
@ -104,12 +104,12 @@ const SCRIPT: &str = r#"
|
|||
|
||||
fn app() -> Element {
|
||||
// ❌ You shouldn't run eval in the body of a component. This will run before the component has been mounted
|
||||
// eval(SCRIPT);
|
||||
// document::eval(SCRIPT);
|
||||
|
||||
// ✅ You should run eval inside an effect or event. This will run after the component has been mounted
|
||||
use_effect(move || {
|
||||
spawn(async {
|
||||
let count = eval(SCRIPT).await;
|
||||
let count = document::eval(SCRIPT).await;
|
||||
println!("Count is {:?}", count);
|
||||
});
|
||||
});
|
|
@ -4,7 +4,7 @@ Dioxus includes a series of components that render into the head of the page:
|
|||
|
||||
- [Title](crate::Title)
|
||||
- [Meta](crate::Meta)
|
||||
- [head::Link](crate::head::Link)
|
||||
- [document::Link](crate::document::Link)
|
||||
- [Script](crate::Script)
|
||||
- [Style](crate::Style)
|
||||
|
||||
|
@ -25,7 +25,7 @@ fn RedirectToDioxusHomepageWithoutJS() -> Element {
|
|||
rsx! {
|
||||
// You can use the meta component to render a meta tag into the head of the page
|
||||
// This meta tag will redirect the user to the dioxuslabs homepage in 10 seconds
|
||||
Meta {
|
||||
document::Meta {
|
||||
http_equiv: "refresh",
|
||||
content: "10;url=https://dioxuslabs.com",
|
||||
}
|
||||
|
@ -46,12 +46,12 @@ If you have any important metadata that you want to render into the head, make s
|
|||
fn App() -> Element {
|
||||
rsx! {
|
||||
// This will render in SSR
|
||||
Title { "My Page" }
|
||||
document::Title { "My Page" }
|
||||
SuspenseBoundary {
|
||||
fallback: |_| rsx! { "Loading..." },
|
||||
LoadData {
|
||||
// This will only be rendered on the client after hydration so it may not be visible to search engines
|
||||
Meta { name: "description", content: "My Page" }
|
||||
document::Meta { name: "description", content: "My Page" }
|
||||
}
|
||||
}
|
||||
}
|
156
packages/document/src/document.rs
Normal file
156
packages/document/src/document.rs
Normal file
|
@ -0,0 +1,156 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A context for the document
|
||||
pub type DocumentContext = Arc<dyn Document>;
|
||||
|
||||
fn format_string_for_js(s: &str) -> String {
|
||||
let escaped = s
|
||||
.replace('\\', "\\\\")
|
||||
.replace('\n', "\\n")
|
||||
.replace('\r', "\\r")
|
||||
.replace('"', "\\\"");
|
||||
format!("\"{escaped}\"")
|
||||
}
|
||||
|
||||
fn format_attributes(attributes: &[(&str, String)]) -> String {
|
||||
let mut formatted = String::from("[");
|
||||
for (key, value) in attributes {
|
||||
formatted.push_str(&format!(
|
||||
"[{}, {}],",
|
||||
format_string_for_js(key),
|
||||
format_string_for_js(value)
|
||||
));
|
||||
}
|
||||
if formatted.ends_with(',') {
|
||||
formatted.pop();
|
||||
}
|
||||
formatted.push(']');
|
||||
formatted
|
||||
}
|
||||
|
||||
fn create_element_in_head(
|
||||
tag: &str,
|
||||
attributes: &[(&str, String)],
|
||||
children: Option<String>,
|
||||
) -> String {
|
||||
let helpers = include_str!("./js/head.js");
|
||||
let attributes = format_attributes(attributes);
|
||||
let children = children
|
||||
.as_deref()
|
||||
.map(format_string_for_js)
|
||||
.unwrap_or("null".to_string());
|
||||
let tag = format_string_for_js(tag);
|
||||
format!(r#"{helpers};window.createElementInHead({tag}, {attributes}, {children});"#)
|
||||
}
|
||||
|
||||
/// A provider for document-related functionality.
|
||||
///
|
||||
/// Provides things like a history API, a title, a way to run JS, and some other basics/essentials used
|
||||
/// by nearly every platform.
|
||||
///
|
||||
/// An integration with some kind of navigation history.
|
||||
///
|
||||
/// Depending on your use case, your implementation may deviate from the described procedure. This
|
||||
/// is fine, as long as both `current_route` and `current_query` match the described format.
|
||||
///
|
||||
/// However, you should document all deviations. Also, make sure the navigation is user-friendly.
|
||||
/// The described behaviors are designed to mimic a web browser, which most users should already
|
||||
/// know. Deviations might confuse them.
|
||||
pub trait Document: 'static {
|
||||
/// Run `eval` against this document, returning an [`Eval`] that can be used to await the result.
|
||||
fn eval(&self, js: String) -> Eval;
|
||||
|
||||
/// Set the title of the document
|
||||
fn set_title(&self, title: String) {
|
||||
self.eval(format!("document.title = {title:?};"));
|
||||
}
|
||||
|
||||
/// Create a new element in the head
|
||||
fn create_head_element(
|
||||
&self,
|
||||
name: &str,
|
||||
attributes: &[(&str, String)],
|
||||
contents: Option<String>,
|
||||
) {
|
||||
self.eval(create_element_in_head(name, attributes, contents));
|
||||
}
|
||||
|
||||
/// Create a new meta tag in the head
|
||||
fn create_meta(&self, props: MetaProps) {
|
||||
let attributes = props.attributes();
|
||||
self.create_head_element("meta", &attributes, None);
|
||||
}
|
||||
|
||||
/// Create a new script tag in the head
|
||||
fn create_script(&self, props: ScriptProps) {
|
||||
let attributes = props.attributes();
|
||||
match (&props.src, props.script_contents()) {
|
||||
// The script has inline contents, render it as a script tag
|
||||
(_, Ok(contents)) => self.create_head_element("script", &attributes, Some(contents)),
|
||||
// The script has a src, render it as a script tag without a body
|
||||
(Some(_), _) => self.create_head_element("script", &attributes, None),
|
||||
// The script has neither contents nor src, log an error
|
||||
(None, Err(err)) => err.log("Script"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new style tag in the head
|
||||
fn create_style(&self, props: StyleProps) {
|
||||
let mut attributes = props.attributes();
|
||||
match (&props.href, props.style_contents()) {
|
||||
// The style has inline contents, render it as a style tag
|
||||
(_, Ok(contents)) => self.create_head_element("style", &attributes, Some(contents)),
|
||||
// The style has a src, render it as a link tag
|
||||
(Some(_), _) => {
|
||||
attributes.push(("type", "text/css".into()));
|
||||
self.create_head_element("link", &attributes, None)
|
||||
}
|
||||
// The style has neither contents nor src, log an error
|
||||
(None, Err(err)) => err.log("Style"),
|
||||
};
|
||||
}
|
||||
|
||||
/// Create a new link tag in the head
|
||||
fn create_link(&self, props: LinkProps) {
|
||||
let attributes = props.attributes();
|
||||
self.create_head_element("link", &attributes, None);
|
||||
}
|
||||
}
|
||||
|
||||
/// A document that does nothing
|
||||
#[derive(Default)]
|
||||
pub struct NoOpDocument;
|
||||
|
||||
impl Document for NoOpDocument {
|
||||
fn eval(&self, _: String) -> Eval {
|
||||
let owner = generational_box::Owner::default();
|
||||
let boxed = owner.insert(Box::new(NoOpEvaluator {}) as Box<dyn Evaluator + 'static>);
|
||||
Eval::new(boxed)
|
||||
}
|
||||
}
|
||||
|
||||
/// An evaluator that does nothing
|
||||
#[derive(Default)]
|
||||
pub struct NoOpEvaluator;
|
||||
|
||||
impl Evaluator for NoOpEvaluator {
|
||||
fn send(&self, _data: serde_json::Value) -> Result<(), EvalError> {
|
||||
Err(EvalError::Unsupported)
|
||||
}
|
||||
|
||||
fn poll_recv(
|
||||
&mut self,
|
||||
_context: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<serde_json::Value, EvalError>> {
|
||||
std::task::Poll::Ready(Err(EvalError::Unsupported))
|
||||
}
|
||||
|
||||
fn poll_join(
|
||||
&mut self,
|
||||
_context: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<serde_json::Value, EvalError>> {
|
||||
std::task::Poll::Ready(Err(EvalError::Unsupported))
|
||||
}
|
||||
}
|
126
packages/document/src/elements/link.rs
Normal file
126
packages/document/src/elements/link.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
use super::*;
|
||||
use crate::document;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Props, PartialEq)]
|
||||
pub struct LinkProps {
|
||||
pub rel: Option<String>,
|
||||
pub media: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub disabled: Option<bool>,
|
||||
pub r#as: Option<String>,
|
||||
pub sizes: Option<String>,
|
||||
/// Links are deduplicated by their href attribute
|
||||
pub href: Option<String>,
|
||||
pub crossorigin: Option<String>,
|
||||
pub referrerpolicy: Option<String>,
|
||||
pub fetchpriority: Option<String>,
|
||||
pub hreflang: Option<String>,
|
||||
pub integrity: Option<String>,
|
||||
pub r#type: Option<String>,
|
||||
pub blocking: Option<String>,
|
||||
#[props(extends = link, extends = GlobalAttributes)]
|
||||
pub additional_attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl LinkProps {
|
||||
pub(crate) fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut attributes = Vec::new();
|
||||
if let Some(rel) = &self.rel {
|
||||
attributes.push(("rel", rel.clone()));
|
||||
}
|
||||
if let Some(media) = &self.media {
|
||||
attributes.push(("media", media.clone()));
|
||||
}
|
||||
if let Some(title) = &self.title {
|
||||
attributes.push(("title", title.clone()));
|
||||
}
|
||||
if let Some(disabled) = &self.disabled {
|
||||
attributes.push(("disabled", disabled.to_string()));
|
||||
}
|
||||
if let Some(r#as) = &self.r#as {
|
||||
attributes.push(("as", r#as.clone()));
|
||||
}
|
||||
if let Some(sizes) = &self.sizes {
|
||||
attributes.push(("sizes", sizes.clone()));
|
||||
}
|
||||
if let Some(href) = &self.href {
|
||||
attributes.push(("href", href.clone()));
|
||||
}
|
||||
if let Some(crossorigin) = &self.crossorigin {
|
||||
attributes.push(("crossOrigin", crossorigin.clone()));
|
||||
}
|
||||
if let Some(referrerpolicy) = &self.referrerpolicy {
|
||||
attributes.push(("referrerPolicy", referrerpolicy.clone()));
|
||||
}
|
||||
if let Some(fetchpriority) = &self.fetchpriority {
|
||||
attributes.push(("fetchPriority", fetchpriority.clone()));
|
||||
}
|
||||
if let Some(hreflang) = &self.hreflang {
|
||||
attributes.push(("hrefLang", hreflang.clone()));
|
||||
}
|
||||
if let Some(integrity) = &self.integrity {
|
||||
attributes.push(("integrity", integrity.clone()));
|
||||
}
|
||||
if let Some(r#type) = &self.r#type {
|
||||
attributes.push(("type", r#type.clone()));
|
||||
}
|
||||
if let Some(blocking) = &self.blocking {
|
||||
attributes.push(("blocking", blocking.clone()));
|
||||
}
|
||||
attributes
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a [`link`](crate::elements::link) tag into the head of the page.
|
||||
///
|
||||
/// > The [Link](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html) component in dioxus router and this component are completely different.
|
||||
/// > This component links resources in the head of the page, while the router component creates clickable links in the body of the page.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn RedBackground() -> Element {
|
||||
/// rsx! {
|
||||
/// // You can use the meta component to render a meta tag into the head of the page
|
||||
/// // This meta tag will redirect the user to the dioxuslabs homepage in 10 seconds
|
||||
/// document::Link {
|
||||
/// href: asset!("./assets/style.css"),
|
||||
/// rel: "stylesheet",
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// Any updates to the props after the first render will not be reflected in the head.
|
||||
///
|
||||
/// </div>
|
||||
#[doc(alias = "<link>")]
|
||||
#[component]
|
||||
pub fn Link(props: LinkProps) -> Element {
|
||||
use_update_warning(&props, "Link {}");
|
||||
|
||||
use_hook(|| {
|
||||
if let Some(href) = &props.href {
|
||||
if !should_insert_link(href) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let document = document();
|
||||
document.create_link(props);
|
||||
});
|
||||
|
||||
VNode::empty()
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct LinkContext(DeduplicationContext);
|
||||
|
||||
fn should_insert_link(href: &str) -> bool {
|
||||
get_or_insert_root_context::<LinkContext>()
|
||||
.0
|
||||
.should_insert(href)
|
||||
}
|
74
packages/document/src/elements/meta.rs
Normal file
74
packages/document/src/elements/meta.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use super::*;
|
||||
use crate::document;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
#[non_exhaustive]
|
||||
/// Props for the [`Meta`] component
|
||||
#[derive(Clone, Props, PartialEq)]
|
||||
pub struct MetaProps {
|
||||
pub property: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub charset: Option<String>,
|
||||
pub http_equiv: Option<String>,
|
||||
pub content: Option<String>,
|
||||
#[props(extends = meta, extends = GlobalAttributes)]
|
||||
pub additional_attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl MetaProps {
|
||||
pub(crate) fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut attributes = Vec::new();
|
||||
if let Some(property) = &self.property {
|
||||
attributes.push(("property", property.clone()));
|
||||
}
|
||||
if let Some(name) = &self.name {
|
||||
attributes.push(("name", name.clone()));
|
||||
}
|
||||
if let Some(charset) = &self.charset {
|
||||
attributes.push(("charset", charset.clone()));
|
||||
}
|
||||
if let Some(http_equiv) = &self.http_equiv {
|
||||
attributes.push(("http-equiv", http_equiv.clone()));
|
||||
}
|
||||
if let Some(content) = &self.content {
|
||||
attributes.push(("content", content.clone()));
|
||||
}
|
||||
attributes
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a [`meta`](crate::elements::meta) tag into the head of the page.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn RedirectToDioxusHomepageWithoutJS() -> Element {
|
||||
/// rsx! {
|
||||
/// // You can use the meta component to render a meta tag into the head of the page
|
||||
/// // This meta tag will redirect the user to the dioxuslabs homepage in 10 seconds
|
||||
/// document::Meta {
|
||||
/// http_equiv: "refresh",
|
||||
/// content: "10;url=https://dioxuslabs.com",
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// Any updates to the props after the first render will not be reflected in the head.
|
||||
///
|
||||
/// </div>
|
||||
#[component]
|
||||
#[doc(alias = "<meta>")]
|
||||
pub fn Meta(props: MetaProps) -> Element {
|
||||
use_update_warning(&props, "Meta {}");
|
||||
|
||||
use_hook(|| {
|
||||
let document = document();
|
||||
document.create_meta(props);
|
||||
});
|
||||
|
||||
VNode::empty()
|
||||
}
|
124
packages/document/src/elements/mod.rs
Normal file
124
packages/document/src/elements/mod.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
#![doc = include_str!("../../docs/head.md")]
|
||||
|
||||
use std::{cell::RefCell, collections::HashSet, rc::Rc};
|
||||
|
||||
use dioxus_core::{prelude::*, DynamicNode};
|
||||
use dioxus_core_macro::*;
|
||||
|
||||
mod link;
|
||||
pub use link::*;
|
||||
mod meta;
|
||||
pub use meta::*;
|
||||
mod script;
|
||||
pub use script::*;
|
||||
mod style;
|
||||
pub use style::*;
|
||||
mod title;
|
||||
pub use title::*;
|
||||
|
||||
/// Warn the user if they try to change props on a element that is injected into the head
|
||||
#[allow(unused)]
|
||||
fn use_update_warning<T: PartialEq + Clone + 'static>(value: &T, name: &'static str) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let cloned_value = value.clone();
|
||||
let initial = use_hook(move || value.clone());
|
||||
|
||||
if initial != cloned_value {
|
||||
tracing::warn!("Changing the props of `{name}` is not supported ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that can occur when extracting a single text node from a component
|
||||
pub enum ExtractSingleTextNodeError<'a> {
|
||||
/// The node contained an render error, so we can't extract the text node
|
||||
RenderError(&'a RenderError),
|
||||
/// There was only one child, but it wasn't a text node
|
||||
NonTextNode,
|
||||
/// There is multiple child nodes
|
||||
NonTemplate,
|
||||
}
|
||||
|
||||
impl ExtractSingleTextNodeError<'_> {
|
||||
/// Log a warning depending on the error
|
||||
pub fn log(&self, component: &str) {
|
||||
match self {
|
||||
ExtractSingleTextNodeError::RenderError(err) => {
|
||||
tracing::error!("Error while rendering {component}: {err}");
|
||||
}
|
||||
ExtractSingleTextNodeError::NonTextNode => {
|
||||
tracing::error!(
|
||||
"Error while rendering {component}: The children of {component} must be a single text node"
|
||||
);
|
||||
}
|
||||
ExtractSingleTextNodeError::NonTemplate => {
|
||||
tracing::error!(
|
||||
"Error while rendering {component}: The children of {component} must be a single text node"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_single_text_node(children: &Element) -> Result<String, ExtractSingleTextNodeError<'_>> {
|
||||
let vnode = match children {
|
||||
Element::Ok(vnode) => vnode,
|
||||
Element::Err(err) => {
|
||||
return Err(ExtractSingleTextNodeError::RenderError(err));
|
||||
}
|
||||
};
|
||||
// The title's children must be in one of two forms:
|
||||
// 1. rsx! { "static text" }
|
||||
// 2. rsx! { "title: {dynamic_text}" }
|
||||
match vnode.template {
|
||||
// rsx! { "static text" }
|
||||
Template {
|
||||
roots: &[TemplateNode::Text { text }],
|
||||
node_paths: &[],
|
||||
attr_paths: &[],
|
||||
..
|
||||
} => Ok(text.to_string()),
|
||||
// rsx! { "title: {dynamic_text}" }
|
||||
Template {
|
||||
roots: &[TemplateNode::Dynamic { id }],
|
||||
node_paths: &[&[0]],
|
||||
attr_paths: &[],
|
||||
..
|
||||
} => {
|
||||
let node = &vnode.dynamic_nodes[id];
|
||||
match node {
|
||||
DynamicNode::Text(text) => Ok(text.value.clone()),
|
||||
_ => Err(ExtractSingleTextNodeError::NonTextNode),
|
||||
}
|
||||
}
|
||||
_ => Err(ExtractSingleTextNodeError::NonTemplate),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_or_insert_root_context<T: Default + Clone + 'static>() -> T {
|
||||
match ScopeId::ROOT.has_context::<T>() {
|
||||
Some(context) => context,
|
||||
None => {
|
||||
let context = T::default();
|
||||
ScopeId::ROOT.provide_context(context.clone());
|
||||
context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct DeduplicationContext(Rc<RefCell<HashSet<String>>>);
|
||||
|
||||
impl DeduplicationContext {
|
||||
fn should_insert(&self, href: &str) -> bool {
|
||||
let mut set = self.0.borrow_mut();
|
||||
let present = set.contains(href);
|
||||
if !present {
|
||||
set.insert(href.to_string());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
113
packages/document/src/elements/script.rs
Normal file
113
packages/document/src/elements/script.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
use super::*;
|
||||
use crate::document;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Props, PartialEq)]
|
||||
pub struct ScriptProps {
|
||||
/// The contents of the script tag. If present, the children must be a single text node.
|
||||
pub children: Element,
|
||||
/// Scripts are deduplicated by their src attribute
|
||||
pub src: Option<String>,
|
||||
pub defer: Option<bool>,
|
||||
pub crossorigin: Option<String>,
|
||||
pub fetchpriority: Option<String>,
|
||||
pub integrity: Option<String>,
|
||||
pub nomodule: Option<bool>,
|
||||
pub nonce: Option<String>,
|
||||
pub referrerpolicy: Option<String>,
|
||||
pub r#type: Option<String>,
|
||||
#[props(extends = script, extends = GlobalAttributes)]
|
||||
pub additional_attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl ScriptProps {
|
||||
pub(crate) fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut attributes = Vec::new();
|
||||
if let Some(defer) = &self.defer {
|
||||
attributes.push(("defer", defer.to_string()));
|
||||
}
|
||||
if let Some(crossorigin) = &self.crossorigin {
|
||||
attributes.push(("crossorigin", crossorigin.clone()));
|
||||
}
|
||||
if let Some(fetchpriority) = &self.fetchpriority {
|
||||
attributes.push(("fetchpriority", fetchpriority.clone()));
|
||||
}
|
||||
if let Some(integrity) = &self.integrity {
|
||||
attributes.push(("integrity", integrity.clone()));
|
||||
}
|
||||
if let Some(nomodule) = &self.nomodule {
|
||||
attributes.push(("nomodule", nomodule.to_string()));
|
||||
}
|
||||
if let Some(nonce) = &self.nonce {
|
||||
attributes.push(("nonce", nonce.clone()));
|
||||
}
|
||||
if let Some(referrerpolicy) = &self.referrerpolicy {
|
||||
attributes.push(("referrerpolicy", referrerpolicy.clone()));
|
||||
}
|
||||
if let Some(r#type) = &self.r#type {
|
||||
attributes.push(("type", r#type.clone()));
|
||||
}
|
||||
if let Some(src) = &self.src {
|
||||
attributes.push(("src", src.clone()));
|
||||
}
|
||||
attributes
|
||||
}
|
||||
|
||||
pub fn script_contents(&self) -> Result<String, ExtractSingleTextNodeError<'_>> {
|
||||
extract_single_text_node(&self.children)
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a [`script`](crate::elements::script) tag into the head of the page.
|
||||
///
|
||||
///
|
||||
/// If present, the children of the script component must be a single static or formatted string. If there are more children or the children contain components, conditionals, loops, or fragments, the script will not be added.
|
||||
///
|
||||
///
|
||||
/// Any scripts you add will be deduplicated by their `src` attribute (if present).
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn LoadScript() -> Element {
|
||||
/// rsx! {
|
||||
/// // You can use the Script component to render a script tag into the head of the page
|
||||
/// document::Script {
|
||||
/// src: asset!("./assets/script.js"),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// Any updates to the props after the first render will not be reflected in the head.
|
||||
///
|
||||
/// </div>
|
||||
#[component]
|
||||
pub fn Script(props: ScriptProps) -> Element {
|
||||
use_update_warning(&props, "Script {}");
|
||||
|
||||
use_hook(|| {
|
||||
if let Some(src) = &props.src {
|
||||
if !should_insert_script(src) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let document = document();
|
||||
document.create_script(props);
|
||||
});
|
||||
|
||||
VNode::empty()
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct ScriptContext(DeduplicationContext);
|
||||
|
||||
fn should_insert_script(src: &str) -> bool {
|
||||
get_or_insert_root_context::<ScriptContext>()
|
||||
.0
|
||||
.should_insert(src)
|
||||
}
|
93
packages/document/src/elements/style.rs
Normal file
93
packages/document/src/elements/style.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use super::*;
|
||||
use crate::document;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Props, PartialEq)]
|
||||
pub struct StyleProps {
|
||||
/// Styles are deduplicated by their href attribute
|
||||
pub href: Option<String>,
|
||||
pub media: Option<String>,
|
||||
pub nonce: Option<String>,
|
||||
pub title: Option<String>,
|
||||
/// The contents of the style tag. If present, the children must be a single text node.
|
||||
pub children: Element,
|
||||
#[props(extends = style, extends = GlobalAttributes)]
|
||||
pub additional_attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl StyleProps {
|
||||
pub(crate) fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut attributes = Vec::new();
|
||||
if let Some(href) = &self.href {
|
||||
attributes.push(("href", href.clone()));
|
||||
}
|
||||
if let Some(media) = &self.media {
|
||||
attributes.push(("media", media.clone()));
|
||||
}
|
||||
if let Some(nonce) = &self.nonce {
|
||||
attributes.push(("nonce", nonce.clone()));
|
||||
}
|
||||
if let Some(title) = &self.title {
|
||||
attributes.push(("title", title.clone()));
|
||||
}
|
||||
attributes
|
||||
}
|
||||
|
||||
pub fn style_contents(&self) -> Result<String, ExtractSingleTextNodeError<'_>> {
|
||||
extract_single_text_node(&self.children)
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a [`style`](crate::elements::style) tag into the head of the page.
|
||||
///
|
||||
/// If present, the children of the style component must be a single static or formatted string. If there are more children or the children contain components, conditionals, loops, or fragments, the style will not be added.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn RedBackground() -> Element {
|
||||
/// rsx! {
|
||||
/// // You can use the style component to render a style tag into the head of the page
|
||||
/// // This style tag will set the background color of the page to red
|
||||
/// document::Style {
|
||||
/// r#"
|
||||
/// body {{
|
||||
/// background-color: red;
|
||||
/// }}
|
||||
/// "#
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// Any updates to the props after the first render will not be reflected in the head.
|
||||
///
|
||||
/// </div>
|
||||
#[component]
|
||||
pub fn Style(props: StyleProps) -> Element {
|
||||
use_update_warning(&props, "Style {}");
|
||||
|
||||
use_hook(|| {
|
||||
if let Some(href) = &props.href {
|
||||
if !should_insert_style(href) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let document = document();
|
||||
document.create_style(props);
|
||||
});
|
||||
|
||||
VNode::empty()
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct StyleContext(DeduplicationContext);
|
||||
|
||||
fn should_insert_style(href: &str) -> bool {
|
||||
get_or_insert_root_context::<StyleContext>()
|
||||
.0
|
||||
.should_insert(href)
|
||||
}
|
57
packages/document/src/elements/title.rs
Normal file
57
packages/document/src/elements/title.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
use crate::document;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Props, PartialEq)]
|
||||
pub struct TitleProps {
|
||||
/// The contents of the title tag. The children must be a single text node.
|
||||
children: Element,
|
||||
}
|
||||
|
||||
/// Render the title of the page. On web renderers, this will set the [title](crate::elements::title) in the head. On desktop, it will set the window title.
|
||||
///
|
||||
/// Unlike most head components, the Title can be modified after the first render. Only the latest update to the title will be reflected if multiple title components are rendered.
|
||||
///
|
||||
///
|
||||
/// The children of the title component must be a single static or formatted string. If there are more children or the children contain components, conditionals, loops, or fragments, the title will not be updated.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn App() -> Element {
|
||||
/// rsx! {
|
||||
/// // You can use the Title component to render a title tag into the head of the page or window
|
||||
/// document::Title { "My Page" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[component]
|
||||
#[doc(alias = "<title>")]
|
||||
pub fn Title(props: TitleProps) -> Element {
|
||||
let children = props.children;
|
||||
let text = match extract_single_text_node(&children) {
|
||||
Ok(text) => text,
|
||||
Err(err) => {
|
||||
err.log("Title");
|
||||
return VNode::empty();
|
||||
}
|
||||
};
|
||||
|
||||
// Update the title as it changes. NOTE: We don't use use_effect here because we need this to run on the server
|
||||
let document = use_hook(document);
|
||||
let last_text = use_hook(|| {
|
||||
// Set the title initially
|
||||
document.set_title(text.clone());
|
||||
Rc::new(RefCell::new(text.clone()))
|
||||
});
|
||||
|
||||
// If the text changes, update the title
|
||||
let mut last_text = last_text.borrow_mut();
|
||||
if text != *last_text {
|
||||
document.set_title(text.clone());
|
||||
*last_text = text;
|
||||
}
|
||||
|
||||
VNode::empty()
|
||||
}
|
36
packages/document/src/error.rs
Normal file
36
packages/document/src/error.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
|
||||
/// Represents an error when evaluating JavaScript
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum EvalError {
|
||||
/// The platform does not support evaluating JavaScript.
|
||||
Unsupported,
|
||||
|
||||
/// The provided JavaScript has already been ran.
|
||||
Finished,
|
||||
|
||||
/// The provided JavaScript is not valid and can't be ran.
|
||||
InvalidJs(String),
|
||||
|
||||
/// Represents an error communicating between JavaScript and Rust.
|
||||
Communication(String),
|
||||
|
||||
/// Represents an error serializing or deserializing the result of an eval
|
||||
Serialization(serde_json::Error),
|
||||
}
|
||||
|
||||
impl Display for EvalError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
EvalError::Unsupported => write!(f, "EvalError::Unsupported - eval is not supported on the current platform"),
|
||||
EvalError::Finished => write!(f, "EvalError::Finished - eval has already ran"),
|
||||
EvalError::InvalidJs(_) => write!(f, "EvalError::InvalidJs - the provided javascript is invalid"),
|
||||
EvalError::Communication(_) => write!(f, "EvalError::Communication - there was an error trying to communicate with between javascript and rust"),
|
||||
EvalError::Serialization(_) => write!(f, "EvalError::Serialization - there was an error trying to serialize or deserialize the result of an eval"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for EvalError {}
|
74
packages/document/src/eval.rs
Normal file
74
packages/document/src/eval.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
#![doc = include_str!("../docs/eval.md")]
|
||||
|
||||
use crate::error::EvalError;
|
||||
use generational_box::GenerationalBox;
|
||||
use std::future::{poll_fn, Future, IntoFuture};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[doc = include_str!("../docs/eval.md")]
|
||||
pub struct Eval {
|
||||
evaluator: GenerationalBox<Box<dyn Evaluator>>,
|
||||
}
|
||||
|
||||
impl Eval {
|
||||
/// Create this eval from a dynamic evaluator
|
||||
pub fn new(evaluator: GenerationalBox<Box<dyn Evaluator + 'static>>) -> Self {
|
||||
Self { evaluator }
|
||||
}
|
||||
|
||||
/// Wait until the javascript task is finished and return the result
|
||||
pub async fn join<T: serde::de::DeserializeOwned>(self) -> Result<T, EvalError> {
|
||||
let json_value = poll_fn(|cx| match self.evaluator.try_write() {
|
||||
Ok(mut evaluator) => evaluator.poll_join(cx),
|
||||
Err(_) => Poll::Ready(Err(EvalError::Finished)),
|
||||
})
|
||||
.await?;
|
||||
serde_json::from_value(json_value).map_err(EvalError::Serialization)
|
||||
}
|
||||
|
||||
/// Send a message to the javascript task
|
||||
pub fn send(&self, data: impl serde::Serialize) -> Result<(), EvalError> {
|
||||
match self.evaluator.try_read() {
|
||||
Ok(evaluator) => {
|
||||
evaluator.send(serde_json::to_value(data).map_err(EvalError::Serialization)?)
|
||||
}
|
||||
Err(_) => Err(EvalError::Finished),
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive a message from the javascript task
|
||||
pub async fn recv<T: serde::de::DeserializeOwned>(&mut self) -> Result<T, EvalError> {
|
||||
let json_value = poll_fn(|cx| match self.evaluator.try_write() {
|
||||
Ok(mut evaluator) => evaluator.poll_recv(cx),
|
||||
Err(_) => Poll::Ready(Err(EvalError::Finished)),
|
||||
})
|
||||
.await?;
|
||||
serde_json::from_value(json_value).map_err(EvalError::Serialization)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFuture for Eval {
|
||||
type Output = Result<serde_json::Value, EvalError>;
|
||||
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
Box::pin(self.join().into_future())
|
||||
}
|
||||
}
|
||||
|
||||
/// The platform's evaluator.
|
||||
pub trait Evaluator {
|
||||
/// Sends a message to the evaluated JavaScript.
|
||||
fn send(&self, data: serde_json::Value) -> Result<(), EvalError>;
|
||||
/// Receive any queued messages from the evaluated JavaScript.
|
||||
fn poll_recv(
|
||||
&mut self,
|
||||
context: &mut Context<'_>,
|
||||
) -> Poll<Result<serde_json::Value, EvalError>>;
|
||||
/// Gets the return value of the JavaScript
|
||||
fn poll_join(
|
||||
&mut self,
|
||||
context: &mut Context<'_>,
|
||||
) -> Poll<Result<serde_json::Value, EvalError>>;
|
||||
}
|
31
packages/document/src/lib.rs
Normal file
31
packages/document/src/lib.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
mod document;
|
||||
mod elements;
|
||||
mod error;
|
||||
mod eval;
|
||||
|
||||
pub use document::*;
|
||||
pub use elements::*;
|
||||
pub use error::*;
|
||||
pub use eval::*;
|
||||
|
||||
/// Get the document provider for the current platform or a no-op provider if the platform doesn't document functionality.
|
||||
pub fn document() -> Rc<dyn Document> {
|
||||
match dioxus_core::prelude::try_consume_context::<Rc<dyn Document>>() {
|
||||
Some(document) => document,
|
||||
None => {
|
||||
tracing::error!(
|
||||
"Unable to find a document in the renderer. Using the default no-op document."
|
||||
);
|
||||
Rc::new(NoOpDocument)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate some javascript in the current document
|
||||
#[doc = include_str!("../docs/eval.md")]
|
||||
#[doc(alias = "javascript")]
|
||||
pub fn eval(script: &str) -> Eval {
|
||||
document().eval(script.to_string())
|
||||
}
|
18
packages/document/tsconfig.json
Normal file
18
packages/document/tsconfig.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"lib": [
|
||||
"ES2015",
|
||||
"DOM",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"ESNext"
|
||||
],
|
||||
"noImplicitAny": true,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
},
|
||||
"exclude": [
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
|
@ -4,9 +4,8 @@
|
|||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use dioxus_lib::{html::document::*, prelude::*};
|
||||
use dioxus_lib::{document::*, prelude::*};
|
||||
use dioxus_ssr::Renderer;
|
||||
use generational_box::GenerationalBox;
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
|
@ -71,8 +70,8 @@ impl ServerDocument {
|
|||
}
|
||||
|
||||
impl Document for ServerDocument {
|
||||
fn new_evaluator(&self, js: String) -> GenerationalBox<Box<dyn Evaluator>> {
|
||||
NoOpDocument.new_evaluator(js)
|
||||
fn eval(&self, js: String) -> Eval {
|
||||
NoOpDocument.eval(js)
|
||||
}
|
||||
|
||||
fn set_title(&self, title: String) {
|
||||
|
@ -151,7 +150,7 @@ impl Document for ServerDocument {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_link(&self, props: head::LinkProps) {
|
||||
fn create_link(&self, props: LinkProps) {
|
||||
self.warn_if_streaming();
|
||||
self.serialize_for_hydration();
|
||||
self.0.borrow_mut().link.push(rsx! {
|
||||
|
@ -173,8 +172,4 @@ impl Document for ServerDocument {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![allow(unused)]
|
||||
//! On the client, we use the [`WebDocument`] implementation to render the head for any elements that were not rendered on the server.
|
||||
|
||||
use dioxus_lib::events::Document;
|
||||
use dioxus_lib::document::*;
|
||||
use dioxus_web::WebDocument;
|
||||
|
||||
fn head_element_written_on_server() -> bool {
|
||||
|
@ -15,11 +15,8 @@ fn head_element_written_on_server() -> bool {
|
|||
pub struct FullstackWebDocument;
|
||||
|
||||
impl Document for FullstackWebDocument {
|
||||
fn new_evaluator(
|
||||
&self,
|
||||
js: String,
|
||||
) -> generational_box::GenerationalBox<Box<dyn dioxus_lib::prelude::document::Evaluator>> {
|
||||
WebDocument.new_evaluator(js)
|
||||
fn eval(&self, js: String) -> Eval {
|
||||
WebDocument.eval(js)
|
||||
}
|
||||
|
||||
fn set_title(&self, title: String) {
|
||||
|
@ -29,35 +26,31 @@ impl Document for FullstackWebDocument {
|
|||
WebDocument.set_title(title);
|
||||
}
|
||||
|
||||
fn create_meta(&self, props: dioxus_lib::prelude::MetaProps) {
|
||||
fn create_meta(&self, props: MetaProps) {
|
||||
if head_element_written_on_server() {
|
||||
return;
|
||||
}
|
||||
WebDocument.create_meta(props);
|
||||
}
|
||||
|
||||
fn create_script(&self, props: dioxus_lib::prelude::ScriptProps) {
|
||||
fn create_script(&self, props: ScriptProps) {
|
||||
if head_element_written_on_server() {
|
||||
return;
|
||||
}
|
||||
WebDocument.create_script(props);
|
||||
}
|
||||
|
||||
fn create_style(&self, props: dioxus_lib::prelude::StyleProps) {
|
||||
fn create_style(&self, props: StyleProps) {
|
||||
if head_element_written_on_server() {
|
||||
return;
|
||||
}
|
||||
WebDocument.create_style(props);
|
||||
}
|
||||
|
||||
fn create_link(&self, props: dioxus_lib::prelude::head::LinkProps) {
|
||||
fn create_link(&self, props: LinkProps) {
|
||||
if head_element_written_on_server() {
|
||||
return;
|
||||
}
|
||||
WebDocument.create_link(props);
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
//! A shared pool of renderers for efficient server side rendering.
|
||||
use crate::document::ServerDocument;
|
||||
use crate::streaming::{Mount, StreamingRenderer};
|
||||
use dioxus_interpreter_js::INITIALIZE_STREAMING_JS;
|
||||
use dioxus_isrg::{CachedRender, RenderFreshness};
|
||||
use dioxus_lib::document::Document;
|
||||
use dioxus_ssr::Renderer;
|
||||
use futures_channel::mpsc::Sender;
|
||||
use futures_util::{Stream, StreamExt};
|
||||
|
@ -165,6 +167,7 @@ impl SsrRendererPool {
|
|||
let join_handle = spawn_platform(move || async move {
|
||||
let mut virtual_dom = virtual_dom_factory();
|
||||
let document = std::rc::Rc::new(crate::document::server::ServerDocument::default());
|
||||
virtual_dom.provide_root_context(document.clone());
|
||||
virtual_dom.provide_root_context(document.clone() as std::rc::Rc<dyn Document>);
|
||||
|
||||
// poll the future, which may call server_context()
|
||||
|
@ -431,11 +434,8 @@ impl FullstackHTMLTemplate {
|
|||
let ServeConfig { index, .. } = &self.cfg;
|
||||
|
||||
let title = {
|
||||
let document: Option<std::rc::Rc<dyn Document>> =
|
||||
let document: Option<std::rc::Rc<ServerDocument>> =
|
||||
virtual_dom.in_runtime(|| ScopeId::ROOT.consume_context());
|
||||
let document: Option<&crate::document::server::ServerDocument> = document
|
||||
.as_ref()
|
||||
.and_then(|document| document.as_any().downcast_ref());
|
||||
// Collect any head content from the document provider and inject that into the head
|
||||
document.and_then(|document| document.title())
|
||||
};
|
||||
|
@ -448,11 +448,8 @@ impl FullstackHTMLTemplate {
|
|||
}
|
||||
to.write_str(&index.head_after_title)?;
|
||||
|
||||
let document: Option<std::rc::Rc<dyn dioxus_lib::prelude::document::Document>> =
|
||||
let document: Option<std::rc::Rc<ServerDocument>> =
|
||||
virtual_dom.in_runtime(|| ScopeId::ROOT.consume_context());
|
||||
let document: Option<&crate::document::server::ServerDocument> = document
|
||||
.as_ref()
|
||||
.and_then(|document| document.as_any().downcast_ref());
|
||||
if let Some(document) = document {
|
||||
// Collect any head content from the document provider and inject that into the head
|
||||
document.render(to)?;
|
||||
|
|
|
@ -16,7 +16,7 @@ fn MyComponent() -> Element {
|
|||
let count = count.read();
|
||||
|
||||
// You can use the count value to update the DOM manually
|
||||
eval(&format!(
|
||||
document::eval(&format!(
|
||||
r#"var c = document.getElementById("dioxus-canvas");
|
||||
var ctx = c.getContext("2d");
|
||||
ctx.font = "30px Arial";
|
||||
|
|
|
@ -41,7 +41,7 @@ tokio = { workspace = true, features = ["time"] }
|
|||
manganis = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["serialize", "mounted", "document", "file_engine"]
|
||||
default = ["serialize", "mounted", "file_engine"]
|
||||
serialize = [
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
|
@ -51,10 +51,6 @@ serialize = [
|
|||
"dioxus-core/serialize"
|
||||
]
|
||||
mounted = []
|
||||
document = [
|
||||
"dep:serde",
|
||||
"dep:serde_json"
|
||||
]
|
||||
file_engine = [
|
||||
"dep:async-trait",
|
||||
]
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
#![allow(clippy::await_holding_refcell_ref)]
|
||||
#![doc = include_str!("../../docs/eval.md")]
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
use generational_box::GenerationalBox;
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
use std::future::{poll_fn, Future, IntoFuture};
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use super::document;
|
||||
|
||||
/// The platform's evaluator.
|
||||
pub trait Evaluator {
|
||||
/// Sends a message to the evaluated JavaScript.
|
||||
fn send(&self, data: serde_json::Value) -> Result<(), EvalError>;
|
||||
/// Receive any queued messages from the evaluated JavaScript.
|
||||
fn poll_recv(
|
||||
&mut self,
|
||||
context: &mut Context<'_>,
|
||||
) -> Poll<Result<serde_json::Value, EvalError>>;
|
||||
/// Gets the return value of the JavaScript
|
||||
fn poll_join(
|
||||
&mut self,
|
||||
context: &mut Context<'_>,
|
||||
) -> Poll<Result<serde_json::Value, EvalError>>;
|
||||
}
|
||||
|
||||
type EvalCreator = Rc<dyn Fn(&str) -> UseEval>;
|
||||
|
||||
/// Get a struct that can execute any JavaScript.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Please be very careful with this function. A script with too many dynamic
|
||||
/// parts is practically asking for a hacker to find an XSS vulnerability in
|
||||
/// it. **This applies especially to web targets, where the JavaScript context
|
||||
/// has access to most, if not all of your application data.**
|
||||
#[must_use]
|
||||
pub fn eval_provider() -> EvalCreator {
|
||||
let eval_provider = document();
|
||||
|
||||
Rc::new(move |script: &str| UseEval::new(eval_provider.new_evaluator(script.to_string())))
|
||||
as Rc<dyn Fn(&str) -> UseEval>
|
||||
}
|
||||
|
||||
#[doc = include_str!("../../docs/eval.md")]
|
||||
#[doc(alias = "javascript")]
|
||||
pub fn eval(script: &str) -> UseEval {
|
||||
let document = use_hook(document);
|
||||
UseEval::new(document.new_evaluator(script.to_string()))
|
||||
}
|
||||
|
||||
/// A wrapper around the target platform's evaluator that lets you send and receive data from JavaScript spawned by [`eval`].
|
||||
///
|
||||
#[doc = include_str!("../../docs/eval.md")]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UseEval {
|
||||
evaluator: GenerationalBox<Box<dyn Evaluator>>,
|
||||
}
|
||||
|
||||
impl UseEval {
|
||||
/// Creates a new UseEval
|
||||
pub fn new(evaluator: GenerationalBox<Box<dyn Evaluator + 'static>>) -> Self {
|
||||
Self { evaluator }
|
||||
}
|
||||
|
||||
/// Sends a [`serde_json::Value`] to the evaluated JavaScript.
|
||||
pub fn send(&self, data: serde_json::Value) -> Result<(), EvalError> {
|
||||
match self.evaluator.try_read() {
|
||||
Ok(evaluator) => evaluator.send(data),
|
||||
Err(_) => Err(EvalError::Finished),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript.
|
||||
pub async fn recv(&mut self) -> Result<serde_json::Value, EvalError> {
|
||||
poll_fn(|cx| match self.evaluator.try_write() {
|
||||
Ok(mut evaluator) => evaluator.poll_recv(cx),
|
||||
Err(_) => Poll::Ready(Err(EvalError::Finished)),
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets the return value of the evaluated JavaScript.
|
||||
pub async fn join(self) -> Result<serde_json::Value, EvalError> {
|
||||
poll_fn(|cx| match self.evaluator.try_write() {
|
||||
Ok(mut evaluator) => evaluator.poll_join(cx),
|
||||
Err(_) => Poll::Ready(Err(EvalError::Finished)),
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFuture for UseEval {
|
||||
type Output = Result<serde_json::Value, EvalError>;
|
||||
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
Box::pin(self.join())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an error when evaluating JavaScript
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum EvalError {
|
||||
/// The platform does not support evaluating JavaScript.
|
||||
Unsupported,
|
||||
|
||||
/// The provided JavaScript has already been ran.
|
||||
Finished,
|
||||
|
||||
/// The provided JavaScript is not valid and can't be ran.
|
||||
InvalidJs(String),
|
||||
|
||||
/// Represents an error communicating between JavaScript and Rust.
|
||||
Communication(String),
|
||||
}
|
||||
|
||||
impl Display for EvalError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
EvalError::Unsupported => write!(f, "EvalError::Unsupported - eval is not supported on the current platform"),
|
||||
EvalError::Finished => write!(f, "EvalError::Finished - eval has already ran"),
|
||||
EvalError::InvalidJs(_) => write!(f, "EvalError::InvalidJs - the provided javascript is invalid"),
|
||||
EvalError::Communication(_) => write!(f, "EvalError::Communication - there was an error trying to communicate with between javascript and rust"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for EvalError {}
|
|
@ -1,562 +0,0 @@
|
|||
#![doc = include_str!("../../docs/head.md")]
|
||||
|
||||
use std::{cell::RefCell, collections::HashSet, rc::Rc};
|
||||
|
||||
use crate as dioxus_elements;
|
||||
use dioxus_core::{prelude::*, DynamicNode};
|
||||
use dioxus_core_macro::*;
|
||||
|
||||
/// Warn the user if they try to change props on a element that is injected into the head
|
||||
#[allow(unused)]
|
||||
fn use_update_warning<T: PartialEq + Clone + 'static>(value: &T, name: &'static str) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let cloned_value = value.clone();
|
||||
let initial = use_hook(move || value.clone());
|
||||
|
||||
if initial != cloned_value {
|
||||
tracing::warn!("Changing the props of `{name}` is not supported ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that can occur when extracting a single text node from a component
|
||||
pub enum ExtractSingleTextNodeError<'a> {
|
||||
/// The node contained an render error, so we can't extract the text node
|
||||
RenderError(&'a RenderError),
|
||||
/// There was only one child, but it wasn't a text node
|
||||
NonTextNode,
|
||||
/// There is multiple child nodes
|
||||
NonTemplate,
|
||||
}
|
||||
|
||||
impl ExtractSingleTextNodeError<'_> {
|
||||
/// Log a warning depending on the error
|
||||
pub fn log(&self, component: &str) {
|
||||
match self {
|
||||
ExtractSingleTextNodeError::RenderError(err) => {
|
||||
tracing::error!("Error while rendering {component}: {err}");
|
||||
}
|
||||
ExtractSingleTextNodeError::NonTextNode => {
|
||||
tracing::error!(
|
||||
"Error while rendering {component}: The children of {component} must be a single text node"
|
||||
);
|
||||
}
|
||||
ExtractSingleTextNodeError::NonTemplate => {
|
||||
tracing::error!(
|
||||
"Error while rendering {component}: The children of {component} must be a single text node"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_single_text_node(children: &Element) -> Result<String, ExtractSingleTextNodeError<'_>> {
|
||||
let vnode = match children {
|
||||
Element::Ok(vnode) => vnode,
|
||||
Element::Err(err) => {
|
||||
return Err(ExtractSingleTextNodeError::RenderError(err));
|
||||
}
|
||||
};
|
||||
// The title's children must be in one of two forms:
|
||||
// 1. rsx! { "static text" }
|
||||
// 2. rsx! { "title: {dynamic_text}" }
|
||||
match vnode.template {
|
||||
// rsx! { "static text" }
|
||||
Template {
|
||||
roots: &[TemplateNode::Text { text }],
|
||||
node_paths: &[],
|
||||
attr_paths: &[],
|
||||
..
|
||||
} => Ok(text.to_string()),
|
||||
// rsx! { "title: {dynamic_text}" }
|
||||
Template {
|
||||
roots: &[TemplateNode::Dynamic { id }],
|
||||
node_paths: &[&[0]],
|
||||
attr_paths: &[],
|
||||
..
|
||||
} => {
|
||||
let node = &vnode.dynamic_nodes[id];
|
||||
match node {
|
||||
DynamicNode::Text(text) => Ok(text.value.clone()),
|
||||
_ => Err(ExtractSingleTextNodeError::NonTextNode),
|
||||
}
|
||||
}
|
||||
_ => Err(ExtractSingleTextNodeError::NonTemplate),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Props, PartialEq)]
|
||||
pub struct TitleProps {
|
||||
/// The contents of the title tag. The children must be a single text node.
|
||||
children: Element,
|
||||
}
|
||||
|
||||
/// Render the title of the page. On web renderers, this will set the [title](crate::elements::title) in the head. On desktop, it will set the window title.
|
||||
///
|
||||
/// Unlike most head components, the Title can be modified after the first render. Only the latest update to the title will be reflected if multiple title components are rendered.
|
||||
///
|
||||
///
|
||||
/// The children of the title component must be a single static or formatted string. If there are more children or the children contain components, conditionals, loops, or fragments, the title will not be updated.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn App() -> Element {
|
||||
/// rsx! {
|
||||
/// // You can use the Title component to render a title tag into the head of the page or window
|
||||
/// Title { "My Page" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[component]
|
||||
pub fn Title(props: TitleProps) -> Element {
|
||||
let children = props.children;
|
||||
let text = match extract_single_text_node(&children) {
|
||||
Ok(text) => text,
|
||||
Err(err) => {
|
||||
err.log("Title");
|
||||
return VNode::empty();
|
||||
}
|
||||
};
|
||||
|
||||
// Update the title as it changes. NOTE: We don't use use_effect here because we need this to run on the server
|
||||
let document = use_hook(document);
|
||||
let last_text = use_hook(|| {
|
||||
// Set the title initially
|
||||
document.set_title(text.clone());
|
||||
Rc::new(RefCell::new(text.clone()))
|
||||
});
|
||||
|
||||
// If the text changes, update the title
|
||||
let mut last_text = last_text.borrow_mut();
|
||||
if text != *last_text {
|
||||
document.set_title(text.clone());
|
||||
*last_text = text;
|
||||
}
|
||||
|
||||
VNode::empty()
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
/// Props for the [`Meta`] component
|
||||
#[derive(Clone, Props, PartialEq)]
|
||||
pub struct MetaProps {
|
||||
pub property: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub charset: Option<String>,
|
||||
pub http_equiv: Option<String>,
|
||||
pub content: Option<String>,
|
||||
#[props(extends = meta, extends = GlobalAttributes)]
|
||||
pub additional_attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl MetaProps {
|
||||
pub(crate) fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut attributes = Vec::new();
|
||||
if let Some(property) = &self.property {
|
||||
attributes.push(("property", property.clone()));
|
||||
}
|
||||
if let Some(name) = &self.name {
|
||||
attributes.push(("name", name.clone()));
|
||||
}
|
||||
if let Some(charset) = &self.charset {
|
||||
attributes.push(("charset", charset.clone()));
|
||||
}
|
||||
if let Some(http_equiv) = &self.http_equiv {
|
||||
attributes.push(("http-equiv", http_equiv.clone()));
|
||||
}
|
||||
if let Some(content) = &self.content {
|
||||
attributes.push(("content", content.clone()));
|
||||
}
|
||||
attributes
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a [`meta`](crate::elements::meta) tag into the head of the page.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn RedirectToDioxusHomepageWithoutJS() -> Element {
|
||||
/// rsx! {
|
||||
/// // You can use the meta component to render a meta tag into the head of the page
|
||||
/// // This meta tag will redirect the user to the dioxuslabs homepage in 10 seconds
|
||||
/// Meta {
|
||||
/// http_equiv: "refresh",
|
||||
/// content: "10;url=https://dioxuslabs.com",
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// Any updates to the props after the first render will not be reflected in the head.
|
||||
///
|
||||
/// </div>
|
||||
#[component]
|
||||
pub fn Meta(props: MetaProps) -> Element {
|
||||
use_update_warning(&props, "Meta {}");
|
||||
|
||||
use_hook(|| {
|
||||
let document = document();
|
||||
document.create_meta(props);
|
||||
});
|
||||
|
||||
VNode::empty()
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Props, PartialEq)]
|
||||
pub struct ScriptProps {
|
||||
/// The contents of the script tag. If present, the children must be a single text node.
|
||||
pub children: Element,
|
||||
/// Scripts are deduplicated by their src attribute
|
||||
pub src: Option<String>,
|
||||
pub defer: Option<bool>,
|
||||
pub crossorigin: Option<String>,
|
||||
pub fetchpriority: Option<String>,
|
||||
pub integrity: Option<String>,
|
||||
pub nomodule: Option<bool>,
|
||||
pub nonce: Option<String>,
|
||||
pub referrerpolicy: Option<String>,
|
||||
pub r#type: Option<String>,
|
||||
#[props(extends = script, extends = GlobalAttributes)]
|
||||
pub additional_attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl ScriptProps {
|
||||
pub(crate) fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut attributes = Vec::new();
|
||||
if let Some(defer) = &self.defer {
|
||||
attributes.push(("defer", defer.to_string()));
|
||||
}
|
||||
if let Some(crossorigin) = &self.crossorigin {
|
||||
attributes.push(("crossorigin", crossorigin.clone()));
|
||||
}
|
||||
if let Some(fetchpriority) = &self.fetchpriority {
|
||||
attributes.push(("fetchpriority", fetchpriority.clone()));
|
||||
}
|
||||
if let Some(integrity) = &self.integrity {
|
||||
attributes.push(("integrity", integrity.clone()));
|
||||
}
|
||||
if let Some(nomodule) = &self.nomodule {
|
||||
attributes.push(("nomodule", nomodule.to_string()));
|
||||
}
|
||||
if let Some(nonce) = &self.nonce {
|
||||
attributes.push(("nonce", nonce.clone()));
|
||||
}
|
||||
if let Some(referrerpolicy) = &self.referrerpolicy {
|
||||
attributes.push(("referrerpolicy", referrerpolicy.clone()));
|
||||
}
|
||||
if let Some(r#type) = &self.r#type {
|
||||
attributes.push(("type", r#type.clone()));
|
||||
}
|
||||
if let Some(src) = &self.src {
|
||||
attributes.push(("src", src.clone()));
|
||||
}
|
||||
attributes
|
||||
}
|
||||
|
||||
pub fn script_contents(&self) -> Result<String, ExtractSingleTextNodeError<'_>> {
|
||||
extract_single_text_node(&self.children)
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a [`script`](crate::elements::script) tag into the head of the page.
|
||||
///
|
||||
///
|
||||
/// If present, the children of the script component must be a single static or formatted string. If there are more children or the children contain components, conditionals, loops, or fragments, the script will not be added.
|
||||
///
|
||||
///
|
||||
/// Any scripts you add will be deduplicated by their `src` attribute (if present).
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn LoadScript() -> Element {
|
||||
/// rsx! {
|
||||
/// // You can use the Script component to render a script tag into the head of the page
|
||||
/// Script {
|
||||
/// src: asset!("./assets/script.js"),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// Any updates to the props after the first render will not be reflected in the head.
|
||||
///
|
||||
/// </div>
|
||||
#[component]
|
||||
pub fn Script(props: ScriptProps) -> Element {
|
||||
use_update_warning(&props, "Script {}");
|
||||
|
||||
use_hook(|| {
|
||||
if let Some(src) = &props.src {
|
||||
if !should_insert_script(src) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let document = document();
|
||||
document.create_script(props);
|
||||
});
|
||||
|
||||
VNode::empty()
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Props, PartialEq)]
|
||||
pub struct StyleProps {
|
||||
/// Styles are deduplicated by their href attribute
|
||||
pub href: Option<String>,
|
||||
pub media: Option<String>,
|
||||
pub nonce: Option<String>,
|
||||
pub title: Option<String>,
|
||||
/// The contents of the style tag. If present, the children must be a single text node.
|
||||
pub children: Element,
|
||||
#[props(extends = style, extends = GlobalAttributes)]
|
||||
pub additional_attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl StyleProps {
|
||||
pub(crate) fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut attributes = Vec::new();
|
||||
if let Some(href) = &self.href {
|
||||
attributes.push(("href", href.clone()));
|
||||
}
|
||||
if let Some(media) = &self.media {
|
||||
attributes.push(("media", media.clone()));
|
||||
}
|
||||
if let Some(nonce) = &self.nonce {
|
||||
attributes.push(("nonce", nonce.clone()));
|
||||
}
|
||||
if let Some(title) = &self.title {
|
||||
attributes.push(("title", title.clone()));
|
||||
}
|
||||
attributes
|
||||
}
|
||||
|
||||
pub fn style_contents(&self) -> Result<String, ExtractSingleTextNodeError<'_>> {
|
||||
extract_single_text_node(&self.children)
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a [`style`](crate::elements::style) tag into the head of the page.
|
||||
///
|
||||
/// If present, the children of the style component must be a single static or formatted string. If there are more children or the children contain components, conditionals, loops, or fragments, the style will not be added.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn RedBackground() -> Element {
|
||||
/// rsx! {
|
||||
/// // You can use the style component to render a style tag into the head of the page
|
||||
/// // This style tag will set the background color of the page to red
|
||||
/// Style {
|
||||
/// r#"
|
||||
/// body {{
|
||||
/// background-color: red;
|
||||
/// }}
|
||||
/// "#
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// Any updates to the props after the first render will not be reflected in the head.
|
||||
///
|
||||
/// </div>
|
||||
#[component]
|
||||
pub fn Style(props: StyleProps) -> Element {
|
||||
use_update_warning(&props, "Style {}");
|
||||
|
||||
use_hook(|| {
|
||||
if let Some(href) = &props.href {
|
||||
if !should_insert_style(href) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let document = document();
|
||||
document.create_style(props);
|
||||
});
|
||||
|
||||
VNode::empty()
|
||||
}
|
||||
|
||||
use super::*;
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Props, PartialEq)]
|
||||
pub struct LinkProps {
|
||||
pub rel: Option<String>,
|
||||
pub media: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub disabled: Option<bool>,
|
||||
pub r#as: Option<String>,
|
||||
pub sizes: Option<String>,
|
||||
/// Links are deduplicated by their href attribute
|
||||
pub href: Option<String>,
|
||||
pub crossorigin: Option<String>,
|
||||
pub referrerpolicy: Option<String>,
|
||||
pub fetchpriority: Option<String>,
|
||||
pub hreflang: Option<String>,
|
||||
pub integrity: Option<String>,
|
||||
pub r#type: Option<String>,
|
||||
pub blocking: Option<String>,
|
||||
#[props(extends = link, extends = GlobalAttributes)]
|
||||
pub additional_attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl LinkProps {
|
||||
pub(crate) fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut attributes = Vec::new();
|
||||
if let Some(rel) = &self.rel {
|
||||
attributes.push(("rel", rel.clone()));
|
||||
}
|
||||
if let Some(media) = &self.media {
|
||||
attributes.push(("media", media.clone()));
|
||||
}
|
||||
if let Some(title) = &self.title {
|
||||
attributes.push(("title", title.clone()));
|
||||
}
|
||||
if let Some(disabled) = &self.disabled {
|
||||
attributes.push(("disabled", disabled.to_string()));
|
||||
}
|
||||
if let Some(r#as) = &self.r#as {
|
||||
attributes.push(("as", r#as.clone()));
|
||||
}
|
||||
if let Some(sizes) = &self.sizes {
|
||||
attributes.push(("sizes", sizes.clone()));
|
||||
}
|
||||
if let Some(href) = &self.href {
|
||||
attributes.push(("href", href.clone()));
|
||||
}
|
||||
if let Some(crossorigin) = &self.crossorigin {
|
||||
attributes.push(("crossOrigin", crossorigin.clone()));
|
||||
}
|
||||
if let Some(referrerpolicy) = &self.referrerpolicy {
|
||||
attributes.push(("referrerPolicy", referrerpolicy.clone()));
|
||||
}
|
||||
if let Some(fetchpriority) = &self.fetchpriority {
|
||||
attributes.push(("fetchPriority", fetchpriority.clone()));
|
||||
}
|
||||
if let Some(hreflang) = &self.hreflang {
|
||||
attributes.push(("hrefLang", hreflang.clone()));
|
||||
}
|
||||
if let Some(integrity) = &self.integrity {
|
||||
attributes.push(("integrity", integrity.clone()));
|
||||
}
|
||||
if let Some(r#type) = &self.r#type {
|
||||
attributes.push(("type", r#type.clone()));
|
||||
}
|
||||
if let Some(blocking) = &self.blocking {
|
||||
attributes.push(("blocking", blocking.clone()));
|
||||
}
|
||||
attributes
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a [`link`](crate::elements::link) tag into the head of the page.
|
||||
///
|
||||
/// > The [Link](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html) component in dioxus router and this component are completely different.
|
||||
/// > This component links resources in the head of the page, while the router component creates clickable links in the body of the page.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn RedBackground() -> Element {
|
||||
/// rsx! {
|
||||
/// // You can use the meta component to render a meta tag into the head of the page
|
||||
/// // This meta tag will redirect the user to the dioxuslabs homepage in 10 seconds
|
||||
/// head::Link {
|
||||
/// href: asset!("./assets/style.css"),
|
||||
/// rel: "stylesheet",
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// Any updates to the props after the first render will not be reflected in the head.
|
||||
///
|
||||
/// </div>
|
||||
#[doc(alias = "<link>")]
|
||||
#[component]
|
||||
pub fn Link(props: LinkProps) -> Element {
|
||||
use_update_warning(&props, "Link {}");
|
||||
|
||||
use_hook(|| {
|
||||
if let Some(href) = &props.href {
|
||||
if !should_insert_link(href) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let document = document();
|
||||
document.create_link(props);
|
||||
});
|
||||
|
||||
VNode::empty()
|
||||
}
|
||||
|
||||
fn get_or_insert_root_context<T: Default + Clone + 'static>() -> T {
|
||||
match ScopeId::ROOT.has_context::<T>() {
|
||||
Some(context) => context,
|
||||
None => {
|
||||
let context = T::default();
|
||||
ScopeId::ROOT.provide_context(context.clone());
|
||||
context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct LinkContext(DeduplicationContext);
|
||||
|
||||
fn should_insert_link(href: &str) -> bool {
|
||||
get_or_insert_root_context::<LinkContext>()
|
||||
.0
|
||||
.should_insert(href)
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct ScriptContext(DeduplicationContext);
|
||||
|
||||
fn should_insert_script(src: &str) -> bool {
|
||||
get_or_insert_root_context::<ScriptContext>()
|
||||
.0
|
||||
.should_insert(src)
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct StyleContext(DeduplicationContext);
|
||||
|
||||
fn should_insert_style(href: &str) -> bool {
|
||||
get_or_insert_root_context::<StyleContext>()
|
||||
.0
|
||||
.should_insert(href)
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct DeduplicationContext(Rc<RefCell<HashSet<String>>>);
|
||||
|
||||
impl DeduplicationContext {
|
||||
fn should_insert(&self, href: &str) -> bool {
|
||||
let mut set = self.0.borrow_mut();
|
||||
let present = set.contains(href);
|
||||
if !present {
|
||||
set.insert(href.to_string());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
// API inspired by Reacts implementation of head only elements. We use components here instead of elements to simplify internals.
|
||||
|
||||
use std::{
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
|
||||
|
||||
#[allow(unused)]
|
||||
mod eval;
|
||||
pub use eval::*;
|
||||
|
||||
pub mod head;
|
||||
pub use head::{Meta, MetaProps, Script, ScriptProps, Style, StyleProps, Title, TitleProps};
|
||||
|
||||
fn format_string_for_js(s: &str) -> String {
|
||||
let escaped = s
|
||||
.replace('\\', "\\\\")
|
||||
.replace('\n', "\\n")
|
||||
.replace('\r', "\\r")
|
||||
.replace('"', "\\\"");
|
||||
format!("\"{escaped}\"")
|
||||
}
|
||||
|
||||
fn format_attributes(attributes: &[(&str, String)]) -> String {
|
||||
let mut formatted = String::from("[");
|
||||
for (key, value) in attributes {
|
||||
formatted.push_str(&format!(
|
||||
"[{}, {}],",
|
||||
format_string_for_js(key),
|
||||
format_string_for_js(value)
|
||||
));
|
||||
}
|
||||
if formatted.ends_with(',') {
|
||||
formatted.pop();
|
||||
}
|
||||
formatted.push(']');
|
||||
formatted
|
||||
}
|
||||
|
||||
fn create_element_in_head(
|
||||
tag: &str,
|
||||
attributes: &[(&str, String)],
|
||||
children: Option<String>,
|
||||
) -> String {
|
||||
let helpers = include_str!("../js/head.js");
|
||||
let attributes = format_attributes(attributes);
|
||||
let children = children
|
||||
.as_deref()
|
||||
.map(format_string_for_js)
|
||||
.unwrap_or("null".to_string());
|
||||
let tag = format_string_for_js(tag);
|
||||
format!(r#"{helpers};window.createElementInHead({tag}, {attributes}, {children});"#)
|
||||
}
|
||||
|
||||
/// A provider for document-related functionality. By default most methods are driven through [`eval`].
|
||||
pub trait Document {
|
||||
/// Create a new evaluator for the document that evaluates JavaScript and facilitates communication between JavaScript and Rust.
|
||||
fn new_evaluator(&self, js: String) -> GenerationalBox<Box<dyn Evaluator>>;
|
||||
|
||||
/// Set the title of the document
|
||||
fn set_title(&self, title: String) {
|
||||
self.new_evaluator(format!("document.title = {title:?};"));
|
||||
}
|
||||
|
||||
/// Create a new meta tag
|
||||
fn create_meta(&self, props: MetaProps) {
|
||||
let attributes = props.attributes();
|
||||
let js = create_element_in_head("meta", &attributes, None);
|
||||
self.new_evaluator(js);
|
||||
}
|
||||
|
||||
/// Create a new script tag
|
||||
fn create_script(&self, props: ScriptProps) {
|
||||
let attributes = props.attributes();
|
||||
let js = match (&props.src, props.script_contents()) {
|
||||
// The script has inline contents, render it as a script tag
|
||||
(_, Ok(contents)) => create_element_in_head("script", &attributes, Some(contents)),
|
||||
// The script has a src, render it as a script tag without a body
|
||||
(Some(_), _) => create_element_in_head("script", &attributes, None),
|
||||
// The script has neither contents nor src, log an error
|
||||
(None, Err(err)) => {
|
||||
err.log("Script");
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.new_evaluator(js);
|
||||
}
|
||||
|
||||
/// Create a new style tag
|
||||
fn create_style(&self, props: StyleProps) {
|
||||
let mut attributes = props.attributes();
|
||||
let js = match (&props.href, props.style_contents()) {
|
||||
// The style has inline contents, render it as a style tag
|
||||
(_, Ok(contents)) => create_element_in_head("style", &attributes, Some(contents)),
|
||||
// The style has a src, render it as a link tag
|
||||
(Some(_), _) => {
|
||||
attributes.push(("type", "text/css".into()));
|
||||
create_element_in_head("link", &attributes, None)
|
||||
}
|
||||
// The style has neither contents nor src, log an error
|
||||
(None, Err(err)) => {
|
||||
err.log("Style");
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.new_evaluator(js);
|
||||
}
|
||||
|
||||
/// Create a new link tag
|
||||
fn create_link(&self, props: head::LinkProps) {
|
||||
let attributes = props.attributes();
|
||||
let js = create_element_in_head("link", &attributes, None);
|
||||
self.new_evaluator(js);
|
||||
}
|
||||
|
||||
/// Get a reference to the document as `dyn Any`
|
||||
fn as_any(&self) -> &dyn std::any::Any;
|
||||
}
|
||||
|
||||
/// The default No-Op document
|
||||
pub struct NoOpDocument;
|
||||
|
||||
impl Document for NoOpDocument {
|
||||
fn new_evaluator(&self, _js: String) -> GenerationalBox<Box<dyn Evaluator>> {
|
||||
tracing::error!("Eval is not supported on this platform. If you are using dioxus fullstack, you can wrap your code with `client! {{}}` to only include the code that runs eval in the client bundle.");
|
||||
UnsyncStorage::owner().insert(Box::new(NoOpEvaluator))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The default No-Op evaluator
|
||||
pub struct NoOpEvaluator;
|
||||
impl Evaluator for NoOpEvaluator {
|
||||
fn send(&self, _data: serde_json::Value) -> Result<(), EvalError> {
|
||||
Err(EvalError::Unsupported)
|
||||
}
|
||||
fn poll_recv(
|
||||
&mut self,
|
||||
_context: &mut Context<'_>,
|
||||
) -> Poll<Result<serde_json::Value, EvalError>> {
|
||||
Poll::Ready(Err(EvalError::Unsupported))
|
||||
}
|
||||
fn poll_join(
|
||||
&mut self,
|
||||
_context: &mut Context<'_>,
|
||||
) -> Poll<Result<serde_json::Value, EvalError>> {
|
||||
Poll::Ready(Err(EvalError::Unsupported))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the document provider for the current platform or a no-op provider if the platform doesn't document functionality.
|
||||
pub fn document() -> Rc<dyn Document> {
|
||||
dioxus_core::prelude::try_consume_context::<Rc<dyn Document>>()
|
||||
// Create a NoOp provider that always logs an error when trying to evaluate
|
||||
// That way, we can still compile and run the code without a real provider
|
||||
.unwrap_or_else(|| Rc::new(NoOpDocument) as Rc<dyn Document>)
|
||||
}
|
|
@ -41,9 +41,6 @@ pub use elements::*;
|
|||
pub use events::*;
|
||||
pub use render_template::*;
|
||||
|
||||
#[cfg(feature = "document")]
|
||||
pub mod document;
|
||||
|
||||
pub mod extensions {
|
||||
pub use crate::attribute_groups::{GlobalAttributesExtension, SvgAttributesExtension};
|
||||
pub use crate::elements::extensions::*;
|
||||
|
@ -51,11 +48,6 @@ pub mod extensions {
|
|||
|
||||
pub mod prelude {
|
||||
pub use crate::attribute_groups::{GlobalAttributesExtension, SvgAttributesExtension};
|
||||
#[cfg(feature = "document")]
|
||||
pub use crate::document::{
|
||||
self, document, eval, head, Document, Meta, MetaProps, Script, ScriptProps, Style,
|
||||
StyleProps, Title, TitleProps, UseEval,
|
||||
};
|
||||
pub use crate::elements::extensions::*;
|
||||
pub use crate::events::*;
|
||||
pub use crate::point_interaction::*;
|
||||
|
|
|
@ -22,7 +22,8 @@ tokio-stream = { version = "0.1.11", features = ["net"] }
|
|||
tokio-util = { version = "0.7.4", features = ["rt"] }
|
||||
serde = { version = "1.0.151", features = ["derive"] }
|
||||
serde_json = "1.0.91"
|
||||
dioxus-html = { workspace = true, features = ["serialize", "document", "mounted"] }
|
||||
dioxus-html = { workspace = true, features = ["serialize", "mounted"] }
|
||||
dioxus-document = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
dioxus-core = { workspace = true, features = ["serialize"] }
|
||||
dioxus-interpreter-js = { workspace = true, features = ["binary-protocol"] }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use dioxus_core::ScopeId;
|
||||
use dioxus_html::document::{Document, EvalError, Evaluator};
|
||||
use dioxus_document::{Document, Eval, EvalError, Evaluator};
|
||||
use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
|
||||
use std::rc::Rc;
|
||||
|
||||
|
@ -18,12 +18,8 @@ pub struct LiveviewDocument {
|
|||
}
|
||||
|
||||
impl Document for LiveviewDocument {
|
||||
fn new_evaluator(&self, js: String) -> GenerationalBox<Box<dyn Evaluator>> {
|
||||
LiveviewEvaluator::create(self.query.clone(), js)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
fn eval(&self, js: String) -> Eval {
|
||||
Eval::new(LiveviewEvaluator::create(self.query.clone(), js))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ fn app() -> Element {
|
|||
|
||||
rsx! {
|
||||
h1 { "hello axum! {count}" }
|
||||
Title { "hello axum! {count}" }
|
||||
document::Title { "hello axum! {count}" }
|
||||
button { class: "increment-button", onclick: move |_| count += 1, "Increment" }
|
||||
button {
|
||||
class: "server-button",
|
||||
|
|
|
@ -44,7 +44,7 @@ fn LoadTitle() -> Element {
|
|||
.unwrap();
|
||||
|
||||
rsx! {
|
||||
Title { "{title.title}" }
|
||||
document::Title { "{title.title}" }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ fn app() -> Element {
|
|||
rsx! {
|
||||
div {
|
||||
"hello axum! {num}"
|
||||
Title { "hello axum! {num}" }
|
||||
document::Title { "hello axum! {num}" }
|
||||
button { class: "increment-button", onclick: move |_| num += 1, "Increment" }
|
||||
}
|
||||
svg { circle { cx: 50, cy: 50, r: 40, stroke: "green", fill: "yellow" } }
|
||||
|
@ -24,7 +24,7 @@ fn app() -> Element {
|
|||
button {
|
||||
class: "eval-button",
|
||||
onclick: move |_| async move {
|
||||
let mut eval = eval(
|
||||
let mut eval = document::eval(
|
||||
r#"
|
||||
window.document.title = 'Hello from Dioxus Eval!';
|
||||
// Receive and multiply 10 numbers
|
||||
|
@ -38,9 +38,9 @@ fn app() -> Element {
|
|||
|
||||
// Send 10 numbers
|
||||
for i in 0..10 {
|
||||
eval.send(serde_json::Value::from(i)).unwrap();
|
||||
let value = eval.recv().await.unwrap();
|
||||
assert_eq!(value, serde_json::Value::from(i * 2));
|
||||
eval.send(i).unwrap();
|
||||
let value: i32 = eval.recv().await.unwrap();
|
||||
assert_eq!(value, i * 2);
|
||||
}
|
||||
|
||||
let result = eval.recv().await;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::HistoryProvider;
|
||||
use crate::routable::Routable;
|
||||
use dioxus_lib::document::Eval;
|
||||
use dioxus_lib::prelude::*;
|
||||
use document::UseEval;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::{Mutex, RwLock};
|
||||
use std::{collections::BTreeMap, rc::Rc, str::FromStr, sync::Arc};
|
||||
|
@ -168,11 +168,10 @@ where
|
|||
let updater_callback: Arc<RwLock<Arc<dyn Fn() + Send + Sync>>> =
|
||||
Arc::new(RwLock::new(Arc::new(|| {})));
|
||||
|
||||
let eval_provider = document();
|
||||
let eval_provider = dioxus_lib::document::document();
|
||||
|
||||
let create_eval = Rc::new(move |script: &str| {
|
||||
UseEval::new(eval_provider.new_evaluator(script.to_string()))
|
||||
}) as Rc<dyn Fn(&str) -> UseEval>;
|
||||
let create_eval = Rc::new(move |script: &str| eval_provider.eval(script.to_string()))
|
||||
as Rc<dyn Fn(&str) -> Eval>;
|
||||
|
||||
// Listen to server actions
|
||||
spawn({
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use dioxus_isrg::*;
|
||||
use dioxus_lib::document::Document;
|
||||
use dioxus_lib::prelude::*;
|
||||
use dioxus_router::prelude::*;
|
||||
use dioxus_ssr::renderer;
|
||||
|
|
|
@ -13,6 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||
dioxus-core = { workspace = true }
|
||||
dioxus-core-types = { workspace = true }
|
||||
dioxus-html = { workspace = true }
|
||||
dioxus-document = { workspace = true }
|
||||
dioxus-devtools = { workspace = true }
|
||||
dioxus-signals = { workspace = true }
|
||||
dioxus-interpreter-js = { workspace = true, features = [
|
||||
|
@ -100,7 +101,7 @@ file_engine = [
|
|||
"web-sys/FileReader"
|
||||
]
|
||||
devtools = ["web-sys/MessageEvent", "web-sys/WebSocket", "web-sys/Location", "dep:serde_json", "dep:serde", "dioxus-core/serialize"]
|
||||
document = ["dioxus-html/document", "dep:serde-wasm-bindgen", "dep:serde_json", "dep:serde"]
|
||||
document = ["dep:serde-wasm-bindgen", "dep:serde_json", "dep:serde"]
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { workspace = true, default-features = true }
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::time::Duration;
|
|||
|
||||
use dioxus_core::ScopeId;
|
||||
use dioxus_devtools::{DevserverMsg, HotReloadMsg};
|
||||
use dioxus_html::prelude::eval;
|
||||
use dioxus_document::eval;
|
||||
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
|
||||
use js_sys::JsString;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use dioxus_core::ScopeId;
|
||||
use dioxus_html::document::{Document, EvalError, Evaluator, NoOpEvaluator};
|
||||
use dioxus_document::{Document, Eval, EvalError, Evaluator};
|
||||
use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
|
||||
use js_sys::Function;
|
||||
use serde::Serialize;
|
||||
|
@ -64,12 +64,8 @@ pub fn init_document() {
|
|||
/// The web-target's document provider.
|
||||
pub struct WebDocument;
|
||||
impl Document for WebDocument {
|
||||
fn new_evaluator(&self, js: String) -> GenerationalBox<Box<dyn Evaluator>> {
|
||||
WebEvaluator::create(js)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
fn eval(&self, js: String) -> Eval {
|
||||
Eval::new(WebEvaluator::create(js))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,10 +91,8 @@ impl WebEvaluator {
|
|||
fn create(js: String) -> GenerationalBox<Box<dyn Evaluator>> {
|
||||
let owner = UnsyncStorage::owner();
|
||||
|
||||
let generational_box = owner.insert(Box::new(NoOpEvaluator) as Box<dyn Evaluator>);
|
||||
|
||||
// add the drop handler to DioxusChannel so that it gets dropped when the channel is dropped in js
|
||||
let channels = WebDioxusChannel::new(JSOwner::new(owner));
|
||||
let channels = WebDioxusChannel::new(JSOwner::new(owner.clone()));
|
||||
|
||||
// The Rust side of the channel is a weak reference to the DioxusChannel
|
||||
let weak_channels = channels.weak();
|
||||
|
@ -131,13 +125,11 @@ impl WebEvaluator {
|
|||
)),
|
||||
};
|
||||
|
||||
generational_box.set(Box::new(Self {
|
||||
owner.insert(Box::new(Self {
|
||||
channels: weak_channels,
|
||||
result: Some(result),
|
||||
next_future: None,
|
||||
}) as Box<dyn Evaluator>);
|
||||
|
||||
generational_box
|
||||
}) as Box<dyn Evaluator>)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
[3479327739946104450]
|
||||
[1614426347475783279]
|
||||
|
|
|
@ -2,7 +2,7 @@ import {
|
|||
DioxusChannel,
|
||||
Channel,
|
||||
WeakDioxusChannel,
|
||||
} from "../../../html/src/ts/eval";
|
||||
} from "../../../document/src/ts/eval";
|
||||
|
||||
export class WebDioxusChannel extends DioxusChannel {
|
||||
js_to_rust: Channel;
|
||||
|
|
Loading…
Reference in a new issue