use crate::SilexError;
use crate::flow::for_loop::ForLoopSource;
use silex_core::reactivity::{
Effect, NodeId, ReadSignal, WriteSignal, batch, create_scope, dispose, signal,
};
use silex_core::traits::{IntoSignal, Set, With};
use silex_dom::View;
use std::cell::RefCell;
use std::rc::Rc;
use web_sys::Node;
#[derive(Clone)]
pub struct Index<ItemsFn, Item, Items, MapFn, V> {
items: Rc<ItemsFn>,
map: Rc<MapFn>,
_marker: std::marker::PhantomData<(Item, Items, V)>,
}
impl<ItemsFn, Item, Items, MapFn, V> Index<ItemsFn, Item, Items, MapFn, V>
where
ItemsFn: With<Value = Items> + 'static,
Items: ForLoopSource<Item = Item> + 'static,
MapFn: Fn(ReadSignal<Item>, usize) -> V + 'static,
V: View,
Item: 'static,
{
pub fn new(items: impl IntoSignal<Value = Items, Signal = ItemsFn>, map: MapFn) -> Self {
Self {
items: Rc::new(items.into_signal()),
map: Rc::new(map),
_marker: std::marker::PhantomData,
}
}
}
struct IndexRow<Item> {
setter: WriteSignal<Item>,
scope_id: NodeId,
nodes: Vec<Node>,
}
impl<ItemsFn, Item, Items, MapFn, V> View for Index<ItemsFn, Item, Items, MapFn, V>
where
ItemsFn: With<Value = Items> + 'static,
Items: ForLoopSource<Item = Item> + 'static,
MapFn: Fn(ReadSignal<Item>, usize) -> V + 'static,
V: View,
Item: Clone + 'static, {
fn mount(self, parent: &Node) {
let document = silex_dom::document();
let start_marker = document.create_comment("index-start");
let start_node: Node = start_marker.into();
if let Err(e) = parent.append_child(&start_node).map_err(SilexError::from) {
silex_core::error::handle_error(e);
return;
}
let end_marker = document.create_comment("index-end");
let end_node: Node = end_marker.into();
if let Err(e) = parent.append_child(&end_node).map_err(SilexError::from) {
silex_core::error::handle_error(e);
return;
}
let rows = Rc::new(RefCell::new(Vec::<IndexRow<Item>>::new()));
let items_fn = self.items;
let map_fn = self.map;
Effect::new(move |_| {
items_fn.with(|items| {
let items_slice = match items.as_slice() {
Ok(s) => s,
Err(e) => {
silex_core::error::handle_error(e);
return;
}
};
let mut rows_lock = rows.borrow_mut();
batch(|| {
let new_len = items_slice.len();
let old_len = rows_lock.len();
let common_len = std::cmp::min(new_len, old_len);
for (i, item) in items_slice.iter().take(common_len).enumerate() {
rows_lock[i].setter.set(item.clone());
}
if new_len > old_len {
for (i, item) in items_slice[common_len..].iter().enumerate() {
let real_index = common_len + i;
let (get, set) = signal(item.clone());
let fragment = document.create_document_fragment();
let fragment_node: Node = fragment.clone().into();
let fragment_node_clone = fragment_node.clone();
let map_fn = map_fn.clone();
let scope_id = create_scope(move || {
(map_fn)(get, real_index).mount(&fragment_node_clone);
});
let nodes_list = fragment.child_nodes();
let len = nodes_list.length();
let mut nodes = Vec::with_capacity(len as usize);
for j in 0..len {
if let Some(n) = nodes_list.item(j) {
nodes.push(n);
}
}
if let Some(p) = end_node.parent_node() {
let _ = p.insert_before(&fragment_node, Some(&end_node)); }
rows_lock.push(IndexRow {
setter: set,
scope_id,
nodes,
});
}
}
if old_len > new_len {
let to_remove = rows_lock.split_off(new_len);
for row in to_remove {
dispose(row.scope_id);
for node in row.nodes {
if let Some(p) = node.parent_node() {
let _ = p.remove_child(&node);
}
}
}
}
});
});
});
}
}