@connectorvol/tree 2.1.2 → 4.1.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/(classes)/chessTree.svelte.d.ts +83 -0
- package/dist/(classes)/chessTree.svelte.js +198 -0
- package/dist/(components)/DrillBreadcrumbs.svelte +135 -0
- package/dist/(components)/DrillBreadcrumbs.svelte.d.ts +20 -0
- package/dist/(components)/DrillForkSanLabel.svelte +26 -0
- package/dist/(components)/DrillForkSanLabel.svelte.d.ts +11 -0
- package/dist/(components)/DrillVariationList.svelte +65 -0
- package/dist/(components)/DrillVariationList.svelte.d.ts +15 -0
- package/dist/(components)/Move.svelte +242 -173
- package/dist/(components)/Move.svelte.d.ts +11 -5
- package/dist/(components)/MoveComment.svelte +32 -0
- package/dist/(components)/MoveComment.svelte.d.ts +11 -0
- package/dist/(components)/MoveContextMenu.svelte +73 -0
- package/dist/(components)/MoveContextMenu.svelte.d.ts +17 -0
- package/dist/(components)/MoveSanWithMenu.svelte +158 -0
- package/dist/(components)/MoveSanWithMenu.svelte.d.ts +42 -0
- package/dist/(components)/NagBadges.svelte +37 -0
- package/dist/(components)/NagBadges.svelte.d.ts +9 -0
- package/dist/(components)/TreeViewer.svelte +861 -42
- package/dist/(components)/TreeViewer.svelte.d.ts +29 -3
- package/dist/(components)/TreeViewerPanelManager.svelte +18 -155
- package/dist/(components)/VariantDropdownNavigator.svelte +173 -0
- package/dist/(components)/VariantDropdownNavigator.svelte.d.ts +16 -0
- package/dist/(components)/VariationGroup.svelte +100 -0
- package/dist/(components)/VariationGroup.svelte.d.ts +21 -0
- package/dist/(constants)/png.d.ts +1 -1
- package/dist/(constants)/png.js +2 -4
- package/dist/(models)/nagCatalog.d.ts +36 -0
- package/dist/(models)/nagCatalog.js +97 -0
- package/dist/(models)/pgnNodeCustomData.d.ts +3 -0
- package/dist/(utils)/context.d.ts +10 -3
- package/dist/(utils)/context.js +3 -14
- package/dist/(utils)/createPreviewHover.d.ts +19 -0
- package/dist/(utils)/createPreviewHover.js +37 -0
- package/dist/(utils)/nagBadgeStyle.d.ts +1 -0
- package/dist/(utils)/nagBadgeStyle.js +1 -0
- package/dist/(utils)/scrollToActiveMove.js +6 -5
- package/dist/(utils)/transformNag.d.ts +1 -1
- package/dist/(utils)/transformNag.js +1 -34
- package/dist/(utils)/transformPgnToChessNode.js +13 -0
- package/dist/(utils)/treeViewerPanelNavigation.svelte.d.ts +39 -0
- package/dist/(utils)/treeViewerPanelNavigation.svelte.js +257 -0
- package/dist/components/ui/button/button-variants.d.ts +65 -0
- package/dist/components/ui/button/button-variants.js +28 -0
- package/dist/components/ui/button/button.svelte +3 -43
- package/dist/components/ui/button/button.svelte.d.ts +1 -61
- package/dist/components/ui/button/index.d.ts +3 -2
- package/dist/components/ui/button/index.js +3 -4
- package/dist/components/ui/button-group/button-group-separator.svelte.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/package.json +4 -4
|
@@ -1,257 +1,316 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ChessTreeNode } from "../(models)/chessTreeNode.js";
|
|
3
3
|
|
|
4
|
-
import ArrowUpIcon from "@lucide/svelte/icons/arrow-up";
|
|
5
|
-
import CheckCheckIcon from "@lucide/svelte/icons/check-check";
|
|
6
|
-
import FlagIcon from "@lucide/svelte/icons/flag";
|
|
7
|
-
import Trash2Icon from "@lucide/svelte/icons/trash-2";
|
|
8
4
|
import { parseComment } from "@connectorvol/chessops/pgn";
|
|
9
|
-
import * as ContextMenu from "../components/ui/context-menu/index.js";
|
|
10
5
|
|
|
6
|
+
import { createPreviewHover } from "../(utils)/createPreviewHover.js";
|
|
11
7
|
import { getGameContext } from "../(utils)/context.js";
|
|
12
|
-
import { transformNag } from "../(utils)/transformNag.js";
|
|
13
8
|
import Move from "./Move.svelte";
|
|
14
|
-
import
|
|
9
|
+
import MoveComment from "./MoveComment.svelte";
|
|
10
|
+
import MoveSanWithMenu from "./MoveSanWithMenu.svelte";
|
|
11
|
+
import VariationGroup from "./VariationGroup.svelte";
|
|
15
12
|
import type { PieceSet } from "@connectorvol/shared";
|
|
16
|
-
import { ACTIVE_MOVE_CLASS } from "../(utils)/scrollToActiveMove.js";
|
|
17
13
|
|
|
18
14
|
const HIGHEST_LEVEL = 1;
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
type TMoveProps = {
|
|
17
|
+
/** Возвращает узел хода в дереве партии. */
|
|
21
18
|
chessNode: ChessTreeNode;
|
|
19
|
+
/** Возвращает глубину вложенности относительно корня отображения. */
|
|
22
20
|
depth: number;
|
|
21
|
+
/** Возвращает родительский узел или null для корня. */
|
|
23
22
|
parentNode: ChessTreeNode | null;
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
/** Возвращает true, если нужна пустая ячейка хода (сетка верхнего уровня). */
|
|
24
|
+
shouldReserveEmptyMoveCell: boolean;
|
|
25
|
+
/** Возвращает true, если нужно отрисовать соседние варианты у развилки. */
|
|
26
|
+
shouldRenderSiblingVariations?: boolean;
|
|
27
|
+
/** Возвращает набор фигур для иконок SAN. */
|
|
26
28
|
pieceSet: PieceSet;
|
|
27
|
-
}
|
|
29
|
+
};
|
|
28
30
|
|
|
29
31
|
const {
|
|
30
32
|
chessNode,
|
|
31
33
|
depth,
|
|
32
34
|
parentNode,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
shouldReserveEmptyMoveCell,
|
|
36
|
+
shouldRenderSiblingVariations = true,
|
|
35
37
|
pieceSet,
|
|
36
|
-
}:
|
|
38
|
+
}: TMoveProps = $props();
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
/** Контекст игры без деструктуризации — иначе selectable и chessTree «замораживаются» при первом рендере TreeViewer. */
|
|
41
|
+
const game = getGameContext();
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
/** Представляет обработчик выбора текущего узла по `mousedown`. */
|
|
44
|
+
function handleMoveMouseDown() {
|
|
45
|
+
if (!game.selectable || !moveNode) return;
|
|
46
|
+
game.chessTree.currentNode = chessNode;
|
|
43
47
|
|
|
44
|
-
onSelectNode();
|
|
48
|
+
game.onSelectNode();
|
|
49
|
+
game.onChessNodeSelected?.(game.chessTree.currentNode);
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
const
|
|
48
|
-
const
|
|
52
|
+
const moveNode = $derived(chessNode);
|
|
53
|
+
const previewHover = createPreviewHover({
|
|
54
|
+
setPreviewFen: game.setPreviewFen,
|
|
55
|
+
getFen: () => moveNode.data.fen,
|
|
56
|
+
getLastMove: () => moveNode.data.lastMove ?? null,
|
|
57
|
+
getDelayMs: () => game.previewHoverDelayMs,
|
|
58
|
+
});
|
|
59
|
+
const isCurrentMove = $derived(chessNode.id === game.chessTree.currentNode.id);
|
|
60
|
+
/** Представляет текст первого PGN-комментария узла после разбора `comments[0]`, синхронно с правкой строки под деревом. */
|
|
49
61
|
const firstCommentParsed = $derived(
|
|
50
|
-
|
|
62
|
+
moveNode.data.comments?.[0] !== undefined
|
|
63
|
+
? parseComment(moveNode.data.comments[0])
|
|
64
|
+
: null,
|
|
51
65
|
);
|
|
52
66
|
const firstCommentText = $derived(firstCommentParsed?.text);
|
|
53
67
|
const childMoves = $derived(chessNode.children);
|
|
54
|
-
const
|
|
68
|
+
const isForcedLineStart = $derived(chessNode.id === game.chessTree.forcedNodeId);
|
|
69
|
+
|
|
70
|
+
/** Представляет признак активного drill-down «варианты отдельно». */
|
|
71
|
+
const variationDrillDownActive = $derived(
|
|
72
|
+
game.chessTree.isVariationDrillDownActive(),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Представляет признак: развилка на этом уровне совпадает с узлом, для которого
|
|
77
|
+
* `ChessTree` рассчитывает акцент направляющих при текущем выборе.
|
|
78
|
+
*/
|
|
79
|
+
const isActiveForkForSelection = $derived(
|
|
80
|
+
shouldRenderSiblingVariations &&
|
|
81
|
+
parentNode !== null &&
|
|
82
|
+
(parentNode.children?.length ?? 0) > 1 &&
|
|
83
|
+
game.chessTree.currentGuideHighlightForkParent?.id === parentNode.id,
|
|
84
|
+
);
|
|
55
85
|
|
|
56
|
-
const spaceBlockClass = "
|
|
86
|
+
const spaceBlockClass = "px-3 py-2 !bg-gray-50/95";
|
|
57
87
|
const spaceInlineBlockClass = "p-2";
|
|
58
88
|
|
|
59
|
-
const isHighestLevel = depth === HIGHEST_LEVEL;
|
|
60
|
-
</script>
|
|
89
|
+
const isHighestLevel = $derived(depth === HIGHEST_LEVEL);
|
|
61
90
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
{/if}
|
|
113
|
-
{:else}
|
|
114
|
-
<ContextMenu.Item
|
|
115
|
-
class="gap-4"
|
|
116
|
-
onclick={() => (chessTree.forcedNodeId = chessNode.parentId ?? null)}
|
|
117
|
-
>Завершить главную линию <ContextMenu.Shortcut
|
|
118
|
-
><FlagIcon /></ContextMenu.Shortcut
|
|
119
|
-
></ContextMenu.Item
|
|
120
|
-
>
|
|
121
|
-
{/if}
|
|
122
|
-
<ContextMenu.Separator />
|
|
123
|
-
<ContextMenu.Item
|
|
124
|
-
class="gap-4 "
|
|
125
|
-
onclick={() => {
|
|
126
|
-
chessTree.deleteVariant(chessNode);
|
|
127
|
-
// Обновляем состояние игры после удаления
|
|
128
|
-
onDeleteVariant();
|
|
129
|
-
}}
|
|
130
|
-
>Удалить вариант <ContextMenu.Shortcut
|
|
131
|
-
><Trash2Icon /></ContextMenu.Shortcut
|
|
132
|
-
></ContextMenu.Item
|
|
133
|
-
>
|
|
134
|
-
</ContextMenu.Content>
|
|
135
|
-
</ContextMenu.Root>
|
|
136
|
-
{/snippet}
|
|
91
|
+
/**
|
|
92
|
+
* Представляет признак: в верхней сетке после белого полухода показывается пустая ячейка чёрных
|
|
93
|
+
* без ноды (партия оборвалась на ходе белых) — нижнюю границу рисуем только под двумя первыми колонками.
|
|
94
|
+
*/
|
|
95
|
+
const addsEmptyMainLineBlackCell = $derived(
|
|
96
|
+
isHighestLevel &&
|
|
97
|
+
moveNode.data.ply % 2 === 1 &&
|
|
98
|
+
!isForcedLineStart &&
|
|
99
|
+
(parentNode === null || parentNode.children.length <= 1) &&
|
|
100
|
+
!firstCommentText &&
|
|
101
|
+
(childMoves?.length ?? 0) === 0,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Представляет признак: верхняя строка — первый полуход чёрных от корня партии (нотация «1... SAN»).
|
|
106
|
+
*/
|
|
107
|
+
const isLeadingBlackHalfMoveFromRoot = $derived(
|
|
108
|
+
isHighestLevel &&
|
|
109
|
+
parentNode !== null &&
|
|
110
|
+
parentNode.data.ply === 0 &&
|
|
111
|
+
moveNode.data.ply % 2 === 0,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
/** Представляет true, если в контекстном меню показывать «Поднять приоритет». */
|
|
115
|
+
const showUpPriorityItem = $derived(depth > HIGHEST_LEVEL + 1);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Представляет признак: SAN главного варианта (children[0]) переносится в блок списка веток,
|
|
119
|
+
* чтобы не дублировать его перед группой (например «8. g3» только среди g3 / f3 / a3).
|
|
120
|
+
*/
|
|
121
|
+
const shouldRenderSanInsideVariationGroup = $derived(
|
|
122
|
+
!isHighestLevel &&
|
|
123
|
+
shouldRenderSiblingVariations &&
|
|
124
|
+
parentNode !== null &&
|
|
125
|
+
parentNode.children.length > 1 &&
|
|
126
|
+
parentNode.children[0]?.id === chessNode.id,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Представляет обработчик клика по зоне линий ветвления, открывающий режим «варианты отдельно».
|
|
131
|
+
*/
|
|
132
|
+
function onVariationLinesHit(e: MouseEvent) {
|
|
133
|
+
e.stopPropagation();
|
|
134
|
+
if (parentNode === null) return;
|
|
135
|
+
const selectFork = !game.chessTree.isCurrentNodeWithinForkSubtree(parentNode);
|
|
136
|
+
game.chessTree.openVariationDrillDown(parentNode, { selectFork });
|
|
137
|
+
game.onSelectNode();
|
|
138
|
+
game.onChessNodeSelected?.(game.chessTree.currentNode);
|
|
139
|
+
}
|
|
140
|
+
</script>
|
|
137
141
|
|
|
138
142
|
{#snippet emptyMove()}
|
|
139
143
|
<div class="flex items-center justify-center text-center">...</div>
|
|
140
144
|
{/snippet}
|
|
141
145
|
|
|
146
|
+
{#snippet emptyMainLineBlackCell()}
|
|
147
|
+
<div
|
|
148
|
+
class="tree-viewer-tail-no-node-cell flex min-w-0 items-center justify-center border-b-0 p-2 text-center"
|
|
149
|
+
></div>
|
|
150
|
+
{/snippet}
|
|
151
|
+
|
|
142
152
|
{#snippet fullMovesCounter(minus: number = 0)}
|
|
143
153
|
<span
|
|
144
154
|
class={{
|
|
145
155
|
"flex items-center justify-center text-center": isHighestLevel,
|
|
146
156
|
inline: !isHighestLevel,
|
|
157
|
+
"border-b border-gray-300": addsEmptyMainLineBlackCell && isHighestLevel,
|
|
158
|
+
"tree-viewer-overflow-tail-border":
|
|
159
|
+
addsEmptyMainLineBlackCell && isHighestLevel,
|
|
147
160
|
}}
|
|
148
161
|
>
|
|
149
|
-
{
|
|
162
|
+
{moveNode.data.fullMoves - minus}{!isHighestLevel &&
|
|
163
|
+
moveNode.data.ply % 2 === 1
|
|
150
164
|
? "..."
|
|
151
|
-
: !isHighestLevel &&
|
|
165
|
+
: !isHighestLevel && moveNode.data.ply % 2 === 0
|
|
152
166
|
? "..."
|
|
153
167
|
: ""}
|
|
154
168
|
</span>
|
|
155
169
|
{/snippet}
|
|
156
170
|
|
|
157
|
-
{#snippet
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
{#if index > 0}<br />{/if}{line}
|
|
168
|
-
{/each}
|
|
169
|
-
</div>
|
|
171
|
+
{#snippet variationMoveRow(child: ChessTreeNode)}
|
|
172
|
+
<Move
|
|
173
|
+
{pieceSet}
|
|
174
|
+
chessNode={child}
|
|
175
|
+
shouldReserveEmptyMoveCell={parentNode !== null &&
|
|
176
|
+
parentNode.children.length > 1}
|
|
177
|
+
depth={depth + 1}
|
|
178
|
+
shouldRenderSiblingVariations={false}
|
|
179
|
+
{parentNode}
|
|
180
|
+
/>
|
|
170
181
|
{/snippet}
|
|
171
182
|
|
|
172
183
|
{#if isHighestLevel}
|
|
173
|
-
{#if
|
|
184
|
+
{#if moveNode.data.ply % 2 === 1}
|
|
174
185
|
{@render fullMovesCounter()}
|
|
175
186
|
{/if}
|
|
176
187
|
|
|
177
|
-
{#if
|
|
178
|
-
|
|
188
|
+
{#if variationDrillDownActive && moveNode.data.ply % 2 === 0 && game.chessTree.drillForkParent !== null && game.chessTree.drillForkParent.children[0]?.id === chessNode.id && moveNode.parentId}
|
|
189
|
+
<span
|
|
190
|
+
class={{
|
|
191
|
+
"flex items-center justify-center text-center": isHighestLevel,
|
|
192
|
+
}}
|
|
193
|
+
>
|
|
194
|
+
{moveNode.data.fullMoves}
|
|
195
|
+
</span>
|
|
196
|
+
{@render emptyMove()}
|
|
179
197
|
{/if}
|
|
180
|
-
|
|
198
|
+
|
|
199
|
+
{#if isLeadingBlackHalfMoveFromRoot}
|
|
200
|
+
<span
|
|
201
|
+
class={{
|
|
202
|
+
"flex items-center justify-center text-center": isHighestLevel,
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
{Math.max(1, moveNode.data.fullMoves - 1)}
|
|
206
|
+
</span>
|
|
181
207
|
{@render emptyMove()}
|
|
182
208
|
{/if}
|
|
183
209
|
|
|
210
|
+
{#if moveNode.parentId}
|
|
211
|
+
<MoveSanWithMenu
|
|
212
|
+
{chessNode}
|
|
213
|
+
{parentNode}
|
|
214
|
+
{pieceSet}
|
|
215
|
+
selectable={game.selectable}
|
|
216
|
+
{isHighestLevel}
|
|
217
|
+
{isCurrentMove}
|
|
218
|
+
previewHover={{
|
|
219
|
+
onmouseenter: previewHover.onmouseenter,
|
|
220
|
+
onmouseleave: previewHover.onmouseleave,
|
|
221
|
+
}}
|
|
222
|
+
onMoveMouseDown={handleMoveMouseDown}
|
|
223
|
+
{spaceInlineBlockClass}
|
|
224
|
+
nags={moveNode.data.nags}
|
|
225
|
+
chessTree={game.chessTree}
|
|
226
|
+
{showUpPriorityItem}
|
|
227
|
+
onDeleteVariant={game.onDeleteVariant}
|
|
228
|
+
mainRowBottomEdge={addsEmptyMainLineBlackCell}
|
|
229
|
+
/>
|
|
230
|
+
{/if}
|
|
231
|
+
{#if isHighestLevel && moveNode.data.ply % 2 === 1}
|
|
232
|
+
{#if isForcedLineStart || (parentNode != null && parentNode.children.length > 1) || !!firstCommentText}
|
|
233
|
+
{@render emptyMove()}
|
|
234
|
+
{:else if (childMoves?.length ?? 0) === 0}
|
|
235
|
+
{@render emptyMainLineBlackCell()}
|
|
236
|
+
{/if}
|
|
237
|
+
{/if}
|
|
238
|
+
|
|
184
239
|
{#if firstCommentText}
|
|
185
|
-
{#if
|
|
186
|
-
{
|
|
187
|
-
{#if !
|
|
240
|
+
{#if moveNode.data.ply % 2 === 1}
|
|
241
|
+
<MoveComment text={firstCommentText} {isHighestLevel} {spaceBlockClass} />
|
|
242
|
+
{#if !shouldReserveEmptyMoveCell && childMoves?.[0] && !isForcedLineStart}
|
|
188
243
|
{@render fullMovesCounter()}
|
|
189
244
|
{@render emptyMove()}
|
|
190
245
|
{/if}
|
|
191
246
|
{/if}
|
|
192
247
|
|
|
193
|
-
{#if
|
|
194
|
-
{
|
|
248
|
+
{#if moveNode.data.ply % 2 === 0}
|
|
249
|
+
<MoveComment text={firstCommentText} {isHighestLevel} {spaceBlockClass} />
|
|
195
250
|
{/if}
|
|
196
251
|
{/if}
|
|
197
252
|
{:else}
|
|
198
|
-
{#if
|
|
253
|
+
{#if moveNode.data.ply % 2 === 0 && game.chessTree.forcedNodeId === moveNode.parentId && parentNode?.children[0].id === moveNode.id}
|
|
199
254
|
{@render fullMovesCounter(1)}
|
|
200
255
|
{/if}
|
|
201
256
|
|
|
202
|
-
{
|
|
257
|
+
{#if !shouldRenderSanInsideVariationGroup}
|
|
258
|
+
<MoveSanWithMenu
|
|
259
|
+
{chessNode}
|
|
260
|
+
{parentNode}
|
|
261
|
+
{pieceSet}
|
|
262
|
+
selectable={game.selectable}
|
|
263
|
+
{isHighestLevel}
|
|
264
|
+
{isCurrentMove}
|
|
265
|
+
previewHover={{
|
|
266
|
+
onmouseenter: previewHover.onmouseenter,
|
|
267
|
+
onmouseleave: previewHover.onmouseleave,
|
|
268
|
+
}}
|
|
269
|
+
onMoveMouseDown={handleMoveMouseDown}
|
|
270
|
+
{spaceInlineBlockClass}
|
|
271
|
+
nags={moveNode.data.nags}
|
|
272
|
+
chessTree={game.chessTree}
|
|
273
|
+
{showUpPriorityItem}
|
|
274
|
+
onDeleteVariant={game.onDeleteVariant}
|
|
275
|
+
/>
|
|
276
|
+
{/if}
|
|
203
277
|
{#if firstCommentText}
|
|
204
|
-
{#if
|
|
205
|
-
{
|
|
206
|
-
{#if !
|
|
278
|
+
{#if moveNode.data.ply % 2 === 1}
|
|
279
|
+
<MoveComment text={firstCommentText} {isHighestLevel} {spaceBlockClass} />
|
|
280
|
+
{#if !shouldReserveEmptyMoveCell && childMoves?.[0] && !shouldRenderSanInsideVariationGroup}
|
|
207
281
|
{@render fullMovesCounter()}
|
|
208
282
|
{/if}
|
|
209
283
|
{/if}
|
|
210
284
|
|
|
211
|
-
{#if
|
|
212
|
-
{
|
|
285
|
+
{#if moveNode.data.ply % 2 === 0}
|
|
286
|
+
<MoveComment text={firstCommentText} {isHighestLevel} {spaceBlockClass} />
|
|
213
287
|
{/if}
|
|
214
288
|
{/if}
|
|
215
289
|
{/if}
|
|
216
290
|
|
|
217
|
-
{#if parentNode && parentNode?.children.length > 1 &&
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
{
|
|
222
|
-
{
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
[spaceBlockClass]: isHighestLevel,
|
|
229
|
-
"tracking-tight": true,
|
|
230
|
-
}}
|
|
231
|
-
>
|
|
232
|
-
<Move
|
|
233
|
-
{pieceSet}
|
|
234
|
-
chessNode={child}
|
|
235
|
-
needSpace={parentNode?.children.length > 1}
|
|
236
|
-
depth={depth + 1}
|
|
237
|
-
needRenderParentChildrens={false}
|
|
238
|
-
{parentNode}
|
|
239
|
-
></Move>
|
|
240
|
-
</div>
|
|
241
|
-
{/if}
|
|
242
|
-
{/each}
|
|
243
|
-
{/if}
|
|
244
|
-
{#if !isHighestLevel}
|
|
245
|
-
<span>)</span>
|
|
246
|
-
{/if}
|
|
247
|
-
</div>
|
|
291
|
+
{#if parentNode && parentNode?.children.length > 1 && shouldRenderSiblingVariations}
|
|
292
|
+
{@const showVariationDrillExpand = !game.chessTree.isNodeOnMainLine(chessNode)}
|
|
293
|
+
<VariationGroup
|
|
294
|
+
{parentNode}
|
|
295
|
+
{isHighestLevel}
|
|
296
|
+
{isActiveForkForSelection}
|
|
297
|
+
{showVariationDrillExpand}
|
|
298
|
+
{onVariationLinesHit}
|
|
299
|
+
{variationMoveRow}
|
|
300
|
+
isBranchOnCurrentPath={(n) => game.chessTree.isCurrentOnPathFromNode(n)}
|
|
301
|
+
/>
|
|
248
302
|
{/if}
|
|
249
303
|
|
|
250
|
-
{#if
|
|
251
|
-
<div
|
|
304
|
+
{#if isForcedLineStart}
|
|
305
|
+
<div
|
|
306
|
+
class={{
|
|
307
|
+
"col-span-3 col-start-1 w-full min-w-0": isHighestLevel,
|
|
308
|
+
"block w-full min-w-0 basis-full shrink-0": !isHighestLevel,
|
|
309
|
+
}}
|
|
310
|
+
>
|
|
252
311
|
<div
|
|
253
312
|
class={{
|
|
254
|
-
|
|
313
|
+
"block w-full min-w-0": !isHighestLevel,
|
|
255
314
|
[spaceBlockClass]: isHighestLevel,
|
|
256
315
|
"tracking-tight": true,
|
|
257
316
|
}}
|
|
@@ -259,25 +318,35 @@
|
|
|
259
318
|
<Move
|
|
260
319
|
{pieceSet}
|
|
261
320
|
chessNode={chessNode.children[0]}
|
|
262
|
-
|
|
321
|
+
shouldReserveEmptyMoveCell={chessNode.children.length > 1}
|
|
263
322
|
depth={depth + 1}
|
|
264
|
-
|
|
323
|
+
shouldRenderSiblingVariations={true}
|
|
265
324
|
parentNode={chessNode}
|
|
266
325
|
></Move>
|
|
267
326
|
</div>
|
|
268
327
|
</div>
|
|
269
328
|
{/if}
|
|
270
329
|
|
|
271
|
-
{#if
|
|
330
|
+
{#if shouldReserveEmptyMoveCell && moveNode.data.ply % 2 === 1 && isHighestLevel && !isForcedLineStart && childMoves?.[0]}
|
|
272
331
|
{@render fullMovesCounter()}
|
|
273
332
|
{@render emptyMove()}
|
|
274
333
|
{/if}
|
|
275
|
-
{#if childMoves?.[0]}
|
|
276
|
-
|
|
334
|
+
{#if variationDrillDownActive && isHighestLevel && moveNode.data.ply % 2 === 1 && !shouldReserveEmptyMoveCell && childMoves?.[0] && !isForcedLineStart && game.chessTree.drillForkParent !== null && game.chessTree.drillForkParent.children[0]?.id === chessNode.id}
|
|
335
|
+
<span
|
|
336
|
+
class={{
|
|
337
|
+
"flex items-center justify-center text-center": isHighestLevel,
|
|
338
|
+
}}
|
|
339
|
+
>
|
|
340
|
+
{moveNode.data.fullMoves}
|
|
341
|
+
</span>
|
|
342
|
+
{@render emptyMove()}
|
|
343
|
+
{/if}
|
|
344
|
+
{#if childMoves?.[0] && !shouldRenderSanInsideVariationGroup}
|
|
345
|
+
{#if !isForcedLineStart}
|
|
277
346
|
<Move
|
|
278
347
|
{pieceSet}
|
|
279
348
|
chessNode={chessNode.children[0]}
|
|
280
|
-
|
|
349
|
+
shouldReserveEmptyMoveCell={childMoves?.length > 1}
|
|
281
350
|
{depth}
|
|
282
351
|
parentNode={chessNode}
|
|
283
352
|
></Move>
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import type { ChessTreeNode } from "../(models)/chessTreeNode.js";
|
|
2
2
|
import Move from "./Move.svelte";
|
|
3
3
|
import type { PieceSet } from "@connectorvol/shared";
|
|
4
|
-
|
|
4
|
+
type TMoveProps = {
|
|
5
|
+
/** Возвращает узел хода в дереве партии. */
|
|
5
6
|
chessNode: ChessTreeNode;
|
|
7
|
+
/** Возвращает глубину вложенности относительно корня отображения. */
|
|
6
8
|
depth: number;
|
|
9
|
+
/** Возвращает родительский узел или null для корня. */
|
|
7
10
|
parentNode: ChessTreeNode | null;
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
/** Возвращает true, если нужна пустая ячейка хода (сетка верхнего уровня). */
|
|
12
|
+
shouldReserveEmptyMoveCell: boolean;
|
|
13
|
+
/** Возвращает true, если нужно отрисовать соседние варианты у развилки. */
|
|
14
|
+
shouldRenderSiblingVariations?: boolean;
|
|
15
|
+
/** Возвращает набор фигур для иконок SAN. */
|
|
10
16
|
pieceSet: PieceSet;
|
|
11
|
-
}
|
|
12
|
-
declare const Move: import("svelte").Component<
|
|
17
|
+
};
|
|
18
|
+
declare const Move: import("svelte").Component<TMoveProps, {}, "">;
|
|
13
19
|
type Move = ReturnType<typeof Move>;
|
|
14
20
|
export default Move;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
type TMoveCommentProps = {
|
|
3
|
+
/** Возвращает текст комментария (возможны переводы строк). */
|
|
4
|
+
text: string;
|
|
5
|
+
/** Возвращает true, если строка в верхнем уровне сетки дерева. */
|
|
6
|
+
isHighestLevel: boolean;
|
|
7
|
+
/** Возвращает класс фона/отступов для уровня «верхняя строка». */
|
|
8
|
+
spaceBlockClass: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
text,
|
|
13
|
+
isHighestLevel,
|
|
14
|
+
spaceBlockClass,
|
|
15
|
+
}: TMoveCommentProps = $props();
|
|
16
|
+
|
|
17
|
+
/** Представляет строки комментария после разбиения по `\n`. */
|
|
18
|
+
const commentLines = $derived(text.split("\n"));
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
class={{
|
|
23
|
+
"select-text": true,
|
|
24
|
+
"col-span-3 flex flex-col": isHighestLevel,
|
|
25
|
+
inline: !isHighestLevel,
|
|
26
|
+
[spaceBlockClass]: isHighestLevel,
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
{#each commentLines as line, index (index)}
|
|
30
|
+
{#if index > 0}<br />{/if}{line}
|
|
31
|
+
{/each}
|
|
32
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type TMoveCommentProps = {
|
|
2
|
+
/** Возвращает текст комментария (возможны переводы строк). */
|
|
3
|
+
text: string;
|
|
4
|
+
/** Возвращает true, если строка в верхнем уровне сетки дерева. */
|
|
5
|
+
isHighestLevel: boolean;
|
|
6
|
+
/** Возвращает класс фона/отступов для уровня «верхняя строка». */
|
|
7
|
+
spaceBlockClass: string;
|
|
8
|
+
};
|
|
9
|
+
declare const MoveComment: import("svelte").Component<TMoveCommentProps, {}, "">;
|
|
10
|
+
type MoveComment = ReturnType<typeof MoveComment>;
|
|
11
|
+
export default MoveComment;
|