From a01640cafdd4fc07806ccd841866802ffdf69b88 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Wed, 10 Apr 2024 08:56:12 -0400 Subject: [PATCH] updates toward `todo_app_sqlite` --- examples/todo_app_sqlite_axum/Cargo.toml | 2 - .../src/error_template.rs | 9 +- examples/todo_app_sqlite_axum/src/fallback.rs | 3 +- examples/todo_app_sqlite_axum/src/lib.rs | 3 +- examples/todo_app_sqlite_axum/src/main.rs | 31 ++++- examples/todo_app_sqlite_axum/src/todo.rs | 122 ++++++++---------- integrations/axum/src/lib.rs | 2 +- leptos/Cargo.toml | 4 +- leptos/src/suspense_component.rs | 11 +- meta/src/body.rs | 7 +- meta/src/html.rs | 7 +- meta/src/lib.rs | 22 +++- meta/src/title.rs | 7 +- reactive_graph/src/signal/rw.rs | 7 + routing/src/matching/choose_view.rs | 4 +- routing/src/matching/mod.rs | 2 +- routing/src/matching/nested/mod.rs | 4 +- routing/src/router.rs | 34 +++-- tachys/src/html/attribute/value.rs | 96 +++++++++++++- 19 files changed, 270 insertions(+), 107 deletions(-) diff --git a/examples/todo_app_sqlite_axum/Cargo.toml b/examples/todo_app_sqlite_axum/Cargo.toml index 0b4db9d77..cd7d1d54f 100644 --- a/examples/todo_app_sqlite_axum/Cargo.toml +++ b/examples/todo_app_sqlite_axum/Cargo.toml @@ -14,8 +14,6 @@ http = "1.0" leptos = { path = "../../leptos" } server_fn = { path = "../../server_fn", features = ["serde-lite"] } leptos_axum = { path = "../../integrations/axum", optional = true } -leptos_meta = { path = "../../meta" } -leptos_router = { path = "../../router" } log = "0.4" simple_logger = "4.0" serde = { version = "1", features = ["derive"] } diff --git a/examples/todo_app_sqlite_axum/src/error_template.rs b/examples/todo_app_sqlite_axum/src/error_template.rs index d68b2b58f..98c590180 100644 --- a/examples/todo_app_sqlite_axum/src/error_template.rs +++ b/examples/todo_app_sqlite_axum/src/error_template.rs @@ -1,5 +1,8 @@ use crate::errors::TodoAppError; -use leptos::*; +use leptos::context::use_context; +use leptos::signals::RwSignal; +use leptos::{component, server, view, For, IntoView}; +use leptos::{prelude::*, Errors}; #[cfg(feature = "ssr")] use leptos_axum::ResponseOptions; @@ -8,10 +11,10 @@ use leptos_axum::ResponseOptions; #[component] pub fn ErrorTemplate( #[prop(optional)] outside_errors: Option, - #[prop(optional)] errors: Option>, + #[prop(optional, into)] errors: Option>, ) -> impl IntoView { let errors = match outside_errors { - Some(e) => create_rw_signal(e), + Some(e) => RwSignal::new(e), None => match errors { Some(e) => e, None => panic!("No Errors found and we expected errors!"), diff --git a/examples/todo_app_sqlite_axum/src/fallback.rs b/examples/todo_app_sqlite_axum/src/fallback.rs index 66b2a5ffe..bef4bdec3 100644 --- a/examples/todo_app_sqlite_axum/src/fallback.rs +++ b/examples/todo_app_sqlite_axum/src/fallback.rs @@ -5,7 +5,8 @@ use axum::{ http::{Request, Response, StatusCode, Uri}, response::{IntoResponse, Response as AxumResponse}, }; -use leptos::{view, Errors, LeptosOptions}; +use leptos::config::LeptosOptions; +use leptos::{view, Errors}; use tower::ServiceExt; use tower_http::services::ServeDir; diff --git a/examples/todo_app_sqlite_axum/src/lib.rs b/examples/todo_app_sqlite_axum/src/lib.rs index 69c52ba4e..fb719ae3e 100644 --- a/examples/todo_app_sqlite_axum/src/lib.rs +++ b/examples/todo_app_sqlite_axum/src/lib.rs @@ -4,6 +4,7 @@ pub mod errors; pub mod fallback; pub mod todo; +#[cfg(feature = "hydrate")] #[wasm_bindgen::prelude::wasm_bindgen] pub fn hydrate() { use crate::todo::TodoApp; @@ -11,5 +12,5 @@ pub fn hydrate() { _ = console_log::init_with_level(log::Level::Error); console_error_panic_hook::set_once(); - leptos::mount_to_body(TodoApp); + leptos::hydrate_body(TodoApp); } diff --git a/examples/todo_app_sqlite_axum/src/main.rs b/examples/todo_app_sqlite_axum/src/main.rs index 2b4a1cd8a..48b0faf46 100644 --- a/examples/todo_app_sqlite_axum/src/main.rs +++ b/examples/todo_app_sqlite_axum/src/main.rs @@ -7,7 +7,11 @@ use axum::{ routing::get, Router, }; -use leptos::*; +use leptos::{ + config::{get_configuration, LeptosOptions}, + view, +}; +use leptos::{context::provide_context, HydrationScripts}; use leptos_axum::{generate_route_list, LeptosRoutes}; use todo_app_sqlite_axum::*; @@ -48,14 +52,35 @@ async fn main() { // build our application with a route let app = Router::new() .route("/special/:id", get(custom_handler)) - .leptos_routes(&leptos_options, routes, || view! { }) + .leptos_routes(&leptos_options, routes, { + let leptos_options = leptos_options.clone(); + move || { + use leptos::prelude::*; + + view! { + + + + + + // + + + + + + + + + } + }}) .fallback(file_and_error_handler) .with_state(leptos_options); // run our app with hyper // `axum::Server` is a re-export of `hyper::Server` let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); - logging::log!("listening on http://{}", &addr); + println!("listening on http://{}", &addr); axum::serve(listener, app.into_make_service()) .await .unwrap(); diff --git a/examples/todo_app_sqlite_axum/src/todo.rs b/examples/todo_app_sqlite_axum/src/todo.rs index 8fc910ce5..d4319ab02 100644 --- a/examples/todo_app_sqlite_axum/src/todo.rs +++ b/examples/todo_app_sqlite_axum/src/todo.rs @@ -1,9 +1,14 @@ use crate::error_template::ErrorTemplate; -use leptos::*; -use leptos_meta::*; -use leptos_router::*; +use leptos::context::use_context; +use leptos::server::{Resource, ServerAction}; +use leptos::tachys::either::Either; +use leptos::{ + component, server, suspend, view, ActionForm, ErrorBoundary, IntoView, +}; +use leptos::{prelude::*, Transition}; use serde::{Deserialize, Serialize}; use server_fn::codec::SerdeLite; +use server_fn::ServerFnError; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] @@ -16,7 +21,7 @@ pub struct Todo { #[cfg(feature = "ssr")] pub mod ssr { // use http::{header::SET_COOKIE, HeaderMap, HeaderValue, StatusCode}; - use leptos::ServerFnError; + use leptos::server_fn::ServerFnError; use sqlx::{Connection, SqliteConnection}; pub async fn db() -> Result { @@ -87,82 +92,65 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { #[component] pub fn TodoApp() -> impl IntoView { - //let id = use_context::(); - provide_meta_context(); view! { - - - -
-

