use std::collections::BTreeSet; use apb::{Activity, Base}; use leptos::*; use crate::prelude::*; #[derive(Debug, Clone, Copy)] pub struct Timeline { pub feed: RwSignal>, pub next: RwSignal, } impl Timeline { pub fn new(url: String) -> Self { let feed = create_rw_signal(vec![]); let next = create_rw_signal(url); Timeline { feed, next } } pub fn reset(&self, url: String) { self.feed.set(vec![]); self.next.set(url); } pub async fn more(&self, auth: Signal>) -> reqwest::Result<()> { use apb::{Collection, CollectionPage}; let feed_url = self.next.get(); let collection : serde_json::Value = Http::fetch(&feed_url, auth).await?; let activities : Vec = collection .ordered_items() .collect(); let mut feed = self.feed.get(); let mut older = process_activities(activities, auth).await; feed.append(&mut older); self.feed.set(feed); if let Some(next) = collection.next().id() { self.next.set(next); } Ok(()) } } #[component] pub fn TimelineFeed(tl: Timeline) -> impl IntoView { let auth = use_context::().expect("missing auth context"); view! { match item.base_type() { Some(apb::BaseType::Object(apb::ObjectType::Activity(_))) => { let author_id = item.actor().id().unwrap_or_default(); let author = CACHE.get(&author_id).unwrap_or(serde_json::Value::String(author_id.clone())); let object_id = item.object().id().unwrap_or_default(); let object = CACHE.get(&object_id).map(|obj| view! { }); view! { {object}
}.into_view() }, Some(apb::BaseType::Object(apb::ObjectType::Note)) => view! {
}.into_view(), _ => view! {

type not implemented


}.into_view(), }, None => view! {

{id}" "[go]

}.into_view(), } } / >
} } async fn process_activities( activities: Vec, auth: Signal>, ) -> Vec { use apb::ActivityMut; let mut sub_tasks = Vec::new(); let mut gonna_fetch = BTreeSet::new(); let mut out = Vec::new(); for activity in activities { // save embedded object if present if let Some(object) = activity.object().get() { if let Some(object_uri) = object.id() { CACHE.put(object_uri.to_string(), object.clone()); } } else { // try fetching it if let Some(object_id) = activity.object().id() { if !gonna_fetch.contains(&object_id) { gonna_fetch.insert(object_id.clone()); sub_tasks.push(fetch_and_update("objects", object_id, auth)); } } } // save activity, removing embedded object let object_id = activity.object().id(); if let Some(activity_id) = activity.id() { out.push(activity_id.to_string()); CACHE.put( activity_id.to_string(), activity.clone().set_object(apb::Node::maybe_link(object_id)) ); } if let Some(uid) = activity.actor().id() { if CACHE.get(&uid).is_none() && !gonna_fetch.contains(&uid) { gonna_fetch.insert(uid.clone()); sub_tasks.push(fetch_and_update("users", uid, auth)); } } } futures::future::join_all(sub_tasks).await; out } async fn fetch_and_update(kind: &'static str, id: String, auth: Signal>) { match Http::fetch(&Uri::api(kind, &id, false), auth).await { Ok(data) => CACHE.put(id, data), Err(e) => console_warn(&format!("could not fetch '{id}': {e}")), } }