//! # Rust Letter Frontend //! //! Rendering functions for the site using [Dioxus](https://dioxuslabs.com/). #![allow(non_snake_case)] /// A module that handles the functions needed /// to render the site. pub mod void_app { // import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types pub use dioxus::prelude::*; use markdown::{self, Options}; use rust_embed::RustEmbed; #[cfg(any(target_family = "wasm"))] use dioxus_helmet::Helmet; #[cfg(any(target_family = "wasm"))] use dioxus_router::{Link, Redirect, Route, Router}; #[derive(PartialEq, Props)] pub struct PoemRequest { pub slug: String, pub dark_mode: Option, } #[derive(PartialEq, Props)] pub struct HomeProps { pub dark_mode: bool, } #[derive(PartialEq, Props)] struct PoemData { title: Option, content: Option, creation_date: Option, slug: Option, dark_mode: Option, } #[derive(RustEmbed)] #[folder = "data/poems"] pub struct Poems; #[cfg(target_family = "wasm")] pub fn DioxusApp(cx: Scope) -> Element { use dioxus_router::Redirect; let poem_list = get_poem_list(); cx.render(rsx! { Router { Route { to: "/", HomePage {} } Route { to: "/poem/:slug/", PoemPage {} } Route { to: "", PageNotFound {} } } }) } #[cfg(target_family = "wasm")] fn PageNotFound(cx: Scope) -> Element { cx.render(rsx! { p { "That page doesn't exist, sorry!" } Redirect { to: "/" } }) } /// Renders the app and returns the rendered Element. pub fn HomePage(cx: Scope) -> Element { let dark_mode = cx.props.dark_mode.clone(); cx.render(rsx!{ div { class: "min-h-screen font-nerd bg-alice-werefox-grey-light dark:bg-alice-werefox-grey", div { class: "container space-y-4 mx-auto p-4", div { class: "p-4 ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light", p { class: "flex flex-row mx-auto max-w-full justify-center text-lg text-center", "A Letter to the Void " a { class: "max-h-sm", href: "?set-dark-mode", match dark_mode { true => { rsx! { img { class: "", src: "/images/white_square_button.png", alt: "A white square button that can toggle dark mode.", } } }, false => { rsx! { img { class: "", src: "/images/black_square_button.png", alt: "A black square button that can toggle dark mode.", } } }, } } } } PoemList {} div { class: "p-4 ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light", "This site uses Mutant Standard emoji, which are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License" } } } }) } fn get_poem_list() -> Vec<(String, String)> { let mut poem_list = Vec::new(); for p in Poems::iter() { let filename = p.to_string(); let poem_content = Poems::get(&filename).expect("Found poem {filename:?}"); let mut poem_to_str = std::str::from_utf8(poem_content.data.as_ref()) .expect("Title is valid UT8.") .lines(); let title_markdown = poem_to_str.next().expect("No title specified."); let title = markdown::to_html_with_options(title_markdown, &Options::gfm()).unwrap(); let slug = String::from(filename.trim_end_matches(".md")); poem_list.push((title.clone(), slug.clone())); } log::trace!("{poem_list:?}"); poem_list } fn PoemList(cx: Scope) -> Element { let poem_list = get_poem_list(); cx.render(rsx! { ul { class: "space-y-4", poem_list.into_iter().map(|p| { rsx!{ div { PoemButton { title: p.0, slug: p.1 } } } }) } }) } #[cfg(any(target_family = "unix", target_family = "windows"))] pub fn PoemPage(cx: Scope) -> Element { let slug = String::from(cx.props.slug.clone()); let dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed."); cx.render(rsx!{ div { class: "min-h-screen font-nerd bg-alice-werefox-grey-light dark:bg-alice-werefox-grey", div { class: "container space-y-4 mx-auto p-4", a { href: "/", div { class: "p-4 ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition", p { class: "text-lg text-center", "Take me back!" } } } GetPoem { slug: slug, dark_mode: dark_mode } } } }) } #[cfg(target_family = "wasm")] fn PoemPage(cx: Scope) -> Element { let slug = String::from( dioxus_router::use_route(cx) .segment("slug") .expect("No slug specified."), ); let dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed."); cx.render(rsx!{ div { class: "min-h-screen font-nerd bg-alice-werefox-grey-light dark:bg-alice-werefox-grey", div { class: "container space-y-4 mx-auto p-4", Link { to: "/" div { class: "p-4 ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition", p { class: "text-lg text-center", "Take me back!" } } } GetPoem { slug: slug, dark_mode: dark_mode } } } }) } #[cfg(any(target_family = "unix", target_family = "windows"))] fn PoemButton(cx: Scope) -> Element { let title = cx.props.title.clone().expect("No title specified."); let slug = cx.props.slug.clone().expect("No slug specified."); let slug_ref = slug.as_str(); cx.render(rsx!{ a { href: "/poem/{slug_ref}", li { class: "p-4 ml-6 mr-6 ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition", div { class: "text-lg text-center", "{title}" } } } }) } #[cfg(target_family = "wasm")] fn PoemButton(cx: Scope) -> Element { let title = cx.props.title.clone().expect("No title specified."); let slug = cx.props.slug.clone().expect("No slug specified."); let slug_ref = slug.as_str(); cx.render(rsx!{ Link { to: "/poem/{slug_ref}", li { class: "p-4 ml-6 mr-6 ring-2 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition", div { class: "mx-auto max-w-fit flex justify-center text-lg text-center", dangerous_inner_html: "{title}" } } } }) } fn GetPoem(cx: Scope) -> Element { let dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed."); let slug = String::from(cx.props.slug.clone().expect("No slug specified.")); let filename = String::from(String::from(slug.clone()) + ".md"); let creation_date = String::from(String::from("
Written on: ") + filename.split("_").next().unwrap()); let poem_content = Poems::get(&filename).expect("Found poem {filename:?}"); let mut poem_to_str = std::str::from_utf8(poem_content.data.as_ref()) .expect("Title is valid UT8.") .lines(); let poem_title = poem_to_str.next().unwrap(); let poem_content = poem_to_str.into_iter().collect::>().join("\n"); let poem_title_to_html_string = markdown::to_html_with_options(poem_title, &Options::gfm()).unwrap(); let poem_content_to_html_string = markdown::to_html_with_options(poem_content.as_str(), &Options::gfm()).unwrap(); cx.render(rsx!{ MakePoem{ title: poem_title_to_html_string, content: poem_content_to_html_string, creation_date: creation_date, slug: slug, dark_mode: dark_mode } } ) } #[cfg(any(target_family = "unix", target_family = "windows"))] fn RenderPoemTitle(cx: Scope) -> Element { let slug = cx.props.slug.clone().expect("Slug prop was not passed."); let dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed."); let title = cx.props.title.clone().expect("No title specified."); cx.render(rsx!{ span { class: "mx-auto max-w-fit flex justify-center bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark border-4 border-alice-werefox-red-dark dark:border-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light p-4", "{title} " a { class: "max-h-sm", href: "/poem/{slug}?set-dark-mode", match dark_mode { true => { rsx! { img { class: "", src: "/images/white_square_button.png", alt: "A white square button that can toggle dark mode.", } } }, false => { rsx! { img { class: "", src: "/images/black_square_button.png", alt: "A black square button that can toggle dark mode.", } } }, } } } }) } #[cfg(target_family = "wasm")] fn RenderPoemTitle(cx: Scope) -> Element { let slug = cx.props.slug.clone().expect("Slug prop was not passed."); let dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed."); let title = cx .props .title .clone() .expect("This poem has an empty title."); cx.render(rsx!{ p { class: "mx-auto max-w-fit flex flex-row justify-center bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark border-4 border-alice-werefox-red-dark dark:border-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light p-4", dangerous_inner_html: "{title} ", a { class: "max-h-sm", href: "/poem/{slug}?set-dark-mode", match dark_mode { true => { rsx! { img { class: "", src: "/images/white_square_button.png", alt: "A white square button that can toggle dark mode.", } } }, false => { rsx! { img { class: "", src: "/images/black_square_button.png", alt: "A black square button that can toggle dark mode.", } } }, } } } }) } #[cfg(any(target_family = "unix", target_family = "windows"))] fn RenderPoemElement(cx: Scope) -> Element { let content = cx.props.content.clone().expect("No content specified."); let creation_date = cx .props .creation_date .clone() .expect("No creation date specified."); cx.render(rsx! { div { class: "font-nerd flex flex-col space-y-4 mx-4 py-4", "{content}{creation_date} " } }) } #[cfg(target_family = "wasm")] fn RenderPoemElement(cx: Scope) -> Element { let content = cx.props.content.clone().expect("No content specified."); let creation_date = cx .props .creation_date .clone() .expect("No creation date specified."); cx.render(rsx! { div { class: "font-nerd flex flex-col space-y-4 mx-4 py-4", dangerous_inner_html: "{content}{creation_date}" } }) } fn MakePoem(cx: Scope) -> Element { let slug = String::from(cx.props.slug.clone().expect("Slug prop was not passed.")); let dark_mode = cx.props.dark_mode.clone().expect("Dark mode prop not passed."); let title = cx.props.title.clone().expect("No title specified."); let creation_date = cx .props .creation_date .clone() .expect("No creation date specified."); let content = cx.props.content.clone().expect("No content specified."); cx.render(rsx!{ div { class: "flex-col space-y-4", RenderPoemTitle { title: title.clone(), slug: slug, dark_mode: dark_mode } details { class: "group mx-auto max-w-fit space-y-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark border-4 border-alice-werefox-red-dark dark:border-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light p-4", summary { class: "group-open:before:content-['Close'] before:content-['Open'] flex justify-center p-4 ring-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light hover:text-alice-werefox-blue-dark dark:hover:text-alice-werefox-blue-light hover:ring-alice-werefox-blue dark:hover:ring-alice-werefox-blue hover:animate-yip transition", } RenderPoemElement { content: content.clone(), creation_date: creation_date.clone() } } } }) } }