"My Tasks"

-
-
- - - -
-
+
+

"My Tasks"

+
+
+ +
} } #[component] pub fn Todos() -> impl IntoView { - let add_todo = create_server_multi_action::(); - let delete_todo = create_server_action::(); - let submissions = add_todo.submissions(); + //let add_todo = create_server_multi_action::(); + let delete_todo = ServerAction::::new(); + //let submissions = add_todo.submissions(); // list of todos is loaded from the server in reaction to changes - let todos = create_resource( - move || (add_todo.version().get(), delete_todo.version().get()), + let todos = Resource::new_serde( + move || (delete_todo.version().get()), //(add_todo.version().get(), delete_todo.version().get()), move |_| get_todos(), ); view! {
- + /* - + */ "Loading..."

}> - }> - {move || { - let existing_todos = { - move || { - todos.get() - .map(move |todos| match todos { - Err(e) => { - view! {
"Server Error: " {e.to_string()}
}.into_view() - } - Ok(todos) => { - if todos.is_empty() { - view! {

"No tasks were found."

}.into_view() - } else { - todos - .into_iter() - .map(move |todo| { - view! { - -
  • - {todo.title} - - - - -
  • - } - }) - .collect_view() + }> +
      + {suspend!( + todos.await.map(|todos| { + if todos.is_empty() { + Either::Left(view! {

      "No tasks were found."

      }) + } else { + Either::Right(todos + .into_iter() + .map(move |todo| { + view! { +
    • + {todo.title} + + + + +
    • } - } - }) - .unwrap_or_default() - } - }; + }) + .collect::>() + ) + } + }) + )} - let pending_todos = move || { + /*let pending_todos = move || { submissions .get() .into_iter() @@ -173,18 +161,12 @@ pub fn Todos() -> impl IntoView {
    • {move || submission.input.get().map(|data| data.title) }
    • } }) - .collect_view() - }; + .collect::>() + };*/ - view! { - -
        - {existing_todos} - {pending_todos} -
      - } - } - } + // {existing_todos} + //{pending_todos} +
    diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index 8c71c1939..b94d90ef4 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -1435,7 +1435,7 @@ where additional_context(); RouteList::generate(&app_fn) }) - .expect("could not generate routes"); + .unwrap_or_default(); // Axum's Router defines Root routes as "/" not "" let mut routes = routes diff --git a/leptos/Cargo.toml b/leptos/Cargo.toml index 2c1b8be64..5bda02a3c 100644 --- a/leptos/Cargo.toml +++ b/leptos/Cargo.toml @@ -27,7 +27,7 @@ paste = "1" rand = { version = "0.8", optional = true } reactive_graph = { workspace = true, features = ["serde"] } rustc-hash = "1" -tachys = { workspace = true, features = ["reactive_graph"] } +tachys = { workspace = true, features = ["reactive_graph", "oco"] } thiserror = "1" tracing = "0.1" typed-builder = "0.18" @@ -86,7 +86,7 @@ rkyv = ["leptos_reactive/rkyv", "server_fn/rkyv"] tracing = [ "reactive_graph/tracing", ] #, "leptos_macro/tracing", "leptos_dom/tracing"] -nonce = ["leptos_dom/nonce"] +nonce = ["base64", "leptos_dom/nonce", "rand"] spin = ["leptos_reactive/spin", "leptos-spin-macro"] experimental-islands = [ "leptos_dom/experimental-islands", diff --git a/leptos/src/suspense_component.rs b/leptos/src/suspense_component.rs index ff3b92de3..f1756d3c9 100644 --- a/leptos/src/suspense_component.rs +++ b/leptos/src/suspense_component.rs @@ -115,7 +115,16 @@ where const MIN_LENGTH: usize = Chil::MIN_LENGTH; fn to_html_with_buf(self, buf: &mut String, position: &mut Position) { - todo!() + self.fallback.to_html_with_buf(buf, position); + } + + fn to_html_async_with_buf( + self, + buf: &mut StreamBuilder, + position: &mut Position, + ) where + Self: Sized, + { } fn hydrate( diff --git a/meta/src/body.rs b/meta/src/body.rs index 692c29bb8..d1b5d485f 100644 --- a/meta/src/body.rs +++ b/meta/src/body.rs @@ -2,11 +2,11 @@ use crate::ServerMetaContext; use indexmap::IndexMap; use leptos::{ component, + error::Result, oco::Oco, reactive_graph::{effect::RenderEffect, owner::use_context}, tachys::{ dom::document, - error::Result, html::{ attribute::{ any_attribute::{ @@ -96,6 +96,7 @@ struct BodyViewState { impl Render for BodyView { type State = BodyViewState; type FallibleState = BodyViewState; + type AsyncOutput = Self; fn build(self) -> Self::State { let el = document().body().expect("there to be a element"); @@ -121,6 +122,10 @@ impl Render for BodyView { self.rebuild(state); Ok(()) } + + async fn resolve(self) -> Self { + self + } } impl RenderHtml for BodyView { diff --git a/meta/src/html.rs b/meta/src/html.rs index 224da82af..e562bb5d3 100644 --- a/meta/src/html.rs +++ b/meta/src/html.rs @@ -2,11 +2,11 @@ use crate::ServerMetaContext; use indexmap::IndexMap; use leptos::{ component, + error::Result, oco::Oco, reactive_graph::{effect::RenderEffect, owner::use_context}, tachys::{ dom::document, - error::Result, html::{ attribute::{ self, @@ -107,6 +107,7 @@ struct HtmlViewState { impl Render for HtmlView { type State = HtmlViewState; type FallibleState = HtmlViewState; + type AsyncOutput = Self; fn build(self) -> Self::State { let el = document() @@ -134,6 +135,10 @@ impl Render for HtmlView { self.rebuild(state); Ok(()) } + + async fn resolve(self) -> Self { + self + } } impl RenderHtml for HtmlView { diff --git a/meta/src/lib.rs b/meta/src/lib.rs index 4a1ea67f8..a79327fdb 100644 --- a/meta/src/lib.rs +++ b/meta/src/lib.rs @@ -165,8 +165,8 @@ impl ServerMetaContext { /// included. pub async fn inject_meta_context( self, - mut stream: impl Stream + Send + Sync + Unpin, - ) -> impl Stream + Send + Sync { + mut stream: impl Stream + Send + Unpin, + ) -> impl Stream + Send { let mut first_chunk = stream.next().await.unwrap_or_default(); let meta_buf = @@ -324,6 +324,7 @@ where { type State = RegisteredMetaTagState; type FallibleState = RegisteredMetaTagState; + type AsyncOutput = Self; fn build(self) -> Self::State { let state = self.el.unwrap().build(); @@ -334,17 +335,21 @@ where self.el.unwrap().rebuild(&mut state.state); } - fn try_build(self) -> leptos::tachys::error::Result { + fn try_build(self) -> leptos::error::Result { Ok(self.build()) } fn try_rebuild( self, state: &mut Self::FallibleState, - ) -> leptos::tachys::error::Result<()> { + ) -> leptos::error::Result<()> { self.rebuild(state); Ok(()) } + + async fn resolve(self) -> Self { + self + } } impl RenderHtml for RegisteredMetaTag @@ -435,21 +440,26 @@ struct MetaTagsView { impl Render for MetaTagsView { type State = (); type FallibleState = (); + type AsyncOutput = Self; fn build(self) -> Self::State {} fn rebuild(self, state: &mut Self::State) {} - fn try_build(self) -> leptos::tachys::error::Result { + fn try_build(self) -> leptos::error::Result { Ok(()) } fn try_rebuild( self, state: &mut Self::FallibleState, - ) -> leptos::tachys::error::Result<()> { + ) -> leptos::error::Result<()> { Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + self + } } impl RenderHtml for MetaTagsView { diff --git a/meta/src/title.rs b/meta/src/title.rs index 24ee5047e..1a65350da 100644 --- a/meta/src/title.rs +++ b/meta/src/title.rs @@ -1,6 +1,7 @@ use crate::{use_head, MetaContext, ServerMetaContext}; use leptos::{ component, + error::Result, oco::Oco, reactive_graph::{ effect::RenderEffect, @@ -8,7 +9,6 @@ use leptos::{ }, tachys::{ dom::document, - error::Result, hydration::Cursor, renderer::{dom::Dom, Renderer}, view::{Mountable, Position, PositionState, Render, RenderHtml}, @@ -193,6 +193,7 @@ struct TitleViewState { impl Render for TitleView { type State = TitleViewState; type FallibleState = TitleViewState; + type AsyncOutput = Self; fn build(self) -> Self::State { let el = self.el(); @@ -229,6 +230,10 @@ impl Render for TitleView { self.rebuild(state); Ok(()) } + + async fn resolve(self) -> Self { + self + } } impl RenderHtml for TitleView { diff --git a/reactive_graph/src/signal/rw.rs b/reactive_graph/src/signal/rw.rs index 4b7aeff9b..7255ca4ae 100644 --- a/reactive_graph/src/signal/rw.rs +++ b/reactive_graph/src/signal/rw.rs @@ -198,6 +198,13 @@ impl From> for RwSignal { } } +impl<'a, T: Send + Sync + 'static> From<&'a ArcRwSignal> for RwSignal { + #[track_caller] + fn from(value: &'a ArcRwSignal) -> Self { + value.clone().into() + } +} + impl From> for ArcRwSignal { #[track_caller] fn from(value: RwSignal) -> Self { diff --git a/routing/src/matching/choose_view.rs b/routing/src/matching/choose_view.rs index 2246a7d1d..a5bd6b53c 100644 --- a/routing/src/matching/choose_view.rs +++ b/routing/src/matching/choose_view.rs @@ -7,7 +7,7 @@ where Self: 'static, R: Renderer + 'static, { - type Output: Render; + type Output: Render + Send; fn choose(self, route_data: RouteData) -> Self::Output; } @@ -15,7 +15,7 @@ where impl ChooseView for F where F: Fn(RouteData) -> View + 'static, - View: Render, + View: Render + Send, R: Renderer + 'static, { type Output = View; diff --git a/routing/src/matching/mod.rs b/routing/src/matching/mod.rs index 98ca376db..63b4f3cc3 100644 --- a/routing/src/matching/mod.rs +++ b/routing/src/matching/mod.rs @@ -91,7 +91,7 @@ where R: Renderer + 'static, { type Child: MatchInterface + MatchParams + 'static; - type View: Render + RenderHtml + 'static; + type View: Render + RenderHtml + Send + 'static; fn as_id(&self) -> RouteMatchId; diff --git a/routing/src/matching/nested/mod.rs b/routing/src/matching/nested/mod.rs index 3b04cf4e3..bb8af58f2 100644 --- a/routing/src/matching/nested/mod.rs +++ b/routing/src/matching/nested/mod.rs @@ -112,7 +112,7 @@ where Rndr: Renderer + 'static, Child: MatchInterface + MatchParams + 'static, ViewFn: Fn(RouteData) -> View + 'static, - View: Render + RenderHtml + 'static, + View: Render + RenderHtml + Send + 'static, { type Child = Child; type View = ViewFn::Output; @@ -148,7 +148,7 @@ where Children: 'static, ::Params: Clone, ViewFn: Fn(RouteData) -> View + Clone + 'static, - View: Render + RenderHtml + 'static, + View: Render + RenderHtml + Send + 'static, { type Data = Data; type View = View; diff --git a/routing/src/router.rs b/routing/src/router.rs index b57345aa3..92eddae60 100644 --- a/routing/src/router.rs +++ b/routing/src/router.rs @@ -120,6 +120,7 @@ where >, >; type FallibleState = (); // TODO + type AsyncOutput = (); // TODO fn build(self) -> Self::State { let location = Loc::new().unwrap(); // TODO @@ -176,16 +177,18 @@ where fn rebuild(self, state: &mut Self::State) {} - fn try_build(self) -> tachys::error::Result { + fn try_build(self) -> leptos::error::Result { todo!() } fn try_rebuild( self, state: &mut Self::FallibleState, - ) -> tachys::error::Result<()> { + ) -> leptos::error::Result<()> { todo!() } + + async fn resolve(self) -> Self::AsyncOutput {} } impl RenderHtml @@ -631,6 +634,7 @@ where { type State = Outlet; type FallibleState = (); + type AsyncOutput = Self; fn build(self) -> Self::State { self @@ -640,16 +644,20 @@ where todo!() } - fn try_build(self) -> tachys::error::Result { + fn try_build(self) -> leptos::error::Result { todo!() } fn try_rebuild( self, state: &mut Self::FallibleState, - ) -> tachys::error::Result<()> { + ) -> leptos::error::Result<()> { todo!() } + + async fn resolve(self) -> Self { + self + } } impl RenderHtml for Outlet @@ -857,6 +865,7 @@ where { type State = NestedRouteState; type FallibleState = (); + type AsyncOutput = Self; fn build(self) -> Self::State { let NestedRouteView { @@ -892,16 +901,20 @@ where view.rebuild(&mut state.view); } - fn try_build(self) -> tachys::error::Result { + fn try_build(self) -> leptos::error::Result { todo!() } fn try_rebuild( self, state: &mut Self::FallibleState, - ) -> tachys::error::Result<()> { + ) -> leptos::error::Result<()> { todo!() } + + async fn resolve(self) -> Self { + self + } } impl RenderHtml for NestedRouteView @@ -1075,6 +1088,7 @@ where >, >; type FallibleState = Self::State; + type AsyncOutput = Self; fn build(self) -> Self::State { let location = Loc::new().unwrap(); // TODO @@ -1158,16 +1172,20 @@ where fn rebuild(self, state: &mut Self::State) {} - fn try_build(self) -> tachys::error::Result { + fn try_build(self) -> leptos::error::Result { todo!() } fn try_rebuild( self, state: &mut Self::FallibleState, - ) -> tachys::error::Result<()> { + ) -> leptos::error::Result<()> { todo!() } + + async fn resolve(self) -> Self { + self + } } impl RenderHtml diff --git a/tachys/src/html/attribute/value.rs b/tachys/src/html/attribute/value.rs index d5ad8a822..d6fe6decc 100644 --- a/tachys/src/html/attribute/value.rs +++ b/tachys/src/html/attribute/value.rs @@ -1,5 +1,14 @@ use crate::renderer::Renderer; -use std::borrow::Cow; +use std::{ + borrow::Cow, + fmt::Write, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, + num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, + NonZeroIsize, NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, + NonZeroU8, NonZeroUsize, + }, +}; pub trait AttributeValue { type State; @@ -326,3 +335,88 @@ where fn escape_attr(value: &str) -> Cow<'_, str> { html_escape::encode_double_quoted_attribute(value) } + +macro_rules! render_primitive { + ($($child_type:ty),* $(,)?) => { + $( + impl AttributeValue for $child_type + where + R: Renderer, + { + type State = (R::Element, $child_type); + + fn html_len(&self) -> usize { + 0 + } + + fn to_html(self, key: &str, buf: &mut String) { + >::to_html(self.to_string(), key, buf); + } + + fn to_template(_key: &str, _buf: &mut String) {} + + fn hydrate( + self, + key: &str, + el: &R::Element, + ) -> Self::State { + // if we're actually hydrating from SSRed HTML, we don't need to set the attribute + // if we're hydrating from a CSR-cloned