create-foldkit-app 0.32.0 → 0.32.1
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/dist/commands/create.js +9 -7
- package/dist/templates/base/FOLDKIT.md +22 -6
- package/dist/templates/examples/api-cache/src/data.ts +14 -14
- package/dist/templates/examples/api-cache/src/main.ts +42 -39
- package/dist/templates/examples/auth/src/command.ts +3 -3
- package/dist/templates/examples/auth/src/domain/session.ts +8 -6
- package/dist/templates/examples/auth/src/main.ts +11 -11
- package/dist/templates/examples/auth/src/message.ts +3 -3
- package/dist/templates/examples/auth/src/model.ts +2 -2
- package/dist/templates/examples/auth/src/page/loggedIn/index.ts +1 -1
- package/dist/templates/examples/auth/src/page/loggedIn/view.ts +3 -3
- package/dist/templates/examples/auth/src/page/loggedOut/index.ts +1 -1
- package/dist/templates/examples/auth/src/page/loggedOut/page/login.ts +9 -17
- package/dist/templates/examples/auth/src/page/loggedOut/update.ts +4 -6
- package/dist/templates/examples/auth/src/page/loggedOut/view.ts +3 -3
- package/dist/templates/examples/auth/src/route.ts +2 -2
- package/dist/templates/examples/auth/src/update.ts +37 -40
- package/dist/templates/examples/auth/src/view.ts +6 -6
- package/dist/templates/examples/canvas-art/src/main.ts +22 -30
- package/dist/templates/examples/charting/src/command.ts +3 -3
- package/dist/templates/examples/charting/src/domain.ts +53 -48
- package/dist/templates/examples/charting/src/echarts.ts +6 -6
- package/dist/templates/examples/charting/src/githubApi.ts +33 -34
- package/dist/templates/examples/charting/src/http.ts +7 -7
- package/dist/templates/examples/charting/src/message.ts +6 -6
- package/dist/templates/examples/charting/src/model.ts +6 -6
- package/dist/templates/examples/charting/src/npmApi.ts +19 -15
- package/dist/templates/examples/charting/src/subscription.ts +5 -5
- package/dist/templates/examples/charting/src/telemetry.test.ts +18 -12
- package/dist/templates/examples/charting/src/telemetry.ts +11 -18
- package/dist/templates/examples/charting/src/update.ts +17 -23
- package/dist/templates/examples/charting/src/view/chart.ts +2 -2
- package/dist/templates/examples/counter/src/main.ts +2 -2
- package/dist/templates/examples/counters/src/counter.ts +2 -2
- package/dist/templates/examples/counters/src/main.ts +8 -8
- package/dist/templates/examples/embedding/src/main.ts +10 -7
- package/dist/templates/examples/form/src/main.ts +18 -14
- package/dist/templates/examples/generative-art/src/command.ts +7 -2
- package/dist/templates/examples/generative-art/src/message.ts +14 -14
- package/dist/templates/examples/generative-art/src/model.ts +21 -21
- package/dist/templates/examples/generative-art/src/update.ts +15 -17
- package/dist/templates/examples/interrupting-commands/src/main.ts +30 -27
- package/dist/templates/examples/job-application/src/domain/proficiencyLevel.ts +2 -2
- package/dist/templates/examples/job-application/src/domain/pronounOption.ts +2 -2
- package/dist/templates/examples/job-application/src/domain/step.ts +11 -11
- package/dist/templates/examples/job-application/src/main.ts +5 -5
- package/dist/templates/examples/job-application/src/message.ts +2 -2
- package/dist/templates/examples/job-application/src/model.ts +5 -5
- package/dist/templates/examples/job-application/src/step/attachments.ts +32 -34
- package/dist/templates/examples/job-application/src/step/coverLetter.ts +4 -4
- package/dist/templates/examples/job-application/src/step/education/education.ts +11 -14
- package/dist/templates/examples/job-application/src/step/education/entry.ts +21 -22
- package/dist/templates/examples/job-application/src/step/personalInfo.ts +44 -54
- package/dist/templates/examples/job-application/src/step/skills/entry.ts +12 -14
- package/dist/templates/examples/job-application/src/step/skills/skills.ts +11 -14
- package/dist/templates/examples/job-application/src/step/workHistory/entry.ts +43 -45
- package/dist/templates/examples/job-application/src/step/workHistory/workHistory.ts +11 -14
- package/dist/templates/examples/job-application/src/update.ts +17 -17
- package/dist/templates/examples/job-application/src/view/attachments.ts +5 -5
- package/dist/templates/examples/job-application/src/view/datePicker.ts +3 -3
- package/dist/templates/examples/job-application/src/view/field.ts +5 -5
- package/dist/templates/examples/job-application/src/view/index.ts +28 -22
- package/dist/templates/examples/kanban/src/command.ts +4 -4
- package/dist/templates/examples/kanban/src/domain/card.ts +6 -6
- package/dist/templates/examples/kanban/src/domain/column.ts +9 -9
- package/dist/templates/examples/kanban/src/main.ts +4 -4
- package/dist/templates/examples/kanban/src/message.ts +6 -6
- package/dist/templates/examples/kanban/src/model.ts +11 -9
- package/dist/templates/examples/kanban/src/update.ts +5 -5
- package/dist/templates/examples/managed-resource-layer/src/main.ts +20 -20
- package/dist/templates/examples/map/src/locations.ts +7 -7
- package/dist/templates/examples/map/src/main.ts +83 -44
- package/dist/templates/examples/map/src/mount.test.ts +127 -0
- package/dist/templates/examples/pixel-art/src/command.ts +12 -5
- package/dist/templates/examples/pixel-art/src/main.ts +4 -4
- package/dist/templates/examples/pixel-art/src/message.ts +7 -7
- package/dist/templates/examples/pixel-art/src/model.ts +34 -29
- package/dist/templates/examples/pixel-art/src/subscription.ts +2 -2
- package/dist/templates/examples/pixel-art/src/update.ts +79 -86
- package/dist/templates/examples/query-sync/src/main.ts +106 -102
- package/dist/templates/examples/route-transitions/src/data.ts +6 -6
- package/dist/templates/examples/route-transitions/src/main.ts +23 -29
- package/dist/templates/examples/route-transitions/src/route.ts +3 -3
- package/dist/templates/examples/routing/src/fileTree.ts +10 -8
- package/dist/templates/examples/routing/src/main.ts +23 -21
- package/dist/templates/examples/routing/src/page/people.ts +14 -22
- package/dist/templates/examples/routing/src/route.ts +7 -7
- package/dist/templates/examples/shopping-cart/src/main.ts +30 -31
- package/dist/templates/examples/shopping-cart/src/page/products.ts +10 -10
- package/dist/templates/examples/shopping-cart/src/route.ts +6 -4
- package/dist/templates/examples/slow-warnings/src/main.ts +35 -43
- package/dist/templates/examples/snake/src/domain/snake.ts +2 -2
- package/dist/templates/examples/snake/src/main.ts +39 -47
- package/dist/templates/examples/ssg/src/main.ts +5 -5
- package/dist/templates/examples/ssg/src/route.ts +2 -2
- package/dist/templates/examples/ssr/src/cookie.ts +3 -3
- package/dist/templates/examples/ssr/src/main.ts +10 -10
- package/dist/templates/examples/state-machine/src/main.ts +55 -53
- package/dist/templates/examples/state-machine/src/view.ts +7 -7
- package/dist/templates/examples/stopwatch/src/main.ts +10 -10
- package/dist/templates/examples/todo/src/main.ts +41 -34
- package/dist/templates/examples/ui-showcase/src/main.ts +54 -42
- package/dist/templates/examples/ui-showcase/src/scene.test.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/init.ts +1 -2
- package/dist/templates/examples/ui-showcase/src/ui/message.ts +17 -18
- package/dist/templates/examples/ui-showcase/src/ui/model.ts +47 -44
- package/dist/templates/examples/ui-showcase/src/ui/subscriptions.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/toast.ts +4 -4
- package/dist/templates/examples/ui-showcase/src/ui/update.ts +287 -298
- package/dist/templates/examples/ui-showcase/src/ui/view/animation.ts +2 -2
- package/dist/templates/examples/ui-showcase/src/ui/view/button.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/calendar.ts +4 -4
- package/dist/templates/examples/ui-showcase/src/ui/view/checkbox.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/combobox.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/datePicker.ts +4 -4
- package/dist/templates/examples/ui-showcase/src/ui/view/dialog.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/disclosure.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/dragAndDrop.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/fieldset.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/fileDrop.ts +6 -6
- package/dist/templates/examples/ui-showcase/src/ui/view/hoverIntent.ts +9 -20
- package/dist/templates/examples/ui-showcase/src/ui/view/input.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/listbox.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/menu.ts +15 -15
- package/dist/templates/examples/ui-showcase/src/ui/view/popover.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/radioGroup.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/select.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/slider.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/switch.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/tabs.ts +7 -7
- package/dist/templates/examples/ui-showcase/src/ui/view/textarea.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/toast.ts +8 -8
- package/dist/templates/examples/ui-showcase/src/ui/view/tooltip.ts +1 -1
- package/dist/templates/examples/ui-showcase/src/ui/view/virtualList.ts +7 -7
- package/dist/templates/examples/view-transitions/src/artwork.ts +7 -7
- package/dist/templates/examples/view-transitions/src/main.ts +6 -6
- package/dist/templates/examples/view-transitions/src/route.ts +3 -3
- package/dist/templates/examples/weather/src/main.ts +42 -42
- package/dist/templates/examples/weather/src/story.test.ts +5 -5
- package/dist/templates/examples/web-components/src/main.ts +16 -16
- package/dist/templates/examples/websocket-chat/src/main.ts +30 -30
- package/dist/templates/release.json +13 -13
- package/dist/templates/rendering/ssg/src/main.ts +5 -5
- package/dist/templates/rendering/ssg/src/route.ts +2 -2
- package/dist/templates/rendering/ssr/server/main.ts +15 -15
- package/dist/templates/rendering/ssr/src/cookie.ts +3 -3
- package/dist/templates/rendering/ssr/src/main.ts +10 -10
- package/dist/utils/packages.js +17 -0
- package/package.json +1 -1
|
@@ -2,10 +2,10 @@ import { clsx } from 'clsx'
|
|
|
2
2
|
import {
|
|
3
3
|
Array,
|
|
4
4
|
Effect,
|
|
5
|
-
Match
|
|
5
|
+
Match,
|
|
6
6
|
Option,
|
|
7
7
|
Order,
|
|
8
|
-
Schema
|
|
8
|
+
Schema,
|
|
9
9
|
SchemaTransformation,
|
|
10
10
|
String,
|
|
11
11
|
Types,
|
|
@@ -25,9 +25,15 @@ import { AnchorConfig } from '@foldkit/ui/listbox'
|
|
|
25
25
|
|
|
26
26
|
import { type Dinosaur, dinosaurs } from './data'
|
|
27
27
|
|
|
28
|
-
const Diet =
|
|
29
|
-
const Period =
|
|
30
|
-
const SortColumn =
|
|
28
|
+
const Diet = Schema.Literals(['Carnivore', 'Herbivore', 'Omnivore'])
|
|
29
|
+
const Period = Schema.Literals(['Triassic', 'Jurassic', 'Cretaceous'])
|
|
30
|
+
const SortColumn = Schema.Literals([
|
|
31
|
+
'Name',
|
|
32
|
+
'Period',
|
|
33
|
+
'Diet',
|
|
34
|
+
'Length',
|
|
35
|
+
'Weight',
|
|
36
|
+
])
|
|
31
37
|
type SortColumn = typeof SortColumn.Type
|
|
32
38
|
|
|
33
39
|
export const Sorting = defineTaggedUnion({
|
|
@@ -44,12 +50,12 @@ const periodFilterItems: ReadonlyArray<string> = ['', ...Period.literals]
|
|
|
44
50
|
|
|
45
51
|
const SORT_PARAM_SEPARATOR = ':'
|
|
46
52
|
|
|
47
|
-
const optionFromValidParam = <A extends string>(schema:
|
|
48
|
-
const decode =
|
|
53
|
+
const optionFromValidParam = <A extends string>(schema: Schema.Codec<A, A>) => {
|
|
54
|
+
const decode = Schema.decodeUnknownOption(schema)
|
|
49
55
|
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
return Schema.OptionFromOptional(Schema.String).pipe(
|
|
57
|
+
Schema.decodeTo(
|
|
58
|
+
Schema.Option(schema),
|
|
53
59
|
SchemaTransformation.transform({
|
|
54
60
|
decode: (maybeRaw: Option.Option<string>): Option.Option<A> =>
|
|
55
61
|
Option.flatMap(maybeRaw, decode),
|
|
@@ -60,14 +66,14 @@ const optionFromValidParam = <A extends string>(schema: S.Codec<A, A>) => {
|
|
|
60
66
|
)
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
const SortDirection =
|
|
69
|
+
const SortDirection = Schema.Literals(['Ascending', 'Descending'])
|
|
64
70
|
|
|
65
71
|
const sortingFromParam = (() => {
|
|
66
|
-
const decodeColumn =
|
|
67
|
-
const decodeDirection =
|
|
72
|
+
const decodeColumn = Schema.decodeUnknownOption(SortColumn)
|
|
73
|
+
const decodeDirection = Schema.decodeUnknownOption(SortDirection)
|
|
68
74
|
|
|
69
|
-
return
|
|
70
|
-
|
|
75
|
+
return Schema.OptionFromOptional(Schema.String).pipe(
|
|
76
|
+
Schema.decodeTo(
|
|
71
77
|
Sorting,
|
|
72
78
|
SchemaTransformation.transform({
|
|
73
79
|
decode: (maybeRaw: Option.Option<string>): Sorting =>
|
|
@@ -90,10 +96,14 @@ const sortingFromParam = (() => {
|
|
|
90
96
|
),
|
|
91
97
|
}),
|
|
92
98
|
Option.map(({ column, direction }) =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
Match.value(direction).pipe(
|
|
100
|
+
Match.when('Ascending', () =>
|
|
101
|
+
Sorting.Ascending({ column }),
|
|
102
|
+
),
|
|
103
|
+
Match.when('Descending', () =>
|
|
104
|
+
Sorting.Descending({ column }),
|
|
105
|
+
),
|
|
106
|
+
Match.exhaustive,
|
|
97
107
|
),
|
|
98
108
|
),
|
|
99
109
|
Option.getOrElse(() => Sorting.Unsorted()),
|
|
@@ -115,12 +125,12 @@ const sortingFromParam = (() => {
|
|
|
115
125
|
|
|
116
126
|
export const AppRoute = defineRouteUnion({
|
|
117
127
|
Browse: {
|
|
118
|
-
search:
|
|
128
|
+
search: Schema.Option(Schema.String),
|
|
119
129
|
sorting: Sorting,
|
|
120
|
-
diet:
|
|
121
|
-
period:
|
|
130
|
+
diet: Schema.Option(Diet),
|
|
131
|
+
period: Schema.Option(Period),
|
|
122
132
|
},
|
|
123
|
-
NotFound: { path:
|
|
133
|
+
NotFound: { path: Schema.String },
|
|
124
134
|
})
|
|
125
135
|
|
|
126
136
|
export type AppRoute = typeof AppRoute.Type
|
|
@@ -128,8 +138,8 @@ export type AppRoute = typeof AppRoute.Type
|
|
|
128
138
|
export const browseRouter = pipe(
|
|
129
139
|
Route.root,
|
|
130
140
|
Route.query(
|
|
131
|
-
|
|
132
|
-
search:
|
|
141
|
+
Schema.Struct({
|
|
142
|
+
search: Schema.OptionFromOptional(Schema.String),
|
|
133
143
|
sorting: sortingFromParam,
|
|
134
144
|
diet: optionFromValidParam(Diet),
|
|
135
145
|
period: optionFromValidParam(Period),
|
|
@@ -143,7 +153,7 @@ const urlToAppRoute = Route.parseUrlWithFallback(routeParser, AppRoute.NotFound)
|
|
|
143
153
|
|
|
144
154
|
// MODEL
|
|
145
155
|
|
|
146
|
-
export const Model =
|
|
156
|
+
export const Model = Schema.Struct({
|
|
147
157
|
route: AppRoute,
|
|
148
158
|
dietListbox: Listbox.Model,
|
|
149
159
|
periodListbox: Listbox.Model,
|
|
@@ -158,7 +168,7 @@ export const Message = defineMessageUnion({
|
|
|
158
168
|
CompletedReplaceFilters: {},
|
|
159
169
|
ClickedLink: { request: UrlRequest },
|
|
160
170
|
ChangedUrl: { url: Url },
|
|
161
|
-
ChangedSearchInput: { value:
|
|
171
|
+
ChangedSearchInput: { value: Schema.String },
|
|
162
172
|
ClickedColumnHeader: { column: SortColumn },
|
|
163
173
|
GotDietListboxMessage: { message: Listbox.Message },
|
|
164
174
|
GotPeriodListboxMessage: { message: Listbox.Message },
|
|
@@ -178,9 +188,9 @@ const emptyBrowseFields: BrowseFields = {
|
|
|
178
188
|
}
|
|
179
189
|
|
|
180
190
|
const routeToBrowseFields = (route: AppRoute): BrowseFields =>
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
191
|
+
Match.value(route).pipe(
|
|
192
|
+
Match.tag('Browse', route => route),
|
|
193
|
+
Match.orElse(() => emptyBrowseFields),
|
|
184
194
|
)
|
|
185
195
|
|
|
186
196
|
export const init: Runtime.RoutingApplicationInit<Model, Message> = (
|
|
@@ -216,18 +226,18 @@ const columnSortDirection = (
|
|
|
216
226
|
const nextSorting = (sorting: Sorting, column: SortColumn): Sorting =>
|
|
217
227
|
pipe(
|
|
218
228
|
columnSortDirection(sorting, column),
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
229
|
+
Match.value,
|
|
230
|
+
Match.when('Unsorted', () => Sorting.Ascending({ column })),
|
|
231
|
+
Match.when('Ascending', () => Sorting.Descending({ column })),
|
|
232
|
+
Match.when('Descending', () => Sorting.Unsorted()),
|
|
233
|
+
Match.exhaustive,
|
|
224
234
|
)
|
|
225
235
|
|
|
226
236
|
const selectionToParam = <A extends string>(
|
|
227
237
|
maybeSelectedItem: Option.Option<string>,
|
|
228
|
-
schema:
|
|
238
|
+
schema: Schema.Codec<A, A>,
|
|
229
239
|
): Option.Option<A> => {
|
|
230
|
-
const decode =
|
|
240
|
+
const decode = Schema.decodeUnknownOption(schema)
|
|
231
241
|
|
|
232
242
|
return pipe(
|
|
233
243
|
maybeSelectedItem,
|
|
@@ -238,10 +248,10 @@ const selectionToParam = <A extends string>(
|
|
|
238
248
|
|
|
239
249
|
export const ReplaceFilters = Command.define('ReplaceFilters', {
|
|
240
250
|
args: {
|
|
241
|
-
search:
|
|
251
|
+
search: Schema.Option(Schema.String),
|
|
242
252
|
sorting: Sorting,
|
|
243
|
-
diet:
|
|
244
|
-
period:
|
|
253
|
+
diet: Schema.Option(Diet),
|
|
254
|
+
period: Schema.Option(Period),
|
|
245
255
|
},
|
|
246
256
|
messages: [Message.CompletedReplaceFilters],
|
|
247
257
|
execute: fields =>
|
|
@@ -251,14 +261,14 @@ export const ReplaceFilters = Command.define('ReplaceFilters', {
|
|
|
251
261
|
})
|
|
252
262
|
|
|
253
263
|
const NavigateInternal = Command.define('NavigateInternal', {
|
|
254
|
-
args: { url:
|
|
264
|
+
args: { url: Schema.String },
|
|
255
265
|
messages: [Message.CompletedNavigateInternal],
|
|
256
266
|
execute: ({ url }) =>
|
|
257
267
|
pushUrl(url).pipe(Effect.as(Message.CompletedNavigateInternal())),
|
|
258
268
|
})
|
|
259
269
|
|
|
260
270
|
const LoadExternal = Command.define('LoadExternal', {
|
|
261
|
-
args: { href:
|
|
271
|
+
args: { href: Schema.String },
|
|
262
272
|
messages: [Message.CompletedLoadExternal],
|
|
263
273
|
execute: ({ href }) =>
|
|
264
274
|
load(href).pipe(Effect.as(Message.CompletedLoadExternal())),
|
|
@@ -269,27 +279,24 @@ type UpdateReturn = Update.Return<Model, Message>
|
|
|
269
279
|
const DietListbox = Listbox.create<string>()
|
|
270
280
|
const PeriodListbox = Listbox.create<string>()
|
|
271
281
|
|
|
272
|
-
const foldDietListboxOutMessage
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
},
|
|
291
|
-
}),
|
|
292
|
-
)
|
|
282
|
+
const foldDietListboxOutMessage = Listbox.OutMessage.match<
|
|
283
|
+
Update.Step<Model, Message>
|
|
284
|
+
>({
|
|
285
|
+
Selected:
|
|
286
|
+
({ value }) =>
|
|
287
|
+
model => {
|
|
288
|
+
const fields = routeToBrowseFields(model.route)
|
|
289
|
+
return {
|
|
290
|
+
model,
|
|
291
|
+
commands: [
|
|
292
|
+
ReplaceFilters({
|
|
293
|
+
...fields,
|
|
294
|
+
diet: selectionToParam(Option.some(value), Diet),
|
|
295
|
+
}),
|
|
296
|
+
],
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
})
|
|
293
300
|
|
|
294
301
|
const foldDietListbox = Update.foldChild({
|
|
295
302
|
update: DietListbox.update,
|
|
@@ -300,27 +307,24 @@ const foldDietListbox = Update.foldChild({
|
|
|
300
307
|
foldOutMessage: foldDietListboxOutMessage,
|
|
301
308
|
})
|
|
302
309
|
|
|
303
|
-
const foldPeriodListboxOutMessage
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
},
|
|
322
|
-
}),
|
|
323
|
-
)
|
|
310
|
+
const foldPeriodListboxOutMessage = Listbox.OutMessage.match<
|
|
311
|
+
Update.Step<Model, Message>
|
|
312
|
+
>({
|
|
313
|
+
Selected:
|
|
314
|
+
({ value }) =>
|
|
315
|
+
model => {
|
|
316
|
+
const fields = routeToBrowseFields(model.route)
|
|
317
|
+
return {
|
|
318
|
+
model,
|
|
319
|
+
commands: [
|
|
320
|
+
ReplaceFilters({
|
|
321
|
+
...fields,
|
|
322
|
+
period: selectionToParam(Option.some(value), Period),
|
|
323
|
+
}),
|
|
324
|
+
],
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
})
|
|
324
328
|
|
|
325
329
|
const foldPeriodListbox = Update.foldChild({
|
|
326
330
|
update: PeriodListbox.update,
|
|
@@ -439,11 +443,11 @@ const filterAndSort = (fields: BrowseFields): ReadonlyArray<Dinosaur> =>
|
|
|
439
443
|
)
|
|
440
444
|
|
|
441
445
|
const sortIndicator = (column: SortColumn, sorting: Sorting): string =>
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
446
|
+
Match.value(columnSortDirection(sorting, column)).pipe(
|
|
447
|
+
Match.when('Unsorted', () => ''),
|
|
448
|
+
Match.when('Ascending', () => '↑'),
|
|
449
|
+
Match.when('Descending', () => '↓'),
|
|
450
|
+
Match.exhaustive,
|
|
447
451
|
)
|
|
448
452
|
|
|
449
453
|
const BADGE_BASE = 'px-2 py-0.5 rounded-full text-xs font-medium'
|
|
@@ -503,19 +507,19 @@ const dinosaurRowView = (dinosaur: Dinosaur, h: HtmlBuilder<Message>): Html =>
|
|
|
503
507
|
)
|
|
504
508
|
|
|
505
509
|
const sortAriaLabel = (column: SortColumn, sorting: Sorting): string =>
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
510
|
+
Match.value(columnSortDirection(sorting, column)).pipe(
|
|
511
|
+
Match.when('Unsorted', () => `Sort by ${column}`),
|
|
512
|
+
Match.when('Ascending', () => `Sort by ${column}, currently ascending`),
|
|
513
|
+
Match.when('Descending', () => `Sort by ${column}, currently descending`),
|
|
514
|
+
Match.exhaustive,
|
|
511
515
|
)
|
|
512
516
|
|
|
513
517
|
const ariaSortValue = (column: SortColumn, sorting: Sorting): string =>
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
518
|
+
Match.value(columnSortDirection(sorting, column)).pipe(
|
|
519
|
+
Match.when('Unsorted', () => 'none'),
|
|
520
|
+
Match.when('Ascending', () => 'ascending'),
|
|
521
|
+
Match.when('Descending', () => 'descending'),
|
|
522
|
+
Match.exhaustive,
|
|
519
523
|
)
|
|
520
524
|
|
|
521
525
|
const sortableColumnHeader = (
|
|
@@ -852,9 +856,9 @@ const notFoundView = (path: string, h: HtmlBuilder<Message>): Html =>
|
|
|
852
856
|
)
|
|
853
857
|
|
|
854
858
|
const routeTitle = (route: Model['route']): string =>
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
859
|
+
Match.value(route).pipe(
|
|
860
|
+
Match.tag('Browse', () => 'Dinosaur Explorer'),
|
|
861
|
+
Match.orElse(() => 'Not Found | Dinosaur Explorer'),
|
|
858
862
|
)
|
|
859
863
|
|
|
860
864
|
export const view = (model: Model, h: HtmlBuilder<Message>): Document => {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Array, Option, Schema
|
|
1
|
+
import { Array, Option, Schema, pipe } from 'effect'
|
|
2
2
|
|
|
3
|
-
export const Painting =
|
|
4
|
-
id:
|
|
5
|
-
title:
|
|
6
|
-
artist:
|
|
7
|
-
gradient:
|
|
3
|
+
export const Painting = Schema.Struct({
|
|
4
|
+
id: Schema.Number,
|
|
5
|
+
title: Schema.String,
|
|
6
|
+
artist: Schema.String,
|
|
7
|
+
gradient: Schema.String,
|
|
8
8
|
})
|
|
9
9
|
export type Painting = typeof Painting.Type
|
|
10
10
|
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Array,
|
|
3
|
-
Duration,
|
|
4
|
-
Effect,
|
|
5
|
-
Match as M,
|
|
6
|
-
Option,
|
|
7
|
-
Schema as S,
|
|
8
|
-
pipe,
|
|
9
|
-
} from 'effect'
|
|
1
|
+
import { Array, Duration, Effect, Match, Option, Schema, pipe } from 'effect'
|
|
10
2
|
import { Command, Runtime, Update } from 'foldkit'
|
|
11
3
|
import { Document, Html, HtmlBuilder } from 'foldkit/html'
|
|
12
4
|
import { defineMessageUnion } from 'foldkit/message'
|
|
@@ -35,30 +27,30 @@ const MAX_LOGGED_TRANSITIONS = 20
|
|
|
35
27
|
|
|
36
28
|
// MODEL
|
|
37
29
|
|
|
38
|
-
export const CatalogStatus =
|
|
30
|
+
export const CatalogStatus = Schema.Literals(['Idle', 'Loading', 'Ready'])
|
|
39
31
|
export type CatalogStatus = typeof CatalogStatus.Type
|
|
40
32
|
|
|
41
33
|
export const PaintingStatus = defineTaggedUnion({
|
|
42
34
|
Idle: {},
|
|
43
|
-
Loading: { paintingId:
|
|
44
|
-
Ready: { paintingId:
|
|
35
|
+
Loading: { paintingId: Schema.Number },
|
|
36
|
+
Ready: { paintingId: Schema.Number },
|
|
45
37
|
})
|
|
46
38
|
export type PaintingStatus = typeof PaintingStatus.Type
|
|
47
39
|
|
|
48
|
-
export const LoggedTransition =
|
|
49
|
-
sequenceNumber:
|
|
50
|
-
maybePreviousRoute:
|
|
40
|
+
export const LoggedTransition = Schema.Struct({
|
|
41
|
+
sequenceNumber: Schema.Number,
|
|
42
|
+
maybePreviousRoute: Schema.Option(AppRoute),
|
|
51
43
|
nextRoute: AppRoute,
|
|
52
44
|
})
|
|
53
45
|
export type LoggedTransition = typeof LoggedTransition.Type
|
|
54
46
|
|
|
55
|
-
export const Model =
|
|
47
|
+
export const Model = Schema.Struct({
|
|
56
48
|
route: AppRoute,
|
|
57
|
-
transitionLog:
|
|
49
|
+
transitionLog: Schema.Array(LoggedTransition),
|
|
58
50
|
catalogStatus: CatalogStatus,
|
|
59
51
|
paintingStatus: PaintingStatus,
|
|
60
|
-
studioDraft:
|
|
61
|
-
maybeSavedDraft:
|
|
52
|
+
studioDraft: Schema.String,
|
|
53
|
+
maybeSavedDraft: Schema.Option(Schema.String),
|
|
62
54
|
})
|
|
63
55
|
export type Model = typeof Model.Type
|
|
64
56
|
|
|
@@ -70,9 +62,9 @@ export const Message = defineMessageUnion({
|
|
|
70
62
|
ClickedLink: { request: UrlRequest },
|
|
71
63
|
ChangedUrl: { url: Url },
|
|
72
64
|
SucceededLoadCatalog: {},
|
|
73
|
-
SucceededLoadPainting: { paintingId:
|
|
74
|
-
UpdatedStudioDraft: { value:
|
|
75
|
-
SucceededSaveDraft: { draft:
|
|
65
|
+
SucceededLoadPainting: { paintingId: Schema.Number },
|
|
66
|
+
UpdatedStudioDraft: { value: Schema.String },
|
|
67
|
+
SucceededSaveDraft: { draft: Schema.String },
|
|
76
68
|
})
|
|
77
69
|
|
|
78
70
|
export type Message = typeof Message.Type
|
|
@@ -80,14 +72,14 @@ export type Message = typeof Message.Type
|
|
|
80
72
|
// COMMAND
|
|
81
73
|
|
|
82
74
|
const NavigateInternal = Command.define('NavigateInternal', {
|
|
83
|
-
args: { url:
|
|
75
|
+
args: { url: Schema.String },
|
|
84
76
|
messages: [Message.CompletedNavigateInternal],
|
|
85
77
|
execute: ({ url }) =>
|
|
86
78
|
pushUrl(url).pipe(Effect.as(Message.CompletedNavigateInternal())),
|
|
87
79
|
})
|
|
88
80
|
|
|
89
81
|
const LoadExternal = Command.define('LoadExternal', {
|
|
90
|
-
args: { href:
|
|
82
|
+
args: { href: Schema.String },
|
|
91
83
|
messages: [Message.CompletedLoadExternal],
|
|
92
84
|
execute: ({ href }) =>
|
|
93
85
|
load(href).pipe(Effect.as(Message.CompletedLoadExternal())),
|
|
@@ -101,7 +93,7 @@ export const LoadCatalog = Command.define('LoadCatalog', {
|
|
|
101
93
|
})
|
|
102
94
|
|
|
103
95
|
export const LoadPainting = Command.define('LoadPainting', {
|
|
104
|
-
args: { paintingId:
|
|
96
|
+
args: { paintingId: Schema.Number },
|
|
105
97
|
messages: [Message.SucceededLoadPainting],
|
|
106
98
|
execute: ({ paintingId }) =>
|
|
107
99
|
Effect.sleep(PAINTING_LATENCY).pipe(
|
|
@@ -110,7 +102,7 @@ export const LoadPainting = Command.define('LoadPainting', {
|
|
|
110
102
|
})
|
|
111
103
|
|
|
112
104
|
export const SaveDraft = Command.define('SaveDraft', {
|
|
113
|
-
args: { draft:
|
|
105
|
+
args: { draft: Schema.String },
|
|
114
106
|
messages: [Message.SucceededSaveDraft],
|
|
115
107
|
execute: ({ draft }) =>
|
|
116
108
|
Effect.sleep(SAVE_LATENCY).pipe(
|
|
@@ -748,9 +740,11 @@ const transitionLogView = (
|
|
|
748
740
|
)
|
|
749
741
|
|
|
750
742
|
const routeTitle = (route: AppRoute): string =>
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
743
|
+
Match.value(route).pipe(
|
|
744
|
+
Match.tag('Home', () => 'Route Transitions'),
|
|
745
|
+
Match.orElse(
|
|
746
|
+
currentRoute => `${routeLabel(currentRoute)} | Route Transitions`,
|
|
747
|
+
),
|
|
754
748
|
)
|
|
755
749
|
|
|
756
750
|
export const view = (model: Model, h: HtmlBuilder<Message>): Document => {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { Schema
|
|
1
|
+
import { Schema, pipe } from 'effect'
|
|
2
2
|
import { Route } from 'foldkit'
|
|
3
3
|
import { defineRouteUnion, int, literal, slash } from 'foldkit/route'
|
|
4
4
|
|
|
5
5
|
export const AppRoute = defineRouteUnion({
|
|
6
6
|
Home: {},
|
|
7
7
|
Gallery: {},
|
|
8
|
-
Painting: { paintingId:
|
|
8
|
+
Painting: { paintingId: Schema.Number },
|
|
9
9
|
Studio: {},
|
|
10
|
-
NotFound: { path:
|
|
10
|
+
NotFound: { path: Schema.String },
|
|
11
11
|
})
|
|
12
12
|
|
|
13
13
|
export type AppRoute = typeof AppRoute.Type
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Array, Match
|
|
1
|
+
import { Array, Match, Option, Schema, pipe } from 'effect'
|
|
2
2
|
import { taggedStruct } from 'foldkit/schema'
|
|
3
3
|
|
|
4
4
|
export type File = typeof File.Type
|
|
@@ -10,16 +10,18 @@ export type Directory = Readonly<{
|
|
|
10
10
|
export type FileTreeEntry = File | Directory
|
|
11
11
|
|
|
12
12
|
export const File = taggedStruct('File', {
|
|
13
|
-
name:
|
|
14
|
-
sizeInBytes:
|
|
13
|
+
name: Schema.String,
|
|
14
|
+
sizeInBytes: Schema.Number,
|
|
15
15
|
})
|
|
16
16
|
|
|
17
17
|
export const Directory = taggedStruct('Directory', {
|
|
18
|
-
name:
|
|
19
|
-
entries:
|
|
18
|
+
name: Schema.String,
|
|
19
|
+
entries: Schema.Array(
|
|
20
|
+
Schema.suspend((): Schema.Codec<FileTreeEntry> => FileTreeEntry),
|
|
21
|
+
),
|
|
20
22
|
})
|
|
21
23
|
|
|
22
|
-
export const FileTreeEntry =
|
|
24
|
+
export const FileTreeEntry = Schema.Union([File, Directory])
|
|
23
25
|
|
|
24
26
|
export const fileTree: ReadonlyArray<FileTreeEntry> = [
|
|
25
27
|
Directory({
|
|
@@ -57,8 +59,8 @@ const findDirectoryEntries = (
|
|
|
57
59
|
pipe(
|
|
58
60
|
Array.findFirst(entries, entry => entry.name === name),
|
|
59
61
|
Option.flatMap(entry =>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
Match.value(entry).pipe(
|
|
63
|
+
Match.tagsExhaustive({
|
|
62
64
|
File: () => Option.none(),
|
|
63
65
|
Directory: directory => Option.some(directory.entries),
|
|
64
66
|
}),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Array, Effect, Match
|
|
1
|
+
import { Array, Effect, Match, Option, Schema } from 'effect'
|
|
2
2
|
import { Command, Runtime, Update } from 'foldkit'
|
|
3
3
|
import { Document, Html, HtmlBuilder } from 'foldkit/html'
|
|
4
4
|
import { defineMessageUnion } from 'foldkit/message'
|
|
@@ -28,7 +28,7 @@ export { AppRoute } from './route'
|
|
|
28
28
|
|
|
29
29
|
// MODEL
|
|
30
30
|
|
|
31
|
-
export const Model =
|
|
31
|
+
export const Model = Schema.Struct({
|
|
32
32
|
route: AppRoute,
|
|
33
33
|
peoplePage: People.Model,
|
|
34
34
|
})
|
|
@@ -54,9 +54,9 @@ export const init: Runtime.RoutingApplicationInit<Model, Message> = (
|
|
|
54
54
|
) => {
|
|
55
55
|
const route = urlToAppRoute(url)
|
|
56
56
|
|
|
57
|
-
const initialPeopleRoute =
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
const initialPeopleRoute = Match.value(route).pipe(
|
|
58
|
+
Match.tag('People', peopleRoute => peopleRoute),
|
|
59
|
+
Match.orElse(() => AppRoute.People({ searchText: Option.none() })),
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
const peopleInit = People.init(initialPeopleRoute)
|
|
@@ -71,14 +71,14 @@ export const init: Runtime.RoutingApplicationInit<Model, Message> = (
|
|
|
71
71
|
// COMMAND
|
|
72
72
|
|
|
73
73
|
const NavigateInternal = Command.define('NavigateInternal', {
|
|
74
|
-
args: { url:
|
|
74
|
+
args: { url: Schema.String },
|
|
75
75
|
messages: [Message.CompletedNavigateInternal],
|
|
76
76
|
execute: ({ url }) =>
|
|
77
77
|
pushUrl(url).pipe(Effect.as(Message.CompletedNavigateInternal())),
|
|
78
78
|
})
|
|
79
79
|
|
|
80
80
|
const LoadExternal = Command.define('LoadExternal', {
|
|
81
|
-
args: { href:
|
|
81
|
+
args: { href: Schema.String },
|
|
82
82
|
messages: [Message.CompletedLoadExternal],
|
|
83
83
|
execute: ({ href }) =>
|
|
84
84
|
load(href).pipe(Effect.as(Message.CompletedLoadExternal())),
|
|
@@ -127,10 +127,12 @@ export const update = (model: Model, message: Message) =>
|
|
|
127
127
|
ChangedUrl: ({ url }) => {
|
|
128
128
|
const nextRoute = urlToAppRoute(url)
|
|
129
129
|
|
|
130
|
-
const routeSteps =
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
const routeSteps = Match.value(nextRoute).pipe(
|
|
131
|
+
Match.withReturnType<ReadonlyArray<Update.Step<Model, Message>>>(),
|
|
132
|
+
Match.tag('People', peopleRoute => [
|
|
133
|
+
foldPeopleRouteChanged(peopleRoute),
|
|
134
|
+
]),
|
|
135
|
+
Match.orElse(() => []),
|
|
134
136
|
)
|
|
135
137
|
|
|
136
138
|
return Update.combine(model, [setRoute(nextRoute), ...routeSteps])
|
|
@@ -372,8 +374,8 @@ const entryListView = (
|
|
|
372
374
|
],
|
|
373
375
|
[entry.name],
|
|
374
376
|
),
|
|
375
|
-
|
|
376
|
-
|
|
377
|
+
Match.value(entry).pipe(
|
|
378
|
+
Match.tagsExhaustive({
|
|
377
379
|
File: file =>
|
|
378
380
|
h.span(
|
|
379
381
|
[h.Class('text-sm text-gray-500')],
|
|
@@ -484,8 +486,8 @@ const filesView = (
|
|
|
484
486
|
const content = Option.match(maybeEntry, {
|
|
485
487
|
onNone: () => missingEntryView(path, h),
|
|
486
488
|
onSome: entry =>
|
|
487
|
-
|
|
488
|
-
|
|
489
|
+
Match.value(entry).pipe(
|
|
490
|
+
Match.tagsExhaustive({
|
|
489
491
|
File: file => fileDetailView(file, h),
|
|
490
492
|
Directory: directory => entryListView(path, directory.entries, h),
|
|
491
493
|
}),
|
|
@@ -518,15 +520,15 @@ const notFoundView = (path: string, h: HtmlBuilder<Message>): Html =>
|
|
|
518
520
|
)
|
|
519
521
|
|
|
520
522
|
const routeTitle = (route: Model['route']): string =>
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
523
|
+
Match.value(route).pipe(
|
|
524
|
+
Match.tag('Home', () => 'Routing'),
|
|
525
|
+
Match.tag('Person', ({ personId }) => `Person ${personId} | Routing`),
|
|
526
|
+
Match.tag('FilesIndex', () => 'Files | Routing'),
|
|
527
|
+
Match.tag(
|
|
526
528
|
'Files',
|
|
527
529
|
({ path }) => `${Array.lastNonEmpty(path)} | Files | Routing`,
|
|
528
530
|
),
|
|
529
|
-
|
|
531
|
+
Match.orElse(({ _tag }) => `${_tag} | Routing`),
|
|
530
532
|
)
|
|
531
533
|
|
|
532
534
|
export const view = (model: Model, h: HtmlBuilder<Message>): Document => {
|