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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Deferred, Effect, PubSub, Stream } from 'effect'
|
|
2
|
+
import { Mount } from 'foldkit'
|
|
3
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import { MountMap } from './main'
|
|
6
|
+
|
|
7
|
+
const maplibre = vi.hoisted(() => {
|
|
8
|
+
let container: HTMLElement | undefined
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
get container(): HTMLElement | undefined {
|
|
12
|
+
return container
|
|
13
|
+
},
|
|
14
|
+
set container(nextContainer: HTMLElement | undefined) {
|
|
15
|
+
container = nextContainer
|
|
16
|
+
},
|
|
17
|
+
disableKeyboard: vi.fn(),
|
|
18
|
+
enableKeyboard: vi.fn(),
|
|
19
|
+
makeMap: vi.fn(),
|
|
20
|
+
removeMap: vi.fn(),
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
vi.mock('maplibre-gl', () => {
|
|
25
|
+
class Map {
|
|
26
|
+
readonly keyboard = {
|
|
27
|
+
disable: maplibre.disableKeyboard,
|
|
28
|
+
enable: maplibre.enableKeyboard,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
constructor({ container }: { container: HTMLElement }) {
|
|
32
|
+
maplibre.container = container
|
|
33
|
+
maplibre.makeMap()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
remove(): void {
|
|
37
|
+
maplibre.removeMap()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class Marker {
|
|
42
|
+
readonly element: HTMLButtonElement
|
|
43
|
+
|
|
44
|
+
constructor({ element }: { element: HTMLButtonElement }) {
|
|
45
|
+
this.element = element
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setLngLat(): this {
|
|
49
|
+
return this
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
addTo(): this {
|
|
53
|
+
maplibre.container?.appendChild(this.element)
|
|
54
|
+
return this
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { Map, Marker, setWorkerUrl: vi.fn() }
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('MountMap', () => {
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
maplibre.container = undefined
|
|
64
|
+
vi.clearAllMocks()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('makes the surviving map read-only while its view is paused', async () => {
|
|
68
|
+
const host = document.createElement('div')
|
|
69
|
+
|
|
70
|
+
await Effect.runPromise(
|
|
71
|
+
Effect.scoped(
|
|
72
|
+
Effect.gen(function* () {
|
|
73
|
+
const observedInitialLive = yield* Deferred.make<void>()
|
|
74
|
+
const observedPaused = yield* Deferred.make<void>()
|
|
75
|
+
const observedResumed = yield* Deferred.make<void>()
|
|
76
|
+
maplibre.enableKeyboard.mockImplementation(() => {
|
|
77
|
+
if (maplibre.enableKeyboard.mock.calls.length === 1) {
|
|
78
|
+
Effect.runSync(Deferred.succeed(observedInitialLive, undefined))
|
|
79
|
+
} else if (maplibre.enableKeyboard.mock.calls.length === 2) {
|
|
80
|
+
Effect.runSync(Deferred.succeed(observedResumed, undefined))
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
maplibre.disableKeyboard.mockImplementation(() => {
|
|
84
|
+
Effect.runSync(Deferred.succeed(observedPaused, undefined))
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const viewStates = yield* PubSub.unbounded<Mount.ViewState>({
|
|
88
|
+
replay: 1,
|
|
89
|
+
})
|
|
90
|
+
yield* PubSub.publish(viewStates, Mount.ViewState.make('Live'))
|
|
91
|
+
yield* MountMap({ hostId: 'test-map-host' })
|
|
92
|
+
.f(host, Stream.fromPubSub(viewStates))
|
|
93
|
+
.pipe(Stream.runDrain, Effect.forkScoped)
|
|
94
|
+
|
|
95
|
+
yield* Deferred.await(observedInitialLive)
|
|
96
|
+
yield* Effect.yieldNow
|
|
97
|
+
expect(maplibre.makeMap).toHaveBeenCalledOnce()
|
|
98
|
+
const markers = host.querySelectorAll('button[data-location-id]')
|
|
99
|
+
expect(markers.length).toBeGreaterThan(0)
|
|
100
|
+
for (const marker of markers) {
|
|
101
|
+
expect(marker).toHaveProperty('disabled', false)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
yield* PubSub.publish(viewStates, Mount.ViewState.make('Paused'))
|
|
105
|
+
yield* Deferred.await(observedPaused)
|
|
106
|
+
yield* Effect.yieldNow
|
|
107
|
+
expect(maplibre.makeMap).toHaveBeenCalledOnce()
|
|
108
|
+
for (const marker of markers) {
|
|
109
|
+
expect(marker).toHaveProperty('disabled', true)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
yield* PubSub.publish(viewStates, Mount.ViewState.make('Live'))
|
|
113
|
+
yield* Deferred.await(observedResumed)
|
|
114
|
+
yield* Effect.yieldNow
|
|
115
|
+
expect(maplibre.makeMap).toHaveBeenCalledOnce()
|
|
116
|
+
for (const marker of markers) {
|
|
117
|
+
expect(marker).toHaveProperty('disabled', false)
|
|
118
|
+
}
|
|
119
|
+
}),
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
expect(maplibre.disableKeyboard).toHaveBeenCalledOnce()
|
|
124
|
+
expect(maplibre.enableKeyboard).toHaveBeenCalledTimes(2)
|
|
125
|
+
expect(maplibre.removeMap).toHaveBeenCalledOnce()
|
|
126
|
+
})
|
|
127
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Array, Effect, Predicate, Schema
|
|
1
|
+
import { Array, Effect, Predicate, Schema } from 'effect'
|
|
2
2
|
import { KeyValueStore } from 'effect/unstable/persistence'
|
|
3
3
|
import { Command } from 'foldkit'
|
|
4
4
|
|
|
@@ -14,8 +14,8 @@ import { PALETTE_THEMES, resolveColor } from './palette'
|
|
|
14
14
|
export const SaveCanvas = Command.define('SaveCanvas', {
|
|
15
15
|
args: {
|
|
16
16
|
grid: Grid,
|
|
17
|
-
gridSize:
|
|
18
|
-
paletteThemeIndex:
|
|
17
|
+
gridSize: Schema.Number,
|
|
18
|
+
paletteThemeIndex: Schema.Number,
|
|
19
19
|
selectedColorIndex: PaletteIndex,
|
|
20
20
|
},
|
|
21
21
|
messages: [Message.CompletedSaveCanvas],
|
|
@@ -28,7 +28,10 @@ export const SaveCanvas = Command.define('SaveCanvas', {
|
|
|
28
28
|
paletteThemeIndex,
|
|
29
29
|
selectedColorIndex,
|
|
30
30
|
}
|
|
31
|
-
yield* store.set(
|
|
31
|
+
yield* store.set(
|
|
32
|
+
STORAGE_KEY,
|
|
33
|
+
Schema.encodeSync(SavedCanvasJsonString)(data),
|
|
34
|
+
)
|
|
32
35
|
return Message.CompletedSaveCanvas()
|
|
33
36
|
}).pipe(
|
|
34
37
|
Effect.catch(() => Effect.succeed(Message.CompletedSaveCanvas())),
|
|
@@ -45,7 +48,11 @@ export const saveCanvas = (model: Model) =>
|
|
|
45
48
|
})
|
|
46
49
|
|
|
47
50
|
export const ExportPng = Command.define('ExportPng', {
|
|
48
|
-
args: {
|
|
51
|
+
args: {
|
|
52
|
+
grid: Grid,
|
|
53
|
+
gridSize: Schema.Number,
|
|
54
|
+
paletteThemeIndex: Schema.Number,
|
|
55
|
+
},
|
|
49
56
|
messages: [Message.SucceededExportPng, Message.FailedExportPng],
|
|
50
57
|
execute: ({ grid, gridSize, paletteThemeIndex }) =>
|
|
51
58
|
Effect.gen(function* () {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Option, Schema
|
|
1
|
+
import { Effect, Option, Schema } from 'effect'
|
|
2
2
|
import { KeyValueStore } from 'effect/unstable/persistence'
|
|
3
3
|
import { Runtime } from 'foldkit'
|
|
4
4
|
|
|
@@ -25,8 +25,8 @@ import {
|
|
|
25
25
|
|
|
26
26
|
// FLAGS
|
|
27
27
|
|
|
28
|
-
export const Flags =
|
|
29
|
-
maybeSavedCanvas:
|
|
28
|
+
export const Flags = Schema.Struct({
|
|
29
|
+
maybeSavedCanvas: Schema.Option(SavedCanvas),
|
|
30
30
|
})
|
|
31
31
|
export type Flags = typeof Flags.Type
|
|
32
32
|
|
|
@@ -35,7 +35,7 @@ export const flags: Effect.Effect<Flags> = Effect.gen(function* () {
|
|
|
35
35
|
const json = yield* Effect.fromOption(
|
|
36
36
|
Option.fromNullishOr(yield* store.get(STORAGE_KEY)),
|
|
37
37
|
)
|
|
38
|
-
const decoded = yield*
|
|
38
|
+
const decoded = yield* Schema.decodeEffect(SavedCanvasJsonString)(json)
|
|
39
39
|
return Flags.make({ maybeSavedCanvas: Option.some(decoded) })
|
|
40
40
|
}).pipe(
|
|
41
41
|
Effect.catch(() =>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Schema
|
|
1
|
+
import { Schema } from 'effect'
|
|
2
2
|
import { defineMessageUnion } from 'foldkit/message'
|
|
3
3
|
|
|
4
4
|
import { Dialog, Listbox, RadioGroup } from '@foldkit/ui'
|
|
@@ -6,23 +6,23 @@ import { Dialog, Listbox, RadioGroup } from '@foldkit/ui'
|
|
|
6
6
|
import { PaletteIndex, Tool } from './model'
|
|
7
7
|
|
|
8
8
|
export const Message = defineMessageUnion({
|
|
9
|
-
PressedCell: { x:
|
|
10
|
-
EnteredCell: { x:
|
|
9
|
+
PressedCell: { x: Schema.Number, y: Schema.Number },
|
|
10
|
+
EnteredCell: { x: Schema.Number, y: Schema.Number },
|
|
11
11
|
LeftCanvas: {},
|
|
12
12
|
ReleasedMouse: {},
|
|
13
13
|
SelectedColor: { colorIndex: PaletteIndex },
|
|
14
14
|
SelectedTool: { tool: Tool },
|
|
15
|
-
SelectedGridSize: { size:
|
|
15
|
+
SelectedGridSize: { size: Schema.Number },
|
|
16
16
|
ToggledMirrorHorizontal: {},
|
|
17
17
|
ToggledMirrorVertical: {},
|
|
18
18
|
ClickedUndo: {},
|
|
19
19
|
ClickedRedo: {},
|
|
20
|
-
ClickedHistoryStep: { stepIndex:
|
|
21
|
-
ClickedRedoStep: { stepIndex:
|
|
20
|
+
ClickedHistoryStep: { stepIndex: Schema.Number },
|
|
21
|
+
ClickedRedoStep: { stepIndex: Schema.Number },
|
|
22
22
|
ClickedClear: {},
|
|
23
23
|
ClickedExport: {},
|
|
24
24
|
SucceededExportPng: {},
|
|
25
|
-
FailedExportPng: { error:
|
|
25
|
+
FailedExportPng: { error: Schema.String },
|
|
26
26
|
GotErrorDialogMessage: { message: Dialog.Message },
|
|
27
27
|
GotThemeListboxMessage: { message: Listbox.Message },
|
|
28
28
|
GotToolRadioGroupMessage: { message: RadioGroup.Message },
|
|
@@ -1,67 +1,72 @@
|
|
|
1
|
-
import { Option, Schema
|
|
1
|
+
import { Option, Schema } from 'effect'
|
|
2
2
|
|
|
3
3
|
import { Dialog, Listbox, RadioGroup } from '@foldkit/ui'
|
|
4
4
|
|
|
5
5
|
// CONSTANT
|
|
6
6
|
|
|
7
|
-
export const PaletteIndex =
|
|
7
|
+
export const PaletteIndex = Schema.Literals([
|
|
8
8
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
|
9
9
|
])
|
|
10
10
|
export type PaletteIndex = typeof PaletteIndex.Type
|
|
11
11
|
|
|
12
|
-
export const HexColor =
|
|
13
|
-
|
|
14
|
-
)
|
|
12
|
+
export const HexColor = Schema.String.check(
|
|
13
|
+
Schema.isPattern(/^#[0-9a-f]{6}$/),
|
|
14
|
+
).pipe(Schema.brand('HexColor'))
|
|
15
15
|
export type HexColor = typeof HexColor.Type
|
|
16
16
|
|
|
17
|
-
export const Tool =
|
|
17
|
+
export const Tool = Schema.Literals(['Brush', 'Fill', 'Eraser'])
|
|
18
18
|
export type Tool = typeof Tool.Type
|
|
19
19
|
|
|
20
|
-
export const MirrorMode =
|
|
20
|
+
export const MirrorMode = Schema.Literals([
|
|
21
|
+
'None',
|
|
22
|
+
'Horizontal',
|
|
23
|
+
'Vertical',
|
|
24
|
+
'Both',
|
|
25
|
+
])
|
|
21
26
|
export type MirrorMode = typeof MirrorMode.Type
|
|
22
27
|
|
|
23
|
-
export const Cell =
|
|
28
|
+
export const Cell = Schema.Option(PaletteIndex)
|
|
24
29
|
export type Cell = typeof Cell.Type
|
|
25
30
|
|
|
26
|
-
const Row =
|
|
27
|
-
export const Grid =
|
|
31
|
+
const Row = Schema.Array(Cell)
|
|
32
|
+
export const Grid = Schema.Array(Row)
|
|
28
33
|
export type Grid = typeof Grid.Type
|
|
29
34
|
|
|
30
|
-
export const Position =
|
|
35
|
+
export const Position = Schema.Struct({ x: Schema.Number, y: Schema.Number })
|
|
31
36
|
|
|
32
|
-
const SavedCell =
|
|
33
|
-
const SavedRow =
|
|
34
|
-
const SavedGrid =
|
|
37
|
+
const SavedCell = Schema.Option(PaletteIndex)
|
|
38
|
+
const SavedRow = Schema.Array(SavedCell)
|
|
39
|
+
const SavedGrid = Schema.Array(SavedRow)
|
|
35
40
|
|
|
36
|
-
export const SavedCanvas =
|
|
41
|
+
export const SavedCanvas = Schema.Struct({
|
|
37
42
|
grid: SavedGrid,
|
|
38
|
-
gridSize:
|
|
39
|
-
paletteThemeIndex:
|
|
43
|
+
gridSize: Schema.Number,
|
|
44
|
+
paletteThemeIndex: Schema.Number,
|
|
40
45
|
selectedColorIndex: PaletteIndex,
|
|
41
46
|
})
|
|
42
47
|
export type SavedCanvas = typeof SavedCanvas.Type
|
|
43
48
|
|
|
44
|
-
export const SavedCanvasJsonString =
|
|
45
|
-
|
|
49
|
+
export const SavedCanvasJsonString = Schema.fromJsonString(
|
|
50
|
+
Schema.toCodecJson(SavedCanvas),
|
|
46
51
|
)
|
|
47
52
|
|
|
48
53
|
// MODEL
|
|
49
54
|
|
|
50
|
-
export const Model =
|
|
55
|
+
export const Model = Schema.Struct({
|
|
51
56
|
grid: Grid,
|
|
52
|
-
undoStack:
|
|
53
|
-
redoStack:
|
|
57
|
+
undoStack: Schema.Array(Grid),
|
|
58
|
+
redoStack: Schema.Array(Grid),
|
|
54
59
|
selectedColorIndex: PaletteIndex,
|
|
55
|
-
gridSize:
|
|
60
|
+
gridSize: Schema.Number,
|
|
56
61
|
tool: Tool,
|
|
57
62
|
mirrorMode: MirrorMode,
|
|
58
|
-
isDrawing:
|
|
59
|
-
maybeHoveredCell:
|
|
63
|
+
isDrawing: Schema.Boolean,
|
|
64
|
+
maybeHoveredCell: Schema.Option(Position),
|
|
60
65
|
errorDialog: Dialog.Model,
|
|
61
|
-
maybeExportError:
|
|
62
|
-
paletteThemeIndex:
|
|
66
|
+
maybeExportError: Schema.Option(Schema.String),
|
|
67
|
+
paletteThemeIndex: Schema.Number,
|
|
63
68
|
gridSizeConfirmDialog: Dialog.Model,
|
|
64
|
-
maybePendingGridSize:
|
|
69
|
+
maybePendingGridSize: Schema.Option(Schema.Number),
|
|
65
70
|
themeListbox: Listbox.Model,
|
|
66
71
|
toolRadioGroup: RadioGroup.Model,
|
|
67
72
|
gridSizeRadioGroup: RadioGroup.Model,
|
|
@@ -74,6 +79,6 @@ export const paletteIndexFromValue = (
|
|
|
74
79
|
fallback: PaletteIndex,
|
|
75
80
|
): PaletteIndex =>
|
|
76
81
|
Option.getOrElse(
|
|
77
|
-
|
|
82
|
+
Schema.decodeUnknownOption(PaletteIndex)(Number(value)),
|
|
78
83
|
() => fallback,
|
|
79
84
|
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Option, Schema
|
|
1
|
+
import { Effect, Option, Schema, Stream } from 'effect'
|
|
2
2
|
import { Subscription } from 'foldkit'
|
|
3
3
|
|
|
4
4
|
import { Message } from './message'
|
|
@@ -46,7 +46,7 @@ export const subscriptions = Subscription.make<Model, Message>()(entry => ({
|
|
|
46
46
|
),
|
|
47
47
|
|
|
48
48
|
mouseRelease: entry(
|
|
49
|
-
{ isDrawing:
|
|
49
|
+
{ isDrawing: Schema.Boolean },
|
|
50
50
|
{
|
|
51
51
|
modelToDependencies: model => ({ isDrawing: model.isDrawing }),
|
|
52
52
|
dependenciesToStream: ({ isDrawing }) =>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Array, Match
|
|
1
|
+
import { Array, Match, Option } from 'effect'
|
|
2
2
|
import { Update } from 'foldkit'
|
|
3
3
|
import { evo } from 'foldkit/struct'
|
|
4
4
|
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
|
|
33
33
|
type UpdateReturn = Update.Return<Model, Message>
|
|
34
34
|
|
|
35
|
-
const withUpdateReturn =
|
|
35
|
+
const withUpdateReturn = Match.withReturnType<UpdateReturn>()
|
|
36
36
|
|
|
37
37
|
const applyEraser = (model: Model, x: number, y: number) => {
|
|
38
38
|
const positions = getMirroredPositions(x, y, model.gridSize, model.mirrorMode)
|
|
@@ -51,15 +51,14 @@ const applyFill = (model: Model, x: number, y: number) => {
|
|
|
51
51
|
)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
const foldErrorDialogOutMessage =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}),
|
|
54
|
+
const foldErrorDialogOutMessage = Dialog.OutMessage.match<
|
|
55
|
+
Update.Step<Model, Message>
|
|
56
|
+
>({
|
|
57
|
+
Opened: () => model => ({ model }),
|
|
58
|
+
Closed: () => model => ({
|
|
59
|
+
model: evo(model, { maybeExportError: () => Option.none() }),
|
|
61
60
|
}),
|
|
62
|
-
)
|
|
61
|
+
})
|
|
63
62
|
|
|
64
63
|
const foldErrorDialog = Update.foldChild({
|
|
65
64
|
update: Dialog.update,
|
|
@@ -79,27 +78,24 @@ const foldErrorDialogOpen = Update.foldChildStep({
|
|
|
79
78
|
foldOutMessage: foldErrorDialogOutMessage,
|
|
80
79
|
})
|
|
81
80
|
|
|
82
|
-
const foldThemeListboxOutMessage
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
},
|
|
101
|
-
}),
|
|
102
|
-
)
|
|
81
|
+
const foldThemeListboxOutMessage = Listbox.OutMessage.match<
|
|
82
|
+
Update.Step<Model, Message>
|
|
83
|
+
>({
|
|
84
|
+
Selected:
|
|
85
|
+
({ value }) =>
|
|
86
|
+
model => {
|
|
87
|
+
const themeIndex = Number(value)
|
|
88
|
+
const maybeNextTheme = Array.get(PALETTE_THEMES, themeIndex)
|
|
89
|
+
if (Option.isNone(maybeNextTheme)) {
|
|
90
|
+
return { model }
|
|
91
|
+
}
|
|
92
|
+
const nextModel = evo(model, {
|
|
93
|
+
paletteThemeIndex: () => themeIndex,
|
|
94
|
+
selectedColorIndex: () => DEFAULT_COLOR_INDEX,
|
|
95
|
+
})
|
|
96
|
+
return { model: nextModel, commands: [saveCanvas(nextModel)] }
|
|
97
|
+
},
|
|
98
|
+
})
|
|
103
99
|
|
|
104
100
|
const foldThemeListbox = Update.foldChild({
|
|
105
101
|
update: ThemeListbox.update,
|
|
@@ -110,15 +106,14 @@ const foldThemeListbox = Update.foldChild({
|
|
|
110
106
|
foldOutMessage: foldThemeListboxOutMessage,
|
|
111
107
|
})
|
|
112
108
|
|
|
113
|
-
const foldGridSizeConfirmDialogOutMessage =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}),
|
|
109
|
+
const foldGridSizeConfirmDialogOutMessage = Dialog.OutMessage.match<
|
|
110
|
+
Update.Step<Model, Message>
|
|
111
|
+
>({
|
|
112
|
+
Opened: () => model => ({ model }),
|
|
113
|
+
Closed: () => model => ({
|
|
114
|
+
model: evo(model, { maybePendingGridSize: () => Option.none() }),
|
|
120
115
|
}),
|
|
121
|
-
)
|
|
116
|
+
})
|
|
122
117
|
|
|
123
118
|
const foldGridSizeConfirmDialog = Update.foldChild({
|
|
124
119
|
update: Dialog.update,
|
|
@@ -159,15 +154,15 @@ const selectColor = (model: Model, colorIndex: PaletteIndex): UpdateReturn => {
|
|
|
159
154
|
return { model: nextModel, commands: [saveCanvas(nextModel)] }
|
|
160
155
|
}
|
|
161
156
|
|
|
162
|
-
const foldToolRadioGroupOutMessage =
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
157
|
+
const foldToolRadioGroupOutMessage = RadioGroup.OutMessage.match<
|
|
158
|
+
Update.Step<Model, Message>,
|
|
159
|
+
RadioGroup.OutMessage<Tool>
|
|
160
|
+
>({
|
|
161
|
+
Selected:
|
|
162
|
+
({ value }) =>
|
|
163
|
+
model =>
|
|
164
|
+
selectTool(model, value),
|
|
165
|
+
})
|
|
171
166
|
|
|
172
167
|
const foldToolRadioGroup = Update.foldChild({
|
|
173
168
|
update: ToolRadioGroup.update,
|
|
@@ -178,15 +173,14 @@ const foldToolRadioGroup = Update.foldChild({
|
|
|
178
173
|
foldOutMessage: foldToolRadioGroupOutMessage,
|
|
179
174
|
})
|
|
180
175
|
|
|
181
|
-
const foldGridSizeRadioGroupOutMessage =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
)
|
|
176
|
+
const foldGridSizeRadioGroupOutMessage = RadioGroup.OutMessage.match<
|
|
177
|
+
Update.Step<Model, Message>
|
|
178
|
+
>({
|
|
179
|
+
Selected:
|
|
180
|
+
({ value }) =>
|
|
181
|
+
model =>
|
|
182
|
+
requestGridSizeChange(model, Number(value)),
|
|
183
|
+
})
|
|
190
184
|
|
|
191
185
|
const foldGridSizeRadioGroup = Update.foldChild({
|
|
192
186
|
update: GridSizeRadioGroup.update,
|
|
@@ -197,18 +191,17 @@ const foldGridSizeRadioGroup = Update.foldChild({
|
|
|
197
191
|
foldOutMessage: foldGridSizeRadioGroupOutMessage,
|
|
198
192
|
})
|
|
199
193
|
|
|
200
|
-
const foldPaletteRadioGroupOutMessage =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
)
|
|
194
|
+
const foldPaletteRadioGroupOutMessage = RadioGroup.OutMessage.match<
|
|
195
|
+
Update.Step<Model, Message>
|
|
196
|
+
>({
|
|
197
|
+
Selected:
|
|
198
|
+
({ value }) =>
|
|
199
|
+
model =>
|
|
200
|
+
selectColor(
|
|
201
|
+
model,
|
|
202
|
+
paletteIndexFromValue(value, model.selectedColorIndex),
|
|
203
|
+
),
|
|
204
|
+
})
|
|
212
205
|
|
|
213
206
|
const foldPaletteRadioGroup = Update.foldChild({
|
|
214
207
|
update: PaletteRadioGroup.update,
|
|
@@ -222,9 +215,9 @@ const foldPaletteRadioGroup = Update.foldChild({
|
|
|
222
215
|
export const update = (model: Model, message: Message) =>
|
|
223
216
|
Message.match<UpdateReturn>(message, {
|
|
224
217
|
PressedCell: ({ x, y }) =>
|
|
225
|
-
|
|
218
|
+
Match.value(model.tool).pipe(
|
|
226
219
|
withUpdateReturn,
|
|
227
|
-
|
|
220
|
+
Match.when('Brush', () => ({
|
|
228
221
|
model: evo(model, {
|
|
229
222
|
grid: () => applyBrush(model, x, y),
|
|
230
223
|
undoStack: () => pushHistory(model.undoStack, model.grid),
|
|
@@ -232,7 +225,7 @@ export const update = (model: Model, message: Message) =>
|
|
|
232
225
|
isDrawing: () => true,
|
|
233
226
|
}),
|
|
234
227
|
})),
|
|
235
|
-
|
|
228
|
+
Match.when('Fill', () => {
|
|
236
229
|
const nextModel = evo(model, {
|
|
237
230
|
grid: () => applyFill(model, x, y),
|
|
238
231
|
undoStack: () => pushHistory(model.undoStack, model.grid),
|
|
@@ -240,7 +233,7 @@ export const update = (model: Model, message: Message) =>
|
|
|
240
233
|
})
|
|
241
234
|
return { model: nextModel, commands: [saveCanvas(nextModel)] }
|
|
242
235
|
}),
|
|
243
|
-
|
|
236
|
+
Match.when('Eraser', () => ({
|
|
244
237
|
model: evo(model, {
|
|
245
238
|
grid: () => applyEraser(model, x, y),
|
|
246
239
|
undoStack: () => pushHistory(model.undoStack, model.grid),
|
|
@@ -248,7 +241,7 @@ export const update = (model: Model, message: Message) =>
|
|
|
248
241
|
isDrawing: () => true,
|
|
249
242
|
}),
|
|
250
243
|
})),
|
|
251
|
-
|
|
244
|
+
Match.exhaustive,
|
|
252
245
|
),
|
|
253
246
|
|
|
254
247
|
EnteredCell: ({ x, y }) => {
|
|
@@ -299,23 +292,23 @@ export const update = (model: Model, message: Message) =>
|
|
|
299
292
|
foldPaletteRadioGroup(model, message),
|
|
300
293
|
|
|
301
294
|
ToggledMirrorHorizontal: () => {
|
|
302
|
-
const nextMirrorMode =
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
295
|
+
const nextMirrorMode = Match.value(model.mirrorMode).pipe(
|
|
296
|
+
Match.when('None', () => 'Horizontal' as const),
|
|
297
|
+
Match.when('Horizontal', () => 'None' as const),
|
|
298
|
+
Match.when('Vertical', () => 'Both' as const),
|
|
299
|
+
Match.when('Both', () => 'Vertical' as const),
|
|
300
|
+
Match.exhaustive,
|
|
308
301
|
)
|
|
309
302
|
return { model: evo(model, { mirrorMode: () => nextMirrorMode }) }
|
|
310
303
|
},
|
|
311
304
|
|
|
312
305
|
ToggledMirrorVertical: () => {
|
|
313
|
-
const nextMirrorMode =
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
306
|
+
const nextMirrorMode = Match.value(model.mirrorMode).pipe(
|
|
307
|
+
Match.when('None', () => 'Vertical' as const),
|
|
308
|
+
Match.when('Vertical', () => 'None' as const),
|
|
309
|
+
Match.when('Horizontal', () => 'Both' as const),
|
|
310
|
+
Match.when('Both', () => 'Horizontal' as const),
|
|
311
|
+
Match.exhaustive,
|
|
319
312
|
)
|
|
320
313
|
return { model: evo(model, { mirrorMode: () => nextMirrorMode }) }
|
|
321
314
|
},
|