react-chess-explorer 0.0.1 → 0.0.2
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/components/PositionGamesPanel.d.ts +4 -2
- package/dist/features/explorer/core/renderProps.d.ts +3 -1
- package/dist/features/explorer/hooks/usePositionReferenceData.d.ts +3 -1
- package/dist/features/explorer/index.d.ts +2 -1
- package/dist/features/explorer/types.d.ts +5 -1
- package/dist/index.esm.js +32 -6
- package/dist/index.js +32 -5
- package/dist/stories/PositionReferenceExplorer.stories.d.ts +10 -0
- package/dist/stories/fixtures/nc6MockApi.d.ts +13 -0
- package/dist/stories/fixtures/nc6SampleGames.d.ts +39 -0
- package/package.json +16 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PositionGameRowApiDto } from "../types";
|
|
1
|
+
import type { GameSource, PositionGameRowApiDto } from "../types";
|
|
2
2
|
export type PositionGamesPanelProps = {
|
|
3
3
|
games: PositionGameRowApiDto[];
|
|
4
4
|
lineLabel: string;
|
|
@@ -7,9 +7,11 @@ export type PositionGamesPanelProps = {
|
|
|
7
7
|
defaultMinElo: number;
|
|
8
8
|
defaultMaxElo: number;
|
|
9
9
|
topOnly: boolean;
|
|
10
|
+
sources: GameSource[];
|
|
10
11
|
onMinEloChange: (value: number) => void;
|
|
11
12
|
onMaxEloChange: (value: number) => void;
|
|
12
13
|
onTopOnlyChange: (value: boolean) => void;
|
|
14
|
+
onSourcesChange: (sources: GameSource[]) => void;
|
|
13
15
|
onGameSelect?: (game: PositionGameRowApiDto) => void;
|
|
14
16
|
};
|
|
15
|
-
export declare const PositionGamesPanel: ({ games, lineLabel, minElo, maxElo, defaultMinElo, defaultMaxElo, topOnly, onMinEloChange, onMaxEloChange, onTopOnlyChange, onGameSelect, }: PositionGamesPanelProps) => import("react").JSX.Element;
|
|
17
|
+
export declare const PositionGamesPanel: ({ games, lineLabel, minElo, maxElo, defaultMinElo, defaultMaxElo, topOnly, sources, onMinEloChange, onMaxEloChange, onTopOnlyChange, onSourcesChange, onGameSelect, }: PositionGamesPanelProps) => import("react").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import type { FetchPositionGamesParams, FetchPositionVariationsParams, PositionApiDto, PositionGameRowApiDto, PositionGamesApiDto, PositionMoveApiDto, PositionVariationLineApiDto, PositionVariationsApiDto } from "../types";
|
|
2
|
+
import type { FetchPositionGamesParams, FetchPositionVariationsParams, GameSource, PositionApiDto, PositionGameRowApiDto, PositionGamesApiDto, PositionMoveApiDto, PositionVariationLineApiDto, PositionVariationsApiDto } from "../types";
|
|
3
3
|
import type { VariationsTab } from "../variationLines";
|
|
4
4
|
export type ReferenceLayoutRenderProps = {
|
|
5
5
|
theme: "light" | "dark";
|
|
@@ -33,9 +33,11 @@ export type GamesPanelRenderProps = {
|
|
|
33
33
|
defaultMinElo: number;
|
|
34
34
|
defaultMaxElo: number;
|
|
35
35
|
topOnly: boolean;
|
|
36
|
+
sources: GameSource[];
|
|
36
37
|
onMinEloChange: (value: number) => void;
|
|
37
38
|
onMaxEloChange: (value: number) => void;
|
|
38
39
|
onTopOnlyChange: (value: boolean) => void;
|
|
40
|
+
onSourcesChange: (sources: GameSource[]) => void;
|
|
39
41
|
/** Start replay training for a database game from the current board FEN. */
|
|
40
42
|
onGameSelect?: (game: PositionGameRowApiDto) => void;
|
|
41
43
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type FetchPositionGamesParams, type GameSource, type PositionApiDto, type PositionGamesApiDto, type PositionMoveApiDto } from "../types";
|
|
2
2
|
import type { PositionVariationLineApiDto } from "../types";
|
|
3
3
|
import type { VariationsTab } from "../variationLines";
|
|
4
4
|
export type UsePositionReferenceDataOptions = {
|
|
@@ -20,6 +20,7 @@ export declare function usePositionReferenceData({ fenProp, onFenChange, initial
|
|
|
20
20
|
minElo: number;
|
|
21
21
|
maxElo: number;
|
|
22
22
|
topOnly: boolean;
|
|
23
|
+
sources: GameSource[];
|
|
23
24
|
loading: boolean;
|
|
24
25
|
error: string | null;
|
|
25
26
|
lineLabel: string;
|
|
@@ -31,6 +32,7 @@ export declare function usePositionReferenceData({ fenProp, onFenChange, initial
|
|
|
31
32
|
setMinElo: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
32
33
|
setMaxElo: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
33
34
|
setTopOnly: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
35
|
+
setSources: import("react").Dispatch<import("react").SetStateAction<GameSource[]>>;
|
|
34
36
|
setVariationsTab: import("react").Dispatch<import("react").SetStateAction<VariationsTab>>;
|
|
35
37
|
setGamesMoveFilterUci: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
|
|
36
38
|
handleMoveSelect: (move: PositionMoveApiDto) => void;
|
|
@@ -8,7 +8,8 @@ export { DEFAULT_REFERENCE_LAYOUT, DefaultReferenceLayout, DefaultReferencePanel
|
|
|
8
8
|
export type { ReferenceLayoutConfig } from "./referenceLayout";
|
|
9
9
|
export { GameReplayTrainer, type GameReplayTrainerProps, } from "./GameReplayTrainer";
|
|
10
10
|
export { useGameReplayTraining } from "./hooks/useGameReplayTraining";
|
|
11
|
-
export type { PositionApiDto, PositionGamesApiDto, PositionGameRowApiDto, PositionMoveApiDto, PositionVariationLineApiDto, PositionVariationsApiDto, FetchPositionGamesParams, FetchPositionVariationsParams, ExplorerGameReplayApiDto, } from "./types";
|
|
11
|
+
export type { GameSource, PositionApiDto, PositionGamesApiDto, PositionGameRowApiDto, PositionMoveApiDto, PositionVariationLineApiDto, PositionVariationsApiDto, FetchPositionGamesParams, FetchPositionVariationsParams, ExplorerGameReplayApiDto, } from "./types";
|
|
12
|
+
export { ALL_GAME_SOURCES } from "./types";
|
|
12
13
|
export { mockFetchPosition, mockFetchPositionGames, mockFetchPositionVariations, } from "./mocks";
|
|
13
14
|
export { applyLineSans, fenAfterUci, normalizeFen, whiteScorePercent, } from "./positionUtils";
|
|
14
15
|
export { isVariationLineActive } from "./variationLines";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type GameSource = "lichess" | "twic";
|
|
2
|
+
export declare const ALL_GAME_SOURCES: GameSource[];
|
|
1
3
|
/** Align with endchess-backend GET /positions */
|
|
2
4
|
export type PositionMoveApiDto = {
|
|
3
5
|
san: string;
|
|
@@ -13,7 +15,6 @@ export type PositionApiDto = {
|
|
|
13
15
|
fen: string;
|
|
14
16
|
totalGames: number;
|
|
15
17
|
moves: PositionMoveApiDto[];
|
|
16
|
-
sampleGameIds: string[];
|
|
17
18
|
};
|
|
18
19
|
/** Align with endchess-backend GET /positions/games */
|
|
19
20
|
export type PositionGameRowApiDto = {
|
|
@@ -31,6 +32,7 @@ export type PositionGameRowApiDto = {
|
|
|
31
32
|
nextSan: string;
|
|
32
33
|
nextUci: string;
|
|
33
34
|
avgElo: number;
|
|
35
|
+
source: GameSource;
|
|
34
36
|
};
|
|
35
37
|
export type PositionGamesApiDto = {
|
|
36
38
|
positionKey: string;
|
|
@@ -48,6 +50,7 @@ export type FetchPositionGamesParams = {
|
|
|
48
50
|
uci?: string;
|
|
49
51
|
topOnly: boolean;
|
|
50
52
|
limit?: number;
|
|
53
|
+
sources?: GameSource[];
|
|
51
54
|
};
|
|
52
55
|
/** Align with endchess-backend GET /positions/variations */
|
|
53
56
|
export type PositionVariationLineApiDto = {
|
|
@@ -91,6 +94,7 @@ export type ExplorerGameReplayApiDto = {
|
|
|
91
94
|
event?: string;
|
|
92
95
|
timeControl?: string;
|
|
93
96
|
timeClass?: string;
|
|
97
|
+
source: GameSource;
|
|
94
98
|
movesUci: string[];
|
|
95
99
|
movesSan: string[];
|
|
96
100
|
};
|
package/dist/index.esm.js
CHANGED
|
@@ -284,12 +284,24 @@ const linkStyle = {
|
|
|
284
284
|
textUnderlineOffset: 2,
|
|
285
285
|
};
|
|
286
286
|
const LichessGameLink = ({ url, name }) => (jsx("a", { href: url, target: "_blank", rel: "noopener noreferrer", style: linkStyle, children: name }));
|
|
287
|
-
const GamesTable = ({ games, onGameSelect }) => (jsxs("table", { style: tableStyle, children: [jsx("thead", { children: jsxs("tr", { children: [jsx("th", { style: thStyle, children: "White" }), jsx("th", { style: thStyle, children: "Elo" }), jsx("th", { style: thStyle, children: "Black" }), jsx("th", { style: thStyle, children: "Elo" }), jsx("th", { style: thStyle, children: "Result" }), onGameSelect && jsx("th", { style: thStyle })] }) }), jsx("tbody", { children: games.length === 0 ? (jsx("tr", { children: jsx("td", { colSpan: onGameSelect ?
|
|
287
|
+
const GamesTable = ({ games, onGameSelect }) => (jsxs("table", { style: tableStyle, children: [jsx("thead", { children: jsxs("tr", { children: [jsx("th", { style: thStyle, children: "White" }), jsx("th", { style: thStyle, children: "Elo" }), jsx("th", { style: thStyle, children: "Black" }), jsx("th", { style: thStyle, children: "Elo" }), jsx("th", { style: thStyle, children: "Result" }), jsx("th", { style: thStyle, children: "Source" }), onGameSelect && jsx("th", { style: thStyle })] }) }), jsx("tbody", { children: games.length === 0 ? (jsx("tr", { children: jsx("td", { colSpan: onGameSelect ? 7 : 6, style: Object.assign(Object.assign({}, tdStyle), { opacity: 0.7, fontStyle: "italic" }), children: "No games match this position and filter. Widen the Elo range or turn off Top games." }) })) : (games.map((g) => (jsxs("tr", { children: [jsx("td", { style: tdStyle, children: jsx(LichessGameLink, { url: g.url, name: g.white }) }), jsx("td", { style: tdStyle, children: g.whiteElo }), jsx("td", { style: tdStyle, children: jsx(LichessGameLink, { url: g.url, name: g.black }) }), jsx("td", { style: tdStyle, children: g.blackElo }), jsx("td", { style: tdStyle, children: g.result }), jsx("td", { style: tdStyle, children: g.source === "twic" ? "Master" : "Lichess" }), onGameSelect && (jsx("td", { style: tdStyle, children: jsx("button", { type: "button", onClick: () => onGameSelect(g), children: "Train" }) }))] }, g.gameId)))) })] }));
|
|
288
288
|
|
|
289
289
|
const mainLineTitleStyle = {
|
|
290
290
|
fontWeight: 600,
|
|
291
291
|
};
|
|
292
|
-
const PositionGamesPanel = ({ games, lineLabel, minElo, maxElo, defaultMinElo, defaultMaxElo, topOnly, onMinEloChange, onMaxEloChange, onTopOnlyChange,
|
|
292
|
+
const PositionGamesPanel = ({ games, lineLabel, minElo, maxElo, defaultMinElo, defaultMaxElo, topOnly, sources, onMinEloChange, onMaxEloChange, onTopOnlyChange, onSourcesChange, onGameSelect, }) => {
|
|
293
|
+
const toggleSource = (source) => {
|
|
294
|
+
if (sources.includes(source)) {
|
|
295
|
+
if (sources.length === 1) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
onSourcesChange(sources.filter((value) => value !== source));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
onSourcesChange([...sources, source]);
|
|
302
|
+
};
|
|
303
|
+
return (jsxs("div", { style: gamesSectionStyle, children: [jsx("div", { style: gamesHeaderStyle, children: lineLabel ? (jsxs(Fragment, { children: [jsx("span", { style: mainLineTitleStyle, children: "Main line: " }), lineLabel, jsxs("span", { style: { opacity: 0.75 }, children: [" (", games.length, " games)"] })] })) : (jsxs("span", { children: ["Games ", jsxs("span", { style: { opacity: 0.75 }, children: ["(", games.length, ")"] })] })) }), jsx("div", { style: gamesScrollStyle, children: jsx(GamesTable, { games: games, onGameSelect: onGameSelect }) }), jsxs("div", { style: gamesToolbarStyle, children: [jsx(EloRangeFilter, { minElo: minElo, maxElo: maxElo, defaultMinElo: defaultMinElo, defaultMaxElo: defaultMaxElo, onMinEloChange: onMinEloChange, onMaxEloChange: onMaxEloChange }), jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [jsx("input", { type: "checkbox", checked: topOnly, onChange: (e) => onTopOnlyChange(e.target.checked) }), "Top games"] }), jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [jsx("input", { type: "checkbox", checked: sources.includes("lichess"), onChange: () => toggleSource("lichess") }), "Lichess"] }), jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [jsx("input", { type: "checkbox", checked: sources.includes("twic"), onChange: () => toggleSource("twic") }), "Master"] })] })] }));
|
|
304
|
+
};
|
|
293
305
|
|
|
294
306
|
const DefaultReferenceLayout = ({ board, referencePanel, }) => (jsxs("div", { style: referenceShellStyle, children: [jsx("div", { style: boardColumnStyle, children: board }), referencePanel] }));
|
|
295
307
|
const defaultRenderLayout = (props) => (jsx(DefaultReferenceLayout, Object.assign({}, props)));
|
|
@@ -367,6 +379,8 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
367
379
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
368
380
|
};
|
|
369
381
|
|
|
382
|
+
const ALL_GAME_SOURCES = ["lichess", "twic"];
|
|
383
|
+
|
|
370
384
|
function usePositionHistory(initialFen) {
|
|
371
385
|
const [history, setHistory] = useState([
|
|
372
386
|
{ fen: initialFen },
|
|
@@ -448,6 +462,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
448
462
|
const [minElo, setMinElo] = useState(defaultMinElo);
|
|
449
463
|
const [maxElo, setMaxElo] = useState(defaultMaxElo);
|
|
450
464
|
const [topOnly, setTopOnly] = useState(false);
|
|
465
|
+
const [sources, setSources] = useState([...ALL_GAME_SOURCES]);
|
|
451
466
|
const [loading, setLoading] = useState(false);
|
|
452
467
|
const [error, setError] = useState(null);
|
|
453
468
|
const [variationsTab, setVariationsTab] = useState("variations");
|
|
@@ -540,6 +555,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
540
555
|
maxElo,
|
|
541
556
|
uci: gamesMoveFilterUci,
|
|
542
557
|
topOnly,
|
|
558
|
+
sources: sources.length < ALL_GAME_SOURCES.length ? sources : undefined,
|
|
543
559
|
}),
|
|
544
560
|
]);
|
|
545
561
|
if (cancelled)
|
|
@@ -569,6 +585,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
569
585
|
maxElo,
|
|
570
586
|
gamesMoveFilterUci,
|
|
571
587
|
topOnly,
|
|
588
|
+
sources,
|
|
572
589
|
fetchPosition,
|
|
573
590
|
fetchPositionGames,
|
|
574
591
|
]);
|
|
@@ -675,6 +692,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
675
692
|
minElo,
|
|
676
693
|
maxElo,
|
|
677
694
|
topOnly,
|
|
695
|
+
sources,
|
|
678
696
|
loading,
|
|
679
697
|
error,
|
|
680
698
|
lineLabel,
|
|
@@ -686,6 +704,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
686
704
|
setMinElo,
|
|
687
705
|
setMaxElo,
|
|
688
706
|
setTopOnly,
|
|
707
|
+
setSources,
|
|
689
708
|
setVariationsTab,
|
|
690
709
|
setGamesMoveFilterUci,
|
|
691
710
|
handleMoveSelect,
|
|
@@ -761,7 +780,7 @@ const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineS
|
|
|
761
780
|
defaultMinElo,
|
|
762
781
|
defaultMaxElo,
|
|
763
782
|
});
|
|
764
|
-
const { fen, boardFen, position, games, minElo, maxElo, topOnly, loading, error, lineLabel, canGoBack, canGoForward, variationsTab, forwardSans, selectedVariationKey, setMinElo, setMaxElo, setTopOnly, setVariationsTab, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, } = referenceData;
|
|
783
|
+
const { fen, boardFen, position, games, minElo, maxElo, topOnly, sources, loading, error, lineLabel, canGoBack, canGoForward, variationsTab, forwardSans, selectedVariationKey, setMinElo, setMaxElo, setTopOnly, setSources, setVariationsTab, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, } = referenceData;
|
|
765
784
|
const { lines: variationLines, loading: variationLinesLoading } = useVariationLines({
|
|
766
785
|
fen,
|
|
767
786
|
tab: variationsTab,
|
|
@@ -803,9 +822,11 @@ const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineS
|
|
|
803
822
|
defaultMinElo,
|
|
804
823
|
defaultMaxElo,
|
|
805
824
|
topOnly,
|
|
825
|
+
sources,
|
|
806
826
|
onMinEloChange: setMinElo,
|
|
807
827
|
onMaxEloChange: setMaxElo,
|
|
808
828
|
onTopOnlyChange: setTopOnly,
|
|
829
|
+
onSourcesChange: setSources,
|
|
809
830
|
onGameSelect,
|
|
810
831
|
}) }));
|
|
811
832
|
return (jsx(ThemeProvider, { theme: theme, children: jsx("div", { style: outerStyle, children: renderLayout({ theme, board, referencePanel }) }) }));
|
|
@@ -1016,7 +1037,6 @@ const mockPosition = {
|
|
|
1016
1037
|
avgElo: 2435,
|
|
1017
1038
|
},
|
|
1018
1039
|
],
|
|
1019
|
-
sampleGameIds: ["abc123", "def456"],
|
|
1020
1040
|
};
|
|
1021
1041
|
const mockGames = {
|
|
1022
1042
|
positionKey: "mock-start",
|
|
@@ -1037,6 +1057,7 @@ const mockGames = {
|
|
|
1037
1057
|
nextSan: "e4",
|
|
1038
1058
|
nextUci: "e2e4",
|
|
1039
1059
|
avgElo: 2843,
|
|
1060
|
+
source: "lichess",
|
|
1040
1061
|
},
|
|
1041
1062
|
{
|
|
1042
1063
|
gameId: "def456",
|
|
@@ -1050,6 +1071,7 @@ const mockGames = {
|
|
|
1050
1071
|
nextSan: "d4",
|
|
1051
1072
|
nextUci: "d2d4",
|
|
1052
1073
|
avgElo: 2778,
|
|
1074
|
+
source: "twic",
|
|
1053
1075
|
},
|
|
1054
1076
|
],
|
|
1055
1077
|
};
|
|
@@ -1095,11 +1117,15 @@ function mockFetchPositionVariations(params) {
|
|
|
1095
1117
|
}
|
|
1096
1118
|
function mockFetchPositionGames(params) {
|
|
1097
1119
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1098
|
-
|
|
1120
|
+
var _a;
|
|
1121
|
+
let filtered = params.uci
|
|
1099
1122
|
? mockGames.games.filter((g) => g.nextUci === params.uci)
|
|
1100
1123
|
: mockGames.games;
|
|
1124
|
+
if ((_a = params.sources) === null || _a === void 0 ? void 0 : _a.length) {
|
|
1125
|
+
filtered = filtered.filter((game) => params.sources.includes(game.source));
|
|
1126
|
+
}
|
|
1101
1127
|
return Object.assign(Object.assign({}, mockGames), { fen: params.fen, minElo: params.minElo, maxElo: params.maxElo, uci: params.uci, topOnly: params.topOnly, games: params.topOnly ? filtered.filter((g) => g.avgElo >= 2500) : filtered });
|
|
1102
1128
|
});
|
|
1103
1129
|
}
|
|
1104
1130
|
|
|
1105
|
-
export { DEFAULT_REFERENCE_LAYOUT, DefaultBoardNav, DefaultReferenceLayout, DefaultReferencePanel, DefaultVariationsStrip, EXPLORER_START_FEN, ExplorerPlaceholder, GameReplayTrainer, PositionReferenceExplorer, PositionReferenceExplorerCore, applyLineSans, defaultRenderBoardNav, defaultRenderGamesPanel, defaultRenderLayout, defaultRenderMoveStats, defaultRenderStatus, defaultRenderVariationsStrip, fenAfterUci, fenAtPly, findPlyIndexForFen, isVariationLineActive, mockFetchPosition, mockFetchPositionGames, mockFetchPositionVariations, normalizeFen, useGameReplayTraining, whiteScorePercent };
|
|
1131
|
+
export { ALL_GAME_SOURCES, DEFAULT_REFERENCE_LAYOUT, DefaultBoardNav, DefaultReferenceLayout, DefaultReferencePanel, DefaultVariationsStrip, EXPLORER_START_FEN, ExplorerPlaceholder, GameReplayTrainer, PositionReferenceExplorer, PositionReferenceExplorerCore, applyLineSans, defaultRenderBoardNav, defaultRenderGamesPanel, defaultRenderLayout, defaultRenderMoveStats, defaultRenderStatus, defaultRenderVariationsStrip, fenAfterUci, fenAtPly, findPlyIndexForFen, isVariationLineActive, mockFetchPosition, mockFetchPositionGames, mockFetchPositionVariations, normalizeFen, useGameReplayTraining, whiteScorePercent };
|
package/dist/index.js
CHANGED
|
@@ -286,12 +286,24 @@ const linkStyle = {
|
|
|
286
286
|
textUnderlineOffset: 2,
|
|
287
287
|
};
|
|
288
288
|
const LichessGameLink = ({ url, name }) => (jsxRuntime.jsx("a", { href: url, target: "_blank", rel: "noopener noreferrer", style: linkStyle, children: name }));
|
|
289
|
-
const GamesTable = ({ games, onGameSelect }) => (jsxRuntime.jsxs("table", { style: tableStyle, children: [jsxRuntime.jsx("thead", { children: jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("th", { style: thStyle, children: "White" }), jsxRuntime.jsx("th", { style: thStyle, children: "Elo" }), jsxRuntime.jsx("th", { style: thStyle, children: "Black" }), jsxRuntime.jsx("th", { style: thStyle, children: "Elo" }), jsxRuntime.jsx("th", { style: thStyle, children: "Result" }), onGameSelect && jsxRuntime.jsx("th", { style: thStyle })] }) }), jsxRuntime.jsx("tbody", { children: games.length === 0 ? (jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { colSpan: onGameSelect ?
|
|
289
|
+
const GamesTable = ({ games, onGameSelect }) => (jsxRuntime.jsxs("table", { style: tableStyle, children: [jsxRuntime.jsx("thead", { children: jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("th", { style: thStyle, children: "White" }), jsxRuntime.jsx("th", { style: thStyle, children: "Elo" }), jsxRuntime.jsx("th", { style: thStyle, children: "Black" }), jsxRuntime.jsx("th", { style: thStyle, children: "Elo" }), jsxRuntime.jsx("th", { style: thStyle, children: "Result" }), jsxRuntime.jsx("th", { style: thStyle, children: "Source" }), onGameSelect && jsxRuntime.jsx("th", { style: thStyle })] }) }), jsxRuntime.jsx("tbody", { children: games.length === 0 ? (jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { colSpan: onGameSelect ? 7 : 6, style: Object.assign(Object.assign({}, tdStyle), { opacity: 0.7, fontStyle: "italic" }), children: "No games match this position and filter. Widen the Elo range or turn off Top games." }) })) : (games.map((g) => (jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("td", { style: tdStyle, children: jsxRuntime.jsx(LichessGameLink, { url: g.url, name: g.white }) }), jsxRuntime.jsx("td", { style: tdStyle, children: g.whiteElo }), jsxRuntime.jsx("td", { style: tdStyle, children: jsxRuntime.jsx(LichessGameLink, { url: g.url, name: g.black }) }), jsxRuntime.jsx("td", { style: tdStyle, children: g.blackElo }), jsxRuntime.jsx("td", { style: tdStyle, children: g.result }), jsxRuntime.jsx("td", { style: tdStyle, children: g.source === "twic" ? "Master" : "Lichess" }), onGameSelect && (jsxRuntime.jsx("td", { style: tdStyle, children: jsxRuntime.jsx("button", { type: "button", onClick: () => onGameSelect(g), children: "Train" }) }))] }, g.gameId)))) })] }));
|
|
290
290
|
|
|
291
291
|
const mainLineTitleStyle = {
|
|
292
292
|
fontWeight: 600,
|
|
293
293
|
};
|
|
294
|
-
const PositionGamesPanel = ({ games, lineLabel, minElo, maxElo, defaultMinElo, defaultMaxElo, topOnly, onMinEloChange, onMaxEloChange, onTopOnlyChange,
|
|
294
|
+
const PositionGamesPanel = ({ games, lineLabel, minElo, maxElo, defaultMinElo, defaultMaxElo, topOnly, sources, onMinEloChange, onMaxEloChange, onTopOnlyChange, onSourcesChange, onGameSelect, }) => {
|
|
295
|
+
const toggleSource = (source) => {
|
|
296
|
+
if (sources.includes(source)) {
|
|
297
|
+
if (sources.length === 1) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
onSourcesChange(sources.filter((value) => value !== source));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
onSourcesChange([...sources, source]);
|
|
304
|
+
};
|
|
305
|
+
return (jsxRuntime.jsxs("div", { style: gamesSectionStyle, children: [jsxRuntime.jsx("div", { style: gamesHeaderStyle, children: lineLabel ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { style: mainLineTitleStyle, children: "Main line: " }), lineLabel, jsxRuntime.jsxs("span", { style: { opacity: 0.75 }, children: [" (", games.length, " games)"] })] })) : (jsxRuntime.jsxs("span", { children: ["Games ", jsxRuntime.jsxs("span", { style: { opacity: 0.75 }, children: ["(", games.length, ")"] })] })) }), jsxRuntime.jsx("div", { style: gamesScrollStyle, children: jsxRuntime.jsx(GamesTable, { games: games, onGameSelect: onGameSelect }) }), jsxRuntime.jsxs("div", { style: gamesToolbarStyle, children: [jsxRuntime.jsx(EloRangeFilter, { minElo: minElo, maxElo: maxElo, defaultMinElo: defaultMinElo, defaultMaxElo: defaultMaxElo, onMinEloChange: onMinEloChange, onMaxEloChange: onMaxEloChange }), jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: topOnly, onChange: (e) => onTopOnlyChange(e.target.checked) }), "Top games"] }), jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: sources.includes("lichess"), onChange: () => toggleSource("lichess") }), "Lichess"] }), jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: sources.includes("twic"), onChange: () => toggleSource("twic") }), "Master"] })] })] }));
|
|
306
|
+
};
|
|
295
307
|
|
|
296
308
|
const DefaultReferenceLayout = ({ board, referencePanel, }) => (jsxRuntime.jsxs("div", { style: referenceShellStyle, children: [jsxRuntime.jsx("div", { style: boardColumnStyle, children: board }), referencePanel] }));
|
|
297
309
|
const defaultRenderLayout = (props) => (jsxRuntime.jsx(DefaultReferenceLayout, Object.assign({}, props)));
|
|
@@ -369,6 +381,8 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
369
381
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
370
382
|
};
|
|
371
383
|
|
|
384
|
+
const ALL_GAME_SOURCES = ["lichess", "twic"];
|
|
385
|
+
|
|
372
386
|
function usePositionHistory(initialFen) {
|
|
373
387
|
const [history, setHistory] = react.useState([
|
|
374
388
|
{ fen: initialFen },
|
|
@@ -450,6 +464,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
450
464
|
const [minElo, setMinElo] = react.useState(defaultMinElo);
|
|
451
465
|
const [maxElo, setMaxElo] = react.useState(defaultMaxElo);
|
|
452
466
|
const [topOnly, setTopOnly] = react.useState(false);
|
|
467
|
+
const [sources, setSources] = react.useState([...ALL_GAME_SOURCES]);
|
|
453
468
|
const [loading, setLoading] = react.useState(false);
|
|
454
469
|
const [error, setError] = react.useState(null);
|
|
455
470
|
const [variationsTab, setVariationsTab] = react.useState("variations");
|
|
@@ -542,6 +557,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
542
557
|
maxElo,
|
|
543
558
|
uci: gamesMoveFilterUci,
|
|
544
559
|
topOnly,
|
|
560
|
+
sources: sources.length < ALL_GAME_SOURCES.length ? sources : undefined,
|
|
545
561
|
}),
|
|
546
562
|
]);
|
|
547
563
|
if (cancelled)
|
|
@@ -571,6 +587,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
571
587
|
maxElo,
|
|
572
588
|
gamesMoveFilterUci,
|
|
573
589
|
topOnly,
|
|
590
|
+
sources,
|
|
574
591
|
fetchPosition,
|
|
575
592
|
fetchPositionGames,
|
|
576
593
|
]);
|
|
@@ -677,6 +694,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
677
694
|
minElo,
|
|
678
695
|
maxElo,
|
|
679
696
|
topOnly,
|
|
697
|
+
sources,
|
|
680
698
|
loading,
|
|
681
699
|
error,
|
|
682
700
|
lineLabel,
|
|
@@ -688,6 +706,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
|
|
|
688
706
|
setMinElo,
|
|
689
707
|
setMaxElo,
|
|
690
708
|
setTopOnly,
|
|
709
|
+
setSources,
|
|
691
710
|
setVariationsTab,
|
|
692
711
|
setGamesMoveFilterUci,
|
|
693
712
|
handleMoveSelect,
|
|
@@ -763,7 +782,7 @@ const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineS
|
|
|
763
782
|
defaultMinElo,
|
|
764
783
|
defaultMaxElo,
|
|
765
784
|
});
|
|
766
|
-
const { fen, boardFen, position, games, minElo, maxElo, topOnly, loading, error, lineLabel, canGoBack, canGoForward, variationsTab, forwardSans, selectedVariationKey, setMinElo, setMaxElo, setTopOnly, setVariationsTab, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, } = referenceData;
|
|
785
|
+
const { fen, boardFen, position, games, minElo, maxElo, topOnly, sources, loading, error, lineLabel, canGoBack, canGoForward, variationsTab, forwardSans, selectedVariationKey, setMinElo, setMaxElo, setTopOnly, setSources, setVariationsTab, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, } = referenceData;
|
|
767
786
|
const { lines: variationLines, loading: variationLinesLoading } = useVariationLines({
|
|
768
787
|
fen,
|
|
769
788
|
tab: variationsTab,
|
|
@@ -805,9 +824,11 @@ const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineS
|
|
|
805
824
|
defaultMinElo,
|
|
806
825
|
defaultMaxElo,
|
|
807
826
|
topOnly,
|
|
827
|
+
sources,
|
|
808
828
|
onMinEloChange: setMinElo,
|
|
809
829
|
onMaxEloChange: setMaxElo,
|
|
810
830
|
onTopOnlyChange: setTopOnly,
|
|
831
|
+
onSourcesChange: setSources,
|
|
811
832
|
onGameSelect,
|
|
812
833
|
}) }));
|
|
813
834
|
return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, children: jsxRuntime.jsx("div", { style: outerStyle, children: renderLayout({ theme, board, referencePanel }) }) }));
|
|
@@ -1018,7 +1039,6 @@ const mockPosition = {
|
|
|
1018
1039
|
avgElo: 2435,
|
|
1019
1040
|
},
|
|
1020
1041
|
],
|
|
1021
|
-
sampleGameIds: ["abc123", "def456"],
|
|
1022
1042
|
};
|
|
1023
1043
|
const mockGames = {
|
|
1024
1044
|
positionKey: "mock-start",
|
|
@@ -1039,6 +1059,7 @@ const mockGames = {
|
|
|
1039
1059
|
nextSan: "e4",
|
|
1040
1060
|
nextUci: "e2e4",
|
|
1041
1061
|
avgElo: 2843,
|
|
1062
|
+
source: "lichess",
|
|
1042
1063
|
},
|
|
1043
1064
|
{
|
|
1044
1065
|
gameId: "def456",
|
|
@@ -1052,6 +1073,7 @@ const mockGames = {
|
|
|
1052
1073
|
nextSan: "d4",
|
|
1053
1074
|
nextUci: "d2d4",
|
|
1054
1075
|
avgElo: 2778,
|
|
1076
|
+
source: "twic",
|
|
1055
1077
|
},
|
|
1056
1078
|
],
|
|
1057
1079
|
};
|
|
@@ -1097,13 +1119,18 @@ function mockFetchPositionVariations(params) {
|
|
|
1097
1119
|
}
|
|
1098
1120
|
function mockFetchPositionGames(params) {
|
|
1099
1121
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1100
|
-
|
|
1122
|
+
var _a;
|
|
1123
|
+
let filtered = params.uci
|
|
1101
1124
|
? mockGames.games.filter((g) => g.nextUci === params.uci)
|
|
1102
1125
|
: mockGames.games;
|
|
1126
|
+
if ((_a = params.sources) === null || _a === void 0 ? void 0 : _a.length) {
|
|
1127
|
+
filtered = filtered.filter((game) => params.sources.includes(game.source));
|
|
1128
|
+
}
|
|
1103
1129
|
return Object.assign(Object.assign({}, mockGames), { fen: params.fen, minElo: params.minElo, maxElo: params.maxElo, uci: params.uci, topOnly: params.topOnly, games: params.topOnly ? filtered.filter((g) => g.avgElo >= 2500) : filtered });
|
|
1104
1130
|
});
|
|
1105
1131
|
}
|
|
1106
1132
|
|
|
1133
|
+
exports.ALL_GAME_SOURCES = ALL_GAME_SOURCES;
|
|
1107
1134
|
exports.DEFAULT_REFERENCE_LAYOUT = DEFAULT_REFERENCE_LAYOUT;
|
|
1108
1135
|
exports.DefaultBoardNav = DefaultBoardNav;
|
|
1109
1136
|
exports.DefaultReferenceLayout = DefaultReferenceLayout;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { PositionReferenceExplorer } from "../features/explorer/PositionReferenceExplorer";
|
|
3
|
+
declare const meta: Meta<typeof PositionReferenceExplorer>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof PositionReferenceExplorer>;
|
|
6
|
+
/** 100 sample games after 1.e4 e5 2.Nf3 Nc6 — Open Game / Italian–Spanish tabiya. */
|
|
7
|
+
export declare const Nc6100Games: Story;
|
|
8
|
+
export declare const LightTheme: Story;
|
|
9
|
+
export declare const StartingPosition: Story;
|
|
10
|
+
export declare const WithReplayFetch: Story;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FetchPositionGamesParams, FetchPositionVariationsParams, PositionGamesApiDto, PositionVariationsApiDto } from "../../features/explorer/types";
|
|
2
|
+
import { nc6SampleGames, NC6_TABIYA_FEN } from "./nc6SampleGames";
|
|
3
|
+
export declare function nc6FetchPosition(fen: string): Promise<import("../..").PositionApiDto | null>;
|
|
4
|
+
export declare function nc6FetchPositionGames(params: FetchPositionGamesParams): Promise<PositionGamesApiDto>;
|
|
5
|
+
export declare function nc6FetchPositionVariations(params: FetchPositionVariationsParams): Promise<PositionVariationsApiDto | null>;
|
|
6
|
+
export declare function nc6FetchGame(gameId: string): Promise<(import("../..").ExplorerGameReplayApiDto & {
|
|
7
|
+
avgElo: number;
|
|
8
|
+
occurrences: Map<string, {
|
|
9
|
+
nextSan: string;
|
|
10
|
+
nextUci: string;
|
|
11
|
+
}>;
|
|
12
|
+
}) | null>;
|
|
13
|
+
export { NC6_TABIYA_FEN, nc6SampleGames };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ExplorerGameReplayApiDto, PositionApiDto, PositionGameRowApiDto, PositionMoveApiDto } from "../../features/explorer/types";
|
|
2
|
+
export declare const NC6_OPENING_SANS: readonly ["e4", "e5", "Nf3", "Nc6"];
|
|
3
|
+
export declare const NC6_GAME_COUNT = 100;
|
|
4
|
+
export declare const NC6_GAME_ID_PREFIX = "nc6-sb-";
|
|
5
|
+
type MoveBucket = {
|
|
6
|
+
san: string;
|
|
7
|
+
uci: string;
|
|
8
|
+
games: number;
|
|
9
|
+
whiteWins: number;
|
|
10
|
+
draws: number;
|
|
11
|
+
blackWins: number;
|
|
12
|
+
eloSum: number;
|
|
13
|
+
};
|
|
14
|
+
type PositionBucket = {
|
|
15
|
+
fen: string;
|
|
16
|
+
totalGames: number;
|
|
17
|
+
moves: Map<string, MoveBucket>;
|
|
18
|
+
gameRows: PositionGameRowApiDto[];
|
|
19
|
+
};
|
|
20
|
+
type BuiltGame = ExplorerGameReplayApiDto & {
|
|
21
|
+
avgElo: number;
|
|
22
|
+
occurrences: Map<string, {
|
|
23
|
+
nextSan: string;
|
|
24
|
+
nextUci: string;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
export declare const NC6_TABIYA_FEN: string;
|
|
28
|
+
export declare const nc6SampleGames: BuiltGame[];
|
|
29
|
+
export declare const nc6PositionBuckets: Map<string, PositionBucket>;
|
|
30
|
+
export declare function nc6PositionForFen(fen: string): PositionApiDto | null;
|
|
31
|
+
export declare function nc6GamesForPosition(fen: string, options: {
|
|
32
|
+
minElo: number;
|
|
33
|
+
maxElo: number;
|
|
34
|
+
uci?: string;
|
|
35
|
+
topOnly: boolean;
|
|
36
|
+
sources?: ("lichess" | "twic")[];
|
|
37
|
+
}): PositionGameRowApiDto[];
|
|
38
|
+
export declare function nc6ScorePercentForMove(move: PositionMoveApiDto): number | null;
|
|
39
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-chess-explorer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "React components for browsing and replaying chess games (depends on react-chess-core only)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Robert Blackwell",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "rollup -c",
|
|
19
|
-
"prepublishOnly": "npm run build"
|
|
19
|
+
"prepublishOnly": "npm run build",
|
|
20
|
+
"storybook": "storybook dev -p 6008",
|
|
21
|
+
"build-storybook": "storybook build"
|
|
20
22
|
},
|
|
21
23
|
"dependencies": {
|
|
22
24
|
"rollup": "^4.22.2",
|
|
@@ -30,16 +32,28 @@
|
|
|
30
32
|
"react-chessboard": "^4.7.1"
|
|
31
33
|
},
|
|
32
34
|
"devDependencies": {
|
|
35
|
+
"@chromatic-com/storybook": "^1.9.0",
|
|
33
36
|
"@rollup/plugin-commonjs": "^26.0.1",
|
|
34
37
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
35
38
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
39
|
+
"@storybook/addon-essentials": "^8.2.9",
|
|
40
|
+
"@storybook/addon-interactions": "^8.2.9",
|
|
41
|
+
"@storybook/addon-links": "^8.2.9",
|
|
42
|
+
"@storybook/addon-onboarding": "^8.2.9",
|
|
43
|
+
"@storybook/blocks": "^8.2.9",
|
|
44
|
+
"@storybook/preset-typescript": "^3.0.0",
|
|
45
|
+
"@storybook/react": "^8.2.9",
|
|
46
|
+
"@storybook/react-vite": "^8.2.9",
|
|
47
|
+
"@storybook/test": "^8.2.9",
|
|
36
48
|
"@types/react": "^18.3.12",
|
|
37
49
|
"@types/react-dom": "^18.3.1",
|
|
50
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
38
51
|
"chess.js": "^1.0.0-beta.8",
|
|
39
52
|
"react": "^18.3.1",
|
|
40
53
|
"react-chess-core": "^0.1.0",
|
|
41
54
|
"react-chessboard": "^4.7.1",
|
|
42
55
|
"react-dom": "^18.3.1",
|
|
56
|
+
"storybook": "^8.2.9",
|
|
43
57
|
"tslib": "^2.8.1"
|
|
44
58
|
}
|
|
45
59
|
}
|