@inglorious/create-app 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -8
- package/bin/index.js +79 -401
- package/bin/lib/app-jsx.js +280 -0
- package/bin/lib/app-lit.js +275 -0
- package/bin/lib/app-vue.js +239 -0
- package/bin/lib/app.js +402 -0
- package/bin/lib/cli.js +49 -0
- package/bin/lib/constants.js +14 -0
- package/bin/lib/features.js +142 -0
- package/bin/lib/file.js +88 -0
- package/bin/lib/imports.js +64 -0
- package/bin/lib/mobile.js +140 -0
- package/bin/lib/pwa.js +168 -0
- package/bin/lib/questions.js +184 -0
- package/bin/lib/source.js +58 -0
- package/package.json +4 -4
- package/templates/js/package.json +8 -7
- package/templates/js/vite.config.js +2 -1
- package/templates/js-jsx/package.json +8 -7
- package/templates/js-jsx/vite.config.js +2 -1
- package/templates/js-vue/package.json +7 -6
- package/templates/js-vue/vite.config.js +2 -1
- package/templates/ssx-js/package.json +8 -7
- package/templates/ssx-js/src/site.config.js +2 -1
- package/templates/ssx-ts/package.json +9 -7
- package/templates/ssx-ts/src/site.config.ts +1 -0
- package/templates/ts/package.json +10 -8
- package/templates/ts/src/app.ts +4 -2
- package/templates/ts/tsconfig.app.json +2 -0
- package/templates/ts/tsconfig.node.json +2 -0
- package/templates/ts/types/index.d.ts +4 -1
- package/templates/ts/vite.config.ts +2 -1
- package/templates/ts-jsx/package.json +10 -8
- package/templates/ts-jsx/src/app.tsx +2 -2
- package/templates/ts-jsx/tsconfig.app.json +2 -0
- package/templates/ts-jsx/tsconfig.node.json +2 -0
- package/templates/ts-jsx/types/index.d.ts +3 -1
- package/templates/ts-jsx/types/vite-end.d.ts +1 -0
- package/templates/ts-jsx/vite.config.ts +2 -1
- package/templates/ts-vue/eslint.config.ts +1 -1
- package/templates/ts-vue/package.json +10 -8
- package/templates/ts-vue/src/store/middlewares.ts +5 -2
- package/templates/ts-vue/tsconfig.app.json +2 -0
- package/templates/ts-vue/tsconfig.node.json +2 -0
- package/templates/ts-vue/types/index.d.ts +3 -1
- package/templates/ts-vue/vite.config.ts +2 -1
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// The app's components in JSX: .jsx or .tsx files, compiled to lit-html by
|
|
2
|
+
// @inglorious/vite-plugin-jsx. A capitalized tag with props renders a
|
|
3
|
+
// component; `<Message entityId="message1" />` renders an entity. TypeScript
|
|
4
|
+
// expects components to be functions, so in .tsx each capitalized tag is
|
|
5
|
+
// preceded by a `@ts-expect-error`.
|
|
6
|
+
|
|
7
|
+
import { renderLocals, selectChange } from "./app-lit.js"
|
|
8
|
+
import { importBlock } from "./imports.js"
|
|
9
|
+
|
|
10
|
+
export const extension = (app) => (app.isTypeScript ? "tsx" : "jsx")
|
|
11
|
+
|
|
12
|
+
const EXPECT_ERROR =
|
|
13
|
+
"@ts-expect-error - Inglorious components are objects, not functions"
|
|
14
|
+
|
|
15
|
+
// The comment line before a capitalized tag in .tsx: `{/* */}` among JSX
|
|
16
|
+
// children, `//` inside code.
|
|
17
|
+
function expectError(app, indent, isChild = true) {
|
|
18
|
+
if (!app.isTypeScript) return ""
|
|
19
|
+
const comment = isChild ? `{/* ${EXPECT_ERROR} */}` : `// ${EXPECT_ERROR}`
|
|
20
|
+
return `${indent}${comment}\n`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function appSource(app, { pages, navIcons }) {
|
|
24
|
+
const imports = []
|
|
25
|
+
if (navIcons) {
|
|
26
|
+
for (const icon of Object.values(navIcons)) {
|
|
27
|
+
imports.push(`import ${icon.name} from "${icon.from}"`)
|
|
28
|
+
}
|
|
29
|
+
imports.push('import { Icon } from "@inglorious/ui/icon"')
|
|
30
|
+
}
|
|
31
|
+
if (app.ui) {
|
|
32
|
+
imports.push(
|
|
33
|
+
'import { AppBar } from "@inglorious/ui/app-bar"',
|
|
34
|
+
'import { Container } from "@inglorious/ui/container"',
|
|
35
|
+
)
|
|
36
|
+
if (app.tabs) {
|
|
37
|
+
imports.push(
|
|
38
|
+
'import { BottomNavigation } from "@inglorious/ui/bottom-navigation"',
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
if (app.links) imports.push('import { Link } from "@inglorious/ui/link"')
|
|
42
|
+
if (app.i18n) imports.push('import { Select } from "@inglorious/ui/select"')
|
|
43
|
+
}
|
|
44
|
+
if (app.isTypeScript) imports.push('import type { AppApi } from "../types"')
|
|
45
|
+
if (app.i18n) imports.push('import { t } from "./i18n"')
|
|
46
|
+
|
|
47
|
+
const signature = app.isTypeScript
|
|
48
|
+
? "render(props: Record<string, unknown> | null, api: AppApi)"
|
|
49
|
+
: "render(props, api)"
|
|
50
|
+
const content = app.routing
|
|
51
|
+
? "api.render(router.route)"
|
|
52
|
+
: 'api.render("home")'
|
|
53
|
+
|
|
54
|
+
const shell = app.ui
|
|
55
|
+
? uiShell(app, { pages, content })
|
|
56
|
+
: plainShell(app, { pages, content })
|
|
57
|
+
|
|
58
|
+
return `${importBlock(imports)}
|
|
59
|
+
|
|
60
|
+
export const App = {
|
|
61
|
+
${signature} {
|
|
62
|
+
${renderLocals(app)} return (
|
|
63
|
+
<>
|
|
64
|
+
${shell}
|
|
65
|
+
</>
|
|
66
|
+
)
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// A callback's parameter. The components' props are not typed in .tsx, so
|
|
73
|
+
// TypeScript cannot infer it.
|
|
74
|
+
function typed(app, name, type) {
|
|
75
|
+
return app.isTypeScript ? `${name}: ${type}` : name
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// A page's label: translated, or the English text.
|
|
79
|
+
function labelText(app, page) {
|
|
80
|
+
return app.i18n ? `{t("${page.key}")}` : page.text
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function labelValue(app, page) {
|
|
84
|
+
return app.i18n ? `t("${page.key}")` : `"${page.text}"`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function uiShell(app, { pages, content }) {
|
|
88
|
+
const nav = []
|
|
89
|
+
if (app.links) {
|
|
90
|
+
nav.push(
|
|
91
|
+
...pages.map(
|
|
92
|
+
(page) =>
|
|
93
|
+
`${expectError(app, " ")} <Link href="${page.path}">${labelText(app, page)}</Link>`,
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
if (app.i18n) {
|
|
98
|
+
nav.push(`${expectError(app, " ")} <Select
|
|
99
|
+
value={i18n.locale}
|
|
100
|
+
options={i18n.languages}
|
|
101
|
+
size="sm"
|
|
102
|
+
ariaLabel={t("language")}
|
|
103
|
+
onChange={(${typed(app, "locale", "string")}) =>
|
|
104
|
+
api.notify("#i18n:localeChange", locale)
|
|
105
|
+
}
|
|
106
|
+
/>`)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const appBar = nav.length
|
|
110
|
+
? `${expectError(app, " ")} <AppBar
|
|
111
|
+
title="${app.projectName}"
|
|
112
|
+
trailing={
|
|
113
|
+
<nav className="app-nav">
|
|
114
|
+
${nav.join("\n")}
|
|
115
|
+
</nav>
|
|
116
|
+
}
|
|
117
|
+
/>`
|
|
118
|
+
: `${expectError(app, " ")} <AppBar title="${app.projectName}" />`
|
|
119
|
+
|
|
120
|
+
const header = ` <header${app.mobile ? ' className="pt-safe"' : ""}>
|
|
121
|
+
${appBar}
|
|
122
|
+
</header>
|
|
123
|
+
<main>
|
|
124
|
+
${expectError(app, " ")} <Container maxWidth="md">{${content}}</Container>
|
|
125
|
+
</main>`
|
|
126
|
+
|
|
127
|
+
if (!app.tabs) return header
|
|
128
|
+
|
|
129
|
+
const actions = pages.map((page) => {
|
|
130
|
+
const icon = page.icon
|
|
131
|
+
? `
|
|
132
|
+
${expectError(app, " ", false)} icon: <Icon icon={${page.icon.name}} size="lg" />,`
|
|
133
|
+
: ""
|
|
134
|
+
return ` {
|
|
135
|
+
value: "${page.path}",
|
|
136
|
+
label: ${labelValue(app, page)},${icon}
|
|
137
|
+
},`
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
return `${header}
|
|
141
|
+
<footer className="pb-safe">
|
|
142
|
+
${expectError(app, " ")} <BottomNavigation
|
|
143
|
+
value={router.path}
|
|
144
|
+
actions={[
|
|
145
|
+
${actions.join("\n")}
|
|
146
|
+
]}
|
|
147
|
+
onChange={(${typed(app, "path", "string")}) => api.notify("#router:navigate", path)}
|
|
148
|
+
/>
|
|
149
|
+
</footer>`
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function plainShell(app, { pages, content }) {
|
|
153
|
+
const nav = app.routing
|
|
154
|
+
? pages.map(
|
|
155
|
+
(page) =>
|
|
156
|
+
` <a href="${page.path}">${labelText(app, page)}</a>`,
|
|
157
|
+
)
|
|
158
|
+
: []
|
|
159
|
+
if (app.i18n) {
|
|
160
|
+
nav.push(` <select
|
|
161
|
+
aria-label={t("language")}
|
|
162
|
+
onChange={${selectChange(app, " ")}
|
|
163
|
+
}
|
|
164
|
+
>
|
|
165
|
+
{i18n.languages${app.isTypeScript ? "?." : "."}map((language) => (
|
|
166
|
+
<option
|
|
167
|
+
key={language.value}
|
|
168
|
+
value={language.value}
|
|
169
|
+
selected={language.value === i18n.locale}
|
|
170
|
+
>
|
|
171
|
+
{language.label}
|
|
172
|
+
</option>
|
|
173
|
+
))}
|
|
174
|
+
</select>`)
|
|
175
|
+
}
|
|
176
|
+
const navBlock = nav.length
|
|
177
|
+
? `
|
|
178
|
+
<nav className="app-nav">
|
|
179
|
+
${nav.join("\n")}
|
|
180
|
+
</nav>`
|
|
181
|
+
: ""
|
|
182
|
+
|
|
183
|
+
return ` <header>
|
|
184
|
+
<strong>${app.projectName}</strong>${navBlock}
|
|
185
|
+
</header>
|
|
186
|
+
<main>{${content}}</main>`
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function homeSource(app, { showcase }) {
|
|
190
|
+
const imports = ['import { Message } from "../types/message"']
|
|
191
|
+
for (const icon of showcase) {
|
|
192
|
+
imports.push(`import ${icon.name} from "${icon.from}"`)
|
|
193
|
+
}
|
|
194
|
+
if (showcase.length) {
|
|
195
|
+
imports.push('import { Icon } from "@inglorious/ui/icon"')
|
|
196
|
+
}
|
|
197
|
+
if (app.i18n) imports.push('import { t } from "../i18n"')
|
|
198
|
+
|
|
199
|
+
const text = (key, fallback) => (app.i18n ? `{t("${key}")}` : fallback)
|
|
200
|
+
const icons = showcase.length
|
|
201
|
+
? `
|
|
202
|
+
<p className="app-icons">
|
|
203
|
+
${showcase
|
|
204
|
+
.map(
|
|
205
|
+
(icon) =>
|
|
206
|
+
`${expectError(app, " ")} <Icon icon={${icon.name}} size="lg" label="${icon.label}" />`,
|
|
207
|
+
)
|
|
208
|
+
.join("\n")}
|
|
209
|
+
</p>`
|
|
210
|
+
: ""
|
|
211
|
+
|
|
212
|
+
return `${importBlock(imports)}
|
|
213
|
+
|
|
214
|
+
export const Home = {
|
|
215
|
+
render() {
|
|
216
|
+
return (
|
|
217
|
+
<>
|
|
218
|
+
<h1>${text("home.title", "Welcome")}</h1>
|
|
219
|
+
<p>
|
|
220
|
+
${expectError(app, " ")} <Message entityId="message1" />, <Message entityId="message2" />,{" "}
|
|
221
|
+
${expectError(app, " ")} <Message entityId="message3" />!
|
|
222
|
+
</p>
|
|
223
|
+
<p>${text("home.hint", "Click a greeting to shout it.")}</p>${icons}
|
|
224
|
+
</>
|
|
225
|
+
)
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
`
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function aboutSource(app) {
|
|
232
|
+
const head = app.i18n ? 'import { t } from "../i18n"\n\n' : ""
|
|
233
|
+
const text = (key, fallback) => (app.i18n ? `{t("${key}")}` : fallback)
|
|
234
|
+
|
|
235
|
+
return `${head}export const About = {
|
|
236
|
+
render() {
|
|
237
|
+
return (
|
|
238
|
+
<>
|
|
239
|
+
<h1>${text("about.title", "About")}</h1>
|
|
240
|
+
<p>${text("about.body", "A page of its own, at its own address.")}</p>
|
|
241
|
+
</>
|
|
242
|
+
)
|
|
243
|
+
},
|
|
244
|
+
}
|
|
245
|
+
`
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function messageSource(app) {
|
|
249
|
+
const imports = []
|
|
250
|
+
if (app.isTypeScript) {
|
|
251
|
+
imports.push('import type { MessageType } from "../../types"')
|
|
252
|
+
}
|
|
253
|
+
if (app.i18n) imports.push('import { t } from "../i18n"')
|
|
254
|
+
const greeting = app.i18n ? '{t("message.hello", { who })}' : "Hello {who}"
|
|
255
|
+
const declaration = app.isTypeScript
|
|
256
|
+
? "export const Message: MessageType = {"
|
|
257
|
+
: "export const Message = {"
|
|
258
|
+
|
|
259
|
+
const span = app.i18n
|
|
260
|
+
? ` <span onClick={() => api.notify(\`#\${entity.id}:click\`)}>
|
|
261
|
+
${greeting}
|
|
262
|
+
</span>`
|
|
263
|
+
: ` <span onClick={() => api.notify(\`#\${entity.id}:click\`)}>${greeting}</span>`
|
|
264
|
+
|
|
265
|
+
const head = imports.length ? `${importBlock(imports)}\n\n` : ""
|
|
266
|
+
|
|
267
|
+
return `${head}${declaration}
|
|
268
|
+
click(entity) {
|
|
269
|
+
entity.isUpperCase = !entity.isUpperCase
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
render(entity, api) {
|
|
273
|
+
const who = entity.isUpperCase ? entity.who.toUpperCase() : entity.who
|
|
274
|
+
return (
|
|
275
|
+
${span}
|
|
276
|
+
)
|
|
277
|
+
},
|
|
278
|
+
}
|
|
279
|
+
`
|
|
280
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// The app's components in lit-html: tagged templates in .js or .ts files.
|
|
2
|
+
|
|
3
|
+
import { importBlock } from "./imports.js"
|
|
4
|
+
|
|
5
|
+
export const extension = (app) => (app.isTypeScript ? "ts" : "js")
|
|
6
|
+
|
|
7
|
+
export function appSource(app, { pages, navIcons }) {
|
|
8
|
+
const imports = ['import { html } from "@inglorious/web"']
|
|
9
|
+
if (navIcons) {
|
|
10
|
+
for (const icon of Object.values(navIcons)) {
|
|
11
|
+
imports.push(`import ${icon.name} from "${icon.from}"`)
|
|
12
|
+
}
|
|
13
|
+
imports.push('import { Icon } from "@inglorious/ui/icon"')
|
|
14
|
+
}
|
|
15
|
+
if (app.ui) {
|
|
16
|
+
imports.push(
|
|
17
|
+
'import { AppBar } from "@inglorious/ui/app-bar"',
|
|
18
|
+
'import { Container } from "@inglorious/ui/container"',
|
|
19
|
+
)
|
|
20
|
+
if (app.tabs) {
|
|
21
|
+
imports.push(
|
|
22
|
+
'import { BottomNavigation } from "@inglorious/ui/bottom-navigation"',
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
if (app.links) imports.push('import { Link } from "@inglorious/ui/link"')
|
|
26
|
+
if (app.i18n) imports.push('import { Select } from "@inglorious/ui/select"')
|
|
27
|
+
}
|
|
28
|
+
if (app.isTypeScript) imports.push('import type { AppApi } from "../types"')
|
|
29
|
+
if (app.i18n) imports.push('import { t } from "./i18n"')
|
|
30
|
+
|
|
31
|
+
const label = (key, text) => (app.i18n ? `t("${key}")` : `"${text}"`)
|
|
32
|
+
const signature = app.isTypeScript
|
|
33
|
+
? "render(props: Record<string, unknown> | null, api: AppApi)"
|
|
34
|
+
: "render(props, api)"
|
|
35
|
+
|
|
36
|
+
const content = app.routing
|
|
37
|
+
? "api.render(router.route)"
|
|
38
|
+
: 'api.render("home")'
|
|
39
|
+
|
|
40
|
+
const template = app.ui
|
|
41
|
+
? uiShell(app, { label, pages, content })
|
|
42
|
+
: plainShell(app, { label, pages, content })
|
|
43
|
+
|
|
44
|
+
return `${importBlock(imports)}
|
|
45
|
+
|
|
46
|
+
export const App = {
|
|
47
|
+
${signature} {
|
|
48
|
+
${renderLocals(app)} return html\`
|
|
49
|
+
${template}
|
|
50
|
+
\`
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// The app's own entities are always there; TypeScript cannot know that.
|
|
57
|
+
export function renderLocals(app) {
|
|
58
|
+
const always = app.isTypeScript ? "!" : ""
|
|
59
|
+
const locals = []
|
|
60
|
+
if (app.routing) {
|
|
61
|
+
locals.push(` const router = api.getEntity("router")${always}`)
|
|
62
|
+
}
|
|
63
|
+
if (app.i18n) locals.push(` const i18n = api.getEntity("i18n")${always}`)
|
|
64
|
+
return locals.length ? `${locals.join("\n")}\n\n` : ""
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function uiShell(app, { label, pages, content }) {
|
|
68
|
+
const nav = []
|
|
69
|
+
if (app.links) {
|
|
70
|
+
nav.push(
|
|
71
|
+
...pages.map(
|
|
72
|
+
(page) =>
|
|
73
|
+
`Link.render({ href: "${page.path}", children: ${label(page.key, page.text)} })`,
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
if (app.i18n) {
|
|
78
|
+
nav.push(`Select.render({
|
|
79
|
+
value: i18n.locale,
|
|
80
|
+
options: i18n.languages,
|
|
81
|
+
size: "sm",
|
|
82
|
+
ariaLabel: t("language"),
|
|
83
|
+
onChange: (locale) => api.notify("#i18n:localeChange", locale),
|
|
84
|
+
})`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const trailing = nav.length
|
|
88
|
+
? `
|
|
89
|
+
trailing: html\`<nav class="app-nav">
|
|
90
|
+
\${[
|
|
91
|
+
${nav.join(",\n ")},
|
|
92
|
+
]}
|
|
93
|
+
</nav>\`,`
|
|
94
|
+
: ""
|
|
95
|
+
|
|
96
|
+
const header = ` <header${app.mobile ? ' class="pt-safe"' : ""}>
|
|
97
|
+
\${AppBar.render({
|
|
98
|
+
title: "${app.projectName}",${trailing}
|
|
99
|
+
})}
|
|
100
|
+
</header>
|
|
101
|
+
<main>
|
|
102
|
+
\${Container.render({ maxWidth: "md", children: ${content} })}
|
|
103
|
+
</main>`
|
|
104
|
+
|
|
105
|
+
if (!app.tabs) return header
|
|
106
|
+
|
|
107
|
+
const actions = pages.map(
|
|
108
|
+
(page) => ` {
|
|
109
|
+
value: "${page.path}",
|
|
110
|
+
label: ${label(page.key, page.text)},${
|
|
111
|
+
page.icon
|
|
112
|
+
? `
|
|
113
|
+
icon: Icon.render({ icon: ${page.icon.name}, size: "lg" }),`
|
|
114
|
+
: ""
|
|
115
|
+
}
|
|
116
|
+
},`,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return `${header}
|
|
120
|
+
<footer class="pb-safe">
|
|
121
|
+
\${BottomNavigation.render({
|
|
122
|
+
value: router.path,
|
|
123
|
+
actions: [
|
|
124
|
+
${actions.join("\n")}
|
|
125
|
+
],
|
|
126
|
+
onChange: (path) => api.notify("#router:navigate", path),
|
|
127
|
+
})}
|
|
128
|
+
</footer>`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// The native language picker's change handler, laid out as Prettier would at
|
|
132
|
+
// `indent`. TypeScript has to be told what the event's target is.
|
|
133
|
+
export function selectChange(app, indent) {
|
|
134
|
+
if (!app.isTypeScript) {
|
|
135
|
+
return `(event) =>
|
|
136
|
+
${indent} api.notify("#i18n:localeChange", event.target.value)`
|
|
137
|
+
}
|
|
138
|
+
return `(event: Event) =>
|
|
139
|
+
${indent} api.notify(
|
|
140
|
+
${indent} "#i18n:localeChange",
|
|
141
|
+
${indent} (event.target as HTMLSelectElement).value,
|
|
142
|
+
${indent} )`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function plainShell(app, { label, pages, content }) {
|
|
146
|
+
const nav = app.routing
|
|
147
|
+
? pages.map(
|
|
148
|
+
(page) =>
|
|
149
|
+
` <a href="${page.path}">\${${label(page.key, page.text)}}</a>`,
|
|
150
|
+
)
|
|
151
|
+
: []
|
|
152
|
+
if (app.i18n) {
|
|
153
|
+
nav.push(` <select
|
|
154
|
+
aria-label=\${t("language")}
|
|
155
|
+
@change=\${${selectChange(app, " ")}}
|
|
156
|
+
>
|
|
157
|
+
\${i18n.languages${app.isTypeScript ? "?." : "."}map(
|
|
158
|
+
(language) =>
|
|
159
|
+
html\`<option
|
|
160
|
+
value=\${language.value}
|
|
161
|
+
?selected=\${language.value === i18n.locale}
|
|
162
|
+
>
|
|
163
|
+
\${language.label}
|
|
164
|
+
</option>\`,
|
|
165
|
+
)}
|
|
166
|
+
</select>`)
|
|
167
|
+
}
|
|
168
|
+
const navBlock = nav.length
|
|
169
|
+
? `
|
|
170
|
+
<nav class="app-nav">
|
|
171
|
+
${nav.join("\n")}
|
|
172
|
+
</nav>`
|
|
173
|
+
: ""
|
|
174
|
+
|
|
175
|
+
return ` <header>
|
|
176
|
+
<strong>${app.projectName}</strong>${navBlock}
|
|
177
|
+
</header>
|
|
178
|
+
<main>\${${content}}</main>`
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function homeSource(app, { showcase }) {
|
|
182
|
+
const imports = ['import { html } from "@inglorious/web"']
|
|
183
|
+
for (const icon of showcase) {
|
|
184
|
+
imports.push(`import ${icon.name} from "${icon.from}"`)
|
|
185
|
+
}
|
|
186
|
+
if (showcase.length) {
|
|
187
|
+
imports.push('import { Icon } from "@inglorious/ui/icon"')
|
|
188
|
+
}
|
|
189
|
+
if (app.isTypeScript) {
|
|
190
|
+
imports.push('import type { AppApi, HomeEntity } from "../../types"')
|
|
191
|
+
}
|
|
192
|
+
if (app.i18n) imports.push('import { t } from "../i18n"')
|
|
193
|
+
|
|
194
|
+
const text = (key, fallback) => (app.i18n ? `\${t("${key}")}` : fallback)
|
|
195
|
+
const icons = showcase.length
|
|
196
|
+
? `
|
|
197
|
+
<p class="app-icons">
|
|
198
|
+
${showcase
|
|
199
|
+
.map(
|
|
200
|
+
(icon) => ` \${Icon.render({
|
|
201
|
+
icon: ${icon.name},
|
|
202
|
+
size: "lg",
|
|
203
|
+
label: "${icon.label}",
|
|
204
|
+
})}`,
|
|
205
|
+
)
|
|
206
|
+
.join("\n")}
|
|
207
|
+
</p>`
|
|
208
|
+
: ""
|
|
209
|
+
|
|
210
|
+
const signature = app.isTypeScript
|
|
211
|
+
? "render(entity: HomeEntity, api: AppApi)"
|
|
212
|
+
: "render(entity, api)"
|
|
213
|
+
|
|
214
|
+
return `${importBlock(imports)}
|
|
215
|
+
|
|
216
|
+
export const Home = {
|
|
217
|
+
${signature} {
|
|
218
|
+
return html\`
|
|
219
|
+
<h1>${text("home.title", "Welcome")}</h1>
|
|
220
|
+
<p>
|
|
221
|
+
\${api.render("message1")}, \${api.render("message2")},
|
|
222
|
+
\${api.render("message3")}!
|
|
223
|
+
</p>
|
|
224
|
+
<p>${text("home.hint", "Click a greeting to shout it.")}</p>${icons}
|
|
225
|
+
\`
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
`
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function aboutSource(app) {
|
|
232
|
+
const imports = ['import { html } from "@inglorious/web"']
|
|
233
|
+
if (app.i18n) imports.push('import { t } from "../i18n"')
|
|
234
|
+
const text = (key, fallback) => (app.i18n ? `\${t("${key}")}` : fallback)
|
|
235
|
+
|
|
236
|
+
return `${importBlock(imports)}
|
|
237
|
+
|
|
238
|
+
export const About = {
|
|
239
|
+
render() {
|
|
240
|
+
return html\`
|
|
241
|
+
<h1>${text("about.title", "About")}</h1>
|
|
242
|
+
<p>${text("about.body", "A page of its own, at its own address.")}</p>
|
|
243
|
+
\`
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
`
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function messageSource(app) {
|
|
250
|
+
const imports = ['import { html } from "@inglorious/web"']
|
|
251
|
+
if (app.isTypeScript) {
|
|
252
|
+
imports.push('import type { MessageType } from "../../types"')
|
|
253
|
+
}
|
|
254
|
+
if (app.i18n) imports.push('import { t } from "../i18n"')
|
|
255
|
+
const greeting = app.i18n ? '${t("message.hello", { who })}' : "Hello ${who}"
|
|
256
|
+
const declaration = app.isTypeScript
|
|
257
|
+
? "export const Message: MessageType = {"
|
|
258
|
+
: "export const Message = {"
|
|
259
|
+
|
|
260
|
+
return `${importBlock(imports)}
|
|
261
|
+
|
|
262
|
+
${declaration}
|
|
263
|
+
click(entity) {
|
|
264
|
+
entity.isUpperCase = !entity.isUpperCase
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
render(entity, api) {
|
|
268
|
+
const who = entity.isUpperCase ? entity.who.toUpperCase() : entity.who
|
|
269
|
+
return html\`<span @click=\${() => api.notify(\`#\${entity.id}:click\`)}
|
|
270
|
+
>${greeting}</span
|
|
271
|
+
>\`
|
|
272
|
+
},
|
|
273
|
+
}
|
|
274
|
+
`
|
|
275
|
+
}
|