diff --git a/examples/hackernews_islands_axum/Cargo.toml b/examples/hackernews_islands_axum/Cargo.toml index 6e22acfe7..241a2f5fa 100644 --- a/examples/hackernews_islands_axum/Cargo.toml +++ b/examples/hackernews_islands_axum/Cargo.toml @@ -11,16 +11,11 @@ codegen-units = 1 lto = true [dependencies] -console_log = "1.0" console_error_panic_hook = "0.1" leptos = { path = "../../leptos", features = ["experimental-islands"] } -leptos_axum = { path = "../../integrations/axum", optional = true, features = [ - "experimental-islands", -] } +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.0", features = ["derive"] } tracing = "0.1" gloo-net = { version = "0.6", features = ["http"] } @@ -46,8 +41,8 @@ mime_guess = { version = "2.0.4", optional = true } [features] default = [] -csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] -hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] +csr = ["leptos/csr"] +hydrate = ["leptos/hydrate"] ssr = [ "dep:axum", "dep:tower", diff --git a/examples/hackernews_islands_axum/src/api.rs b/examples/hackernews_islands_axum/src/api.rs index dedf95ef3..2c345f154 100644 --- a/examples/hackernews_islands_axum/src/api.rs +++ b/examples/hackernews_islands_axum/src/api.rs @@ -1,27 +1,30 @@ -#![allow(unused)] - -use leptos::Serializable; +#[cfg(feature = "ssr")] +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; +#[cfg(feature = "ssr")] pub fn story(path: &str) -> String { format!("https://node-hnapi.herokuapp.com/{path}") } +#[cfg(feature = "ssr")] pub fn user(path: &str) -> String { format!("https://hacker-news.firebaseio.com/v0/user/{path}.json") } -lazy_static::lazy_static! { - static ref CLIENT: reqwest::Client = reqwest::Client::new(); -} - #[cfg(feature = "ssr")] pub async fn fetch_api(path: &str) -> Option where - T: Serializable, + T: Serialize + DeserializeOwned, { - let json = CLIENT.get(path).send().await.ok()?.text().await.ok()?; - T::de(&json).map_err(|e| log::error!("{e}")).ok() + use leptos::logging; + reqwest::get(path) + .await + .map_err(|e| logging::error!("{e}")) + .ok()? + .json() + .await + .ok() } #[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)] diff --git a/examples/hackernews_islands_axum/src/error_template.rs b/examples/hackernews_islands_axum/src/error_template.rs deleted file mode 100644 index d2df58274..000000000 --- a/examples/hackernews_islands_axum/src/error_template.rs +++ /dev/null @@ -1,28 +0,0 @@ -use leptos::{view, Errors, For, IntoView, RwSignal, SignalGet, View}; - -// A basic function to display errors served by the error boundaries. Feel free to do more complicated things -// here than just displaying them -pub fn error_template(errors: Option>) -> View { - let Some(errors) = errors else { - panic!("No Errors found and we expected errors!"); - }; - - view! { -

"Errors"

- "Error: " {error_string}

