mirror of
https://github.com/chrislicodes/obsidian-chess-study.git
synced 2026-07-22 07:50:30 +00:00
chore: minor improvements
This commit is contained in:
parent
e5381f1cf5
commit
137821c4da
14 changed files with 583 additions and 553 deletions
48
.github/workflows/release.yml
vendored
48
.github/workflows/release.yml
vendored
|
|
@ -1,34 +1,34 @@
|
||||||
name: Release Obsidian plugin
|
name: Release Obsidian plugin
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '18.x'
|
node-version: '18.x'
|
||||||
|
|
||||||
- name: Build plugin
|
- name: Build plugin
|
||||||
run: |
|
run: |
|
||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
tag="${GITHUB_REF#refs/tags/}"
|
tag="${GITHUB_REF#refs/tags/}"
|
||||||
|
|
||||||
gh release create "$tag" \
|
gh release create "$tag" \
|
||||||
--title="$tag" \
|
--title="$tag" \
|
||||||
--draft \
|
--draft \
|
||||||
main.js manifest.json styles.css
|
main.js manifest.json styles.css
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"tabWidth": 2,
|
"tabWidth": 1,
|
||||||
"proseWrap": "preserve",
|
"proseWrap": "preserve",
|
||||||
"semi": true
|
"semi": true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
README.md
19
README.md
|
|
@ -12,8 +12,9 @@ With this plugin, you can either import PGNs or simply start a fresh new game. I
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [1.0.0](#100)
|
- [1.0.0](#100)
|
||||||
- [1.1.0](#110)
|
- [1.1.0](#110)
|
||||||
|
- [1.2.0](#120)
|
||||||
- [Settings](#settings)
|
- [Settings](#settings)
|
||||||
- [Roadmap](#roadmap)
|
- [Roadmap](#roadmap)
|
||||||
- [Tools Used](#tools-used)
|
- [Tools Used](#tools-used)
|
||||||
|
|
@ -62,6 +63,14 @@ After that the PGN viewer/editor will render and you are good to go (styles are
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### 1.2.0
|
||||||
|
|
||||||
|
- [x] Add FEN start position support
|
||||||
|
- [x] Add undo button
|
||||||
|
- [x] Copy current FEN string to clipboard
|
||||||
|
|
||||||
|
Thanks to @latenitecoding for the contributions
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
Here are the available settings for a `chessStudy` code block:
|
Here are the available settings for a `chessStudy` code block:
|
||||||
|
|
@ -71,19 +80,17 @@ Here are the available settings for a `chessStudy` code block:
|
||||||
| `chessStudyId` | Valid nanoid | Valid ID for a file stored in the plugin storage |
|
| `chessStudyId` | Valid nanoid | Valid ID for a file stored in the plugin storage |
|
||||||
| `boardOrientation` | `white` \| `black` | Orientation of the board |
|
| `boardOrientation` | `white` \| `black` | Orientation of the board |
|
||||||
| `boardColor` | `green` \| `brown` | Color 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 |
|
| `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.
|
You can permanently set some settings in the [Obsidian](https://obsidian.md/) plugin settings for Obsidian Chess Study.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- [ ] Add option to export current FEN
|
|
||||||
- [ ] Add option to start from a specific position (FEN)
|
|
||||||
- [ ] Add undo button
|
|
||||||
- [ ] Add view to manage stored games
|
- [ ] Add view to manage stored games
|
||||||
- [ ] Add more styles
|
- [ ] Add more styles
|
||||||
- [ ] Add more settings
|
- [ ] Add more settings
|
||||||
|
- [ ] Support canvas view
|
||||||
|
- [ ] Mobile support
|
||||||
|
|
||||||
## Tools Used
|
## Tools Used
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { App, Modal, Setting } from 'obsidian';
|
import { App, Modal, Setting } from 'obsidian';
|
||||||
|
import { ChessString } from 'src/main';
|
||||||
|
|
||||||
export class PgnModal extends Modal {
|
export class ChessStringModal extends Modal {
|
||||||
pgn: string;
|
chessString: ChessString;
|
||||||
onSubmit: (pgn: string) => void;
|
onSubmit: (pgn: string) => void;
|
||||||
|
|
||||||
constructor(app: App, onSubmit: (pgn: string) => void) {
|
constructor(app: App, onSubmit: (pgn: string) => void) {
|
||||||
|
|
@ -13,13 +14,13 @@ export class PgnModal extends Modal {
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
|
|
||||||
contentEl.createEl('h1', {
|
contentEl.createEl('h1', {
|
||||||
text: 'Paste the full PGN (leave empty for a new game):',
|
text: 'Paste the full PGN/FEN (leave empty for a new game):',
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(contentEl).setName('PGN').addTextArea((text) =>
|
new Setting(contentEl).setName('PGN/FEN').addTextArea((text) =>
|
||||||
text
|
text
|
||||||
.onChange((value) => {
|
.onChange((value) => {
|
||||||
this.pgn = value;
|
this.chessString = value;
|
||||||
})
|
})
|
||||||
.inputEl.setCssStyles({ width: '100%', height: '250px' })
|
.inputEl.setCssStyles({ width: '100%', height: '250px' })
|
||||||
);
|
);
|
||||||
|
|
@ -30,7 +31,7 @@ export class PgnModal extends Modal {
|
||||||
.setCta()
|
.setCta()
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
this.close();
|
this.close();
|
||||||
this.onSubmit(this.pgn);
|
this.onSubmit(this.chessString);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -4,14 +4,12 @@ import ChessStudyPlugin from 'src/main';
|
||||||
export interface ChessStudyPluginSettings {
|
export interface ChessStudyPluginSettings {
|
||||||
boardOrientation: 'white' | 'black';
|
boardOrientation: 'white' | 'black';
|
||||||
boardColor: 'green' | 'brown';
|
boardColor: 'green' | 'brown';
|
||||||
fen: string;
|
|
||||||
viewComments: true | false;
|
viewComments: true | false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: ChessStudyPluginSettings = {
|
export const DEFAULT_SETTINGS: ChessStudyPluginSettings = {
|
||||||
boardOrientation: 'white',
|
boardOrientation: 'white',
|
||||||
boardColor: 'green',
|
boardColor: 'green',
|
||||||
fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
|
|
||||||
viewComments: true,
|
viewComments: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -38,9 +36,7 @@ export class SettingsTab extends PluginSettingTab {
|
||||||
dropdown
|
dropdown
|
||||||
.setValue(this.plugin.settings.boardOrientation)
|
.setValue(this.plugin.settings.boardOrientation)
|
||||||
.onChange((orientation) => {
|
.onChange((orientation) => {
|
||||||
this.plugin.settings.boardOrientation = orientation as
|
this.plugin.settings.boardOrientation = orientation as 'white' | 'black';
|
||||||
| 'white'
|
|
||||||
| 'black';
|
|
||||||
this.plugin.saveSettings();
|
this.plugin.saveSettings();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -60,32 +56,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)
|
new Setting(containerEl)
|
||||||
.setName('View Comments')
|
.setName('View Comments')
|
||||||
.setDesc('Sets the default view of the comments')
|
.setDesc('Sets the default view of the comments')
|
||||||
.addDropdown((dropdown) => {
|
.addDropdown((dropdown) => {
|
||||||
dropdown.addOption('true', 'True');
|
dropdown.addOption('true', 'True');
|
||||||
dropdown.addOption('false', 'False');
|
dropdown.addOption('false', 'False');
|
||||||
|
|
||||||
dropdown
|
dropdown
|
||||||
.setValue(this.plugin.settings.viewComments)
|
.setValue(this.plugin.settings.viewComments.toString())
|
||||||
.onChange((viewComments) => {
|
.onChange((viewComments) => {
|
||||||
this.plugin.settings.viewComments = (viewComments == true || viewComments == "true" || viewComments == "True") as true | false;
|
this.plugin.settings.viewComments = viewComments === 'true';
|
||||||
this.plugin.saveSettings();
|
this.plugin.saveSettings();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ interface AppProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GameState {
|
export interface GameState {
|
||||||
currentMove: ChessStudyMove | VariantMove;
|
currentMove: ChessStudyMove | VariantMove | null;
|
||||||
isViewOnly: boolean;
|
isViewOnly: boolean;
|
||||||
study: ChessStudyFileData;
|
study: ChessStudyFileData;
|
||||||
}
|
}
|
||||||
|
|
@ -56,16 +56,15 @@ export const ChessStudy = ({
|
||||||
dataAdapter,
|
dataAdapter,
|
||||||
}: AppProps) => {
|
}: AppProps) => {
|
||||||
// Parse Obsidian / Code Block Settings
|
// Parse Obsidian / Code Block Settings
|
||||||
const { boardColor, boardOrientation, fen, viewComments, chessStudyId } =
|
const { boardColor, boardOrientation, viewComments, chessStudyId } =
|
||||||
|
|
||||||
parseUserConfig(pluginSettings, source);
|
parseUserConfig(pluginSettings, source);
|
||||||
|
|
||||||
// Setup Chessground API
|
// Setup Chessground API
|
||||||
const [chessView, setChessView] = useState<Api | null>(null);
|
const [chessView, setChessView] = useState<Api | null>(null);
|
||||||
|
|
||||||
// Setup Chess.js API
|
// Setup Chess.js API
|
||||||
const [firstPlayer, initialMoveNumber, initialChessLogic] = useMemo(() => {
|
const [initialChessLogic, firstPlayer, initialMoveNumber] = useMemo(() => {
|
||||||
const chess = (fen) ? new Chess(fen) : new Chess();
|
const chess = new Chess(chessStudyData.rootFEN);
|
||||||
|
|
||||||
const firstPlayer = chess.turn();
|
const firstPlayer = chess.turn();
|
||||||
const initialMoveNumber = chess.moveNumber();
|
const initialMoveNumber = chess.moveNumber();
|
||||||
|
|
@ -78,16 +77,17 @@ export const ChessStudy = ({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return [firstPlayer, initialMoveNumber, chess];
|
return [chess, firstPlayer, initialMoveNumber];
|
||||||
}, [chessStudyData.moves]);
|
}, [chessStudyData.moves, chessStudyData.rootFEN]);
|
||||||
|
|
||||||
const [chessLogic, setChessLogic] = useState(initialChessLogic);
|
const [chessLogic, setChessLogic] = useState(initialChessLogic);
|
||||||
|
|
||||||
const [gameState, dispatch] = useImmerReducer<GameState, GameActions>(
|
const [gameState, dispatch] = useImmerReducer<GameState, GameActions>(
|
||||||
(draft, action) => {
|
(draft, action) => {
|
||||||
|
const hasNoMoves = draft.study.moves.length === 0;
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'DISPLAY_NEXT_MOVE_IN_HISTORY': {
|
case 'DISPLAY_NEXT_MOVE_IN_HISTORY': {
|
||||||
if (!chessView || !draft || draft.study.moves.length == 0) return draft;
|
if (!chessView || hasNoMoves) return draft;
|
||||||
|
|
||||||
displayMoveInHistory(draft, chessView, setChessLogic, {
|
displayMoveInHistory(draft, chessView, setChessLogic, {
|
||||||
offset: 1,
|
offset: 1,
|
||||||
|
|
@ -97,7 +97,7 @@ export const ChessStudy = ({
|
||||||
return draft;
|
return draft;
|
||||||
}
|
}
|
||||||
case 'DISPLAY_PREVIOUS_MOVE_IN_HISTORY': {
|
case 'DISPLAY_PREVIOUS_MOVE_IN_HISTORY': {
|
||||||
if (!chessView || !draft || draft.study.moves.length == 0) return draft;
|
if (!chessView || hasNoMoves) return draft;
|
||||||
|
|
||||||
displayMoveInHistory(draft, chessView, setChessLogic, {
|
displayMoveInHistory(draft, chessView, setChessLogic, {
|
||||||
offset: -1,
|
offset: -1,
|
||||||
|
|
@ -107,56 +107,61 @@ export const ChessStudy = ({
|
||||||
return draft;
|
return draft;
|
||||||
}
|
}
|
||||||
case 'REMOVE_LAST_MOVE_FROM_HISTORY': {
|
case 'REMOVE_LAST_MOVE_FROM_HISTORY': {
|
||||||
if (!chessView || !draft || draft.study.moves.length == 0) return draft;
|
if (!chessView || hasNoMoves) return draft;
|
||||||
|
|
||||||
let moves = draft.study.moves;
|
const moves = draft.study.moves;
|
||||||
|
|
||||||
const currentMoveId = draft.currentMove?.moveId;
|
const currentMoveId = draft.currentMove?.moveId;
|
||||||
|
|
||||||
const { variant, moveIndex } = findMoveIndex(moves, currentMoveId);
|
if (currentMoveId) {
|
||||||
|
const { variant, moveIndex } = findMoveIndex(moves, currentMoveId);
|
||||||
|
|
||||||
if (variant) {
|
if (variant) {
|
||||||
const parent = moves[variant.parentMoveIndex];
|
const parent = moves[variant.parentMoveIndex];
|
||||||
const variantMoves = parent.variants[variant.variantIndex].moves;
|
const variantMoves = parent.variants[variant.variantIndex].moves;
|
||||||
|
|
||||||
const isLastMove = moveIndex === variantMoves.length - 1;
|
const isLastMove = moveIndex === variantMoves.length - 1;
|
||||||
|
|
||||||
if (isLastMove) {
|
if (isLastMove) {
|
||||||
displayMoveInHistory(draft, chessView, setChessLogic, {
|
displayMoveInHistory(draft, chessView, setChessLogic, {
|
||||||
offset: -1,
|
offset: -1,
|
||||||
selectedMoveId: currentMoveId,
|
selectedMoveId: currentMoveId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
variantMoves.pop();
|
variantMoves.pop();
|
||||||
if (variantMoves.length == 0) {
|
if (variantMoves.length === 0) {
|
||||||
parent.variants.splice(variant.variantIndex, 1);
|
parent.variants.splice(variant.variantIndex, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLastMove) {
|
if (isLastMove) {
|
||||||
draft.currentMove = (variantMoves.length > 0) ? variantMoves[variantMoves.length - 1] : moves[variant.parentMoveIndex];
|
draft.currentMove =
|
||||||
}
|
variantMoves.length > 0
|
||||||
} else {
|
? variantMoves[variantMoves.length - 1]
|
||||||
const isLastMove = moveIndex === moves.length - 1;
|
: moves[variant.parentMoveIndex];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const isLastMove = moveIndex === moves.length - 1;
|
||||||
|
|
||||||
if (isLastMove) {
|
if (isLastMove) {
|
||||||
displayMoveInHistory(draft, chessView, setChessLogic, {
|
displayMoveInHistory(draft, chessView, setChessLogic, {
|
||||||
offset: -1,
|
offset: -1,
|
||||||
selectedMoveId: currentMoveId,
|
selectedMoveId: currentMoveId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
moves.pop();
|
moves.pop();
|
||||||
|
|
||||||
if (isLastMove) {
|
if (isLastMove) {
|
||||||
draft.currentMove = (moves.length > 0) ? moves[moves.length - 1] : null;
|
draft.currentMove = moves.length > 0 ? moves[moves.length - 1] : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return draft;
|
return draft;
|
||||||
}
|
}
|
||||||
case 'DISPLAY_SELECTED_MOVE_IN_HISTORY': {
|
case 'DISPLAY_SELECTED_MOVE_IN_HISTORY': {
|
||||||
if (!chessView || !draft || draft.study.moves.length == 0) return draft;
|
if (!chessView || hasNoMoves) return draft;
|
||||||
|
|
||||||
const selectedMoveId = action.moveId;
|
const selectedMoveId = action.moveId;
|
||||||
|
|
||||||
|
|
@ -168,22 +173,26 @@ export const ChessStudy = ({
|
||||||
return draft;
|
return draft;
|
||||||
}
|
}
|
||||||
case 'SYNC_SHAPES': {
|
case 'SYNC_SHAPES': {
|
||||||
if (!chessView || !draft || draft.study.moves.length == 0) return draft;
|
if (!chessView || hasNoMoves) return draft;
|
||||||
|
|
||||||
const move = getCurrentMove(draft);
|
const move = getCurrentMove(draft);
|
||||||
|
|
||||||
move.shapes = action.shapes;
|
if (move) {
|
||||||
draft.currentMove = move;
|
move.shapes = action.shapes;
|
||||||
|
draft.currentMove = move;
|
||||||
|
}
|
||||||
|
|
||||||
return draft;
|
return draft;
|
||||||
}
|
}
|
||||||
case 'SYNC_COMMENT': {
|
case 'SYNC_COMMENT': {
|
||||||
if (!chessView || !draft || draft.study.moves.length == 0) return draft;
|
if (!chessView || hasNoMoves) return draft;
|
||||||
|
|
||||||
const move = getCurrentMove(draft);
|
const move = getCurrentMove(draft);
|
||||||
|
|
||||||
move.comment = action.comment;
|
if (move) {
|
||||||
draft.currentMove = move;
|
move.comment = action.comment;
|
||||||
|
draft.currentMove = move;
|
||||||
|
}
|
||||||
|
|
||||||
return draft;
|
return draft;
|
||||||
}
|
}
|
||||||
|
|
@ -193,80 +202,94 @@ export const ChessStudy = ({
|
||||||
const moves = draft.study.moves;
|
const moves = draft.study.moves;
|
||||||
const currentMoveId = draft.currentMove?.moveId;
|
const currentMoveId = draft.currentMove?.moveId;
|
||||||
|
|
||||||
const currentMoveIndex = moves.findIndex(
|
|
||||||
(move) => move.moveId === currentMoveId
|
|
||||||
);
|
|
||||||
|
|
||||||
const { variant, moveIndex } = findMoveIndex(moves, currentMoveId);
|
|
||||||
const moveId = nanoid();
|
const moveId = nanoid();
|
||||||
|
|
||||||
if (variant) {
|
if (currentMoveId) {
|
||||||
//handle variant
|
const currentMoveIndex = moves.findIndex(
|
||||||
const parent = moves[variant.parentMoveIndex];
|
(move) => move.moveId === currentMoveId
|
||||||
const variantMoves = parent.variants[variant.variantIndex].moves;
|
);
|
||||||
|
|
||||||
const isLastMove = moveIndex === variantMoves.length - 1;
|
const { variant, moveIndex } = findMoveIndex(moves, currentMoveId);
|
||||||
|
|
||||||
//Only push if its the last move in the variant because depth can only be 1
|
if (variant) {
|
||||||
if (isLastMove) {
|
//handle variant
|
||||||
const move = {
|
const parent = moves[variant.parentMoveIndex];
|
||||||
...newMove,
|
const variantMoves = parent.variants[variant.variantIndex].moves;
|
||||||
moveId: moveId,
|
|
||||||
shapes: [],
|
|
||||||
comment: null,
|
|
||||||
};
|
|
||||||
variantMoves.push(move);
|
|
||||||
|
|
||||||
const tempChess = new Chess(newMove.after);
|
const isLastMove = moveIndex === variantMoves.length - 1;
|
||||||
|
|
||||||
draft.currentMove = move;
|
//Only push if its the last move in the variant because depth can only be 1
|
||||||
|
if (isLastMove) {
|
||||||
|
const move = {
|
||||||
|
...newMove,
|
||||||
|
moveId: moveId,
|
||||||
|
shapes: [],
|
||||||
|
comment: null,
|
||||||
|
};
|
||||||
|
variantMoves.push(move);
|
||||||
|
|
||||||
chessView?.set({
|
const tempChess = new Chess(newMove.after);
|
||||||
fen: newMove.after,
|
|
||||||
check: tempChess.isCheck(),
|
draft.currentMove = move;
|
||||||
});
|
|
||||||
|
chessView?.set({
|
||||||
|
fen: newMove.after,
|
||||||
|
check: tempChess.isCheck(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//handle main line
|
||||||
|
const isLastMove = currentMoveIndex === moves.length - 1;
|
||||||
|
|
||||||
|
if (isLastMove) {
|
||||||
|
const move = {
|
||||||
|
...newMove,
|
||||||
|
moveId: moveId,
|
||||||
|
variants: [],
|
||||||
|
shapes: [],
|
||||||
|
comment: null,
|
||||||
|
};
|
||||||
|
moves.push(move);
|
||||||
|
|
||||||
|
draft.currentMove = move;
|
||||||
|
} else {
|
||||||
|
const currentMove = moves[moveIndex];
|
||||||
|
|
||||||
|
// check if the next move is the same move
|
||||||
|
const nextMove = moves[moveIndex + 1];
|
||||||
|
|
||||||
|
if (nextMove.san === newMove.san) {
|
||||||
|
draft.currentMove = nextMove;
|
||||||
|
return draft;
|
||||||
|
}
|
||||||
|
|
||||||
|
const move = {
|
||||||
|
...newMove,
|
||||||
|
moveId: moveId,
|
||||||
|
shapes: [],
|
||||||
|
comment: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
currentMove.variants.push({
|
||||||
|
parentMoveId: currentMove.moveId,
|
||||||
|
variantId: nanoid(),
|
||||||
|
moves: [move],
|
||||||
|
});
|
||||||
|
|
||||||
|
draft.currentMove = move;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//handle main line
|
const move = {
|
||||||
const isLastMove = currentMoveIndex === moves.length - 1;
|
...newMove,
|
||||||
|
moveId: moveId,
|
||||||
|
variants: [],
|
||||||
|
shapes: [],
|
||||||
|
comment: null,
|
||||||
|
};
|
||||||
|
moves.push(move);
|
||||||
|
|
||||||
if (isLastMove) {
|
draft.currentMove = move;
|
||||||
const move = {
|
|
||||||
...newMove,
|
|
||||||
moveId: moveId,
|
|
||||||
variants: [],
|
|
||||||
shapes: [],
|
|
||||||
comment: null,
|
|
||||||
};
|
|
||||||
moves.push(move);
|
|
||||||
|
|
||||||
draft.currentMove = move;
|
|
||||||
} else {
|
|
||||||
const currentMove = moves[moveIndex];
|
|
||||||
|
|
||||||
// check if the next move is the same move
|
|
||||||
const nextMove = moves[moveIndex + 1];
|
|
||||||
|
|
||||||
if (nextMove.san === newMove.san) {
|
|
||||||
draft.currentMove = nextMove;
|
|
||||||
return draft;
|
|
||||||
}
|
|
||||||
|
|
||||||
const move = {
|
|
||||||
...newMove,
|
|
||||||
moveId: moveId,
|
|
||||||
shapes: [],
|
|
||||||
comment: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
currentMove.variants.push({
|
|
||||||
parentMoveId: currentMove.moveId,
|
|
||||||
variantId: nanoid(),
|
|
||||||
moves: [move],
|
|
||||||
});
|
|
||||||
|
|
||||||
draft.currentMove = move;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return draft;
|
return draft;
|
||||||
|
|
@ -276,7 +299,7 @@ export const ChessStudy = ({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
currentMove: chessStudyData.moves[chessStudyData.moves.length - 1],
|
currentMove: chessStudyData.moves[chessStudyData.moves.length - 1] ?? null,
|
||||||
isViewOnly: false,
|
isViewOnly: false,
|
||||||
study: chessStudyData,
|
study: chessStudyData,
|
||||||
}
|
}
|
||||||
|
|
@ -310,14 +333,14 @@ export const ChessStudy = ({
|
||||||
syncShapes={(shapes: DrawShape[]) =>
|
syncShapes={(shapes: DrawShape[]) =>
|
||||||
dispatch({ type: 'SYNC_SHAPES', shapes })
|
dispatch({ type: 'SYNC_SHAPES', shapes })
|
||||||
}
|
}
|
||||||
shapes={gameState.currentMove?.shapes}
|
shapes={gameState.currentMove?.shapes || []}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pgn-container">
|
<div className="pgn-container">
|
||||||
<PgnViewer
|
<PgnViewer
|
||||||
history={gameState.study.moves}
|
history={gameState.study.moves}
|
||||||
currentMoveId={gameState.currentMove?.moveId}
|
currentMoveId={gameState.currentMove?.moveId ?? null}
|
||||||
|
|
||||||
firstPlayer={firstPlayer}
|
firstPlayer={firstPlayer}
|
||||||
initialMoveNumber={initialMoveNumber}
|
initialMoveNumber={initialMoveNumber}
|
||||||
onUndoButtonClick={() =>
|
onUndoButtonClick={() =>
|
||||||
|
|
@ -338,7 +361,7 @@ export const ChessStudy = ({
|
||||||
onSaveButtonClick={onSaveButtonClick}
|
onSaveButtonClick={onSaveButtonClick}
|
||||||
onCopyButtonClick={() => {
|
onCopyButtonClick={() => {
|
||||||
try {
|
try {
|
||||||
navigator.clipboard.writeText(chessLogic.fen())
|
navigator.clipboard.writeText(chessLogic.fen());
|
||||||
new Notice('Copied to clipboard!');
|
new Notice('Copied to clipboard!');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
new Notice('Could not copy to clipboard:', e);
|
new Notice('Could not copy to clipboard:', e);
|
||||||
|
|
@ -350,7 +373,7 @@ export const ChessStudy = ({
|
||||||
{viewComments && (
|
{viewComments && (
|
||||||
<div className="CommentSection">
|
<div className="CommentSection">
|
||||||
<CommentSection
|
<CommentSection
|
||||||
currentComment={gameState.currentMove?.comment}
|
currentComment={gameState.currentMove?.comment ?? null}
|
||||||
setComments={(comment: JSONContent) =>
|
setComments={(comment: JSONContent) =>
|
||||||
dispatch({ type: 'SYNC_COMMENT', comment: comment })
|
dispatch({ type: 'SYNC_COMMENT', comment: comment })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
src/components/react/PgnViewer/Controls/index.tsx
Normal file
36
src/components/react/PgnViewer/Controls/index.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { ArrowLeft, ArrowRight, Copy, Save, Undo2 } from 'lucide-react';
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export interface ControlActions {
|
||||||
|
onUndoButtonClick: () => void;
|
||||||
|
onBackButtonClick: () => void;
|
||||||
|
onForwardButtonClick: () => void;
|
||||||
|
onSaveButtonClick: () => void;
|
||||||
|
onCopyButtonClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Controls = (props: ControlActions) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="button-section">
|
||||||
|
<button onClick={() => props.onBackButtonClick()}>
|
||||||
|
<ArrowLeft />
|
||||||
|
</button>
|
||||||
|
<button onClick={() => props.onForwardButtonClick()}>
|
||||||
|
<ArrowRight />
|
||||||
|
</button>
|
||||||
|
<button onClick={() => props.onSaveButtonClick()}>
|
||||||
|
<Save strokeWidth={'1px'} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="button-section">
|
||||||
|
<button onClick={() => props.onCopyButtonClick()}>
|
||||||
|
<Copy strokeWidth={'1px'} />
|
||||||
|
</button>
|
||||||
|
<button onClick={() => props.onUndoButtonClick()}>
|
||||||
|
<Undo2 />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
74
src/components/react/PgnViewer/MoveItems/index.tsx
Normal file
74
src/components/react/PgnViewer/MoveItems/index.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export const MoveItem = ({
|
||||||
|
isCurrentMove,
|
||||||
|
san,
|
||||||
|
onMoveItemClick,
|
||||||
|
}: {
|
||||||
|
isCurrentMove: boolean;
|
||||||
|
san: string;
|
||||||
|
onMoveItemClick: () => void;
|
||||||
|
}) => {
|
||||||
|
const ref = React.useRef<HTMLParagraphElement>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (ref.current && isCurrentMove) {
|
||||||
|
ref.current?.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'nearest',
|
||||||
|
inline: 'end',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isCurrentMove]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
className={`move-item ${(isCurrentMove && 'active') || ''} vertical-align`}
|
||||||
|
ref={ref}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onMoveItemClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{san}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VariantMoveItem = ({
|
||||||
|
isCurrentMove,
|
||||||
|
san,
|
||||||
|
onMoveItemClick,
|
||||||
|
moveIndicator = null,
|
||||||
|
}: {
|
||||||
|
isCurrentMove: boolean;
|
||||||
|
san: string;
|
||||||
|
onMoveItemClick: () => void;
|
||||||
|
moveIndicator?: string | null;
|
||||||
|
}) => {
|
||||||
|
const ref = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (ref.current && isCurrentMove) {
|
||||||
|
ref.current?.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'nearest',
|
||||||
|
inline: 'end',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isCurrentMove]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`variant-move-item ${(isCurrentMove && 'active') || ''}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onMoveItemClick();
|
||||||
|
}}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<span className={'variant-move-indicator'}>{moveIndicator}</span>
|
||||||
|
{san}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import { ArrowLeft, ArrowRight, Save, Undo2 } from 'lucide-react';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ReactNode, useEffect, useMemo, useRef } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { ChessStudyMove } from 'src/lib/storage';
|
import { ChessStudyMove } from 'src/lib/storage';
|
||||||
|
import { Controls } from './Controls';
|
||||||
|
import { MoveItem, VariantMoveItem } from './MoveItems';
|
||||||
|
|
||||||
const chunkArray = <T,>(array: T[], chunkSize: number, offsetByOne: boolean = false) => {
|
const chunkArray = <T,>(array: T[], chunkSize: number, offsetByOne = false) => {
|
||||||
return array.reduce((resultArray, item, index) => {
|
return array.reduce((resultArray, item, index) => {
|
||||||
const chunkIndex = Math.floor((index + ((offsetByOne) ? 1 : 0)) / chunkSize);
|
const chunkIndex = Math.floor((index + (offsetByOne ? 1 : 0)) / chunkSize);
|
||||||
|
|
||||||
if (!resultArray[chunkIndex]) {
|
if (!resultArray[chunkIndex]) {
|
||||||
resultArray[chunkIndex] = [];
|
resultArray[chunkIndex] = [];
|
||||||
|
|
@ -17,307 +18,187 @@ const chunkArray = <T,>(array: T[], chunkSize: number, offsetByOne: boolean = fa
|
||||||
}, [] as T[][]);
|
}, [] as T[][]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const MoveItem = ({
|
export const VariantMoveItemContainer = ({
|
||||||
isCurrentMove,
|
children,
|
||||||
san,
|
|
||||||
onMoveItemClick,
|
|
||||||
}: {
|
}: {
|
||||||
isCurrentMove: boolean;
|
children: React.ReactNode;
|
||||||
san: string;
|
|
||||||
onMoveItemClick: () => void;
|
|
||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLParagraphElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (ref.current && isCurrentMove) {
|
|
||||||
ref.current?.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'nearest',
|
|
||||||
inline: 'end',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isCurrentMove]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
className={`move-item ${
|
|
||||||
(isCurrentMove && 'active') || ''
|
|
||||||
} vertical-align`}
|
|
||||||
ref={ref}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onMoveItemClick();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{san}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const VariantMoveItem = ({
|
|
||||||
isCurrentMove,
|
|
||||||
san,
|
|
||||||
onMoveItemClick,
|
|
||||||
moveIndicator = null,
|
|
||||||
}: {
|
|
||||||
isCurrentMove: boolean;
|
|
||||||
san: string;
|
|
||||||
onMoveItemClick: () => void;
|
|
||||||
moveIndicator?: string | null;
|
|
||||||
}) => {
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (ref.current && isCurrentMove) {
|
|
||||||
ref.current?.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'nearest',
|
|
||||||
inline: 'end',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isCurrentMove]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`variant-move-item ${(isCurrentMove && 'active') || ''}`}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onMoveItemClick();
|
|
||||||
}}
|
|
||||||
ref={ref}
|
|
||||||
>
|
|
||||||
<span className={'variant-move-indicator'}>{moveIndicator}</span>
|
|
||||||
{san}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const VariantMoveItemContainer = ({ children }: { children: ReactNode }) => {
|
|
||||||
return <div className="variant-move-item-container">{children}</div>;
|
return <div className="variant-move-item-container">{children}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const VariantContainer = ({ children }: { children: ReactNode }) => {
|
export const VariantContainer = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
return <div className="variant-container">{children}</div>;
|
return <div className="variant-container">{children}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const VariantsContainer = ({ children }: { children: ReactNode }) => {
|
export const VariantsContainer = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
return <div className="variants-container">{children}</div>;
|
return <div className="variants-container">{children}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PgnViewer = React.memo(
|
interface PgnViewerProps {
|
||||||
({
|
history: ChessStudyMove[];
|
||||||
|
currentMoveId: string | null;
|
||||||
|
firstPlayer: string;
|
||||||
|
initialMoveNumber: number;
|
||||||
|
onMoveItemClick: (moveId: string) => void;
|
||||||
|
onUndoButtonClick: () => void;
|
||||||
|
onBackButtonClick: () => void;
|
||||||
|
onForwardButtonClick: () => void;
|
||||||
|
onSaveButtonClick: () => void;
|
||||||
|
onCopyButtonClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PgnViewer = React.memo((props: PgnViewerProps) => {
|
||||||
|
const {
|
||||||
history,
|
history,
|
||||||
currentMoveId,
|
currentMoveId,
|
||||||
firstPlayer,
|
firstPlayer,
|
||||||
initialMoveNumber,
|
initialMoveNumber,
|
||||||
onUndoButtonClick,
|
|
||||||
onBackButtonClick,
|
|
||||||
onForwardButtonClick,
|
|
||||||
onMoveItemClick,
|
onMoveItemClick,
|
||||||
onSaveButtonClick,
|
...controlActions
|
||||||
onCopyButtonClick,
|
} = props;
|
||||||
}: {
|
|
||||||
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, firstPlayer === 'b'), [history]);
|
|
||||||
|
|
||||||
return (
|
const movePairs = useMemo(
|
||||||
<div className="height-width-100">
|
() => chunkArray(history, 2, firstPlayer === 'b'),
|
||||||
<div className="move-item-section">
|
[firstPlayer, history]
|
||||||
<div className="move-item-container">
|
);
|
||||||
{movePairs.map((pair, currentMoveIndex) => {
|
|
||||||
const [wMove, bMove] = pair;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={wMove.san + bMove?.san + currentMoveIndex}>
|
<div className="height-width-100">
|
||||||
<p className="move-indicator center">
|
<div className="move-item-section">
|
||||||
{currentMoveIndex + initialMoveNumber}
|
<div className="move-item-container">
|
||||||
</p>
|
{movePairs.map((pair, currentMoveIndex) => {
|
||||||
{(firstPlayer === 'b' && bMove === undefined && currentMoveIndex === 0) && (
|
const [wMove, bMove] = pair;
|
||||||
<MoveItem
|
|
||||||
san={'...'}
|
return (
|
||||||
isCurrentMove={false}
|
<React.Fragment key={wMove.san + bMove?.san + currentMoveIndex}>
|
||||||
onMoveItemClick={() => {}}
|
<p className="move-indicator center">
|
||||||
/>
|
{currentMoveIndex + initialMoveNumber}
|
||||||
)}
|
</p>
|
||||||
|
{firstPlayer === 'b' && !bMove && currentMoveIndex === 0 && (
|
||||||
<MoveItem
|
<MoveItem
|
||||||
san={wMove.san}
|
san={'...'}
|
||||||
isCurrentMove={wMove.moveId === currentMoveId}
|
isCurrentMove={false}
|
||||||
onMoveItemClick={() => onMoveItemClick(wMove.moveId)}
|
onMoveItemClick={() => {}}
|
||||||
/>
|
/>
|
||||||
{bMove && (
|
)}
|
||||||
<MoveItem
|
<MoveItem
|
||||||
san={bMove.san}
|
san={wMove.san}
|
||||||
isCurrentMove={bMove.moveId === currentMoveId}
|
isCurrentMove={wMove.moveId === currentMoveId}
|
||||||
onMoveItemClick={() => onMoveItemClick(bMove.moveId)}
|
onMoveItemClick={() => onMoveItemClick(wMove.moveId)}
|
||||||
/>
|
/>
|
||||||
)}
|
{bMove && (
|
||||||
{!!wMove.variants.concat(bMove?.variants || []).length && (
|
<MoveItem
|
||||||
<VariantsContainer>
|
san={bMove.san}
|
||||||
{!!wMove.variants.length && (
|
isCurrentMove={bMove.moveId === currentMoveId}
|
||||||
<VariantContainer>
|
onMoveItemClick={() => onMoveItemClick(bMove.moveId)}
|
||||||
{wMove.variants.map((variant) => {
|
/>
|
||||||
return (
|
)}
|
||||||
<VariantMoveItemContainer key={variant.variantId}>
|
{!!wMove.variants.concat(bMove?.variants || []).length && (
|
||||||
{chunkArray(variant.moves, 2).map(
|
<VariantsContainer>
|
||||||
(pair, wMoveVarianti) => {
|
{!!wMove.variants.length && (
|
||||||
const [bMove, wMove] = pair;
|
<VariantContainer>
|
||||||
|
{wMove.variants.map((variant) => {
|
||||||
|
return (
|
||||||
|
<VariantMoveItemContainer key={variant.variantId}>
|
||||||
|
{chunkArray(variant.moves, 2).map((pair, wMoveVarianti) => {
|
||||||
|
const [bMove, wMove] = pair;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment
|
<React.Fragment
|
||||||
key={
|
key={bMove.san + wMove?.san + currentMoveIndex}
|
||||||
bMove.san +
|
>
|
||||||
wMove?.san +
|
<VariantMoveItem
|
||||||
currentMoveIndex
|
isCurrentMove={bMove.moveId === currentMoveId}
|
||||||
}
|
san={bMove.san}
|
||||||
>
|
onMoveItemClick={() => onMoveItemClick(bMove.moveId)}
|
||||||
<VariantMoveItem
|
moveIndicator={
|
||||||
isCurrentMove={
|
(wMoveVarianti === 0 &&
|
||||||
bMove.moveId === currentMoveId
|
(firstPlayer === 'w' || currentMoveIndex > 0) &&
|
||||||
}
|
`${
|
||||||
san={bMove.san}
|
currentMoveIndex + initialMoveNumber + wMoveVarianti
|
||||||
onMoveItemClick={() =>
|
}... `) ||
|
||||||
onMoveItemClick(bMove.moveId)
|
(firstPlayer === 'b' &&
|
||||||
}
|
currentMoveIndex === 0 &&
|
||||||
moveIndicator={
|
`${
|
||||||
(wMoveVarianti === 0 &&
|
currentMoveIndex + initialMoveNumber + wMoveVarianti
|
||||||
(firstPlayer === 'w'
|
}. `) ||
|
||||||
|| currentMoveIndex > 0) &&
|
null
|
||||||
`${
|
}
|
||||||
currentMoveIndex +
|
/>
|
||||||
initialMoveNumber +
|
{wMove && (
|
||||||
wMoveVarianti
|
<VariantMoveItem
|
||||||
}... `) ||
|
isCurrentMove={wMove.moveId === currentMoveId}
|
||||||
(firstPlayer === 'b' &&
|
san={wMove.san}
|
||||||
currentMoveIndex === 0 &&
|
onMoveItemClick={() => onMoveItemClick(wMove.moveId)}
|
||||||
`${
|
moveIndicator={
|
||||||
currentMoveIndex +
|
((firstPlayer === 'w' || currentMoveIndex > 0) &&
|
||||||
initialMoveNumber +
|
|
||||||
wMoveVarianti
|
|
||||||
}. `) ||
|
|
||||||
null
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{wMove && (
|
|
||||||
<VariantMoveItem
|
|
||||||
isCurrentMove={
|
|
||||||
wMove.moveId === currentMoveId
|
|
||||||
}
|
|
||||||
san={wMove.san}
|
|
||||||
onMoveItemClick={() =>
|
|
||||||
onMoveItemClick(wMove.moveId)
|
|
||||||
}
|
|
||||||
moveIndicator={
|
|
||||||
((firstPlayer === 'w' ||
|
|
||||||
currentMoveIndex > 0) &&
|
|
||||||
`${
|
|
||||||
currentMoveIndex +
|
|
||||||
initialMoveNumber + 1 +
|
|
||||||
wMoveVarianti
|
|
||||||
}. `) ||
|
|
||||||
null
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</VariantMoveItemContainer>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</VariantContainer>
|
|
||||||
)}
|
|
||||||
{!!bMove?.variants.length && (
|
|
||||||
<VariantContainer>
|
|
||||||
{bMove.variants.map((variant) => {
|
|
||||||
return (
|
|
||||||
<VariantMoveItemContainer key={variant.variantId}>
|
|
||||||
{chunkArray(variant.moves, 2).map(
|
|
||||||
(pair, bMoveVarianti) => {
|
|
||||||
const [wMove, bMove] = pair;
|
|
||||||
return (
|
|
||||||
<React.Fragment
|
|
||||||
key={
|
|
||||||
wMove.san +
|
|
||||||
bMove?.san +
|
|
||||||
currentMoveIndex
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<VariantMoveItem
|
|
||||||
isCurrentMove={
|
|
||||||
wMove.moveId === currentMoveId
|
|
||||||
}
|
|
||||||
san={wMove.san}
|
|
||||||
onMoveItemClick={() =>
|
|
||||||
onMoveItemClick(wMove.moveId)
|
|
||||||
}
|
|
||||||
moveIndicator={
|
|
||||||
`${
|
`${
|
||||||
currentMoveIndex
|
currentMoveIndex + initialMoveNumber + 1 + wMoveVarianti
|
||||||
+ initialMoveNumber + 1
|
}. `) ||
|
||||||
+ bMoveVarianti
|
null
|
||||||
}. `
|
}
|
||||||
}
|
/>
|
||||||
/>
|
)}
|
||||||
{bMove && (
|
</React.Fragment>
|
||||||
<VariantMoveItem
|
);
|
||||||
isCurrentMove={
|
})}
|
||||||
bMove.moveId === currentMoveId
|
</VariantMoveItemContainer>
|
||||||
}
|
);
|
||||||
san={bMove.san}
|
})}
|
||||||
onMoveItemClick={() =>
|
</VariantContainer>
|
||||||
onMoveItemClick(bMove.moveId)
|
)}
|
||||||
}
|
{!!bMove?.variants.length && (
|
||||||
/>
|
<VariantContainer>
|
||||||
)}
|
{bMove.variants.map((variant) => {
|
||||||
</React.Fragment>
|
return (
|
||||||
);
|
<VariantMoveItemContainer key={variant.variantId}>
|
||||||
}
|
{chunkArray(variant.moves, 2).map((pair, bMoveVarianti) => {
|
||||||
)}
|
const [wMove, bMove] = pair;
|
||||||
</VariantMoveItemContainer>
|
return (
|
||||||
);
|
<React.Fragment
|
||||||
})}
|
key={wMove.san + bMove?.san + currentMoveIndex}
|
||||||
</VariantContainer>
|
>
|
||||||
)}
|
<VariantMoveItem
|
||||||
</VariantsContainer>
|
isCurrentMove={wMove.moveId === currentMoveId}
|
||||||
)}
|
san={wMove.san}
|
||||||
</React.Fragment>
|
onMoveItemClick={() => onMoveItemClick(wMove.moveId)}
|
||||||
);
|
moveIndicator={`${
|
||||||
})}
|
currentMoveIndex + initialMoveNumber + 1 + bMoveVarianti
|
||||||
</div>
|
}. `}
|
||||||
</div>
|
/>
|
||||||
<div className="button-section">
|
{bMove && (
|
||||||
<button onClick={() => onUndoButtonClick()}>
|
<VariantMoveItem
|
||||||
<Undo2 />
|
isCurrentMove={bMove.moveId === currentMoveId}
|
||||||
</button>
|
san={bMove.san}
|
||||||
<button onClick={() => onBackButtonClick()}>
|
onMoveItemClick={() => onMoveItemClick(bMove.moveId)}
|
||||||
<ArrowLeft />
|
/>
|
||||||
</button>
|
)}
|
||||||
<button onClick={() => onForwardButtonClick()}>
|
</React.Fragment>
|
||||||
<ArrowRight />
|
);
|
||||||
</button>
|
})}
|
||||||
</div>
|
</VariantMoveItemContainer>
|
||||||
<div className="button-section">
|
);
|
||||||
<button onClick={() => onSaveButtonClick()}>
|
})}
|
||||||
<Save strokeWidth={'1px'} />
|
</VariantContainer>
|
||||||
</button>
|
)}
|
||||||
<button onClick={() => onCopyButtonClick()}>
|
</VariantsContainer>
|
||||||
<Copy strokeWidth={'1px'} />
|
)}
|
||||||
</button>
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
<Controls {...controlActions} />
|
||||||
}
|
</div>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
PgnViewer.displayName = 'PgnViewer';
|
PgnViewer.displayName = 'PgnViewer';
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,9 @@ import { Move } from 'chess.js';
|
||||||
import { DrawShape } from 'chessground/draw';
|
import { DrawShape } from 'chessground/draw';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { DataAdapter, normalizePath } from 'obsidian';
|
import { DataAdapter, normalizePath } from 'obsidian';
|
||||||
|
import { ROOT_FEN } from 'src/main';
|
||||||
|
|
||||||
export const CURRENT_STORAGE_VERSION = '0.0.1';
|
export const CURRENT_STORAGE_VERSION = '0.0.2';
|
||||||
|
|
||||||
export interface VariantMove extends Move {
|
|
||||||
moveId: string;
|
|
||||||
shapes: DrawShape[];
|
|
||||||
comment: JSONContent | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Variant {
|
export interface Variant {
|
||||||
variantId: string;
|
variantId: string;
|
||||||
|
|
@ -18,6 +13,12 @@ export interface Variant {
|
||||||
moves: VariantMove[];
|
moves: VariantMove[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VariantMove extends Move {
|
||||||
|
moveId: string;
|
||||||
|
shapes: DrawShape[];
|
||||||
|
comment: JSONContent | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChessStudyMove extends Move {
|
export interface ChessStudyMove extends Move {
|
||||||
moveId: string;
|
moveId: string;
|
||||||
variants: Variant[];
|
variants: Variant[];
|
||||||
|
|
@ -29,6 +30,7 @@ export interface ChessStudyFileData {
|
||||||
version: string;
|
version: string;
|
||||||
header: { title: string | null };
|
header: { title: string | null };
|
||||||
moves: ChessStudyMove[];
|
moves: ChessStudyMove[];
|
||||||
|
rootFEN: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ChessStudyDataAdapter {
|
export class ChessStudyDataAdapter {
|
||||||
|
|
@ -42,6 +44,13 @@ export class ChessStudyDataAdapter {
|
||||||
|
|
||||||
async saveFile(data: ChessStudyFileData, id?: string) {
|
async saveFile(data: ChessStudyFileData, id?: string) {
|
||||||
const chessStudyId = id || nanoid();
|
const chessStudyId = id || nanoid();
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Writing file to ${normalizePath(
|
||||||
|
`${this.storagePath}/${chessStudyId}.json`
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
await this.adapter.write(
|
await this.adapter.write(
|
||||||
normalizePath(`${this.storagePath}/${chessStudyId}.json`),
|
normalizePath(`${this.storagePath}/${chessStudyId}.json`),
|
||||||
JSON.stringify(data, null, 2),
|
JSON.stringify(data, null, 2),
|
||||||
|
|
@ -52,16 +61,29 @@ export class ChessStudyDataAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadFile(id: string): Promise<ChessStudyFileData> {
|
async loadFile(id: string): Promise<ChessStudyFileData> {
|
||||||
|
console.log(
|
||||||
|
`Reading file from ${normalizePath(`${this.storagePath}/${id}.json`)}`
|
||||||
|
);
|
||||||
|
|
||||||
const data = await this.adapter.read(
|
const data = await this.adapter.read(
|
||||||
normalizePath(`${this.storagePath}/${id}.json`)
|
normalizePath(`${this.storagePath}/${id}.json`)
|
||||||
);
|
);
|
||||||
return JSON.parse(data);
|
|
||||||
|
const jsonData = JSON.parse(data);
|
||||||
|
|
||||||
|
//Make sure data is compatible with storage version 0.0.1.
|
||||||
|
if (!jsonData.rootFEN) {
|
||||||
|
return { ...jsonData, rootFEN: ROOT_FEN };
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createStorageFolderIfNotExists() {
|
async createStorageFolderIfNotExists() {
|
||||||
const folderExists = await this.adapter.exists(this.storagePath);
|
const folderExists = await this.adapter.exists(this.storagePath);
|
||||||
|
|
||||||
if (!folderExists) {
|
if (!folderExists) {
|
||||||
|
console.log(`Creating storage folder at: ${this.storagePath}`);
|
||||||
this.adapter.mkdir(this.storagePath);
|
this.adapter.mkdir(this.storagePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ export const findMoveIndex = (
|
||||||
if (move.moveId === moveId) return { variant: null, moveIndex: iMainLine };
|
if (move.moveId === moveId) return { variant: null, moveIndex: iMainLine };
|
||||||
|
|
||||||
for (const [iVariant, variant] of move.variants.entries()) {
|
for (const [iVariant, variant] of move.variants.entries()) {
|
||||||
const moveIndex = variant.moves.findIndex(
|
const moveIndex = variant.moves.findIndex((move) => move.moveId === moveId);
|
||||||
(move) => move.moveId === moveId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moveIndex >= 0)
|
if (moveIndex >= 0)
|
||||||
return {
|
return {
|
||||||
|
|
@ -48,64 +46,74 @@ export const displayMoveInHistory = (
|
||||||
|
|
||||||
//Figure out where we are
|
//Figure out where we are
|
||||||
const currentMove = draft.currentMove;
|
const currentMove = draft.currentMove;
|
||||||
const currentMoveId = currentMove.moveId;
|
|
||||||
|
|
||||||
const moves = draft.study.moves;
|
if (currentMove) {
|
||||||
|
const currentMoveId = currentMove.moveId;
|
||||||
|
|
||||||
//If we pass a moveId, find out where that is and offset from there, otherwise take current moveId
|
const moves = draft.study.moves;
|
||||||
const baseMoveId = selectedMoveId || currentMoveId;
|
|
||||||
|
|
||||||
const { variant, moveIndex } = findMoveIndex(moves, baseMoveId);
|
//If we pass a moveId, find out where that is and offset from there, otherwise take current moveId
|
||||||
//Are we in a variant? Are we not? Decide which move to display
|
const baseMoveId = selectedMoveId || currentMoveId;
|
||||||
|
|
||||||
if (variant) {
|
const { variant, moveIndex } = findMoveIndex(moves, baseMoveId);
|
||||||
const variantMoves =
|
//Are we in a variant? Are we not? Decide which move to display
|
||||||
moves[variant.parentMoveIndex].variants[variant.variantIndex].moves;
|
|
||||||
|
|
||||||
if (typeof variantMoves[moveIndex + offset] !== 'undefined') {
|
if (variant) {
|
||||||
moveToDisplay = variantMoves[moveIndex + offset];
|
const variantMoves =
|
||||||
|
moves[variant.parentMoveIndex].variants[variant.variantIndex].moves;
|
||||||
|
|
||||||
|
if (typeof variantMoves[moveIndex + offset] !== 'undefined') {
|
||||||
|
moveToDisplay = variantMoves[moveIndex + offset];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof moves[moveIndex + offset] !== 'undefined') {
|
||||||
|
moveToDisplay = moves[moveIndex + offset];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (typeof moves[moveIndex + offset] !== 'undefined') {
|
if (!moveToDisplay) {
|
||||||
moveToDisplay = moves[moveIndex + offset];
|
console.log(`No move to display found`);
|
||||||
|
return draft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chess = new Chess(moveToDisplay.after);
|
||||||
|
|
||||||
|
chessView.set({
|
||||||
|
fen: moveToDisplay.after,
|
||||||
|
check: chess.isCheck(),
|
||||||
|
movable: {
|
||||||
|
free: false,
|
||||||
|
color: toColor(chess),
|
||||||
|
dests: toDests(chess),
|
||||||
|
},
|
||||||
|
turnColor: toColor(chess),
|
||||||
|
});
|
||||||
|
|
||||||
|
draft.currentMove = moveToDisplay;
|
||||||
|
|
||||||
|
setChessLogic(chess);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!moveToDisplay) return draft;
|
|
||||||
|
|
||||||
const chess = new Chess(moveToDisplay.after);
|
|
||||||
|
|
||||||
chessView.set({
|
|
||||||
fen: moveToDisplay.after,
|
|
||||||
check: chess.isCheck(),
|
|
||||||
movable: {
|
|
||||||
free: false,
|
|
||||||
color: toColor(chess),
|
|
||||||
dests: toDests(chess),
|
|
||||||
},
|
|
||||||
turnColor: toColor(chess),
|
|
||||||
});
|
|
||||||
|
|
||||||
draft.currentMove = moveToDisplay;
|
|
||||||
|
|
||||||
setChessLogic(chess);
|
|
||||||
|
|
||||||
return draft;
|
return draft;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCurrentMove = (
|
export const getCurrentMove = (
|
||||||
draft: Draft<GameState>
|
draft: Draft<GameState>
|
||||||
): Draft<ChessStudyMove> | Draft<VariantMove> => {
|
): Draft<ChessStudyMove> | Draft<VariantMove> | null => {
|
||||||
const currentMoveId = draft.currentMove?.moveId;
|
const currentMoveId = draft.currentMove?.moveId;
|
||||||
const moves = draft.study.moves;
|
const moves = draft.study.moves;
|
||||||
|
|
||||||
const { variant, moveIndex } = findMoveIndex(moves, currentMoveId);
|
if (currentMoveId) {
|
||||||
|
const { variant, moveIndex } = findMoveIndex(moves, currentMoveId);
|
||||||
|
|
||||||
if (variant) {
|
if (variant) {
|
||||||
return moves[variant.parentMoveIndex].variants[variant.variantIndex].moves[
|
return moves[variant.parentMoveIndex].variants[variant.variantIndex].moves[
|
||||||
moveIndex
|
moveIndex
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
return moves[moveIndex];
|
return moves[moveIndex];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
14
src/main.css
14
src/main.css
|
|
@ -87,7 +87,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
height: 35px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Move Items */
|
/* Move Items */
|
||||||
|
|
@ -95,23 +95,23 @@
|
||||||
.chess-study .move-item-container {
|
.chess-study .move-item-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 0.15fr 0.425fr 0.425fr;
|
grid-template-columns: 0.15fr 0.425fr 0.425fr;
|
||||||
grid-auto-rows: minmax(30px, auto);
|
grid-auto-rows: minmax(35px, auto);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 380px;
|
max-height: 380px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chess-study .move-item-container > * {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.chess-study .move-item {
|
.chess-study .move-item {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chess-study .move-item:hover {
|
.chess-study .move-item:hover {
|
||||||
background-color: hsl(
|
background-color: hsl(var(--accent-h), var(--accent-s), calc(var(--accent-l)));
|
||||||
var(--accent-h),
|
|
||||||
var(--accent-s),
|
|
||||||
calc(var(--accent-l))
|
|
||||||
);
|
|
||||||
color: var(--text-on-accent);
|
color: var(--text-on-accent);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
52
src/main.tsx
52
src/main.tsx
|
|
@ -6,7 +6,7 @@ import {
|
||||||
ChessStudyFileData,
|
ChessStudyFileData,
|
||||||
} from 'src/lib/storage';
|
} from 'src/lib/storage';
|
||||||
import { ReactView } from './components/ReactView';
|
import { ReactView } from './components/ReactView';
|
||||||
import { PgnModal } from './components/obsidian/PgnModal';
|
import { ChessStringModal } from './components/obsidian/ChessStringModal';
|
||||||
import {
|
import {
|
||||||
ChessStudyPluginSettings,
|
ChessStudyPluginSettings,
|
||||||
DEFAULT_SETTINGS,
|
DEFAULT_SETTINGS,
|
||||||
|
|
@ -22,6 +22,17 @@ import { nanoid } from 'nanoid';
|
||||||
import { parseUserConfig } from './lib/obsidian';
|
import { parseUserConfig } from './lib/obsidian';
|
||||||
import './main.css';
|
import './main.css';
|
||||||
|
|
||||||
|
type FEN = string;
|
||||||
|
type PGN = string;
|
||||||
|
export type ChessString = FEN | PGN;
|
||||||
|
|
||||||
|
export const ROOT_FEN =
|
||||||
|
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// 1) Allow to show the root position
|
||||||
|
// 2) Display correct move after removing the last move
|
||||||
|
|
||||||
export default class ChessStudyPlugin extends Plugin {
|
export default class ChessStudyPlugin extends Plugin {
|
||||||
settings: ChessStudyPluginSettings;
|
settings: ChessStudyPluginSettings;
|
||||||
dataAdapter: ChessStudyDataAdapter;
|
dataAdapter: ChessStudyDataAdapter;
|
||||||
|
|
@ -39,7 +50,7 @@ export default class ChessStudyPlugin extends Plugin {
|
||||||
this.storagePath
|
this.storagePath
|
||||||
);
|
);
|
||||||
|
|
||||||
this.dataAdapter.createStorageFolderIfNotExists();
|
await this.dataAdapter.createStorageFolderIfNotExists();
|
||||||
|
|
||||||
// Add settings tab
|
// Add settings tab
|
||||||
this.addSettingTab(new SettingsTab(this.app, this));
|
this.addSettingTab(new SettingsTab(this.app, this));
|
||||||
|
|
@ -51,22 +62,17 @@ export default class ChessStudyPlugin extends Plugin {
|
||||||
editorCallback: (editor: Editor) => {
|
editorCallback: (editor: Editor) => {
|
||||||
const cursorPosition = editor.getCursor();
|
const cursorPosition = editor.getCursor();
|
||||||
|
|
||||||
const onSubmit = async (pgn_or_fen: string) => {
|
const onSubmit = async (chessString: ChessString | undefined) => {
|
||||||
try {
|
try {
|
||||||
let pgn = '', fen = '';
|
const chessStringTrimmed = chessString?.trim() ?? '';
|
||||||
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();
|
const isFen = chessStringTrimmed.includes('/');
|
||||||
|
|
||||||
if (pgn) {
|
const chess = isFen ? new Chess(chessStringTrimmed) : new Chess();
|
||||||
|
|
||||||
|
if (!isFen) {
|
||||||
//Try to parse the PGN
|
//Try to parse the PGN
|
||||||
chess.loadPgn(pgn, {
|
chess.loadPgn(chessStringTrimmed, {
|
||||||
strict: false,
|
strict: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -83,18 +89,15 @@ export default class ChessStudyPlugin extends Plugin {
|
||||||
shapes: [],
|
shapes: [],
|
||||||
comment: null,
|
comment: null,
|
||||||
})),
|
})),
|
||||||
|
rootFEN: isFen ? chessStringTrimmed : ROOT_FEN,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dataAdapter.createStorageFolderIfNotExists();
|
this.dataAdapter.createStorageFolderIfNotExists();
|
||||||
|
|
||||||
const id = await this.dataAdapter.saveFile(chessStudyFileData);
|
const id = await this.dataAdapter.saveFile(chessStudyFileData);
|
||||||
|
|
||||||
const blockStr = (fen)
|
|
||||||
? `\`\`\`chessStudy\nchessStudyId: ${id}\nfen: ${fen}\n\`\`\``
|
|
||||||
: `\`\`\`chessStudy\nchessStudyId: ${id}\n\`\`\``;
|
|
||||||
|
|
||||||
editor.replaceRange(
|
editor.replaceRange(
|
||||||
blockStr,
|
`\`\`\`chessStudy\nchessStudyId: ${id}\n\`\`\``,
|
||||||
cursorPosition
|
cursorPosition
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -103,7 +106,7 @@ export default class ChessStudyPlugin extends Plugin {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
new PgnModal(this.app, onSubmit).open();
|
new ChessStringModal(this.app, onSubmit).open();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -123,14 +126,7 @@ export default class ChessStudyPlugin extends Plugin {
|
||||||
const data = await this.dataAdapter.loadFile(chessStudyId);
|
const data = await this.dataAdapter.loadFile(chessStudyId);
|
||||||
|
|
||||||
ctx.addChild(
|
ctx.addChild(
|
||||||
new ReactView(
|
new ReactView(el, source, this.app, this.settings, data, this.dataAdapter)
|
||||||
el,
|
|
||||||
source,
|
|
||||||
this.app,
|
|
||||||
this.settings,
|
|
||||||
data,
|
|
||||||
this.dataAdapter
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
new Notice(
|
new Notice(
|
||||||
|
|
|
||||||
18
styles.css
18
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue