use crate::reactive::{Effect, Scope, Signal};
use super::{FeedItem, FeedState};
pub struct SyncSpec<T, Fp = u64> {
key: Box<dyn Fn(&T) -> String>,
fingerprint: Box<dyn Fn(&T) -> Fp>,
visible: Option<VisibleFn<T>>,
render: Box<dyn Fn(&T) -> FeedItem>,
}
type VisibleFn<T> = Box<dyn Fn(&T) -> bool>;
impl<T, Fp> SyncSpec<T, Fp> {
pub fn new(
key: impl Fn(&T) -> String + 'static,
fingerprint: impl Fn(&T) -> Fp + 'static,
render: impl Fn(&T) -> FeedItem + 'static,
) -> SyncSpec<T, Fp> {
SyncSpec {
key: Box::new(key),
fingerprint: Box::new(fingerprint),
visible: None,
render: Box::new(render),
}
}
pub fn visible(mut self, f: impl Fn(&T) -> bool + 'static) -> SyncSpec<T, Fp> {
self.visible = Some(Box::new(f));
self
}
}
impl FeedState {
pub fn sync<T: 'static, Fp: PartialEq + 'static>(
&self,
cx: Scope,
items: Signal<Vec<T>>,
spec: SyncSpec<T, Fp>,
) -> Effect {
let feed = self.clone();
let mut shown: Vec<(String, Fp)> = Vec::new();
let mut synced_mutations: Option<u64> = None;
cx.effect_labeled("feed.sync", move || {
items.with(|list| {
let SyncSpec {
key,
fingerprint,
visible,
render,
} = &spec;
let is_visible = |item: &T| visible.as_ref().is_none_or(|f| f(item));
let foreign =
synced_mutations.is_some_and(|recorded| recorded != feed.mutation_count());
let mut vis_count = 0usize;
let mut prefix_holds = true;
for item in list.iter().filter(|i| is_visible(i)) {
if vis_count < shown.len() && key(item) != shown[vis_count].0 {
prefix_holds = false;
break;
}
vis_count += 1;
}
let fast = !foreign && prefix_holds && vis_count >= shown.len();
if fast {
for (at, item) in list.iter().filter(|i| is_visible(i)).enumerate() {
if at < shown.len() {
let fp = fingerprint(item);
if fp != shown[at].1 {
feed.update(&shown[at].0, render(item));
shown[at].1 = fp;
}
} else {
let k = key(item);
feed.push(k.clone(), render(item));
shown.push((k, fingerprint(item)));
}
}
} else {
feed.clear();
shown.clear();
for item in list.iter().filter(|i| is_visible(i)) {
let k = key(item);
feed.push(k.clone(), render(item));
shown.push((k, fingerprint(item)));
}
}
synced_mutations = Some(feed.mutation_count());
});
})
}
}
#[cfg(test)]
#[path = "feed_sync_tests.rs"]
mod tests;