info-werefox-cafe/void-fe/src/lib.rs

483 lines
26 KiB
Rust
Raw Normal View History

//! # 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 std::collections::VecDeque;
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<bool>,
}
#[derive(PartialEq, Props)]
pub struct HomeProps {
pub dark_mode: bool,
}
#[derive(PartialEq, Props)]
struct PoemData {
title: Option<String>,
content: Option<String>,
creation_date: Option<String>,
slug: Option<String>,
dark_mode: Option<bool>,
}
#[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: "/poems/: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<HomeProps>) -> 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-xl text-center",
"A Letter to the Void&nbsp;"
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.",
}
}
},
}
}
}
}
div { class: "flex p-4 ml-6 mr-6 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: "text-lg text-center",
"Welcome, and I hope you enjoy your stay!"
"\"A Letter to the Void\" is a passion project of mine in which I wrote poems about my past life experiences, present, and hopes for the future throughout my transition."
"The topics range from my feelings through transitioning (of course), past abuse, mental health exploration, and an overall journey to grow and become a better creature."
br {}
"I hope you enjoy the time you spend here, and sincerely, thank you."
br {}
br {}
"🖤 Alice Icehart Werefox"
}
}
div { class: "flex mx-auto max-w-full justify-center",
a { 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",
href: "/poems/latest",
div { class: "",
p { class: "text-xl text-center",
"See Latest Entry"
}
}
}
}
div { class: "flex mx-auto max-w-full justify-center",
a { 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",
href: "/poems/oldest",
div { class: "",
p { class: "text-xl text-center",
"See Oldest Entry"
}
}
}
}
div { class: "flex mx-auto max-w-full justify-center",
a { 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",
href: "/poems",
div { class: "",
p { class: "text-xl text-center",
"See All Entries"
}
}
}
}
div { class: "flex p-4 text-md text-center 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
}
pub fn get_oldest_entry() -> String {
let mut poem_list = VecDeque::from(get_poem_list());
poem_list.pop_front().expect("There is an entry in this list of poems.").1
}
pub fn get_latest_entry() -> String {
let mut poem_list = get_poem_list();
poem_list.pop().expect("There is an entry in this list of poems.").1
}
/// Renders the app and returns the rendered Element.
pub fn PoemListPage(cx: Scope<HomeProps>) -> 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&nbsp;"
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.",
}
}
},
}
}
}
}
a { class: "flex 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",
href: "/",
div { class: "mx-auto max-w-full justify-center",
p { class: "text-lg text-center",
"Back to the homepage"
}
}
}
PoemList {}
a { class: "flex 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",
href: "/",
div { class: "mx-auto max-w-full justify-center",
p { class: "text-lg text-center",
"Back to the homepage"
}
}
}
div { class: "flex p-4 text-md text-center 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 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<PoemRequest>) -> 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 { class: "flex 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",
href: "/",
div { class: "mx-auto max-w-full justify-center",
p { class: "text-lg text-center",
"Back to the homepage"
}
}
}
GetPoem { slug: slug, dark_mode: dark_mode }
a { class: "flex 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",
href: "/",
div { class: "mx-auto max-w-full justify-center",
p { class: "text-lg text-center",
"Back to the homepage"
}
}
}
div { class: "p-4 text-md text-center 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"
}
}
}
})
}
#[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",
"Back to the homepage"
}
}
}
GetPoem { slug: slug, dark_mode: dark_mode }
div { class: "p-4 text-md text-center 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"
}
}
}
})
}
#[cfg(any(target_family = "unix", target_family = "windows"))]
fn PoemButton(cx: Scope<PoemData>) -> 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: "/poems/{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-xl text-center", "{title}" }
}
}
})
}
#[cfg(target_family = "wasm")]
fn PoemButton(cx: Scope<PoemData>) -> 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: "/poems/{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<PoemData>) -> 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("<br>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::<Vec<&str>>().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<PoemData>) -> 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: "p-4 ml-4 mr-4 flex text-xl text-center 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",
span { class: "flex flex-row align-middle mx-auto max-w-full justify-center",
"{title}&nbsp;"
a { class: "",
href: "/poems/{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<PoemData>) -> 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 ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light p-4",
dangerous_inner_html: "{title}&nbsp;",
a { class: "",
href: "/poems/{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<PoemData>) -> 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<PoemData>) -> 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<PoemData>) -> 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 }
div { class: "flex p-4 ml-6 mr-6 mx-auto max-w-full justify-center",
details { class: "group p-4 max-w-fit space-y-4 bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark ring-4 ring-alice-werefox-red-dark dark:ring-alice-werefox-red text-alice-werefox-red-dark dark:text-alice-werefox-red-light",
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() }
}
}
}
})
}
}