@hackersheet/next-document-content-kifu 0.1.0-alpha.2 → 0.1.0-alpha.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.
Files changed (30) hide show
  1. package/dist/cjs/components/kifu/kifu.d.ts +1 -1
  2. package/dist/cjs/components/kifu/kifu.js +4 -21
  3. package/dist/cjs/components/shogi-player/button.d.ts +8 -0
  4. package/dist/cjs/components/shogi-player/button.js +44 -0
  5. package/dist/cjs/components/shogi-player/index.d.ts +2 -0
  6. package/dist/cjs/components/shogi-player/index.js +38 -0
  7. package/dist/cjs/components/shogi-player/moves-area.d.ts +11 -0
  8. package/dist/cjs/components/shogi-player/moves-area.js +75 -0
  9. package/dist/cjs/components/shogi-player/shogi-board-canvas.d.ts +17 -0
  10. package/dist/cjs/components/shogi-player/shogi-board-canvas.js +167 -0
  11. package/dist/cjs/components/shogi-player/shogi-hands-canvas.d.ts +16 -0
  12. package/dist/cjs/components/shogi-player/shogi-hands-canvas.js +121 -0
  13. package/dist/cjs/components/shogi-player/shogi-player.d.ts +8 -0
  14. package/dist/cjs/components/shogi-player/shogi-player.js +78 -0
  15. package/dist/cjs/index.js +3 -3
  16. package/dist/esm/components/kifu/kifu.d.mts +1 -1
  17. package/dist/esm/components/kifu/kifu.mjs +5 -22
  18. package/dist/esm/components/shogi-player/button.d.mts +8 -0
  19. package/dist/esm/components/shogi-player/button.mjs +14 -0
  20. package/dist/esm/components/shogi-player/index.d.mts +2 -0
  21. package/dist/esm/components/shogi-player/index.mjs +4 -0
  22. package/dist/esm/components/shogi-player/moves-area.d.mts +11 -0
  23. package/dist/esm/components/shogi-player/moves-area.mjs +41 -0
  24. package/dist/esm/components/shogi-player/shogi-board-canvas.d.mts +17 -0
  25. package/dist/esm/components/shogi-player/shogi-board-canvas.mjs +137 -0
  26. package/dist/esm/components/shogi-player/shogi-hands-canvas.d.mts +16 -0
  27. package/dist/esm/components/shogi-player/shogi-hands-canvas.mjs +91 -0
  28. package/dist/esm/components/shogi-player/shogi-player.d.mts +8 -0
  29. package/dist/esm/components/shogi-player/shogi-player.mjs +48 -0
  30. package/package.json +12 -12
@@ -1,6 +1,6 @@
1
1
  import { KifuComponentProps } from '@hackersheet/react-document-content';
2
2
  import React from 'react';
3
3
 
4
- declare function Kifu({ code, language }: KifuComponentProps): React.JSX.Element | null;
4
+ declare function Kifu({ code, language }: KifuComponentProps): React.JSX.Element;
5
5
 
6
6
  export { Kifu as default };
@@ -32,36 +32,19 @@ __export(kifu_exports, {
32
32
  default: () => Kifu
33
33
  });
34
34
  module.exports = __toCommonJS(kifu_exports);
35
- var import_kifu_for_js = require("kifu-for-js");
36
35
  var import_navigation = require("next/navigation");
37
36
  var import_react = __toESM(require("react"));
37
+ var import_shogi_player = require("../shogi-player");
38
38
  function Kifu({ code, language }) {
39
39
  const [, filename] = language.split(":");
40
40
  const searchParams = (0, import_navigation.useSearchParams)();
41
41
  const id = filename ? `user-content-${filename}` : void 0;
42
- const [kifuStore] = (0, import_react.useState)(() => new import_kifu_for_js.KifuStore({ kifu: code }));
43
- (0, import_react.useEffect)(() => {
44
- initKifuUserSettings();
45
- }, []);
46
42
  (0, import_react.useEffect)(() => {
47
43
  const newPly = Number(searchParams.get("ply") ?? 0);
48
44
  const hash = typeof window !== "undefined" ? window.location.hash.replace(/^#!?/, "") : "";
49
45
  if (hash === id) {
50
- kifuStore.player.goto(newPly);
46
+ console.log(newPly);
51
47
  }
52
- }, [searchParams, kifuStore, id]);
53
- if (!kifuStore) {
54
- return null;
55
- }
56
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ import_react.default.createElement(import_kifu_for_js.KifuLite, { style: { width: "100%" }, kifuStore }));
57
- }
58
- function initKifuUserSettings() {
59
- const LOCALSTORAGE_KEY = "kifuforjs";
60
- const defaultSettings = {
61
- hapticFeedback: false
62
- };
63
- const settings = localStorage.getItem(LOCALSTORAGE_KEY);
64
- if (!settings) {
65
- localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(defaultSettings));
66
- }
48
+ }, [searchParams, id]);
49
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ import_react.default.createElement(import_shogi_player.ShogiPlayer, { kifuText: code }));
67
50
  }
