use leptos::prelude::*; use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title}; use leptos_router::{ components::{Outlet, ParentRoute, Route, Router, Routes, A}, path }; use leptos_i18n_router::I18nRoute; use serde::{Deserialize, Serialize}; use crate::i18n::*; pub fn shell(options: LeptosOptions) -> impl IntoView { view! { } } #[component] pub fn App() -> impl IntoView { // Provides context that manages stylesheets, titles, meta tags, etc. provide_meta_context(); view! { // id=leptos means cargo-leptos will hot-reload this stylesheet // content for this welcome page <Router> <Header/> <Routes fallback=|| "Page not found.".into_view()> <I18nRoute<Locale, _, _> view=Outlet> <Route path=path!("/") view=HomePage/> <Route path=path!("/resume") view=Resume/> <ParentRoute path=path!("/blog") view=crate::blog::Blog> <Route path=path!(":slug") view=crate::blog::BlogPost/> <Route path=path!("") view=|| view! {}/> </ParentRoute> <ParentRoute path=path!("/gallery") view=crate::gallery::Gallery> <Route path=path!(":slug") view=crate::gallery::GalleryEntry/> <Route path=path!("") view=|| view! {}/> </ParentRoute> </I18nRoute<Locale, _, _>> </Routes> <Footer/> </Router> </I18nContextProvider> } } #[component] pub fn LanguageSwitcher() -> impl IntoView { let i18n = use_i18n(); view! { <div class="language-switcher main-width"> <p>{t!(i18n, available_in)}</p> <button class="link" on:click=move |_| i18n.set_locale(Locale::en)>english</button> <button class="link" on:click=move |_| i18n.set_locale(Locale::fr)>français</button> <button class="link" on:click=move |_| i18n.set_locale(Locale::fi)>suomi</button> // <button class="link" on:click=move |_| i18n.set_locale(Locale::et)>eesti</button> // <button class="link" on:click=move |_| i18n.set_locale(Locale::sv)>svenska</button> // <button class="link" on:click=move |_| i18n.set_locale(Locale::eo)>esperanto</button> // <button class="link" on:click=move |_| i18n.set_locale(Locale::jbo)>lojban</button> <p>{t!(i18n, partial_translations)}</p> </div> } } #[component] pub fn Header() -> impl IntoView { let i18n = use_i18n(); view! { <header> <div class="title-links-and-banner"> <div class="text"> <h1><span>tanguy</span><span>.gerome</span><span>.fi</span></h1> <div class="links"> <A href="/">{t!(i18n, home)}</A> <A href="/blog">{t!(i18n, blog)}</A> <A href="/gallery">{t!(i18n, gallery)}</A> <A href="/resume">{t!(i18n, resume)}</A> </div> </div> <picture> <img class="image" src="https://directus.gerome.fi/assets/efdc0b69-7a48-4434-bf07-d406e42a971e?width=1600&fit=inside&format=auto&quality=50&withoutEnlargement=true" alt="Banner" /> <source srcset="https://directus.gerome.fi/assets/efdc0b69-7a48-4434-bf07-d406e42a971e?width=1600&fit=inside&format=jpg&quality=50&withoutEnlargement=true"/> </picture> </div> <LanguageSwitcher/> </header> } } #[component] pub fn Footer() -> impl IntoView { let i18n = use_i18n(); view! { <footer> <div class="name-and-links main-width"> <span class="name">{t!(i18n, tanguy_gerome)}</span> <div class="links"> <a href="https://www.instagram.com/kapno.cc/" target="_blank" rel="noopener noreferrer">instagram @kapno.cc</a> <a href="https://forgejo.juustodiilerit.fi/tanguy" target="_blank" rel="noopener noreferrer">git (forgejo.juustodiilerit.fi)</a> <a href="mailto:tanguy@gerome.fi" target="_blank" rel="noopener noreferrer">email tanguy@gerome.fi</a> </div> </div> </footer> } } #[derive(Deserialize, Serialize, Clone, Debug, Default)] pub struct HomePageTranslations { content: String } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct HomePageSingleton { translations: Option<HomePageTranslations> } #[server(GetHomepage, "/api", "GetJson")] pub async fn get_homepage(locale: String) -> Result<HomePageSingleton, ServerFnError> { crate::services::directus::get_client() .get_item::<HomePageSingleton>(&locale, "homepage", "homepage").await .map_err(|e| ServerFnError::ServerError(e.to_string())) } #[component] pub fn HomePage() -> impl IntoView { let i18n = use_i18n(); let homepage = Resource::new(move || i18n.get_locale(), move |locale| get_homepage(locale.to_string())); view! { <main class="main-width"> <Suspense fallback=move || view! { <div>"Loading..."</div> } > {move || homepage.get() .and_then(|homepage| homepage.ok()) .and_then(|homepage| { let html = markdown::to_html(&homepage.translations.unwrap_or_default().content); view! { <div class="markdown" inner_html=html/> }.into() }) } </Suspense> </main> } } #[derive(Deserialize, Serialize, Clone, Debug, Default)] pub struct ResumeTranslations { content: String } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct ResumeSingleton { translations: Option<ResumeTranslations> } #[server(GetResume, "/api", "GetJson")] pub async fn get_resume(locale: String) -> Result<ResumeSingleton, ServerFnError> { crate::services::directus::get_client() .get_item::<ResumeSingleton>(&locale, "resume", "resume").await .map_err(|e| ServerFnError::ServerError(e.to_string())) } #[component] pub fn Resume() -> impl IntoView { let i18n = use_i18n(); let resume = Resource::new(move || i18n.get_locale(), move |locale| get_resume(locale.to_string())); view! { <main class="main-width"> <img class="resume-image" src="https://directus.gerome.fi/assets/0c33f439-4e1b-4a1f-a1ab-df1cc9b60f23?width=600&height=600&fit=cover&format=auto&quality=90&withoutEnlargement=true" alt="Tanguy Gérôme - portait" /> <Suspense fallback=move || view! { <div>"Loading..."</div> } > {move || resume.get() .and_then(|resume| resume.ok()) .and_then(|resume| { let html = markdown::to_html(&resume.translations.unwrap_or_default().content); view! { <div class="markdown" inner_html=html/> }.into() }) } </Suspense> </main> } }