~repos /gromer

#golang#htmx#ssr

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.


92a844ef Peter John

tag: v0.5.0

v0.5.0

4 years ago
improve api
Files changed (7) hide show
  1. .snapshots/TestHtmlPage +55 -0
  2. context.go +7 -48
  3. css.go +471 -0
  4. go.mod +2 -1
  5. go.sum +4 -0
  6. html.go +147 -105
  7. 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 WappContext struct {
9
+ type State map[string]interface{}
15
- c.Context
16
- PathParams map[string]string
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
- f := v.(func() string)
12
+ type Reducer struct {
38
- generatedMap[k] = "() {" + f() + "}"
39
- } else {
13
+ Name string
40
- generatedMap[k+":"] = v
41
- }
42
- }
43
- v.Set("data", generatedMap)
14
+ State
44
- s, err := velvet.Render(`
45
- Alpine.data('{{ name }}', () => {
46
- return {
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.(*Element); ok {
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.6.1
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 MergeAttributes(&Element{tag: tag, selfClosing: selfClosing}, uis...)
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
- e.writeIndent(w, indent)
185
+ writeIndent(w, indent)
57
- if e.tag == "html" {
58
- w.Write([]byte("<!DOCTYPE html>\n"))
59
- }
60
186
  if e.tag == "text" {
61
- e.writeIndent(w, indent)
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
- w.Write([]byte("\n"))
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
- e.writeIndent(w, indent)
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
+ }