@@ -0,0 +1,8 @@
1
+ import React, { MouseEventHandler, PropsWithChildren } from 'react';
2
+
3
+ type ButtonProps = {
4
+ onClick?: MouseEventHandler<HTMLButtonElement>;
5
+ } & PropsWithChildren;
6
+ declare function Button({ children, onClick }: ButtonProps): React.JSX.Element;
7
+
8
+ export { type ButtonProps, Button as default };
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var button_exports = {};
30
+ __export(button_exports, {
31
+ default: () => Button
32
+ });
33
+ module.exports = __toCommonJS(button_exports);
34
+ var import_react = __toESM(require("react"));
35
+ function Button({ children, onClick }) {
36
+ return /* @__PURE__ */ import_react.default.createElement(
37
+ "button",
38
+ {
39
+ className: "border-2 text-black p-2 border-black rounded-lg hover:bg-amber-100 cursor-pointer",
40
+ onClick
41
+ },
42
+ children
43
+ );
44
+ }
@@ -0,0 +1,2 @@
1
+ export { default as ShogiPlayer } from './shogi-player.js';
2
+ import 'react';
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var shogi_player_exports = {};
30
+ __export(shogi_player_exports, {
31
+ ShogiPlayer: () => import_shogi_player.default
32
+ });
33
+ module.exports = __toCommonJS(shogi_player_exports);
34
+ var import_shogi_player = __toESM(require("./shogi-player"));
35
+ // Annotate the CommonJS export names for ESM import in node:
36
+ 0 && (module.exports = {
37
+ ShogiPlayer
38
+ });
@@ -0,0 +1,11 @@
1
+ import { IMoveFormat } from 'json-kifu-format/dist/src/Formats';
2
+ import React from 'react';
3
+
4
+ type MovesAreaProps = {
5
+ moves: IMoveFormat[];
6
+ tesuu: number;
7
+ onTesuuChange?: (tesuu: number) => void;
8
+ };
9
+ declare function MovesArea(props: MovesAreaProps): React.JSX.Element;
10
+
11
+ export { MovesArea, type MovesAreaProps };
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ "use client";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var moves_area_exports = {};
31
+ __export(moves_area_exports, {
32
+ MovesArea: () => MovesArea
33
+ });
34
+ module.exports = __toCommonJS(moves_area_exports);
35
+ var import_json_kifu_format = require("json-kifu-format");
36
+ var import_react = __toESM(require("react"));
37
+ function MovesArea(props) {
38
+ const moves = props.moves;
39
+ const scrollRef = (0, import_react.useRef)(null);
40
+ const containerRef = (0, import_react.useRef)(null);
41
+ (0, import_react.useEffect)(() => {
42
+ if (scrollRef.current && containerRef.current) {
43
+ const containerRect = containerRef.current.getBoundingClientRect();
44
+ const scrollRect = scrollRef.current.getBoundingClientRect();
45
+ const offset = scrollRect.top - containerRect.top + containerRef.current.scrollTop - 72;
46
+ containerRef.current.scrollTo({
47
+ top: offset,
48
+ behavior: "auto"
49
+ });
50
+ }
51
+ }, [props.tesuu]);
52
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "absolute overflow-y-auto h-full w-full border-2 border-black text-black", ref: containerRef }, /* @__PURE__ */ import_react.default.createElement("table", { className: "w-full text-xs", style: { margin: 0 } }, /* @__PURE__ */ import_react.default.createElement("tbody", null, 0 === props.tesuu && /* @__PURE__ */ import_react.default.createElement("tr", { ref: scrollRef }), /* @__PURE__ */ import_react.default.createElement(
53
+ "tr",
54
+ {
55
+ onClick: () => props.onTesuuChange && props.onTesuuChange(0),
56
+ className: 0 === props.tesuu ? "bg-yellow-100 cursor-pointer" : "cursor-pointer hover:bg-amber-300"
57
+ },
58
+ /* @__PURE__ */ import_react.default.createElement("th", { className: "text-right p-1 pl-0" }),
59
+ /* @__PURE__ */ import_react.default.createElement("td", { className: "p-1 pr-0" }, "\u958B\u59CB\u5C40\u9762")
60
+ ), moves.map(
61
+ (move, index) => index > 0 && /* @__PURE__ */ import_react.default.createElement(import_react.Fragment, { key: index }, index === props.tesuu && /* @__PURE__ */ import_react.default.createElement("tr", { ref: scrollRef }), /* @__PURE__ */ import_react.default.createElement(
62
+ "tr",
63
+ {
64
+ className: index === props.tesuu ? "bg-yellow-100 border-t border-black/60 cursor-pointer" : "border-t border-black/60 cursor-pointer hover:bg-amber-300",
65
+ onClick: () => props.onTesuuChange && props.onTesuuChange(index)
66
+ },
67
+ /* @__PURE__ */ import_react.default.createElement("th", { className: "text-right p-1 pl-2 w-0", style: { width: 0 } }, /* @__PURE__ */ import_react.default.createElement("div", null, index)),
68
+ /* @__PURE__ */ import_react.default.createElement("td", { className: "p-1 pr-0" }, import_json_kifu_format.JKFPlayer.moveToReadableKifu(move))
69
+ ))
70
+ ))));
71
+ }
72
+ // Annotate the CommonJS export names for ESM import in node:
73
+ 0 && (module.exports = {
74
+ MovesArea
75
+ });
@@ -0,0 +1,17 @@
1
+ import { IMoveMoveFormat } from 'json-kifu-format/dist/src/Formats';
2
+ import React from 'react';
3
+ import { Piece } from 'shogi.js';
4
+
5
+ type Props = {
6
+ size?: number;
7
+ boardColor?: string;
8
+ lineColor?: string;
9
+ fontFamily?: string;
10
+ fontSizeRatio?: number;
11
+ pieces: Piece[][];
12
+ isSente?: boolean;
13
+ currentMove?: IMoveMoveFormat;
14
+ };
15
+ declare const ShogiBoardCanvas: React.FC<Props>;
16
+
17
+ export { ShogiBoardCanvas as default };
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ "use client";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var shogi_board_canvas_exports = {};
31
+ __export(shogi_board_canvas_exports, {
32
+ default: () => shogi_board_canvas_default
33
+ });
34
+ module.exports = __toCommonJS(shogi_board_canvas_exports);
35
+ var import_json_kifu_format = require("json-kifu-format");
36
+ var import_react = __toESM(require("react"));
37
+ var import_shogi = require("shogi.js");
38
+ const getCanvasDimensions = (size) => {
39
+ const dpr = window.devicePixelRatio || 1;
40
+ return { width: size * dpr, height: size * dpr, dpr };
41
+ };
42
+ const getBoardLayout = (size) => {
43
+ const margin = size * 0.06;
44
+ const boardSize = size - margin * 2;
45
+ const cell = boardSize / 9;
46
+ return { margin, boardSize, cell };
47
+ };
48
+ const drawBackground = (ctx, size, boardColor) => {
49
+ ctx.fillStyle = boardColor;
50
+ ctx.fillRect(0, 0, size, size);
51
+ };
52
+ const drawBoard = (ctx, margin, boardSize, lineColor) => {
53
+ ctx.strokeStyle = lineColor;
54
+ ctx.lineWidth = 2;
55
+ ctx.strokeRect(margin, margin, boardSize, boardSize);
56
+ ctx.lineWidth = 1;
57
+ Array.from({ length: 8 }).forEach((_, i) => {
58
+ const offset = (i + 1) * (boardSize / 9);
59
+ ctx.beginPath();
60
+ ctx.moveTo(margin + offset, margin);
61
+ ctx.lineTo(margin + offset, margin + boardSize);
62
+ ctx.stroke();
63
+ ctx.beginPath();
64
+ ctx.moveTo(margin, margin + offset);
65
+ ctx.lineTo(margin + boardSize, margin + offset);
66
+ ctx.stroke();
67
+ });
68
+ };
69
+ const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente, currentMove) => {
70
+ ctx.textAlign = "center";
71
+ ctx.textBaseline = "middle";
72
+ pieces.forEach((row, rowIndex) => {
73
+ row.forEach((piece, colIndex) => {
74
+ if (!piece) return;
75
+ const x = isSente ? 8 - rowIndex : rowIndex;
76
+ const y = isSente ? colIndex : 8 - colIndex;
77
+ const px = margin + x * cell + cell / 2;
78
+ const py = margin + y * cell + cell / 2;
79
+ ctx.save();
80
+ ctx.translate(px, py);
81
+ if (isSente && piece.color === import_shogi.Color.White) {
82
+ ctx.rotate(Math.PI);
83
+ } else if (!isSente && piece.color === import_shogi.Color.Black) {
84
+ ctx.rotate(Math.PI);
85
+ }
86
+ const kan = import_json_kifu_format.JKFPlayer.kindToKan(piece.kind);
87
+ ctx.fillStyle = "#000";
88
+ if (currentMove) {
89
+ const to = currentMove.to;
90
+ if (to && to.x === rowIndex + 1 && to.y === colIndex + 1) {
91
+ ctx.fillStyle = "red";
92
+ }
93
+ }
94
+ if (kan.length === 2) {
95
+ const baseFontSize = cell * fontSizeRatio * 0.5;
96
+ ctx.font = `${baseFontSize}px ${fontFamily}`;
97
+ const scaleX = 2;
98
+ const scaleY = 1;
99
+ const offsetY = cell * 0.18;
100
+ ctx.save();
101
+ ctx.scale(scaleX, scaleY);
102
+ ctx.fillText(kan[0], 0 / scaleX, -offsetY / scaleY);
103
+ ctx.restore();
104
+ ctx.save();
105
+ ctx.scale(scaleX, scaleY);
106
+ ctx.fillText(kan[1], 0 / scaleX, offsetY / scaleY);
107
+ ctx.restore();
108
+ } else {
109
+ const fontSize = cell * fontSizeRatio;
110
+ ctx.font = `${fontSize}px ${fontFamily}`;
111
+ ctx.fillText(kan, 0, 0);
112
+ }
113
+ ctx.restore();
114
+ });
115
+ });
116
+ };
117
+ const drawCoordinates = (ctx, margin, boardSize, cell, fontFamily, isSente) => {
118
+ ctx.fillStyle = "#000";
119
+ ctx.font = `${cell * 0.35}px ${fontFamily}`;
120
+ ctx.textAlign = "center";
121
+ ctx.textBaseline = "middle";
122
+ Array.from({ length: 9 }).forEach((_, i) => {
123
+ const x = margin + i * cell + cell / 2;
124
+ const y = margin / 2;
125
+ const label = isSente ? (9 - i).toString() : (i + 1).toString();
126
+ ctx.fillText(label, x, y);
127
+ });
128
+ const kanji = ["\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"];
129
+ kanji.forEach((k, i) => {
130
+ const x = margin + boardSize + margin / 2;
131
+ const y = margin + i * cell + cell / 2;
132
+ const label = isSente ? k : kanji[8 - i];
133
+ ctx.fillText(label, x, y);
134
+ });
135
+ };
136
+ const ShogiBoardCanvas = ({
137
+ size = 360,
138
+ boardColor = "#f9d27a",
139
+ lineColor = "#000",
140
+ fontFamily = "serif",
141
+ fontSizeRatio = 0.7,
142
+ pieces,
143
+ isSente = true,
144
+ currentMove
145
+ }) => {
146
+ const canvasRef = import_react.default.useRef(null);
147
+ import_react.default.useEffect(() => {
148
+ const canvas = canvasRef.current;
149
+ if (!canvas) return;
150
+ const ctx = canvas.getContext("2d");
151
+ if (!ctx) return;
152
+ const { width, height, dpr } = getCanvasDimensions(size);
153
+ canvas.width = width;
154
+ canvas.height = height;
155
+ canvas.style.width = `${size}px`;
156
+ canvas.style.height = `${size}px`;
157
+ ctx.scale(dpr, dpr);
158
+ ctx.clearRect(0, 0, size, size);
159
+ const { margin, boardSize, cell } = getBoardLayout(size);
160
+ drawBackground(ctx, size, boardColor);
161
+ drawBoard(ctx, margin, boardSize, lineColor);
162
+ drawPieces(ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente, currentMove);
163
+ drawCoordinates(ctx, margin, boardSize, cell, fontFamily, isSente);
164
+ }, [size, boardColor, lineColor, fontFamily, fontSizeRatio, pieces, isSente, currentMove]);
165
+ return /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef, width: size, height: size });
166
+ };
167
+ var shogi_board_canvas_default = ShogiBoardCanvas;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { Piece } from 'shogi.js';
3
+
4
+ type Props = {
5
+ size?: number;
6
+ boardColor?: string;
7
+ lineColor?: string;
8
+ fontFamily?: string;
9
+ fontSizeRatio?: number;
10
+ hands: Piece[][];
11
+ isSente?: boolean;
12
+ isTop?: boolean;
13
+ };
14
+ declare const ShogiHandsCanvas: React.FC<Props>;
15
+
16
+ export { ShogiHandsCanvas as default };
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ "use client";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var shogi_hands_canvas_exports = {};
31
+ __export(shogi_hands_canvas_exports, {
32
+ default: () => shogi_hands_canvas_default
33
+ });
34
+ module.exports = __toCommonJS(shogi_hands_canvas_exports);
35
+ var import_json_kifu_format = require("json-kifu-format");
36
+ var import_react = __toESM(require("react"));
37
+ var import_shogi = require("shogi.js");
38
+ const getCanvasDimensions = (size) => {
39
+ const dpr = window.devicePixelRatio || 1;
40
+ return { width: size * dpr, height: size * dpr, dpr };
41
+ };
42
+ const getHandsLayout = (size) => {
43
+ const margin = size * 0.06;
44
+ const boardSize = size - margin * 2;
45
+ const cellSize = boardSize / 9;
46
+ const handsHeight = cellSize + margin + 2;
47
+ return { margin, handsHeight, boardSize, cellSize };
48
+ };
49
+ const drawBackground = (ctx, width, height, color) => {
50
+ ctx.fillStyle = color;
51
+ ctx.fillRect(0, 0, width, height);
52
+ };
53
+ const drawHandsFrame = (ctx, x, y, width, height, lineColor, isTop) => {
54
+ ctx.strokeStyle = lineColor;
55
+ ctx.lineWidth = 2;
56
+ if (isTop) {
57
+ ctx.strokeRect(x, y, width, height);
58
+ } else {
59
+ ctx.strokeRect(x, 2, width, height);
60
+ }
61
+ };
62
+ const drawCoordinates = (ctx, fontSize, fontFamily) => {
63
+ ctx.fillStyle = "#000";
64
+ ctx.font = `${fontSize}px ${fontFamily}`;
65
+ ctx.textAlign = "center";
66
+ ctx.textBaseline = "middle";
67
+ };
68
+ const drawPieces = (ctx, hands, margin, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
69
+ ctx.textAlign = "center";
70
+ ctx.textBaseline = "middle";
71
+ const pieces = isSente && isTop || !isSente && !isTop ? hands[import_shogi.Color.White] : hands[import_shogi.Color.Black];
72
+ pieces.forEach((piece, index) => {
73
+ if (!piece) return;
74
+ const px = margin + index * cell + cell / 2;
75
+ const py = isTop ? margin + cell / 2 : cell / 2 + 2;
76
+ ctx.save();
77
+ ctx.translate(px, py);
78
+ if (isSente && piece.color === import_shogi.Color.White) {
79
+ ctx.rotate(Math.PI);
80
+ } else if (!isSente && piece.color === import_shogi.Color.Black) {
81
+ ctx.rotate(Math.PI);
82
+ }
83
+ const fontSize = cell * fontSizeRatio;
84
+ const kan = import_json_kifu_format.JKFPlayer.kindToKan(piece.kind);
85
+ ctx.font = `${fontSize}px ${fontFamily}`;
86
+ ctx.fillText(kan, 0, 0);
87
+ ctx.restore();
88
+ });
89
+ };
90
+ const ShogiHandsCanvas = ({
91
+ size = 360,
92
+ boardColor = "#f9d27a",
93
+ lineColor = "#000",
94
+ fontFamily = "serif",
95
+ fontSizeRatio = 0.7,
96
+ hands,
97
+ isSente = true,
98
+ isTop = false
99
+ }) => {
100
+ const canvasRef = import_react.default.useRef(null);
101
+ import_react.default.useEffect(() => {
102
+ const canvas = canvasRef.current;
103
+ if (!canvas) return;
104
+ const ctx = canvas.getContext("2d");
105
+ if (!ctx) return;
106
+ const { margin, handsHeight, boardSize, cellSize } = getHandsLayout(size);
107
+ const { width: canvasWidth, dpr } = getCanvasDimensions(size);
108
+ canvas.width = canvasWidth;
109
+ canvas.height = handsHeight * dpr;
110
+ canvas.style.width = `${size}px`;
111
+ canvas.style.height = `${handsHeight}px`;
112
+ ctx.scale(dpr, dpr);
113
+ ctx.clearRect(0, 0, size, handsHeight);
114
+ drawBackground(ctx, size, handsHeight, boardColor);
115
+ drawHandsFrame(ctx, margin, margin, boardSize, cellSize, lineColor, isTop);
116
+ drawCoordinates(ctx, cellSize * 0.35, fontFamily);
117
+ drawPieces(ctx, hands, margin, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
118
+ }, [size, boardColor, lineColor, fontFamily, fontSizeRatio, isSente, hands, isTop]);
119
+ return /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef });
120
+ };
121
+ var shogi_hands_canvas_default = ShogiHandsCanvas;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+
3
+ type ShogiPlayerProps = {
4
+ kifuText: string;
5
+ };
6
+ declare function ShogiPlayer(props: ShogiPlayerProps): React.JSX.Element;
7
+
8
+ export { type ShogiPlayerProps, ShogiPlayer as default };
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ "use client";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var shogi_player_exports = {};
31
+ __export(shogi_player_exports, {
32
+ default: () => ShogiPlayer
33
+ });
34
+ module.exports = __toCommonJS(shogi_player_exports);
35
+ var import_json_kifu_format = require("json-kifu-format");
36
+ var import_react = __toESM(require("react"));
37
+ var import_button = __toESM(require("./button"));
38
+ var import_moves_area = require("./moves-area");
39
+ var import_shogi_board_canvas = __toESM(require("./shogi-board-canvas"));
40
+ var import_shogi_hands_canvas = __toESM(require("./shogi-hands-canvas"));
41
+ function ShogiPlayer(props) {
42
+ const [player] = (0, import_react.useState)(import_json_kifu_format.JKFPlayer.parse(props.kifuText.trim()));
43
+ const [pieces, setPieces] = (0, import_react.useState)(player.shogi.board);
44
+ const [hands, setHands] = (0, import_react.useState)(player.shogi.hands);
45
+ const [moves, setMoves] = (0, import_react.useState)(player.kifu.moves);
46
+ const [tesuu, setTesuu] = (0, import_react.useState)(player.tesuu);
47
+ const [currentMove, setCurrentMove] = (0, import_react.useState)(player.getMove());
48
+ const [isSente, setIsSente] = (0, import_react.useState)(true);
49
+ const [maxTesuu, setMaxTesuu] = (0, import_react.useState)(player.getMaxTesuu());
50
+ const [comments, setComments] = (0, import_react.useState)(player.getComments());
51
+ const size = 360;
52
+ const updateState = () => {
53
+ setPieces([...player.shogi.board]);
54
+ setHands([...player.shogi.hands]);
55
+ setMoves([...player.kifu.moves]);
56
+ setCurrentMove(player.getMove());
57
+ setMaxTesuu(player.getMaxTesuu());
58
+ setComments(player.getComments());
59
+ setTesuu(player.tesuu);
60
+ };
61
+ const handleForward = () => {
62
+ player.forward();
63
+ updateState();
64
+ };
65
+ const handleBackward = () => {
66
+ player.backward();
67
+ updateState();
68
+ };
69
+ const handleGoto = (tesuu2) => {
70
+ player.goto(tesuu2);
71
+ updateState();
72
+ };
73
+ const handeleToggle = () => {
74
+ setIsSente(!isSente);
75
+ updateState();
76
+ };
77
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "flex w-fit", tabIndex: 1 }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ import_react.default.createElement(import_shogi_hands_canvas.default, { size, hands, isSente, isTop: true }), /* @__PURE__ */ import_react.default.createElement(import_shogi_board_canvas.default, { size, pieces, isSente, currentMove }), /* @__PURE__ */ import_react.default.createElement(import_shogi_hands_canvas.default, { size, hands, isSente, isTop: false })), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col w-fit bg-[#f9d27a] p-4 gap-4" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex-1 relative w-full" }, /* @__PURE__ */ import_react.default.createElement(import_moves_area.MovesArea, { moves, tesuu, onTesuuChange: handleGoto })), /* @__PURE__ */ import_react.default.createElement("div", { className: "text-black border-black border-2 p-1 text-xs" }, comments.map((comment, index) => /* @__PURE__ */ import_react.default.createElement("div", { key: index }, comment)), comments.length === 0 && /* @__PURE__ */ import_react.default.createElement("div", null, "\xA0")), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: () => handleGoto(0) }, "\u6700\u521D"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handleBackward }, "\u524D"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handleForward }, "\u6B21"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: () => handleGoto(maxTesuu) }, "\u6700\u5F8C"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handeleToggle }, "\u53CD\u8EE2"))));
78
+ }
package/dist/cjs/index.js CHANGED
@@ -13,9 +13,9 @@ var __copyProps = (to, from, except, desc) => {
13
13
  };
14
14
  var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
15
15
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
16
- var src_exports = {};
17
- module.exports = __toCommonJS(src_exports);
18
- __reExport(src_exports, require("./components"), module.exports);
16
+ var index_exports = {};
17
+ module.exports = __toCommonJS(index_exports);
18
+ __reExport(index_exports, require("./components"), module.exports);
19
19
  // Annotate the CommonJS export names for ESM import in node:
20
20
  0 && (module.exports = {
21
21
  ...require("./components")
@@ -1,6 +1,6 @@
1
1
  import { KifuComponentProps } from '@hackersheet/react-document-content';
2
2
  import React from 'react';
3
3
 
4
- declare function Kifu({ code, language }: KifuComponentProps): React.JSX.Element | null;
4
+ declare function Kifu({ code, language }: KifuComponentProps): React.JSX.Element;
5
5
 
6
6
  export { Kifu as default };
@@ -1,36 +1,19 @@
1
1
  "use client";
2
- import { KifuLite, KifuStore } from "kifu-for-js";
3
2
  import { useSearchParams } from "next/navigation";
4
- import React, { useEffect, useState } from "react";
3
+ import React, { useEffect } from "react";
4
+ import { ShogiPlayer } from "../shogi-player";
5
5
  function Kifu({ code, language }) {
6
6
  const [, filename] = language.split(":");
7
7
  const searchParams = useSearchParams();
8
8
  const id = filename ? `user-content-${filename}` : void 0;
9
- const [kifuStore] = useState(() => new KifuStore({ kifu: code }));
10
- useEffect(() => {
11
- initKifuUserSettings();
12
- }, []);
13
9
  useEffect(() => {
14
10
  const newPly = Number(searchParams.get("ply") ?? 0);
15
11
  const hash = typeof window !== "undefined" ? window.location.hash.replace(/^#!?/, "") : "";
16
12
  if (hash === id) {
17
- kifuStore.player.goto(newPly);
13
+ console.log(newPly);
18
14
  }
19
- }, [searchParams, kifuStore, id]);
20
- if (!kifuStore) {
21
- return null;
22
- }
23
- return /* @__PURE__ */ React.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ React.createElement(KifuLite, { style: { width: "100%" }, kifuStore }));
24
- }
25
- function initKifuUserSettings() {
26
- const LOCALSTORAGE_KEY = "kifuforjs";
27
- const defaultSettings = {
28
- hapticFeedback: false
29
- };
30
- const settings = localStorage.getItem(LOCALSTORAGE_KEY);
31
- if (!settings) {
32
- localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(defaultSettings));
33
- }
15
+ }, [searchParams, id]);
16
+ return /* @__PURE__ */ React.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ React.createElement(ShogiPlayer, { kifuText: code }));
34
17
  }
