Adding components and assets.

Commiting to try the library with the target dioxus project.
This commit is contained in:
2025-09-06 15:05:58 -04:00
parent e6f3ff3c1e
commit 9679578977
10 changed files with 743 additions and 38 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "bard"
version = "0.0.2"
version = "0.0.3"
edition = "2024"
description = "Dioxus components that will display a Tavern blogging system Blog."
repository = "/CyberMages/tavern"
@ -10,8 +10,10 @@ readme = "README.md"
license = "Apache-2.0"
[dependencies]
dioxus = { version = "*", features = ["router", "fullstack"], optional = true }
tavern = { version = "0.2.4", path = "../tavern", registry = "cybermages", optional = true}
tokio = { version = "1.0", features = ["full"], optional = true }
[features]
default = ["tavern"]
server = ["tavern/database"]
default = ["tavern", "dioxus/web"]
server = ["tavern/database", "dioxus/server", "tokio"]

19
bard/assets/css/blog.css Normal file
View File

@ -0,0 +1,19 @@
.blog_style
{
display: flex;
width: 100%;
height: 100%;
background: var(--bg-color);
padding: 50px 0px;
justify-content: center;
container-name: site;
container-type: inline-size;
.page_content
{
display: flex;
width: 67%;
container-name: page;
container-type: inline-size;
}
}

View File

@ -0,0 +1,230 @@
.blog_style
{
display: flex;
width: 100%;
height: 100%;
background: var(--bg-color);
padding: 50px 0px;
justify-content: center;
container-name: site;
container-type: inline-size;
.page_content
{
display: flex;
width: 67%;
container-name: page;
container-type: inline-size;
}
}
.blog_nav
{
width: 15%;
padding: 0px 20px;
h3
{
margin-bottom: 10px;
}
ul
{
list-style: none;
li
{
}
}
@container site (max-width: 1230px)
{
display: none;
}
.social
{
/* Hide .social until the font is loaded */
display: flex;
align-items: center;
gap: 15px;
margin: 25px 0;
font-family: cm_social;
a
{
color: var(--text-color);
font-size: 46px;
text-decoration: none;
background: transparent;
width: 65px;
height: 65px;
border-radius: 50%;
display: flex;
align-items: flex-end;
justify-content: center;
transition: 0.4s ease;
border: 4px solid var(--text-color);
&:hover
{
color: var(--accent-color);
border: 4px solid var(--accent-color);
transform: translateY(-7px);
}
}
}
}
}
.blog_item_area
{
.blog_list
{
list-style: none;
.blog_item
{
margin-bottom: 50px;
h1
{
color: var(--accent-color);
}
h4
{
margin: 10px 0px;
}
p
{
display: block;
}
}
}
}
.blog_article
{
h1
{
color: var(--accent-color);
}
h4
{
margin-top: 10px;
margin-bottom: 30px;
}
p
{
margin-bottom: 20px;
}
.embeded_video
{
margin-top: 50px;
display: flex;
align-content: center;
justify-content: center;
iframe
{
box-shadow: 0 0 80px var(--accent-color);
}
}
}
.tag_style
{
display: block;
.tag_list
{
list-style: none;
.tag_item
{
display: inline-block;
margin: 5px 5px 0px 0px;
a
{
display: block;
padding: 0px 4px;
text-decoration: none;
border: 2px solid var(--text-color);
border-radius: 5px;
color: var(--text-color);
transition: 0.1s ease-in;
&:hover
{
color: var(--accent-color);
border: 2px solid var(--accent-color);
transform: translateY(-2px);
}
}
}
}
}
.easy
{
background-color: #248721;
}
.medium
{
background-color: #d6a318;
}
.hard
{
background-color: #d92121;
}
.social
{
background: transparent;
font-family: cm_social;
margin: 15px 0px;
a
{
color: var(--text-color);
font-size: 30px;
text-decoration: none;
background: transparent;
width: 45px;
height: 45px;
border-radius: 50%;
display: flex;
align-items: flex-end;
justify-content: center;
transition: 0.4s ease;
border: 4px solid var(--text-color);
&:hover
{
color: var(--accent-color);
border: 4px solid var(--accent-color);
transform: translateY(-7px);
}
}
}

View File

@ -0,0 +1,27 @@
.blog_list_style
{
.blog_list
{
list-style: none;
.blog_item
{
margin-bottom: 50px;
h1
{
color: var(--accent-color);
}
h4
{
margin: 10px 0px;
}
p
{
display: block;
}
}
}
}

View File

@ -0,0 +1,116 @@
.blog_style
{
display: flex;
width: 100%;
height: 100%;
background: var(--bg-color);
padding: 50px 0px;
justify-content: center;
container-name: site;
container-type: inline-size;
.page_content
{
display: flex;
width: 67%;
container-name: page;
container-type: inline-size;
}
}
.blog_nav_style
{
width: 15%;
padding: 0px 20px;
h3
{
margin-bottom: 10px;
}
ul
{
list-style: none;
li
{
}
}
@container site (max-width: 1230px)
{
display: none;
}
.social
{
/* Hide .social until the font is loaded */
display: flex;
align-items: center;
gap: 15px;
margin: 25px 0;
font-family: cm_social;
a
{
color: var(--text-color);
font-size: 22px;
text-decoration: none;
background: transparent;
width: 35px;
height: 35px;
border-radius: 50%;
display: flex;
align-items: flex-end;
justify-content: center;
transition: 0.4s ease;
border: 4px solid var(--text-color);
&:hover
{
color: var(--accent-color);
border: 4px solid var(--accent-color);
transform: translateY(-7px);
}
}
}
.tag_style
{
display: block;
.tag_list
{
display: inline-flex;
flex-direction: column;
list-style: none;
.tag_item
{
display: inline-block;
margin: 5px 5px 0px 0px;
a
{
display: inline-flex;
padding: 0px 4px;
text-decoration: none;
border: 2px solid var(--text-color);
border-radius: 5px;
color: var(--text-color);
transition: 0.1s ease-in;
&:hover
{
color: var(--accent-color);
border: 2px solid var(--accent-color);
transform: translateY(-2px);
}
}
}
}
}
}

View File

