From 23946661795017d8ca0c9febc91e74e73f89c583 Mon Sep 17 00:00:00 2001 From: Orual Date: Sun, 16 Nov 2025 18:13:29 -0500 Subject: [PATCH] ssr options for stuff --- crates/weaver-app/{.env-example => .env-prod} | 6 +- crates/weaver-app/src/components/css.rs | 10 +- crates/weaver-app/src/components/identity.rs | 48 ++--- crates/weaver-app/src/data.rs | 188 +++++++++++++++++- crates/weaver-app/src/env.rs | 6 +- crates/weaver-app/src/main.rs | 24 +-- crates/weaver-app/src/views/home.rs | 48 ++--- crates/weaver-app/src/views/notebook.rs | 78 +++----- 8 files changed, 281 insertions(+), 127 deletions(-) rename crates/weaver-app/{.env-example => .env-prod} (59%) diff --git a/crates/weaver-app/.env-example b/crates/weaver-app/.env-prod similarity index 59% rename from crates/weaver-app/.env-example rename to crates/weaver-app/.env-prod index 5d82bc4..dc4c3dd 100644 --- a/crates/weaver-app/.env-example +++ b/crates/weaver-app/.env-prod @@ -1,6 +1,6 @@ -WEAVER_APP_ENV="dev" -WEAVER_APP_HOST="http://localhost" -WEAVER_APP_DOMAIN="" +WEAVER_APP_ENV="prod" +WEAVER_APP_HOST="https://alpha.weaver.sh" +WEAVER_APP_DOMAIN="https://alpha.weaver.sh" WEAVER_PORT=8080 WEAVER_APP_SCOPES="atproto transition:generic" WEAVER_CLIENT_NAME="Weaver" diff --git a/crates/weaver-app/src/components/css.rs b/crates/weaver-app/src/components/css.rs index 87ce073..93d6de7 100644 --- a/crates/weaver-app/src/components/css.rs +++ b/crates/weaver-app/src/components/css.rs @@ -1,7 +1,7 @@ #[allow(unused_imports)] use crate::fetch; #[allow(unused_imports)] -use dioxus::{prelude::*, CapturedError}; +use dioxus::{CapturedError, prelude::*}; #[cfg(feature = "fullstack-server")] use dioxus::fullstack::response::Response; @@ -20,7 +20,7 @@ pub fn NotebookCss(ident: SmolStr, notebook: SmolStr) -> Element { use dioxus::fullstack::get_server_url; rsx! { document::Stylesheet { - href: "{get_server_url()}/{ident}/{notebook}/css" + href: "{get_server_url()}/css/{ident}/{notebook}" } } } @@ -30,7 +30,7 @@ pub fn NotebookCss(ident: SmolStr, notebook: SmolStr) -> Element { pub fn NotebookCss(ident: SmolStr, notebook: SmolStr) -> Element { use jacquard::client::AgentSessionExt; use jacquard::types::ident::AtIdentifier; - use jacquard::{from_data, CowStr}; + use jacquard::{CowStr, from_data}; use weaver_api::sh_weaver::notebook::book::Book; use weaver_renderer::css::{generate_base_css, generate_syntax_css}; use weaver_renderer::theme::{default_resolved_theme, resolve_theme}; @@ -87,12 +87,12 @@ pub fn NotebookCss(ident: SmolStr, notebook: SmolStr) -> Element { } #[cfg(feature = "fullstack-server")] -#[get("/{ident}/{notebook}/css", fetcher: Extension>)] +#[get("/css/{ident}/{notebook}", fetcher: Extension>)] pub async fn css(ident: SmolStr, notebook: SmolStr) -> Result { use dioxus::fullstack::http::header::CONTENT_TYPE; use jacquard::client::AgentSessionExt; use jacquard::types::ident::AtIdentifier; - use jacquard::{from_data, CowStr}; + use jacquard::{CowStr, from_data}; use weaver_api::sh_weaver::notebook::book::Book; use weaver_renderer::css::{generate_base_css, generate_syntax_css}; diff --git a/crates/weaver-app/src/components/identity.rs b/crates/weaver-app/src/components/identity.rs index f1fe31e..b624e94 100644 --- a/crates/weaver-app/src/components/identity.rs +++ b/crates/weaver-app/src/components/identity.rs @@ -1,4 +1,4 @@ -use crate::{Route, fetch}; +use crate::{Route, data, fetch}; use dioxus::prelude::*; use jacquard::{smol_str::SmolStr, types::ident::AtIdentifier}; use weaver_api::com_atproto::repo::strong_ref::StrongRef; @@ -20,13 +20,8 @@ pub fn Repository(ident: ReadSignal>) -> Element { pub fn RepositoryIndex(ident: ReadSignal>) -> Element { use crate::components::ProfileDisplay; - let fetcher = use_context::(); - - // Fetch notebooks for this specific DID - let notebooks = use_resource(move || { - let fetcher = fetcher.clone(); - async move { fetcher.fetch_notebooks_for_did(&ident()).await } - }); + // Fetch notebooks for this specific DID with SSR support + let notebooks = data::use_notebooks_for_did(ident()).ok(); rsx! { document::Stylesheet { href: NOTEBOOK_CARD_CSS } @@ -40,30 +35,31 @@ pub fn RepositoryIndex(ident: ReadSignal>) -> Element { // Main content area main { class: "repository-main", div { class: "notebooks-list", - match notebooks() { - Some(Ok(notebook_list)) => rsx! { - for notebook in notebook_list.iter() { - { - let view = ¬ebook.0; - let entries = ¬ebook.1; - rsx! { - div { - key: "{view.cid}", - NotebookCard { - notebook: view.clone(), - entry_refs: entries.clone() + if let Some(notebooks_memo) = ¬ebooks { + match &*notebooks_memo.read_unchecked() { + Some(notebook_list) => rsx! { + for notebook in notebook_list.iter() { + { + let view = ¬ebook.0; + let entries = ¬ebook.1; + rsx! { + div { + key: "{view.cid}", + NotebookCard { + notebook: view.clone(), + entry_refs: entries.clone() + } } } } } + }, + None => rsx! { + div { "Loading notebooks..." } } - }, - Some(Err(_)) => rsx! { - div { "Error loading notebooks" } - }, - None => rsx! { - div { "Loading notebooks..." } } + } else { + div { "Loading notebooks..." } } } } diff --git a/crates/weaver-app/src/data.rs b/crates/weaver-app/src/data.rs index a749c96..92c244f 100644 --- a/crates/weaver-app/src/data.rs +++ b/crates/weaver-app/src/data.rs @@ -18,7 +18,7 @@ use jacquard::{ }; #[allow(unused_imports)] use std::sync::Arc; -use weaver_api::sh_weaver::notebook::{BookEntryView, entry::Entry}; +use weaver_api::sh_weaver::notebook::{BookEntryView, NotebookView, entry::Entry}; // ============================================================================ // Wrapper Hooks (feature-gated) // ============================================================================ @@ -365,6 +365,192 @@ pub fn use_notebooks_for_did( })) } +/// Fetches notebooks from UFOS with SSR support in fullstack mode +#[cfg(feature = "fullstack-server")] +pub fn use_notebooks_from_ufos() -> Result< + Memo, Vec>)>>>, + RenderError, +> { + let fetcher = use_context::(); + let res = use_server_future(move || { + let fetcher = fetcher.clone(); + async move { + fetcher + .fetch_notebooks_from_ufos() + .await + .ok() + .map(|notebooks| { + notebooks + .iter() + .map(|arc| serde_json::to_value(arc.as_ref()).ok()) + .collect::>>() + }) + .flatten() + } + })?; + Ok(use_memo(move || { + if let Some(Some(values)) = &*res.read_unchecked() { + values + .iter() + .map(|v| { + jacquard::from_json_value::<( + NotebookView, + Vec, + )>(v.clone()) + .ok() + }) + .collect::>>() + } else { + None + } + })) +} + +/// Fetches notebooks from UFOS client-side only (no SSR) +#[cfg(not(feature = "fullstack-server"))] +pub fn use_notebooks_from_ufos() -> Result< + Memo, Vec>)>>>, + RenderError, +> { + let fetcher = use_context::(); + let r = use_resource(move || { + let fetcher = fetcher.clone(); + async move { + fetcher + .fetch_notebooks_from_ufos() + .await + .ok() + .map(|notebooks| notebooks.iter().map(|arc| (*arc).clone()).collect()) + } + }); + Ok(use_memo(move || { + r.read_unchecked().as_ref().and_then(|v| v.clone()) + })) +} + +/// Fetches notebook metadata with SSR support in fullstack mode +#[cfg(feature = "fullstack-server")] +pub fn use_notebook( + ident: AtIdentifier<'static>, + book_title: SmolStr, +) -> Result< + Memo, Vec>)>>, + RenderError, +> { + let fetcher = use_context::(); + let ident = use_signal(|| ident); + let book_title = use_signal(|| book_title); + let res = use_server_future(move || { + let fetcher = fetcher.clone(); + async move { + fetcher + .get_notebook(ident(), book_title()) + .await + .ok() + .flatten() + .map(|arc| serde_json::to_value(arc.as_ref()).ok()) + .flatten() + } + })?; + Ok(use_memo(move || { + if let Some(Some(value)) = &*res.read_unchecked() { + jacquard::from_json_value::<( + NotebookView, + Vec, + )>(value.clone()) + .ok() + } else { + None + } + })) +} + +/// Fetches notebook metadata client-side only (no SSR) +#[cfg(not(feature = "fullstack-server"))] +pub fn use_notebook( + ident: AtIdentifier<'static>, + book_title: SmolStr, +) -> Result< + Memo, Vec>)>>, + RenderError, +> { + let fetcher = use_context::(); + let r = use_resource(use_reactive!(|(ident, book_title)| { + let fetcher = fetcher.clone(); + async move { + fetcher + .get_notebook(ident, book_title) + .await + .ok() + .flatten() + .map(|arc| (*arc).clone()) + } + })); + Ok(use_memo(move || { + r.read_unchecked().as_ref().and_then(|v| v.clone()) + })) +} + +/// Fetches notebook entries with SSR support in fullstack mode +#[cfg(feature = "fullstack-server")] +pub fn use_notebook_entries( + ident: AtIdentifier<'static>, + book_title: SmolStr, +) -> Result>>>, RenderError> { + let fetcher = use_context::(); + let ident = use_signal(|| ident); + let book_title = use_signal(|| book_title); + let res = use_server_future(move || { + let fetcher = fetcher.clone(); + async move { + fetcher + .list_notebook_entries(ident(), book_title()) + .await + .ok() + .flatten() + .map(|entries| { + entries + .iter() + .map(|e| serde_json::to_value(e).ok()) + .collect::>>() + }) + .flatten() + } + })?; + Ok(use_memo(move || { + if let Some(Some(values)) = &*res.read_unchecked() { + values + .iter() + .map(|v| jacquard::from_json_value::(v.clone()).ok()) + .collect::>>() + } else { + None + } + })) +} + +/// Fetches notebook entries client-side only (no SSR) +#[cfg(not(feature = "fullstack-server"))] +pub fn use_notebook_entries( + ident: AtIdentifier<'static>, + book_title: SmolStr, +) -> Result>>>, RenderError> { + let fetcher = use_context::(); + let r = use_resource(use_reactive!(|(ident, book_title)| { + let fetcher = fetcher.clone(); + async move { + fetcher + .list_notebook_entries(ident, book_title) + .await + .ok() + .flatten() + } + })); + Ok(use_memo(move || { + r.read_unchecked().as_ref().and_then(|v| v.clone()) + })) +} + #[cfg(feature = "fullstack-server")] #[put("/cache/{ident}/{cid}?name", cache: Extension>)] pub async fn cache_blob(ident: SmolStr, cid: SmolStr, name: Option) -> Result<()> { diff --git a/crates/weaver-app/src/env.rs b/crates/weaver-app/src/env.rs index e130e54..b8c902b 100644 --- a/crates/weaver-app/src/env.rs +++ b/crates/weaver-app/src/env.rs @@ -1,11 +1,11 @@ // This file is automatically generated by build.rs #[allow(unused)] -pub const WEAVER_APP_ENV: &'static str = "prod"; +pub const WEAVER_APP_ENV: &'static str = "dev"; #[allow(unused)] -pub const WEAVER_APP_HOST: &'static str = "https://alpha.weaver.sh"; +pub const WEAVER_APP_HOST: &'static str = "http://localhost"; #[allow(unused)] -pub const WEAVER_APP_DOMAIN: &'static str = "https://alpha.weaver.sh"; +pub const WEAVER_APP_DOMAIN: &'static str = ""; #[allow(unused)] pub const WEAVER_PORT: &'static str = "8080"; #[allow(unused)] diff --git a/crates/weaver-app/src/main.rs b/crates/weaver-app/src/main.rs index f876c8d..5ca95cd 100644 --- a/crates/weaver-app/src/main.rs +++ b/crates/weaver-app/src/main.rs @@ -136,13 +136,7 @@ fn main() { // Enable incremental rendering .incremental( dioxus::server::IncrementalRendererConfig::new() - .static_dir( - std::env::current_exe() - .unwrap() - .parent() - .unwrap() - .join("public"), - ) + .pre_render(true) .clear_cache(false), ) .enable_out_of_order_streaming(), @@ -260,11 +254,11 @@ pub async fn blob(notebook: SmolStr, cid: SmolStr) -> Result Result, ServerFnError> { - // The `Routable` trait has a `static_routes` method that returns all static routes in the enum - Ok(Route::static_routes() - .iter() - .map(ToString::to_string) - .collect()) -} +// #[server(endpoint = "static_routes", output = server_fn::codec::Json)] +// async fn static_routes() -> Result, ServerFnError> { +// // The `Routable` trait has a `static_routes` method that returns all static routes in the enum +// Ok(Route::static_routes() +// .iter() +// .map(ToString::to_string) +// .collect()) +// } diff --git a/crates/weaver-app/src/views/home.rs b/crates/weaver-app/src/views/home.rs index 833328b..212a6c0 100644 --- a/crates/weaver-app/src/views/home.rs +++ b/crates/weaver-app/src/views/home.rs @@ -1,4 +1,4 @@ -use crate::{Route, components::identity::NotebookCard, fetch}; +use crate::{Route, components::identity::NotebookCard, data}; use dioxus::prelude::*; use jacquard::types::aturi::AtUri; @@ -7,13 +7,8 @@ const NOTEBOOK_CARD_CSS: Asset = asset!("/assets/styling/notebook-card.css"); /// The Home page component that will be rendered when the current route is `[Route::Home]` #[component] pub fn Home() -> Element { - let fetcher = use_context::(); - - // Fetch notebooks from UFOS - let notebooks = use_resource(move || { - let fetcher = fetcher.clone(); - async move { fetcher.fetch_notebooks_from_ufos().await } - }); + // Fetch notebooks from UFOS with SSR support + let notebooks = data::use_notebooks_from_ufos().ok(); let navigator = use_navigator(); let mut uri_input = use_signal(|| String::new()); @@ -49,30 +44,31 @@ pub fn Home() -> Element { } } div { class: "notebooks-list", - match notebooks() { - Some(Ok(notebook_list)) => rsx! { - for notebook in notebook_list.iter() { - { - let view = ¬ebook.0; - let entries = ¬ebook.1; - rsx! { - div { - key: "{view.cid}", - NotebookCard { - notebook: view.clone(), - entry_refs: entries.clone() + if let Some(notebooks_memo) = ¬ebooks { + match &*notebooks_memo.read_unchecked() { + Some(notebook_list) => rsx! { + for notebook in notebook_list.iter() { + { + let view = ¬ebook.0; + let entries = ¬ebook.1; + rsx! { + div { + key: "{view.cid}", + NotebookCard { + notebook: view.clone(), + entry_refs: entries.clone() + } } } } } + }, + None => rsx! { + div { "Loading notebooks..." } } - }, - Some(Err(_)) => rsx! { - div { "Error loading notebooks" } - }, - None => rsx! { - div { "Loading notebooks..." } } + } else { + div { "Loading notebooks..." } } } } diff --git a/crates/weaver-app/src/views/notebook.rs b/crates/weaver-app/src/views/notebook.rs index 4d46f96..5e26177 100644 --- a/crates/weaver-app/src/views/notebook.rs +++ b/crates/weaver-app/src/views/notebook.rs @@ -1,7 +1,7 @@ use crate::{ Route, components::{EntryCard, NotebookCover, NotebookCss}, - fetch, + data, }; use dioxus::prelude::*; use jacquard::{ @@ -28,66 +28,48 @@ pub fn NotebookIndex( ident: ReadSignal>, book_title: ReadSignal, ) -> Element { - let fetcher = use_context::(); - // Fetch full notebook to get author count - let data_fetcher = fetcher.clone(); - let notebook_data = use_resource(move || { - let fetcher = data_fetcher.clone(); - async move { - fetcher - .get_notebook(ident(), book_title()) - .await - .ok() - .flatten() - } - }); + // Fetch full notebook metadata with SSR support + let notebook_data = data::use_notebook(ident(), book_title()).ok(); - // Also fetch entries - let entry_fetcher = fetcher.clone(); - let entries_resource = use_resource(move || { - let fetcher = entry_fetcher.clone(); - async move { - fetcher - .list_notebook_entries(ident(), book_title()) - .await - .ok() - .flatten() - } - }); + // Fetch entries with SSR support + let entries_resource = data::use_notebook_entries(ident(), book_title()).ok(); rsx! { document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS } - match (&*notebook_data.read_unchecked(), &*entries_resource.read_unchecked()) { - (Some(Some(data)), Some(Some(entries))) => { - let (notebook_view, _) = data.as_ref(); - let author_count = notebook_view.authors.len(); + if let (Some(notebook_memo), Some(entries_memo)) = (¬ebook_data, &entries_resource) { + match (&*notebook_memo.read_unchecked(), &*entries_memo.read_unchecked()) { + (Some(data), Some(entries)) => { + let (notebook_view, _) = data; + let author_count = notebook_view.authors.len(); - rsx! { - div { class: "notebook-layout", - aside { class: "notebook-sidebar", - NotebookCover { - notebook: notebook_view.clone(), - title: book_title().to_string() + rsx! { + div { class: "notebook-layout", + aside { class: "notebook-sidebar", + NotebookCover { + notebook: notebook_view.clone(), + title: book_title().to_string() + } } - } - main { class: "notebook-main", - div { class: "entries-list", - for entry in entries { - EntryCard { - entry: entry.clone(), - book_title: book_title(), - author_count + main { class: "notebook-main", + div { class: "entries-list", + for entry in entries { + EntryCard { + entry: entry.clone(), + book_title: book_title(), + author_count + } } } } } } - } - }, - (Some(None), _) | (_, Some(None)) => rsx! { div { class: "error", "Notebook or entries not found" } }, - _ => rsx! { div { class: "loading", "Loading..." } } + }, + _ => rsx! { div { class: "loading", "Loading..." } } + } + } else { + div { class: "loading", "Loading..." } } } } -- knotmirror 0.0.1