+18
src/lib/stores.svelte.ts
+18
src/lib/stores.svelte.ts
···
35
35
id: string;
36
36
description: string;
37
37
is_completed: boolean;
38
+
// optional
39
+
duration?: number;
40
+
stopwatchInterval?: number;
38
41
}
39
42
40
43
export type List = {
···
59
62
export function generateId() {
60
63
return generateRandomString(10, alphabet("a-z", "0-9"));
61
64
}
65
+
66
+
export function formatSecondsToDuration(seconds: number = 0) {
67
+
let hours = Math.floor(seconds / 3600);
68
+
let minutes = Math.floor((seconds - (hours * 3600)) / 60);
69
+
seconds = seconds - (hours * 3600) - (minutes * 60);
70
+
71
+
// string ver.
72
+
let hrs, mins, secs;
73
+
74
+
if (hours < 10) { hrs = "0" + hours; } else { hrs = hours; }
75
+
if (minutes < 10) { mins = "0" + minutes; } else { mins = minutes; }
76
+
if (seconds < 10) { secs = "0" + seconds; } else { secs = seconds; }
77
+
78
+
return hrs + ':' + mins + ':' + secs ;
79
+
}
+51
-8
src/routes/[id]/+page.svelte
+51
-8
src/routes/[id]/+page.svelte
···
1
1
<script lang="ts">
2
2
import { page } from "$app/state";
3
-
import { local_lists, pinned_list, generateId, type List } from "$lib/stores.svelte";
3
+
import { local_lists, pinned_list, generateId, type List, type Task, formatSecondsToDuration } from "$lib/stores.svelte";
4
4
import { goto } from "$app/navigation";
5
5
import toast from "svelte-french-toast";
6
+
import { onMount } from "svelte";
6
7
7
8
let is_menu_open = $state(false);
8
9
let list : List | undefined = $state(local_lists.value!.find((l) => l.id === page.params.id));
···
34
35
}
35
36
}
36
37
38
+
function toggleInterval(id: string) {
39
+
if (list) {
40
+
const task = list.tasks.find((t) => t.id === id) as Task;
41
+
if (task.stopwatchInterval) {
42
+
clearInterval(task.stopwatchInterval);
43
+
task.stopwatchInterval = undefined;
44
+
}
45
+
else {
46
+
if (!task.duration) { task.duration = 0; }
47
+
const interval = setInterval(() => {
48
+
// @ts-ignore
49
+
task.duration += 1;
50
+
}, 1000);
51
+
task.stopwatchInterval = interval;
52
+
}
53
+
}
54
+
}
55
+
37
56
function createList() {
38
57
const new_list = {
39
58
id: generateId(),
···
65
84
list = local_lists.value.find((l) => l.id === pinned_list.value);
66
85
goto(`/${list!.id}`);
67
86
}
87
+
88
+
onMount(() => {
89
+
if (list) {
90
+
for (const task of list.tasks) {
91
+
// if a task's stopwatch is still running
92
+
// remove it so the user can start it again in one click
93
+
// instead of two cause the first `toggleInterval` would
94
+
// just remove the interval
95
+
if (task.stopwatchInterval) {
96
+
clearInterval(task.stopwatchInterval);
97
+
task.stopwatchInterval = undefined;
98
+
local_lists.update();
99
+
}
100
+
}
101
+
}
102
+
});
68
103
</script>
69
104
70
105
<main class="flex flex-col w-full px-2 pt-8 pb-28 lg:px-4 lg:pt-4 gap-8 text-xl lg:text-3xl">
···
122
157
</menu>
123
158
{/if}
124
159
</section>
125
-
<input
126
-
type="text"
127
-
bind:value={list.title}
128
-
placeholder="Untitled"
129
-
class="text-5xl font-bold bg-transparent"
130
-
/>
160
+
161
+
<input
162
+
type="text"
163
+
bind:value={list.title}
164
+
placeholder="Untitled"
165
+
class="text-5xl font-bold bg-transparent"
166
+
/>
167
+
131
168
<ul class="flex flex-col gap-4">
132
169
{#each list.tasks as task (task.id)}
133
170
<li class="group flex justify-between items-center gap-4">
···
144
181
/>
145
182
</div>
146
183
147
-
<div class="flex gap-4 w-fit">
184
+
<div class="flex gap-4 w-fit items-center">
185
+
<button
186
+
onclick={() => toggleInterval(task.id)}
187
+
class="w-fit h-fit tabular-nums text-lg"
188
+
>
189
+
{formatSecondsToDuration(task.duration!)}
190
+
</button>
148
191
<button
149
192
onclick={() => deleteTask(task.id)}
150
193
class="px-4 py-2 bg-red-500 rounded-xl text-white"