Updating components to use the database.

This commit is contained in:
2025-09-08 18:39:30 -04:00
parent 417c0d01e4
commit 1f502a7386
6 changed files with 150 additions and 128 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "bard" name = "bard"
version = "0.0.13" version = "0.0.14"
edition = "2024" edition = "2024"
description = "Dioxus components that will display a Tavern blogging system Blog." description = "Dioxus components that will display a Tavern blogging system Blog."
repository = "/CyberMages/tavern" repository = "/CyberMages/tavern"

View File

@ -1,6 +1,4 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use tavern::{Adventurer, Legend, Lore, Tale}; use tavern::{Adventurer, Legend, Lore, Tale};
use crate::page::Page; use crate::page::Page;
@ -15,51 +13,42 @@ const POST_CSS: Asset = asset!("/assets/css/blog_post.css");
const TAG_CSS: Asset = asset!("/assets/css/blog_tag.css"); const TAG_CSS: Asset = asset!("/assets/css/blog_tag.css");
#[derive(Clone, Copy, PartialEq)]
pub enum TagStyle fn convert_tag<S>(text: S) -> (String, String)
where S: AsRef<str>
{ {
Regular, if let Some((first, second)) = text.as_ref().split_once('-')
Easy, {
Medium, (first.to_owned(), second.to_owned())
Hard }
else
{
(text.as_ref().to_owned(), "none".to_owned())
}
} }
#[derive(Clone, Copy, PartialEq)] fn strip_tag(text: &str) -> &str
pub enum Tag
{ {
LeetCode, if let Some((first, _)) = text.split_once('-')
Embedded, {
Simulation, first
Web }
else
{
text
}
} }
#[component] #[component]
pub fn TagItem(tag: Tag, style: TagStyle) -> Element pub fn TagItem(tag: String, style: String) -> Element
{ {
let text = match tag rsx! {
{
Tag::LeetCode => { "LeetCode" }
Tag::Embedded => { "Embedded" }
Tag::Simulation => { "Simulation" }
Tag::Web => { "Web" }
};
let tag_style = match style
{
TagStyle::Regular => { "none" }
TagStyle::Easy => { "easy" }
TagStyle::Medium => { "medium" }
TagStyle::Hard => { "hard" }
};
rsx!
{
li li
{ {
class: "tag_item", class: "tag_item",
a { class: "{tag_style}", "{text}" } a { class: "{style}", "{tag}" }
} }
} }
} }
@ -67,8 +56,7 @@ pub fn TagItem(tag: Tag, style: TagStyle) -> Element
#[component] #[component]
pub fn TagList(children: Element) -> Element pub fn TagList(children: Element) -> Element
{ {
rsx! rsx! {
{
document::Link { rel: "stylesheet", href: TAG_CSS } document::Link { rel: "stylesheet", href: TAG_CSS }
h5 h5
@ -87,8 +75,7 @@ pub fn TagList(children: Element) -> Element
#[component] #[component]
pub fn BlogAuthor() -> Element pub fn BlogAuthor() -> Element
{ {
rsx! rsx! {
{
document::Link { rel: "stylesheet", href: AUTHOR_CSS } document::Link { rel: "stylesheet", href: AUTHOR_CSS }
section section
@ -101,11 +88,14 @@ pub fn BlogAuthor() -> Element
#[component] #[component]
pub fn BlogItem(title: String, slug: String, author: String, pub fn BlogItem(title: String, slug: String, author: String, summary: String,
summary: String, tags: Vec<(Tag, TagStyle)>) -> Element tags: Vec<String>)
-> Element
{ {
rsx! let tags: Vec<(String, String)> =
{ tags.iter().map(|t| convert_tag(t)).collect();
rsx! {
li li
{ {
class: "blog_item", class: "blog_item",
@ -120,8 +110,8 @@ pub fn BlogItem(title: String, slug: String, author: String,
{ {
TagItem TagItem
{ {
tag: tag, tag: tag.clone(),
style: style style: style.clone()
} }
} }
} }
@ -135,17 +125,12 @@ pub fn BlogItem(title: String, slug: String, author: String,
#[component] #[component]
pub fn BlogList(tags: Vec<String>, children: Element) -> Element pub fn BlogList(tags: Vec<String>, children: Element) -> Element
{ {
let summaries = use_server_future(move || let summaries = use_server_future(move || {
{
let categories = tags.clone(); let categories = tags.clone();
async move async move { get_blog_list(categories).await }
{
get_blog_list(categories).await
}
})?; })?;
rsx! rsx! {
{
document::Link { rel: "stylesheet", href: LIST_CSS } document::Link { rel: "stylesheet", href: LIST_CSS }
section section
@ -155,6 +140,44 @@ pub fn BlogList(tags: Vec<String>, children: Element) -> Element
{ {
class: "blog_list", class: "blog_list",
match &*summaries.read()
{
Some(Ok(lores)) =>
{
rsx!
{
for lore in lores
{
BlogItem
{
title: lore.title.clone(),
slug: lore.slug.clone(),
author: lore.author.clone(),
summary: lore.summary.clone(),
tags: lore.tags.clone()
}
}
}
}
Some(Err(e)) =>
{
rsx!
{
p { "Unable to show post header." }
p { "{e}" }
}
}
None =>
{
rsx!
{
p { "Loading..." }
}
}
}
{children} {children}
} }
} }
@ -164,16 +187,9 @@ pub fn BlogList(tags: Vec<String>, children: Element) -> Element
#[component] #[component]
pub fn BlogNav() -> Element pub fn BlogNav() -> Element
{ {
let tags = use_server_future(move || let tags = use_server_future(move || async move { get_tags().await })?;
{
async move
{
get_tags().await
}
})?;
rsx! rsx! {
{
document::Link { rel: "stylesheet", href: NAV_CSS } document::Link { rel: "stylesheet", href: NAV_CSS }
section section
@ -186,25 +202,39 @@ pub fn BlogNav() -> Element
ul ul
{ {
class: "tag_list", class: "tag_list",
match &*tags.read()
{
Some(Ok(categories)) =>
{
rsx!
{
for tag in categories
{
li li
{ {
class: "tag_item", class: "tag_item",
a { href: "/blog/LeetCode", "LeetCode" } a { href: "/blog/{strip_tag(tag)}", "{strip_tag(tag)}" }
} }
li
{
class: "tag_item",
a { href: "/blog/Embedded", "Embedded" }
} }
li
{
class: "tag_item",
a { href: "/blog/Simulation", "Simulation" }
} }
li }
Some(Err(e)) =>
{ {
class: "tag_item", rsx!
a { href: "/blog/Web", "Web" } {
p { "Unable to show desired post." }
p { "{e}" }
}
}
None =>
{
rsx!
{
p { "Loading..." }
}
}
} }
} }
} }
@ -214,10 +244,10 @@ pub fn BlogNav() -> Element
#[component] #[component]
pub fn PostHeaderAuthor(name: String, handle: String, profile_link: String) -> Element pub fn PostHeaderAuthor(name: String, handle: String, profile_link: String)
-> Element
{ {
rsx! rsx! {
{
h4 { "Author: ", a { href: "{profile_link}", "{name} @{handle}" } } h4 { "Author: ", a { href: "{profile_link}", "{name} @{handle}" } }
} }
} }
@ -225,27 +255,25 @@ pub fn PostHeaderAuthor(name: String, handle: String, profile_link: String) -> E
#[component] #[component]
pub fn PostHeader(title: String, author: String, tags: Vec<String>) -> Element pub fn PostHeader(title: String, author: String, tags: Vec<String>) -> Element
{ {
let author = use_server_future(move || let author = use_server_future(move || {
{
let target_author = author.clone(); let target_author = author.clone();
async move async move { get_author(target_author).await }
{
get_author(target_author).await
}
})?; })?;
rsx! let converted_tags: Vec<(String, String)> =
{ tags.iter().map(|t| convert_tag(t)).collect();
rsx! {
h1 { "{title}" } h1 { "{title}" }
TagList TagList
{ {
for category in tags for (tag, style) in converted_tags
{ {
TagItem TagItem
{ {
tag: Tag::LeetCode, tag: tag.clone(),
style: TagStyle::Regular style: style.clone()
} }
} }
} }
@ -288,17 +316,12 @@ pub fn PostHeader(title: String, author: String, tags: Vec<String>) -> Element
#[component] #[component]
pub fn BlogPost(slug: String, children: Element) -> Element pub fn BlogPost(slug: String, children: Element) -> Element
{ {
let post = use_server_future(move || let post = use_server_future(move || {
{
let url_slug = slug.clone(); let url_slug = slug.clone();
async move async move { get_blog_post(url_slug).await }
{
get_blog_post(url_slug).await
}
})?; })?;
rsx! rsx! {
{
document::Link { rel: "stylesheet", href: POST_CSS } document::Link { rel: "stylesheet", href: POST_CSS }
article article

View File

@ -9,11 +9,9 @@ mod server;
pub use crate::info::{get_name, get_version};
pub use crate::components::*; pub use crate::components::*;
pub use crate::info::{get_name, get_version};
pub use crate::page::Page; pub use crate::page::Page;
pub use crate::pages::*; pub use crate::pages::*;
#[cfg(feature = "server")] #[cfg(feature = "server")]
pub use crate::server::*; pub use crate::server::*;

View File

@ -1,8 +1,7 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use crate::page::Page;
use crate::components::BlogList; use crate::components::BlogList;
use crate::page::Page;
@ -21,8 +20,7 @@ pub fn Blog(tag: String) -> Element
categories.push(tag); categories.push(tag);
} }
rsx! rsx! {
{
document::Stylesheet { href: BLOG_CSS } document::Stylesheet { href: BLOG_CSS }
main main

View File

@ -1,7 +1,7 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use crate::page::Page;
use crate::components::BlogPost; use crate::components::BlogPost;
use crate::page::Page;
@ -13,8 +13,7 @@ const BLOG_CSS: Asset = asset!("/assets/css/blog.css");
#[component] #[component]
pub fn Post(slug: String) -> Element pub fn Post(slug: String) -> Element
{ {
rsx! rsx! {
{
document::Stylesheet { href: BLOG_CSS } document::Stylesheet { href: BLOG_CSS }
main main

View File

@ -3,21 +3,19 @@
// //
// Server functions must be: // Server functions must be:
// * Be an async function // * Be an async function
// * Have arguments and a return type that both implement serialize and deserialize (with serde). // * Have arguments and a return type that both implement serialize and
// deserialize (with serde).
// * Return a Result with an error type of ServerFnError // * Return a Result with an error type of ServerFnError
#[cfg(feature = "server")] #[cfg(feature = "server")]
use std::sync::Arc; use std::sync::Arc;
use dioxus::prelude::*; use dioxus::prelude::*;
#[cfg(feature = "server")]
use tokio::sync::OnceCell;
use tavern::{Adventurer, Lore, Tale};
#[cfg(feature = "server")] #[cfg(feature = "server")]
use tavern::Database; use tavern::Database;
use tavern::{Adventurer, Lore, Tale};
#[cfg(feature = "server")]
use tokio::sync::OnceCell;
@ -27,7 +25,9 @@ static BLOG_DATABASE: OnceCell<Arc<Database>> = OnceCell::const_new();
#[cfg(feature = "server")] #[cfg(feature = "server")]
async fn get_database_instance<P>(path: P) -> Result<&'static Arc<Database>, ServerFnError> async fn get_database_instance<P>(
path: P)
-> Result<&'static Arc<Database>, ServerFnError>
where P: AsRef<std::path::Path> where P: AsRef<std::path::Path>
{ {
BLOG_DATABASE.get_or_try_init(|| async BLOG_DATABASE.get_or_try_init(|| async
@ -45,8 +45,8 @@ pub async fn init_database<P>(path: P) -> Result<(), ServerFnError>
{ {
match get_database_instance(path).await match get_database_instance(path).await
{ {
Ok(_) => { Ok(()) } Ok(_) => Ok(()),
Err(e) => { Err(e) } Err(e) => Err(e)
} }
} }
@ -69,11 +69,13 @@ pub async fn get_tags() -> Result<Vec<String>, ServerFnError>
} }
#[server] #[server]
pub async fn get_blog_list(categories: Vec<String>) -> Result<Vec<Lore>, ServerFnError> pub async fn get_blog_list(categories: Vec<String>)
-> Result<Vec<Lore>, ServerFnError>
{ {
let db = get_database().await?; let db = get_database().await?;
let summaries = db.get_tales_summary(&categories).await let summaries = db.get_tales_summary(&categories)
.await
.map_err(|e| ServerFnError::new(e))?; .map_err(|e| ServerFnError::new(e))?;
Ok(summaries) Ok(summaries)
@ -85,7 +87,8 @@ pub async fn get_blog_post(slug: String) -> Result<Tale, ServerFnError>
{ {
let db = get_database().await?; let db = get_database().await?;
let tale = db.get_tale_by_slug(&slug).await let tale = db.get_tale_by_slug(&slug)
.await
.map_err(|e| ServerFnError::new(e))?; .map_err(|e| ServerFnError::new(e))?;
tale.ok_or(ServerFnError::new(format!("Post {} not found", slug))) tale.ok_or(ServerFnError::new(format!("Post {} not found", slug)))
@ -97,7 +100,8 @@ pub async fn get_author(handle: String) -> Result<Adventurer, ServerFnError>
{ {
let db = get_database().await?; let db = get_database().await?;
let author = db.get_adventurer(&handle).await let author = db.get_adventurer(&handle)
.await
.map_err(|e| ServerFnError::new(e))?; .map_err(|e| ServerFnError::new(e))?;
author.ok_or(ServerFnError::new(format!("Author {} not found.", handle))) author.ok_or(ServerFnError::new(format!("Author {} not found.", handle)))