Update leptos + add WIP translations

This commit is contained in:
Tanguy Gérôme 2025-01-26 17:47:40 +02:00
parent 68ca7284fb
commit 14cff1b306
7 changed files with 352 additions and 585 deletions

617
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,7 @@ leptos_router = { version = "0.6", features = ["nightly"] }
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
tower = { version = "0.4", features = ["util"], optional = true }
tower-http = { version = "0.5", features = ["fs"], optional = true }
wasm-bindgen = "=0.2.95"
wasm-bindgen = "=0.2.100"
thiserror = "1"
tracing = { version = "0.1", optional = true }
http = "1"
@ -50,8 +50,8 @@ ssr = [
[package.metadata.leptos-i18n]
default = "en"
# locales = ["en", "fr", "fi", "et", "sv", "eo", "jbo"]
locales = ["en"]
# locales = ["en", "fr", "fi", "sv", "eo", "uk", "et", "jbo"]
locales = ["en", "fr", "fi"]
# Defines a size-optimized profile for the WASM bundle in release mode
[profile.wasm-release]
@ -81,7 +81,7 @@ style-file = "style/main.scss"
assets-dir = "public"
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
site-addr = "127.0.0.1:3000"
site-addr = "0.0.0.0:3000"
# The port to use for automatic reload monitoring
reload-port = 3001

View file

@ -6,7 +6,5 @@ blog: Blog
welcome_blog: Welcome to my blog!
gallery: Gallery
gallery_title: Gallery
gallery_description: aaa
tanguy_gerome: Tanguy Gérôme

View file

@ -59,8 +59,8 @@ pub fn LanguageSwitcher() -> impl IntoView {
<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::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>
@ -92,7 +92,7 @@ pub fn Header() -> impl IntoView {
/>
</div>
// <LanguageSwitcher/>
<LanguageSwitcher/>
</header>
}
}
@ -121,7 +121,7 @@ pub fn HomePage() -> impl IntoView {
<main class="main-width">
<h1>Welcome!</h1>
<p>"I'm "<b>Tanguy Gérôme</b>, a 26 year old software developer, amateur photographer, and over all nerd living in Helsinki, Finland, originally from Cornimont, France.</p>
<p>I am <b>Tanguy Gérôme</b>, a 26 year old software developer, amateur photographer, and over all nerd living in Helsinki, Finland, originally from Cornimont, France.</p>
<p>
On here you can find my personal photography portfolio, and (coming) a blog where I scribble about whatever comes to mind in relation to my hobbies and interests, including:
@ -137,6 +137,8 @@ pub fn HomePage() -> impl IntoView {
</ul>
</p>
<p>I am also using this website as a way to practice in my language learning journey, so expect unfinished and low quality translations for anything other than English and French :D</p>
<p>This website is still under construction, more content to come (hopefully) soon.</p>
// <Await future=move || get_rich_text_page("home-page".to_string()) let:page>

View file

@ -3,8 +3,8 @@ use leptos_router::*;
use serde::{Deserialize, Serialize};
use crate::i18n::*;
use crate::services::contentful::get_rich_text_page;
use crate::services::rich_text;
// use crate::services::contentful::get_markdown_page;
// use crate::services::rich_text;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BlogPostMetadata {
@ -75,29 +75,29 @@ pub fn BlogList() -> impl IntoView {
#[component]
pub fn BlogPost() -> impl IntoView {
let params = use_params::<BlogPostParams>();
let slug= move || {
params.with(|params| params.as_ref()
.map(|params| params.slug.clone())
.unwrap())
};
let blog_page = create_resource(|| (), move |_| async move { get_rich_text_page(slug()).await });
// let params = use_params::<BlogPostParams>();
// let slug= move || {
// params.with(|params| params.as_ref()
// .map(|params| params.slug.clone())
// .unwrap())
// };
// let blog_page = create_resource(|| (), move |_| async move { get_rich_text_page(slug()).await });
view! {
<main class="main-width">
<Suspense fallback=move || view! { <div>"Loading..."</div> }>
{match blog_page.get().unwrap_or(Err(ServerFnError::ServerError("Loading...".to_string()))) {
Ok(page) => {
view! {
<div>
<h1>{page.english.title}</h1>
{rich_text::document_handler(&page.english.rich_text_content)}
</div>
}
},
Err(error) => view! { <div>"Error: "{error.to_string()}</div> }
}}
</Suspense>
// <Suspense fallback=move || view! { <div>"Loading..."</div> }>
// {match blog_page.get().unwrap_or(Err(ServerFnError::ServerError("Loading...".to_string()))) {
// Ok(page) => {
// view! {
// <div>
// <h1>{page.english.title}</h1>
// {rich_text::document_handler(&page.english.rich_text_content)}
// </div>
// }
// },
// Err(error) => view! { <div>"Error: "{error.to_string()}</div> }
// }}
// </Suspense>
</main>
}
}

View file

@ -1,257 +1,40 @@
use std::env;
// use reqwest::StatusCode;
use leptos::*;
use serde::{Deserialize, Serialize};
use serde_json;
// use crate::services::rich_text;
//pub struct Client {
// access_token: String,
// space_id: String,
// base_url: String,
// environment_id: String,
//}
//impl Client {
// pub fn new(
// access_token: &str,
// space_id: &str,
// base_url: &str,
// environment_id: &str,
// ) -> Client {
// Client {
// base_url: base_url.into(),
// access_token: access_token.into(),
// space_id: space_id.into(),
// environment_id: environment_id.into(),
// }
// }
// fn get_entries_url(&self, query_string: &str) -> String {
// format!(
// "{base_url}/{space_id}/environments/{environment}/entries{query_string}",
// base_url = &self.base_url,
// space_id = &self.space_id,
// environment = &self.environment_id,
// query_string = &query_string
// )
// }
// pub async fn get_entries_by_query_string<T>(
// &self,
// query_string: &str
// ) -> Result<Vec<T>, Box<dyn std::error::Error>>
// where
// for<'a> T: Serialize + Deserialize<'a>,
// {
// let url = self.get_entries_url(query_string);
// let reqwest_client = reqwest::Client::new();
// let request = reqwest_client.get(&url).bearer_auth(&self.access_token);
// let response = request.send().await?;
// let json_response = match response.status() {
// StatusCode::OK => {
// let json = response.json::<serde_json::Value>().await?;
// Some(json)
// },
// StatusCode::NOT_FOUND => None,
// _ => {
// unimplemented!();
// },
// };
// match json_response {
// Some(json) => {
// if let Some(items) = json.clone().get_mut("items") {
// if items.is_array() {
// if let Some(includes) = json.get("includes") {
// self.resolve_array(items, includes)?;
// } else {
// let includes = serde_json::Value::default();
// self.resolve_array(items, &includes)?;
// }
// let ar_string = items.to_string();
// let entries = serde_json::from_str::<Vec<T>>(ar_string.as_str())?;
// Ok(entries)
// } else {
// unimplemented!();
// }
// } else {
// unimplemented!();
// }
// },
// _ => unimplemented!(),
// }
// }
// fn resolve_array(
// &self,
// value: &mut serde_json::Value,
// includes: &serde_json::Value,
// ) -> Result<(), Box<dyn std::error::Error>> {
// let items = value.as_array_mut().unwrap();
// for item in items {
// if item.is_object() {
// self.resolve_object(item, includes)?;
// } else if item.is_string() || item.is_number() {
// // do nothing
// } else {
// unimplemented!();
// }
// }
// Ok(())
// }
// fn resolve_object(
// &self,
// value: &mut serde_json::Value,
// includes: &serde_json::Value,
// ) -> Result<(), Box<dyn std::error::Error>> {
// if let Some(sys) = value.get("sys") {
// if let Some(sys_type) = sys.get("type") {
// if sys_type == "Entry" {
// self.resolve_entry(value, includes)?;
// } else if sys_type == "Link" {
// self.resolve_link(value, includes)?;
// } else {
// let node_type = value["nodeType"].clone();
// if node_type == "document" {
// // log::warn!("TODO: Richtext is not yet implemented");
// } else {
// unimplemented!(
// "{} - {} not implemented for {}",
// &sys_type,
// &node_type,
// &value
// );
// }
// }
// } else {
// unimplemented!("sys.type do not exist, though sys exists") // TODO: Can this ever happen?
// }
// } else {
// // Do nothing, as it likely a json object
// }
// Ok(())
// }
// fn resolve_asset(&self, value: &mut serde_json::Value) -> Result<(), Box<dyn std::error::Error>> {
// if let Some(fields) = value.get_mut("fields") {
// if fields.is_object() {
// *value = fields.clone();
// } else {
// unimplemented!();
// }
// } else {
// unimplemented!();
// }
// Ok(())
// }
// fn resolve_entry(
// &self,
// value: &mut serde_json::Value,
// includes: &serde_json::Value,
// ) -> Result<(), Box<dyn std::error::Error>> {
// if let Some(fields) = value.get_mut("fields") {
// if fields.is_object() {
// let entry_object = fields.as_object_mut().unwrap();
// for (_field_name, field_value) in entry_object {
// if field_value.is_object() {
// self.resolve_object(field_value, includes)?;
// } else if field_value.is_array() {
// self.resolve_array(field_value, includes)?;
// } else {
// // Regular string, number, etc, values. No need to do anything.
// }
// }
// *value = fields.clone();
// } else {
// unimplemented!();
// }
// } else {
// unimplemented!();
// }
// Ok(())
// }
// fn resolve_link(
// &self,
// value: &mut serde_json::Value,
// includes: &serde_json::Value,
// ) -> Result<(), Box<dyn std::error::Error>> {
// let link_type = value["sys"]["linkType"].clone();
// let link_id = value["sys"]["id"].clone();
// if link_type == "Entry" {
// let included_entries = includes["Entry"].as_array().unwrap();
// let mut filtered_entries = included_entries
// .iter()
// .filter(|entry| entry["sys"]["id"] == link_id)
// .take(1);
// let linked_entry = filtered_entries.next();
// if let Some(entry) = linked_entry {
// let mut entry = entry.clone();
// self.resolve_entry(&mut entry, includes)?;
// *value = entry;
// //value["fields"] = entry["fields"].clone();
// //*value = entry["fields"].clone();
// }
// } else if link_type == "Asset" {
// let included_assets = includes["Asset"].as_array().unwrap();
// let mut filtered_assets = included_assets
// .iter()
// .filter(|entry| entry["sys"]["id"] == link_id)
// .take(1);
// let linked_asset = filtered_assets.next();
// if let Some(asset) = linked_asset {
// let mut asset = asset.clone();
// self.resolve_asset(&mut asset)?;
// *value = asset;
// }
// } else {
// unimplemented!();
// }
// //*value = value["fields"].clone();
// Ok(())
// }
//}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RichTextPage {
pub struct MarkdownPage {
pub title: String,
pub page_slug: String,
// pub rich_text_content: rich_text::RichTextNode,
pub rich_text_content: serde_json::Value,
pub slug: String,
pub content: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RichTextPageLanguagesWrapper {
pub struct MarkdownPageLanguagesWrapper {
pub internal_title: String,
pub english: RichTextPage,
pub slug: String,
pub english: MarkdownPage,
}
#[server(GetRichTextPage, "/api", "GetJson")]
pub async fn get_rich_text_page(slug: String) -> Result<RichTextPageLanguagesWrapper, ServerFnError> {
pub async fn get_markdown_page(slug: String) -> Result<MarkdownPageLanguagesWrapper, ServerFnError> {
let contentful_client = get_contentful_client();
// let builder = contentful::QueryBuilder::new()
// .content_type_is("richTextPageLanguagesWrapper")
// .include(10)
// .field_equals("fields.slug", &slug);
let query_string = format!("?content_type=richTextPageLanguagesWrapper&include=10&fields.slug={}", slug);
let pages = contentful_client.get_entries_by_query_string::<RichTextPageLanguagesWrapper>(&query_string).await;
let builder = contentful::QueryBuilder::new()
.content_type_is("markdownPageLanguagesWrapper")
.include(10)
.field_equals("fields.slug", &slug);
// let query_string = format!("?content_type=markdownPageLanguagesWrapper&include=10&fields.slug={}", slug);
// let pages = contentful_client.get_entries_by_query_string::<RichTextPageLanguagesWrapper>(&query_string).await;
let pages = contentful_client.get_entries::<MarkdownPageLanguagesWrapper>(Some(builder)).await;
match pages {
Ok(found_pages) => {
if found_pages.len() != 1 {
return Err(ServerFnError::ServerError("Found none or more than one".to_string()));
}
let english_rich_text_content = found_pages[0].english.rich_text_content.clone();
dbg!(english_rich_text_content);
let english_content = found_pages[0].english.content.clone();
dbg!(english_content);
Ok(found_pages[0].clone())
},
Err(e) => Err(ServerFnError::ServerError(e.to_string())),

View file

@ -87,7 +87,7 @@ body {
.text {
@include top-level-padding;
flex-basis: auto;
flex-shrink: 0;
flex-shrink: 1;
flex-grow: 1;
display: flex;
@ -103,14 +103,17 @@ body {
}
.links {
flex-shrink: 1;
display: flex;
flex-direction: column;
align-items: flex-end;
text-align: right;
gap: 8px;
a {
color: white!important;
font-size: 22px;
font-size: 20px;
}
}
}