This commit is contained in:
Felix Ableitner 2024-01-05 15:53:08 +01:00
parent 42d382d19e
commit db12e5f1e9
4 changed files with 0 additions and 293 deletions

View File

@ -98,7 +98,6 @@ pub async fn start(hostname: &str, database_url: &str) -> MyResult<()> {
.layer(FederationMiddleware::new(config)) .layer(FederationMiddleware::new(config))
.layer(CorsLayer::permissive()); .layer(CorsLayer::permissive());
dbg!(&addr, &hostname);
Server::bind(&addr).serve(app.into_make_service()).await?; Server::bind(&addr).serve(app.into_make_service()).await?;
Ok(()) Ok(())

View File

@ -12,9 +12,7 @@ pub fn App() -> impl IntoView {
provide_meta_context(); provide_meta_context();
view! { view! {
<> <>
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
<Stylesheet id="leptos" href="/style.css"/> <Stylesheet id="leptos" href="/style.css"/>
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
<Router> <Router>
<Nav /> <Nav />
<main> <main>

View File

@ -1,156 +0,0 @@
use crate::api;
use leptos::*;
use leptos_router::*;
fn category(from: &str) -> &'static str {
match from {
"new" => "newest",
"show" => "show",
"ask" => "ask",
"job" => "jobs",
_ => "news",
}
}
#[component]
pub fn Stories() -> impl IntoView {
let query = use_query_map();
let params = use_params_map();
let page = move || {
query
.with(|q| q.get("page").and_then(|page| page.parse::<usize>().ok()))
.unwrap_or(1)
};
let story_type = move || {
params
.with(|p| p.get("stories").cloned())
.unwrap_or_else(|| "top".to_string())
};
let stories = create_resource(
move || (page(), story_type()),
move |(page, story_type)| async move {
let path = format!("{}?page={}", category(&story_type), page);
api::fetch_api::<Vec<api::Story>>(&api::story(&path)).await
},
);
let (pending, set_pending) = create_signal(false);
let hide_more_link = move || {
stories.get().unwrap_or(None).unwrap_or_default().len() < 28
|| pending()
};
view! {
<div class="news-view">
<div class="news-list-nav">
<span>
{move || if page() > 1 {
view! {
<a class="page-link"
href=move || format!("/{}?page={}", story_type(), page() - 1)
attr:aria_label="Previous Page"
>
"< prev"
</a>
}.into_any()
} else {
view! {
<span class="page-link disabled" aria-hidden="true">
"< prev"
</span>
}.into_any()
}}
</span>
<span>"page " {page}</span>
<span class="page-link"
class:disabled=hide_more_link
aria-hidden=hide_more_link
>
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
aria-label="Next Page"
>
"more >"
</a>
</span>
</div>
<main class="news-list">
<div>
<Transition
fallback=move || view! { <p>"Loading..."</p> }
set_pending
>
{move || match stories.get() {
None => None,
Some(None) => Some(view! { <p>"Error loading stories."</p> }.into_any()),
Some(Some(stories)) => {
Some(view! {
<ul>
<For
each=move || stories.clone()
key=|story| story.id
let:story
>
<Story story/>
</For>
</ul>
}.into_any())
}
}}
</Transition>
</div>
</main>
</div>
}
}
#[component]
fn Story(story: api::Story) -> impl IntoView {
view! {
<li class="news-item">
<span class="score">{story.points}</span>
<span class="title">
{if !story.url.starts_with("item?id=") {
view! {
<span>
<a href=story.url target="_blank" rel="noreferrer">
{story.title.clone()}
</a>
<span class="host">"("{story.domain}")"</span>
</span>
}.into_view()
} else {
let title = story.title.clone();
view! { <A href=format!("/stories/{}", story.id)>{title.clone()}</A> }.into_view()
}}
</span>
<br />
<span class="meta">
{if story.story_type != "job" {
view! {
<span>
{"by "}
{story.user.map(|user| view ! { <A href=format!("/users/{user}")>{user.clone()}</A>})}
{format!(" {} | ", story.time_ago)}
<A href=format!("/stories/{}", story.id)>
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"discuss".into()
}}
</A>
</span>
}.into_view()
} else {
let title = story.title.clone();
view! { <A href=format!("/item/{}", story.id)>{title.clone()}</A> }.into_view()
}}
</span>
{(story.story_type != "link").then(|| view! {
" "
<span class="label">{story.story_type}</span>
})}
</li>
}
}

View File

@ -2,137 +2,3 @@ pub mod api;
pub mod app; pub mod app;
pub mod article; pub mod article;
pub mod nav; pub mod nav;
use leptos::error::Result;
use leptos::*;
use log::info;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Cat {
url: String,
}
type CatCount = usize;
async fn fetch_cats(count: CatCount) -> Result<Vec<String>> {
if count > 0 {
// make the request
let res = reqwest::get(&format!(
"https://api.thecatapi.com/v1/images/search?limit={count}",
))
.await?
.json::<Vec<Cat>>()
.await?
// extract the URL field for each cat
.into_iter()
.take(count)
.map(|cat| cat.url)
.collect::<Vec<_>>();
Ok(res)
} else {
Ok(vec![])
}
}
// TODO: import this from backend somehow
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct DbInstance {
pub id: i32,
pub ap_id: Url,
pub articles_url: Url,
pub inbox_url: String,
#[serde(skip)]
pub public_key: String,
#[serde(skip)]
pub private_key: Option<String>,
pub local: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct InstanceView {
pub instance: DbInstance,
pub following: Vec<DbInstance>,
}
async fn fetch_instance(url: &str) -> Result<InstanceView> {
let res = reqwest::get(url).await?.json::<InstanceView>().await?;
info!("{:?}", &res);
Ok(res)
}
pub fn fetch_example() -> impl IntoView {
let (cat_count, set_cat_count) = create_signal::<CatCount>(0);
// we use local_resource here because
// 1) our error type isn't serializable/deserializable
// 2) we're not doing backend-side rendering in this example anyway
// (during SSR, create_resource will begin loading on the backend and resolve on the client)
let cats = create_local_resource(move || cat_count.get(), fetch_cats);
//let instance = create_local_resource(move || "http://localhost:8131/api/v1/instance", fetch_instance);
let fallback = move |errors: RwSignal<Errors>| {
let error_list = move || {
errors.with(|errors| {
errors
.iter()
.map(|(_, e)| view! { <li>{e.to_string()}</li> })
.collect_view()
})
};
view! {
<div class="error">
<h2>"Error"</h2>
<ul>{error_list}</ul>
</div>
}
};
// the renderer can handle Option<_> and Result<_> states
// by displaying nothing for None if the resource is still loading
// and by using the ErrorBoundary fallback to catch Err(_)
// so we'll just use `.and_then()` to map over the happy path
let cats_view = move || {
cats.and_then(|data| {
data.iter()
.map(|s| view! { <p><img src={s}/></p> })
.collect_view()
})
};
/*
let instance_view = move || {
instance.and_then(|data| {
view! { <h1>{data.instance.ap_id.to_string()}</h1> }
})
};
*/
view! {
//{instance_view}
<div>
<label>
"How many cats would you like?"
<input
type="number"
prop:value=move || cat_count.get().to_string()
on:input=move |ev| {
let val = event_target_value(&ev).parse::<CatCount>().unwrap_or(0);
set_cat_count.set(val);
}
/>
</label>
<Transition fallback=move || {
view! { <div>"Loading (Suspense Fallback)..."</div> }
}>
<ErrorBoundary fallback>
<div>
{cats_view}
</div>
</ErrorBoundary>
</Transition>
</div>
}
}