@connectorvol/chess-widgets 1.0.0 → 1.2.0

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.
@@ -1,247 +1,212 @@
1
1
  <script lang="ts" module>
2
- export type { TPuzzleCreationWizardProps } from "./types.js";
2
+ export type { TPuzzleCreationWizardProps } from "./types.js";
3
3
  </script>
4
4
 
5
5
  <script lang="ts">
6
- import { untrack } from "svelte";
7
-
8
- import StepMoves from "./StepMoves.svelte";
9
- import StepPosition from "./StepPosition.svelte";
10
- import StepPreview from "./StepPreview.svelte";
11
- import { DEFAULT_EDITABLE_BOARD_SETTINGS } from "../constants/editable-board-settings.js";
12
- import type { TPuzzleCreationWizardProps } from "./types.js";
13
- import {
14
- createEmptyTreeFromFen,
15
- createInitialPuzzleData,
16
- type PuzzleData,
17
- } from "../puzzle/puzzleData.js";
18
- import { PgnOps } from "@connectorvol/chessops/pgnOps.svelte";
19
- import { ChessTree, createPgnFromTree } from "@connectorvol/tree";
20
- import {
21
- CHESSBOARD_THEMES,
22
- createBoardApi,
23
- DEFAULT_BOARD_SETTINGS,
24
- Draggable,
25
- type IChessBoardActions,
26
- } from "@connectorvol/chessboard";
27
- import { calculatePly, Color, Square } from "@connectorvol/shared";
28
-
29
- const {
30
- onPuzzleCreated,
31
- fen: fenProp,
32
- boardTheme,
33
- boardAppearanceSettings,
34
- }: TPuzzleCreationWizardProps = $props();
35
-
36
- /**
37
- * Представляет нормализацию входного FEN из пропса (пустая строка трактуется как отсутствие значения).
38
- */
39
- function wizardSeedFen(prop: string | undefined): string | undefined {
40
- const t = prop?.trim();
41
- return t ? t : undefined;
42
- }
43
-
44
- /**
45
- * Представляет извлечение признака стороны, имеющей ход, из полной строки FEN.
46
- */
47
- function sideToMoveFromFullFen(fullFen: string): Color {
48
- const token = fullFen.trim().split(/\s+/)[1];
49
- return token === "b" ? Color.BLACK : Color.WHITE;
50
- }
51
-
52
- let step = $state<1 | 2 | 3>(1);
53
- let puzzleData = $derived<PuzzleData>(
54
- createInitialPuzzleData(wizardSeedFen(fenProp)),
55
- );
56
-
57
- /** Возвращает PGN дерева решения для превью на шаге 3. */
58
- let solutionPgnForPreview = $state("");
59
-
60
- function handleMovesBack() {
61
- step = 1;
62
- }
63
-
64
- function handleMovesNext(moves: string[]) {
65
- puzzleData.moves = moves;
66
- solutionPgnForPreview = createPgnFromTree(tree.rootNode);
67
- step = 3;
68
- }
69
-
70
- function handlePreviewBack() {
71
- step = 2;
72
- }
73
-
74
- let chess = $derived(new PgnOps(puzzleData.initialFen, "chess"));
75
- let tree = $derived(
76
- new ChessTree(createEmptyTreeFromFen(puzzleData.initialFen)),
77
- );
78
- const addMoveToTree = (san: string) => {
79
- const move = chess.makeSanMove(san);
80
- if (!move) return;
81
- const fen = chess.fen();
82
- const { halfMoves, fullMoves } = calculatePly(fen);
83
- tree.addNodeToCurrent({
84
- id: "",
85
- children: [],
86
- data: {
87
- fen,
88
- san,
89
- ply: halfMoves,
90
- fullMoves,
91
- },
92
- });
93
- return { move, fen, turn: chess.turn() };
94
- };
95
-
96
- const actions: IChessBoardActions = {
97
- game: {
98
- possibleMovesOnSquare: (square: Square) => chess.moves(square),
99
- beforePieceMoveSan(san: string) {
100
- const result = addMoveToTree(san);
101
- return result;
102
- },
103
- afterPieceMoveSan: () => {},
104
- beforePieceMove: (from, to, promotion) => {
105
- const san = chess.getSanForMove({ from, to, promotion });
106
- chess.makeMove({ from, to, promotion });
6
+ import { untrack } from "svelte";
7
+
8
+ import StepMoves from "./StepMoves.svelte";
9
+ import StepPosition from "./StepPosition.svelte";
10
+ import StepPreview from "./StepPreview.svelte";
11
+ import type { TPuzzleCreationWizardProps } from "./types.js";
12
+ import {
13
+ createEmptyTreeFromFen,
14
+ createInitialPuzzleData,
15
+ type PuzzleData,
16
+ } from "../puzzle/puzzleData.js";
17
+ import { PgnOps } from "@connectorvol/chessops/pgnOps.svelte";
18
+ import { ChessTree, createPgnFromTree } from "@connectorvol/tree";
19
+ import {
20
+ CHESSBOARD_THEMES,
21
+ Draggable,
22
+ type IChessBoardActions,
23
+ } from "@connectorvol/chessboard";
24
+ import { calculatePly, Color, Square } from "@connectorvol/shared";
25
+
26
+ import { createPuzzleLineEditingBoardApi } from "./createPuzzleLineEditingBoard.js";
27
+
28
+ const {
29
+ onPuzzleCreated,
30
+ fen: fenProp,
31
+ boardTheme,
32
+ boardAppearanceSettings,
33
+ }: TPuzzleCreationWizardProps = $props();
34
+
35
+ /**
36
+ * Представляет нормализацию входного FEN из пропса (пустая строка трактуется как отсутствие значения).
37
+ */
38
+ function wizardSeedFen(prop: string | undefined): string | undefined {
39
+ const t = prop?.trim();
40
+ return t ? t : undefined;
41
+ }
42
+
43
+ let step = $state<1 | 2 | 3>(1);
44
+ let puzzleData = $state<PuzzleData>(
45
+ createInitialPuzzleData(wizardSeedFen(fenProp)),
46
+ );
47
+
48
+ /** Возвращает PGN дерева решения для превью на шаге 3. */
49
+ let solutionPgnForPreview = $state("");
50
+
51
+ function handleMovesBack() {
52
+ step = 1;
53
+ }
54
+
55
+ function handleMovesNext(moves: string[]) {
56
+ puzzleData.moves = moves;
57
+ solutionPgnForPreview = createPgnFromTree(tree.rootNode);
58
+ step = 3;
59
+ }
60
+
61
+ function handlePreviewBack() {
62
+ step = 2;
63
+ }
64
+
65
+ let chess = $derived(new PgnOps(puzzleData.initialFen, "chess"));
66
+ let tree = $derived(
67
+ new ChessTree(createEmptyTreeFromFen(puzzleData.initialFen)),
68
+ );
69
+ const addMoveToTree = (san: string) => {
70
+ const move = chess.makeSanMove(san);
71
+ if (!move) return;
107
72
  const fen = chess.fen();
108
73
  const { halfMoves, fullMoves } = calculatePly(fen);
109
74
  tree.addNodeToCurrent({
110
- id: "",
111
- children: [],
112
- data: {
113
- fen,
114
- san,
115
- ply: halfMoves,
116
- fullMoves,
117
- },
75
+ id: "",
76
+ children: [],
77
+ data: {
78
+ fen,
79
+ san,
80
+ ply: halfMoves,
81
+ fullMoves,
82
+ },
118
83
  });
119
- return fen;
120
- },
121
- afterPieceMove: () => {
122
- const side = chess.turn();
123
- chessboard.draggable =
124
- side === Color.WHITE ? Draggable.WHITE : Draggable.BLACK;
125
- },
126
- },
127
- };
128
-
129
- /**
130
- * Представляет сборку доски для шага построения линии: позиция и ориентация по стороне хода в FEN.
131
- */
132
- function createMainChessboard(fullFen: string) {
133
- const side = sideToMoveFromFullFen(fullFen);
134
- const {
135
- boardSize: _boardSize,
136
- orientation: _orientation,
137
- draggable: _draggable,
138
- ...appearanceSettings
139
- } = (boardAppearanceSettings ?? {}) as Record<string, unknown> & {
140
- boardSize?: unknown;
141
- orientation?: unknown;
142
- draggable?: unknown;
84
+ return { move, fen, turn: chess.turn() };
143
85
  };
144
- return createBoardApi({
145
- fen: fullFen,
146
- settings: {
147
- ...DEFAULT_BOARD_SETTINGS,
148
- ...(appearanceSettings as typeof DEFAULT_BOARD_SETTINGS),
149
- boardSize: DEFAULT_EDITABLE_BOARD_SETTINGS.boardSize,
150
- isResizable: false,
151
- orientation: side,
152
- draggable: side === Color.WHITE ? Draggable.WHITE : Draggable.BLACK,
153
- },
154
- actions,
155
- theme: boardTheme ?? CHESSBOARD_THEMES.blue,
156
- });
157
- }
158
-
159
- let chessboard = $derived(createMainChessboard(puzzleData.initialFen));
160
-
161
- /**
162
- * Представляет сброс мастера при смене входного FEN извне (шаг 1 и черновик ходов обнуляются).
163
- */
164
- $effect(() => {
165
- const seed = wizardSeedFen(fenProp);
166
- const next = createInitialPuzzleData(seed);
167
- untrack(() => {
168
- puzzleData = next;
169
- chess = new PgnOps(next.initialFen, "chess");
170
- tree = new ChessTree(createEmptyTreeFromFen(next.initialFen));
171
- chessboard = createMainChessboard(next.initialFen);
172
- step = 1;
173
- solutionPgnForPreview = "";
86
+
87
+ const actions: IChessBoardActions = {
88
+ game: {
89
+ possibleMovesOnSquare: (square: Square) => chess.moves(square),
90
+ beforePieceMoveSan(san: string) {
91
+ const result = addMoveToTree(san);
92
+ return result;
93
+ },
94
+ afterPieceMoveSan: () => {},
95
+ beforePieceMove: (from, to, promotion) => {
96
+ const san = chess.getSanForMove({ from, to, promotion });
97
+ chess.makeMove({ from, to, promotion });
98
+ const fen = chess.fen();
99
+ const { halfMoves, fullMoves } = calculatePly(fen);
100
+ tree.addNodeToCurrent({
101
+ id: "",
102
+ children: [],
103
+ data: {
104
+ fen,
105
+ san,
106
+ ply: halfMoves,
107
+ fullMoves,
108
+ },
109
+ });
110
+ return fen;
111
+ },
112
+ afterPieceMove: () => {
113
+ const side = chess.turn();
114
+ chessboard.draggable =
115
+ side === Color.WHITE ? Draggable.WHITE : Draggable.BLACK;
116
+ },
117
+ },
118
+ };
119
+
120
+ /**
121
+ * Представляет сборку доски для шага построения линии: позиция и ориентация по стороне хода в FEN.
122
+ */
123
+ function createMainChessboard(fullFen: string) {
124
+ return createPuzzleLineEditingBoardApi(fullFen, actions, {
125
+ boardTheme: boardTheme ?? CHESSBOARD_THEMES.blue,
126
+ boardAppearanceSettings,
127
+ });
128
+ }
129
+
130
+ let chessboard = $derived(createMainChessboard(puzzleData.initialFen));
131
+
132
+ /**
133
+ * Представляет сброс мастера при смене входного FEN извне (шаг 1 и черновик ходов обнуляются).
134
+ */
135
+ $effect(() => {
136
+ const seed = wizardSeedFen(fenProp);
137
+ const next = createInitialPuzzleData(seed);
138
+ untrack(() => {
139
+ puzzleData = next;
140
+ step = 1;
141
+ solutionPgnForPreview = "";
142
+ });
174
143
  });
175
- });
176
-
177
- function handlePositionNext(fen: string) {
178
- puzzleData.initialFen = fen;
179
- chess = new PgnOps(fen, "chess");
180
- tree = new ChessTree(createEmptyTreeFromFen(fen));
181
- chessboard = createMainChessboard(fen);
182
- step = 2;
183
- }
144
+
145
+ function handlePositionNext(fen: string) {
146
+ puzzleData.initialFen = fen;
147
+ step = 2;
148
+ }
184
149
  </script>
185
150
 
186
151
  <div class="mx-4 lg:container lg:mx-auto">
187
- <div class="flex flex-col gap-4 pt-4 pb-0">
188
- <div class="flex items-center gap-2">
189
- <span
190
- class="flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium"
191
- class:bg-primary={step >= 1}
192
- class:bg-muted={step < 1}
193
- class:text-primary-foreground={step >= 1}
194
- >
195
- 1
196
- </span>
197
- <span class="text-sm">Позиция</span>
198
- <span class="h-px flex-1 bg-border"></span>
199
- <span
200
- class="flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium"
201
- class:bg-primary={step >= 2}
202
- class:bg-muted={step < 2}
203
- class:text-primary-foreground={step >= 2}
204
- >
205
- 2
206
- </span>
207
- <span class="text-sm">Ходы</span>
208
- <span class="h-px flex-1 bg-border"></span>
209
- <span
210
- class="flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium"
211
- class:bg-primary={step >= 3}
212
- class:bg-muted={step < 3}
213
- class:text-primary-foreground={step >= 3}
214
- >
215
- 3
216
- </span>
217
- <span class="text-sm">Превью</span>
152
+ <div class="flex flex-col gap-4 pt-4 pb-0">
153
+ <div class="flex items-center gap-2">
154
+ <span
155
+ class="flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium"
156
+ class:bg-primary={step >= 1}
157
+ class:bg-muted={step < 1}
158
+ class:text-primary-foreground={step >= 1}
159
+ >
160
+ 1
161
+ </span>
162
+ <span class="text-sm">Позиция</span>
163
+ <span class="h-px flex-1 bg-border"></span>
164
+ <span
165
+ class="flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium"
166
+ class:bg-primary={step >= 2}
167
+ class:bg-muted={step < 2}
168
+ class:text-primary-foreground={step >= 2}
169
+ >
170
+ 2
171
+ </span>
172
+ <span class="text-sm">Ходы</span>
173
+ <span class="h-px flex-1 bg-border"></span>
174
+ <span
175
+ class="flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium"
176
+ class:bg-primary={step >= 3}
177
+ class:bg-muted={step < 3}
178
+ class:text-primary-foreground={step >= 3}
179
+ >
180
+ 3
181
+ </span>
182
+ <span class="text-sm">Превью</span>
183
+ </div>
184
+
185
+ {#if step === 1}
186
+ <StepPosition
187
+ initialFen={puzzleData.initialFen}
188
+ onNext={handlePositionNext}
189
+ {boardTheme}
190
+ {boardAppearanceSettings}
191
+ />
192
+ {:else if step === 2}
193
+ <StepMoves
194
+ {puzzleData}
195
+ {chess}
196
+ {tree}
197
+ {chessboard}
198
+ onBack={handleMovesBack}
199
+ onNext={handleMovesNext}
200
+ />
201
+ {:else}
202
+ <StepPreview
203
+ {puzzleData}
204
+ solutionPgn={solutionPgnForPreview}
205
+ onBack={handlePreviewBack}
206
+ {onPuzzleCreated}
207
+ {boardTheme}
208
+ {boardAppearanceSettings}
209
+ />
210
+ {/if}
218
211
  </div>
219
-
220
- {#if step === 1}
221
- <StepPosition
222
- initialFen={puzzleData.initialFen}
223
- onNext={handlePositionNext}
224
- {boardTheme}
225
- boardAppearanceSettings={boardAppearanceSettings}
226
- />
227
- {:else if step === 2}
228
- <StepMoves
229
- {puzzleData}
230
- {chess}
231
- {tree}
232
- {chessboard}
233
- onBack={handleMovesBack}
234
- onNext={handleMovesNext}
235
- />
236
- {:else}
237
- <StepPreview
238
- {puzzleData}
239
- solutionPgn={solutionPgnForPreview}
240
- onBack={handlePreviewBack}
241
- {onPuzzleCreated}
242
- {boardTheme}
243
- boardAppearanceSettings={boardAppearanceSettings}
244
- />
245
- {/if}
246
- </div>
247
212
  </div>