portfolio_leptos/src/app/pages/posts.rs
2024-01-12 22:11:09 +01:00

193 lines
No EOL
5.6 KiB
Rust

use leptos::*;
use leptos_router::*;
use crate::app::{
models::{Post, PostMetadata},
components::{
Loading, Nav, ReadingTime, DateTime
}
};
#[server]
pub async fn get_posts(
tag: Option<String>
) -> Result<Vec<Post>, ServerFnError> {
leptos_actix::extract(
|data: actix_web::web::Data<crate::app::models::Data>| async move {
let data = data.into_inner();
let default = vec![];
let posts = match tag {
Some(tag) => data.posts_by_tag.get(&tag).unwrap_or(&default),
None => &data.posts
};
posts.iter()
.map(|post| {
Post {
metadata: post.metadata.clone(),
content: post.content.clone(),
}
})
.collect::<Vec<_>, >()
},
).await
}
#[server]
pub async fn get_post(
slug: String
) -> Result<Post, ServerFnError> {
leptos_actix::extract(
|data: actix_web::web::Data<crate::app::models::Data>| async move {
let data = data.into_inner();
data.posts_by_slug.get(&slug)
.and_then(|post| Some(Post {
metadata: post.metadata.clone(),
content: post.content.clone(),
}))
},
)
.await
.and_then(|post| post.ok_or_else(|| ServerFnError::ServerError("Post not found".to_string())))
}
#[component]
pub fn PostHeader(
metadata: PostMetadata,
full_element: bool
) -> impl IntoView {
let class = if full_element {
"metadata border-b-2 pb-5"
} else {
"metadata"
};
let title = if full_element {
view! { <>{metadata.title.clone()}</> }.into_view()
} else {
view! { <A href=format!("/posts/{}", metadata.slug.clone())>{metadata.title.clone()}</A> }
};
view! {
<div class=class>
<h2>{title}</h2>
<p>{metadata.description.clone()}</p>
<DateTime datetime=metadata.date />
<ReadingTime time=metadata.reading_time />
<PostTags tags=metadata.tags.clone()/>
</div>
}
}
#[component]
pub fn PostTags(
tags: Vec<String>
) -> impl IntoView {
view! {
<div class="tags_list">
{
tags.into_iter().map(|tag| view! { <A class="tag" href=format!("/posts?tag={}", tag)>{tag}</A>}).collect_view()
}
</div>
}
}
#[component]
pub fn PostListCard(
post: Post
) -> impl IntoView {
view! {
<div>
<img src={post.metadata.image_path.clone()} alt=format!("Image {}", post.metadata.title)/>
{
if post.metadata.draft {
Some(view!{
<div class="warning">
<leptos_icons::Icon icon=icondata::IoConstruct/>
</div>
})
} else {
None
}
}
<PostHeader metadata=post.metadata.clone() full_element=false />
</div>
}
}
#[component]
pub fn PostList() -> impl IntoView {
let query = use_query_map();
let tag = move || query.with(|query| query.get("tag").cloned());
let posts = create_resource(move || tag(), move |_| get_posts(tag()));
let posts_view = move || {
posts.and_then(|posts| {
posts.iter()
.map(|post| view! { <PostListCard post=post.clone() /> })
.collect_view()
})
};
let filter_view = move || {
tag().and_then(|tag| Some(view! {
<div class="mx-auto max-w-3xl mb-5">
Tag sélectionné : <A class="tag" href="/posts".to_string()>{tag}<leptos_icons::Icon icon=icondata::IoCloseOutline class="scale-125 ml-1 inline" /></A>
</div>
}))
};
view! {
<Suspense fallback=move || view! { <Loading title="Chargement des posts...".to_string() /> }>
<Nav/>
{filter_view}
<main class="posts">
<div class="posts__cards">{posts_view}</div>
</main>
</Suspense>
}
}
#[component]
pub fn PostElement() -> impl IntoView {
let params = use_params_map();
let slug = move || params.with(|params| params.get("slug").cloned().unwrap_or_default());
let post = create_resource(|| (), move |_| get_post(slug()));
let post_view = move || {
post.and_then(|post| {
view! {
<>
{
if post.metadata.draft {
Some(view!{
<div class="bg-warning text-on_warning dark:bg-dark_warning dark:text-dart_on_warning rounded-md p-5 mb-5">
r#"
L'article est en cours d'écriture. La formulation peut ne pas être exacte et les phrases peuvent contenir des fautes.
"#
</div>
})
} else {
None
}
}
<PostHeader metadata=post.metadata.clone() full_element=true />
<div inner_html={post.content.clone()}></div>
</>
}
})
};
view! {
<Suspense fallback=move || view! { <Loading title="Chargement du post...".to_string() /> }>
<Nav/>
<main class="post">
{post_view}
<script>load();</script>
</main>
</Suspense>
}
}