Home Tab + Tauri backend

This commit is contained in:
2025-12-28 09:01:26 +01:00
commit 896841e1c0
85 changed files with 1533 additions and 0 deletions

38
src/app.rs Normal file
View File

@@ -0,0 +1,38 @@
use crate::navbar::Navbar;
use crate::pages::{chat::Chat, home::Home, news::News};
use yew::prelude::*;
use yew_router::prelude::*;
#[derive(Clone, Routable, PartialEq)]
pub enum Route {
#[at("/")]
Home,
#[at("/news")]
News,
#[at("/chat")]
Chat,
#[not_found]
#[at("/404")]
NotFound,
}
fn switch(routes: Route) -> Html {
match routes {
Route::Home => html! { <Home /> },
Route::News => html! { <News /> },
Route::Chat => html! { <Chat /> },
Route::NotFound => html! { <h1 class="status-msg">{ "404 - Not Found" }</h1> },
}
}
#[function_component(App)]
pub fn app() -> Html {
html! {
<BrowserRouter>
<div class="feed-container">
<Switch<Route> render={switch} />
</div>
<Navbar /> // Die Navbar schwebt dank 'fixed' über dem Container
</BrowserRouter>
}
}

10
src/main.rs Normal file
View File

@@ -0,0 +1,10 @@
mod app;
mod navbar; // Deklariert das Verzeichnis src/navbar/
mod pages; // Deklariert das Verzeichnis src/pages/
use app::App;
fn main() {
console_error_panic_hook::set_once();
yew::Renderer::<App>::new().render();
}

2
src/navbar/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod navbar;
pub use navbar::Navbar;

33
src/navbar/navbar.rs Normal file
View File

@@ -0,0 +1,33 @@
use crate::app::Route;
use yew::prelude::*;
use yew_router::prelude::*;
#[function_component(Navbar)]
pub fn navbar() -> Html {
let route = use_route::<Route>().unwrap_or(Route::Home);
html! {
<nav class="navbar-dock">
<Link<Route>
to={Route::Home}
classes={classes!("nav-link", if route == Route::Home { "active" } else { "" })}
>
{ "Home" }
</Link<Route>>
<Link<Route>
to={Route::News}
classes={classes!("nav-link", if route == Route::News { "active" } else { "" })}
>
{ "News" }
</Link<Route>>
<Link<Route>
to={Route::Chat}
classes={classes!("nav-link", if route == Route::Chat { "active" } else { "" })}
>
{ "Chat" }
</Link<Route>>
</nav>
}
}

10
src/pages/chat.rs Normal file
View File

@@ -0,0 +1,10 @@
use yew::prelude::*;
#[function_component(Chat)]
pub fn chat() -> Html {
html! {
<div>
<h1>{"Chat Page"}</h1>
</div>
}
}

116
src/pages/home.rs Normal file
View File

@@ -0,0 +1,116 @@
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use web_sys::{window, MouseEvent, ScrollBehavior, ScrollToOptions};
use yew::prelude::*;
// Tauri 'invoke' Funktion deklarieren
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"])]
async fn invoke(cmd: &str, args: JsValue) -> JsValue;
}
// Wir definieren Post lokal, damit Yew weiß, wie die JSON-Daten vom Backend aussehen
// In src/pages/home.rs
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct Post {
pub content: String,
pub author: String,
pub created_at: u64,
}
#[function_component(Home)]
pub fn home() -> Html {
let posts = use_state(|| None::<Vec<Post>>);
let error = use_state(|| false);
let load_posts = {
let posts = posts.clone();
let error = error.clone();
Callback::from(move |_e: MouseEvent| {
let posts = posts.clone();
let error = error.clone();
// UI zurücksetzen & Scrollen
posts.set(None);
error.set(false);
if let Some(win) = window() {
let options = ScrollToOptions::new();
options.set_top(0.0);
options.set_behavior(ScrollBehavior::Smooth);
let _ = win.scroll_to_with_scroll_to_options(&options);
}
spawn_local(async move {
// Daten vom Rust-Backend anfordern
let res = invoke("fetch_nostr_posts", JsValue::NULL).await;
// JSON-Resultat in Vec<Post> umwandeln
if let Ok(new_posts) = serde_wasm_bindgen::from_value::<Vec<Post>>(res) {
posts.set(Some(new_posts));
} else {
error.set(true);
}
});
})
};
// Initiales Laden beim Start
{
let load_posts = load_posts.clone();
use_effect_with((), move |_| {
load_posts.emit(MouseEvent::new("click").unwrap());
|| ()
});
}
html! {
<div class="feed-container">
<div class="feed-header">
<h1 class="feed-title">{"✨ Entdecken"}</h1>
<button class="reload-btn" onclick={load_posts.clone()}>
{"🔄"}
</button>
</div>
{
if *error {
html! { <p class="status-msg">{"Fehler beim Laden der Posts."}</p> }
} else if let Some(ref posts_list) = *posts {
html! {
<div class="posts-list">
{ for posts_list.iter().map(|p| html! { <PostCard post={p.clone()} /> }) }
<div class="footer-action">
<button class="reload-btn-large" onclick={load_posts}>
{"Nach oben & Neu laden"}
</button>
</div>
</div>
}
} else {
html! { <p class="status-msg">{"Verbinde mit Nostr über Tauri..."}</p> }
}
}
</div>
}
}
#[derive(Properties, PartialEq)]
pub struct PostProps {
pub post: Post,
}
#[function_component(PostCard)]
pub fn post_card(props: &PostProps) -> Html {
let post = &props.post;
html! {
<div class="post-card">
<div class="post-header">
<span class="post-author">{ format!("{:.8}...", post.author) }</span>
<span class="post-date">{ post.created_at }</span>
</div>
<div class="post-content">{ &post.content }</div>
</div>
}
}

3
src/pages/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod chat;
pub mod home;
pub mod news;

10
src/pages/news.rs Normal file
View File

@@ -0,0 +1,10 @@
use yew::prelude::*;
#[function_component(News)]
pub fn news() -> Html {
html! {
<div>
<h1>{"News Page"}</h1>
</div>
}
}