2023-05-17 19:02:43 +00:00
|
|
|
import { JSONContent } from '@tiptap/react';
|
|
|
|
|
import { Chess, Move } from 'chess.js';
|
|
|
|
|
import { Api } from 'chessground/api';
|
|
|
|
|
import { DrawShape } from 'chessground/draw';
|
|
|
|
|
import { App, Notice } from 'obsidian';
|
|
|
|
|
import * as React from 'react';
|
|
|
|
|
import { useCallback, useMemo, useState } from 'react';
|
|
|
|
|
import { ChessifyPluginSettings } from 'src/components/obsidian/SettingsTab';
|
2023-05-15 20:14:35 +00:00
|
|
|
import {
|
|
|
|
|
ChessifyDataAdapter,
|
|
|
|
|
ChessifyFileData,
|
|
|
|
|
parseUserConfig,
|
2023-05-17 19:02:43 +00:00
|
|
|
} from 'src/utils';
|
|
|
|
|
import { ChessGroundSettings, ChessgroundWrapper } from './ChessgroundWrapper';
|
|
|
|
|
import { CommentSection } from './CommentSection';
|
|
|
|
|
import { PgnViewer } from './PgnViewer';
|
2023-05-07 15:22:46 +00:00
|
|
|
|
|
|
|
|
export type ChessifyConfig = ChessGroundSettings;
|
|
|
|
|
|
|
|
|
|
interface AppProps {
|
|
|
|
|
source: string;
|
|
|
|
|
app: App;
|
|
|
|
|
pluginSettings: ChessifyPluginSettings;
|
2023-05-09 20:05:55 +00:00
|
|
|
chessifyData: ChessifyFileData;
|
2023-05-15 20:14:35 +00:00
|
|
|
dataAdapter: ChessifyDataAdapter;
|
2023-05-07 15:22:46 +00:00
|
|
|
}
|
|
|
|
|
|
2023-05-09 20:05:55 +00:00
|
|
|
export const Chessify = ({
|
|
|
|
|
source,
|
|
|
|
|
pluginSettings,
|
|
|
|
|
chessifyData,
|
2023-05-15 20:14:35 +00:00
|
|
|
dataAdapter,
|
2023-05-09 20:05:55 +00:00
|
|
|
}: AppProps) => {
|
|
|
|
|
// Parse Obsidian / Code Block Settings
|
|
|
|
|
const { boardColor, boardOrientation, chessifyId } = parseUserConfig(
|
2023-05-08 19:31:27 +00:00
|
|
|
pluginSettings,
|
|
|
|
|
source
|
|
|
|
|
);
|
2023-05-07 15:22:46 +00:00
|
|
|
|
2023-05-15 20:14:35 +00:00
|
|
|
const [chessifyDataModified] = useState(chessifyData);
|
|
|
|
|
|
2023-05-09 20:05:55 +00:00
|
|
|
// Setup Chessboard and Chess.js APIs
|
|
|
|
|
const [chessView, setChessView] = useState<Api | null>(null);
|
|
|
|
|
const chessLogic = useMemo(() => {
|
|
|
|
|
const chess = new Chess();
|
|
|
|
|
|
|
|
|
|
chessifyData.moves.forEach((move) => {
|
|
|
|
|
chess.move({
|
|
|
|
|
from: move.from,
|
|
|
|
|
to: move.to,
|
|
|
|
|
promotion: move.promotion,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return chess;
|
|
|
|
|
}, [chessifyData.moves]);
|
|
|
|
|
|
|
|
|
|
// Track Application State
|
|
|
|
|
const [history, setHistory] = useState<Move[]>([]);
|
|
|
|
|
const [isViewOnly, setIsViewOnly] = useState<boolean>(false);
|
|
|
|
|
const [currentMove, setCurrentMove] = useState<number>(0);
|
2023-05-15 20:14:35 +00:00
|
|
|
const [shapes, setShapes] = useState<DrawShape[][]>(
|
|
|
|
|
chessifyData.moves.map((data) => data.shapes)
|
|
|
|
|
);
|
2023-05-16 20:00:58 +00:00
|
|
|
const [comments, setComments] = useState<(JSONContent | null)[]>(
|
|
|
|
|
chessifyData.moves.map((data) => data.comment)
|
|
|
|
|
);
|
2023-05-09 20:05:55 +00:00
|
|
|
|
|
|
|
|
//PgnViewer Functions
|
|
|
|
|
const onBackButtonClick = useCallback(() => {
|
|
|
|
|
if (currentMove >= 0) {
|
|
|
|
|
setCurrentMove((currentMove) => {
|
|
|
|
|
const move = history[currentMove];
|
|
|
|
|
const tempChess = new Chess(move.before);
|
|
|
|
|
chessView?.set({
|
|
|
|
|
fen: move.before,
|
|
|
|
|
check: tempChess.isCheck(),
|
|
|
|
|
});
|
|
|
|
|
setIsViewOnly(true);
|
|
|
|
|
return currentMove - 1;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}, [chessView, currentMove, history]);
|
|
|
|
|
|
|
|
|
|
const onForwardButtonClick = useCallback(() => {
|
|
|
|
|
if (currentMove < history.length - 1) {
|
|
|
|
|
setCurrentMove((currentMove) => {
|
|
|
|
|
const move = history[currentMove + 1];
|
|
|
|
|
const tempChess = new Chess(move.after);
|
|
|
|
|
chessView?.set({
|
|
|
|
|
fen: move.after,
|
|
|
|
|
check: tempChess.isCheck(),
|
|
|
|
|
});
|
2023-05-17 19:02:43 +00:00
|
|
|
if (currentMove + 1 === history.length - 1) setIsViewOnly(false);
|
2023-05-09 20:05:55 +00:00
|
|
|
return currentMove + 1;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}, [currentMove, chessView, history]);
|
|
|
|
|
|
|
|
|
|
const onMoveItemClick = useCallback(
|
|
|
|
|
(moveIndex: number) => {
|
|
|
|
|
setCurrentMove(() => {
|
|
|
|
|
const move = history[moveIndex];
|
|
|
|
|
const tempChess = new Chess(move.after);
|
|
|
|
|
chessView?.set({
|
|
|
|
|
fen: move.after,
|
|
|
|
|
check: tempChess.isCheck(),
|
|
|
|
|
});
|
2023-05-16 20:00:58 +00:00
|
|
|
if (moveIndex !== history.length - 1) {
|
2023-05-09 20:05:55 +00:00
|
|
|
setIsViewOnly(true);
|
2023-05-16 20:00:58 +00:00
|
|
|
} else {
|
|
|
|
|
setIsViewOnly(false);
|
2023-05-09 20:05:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return moveIndex;
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[chessView, history]
|
|
|
|
|
);
|
|
|
|
|
|
2023-05-15 20:14:35 +00:00
|
|
|
const onSaveButtonClick = useCallback(async () => {
|
2023-05-16 20:00:58 +00:00
|
|
|
const chessifyData: ChessifyFileData = {
|
2023-05-15 20:14:35 +00:00
|
|
|
header: chessifyDataModified.header,
|
|
|
|
|
moves: chessLogic.history({ verbose: true }).map((move, index) => ({
|
|
|
|
|
...move,
|
2023-05-17 19:02:43 +00:00
|
|
|
variants: [],
|
2023-05-15 20:14:35 +00:00
|
|
|
shapes: shapes[index],
|
2023-05-16 20:00:58 +00:00
|
|
|
comment: comments[index],
|
2023-05-15 20:14:35 +00:00
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await dataAdapter.saveFile(chessifyData, chessifyId);
|
|
|
|
|
|
2023-05-17 19:02:43 +00:00
|
|
|
new Notice('Save successfull!');
|
2023-05-15 20:14:35 +00:00
|
|
|
}, [
|
|
|
|
|
chessLogic,
|
|
|
|
|
chessifyDataModified.header,
|
|
|
|
|
chessifyId,
|
2023-05-16 20:00:58 +00:00
|
|
|
comments,
|
2023-05-15 20:14:35 +00:00
|
|
|
dataAdapter,
|
|
|
|
|
shapes,
|
|
|
|
|
]);
|
|
|
|
|
|
2023-05-07 15:22:46 +00:00
|
|
|
return (
|
2023-05-16 20:00:58 +00:00
|
|
|
<div className="border">
|
2023-05-09 20:05:55 +00:00
|
|
|
<div
|
|
|
|
|
style={{
|
2023-05-17 19:02:43 +00:00
|
|
|
display: 'flex',
|
|
|
|
|
height: '450px',
|
2023-05-09 20:05:55 +00:00
|
|
|
}}
|
|
|
|
|
>
|
2023-05-16 20:00:58 +00:00
|
|
|
<div
|
|
|
|
|
style={{
|
2023-05-17 19:02:43 +00:00
|
|
|
flex: '0 0 450px',
|
|
|
|
|
height: '100%',
|
2023-05-09 20:05:55 +00:00
|
|
|
}}
|
2023-05-16 20:00:58 +00:00
|
|
|
>
|
|
|
|
|
<ChessgroundWrapper
|
|
|
|
|
api={chessView}
|
|
|
|
|
setApi={setChessView}
|
|
|
|
|
chessifyId={chessifyId}
|
|
|
|
|
config={{
|
|
|
|
|
orientation: boardOrientation,
|
|
|
|
|
}}
|
|
|
|
|
boardColor={boardColor}
|
|
|
|
|
chess={chessLogic}
|
|
|
|
|
setHistory={setHistory}
|
|
|
|
|
setMoveNumber={setCurrentMove}
|
|
|
|
|
isViewOnly={isViewOnly}
|
|
|
|
|
setShapes={setShapes}
|
|
|
|
|
currentMoveNumber={currentMove}
|
|
|
|
|
currentMoveShapes={shapes[currentMove]}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2023-05-17 19:02:43 +00:00
|
|
|
<div style={{ flex: 1, height: '100%' }}>
|
2023-05-16 20:00:58 +00:00
|
|
|
<PgnViewer
|
|
|
|
|
history={history}
|
|
|
|
|
currentMove={currentMove}
|
|
|
|
|
onBackButtonClick={onBackButtonClick}
|
|
|
|
|
onForwardButtonClick={onForwardButtonClick}
|
|
|
|
|
onMoveItemClick={onMoveItemClick}
|
|
|
|
|
onSaveButtonClick={onSaveButtonClick}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2023-05-09 20:05:55 +00:00
|
|
|
</div>
|
2023-05-16 20:00:58 +00:00
|
|
|
<div className="CommentSection border-top">
|
|
|
|
|
<CommentSection
|
2023-05-09 20:05:55 +00:00
|
|
|
currentMove={currentMove}
|
2023-05-16 20:00:58 +00:00
|
|
|
currentComment={comments[currentMove]}
|
|
|
|
|
setComments={setComments}
|
2023-05-09 20:05:55 +00:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2023-05-07 15:22:46 +00:00
|
|
|
);
|
|
|
|
|
};
|