@connectorvol/chess-widgets 8.0.1 → 9.0.1
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/(components)/BoardSettingsTrigger.svelte +10 -0
- package/dist/(components)/BoardSettingsTrigger.svelte.d.ts +18 -0
- package/dist/constants/default-board-appearance-settings.d.ts +9 -0
- package/dist/constants/default-board-appearance-settings.js +30 -0
- package/dist/constants/editable-board-settings.d.ts +3 -21
- package/dist/constants/editable-board-settings.js +24 -19
- package/dist/game-analyzer/GameAnalyzer.svelte +28 -10
- package/dist/game-analyzer/gameAnalyzer.svelte.js +6 -9
- package/dist/game-analyzer/types.d.ts +13 -8
- package/dist/index.d.ts +9 -1
- package/dist/index.js +6 -0
- package/dist/position-editor/EditPanel.svelte +9 -6
- package/dist/puzzle/puzzleCreatedPayload.d.ts +17 -0
- package/dist/puzzle/puzzleData.d.ts +4 -0
- package/dist/puzzle/puzzleData.js +10 -0
- package/dist/puzzle/puzzlePreviewConstants.d.ts +4 -0
- package/dist/puzzle/puzzlePreviewConstants.js +4 -0
- package/dist/puzzle/puzzlePreviewPathNags.d.ts +6 -0
- package/dist/puzzle/puzzlePreviewPathNags.js +35 -0
- package/dist/puzzle/puzzleSolverForkAnnotations.d.ts +2 -4
- package/dist/puzzle/puzzleSolverForkAnnotations.js +13 -22
- package/dist/puzzle/puzzleStepPreviewSolver.d.ts +13 -1
- package/dist/puzzle/puzzleStepPreviewSolver.js +69 -9
- package/dist/puzzle/syncPuzzleBranchNags.d.ts +14 -0
- package/dist/puzzle/syncPuzzleBranchNags.js +125 -0
- package/dist/puzzle-creation/OpeningTagHoverPreview.svelte +81 -0
- package/dist/puzzle-creation/OpeningTagHoverPreview.svelte.d.ts +11 -0
- package/dist/puzzle-creation/PuzzleBoardTreeViewerPane.svelte +198 -98
- package/dist/puzzle-creation/PuzzleBoardTreeViewerPane.svelte.d.ts +20 -2
- package/dist/puzzle-creation/PuzzleCreationWizard.svelte +100 -106
- package/dist/puzzle-creation/PuzzleCreationWizard.svelte.d.ts +47 -3
- package/dist/puzzle-creation/PuzzlePgnBoardTreeEditor.svelte +945 -498
- package/dist/puzzle-creation/PuzzleWizardTagsStep.svelte +36 -0
- package/dist/puzzle-creation/PuzzleWizardTagsStep.svelte.d.ts +12 -0
- package/dist/puzzle-creation/StepMoves.svelte +210 -188
- package/dist/puzzle-creation/StepPosition.svelte +35 -9
- package/dist/puzzle-creation/StepPreview.svelte +24 -11
- package/dist/puzzle-creation/StepPreview.svelte.d.ts +3 -1
- package/dist/puzzle-creation/StepTags.svelte +270 -0
- package/dist/puzzle-creation/StepTags.svelte.d.ts +19 -0
- package/dist/puzzle-creation/buildPuzzleWizardBoardSettings.d.ts +9 -0
- package/dist/puzzle-creation/buildPuzzleWizardBoardSettings.js +19 -0
- package/dist/puzzle-creation/createPuzzleLineEditingBoard.d.ts +10 -3
- package/dist/puzzle-creation/createPuzzleLineEditingBoard.js +14 -9
- package/dist/puzzle-creation/puzzleWizardState.d.ts +35 -0
- package/dist/puzzle-creation/puzzleWizardState.js +37 -0
- package/dist/puzzle-creation/types.d.ts +36 -10
- package/package.json +22 -17
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type {
|
|
3
|
+
TPuzzleOpeningTagOption,
|
|
4
|
+
TPuzzleTagOption,
|
|
5
|
+
} from "@connectorvol/shared";
|
|
6
|
+
|
|
7
|
+
import StepTags from "./StepTags.svelte";
|
|
8
|
+
import { getWizardContext } from "@connectorvol/shared";
|
|
9
|
+
import type { TPuzzleWizardState } from "./puzzleWizardState.js";
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
/** Возвращает каталог дебютов для выбора одного тега. */
|
|
13
|
+
openingTags: readonly TPuzzleOpeningTagOption[];
|
|
14
|
+
/** Возвращает каталог тактических приёмов. */
|
|
15
|
+
tacticTags: readonly TPuzzleTagOption[];
|
|
16
|
+
/** Возвращает максимальное число выбираемых тактических тегов. */
|
|
17
|
+
tacticTagsMax: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { openingTags, tacticTags, tacticTagsMax }: Props = $props();
|
|
21
|
+
|
|
22
|
+
const w = getWizardContext<TPuzzleWizardState>();
|
|
23
|
+
let tags = $state(w.state.puzzleTags);
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<StepTags
|
|
27
|
+
bind:tags
|
|
28
|
+
{openingTags}
|
|
29
|
+
{tacticTags}
|
|
30
|
+
{tacticTagsMax}
|
|
31
|
+
onBack={w.back}
|
|
32
|
+
onDone={(tags) => {
|
|
33
|
+
w.state.puzzleTags = tags;
|
|
34
|
+
w.done();
|
|
35
|
+
}}
|
|
36
|
+
/>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TPuzzleOpeningTagOption, TPuzzleTagOption } from "@connectorvol/shared";
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Возвращает каталог дебютов для выбора одного тега. */
|
|
4
|
+
openingTags: readonly TPuzzleOpeningTagOption[];
|
|
5
|
+
/** Возвращает каталог тактических приёмов. */
|
|
6
|
+
tacticTags: readonly TPuzzleTagOption[];
|
|
7
|
+
/** Возвращает максимальное число выбираемых тактических тегов. */
|
|
8
|
+
tacticTagsMax: number;
|
|
9
|
+
}
|
|
10
|
+
declare const PuzzleWizardTagsStep: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type PuzzleWizardTagsStep = ReturnType<typeof PuzzleWizardTagsStep>;
|
|
12
|
+
export default PuzzleWizardTagsStep;
|
|
@@ -1,199 +1,221 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const { puzzleData, onBack, onNext, chess, tree, chessboard }: Props =
|
|
31
|
-
$props();
|
|
32
|
-
|
|
33
|
-
const initialTurn = $derived(
|
|
34
|
-
puzzleData.initialFen.trim().split(/\s+/)[1] === "b"
|
|
35
|
-
? Color.BLACK
|
|
36
|
-
: Color.WHITE,
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
const mainLine = $derived(getMainLineFromTree(tree.rootNode));
|
|
40
|
-
|
|
41
|
-
function setChessFen(fen: string) {
|
|
42
|
-
chess.setFen(fen);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function setChessboardFen(_animationTime?: number) {
|
|
46
|
-
chessboard.fen = chess.fen();
|
|
47
|
-
const side = chess.turn();
|
|
48
|
-
chessboard.draggable =
|
|
49
|
-
side === Color.WHITE ? Draggable.WHITE : Draggable.BLACK;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function onSelectNode() {
|
|
53
|
-
setChessFen(tree.currentNode.data.fen);
|
|
54
|
-
setChessboardFen();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function onDeleteVariant() {
|
|
58
|
-
setChessFen(tree.currentNode.data.fen);
|
|
59
|
-
setChessboardFen();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const canGoNext = $derived(mainLine.length >= 1 && mainLine.length % 2 === 1);
|
|
63
|
-
|
|
64
|
-
const solverForkAnnotations = $derived(
|
|
65
|
-
validatePuzzleSolverForkAnnotations(tree, initialTurn),
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
const canProceedToNextStep = $derived(canGoNext && solverForkAnnotations.ok);
|
|
69
|
-
|
|
70
|
-
/** Текст предупреждения по шагу для кнопки «!» (если пусто — предупреждений нет). */
|
|
71
|
-
const stepIssueMessage = $derived.by(() => {
|
|
72
|
-
if (!canGoNext && mainLine.length > 0) {
|
|
73
|
-
return `Последний ход должна сделать сторона, которая начинала. Добавьте ещё один ход за ${initialTurn === Color.WHITE ? "белых" : "чёрных"}.`;
|
|
2
|
+
import type { BoardApi } from "@connectorvol/chessboard";
|
|
3
|
+
|
|
4
|
+
import { Draggable } from "@connectorvol/chessboard";
|
|
5
|
+
import type { ChessTree } from "@connectorvol/tree";
|
|
6
|
+
|
|
7
|
+
import { Color } from "@connectorvol/shared";
|
|
8
|
+
import { Popover } from "bits-ui";
|
|
9
|
+
|
|
10
|
+
import { buttonVariants } from "../button-variants.js";
|
|
11
|
+
import PuzzleBoardTreeViewerPane from "./PuzzleBoardTreeViewerPane.svelte";
|
|
12
|
+
import { cn } from "../utils.js";
|
|
13
|
+
|
|
14
|
+
import type { PgnOps } from "@connectorvol/chessops/pgnOps.svelte";
|
|
15
|
+
import {
|
|
16
|
+
getMainLineFromTree,
|
|
17
|
+
type PuzzleData,
|
|
18
|
+
} from "../puzzle/puzzleData.js";
|
|
19
|
+
import { validatePuzzleSolverForkAnnotations } from "../puzzle/puzzleSolverForkAnnotations.js";
|
|
20
|
+
import { syncPuzzleBranchNagsOnTree } from "../puzzle/syncPuzzleBranchNags.js";
|
|
21
|
+
import { untrack } from "svelte";
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
puzzleData: PuzzleData;
|
|
25
|
+
onBack: () => void;
|
|
26
|
+
onNext: (moves: string[]) => void;
|
|
27
|
+
chess: PgnOps;
|
|
28
|
+
tree: ChessTree;
|
|
29
|
+
chessboard: BoardApi;
|
|
74
30
|
}
|
|
75
|
-
|
|
76
|
-
|
|
31
|
+
|
|
32
|
+
const { puzzleData, onBack, onNext, chess, tree, chessboard }: Props =
|
|
33
|
+
$props();
|
|
34
|
+
|
|
35
|
+
const initialTurn = $derived(
|
|
36
|
+
puzzleData.initialFen.trim().split(/\s+/)[1] === "b"
|
|
37
|
+
? Color.BLACK
|
|
38
|
+
: Color.WHITE,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const mainLine = $derived(getMainLineFromTree(tree.rootNode));
|
|
42
|
+
|
|
43
|
+
function setChessFen(fen: string) {
|
|
44
|
+
chess.setFen(fen);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function setChessboardFen(_animationTime: number | undefined) {
|
|
48
|
+
chessboard.fen = chess.fen();
|
|
49
|
+
const side = chess.turn();
|
|
50
|
+
chessboard.draggable =
|
|
51
|
+
side === Color.WHITE ? Draggable.WHITE : Draggable.BLACK;
|
|
77
52
|
}
|
|
78
|
-
return null;
|
|
79
|
-
});
|
|
80
53
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
54
|
+
function onSelectNode() {
|
|
55
|
+
setChessFen(tree.currentNode.data.fen);
|
|
56
|
+
setChessboardFen(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function onDeleteVariant() {
|
|
60
|
+
setChessFen(tree.currentNode.data.fen);
|
|
61
|
+
setChessboardFen(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const canGoNext = $derived(
|
|
65
|
+
mainLine.length >= 1 && mainLine.length % 2 === 1,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const solverForkAnnotations = $derived(
|
|
69
|
+
validatePuzzleSolverForkAnnotations(tree, initialTurn),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const canProceedToNextStep = $derived(
|
|
73
|
+
canGoNext && solverForkAnnotations.ok,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
/** Представляет автоматическую разметку ✓/✗ на развилках решателя после каждой правки дерева. */
|
|
77
|
+
$effect(() => {
|
|
78
|
+
void tree.mutationVersion;
|
|
79
|
+
untrack(() => {
|
|
80
|
+
syncPuzzleBranchNagsOnTree(tree, initialTurn);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/** Текст предупреждения по шагу для кнопки «!» (если пусто — предупреждений нет). */
|
|
85
|
+
const stepIssueMessage = $derived.by(() => {
|
|
86
|
+
if (!canGoNext && mainLine.length > 0) {
|
|
87
|
+
return `Последний ход должна сделать сторона, которая начинала. Добавьте ещё один ход за ${initialTurn === Color.WHITE ? "белых" : "чёрных"}.`;
|
|
88
|
+
}
|
|
89
|
+
if (canGoNext && !solverForkAnnotations.ok) {
|
|
90
|
+
return solverForkAnnotations.reason;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
function handleNext() {
|
|
96
|
+
if (canProceedToNextStep) {
|
|
97
|
+
onNext(mainLine);
|
|
98
|
+
}
|
|
84
99
|
}
|
|
85
|
-
}
|
|
86
100
|
</script>
|
|
87
101
|
|
|
88
102
|
<div class="flex flex-col gap-3 pt-2 pb-0">
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
104
|
+
<h2 class="text-xl font-semibold">Шаг 2: Построение линии решения</h2>
|
|
105
|
+
<Popover.Root>
|
|
106
|
+
<Popover.Trigger type="button">
|
|
107
|
+
{#snippet child({ props })}
|
|
108
|
+
<button
|
|
109
|
+
{...props}
|
|
110
|
+
type="button"
|
|
111
|
+
class={cn(
|
|
112
|
+
buttonVariants({
|
|
113
|
+
variant: "outline",
|
|
114
|
+
size: "icon-sm",
|
|
115
|
+
}),
|
|
116
|
+
"size-7 shrink-0 rounded-full text-sm font-semibold",
|
|
117
|
+
)}
|
|
118
|
+
aria-label="Справка: построение линии решения"
|
|
119
|
+
>
|
|
120
|
+
?
|
|
121
|
+
</button>
|
|
122
|
+
{/snippet}
|
|
123
|
+
</Popover.Trigger>
|
|
124
|
+
<Popover.Portal>
|
|
125
|
+
<Popover.Content
|
|
126
|
+
side="bottom"
|
|
127
|
+
align="start"
|
|
128
|
+
sideOffset={8}
|
|
129
|
+
class={cn(
|
|
130
|
+
"bg-popover text-popover-foreground border-border z-50 max-h-[min(70vh,32rem)] w-[min(calc(100vw-2rem),28rem)] overflow-y-auto rounded-lg border p-4 text-sm shadow-md outline-none",
|
|
131
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
132
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
133
|
+
)}
|
|
134
|
+
>
|
|
135
|
+
<p class="text-muted-foreground leading-relaxed">
|
|
136
|
+
Делайте ходы за обе стороны. Последний ход должна
|
|
137
|
+
сделать сторона, которая начинала игру (ключевой ход). В
|
|
138
|
+
дереве можно выбирать ходы, добавлять комментарии и
|
|
139
|
+
символы оценки в режиме правки. Ходы главной линии
|
|
140
|
+
автоматически получают ✓ (верный ход), неверные ходы
|
|
141
|
+
решателя в вариациях — ✗; ходы соперника без этих меток.
|
|
142
|
+
Если от позиции решателя есть несколько вариантов хода,
|
|
143
|
+
главный вариант должен быть верным, остальные —
|
|
144
|
+
неверными. После ✓ главный вариант нужно довести до
|
|
145
|
+
позиции, где последним походил решатель. Если несколько
|
|
146
|
+
ответов соперника ведут из одной позиции, каждая такая
|
|
147
|
+
линия должна заканчиваться ходом решателя.
|
|
148
|
+
</p>
|
|
149
|
+
</Popover.Content>
|
|
150
|
+
</Popover.Portal>
|
|
151
|
+
</Popover.Root>
|
|
152
|
+
{#if stepIssueMessage}
|
|
153
|
+
<Popover.Root>
|
|
154
|
+
<Popover.Trigger type="button">
|
|
155
|
+
{#snippet child({ props })}
|
|
156
|
+
<button
|
|
157
|
+
{...props}
|
|
158
|
+
type="button"
|
|
159
|
+
class={cn(
|
|
160
|
+
buttonVariants({
|
|
161
|
+
variant: "outline",
|
|
162
|
+
size: "icon-sm",
|
|
163
|
+
}),
|
|
164
|
+
"size-7 shrink-0 rounded-full border-amber-600/60 text-sm font-semibold text-amber-600 dark:border-amber-400/60 dark:text-amber-400",
|
|
165
|
+
)}
|
|
166
|
+
aria-label="Предупреждение по условиям шага"
|
|
167
|
+
>
|
|
168
|
+
!
|
|
169
|
+
</button>
|
|
170
|
+
{/snippet}
|
|
171
|
+
</Popover.Trigger>
|
|
172
|
+
<Popover.Portal>
|
|
173
|
+
<Popover.Content
|
|
174
|
+
side="bottom"
|
|
175
|
+
align="start"
|
|
176
|
+
sideOffset={8}
|
|
177
|
+
class={cn(
|
|
178
|
+
"bg-popover text-popover-foreground border-border z-50 max-h-[min(70vh,32rem)] w-[min(calc(100vw-2rem),28rem)] overflow-y-auto rounded-lg border p-4 text-sm shadow-md outline-none",
|
|
179
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
180
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
181
|
+
)}
|
|
182
|
+
>
|
|
183
|
+
<p
|
|
184
|
+
class="leading-relaxed text-amber-700 dark:text-amber-400"
|
|
185
|
+
>
|
|
186
|
+
{stepIssueMessage}
|
|
187
|
+
</p>
|
|
188
|
+
</Popover.Content>
|
|
189
|
+
</Popover.Portal>
|
|
190
|
+
</Popover.Root>
|
|
191
|
+
{/if}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<PuzzleBoardTreeViewerPane
|
|
195
|
+
{chessboard}
|
|
196
|
+
chessTree={tree}
|
|
197
|
+
{onSelectNode}
|
|
198
|
+
{onDeleteVariant}
|
|
199
|
+
{setChessFen}
|
|
200
|
+
{setChessboardFen}
|
|
201
|
+
showPuzzleBranchNags={true}
|
|
202
|
+
/>
|
|
203
|
+
|
|
204
|
+
<div class="flex justify-between">
|
|
205
|
+
<button
|
|
206
|
+
type="button"
|
|
207
|
+
class={cn(buttonVariants({ variant: "outline" }))}
|
|
208
|
+
onclick={onBack}
|
|
209
|
+
>
|
|
210
|
+
Назад
|
|
211
|
+
</button>
|
|
212
|
+
<button
|
|
96
213
|
type="button"
|
|
97
|
-
class={cn(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
)}
|
|
101
|
-
aria-label="Справка: построение линии решения"
|
|
102
|
-
>
|
|
103
|
-
?
|
|
104
|
-
</button>
|
|
105
|
-
{/snippet}
|
|
106
|
-
</Popover.Trigger>
|
|
107
|
-
<Popover.Portal>
|
|
108
|
-
<Popover.Content
|
|
109
|
-
side="bottom"
|
|
110
|
-
align="start"
|
|
111
|
-
sideOffset={8}
|
|
112
|
-
class={cn(
|
|
113
|
-
"bg-popover text-popover-foreground border-border z-50 max-h-[min(70vh,32rem)] w-[min(calc(100vw-2rem),28rem)] overflow-y-auto rounded-lg border p-4 text-sm shadow-md outline-none",
|
|
114
|
-
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
115
|
-
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
116
|
-
)}
|
|
214
|
+
class={cn(buttonVariants({ variant: "default" }))}
|
|
215
|
+
onclick={handleNext}
|
|
216
|
+
disabled={!canProceedToNextStep}
|
|
117
217
|
>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
добавлять комментарии и символы оценки в режиме правки. Если от
|
|
122
|
-
позиции решателя есть несколько вариантов хода, у каждого обязательно
|
|
123
|
-
отметьте ✓ или ✗ в блоке «Задача (развилка)» под деревом (эти метки
|
|
124
|
-
только для ходов из развилки, не для единственного продолжения).
|
|
125
|
-
После ✓ главный вариант нужно довести до позиции, где последним
|
|
126
|
-
походил решатель. Если несколько ответов соперника ведут из одной
|
|
127
|
-
позиции, каждая такая линия должна заканчиваться ходом решателя (это не
|
|
128
|
-
требуется внутри продолжения после хода решателя с меткой ✗
|
|
129
|
-
«неверное решение»). Ниже по этой линии метки ✓ и ✗ не ставятся.
|
|
130
|
-
</p>
|
|
131
|
-
</Popover.Content>
|
|
132
|
-
</Popover.Portal>
|
|
133
|
-
</Popover.Root>
|
|
134
|
-
{#if stepIssueMessage}
|
|
135
|
-
<Popover.Root>
|
|
136
|
-
<Popover.Trigger type="button">
|
|
137
|
-
{#snippet child({ props })}
|
|
138
|
-
<button
|
|
139
|
-
{...props}
|
|
140
|
-
type="button"
|
|
141
|
-
class={cn(
|
|
142
|
-
buttonVariants({ variant: "outline", size: "icon-sm" }),
|
|
143
|
-
"size-7 shrink-0 rounded-full border-amber-600/60 text-sm font-semibold text-amber-600 dark:border-amber-400/60 dark:text-amber-400",
|
|
144
|
-
)}
|
|
145
|
-
aria-label="Предупреждение по условиям шага"
|
|
146
|
-
>
|
|
147
|
-
!
|
|
148
|
-
</button>
|
|
149
|
-
{/snippet}
|
|
150
|
-
</Popover.Trigger>
|
|
151
|
-
<Popover.Portal>
|
|
152
|
-
<Popover.Content
|
|
153
|
-
side="bottom"
|
|
154
|
-
align="start"
|
|
155
|
-
sideOffset={8}
|
|
156
|
-
class={cn(
|
|
157
|
-
"bg-popover text-popover-foreground border-border z-50 max-h-[min(70vh,32rem)] w-[min(calc(100vw-2rem),28rem)] overflow-y-auto rounded-lg border p-4 text-sm shadow-md outline-none",
|
|
158
|
-
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
159
|
-
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
160
|
-
)}
|
|
161
|
-
>
|
|
162
|
-
<p
|
|
163
|
-
class="leading-relaxed text-amber-700 dark:text-amber-400"
|
|
164
|
-
>
|
|
165
|
-
{stepIssueMessage}
|
|
166
|
-
</p>
|
|
167
|
-
</Popover.Content>
|
|
168
|
-
</Popover.Portal>
|
|
169
|
-
</Popover.Root>
|
|
170
|
-
{/if}
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
<PuzzleBoardTreeViewerPane
|
|
174
|
-
{chessboard}
|
|
175
|
-
chessTree={tree}
|
|
176
|
-
{onSelectNode}
|
|
177
|
-
{onDeleteVariant}
|
|
178
|
-
{setChessFen}
|
|
179
|
-
{setChessboardFen}
|
|
180
|
-
/>
|
|
181
|
-
|
|
182
|
-
<div class="flex justify-between">
|
|
183
|
-
<button
|
|
184
|
-
type="button"
|
|
185
|
-
class={cn(buttonVariants({ variant: "outline" }))}
|
|
186
|
-
onclick={onBack}
|
|
187
|
-
>
|
|
188
|
-
Назад
|
|
189
|
-
</button>
|
|
190
|
-
<button
|
|
191
|
-
type="button"
|
|
192
|
-
class={cn(buttonVariants({ variant: "default" }))}
|
|
193
|
-
onclick={handleNext}
|
|
194
|
-
disabled={!canProceedToNextStep}
|
|
195
|
-
>
|
|
196
|
-
Далее
|
|
197
|
-
</button>
|
|
198
|
-
</div>
|
|
218
|
+
Далее
|
|
219
|
+
</button>
|
|
220
|
+
</div>
|
|
199
221
|
</div>
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
Chessboard,
|
|
4
4
|
CHESSBOARD_THEMES,
|
|
5
5
|
createBoardApi,
|
|
6
|
+
DEFAULT_DESIGN_SETTINGS,
|
|
7
|
+
getBoardDesignSettingsContext,
|
|
6
8
|
type ChessboardTheme,
|
|
7
9
|
} from "@connectorvol/chessboard";
|
|
10
|
+
import { untrack } from "svelte";
|
|
8
11
|
|
|
9
12
|
import { DEFAULT_EDITABLE_BOARD_SETTINGS } from "../constants/editable-board-settings.js";
|
|
10
13
|
import EditFen from "../position-editor/EditFen.svelte";
|
|
@@ -28,21 +31,40 @@
|
|
|
28
31
|
|
|
29
32
|
/** Настройки доски мастера задачи: фиксированный размер, без ручного resize. */
|
|
30
33
|
const puzzleStepBoardSettings = $derived({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
play: {
|
|
35
|
+
...DEFAULT_EDITABLE_BOARD_SETTINGS.play,
|
|
36
|
+
...(props.boardAppearanceSettings?.play ?? {}),
|
|
37
|
+
boardSize: 29,
|
|
38
|
+
isResizable: false,
|
|
39
|
+
editSettings: DEFAULT_EDITABLE_BOARD_SETTINGS.play.editSettings,
|
|
40
|
+
},
|
|
41
|
+
design: {
|
|
42
|
+
...DEFAULT_EDITABLE_BOARD_SETTINGS.design,
|
|
43
|
+
...(props.boardAppearanceSettings?.design ?? {}),
|
|
44
|
+
theme: props.boardTheme ?? CHESSBOARD_THEMES.blue,
|
|
45
|
+
},
|
|
36
46
|
});
|
|
37
47
|
|
|
38
48
|
let chessboard = $derived(
|
|
39
49
|
createBoardApi({
|
|
40
50
|
fen: (() => props.initialFen)(),
|
|
41
|
-
|
|
42
|
-
theme: props.boardTheme ?? CHESSBOARD_THEMES.blue,
|
|
51
|
+
playSettings: puzzleStepBoardSettings.play,
|
|
43
52
|
}),
|
|
44
53
|
);
|
|
45
54
|
|
|
55
|
+
// ponytail: компонент только читает контекст дизайна. Если layout уже
|
|
56
|
+
// поднял его — Chessboard берёт дизайн из контекста сам, иначе
|
|
57
|
+
// используем дизайн из `puzzleStepBoardSettings.design`
|
|
58
|
+
// (`boardTheme` + `boardAppearanceSettings.design`).
|
|
59
|
+
const inheritedDesign = getBoardDesignSettingsContext();
|
|
60
|
+
const localDesign = $derived({
|
|
61
|
+
...DEFAULT_DESIGN_SETTINGS,
|
|
62
|
+
...puzzleStepBoardSettings.design,
|
|
63
|
+
});
|
|
64
|
+
const chessboardDesign = $derived(
|
|
65
|
+
inheritedDesign ? undefined : localDesign,
|
|
66
|
+
);
|
|
67
|
+
|
|
46
68
|
/** Один объект Fen на доску: создаём сразу с нужной начальной строкой. */
|
|
47
69
|
const fen = $derived.by(() => {
|
|
48
70
|
const f = new Fen(chessboard);
|
|
@@ -121,9 +143,13 @@
|
|
|
121
143
|
<!-- Явная ширина: при родителе с w-fit/min-content BoardContainer даёт min(100%, Nrem), и 100% может схлопнуться до нуля. -->
|
|
122
144
|
<div
|
|
123
145
|
class="shrink-0 max-w-full"
|
|
124
|
-
style="width: {puzzleStepBoardSettings.
|
|
146
|
+
style="width: {puzzleStepBoardSettings.play
|
|
147
|
+
.boardSize}rem; max-width: 100%;"
|
|
125
148
|
>
|
|
126
|
-
<Chessboard
|
|
149
|
+
<Chessboard
|
|
150
|
+
facade={chessboard}
|
|
151
|
+
design={chessboardDesign}
|
|
152
|
+
/>
|
|
127
153
|
</div>
|
|
128
154
|
<div
|
|
129
155
|
class="hidden min-w-0 flex-1 flex-col gap-3 md:flex md:min-w-[280px]"
|
|
@@ -27,10 +27,12 @@
|
|
|
27
27
|
* Возвращает колбэк сохранения задачи с FEN и строкой ходов (кнопка «Готово» показывается после решения превью).
|
|
28
28
|
*/
|
|
29
29
|
onPuzzleCreated?: (payload: TPuzzleCreatedPayload) => void;
|
|
30
|
+
/** Возвращает переход на шаг выбора тегов вместо немедленного завершения. */
|
|
31
|
+
onNext?: () => void;
|
|
30
32
|
/** Возвращает тему оформления шахматной доски (по умолчанию `CHESSBOARD_THEMES.blue`). */
|
|
31
33
|
boardTheme?: ChessboardTheme;
|
|
32
34
|
/** Возвращает дополнительные визуальные настройки шахматной доски (кроме `boardSize`, `orientation`, `draggable`). */
|
|
33
|
-
boardAppearanceSettings
|
|
35
|
+
boardAppearanceSettings?: TChessboardAppearanceSettings;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
const props: Props = $props();
|
|
@@ -150,16 +152,27 @@
|
|
|
150
152
|
>
|
|
151
153
|
Назад
|
|
152
154
|
</button>
|
|
153
|
-
{#if
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
props.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
155
|
+
{#if solved}
|
|
156
|
+
{#if props.onNext}
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
class={cn(buttonVariants({ variant: "default" }))}
|
|
160
|
+
onclick={props.onNext}
|
|
161
|
+
disabled={!props.solutionPgn.trim()}
|
|
162
|
+
>
|
|
163
|
+
Далее
|
|
164
|
+
</button>
|
|
165
|
+
{:else if props.onPuzzleCreated}
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
class={cn(buttonVariants({ variant: "default" }))}
|
|
169
|
+
onclick={() =>
|
|
170
|
+
props.onPuzzleCreated?.(puzzlePartsFromFullPgn(props.solutionPgn))}
|
|
171
|
+
disabled={!props.solutionPgn.trim()}
|
|
172
|
+
>
|
|
173
|
+
Готово
|
|
174
|
+
</button>
|
|
175
|
+
{/if}
|
|
163
176
|
{/if}
|
|
164
177
|
</div>
|
|
165
178
|
</div>
|
|
@@ -13,10 +13,12 @@ interface Props {
|
|
|
13
13
|
* Возвращает колбэк сохранения задачи с FEN и строкой ходов (кнопка «Готово» показывается после решения превью).
|
|
14
14
|
*/
|
|
15
15
|
onPuzzleCreated?: (payload: TPuzzleCreatedPayload) => void;
|
|
16
|
+
/** Возвращает переход на шаг выбора тегов вместо немедленного завершения. */
|
|
17
|
+
onNext?: () => void;
|
|
16
18
|
/** Возвращает тему оформления шахматной доски (по умолчанию `CHESSBOARD_THEMES.blue`). */
|
|
17
19
|
boardTheme?: ChessboardTheme;
|
|
18
20
|
/** Возвращает дополнительные визуальные настройки шахматной доски (кроме `boardSize`, `orientation`, `draggable`). */
|
|
19
|
-
boardAppearanceSettings
|
|
21
|
+
boardAppearanceSettings?: TChessboardAppearanceSettings;
|
|
20
22
|
}
|
|
21
23
|
declare const StepPreview: import("svelte").Component<Props, {}, "">;
|
|
22
24
|
type StepPreview = ReturnType<typeof StepPreview>;
|