diff --git a/Cargo.lock b/Cargo.lock index 0b2684e..869a952 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -796,6 +796,7 @@ dependencies = [ "dioxus-signals", "dioxus-web", "manganis", + "serde", "warnings", ] diff --git a/Cargo.toml b/Cargo.toml index db52898..7df6739 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,9 @@ web = ["dioxus/web"] desktop = ["dioxus/desktop"] # The feature that are only required for the mobile = ["dioxus/mobile"] build target should be optional and only enabled in the mobile = ["dioxus/mobile"] feature mobile = ["dioxus/mobile"] +ssg = ["dioxus/fullstack"] +# "fullstack" [profile] [profile.wasm-dev] diff --git a/assets/styling/blog.css b/assets/styling/blog.css index a0f0c8b..3f97a1c 100644 --- a/assets/styling/blog.css +++ b/assets/styling/blog.css @@ -7,30 +7,122 @@ margin-top: 50px; } +#blog a:hover { + color: #91a4d2; +} + #blogs { display: flex; flex-direction: column; - justify-content: center; - align-items: center; - height: 80svh; + min-height: 80svh; } #blogs h1 { border-bottom: var(--underlineTitle); border-radius: var(--underlineTitleBorderRadius); + margin: 1svh 2svw; + padding: 1svh 2svw; + display: flex; } #blogs button { + /* background-color: transparent; */ background-color: var(--card-background-color); border-radius: var(--card-border-radius); border: none; - color: #ffffff; - font-size: xx-large; - padding: 1rem; + color: inherit; + font-size: x-large; + padding: 0.5rem; + margin: 1svh 0svw; +} + +#blogs p { + margin: 0px; + padding: 0px; } #blogs a { text-decoration: none; + color: inherit; +} + +#blogs a:hover { + color: #91a4d2; +} + +#blogs-title { + display: flex; + flex-direction: column; + padding-bottom: 1svh; +} + +#blogs-title p { + margin: 0svh 1svw; + padding: 1svh 0svw; +} + +#blog-loading { + display: flex; + flex-direction: column; + justify-self: center; + align-self: center; + justify-content: center; + align-items: center; + + background-color: var(--card-background-color); + border-radius: var(--card-border-radius); + padding: 2svh 2svw; + margin: 8svh 0svw; +} + +#blog-out-of { + display: flex; + flex-direction: column; + justify-self: center; + align-self: center; + justify-content: center; + align-items: center; + + background-color: var(--card-background-color); + border-radius: var(--card-border-radius); + padding: 2svh 2svw; + margin: 8svh 0svw; +} + +#blog-nav { + display: flex; + align-items: center; + position: relative; + + padding: 0svh 2svw; + margin-top: auto; + margin-bottom: 2svh; + background-color: var(--card-background-color); border-radius: var(--card-border-radius); } + +#blog-nav a:last-child { + margin-left: auto; +} + +#blog-nav div { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + justify-self: center; + + margin: 1svh 0svw; + padding: 0.5rem; + column-gap: 1svw; + min-height: 28px; + font-size: large; + + background-color: var(--card-background-color); + border-radius: var(--card-border-radius); + + position: absolute; + left: 50%; + transform: translateX(-50%); +} diff --git a/src/components/experience.rs b/src/components/experience.rs index b237bea..51ff2ec 100644 --- a/src/components/experience.rs +++ b/src/components/experience.rs @@ -1,6 +1,4 @@ -use dioxus::prelude::*; - -const EXPERIENCE_CSS: Asset = asset!("/assets/styling/experience.css"); +use dioxus::{document, prelude::*}; #[component] pub fn Experience(professional_jobs: bool) -> Element { @@ -10,7 +8,7 @@ pub fn Experience(professional_jobs: bool) -> Element { }; rsx! { div { class: "experience-comp", - document::Link { rel: "stylesheet", href: EXPERIENCE_CSS } + document::Stylesheet { href: asset!("/assets/styling/experience.css") } if professional_jobs { h3 { "Professional" } } else { @@ -32,7 +30,6 @@ pub fn Experience(professional_jobs: bool) -> Element { td { "{exp.start_month} - {exp.end_month}" } } tr { - // td { "" } td { "{exp.company}" } td { "{exp.location}" } } diff --git a/src/components/footer.rs b/src/components/footer.rs index 042f87f..4e476af 100644 --- a/src/components/footer.rs +++ b/src/components/footer.rs @@ -3,8 +3,6 @@ use std::collections::HashMap; use crate::helper_fun::{tech_table_lookup, TechDes}; use dioxus::prelude::*; -const ENDER_CSS: Asset = asset!("/assets/styling/ender.css"); - #[component] pub fn Ender() -> Element { // gets list of items to get @@ -16,7 +14,7 @@ pub fn Ender() -> Element { footer_info.insert(used_tech_item, *tech_table_lookup(used_tech_item)); } rsx! { - document::Link { rel: "stylesheet", href: ENDER_CSS } + document::Stylesheet { href: asset!("/assets/styling/ender.css") } footer { p { "Brock Tomlinson © 2025" } div { diff --git a/src/components/techs.rs b/src/components/techs.rs index 45766f8..07c978f 100644 --- a/src/components/techs.rs +++ b/src/components/techs.rs @@ -1,8 +1,6 @@ use crate::helper_fun::tech_table_lookup; use dioxus::prelude::*; -const TECHS_CSS: Asset = asset!("/assets/styling/techs.css"); - #[component] pub fn TechCard(tech_props: &'static str) -> Element { let props_tech = tech_table_lookup(tech_props); @@ -19,14 +17,12 @@ pub fn TechCard(tech_props: &'static str) -> Element { #[component] pub fn TechCat(cat: &'static str, tech_vec: Vec<&'static str>) -> Element { rsx! { - document::Link { rel: "stylesheet", href: TECHS_CSS } + document::Stylesheet { href: asset!("/assets/styling/techs.css") } div { class: "tech-cat", h3 { "{cat}" } div { class: "tech-row", for tech in tech_vec { - TechCard { - tech_props: tech, - } + TechCard { tech_props: tech } } } } diff --git a/src/lib.rs b/src/lib.rs index eb6e9c8..b40b3ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,12 +49,10 @@ pub enum Route { PageNotFound { route: Vec }, } -const NOT_FOUND_CSS: Asset = asset!("/assets/styling/notFound.css"); - #[component] fn PageNotFound(route: Vec) -> Element { rsx! { - document::Link { rel: "stylesheet", href: NOT_FOUND_CSS } + document::Stylesheet { href: asset!("/assets/styling/notFound.css") } div { id: "not-found", h1 { "Page not found" } p { "We are terribly sorry, but the page you requested doesn't exist." } @@ -64,3 +62,15 @@ fn PageNotFound(route: Vec) -> Element { } } } + +// The server function at the endpoint "static_routes" will be called by the CLI to generate the list of static +// routes. You must explicitly set the endpoint to `"static_routes"` in the server function attribute instead of +// the default randomly generated endpoint. +// #[server(endpoint = "static_routes", output = server_fn::codec::Json)] +// async fn static_routes() -> Result, ServerFnError> { +// // The `Routable` trait has a `static_routes` method that returns all static routes in the enum +// Ok(Route::static_routes() +// .iter() +// .map(ToString::to_string) +// .collect()) +// } diff --git a/src/main.rs b/src/main.rs index 4327793..2b25617 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use personal_site::Route; // The macro returns an `Asset` type that will display as the path to the asset in the browser or a local path in desktop bundles. const FAVICON: Asset = asset!("/assets/favicon.ico"); // The asset macro also minifies some assets like CSS and JS to make bundled smaller -const MAIN_CSS: Asset = asset!("/assets/styling/main.css"); +// const MAIN_CSS: Asset = asset!("/assets/styling/main.css"); fn main() { // The `launch` function is the main entry point for a dioxus app. It takes a component and renders it with the platform feature @@ -26,10 +26,34 @@ fn App() -> Element { // In addition to element and text (which we will see later), rsx can contain other components. In this case, // we are using the `document::Link` component to add a link to our favicon and main CSS file into the head of our app. document::Link { rel: "icon", href: FAVICON } - document::Link { rel: "stylesheet", href: MAIN_CSS } + // document::Link { rel: "stylesheet", href: MAIN_CSS } + document::Stylesheet { href: asset!("/assets/styling/main.css") } // The router component renders the route enum we defined above. It will handle synchronization of the URL and render // the layouts and components for the active route. Router:: {} } } + +#[cfg(feature = "ssg")] +fn main() { + use dioxus_fullstack::{incremental::IncrementalRendererConfig, prelude::*}; + + LaunchBuilder::new() + .with_cfg( + ServeConfig::builder() + .incremental( + IncrementalRendererConfig::new() + .static_dir( + std::env::current_exe() + .unwrap() + .parent() + .unwrap() + .join("public"), + ) + .clear_cache(false), + ) + .enable_out_of_order_streaming(), + ) + .launch(app); +} diff --git a/src/views/blog.rs b/src/views/blog.rs index 263c59e..d22c4fb 100644 --- a/src/views/blog.rs +++ b/src/views/blog.rs @@ -1,11 +1,9 @@ -use std::thread::Scope; - use crate::Route; -use dioxus::{html::script::r#async, logger::tracing, prelude::*}; +use dioxus::{logger::tracing, prelude::*}; use reqwest; use serde::{Deserialize, Serialize}; -const BLOG_CSS: Asset = asset!("/assets/styling/blog.css"); +// const BLOG_CSS: Asset = asset!("/assets/styling/blog.css"); /// The Blog page component that will be rendered when the current route is `[Route::Blog]` /// @@ -13,17 +11,32 @@ const BLOG_CSS: Asset = asset!("/assets/styling/blog.css"); /// re-run and the rendered HTML will be updated. #[component] pub fn Blog(blog_title: String) -> Element { - let blog_content = use_signal(|| { - "

