kokopu-react 1.8.0 → 1.9.1

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 (158) hide show
  1. package/dist/lib/ArrowMarkerIcon.js +1 -29
  2. package/dist/lib/Chessboard.js +152 -298
  3. package/dist/lib/ErrorBox.js +7 -24
  4. package/dist/lib/Movetext.js +74 -178
  5. package/dist/lib/SquareMarkerIcon.js +2 -10
  6. package/dist/lib/TextMarkerIcon.js +0 -11
  7. package/dist/lib/css/movetext.css +4 -0
  8. package/dist/lib/formatmove.js +2 -14
  9. package/dist/lib/i18n.js +1 -1
  10. package/dist/lib/impl/ArrowTip.js +2 -8
  11. package/dist/lib/impl/HtmlSanitizer.js +13 -46
  12. package/dist/lib/impl/Motion.js +107 -0
  13. package/dist/lib/impl/TextSymbol.js +2 -11
  14. package/dist/lib/impl/colorsets.js +0 -2
  15. package/dist/lib/impl/piecesets.js +0 -87
  16. package/dist/lib/impl/util.js +1 -11
  17. package/dist/lib/index.js +0 -10
  18. package/dist/lib/markers.js +18 -38
  19. package/graphic_test_src/05_chessboard_move.js +2 -2
  20. package/graphic_test_src/06a_chessboard_animated_move.js +36 -0
  21. package/graphic_test_src/06b_chessboard_animated_move.js +36 -0
  22. package/graphic_test_src/06c_chessboard_animated_move.js +36 -0
  23. package/graphic_test_src/{06_chessboard_theme.js → 07_chessboard_theme.js} +0 -0
  24. package/graphic_test_src/{07_chessboard_click_squares.js → 08_chessboard_click_squares.js} +0 -0
  25. package/graphic_test_src/{08_chessboard_move_pieces.js → 09_chessboard_move_pieces.js} +0 -0
  26. package/graphic_test_src/{09_chessboard_edit_arrows.js → 10_chessboard_edit_arrows.js} +0 -0
  27. package/graphic_test_src/{10_chessboard_play_moves.js → 11_chessboard_play_moves.js} +0 -0
  28. package/graphic_test_src/{11_chessboard_play_promotions.js → 12_chessboard_play_promotions.js} +0 -0
  29. package/graphic_test_src/{12_movetext_simple.js → 13_movetext_simple.js} +0 -0
  30. package/graphic_test_src/{13_movetext_error.js → 14_movetext_error.js} +0 -0
  31. package/graphic_test_src/{14_movetext_html.js → 15_movetext_html.js} +0 -0
  32. package/graphic_test_src/{15_movetext_options.js → 16_movetext_options.js} +0 -0
  33. package/graphic_test_src/{16_movetext_interaction.js → 17_movetext_interaction.js} +0 -0
  34. package/package.json +19 -21
  35. package/scripts/docker-compose.yml +1 -1
  36. package/src/Chessboard.js +24 -20
  37. package/src/css/movetext.css +4 -0
  38. package/src/impl/ArrowTip.js +2 -2
  39. package/src/impl/Motion.js +99 -0
  40. package/test/4_chessboard_graphic.js +4 -1
  41. package/test/5_chessboard_interaction.js +5 -5
  42. package/test/6_chessboard_play_moves.js +4 -4
  43. package/test/7_movetext_graphic.js +4 -4
  44. package/test/8_movetext_interaction.js +2 -2
  45. package/test/common/graphic.js +6 -0
  46. package/test/references/06a_chessboard_animated_move/0.png +0 -0
  47. package/test/references/06a_chessboard_animated_move/1.png +0 -0
  48. package/test/references/06a_chessboard_animated_move/2.png +0 -0
  49. package/test/references/06a_chessboard_animated_move/3.png +0 -0
  50. package/test/references/06a_chessboard_animated_move/4.png +0 -0
  51. package/test/references/06a_chessboard_animated_move/5.png +0 -0
  52. package/test/references/06b_chessboard_animated_move/0.png +0 -0
  53. package/test/references/06b_chessboard_animated_move/1.png +0 -0
  54. package/test/references/06b_chessboard_animated_move/2.png +0 -0
  55. package/test/references/06b_chessboard_animated_move/3.png +0 -0
  56. package/test/references/06b_chessboard_animated_move/4.png +0 -0
  57. package/test/references/06b_chessboard_animated_move/5.png +0 -0
  58. package/test/references/06c_chessboard_animated_move/0.png +0 -0
  59. package/test/references/06c_chessboard_animated_move/1.png +0 -0
  60. package/test/references/06c_chessboard_animated_move/2.png +0 -0
  61. package/test/references/06c_chessboard_animated_move/3.png +0 -0
  62. package/test/references/06c_chessboard_animated_move/4.png +0 -0
  63. package/test/references/06c_chessboard_animated_move/5.png +0 -0
  64. package/test/references/{06_chessboard_theme → 07_chessboard_theme}/0.png +0 -0
  65. package/test/references/{06_chessboard_theme → 07_chessboard_theme}/1.png +0 -0
  66. package/test/references/{06_chessboard_theme → 07_chessboard_theme}/2.png +0 -0
  67. package/test/references/{06_chessboard_theme → 07_chessboard_theme}/3.png +0 -0
  68. package/test/references/{06_chessboard_theme → 07_chessboard_theme}/4.png +0 -0
  69. package/test/references/{06_chessboard_theme → 07_chessboard_theme}/5.png +0 -0
  70. package/test/references/{06_chessboard_theme → 07_chessboard_theme}/6.png +0 -0
  71. package/test/references/{06_chessboard_theme → 07_chessboard_theme}/7.png +0 -0
  72. package/test/references/{06_chessboard_theme → 07_chessboard_theme}/8.png +0 -0
  73. package/test/references/{08_chessboard_move_pieces → 09_chessboard_move_pieces}/after_move.png +0 -0
  74. package/test/references/{08_chessboard_move_pieces → 09_chessboard_move_pieces}/empty_square.png +0 -0
  75. package/test/references/{08_chessboard_move_pieces → 09_chessboard_move_pieces}/null_vector.png +0 -0
  76. package/test/references/{08_chessboard_move_pieces → 09_chessboard_move_pieces}/out_of_board.png +0 -0
  77. package/test/references/{08_chessboard_move_pieces → 09_chessboard_move_pieces}/over_arrow_marker.png +0 -0
  78. package/test/references/{08_chessboard_move_pieces → 09_chessboard_move_pieces}/over_empty.png +0 -0
  79. package/test/references/{08_chessboard_move_pieces → 09_chessboard_move_pieces}/over_non_empty_1.png +0 -0
  80. package/test/references/{08_chessboard_move_pieces → 09_chessboard_move_pieces}/over_non_empty_2.png +0 -0
  81. package/test/references/{08_chessboard_move_pieces → 09_chessboard_move_pieces}/over_square_marker.png +0 -0
  82. package/test/references/{08_chessboard_move_pieces → 09_chessboard_move_pieces}/over_text_marker.png +0 -0
  83. package/test/references/{09_chessboard_edit_arrows → 10_chessboard_edit_arrows}/base_1.png +0 -0
  84. package/test/references/{09_chessboard_edit_arrows → 10_chessboard_edit_arrows}/base_2.png +0 -0
  85. package/test/references/{09_chessboard_edit_arrows → 10_chessboard_edit_arrows}/edit_color_not_set.png +0 -0
  86. package/test/references/{09_chessboard_edit_arrows → 10_chessboard_edit_arrows}/null_vector.png +0 -0
  87. package/test/references/{09_chessboard_edit_arrows → 10_chessboard_edit_arrows}/out_of_board.png +0 -0
  88. package/test/references/{09_chessboard_edit_arrows → 10_chessboard_edit_arrows}/over_arrow_marker.png +0 -0
  89. package/test/references/{09_chessboard_edit_arrows → 10_chessboard_edit_arrows}/over_square_marker.png +0 -0
  90. package/test/references/{10_chessboard_play_moves → 11_chessboard_play_moves}/castling_move.png +0 -0
  91. package/test/references/{10_chessboard_play_moves → 11_chessboard_play_moves}/chess960_ambiguous_king_move.png +0 -0
  92. package/test/references/{10_chessboard_play_moves → 11_chessboard_play_moves}/chess960_castling_move_1.png +0 -0
  93. package/test/references/{10_chessboard_play_moves → 11_chessboard_play_moves}/chess960_castling_move_2.png +0 -0
  94. package/test/references/{10_chessboard_play_moves → 11_chessboard_play_moves}/chess960_non_kxr_castling.png +0 -0
  95. package/test/references/{10_chessboard_play_moves → 11_chessboard_play_moves}/illegal_move.png +0 -0
  96. package/test/references/{10_chessboard_play_moves → 11_chessboard_play_moves}/illegal_position.png +0 -0
  97. package/test/references/{10_chessboard_play_moves → 11_chessboard_play_moves}/null_vector.png +0 -0
  98. package/test/references/{10_chessboard_play_moves → 11_chessboard_play_moves}/out_of_board.png +0 -0
  99. package/test/references/{10_chessboard_play_moves → 11_chessboard_play_moves}/regular_move_1.png +0 -0
  100. package/test/references/{10_chessboard_play_moves → 11_chessboard_play_moves}/regular_move_2.png +0 -0
  101. package/test/references/{10_chessboard_play_moves → 11_chessboard_play_moves}/wrong_color.png +0 -0
  102. package/test/references/{11_chessboard_play_promotions → 12_chessboard_play_promotions}/antichess_promotion.png +0 -0
  103. package/test/references/{11_chessboard_play_promotions → 12_chessboard_play_promotions}/cancel_promotion.png +0 -0
  104. package/test/references/{11_chessboard_play_promotions → 12_chessboard_play_promotions}/regular_promotion_1.png +0 -0
  105. package/test/references/{11_chessboard_play_promotions → 12_chessboard_play_promotions}/regular_promotion_2.png +0 -0
  106. package/test/references/{12_movetext_simple → 13_movetext_simple}/0.png +0 -0
  107. package/test/references/{12_movetext_simple → 13_movetext_simple}/1.png +0 -0
  108. package/test/references/{12_movetext_simple → 13_movetext_simple}/2.png +0 -0
  109. package/test/references/{12_movetext_simple → 13_movetext_simple}/3.png +0 -0
  110. package/test/references/{13_movetext_error → 14_movetext_error}/0.png +0 -0
  111. package/test/references/{13_movetext_error → 14_movetext_error}/1.png +0 -0
  112. package/test/references/{13_movetext_error → 14_movetext_error}/2.png +0 -0
  113. package/test/references/{13_movetext_error → 14_movetext_error}/3.png +0 -0
  114. package/test/references/{13_movetext_error → 14_movetext_error}/4.png +0 -0
  115. package/test/references/{14_movetext_html → 15_movetext_html}/0.png +0 -0
  116. package/test/references/{14_movetext_html → 15_movetext_html}/1.png +0 -0
  117. package/test/references/{14_movetext_html → 15_movetext_html}/2.png +0 -0
  118. package/test/references/{15_movetext_options → 16_movetext_options}/0.png +0 -0
  119. package/test/references/{15_movetext_options → 16_movetext_options}/1.png +0 -0
  120. package/test/references/{15_movetext_options → 16_movetext_options}/2.png +0 -0
  121. package/test/references/{15_movetext_options → 16_movetext_options}/3.png +0 -0
  122. package/test/references/{16_movetext_interaction → 17_movetext_interaction}/0.png +0 -0
  123. package/test/references/{16_movetext_interaction → 17_movetext_interaction}/1.png +0 -0
  124. package/test/references/{16_movetext_interaction → 17_movetext_interaction}/2.png +0 -0
  125. package/CHANGELOG.md +0 -141
  126. package/graphics/chess_sprites/cburnett/bb.svg +0 -45
  127. package/graphics/chess_sprites/cburnett/bk.svg +0 -47
  128. package/graphics/chess_sprites/cburnett/bn.svg +0 -37
  129. package/graphics/chess_sprites/cburnett/bp.svg +0 -19
  130. package/graphics/chess_sprites/cburnett/bq.svg +0 -58
  131. package/graphics/chess_sprites/cburnett/br.svg +0 -60
  132. package/graphics/chess_sprites/cburnett/bx.svg +0 -56
  133. package/graphics/chess_sprites/cburnett/wb.svg +0 -48
  134. package/graphics/chess_sprites/cburnett/wk.svg +0 -26
  135. package/graphics/chess_sprites/cburnett/wn.svg +0 -37
  136. package/graphics/chess_sprites/cburnett/wp.svg +0 -19
  137. package/graphics/chess_sprites/cburnett/wq.svg +0 -60
  138. package/graphics/chess_sprites/cburnett/wr.svg +0 -44
  139. package/graphics/chess_sprites/cburnett/wx.svg +0 -56
  140. package/graphics/chess_sprites/generate_sprites.sh +0 -135
  141. package/graphics/chess_sprites/mmonge/celtic-bx.svg +0 -112
  142. package/graphics/chess_sprites/mmonge/celtic-wx.svg +0 -111
  143. package/graphics/chess_sprites/mmonge/celtic.svg +0 -1576
  144. package/graphics/chess_sprites/mmonge/eyes-spatial-bx.svg +0 -101
  145. package/graphics/chess_sprites/mmonge/eyes-spatial-wx.svg +0 -100
  146. package/graphics/chess_sprites/mmonge/eyes.svg +0 -2538
  147. package/graphics/chess_sprites/mmonge/fantasy-bx.svg +0 -112
  148. package/graphics/chess_sprites/mmonge/fantasy-wx.svg +0 -111
  149. package/graphics/chess_sprites/mmonge/fantasy.svg +0 -5497
  150. package/graphics/chess_sprites/mmonge/fantasy_alt.svg +0 -3742
  151. package/graphics/chess_sprites/mmonge/freak.svg +0 -3479
  152. package/graphics/chess_sprites/mmonge/prmi.svg +0 -6776
  153. package/graphics/chess_sprites/mmonge/skulls-bx.svg +0 -124
  154. package/graphics/chess_sprites/mmonge/skulls-wx.svg +0 -111
  155. package/graphics/chess_sprites/mmonge/skulls.svg +0 -12978
  156. package/graphics/chess_sprites/mmonge/spatial.svg +0 -1223
  157. package/graphics/logo-small.svg +0 -14
  158. package/graphics/logo.svg +0 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kokopu-react",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "description": "A React-based library to create and display chessboard and chess-related components.",
