Skip to content

Commit

Permalink
Add NavigationBoard (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
yo35 committed Feb 25, 2024
1 parent 1ba69f3 commit da12656
Show file tree
Hide file tree
Showing 53 changed files with 985 additions and 10 deletions.
194 changes: 194 additions & 0 deletions doc_src/demo/PageNavigationBoardBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*!
* -------------------------------------------------------------------------- *
* *
* Kokopu-React - A React-based library of chess-related components. *
* <https://www.npmjs.com/package/kokopu-react> *
* Copyright (C) 2021-2024 Yoann Le Montagner <yo35 -at- melix.net> *
* *
* Kokopu-React is free software: you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public License *
* as published by the Free Software Foundation, either version 3 of *
* the License, or (at your option) any later version. *
* *
* Kokopu-React is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General *
* Public License along with this program. If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* -------------------------------------------------------------------------- */


import * as React from 'react';

import { AnnotationColor, ArrowMarkerIcon, Chessboard, NavigationBoard } from '../../src/index';
import { buildComponentDemoCode } from './util';

import Box from '@mui/material/Box';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import Slider from '@mui/material/Slider';
import Stack from '@mui/material/Stack';
import Switch from '@mui/material/Switch';
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import Typography from '@mui/material/Typography';

import './demo.css';
import pgn from './game-3.pgn';


const COLOR_ICON_SIZE = 16;


interface PageState {
squareSize: number;
coordinateVisible: boolean;
turnVisible: boolean;
colorset: string;
pieceset: string;
moveArrowVisible: boolean;
moveArrowColor: AnnotationColor;
animated: boolean;
flipButtonVisible: boolean;
}


export default class Page extends React.Component<object, PageState> {

constructor(props: object) {
super(props);
this.state = {
squareSize: 40,
coordinateVisible: true,
turnVisible: true,
colorset: 'original',
pieceset: 'cburnett',
moveArrowVisible: true,
moveArrowColor: 'b',
animated: true,
flipButtonVisible: true,
};
}

render() {
return (
<Stack spacing={2} mt={2}>
{this.renderControls()}
{this.renderNavigationBoard()}
{this.renderCode()}
</Stack>
);
}

private renderControls() {
return (<>
<Stack direction="row" spacing={2} alignItems="center">
<FormControlLabel label="Show flip button"
control={<Switch checked={this.state.flipButtonVisible} onChange={() => this.setState({ flipButtonVisible: !this.state.flipButtonVisible })} color="primary" />}
/>
<FormControlLabel label="Show coordinates"
control={<Switch checked={this.state.coordinateVisible} onChange={() => this.setState({ coordinateVisible: !this.state.coordinateVisible })} color="primary" />}
/>
<FormControlLabel label="Show turn"
control={<Switch checked={this.state.turnVisible} onChange={() => this.setState({ turnVisible: !this.state.turnVisible })} color="primary" />}
/>
<FormControlLabel label="Animated"
control={<Switch checked={this.state.animated} onChange={() => this.setState({ animated: !this.state.animated })} color="primary" />}
/>
</Stack>
<Box>
<Typography gutterBottom>Square size</Typography>
<Slider
value={this.state.squareSize} onChange={(_, newValue) => this.setState({ squareSize: newValue as number })}
min={Chessboard.minSquareSize()} max={Chessboard.maxSquareSize()} step={1} valueLabelDisplay="on" color="primary"
/>
</Box>
<Stack direction="row" spacing={2} alignItems="center">
<FormControl variant="standard">
<InputLabel id="colorset-label">Colorset</InputLabel>
<Select labelId="colorset-label" sx={{ width: '8em' }} value={this.state.colorset} onChange={evt => this.setState({ colorset: evt.target.value })}>
{Object.keys(Chessboard.colorsets()).sort().map(colorset => <MenuItem key={colorset} value={colorset}>{colorset}</MenuItem>)}
</Select>
</FormControl>
<FormControl variant="standard">
<InputLabel id="pieceset-label">Pieceset</InputLabel>
<Select labelId="pieceset-label" sx={{ width: '8em' }} value={this.state.pieceset} onChange={evt => this.setState({ pieceset: evt.target.value })}>
{Object.keys(Chessboard.piecesets()).sort().map(pieceset => <MenuItem key={pieceset} value={pieceset}>{pieceset}</MenuItem>)}
</Select>
</FormControl>
<FormControlLabel label="Show move arrow"
control={<Switch checked={this.state.moveArrowVisible} onChange={() => this.setState({ moveArrowVisible: !this.state.moveArrowVisible })} color="primary" />}
/>
{this.renderMoveArrowColorSelector()}
</Stack>
</>);
}

private renderMoveArrowColorSelector() {
if (!this.state.moveArrowVisible) {
return undefined;
}
const colorset = Chessboard.colorsets()['original'];
return (
<ToggleButtonGroup value={this.state.moveArrowColor} exclusive size="small" onChange={(_, newColor) => this.handleMoveArrowColorChanged(newColor)}>
<ToggleButton value="b"><ArrowMarkerIcon size={COLOR_ICON_SIZE} color={colorset.cb} /></ToggleButton>
<ToggleButton value="g"><ArrowMarkerIcon size={COLOR_ICON_SIZE} color={colorset.cg} /></ToggleButton>
<ToggleButton value="r"><ArrowMarkerIcon size={COLOR_ICON_SIZE} color={colorset.cr} /></ToggleButton>
<ToggleButton value="y"><ArrowMarkerIcon size={COLOR_ICON_SIZE} color={colorset.cy} /></ToggleButton>
</ToggleButtonGroup>
);
}

private renderNavigationBoard() {
return (
<Box>
<NavigationBoard
game={pgn}
initialNodeId="end"
squareSize={this.state.squareSize}
coordinateVisible={this.state.coordinateVisible}
turnVisible={this.state.turnVisible}
colorset={this.state.colorset}
pieceset={this.state.pieceset}
moveArrowVisible={this.state.moveArrowVisible}
moveArrowColor={this.state.moveArrowColor}
animated={this.state.animated}
flipButtonVisible={this.state.flipButtonVisible}
/>
</Box>
);
}

private renderCode() {
const attributes: string[] = [];
attributes.push('game={pgn}');
attributes.push('initialNodeId="end"');
attributes.push(`squareSize={${this.state.squareSize}}`);
attributes.push(`coordinateVisible={${this.state.coordinateVisible}}`);
attributes.push(`turnVisible={${this.state.turnVisible}}`);
attributes.push(`colorset="${this.state.colorset}"`);
attributes.push(`pieceset="${this.state.pieceset}"`);
attributes.push(`moveArrowVisible={${this.state.moveArrowVisible}}`);
if (this.state.moveArrowVisible) {
attributes.push(`moveArrowColor="${this.state.moveArrowColor}"`);
}
attributes.push(`animated={${this.state.animated}}`);
attributes.push(`flipButtonVisible={${this.state.flipButtonVisible}}`);
const pgnDeclaration = 'const pgn = `\n' + pgn.trim() + '`;\n\n';
return <pre className="kokopu-demoCode">{pgnDeclaration + buildComponentDemoCode('NavigationBoard', attributes)}</pre>;
}

private handleMoveArrowColorChanged(newColor: AnnotationColor | null) {
if (newColor !== null) {
this.setState({ moveArrowColor: newColor });
}
}

}
10 changes: 10 additions & 0 deletions doc_src/demo/game-3.pgn
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Event "Television Exhibition"]
[Site "London ENG"]
[Date "2014.01.23"]
[Round "?"]
[White "Bill Gates"]
[Black "Magnus Carlsen"]
[Result "0-1"]

1.e4 Nc6 2.Nf3 d5 3.Bd3 Nf6 4.exd5 Qxd5 5.Nc3 Qh5 6.O-O Bg4
7.h3 Ne5 8.hxg4 Nfxg4 9.Nxe5 Qh2# 0-1
15 changes: 15 additions & 0 deletions doc_src/examples/NavigationBoard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
```js
const pgn = `
[Event "Television Exhibition"]
[Site "London ENG"]
[Date "2014.01.23"]
[Round "?"]
[White "Bill Gates"]
[Black "Magnus Carlsen"]
[Result "0-1"]
1.e4 Nc6 2.Nf3 d5 3.Bd3 Nf6 4.exd5 Qxd5 5.Nc3 Qh5 6.O-O Bg4
7.h3 Ne5 8.hxg4 Nfxg4 9.Nxe5 Qh2# 0-1`;

<NavigationBoard game={pgn} initialNodeId="end" />
```
3 changes: 2 additions & 1 deletion scripts/doc.styleguide.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fs.mkdirSync(path.resolve(__dirname, tmpDir), { recursive: true });

const componentSectionTitle = 'Components';
const components = [ 'errorbox/ErrorBox', 'icons/SquareMarkerIcon', 'icons/TextMarkerIcon', 'icons/ArrowMarkerIcon', 'icons/ChessPieceIcon',
'chessboard/Chessboard', 'movetext/Movetext' ];
'chessboard/Chessboard', 'movetext/Movetext', 'navigationboard/NavigationBoard' ];


// Demo section configuration
Expand All @@ -54,6 +54,7 @@ const demoPages = [
{ id: 'ChessboardSmallScreens', title: 'Chessboard - Small screens' },
{ id: 'MovetextBase', title: 'Movetext - Basic features' },
{ id: 'MovetextInteraction', title: 'Movetext - Interactions' },
{ id: 'NavigationBoardBase', title: 'NavigationBoard - Basic features' },
];


Expand Down
6 changes: 0 additions & 6 deletions src/chessboard/BoardProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ export interface SmallScreenLimit {
*/
export interface StaticBoardGraphicProps {

/**
* Whether the board is flipped (i.e. seen from Black's point of view) or not.
*/
flipped: boolean;

/**
* Size of the squares (in pixels). Must be an integer between `Chessboard.minSquareSize()` and `Chessboard.maxSquareSize()`.
*/
Expand Down Expand Up @@ -117,7 +112,6 @@ export const DEFAULT_SQUARE_SIZE = 40;

export function defaultStaticBoardProps(): StaticBoardGraphicProps {
return {
flipped: false,
squareSize: DEFAULT_SQUARE_SIZE,
coordinateVisible: true,
turnVisible: true,
Expand Down
6 changes: 6 additions & 0 deletions src/chessboard/Chessboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ export interface ChessboardProps extends DynamicBoardGraphicProps {
*/
arrowMarkers?: ArrowMarkerSet | string;

/**
* Whether the board is flipped (i.e. seen from Black's point of view) or not.
*/
flipped: boolean;

/**
* Type of action allowed with the mouse on the chessboard. If undefined, then the user cannot interact with the component.
*
Expand Down Expand Up @@ -126,6 +131,7 @@ export class Chessboard extends React.Component<ChessboardProps, ChessboardState
static defaultProps: Partial<ChessboardProps> = {
...defaultDynamicBoardProps(),
position: 'start',
flipped: false,
};

private windowResizeListener = () => this.handleWindowResize();
Expand Down
7 changes: 7 additions & 0 deletions src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export let LINE = 'line {0}';
export let INVALID_FEN_ERROR_TITLE = 'Invalid FEN string.';
export let INVALID_NOTATION_ERROR_TITLE = 'Invalid move notation.';

// Navigation board
export let TOOLTIP_GO_FIRST = 'Go to the beginning of the game';
export let TOOLTIP_GO_PREVIOUS = 'Go to the previous move';
export let TOOLTIP_GO_NEXT = 'Go to the next move';
export let TOOLTIP_GO_LAST = 'Go to the end of the game';
export let TOOLTIP_FLIP = 'Flip the board';

// Movetext
export let PIECE_SYMBOLS = { 'K':'K', 'Q':'Q', 'R':'R', 'B':'B', 'N':'N', 'P':'P' };
export let ANNOTATED_BY = 'Annotated by {0}';
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ export { Chessboard, ChessboardProps } from './chessboard/Chessboard';

export { formatMove, moveFormatter } from './movetext/moveFormatter';
export { Movetext, MovetextProps, MoveSelectEventOrigin } from './movetext/Movetext';

export { NavigationBoard, NavigationBoardProps } from './navigationboard/NavigationBoard';
4 changes: 3 additions & 1 deletion src/movetext/Movetext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ export interface MovetextProps {
/**
* Options applicable to the diagrams in the comments. See [Chessboard](#/Components/Chessboard) for more details about each option.
*/
diagramOptions: Partial<StaticBoardGraphicProps>;
diagramOptions: Partial<StaticBoardGraphicProps> & {
flipped?: boolean,
};

/**
* Symbols to use for the chess pieces. See {@link moveFormatter}.
Expand Down
12 changes: 10 additions & 2 deletions src/movetext/MovetextImpl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ interface MovetextImplProps {

game: Game;

diagramOptions: Partial<StaticBoardGraphicProps>;
diagramOptions: Partial<StaticBoardGraphicProps> & {
flipped?: boolean,
};
moveFormatter: (notation: string) => React.ReactNode;
diagramVisible: boolean;
headerVisible: boolean;
Expand Down Expand Up @@ -309,9 +311,15 @@ export class MovetextImpl extends React.Component<MovetextImplProps> {
if (!isFirstTextSegment) {
const position = node instanceof Variation ? node.initialPosition() : node.position();
const diagram = <Chessboard
{ ...this.props.diagramOptions }
position={position}
squareMarkers={node.tag('csl')} arrowMarkers={node.tag('cal')} textMarkers={node.tag('ctl')}
flipped={this.props.diagramOptions.flipped}
squareSize={this.props.diagramOptions.squareSize}
coordinateVisible={this.props.diagramOptions.coordinateVisible}
turnVisible={this.props.diagramOptions.turnVisible}
colorset={this.props.diagramOptions.colorset}
pieceset={this.props.diagramOptions.pieceset}
smallScreenLimits={this.props.diagramOptions.smallScreenLimits}
/>;
segmentElements.push(<div className="kokopu-diagram" key={'diagram-' + (diagramIndex++)}>{diagram}</div>);
}
Expand Down
Loading

0 comments on commit da12656

Please sign in to comment.