Skip to content

Commit f29eebf

Browse files
authored
Merge pull request #32 from ut-code/chore/improve-database-ui
Chore/improve database UI
2 parents 4bc03af + 0a52774 commit f29eebf

File tree

9 files changed

+129
-56
lines changed

9 files changed

+129
-56
lines changed

src/iframe/life-game.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,6 @@ on.save_board = async () => {
281281
};
282282

283283
on.apply_board = (newBoard) => {
284-
boardSize = newBoard.length;
285284
board = newBoard;
286285
renderBoard();
287286
generationChange(0);

src/lib/api/board.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { toast } from "$lib/models/ToastStore.svelte";
2+
13
export async function saveBoard(data: { board: number[][]; name: string }, isJapanese: boolean) {
24
try {
35
const response = await fetch("/api/board", {
@@ -13,16 +15,16 @@ export async function saveBoard(data: { board: number[][]; name: string }, isJap
1315
}
1416

1517
if (isJapanese) {
16-
alert("盤面を保存しました!");
18+
toast.show("盤面を保存しました!", "success");
1719
} else {
18-
alert("Board saved!");
20+
toast.show("Board saved!", "success");
1921
}
2022
} catch (err) {
2123
console.error("Save Error:", err);
2224
if (isJapanese) {
23-
alert("保存に失敗しました。");
25+
toast.show("保存に失敗しました。", "error");
2426
} else {
25-
alert("Failed to save.");
27+
toast.show("Failed to save.", "error");
2628
}
2729
}
2830
}
@@ -52,9 +54,9 @@ export async function fetchBoardList(isJapanese: boolean): Promise<BoardListItem
5254
} catch (err) {
5355
console.error("Load error", err);
5456
if (isJapanese) {
55-
alert("読み込みに失敗しました。");
57+
toast.show("読み込みに失敗しました。", "error");
5658
} else {
57-
alert("Failed to load.");
59+
toast.show("Failed to load.", "error");
5860
}
5961
}
6062
}
@@ -80,9 +82,9 @@ export async function loadBoardById(
8082
} catch (err) {
8183
console.error("Load error", err);
8284
if (isJapanese) {
83-
alert("読み込みに失敗しました。");
85+
toast.show("読み込みに失敗しました。", "error");
8486
} else {
85-
alert("Failed to load.");
87+
toast.show("Failed to load.", "error");
8688
}
8789
}
8890
}

src/lib/api/code.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { toast } from "$lib/models/ToastStore.svelte";
2+
13
export async function saveCode(data: { code: string; name: string }, isJapanese: boolean) {
24
try {
35
const response = await fetch("/api/code", {
@@ -13,16 +15,16 @@ export async function saveCode(data: { code: string; name: string }, isJapanese:
1315
}
1416

1517
if (isJapanese) {
16-
alert("コードを保存しました!");
18+
toast.show("コードを保存しました!", "success");
1719
} else {
18-
alert("Code saved!");
20+
toast.show("Code saved!", "success");
1921
}
2022
} catch (err) {
2123
console.error("Save Error:", err);
2224
if (isJapanese) {
23-
alert("保存に失敗しました。");
25+
toast.show("保存に失敗しました。", "error");
2426
} else {
25-
alert("Failed to save.");
27+
toast.show("Failed to save.", "error");
2628
}
2729
}
2830
}
@@ -51,9 +53,9 @@ export async function fetchCodeList(isJapanese: boolean): Promise<CodeListItem[]
5153
} catch (err) {
5254
console.error("Load error", err);
5355
if (isJapanese) {
54-
alert("読み込みに失敗しました。");
56+
toast.show("読み込みに失敗しました。", "error");
5557
} else {
56-
alert("Failed to load.");
58+
toast.show("Failed to load.", "error");
5759
}
5860
}
5961
}
@@ -76,9 +78,9 @@ export async function loadCodeById(id: number, isJapanese: boolean): Promise<str
7678
} catch (err) {
7779
console.error("Load error", err);
7880
if (isJapanese) {
79-
alert("読み込みに失敗しました。");
81+
toast.show("読み込みに失敗しました。", "error");
8082
} else {
81-
alert("Failed to load.");
83+
toast.show("Failed to load.", "error");
8284
}
8385
}
8486
}

src/lib/components/BoardModals.svelte

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
>
5252
<button
5353
type="submit"
54-
class="btn btn-primary"
54+
class="btn btn-success text-black"
5555
onclick={() => manager.save(isJapanese)}
5656
disabled={!manager.saveState.saving}
5757
>
@@ -103,7 +103,10 @@
103103
<td>{item.name}</td>
104104
<td>{new Date(item.createdAt).toLocaleString(isJapanese ? "ja-JP" : "en-US")}</td>
105105
<td class="text-right">
106-
<button class="btn btn-sm btn-primary" onclick={() => onSelect(item.id)}>
106+
<button
107+
class="btn btn-sm btn-success text-black"
108+
onclick={() => onSelect(item.id)}
109+
>
107110
{isJapanese ? "ロード" : "Load"}
108111
</button>
109112
</td>

