use std::collections::HashMap;
use web_sys;
use wasm_bindgen::{prelude::*, JsCast};
use crate::vdom::Mailbox;
pub trait UpdateListener<T> {
fn update_l(self, el: &mut T);
}
pub fn simple_ev<Ms: Clone + 'static>(trigger: &str, message: Ms) -> Listener<Ms> {
let mut listener = Listener::empty(trigger.into());
listener.add_handler_simple(message);
listener
}
pub fn input_ev<Ms: Clone + 'static>(trigger: &str, handler: impl FnMut(String) -> Ms + 'static) -> Listener<Ms> {
let mut listener = Listener::empty(trigger.into());
listener.add_handler_input(Box::new(handler));
listener
}
pub fn raw_ev<Ms: Clone + 'static>(trigger: &str, handler: impl FnMut(web_sys::Event) -> Ms + 'static) -> Listener<Ms> {
let mut listener = Listener::empty(trigger.into());
listener.add_handler_raw(Box::new(handler));
listener
}
pub fn keyboard_ev<Ms: Clone + 'static>(trigger: &str, handler: impl FnMut(web_sys::KeyboardEvent) -> Ms + 'static) -> Listener<Ms> {
let mut listener = Listener::empty(trigger.into());
listener.handler_keyboard(Box::new(handler));
listener
}
pub struct Listener<Ms: Clone> {
pub trigger: String, pub handler: Option<Box<FnMut(web_sys::Event) -> Ms>>,
pub closure: Option<Closure<FnMut(web_sys::Event)>>,
}
impl<Ms: Clone + 'static> Listener<Ms> {
pub fn empty(event: Event) -> Self {
Self {
trigger: String::from(event.as_str()),
handler: None,
closure: None,
}
}
fn add_handler_simple(&mut self, message: Ms) {
let handler = || message;
let closure = move |_| handler.clone()();
self.handler = Some(Box::new(closure));
}
fn add_handler_input(&mut self, mut handler: Box<FnMut(String) -> Ms + 'static>){
let closure = move |event: web_sys::Event| {
if let Some(target) = event.target() {
if let Some(input) = target.dyn_ref::<web_sys::HtmlInputElement>() {
return handler(input.value());
}
if let Some(input) = target.dyn_ref::<web_sys::HtmlTextAreaElement>() {
return handler(input.value());
}
if let Some(input) = target.dyn_ref::<web_sys::HtmlSelectElement>() {
return handler(input.value());
}
}
handler(String::new())
};
self.handler = Some(Box::new(closure));
}
fn add_handler_raw(&mut self, mut handler: Box<FnMut(web_sys::Event) -> Ms + 'static>){
let closure = move |event: web_sys::Event| handler(event);
self.handler = Some(Box::new(closure));
}
fn handler_keyboard(&mut self, mut handler: Box<FnMut(web_sys::KeyboardEvent) -> Ms + 'static>){
let closure = move |event: web_sys::Event| {
handler(event.dyn_ref::<web_sys::KeyboardEvent>().unwrap().clone())
};
self.handler = Some(Box::new(closure));
}
pub fn attach(&mut self, element: &web_sys::Element, mailbox: Mailbox<Ms>) {
let mut handler = self.handler.take().unwrap();
let closure = Closure::wrap(
Box::new(move |event: web_sys::Event| {
mailbox.send(handler(event))
})
as Box<FnMut(web_sys::Event) + 'static>,
);
(element.as_ref() as &web_sys::EventTarget)
.add_event_listener_with_callback(&self.trigger, closure.as_ref().unchecked_ref())
.expect("add_event_listener_with_callback");
closure.forget();
}
pub fn detach(&self, el_ws: &web_sys::Element) {
crate::log("Pre CLOSURE");
let closure = self.closure.as_ref().unwrap();
crate::log("POST CLOSURE");
(el_ws.as_ref() as &web_sys::EventTarget)
.remove_event_listener_with_callback(&self.trigger, closure.as_ref().unchecked_ref())
.expect("remove_event_listener_with_callback");
}
}
impl<Ms: Clone + 'static> PartialEq for Listener<Ms> {
fn eq(&self, other: &Self) -> bool {
self.trigger == other.trigger
}
}
pub trait UpdateEl<T> {
fn update(self, el: &mut T);
}
impl<Ms: Clone> UpdateEl<El<Ms>> for Attrs {
fn update(self, el: &mut El<Ms>) {
el.attrs = self;
}
}
impl<Ms: Clone> UpdateEl<El<Ms>> for Style {
fn update(self, el: &mut El<Ms>) {
el.style = self;
}
}
impl<Ms: Clone> UpdateEl<El<Ms>> for Vec<Listener<Ms>> {
fn update(self, el: &mut El<Ms>) {
el.listeners = self;
}
}
impl<Ms: Clone> UpdateEl<El<Ms>> for &str {
fn update(self, el: &mut El<Ms>) {
el.text = Some(self.into());
}
}
impl<Ms: Clone> UpdateEl<El<Ms>> for Vec<El<Ms>> {
fn update(self, el: &mut El<Ms>) {
el.children = self;
}
}
pub enum _Attr {
Action,
Alt,
Class,
Disabled,
Height,
Href,
Id,
Lang,
OnChange,
OnClick,
OnContextMenu,
OnDblClick,
OnMouseOver,
Src,
Title,
Width,
}
#[derive(Clone, PartialEq)]
pub struct Attrs {
pub vals: HashMap<String, String>
}
impl Attrs {
pub fn new(vals: HashMap<String, String>) -> Self {
Self { vals }
}
pub fn empty() -> Self {
Self { vals: HashMap::new() }
}
pub fn as_str(&self) -> String {
let mut result = String::new();
for (key, val) in &self.vals {
result += &format!(" {k}=\"{v}\"", k=key, v=val);
}
result
}
pub fn add(&mut self, key: &str, val: &str) {
self.vals.insert(key.to_string(), val.to_string());
}
}
#[derive(Clone, PartialEq)]
pub struct Style {
pub vals: HashMap<String, String>
}
impl Style {
pub fn new(vals: HashMap<String, String>) -> Self {
let mut new_vals = HashMap::new();
for (key, val) in vals.into_iter() {
let val_backup = val.clone();
match val.parse::<u32>() {
Ok(_) => new_vals.insert(key, val_backup + "px"),
Err(_) => new_vals.insert(key, val_backup),
};
}
Self { vals: new_vals }
}
pub fn empty() -> Self {
Self { vals: HashMap::new() }
}
pub fn as_str(&self) -> String {
let mut result = String::new();
if self.vals.keys().len() > 0 {
for (key, val) in &self.vals {
result += &format!("{k}: {v}; ", k = key, v = val);
}
result += "\"";
}
result
}
pub fn add(&mut self, key: &str, val: &str) {
self.vals.insert(key.to_string(), val.to_string());
}
}
macro_rules! make_events {
{ $($event_camel:ident => $event:expr),+ } => {
#[derive(Clone)]
pub enum Event {
$(
$event_camel,
)+
}
impl Event {
pub fn as_str(&self) -> &str {
match self {
$ (
Event::$event_camel => $event,
) +
}
}
}
impl From<&str> for Event {
fn from(event: &str) -> Self {
match event {
$ (
$event => Event::$event_camel,
) +
_ => {
crate::log(&format!("Can't find this event: {}", event));
Event::Click
}
}
}
}
}
}
make_events! {
Cached => "cached", Error => "error", Abort => "abort", Load => "load", BeforeUnload => "beforeunload",
Unload => "unload", Online => "online", Offline => "offline", Focus => "focus", Blur => "blur",
Open => "open", Message => "message", Close => "close", PageHide => "pagehide",
PageShow => "pageshow", PopState => "popstate", AnimationStart => "animationstart", AnimationEnd => "animationend",
AnimationIteration => "animationiteration", TransitionStart => "transtionstart", TransitionEnd => "transitionend",
TranstionRun => "transitionrun",
Rest => "rest", Submit => "submit", BeforePrint => "beforeprint", AfterPrint => "afterprint",
CompositionStart => "compositionstart", CompositionUpdate => "compositionupdate", CompositionEnd => "compositionend",
FullScreenChange => "fullscreenchange", FullScreenError => "fullscreenerror", Resize => "resize",
Scroll => "scroll", Cut => "cut", Copy => "copy", Paste => "paste",
KeyDown => "keydown",
KeyPress => "keypress", AuxClick => "auxclick", Click => "click", ContextMenu => "contextmenu", DblClick => "dblclick",
MouseDown => "mousedown", MouseEnter => "mouseenter", MouseLeave => "mouseleave",
MouseMove => "mousemove", MouseOver => "mouseover", MouseOut => "mouseout", MouseUp => "mouseup",
PointerLockChange => "pointerlockchange", PointerLockError => "pointerlockerror", Select => "select",
Wheel => "wheel",
Drag => "drag", DragEnd => "dragend", DragEnter => "dragenter", DragStart => "dragstart", DragLeave => "dragleave",
DragOver => "dragover", Drop => "drop",
AudioProcess => "audioprocess", CanPlay => "canplay", CanPlayThrough => "canplaythrough", Complete => "complete",
DurationChange => "durationchange", Emptied => "emptied", Ended => "ended", LoadedData => "loadeddata",
LoadedMetaData => "loadedmetadata", Pause => "pause", Play => "play", Playing => "playing", RateChagne => "ratechange",
Seeked => "seeked", Seeking => "seeking", Stalled => "stalled", Suspend => "suspend", TimeUpdate => "timeupdate",
VolumeChange => "volumechange",
Change => "change",
Input => "input"
}
macro_rules! make_tags {
{ $($tag_camel:ident => $tag:expr),+ } => {
#[derive(Clone, PartialEq)]
pub enum Tag {
$(
$tag_camel,
)+
}
impl Tag {
pub fn as_str(&self) -> &str {
match self {
$ (
Tag::$tag_camel => $tag,
) +
}
}
}
}
}
make_tags! {
Address => "address", Article => "article", Aside => "aside", Footer => "footer",
Header => "header", H1 => "h1",
H2 => "h2", H3 => "h3", H4 => "h4", H5 => "h5", H6 => "h6",
Hgroup => "hgroup", Main => "main", Nav => "nav", Section => "section",
BlockQuote => "blockquote",
Dd => "dd", Dir => "dir", Div => "div", Dl => "dl", Dt => "dt", FigCaption => "figcaption", Figure => "figure",
Hr => "hr", Li => "li", Ol => "ol", P => "p", Pre => "pre", Ul => "ul",
A => "a", Abbr => "abbr",
B => "b", Bdi => "bdi", Bdo => "bdo", Br => "br", Cite => "cite", Code => "code", Data => "data",
Dfn => "dfn", Em => "em", I => "i", Kbd => "kbd", Mark => "mark", Q => "q", Rb => "rb",
Rp => "rp", Rt => "rt", Rtc => "rtc", Ruby => "ruby", S => "s", Samp => "samp", Small => "small",
Span => "span", Strong => "strong", Sub => "sub", Sup => "sup", Time => "time", Tt => "tt",
U => "u", Var => "var", Wbr => "wbr",
Area => "area", Audio => "audio", Img => "img", Map => "map", Track => "track", Video => "video",
Applet => "applet", Embed => "embed", Iframe => "iframe",
NoEmbed => "noembed", Object => "object", Param => "param", Picture => "picture", Source => "source",
Canvas => "canvas", NoScript => "noscript", Script => "Script",
Del => "del", Ins => "ins",
Caption => "caption", Col => "col", ColGroup => "colgroup", Table => "table", Tbody => "tbody",
Td => "td", Tfoot =>"tfoot", Th => "th", Thead => "thead", Tr => "tr",
Button => "button", DataList => "datalist", FieldSet => "fieldset", Form => "form", Input => "input",
Label => "label", Legend => "legend", Meter => "meter", OptGroup => "optgroup", Option => "option",
Output => "output", Progress => "progress", Select => "select", TextArea => "textarea",
Details => "details", Dialog => "dialog", Menu => "menu", MenuItem => "menuitem", Summary => "summary",
Content => "content", Element => "element", Shadow => "shadow", Slot => "slot", Template => "template"
}
pub struct El<Ms: Clone + 'static> {
pub tag: Tag,
pub attrs: Attrs,
pub style: Style,
pub text: Option<String>,
pub children: Vec<El<Ms>>,
pub id: Option<u32>,
pub nest_level: Option<u32>,
pub el_ws: Option<web_sys::Element>,
pub key: Option<u32>,
pub listeners: Vec<Listener<Ms>>,
}
impl<Ms: Clone + 'static> El<Ms> {
pub fn new(tag: Tag, attrs: Attrs, style: Style,
listeners: Vec<Listener<Ms>>, text: &str, children: Vec<El<Ms>>) -> Self {
Self {tag, attrs, style, text: Some(text.into()), children,
el_ws: None, listeners, key: None, id: None, nest_level: None}
}
pub fn empty(tag: Tag) -> Self {
Self {tag, attrs: Attrs::empty(), style: Style::empty(),
text: None, children: Vec::new(), el_ws: None,
listeners: Vec::new(), key: None, id: None, nest_level: None}
}
pub fn add_child(&mut self, element: El<Ms>) {
self.children.push(element);
}
pub fn add_attr(&mut self, key: String, val: String) {
self.attrs.vals.insert(key, val);
}
pub fn add_style(&mut self, key: String, val: String) {
self.style.vals.insert(key, val);
}
pub fn set_text(&mut self, text: &str) {
self.text = Some(text.into())
}
fn _html(&self) -> String {
let text = self.text.clone().unwrap_or_default();
let opening = String::from("<") + self.tag.as_str() + &self.attrs.as_str() +
" style=\"" + &self.style.as_str() + ">\n";
let inner = self.children.iter().fold(String::new(), |result, child| result + &child._html());
let closing = String::from("\n</") + self.tag.as_str() + ">";
opening + &text + &inner + &closing
}
pub fn quick_clone(&self) -> Self {
Self {
tag: self.tag.clone(),
attrs: Attrs::empty(),
style: Style::empty(),
text: None,
children: Vec::new(),
key: None,
id: None,
nest_level: None,
el_ws: self.el_ws.clone(),
listeners: Vec::new(),
}
}
pub fn is_dummy(&self) -> bool {
if let Tag::Del = self.tag {
if self.attrs.vals.get("dummy-element").is_some() {
return true;
}
}
false
}
}
impl<Ms: Clone + 'static> Clone for El<Ms> {
fn clone(&self) -> Self {
Self {
tag: self.tag.clone(),
attrs: self.attrs.clone(),
style: self.style.clone(),
text: self.text.clone(),
children: self.children.clone(),
key: self.key,
id: self.id,
nest_level: self.nest_level,
el_ws: self.el_ws.clone(),
listeners: Vec::new(),
}
}
}
impl<Ms: Clone + 'static> PartialEq for El<Ms> {
fn eq(&self, other: &Self) -> bool {
self.tag == other.tag &&
self.attrs == other.attrs &&
self.style == other.style &&
self.text == other.text &&
self.listeners == other.listeners &&
self.nest_level == other.nest_level
}
}