|
5 | 5 | import lifeGameJS from "@/iframe/life-game.js?raw"; |
6 | 6 | import patterns from "$lib/board-templates"; |
7 | 7 | import * as icons from "$lib/icons/index.ts"; |
8 | | - import { loadBoard, saveBoard } from "./api.ts"; |
| 8 | + import { saveBoard, fetchBoardList, loadBoardById, type BoardListItem } from "./api.ts"; |
9 | 9 |
|
10 | 10 | let editingCode = $state(lifeGameJS); |
11 | 11 | let appliedCode = $state(lifeGameJS); |
|
23 | 23 | let generationFigure = $state(0); |
24 | 24 | let sizeValue = $state(20); |
25 | 25 |
|
26 | | - type SaveState = { saving: false } | { saving: true; boardData: boolean[][] }; |
| 26 | + type SaveState = { saving: false } | { saving: true; boardData: boolean[][]; boardName: string }; |
27 | 27 | let saveState: SaveState = $state({ saving: false }); |
28 | | - let boardNameInput = $state(""); |
| 28 | +
|
| 29 | + type LoadState = |
| 30 | + | { state: "closed" } |
| 31 | + | { state: "loading" } |
| 32 | + | { state: "list"; list: BoardListItem[] }; |
| 33 | + let loadState: LoadState = $state({ state: "closed" }); |
29 | 34 |
|
30 | 35 | type OngoingEvent = |
31 | 36 | | "play" |
|
56 | 61 | break; |
57 | 62 | } |
58 | 63 | case "save_board": { |
59 | | - saveState = { saving: true, boardData: event.data.data as boolean[][] }; |
60 | | - boardNameInput = ""; |
| 64 | + saveState = { saving: true, boardData: event.data.data as boolean[][], boardName: "" }; |
61 | 65 | break; |
62 | 66 | } |
63 | 67 | default: { |
|
81 | 85 | async function handleSave() { |
82 | 86 | if (!saveState.saving) return; |
83 | 87 |
|
84 | | - const name = boardNameInput.trim() === "" ? "Unnamed Board" : boardNameInput.trim(); |
| 88 | + const name = saveState.boardName.trim() === "" ? "Unnamed Board" : saveState.boardName.trim(); |
85 | 89 |
|
86 | 90 | await saveBoard({ board: saveState.boardData, name: name }, isJapanese); |
87 | 91 |
|
88 | 92 | saveState = { saving: false }; |
89 | | - boardNameInput = ""; |
90 | 93 | } |
91 | 94 |
|
92 | 95 | async function handleLoad() { |
93 | | - const board = await loadBoard(isJapanese); |
| 96 | + loadState = { state: "loading" }; |
| 97 | +
|
| 98 | + const list = await fetchBoardList(isJapanese); |
| 99 | +
|
| 100 | + if (list) { |
| 101 | + loadState = { state: "list", list }; |
| 102 | + } else { |
| 103 | + loadState = { state: "closed" }; |
| 104 | + } |
| 105 | + } |
| 106 | +
|
| 107 | + async function selectBoard(id: number) { |
| 108 | + loadState = { state: "closed" }; |
| 109 | +
|
| 110 | + const board = await loadBoardById(id, isJapanese); |
94 | 111 | if (board) { |
95 | 112 | sendEvent("apply_board", board); |
96 | 113 | } |
97 | | - return; |
98 | 114 | } |
99 | 115 | </script> |
100 | 116 |
|
|
196 | 212 | </div> |
197 | 213 | </div> |
198 | 214 |
|
199 | | -<input type="checkbox" class="modal-toggle" bind:checked={saveState.saving} /> |
200 | | -<div class="modal" class:modal-open={saveState.saving}> |
201 | | - <div class="modal-box"> |
| 215 | +<dialog class="modal" open={saveState.saving}> |
| 216 | + <form method="dialog" class="modal-box"> |
202 | 217 | <h3 class="font-bold text-lg">{isJapanese ? "盤面を保存" : "Save board"}</h3> |
203 | | - <p class="py-4"> |
204 | | - {isJapanese |
205 | | - ? "保存する盤面に名前を付けてください(任意)。" |
206 | | - : "Please name the board you wish to save (optional)."} |
207 | | - </p> |
208 | | - <input |
209 | | - type="text" |
210 | | - placeholder={isJapanese ? "盤面名を入力" : "Enter board name"} |
211 | | - class="input input-bordered w-full max-w-xs" |
212 | | - bind:value={boardNameInput} |
213 | | - /> |
| 218 | + {#if saveState.saving} |
| 219 | + <p class="py-4"> |
| 220 | + {isJapanese |
| 221 | + ? "保存する盤面に名前を付けてください(任意)。" |
| 222 | + : "Please name the board you wish to save (optional)."} |
| 223 | + </p> |
| 224 | + <input |
| 225 | + type="text" |
| 226 | + placeholder={isJapanese ? "盤面名を入力" : "Enter board name"} |
| 227 | + class="input input-bordered w-full max-w-xs" |
| 228 | + bind:value={saveState.boardName} |
| 229 | + /> |
| 230 | + <div class="modal-action"> |
| 231 | + <button type="button" class="btn" onclick={() => (saveState = { saving: false })} |
| 232 | + >{isJapanese ? "キャンセル" : "Cancel"}</button |
| 233 | + > |
| 234 | + <button |
| 235 | + type="submit" |
| 236 | + class="btn btn-primary" |
| 237 | + onclick={handleSave} |
| 238 | + disabled={!saveState.saving} |
| 239 | + > |
| 240 | + {isJapanese ? "保存" : "Save"} |
| 241 | + </button> |
| 242 | + </div> |
| 243 | + {/if} |
| 244 | + </form> |
| 245 | +</dialog> |
| 246 | + |
| 247 | +<dialog class="modal" open={loadState.state !== "closed"}> |
| 248 | + <div class="modal-box w-11/12 max-w-5xl"> |
| 249 | + <h3 class="font-bold text-lg">{isJapanese ? "盤面をロード" : "Load board"}</h3> |
| 250 | + |
| 251 | + {#if loadState.state === "loading"} |
| 252 | + <p class="py-4"> |
| 253 | + {isJapanese ? "保存されている盤面を読み込み中..." : "Loading saved boards..."} |
| 254 | + </p> |
| 255 | + <span class="loading loading-spinner loading-lg"></span> |
| 256 | + {:else if loadState.state === "list" && loadState.list.length === 0} |
| 257 | + <p class="py-4"> |
| 258 | + {isJapanese ? "保存されている盤面はありません。" : "No saved boards found."} |
| 259 | + </p> |
| 260 | + {:else if loadState.state === "list"} |
| 261 | + <div class="overflow-x-auto h-96"> |
| 262 | + <table class="table w-full"> |
| 263 | + <thead> |
| 264 | + <tr> |
| 265 | + <th>{isJapanese ? "盤面名" : "Board Name"}</th> |
| 266 | + <th>{isJapanese ? "保存日時" : "Saved At"}</th> |
| 267 | + <th></th> |
| 268 | + </tr> |
| 269 | + </thead> |
| 270 | + <tbody> |
| 271 | + {#each loadState.list as item (item.id)} |
| 272 | + <tr class="hover:bg-base-300"> |
| 273 | + <td>{item.boardName}</td> |
| 274 | + <td>{new Date(item.createdAt).toLocaleString(isJapanese ? "ja-JP" : "en-US")}</td> |
| 275 | + <td class="text-right"> |
| 276 | + <button class="btn btn-sm btn-primary" onclick={() => selectBoard(item.id)}> |
| 277 | + {isJapanese ? "ロード" : "Load"} |
| 278 | + </button> |
| 279 | + </td> |
| 280 | + </tr> |
| 281 | + {/each} |
| 282 | + </tbody> |
| 283 | + </table> |
| 284 | + </div> |
| 285 | + {/if} |
| 286 | + |
214 | 287 | <div class="modal-action"> |
215 | | - <button class="btn" onclick={() => (saveState = { saving: false })} |
216 | | - >{isJapanese ? "キャンセル" : "Cancel"}</button |
217 | | - > |
218 | | - <button class="btn btn-primary" onclick={handleSave} disabled={!saveState.saving}> |
219 | | - {isJapanese ? "保存" : "Save"} |
| 288 | + <button class="btn" onclick={() => (loadState = { state: "closed" })}> |
| 289 | + {isJapanese ? "閉じる" : "Close"} |
220 | 290 | </button> |
221 | 291 | </div> |
222 | 292 | </div> |
223 | | -</div> |
| 293 | +</dialog> |
224 | 294 |
|
225 | | -<input type="checkbox" class="modal-toggle" bind:checked={resetModalOpen} /> |
226 | | -<div class="modal" class:modal-open={resetModalOpen}> |
| 295 | +<dialog class="modal" open={resetModalOpen}> |
227 | 296 | <div class="modal-box"> |
228 | 297 | <h3 class="font-bold text-lg">{isJapanese ? "リセット確認" : "Reset confirmation"}</h3> |
229 | 298 | <p class="py-4"> |
|
246 | 315 | > |
247 | 316 | </div> |
248 | 317 | </div> |
249 | | -</div> |
| 318 | +</dialog> |
250 | 319 |
|
251 | 320 | <div class="flex box-border h-screen" style="height: calc(100vh - 4rem - 3rem);"> |
252 | 321 | <div |
|
374 | 443 | class="btn btn-ghost hover:bg-[rgb(220,220,220)] text-black" |
375 | 444 | onclick={() => { |
376 | 445 | isProgress = false; |
| 446 | + sendEvent("pause"); |
377 | 447 | sendEvent("save_board"); |
378 | 448 | }} |
379 | 449 | > |
|
0 commit comments