kokopu-react 3.1.0 → 3.3.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.
Files changed (236) hide show
  1. package/.eslintrc.yml +1 -1
  2. package/CHANGELOG.md +10 -0
  3. package/dist/lib/chessboard/BoardProperties.js.map +1 -1
  4. package/dist/lib/chessboard/Chessboard.css +13 -13
  5. package/dist/lib/chessboard/Chessboard.d.ts +27 -2
  6. package/dist/lib/chessboard/Chessboard.js +36 -17
  7. package/dist/lib/chessboard/Chessboard.js.map +1 -1
  8. package/dist/lib/chessboard/ChessboardImpl.js.map +1 -1
  9. package/dist/lib/chessboard/Draggable.js.map +1 -1
  10. package/dist/lib/chessboard/Motion.js.map +1 -1
  11. package/dist/lib/chessboard/colorsets.js.map +1 -1
  12. package/dist/lib/chessboard/piecesets.js.map +1 -1
  13. package/dist/lib/errorbox/ErrorBox.css +18 -18
  14. package/dist/lib/errorbox/ErrorBox.js.map +1 -1
  15. package/dist/lib/errorbox/parsing.js.map +1 -1
  16. package/dist/lib/exception.js.map +1 -1
  17. package/dist/lib/i18n.d.ts +1 -0
  18. package/dist/lib/i18n.js +1 -0
  19. package/dist/lib/i18n.js.map +1 -1
  20. package/dist/lib/icons/AnnotationSymbolShape.css +6 -6
  21. package/dist/lib/icons/AnnotationSymbolShape.js.map +1 -1
  22. package/dist/lib/icons/ArrowMarkerIcon.js.map +1 -1
  23. package/dist/lib/icons/ArrowTip.js.map +1 -1
  24. package/dist/lib/icons/ChessPieceIcon.js.map +1 -1
  25. package/dist/lib/icons/SquareMarkerIcon.js.map +1 -1
  26. package/dist/lib/icons/TextMarkerIcon.js.map +1 -1
  27. package/dist/lib/icons/arrow.css +1 -1
  28. package/dist/lib/index.d.ts +1 -0
  29. package/dist/lib/index.js +1 -0
  30. package/dist/lib/index.js.map +1 -1
  31. package/dist/lib/movetext/Movetext.css +27 -27
  32. package/dist/lib/movetext/Movetext.js.map +1 -1
  33. package/dist/lib/movetext/MovetextImpl.js.map +1 -1
  34. package/dist/lib/movetext/fonts.css +7 -7
  35. package/dist/lib/movetext/htmlFilter.js.map +1 -1
  36. package/dist/lib/movetext/moveFormatter.js.map +1 -1
  37. package/dist/lib/navigationboard/NavigationBoard.d.ts +68 -1
  38. package/dist/lib/navigationboard/NavigationBoard.js +141 -13
  39. package/dist/lib/navigationboard/NavigationBoard.js.map +1 -1
  40. package/dist/lib/navigationboard/NavigationButton.d.ts +66 -0
  41. package/dist/lib/navigationboard/NavigationButton.js +67 -0
  42. package/dist/lib/navigationboard/NavigationButton.js.map +1 -0
  43. package/dist/lib/navigationboard/NavigationField.css +7 -7
  44. package/dist/lib/navigationboard/NavigationField.js.map +1 -1
  45. package/dist/lib/navigationboard/NavigationToolbar.css +16 -13
  46. package/dist/lib/navigationboard/NavigationToolbar.d.ts +10 -10
  47. package/dist/lib/navigationboard/NavigationToolbar.js +61 -79
  48. package/dist/lib/navigationboard/NavigationToolbar.js.map +1 -1
  49. package/dist/lib/navigationboard/iconPaths.d.ts +29 -0
  50. package/dist/lib/navigationboard/iconPaths.js +91 -0
  51. package/dist/lib/navigationboard/iconPaths.js.map +1 -0
  52. package/dist/lib/sanitization.js.map +1 -1
  53. package/dist/lib/types.js.map +1 -1
  54. package/dist/lib/util.js.map +1 -1
  55. package/doc_src/demo/PageChessboardBase.tsx +115 -115
  56. package/doc_src/demo/PageChessboardInteraction.tsx +303 -303
  57. package/doc_src/demo/PageChessboardMove.tsx +173 -173
  58. package/doc_src/demo/PageChessboardSmallScreens.tsx +94 -94
  59. package/doc_src/demo/PageMovetextBase.tsx +180 -180
  60. package/doc_src/demo/PageMovetextInteraction.tsx +123 -123
  61. package/doc_src/demo/PageNavigationBoardBase.tsx +144 -137
  62. package/doc_src/demo/PageNavigationBoardUncontrolledVsControlled.tsx +69 -69
  63. package/doc_src/demo/decs.d.ts +4 -4
  64. package/doc_src/demo/demo.css +12 -12
  65. package/doc_src/demo/util.ts +1 -1
  66. package/doc_src/examples/NavigationBoard.md +1 -1
  67. package/doc_src/theming/LogoRenderer.js +13 -13
  68. package/doc_src/theming/theming.css +5 -5
  69. package/package.json +95 -95
  70. package/scripts/doc.styleguide.config.js +131 -131
  71. package/scripts/doc.tsconfig.json +5 -5
  72. package/scripts/docker-compose.yml +1 -1
  73. package/scripts/test-graphic.tsconfig.json +5 -5
  74. package/scripts/test-graphic.webpack.config.js +57 -57
  75. package/scripts/test-headless.webpack.config.js +29 -29
  76. package/src/chessboard/BoardProperties.ts +68 -68
  77. package/src/chessboard/Chessboard.css +13 -13
  78. package/src/chessboard/Chessboard.tsx +436 -380
  79. package/src/chessboard/ChessboardImpl.tsx +557 -557
  80. package/src/chessboard/Draggable.tsx +185 -185
  81. package/src/chessboard/Motion.tsx +67 -67
  82. package/src/chessboard/colorsets.ts +66 -66
  83. package/src/chessboard/piecesets.ts +84 -84
  84. package/src/decs.d.ts +2 -2
  85. package/src/errorbox/ErrorBox.css +18 -18
  86. package/src/errorbox/ErrorBox.tsx +75 -75
  87. package/src/errorbox/parsing.tsx +95 -95
  88. package/src/exception.ts +17 -17
  89. package/src/i18n.ts +1 -0
  90. package/src/icons/AnnotationSymbolShape.css +6 -6
  91. package/src/icons/AnnotationSymbolShape.tsx +31 -31
  92. package/src/icons/ArrowMarkerIcon.tsx +34 -34
  93. package/src/icons/ArrowTip.tsx +14 -14
  94. package/src/icons/ChessPieceIcon.tsx +45 -45
  95. package/src/icons/SquareMarkerIcon.tsx +20 -20
  96. package/src/icons/TextMarkerIcon.tsx +29 -29
  97. package/src/icons/arrow.css +1 -1
  98. package/src/index.ts +3 -2
  99. package/src/movetext/Movetext.css +27 -27
  100. package/src/movetext/Movetext.tsx +158 -158
  101. package/src/movetext/MovetextImpl.tsx +345 -345
  102. package/src/movetext/fonts.css +7 -7
  103. package/src/movetext/htmlFilter.tsx +129 -129
  104. package/src/movetext/moveFormatter.tsx +37 -37
  105. package/src/navigationboard/NavigationBoard.tsx +386 -180
  106. package/src/navigationboard/NavigationButton.ts +114 -0
  107. package/src/navigationboard/NavigationField.css +7 -7
  108. package/src/navigationboard/NavigationField.tsx +90 -90
  109. package/src/navigationboard/NavigationToolbar.css +16 -13
  110. package/src/navigationboard/NavigationToolbar.tsx +98 -112
  111. package/src/navigationboard/iconPaths.ts +98 -0
  112. package/src/sanitization.ts +17 -17
  113. package/src/types.ts +116 -116
  114. package/src/util.ts +11 -11
  115. package/test/01_types.js +167 -167
  116. package/test/02_chessboard_util.js +191 -115
  117. package/test/03_error_box_graphic.js +16 -16
  118. package/test/04_icons_graphic.js +32 -32
  119. package/test/05_chessboard_graphic.js +60 -60
  120. package/test/06_chessboard_animation.js +36 -36
  121. package/test/07_chessboard_interaction.js +101 -101
  122. package/test/08_chessboard_play_moves.js +62 -62
  123. package/test/09_chessboard_small_screens.js +32 -32
  124. package/test/10_movetext_graphic.js +32 -32
  125. package/test/11_movetext_interaction.js +100 -100
  126. package/test/12_navigation_board_base.js +151 -0
  127. package/test/13_navigation_board_graphic.js +68 -0
  128. package/test/14_navigation_board_autoplay.js +100 -0
  129. package/test/common/graphic.js +106 -97
  130. package/test/graphic_references/12_navigation_board_base/controlled/default.png +0 -0
  131. package/test/graphic_references/{12_navigation_board/controlled/forbidden_navigation_1.png → 12_navigation_board_base/controlled/forbidden_navigation_0.png} +0 -0
  132. package/test/graphic_references/{12_navigation_board/controlled/invalid_node_id.png → 12_navigation_board_base/controlled/forbidden_navigation_1.png} +0 -0
  133. package/test/graphic_references/{12_navigation_board/controlled/forbidden_navigation_0.png → 12_navigation_board_base/controlled/invalid_node_id.png} +0 -0
  134. package/test/graphic_references/12_navigation_board_base/controlled/no_callback_defined_0.png +0 -0
  135. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/controlled/no_callback_defined_1.png +0 -0
  136. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/controlled/overriding_initial_node_id.png +0 -0
  137. package/test/graphic_references/{12_navigation_board/controlled/regular_navigation_1.png → 12_navigation_board_base/controlled/regular_navigation_0.png} +0 -0
  138. package/test/graphic_references/{12_navigation_board/uncontrolled/initial_state_at_specific_id.png → 12_navigation_board_base/controlled/regular_navigation_1.png} +0 -0
  139. package/test/graphic_references/{12_navigation_board/controlled/regular_navigation_3.png → 12_navigation_board_base/controlled/regular_navigation_2.png} +0 -0
  140. package/test/graphic_references/{12_navigation_board/controlled/regular_navigation_0.png → 12_navigation_board_base/controlled/regular_navigation_3.png} +0 -0
  141. package/test/graphic_references/{12_navigation_board/uncontrolled/forbidden_navigation_at_beginning_2.png → 12_navigation_board_base/uncontrolled/forbidden_navigation_at_beginning_0.png} +0 -0
  142. package/test/graphic_references/{12_navigation_board/uncontrolled/forbidden_navigation_at_beginning_0.png → 12_navigation_board_base/uncontrolled/forbidden_navigation_at_beginning_1.png} +0 -0
  143. package/test/graphic_references/{12_navigation_board/uncontrolled/forbidden_navigation_at_beginning_1.png → 12_navigation_board_base/uncontrolled/forbidden_navigation_at_beginning_2.png} +0 -0
  144. package/test/graphic_references/{12_navigation_board/controlled/no_callback_defined_0.png → 12_navigation_board_base/uncontrolled/forbidden_navigation_at_end_0.png} +0 -0
  145. package/test/graphic_references/12_navigation_board_base/uncontrolled/forbidden_navigation_at_end_1.png +0 -0
  146. package/test/graphic_references/12_navigation_board_base/uncontrolled/forbidden_navigation_at_end_2.png +0 -0
  147. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/initial_state_at_end.png +0 -0
  148. package/test/graphic_references/12_navigation_board_base/uncontrolled/initial_state_at_specific_id.png +0 -0
  149. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/initial_state_default.png +0 -0
  150. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/initial_state_default_with_game_index.png +0 -0
  151. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/initial_state_on_null-move.png +0 -0
  152. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/navigation_with_keys_0.png +0 -0
  153. package/test/graphic_references/12_navigation_board_base/uncontrolled/navigation_with_keys_1.png +0 -0
  154. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/navigation_with_keys_2.png +0 -0
  155. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/navigation_with_keys_3.png +0 -0
  156. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/navigation_with_keys_4.png +0 -0
  157. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/regular_flipping_0.png +0 -0
  158. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/regular_flipping_1.png +0 -0
  159. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/regular_navigation_0.png +0 -0
  160. package/test/graphic_references/{12_navigation_board/controlled/default.png → 12_navigation_board_base/uncontrolled/regular_navigation_1.png} +0 -0
  161. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/regular_navigation_2.png +0 -0
  162. package/test/graphic_references/12_navigation_board_base/uncontrolled/regular_navigation_3.png +0 -0
  163. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/regular_navigation_4.png +0 -0
  164. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/regular_navigation_5.png +0 -0
  165. package/test/graphic_references/13_navigation_board_graphic/additional_buttons/error_1.png +0 -0
  166. package/test/graphic_references/13_navigation_board_graphic/additional_buttons/error_2.png +0 -0
  167. package/test/graphic_references/13_navigation_board_graphic/additional_buttons/multiple_buttons.png +0 -0
  168. package/test/graphic_references/{12_navigation_board/uncontrolled/regular_navigation_3.png → 13_navigation_board_graphic/additional_buttons/single_button.png} +0 -0
  169. package/test/graphic_references/{12_navigation_board → 13_navigation_board_graphic}/theme/custom_1.png +0 -0
  170. package/test/graphic_references/{12_navigation_board → 13_navigation_board_graphic}/theme/custom_2.png +0 -0
  171. package/test/graphic_references/{12_navigation_board → 13_navigation_board_graphic}/theme/large.png +0 -0
  172. package/test/graphic_references/13_navigation_board_graphic/theme/small.png +0 -0
  173. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_1_move_acknowledging_is-playing_changes.png +0 -0
  174. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_1_move_not_acknowledging_is-playing_changes.png +0 -0
  175. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_2_moves_acknowledging_node_changes.png +0 -0
  176. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_2_moves_not_acknowledging_node_changes.png +0 -0
  177. package/test/graphic_references/{12_navigation_board/uncontrolled/forbidden_navigation_at_end_1.png → 14_navigation_board_autoplay/controlled/after_reaching_the_end.png} +0 -0
  178. package/test/graphic_references/{12_navigation_board/uncontrolled/forbidden_navigation_at_end_2.png → 14_navigation_board_autoplay/controlled/already_at_the_end.png} +0 -0
  179. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_acknowledging_is-playing_changes.png +0 -0
  180. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_acknowledging_node_changes.png +0 -0
  181. package/test/graphic_references/{12_navigation_board/uncontrolled/forbidden_navigation_at_end_0.png → 14_navigation_board_autoplay/controlled/initial_state_at_the_end.png} +0 -0
  182. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_close_to_end.png +0 -0
  183. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_not_acknowledging_is-playing_changes.png +0 -0
  184. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_not_acknowledging_node_changes.png +0 -0
  185. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/after_2_moves_from_beginning_after_stop.png +0 -0
  186. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/after_2_moves_from_beginning_before_stop.png +0 -0
  187. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/after_reaching_the_end_after_stop.png +0 -0
  188. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/after_reaching_the_end_before_stop.png +0 -0
  189. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/initial_state_close_to_end.png +0 -0
  190. package/test/graphic_references/{12_navigation_board/uncontrolled/navigation_with_keys_1.png → 14_navigation_board_autoplay/uncontrolled/initial_state_default.png} +0 -0
  191. package/test/graphic_test_app/03_error_box_graphic/base.tsx +14 -14
  192. package/test/graphic_test_app/03_error_box_graphic/localization.tsx +1 -1
  193. package/test/graphic_test_app/04_icons_graphic/chess_piece.tsx +7 -7
  194. package/test/graphic_test_app/04_icons_graphic/marker.tsx +15 -15
  195. package/test/graphic_test_app/05_chessboard_graphic/annotations.tsx +7 -7
  196. package/test/graphic_test_app/05_chessboard_graphic/base.tsx +6 -6
  197. package/test/graphic_test_app/05_chessboard_graphic/chess_variants.tsx +7 -7
  198. package/test/graphic_test_app/05_chessboard_graphic/flipped.tsx +5 -5
  199. package/test/graphic_test_app/05_chessboard_graphic/move.tsx +12 -12
  200. package/test/graphic_test_app/05_chessboard_graphic/theme.tsx +12 -12
  201. package/test/graphic_test_app/06_chessboard_animation/base_1.tsx +6 -6
  202. package/test/graphic_test_app/06_chessboard_animation/base_2.tsx +6 -6
  203. package/test/graphic_test_app/06_chessboard_animation/base_3.tsx +6 -6
  204. package/test/graphic_test_app/06_chessboard_animation/on_redraw.tsx +16 -16
  205. package/test/graphic_test_app/07_chessboard_interaction/click_squares.tsx +5 -5
  206. package/test/graphic_test_app/07_chessboard_interaction/edit_arrows.tsx +4 -4
  207. package/test/graphic_test_app/07_chessboard_interaction/error.tsx +2 -2
  208. package/test/graphic_test_app/07_chessboard_interaction/move_pieces.tsx +5 -5
  209. package/test/graphic_test_app/08_chessboard_play_moves/base.tsx +8 -8
  210. package/test/graphic_test_app/08_chessboard_play_moves/promotions.tsx +4 -4
  211. package/test/graphic_test_app/09_chessboard_small_screens/base.tsx +9 -9
  212. package/test/graphic_test_app/09_chessboard_small_screens/top_bottom_components.tsx +9 -9
  213. package/test/graphic_test_app/10_movetext_graphic/base.tsx +4 -4
  214. package/test/graphic_test_app/10_movetext_graphic/error.tsx +6 -6
  215. package/test/graphic_test_app/10_movetext_graphic/format_move.tsx +1 -1
  216. package/test/graphic_test_app/10_movetext_graphic/html.tsx +5 -5
  217. package/test/graphic_test_app/10_movetext_graphic/options.tsx +10 -10
  218. package/test/graphic_test_app/11_movetext_interaction/error.tsx +1 -1
  219. package/test/graphic_test_app/11_movetext_interaction/select_moves.tsx +11 -11
  220. package/test/graphic_test_app/{12_navigation_board → 12_navigation_board_base}/controlled.tsx +5 -5
  221. package/test/graphic_test_app/{12_navigation_board → 12_navigation_board_base}/uncontrolled.tsx +7 -7
  222. package/test/graphic_test_app/13_navigation_board_graphic/additional_buttons.tsx +49 -0
  223. package/test/graphic_test_app/{12_navigation_board → 13_navigation_board_graphic}/error.tsx +2 -2
  224. package/test/graphic_test_app/{12_navigation_board → 13_navigation_board_graphic}/theme.tsx +4 -4
  225. package/test/graphic_test_app/14_navigation_board_autoplay/controlled.tsx +73 -0
  226. package/test/graphic_test_app/14_navigation_board_autoplay/uncontrolled.tsx +34 -0
  227. package/test/graphic_test_app/common/decs.d.ts +2 -2
  228. package/test/graphic_test_app/common/test_app.css +15 -15
  229. package/test/graphic_test_app/common/test_app.tsx +48 -48
  230. package/tsconfig.json +13 -13
  231. package/test/12_navigation_board.js +0 -168
  232. package/test/graphic_references/12_navigation_board/controlled/regular_navigation_2.png +0 -0
  233. package/test/graphic_references/12_navigation_board/theme/small.png +0 -0
  234. package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_1.png +0 -0
  235. /package/test/graphic_references/{12_navigation_board → 13_navigation_board_graphic}/error/pgn_parsing_error.png +0 -0
  236. /package/test/graphic_references/{12_navigation_board → 13_navigation_board_graphic}/error/wrong_game_type.png +0 -0
