Skip to content

Commit 880b4bd

Browse files
committed
{admin,template}: adds initial working version
1 parent 5a25d76 commit 880b4bd

File tree

3 files changed

+851
-0
lines changed

3 files changed

+851
-0
lines changed

admin.go

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
package admin
2+
3+
import (
4+
"bytes"
5+
_ "embed"
6+
"html/template"
7+
"log"
8+
"net/http"
9+
textTemplate "text/template"
10+
11+
"github.com/evanw/esbuild/pkg/api"
12+
)
13+
14+
//go:embed templates/materialUI.tsx
15+
var materialUIJSXTemplateText string
16+
17+
//go:embed templates/antDesignUI.tsx
18+
var antDesignUIJSXTemplateText string
19+
20+
var debug = true
21+
22+
type UITheme int8
23+
24+
const (
25+
MaterialUI UITheme = iota
26+
AntDesignUI
27+
)
28+
29+
type FieldType uint
30+
31+
const (
32+
InputPassword FieldType = iota
33+
InputText
34+
)
35+
36+
type PageType uint
37+
38+
const (
39+
DashboardPage PageType = iota
40+
SideFormPage
41+
)
42+
43+
type Field struct {
44+
ID string
45+
Label string
46+
Type FieldType
47+
IsRequired bool
48+
Value string
49+
}
50+
51+
type Fields []Field
52+
53+
type Pager interface {
54+
Fields() Fields
55+
}
56+
57+
type Page struct {
58+
ID string
59+
URL string
60+
Type PageType
61+
Form Form
62+
}
63+
64+
type Pages []Page
65+
66+
type Submit struct {
67+
Label string
68+
URL string
69+
Method string
70+
RedirectURL string
71+
}
72+
73+
type Form struct {
74+
ID string
75+
Fields Fields
76+
Submit Submit
77+
}
78+
79+
type Config struct {
80+
UITheme UITheme
81+
Endpoint string
82+
Pages Pages
83+
}
84+
85+
type (
86+
Admin interface {
87+
ServeHTTP(w http.ResponseWriter, r *http.Request)
88+
}
89+
90+
admin struct {
91+
pages Pages
92+
uiTheme UITheme
93+
}
94+
)
95+
96+
func newTemplate(name string) *textTemplate.Template {
97+
return textTemplate.New(name).Delims("[[", "]]")
98+
}
99+
100+
func (ad *admin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
101+
materialUIJSXTemplateTextFn := func() string {
102+
return materialUIJSXTemplateText
103+
}
104+
105+
materialUIThemeLinkCSSFn := func() template.HTML {
106+
return template.HTML(`
107+
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
108+
<link rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons" />
109+
`)
110+
}
111+
112+
materialUIThemeScriptJSFn := func() template.HTML {
113+
if debug {
114+
return template.HTML(`
115+
<script src="https://cdn.jsdelivr.net/npm/@material-ui/[email protected]/umd/material-ui.development.js"></script>
116+
`)
117+
}
118+
119+
return template.HTML(`
120+
<script src="//cdn.jsdelivr.net/npm/@material-ui/[email protected]/umd/material-ui.production.min.js"></script>
121+
`)
122+
}
123+
124+
antDesignUIJSXTemplateTextFn := func() string {
125+
return antDesignUIJSXTemplateText
126+
}
127+
128+
antDesignUIThemeLinkCSSFn := func() template.HTML {
129+
return template.HTML(`
130+
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/[email protected]/dist/antd.min.css">
131+
`)
132+
}
133+
134+
antDesignUIThemeScriptJSFn := func() template.HTML {
135+
return template.HTML(`
136+
<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/antd.min.js"></script>
137+
`)
138+
}
139+
140+
var jsxTemplateText string
141+
var themeLinkCSS template.HTML
142+
var themeScriptJS template.HTML
143+
144+
switch ad.uiTheme {
145+
case MaterialUI:
146+
jsxTemplateText = materialUIJSXTemplateTextFn()
147+
themeLinkCSS = materialUIThemeLinkCSSFn()
148+
themeScriptJS = materialUIThemeScriptJSFn()
149+
case AntDesignUI:
150+
jsxTemplateText = antDesignUIJSXTemplateTextFn()
151+
themeLinkCSS = antDesignUIThemeLinkCSSFn()
152+
themeScriptJS = antDesignUIThemeScriptJSFn()
153+
default:
154+
jsxTemplateText = materialUIJSXTemplateTextFn()
155+
themeLinkCSS = materialUIThemeLinkCSSFn()
156+
themeScriptJS = materialUIThemeScriptJSFn()
157+
}
158+
159+
wrap := func(ID string, label string, isRequired bool, value string) map[string]interface{} {
160+
return map[string]interface{}{
161+
"ID": ID,
162+
"label": label,
163+
"isRequired": isRequired,
164+
"value": value,
165+
}
166+
}
167+
168+
wrapPage := func(page Page) map[string]interface{} {
169+
return map[string]interface{}{
170+
"page": page,
171+
}
172+
}
173+
174+
jsxTemplate, err := newTemplate("JSX").Funcs(textTemplate.FuncMap{
175+
"Wrap": wrap,
176+
"WrapPage": wrapPage,
177+
}).Parse(jsxTemplateText)
178+
if err != nil {
179+
log.Printf("failed to parse TSX template: %v", err)
180+
181+
http.Error(w, err.Error(), http.StatusInternalServerError)
182+
return
183+
}
184+
185+
var jsxWriter bytes.Buffer
186+
187+
jsxData := struct {
188+
Pages Pages
189+
DashboardPage PageType
190+
SideFormPage PageType
191+
}{
192+
Pages: ad.pages,
193+
DashboardPage: DashboardPage,
194+
SideFormPage: SideFormPage,
195+
}
196+
197+
if err := jsxTemplate.Execute(&jsxWriter, jsxData); err != nil {
198+
log.Printf("failed to render Go template to TSX template: %v", err)
199+
200+
http.Error(w, err.Error(), http.StatusInternalServerError)
201+
return
202+
}
203+
204+
indexJSMinified := api.Transform(jsxWriter.String(), api.TransformOptions{
205+
Loader: api.LoaderTSX,
206+
MinifyWhitespace: !debug,
207+
MinifyIdentifiers: !debug,
208+
MinifySyntax: !debug,
209+
})
210+
211+
if len(indexJSMinified.Errors) > 0 {
212+
log.Printf("failed to transform and minify TSX template, errors: %+v", indexJSMinified.Errors)
213+
214+
http.Error(w, "failed to render", http.StatusInternalServerError)
215+
return
216+
}
217+
218+
indexData := struct {
219+
Debug bool
220+
ThemeJS template.JS
221+
ThemeLinkCSS template.HTML
222+
ThemeScriptJS template.HTML
223+
Title string
224+
Pages Pages
225+
}{
226+
Debug: debug,
227+
ThemeJS: template.JS(indexJSMinified.Code),
228+
ThemeLinkCSS: themeLinkCSS,
229+
ThemeScriptJS: themeScriptJS,
230+
Title: "admin",
231+
Pages: ad.pages,
232+
}
233+
234+
adminTemplate, err := newTemplate("Admin").Parse(adminTemplateText)
235+
if err != nil {
236+
log.Printf("failed to parse TSX template: %v", err)
237+
238+
http.Error(w, err.Error(), http.StatusInternalServerError)
239+
return
240+
}
241+
242+
if err := adminTemplate.Execute(w, indexData); err != nil {
243+
log.Printf("failed to render Go template to http.ResponseWriter: %v", err)
244+
245+
http.Error(w, err.Error(), http.StatusInternalServerError)
246+
return
247+
}
248+
}
249+
250+
func New(config *Config) Admin {
251+
return &admin{pages: config.Pages, uiTheme: config.UITheme}
252+
}
253+
254+
const adminTemplateText string = `
255+
[[ define "Admin" ]]
256+
257+
<!DOCTYPE html>
258+
<html>
259+
<head>
260+
<meta charset=utf-8/>
261+
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
262+
<title>[[ .Title ]]</title>
263+
[[ .ThemeLinkCSS ]]
264+
</head>
265+
<body>
266+
<div id="root"></div>
267+
268+
[[ if .Debug ]]
269+
<script src="//cdn.jsdelivr.net/npm/[email protected]/umd/react.development.js"></script>
270+
<script src="//cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.development.js"></script>
271+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react-router-dom.js"></script>
272+
[[ else ]]
273+
<script src="//cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js"></script>
274+
<script src="//cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.production.min.js"></script>
275+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react-router-dom.min.js"></script>
276+
[[ end ]]
277+
278+
[[ .ThemeScriptJS ]]
279+
280+
<script type="text/javascript">[[ .ThemeJS ]]</script>
281+
</body>
282+
283+
[[ end ]]
284+
`