- } - } - /> - } - .into_view() -} diff --git a/examples/hackernews_islands_axum/src/lib.rs b/examples/hackernews_islands_axum/src/lib.rs index ef770b8d0..b4cdfc245 100644 --- a/examples/hackernews_islands_axum/src/lib.rs +++ b/examples/hackernews_islands_axum/src/lib.rs @@ -1,13 +1,31 @@ use leptos::prelude::*; -use leptos_meta::*; -use leptos_router::*; mod api; -pub mod error_template; -#[cfg(feature = "ssr")] -pub mod fallback; mod routes; +use leptos_meta::{provide_meta_context, Link, Meta, MetaTags, Stylesheet}; +use leptos_router::{ + components::{FlatRoutes, Route, Router}, + ParamSegment, StaticSegment, +}; use routes::{nav::*, stories::*, story::*, users::*}; +pub fn shell(options: LeptosOptions) -> impl IntoView { + view! { + + + + + + + + + + + + + + } +} + #[component] pub fn App() -> impl IntoView { provide_meta_context(); @@ -16,66 +34,24 @@ pub fn App() -> impl IntoView { - diff --git a/examples/hackernews_islands_axum/src/routes/stories.rs b/examples/hackernews_islands_axum/src/routes/stories.rs index b9983aa42..8e60c1b91 100644 --- a/examples/hackernews_islands_axum/src/routes/stories.rs +++ b/examples/hackernews_islands_axum/src/routes/stories.rs @@ -1,6 +1,9 @@ use crate::api; -use leptos::prelude::*; -use leptos_router::*; +use leptos::{either::Either, prelude::*}; +use leptos_router::{ + components::A, + hooks::{use_params_map, use_query_map}, +}; fn category(from: &str) -> String { match from { @@ -13,7 +16,7 @@ fn category(from: &str) -> String { .to_string() } -#[server(FetchStories, "/api")] +#[server] pub async fn fetch_stories( story_type: String, page: usize, @@ -30,80 +33,84 @@ pub fn Stories() -> impl IntoView { let params = use_params_map(); let page = move || { query - .with(|q| q.get("page").and_then(|page| page.parse::().ok())) + .read() + .get("page") + .and_then(|page| page.parse::().ok()) .unwrap_or(1) }; let story_type = move || { params - .with(|p| p.get("stories").cloned()) + .read() + .get("stories") .unwrap_or_else(|| "top".to_string()) }; - let stories = create_resource( + let stories = Resource::new( move || (page(), story_type()), - move |(page, story_type)| fetch_stories(category(&story_type), page), + move |(page, story_type)| async move { + fetch_stories(category(&story_type), page).await.ok() + }, ); - let (pending, set_pending) = create_signal(false); + let (pending, set_pending) = signal(false); - let hide_more_link = move || { - pending.get() - || stories - .map(|stories| { - stories.as_ref().map(|s| s.len() < 28).unwrap_or_default() - }) - .unwrap_or_default() - }; + let hide_more_link = move || match &*stories.read() { + Some(Some(stories)) => stories.len() < 28, + _ => true + } || pending.get(); view! { -
{move || if page() > 1 { - view! { + Either::Left(view! { "< prev" - }.into_any() + }) } else { - view! { + Either::Right(view! { - }.into_any() + }) }} "page " {page} - - + - "more >" - - + + "more >" + + +
"Loading..."

} set_pending > - {move || stories.get().map(|story| story.map(|stories| view! { -
    - - - -
- }))} + + > +

"Error loading stories."

+
+
    + + + +
@@ -118,26 +125,26 @@ fn Story(story: api::Story) -> impl IntoView { {story.points} {if !story.url.starts_with("item?id=") { - view! { + Either::Left(view! { {story.title.clone()} "("{story.domain}")" - }.into_view() + }) } else { let title = story.title.clone(); - view! { {title.clone()} }.into_view() + Either::Right(view! { {title} }) }}
{if story.story_type != "job" { - view! { + Either::Left(view! { {"by "} - {story.user.map(|user| view ! { {user.clone()}})} + {story.user.map(|user| view ! { {user.clone()}})} {format!(" {} | ", story.time_ago)} {if story.comments_count.unwrap_or_default() > 0 { @@ -147,10 +154,10 @@ fn Story(story: api::Story) -> impl IntoView { }} - }.into_view() + }) } else { let title = story.title.clone(); - view! { {title.clone()} }.into_view() + Either::Right(view! { {title} }) }} {(story.story_type != "link").then(|| view! { diff --git a/examples/hackernews_islands_axum/src/routes/story.rs b/examples/hackernews_islands_axum/src/routes/story.rs index 418fb9175..0085c42b9 100644 --- a/examples/hackernews_islands_axum/src/routes/story.rs +++ b/examples/hackernews_islands_axum/src/routes/story.rs @@ -1,52 +1,37 @@ use crate::api; +use leptos::either::Either; use leptos::prelude::*; -use leptos_meta::*; -use leptos_router::*; -use std::cell::RefCell; +use leptos_meta::Meta; +use leptos_router::components::A; +use leptos_router::hooks::use_params_map; -#[server(FetchStory, "/api")] +#[server] pub async fn fetch_story( id: String, -) -> Result>, ServerFnError> { - Ok(RefCell::new( - api::fetch_api::(&api::story(&format!("item/{id}"))).await, - )) +) -> Result, ServerFnError> { + Ok(api::fetch_api::(&api::story(&format!("item/{id}"))).await) } #[component] pub fn Story() -> impl IntoView { let params = use_params_map(); - let story = create_resource( - move || params.get().get("id").cloned().unwrap_or_default(), + let story = Resource::new( + move || params.read().get("id").unwrap_or_default(), move |id| async move { if id.is_empty() { - Ok(RefCell::new(None)) + Ok(None) } else { fetch_story(id).await } }, ); - let meta_description = move || { - story - .map(|story| { - story - .as_ref() - .map(|story| { - story.borrow().as_ref().map(|story| story.title.clone()) - }) - .ok() - }) - .flatten() - .flatten() - .unwrap_or_else(|| "Loading story...".to_string()) - }; - let story_view = move || { - story.map(|story| { - story.as_ref().ok().and_then(|story| { - let story: Option = story.borrow_mut().take(); - story.map(|story| { - view! { + Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend(async move { + match story.await.ok().flatten() { + None => Either::Left("Story not found."), + Some(story) => { + Either::Right(view! { +
+
+

+ {if story.comments_count.unwrap_or_default() > 0 { + format!("{} comments", story.comments_count.unwrap_or_default()) + } else { + "No comments yet.".into() + }} +

+
    + + + +
+
-
-

- {if story.comments_count.unwrap_or_default() > 0 { - format!("{} comments", story.comments_count.unwrap_or_default()) - } else { - "No comments yet.".into() - }} -

-
    - {story.comments.unwrap_or_default().into_iter() - .map(|comment: api::Comment| view! { }) - .collect_view()} -
-
-
- }})})}) - }; - - view! { - - - {story_view} - - } -} - -#[island] -pub fn Toggle(children: Children) -> impl IntoView { - let (open, set_open) = create_signal(true); - view! { - -
    - {children()} -
- } + } + }))).build()) } #[component] @@ -135,3 +93,29 @@ pub fn Comment(comment: api::Comment) -> impl IntoView { } } + +#[island] +pub fn Toggle(children: Children) -> impl IntoView { + let (open, set_open) = signal(true); + view! { + +
    + {children()} +
+ } +} diff --git a/examples/hackernews_islands_axum/src/routes/users.rs b/examples/hackernews_islands_axum/src/routes/users.rs index c0557d8f5..94ebe1a5d 100644 --- a/examples/hackernews_islands_axum/src/routes/users.rs +++ b/examples/hackernews_islands_axum/src/routes/users.rs @@ -1,20 +1,20 @@ -#[allow(unused)] // User is unused in WASM build -use crate::api::{self, User}; -use leptos::prelude::*; -use leptos_router::*; +use crate::api; +use leptos::server::Resource; +use leptos::{either::Either, prelude::*}; +use leptos_router::hooks::use_params_map; -#[server(FetchUser, "/api")] +#[server] pub async fn fetch_user( id: String, ) -> Result, ServerFnError> { - Ok(api::fetch_api::(&api::user(&id)).await) + Ok(api::fetch_api::(&api::user(&id)).await) } #[component] pub fn User() -> impl IntoView { let params = use_params_map(); - let user = create_resource( - move || params.get().get("id").cloned().unwrap_or_default(), + let user = Resource::new( + move || params.read().get("id").unwrap_or_default(), move |id| async move { if id.is_empty() { Ok(None) @@ -25,12 +25,12 @@ pub fn User() -> impl IntoView { ); view! {
- - {move || user.get().map(|user| user.map(|user| match user { - None => view! {

"User not found."

}.into_view(), - Some(user) => view! { + + {move || Suspend(async move { match user.await.ok().flatten() { + None => Either::Left(view! {

"User not found."

}), + Some(user) => Either::Right(view! {
-

"User: " {&user.id}

+

"User: " {user.id.clone()}

  • "Created: " {user.created} @@ -46,8 +46,8 @@ pub fn User() -> impl IntoView { "comments"

- }.into_view() - }))} + }) + }})}
}