Merge branch 'master' into bump-salvo-utils
This commit is contained in:
14 files changed
+259
-25
No files matched your search
@@ -161,7 +161,7 @@ So... Dioxus is great, but why won't it work for me?
|
|||||||
## Contributing
|
## Contributing
|
||||||
- Check out the website [section on contributing](https://dioxuslabs.com/learn/0.4/contributing).
|
- Check out the website [section on contributing](https://dioxuslabs.com/learn/0.4/contributing).
|
||||||
- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).
|
- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).
|
||||||
- Join the discord and ask questions!
|
- [Join](https://discord.gg/XgGxMSkvUM) the discord and ask questions!
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/dioxuslabs/dioxus/graphs/contributors">
|
<a href="https://github.com/dioxuslabs/dioxus/graphs/contributors">
|
||||||
|
|||||||
@@ -58,8 +58,11 @@ impl ToTokens for ComponentDeserializerOutput {
|
|||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let comp_fn = &self.comp_fn;
|
let comp_fn = &self.comp_fn;
|
||||||
let props_struct = &self.props_struct;
|
let props_struct = &self.props_struct;
|
||||||
|
let fn_ident = &comp_fn.sig.ident;
|
||||||
|
|
||||||
|
let doc = format!("Properties for the [`{fn_ident}`] component.");
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
|
#[doc = #doc]
|
||||||
#props_struct
|
#props_struct
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#comp_fn
|
#comp_fn
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ impl<'b> VirtualDom {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We write all the descndent data for this element
|
/// We write all the descendent data for this element
|
||||||
///
|
///
|
||||||
/// Elements can contain other nodes - and those nodes can be dynamic or static
|
/// Elements can contain other nodes - and those nodes can be dynamic or static
|
||||||
///
|
///
|
||||||
@@ -405,6 +405,7 @@ impl<'b> VirtualDom {
|
|||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
pub(crate) fn register_template(&mut self, mut template: Template<'static>) {
|
pub(crate) fn register_template(&mut self, mut template: Template<'static>) {
|
||||||
let (path, byte_index) = template.name.rsplit_once(':').unwrap();
|
let (path, byte_index) = template.name.rsplit_once(':').unwrap();
|
||||||
|
|
||||||
let byte_index = byte_index.parse::<usize>().unwrap();
|
let byte_index = byte_index.parse::<usize>().unwrap();
|
||||||
// First, check if we've already seen this template
|
// First, check if we've already seen this template
|
||||||
if self
|
if self
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use crate::{innerlude::VNode, ScopeState};
|
|||||||
|
|
||||||
/// A concrete type provider for closures that build [`VNode`] structures.
|
/// A concrete type provider for closures that build [`VNode`] structures.
|
||||||
///
|
///
|
||||||
/// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over
|
/// This struct wraps lazy structs that build [`VNode`] trees. Normally, we cannot perform a blanket implementation over
|
||||||
/// closures, but if we wrap the closure in a concrete type, we can use it for different branches in matching.
|
/// closures, but if we wrap the closure in a concrete type, we can use it for different branches in matching.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ salvo = { version = "0.63.0", optional = true, features = ["websocket"] }
|
|||||||
once_cell = "1.17.1"
|
once_cell = "1.17.1"
|
||||||
async-trait = "0.1.71"
|
async-trait = "0.1.71"
|
||||||
|
|
||||||
|
# rocket
|
||||||
|
rocket = { version = "0.5.0", optional = true }
|
||||||
|
rocket_ws = { version = "0.1.0", optional = true }
|
||||||
|
|
||||||
# actix is ... complicated?
|
# actix is ... complicated?
|
||||||
# actix-files = { version = "0.6.2", optional = true }
|
# actix-files = { version = "0.6.2", optional = true }
|
||||||
# actix-web = { version = "4.2.1", optional = true }
|
# actix-web = { version = "4.2.1", optional = true }
|
||||||
@@ -50,12 +54,15 @@ dioxus = { workspace = true }
|
|||||||
warp = "0.3.3"
|
warp = "0.3.3"
|
||||||
axum = { version = "0.6.1", features = ["ws"] }
|
axum = { version = "0.6.1", features = ["ws"] }
|
||||||
salvo = { version = "0.63.0", features = ["affix", "websocket"] }
|
salvo = { version = "0.63.0", features = ["affix", "websocket"] }
|
||||||
|
rocket = "0.5.0"
|
||||||
|
rocket_ws = "0.1.0"
|
||||||
tower = "0.4.13"
|
tower = "0.4.13"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["hot-reload"]
|
default = ["hot-reload"]
|
||||||
# actix = ["actix-files", "actix-web", "actix-ws"]
|
# actix = ["actix-files", "actix-web", "actix-ws"]
|
||||||
hot-reload = ["dioxus-hot-reload"]
|
hot-reload = ["dioxus-hot-reload"]
|
||||||
|
rocket = ["dep:rocket", "dep:rocket_ws"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
@@ -68,3 +75,7 @@ required-features = ["salvo"]
|
|||||||
[[example]]
|
[[example]]
|
||||||
name = "warp"
|
name = "warp"
|
||||||
required-features = ["warp"]
|
required-features = ["warp"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "rocket"
|
||||||
|
required-features = ["rocket"]
|
||||||
@@ -28,6 +28,7 @@ The current backend frameworks supported include:
|
|||||||
- Axum
|
- Axum
|
||||||
- Warp
|
- Warp
|
||||||
- Salvo
|
- Salvo
|
||||||
|
- Rocket
|
||||||
|
|
||||||
Dioxus-LiveView exports some primitives to wire up an app into an existing backend framework.
|
Dioxus-LiveView exports some primitives to wire up an app into an existing backend framework.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_liveview::LiveViewPool;
|
||||||
|
use rocket::response::content::RawHtml;
|
||||||
|
use rocket::{Config, Rocket, State};
|
||||||
|
use rocket_ws::{Channel, WebSocket};
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let mut num = use_state(cx, || 0);
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
"hello Rocket! {num}"
|
||||||
|
button { onclick: move |_| num += 1, "Increment" }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_page_with_glue(glue: &str) -> RawHtml<String> {
|
||||||
|
RawHtml(format!(
|
||||||
|
r#"
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head> <title>Dioxus LiveView with Rocket</title> </head>
|
||||||
|
<body> <div id="main"></div> </body>
|
||||||
|
{glue}
|
||||||
|
</html>
|
||||||
|
"#,
|
||||||
|
glue = glue
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn index(config: &Config) -> RawHtml<String> {
|
||||||
|
index_page_with_glue(&dioxus_liveview::interpreter_glue(&format!(
|
||||||
|
"ws://{addr}:{port}/ws",
|
||||||
|
addr = config.address,
|
||||||
|
port = config.port,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/as-path")]
|
||||||
|
async fn as_path() -> RawHtml<String> {
|
||||||
|
index_page_with_glue(&dioxus_liveview::interpreter_glue("/ws"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/ws")]
|
||||||
|
fn ws(ws: WebSocket, pool: &State<LiveViewPool>) -> Channel<'static> {
|
||||||
|
let pool = pool.inner().to_owned();
|
||||||
|
|
||||||
|
ws.channel(move |stream| {
|
||||||
|
Box::pin(async move {
|
||||||
|
let _ = pool
|
||||||
|
.launch(dioxus_liveview::rocket_socket(stream), app)
|
||||||
|
.await;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let view = dioxus_liveview::LiveViewPool::new();
|
||||||
|
|
||||||
|
Rocket::build()
|
||||||
|
.manage(view)
|
||||||
|
.mount("/", routes![index, as_path, ws])
|
||||||
|
.ignite()
|
||||||
|
.await
|
||||||
|
.expect("Failed to ignite rocket")
|
||||||
|
.launch()
|
||||||
|
.await
|
||||||
|
.expect("Failed to launch rocket");
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
use crate::{LiveViewError, LiveViewSocket};
|
||||||
|
use rocket::futures::{SinkExt, StreamExt};
|
||||||
|
use rocket_ws::{result::Error, stream::DuplexStream, Message};
|
||||||
|
|
||||||
|
/// Convert a rocket websocket into a LiveViewSocket
|
||||||
|
///
|
||||||
|
/// This is required to launch a LiveView app using the rocket web framework
|
||||||
|
pub fn rocket_socket(stream: DuplexStream) -> impl LiveViewSocket {
|
||||||
|
stream
|
||||||
|
.map(transform_rx)
|
||||||
|
.with(transform_tx)
|
||||||
|
.sink_map_err(|_| LiveViewError::SendingFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_rx(message: Result<Message, Error>) -> Result<Vec<u8>, LiveViewError> {
|
||||||
|
message
|
||||||
|
.map_err(|_| LiveViewError::SendingFailed)?
|
||||||
|
.into_text()
|
||||||
|
.map(|s| s.into_bytes())
|
||||||
|
.map_err(|_| LiveViewError::SendingFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn transform_tx(message: Vec<u8>) -> Result<Message, Error> {
|
||||||
|
Ok(Message::Text(String::from_utf8_lossy(&message).to_string()))
|
||||||
|
}
|
||||||
@@ -18,6 +18,11 @@ pub mod adapters {
|
|||||||
|
|
||||||
#[cfg(feature = "salvo")]
|
#[cfg(feature = "salvo")]
|
||||||
pub use salvo_adapter::*;
|
pub use salvo_adapter::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "rocket")]
|
||||||
|
pub mod rocket_adapter;
|
||||||
|
#[cfg(feature = "rocket")]
|
||||||
|
pub use rocket_adapter::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use adapters::*;
|
pub use adapters::*;
|
||||||
|
|||||||
+11
-6
@@ -204,12 +204,17 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
|
|||||||
None => quote! { None },
|
None => quote! { None },
|
||||||
};
|
};
|
||||||
|
|
||||||
let spndbg = format!("{:?}", self.roots[0].span());
|
let root_col = match self.roots.first() {
|
||||||
let root_col = spndbg
|
Some(first_root) => {
|
||||||
.rsplit_once("..")
|
let first_root_span = format!("{:?}", first_root.span());
|
||||||
.and_then(|(_, after)| after.split_once(')').map(|(before, _)| before))
|
first_root_span
|
||||||
.unwrap_or_default();
|
.rsplit_once("..")
|
||||||
|
.and_then(|(_, after)| after.split_once(')').map(|(before, _)| before))
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
_ => "0".to_string(),
|
||||||
|
};
|
||||||
let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
|
let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
|
||||||
context.current_path.push(idx as u8);
|
context.current_path.push(idx as u8);
|
||||||
let out = context.render_static_node(root);
|
let out = context.render_static_node(root);
|
||||||
|
|||||||
@@ -495,3 +495,9 @@ impl<T> Deref for ReadOnlySignal<T> {
|
|||||||
reference_to_closure as &Self::Target
|
reference_to_closure as &Self::Target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> From<Signal<T>> for ReadOnlySignal<T> {
|
||||||
|
fn from(signal: Signal<T>) -> Self {
|
||||||
|
Self::new(signal)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -238,6 +238,94 @@ fn to_string_works() {
|
|||||||
assert_eq!(out, "<div class=\"asdasdasd\" class=\"asdasdasd\" id=\"id-123\">Hello world 1 -->123<-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div></diiiiiiiiv><div>finalize 0</div><div>finalize 1</div><div>finalize 2</div><div>finalize 3</div><div>finalize 4</div></div>");
|
assert_eq!(out, "<div class=\"asdasdasd\" class=\"asdasdasd\" id=\"id-123\">Hello world 1 -->123<-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div></diiiiiiiiv><div>finalize 0</div><div>finalize 1</div><div>finalize 2</div><div>finalize 3</div><div>finalize 4</div></div>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_for_loop_works() {
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
div { class: "asdasdasd",
|
||||||
|
for _ in (0..5) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
_ = dom.rebuild();
|
||||||
|
|
||||||
|
let mut renderer = Renderer::new();
|
||||||
|
let out = renderer.render(&dom);
|
||||||
|
|
||||||
|
for item in renderer.template_cache.iter() {
|
||||||
|
if item.1.segments.len() > 5 {
|
||||||
|
assert_eq!(
|
||||||
|
item.1.segments,
|
||||||
|
vec![
|
||||||
|
PreRendered("<div class=\"asdasdasd\"".into(),),
|
||||||
|
Attr(0,),
|
||||||
|
StyleMarker {
|
||||||
|
inside_style_tag: false,
|
||||||
|
},
|
||||||
|
PreRendered(">".into()),
|
||||||
|
InnerHtmlMarker,
|
||||||
|
PreRendered("</div>".into(),),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use Segment::*;
|
||||||
|
|
||||||
|
assert_eq!(out, "<div class=\"asdasdasd\"></div>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_render_works() {
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
render! {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
_ = dom.rebuild();
|
||||||
|
|
||||||
|
let mut renderer = Renderer::new();
|
||||||
|
let out = renderer.render(&dom);
|
||||||
|
|
||||||
|
for item in renderer.template_cache.iter() {
|
||||||
|
if item.1.segments.len() > 5 {
|
||||||
|
assert_eq!(item.1.segments, vec![]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(out, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_rsx_works() {
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
fn app(_: Scope) -> Element {
|
||||||
|
rsx! {};
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
_ = dom.rebuild();
|
||||||
|
|
||||||
|
let mut renderer = Renderer::new();
|
||||||
|
let out = renderer.render(&dom);
|
||||||
|
|
||||||
|
for item in renderer.template_cache.iter() {
|
||||||
|
if item.1.segments.len() > 5 {
|
||||||
|
assert_eq!(item.1.segments, vec![]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(out, "");
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) const BOOL_ATTRS: &[&str] = &[
|
pub(crate) const BOOL_ATTRS: &[&str] = &[
|
||||||
"allowfullscreen",
|
"allowfullscreen",
|
||||||
"allowpaymentrequest",
|
"allowpaymentrequest",
|
||||||
|
|||||||
+15
-11
@@ -256,17 +256,21 @@ impl WebsysDom {
|
|||||||
i.flush();
|
i.flush();
|
||||||
|
|
||||||
for id in to_mount {
|
for id in to_mount {
|
||||||
let node = get_node(id.0 as u32);
|
self.send_mount_event(id);
|
||||||
if let Some(element) = node.dyn_ref::<Element>() {
|
}
|
||||||
let data: MountedData = element.into();
|
}
|
||||||
let data = Rc::new(data);
|
|
||||||
let _ = self.event_channel.unbounded_send(UiEvent {
|
pub(crate) fn send_mount_event(&self, id: ElementId) {
|
||||||
name: "mounted".to_string(),
|
let node = get_node(id.0 as u32);
|
||||||
bubbles: false,
|
if let Some(element) = node.dyn_ref::<Element>() {
|
||||||
element: id,
|
let data: MountedData = element.into();
|
||||||
data,
|
let data = Rc::new(data);
|
||||||
});
|
let _ = self.event_channel.unbounded_send(UiEvent {
|
||||||
}
|
name: "mounted".to_string(),
|
||||||
|
bubbles: false,
|
||||||
|
element: id,
|
||||||
|
data,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ impl WebsysDom {
|
|||||||
children, attrs, ..
|
children, attrs, ..
|
||||||
} => {
|
} => {
|
||||||
let mut mounted_id = None;
|
let mut mounted_id = None;
|
||||||
|
let mut should_send_mount_event = true;
|
||||||
for attr in *attrs {
|
for attr in *attrs {
|
||||||
if let dioxus_core::TemplateAttribute::Dynamic { id } = attr {
|
if let dioxus_core::TemplateAttribute::Dynamic { id } = attr {
|
||||||
let attribute = &vnode.dynamic_attrs[*id];
|
let attribute = &vnode.dynamic_attrs[*id];
|
||||||
@@ -134,16 +135,24 @@ impl WebsysDom {
|
|||||||
let name = attribute.name;
|
let name = attribute.name;
|
||||||
if let AttributeValue::Listener(_) = value {
|
if let AttributeValue::Listener(_) = value {
|
||||||
let event_name = &name[2..];
|
let event_name = &name[2..];
|
||||||
self.interpreter.new_event_listener(
|
match event_name {
|
||||||
event_name,
|
"mounted" => should_send_mount_event = true,
|
||||||
id.0 as u32,
|
_ => {
|
||||||
event_bubbles(event_name) as u8,
|
self.interpreter.new_event_listener(
|
||||||
);
|
event_name,
|
||||||
|
id.0 as u32,
|
||||||
|
event_bubbles(event_name) as u8,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(id) = mounted_id {
|
if let Some(id) = mounted_id {
|
||||||
set_node(hydrated, id, current_child.clone()?);
|
set_node(hydrated, id, current_child.clone()?);
|
||||||
|
if should_send_mount_event {
|
||||||
|
self.send_mount_event(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !children.is_empty() {
|
if !children.is_empty() {
|
||||||
let mut children_current_child = current_child
|
let mut children_current_child = current_child
|
||||||
|
|||||||
Reference in new issue
Block a user