2024-03-21 20:15:54 -05:00
//! # Rust Info Site
2024-03-17 15:41:48 -05:00
//!
//! Rendering functions for the site using [Dioxus](https://dioxuslabs.com/).
#![ allow(non_snake_case) ]
pub mod components ;
pub mod utils ;
// Urls are relative to your Cargo.toml file
const _TAILWIND_URL : & str = manganis ::mg! ( file ( " public/tailwind.css " ) ) ;
/// A module that handles the functions needed
/// to render the site.
pub mod info_app {
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
pub use dioxus ::prelude ::* ;
2024-03-25 20:42:59 -05:00
use serde ::{ Deserialize , Serialize } ;
2024-03-17 15:41:48 -05:00
use crate ::components ::basic_page ::* ;
2024-03-25 20:42:59 -05:00
use crate ::components ::faq_card ::* ;
2024-03-17 15:41:48 -05:00
use crate ::components ::home_page ::* ;
use crate ::components ::identity_button ::* ;
use crate ::components ::introduction_card ::* ;
use crate ::components ::page_button ::* ;
use crate ::components ::project_card ::* ;
2024-03-21 20:15:54 -05:00
use crate ::components ::project_foldable ::* ;
2024-03-25 20:42:59 -05:00
use crate ::components ::werefox_card ::* ;
2024-03-17 15:41:48 -05:00
use crate ::utils ::prop_structs ::* ;
2024-05-01 00:32:26 -05:00
// use dioxus_router::prelude::*;
2024-03-17 15:41:48 -05:00
#[ derive(Routable, PartialEq, Clone) ]
pub enum Route {
#[ route( " / " ) ]
#[ redirect( " /:..segments " , |segments: Vec<String>| Route::Home {}) ]
Home { } ,
#[ route( " /projects " ) ]
Projects { } ,
#[ route( " /testimonials " ) ]
Testimonials { } ,
#[ route( " /hrt " ) ]
Hrt { } ,
#[ route( " /faq " ) ]
Faq { } ,
}
pub fn DioxusApp ( ) -> Element {
rsx! { Router ::< Route > { } }
}
#[ component ]
/// Renders the app and returns the rendered Element.
pub fn Home ( ) -> Element {
let identity_list = [
(
" 29 " ,
ImageProps {
src : " /emoji/18_plus.svg " . to_string ( ) ,
alt : " Over 18 emoji " . to_string ( ) ,
} ,
) ,
(
" Transfemme " ,
ImageProps {
src : " /emoji/female_symbol.svg " . to_string ( ) ,
alt : " Female symbol emoji " . to_string ( ) ,
} ,
) ,
(
" She/It " ,
ImageProps {
src : " /emoji/speech_bubble_left.svg " . to_string ( ) ,
alt : " A speech bubble emoji " . to_string ( ) ,
} ,
) ,
(
" Pansexual " ,
ImageProps {
src : " /emoji/pansexual_flag.svg " . to_string ( ) ,
alt : " Pansexual flag emoji " . to_string ( ) ,
} ,
) ,
(
" Lesbian " ,
ImageProps {
src : " /emoji/lesbian_flag.svg " . to_string ( ) ,
alt : " Lesbian flag emoji " . to_string ( ) ,
} ,
) ,
(
" Dragon " ,
ImageProps {
src : " /emoji/:alicehappy:.png " . to_string ( ) ,
alt : " Alice happy emoji " . to_string ( ) ,
} ,
) ,
] ;
let project_list = [
(
" Stuff I Do! " ,
Route ::Projects { } ,
ImageProps {
src : " /emoji/crt_prompt.svg " . to_string ( ) ,
alt : " CRT prompt emoji " . to_string ( ) ,
} ,
) ,
(
" See Testimonials! " ,
Route ::Testimonials { } ,
ImageProps {
src : " /emoji/awoo.svg " . to_string ( ) ,
alt : " Awoo emoji " . to_string ( ) ,
} ,
) ,
(
" HRT Tracker! " ,
Route ::Hrt { } ,
ImageProps {
src : " /emoji/trans_heart.png " . to_string ( ) ,
alt : " Transgender heart emoji " . to_string ( ) ,
} ,
) ,
(
" FAQ " ,
Route ::Faq { } ,
ImageProps {
src : " /emoji/red_question_mark.svg " . to_string ( ) ,
alt : " Red question mark emoji " . to_string ( ) ,
} ,
) ,
] ;
rsx! {
HomePage { page_title : " About a Werefox " , card_title : " Hi! I'm Alice Werefox! " ,
WerefoxCard { card_title : " Basic Info " . to_string ( ) , title_emoji : None ,
2024-04-01 18:50:11 -05:00
div { class : " grid grid-cols-1 gap-0 xl:grid-rows-1 xl:grid-cols-6 sm:grid-rows-2 sm:grid-cols-3 grid-rows-9 sm:gap-2 " ,
2024-03-17 15:41:48 -05:00
for e in identity_list {
IdentityButton { button_text : e . 0 , image_props : e . 1 }
}
}
}
WerefoxCard { card_title : " Welcome to my little info site! " , title_emoji : None , IntroductionCard { } }
WerefoxCard { card_title : " Neat Pages! " , title_emoji : None ,
div { class : " flex flex-col space-y-4 " ,
for e in project_list {
PageButton { button_text : e . 0 , button_route : e . 1 , image_props : e . 2 }
}
}
}
}
}
}
#[ component ]
pub fn Projects ( ) -> Element {
let project_list = [
(
" Streaming " ,
" I stream regularly! Sometimes randomizers, sometimes just a chill time with a fun game. " ,
" https://twitch.tv/alice_werefox " ,
ImageProps {
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
} ,
) ,
(
" Poetry " ,
" Sometimes, I write poetry. It's not always the happiest, but I am proud of it. It would mean a lot to me if you took a look! " ,
" https://void.werefox.cafe " ,
ImageProps {
src : " /emoji/pen.svg " . to_string ( ) ,
alt : " Pen emoji " . to_string ( ) ,
} ,
) ,
2024-03-21 20:15:54 -05:00
(
" Valentine's Day Letter " ,
" Here's a little treat I put together for Valentine's Day one year, and I decided to just keep it up all year. Feel free to take a look if you ever need a little pick-me-up. " ,
" https://letter.werefox.cafe " ,
ImageProps {
src : " /emoji/red_heart.svg " . to_string ( ) ,
alt : " Red heart emoji " . to_string ( ) ,
} ,
) ,
2024-03-17 15:41:48 -05:00
(
" Programming " ,
" Much like this site, sometimes I program things! Gotta at least try and put that Comp. Sci. degree to good use! " ,
" https://gitea.werefox.cafe/ada " ,
ImageProps {
src : " /emoji/laptop.svg " . to_string ( ) ,
alt : " Laptop emoji " . to_string ( ) ,
} ,
) ,
2024-03-21 20:15:54 -05:00
] ;
let service_list = [
2024-03-17 15:41:48 -05:00
(
2024-03-21 20:15:54 -05:00
" Nextcloud " ,
None ,
" https://cloud.werefox.cafe " ,
2024-03-17 15:41:48 -05:00
ImageProps {
2024-03-21 20:15:54 -05:00
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
} ,
) ,
(
" Gitea " ,
None ,
" https://gitea.werefox.cafe " ,
ImageProps {
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
} ,
) ,
(
" GoToSocial " ,
None ,
" https://gts.werefox.cafe " ,
ImageProps {
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
} ,
) ,
(
" Headscale " ,
Some ( " (A self-hosted tailscale server) " . to_string ( ) ) ,
" https://headscale.net/ " ,
ImageProps {
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
} ,
) ,
(
" Jellyfin " ,
None ,
" https://watch.werefox.cafe " ,
ImageProps {
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
} ,
) ,
(
" Matrix " ,
None ,
" https://matrix.werefox.cafe " ,
ImageProps {
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
} ,
) ,
(
" Navidrome " ,
None ,
" https://music.werefox.cafe " ,
ImageProps {
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
} ,
) ,
(
" Tunic Transition Tracker " ,
Some ( " (A tracker I made for a game I love) " . to_string ( ) ) ,
" https://tunic.werefox.cafe " ,
ImageProps {
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
} ,
) ,
] ;
let personal_list = [
(
" Cockpit " ,
None ,
" https://cockpit-project.org/ " ,
ImageProps {
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
} ,
) ,
(
" Dockge " ,
None ,
" https://dockge.kuma.pet/ " ,
ImageProps {
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
} ,
) ,
(
" Home Assistant " ,
None ,
" https://www.home-assistant.io/ " ,
ImageProps {
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
} ,
) ,
(
" Pi-Hole " ,
None ,
" https://pi-hole.net/ " ,
ImageProps {
src : " /emoji/twitch-logo.png " . to_string ( ) ,
alt : " Twitch logo " . to_string ( ) ,
2024-03-17 15:41:48 -05:00
} ,
) ,
] ;
rsx! {
2024-04-01 18:50:11 -05:00
BasicPage { page_title : " Some stuff I do! " ,
div { class : " rounded-lg ring-2 ring-alice-werefox-grey dark:ring-alice-werefox-grey-darker bg-alice-werefox-grey-light dark:bg-alice-werefox-grey " ,
WerefoxCard { card_title : " Personal Projects " ,
for project in project_list {
ProjectCard { project_title : project . 0 , project_description : project . 1 , project_link : project . 2 , image_props : project . 3 }
}
ProjectFoldable {
project_title : " Services " ,
project_description : " Click here for a list of the services I host. " ,
image_props : ImageProps {
src : " /emoji/crt_blue_screen.svg " . to_string ( ) ,
alt : " A CRT blue screen emoji. " . to_string ( ) ,
} ,
for service in service_list {
ProjectCard { project_title : service . 0 , project_description : service . 1 , project_link : service . 2 , image_props : service . 3 }
}
}
ProjectFoldable {
project_title : " Personal Use " ,
project_description : " Click here for a list of services I have set up for just me. " ,
image_props : ImageProps {
src : " /emoji/crt_blue_screen.svg " . to_string ( ) ,
alt : " A CRT blue screen emoji. " . to_string ( ) ,
} ,
for personal in personal_list {
ProjectCard {
project_title : personal . 0 ,
project_description : personal . 1 ,
project_link : personal . 2 ,
image_props : personal . 3
2024-03-25 20:42:59 -05:00
}
2024-03-21 20:15:54 -05:00
}
}
2024-03-17 15:41:48 -05:00
}
}
2024-04-01 18:50:11 -05:00
}
}
2024-03-17 15:41:48 -05:00
}
#[ component ]
pub fn Testimonials ( ) -> Element {
2024-04-01 18:50:11 -05:00
let mastodon_status = use_resource ( get_account_avatars ) ;
rsx! {
BasicPage { page_title : " Testimonials! " ,
WerefoxCard {
card_title : " Sometimes, people say some nice things about me. Here are some
examples ! " ,
div { class : " p-2 space-y-4 " ,
match ( mastodon_status . value ( ) ) ( ) {
Some ( account ) = > rsx! {
for a in account . unwrap ( ) {
div { class : " transition rounded-sm ring-2 ring-alice-werefox-red-dark dark:ring-alice-werefox-red hover:ring-alice-werefox-blue-dark dark:hover:ring-alice-werefox-blue 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:animate-yip " ,
a { href : format ! ( " {} " , a . url . unwrap ( ) . trim_matches ( | c | c = = '\"' ) ) ,
target : " _blank " ,
div { class : " flex flex-row min-w-full overflow-hidden " ,
div { class : " order-1 inline-block object-contain pt-4 pb-4 pl-4 " ,
// span { class: "relative w-16 h-16 sm:w-32 sm:h-32",
img {
src : format ! ( " {} " , a . avatar . unwrap ( ) . trim_matches ( | c | c = = '\"' ) ) ,
class : " object-contain h-16 rounded-md sm:h-32 " ,
alt : " "
}
// }
}
div { class : " flex items-center justify-center order-2 w-full min-h-full p-4 " ,
div { class : " text-xs text-center animate-wiggle sm:text-lg " ,
p { class : " overflow-auto " ,
{ format! ( " {} " , a . content ) }
}
{ format! ( " {} " , a . username ) }
}
}
}
}
}
}
} ,
_ = > rsx! {
div { class : " flex flex-col justify-center w-full m-auto space-y-2 align-middle " ,
img { class : " flex justify-center m-auto align-middle " ,
src : " emoji/:ablobalicebongo:.gif " ,
alt : " A gif of Alice in a blob emoji form, performing the bongocat animation. "
}
p { " Just a sec, lemme grab those for ya... " }
}
}
}
}
}
}
}
2024-03-26 14:36:08 -05:00
}
#[ server ]
2024-04-01 18:50:11 -05:00
async fn get_account_avatars ( ) -> Result < Vec < Testimonial > , ServerFnError > {
2024-03-26 14:36:08 -05:00
let client = megalodon ::generator (
megalodon ::SNS ::Mastodon ,
2024-05-01 00:32:26 -05:00
String ::from ( " https://yiff.life " ) ,
2024-03-26 14:36:08 -05:00
Some ( tokio ::fs ::read_to_string ( " access_token.key " ) . await ? ) ,
None ,
) ;
let testimonial_toml : String = tokio ::fs ::read_to_string ( " data/testimonials.toml " ) . await ? ;
2024-04-01 18:50:11 -05:00
let testimonial_accounts : TestimonialAccount =
toml ::from_str ( testimonial_toml . as_str ( ) ) . unwrap ( ) ;
let mut testimonial_avatars : Vec < Testimonial > = vec! [ ] ;
for account in testimonial_accounts . accounts {
let res = client
. search_account (
String ::from ( account . username . clone ( ) ) ,
Some ( & megalodon ::megalodon ::SearchAccountInputOptions {
following : None ,
resolve : Some ( false ) ,
limit : Some ( 1 ) ,
max_id : None ,
since_id : None ,
} ) ,
)
. await ? ;
testimonial_avatars . push ( Testimonial {
username : account . username ,
content : account . content ,
avatar : Some ( format! ( " {:?} " , res . json ( ) [ 0 ] . avatar_static ) ) ,
url : Some ( format! ( " {:?} " , res . json ( ) [ 0 ] . url ) ) ,
} )
}
Ok ( testimonial_avatars )
2024-03-26 14:36:08 -05:00
}
2024-04-01 18:50:11 -05:00
#[ derive(Deserialize, Debug) ]
2024-03-26 14:36:08 -05:00
struct TestimonialAccount {
accounts : Vec < Testimonial > ,
}
2024-04-01 18:50:11 -05:00
#[ derive(Serialize, Deserialize, Debug, Clone) ]
pub struct Testimonial {
2024-03-26 14:36:08 -05:00
username : String ,
content : String ,
2024-04-01 18:50:11 -05:00
avatar : Option < String > ,
url : Option < String > ,
2024-03-17 15:41:48 -05:00
}
#[ component ]
pub fn Hrt ( ) -> Element {
2024-03-25 20:42:59 -05:00
let time_elapsed = use_signal ( | | TimeElapsed {
days : 0 ,
hours : 0 ,
minutes : 0 ,
seconds : 0 ,
} ) ;
use_coroutine ( | _rx : UnboundedReceiver < ( ) > | {
to_owned! [ time_elapsed ] ;
async move {
loop {
time_elapsed . set ( get_hrt_time ( ) . await . unwrap ( ) ) ;
}
}
} ) ;
rsx! {
BasicPage { page_title : " Track my HRT progress! " ,
WerefoxCard { card_title : " I'm so glad you're interested!! " ,
2024-04-01 18:50:11 -05:00
div { class : " grid grid-cols-1 grid-rows-4 gap-2 p-2 text-lg text-center sm:text-xl text-alice-werefox-red-dark dark:text-alice-werefox-red-light " ,
2024-03-25 20:42:59 -05:00
p { " I've been on HRT for: " }
p { " {time_elapsed.read().days} days " }
p { " {time_elapsed.read().hours} hours " }
p { " {time_elapsed.read().minutes} minutes " }
p { " {time_elapsed.read().seconds} seconds " }
}
}
}
}
2024-03-17 15:41:48 -05:00
}
2024-03-25 20:42:59 -05:00
#[ derive(Serialize, Deserialize, Debug, Clone, PartialEq) ]
pub struct TimeElapsed {
days : i64 ,
hours : i64 ,
minutes : i64 ,
seconds : i64 ,
}
#[ server ]
async fn get_hrt_time ( ) -> Result < TimeElapsed , ServerFnError > {
use time ::{ macros ::datetime , Duration , OffsetDateTime , UtcOffset } ;
let mut hrt_start_date : OffsetDateTime = datetime! ( 2020 - 12 - 11 0 :00 UTC ) ;
// TODO: Fix this so that the offset time total matches the server's UTC offset.
// let hrt_start_date_offset: UtcOffset = UtcOffset::local_offset_at(hrt_start_date).unwrap();
// hrt_start_date = hrt_start_date.replace_offset(hrt_start_date_offset);
let now : OffsetDateTime = OffsetDateTime ::now_utc ( ) ;
let duration : Duration = now - hrt_start_date ;
Ok ( TimeElapsed {
days : duration . whole_days ( ) ,
hours : duration . whole_hours ( ) % 24 ,
minutes : duration . whole_minutes ( ) % 60 ,
seconds : duration . whole_seconds ( ) % 60 ,
} )
2024-03-17 15:41:48 -05:00
}
#[ component ]
2024-03-25 20:42:59 -05:00
pub fn Faq ( ) -> Element {
let faq_list = [ (
" So is Werefox like a species or...? " ,
" That's a good question! No, my fursona's full name is Alice Icehart Werefox, Werefox is just a last name. I got it from playing Second Life back in the day. " ,
ImageProps {
src : " /images/AliceDefault.png " . to_string ( ) ,
alt : " Alice as an emoji " . to_string ( ) ,
} ,
) , (
" How can you be Pansexual and a Lesbian? " ,
" I believe I've been told the proper term is \" sapphic \" , it just means I *am* Pansexual, but I tend to prefer those who identify more femme. " ,
ImageProps {
src : " /images/AliceHeartRainbow.png " . to_string ( ) ,
alt : " Alice with a gay pride heart " . to_string ( ) ,
} ,
) , (
" How do I get those Xenia stickers? " ,
" Yeah, about that. So, I've made a few posts about this, but when I first started giving those out, I was in a good financial position, among other things. Now I'm not! I will get to it when I do. " ,
ImageProps {
src : " /images/alice_Sweatdrop.svg " . to_string ( ) ,
alt : " Alice with a sweatdrop " . to_string ( ) ,
} ,
) , (
" What do you do? " ,
" Lots of things! If you want to know more about what I do, you can check out \" Stuff I do! \" from the main page, or \" Support Me? \" if you wanna toss a donation! I'm currently still unemployed, so any extra funds would be appreciated! " ,
ImageProps {
src : " /images/AliceEmojiAliceYay.png " . to_string ( ) ,
alt : " Happy Alice emoji " . to_string ( ) ,
} ,
) ] ;
rsx! {
BasicPage { page_title : " Here are some FAQs " ,
div { class : " p-4 space-y-4 rounded-lg ring-2 ring-alice-werefox-grey dark:ring-alice-werefox-grey-darker bg-alice-werefox-grey-lightest dark:bg-alice-werefox-grey-dark " ,
for faq in faq_list {
FaqCard { question : faq . 0 , answer : faq . 1 , image_props : faq . 2 }
}
}
}
}
2024-03-17 15:41:48 -05:00
}
}