mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
chore: re-export untrack
(#2991)
This commit is contained in:
parent
5af7b54c9c
commit
f3b6d1f351
3 changed files with 36 additions and 284 deletions
|
@ -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>
|
||||
// We’ll 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()])
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in a new issue