@hkdigital/lib-core 0.5.57 → 0.5.59
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/logging/internal/logger/Logger.js +1 -1
- package/dist/state/machines/page-machine/PageMachine.svelte.d.ts +146 -138
- package/dist/state/machines/page-machine/PageMachine.svelte.js +263 -445
- package/dist/state/machines/page-machine/PageMachine.svelte.js__ +708 -0
- package/dist/state/machines/page-machine/README.md +121 -69
- package/package.json +1 -1
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
# PageMachine
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Route-aware data manager for tracking navigation and managing business data
|
|
4
|
+
within a route group.
|
|
4
5
|
|
|
5
6
|
## How it connects
|
|
6
7
|
|
|
7
8
|
```
|
|
8
9
|
┌─────────────────────────────────────────────────────────┐
|
|
9
10
|
│ PuzzleState (extends PageMachine) │
|
|
10
|
-
│ -
|
|
11
|
-
│ -
|
|
12
|
-
│ -
|
|
13
|
-
│ - Provides computed properties (
|
|
11
|
+
│ - Tracks current route within a route group │
|
|
12
|
+
│ - Manages business/domain data │
|
|
13
|
+
│ - Tracks visited routes │
|
|
14
|
+
│ - Provides computed properties (isOnIntro, etc.) │
|
|
14
15
|
│ - Contains GameLogic for reactive game state │
|
|
15
16
|
│ - Optional: services, preload, reset, etc. │
|
|
16
17
|
└────────────────┬────────────────────────────────────────┘
|
|
@@ -19,7 +20,7 @@ State machine for managing page view states with URL route mapping.
|
|
|
19
20
|
│
|
|
20
21
|
┌────────────────▼────────────────────────────────────────┐
|
|
21
22
|
│ +layout.svelte │
|
|
22
|
-
│ IMPORTANT: Must sync URL with
|
|
23
|
+
│ IMPORTANT: Must sync URL with machine: │
|
|
23
24
|
│ $effect(() => { │
|
|
24
25
|
│ puzzleState.syncFromPath($page.url.pathname); │
|
|
25
26
|
│ }); │
|
|
@@ -33,28 +34,28 @@ State machine for managing page view states with URL route mapping.
|
|
|
33
34
|
│ +page │ │ +page │ │ Component│
|
|
34
35
|
│ │ │ │ │ │
|
|
35
36
|
└──────────┘ └──────────┘ └──────────┘
|
|
36
|
-
Access via: puzzleState.current, puzzleState.
|
|
37
|
+
Access via: puzzleState.current, puzzleState.isOnIntro, etc.
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
## Main Purposes
|
|
40
41
|
|
|
41
|
-
1. **Track current
|
|
42
|
-
2. **
|
|
43
|
-
3. **Manage
|
|
44
|
-
4. **
|
|
45
|
-
5. **
|
|
42
|
+
1. **Track current route** - Which page is active
|
|
43
|
+
2. **Sync with browser navigation** - Keep state in sync with URL
|
|
44
|
+
3. **Manage business data** - Persistent settings and progress
|
|
45
|
+
4. **Track visited routes** - Know which pages user has seen
|
|
46
|
+
5. **Manage start path** - Define and navigate to the entry point
|
|
46
47
|
|
|
47
48
|
## Basic Usage
|
|
48
49
|
|
|
49
|
-
### 1. Define
|
|
50
|
+
### 1. Define route constants
|
|
50
51
|
|
|
51
52
|
```javascript
|
|
52
|
-
// puzzle.
|
|
53
|
-
export const
|
|
54
|
-
export const
|
|
55
|
-
export const
|
|
56
|
-
export const
|
|
57
|
-
export const
|
|
53
|
+
// puzzle.routes.js
|
|
54
|
+
export const ROUTE_INTRO = '/puzzle/intro';
|
|
55
|
+
export const ROUTE_TUTORIAL = '/puzzle/tutorial';
|
|
56
|
+
export const ROUTE_LEVEL1 = '/puzzle/level1';
|
|
57
|
+
export const ROUTE_LEVEL2 = '/puzzle/level2';
|
|
58
|
+
export const ROUTE_COMPLETE = '/puzzle/complete';
|
|
58
59
|
```
|
|
59
60
|
|
|
60
61
|
### 2. Create state class (extends PageMachine)
|
|
@@ -65,12 +66,12 @@ import { defineStateContext } from '@hkdigital/lib-core/state/context.js';
|
|
|
65
66
|
import PageMachine from '$lib/state/machines/PageMachine.svelte.js';
|
|
66
67
|
import PuzzleGameLogic from './puzzle.game-logic.svelte.js';
|
|
67
68
|
import {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
} from './puzzle.
|
|
69
|
+
ROUTE_INTRO,
|
|
70
|
+
ROUTE_TUTORIAL,
|
|
71
|
+
ROUTE_LEVEL1,
|
|
72
|
+
ROUTE_LEVEL2,
|
|
73
|
+
ROUTE_COMPLETE
|
|
74
|
+
} from './puzzle.routes.js';
|
|
74
75
|
|
|
75
76
|
// Data keys for persistent data
|
|
76
77
|
const KEY_TUTORIAL_SEEN = 'tutorial-seen';
|
|
@@ -83,13 +84,18 @@ export class PuzzleState extends PageMachine {
|
|
|
83
84
|
constructor() {
|
|
84
85
|
// Call PageMachine constructor with route config
|
|
85
86
|
super({
|
|
86
|
-
startPath:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
startPath: ROUTE_INTRO,
|
|
88
|
+
routes: [
|
|
89
|
+
ROUTE_INTRO,
|
|
90
|
+
ROUTE_TUTORIAL,
|
|
91
|
+
ROUTE_LEVEL1,
|
|
92
|
+
ROUTE_LEVEL2,
|
|
93
|
+
ROUTE_COMPLETE
|
|
94
|
+
],
|
|
95
|
+
initialData: {
|
|
96
|
+
[KEY_TUTORIAL_SEEN]: false,
|
|
97
|
+
[KEY_HIGHEST_LEVEL]: 1,
|
|
98
|
+
[KEY_DIFFICULTY]: 'normal'
|
|
93
99
|
}
|
|
94
100
|
});
|
|
95
101
|
|
|
@@ -101,24 +107,24 @@ export class PuzzleState extends PageMachine {
|
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
// Computed properties for convenience
|
|
104
|
-
get
|
|
105
|
-
return this.current ===
|
|
110
|
+
get isOnIntro() {
|
|
111
|
+
return this.current === ROUTE_INTRO;
|
|
106
112
|
}
|
|
107
113
|
|
|
108
|
-
get
|
|
109
|
-
return this.current ===
|
|
114
|
+
get isOnTutorial() {
|
|
115
|
+
return this.current === ROUTE_TUTORIAL;
|
|
110
116
|
}
|
|
111
117
|
|
|
112
|
-
get
|
|
113
|
-
return this.current ===
|
|
118
|
+
get isOnLevel1() {
|
|
119
|
+
return this.current === ROUTE_LEVEL1;
|
|
114
120
|
}
|
|
115
121
|
|
|
116
|
-
get
|
|
117
|
-
return this.current ===
|
|
122
|
+
get isOnLevel2() {
|
|
123
|
+
return this.current === ROUTE_LEVEL2;
|
|
118
124
|
}
|
|
119
125
|
|
|
120
126
|
get isComplete() {
|
|
121
|
-
return this.current ===
|
|
127
|
+
return this.current === ROUTE_COMPLETE;
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
// Persistent settings/progress (use getData/setData)
|
|
@@ -166,12 +172,13 @@ export const [createOrGetPuzzleState, createPuzzleState, getPuzzleState] =
|
|
|
166
172
|
|
|
167
173
|
### 3. Sync with route in +layout.svelte component
|
|
168
174
|
|
|
169
|
-
**This is IMPORTANT for
|
|
175
|
+
**This is IMPORTANT for URL path to be connected to the page machine**
|
|
170
176
|
|
|
171
177
|
```svelte
|
|
172
178
|
<script>
|
|
173
179
|
import { page } from '$app/stores';
|
|
174
180
|
import { createOrGetPuzzleState } from '../puzzle.state.svelte.js';
|
|
181
|
+
import { ROUTE_INTRO } from '../puzzle.routes.js';
|
|
175
182
|
|
|
176
183
|
const puzzleState = createOrGetPuzzleState();
|
|
177
184
|
|
|
@@ -186,7 +193,7 @@ export const [createOrGetPuzzleState, createPuzzleState, getPuzzleState] =
|
|
|
186
193
|
|
|
187
194
|
if (!puzzleState.isStartPath(currentPath) &&
|
|
188
195
|
currentPath.startsWith('/puzzle') &&
|
|
189
|
-
!puzzleState.hasVisited(
|
|
196
|
+
!puzzleState.hasVisited(ROUTE_INTRO)) {
|
|
190
197
|
puzzleState.redirectToStartPath();
|
|
191
198
|
}
|
|
192
199
|
});
|
|
@@ -197,13 +204,13 @@ export const [createOrGetPuzzleState, createPuzzleState, getPuzzleState] =
|
|
|
197
204
|
});
|
|
198
205
|
</script>
|
|
199
206
|
|
|
200
|
-
{#if puzzleState.
|
|
207
|
+
{#if puzzleState.isOnIntro}
|
|
201
208
|
<IntroView onComplete={() => puzzleState.markTutorialComplete()} />
|
|
202
|
-
{:else if puzzleState.
|
|
209
|
+
{:else if puzzleState.isOnTutorial}
|
|
203
210
|
<TutorialView />
|
|
204
|
-
{:else if puzzleState.
|
|
211
|
+
{:else if puzzleState.isOnLevel1}
|
|
205
212
|
<Level1View gameLogic={puzzleState.gameLogic} />
|
|
206
|
-
{:else if puzzleState.
|
|
213
|
+
{:else if puzzleState.isOnLevel2}
|
|
207
214
|
<Level2View gameLogic={puzzleState.gameLogic} />
|
|
208
215
|
{:else if puzzleState.isComplete}
|
|
209
216
|
<CompleteView
|
|
@@ -212,6 +219,35 @@ export const [createOrGetPuzzleState, createPuzzleState, getPuzzleState] =
|
|
|
212
219
|
{/if}
|
|
213
220
|
```
|
|
214
221
|
|
|
222
|
+
## Animations and Page-Specific Logic
|
|
223
|
+
|
|
224
|
+
For page-specific animations or initialization, use `$effect` in your
|
|
225
|
+
`+page.svelte` files rather than centralized hooks:
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
// +page.svelte
|
|
229
|
+
<script>
|
|
230
|
+
import { PageAnimations } from './animations.svelte.js';
|
|
231
|
+
|
|
232
|
+
const animations = new PageAnimations();
|
|
233
|
+
|
|
234
|
+
// Trigger animations when conditions are met
|
|
235
|
+
$effect(() => {
|
|
236
|
+
if (someCondition) {
|
|
237
|
+
animations.start();
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Or use onMount for one-time initialization
|
|
242
|
+
onMount(() => {
|
|
243
|
+
animations.initialize();
|
|
244
|
+
});
|
|
245
|
+
</script>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
This approach keeps animation logic co-located with the components that
|
|
249
|
+
render them, making it easier to understand and maintain.
|
|
250
|
+
|
|
215
251
|
## Key Methods
|
|
216
252
|
|
|
217
253
|
All PageMachine methods are directly available on your state class:
|
|
@@ -220,30 +256,33 @@ All PageMachine methods are directly available on your state class:
|
|
|
220
256
|
// Sync with URL path
|
|
221
257
|
puzzleState.syncFromPath(currentPath)
|
|
222
258
|
|
|
223
|
-
// Get current
|
|
224
|
-
puzzleState.current
|
|
259
|
+
// Get current route
|
|
260
|
+
puzzleState.current // e.g., '/puzzle/level1'
|
|
225
261
|
|
|
226
262
|
// Start path management
|
|
227
263
|
puzzleState.startPath // Get start path
|
|
228
|
-
puzzleState.startState // Get start state
|
|
229
264
|
puzzleState.isStartPath(path) // Check if path is start path
|
|
230
|
-
puzzleState.
|
|
265
|
+
puzzleState.isOnStartPath // Check if on start path
|
|
231
266
|
puzzleState.redirectToStartPath() // Navigate to start path
|
|
232
267
|
|
|
233
|
-
// Get route for state
|
|
234
|
-
puzzleState.getPathForState(stateName)
|
|
235
|
-
|
|
236
268
|
// Persistent data properties
|
|
237
269
|
puzzleState.setData(KEY_NAME, value)
|
|
238
270
|
puzzleState.getData(KEY_NAME)
|
|
271
|
+
puzzleState.getAllData()
|
|
272
|
+
puzzleState.updateData({ KEY1: val1, KEY2: val2 })
|
|
273
|
+
|
|
274
|
+
// Visited routes tracking
|
|
275
|
+
puzzleState.hasVisited(route) // e.g., hasVisited('/puzzle/intro')
|
|
276
|
+
puzzleState.hasVisitedStart // Has visited start path
|
|
277
|
+
puzzleState.getVisitedRoutes()
|
|
278
|
+
puzzleState.resetVisitedRoutes()
|
|
239
279
|
|
|
240
|
-
//
|
|
241
|
-
puzzleState.
|
|
242
|
-
puzzleState.getVisitedStates()
|
|
280
|
+
// Get routes list
|
|
281
|
+
puzzleState.routes // Array of all routes
|
|
243
282
|
|
|
244
283
|
// Custom computed properties (from your class)
|
|
245
|
-
puzzleState.
|
|
246
|
-
puzzleState.
|
|
284
|
+
puzzleState.isOnIntro
|
|
285
|
+
puzzleState.isOnLevel1
|
|
247
286
|
puzzleState.hasSeenTutorial
|
|
248
287
|
```
|
|
249
288
|
|
|
@@ -259,20 +298,32 @@ Use PageMachine's data properties for **persistent settings and progress**:
|
|
|
259
298
|
- ✅ Settings that survive page navigation
|
|
260
299
|
- ✅ Data that might be saved to server
|
|
261
300
|
|
|
301
|
+
**IMPORTANT**: Use KEY_ constants for data keys to get:
|
|
302
|
+
- ✅ Autocomplete support
|
|
303
|
+
- ✅ Typo prevention
|
|
304
|
+
- ✅ Easy refactoring
|
|
305
|
+
- ✅ Self-documenting code
|
|
306
|
+
|
|
262
307
|
```javascript
|
|
308
|
+
// Define constants (at top of file)
|
|
263
309
|
const KEY_TUTORIAL_SEEN = 'tutorial-seen';
|
|
264
310
|
const KEY_DIFFICULTY = 'difficulty';
|
|
265
311
|
const KEY_HIGHEST_LEVEL = 'highest-level';
|
|
266
312
|
|
|
267
|
-
//
|
|
268
|
-
pageMachine.setData(KEY_TUTORIAL_SEEN, true);
|
|
269
|
-
pageMachine.setData(KEY_DIFFICULTY, 'hard');
|
|
270
|
-
pageMachine.setData(KEY_HIGHEST_LEVEL, 5);
|
|
313
|
+
// Use constants (not strings!)
|
|
314
|
+
pageMachine.setData(KEY_TUTORIAL_SEEN, true); // ✅ Good
|
|
315
|
+
pageMachine.setData(KEY_DIFFICULTY, 'hard'); // ✅ Good
|
|
316
|
+
pageMachine.setData(KEY_HIGHEST_LEVEL, 5); // ✅ Good
|
|
317
|
+
|
|
318
|
+
// DON'T use magic strings
|
|
319
|
+
pageMachine.setData('tutorial-seen', true); // ❌ Avoid
|
|
320
|
+
pageMachine.setData('TUTORIAL_SEEN', true); // ❌ Avoid
|
|
271
321
|
```
|
|
272
322
|
|
|
273
323
|
### When to use GameLogic with `$state`
|
|
274
324
|
|
|
275
|
-
Use a separate GameLogic class with `$state` fields for
|
|
325
|
+
Use a separate GameLogic class with `$state` fields for
|
|
326
|
+
**reactive game state**:
|
|
276
327
|
|
|
277
328
|
- ✅ Live scores, lives, health
|
|
278
329
|
- ✅ Current player, selected items
|
|
@@ -295,9 +346,10 @@ export class PuzzleGameLogic {
|
|
|
295
346
|
|
|
296
347
|
## Important Notes
|
|
297
348
|
|
|
298
|
-
-
|
|
299
|
-
-
|
|
300
|
-
- Use
|
|
349
|
+
- Does not enforce transitions - allows free navigation via browser
|
|
350
|
+
- Routes are the source of truth (no state abstraction layer)
|
|
351
|
+
- Use route constants for clarity and maintainability
|
|
301
352
|
- Always sync in `$effect` watching `$page.url.pathname`
|
|
302
|
-
- Use constants for data keys (e.g., `KEY_TUTORIAL_SEEN
|
|
303
|
-
- Separate persistent data (PageMachine) from reactive
|
|
353
|
+
- Use constants for data keys (e.g., `KEY_TUTORIAL_SEEN`)
|
|
354
|
+
- Separate persistent data (PageMachine) from reactive state (GameLogic)
|
|
355
|
+
- Handle animations in pages using `$effect` or `onMount`
|