@connectorvol/tree 2.1.2 → 4.0.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 +247 -171
- 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 +843 -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,323 @@
|
|
|
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
|
-
const {
|
|
40
|
+
const {
|
|
41
|
+
chessTree,
|
|
42
|
+
onSelectNode,
|
|
43
|
+
onDeleteVariant,
|
|
44
|
+
onChessNodeSelected,
|
|
45
|
+
selectable,
|
|
46
|
+
setPreviewFen,
|
|
47
|
+
previewHoverDelayMs,
|
|
48
|
+
} = getGameContext();
|
|
39
49
|
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
/** Представляет обработчик выбора текущего узла по `mousedown`. */
|
|
51
|
+
function handleMoveMouseDown() {
|
|
52
|
+
if (!selectable || !moveNode) return;
|
|
42
53
|
chessTree.currentNode = chessNode;
|
|
43
54
|
|
|
44
55
|
onSelectNode();
|
|
56
|
+
onChessNodeSelected?.(chessTree.currentNode);
|
|
45
57
|
}
|
|
46
58
|
|
|
47
|
-
const
|
|
48
|
-
const
|
|
59
|
+
const moveNode = $derived(chessNode);
|
|
60
|
+
const previewHover = createPreviewHover({
|
|
61
|
+
setPreviewFen,
|
|
62
|
+
getFen: () => moveNode.data.fen,
|
|
63
|
+
getLastMove: () => moveNode.data.lastMove ?? null,
|
|
64
|
+
getDelayMs: () => previewHoverDelayMs,
|
|
65
|
+
});
|
|
66
|
+
const isCurrentMove = $derived(chessNode.id === chessTree.currentNode.id);
|
|
67
|
+
/** Представляет текст первого PGN-комментария узла после разбора `comments[0]`, синхронно с правкой строки под деревом. */
|
|
49
68
|
const firstCommentParsed = $derived(
|
|
50
|
-
|
|
69
|
+
moveNode.data.comments?.[0] !== undefined
|
|
70
|
+
? parseComment(moveNode.data.comments[0])
|
|
71
|
+
: null,
|
|
51
72
|
);
|
|
52
73
|
const firstCommentText = $derived(firstCommentParsed?.text);
|
|
53
74
|
const childMoves = $derived(chessNode.children);
|
|
54
|
-
const
|
|
75
|
+
const isForcedLineStart = $derived(chessNode.id === chessTree.forcedNodeId);
|
|
55
76
|
|
|
56
|
-
|
|
77
|
+
/** Представляет признак активного drill-down «варианты отдельно». */
|
|
78
|
+
const variationDrillDownActive = $derived(
|
|
79
|
+
chessTree.isVariationDrillDownActive(),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Представляет признак: развилка на этом уровне совпадает с узлом, для которого
|
|
84
|
+
* `ChessTree` рассчитывает акцент направляющих при текущем выборе.
|
|
85
|
+
*/
|
|
86
|
+
const isActiveForkForSelection = $derived(
|
|
87
|
+
shouldRenderSiblingVariations &&
|
|
88
|
+
parentNode !== null &&
|
|
89
|
+
(parentNode.children?.length ?? 0) > 1 &&
|
|
90
|
+
chessTree.currentGuideHighlightForkParent?.id === parentNode.id,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const spaceBlockClass = "px-3 py-2 !bg-gray-50/95";
|
|
57
94
|
const spaceInlineBlockClass = "p-2";
|
|
58
95
|
|
|
59
|
-
const isHighestLevel = depth === HIGHEST_LEVEL;
|
|
60
|
-
</script>
|
|
96
|
+
const isHighestLevel = $derived(depth === HIGHEST_LEVEL);
|
|
61
97
|
|
|
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}
|
|
98
|
+
/**
|
|
99
|
+
* Представляет признак: в верхней сетке после белого полухода показывается пустая ячейка чёрных
|
|
100
|
+
* без ноды (партия оборвалась на ходе белых) — нижнюю границу рисуем только под двумя первыми колонками.
|
|
101
|
+
*/
|
|
102
|
+
const addsEmptyMainLineBlackCell = $derived(
|
|
103
|
+
isHighestLevel &&
|
|
104
|
+
moveNode.data.ply % 2 === 1 &&
|
|
105
|
+
!isForcedLineStart &&
|
|
106
|
+
(parentNode === null || parentNode.children.length <= 1) &&
|
|
107
|
+
!firstCommentText &&
|
|
108
|
+
(childMoves?.length ?? 0) === 0,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Представляет признак: верхняя строка — первый полуход чёрных от корня партии (нотация «1... SAN»).
|
|
113
|
+
*/
|
|
114
|
+
const isLeadingBlackHalfMoveFromRoot = $derived(
|
|
115
|
+
isHighestLevel &&
|
|
116
|
+
parentNode !== null &&
|
|
117
|
+
parentNode.data.ply === 0 &&
|
|
118
|
+
moveNode.data.ply % 2 === 0,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
/** Представляет true, если в контекстном меню показывать «Поднять приоритет». */
|
|
122
|
+
const showUpPriorityItem = $derived(depth > HIGHEST_LEVEL + 1);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Представляет признак: SAN главного варианта (children[0]) переносится в блок списка веток,
|
|
126
|
+
* чтобы не дублировать его перед группой (например «8. g3» только среди g3 / f3 / a3).
|
|
127
|
+
*/
|
|
128
|
+
const shouldRenderSanInsideVariationGroup = $derived(
|
|
129
|
+
!isHighestLevel &&
|
|
130
|
+
shouldRenderSiblingVariations &&
|
|
131
|
+
parentNode !== null &&
|
|
132
|
+
parentNode.children.length > 1 &&
|
|
133
|
+
parentNode.children[0]?.id === chessNode.id,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Представляет обработчик клика по зоне линий ветвления, открывающий режим «варианты отдельно».
|
|
138
|
+
*/
|
|
139
|
+
function onVariationLinesHit(e: MouseEvent) {
|
|
140
|
+
e.stopPropagation();
|
|
141
|
+
if (parentNode === null) return;
|
|
142
|
+
const selectFork = !chessTree.isCurrentNodeWithinForkSubtree(parentNode);
|
|
143
|
+
chessTree.openVariationDrillDown(parentNode, { selectFork });
|
|
144
|
+
onSelectNode();
|
|
145
|
+
onChessNodeSelected?.(chessTree.currentNode);
|
|
146
|
+
}
|
|
147
|
+
</script>
|
|
137
148
|
|
|
138
149
|
{#snippet emptyMove()}
|
|
139
150
|
<div class="flex items-center justify-center text-center">...</div>
|
|
140
151
|
{/snippet}
|
|
141
152
|
|
|
153
|
+
{#snippet emptyMainLineBlackCell()}
|
|
154
|
+
<div
|
|
155
|
+
class="tree-viewer-tail-no-node-cell flex min-w-0 items-center justify-center border-b-0 p-2 text-center"
|
|
156
|
+
></div>
|
|
157
|
+
{/snippet}
|
|
158
|
+
|
|
142
159
|
{#snippet fullMovesCounter(minus: number = 0)}
|
|
143
160
|
<span
|
|
144
161
|
class={{
|
|
145
162
|
"flex items-center justify-center text-center": isHighestLevel,
|
|
146
163
|
inline: !isHighestLevel,
|
|
164
|
+
"border-b border-gray-300": addsEmptyMainLineBlackCell && isHighestLevel,
|
|
165
|
+
"tree-viewer-overflow-tail-border":
|
|
166
|
+
addsEmptyMainLineBlackCell && isHighestLevel,
|
|
147
167
|
}}
|
|
148
168
|
>
|
|
149
|
-
{
|
|
169
|
+
{moveNode.data.fullMoves - minus}{!isHighestLevel &&
|
|
170
|
+
moveNode.data.ply % 2 === 1
|
|
150
171
|
? "..."
|
|
151
|
-
: !isHighestLevel &&
|
|
172
|
+
: !isHighestLevel && moveNode.data.ply % 2 === 0
|
|
152
173
|
? "..."
|
|
153
174
|
: ""}
|
|
154
175
|
</span>
|
|
155
176
|
{/snippet}
|
|
156
177
|
|
|
157
|
-
{#snippet
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
{#if index > 0}<br />{/if}{line}
|
|
168
|
-
{/each}
|
|
169
|
-
</div>
|
|
178
|
+
{#snippet variationMoveRow(child: ChessTreeNode)}
|
|
179
|
+
<Move
|
|
180
|
+
{pieceSet}
|
|
181
|
+
chessNode={child}
|
|
182
|
+
shouldReserveEmptyMoveCell={parentNode !== null &&
|
|
183
|
+
parentNode.children.length > 1}
|
|
184
|
+
depth={depth + 1}
|
|
185
|
+
shouldRenderSiblingVariations={false}
|
|
186
|
+
{parentNode}
|
|
187
|
+
/>
|
|
170
188
|
{/snippet}
|
|
171
189
|
|
|
172
190
|
{#if isHighestLevel}
|
|
173
|
-
{#if
|
|
191
|
+
{#if moveNode.data.ply % 2 === 1}
|
|
174
192
|
{@render fullMovesCounter()}
|
|
175
193
|
{/if}
|
|
176
194
|
|
|
177
|
-
{#if
|
|
178
|
-
|
|
195
|
+
{#if variationDrillDownActive && moveNode.data.ply % 2 === 0 && chessTree.drillForkParent !== null && chessTree.drillForkParent.children[0]?.id === chessNode.id && moveNode.parentId}
|
|
196
|
+
<span
|
|
197
|
+
class={{
|
|
198
|
+
"flex items-center justify-center text-center": isHighestLevel,
|
|
199
|
+
}}
|
|
200
|
+
>
|
|
201
|
+
{moveNode.data.fullMoves}
|
|
202
|
+
</span>
|
|
203
|
+
{@render emptyMove()}
|
|
179
204
|
{/if}
|
|
180
|
-
|
|
205
|
+
|
|
206
|
+
{#if isLeadingBlackHalfMoveFromRoot}
|
|
207
|
+
<span
|
|
208
|
+
class={{
|
|
209
|
+
"flex items-center justify-center text-center": isHighestLevel,
|
|
210
|
+
}}
|
|
211
|
+
>
|
|
212
|
+
{Math.max(1, moveNode.data.fullMoves - 1)}
|
|
213
|
+
</span>
|
|
181
214
|
{@render emptyMove()}
|
|
182
215
|
{/if}
|
|
183
216
|
|
|
217
|
+
{#if moveNode.parentId}
|
|
218
|
+
<MoveSanWithMenu
|
|
219
|
+
{chessNode}
|
|
220
|
+
{parentNode}
|
|
221
|
+
{pieceSet}
|
|
222
|
+
{selectable}
|
|
223
|
+
{isHighestLevel}
|
|
224
|
+
{isCurrentMove}
|
|
225
|
+
previewHover={{
|
|
226
|
+
onmouseenter: previewHover.onmouseenter,
|
|
227
|
+
onmouseleave: previewHover.onmouseleave,
|
|
228
|
+
}}
|
|
229
|
+
onMoveMouseDown={handleMoveMouseDown}
|
|
230
|
+
{spaceInlineBlockClass}
|
|
231
|
+
nags={moveNode.data.nags}
|
|
232
|
+
{chessTree}
|
|
233
|
+
{showUpPriorityItem}
|
|
234
|
+
{onDeleteVariant}
|
|
235
|
+
mainRowBottomEdge={addsEmptyMainLineBlackCell}
|
|
236
|
+
/>
|
|
237
|
+
{/if}
|
|
238
|
+
{#if isHighestLevel && moveNode.data.ply % 2 === 1}
|
|
239
|
+
{#if isForcedLineStart || (parentNode != null && parentNode.children.length > 1) || !!firstCommentText}
|
|
240
|
+
{@render emptyMove()}
|
|
241
|
+
{:else if (childMoves?.length ?? 0) === 0}
|
|
242
|
+
{@render emptyMainLineBlackCell()}
|
|
243
|
+
{/if}
|
|
244
|
+
{/if}
|
|
245
|
+
|
|
184
246
|
{#if firstCommentText}
|
|
185
|
-
{#if
|
|
186
|
-
{
|
|
187
|
-
{#if !
|
|
247
|
+
{#if moveNode.data.ply % 2 === 1}
|
|
248
|
+
<MoveComment text={firstCommentText} {isHighestLevel} {spaceBlockClass} />
|
|
249
|
+
{#if !shouldReserveEmptyMoveCell && childMoves?.[0] && !isForcedLineStart}
|
|
188
250
|
{@render fullMovesCounter()}
|
|
189
251
|
{@render emptyMove()}
|
|
190
252
|
{/if}
|
|
191
253
|
{/if}
|
|
192
254
|
|
|
193
|
-
{#if
|
|
194
|
-
{
|
|
255
|
+
{#if moveNode.data.ply % 2 === 0}
|
|
256
|
+
<MoveComment text={firstCommentText} {isHighestLevel} {spaceBlockClass} />
|
|
195
257
|
{/if}
|
|
196
258
|
{/if}
|
|
197
259
|
{:else}
|
|
198
|
-
{#if
|
|
260
|
+
{#if moveNode.data.ply % 2 === 0 && chessTree.forcedNodeId === moveNode.parentId && parentNode?.children[0].id === moveNode.id}
|
|
199
261
|
{@render fullMovesCounter(1)}
|
|
200
262
|
{/if}
|
|
201
263
|
|
|
202
|
-
{
|
|
264
|
+
{#if !shouldRenderSanInsideVariationGroup}
|
|
265
|
+
<MoveSanWithMenu
|
|
266
|
+
{chessNode}
|
|
267
|
+
{parentNode}
|
|
268
|
+
{pieceSet}
|
|
269
|
+
{selectable}
|
|
270
|
+
{isHighestLevel}
|
|
271
|
+
{isCurrentMove}
|
|
272
|
+
previewHover={{
|
|
273
|
+
onmouseenter: previewHover.onmouseenter,
|
|
274
|
+
onmouseleave: previewHover.onmouseleave,
|
|
275
|
+
}}
|
|
276
|
+
onMoveMouseDown={handleMoveMouseDown}
|
|
277
|
+
{spaceInlineBlockClass}
|
|
278
|
+
nags={moveNode.data.nags}
|
|
279
|
+
{chessTree}
|
|
280
|
+
{showUpPriorityItem}
|
|
281
|
+
{onDeleteVariant}
|
|
282
|
+
/>
|
|
283
|
+
{/if}
|
|
203
284
|
{#if firstCommentText}
|
|
204
|
-
{#if
|
|
205
|
-
{
|
|
206
|
-
{#if !
|
|
285
|
+
{#if moveNode.data.ply % 2 === 1}
|
|
286
|
+
<MoveComment text={firstCommentText} {isHighestLevel} {spaceBlockClass} />
|
|
287
|
+
{#if !shouldReserveEmptyMoveCell && childMoves?.[0] && !shouldRenderSanInsideVariationGroup}
|
|
207
288
|
{@render fullMovesCounter()}
|
|
208
289
|
{/if}
|
|
209
290
|
{/if}
|
|
210
291
|
|
|
211
|
-
{#if
|
|
212
|
-
{
|
|
292
|
+
{#if moveNode.data.ply % 2 === 0}
|
|
293
|
+
<MoveComment text={firstCommentText} {isHighestLevel} {spaceBlockClass} />
|
|
213
294
|
{/if}
|
|
214
295
|
{/if}
|
|
215
296
|
{/if}
|
|
216
297
|
|
|
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>
|
|
298
|
+
{#if parentNode && parentNode?.children.length > 1 && shouldRenderSiblingVariations}
|
|
299
|
+
{@const showVariationDrillExpand = !chessTree.isNodeOnMainLine(chessNode)}
|
|
300
|
+
<VariationGroup
|
|
301
|
+
{parentNode}
|
|
302
|
+
{isHighestLevel}
|
|
303
|
+
{isActiveForkForSelection}
|
|
304
|
+
{showVariationDrillExpand}
|
|
305
|
+
{onVariationLinesHit}
|
|
306
|
+
{variationMoveRow}
|
|
307
|
+
isBranchOnCurrentPath={(n) => chessTree.isCurrentOnPathFromNode(n)}
|
|
308
|
+
/>
|
|
248
309
|
{/if}
|
|
249
310
|
|
|
250
|
-
{#if
|
|
251
|
-
<div
|
|
311
|
+
{#if isForcedLineStart}
|
|
312
|
+
<div
|
|
313
|
+
class={{
|
|
314
|
+
"col-span-3 col-start-1 w-full min-w-0": isHighestLevel,
|
|
315
|
+
"block w-full min-w-0 basis-full shrink-0": !isHighestLevel,
|
|
316
|
+
}}
|
|
317
|
+
>
|
|
252
318
|
<div
|
|
253
319
|
class={{
|
|
254
|
-
|
|
320
|
+
"block w-full min-w-0": !isHighestLevel,
|
|
255
321
|
[spaceBlockClass]: isHighestLevel,
|
|
256
322
|
"tracking-tight": true,
|
|
257
323
|
}}
|
|
@@ -259,25 +325,35 @@
|
|
|
259
325
|
<Move
|
|
260
326
|
{pieceSet}
|
|
261
327
|
chessNode={chessNode.children[0]}
|
|
262
|
-
|
|
328
|
+
shouldReserveEmptyMoveCell={chessNode.children.length > 1}
|
|
263
329
|
depth={depth + 1}
|
|
264
|
-
|
|
330
|
+
shouldRenderSiblingVariations={true}
|
|
265
331
|
parentNode={chessNode}
|
|
266
332
|
></Move>
|
|
267
333
|
</div>
|
|
268
334
|
</div>
|
|
269
335
|
{/if}
|
|
270
336
|
|
|
271
|
-
{#if
|
|
337
|
+
{#if shouldReserveEmptyMoveCell && moveNode.data.ply % 2 === 1 && isHighestLevel && !isForcedLineStart && childMoves?.[0]}
|
|
272
338
|
{@render fullMovesCounter()}
|
|
273
339
|
{@render emptyMove()}
|
|
274
340
|
{/if}
|
|
275
|
-
{#if childMoves?.[0]}
|
|
276
|
-
|
|
341
|
+
{#if variationDrillDownActive && isHighestLevel && moveNode.data.ply % 2 === 1 && !shouldReserveEmptyMoveCell && childMoves?.[0] && !isForcedLineStart && chessTree.drillForkParent !== null && chessTree.drillForkParent.children[0]?.id === chessNode.id}
|
|
342
|
+
<span
|
|
343
|
+
class={{
|
|
344
|
+
"flex items-center justify-center text-center": isHighestLevel,
|
|
345
|
+
}}
|
|
346
|
+
>
|
|
347
|
+
{moveNode.data.fullMoves}
|
|
348
|
+
</span>
|
|
349
|
+
{@render emptyMove()}
|
|
350
|
+
{/if}
|
|
351
|
+
{#if childMoves?.[0] && !shouldRenderSanInsideVariationGroup}
|
|
352
|
+
{#if !isForcedLineStart}
|
|
277
353
|
<Move
|
|
278
354
|
{pieceSet}
|
|
279
355
|
chessNode={chessNode.children[0]}
|
|
280
|
-
|
|
356
|
+
shouldReserveEmptyMoveCell={childMoves?.length > 1}
|
|
281
357
|
{depth}
|
|
282
358
|
parentNode={chessNode}
|
|
283
359
|
></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;
|