#![allow(non_snake_case)]
use std::collections::HashMap;
use std::hash::Hash;
use std::ops::Deref;
use sycamore_macro::{component, Props};
use wasm_bindgen::prelude::*;
use crate::*;
#[derive(Props)]
pub struct KeyedProps<T, K, U, List, F, Key>
where
List: Into<MaybeDyn<Vec<T>>> + 'static,
F: Fn(T) -> U + 'static,
Key: Fn(&T) -> K + 'static,
T: 'static,
{
list: List,
view: F,
key: Key,
#[prop(default)]
_phantom: std::marker::PhantomData<(T, K, U)>,
}
#[component]
pub fn Keyed<T, K, U, List, F, Key>(props: KeyedProps<T, K, U, List, F, Key>) -> View
where
T: PartialEq + Clone + 'static,
K: Hash + Eq + 'static,
U: Into<View>,
List: Into<MaybeDyn<Vec<T>>> + 'static,
F: Fn(T) -> U + 'static,
Key: Fn(&T) -> K + 'static,
{
let KeyedProps {
list, view, key, ..
} = props;
if is_ssr!() {
View::from(
list.into()
.evaluate()
.into_iter()
.map(|x| view(x).into())
.collect::<Vec<_>>(),
)
} else {
let start = HtmlNode::create_marker_node();
let start_node = start.as_web_sys().clone();
let end = HtmlNode::create_marker_node();
let end_node = end.as_web_sys().clone();
let scope = use_current_scope();
create_effect_initial(move || {
scope.run_in(move || {
let nodes = map_keyed(list, move |x| view(x).into().as_web_sys(), key);
let flattened = nodes.map(|x| x.iter().flatten().cloned().collect::<Vec<_>>());
let view = flattened.with(|x| {
View::from_nodes(
x.iter()
.map(|x| HtmlNode::from_web_sys(x.clone()))
.collect(),
)
});
(
Box::new(move || {
let mut new = flattened.get_clone();
let mut old = utils::get_nodes_between(&start_node, &end_node);
new.push(end_node.clone());
old.push(end_node.clone());
if let Some(parent) = start_node.parent_node() {
reconcile_fragments(&parent, &mut old, &new);
}
}) as Box<dyn FnMut()>,
(start, view, end).into(),
)
})
})
}
}
#[derive(Props)]
pub struct IndexedProps<T, U, List, F>
where
List: Into<MaybeDyn<Vec<T>>> + 'static,
F: Fn(T) -> U + 'static,
T: 'static,
{
list: List,
view: F,
#[prop(default)]
_phantom: std::marker::PhantomData<(T, U)>,
}
#[component]
pub fn Indexed<T, U, List, F>(props: IndexedProps<T, U, List, F>) -> View
where
T: PartialEq + Clone + 'static,
U: Into<View>,
List: Into<MaybeDyn<Vec<T>>> + 'static,
F: Fn(T) -> U + 'static,
{
let IndexedProps { list, view, .. } = props;
if is_ssr!() {
View::from(
list.into()
.evaluate()
.into_iter()
.map(|x| view(x).into())
.collect::<Vec<_>>(),
)
} else {
let start = HtmlNode::create_marker_node();
let start_node = start.as_web_sys().clone();
let end = HtmlNode::create_marker_node();
let end_node = end.as_web_sys().clone();
let scope = use_current_scope();
create_effect_initial(move || {
scope.run_in(move || {
let nodes = map_indexed(list, move |x| view(x).into().as_web_sys());
let flattened = nodes.map(|x| x.iter().flatten().cloned().collect::<Vec<_>>());
let view = flattened.with(|x| {
View::from_nodes(
x.iter()
.map(|x| HtmlNode::from_web_sys(x.clone()))
.collect(),
)
});
(
Box::new(move || {
let mut new = flattened.get_clone();
let mut old = utils::get_nodes_between(&start_node, &end_node);
new.push(end_node.clone());
old.push(end_node.clone());
if let Some(parent) = start_node.parent_node() {
reconcile_fragments(&parent, &mut old, &new);
}
}) as Box<dyn FnMut()>,
(start, view, end).into(),
)
})
})
}
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = web_sys::Node)]
pub(super) type NodeWithId;
#[wasm_bindgen(method, getter, js_name = "$id")]
pub fn node_id(this: &NodeWithId) -> Option<usize>;
#[wasm_bindgen(method, setter, js_name = "$id")]
pub fn set_node_id(this: &NodeWithId, id: usize);
}
struct HashableNode<'a>(&'a NodeWithId, usize);
impl<'a> HashableNode<'a> {
thread_local! {
static NEXT_ID: Cell<usize> = const { Cell::new(0) };
}
fn new(node: &'a web_sys::Node) -> Self {
let node = node.unchecked_ref::<NodeWithId>();
let id = if let Some(id) = node.node_id() {
id
} else {
Self::NEXT_ID.with(|cell| {
let id = cell.get();
cell.set(id + 1);
node.set_node_id(id);
id
})
};
Self(node, id)
}
}
impl PartialEq for HashableNode<'_> {
fn eq(&self, other: &Self) -> bool {
self.1 == other.1
}
}
impl Eq for HashableNode<'_> {}
impl Hash for HashableNode<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.1.hash(state);
}
}
impl Deref for HashableNode<'_> {
type Target = NodeWithId;
fn deref(&self) -> &Self::Target {
self.0
}
}
fn reconcile_fragments(parent: &web_sys::Node, a: &mut [web_sys::Node], b: &[web_sys::Node]) {
debug_assert!(!a.is_empty(), "a cannot be empty");
#[cfg(debug_assertions)]
{
for (i, node) in a.iter().enumerate() {
if node.parent_node().as_ref() != Some(parent) {
panic!("node {i} in existing nodes Vec is not a child of parent. node = {node:#?}",);
}
}
}
let b_len = b.len();
let mut a_end = a.len();
let mut b_end = b_len;
let mut a_start = 0;
let mut b_start = 0;
let mut map = None::<HashMap<HashableNode, usize>>;
let after = a[a_end - 1].next_sibling();
while a_start < a_end || b_start < b_end {
if a_end == a_start {
let node = if b_end < b_len {
if b_start != 0 {
b[b_start - 1].next_sibling()
} else {
Some(b[b_end - b_start].clone())
}
} else {
after.clone()
};
for new_node in &b[b_start..b_end] {
parent.insert_before(new_node, node.as_ref()).unwrap();
}
b_start = b_end;
} else if b_end == b_start {
for node in &a[a_start..a_end] {
if map.is_none() || !map.as_ref().unwrap().contains_key(&HashableNode::new(node)) {
parent.remove_child(node).unwrap();
}
}
a_start = a_end;
} else if a[a_start] == b[b_start] {
a_start += 1;
b_start += 1;
} else if a[a_end - 1] == b[b_end - 1] {
a_end -= 1;
b_end -= 1;
} else if a[a_start] == b[b_end - 1] && b[b_start] == a[a_end - 1] {
let node = a[a_end - 1].next_sibling();
parent
.insert_before(&b[b_start], a[a_start].next_sibling().as_ref())
.unwrap();
parent.insert_before(&b[b_end - 1], node.as_ref()).unwrap();
a_start += 1;
b_start += 1;
a_end -= 1;
b_end -= 1;
a[a_end] = b[b_end].clone();
} else {
if map.is_none() {
let tmp = b[b_start..b_end]
.iter()
.enumerate()
.map(|(i, g)| (HashableNode::new(g), b_start + i))
.collect();
map = Some(tmp);
}
let map = map.as_ref().unwrap();
if let Some(&index) = map.get(&HashableNode::new(&a[a_start])) {
if b_start < index && index < b_end {
let mut i = a_start;
let mut sequence = 1;
let mut t;
while i + 1 < a_end && i + 1 < b_end {
i += 1;
t = map.get(&HashableNode::new(&a[i])).copied();
if t != Some(index + sequence) {
break;
}
sequence += 1;
}
if sequence > index - b_start {
let node = &a[a_start];
while b_start < index {
parent.insert_before(&b[b_start], Some(node)).unwrap();
b_start += 1;
}
} else {
parent.replace_child(&b[b_start], &a[a_start]).unwrap();
a_start += 1;
b_start += 1;
}
} else {
a_start += 1;
}
} else {
parent.remove_child(&a[a_start]).unwrap();
a_start += 1;
}
}
}
#[cfg(debug_assertions)]
{
for (i, node) in b.iter().enumerate() {
if node.parent_node().as_ref() != Some(parent) {
panic!(
"node {i} in new nodes Vec is not a child of parent after reconciliation. node = {node:#?}",
);
}
}
}
}