mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 14:44:12 +00:00
wip: some ideas
This commit is contained in:
parent
583fdfa561
commit
05c909f320
21 changed files with 662 additions and 1112 deletions
|
@ -2,43 +2,41 @@
|
||||||
//!
|
//!
|
||||||
//! The example from the README.md
|
//! The example from the README.md
|
||||||
|
|
||||||
use std::{pin::Pin, time::Duration};
|
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use futures::Future;
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init();
|
|
||||||
log::info!("hello world");
|
|
||||||
dioxus::desktop::launch(App, |c| c).expect("faield to launch");
|
dioxus::desktop::launch(App, |c| c).expect("faield to launch");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
pub static App: FC<()> = |cx| {
|
||||||
struct DogApi {
|
let count = use_state(cx, || 0);
|
||||||
message: String,
|
let mut direction = use_state(cx, || 1);
|
||||||
}
|
|
||||||
|
|
||||||
const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random";
|
let (async_count, dir) = (count.for_async(), *direction);
|
||||||
|
let (task, _result) = cx.use_task(move || async move {
|
||||||
static App: FC<()> = |cx| {
|
|
||||||
let mut count = use_state(cx, || 0);
|
|
||||||
let mut fut = Box::pin(async {
|
|
||||||
let mut tick: i32 = 0;
|
|
||||||
loop {
|
loop {
|
||||||
async_std::task::sleep(Duration::from_millis(250)).await;
|
gloo_timers::future::TimeoutFuture::new(250).await;
|
||||||
log::debug!("ticking forward... {}", tick);
|
*async_count.get_mut() += dir;
|
||||||
tick += 1;
|
|
||||||
// match surf::get(ENDPOINT).recv_json::<DogApi>().await {
|
|
||||||
// Ok(_) => (),
|
|
||||||
// Err(_) => (),
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.submit_task(fut);
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 {"it's working somewhat properly"}
|
h1 {"count is {count}"}
|
||||||
|
button {
|
||||||
|
"Stop counting"
|
||||||
|
onclick: move |_| task.stop()
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
"Start counting"
|
||||||
|
onclick: move |_| task.start()
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
"Switch counting direcion"
|
||||||
|
onclick: move |_| {
|
||||||
|
direction *= -1;
|
||||||
|
task.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
use dioxus::prelude::*;
|
|
||||||
fn main() {}
|
|
||||||
|
|
||||||
pub static Example: FC<()> = |cx| {
|
|
||||||
let (g, set_g) = use_state(cx, || 0).classic();
|
|
||||||
let v = (0..10).map(move |f| {
|
|
||||||
rsx!(li {
|
|
||||||
onclick: move |_| set_g(10)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
cx.render(rsx! {
|
|
||||||
div {
|
|
||||||
{v}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
|
@ -1,170 +0,0 @@
|
||||||
#![allow(
|
|
||||||
unused,
|
|
||||||
dead_code,
|
|
||||||
non_upper_case_globals,
|
|
||||||
non_camel_case_types,
|
|
||||||
non_snake_case
|
|
||||||
)]
|
|
||||||
|
|
||||||
mod antipatterns;
|
|
||||||
mod basics;
|
|
||||||
mod children;
|
|
||||||
mod conditional_rendering;
|
|
||||||
mod controlled_inputs;
|
|
||||||
mod custom_elements;
|
|
||||||
mod empty;
|
|
||||||
mod fragments;
|
|
||||||
mod global_css;
|
|
||||||
mod inline_styles;
|
|
||||||
mod iterators;
|
|
||||||
mod listener;
|
|
||||||
mod memo;
|
|
||||||
mod noderefs;
|
|
||||||
mod signals;
|
|
||||||
mod spreadpattern;
|
|
||||||
mod statemanagement;
|
|
||||||
mod suspense;
|
|
||||||
mod task;
|
|
||||||
mod testing;
|
|
||||||
mod tostring;
|
|
||||||
|
|
||||||
fn main() {}
|
|
||||||
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
static App: FC<()> = |cx| {
|
|
||||||
let (selection, set_selection) = use_state(cx, || None as Option<usize>).classic();
|
|
||||||
|
|
||||||
let body = match selection {
|
|
||||||
Some(id) => rsx!(in cx, ReferenceItem { selected: *id }),
|
|
||||||
None => rsx!(in cx, div { "Select an concept to explore" }),
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
|
||||||
div {
|
|
||||||
ScrollSelector { onselect: move |id| set_selection(id) }
|
|
||||||
{body}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// this is its own component to stay memoized
|
|
||||||
#[derive(Props)]
|
|
||||||
struct ScrollSelectorProps<'a> {
|
|
||||||
onselect: &'a dyn Fn(Option<usize>),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ScrollSelector<'a>(cx: Context<'a, ScrollSelectorProps>) -> VNode<'a> {
|
|
||||||
let selection_list = (&REFERENCES).iter().enumerate().map(|(id, _)| {
|
|
||||||
rsx! {
|
|
||||||
li {
|
|
||||||
h3 {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cx.render(rsx! {
|
|
||||||
div {
|
|
||||||
h1 {""}
|
|
||||||
ul {
|
|
||||||
{selection_list}
|
|
||||||
button {
|
|
||||||
onclick: move |_| (cx.onselect)(Some(10))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Props)]
|
|
||||||
struct ReferenceItemProps {
|
|
||||||
selected: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
static ReferenceItem: FC<ReferenceItemProps> = |cx| {
|
|
||||||
let (caller, name, code) = REFERENCES[cx.selected];
|
|
||||||
|
|
||||||
// Create the component using the factory API directly
|
|
||||||
let caller_node = LazyNodes::new(move |f| f.component(caller, (), None, &[]));
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
|
||||||
div {
|
|
||||||
// Source of the left, rendered on the right
|
|
||||||
div {
|
|
||||||
code { "{code}" }
|
|
||||||
}
|
|
||||||
div {
|
|
||||||
{caller_node}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
static REFERENCES: &[(FC<()>, &str, &str)] = &[
|
|
||||||
(basics::Example, "Basics", include_str!("./basics.rs")),
|
|
||||||
(children::Example, "Children", include_str!("./children.rs")),
|
|
||||||
(
|
|
||||||
conditional_rendering::Example,
|
|
||||||
"Conditional Rendering",
|
|
||||||
include_str!("./conditional_rendering.rs"),
|
|
||||||
),
|
|
||||||
// TODO
|
|
||||||
(
|
|
||||||
controlled_inputs::Example,
|
|
||||||
"Controlled Inputs",
|
|
||||||
include_str!("./controlled_inputs.rs"),
|
|
||||||
),
|
|
||||||
(empty::Example, "empty", include_str!("./empty.rs")),
|
|
||||||
(
|
|
||||||
custom_elements::Example,
|
|
||||||
"Custom Elements & Web Components",
|
|
||||||
include_str!("./custom_elements.rs"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
fragments::Example,
|
|
||||||
"Fragments",
|
|
||||||
include_str!("./fragments.rs"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
iterators::Example,
|
|
||||||
"Iterators",
|
|
||||||
include_str!("./iterators.rs"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
global_css::Example,
|
|
||||||
"Global CSS",
|
|
||||||
include_str!("./global_css.rs"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
inline_styles::Example,
|
|
||||||
"Inline Styles",
|
|
||||||
include_str!("./inline_styles.rs"),
|
|
||||||
),
|
|
||||||
(listener::Example, "Listener", include_str!("./listener.rs")),
|
|
||||||
(memo::Example, "Memo", include_str!("./memo.rs")),
|
|
||||||
(
|
|
||||||
spreadpattern::Example,
|
|
||||||
"Spread Pattern",
|
|
||||||
include_str!("./spreadpattern.rs"),
|
|
||||||
),
|
|
||||||
(suspense::Example, "Suspense", include_str!("./suspense.rs")),
|
|
||||||
(task::Example, "Task", include_str!("./task.rs")),
|
|
||||||
(tostring::Example, "Tostring", include_str!("./tostring.rs")),
|
|
||||||
(
|
|
||||||
antipatterns::Example,
|
|
||||||
"Anti-patterns",
|
|
||||||
include_str!("./antipatterns.rs"),
|
|
||||||
),
|
|
||||||
/*
|
|
||||||
TODO!
|
|
||||||
*/
|
|
||||||
(signals::Example, "Signals", include_str!("./signals.rs")),
|
|
||||||
(noderefs::Example, "NodeRefs", include_str!("./noderefs.rs")),
|
|
||||||
(
|
|
||||||
statemanagement::Example,
|
|
||||||
"State Management",
|
|
||||||
include_str!("./statemanagement.rs"),
|
|
||||||
),
|
|
||||||
(testing::Example, "Testing", include_str!("./testing.rs")),
|
|
||||||
];
|
|
|
@ -7,7 +7,7 @@ pub static Example: FC<()> = |cx| {
|
||||||
// This is an easy/low hanging fruit to improve upon
|
// This is an easy/low hanging fruit to improve upon
|
||||||
let mut dom = VirtualDom::new(SomeApp);
|
let mut dom = VirtualDom::new(SomeApp);
|
||||||
dom.rebuild_in_place().unwrap();
|
dom.rebuild_in_place().unwrap();
|
||||||
ssr::render_root(&dom)
|
ssr::render_vdom(&dom)
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
|
|
|
@ -1,8 +1,84 @@
|
||||||
//! Example: Reference Showcase
|
#![allow(
|
||||||
//! ---------------------------
|
unused,
|
||||||
//!
|
dead_code,
|
||||||
//! This example provides a cute interface for browsing the reference examples.
|
non_upper_case_globals,
|
||||||
|
non_camel_case_types,
|
||||||
|
non_snake_case
|
||||||
|
)]
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
static App: FC<()> = |cx| {
|
||||||
|
let (selection, set_selection) = use_state(cx, || None as Option<usize>).classic();
|
||||||
|
|
||||||
|
let body = match selection {
|
||||||
|
Some(id) => rsx!(in cx, ReferenceItem { selected: *id }),
|
||||||
|
None => rsx!(in cx, div { "Select an concept to explore" }),
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
ScrollSelector { onselect: move |id| set_selection(id) }
|
||||||
|
{body}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
// this is its own component to stay memoized
|
||||||
|
#[derive(Props)]
|
||||||
|
struct ScrollSelectorProps<'a> {
|
||||||
|
onselect: &'a dyn Fn(Option<usize>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ScrollSelector<'a>(cx: Context<'a, ScrollSelectorProps>) -> VNode<'a> {
|
||||||
|
let selection_list = (&REFERENCES).iter().enumerate().map(|(id, _)| {
|
||||||
|
rsx! {
|
||||||
|
li {
|
||||||
|
h3 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
h1 {""}
|
||||||
|
ul {
|
||||||
|
{selection_list}
|
||||||
|
button {
|
||||||
|
onclick: move |_| (cx.onselect)(Some(10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Props)]
|
||||||
|
struct ReferenceItemProps {
|
||||||
|
selected: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
static ReferenceItem: FC<ReferenceItemProps> = |cx| {
|
||||||
|
let (caller, name, code) = REFERENCES[cx.selected];
|
||||||
|
|
||||||
|
// Create the component using the factory API directly
|
||||||
|
let caller_node = LazyNodes::new(move |f| f.component(caller, (), None, &[]));
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
// Source of the left, rendered on the right
|
||||||
|
div {
|
||||||
|
code { "{code}" }
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
{caller_node}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
use reference::REFERENCES;
|
||||||
mod reference {
|
mod reference {
|
||||||
mod antipatterns;
|
mod antipatterns;
|
||||||
mod basics;
|
mod basics;
|
||||||
|
@ -22,9 +98,111 @@ mod reference {
|
||||||
mod spreadpattern;
|
mod spreadpattern;
|
||||||
mod statemanagement;
|
mod statemanagement;
|
||||||
mod suspense;
|
mod suspense;
|
||||||
|
mod task;
|
||||||
mod testing;
|
mod testing;
|
||||||
mod tostring;
|
mod tostring;
|
||||||
mod webcomponents;
|
use super::*;
|
||||||
|
pub static REFERENCES: &[(FC<()>, &str, &str)] = &[
|
||||||
|
(
|
||||||
|
basics::Example,
|
||||||
|
"Basics",
|
||||||
|
include_str!("./reference/basics.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
children::Example,
|
||||||
|
"Children",
|
||||||
|
include_str!("./reference/children.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
conditional_rendering::Example,
|
||||||
|
"Conditional Rendering",
|
||||||
|
include_str!("./reference/conditional_rendering.rs"),
|
||||||
|
),
|
||||||
|
// TODO
|
||||||
|
(
|
||||||
|
controlled_inputs::Example,
|
||||||
|
"Controlled Inputs",
|
||||||
|
include_str!("./reference/controlled_inputs.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
empty::Example,
|
||||||
|
"empty",
|
||||||
|
include_str!("./reference/empty.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
custom_elements::Example,
|
||||||
|
"Custom Elements & Web Components",
|
||||||
|
include_str!("./reference/custom_elements.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
fragments::Example,
|
||||||
|
"Fragments",
|
||||||
|
include_str!("./reference/fragments.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
iterators::Example,
|
||||||
|
"Iterators",
|
||||||
|
include_str!("./reference/iterators.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
global_css::Example,
|
||||||
|
"Global CSS",
|
||||||
|
include_str!("./reference/global_css.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
inline_styles::Example,
|
||||||
|
"Inline Styles",
|
||||||
|
include_str!("./reference/inline_styles.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
listener::Example,
|
||||||
|
"Listener",
|
||||||
|
include_str!("./reference/listener.rs"),
|
||||||
|
),
|
||||||
|
(memo::Example, "Memo", include_str!("./reference/memo.rs")),
|
||||||
|
(
|
||||||
|
spreadpattern::Example,
|
||||||
|
"Spread Pattern",
|
||||||
|
include_str!("./reference/spreadpattern.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
suspense::Example,
|
||||||
|
"Suspense",
|
||||||
|
include_str!("./reference/suspense.rs"),
|
||||||
|
),
|
||||||
|
(task::Example, "Task", include_str!("./reference/task.rs")),
|
||||||
|
(
|
||||||
|
tostring::Example,
|
||||||
|
"Tostring",
|
||||||
|
include_str!("./reference/tostring.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
antipatterns::Example,
|
||||||
|
"Anti-patterns",
|
||||||
|
include_str!("./reference/antipatterns.rs"),
|
||||||
|
),
|
||||||
|
/*
|
||||||
|
TODO! these showcase features that aren't quite ready
|
||||||
|
*/
|
||||||
|
(
|
||||||
|
signals::Example,
|
||||||
|
"Signals",
|
||||||
|
include_str!("./reference/signals.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
noderefs::Example,
|
||||||
|
"NodeRefs",
|
||||||
|
include_str!("./reference/noderefs.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
statemanagement::Example,
|
||||||
|
"State Management",
|
||||||
|
include_str!("./reference/statemanagement.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
testing::Example,
|
||||||
|
"Testing",
|
||||||
|
include_str!("./reference/testing.rs"),
|
||||||
|
),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use dioxus::ssr;
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut vdom = VirtualDom::new(App);
|
let mut vdom = VirtualDom::new(App);
|
||||||
vdom.rebuild_in_place();
|
vdom.rebuild_in_place();
|
||||||
println!("{}", ssr::render_root(&vdom));
|
println!("{}", ssr::render_vdom(&vdom));
|
||||||
}
|
}
|
||||||
|
|
||||||
const App: FC<()> = |cx| {
|
const App: FC<()> = |cx| {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use dioxus::desktop::wry::application::platform::macos::*;
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
use dioxus::desktop::wry::application::platform::macos::*;
|
||||||
dioxus::desktop::launch(App, |c| {
|
dioxus::desktop::launch(App, |c| {
|
||||||
c.with_fullsize_content_view(true)
|
c.with_fullsize_content_view(true)
|
||||||
.with_titlebar_buttons_hidden(false)
|
.with_titlebar_buttons_hidden(false)
|
||||||
|
|
|
@ -22,18 +22,16 @@ impl SharedArena {
|
||||||
|
|
||||||
/// THIS METHOD IS CURRENTLY UNSAFE
|
/// THIS METHOD IS CURRENTLY UNSAFE
|
||||||
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
|
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
|
||||||
pub fn try_get(&self, idx: ScopeId) -> Result<&Scope> {
|
pub fn get(&self, idx: ScopeId) -> Option<&Scope> {
|
||||||
let inner = unsafe { &*self.components.get() };
|
let inner = unsafe { &*self.components.get() };
|
||||||
let scope = inner.get(idx);
|
inner.get(idx)
|
||||||
scope.ok_or_else(|| Error::FatalInternal("Scope not found"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// THIS METHOD IS CURRENTLY UNSAFE
|
/// THIS METHOD IS CURRENTLY UNSAFE
|
||||||
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
|
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
|
||||||
pub fn try_get_mut(&self, idx: ScopeId) -> Result<&mut Scope> {
|
pub fn get_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
|
||||||
let inner = unsafe { &mut *self.components.get() };
|
let inner = unsafe { &mut *self.components.get() };
|
||||||
let scope = inner.get_mut(idx);
|
inner.get_mut(idx)
|
||||||
scope.ok_or_else(|| Error::FatalInternal("Scope not found"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inner(&self) -> &ScopeMap {
|
fn inner(&self) -> &ScopeMap {
|
||||||
|
|
|
@ -278,8 +278,8 @@ Any function prefixed with "use" should not be called conditionally.
|
||||||
Some(parent_id) => {
|
Some(parent_id) => {
|
||||||
let parent = inner
|
let parent = inner
|
||||||
.arena_link
|
.arena_link
|
||||||
.try_get(parent_id)
|
.get(parent_id)
|
||||||
.map_err(|_| Error::FatalInternal("Failed to find parent"))?;
|
.ok_or_else(|| Error::FatalInternal("Failed to find parent"))?;
|
||||||
|
|
||||||
scope = Some(parent);
|
scope = Some(parent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,7 +162,7 @@ where
|
||||||
new.ass_scope.set(scope_id);
|
new.ass_scope.set(scope_id);
|
||||||
|
|
||||||
// make sure the component's caller function is up to date
|
// make sure the component's caller function is up to date
|
||||||
let scope = self.components.try_get_mut(scope_id.unwrap()).unwrap();
|
let scope = self.components.get_mut(scope_id.unwrap()).unwrap();
|
||||||
|
|
||||||
scope.caller = new.caller.clone();
|
scope.caller = new.caller.clone();
|
||||||
|
|
||||||
|
@ -406,7 +406,7 @@ where
|
||||||
.components
|
.components
|
||||||
.with(|components| {
|
.with(|components| {
|
||||||
components.insert_with_key(|new_idx| {
|
components.insert_with_key(|new_idx| {
|
||||||
let parent_scope = self.components.try_get(parent_idx).unwrap();
|
let parent_scope = self.components.get(parent_idx).unwrap();
|
||||||
let height = parent_scope.height + 1;
|
let height = parent_scope.height + 1;
|
||||||
Scope::new(
|
Scope::new(
|
||||||
caller,
|
caller,
|
||||||
|
@ -495,7 +495,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
||||||
while let Some(scope_id) = scopes_to_explore.pop() {
|
while let Some(scope_id) = scopes_to_explore.pop() {
|
||||||
// If we're planning on deleting this node, then we don't need to both rendering it
|
// If we're planning on deleting this node, then we don't need to both rendering it
|
||||||
self.seen_nodes.insert(scope_id);
|
self.seen_nodes.insert(scope_id);
|
||||||
let scope = self.components.try_get(scope_id).unwrap();
|
let scope = self.components.get(scope_id).unwrap();
|
||||||
for child in scope.descendents.borrow().iter() {
|
for child in scope.descendents.borrow().iter() {
|
||||||
// Add this node to be explored
|
// Add this node to be explored
|
||||||
scopes_to_explore.push(child.clone());
|
scopes_to_explore.push(child.clone());
|
||||||
|
@ -1324,7 +1324,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
||||||
|
|
||||||
// For components, we load their root and push them onto the stack
|
// For components, we load their root and push them onto the stack
|
||||||
VNodeKind::Component(sc) => {
|
VNodeKind::Component(sc) => {
|
||||||
let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap();
|
let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap();
|
||||||
|
|
||||||
// Simply swap the current node on the stack with the root of the component
|
// Simply swap the current node on the stack with the root of the component
|
||||||
*node = scope.root();
|
*node = scope.root();
|
||||||
|
|
|
@ -10,8 +10,9 @@
|
||||||
//!
|
//!
|
||||||
|
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
format_args_f, html, rsx, DioxusElement, DomEdit, EventTrigger, LazyNodes, NodeFactory,
|
format_args_f, html, rsx, Context, DioxusElement, DomEdit, EventTrigger, LazyNodes,
|
||||||
Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom, VirtualEvent, FC,
|
NodeFactory, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom,
|
||||||
|
VirtualEvent, FC,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
|
|
@ -224,7 +224,7 @@ impl VirtualDom {
|
||||||
&self.tasks,
|
&self.tasks,
|
||||||
);
|
);
|
||||||
|
|
||||||
let cur_component = self.components.try_get_mut(self.base_scope).unwrap();
|
let cur_component = self.components.get_mut(self.base_scope).unwrap();
|
||||||
cur_component.run_scope()?;
|
cur_component.run_scope()?;
|
||||||
|
|
||||||
let meta = diff_machine.create(cur_component.next_frame());
|
let meta = diff_machine.create(cur_component.next_frame());
|
||||||
|
@ -312,7 +312,7 @@ impl VirtualDom {
|
||||||
|
|
||||||
// Suspense Events! A component's suspended node is updated
|
// Suspense Events! A component's suspended node is updated
|
||||||
VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
|
VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
|
||||||
let scope = self.components.try_get_mut(trigger.originator).unwrap();
|
let scope = self.components.get_mut(trigger.originator).unwrap();
|
||||||
|
|
||||||
// safety: we are sure that there are no other references to the inner content of this hook
|
// safety: we are sure that there are no other references to the inner content of this hook
|
||||||
let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
|
let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
|
||||||
|
@ -339,8 +339,8 @@ impl VirtualDom {
|
||||||
// We use the reconciler to request new IDs and then commit/uncommit the IDs when the scheduler is finished
|
// We use the reconciler to request new IDs and then commit/uncommit the IDs when the scheduler is finished
|
||||||
_ => {
|
_ => {
|
||||||
self.components
|
self.components
|
||||||
.try_get_mut(trigger.originator)?
|
.get_mut(trigger.originator)
|
||||||
.call_listener(trigger)?;
|
.map(|f| f.call_listener(trigger));
|
||||||
|
|
||||||
// Now, there are events in the queue
|
// Now, there are events in the queue
|
||||||
let mut updates = self.event_queue.queue.as_ref().borrow_mut();
|
let mut updates = self.event_queue.queue.as_ref().borrow_mut();
|
||||||
|
@ -366,7 +366,7 @@ impl VirtualDom {
|
||||||
|
|
||||||
// Start a new mutable borrow to components
|
// Start a new mutable borrow to components
|
||||||
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified
|
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified
|
||||||
let cur_component = self.components.try_get_mut(update.idx).unwrap();
|
let cur_component = self.components.get_mut(update.idx).unwrap();
|
||||||
|
|
||||||
cur_component.run_scope()?;
|
cur_component.run_scope()?;
|
||||||
|
|
||||||
|
@ -380,7 +380,7 @@ impl VirtualDom {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn base_scope(&self) -> &Scope {
|
pub fn base_scope(&self) -> &Scope {
|
||||||
self.components.try_get(self.base_scope).unwrap()
|
self.components.get(self.base_scope).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -423,7 +423,7 @@ pub trait GlobalAttributes {
|
||||||
list_style_position: "list-style-position",
|
list_style_position: "list-style-position",
|
||||||
|
|
||||||
/// Specifies the marker style for a list_item.
|
/// Specifies the marker style for a list_item.
|
||||||
list_style_type: "list-style-type",
|
list_styler_type: "list-style-type",
|
||||||
|
|
||||||
/// Sets the margin on all four sides of the element.
|
/// Sets the margin on all four sides of the element.
|
||||||
margin: "margin",
|
margin: "margin",
|
||||||
|
@ -1016,6 +1016,8 @@ builder_constructors! {
|
||||||
r#type: Mime,
|
r#type: Mime,
|
||||||
// ping: SpacedList<Uri>,
|
// ping: SpacedList<Uri>,
|
||||||
// rel: SpacedList<LinkType>,
|
// rel: SpacedList<LinkType>,
|
||||||
|
ping: SpacedList,
|
||||||
|
rel: SpacedList,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Build a
|
/// Build a
|
||||||
|
@ -1274,6 +1276,14 @@ builder_constructors! {
|
||||||
src: Uri,
|
src: Uri,
|
||||||
srcdoc: Uri,
|
srcdoc: Uri,
|
||||||
width: usize,
|
width: usize,
|
||||||
|
|
||||||
|
marginWidth: String,
|
||||||
|
align: String,
|
||||||
|
longdesc: String,
|
||||||
|
|
||||||
|
scrolling: String,
|
||||||
|
marginHeight: String,
|
||||||
|
frameBorder: String,
|
||||||
// sandbox: SpacedSet<Sandbox>,
|
// sandbox: SpacedSet<Sandbox>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1850,7 +1860,7 @@ pub trait SvgAttributes {
|
||||||
to: "to",
|
to: "to",
|
||||||
transform: "transform",
|
transform: "transform",
|
||||||
transform_origin: "transform-origin",
|
transform_origin: "transform-origin",
|
||||||
_type: "_type",
|
r#type: "_type",
|
||||||
u1: "u1",
|
u1: "u1",
|
||||||
u2: "u2",
|
u2: "u2",
|
||||||
underline_position: "underline-position",
|
underline_position: "underline-position",
|
||||||
|
|
|
@ -22,7 +22,7 @@ async fn main() -> Result<(), std::io::Error> {
|
||||||
let dom = VirtualDom::launch_with_props_in_place(Example, ExampleProps { initial_name });
|
let dom = VirtualDom::launch_with_props_in_place(Example, ExampleProps { initial_name });
|
||||||
|
|
||||||
Ok(Response::builder(200)
|
Ok(Response::builder(200)
|
||||||
.body(format!("{}", dioxus_ssr::render_root(&dom)))
|
.body(format!("{}", dioxus_ssr::render_vdom(&dom)))
|
||||||
.content_type(tide::http::mime::HTML)
|
.content_type(tide::http::mime::HTML)
|
||||||
.build())
|
.build())
|
||||||
});
|
});
|
||||||
|
@ -38,7 +38,7 @@ struct ExampleProps {
|
||||||
initial_name: String,
|
initial_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static Example: FC<ExampleProps> = |cx| {
|
static Example: FC<ExampleProps> = |cx| {
|
||||||
let dispaly_name = use_state(cx, move || cx.initial_name.clone());
|
let dispaly_name = use_state(cx, move || cx.initial_name.clone());
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
|
|
|
@ -15,7 +15,7 @@ fn main() {
|
||||||
let mut dom = VirtualDom::new(App);
|
let mut dom = VirtualDom::new(App);
|
||||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||||
|
|
||||||
file.write_fmt(format_args!("{}", TextRenderer::new(&dom)))
|
file.write_fmt(format_args!("{}", TextRenderer::from_vdom(&dom)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,21 @@ use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
|
|
||||||
pub fn render_root(vdom: &VirtualDom) -> String {
|
pub fn render_vnode(vnode: &VNode, string: &mut String) {}
|
||||||
format!("{:}", TextRenderer::new(vdom))
|
|
||||||
|
pub fn render_vdom(vdom: &VirtualDom) -> String {
|
||||||
|
format!("{:}", TextRenderer::from_vdom(vdom))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
|
||||||
|
Some(format!(
|
||||||
|
"{:}",
|
||||||
|
TextRenderer {
|
||||||
|
cfg: SsrConfig::default(),
|
||||||
|
root: vdom.components.get(scope).unwrap().root(),
|
||||||
|
vdom: Some(vdom)
|
||||||
|
}
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SsrConfig {
|
pub struct SsrConfig {
|
||||||
|
@ -56,14 +69,22 @@ impl Default for SsrConfig {
|
||||||
/// assert_eq!(output, "<div>hello world</div>");
|
/// assert_eq!(output, "<div>hello world</div>");
|
||||||
/// ```
|
/// ```
|
||||||
pub struct TextRenderer<'a> {
|
pub struct TextRenderer<'a> {
|
||||||
vdom: &'a VirtualDom,
|
vdom: Option<&'a VirtualDom>,
|
||||||
|
root: &'a VNode<'a>,
|
||||||
cfg: SsrConfig,
|
cfg: SsrConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for TextRenderer<'_> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.html_render(self.root, f, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> TextRenderer<'a> {
|
impl<'a> TextRenderer<'a> {
|
||||||
pub fn new(vdom: &'a VirtualDom) -> Self {
|
pub fn from_vdom(vdom: &'a VirtualDom) -> Self {
|
||||||
Self {
|
Self {
|
||||||
vdom,
|
root: vdom.base_scope().root(),
|
||||||
|
vdom: Some(vdom),
|
||||||
cfg: SsrConfig::default(),
|
cfg: SsrConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,10 +162,10 @@ impl<'a> TextRenderer<'a> {
|
||||||
}
|
}
|
||||||
VNodeKind::Component(vcomp) => {
|
VNodeKind::Component(vcomp) => {
|
||||||
let idx = vcomp.ass_scope.get().unwrap();
|
let idx = vcomp.ass_scope.get().unwrap();
|
||||||
|
if let Some(vdom) = self.vdom {
|
||||||
let new_node = self.vdom.components.try_get(idx).unwrap().root();
|
let new_node = vdom.components.get(idx).unwrap().root();
|
||||||
|
self.html_render(new_node, f, il + 1)?;
|
||||||
self.html_render(new_node, f, il + 1)?;
|
}
|
||||||
}
|
}
|
||||||
VNodeKind::Suspended { .. } => {
|
VNodeKind::Suspended { .. } => {
|
||||||
// we can't do anything with suspended nodes
|
// we can't do anything with suspended nodes
|
||||||
|
@ -154,14 +175,6 @@ impl<'a> TextRenderer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for TextRenderer<'_> {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let root = self.vdom.base_scope();
|
|
||||||
let root_node = root.root();
|
|
||||||
self.html_render(root_node, f, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -216,21 +229,21 @@ mod tests {
|
||||||
fn to_string_works() {
|
fn to_string_works() {
|
||||||
let mut dom = VirtualDom::new(SIMPLE_APP);
|
let mut dom = VirtualDom::new(SIMPLE_APP);
|
||||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||||
dbg!(render_root(&dom));
|
dbg!(render_vdom(&dom));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nested() {
|
fn nested() {
|
||||||
let mut dom = VirtualDom::new(NESTED_APP);
|
let mut dom = VirtualDom::new(NESTED_APP);
|
||||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||||
dbg!(render_root(&dom));
|
dbg!(render_vdom(&dom));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fragment_app() {
|
fn fragment_app() {
|
||||||
let mut dom = VirtualDom::new(FRAGMENT_APP);
|
let mut dom = VirtualDom::new(FRAGMENT_APP);
|
||||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||||
dbg!(render_root(&dom));
|
dbg!(render_vdom(&dom));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -243,7 +256,7 @@ mod tests {
|
||||||
let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
|
let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
|
||||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||||
|
|
||||||
file.write_fmt(format_args!("{}", TextRenderer::new(&dom)))
|
file.write_fmt(format_args!("{}", TextRenderer::from_vdom(&dom)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,6 +276,6 @@ mod tests {
|
||||||
|
|
||||||
let mut dom = VirtualDom::new(STLYE_APP);
|
let mut dom = VirtualDom::new(STLYE_APP);
|
||||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||||
dbg!(render_root(&dom));
|
dbg!(render_vdom(&dom));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,12 @@ use std::{pin::Pin, time::Duration};
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
use dioxus_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Setup logging
|
// Setup logging
|
||||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||||
|
|
||||||
// Run the app
|
dioxus_web::launch(App, |c| c)
|
||||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
|
@ -38,17 +35,12 @@ static App: FC<()> = |cx| {
|
||||||
|| surf::get(ENDPOINT).recv_json::<DogApi>(),
|
|| surf::get(ENDPOINT).recv_json::<DogApi>(),
|
||||||
|cx, res| match res {
|
|cx, res| match res {
|
||||||
Ok(res) => rsx!(in cx, img { src: "{res.message}" }),
|
Ok(res) => rsx!(in cx, img { src: "{res.message}" }),
|
||||||
Err(err) => rsx!(in cx, div { "No doggos for you :(" }),
|
Err(_err) => rsx!(in cx, div { "No doggos for you :(" }),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
log::error!("RIP WE RAN THE COMPONENT");
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div { style: { align_items: "center" }
|
||||||
style: {
|
|
||||||
align_items: "center"
|
|
||||||
}
|
|
||||||
section { class: "py-12 px-4 text-center"
|
section { class: "py-12 px-4 text-center"
|
||||||
div { class: "w-full max-w-2xl mx-auto"
|
div { class: "w-full max-w-2xl mx-auto"
|
||||||
span { class: "text-sm font-semibold"
|
span { class: "text-sm font-semibold"
|
||||||
|
|
292
packages/web/src/cache.rs
Normal file
292
packages/web/src/cache.rs
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
/// Wasm-bindgen has a performance option to intern commonly used phrases
|
||||||
|
/// This saves the decoding cost, making the interaction of Rust<->JS more performant.
|
||||||
|
/// We intern all the HTML tags and attributes, making most operations much faster.
|
||||||
|
///
|
||||||
|
/// Interning takes about 1ms at the start of the app, but saves a *ton* of time later on.
|
||||||
|
pub fn intern_cache() {
|
||||||
|
let cached_words = [
|
||||||
|
// All the HTML Tags
|
||||||
|
"a",
|
||||||
|
"abbr",
|
||||||
|
"address",
|
||||||
|
"area",
|
||||||
|
"article",
|
||||||
|
"aside",
|
||||||
|
"audio",
|
||||||
|
"b",
|
||||||
|
"base",
|
||||||
|
"bdi",
|
||||||
|
"bdo",
|
||||||
|
"big",
|
||||||
|
"blockquote",
|
||||||
|
"body",
|
||||||
|
"br",
|
||||||
|
"button",
|
||||||
|
"canvas",
|
||||||
|
"caption",
|
||||||
|
"cite",
|
||||||
|
"code",
|
||||||
|
"col",
|
||||||
|
"colgroup",
|
||||||
|
"command",
|
||||||
|
"data",
|
||||||
|
"datalist",
|
||||||
|
"dd",
|
||||||
|
"del",
|
||||||
|
"details",
|
||||||
|
"dfn",
|
||||||
|
"dialog",
|
||||||
|
"div",
|
||||||
|
"dl",
|
||||||
|
"dt",
|
||||||
|
"em",
|
||||||
|
"embed",
|
||||||
|
"fieldset",
|
||||||
|
"figcaption",
|
||||||
|
"figure",
|
||||||
|
"footer",
|
||||||
|
"form",
|
||||||
|
"h1",
|
||||||
|
"h2",
|
||||||
|
"h3",
|
||||||
|
"h4",
|
||||||
|
"h5",
|
||||||
|
"h6",
|
||||||
|
"head",
|
||||||
|
"header",
|
||||||
|
"hr",
|
||||||
|
"html",
|
||||||
|
"i",
|
||||||
|
"iframe",
|
||||||
|
"img",
|
||||||
|
"input",
|
||||||
|
"ins",
|
||||||
|
"kbd",
|
||||||
|
"keygen",
|
||||||
|
"label",
|
||||||
|
"legend",
|
||||||
|
"li",
|
||||||
|
"link",
|
||||||
|
"main",
|
||||||
|
"map",
|
||||||
|
"mark",
|
||||||
|
"menu",
|
||||||
|
"menuitem",
|
||||||
|
"meta",
|
||||||
|
"meter",
|
||||||
|
"nav",
|
||||||
|
"noscript",
|
||||||
|
"object",
|
||||||
|
"ol",
|
||||||
|
"optgroup",
|
||||||
|
"option",
|
||||||
|
"output",
|
||||||
|
"p",
|
||||||
|
"param",
|
||||||
|
"picture",
|
||||||
|
"pre",
|
||||||
|
"progress",
|
||||||
|
"q",
|
||||||
|
"rp",
|
||||||
|
"rt",
|
||||||
|
"ruby",
|
||||||
|
"s",
|
||||||
|
"samp",
|
||||||
|
"script",
|
||||||
|
"section",
|
||||||
|
"select",
|
||||||
|
"small",
|
||||||
|
"source",
|
||||||
|
"span",
|
||||||
|
"strong",
|
||||||
|
"style",
|
||||||
|
"sub",
|
||||||
|
"summary",
|
||||||
|
"sup",
|
||||||
|
"table",
|
||||||
|
"tbody",
|
||||||
|
"td",
|
||||||
|
"textarea",
|
||||||
|
"tfoot",
|
||||||
|
"th",
|
||||||
|
"thead",
|
||||||
|
"time",
|
||||||
|
"title",
|
||||||
|
"tr",
|
||||||
|
"track",
|
||||||
|
"u",
|
||||||
|
"ul",
|
||||||
|
"var",
|
||||||
|
"video",
|
||||||
|
"wbr",
|
||||||
|
// All the event handlers
|
||||||
|
"Attribute",
|
||||||
|
"accept",
|
||||||
|
"accept-charset",
|
||||||
|
"accesskey",
|
||||||
|
"action",
|
||||||
|
"alt",
|
||||||
|
"async",
|
||||||
|
"autocomplete",
|
||||||
|
"autofocus",
|
||||||
|
"autoplay",
|
||||||
|
"charset",
|
||||||
|
"checked",
|
||||||
|
"cite",
|
||||||
|
"class",
|
||||||
|
"cols",
|
||||||
|
"colspan",
|
||||||
|
"content",
|
||||||
|
"contenteditable",
|
||||||
|
"controls",
|
||||||
|
"coords",
|
||||||
|
"data",
|
||||||
|
"data-*",
|
||||||
|
"datetime",
|
||||||
|
"default",
|
||||||
|
"defer",
|
||||||
|
"dir",
|
||||||
|
"dirname",
|
||||||
|
"disabled",
|
||||||
|
"download",
|
||||||
|
"draggable",
|
||||||
|
"enctype",
|
||||||
|
"for",
|
||||||
|
"form",
|
||||||
|
"formaction",
|
||||||
|
"headers",
|
||||||
|
"height",
|
||||||
|
"hidden",
|
||||||
|
"high",
|
||||||
|
"href",
|
||||||
|
"hreflang",
|
||||||
|
"http-equiv",
|
||||||
|
"id",
|
||||||
|
"ismap",
|
||||||
|
"kind",
|
||||||
|
"label",
|
||||||
|
"lang",
|
||||||
|
"list",
|
||||||
|
"loop",
|
||||||
|
"low",
|
||||||
|
"max",
|
||||||
|
"maxlength",
|
||||||
|
"media",
|
||||||
|
"method",
|
||||||
|
"min",
|
||||||
|
"multiple",
|
||||||
|
"muted",
|
||||||
|
"name",
|
||||||
|
"novalidate",
|
||||||
|
"onabort",
|
||||||
|
"onafterprint",
|
||||||
|
"onbeforeprint",
|
||||||
|
"onbeforeunload",
|
||||||
|
"onblur",
|
||||||
|
"oncanplay",
|
||||||
|
"oncanplaythrough",
|
||||||
|
"onchange",
|
||||||
|
"onclick",
|
||||||
|
"oncontextmenu",
|
||||||
|
"oncopy",
|
||||||
|
"oncuechange",
|
||||||
|
"oncut",
|
||||||
|
"ondblclick",
|
||||||
|
"ondrag",
|
||||||
|
"ondragend",
|
||||||
|
"ondragenter",
|
||||||
|
"ondragleave",
|
||||||
|
"ondragover",
|
||||||
|
"ondragstart",
|
||||||
|
"ondrop",
|
||||||
|
"ondurationchange",
|
||||||
|
"onemptied",
|
||||||
|
"onended",
|
||||||
|
"onerror",
|
||||||
|
"onfocus",
|
||||||
|
"onhashchange",
|
||||||
|
"oninput",
|
||||||
|
"oninvalid",
|
||||||
|
"onkeydown",
|
||||||
|
"onkeypress",
|
||||||
|
"onkeyup",
|
||||||
|
"onload",
|
||||||
|
"onloadeddata",
|
||||||
|
"onloadedmetadata",
|
||||||
|
"onloadstart",
|
||||||
|
"onmousedown",
|
||||||
|
"onmousemove",
|
||||||
|
"onmouseout",
|
||||||
|
"onmouseover",
|
||||||
|
"onmouseup",
|
||||||
|
"onmousewheel",
|
||||||
|
"onoffline",
|
||||||
|
"ononline",
|
||||||
|
"<body>",
|
||||||
|
"onpageshow",
|
||||||
|
"onpaste",
|
||||||
|
"onpause",
|
||||||
|
"onplay",
|
||||||
|
"onplaying",
|
||||||
|
"<body>",
|
||||||
|
"onprogress",
|
||||||
|
"onratechange",
|
||||||
|
"onreset",
|
||||||
|
"onresize",
|
||||||
|
"onscroll",
|
||||||
|
"onsearch",
|
||||||
|
"onseeked",
|
||||||
|
"onseeking",
|
||||||
|
"onselect",
|
||||||
|
"onstalled",
|
||||||
|
"<body>",
|
||||||
|
"onsubmit",
|
||||||
|
"onsuspend",
|
||||||
|
"ontimeupdate",
|
||||||
|
"ontoggle",
|
||||||
|
"onunload",
|
||||||
|
"onvolumechange",
|
||||||
|
"onwaiting",
|
||||||
|
"onwheel",
|
||||||
|
"open",
|
||||||
|
"optimum",
|
||||||
|
"pattern",
|
||||||
|
"placeholder",
|
||||||
|
"poster",
|
||||||
|
"preload",
|
||||||
|
"readonly",
|
||||||
|
"rel",
|
||||||
|
"required",
|
||||||
|
"reversed",
|
||||||
|
"rows",
|
||||||
|
"rowspan",
|
||||||
|
"sandbox",
|
||||||
|
"scope",
|
||||||
|
"selected",
|
||||||
|
"shape",
|
||||||
|
"size",
|
||||||
|
"sizes",
|
||||||
|
"span",
|
||||||
|
"spellcheck",
|
||||||
|
"src",
|
||||||
|
"srcdoc",
|
||||||
|
"srclang",
|
||||||
|
"srcset",
|
||||||
|
"start",
|
||||||
|
"step",
|
||||||
|
"style",
|
||||||
|
"tabindex",
|
||||||
|
"target",
|
||||||
|
"title",
|
||||||
|
"translate",
|
||||||
|
"type",
|
||||||
|
"usemap",
|
||||||
|
"value",
|
||||||
|
"width",
|
||||||
|
"wrap",
|
||||||
|
];
|
||||||
|
|
||||||
|
for s in cached_words {
|
||||||
|
wasm_bindgen::intern(s);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,449 +3,112 @@
|
||||||
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
|
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
|
||||||
|
|
||||||
use dioxus::prelude::{Context, Properties, VNode};
|
use dioxus::prelude::{Context, Properties, VNode};
|
||||||
use futures_util::{pin_mut, Stream, StreamExt};
|
|
||||||
|
|
||||||
use fxhash::FxHashMap;
|
|
||||||
use web_sys::{window, Document, Element, Event, Node};
|
|
||||||
// use futures::{channel::mpsc, SinkExt, StreamExt};
|
|
||||||
|
|
||||||
use dioxus::virtual_dom::VirtualDom;
|
use dioxus::virtual_dom::VirtualDom;
|
||||||
pub use dioxus_core as dioxus;
|
pub use dioxus_core as dioxus;
|
||||||
use dioxus_core::{events::EventTrigger, prelude::FC};
|
use dioxus_core::{events::EventTrigger, prelude::FC};
|
||||||
|
use futures_util::{pin_mut, Stream, StreamExt};
|
||||||
|
use fxhash::FxHashMap;
|
||||||
|
use web_sys::{window, Document, Element, Event, Node};
|
||||||
|
|
||||||
pub use dioxus_core::prelude;
|
mod cache;
|
||||||
// pub mod interpreter;
|
mod new;
|
||||||
pub mod new;
|
|
||||||
|
|
||||||
/// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM.
|
/// Launches the VirtualDOM from the specified component function.
|
||||||
/// Under the hood, we leverage WebSys and interact directly with the DOM
|
|
||||||
///
|
///
|
||||||
pub struct WebsysRenderer {
|
/// This method will block the thread with `spawn_local`
|
||||||
internal_dom: VirtualDom,
|
pub fn launch<F>(root: FC<()>, config: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(()),
|
||||||
|
{
|
||||||
|
wasm_bindgen_futures::spawn_local(run(root))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebsysRenderer {
|
pub fn launch_with_props<T, F>(root: FC<T>, root_props: T, config: F)
|
||||||
/// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
|
where
|
||||||
/// See DioxusErrors for more information on how these errors could occour.
|
T: Properties + 'static,
|
||||||
///
|
F: FnOnce(()),
|
||||||
/// ```ignore
|
{
|
||||||
/// fn main() {
|
wasm_bindgen_futures::spawn_local(run_with_props(root, root_props))
|
||||||
/// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
}
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Run the app to completion, panicing if any error occurs while rendering.
|
|
||||||
/// Pairs well with the wasm_bindgen async handler
|
|
||||||
pub async fn start(root: FC<()>) {
|
|
||||||
Self::new(root).run().await.expect("Virtual DOM failed :(");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
|
/// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
|
||||||
///
|
/// See DioxusErrors for more information on how these errors could occour.
|
||||||
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
|
///
|
||||||
/// The root component can access things like routing in its context.
|
/// ```ignore
|
||||||
pub fn new(root: FC<()>) -> Self {
|
/// fn main() {
|
||||||
Self::new_with_props(root, ())
|
/// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||||
}
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Run the app to completion, panicing if any error occurs while rendering.
|
||||||
|
/// Pairs well with the wasm_bindgen async handler
|
||||||
|
pub async fn run(root: FC<()>) {
|
||||||
|
run_with_props(root, ()).await;
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new text-renderer instance from a functional component root.
|
pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) {
|
||||||
/// Automatically progresses the creation of the VNode tree to completion.
|
let dom = VirtualDom::new_with_props(root, root_props);
|
||||||
///
|
event_loop(dom).await.expect("Event loop failed");
|
||||||
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
|
}
|
||||||
pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
|
|
||||||
Self::from_vdom(VirtualDom::new_with_props(root, root_props))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new text renderer from an existing Virtual DOM.
|
pub async fn event_loop(mut internal_dom: VirtualDom) -> dioxus_core::error::Result<()> {
|
||||||
pub fn from_vdom(dom: VirtualDom) -> Self {
|
use wasm_bindgen::JsCast;
|
||||||
Self { internal_dom: dom }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
|
let root = prepare_websys_dom();
|
||||||
use wasm_bindgen::JsCast;
|
let root_node = root.clone().dyn_into::<Node>().unwrap();
|
||||||
|
|
||||||
let root = prepare_websys_dom();
|
let mut websys_dom = crate::new::WebsysDom::new(root.clone());
|
||||||
let root_node = root.clone().dyn_into::<Node>().unwrap();
|
|
||||||
|
|
||||||
let mut websys_dom = crate::new::WebsysDom::new(root.clone());
|
websys_dom.stack.push(root_node.clone());
|
||||||
|
websys_dom.stack.push(root_node);
|
||||||
|
|
||||||
websys_dom.stack.push(root_node.clone());
|
let mut edits = Vec::new();
|
||||||
websys_dom.stack.push(root_node);
|
internal_dom.rebuild(&mut websys_dom, &mut edits)?;
|
||||||
|
websys_dom.process_edits(&mut edits);
|
||||||
|
|
||||||
let mut edits = Vec::new();
|
log::info!("Going into event loop");
|
||||||
self.internal_dom.rebuild(&mut websys_dom, &mut edits)?;
|
loop {
|
||||||
websys_dom.process_edits(&mut edits);
|
let trigger = {
|
||||||
|
let real_queue = websys_dom.wait_for_event();
|
||||||
|
if internal_dom.tasks.is_empty() {
|
||||||
|
log::info!("tasks is empty, waiting for dom event to trigger soemthing");
|
||||||
|
real_queue.await
|
||||||
|
} else {
|
||||||
|
log::info!("tasks is not empty, waiting for either tasks or event system");
|
||||||
|
let task_queue = (&mut internal_dom.tasks).next();
|
||||||
|
|
||||||
log::info!("Going into event loop");
|
pin_mut!(real_queue);
|
||||||
loop {
|
pin_mut!(task_queue);
|
||||||
let trigger = {
|
|
||||||
let real_queue = websys_dom.wait_for_event();
|
|
||||||
if self.internal_dom.tasks.is_empty() {
|
|
||||||
log::info!("tasks is empty, waiting for dom event to trigger soemthing");
|
|
||||||
real_queue.await
|
|
||||||
} else {
|
|
||||||
log::info!("tasks is not empty, waiting for either tasks or event system");
|
|
||||||
let task_queue = (&mut self.internal_dom.tasks).next();
|
|
||||||
|
|
||||||
pin_mut!(real_queue);
|
match futures_util::future::select(real_queue, task_queue).await {
|
||||||
pin_mut!(task_queue);
|
futures_util::future::Either::Left((trigger, _)) => trigger,
|
||||||
|
futures_util::future::Either::Right((trigger, _)) => trigger,
|
||||||
match futures_util::future::select(real_queue, task_queue).await {
|
|
||||||
futures_util::future::Either::Left((trigger, _)) => trigger,
|
|
||||||
futures_util::future::Either::Right((trigger, _)) => trigger,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(real_trigger) = trigger {
|
|
||||||
log::info!("event received");
|
|
||||||
|
|
||||||
self.internal_dom.queue_event(real_trigger)?;
|
|
||||||
|
|
||||||
let mut edits = Vec::new();
|
|
||||||
self.internal_dom
|
|
||||||
.progress_with_event(&mut websys_dom, &mut edits)
|
|
||||||
.await?;
|
|
||||||
websys_dom.process_edits(&mut edits);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// should actually never return from this, should be an error, rustc just cant see it
|
if let Some(real_trigger) = trigger {
|
||||||
Ok(())
|
log::info!("event received");
|
||||||
|
|
||||||
|
internal_dom.queue_event(real_trigger)?;
|
||||||
|
|
||||||
|
let mut edits = Vec::new();
|
||||||
|
internal_dom
|
||||||
|
.progress_with_event(&mut websys_dom, &mut edits)
|
||||||
|
.await?;
|
||||||
|
websys_dom.process_edits(&mut edits);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// should actually never return from this, should be an error, rustc just cant see it
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_websys_dom() -> Element {
|
fn prepare_websys_dom() -> Element {
|
||||||
// Initialize the container on the dom
|
web_sys::window()
|
||||||
// Hook up the body as the root component to render tinto
|
.expect("should have access to the Window")
|
||||||
let window = web_sys::window().expect("should have access to the Window");
|
|
||||||
let document = window
|
|
||||||
.document()
|
.document()
|
||||||
.expect("should have access to the Document");
|
.expect("should have access to the Document")
|
||||||
|
.get_element_by_id("dioxusroot")
|
||||||
// let body = document.body().unwrap();
|
.unwrap()
|
||||||
let el = document.get_element_by_id("dioxusroot").unwrap();
|
|
||||||
|
|
||||||
// Build a dummy div
|
|
||||||
// let container: &Element = body.as_ref();
|
|
||||||
// container.set_inner_html("");
|
|
||||||
// container
|
|
||||||
// .append_child(
|
|
||||||
// document
|
|
||||||
// .create_element("div")
|
|
||||||
// .expect("should create element OK")
|
|
||||||
// .as_ref(),
|
|
||||||
// )
|
|
||||||
// .expect("should append child OK");
|
|
||||||
el
|
|
||||||
// container.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Progress the mount of the root component
|
|
||||||
|
|
||||||
// Iterate through the nodes, attaching the closure and sender to the listener
|
|
||||||
// {
|
|
||||||
// let mut remote_sender = sender.clone();
|
|
||||||
// let listener = move || {
|
|
||||||
// let event = EventTrigger::new();
|
|
||||||
// wasm_bindgen_futures::spawn_local(async move {
|
|
||||||
// remote_sender
|
|
||||||
// .send(event)
|
|
||||||
// .await
|
|
||||||
// .expect("Updating receiver failed");
|
|
||||||
// })
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Wasm-bindgen has a performance option to intern commonly used phrases
|
|
||||||
/// This saves the decoding cost, making the interaction of Rust<->JS more performant.
|
|
||||||
/// We intern all the HTML tags and attributes, making most operations much faster.
|
|
||||||
///
|
|
||||||
/// Interning takes about 1ms at the start of the app, but saves a *ton* of time later on.
|
|
||||||
pub fn intern_cache() {
|
|
||||||
let cached_words = [
|
|
||||||
// All the HTML Tags
|
|
||||||
"a",
|
|
||||||
"abbr",
|
|
||||||
"address",
|
|
||||||
"area",
|
|
||||||
"article",
|
|
||||||
"aside",
|
|
||||||
"audio",
|
|
||||||
"b",
|
|
||||||
"base",
|
|
||||||
"bdi",
|
|
||||||
"bdo",
|
|
||||||
"big",
|
|
||||||
"blockquote",
|
|
||||||
"body",
|
|
||||||
"br",
|
|
||||||
"button",
|
|
||||||
"canvas",
|
|
||||||
"caption",
|
|
||||||
"cite",
|
|
||||||
"code",
|
|
||||||
"col",
|
|
||||||
"colgroup",
|
|
||||||
"command",
|
|
||||||
"data",
|
|
||||||
"datalist",
|
|
||||||
"dd",
|
|
||||||
"del",
|
|
||||||
"details",
|
|
||||||
"dfn",
|
|
||||||
"dialog",
|
|
||||||
"div",
|
|
||||||
"dl",
|
|
||||||
"dt",
|
|
||||||
"em",
|
|
||||||
"embed",
|
|
||||||
"fieldset",
|
|
||||||
"figcaption",
|
|
||||||
"figure",
|
|
||||||
"footer",
|
|
||||||
"form",
|
|
||||||
"h1",
|
|
||||||
"h2",
|
|
||||||
"h3",
|
|
||||||
"h4",
|
|
||||||
"h5",
|
|
||||||
"h6",
|
|
||||||
"head",
|
|
||||||
"header",
|
|
||||||
"hr",
|
|
||||||
"html",
|
|
||||||
"i",
|
|
||||||
"iframe",
|
|
||||||
"img",
|
|
||||||
"input",
|
|
||||||
"ins",
|
|
||||||
"kbd",
|
|
||||||
"keygen",
|
|
||||||
"label",
|
|
||||||
"legend",
|
|
||||||
"li",
|
|
||||||
"link",
|
|
||||||
"main",
|
|
||||||
"map",
|
|
||||||
"mark",
|
|
||||||
"menu",
|
|
||||||
"menuitem",
|
|
||||||
"meta",
|
|
||||||
"meter",
|
|
||||||
"nav",
|
|
||||||
"noscript",
|
|
||||||
"object",
|
|
||||||
"ol",
|
|
||||||
"optgroup",
|
|
||||||
"option",
|
|
||||||
"output",
|
|
||||||
"p",
|
|
||||||
"param",
|
|
||||||
"picture",
|
|
||||||
"pre",
|
|
||||||
"progress",
|
|
||||||
"q",
|
|
||||||
"rp",
|
|
||||||
"rt",
|
|
||||||
"ruby",
|
|
||||||
"s",
|
|
||||||
"samp",
|
|
||||||
"script",
|
|
||||||
"section",
|
|
||||||
"select",
|
|
||||||
"small",
|
|
||||||
"source",
|
|
||||||
"span",
|
|
||||||
"strong",
|
|
||||||
"style",
|
|
||||||
"sub",
|
|
||||||
"summary",
|
|
||||||
"sup",
|
|
||||||
"table",
|
|
||||||
"tbody",
|
|
||||||
"td",
|
|
||||||
"textarea",
|
|
||||||
"tfoot",
|
|
||||||
"th",
|
|
||||||
"thead",
|
|
||||||
"time",
|
|
||||||
"title",
|
|
||||||
"tr",
|
|
||||||
"track",
|
|
||||||
"u",
|
|
||||||
"ul",
|
|
||||||
"var",
|
|
||||||
"video",
|
|
||||||
"wbr",
|
|
||||||
// All the event handlers
|
|
||||||
"Attribute",
|
|
||||||
"accept",
|
|
||||||
"accept-charset",
|
|
||||||
"accesskey",
|
|
||||||
"action",
|
|
||||||
"alt",
|
|
||||||
"async",
|
|
||||||
"autocomplete",
|
|
||||||
"autofocus",
|
|
||||||
"autoplay",
|
|
||||||
"charset",
|
|
||||||
"checked",
|
|
||||||
"cite",
|
|
||||||
"class",
|
|
||||||
"cols",
|
|
||||||
"colspan",
|
|
||||||
"content",
|
|
||||||
"contenteditable",
|
|
||||||
"controls",
|
|
||||||
"coords",
|
|
||||||
"data",
|
|
||||||
"data-*",
|
|
||||||
"datetime",
|
|
||||||
"default",
|
|
||||||
"defer",
|
|
||||||
"dir",
|
|
||||||
"dirname",
|
|
||||||
"disabled",
|
|
||||||
"download",
|
|
||||||
"draggable",
|
|
||||||
"enctype",
|
|
||||||
"for",
|
|
||||||
"form",
|
|
||||||
"formaction",
|
|
||||||
"headers",
|
|
||||||
"height",
|
|
||||||
"hidden",
|
|
||||||
"high",
|
|
||||||
"href",
|
|
||||||
"hreflang",
|
|
||||||
"http-equiv",
|
|
||||||
"id",
|
|
||||||
"ismap",
|
|
||||||
"kind",
|
|
||||||
"label",
|
|
||||||
"lang",
|
|
||||||
"list",
|
|
||||||
"loop",
|
|
||||||
"low",
|
|
||||||
"max",
|
|
||||||
"maxlength",
|
|
||||||
"media",
|
|
||||||
"method",
|
|
||||||
"min",
|
|
||||||
"multiple",
|
|
||||||
"muted",
|
|
||||||
"name",
|
|
||||||
"novalidate",
|
|
||||||
"onabort",
|
|
||||||
"onafterprint",
|
|
||||||
"onbeforeprint",
|
|
||||||
"onbeforeunload",
|
|
||||||
"onblur",
|
|
||||||
"oncanplay",
|
|
||||||
"oncanplaythrough",
|
|
||||||
"onchange",
|
|
||||||
"onclick",
|
|
||||||
"oncontextmenu",
|
|
||||||
"oncopy",
|
|
||||||
"oncuechange",
|
|
||||||
"oncut",
|
|
||||||
"ondblclick",
|
|
||||||
"ondrag",
|
|
||||||
"ondragend",
|
|
||||||
"ondragenter",
|
|
||||||
"ondragleave",
|
|
||||||
"ondragover",
|
|
||||||
"ondragstart",
|
|
||||||
"ondrop",
|
|
||||||
"ondurationchange",
|
|
||||||
"onemptied",
|
|
||||||
"onended",
|
|
||||||
"onerror",
|
|
||||||
"onfocus",
|
|
||||||
"onhashchange",
|
|
||||||
"oninput",
|
|
||||||
"oninvalid",
|
|
||||||
"onkeydown",
|
|
||||||
"onkeypress",
|
|
||||||
"onkeyup",
|
|
||||||
"onload",
|
|
||||||
"onloadeddata",
|
|
||||||
"onloadedmetadata",
|
|
||||||
"onloadstart",
|
|
||||||
"onmousedown",
|
|
||||||
"onmousemove",
|
|
||||||
"onmouseout",
|
|
||||||
"onmouseover",
|
|
||||||
"onmouseup",
|
|
||||||
"onmousewheel",
|
|
||||||
"onoffline",
|
|
||||||
"ononline",
|
|
||||||
"<body>",
|
|
||||||
"onpageshow",
|
|
||||||
"onpaste",
|
|
||||||
"onpause",
|
|
||||||
"onplay",
|
|
||||||
"onplaying",
|
|
||||||
"<body>",
|
|
||||||
"onprogress",
|
|
||||||
"onratechange",
|
|
||||||
"onreset",
|
|
||||||
"onresize",
|
|
||||||
"onscroll",
|
|
||||||
"onsearch",
|
|
||||||
"onseeked",
|
|
||||||
"onseeking",
|
|
||||||
"onselect",
|
|
||||||
"onstalled",
|
|
||||||
"<body>",
|
|
||||||
"onsubmit",
|
|
||||||
"onsuspend",
|
|
||||||
"ontimeupdate",
|
|
||||||
"ontoggle",
|
|
||||||
"onunload",
|
|
||||||
"onvolumechange",
|
|
||||||
"onwaiting",
|
|
||||||
"onwheel",
|
|
||||||
"open",
|
|
||||||
"optimum",
|
|
||||||
"pattern",
|
|
||||||
"placeholder",
|
|
||||||
"poster",
|
|
||||||
"preload",
|
|
||||||
"readonly",
|
|
||||||
"rel",
|
|
||||||
"required",
|
|
||||||
"reversed",
|
|
||||||
"rows",
|
|
||||||
"rowspan",
|
|
||||||
"sandbox",
|
|
||||||
"scope",
|
|
||||||
"selected",
|
|
||||||
"shape",
|
|
||||||
"size",
|
|
||||||
"sizes",
|
|
||||||
"span",
|
|
||||||
"spellcheck",
|
|
||||||
"src",
|
|
||||||
"srcdoc",
|
|
||||||
"srclang",
|
|
||||||
"srcset",
|
|
||||||
"start",
|
|
||||||
"step",
|
|
||||||
"style",
|
|
||||||
"tabindex",
|
|
||||||
"target",
|
|
||||||
"title",
|
|
||||||
"translate",
|
|
||||||
"type",
|
|
||||||
"usemap",
|
|
||||||
"value",
|
|
||||||
"width",
|
|
||||||
"wrap",
|
|
||||||
];
|
|
||||||
|
|
||||||
for s in cached_words {
|
|
||||||
wasm_bindgen::intern(s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,409 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::{cmp::min, rc::Rc};
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use wasm_bindgen::JsValue;
|
|
||||||
use web_sys::{Element, Node, Text};
|
|
||||||
|
|
||||||
/// Apply all of the patches to our old root node in order to create the new root node
|
|
||||||
/// that we desire.
|
|
||||||
/// This is usually used after diffing two virtual nodes.
|
|
||||||
pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<(), JsValue> {
|
|
||||||
// pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<ActiveClosures, JsValue> {
|
|
||||||
let root_node: Node = root_node.into();
|
|
||||||
|
|
||||||
let mut cur_node_idx = 0;
|
|
||||||
|
|
||||||
let mut nodes_to_find = HashSet::new();
|
|
||||||
|
|
||||||
for patch in patches {
|
|
||||||
nodes_to_find.insert(patch.node_idx());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut element_nodes_to_patch = HashMap::new();
|
|
||||||
let mut text_nodes_to_patch = HashMap::new();
|
|
||||||
|
|
||||||
// Closures that were added to the DOM during this patch operation.
|
|
||||||
// let mut active_closures = HashMap::new();
|
|
||||||
|
|
||||||
find_nodes(
|
|
||||||
root_node,
|
|
||||||
&mut cur_node_idx,
|
|
||||||
&mut nodes_to_find,
|
|
||||||
&mut element_nodes_to_patch,
|
|
||||||
&mut text_nodes_to_patch,
|
|
||||||
);
|
|
||||||
|
|
||||||
for patch in patches {
|
|
||||||
let patch_node_idx = patch.node_idx();
|
|
||||||
|
|
||||||
if let Some(element) = element_nodes_to_patch.get(&patch_node_idx) {
|
|
||||||
let new_closures = apply_element_patch(&element, &patch)?;
|
|
||||||
// active_closures.extend(new_closures);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(text_node) = text_nodes_to_patch.get(&patch_node_idx) {
|
|
||||||
apply_text_patch(&text_node, &patch)?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
unreachable!("Getting here means we didn't find the element or next node that we were supposed to patch.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ok(active_closures)
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_nodes(
|
|
||||||
root_node: Node,
|
|
||||||
cur_node_idx: &mut usize,
|
|
||||||
nodes_to_find: &mut HashSet<usize>,
|
|
||||||
element_nodes_to_patch: &mut HashMap<usize, Element>,
|
|
||||||
text_nodes_to_patch: &mut HashMap<usize, Text>,
|
|
||||||
) {
|
|
||||||
if nodes_to_find.len() == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use child_nodes() instead of children() because children() ignores text nodes
|
|
||||||
let children = root_node.child_nodes();
|
|
||||||
let child_node_count = children.length();
|
|
||||||
|
|
||||||
// If the root node matches, mark it for patching
|
|
||||||
if nodes_to_find.get(&cur_node_idx).is_some() {
|
|
||||||
match root_node.node_type() {
|
|
||||||
Node::ELEMENT_NODE => {
|
|
||||||
element_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
|
|
||||||
}
|
|
||||||
Node::TEXT_NODE => {
|
|
||||||
text_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
|
|
||||||
}
|
|
||||||
other => unimplemented!("Unsupported root node type: {}", other),
|
|
||||||
}
|
|
||||||
nodes_to_find.remove(&cur_node_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
*cur_node_idx += 1;
|
|
||||||
|
|
||||||
for i in 0..child_node_count {
|
|
||||||
let node = children.item(i).unwrap();
|
|
||||||
|
|
||||||
match node.node_type() {
|
|
||||||
Node::ELEMENT_NODE => {
|
|
||||||
find_nodes(
|
|
||||||
node,
|
|
||||||
cur_node_idx,
|
|
||||||
nodes_to_find,
|
|
||||||
element_nodes_to_patch,
|
|
||||||
text_nodes_to_patch,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Node::TEXT_NODE => {
|
|
||||||
if nodes_to_find.get(&cur_node_idx).is_some() {
|
|
||||||
text_nodes_to_patch.insert(*cur_node_idx, node.unchecked_into());
|
|
||||||
}
|
|
||||||
|
|
||||||
*cur_node_idx += 1;
|
|
||||||
}
|
|
||||||
Node::COMMENT_NODE => {
|
|
||||||
// At this time we do not support user entered comment nodes, so if we see a comment
|
|
||||||
// then it was a delimiter created by virtual-dom-rs in order to ensure that two
|
|
||||||
// neighboring text nodes did not get merged into one by the browser. So we skip
|
|
||||||
// over this virtual-dom-rs generated comment node.
|
|
||||||
}
|
|
||||||
_other => {
|
|
||||||
// Ignoring unsupported child node type
|
|
||||||
// TODO: What do we do with this situation? Log a warning?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub type ActiveClosures = HashMap<u32, Vec<DynClosure>>;
|
|
||||||
|
|
||||||
// fn apply_element_patch(node: &Element, patch: &Patch) -> Result<ActiveClosures, JsValue> {
|
|
||||||
fn apply_element_patch(node: &Element, patch: &Patch) -> Result<(), JsValue> {
|
|
||||||
// let active_closures = HashMap::new();
|
|
||||||
|
|
||||||
match patch {
|
|
||||||
Patch::AddAttributes(_node_idx, attributes) => {
|
|
||||||
for (attrib_name, attrib_val) in attributes.iter() {
|
|
||||||
node.set_attribute(attrib_name, attrib_val)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ok(active_closures)
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Patch::RemoveAttributes(_node_idx, attributes) => {
|
|
||||||
for attrib_name in attributes.iter() {
|
|
||||||
node.remove_attribute(attrib_name)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ok(active_closures)
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Patch::Replace(_node_idx, new_node) => {
|
|
||||||
let created_node = create_dom_node(&new_node);
|
|
||||||
|
|
||||||
node.replace_with_with_node_1(&created_node.node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
// Ok(created_node.closures)
|
|
||||||
}
|
|
||||||
Patch::TruncateChildren(_node_idx, num_children_remaining) => {
|
|
||||||
let children = node.child_nodes();
|
|
||||||
let mut child_count = children.length();
|
|
||||||
|
|
||||||
// We skip over any separators that we placed between two text nodes
|
|
||||||
// -> `<!--ptns-->`
|
|
||||||
// and trim all children that come after our new desired `num_children_remaining`
|
|
||||||
let mut non_separator_children_found = 0;
|
|
||||||
|
|
||||||
for index in 0 as u32..child_count {
|
|
||||||
let child = children
|
|
||||||
.get(min(index, child_count - 1))
|
|
||||||
.expect("Potential child to truncate");
|
|
||||||
|
|
||||||
// If this is a comment node then we know that it is a `<!--ptns-->`
|
|
||||||
// text node separator that was created in virtual_node/mod.rs.
|
|
||||||
if child.node_type() == Node::COMMENT_NODE {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
non_separator_children_found += 1;
|
|
||||||
|
|
||||||
if non_separator_children_found <= *num_children_remaining as u32 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
node.remove_child(&child).expect("Truncated children");
|
|
||||||
child_count -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
// Ok(active_closures)
|
|
||||||
}
|
|
||||||
Patch::AppendChildren(_node_idx, new_nodes) => {
|
|
||||||
let parent = &node;
|
|
||||||
|
|
||||||
let mut active_closures = HashMap::new();
|
|
||||||
|
|
||||||
for new_node in new_nodes {
|
|
||||||
let created_node = create_dom_node(&new_node);
|
|
||||||
// let created_node = new_node.create_dom_node();
|
|
||||||
|
|
||||||
parent.append_child(&created_node.node)?;
|
|
||||||
|
|
||||||
active_closures.extend(created_node.closures);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
// Ok(active_closures)
|
|
||||||
}
|
|
||||||
Patch::ChangeText(_node_idx, _new_node) => {
|
|
||||||
unreachable!("Elements should not receive ChangeText patches.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_text_patch(node: &Text, patch: &Patch) -> Result<(), JsValue> {
|
|
||||||
match patch {
|
|
||||||
Patch::ChangeText(_node_idx, new_node) => {
|
|
||||||
node.set_node_value(Some(&new_node.text));
|
|
||||||
}
|
|
||||||
Patch::Replace(_node_idx, new_node) => {
|
|
||||||
node.replace_with_with_node_1(&create_dom_node(&new_node).node)?;
|
|
||||||
// node.replace_with_with_node_1(&new_node.create_dom_node().node)?;
|
|
||||||
}
|
|
||||||
other => unreachable!(
|
|
||||||
"Text nodes should only receive ChangeText or Replace patches, not ",
|
|
||||||
// other,
|
|
||||||
// "Text nodes should only receive ChangeText or Replace patches, not {:?}.",
|
|
||||||
// other,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node along with all of the closures that were created for that
|
|
||||||
/// node's events and all of it's child node's events.
|
|
||||||
pub struct CreatedNode<T> {
|
|
||||||
/// A `Node` or `Element` that was created from a `VirtualNode`
|
|
||||||
pub node: T,
|
|
||||||
/// A map of a node's unique identifier along with all of the Closures for that node.
|
|
||||||
///
|
|
||||||
/// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
|
|
||||||
/// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
|
|
||||||
/// memory.
|
|
||||||
pub closures: HashMap<u32, Vec<DynClosure>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
|
|
||||||
/// any Closure regardless of the arguments.
|
|
||||||
pub type DynClosure = Rc<dyn AsRef<JsValue>>;
|
|
||||||
|
|
||||||
impl<T> CreatedNode<T> {
|
|
||||||
pub fn without_closures<N: Into<T>>(node: N) -> Self {
|
|
||||||
CreatedNode {
|
|
||||||
node: node.into(),
|
|
||||||
closures: HashMap::with_capacity(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> std::ops::Deref for CreatedNode<T> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CreatedNode<Element>> for CreatedNode<Node> {
|
|
||||||
fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
|
|
||||||
CreatedNode {
|
|
||||||
node: other.node.into(),
|
|
||||||
closures: other.closures,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn create_dom_node(node: &VNode<'_>) -> CreatedNode<Node> {
|
|
||||||
match node {
|
|
||||||
VNode::Text(text_node) => CreatedNode::without_closures(create_text_node(text_node)),
|
|
||||||
VNode::Element(element_node) => create_element_node(element_node).into(),
|
|
||||||
// VNode::Element(element_node) => element_node.create_element_node().into(),
|
|
||||||
VNode::Suspended => todo!(" not iimplemented yet"),
|
|
||||||
VNode::Component(_) => todo!(" not iimplemented yet"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a DOM element by recursively creating DOM nodes for this element and it's
|
|
||||||
/// children, it's children's children, etc.
|
|
||||||
pub fn create_element_node(node: &dioxus_core::nodes::VElement) -> CreatedNode<Element> {
|
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
|
||||||
|
|
||||||
// TODO: enable svg again
|
|
||||||
// let element = if html_validation::is_svg_namespace(&node.tag_name) {
|
|
||||||
// document
|
|
||||||
// .create_element_ns(Some("http://www.w3.org/2000/svg"), &node.tag_name)
|
|
||||||
// .unwrap()
|
|
||||||
// } else {
|
|
||||||
let element = document.create_element(&node.tag_name).unwrap();
|
|
||||||
// };
|
|
||||||
|
|
||||||
let mut closures = HashMap::new();
|
|
||||||
|
|
||||||
node.attributes
|
|
||||||
.iter()
|
|
||||||
.map(|f| (f.name, f.value))
|
|
||||||
.for_each(|(name, value)| {
|
|
||||||
if name == "unsafe_inner_html" {
|
|
||||||
element.set_inner_html(value);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
element
|
|
||||||
.set_attribute(name, value)
|
|
||||||
.expect("Set element attribute in create element");
|
|
||||||
});
|
|
||||||
|
|
||||||
// if node.events.0.len() > 0 {
|
|
||||||
// let unique_id = create_unique_identifier();
|
|
||||||
|
|
||||||
// element
|
|
||||||
// .set_attribute("data-vdom-id".into(), &unique_id.to_string())
|
|
||||||
// .expect("Could not set attribute on element");
|
|
||||||
|
|
||||||
// closures.insert(unique_id, vec![]);
|
|
||||||
|
|
||||||
// node.events.0.iter().for_each(|(onevent, callback)| {
|
|
||||||
// // onclick -> click
|
|
||||||
// let event = &onevent[2..];
|
|
||||||
|
|
||||||
// let current_elem: &EventTarget = element.dyn_ref().unwrap();
|
|
||||||
|
|
||||||
// current_elem
|
|
||||||
// .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
// closures
|
|
||||||
// .get_mut(&unique_id)
|
|
||||||
// .unwrap()
|
|
||||||
// .push(Rc::clone(callback));
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
let mut previous_node_was_text = false;
|
|
||||||
|
|
||||||
node.children.iter().for_each(|child| {
|
|
||||||
// log::info!("Patching child");
|
|
||||||
match child {
|
|
||||||
VNode::Text(text_node) => {
|
|
||||||
let current_node = element.as_ref() as &web_sys::Node;
|
|
||||||
|
|
||||||
// We ensure that the text siblings are patched by preventing the browser from merging
|
|
||||||
// neighboring text nodes. Originally inspired by some of React's work from 2016.
|
|
||||||
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
|
|
||||||
// -> https://github.com/facebook/react/pull/5753
|
|
||||||
//
|
|
||||||
// `ptns` = Percy text node separator
|
|
||||||
if previous_node_was_text {
|
|
||||||
let separator = document.create_comment("ptns");
|
|
||||||
current_node
|
|
||||||
.append_child(separator.as_ref() as &web_sys::Node)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
current_node
|
|
||||||
.append_child(&create_text_node(&text_node))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
previous_node_was_text = true;
|
|
||||||
}
|
|
||||||
VNode::Element(element_node) => {
|
|
||||||
previous_node_was_text = false;
|
|
||||||
|
|
||||||
let child = create_element_node(element_node);
|
|
||||||
// let child = element_node.create_element_node();
|
|
||||||
let child_elem: Element = child.node;
|
|
||||||
|
|
||||||
closures.extend(child.closures);
|
|
||||||
|
|
||||||
element.append_child(&child_elem).unwrap();
|
|
||||||
}
|
|
||||||
VNode::Suspended => {
|
|
||||||
todo!("Not yet supported")
|
|
||||||
}
|
|
||||||
VNode::Component(_) => {
|
|
||||||
todo!("Not yet supported")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: connect on mount to the event system somehow
|
|
||||||
// if let Some(on_create_elem) = node.events.0.get("on_create_elem") {
|
|
||||||
// let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
|
|
||||||
// on_create_elem
|
|
||||||
// .call1(&wasm_bindgen::JsValue::NULL, &element)
|
|
||||||
// .unwrap();
|
|
||||||
// }
|
|
||||||
|
|
||||||
CreatedNode {
|
|
||||||
node: element,
|
|
||||||
closures,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a `Text` element from a `VirtualNode`, typically right before adding it
|
|
||||||
/// into the DOM.
|
|
||||||
pub fn create_text_node(node: &VText) -> Text {
|
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
|
||||||
document.create_text_node(&node.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// /// For any listeners in the tree, attach the sender closure.
|
|
||||||
// /// When a event is triggered, we convert it into the synthetic event type and dump it back in the Virtual Dom's queu
|
|
||||||
// fn attach_listeners(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom) {}
|
|
||||||
// fn render_diffs() {}
|
|
|
@ -49,7 +49,7 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Props that are valid for the `'static` lifetime automatically get memoized by Diouxs. This means the component won't
|
//! Props that are valid for the `'pub static` lifetime automatically get memoized by Diouxs. This means the component won't
|
||||||
//! re-render if its Props didn't change. However, Props that borrow data from their parent cannot be safely memoized, and
|
//! re-render if its Props didn't change. However, Props that borrow data from their parent cannot be safely memoized, and
|
||||||
//! will always re-render if their parent changes. To borrow data from a parent, your component needs to add explicit lifetimes,
|
//! will always re-render if their parent changes. To borrow data from a parent, your component needs to add explicit lifetimes,
|
||||||
//! otherwise Rust will get confused about whether data is borrowed from either Props or Context. Since Dioxus manages
|
//! otherwise Rust will get confused about whether data is borrowed from either Props or Context. Since Dioxus manages
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
//! The lifetimes might look a little messy, but are crucially important for Dioxus's efficiency and overall ergonimics.
|
//! The lifetimes might look a little messy, but are crucially important for Dioxus's efficiency and overall ergonimics.
|
||||||
//! Components can also be crafted as static closures, enabling type inference without all the type signature noise. However,
|
//! Components can also be crafted as pub static closures, enabling type inference without all the type signature noise. However,
|
||||||
//! closure-style components cannot work with borrowed data due to limitations in Rust's lifetime system.
|
//! closure-style components cannot work with borrowed data due to limitations in Rust's lifetime system.
|
||||||
//!
|
//!
|
||||||
//! To use custom properties for components, you'll need to derive the `Props` trait for your properties. This trait
|
//! To use custom properties for components, you'll need to derive the `Props` trait for your properties. This trait
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
//! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component.
|
//! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component.
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! pub static Example: FC<()> = |cx| {
|
//! pub pub static Example: FC<()> = |cx| {
|
||||||
//! let (val, set_val) = use_state(cx, || 0);
|
//! let (val, set_val) = use_state(cx, || 0);
|
||||||
//! cx.render(rsx!(
|
//! cx.render(rsx!(
|
||||||
//! button { onclick: move |_| set_val(val + 1) }
|
//! button { onclick: move |_| set_val(val + 1) }
|
||||||
|
@ -156,7 +156,7 @@
|
||||||
//! diouxs::web::launch(Example);
|
//! diouxs::web::launch(Example);
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! pub static Example: FC<()> = |cx| {
|
//! pub pub static Example: FC<()> = |cx| {
|
||||||
//! cx.render(rsx! {
|
//! cx.render(rsx! {
|
||||||
//! div { "Hello World!" }
|
//! div { "Hello World!" }
|
||||||
//! })
|
//! })
|
||||||
|
|
Loading…
Reference in a new issue