use crate::{
CollapsedHtmlToken,
CollapsedNode,
};
use custom_derive::custom_derive;
use enum_derive::{
enum_derive_util,
EnumFromInner,
};
pub type Attributes = std::collections::HashMap<String, String>;
custom_derive! {
#[derive(Debug, Clone, EnumFromInner, Eq, PartialEq)]
pub enum Node {
Dom(HtmlToken),
Text(String),
Vec(Vec<Node>),
Comment(Option<String>),
}
}
pub trait AsInnerHtml {
fn as_inner_html(&self) -> String;
}
impl AsInnerHtml for Vec<CollapsedNode> {
fn as_inner_html(&self) -> String {
self.iter().map(|node| node.as_inner_html()).collect()
}
}
impl AsInnerHtml for CollapsedNode {
fn as_inner_html(&self) -> String {
match self {
CollapsedNode::Dom(token) => token.as_inner_html(),
CollapsedNode::Text(s) => s.to_string(),
CollapsedNode::Comment(str_opt) => match str_opt {
Some(s) => format!("<!-- {} -->", s),
None => "<!-- -->".into(),
},
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct HtmlToken {
pub node_type: String,
pub children: Vec<Node>,
pub attributes: Attributes,
}
fn format_attributes(attr: &Attributes) -> String {
attr.iter().fold("".to_string(), |accum, (key, val)| {
if val != "" {
format!("{} {}=\"{}\"", accum, key, val)
} else {
format!("{} {}", accum, key)
}
})
}
fn format_path(path: &Path) -> String {
if path.len() > 0 {
let path_str = path.iter().fold("".to_string(), |accum, path_segment| {
format!("{}{},", accum, path_segment)
});
path_str.chars().take(path_str.len() - 1).collect()
} else {
"".to_string()
}
}
use lazy_static::lazy_static;
lazy_static! {
static ref VOID_TAGS: std::collections::HashSet<String> = {
let mut void_tags = std::collections::HashSet::new();
void_tags.insert("area".to_string());
void_tags.insert("base".to_string());
void_tags.insert("br".to_string());
void_tags.insert("col".to_string());
void_tags.insert("command".to_string());
void_tags.insert("embed".to_string());
void_tags.insert("hr".to_string());
void_tags.insert("img".to_string());
void_tags.insert("input".to_string());
void_tags.insert("keygen".to_string());
void_tags.insert("link".to_string());
void_tags.insert("meta".to_string());
void_tags.insert("param".to_string());
void_tags.insert("source".to_string());
void_tags.insert("track".to_string());
void_tags.insert("wbr".to_string());
void_tags
};
}
impl AsInnerHtml for CollapsedHtmlToken {
fn as_inner_html(&self) -> String {
let path_string = format!(" data-smithy-path=\"{}\"", format_path(&self.path));
let attributes_string = if self.attributes.len() > 0 {
format!(" {}", format_attributes(&self.attributes))
} else {
"".to_string()
};
if !VOID_TAGS.contains(&self.node_type) {
let child_html = self
.children
.iter()
.map(|node| node.as_inner_html())
.collect::<Vec<String>>()
.join("");
format!(
"<{}{}{}>{}</{}>",
self.node_type, attributes_string, path_string, child_html, self.node_type
)
} else {
format!("<{}{}{} />", self.node_type, attributes_string, path_string)
}
}
}
pub type Path = [usize];
pub enum Phase<'a> {
Rendering,
PostRendering,
UiEventHandling((&'a crate::UiEvent, &'a Path)),
WindowEventHandling(&'a crate::WindowEvent),
RefAssignment(Vec<usize>),
}
pub type EventHandled = bool;
#[derive(Debug)]
pub enum PhaseResult {
Rendering(Node),
PostRendering,
UiEventHandling(EventHandled),
WindowEventHandling(EventHandled),
RefAssignment,
}
impl PhaseResult {
pub fn unwrap_node(self) -> Node {
match self {
PhaseResult::Rendering(node) => node,
_ => panic!("unwrap_node called on PhaseResult that was not of variant Rendering"),
}
}
pub fn unwrap_event_handled(self) -> EventHandled {
match self {
PhaseResult::UiEventHandling(event_handled) => event_handled,
PhaseResult::WindowEventHandling(event_handled) => event_handled,
_ => {
panic!("unwrap_event_handled called on PhaseResult that was not of variant UiEventHandling or WindowEventHandling")
},
}
}
}
pub struct SmithyComponent<'a>(pub Box<dyn FnMut(Phase) -> PhaseResult + 'a>);
pub trait Component {
fn render(&mut self) -> Node;
fn handle_post_render(&mut self) {}
fn handle_ref_assignment(&mut self, _path_so_far: Vec<usize>) {}
fn handle_ui_event(&mut self, _event: &crate::UiEvent, _path: &Path) -> EventHandled {
false
}
fn handle_window_event(&mut self, _event: &crate::WindowEvent) -> EventHandled {
false
}
}
impl<'a> Component for SmithyComponent<'a> {
fn handle_ui_event(&mut self, event: &crate::UiEvent, path: &Path) -> EventHandled {
self.0(Phase::UiEventHandling((event, path))).unwrap_event_handled()
}
fn handle_window_event(&mut self, event: &crate::WindowEvent) -> EventHandled {
self.0(Phase::WindowEventHandling(event)).unwrap_event_handled()
}
fn render(&mut self) -> Node {
self.0(Phase::Rendering).unwrap_node()
}
fn handle_post_render(&mut self) {
self.0(Phase::PostRendering);
}
fn handle_ref_assignment(&mut self, path_so_far: Vec<usize>) {
self.0(Phase::RefAssignment(path_so_far));
}
}