feat(web): infinite scroll (optional) in timelines
This commit is contained in:
parent
c2f4f24586
commit
9b64503c02
3 changed files with 64 additions and 26 deletions
|
@ -2,6 +2,7 @@ use std::{collections::BTreeSet, pin::Pin, sync::Arc};
|
|||
|
||||
use apb::{Activity, ActivityMut, Base, Object};
|
||||
use leptos::*;
|
||||
use leptos_use::{signal_debounced, signal_throttled, use_display_media, use_document_visibility, use_element_size, use_infinite_scroll_with_options, use_scroll, use_scroll_with_options, use_window, use_window_scroll, UseDisplayMediaReturn, UseElementSizeReturn, UseInfiniteScrollOptions, UseScrollOptions, UseScrollReturn};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
@ -137,7 +138,28 @@ pub fn TimelineReplies(tl: Timeline, root: String) -> impl IntoView {
|
|||
#[component]
|
||||
pub fn TimelineFeed(tl: Timeline) -> impl IntoView {
|
||||
let auth = use_context::<Auth>().expect("missing auth context");
|
||||
let config = use_context::<Signal<crate::Config>>().expect("missing config context");
|
||||
// double view height: preload when 1 screen away
|
||||
let view_height = 2.0 * window()
|
||||
.inner_height()
|
||||
.map_or(500.0, |v| v.as_f64().unwrap_or_default());
|
||||
let scroll_ref = create_node_ref();
|
||||
let UseElementSizeReturn { width: _w, height } = use_element_size(scroll_ref);
|
||||
let (_x, scroll) = use_window_scroll();
|
||||
let scroll_debounced = signal_throttled(scroll, 500.0);
|
||||
let _auto_loader = create_local_resource(
|
||||
move || (scroll_debounced.get(), height.get()),
|
||||
move |(s, h)| async move {
|
||||
if !config.get().infinite_scroll { return }
|
||||
if s > 0.0 && h - s < view_height && !tl.loading.get() {
|
||||
if let Err(e) = tl.more(auth).await {
|
||||
tracing::error!("auto load failed: {e}");
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
view! {
|
||||
<div ref=scroll_ref>
|
||||
<For
|
||||
each=move || tl.feed.get()
|
||||
key=|k| k.to_string()
|
||||
|
@ -153,16 +175,11 @@ pub fn TimelineFeed(tl: Timeline) -> impl IntoView {
|
|||
}
|
||||
}
|
||||
/ >
|
||||
</div>
|
||||
<div class="center mt-1 mb-1" class:hidden=tl.over >
|
||||
<button type="button"
|
||||
prop:disabled=tl.loading
|
||||
on:click=move |_| {
|
||||
spawn_local(async move {
|
||||
if let Err(e) = tl.more(auth).await {
|
||||
tracing::error!("error fetching more items for timeline: {e}");
|
||||
}
|
||||
})
|
||||
}
|
||||
on:click=move |_| load_more(tl, auth)
|
||||
>
|
||||
{move || if tl.loading.get() {
|
||||
view! { "loading"<span class="dots"></span> }.into_view()
|
||||
|
@ -172,6 +189,16 @@ pub fn TimelineFeed(tl: Timeline) -> impl IntoView {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_more(tl: Timeline, auth: Auth) {
|
||||
if !tl.loading.get() {
|
||||
spawn_local(async move {
|
||||
if let Err(e) = tl.more(auth).await {
|
||||
tracing::error!("error fetching more items for timeline: {e}");
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_activities(activities: Vec<serde_json::Value>, auth: Auth) -> Vec<String> {
|
||||
let mut sub_tasks : Vec<Pin<Box<dyn futures::Future<Output = ()>>>> = Vec::new();
|
||||
let mut gonna_fetch = BTreeSet::new();
|
||||
|
|
|
@ -11,6 +11,9 @@ pub struct Config {
|
|||
#[serde_inline_default(true)]
|
||||
pub loop_videos: bool,
|
||||
|
||||
#[serde_inline_default(true)]
|
||||
pub infinite_scroll: bool,
|
||||
|
||||
#[serde_inline_default("#BF616A".to_string())]
|
||||
pub accent_color: String,
|
||||
}
|
||||
|
|
|
@ -57,6 +57,14 @@ pub fn ConfigPage(setter: WriteSignal<crate::Config>) -> impl IntoView {
|
|||
/> collapse content warnings
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<span title="new posts will be fetched automatically when scrolling down enough">
|
||||
<input type="checkbox" class="mr-1"
|
||||
prop:checked=get_cfg!(infinite_scroll)
|
||||
on:input=set_cfg!(infinite_scroll)
|
||||
/> infinite scroll
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
accent color
|
||||
<input type="text" class="ma-1"
|
||||
|
|
Loading…
Reference in a new issue