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

588 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 markdown::{self, Options};
use rust_embed::RustEmbed;
use std::collections::VecDeque;
#[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 DarkModeProps {
pub dark_mode: bool,
pub slug: Option<String>,
}
#[derive(PartialEq, Props)]
struct ButtonProps {
title: String,
slug: String,
}
#[derive(PartialEq, Props)]
struct PoemData {
title: Option<String>,
content: Option<String>,
creation_date: Option<String>,
slug: Option<String>,
dark_mode: Option<bool>,
}
#[derive(Props)]
struct PoemChildren<'a> {
children: Element<'a>,
}
#[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: "/" }
})
}
fn MutantStandardFooter(cx: Scope) -> Element {
cx.render(rsx!{
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-grey-dark dark:text-alice-werefox-grey-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 = "windows", target_family = "unix"))]
fn BackToHomePage(cx: Scope) -> Element {
cx.render(rsx!{
a { class: "flex justify-center p-4 text-xl text-center 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-grey-dark dark:text-alice-werefox-grey-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: "/",
p {
"Back to the homepage"
}
}
})
}
#[cfg(target_family = "wasm")]
fn BackToHomePage(cx: Scope) -> Element {
cx.render(rsx!{
Link { to: "flex justify-center p-4 text-xl text-center 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-grey-dark dark:text-alice-werefox-grey-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: "/",
p {
"Back to the homepage"
}
}
})
}
#[cfg(any(target_family = "windows", target_family = "unix"))]
fn NavigationButton(cx: Scope<ButtonProps>) -> Element {
let title = cx.props.title.clone();
let title_ref = title.as_str();
let slug = cx.props.slug.clone();
let slug_ref = slug.as_str();
cx.render(rsx!{
a { class: "flex mx-auto max-w-full justify-center p-4 ml-6 mr-6 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-grey-dark dark:text-alice-werefox-grey-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: "{slug_ref}",
"{title_ref}"
}
})
}
#[cfg(target_family = "wasm")]
fn NavigationButton(cx: Scope<ButtonProps>) -> Element {
let title = cx.props.title.clone();
let title_ref = title.as_str();
let slug = cx.props.slug.clone();
let slug_ref = slug.as_str();
cx.render(rsx!{
Link { class: "flex mx-auto max-w-full justify-center p-4 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-grey-dark dark:text-alice-werefox-grey-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",
to: slug_ref,
title_ref
}
})
}
#[cfg(any(target_family = "windows", target_family = "unix"))]
fn DarkModeButton(cx: Scope<DarkModeProps>) -> Element {
let slug = cx
.props
.slug
.clone()
.expect("Slug found to know where to redirect after dark mode setting.");
let slug_ref = slug.as_str();
let dark_mode = cx.props.dark_mode;
cx.render(rsx! {
a { href: "{slug_ref}/toggle-dark-mode",
match dark_mode {
true => {
rsx! {
img { src: "/images/white_square_button.png",
alt: "A white square button that can toggle dark mode.",
}
}
},
false => {
rsx! {
img { src: "/images/black_square_button.png",
alt: "A black square button that can toggle dark mode.",
}
}
},
}
}
})
}
#[cfg(target_family = "wasm")]
fn DarkModeButton(cx: Scope<DarkModeProps>) -> Element {
let slug = cx
.props
.slug
.clone()
.expect("Slug found to know where to redirect after dark mode setting.");
let slug_ref = slug.as_str();
let dark_mode = cx.props.dark_mode;
cx.render(rsx! {
Link { to: "{slug_ref}/toggle-dark-mode",
match dark_mode {
true => {
rsx! {
img { src: "/images/white_square_button.png",
alt: "A white square button that can toggle dark mode.",
}
}
},
false => {
rsx! {
img { 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 PoemButton(cx: Scope<ButtonProps>) -> Element {
let title = cx.props.title.clone();
let title_ref = title.as_str();
let slug = cx.props.slug.clone();
let slug_ref = slug.as_str();
cx.render(rsx!{
a { href: "/poems/{slug_ref}",
li { class: "p-4 ml-6 mr-6 text-xl text-center 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-grey-dark dark:text-alice-werefox-grey-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",
"{title_ref}"
}
}
})
}
#[cfg(target_family = "wasm")]
fn PoemButton(cx: Scope<ButtonProps>) -> Element {
let title = cx.props.title.clone();
let title_ref = title.as_str();
let slug = cx.props.slug.clone();
let slug_ref = slug.as_str();
cx.render(rsx!{
Link { to: "/poems/{slug_ref}",
li { class: "p-4 ml-6 mr-6 text-xl text-center 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-grey-dark dark:text-alice-werefox-grey-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",
dangerous_inner_html: "{title_ref}"
}
}
})
}
/// Renders the app and returns the rendered Element.
pub fn HomePage(cx: Scope<DarkModeProps>) -> Element {
let slug = cx.props.slug.clone().expect("Slug for dark mode redirect.");
// let slug_ref = slug.as_str();
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-grey-dark dark:text-alice-werefox-grey-light",
p { class: "flex flex-row mx-auto max-w-full justify-center text-xl text-center",
"A Letter to the Void&nbsp;"
DarkModeButton { slug: slug, dark_mode: 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-grey-dark dark:text-alice-werefox-grey-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"
}
}
NavigationButton { title: "See Earliest Entry".to_string(), slug: "/poems/latest".to_string() }
NavigationButton { title: "See Oldest Entry".to_string(), slug: "/poems/oldest".to_string() }
NavigationButton { title: "See All Entries".to_string(), slug: "/poems".to_string() }
MutantStandardFooter {}
}
}
})
}
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
}
pub fn get_previous_entry(slug: String) -> Option<String> {
let poem_list = get_poem_list();
poem_list.iter().enumerate().find_map(|(index, p)| {
if p.1 == slug {
if index != 0 {
Some(poem_list[index - 1].1.clone())
} else {
None
}
} else {
None
}
})
}
pub fn get_next_entry(slug: String) -> Option<String> {
let poem_list = get_poem_list();
poem_list.iter().enumerate().find_map(|(index, p)| {
if p.1 == slug {
if index != poem_list.len() - 1 {
Some(poem_list[index + 1].1.clone())
} else {
None
}
} else {
None
}
})
}
/// Renders the app and returns the rendered Element.
pub fn PoemListPage(cx: Scope<DarkModeProps>) -> Element {
let slug = cx.props.slug.clone().expect("Slug for dark mode redirect.");
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-grey-dark dark:text-alice-werefox-grey-light",
p { class: "flex flex-row mx-auto max-w-full justify-center text-lg text-center",
"A Letter to the Void&nbsp;"
DarkModeButton { slug: slug, dark_mode: dark_mode }
}
}
BackToHomePage {}
PoemList {}
BackToHomePage {}
MutantStandardFooter {}
}
}
})
}
fn PoemList(cx: Scope) -> Element {
let poem_list = get_poem_list();
cx.render(rsx! {
ul { class: "flex flex-col space-y-4",
poem_list.into_iter().map(|p| {
rsx!{
PoemButton { title: p.0, slug: p.1 }
}
})
}
})
}
#[cfg(any(target_family = "unix", target_family = "windows"))]
pub fn PoemPage(cx: Scope<PoemRequest>) -> Element {
let slug = cx.props.slug.clone();
let mut oldest_uri = "#".to_string();
if get_oldest_entry() != slug.to_string() {
oldest_uri = format!("/poems/oldest");
}
let mut previous_uri = "#".to_string();
if let Some(_) = get_previous_entry(slug.clone()) {
previous_uri = format!("/poems/previous/?current={slug}");
}
let mut next_uri = "#".to_string();
if let Some(_) = get_next_entry(slug.clone()) {
next_uri = format!("/poems/next/?current={slug}");
}
let mut latest_uri = "#".to_string();
if get_latest_entry() != slug.to_string() {
latest_uri = format!("/poems/latest");
}
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",
BackToHomePage {}
GetPoem { slug: slug, dark_mode: dark_mode }
div { class: "grid md:grid-cols-4 md:grid-rows-1 grid-cols-1 grid-rows-4 gap-y-4",
NavigationButton { title: "Oldest".to_string(), slug: oldest_uri }
NavigationButton { title: "Previous".to_string(), slug: previous_uri }
NavigationButton { title: "Next".to_string(), slug: next_uri }
NavigationButton { title: "Latest".to_string(), slug: latest_uri }
}
BackToHomePage {}
MutantStandardFooter {}
}
}
})
}
#[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",
BackToHomePage {}
GetPoem { slug: slug, dark_mode: dark_mode }
BackToHomePage {}
MutantStandardFooter {}
}
}
})
}
// 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 class = String::from("p-4 ml-6 mr-6 text-xl text-center 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-grey-dark dark:text-alice-werefox-grey-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");
// cx.render(rsx!{
// PoemButtonLink { title: title, slug: slug, classes: class }
// })
// }
// 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-grey-dark dark:text-alice-werefox-grey-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 {
// It would be good to implement some kind of "get_poem_data" or "into" functionality for the struct, so I don't have to write all this here.
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{
// data: PoemData{
// title: None,
// content: Some(poem_content_to_html_string.clone()),
// creation_date: Some(creation_date.clone()),
// slug: Some(slug.clone()),
// dark_mode: Some(dark_mode),
// },
RenderPoemTitle { title: poem_title_to_html_string, slug: slug, dark_mode: dark_mode }
RenderPoemElement { content: poem_content_to_html_string, creation_date: creation_date }
}})
}
// #[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-grey-dark dark:text-alice-werefox-grey-light",
span { class: "flex flex-row align-middle mx-auto max-w-full justify-center",
"{title}&nbsp;"
DarkModeButton { slug: slug, dark_mode: 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-grey-dark dark:text-alice-werefox-grey-light p-4",
// dangerous_inner_html: "{title}&nbsp;",
// a { class: "",
// href: "/poems/{slug}/toggle-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: "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-grey-dark dark:text-alice-werefox-grey-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-grey-dark dark:text-alice-werefox-grey-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: "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<'a>(cx: Scope<'a, PoemChildren<'a>>) -> Element {
cx.render(rsx! {
div { class: "flex-col space-y-4",
&cx.props.children
}
})
}
}