chore: re-export untrack (#2991)

This commit is contained in:
Greg Johnston 2024-09-18 19:51:20 -04:00 committed by GitHub
parent 5af7b54c9c
commit f3b6d1f351
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 36 additions and 284 deletions

View file

@ -1,17 +1,9 @@
use lazy_static::lazy_static;
use leptos::prelude::*;
use leptos_meta::MetaTags;
use leptos_meta::*;
use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title};
use leptos_router::{
components::{FlatRoutes, ProtectedRoute, Route, Router},
hooks::use_params,
params::Params,
ParamSegment, SsrMode, StaticSegment,
components::{ProtectedParentRoute, Route, Router, Routes, A},
StaticSegment,
};
use serde::{Deserialize, Serialize};
#[cfg(feature = "ssr")]
use std::sync::atomic::{AtomicBool, Ordering};
use thiserror::Error;
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
@ -31,90 +23,34 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
}
}
#[cfg(feature = "ssr")]
static IS_ADMIN: AtomicBool = AtomicBool::new(true);
#[server]
pub async fn is_admin() -> Result<bool, ServerFnError> {
Ok(IS_ADMIN.load(Ordering::Relaxed))
}
#[server]
pub async fn set_is_admin(is_admin: bool) -> Result<(), ServerFnError> {
IS_ADMIN.store(is_admin, Ordering::Relaxed);
Ok(())
}
#[component]
pub fn App() -> impl IntoView {
// Provides context that manages stylesheets, titles, meta tags, etc.
provide_meta_context();
let fallback = || view! { "Page not found." }.into_view();
let toggle_admin = ServerAction::<SetIsAdmin>::new();
let is_admin =
Resource::new(move || toggle_admin.version().get(), |_| is_admin());
let x = untrack(|| "foo");
view! {
<Stylesheet id="leptos" href="/pkg/ssr_modes.css"/>
<Title text="Welcome to Leptos"/>
<Meta name="color-scheme" content="dark light"/>
<Router>
<nav>
<a href="/">"Home"</a>
<a href="/admin">"Admin"</a>
<Transition>
<ActionForm action=toggle_admin>
<input
type="hidden"
name="is_admin"
value=move || {
(!is_admin.get().and_then(|n| n.ok()).unwrap_or_default())
.to_string()
}
/>
<button>
{move || {
if is_admin.get().and_then(Result::ok).unwrap_or_default() {
"Log Out"
} else {
"Log In"
}
}}
</button>
</ActionForm>
</Transition>
<A href="/">"Home"</A>
" | "
<A href="/dashboard">"Dashboard"</A>
" | "
<A href="/profile">"Profile"</A>
</nav>
<main>
<FlatRoutes fallback>
// Well load the home page with out-of-order streaming and <Suspense/>
<Routes fallback=|| "Page not found.".into_view()>
<Route path=StaticSegment("") view=HomePage/>
// We'll load the posts with async rendering, so they can set
// the title and metadata *after* loading the data
<Route
path=(StaticSegment("post"), ParamSegment("id"))
view=Post
ssr=SsrMode::Async
/>
<Route
path=(StaticSegment("post_in_order"), ParamSegment("id"))
view=Post
ssr=SsrMode::InOrder
/>
<Route
path=(StaticSegment("post_partially_blocked"), ParamSegment("id"))
view=Post
/>
<ProtectedRoute
path=StaticSegment("admin")
view=Admin
ssr=SsrMode::Async
condition=move || is_admin.get().map(|n| n.unwrap_or(false))
redirect_path=|| "/"
/>
</FlatRoutes>
<ProtectedParentRoute
path=StaticSegment("")
view=|| view! { <div>"Protected Content"</div> }
condition=move || Some(false)
redirect_path=|| "/".to_string()
>
<Route path=StaticSegment("dashboard") view=DashboardPage/>
<Route path=StaticSegment("profile") view=ProfilePage/>
</ProtectedParentRoute>
</Routes>
</main>
</Router>
}
@ -122,209 +58,24 @@ pub fn App() -> impl IntoView {
#[component]
fn HomePage() -> impl IntoView {
// load the posts
let posts = Resource::new(|| (), |_| list_post_metadata());
let posts = move || {
posts
.get()
.map(|n| n.unwrap_or_default())
.unwrap_or_default()
};
let posts2 = Resource::new(|| (), |_| list_post_metadata());
let posts2 = Resource::new(
|| (),
move |_| async move { posts2.await.as_ref().map(Vec::len).unwrap_or(0) },
);
view! {
<h1>"My Great Blog"</h1>
<Suspense fallback=move || view! { <p>"Loading posts..."</p> }>
<p>"number of posts: " {Suspend::new(async move { posts2.await })}</p>
</Suspense>
<Suspense fallback=move || view! { <p>"Loading posts..."</p> }>
<ul>
<For each=posts key=|post| post.id let:post>
<li>
<a href=format!("/post/{}", post.id)>{post.title.clone()}</a>
"|"
<a href=format!(
"/post_in_order/{}",
post.id,
)>{post.title.clone()} "(in order)"</a>
"|"
<a href=format!(
"/post_partially_blocked/{}",
post.id,
)>{post.title} "(partially blocked)"</a>
</li>
</For>
</ul>
</Suspense>
}
}
#[derive(Params, Copy, Clone, Debug, PartialEq, Eq)]
pub struct PostParams {
id: Option<usize>,
}
#[component]
fn Post() -> impl IntoView {
let query = use_params::<PostParams>();
let id = move || {
query.with(|q| {
q.as_ref()
.map(|q| q.id.unwrap_or_default())
.map_err(|_| PostError::InvalidId)
})
};
let post_resource = Resource::new_blocking(id, |id| async move {
match id {
Err(e) => Err(e),
Ok(id) => get_post(id)
.await
.map(|data| data.ok_or(PostError::PostNotFound))
.map_err(|_| PostError::ServerError),
}
});
let comments_resource = Resource::new(id, |id| async move {
match id {
Err(e) => Err(e),
Ok(id) => {
get_comments(id).await.map_err(|_| PostError::ServerError)
}
}
});
let post_view = Suspend::new(async move {
match post_resource.await {
Ok(Ok(post)) => {
Ok(view! {
<h1>{post.title.clone()}</h1>
<p>{post.content.clone()}</p>
// since we're using async rendering for this page,
// this metadata should be included in the actual HTML <head>
// when it's first served
<Title text=post.title/>
<Meta name="description" content=post.content/>
})
}
_ => Err(PostError::ServerError),
}
});
let comments_view = Suspend::new(async move {
match comments_resource.await {
Ok(comments) => Ok(view! {
<h1>"Comments"</h1>
<ul>
{comments
.into_iter()
.map(|comment| view! { <li>{comment}</li> })
.collect_view()}
</ul>
}),
_ => Err(PostError::ServerError),
}
});
view! {
<em>"The world's best content."</em>
<Suspense fallback=move || view! { <p>"Loading post..."</p> }>
<ErrorBoundary fallback=|errors| {
view! {
<div class="error">
<h1>"Something went wrong."</h1>
<ul>
{move || {
errors
.get()
.into_iter()
.map(|(_, error)| view! { <li>{error.to_string()}</li> })
.collect::<Vec<_>>()
}}
</ul>
</div>
}
}>{post_view}</ErrorBoundary>
</Suspense>
<Suspense fallback=move || view! { <p>"Loading comments..."</p> }>{comments_view}</Suspense>
<h1>"Welcome to the Home Page"</h1>
<p>"This page is accessible to everyone."</p>
}
}
#[component]
pub fn Admin() -> impl IntoView {
view! { <p>"You can only see this page if you're logged in."</p> }
fn DashboardPage() -> impl IntoView {
view! {
<h1>"Dashboard"</h1>
<p>"This is a protected page. You should only see this if you're authenticated."</p>
}
}
// Dummy API
lazy_static! {
static ref POSTS: Vec<Post> = vec![
Post {
id: 0,
title: "My first post".to_string(),
content: "This is my first post".to_string(),
},
Post {
id: 1,
title: "My second post".to_string(),
content: "This is my second post".to_string(),
},
Post {
id: 2,
title: "My third post".to_string(),
content: "This is my third post".to_string(),
},
];
#[component]
fn ProfilePage() -> impl IntoView {
view! {
<h1>"Profile"</h1>
<p>"This is another protected page. You should only see this if you're authenticated."</p>
}
#[derive(Error, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PostError {
#[error("Invalid post ID.")]
InvalidId,
#[error("Post not found.")]
PostNotFound,
#[error("Server error.")]
ServerError,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Post {
id: usize,
title: String,
content: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct PostMetadata {
id: usize,
title: String,
}
#[server]
pub async fn list_post_metadata() -> Result<Vec<PostMetadata>, ServerFnError> {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
Ok(POSTS
.iter()
.map(|data| PostMetadata {
id: data.id,
title: data.title.clone(),
})
.collect())
}
#[server]
pub async fn get_post(id: usize) -> Result<Option<Post>, ServerFnError> {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
Ok(POSTS.iter().find(|post| post.id == id).cloned())
}
#[server]
pub async fn get_comments(id: usize) -> Result<Vec<String>, ServerFnError> {
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
_ = id;
Ok(vec!["Some comment".into(), "Some other comment".into()])
}

View file

@ -168,7 +168,7 @@ pub mod prelude {
pub use leptos_server::*;
pub use oco_ref::*;
pub use reactive_graph::{
actions::*, computed::*, effect::*, owner::*, signal::*,
actions::*, computed::*, effect::*, owner::*, signal::*, untrack,
wrappers::read::*,
};
pub use server_fn::{self, ServerFnError};

View file

@ -443,6 +443,7 @@ pub fn Redirect<P>(
) where
P: core::fmt::Display + 'static,
{
leptos::logging::log!("running Redirect component");
// TODO resolve relative path
let path = path.to_string();