+20
-52
src/lib/stores.svelte.ts
+20
-52
src/lib/stores.svelte.ts
···
2
2
const browser_exists = (typeof window !== "undefined") && (typeof (document) !== "undefined");
3
3
const storage = browser_exists ? localStorage : null;
4
4
5
-
// Color Theme (from DaisyUI)
6
-
const initial_theme : string = getInitialTheme();
5
+
// Generalized Local Storage
6
+
function persisted<T>(key: string, default_value: T) {
7
+
let value : T = $state(default_value);
7
8
8
-
function getInitialTheme() {
9
-
const initial_local = storage?.getItem("local_theme");
10
-
if (initial_local) {
11
-
console.log("getInitial", initial_local);
12
-
return initial_local
9
+
const initial_local = storage?.getItem(key);
10
+
if (initial_local) {
11
+
try {
12
+
value = JSON.parse(initial_local).value as T;
13
+
if (!value) { update(default_value); }
14
+
}
15
+
catch (e) {
16
+
update(default_value);
17
+
}
13
18
}
14
-
return "default";
15
-
}
16
19
17
-
function localTheme() {
18
-
let theme : string = $state(initial_theme);
19
-
20
-
function updateTheme(new_theme : string) {
20
+
function update(new_value : T) {
21
21
if (browser_exists) {
22
-
theme = new_theme;
23
-
storage?.setItem("local_theme", theme);
22
+
value = new_value;
23
+
storage?.setItem(key, JSON.stringify({ value }));
24
24
}
25
25
}
26
26
27
27
return {
28
-
get theme() { return theme; },
29
-
updateTheme
28
+
get value() { return value; },
29
+
update
30
30
}
31
31
}
32
32
33
-
export const color_theme = localTheme();
33
+
// Color Theme (from DaisyUI)
34
+
export const color_theme = persisted<string>("local_theme", "default");
34
35
35
36
// Task + Todo List
36
37
export type Task = {
···
39
40
is_completed: boolean;
40
41
}
41
42
42
-
const initial_list : Task[] = getInitialList();
43
-
44
-
function getInitialList() {
45
-
const initial_local = storage?.getItem("local_todo_list");
46
-
if (initial_local) { return JSON.parse(initial_local) as Task[]; }
47
-
return [];
48
-
}
49
-
50
-
function localTodoList() {
51
-
let tasks : Task[] = $state(initial_list);
52
-
53
-
function addTask(new_task : Task) {
54
-
tasks.push(new_task);
55
-
}
56
-
57
-
function removeTask(id : string) {
58
-
tasks = tasks.filter((t) => t.id !== id);
59
-
}
60
-
61
-
function update() {
62
-
if (browser_exists) {
63
-
storage?.setItem("local_todo_list", JSON.stringify(tasks));
64
-
}
65
-
}
66
-
67
-
return {
68
-
get tasks() { return tasks; },
69
-
addTask,
70
-
removeTask,
71
-
update
72
-
}
73
-
}
74
-
75
-
export const todo_list = localTodoList();
43
+
export const todo_list = persisted<Task[]>("local_todo_list", []);
+2
-2
src/routes/+layout.svelte
+2
-2
src/routes/+layout.svelte
+25
-13
src/routes/+page.svelte
+25
-13
src/routes/+page.svelte
···
1
1
<script lang="ts">
2
2
import { todo_list, type Task } from "$lib/stores.svelte";
3
3
4
-
let id = 0;
4
+
$effect(() => console.log(todo_list));
5
+
6
+
// TODO: get better ID management
7
+
let id = $state(0);
5
8
let description = $state("");
6
9
7
10
function randomizeId() {
8
11
let random_id = id;
9
-
while (todo_list.tasks.find(t => t.id === `${random_id}`)) {
12
+
while (todo_list.value.find(t => t.id === `${random_id}`)) {
10
13
random_id = Math.floor(Math.random() * 100);
11
14
}
12
15
id = random_id;
···
14
17
15
18
function addTask() {
16
19
randomizeId();
17
-
todo_list.addTask({
18
-
id: `${id}`,
19
-
description,
20
-
is_completed: false
21
-
});
20
+
todo_list.update(
21
+
[...todo_list.value, {
22
+
id: `${id}`,
23
+
description,
24
+
is_completed: false
25
+
}]
26
+
);
22
27
description = "";
23
28
}
24
29
25
-
$effect(() => todo_list.update());
30
+
function removeTask(id: string) {
31
+
todo_list.update(
32
+
todo_list.value.filter((t) => t.id !== id)
33
+
);
34
+
}
35
+
36
+
// update when tasks change (description, is_completed)
37
+
$effect(() => todo_list.update(todo_list.value));
26
38
</script>
27
39
28
40
<ul class="flex flex-col gap-8 overflow-y-scroll h-full max-h-[48rem] p-2">
29
-
{#each todo_list.tasks as task : Task}
41
+
{#each todo_list.value as task : Task}
30
42
<li class="group flex gap-4 items-center">
31
43
<input
32
44
type="checkbox"
···
39
51
class={`text-lg lg:text-xl input lg:input-lg w-full max-w-lg ${task.is_completed && "line-through"}`}
40
52
/>
41
53
<button
42
-
onclick={() => todo_list.removeTask(task.id)}
54
+
onclick={() => removeTask(task.id)}
43
55
class="lg:invisible lg:group-hover:visible btn btn-error"
44
56
>
45
57
<img
···
53
65
54
66
</ul>
55
67
56
-
<form onsubmit={addTask} class="flex gap-4">
68
+
<section class="flex gap-4">
57
69
<input
58
70
type="text"
59
71
bind:value={description}
60
72
class="text-lg lg:text-xl input input-bordered lg:input-lg w-full max-w-lg"
61
73
/>
62
-
<button class="btn lg:btn-lg btn-primary">Add</button>
63
-
</form>
74
+
<button onclick={addTask} class="btn lg:btn-lg btn-primary">Add</button>
75
+
</section>
64
76