feat: routing under /web, scroll only tl

This commit is contained in:
əlemi 2024-04-15 05:00:23 +02:00
parent 639a982bd0
commit 268265e34a
Signed by: alemi
GPG key ID: A4895B84D311642C
3 changed files with 52 additions and 38 deletions

View file

@ -20,6 +20,15 @@
max-height: 2em; max-height: 2em;
border-radius: 50%; border-radius: 50%;
} }
div.boxscroll {
max-height: calc(100vh - 8rem);
overflow-y: scroll;
}
@media screen and (max-width: 786px) {
div.boxscroll {
max-height: 100%;
}
}
table.align { table.align {
line-height: 1rem; line-height: 1rem;
} }

View file

@ -3,9 +3,10 @@ use std::sync::Arc;
use apb::{target::Addressed, Activity, ActivityMut, Actor, Base, Collection, Object, ObjectMut}; use apb::{target::Addressed, Activity, ActivityMut, Actor, Base, Collection, Object, ObjectMut};
use dashmap::DashMap; use dashmap::DashMap;
use leptos::{leptos_dom::logging::console_log, *}; use leptos::{leptos_dom::logging::console_log, *};
use leptos_router::use_params_map; use leptos_router::*;
pub const BASE_URL: &str = "https://feditest.alemi.dev"; pub const URL_BASE: &str = "https://feditest.alemi.dev";
pub const URL_PREFIX: &str = "/web";
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]
struct LoginForm { struct LoginForm {
@ -23,7 +24,7 @@ pub fn LoginBox(
view! { view! {
<div> <div>
<div class="w-100" class:hidden=move || { rx.get().unwrap_or_default().is_empty() }> <div class="w-100" class:hidden=move || { rx.get().unwrap_or_default().is_empty() }>
"Hello "<a href="/users/test" >test</a> "Hello "<a href="/web/users/test" >test</a>
<input style="float:right" type="submit" value="logout" on:click=move |_| { <input style="float:right" type="submit" value="logout" on:click=move |_| {
tx.set(None); tx.set(None);
} /> } />
@ -38,7 +39,7 @@ pub fn LoginBox(
let password = password_ref.get().map(|x| x.value()).unwrap_or("".into()); let password = password_ref.get().map(|x| x.value()).unwrap_or("".into());
spawn_local(async move { spawn_local(async move {
let auth = reqwest::Client::new() let auth = reqwest::Client::new()
.post(format!("{BASE_URL}/auth")) .post(format!("{URL_BASE}/auth"))
.json(&LoginForm { email, password }) .json(&LoginForm { email, password })
.send() .send()
.await.unwrap() .await.unwrap()
@ -66,7 +67,7 @@ pub fn PostBox(token: Signal<Option<String>>) -> impl IntoView {
let summary = summary_ref.get().map(|x| x.value()); let summary = summary_ref.get().map(|x| x.value());
let content = content_ref.get().map(|x| x.value()).unwrap_or("".into()); let content = content_ref.get().map(|x| x.value()).unwrap_or("".into());
reqwest::Client::new() reqwest::Client::new()
.post(format!("{BASE_URL}/users/test/outbox")) .post(format!("{URL_BASE}/users/test/outbox"))
.header("Authorization", format!("Bearer {}", token.get().unwrap_or_default())) .header("Authorization", format!("Bearer {}", token.get().unwrap_or_default()))
.json( .json(
&serde_json::Value::Object(serde_json::Map::default()) &serde_json::Value::Object(serde_json::Map::default())
@ -74,7 +75,7 @@ pub fn PostBox(token: Signal<Option<String>>) -> impl IntoView {
.set_summary(summary.as_deref()) .set_summary(summary.as_deref())
.set_content(Some(&content)) .set_content(Some(&content))
.set_to(apb::Node::links(vec![apb::target::PUBLIC.to_string()])) .set_to(apb::Node::links(vec![apb::target::PUBLIC.to_string()]))
.set_cc(apb::Node::links(vec![format!("{BASE_URL}/users/test/followers")])) .set_cc(apb::Node::links(vec![format!("{URL_BASE}/users/test/followers")]))
) )
.send() .send()
.await.unwrap() .await.unwrap()
@ -114,6 +115,7 @@ pub fn ActorBanner(object: serde_json::Value) -> impl IntoView {
<div><b>{id}</b></div> <div><b>{id}</b></div>
}, },
serde_json::Value::Object(_) => { serde_json::Value::Object(_) => {
let uid = object.id().unwrap_or_default().split('/').last().unwrap_or_default().to_string();
let avatar_url = object.icon().get().map(|x| x.url().id().unwrap_or_default()).unwrap_or_default(); let avatar_url = object.icon().get().map(|x| x.url().id().unwrap_or_default()).unwrap_or_default();
let display_name = object.name().unwrap_or_default().to_string(); let display_name = object.name().unwrap_or_default().to_string();
let username = object.preferred_username().unwrap_or_default().to_string(); let username = object.preferred_username().unwrap_or_default().to_string();
@ -126,7 +128,7 @@ pub fn ActorBanner(object: serde_json::Value) -> impl IntoView {
<td><b>{display_name}</b></td> <td><b>{display_name}</b></td>
</tr> </tr>
<tr> <tr>
<td class="top" ><small>{username}@{domain}</small></td> <td class="top" ><a class="clean" href={format!("/web/users/{uid}")} ><small>{username}@{domain}</small></a></td>
</tr> </tr>
</table> </table>
</div> </div>
@ -143,7 +145,7 @@ pub fn Actor() -> impl IntoView {
let params = use_params_map(); let params = use_params_map();
let actor = create_local_resource(move || params.get().get("id").cloned().unwrap_or_default(), |uid| { let actor = create_local_resource(move || params.get().get("id").cloned().unwrap_or_default(), |uid| {
async move { async move {
reqwest::get(format!("{BASE_URL}/users/{uid}")) reqwest::get(format!("{URL_BASE}/users/{uid}"))
.await .await
.unwrap() .unwrap()
.json::<serde_json::Value>() .json::<serde_json::Value>()
@ -228,7 +230,7 @@ struct OmgReqwestErrorIsNotClonable(String);
pub fn Timeline( pub fn Timeline(
token: Signal<Option<String>>, token: Signal<Option<String>>,
) -> impl IntoView { ) -> impl IntoView {
let (timeline, set_timeline) = create_signal(format!("{BASE_URL}/inbox/page")); let (timeline, set_timeline) = create_signal(format!("{URL_BASE}/inbox/page"));
let users : Arc<DashMap<String, serde_json::Value>> = Arc::new(DashMap::new()); let users : Arc<DashMap<String, serde_json::Value>> = Arc::new(DashMap::new());
let _users = users.clone(); // TODO i think there is syntactic sugar i forgot? let _users = users.clone(); // TODO i think there is syntactic sugar i forgot?
let items = create_local_resource(move || timeline.get(), move |feed_url| { let items = create_local_resource(move || timeline.get(), move |feed_url| {
@ -238,32 +240,34 @@ pub fn Timeline(
view! { view! {
<div class="ml-1"> <div class="ml-1">
<TimelinePicker tx=set_timeline rx=timeline /> <TimelinePicker tx=set_timeline rx=timeline />
<ErrorBoundary fallback=move |err| view! { <p>{format!("{:?}", err.get())}</p> } > <div class="boxscroll mt-1" >
{move || items.with(|x| match x { <ErrorBoundary fallback=move |err| view! { <p>{format!("{:?}", err.get())}</p> } >
None => Ok(view! { <p>loading...</p> }.into_view()), {move || items.with(|x| match x {
Some(data) => match data { None => Ok(view! { <p>loading...</p> }.into_view()),
Err(e) => Err(OmgReqwestErrorIsNotClonable(e.to_string())), Some(data) => match data {
Ok(values) => Ok( Err(e) => Err(OmgReqwestErrorIsNotClonable(e.to_string())),
values Ok(values) => Ok(
.iter() values
.map(|object| { .iter()
let actor = object.actor().extract().unwrap_or_else(|| .map(|object| {
serde_json::Value::String(object.actor().id().unwrap_or_default()) let actor = object.actor().extract().unwrap_or_else(||
); serde_json::Value::String(object.actor().id().unwrap_or_default())
view! { );
<div class="post-card ml-1 mr-1 mt-1"> view! {
<ActorBanner object=actor /> <div class="ml-1 mr-1 mt-1">
<Activity activity=object.clone() /> <ActorBanner object=actor />
</div> <Activity activity=object.clone() />
<hr/ > </div>
} <hr/ >
}) }
.collect::<Vec<Fragment>>() })
.into_view() .collect::<Vec<Fragment>>()
), .into_view()
} ),
})} }
</ErrorBoundary> })}
</ErrorBoundary>
</div>
</div> </div>
} }
} }

View file

@ -6,6 +6,7 @@ use upub_web::{
Actor, LoginBox, PostBox, Timeline Actor, LoginBox, PostBox, Timeline
}; };
fn main() { fn main() {
_ = console_log::init_with_level(log::Level::Debug); _ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
@ -15,7 +16,7 @@ fn main() {
<nav class="w-100"> <nav class="w-100">
<p> <p>
<code>μpub</code> <code>μpub</code>
<small class="ml-1 mr-1" ><a class="clean" href="/" >micro social network, federated</a></small> <small class="ml-1 mr-1" ><a class="clean" href="/web" >micro social network, federated</a></small>
/* TODO kinda jank with the float but whatever, will do for now */ /* TODO kinda jank with the float but whatever, will do for now */
<small style="float: right" ><a href="https://git.alemi.dev/upub.git" >src</a></small> <small style="float: right" ><a href="https://git.alemi.dev/upub.git" >src</a></small>
</p> </p>
@ -36,8 +37,8 @@ fn main() {
<Router> <Router>
<main> <main>
<Routes> <Routes>
<Route path="/" view=move || view! { <Timeline token=cookie /> } /> <Route path="/web" view=move || view! { <Timeline token=cookie /> } />
<Route path="/users/:id" view=Actor /> <Route path="/web/users/:id" view=Actor />
// <Route path="/object/:id" view=Object /> // <Route path="/object/:id" view=Object />
</Routes> </Routes>
</main> </main>