feat(dioxus): added backend offline error handling, added titles to each page, and now using stylesheet as it removed content preloading before css

This commit is contained in:
darkicewolf50 2025-05-20 17:18:20 -06:00
parent 0ec9ee66f2
commit bff6a997be
14 changed files with 269 additions and 94 deletions

1
Cargo.lock generated
View File

@ -796,6 +796,7 @@ dependencies = [
"dioxus-signals",
"dioxus-web",
"manganis",
"serde",
"warnings",
]

View File

@ -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]

View File

@ -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%);
}

View File

@ -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}" }
}

View File

@ -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 {

View File

@ -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 }
}
}
}

View File

@ -49,12 +49,10 @@ pub enum Route {
PageNotFound { route: Vec<String> },
}
const NOT_FOUND_CSS: Asset = asset!("/assets/styling/notFound.css");
#[component]
fn PageNotFound(route: Vec<String>) -> 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<String>) -> 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<Vec<String>, 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())
// }

View File

@ -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::<Route> {}
}
}
#[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);
}

View File

@ -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(|| {
"<h1>This is a blog #blog_title</h1>
<p>
let blog_content = use_signal(move || {
"<h1>This is a blog #blog_title</h1><p>
In blog #{blog_title}, we show how the Dioxus router works and
how URL parameters can be passed as props to our route components.
</p>"
.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: "<p>
// In blog #{blog_title}, we show how the Dioxus router works and
// how URL parameters can be passed as props to our route components.
// </p>"
// .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<u8> = use_signal(|| 10);
let blogs_resource: Resource<Vec<BlogPreview>> =
use_resource(move || async move { get_blogs_preview(_num_limit(), page_num).await });
let blogs_resource: Resource<Vec<BlogContent>> = 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::<u8>().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<BlogPreview> {
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<Vec<BlogContent>, 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<BlogPreview>
let blogs: Vec<BlogPreview> = serde_json::from_value(
// Extract the "Blogs" array and deserialize it into Vec<BlogContent>
let blogs: Vec<BlogContent> = 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<BlogPreview> {
.unwrap_or_default();
// tracing::info!("{:?}", blogs);
blogs
Ok(blogs)
}

View File

@ -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",
}
}
}

View File

@ -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" }

View File

@ -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" }

View File

@ -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" }

View File

@ -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}",