@hkdigital/lib-core 0.5.88 → 0.5.91
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/state/classes/reactive-data-store/README.md +445 -0
- package/dist/state/classes/reactive-data-store/ReactiveDataStore.svelte.d.ts +152 -0
- package/dist/state/classes/reactive-data-store/ReactiveDataStore.svelte.js +300 -0
- package/dist/state/classes.d.ts +1 -0
- package/dist/state/classes.js +1 -0
- package/dist/state/machines/page-machine/PageMachine.svelte.d.ts +45 -232
- package/dist/state/machines/page-machine/PageMachine.svelte.js +61 -308
- package/dist/state/machines/page-machine/README.md +79 -20
- package/dist/ui/CHANGES-COMPONENT-PROPS.md +121 -0
- package/dist/ui/components/game-box/GameBox.svelte +10 -7
- package/dist/ui/components/game-box/GameBox.svelte.d.ts +2 -0
- package/dist/ui/components/game-box/ScaledContainer.svelte +14 -7
- package/dist/ui/components/grid-layers/GridLayers.svelte +9 -7
- package/dist/ui/components/grid-layers/GridLayers.svelte.d.ts +2 -0
- package/dist/ui/components/image-box/ImageBox.svelte +9 -7
- package/dist/ui/components/image-box/ImageBox.svelte.d.ts +2 -0
- package/dist/ui/components/presenter/Presenter.svelte +5 -3
- package/dist/ui/components/presenter/Presenter.svelte.d.ts +2 -0
- package/dist/ui/primitives/buttons/button/Button.svelte +9 -7
- package/dist/ui/primitives/buttons/button/Button.svelte.d.ts +2 -0
- package/dist/ui/primitives/icons/SteezeIcon.svelte +7 -5
- package/dist/ui/primitives/icons/SteezeIcon.svelte.d.ts +2 -0
- package/dist/ui/primitives/panels/panel/Panel.svelte +9 -7
- package/dist/ui/primitives/panels/panel/Panel.svelte.d.ts +2 -0
- package/package.json +1 -1
- package/dist/state/classes/subscribers-count/index.d.ts +0 -1
- package/dist/state/classes/subscribers-count/index.js +0 -1
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
* });
|
|
70
70
|
* ```
|
|
71
71
|
*/
|
|
72
|
-
import {
|
|
73
|
-
import {
|
|
72
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
73
|
+
import { ReactiveDataStore } from '../../classes.js';
|
|
74
74
|
|
|
75
75
|
export default class PageMachine {
|
|
76
76
|
/**
|
|
@@ -99,17 +99,14 @@ export default class PageMachine {
|
|
|
99
99
|
#routes = [];
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* Reactive
|
|
103
|
-
*
|
|
104
|
-
* @type {SvelteMap<string, any>}
|
|
102
|
+
* Reactive data store for business/domain data
|
|
103
|
+
* @type {ReactiveDataStore}
|
|
105
104
|
*/
|
|
106
105
|
#data;
|
|
107
106
|
|
|
108
107
|
/**
|
|
109
|
-
* Reactive
|
|
110
|
-
*
|
|
111
|
-
* Only available in dev mode
|
|
112
|
-
* @type {SvelteMap<string, any>|null}
|
|
108
|
+
* Reactive data store for dev-mode helper data
|
|
109
|
+
* @type {ReactiveDataStore}
|
|
113
110
|
*/
|
|
114
111
|
#devData;
|
|
115
112
|
|
|
@@ -130,6 +127,8 @@ export default class PageMachine {
|
|
|
130
127
|
* Optional list of valid routes (for validation/dev tools)
|
|
131
128
|
* @param {Record<string, any>} [config.initialData={}]
|
|
132
129
|
* Initial data properties (use KEY_ constants for keys)
|
|
130
|
+
* @param {Record<string, any>} [config.initialDevData={}]
|
|
131
|
+
* Initial dev data properties (use KEY_DEV_ constants for keys)
|
|
133
132
|
* @param {import('../../../logging/client.js').Logger} [config.logger]
|
|
134
133
|
* Logger instance (optional)
|
|
135
134
|
*
|
|
@@ -138,6 +137,7 @@ export default class PageMachine {
|
|
|
138
137
|
* const ROUTE_INTRO = '/intro/start';
|
|
139
138
|
* const KEY_INTRO_COMPLETED = 'intro-completed';
|
|
140
139
|
* const KEY_SCORE = 'score';
|
|
140
|
+
* const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
|
|
141
141
|
*
|
|
142
142
|
* const machine = new PageMachine({
|
|
143
143
|
* startPath: ROUTE_INTRO,
|
|
@@ -145,11 +145,14 @@ export default class PageMachine {
|
|
|
145
145
|
* initialData: {
|
|
146
146
|
* [KEY_INTRO_COMPLETED]: false,
|
|
147
147
|
* [KEY_SCORE]: 0
|
|
148
|
+
* },
|
|
149
|
+
* initialDevData: {
|
|
150
|
+
* [KEY_DEV_AUTO_NAVIGATION]: false
|
|
148
151
|
* }
|
|
149
152
|
* });
|
|
150
153
|
* ```
|
|
151
154
|
*/
|
|
152
|
-
constructor({ startPath, routes = [], initialData = {}, logger = null }) {
|
|
155
|
+
constructor({ startPath, routes = [], initialData = {}, initialDevData = {}, logger = null }) {
|
|
153
156
|
if (!startPath) {
|
|
154
157
|
throw new Error('PageMachine requires startPath parameter');
|
|
155
158
|
}
|
|
@@ -160,18 +163,17 @@ export default class PageMachine {
|
|
|
160
163
|
this.#current = startPath;
|
|
161
164
|
|
|
162
165
|
// Initialize reactive data structures
|
|
163
|
-
this.#data = new
|
|
164
|
-
|
|
166
|
+
this.#data = new ReactiveDataStore({
|
|
167
|
+
initialData
|
|
168
|
+
});
|
|
165
169
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
this.#devData = new ReactiveDataStore({
|
|
171
|
+
initialData: initialDevData,
|
|
172
|
+
productionGuard: true,
|
|
173
|
+
errorPrefix: 'Dev data key'
|
|
174
|
+
});
|
|
170
175
|
|
|
171
|
-
|
|
172
|
-
for (const [key, value] of Object.entries(initialData)) {
|
|
173
|
-
this.#data.set(key, value);
|
|
174
|
-
}
|
|
176
|
+
this.#visitedRoutes = new SvelteSet();
|
|
175
177
|
|
|
176
178
|
// Mark start path as visited
|
|
177
179
|
this.#visitedRoutes.add(startPath);
|
|
@@ -256,324 +258,75 @@ export default class PageMachine {
|
|
|
256
258
|
/* ===== Data Properties (Business/Domain State) ===== */
|
|
257
259
|
|
|
258
260
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
* Automatically reactive - effects watching this key will re-run.
|
|
262
|
-
* Uses fine-grained reactivity, so only effects watching this specific
|
|
263
|
-
* key will be triggered.
|
|
264
|
-
*
|
|
265
|
-
* @param {string} key - Property key (use KEY_ constant)
|
|
266
|
-
* @param {any} value - Property value
|
|
267
|
-
*
|
|
268
|
-
* @example
|
|
269
|
-
* ```javascript
|
|
270
|
-
* const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
|
|
271
|
-
* const KEY_PROFILE_SCORE = 'profile-score';
|
|
272
|
-
*
|
|
273
|
-
* machine.setData(KEY_HAS_STRONG_PROFILE, true);
|
|
274
|
-
* machine.setData(KEY_PROFILE_SCORE, 85);
|
|
275
|
-
* ```
|
|
276
|
-
*/
|
|
277
|
-
setData(key, value) {
|
|
278
|
-
this.#data.set(key, value);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Get a data property value
|
|
261
|
+
* Get the reactive data store
|
|
283
262
|
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* keys change.
|
|
263
|
+
* Provides read-only access to the data store instance.
|
|
264
|
+
* Access all data methods through this property.
|
|
287
265
|
*
|
|
288
|
-
* @
|
|
289
|
-
*
|
|
290
|
-
* @returns {any} Property value or undefined
|
|
266
|
+
* @returns {ReactiveDataStore} The data store
|
|
291
267
|
*
|
|
292
268
|
* @example
|
|
293
269
|
* ```javascript
|
|
294
270
|
* const KEY_SCORE = 'score';
|
|
295
271
|
*
|
|
296
|
-
* //
|
|
272
|
+
* // Set data
|
|
273
|
+
* machine.data.set(KEY_SCORE, 100);
|
|
274
|
+
*
|
|
275
|
+
* // Get data (reactive)
|
|
297
276
|
* $effect(() => {
|
|
298
|
-
* const score = machine.
|
|
277
|
+
* const score = machine.data.get(KEY_SCORE);
|
|
299
278
|
* console.log('Score:', score);
|
|
300
279
|
* });
|
|
301
|
-
* ```
|
|
302
|
-
*/
|
|
303
|
-
getData(key) {
|
|
304
|
-
return this.#data.get(key);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Get all data properties as plain object
|
|
309
|
-
*
|
|
310
|
-
* Note: This returns a snapshot (plain object), not a reactive map.
|
|
311
|
-
* Use this for serialization or server sync, not for reactive tracking.
|
|
312
|
-
*
|
|
313
|
-
* @returns {Record<string, any>} Plain object with all data
|
|
314
|
-
*
|
|
315
|
-
* @example
|
|
316
|
-
* ```javascript
|
|
317
|
-
* const allData = machine.getAllData();
|
|
318
|
-
* await playerService.saveData(allData);
|
|
319
|
-
* ```
|
|
320
|
-
*/
|
|
321
|
-
getAllData() {
|
|
322
|
-
return Object.fromEntries(this.#data);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Update multiple data properties at once
|
|
327
|
-
*
|
|
328
|
-
* Each property update triggers fine-grained reactivity.
|
|
329
|
-
*
|
|
330
|
-
* @param {Record<string, any>} dataUpdates
|
|
331
|
-
* Object with key-value pairs (use KEY_ constants for keys)
|
|
332
|
-
*
|
|
333
|
-
* @example
|
|
334
|
-
* ```javascript
|
|
335
|
-
* const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
|
|
336
|
-
* const KEY_PROFILE_SCORE = 'profile-score';
|
|
337
|
-
* const KEY_MATCHED_SECTOR = 'matched-sector';
|
|
338
|
-
*
|
|
339
|
-
* machine.updateData({
|
|
340
|
-
* [KEY_HAS_STRONG_PROFILE]: true,
|
|
341
|
-
* [KEY_PROFILE_SCORE]: 85,
|
|
342
|
-
* [KEY_MATCHED_SECTOR]: 'technology'
|
|
343
|
-
* });
|
|
344
|
-
* ```
|
|
345
|
-
*/
|
|
346
|
-
updateData(dataUpdates) {
|
|
347
|
-
for (const [key, value] of Object.entries(dataUpdates)) {
|
|
348
|
-
this.#data.set(key, value);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Delete a data property
|
|
354
|
-
*
|
|
355
|
-
* @param {string} key - Property key to delete (use KEY_ constant)
|
|
356
|
-
*
|
|
357
|
-
* @returns {boolean} True if the key existed and was deleted
|
|
358
280
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
* machine.
|
|
281
|
+
* // Other operations
|
|
282
|
+
* machine.data.update({ [KEY_SCORE]: 200 });
|
|
283
|
+
* machine.data.has(KEY_SCORE);
|
|
284
|
+
* machine.data.delete(KEY_SCORE);
|
|
285
|
+
* machine.data.clear();
|
|
286
|
+
* console.log(machine.data.size);
|
|
364
287
|
* ```
|
|
365
288
|
*/
|
|
366
|
-
|
|
367
|
-
return this.#data
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Check if data property exists
|
|
372
|
-
*
|
|
373
|
-
* @param {string} key - Property key to check (use KEY_ constant)
|
|
374
|
-
*
|
|
375
|
-
* @returns {boolean} True if the key exists
|
|
376
|
-
*
|
|
377
|
-
* @example
|
|
378
|
-
* ```javascript
|
|
379
|
-
* const KEY_TUTORIAL_SEEN = 'tutorial-seen';
|
|
380
|
-
*
|
|
381
|
-
* if (machine.hasData(KEY_TUTORIAL_SEEN)) {
|
|
382
|
-
* // Skip tutorial
|
|
383
|
-
* }
|
|
384
|
-
* ```
|
|
385
|
-
*/
|
|
386
|
-
hasData(key) {
|
|
387
|
-
return this.#data.has(key);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Clear all data properties
|
|
392
|
-
*
|
|
393
|
-
* @example
|
|
394
|
-
* ```javascript
|
|
395
|
-
* machine.clearData(); // Reset all game data
|
|
396
|
-
* ```
|
|
397
|
-
*/
|
|
398
|
-
clearData() {
|
|
399
|
-
this.#data.clear();
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Get number of data properties
|
|
404
|
-
*
|
|
405
|
-
* @returns {number} Number of data entries
|
|
406
|
-
*/
|
|
407
|
-
get dataSize() {
|
|
408
|
-
return this.#data.size;
|
|
289
|
+
get data() {
|
|
290
|
+
return this.#data;
|
|
409
291
|
}
|
|
410
292
|
|
|
411
293
|
/* ===== Dev Data Properties (Dev-Mode Helpers) ===== */
|
|
412
294
|
|
|
413
295
|
/**
|
|
414
|
-
*
|
|
296
|
+
* Get the reactive dev data store
|
|
415
297
|
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
298
|
+
* Provides read-only access to the dev data store instance.
|
|
299
|
+
* Access all dev data methods through this property.
|
|
418
300
|
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
301
|
+
* Dev data is only available in development mode. In production:
|
|
302
|
+
* - SET operations are silent no-ops
|
|
303
|
+
* - GET/HAS operations throw errors (programming errors)
|
|
421
304
|
*
|
|
422
|
-
* @
|
|
423
|
-
* ```javascript
|
|
424
|
-
* const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
|
|
425
|
-
* const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
|
|
426
|
-
*
|
|
427
|
-
* machine.setDevData(KEY_DEV_AUTO_NAVIGATION, true);
|
|
428
|
-
* machine.setDevData(KEY_DEV_SKIP_ANIMATIONS, false);
|
|
429
|
-
* ```
|
|
430
|
-
*/
|
|
431
|
-
setDevData(key, value) {
|
|
432
|
-
if (!dev) return;
|
|
433
|
-
this.#devData.set(key, value);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Get a dev data property value
|
|
438
|
-
*
|
|
439
|
-
* Automatically reactive - creates a dependency on this specific key.
|
|
440
|
-
* Only available in dev mode - returns undefined in production.
|
|
441
|
-
*
|
|
442
|
-
* @param {string} key - Property key (use KEY_DEV_ constant)
|
|
443
|
-
*
|
|
444
|
-
* @returns {any} Property value or undefined
|
|
305
|
+
* @returns {ReactiveDataStore} The dev data store
|
|
445
306
|
*
|
|
446
307
|
* @example
|
|
447
308
|
* ```javascript
|
|
448
|
-
* const
|
|
309
|
+
* const KEY_DEV_AUTO_NAV = 'dev-auto-navigation';
|
|
310
|
+
*
|
|
311
|
+
* // Set dev data (no-op in production)
|
|
312
|
+
* machine.devData.set(KEY_DEV_AUTO_NAV, true);
|
|
449
313
|
*
|
|
450
|
-
* //
|
|
314
|
+
* // Get dev data (throws in production)
|
|
451
315
|
* $effect(() => {
|
|
452
|
-
* const autoNav = machine.
|
|
453
|
-
* console.log('Auto-
|
|
316
|
+
* const autoNav = machine.devData.get(KEY_DEV_AUTO_NAV);
|
|
317
|
+
* console.log('Auto-nav:', autoNav);
|
|
454
318
|
* });
|
|
455
|
-
* ```
|
|
456
|
-
*/
|
|
457
|
-
getDevData(key) {
|
|
458
|
-
if (!dev) return undefined;
|
|
459
|
-
return this.#devData.get(key);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Get all dev data properties as plain object
|
|
464
|
-
*
|
|
465
|
-
* Note: Returns a snapshot (plain object), not reactive.
|
|
466
|
-
* Only available in dev mode - returns empty object in production.
|
|
467
|
-
*
|
|
468
|
-
* @returns {Record<string, any>} Plain object with all dev data
|
|
469
319
|
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
320
|
+
* // Other operations
|
|
321
|
+
* machine.devData.update({ [KEY_DEV_AUTO_NAV]: false });
|
|
322
|
+
* machine.devData.has(KEY_DEV_AUTO_NAV);
|
|
323
|
+
* machine.devData.delete(KEY_DEV_AUTO_NAV);
|
|
324
|
+
* machine.devData.clear();
|
|
325
|
+
* console.log(machine.devData.size);
|
|
474
326
|
* ```
|
|
475
327
|
*/
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
return Object.fromEntries(this.#devData);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Update multiple dev data properties at once
|
|
483
|
-
*
|
|
484
|
-
* Each property update triggers fine-grained reactivity.
|
|
485
|
-
* Only available in dev mode - no-op in production.
|
|
486
|
-
*
|
|
487
|
-
* @param {Record<string, any>} dataUpdates
|
|
488
|
-
* Object with key-value pairs (use KEY_DEV_ constants for keys)
|
|
489
|
-
*
|
|
490
|
-
* @example
|
|
491
|
-
* ```javascript
|
|
492
|
-
* const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
|
|
493
|
-
* const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
|
|
494
|
-
*
|
|
495
|
-
* machine.updateDevData({
|
|
496
|
-
* [KEY_DEV_AUTO_NAVIGATION]: true,
|
|
497
|
-
* [KEY_DEV_SKIP_ANIMATIONS]: false
|
|
498
|
-
* });
|
|
499
|
-
* ```
|
|
500
|
-
*/
|
|
501
|
-
updateDevData(dataUpdates) {
|
|
502
|
-
if (!dev) return;
|
|
503
|
-
for (const [key, value] of Object.entries(dataUpdates)) {
|
|
504
|
-
this.#devData.set(key, value);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Delete a dev data property
|
|
510
|
-
*
|
|
511
|
-
* Only available in dev mode - no-op in production.
|
|
512
|
-
*
|
|
513
|
-
* @param {string} key - Property key to delete (use KEY_DEV_ constant)
|
|
514
|
-
*
|
|
515
|
-
* @returns {boolean} True if the key existed and was deleted
|
|
516
|
-
*
|
|
517
|
-
* @example
|
|
518
|
-
* ```javascript
|
|
519
|
-
* const KEY_DEV_TEMP_FLAG = 'dev-temp-flag';
|
|
520
|
-
*
|
|
521
|
-
* machine.deleteDevData(KEY_DEV_TEMP_FLAG);
|
|
522
|
-
* ```
|
|
523
|
-
*/
|
|
524
|
-
deleteDevData(key) {
|
|
525
|
-
if (!dev) return false;
|
|
526
|
-
return this.#devData.delete(key);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* Check if dev data property exists
|
|
531
|
-
*
|
|
532
|
-
* Only available in dev mode - returns false in production.
|
|
533
|
-
*
|
|
534
|
-
* @param {string} key - Property key to check (use KEY_DEV_ constant)
|
|
535
|
-
*
|
|
536
|
-
* @returns {boolean} True if the key exists
|
|
537
|
-
*
|
|
538
|
-
* @example
|
|
539
|
-
* ```javascript
|
|
540
|
-
* const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
|
|
541
|
-
*
|
|
542
|
-
* if (machine.hasDevData(KEY_DEV_AUTO_NAVIGATION)) {
|
|
543
|
-
* // Dev setting exists
|
|
544
|
-
* }
|
|
545
|
-
* ```
|
|
546
|
-
*/
|
|
547
|
-
hasDevData(key) {
|
|
548
|
-
if (!dev) return false;
|
|
549
|
-
return this.#devData.has(key);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Clear all dev data properties
|
|
554
|
-
*
|
|
555
|
-
* Only available in dev mode - no-op in production.
|
|
556
|
-
*
|
|
557
|
-
* @example
|
|
558
|
-
* ```javascript
|
|
559
|
-
* machine.clearDevData(); // Reset all dev settings
|
|
560
|
-
* ```
|
|
561
|
-
*/
|
|
562
|
-
clearDevData() {
|
|
563
|
-
if (!dev) return;
|
|
564
|
-
this.#devData.clear();
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* Get number of dev data properties
|
|
569
|
-
*
|
|
570
|
-
* Only available in dev mode - returns 0 in production.
|
|
571
|
-
*
|
|
572
|
-
* @returns {number} Number of dev data entries
|
|
573
|
-
*/
|
|
574
|
-
get devDataSize() {
|
|
575
|
-
if (!dev) return 0;
|
|
576
|
-
return this.#devData.size;
|
|
328
|
+
get devData() {
|
|
329
|
+
return this.#devData;
|
|
577
330
|
}
|
|
578
331
|
|
|
579
332
|
/* ===== Visited Routes Tracking ===== */
|
|
@@ -78,6 +78,10 @@ const KEY_TUTORIAL_SEEN = 'tutorial-seen';
|
|
|
78
78
|
const KEY_HIGHEST_LEVEL = 'highest-level';
|
|
79
79
|
const KEY_DIFFICULTY = 'difficulty';
|
|
80
80
|
|
|
81
|
+
// Dev data keys (use KEY_DEV_ prefix)
|
|
82
|
+
const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
|
|
83
|
+
const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
|
|
84
|
+
|
|
81
85
|
export class PuzzleState extends PageMachine {
|
|
82
86
|
#logic;
|
|
83
87
|
|
|
@@ -96,6 +100,10 @@ export class PuzzleState extends PageMachine {
|
|
|
96
100
|
[KEY_TUTORIAL_SEEN]: false,
|
|
97
101
|
[KEY_HIGHEST_LEVEL]: 1,
|
|
98
102
|
[KEY_DIFFICULTY]: 'normal'
|
|
103
|
+
},
|
|
104
|
+
initialDevData: {
|
|
105
|
+
[KEY_DEV_AUTO_NAVIGATION]: false,
|
|
106
|
+
[KEY_DEV_SKIP_ANIMATIONS]: false
|
|
99
107
|
}
|
|
100
108
|
});
|
|
101
109
|
|
|
@@ -127,32 +135,32 @@ export class PuzzleState extends PageMachine {
|
|
|
127
135
|
return this.current === ROUTE_COMPLETE;
|
|
128
136
|
}
|
|
129
137
|
|
|
130
|
-
// Persistent settings/progress (use
|
|
138
|
+
// Persistent settings/progress (use machine.data)
|
|
131
139
|
get hasSeenTutorial() {
|
|
132
|
-
return this.
|
|
140
|
+
return this.data.get(KEY_TUTORIAL_SEEN) || false;
|
|
133
141
|
}
|
|
134
142
|
|
|
135
143
|
markTutorialComplete() {
|
|
136
|
-
this.
|
|
144
|
+
this.data.set(KEY_TUTORIAL_SEEN, true);
|
|
137
145
|
}
|
|
138
146
|
|
|
139
147
|
get highestLevel() {
|
|
140
|
-
return this.
|
|
148
|
+
return this.data.get(KEY_HIGHEST_LEVEL) || 1;
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
updateHighestLevel(level) {
|
|
144
152
|
const current = this.highestLevel;
|
|
145
153
|
if (level > current) {
|
|
146
|
-
this.
|
|
154
|
+
this.data.set(KEY_HIGHEST_LEVEL, level);
|
|
147
155
|
}
|
|
148
156
|
}
|
|
149
157
|
|
|
150
158
|
get difficulty() {
|
|
151
|
-
return this.
|
|
159
|
+
return this.data.get(KEY_DIFFICULTY) || 'normal';
|
|
152
160
|
}
|
|
153
161
|
|
|
154
162
|
setDifficulty(level) {
|
|
155
|
-
this.
|
|
163
|
+
this.data.set(KEY_DIFFICULTY, level);
|
|
156
164
|
}
|
|
157
165
|
|
|
158
166
|
// Optional: Lifecycle methods
|
|
@@ -265,11 +273,25 @@ puzzleState.isStartPath(path) // Check if path is start path
|
|
|
265
273
|
puzzleState.isOnStartPath // Check if on start path
|
|
266
274
|
puzzleState.redirectToStartPath() // Navigate to start path
|
|
267
275
|
|
|
268
|
-
// Persistent data properties
|
|
269
|
-
puzzleState.
|
|
270
|
-
puzzleState.
|
|
271
|
-
puzzleState.
|
|
272
|
-
puzzleState.
|
|
276
|
+
// Persistent data properties (via ReactiveDataStore)
|
|
277
|
+
puzzleState.data.set(KEY_NAME, value)
|
|
278
|
+
puzzleState.data.get(KEY_NAME)
|
|
279
|
+
puzzleState.data.getAll()
|
|
280
|
+
puzzleState.data.update({ KEY1: val1, KEY2: val2 })
|
|
281
|
+
puzzleState.data.has(KEY_NAME)
|
|
282
|
+
puzzleState.data.delete(KEY_NAME)
|
|
283
|
+
puzzleState.data.clear()
|
|
284
|
+
puzzleState.data.size
|
|
285
|
+
|
|
286
|
+
// Dev data properties (dev-only, via ReactiveDataStore)
|
|
287
|
+
puzzleState.devData.set(KEY_DEV_NAME, value)
|
|
288
|
+
puzzleState.devData.get(KEY_DEV_NAME)
|
|
289
|
+
puzzleState.devData.getAll()
|
|
290
|
+
puzzleState.devData.update({ KEY1: val1 })
|
|
291
|
+
puzzleState.devData.has(KEY_DEV_NAME)
|
|
292
|
+
puzzleState.devData.delete(KEY_DEV_NAME)
|
|
293
|
+
puzzleState.devData.clear()
|
|
294
|
+
puzzleState.devData.size
|
|
273
295
|
|
|
274
296
|
// Visited routes tracking
|
|
275
297
|
puzzleState.hasVisited(route) // e.g., hasVisited('/puzzle/intro')
|
|
@@ -288,9 +310,9 @@ puzzleState.hasSeenTutorial
|
|
|
288
310
|
|
|
289
311
|
## Data Storage Guidelines
|
|
290
312
|
|
|
291
|
-
### When to use `
|
|
313
|
+
### When to use `machine.data` (ReactiveDataStore)
|
|
292
314
|
|
|
293
|
-
Use PageMachine's data
|
|
315
|
+
Use PageMachine's data store for **persistent settings and progress**:
|
|
294
316
|
|
|
295
317
|
- ✅ Tutorial completion flags
|
|
296
318
|
- ✅ User preferences (difficulty, language, sound)
|
|
@@ -311,13 +333,39 @@ const KEY_DIFFICULTY = 'difficulty';
|
|
|
311
333
|
const KEY_HIGHEST_LEVEL = 'highest-level';
|
|
312
334
|
|
|
313
335
|
// Use constants (not strings!)
|
|
314
|
-
pageMachine.
|
|
315
|
-
pageMachine.
|
|
316
|
-
pageMachine.
|
|
336
|
+
pageMachine.data.set(KEY_TUTORIAL_SEEN, true); // ✅ Good
|
|
337
|
+
pageMachine.data.set(KEY_DIFFICULTY, 'hard'); // ✅ Good
|
|
338
|
+
pageMachine.data.set(KEY_HIGHEST_LEVEL, 5); // ✅ Good
|
|
317
339
|
|
|
318
340
|
// DON'T use magic strings
|
|
319
|
-
pageMachine.
|
|
320
|
-
pageMachine.
|
|
341
|
+
pageMachine.data.set('tutorial-seen', true); // ❌ Avoid
|
|
342
|
+
pageMachine.data.set('TUTORIAL_SEEN', true); // ❌ Avoid
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### When to use `machine.devData` (Dev-only ReactiveDataStore)
|
|
346
|
+
|
|
347
|
+
Use PageMachine's devData store for **development helpers**:
|
|
348
|
+
|
|
349
|
+
- ✅ Auto-navigation flags
|
|
350
|
+
- ✅ Skip animations/delays
|
|
351
|
+
- ✅ Mock API endpoints
|
|
352
|
+
- ✅ Debug mode flags
|
|
353
|
+
- ✅ Development-only settings
|
|
354
|
+
|
|
355
|
+
**IMPORTANT**: Use KEY_DEV_ prefix for dev data constants:
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
// Define constants (at top of file)
|
|
359
|
+
const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
|
|
360
|
+
const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
|
|
361
|
+
const KEY_DEV_MOCK_API = 'dev-mock-api';
|
|
362
|
+
|
|
363
|
+
// Use in development (no-op in production)
|
|
364
|
+
pageMachine.devData.set(KEY_DEV_AUTO_NAVIGATION, true); // ✅ Good
|
|
365
|
+
pageMachine.devData.set(KEY_DEV_SKIP_ANIMATIONS, false); // ✅ Good
|
|
366
|
+
|
|
367
|
+
// Reading in production throws error (programming bug)
|
|
368
|
+
const autoNav = pageMachine.devData.get(KEY_DEV_AUTO_NAVIGATION);
|
|
321
369
|
```
|
|
322
370
|
|
|
323
371
|
### When to use GameLogic with `$state`
|
|
@@ -350,6 +398,17 @@ export class PuzzleGameLogic {
|
|
|
350
398
|
- Routes are the source of truth (no state abstraction layer)
|
|
351
399
|
- Use route constants for clarity and maintainability
|
|
352
400
|
- Always sync in `$effect` watching `$page.url.pathname`
|
|
353
|
-
- Use constants for data keys (e.g., `KEY_TUTORIAL_SEEN`)
|
|
401
|
+
- Use constants for data keys (e.g., `KEY_TUTORIAL_SEEN`, `KEY_DEV_AUTO_NAV`)
|
|
354
402
|
- Separate persistent data (PageMachine) from reactive state (GameLogic)
|
|
355
403
|
- Handle animations in pages using `$effect` or `onMount`
|
|
404
|
+
- Data and devData use [ReactiveDataStore](../../classes/reactive-data-store/README.md)
|
|
405
|
+
for fine-grained reactivity
|
|
406
|
+
- Access data via read-only getters: `machine.data.get()`, `machine.devData.set()`
|
|
407
|
+
|
|
408
|
+
## See Also
|
|
409
|
+
|
|
410
|
+
- [ReactiveDataStore](../../classes/reactive-data-store/README.md) - The
|
|
411
|
+
underlying reactive data storage implementation
|
|
412
|
+
- [FiniteStateMachine](../finite-state-machine/README.md) - For enforced state
|
|
413
|
+
transitions
|
|
414
|
+
- [State Context](../../context/README.md) - For providing state to components
|