diff --git a/src/iframe/life-game.js b/src/iframe/life-game.js index 48f5517..ba97e7d 100644 --- a/src/iframe/life-game.js +++ b/src/iframe/life-game.js @@ -281,7 +281,6 @@ on.save_board = async () => { }; on.apply_board = (newBoard) => { - boardSize = newBoard.length; board = newBoard; renderBoard(); generationChange(0); diff --git a/src/lib/api/board.ts b/src/lib/api/board.ts index 9d62781..bf474eb 100644 --- a/src/lib/api/board.ts +++ b/src/lib/api/board.ts @@ -1,3 +1,5 @@ +import { toast } from "$lib/models/ToastStore.svelte"; + export async function saveBoard(data: { board: number[][]; name: string }, isJapanese: boolean) { try { const response = await fetch("/api/board", { @@ -13,16 +15,16 @@ export async function saveBoard(data: { board: number[][]; name: string }, isJap } if (isJapanese) { - alert("盤面を保存しました!"); + toast.show("盤面を保存しました!", "success"); } else { - alert("Board saved!"); + toast.show("Board saved!", "success"); } } catch (err) { console.error("Save Error:", err); if (isJapanese) { - alert("保存に失敗しました。"); + toast.show("保存に失敗しました。", "error"); } else { - alert("Failed to save."); + toast.show("Failed to save.", "error"); } } } @@ -52,9 +54,9 @@ export async function fetchBoardList(isJapanese: boolean): Promise diff --git a/src/lib/components/CodeModals.svelte b/src/lib/components/CodeModals.svelte index a1efac5..a7ee468 100644 --- a/src/lib/components/CodeModals.svelte +++ b/src/lib/components/CodeModals.svelte @@ -37,7 +37,7 @@ > diff --git a/src/lib/components/GlobalToast.svelte b/src/lib/components/GlobalToast.svelte new file mode 100644 index 0000000..e25e48f --- /dev/null +++ b/src/lib/components/GlobalToast.svelte @@ -0,0 +1,24 @@ + + +{#if toast.visible} +
+
+
+ {toast.message} +
+
+
+{/if} diff --git a/src/lib/models/ToastStore.svelte.ts b/src/lib/models/ToastStore.svelte.ts new file mode 100644 index 0000000..e6e141e --- /dev/null +++ b/src/lib/models/ToastStore.svelte.ts @@ -0,0 +1,38 @@ +export class ToastStore { + message = $state(""); + type = $state<"success" | "error" | "info">("info"); + visible = $state(false); + + private timerId: ReturnType | undefined = $state(undefined); + + /** + * トーストを表示する + * @param message 表示するメッセージ + * @param type "success" | "error" | "info" + * @param duration 表示時間(ミリ秒) + */ + show(message: string, type: "success" | "error" | "info" = "info", duration: number = 2000) { + this.message = message; + this.type = type; + this.visible = true; + + if (this.timerId) { + clearTimeout(this.timerId); + } + + this.timerId = setTimeout(() => { + this.hide(); + }, duration); + } + + hide() { + this.visible = false; + if (this.timerId) { + clearTimeout(this.timerId); + this.timerId = undefined; + } + } +} + +// グローバルで使うための単一インスタンスを作成してエクスポート +export const toast = new ToastStore(); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d8d8756..9600347 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,6 +1,7 @@ @@ -8,4 +9,4 @@ -{@render children?.()} +{@render children?.()} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index a3724e7..00ffdd9 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -9,6 +9,7 @@ import { CodeManager } from "$lib/models/CodeManager.svelte"; import BoardModals from "$lib/components/BoardModals.svelte"; import CodeModals from "$lib/components/CodeModals.svelte"; + import { toast } from "$lib/models/ToastStore.svelte"; import CodeMirror from "svelte-codemirror-editor"; import { javascript } from "@codemirror/lang-javascript"; import { oneDark } from "@codemirror/theme-one-dark"; @@ -32,6 +33,16 @@ const boardManager = new BoardManager(); const codeManager = new CodeManager(); + let disabledTemplates: { [key: string]: boolean } = $derived.by(() => { + const newDisabledState: { [key: string]: boolean } = {}; + for (const key in patterns) { + const patternName = key as keyof typeof patterns; + const patternData = patterns[patternName]; + newDisabledState[patternName] = sizeValue < (patternData.minBoardSize || 0); + } + return newDisabledState; + }); + let timer: "running" | "stopped" = $state("stopped"); let intervalMs = $state(1000); $effect(() => { @@ -45,7 +56,6 @@ type OngoingEvent = | "play" | "pause" - | "state_update" | "board_reset" | "board_randomize" | "place_template" @@ -69,10 +79,11 @@ break; } case "Size shortage": { - alert( + toast.show( isJapanese ? "盤面からはみ出してしまうため、キャンセルしました" : "This action was canceled because it would have extended beyond the board.", + "error", ); break; } @@ -101,7 +112,6 @@ async function onBoardSelect(id: number) { const board = await boardManager.load(id, isJapanese); if (board) { - sizeValue = board.length; sendEvent("apply_board", board); } } @@ -169,31 +179,27 @@
{#each Object.keys(patterns) as (keyof typeof patterns)[] as patternName (patternName)} -
+

{isJapanese ? patterns[patternName].names.ja : patterns[patternName].names.en}

-
- {isJapanese ? "第" + generationFigure + "世代" : "Generation:" + generationFigure} +
+ {isJapanese ? "世代数:" + generationFigure : "Generation:" + generationFigure}
@@ -329,17 +335,13 @@ accelerate -
+
{isJapanese ? "現在の速度" : "Current speed"}: x{1000 / intervalMs}
- - - -
@@ -418,7 +416,10 @@
{isJapanese ? "コード" : "Code"}: