mirror of
https://github.com/chrislicodes/obsidian-chess-study.git
synced 2026-07-22 07:50:30 +00:00
feat: FEN support
This commit is contained in:
parent
87e4b522dc
commit
e5381f1cf5
6 changed files with 99 additions and 25 deletions
|
|
@ -71,6 +71,7 @@ Here are the available settings for a `chessStudy` code block:
|
|||
| `chessStudyId` | Valid nanoid | Valid ID for a file stored in the plugin storage |
|
||||
| `boardOrientation` | `white` \| `black` | Orientation of the board |
|
||||
| `boardColor` | `green` \| `brown` | Color of the board |
|
||||
| `fen` | Valid FEN string | FEN string |
|
||||
| `viewComments` | `true` \| `false` | Whether to display the comments section |
|
||||
|
||||
You can permanently set some settings in the [Obsidian](https://obsidian.md/) plugin settings for Obsidian Chess Study.
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ import ChessStudyPlugin from 'src/main';
|
|||
export interface ChessStudyPluginSettings {
|
||||
boardOrientation: 'white' | 'black';
|
||||
boardColor: 'green' | 'brown';
|
||||
fen: string;
|
||||
viewComments: true | false;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: ChessStudyPluginSettings = {
|
||||
boardOrientation: 'white',
|
||||
boardColor: 'green',
|
||||
fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
|
||||
viewComments: true,
|
||||
};
|
||||
|
||||
|
|
@ -58,6 +60,18 @@ export class SettingsTab extends PluginSettingTab {
|
|||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('FEN')
|
||||
.setDesc('Sets the default board position')
|
||||
.addText((text) => {
|
||||
text
|
||||
.setValue(this.plugin.settings.fen)
|
||||
.onChange((fen) => {
|
||||
this.plugin.settings.fen = fen as string;
|
||||
this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('View Comments')
|
||||
.setDesc('Sets the default view of the comments')
|
||||
|
|
|
|||
|
|
@ -56,15 +56,19 @@ export const ChessStudy = ({
|
|||
dataAdapter,
|
||||
}: AppProps) => {
|
||||
// Parse Obsidian / Code Block Settings
|
||||
const { boardColor, boardOrientation, viewComments, chessStudyId } =
|
||||
const { boardColor, boardOrientation, fen, viewComments, chessStudyId } =
|
||||
|
||||
parseUserConfig(pluginSettings, source);
|
||||
|
||||
// Setup Chessground API
|
||||
const [chessView, setChessView] = useState<Api | null>(null);
|
||||
|
||||
// Setup Chess.js API
|
||||
const initialChessLogic = useMemo(() => {
|
||||
const chess = new Chess();
|
||||
const [firstPlayer, initialMoveNumber, initialChessLogic] = useMemo(() => {
|
||||
const chess = (fen) ? new Chess(fen) : new Chess();
|
||||
|
||||
const firstPlayer = chess.turn();
|
||||
const initialMoveNumber = chess.moveNumber();
|
||||
|
||||
chessStudyData.moves.forEach((move) => {
|
||||
chess.move({
|
||||
|
|
@ -73,7 +77,8 @@ export const ChessStudy = ({
|
|||
promotion: move.promotion,
|
||||
});
|
||||
});
|
||||
return chess;
|
||||
|
||||
return [firstPlayer, initialMoveNumber, chess];
|
||||
}, [chessStudyData.moves]);
|
||||
|
||||
const [chessLogic, setChessLogic] = useState(initialChessLogic);
|
||||
|
|
@ -312,6 +317,9 @@ export const ChessStudy = ({
|
|||
<PgnViewer
|
||||
history={gameState.study.moves}
|
||||
currentMoveId={gameState.currentMove?.moveId}
|
||||
|
||||
firstPlayer={firstPlayer}
|
||||
initialMoveNumber={initialMoveNumber}
|
||||
onUndoButtonClick={() =>
|
||||
dispatch({ type: 'REMOVE_LAST_MOVE_FROM_HISTORY' })
|
||||
}
|
||||
|
|
@ -328,6 +336,14 @@ export const ChessStudy = ({
|
|||
})
|
||||
}
|
||||
onSaveButtonClick={onSaveButtonClick}
|
||||
onCopyButtonClick={() => {
|
||||
try {
|
||||
navigator.clipboard.writeText(chessLogic.fen())
|
||||
new Notice('Copied to clipboard!');
|
||||
} catch (e) {
|
||||
new Notice('Could not copy to clipboard:', e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import * as React from 'react';
|
|||
import { ReactNode, useEffect, useMemo, useRef } from 'react';
|
||||
import { ChessStudyMove } from 'src/lib/storage';
|
||||
|
||||
const chunkArray = <T,>(array: T[], chunkSize: number) => {
|
||||
const chunkArray = <T,>(array: T[], chunkSize: number, offsetByOne: boolean = false) => {
|
||||
return array.reduce((resultArray, item, index) => {
|
||||
const chunkIndex = Math.floor(index / chunkSize);
|
||||
const chunkIndex = Math.floor((index + ((offsetByOne) ? 1 : 0)) / chunkSize);
|
||||
|
||||
if (!resultArray[chunkIndex]) {
|
||||
resultArray[chunkIndex] = [];
|
||||
|
|
@ -108,21 +108,27 @@ export const PgnViewer = React.memo(
|
|||
({
|
||||
history,
|
||||
currentMoveId,
|
||||
firstPlayer,
|
||||
initialMoveNumber,
|
||||
onUndoButtonClick,
|
||||
onBackButtonClick,
|
||||
onForwardButtonClick,
|
||||
onMoveItemClick,
|
||||
onSaveButtonClick,
|
||||
onCopyButtonClick,
|
||||
}: {
|
||||
history: ChessStudyMove[];
|
||||
currentMoveId: string;
|
||||
firstPlayer: string;
|
||||
initialMoveNumber: number;
|
||||
onUndoButtonClick: () => void;
|
||||
onBackButtonClick: () => void;
|
||||
onForwardButtonClick: () => void;
|
||||
onMoveItemClick: (moveId: string) => void;
|
||||
onSaveButtonClick: () => void;
|
||||
onCopyButtonClick: () => void;
|
||||
}) => {
|
||||
const movePairs = useMemo(() => chunkArray(history, 2), [history]);
|
||||
const movePairs = useMemo(() => chunkArray(history, 2, firstPlayer === 'b'), [history]);
|
||||
|
||||
return (
|
||||
<div className="height-width-100">
|
||||
|
|
@ -134,8 +140,15 @@ export const PgnViewer = React.memo(
|
|||
return (
|
||||
<React.Fragment key={wMove.san + bMove?.san + currentMoveIndex}>
|
||||
<p className="move-indicator center">
|
||||
{currentMoveIndex + 1}
|
||||
{currentMoveIndex + initialMoveNumber}
|
||||
</p>
|
||||
{(firstPlayer === 'b' && bMove === undefined && currentMoveIndex === 0) && (
|
||||
<MoveItem
|
||||
san={'...'}
|
||||
isCurrentMove={false}
|
||||
onMoveItemClick={() => {}}
|
||||
/>
|
||||
)}
|
||||
<MoveItem
|
||||
san={wMove.san}
|
||||
isCurrentMove={wMove.moveId === currentMoveId}
|
||||
|
|
@ -177,11 +190,20 @@ export const PgnViewer = React.memo(
|
|||
}
|
||||
moveIndicator={
|
||||
(wMoveVarianti === 0 &&
|
||||
(firstPlayer === 'w'
|
||||
|| currentMoveIndex > 0) &&
|
||||
`${
|
||||
currentMoveIndex +
|
||||
1 +
|
||||
initialMoveNumber +
|
||||
wMoveVarianti
|
||||
}... `) ||
|
||||
(firstPlayer === 'b' &&
|
||||
currentMoveIndex === 0 &&
|
||||
`${
|
||||
currentMoveIndex +
|
||||
initialMoveNumber +
|
||||
wMoveVarianti
|
||||
}. `) ||
|
||||
null
|
||||
}
|
||||
/>
|
||||
|
|
@ -195,13 +217,14 @@ export const PgnViewer = React.memo(
|
|||
onMoveItemClick(wMove.moveId)
|
||||
}
|
||||
moveIndicator={
|
||||
(wMoveVarianti % 2 === 0 &&
|
||||
null) ||
|
||||
`${
|
||||
currentMoveIndex +
|
||||
2 +
|
||||
wMoveVarianti
|
||||
}. `
|
||||
((firstPlayer === 'w' ||
|
||||
currentMoveIndex > 0) &&
|
||||
`${
|
||||
currentMoveIndex +
|
||||
initialMoveNumber + 1 +
|
||||
wMoveVarianti
|
||||
}. `) ||
|
||||
null
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -238,9 +261,13 @@ export const PgnViewer = React.memo(
|
|||
onMoveItemClick={() =>
|
||||
onMoveItemClick(wMove.moveId)
|
||||
}
|
||||
moveIndicator={`${
|
||||
currentMoveIndex + 2 + bMoveVarianti
|
||||
}. `}
|
||||
moveIndicator={
|
||||
`${
|
||||
currentMoveIndex
|
||||
+ initialMoveNumber + 1
|
||||
+ bMoveVarianti
|
||||
}. `
|
||||
}
|
||||
/>
|
||||
{bMove && (
|
||||
<VariantMoveItem
|
||||
|
|
@ -284,6 +311,9 @@ export const PgnViewer = React.memo(
|
|||
<button onClick={() => onSaveButtonClick()}>
|
||||
<Save strokeWidth={'1px'} />
|
||||
</button>
|
||||
<button onClick={() => onCopyButtonClick()}>
|
||||
<Copy strokeWidth={'1px'} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
/* PGN Viewer */
|
||||
|
||||
.chess-study .move-item-section {
|
||||
height: 415px;
|
||||
height: 380px;
|
||||
}
|
||||
|
||||
.chess-study .button-section {
|
||||
|
|
@ -97,7 +97,7 @@
|
|||
grid-template-columns: 0.15fr 0.425fr 0.425fr;
|
||||
grid-auto-rows: minmax(30px, auto);
|
||||
width: 100%;
|
||||
max-height: 415px;
|
||||
max-height: 380px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
|
|
|
|||
21
src/main.tsx
21
src/main.tsx
|
|
@ -47,13 +47,22 @@ export default class ChessStudyPlugin extends Plugin {
|
|||
// Add command
|
||||
this.addCommand({
|
||||
id: 'insert-chess-study',
|
||||
name: 'Insert PGN-Editor at cursor position',
|
||||
name: 'Insert FEN/PGN-Editor at cursor position',
|
||||
editorCallback: (editor: Editor) => {
|
||||
const cursorPosition = editor.getCursor();
|
||||
|
||||
const onSubmit = async (pgn: string) => {
|
||||
const onSubmit = async (pgn_or_fen: string) => {
|
||||
try {
|
||||
const chess = new Chess();
|
||||
let pgn = '', fen = '';
|
||||
if (pgn_or_fen) {
|
||||
if (pgn_or_fen.includes('/')) {
|
||||
fen = pgn_or_fen.trim();
|
||||
} else {
|
||||
pgn = pgn_or_fen.trim();
|
||||
}
|
||||
}
|
||||
|
||||
const chess = (fen) ? new Chess(fen) : new Chess();
|
||||
|
||||
if (pgn) {
|
||||
//Try to parse the PGN
|
||||
|
|
@ -80,8 +89,12 @@ export default class ChessStudyPlugin extends Plugin {
|
|||
|
||||
const id = await this.dataAdapter.saveFile(chessStudyFileData);
|
||||
|
||||
const blockStr = (fen)
|
||||
? `\`\`\`chessStudy\nchessStudyId: ${id}\nfen: ${fen}\n\`\`\``
|
||||
: `\`\`\`chessStudy\nchessStudyId: ${id}\n\`\`\``;
|
||||
|
||||
editor.replaceRange(
|
||||
`\`\`\`chessStudy\nchessStudyId: ${id}\n\`\`\``,
|
||||
blockStr,
|
||||
cursorPosition
|
||||
);
|
||||
} catch (e) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue