From cc67602d3c7422ec73f06dad00a72c08e926eee2 Mon Sep 17 00:00:00 2001 From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:52:39 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=E7=9B=A4=E9=9D=A2=E3=81=AE=E3=83=97?= =?UTF-8?q?=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC=E3=82=92=E7=9B=A4=E9=9D=A2?= =?UTF-8?q?=E9=81=B8=E6=8A=9E=E7=94=BB=E9=9D=A2=E3=81=AB=E6=8F=8F=E7=94=BB?= =?UTF-8?q?=E3=81=99=E3=82=8B=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/+page.svelte | 34 +++++++++++++++++++++++++++++++++ src/routes/api.ts | 1 + src/routes/api/board/+server.ts | 1 + 3 files changed, 36 insertions(+) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index f2ec61f..373520f 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -262,6 +262,7 @@ + @@ -270,6 +271,17 @@ {#each loadState.list as item (item.id)} +
{isJapanese ? "プレビュー" : "Preview"} {isJapanese ? "盤面名" : "Board Name"} {isJapanese ? "保存日時" : "Saved At"}
+
+ {#each item.boardData as row, i (i)} +
+ {#each row as cell, j (j)} +
+ {/each} +
+ {/each} +
+
{item.boardName} {new Date(item.createdAt).toLocaleString(isJapanese ? "ja-JP" : "en-US")} @@ -494,3 +506,25 @@ + + diff --git a/src/routes/api.ts b/src/routes/api.ts index 0c126da..535858e 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -36,6 +36,7 @@ export type BoardListItem = { id: number; boardName: string; createdAt: string; + boardData: boolean[][]; }; export async function fetchBoardList(isJapanese: boolean): Promise { diff --git a/src/routes/api/board/+server.ts b/src/routes/api/board/+server.ts index d79eeed..cf3bb1d 100644 --- a/src/routes/api/board/+server.ts +++ b/src/routes/api/board/+server.ts @@ -58,6 +58,7 @@ export async function GET({ url }) { id: true, boardName: true, createdAt: true, + boardData: true, }, }); From 3ac530f05b5d5c2e305e1e0cc70a0c799a1c3b18 Mon Sep 17 00:00:00 2001 From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com> Date: Wed, 12 Nov 2025 19:55:19 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=E3=82=B5=E3=82=A4=E3=82=BA20=E4=BB=A5?= =?UTF-8?q?=E5=A4=96=E3=81=AE=E7=9B=A4=E9=9D=A2=E3=81=AB=E3=81=A4=E3=81=84?= =?UTF-8?q?=E3=81=A6=E3=82=82=E3=83=97=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=81=8C=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration.sql | 8 ++ prisma/schema.prisma | 1 + src/lib/board-preview.ts | 38 +++++++++ src/routes/+page.svelte | 80 ++++++++++++------- src/routes/api.ts | 7 +- src/routes/api/board/+server.ts | 6 +- 6 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 prisma/migrations/20251112091315_add_board_preview_column/migration.sql create mode 100644 src/lib/board-preview.ts diff --git a/prisma/migrations/20251112091315_add_board_preview_column/migration.sql b/prisma/migrations/20251112091315_add_board_preview_column/migration.sql new file mode 100644 index 0000000..0de9d26 --- /dev/null +++ b/prisma/migrations/20251112091315_add_board_preview_column/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `boardPreview` to the `BoardState` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "BoardState" ADD COLUMN "boardPreview" JSONB NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ff842dc..4719673 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,4 +20,5 @@ model BoardState { createdAt DateTime @default(now()) boardData Json boardName String + boardPreview Json } diff --git a/src/lib/board-preview.ts b/src/lib/board-preview.ts new file mode 100644 index 0000000..79736ab --- /dev/null +++ b/src/lib/board-preview.ts @@ -0,0 +1,38 @@ +const PREVIEW_SIZE = 20; + +/** + * 任意のサイズの盤面データから、中央 20x20 のプレビューを生成します。 + * 20x20 に満たない場合は、中央に配置し、周囲を false (空白) で埋めます。 + */ +export function createPreview(boardData: boolean[][]): boolean[][] { + const boardHeight = boardData.length; + const boardWidth = boardData[0]?.length || 0; + + const finalPreview: boolean[][] = Array.from({ length: PREVIEW_SIZE }, () => + Array(PREVIEW_SIZE).fill(false), + ); + + const sourceStartRow = Math.max(0, Math.floor((boardHeight - PREVIEW_SIZE) / 2)); + const sourceStartCol = Math.max(0, Math.floor((boardWidth - PREVIEW_SIZE) / 2)); + + const destStartRow = Math.max(0, Math.floor((PREVIEW_SIZE - boardHeight) / 2)); + const destStartCol = Math.max(0, Math.floor((PREVIEW_SIZE - boardWidth) / 2)); + + const rowsToCopy = Math.min(PREVIEW_SIZE - destStartRow, boardHeight - sourceStartRow); + const colsToCopy = Math.min(PREVIEW_SIZE - destStartCol, boardWidth - sourceStartCol); + + if (rowsToCopy <= 0 || colsToCopy <= 0) { + return finalPreview; + } + + for (let i = 0; i < rowsToCopy; i++) { + for (let j = 0; j < colsToCopy; j++) { + if (boardData[sourceStartRow + i]?.[sourceStartCol + j] !== undefined) { + finalPreview[destStartRow + i][destStartCol + j] = + boardData[sourceStartRow + i][sourceStartCol + j]; + } + } + } + + return finalPreview; +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 373520f..a73e55f 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -6,6 +6,7 @@ import patterns from "$lib/board-templates"; import * as icons from "$lib/icons/index.ts"; import { saveBoard, fetchBoardList, loadBoardById, type BoardListItem } from "./api.ts"; + import { createPreview } from "$lib/board-preview"; let editingCode = $state(lifeGameJS); let appliedCode = $state(lifeGameJS); @@ -23,7 +24,9 @@ let generationFigure = $state(0); let sizeValue = $state(20); - type SaveState = { saving: false } | { saving: true; boardData: boolean[][]; boardName: string }; + type SaveState = + | { saving: false } + | { saving: true; boardData: boolean[][]; boardName: string; boardPreview: boolean[][] }; let saveState: SaveState = $state({ saving: false }); type LoadState = @@ -61,7 +64,9 @@ break; } case "save_board": { - saveState = { saving: true, boardData: event.data.data as boolean[][], boardName: "" }; + const board = event.data.data as boolean[][]; + const preview = createPreview(board); + saveState = { saving: true, boardData: board, boardName: "", boardPreview: preview }; break; } default: { @@ -87,7 +92,10 @@ const name = saveState.boardName.trim() === "" ? "Unnamed Board" : saveState.boardName.trim(); - await saveBoard({ board: saveState.boardData, name: name }, isJapanese); + await saveBoard( + { board: saveState.boardData, name: name, preview: saveState.boardPreview }, + isJapanese, + ); saveState = { saving: false }; } @@ -216,29 +224,47 @@
- {#each item.boardData as row, i (i)} + {#each item.boardPreview as row, i (i)}
{#each row as cell, j (j)}
diff --git a/src/routes/api.ts b/src/routes/api.ts index 535858e..9c282d4 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1,4 +1,7 @@ -export async function saveBoard(data: { board: boolean[][]; name: string }, isJapanese: boolean) { +export async function saveBoard( + data: { board: boolean[][]; name: string; preview: boolean[][] }, + isJapanese: boolean, +) { try { const response = await fetch("/api/board", { method: "POST", @@ -36,7 +39,7 @@ export type BoardListItem = { id: number; boardName: string; createdAt: string; - boardData: boolean[][]; + boardPreview: boolean[][]; }; export async function fetchBoardList(isJapanese: boolean): Promise { diff --git a/src/routes/api/board/+server.ts b/src/routes/api/board/+server.ts index cf3bb1d..965669a 100644 --- a/src/routes/api/board/+server.ts +++ b/src/routes/api/board/+server.ts @@ -5,6 +5,7 @@ import * as v from "valibot"; const BoardSchema = v.object({ board: v.array(v.array(v.boolean())), name: v.pipe(v.string(), v.minLength(1, "盤面名は必須です。")), + preview: v.array(v.array(v.boolean())), }); export async function POST({ request }) { @@ -16,12 +17,13 @@ export async function POST({ request }) { return json({ message: "無効なリクエストデータです。" }, { status: 400 }); } - const { board, name } = body; + const { board, name, preview } = body; const newState = await prisma.boardState.create({ data: { boardData: board, boardName: name, + boardPreview: preview, }, }); @@ -58,7 +60,7 @@ export async function GET({ url }) { id: true, boardName: true, createdAt: true, - boardData: true, + boardPreview: true, }, }); From 4c74a187274bbe1e18f728ad429f98fcaa0a9be3 Mon Sep 17 00:00:00 2001 From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com> Date: Wed, 12 Nov 2025 20:14:30 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E3=81=AE=E3=83=9F=E3=82=B9=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/+page.svelte | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index a73e55f..0708c3f 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -237,19 +237,6 @@ class="input input-bordered w-full max-w-xs" bind:value={saveState.boardName} /> -
@@ -266,6 +253,19 @@
+ {/if} From 1d3b10034d8d799447f394cf432c73c9d8fadfcb Mon Sep 17 00:00:00 2001 From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com> Date: Wed, 12 Nov 2025 21:20:00 +0900 Subject: [PATCH 4/7] =?UTF-8?q?=E7=9B=A4=E9=9D=A2=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=89=E6=99=82=E3=81=AB=E3=80=81=E7=9B=A4=E9=9D=A2=E3=82=B5?= =?UTF-8?q?=E3=82=A4=E3=82=BA=E3=82=92=E5=A4=89=E6=9B=B4=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/iframe/life-game.js | 1 + src/routes/+page.svelte | 1 + 2 files changed, 2 insertions(+) diff --git a/src/iframe/life-game.js b/src/iframe/life-game.js index 8ce2604..91f86a4 100644 --- a/src/iframe/life-game.js +++ b/src/iframe/life-game.js @@ -209,6 +209,7 @@ on.save_board = async () => { }; on.apply_board = (newBoard) => { + boardSize = newBoard.length; board = newBoard; renderBoard(); generationChange(0); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 0708c3f..211a7a2 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -117,6 +117,7 @@ const board = await loadBoardById(id, isJapanese); if (board) { + sizeValue = board.length; sendEvent("apply_board", board); } } From 9610bd8fe7519f485dc49fdb0a3097e6ffd351d3 Mon Sep 17 00:00:00 2001 From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com> Date: Wed, 12 Nov 2025 22:27:47 +0900 Subject: [PATCH 5/7] =?UTF-8?q?=E7=9B=A4=E9=9D=A2=E3=82=B5=E3=82=A4?= =?UTF-8?q?=E3=82=BA=E3=81=8C=E5=A4=A7=E3=81=8D=E3=81=84=E3=81=A8=E3=81=8D?= =?UTF-8?q?=E7=9B=A4=E9=9D=A2=E3=81=AE=E5=B7=A6=E7=AB=AF=E3=81=8C=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/iframe/life-game.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/iframe/life-game.html b/src/iframe/life-game.html index 8d75d39..0c4d69c 100644 --- a/src/iframe/life-game.html +++ b/src/iframe/life-game.html @@ -5,6 +5,7 @@ -
+
+
+
From c4b5a7772ccc23dbb3a9ef190ad47f875dd4b8ea Mon Sep 17 00:00:00 2001 From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com> Date: Thu, 13 Nov 2025 09:42:29 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=E7=9B=A4=E9=9D=A2=E3=81=AE=E3=83=97?= =?UTF-8?q?=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC=E3=82=92=E3=82=B5=E3=83=BC?= =?UTF-8?q?=E3=83=90=E3=83=BC=E5=81=B4=E3=81=A7=E4=BD=9C=E6=88=90=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E5=A4=89=E6=9B=B4=E3=81=97=E3=81=BE?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/+page.svelte | 5 +---- src/routes/api.ts | 5 +---- src/routes/api/board/+server.ts | 17 ++++++++++++----- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 211a7a2..578ef5e 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -92,10 +92,7 @@ const name = saveState.boardName.trim() === "" ? "Unnamed Board" : saveState.boardName.trim(); - await saveBoard( - { board: saveState.boardData, name: name, preview: saveState.boardPreview }, - isJapanese, - ); + await saveBoard({ board: saveState.boardData, name: name }, isJapanese); saveState = { saving: false }; } diff --git a/src/routes/api.ts b/src/routes/api.ts index 9c282d4..c77ea2d 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1,7 +1,4 @@ -export async function saveBoard( - data: { board: boolean[][]; name: string; preview: boolean[][] }, - isJapanese: boolean, -) { +export async function saveBoard(data: { board: boolean[][]; name: string }, isJapanese: boolean) { try { const response = await fetch("/api/board", { method: "POST", diff --git a/src/routes/api/board/+server.ts b/src/routes/api/board/+server.ts index 965669a..06e6f48 100644 --- a/src/routes/api/board/+server.ts +++ b/src/routes/api/board/+server.ts @@ -1,23 +1,30 @@ import { json } from "@sveltejs/kit"; import { prisma } from "@/lib/prisma.server.ts"; +import { createPreview } from "@/lib/board-preview.js"; import * as v from "valibot"; const BoardSchema = v.object({ board: v.array(v.array(v.boolean())), name: v.pipe(v.string(), v.minLength(1, "盤面名は必須です。")), - preview: v.array(v.array(v.boolean())), }); export async function POST({ request }) { - let body; + let requestData; try { - body = v.parse(BoardSchema, await request.json()); + requestData = await request.json(); } catch (error) { - console.error("Request validation failed:", error); + console.error("Request body JSON parsing failed:", error); + return json({ message: "無効なリクエスト形式です。" }, { status: 400 }); + } + + const result = v.safeParse(BoardSchema, requestData); + if (!result.success) { + console.error("Request validation failed:", result.issues); return json({ message: "無効なリクエストデータです。" }, { status: 400 }); } - const { board, name, preview } = body; + const { board, name } = result.output; + const preview = createPreview(board); const newState = await prisma.boardState.create({ data: { From 765f4c4e68d0dc6b7f6947de95f65ca2683392d9 Mon Sep 17 00:00:00 2001 From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com> Date: Thu, 13 Nov 2025 09:45:22 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=A1?= =?UTF-8?q?=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E5=B0=91=E3=81=97?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=81=97=E3=81=BE=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/api.ts | 39 +++++++-------------------------- src/routes/api/board/+server.ts | 4 ++-- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/src/routes/api.ts b/src/routes/api.ts index c77ea2d..a20f085 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -9,11 +9,7 @@ export async function saveBoard(data: { board: boolean[][]; name: string }, isJa }); if (!response.ok) { - if (isJapanese) { - throw new Error("サーバーとの通信に失敗しました。"); - } else { - throw new Error("Failed to communicate with the server."); - } + throw new Error("Failed to communicate with the server."); } if (isJapanese) { @@ -22,11 +18,10 @@ export async function saveBoard(data: { board: boolean[][]; name: string }, isJa alert("Board saved!"); } } catch (err) { + console.error("Save Error:", err); if (isJapanese) { - console.error("保存エラー:", err); alert("保存に失敗しました。"); } else { - console.error("Save Error:", err); alert("Failed to save."); } } @@ -45,17 +40,9 @@ export async function fetchBoardList(isJapanese: boolean): Promise