@ -0,0 +1,117 @@
.blog_post_style
{
h1
{
color: var(--accent-color);
}
h4
{
margin-top: 10px;
margin-bottom: 30px;
}
p
{
margin-bottom: 20px;
}
.embeded_video
{
margin-top: 50px;
display: flex;
align-content: center;
justify-content: center;
iframe
{
box-shadow: 0 0 80px var(--accent-color);
}
}
.tag_style
{
display: block;
.tag_list
{
list-style: none;
.tag_item
{
display: inline-block;
margin: 5px 5px 0px 0px;
a
{
display: block;
padding: 0px 4px;
text-decoration: none;
border: 2px solid var(--text-color);
border-radius: 5px;
color: var(--text-color);
transition: 0.1s ease-in;
&:hover
{
color: var(--accent-color);
border: 2px solid var(--accent-color);
transform: translateY(-2px);
}
}
}
}
}
.easy
{
background-color: #248721;
}
.medium
{
background-color: #d6a318;
}
.hard
{
background-color: #d92121;
}
.social
{
background: transparent;
font-family: cm_social;
margin: 15px 0px;
a
{
color: var(--text-color);
font-size: 30px;
text-decoration: none;
background: transparent;
width: 45px;
height: 45px;
border-radius: 50%;
display: flex;
align-items: flex-end;
justify-content: center;
transition: 0.4s ease;
border: 4px solid var(--text-color);
&:hover
{
color: var(--accent-color);
border: 4px solid var(--accent-color);
transform: translateY(-7px);
}
}
}

View File

@ -0,0 +1,54 @@
.blog_tag_style
{
display: block;
.tag_list
{
list-style: none;
.tag_item
{
display: inline-block;
margin: 5px 5px 0px 0px;
a
{
display: block;
padding: 0px 4px;
text-decoration: none;
border: 2px solid var(--text-color);
border-radius: 5px;
color: var(--text-color);
transition: 0.1s ease-in;
&:hover
{
color: var(--accent-color);
border: 2px solid var(--accent-color);
transform: translateY(-2px);
}
}
}
}
.none
{
background-color: var(--bg-color);
}
.easy
{
background-color: #248721;
}
.medium
{
background-color: #d6a318;
}
.hard
{
background-color: #d92121;
}
}

View File

@ -1,10 +1,9 @@
use dioxus::prelude::*;
use tavern::Lore;
use tavern::{Adventurer, Legend, Lore, Tale};
use crate::components::social::*;
use crate::page::Page;
use crate::server::*;
@ -15,7 +14,6 @@ const POST_CSS: Asset = asset!("/assets/css/blog_post.css");
const TAG_CSS: Asset = asset!("/assets/css/blog_tag.css");
#[derive(Clone, Copy, PartialEq)]
pub enum TagStyle
{
@ -85,6 +83,7 @@ pub fn TagList(children: Element) -> Element
}
/*
#[component]
pub fn BlogAuthor() -> Element
{
@ -210,20 +209,32 @@ pub fn BlogNav() -> Element
}
}
}
*/
#[component]
pub fn BlogPost(children: Element) -> Element
pub fn PostHeaderAuthor(name: String, handle: String, profile_link: String) -> Element
{
rsx!
{
document::Link { rel: "stylesheet", href: POST_CSS }
h4 { "Author: ", a { href: "{profile_link}", "{name} @{handle}" } }
}
}
article
#[component]
pub fn PostHeader(title: String, author: String, tags: Vec<String>) -> Element
{
let Ok(author) = use_server_future(move || get_author(author.clone())) else
{
return rsx! { p { "Failed to load author." } }
};
rsx!
{
h1 { "{title}" }
TagList
{
class: "blog_post_style",
h1 { "Leet Code: Let's Get Started" }
TagList
for category in tags
{
TagItem
{
@ -231,7 +242,96 @@ pub fn BlogPost(children: Element) -> Element
style: TagStyle::Regular
}
}
h4 { "Author: ", a { href: "https://cybermages.tech", "Jason Smith" } }
}
match &*author.read()
{
Some(Ok(author)) =>
{
rsx!
{
PostHeaderAuthor
{
name: author.name.clone(),
handle: author.handle.clone(),
profile_link: author.legend.profile.clone()
}
}
}
Some(Err(e)) =>
{
rsx!
{
p { "Unable to show post header." }
p { "{e}" }
}
}
None =>
{
rsx!
{
p { "Loading..." }
}
}
}
}
}
#[component]
pub fn BlogPost(slug: String, children: Element) -> Element
{
// 1. Fetch the blog post using the slug.
let Ok(post) = use_server_future(move || get_blog_post(slug.clone())) else
{
return rsx! { p { "Failed to load post." } }
};
// Then build the component.
rsx!
{
document::Link { rel: "stylesheet", href: POST_CSS }
article
{
class: "blog_post_style",
match &*post.read()
{
Some(Ok(post)) =>
{
rsx!
{
PostHeader
{
title: post.lore.title.clone(),
author: post.lore.author.clone(),
tags: post.lore.tags.clone()
}
div { dangerous_inner_html: "{post.story}" }
}
}
Some(Err(e)) =>
{
rsx!
{
p { "Unable to show desired post." }
p { "{e}" }
}
}
None =>
{
rsx!
{
p { "Loading..." }
}
}
}
{children}
}
}

View File

@ -2,20 +2,17 @@
mod info;
#[cfg(not(feature = "server"))]
mod components;
mod server;
#[cfg(feature = "server")]
use tavern::Database;
pub use crate::info::{get_name, get_version};
#[cfg(not(feature = "server"))]
pub use crate::components::*;
#[cfg(feature = "server")]
pub async fn init_database<P>(path: P)
-> Result<std::sync::Arc<Database>, Box<dyn std::error::Error>>
where P: AsRef<std::path::Path>
{
let db = Database::open(path).await?;
Ok(std::sync::Arc::new(db))
}
pub use crate::server::*;

View File

@ -1,27 +1,70 @@
use dioxus::prelude::*;
// Remember that Server functions parameters must not be references.
// They are coming from potentially other computers.
#[cfg(feature = "server")]
use std::sync::Arc;
use crate::{Database, Lore, Tale};
use dioxus::prelude::*;
#[cfg(feature = "server")]
use tokio::sync::OnceCell;
use tavern::{Adventurer, Lore, Tale};
#[cfg(feature = "server")]
use tavern::Database;
#[cfg(feature = "server")]
static BLOG_DATABASE: OnceCell<Arc<Database>> = OnceCell::const_new();
#[cfg(feature = "server")]
async fn get_database_instance() -> &'static Arc<Database>
{
BLOG_DATABASE.get_or_init(|| async {
let db = Database::open("tavern.db")
.await
.expect("Failed to open database");
Arc::new(db)
}).await
}
#[server]
pub async fn get_blog_list() -> Result<Vec<Lore>, ServerFnError> {
let db = server_context()
.get::<Arc<Database>>()
.ok_or_else(|| ServerFnError::ServerError("Database context not available".to_string()))?;
pub async fn get_blog_list(categories: Vec<String>) -> Result<Vec<Lore>, ServerFnError>
{
let db = get_database_instance().await;
let summaries = db.get_tales_summary(&[]).await
.map_err(|e| ServerFnError::ServerError(e.to_string()))?;
let summaries = db.get_tales_summary(&categories).await
.map_err(|e| ServerFnError::new(e))?;
Ok(summaries)
}
#[server]
pub async fn get_blog_post(slug: String) -> Result<Tale, ServerFnError> {
let db = server_context()
.get::<Arc<Database>>()
.ok_or_else(|| ServerFnError::ServerError("Database context not available".to_string()))?;
pub async fn get_blog_post(slug: String) -> Result<Tale, ServerFnError>
{
let db = get_database_instance().await;
let tale = db.get_tale_by_slug(&slug).await
.map_err(|e| ServerFnError::ServerError(e.to_string()))?;
.map_err(|e| ServerFnError::new(e))?;
tale.ok_or(ServerFnError::ServerError("Post not found".into()))
tale.ok_or(ServerFnError::new(format!("Post {} not found", slug)))
}
#[server]
pub async fn get_author(handle: String) -> Result<Adventurer, ServerFnError>
{
let db = get_database_instance().await;
let author = db.get_adventurer(&handle).await
.map_err(|e| ServerFnError::new(e))?;
author.ok_or(ServerFnError::new(format!("Author {} not found.", handle)))
}