templates/antDesignUI.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
[[ define "JSX" ]]
2+
3+
const { Form, Input, Button, Checkbox, Layout } = antd;
4+
5+
const { Header, Content, Footer } = Layout;
6+
7+
const layout = {
8+
labelCol: { offset: 0, span: 8 },
9+
wrapperCol: { offset: 0, span: 8 },
10+
};
11+
12+
const tailLayout = {
13+
wrapperCol: { offset: 8, span: 16 },
14+
};
15+
16+
const Demo = () => {
17+
const onFinish = (values: any) => {
18+
console.log("Success:", values);
19+
};
20+
21+
const onFinishFailed = (errorInfo: any) => {
22+
console.log("Failed:", errorInfo);
23+
};
24+
25+
const usernameRules = [
26+
{ required: true, message: "Please type your Username!" },
27+
];
28+
29+
const passwordRules = [
30+
{ required: true, message: "Please type your Password!" },
31+
];
32+
33+
return (
34+
<Layout className="layout">
35+
<Content style={{ paddingTop: 50 }}>
36+
<div
37+
style={{ background: "#fff", padding: "24px", minHeight: "280px" }}
38+
className="site-layout-content"
39+
>
40+
<Form
41+
{...layout}
42+
name="basic"
43+
initialValues={{ remember: true }}
44+
onFinish={onFinish}
45+
onFinishFailed={onFinishFailed}
46+
>
47+
[[ template "TextField" . ]]
48+
<Form.Item {...tailLayout} name="remember" valuePropName="checked">
49+
<Checkbox>Remember me</Checkbox>
50+
</Form.Item>
51+
<Form.Item {...tailLayout}>
52+
<Button type="primary" htmlType="submit">
53+
Submit
54+
</Button>
55+
</Form.Item>
56+
</Form>
57+
</div>
58+
</Content>
59+
</Layout>
60+
);
61+
};
62+
63+
ReactDOM.render(<Demo />, document.querySelector("#root"));
64+
[[ end ]]
65+
66+
[[ define "TextField" ]]
67+
<Form.Item label="Username" name="username" rules={usernameRules}>
68+
<Input />
69+
</Form.Item>
70+
[[ end ]]

0 commit comments

Comments
 (0)