use crate::v_node::{VElement, VText};
use crate::Html;
use crate::HtmlNode;
use crate::VNode;
use kagura::component::Render;
use kagura::node::{Msg, NodeCmd};
use std::collections::VecDeque;
use std::pin::Pin;
use std::rc::Rc;
mod namespace_context;
use namespace_context::NamespaceContext;
enum RenderedNode {
None,
RNode(web_sys::Node),
Text,
Fragment(VecDeque<RenderedNode>),
Element(VecDeque<RenderedNode>),
Component(Box<dyn HtmlNode>),
}
pub struct HtmlRenderer<This: Render<Html>> {
children: Option<This::Children>,
rendered_node: RenderedNode,
}
impl<This: Render<Html>> HtmlRenderer<This> {
pub fn new() -> Self {
Self {
children: None,
rendered_node: RenderedNode::Fragment(VecDeque::new()),
}
}
pub fn set_children(&mut self, children: This::Children) {
self.children = Some(children);
}
pub fn update(&mut self, msg: Msg) -> NodeCmd {
Self::update_rendered(&mut self.rendered_node, msg)
}
fn update_rendered(node: &mut RenderedNode, msg: Msg) -> NodeCmd {
match node {
RenderedNode::Component(node) => node.update(msg),
RenderedNode::Element(children) => {
let mut scedules = NodeCmd::new(VecDeque::new());
for child in children {
let mut node_cmd = Self::update_rendered(child, msg.clone());
scedules.append(&mut node_cmd);
}
scedules
}
RenderedNode::Fragment(children) => {
let mut scedules = NodeCmd::new(VecDeque::new());
for child in children {
let mut node_cmd = Self::update_rendered(child, msg.clone());
scedules.append(&mut node_cmd);
}
scedules
}
RenderedNode::RNode(..) => NodeCmd::new(VecDeque::new()),
RenderedNode::None => NodeCmd::new(VecDeque::new()),
RenderedNode::Text => NodeCmd::new(VecDeque::new()),
}
}
pub fn render(&mut self, state: &Pin<Box<This>>) -> (VecDeque<VNode>, NodeCmd) {
let html = state
.as_ref()
.render(self.children.take().unwrap_or_default());
let mut rendered_node = RenderedNode::None;
std::mem::swap(&mut self.rendered_node, &mut rendered_node);
let mut namespace_context = NamespaceContext::new();
let (rendered_node, v_nodes, node_cmd) =
Self::render_html(rendered_node, html, &mut namespace_context);
self.rendered_node = rendered_node;
(v_nodes, node_cmd)
}
fn render_html(
rendered_node: RenderedNode,
html: Html,
namespace_context: &mut NamespaceContext,
) -> (RenderedNode, VecDeque<VNode>, NodeCmd) {
match html {
Html::Fragment(htmls) => {
let rendered_nodes = if let RenderedNode::Fragment(rendered_nodes) = rendered_node {
rendered_nodes
} else {
VecDeque::new()
};
let rendered =
Self::render_html_group(rendered_nodes, htmls.into(), namespace_context);
(RenderedNode::Fragment(rendered.0), rendered.1, rendered.2)
}
Html::HtmlElement(element) => {
let rendered_nodes = if let RenderedNode::Element(rendered_nodes) = rendered_node {
rendered_nodes
} else {
VecDeque::new()
};
let (default_namespace, is_new_default_namespace_scope) =
if let Some(default_namespace) = element.attributes.get("xmlns") {
let default_namespace = default_namespace.to_string();
namespace_context.push_default_ns(default_namespace.clone());
(Some(default_namespace), true)
} else {
(namespace_context.default_ns().map(String::clone), false)
};
let children = Self::render_html_group(
rendered_nodes,
element.children.into(),
namespace_context,
);
if is_new_default_namespace_scope {
namespace_context.pop_default_ns();
}
let namespace = if let Some(namespace_name) = element.namespace_name {
namespace_context
.get_ns(namespace_name.as_str())
.map(String::clone)
} else {
default_namespace
};
(
RenderedNode::Element(children.0),
vec![VNode::VElement(VElement {
tag_name: Rc::new(element.tag_name),
namespace: namespace,
attributes: element.attributes,
events: element.events,
children: children.1,
index_id: element.index_id,
})]
.into(),
children.2,
)
}
Html::Component(prefab) => match rendered_node {
RenderedNode::Component(mut component) if component.is(prefab.as_ref()) => {
let mut node_cmd = component.update_by_prefab(prefab);
let (v_nodes, mut child_node_cmd) = component.render();
node_cmd.append(&mut child_node_cmd);
(RenderedNode::Component(component), v_nodes, node_cmd)
}
_ => {
let mut component = prefab.into_node();
let mut node_cmd = component.on_assemble();
let (v_nodes, mut child_node_cmd) = component.render();
node_cmd.append(&mut child_node_cmd);
(RenderedNode::Component(component), v_nodes, node_cmd)
}
},
Html::HtmlText(text) => (
RenderedNode::Text,
vec![VNode::VText(VText {
text: Rc::new(text.text),
})]
.into(),
NodeCmd::new(VecDeque::new()),
),
Html::RNode(r_node) => (
RenderedNode::RNode(r_node.clone()),
vec![VNode::RNode(r_node)].into(),
NodeCmd::new(VecDeque::new()),
),
Html::None => (
RenderedNode::None,
VecDeque::new(),
NodeCmd::new(VecDeque::new()),
),
}
}
fn render_html_group(
prev_rendered_nodes: VecDeque<RenderedNode>,
htmls: VecDeque<Html>,
namespace_context: &mut NamespaceContext,
) -> (VecDeque<RenderedNode>, VecDeque<VNode>, NodeCmd) {
let mixeds = crate::util::mix(
prev_rendered_nodes,
htmls.into(),
Self::compare_node_and_html,
1.0,
1.0,
1.0,
);
mixeds.into_iter().fold(
(
VecDeque::new(),
VecDeque::new(),
NodeCmd::new(VecDeque::new()),
),
|(mut now_rendered_nodes, mut v_nodes, mut node_cmd), mixed| {
let rendered = match mixed {
crate::util::mix::Edit::Append(html) => Some(Self::render_html(
RenderedNode::None,
html,
namespace_context,
)),
crate::util::mix::Edit::Keep(prev_rendered_node, html) => Some(
Self::render_html(prev_rendered_node, html, namespace_context),
),
crate::util::mix::Edit::Remove(..) => None,
crate::util::mix::Edit::Replace(prev_rendered_node, html) => Some(
Self::render_html(prev_rendered_node, html, namespace_context),
),
};
if let Some(mut rendered) = rendered {
now_rendered_nodes.push_back(rendered.0);
v_nodes.append(&mut rendered.1);
node_cmd.append(&mut rendered.2);
}
(now_rendered_nodes, v_nodes, node_cmd)
},
)
}
fn compare_node_and_html(rendered_node: &RenderedNode, html: &Html) -> bool {
match rendered_node {
RenderedNode::Component(component) => match html {
Html::Component(prefab) => component.is(prefab.as_ref()),
_ => false,
},
RenderedNode::RNode(prev_r_node) => match html {
Html::RNode(now_r_node) => prev_r_node.is_same_node(Some(&now_r_node)),
_ => false,
},
RenderedNode::Element(..) => match html {
Html::HtmlElement(..) => true,
_ => false,
},
RenderedNode::Fragment(..) => match html {
Html::Fragment(..) => true,
_ => false,
},
RenderedNode::None => match html {
Html::None => true,
_ => false,
},
RenderedNode::Text => match html {
Html::HtmlText(..) => true,
_ => false,
},
}
}
}