use crate::browser::dom::Namespace;
use crate::virtual_dom::{El, ElKey, Node, Tag, Text};
use std::borrow::Borrow;
use std::collections::{BTreeSet, VecDeque};
use std::iter::Peekable;
#[allow(clippy::large_enum_variant)]
pub enum PatchCommand<'a, Ms: 'static> {
AppendEl {
el_new: &'a mut El<Ms>,
},
AppendText {
text_new: &'a mut Text,
},
InsertEl {
el_new: &'a mut El<Ms>,
next_node: web_sys::Node,
},
InsertText {
text_new: &'a mut Text,
next_node: web_sys::Node,
},
PatchEl {
el_old: El<Ms>,
el_new: &'a mut El<Ms>,
},
PatchText {
text_old: Text,
text_new: &'a mut Text,
},
ReplaceElByEl {
el_old: El<Ms>,
el_new: &'a mut El<Ms>,
},
ReplaceElByText {
el_old: El<Ms>,
text_new: &'a mut Text,
},
ReplaceTextByEl {
text_old: Text,
el_new: &'a mut El<Ms>,
},
RemoveEl {
el_old: El<Ms>,
},
RemoveText {
text_old: Text,
},
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum PatchKey {
Element {
namespace: Option<Namespace>,
tag: Tag,
el_key: Option<ElKey>,
},
Text,
}
impl PatchKey {
fn new<Ms: 'static>(node: &Node<Ms>) -> Option<Self> {
match node {
Node::Element(el) => Some(PatchKey::Element {
namespace: el.namespace.clone(),
tag: el.tag.clone(),
el_key: el.key.clone(),
}),
Node::Text(_) => Some(PatchKey::Text),
Node::Empty | Node::NoChange => None,
}
}
}
pub struct PatchGen<'a, Ms, OI, NI>
where
Ms: 'static,
OI: Iterator<Item = Node<Ms>>,
NI: Iterator<Item = &'a mut Node<Ms>>,
{
old_children_iter: Peekable<OI>,
new_children_iter: Peekable<NI>,
old_children: VecDeque<Node<Ms>>,
new_children: VecDeque<&'a mut Node<Ms>>,
matching_child_old: Option<OI::Item>,
matching_child_new: Option<NI::Item>,
matching_key: Option<PatchKey>,
keyed_mode: bool,
}
impl<'a, Ms, OI, NI> PatchGen<'a, Ms, OI, NI>
where
Ms: 'static,
OI: Iterator<Item = Node<Ms>>,
NI: Iterator<Item = &'a mut Node<Ms>>,
{
pub fn new(old_children_iter: OI, new_children_iter: NI) -> Self {
Self {
old_children_iter: old_children_iter.peekable(),
new_children_iter: new_children_iter.peekable(),
old_children: VecDeque::new(),
new_children: VecDeque::new(),
matching_child_old: None,
matching_child_new: None,
matching_key: None,
keyed_mode: false,
}
}
fn next_command(&mut self) -> Option<PatchCommand<'a, Ms>> {
if !self.keyed_mode {
return self.yield_keyless();
}
if self.matching_key.is_some() {
return self.yield_keyed();
}
if self.old_children_iter.peek().is_some() || self.new_children_iter.peek().is_some() {
self.matching_key = find_matching(
&mut self.old_children_iter,
&mut self.old_children,
&mut self.new_children_iter,
&mut self.new_children,
);
if self.matching_key.is_some() {
return self.yield_keyed();
}
}
self.yield_remaining()
}
fn yield_keyless(&mut self) -> Option<PatchCommand<'a, Ms>> {
let (child_old, child_new) = loop {
let old = self
.old_children
.pop_back()
.or_else(|| self.old_children_iter.next());
let new = self.new_children_iter.next();
if let (Some(Node::Empty), Some(Node::Empty)) = (&old, &new) {
continue;
}
break (old, new);
};
match (child_old, child_new) {
(Some(child_old), Some(child_new)) => {
if child_old.el_key().is_none() && child_new.el_key().is_none() {
return self.patch_or_replace(child_old, child_new);
}
self.keyed_mode = true;
let key_old = PatchKey::new(&child_old);
let key_new = PatchKey::new(child_new);
if key_old == key_new {
self.matching_key = key_new;
}
if !child_old.is_empty() {
self.old_children.push_back(child_old);
}
if !child_new.is_empty() {
self.new_children.push_back(child_new);
}
self.next_command()
}
(None, Some(child_new)) => self.append(child_new),
(Some(child_old), None) => self.remove(child_old),
(None, None) => None,
}
}
fn yield_keyed(&mut self) -> Option<PatchCommand<'a, Ms>> {
match (
self.matching_child_old.as_ref(),
self.matching_child_new.as_ref(),
) {
(None, None) => {
let child_old = self
.old_children
.pop_back()
.expect("old child from the queue");
let child_new = self
.new_children
.pop_back()
.expect("new child from the queue");
let key_old = PatchKey::new(&child_old);
let key_new = PatchKey::new(child_new);
if key_old == self.matching_key && key_new == self.matching_key {
self.matching_child_old = Some(child_old);
self.matching_child_new = Some(child_new);
return self.yield_keyed();
}
if key_old == self.matching_key {
let next_node = child_old.node_ws().unwrap().clone();
self.matching_child_old = Some(child_old);
return self.insert(child_new, next_node);
}
if key_new == self.matching_key {
self.matching_child_new = Some(child_new);
return self.remove(child_old);
}
self.patch_or_replace(child_old, child_new)
}
(Some(child_old), None) => {
let child_new = self
.new_children
.pop_back()
.expect("node with a matching key");
if PatchKey::new(child_new) == self.matching_key {
self.matching_child_new = Some(child_new);
return self.yield_keyed();
}
let next_node = child_old
.node_ws()
.expect("old node connected to web_sys node")
.clone();
self.insert(child_new, next_node)
}
(None, Some(_)) => {
let child_old = self
.old_children
.pop_back()
.expect("node with a matching key");
if PatchKey::new(&child_old) == self.matching_key {
self.matching_child_old = Some(child_old);
return self.yield_keyed();
}
self.remove(child_old)
}
(Some(_), Some(_)) => {
self.matching_key = None;
let child_old = self.matching_child_old.take().unwrap();
let child_new = self.matching_child_new.take().unwrap();
self.patch_or_replace(child_old, child_new)
}
}
}
fn yield_remaining(&mut self) -> Option<PatchCommand<'a, Ms>> {
match (self.old_children.pop_back(), self.new_children.pop_back()) {
(Some(child_old), Some(child_new)) => self.patch_or_replace(child_old, child_new),
(Some(child_old), None) => self.remove(child_old),
(None, Some(child_new)) => self.append(child_new),
(None, None) => None,
}
}
fn append(&mut self, child_new: &'a mut Node<Ms>) -> Option<PatchCommand<'a, Ms>> {
Some(match child_new {
Node::Element(el_new) => PatchCommand::AppendEl { el_new },
Node::Text(text_new) => PatchCommand::AppendText { text_new },
Node::Empty | Node::NoChange => return self.next_command(),
})
}
fn insert(
&mut self,
child_new: &'a mut Node<Ms>,
next_node: web_sys::Node,
) -> Option<PatchCommand<'a, Ms>> {
Some(match child_new {
Node::Element(el_new) => PatchCommand::InsertEl { el_new, next_node },
Node::Text(text_new) => PatchCommand::InsertText {
text_new,
next_node,
},
Node::Empty | Node::NoChange => return self.next_command(),
})
}
#[allow(clippy::option_if_let_else)]
fn patch_or_replace(
&mut self,
child_old: Node<Ms>,
child_new: &'a mut Node<Ms>,
) -> Option<PatchCommand<'a, Ms>> {
Some(match child_old {
Node::Element(el_old) => match child_new {
Node::Element(el_new) => {
if el_can_be_patched(&el_old, el_new) {
PatchCommand::PatchEl { el_old, el_new }
} else {
PatchCommand::ReplaceElByEl { el_old, el_new }
}
}
Node::Text(text_new) => PatchCommand::ReplaceElByText { el_old, text_new },
Node::Empty => PatchCommand::RemoveEl { el_old },
Node::NoChange => {
*child_new = Node::Element(el_old);
return self.next_command();
}
},
Node::Text(text_old) => match child_new {
Node::Element(el_new) => PatchCommand::ReplaceTextByEl { text_old, el_new },
Node::Text(text_new) => PatchCommand::PatchText { text_old, text_new },
Node::Empty => PatchCommand::RemoveText { text_old },
Node::NoChange => {
*child_new = Node::Text(text_old);
return self.next_command();
}
},
Node::Empty => match child_new {
Node::Element(el_new) => {
if let Some(next_node) =
find_next_node_ws(&mut self.old_children_iter, &mut self.old_children)
{
PatchCommand::InsertEl { el_new, next_node }
} else {
PatchCommand::AppendEl { el_new }
}
}
Node::Text(text_new) => {
if let Some(next_node) =
find_next_node_ws(&mut self.old_children_iter, &mut self.old_children)
{
PatchCommand::InsertText {
text_new,
next_node,
}
} else {
PatchCommand::AppendText { text_new }
}
}
Node::Empty => return self.next_command(),
Node::NoChange => {
*child_new = child_old;
return self.next_command();
}
},
Node::NoChange => panic!("Node::NoChange cannot be an old VDOM node!"),
})
}
fn remove(&mut self, child_old: Node<Ms>) -> Option<PatchCommand<'a, Ms>> {
Some(match child_old {
Node::Element(el_old) => PatchCommand::RemoveEl { el_old },
Node::Text(text_old) => PatchCommand::RemoveText { text_old },
Node::Empty | Node::NoChange => return self.next_command(),
})
}
}
impl<'a, Ms, OI, NI> Iterator for PatchGen<'a, Ms, OI, NI>
where
Ms: 'static,
OI: Iterator<Item = Node<Ms>>,
NI: Iterator<Item = &'a mut Node<Ms>>,
{
type Item = PatchCommand<'a, Ms>;
fn next(&mut self) -> Option<Self::Item> {
self.next_command()
}
}
pub fn el_can_be_patched<Ms>(el_old: &El<Ms>, el_new: &El<Ms>) -> bool {
el_old.namespace == el_new.namespace && el_old.tag == el_new.tag && el_old.key == el_new.key
}
fn find_matching<OI, NI, ON, NN, Ms>(
old_children_iter: &mut Peekable<OI>,
old_children: &mut VecDeque<ON>,
new_children_iter: &mut Peekable<NI>,
new_children: &mut VecDeque<NN>,
) -> Option<PatchKey>
where
OI: Iterator<Item = ON>,
NI: Iterator<Item = NN>,
ON: Borrow<Node<Ms>>,
NN: Borrow<Node<Ms>>,
Ms: 'static,
{
let mut seen_old_keys: BTreeSet<_> = old_children
.iter()
.filter_map(|node| PatchKey::new(node.borrow()))
.collect();
let mut seen_new_keys: BTreeSet<_> = new_children
.iter()
.filter_map(|node| PatchKey::new(node.borrow()))
.collect();
while old_children_iter.peek().is_some() || new_children_iter.peek().is_some() {
let should_pick_old_child = old_children_iter.peek().is_some()
&& (new_children_iter.peek().is_none() || new_children.len() > old_children.len());
if should_pick_old_child {
if let Some(key) = fetch_next_item(old_children_iter, old_children)
.and_then(|child| PatchKey::new(child.borrow()))
{
if seen_new_keys.contains(&key) {
return Some(key);
}
seen_old_keys.insert(key);
}
} else if new_children_iter.peek().is_some() {
if let Some(key) = fetch_next_item(new_children_iter, new_children)
.and_then(|child| PatchKey::new(child.borrow()))
{
if seen_old_keys.contains(&key) {
return Some(key);
}
seen_new_keys.insert(key);
}
}
}
None
}
fn find_next_node_ws<I, N, Ms>(
children_iter: &mut Peekable<I>,
children: &mut VecDeque<N>,
) -> Option<web_sys::Node>
where
I: Iterator<Item = N>,
N: Borrow<Node<Ms>>,
Ms: 'static,
{
if let node_ws @ Some(_) = children.iter().find_map(|child| child.borrow().node_ws()) {
return node_ws.cloned();
}
while let Some(child) = fetch_next_item(children_iter, children) {
if let node_ws @ Some(_) = child.borrow().node_ws() {
return node_ws.cloned();
}
}
None
}
fn fetch_next_item<'a, I, T>(source_iter: &'a mut I, queue: &'a mut VecDeque<T>) -> Option<&'a T>
where
I: Iterator<Item = T>,
{
source_iter.next().and_then(move |item| {
queue.push_front(item);
queue.front()
})
}