@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.
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/position-editor/EditMove.svelte +164 -158
- package/dist/puzzle/mergePgnWithSetupFen.d.ts +8 -0
- package/dist/puzzle/mergePgnWithSetupFen.js +28 -0
- package/dist/puzzle/puzzlePreviewConstants.d.ts +4 -0
- package/dist/puzzle/puzzlePreviewConstants.js +4 -0
- package/dist/puzzle/puzzleStepPreviewSolver.d.ts +1 -1
- package/dist/puzzle/puzzleStepPreviewSolver.js +4 -1
- package/dist/puzzle-creation/PuzzleBoardTreeViewerPane.svelte +106 -0
- package/dist/puzzle-creation/PuzzleBoardTreeViewerPane.svelte.d.ts +51 -0
- package/dist/puzzle-creation/PuzzleCreationWizard.svelte +197 -232
- package/dist/puzzle-creation/PuzzlePgnBoardTreeEditor.svelte +545 -0
- package/dist/puzzle-creation/PuzzlePgnBoardTreeEditor.svelte.d.ts +5 -0
- package/dist/puzzle-creation/StepMoves.svelte +14 -40
- package/dist/puzzle-creation/StepMoves.svelte.d.ts +1 -1
- package/dist/puzzle-creation/StepPosition.svelte +196 -190
- package/dist/puzzle-creation/StepPreview.svelte +14 -438
- package/dist/puzzle-creation/StepPreview.svelte.d.ts +1 -1
- package/dist/puzzle-creation/createPuzzleLineEditingBoard.d.ts +16 -0
- package/dist/puzzle-creation/createPuzzleLineEditingBoard.js +31 -0
- package/dist/puzzle-creation/types.d.ts +42 -0
- package/package.json +11 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export { default as PuzzleCreationWizard } from "./puzzle-creation/PuzzleCreationWizard.svelte";
|
|
2
|
-
export
|
|
2
|
+
export { default as PuzzlePgnBoardTreeEditor } from "./puzzle-creation/PuzzlePgnBoardTreeEditor.svelte";
|
|
3
|
+
export type { TPuzzleCreationWizardProps, TPuzzlePgnBoardTreeEditorOutcome, TPuzzlePgnBoardTreeEditorProps, } from "./puzzle-creation/types.js";
|
|
3
4
|
export type { TPuzzleCreatedPayload } from "./puzzle/puzzleCreatedPayload.js";
|
|
4
5
|
export { puzzlePartsFromFullPgn } from "./puzzle/puzzleCreatedPayload.js";
|
|
5
6
|
export type { PuzzleData } from "./puzzle/puzzleData.js";
|
|
6
7
|
export { PUZZLE_EMPTY_BOARD_FEN, createInitialPuzzleData, createEmptyTreeFromFen, getMainLineFromTree, } from "./puzzle/puzzleData.js";
|
|
7
8
|
export type { TSolverMoveOutcome } from "./puzzle/puzzleStepPreviewSolver.js";
|
|
9
|
+
export { PREVIEW_WRONG_NO_VARIANT_MESSAGE } from "./puzzle/puzzlePreviewConstants.js";
|
|
8
10
|
export { DEFAULT_EDITABLE_BOARD_SETTINGS } from "./constants/editable-board-settings.js";
|
|
9
11
|
export { default as EditFen } from "./position-editor/EditFen.svelte";
|
|
10
12
|
export { default as EditMove } from "./position-editor/EditMove.svelte";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { default as PuzzleCreationWizard } from "./puzzle-creation/PuzzleCreationWizard.svelte";
|
|
2
|
+
export { default as PuzzlePgnBoardTreeEditor } from "./puzzle-creation/PuzzlePgnBoardTreeEditor.svelte";
|
|
2
3
|
export { puzzlePartsFromFullPgn } from "./puzzle/puzzleCreatedPayload.js";
|
|
3
4
|
export { PUZZLE_EMPTY_BOARD_FEN, createInitialPuzzleData, createEmptyTreeFromFen, getMainLineFromTree, } from "./puzzle/puzzleData.js";
|
|
5
|
+
export { PREVIEW_WRONG_NO_VARIANT_MESSAGE } from "./puzzle/puzzlePreviewConstants.js";
|
|
4
6
|
export { DEFAULT_EDITABLE_BOARD_SETTINGS } from "./constants/editable-board-settings.js";
|
|
5
7
|
export { default as EditFen } from "./position-editor/EditFen.svelte";
|
|
6
8
|
export { default as EditMove } from "./position-editor/EditMove.svelte";
|
|
@@ -1,180 +1,186 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import type { BoardApi } from "@connectorvol/chessboard";
|
|
3
|
+
import type { Castling, Fen } from "./fen.svelte.js";
|
|
4
|
+
import { cn } from "../utils.js";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
interface Props {
|
|
12
|
-
fen: Fen;
|
|
13
|
-
api: BoardApi;
|
|
14
|
-
}
|
|
6
|
+
interface Props {
|
|
7
|
+
fen: Fen;
|
|
8
|
+
api: BoardApi;
|
|
9
|
+
}
|
|
15
10
|
|
|
16
|
-
|
|
11
|
+
const { fen, api: _api }: Props = $props();
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
isPossibleLongCastleBlack = fen.castling.includes("q");
|
|
23
|
-
});
|
|
13
|
+
const isPossibleShortCastleWhite = $derived(fen.castling.includes("K"));
|
|
14
|
+
const isPossibleShortCastleBlack = $derived(fen.castling.includes("k"));
|
|
15
|
+
const isPossibleLongCastleWhite = $derived(fen.castling.includes("Q"));
|
|
16
|
+
const isPossibleLongCastleBlack = $derived(fen.castling.includes("q"));
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
fen.castling = "-";
|
|
36
|
-
} else {
|
|
37
|
-
fen.castling = castling as Castling;
|
|
18
|
+
function buildCastling(
|
|
19
|
+
shortWhite: boolean,
|
|
20
|
+
longWhite: boolean,
|
|
21
|
+
shortBlack: boolean,
|
|
22
|
+
longBlack: boolean,
|
|
23
|
+
): Castling {
|
|
24
|
+
const castling = [shortWhite, longWhite, shortBlack, longBlack]
|
|
25
|
+
.map((c, i) => (c ? "KQkq"[i] : ""))
|
|
26
|
+
.join("");
|
|
27
|
+
return (castling || "-") as Castling;
|
|
38
28
|
}
|
|
39
|
-
});
|
|
40
29
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
function toggleCastling(flagIndex: number, value: boolean): void {
|
|
31
|
+
const flags = [
|
|
32
|
+
isPossibleShortCastleWhite,
|
|
33
|
+
isPossibleLongCastleWhite,
|
|
34
|
+
isPossibleShortCastleBlack,
|
|
35
|
+
isPossibleLongCastleBlack,
|
|
36
|
+
];
|
|
37
|
+
flags[flagIndex] = value;
|
|
38
|
+
fen.castling = buildCastling(
|
|
39
|
+
flags[0]!,
|
|
40
|
+
flags[1]!,
|
|
41
|
+
flags[2]!,
|
|
42
|
+
flags[3]!,
|
|
43
|
+
);
|
|
45
44
|
}
|
|
46
|
-
|
|
45
|
+
|
|
46
|
+
const enPassantMoves = $derived(fen.genPossibleEnPassantMoves());
|
|
47
47
|
</script>
|
|
48
48
|
|
|
49
49
|
<div class="space-y-3 md:space-y-4">
|
|
50
|
-
|
|
51
|
-
<label
|
|
52
|
-
for="move-select"
|
|
53
|
-
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
54
|
-
>
|
|
55
|
-
Установить ход
|
|
56
|
-
</label>
|
|
57
|
-
<select
|
|
58
|
-
id="move-select"
|
|
59
|
-
bind:value={fen.move}
|
|
60
|
-
class={cn(
|
|
61
|
-
"border-input bg-background ring-offset-background shadow-xs flex h-9 w-full rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
62
|
-
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
63
|
-
)}
|
|
64
|
-
>
|
|
65
|
-
<option value="w">w</option>
|
|
66
|
-
<option value="b">b</option>
|
|
67
|
-
</select>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<div class="space-y-2">
|
|
71
|
-
<label
|
|
72
|
-
for="enpassant-select"
|
|
73
|
-
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
74
|
-
>
|
|
75
|
-
Enpassant
|
|
76
|
-
</label>
|
|
77
|
-
<select
|
|
78
|
-
id="enpassant-select"
|
|
79
|
-
bind:value={fen.enPassant}
|
|
80
|
-
class={cn(
|
|
81
|
-
"border-input bg-background ring-offset-background shadow-xs flex h-9 w-full rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
82
|
-
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
83
|
-
)}
|
|
84
|
-
>
|
|
85
|
-
<option value="-">-</option>
|
|
86
|
-
{#each enPassantMoves as move}
|
|
87
|
-
<option value={move}>{move}</option>
|
|
88
|
-
{/each}
|
|
89
|
-
</select>
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
<div class="space-y-2 md:space-y-3">
|
|
93
|
-
<div class="text-sm font-medium leading-none">Возможность рокировки</div>
|
|
94
|
-
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-4">
|
|
95
|
-
<div class="flex items-center space-x-2">
|
|
96
|
-
<input
|
|
97
|
-
type="checkbox"
|
|
98
|
-
id="isPossibleShortCastleWhite"
|
|
99
|
-
bind:checked={isPossibleShortCastleWhite}
|
|
100
|
-
class={cn(
|
|
101
|
-
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
102
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
103
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
104
|
-
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
105
|
-
"accent-primary",
|
|
106
|
-
)}
|
|
107
|
-
/>
|
|
50
|
+
<div class="space-y-2">
|
|
108
51
|
<label
|
|
109
|
-
|
|
110
|
-
|
|
52
|
+
for="move-select"
|
|
53
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
111
54
|
>
|
|
112
|
-
|
|
55
|
+
Установить ход
|
|
113
56
|
</label>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
class={cn(
|
|
122
|
-
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
123
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
124
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
125
|
-
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
126
|
-
"accent-primary",
|
|
127
|
-
)}
|
|
128
|
-
/>
|
|
129
|
-
<label
|
|
130
|
-
for="isPossibleShortCastleBlack"
|
|
131
|
-
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
57
|
+
<select
|
|
58
|
+
id="move-select"
|
|
59
|
+
bind:value={fen.move}
|
|
60
|
+
class={cn(
|
|
61
|
+
"border-input bg-background ring-offset-background shadow-xs flex h-9 w-full rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
62
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
63
|
+
)}
|
|
132
64
|
>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
65
|
+
<option value="w">w</option>
|
|
66
|
+
<option value="b">b</option>
|
|
67
|
+
</select>
|
|
68
|
+
</div>
|
|
136
69
|
|
|
137
|
-
|
|
138
|
-
<input
|
|
139
|
-
type="checkbox"
|
|
140
|
-
id="isPossibleLongCastleWhite"
|
|
141
|
-
bind:checked={isPossibleLongCastleWhite}
|
|
142
|
-
class={cn(
|
|
143
|
-
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
144
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
145
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
146
|
-
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
147
|
-
"accent-primary",
|
|
148
|
-
)}
|
|
149
|
-
/>
|
|
70
|
+
<div class="space-y-2">
|
|
150
71
|
<label
|
|
151
|
-
|
|
152
|
-
|
|
72
|
+
for="enpassant-select"
|
|
73
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
153
74
|
>
|
|
154
|
-
|
|
75
|
+
Enpassant
|
|
155
76
|
</label>
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class={cn(
|
|
164
|
-
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
165
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
166
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
167
|
-
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
168
|
-
"accent-primary",
|
|
169
|
-
)}
|
|
170
|
-
/>
|
|
171
|
-
<label
|
|
172
|
-
for="isPossibleLongCastleBlack"
|
|
173
|
-
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
77
|
+
<select
|
|
78
|
+
id="enpassant-select"
|
|
79
|
+
bind:value={fen.enPassant}
|
|
80
|
+
class={cn(
|
|
81
|
+
"border-input bg-background ring-offset-background shadow-xs flex h-9 w-full rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
82
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
83
|
+
)}
|
|
174
84
|
>
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
85
|
+
<option value="-">-</option>
|
|
86
|
+
{#each enPassantMoves as move}
|
|
87
|
+
<option value={move}>{move}</option>
|
|
88
|
+
{/each}
|
|
89
|
+
</select>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div class="space-y-2 md:space-y-3">
|
|
93
|
+
<div class="text-sm font-medium leading-none">
|
|
94
|
+
Возможность рокировки
|
|
95
|
+
</div>
|
|
96
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-4">
|
|
97
|
+
<div class="flex items-center space-x-2">
|
|
98
|
+
<input
|
|
99
|
+
type="checkbox"
|
|
100
|
+
id="isPossibleShortCastleWhite"
|
|
101
|
+
checked={isPossibleShortCastleWhite}
|
|
102
|
+
onchange={(e) => toggleCastling(0, e.currentTarget.checked)}
|
|
103
|
+
class={cn(
|
|
104
|
+
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
105
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
106
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
107
|
+
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
108
|
+
"accent-primary",
|
|
109
|
+
)}
|
|
110
|
+
/>
|
|
111
|
+
<label
|
|
112
|
+
for="isPossibleShortCastleWhite"
|
|
113
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
114
|
+
>
|
|
115
|
+
Белые 0-0
|
|
116
|
+
</label>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div class="flex items-center space-x-2">
|
|
120
|
+
<input
|
|
121
|
+
type="checkbox"
|
|
122
|
+
id="isPossibleShortCastleBlack"
|
|
123
|
+
checked={isPossibleShortCastleBlack}
|
|
124
|
+
onchange={(e) => toggleCastling(2, e.currentTarget.checked)}
|
|
125
|
+
class={cn(
|
|
126
|
+
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
127
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
128
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
129
|
+
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
130
|
+
"accent-primary",
|
|
131
|
+
)}
|
|
132
|
+
/>
|
|
133
|
+
<label
|
|
134
|
+
for="isPossibleShortCastleBlack"
|
|
135
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
136
|
+
>
|
|
137
|
+
Черные 0-0
|
|
138
|
+
</label>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div class="flex items-center space-x-2">
|
|
142
|
+
<input
|
|
143
|
+
type="checkbox"
|
|
144
|
+
id="isPossibleLongCastleWhite"
|
|
145
|
+
checked={isPossibleLongCastleWhite}
|
|
146
|
+
onchange={(e) => toggleCastling(1, e.currentTarget.checked)}
|
|
147
|
+
class={cn(
|
|
148
|
+
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
149
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
150
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
151
|
+
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
152
|
+
"accent-primary",
|
|
153
|
+
)}
|
|
154
|
+
/>
|
|
155
|
+
<label
|
|
156
|
+
for="isPossibleLongCastleWhite"
|
|
157
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
158
|
+
>
|
|
159
|
+
Белые 0-0-0
|
|
160
|
+
</label>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div class="flex items-center space-x-2">
|
|
164
|
+
<input
|
|
165
|
+
type="checkbox"
|
|
166
|
+
id="isPossibleLongCastleBlack"
|
|
167
|
+
checked={isPossibleLongCastleBlack}
|
|
168
|
+
onchange={(e) => toggleCastling(3, e.currentTarget.checked)}
|
|
169
|
+
class={cn(
|
|
170
|
+
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
171
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
172
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
173
|
+
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
174
|
+
"accent-primary",
|
|
175
|
+
)}
|
|
176
|
+
/>
|
|
177
|
+
<label
|
|
178
|
+
for="isPossibleLongCastleBlack"
|
|
179
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
180
|
+
>
|
|
181
|
+
Черные 0-0-0
|
|
182
|
+
</label>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
178
185
|
</div>
|
|
179
|
-
</div>
|
|
180
186
|
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Представляет извлечение блока ходов из строки PGN без заголовков (только партия / варианты).
|
|
3
|
+
*/
|
|
4
|
+
export declare function stripPgnHeaders(movetextSource: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Представляет сборку строки PGN с заголовком `[FEN "..."]` для парсера дерева при раздельной передаче FEN и текста ходов.
|
|
7
|
+
*/
|
|
8
|
+
export declare function mergePgnWithSetupFen(movetextOrFullPgn: string, fen: string): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Представляет извлечение блока ходов из строки PGN без заголовков (только партия / варианты).
|
|
3
|
+
*/
|
|
4
|
+
export function stripPgnHeaders(movetextSource) {
|
|
5
|
+
const lines = movetextSource.split("\n");
|
|
6
|
+
let i = 0;
|
|
7
|
+
while (i < lines.length) {
|
|
8
|
+
const line = lines[i].trim();
|
|
9
|
+
if (line === "") {
|
|
10
|
+
i++;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (line.startsWith("[")) {
|
|
14
|
+
i++;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
return lines.slice(i).join("\n").trim();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Представляет сборку строки PGN с заголовком `[FEN "..."]` для парсера дерева при раздельной передаче FEN и текста ходов.
|
|
23
|
+
*/
|
|
24
|
+
export function mergePgnWithSetupFen(movetextOrFullPgn, fen) {
|
|
25
|
+
const escaped = fen.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
26
|
+
const body = stripPgnHeaders(movetextOrFullPgn);
|
|
27
|
+
return `[SetUp "1"]\n[FEN "${escaped}"]\n\n${body}`;
|
|
28
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Представляет текст сообщения «нет такого продолжения» для превью задачи (как на шаге 3).
|
|
3
|
+
*/
|
|
4
|
+
export declare const PREVIEW_WRONG_NO_VARIANT_MESSAGE = "\u0422\u0430\u043A\u043E\u0433\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u044F \u043D\u0435\u0442 \u0441\u0440\u0435\u0434\u0438 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u043E\u0432 \u0437\u0430\u0434\u0430\u0447\u0438.";
|
|
@@ -20,7 +20,7 @@ export declare function puzzlePreviewSideToMoveFromFen(fullFen: string): Color;
|
|
|
20
20
|
/**
|
|
21
21
|
* Представляет получение узла дерева по пути индексов детей от корня партии.
|
|
22
22
|
*/
|
|
23
|
-
export declare function puzzlePreviewNodeAtPath(rootMoves: ChessTreeNode, path: number[]): ChessTreeNode;
|
|
23
|
+
export declare function puzzlePreviewNodeAtPath(rootMoves: ChessTreeNode, path: number[]): ChessTreeNode | null;
|
|
24
24
|
/**
|
|
25
25
|
* Представляет поиск индекса ребёнка, чей SAN даёт ту же результирующую позицию, что и сыгранный SAN.
|
|
26
26
|
*/
|
|
@@ -13,7 +13,10 @@ export function puzzlePreviewSideToMoveFromFen(fullFen) {
|
|
|
13
13
|
export function puzzlePreviewNodeAtPath(rootMoves, path) {
|
|
14
14
|
let node = rootMoves;
|
|
15
15
|
for (const idx of path) {
|
|
16
|
-
|
|
16
|
+
const child = node.children[idx];
|
|
17
|
+
if (!child)
|
|
18
|
+
return null;
|
|
19
|
+
node = child;
|
|
17
20
|
}
|
|
18
21
|
return node;
|
|
19
22
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
|
|
4
|
+
import type { BoardApi } from "@connectorvol/chessboard";
|
|
5
|
+
import { Chessboard } from "@connectorvol/chessboard";
|
|
6
|
+
import type { ChessTree } from "@connectorvol/tree";
|
|
7
|
+
import { TreeViewer } from "@connectorvol/tree";
|
|
8
|
+
import { DEFAULT_PIECE_SET } from "@connectorvol/shared";
|
|
9
|
+
|
|
10
|
+
import { cn } from "../utils.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Представляет свойства панели разметки «доска и просмотр дерева партии».
|
|
14
|
+
*/
|
|
15
|
+
interface Props {
|
|
16
|
+
/**
|
|
17
|
+
* Возвращает API шахматной доски для компонента `Chessboard`.
|
|
18
|
+
*/
|
|
19
|
+
chessboard: BoardApi;
|
|
20
|
+
/**
|
|
21
|
+
* Возвращает экземпляр дерева ходов для `TreeViewer`.
|
|
22
|
+
*/
|
|
23
|
+
chessTree: ChessTree;
|
|
24
|
+
/**
|
|
25
|
+
* Возвращает обработчик выбора узла в дереве.
|
|
26
|
+
*/
|
|
27
|
+
onSelectNode: () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Возвращает обработчик после удаления варианта.
|
|
30
|
+
*/
|
|
31
|
+
onDeleteVariant: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Возвращает функцию установки FEN в движке партии.
|
|
34
|
+
*/
|
|
35
|
+
setChessFen: (fen: string) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Возвращает функцию синхронизации FEN и UI доски.
|
|
38
|
+
*/
|
|
39
|
+
setChessboardFen: (animationTime?: number) => void;
|
|
40
|
+
/**
|
|
41
|
+
* Возвращает признак режима правки дерева (`TreeViewer.editMode`).
|
|
42
|
+
*/
|
|
43
|
+
editMode?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Возвращает признак возможности выбора узлов в дереве.
|
|
46
|
+
*/
|
|
47
|
+
selectable?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Возвращает дополнительные классы Tailwind для корневого контейнера.
|
|
50
|
+
*/
|
|
51
|
+
class?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Возвращает фрагмент разметки под шахматной доской (например сообщение об ошибке в превью).
|
|
54
|
+
*/
|
|
55
|
+
belowChessboard?: Snippet;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const {
|
|
59
|
+
chessboard,
|
|
60
|
+
chessTree,
|
|
61
|
+
onSelectNode,
|
|
62
|
+
onDeleteVariant,
|
|
63
|
+
setChessFen,
|
|
64
|
+
setChessboardFen,
|
|
65
|
+
editMode = true,
|
|
66
|
+
selectable = true,
|
|
67
|
+
class: className,
|
|
68
|
+
belowChessboard,
|
|
69
|
+
}: Props = $props();
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<div
|
|
73
|
+
class={cn(
|
|
74
|
+
"relative flex min-h-0 flex-col justify-start lg:flex-row lg:items-stretch lg:justify-start lg:gap-4",
|
|
75
|
+
className,
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
<div
|
|
79
|
+
class="order-2 flex min-h-48 w-full flex-1 flex-col gap-2 lg:order-2 lg:min-h-0 lg:min-w-0"
|
|
80
|
+
>
|
|
81
|
+
<div
|
|
82
|
+
class="flex min-h-28 min-w-0 flex-1 flex-col overflow-hidden rounded-md border border-border lg:min-h-0"
|
|
83
|
+
>
|
|
84
|
+
<TreeViewer
|
|
85
|
+
chessTree={chessTree}
|
|
86
|
+
{onSelectNode}
|
|
87
|
+
{onDeleteVariant}
|
|
88
|
+
{setChessFen}
|
|
89
|
+
{setChessboardFen}
|
|
90
|
+
pieceSet={DEFAULT_PIECE_SET}
|
|
91
|
+
className="min-h-0 flex-1 border-x-0 border-t-0"
|
|
92
|
+
{editMode}
|
|
93
|
+
{selectable}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div
|
|
99
|
+
class="order-1 flex w-full max-w-[38rem] shrink-0 flex-col gap-2 lg:order-1 lg:min-h-0"
|
|
100
|
+
>
|
|
101
|
+
<div class="w-full shrink-0">
|
|
102
|
+
<Chessboard facade={chessboard} />
|
|
103
|
+
</div>
|
|
104
|
+
{@render belowChessboard?.()}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { BoardApi } from "@connectorvol/chessboard";
|
|
3
|
+
import type { ChessTree } from "@connectorvol/tree";
|
|
4
|
+
/**
|
|
5
|
+
* Представляет свойства панели разметки «доска и просмотр дерева партии».
|
|
6
|
+
*/
|
|
7
|
+
interface Props {
|
|
8
|
+
/**
|
|
9
|
+
* Возвращает API шахматной доски для компонента `Chessboard`.
|
|
10
|
+
*/
|
|
11
|
+
chessboard: BoardApi;
|
|
12
|
+
/**
|
|
13
|
+
* Возвращает экземпляр дерева ходов для `TreeViewer`.
|
|
14
|
+
*/
|
|
15
|
+
chessTree: ChessTree;
|
|
16
|
+
/**
|
|
17
|
+
* Возвращает обработчик выбора узла в дереве.
|
|
18
|
+
*/
|
|
19
|
+
onSelectNode: () => void;
|
|
20
|
+
/**
|
|
21
|
+
* Возвращает обработчик после удаления варианта.
|
|
22
|
+
*/
|
|
23
|
+
onDeleteVariant: () => void;
|
|
24
|
+
/**
|
|
25
|
+
* Возвращает функцию установки FEN в движке партии.
|
|
26
|
+
*/
|
|
27
|
+
setChessFen: (fen: string) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Возвращает функцию синхронизации FEN и UI доски.
|
|
30
|
+
*/
|
|
31
|
+
setChessboardFen: (animationTime?: number) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Возвращает признак режима правки дерева (`TreeViewer.editMode`).
|
|
34
|
+
*/
|
|
35
|
+
editMode?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Возвращает признак возможности выбора узлов в дереве.
|
|
38
|
+
*/
|
|
39
|
+
selectable?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Возвращает дополнительные классы Tailwind для корневого контейнера.
|
|
42
|
+
*/
|
|
43
|
+
class?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Возвращает фрагмент разметки под шахматной доской (например сообщение об ошибке в превью).
|
|
46
|
+
*/
|
|
47
|
+
belowChessboard?: Snippet;
|
|
48
|
+
}
|
|
49
|
+
declare const PuzzleBoardTreeViewerPane: import("svelte").Component<Props, {}, "">;
|
|
50
|
+
type PuzzleBoardTreeViewerPane = ReturnType<typeof PuzzleBoardTreeViewerPane>;
|
|
51
|
+
export default PuzzleBoardTreeViewerPane;
|