|
24 | 24 | let showEditor = $state(true); |
25 | 25 | let preview_iframe: HTMLIFrameElement | undefined = $state(); |
26 | 26 | let isProgress = $state(false); |
| 27 | + let isJapanese = $state(true); |
27 | 28 | let resetModalOpen = $state(false); |
28 | 29 | let bottomDrawerOpen = $state(false); |
29 | 30 |
|
|
78 | 79 |
|
79 | 80 | const name = boardNameInput.trim() === "" ? "Unnamed Board" : boardNameInput.trim(); |
80 | 81 |
|
81 | | - await saveBoard({ board: saveState.boardData, name: name }); |
| 82 | + await saveBoard({ board: saveState.boardData, name: name }, isJapanese); |
82 | 83 |
|
83 | 84 | saveState = { saving: false }; |
84 | 85 | boardNameInput = ""; |
85 | 86 | } |
86 | 87 |
|
87 | 88 | async function handleLoad() { |
88 | | - const board = await loadBoard(); |
| 89 | + const board = await loadBoard(isJapanese); |
89 | 90 | if (board) { |
90 | 91 | sendEvent("apply_board", board); |
91 | 92 | } |
|
116 | 117 | </div> |
117 | 118 | </label> |
118 | 119 |
|
119 | | - <div class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)] mx-5"> |
| 120 | + <button |
| 121 | + class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)] mx-5" |
| 122 | + onclick={() => { |
| 123 | + isJapanese = !isJapanese; |
| 124 | + }} |
| 125 | + > |
120 | 126 | <img class="size-6" src={icons.language} alt="Language" /> |
121 | | - </div> |
| 127 | + </button> |
122 | 128 | </div> |
123 | 129 |
|
124 | 130 | <div |
|
130 | 136 | <div class="flex gap-4"> |
131 | 137 | {#each Object.keys(patterns) as (keyof typeof patterns)[] as patternName (patternName)} |
132 | 138 | <div class="text-center flex-shrink-0"> |
133 | | - <p class="font-bold mb-2">{patterns[patternName].names.ja}</p> |
| 139 | + <p class="font-bold mb-2"> |
| 140 | + {isJapanese ? patterns[patternName].names.ja : patterns[patternName].names.en} |
| 141 | + </p> |
134 | 142 | <button |
135 | 143 | class="btn overflow-hidden p-0 w-24 h-24" |
136 | 144 | onclick={() => { |
|
145 | 153 | const patternWidth = patternShape[0].length; |
146 | 154 |
|
147 | 155 | if (sizeValue < (patternData.minBoardSize || 0)) { |
148 | | - alert( |
149 | | - `このパターンには ${patternData.minBoardSize}x${patternData.minBoardSize} 以上の盤面が必要です`, |
150 | | - ); |
| 156 | + if (isJapanese) { |
| 157 | + alert( |
| 158 | + `このパターンには ${patternData.minBoardSize}x${patternData.minBoardSize} 以上の盤面が必要です`, |
| 159 | + ); |
| 160 | + } else { |
| 161 | + alert( |
| 162 | + `This pattern requires a board size of at least ${patternData.minBoardSize}x${patternData.minBoardSize}.`, |
| 163 | + ); |
| 164 | + } |
| 165 | + |
151 | 166 | return; |
152 | 167 | } |
153 | 168 | // パターンがボードの中央に来るよう、パターンの左上のセルの位置(startRow, startCol)を調整 |
|
180 | 195 | <input type="checkbox" class="modal-toggle" bind:checked={saveState.saving} /> |
181 | 196 | <div class="modal" class:modal-open={saveState.saving}> |
182 | 197 | <div class="modal-box"> |
183 | | - <h3 class="font-bold text-lg">盤面を保存</h3> |
184 | | - <p class="py-4">保存する盤面に名前を付けてください(任意)。</p> |
| 198 | + <h3 class="font-bold text-lg">{isJapanese ? "盤面を保存" : "Save board"}</h3> |
| 199 | + <p class="py-4"> |
| 200 | + {isJapanese |
| 201 | + ? "保存する盤面に名前を付けてください(任意)。" |
| 202 | + : "Please name the board you wish to save (optional)."} |
| 203 | + </p> |
185 | 204 | <input |
186 | 205 | type="text" |
187 | | - placeholder="盤面名を入力" |
| 206 | + placeholder={isJapanese ? "盤面名を入力" : "Enter board name"} |
188 | 207 | class="input input-bordered w-full max-w-xs" |
189 | 208 | bind:value={boardNameInput} |
190 | 209 | /> |
191 | 210 | <div class="modal-action"> |
192 | | - <button class="btn" onclick={() => (saveState = { saving: false })}>キャンセル</button> |
| 211 | + <button class="btn" onclick={() => (saveState = { saving: false })} |
| 212 | + >{isJapanese ? "キャンセル" : "Cancel"}</button |
| 213 | + > |
193 | 214 | <button class="btn btn-primary" onclick={handleSave} disabled={!saveState.saving}> |
194 | | - 保存 |
| 215 | + {isJapanese ? "保存" : "Save"} |
195 | 216 | </button> |
196 | 217 | </div> |
197 | 218 | </div> |
|
200 | 221 | <input type="checkbox" class="modal-toggle" bind:checked={resetModalOpen} /> |
201 | 222 | <div class="modal" class:modal-open={resetModalOpen}> |
202 | 223 | <div class="modal-box"> |
203 | | - <h3 class="font-bold text-lg">リセット確認</h3> |
204 | | - <p class="py-4">本当にコードをリセットしますか?この操作は取り消せません。</p> |
| 224 | + <h3 class="font-bold text-lg">{isJapanese ? "リセット確認" : "Reset confirmation"}</h3> |
| 225 | + <p class="py-4"> |
| 226 | + {isJapanese |
| 227 | + ? "本当にコードをリセットしますか?この操作は取り消せません。" |
| 228 | + : "Are you sure you want to reset the code? This action cannot be undone."} |
| 229 | + </p> |
205 | 230 | <div class="modal-action"> |
206 | | - <button class="btn" onclick={() => (resetModalOpen = false)}>キャンセル</button> |
| 231 | + <button class="btn" onclick={() => (resetModalOpen = false)} |
| 232 | + >{isJapanese ? "キャンセル" : "Cancel"}</button |
| 233 | + > |
207 | 234 | <button |
208 | 235 | class="btn btn-error" |
209 | 236 | onclick={() => { |
210 | 237 | appliedCode = lifeGameJS; |
211 | 238 | editingcode = lifeGameJS; |
212 | 239 | console.log("Reset executed"); |
213 | 240 | resetModalOpen = false; |
214 | | - }}>リセット</button |
| 241 | + }}>{isJapanese ? "リセット" : "Reset"}</button |
215 | 242 | > |
216 | 243 | </div> |
217 | 244 | </div> |
|
252 | 279 | </div> |
253 | 280 | </div> |
254 | 281 |
|
255 | | -<div class="bg-[#E0E0E0] shadow-sm fixed bottom-0 left-0 right-0 z-50 h-12 p-0 flex items-center"> |
256 | | - <button |
257 | | - class="btn rounded-none h-12 justify-start" |
258 | | - onclick={() => (bottomDrawerOpen = !bottomDrawerOpen)} |
259 | | - > |
260 | | - {bottomDrawerOpen ? "▼" : "▲ テンプレート"} |
261 | | - </button> |
262 | | - |
263 | | - <div class="font-bold text-black ml-10"> |
264 | | - 第 {generationFigure} 世代 |
| 282 | +<div |
| 283 | + class="bg-[#E0E0E0] shadow-sm fixed bottom-0 left-0 right-0 z-50 h-12 p-0 flex items-center px-4" |
| 284 | +> |
| 285 | + <!-- Left Section --> |
| 286 | + <div class="flex items-center"> |
| 287 | + <button |
| 288 | + class="btn rounded-none h-12 justify-start" |
| 289 | + onclick={() => (bottomDrawerOpen = !bottomDrawerOpen)} |
| 290 | + > |
| 291 | + {#if bottomDrawerOpen} |
| 292 | + ▼ |
| 293 | + {:else if isJapanese} |
| 294 | + ▲ テンプレート |
| 295 | + {:else} |
| 296 | + ▲ Template |
| 297 | + {/if} |
| 298 | + </button> |
| 299 | + |
| 300 | + <div class="font-bold text-black ml-4"> |
| 301 | + {isJapanese ? "第" + generationFigure + "世代" : "Generation" + generationFigure} |
| 302 | + </div> |
265 | 303 | </div> |
266 | 304 |
|
267 | | - <button |
268 | | - class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)] text-black ml-20" |
269 | | - onclick={() => { |
270 | | - intervalMs = intervalMs * 2; |
271 | | - sendEvent("timer_change", intervalMs); |
272 | | - }} |
273 | | - > |
274 | | - <img class="size-6" src={icons.decelerate} alt="decelerate" /> |
275 | | - </button> |
276 | | - |
277 | | - <button |
278 | | - class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)] text-black ml-2" |
279 | | - onclick={() => { |
280 | | - intervalMs = 1000; |
281 | | - sendEvent("timer_change", intervalMs); |
282 | | - }} |
283 | | - > |
284 | | - x1 |
285 | | - </button> |
286 | | - |
287 | | - <button |
288 | | - class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)] text-black ml-2" |
289 | | - onclick={() => { |
290 | | - intervalMs = intervalMs / 2; |
291 | | - sendEvent("timer_change", intervalMs); |
292 | | - }} |
293 | | - > |
294 | | - <img class="size-6" src={icons.accelerate} alt="accelerate" /> |
295 | | - </button> |
| 305 | + <!-- Center Section --> |
| 306 | + <div class="flex-1 flex justify-center items-center gap-x-2"> |
| 307 | + <button |
| 308 | + class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)]" |
| 309 | + onclick={() => { |
| 310 | + intervalMs = intervalMs * 2; |
| 311 | + sendEvent("timer_change", intervalMs); |
| 312 | + }} |
| 313 | + > |
| 314 | + <img class="size-6" src={icons.decelerate} alt="decelerate" /> |
| 315 | + </button> |
| 316 | + |
| 317 | + <button |
| 318 | + class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)]" |
| 319 | + onclick={() => { |
| 320 | + intervalMs = 1000; |
| 321 | + sendEvent("timer_change", intervalMs); |
| 322 | + }} |
| 323 | + > |
| 324 | + x1 |
| 325 | + </button> |
| 326 | + |
| 327 | + <button |
| 328 | + class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)]" |
| 329 | + onclick={() => { |
| 330 | + intervalMs = intervalMs / 2; |
| 331 | + sendEvent("timer_change", intervalMs); |
| 332 | + }} |
| 333 | + > |
| 334 | + <img class="size-6" src={icons.accelerate} alt="accelerate" /> |
| 335 | + </button> |
296 | 336 |
|
297 | | - <div class="font-bold text-black ml-5"> |
298 | | - 現在の速度: x{1000 / intervalMs} |
299 | | - </div> |
| 337 | + <div class="font-bold text-black ml-2"> |
| 338 | + {isJapanese ? "現在の速度" : "Current speed"}: x{1000 / intervalMs} |
| 339 | + </div> |
300 | 340 |
|
301 | | - <div |
302 | | - class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)] swap fixed left-1/2 !-translate-x-1/2 -ml-15 bottom-1" |
303 | | - > |
304 | | - <img class="size-6" src={icons.LeftArrow} alt="Left Arrow" /> |
305 | | - </div> |
| 341 | + <div class="w-px bg-gray-400 h-6 mx-4"></div> |
| 342 | + <!-- Separator --> |
306 | 343 |
|
307 | | - <button |
308 | | - class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)] swap fixed left-1/2 !-translate-x-1/2 bottom-1" |
309 | | - onclick={() => { |
310 | | - const eventName = isProgress ? "pause" : "play"; |
311 | | - sendEvent(eventName); |
312 | | - isProgress = !isProgress; |
313 | | - }} |
314 | | - > |
315 | | - <input type="checkbox" bind:checked={isProgress} /> |
316 | | - <img class="size-6 swap-on" src={icons.Pause} alt="Pause" /> |
317 | | - <img class="size-6 swap-off" src={icons.Play} alt="Play" /> |
318 | | - </button> |
| 344 | + <button class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)]"> |
| 345 | + <img class="size-6" src={icons.LeftArrow} alt="Left Arrow" /> |
| 346 | + </button> |
319 | 347 |
|
320 | | - <div |
321 | | - class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)] swap fixed left-1/2 !-translate-x-1/2 ml-15 bottom-1" |
322 | | - > |
323 | | - <img class="size-6" src={icons.RightArrow} alt="Right Arrow" /> |
| 348 | + <button |
| 349 | + class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)] swap" |
| 350 | + onclick={() => { |
| 351 | + const eventName = isProgress ? "pause" : "play"; |
| 352 | + sendEvent(eventName); |
| 353 | + isProgress = !isProgress; |
| 354 | + }} |
| 355 | + > |
| 356 | + <input type="checkbox" bind:checked={isProgress} /> |
| 357 | + <img class="size-6 swap-on" src={icons.Pause} alt="Pause" /> |
| 358 | + <img class="size-6 swap-off" src={icons.Play} alt="Play" /> |
| 359 | + </button> |
| 360 | + |
| 361 | + <button class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)]"> |
| 362 | + <img class="size-6" src={icons.RightArrow} alt="Right Arrow" /> |
| 363 | + </button> |
324 | 364 | </div> |
325 | 365 |
|
326 | | - <div class="font-bold text-black absolute right-143">Board:</div> |
327 | | - |
328 | | - <button |
329 | | - class="btn btn-ghost hover:bg-[rgb(220,220,220)] text-black fixed right-125 bottom-1" |
330 | | - onclick={() => { |
331 | | - isProgress = false; |
332 | | - sendEvent("save_board"); |
333 | | - }} |
334 | | - > |
335 | | - Save |
336 | | - </button> |
337 | | - |
338 | | - <button |
339 | | - class="btn btn-ghost hover:bg-[rgb(220,220,220)] text-black fixed right-109 bottom-1" |
340 | | - onclick={() => { |
341 | | - isProgress = false; |
342 | | - sendEvent("pause"); |
343 | | - handleLoad(); |
344 | | - }} |
345 | | - > |
346 | | - Load |
347 | | - </button> |
348 | | - |
349 | | - <button |
350 | | - class="btn btn-ghost hover:bg-[rgb(220,220,220)] text-black fixed right-92 bottom-1" |
351 | | - onclick={() => { |
352 | | - isProgress = false; |
353 | | - sendEvent("boardreset"); |
354 | | - }} |
355 | | - > |
356 | | - Reset |
357 | | - </button> |
| 366 | + <!-- Right Section --> |
| 367 | + <div class="flex items-center gap-x-2"> |
| 368 | + <div class="font-bold text-black">{isJapanese ? "盤面" : "Board"}:</div> |
| 369 | + <button |
| 370 | + class="btn btn-ghost hover:bg-[rgb(220,220,220)] text-black" |
| 371 | + onclick={() => { |
| 372 | + isProgress = false; |
| 373 | + sendEvent("save_board"); |
| 374 | + }} |
| 375 | + > |
| 376 | + {isJapanese ? "保存" : "Save"} |
| 377 | + </button> |
| 378 | + |
| 379 | + <button |
| 380 | + class="btn btn-ghost hover:bg-[rgb(220,220,220)] text-black" |
| 381 | + onclick={() => { |
| 382 | + isProgress = false; |
| 383 | + sendEvent("pause"); |
| 384 | + handleLoad(); |
| 385 | + }} |
| 386 | + > |
| 387 | + {isJapanese ? "ロード" : "Load"} |
| 388 | + </button> |
| 389 | + |
| 390 | + <button |
| 391 | + class="btn btn-ghost hover:bg-[rgb(220,220,220)] text-black" |
| 392 | + onclick={() => { |
| 393 | + isProgress = false; |
| 394 | + sendEvent("boardreset"); |
| 395 | + }} |
| 396 | + > |
| 397 | + {isJapanese ? "リセット" : "Reset"} |
| 398 | + </button> |
| 399 | + |
| 400 | + <button |
| 401 | + class="btn btn-ghost hover:bg-[rgb(220,220,220)] text-black" |
| 402 | + onclick={() => { |
| 403 | + isProgress = false; |
| 404 | + sendEvent("boardrandom"); |
| 405 | + }} |
| 406 | + > |
| 407 | + {isJapanese ? "ランダム" : "Random"} |
| 408 | + </button> |
358 | 409 |
|
359 | | - <button |
360 | | - class="btn btn-ghost hover:bg-[rgb(220,220,220)] text-black fixed right-70 bottom-1" |
361 | | - onclick={() => { |
362 | | - isProgress = false; |
363 | | - sendEvent("boardrandom"); |
364 | | - }} |
365 | | - > |
366 | | - Random |
367 | | - </button> |
| 410 | + <div class="w-px bg-gray-400 h-6 mx-2"></div> |
| 411 | + <!-- Separator --> |
368 | 412 |
|
369 | | - <button |
370 | | - class="btn btn-ghost hover:bg-[rgb(220,220,220)] ml-5 text-black fixed right-20 bottom-1" |
371 | | - onclick={() => { |
372 | | - appliedCode = editingcode; |
373 | | - }} |
374 | | - > |
375 | | - Apply Code |
376 | | - </button> |
| 413 | + <button |
| 414 | + class="btn btn-ghost hover:bg-[rgb(220,220,220)] text-black" |
| 415 | + onclick={() => { |
| 416 | + appliedCode = editingcode; |
| 417 | + }} |
| 418 | + > |
| 419 | + {isJapanese ? "コードを適用" : "Apply Code"} |
| 420 | + </button> |
| 421 | + </div> |
377 | 422 | </div> |
0 commit comments