~repos /gromer
git clone https://pyrossh.dev/repos/gromer.git
gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
improve api
- .snapshots/TestHtmlPage +55 -0
- context.go +7 -48
- css.go +471 -0
- go.mod +2 -1
- go.sum +4 -0
- html.go +147 -105
- html_test.go +112 -0
.snapshots/TestHtmlPage
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
|
6
|
+
<meta http-equiv="encoding" content="utf-8">
|
|
7
|
+
<meta name="title" content="title">
|
|
8
|
+
<style>
|
|
9
|
+
.flex { display: flex; }
|
|
10
|
+
.flex-col { flex-direction: column; }
|
|
11
|
+
.justify-center { justify-content: center; }
|
|
12
|
+
.items-center { align-items: center; }
|
|
13
|
+
.text-3xl { font-size: 1.875rem; line-height: 2.25rem; }
|
|
14
|
+
.text-gray-700 { color: rgba(55, 65, 81, 1); }
|
|
15
|
+
.flex-row { flex-direction: row; }
|
|
16
|
+
.underline { text-decoration: underline; }
|
|
17
|
+
.m-20 { margin: 5rem; }
|
|
18
|
+
.text-8xl { font-size: 6rem; line-height: 1; } </style>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<h1> Hello this is a h1 </h1>
|
|
22
|
+
|
|
23
|
+
<h2> Hello this is a h2 </h2>
|
|
24
|
+
|
|
25
|
+
<h3 x-data="{ message: 'I ❤️ Alpine' }" x-text="message"> </h3>
|
|
26
|
+
|
|
27
|
+
<counter> <div class="flex flex-col justify-center items-center text-3xl text-gray-700">
|
|
28
|
+
<div class="flex flex-row justify-center items-center"> <div class="flex flex-row justify-center items-center underline"> Counter </div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div x-data="counter" class="flex flex-row justify-center items-center">
|
|
32
|
+
<button class="btn m-20" @click="decrement"> - </button>
|
|
33
|
+
|
|
34
|
+
<div x-text="state.count" class="flex flex-row justify-center items-center m-20 text-8xl"> 4 </div>
|
|
35
|
+
|
|
36
|
+
<button class="btn m-20" @click="increment"> + </button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</counter>
|
|
40
|
+
|
|
41
|
+
<script>
|
|
42
|
+
Alpine.data('counter', ({
|
|
43
|
+
state: {
|
|
44
|
+
count:1,
|
|
45
|
+
|
|
46
|
+
},
|
|
47
|
+
increment() {this.state.count += 1;},
|
|
48
|
+
decrement() {this.state.count -= 1;},
|
|
49
|
+
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
</script>
|
|
53
|
+
</body>
|
|
54
|
+
|
|
55
|
+
</html>
|
context.go
CHANGED
|
@@ -1,59 +1,18 @@
|
|
|
1
1
|
package wapp
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"bytes"
|
|
5
|
-
c "context"
|
|
6
4
|
"encoding/json"
|
|
7
5
|
"net/http"
|
|
8
6
|
"reflect"
|
|
9
|
-
|
|
10
|
-
"github.com/gobuffalo/velvet"
|
|
11
|
-
"github.com/gorilla/mux"
|
|
12
7
|
)
|
|
13
8
|
|
|
14
|
-
type
|
|
9
|
+
type State map[string]interface{}
|
|
15
|
-
c.Context
|
|
16
|
-
|
|
10
|
+
type Actions map[string]func() string
|
|
17
|
-
JS *bytes.Buffer
|
|
18
|
-
CSS *bytes.Buffer
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
func NewWappContext(r *http.Request) (WappContext, error) {
|
|
22
|
-
pathParams := mux.Vars(r)
|
|
23
|
-
return WappContext{
|
|
24
|
-
Context: r.Context(),
|
|
25
|
-
PathParams: pathParams,
|
|
26
|
-
JS: bytes.NewBuffer(nil),
|
|
27
|
-
CSS: bytes.NewBuffer(nil),
|
|
28
|
-
}, nil
|
|
29
|
-
}
|
|
30
11
|
|
|
31
|
-
func (r WappContext) UseData(name string, data map[string]interface{}) {
|
|
32
|
-
v := velvet.NewContext()
|
|
33
|
-
v.Set("name", name)
|
|
34
|
-
generatedMap := map[string]interface{}{}
|
|
35
|
-
for k, v := range data {
|
|
36
|
-
if reflect.TypeOf(v).Kind() == reflect.Func {
|
|
37
|
-
|
|
12
|
+
type Reducer struct {
|
|
38
|
-
generatedMap[k] = "() {" + f() + "}"
|
|
39
|
-
|
|
13
|
+
Name string
|
|
40
|
-
generatedMap[k+":"] = v
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
14
|
+
State
|
|
44
|
-
s, err := velvet.Render(`
|
|
45
|
-
Alpine.data('{{ name }}', () => {
|
|
46
|
-
|
|
15
|
+
Actions
|
|
47
|
-
{{#each data}}
|
|
48
|
-
{{ @key }}{{ @value }},
|
|
49
|
-
{{/each}}
|
|
50
|
-
};
|
|
51
|
-
});
|
|
52
|
-
`, v)
|
|
53
|
-
if err != nil {
|
|
54
|
-
panic(err)
|
|
55
|
-
}
|
|
56
|
-
r.JS.WriteString(s)
|
|
57
16
|
}
|
|
58
17
|
|
|
59
18
|
func RespondError(w http.ResponseWriter, status int, err error) {
|
|
@@ -98,7 +57,7 @@ func PerformRequest(h interface{}, ctx interface{}, w http.ResponseWriter, r *ht
|
|
|
98
57
|
RespondError(w, responseStatus, responseError.(error))
|
|
99
58
|
return responseError.(error)
|
|
100
59
|
}
|
|
101
|
-
if v, ok := response.(*
|
|
60
|
+
if v, ok := response.(*HtmlPage); ok {
|
|
102
61
|
w.WriteHeader(responseStatus)
|
|
103
62
|
w.Header().Set("Content-Type", "text/html")
|
|
104
63
|
v.WriteHtml(w)
|
css.go
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
package wapp
|
|
2
|
+
|
|
3
|
+
type M map[string]interface{}
|
|
4
|
+
type MS map[string]string
|
|
5
|
+
type Arr []interface{}
|
|
6
|
+
type KeyValues struct {
|
|
7
|
+
Keys M
|
|
8
|
+
Values MS
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
var colors = KeyValues{
|
|
12
|
+
Keys: M{
|
|
13
|
+
"bg": "background-color",
|
|
14
|
+
"text": "color",
|
|
15
|
+
"divide": "border-color",
|
|
16
|
+
"border": "border-color",
|
|
17
|
+
"ring": "--tw-ring-color",
|
|
18
|
+
"border-l": "border-left-color",
|
|
19
|
+
"border-r": "border-right-color",
|
|
20
|
+
"border-t": "border-top-color",
|
|
21
|
+
"border-b": "border-bottom-color",
|
|
22
|
+
},
|
|
23
|
+
Values: MS{
|
|
24
|
+
"transparent": "transparent",
|
|
25
|
+
"current": "currentColor",
|
|
26
|
+
"black": "rgba(0, 0, 0, 1)",
|
|
27
|
+
"white": "rgba(255, 255, 255, 1)",
|
|
28
|
+
"gray-50": "rgba(249, 250, 251, 1)",
|
|
29
|
+
"gray-100": "rgba(243, 244, 246, 1)",
|
|
30
|
+
"gray-200": "rgba(229, 231, 235, 1)",
|
|
31
|
+
"gray-300": "rgba(209, 213, 219, 1)",
|
|
32
|
+
"gray-400": "rgba(156, 163, 175, 1)",
|
|
33
|
+
"gray-500": "rgba(107, 114, 128, 1)",
|
|
34
|
+
"gray-600": "rgba(75, 85, 99, 1)",
|
|
35
|
+
"gray-700": "rgba(55, 65, 81, 1)",
|
|
36
|
+
"gray-800": "rgba(31, 41, 55, 1)",
|
|
37
|
+
"gray-900": "rgba(17, 24, 39, 1)",
|
|
38
|
+
"red-50": "rgba(254, 242, 242, 1)",
|
|
39
|
+
"red-100": "rgba(254, 226, 226, 1)",
|
|
40
|
+
"red-200": "rgba(254, 202, 202, 1)",
|
|
41
|
+
"red-300": "rgba(252, 165, 165, 1)",
|
|
42
|
+
"red-400": "rgba(248, 113, 113, 1)",
|
|
43
|
+
"red-500": "rgba(239, 68, 68, 1)",
|
|
44
|
+
"red-600": "rgba(220, 38, 38, 1)",
|
|
45
|
+
"red-700": "rgba(185, 28, 28, 1)",
|
|
46
|
+
"red-800": "rgba(153, 27, 27, 1)",
|
|
47
|
+
"red-900": "rgba(127, 29, 29, 1)",
|
|
48
|
+
"yellow-50": "rgba(255, 251, 235, 1)",
|
|
49
|
+
"yellow-100": "rgba(254, 243, 199, 1)",
|
|
50
|
+
"yellow-200": "rgba(253, 230, 138, 1)",
|
|
51
|
+
"yellow-300": "rgba(252, 211, 77, 1)",
|
|
52
|
+
"yellow-400": "rgba(251, 191, 36, 1)",
|
|
53
|
+
"yellow-500": "rgba(245, 158, 11, 1)",
|
|
54
|
+
"yellow-600": "rgba(217, 119, 6, 1)",
|
|
55
|
+
"yellow-700": "rgba(180, 83, 9, 1)",
|
|
56
|
+
"yellow-800": "rgba(146, 64, 14, 1)",
|
|
57
|
+
"yellow-900": "rgba(120, 53, 15, 1)",
|
|
58
|
+
"green-50": "rgba(236, 253, 245, 1)",
|
|
59
|
+
"green-100": "rgba(209, 250, 229, 1)",
|
|
60
|
+
"green-200": "rgba(167, 243, 208, 1)",
|
|
61
|
+
"green-300": "rgba(110, 231, 183, 1)",
|
|
62
|
+
"green-400": "rgba(52, 211, 153, 1)",
|
|
63
|
+
"green-500": "rgba(16, 185, 129, 1)",
|
|
64
|
+
"green-600": "rgba(5, 150, 105, 1)",
|
|
65
|
+
"green-700": "rgba(4, 120, 87, 1)",
|
|
66
|
+
"green-800": "rgba(6, 95, 70, 1)",
|
|
67
|
+
"green-900": "rgba(6, 78, 59, 1)",
|
|
68
|
+
"blue-50": "rgba(239, 246, 255, 1)",
|
|
69
|
+
"blue-100": "rgba(219, 234, 254, 1)",
|
|
70
|
+
"blue-200": "rgba(191, 219, 254, 1)",
|
|
71
|
+
"blue-300": "rgba(147, 197, 253, 1)",
|
|
72
|
+
"blue-400": "rgba(96, 165, 250, 1)",
|
|
73
|
+
"blue-500": "rgba(59, 130, 246, 1)",
|
|
74
|
+
"blue-600": "rgba(37, 99, 235, 1)",
|
|
75
|
+
"blue-700": "rgba(29, 78, 216, 1)",
|
|
76
|
+
"blue-800": "rgba(30, 64, 175, 1)",
|
|
77
|
+
"blue-900": "rgba(30, 58, 138, 1)",
|
|
78
|
+
"indigo-50": "rgba(238, 242, 255, 1)",
|
|
79
|
+
"indigo-100": "rgba(224, 231, 255, 1)",
|
|
80
|
+
"indigo-200": "rgba(199, 210, 254, 1)",
|
|
81
|
+
"indigo-300": "rgba(165, 180, 252, 1)",
|
|
82
|
+
"indigo-400": "rgba(129, 140, 248, 1)",
|
|
83
|
+
"indigo-500": "rgba(99, 102, 241, 1)",
|
|
84
|
+
"indigo-600": "rgba(79, 70, 229, 1)",
|
|
85
|
+
"indigo-700": "rgba(67, 56, 202, 1)",
|
|
86
|
+
"indigo-800": "rgba(55, 48, 163, 1)",
|
|
87
|
+
"indigo-900": "rgba(49, 46, 129, 1)",
|
|
88
|
+
"purple-50": "rgba(245, 243, 255, 1)",
|
|
89
|
+
"purple-100": "rgba(237, 233, 254, 1)",
|
|
90
|
+
"purple-200": "rgba(221, 214, 254, 1)",
|
|
91
|
+
"purple-300": "rgba(196, 181, 253, 1)",
|
|
92
|
+
"purple-400": "rgba(167, 139, 250, 1)",
|
|
93
|
+
"purple-500": "rgba(139, 92, 246, 1)",
|
|
94
|
+
"purple-600": "rgba(124, 58, 237, 1)",
|
|
95
|
+
"purple-700": "rgba(109, 40, 217, 1)",
|
|
96
|
+
"purple-800": "rgba(91, 33, 182, 1)",
|
|
97
|
+
"purple-900": "rgba(76, 29, 149, 1)",
|
|
98
|
+
"pink-50": "rgba(253, 242, 248, 1)",
|
|
99
|
+
"pink-100": "rgba(252, 231, 243, 1)",
|
|
100
|
+
"pink-200": "rgba(251, 207, 232, 1)",
|
|
101
|
+
"pink-300": "rgba(249, 168, 212, 1)",
|
|
102
|
+
"pink-400": "rgba(244, 114, 182, 1)",
|
|
103
|
+
"pink-500": "rgba(236, 72, 153, 1)",
|
|
104
|
+
"pink-600": "rgba(219, 39, 119, 1)",
|
|
105
|
+
"pink-700": "rgba(190, 24, 93, 1)",
|
|
106
|
+
"pink-800": "rgba(157, 23, 77, 1)",
|
|
107
|
+
"pink-900": "rgba(131, 24, 67, 1)",
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
var spacing = KeyValues{
|
|
112
|
+
Keys: M{
|
|
113
|
+
"mr": "margin-right",
|
|
114
|
+
"ml": "margin-left",
|
|
115
|
+
"mt": "margin-top",
|
|
116
|
+
"mb": "margin-bottom",
|
|
117
|
+
"mx": Arr{
|
|
118
|
+
"margin-left",
|
|
119
|
+
"margin-right",
|
|
120
|
+
},
|
|
121
|
+
"my": Arr{
|
|
122
|
+
"margin-top",
|
|
123
|
+
"margin-bottom",
|
|
124
|
+
},
|
|
125
|
+
"m": "margin",
|
|
126
|
+
"pr": "padding-right",
|
|
127
|
+
"pl": "padding-left",
|
|
128
|
+
"pt": "padding-top",
|
|
129
|
+
"pb": "padding-bottom",
|
|
130
|
+
"px": Arr{
|
|
131
|
+
"padding-left",
|
|
132
|
+
"padding-right",
|
|
133
|
+
},
|
|
134
|
+
"py": Arr{
|
|
135
|
+
"padding-top",
|
|
136
|
+
"padding-bottom",
|
|
137
|
+
},
|
|
138
|
+
"p": "padding",
|
|
139
|
+
},
|
|
140
|
+
Values: MS{
|
|
141
|
+
"0": "0px",
|
|
142
|
+
"1": "0.25rem",
|
|
143
|
+
"2": "0.5rem",
|
|
144
|
+
"3": "0.75rem",
|
|
145
|
+
"4": "1rem",
|
|
146
|
+
"5": "1.25rem",
|
|
147
|
+
"6": "1.5rem",
|
|
148
|
+
"7": "1.75rem",
|
|
149
|
+
"8": "2rem",
|
|
150
|
+
"9": "2.25rem",
|
|
151
|
+
"10": "2.5rem",
|
|
152
|
+
"11": "2.75rem",
|
|
153
|
+
"12": "3rem",
|
|
154
|
+
"14": "3.5rem",
|
|
155
|
+
"16": "4rem",
|
|
156
|
+
"20": "5rem",
|
|
157
|
+
"24": "6rem",
|
|
158
|
+
"28": "7rem",
|
|
159
|
+
"32": "8rem",
|
|
160
|
+
"36": "9rem",
|
|
161
|
+
"40": "10rem",
|
|
162
|
+
"44": "11rem",
|
|
163
|
+
"48": "12rem",
|
|
164
|
+
"52": "13rem",
|
|
165
|
+
"56": "14rem",
|
|
166
|
+
"60": "15rem",
|
|
167
|
+
"64": "16rem",
|
|
168
|
+
"72": "18rem",
|
|
169
|
+
"80": "20rem",
|
|
170
|
+
"96": "24rem",
|
|
171
|
+
"auto": "auto",
|
|
172
|
+
"px": "1px",
|
|
173
|
+
"0.5": "0.125rem",
|
|
174
|
+
"1.5": "0.375rem",
|
|
175
|
+
"2.5": "0.625rem",
|
|
176
|
+
"3.5": "0.875rem",
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
var radius = KeyValues{
|
|
181
|
+
Keys: M{
|
|
182
|
+
"rounded": "border-radius",
|
|
183
|
+
"rounded-t": "border-top-radius",
|
|
184
|
+
"rounded-r": "border-right-radius",
|
|
185
|
+
"rounded-l": "border-left-radius",
|
|
186
|
+
"rounded-b": "border-bottom-radius",
|
|
187
|
+
"rounded-tl": Arr{
|
|
188
|
+
"border-top-radius",
|
|
189
|
+
"border-left-radius",
|
|
190
|
+
},
|
|
191
|
+
"rounded-tr": Arr{
|
|
192
|
+
"border-top-radius",
|
|
193
|
+
"border-right-radius",
|
|
194
|
+
},
|
|
195
|
+
"rounded-bl": Arr{
|
|
196
|
+
"border-bottom-radius",
|
|
197
|
+
"border-left-radius",
|
|
198
|
+
},
|
|
199
|
+
"rounded-br": Arr{
|
|
200
|
+
"border-bottom-radius",
|
|
201
|
+
"border-right-radius",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
Values: MS{
|
|
205
|
+
"none": "0px",
|
|
206
|
+
"sm": "0.125rem",
|
|
207
|
+
"": "0.25rem",
|
|
208
|
+
"md": "0.375rem",
|
|
209
|
+
"lg": "0.5rem",
|
|
210
|
+
"xl": "0.75rem",
|
|
211
|
+
"2xl": "1rem",
|
|
212
|
+
"3xl": "1.5rem",
|
|
213
|
+
"full": "9999px",
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
var borders = KeyValues{
|
|
218
|
+
Keys: M{
|
|
219
|
+
"border": "border-width",
|
|
220
|
+
"border-l": "border-left-width",
|
|
221
|
+
"border-r": "border-right-width",
|
|
222
|
+
"border-t": "border-top-width",
|
|
223
|
+
"border-b": "border-bottom-width",
|
|
224
|
+
},
|
|
225
|
+
Values: MS{
|
|
226
|
+
"0": "0px",
|
|
227
|
+
"2": "2px",
|
|
228
|
+
"4": "4px",
|
|
229
|
+
"8": "8px",
|
|
230
|
+
"": "1px",
|
|
231
|
+
},
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
var sizes = KeyValues{
|
|
235
|
+
Keys: M{
|
|
236
|
+
"h": "height",
|
|
237
|
+
"w": "width",
|
|
238
|
+
"top": "top",
|
|
239
|
+
"left": "left",
|
|
240
|
+
"bottom": "bottom",
|
|
241
|
+
"right": "right",
|
|
242
|
+
"minh": "min-height",
|
|
243
|
+
"minw": "min-width",
|
|
244
|
+
"maxh": "max-height",
|
|
245
|
+
"maxw": "max-width",
|
|
246
|
+
},
|
|
247
|
+
Values: MS{
|
|
248
|
+
"auto": "auto",
|
|
249
|
+
"min": "min-content",
|
|
250
|
+
"max": "max-content",
|
|
251
|
+
"0": "0px",
|
|
252
|
+
"1": "0.25rem",
|
|
253
|
+
"2": "0.5rem",
|
|
254
|
+
"3": "0.75rem",
|
|
255
|
+
"4": "1rem",
|
|
256
|
+
"5": "1.25rem",
|
|
257
|
+
"6": "1.5rem",
|
|
258
|
+
"7": "1.75rem",
|
|
259
|
+
"8": "2rem",
|
|
260
|
+
"9": "2.25rem",
|
|
261
|
+
"10": "2.5rem",
|
|
262
|
+
"11": "2.75rem",
|
|
263
|
+
"12": "3rem",
|
|
264
|
+
"14": "3.5rem",
|
|
265
|
+
"16": "4rem",
|
|
266
|
+
"20": "5rem",
|
|
267
|
+
"24": "6rem",
|
|
268
|
+
"28": "7rem",
|
|
269
|
+
"32": "8rem",
|
|
270
|
+
"36": "9rem",
|
|
271
|
+
"40": "10rem",
|
|
272
|
+
"44": "11rem",
|
|
273
|
+
"48": "12rem",
|
|
274
|
+
"52": "13rem",
|
|
275
|
+
"56": "14rem",
|
|
276
|
+
"60": "15rem",
|
|
277
|
+
"64": "16rem",
|
|
278
|
+
"72": "18rem",
|
|
279
|
+
"80": "20rem",
|
|
280
|
+
"96": "24rem",
|
|
281
|
+
"px": "1px",
|
|
282
|
+
"0.5": "0.125rem",
|
|
283
|
+
"1.5": "0.375rem",
|
|
284
|
+
"2.5": "0.625rem",
|
|
285
|
+
"3.5": "0.875rem",
|
|
286
|
+
"1/2": "50%",
|
|
287
|
+
"1/3": "33.33%",
|
|
288
|
+
"2/3": "66.66%",
|
|
289
|
+
"1/4": "25%",
|
|
290
|
+
"2/4": "50%",
|
|
291
|
+
"3/4": "75%",
|
|
292
|
+
"1/5": "20%",
|
|
293
|
+
"2/5": "40%",
|
|
294
|
+
"3/5": "60%",
|
|
295
|
+
"4/5": "80%",
|
|
296
|
+
"1/6": "16.66%",
|
|
297
|
+
"2/6": "33.33%",
|
|
298
|
+
"3/6": "50%",
|
|
299
|
+
"4/6": "66.66%",
|
|
300
|
+
"5/6": "83.33%",
|
|
301
|
+
"1/12": "8.33%",
|
|
302
|
+
"2/12": "16.66%",
|
|
303
|
+
"3/12": "25%",
|
|
304
|
+
"4/12": "33.33%",
|
|
305
|
+
"5/12": "41.66%",
|
|
306
|
+
"6/12": "50%",
|
|
307
|
+
"7/12": "58.33%",
|
|
308
|
+
"8/12": "66.66%",
|
|
309
|
+
"9/12": "75%",
|
|
310
|
+
"10/12": "83.33%",
|
|
311
|
+
"11/12": "91.66%",
|
|
312
|
+
"full": "100%",
|
|
313
|
+
},
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
var twClassLookup = map[string]string{
|
|
317
|
+
"flex": "display: flex;",
|
|
318
|
+
"inline-flex": "display: inline-flex;",
|
|
319
|
+
"block": "display: block;",
|
|
320
|
+
"inline-block": "display: inline-block;",
|
|
321
|
+
"inline": "display: inline;",
|
|
322
|
+
"table": "display: table;",
|
|
323
|
+
"inline-table": "display: inline-table;",
|
|
324
|
+
"grid": "display: grid;",
|
|
325
|
+
"inline-grid": "display: inline-grid;",
|
|
326
|
+
"contents": "display: contents;",
|
|
327
|
+
"list-item": "display: list-item;",
|
|
328
|
+
"hidden": "display: none;",
|
|
329
|
+
"flex-1": "flex: 1;",
|
|
330
|
+
"flex-row": "flex-direction: row;",
|
|
331
|
+
"flex-col": "flex-direction: column;",
|
|
332
|
+
"flex-wrap": "flex-wrap: wrap;",
|
|
333
|
+
"flex-nowrap": "flex-wrap: nowrap;",
|
|
334
|
+
"flex-wrap-reverse": "flex-wrap: wrap-reverse;",
|
|
335
|
+
"items-baseline": "align-items: baseline;",
|
|
336
|
+
"items-start": "align-items: flex-start;",
|
|
337
|
+
"items-center": "align-items: center;",
|
|
338
|
+
"items-end": "align-items: flex-end;",
|
|
339
|
+
"items-stretch": "align-items: stretch;",
|
|
340
|
+
"justify-start": "justify-content: flex-start;",
|
|
341
|
+
"justify-end": "justify-content: flex-end;",
|
|
342
|
+
"justify-center": "justify-content: center;",
|
|
343
|
+
"justify-between": "justify-content: space-between;",
|
|
344
|
+
"justify-around": "justify-content: space-around;",
|
|
345
|
+
"justify-evenly": "justify-content: space-evenly;",
|
|
346
|
+
"text-left": "text-align: left;",
|
|
347
|
+
"text-center": "text-align: center;",
|
|
348
|
+
"text-right": "text-align: right;",
|
|
349
|
+
"text-justify": "text-align: justify;",
|
|
350
|
+
"underline": "text-decoration: underline;",
|
|
351
|
+
"line-through": "text-decoration: line-through;",
|
|
352
|
+
"no-underline": "text-decoration: none;",
|
|
353
|
+
"whitespace-normal": "white-space: normal;",
|
|
354
|
+
"whitespace-nowrap": "white-space: nowrap;",
|
|
355
|
+
"whitespace-pre": "white-space: pre;",
|
|
356
|
+
"whitespace-pre-line": "white-space: pre-line;",
|
|
357
|
+
"whitespace-pre-wrap": "white-space: pre-wrap;",
|
|
358
|
+
"break-normal": "word-break: normal; overflow-wrap: normal;",
|
|
359
|
+
"break-words": "word-break: break-word;",
|
|
360
|
+
"break-all": "word-break: break-all;",
|
|
361
|
+
"font-sans": "font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";",
|
|
362
|
+
"font-serif": "font-family: ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif;",
|
|
363
|
+
"font-mono": "font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;",
|
|
364
|
+
"font-thin": "font-weight: 100;",
|
|
365
|
+
"font-extralight": "font-weight: 200;",
|
|
366
|
+
"font-light": "font-weight: 300;",
|
|
367
|
+
"font-normal": "font-weight: 400;",
|
|
368
|
+
"font-medium": "font-weight: 500;",
|
|
369
|
+
"font-semibold": "font-weight: 600;",
|
|
370
|
+
"font-bold": "font-weight: 700;",
|
|
371
|
+
"font-extrabold": "font-weight: 800;",
|
|
372
|
+
"font-black": "font-weight: 900;",
|
|
373
|
+
"text-xs": "font-size: 0.75rem; line-height: 1rem;",
|
|
374
|
+
"text-sm": "font-size: 0.875rem; line-height: 1.25rem;",
|
|
375
|
+
"text-base": "font-size: 1rem; line-height: 1.5rem;",
|
|
376
|
+
"text-lg": "font-size: 1.125rem; line-height: 1.75rem;",
|
|
377
|
+
"text-xl": "font-size: 1.25rem; line-height: 1.75rem;",
|
|
378
|
+
"text-2xl": "font-size: 1.5rem; line-height: 2rem;",
|
|
379
|
+
"text-3xl": "font-size: 1.875rem; line-height: 2.25rem;",
|
|
380
|
+
"text-4xl": "font-size: 2.25rem; line-height: 2.5rem;",
|
|
381
|
+
"text-5xl": "font-size: 3rem; line-height: 1;",
|
|
382
|
+
"text-6xl": "font-size: 3.75rem;; line-height: 1;",
|
|
383
|
+
"text-7xl": "font-size: 4.5rem; line-height: 1;",
|
|
384
|
+
"text-8xl": "font-size: 6rem; line-height: 1;",
|
|
385
|
+
"text-9xl": "font-size: 8rem; line-height: 1;",
|
|
386
|
+
"cursor-auto": "cursor: auto;",
|
|
387
|
+
"cursor-default": "cursor: default;",
|
|
388
|
+
"cursor-pointer": "cursor: pointer;",
|
|
389
|
+
"cursor-wait": "cursor: wait;",
|
|
390
|
+
"cursor-text": "cursor: text;",
|
|
391
|
+
"cursor-move": "cursor: move;",
|
|
392
|
+
"cursor-help": "cursor: help;",
|
|
393
|
+
"cursor-not-allowed": "cursor: not-allowed;",
|
|
394
|
+
"pointer-events-none": "pointer-events: none;",
|
|
395
|
+
"pointer-events-auto": "pointer-events: auto;",
|
|
396
|
+
"select-none": "user-select: none;",
|
|
397
|
+
"select-text": "user-select: text;",
|
|
398
|
+
"select-all": "user-select: all;",
|
|
399
|
+
"select-auto": "user-select: auto;",
|
|
400
|
+
"w-screen": "100vw",
|
|
401
|
+
"h-screen": "100vh",
|
|
402
|
+
"static": "position: static;",
|
|
403
|
+
"fixed": "position: fixed;",
|
|
404
|
+
"absolute": "position: absolute;",
|
|
405
|
+
"relative": "position: relative;",
|
|
406
|
+
"sticky": "position: sticky;",
|
|
407
|
+
"overflow-auto": "overflow: auto;",
|
|
408
|
+
"overflow-hidden": "overflow: hidden;",
|
|
409
|
+
"overflow-visible": "overflow: visible;",
|
|
410
|
+
"overflow-scroll": "overflow: scroll;",
|
|
411
|
+
"overflow-x-auto": "overflow-x: auto;",
|
|
412
|
+
"overflow-y-auto": "overflow-y: auto;",
|
|
413
|
+
"overflow-x-hidden": "overflow-x: hidden;",
|
|
414
|
+
"overflow-y-hidden": "overflow-y: hidden;",
|
|
415
|
+
"overflow-x-visible": "overflow-x: visible;",
|
|
416
|
+
"overflow-y-visible": "overflow-y: visible;",
|
|
417
|
+
"overflow-x-scroll": "overflow-x: scroll;",
|
|
418
|
+
"overflow-y-scroll": "overflow-y: scroll;",
|
|
419
|
+
"origin-center": "transform-origin: center;",
|
|
420
|
+
"origin-top": "transform-origin: top;",
|
|
421
|
+
"origin-top-right": "transform-origin: top right;",
|
|
422
|
+
"origin-right": "transform-origin: right;",
|
|
423
|
+
"origin-bottom-right": "transform-origin: bottom right;",
|
|
424
|
+
"origin-bottom": "transform-origin: bottom;",
|
|
425
|
+
"origin-bottom-left": "transform-origin: bottom left;",
|
|
426
|
+
"origin-left": "transform-origin: left;",
|
|
427
|
+
"origin-top-left": "transform-origin: top left;",
|
|
428
|
+
"shadow-sm": "box-shadow: 0 0 #0000, 0 0 #0000, 0 1px 2px 0 rgba(0, 0, 0, 0.05);",
|
|
429
|
+
"shadow": "box-shadow: 0 0 #0000, 0 0 #0000, 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);",
|
|
430
|
+
"shadow-md": "box-shadow: 0 0 #0000, 0 0 #0000, 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);",
|
|
431
|
+
"shadow-lg": "box-shadow: 0 0 #0000, 0 0 #0000, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);",
|
|
432
|
+
"shadow-xl": "box-shadow: 0 0 #0000, 0 0 #0000, 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);",
|
|
433
|
+
"shadow-2xl": "box-shadow: 0 0 #0000, 0 0 #0000, 0 25px 50px -12px rgba(0, 0, 0, 0.25);",
|
|
434
|
+
"shadow-inner": "box-shadow: 0 0 #0000, 0 0 #0000, inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);",
|
|
435
|
+
"shadow-none": "box-shadow: 0 0 #0000, 0 0 #0000, 0 0 #0000;",
|
|
436
|
+
"ring-inset": "--tw-ring-inset: insest;",
|
|
437
|
+
"ring-0": "box-shadow: 0 0 0 calc(0px + 0px) rgba(59, 130, 246, 0.5);",
|
|
438
|
+
"ring-1": "box-shadow: 0 0 0 calc(1px + 0px) rgba(59, 130, 246, 0.5);",
|
|
439
|
+
"ring-2": "box-shadow: 0 0 0 calc(2px + 0px) rgba(59, 130, 246, 0.5);",
|
|
440
|
+
"ring-4": "box-shadow: 0 0 0 calc(4px + 0px) rgba(59, 130, 246, 0.5);",
|
|
441
|
+
"ring-8": "box-shadow: 0 0 0 calc(8px + 0px) rgba(59, 130, 246, 0.5);",
|
|
442
|
+
"ring": "box-shadow: 0 0 0 calc(3px + 0px) rgba(59, 130, 246, 0.5);",
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
func init() {
|
|
446
|
+
mapApply(sizes)
|
|
447
|
+
mapApply(spacing)
|
|
448
|
+
mapApply(colors)
|
|
449
|
+
mapApply(borders)
|
|
450
|
+
mapApply(radius)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
func mapApply(obj KeyValues) {
|
|
454
|
+
for key, v := range obj.Keys {
|
|
455
|
+
for vkey, vv := range obj.Values {
|
|
456
|
+
suffix := ""
|
|
457
|
+
if vkey != "" {
|
|
458
|
+
suffix = "-" + vkey
|
|
459
|
+
}
|
|
460
|
+
className := key + suffix
|
|
461
|
+
if vstring, ok := v.(string); ok {
|
|
462
|
+
twClassLookup[className] = vstring + ": " + vv + ";"
|
|
463
|
+
}
|
|
464
|
+
if varr, ok := v.(Arr); ok {
|
|
465
|
+
for _, kk := range varr {
|
|
466
|
+
twClassLookup[className] = kk.(string) + ": " + vv + ";"
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
go.mod
CHANGED
|
@@ -6,6 +6,7 @@ require (
|
|
|
6
6
|
github.com/PuerkitoBio/goquery v1.7.1 // indirect
|
|
7
7
|
github.com/apex/gateway/v2 v2.0.0
|
|
8
8
|
github.com/aymerick/raymond v2.0.2+incompatible // indirect
|
|
9
|
+
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible // indirect
|
|
9
10
|
github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f // indirect
|
|
10
11
|
github.com/gorilla/mux v1.8.0
|
|
11
12
|
github.com/kr/pretty v0.1.0 // indirect
|
|
@@ -18,7 +19,7 @@ require (
|
|
|
18
19
|
github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c // indirect
|
|
19
20
|
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect
|
|
20
21
|
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect
|
|
21
|
-
github.com/stretchr/testify v1.
|
|
22
|
+
github.com/stretchr/testify v1.7.0
|
|
22
23
|
golang.org/x/mod v0.5.1 // indirect
|
|
23
24
|
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf // indirect
|
|
24
25
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
|
go.sum
CHANGED
|
@@ -12,6 +12,8 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP
|
|
|
12
12
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
|
13
13
|
github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0=
|
|
14
14
|
github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
|
15
|
+
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible h1:UafIjBvWQmS9i/xRg+CamMrnLTKNzo+bdmT/oH34c2Y=
|
|
16
|
+
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible/go.mod h1:Au1Xw1sgaJ5iSFktEhYsS0dbQiS1B0/XMXl+42y9Ilk=
|
|
15
17
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
16
18
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
17
19
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
@@ -63,6 +65,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|
|
63
65
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
64
66
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
|
65
67
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
68
|
+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
69
|
+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
66
70
|
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
|
|
67
71
|
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
|
|
68
72
|
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
html.go
CHANGED
|
@@ -1,11 +1,144 @@
|
|
|
1
1
|
package wapp
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"fmt"
|
|
4
6
|
"io"
|
|
5
|
-
"reflect"
|
|
6
7
|
"strconv"
|
|
8
|
+
"strings"
|
|
9
|
+
|
|
10
|
+
"github.com/gobuffalo/velvet"
|
|
7
11
|
)
|
|
8
12
|
|
|
13
|
+
func writeIndent(w io.Writer, indent int) {
|
|
14
|
+
for i := 0; i < indent*2; i++ {
|
|
15
|
+
w.Write([]byte(" "))
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func mergeAttributes(parent *Element, uis ...interface{}) *Element {
|
|
20
|
+
elems := []*Element{}
|
|
21
|
+
for _, v := range uis {
|
|
22
|
+
switch c := v.(type) {
|
|
23
|
+
case Attribute:
|
|
24
|
+
parent.setAttr(c.Key, c.Value)
|
|
25
|
+
case *Element:
|
|
26
|
+
elems = append(elems, c)
|
|
27
|
+
case nil:
|
|
28
|
+
// dont need to add nil items
|
|
29
|
+
default:
|
|
30
|
+
panic(fmt.Sprintf("unknown type in render %+v", v))
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if !parent.selfClosing {
|
|
34
|
+
parent.body = elems
|
|
35
|
+
}
|
|
36
|
+
return parent
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type HtmlPage struct {
|
|
40
|
+
classLookup map[string]bool
|
|
41
|
+
css *bytes.Buffer
|
|
42
|
+
js *bytes.Buffer
|
|
43
|
+
Head *Element
|
|
44
|
+
Body *Element
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func (p *HtmlPage) computeCss(elems []*Element) {
|
|
48
|
+
for _, el := range elems {
|
|
49
|
+
if v, ok := el.attrs["class"]; ok {
|
|
50
|
+
classes := strings.Split(v, " ")
|
|
51
|
+
for _, c := range classes {
|
|
52
|
+
if s, ok := twClassLookup[c]; ok {
|
|
53
|
+
if _, ok2 := p.classLookup[c]; !ok2 {
|
|
54
|
+
p.classLookup[c] = true
|
|
55
|
+
p.css.WriteString("\n ." + c + " { " + s + " } ")
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if len(el.body) > 0 {
|
|
61
|
+
p.computeCss(el.body)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func (p *HtmlPage) computeJs(elems []*Element) {
|
|
67
|
+
for _, el := range elems {
|
|
68
|
+
if strings.HasPrefix(el.text, "wapp_js|") {
|
|
69
|
+
p.js.WriteString(strings.Replace(el.text, "wapp_js|", "", 1) + "\n")
|
|
70
|
+
}
|
|
71
|
+
if len(el.body) > 0 {
|
|
72
|
+
p.computeJs(el.body)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func (p *HtmlPage) WriteHtml(w io.Writer) {
|
|
78
|
+
w.Write([]byte("<!DOCTYPE html>\n"))
|
|
79
|
+
w.Write([]byte("<html>\n"))
|
|
80
|
+
p.computeCss(p.Body.body)
|
|
81
|
+
p.computeJs(p.Body.body)
|
|
82
|
+
p.Head.body = append(p.Head.body, StyleTag(Text(p.css.String())))
|
|
83
|
+
p.Head.writeHtmlIndent(w, 1)
|
|
84
|
+
p.Body.body = append(p.Body.body, Script(Text(p.js.String())))
|
|
85
|
+
p.Body.writeHtmlIndent(w, 1)
|
|
86
|
+
w.Write([]byte("\n</html>"))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func Html(h *Element, b *Element) HtmlPage {
|
|
90
|
+
return HtmlPage{
|
|
91
|
+
classLookup: map[string]bool{},
|
|
92
|
+
js: bytes.NewBuffer(nil),
|
|
93
|
+
css: bytes.NewBuffer(nil),
|
|
94
|
+
Head: h,
|
|
95
|
+
Body: b,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func Head(elems ...*Element) *Element {
|
|
100
|
+
basic := []*Element{
|
|
101
|
+
&Element{tag: "meta", selfClosing: true, attrs: map[string]string{"charset": "UTF-8"}},
|
|
102
|
+
&Element{tag: "meta", selfClosing: true, attrs: map[string]string{"http-equiv": "Content-Type", "content": "text/html;charset=utf-8"}},
|
|
103
|
+
&Element{tag: "meta", selfClosing: true, attrs: map[string]string{"http-equiv": "encoding", "content": "utf-8"}},
|
|
104
|
+
}
|
|
105
|
+
return &Element{tag: "head", body: append(basic, elems...)}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func Body(elems ...*Element) *Element {
|
|
109
|
+
return &Element{tag: "body", body: elems}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func Component(r Reducer, elems ...*Element) *Element {
|
|
113
|
+
v := velvet.NewContext()
|
|
114
|
+
stateMap := map[string]interface{}{}
|
|
115
|
+
actionsMap := map[string]interface{}{}
|
|
116
|
+
// structType := reflect.TypeOf(r)
|
|
117
|
+
for k, v := range r.State {
|
|
118
|
+
stateMap[k+":"] = v
|
|
119
|
+
}
|
|
120
|
+
for k, v := range r.Actions {
|
|
121
|
+
actionsMap[k] = "() {" + v() + "}"
|
|
122
|
+
}
|
|
123
|
+
v.Set("name", r.Name)
|
|
124
|
+
v.Set("state", stateMap)
|
|
125
|
+
v.Set("actions", actionsMap)
|
|
126
|
+
s, err := velvet.Render(`
|
|
127
|
+
Alpine.data('{{ name }}', ({
|
|
128
|
+
state: {
|
|
129
|
+
{{#each state}}{{ @key }}{{ @value }},
|
|
130
|
+
{{/each}}
|
|
131
|
+
},
|
|
132
|
+
{{#each actions}}{{ @key }}{{ @value }},
|
|
133
|
+
{{/each}}
|
|
134
|
+
}));
|
|
135
|
+
`, v)
|
|
136
|
+
if err != nil {
|
|
137
|
+
panic(err)
|
|
138
|
+
}
|
|
139
|
+
return &Element{tag: r.Name, text: "wapp_js|" + s, body: elems}
|
|
140
|
+
}
|
|
141
|
+
|
|
9
142
|
type Element struct {
|
|
10
143
|
tag string
|
|
11
144
|
attrs map[string]string
|
|
@@ -15,7 +148,7 @@ type Element struct {
|
|
|
15
148
|
}
|
|
16
149
|
|
|
17
150
|
func NewElement(tag string, selfClosing bool, uis ...interface{}) *Element {
|
|
18
|
-
return
|
|
151
|
+
return mergeAttributes(&Element{tag: tag, selfClosing: selfClosing}, uis...)
|
|
19
152
|
}
|
|
20
153
|
|
|
21
154
|
func (e *Element) setAttr(k string, v string) {
|
|
@@ -48,17 +181,10 @@ func (e *Element) setAttr(k string, v string) {
|
|
|
48
181
|
}
|
|
49
182
|
}
|
|
50
183
|
|
|
51
|
-
func (e *Element) WriteHtml(w io.Writer) {
|
|
52
|
-
e.writeHtmlIndent(w, 0)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
184
|
func (e *Element) writeHtmlIndent(w io.Writer, indent int) {
|
|
56
|
-
|
|
185
|
+
writeIndent(w, indent)
|
|
57
|
-
if e.tag == "html" {
|
|
58
|
-
w.Write([]byte("<!DOCTYPE html>\n"))
|
|
59
|
-
}
|
|
60
186
|
if e.tag == "text" {
|
|
61
|
-
|
|
187
|
+
writeIndent(w, indent)
|
|
62
188
|
w.Write([]byte(e.text))
|
|
63
189
|
return
|
|
64
190
|
}
|
|
@@ -83,43 +209,22 @@ func (e *Element) writeHtmlIndent(w io.Writer, indent int) {
|
|
|
83
209
|
}
|
|
84
210
|
|
|
85
211
|
for _, c := range e.body {
|
|
212
|
+
if len(e.body) > 1 {
|
|
86
|
-
|
|
213
|
+
w.Write([]byte("\n"))
|
|
214
|
+
}
|
|
87
215
|
if c != nil {
|
|
88
216
|
c.writeHtmlIndent(w, indent+1)
|
|
89
217
|
}
|
|
90
218
|
}
|
|
91
219
|
|
|
92
220
|
if len(e.body) != 0 {
|
|
93
|
-
w.Write([]byte("\n"))
|
|
221
|
+
// w.Write([]byte("\n"))
|
|
94
|
-
|
|
222
|
+
writeIndent(w, indent)
|
|
95
223
|
}
|
|
96
224
|
|
|
97
225
|
w.Write([]byte("</"))
|
|
98
226
|
w.Write([]byte(e.tag))
|
|
99
|
-
w.Write([]byte(">"))
|
|
227
|
+
w.Write([]byte(">\n"))
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
func (e *Element) writeIndent(w io.Writer, indent int) {
|
|
103
|
-
for i := 0; i < indent*4; i++ {
|
|
104
|
-
w.Write([]byte(" "))
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
func Html(elems ...*Element) *Element {
|
|
109
|
-
return &Element{tag: "html", body: elems}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
func Head(elems ...*Element) *Element {
|
|
113
|
-
basic := []*Element{
|
|
114
|
-
&Element{tag: "meta", selfClosing: true, attrs: map[string]string{"charset": "UTF-8"}},
|
|
115
|
-
&Element{tag: "meta", selfClosing: true, attrs: map[string]string{"http-equiv": "Content-Type", "content": "text/html;charset=utf-8"}},
|
|
116
|
-
&Element{tag: "meta", selfClosing: true, attrs: map[string]string{"http-equiv": "encoding", "content": "utf-8"}},
|
|
117
|
-
}
|
|
118
|
-
return &Element{tag: "head", body: append(basic, elems...)}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
func Body(elems ...*Element) *Element {
|
|
122
|
-
return &Element{tag: "body", body: elems}
|
|
123
228
|
}
|
|
124
229
|
|
|
125
230
|
func Title(v string) *Element {
|
|
@@ -161,6 +266,10 @@ func Script(uis ...interface{}) *Element {
|
|
|
161
266
|
return NewElement("script", false, uis...)
|
|
162
267
|
}
|
|
163
268
|
|
|
269
|
+
func StyleTag(uis ...interface{}) *Element {
|
|
270
|
+
return NewElement("style", false, uis...)
|
|
271
|
+
}
|
|
272
|
+
|
|
164
273
|
func Div(uis ...interface{}) *Element {
|
|
165
274
|
return NewElement("div", false, uis...)
|
|
166
275
|
}
|
|
@@ -224,52 +333,6 @@ func Li(uis ...interface{}) *Element {
|
|
|
224
333
|
return NewElement("li", false, uis...)
|
|
225
334
|
}
|
|
226
335
|
|
|
227
|
-
func Row(uis ...interface{}) *Element {
|
|
228
|
-
return NewElement("div", false, append([]interface{}{Css("flex flex-row justify-center items-center")}, uis...)...)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
func Col(uis ...interface{}) *Element {
|
|
232
|
-
return NewElement("div", false, append([]interface{}{Css("flex flex-col justify-center items-center")}, uis...)...)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
func If(expr bool, a *Element) *Element {
|
|
236
|
-
if expr {
|
|
237
|
-
return a
|
|
238
|
-
}
|
|
239
|
-
return nil
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
func IfElse(expr bool, a *Element, b *Element) *Element {
|
|
243
|
-
if expr {
|
|
244
|
-
return a
|
|
245
|
-
}
|
|
246
|
-
return b
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
func Map(source interface{}, f func(i int) *Element) []*Element {
|
|
250
|
-
src := reflect.ValueOf(source)
|
|
251
|
-
if src.Kind() != reflect.Slice {
|
|
252
|
-
panic("range loop source is not a slice: " + src.Type().String())
|
|
253
|
-
}
|
|
254
|
-
body := make([]*Element, 0, src.Len())
|
|
255
|
-
for i := 0; i < src.Len(); i++ {
|
|
256
|
-
body = append(body, f(i))
|
|
257
|
-
}
|
|
258
|
-
return body
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
func Map2(source interface{}, f func(v interface{}, i int) *Element) []*Element {
|
|
262
|
-
src := reflect.ValueOf(source)
|
|
263
|
-
if src.Kind() != reflect.Slice {
|
|
264
|
-
panic("range loop source is not a slice: " + src.Type().String())
|
|
265
|
-
}
|
|
266
|
-
body := make([]*Element, 0, src.Len())
|
|
267
|
-
for i := 0; i < src.Len(); i++ {
|
|
268
|
-
body = append(body, f(src.Index(i), i))
|
|
269
|
-
}
|
|
270
|
-
return body
|
|
271
|
-
}
|
|
272
|
-
|
|
273
336
|
type Attribute struct {
|
|
274
337
|
Key string
|
|
275
338
|
Value string
|
|
@@ -366,24 +429,3 @@ func XData(v string) Attribute {
|
|
|
366
429
|
func XText(v string) Attribute {
|
|
367
430
|
return Attribute{"x-text", v}
|
|
368
431
|
}
|
|
369
|
-
|
|
370
|
-
func MergeAttributes(parent *Element, uis ...interface{}) *Element {
|
|
371
|
-
elems := []*Element{}
|
|
372
|
-
for _, v := range uis {
|
|
373
|
-
switch c := v.(type) {
|
|
374
|
-
case Attribute:
|
|
375
|
-
parent.setAttr(c.Key, c.Value)
|
|
376
|
-
case *Element:
|
|
377
|
-
elems = append(elems, c)
|
|
378
|
-
case nil:
|
|
379
|
-
// dont need to add nil items
|
|
380
|
-
default:
|
|
381
|
-
// fmt.Printf("%v\n", v)
|
|
382
|
-
panic("unknown type in render")
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
if !parent.selfClosing {
|
|
386
|
-
parent.body = elems
|
|
387
|
-
}
|
|
388
|
-
return parent
|
|
389
|
-
}
|
html_test.go
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
package wapp
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"strconv"
|
|
6
|
+
"testing"
|
|
7
|
+
|
|
8
|
+
"github.com/bradleyjkemp/cupaloy"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func Row(uis ...interface{}) *Element {
|
|
12
|
+
return NewElement("div", false, append([]interface{}{Css("flex flex-row justify-center items-center")}, uis...)...)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func Col(uis ...interface{}) *Element {
|
|
16
|
+
return NewElement("div", false, append([]interface{}{Css("flex flex-col justify-center items-center")}, uis...)...)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// type Container func() *Element
|
|
20
|
+
|
|
21
|
+
// func CounterData(start int) Container {
|
|
22
|
+
// return CreateContainer(Reducer{
|
|
23
|
+
// Name: "counter",
|
|
24
|
+
// State: State{
|
|
25
|
+
// "count": start,
|
|
26
|
+
// },
|
|
27
|
+
// Actions: Actions{
|
|
28
|
+
// "increment": func() string { return "this.state.count += 1;" },
|
|
29
|
+
// "decrement": func() string { return "this.state.count -= 1;" },
|
|
30
|
+
// },
|
|
31
|
+
// })
|
|
32
|
+
// }
|
|
33
|
+
|
|
34
|
+
// type CounterState struct {
|
|
35
|
+
// Count int
|
|
36
|
+
// }
|
|
37
|
+
|
|
38
|
+
// type CounterActions struct {
|
|
39
|
+
// Increment func() string
|
|
40
|
+
// Decrement func() string
|
|
41
|
+
// }
|
|
42
|
+
|
|
43
|
+
// type CounterReducer struct {
|
|
44
|
+
// Reducer
|
|
45
|
+
// State CounterState
|
|
46
|
+
// Actions CounterActions
|
|
47
|
+
// }
|
|
48
|
+
|
|
49
|
+
// func CounterData(start int) CounterReducer {
|
|
50
|
+
// return CounterReducer{
|
|
51
|
+
// Reducer: "counter",
|
|
52
|
+
// State: CounterState{
|
|
53
|
+
// Count: 1,
|
|
54
|
+
// },
|
|
55
|
+
// Actions: CounterActions{
|
|
56
|
+
// Increment: func() string { return "this.state.count += 1;" },
|
|
57
|
+
// Decrement: func() string { return "this.state.count -= 1;" },
|
|
58
|
+
// },
|
|
59
|
+
// }
|
|
60
|
+
// }
|
|
61
|
+
|
|
62
|
+
func CounterData(start int) Reducer {
|
|
63
|
+
return Reducer{
|
|
64
|
+
Name: "counter",
|
|
65
|
+
State: State{
|
|
66
|
+
"count": 1,
|
|
67
|
+
},
|
|
68
|
+
Actions: Actions{
|
|
69
|
+
"increment": func() string { return "this.state.count += 1;" },
|
|
70
|
+
"decrement": func() string { return "this.state.count -= 1;" },
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func Counter(start int) *Element {
|
|
76
|
+
data := CounterData(start)
|
|
77
|
+
return Component(data, Col(Css("text-3xl text-gray-700"),
|
|
78
|
+
Row(
|
|
79
|
+
Row(Css("underline"),
|
|
80
|
+
Text("Counter"),
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
Row(XData("counter"),
|
|
84
|
+
Button(Css("btn m-20"), OnClick("decrement"),
|
|
85
|
+
Text("-"),
|
|
86
|
+
),
|
|
87
|
+
Row(Css("m-20 text-8xl"), XText("state.count"),
|
|
88
|
+
Text(strconv.Itoa(start)),
|
|
89
|
+
),
|
|
90
|
+
Button(Css("btn m-20"), OnClick("increment"),
|
|
91
|
+
Text("+"),
|
|
92
|
+
),
|
|
93
|
+
),
|
|
94
|
+
))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func TestHtmlPage(t *testing.T) {
|
|
98
|
+
b := bytes.NewBuffer(nil)
|
|
99
|
+
p := Html(
|
|
100
|
+
Head(
|
|
101
|
+
Meta("title", "title"),
|
|
102
|
+
),
|
|
103
|
+
Body(
|
|
104
|
+
H1(Text("Hello this is a h1")),
|
|
105
|
+
H2(Text("Hello this is a h2")),
|
|
106
|
+
H3(XData("{ message: 'I ❤️ Alpine' }"), XText("message"), Text("")),
|
|
107
|
+
Counter(4),
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
p.WriteHtml(b)
|
|
111
|
+
cupaloy.SnapshotT(t, b.String())
|
|
112
|
+
}
|