From c71446053df2bdcf7545a902f48870340011f171 Mon Sep 17 00:00:00 2001 From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com> Date: Thu, 13 Nov 2025 19:35:35 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=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 --- .../migration.sql | 9 + prisma/schema.prisma | 7 + src/{routes/api.ts => lib/api/board.ts} | 0 src/lib/api/code.ts | 84 +++++++ src/lib/board-preview.ts | 2 +- src/routes/+page.svelte | 217 +++++++++++++++--- src/routes/api/board/+server.ts | 4 +- src/routes/api/code/+server.ts | 76 ++++++ 8 files changed, 362 insertions(+), 37 deletions(-) create mode 100644 prisma/migrations/20251113084438_add_code_state/migration.sql rename src/{routes/api.ts => lib/api/board.ts} (100%) create mode 100644 src/lib/api/code.ts create mode 100644 src/routes/api/code/+server.ts diff --git a/prisma/migrations/20251113084438_add_code_state/migration.sql b/prisma/migrations/20251113084438_add_code_state/migration.sql new file mode 100644 index 0000000..0cc905a --- /dev/null +++ b/prisma/migrations/20251113084438_add_code_state/migration.sql @@ -0,0 +1,9 @@ +-- CreateTable +CREATE TABLE "CodeState" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "codeData" TEXT NOT NULL, + "codeName" TEXT NOT NULL, + + CONSTRAINT "CodeState_pkey" PRIMARY KEY ("id") +); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4719673..7436838 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -22,3 +22,10 @@ model BoardState { boardName String boardPreview Json } + +model CodeState { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + codeData String + codeName String +} \ No newline at end of file diff --git a/src/routes/api.ts b/src/lib/api/board.ts similarity index 100% rename from src/routes/api.ts rename to src/lib/api/board.ts diff --git a/src/lib/api/code.ts b/src/lib/api/code.ts new file mode 100644 index 0000000..a771b87 --- /dev/null +++ b/src/lib/api/code.ts @@ -0,0 +1,84 @@ +export async function saveCode(data: { code: string; name: string }, isJapanese: boolean) { + try { + const response = await fetch("/api/code", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error("Failed to communicate with the server."); + } + + if (isJapanese) { + alert("コードを保存しました!"); + } else { + alert("Code saved!"); + } + } catch (err) { + console.error("Save Error:", err); + if (isJapanese) { + alert("保存に失敗しました。"); + } else { + alert("Failed to save."); + } + } +} + +export type CodeListItem = { + id: number; + codeName: string; + createdAt: string; +}; + +export async function fetchCodeList(isJapanese: boolean): Promise { + try { + const response = await fetch("/api/code"); + + if (!response.ok) { + if (response.status === 404) { + throw new Error("There is no saved data."); + } else { + throw new Error("Failed to communicate with the server."); + } + } + + const codeList = await response.json(); + + return codeList as CodeListItem[]; + } catch (err) { + console.error("Load error", err); + if (isJapanese) { + alert("読み込みに失敗しました。"); + } else { + alert("Failed to load."); + } + } +} + +export async function loadCodeById(id: number, isJapanese: boolean): Promise { + try { + const response = await fetch(`/api/code?id=${id}`); + + if (!response.ok) { + if (response.status === 404) { + throw new Error("The specified ID data was not found."); + } else { + throw new Error("Failed to communicate with the server."); + } + } + + const loadedCode = await response.json(); + + return loadedCode as string; + } catch (err) { + console.error("Load error", err); + if (isJapanese) { + alert("読み込みに失敗しました。"); + } else { + alert("Failed to load."); + } + } +} diff --git a/src/lib/board-preview.ts b/src/lib/board-preview.ts index 79736ab..97ec012 100644 --- a/src/lib/board-preview.ts +++ b/src/lib/board-preview.ts @@ -4,7 +4,7 @@ const PREVIEW_SIZE = 20; * 任意のサイズの盤面データから、中央 20x20 のプレビューを生成します。 * 20x20 に満たない場合は、中央に配置し、周囲を false (空白) で埋めます。 */ -export function createPreview(boardData: boolean[][]): boolean[][] { +export function createBoardPreview(boardData: boolean[][]): boolean[][] { const boardHeight = boardData.length; const boardWidth = boardData[0]?.length || 0; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 578ef5e..3bcb4ee 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -5,8 +5,9 @@ import lifeGameJS from "@/iframe/life-game.js?raw"; 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"; + import { createBoardPreview } from "$lib/board-preview"; + import { saveBoard, fetchBoardList, loadBoardById, type BoardListItem } from "$lib/api/board"; + import { saveCode, fetchCodeList, loadCodeById, type CodeListItem } from "$lib/api/code"; let editingCode = $state(lifeGameJS); let appliedCode = $state(lifeGameJS); @@ -24,16 +25,25 @@ let generationFigure = $state(0); let sizeValue = $state(20); - type SaveState = + type SaveBoardState = | { saving: false } | { saving: true; boardData: boolean[][]; boardName: string; boardPreview: boolean[][] }; - let saveState: SaveState = $state({ saving: false }); + let saveBoardState: SaveBoardState = $state({ saving: false }); - type LoadState = + type LoadBoardState = | { state: "closed" } | { state: "loading" } | { state: "list"; list: BoardListItem[] }; - let loadState: LoadState = $state({ state: "closed" }); + let loadBoardState: LoadBoardState = $state({ state: "closed" }); + + type SaveCodeState = { saving: false } | { saving: true; codeData: string; codeName: string }; + let saveCodeState: SaveCodeState = $state({ saving: false }); + + type LoadCodeState = + | { state: "closed" } + | { state: "loading" } + | { state: "list"; list: CodeListItem[] }; + let loadCodeState: LoadCodeState = $state({ state: "closed" }); type OngoingEvent = | "play" @@ -65,8 +75,8 @@ } case "save_board": { const board = event.data.data as boolean[][]; - const preview = createPreview(board); - saveState = { saving: true, boardData: board, boardName: "", boardPreview: preview }; + const preview = createBoardPreview(board); + saveBoardState = { saving: true, boardData: board, boardName: "", boardPreview: preview }; break; } default: { @@ -87,30 +97,31 @@ preview_iframe?.contentWindow?.postMessage({ type: event, data: message }, "*"); } - async function handleSave() { - if (!saveState.saving) return; + async function handleBoardSave() { + if (!saveBoardState.saving) return; - const name = saveState.boardName.trim() === "" ? "Unnamed Board" : saveState.boardName.trim(); + const name = + saveBoardState.boardName.trim() === "" ? "Unnamed Board" : saveBoardState.boardName.trim(); - await saveBoard({ board: saveState.boardData, name: name }, isJapanese); + await saveBoard({ board: saveBoardState.boardData, name: name }, isJapanese); - saveState = { saving: false }; + saveBoardState = { saving: false }; } - async function handleLoad() { - loadState = { state: "loading" }; + async function handleBoardLoad() { + loadBoardState = { state: "loading" }; const list = await fetchBoardList(isJapanese); if (list) { - loadState = { state: "list", list }; + loadBoardState = { state: "list", list }; } else { - loadState = { state: "closed" }; + loadBoardState = { state: "closed" }; } } async function selectBoard(id: number) { - loadState = { state: "closed" }; + loadBoardState = { state: "closed" }; const board = await loadBoardById(id, isJapanese); if (board) { @@ -118,6 +129,38 @@ sendEvent("apply_board", board); } } + + async function handleCodeSave() { + if (!saveCodeState.saving) return; + + const name = + saveCodeState.codeName.trim() === "" ? "Unnamed Code" : saveCodeState.codeName.trim(); + + await saveCode({ code: saveCodeState.codeData, name: name }, isJapanese); + + saveCodeState = { saving: false }; + } + + async function handleCodeLoad() { + loadCodeState = { state: "loading" }; + + const list = await fetchCodeList(isJapanese); + + if (list) { + loadCodeState = { state: "list", list }; + } else { + loadCodeState = { state: "closed" }; + } + } + + async function selectCode(id: number) { + loadCodeState = { state: "closed" }; + + const code = await loadCodeById(id, isJapanese); + if (code) { + editingCode = code; + } + } - + - +