35
18
  export {
36
19
  Kifu as default
@@ -0,0 +1,8 @@
1
+ import React, { MouseEventHandler, PropsWithChildren } from 'react';
2
+
3
+ type ButtonProps = {
4
+ onClick?: MouseEventHandler<HTMLButtonElement>;
5
+ } & PropsWithChildren;
6
+ declare function Button({ children, onClick }: ButtonProps): React.JSX.Element;
7
+
8
+ export { type ButtonProps, Button as default };
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ function Button({ children, onClick }) {
3
+ return /* @__PURE__ */ React.createElement(
4
+ "button",
5
+ {
6
+ className: "border-2 text-black p-2 border-black rounded-lg hover:bg-amber-100 cursor-pointer",
7
+ onClick
8
+ },
9
+ children
10
+ );
11
+ }
12
+ export {
13
+ Button as default
14
+ };
@@ -0,0 +1,2 @@
1
+ export { default as ShogiPlayer } from './shogi-player.mjs';
2
+ import 'react';
@@ -0,0 +1,4 @@
1
+ import ShogiPlayer from "./shogi-player";
2
+ export {
3
+ ShogiPlayer
4
+ };
@@ -0,0 +1,11 @@
1
+ import { IMoveFormat } from 'json-kifu-format/dist/src/Formats';
2
+ import React from 'react';
3
+
4
+ type MovesAreaProps = {
5
+ moves: IMoveFormat[];
6
+ tesuu: number;
7
+ onTesuuChange?: (tesuu: number) => void;
8
+ };
9
+ declare function MovesArea(props: MovesAreaProps): React.JSX.Element;
10
+
11
+ export { MovesArea, type MovesAreaProps };
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { JKFPlayer } from "json-kifu-format";
3
+ import React, { Fragment, useEffect, useRef } from "react";
4
+ function MovesArea(props) {
5
+ const moves = props.moves;
6
+ const scrollRef = useRef(null);
7
+ const containerRef = useRef(null);
8
+ useEffect(() => {
9
+ if (scrollRef.current && containerRef.current) {
10
+ const containerRect = containerRef.current.getBoundingClientRect();
11
+ const scrollRect = scrollRef.current.getBoundingClientRect();
12
+ const offset = scrollRect.top - containerRect.top + containerRef.current.scrollTop - 72;
13
+ containerRef.current.scrollTo({
14
+ top: offset,
15
+ behavior: "auto"
16
+ });
17
+ }
18
+ }, [props.tesuu]);
19
+ return /* @__PURE__ */ React.createElement("div", { className: "absolute overflow-y-auto h-full w-full border-2 border-black text-black", ref: containerRef }, /* @__PURE__ */ React.createElement("table", { className: "w-full text-xs", style: { margin: 0 } }, /* @__PURE__ */ React.createElement("tbody", null, 0 === props.tesuu && /* @__PURE__ */ React.createElement("tr", { ref: scrollRef }), /* @__PURE__ */ React.createElement(
20
+ "tr",
21
+ {
22
+ onClick: () => props.onTesuuChange && props.onTesuuChange(0),
23
+ className: 0 === props.tesuu ? "bg-yellow-100 cursor-pointer" : "cursor-pointer hover:bg-amber-300"
24
+ },
25
+ /* @__PURE__ */ React.createElement("th", { className: "text-right p-1 pl-0" }),
26
+ /* @__PURE__ */ React.createElement("td", { className: "p-1 pr-0" }, "\u958B\u59CB\u5C40\u9762")
27
+ ), moves.map(
28
+ (move, index) => index > 0 && /* @__PURE__ */ React.createElement(Fragment, { key: index }, index === props.tesuu && /* @__PURE__ */ React.createElement("tr", { ref: scrollRef }), /* @__PURE__ */ React.createElement(
29
+ "tr",
30
+ {
31
+ className: index === props.tesuu ? "bg-yellow-100 border-t border-black/60 cursor-pointer" : "border-t border-black/60 cursor-pointer hover:bg-amber-300",
32
+ onClick: () => props.onTesuuChange && props.onTesuuChange(index)
33
+ },
34
+ /* @__PURE__ */ React.createElement("th", { className: "text-right p-1 pl-2 w-0", style: { width: 0 } }, /* @__PURE__ */ React.createElement("div", null, index)),
35
+ /* @__PURE__ */ React.createElement("td", { className: "p-1 pr-0" }, JKFPlayer.moveToReadableKifu(move))
36
+ ))
37
+ ))));
38
+ }
39
+ export {
40
+ MovesArea
41
+ };
@@ -0,0 +1,17 @@
1
+ import { IMoveMoveFormat } from 'json-kifu-format/dist/src/Formats';
2
+ import React from 'react';
3
+ import { Piece } from 'shogi.js';
4
+
5
+ type Props = {
6
+ size?: number;
7
+ boardColor?: string;
8
+ lineColor?: string;
9
+ fontFamily?: string;
10
+ fontSizeRatio?: number;
11
+ pieces: Piece[][];
12
+ isSente?: boolean;
13
+ currentMove?: IMoveMoveFormat;
14
+ };
15
+ declare const ShogiBoardCanvas: React.FC<Props>;
16
+
17
+ export { ShogiBoardCanvas as default };
@@ -0,0 +1,137 @@
1
+ "use client";
2
+ import { JKFPlayer } from "json-kifu-format";
3
+ import React from "react";
4
+ import { Color } from "shogi.js";
5
+ const getCanvasDimensions = (size) => {
6
+ const dpr = window.devicePixelRatio || 1;
7
+ return { width: size * dpr, height: size * dpr, dpr };
8
+ };
9
+ const getBoardLayout = (size) => {
10
+ const margin = size * 0.06;
11
+ const boardSize = size - margin * 2;
12
+ const cell = boardSize / 9;
13
+ return { margin, boardSize, cell };
14
+ };
15
+ const drawBackground = (ctx, size, boardColor) => {
16
+ ctx.fillStyle = boardColor;
17
+ ctx.fillRect(0, 0, size, size);
18
+ };
19
+ const drawBoard = (ctx, margin, boardSize, lineColor) => {
20
+ ctx.strokeStyle = lineColor;
21
+ ctx.lineWidth = 2;
22
+ ctx.strokeRect(margin, margin, boardSize, boardSize);
23
+ ctx.lineWidth = 1;
24
+ Array.from({ length: 8 }).forEach((_, i) => {
25
+ const offset = (i + 1) * (boardSize / 9);
26
+ ctx.beginPath();
27
+ ctx.moveTo(margin + offset, margin);
28
+ ctx.lineTo(margin + offset, margin + boardSize);
29
+ ctx.stroke();
30
+ ctx.beginPath();
31
+ ctx.moveTo(margin, margin + offset);
32
+ ctx.lineTo(margin + boardSize, margin + offset);
33
+ ctx.stroke();
34
+ });
35
+ };
36
+ const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente, currentMove) => {
37
+ ctx.textAlign = "center";
38
+ ctx.textBaseline = "middle";
39
+ pieces.forEach((row, rowIndex) => {
40
+ row.forEach((piece, colIndex) => {
41
+ if (!piece) return;
42
+ const x = isSente ? 8 - rowIndex : rowIndex;
43
+ const y = isSente ? colIndex : 8 - colIndex;
44
+ const px = margin + x * cell + cell / 2;
45
+ const py = margin + y * cell + cell / 2;
46
+ ctx.save();
47
+ ctx.translate(px, py);
48
+ if (isSente && piece.color === Color.White) {
49
+ ctx.rotate(Math.PI);
50
+ } else if (!isSente && piece.color === Color.Black) {
51
+ ctx.rotate(Math.PI);
52
+ }
53
+ const kan = JKFPlayer.kindToKan(piece.kind);
54
+ ctx.fillStyle = "#000";
55
+ if (currentMove) {
56
+ const to = currentMove.to;
57
+ if (to && to.x === rowIndex + 1 && to.y === colIndex + 1) {
58
+ ctx.fillStyle = "red";
59
+ }
60
+ }
61
+ if (kan.length === 2) {
62
+ const baseFontSize = cell * fontSizeRatio * 0.5;
63
+ ctx.font = `${baseFontSize}px ${fontFamily}`;
64
+ const scaleX = 2;
65
+ const scaleY = 1;
66
+ const offsetY = cell * 0.18;
67
+ ctx.save();
68
+ ctx.scale(scaleX, scaleY);
69
+ ctx.fillText(kan[0], 0 / scaleX, -offsetY / scaleY);
70
+ ctx.restore();
71
+ ctx.save();
72
+ ctx.scale(scaleX, scaleY);
73
+ ctx.fillText(kan[1], 0 / scaleX, offsetY / scaleY);
74
+ ctx.restore();
75
+ } else {
76
+ const fontSize = cell * fontSizeRatio;
77
+ ctx.font = `${fontSize}px ${fontFamily}`;
78
+ ctx.fillText(kan, 0, 0);
79
+ }
80
+ ctx.restore();
81
+ });
82
+ });
83
+ };
84
+ const drawCoordinates = (ctx, margin, boardSize, cell, fontFamily, isSente) => {
85
+ ctx.fillStyle = "#000";
86
+ ctx.font = `${cell * 0.35}px ${fontFamily}`;
87
+ ctx.textAlign = "center";
88
+ ctx.textBaseline = "middle";
89
+ Array.from({ length: 9 }).forEach((_, i) => {
90
+ const x = margin + i * cell + cell / 2;
91
+ const y = margin / 2;
92
+ const label = isSente ? (9 - i).toString() : (i + 1).toString();
93
+ ctx.fillText(label, x, y);
94
+ });
95
+ const kanji = ["\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"];
96
+ kanji.forEach((k, i) => {
97
+ const x = margin + boardSize + margin / 2;
98
+ const y = margin + i * cell + cell / 2;
99
+ const label = isSente ? k : kanji[8 - i];
100
+ ctx.fillText(label, x, y);
101
+ });
102
+ };
103
+ const ShogiBoardCanvas = ({
104
+ size = 360,
105
+ boardColor = "#f9d27a",
106
+ lineColor = "#000",
107
+ fontFamily = "serif",
108
+ fontSizeRatio = 0.7,
109
+ pieces,
110
+ isSente = true,
111
+ currentMove
112
+ }) => {
113
+ const canvasRef = React.useRef(null);
114
+ React.useEffect(() => {
115
+ const canvas = canvasRef.current;
116
+ if (!canvas) return;
117
+ const ctx = canvas.getContext("2d");
118
+ if (!ctx) return;
119
+ const { width, height, dpr } = getCanvasDimensions(size);
120
+ canvas.width = width;
121
+ canvas.height = height;
122
+ canvas.style.width = `${size}px`;
123
+ canvas.style.height = `${size}px`;
124
+ ctx.scale(dpr, dpr);
125
+ ctx.clearRect(0, 0, size, size);
126
+ const { margin, boardSize, cell } = getBoardLayout(size);
127
+ drawBackground(ctx, size, boardColor);
128
+ drawBoard(ctx, margin, boardSize, lineColor);
129
+ drawPieces(ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente, currentMove);
130
+ drawCoordinates(ctx, margin, boardSize, cell, fontFamily, isSente);
131
+ }, [size, boardColor, lineColor, fontFamily, fontSizeRatio, pieces, isSente, currentMove]);
132
+ return /* @__PURE__ */ React.createElement("canvas", { ref: canvasRef, width: size, height: size });
133
+ };
134
+ var shogi_board_canvas_default = ShogiBoardCanvas;
135
+ export {
136
+ shogi_board_canvas_default as default
137
+ };
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { Piece } from 'shogi.js';
3
+
4
+ type Props = {
5
+ size?: number;
6
+ boardColor?: string;
7
+ lineColor?: string;
8
+ fontFamily?: string;
9
+ fontSizeRatio?: number;
10
+ hands: Piece[][];
11
+ isSente?: boolean;
12
+ isTop?: boolean;
13
+ };
14
+ declare const ShogiHandsCanvas: React.FC<Props>;
15
+
16
+ export { ShogiHandsCanvas as default };
@@ -0,0 +1,91 @@
1
+ "use client";
2
+ import { JKFPlayer } from "json-kifu-format";
3
+ import React from "react";
4
+ import { Color } from "shogi.js";
5
+ const getCanvasDimensions = (size) => {
6
+ const dpr = window.devicePixelRatio || 1;
7
+ return { width: size * dpr, height: size * dpr, dpr };
8
+ };
9
+ const getHandsLayout = (size) => {
10
+ const margin = size * 0.06;
11
+ const boardSize = size - margin * 2;
12
+ const cellSize = boardSize / 9;
13
+ const handsHeight = cellSize + margin + 2;
14
+ return { margin, handsHeight, boardSize, cellSize };
15
+ };
16
+ const drawBackground = (ctx, width, height, color) => {
17
+ ctx.fillStyle = color;
18
+ ctx.fillRect(0, 0, width, height);
19
+ };
20
+ const drawHandsFrame = (ctx, x, y, width, height, lineColor, isTop) => {
21
+ ctx.strokeStyle = lineColor;
22
+ ctx.lineWidth = 2;
23
+ if (isTop) {
24
+ ctx.strokeRect(x, y, width, height);
25
+ } else {
26
+ ctx.strokeRect(x, 2, width, height);
27
+ }
28
+ };
29
+ const drawCoordinates = (ctx, fontSize, fontFamily) => {
30
+ ctx.fillStyle = "#000";
31
+ ctx.font = `${fontSize}px ${fontFamily}`;
32
+ ctx.textAlign = "center";
33
+ ctx.textBaseline = "middle";
34
+ };
35
+ const drawPieces = (ctx, hands, margin, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
36
+ ctx.textAlign = "center";
37
+ ctx.textBaseline = "middle";
38
+ const pieces = isSente && isTop || !isSente && !isTop ? hands[Color.White] : hands[Color.Black];
39
+ pieces.forEach((piece, index) => {
40
+ if (!piece) return;
41
+ const px = margin + index * cell + cell / 2;
42
+ const py = isTop ? margin + cell / 2 : cell / 2 + 2;
43
+ ctx.save();
44
+ ctx.translate(px, py);
45
+ if (isSente && piece.color === Color.White) {
46
+ ctx.rotate(Math.PI);
47
+ } else if (!isSente && piece.color === Color.Black) {
48
+ ctx.rotate(Math.PI);
49
+ }
50
+ const fontSize = cell * fontSizeRatio;
51
+ const kan = JKFPlayer.kindToKan(piece.kind);
52
+ ctx.font = `${fontSize}px ${fontFamily}`;
53
+ ctx.fillText(kan, 0, 0);
54
+ ctx.restore();
55
+ });
56
+ };
57
+ const ShogiHandsCanvas = ({
58
+ size = 360,
59
+ boardColor = "#f9d27a",
60
+ lineColor = "#000",
61
+ fontFamily = "serif",
62
+ fontSizeRatio = 0.7,
63
+ hands,
64
+ isSente = true,
65
+ isTop = false
66
+ }) => {
67
+ const canvasRef = React.useRef(null);
68
+ React.useEffect(() => {
69
+ const canvas = canvasRef.current;
70
+ if (!canvas) return;
71
+ const ctx = canvas.getContext("2d");
72
+ if (!ctx) return;
73
+ const { margin, handsHeight, boardSize, cellSize } = getHandsLayout(size);
74
+ const { width: canvasWidth, dpr } = getCanvasDimensions(size);
75
+ canvas.width = canvasWidth;
76
+ canvas.height = handsHeight * dpr;
77
+ canvas.style.width = `${size}px`;
78
+ canvas.style.height = `${handsHeight}px`;
79
+ ctx.scale(dpr, dpr);
80
+ ctx.clearRect(0, 0, size, handsHeight);
81
+ drawBackground(ctx, size, handsHeight, boardColor);
82
+ drawHandsFrame(ctx, margin, margin, boardSize, cellSize, lineColor, isTop);
83
+ drawCoordinates(ctx, cellSize * 0.35, fontFamily);
84
+ drawPieces(ctx, hands, margin, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
85
+ }, [size, boardColor, lineColor, fontFamily, fontSizeRatio, isSente, hands, isTop]);
86
+ return /* @__PURE__ */ React.createElement("canvas", { ref: canvasRef });
87
+ };
88
+ var shogi_hands_canvas_default = ShogiHandsCanvas;
89
+ export {
90
+ shogi_hands_canvas_default as default
91
+ };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+
3
+ type ShogiPlayerProps = {
4
+ kifuText: string;
5
+ };
6
+ declare function ShogiPlayer(props: ShogiPlayerProps): React.JSX.Element;
7
+
8
+ export { type ShogiPlayerProps, ShogiPlayer as default };
@@ -0,0 +1,48 @@
1
+ "use client";
2
+ import { JKFPlayer } from "json-kifu-format";
3
+ import React, { useState } from "react";
4
+ import Button from "./button";
5
+ import { MovesArea } from "./moves-area";
6
+ import ShogiBoardCanvas from "./shogi-board-canvas";
7
+ import ShogiHandsCanvas from "./shogi-hands-canvas";
8
+ function ShogiPlayer(props) {
9
+ const [player] = useState(JKFPlayer.parse(props.kifuText.trim()));
10
+ const [pieces, setPieces] = useState(player.shogi.board);
11
+ const [hands, setHands] = useState(player.shogi.hands);
12
+ const [moves, setMoves] = useState(player.kifu.moves);
13
+ const [tesuu, setTesuu] = useState(player.tesuu);
14
+ const [currentMove, setCurrentMove] = useState(player.getMove());
15
+ const [isSente, setIsSente] = useState(true);
16
+ const [maxTesuu, setMaxTesuu] = useState(player.getMaxTesuu());
17
+ const [comments, setComments] = useState(player.getComments());
18
+ const size = 360;
19
+ const updateState = () => {
20
+ setPieces([...player.shogi.board]);
21
+ setHands([...player.shogi.hands]);
22
+ setMoves([...player.kifu.moves]);
23
+ setCurrentMove(player.getMove());
24
+ setMaxTesuu(player.getMaxTesuu());
25
+ setComments(player.getComments());
26
+ setTesuu(player.tesuu);
27
+ };
28
+ const handleForward = () => {
29
+ player.forward();
30
+ updateState();
31
+ };
32
+ const handleBackward = () => {
33
+ player.backward();
34
+ updateState();
35
+ };
36
+ const handleGoto = (tesuu2) => {
37
+ player.goto(tesuu2);
38
+ updateState();
39
+ };
40
+ const handeleToggle = () => {
41
+ setIsSente(!isSente);
42
+ updateState();
43
+ };
44
+ return /* @__PURE__ */ React.createElement("div", { className: "flex w-fit", tabIndex: 1 }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ React.createElement(ShogiHandsCanvas, { size, hands, isSente, isTop: true }), /* @__PURE__ */ React.createElement(ShogiBoardCanvas, { size, pieces, isSente, currentMove }), /* @__PURE__ */ React.createElement(ShogiHandsCanvas, { size, hands, isSente, isTop: false })), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col w-fit bg-[#f9d27a] p-4 gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex-1 relative w-full" }, /* @__PURE__ */ React.createElement(MovesArea, { moves, tesuu, onTesuuChange: handleGoto })), /* @__PURE__ */ React.createElement("div", { className: "text-black border-black border-2 p-1 text-xs" }, comments.map((comment, index) => /* @__PURE__ */ React.createElement("div", { key: index }, comment)), comments.length === 0 && /* @__PURE__ */ React.createElement("div", null, "\xA0")), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React.createElement(Button, { onClick: () => handleGoto(0) }, "\u6700\u521D"), /* @__PURE__ */ React.createElement(Button, { onClick: handleBackward }, "\u524D"), /* @__PURE__ */ React.createElement(Button, { onClick: handleForward }, "\u6B21"), /* @__PURE__ */ React.createElement(Button, { onClick: () => handleGoto(maxTesuu) }, "\u6700\u5F8C"), /* @__PURE__ */ React.createElement(Button, { onClick: handeleToggle }, "\u53CD\u8EE2"))));
45
+ }
46
+ export {
47
+ ShogiPlayer as default
48
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hackersheet/next-document-content-kifu",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.3",
4
4
  "description": "Hacker Sheet document content kifu components for Next.js",
5
5
  "keywords": [],
6
6
  "repository": {
@@ -24,22 +24,22 @@
24
24
  "dist"
25
25
  ],
26
26
  "dependencies": {
27
- "@hackersheet/core": "alpha",
28
- "@hackersheet/react-document-content": "alpha",
29
- "kifu-for-js": "^5.4.1"
27
+ "shogi.js": "^5.4.1",
28
+ "json-kifu-format": "^5.4.1",
29
+ "@hackersheet/core": "0.1.0-alpha.11",
30
+ "@hackersheet/react-document-content": "0.1.0-alpha.12"
30
31
  },
31
32
  "devDependencies": {
32
- "@types/react": "^18.0.0",
33
- "@types/react-dom": "^18.0.0",
34
- "next": "14.2.5",
35
- "react": "^18.0.0",
36
- "react-dom": "^18.0.0",
37
- "@hackersheet/eslint-config-custom": "0.0.0"
33
+ "@types/react": "^19.2.0",
34
+ "@types/react-dom": "^19.2.0",
35
+ "next": "^15.5.4",
36
+ "react": "^19.2.0",
37
+ "react-dom": "^19.2.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "next": "^14.0.0",
41
- "react": "^18.0.0",
42
- "react-dom": "^18.0.0"
41
+ "react": "^19.0.0",
42
+ "react-dom": "^19.0.0"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "rm -rf ./dist && pnpm tsup",