Ready Home Tab

This commit is contained in:
Malte Schröder
2025-12-18 15:03:27 +01:00
parent e7f1b4b566
commit 9dba343301
11 changed files with 284 additions and 25 deletions

View File

@@ -1,11 +1,155 @@
use leptos::prelude::*;
use leptos::html::Div;
use leptos::wasm_bindgen::JsCast;
use web_sys::HtmlElement;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct Project {
pub id: usize,
pub title: String,
pub description: String,
pub image_url: String,
pub download_url: String,
pub code_url: String,
}
fn get_all_projects() -> Vec<Project> {
vec![
Project {
id: 1,
title: "Malxte.de Rust Website".into(),
description: "Eine voll in Rust mit Yew Programmierte Website mit allen Links und allem über mich.".into(),
image_url: "/assets/malxte_de.png".into(),
download_url: "https://github.com/.../releases".into(), // Dein Link
code_url: "https://gitea.malxte.de/Bytemalte/malxte_de.git".into(), // Dein Code Link
},
Project {
id: 2,
title: "Bytemalte.de Community Website".into(),
description: "Die Webseite auf der du dich gerade befindest, gebaut mit Leptos für die Bytemalte Community.".into(),
image_url: "/assets/bytemalte_de.png".into(),
download_url: "#".into(),
code_url: "https://gitea.malxte.de/Bytemalte/bytemalte_de".into(),
},
Project {
id: 3,
title: "Logo Design".into(),
description: "Das offizielle Bytemalte Logo. (Nicht frei verwendbar!)".into(),
image_url: "/assets/Logo.jpg".into(), // JPG geht natürlich auch
download_url: "/assets/Logo.jpg".into(), // Man kann Bilder auch direkt verlinken
code_url: "#".into(),
},
]
}
#[component]
pub fn HomePage() -> impl IntoView {
let (projects, set_projects) = signal(Vec::<Project>::new());
let (page, set_page) = signal(1);
let (loading, set_loading) = signal(false);
let (all_loaded, set_all_loaded) = signal(false);
let items_per_page = 3; // Wie viele Projekte pro Scroll-Schub geladen werden
let sentinel = NodeRef::<Div>::new();
let fetch_projects = move |p: usize| {
if all_loaded.get_untracked() { return; }
set_loading.set(true);
// Simuliert eine kurze Ladezeit für das Feeling
set_timeout(move || {
let all_my_projects = get_all_projects();
let start = (p - 1) * items_per_page;
let end = (start + items_per_page).min(all_my_projects.len());
if start >= all_my_projects.len() {
set_all_loaded.set(true);
set_loading.set(false);
return;
}
let slice = &all_my_projects[start..end];
set_projects.update(|projs| projs.extend(slice.to_vec()));
if end >= all_my_projects.len() {
set_all_loaded.set(true);
}
set_loading.set(false);
}, std::time::Duration::from_millis(400));
};
// Initialer Load
Effect::new(move |_| {
fetch_projects(page.get());
});
// Scroll-Event-Listener
Effect::new(move |_| {
let w = window();
let w_clone = w.clone();
let handle = gloo_events::EventListener::new(&w, "scroll", move |_| {
if all_loaded.get_untracked() || loading.get_untracked() { return; }
if let Some(doc) = w_clone.document() {
if let Some(el) = doc.document_element().and_then(|e| e.dyn_into::<HtmlElement>().ok()) {
let scroll_y = w_clone.scroll_y().unwrap_or(0.0);
let inner_height = w_clone.inner_height().unwrap().as_f64().unwrap_or(0.0);
let total_height = el.offset_height() as f64;
if scroll_y + inner_height >= total_height - 200.0 {
set_page.update(|p| *p += 1);
fetch_projects(page.get_untracked());
}
}
}
});
handle.forget();
});
view! {
<div class="home-hero">
<h1>"Malxte."</h1>
<p>"Willkommen in der Rust-Community."</p>
<div class="home-container">
<header class="home-hero">
<h1>"Bytemalte."</h1>
<p>"Entdecke meine neuesten Projekte - Bytemalte."</p>
</header>
<main class="projects-grid">
<For
each=move || projects.get()
key=|p| p.id
children=|p| {
view! {
<div class="project-card">
<div class="project-image">
<img src=p.image_url alt=p.title.clone() />
</div>
<div class="project-content">
<h3>{p.title}</h3>
<p>{p.description}</p>
<div class="project-links">
<a href=p.download_url target="_blank" class="btn-download">"Download"</a>
<a href=p.code_url target="_blank" class="btn-code">"Source Code"</a>
</div>
</div>
</div>
}
}
/>
</main>
<div node_ref=sentinel class="loading-trigger">
{move || {
if loading.get() {
view! { <div class="spinner"></div> }.into_any()
} else if all_loaded.get() {
view! { <p class="end-msg">"Das waren alle Projekte ✨"</p> }.into_any()
} else {
view! { <p>"Mehr laden..."</p> }.into_any()
}
}}
</div>
</div>
}
}