react-chess-explorer 0.0.4 → 0.0.5
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/features/explorer/hooks/useGameReplayTraining.d.ts +1 -0
- package/dist/features/explorer/hooks/usePositionHistory.d.ts +6 -1
- package/dist/features/explorer/hooks/usePositionReferenceData.d.ts +1 -0
- package/dist/features/explorer/positionUtils.d.ts +1 -0
- package/dist/index.esm.js +30 -15
- package/dist/index.js +29 -14
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ export declare function useGameReplayTraining({ gameId, startFen, fetchGame, }:
|
|
|
16
16
|
error: string | null;
|
|
17
17
|
feedback: GameReplayFeedback;
|
|
18
18
|
lastExpectedSan: string | null;
|
|
19
|
+
lastMoveUci: any;
|
|
19
20
|
expectedSan: string | undefined;
|
|
20
21
|
handlePieceDrop: (sourceSquare: string, targetSquare: string, piece: string) => boolean;
|
|
21
22
|
revealMove: () => void;
|
|
@@ -2,6 +2,8 @@ export type PositionHistoryEntry = {
|
|
|
2
2
|
fen: string;
|
|
3
3
|
/** SAN of the move played from the previous entry to reach this FEN. */
|
|
4
4
|
lastSan?: string;
|
|
5
|
+
/** UCI of the move played from the previous entry to reach this FEN. */
|
|
6
|
+
lastUci?: string;
|
|
5
7
|
};
|
|
6
8
|
export declare function initialHistoryState(initialFen: string, initialLineSans?: string[]): {
|
|
7
9
|
history: PositionHistoryEntry[];
|
|
@@ -13,14 +15,17 @@ export declare function usePositionHistory(initialFen: string, initialLineSans?:
|
|
|
13
15
|
lineSans: string[];
|
|
14
16
|
forwardSans: string[];
|
|
15
17
|
currentFen: string;
|
|
16
|
-
|
|
18
|
+
lastMoveUci: string | null;
|
|
19
|
+
pushEntry: (fen: string, lastSan?: string, lastUci?: string) => PositionHistoryEntry;
|
|
17
20
|
pushEntries: (entries: {
|
|
18
21
|
fen: string;
|
|
19
22
|
lastSan: string;
|
|
23
|
+
lastUci?: string;
|
|
20
24
|
}[]) => void;
|
|
21
25
|
replaceLineEntries: (startFen: string, entries: {
|
|
22
26
|
fen: string;
|
|
23
27
|
lastSan: string;
|
|
28
|
+
lastUci?: string;
|
|
24
29
|
}[]) => void;
|
|
25
30
|
goBack: () => PositionHistoryEntry | null;
|
|
26
31
|
goForward: () => PositionHistoryEntry | null;
|
|
@@ -28,6 +28,7 @@ export declare function usePositionReferenceData({ fenProp, onFenChange, initial
|
|
|
28
28
|
canGoForward: boolean;
|
|
29
29
|
forwardSans: string[];
|
|
30
30
|
selectedVariationKey: string | undefined;
|
|
31
|
+
lastMoveUci: string | null;
|
|
31
32
|
setSources: import("react").Dispatch<import("react").SetStateAction<GameSource[]>>;
|
|
32
33
|
setGamesMoveFilterUci: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
|
|
33
34
|
handleMoveSelect: (move: PositionMoveApiDto) => void;
|
|
@@ -10,6 +10,7 @@ export declare function applyBoardMove(fen: string, sourceSquare: string, target
|
|
|
10
10
|
export type LineSansEntry = {
|
|
11
11
|
fen: string;
|
|
12
12
|
lastSan: string;
|
|
13
|
+
lastUci: string;
|
|
13
14
|
};
|
|
14
15
|
/** Play a SAN sequence from a start FEN; returns null if any move is illegal. */
|
|
15
16
|
export declare function applyLineSans(startFen: string, sans: string[]): {
|
package/dist/index.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Chess } from 'chess.js';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
-
import { ThemeProvider, HighlightChessboard, usePositionKeyboardNav, ChessboardDnDProvider } from 'react-chess-core';
|
|
3
|
+
import { ThemeProvider, HighlightChessboard, usePositionKeyboardNav, ChessboardDnDProvider, lastMoveUciAtPly } from 'react-chess-core';
|
|
4
4
|
import { useEffect, useState, useCallback, useRef, useMemo } from 'react';
|
|
5
5
|
|
|
6
6
|
/** Standard start position — placeholder until game replay is implemented. */
|
|
@@ -39,6 +39,7 @@ function applyBoardMove(fen, sourceSquare, targetSquare, piece) {
|
|
|
39
39
|
}
|
|
40
40
|
/** Play a SAN sequence from a start FEN; returns null if any move is illegal. */
|
|
41
41
|
function applyLineSans(startFen, sans) {
|
|
42
|
+
var _a;
|
|
42
43
|
const chess = new Chess(startFen);
|
|
43
44
|
const entries = [];
|
|
44
45
|
for (const san of sans) {
|
|
@@ -46,9 +47,10 @@ function applyLineSans(startFen, sans) {
|
|
|
46
47
|
const move = chess.move(san);
|
|
47
48
|
if (!move)
|
|
48
49
|
return null;
|
|
49
|
-
|
|
50
|
+
const lastUci = `${move.from}${move.to}${(_a = move.promotion) !== null && _a !== void 0 ? _a : ""}`;
|
|
51
|
+
entries.push({ fen: chess.fen(), lastSan: move.san, lastUci });
|
|
50
52
|
}
|
|
51
|
-
catch (
|
|
53
|
+
catch (_b) {
|
|
52
54
|
return null;
|
|
53
55
|
}
|
|
54
56
|
}
|
|
@@ -523,19 +525,19 @@ function initialHistoryState(initialFen, initialLineSans) {
|
|
|
523
525
|
};
|
|
524
526
|
}
|
|
525
527
|
function usePositionHistory(initialFen, initialLineSans) {
|
|
526
|
-
var _a, _b;
|
|
528
|
+
var _a, _b, _c, _d;
|
|
527
529
|
const [history, setHistory] = useState(() => initialHistoryState(initialFen, initialLineSans).history);
|
|
528
530
|
const [historyIndex, setHistoryIndex] = useState(() => initialHistoryState(initialFen, initialLineSans).historyIndex);
|
|
529
531
|
const canGoBack = historyIndex > 0;
|
|
530
532
|
const canGoForward = historyIndex < history.length - 1;
|
|
531
|
-
const pushEntry = useCallback((fen, lastSan) => {
|
|
533
|
+
const pushEntry = useCallback((fen, lastSan, lastUci) => {
|
|
532
534
|
setHistory((prev) => {
|
|
533
535
|
const trimmed = prev.slice(0, historyIndex + 1);
|
|
534
|
-
const next = [...trimmed, { fen, lastSan }];
|
|
536
|
+
const next = [...trimmed, { fen, lastSan, lastUci }];
|
|
535
537
|
setHistoryIndex(next.length - 1);
|
|
536
538
|
return next;
|
|
537
539
|
});
|
|
538
|
-
return { fen, lastSan };
|
|
540
|
+
return { fen, lastSan, lastUci };
|
|
539
541
|
}, [historyIndex]);
|
|
540
542
|
const goBack = useCallback(() => {
|
|
541
543
|
if (historyIndex <= 0)
|
|
@@ -596,12 +598,14 @@ function usePositionHistory(initialFen, initialLineSans) {
|
|
|
596
598
|
setHistory([{ fen: startFen }, ...entries]);
|
|
597
599
|
setHistoryIndex(entries.length);
|
|
598
600
|
}, []);
|
|
601
|
+
const lastMoveUci = historyIndex > 0 ? (_d = (_c = history[historyIndex]) === null || _c === void 0 ? void 0 : _c.lastUci) !== null && _d !== void 0 ? _d : null : null;
|
|
599
602
|
return {
|
|
600
603
|
canGoBack,
|
|
601
604
|
canGoForward,
|
|
602
605
|
lineSans,
|
|
603
606
|
forwardSans,
|
|
604
607
|
currentFen,
|
|
608
|
+
lastMoveUci,
|
|
605
609
|
pushEntry,
|
|
606
610
|
pushEntries,
|
|
607
611
|
replaceLineEntries,
|
|
@@ -646,7 +650,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
646
650
|
}
|
|
647
651
|
isAnimatingVariationRef.current = false;
|
|
648
652
|
}, []);
|
|
649
|
-
const { canGoBack, canGoForward, lineSans, forwardSans, currentFen, pushEntry, pushEntries, replaceLineEntries, goBack, goForward, goFirst, goLast, resetHistory, } = usePositionHistory(initialFen, initialLineSans);
|
|
653
|
+
const { canGoBack, canGoForward, lineSans, forwardSans, currentFen, pushEntry, pushEntries, replaceLineEntries, goBack, goForward, goFirst, goLast, resetHistory, lastMoveUci: historyLastMoveUci, } = usePositionHistory(initialFen, initialLineSans);
|
|
650
654
|
/** FEN used for explorer API queries — follows move history, not animation frames. */
|
|
651
655
|
const queryFen = currentFen;
|
|
652
656
|
const initialLineKey = useMemo(() => { var _a; return (_a = initialLineSans === null || initialLineSans === void 0 ? void 0 : initialLineSans.join("|")) !== null && _a !== void 0 ? _a : ""; }, [initialLineSans]);
|
|
@@ -681,6 +685,10 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
681
685
|
}), [sources]);
|
|
682
686
|
const lastAppliedLineKeyRef = useRef(initialLineKey);
|
|
683
687
|
useEffect(() => () => cancelVariationAnimation(), [cancelVariationAnimation]);
|
|
688
|
+
// Hold URL line callbacks until history catches up after an external line change.
|
|
689
|
+
useEffect(() => {
|
|
690
|
+
readyForLineSyncRef.current = false;
|
|
691
|
+
}, [initialLineKey]);
|
|
684
692
|
// Apply URL line changes only when the external line key changes — not when the
|
|
685
693
|
// user clicks moves (internal lineSans updates must not re-sync from stale props).
|
|
686
694
|
useEffect(() => {
|
|
@@ -891,7 +899,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
891
899
|
const nextFen = fenAfterUci(queryFen, move.uci);
|
|
892
900
|
if (!nextFen)
|
|
893
901
|
return;
|
|
894
|
-
pushEntry(nextFen, move.san);
|
|
902
|
+
pushEntry(nextFen, move.san, move.uci);
|
|
895
903
|
setSelectedVariationKey(undefined);
|
|
896
904
|
applyNavigation(nextFen, true);
|
|
897
905
|
}, [queryFen, pushEntry, applyNavigation, cancelVariationAnimation]);
|
|
@@ -905,7 +913,11 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
905
913
|
const nextFen = fenAfterUci(lineFen, line.uciPath[i]);
|
|
906
914
|
if (!nextFen)
|
|
907
915
|
return;
|
|
908
|
-
entries.push({
|
|
916
|
+
entries.push({
|
|
917
|
+
fen: nextFen,
|
|
918
|
+
lastSan: line.moves[i].san,
|
|
919
|
+
lastUci: line.uciPath[i],
|
|
920
|
+
});
|
|
909
921
|
lineFen = nextFen;
|
|
910
922
|
}
|
|
911
923
|
if (entries.length === 0)
|
|
@@ -940,7 +952,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
940
952
|
const result = applyBoardMove(queryFen, sourceSquare, targetSquare, piece);
|
|
941
953
|
if (!result)
|
|
942
954
|
return false;
|
|
943
|
-
pushEntry(result.fen, result.san);
|
|
955
|
+
pushEntry(result.fen, result.san, result.uci);
|
|
944
956
|
setSelectedVariationKey(undefined);
|
|
945
957
|
applyNavigation(result.fen, true);
|
|
946
958
|
return true;
|
|
@@ -1017,6 +1029,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
1017
1029
|
canGoForward,
|
|
1018
1030
|
forwardSans,
|
|
1019
1031
|
selectedVariationKey,
|
|
1032
|
+
lastMoveUci: historyLastMoveUci,
|
|
1020
1033
|
setSources,
|
|
1021
1034
|
setGamesMoveFilterUci,
|
|
1022
1035
|
handleMoveSelect,
|
|
@@ -1115,7 +1128,7 @@ const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineS
|
|
|
1115
1128
|
fetchPositionGames,
|
|
1116
1129
|
fetchPositionVariations,
|
|
1117
1130
|
});
|
|
1118
|
-
const { fen, boardFen, games, sources, lineSans, loading, showPositionLoading, gamesLoading, positionReady, displayMoves, error, lineLabel, canGoBack, canGoForward, forwardSans, selectedVariationKey, setSources, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, handleFirst, handleLast, } = referenceData;
|
|
1131
|
+
const { fen, boardFen, games, sources, lineSans, loading, showPositionLoading, gamesLoading, positionReady, displayMoves, error, lineLabel, canGoBack, canGoForward, forwardSans, selectedVariationKey, lastMoveUci, setSources, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, handleFirst, handleLast, } = referenceData;
|
|
1119
1132
|
usePositionKeyboardNav({
|
|
1120
1133
|
enabled: keyboardNav,
|
|
1121
1134
|
canPrev: canGoBack,
|
|
@@ -1179,7 +1192,7 @@ const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineS
|
|
|
1179
1192
|
overflow: fillHeight ? "hidden" : "visible",
|
|
1180
1193
|
boxSizing: "border-box",
|
|
1181
1194
|
};
|
|
1182
|
-
const board = (jsxs(Fragment, { children: [jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, position: boardFen, boardOrientation: boardOrientation, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }, boardOrientation) }), renderBoardNav(boardNavProps)] }));
|
|
1195
|
+
const board = (jsxs(Fragment, { children: [jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, position: boardFen, boardOrientation: boardOrientation, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, lastMoveUci: lastMoveUci, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }, boardOrientation) }), renderBoardNav(boardNavProps)] }));
|
|
1183
1196
|
const referencePanel = (jsx(DefaultReferencePanel, { theme: theme, fillHeight: fillHeight, status: renderStatus({
|
|
1184
1197
|
error,
|
|
1185
1198
|
loading: showPositionLoading,
|
|
@@ -1301,6 +1314,7 @@ function useGameReplayTraining({ gameId, startFen, fetchGame, }) {
|
|
|
1301
1314
|
const complete = game ? plyIndex >= game.movesUci.length : false;
|
|
1302
1315
|
const expectedUci = game === null || game === void 0 ? void 0 : game.movesUci[plyIndex];
|
|
1303
1316
|
const expectedSan = game === null || game === void 0 ? void 0 : game.movesSan[plyIndex];
|
|
1317
|
+
const lastMoveUci = useMemo(() => (game ? lastMoveUciAtPly(game.movesUci, plyIndex) : null), [game, plyIndex]);
|
|
1304
1318
|
const handlePieceDrop = useCallback((sourceSquare, targetSquare, piece) => {
|
|
1305
1319
|
if (!game || complete || !expectedUci)
|
|
1306
1320
|
return false;
|
|
@@ -1334,6 +1348,7 @@ function useGameReplayTraining({ gameId, startFen, fetchGame, }) {
|
|
|
1334
1348
|
error,
|
|
1335
1349
|
feedback,
|
|
1336
1350
|
lastExpectedSan,
|
|
1351
|
+
lastMoveUci,
|
|
1337
1352
|
expectedSan,
|
|
1338
1353
|
handlePieceDrop,
|
|
1339
1354
|
revealMove,
|
|
@@ -1354,7 +1369,7 @@ const defaultButtonStyle = {
|
|
|
1354
1369
|
cursor: "pointer",
|
|
1355
1370
|
};
|
|
1356
1371
|
const GameReplayTrainer = ({ gameId, startFen, fetchGame, onExit, theme = "dark", boardWidth = DEFAULT_REFERENCE_LAYOUT.boardWidth, renderStatus, }) => {
|
|
1357
|
-
const { game, fen, plyIndex, totalPlies, complete, loading, error, feedback, lastExpectedSan, handlePieceDrop, revealMove, } = useGameReplayTraining({ gameId, startFen, fetchGame });
|
|
1372
|
+
const { game, fen, plyIndex, totalPlies, complete, loading, error, feedback, lastExpectedSan, lastMoveUci, handlePieceDrop, revealMove, } = useGameReplayTraining({ gameId, startFen, fetchGame });
|
|
1358
1373
|
const status = renderStatus === null || renderStatus === void 0 ? void 0 : renderStatus({
|
|
1359
1374
|
loading,
|
|
1360
1375
|
error,
|
|
@@ -1374,7 +1389,7 @@ const GameReplayTrainer = ({ gameId, startFen, fetchGame, onExit, theme = "dark"
|
|
|
1374
1389
|
!error &&
|
|
1375
1390
|
!complete &&
|
|
1376
1391
|
feedback === "incorrect" &&
|
|
1377
|
-
lastExpectedSan && (jsxs("span", { style: { color: "#ef6c00" }, children: ["Expected ", lastExpectedSan] })), !loading && !error && !complete && feedback === null && (jsxs("span", { children: ["Guess move ", plyIndex + 1, " of ", totalPlies] }))] })), jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, position: fen, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }) }), !complete && !loading && !error && (jsx("button", { type: "button", style: defaultButtonStyle, onClick: revealMove, children: "Show move" }))] }) }));
|
|
1392
|
+
lastExpectedSan && (jsxs("span", { style: { color: "#ef6c00" }, children: ["Expected ", lastExpectedSan] })), !loading && !error && !complete && feedback === null && (jsxs("span", { children: ["Guess move ", plyIndex + 1, " of ", totalPlies] }))] })), jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, position: fen, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, lastMoveUci: lastMoveUci, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }) }), !complete && !loading && !error && (jsx("button", { type: "button", style: defaultButtonStyle, onClick: revealMove, children: "Show move" }))] }) }));
|
|
1378
1393
|
};
|
|
1379
1394
|
|
|
1380
1395
|
const mockPosition = {
|
package/dist/index.js
CHANGED
|
@@ -41,6 +41,7 @@ function applyBoardMove(fen, sourceSquare, targetSquare, piece) {
|
|
|
41
41
|
}
|
|
42
42
|
/** Play a SAN sequence from a start FEN; returns null if any move is illegal. */
|
|
43
43
|
function applyLineSans(startFen, sans) {
|
|
44
|
+
var _a;
|
|
44
45
|
const chess = new chess_js.Chess(startFen);
|
|
45
46
|
const entries = [];
|
|
46
47
|
for (const san of sans) {
|
|
@@ -48,9 +49,10 @@ function applyLineSans(startFen, sans) {
|
|
|
48
49
|
const move = chess.move(san);
|
|
49
50
|
if (!move)
|
|
50
51
|
return null;
|
|
51
|
-
|
|
52
|
+
const lastUci = `${move.from}${move.to}${(_a = move.promotion) !== null && _a !== void 0 ? _a : ""}`;
|
|
53
|
+
entries.push({ fen: chess.fen(), lastSan: move.san, lastUci });
|
|
52
54
|
}
|
|
53
|
-
catch (
|
|
55
|
+
catch (_b) {
|
|
54
56
|
return null;
|
|
55
57
|
}
|
|
56
58
|
}
|
|
@@ -525,19 +527,19 @@ function initialHistoryState(initialFen, initialLineSans) {
|
|
|
525
527
|
};
|
|
526
528
|
}
|
|
527
529
|
function usePositionHistory(initialFen, initialLineSans) {
|
|
528
|
-
var _a, _b;
|
|
530
|
+
var _a, _b, _c, _d;
|
|
529
531
|
const [history, setHistory] = react.useState(() => initialHistoryState(initialFen, initialLineSans).history);
|
|
530
532
|
const [historyIndex, setHistoryIndex] = react.useState(() => initialHistoryState(initialFen, initialLineSans).historyIndex);
|
|
531
533
|
const canGoBack = historyIndex > 0;
|
|
532
534
|
const canGoForward = historyIndex < history.length - 1;
|
|
533
|
-
const pushEntry = react.useCallback((fen, lastSan) => {
|
|
535
|
+
const pushEntry = react.useCallback((fen, lastSan, lastUci) => {
|
|
534
536
|
setHistory((prev) => {
|
|
535
537
|
const trimmed = prev.slice(0, historyIndex + 1);
|
|
536
|
-
const next = [...trimmed, { fen, lastSan }];
|
|
538
|
+
const next = [...trimmed, { fen, lastSan, lastUci }];
|
|
537
539
|
setHistoryIndex(next.length - 1);
|
|
538
540
|
return next;
|
|
539
541
|
});
|
|
540
|
-
return { fen, lastSan };
|
|
542
|
+
return { fen, lastSan, lastUci };
|
|
541
543
|
}, [historyIndex]);
|
|
542
544
|
const goBack = react.useCallback(() => {
|
|
543
545
|
if (historyIndex <= 0)
|
|
@@ -598,12 +600,14 @@ function usePositionHistory(initialFen, initialLineSans) {
|
|
|
598
600
|
setHistory([{ fen: startFen }, ...entries]);
|
|
599
601
|
setHistoryIndex(entries.length);
|
|
600
602
|
}, []);
|
|
603
|
+
const lastMoveUci = historyIndex > 0 ? (_d = (_c = history[historyIndex]) === null || _c === void 0 ? void 0 : _c.lastUci) !== null && _d !== void 0 ? _d : null : null;
|
|
601
604
|
return {
|
|
602
605
|
canGoBack,
|
|
603
606
|
canGoForward,
|
|
604
607
|
lineSans,
|
|
605
608
|
forwardSans,
|
|
606
609
|
currentFen,
|
|
610
|
+
lastMoveUci,
|
|
607
611
|
pushEntry,
|
|
608
612
|
pushEntries,
|
|
609
613
|
replaceLineEntries,
|
|
@@ -648,7 +652,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
648
652
|
}
|
|
649
653
|
isAnimatingVariationRef.current = false;
|
|
650
654
|
}, []);
|
|
651
|
-
const { canGoBack, canGoForward, lineSans, forwardSans, currentFen, pushEntry, pushEntries, replaceLineEntries, goBack, goForward, goFirst, goLast, resetHistory, } = usePositionHistory(initialFen, initialLineSans);
|
|
655
|
+
const { canGoBack, canGoForward, lineSans, forwardSans, currentFen, pushEntry, pushEntries, replaceLineEntries, goBack, goForward, goFirst, goLast, resetHistory, lastMoveUci: historyLastMoveUci, } = usePositionHistory(initialFen, initialLineSans);
|
|
652
656
|
/** FEN used for explorer API queries — follows move history, not animation frames. */
|
|
653
657
|
const queryFen = currentFen;
|
|
654
658
|
const initialLineKey = react.useMemo(() => { var _a; return (_a = initialLineSans === null || initialLineSans === void 0 ? void 0 : initialLineSans.join("|")) !== null && _a !== void 0 ? _a : ""; }, [initialLineSans]);
|
|
@@ -683,6 +687,10 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
683
687
|
}), [sources]);
|
|
684
688
|
const lastAppliedLineKeyRef = react.useRef(initialLineKey);
|
|
685
689
|
react.useEffect(() => () => cancelVariationAnimation(), [cancelVariationAnimation]);
|
|
690
|
+
// Hold URL line callbacks until history catches up after an external line change.
|
|
691
|
+
react.useEffect(() => {
|
|
692
|
+
readyForLineSyncRef.current = false;
|
|
693
|
+
}, [initialLineKey]);
|
|
686
694
|
// Apply URL line changes only when the external line key changes — not when the
|
|
687
695
|
// user clicks moves (internal lineSans updates must not re-sync from stale props).
|
|
688
696
|
react.useEffect(() => {
|
|
@@ -893,7 +901,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
893
901
|
const nextFen = fenAfterUci(queryFen, move.uci);
|
|
894
902
|
if (!nextFen)
|
|
895
903
|
return;
|
|
896
|
-
pushEntry(nextFen, move.san);
|
|
904
|
+
pushEntry(nextFen, move.san, move.uci);
|
|
897
905
|
setSelectedVariationKey(undefined);
|
|
898
906
|
applyNavigation(nextFen, true);
|
|
899
907
|
}, [queryFen, pushEntry, applyNavigation, cancelVariationAnimation]);
|
|
@@ -907,7 +915,11 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
907
915
|
const nextFen = fenAfterUci(lineFen, line.uciPath[i]);
|
|
908
916
|
if (!nextFen)
|
|
909
917
|
return;
|
|
910
|
-
entries.push({
|
|
918
|
+
entries.push({
|
|
919
|
+
fen: nextFen,
|
|
920
|
+
lastSan: line.moves[i].san,
|
|
921
|
+
lastUci: line.uciPath[i],
|
|
922
|
+
});
|
|
911
923
|
lineFen = nextFen;
|
|
912
924
|
}
|
|
913
925
|
if (entries.length === 0)
|
|
@@ -942,7 +954,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
942
954
|
const result = applyBoardMove(queryFen, sourceSquare, targetSquare, piece);
|
|
943
955
|
if (!result)
|
|
944
956
|
return false;
|
|
945
|
-
pushEntry(result.fen, result.san);
|
|
957
|
+
pushEntry(result.fen, result.san, result.uci);
|
|
946
958
|
setSelectedVariationKey(undefined);
|
|
947
959
|
applyNavigation(result.fen, true);
|
|
948
960
|
return true;
|
|
@@ -1019,6 +1031,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
1019
1031
|
canGoForward,
|
|
1020
1032
|
forwardSans,
|
|
1021
1033
|
selectedVariationKey,
|
|
1034
|
+
lastMoveUci: historyLastMoveUci,
|
|
1022
1035
|
setSources,
|
|
1023
1036
|
setGamesMoveFilterUci,
|
|
1024
1037
|
handleMoveSelect,
|
|
@@ -1117,7 +1130,7 @@ const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineS
|
|
|
1117
1130
|
fetchPositionGames,
|
|
1118
1131
|
fetchPositionVariations,
|
|
1119
1132
|
});
|
|
1120
|
-
const { fen, boardFen, games, sources, lineSans, loading, showPositionLoading, gamesLoading, positionReady, displayMoves, error, lineLabel, canGoBack, canGoForward, forwardSans, selectedVariationKey, setSources, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, handleFirst, handleLast, } = referenceData;
|
|
1133
|
+
const { fen, boardFen, games, sources, lineSans, loading, showPositionLoading, gamesLoading, positionReady, displayMoves, error, lineLabel, canGoBack, canGoForward, forwardSans, selectedVariationKey, lastMoveUci, setSources, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, handleFirst, handleLast, } = referenceData;
|
|
1121
1134
|
reactChessCore.usePositionKeyboardNav({
|
|
1122
1135
|
enabled: keyboardNav,
|
|
1123
1136
|
canPrev: canGoBack,
|
|
@@ -1181,7 +1194,7 @@ const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineS
|
|
|
1181
1194
|
overflow: fillHeight ? "hidden" : "visible",
|
|
1182
1195
|
boxSizing: "border-box",
|
|
1183
1196
|
};
|
|
1184
|
-
const board = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, position: boardFen, boardOrientation: boardOrientation, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }, boardOrientation) }), renderBoardNav(boardNavProps)] }));
|
|
1197
|
+
const board = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, position: boardFen, boardOrientation: boardOrientation, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, lastMoveUci: lastMoveUci, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }, boardOrientation) }), renderBoardNav(boardNavProps)] }));
|
|
1185
1198
|
const referencePanel = (jsxRuntime.jsx(DefaultReferencePanel, { theme: theme, fillHeight: fillHeight, status: renderStatus({
|
|
1186
1199
|
error,
|
|
1187
1200
|
loading: showPositionLoading,
|
|
@@ -1303,6 +1316,7 @@ function useGameReplayTraining({ gameId, startFen, fetchGame, }) {
|
|
|
1303
1316
|
const complete = game ? plyIndex >= game.movesUci.length : false;
|
|
1304
1317
|
const expectedUci = game === null || game === void 0 ? void 0 : game.movesUci[plyIndex];
|
|
1305
1318
|
const expectedSan = game === null || game === void 0 ? void 0 : game.movesSan[plyIndex];
|
|
1319
|
+
const lastMoveUci = react.useMemo(() => (game ? reactChessCore.lastMoveUciAtPly(game.movesUci, plyIndex) : null), [game, plyIndex]);
|
|
1306
1320
|
const handlePieceDrop = react.useCallback((sourceSquare, targetSquare, piece) => {
|
|
1307
1321
|
if (!game || complete || !expectedUci)
|
|
1308
1322
|
return false;
|
|
@@ -1336,6 +1350,7 @@ function useGameReplayTraining({ gameId, startFen, fetchGame, }) {
|
|
|
1336
1350
|
error,
|
|
1337
1351
|
feedback,
|
|
1338
1352
|
lastExpectedSan,
|
|
1353
|
+
lastMoveUci,
|
|
1339
1354
|
expectedSan,
|
|
1340
1355
|
handlePieceDrop,
|
|
1341
1356
|
revealMove,
|
|
@@ -1356,7 +1371,7 @@ const defaultButtonStyle = {
|
|
|
1356
1371
|
cursor: "pointer",
|
|
1357
1372
|
};
|
|
1358
1373
|
const GameReplayTrainer = ({ gameId, startFen, fetchGame, onExit, theme = "dark", boardWidth = DEFAULT_REFERENCE_LAYOUT.boardWidth, renderStatus, }) => {
|
|
1359
|
-
const { game, fen, plyIndex, totalPlies, complete, loading, error, feedback, lastExpectedSan, handlePieceDrop, revealMove, } = useGameReplayTraining({ gameId, startFen, fetchGame });
|
|
1374
|
+
const { game, fen, plyIndex, totalPlies, complete, loading, error, feedback, lastExpectedSan, lastMoveUci, handlePieceDrop, revealMove, } = useGameReplayTraining({ gameId, startFen, fetchGame });
|
|
1360
1375
|
const status = renderStatus === null || renderStatus === void 0 ? void 0 : renderStatus({
|
|
1361
1376
|
loading,
|
|
1362
1377
|
error,
|
|
@@ -1376,7 +1391,7 @@ const GameReplayTrainer = ({ gameId, startFen, fetchGame, onExit, theme = "dark"
|
|
|
1376
1391
|
!error &&
|
|
1377
1392
|
!complete &&
|
|
1378
1393
|
feedback === "incorrect" &&
|
|
1379
|
-
lastExpectedSan && (jsxRuntime.jsxs("span", { style: { color: "#ef6c00" }, children: ["Expected ", lastExpectedSan] })), !loading && !error && !complete && feedback === null && (jsxRuntime.jsxs("span", { children: ["Guess move ", plyIndex + 1, " of ", totalPlies] }))] })), jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, position: fen, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }) }), !complete && !loading && !error && (jsxRuntime.jsx("button", { type: "button", style: defaultButtonStyle, onClick: revealMove, children: "Show move" }))] }) }));
|
|
1394
|
+
lastExpectedSan && (jsxRuntime.jsxs("span", { style: { color: "#ef6c00" }, children: ["Expected ", lastExpectedSan] })), !loading && !error && !complete && feedback === null && (jsxRuntime.jsxs("span", { children: ["Guess move ", plyIndex + 1, " of ", totalPlies] }))] })), jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, position: fen, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, lastMoveUci: lastMoveUci, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }) }), !complete && !loading && !error && (jsxRuntime.jsx("button", { type: "button", style: defaultButtonStyle, onClick: revealMove, children: "Show move" }))] }) }));
|
|
1380
1395
|
};
|
|
1381
1396
|
|
|
1382
1397
|
const mockPosition = {
|
package/package.json
CHANGED