@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
|
@@ -2,12 +2,70 @@ import type { ChessTreeNode, TChessTree } from "../(models)/chessTreeNode.js";
|
|
|
2
2
|
export declare class ChessTree {
|
|
3
3
|
rootNode: TChessTree;
|
|
4
4
|
currentNode: ChessTreeNode;
|
|
5
|
+
/** Представляет признак: меню выхода из drill-down у активной крошки сейчас открыто. */
|
|
6
|
+
drillExitPromptOpen: boolean;
|
|
5
7
|
forcedNodeId: string | null;
|
|
8
|
+
/**
|
|
9
|
+
* Представляет стек родительских узлов развилок при drill-down «варианты отдельно»;
|
|
10
|
+
* пустой массив — полное дерево в основной области.
|
|
11
|
+
*/
|
|
12
|
+
private drillForkStack;
|
|
6
13
|
/** Представляет индекс узлов по id для O(1) доступа к родителю по parentId. */
|
|
7
14
|
private nodeById;
|
|
15
|
+
/** Представляет набор id узлов главной линии для O(1) проверки принадлежности. */
|
|
16
|
+
private mainLineNodeIds;
|
|
17
|
+
/** Представляет набор id узлов на пути от текущего хода к корню партии. */
|
|
18
|
+
private currentPathNodeIds;
|
|
19
|
+
/** Представляет текущую развилку, для которой нужно подсвечивать направляющие. */
|
|
20
|
+
private guideHighlightForkParent;
|
|
8
21
|
constructor(rootNode: TChessTree);
|
|
22
|
+
/**
|
|
23
|
+
* Представляет замену корня партии (полное дерево, заголовки, комментарии)
|
|
24
|
+
* и сброс навигации к первому ходу после парсинга PGN.
|
|
25
|
+
*/
|
|
26
|
+
replaceRootTree(nextRoot: TChessTree): void;
|
|
27
|
+
/** Представляет запрос на показ меню выхода из drill-down у активной крошки в header. */
|
|
28
|
+
requestDrillExitPrompt(): void;
|
|
29
|
+
/** Представляет родительский узел текущего уровня drill-down или null. */
|
|
30
|
+
get drillForkParent(): ChessTreeNode | null;
|
|
31
|
+
/** Представляет копию стека развилок для навигации «хлебными крошками» (от корня партии к текущему уровню). */
|
|
32
|
+
get drillForkBreadcrumbs(): ChessTreeNode[];
|
|
33
|
+
/** Представляет текущую развилку, для которой нужно подсвечивать направляющие в дереве. */
|
|
34
|
+
get currentGuideHighlightForkParent(): ChessTreeNode | null;
|
|
35
|
+
/**
|
|
36
|
+
* Представляет проверку: текущий узел совпадает с развилкой или лежит в поддереве её вариантов
|
|
37
|
+
* (цепочка родителей от currentNode до корня содержит forkParent).
|
|
38
|
+
* Возвращает true, если позиция на доске уже «внутри» этой развилки.
|
|
39
|
+
*/
|
|
40
|
+
isCurrentNodeWithinForkSubtree(forkParent: ChessTreeNode): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Представляет открытие drill-down от указанной развилки (новый уровень в стеке).
|
|
43
|
+
* В отдельном виде ответы на развилку показываются в одном блоке вариантов, как в основном дереве.
|
|
44
|
+
*/
|
|
45
|
+
openVariationDrillDown(forkParent: ChessTreeNode, opts?: {
|
|
46
|
+
selectFork?: boolean;
|
|
47
|
+
}): void;
|
|
48
|
+
/** Представляет выход на один уровень вверх по drill-down; при пустом стеке — без изменений. */
|
|
49
|
+
closeVariationDrillDown(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Представляет сброс drill-down к отображению полного дерева без смены текущего узла и выбора на доске.
|
|
52
|
+
*/
|
|
53
|
+
exitDrillToFullTreeWithoutSelectingNode(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Представляет переход по цепочке drill-down: при отрицательном индексе — полное дерево и корень партии;
|
|
56
|
+
* иначе стек обрезается до форка с этим индексом, текущим узлом становится этот форк.
|
|
57
|
+
*/
|
|
58
|
+
navigateDrillBreadcrumb(targetForkIndex: number): void;
|
|
59
|
+
/** Представляет проверку активности режима drill-down. */
|
|
60
|
+
isVariationDrillDownActive(): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Представляет проверку: узел лежит на главной линии партии (цепочка только children[0] от корня `rootNode.moves`).
|
|
63
|
+
*/
|
|
64
|
+
isNodeOnMainLine(node: ChessTreeNode): boolean;
|
|
9
65
|
/** Представляет перестроение индекса nodeById по всему дереву. */
|
|
10
66
|
private rebuildNodeById;
|
|
67
|
+
/** Представляет поиск узла партии по id в индексе дерева. */
|
|
68
|
+
getNodeById(id: string): ChessTreeNode | undefined;
|
|
11
69
|
/** Представляет добавление узла и всех потомков в индекс nodeById. */
|
|
12
70
|
private addNodeToMap;
|
|
13
71
|
/** Представляет удаление узла и всех потомков из индекса nodeById. */
|
|
@@ -31,6 +89,31 @@ export declare class ChessTree {
|
|
|
31
89
|
* Возвращает родительский узел или null, если текущий узел — корень.
|
|
32
90
|
*/
|
|
33
91
|
getCurrentNodeParent(): ChessTreeNode | null;
|
|
92
|
+
/**
|
|
93
|
+
* Представляет получение родителя указанного узла в дереве партии.
|
|
94
|
+
* Возвращает родительский узел или null, если у узла нет родителя (корень).
|
|
95
|
+
*/
|
|
96
|
+
getNodeParent(node: ChessTreeNode): ChessTreeNode | null;
|
|
97
|
+
/**
|
|
98
|
+
* Представляет проверку: `branchRoot` встречается на пути от `currentNode` к корню
|
|
99
|
+
* (текущий узел, либо предок на этой цепи).
|
|
100
|
+
* Возвращает true, если горизонтальную «птичку» у строки, начинающейся с `branchRoot`, нужно
|
|
101
|
+
* подсвечивать вместе с ветвью, а не у всех соседей развилки.
|
|
102
|
+
*/
|
|
103
|
+
isCurrentOnPathFromNode(branchRoot: ChessTreeNode): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Представляет поиск ближайшей к выбранному узлу развилки, двигаясь к корню: проверяется
|
|
106
|
+
* сам `node` и далее цепь родителей; выбирается первый с более чем одним ребёнком.
|
|
107
|
+
* Возвращает этот узел-развилку или `null`, если вариантов вверх не найдено.
|
|
108
|
+
*/
|
|
109
|
+
getDeepestForkParentOnPathFromNode(node: ChessTreeNode): ChessTreeNode | null;
|
|
110
|
+
/**
|
|
111
|
+
* Представляет поиск узла-развилки для акцентных направляющих. На главной линии — `null`. Иначе
|
|
112
|
+
* ближайшая развилка вверх. Если `node` сам — развилка, акцент ведём к выбранному ходу через
|
|
113
|
+
* ближайшую развилку выше, а не к его дочерним вариантам.
|
|
114
|
+
* Возвращает этот узел или `null`.
|
|
115
|
+
*/
|
|
116
|
+
getForkParentForGuideHighlight(node: ChessTreeNode): ChessTreeNode | null;
|
|
34
117
|
/**
|
|
35
118
|
* Представляет функцию удаления варианта из дерева.
|
|
36
119
|
* Удаляет указанный узел и все его дочерние узлы.
|
|
@@ -2,16 +2,140 @@ import { generateId } from "@connectorvol/shared";
|
|
|
2
2
|
export class ChessTree {
|
|
3
3
|
rootNode;
|
|
4
4
|
currentNode;
|
|
5
|
+
/** Представляет признак: меню выхода из drill-down у активной крошки сейчас открыто. */
|
|
6
|
+
drillExitPromptOpen = $state(false);
|
|
5
7
|
// Заставляет узел отображаться как побочная линия, а не главный вариант
|
|
6
8
|
forcedNodeId;
|
|
9
|
+
/**
|
|
10
|
+
* Представляет стек родительских узлов развилок при drill-down «варианты отдельно»;
|
|
11
|
+
* пустой массив — полное дерево в основной области.
|
|
12
|
+
*/
|
|
13
|
+
drillForkStack = $state([]);
|
|
7
14
|
/** Представляет индекс узлов по id для O(1) доступа к родителю по parentId. */
|
|
8
15
|
nodeById = new Map();
|
|
16
|
+
/** Представляет набор id узлов главной линии для O(1) проверки принадлежности. */
|
|
17
|
+
mainLineNodeIds = $derived.by(() => {
|
|
18
|
+
const ids = new Set();
|
|
19
|
+
let node = this.rootNode.moves;
|
|
20
|
+
while (node) {
|
|
21
|
+
ids.add(node.id);
|
|
22
|
+
node = node.children[0];
|
|
23
|
+
}
|
|
24
|
+
return ids;
|
|
25
|
+
});
|
|
26
|
+
/** Представляет набор id узлов на пути от текущего хода к корню партии. */
|
|
27
|
+
currentPathNodeIds = $derived.by(() => {
|
|
28
|
+
const ids = new Set();
|
|
29
|
+
let node = this.currentNode;
|
|
30
|
+
while (node) {
|
|
31
|
+
ids.add(node.id);
|
|
32
|
+
node = this.findParentNode(node);
|
|
33
|
+
}
|
|
34
|
+
return ids;
|
|
35
|
+
});
|
|
36
|
+
/** Представляет текущую развилку, для которой нужно подсвечивать направляющие. */
|
|
37
|
+
guideHighlightForkParent = $derived.by(() => this.getForkParentForGuideHighlight(this.currentNode));
|
|
9
38
|
constructor(rootNode) {
|
|
10
39
|
this.rootNode = $state(rootNode);
|
|
11
40
|
this.currentNode = $state.raw(this.rootNode.moves);
|
|
12
41
|
this.forcedNodeId = $state(null);
|
|
13
42
|
this.rebuildNodeById();
|
|
14
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Представляет замену корня партии (полное дерево, заголовки, комментарии)
|
|
46
|
+
* и сброс навигации к первому ходу после парсинга PGN.
|
|
47
|
+
*/
|
|
48
|
+
replaceRootTree(nextRoot) {
|
|
49
|
+
this.drillExitPromptOpen = false;
|
|
50
|
+
this.drillForkStack = [];
|
|
51
|
+
this.forcedNodeId = null;
|
|
52
|
+
this.rootNode = nextRoot;
|
|
53
|
+
this.rebuildNodeById();
|
|
54
|
+
this.currentNode = this.rootNode.moves;
|
|
55
|
+
}
|
|
56
|
+
/** Представляет запрос на показ меню выхода из drill-down у активной крошки в header. */
|
|
57
|
+
requestDrillExitPrompt() {
|
|
58
|
+
this.drillExitPromptOpen = true;
|
|
59
|
+
}
|
|
60
|
+
/** Представляет родительский узел текущего уровня drill-down или null. */
|
|
61
|
+
get drillForkParent() {
|
|
62
|
+
const stack = this.drillForkStack;
|
|
63
|
+
const len = stack.length;
|
|
64
|
+
return len === 0 ? null : stack[len - 1];
|
|
65
|
+
}
|
|
66
|
+
/** Представляет копию стека развилок для навигации «хлебными крошками» (от корня партии к текущему уровню). */
|
|
67
|
+
get drillForkBreadcrumbs() {
|
|
68
|
+
return [...this.drillForkStack];
|
|
69
|
+
}
|
|
70
|
+
/** Представляет текущую развилку, для которой нужно подсвечивать направляющие в дереве. */
|
|
71
|
+
get currentGuideHighlightForkParent() {
|
|
72
|
+
return this.guideHighlightForkParent;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Представляет проверку: текущий узел совпадает с развилкой или лежит в поддереве её вариантов
|
|
76
|
+
* (цепочка родителей от currentNode до корня содержит forkParent).
|
|
77
|
+
* Возвращает true, если позиция на доске уже «внутри» этой развилки.
|
|
78
|
+
*/
|
|
79
|
+
isCurrentNodeWithinForkSubtree(forkParent) {
|
|
80
|
+
return this.currentPathNodeIds.has(forkParent.id);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Представляет открытие drill-down от указанной развилки (новый уровень в стеке).
|
|
84
|
+
* В отдельном виде ответы на развилку показываются в одном блоке вариантов, как в основном дереве.
|
|
85
|
+
*/
|
|
86
|
+
openVariationDrillDown(forkParent, opts) {
|
|
87
|
+
this.drillExitPromptOpen = false;
|
|
88
|
+
const stack = this.drillForkStack;
|
|
89
|
+
const top = stack.length > 0 ? stack[stack.length - 1] : null;
|
|
90
|
+
if (top?.id !== forkParent.id) {
|
|
91
|
+
this.drillForkStack = [...stack, forkParent];
|
|
92
|
+
}
|
|
93
|
+
if (opts?.selectFork) {
|
|
94
|
+
this.currentNode = forkParent;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Представляет выход на один уровень вверх по drill-down; при пустом стеке — без изменений. */
|
|
98
|
+
closeVariationDrillDown() {
|
|
99
|
+
if (this.drillForkStack.length === 0)
|
|
100
|
+
return;
|
|
101
|
+
this.drillExitPromptOpen = false;
|
|
102
|
+
this.drillForkStack = this.drillForkStack.slice(0, -1);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Представляет сброс drill-down к отображению полного дерева без смены текущего узла и выбора на доске.
|
|
106
|
+
*/
|
|
107
|
+
exitDrillToFullTreeWithoutSelectingNode() {
|
|
108
|
+
this.drillExitPromptOpen = false;
|
|
109
|
+
this.drillForkStack = [];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Представляет переход по цепочке drill-down: при отрицательном индексе — полное дерево и корень партии;
|
|
113
|
+
* иначе стек обрезается до форка с этим индексом, текущим узлом становится этот форк.
|
|
114
|
+
*/
|
|
115
|
+
navigateDrillBreadcrumb(targetForkIndex) {
|
|
116
|
+
if (targetForkIndex < 0) {
|
|
117
|
+
this.drillExitPromptOpen = false;
|
|
118
|
+
this.drillForkStack = [];
|
|
119
|
+
this.currentNode = this.rootNode.moves;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const stack = this.drillForkStack;
|
|
123
|
+
if (targetForkIndex >= stack.length)
|
|
124
|
+
return;
|
|
125
|
+
this.drillExitPromptOpen = false;
|
|
126
|
+
this.drillForkStack = stack.slice(0, targetForkIndex + 1);
|
|
127
|
+
this.currentNode = this.drillForkStack[this.drillForkStack.length - 1];
|
|
128
|
+
}
|
|
129
|
+
/** Представляет проверку активности режима drill-down. */
|
|
130
|
+
isVariationDrillDownActive() {
|
|
131
|
+
return this.drillForkStack.length > 0;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Представляет проверку: узел лежит на главной линии партии (цепочка только children[0] от корня `rootNode.moves`).
|
|
135
|
+
*/
|
|
136
|
+
isNodeOnMainLine(node) {
|
|
137
|
+
return this.mainLineNodeIds.has(node.id);
|
|
138
|
+
}
|
|
15
139
|
/** Представляет перестроение индекса nodeById по всему дереву. */
|
|
16
140
|
rebuildNodeById() {
|
|
17
141
|
this.nodeById.clear();
|
|
@@ -23,6 +147,10 @@ export class ChessTree {
|
|
|
23
147
|
};
|
|
24
148
|
addToMap(this.rootNode.moves);
|
|
25
149
|
}
|
|
150
|
+
/** Представляет поиск узла партии по id в индексе дерева. */
|
|
151
|
+
getNodeById(id) {
|
|
152
|
+
return this.nodeById.get(id);
|
|
153
|
+
}
|
|
26
154
|
/** Представляет добавление узла и всех потомков в индекс nodeById. */
|
|
27
155
|
addNodeToMap(node) {
|
|
28
156
|
this.nodeById.set(node.id, node);
|
|
@@ -110,6 +238,76 @@ export class ChessTree {
|
|
|
110
238
|
getCurrentNodeParent() {
|
|
111
239
|
return this.findParentNode(this.currentNode);
|
|
112
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Представляет получение родителя указанного узла в дереве партии.
|
|
243
|
+
* Возвращает родительский узел или null, если у узла нет родителя (корень).
|
|
244
|
+
*/
|
|
245
|
+
getNodeParent(node) {
|
|
246
|
+
return this.findParentNode(node);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Представляет проверку: `branchRoot` встречается на пути от `currentNode` к корню
|
|
250
|
+
* (текущий узел, либо предок на этой цепи).
|
|
251
|
+
* Возвращает true, если горизонтальную «птичку» у строки, начинающейся с `branchRoot`, нужно
|
|
252
|
+
* подсвечивать вместе с ветвью, а не у всех соседей развилки.
|
|
253
|
+
*/
|
|
254
|
+
isCurrentOnPathFromNode(branchRoot) {
|
|
255
|
+
return this.currentPathNodeIds.has(branchRoot.id);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Представляет поиск ближайшей к выбранному узлу развилки, двигаясь к корню: проверяется
|
|
259
|
+
* сам `node` и далее цепь родителей; выбирается первый с более чем одним ребёнком.
|
|
260
|
+
* Возвращает этот узел-развилку или `null`, если вариантов вверх не найдено.
|
|
261
|
+
*/
|
|
262
|
+
getDeepestForkParentOnPathFromNode(node) {
|
|
263
|
+
let d = node;
|
|
264
|
+
while (d) {
|
|
265
|
+
if (d.children.length > 1) {
|
|
266
|
+
return d;
|
|
267
|
+
}
|
|
268
|
+
d = this.findParentNode(d);
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Представляет поиск узла-развилки для акцентных направляющих. На главной линии — `null`. Иначе
|
|
274
|
+
* ближайшая развилка вверх. Если `node` сам — развилка, акцент ведём к выбранному ходу через
|
|
275
|
+
* ближайшую развилку выше, а не к его дочерним вариантам.
|
|
276
|
+
* Возвращает этот узел или `null`.
|
|
277
|
+
*/
|
|
278
|
+
getForkParentForGuideHighlight(node) {
|
|
279
|
+
if (this.isNodeOnMainLine(node)) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
const deepest = this.getDeepestForkParentOnPathFromNode(node);
|
|
283
|
+
if (deepest === null) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
if (deepest.id !== node.id) {
|
|
287
|
+
return deepest;
|
|
288
|
+
}
|
|
289
|
+
const p = this.getNodeParent(deepest);
|
|
290
|
+
const parentFork = p === null ? null : this.getDeepestForkParentOnPathFromNode(p);
|
|
291
|
+
if (parentFork !== null) {
|
|
292
|
+
return parentFork;
|
|
293
|
+
}
|
|
294
|
+
if (p !== null && p.children.length > 1) {
|
|
295
|
+
return parentFork ?? deepest;
|
|
296
|
+
}
|
|
297
|
+
if (p === null) {
|
|
298
|
+
return deepest;
|
|
299
|
+
}
|
|
300
|
+
const { children } = deepest;
|
|
301
|
+
if (children.length < 1) {
|
|
302
|
+
return deepest;
|
|
303
|
+
}
|
|
304
|
+
const minChildFull = Math.min(...children.map((c) => c.data.fullMoves));
|
|
305
|
+
const forkIsOnlyForSameFullMoveReplies = minChildFull === deepest.data.fullMoves;
|
|
306
|
+
if (forkIsOnlyForSameFullMoveReplies) {
|
|
307
|
+
return parentFork ?? deepest;
|
|
308
|
+
}
|
|
309
|
+
return deepest;
|
|
310
|
+
}
|
|
113
311
|
/**
|
|
114
312
|
* Представляет функцию удаления варианта из дерева.
|
|
115
313
|
* Удаляет указанный узел и все его дочерние узлы.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ChessTreeNode } from "../(models)/chessTreeNode.js";
|
|
3
|
+
import type { PieceSet } from "@connectorvol/shared";
|
|
4
|
+
import ArrowRight from "@lucide/svelte/icons/arrow-right";
|
|
5
|
+
import HomeIcon from "@lucide/svelte/icons/house";
|
|
6
|
+
import * as DropdownMenu from "../components/ui/dropdown-menu/index.js";
|
|
7
|
+
import type { ChessTree } from "../(classes)/chessTree.svelte.js";
|
|
8
|
+
import DrillForkSanLabel from "./DrillForkSanLabel.svelte";
|
|
9
|
+
|
|
10
|
+
type TDrillBreadcrumbsProps = {
|
|
11
|
+
/** Возвращает дерево партии. */
|
|
12
|
+
chessTree: ChessTree;
|
|
13
|
+
/** Возвращает цепочку узлов-развилок для хлебных крошек. */
|
|
14
|
+
drillForkCrumbs: ChessTreeNode[];
|
|
15
|
+
/** Возвращает набор фигур для иконок SAN. */
|
|
16
|
+
pieceSet: PieceSet;
|
|
17
|
+
/** Представляет переход по крошке drill-down. */
|
|
18
|
+
onNavigateCrumb: (targetForkIndex: number) => void;
|
|
19
|
+
/** Представляет выход на уровень выше относительно крошки с индексом. */
|
|
20
|
+
onNavigateToParentFromCrumb: (currentIndex: number) => void;
|
|
21
|
+
/** Представляет выход к полному дереву по кнопке «дом». */
|
|
22
|
+
onExitToFullTree: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
chessTree,
|
|
27
|
+
drillForkCrumbs,
|
|
28
|
+
pieceSet,
|
|
29
|
+
onNavigateCrumb,
|
|
30
|
+
onNavigateToParentFromCrumb,
|
|
31
|
+
onExitToFullTree,
|
|
32
|
+
}: TDrillBreadcrumbsProps = $props();
|
|
33
|
+
|
|
34
|
+
let drillExitPromptTriggerRef = $state<HTMLElement | null>(null);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<nav
|
|
38
|
+
class="col-span-3 border-b border-gray-300 !bg-gray-50 px-3 py-2"
|
|
39
|
+
aria-label="Навигация по уровням вариантов"
|
|
40
|
+
>
|
|
41
|
+
<ol
|
|
42
|
+
class="flex flex-wrap items-center gap-x-0.5 gap-y-1 text-sm list-none m-0 p-0"
|
|
43
|
+
>
|
|
44
|
+
<li class="inline-flex items-center">
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
class="inline-flex items-center justify-center rounded p-1 text-primary hover:bg-gray-200/80 outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
48
|
+
aria-label="Полное дерево ходов"
|
|
49
|
+
onclick={onExitToFullTree}
|
|
50
|
+
>
|
|
51
|
+
<HomeIcon class="size-4 shrink-0" aria-hidden="true" />
|
|
52
|
+
</button>
|
|
53
|
+
</li>
|
|
54
|
+
{#each drillForkCrumbs as fork, i (fork.id)}
|
|
55
|
+
<li class="inline-flex items-center gap-x-0.5">
|
|
56
|
+
<ArrowRight class="size-4 shrink-0 " aria-hidden="true" />
|
|
57
|
+
<div class="relative inline-flex">
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
class={{
|
|
61
|
+
"inline-flex items-baseline gap-y-0.5 rounded px-0.5 py-0.5 text-left underline-offset-2 cursor-pointer outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0": true,
|
|
62
|
+
"font-bold ": fork.id === chessTree.currentNode.id,
|
|
63
|
+
"text-gray-900": i === drillForkCrumbs.length - 1,
|
|
64
|
+
"text-primary": i !== drillForkCrumbs.length - 1,
|
|
65
|
+
}}
|
|
66
|
+
aria-current={i === drillForkCrumbs.length - 1 ? "page" : undefined}
|
|
67
|
+
aria-label="Перейти к этому уровню вариантов"
|
|
68
|
+
onclick={() => onNavigateCrumb(i)}
|
|
69
|
+
>
|
|
70
|
+
<DrillForkSanLabel {fork} {pieceSet} />
|
|
71
|
+
</button>
|
|
72
|
+
|
|
73
|
+
{#if fork.id === chessTree.currentNode.id}
|
|
74
|
+
<DropdownMenu.Root
|
|
75
|
+
open={chessTree.drillExitPromptOpen}
|
|
76
|
+
onOpenChange={(open) => {
|
|
77
|
+
chessTree.drillExitPromptOpen = open;
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
<DropdownMenu.Trigger
|
|
81
|
+
bind:ref={drillExitPromptTriggerRef}
|
|
82
|
+
class="absolute inset-0 opacity-0 pointer-events-none"
|
|
83
|
+
aria-label="Меню выхода из дерева вариантов"
|
|
84
|
+
>
|
|
85
|
+
Меню выхода из дерева вариантов
|
|
86
|
+
</DropdownMenu.Trigger>
|
|
87
|
+
<DropdownMenu.Content
|
|
88
|
+
align="start"
|
|
89
|
+
class="w-fit"
|
|
90
|
+
onkeydown={(e) => {
|
|
91
|
+
if (e.key !== "ArrowRight") return;
|
|
92
|
+
chessTree.drillExitPromptOpen = false;
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
e.stopPropagation();
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
{@const parent = chessTree.getNodeParent(fork)}
|
|
98
|
+
{#if parent}
|
|
99
|
+
<DropdownMenu.Item
|
|
100
|
+
onclick={() => {
|
|
101
|
+
onNavigateToParentFromCrumb(i);
|
|
102
|
+
chessTree.drillExitPromptOpen = false;
|
|
103
|
+
}}
|
|
104
|
+
onkeydown={(e) => {
|
|
105
|
+
if (e.key !== "ArrowLeft") return;
|
|
106
|
+
onNavigateToParentFromCrumb(i);
|
|
107
|
+
chessTree.drillExitPromptOpen = false;
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
e.stopPropagation();
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<DrillForkSanLabel fork={parent} {pieceSet} />
|
|
113
|
+
</DropdownMenu.Item>
|
|
114
|
+
{/if}
|
|
115
|
+
<DropdownMenu.Item
|
|
116
|
+
onclick={() => {
|
|
117
|
+
chessTree.drillExitPromptOpen = false;
|
|
118
|
+
}}
|
|
119
|
+
onkeydown={(e) => {
|
|
120
|
+
if (e.key !== "ArrowLeft") return;
|
|
121
|
+
chessTree.drillExitPromptOpen = false;
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
e.stopPropagation();
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
Остаться
|
|
127
|
+
</DropdownMenu.Item>
|
|
128
|
+
</DropdownMenu.Content>
|
|
129
|
+
</DropdownMenu.Root>
|
|
130
|
+
{/if}
|
|
131
|
+
</div>
|
|
132
|
+
</li>
|
|
133
|
+
{/each}
|
|
134
|
+
</ol>
|
|
135
|
+
</nav>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ChessTreeNode } from "../(models)/chessTreeNode.js";
|
|
2
|
+
import type { PieceSet } from "@connectorvol/shared";
|
|
3
|
+
import type { ChessTree } from "../(classes)/chessTree.svelte.js";
|
|
4
|
+
type TDrillBreadcrumbsProps = {
|
|
5
|
+
/** Возвращает дерево партии. */
|
|
6
|
+
chessTree: ChessTree;
|
|
7
|
+
/** Возвращает цепочку узлов-развилок для хлебных крошек. */
|
|
8
|
+
drillForkCrumbs: ChessTreeNode[];
|
|
9
|
+
/** Возвращает набор фигур для иконок SAN. */
|
|
10
|
+
pieceSet: PieceSet;
|
|
11
|
+
/** Представляет переход по крошке drill-down. */
|
|
12
|
+
onNavigateCrumb: (targetForkIndex: number) => void;
|
|
13
|
+
/** Представляет выход на уровень выше относительно крошки с индексом. */
|
|
14
|
+
onNavigateToParentFromCrumb: (currentIndex: number) => void;
|
|
15
|
+
/** Представляет выход к полному дереву по кнопке «дом». */
|
|
16
|
+
onExitToFullTree: () => void;
|
|
17
|
+
};
|
|
18
|
+
declare const DrillBreadcrumbs: import("svelte").Component<TDrillBreadcrumbsProps, {}, "">;
|
|
19
|
+
type DrillBreadcrumbs = ReturnType<typeof DrillBreadcrumbs>;
|
|
20
|
+
export default DrillBreadcrumbs;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ChessTreeNode } from "../(models)/chessTreeNode.js";
|
|
3
|
+
import MoveWithIcon from "./MoveWithIcon.svelte";
|
|
4
|
+
import NagBadges from "./NagBadges.svelte";
|
|
5
|
+
import type { PieceSet } from "@connectorvol/shared";
|
|
6
|
+
|
|
7
|
+
type TDrillForkSanLabelProps = {
|
|
8
|
+
/** Возвращает узел-развилку или ход для подписи. */
|
|
9
|
+
fork: ChessTreeNode;
|
|
10
|
+
/** Возвращает набор фигур для иконок SAN. */
|
|
11
|
+
pieceSet: PieceSet;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const { fork, pieceSet }: TDrillForkSanLabelProps = $props();
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
{#if fork.data.ply % 2 === 1}
|
|
18
|
+
<span>{fork.data.fullMoves}.</span>
|
|
19
|
+
{:else}
|
|
20
|
+
<span>{Math.max(0, fork.data.fullMoves - 1)}...</span>
|
|
21
|
+
{/if}
|
|
22
|
+
<span class="inline-flex items-center gap-0 -ml-px">
|
|
23
|
+
<MoveWithIcon san={fork.data.san} ply={fork.data.ply} {pieceSet} /><NagBadges
|
|
24
|
+
nags={fork.data.nags}
|
|
25
|
+
/>
|
|
26
|
+
</span>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ChessTreeNode } from "../(models)/chessTreeNode.js";
|
|
2
|
+
import type { PieceSet } from "@connectorvol/shared";
|
|
3
|
+
type TDrillForkSanLabelProps = {
|
|
4
|
+
/** Возвращает узел-развилку или ход для подписи. */
|
|
5
|
+
fork: ChessTreeNode;
|
|
6
|
+
/** Возвращает набор фигур для иконок SAN. */
|
|
7
|
+
pieceSet: PieceSet;
|
|
8
|
+
};
|
|
9
|
+
declare const DrillForkSanLabel: import("svelte").Component<TDrillForkSanLabelProps, {}, "">;
|
|
10
|
+
type DrillForkSanLabel = ReturnType<typeof DrillForkSanLabel>;
|
|
11
|
+
export default DrillForkSanLabel;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ChessTreeNode } from "../(models)/chessTreeNode.js";
|
|
3
|
+
import type { PieceSet } from "@connectorvol/shared";
|
|
4
|
+
import Move from "./Move.svelte";
|
|
5
|
+
|
|
6
|
+
type TDrillVariationListProps = {
|
|
7
|
+
/** Возвращает родительский узел текущего уровня drill-down. */
|
|
8
|
+
drillForkParent: ChessTreeNode;
|
|
9
|
+
/** Возвращает набор фигур для иконок SAN. */
|
|
10
|
+
pieceSet: PieceSet;
|
|
11
|
+
/** Возвращает true, если направляющие drill-панели должны быть акцентными. */
|
|
12
|
+
isDrillForkGuideForSelection: boolean;
|
|
13
|
+
/** Возвращает true, если ветка текущего выбора проходит через узел. */
|
|
14
|
+
isBranchOnCurrentPath: (node: ChessTreeNode) => boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
drillForkParent,
|
|
19
|
+
pieceSet,
|
|
20
|
+
isDrillForkGuideForSelection,
|
|
21
|
+
isBranchOnCurrentPath,
|
|
22
|
+
}: TDrillVariationListProps = $props();
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<div class="col-span-3 block w-full min-w-0">
|
|
26
|
+
<div
|
|
27
|
+
class="tree-viewer-overflow-tail-border relative flex min-w-0 w-full max-w-full gap-0 bg-gray-50/95 align-top border-b"
|
|
28
|
+
>
|
|
29
|
+
<div
|
|
30
|
+
class={{
|
|
31
|
+
"w-4 shrink-0 self-stretch border-r-2 bg-gray-100/60 transition-[border-color] duration-150": true,
|
|
32
|
+
"border-gray-600": isDrillForkGuideForSelection,
|
|
33
|
+
"border-gray-300": !isDrillForkGuideForSelection,
|
|
34
|
+
}}
|
|
35
|
+
aria-hidden="true"
|
|
36
|
+
></div>
|
|
37
|
+
<ul
|
|
38
|
+
class="m-0 flex min-w-0 flex-1 list-none flex-col gap-1.5 py-2 pr-2 pl-0"
|
|
39
|
+
>
|
|
40
|
+
{#each drillForkParent.children as drillChild (drillChild.id)}
|
|
41
|
+
{@const branchOnPath = isBranchOnCurrentPath(drillChild)}
|
|
42
|
+
<li class="relative min-w-0 pl-3">
|
|
43
|
+
<span
|
|
44
|
+
class={{
|
|
45
|
+
"pointer-events-none absolute left-0 top-[0.65em] w-3 border-t-2 transition-[border-color] duration-150": true,
|
|
46
|
+
"border-gray-600": isDrillForkGuideForSelection && branchOnPath,
|
|
47
|
+
"border-gray-300": !isDrillForkGuideForSelection || !branchOnPath,
|
|
48
|
+
}}
|
|
49
|
+
aria-hidden="true"
|
|
50
|
+
></span>
|
|
51
|
+
<div class="tracking-tight">
|
|
52
|
+
<Move
|
|
53
|
+
chessNode={drillChild}
|
|
54
|
+
depth={2}
|
|
55
|
+
shouldReserveEmptyMoveCell={drillForkParent.children.length > 1}
|
|
56
|
+
shouldRenderSiblingVariations={false}
|
|
57
|
+
parentNode={drillForkParent}
|
|
58
|
+
{pieceSet}
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
</li>
|
|
62
|
+
{/each}
|
|
63
|
+
</ul>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ChessTreeNode } from "../(models)/chessTreeNode.js";
|
|
2
|
+
import type { PieceSet } from "@connectorvol/shared";
|
|
3
|
+
type TDrillVariationListProps = {
|
|
4
|
+
/** Возвращает родительский узел текущего уровня drill-down. */
|
|
5
|
+
drillForkParent: ChessTreeNode;
|
|
6
|
+
/** Возвращает набор фигур для иконок SAN. */
|
|
7
|
+
pieceSet: PieceSet;
|
|
8
|
+
/** Возвращает true, если направляющие drill-панели должны быть акцентными. */
|
|
9
|
+
isDrillForkGuideForSelection: boolean;
|
|
10
|
+
/** Возвращает true, если ветка текущего выбора проходит через узел. */
|
|
11
|
+
isBranchOnCurrentPath: (node: ChessTreeNode) => boolean;
|
|
12
|
+
};
|
|
13
|
+
declare const DrillVariationList: import("svelte").Component<TDrillVariationListProps, {}, "">;
|
|
14
|
+
type DrillVariationList = ReturnType<typeof DrillVariationList>;
|
|
15
|
+
export default DrillVariationList;
|