create-foldkit-app 0.30.1 → 0.31.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/dist/templates/base/FOLDKIT.md +3 -1
- package/dist/templates/examples/auth/src/main.ts +3 -3
- package/dist/templates/examples/auth/src/page/loggedIn/model.ts +2 -2
- package/dist/templates/examples/auth/src/page/loggedOut/model.ts +2 -2
- package/dist/templates/examples/auth/src/route.ts +19 -29
- package/dist/templates/examples/auth/src/scene.test.ts +2 -2
- package/dist/templates/examples/auth/src/update.ts +3 -4
- package/dist/templates/examples/form/src/main.ts +12 -21
- package/dist/templates/examples/interrupting-commands/src/scene.test.ts +2 -2
- package/dist/templates/examples/interrupting-commands/src/story.test.ts +7 -7
- package/dist/templates/examples/job-application/src/main.ts +2 -2
- package/dist/templates/examples/job-application/src/model.ts +7 -12
- package/dist/templates/examples/job-application/src/scene.test.ts +5 -11
- package/dist/templates/examples/job-application/src/story.test.ts +4 -4
- package/dist/templates/examples/job-application/src/update.ts +8 -4
- package/dist/templates/examples/job-application/src/view/review.ts +92 -94
- package/dist/templates/examples/managed-resource-layer/src/main.ts +23 -27
- package/dist/templates/examples/managed-resource-layer/src/scene.test.ts +3 -4
- package/dist/templates/examples/managed-resource-layer/src/story.test.ts +11 -20
- package/dist/templates/examples/map/src/main.fixtures.ts +2 -2
- package/dist/templates/examples/map/src/main.ts +23 -21
- package/dist/templates/examples/map/src/scene.test.ts +4 -2
- package/dist/templates/examples/map/src/story.test.ts +9 -10
- package/dist/templates/examples/query-sync/src/main.ts +30 -29
- package/dist/templates/examples/query-sync/src/scene.test.ts +8 -15
- package/dist/templates/examples/query-sync/src/story.test.ts +21 -8
- package/dist/templates/examples/route-transitions/src/main.ts +15 -25
- package/dist/templates/examples/route-transitions/src/route.ts +21 -26
- package/dist/templates/examples/route-transitions/src/story.test.ts +20 -26
- package/dist/templates/examples/routing/src/fileTree.ts +3 -3
- package/dist/templates/examples/routing/src/main.ts +2 -12
- package/dist/templates/examples/routing/src/page/people.ts +18 -16
- package/dist/templates/examples/routing/src/route.ts +18 -32
- package/dist/templates/examples/routing/src/scene.test.ts +12 -20
- package/dist/templates/examples/routing/src/story.test.ts +9 -6
- package/dist/templates/examples/shopping-cart/src/route.ts +11 -19
- package/dist/templates/examples/shopping-cart/src/scene.test.ts +10 -8
- package/dist/templates/examples/shopping-cart/src/story.test.ts +2 -2
- package/dist/templates/examples/ssg/src/route.ts +9 -8
- package/dist/templates/examples/state-machine/src/main.ts +68 -63
- package/dist/templates/examples/state-machine/src/story.test.ts +8 -7
- package/dist/templates/examples/state-machine/src/view.ts +7 -13
- package/dist/templates/examples/todo/src/main.ts +11 -17
- package/dist/templates/examples/todo/src/scene.test.ts +2 -2
- package/dist/templates/examples/todo/src/story.test.ts +11 -12
- package/dist/templates/examples/ui-showcase/src/main.ts +64 -85
- package/dist/templates/examples/ui-showcase/src/scene.test.ts +14 -30
- package/dist/templates/examples/ui-showcase/src/story.test.ts +2 -2
- package/dist/templates/examples/ui-showcase/src/ui/message.ts +0 -70
- package/dist/templates/examples/view-transitions/src/main.ts +1 -1
- package/dist/templates/examples/view-transitions/src/route.ts +9 -12
- package/dist/templates/examples/view-transitions/src/scene.test.ts +5 -14
- package/dist/templates/examples/websocket-chat/src/main.ts +29 -37
- package/dist/templates/examples/websocket-chat/src/scene.test.ts +18 -10
- package/dist/templates/examples/websocket-chat/src/story.test.ts +19 -15
- package/dist/templates/release.json +13 -13
- package/dist/templates/rendering/ssg/src/route.ts +9 -8
- package/dist/templates/rendering/ssg/src/scene.test.ts +2 -2
- package/package.json +1 -1
|
@@ -15,8 +15,8 @@ import { Command, Route, Runtime, Update } from 'foldkit'
|
|
|
15
15
|
import { Document, Html, HtmlBuilder, childAttributes } from 'foldkit/html'
|
|
16
16
|
import { defineMessageUnion } from 'foldkit/message'
|
|
17
17
|
import { UrlRequest, load, pushUrl, replaceUrl } from 'foldkit/navigation'
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
18
|
+
import { defineRouteUnion } from 'foldkit/route'
|
|
19
|
+
import { defineTaggedUnion } from 'foldkit/schema'
|
|
20
20
|
import { evo } from 'foldkit/struct'
|
|
21
21
|
import { Url, toString as urlToString } from 'foldkit/url'
|
|
22
22
|
|
|
@@ -30,11 +30,12 @@ const Period = S.Literals(['Triassic', 'Jurassic', 'Cretaceous'])
|
|
|
30
30
|
const SortColumn = S.Literals(['Name', 'Period', 'Diet', 'Length', 'Weight'])
|
|
31
31
|
type SortColumn = typeof SortColumn.Type
|
|
32
32
|
|
|
33
|
-
export const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
export const Sorting = defineTaggedUnion({
|
|
34
|
+
Unsorted: {},
|
|
35
|
+
Ascending: { column: SortColumn },
|
|
36
|
+
Descending: { column: SortColumn },
|
|
37
|
+
})
|
|
38
|
+
export type Sorting = typeof Sorting.Type
|
|
38
39
|
|
|
39
40
|
const dietFilterItems: ReadonlyArray<string> = ['', ...Diet.literals]
|
|
40
41
|
const periodFilterItems: ReadonlyArray<string> = ['', ...Period.literals]
|
|
@@ -71,7 +72,7 @@ const sortingFromParam = (() => {
|
|
|
71
72
|
SchemaTransformation.transform({
|
|
72
73
|
decode: (maybeRaw: Option.Option<string>): Sorting =>
|
|
73
74
|
Option.match(maybeRaw, {
|
|
74
|
-
onNone: () => Unsorted(),
|
|
75
|
+
onNone: () => Sorting.Unsorted(),
|
|
75
76
|
onSome: value => {
|
|
76
77
|
const parts = String.split(value, SORT_PARAM_SEPARATOR)
|
|
77
78
|
|
|
@@ -90,12 +91,12 @@ const sortingFromParam = (() => {
|
|
|
90
91
|
}),
|
|
91
92
|
Option.map(({ column, direction }) =>
|
|
92
93
|
M.value(direction).pipe(
|
|
93
|
-
M.when('Ascending', () => Ascending({ column })),
|
|
94
|
-
M.when('Descending', () => Descending({ column })),
|
|
94
|
+
M.when('Ascending', () => Sorting.Ascending({ column })),
|
|
95
|
+
M.when('Descending', () => Sorting.Descending({ column })),
|
|
95
96
|
M.exhaustive,
|
|
96
97
|
),
|
|
97
98
|
),
|
|
98
|
-
Option.getOrElse(() => Unsorted()),
|
|
99
|
+
Option.getOrElse(() => Sorting.Unsorted()),
|
|
99
100
|
)
|
|
100
101
|
},
|
|
101
102
|
}),
|
|
@@ -115,19 +116,19 @@ const sortingFromParam = (() => {
|
|
|
115
116
|
)
|
|
116
117
|
})()
|
|
117
118
|
|
|
118
|
-
export const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
export const AppRoute = defineRouteUnion({
|
|
120
|
+
Browse: {
|
|
121
|
+
search: S.Option(S.String),
|
|
122
|
+
sorting: Sorting,
|
|
123
|
+
diet: S.Option(Diet),
|
|
124
|
+
period: S.Option(Period),
|
|
125
|
+
},
|
|
126
|
+
NotFound: { path: S.String },
|
|
123
127
|
})
|
|
124
128
|
|
|
125
|
-
export
|
|
126
|
-
|
|
127
|
-
const AppRoute = S.Union([BrowseRoute, NotFoundRoute])
|
|
128
|
-
type AppRoute = typeof AppRoute.Type
|
|
129
|
+
export type AppRoute = typeof AppRoute.Type
|
|
129
130
|
|
|
130
|
-
const browseRouter = pipe(
|
|
131
|
+
export const browseRouter = pipe(
|
|
131
132
|
Route.root,
|
|
132
133
|
Route.query(
|
|
133
134
|
S.Struct({
|
|
@@ -137,11 +138,11 @@ const browseRouter = pipe(
|
|
|
137
138
|
period: optionFromValidParam(Period),
|
|
138
139
|
}),
|
|
139
140
|
),
|
|
140
|
-
Route.mapTo(
|
|
141
|
+
Route.mapTo(AppRoute.Browse),
|
|
141
142
|
)
|
|
142
143
|
|
|
143
144
|
const routeParser = Route.oneOf(browseRouter)
|
|
144
|
-
const urlToAppRoute = Route.parseUrlWithFallback(routeParser,
|
|
145
|
+
const urlToAppRoute = Route.parseUrlWithFallback(routeParser, AppRoute.NotFound)
|
|
145
146
|
|
|
146
147
|
// MODEL
|
|
147
148
|
|
|
@@ -170,11 +171,11 @@ export type Message = typeof Message.Type
|
|
|
170
171
|
|
|
171
172
|
// INIT
|
|
172
173
|
|
|
173
|
-
type BrowseFields = Omit<typeof
|
|
174
|
+
type BrowseFields = Omit<typeof AppRoute.Browse.Type, '_tag'>
|
|
174
175
|
|
|
175
176
|
const emptyBrowseFields: BrowseFields = {
|
|
176
177
|
search: Option.none(),
|
|
177
|
-
sorting: Unsorted(),
|
|
178
|
+
sorting: Sorting.Unsorted(),
|
|
178
179
|
diet: Option.none(),
|
|
179
180
|
period: Option.none(),
|
|
180
181
|
}
|
|
@@ -219,9 +220,9 @@ const nextSorting = (sorting: Sorting, column: SortColumn): Sorting =>
|
|
|
219
220
|
pipe(
|
|
220
221
|
columnSortDirection(sorting, column),
|
|
221
222
|
M.value,
|
|
222
|
-
M.when('Unsorted', () => Ascending({ column })),
|
|
223
|
-
M.when('Ascending', () => Descending({ column })),
|
|
224
|
-
M.when('Descending', () => Unsorted()),
|
|
223
|
+
M.when('Unsorted', () => Sorting.Ascending({ column })),
|
|
224
|
+
M.when('Ascending', () => Sorting.Descending({ column })),
|
|
225
|
+
M.when('Descending', () => Sorting.Unsorted()),
|
|
225
226
|
M.exhaustive,
|
|
226
227
|
)
|
|
227
228
|
|
|
@@ -649,7 +650,7 @@ const periodLabel = (item: string): string =>
|
|
|
649
650
|
|
|
650
651
|
const browseView = (
|
|
651
652
|
model: Model,
|
|
652
|
-
route: typeof
|
|
653
|
+
route: typeof AppRoute.Browse.Type,
|
|
653
654
|
h: HtmlBuilder<Message>,
|
|
654
655
|
): Html => {
|
|
655
656
|
const fields = routeToBrowseFields(route)
|
|
@@ -4,19 +4,12 @@ import { describe, test } from 'vitest'
|
|
|
4
4
|
|
|
5
5
|
import { Listbox } from '@foldkit/ui'
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
BrowseRoute,
|
|
9
|
-
type Model,
|
|
10
|
-
NotFoundRoute,
|
|
11
|
-
Unsorted,
|
|
12
|
-
update,
|
|
13
|
-
view,
|
|
14
|
-
} from './main'
|
|
7
|
+
import { AppRoute, type Model, Sorting, update, view } from './main'
|
|
15
8
|
|
|
16
9
|
const browseModel: Model = {
|
|
17
|
-
route:
|
|
10
|
+
route: AppRoute.Browse({
|
|
18
11
|
search: Option.none(),
|
|
19
|
-
sorting: Unsorted(),
|
|
12
|
+
sorting: Sorting.Unsorted(),
|
|
20
13
|
diet: Option.none(),
|
|
21
14
|
period: Option.none(),
|
|
22
15
|
}),
|
|
@@ -47,9 +40,9 @@ describe('view', () => {
|
|
|
47
40
|
{ update, view },
|
|
48
41
|
given({
|
|
49
42
|
...browseModel,
|
|
50
|
-
route:
|
|
43
|
+
route: AppRoute.Browse({
|
|
51
44
|
search: Option.some('Tyranno'),
|
|
52
|
-
sorting: Unsorted(),
|
|
45
|
+
sorting: Sorting.Unsorted(),
|
|
53
46
|
diet: Option.none(),
|
|
54
47
|
period: Option.none(),
|
|
55
48
|
}),
|
|
@@ -63,9 +56,9 @@ describe('view', () => {
|
|
|
63
56
|
{ update, view },
|
|
64
57
|
given({
|
|
65
58
|
...browseModel,
|
|
66
|
-
route:
|
|
59
|
+
route: AppRoute.Browse({
|
|
67
60
|
search: Option.some('zzzNoMatch'),
|
|
68
|
-
sorting: Unsorted(),
|
|
61
|
+
sorting: Sorting.Unsorted(),
|
|
69
62
|
diet: Option.none(),
|
|
70
63
|
period: Option.none(),
|
|
71
64
|
}),
|
|
@@ -79,7 +72,7 @@ describe('view', () => {
|
|
|
79
72
|
{ update, view },
|
|
80
73
|
given({
|
|
81
74
|
...browseModel,
|
|
82
|
-
route:
|
|
75
|
+
route: AppRoute.NotFound({ path: '/oops' }),
|
|
83
76
|
}),
|
|
84
77
|
expect(role('heading', { name: '404 — Page Not Found' })).toExist(),
|
|
85
78
|
expect(text('The path "/oops" was not found.')).toExist(),
|
|
@@ -7,19 +7,19 @@ import { Listbox } from '@foldkit/ui'
|
|
|
7
7
|
import { Message as ListboxMessage } from '@foldkit/ui/listbox'
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
BrowseRoute,
|
|
10
|
+
AppRoute,
|
|
12
11
|
Message,
|
|
13
12
|
type Model,
|
|
14
13
|
ReplaceFilters,
|
|
15
|
-
|
|
14
|
+
Sorting,
|
|
15
|
+
browseRouter,
|
|
16
16
|
update,
|
|
17
17
|
} from './main'
|
|
18
18
|
|
|
19
19
|
const browseModel: Model = {
|
|
20
|
-
route:
|
|
20
|
+
route: AppRoute.Browse({
|
|
21
21
|
search: Option.none(),
|
|
22
|
-
sorting: Unsorted(),
|
|
22
|
+
sorting: Sorting.Unsorted(),
|
|
23
23
|
diet: Option.none(),
|
|
24
24
|
period: Option.none(),
|
|
25
25
|
}),
|
|
@@ -33,6 +33,19 @@ const urlOrThrow = (raw: string) =>
|
|
|
33
33
|
() => new Error(`Failed to parse url: ${raw}`),
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
+
describe('routing', () => {
|
|
37
|
+
test('prints sorting in the same format the parser accepts', () => {
|
|
38
|
+
const path = browseRouter({
|
|
39
|
+
search: Option.none(),
|
|
40
|
+
sorting: Sorting.Ascending({ column: 'Length' }),
|
|
41
|
+
diet: Option.none(),
|
|
42
|
+
period: Option.none(),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
expect(decodeURIComponent(path)).toBe('/?sorting=Length:Ascending')
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
36
49
|
describe('update', () => {
|
|
37
50
|
describe('ChangedUrl', () => {
|
|
38
51
|
test('parses search, sorting, diet, and period from the URL', () => {
|
|
@@ -52,7 +65,7 @@ describe('update', () => {
|
|
|
52
65
|
}
|
|
53
66
|
expect(model.route.search).toStrictEqual(Option.some('raptor'))
|
|
54
67
|
expect(model.route.sorting).toStrictEqual(
|
|
55
|
-
Ascending({ column: 'Length' }),
|
|
68
|
+
Sorting.Ascending({ column: 'Length' }),
|
|
56
69
|
)
|
|
57
70
|
expect(model.route.diet).toStrictEqual(Option.some('Carnivore'))
|
|
58
71
|
expect(model.route.period).toStrictEqual(Option.some('Cretaceous'))
|
|
@@ -92,9 +105,9 @@ describe('update', () => {
|
|
|
92
105
|
update,
|
|
93
106
|
given({
|
|
94
107
|
...browseModel,
|
|
95
|
-
route:
|
|
108
|
+
route: AppRoute.Browse({
|
|
96
109
|
search: Option.some('foo'),
|
|
97
|
-
sorting: Unsorted(),
|
|
110
|
+
sorting: Sorting.Unsorted(),
|
|
98
111
|
diet: Option.none(),
|
|
99
112
|
period: Option.none(),
|
|
100
113
|
}),
|
|
@@ -12,7 +12,7 @@ import { Document, Html, HtmlBuilder } from 'foldkit/html'
|
|
|
12
12
|
import { defineMessageUnion } from 'foldkit/message'
|
|
13
13
|
import { UrlRequest, load, pushUrl } from 'foldkit/navigation'
|
|
14
14
|
import { Transition } from 'foldkit/route'
|
|
15
|
-
import {
|
|
15
|
+
import { defineTaggedUnion } from 'foldkit/schema'
|
|
16
16
|
import { evo } from 'foldkit/struct'
|
|
17
17
|
import { Url, toString as urlToString } from 'foldkit/url'
|
|
18
18
|
|
|
@@ -26,14 +26,7 @@ import {
|
|
|
26
26
|
urlToAppRoute,
|
|
27
27
|
} from './route'
|
|
28
28
|
|
|
29
|
-
export {
|
|
30
|
-
AppRoute,
|
|
31
|
-
GalleryRoute,
|
|
32
|
-
HomeRoute,
|
|
33
|
-
NotFoundRoute,
|
|
34
|
-
PaintingRoute,
|
|
35
|
-
StudioRoute,
|
|
36
|
-
} from './route'
|
|
29
|
+
export { AppRoute } from './route'
|
|
37
30
|
|
|
38
31
|
const CATALOG_LATENCY = Duration.millis(600)
|
|
39
32
|
const PAINTING_LATENCY = Duration.millis(400)
|
|
@@ -45,15 +38,11 @@ const MAX_LOGGED_TRANSITIONS = 20
|
|
|
45
38
|
export const CatalogStatus = S.Literals(['Idle', 'Loading', 'Ready'])
|
|
46
39
|
export type CatalogStatus = typeof CatalogStatus.Type
|
|
47
40
|
|
|
48
|
-
export const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
PaintingIdle,
|
|
54
|
-
PaintingLoading,
|
|
55
|
-
PaintingReady,
|
|
56
|
-
])
|
|
41
|
+
export const PaintingStatus = defineTaggedUnion({
|
|
42
|
+
Idle: {},
|
|
43
|
+
Loading: { paintingId: S.Number },
|
|
44
|
+
Ready: { paintingId: S.Number },
|
|
45
|
+
})
|
|
57
46
|
export type PaintingStatus = typeof PaintingStatus.Type
|
|
58
47
|
|
|
59
48
|
export const LoggedTransition = S.Struct({
|
|
@@ -180,7 +169,7 @@ const loadPaintingOnEntry =
|
|
|
180
169
|
onNone: () => ({ model }),
|
|
181
170
|
onSome: ({ paintingId }) => ({
|
|
182
171
|
model: evo(model, {
|
|
183
|
-
paintingStatus: () =>
|
|
172
|
+
paintingStatus: () => PaintingStatus.Loading({ paintingId }),
|
|
184
173
|
}),
|
|
185
174
|
commands: [LoadPainting({ paintingId })],
|
|
186
175
|
}),
|
|
@@ -197,7 +186,9 @@ const reloadPaintingOnIdChange =
|
|
|
197
186
|
: {
|
|
198
187
|
model: evo(model, {
|
|
199
188
|
paintingStatus: () =>
|
|
200
|
-
|
|
189
|
+
PaintingStatus.Loading({
|
|
190
|
+
paintingId: nextRoute.paintingId,
|
|
191
|
+
}),
|
|
201
192
|
}),
|
|
202
193
|
commands: [LoadPainting({ paintingId: nextRoute.paintingId })],
|
|
203
194
|
},
|
|
@@ -260,11 +251,11 @@ export const update = (model: Model, message: Message) =>
|
|
|
260
251
|
}),
|
|
261
252
|
|
|
262
253
|
SucceededLoadPainting: ({ paintingId }) =>
|
|
263
|
-
model.paintingStatus._tag === '
|
|
254
|
+
model.paintingStatus._tag === 'Loading' &&
|
|
264
255
|
model.paintingStatus.paintingId === paintingId
|
|
265
256
|
? {
|
|
266
257
|
model: evo(model, {
|
|
267
|
-
paintingStatus: () =>
|
|
258
|
+
paintingStatus: () => PaintingStatus.Ready({ paintingId }),
|
|
268
259
|
}),
|
|
269
260
|
}
|
|
270
261
|
: { model },
|
|
@@ -288,7 +279,7 @@ export const init: Runtime.RoutingApplicationInit<Model, Message> = (
|
|
|
288
279
|
route,
|
|
289
280
|
transitionLog: [],
|
|
290
281
|
catalogStatus: 'Idle',
|
|
291
|
-
paintingStatus:
|
|
282
|
+
paintingStatus: PaintingStatus.Idle(),
|
|
292
283
|
studioDraft: '',
|
|
293
284
|
maybeSavedDraft: Option.none(),
|
|
294
285
|
})
|
|
@@ -544,8 +535,7 @@ const foundPaintingView = (
|
|
|
544
535
|
h: HtmlBuilder<Message>,
|
|
545
536
|
): Html => {
|
|
546
537
|
const isPaintingReady =
|
|
547
|
-
paintingStatus._tag === '
|
|
548
|
-
paintingStatus.paintingId === painting.id
|
|
538
|
+
paintingStatus._tag === 'Ready' && paintingStatus.paintingId === painting.id
|
|
549
539
|
|
|
550
540
|
return h.div(
|
|
551
541
|
[],
|
|
@@ -1,39 +1,34 @@
|
|
|
1
1
|
import { Schema as S, pipe } from 'effect'
|
|
2
2
|
import { Route } from 'foldkit'
|
|
3
|
-
import { int, literal,
|
|
4
|
-
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
GalleryRoute,
|
|
14
|
-
PaintingRoute,
|
|
15
|
-
StudioRoute,
|
|
16
|
-
NotFoundRoute,
|
|
17
|
-
])
|
|
18
|
-
|
|
19
|
-
export type HomeRoute = typeof HomeRoute.Type
|
|
20
|
-
export type GalleryRoute = typeof GalleryRoute.Type
|
|
21
|
-
export type PaintingRoute = typeof PaintingRoute.Type
|
|
22
|
-
export type StudioRoute = typeof StudioRoute.Type
|
|
23
|
-
export type NotFoundRoute = typeof NotFoundRoute.Type
|
|
3
|
+
import { defineRouteUnion, int, literal, slash } from 'foldkit/route'
|
|
4
|
+
|
|
5
|
+
export const AppRoute = defineRouteUnion({
|
|
6
|
+
Home: {},
|
|
7
|
+
Gallery: {},
|
|
8
|
+
Painting: { paintingId: S.Number },
|
|
9
|
+
Studio: {},
|
|
10
|
+
NotFound: { path: S.String },
|
|
11
|
+
})
|
|
12
|
+
|
|
24
13
|
export type AppRoute = typeof AppRoute.Type
|
|
25
14
|
|
|
26
|
-
export const homeRouter = pipe(Route.root, Route.mapTo(
|
|
15
|
+
export const homeRouter = pipe(Route.root, Route.mapTo(AppRoute.Home))
|
|
27
16
|
|
|
28
|
-
export const galleryRouter = pipe(
|
|
17
|
+
export const galleryRouter = pipe(
|
|
18
|
+
literal('gallery'),
|
|
19
|
+
Route.mapTo(AppRoute.Gallery),
|
|
20
|
+
)
|
|
29
21
|
|
|
30
22
|
export const paintingRouter = pipe(
|
|
31
23
|
literal('gallery'),
|
|
32
24
|
slash(int('paintingId')),
|
|
33
|
-
Route.mapTo(
|
|
25
|
+
Route.mapTo(AppRoute.Painting),
|
|
34
26
|
)
|
|
35
27
|
|
|
36
|
-
export const studioRouter = pipe(
|
|
28
|
+
export const studioRouter = pipe(
|
|
29
|
+
literal('studio'),
|
|
30
|
+
Route.mapTo(AppRoute.Studio),
|
|
31
|
+
)
|
|
37
32
|
|
|
38
33
|
const routeParser = Route.oneOf(
|
|
39
34
|
paintingRouter,
|
|
@@ -44,5 +39,5 @@ const routeParser = Route.oneOf(
|
|
|
44
39
|
|
|
45
40
|
export const urlToAppRoute = Route.parseUrlWithFallback(
|
|
46
41
|
routeParser,
|
|
47
|
-
|
|
42
|
+
AppRoute.NotFound,
|
|
48
43
|
)
|
|
@@ -6,18 +6,12 @@ import { describe, expect, test } from 'vitest'
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
AppRoute,
|
|
9
|
-
GalleryRoute,
|
|
10
|
-
HomeRoute,
|
|
11
9
|
LoadCatalog,
|
|
12
10
|
LoadPainting,
|
|
13
11
|
Message,
|
|
14
12
|
Model,
|
|
15
|
-
|
|
16
|
-
PaintingLoading,
|
|
17
|
-
PaintingReady,
|
|
18
|
-
PaintingRoute,
|
|
13
|
+
PaintingStatus,
|
|
19
14
|
SaveDraft,
|
|
20
|
-
StudioRoute,
|
|
21
15
|
init,
|
|
22
16
|
update,
|
|
23
17
|
} from './main'
|
|
@@ -33,7 +27,7 @@ const modelOn = (route: AppRoute): Model =>
|
|
|
33
27
|
route,
|
|
34
28
|
transitionLog: [],
|
|
35
29
|
catalogStatus: 'Idle',
|
|
36
|
-
paintingStatus:
|
|
30
|
+
paintingStatus: PaintingStatus.Idle(),
|
|
37
31
|
studioDraft: '',
|
|
38
32
|
maybeSavedDraft: Option.none(),
|
|
39
33
|
})
|
|
@@ -67,7 +61,7 @@ describe('update', () => {
|
|
|
67
61
|
test('entering the gallery loads the catalog and logs the transition', () => {
|
|
68
62
|
story(
|
|
69
63
|
update,
|
|
70
|
-
given(modelOn(
|
|
64
|
+
given(modelOn(AppRoute.Home())),
|
|
71
65
|
message(
|
|
72
66
|
Message.ChangedUrl({ url: urlOrThrow('http://localhost/gallery') }),
|
|
73
67
|
),
|
|
@@ -87,7 +81,7 @@ describe('update', () => {
|
|
|
87
81
|
test('re-entering the gallery while a catalog load is in flight does not fire another', () => {
|
|
88
82
|
story(
|
|
89
83
|
update,
|
|
90
|
-
given(evo(modelOn(
|
|
84
|
+
given(evo(modelOn(AppRoute.Home()), { catalogStatus: () => 'Loading' })),
|
|
91
85
|
message(
|
|
92
86
|
Message.ChangedUrl({ url: urlOrThrow('http://localhost/gallery') }),
|
|
93
87
|
),
|
|
@@ -101,13 +95,13 @@ describe('update', () => {
|
|
|
101
95
|
test('entering a painting loads it with the payload from the route', () => {
|
|
102
96
|
story(
|
|
103
97
|
update,
|
|
104
|
-
given(modelOn(
|
|
98
|
+
given(modelOn(AppRoute.Gallery())),
|
|
105
99
|
message(
|
|
106
100
|
Message.ChangedUrl({ url: urlOrThrow('http://localhost/gallery/3') }),
|
|
107
101
|
),
|
|
108
102
|
model(model => {
|
|
109
103
|
expect(model.paintingStatus).toStrictEqual(
|
|
110
|
-
|
|
104
|
+
PaintingStatus.Loading({ paintingId: 3 }),
|
|
111
105
|
)
|
|
112
106
|
}),
|
|
113
107
|
Command.expectHas(LoadPainting),
|
|
@@ -117,7 +111,7 @@ describe('update', () => {
|
|
|
117
111
|
),
|
|
118
112
|
model(model => {
|
|
119
113
|
expect(model.paintingStatus).toStrictEqual(
|
|
120
|
-
|
|
114
|
+
PaintingStatus.Ready({ paintingId: 3 }),
|
|
121
115
|
)
|
|
122
116
|
}),
|
|
123
117
|
)
|
|
@@ -127,8 +121,8 @@ describe('update', () => {
|
|
|
127
121
|
story(
|
|
128
122
|
update,
|
|
129
123
|
given(
|
|
130
|
-
evo(modelOn(
|
|
131
|
-
paintingStatus: () =>
|
|
124
|
+
evo(modelOn(AppRoute.Painting({ paintingId: 1 })), {
|
|
125
|
+
paintingStatus: () => PaintingStatus.Ready({ paintingId: 1 }),
|
|
132
126
|
}),
|
|
133
127
|
),
|
|
134
128
|
message(
|
|
@@ -136,11 +130,11 @@ describe('update', () => {
|
|
|
136
130
|
),
|
|
137
131
|
model(model => {
|
|
138
132
|
expect(model.paintingStatus).toStrictEqual(
|
|
139
|
-
|
|
133
|
+
PaintingStatus.Loading({ paintingId: 2 }),
|
|
140
134
|
)
|
|
141
135
|
expect(
|
|
142
136
|
Array.map(model.transitionLog, entry => entry.maybePreviousRoute),
|
|
143
|
-
).toStrictEqual([Option.some(
|
|
137
|
+
).toStrictEqual([Option.some(AppRoute.Painting({ paintingId: 1 }))])
|
|
144
138
|
}),
|
|
145
139
|
Command.expectHas(LoadPainting),
|
|
146
140
|
Command.resolve(
|
|
@@ -149,7 +143,7 @@ describe('update', () => {
|
|
|
149
143
|
),
|
|
150
144
|
model(model => {
|
|
151
145
|
expect(model.paintingStatus).toStrictEqual(
|
|
152
|
-
|
|
146
|
+
PaintingStatus.Ready({ paintingId: 2 }),
|
|
153
147
|
)
|
|
154
148
|
}),
|
|
155
149
|
)
|
|
@@ -159,8 +153,8 @@ describe('update', () => {
|
|
|
159
153
|
story(
|
|
160
154
|
update,
|
|
161
155
|
given(
|
|
162
|
-
evo(modelOn(
|
|
163
|
-
paintingStatus: () =>
|
|
156
|
+
evo(modelOn(AppRoute.Painting({ paintingId: 1 })), {
|
|
157
|
+
paintingStatus: () => PaintingStatus.Ready({ paintingId: 1 }),
|
|
164
158
|
}),
|
|
165
159
|
),
|
|
166
160
|
message(
|
|
@@ -169,7 +163,7 @@ describe('update', () => {
|
|
|
169
163
|
Command.expectNone(),
|
|
170
164
|
model(model => {
|
|
171
165
|
expect(model.paintingStatus).toStrictEqual(
|
|
172
|
-
|
|
166
|
+
PaintingStatus.Ready({ paintingId: 1 }),
|
|
173
167
|
)
|
|
174
168
|
}),
|
|
175
169
|
)
|
|
@@ -179,14 +173,14 @@ describe('update', () => {
|
|
|
179
173
|
story(
|
|
180
174
|
update,
|
|
181
175
|
given(
|
|
182
|
-
evo(modelOn(
|
|
183
|
-
paintingStatus: () =>
|
|
176
|
+
evo(modelOn(AppRoute.Painting({ paintingId: 2 })), {
|
|
177
|
+
paintingStatus: () => PaintingStatus.Loading({ paintingId: 2 }),
|
|
184
178
|
}),
|
|
185
179
|
),
|
|
186
180
|
message(Message.SucceededLoadPainting({ paintingId: 1 })),
|
|
187
181
|
model(model => {
|
|
188
182
|
expect(model.paintingStatus).toStrictEqual(
|
|
189
|
-
|
|
183
|
+
PaintingStatus.Loading({ paintingId: 2 }),
|
|
190
184
|
)
|
|
191
185
|
}),
|
|
192
186
|
)
|
|
@@ -196,7 +190,7 @@ describe('update', () => {
|
|
|
196
190
|
story(
|
|
197
191
|
update,
|
|
198
192
|
given(
|
|
199
|
-
evo(modelOn(
|
|
193
|
+
evo(modelOn(AppRoute.Studio()), {
|
|
200
194
|
studioDraft: () => 'half-finished thought',
|
|
201
195
|
}),
|
|
202
196
|
),
|
|
@@ -217,7 +211,7 @@ describe('update', () => {
|
|
|
217
211
|
test('leaving the studio with an empty draft saves nothing', () => {
|
|
218
212
|
story(
|
|
219
213
|
update,
|
|
220
|
-
given(modelOn(
|
|
214
|
+
given(modelOn(AppRoute.Studio())),
|
|
221
215
|
message(Message.ChangedUrl({ url: urlOrThrow('http://localhost/') })),
|
|
222
216
|
Command.expectNone(),
|
|
223
217
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Array, Match as M, Option, Schema as S, pipe } from 'effect'
|
|
2
|
-
import {
|
|
2
|
+
import { taggedStruct } from 'foldkit/schema'
|
|
3
3
|
|
|
4
4
|
export type File = typeof File.Type
|
|
5
5
|
export type Directory = Readonly<{
|
|
@@ -9,12 +9,12 @@ export type Directory = Readonly<{
|
|
|
9
9
|
}>
|
|
10
10
|
export type FileTreeEntry = File | Directory
|
|
11
11
|
|
|
12
|
-
export const File =
|
|
12
|
+
export const File = taggedStruct('File', {
|
|
13
13
|
name: S.String,
|
|
14
14
|
sizeInBytes: S.Number,
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
-
export const Directory =
|
|
17
|
+
export const Directory = taggedStruct('Directory', {
|
|
18
18
|
name: S.String,
|
|
19
19
|
entries: S.Array(S.suspend((): S.Codec<FileTreeEntry> => FileTreeEntry)),
|
|
20
20
|
})
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
import { People } from './page'
|
|
17
17
|
import {
|
|
18
18
|
AppRoute,
|
|
19
|
-
PeopleRoute,
|
|
20
19
|
filesIndexRouter,
|
|
21
20
|
filesRouter,
|
|
22
21
|
homeRouter,
|
|
@@ -25,16 +24,7 @@ import {
|
|
|
25
24
|
urlToAppRoute,
|
|
26
25
|
} from './route'
|
|
27
26
|
|
|
28
|
-
export {
|
|
29
|
-
AppRoute,
|
|
30
|
-
FilesIndexRoute,
|
|
31
|
-
FilesRoute,
|
|
32
|
-
HomeRoute,
|
|
33
|
-
NestedRoute,
|
|
34
|
-
NotFoundRoute,
|
|
35
|
-
PeopleRoute,
|
|
36
|
-
PersonRoute,
|
|
37
|
-
} from './route'
|
|
27
|
+
export { AppRoute } from './route'
|
|
38
28
|
|
|
39
29
|
// MODEL
|
|
40
30
|
|
|
@@ -66,7 +56,7 @@ export const init: Runtime.RoutingApplicationInit<Model, Message> = (
|
|
|
66
56
|
|
|
67
57
|
const initialPeopleRoute = M.value(route).pipe(
|
|
68
58
|
M.tag('People', peopleRoute => peopleRoute),
|
|
69
|
-
M.orElse(() =>
|
|
59
|
+
M.orElse(() => AppRoute.People({ searchText: Option.none() })),
|
|
70
60
|
)
|
|
71
61
|
|
|
72
62
|
const peopleInit = People.init(initialPeopleRoute)
|