From 1880bdb308c15233a78cf313190ec41254137b93 Mon Sep 17 00:00:00 2001 From: ealmloff Date: Wed, 20 Dec 2023 10:28:24 -0600 Subject: [PATCH 1/7] Remove link to Tide's contributing guidelines --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d1ec98019..65cd08263 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ So... Dioxus is great, but why won't it work for me? ## 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). -- Join the discord and ask questions! +- [Join](https://discord.gg/XgGxMSkvUM) the discord and ask questions! From 451d935aa2cadbf0c681b6296b89d7220feda4d2 Mon Sep 17 00:00:00 2001 From: tigerros Date: Sat, 30 Dec 2023 01:18:50 +0100 Subject: [PATCH 2/7] fix typo (#1759) --- packages/core/src/lazynodes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 3189086b4..ae1bd2109 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -18,7 +18,7 @@ use crate::{innerlude::VNode, ScopeState}; /// 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. /// /// From 3733fbf47d8da934432778c149073473297f6715 Mon Sep 17 00:00:00 2001 From: Georges KABBOUCHI Date: Sat, 30 Dec 2023 21:49:32 +0200 Subject: [PATCH 3/7] feat(liveview): add rocket adapter (#1761) --- packages/liveview/Cargo.toml | 13 +++- packages/liveview/README.md | 1 + packages/liveview/examples/rocket.rs | 76 +++++++++++++++++++ .../liveview/src/adapters/rocket_adapter.rs | 25 ++++++ packages/liveview/src/lib.rs | 5 ++ 5 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 packages/liveview/examples/rocket.rs create mode 100644 packages/liveview/src/adapters/rocket_adapter.rs diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index cbd7b75f5..0f35f4ae6 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -38,6 +38,10 @@ salvo = { version = "0.44.1", optional = true, features = ["ws"] } once_cell = "1.17.1" 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-files = { version = "0.6.2", optional = true } # actix-web = { version = "4.2.1", optional = true } @@ -49,13 +53,16 @@ tokio = { workspace = true, features = ["full"] } dioxus = { workspace = true } warp = "0.3.3" axum = { version = "0.6.1", features = ["ws"] } -salvo = { version = "0.44.1", features = ["affix", "ws"] } +# salvo = { version = "0.44.1", features = ["affix", "ws"] } +rocket = "0.5.0" +rocket_ws = "0.1.0" tower = "0.4.13" [features] default = ["hot-reload"] # actix = ["actix-files", "actix-web", "actix-ws"] hot-reload = ["dioxus-hot-reload"] +rocket = ["dep:rocket", "dep:rocket_ws"] [[example]] name = "axum" @@ -68,3 +75,7 @@ required-features = ["salvo"] [[example]] name = "warp" required-features = ["warp"] + +[[example]] +name = "rocket" +required-features = ["rocket"] diff --git a/packages/liveview/README.md b/packages/liveview/README.md index cbae3c53a..13af3c36f 100644 --- a/packages/liveview/README.md +++ b/packages/liveview/README.md @@ -28,6 +28,7 @@ The current backend frameworks supported include: - Axum - Warp - Salvo +- Rocket Dioxus-LiveView exports some primitives to wire up an app into an existing backend framework. diff --git a/packages/liveview/examples/rocket.rs b/packages/liveview/examples/rocket.rs new file mode 100644 index 000000000..8256db7f8 --- /dev/null +++ b/packages/liveview/examples/rocket.rs @@ -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 { + RawHtml(format!( + r#" + + + Dioxus LiveView with Rocket +
+ {glue} + + "#, + glue = glue + )) +} + +#[get("/")] +async fn index(config: &Config) -> RawHtml { + 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 { + index_page_with_glue(&dioxus_liveview::interpreter_glue("/ws")) +} + +#[get("/ws")] +fn ws(ws: WebSocket, pool: &State) -> 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"); +} diff --git a/packages/liveview/src/adapters/rocket_adapter.rs b/packages/liveview/src/adapters/rocket_adapter.rs new file mode 100644 index 000000000..8a9840bf2 --- /dev/null +++ b/packages/liveview/src/adapters/rocket_adapter.rs @@ -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) -> Result, LiveViewError> { + message + .map_err(|_| LiveViewError::SendingFailed)? + .into_text() + .map(|s| s.into_bytes()) + .map_err(|_| LiveViewError::SendingFailed) +} + +async fn transform_tx(message: Vec) -> Result { + Ok(Message::Text(String::from_utf8_lossy(&message).to_string())) +} diff --git a/packages/liveview/src/lib.rs b/packages/liveview/src/lib.rs index 6a2768fd5..40d6186dc 100644 --- a/packages/liveview/src/lib.rs +++ b/packages/liveview/src/lib.rs @@ -18,6 +18,11 @@ pub mod adapters { #[cfg(feature = "salvo")] pub use salvo_adapter::*; + + #[cfg(feature = "rocket")] + pub mod rocket_adapter; + #[cfg(feature = "rocket")] + pub use rocket_adapter::*; } pub use adapters::*; From 0166eafafb20eaf7acdbdd67bae44d037c3870b1 Mon Sep 17 00:00:00 2001 From: Hampus Lidin Date: Sat, 30 Dec 2023 20:53:12 +0100 Subject: [PATCH 4/7] Send mount event when hydrating (#1764) --- packages/web/src/dom.rs | 26 +++++++++++++++----------- packages/web/src/rehydrate.rs | 19 ++++++++++++++----- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index b8e9255f7..22701bf4e 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -256,17 +256,21 @@ impl WebsysDom { i.flush(); for id in to_mount { - let node = get_node(id.0 as u32); - if let Some(element) = node.dyn_ref::() { - let data: MountedData = element.into(); - let data = Rc::new(data); - let _ = self.event_channel.unbounded_send(UiEvent { - name: "mounted".to_string(), - bubbles: false, - element: id, - data, - }); - } + self.send_mount_event(id); + } + } + + pub(crate) fn send_mount_event(&self, id: ElementId) { + let node = get_node(id.0 as u32); + if let Some(element) = node.dyn_ref::() { + let data: MountedData = element.into(); + let data = Rc::new(data); + let _ = self.event_channel.unbounded_send(UiEvent { + name: "mounted".to_string(), + bubbles: false, + element: id, + data, + }); } } } diff --git a/packages/web/src/rehydrate.rs b/packages/web/src/rehydrate.rs index 4415ceb37..11c68f89c 100644 --- a/packages/web/src/rehydrate.rs +++ b/packages/web/src/rehydrate.rs @@ -125,6 +125,7 @@ impl WebsysDom { children, attrs, .. } => { let mut mounted_id = None; + let mut should_send_mount_event = true; for attr in *attrs { if let dioxus_core::TemplateAttribute::Dynamic { id } = attr { let attribute = &vnode.dynamic_attrs[*id]; @@ -134,16 +135,24 @@ impl WebsysDom { let name = attribute.name; if let AttributeValue::Listener(_) = value { let event_name = &name[2..]; - self.interpreter.new_event_listener( - event_name, - id.0 as u32, - event_bubbles(event_name) as u8, - ); + match event_name { + "mounted" => should_send_mount_event = true, + _ => { + self.interpreter.new_event_listener( + event_name, + id.0 as u32, + event_bubbles(event_name) as u8, + ); + } + } } } } if let Some(id) = mounted_id { set_node(hydrated, id, current_child.clone()?); + if should_send_mount_event { + self.send_mount_event(id); + } } if !children.is_empty() { let mut children_current_child = current_child From a0b95a82a0fa16d9101897554444878da7096f74 Mon Sep 17 00:00:00 2001 From: werner291 Date: Mon, 1 Jan 2024 17:22:52 +0100 Subject: [PATCH 5/7] Implemented From> for ReadOnlySignal (#1772) --- packages/signals/src/signal.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 1dc4bd36e..946a6c380 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -495,3 +495,9 @@ impl Deref for ReadOnlySignal { reference_to_closure as &Self::Target } } + +impl From> for ReadOnlySignal { + fn from(signal: Signal) -> Self { + Self::new(signal) + } +} From 8230566c4d761f6235ce4eba5a990bcb35af194f Mon Sep 17 00:00:00 2001 From: Hampus Lidin Date: Tue, 2 Jan 2024 17:01:53 +0100 Subject: [PATCH 6/7] Add doc comment on generated inline properties struct (#1775) --- .../core-macro/src/component_body_deserializers/component.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/core-macro/src/component_body_deserializers/component.rs b/packages/core-macro/src/component_body_deserializers/component.rs index 36cda850c..994e51709 100644 --- a/packages/core-macro/src/component_body_deserializers/component.rs +++ b/packages/core-macro/src/component_body_deserializers/component.rs @@ -58,8 +58,11 @@ impl ToTokens for ComponentDeserializerOutput { fn to_tokens(&self, tokens: &mut TokenStream2) { let comp_fn = &self.comp_fn; 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! { + #[doc = #doc] #props_struct #[allow(non_snake_case)] #comp_fn From fd7c9e0359823f767f8b90a0b156be4babdb7a22 Mon Sep 17 00:00:00 2001 From: abhi Date: Tue, 2 Jan 2024 16:02:58 +0000 Subject: [PATCH 7/7] fix empty render causing panic (#1769) (#1774) * fix empty render causing panic (#1769) * use an default root column instead of an empty root column * fix formatting * restore create.rs in core --------- Co-authored-by: Evan Almloff --- packages/core/src/create.rs | 3 +- packages/rsx/src/lib.rs | 17 ++++--- packages/ssr/src/renderer.rs | 88 ++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 7 deletions(-) diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index a61a8cace..733a796ce 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -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 /// @@ -405,6 +405,7 @@ impl<'b> VirtualDom { #[allow(unused_mut)] pub(crate) fn register_template(&mut self, mut template: Template<'static>) { let (path, byte_index) = template.name.rsplit_once(':').unwrap(); + let byte_index = byte_index.parse::().unwrap(); // First, check if we've already seen this template if self diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 5db6baee9..458075b63 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -204,12 +204,17 @@ impl<'a> ToTokens for TemplateRenderer<'a> { None => quote! { None }, }; - let spndbg = format!("{:?}", self.roots[0].span()); - let root_col = spndbg - .rsplit_once("..") - .and_then(|(_, after)| after.split_once(')').map(|(before, _)| before)) - .unwrap_or_default(); - + let root_col = match self.roots.first() { + Some(first_root) => { + let first_root_span = format!("{:?}", first_root.span()); + first_root_span + .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)| { context.current_path.push(idx as u8); let out = context.render_static_node(root); diff --git a/packages/ssr/src/renderer.rs b/packages/ssr/src/renderer.rs index 98afef8b7..c0f9b6c75 100644 --- a/packages/ssr/src/renderer.rs +++ b/packages/ssr/src/renderer.rs @@ -238,6 +238,94 @@ fn to_string_works() { assert_eq!(out, "
Hello world 1 -->123<-- Hello world 2
nest 1
nest 2
</diiiiiiiiv>
finalize 0
finalize 1
finalize 2
finalize 3
finalize 4
"); } +#[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("
".into()), + InnerHtmlMarker, + PreRendered("
".into(),), + ] + ); + } + } + + use Segment::*; + + assert_eq!(out, "
"); +} + +#[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] = &[ "allowfullscreen", "allowpaymentrequest",