kokopu-react 1.5.0 → 1.5.3
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 +14 -0
- package/dist/lib/Movetext.js +168 -65
- package/doc_src/demo/PageMovetextInteraction.js +10 -6
- package/graphic_test_app/16_movetext_interaction.js +3 -2
- package/package.json +5 -5
- package/scripts/test_graphic/docker-compose.yml +1 -1
- package/src/Chessboard.js +2 -2
- package/src/Movetext.js +142 -68
- package/test/8_movetext_interaction.js +17 -8
- package/test/references/01_marker_icons/0.png +0 -0
- package/test/references/01_marker_icons/1.png +0 -0
- package/test/references/01_marker_icons/10.png +0 -0
- package/test/references/01_marker_icons/2.png +0 -0
- package/test/references/01_marker_icons/3.png +0 -0
- package/test/references/01_marker_icons/4.png +0 -0
- package/test/references/01_marker_icons/5.png +0 -0
- package/test/references/01_marker_icons/6.png +0 -0
- package/test/references/01_marker_icons/7.png +0 -0
- package/test/references/01_marker_icons/8.png +0 -0
- package/test/references/01_marker_icons/9.png +0 -0
- package/test/references/02_chessboard_simple/0.png +0 -0
- package/test/references/02_chessboard_simple/1.png +0 -0
- package/test/references/02_chessboard_simple/2.png +0 -0
- package/test/references/02_chessboard_simple/3.png +0 -0
- package/test/references/02_chessboard_simple/4.png +0 -0
- package/test/references/02_chessboard_simple/5.png +0 -0
- package/test/references/03_chessboard_flipped/0.png +0 -0
- package/test/references/03_chessboard_flipped/1.png +0 -0
- package/test/references/03_chessboard_flipped/2.png +0 -0
- package/test/references/03_chessboard_flipped/3.png +0 -0
- package/test/references/03_chessboard_flipped/4.png +0 -0
- package/test/references/04_chessboard_annotations/0.png +0 -0
- package/test/references/04_chessboard_annotations/1.png +0 -0
- package/test/references/04_chessboard_annotations/2.png +0 -0
- package/test/references/04_chessboard_annotations/3.png +0 -0
- package/test/references/04_chessboard_annotations/4.png +0 -0
- package/test/references/04_chessboard_annotations/5.png +0 -0
- package/test/references/05_chessboard_move/0.png +0 -0
- package/test/references/05_chessboard_move/1.png +0 -0
- package/test/references/05_chessboard_move/2.png +0 -0
- package/test/references/05_chessboard_move/3.png +0 -0
- package/test/references/05_chessboard_move/4.png +0 -0
- package/test/references/05_chessboard_move/5.png +0 -0
- package/test/references/05_chessboard_move/6.png +0 -0
- package/test/references/05_chessboard_move/7.png +0 -0
- package/test/references/05_chessboard_move/8.png +0 -0
- package/test/references/06_chessboard_theme/0.png +0 -0
- package/test/references/06_chessboard_theme/1.png +0 -0
- package/test/references/06_chessboard_theme/2.png +0 -0
- package/test/references/06_chessboard_theme/3.png +0 -0
- package/test/references/06_chessboard_theme/4.png +0 -0
- package/test/references/06_chessboard_theme/5.png +0 -0
- package/test/references/06_chessboard_theme/6.png +0 -0
- package/test/references/06_chessboard_theme/7.png +0 -0
- package/test/references/06_chessboard_theme/8.png +0 -0
- package/test/references/08_chessboard_move_pieces/after_move.png +0 -0
- package/test/references/08_chessboard_move_pieces/empty_square.png +0 -0
- package/test/references/08_chessboard_move_pieces/null_vector.png +0 -0
- package/test/references/08_chessboard_move_pieces/out_of_board.png +0 -0
- package/test/references/08_chessboard_move_pieces/over_arrow_marker.png +0 -0
- package/test/references/08_chessboard_move_pieces/over_empty.png +0 -0
- package/test/references/08_chessboard_move_pieces/over_non_empty_1.png +0 -0
- package/test/references/08_chessboard_move_pieces/over_non_empty_2.png +0 -0
- package/test/references/08_chessboard_move_pieces/over_square_marker.png +0 -0
- package/test/references/08_chessboard_move_pieces/over_text_marker.png +0 -0
- package/test/references/09_chessboard_edit_arrows/base_1.png +0 -0
- package/test/references/09_chessboard_edit_arrows/base_2.png +0 -0
- package/test/references/09_chessboard_edit_arrows/edit_color_not_set.png +0 -0
- package/test/references/09_chessboard_edit_arrows/null_vector.png +0 -0
- package/test/references/09_chessboard_edit_arrows/out_of_board.png +0 -0
- package/test/references/09_chessboard_edit_arrows/over_arrow_marker.png +0 -0
- package/test/references/09_chessboard_edit_arrows/over_square_marker.png +0 -0
- package/test/references/10_chessboard_play_moves/castling_move.png +0 -0
- package/test/references/10_chessboard_play_moves/chess960_ambiguous_king_move.png +0 -0
- package/test/references/10_chessboard_play_moves/chess960_castling_move_1.png +0 -0
- package/test/references/10_chessboard_play_moves/chess960_castling_move_2.png +0 -0
- package/test/references/10_chessboard_play_moves/chess960_non_kxr_castling.png +0 -0
- package/test/references/10_chessboard_play_moves/illegal_move.png +0 -0
- package/test/references/10_chessboard_play_moves/illegal_position.png +0 -0
- package/test/references/10_chessboard_play_moves/null_vector.png +0 -0
- package/test/references/10_chessboard_play_moves/out_of_board.png +0 -0
- package/test/references/10_chessboard_play_moves/regular_move_1.png +0 -0
- package/test/references/10_chessboard_play_moves/regular_move_2.png +0 -0
- package/test/references/10_chessboard_play_moves/wrong_color.png +0 -0
- package/test/references/11_chessboard_play_promotions/antichess_promotion.png +0 -0
- package/test/references/11_chessboard_play_promotions/cancel_promotion.png +0 -0
- package/test/references/11_chessboard_play_promotions/regular_promotion_1.png +0 -0
- package/test/references/11_chessboard_play_promotions/regular_promotion_2.png +0 -0
- package/test/references/12_movetext_simple/2.png +0 -0
- package/test/references/12_movetext_simple/3.png +0 -0
- package/test/references/15_movetext_options/2.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
ChangeLog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
1.5.3 (April 30, 2022)
|
|
5
|
+
----------------------
|
|
6
|
+
* Add method `Movetext#focus()`.
|
|
7
|
+
* Expose static methods `Movetext#firstNodeId(..)`, `Movetext#previousNodeId(..)`, `Movetext#nextNodeId(..)`
|
|
8
|
+
and `Movetext#lastNodeId(..)`.
|
|
9
|
+
|
|
10
|
+
1.5.2 (April 29, 2022)
|
|
11
|
+
----------------------
|
|
12
|
+
* Prevent default actions when handling key events in component `Movetext`.
|
|
13
|
+
|
|
14
|
+
1.5.1 (April 29, 2022)
|
|
15
|
+
----------------------
|
|
16
|
+
* Add argument `evtOrigin` in `onMoveSelected(..)` callback in component `Movetext`.
|
|
17
|
+
|
|
4
18
|
1.5.0 (April 27, 2022)
|
|
5
19
|
----------------------
|
|
6
20
|
* Add component `Movetext` to display chess games.
|
package/dist/lib/Movetext.js
CHANGED
|
@@ -94,10 +94,9 @@ var Movetext = /*#__PURE__*/function (_React$Component) {
|
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
var game = info.game;
|
|
98
97
|
return /*#__PURE__*/_react["default"].createElement("div", {
|
|
99
98
|
className: "kokopu-movetext"
|
|
100
|
-
}, this.renderHeaders(game), this.renderBody(game), this.renderFocusField());
|
|
99
|
+
}, this.renderHeaders(info.game), this.renderBody(info.game), this.renderFocusField());
|
|
101
100
|
}
|
|
102
101
|
}, {
|
|
103
102
|
key: "renderHeaders",
|
|
@@ -524,48 +523,48 @@ var Movetext = /*#__PURE__*/function (_React$Component) {
|
|
|
524
523
|
}, {
|
|
525
524
|
key: "handleKeyDownInFocusField",
|
|
526
525
|
value: function handleKeyDownInFocusField(evt) {
|
|
527
|
-
if (
|
|
526
|
+
if (evt.key !== 'Home' && evt.key !== 'ArrowLeft' && evt.key !== 'ArrowRight' && evt.key !== 'End') {
|
|
528
527
|
return;
|
|
529
528
|
}
|
|
530
529
|
|
|
531
|
-
|
|
532
|
-
var nodeId = false;
|
|
533
|
-
|
|
534
|
-
if (this.props.selection === 'start') {
|
|
535
|
-
if (evt.key === 'ArrowRight') {
|
|
536
|
-
nodeId = getNextNodeId(game.mainVariation(), true);
|
|
537
|
-
} else if (evt.key === 'End') {
|
|
538
|
-
nodeId = getLastNodeId(game.mainVariation(), true);
|
|
539
|
-
}
|
|
540
|
-
} else {
|
|
541
|
-
var currentNode = game.findById(this.props.selection);
|
|
530
|
+
evt.preventDefault();
|
|
542
531
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
532
|
+
if (!this.props.selection) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
546
535
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
536
|
+
var _parseGame = parseGame(this.props.game, this.props.gameIndex),
|
|
537
|
+
game = _parseGame.game;
|
|
538
|
+
|
|
539
|
+
var nodeId = undefined;
|
|
540
|
+
var evtOrigin = '';
|
|
541
|
+
|
|
542
|
+
if (evt.key === 'Home') {
|
|
543
|
+
nodeId = Movetext.firstNodeId(game, this.props.selection);
|
|
544
|
+
evtOrigin = 'key-first';
|
|
545
|
+
} else if (evt.key === 'ArrowLeft') {
|
|
546
|
+
nodeId = Movetext.previousNodeId(game, this.props.selection);
|
|
547
|
+
evtOrigin = 'key-previous';
|
|
548
|
+
} else if (evt.key === 'ArrowRight') {
|
|
549
|
+
nodeId = Movetext.nextNodeId(game, this.props.selection);
|
|
550
|
+
evtOrigin = 'key-next';
|
|
551
|
+
} else {
|
|
552
|
+
// evt.key === 'End'
|
|
553
|
+
nodeId = Movetext.lastNodeId(game, this.props.selection);
|
|
554
|
+
evtOrigin = 'key-last';
|
|
556
555
|
}
|
|
557
556
|
|
|
558
557
|
if (nodeId && this.props.onMoveSelected) {
|
|
559
|
-
this.props.onMoveSelected(nodeId);
|
|
558
|
+
this.props.onMoveSelected(nodeId, evtOrigin);
|
|
560
559
|
}
|
|
561
560
|
}
|
|
562
561
|
}, {
|
|
563
562
|
key: "handleNodeClicked",
|
|
564
563
|
value: function handleNodeClicked(nodeId) {
|
|
565
|
-
this.
|
|
564
|
+
this.focus();
|
|
566
565
|
|
|
567
566
|
if (this.props.onMoveSelected) {
|
|
568
|
-
this.props.onMoveSelected(nodeId === this.props.selection ? undefined : nodeId);
|
|
567
|
+
this.props.onMoveSelected(nodeId === this.props.selection ? undefined : nodeId, 'click');
|
|
569
568
|
}
|
|
570
569
|
}
|
|
571
570
|
/**
|
|
@@ -602,6 +601,139 @@ var Movetext = /*#__PURE__*/function (_React$Component) {
|
|
|
602
601
|
};
|
|
603
602
|
}
|
|
604
603
|
}
|
|
604
|
+
/**
|
|
605
|
+
* Set the focus to the current component.
|
|
606
|
+
*
|
|
607
|
+
* @public
|
|
608
|
+
*/
|
|
609
|
+
|
|
610
|
+
}, {
|
|
611
|
+
key: "focus",
|
|
612
|
+
value: function focus() {
|
|
613
|
+
this.focusFieldRef.current.focus();
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Return the ID of the main variation in the given chess game.
|
|
617
|
+
* If the given selection corresponds already at the main variation, `undefined` is returned.
|
|
618
|
+
*
|
|
619
|
+
* This corresponds to the operation performed when the user presses key "home" on a `Movetext` component.
|
|
620
|
+
*
|
|
621
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
622
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
623
|
+
* @returns {string?}
|
|
624
|
+
* @public
|
|
625
|
+
*/
|
|
626
|
+
|
|
627
|
+
}], [{
|
|
628
|
+
key: "firstNodeId",
|
|
629
|
+
value: function firstNodeId(game, selection) {
|
|
630
|
+
if (!game.findById(selection)) {
|
|
631
|
+
return undefined;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return selection === 'start' ? undefined : 'start';
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Return the ID of the node immediately preceding the given selection in the given chess game.
|
|
638
|
+
* If no such node exists, `undefined` is returned.
|
|
639
|
+
*
|
|
640
|
+
* This corresponds to the operation performed when the user presses key "arrow left" on a `Movetext` component.
|
|
641
|
+
*
|
|
642
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
643
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
644
|
+
* @returns {string?}
|
|
645
|
+
* @public
|
|
646
|
+
*/
|
|
647
|
+
|
|
648
|
+
}, {
|
|
649
|
+
key: "previousNodeId",
|
|
650
|
+
value: function previousNodeId(game, selection) {
|
|
651
|
+
var currentNode = game.findById(selection);
|
|
652
|
+
|
|
653
|
+
if (!currentNode) {
|
|
654
|
+
return undefined;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (selection === 'start') {
|
|
658
|
+
return undefined;
|
|
659
|
+
} else if (selection.endsWith('start')) {
|
|
660
|
+
currentNode = currentNode.parentNode(); // `.parentNode()` returns always non-null ref here
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
while (currentNode) {
|
|
664
|
+
var previousNode = currentNode.previous();
|
|
665
|
+
|
|
666
|
+
if (previousNode) {
|
|
667
|
+
return previousNode.id();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
currentNode = currentNode.parentVariation().parentNode();
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return 'start';
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Return the ID of the node immediately following the given selection in the given chess game.
|
|
677
|
+
* If no such node exists, `undefined` is returned.
|
|
678
|
+
*
|
|
679
|
+
* This corresponds to the operation performed when the user presses key "arrow right" on a `Movetext` component.
|
|
680
|
+
*
|
|
681
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
682
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
683
|
+
* @returns {string?}
|
|
684
|
+
* @public
|
|
685
|
+
*/
|
|
686
|
+
|
|
687
|
+
}, {
|
|
688
|
+
key: "nextNodeId",
|
|
689
|
+
value: function nextNodeId(game, selection) {
|
|
690
|
+
var currentNode = game.findById(selection);
|
|
691
|
+
|
|
692
|
+
if (!currentNode) {
|
|
693
|
+
return undefined;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
var nextNode = selection.endsWith('start') ? currentNode.first() : currentNode.next();
|
|
697
|
+
return nextNode ? nextNode.id() : undefined;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Return the ID of the node at then end of the variation in which lies the given selection in the given chess game.
|
|
701
|
+
* If the selection is already at the end its variation, `undefined` is returned.
|
|
702
|
+
*
|
|
703
|
+
* This corresponds to the operation performed when the user presses key "end" on a `Movetext` component.
|
|
704
|
+
*
|
|
705
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
706
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
707
|
+
* @returns {string?}
|
|
708
|
+
* @public
|
|
709
|
+
*/
|
|
710
|
+
|
|
711
|
+
}, {
|
|
712
|
+
key: "lastNodeId",
|
|
713
|
+
value: function lastNodeId(game, selection) {
|
|
714
|
+
var currentNode = game.findById(selection);
|
|
715
|
+
|
|
716
|
+
if (!currentNode) {
|
|
717
|
+
return undefined;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
currentNode = selection.endsWith('start') ? currentNode.first() : currentNode.next();
|
|
721
|
+
|
|
722
|
+
if (!currentNode) {
|
|
723
|
+
// Ensure that the input node is not already the last one.
|
|
724
|
+
return undefined;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
while (true) {
|
|
728
|
+
var nextNode = currentNode.next();
|
|
729
|
+
|
|
730
|
+
if (!nextNode) {
|
|
731
|
+
return currentNode.id();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
currentNode = nextNode;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
605
737
|
}]);
|
|
606
738
|
|
|
607
739
|
return Movetext;
|
|
@@ -675,10 +807,16 @@ Movetext.propTypes = {
|
|
|
675
807
|
interactionMode: _propTypes["default"].oneOf(['selectMove']),
|
|
676
808
|
|
|
677
809
|
/**
|
|
678
|
-
* Callback invoked when the user
|
|
810
|
+
* Callback invoked when the user selects a move (only if `interactionMode` is set to `'selectMove'`).
|
|
679
811
|
*
|
|
680
812
|
* @param {string?} nodeId ID of the selected move (as returned by [kokopu.Node#id](https://kokopu.yo35.org/docs/Node.html#id)),
|
|
681
813
|
* `'start'` for the beginning of the main variation, or `undefined` if the user unselects the previously selected move.
|
|
814
|
+
* @param {string} evtOrigin Origin of the event. Can be:
|
|
815
|
+
* - `'key-first'`: the event has been triggered by the "go-to-first-move" key (aka. the home key),
|
|
816
|
+
* - `'key-previous'`: the event has been triggered by the "go-to-previous-move" key (aka. the arrow left key),
|
|
817
|
+
* - `'key-next'`: the event has been triggered by the "go-to-next-move" key (aka. the arrow right key),
|
|
818
|
+
* - `'key-last'`: the event has been triggered by the "go-to-last-move" key (aka. the end key),
|
|
819
|
+
* - `'click'`: the event has been triggered by a mouse click on a move.
|
|
682
820
|
*/
|
|
683
821
|
onMoveSelected: _propTypes["default"].func
|
|
684
822
|
};
|
|
@@ -763,41 +901,6 @@ function figurineNotation(text, fontName) {
|
|
|
763
901
|
|
|
764
902
|
return result;
|
|
765
903
|
}
|
|
766
|
-
|
|
767
|
-
function getPreviousNodeId(currentNode) {
|
|
768
|
-
var previousNode = currentNode.previous();
|
|
769
|
-
|
|
770
|
-
if (previousNode) {
|
|
771
|
-
return previousNode.id();
|
|
772
|
-
} else {
|
|
773
|
-
var parentNode = currentNode.parentVariation().parentNode();
|
|
774
|
-
return parentNode ? getPreviousNodeId(parentNode) : 'start';
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
function getNextNodeId(currentNode, isVariation) {
|
|
779
|
-
var nextNode = isVariation ? currentNode.first() : currentNode.next();
|
|
780
|
-
return nextNode ? nextNode.id() : false;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
function getLastNodeId(currentNode, isVariation) {
|
|
784
|
-
currentNode = isVariation ? currentNode.first() : currentNode.next();
|
|
785
|
-
|
|
786
|
-
if (!currentNode) {
|
|
787
|
-
// Ensure that the input node is not already the last one.
|
|
788
|
-
return false;
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
while (true) {
|
|
792
|
-
var nextNode = currentNode.next();
|
|
793
|
-
|
|
794
|
-
if (!nextNode) {
|
|
795
|
-
return currentNode.id();
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
currentNode = nextNode;
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
904
|
/**
|
|
802
905
|
* Try to interpret the given object as a chess game.
|
|
803
906
|
*/
|
|
@@ -50,6 +50,7 @@ export default class Page extends React.Component {
|
|
|
50
50
|
selection: '28b',
|
|
51
51
|
interactionMode: 'selectMove',
|
|
52
52
|
withPopup: true,
|
|
53
|
+
withMove: false,
|
|
53
54
|
};
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -91,7 +92,7 @@ export default class Page extends React.Component {
|
|
|
91
92
|
pieceSymbols="figurines"
|
|
92
93
|
diagramVisible={false}
|
|
93
94
|
interactionMode={this.state.interactionMode}
|
|
94
|
-
onMoveSelected={nodeId => this.handleMoveSelected(nodeId)}
|
|
95
|
+
onMoveSelected={(nodeId, evtOrigin) => this.handleMoveSelected(nodeId, evtOrigin)}
|
|
95
96
|
/>
|
|
96
97
|
</Box>
|
|
97
98
|
);
|
|
@@ -117,14 +118,14 @@ export default class Page extends React.Component {
|
|
|
117
118
|
|
|
118
119
|
renderNavigationBoard() {
|
|
119
120
|
let button = <Button size="small" onClick={() => this.handlePopupToggled()}>{this.state.withPopup ? 'Reduce' : 'Open'}</Button>;
|
|
120
|
-
let { position, csl, cal } = this.getCurrentPositionAndAnnotations();
|
|
121
|
-
let content = this.state.withPopup ? <Stack><Chessboard position={position} squareMarkers={csl} arrowMarkers={cal} />{button}</Stack> : button;
|
|
121
|
+
let { position, move, csl, cal } = this.getCurrentPositionAndAnnotations();
|
|
122
|
+
let content = this.state.withPopup ? <Stack><Chessboard position={position} move={move} squareMarkers={csl} arrowMarkers={cal} animated={true} />{button}</Stack> : button;
|
|
122
123
|
return <Paper className="kokopu-fixedPopup" elevation={3}>{content}</Paper>;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
handleMoveSelected(nodeId) {
|
|
126
|
+
handleMoveSelected(nodeId, evtOrigin) {
|
|
126
127
|
if (nodeId) {
|
|
127
|
-
this.set('selection', nodeId);
|
|
128
|
+
this.set('selection', nodeId, 'withMove', evtOrigin === 'key-next');
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
|
|
@@ -149,7 +150,10 @@ export default class Page extends React.Component {
|
|
|
149
150
|
}
|
|
150
151
|
else {
|
|
151
152
|
let node = game.findById(this.state.selection);
|
|
152
|
-
|
|
153
|
+
let result = this.state.withMove ? { position: node.positionBefore(), move: node.notation() } : { position: node.position() };
|
|
154
|
+
result.csl = node.tag('csl');
|
|
155
|
+
result.cal = node.tag('cal');
|
|
156
|
+
return result;
|
|
153
157
|
}
|
|
154
158
|
}
|
|
155
159
|
}
|
|
@@ -26,8 +26,8 @@ import { Movetext } from '../src/index';
|
|
|
26
26
|
|
|
27
27
|
import pgn from './common/games.pgn';
|
|
28
28
|
|
|
29
|
-
function onMoveSelected(nodeId) {
|
|
30
|
-
setSandbox(`move selected: ${nodeId}`);
|
|
29
|
+
function onMoveSelected(nodeId, evtOrigin) {
|
|
30
|
+
setSandbox(`move selected: ${nodeId}\norigin: ${evtOrigin}`);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
testApp([ /* eslint-disable react/jsx-key */
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kokopu-react",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
4
4
|
"description": "A React-based library to create and display chessboard components.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chess",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"kokopu": "^2.7.0",
|
|
34
34
|
"prop-types": "^15.8.1",
|
|
35
35
|
"react": "^17.0.2",
|
|
36
|
-
"react-draggable": "^4.4.
|
|
36
|
+
"react-draggable": "^4.4.5",
|
|
37
37
|
"react-motion": "^0.5.2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"@babel/preset-react": "^7.16.7",
|
|
44
44
|
"@emotion/react": "^11.9.0",
|
|
45
45
|
"@emotion/styled": "^11.8.1",
|
|
46
|
-
"@mui/material": "^5.6.
|
|
46
|
+
"@mui/material": "^5.6.3",
|
|
47
47
|
"babel-loader": "^8.2.5",
|
|
48
48
|
"babel-plugin-istanbul": "^6.1.1",
|
|
49
49
|
"copy-webpack-plugin": "^10.2.4",
|
|
50
50
|
"coveralls": "^3.1.1",
|
|
51
51
|
"css-loader": "^6.7.1",
|
|
52
|
-
"eslint": "^8.
|
|
52
|
+
"eslint": "^8.14.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",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"prompt": "^1.3.0",
|
|
60
60
|
"react-dom": "^17.0.2",
|
|
61
61
|
"react-styleguidist": "^11.2.0",
|
|
62
|
-
"selenium-webdriver": "^4.1.
|
|
62
|
+
"selenium-webdriver": "^4.1.2",
|
|
63
63
|
"ssh2-sftp-client": "^8.0.0",
|
|
64
64
|
"style-loader": "^3.3.1",
|
|
65
65
|
"unit.js": "^2.1.1",
|
package/src/Chessboard.js
CHANGED
|
@@ -83,10 +83,10 @@ export default class Chessboard extends React.Component {
|
|
|
83
83
|
// Compute the current position.
|
|
84
84
|
let info = this.getPositionAndMoveInfo();
|
|
85
85
|
if (info.positionError) {
|
|
86
|
-
return <ErrorBox title={i18n.INVALID_FEN_ERROR_TITLE} message={info.message}
|
|
86
|
+
return <ErrorBox title={i18n.INVALID_FEN_ERROR_TITLE} message={info.message} />;
|
|
87
87
|
}
|
|
88
88
|
else if (info.moveError) {
|
|
89
|
-
return <ErrorBox title={i18n.INVALID_NOTATION_ERROR_TITLE} message={info.message}
|
|
89
|
+
return <ErrorBox title={i18n.INVALID_NOTATION_ERROR_TITLE} message={info.message} />;
|
|
90
90
|
}
|
|
91
91
|
let { position, move, positionBefore } = info;
|
|
92
92
|
|
package/src/Movetext.js
CHANGED
|
@@ -46,14 +46,12 @@ export default class Movetext extends React.Component {
|
|
|
46
46
|
render() {
|
|
47
47
|
let info = parseGame(this.props.game, this.props.gameIndex);
|
|
48
48
|
if (info.error) {
|
|
49
|
-
return <ErrorBox title={i18n.INVALID_PGN_ERROR_TITLE} message={info.message} text={info.text} errorIndex={info.errorIndex} lineNumber={info.lineNumber}
|
|
49
|
+
return <ErrorBox title={i18n.INVALID_PGN_ERROR_TITLE} message={info.message} text={info.text} errorIndex={info.errorIndex} lineNumber={info.lineNumber} />;
|
|
50
50
|
}
|
|
51
|
-
let game = info.game;
|
|
52
|
-
|
|
53
51
|
return (
|
|
54
52
|
<div className="kokopu-movetext">
|
|
55
|
-
{this.renderHeaders(game)}
|
|
56
|
-
{this.renderBody(game)}
|
|
53
|
+
{this.renderHeaders(info.game)}
|
|
54
|
+
{this.renderBody(info.game)}
|
|
57
55
|
{this.renderFocusField()}
|
|
58
56
|
</div>
|
|
59
57
|
);
|
|
@@ -333,46 +331,41 @@ export default class Movetext extends React.Component {
|
|
|
333
331
|
}
|
|
334
332
|
|
|
335
333
|
handleKeyDownInFocusField(evt) {
|
|
334
|
+
if (evt.key !== 'Home' && evt.key !== 'ArrowLeft' && evt.key !== 'ArrowRight' && evt.key !== 'End') {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
evt.preventDefault();
|
|
336
338
|
if (!this.props.selection) {
|
|
337
339
|
return;
|
|
338
340
|
}
|
|
339
|
-
let game = parseGame(this.props.game, this.props.gameIndex)
|
|
340
|
-
let nodeId =
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
else if (evt.key === 'End') {
|
|
346
|
-
nodeId = getLastNodeId(game.mainVariation(), true);
|
|
347
|
-
}
|
|
341
|
+
let { game } = parseGame(this.props.game, this.props.gameIndex);
|
|
342
|
+
let nodeId = undefined;
|
|
343
|
+
let evtOrigin = '';
|
|
344
|
+
if (evt.key === 'Home') {
|
|
345
|
+
nodeId = Movetext.firstNodeId(game, this.props.selection);
|
|
346
|
+
evtOrigin = 'key-first';
|
|
348
347
|
}
|
|
349
|
-
else {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
else if (evt.key === 'ArrowRight') {
|
|
361
|
-
nodeId = getNextNodeId(currentNode, false);
|
|
362
|
-
}
|
|
363
|
-
else if (evt.key === 'End') {
|
|
364
|
-
nodeId = getLastNodeId(currentNode, false);
|
|
365
|
-
}
|
|
348
|
+
else if (evt.key === 'ArrowLeft') {
|
|
349
|
+
nodeId = Movetext.previousNodeId(game, this.props.selection);
|
|
350
|
+
evtOrigin = 'key-previous';
|
|
351
|
+
}
|
|
352
|
+
else if (evt.key === 'ArrowRight') {
|
|
353
|
+
nodeId = Movetext.nextNodeId(game, this.props.selection);
|
|
354
|
+
evtOrigin = 'key-next';
|
|
355
|
+
}
|
|
356
|
+
else { // evt.key === 'End'
|
|
357
|
+
nodeId = Movetext.lastNodeId(game, this.props.selection);
|
|
358
|
+
evtOrigin = 'key-last';
|
|
366
359
|
}
|
|
367
360
|
if (nodeId && this.props.onMoveSelected) {
|
|
368
|
-
this.props.onMoveSelected(nodeId);
|
|
361
|
+
this.props.onMoveSelected(nodeId, evtOrigin);
|
|
369
362
|
}
|
|
370
363
|
}
|
|
371
364
|
|
|
372
365
|
handleNodeClicked(nodeId) {
|
|
373
|
-
this.
|
|
366
|
+
this.focus();
|
|
374
367
|
if (this.props.onMoveSelected) {
|
|
375
|
-
this.props.onMoveSelected(nodeId === this.props.selection ? undefined : nodeId);
|
|
368
|
+
this.props.onMoveSelected(nodeId === this.props.selection ? undefined : nodeId, 'click');
|
|
376
369
|
}
|
|
377
370
|
}
|
|
378
371
|
|
|
@@ -395,6 +388,114 @@ export default class Movetext extends React.Component {
|
|
|
395
388
|
return notation => notation;
|
|
396
389
|
}
|
|
397
390
|
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Set the focus to the current component.
|
|
394
|
+
*
|
|
395
|
+
* @public
|
|
396
|
+
*/
|
|
397
|
+
focus() {
|
|
398
|
+
this.focusFieldRef.current.focus();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Return the ID of the main variation in the given chess game.
|
|
403
|
+
* If the given selection corresponds already at the main variation, `undefined` is returned.
|
|
404
|
+
*
|
|
405
|
+
* This corresponds to the operation performed when the user presses key "home" on a `Movetext` component.
|
|
406
|
+
*
|
|
407
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
408
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
409
|
+
* @returns {string?}
|
|
410
|
+
* @public
|
|
411
|
+
*/
|
|
412
|
+
static firstNodeId(game, selection) {
|
|
413
|
+
if (!game.findById(selection)) {
|
|
414
|
+
return undefined;
|
|
415
|
+
}
|
|
416
|
+
return selection === 'start' ? undefined : 'start';
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Return the ID of the node immediately preceding the given selection in the given chess game.
|
|
421
|
+
* If no such node exists, `undefined` is returned.
|
|
422
|
+
*
|
|
423
|
+
* This corresponds to the operation performed when the user presses key "arrow left" on a `Movetext` component.
|
|
424
|
+
*
|
|
425
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
426
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
427
|
+
* @returns {string?}
|
|
428
|
+
* @public
|
|
429
|
+
*/
|
|
430
|
+
static previousNodeId(game, selection) {
|
|
431
|
+
let currentNode = game.findById(selection);
|
|
432
|
+
if (!currentNode) {
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
if (selection === 'start') {
|
|
436
|
+
return undefined;
|
|
437
|
+
}
|
|
438
|
+
else if (selection.endsWith('start')) {
|
|
439
|
+
currentNode = currentNode.parentNode(); // `.parentNode()` returns always non-null ref here
|
|
440
|
+
}
|
|
441
|
+
while (currentNode) {
|
|
442
|
+
let previousNode = currentNode.previous();
|
|
443
|
+
if (previousNode) {
|
|
444
|
+
return previousNode.id();
|
|
445
|
+
}
|
|
446
|
+
currentNode = currentNode.parentVariation().parentNode();
|
|
447
|
+
}
|
|
448
|
+
return 'start';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Return the ID of the node immediately following the given selection in the given chess game.
|
|
453
|
+
* If no such node exists, `undefined` is returned.
|
|
454
|
+
*
|
|
455
|
+
* This corresponds to the operation performed when the user presses key "arrow right" on a `Movetext` component.
|
|
456
|
+
*
|
|
457
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
458
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
459
|
+
* @returns {string?}
|
|
460
|
+
* @public
|
|
461
|
+
*/
|
|
462
|
+
static nextNodeId(game, selection) {
|
|
463
|
+
let currentNode = game.findById(selection);
|
|
464
|
+
if (!currentNode) {
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
let nextNode = selection.endsWith('start') ? currentNode.first() : currentNode.next();
|
|
468
|
+
return nextNode ? nextNode.id() : undefined;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Return the ID of the node at then end of the variation in which lies the given selection in the given chess game.
|
|
473
|
+
* If the selection is already at the end its variation, `undefined` is returned.
|
|
474
|
+
*
|
|
475
|
+
* This corresponds to the operation performed when the user presses key "end" on a `Movetext` component.
|
|
476
|
+
*
|
|
477
|
+
* @param {kokopu.Game} game Considered chess game.
|
|
478
|
+
* @param {string} selection ID of the selected move (or `'start'` for the beginning of the main variation).
|
|
479
|
+
* @returns {string?}
|
|
480
|
+
* @public
|
|
481
|
+
*/
|
|
482
|
+
static lastNodeId(game, selection) {
|
|
483
|
+
let currentNode = game.findById(selection);
|
|
484
|
+
if (!currentNode) {
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
487
|
+
currentNode = selection.endsWith('start') ? currentNode.first() : currentNode.next();
|
|
488
|
+
if (!currentNode) { // Ensure that the input node is not already the last one.
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
while (true) {
|
|
492
|
+
let nextNode = currentNode.next();
|
|
493
|
+
if (!nextNode) {
|
|
494
|
+
return currentNode.id();
|
|
495
|
+
}
|
|
496
|
+
currentNode = nextNode;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
398
499
|
}
|
|
399
500
|
|
|
400
501
|
|
|
@@ -473,10 +574,16 @@ Movetext.propTypes = {
|
|
|
473
574
|
interactionMode: PropTypes.oneOf([ 'selectMove' ]),
|
|
474
575
|
|
|
475
576
|
/**
|
|
476
|
-
* Callback invoked when the user
|
|
577
|
+
* Callback invoked when the user selects a move (only if `interactionMode` is set to `'selectMove'`).
|
|
477
578
|
*
|
|
478
579
|
* @param {string?} nodeId ID of the selected move (as returned by [kokopu.Node#id](https://kokopu.yo35.org/docs/Node.html#id)),
|
|
479
580
|
* `'start'` for the beginning of the main variation, or `undefined` if the user unselects the previously selected move.
|
|
581
|
+
* @param {string} evtOrigin Origin of the event. Can be:
|
|
582
|
+
* - `'key-first'`: the event has been triggered by the "go-to-first-move" key (aka. the home key),
|
|
583
|
+
* - `'key-previous'`: the event has been triggered by the "go-to-previous-move" key (aka. the arrow left key),
|
|
584
|
+
* - `'key-next'`: the event has been triggered by the "go-to-next-move" key (aka. the arrow right key),
|
|
585
|
+
* - `'key-last'`: the event has been triggered by the "go-to-last-move" key (aka. the end key),
|
|
586
|
+
* - `'click'`: the event has been triggered by a mouse click on a move.
|
|
480
587
|
*/
|
|
481
588
|
onMoveSelected: PropTypes.func,
|
|
482
589
|
};
|
|
@@ -555,39 +662,6 @@ function figurineNotation(text, fontName) {
|
|
|
555
662
|
}
|
|
556
663
|
|
|
557
664
|
|
|
558
|
-
function getPreviousNodeId(currentNode) {
|
|
559
|
-
let previousNode = currentNode.previous();
|
|
560
|
-
if (previousNode) {
|
|
561
|
-
return previousNode.id();
|
|
562
|
-
}
|
|
563
|
-
else {
|
|
564
|
-
let parentNode = currentNode.parentVariation().parentNode();
|
|
565
|
-
return parentNode ? getPreviousNodeId(parentNode) : 'start';
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
function getNextNodeId(currentNode, isVariation) {
|
|
571
|
-
let nextNode = isVariation ? currentNode.first() : currentNode.next();
|
|
572
|
-
return nextNode ? nextNode.id() : false;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
function getLastNodeId(currentNode, isVariation) {
|
|
577
|
-
currentNode = isVariation ? currentNode.first() : currentNode.next();
|
|
578
|
-
if (!currentNode) { // Ensure that the input node is not already the last one.
|
|
579
|
-
return false;
|
|
580
|
-
}
|
|
581
|
-
while (true) {
|
|
582
|
-
let nextNode = currentNode.next();
|
|
583
|
-
if (!nextNode) {
|
|
584
|
-
return currentNode.id();
|
|
585
|
-
}
|
|
586
|
-
currentNode = nextNode;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
|
|
591
665
|
/**
|
|
592
666
|
* Try to interpret the given object as a chess game.
|
|
593
667
|
*/
|
|
@@ -53,18 +53,22 @@ describe('Movetext interaction', function() {
|
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function tpl(nodeId, evtOrigin) {
|
|
57
|
+
return `move selected: ${nodeId}\norigin: ${evtOrigin}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
56
60
|
itCheckClickMove(0, 'click on disabled', [
|
|
57
61
|
{ searchedText: 'Nf6', expectedText: '' },
|
|
58
62
|
{ searchedText: 'Qc2', expectedText: '' },
|
|
59
63
|
]);
|
|
60
64
|
|
|
61
65
|
itCheckClickMove(1, 'click on unselected', [
|
|
62
|
-
{ searchedText: 'cxd5', expectedText: '
|
|
66
|
+
{ searchedText: 'cxd5', expectedText: tpl('5w', 'click') },
|
|
63
67
|
]);
|
|
64
68
|
|
|
65
69
|
itCheckClickMove(2, 'click on with variations 1', [
|
|
66
|
-
{ searchedText: 'Bb5', expectedText:
|
|
67
|
-
{ searchedText: 'Nc6', expectedText: '
|
|
70
|
+
{ searchedText: 'Bb5', expectedText: tpl(undefined, 'click') },
|
|
71
|
+
{ searchedText: 'Nc6', expectedText: tpl('1b-v0-2b', 'click') },
|
|
68
72
|
]);
|
|
69
73
|
|
|
70
74
|
function itCheckKeyboardActions(itemIndex, label, expectedOnGoFirst, expectedOnGoPrevious, expectedOnGoNext, expectedOnGoLast) {
|
|
@@ -86,14 +90,19 @@ describe('Movetext interaction', function() {
|
|
|
86
90
|
await setSandbox(browserContext, '');
|
|
87
91
|
await focusFieldElement.sendKeys(Key.END);
|
|
88
92
|
await compareSandbox(browserContext, expectedOnGoLast);
|
|
93
|
+
|
|
94
|
+
await setSandbox(browserContext, '');
|
|
95
|
+
await focusFieldElement.sendKeys(Key.ARROW_UP);
|
|
96
|
+
await compareSandbox(browserContext, '');
|
|
89
97
|
});
|
|
90
98
|
}
|
|
91
99
|
|
|
92
100
|
itCheckKeyboardActions(1, 'key on unselected', '', '', '', '');
|
|
93
|
-
itCheckKeyboardActions(2, 'key on with variations 1', '
|
|
94
|
-
itCheckKeyboardActions(3, 'key on with variations 2', '
|
|
95
|
-
itCheckKeyboardActions(4, 'key on start selected', '', '', '
|
|
96
|
-
itCheckKeyboardActions(5, 'key on first selected', '
|
|
97
|
-
itCheckKeyboardActions(6, 'key on last selected', '
|
|
101
|
+
itCheckKeyboardActions(2, 'key on with variations 1', tpl('start', 'key-first'), tpl('1b-v0-2b', 'key-previous'), tpl('1b-v0-3w-v0-3b', 'key-next'), tpl('1b-v0-3w-v0-4w', 'key-last'));
|
|
102
|
+
itCheckKeyboardActions(3, 'key on with variations 2', tpl('start', 'key-first'), tpl('1b-v0-2w', 'key-previous'), tpl('1b-v0-3w', 'key-next'), tpl('1b-v0-3w', 'key-last'));
|
|
103
|
+
itCheckKeyboardActions(4, 'key on start selected', '', '', tpl('1w', 'key-next'), tpl('6b', 'key-last'));
|
|
104
|
+
itCheckKeyboardActions(5, 'key on first selected', tpl('start', 'key-first'), tpl('start', 'key-previous'), tpl('1b', 'key-next'), tpl('6b', 'key-last'));
|
|
105
|
+
itCheckKeyboardActions(6, 'key on last selected', tpl('start', 'key-first'), tpl('6w', 'key-previous'), '', '');
|
|
98
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'));
|
|
99
108
|
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|