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

522 lines
24 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;
use dioxus_router::{Route, Router, Redirect};
#[cfg(any(target_family = "wasm"))]
use dioxus_helmet::Helmet;
#[cfg(any(target_family = "wasm"))]
use dioxus_router::{Link};
#[cfg(any(target_family = "wasm"))]
use dioxus_use_storage::use_local_storage;
#[cfg(any(target_family = "wasm"))]
use serde::Deserialize;
#[cfg(target_family = "wasm")]
#[derive(Deserialize)]
struct CurrentPoem {
current: String,
}
#[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;
pub fn DioxusApp(cx: Scope) -> Element {
// use dioxus_router::Redirect;
cx.render(rsx! {
Router {
Route { to: "/", HomePage { dark_mode: true, } }
Route { to: "/poems/", PoemListPage { slug: "".to_string(), dark_mode: true, } }
Route { to: "/poems/:slug", PoemPage { slug: "".to_string(), dark_mode: true, } }
Route { to: "", PageNotFound {} }
}
})
}
#[cfg(target_family = "wasm")]
fn RoutePreviousPage(cx: Scope<PoemRequest>) -> Element {
let current = String::from(dioxus_router::use_route(cx)
.segment("slug")
.expect("Slug to know which poem we are at."));
log::info!("{}", current.clone());
let previous = current.clone();
cx.render(rsx!{
Redirect { to: "{previous}" }
})
}
#[cfg(target_family = "wasm")]
fn RouteNextPage(cx: Scope<PoemRequest>) -> Element {
let current = String::from(dioxus_router::use_route(cx)
.segment("slug")
.expect("Slug to know which poem we are at."));
let next = current.clone();
cx.render(rsx!{
Redirect { to: "{next}" }
})
}
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 mx-auto max-w-full justify-center 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"
}
})
}
fn BackToHomePage(cx: Scope) -> Element {
#[cfg(any(target_family = "windows", target_family = "unix"))]
return 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")]
return cx.render(rsx!{
Link { 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",
to: "/",
p {
"Back to the homepage"
}
}
});
}
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();
#[cfg(any(target_family = "windows", target_family = "unix"))]
return cx.render(rsx!{
a { class: "flex mx-auto max-w-full justify-center p-4 ml-2 mr-2 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: "{slug_ref}",
"{title_ref}"
}
});
#[cfg(target_family = "wasm")]
return cx.render(rsx!{
Link { class: "flex mx-auto max-w-full justify-center p-4 ml-2 mr-2 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",
to: "{slug_ref}",
div {
dangerous_inner_html: "{title_ref}",
}
}
});
}
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;
#[cfg(any(target_family = "windows", target_family = "unix"))]
return cx.render(rsx! {
2023-04-12 00:42:59 -05:00
a { href: "/?dark_mode&callback={slug_ref}",
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")]
return 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.",
}
}
},
}
}
});
}
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();
#[cfg(any(target_family = "unix", target_family = "windows"))]
return cx.render(rsx!{
a { href: "/poems/{slug_ref}",
li { class: "p-4 ml-2 mr-2 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")]
return cx.render(rsx!{
Link { to: "/poems/{slug_ref}",
li { class: "p-4 ml-2 mr-2 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 {
#[cfg(any(target_family = "unix", target_family = "windows"))]
let slug = cx.props.slug.clone().expect("Slug for dark mode redirect.");
#[cfg(target_family = "wasm")]
let slug = "".to_string();
let oldest_uri = format!("/poems/{}", get_oldest_entry());
let latest_uri = format!("/poems/{}", get_latest_entry());
let title = "A Letter to the Void".to_string();
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",
"{title}"
DarkModeButton { slug: slug, dark_mode: dark_mode }
}
}
div { class: "flex p-4 ml-2 mr-2 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!"
br {}
"\"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."
br {}
"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 Latest Entry".to_string(), slug: latest_uri }
NavigationButton { title: "See Oldest Entry".to_string(), slug: oldest_uri }
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 }
}
})
}
})
}
pub fn PoemPage(cx: Scope<PoemRequest>) -> Element {
#[cfg(any(target_family = "unix", target_family = "windows"))]
let slug = cx.props.slug.clone();
#[cfg(target_family = "wasm")]
let slug = String::from(
dioxus_router::use_route(cx)
.segment("slug")
.expect("No slug specified."),
);
let mut oldest_uri = get_oldest_entry();
if oldest_uri == slug.to_string() {
oldest_uri = format!("/poems/{slug}#");
}
let previous_uri = match get_previous_entry(slug.clone()) {
Some(p) => format!("/poems/{p}"),
None => format!("/poems/{slug}#"),
};
let next_uri = match get_next_entry(slug.clone()) {
Some(p) => format!("/poems/{p}"),
None => format!("/poems/{slug}#"),
};
let mut latest_uri = get_latest_entry();
if latest_uri == slug.to_string() {
latest_uri = format!("{slug}#");
}
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 {}
}
}
})
}
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{
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.");
2023-04-12 00:42:59 -05:00
let callback = format!("/poems/{slug}");
let dark_mode = cx
.props
.dark_mode
.clone()
.expect("Dark mode prop not passed.");
let title = cx.props.title.clone().expect("No title specified.");
#[cfg(any(target_family = "unix", target_family = "windows"))]
return cx.render(rsx!{
span { class: "p-4 ml-2 mr-2 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;"
2023-04-12 00:42:59 -05:00
DarkModeButton { slug: callback, dark_mode: dark_mode }
}
}
});
#[cfg(target_family = "wasm")]
return cx.render(rsx!{
span { class: "p-4 ml-2 mr-2 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",
div { dangerous_inner_html: "{title}", }
DarkModeButton { slug: callback, dark_mode: dark_mode }
}
}
});
}
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.");
#[cfg(any(target_family = "unix", target_family = "windows"))]
return cx.render(rsx! {
div { class: "flex p-2 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-2 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: "font-nerd flex flex-col space-y-4 py-4", "{content}{creation_date}"
}
}
}
});
#[cfg(target_family = "wasm")]
return cx.render(rsx! {
div { class: "flex p-2 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-2 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: "font-nerd flex flex-col space-y-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
}
})
}
}