use super::super::{
At, AtValue, Attrs, CSSValue, EventHandler, EventHandlerManager, Node, SharedNodeWs, St, Style,
Tag, Text,
};
use crate::app::MessageMapper;
use crate::browser::{
dom::{virtual_dom_bridge, Namespace},
util,
};
use std::borrow::Cow;
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ElKey(String);
#[allow(clippy::module_name_repetitions)]
pub fn el_key(key: &impl ToString) -> ElKey {
ElKey(key.to_string())
}
#[derive(Debug)] pub struct El<Ms> {
pub tag: Tag,
pub attrs: Attrs,
pub style: Style,
pub event_handler_manager: EventHandlerManager<Ms>,
pub children: Vec<Node<Ms>>,
pub namespace: Option<Namespace>,
pub node_ws: Option<web_sys::Node>,
pub refs: Vec<SharedNodeWs>,
pub key: Option<ElKey>,
}
impl<Ms> Clone for El<Ms> {
fn clone(&self) -> Self {
Self {
tag: self.tag.clone(),
attrs: self.attrs.clone(),
style: self.style.clone(),
event_handler_manager: self.event_handler_manager.clone(),
children: self.children.clone(),
namespace: self.namespace.clone(),
node_ws: self.node_ws.clone(),
refs: self.refs.clone(),
key: self.key.clone(),
}
}
}
impl<Ms: 'static, OtherMs: 'static> MessageMapper<Ms, OtherMs> for El<Ms> {
type SelfWithOtherMs = El<OtherMs>;
fn map_msg(self, f: impl FnOnce(Ms) -> OtherMs + 'static + Clone) -> El<OtherMs> {
El {
tag: self.tag,
attrs: self.attrs,
style: self.style,
children: self
.children
.into_iter()
.map(|c| c.map_msg(f.clone()))
.collect(),
node_ws: self.node_ws,
namespace: self.namespace,
event_handler_manager: self.event_handler_manager.map_msg(f),
refs: self.refs,
key: self.key,
}
}
}
impl<Ms: 'static, OtherMs: 'static> MessageMapper<Ms, OtherMs> for Vec<El<Ms>> {
type SelfWithOtherMs = Vec<El<OtherMs>>;
fn map_msg(self, f: impl FnOnce(Ms) -> OtherMs + 'static + Clone) -> Vec<El<OtherMs>> {
self.into_iter().map(|el| el.map_msg(f.clone())).collect()
}
}
impl<Ms> El<Ms> {
pub fn empty(tag: Tag) -> Self {
Self {
tag,
attrs: Attrs::empty(),
style: Style::empty(),
event_handler_manager: EventHandlerManager::new(),
children: Vec::new(),
namespace: None,
node_ws: None,
refs: Vec::new(),
key: None,
}
}
pub fn empty_svg(tag: Tag) -> Self {
let mut el = El::empty(tag);
el.namespace = Some(Namespace::Svg);
el
}
pub fn from_markdown(markdown: &str) -> Vec<Node<Ms>> {
let options = pulldown_cmark::Options::all();
let parser = pulldown_cmark::Parser::new_ext(markdown, options);
let mut html_text = String::new();
pulldown_cmark::html::push_html(&mut html_text, parser);
Self::from_html(&html_text)
}
pub fn from_html(html: &str) -> Vec<Node<Ms>> {
let wrapper = util::document()
.create_element("placeholder")
.expect("Problem creating web-sys element");
wrapper.set_inner_html(html);
let mut result = Vec::new();
let children = wrapper.child_nodes();
for i in 0..children.length() {
let child = children
.get(i)
.expect("Can't find child in raw html element.");
if let Some(child_vdom) = virtual_dom_bridge::node_from_ws(&child) {
result.push(child_vdom)
}
}
result
}
pub fn add_child(&mut self, element: Node<Ms>) -> &mut Self {
self.children.push(element);
self
}
pub fn add_attr(
&mut self,
key: impl Into<Cow<'static, str>>,
val: impl Into<AtValue>,
) -> &mut Self {
self.attrs.vals.insert(At::from(key), val.into());
self
}
pub fn add_class(&mut self, name: impl Into<Cow<'static, str>>) -> &mut Self {
let name = name.into();
self.attrs
.vals
.entry(At::Class)
.and_modify(|at_value| match at_value {
AtValue::Some(v) => {
if !v.is_empty() {
*v += " ";
}
*v += name.as_ref();
}
_ => *at_value = AtValue::Some(name.clone().into_owned()),
})
.or_insert(AtValue::Some(name.into_owned()));
self
}
pub fn add_style(&mut self, key: impl Into<St>, val: impl Into<CSSValue>) -> &mut Self {
self.style.vals.insert(key.into(), val.into());
self
}
pub fn add_event_handler(&mut self, event_handler: EventHandler<Ms>) -> &mut Self {
self.event_handler_manager
.add_event_handlers(vec![event_handler]);
self
}
pub fn add_text(&mut self, text: impl Into<Cow<'static, str>>) -> &mut Self {
self.children.push(Node::Text(Text::new(text)));
self
}
pub fn replace_text(&mut self, text: impl Into<Cow<'static, str>>) -> &mut Self {
self.children.retain(|node| !node.is_text());
self.children.push(Node::new_text(text));
self
}
pub fn get_text(&self) -> String {
self.children
.iter()
.filter_map(|child| match child {
Node::Text(text_node) => Some(text_node.text.to_string()),
_ => None,
})
.collect()
}
#[cfg(debug_assertions)]
pub fn warn_about_script_tags(&self) {
let script_found = match &self.tag {
Tag::Script => true,
Tag::Custom(tag) if tag == "script" => true,
_ => false,
};
if script_found {
error!("Script tag found inside mount point! \
Please check https://docs.rs/seed/latest/seed/app/builder/struct.Builder.html#examples");
}
for child in &self.children {
child.warn_about_script_tags();
}
}
pub fn strip_ws_nodes_from_self_and_children(&mut self) {
self.node_ws.take();
for child in &mut self.children {
child.strip_ws_nodes_from_self_and_children();
}
}
pub fn is_custom(&self) -> bool {
matches!(self.tag, Tag::Custom(_))
}
}