use crate::{SilexError, SilexResult};
use silex_core::reactivity::{Effect, NodeId, batch, create_scope, dispose};
use silex_core::traits::Accessor;
use silex_dom::View;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use web_sys::Node;
pub trait IntoForLoopResult {
type Item;
type Iter: IntoIterator<Item = Self::Item>;
fn into_result(self) -> SilexResult<Self::Iter>;
}
impl<T> IntoForLoopResult for Vec<T> {
type Item = T;
type Iter = std::vec::IntoIter<T>;
fn into_result(self) -> SilexResult<Self::Iter> {
Ok(self.into_iter())
}
}
impl<T> IntoForLoopResult for Option<Vec<T>> {
type Item = T;
type Iter = std::vec::IntoIter<T>;
fn into_result(self) -> SilexResult<Self::Iter> {
Ok(self.unwrap_or_default().into_iter())
}
}
impl<T> IntoForLoopResult for SilexResult<Vec<T>> {
type Item = T;
type Iter = std::vec::IntoIter<T>;
fn into_result(self) -> SilexResult<Self::Iter> {
self.map(|v| v.into_iter())
}
}
pub struct For<ItemsFn, Item, Items, KeyFn, Key, MapFn, V> {
items: Rc<ItemsFn>,
key: Rc<KeyFn>,
map: Rc<MapFn>,
_marker: std::marker::PhantomData<(Item, Items, Key, V)>,
}
impl<ItemsFn, Item, Items, KeyFn, Key, MapFn, V> Clone
for For<ItemsFn, Item, Items, KeyFn, Key, MapFn, V>
{
fn clone(&self) -> Self {
Self {
items: self.items.clone(),
key: self.key.clone(),
map: self.map.clone(),
_marker: std::marker::PhantomData,
}
}
}
impl<ItemsFn, Item, Items, KeyFn, Key, MapFn, V> For<ItemsFn, Item, Items, KeyFn, Key, MapFn, V>
where
ItemsFn: Accessor<Items> + 'static,
Items: IntoForLoopResult<Item = Item>,
KeyFn: Fn(&Item) -> Key + 'static,
MapFn: Fn(Item) -> V + 'static,
V: View,
Item: 'static,
{
pub fn new(items: ItemsFn, key: KeyFn, map: MapFn) -> Self {
Self {
items: Rc::new(items),
key: Rc::new(key),
map: Rc::new(map),
_marker: std::marker::PhantomData,
}
}
}
impl<ItemsFn, Item, Items, KeyFn, Key, MapFn, V> View
for For<ItemsFn, Item, Items, KeyFn, Key, MapFn, V>
where
ItemsFn: Accessor<Items> + 'static,
Items: IntoForLoopResult<Item = Item> + 'static,
<Items as IntoForLoopResult>::Iter: IntoIterator<Item = Item>,
KeyFn: Fn(&Item) -> Key + 'static,
Key: std::hash::Hash + Eq + Clone + 'static,
MapFn: Fn(Item) -> V + 'static,
V: View,
Item: 'static,
{
fn mount(self, parent: &Node) {
let document = silex_dom::document();
let start_marker = document.create_comment("for-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("for-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 items_fn = self.items;
let key_fn = self.key;
let map_fn = self.map;
let active_rows = Rc::new(RefCell::new(HashMap::<Key, (Vec<Node>, NodeId)>::new()));
Effect::new(move |_| {
let mut rows_map = active_rows.borrow_mut();
let result = items_fn.value().into_result();
let items_iter = match result {
Ok(iter) => iter,
Err(e) => {
silex_core::error::handle_error(e);
return;
}
};
batch(|| {
let mut new_keys = HashSet::new();
let mut new_rows_order = Vec::new();
for item in items_iter {
let key = (key_fn)(&item);
new_keys.insert(key.clone());
if let Some((nodes, id)) = rows_map.get(&key) {
new_rows_order.push((key, nodes.clone(), *id, None));
} else {
let fragment = document.create_document_fragment();
let fragment_node: Node = fragment.clone().into();
let map_fn = map_fn.clone();
let scope_id = create_scope(move || {
let view = (map_fn)(item);
view.mount(&fragment_node);
});
let nodes_list = fragment.child_nodes();
let len = nodes_list.length();
let mut nodes = Vec::with_capacity(len as usize);
for i in 0..len {
if let Some(n) = nodes_list.item(i) {
nodes.push(n);
}
}
new_rows_order.push((key, nodes, scope_id, Some(fragment)));
};
}
rows_map.retain(|k, (nodes, id)| {
if !new_keys.contains(k) {
for node in nodes {
if let Some(p) = node.parent_node() {
let _ = p.remove_child(node);
}
}
dispose(*id);
false
} else {
true
}
});
let mut cursor = start_node.next_sibling();
for (key, nodes, id, fragment_opt) in new_rows_order {
if let Some(frag) = fragment_opt {
let effective_cursor = cursor.as_ref().unwrap_or(&end_node);
if let Some(parent) = effective_cursor.parent_node() {
let _ = parent.insert_before(&frag, Some(effective_cursor));
}
rows_map.insert(key, (nodes, id));
} else {
if nodes.is_empty() {
rows_map.insert(key, (nodes, id));
continue;
}
let first_node = &nodes[0];
let is_in_place = if let Some(ref c) = cursor {
c.is_same_node(Some(first_node))
} else {
false
};
if is_in_place {
for _ in 0..nodes.len() {
cursor = cursor.and_then(|c| c.next_sibling());
}
} else {
let effective_cursor = cursor.as_ref().unwrap_or(&end_node);
if let Some(parent) = effective_cursor.parent_node() {
for node in &nodes {
let _ = parent.insert_before(node, Some(effective_cursor));
}
}
}
rows_map.insert(key, (nodes, id));
}
}
});
});
}
}