This repository has been archived by the owner on Apr 29, 2023. It is now read-only.
/
html.rs
207 lines (182 loc) · 5.51 KB
/
html.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// TODO: Attributes
// TODO: Are we able to convert Html<A> to Html<B>?
use std::any::Any;
use std::cell::RefCell;
use std::cmp::PartialEq;
use std::fmt::{self, Debug};
use std::rc::Rc;
#[derive(Clone, Debug)]
pub struct HtmlTag<Msg> {
pub tag: String,
pub attrs: Vec<Attribute<Msg>>,
pub children: Vec<Html<Msg>>,
}
#[derive(Clone, Debug)]
pub enum Html<Msg> {
Tag(HtmlTag<Msg>),
Text(String),
}
impl<Msg> Html<Msg> {
pub fn to_html_text(&self, indent: u32) -> String {
let indent_s = " ".repeat(indent as usize);
match self {
Html::Text(text) => format!("{}{}", indent_s, text),
Html::Tag(tag) => {
if tag.children.is_empty() {
return format!("{}<{} />", indent_s, tag.tag);
}
let children = tag
.children
.iter()
.map(|child| child.to_html_text(indent + 1))
.collect::<Vec<_>>()
.join("\n");;
format!(
"{}<{}>\n{}\n{}</{}>",
indent_s, tag.tag, children, indent_s, tag.tag,
)
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PropertyValue {
String(String),
Bool(bool),
}
#[derive(Clone, Default)]
pub struct JsClosure(pub Rc<RefCell<Option<wasm_bindgen::closure::Closure<Fn(web_sys::Event)>>>>);
impl Debug for JsClosure {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.0.borrow().is_some() {
write!(f, "HAS A CLOSURE")
} else {
write!(f, "NO CLOSURE")
}
}
}
impl PartialEq for JsClosure {
fn eq(&self, _: &JsClosure) -> bool {
// This is not good enough to implent Eq, i think
// And its a bit weird. But it's to ignore this in the Attribute enum
true
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Attribute<Msg> {
// Event where the message depends on the event data
Event {
js_closure: JsClosure,
type_: String,
stop_propagation: bool,
prevent_default: bool,
to_message: EventToMessage<Msg>,
},
// TODO: Value should be JsValue or something like that, not String
Property(&'static str, PropertyValue),
Style(String, String),
}
impl<Msg> Attribute<Msg> {
pub fn is_event(&self) -> bool {
match self {
Attribute::Event { .. } => true,
_ => false,
}
}
/// Panics if self is not an event
pub fn get_js_closure(&self) -> JsClosure {
match self {
Attribute::Event { js_closure, .. } => js_closure.clone(),
_ => panic!("get_js_closure called with something that is not an event"),
}
}
/// Panics if self is not an event
pub fn set_js_closure(&self, closure: wasm_bindgen::closure::Closure<Fn(web_sys::Event)>) {
match self {
Attribute::Event { js_closure, .. } => {
let ret = js_closure.0.replace(Some(closure));
if ret.is_some() {
console_log!("set_js_closure called, but event did already have a closure???");
}
}
_ => panic!("set_js_closure called with something that is not an event"),
}
}
}
pub trait EventClosure<Input, Msg>: Debug {
fn call_ish(&self, input: Input) -> Msg;
fn eq(&self, other: &Rc<EventClosure<Input, Msg>>) -> bool;
}
#[derive(Debug)]
pub struct EventClosureImpl<Input, Data, Msg> {
data: Data,
func: fn(Data, Input) -> Msg,
}
impl<Input, Data, Msg> EventClosureImpl<Input, Data, Msg> {
pub fn new(data: Data, func: fn(Data, Input) -> Msg) -> Self {
Self { data, func }
}
}
impl<Input: Debug + 'static, Data: PartialEq + Debug + Clone + 'static, Msg: Debug + 'static>
EventClosure<Input, Msg> for EventClosureImpl<Input, Data, Msg>
{
fn call_ish(&self, input: Input) -> Msg {
(self.func)(self.data.clone(), input)
}
fn eq(&self, other: &Rc<EventClosure<Input, Msg>>) -> bool {
let other = other as &Any;
if let Some(other_down) = other.downcast_ref::<EventClosureImpl<Input, Data, Msg>>() {
self.data == other_down.data && self.func == other_down.func
} else {
false
}
}
}
#[derive(Clone, Debug)]
pub struct RcEventClosure<Input, Msg>(pub Rc<EventClosure<Input, Msg>>);
impl<Input, Msg> PartialEq for RcEventClosure<Input, Msg> {
fn eq(&self, other: &RcEventClosure<Input, Msg>) -> bool {
self.eq(other)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum EventToMessage<Msg> {
StaticMsg(Msg),
Input(fn(String) -> Msg),
InputWithClosure(RcEventClosure<String, Msg>),
WithFilter {
msg: Msg,
filter: fn(web_sys::Event) -> bool,
},
}
macro_rules! create_node {
($x:ident) => {
pub fn $x<Msg: Clone>(attrs: &[Attribute<Msg>], children: &[Html<Msg>]) -> Html<Msg> {
Html::Tag(HtmlTag {
tag: stringify!($x).to_owned(),
children: children.to_vec(),
attrs: attrs.to_vec(),
})
}
};
}
create_node!(div);
create_node!(button);
create_node!(section);
create_node!(header);
create_node!(h1);
create_node!(h2);
create_node!(h3);
create_node!(h4);
create_node!(input);
create_node!(label);
create_node!(ul);
create_node!(li);
create_node!(footer);
create_node!(span);
create_node!(strong);
create_node!(a);
create_node!(p);
pub fn text<Msg>(inner: &str) -> Html<Msg> {
Html::Text(inner.to_owned())
}