5
5
  "keywords": [
6
6
  "chess",
@@ -19,7 +19,6 @@
19
19
  "dist/lib",
20
20
  "doc_src",
21
21
  "graphic_test_src",
22
- "graphics",
23
22
  "scripts/clean-graphic-output.js",
24
23
  "scripts/docker-compose.yml",
25
24
  "scripts/test-graphic.webpack.config.js",
@@ -37,39 +36,38 @@
37
36
  "dependencies": {
38
37
  "htmlparser2": "^8.0.1",
39
38
  "prop-types": "^15.8.1",
40
- "react-draggable": "^4.4.5",
41
- "react-motion": "^0.5.2"
39
+ "react-draggable": "^4.4.5"
42
40
  },
43
41
  "devDependencies": {
44
- "@babel/cli": "^7.18.10",
45
- "@babel/core": "^7.18.13",
46
- "@babel/preset-env": "^7.18.10",
42
+ "@babel/cli": "^7.19.3",
43
+ "@babel/core": "^7.20.2",
44
+ "@babel/preset-env": "^7.20.2",
47
45
  "@babel/preset-react": "^7.18.6",
48
- "@emotion/react": "^11.10.0",
49
- "@emotion/styled": "^11.10.0",
50
- "@mui/material": "^5.10.2",
51
- "babel-loader": "^8.2.5",
46
+ "@emotion/react": "^11.10.5",
47
+ "@emotion/styled": "^11.10.5",
48
+ "@mui/material": "^5.10.14",
49
+ "babel-loader": "^9.1.0",
52
50
  "babel-plugin-istanbul": "^6.1.1",
53
51
  "copy-webpack-plugin": "^11.0.0",
54
52
  "coveralls": "^3.1.1",
55
- "css-loader": "^6.7.1",
56
- "eslint": "^8.22.0",
57
- "eslint-plugin-react": "^7.31.0",
53
+ "css-loader": "^6.7.2",
54
+ "eslint": "^8.27.0",
55
+ "eslint-plugin-react": "^7.31.10",
58
56
  "html-webpack-plugin": "^5.5.0",
59
57
  "img-diff-js": "^0.5.2",
60
- "kokopu": "^3.0.0",
61
- "mocha": "^10.0.0",
58
+ "kokopu": "^3.3.0",
59
+ "mocha": "^10.1.0",
62
60
  "null-loader": "^4.0.1",
63
61
  "nyc": "^15.1.0",
64
62
  "react": "^18.2.0",
65
63
  "react-dom": "^18.2.0",
66
- "react-styleguidist": "^11.2.0",
67
- "selenium-webdriver": "^4.3.1",
68
- "ssh2-sftp-client": "^9.0.3",
64
+ "react-styleguidist": "^13.0.0",
65
+ "selenium-webdriver": "^4.6.1",
66
+ "ssh2-sftp-client": "^9.0.4",
69
67
  "style-loader": "^3.3.1",
70
68
  "unit.js": "^2.1.1",
71
- "webpack": "^5.74.0",
72
- "webpack-cli": "^4.10.0"
69
+ "webpack": "^5.75.0",
70
+ "webpack-cli": "^5.0.0"
73
71
  },
74
72
  "scripts": {
75
73
  "lint": "eslint doc_src graphic_test_src src test",
@@ -2,7 +2,7 @@ version: "3.7"
2
2
 
3
3
  services:
4
4
  web-client:
5
- image: selenium/standalone-firefox:4.3.0-20220726
5
+ image: selenium/standalone-firefox:4.6.0-20221104
6
6
  shm_size: '2gb'
7
7
  ports:
8
8
  - 4444:4444 # http://localhost:4444 (driver)
package/src/Chessboard.js CHANGED
@@ -23,12 +23,12 @@
23
23
  import PropTypes from 'prop-types';
24
24
  import React from 'react';
25
25
  import Draggable from 'react-draggable';
26
- import { Motion, spring } from 'react-motion';
27
26
  import { exception, MoveDescriptor, Position, coordinatesToSquare, forEachSquare, oppositeColor, squareColor, squareToCoordinates } from 'kokopu';
28
27
 
29
28
  import colorsets from './impl/colorsets';
30
29
  import piecesets from './impl/piecesets';
31
30
  import ArrowTip from './impl/ArrowTip';
31
+ import Motion from './impl/Motion';
32
32
  import TextSymbol from './impl/TextSymbol';
33
33
  import ErrorBox from './ErrorBox';
34
34
  import i18n from './i18n';
@@ -45,7 +45,7 @@ const HOVER_MARKER_THICKNESS_FACTOR = 0.1;
45
45
  const STROKE_THICKNESS_FACTOR = 0.15;
46
46
  const ARROW_TIP_OFFSET_FACTOR = 0.3;
47
47
 
48
- const ANIMATION_SPEED = { stiffness: 700, damping: 40 };
48
+ const MOTION_DURATION = 150;
49
49
 
50
50
  const RANK_LABELS = '12345678';
51
51
  const FILE_LABELS = 'abcdefgh';
@@ -67,15 +67,18 @@ export default class Chessboard extends React.Component {
67
67
  windowWidth: window.innerWidth,
68
68
  };
69
69
  this.arrowTipIdSuffix = generateRandomId();
70
- this.windowResizeListener = () => this.handleWindowResize();
71
70
  }
72
71
 
73
72
  componentDidMount() {
73
+ this.windowResizeListener = () => this.handleWindowResize();
74
74
  window.addEventListener('resize', this.windowResizeListener);
75
75
  }
76
76
 
77
77
  componentWillUnmount() {
78
- window.removeEventListener('resize', this.windowResizeListener);
78
+ if (this.windowResizeListener) {
79
+ window.removeEventListener('resize', this.windowResizeListener);
80
+ this.windowResizeListener = null;
81
+ }
79
82
  }
80
83
 
81
84
  render() {
@@ -138,10 +141,11 @@ export default class Chessboard extends React.Component {
138
141
 
139
142
  renderBoardContent(position, positionBefore, move, squareSize, colorset, pieceset) {
140
143
  if (move && this.props.animated) {
144
+ let key = positionBefore.variant() + '|' + positionBefore.fen() + '|' + move.toString();
141
145
  return (
142
- <Motion key={move.toString()} defaultStyle={{ alpha: 0 }} style={{ alpha: spring(1, ANIMATION_SPEED) }}>
143
- {currentStyle => (currentStyle.alpha >= 1 ? this.renderBoardContentStill(position, move, squareSize, colorset, pieceset)
144
- : this.renderBoardContentAnimated(positionBefore, move, currentStyle.alpha, squareSize, colorset, pieceset))}
146
+ <Motion key={key} duration={MOTION_DURATION}>
147
+ {motionCursor => (motionCursor === 1 ? this.renderBoardContentStill(position, move, squareSize, colorset, pieceset)
148
+ : this.renderBoardContentAnimated(positionBefore, move, motionCursor, squareSize, colorset, pieceset))}
145
149
  </Motion>
146
150
  );
147
151
  }
@@ -153,13 +157,13 @@ export default class Chessboard extends React.Component {
153
157
  /**
154
158
  * Render the board content during the animation.
155
159
  */
156
- renderBoardContentAnimated(positionBefore, move, alpha, squareSize, colorset, pieceset) {
160
+ renderBoardContentAnimated(positionBefore, move, motionCursor, squareSize, colorset, pieceset) {
157
161
  let pieces = [];
158
- forEachSquare(sq => pieces.push(this.renderPieceAnimated(positionBefore, move, alpha, squareSize, pieceset, sq)));
162
+ forEachSquare(sq => pieces.push(this.renderPieceAnimated(positionBefore, move, motionCursor, squareSize, pieceset, sq)));
159
163
  return (
160
164
  <>
161
165
  {pieces}
162
- {this.renderMoveArrow(move, alpha, squareSize, colorset)}
166
+ {this.renderMoveArrow(move, motionCursor, squareSize, colorset)}
163
167
  {this.renderTurnFlag(oppositeColor(positionBefore.turn()), squareSize, pieceset)}
164
168
  </>
165
169
  );
@@ -226,7 +230,7 @@ export default class Chessboard extends React.Component {
226
230
  return <image key={'piece-' + sq} x={x} y={y} width={squareSize} height={squareSize} href={pieceset[cp]} />;
227
231
  }
228
232
 
229
- renderPieceAnimated(positionBefore, move, alpha, squareSize, pieceset, sq) {
233
+ renderPieceAnimated(positionBefore, move, motionCursor, squareSize, pieceset, sq) {
230
234
  let cp = positionBefore.square(sq);
231
235
  if (cp === '-' || move.to() === sq || (move.isEnPassant() && move.enPassantSquare() === sq)) {
232
236
  return undefined;
@@ -234,16 +238,16 @@ export default class Chessboard extends React.Component {
234
238
  let { x, y } = this.getSquareCoordinates(squareSize, sq);
235
239
  if (sq === move.from()) {
236
240
  let { x: xTo, y: yTo } = this.getSquareCoordinates(squareSize, move.to());
237
- x = xTo * alpha + x * (1 - alpha);
238
- y = yTo * alpha + y * (1 - alpha);
239
- if (move.isPromotion() && alpha > 0.8) {
241
+ x = xTo * motionCursor + x * (1 - motionCursor);
242
+ y = yTo * motionCursor + y * (1 - motionCursor);
243
+ if (move.isPromotion() && motionCursor > 0.8) {
240
244
  cp = move.coloredPromotion();
241
245
  }
242
246
  }
243
247
  else if (move.isCastling() && sq === move.rookFrom()) {
244
248
  let { x: xTo, y: yTo } = this.getSquareCoordinates(squareSize, move.rookTo());
245
- x = xTo * alpha + x * (1 - alpha);
246
- y = yTo * alpha + y * (1 - alpha);
249
+ x = xTo * motionCursor + x * (1 - motionCursor);
250
+ y = yTo * motionCursor + y * (1 - motionCursor);
247
251
  }
248
252
  return <image key={'piece-' + sq} x={x} y={y} width={squareSize} height={squareSize} href={pieceset[cp]} />;
249
253
  }
@@ -384,8 +388,8 @@ export default class Chessboard extends React.Component {
384
388
  return result;
385
389
  }
386
390
 
387
- renderMoveArrow(move, alpha, squareSize, colorset) {
388
- if (!move || alpha < 0.1 || !this.props.moveArrowVisible || move.from() === move.to()) {
391
+ renderMoveArrow(move, motionCursor, squareSize, colorset) {
392
+ if (!move || motionCursor < 0.1 || !this.props.moveArrowVisible || move.from() === move.to()) {
389
393
  return undefined;
390
394
  }
391
395
  let { x: xFrom, y: yFrom } = this.getSquareCoordinates(squareSize, move.from());
@@ -396,8 +400,8 @@ export default class Chessboard extends React.Component {
396
400
  yTo += squareSize / 2;
397
401
  xTo += Math.sign(xFrom - xTo) * ARROW_TIP_OFFSET_FACTOR * squareSize;
398
402
  yTo += Math.sign(yFrom - yTo) * ARROW_TIP_OFFSET_FACTOR * squareSize;
399
- let x = xTo * alpha + xFrom * (1 - alpha);
400
- let y = yTo * alpha + yFrom * (1 - alpha);
403
+ let x = xTo * motionCursor + xFrom * (1 - motionCursor);
404
+ let y = yTo * motionCursor + yFrom * (1 - motionCursor);
401
405
  let color = this.props.moveArrowColor;
402
406
  return (
403
407
  <line
@@ -115,3 +115,7 @@ div.kokopu-subVariation {
115
115
  height: 0px;
116
116
  overflow: hidden;
117
117
  }
118
+
119
+ .kokopu-focusField {
120
+ position: absolute; /* see #1 */
121
+ }
@@ -29,8 +29,8 @@ import React from 'react';
29
29
  */
30
30
  export default function ArrowTip(props) {
31
31
  return (
32
- <marker id={props.id} markerWidth={4} markerHeight={4} refX={2.5} refY={2} orient="auto" fill={props.color}>
33
- <path d="M 4,2 L 0,4 L 1,2 L 0,0 Z" />
32
+ <marker id={props.id} markerWidth={4} markerHeight={4} refX={2.5} refY={2} orient="auto">
33
+ <path fill={props.color} d="M 4,2 L 0,4 L 1,2 L 0,0 Z" />
34
34
  </marker>
35
35
  );
36
36
  }
@@ -0,0 +1,99 @@
1
+ /******************************************************************************
2
+ * *
3
+ * This file is part of Kokopu-React, a JavaScript chess library. *
4
+ * Copyright (C) 2021-2022 Yoann Le Montagner <yo35 -at- melix.net> *
5
+ * *
6
+ * This program is free software: you can redistribute it and/or *
7
+ * modify it under the terms of the GNU Lesser General Public License *
8
+ * as published by the Free Software Foundation, either version 3 of *
9
+ * the License, or (at your option) any later version. *
10
+ * *
11
+ * This program is distributed in the hope that it will be useful, *
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
+ * GNU Lesser General Public License for more details. *
15
+ * *
16
+ * You should have received a copy of the GNU Lesser General *
17
+ * Public License along with this program. If not, see *
18
+ * <http://www.gnu.org/licenses/>. *
19
+ * *
20
+ ******************************************************************************/
21
+
22
+
23
+ import PropTypes from 'prop-types';
24
+ import React from 'react';
25
+
26
+ const DEBUG_KEY = '__kokopu_debug_freeze_motion';
27
+
28
+
29
+ /**
30
+ * Wrap some animated content.
31
+ */
32
+ export default class Motion extends React.Component {
33
+
34
+ constructor(props) {
35
+ super(props);
36
+ this.state = {
37
+ cursor: 0,
38
+ };
39
+ this.animationFrameId = null;
40
+ this.cursorStop = 1;
41
+
42
+ // WARNING: this hack exists only for testing purposes. DO NOT USE IT IN PRODUCTION.
43
+ if (typeof window[DEBUG_KEY] === 'number' && window[DEBUG_KEY] >= 0 && window[DEBUG_KEY] <= 1) {
44
+ this.cursorStop = window[DEBUG_KEY];
45
+ }
46
+ }
47
+
48
+ componentDidMount() {
49
+ this.animationFrameId = window.requestAnimationFrame(ts => this.handleAnimationStep(ts));
50
+ }
51
+
52
+ componentWillUnmount() {
53
+ if (this.animationFrameId !== null) {
54
+ window.cancelAnimationFrame(this.animationFrameId);
55
+ this.animationFrameId = null;
56
+ }
57
+ }
58
+
59
+ handleAnimationStep(timestamp) {
60
+ if (this.initialTimestamp === undefined) {
61
+ this.initialTimestamp = timestamp;
62
+ }
63
+
64
+ // Compute the animation cursor and update the state.
65
+ let cursor = (timestamp - this.initialTimestamp) / this.props.duration;
66
+ if (cursor < 0) {
67
+ cursor = 0;
68
+ }
69
+ else if (cursor > this.cursorStop) {
70
+ cursor = this.cursorStop;
71
+ }
72
+ this.setState({ cursor: cursor });
73
+
74
+ // Schedule the next animation frame if necessary.
75
+ if (cursor === this.cursorStop) {
76
+ this.animationFrameId = null;
77
+ }
78
+ else {
79
+ this.animationFrameId = window.requestAnimationFrame(ts => this.handleAnimationStep(ts));
80
+ }
81
+ }
82
+
83
+ render() {
84
+ return this.props.children(this.state.cursor);
85
+ }
86
+ }
87
+
88
+ Motion.propTypes = {
89
+
90
+ /**
91
+ * Duration of the animation. Must be > 0.
92
+ */
93
+ duration: PropTypes.number.isRequired,
94
+
95
+ /**
96
+ * Factory for the content being animated. The animation cursor is guaranteed to be valued between 0 and 1 inclusive.
97
+ */
98
+ children: PropTypes.func.isRequired,
99
+ };
@@ -41,6 +41,9 @@ describe('Chessboard graphic', function() {
41
41
  'overlap', 'overlap & flip' ]);
42
42
  itChecksScreenshots(browserContext, '05_chessboard_move', [ 'default', 'no move arrow', 'invalid', 'with forced move arrow & flip', 'capture', 'castling move',
43
43
  'en-passant', 'promotion', 'wrong type' ]);
44
- itChecksScreenshots(browserContext, '06_chessboard_theme', [ 'default', 'large', 'small', 'custom 1', 'custom 2', 'custom 3',
44
+ itChecksScreenshots(browserContext, '06a_chessboard_animated_move', [ 'move 1', 'move 2', 'capture', 'castling move', 'en-pssant', 'promotion' ]);
45
+ itChecksScreenshots(browserContext, '06b_chessboard_animated_move', [ 'move 1', 'move 2', 'capture', 'castling move', 'en-pssant', 'promotion' ]);
46
+ itChecksScreenshots(browserContext, '06c_chessboard_animated_move', [ 'move 1', 'move 2', 'capture', 'castling move', 'en-pssant', 'promotion' ]);
47
+ itChecksScreenshots(browserContext, '07_chessboard_theme', [ 'default', 'large', 'small', 'custom 1', 'custom 2', 'custom 3',
45
48
  'custom 4', 'custom 5', 'custom 6' ]);
46
49
  });
@@ -36,7 +36,7 @@ describe('Chessboard interaction', function() {
36
36
  });
37
37
 
38
38
  function itCheckClickSquare(itemIndex, label, targets) {
39
- itCustom(browserContext, '07_chessboard_click_squares', itemIndex, label, async function(element) {
39
+ itCustom(browserContext, '08_chessboard_click_squares', itemIndex, label, async function(element) {
40
40
  let actions = browserContext.driver.actions({ async: true });
41
41
  let area = await element.getRect();
42
42
  for (let i = 0; i < targets.length; ++i) {
@@ -63,7 +63,7 @@ describe('Chessboard interaction', function() {
63
63
  ]);
64
64
 
65
65
  function itCheckMovePiece(itemIndex, label, xFrom, yFrom, xTo, yTo, imageBaseName, expectedText) {
66
- itCustom(browserContext, '08_chessboard_move_pieces', itemIndex, label, async function(element) {
66
+ itCustom(browserContext, '09_chessboard_move_pieces', itemIndex, label, async function(element) {
67
67
  let actions = browserContext.driver.actions({ async: true });
68
68
  let area = await element.getRect();
69
69
  await actions.move({ x: area.x + xFrom, y: area.y + yFrom }).press().move({ x: area.x + xTo, y: area.y + yTo }).perform();
@@ -83,7 +83,7 @@ describe('Chessboard interaction', function() {
83
83
  itCheckMovePiece(2, 'after move', 225, 225, 75, 260, 'after_move', 'piece moved: e4 -> b3');
84
84
 
85
85
  function itCheckNonMovePiece(itemIndex, label, xFrom, yFrom, xTo, yTo, imageBaseName) {
86
- itCustom(browserContext, '08_chessboard_move_pieces', itemIndex, label, async function(element) {
86
+ itCustom(browserContext, '09_chessboard_move_pieces', itemIndex, label, async function(element) {
87
87
  await setSandbox(browserContext, imageBaseName); // can be any value as long as it is unique among other test-cases
88
88
  let actions = browserContext.driver.actions({ async: true });
89
89
  let area = await element.getRect();
@@ -100,7 +100,7 @@ describe('Chessboard interaction', function() {
100
100
  itCheckNonMovePiece(0, 'out of board', 175, 25, 500, 210, 'out_of_board');
101
101
 
102
102
  function itCheckEditArrow(itemIndex, label, xFrom, yFrom, xTo, yTo, imageBaseName, expectedText) {
103
- itCustom(browserContext, '09_chessboard_edit_arrows', itemIndex, label, async function(element) {
103
+ itCustom(browserContext, '10_chessboard_edit_arrows', itemIndex, label, async function(element) {
104
104
  let actions = browserContext.driver.actions({ async: true });
105
105
  let area = await element.getRect();
106
106
  await actions.move({ x: area.x + xFrom, y: area.y + yFrom }).press().move({ x: area.x + xTo, y: area.y + yTo }).perform();
@@ -117,7 +117,7 @@ describe('Chessboard interaction', function() {
117
117
  itCheckEditArrow(1, 'over arrow marker', 40, 110, 125, 290, 'over_arrow_marker', 'arrow edited: h3 -> f6');
118
118
 
119
119
  function itCheckNonEditArrow(itemIndex, label, xFrom, yFrom, xTo, yTo, imageBaseName) {
120
- itCustom(browserContext, '09_chessboard_edit_arrows', itemIndex, label, async function(element) {
120
+ itCustom(browserContext, '10_chessboard_edit_arrows', itemIndex, label, async function(element) {
121
121
  await setSandbox(browserContext, imageBaseName); // can be any value as long as it is unique among other test-cases
122
122
  let actions = browserContext.driver.actions({ async: true });
123
123
  let area = await element.getRect();
@@ -36,7 +36,7 @@ describe('Chessboard play moves', function() {
36
36
  });
37
37
 
38
38
  function itCheckPlayMove(itemIndex, label, xFrom, yFrom, xTo, yTo, imageBaseName, expectedText) {
39
- itCustom(browserContext, '10_chessboard_play_moves', itemIndex, label, async function(element) {
39
+ itCustom(browserContext, '11_chessboard_play_moves', itemIndex, label, async function(element) {
40
40
  let actions = browserContext.driver.actions({ async: true });
41
41
  let area = await element.getRect();
42
42
  await actions.move({ x: area.x + xFrom, y: area.y + yFrom }).press().move({ x: area.x + xTo, y: area.y + yTo }).perform();
@@ -55,7 +55,7 @@ describe('Chessboard play moves', function() {
55
55
  itCheckPlayMove(3, 'chess960 ambiguous king move', 275, 375, 320, 375, 'chess960_ambiguous_king_move', 'move played: Kg1');
56
56
 
57
57
  function itCheckNonPlayMove(itemIndex, label, xFrom, yFrom, xTo, yTo, imageBaseName) {
58
- itCustom(browserContext, '10_chessboard_play_moves', itemIndex, label, async function(element) {
58
+ itCustom(browserContext, '11_chessboard_play_moves', itemIndex, label, async function(element) {
59
59
  await setSandbox(browserContext, imageBaseName); // can be any value as long as it is unique among other test-cases
60
60
  let actions = browserContext.driver.actions({ async: true });
61
61
  let area = await element.getRect();
@@ -75,7 +75,7 @@ describe('Chessboard play moves', function() {
75
75
  itCheckNonPlayMove(3, 'chess960 non-KxR castling', 275, 375, 130, 375, 'chess960_non_kxr_castling');
76
76
 
77
77
  function itCheckPlayPromotion(itemIndex, label, xFrom, yFrom, xTo, yTo, xPromo, yPromo, imageBaseName, expectedText) {
78
- itCustom(browserContext, '11_chessboard_play_promotions', itemIndex, label, async function(element) {
78
+ itCustom(browserContext, '12_chessboard_play_promotions', itemIndex, label, async function(element) {
79
79
  let actions = browserContext.driver.actions({ async: true });
80
80
  let area = await element.getRect();
81
81
  await actions.move({ x: area.x + xFrom, y: area.y + yFrom }).press().move({ x: area.x + xTo, y: area.y + yTo }).release().perform();
@@ -91,7 +91,7 @@ describe('Chessboard play moves', function() {
91
91
  itCheckPlayPromotion(2, 'antichess promotion', 325, 325, 325, 375, 325, 175, 'antichess_promotion', 'promotion move played: b8=K');
92
92
 
93
93
  function itCheckNonPlayPromotion(itemIndex, label, xFrom, yFrom, xTo, yTo, xPromo, yPromo, imageBaseName) {
94
- itCustom(browserContext, '11_chessboard_play_promotions', itemIndex, label, async function(element) {
94
+ itCustom(browserContext, '12_chessboard_play_promotions', itemIndex, label, async function(element) {
95
95
  await setSandbox(browserContext, imageBaseName); // can be any value as long as it is unique among other test-cases
96
96
  let actions = browserContext.driver.actions({ async: true });
97
97
  let area = await element.getRect();
@@ -35,9 +35,9 @@ describe('Movetext graphic', function() {
35
35
  await closeBrowser(browserContext);
36
36
  });
37
37
 
38
- itChecksScreenshots(browserContext, '12_movetext_simple', [ 'game-0', 'game-1', 'game-2', 'game-3' ]);
39
- itChecksScreenshots(browserContext, '13_movetext_error', [ 'wrong-game-index-1', 'wrong-game-index-2', 'pgn-parsing-error-1', 'pgn-parsing-error-2', 'wrong type' ]);
40
- itChecksScreenshots(browserContext, '14_movetext_html', [ 'html-in-headers', 'html-in-comments', 'filtered-tags-and-attributes' ]);
41
- itChecksScreenshots(browserContext, '15_movetext_options', [ 'localized-piece-symbols', 'custom-piece-symbols', 'figurine-piece-symbols-and-diagram-options',
38
+ itChecksScreenshots(browserContext, '13_movetext_simple', [ 'game-0', 'game-1', 'game-2', 'game-3' ]);
39
+ itChecksScreenshots(browserContext, '14_movetext_error', [ 'wrong-game-index-1', 'wrong-game-index-2', 'pgn-parsing-error-1', 'pgn-parsing-error-2', 'wrong type' ]);
40
+ itChecksScreenshots(browserContext, '15_movetext_html', [ 'html-in-headers', 'html-in-comments', 'filtered-tags-and-attributes' ]);
41
+ itChecksScreenshots(browserContext, '16_movetext_options', [ 'localized-piece-symbols', 'custom-piece-symbols', 'figurine-piece-symbols-and-diagram-options',
42
42
  'hidden-diagrams' ]);
43
43
  });
@@ -37,7 +37,7 @@ describe('Movetext interaction', function() {
37
37
  });
38
38
 
39
39
  function itCheckClickMove(itemIndex, label, targets) {
40
- itCustom(browserContext, '16_movetext_interaction', itemIndex, label, async function(element) {
40
+ itCustom(browserContext, '17_movetext_interaction', itemIndex, label, async function(element) {
41
41
  let actions = browserContext.driver.actions({ async: true });
42
42
  await setSandbox(browserContext, '');
43
43
  for (let i = 0; i < targets.length; ++i) {
@@ -72,7 +72,7 @@ describe('Movetext interaction', function() {
72
72
  ]);
73
73
 
74
74
  function itCheckKeyboardActions(itemIndex, label, expectedOnGoFirst, expectedOnGoPrevious, expectedOnGoNext, expectedOnGoLast) {
75
- itCustom(browserContext, '16_movetext_interaction', itemIndex, label, async function(element) {
75
+ itCustom(browserContext, '17_movetext_interaction', itemIndex, label, async function(element) {
76
76
  let focusFieldElement = await element.findElement(By.className('kokopu-focusField'));
77
77
 
78
78
  await setSandbox(browserContext, '');
@@ -38,6 +38,9 @@ const UNREACHABLE_TEST_CLIENT_MESSAGE =
38
38
  'Do not forget to run `npm run test_env:stop` when finished.\n';
39
39
 
40
40
 
41
+ const ANIMATION_DELAY = 200;
42
+
43
+
41
44
  /**
42
45
  * Open a browser in the client (+ ensure everything else is ready to execute graphic tests).
43
46
  */
@@ -111,6 +114,9 @@ async function fetchTestCase(browserContext, testCaseName) {
111
114
  // Fetch the new page.
112
115
  await browserContext.driver.get(`file:///app/build/test_graphic/${testCaseName}/index.html`);
113
116
  browserContext.latestTestCase = testCaseName;
117
+
118
+ // Wait until the end of the animations.
119
+ await new Promise(resolve => setTimeout(resolve, ANIMATION_DELAY));
114
120
  }
115
121
 
116
122