kokopu-react 1.5.2 → 1.5.5
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 +15 -0
- package/README.md +1 -1
- package/dist/lib/Chessboard.js +41 -1
- package/dist/lib/Movetext.js +155 -71
- package/doc_src/home.md +1 -1
- package/graphic_test_app/16_movetext_interaction.js +1 -0
- package/package.json +14 -15
- package/src/Chessboard.js +32 -0
- package/src/Movetext.js +131 -72
- package/test/2_chessboard_util.js +26 -12
- package/test/8_movetext_interaction.js +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
ChangeLog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
1.5.5 (May 18, 2022)
|
|
5
|
+
--------------------
|
|
6
|
+
* Upgrade dependencies.
|
|
7
|
+
* Remove dependency with security issue.
|
|
8
|
+
|
|
9
|
+
1.5.4 (May 1, 2022)
|
|
10
|
+
-------------------
|
|
11
|
+
* Provide method `Chessboard#size(..)` to retrieve the width/height of a `Chessboard` component instance.
|
|
12
|
+
|
|
13
|
+
1.5.3 (April 30, 2022)
|
|
14
|
+
----------------------
|
|
15
|
+
* Add method `Movetext#focus()`.
|
|
16
|
+
* Expose static methods `Movetext#firstNodeId(..)`, `Movetext#previousNodeId(..)`, `Movetext#nextNodeId(..)`
|
|
17
|
+
and `Movetext#lastNodeId(..)`.
|
|
18
|
+
|
|
4
19
|
1.5.2 (April 29, 2022)
|
|
5
20
|
----------------------
|
|
6
21
|
* Prevent default actions when handling key events in component `Movetext`.
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ Kokopu-React
|
|
|
3
3
|
|
|
4
4
|
<img align="right" width="96" height="96" src="graphics/logo.svg" />
|
|
5
5
|
|
|
6
|
-
Kokopu-React is a [React](https://reactjs.org/)-based library to create and display chessboard components.
|
|
6
|
+
Kokopu-React is a [React](https://reactjs.org/)-based library to create and display chessboard and chess-related components.
|
|
7
7
|
Kokopu-React is built on top of [Kokopu](https://www.npmjs.com/package/kokopu), a headless library that
|
|
8
8
|
implements all the chess logic (game rules, parsing of [FEN](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation)
|
|
9
9
|
and [PGN](https://en.wikipedia.org/wiki/Portable_Game_Notation) formats...).
|
package/dist/lib/Chessboard.js
CHANGED
|
@@ -984,6 +984,46 @@ var Chessboard = /*#__PURE__*/function (_React$Component) {
|
|
|
984
984
|
value: function getArrowTipId(color) {
|
|
985
985
|
return 'kokopu-arrowTip-' + color + '-' + this.arrowTipIdSuffix;
|
|
986
986
|
}
|
|
987
|
+
/**
|
|
988
|
+
* Return the size of the chessboard, assuming it is built with the given attributes.
|
|
989
|
+
*
|
|
990
|
+
* @param {number} squareSize
|
|
991
|
+
* @param {boolean} coordinateVisible
|
|
992
|
+
* @param {{width:number, squareSize:number, coordinateVisible:boolean}[]} smallScreenLimits
|
|
993
|
+
* @returns {{width:number, height:number}}
|
|
994
|
+
* @public
|
|
995
|
+
*/
|
|
996
|
+
|
|
997
|
+
}], [{
|
|
998
|
+
key: "size",
|
|
999
|
+
value: function size(squareSize, coordinateVisible, smallScreenLimits) {
|
|
1000
|
+
// Enforce small-screen limits, if any.
|
|
1001
|
+
if (typeof window !== 'undefined') {
|
|
1002
|
+
var squareSizeLimit = computeSmallScreenLimits('squareSize', smallScreenLimits, window.innerWidth);
|
|
1003
|
+
var coordinateVisibleLimit = computeSmallScreenLimits('coordinateVisible', smallScreenLimits, window.innerWidth);
|
|
1004
|
+
|
|
1005
|
+
if (!isNaN(squareSizeLimit)) {
|
|
1006
|
+
squareSize = (0, _util.sanitizeInteger)(squareSizeLimit, _util.MIN_SQUARE_SIZE, squareSize);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
coordinateVisible = coordinateVisible && (coordinateVisibleLimit === undefined || coordinateVisibleLimit);
|
|
1010
|
+
} // Compute the dimensions.
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
var width = 9 * squareSize + Math.round(TURN_FLAG_SPACING_FACTOR * squareSize);
|
|
1014
|
+
var height = 8 * squareSize;
|
|
1015
|
+
|
|
1016
|
+
if (coordinateVisible) {
|
|
1017
|
+
var fontSize = computeCoordinateFontSize(squareSize);
|
|
1018
|
+
width += Math.round(RANK_COORDINATE_WIDTH_FACTOR * fontSize);
|
|
1019
|
+
height += Math.round(FILE_COORDINATE_HEIGHT_FACTOR * fontSize);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
return {
|
|
1023
|
+
width: width,
|
|
1024
|
+
height: height
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
987
1027
|
/**
|
|
988
1028
|
* Return the maximum square size that would allow the chessboard to fit in a rectangle of size `width x height`.
|
|
989
1029
|
*
|
|
@@ -995,7 +1035,7 @@ var Chessboard = /*#__PURE__*/function (_React$Component) {
|
|
|
995
1035
|
* @public
|
|
996
1036
|
*/
|
|
997
1037
|
|
|
998
|
-
}
|
|
1038
|
+
}, {
|
|
999
1039
|
key: "adaptSquareSize",
|
|
1000
1040
|
value: function adaptSquareSize(width, height, coordinateVisible, smallScreenLimits) {
|
|
1001
1041
|
var maxSquareSize = _util.MAX_SQUARE_SIZE; // Enforce small-screen limits, if any.
|
package/dist/lib/Movetext.js
CHANGED
|
@@ -96,7 +96,7 @@ var Movetext = /*#__PURE__*/function (_React$Component) {
|
|
|
96
96
|
|
|
97
97
|
return /*#__PURE__*/_react["default"].createElement("div", {
|
|
98
98
|
className: "kokopu-movetext"
|
|
99
|
-
}, this.renderHeaders(info.game), this.renderBody(info.game), this.renderFocusField());
|
|
99
|
+
}, this.renderHeaders(info.game), this.renderBody(info.game), this.renderFocusField(info.game));
|
|
100
100
|
}
|
|
101
101
|
}, {
|
|
102
102
|
key: "renderHeaders",
|
|
@@ -233,7 +233,7 @@ var Movetext = /*#__PURE__*/function (_React$Component) {
|
|
|
233
233
|
}
|
|
234
234
|
}, {
|
|
235
235
|
key: "renderFocusField",
|
|
236
|
-
value: function renderFocusField() {
|
|
236
|
+
value: function renderFocusField(game) {
|
|
237
237
|
var _this2 = this;
|
|
238
238
|
|
|
239
239
|
if (this.props.interactionMode !== 'selectMove') {
|
|
@@ -247,7 +247,7 @@ var Movetext = /*#__PURE__*/function (_React$Component) {
|
|
|
247
247
|
href: "#",
|
|
248
248
|
ref: this.focusFieldRef,
|
|
249
249
|
onKeyDown: function onKeyDown(evt) {
|
|
250
|
-
return _this2.handleKeyDownInFocusField(evt);
|
|
250
|
+
return _this2.handleKeyDownInFocusField(evt, game);
|
|
251
251
|
}
|
|
252
252
|
}));
|
|
253
253
|
}
|
|
@@ -522,47 +522,33 @@ var Movetext = /*#__PURE__*/function (_React$Component) {
|
|
|
522
522
|
}
|
|
523
523
|
}, {
|
|
524
524
|
key: "handleKeyDownInFocusField",
|
|
525
|
-
value: function handleKeyDownInFocusField(evt) {
|
|
526
|
-
if (evt.key
|
|
527
|
-
|
|
525
|
+
value: function handleKeyDownInFocusField(evt, game) {
|
|
526
|
+
if (evt.key !== 'Home' && evt.key !== 'ArrowLeft' && evt.key !== 'ArrowRight' && evt.key !== 'End') {
|
|
527
|
+
return;
|
|
528
528
|
}
|
|
529
529
|
|
|
530
|
+
evt.preventDefault();
|
|
531
|
+
|
|
530
532
|
if (!this.props.selection) {
|
|
531
533
|
return;
|
|
532
534
|
}
|
|
533
535
|
|
|
534
|
-
var
|
|
535
|
-
var nodeId = false;
|
|
536
|
+
var nodeId = undefined;
|
|
536
537
|
var evtOrigin = '';
|
|
537
538
|
|
|
538
|
-
if (
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
539
|
+
if (evt.key === 'Home') {
|
|
540
|
+
nodeId = Movetext.firstNodeId(game, this.props.selection);
|
|
541
|
+
evtOrigin = 'key-first';
|
|
542
|
+
} else if (evt.key === 'ArrowLeft') {
|
|
543
|
+
nodeId = Movetext.previousNodeId(game, this.props.selection);
|
|
544
|
+
evtOrigin = 'key-previous';
|
|
545
|
+
} else if (evt.key === 'ArrowRight') {
|
|
546
|
+
nodeId = Movetext.nextNodeId(game, this.props.selection);
|
|
547
|
+
evtOrigin = 'key-next';
|
|
546
548
|
} else {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
if (evt.key === 'Home') {
|
|
554
|
-
nodeId = 'start';
|
|
555
|
-
evtOrigin = 'key-first';
|
|
556
|
-
} else if (evt.key === 'ArrowLeft') {
|
|
557
|
-
nodeId = getPreviousNodeId(currentNode);
|
|
558
|
-
evtOrigin = 'key-previous';
|
|
559
|
-
} else if (evt.key === 'ArrowRight') {
|
|
560
|
-
nodeId = getNextNodeId(currentNode, false);
|
|
561
|
-
evtOrigin = 'key-next';
|
|
562
|
-
} else if (evt.key === 'End') {
|
|
563
|
-
nodeId = getLastNodeId(currentNode, false);
|
|
564
|
-
evtOrigin = 'key-last';
|
|
565
|
-
}
|
|
549
|
+
// evt.key === 'End'
|
|
550
|
+
nodeId = Movetext.lastNodeId(game, this.props.selection);
|
|
551
|
+
evtOrigin = 'key-last';
|
|
566
552
|
}
|
|
567
553
|
|
|
568
554
|
if (nodeId && this.props.onMoveSelected) {
|
|
@@ -572,7 +558,7 @@ var Movetext = /*#__PURE__*/function (_React$Component) {
|
|
|
572
558
|
}, {
|
|
573
559
|
key: "handleNodeClicked",
|
|
574
560
|
value: function handleNodeClicked(nodeId) {
|
|
575
|
-
this.
|
|
561
|
+
this.focus();
|
|
576
562
|
|
|
577
563
|
if (this.props.onMoveSelected) {
|
|
578
564
|
this.props.onMoveSelected(nodeId === this.props.selection ? undefined : nodeId, 'click');
|
|
@@ -612,6 +598,139 @@ var Movetext = /*#__PURE__*/function (_React$Component) {
|
|
|
612
598
|
};
|
|
613
599
|
}
|
|
614
600
|
}
|
|
601
|
+
/**
|
|
602
|
+
* Set the focus to the current component.
|
|
603
|
+
*
|
|
604
|
+
* @public
|
|
605
|
+
*/
|
|
606
|
+
|
|
607
|
+
}, {
|
|
608
|
+
key: "focus",
|
|
609
|
+
value: function focus() {
|
|
610
|
+
this.focusFieldRef.current.focus();
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Return the ID of the main variation in the given chess game.
|
|
614
|
+
* If the given selection corresponds already at the main variation, `undefined` is returned.
|
|
615
|
+
*
|
|
616
|
+
* This corresponds to the operation performed when the user presses key "home" on a `Movetext` component.
|
|
617
|
+
*
|
|
618
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
619
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
620
|
+
* @returns {string?}
|
|
621
|
+
* @public
|
|
622
|
+
*/
|
|
623
|
+
|
|
624
|
+
}], [{
|
|
625
|
+
key: "firstNodeId",
|
|
626
|
+
value: function firstNodeId(game, selection) {
|
|
627
|
+
if (!game.findById(selection)) {
|
|
628
|
+
return undefined;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return selection === 'start' ? undefined : 'start';
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Return the ID of the node immediately preceding the given selection in the given chess game.
|
|
635
|
+
* If no such node exists, `undefined` is returned.
|
|
636
|
+
*
|
|
637
|
+
* This corresponds to the operation performed when the user presses key "arrow left" on a `Movetext` component.
|
|
638
|
+
*
|
|
639
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
640
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
641
|
+
* @returns {string?}
|
|
642
|
+
* @public
|
|
643
|
+
*/
|
|
644
|
+
|
|
645
|
+
}, {
|
|
646
|
+
key: "previousNodeId",
|
|
647
|
+
value: function previousNodeId(game, selection) {
|
|
648
|
+
var currentNode = game.findById(selection);
|
|
649
|
+
|
|
650
|
+
if (!currentNode) {
|
|
651
|
+
return undefined;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (selection === 'start') {
|
|
655
|
+
return undefined;
|
|
656
|
+
} else if (selection.endsWith('start')) {
|
|
657
|
+
currentNode = currentNode.parentNode(); // `.parentNode()` returns always non-null ref here
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
while (currentNode) {
|
|
661
|
+
var previousNode = currentNode.previous();
|
|
662
|
+
|
|
663
|
+
if (previousNode) {
|
|
664
|
+
return previousNode.id();
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
currentNode = currentNode.parentVariation().parentNode();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return 'start';
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Return the ID of the node immediately following the given selection in the given chess game.
|
|
674
|
+
* If no such node exists, `undefined` is returned.
|
|
675
|
+
*
|
|
676
|
+
* This corresponds to the operation performed when the user presses key "arrow right" on a `Movetext` component.
|
|
677
|
+
*
|
|
678
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
679
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
680
|
+
* @returns {string?}
|
|
681
|
+
* @public
|
|
682
|
+
*/
|
|
683
|
+
|
|
684
|
+
}, {
|
|
685
|
+
key: "nextNodeId",
|
|
686
|
+
value: function nextNodeId(game, selection) {
|
|
687
|
+
var currentNode = game.findById(selection);
|
|
688
|
+
|
|
689
|
+
if (!currentNode) {
|
|
690
|
+
return undefined;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
var nextNode = selection.endsWith('start') ? currentNode.first() : currentNode.next();
|
|
694
|
+
return nextNode ? nextNode.id() : undefined;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Return the ID of the node at then end of the variation in which lies the given selection in the given chess game.
|
|
698
|
+
* If the selection is already at the end its variation, `undefined` is returned.
|
|
699
|
+
*
|
|
700
|
+
* This corresponds to the operation performed when the user presses key "end" on a `Movetext` component.
|
|
701
|
+
*
|
|
702
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
703
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
704
|
+
* @returns {string?}
|
|
705
|
+
* @public
|
|
706
|
+
*/
|
|
707
|
+
|
|
708
|
+
}, {
|
|
709
|
+
key: "lastNodeId",
|
|
710
|
+
value: function lastNodeId(game, selection) {
|
|
711
|
+
var currentNode = game.findById(selection);
|
|
712
|
+
|
|
713
|
+
if (!currentNode) {
|
|
714
|
+
return undefined;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
currentNode = selection.endsWith('start') ? currentNode.first() : currentNode.next();
|
|
718
|
+
|
|
719
|
+
if (!currentNode) {
|
|
720
|
+
// Ensure that the input node is not already the last one.
|
|
721
|
+
return undefined;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
while (true) {
|
|
725
|
+
var nextNode = currentNode.next();
|
|
726
|
+
|
|
727
|
+
if (!nextNode) {
|
|
728
|
+
return currentNode.id();
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
currentNode = nextNode;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
615
734
|
}]);
|
|
616
735
|
|
|
617
736
|
return Movetext;
|
|
@@ -779,41 +898,6 @@ function figurineNotation(text, fontName) {
|
|
|
779
898
|
|
|
780
899
|
return result;
|
|
781
900
|
}
|
|
782
|
-
|
|
783
|
-
function getPreviousNodeId(currentNode) {
|
|
784
|
-
var previousNode = currentNode.previous();
|
|
785
|
-
|
|
786
|
-
if (previousNode) {
|
|
787
|
-
return previousNode.id();
|
|
788
|
-
} else {
|
|
789
|
-
var parentNode = currentNode.parentVariation().parentNode();
|
|
790
|
-
return parentNode ? getPreviousNodeId(parentNode) : 'start';
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
function getNextNodeId(currentNode, isVariation) {
|
|
795
|
-
var nextNode = isVariation ? currentNode.first() : currentNode.next();
|
|
796
|
-
return nextNode ? nextNode.id() : false;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
function getLastNodeId(currentNode, isVariation) {
|
|
800
|
-
currentNode = isVariation ? currentNode.first() : currentNode.next();
|
|
801
|
-
|
|
802
|
-
if (!currentNode) {
|
|
803
|
-
// Ensure that the input node is not already the last one.
|
|
804
|
-
return false;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
while (true) {
|
|
808
|
-
var nextNode = currentNode.next();
|
|
809
|
-
|
|
810
|
-
if (!nextNode) {
|
|
811
|
-
return currentNode.id();
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
currentNode = nextNode;
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
901
|
/**
|
|
818
902
|
* Try to interpret the given object as a chess game.
|
|
819
903
|
*/
|
package/doc_src/home.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Kokopu-React is a [React](https://reactjs.org/)-based library to create and display chessboard components.
|
|
1
|
+
Kokopu-React is a [React](https://reactjs.org/)-based library to create and display chessboard and chess-related components.
|
|
2
2
|
Kokopu-React is built on top of [Kokopu](https://www.npmjs.com/package/kokopu), a headless library that
|
|
3
3
|
implements all the chess logic (game rules, parsing of [FEN](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation)
|
|
4
4
|
and [PGN](https://en.wikipedia.org/wiki/Portable_Game_Notation) formats...).
|
|
@@ -39,4 +39,5 @@ testApp([ /* eslint-disable react/jsx-key */
|
|
|
39
39
|
<Movetext game={pgn} gameIndex={7} headerVisible={false} onMoveSelected={onMoveSelected} interactionMode="selectMove" selection="1w" />,
|
|
40
40
|
<Movetext game={pgn} gameIndex={7} headerVisible={false} onMoveSelected={onMoveSelected} interactionMode="selectMove" selection="6b" />,
|
|
41
41
|
<Movetext game={pgn} gameIndex={7} headerVisible={false} onMoveSelected={onMoveSelected} interactionMode="selectMove" selection="invalid-id" />,
|
|
42
|
+
<Movetext game={pgn} gameIndex={8} headerVisible={false} onMoveSelected={onMoveSelected} interactionMode="selectMove" selection="1b-v0-start" />,
|
|
42
43
|
], 'width-600'); /* eslint-enable react/jsx-key */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kokopu-react",
|
|
3
|
-
"version": "1.5.
|
|
4
|
-
"description": "A React-based library to create and display chessboard components.",
|
|
3
|
+
"version": "1.5.5",
|
|
4
|
+
"description": "A React-based library to create and display chessboard and chess-related components.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chess",
|
|
7
7
|
"react",
|
|
@@ -29,41 +29,40 @@
|
|
|
29
29
|
".nycrc.yml"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"htmlparser2": "^
|
|
33
|
-
"kokopu": "^2.
|
|
32
|
+
"htmlparser2": "^8.0.1",
|
|
33
|
+
"kokopu": "^2.8.0",
|
|
34
34
|
"prop-types": "^15.8.1",
|
|
35
35
|
"react": "^17.0.2",
|
|
36
36
|
"react-draggable": "^4.4.5",
|
|
37
37
|
"react-motion": "^0.5.2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@babel/cli": "^7.17.
|
|
41
|
-
"@babel/core": "^7.17.
|
|
42
|
-
"@babel/preset-env": "^7.
|
|
43
|
-
"@babel/preset-react": "^7.
|
|
40
|
+
"@babel/cli": "^7.17.10",
|
|
41
|
+
"@babel/core": "^7.17.12",
|
|
42
|
+
"@babel/preset-env": "^7.17.12",
|
|
43
|
+
"@babel/preset-react": "^7.17.12",
|
|
44
44
|
"@emotion/react": "^11.9.0",
|
|
45
45
|
"@emotion/styled": "^11.8.1",
|
|
46
|
-
"@mui/material": "^5.
|
|
46
|
+
"@mui/material": "^5.8.0",
|
|
47
47
|
"babel-loader": "^8.2.5",
|
|
48
48
|
"babel-plugin-istanbul": "^6.1.1",
|
|
49
|
-
"copy-webpack-plugin": "^
|
|
49
|
+
"copy-webpack-plugin": "^11.0.0",
|
|
50
50
|
"coveralls": "^3.1.1",
|
|
51
51
|
"css-loader": "^6.7.1",
|
|
52
|
-
"eslint": "^8.
|
|
52
|
+
"eslint": "^8.15.0",
|
|
53
53
|
"eslint-plugin-react": "^7.29.4",
|
|
54
54
|
"html-webpack-plugin": "^5.5.0",
|
|
55
55
|
"img-diff-js": "^0.5.2",
|
|
56
|
-
"mocha": "^
|
|
56
|
+
"mocha": "^10.0.0",
|
|
57
57
|
"null-loader": "^4.0.1",
|
|
58
58
|
"nyc": "^15.1.0",
|
|
59
|
-
"prompt": "^1.3.0",
|
|
60
59
|
"react-dom": "^17.0.2",
|
|
61
60
|
"react-styleguidist": "^11.2.0",
|
|
62
61
|
"selenium-webdriver": "^4.1.2",
|
|
63
|
-
"ssh2-sftp-client": "^8.
|
|
62
|
+
"ssh2-sftp-client": "^8.1.0",
|
|
64
63
|
"style-loader": "^3.3.1",
|
|
65
64
|
"unit.js": "^2.1.1",
|
|
66
|
-
"webpack": "^5.72.
|
|
65
|
+
"webpack": "^5.72.1",
|
|
67
66
|
"webpack-cli": "^4.9.2"
|
|
68
67
|
},
|
|
69
68
|
"scripts": {
|
package/src/Chessboard.js
CHANGED
|
@@ -623,6 +623,38 @@ export default class Chessboard extends React.Component {
|
|
|
623
623
|
return 'kokopu-arrowTip-' + color + '-' + this.arrowTipIdSuffix;
|
|
624
624
|
}
|
|
625
625
|
|
|
626
|
+
/**
|
|
627
|
+
* Return the size of the chessboard, assuming it is built with the given attributes.
|
|
628
|
+
*
|
|
629
|
+
* @param {number} squareSize
|
|
630
|
+
* @param {boolean} coordinateVisible
|
|
631
|
+
* @param {{width:number, squareSize:number, coordinateVisible:boolean}[]} smallScreenLimits
|
|
632
|
+
* @returns {{width:number, height:number}}
|
|
633
|
+
* @public
|
|
634
|
+
*/
|
|
635
|
+
static size(squareSize, coordinateVisible, smallScreenLimits) {
|
|
636
|
+
|
|
637
|
+
// Enforce small-screen limits, if any.
|
|
638
|
+
if (typeof window !== 'undefined') {
|
|
639
|
+
let squareSizeLimit = computeSmallScreenLimits('squareSize', smallScreenLimits, window.innerWidth);
|
|
640
|
+
let coordinateVisibleLimit = computeSmallScreenLimits('coordinateVisible', smallScreenLimits, window.innerWidth);
|
|
641
|
+
if (!isNaN(squareSizeLimit)) {
|
|
642
|
+
squareSize = sanitizeInteger(squareSizeLimit, MIN_SQUARE_SIZE, squareSize);
|
|
643
|
+
}
|
|
644
|
+
coordinateVisible = coordinateVisible && (coordinateVisibleLimit === undefined || coordinateVisibleLimit);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Compute the dimensions.
|
|
648
|
+
let width = 9 * squareSize + Math.round(TURN_FLAG_SPACING_FACTOR * squareSize);
|
|
649
|
+
let height = 8 * squareSize;
|
|
650
|
+
if (coordinateVisible) {
|
|
651
|
+
let fontSize = computeCoordinateFontSize(squareSize);
|
|
652
|
+
width += Math.round(RANK_COORDINATE_WIDTH_FACTOR * fontSize);
|
|
653
|
+
height += Math.round(FILE_COORDINATE_HEIGHT_FACTOR * fontSize);
|
|
654
|
+
}
|
|
655
|
+
return { width: width, height: height };
|
|
656
|
+
}
|
|
657
|
+
|
|
626
658
|
/**
|
|
627
659
|
* Return the maximum square size that would allow the chessboard to fit in a rectangle of size `width x height`.
|
|
628
660
|
*
|
package/src/Movetext.js
CHANGED
|
@@ -52,7 +52,7 @@ export default class Movetext extends React.Component {
|
|
|
52
52
|
<div className="kokopu-movetext">
|
|
53
53
|
{this.renderHeaders(info.game)}
|
|
54
54
|
{this.renderBody(info.game)}
|
|
55
|
-
{this.renderFocusField()}
|
|
55
|
+
{this.renderFocusField(info.game)}
|
|
56
56
|
</div>
|
|
57
57
|
);
|
|
58
58
|
}
|
|
@@ -132,13 +132,13 @@ export default class Movetext extends React.Component {
|
|
|
132
132
|
return <div className="kokopu-header-annotator" key="annotator">{sanitizeHtml(annotator)}</div>;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
renderFocusField() {
|
|
135
|
+
renderFocusField(game) {
|
|
136
136
|
if (this.props.interactionMode !== 'selectMove') {
|
|
137
137
|
return undefined;
|
|
138
138
|
}
|
|
139
139
|
return (
|
|
140
140
|
<div className="kokopu-focusFieldContainer">
|
|
141
|
-
<a className="kokopu-focusField" href="#" ref={this.focusFieldRef} onKeyDown={evt => this.handleKeyDownInFocusField(evt)}></a>
|
|
141
|
+
<a className="kokopu-focusField" href="#" ref={this.focusFieldRef} onKeyDown={evt => this.handleKeyDownInFocusField(evt, game)}></a>
|
|
142
142
|
</div>
|
|
143
143
|
);
|
|
144
144
|
}
|
|
@@ -330,47 +330,31 @@ export default class Movetext extends React.Component {
|
|
|
330
330
|
return comment ? comment : undefined;
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
handleKeyDownInFocusField(evt) {
|
|
334
|
-
if (evt.key
|
|
335
|
-
|
|
333
|
+
handleKeyDownInFocusField(evt, game) {
|
|
334
|
+
if (evt.key !== 'Home' && evt.key !== 'ArrowLeft' && evt.key !== 'ArrowRight' && evt.key !== 'End') {
|
|
335
|
+
return;
|
|
336
336
|
}
|
|
337
|
+
evt.preventDefault();
|
|
337
338
|
if (!this.props.selection) {
|
|
338
339
|
return;
|
|
339
340
|
}
|
|
340
|
-
let
|
|
341
|
-
let nodeId = false;
|
|
341
|
+
let nodeId = undefined;
|
|
342
342
|
let evtOrigin = '';
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
evtOrigin = 'key-next';
|
|
347
|
-
}
|
|
348
|
-
else if (evt.key === 'End') {
|
|
349
|
-
nodeId = getLastNodeId(game.mainVariation(), true);
|
|
350
|
-
evtOrigin = 'key-last';
|
|
351
|
-
}
|
|
343
|
+
if (evt.key === 'Home') {
|
|
344
|
+
nodeId = Movetext.firstNodeId(game, this.props.selection);
|
|
345
|
+
evtOrigin = 'key-first';
|
|
352
346
|
}
|
|
353
|
-
else {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
evtOrigin = 'key-previous';
|
|
365
|
-
}
|
|
366
|
-
else if (evt.key === 'ArrowRight') {
|
|
367
|
-
nodeId = getNextNodeId(currentNode, false);
|
|
368
|
-
evtOrigin = 'key-next';
|
|
369
|
-
}
|
|
370
|
-
else if (evt.key === 'End') {
|
|
371
|
-
nodeId = getLastNodeId(currentNode, false);
|
|
372
|
-
evtOrigin = 'key-last';
|
|
373
|
-
}
|
|
347
|
+
else if (evt.key === 'ArrowLeft') {
|
|
348
|
+
nodeId = Movetext.previousNodeId(game, this.props.selection);
|
|
349
|
+
evtOrigin = 'key-previous';
|
|
350
|
+
}
|
|
351
|
+
else if (evt.key === 'ArrowRight') {
|
|
352
|
+
nodeId = Movetext.nextNodeId(game, this.props.selection);
|
|
353
|
+
evtOrigin = 'key-next';
|
|
354
|
+
}
|
|
355
|
+
else { // evt.key === 'End'
|
|
356
|
+
nodeId = Movetext.lastNodeId(game, this.props.selection);
|
|
357
|
+
evtOrigin = 'key-last';
|
|
374
358
|
}
|
|
375
359
|
if (nodeId && this.props.onMoveSelected) {
|
|
376
360
|
this.props.onMoveSelected(nodeId, evtOrigin);
|
|
@@ -378,7 +362,7 @@ export default class Movetext extends React.Component {
|
|
|
378
362
|
}
|
|
379
363
|
|
|
380
364
|
handleNodeClicked(nodeId) {
|
|
381
|
-
this.
|
|
365
|
+
this.focus();
|
|
382
366
|
if (this.props.onMoveSelected) {
|
|
383
367
|
this.props.onMoveSelected(nodeId === this.props.selection ? undefined : nodeId, 'click');
|
|
384
368
|
}
|
|
@@ -403,6 +387,114 @@ export default class Movetext extends React.Component {
|
|
|
403
387
|
return notation => notation;
|
|
404
388
|
}
|
|
405
389
|
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Set the focus to the current component.
|
|
393
|
+
*
|
|
394
|
+
* @public
|
|
395
|
+
*/
|
|
396
|
+
focus() {
|
|
397
|
+
this.focusFieldRef.current.focus();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Return the ID of the main variation in the given chess game.
|
|
402
|
+
* If the given selection corresponds already at the main variation, `undefined` is returned.
|
|
403
|
+
*
|
|
404
|
+
* This corresponds to the operation performed when the user presses key "home" on a `Movetext` component.
|
|
405
|
+
*
|
|
406
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
407
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
408
|
+
* @returns {string?}
|
|
409
|
+
* @public
|
|
410
|
+
*/
|
|
411
|
+
static firstNodeId(game, selection) {
|
|
412
|
+
if (!game.findById(selection)) {
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
return selection === 'start' ? undefined : 'start';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Return the ID of the node immediately preceding the given selection in the given chess game.
|
|
420
|
+
* If no such node exists, `undefined` is returned.
|
|
421
|
+
*
|
|
422
|
+
* This corresponds to the operation performed when the user presses key "arrow left" on a `Movetext` component.
|
|
423
|
+
*
|
|
424
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
425
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
426
|
+
* @returns {string?}
|
|
427
|
+
* @public
|
|
428
|
+
*/
|
|
429
|
+
static previousNodeId(game, selection) {
|
|
430
|
+
let currentNode = game.findById(selection);
|
|
431
|
+
if (!currentNode) {
|
|
432
|
+
return undefined;
|
|
433
|
+
}
|
|
434
|
+
if (selection === 'start') {
|
|
435
|
+
return undefined;
|
|
436
|
+
}
|
|
437
|
+
else if (selection.endsWith('start')) {
|
|
438
|
+
currentNode = currentNode.parentNode(); // `.parentNode()` returns always non-null ref here
|
|
439
|
+
}
|
|
440
|
+
while (currentNode) {
|
|
441
|
+
let previousNode = currentNode.previous();
|
|
442
|
+
if (previousNode) {
|
|
443
|
+
return previousNode.id();
|
|
444
|
+
}
|
|
445
|
+
currentNode = currentNode.parentVariation().parentNode();
|
|
446
|
+
}
|
|
447
|
+
return 'start';
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Return the ID of the node immediately following the given selection in the given chess game.
|
|
452
|
+
* If no such node exists, `undefined` is returned.
|
|
453
|
+
*
|
|
454
|
+
* This corresponds to the operation performed when the user presses key "arrow right" on a `Movetext` component.
|
|
455
|
+
*
|
|
456
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
457
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
458
|
+
* @returns {string?}
|
|
459
|
+
* @public
|
|
460
|
+
*/
|
|
461
|
+
static nextNodeId(game, selection) {
|
|
462
|
+
let currentNode = game.findById(selection);
|
|
463
|
+
if (!currentNode) {
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
let nextNode = selection.endsWith('start') ? currentNode.first() : currentNode.next();
|
|
467
|
+
return nextNode ? nextNode.id() : undefined;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Return the ID of the node at then end of the variation in which lies the given selection in the given chess game.
|
|
472
|
+
* If the selection is already at the end its variation, `undefined` is returned.
|
|
473
|
+
*
|
|
474
|
+
* This corresponds to the operation performed when the user presses key "end" on a `Movetext` component.
|
|
475
|
+
*
|
|
476
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
477
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
478
|
+
* @returns {string?}
|
|
479
|
+
* @public
|
|
480
|
+
*/
|
|
481
|
+
static lastNodeId(game, selection) {
|
|
482
|
+
let currentNode = game.findById(selection);
|
|
483
|
+
if (!currentNode) {
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
currentNode = selection.endsWith('start') ? currentNode.first() : currentNode.next();
|
|
487
|
+
if (!currentNode) { // Ensure that the input node is not already the last one.
|
|
488
|
+
return undefined;
|
|
489
|
+
}
|
|
490
|
+
while (true) {
|
|
491
|
+
let nextNode = currentNode.next();
|
|
492
|
+
if (!nextNode) {
|
|
493
|
+
return currentNode.id();
|
|
494
|
+
}
|
|
495
|
+
currentNode = nextNode;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
406
498
|
}
|
|
407
499
|
|
|
408
500
|
|
|
@@ -569,39 +661,6 @@ function figurineNotation(text, fontName) {
|
|
|
569
661
|
}
|
|
570
662
|
|
|
571
663
|
|
|
572
|
-
function getPreviousNodeId(currentNode) {
|
|
573
|
-
let previousNode = currentNode.previous();
|
|
574
|
-
if (previousNode) {
|
|
575
|
-
return previousNode.id();
|
|
576
|
-
}
|
|
577
|
-
else {
|
|
578
|
-
let parentNode = currentNode.parentVariation().parentNode();
|
|
579
|
-
return parentNode ? getPreviousNodeId(parentNode) : 'start';
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
function getNextNodeId(currentNode, isVariation) {
|
|
585
|
-
let nextNode = isVariation ? currentNode.first() : currentNode.next();
|
|
586
|
-
return nextNode ? nextNode.id() : false;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
function getLastNodeId(currentNode, isVariation) {
|
|
591
|
-
currentNode = isVariation ? currentNode.first() : currentNode.next();
|
|
592
|
-
if (!currentNode) { // Ensure that the input node is not already the last one.
|
|
593
|
-
return false;
|
|
594
|
-
}
|
|
595
|
-
while (true) {
|
|
596
|
-
let nextNode = currentNode.next();
|
|
597
|
-
if (!nextNode) {
|
|
598
|
-
return currentNode.id();
|
|
599
|
-
}
|
|
600
|
-
currentNode = nextNode;
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
|
|
605
664
|
/**
|
|
606
665
|
* Try to interpret the given object as a chess game.
|
|
607
666
|
*/
|
|
@@ -24,19 +24,33 @@ let { Chessboard } = require('../build/test_headless/index');
|
|
|
24
24
|
let test = require('unit.js');
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
function testAdaptSquareSize(expectedSquareSize, width, height, coordinateVisible, smallScreenLimits) {
|
|
28
|
+
test.value(Chessboard.adaptSquareSize(width, height, coordinateVisible, smallScreenLimits)).is(expectedSquareSize);
|
|
29
|
+
|
|
30
|
+
let actualSize = Chessboard.size(expectedSquareSize, coordinateVisible, smallScreenLimits);
|
|
31
|
+
test.value(actualSize.width <= width && actualSize.height <= height).isTrue();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function testAdaptSquareSizeWithIncrement(expectedSquareSize, width, height, coordinateVisible, smallScreenLimits) {
|
|
35
|
+
testAdaptSquareSize(expectedSquareSize, width, height, coordinateVisible, smallScreenLimits);
|
|
36
|
+
|
|
37
|
+
let actualSizeIncremented = Chessboard.size(expectedSquareSize + 1, coordinateVisible, smallScreenLimits);
|
|
38
|
+
test.value(actualSizeIncremented.width > width || actualSizeIncremented.height > height).isTrue();
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
describe('Adapt square-size', () => {
|
|
28
42
|
|
|
29
43
|
it('Very small', () => { test.value(Chessboard.adaptSquareSize(10, 10, false)).is(Chessboard.minSquareSize()); });
|
|
30
44
|
it('Very large', () => { test.value(Chessboard.adaptSquareSize(9999, 9999, true)).is(Chessboard.maxSquareSize()); });
|
|
31
45
|
|
|
32
|
-
it('Size 185x300 with coordinates', () =>
|
|
33
|
-
it('Size 185x300 without coordinates', () =>
|
|
34
|
-
it('Size 300x200 with coordinates', () =>
|
|
35
|
-
it('Size 300x200 without coordinates', () =>
|
|
36
|
-
it('Size 375x500 with coordinates', () =>
|
|
37
|
-
it('Size 375x500 without coordinates', () =>
|
|
38
|
-
it('Size 600x450 with coordinates', () =>
|
|
39
|
-
it('Size 600x450 without coordinates', () =>
|
|
46
|
+
it('Size 185x300 with coordinates', () => testAdaptSquareSizeWithIncrement(19, 185, 300, true));
|
|
47
|
+
it('Size 185x300 without coordinates', () => testAdaptSquareSizeWithIncrement(20, 185, 300, false));
|
|
48
|
+
it('Size 300x200 with coordinates', () => testAdaptSquareSizeWithIncrement(23, 300, 200, true));
|
|
49
|
+
it('Size 300x200 without coordinates', () => testAdaptSquareSizeWithIncrement(25, 300, 200, false));
|
|
50
|
+
it('Size 375x500 with coordinates', () => testAdaptSquareSizeWithIncrement(40, 375, 500, true));
|
|
51
|
+
it('Size 375x500 without coordinates', () => testAdaptSquareSizeWithIncrement(41, 375, 500, false));
|
|
52
|
+
it('Size 600x450 with coordinates', () => testAdaptSquareSizeWithIncrement(54, 600, 450, true));
|
|
53
|
+
it('Size 600x450 without coordinates', () => testAdaptSquareSizeWithIncrement(56, 600, 450, false));
|
|
40
54
|
});
|
|
41
55
|
|
|
42
56
|
describe('Adapt square-size with small-screen limits', () => {
|
|
@@ -58,18 +72,18 @@ describe('Adapt square-size with small-screen limits', () => {
|
|
|
58
72
|
|
|
59
73
|
it('Window-limited', () => {
|
|
60
74
|
window.innerWidth = 640;
|
|
61
|
-
|
|
75
|
+
testAdaptSquareSize(32, 9999, 9999, true, limits);
|
|
62
76
|
});
|
|
63
77
|
it('Available-space-limited 1', () => {
|
|
64
78
|
window.innerWidth = 800;
|
|
65
|
-
|
|
79
|
+
testAdaptSquareSize(40, 375, 500, true, limits);
|
|
66
80
|
});
|
|
67
81
|
it('Available-space-limited 2', () => {
|
|
68
82
|
window.innerWidth = 800;
|
|
69
|
-
|
|
83
|
+
testAdaptSquareSize(41, 375, 500, false, limits);
|
|
70
84
|
});
|
|
71
85
|
it('Force hidden coordinates', () => {
|
|
72
86
|
window.innerWidth = 600;
|
|
73
|
-
|
|
87
|
+
testAdaptSquareSize(20, 185, 300, true, limits);
|
|
74
88
|
});
|
|
75
89
|
});
|
|
@@ -90,6 +90,10 @@ describe('Movetext interaction', function() {
|
|
|
90
90
|
await setSandbox(browserContext, '');
|
|
91
91
|
await focusFieldElement.sendKeys(Key.END);
|
|
92
92
|
await compareSandbox(browserContext, expectedOnGoLast);
|
|
93
|
+
|
|
94
|
+
await setSandbox(browserContext, '');
|
|
95
|
+
await focusFieldElement.sendKeys(Key.ARROW_UP);
|
|
96
|
+
await compareSandbox(browserContext, '');
|
|
93
97
|
});
|
|
94
98
|
}
|
|
95
99
|
|
|
@@ -100,4 +104,5 @@ describe('Movetext interaction', function() {
|
|
|
100
104
|
itCheckKeyboardActions(5, 'key on first selected', tpl('start', 'key-first'), tpl('start', 'key-previous'), tpl('1b', 'key-next'), tpl('6b', 'key-last'));
|
|
101
105
|
itCheckKeyboardActions(6, 'key on last selected', tpl('start', 'key-first'), tpl('6w', 'key-previous'), '', '');
|
|
102
106
|
itCheckKeyboardActions(7, 'key on invalid selection', '', '', '', '');
|
|
107
|
+
itCheckKeyboardActions(8, 'key on sub-variation selected', tpl('start', 'key-first'), tpl('1w', 'key-previous'), tpl('1b-v0-1b', 'key-next'), tpl('1b-v0-3w', 'key-last'));
|
|
103
108
|
});
|