feat(chess): add localStorage auto-save and restore (P3.14)
This commit is contained in:
parent
3fdeb5822d
commit
a75b6f041b
4 changed files with 43 additions and 2 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useEffect } from 'react'
|
||||||
import { Routes, Route, useNavigate } from 'react-router-dom'
|
import { Routes, Route, useNavigate } from 'react-router-dom'
|
||||||
import { GameView } from '../ui/GameView'
|
import { GameView } from '../ui/GameView'
|
||||||
import { RulesView } from '../ui/RulesView'
|
import { RulesView } from '../ui/RulesView'
|
||||||
|
|
@ -5,11 +6,23 @@ import { SavePanel } from '../ui/SavePanel'
|
||||||
import { ImportExport } from '../ui/ImportExport'
|
import { ImportExport } from '../ui/ImportExport'
|
||||||
import { useChessEngine } from '../hooks/useChessEngine'
|
import { useChessEngine } from '../hooks/useChessEngine'
|
||||||
import { ChessEngine } from '../engine'
|
import { ChessEngine } from '../engine'
|
||||||
|
import { loadAutoSave } from '../persist/autosave.js'
|
||||||
import type { AttrKey, FactValue, EntityId } from '@paratype/rete'
|
import type { AttrKey, FactValue, EntityId } from '@paratype/rete'
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const chessState = useChessEngine()
|
const chessState = useChessEngine()
|
||||||
|
|
||||||
|
// Restore autosave on mount — [] deps is intentional (run once, avoid infinite loop)
|
||||||
|
useEffect(() => {
|
||||||
|
const saved = loadAutoSave();
|
||||||
|
if (!saved) return;
|
||||||
|
const newEngine = new ChessEngine();
|
||||||
|
const oldFacts = newEngine.session.allFacts();
|
||||||
|
for (const f of oldFacts) newEngine.session.retract(f.id, f.attr as AttrKey);
|
||||||
|
for (const f of saved) newEngine.session.insert(f.id as EntityId, f.attr as AttrKey, f.value as FactValue);
|
||||||
|
chessState.loadEngine(newEngine);
|
||||||
|
}, []); // intentional: run once on mount, chessState ref is stable
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="app-root">
|
<div data-testid="app-root">
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useState, useRef, useCallback } from 'react';
|
import { useState, useRef, useCallback } from 'react';
|
||||||
import { ChessEngine, type GameResult } from '../engine';
|
import { ChessEngine, type GameResult } from '../engine';
|
||||||
import type { PieceType } from '../schema';
|
import type { PieceType } from '../schema';
|
||||||
|
import { saveAutoSave } from '../persist/autosave.js';
|
||||||
|
|
||||||
export function useChessEngine() {
|
export function useChessEngine() {
|
||||||
const engineRef = useRef<ChessEngine | null>(null);
|
const engineRef = useRef<ChessEngine | null>(null);
|
||||||
|
|
@ -19,6 +20,7 @@ export function useChessEngine() {
|
||||||
const loadEngine = useCallback((newEngine: ChessEngine) => {
|
const loadEngine = useCallback((newEngine: ChessEngine) => {
|
||||||
engineRef.current = newEngine;
|
engineRef.current = newEngine;
|
||||||
moveHistoryRef.current = [];
|
moveHistoryRef.current = [];
|
||||||
|
saveAutoSave(newEngine.session.allFacts());
|
||||||
setTick(t => t + 1);
|
setTick(t => t + 1);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -43,6 +45,7 @@ export function useChessEngine() {
|
||||||
if (move) {
|
if (move) {
|
||||||
moveHistoryRef.current.push({ from, to, promoteTo });
|
moveHistoryRef.current.push({ from, to, promoteTo });
|
||||||
const result = engine.applyMove(move, promoteTo);
|
const result = engine.applyMove(move, promoteTo);
|
||||||
|
saveAutoSave(engine.session.allFacts());
|
||||||
setTick(t => t + 1); // trigger re-render
|
setTick(t => t + 1); // trigger re-render
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -60,6 +63,7 @@ export function useChessEngine() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
engineRef.current = newEngine;
|
engineRef.current = newEngine;
|
||||||
|
saveAutoSave(newEngine.session.allFacts());
|
||||||
setTick(t => t + 1);
|
setTick(t => t + 1);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
||||||
23
packages/chess/src/persist/autosave.ts
Normal file
23
packages/chess/src/persist/autosave.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { exportGame, importGame } from './io.js';
|
||||||
|
|
||||||
|
const AUTOSAVE_KEY = 'paratype-chess:v1:autosave';
|
||||||
|
|
||||||
|
export function saveAutoSave(facts: Array<{ id: number; attr: string; value: unknown }>): void {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(AUTOSAVE_KEY, exportGame(facts));
|
||||||
|
} catch { /* ignore quota errors */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadAutoSave(): Array<{ id: number; attr: string; value: unknown }> | null {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(AUTOSAVE_KEY);
|
||||||
|
if (!raw) return null;
|
||||||
|
return importGame(raw);
|
||||||
|
} catch {
|
||||||
|
return null; // silently discard invalid/stale/version-mismatched data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearAutoSave(): void {
|
||||||
|
localStorage.removeItem(AUTOSAVE_KEY);
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
"composite": true
|
"composite": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "tests/**/*"]
|
"include": ["src/**/*"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue