@hackersheet/next-document-content-kifu 0.1.0-alpha.5 → 0.1.0-alpha.7

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.
@@ -36,6 +36,7 @@ var import_navigation = require("next/navigation");
36
36
  var import_react = __toESM(require("react"));
37
37
  var import_shogi_player = require("../shogi-player");
38
38
  function Kifu({ code, language }) {
39
+ const [ply, setPly] = (0, import_react.useState)(0);
39
40
  const [, filename] = language.split(":");
40
41
  const searchParams = (0, import_navigation.useSearchParams)();
41
42
  const id = filename ? `user-content-${filename}` : void 0;
@@ -43,8 +44,8 @@ function Kifu({ code, language }) {
43
44
  const newPly = Number(searchParams.get("ply") ?? 0);
44
45
  const hash = typeof window !== "undefined" ? window.location.hash.replace(/^#!?/, "") : "";
45
46
  if (hash === id) {
46
- console.log(newPly);
47
+ setPly(newPly);
47
48
  }
48
49
  }, [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 }));
50
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ import_react.default.createElement(import_shogi_player.ShogiPlayer, { kifuText: code, tesuu: ply, size: 320 }));
50
51
  }
@@ -36,7 +36,7 @@ function Button({ children, onClick }) {
36
36
  return /* @__PURE__ */ import_react.default.createElement(
37
37
  "button",
38
38
  {
39
- className: "border-2 text-black p-2 border-black rounded-lg hover:bg-amber-100 cursor-pointer",
39
+ className: "border-2 text-xs text-black p-2 border-black rounded-lg hover:bg-amber-100 cursor-pointer",
40
40
  onClick
41
41
  },
42
42
  children
@@ -49,24 +49,28 @@ function MovesArea(props) {
49
49
  });
50
50
  }
51
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("div", { className: "w-full text-xs" }, 0 === props.tesuu && /* @__PURE__ */ import_react.default.createElement("div", { ref: scrollRef }), /* @__PURE__ */ import_react.default.createElement(
52
+ const current = 0 === props.tesuu ? " bg-amber-600" : "";
53
+ 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("div", { className: "grid gap-0 text-xs" }, /* @__PURE__ */ import_react.default.createElement(
53
54
  "div",
54
55
  {
55
56
  onClick: () => props.onTesuuChange && props.onTesuuChange(0),
56
- className: 0 === props.tesuu ? "bg-yellow-100 cursor-pointer flex p-2" : "cursor-pointer hover:bg-amber-300 flex p-2"
57
+ className: "col-span-500 grid grid-cols-subgrid gap-2 py-1 px-2 cursor-pointer hover:bg-amber-100" + current
57
58
  },
59
+ /* @__PURE__ */ import_react.default.createElement("div", null, 0 === props.tesuu && /* @__PURE__ */ import_react.default.createElement("div", { ref: scrollRef })),
58
60
  /* @__PURE__ */ import_react.default.createElement("div", null, "\u958B\u59CB\u5C40\u9762")
59
- ), moves.map(
60
- (move, index) => index > 0 && /* @__PURE__ */ import_react.default.createElement(import_react.Fragment, { key: index }, index === props.tesuu && /* @__PURE__ */ import_react.default.createElement("div", { ref: scrollRef }), /* @__PURE__ */ import_react.default.createElement(
61
+ ), moves.map((move, index) => {
62
+ if (index === 0) return;
63
+ const current2 = index === props.tesuu ? " bg-amber-600" : "";
64
+ return /* @__PURE__ */ import_react.default.createElement(import_react.Fragment, { key: index }, /* @__PURE__ */ import_react.default.createElement(
61
65
  "div",
62
66
  {
63
- className: index === props.tesuu ? "bg-yellow-100 border-t border-black/60 cursor-pointer flex gap-2 p-2" : "border-t border-black/60 cursor-pointer hover:bg-amber-300 flex gap-2 p-2",
67
+ className: "col-span-500 grid grid-cols-subgrid border-black gap-2 border-t py-1 px-2 cursor-pointer hover:bg-amber-100" + current2,
64
68
  onClick: () => props.onTesuuChange && props.onTesuuChange(index)
65
69
  },
66
- /* @__PURE__ */ import_react.default.createElement("div", { className: "w-4 text-right" }, /* @__PURE__ */ import_react.default.createElement("div", null, index)),
70
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "flex" }, index === props.tesuu && /* @__PURE__ */ import_react.default.createElement("div", { ref: scrollRef }), /* @__PURE__ */ import_react.default.createElement("div", { className: "tabular-nums text-right flex-auto" }, index)),
67
71
  /* @__PURE__ */ import_react.default.createElement("div", null, import_json_kifu_format.JKFPlayer.moveToReadableKifu(move))
68
- ))
69
- )));
72
+ ));
73
+ })));
70
74
  }