src/lib/components/CodeModals.svelte

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
>
3838
<button
3939
type="submit"
40-
class="btn btn-primary"
40+
class="btn btn-success text-black"
4141
onclick={() => manager.save(isJapanese)}
4242
disabled={!manager.saveState.saving}
4343
>
@@ -77,7 +77,10 @@
7777
<td>{item.name}</td>
7878
<td>{new Date(item.createdAt).toLocaleString(isJapanese ? "ja-JP" : "en-US")}</td>
7979
<td class="text-right">
80-
<button class="btn btn-sm btn-primary" onclick={() => onSelect(item.id)}>
80+
<button
81+
class="btn btn-sm btn-success text-black"
82+
onclick={() => onSelect(item.id)}
83+
>
8184
{isJapanese ? "ロード" : "Load"}
8285
</button>
8386
</td>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script lang="ts">
2+
import { toast } from "$lib/models/ToastStore.svelte";
3+
4+
let alertClass: string = $derived.by(() => {
5+
switch (toast.type) {
6+
case "success":
7+
return "alert-success";
8+
case "error":
9+
return "alert-error";
10+
default:
11+
return "alert-info";
12+
}
13+
});
14+
</script>
15+
16+
{#if toast.visible}
17+
<div class="toast toast-middle toast-center">
18+
<div class={`alert ${alertClass} p-3 text-lg`}>
19+
<div>
20+
<span>{toast.message}</span>
21+
</div>
22+
</div>
23+
</div>
24+
{/if}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export class ToastStore {
2+
message = $state("");
3+
type = $state<"success" | "error" | "info">("info");
4+
visible = $state(false);
5+
6+
private timerId: ReturnType<typeof setTimeout> | undefined = $state(undefined);
7+
8+
/**
9+
* トーストを表示する
10+
* @param message 表示するメッセージ
11+
* @param type "success" | "error" | "info"
12+
* @param duration 表示時間(ミリ秒)
13+
*/
14+
show(message: string, type: "success" | "error" | "info" = "info", duration: number = 2000) {
15+
this.message = message;
16+
this.type = type;
17+
this.visible = true;
18+
19+
if (this.timerId) {
20+
clearTimeout(this.timerId);
21+
}
22+
23+
this.timerId = setTimeout(() => {
24+
this.hide();
25+
}, duration);
26+
}
27+
28+
hide() {
29+
this.visible = false;
30+
if (this.timerId) {
31+
clearTimeout(this.timerId);
32+
this.timerId = undefined;
33+
}
34+
}
35+
}
36+
37+
// グローバルで使うための単一インスタンスを作成してエクスポート
38+
export const toast = new ToastStore();

src/routes/+layout.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<script lang="ts">
22
import favicon from "$lib/assets/favicon.svg";
33
import "../app.css";
4+
import GlobalToast from "$lib/components/GlobalToast.svelte";
45
let { children } = $props();
56
</script>
67

78
<svelte:head>
89
<link rel="icon" href={favicon} />
910
</svelte:head>
1011

11-
{@render children?.()}
12+
<GlobalToast />{@render children?.()}

src/routes/+page.svelte

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import { CodeManager } from "$lib/models/CodeManager.svelte";
1010
import BoardModals from "$lib/components/BoardModals.svelte";
1111
import CodeModals from "$lib/components/CodeModals.svelte";
12+
import { toast } from "$lib/models/ToastStore.svelte";
1213
import CodeMirror from "svelte-codemirror-editor";
1314
import { javascript } from "@codemirror/lang-javascript";
1415
import { oneDark } from "@codemirror/theme-one-dark";
@@ -32,6 +33,16 @@
3233
const boardManager = new BoardManager();
3334
const codeManager = new CodeManager();
3435
36+
let disabledTemplates: { [key: string]: boolean } = $derived.by(() => {
37+
const newDisabledState: { [key: string]: boolean } = {};
38+
for (const key in patterns) {
39+
const patternName = key as keyof typeof patterns;
40+
const patternData = patterns[patternName];
41+
newDisabledState[patternName] = sizeValue < (patternData.minBoardSize || 0);
42+
}
43+
return newDisabledState;
44+
});
45+
3546
let timer: "running" | "stopped" = $state("stopped");
3647
let intervalMs = $state(1000);
3748
$effect(() => {
@@ -45,7 +56,6 @@
4556
type OngoingEvent =
4657
| "play"
4758
| "pause"
48-
| "state_update"
4959
| "board_reset"
5060
| "board_randomize"
5161
| "place_template"
@@ -69,10 +79,11 @@
6979
break;
7080
}
7181
case "Size shortage": {
72-
alert(
82+
toast.show(
7383
isJapanese
7484
? "盤面からはみ出してしまうため、キャンセルしました"
7585
: "This action was canceled because it would have extended beyond the board.",
86+
"error",
7687
);
7788
break;
7889
}
@@ -101,7 +112,6 @@
101112
async function onBoardSelect(id: number) {
102113
const board = await boardManager.load(id, isJapanese);
103114
if (board) {
104-
sizeValue = board.length;
105115
sendEvent("apply_board", board);
106116
}
107117
}
@@ -169,31 +179,27 @@
169179
<div class="bg-base-200 shadow-lg p-4 h-48 w-full overflow-x-auto">
170180
<div class="flex gap-4">
171181
{#each Object.keys(patterns) as (keyof typeof patterns)[] as patternName (patternName)}
172-
<div class="text-center flex-shrink-0">
182+
<div
183+
class="text-center flex-shrink-0"
184+
style:opacity={disabledTemplates[patternName] ? 0.3 : 1}
185+
>
173186
<p class="font-bold mb-2">
174187
{isJapanese ? patterns[patternName].names.ja : patterns[patternName].names.en}
175188
</p>
176189
<button
177190
class="btn overflow-hidden p-0 w-24 h-24"
178191
onclick={() => {
179-
sendEvent("request_sync");
180-
181-
const patternData = patterns[patternName];
182-
const patternShape = patternData.shape;
183-
184-
if (sizeValue < (patternData.minBoardSize || 0)) {
185-
if (isJapanese) {
186-
alert(
187-
`このパターンには ${patternData.minBoardSize}x${patternData.minBoardSize} 以上の盤面が必要です`,
188-
);
189-
} else {
190-
alert(
191-
`This pattern requires a board size of at least ${patternData.minBoardSize}x${patternData.minBoardSize}.`,
192-
);
193-
}
194-
192+
if (disabledTemplates[patternName]) {
193+
toast.show(
194+
isJapanese
195+
? `このパターンには ${patterns[patternName].minBoardSize}x${patterns[patternName].minBoardSize} 以上の盤面が必要です`
196+
: `This pattern requires a board size of at least ${patterns[patternName].minBoardSize}x${patterns[patternName].minBoardSize}.`,
197+
"error",
198+
);
195199
return;
196200
}
201+
const patternData = patterns[patternName];
202+
const patternShape = patternData.shape;
197203
bottomDrawerOpen = false;
198204
sendEvent("place_template", patternShape);
199205
}}
@@ -253,7 +259,7 @@
253259
class="w-[80%] h-[90%] rounded-lg mx-auto my-5 bg-white shadow-lg"
254260
onload={() => {
255261
setTimeout(() => {
256-
sendEvent("state_update");
262+
sendEvent("request_sync");
257263
console.log("generationFigure onload:", generationFigure);
258264
}, 50);
259265
}}
@@ -283,7 +289,7 @@
283289
<!-- Left Section -->
284290
<div class="flex items-center">
285291
<button
286-
class="btn rounded-none h-12 justify-start"
292+
class="btn rounded-none h-12 justify-start w-30"
287293
onclick={() => (bottomDrawerOpen = !bottomDrawerOpen)}
288294
>
289295
{#if bottomDrawerOpen}
@@ -295,8 +301,8 @@
295301
{/if}
296302
</button>
297303

298-
<div class="font-bold text-black ml-4">
299-
{isJapanese ? "" + generationFigure + "世代" : "Generation:" + generationFigure}
304+
<div class="font-bold text-black ml-4 w-25">
305+
{isJapanese ? "世代数:" + generationFigure : "Generation:" + generationFigure}
300306
</div>
301307
</div>
302308

@@ -329,17 +335,13 @@
329335
<img class="size-6" src={icons.accelerate} alt="accelerate" />
330336
</button>
331337

332-
<div class="font-bold text-black ml-2">
338+
<div class="font-bold text-black ml-2 w-25">
333339
{isJapanese ? "現在の速度" : "Current speed"}: x{1000 / intervalMs}
334340
</div>
335341

336342
<div class="w-px bg-gray-400 h-6 mx-4"></div>
337343
<!-- Separator -->
338344

339-
<button class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)]">
340-
<img class="size-6" src={icons.LeftArrow} alt="Left Arrow" />
341-
</button>
342-
343345
<button
344346
class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)] swap"
345347
onclick={() => {
@@ -357,10 +359,6 @@
357359
<img class="size-6 swap-on" src={icons.Pause} alt="Pause" />
358360
<img class="size-6 swap-off" src={icons.Play} alt="Play" />
359361
</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>
364362
</div>
365363

366364
<!-- Right Section -->
@@ -418,7 +416,10 @@
418416
<!-- Separator -->
419417
<div class="font-bold text-black">{isJapanese ? "コード" : "Code"}:</div>
420418
<button
421-
class="btn btn-ghost hover:bg-[rgb(220,220,220)] text-black"
419+
class={[
420+
"btn text-black",
421+
editingCode === appliedCode ? "btn-ghost hover:bg-[rgb(220,220,220)]" : "btn-success",
422+
]}
422423
onclick={() => {
423424
appliedCode = editingCode;
424425
isProgress = false;

0 commit comments

Comments
 (0)