From 9af2df7c760b383bbb087923b85fe899c134eaec Mon Sep 17 00:00:00 2001
From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com>
Date: Mon, 10 Nov 2025 21:27:46 +0900
Subject: [PATCH 1/6] =?UTF-8?q?=E7=9B=A4=E9=9D=A2=E3=81=AE=E4=BF=9D?=
=?UTF-8?q?=E5=AD=98=E6=99=82=E3=81=AB=E5=81=9C=E6=AD=A2=E3=81=97=E3=81=AA?=
=?UTF-8?q?=E3=81=84=E5=95=8F=E9=A1=8C=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 | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 98c0e5d..2f66966 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -374,6 +374,7 @@
class="btn btn-ghost hover:bg-[rgb(220,220,220)] text-black"
onclick={() => {
isProgress = false;
+ sendEvent("pause");
sendEvent("save_board");
}}
>
From aa545a60e582245d18638bc533613702da1b65dc Mon Sep 17 00:00:00 2001
From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com>
Date: Tue, 11 Nov 2025 12:05:46 +0900
Subject: [PATCH 2/6] =?UTF-8?q?=E7=9B=A4=E9=9D=A2=E9=81=B8=E6=8A=9E?=
=?UTF-8?q?=E7=94=BB=E9=9D=A2=E3=81=AE=E4=BD=9C=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/routes/+page.svelte | 74 +++++++++++++++++++++++++++++++--
src/routes/api.ts | 47 ++++++++++++++++++++-
src/routes/api/board/+server.ts | 48 ++++++++++++++++-----
3 files changed, 153 insertions(+), 16 deletions(-)
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 2f66966..f4f76f2 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -5,7 +5,7 @@
import lifeGameJS from "@/iframe/life-game.js?raw";
import patterns from "$lib/board-templates";
import * as icons from "$lib/icons/index.ts";
- import { loadBoard, saveBoard } from "./api.ts";
+ import { saveBoard, fetchBoardList, loadBoardById, type BoardListItem } from "./api.ts";
let editingCode = $state(lifeGameJS);
let appliedCode = $state(lifeGameJS);
@@ -27,6 +27,12 @@
let saveState: SaveState = $state({ saving: false });
let boardNameInput = $state("");
+ type LoadState =
+ | { state: "closed" }
+ | { state: "loading" }
+ | { state: "list"; list: BoardListItem[] };
+ let loadState: LoadState = $state({ state: "closed" });
+
type OngoingEvent =
| "play"
| "pause"
@@ -90,11 +96,24 @@
}
async function handleLoad() {
- const board = await loadBoard(isJapanese);
+ loadState = { state: "loading" };
+
+ const list = await fetchBoardList(isJapanese);
+
+ if (list) {
+ loadState = { state: "list", list };
+ } else {
+ loadState = { state: "closed" };
+ }
+ }
+
+ async function selectBoard(id: number) {
+ loadState = { state: "closed" };
+
+ const board = await loadBoardById(id, isJapanese);
if (board) {
sendEvent("apply_board", board);
}
- return;
}
@@ -222,6 +241,55 @@
+
+
+
+
{isJapanese ? "盤面をロード" : "Load board"}
+
+ {#if loadState.state === "loading"}
+
+ {isJapanese ? "保存されている盤面を読み込み中..." : "Loading saved boards..."}
+
+
+ {:else if loadState.state === "list" && loadState.list.length === 0}
+
+ {isJapanese ? "保存されている盤面はありません。" : "No saved boards found."}
+
+ {:else if loadState.state === "list"}
+
+
+
+
+ | {isJapanese ? "盤面名" : "Board Name"} |
+ {isJapanese ? "保存日時" : "Saved At"} |
+ |
+
+
+
+ {#each loadState.list as item (item.id)}
+
+ | {item.boardName} |
+ {new Date(item.createdAt).toLocaleString(isJapanese ? "ja-JP" : "en-US")} |
+
+
+ |
+
+ {/each}
+
+
+
+ {/if}
+
+
+
+
+
+
+
diff --git a/src/routes/api.ts b/src/routes/api.ts
index 711c995..0c126da 100644
--- a/src/routes/api.ts
+++ b/src/routes/api.ts
@@ -32,7 +32,13 @@ export async function saveBoard(data: { board: boolean[][]; name: string }, isJa
}
}
-export async function loadBoard(isJapanese: boolean): Promise
{
+export type BoardListItem = {
+ id: number;
+ boardName: string;
+ createdAt: string;
+};
+
+export async function fetchBoardList(isJapanese: boolean): Promise {
try {
const response = await fetch("/api/board");
@@ -52,9 +58,46 @@ export async function loadBoard(isJapanese: boolean): Promise {
+ try {
+ const response = await fetch(`/api/board?id=${id}`);
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ if (isJapanese) {
+ throw new Error("指定されたIDのデータが見つかりません。");
+ } else {
+ throw new Error("The specified ID data was not found.");
+ }
+ } else {
+ if (isJapanese) {
+ throw new Error("サーバーとの通信に失敗しました。");
+ } else {
+ throw new Error("Failed to communicate with the server.");
+ }
+ }
+ }
+
const loadedBoard = await response.json();
- return loadedBoard as boolean[][]; // TODO: add proper types
+ return loadedBoard as boolean[][];
} catch (err) {
if (isJapanese) {
console.error("読込エラー:", err);
diff --git a/src/routes/api/board/+server.ts b/src/routes/api/board/+server.ts
index 9270476..d79eeed 100644
--- a/src/routes/api/board/+server.ts
+++ b/src/routes/api/board/+server.ts
@@ -28,17 +28,43 @@ export async function POST({ request }) {
return json(newState, { status: 201 });
}
-export async function GET() {
- // データベースから一番「最後」に保存されたデータを1件だけ探す
- const latestState = await prisma.boardState.findFirst({
- orderBy: {
- createdAt: "desc", // 作成日時(createdAt)の降順(desc)で並び替え
- },
- });
+export async function GET({ url }) {
+ const boardId = url.searchParams.get("id");
- if (!latestState) {
- return json({ message: "No state found" }, { status: 404 });
- }
+ if (boardId) {
+ //IDが指定された場合、そのIDの盤面を返す
+ const id = parseInt(boardId, 10);
+ if (isNaN(id)) {
+ return json({ message: "無効なIDです。" }, { status: 400 });
+ }
+
+ const state = await prisma.boardState.findUnique({
+ where: { id: id },
+ select: { boardData: true },
+ });
- return json(latestState.boardData);
+ if (!state) {
+ return json({ message: `ID: ${id} の盤面は見つかりません。` }, { status: 404 });
+ }
+
+ return json(state.boardData);
+ } else {
+ //IDが指定されなかった場合、全ての盤面のリストを返す
+ const allStates = await prisma.boardState.findMany({
+ orderBy: {
+ createdAt: "desc",
+ },
+ select: {
+ id: true,
+ boardName: true,
+ createdAt: true,
+ },
+ });
+
+ if (!allStates || allStates.length === 0) {
+ return json({ message: "No state found" }, { status: 404 });
+ }
+
+ return json(allStates);
+ }
}
From 53fff5bc1efad269c8552b5555efe479d26f6576 Mon Sep 17 00:00:00 2001
From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com>
Date: Tue, 11 Nov 2025 13:06:25 +0900
Subject: [PATCH 3/6] =?UTF-8?q?boardNameInput=E3=82=92saveState=E3=81=AB?=
=?UTF-8?q?=E7=B5=84=E3=81=BF=E8=BE=BC=E3=81=BF=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/+page.svelte | 49 ++++++++++++++++++++---------------------
1 file changed, 24 insertions(+), 25 deletions(-)
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index f4f76f2..5874b78 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -23,9 +23,8 @@
let generationFigure = $state(0);
let sizeValue = $state(20);
- type SaveState = { saving: false } | { saving: true; boardData: boolean[][] };
+ type SaveState = { saving: false } | { saving: true; boardData: boolean[][]; boardName: string };
let saveState: SaveState = $state({ saving: false });
- let boardNameInput = $state("");
type LoadState =
| { state: "closed" }
@@ -62,8 +61,7 @@
break;
}
case "save_board": {
- saveState = { saving: true, boardData: event.data.data as boolean[][] };
- boardNameInput = "";
+ saveState = { saving: true, boardData: event.data.data as boolean[][], boardName: "" };
break;
}
default: {
@@ -87,12 +85,11 @@
async function handleSave() {
if (!saveState.saving) return;
- const name = boardNameInput.trim() === "" ? "Unnamed Board" : boardNameInput.trim();
+ const name = saveState.boardName.trim() === "" ? "Unnamed Board" : saveState.boardName.trim();
await saveBoard({ board: saveState.boardData, name: name }, isJapanese);
saveState = { saving: false };
- boardNameInput = "";
}
async function handleLoad() {
@@ -219,25 +216,27 @@
{isJapanese ? "盤面を保存" : "Save board"}
-
- {isJapanese
- ? "保存する盤面に名前を付けてください(任意)。"
- : "Please name the board you wish to save (optional)."}
-
-
-
-
-
-
+ {#if saveState.saving}
+
+ {isJapanese
+ ? "保存する盤面に名前を付けてください(任意)。"
+ : "Please name the board you wish to save (optional)."}
+
+
+
+
+
+
+ {/if}
From 2dc1705fe1e4c93c017f5520c146fcff8c11a963 Mon Sep 17 00:00:00 2001
From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com>
Date: Tue, 11 Nov 2025 17:59:17 +0900
Subject: [PATCH 4/6] =?UTF-8?q?Enter=E3=82=AD=E3=83=BC=E3=82=92=E6=8A=BC?=
=?UTF-8?q?=E3=81=97=E3=81=A6=E3=80=81=E7=9B=A4=E9=9D=A2=E3=81=8C=E4=BF=9D?=
=?UTF-8?q?=E5=AD=98=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?=
=?UTF-8?q?=E3=81=AA=E3=82=8A=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/+page.svelte | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 5874b78..2533657 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -227,6 +227,12 @@
placeholder={isJapanese ? "盤面名を入力" : "Enter board name"}
class="input input-bordered w-full max-w-xs"
bind:value={saveState.boardName}
+ onkeydown={(e) => {
+ if (e.key === "Enter" && !e.isComposing) {
+ e.preventDefault();
+ handleSave();
+ }
+ }}
/>
-
-
+
-
+
-
-
+
-
+
-
-
+
-
+
Date: Tue, 11 Nov 2025 21:30:27 +0900
Subject: [PATCH 6/6] =?UTF-8?q?onkeydown=E3=82=92=E5=89=8A=E9=99=A4?=
=?UTF-8?q?=E3=81=97=E3=80=81