+
+
+ {#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 @@
|