use leptos::*; use leptos_router::*; use crate::prelude::*; use crate::CONTACT; use leptos_use::{signal_debounced, storage::use_local_storage, use_cookie, use_element_size, use_window_scroll, utils::{FromToStringCodec, JsonCodec}, UseElementSizeReturn}; #[derive(Clone, Copy)] pub struct Feeds { pub home: Timeline, pub global: Timeline, pub notifications: Timeline, // exploration feeds pub user: Timeline, pub server: Timeline, pub context: Timeline, pub tag: Timeline, } impl Feeds { pub fn new(username: &str) -> Self { Feeds { home: Timeline::new(format!("{URL_BASE}/actors/{username}/inbox/page")), notifications: Timeline::new(format!("{URL_BASE}/actors/{username}/notifications/page")), global: Timeline::new(format!("{URL_BASE}/inbox/page")), user: Timeline::new(format!("{URL_BASE}/actors/{username}/outbox/page")), server: Timeline::new(format!("{URL_BASE}/outbox/page")), tag: Timeline::new(format!("{URL_BASE}/tags/upub/page")), context: Timeline::new(format!("{URL_BASE}/outbox/page")), // TODO ehhh } } pub fn reset(&self) { self.home.reset(None); self.notifications.reset(None); self.global.reset(None); self.user.reset(None); self.server.reset(None); self.context.reset(None); self.tag.reset(None); } } #[component] pub fn App() -> impl IntoView { let (token, set_token) = use_cookie::("token"); let (userid, set_userid) = use_cookie::("user_id"); let (config, set_config, _) = use_local_storage::("config"); let auth = Auth { token, userid }; let username = auth.userid.get_untracked() .map(|x| x.split('/').last().unwrap_or_default().to_string()) .unwrap_or_default(); let feeds = Feeds::new(&username); provide_context(auth); provide_context(config); provide_context(feeds); let reply_controls = ReplyControls::default(); provide_context(reply_controls); let screen_width = document().body().map(|x| x.client_width()).unwrap_or_default(); tracing::info!("detected width of {screen_width}"); let (menu, set_menu) = create_signal(screen_width < 768); let (advanced, set_advanced) = create_signal(false); let title_target = move || if auth.present() { "/web/home" } else { "/web/global" }; // refresh token immediately and every hour let refresh = move || spawn_local(async move { Auth::refresh(auth.token, set_token, set_userid).await; }); refresh(); set_interval(refresh, std::time::Duration::from_secs(3600)); view! {


{move || if advanced.get() { view! { }} else { view! { }}}
} >
} /> } } else { view! { } } /> } /> } /> } /> } /> } /> // TODO can we avoid this? } /> } /> } /> // } />
} } #[component] fn Scrollable() -> impl IntoView { let location = use_location(); let feeds = use_context::().expect("missing feeds context"); let relevant_timeline = Signal::derive(move || { let path = location.pathname.get(); if path.contains("/web/home") { Some(feeds.home) } else if path.contains("/web/global") { Some(feeds.global) } else if path.contains("/web/local") { Some(feeds.server) } else if path.starts_with("/web/notifications") { Some(feeds.notifications) } else if path.starts_with("/web/actors") { Some(feeds.user) } else if path.starts_with("/web/objects") { Some(feeds.context) } else { None } }); let breadcrumb = Signal::derive(move || { let path = location.pathname.get(); let mut path_iter = path.split('/').skip(2); // TODO wow this breadcrumb logic really isnt nice can we make it better?? match path_iter.next() { Some("actors") => match path_iter.next() { None => "actors :: all".to_string(), Some(id) => { let mut out = "actors :: ".to_string(); if id.starts_with('+') { out.push_str("proxy"); } else { out.push_str(id); } if let Some(x) = path_iter.next() { out.push_str(" :: "); out.push_str(x); } out }, }, Some("tags") => format!("tags :: {}", path_iter.next().unwrap_or_default()), Some(p) => p.to_string(), None => "?".to_string(), } }); let element = create_node_ref(); let should_load = use_scroll_limit(element, 500.0); provide_context(should_load); view! {
"<<" {crate::NAME}" :: "{breadcrumb} {move || relevant_timeline.get().map(|tl| view! { "↺" })}
} } #[component] pub fn NotFound() -> impl IntoView { view! {

nothing to see here!

} } #[component] pub fn Loader() -> impl IntoView { view! {
} } pub fn use_scroll_limit(el: El, offset: f64) -> Signal where El: Into> + Clone + 'static, T: Into + Clone + 'static, { let (load, set_load) = create_signal(false); let (_x, y) = use_window_scroll(); let UseElementSizeReturn { height: screen_height, .. } = use_element_size("html"); let UseElementSizeReturn { height, .. } = use_element_size(el); let scroll_state = Signal::derive(move || (y.get(), height.get(), screen_height.get())); let scroll_state_throttled = signal_debounced( scroll_state, 50. ); let _ = watch( move || scroll_state_throttled.get(), move |(y, height, screen), _, _| { let before = load.get(); let after = *height <= *screen || y + screen + offset >= *height; let force = *y + screen >= *height; if force || after != before || *height < *screen { set_load.set(after) } }, false, ); load.into() }