kokopu-react 3.0.2 → 3.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/CHANGELOG.md +7 -1
- package/dist/lib/chessboard/BoardProperties.d.ts +91 -0
- package/dist/lib/chessboard/BoardProperties.js +43 -0
- package/dist/lib/chessboard/BoardProperties.js.map +1 -0
- package/dist/lib/chessboard/Chessboard.d.ts +14 -58
- package/dist/lib/chessboard/Chessboard.js +26 -92
- package/dist/lib/chessboard/Chessboard.js.map +1 -1
- package/dist/lib/errorbox/parsing.d.ts +57 -0
- package/dist/lib/errorbox/parsing.js +140 -0
- package/dist/lib/errorbox/parsing.js.map +1 -0
- package/dist/lib/i18n.d.ts +5 -0
- package/dist/lib/i18n.js +6 -0
- package/dist/lib/i18n.js.map +1 -1
- package/dist/lib/index.d.ts +4 -1
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/movetext/Movetext.css +0 -13
- package/dist/lib/movetext/Movetext.d.ts +9 -43
- package/dist/lib/movetext/Movetext.js +15 -67
- package/dist/lib/movetext/Movetext.js.map +1 -1
- package/dist/lib/movetext/MovetextImpl.d.ts +7 -28
- package/dist/lib/movetext/MovetextImpl.js +15 -94
- package/dist/lib/movetext/MovetextImpl.js.map +1 -1
- package/dist/lib/navigationboard/NavigationBoard.d.ts +118 -0
- package/dist/lib/navigationboard/NavigationBoard.js +112 -0
- package/dist/lib/navigationboard/NavigationBoard.js.map +1 -0
- package/dist/lib/navigationboard/NavigationField.css +36 -0
- package/dist/lib/navigationboard/NavigationField.d.ts +81 -0
- package/dist/lib/navigationboard/NavigationField.js +154 -0
- package/dist/lib/navigationboard/NavigationField.js.map +1 -0
- package/dist/lib/navigationboard/NavigationToolbar.css +51 -0
- package/dist/lib/navigationboard/NavigationToolbar.d.ts +41 -0
- package/dist/lib/navigationboard/NavigationToolbar.js +124 -0
- package/dist/lib/navigationboard/NavigationToolbar.js.map +1 -0
- package/doc_src/demo/PageChessboardMove.tsx +17 -0
- package/doc_src/demo/PageNavigationBoardBase.tsx +194 -0
- package/doc_src/demo/PageNavigationBoardUncontrolledVsControlled.tsx +114 -0
- package/doc_src/demo/game-3.pgn +10 -0
- package/doc_src/examples/Movetext.md +1 -1
- package/doc_src/examples/NavigationBoard.md +15 -0
- package/package.json +16 -16
- package/scripts/doc.styleguide.config.js +12 -2
- package/scripts/docker-compose.yml +1 -1
- package/src/chessboard/BoardProperties.ts +131 -0
- package/src/chessboard/Chessboard.tsx +41 -170
- package/src/errorbox/parsing.tsx +158 -0
- package/src/i18n.ts +7 -0
- package/src/index.ts +5 -1
- package/src/movetext/Movetext.css +0 -13
- package/src/movetext/Movetext.tsx +20 -85
- package/src/movetext/MovetextImpl.tsx +28 -115
- package/src/navigationboard/NavigationBoard.tsx +231 -0
- package/src/navigationboard/NavigationField.css +36 -0
- package/src/navigationboard/NavigationField.tsx +185 -0
- package/src/navigationboard/NavigationToolbar.css +51 -0
- package/src/navigationboard/NavigationToolbar.tsx +174 -0
- package/test/05_chessboard_graphic.js +2 -0
- package/test/09_chessboard_small_screens.js +21 -0
- package/test/10_movetext_graphic.js +5 -0
- package/test/11_movetext_interaction.js +12 -8
- package/test/12_navigation_board.js +168 -0
- package/test/graphic_references/05_chessboard_graphic/move/illegal_null-move.png +0 -0
- package/test/graphic_references/05_chessboard_graphic/move/null-move.png +0 -0
- package/test/graphic_references/09_chessboard_small_screens/top_bottom_components/default_bottom_component.png +0 -0
- package/test/graphic_references/09_chessboard_small_screens/top_bottom_components/default_top_component.png +0 -0
- package/test/graphic_references/09_chessboard_small_screens/top_bottom_components/reduced_bottom_component.png +0 -0
- package/test/graphic_references/09_chessboard_small_screens/top_bottom_components/reduced_top_component.png +0 -0
- package/test/graphic_references/10_movetext_graphic/format_move/format_text.png +0 -0
- package/test/graphic_references/10_movetext_graphic/options/wrong_piece_symbol_value.png +0 -0
- package/test/graphic_references/11_movetext_interaction/select_moves/click_on_unselected_without_callback.png +0 -0
- package/test/graphic_references/12_navigation_board/controlled/default.png +0 -0
- package/test/graphic_references/12_navigation_board/controlled/forbidden_navigation_0.png +0 -0
- package/test/graphic_references/12_navigation_board/controlled/forbidden_navigation_1.png +0 -0
- package/test/graphic_references/12_navigation_board/controlled/invalid_node_id.png +0 -0
- package/test/graphic_references/12_navigation_board/controlled/no_callback_defined_0.png +0 -0
- package/test/graphic_references/12_navigation_board/controlled/no_callback_defined_1.png +0 -0
- package/test/graphic_references/12_navigation_board/controlled/overriding_initial_node_id.png +0 -0
- package/test/graphic_references/12_navigation_board/controlled/regular_navigation_0.png +0 -0
- package/test/graphic_references/12_navigation_board/controlled/regular_navigation_1.png +0 -0
- package/test/graphic_references/12_navigation_board/controlled/regular_navigation_2.png +0 -0
- package/test/graphic_references/12_navigation_board/controlled/regular_navigation_3.png +0 -0
- package/test/graphic_references/12_navigation_board/error/pgn_parsing_error.png +0 -0
- package/test/graphic_references/12_navigation_board/error/wrong_game_type.png +0 -0
- package/test/graphic_references/12_navigation_board/theme/custom_1.png +0 -0
- package/test/graphic_references/12_navigation_board/theme/custom_2.png +0 -0
- package/test/graphic_references/12_navigation_board/theme/large.png +0 -0
- package/test/graphic_references/12_navigation_board/theme/small.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/forbidden_navigation_at_beginning_0.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/forbidden_navigation_at_beginning_1.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/forbidden_navigation_at_beginning_2.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/forbidden_navigation_at_end_0.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/forbidden_navigation_at_end_1.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/forbidden_navigation_at_end_2.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/initial_state_at_end.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/initial_state_at_specific_id.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/initial_state_default.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/initial_state_default_with_game_index.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/initial_state_on_null-move.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/navigation_with_keys_0.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/navigation_with_keys_1.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/navigation_with_keys_2.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/navigation_with_keys_3.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/navigation_with_keys_4.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/regular_flipping_0.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/regular_flipping_1.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_0.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_1.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_2.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_3.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_4.png +0 -0
- package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_5.png +0 -0
- package/test/graphic_test_app/05_chessboard_graphic/move.tsx +2 -0
- package/test/graphic_test_app/09_chessboard_small_screens/top_bottom_components.tsx +48 -0
- package/test/graphic_test_app/10_movetext_graphic/format_move.tsx +31 -0
- package/test/graphic_test_app/10_movetext_graphic/options.tsx +1 -0
- package/test/graphic_test_app/11_movetext_interaction/select_moves.tsx +1 -0
- package/test/graphic_test_app/12_navigation_board/controlled.tsx +43 -0
- package/test/graphic_test_app/12_navigation_board/error.tsx +34 -0
- package/test/graphic_test_app/12_navigation_board/theme.tsx +38 -0
- package/test/graphic_test_app/12_navigation_board/uncontrolled.tsx +56 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* -------------------------------------------------------------------------- *
|
|
3
|
+
* *
|
|
4
|
+
* Kokopu-React - A React-based library of chess-related components. *
|
|
5
|
+
* <https://www.npmjs.com/package/kokopu-react> *
|
|
6
|
+
* Copyright (C) 2021-2024 Yoann Le Montagner <yo35 -at- melix.net> *
|
|
7
|
+
* *
|
|
8
|
+
* Kokopu-React is free software: you can redistribute it and/or *
|
|
9
|
+
* modify it under the terms of the GNU Lesser General Public License *
|
|
10
|
+
* as published by the Free Software Foundation, either version 3 of *
|
|
11
|
+
* the License, or (at your option) any later version. *
|
|
12
|
+
* *
|
|
13
|
+
* Kokopu-React is distributed in the hope that it will be useful, *
|
|
14
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
15
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
16
|
+
* GNU Lesser General Public License for more details. *
|
|
17
|
+
* *
|
|
18
|
+
* You should have received a copy of the GNU Lesser General *
|
|
19
|
+
* Public License along with this program. If not, see *
|
|
20
|
+
* <http://www.gnu.org/licenses/>. *
|
|
21
|
+
* *
|
|
22
|
+
* -------------------------------------------------------------------------- */
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
import * as React from 'react';
|
|
26
|
+
|
|
27
|
+
import { Game, Variation } from 'kokopu';
|
|
28
|
+
|
|
29
|
+
import './NavigationField.css';
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
interface NavigationFieldProps {
|
|
33
|
+
onFirstPressed?: () => void;
|
|
34
|
+
onPreviousPressed?: () => void;
|
|
35
|
+
onNextPressed?: () => void;
|
|
36
|
+
onLastPressed?: () => void;
|
|
37
|
+
onExitPressed?: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Invisible field, to capture keyboard events.
|
|
43
|
+
*/
|
|
44
|
+
export class NavigationField extends React.Component<NavigationFieldProps> {
|
|
45
|
+
|
|
46
|
+
private focusFieldRef: React.RefObject<HTMLAnchorElement> = React.createRef();
|
|
47
|
+
|
|
48
|
+
render() {
|
|
49
|
+
return (
|
|
50
|
+
<div className="kokopu-focusFieldContainer">
|
|
51
|
+
<a className="kokopu-focusField" href="#" ref={this.focusFieldRef} onKeyDown={evt => this.handleKeyDown(evt)}></a>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private handleKeyDown(evt: React.KeyboardEvent<HTMLAnchorElement>) {
|
|
57
|
+
/* eslint-disable no-unused-expressions */
|
|
58
|
+
doHandleKeyDown(evt, 'Home', this.props.onFirstPressed) ||
|
|
59
|
+
doHandleKeyDown(evt, 'ArrowLeft', this.props.onPreviousPressed) ||
|
|
60
|
+
doHandleKeyDown(evt, 'ArrowRight', this.props.onNextPressed) ||
|
|
61
|
+
doHandleKeyDown(evt, 'End', this.props.onLastPressed) ||
|
|
62
|
+
doHandleKeyDown(evt, 'Escape', this.props.onExitPressed);
|
|
63
|
+
/* eslint-enable */
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Set the focus to the component.
|
|
68
|
+
*/
|
|
69
|
+
focus(): void {
|
|
70
|
+
const target = this.focusFieldRef.current;
|
|
71
|
+
// istanbul ignore else
|
|
72
|
+
if (target) {
|
|
73
|
+
target.focus();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
function doHandleKeyDown(evt: React.KeyboardEvent<HTMLAnchorElement>, targetKey: string, targetCallback: (() => void) | undefined) {
|
|
81
|
+
|
|
82
|
+
// Skip if the pressed key does not match the target key.
|
|
83
|
+
if (evt.key !== targetKey) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Otherwise, invoke the callback.
|
|
88
|
+
if (targetCallback) {
|
|
89
|
+
evt.preventDefault();
|
|
90
|
+
targetCallback();
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Return the ID of the main variation in the given chess game.
|
|
98
|
+
* If the given selection corresponds already at the main variation, `undefined` is returned.
|
|
99
|
+
*
|
|
100
|
+
* @param game - Considered chess game.
|
|
101
|
+
* @param selection - ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
102
|
+
* @public
|
|
103
|
+
*/
|
|
104
|
+
export function firstNodeId(game: Game, selection: string): string | undefined {
|
|
105
|
+
if (!game.findById(selection)) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
return selection === 'start' ? undefined : 'start';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Return the ID of the node immediately preceding the given selection in the given chess game.
|
|
114
|
+
* If no such node exists, `undefined` is returned.
|
|
115
|
+
*
|
|
116
|
+
* @param game - Considered chess game.
|
|
117
|
+
* @param selection - ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
118
|
+
* @public
|
|
119
|
+
*/
|
|
120
|
+
export function previousNodeId(game: Game, selection: string): string | undefined {
|
|
121
|
+
let currentNode = game.findById(selection);
|
|
122
|
+
if (!currentNode) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
if (currentNode instanceof Variation) {
|
|
126
|
+
currentNode = currentNode.parentNode();
|
|
127
|
+
if (!currentNode) {
|
|
128
|
+
return undefined; // This case corresponds to the first variation being selected initially.
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
while (currentNode) {
|
|
132
|
+
const previousNode = currentNode.previous();
|
|
133
|
+
if (previousNode) {
|
|
134
|
+
return previousNode.id();
|
|
135
|
+
}
|
|
136
|
+
currentNode = currentNode.parentVariation().parentNode();
|
|
137
|
+
}
|
|
138
|
+
return 'start';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Return the ID of the node immediately following the given selection in the given chess game.
|
|
144
|
+
* If no such node exists, `undefined` is returned.
|
|
145
|
+
*
|
|
146
|
+
* @param game - Considered chess game.
|
|
147
|
+
* @param selection - ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
148
|
+
* @public
|
|
149
|
+
*/
|
|
150
|
+
export function nextNodeId(game: Game, selection: string): string | undefined {
|
|
151
|
+
const currentNode = game.findById(selection);
|
|
152
|
+
if (!currentNode) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const nextNode = currentNode instanceof Variation ? currentNode.first() : currentNode.next();
|
|
156
|
+
return nextNode ? nextNode.id() : undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Return the ID of the node at then end of the variation in which lies the given selection in the given chess game.
|
|
162
|
+
* If the selection is already at the end its variation, `undefined` is returned.
|
|
163
|
+
*
|
|
164
|
+
* @param game - Considered chess game.
|
|
165
|
+
* @param selection - ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
166
|
+
* @public
|
|
167
|
+
*/
|
|
168
|
+
export function lastNodeId(game: Game, selection: string): string | undefined {
|
|
169
|
+
let currentNode = game.findById(selection);
|
|
170
|
+
if (!currentNode) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
currentNode = currentNode instanceof Variation ? currentNode.first() : currentNode.next();
|
|
174
|
+
if (!currentNode) { // Ensure that the input node is not already the last one.
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
let currentNodeNotNull = currentNode;
|
|
178
|
+
while (true) {
|
|
179
|
+
const nextNode = currentNodeNotNull.next();
|
|
180
|
+
if (!nextNode) {
|
|
181
|
+
return currentNodeNotNull.id();
|
|
182
|
+
}
|
|
183
|
+
currentNodeNotNull = nextNode;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* -------------------------------------------------------------------------- *
|
|
3
|
+
* *
|
|
4
|
+
* Kokopu-React - A React-based library of chess-related components. *
|
|
5
|
+
* <https://www.npmjs.com/package/kokopu-react> *
|
|
6
|
+
* Copyright (C) 2021-2024 Yoann Le Montagner <yo35 -at- melix.net> *
|
|
7
|
+
* *
|
|
8
|
+
* Kokopu-React is free software: you can redistribute it and/or *
|
|
9
|
+
* modify it under the terms of the GNU Lesser General Public License *
|
|
10
|
+
* as published by the Free Software Foundation, either version 3 of *
|
|
11
|
+
* the License, or (at your option) any later version. *
|
|
12
|
+
* *
|
|
13
|
+
* Kokopu-React is distributed in the hope that it will be useful, *
|
|
14
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
15
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
16
|
+
* GNU Lesser General Public License for more details. *
|
|
17
|
+
* *
|
|
18
|
+
* You should have received a copy of the GNU Lesser General *
|
|
19
|
+
* Public License along with this program. If not, see *
|
|
20
|
+
* <http://www.gnu.org/licenses/>. *
|
|
21
|
+
* *
|
|
22
|
+
* -------------------------------------------------------------------------- */
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
.kokopu-navigationBoard {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
align-items: center;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.kokopu-navigationToolbar {
|
|
32
|
+
line-height: 0px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.kokopu-navigationButton {
|
|
36
|
+
display: inline-block;
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.kokopu-navigationButton:hover {
|
|
41
|
+
opacity: 0.6;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
* + .kokopu-navigationButton {
|
|
45
|
+
margin-left: 2px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.kokopu-navigationSpacer {
|
|
49
|
+
display: inline-block;
|
|
50
|
+
width: 10px;
|
|
51
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* -------------------------------------------------------------------------- *
|
|
3
|
+
* *
|
|
4
|
+
* Kokopu-React - A React-based library of chess-related components. *
|
|
5
|
+
* <https://www.npmjs.com/package/kokopu-react> *
|
|
6
|
+
* Copyright (C) 2021-2024 Yoann Le Montagner <yo35 -at- melix.net> *
|
|
7
|
+
* *
|
|
8
|
+
* Kokopu-React is free software: you can redistribute it and/or *
|
|
9
|
+
* modify it under the terms of the GNU Lesser General Public License *
|
|
10
|
+
* as published by the Free Software Foundation, either version 3 of *
|
|
11
|
+
* the License, or (at your option) any later version. *
|
|
12
|
+
* *
|
|
13
|
+
* Kokopu-React is distributed in the hope that it will be useful, *
|
|
14
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
15
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
16
|
+
* GNU Lesser General Public License for more details. *
|
|
17
|
+
* *
|
|
18
|
+
* You should have received a copy of the GNU Lesser General *
|
|
19
|
+
* Public License along with this program. If not, see *
|
|
20
|
+
* <http://www.gnu.org/licenses/>. *
|
|
21
|
+
* *
|
|
22
|
+
* -------------------------------------------------------------------------- */
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
import * as React from 'react';
|
|
27
|
+
|
|
28
|
+
import { i18n } from '../i18n';
|
|
29
|
+
|
|
30
|
+
import './NavigationToolbar.css';
|
|
31
|
+
|
|
32
|
+
const THICKNESS = 2;
|
|
33
|
+
const CHEVRON_SIZE = 7;
|
|
34
|
+
const ARROW_SIZE = 5;
|
|
35
|
+
const ARROW_LENGTH = 16;
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
interface NavigationToolbarProps {
|
|
39
|
+
flipButtonVisible: boolean;
|
|
40
|
+
squareSize: number;
|
|
41
|
+
onFirstClicked?: () => void;
|
|
42
|
+
onPreviousClicked?: () => void;
|
|
43
|
+
onNextClicked?: () => void;
|
|
44
|
+
onLastClicked?: () => void;
|
|
45
|
+
onFlipClicked?: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Toolbar below the navigation board.
|
|
51
|
+
*/
|
|
52
|
+
export class NavigationToolbar extends React.Component<NavigationToolbarProps> {
|
|
53
|
+
|
|
54
|
+
render() {
|
|
55
|
+
const buttonSize = computeButtonSize(this.props.squareSize);
|
|
56
|
+
const marginTop = Math.round(buttonSize / 4);
|
|
57
|
+
return (
|
|
58
|
+
<div className="kokopu-navigationToolbar" style={{ marginTop: marginTop }}>
|
|
59
|
+
<NavigationButton size={buttonSize} tooltip={i18n.TOOLTIP_GO_FIRST} path={chevronPath(14, 16, -1) + ' ' + chevronPath(10, 16, -1)}
|
|
60
|
+
onClick={this.props.onFirstClicked} />
|
|
61
|
+
<NavigationButton size={buttonSize} tooltip={i18n.TOOLTIP_GO_PREVIOUS} path={chevronPath(12, 16, -1)}
|
|
62
|
+
onClick={this.props.onPreviousClicked} />
|
|
63
|
+
<NavigationButton size={buttonSize} tooltip={i18n.TOOLTIP_GO_NEXT} path={chevronPath(20, 16, 1)}
|
|
64
|
+
onClick={this.props.onNextClicked} />
|
|
65
|
+
<NavigationButton size={buttonSize} tooltip={i18n.TOOLTIP_GO_LAST} path={chevronPath(18, 16, 1) + ' ' + chevronPath(22, 16, 1)}
|
|
66
|
+
onClick={this.props.onLastClicked} />
|
|
67
|
+
{this.renderFlipButton(buttonSize)}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private renderFlipButton(buttonSize: number) {
|
|
73
|
+
if (!this.props.flipButtonVisible) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
return (<>
|
|
77
|
+
<div className="kokopu-navigationSpacer" />
|
|
78
|
+
<NavigationButton size={buttonSize} tooltip={i18n.TOOLTIP_FLIP} path={arrowPath(11, 25, 1) + ' ' + arrowPath(21, 7, -1)}
|
|
79
|
+
onClick={this.props.onFlipClicked} />
|
|
80
|
+
</>);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
interface NavigationButtonProps {
|
|
87
|
+
size: number;
|
|
88
|
+
tooltip: string;
|
|
89
|
+
path: string;
|
|
90
|
+
onClick?: () => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Button for the navigation toolbar.
|
|
96
|
+
*/
|
|
97
|
+
function NavigationButton({ size, tooltip, path, onClick }: NavigationButtonProps) {
|
|
98
|
+
return (
|
|
99
|
+
<div className="kokopu-navigationButton" title={tooltip} onClick={onClick}>
|
|
100
|
+
<svg viewBox="0 0 32 32" width={size} height={size}>
|
|
101
|
+
<path d={`M 16 0 A 16 16 0 0 0 16 32 A 16 16 0 0 0 16 0 Z ${path}`} fill="currentcolor" />
|
|
102
|
+
</svg>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* * C
|
|
110
|
+
* / \
|
|
111
|
+
* B * \
|
|
112
|
+
* \ \
|
|
113
|
+
* \ \
|
|
114
|
+
* A * * D
|
|
115
|
+
* / /
|
|
116
|
+
* / /
|
|
117
|
+
* F * /
|
|
118
|
+
* \ /
|
|
119
|
+
* * E
|
|
120
|
+
*
|
|
121
|
+
* (x,y) = middle of [AD]
|
|
122
|
+
*/
|
|
123
|
+
function chevronPath(x: number, y: number, direction: 1 | -1) {
|
|
124
|
+
const xA = x - direction * THICKNESS * Math.sqrt(2) / 2;
|
|
125
|
+
const xB = x - direction * (CHEVRON_SIZE + THICKNESS / Math.sqrt(2) / 2);
|
|
126
|
+
const xC = x - direction * (CHEVRON_SIZE - THICKNESS / Math.sqrt(2) / 2);
|
|
127
|
+
const xD = x + direction * THICKNESS * Math.sqrt(2) / 2;
|
|
128
|
+
const yB = y - direction * (CHEVRON_SIZE - THICKNESS / Math.sqrt(2) / 2);
|
|
129
|
+
const yC = y - direction * (CHEVRON_SIZE + THICKNESS / Math.sqrt(2) / 2);
|
|
130
|
+
const yE = y + direction * (CHEVRON_SIZE + THICKNESS / Math.sqrt(2) / 2);
|
|
131
|
+
const yF = y + direction * (CHEVRON_SIZE - THICKNESS / Math.sqrt(2) / 2);
|
|
132
|
+
return `M ${xA} ${y} L ${xB} ${yB} L ${xC} ${yC} L ${xD} ${y} L ${xC} ${yE} L ${xB} ${yF} Z`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* D +---+ E
|
|
138
|
+
* | |
|
|
139
|
+
* | |
|
|
140
|
+
* | |
|
|
141
|
+
* B * | | * G
|
|
142
|
+
* / \ | | / \
|
|
143
|
+
* A * \| |/ * H
|
|
144
|
+
* \ * * /
|
|
145
|
+
* \ C F /
|
|
146
|
+
* \ O /
|
|
147
|
+
* \ * /
|
|
148
|
+
* \ /
|
|
149
|
+
* \ /
|
|
150
|
+
* *
|
|
151
|
+
* (x,y)
|
|
152
|
+
*/
|
|
153
|
+
function arrowPath(x: number, y: number, direction: 1 | -1) {
|
|
154
|
+
const xA = x - direction * (ARROW_SIZE + THICKNESS / Math.sqrt(2) / 2);
|
|
155
|
+
const xB = x - direction * (ARROW_SIZE - THICKNESS / Math.sqrt(2) / 2);
|
|
156
|
+
const xC = x - direction * THICKNESS / 2;
|
|
157
|
+
const xF = x + direction * THICKNESS / 2;
|
|
158
|
+
const xG = x + direction * (ARROW_SIZE - THICKNESS / Math.sqrt(2) / 2);
|
|
159
|
+
const xH = x + direction * (ARROW_SIZE + THICKNESS / Math.sqrt(2) / 2);
|
|
160
|
+
const y0 = y - direction * THICKNESS * Math.sqrt(2) / 2;
|
|
161
|
+
const yA = y0 - direction * (ARROW_SIZE - THICKNESS / Math.sqrt(2) / 2);
|
|
162
|
+
const yB = y0 - direction * (ARROW_SIZE + THICKNESS / Math.sqrt(2) / 2);
|
|
163
|
+
const yC = yB + (xC - xB);
|
|
164
|
+
const yD = y - direction * ARROW_LENGTH;
|
|
165
|
+
return `M ${x} ${y} L ${xA} ${yA} L ${xB} ${yB} L ${xC} ${yC} V ${yD} H ${xF} V ${yC} L ${xG} ${yB} L ${xH} ${yA} Z`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Return the height/width of the buttons assuming the given square size.
|
|
171
|
+
*/
|
|
172
|
+
function computeButtonSize(squareSize: number) {
|
|
173
|
+
return Math.round((squareSize * 2 + 116) / 7);
|
|
174
|
+
}
|
|
@@ -50,3 +50,24 @@ describeWithBrowser('Chessboard on small-screens', browserContext => {
|
|
|
50
50
|
itCheckScreenshotWithWidth(690);
|
|
51
51
|
|
|
52
52
|
});
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
describeWithBrowser('Chessboard on small-screens with top/bottom components', browserContext => {
|
|
56
|
+
|
|
57
|
+
itChecksScreenshots(browserContext, '09_chessboard_small_screens/top_bottom_components', [
|
|
58
|
+
'default top component',
|
|
59
|
+
'default bottom component',
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
function itCheckScreenshotWithReducedWidth(itemIndex, label) {
|
|
63
|
+
itCustom(browserContext, '09_chessboard_small_screens/top_bottom_components', itemIndex, label, async element => {
|
|
64
|
+
await setWindowWidth(browserContext, 700);
|
|
65
|
+
await takeScreenshot(browserContext, label, element);
|
|
66
|
+
await compareScreenshot(browserContext, label);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
itCheckScreenshotWithReducedWidth(0, 'reduced top component');
|
|
71
|
+
itCheckScreenshotWithReducedWidth(1, 'reduced bottom component');
|
|
72
|
+
|
|
73
|
+
});
|
|
@@ -57,6 +57,11 @@ describeWithBrowser('Movetext graphic', browserContext => {
|
|
|
57
57
|
'hidden diagrams',
|
|
58
58
|
'wrong diagram options 1',
|
|
59
59
|
'wrong diagram options 2',
|
|
60
|
+
'wrong piece symbol value',
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
itChecksScreenshots(browserContext, '10_movetext_graphic/format_move', [
|
|
64
|
+
'format text',
|
|
60
65
|
]);
|
|
61
66
|
|
|
62
67
|
});
|
|
@@ -59,7 +59,11 @@ describeWithBrowser('Movetext interaction', browserContext => {
|
|
|
59
59
|
{ searchedText: 'cxd5', expectedText: tpl('5w', 'click') },
|
|
60
60
|
]);
|
|
61
61
|
|
|
62
|
-
itCheckClickMove(2, 'click on
|
|
62
|
+
itCheckClickMove(2, 'click on unselected without callback', [
|
|
63
|
+
{ searchedText: 'cxd5', expectedText: '' },
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
itCheckClickMove(3, 'click on with variations', [
|
|
63
67
|
{ searchedText: 'Bb5', expectedText: tpl(undefined, 'click') },
|
|
64
68
|
{ searchedText: 'Nc6', expectedText: tpl('1b-v0-2b', 'click') },
|
|
65
69
|
]);
|
|
@@ -111,15 +115,15 @@ describeWithBrowser('Movetext interaction', browserContext => {
|
|
|
111
115
|
|
|
112
116
|
itCheckKeyboardActions(0, 'key on disabled', '', '', '', '', '');
|
|
113
117
|
itCheckKeyboardActions(1, 'key on unselected', '', '', '', '', '');
|
|
114
|
-
itCheckKeyboardActions(
|
|
118
|
+
itCheckKeyboardActions(3, 'key on with variations 1', tpl('start', 'key-first'), tpl('1b-v0-2b', 'key-previous'), tpl('1b-v0-3w-v0-3b', 'key-next'),
|
|
115
119
|
tpl('1b-v0-3w-v0-4w', 'key-last'), TPL_EXIT);
|
|
116
|
-
itCheckKeyboardActions(
|
|
120
|
+
itCheckKeyboardActions(4, 'key on with variations 2', tpl('start', 'key-first'), tpl('1b-v0-2w', 'key-previous'), tpl('1b-v0-3w', 'key-next'),
|
|
117
121
|
tpl('1b-v0-3w', 'key-last'), TPL_EXIT);
|
|
118
|
-
itCheckKeyboardActions(
|
|
119
|
-
itCheckKeyboardActions(
|
|
120
|
-
itCheckKeyboardActions(
|
|
121
|
-
itCheckKeyboardActions(
|
|
122
|
-
itCheckKeyboardActions(
|
|
122
|
+
itCheckKeyboardActions(5, 'key on start selected', '', '', tpl('1w', 'key-next'), tpl('6b', 'key-last'), TPL_EXIT);
|
|
123
|
+
itCheckKeyboardActions(6, 'key on first selected', tpl('start', 'key-first'), tpl('start', 'key-previous'), tpl('1b', 'key-next'), tpl('6b', 'key-last'), TPL_EXIT);
|
|
124
|
+
itCheckKeyboardActions(7, 'key on last selected', tpl('start', 'key-first'), tpl('6w', 'key-previous'), '', '', TPL_EXIT);
|
|
125
|
+
itCheckKeyboardActions(8, 'key on invalid selection', '', '', '', '', '');
|
|
126
|
+
itCheckKeyboardActions(9, 'key on sub-variation selected', tpl('start', 'key-first'), tpl('1w', 'key-previous'), tpl('1b-v0-1b', 'key-next'),
|
|
123
127
|
tpl('1b-v0-3w', 'key-last'), TPL_EXIT);
|
|
124
128
|
|
|
125
129
|
itChecksScreenshots(browserContext, '11_movetext_interaction/error', [
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* -------------------------------------------------------------------------- *
|
|
3
|
+
* *
|
|
4
|
+
* Kokopu-React - A React-based library of chess-related components. *
|
|
5
|
+
* <https://www.npmjs.com/package/kokopu-react> *
|
|
6
|
+
* Copyright (C) 2021-2024 Yoann Le Montagner <yo35 -at- melix.net> *
|
|
7
|
+
* *
|
|
8
|
+
* Kokopu-React is free software: you can redistribute it and/or *
|
|
9
|
+
* modify it under the terms of the GNU Lesser General Public License *
|
|
10
|
+
* as published by the Free Software Foundation, either version 3 of *
|
|
11
|
+
* the License, or (at your option) any later version. *
|
|
12
|
+
* *
|
|
13
|
+
* Kokopu-React is distributed in the hope that it will be useful, *
|
|
14
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
15
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
16
|
+
* GNU Lesser General Public License for more details. *
|
|
17
|
+
* *
|
|
18
|
+
* You should have received a copy of the GNU Lesser General *
|
|
19
|
+
* Public License along with this program. If not, see *
|
|
20
|
+
* <http://www.gnu.org/licenses/>. *
|
|
21
|
+
* *
|
|
22
|
+
* -------------------------------------------------------------------------- */
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
const { describeWithBrowser, itCustom, setSandbox, compareSandbox, takeScreenshot, compareScreenshot, itChecksScreenshots } = require('./common/graphic');
|
|
26
|
+
const { By, Key } = require('selenium-webdriver');
|
|
27
|
+
const test = require('unit.js');
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
describeWithBrowser('Navigation board - Uncontrolled behavior', browserContext => {
|
|
31
|
+
|
|
32
|
+
itChecksScreenshots(browserContext, '12_navigation_board/uncontrolled', [
|
|
33
|
+
'initial state default',
|
|
34
|
+
'initial state at end',
|
|
35
|
+
'initial state at specific ID',
|
|
36
|
+
'initial state default with game index',
|
|
37
|
+
'initial state on null-move',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
function itCheckClickButton(itemIndex, label, buttons) {
|
|
41
|
+
itCustom(browserContext, '12_navigation_board/uncontrolled', itemIndex, label, async element => {
|
|
42
|
+
await setSandbox(browserContext, '');
|
|
43
|
+
for (let i = 0; i < buttons.length; ++i) {
|
|
44
|
+
const buttonElement = await element.findElement(By.xpath(`.//div[@title='${buttons[i].title}']`));
|
|
45
|
+
await buttonElement.click();
|
|
46
|
+
await compareSandbox(browserContext, buttons[i].expectedSandbox ?? '');
|
|
47
|
+
await takeScreenshot(browserContext, `${label} ${i}`, element);
|
|
48
|
+
}
|
|
49
|
+
for (let i = 0; i < buttons.length; ++i) {
|
|
50
|
+
await compareScreenshot(browserContext, `${label} ${i}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
itCheckClickButton(0, 'regular flipping', [
|
|
56
|
+
{ title: 'Flip the board' },
|
|
57
|
+
{ title: 'Flip the board' },
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
itCheckClickButton(2, 'regular navigation', [
|
|
61
|
+
{ title: 'Go to the next move' },
|
|
62
|
+
{ title: 'Go to the previous move' },
|
|
63
|
+
{ title: 'Go to the previous move' },
|
|
64
|
+
{ title: 'Go to the beginning of the game' },
|
|
65
|
+
{ title: 'Go to the end of the game' },
|
|
66
|
+
{ title: 'Flip the board' },
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
itCheckClickButton(3, 'forbidden navigation at beginning', [
|
|
70
|
+
{ title: 'Go to the previous move' },
|
|
71
|
+
{ title: 'Go to the beginning of the game' },
|
|
72
|
+
{ title: 'Flip the board', expectedSandbox: 'Flip state changed: false' },
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
itCheckClickButton(1, 'forbidden navigation at end', [
|
|
76
|
+
{ title: 'Go to the next move' },
|
|
77
|
+
{ title: 'Go to the end of the game' },
|
|
78
|
+
{ title: 'Flip the board' },
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
function itCheckPressKey(itemIndex, label, keys) {
|
|
82
|
+
itCustom(browserContext, '12_navigation_board/uncontrolled', itemIndex, label, async element => {
|
|
83
|
+
await setSandbox(browserContext, '');
|
|
84
|
+
const focusFieldElements = await element.findElements(By.className('kokopu-focusField'));
|
|
85
|
+
|
|
86
|
+
test.value(focusFieldElements.length).is(1);
|
|
87
|
+
const focusFieldElement = focusFieldElements[0];
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
90
|
+
await focusFieldElement.sendKeys(keys[i].code);
|
|
91
|
+
await compareSandbox(browserContext, keys[i].expectedSandbox ?? '');
|
|
92
|
+
await takeScreenshot(browserContext, `${label} ${i}`, element);
|
|
93
|
+
}
|
|
94
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
95
|
+
await compareScreenshot(browserContext, `${label} ${i}`);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
itCheckPressKey(4, 'navigation with keys', [
|
|
101
|
+
{ code: Key.ARROW_LEFT },
|
|
102
|
+
{ code: Key.HOME },
|
|
103
|
+
{ code: Key.ARROW_RIGHT },
|
|
104
|
+
{ code: Key.ESCAPE },
|
|
105
|
+
{ code: Key.END },
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
describeWithBrowser('Navigation board - Controlled behavior', browserContext => {
|
|
112
|
+
|
|
113
|
+
itChecksScreenshots(browserContext, '12_navigation_board/controlled', [
|
|
114
|
+
'default',
|
|
115
|
+
'overriding initial node ID',
|
|
116
|
+
'invalid node ID',
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
function itCheckClickButton(itemIndex, label, buttons) {
|
|
120
|
+
itCustom(browserContext, '12_navigation_board/controlled', itemIndex, label, async element => {
|
|
121
|
+
await setSandbox(browserContext, '');
|
|
122
|
+
for (let i = 0; i < buttons.length; ++i) {
|
|
123
|
+
const buttonElement = await element.findElement(By.xpath(`.//div[@title='${buttons[i].title}']`));
|
|
124
|
+
await buttonElement.click();
|
|
125
|
+
await compareSandbox(browserContext, buttons[i].expectedSandbox ?? '');
|
|
126
|
+
await takeScreenshot(browserContext, `${label} ${i}`, element);
|
|
127
|
+
}
|
|
128
|
+
for (let i = 0; i < buttons.length; ++i) {
|
|
129
|
+
await compareScreenshot(browserContext, `${label} ${i}`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
itCheckClickButton(0, 'regular navigation', [
|
|
135
|
+
{ title: 'Go to the beginning of the game', expectedSandbox: 'Node ID changed: start' },
|
|
136
|
+
{ title: 'Go to the previous move', expectedSandbox: 'Node ID changed: 16b' },
|
|
137
|
+
{ title: 'Go to the next move', expectedSandbox: 'Node ID changed: 17b' },
|
|
138
|
+
{ title: 'Go to the end of the game', expectedSandbox: 'Node ID changed: 28b' },
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
itCheckClickButton(1, 'no callback defined', [
|
|
142
|
+
{ title: 'Go to the beginning of the game' },
|
|
143
|
+
{ title: 'Flip the board' },
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
itCheckClickButton(2, 'forbidden navigation', [
|
|
147
|
+
{ title: 'Go to the previous move' },
|
|
148
|
+
{ title: 'Flip the board', expectedSandbox: 'Flip state changed: true' },
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
describeWithBrowser('Navigation board graphic', browserContext => {
|
|
155
|
+
|
|
156
|
+
itChecksScreenshots(browserContext, '12_navigation_board/error', [
|
|
157
|
+
'pgn parsing error',
|
|
158
|
+
'wrong game type',
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
itChecksScreenshots(browserContext, '12_navigation_board/theme', [
|
|
162
|
+
'small',
|
|
163
|
+
'large',
|
|
164
|
+
'custom 1',
|
|
165
|
+
'custom 2',
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|