71
75
  // Annotate the CommonJS export names for ESM import in node:
72
76
  0 && (module.exports = {
@@ -66,7 +66,7 @@ const drawBoard = (ctx, margin, boardSize, lineColor) => {
66
66
  ctx.stroke();
67
67
  });
68
68
  };
69
- const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente, currentMove) => {
69
+ const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente) => {
70
70
  ctx.textAlign = "center";
71
71
  ctx.textBaseline = "middle";
72
72
  pieces.forEach((row, rowIndex) => {
@@ -85,12 +85,6 @@ const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSent
85
85
  }
86
86
  const kan = import_json_kifu_format.JKFPlayer.kindToKan(piece.kind);
87
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
88
  if (kan.length === 2) {
95
89
  const baseFontSize = cell * fontSizeRatio * 0.5;
96
90
  ctx.font = `${baseFontSize}px ${fontFamily}`;
@@ -122,17 +116,35 @@ const drawCoordinates = (ctx, margin, boardSize, cell, fontFamily, isSente) => {
122
116
  Array.from({ length: 9 }).forEach((_, i) => {
123
117
  const x = margin + i * cell + cell / 2;
124
118
  const y = margin / 2;
125
- const label = isSente ? (9 - i).toString() : (i + 1).toString();
119
+ const label = isSente ? import_json_kifu_format.JKFPlayer.numToZen(9 - i) : import_json_kifu_format.JKFPlayer.numToZen(i + 1);
126
120
  ctx.fillText(label, x, y);
127
121
  });
128
- const kanji = ["\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"];
129
- kanji.forEach((k, i) => {
122
+ Array.from({ length: 9 }).forEach((_, i) => {
130
123
  const x = margin + boardSize + margin / 2;
131
124
  const y = margin + i * cell + cell / 2;
132
- const label = isSente ? k : kanji[8 - i];
125
+ const label = isSente ? import_json_kifu_format.JKFPlayer.numToKan(i + 1) : import_json_kifu_format.JKFPlayer.numToKan(9 - i);
133
126
  ctx.fillText(label, x, y);
134
127
  });
135
128
  };
129
+ const drawHighlightedCell = (ctx, margin, cell, isSente, currentMove) => {
130
+ if (!currentMove) return;
131
+ if (currentMove.to) {
132
+ const toRow = currentMove.to.x - 1;
133
+ const toCol = currentMove.to.y - 1;
134
+ const toX = isSente ? 8 - toRow : toRow;
135
+ const toY = isSente ? toCol : 8 - toCol;
136
+ ctx.fillStyle = "rgba(255,0,0,0.1)";
137
+ ctx.fillRect(margin + toX * cell, margin + toY * cell, cell, cell);
138
+ }
139
+ if (currentMove.from) {
140
+ const fromRow = currentMove.from.x - 1;
141
+ const fromCol = currentMove.from.y - 1;
142
+ const fromX = isSente ? 8 - fromRow : fromRow;
143
+ const fromY = isSente ? fromCol : 8 - fromCol;
144
+ ctx.fillStyle = "rgba(255,0,0,0.1)";
145
+ ctx.fillRect(margin + fromX * cell, margin + fromY * cell, cell, cell);
146
+ }
147
+ };
136
148
  const ShogiBoardCanvas = ({
137
149
  size = 360,
138
150
  boardColor = "#f9d27a",
@@ -159,7 +171,8 @@ const ShogiBoardCanvas = ({
159
171
  const { margin, boardSize, cell } = getBoardLayout(size);
160
172
  drawBackground(ctx, size, boardColor);
161
173
  drawBoard(ctx, margin, boardSize, lineColor);
162
- drawPieces(ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente, currentMove);
174
+ drawHighlightedCell(ctx, margin, cell, isSente, currentMove);
175
+ drawPieces(ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente);
163
176
  drawCoordinates(ctx, margin, boardSize, cell, fontFamily, isSente);
164
177
  }, [size, boardColor, lineColor, fontFamily, fontSizeRatio, pieces, isSente, currentMove]);
165
178
  return /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef, width: size, height: size });
@@ -65,25 +65,44 @@ const drawCoordinates = (ctx, fontSize, fontFamily) => {
65
65
  ctx.textAlign = "center";
66
66
  ctx.textBaseline = "middle";
67
67
  };
68
- const drawPieces = (ctx, hands, margin, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
68
+ const drawPieces = (ctx, hands, margin, boardSize, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
69
69
  ctx.textAlign = "center";
70
70
  ctx.textBaseline = "middle";
71
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;
72
+ if (!pieces || pieces.length === 0) return;
73
+ const grouped = pieces.reduce(
74
+ (acc, piece) => {
75
+ if (!piece) return acc;
76
+ const key = piece.kind;
77
+ if (!acc[key]) acc[key] = { count: 0, color: piece.color };
78
+ acc[key].count += 1;
79
+ return acc;
80
+ },
81
+ {}
82
+ );
83
+ const order = ["OU", "HI", "KA", "KI", "GI", "KE", "KY", "FU"];
84
+ const kinds = order.filter((kind) => grouped[kind]);
85
+ kinds.forEach((kind, index) => {
86
+ const { count, color } = grouped[kind];
87
+ const px = isTop ? margin + boardSize - (index * cell + cell / 2) : margin + index * cell + cell / 2;
75
88
  const py = isTop ? margin + cell / 2 : cell / 2 + 2;
76
89
  ctx.save();
77
90
  ctx.translate(px, py);
78
- if (isSente && piece.color === import_shogi.Color.White) {
91
+ if (isSente && color === import_shogi.Color.White) {
79
92
  ctx.rotate(Math.PI);
80
- } else if (!isSente && piece.color === import_shogi.Color.Black) {
93
+ } else if (!isSente && color === import_shogi.Color.Black) {
81
94
  ctx.rotate(Math.PI);
82
95
  }
83
96
  const fontSize = cell * fontSizeRatio;
84
- const kan = import_json_kifu_format.JKFPlayer.kindToKan(piece.kind);
97
+ const kan = import_json_kifu_format.JKFPlayer.kindToKan(kind);
85
98
  ctx.font = `${fontSize}px ${fontFamily}`;
86
99
  ctx.fillText(kan, 0, 0);
100
+ if (count > 1) {
101
+ ctx.font = `${fontSize * 0.5}px ${fontFamily}`;
102
+ const countOffsetX = cell * 0;
103
+ const countOffsetY = cell * 0.8;
104
+ ctx.fillText(String(count), countOffsetX, countOffsetY);
105
+ }
87
106
  ctx.restore();
88
107
  });
89
108
  };
@@ -114,7 +133,7 @@ const ShogiHandsCanvas = ({
114
133
  drawBackground(ctx, size, handsHeight, boardColor);
115
134
  drawHandsFrame(ctx, margin, margin, boardSize, cellSize, lineColor, isTop);
116
135
  drawCoordinates(ctx, cellSize * 0.35, fontFamily);
117
- drawPieces(ctx, hands, margin, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
136
+ drawPieces(ctx, hands, margin, boardSize, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
118
137
  }, [size, boardColor, lineColor, fontFamily, fontSizeRatio, isSente, hands, isTop]);
119
138
  return /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef });
120
139
  };
@@ -2,6 +2,8 @@ import React from 'react';
2
2
 
3
3
  type ShogiPlayerProps = {
4
4
  kifuText: string;
5
+ size?: number;
6
+ tesuu?: number;
5
7
  };
6
8
  declare function ShogiPlayer(props: ShogiPlayerProps): React.JSX.Element;
7
9
 
@@ -48,7 +48,7 @@ function ShogiPlayer(props) {
48
48
  const [isSente, setIsSente] = (0, import_react.useState)(true);
49
49
  const [maxTesuu, setMaxTesuu] = (0, import_react.useState)(player.getMaxTesuu());
50
50
  const [comments, setComments] = (0, import_react.useState)(player.getComments());
51
- const size = 360;
51
+ const size = props.size ? props.size : 360;
52
52
  const updateState = () => {
53
53
  setPieces([...player.shogi.board]);
54
54
  setHands([...player.shogi.hands]);
@@ -74,5 +74,39 @@ function ShogiPlayer(props) {
74
74
  setIsSente(!isSente);
75
75
  updateState();
76
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"))));
77
+ const handleKeydown = (0, import_react.useCallback)(
78
+ (event) => {
79
+ event.preventDefault();
80
+ event.stopPropagation();
81
+ switch (event.key) {
82
+ case "Up":
83
+ case "ArrowUp":
84
+ handleGoto(0);
85
+ break;
86
+ case "Down":
87
+ case "ArrowDown":
88
+ handleGoto(maxTesuu);
89
+ break;
90
+ case "Left":
91
+ case "ArrowLeft":
92
+ handleBackward();
93
+ break;
94
+ case " ":
95
+ case "Right":
96
+ case "ArrowRight":
97
+ handleForward();
98
+ break;
99
+ case "r":
100
+ handeleToggle();
101
+ break;
102
+ }
103
+ },
104
+ [maxTesuu, isSente]
105
+ );
106
+ (0, import_react.useEffect)(() => {
107
+ if (props.tesuu !== void 0) {
108
+ handleGoto(props.tesuu);
109
+ }
110
+ }, [props.tesuu]);
111
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "flex w-fit", tabIndex: 1, onKeyDown: handleKeydown }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-[#f9d27a] text-black text-xs text-right p-1" }, isSente ? "\u2616 " + player.kifu.header["\u5F8C\u624B"] : "\u2617 " + player.kifu.header["\u5148\u624B"]), /* @__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: "bg-[#f9d27a] text-black text-xs p-1" }, isSente ? "\u2617 " + player.kifu.header["\u5148\u624B"] : "\u2616 " + player.kifu.header["\u5F8C\u624B"])), /* @__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 && tesuu !== 0 && /* @__PURE__ */ import_react.default.createElement("div", null, "\xA0"), tesuu === 0 && Object.entries(player.kifu.header).map(([key, value], i) => /* @__PURE__ */ import_react.default.createElement("div", { key: i }, key, ": ", value))), /* @__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
112
  }
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { useSearchParams } from "next/navigation";
3
- import React, { useEffect } from "react";
3
+ import React, { useEffect, useState } from "react";
4
4
  import { ShogiPlayer } from "../shogi-player";
5
5
  function Kifu({ code, language }) {
6
+ const [ply, setPly] = useState(0);
6
7
  const [, filename] = language.split(":");
7
8
  const searchParams = useSearchParams();
8
9
  const id = filename ? `user-content-${filename}` : void 0;
@@ -10,10 +11,10 @@ function Kifu({ code, language }) {
10
11
  const newPly = Number(searchParams.get("ply") ?? 0);
11
12
  const hash = typeof window !== "undefined" ? window.location.hash.replace(/^#!?/, "") : "";
12
13
  if (hash === id) {
13
- console.log(newPly);
14
+ setPly(newPly);
14
15
  }
15
16
  }, [searchParams, id]);
16
- return /* @__PURE__ */ React.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ React.createElement(ShogiPlayer, { kifuText: code }));
17
+ return /* @__PURE__ */ React.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ React.createElement(ShogiPlayer, { kifuText: code, tesuu: ply, size: 320 }));
17
18
  }
18
19
  export {
19
20
  Kifu as default
@@ -3,7 +3,7 @@ function Button({ children, onClick }) {
3
3
  return /* @__PURE__ */ React.createElement(
4
4
  "button",
5
5
  {
6
- className: "border-2 text-black p-2 border-black rounded-lg hover:bg-amber-100 cursor-pointer",
6
+ className: "border-2 text-xs text-black p-2 border-black rounded-lg hover:bg-amber-100 cursor-pointer",
7
7
  onClick
8
8
  },
9
9
  children
@@ -16,24 +16,28 @@ function MovesArea(props) {
16
16
  });
17
17
  }
18
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("div", { className: "w-full text-xs" }, 0 === props.tesuu && /* @__PURE__ */ React.createElement("div", { ref: scrollRef }), /* @__PURE__ */ React.createElement(
19
+ const current = 0 === props.tesuu ? " bg-amber-600" : "";
20
+ 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("div", { className: "grid gap-0 text-xs" }, /* @__PURE__ */ React.createElement(
20
21
  "div",
21
22
  {
22
23
  onClick: () => props.onTesuuChange && props.onTesuuChange(0),
23
- className: 0 === props.tesuu ? "bg-yellow-100 cursor-pointer flex p-2" : "cursor-pointer hover:bg-amber-300 flex p-2"
24
+ className: "col-span-500 grid grid-cols-subgrid gap-2 py-1 px-2 cursor-pointer hover:bg-amber-100" + current
24
25
  },
26
+ /* @__PURE__ */ React.createElement("div", null, 0 === props.tesuu && /* @__PURE__ */ React.createElement("div", { ref: scrollRef })),
25
27
  /* @__PURE__ */ React.createElement("div", null, "\u958B\u59CB\u5C40\u9762")
26
- ), moves.map(
27
- (move, index) => index > 0 && /* @__PURE__ */ React.createElement(Fragment, { key: index }, index === props.tesuu && /* @__PURE__ */ React.createElement("div", { ref: scrollRef }), /* @__PURE__ */ React.createElement(
28
+ ), moves.map((move, index) => {
29
+ if (index === 0) return;
30
+ const current2 = index === props.tesuu ? " bg-amber-600" : "";
31
+ return /* @__PURE__ */ React.createElement(Fragment, { key: index }, /* @__PURE__ */ React.createElement(
28
32
  "div",
29
33
  {
30
- className: index === props.tesuu ? "bg-yellow-100 border-t border-black/60 cursor-pointer flex gap-2 p-2" : "border-t border-black/60 cursor-pointer hover:bg-amber-300 flex gap-2 p-2",
34
+ className: "col-span-500 grid grid-cols-subgrid border-black gap-2 border-t py-1 px-2 cursor-pointer hover:bg-amber-100" + current2,
31
35
  onClick: () => props.onTesuuChange && props.onTesuuChange(index)
32
36
  },
33
- /* @__PURE__ */ React.createElement("div", { className: "w-4 text-right" }, /* @__PURE__ */ React.createElement("div", null, index)),
37
+ /* @__PURE__ */ React.createElement("div", { className: "flex" }, index === props.tesuu && /* @__PURE__ */ React.createElement("div", { ref: scrollRef }), /* @__PURE__ */ React.createElement("div", { className: "tabular-nums text-right flex-auto" }, index)),
34
38
  /* @__PURE__ */ React.createElement("div", null, JKFPlayer.moveToReadableKifu(move))
35
- ))
36
- )));
39
+ ));
40
+ })));
37
41
  }
38
42
  export {
39
43
  MovesArea
@@ -33,7 +33,7 @@ const drawBoard = (ctx, margin, boardSize, lineColor) => {
33
33
  ctx.stroke();
34
34
  });
35
35
  };
36
- const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente, currentMove) => {
36
+ const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente) => {
37
37
  ctx.textAlign = "center";
38
38
  ctx.textBaseline = "middle";
39
39
  pieces.forEach((row, rowIndex) => {
@@ -52,12 +52,6 @@ const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSent
52
52
  }
53
53
  const kan = JKFPlayer.kindToKan(piece.kind);
54
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
55
  if (kan.length === 2) {
62
56
  const baseFontSize = cell * fontSizeRatio * 0.5;
63
57
  ctx.font = `${baseFontSize}px ${fontFamily}`;
@@ -89,17 +83,35 @@ const drawCoordinates = (ctx, margin, boardSize, cell, fontFamily, isSente) => {
89
83
  Array.from({ length: 9 }).forEach((_, i) => {
90
84
  const x = margin + i * cell + cell / 2;
91
85
  const y = margin / 2;
92
- const label = isSente ? (9 - i).toString() : (i + 1).toString();
86
+ const label = isSente ? JKFPlayer.numToZen(9 - i) : JKFPlayer.numToZen(i + 1);
93
87
  ctx.fillText(label, x, y);
94
88
  });
95
- const kanji = ["\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"];
96
- kanji.forEach((k, i) => {
89
+ Array.from({ length: 9 }).forEach((_, i) => {
97
90
  const x = margin + boardSize + margin / 2;
98
91
  const y = margin + i * cell + cell / 2;
99
- const label = isSente ? k : kanji[8 - i];
92
+ const label = isSente ? JKFPlayer.numToKan(i + 1) : JKFPlayer.numToKan(9 - i);
100
93
  ctx.fillText(label, x, y);
101
94
  });
102
95
  };
96
+ const drawHighlightedCell = (ctx, margin, cell, isSente, currentMove) => {
97
+ if (!currentMove) return;
98
+ if (currentMove.to) {
99
+ const toRow = currentMove.to.x - 1;
100
+ const toCol = currentMove.to.y - 1;
101
+ const toX = isSente ? 8 - toRow : toRow;
102
+ const toY = isSente ? toCol : 8 - toCol;
103
+ ctx.fillStyle = "rgba(255,0,0,0.1)";
104
+ ctx.fillRect(margin + toX * cell, margin + toY * cell, cell, cell);
105
+ }
106
+ if (currentMove.from) {
107
+ const fromRow = currentMove.from.x - 1;
108
+ const fromCol = currentMove.from.y - 1;
109
+ const fromX = isSente ? 8 - fromRow : fromRow;
110
+ const fromY = isSente ? fromCol : 8 - fromCol;
111
+ ctx.fillStyle = "rgba(255,0,0,0.1)";
112
+ ctx.fillRect(margin + fromX * cell, margin + fromY * cell, cell, cell);
113
+ }
114
+ };
103
115
  const ShogiBoardCanvas = ({
104
116
  size = 360,
105
117
  boardColor = "#f9d27a",
@@ -126,7 +138,8 @@ const ShogiBoardCanvas = ({
126
138
  const { margin, boardSize, cell } = getBoardLayout(size);
127
139
  drawBackground(ctx, size, boardColor);
128
140
  drawBoard(ctx, margin, boardSize, lineColor);
129
- drawPieces(ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente, currentMove);
141
+ drawHighlightedCell(ctx, margin, cell, isSente, currentMove);
142
+ drawPieces(ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente);
130
143
  drawCoordinates(ctx, margin, boardSize, cell, fontFamily, isSente);
131
144
  }, [size, boardColor, lineColor, fontFamily, fontSizeRatio, pieces, isSente, currentMove]);
132
145
  return /* @__PURE__ */ React.createElement("canvas", { ref: canvasRef, width: size, height: size });
@@ -32,25 +32,44 @@ const drawCoordinates = (ctx, fontSize, fontFamily) => {
32
32
  ctx.textAlign = "center";
33
33
  ctx.textBaseline = "middle";
34
34
  };
35
- const drawPieces = (ctx, hands, margin, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
35
+ const drawPieces = (ctx, hands, margin, boardSize, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
36
36
  ctx.textAlign = "center";
37
37
  ctx.textBaseline = "middle";
38
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;
39
+ if (!pieces || pieces.length === 0) return;
40
+ const grouped = pieces.reduce(
41
+ (acc, piece) => {
42
+ if (!piece) return acc;
43
+ const key = piece.kind;
44
+ if (!acc[key]) acc[key] = { count: 0, color: piece.color };
45
+ acc[key].count += 1;
46
+ return acc;
47
+ },
48
+ {}
49
+ );
50
+ const order = ["OU", "HI", "KA", "KI", "GI", "KE", "KY", "FU"];
51
+ const kinds = order.filter((kind) => grouped[kind]);
52
+ kinds.forEach((kind, index) => {
53
+ const { count, color } = grouped[kind];
54
+ const px = isTop ? margin + boardSize - (index * cell + cell / 2) : margin + index * cell + cell / 2;
42
55
  const py = isTop ? margin + cell / 2 : cell / 2 + 2;
43
56
  ctx.save();
44
57
  ctx.translate(px, py);
45
- if (isSente && piece.color === Color.White) {
58
+ if (isSente && color === Color.White) {
46
59
  ctx.rotate(Math.PI);
47
- } else if (!isSente && piece.color === Color.Black) {
60
+ } else if (!isSente && color === Color.Black) {
48
61
  ctx.rotate(Math.PI);
49
62
  }
50
63
  const fontSize = cell * fontSizeRatio;
51
- const kan = JKFPlayer.kindToKan(piece.kind);
64
+ const kan = JKFPlayer.kindToKan(kind);
52
65
  ctx.font = `${fontSize}px ${fontFamily}`;
53
66
  ctx.fillText(kan, 0, 0);
67
+ if (count > 1) {
68
+ ctx.font = `${fontSize * 0.5}px ${fontFamily}`;
69
+ const countOffsetX = cell * 0;
70
+ const countOffsetY = cell * 0.8;
71
+ ctx.fillText(String(count), countOffsetX, countOffsetY);
72
+ }
54
73
  ctx.restore();
55
74
  });
56
75
  };
@@ -81,7 +100,7 @@ const ShogiHandsCanvas = ({
81
100
  drawBackground(ctx, size, handsHeight, boardColor);
82
101
  drawHandsFrame(ctx, margin, margin, boardSize, cellSize, lineColor, isTop);
83
102
  drawCoordinates(ctx, cellSize * 0.35, fontFamily);
84
- drawPieces(ctx, hands, margin, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
103
+ drawPieces(ctx, hands, margin, boardSize, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
85
104
  }, [size, boardColor, lineColor, fontFamily, fontSizeRatio, isSente, hands, isTop]);
86
105
  return /* @__PURE__ */ React.createElement("canvas", { ref: canvasRef });
87
106
  };
@@ -2,6 +2,8 @@ import React from 'react';
2
2
 
3
3
  type ShogiPlayerProps = {
4
4
  kifuText: string;
5
+ size?: number;
6
+ tesuu?: number;
5
7
  };
6
8
  declare function ShogiPlayer(props: ShogiPlayerProps): React.JSX.Element;
7
9
 
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { JKFPlayer } from "json-kifu-format";
3
- import React, { useState } from "react";
3
+ import React, { useCallback, useEffect, useState } from "react";
4
4
  import Button from "./button";
5
5
  import { MovesArea } from "./moves-area";
6
6
  import ShogiBoardCanvas from "./shogi-board-canvas";
@@ -15,7 +15,7 @@ function ShogiPlayer(props) {
15
15
  const [isSente, setIsSente] = useState(true);
16
16
  const [maxTesuu, setMaxTesuu] = useState(player.getMaxTesuu());
17
17
  const [comments, setComments] = useState(player.getComments());
18
- const size = 360;
18
+ const size = props.size ? props.size : 360;
19
19
  const updateState = () => {
20
20
  setPieces([...player.shogi.board]);
21
21
  setHands([...player.shogi.hands]);
@@ -41,7 +41,41 @@ function ShogiPlayer(props) {
41
41
  setIsSente(!isSente);
42
42
  updateState();
43
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"))));
44
+ const handleKeydown = useCallback(
45
+ (event) => {
46
+ event.preventDefault();
47
+ event.stopPropagation();
48
+ switch (event.key) {
49
+ case "Up":
50
+ case "ArrowUp":
51
+ handleGoto(0);
52
+ break;
53
+ case "Down":
54
+ case "ArrowDown":
55
+ handleGoto(maxTesuu);
56
+ break;
57
+ case "Left":
58
+ case "ArrowLeft":
59
+ handleBackward();
60
+ break;
61
+ case " ":
62
+ case "Right":
63
+ case "ArrowRight":
64
+ handleForward();
65
+ break;
66
+ case "r":
67
+ handeleToggle();
68
+ break;
69
+ }
70
+ },
71
+ [maxTesuu, isSente]
72
+ );
73
+ useEffect(() => {
74
+ if (props.tesuu !== void 0) {
75
+ handleGoto(props.tesuu);
76
+ }
77
+ }, [props.tesuu]);
78
+ return /* @__PURE__ */ React.createElement("div", { className: "flex w-fit", tabIndex: 1, onKeyDown: handleKeydown }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ React.createElement("div", { className: "bg-[#f9d27a] text-black text-xs text-right p-1" }, isSente ? "\u2616 " + player.kifu.header["\u5F8C\u624B"] : "\u2617 " + player.kifu.header["\u5148\u624B"]), /* @__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: "bg-[#f9d27a] text-black text-xs p-1" }, isSente ? "\u2617 " + player.kifu.header["\u5148\u624B"] : "\u2616 " + player.kifu.header["\u5F8C\u624B"])), /* @__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 && tesuu !== 0 && /* @__PURE__ */ React.createElement("div", null, "\xA0"), tesuu === 0 && Object.entries(player.kifu.header).map(([key, value], i) => /* @__PURE__ */ React.createElement("div", { key: i }, key, ": ", value))), /* @__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
79
  }
46
80
  export {
47
81
  ShogiPlayer as default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hackersheet/next-document-content-kifu",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0-alpha.7",
4
4
  "description": "Hacker Sheet document content kifu components for Next.js",
5
5
  "keywords": [],
6
6
  "repository": {
@@ -27,7 +27,7 @@
27
27
  "shogi.js": "^5.4.1",
28
28
  "json-kifu-format": "^5.4.1",
29
29
  "@hackersheet/core": "0.1.0-alpha.11",
30
- "@hackersheet/react-document-content": "0.1.0-alpha.12"
30
+ "@hackersheet/react-document-content": "0.1.0-alpha.13"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/react": "^19.2.0",