@@ -39,19 +39,19 @@ import './Movetext.css';
39
39
 
40
40
  interface MovetextImplProps {
41
41
 
42
- game: Game;
42
+ game: Game;
43
43
 
44
- diagramOptions: Partial<StaticBoardGraphicProps> & {
45
- flipped?: boolean,
46
- };
47
- moveFormatter: (notation: string) => React.ReactNode;
48
- diagramVisible: boolean;
49
- headerVisible: boolean;
44
+ diagramOptions: Partial<StaticBoardGraphicProps> & {
45
+ flipped?: boolean,
46
+ };
47
+ moveFormatter: (notation: string) => React.ReactNode;
48
+ diagramVisible: boolean;
49
+ headerVisible: boolean;
50
50
 
51
- selection?: string;
52
- interactionMode?: 'selectMove';
51
+ selection?: string;
52
+ interactionMode?: 'selectMove';
53
53
 
54
- onMoveSelected?: (nodeId: string | undefined, evtOrigin: 'key-first' | 'key-previous' | 'key-next' | 'key-last' | 'key-exit' | 'click') => void;
54
+ onMoveSelected?: (nodeId: string | undefined, evtOrigin: 'key-first' | 'key-previous' | 'key-next' | 'key-last' | 'key-exit' | 'click') => void;
55
55
  }
56
56
 
57
57
 
@@ -62,335 +62,335 @@ interface MovetextImplProps {
62
62
  */
63
63
  export class MovetextImpl extends React.Component<MovetextImplProps> {
64
64
 
65
- private navigationFieldRef: React.RefObject<NavigationField> = React.createRef();
66
-
67
- render() {
68
- return (
69
- <div className="kokopu-movetext">
70
- {this.renderHeaders()}
71
- {this.renderBody()}
72
- {this.renderNavigationField()}
73
- </div>
74
- );
75
- }
76
-
77
- private renderHeaders() {
78
- if (!this.props.headerVisible) {
79
- return undefined;
80
- }
81
-
82
- const filter = htmlFilter('header');
83
- const headers: React.ReactNode[] = [];
84
- headers.push(this.renderPlayerHeaders(filter, 'w'));
85
- headers.push(this.renderPlayerHeaders(filter, 'b'));
86
- headers.push(this.renderEventRoundHeaders(filter));
87
- headers.push(this.renderDateSiteHeaders(filter));
88
- headers.push(this.renderAnnotatorHeader(filter));
89
- return headers.some(element => element !== undefined) ? <div className="kokopu-headers">{headers}</div> : undefined;
90
- }
91
-
92
- /**
93
- * Header containing the player-related information (name, rating, title) corresponding to the given color.
94
- */
95
- private renderPlayerHeaders(filter: (text: string) => React.ReactNode, color: Color) {
96
- const playerName = this.props.game.playerName(color);
97
- if (playerName === undefined) {
98
- return undefined;
99
- }
100
- const title = this.props.game.playerTitle(color);
101
- const rating = this.props.game.playerElo(color);
102
-
103
- const classNames = [ 'kokopu-headerGroup-player', color === 'w' ? 'kokopu-headerGroup-whitePlayer' : 'kokopu-headerGroup-blackPlayer' ];
104
- const colorTag = <span className="kokopu-colorTag"></span>;
105
- const playerNameElement = <span className="kokopu-header-playerName">{filter(playerName)}</span>;
106
- const titleElement = title === undefined ? undefined : <span className="kokopu-header-playerTitle">{filter(title)}</span>;
107
- const ratingElement = rating === undefined ? undefined : <span className="kokopu-header-playerRating">{rating}</span>;
108
- const separator = title === undefined || rating === undefined ? undefined : '\u00a0'; // \u00a0 == &nbsp;
109
- const titleRatingGroup = title === undefined && rating === undefined ? undefined :
110
- <span className="kokopu-headerGroup-titleRating">{titleElement}{separator}{ratingElement}</span>;
111
- return <div className={classNames.join(' ')} key={'player-' + color}>{colorTag}{playerNameElement}{titleRatingGroup}</div>;
112
- }
113
-
114
- /**
115
- * Header containing the event-related information: event + round.
116
- */
117
- private renderEventRoundHeaders(filter: (text: string) => React.ReactNode) {
118
- const evt = this.props.game.event();
119
- if (evt === undefined) {
120
- return undefined;
121
- }
122
- const fullRound = this.props.game.fullRound();
123
- const roundElement = fullRound === undefined ? undefined : <span className="kokopu-header-round">{fullRound}</span>;
124
- const evtElement = <span className="kokopu-header-event">{filter(evt)}</span>;
125
- return <div className="kokopu-headerGroup-eventRound" key="event-round">{evtElement}{roundElement}</div>;
126
- }
127
-
128
- /**
129
- * Header containing the date/place information.
130
- */
131
- private renderDateSiteHeaders(filter: (text: string) => React.ReactNode) {
132
- const date = this.props.game.dateAsString();
133
- const site = this.props.game.site();
134
- if (date === undefined && site === undefined) {
135
- return undefined;
136
- }
137
- const dateElement = date === undefined ? undefined : <span className="kokopu-header-date">{capitalizeFirstWord(date)}</span>;
138
- const siteElement = site === undefined ? undefined : <span className="kokopu-header-site">{filter(site)}</span>;
139
- const separator = date === undefined || site === undefined ? undefined : '\u00a0\u2013\u00a0'; // \u00a0 == &nbsp; \u2013 == &ndash;
140
- return <div className="kokopu-headerGroup-dateSite" key="date-site">{dateElement}{separator}{siteElement}</div>;
141
- }
142
-
143
- /**
144
- * Header containing the annotator information.
145
- */
146
- private renderAnnotatorHeader(filter: (text: string) => React.ReactNode) {
147
- const annotator = this.props.game.annotator();
148
- if (annotator === undefined) {
149
- return undefined;
150
- }
151
- return <div className="kokopu-header-annotator" key="annotator">{filter(fillPlaceholder(i18n.ANNOTATED_BY, annotator))}</div>;
152
- }
153
-
154
- private renderNavigationField() {
155
- if (this.props.interactionMode !== 'selectMove') {
156
- return undefined;
157
- }
158
- return <NavigationField ref={this.navigationFieldRef}
159
- onFirstPressed={() => this.handleNavigationPressed(firstNodeId, 'key-first')}
160
- onPreviousPressed={() => this.handleNavigationPressed(previousNodeId, 'key-previous')}
161
- onNextPressed={() => this.handleNavigationPressed(nextNodeId, 'key-next')}
162
- onLastPressed={() => this.handleNavigationPressed(lastNodeId, 'key-last')}
163
- onExitPressed={() => this.handleExitPressed()}
164
- />;
165
- }
166
-
167
- private renderBody() {
168
- const filter = htmlFilter('comment');
169
- return this.renderVariation(filter, this.props.game.mainVariation(), true, this.props.game.result());
170
- }
171
-
172
- /**
173
- * Render the given variation and its sub-variations, recursively.
174
- */
175
- private renderVariation(filter: (text: string) => React.ReactNode, variation: Variation, isMainVariation: boolean, gameResult?: GameResult) {
176
-
177
- const moveGroups: React.ReactNode[] = []; // ... and also long comments and long sub-variations
178
- let currentMoveGroupElements: React.ReactNode[] = [];
179
-
180
- // Close the current move group, if any.
181
- function closeMoveGroup() {
182
- if (!variation.isLongVariation() || currentMoveGroupElements.length === 0) {
183
- return;
184
- }
185
- moveGroups.push(<div className="kokopu-moveGroup" key={'group-' + moveGroups.length}>{currentMoveGroupElements}</div>);
186
- currentMoveGroupElements = [];
187
- }
188
-
189
- // Write the initial comment, if any.
190
- const variationComment = this.extractComment(variation);
191
- if (variationComment !== undefined) {
192
- if (variation.isLongComment()) {
193
- moveGroups.push(this.renderComment(filter, variation, variationComment));
194
- }
195
- else {
196
- currentMoveGroupElements.push(this.renderComment(filter, variation, variationComment));
197
- }
198
- }
199
-
200
- // Visit all the PGN nodes (one node per move) within the variation.
201
- let forcePrintMoveNumber = true;
202
- let node = variation.first();
203
- while (node !== undefined) {
204
-
205
- // Write the move, including directly related information (i.e. move number + NAGs).
206
- currentMoveGroupElements.push(this.renderMove(node, forcePrintMoveNumber));
207
-
208
- // Write the comment (if any).
209
- const nodeComment = this.extractComment(node);
210
- if (nodeComment !== undefined) {
211
- if (node.isLongComment()) {
212
- closeMoveGroup();
213
- moveGroups.push(this.renderComment(filter, node, nodeComment));
214
- }
215
- else {
216
- currentMoveGroupElements.push(this.renderComment(filter, node, nodeComment));
217
- }
218
- }
219
-
220
- // Write the sub-variations.
221
- let hasNonEmptySubVariations = false;
222
- for (const subVariation of node.variations()) {
223
- const subVariationElement = this.renderVariation(filter, subVariation, false);
224
- if (subVariationElement) {
225
- if (subVariation.isLongVariation()) {
226
- closeMoveGroup();
227
- moveGroups.push(subVariationElement);
228
- }
229
- else {
230
- currentMoveGroupElements.push(subVariationElement);
231
- }
232
- hasNonEmptySubVariations = true;
233
- }
234
- }
235
-
236
- // Back to the current variation, go to the next move.
237
- forcePrintMoveNumber = (nodeComment !== undefined || hasNonEmptySubVariations);
238
- node = node.next();
239
- }
240
-
241
- // Append the game result at the end of the main variation.
242
- if (isMainVariation && gameResult !== '*') {
243
- currentMoveGroupElements.push(<span className="kokopu-gameResult" key="game-result">{formatResult(gameResult!)}</span>);
244
- }
245
-
246
- // Close the last move group, and return the result.
247
- closeMoveGroup();
248
- const elements = variation.isLongVariation() ? moveGroups : currentMoveGroupElements;
249
- if (elements.length === 0) {
250
- return undefined;
251
- }
252
- if (isMainVariation) {
253
- return <div className="kokopu-variation kokopu-mainVariation" key={variation.id()}>{elements}</div>;
254
- }
255
- else if (variation.isLongVariation()) {
256
- return <div className="kokopu-variation kokopu-subVariation" key={variation.id()}>{elements}</div>;
257
- }
258
- else {
259
- return <span className="kokopu-variation kokopu-subVariation" key={variation.id()}>{elements}</span>;
260
- }
261
- }
262
-
263
- /**
264
- * Render the given move, move number, and NAG (if any).
265
- */
266
- private renderMove(node: GameNode, forcePrintMoveNumber: boolean) {
267
-
268
- // Move number
269
- let moveNumber: React.ReactNode = undefined;
270
- if (forcePrintMoveNumber || node.moveColor() === 'w') {
271
- const moveNumberText = node.fullMoveNumber() + (node.moveColor() === 'w' ? '.' : '\u2026');
272
- moveNumber = <span className="kokopu-moveNumber">{moveNumberText}</span>;
273
- }
274
-
275
- // SAN notation.
276
- const notationText = this.props.moveFormatter(node.notation());
277
-
278
- // NAGs
279
- const nagElements = node.nags().map(nag => <span className="kokopu-nag" key={nag}>{nagSymbol(nag)}</span>);
280
-
281
- // Class
282
- const nodeId = node.id();
283
- const moveClassNames = [ 'kokopu-move' ];
284
- let onClick: (() => void) | undefined = undefined;
285
- if (this.props.selection && this.props.selection === nodeId) {
286
- moveClassNames.push('kokopu-selectedMove');
287
- }
288
- if (this.props.interactionMode === 'selectMove') {
289
- moveClassNames.push('kokopu-clickableMove');
290
- onClick = () => this.handleNodeClicked(nodeId);
291
- }
292
-
293
- return (
294
- <span className={moveClassNames.join(' ')} key={node.id()}>
295
- <span className="kokopu-moveContent" onClick={onClick}>
296
- {moveNumber}
297
- <span className="kokopu-moveNotation">{notationText}</span>
298
- {nagElements.length === 0 ? undefined : nagElements}
299
- </span>
300
- </span>
301
- );
302
- }
303
-
304
- /**
305
- * Render the given text comment with its diagrams, if any.
306
- */
307
- private renderComment(filter: (text: string) => React.ReactNode, node: GameNode | Variation, comment: string) {
308
- let content: React.ReactNode;
309
- if (this.props.diagramVisible && comment.includes('[#]')) {
310
- const segmentElements: React.ReactNode[] = [];
311
- let isFirstTextSegment = true;
312
- let diagramIndex = 0;
313
- for (let textSegment of comment.split('[#]')) {
314
- if (!isFirstTextSegment) {
315
- const position = node instanceof Variation ? node.initialPosition() : node.position();
316
- const diagram = <Chessboard
317
- position={position}
318
- squareMarkers={node.tag('csl')} arrowMarkers={node.tag('cal')} textMarkers={node.tag('ctl')}
319
- flipped={this.props.diagramOptions.flipped}
320
- squareSize={this.props.diagramOptions.squareSize}
321
- coordinateVisible={this.props.diagramOptions.coordinateVisible}
322
- turnVisible={this.props.diagramOptions.turnVisible}
323
- colorset={this.props.diagramOptions.colorset}
324
- pieceset={this.props.diagramOptions.pieceset}
325
- smallScreenLimits={this.props.diagramOptions.smallScreenLimits}
326
- />;
327
- segmentElements.push(<div className="kokopu-diagram" key={'diagram-' + (diagramIndex++)}>{diagram}</div>);
328
- }
329
- isFirstTextSegment = false;
330
- textSegment = textSegment.trim();
331
- if (textSegment.length !== 0) {
332
- segmentElements.push(filter(textSegment));
333
- }
334
- }
335
- content = segmentElements;
336
- }
337
- else {
338
- content = filter(comment);
339
- }
340
- const key = node.id() + '-comment';
341
- return node.isLongComment() ? <div className="kokopu-comment" key={key}>{content}</div> : <span className="kokopu-comment" key={key}>{content}</span>;
342
- }
343
-
344
- private extractComment(node: GameNode | Variation) {
345
- let comment = node.comment();
346
- if (comment) {
347
-
348
- // Remove the diagrams if necessary.
349
- if (!this.props.diagramVisible) {
350
- comment = comment.replace(/\[#\]/g, ' ');
351
- }
352
-
353
- // Trim and sanitize the space characters.
354
- comment = comment.replace(/\s+/g, ' ').trim();
355
- }
356
- return comment ? comment : undefined;
357
- }
358
-
359
- private handleNavigationPressed(transition: (game: Game, nodeId: string) => string | undefined, evtOrigin: 'key-first' | 'key-previous' | 'key-next' | 'key-last') {
360
- if (this.props.selection) {
361
- this.fireMoveSelected(transition(this.props.game, this.props.selection), evtOrigin);
362
- }
363
- }
364
-
365
- private handleExitPressed() {
366
- if (this.props.selection && this.props.game.findById(this.props.selection)) {
367
- this.fireMoveSelected(undefined, 'key-exit');
368
- }
369
- }
370
-
371
- private fireMoveSelected(nodeId: string | undefined, evtOrigin: 'key-first' | 'key-previous' | 'key-next' | 'key-last' | 'key-exit') {
372
- if ((nodeId || evtOrigin === 'key-exit') && this.props.onMoveSelected) {
373
- this.props.onMoveSelected(nodeId, evtOrigin);
374
- }
375
- }
376
-
377
- private handleNodeClicked(nodeId: string) {
378
- this.focus();
379
- if (this.props.onMoveSelected) {
380
- this.props.onMoveSelected(nodeId === this.props.selection ? undefined : nodeId, 'click');
381
- }
382
- }
383
-
384
- /**
385
- * Set the focus to the current component.
386
- */
387
- focus(): void {
388
- const target = this.navigationFieldRef.current;
389
- // istanbul ignore else
390
- if (target) {
391
- target.focus();
392
- }
393
- }
65
+ private navigationFieldRef: React.RefObject<NavigationField> = React.createRef();
66
+
67
+ render() {
68
+ return (
69
+ <div className="kokopu-movetext">
70
+ {this.renderHeaders()}
71
+ {this.renderBody()}
72
+ {this.renderNavigationField()}
73
+ </div>
74
+ );
75
+ }
76
+
77
+ private renderHeaders() {
78
+ if (!this.props.headerVisible) {
79
+ return undefined;
80
+ }
81
+
82
+ const filter = htmlFilter('header');
83
+ const headers: React.ReactNode[] = [];
84
+ headers.push(this.renderPlayerHeaders(filter, 'w'));
85
+ headers.push(this.renderPlayerHeaders(filter, 'b'));
86
+ headers.push(this.renderEventRoundHeaders(filter));
87
+ headers.push(this.renderDateSiteHeaders(filter));
88
+ headers.push(this.renderAnnotatorHeader(filter));
89
+ return headers.some(element => element !== undefined) ? <div className="kokopu-headers">{headers}</div> : undefined;
90
+ }
91
+
92
+ /**
93
+ * Header containing the player-related information (name, rating, title) corresponding to the given color.
94
+ */
95
+ private renderPlayerHeaders(filter: (text: string) => React.ReactNode, color: Color) {
96
+ const playerName = this.props.game.playerName(color);
97
+ if (playerName === undefined) {
98
+ return undefined;
99
+ }
100
+ const title = this.props.game.playerTitle(color);
101
+ const rating = this.props.game.playerElo(color);
102
+
103
+ const classNames = [ 'kokopu-headerGroup-player', color === 'w' ? 'kokopu-headerGroup-whitePlayer' : 'kokopu-headerGroup-blackPlayer' ];
104
+ const colorTag = <span className="kokopu-colorTag"></span>;
105
+ const playerNameElement = <span className="kokopu-header-playerName">{filter(playerName)}</span>;
106
+ const titleElement = title === undefined ? undefined : <span className="kokopu-header-playerTitle">{filter(title)}</span>;
107
+ const ratingElement = rating === undefined ? undefined : <span className="kokopu-header-playerRating">{rating}</span>;
108
+ const separator = title === undefined || rating === undefined ? undefined : '\u00a0'; // \u00a0 == &nbsp;
109
+ const titleRatingGroup = title === undefined && rating === undefined ? undefined :
110
+ <span className="kokopu-headerGroup-titleRating">{titleElement}{separator}{ratingElement}</span>;
111
+ return <div className={classNames.join(' ')} key={'player-' + color}>{colorTag}{playerNameElement}{titleRatingGroup}</div>;
112
+ }
113
+
114
+ /**
115
+ * Header containing the event-related information: event + round.
116
+ */
117
+ private renderEventRoundHeaders(filter: (text: string) => React.ReactNode) {
118
+ const evt = this.props.game.event();
119
+ if (evt === undefined) {
120
+ return undefined;
121
+ }
122
+ const fullRound = this.props.game.fullRound();
123
+ const roundElement = fullRound === undefined ? undefined : <span className="kokopu-header-round">{fullRound}</span>;
124
+ const evtElement = <span className="kokopu-header-event">{filter(evt)}</span>;
125
+ return <div className="kokopu-headerGroup-eventRound" key="event-round">{evtElement}{roundElement}</div>;
126
+ }
127
+
128
+ /**
129
+ * Header containing the date/place information.
130
+ */
131
+ private renderDateSiteHeaders(filter: (text: string) => React.ReactNode) {
132
+ const date = this.props.game.dateAsString();
133
+ const site = this.props.game.site();
134
+ if (date === undefined && site === undefined) {
135
+ return undefined;
136
+ }
137
+ const dateElement = date === undefined ? undefined : <span className="kokopu-header-date">{capitalizeFirstWord(date)}</span>;
138
+ const siteElement = site === undefined ? undefined : <span className="kokopu-header-site">{filter(site)}</span>;
139
+ const separator = date === undefined || site === undefined ? undefined : '\u00a0\u2013\u00a0'; // \u00a0 == &nbsp; \u2013 == &ndash;
140
+ return <div className="kokopu-headerGroup-dateSite" key="date-site">{dateElement}{separator}{siteElement}</div>;
141
+ }
142
+
143
+ /**
144
+ * Header containing the annotator information.
145
+ */
146
+ private renderAnnotatorHeader(filter: (text: string) => React.ReactNode) {
147
+ const annotator = this.props.game.annotator();
148
+ if (annotator === undefined) {
149
+ return undefined;
150
+ }
151
+ return <div className="kokopu-header-annotator" key="annotator">{filter(fillPlaceholder(i18n.ANNOTATED_BY, annotator))}</div>;
152
+ }
153
+
154
+ private renderNavigationField() {
155
+ if (this.props.interactionMode !== 'selectMove') {
156
+ return undefined;
157
+ }
158
+ return <NavigationField ref={this.navigationFieldRef}
159
+ onFirstPressed={() => this.handleNavigationPressed(firstNodeId, 'key-first')}
160
+ onPreviousPressed={() => this.handleNavigationPressed(previousNodeId, 'key-previous')}
161
+ onNextPressed={() => this.handleNavigationPressed(nextNodeId, 'key-next')}
162
+ onLastPressed={() => this.handleNavigationPressed(lastNodeId, 'key-last')}
163
+ onExitPressed={() => this.handleExitPressed()}
164
+ />;
165
+ }
166
+
167
+ private renderBody() {
168
+ const filter = htmlFilter('comment');
169
+ return this.renderVariation(filter, this.props.game.mainVariation(), true, this.props.game.result());
170
+ }
171
+
172
+ /**
173
+ * Render the given variation and its sub-variations, recursively.
174
+ */
175
+ private renderVariation(filter: (text: string) => React.ReactNode, variation: Variation, isMainVariation: boolean, gameResult?: GameResult) {
176
+
177
+ const moveGroups: React.ReactNode[] = []; // ... and also long comments and long sub-variations
178
+ let currentMoveGroupElements: React.ReactNode[] = [];
179
+
180
+ // Close the current move group, if any.
181
+ function closeMoveGroup() {
182
+ if (!variation.isLongVariation() || currentMoveGroupElements.length === 0) {
183
+ return;
184
+ }
185
+ moveGroups.push(<div className="kokopu-moveGroup" key={'group-' + moveGroups.length}>{currentMoveGroupElements}</div>);
186
+ currentMoveGroupElements = [];
187
+ }
188
+
189
+ // Write the initial comment, if any.
190
+ const variationComment = this.extractComment(variation);
191
+ if (variationComment !== undefined) {
192
+ if (variation.isLongComment()) {
193
+ moveGroups.push(this.renderComment(filter, variation, variationComment));
194
+ }
195
+ else {
196
+ currentMoveGroupElements.push(this.renderComment(filter, variation, variationComment));
197
+ }
198
+ }
199
+
200
+ // Visit all the PGN nodes (one node per move) within the variation.
201
+ let forcePrintMoveNumber = true;
202
+ let node = variation.first();
203
+ while (node !== undefined) {
204
+
205
+ // Write the move, including directly related information (i.e. move number + NAGs).
206
+ currentMoveGroupElements.push(this.renderMove(node, forcePrintMoveNumber));
207
+
208
+ // Write the comment (if any).
209
+ const nodeComment = this.extractComment(node);
210
+ if (nodeComment !== undefined) {
211
+ if (node.isLongComment()) {
212
+ closeMoveGroup();
213
+ moveGroups.push(this.renderComment(filter, node, nodeComment));
214
+ }
215
+ else {
216
+ currentMoveGroupElements.push(this.renderComment(filter, node, nodeComment));
217
+ }
218
+ }
219
+
220
+ // Write the sub-variations.
221
+ let hasNonEmptySubVariations = false;
222
+ for (const subVariation of node.variations()) {
223
+ const subVariationElement = this.renderVariation(filter, subVariation, false);
224
+ if (subVariationElement) {
225
+ if (subVariation.isLongVariation()) {
226
+ closeMoveGroup();
227
+ moveGroups.push(subVariationElement);
228
+ }
229
+ else {
230
+ currentMoveGroupElements.push(subVariationElement);
231
+ }
232
+ hasNonEmptySubVariations = true;
233
+ }
234
+ }
235
+
236
+ // Back to the current variation, go to the next move.
237
+ forcePrintMoveNumber = (nodeComment !== undefined || hasNonEmptySubVariations);
238
+ node = node.next();
239
+ }
240
+
241
+ // Append the game result at the end of the main variation.
242
+ if (isMainVariation && gameResult !== '*') {
243
+ currentMoveGroupElements.push(<span className="kokopu-gameResult" key="game-result">{formatResult(gameResult!)}</span>);
244
+ }
245
+
246
+ // Close the last move group, and return the result.
247
+ closeMoveGroup();
248
+ const elements = variation.isLongVariation() ? moveGroups : currentMoveGroupElements;
249
+ if (elements.length === 0) {
250
+ return undefined;
251
+ }
252
+ if (isMainVariation) {
253
+ return <div className="kokopu-variation kokopu-mainVariation" key={variation.id()}>{elements}</div>;
254
+ }
255
+ else if (variation.isLongVariation()) {
256
+ return <div className="kokopu-variation kokopu-subVariation" key={variation.id()}>{elements}</div>;
257
+ }
258
+ else {
259
+ return <span className="kokopu-variation kokopu-subVariation" key={variation.id()}>{elements}</span>;
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Render the given move, move number, and NAG (if any).
265
+ */
266
+ private renderMove(node: GameNode, forcePrintMoveNumber: boolean) {
267
+
268
+ // Move number
269
+ let moveNumber: React.ReactNode = undefined;
270
+ if (forcePrintMoveNumber || node.moveColor() === 'w') {
271
+ const moveNumberText = node.fullMoveNumber() + (node.moveColor() === 'w' ? '.' : '\u2026');
272
+ moveNumber = <span className="kokopu-moveNumber">{moveNumberText}</span>;
273
+ }
274
+
275
+ // SAN notation.
276
+ const notationText = this.props.moveFormatter(node.notation());
277
+
278
+ // NAGs
279
+ const nagElements = node.nags().map(nag => <span className="kokopu-nag" key={nag}>{nagSymbol(nag)}</span>);
280
+
281
+ // Class
282
+ const nodeId = node.id();
283
+ const moveClassNames = [ 'kokopu-move' ];
284
+ let onClick: (() => void) | undefined = undefined;
285
+ if (this.props.selection && this.props.selection === nodeId) {
286
+ moveClassNames.push('kokopu-selectedMove');
287
+ }
288
+ if (this.props.interactionMode === 'selectMove') {
289
+ moveClassNames.push('kokopu-clickableMove');
290
+ onClick = () => this.handleNodeClicked(nodeId);
291
+ }
292
+
293
+ return (
294
+ <span className={moveClassNames.join(' ')} key={node.id()}>
295
+ <span className="kokopu-moveContent" onClick={onClick}>
296
+ {moveNumber}
297
+ <span className="kokopu-moveNotation">{notationText}</span>
298
+ {nagElements.length === 0 ? undefined : nagElements}
299
+ </span>
300
+ </span>
301
+ );
302
+ }
303
+
304
+ /**
305
+ * Render the given text comment with its diagrams, if any.
306
+ */
307
+ private renderComment(filter: (text: string) => React.ReactNode, node: GameNode | Variation, comment: string) {
308
+ let content: React.ReactNode;
309
+ if (this.props.diagramVisible && comment.includes('[#]')) {
310
+ const segmentElements: React.ReactNode[] = [];
311
+ let isFirstTextSegment = true;
312
+ let diagramIndex = 0;
313
+ for (let textSegment of comment.split('[#]')) {
314
+ if (!isFirstTextSegment) {
315
+ const position = node instanceof Variation ? node.initialPosition() : node.position();
316
+ const diagram = <Chessboard
317
+ position={position}
318
+ squareMarkers={node.tag('csl')} arrowMarkers={node.tag('cal')} textMarkers={node.tag('ctl')}
319
+ flipped={this.props.diagramOptions.flipped}
320
+ squareSize={this.props.diagramOptions.squareSize}
321
+ coordinateVisible={this.props.diagramOptions.coordinateVisible}
322
+ turnVisible={this.props.diagramOptions.turnVisible}
323
+ colorset={this.props.diagramOptions.colorset}
324
+ pieceset={this.props.diagramOptions.pieceset}
325
+ smallScreenLimits={this.props.diagramOptions.smallScreenLimits}
326
+ />;
327
+ segmentElements.push(<div className="kokopu-diagram" key={'diagram-' + (diagramIndex++)}>{diagram}</div>);
328
+ }
329
+ isFirstTextSegment = false;
330
+ textSegment = textSegment.trim();
331
+ if (textSegment.length !== 0) {
332
+ segmentElements.push(filter(textSegment));
333
+ }
334
+ }
335
+ content = segmentElements;
336
+ }
337
+ else {
338
+ content = filter(comment);
339
+ }
340
+ const key = node.id() + '-comment';
341
+ return node.isLongComment() ? <div className="kokopu-comment" key={key}>{content}</div> : <span className="kokopu-comment" key={key}>{content}</span>;
342
+ }
343
+
344
+ private extractComment(node: GameNode | Variation) {
345
+ let comment = node.comment();
346
+ if (comment) {
347
+
348
+ // Remove the diagrams if necessary.
349
+ if (!this.props.diagramVisible) {
350
+ comment = comment.replace(/\[#\]/g, ' ');
351
+ }
352
+
353
+ // Trim and sanitize the space characters.
354
+ comment = comment.replace(/\s+/g, ' ').trim();
355
+ }
356
+ return comment ? comment : undefined;
357
+ }
358
+
359
+ private handleNavigationPressed(transition: (game: Game, nodeId: string) => string | undefined, evtOrigin: 'key-first' | 'key-previous' | 'key-next' | 'key-last') {
360
+ if (this.props.selection) {
361
+ this.fireMoveSelected(transition(this.props.game, this.props.selection), evtOrigin);
362
+ }
363
+ }
364
+
365
+ private handleExitPressed() {
366
+ if (this.props.selection && this.props.game.findById(this.props.selection)) {
367
+ this.fireMoveSelected(undefined, 'key-exit');
368
+ }
369
+ }
370
+
371
+ private fireMoveSelected(nodeId: string | undefined, evtOrigin: 'key-first' | 'key-previous' | 'key-next' | 'key-last' | 'key-exit') {
372
+ if ((nodeId || evtOrigin === 'key-exit') && this.props.onMoveSelected) {
373
+ this.props.onMoveSelected(nodeId, evtOrigin);
374
+ }
375
+ }
376
+
377
+ private handleNodeClicked(nodeId: string) {
378
+ this.focus();
379
+ if (this.props.onMoveSelected) {
380
+ this.props.onMoveSelected(nodeId === this.props.selection ? undefined : nodeId, 'click');
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Set the focus to the current component.
386
+ */
387
+ focus(): void {
388
+ const target = this.navigationFieldRef.current;
389
+ // istanbul ignore else
390
+ if (target) {
391
+ target.focus();
392
+ }
393
+ }
394
394
 
395
395
  }
396
396
 
@@ -399,14 +399,14 @@ export class MovetextImpl extends React.Component<MovetextImplProps> {
399
399
  * Example: `'hello world'` is turned into `'Hello world'`.
400
400
  */
401
401
  function capitalizeFirstWord(text: string) {
402
- return text.length === 0 ? '' : text.charAt(0).toUpperCase() + text.slice(1);
402
+ return text.length === 0 ? '' : text.charAt(0).toUpperCase() + text.slice(1);
403
403
  }
404
404
 
405
405
 
406
406
  function formatResult(result: GameResult) {
407
- switch (result) {
408
- case '1/2-1/2': return '\u00bd\u2013\u00bd';
409
- case '1-0': return '1\u20130';
410
- case '0-1': return '0\u20131';
411
- }
407
+ switch (result) {
408
+ case '1/2-1/2': return '\u00bd\u2013\u00bd';
409
+ case '1-0': return '1\u20130';
410
+ case '0-1': return '0\u20131';
411
+ }
412
412
  }