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
|
@@ -14,7 +14,7 @@ import { Command, Runtime, Update } from 'foldkit'
|
|
|
14
14
|
import { Machine } from 'foldkit/experimental'
|
|
15
15
|
import { otherwise, to, when } from 'foldkit/experimental/machine'
|
|
16
16
|
import { defineMessageUnion } from 'foldkit/message'
|
|
17
|
-
import {
|
|
17
|
+
import { defineTaggedUnion } from 'foldkit/schema'
|
|
18
18
|
import { evo } from 'foldkit/struct'
|
|
19
19
|
|
|
20
20
|
import { RadioGroup } from '@foldkit/ui'
|
|
@@ -26,46 +26,34 @@ export const Discount = S.Struct({
|
|
|
26
26
|
percentOff: S.Number,
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
export const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
export const Promo = S.Union([NoPromo, AppliedPromo, RejectedPromo])
|
|
34
|
-
|
|
35
|
-
export const Cart = ts('Cart', { isShippingRequired: S.Boolean })
|
|
36
|
-
export const Shipping = ts('Shipping', { isShippingRequired: S.Boolean })
|
|
37
|
-
export const Payment = ts('Payment', {
|
|
38
|
-
isPaymentMethodSelected: S.Boolean,
|
|
39
|
-
isShippingRequired: S.Boolean,
|
|
40
|
-
})
|
|
41
|
-
export const Review = ts('Review', {
|
|
42
|
-
isPaymentMethodSelected: S.Boolean,
|
|
43
|
-
isShippingRequired: S.Boolean,
|
|
44
|
-
isTermsAccepted: S.Boolean,
|
|
45
|
-
promo: Promo,
|
|
46
|
-
promoCodeInput: S.String,
|
|
29
|
+
export const Promo = defineTaggedUnion({
|
|
30
|
+
NoPromo: {},
|
|
31
|
+
AppliedPromo: { discount: Discount },
|
|
32
|
+
RejectedPromo: {},
|
|
47
33
|
})
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
34
|
+
|
|
35
|
+
export const CheckoutState = defineTaggedUnion({
|
|
36
|
+
Cart: { isShippingRequired: S.Boolean },
|
|
37
|
+
Shipping: { isShippingRequired: S.Boolean },
|
|
38
|
+
Payment: {
|
|
39
|
+
isPaymentMethodSelected: S.Boolean,
|
|
40
|
+
isShippingRequired: S.Boolean,
|
|
41
|
+
},
|
|
42
|
+
Review: {
|
|
43
|
+
isPaymentMethodSelected: S.Boolean,
|
|
44
|
+
isShippingRequired: S.Boolean,
|
|
45
|
+
isTermsAccepted: S.Boolean,
|
|
46
|
+
promo: Promo,
|
|
47
|
+
promoCodeInput: S.String,
|
|
48
|
+
},
|
|
49
|
+
Placing: { isShippingRequired: S.Boolean, maybeDiscount: S.Option(Discount) },
|
|
50
|
+
Confirmed: {
|
|
51
|
+
isShippingRequired: S.Boolean,
|
|
52
|
+
maybeDiscount: S.Option(Discount),
|
|
53
|
+
orderId: S.String,
|
|
54
|
+
},
|
|
55
|
+
Cancelled: { isShippingRequired: S.Boolean },
|
|
59
56
|
})
|
|
60
|
-
export const CheckoutState = S.Union([
|
|
61
|
-
Cart,
|
|
62
|
-
Shipping,
|
|
63
|
-
Payment,
|
|
64
|
-
Review,
|
|
65
|
-
Placing,
|
|
66
|
-
Confirmed,
|
|
67
|
-
Cancelled,
|
|
68
|
-
])
|
|
69
57
|
|
|
70
58
|
export const TransitionLogEntry = S.Struct({
|
|
71
59
|
id: S.Number,
|
|
@@ -146,11 +134,12 @@ export const promoToMaybeDiscount = (
|
|
|
146
134
|
M.option,
|
|
147
135
|
)
|
|
148
136
|
|
|
149
|
-
export const isReviewReady = (
|
|
150
|
-
review
|
|
137
|
+
export const isReviewReady = (
|
|
138
|
+
review: typeof CheckoutState.Review.Type,
|
|
139
|
+
): boolean => review.isPaymentMethodSelected && review.isTermsAccepted
|
|
151
140
|
|
|
152
141
|
export const reviewToMaybeDiscount = (
|
|
153
|
-
review: typeof Review.Type,
|
|
142
|
+
review: typeof CheckoutState.Review.Type,
|
|
154
143
|
): Option.Option<typeof Discount.Type> => {
|
|
155
144
|
const normalizedCode = pipe(
|
|
156
145
|
review.promoCodeInput,
|
|
@@ -168,7 +157,7 @@ export const checkoutMachine = Machine.define({
|
|
|
168
157
|
state: CheckoutState,
|
|
169
158
|
message: Message,
|
|
170
159
|
})({
|
|
171
|
-
initial: Cart({ isShippingRequired: true }),
|
|
160
|
+
initial: CheckoutState.Cart({ isShippingRequired: true }),
|
|
172
161
|
states: {
|
|
173
162
|
Cart: {
|
|
174
163
|
on: {
|
|
@@ -180,11 +169,13 @@ export const checkoutMachine = Machine.define({
|
|
|
180
169
|
state => state.isShippingRequired,
|
|
181
170
|
'Shipping',
|
|
182
171
|
({ state }) =>
|
|
183
|
-
Shipping({
|
|
172
|
+
CheckoutState.Shipping({
|
|
173
|
+
isShippingRequired: state.isShippingRequired,
|
|
174
|
+
}),
|
|
184
175
|
),
|
|
185
176
|
otherwise(
|
|
186
177
|
to('Payment', ({ state }) =>
|
|
187
|
-
Payment({
|
|
178
|
+
CheckoutState.Payment({
|
|
188
179
|
isPaymentMethodSelected: false,
|
|
189
180
|
isShippingRequired: state.isShippingRequired,
|
|
190
181
|
}),
|
|
@@ -192,23 +183,27 @@ export const checkoutMachine = Machine.define({
|
|
|
192
183
|
),
|
|
193
184
|
],
|
|
194
185
|
ClickedCancel: to('Cancelled', ({ state }) =>
|
|
195
|
-
Cancelled({
|
|
186
|
+
CheckoutState.Cancelled({
|
|
187
|
+
isShippingRequired: state.isShippingRequired,
|
|
188
|
+
}),
|
|
196
189
|
),
|
|
197
190
|
},
|
|
198
191
|
},
|
|
199
192
|
Shipping: {
|
|
200
193
|
on: {
|
|
201
194
|
ClickedContinue: to('Payment', ({ state }) =>
|
|
202
|
-
Payment({
|
|
195
|
+
CheckoutState.Payment({
|
|
203
196
|
isPaymentMethodSelected: false,
|
|
204
197
|
isShippingRequired: state.isShippingRequired,
|
|
205
198
|
}),
|
|
206
199
|
),
|
|
207
200
|
ClickedBack: to('Cart', ({ state }) =>
|
|
208
|
-
Cart({ isShippingRequired: state.isShippingRequired }),
|
|
201
|
+
CheckoutState.Cart({ isShippingRequired: state.isShippingRequired }),
|
|
209
202
|
),
|
|
210
203
|
ClickedCancel: to('Cancelled', ({ state }) =>
|
|
211
|
-
Cancelled({
|
|
204
|
+
CheckoutState.Cancelled({
|
|
205
|
+
isShippingRequired: state.isShippingRequired,
|
|
206
|
+
}),
|
|
212
207
|
),
|
|
213
208
|
},
|
|
214
209
|
},
|
|
@@ -218,11 +213,11 @@ export const checkoutMachine = Machine.define({
|
|
|
218
213
|
evo(state, { isPaymentMethodSelected: () => message.isSelected }),
|
|
219
214
|
),
|
|
220
215
|
ClickedContinue: to('Review', ({ state }) =>
|
|
221
|
-
Review({
|
|
216
|
+
CheckoutState.Review({
|
|
222
217
|
isPaymentMethodSelected: state.isPaymentMethodSelected,
|
|
223
218
|
isShippingRequired: state.isShippingRequired,
|
|
224
219
|
isTermsAccepted: false,
|
|
225
|
-
promo: NoPromo(),
|
|
220
|
+
promo: Promo.NoPromo(),
|
|
226
221
|
promoCodeInput: '',
|
|
227
222
|
}),
|
|
228
223
|
),
|
|
@@ -231,16 +226,22 @@ export const checkoutMachine = Machine.define({
|
|
|
231
226
|
state => state.isShippingRequired,
|
|
232
227
|
'Shipping',
|
|
233
228
|
({ state }) =>
|
|
234
|
-
Shipping({
|
|
229
|
+
CheckoutState.Shipping({
|
|
230
|
+
isShippingRequired: state.isShippingRequired,
|
|
231
|
+
}),
|
|
235
232
|
),
|
|
236
233
|
otherwise(
|
|
237
234
|
to('Cart', ({ state }) =>
|
|
238
|
-
Cart({
|
|
235
|
+
CheckoutState.Cart({
|
|
236
|
+
isShippingRequired: state.isShippingRequired,
|
|
237
|
+
}),
|
|
239
238
|
),
|
|
240
239
|
),
|
|
241
240
|
],
|
|
242
241
|
ClickedCancel: to('Cancelled', ({ state }) =>
|
|
243
|
-
Cancelled({
|
|
242
|
+
CheckoutState.Cancelled({
|
|
243
|
+
isShippingRequired: state.isShippingRequired,
|
|
244
|
+
}),
|
|
244
245
|
),
|
|
245
246
|
},
|
|
246
247
|
},
|
|
@@ -256,7 +257,9 @@ export const checkoutMachine = Machine.define({
|
|
|
256
257
|
evo(state, {
|
|
257
258
|
promoCodeInput: () => message.value,
|
|
258
259
|
promo: currentPromo =>
|
|
259
|
-
currentPromo._tag === 'RejectedPromo'
|
|
260
|
+
currentPromo._tag === 'RejectedPromo'
|
|
261
|
+
? Promo.NoPromo()
|
|
262
|
+
: currentPromo,
|
|
260
263
|
}),
|
|
261
264
|
),
|
|
262
265
|
SubmittedPromoCode: [
|
|
@@ -264,11 +267,11 @@ export const checkoutMachine = Machine.define({
|
|
|
264
267
|
reviewToMaybeDiscount,
|
|
265
268
|
'Review',
|
|
266
269
|
({ state, guardValue: discount }) =>
|
|
267
|
-
evo(state, { promo: () => AppliedPromo({ discount }) }),
|
|
270
|
+
evo(state, { promo: () => Promo.AppliedPromo({ discount }) }),
|
|
268
271
|
),
|
|
269
272
|
otherwise(
|
|
270
273
|
to('Review', ({ state }) =>
|
|
271
|
-
evo(state, { promo: () => RejectedPromo() }),
|
|
274
|
+
evo(state, { promo: () => Promo.RejectedPromo() }),
|
|
272
275
|
),
|
|
273
276
|
),
|
|
274
277
|
],
|
|
@@ -277,7 +280,7 @@ export const checkoutMachine = Machine.define({
|
|
|
277
280
|
isReviewReady,
|
|
278
281
|
'Placing',
|
|
279
282
|
({ state }) =>
|
|
280
|
-
Placing({
|
|
283
|
+
CheckoutState.Placing({
|
|
281
284
|
isShippingRequired: state.isShippingRequired,
|
|
282
285
|
maybeDiscount: promoToMaybeDiscount(state.promo),
|
|
283
286
|
}),
|
|
@@ -287,20 +290,22 @@ export const checkoutMachine = Machine.define({
|
|
|
287
290
|
),
|
|
288
291
|
],
|
|
289
292
|
ClickedBack: to('Payment', ({ state }) =>
|
|
290
|
-
Payment({
|
|
293
|
+
CheckoutState.Payment({
|
|
291
294
|
isPaymentMethodSelected: state.isPaymentMethodSelected,
|
|
292
295
|
isShippingRequired: state.isShippingRequired,
|
|
293
296
|
}),
|
|
294
297
|
),
|
|
295
298
|
ClickedCancel: to('Cancelled', ({ state }) =>
|
|
296
|
-
Cancelled({
|
|
299
|
+
CheckoutState.Cancelled({
|
|
300
|
+
isShippingRequired: state.isShippingRequired,
|
|
301
|
+
}),
|
|
297
302
|
),
|
|
298
303
|
},
|
|
299
304
|
},
|
|
300
305
|
Placing: {
|
|
301
306
|
on: {
|
|
302
307
|
SucceededPlaceOrder: to('Confirmed', ({ state, message }) =>
|
|
303
|
-
Confirmed({
|
|
308
|
+
CheckoutState.Confirmed({
|
|
304
309
|
isShippingRequired: state.isShippingRequired,
|
|
305
310
|
maybeDiscount: state.maybeDiscount,
|
|
306
311
|
orderId: message.orderId,
|
|
@@ -311,14 +316,14 @@ export const checkoutMachine = Machine.define({
|
|
|
311
316
|
Confirmed: {
|
|
312
317
|
on: {
|
|
313
318
|
ClickedStartOver: to('Cart', ({ state }) =>
|
|
314
|
-
Cart({ isShippingRequired: state.isShippingRequired }),
|
|
319
|
+
CheckoutState.Cart({ isShippingRequired: state.isShippingRequired }),
|
|
315
320
|
),
|
|
316
321
|
},
|
|
317
322
|
},
|
|
318
323
|
Cancelled: {
|
|
319
324
|
on: {
|
|
320
325
|
ClickedStartOver: to('Cart', ({ state }) =>
|
|
321
|
-
Cart({ isShippingRequired: state.isShippingRequired }),
|
|
326
|
+
CheckoutState.Cart({ isShippingRequired: state.isShippingRequired }),
|
|
322
327
|
),
|
|
323
328
|
},
|
|
324
329
|
},
|
|
@@ -4,16 +4,15 @@ import { Command, given, message, model, story } from 'foldkit/story'
|
|
|
4
4
|
import { describe, expect, test } from 'vitest'
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
Cart,
|
|
7
|
+
CheckoutState,
|
|
9
8
|
Message,
|
|
9
|
+
type Model,
|
|
10
10
|
PlaceOrder,
|
|
11
|
-
|
|
11
|
+
Promo,
|
|
12
12
|
TRANSITION_LOG_LIMIT,
|
|
13
13
|
initialModel,
|
|
14
14
|
update,
|
|
15
15
|
} from './main'
|
|
16
|
-
import type { Model } from './main'
|
|
17
16
|
|
|
18
17
|
const maybeLatestTransitionSummary = (model: Model): Option.Option<string> =>
|
|
19
18
|
pipe(
|
|
@@ -94,7 +93,7 @@ describe('update', () => {
|
|
|
94
93
|
message(Message.ClickedStartOver()),
|
|
95
94
|
model(model => {
|
|
96
95
|
expect(model.checkout).toStrictEqual(
|
|
97
|
-
Cart({ isShippingRequired: false }),
|
|
96
|
+
CheckoutState.Cart({ isShippingRequired: false }),
|
|
98
97
|
)
|
|
99
98
|
}),
|
|
100
99
|
)
|
|
@@ -114,7 +113,9 @@ describe('update', () => {
|
|
|
114
113
|
expect(model.checkout._tag).toBe('Review')
|
|
115
114
|
if (model.checkout._tag === 'Review') {
|
|
116
115
|
expect(model.checkout.promo).toEqual(
|
|
117
|
-
AppliedPromo({
|
|
116
|
+
Promo.AppliedPromo({
|
|
117
|
+
discount: { code: 'READER10', percentOff: 10 },
|
|
118
|
+
}),
|
|
118
119
|
)
|
|
119
120
|
}
|
|
120
121
|
}),
|
|
@@ -123,7 +124,7 @@ describe('update', () => {
|
|
|
123
124
|
model(model => {
|
|
124
125
|
expect(model.checkout._tag).toBe('Review')
|
|
125
126
|
if (model.checkout._tag === 'Review') {
|
|
126
|
-
expect(model.checkout.promo).toEqual(RejectedPromo())
|
|
127
|
+
expect(model.checkout.promo).toEqual(Promo.RejectedPromo())
|
|
127
128
|
}
|
|
128
129
|
expect(maybeLatestTransitionSummary(model)).toEqual(
|
|
129
130
|
Option.some('Review -> Review on SubmittedPromoCode'),
|
|
@@ -6,23 +6,17 @@ import { Button, Checkbox, Input, RadioGroup } from '@foldkit/ui'
|
|
|
6
6
|
|
|
7
7
|
import * as Icon from './icon'
|
|
8
8
|
import {
|
|
9
|
+
CheckoutState,
|
|
10
|
+
Discount,
|
|
9
11
|
EDITIONS,
|
|
10
12
|
EditionRadioGroup,
|
|
11
13
|
Message,
|
|
14
|
+
type Model,
|
|
12
15
|
checkoutMachine,
|
|
13
16
|
editionName,
|
|
14
17
|
isReviewReady,
|
|
15
18
|
promoToMaybeDiscount,
|
|
16
19
|
} from './main'
|
|
17
|
-
import type {
|
|
18
|
-
Cart,
|
|
19
|
-
CheckoutState,
|
|
20
|
-
Confirmed,
|
|
21
|
-
Discount,
|
|
22
|
-
Model,
|
|
23
|
-
Payment,
|
|
24
|
-
Review,
|
|
25
|
-
} from './main'
|
|
26
20
|
|
|
27
21
|
// VIEW
|
|
28
22
|
|
|
@@ -433,7 +427,7 @@ const cancelCheckoutButton = (h: HtmlBuilder<Message>): Html =>
|
|
|
433
427
|
)
|
|
434
428
|
|
|
435
429
|
const cartView = (
|
|
436
|
-
state: typeof Cart.Type,
|
|
430
|
+
state: typeof CheckoutState.Cart.Type,
|
|
437
431
|
editionRadioGroup: RadioGroup.Model,
|
|
438
432
|
h: HtmlBuilder<Message>,
|
|
439
433
|
): Html => {
|
|
@@ -700,7 +694,7 @@ const shippingView = (h: HtmlBuilder<Message>): Html =>
|
|
|
700
694
|
)
|
|
701
695
|
|
|
702
696
|
const paymentView = (
|
|
703
|
-
state: typeof Payment.Type,
|
|
697
|
+
state: typeof CheckoutState.Payment.Type,
|
|
704
698
|
h: HtmlBuilder<Message>,
|
|
705
699
|
): Html =>
|
|
706
700
|
h.div(
|
|
@@ -864,7 +858,7 @@ const paymentView = (
|
|
|
864
858
|
)
|
|
865
859
|
|
|
866
860
|
const reviewView = (
|
|
867
|
-
state: typeof Review.Type,
|
|
861
|
+
state: typeof CheckoutState.Review.Type,
|
|
868
862
|
h: HtmlBuilder<Message>,
|
|
869
863
|
): Html => {
|
|
870
864
|
const isReadyToPlace = isReviewReady(state)
|
|
@@ -1194,7 +1188,7 @@ const placingView = (h: HtmlBuilder<Message>): Html =>
|
|
|
1194
1188
|
)
|
|
1195
1189
|
|
|
1196
1190
|
const confirmedView = (
|
|
1197
|
-
state: typeof Confirmed.Type,
|
|
1191
|
+
state: typeof CheckoutState.Confirmed.Type,
|
|
1198
1192
|
h: HtmlBuilder<Message>,
|
|
1199
1193
|
): Html =>
|
|
1200
1194
|
h.div(
|
|
@@ -13,7 +13,7 @@ import { KeyValueStore } from 'effect/unstable/persistence'
|
|
|
13
13
|
import { Command, Runtime, type Update } from 'foldkit'
|
|
14
14
|
import { Document, Html, HtmlBuilder } from 'foldkit/html'
|
|
15
15
|
import { defineMessageUnion } from 'foldkit/message'
|
|
16
|
-
import {
|
|
16
|
+
import { defineTaggedUnion } from 'foldkit/schema'
|
|
17
17
|
import { evo } from 'foldkit/struct'
|
|
18
18
|
|
|
19
19
|
import { BrowserKeyValueStore } from '@effect/platform-browser'
|
|
@@ -41,17 +41,11 @@ const TodosJsonString = S.fromJsonString(S.toCodecJson(Todos))
|
|
|
41
41
|
const Filter = S.Literals(['All', 'Active', 'Completed'])
|
|
42
42
|
type Filter = typeof Filter.Type
|
|
43
43
|
|
|
44
|
-
export const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
export const Editing = ts('Editing', {
|
|
48
|
-
id: S.String,
|
|
49
|
-
text: S.String,
|
|
44
|
+
export const EditingState = defineTaggedUnion({
|
|
45
|
+
NotEditing: {},
|
|
46
|
+
Editing: { id: S.String, text: S.String },
|
|
50
47
|
})
|
|
51
|
-
type
|
|
52
|
-
|
|
53
|
-
const EditingState = S.Union([NotEditing, Editing])
|
|
54
|
-
type EditingState = typeof EditingState.Type
|
|
48
|
+
export type EditingState = typeof EditingState.Type
|
|
55
49
|
|
|
56
50
|
export const Model = S.Struct({
|
|
57
51
|
todos: Todos,
|
|
@@ -95,7 +89,7 @@ export const init: Runtime.ApplicationInit<Model, Message, Flags> = flags => ({
|
|
|
95
89
|
todos: Option.getOrElse(flags.todos, () => []),
|
|
96
90
|
newTodoText: '',
|
|
97
91
|
filter: 'All',
|
|
98
|
-
editing: NotEditing(),
|
|
92
|
+
editing: EditingState.NotEditing(),
|
|
99
93
|
},
|
|
100
94
|
})
|
|
101
95
|
|
|
@@ -117,7 +111,7 @@ export const update = (model: Model, message: Message) =>
|
|
|
117
111
|
M.value(model.editing).pipe(
|
|
118
112
|
M.tagsExhaustive({
|
|
119
113
|
NotEditing: () => model.editing,
|
|
120
|
-
Editing: ({ id }) => Editing({ id, text }),
|
|
114
|
+
Editing: ({ id }) => EditingState.Editing({ id, text }),
|
|
121
115
|
}),
|
|
122
116
|
),
|
|
123
117
|
}),
|
|
@@ -184,7 +178,7 @@ export const update = (model: Model, message: Message) =>
|
|
|
184
178
|
return {
|
|
185
179
|
model: evo(model, {
|
|
186
180
|
editing: () =>
|
|
187
|
-
Editing({
|
|
181
|
+
EditingState.Editing({
|
|
188
182
|
id,
|
|
189
183
|
text: Option.match(maybeTodo, {
|
|
190
184
|
onNone: () => '',
|
|
@@ -205,7 +199,7 @@ export const update = (model: Model, message: Message) =>
|
|
|
205
199
|
if (String.isEmpty(String.trim(text))) {
|
|
206
200
|
return {
|
|
207
201
|
model: evo(model, {
|
|
208
|
-
editing: () => NotEditing(),
|
|
202
|
+
editing: () => EditingState.NotEditing(),
|
|
209
203
|
}),
|
|
210
204
|
}
|
|
211
205
|
}
|
|
@@ -219,7 +213,7 @@ export const update = (model: Model, message: Message) =>
|
|
|
219
213
|
return {
|
|
220
214
|
model: evo(model, {
|
|
221
215
|
todos: () => updatedTodos,
|
|
222
|
-
editing: () => NotEditing(),
|
|
216
|
+
editing: () => EditingState.NotEditing(),
|
|
223
217
|
}),
|
|
224
218
|
commands: [SaveTodos({ todos: updatedTodos })],
|
|
225
219
|
}
|
|
@@ -229,7 +223,7 @@ export const update = (model: Model, message: Message) =>
|
|
|
229
223
|
|
|
230
224
|
CancelledEdit: () => ({
|
|
231
225
|
model: evo(model, {
|
|
232
|
-
editing: () => NotEditing(),
|
|
226
|
+
editing: () => EditingState.NotEditing(),
|
|
233
227
|
}),
|
|
234
228
|
}),
|
|
235
229
|
|
|
@@ -13,10 +13,10 @@ import {
|
|
|
13
13
|
import { describe, test } from 'vitest'
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
|
+
EditingState,
|
|
16
17
|
GenerateTodo,
|
|
17
18
|
Message,
|
|
18
19
|
type Model,
|
|
19
|
-
NotEditing,
|
|
20
20
|
SaveTodos,
|
|
21
21
|
update,
|
|
22
22
|
view,
|
|
@@ -26,7 +26,7 @@ const emptyModel: Model = {
|
|
|
26
26
|
todos: [],
|
|
27
27
|
newTodoText: '',
|
|
28
28
|
filter: 'All',
|
|
29
|
-
editing: NotEditing(),
|
|
29
|
+
editing: EditingState.NotEditing(),
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const modelWithTodos: Model = {
|
|
@@ -3,11 +3,10 @@ import { Command, given, message, model, story } from 'foldkit/story'
|
|
|
3
3
|
import { describe, expect, test } from 'vitest'
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
EditingState,
|
|
7
7
|
GenerateTodo,
|
|
8
8
|
Message,
|
|
9
9
|
type Model,
|
|
10
|
-
NotEditing,
|
|
11
10
|
SaveTodos,
|
|
12
11
|
update,
|
|
13
12
|
} from './main'
|
|
@@ -16,7 +15,7 @@ const emptyModel: Model = {
|
|
|
16
15
|
todos: [],
|
|
17
16
|
newTodoText: '',
|
|
18
17
|
filter: 'All',
|
|
19
|
-
editing: NotEditing(),
|
|
18
|
+
editing: EditingState.NotEditing(),
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
const buyMilk = {
|
|
@@ -189,7 +188,7 @@ describe('update', () => {
|
|
|
189
188
|
message(Message.StartedEditing({ id: 'abc' })),
|
|
190
189
|
model(model => {
|
|
191
190
|
expect(model.editing).toStrictEqual(
|
|
192
|
-
Editing({ id: 'abc', text: 'Buy milk' }),
|
|
191
|
+
EditingState.Editing({ id: 'abc', text: 'Buy milk' }),
|
|
193
192
|
)
|
|
194
193
|
}),
|
|
195
194
|
)
|
|
@@ -198,7 +197,7 @@ describe('update', () => {
|
|
|
198
197
|
test('UpdatedEditingTodo updates the editing text', () => {
|
|
199
198
|
const editingModel: Model = {
|
|
200
199
|
...modelWithTodos,
|
|
201
|
-
editing: Editing({ id: 'abc', text: 'Buy milk' }),
|
|
200
|
+
editing: EditingState.Editing({ id: 'abc', text: 'Buy milk' }),
|
|
202
201
|
}
|
|
203
202
|
|
|
204
203
|
story(
|
|
@@ -207,7 +206,7 @@ describe('update', () => {
|
|
|
207
206
|
message(Message.UpdatedEditingTodo({ text: 'Buy oat milk' })),
|
|
208
207
|
model(model => {
|
|
209
208
|
expect(model.editing).toStrictEqual(
|
|
210
|
-
Editing({ id: 'abc', text: 'Buy oat milk' }),
|
|
209
|
+
EditingState.Editing({ id: 'abc', text: 'Buy oat milk' }),
|
|
211
210
|
)
|
|
212
211
|
}),
|
|
213
212
|
)
|
|
@@ -216,7 +215,7 @@ describe('update', () => {
|
|
|
216
215
|
test('SavedEdit updates the todo text and exits editing', () => {
|
|
217
216
|
const editingModel: Model = {
|
|
218
217
|
...modelWithTodos,
|
|
219
|
-
editing: Editing({ id: 'abc', text: 'Buy oat milk' }),
|
|
218
|
+
editing: EditingState.Editing({ id: 'abc', text: 'Buy oat milk' }),
|
|
220
219
|
}
|
|
221
220
|
|
|
222
221
|
const editedTodos = modelWithTodos.todos.map(todo =>
|
|
@@ -236,7 +235,7 @@ describe('update', () => {
|
|
|
236
235
|
expect(Option.map(todo, ({ text }) => text)).toStrictEqual(
|
|
237
236
|
Option.some('Buy oat milk'),
|
|
238
237
|
)
|
|
239
|
-
expect(model.editing).toStrictEqual(NotEditing())
|
|
238
|
+
expect(model.editing).toStrictEqual(EditingState.NotEditing())
|
|
240
239
|
}),
|
|
241
240
|
)
|
|
242
241
|
})
|
|
@@ -244,7 +243,7 @@ describe('update', () => {
|
|
|
244
243
|
test('SavedEdit with empty text exits editing without saving', () => {
|
|
245
244
|
const editingModel: Model = {
|
|
246
245
|
...modelWithTodos,
|
|
247
|
-
editing: Editing({ id: 'abc', text: ' ' }),
|
|
246
|
+
editing: EditingState.Editing({ id: 'abc', text: ' ' }),
|
|
248
247
|
}
|
|
249
248
|
|
|
250
249
|
story(
|
|
@@ -256,7 +255,7 @@ describe('update', () => {
|
|
|
256
255
|
expect(Option.map(todo, ({ text }) => text)).toStrictEqual(
|
|
257
256
|
Option.some('Buy milk'),
|
|
258
257
|
)
|
|
259
|
-
expect(model.editing).toStrictEqual(NotEditing())
|
|
258
|
+
expect(model.editing).toStrictEqual(EditingState.NotEditing())
|
|
260
259
|
}),
|
|
261
260
|
Command.expectNone(),
|
|
262
261
|
)
|
|
@@ -265,7 +264,7 @@ describe('update', () => {
|
|
|
265
264
|
test('CancelledEdit exits editing without changes', () => {
|
|
266
265
|
const editingModel: Model = {
|
|
267
266
|
...modelWithTodos,
|
|
268
|
-
editing: Editing({ id: 'abc', text: 'Changed text' }),
|
|
267
|
+
editing: EditingState.Editing({ id: 'abc', text: 'Changed text' }),
|
|
269
268
|
}
|
|
270
269
|
|
|
271
270
|
story(
|
|
@@ -277,7 +276,7 @@ describe('update', () => {
|
|
|
277
276
|
expect(Option.map(todo, ({ text }) => text)).toStrictEqual(
|
|
278
277
|
Option.some('Buy milk'),
|
|
279
278
|
)
|
|
280
|
-
expect(model.editing).toStrictEqual(NotEditing())
|
|
279
|
+
expect(model.editing).toStrictEqual(EditingState.NotEditing())
|
|
281
280
|
}),
|
|
282
281
|
)
|
|
283
282
|
})
|