This is a blog #blog_title

-

+ let blog_content = use_signal(move || { + "

This is a blog #blog_title

In blog #{blog_title}, we show how the Dioxus router works and how URL parameters can be passed as props to our route components.

" .to_string() }); - rsx! { - document::Link { rel: "stylesheet", href: BLOG_CSS } + // let blog_content = use_signal(move || BlogContent { + // blog_file_name: "blog_title".to_string(), + // blog_title: "This is a blog #blog_title".to_string(), + // date_last_edit: "2025-5-20".to_string(), + // tags: "#test".to_string(), + // html_blog_content: "

+ // In blog #{blog_title}, we show how the Dioxus router works and + // how URL parameters can be passed as props to our route components. + //

" + // .to_string(), + // }); + let blog = blog_content(); + // let last_edit = &blog.date_last_edit; + // let tag = &blog.tags; + + rsx! { + document::Stylesheet { href: asset!("/assets/styling/blog.css") } + document::Title { "Brock Tomlinson - {blog_title}" } div { id: "blog", // Content @@ -44,6 +57,13 @@ pub fn Blog(blog_title: String) -> Element { // } // span { " <---> " } Link { to: Route::Blogs { page_num: 0 }, "Go Back" } + // div { dangerous_inner_html: *&blog.html_blog_content.as_str(), + // h1 { "{blog_title}" } + // div { + // p { "{&blog.tags}" } + // p { "{&blog.date_last_edit}" } + // } + // } div { dangerous_inner_html: blog_content } } } @@ -63,20 +83,26 @@ async fn get_blog(blog_name: String) { pub fn Blogs(page_num: u32) -> Element { let mut _num_limit: Signal = use_signal(|| 10); - let blogs_resource: Resource> = - use_resource(move || async move { get_blogs_preview(_num_limit(), page_num).await }); + let blogs_resource: Resource> = use_resource(move || async move { + get_blogs_preview(_num_limit(), page_num) + .await + .unwrap_or_else(|_| vec![]) + }); rsx! { - document::Link { rel: "stylesheet", href: BLOG_CSS } + document::Stylesheet { href: asset!("/assets/styling/blog.css") } div { id: "blogs", - title { "Blogs" } - h1 { "Blogs" } - p { - "This is a collection of blog posts, ranging from tutorials, technologies I found interesting, and opinion pieces" - } - Link { to: Route::Home {}, - button { "Home" } + document::Title { "Brock Tomlinson - Blogs" } + div { id: "blogs-title", + h1 { "Blogs" } + p { + "This is a collection of blog posts, ranging from tutorials, technologies I found interesting, and opinion pieces" + } + p { "These blogs are my opinion and mine alone" } } + // Link { to: Route::Home {}, + // button { "Home" } + // } // Link { // to: Route::Blog { // blog_title: "Test_Blog".to_string(), @@ -85,61 +111,100 @@ pub fn Blogs(page_num: u32) -> Element { // } div { if let Some(blogs) = &*blogs_resource.read() { - for blog in blogs.iter() { + if blogs.len() > 0 { + for blog in blogs.iter() { - Link { - to: Route::Blog { - blog_title: blog.blog_file_name.clone(), - }, - div { dangerous_inner_html: blog.html_preview.as_str() } + Link { + to: Route::Blog { + blog_title: blog.blog_file_name.clone(), + }, + div { dangerous_inner_html: blog.html_blog_content.as_str(), + h1 { "{blog.blog_title}" } + div { + p { "{blog.tags}" } + p { "{blog.date_last_edit}" } + } + } + } + } + } else { + div { id: "blog-out-of", + p { "No more blogs available" } + Link { to: Route::Blogs { page_num: 0 }, + button { "Go Back" } + } } } } else { - div { "Loading blogs..." } + div { id: "blog-loading", + p { "Loading blogs..." } + } } } - div { - Link { - to: Route::Blogs { - page_num: page_num + 1, - }, - "Next" - } + div { id: "blog-nav", if page_num > 0 { Link { to: Route::Blogs { page_num: page_num - 1, }, - "Go Back" + button { "<-- Go Back" } } } + div { + label { "display: " } + select { + onchange: move |event| { + tracing::info!("Change happened {:?}", event.value()); + _num_limit.set(event.value().parse::().unwrap_or(10)); + }, + option { "10" } + option { "25" } + option { "50" } + option { "100" } + } + } + Link { + to: Route::Blogs { + page_num: page_num + 1, + }, + button { "Next -->" } + } + } } } } #[derive(Deserialize, Serialize, Debug)] -struct BlogPreview { +struct BlogContent { pub blog_file_name: String, pub date_last_edit: String, - pub html_preview: String, + pub blog_title: String, + pub tags: String, + pub html_blog_content: String, } -async fn get_blogs_preview(_num_limit: u8, page_num: u32) -> Vec { - let res = reqwest::get(format!( - "http://localhost:8000/blogs/{}/{}", - _num_limit, page_num - )) - .await - .unwrap() - .text() - .await - .unwrap_or("".to_string()); +async fn get_blogs_preview( + _num_limit: u8, + page_num: u32, +) -> Result, reqwest::Error> { + let client = reqwest::Client::new(); + + let res: String = client + .get(format!( + "http://localhost:8000/blogs/{}/{}", + _num_limit, page_num + )) + .timeout(std::time::Duration::from_secs(10)) + .send() + .await? + .text() + .await?; let json: serde_json::Value = serde_json::from_str(&res).unwrap(); - // Extract the "Blogs" array and deserialize it into Vec - let blogs: Vec = serde_json::from_value( + // Extract the "Blogs" array and deserialize it into Vec + let blogs: Vec = serde_json::from_value( json.get("Blogs") .cloned() .unwrap_or(serde_json::Value::Null), @@ -147,5 +212,5 @@ async fn get_blogs_preview(_num_limit: u8, page_num: u32) -> Vec { .unwrap_or_default(); // tracing::info!("{:?}", blogs); - blogs + Ok(blogs) } diff --git a/src/views/contact.rs b/src/views/contact.rs index f5557f4..e99702a 100644 --- a/src/views/contact.rs +++ b/src/views/contact.rs @@ -1,19 +1,18 @@ use dioxus::prelude::*; const PROFESSIONAL_PHOTO_JPG: Asset = asset!("assets/professional_photo_2023.jpg"); -const CONTACT_CSS: Asset = asset!("/assets/styling/contact.css"); #[component] pub fn Contact() -> Element { rsx! { - document::Link { href: CONTACT_CSS, rel: "stylesheet" } + document::Stylesheet { href: asset!("/assets/styling/contact.css") } h2 { "Contact" } div { id: "contact", div { div { img { src: PROFESSIONAL_PHOTO_JPG, - alt: "Borck's professional photo", + alt: "Brock's professional photo", } } } diff --git a/src/views/contact_me.rs b/src/views/contact_me.rs index d396980..13ec323 100644 --- a/src/views/contact_me.rs +++ b/src/views/contact_me.rs @@ -6,8 +6,6 @@ use dioxus::prelude::*; use crate::views::Contact; -const CONTACTME_CSS: Asset = asset!("/assets/styling/contactme.css"); - #[component] pub fn ContactMe() -> Element { let mut contact_me_name = use_signal(|| String::new()); @@ -17,8 +15,8 @@ pub fn ContactMe() -> Element { let mut _error_box_message = use_signal(|| String::new()); rsx! { - document::Link { rel: "stylesheet", href: CONTACTME_CSS } - title { "Brock Tomlinson - Contact" } + document::Stylesheet { href: asset!("/assets/styling/contactme.css") } + document::Title { "Brock Tomlinson - Contact" } div { id: "ContactMe", div { h2 { "Get in Touch" } diff --git a/src/views/home.rs b/src/views/home.rs index 7c878a4..704a601 100644 --- a/src/views/home.rs +++ b/src/views/home.rs @@ -3,8 +3,6 @@ use crate::views::{Contact, Projects}; use crate::Route; use dioxus::prelude::*; -const HOME_CSS: Asset = asset!("/assets/styling/home.css"); - #[component] pub fn Home() -> Element { let languages = vec![ @@ -32,8 +30,8 @@ pub fn Home() -> Element { ]; let platforms = vec!["AWS", "Cloudflare", "Vercel", "Netlify", "Gitea", "Github"]; rsx!( - document::Link { rel: "stylesheet", href: HOME_CSS } - title { "Brock Tomlinson" } + document::Title { "Brock Tomlinson - Home" } + document::Stylesheet { href: asset!("/assets/styling/home.css") } div { div { id: "home-intro", h1 { "Hi I'm Brock" } diff --git a/src/views/navbar.rs b/src/views/navbar.rs index 7f77981..4192823 100644 --- a/src/views/navbar.rs +++ b/src/views/navbar.rs @@ -2,9 +2,6 @@ use crate::components::Ender; use crate::Route; use dioxus::prelude::*; -const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css"); -const STD_COLOUR_AND_FONTS_CSS: Asset = asset!("assets/styling/standardColoursandFonts.css"); - /// The Navbar component that will be rendered on all pages of our app since every page is under the layout. /// /// @@ -13,8 +10,8 @@ const STD_COLOUR_AND_FONTS_CSS: Asset = asset!("assets/styling/standardColoursan #[component] pub fn Navbar() -> Element { rsx! { - document::Link { rel: "stylesheet", href: NAVBAR_CSS } - document::Link { rel: "stylesheet", href: STD_COLOUR_AND_FONTS_CSS } + document::Stylesheet { href: asset!("/assets/styling/navbar.css") } + document::Stylesheet { href: asset!("assets/styling/standardColoursandFonts.css") } div { id: "navbar", Link { to: Route::Home {}, "Home" } diff --git a/src/views/projects.rs b/src/views/projects.rs index f5b7128..fee7e3f 100644 --- a/src/views/projects.rs +++ b/src/views/projects.rs @@ -5,7 +5,7 @@ use dioxus::prelude::*; pub fn Projects(#[props(default = true)] independent_page: bool) -> Element { rsx! { if independent_page { - title { "Brock Tomlinson - Projects" } + document::Title { "Brock Tomlinson - Projects" } } div { h2 { "Projects" } @@ -74,8 +74,6 @@ pub fn Projects(#[props(default = true)] independent_page: bool) -> Element { } } -const PROJECT_CARDS_CSS: Asset = asset!("/assets/styling/projectCards.css"); - #[component] pub fn ProjectCards( website_link: Option<&'static str>, @@ -87,7 +85,7 @@ pub fn ProjectCards( #[props(default = "https://picsum.photos/200")] project_img: &'static str, ) -> Element { rsx! { - document::Link { href: PROJECT_CARDS_CSS, rel: "stylesheet" } + document::Stylesheet { href: asset!("/assets/styling/projectCards.css") } div { class: "project-card", img { src: "{project_img}",