aboutsummaryrefslogtreecommitdiff
path: root/fe
diff options
context:
space:
mode:
authorzberwaldt <17715430+zberwaldt@users.noreply.github.com>2024-03-15 22:03:11 -0400
committerGitHub <noreply@github.com>2024-03-15 22:03:11 -0400
commit6f8cfbd6cc3d5adbda38e74013c68e3d4745766d (patch)
treeb3f045cd06d6622e23441b442e8f3861050ed444 /fe
parentfac21fa0a72d4a7f1a01ccd44e3acf9c90fd95bd (diff)
parentfd1332a3df191577e91c6d846a8b5db1747099fd (diff)
Merge pull request #1 from zberwaldt/staging
Staging to Prod
Diffstat (limited to 'fe')
-rw-r--r--fe/.env.sample1
-rw-r--r--fe/.gitignore24
-rw-r--r--fe/.prettierrc4
-rw-r--r--fe/.vscode/extensions.json3
-rw-r--r--fe/README.md47
-rw-r--r--fe/index.html13
-rw-r--r--fe/package-lock.json1780
-rw-r--r--fe/package.json25
-rw-r--r--fe/public/vite.svg1
-rw-r--r--fe/src/App.svelte16
-rw-r--r--fe/src/app.css120
-rw-r--r--fe/src/assets/svelte.svg1
-rw-r--r--fe/src/errors.ts5
-rw-r--r--fe/src/http.ts92
-rw-r--r--fe/src/lib/Card.svelte23
-rw-r--r--fe/src/lib/Chart.svelte63
-rw-r--r--fe/src/lib/Column.svelte13
-rw-r--r--fe/src/lib/DataView.svelte228
-rw-r--r--fe/src/lib/Layout.svelte74
-rw-r--r--fe/src/lib/LoginForm.svelte86
-rw-r--r--fe/src/lib/PreferencesForm.svelte119
-rw-r--r--fe/src/lib/Table.svelte114
-rw-r--r--fe/src/lib/forms/AddForm.svelte78
-rw-r--r--fe/src/main.ts8
-rw-r--r--fe/src/stores/auth.ts90
-rw-r--r--fe/src/stores/forms.ts6
-rw-r--r--fe/src/types.ts53
-rw-r--r--fe/src/utils.ts14
-rw-r--r--fe/src/vite-env.d.ts2
-rw-r--r--fe/svelte.config.js8
-rw-r--r--fe/tsconfig.json20
-rw-r--r--fe/tsconfig.node.json10
-rw-r--r--fe/vite.config.ts7
33 files changed, 3148 insertions, 0 deletions
diff --git a/fe/.env.sample b/fe/.env.sample
new file mode 100644
index 0000000..60c383f
--- /dev/null
+++ b/fe/.env.sample
@@ -0,0 +1 @@
VITE_API_BASE_URL="https://www.example.org" \ No newline at end of file
diff --git a/fe/.gitignore b/fe/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/fe/.gitignore
@@ -0,0 +1,24 @@
1# Logs
2logs
3*.log
4npm-debug.log*
5yarn-debug.log*
6yarn-error.log*
7pnpm-debug.log*
8lerna-debug.log*
9
10node_modules
11dist
12dist-ssr
13*.local
14
15# Editor directories and files
16.vscode/*
17!.vscode/extensions.json
18.idea
19.DS_Store
20*.suo
21*.ntvs*
22*.njsproj
23*.sln
24*.sw?
diff --git a/fe/.prettierrc b/fe/.prettierrc
new file mode 100644
index 0000000..222861c
--- /dev/null
+++ b/fe/.prettierrc
@@ -0,0 +1,4 @@
1{
2 "tabWidth": 2,
3 "useTabs": false
4}
diff --git a/fe/.vscode/extensions.json b/fe/.vscode/extensions.json
new file mode 100644
index 0000000..bdef820
--- /dev/null
+++ b/fe/.vscode/extensions.json
@@ -0,0 +1,3 @@
1{
2 "recommendations": ["svelte.svelte-vscode"]
3}
diff --git a/fe/README.md b/fe/README.md
new file mode 100644
index 0000000..e6cd94f
--- /dev/null
+++ b/fe/README.md
@@ -0,0 +1,47 @@
1# Svelte + TS + Vite
2
3This template should help get you started developing with Svelte and TypeScript in Vite.
4
5## Recommended IDE Setup
6
7[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
8
9## Need an official Svelte framework?
10
11Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
12
13## Technical considerations
14
15**Why use this over SvelteKit?**
16
17- It brings its own routing solution which might not be preferable for some users.
18- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
19
20This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
21
22Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
23
24**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
25
26Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
27
28**Why include `.vscode/extensions.json`?**
29
30Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
31
32**Why enable `allowJs` in the TS template?**
33
34While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
35
36**Why is HMR not preserving my local component state?**
37
38HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
39
40If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
41
42```ts
43// store.ts
44// An extremely simple external store
45import { writable } from 'svelte/store'
46export default writable(0)
47```
diff --git a/fe/index.html b/fe/index.html
new file mode 100644
index 0000000..b6c5f0a
--- /dev/null
+++ b/fe/index.html
@@ -0,0 +1,13 @@
1<!doctype html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 <title>Vite + Svelte + TS</title>
8 </head>
9 <body>
10 <div id="app"></div>
11 <script type="module" src="/src/main.ts"></script>
12 </body>
13</html>
diff --git a/fe/package-lock.json b/fe/package-lock.json
new file mode 100644
index 0000000..f74bf6c
--- /dev/null
+++ b/fe/package-lock.json
@@ -0,0 +1,1780 @@
1{
2 "name": "fe",
3 "version": "0.0.0",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "fe",
9 "version": "0.0.0",
10 "dependencies": {
11 "chart.js": "^4.4.2",
12 "svelte-chartjs": "^3.1.5"
13 },
14 "devDependencies": {
15 "@sveltejs/vite-plugin-svelte": "^3.0.2",
16 "@tsconfig/svelte": "^5.0.2",
17 "svelte": "^4.2.10",
18 "svelte-check": "^3.6.3",
19 "tslib": "^2.6.2",
20 "typescript": "^5.2.2",
21 "vite": "^5.1.0"
22 }
23 },
24 "node_modules/@ampproject/remapping": {
25 "version": "2.2.1",
26 "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
27 "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
28 "dependencies": {
29 "@jridgewell/gen-mapping": "^0.3.0",
30 "@jridgewell/trace-mapping": "^0.3.9"
31 },
32 "engines": {
33 "node": ">=6.0.0"
34 }
35 },
36 "node_modules/@esbuild/aix-ppc64": {
37 "version": "0.19.12",
38 "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
39 "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
40 "cpu": [
41 "ppc64"
42 ],
43 "dev": true,
44 "optional": true,
45 "os": [
46 "aix"
47 ],
48 "engines": {
49 "node": ">=12"
50 }
51 },
52 "node_modules/@esbuild/android-arm": {
53 "version": "0.19.12",
54 "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
55 "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
56 "cpu": [
57 "arm"
58 ],
59 "dev": true,
60 "optional": true,
61 "os": [
62 "android"
63 ],
64 "engines": {
65 "node": ">=12"
66 }
67 },
68 "node_modules/@esbuild/android-arm64": {
69 "version": "0.19.12",
70 "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
71 "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
72 "cpu": [
73 "arm64"
74 ],
75 "dev": true,
76 "optional": true,
77 "os": [
78 "android"
79 ],
80 "engines": {
81 "node": ">=12"
82 }
83 },
84 "node_modules/@esbuild/android-x64": {
85 "version": "0.19.12",
86 "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
87 "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
88 "cpu": [
89 "x64"
90 ],
91 "dev": true,
92 "optional": true,
93 "os": [
94 "android"
95 ],
96 "engines": {
97 "node": ">=12"
98 }
99 },
100 "node_modules/@esbuild/darwin-arm64": {
101 "version": "0.19.12",
102 "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
103 "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
104 "cpu": [
105 "arm64"
106 ],
107 "dev": true,
108 "optional": true,
109 "os": [
110 "darwin"
111 ],
112 "engines": {
113 "node": ">=12"
114 }
115 },
116 "node_modules/@esbuild/darwin-x64": {
117 "version": "0.19.12",
118 "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
119 "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
120 "cpu": [
121 "x64"
122 ],
123 "dev": true,
124 "optional": true,
125 "os": [
126 "darwin"
127 ],
128 "engines": {
129 "node": ">=12"
130 }
131 },
132 "node_modules/@esbuild/freebsd-arm64": {
133 "version": "0.19.12",
134 "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
135 "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
136 "cpu": [
137 "arm64"
138 ],
139 "dev": true,
140 "optional": true,
141 "os": [
142 "freebsd"
143 ],
144 "engines": {
145 "node": ">=12"
146 }
147 },
148 "node_modules/@esbuild/freebsd-x64": {
149 "version": "0.19.12",
150 "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
151 "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
152 "cpu": [
153 "x64"
154 ],
155 "dev": true,
156 "optional": true,
157 "os": [
158 "freebsd"
159 ],
160 "engines": {
161 "node": ">=12"
162 }
163 },
164 "node_modules/@esbuild/linux-arm": {
165 "version": "0.19.12",
166 "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
167 "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
168 "cpu": [
169 "arm"
170 ],
171 "dev": true,
172 "optional": true,
173 "os": [
174 "linux"
175 ],
176 "engines": {
177 "node": ">=12"
178 }
179 },
180 "node_modules/@esbuild/linux-arm64": {
181 "version": "0.19.12",
182 "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
183 "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
184 "cpu": [
185 "arm64"
186 ],
187 "dev": true,
188 "optional": true,
189 "os": [
190 "linux"
191 ],
192 "engines": {
193 "node": ">=12"
194 }
195 },
196 "node_modules/@esbuild/linux-ia32": {
197 "version": "0.19.12",
198 "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
199 "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
200 "cpu": [
201 "ia32"
202 ],
203 "dev": true,
204 "optional": true,
205 "os": [
206 "linux"
207 ],
208 "engines": {
209 "node": ">=12"
210 }
211 },
212 "node_modules/@esbuild/linux-loong64": {
213 "version": "0.19.12",
214 "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
215 "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
216 "cpu": [
217 "loong64"
218 ],
219 "dev": true,
220 "optional": true,
221 "os": [
222 "linux"
223 ],
224 "engines": {
225 "node": ">=12"
226 }
227 },
228 "node_modules/@esbuild/linux-mips64el": {
229 "version": "0.19.12",
230 "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
231 "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
232 "cpu": [
233 "mips64el"
234 ],
235 "dev": true,
236 "optional": true,
237 "os": [
238 "linux"
239 ],
240 "engines": {
241 "node": ">=12"
242 }
243 },
244 "node_modules/@esbuild/linux-ppc64": {
245 "version": "0.19.12",
246 "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
247 "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
248 "cpu": [
249 "ppc64"
250 ],
251 "dev": true,
252 "optional": true,
253 "os": [
254 "linux"
255 ],
256 "engines": {
257 "node": ">=12"
258 }
259 },
260 "node_modules/@esbuild/linux-riscv64": {
261 "version": "0.19.12",
262 "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
263 "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
264 "cpu": [
265 "riscv64"
266 ],
267 "dev": true,
268 "optional": true,
269 "os": [
270 "linux"
271 ],
272 "engines": {
273 "node": ">=12"
274 }
275 },
276 "node_modules/@esbuild/linux-s390x": {
277 "version": "0.19.12",
278 "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
279 "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
280 "cpu": [
281 "s390x"
282 ],
283 "dev": true,
284 "optional": true,
285 "os": [
286 "linux"
287 ],
288 "engines": {
289 "node": ">=12"
290 }
291 },
292 "node_modules/@esbuild/linux-x64": {
293 "version": "0.19.12",
294 "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
295 "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
296 "cpu": [
297 "x64"
298 ],
299 "dev": true,
300 "optional": true,
301 "os": [
302 "linux"
303 ],
304 "engines": {
305 "node": ">=12"
306 }
307 },
308 "node_modules/@esbuild/netbsd-x64": {
309 "version": "0.19.12",
310 "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
311 "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
312 "cpu": [
313 "x64"
314 ],
315 "dev": true,
316 "optional": true,
317 "os": [
318 "netbsd"
319 ],
320 "engines": {
321 "node": ">=12"
322 }
323 },
324 "node_modules/@esbuild/openbsd-x64": {
325 "version": "0.19.12",
326 "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
327 "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
328 "cpu": [
329 "x64"
330 ],
331 "dev": true,
332 "optional": true,
333 "os": [
334 "openbsd"
335 ],
336 "engines": {
337 "node": ">=12"
338 }
339 },
340 "node_modules/@esbuild/sunos-x64": {
341 "version": "0.19.12",
342 "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
343 "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
344 "cpu": [
345 "x64"
346 ],
347 "dev": true,
348 "optional": true,
349 "os": [
350 "sunos"
351 ],
352 "engines": {
353 "node": ">=12"
354 }
355 },
356 "node_modules/@esbuild/win32-arm64": {
357 "version": "0.19.12",
358 "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
359 "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
360 "cpu": [
361 "arm64"
362 ],
363 "dev": true,
364 "optional": true,
365 "os": [
366 "win32"
367 ],
368 "engines": {
369 "node": ">=12"
370 }
371 },
372 "node_modules/@esbuild/win32-ia32": {
373 "version": "0.19.12",
374 "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
375 "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
376 "cpu": [
377 "ia32"
378 ],
379 "dev": true,
380 "optional": true,
381 "os": [
382 "win32"
383 ],
384 "engines": {
385 "node": ">=12"
386 }
387 },
388 "node_modules/@esbuild/win32-x64": {
389 "version": "0.19.12",
390 "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
391 "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
392 "cpu": [
393 "x64"
394 ],
395 "dev": true,
396 "optional": true,
397 "os": [
398 "win32"
399 ],
400 "engines": {
401 "node": ">=12"
402 }
403 },
404 "node_modules/@jridgewell/gen-mapping": {
405 "version": "0.3.3",
406 "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
407 "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
408 "dependencies": {
409 "@jridgewell/set-array": "^1.0.1",
410 "@jridgewell/sourcemap-codec": "^1.4.10",
411 "@jridgewell/trace-mapping": "^0.3.9"
412 },
413 "engines": {
414 "node": ">=6.0.0"
415 }
416 },
417 "node_modules/@jridgewell/resolve-uri": {
418 "version": "3.1.2",
419 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
420 "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
421 "engines": {
422 "node": ">=6.0.0"
423 }
424 },
425 "node_modules/@jridgewell/set-array": {
426 "version": "1.1.2",
427 "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
428 "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
429 "engines": {
430 "node": ">=6.0.0"
431 }
432 },
433 "node_modules/@jridgewell/sourcemap-codec": {
434 "version": "1.4.15",
435 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
436 "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
437 },
438 "node_modules/@jridgewell/trace-mapping": {
439 "version": "0.3.22",
440 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
441 "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
442 "dependencies": {
443 "@jridgewell/resolve-uri": "^3.1.0",
444 "@jridgewell/sourcemap-codec": "^1.4.14"
445 }
446 },
447 "node_modules/@kurkle/color": {
448 "version": "0.3.2",
449 "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
450 "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
451 },
452 "node_modules/@nodelib/fs.scandir": {
453 "version": "2.1.5",
454 "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
455 "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
456 "dev": true,
457 "dependencies": {
458 "@nodelib/fs.stat": "2.0.5",
459 "run-parallel": "^1.1.9"
460 },
461 "engines": {
462 "node": ">= 8"
463 }
464 },
465 "node_modules/@nodelib/fs.stat": {
466 "version": "2.0.5",
467 "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
468 "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
469 "dev": true,
470 "engines": {
471 "node": ">= 8"
472 }
473 },
474 "node_modules/@nodelib/fs.walk": {
475 "version": "1.2.8",
476 "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
477 "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
478 "dev": true,
479 "dependencies": {
480 "@nodelib/fs.scandir": "2.1.5",
481 "fastq": "^1.6.0"
482 },
483 "engines": {
484 "node": ">= 8"
485 }
486 },
487 "node_modules/@rollup/rollup-android-arm-eabi": {
488 "version": "4.12.0",
489 "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
490 "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
491 "cpu": [
492 "arm"
493 ],
494 "dev": true,
495 "optional": true,
496 "os": [
497 "android"
498 ]
499 },
500 "node_modules/@rollup/rollup-android-arm64": {
501 "version": "4.12.0",
502 "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
503 "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
504 "cpu": [
505 "arm64"
506 ],
507 "dev": true,
508 "optional": true,
509 "os": [
510 "android"
511 ]
512 },
513 "node_modules/@rollup/rollup-darwin-arm64": {
514 "version": "4.12.0",
515 "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
516 "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
517 "cpu": [
518 "arm64"
519 ],
520 "dev": true,
521 "optional": true,
522 "os": [
523 "darwin"
524 ]
525 },
526 "node_modules/@rollup/rollup-darwin-x64": {
527 "version": "4.12.0",
528 "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
529 "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
530 "cpu": [
531 "x64"
532 ],
533 "dev": true,
534 "optional": true,
535 "os": [
536 "darwin"
537 ]
538 },
539 "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
540 "version": "4.12.0",
541 "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
542 "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
543 "cpu": [
544 "arm"
545 ],
546 "dev": true,
547 "optional": true,
548 "os": [
549 "linux"
550 ]
551 },
552 "node_modules/@rollup/rollup-linux-arm64-gnu": {
553 "version": "4.12.0",
554 "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
555 "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
556 "cpu": [
557 "arm64"
558 ],
559 "dev": true,
560 "optional": true,
561 "os": [
562 "linux"
563 ]
564 },
565 "node_modules/@rollup/rollup-linux-arm64-musl": {
566 "version": "4.12.0",
567 "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
568 "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
569 "cpu": [
570 "arm64"
571 ],
572 "dev": true,
573 "optional": true,
574 "os": [
575 "linux"
576 ]
577 },
578 "node_modules/@rollup/rollup-linux-riscv64-gnu": {
579 "version": "4.12.0",
580 "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
581 "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
582 "cpu": [
583 "riscv64"
584 ],
585 "dev": true,
586 "optional": true,
587 "os": [
588 "linux"
589 ]
590 },
591 "node_modules/@rollup/rollup-linux-x64-gnu": {
592 "version": "4.12.0",
593 "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
594 "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
595 "cpu": [
596 "x64"
597 ],
598 "dev": true,
599 "optional": true,
600 "os": [
601 "linux"
602 ]
603 },
604 "node_modules/@rollup/rollup-linux-x64-musl": {
605 "version": "4.12.0",
606 "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
607 "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
608 "cpu": [
609 "x64"
610 ],
611 "dev": true,
612 "optional": true,
613 "os": [
614 "linux"
615 ]
616 },
617 "node_modules/@rollup/rollup-win32-arm64-msvc": {
618 "version": "4.12.0",
619 "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
620 "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
621 "cpu": [
622 "arm64"
623 ],
624 "dev": true,
625 "optional": true,
626 "os": [
627 "win32"
628 ]
629 },
630 "node_modules/@rollup/rollup-win32-ia32-msvc": {
631 "version": "4.12.0",
632 "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
633 "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
634 "cpu": [
635 "ia32"
636 ],
637 "dev": true,
638 "optional": true,
639 "os": [
640 "win32"
641 ]
642 },
643 "node_modules/@rollup/rollup-win32-x64-msvc": {
644 "version": "4.12.0",
645 "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
646 "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
647 "cpu": [
648 "x64"
649 ],
650 "dev": true,
651 "optional": true,
652 "os": [
653 "win32"
654 ]
655 },
656 "node_modules/@sveltejs/vite-plugin-svelte": {
657 "version": "3.0.2",
658 "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.0.2.tgz",
659 "integrity": "sha512-MpmF/cju2HqUls50WyTHQBZUV3ovV/Uk8k66AN2gwHogNAG8wnW8xtZDhzNBsFJJuvmq1qnzA5kE7YfMJNFv2Q==",
660 "dev": true,
661 "dependencies": {
662 "@sveltejs/vite-plugin-svelte-inspector": "^2.0.0",
663 "debug": "^4.3.4",
664 "deepmerge": "^4.3.1",
665 "kleur": "^4.1.5",
666 "magic-string": "^0.30.5",
667 "svelte-hmr": "^0.15.3",
668 "vitefu": "^0.2.5"
669 },
670 "engines": {
671 "node": "^18.0.0 || >=20"
672 },
673 "peerDependencies": {
674 "svelte": "^4.0.0 || ^5.0.0-next.0",
675 "vite": "^5.0.0"
676 }
677 },
678 "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
679 "version": "2.0.0",
680 "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.0.0.tgz",
681 "integrity": "sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==",
682 "dev": true,
683 "dependencies": {
684 "debug": "^4.3.4"
685 },
686 "engines": {
687 "node": "^18.0.0 || >=20"
688 },
689 "peerDependencies": {
690 "@sveltejs/vite-plugin-svelte": "^3.0.0",
691 "svelte": "^4.0.0 || ^5.0.0-next.0",
692 "vite": "^5.0.0"
693 }
694 },
695 "node_modules/@tsconfig/svelte": {
696 "version": "5.0.2",
697 "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.2.tgz",
698 "integrity": "sha512-BRbo1fOtyVbhfLyuCWw6wAWp+U8UQle+ZXu84MYYWzYSEB28dyfnRBIE99eoG+qdAC0po6L2ScIEivcT07UaMA==",
699 "dev": true
700 },
701 "node_modules/@types/estree": {
702 "version": "1.0.5",
703 "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
704 "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
705 },
706 "node_modules/@types/pug": {
707 "version": "2.0.10",
708 "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
709 "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==",
710 "dev": true
711 },
712 "node_modules/acorn": {
713 "version": "8.11.3",
714 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
715 "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
716 "bin": {
717 "acorn": "bin/acorn"
718 },
719 "engines": {
720 "node": ">=0.4.0"
721 }
722 },
723 "node_modules/anymatch": {
724 "version": "3.1.3",
725 "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
726 "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
727 "dev": true,
728 "dependencies": {
729 "normalize-path": "^3.0.0",
730 "picomatch": "^2.0.4"
731 },
732 "engines": {
733 "node": ">= 8"
734 }
735 },
736 "node_modules/aria-query": {
737 "version": "5.3.0",
738 "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
739 "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
740 "dependencies": {
741 "dequal": "^2.0.3"
742 }
743 },
744 "node_modules/axobject-query": {
745 "version": "4.0.0",
746 "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz",
747 "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==",
748 "dependencies": {
749 "dequal": "^2.0.3"
750 }
751 },
752 "node_modules/balanced-match": {
753 "version": "1.0.2",
754 "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
755 "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
756 "dev": true
757 },
758 "node_modules/binary-extensions": {
759 "version": "2.2.0",
760 "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
761 "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
762 "dev": true,
763 "engines": {
764 "node": ">=8"
765 }
766 },
767 "node_modules/brace-expansion": {
768 "version": "1.1.11",
769 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
770 "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
771 "dev": true,
772 "dependencies": {
773 "balanced-match": "^1.0.0",
774 "concat-map": "0.0.1"
775 }
776 },
777 "node_modules/braces": {
778 "version": "3.0.2",
779 "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
780 "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
781 "dev": true,
782 "dependencies": {
783 "fill-range": "^7.0.1"
784 },
785 "engines": {
786 "node": ">=8"
787 }
788 },
789 "node_modules/buffer-crc32": {
790 "version": "0.2.13",
791 "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
792 "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
793 "dev": true,
794 "engines": {
795 "node": "*"
796 }
797 },
798 "node_modules/callsites": {
799 "version": "3.1.0",
800 "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
801 "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
802 "dev": true,
803 "engines": {
804 "node": ">=6"
805 }
806 },
807 "node_modules/chart.js": {
808 "version": "4.4.2",
809 "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz",
810 "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==",
811 "dependencies": {
812 "@kurkle/color": "^0.3.0"
813 },
814 "engines": {
815 "pnpm": ">=8"
816 }
817 },
818 "node_modules/chokidar": {
819 "version": "3.6.0",
820 "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
821 "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
822 "dev": true,
823 "dependencies": {
824 "anymatch": "~3.1.2",
825 "braces": "~3.0.2",
826 "glob-parent": "~5.1.2",
827 "is-binary-path": "~2.1.0",
828 "is-glob": "~4.0.1",
829 "normalize-path": "~3.0.0",
830 "readdirp": "~3.6.0"
831 },
832 "engines": {
833 "node": ">= 8.10.0"
834 },
835 "funding": {
836 "url": "https://paulmillr.com/funding/"
837 },
838 "optionalDependencies": {
839 "fsevents": "~2.3.2"
840 }
841 },
842 "node_modules/code-red": {
843 "version": "1.0.4",
844 "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
845 "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
846 "dependencies": {
847 "@jridgewell/sourcemap-codec": "^1.4.15",
848 "@types/estree": "^1.0.1",
849 "acorn": "^8.10.0",
850 "estree-walker": "^3.0.3",
851 "periscopic": "^3.1.0"
852 }
853 },
854 "node_modules/concat-map": {
855 "version": "0.0.1",
856 "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
857 "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
858 "dev": true
859 },
860 "node_modules/css-tree": {
861 "version": "2.3.1",
862 "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
863 "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
864 "dependencies": {
865 "mdn-data": "2.0.30",
866 "source-map-js": "^1.0.1"
867 },
868 "engines": {
869 "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
870 }
871 },
872 "node_modules/debug": {
873 "version": "4.3.4",
874 "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
875 "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
876 "dev": true,
877 "dependencies": {
878 "ms": "2.1.2"
879 },
880 "engines": {
881 "node": ">=6.0"
882 },
883 "peerDependenciesMeta": {
884 "supports-color": {
885 "optional": true
886 }
887 }
888 },
889 "node_modules/deepmerge": {
890 "version": "4.3.1",
891 "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
892 "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
893 "dev": true,
894 "engines": {
895 "node": ">=0.10.0"
896 }
897 },
898 "node_modules/dequal": {
899 "version": "2.0.3",
900 "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
901 "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
902 "engines": {
903 "node": ">=6"
904 }
905 },
906 "node_modules/detect-indent": {
907 "version": "6.1.0",
908 "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
909 "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
910 "dev": true,
911 "engines": {
912 "node": ">=8"
913 }
914 },
915 "node_modules/es6-promise": {
916 "version": "3.3.1",
917 "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
918 "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
919 "dev": true
920 },
921 "node_modules/esbuild": {
922 "version": "0.19.12",
923 "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
924 "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
925 "dev": true,
926 "hasInstallScript": true,
927 "bin": {
928 "esbuild": "bin/esbuild"
929 },
930 "engines": {
931 "node": ">=12"
932 },
933 "optionalDependencies": {
934 "@esbuild/aix-ppc64": "0.19.12",
935 "@esbuild/android-arm": "0.19.12",
936 "@esbuild/android-arm64": "0.19.12",
937 "@esbuild/android-x64": "0.19.12",
938 "@esbuild/darwin-arm64": "0.19.12",
939 "@esbuild/darwin-x64": "0.19.12",
940 "@esbuild/freebsd-arm64": "0.19.12",
941 "@esbuild/freebsd-x64": "0.19.12",
942 "@esbuild/linux-arm": "0.19.12",
943 "@esbuild/linux-arm64": "0.19.12",
944 "@esbuild/linux-ia32": "0.19.12",
945 "@esbuild/linux-loong64": "0.19.12",
946 "@esbuild/linux-mips64el": "0.19.12",
947 "@esbuild/linux-ppc64": "0.19.12",
948 "@esbuild/linux-riscv64": "0.19.12",
949 "@esbuild/linux-s390x": "0.19.12",
950 "@esbuild/linux-x64": "0.19.12",
951 "@esbuild/netbsd-x64": "0.19.12",
952 "@esbuild/openbsd-x64": "0.19.12",
953 "@esbuild/sunos-x64": "0.19.12",
954 "@esbuild/win32-arm64": "0.19.12",
955 "@esbuild/win32-ia32": "0.19.12",
956 "@esbuild/win32-x64": "0.19.12"
957 }
958 },
959 "node_modules/estree-walker": {
960 "version": "3.0.3",
961 "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
962 "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
963 "dependencies": {
964 "@types/estree": "^1.0.0"
965 }
966 },
967 "node_modules/fast-glob": {
968 "version": "3.3.2",
969 "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
970 "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
971 "dev": true,
972 "dependencies": {
973 "@nodelib/fs.stat": "^2.0.2",
974 "@nodelib/fs.walk": "^1.2.3",
975 "glob-parent": "^5.1.2",
976 "merge2": "^1.3.0",
977 "micromatch": "^4.0.4"
978 },
979 "engines": {
980 "node": ">=8.6.0"
981 }
982 },
983 "node_modules/fastq": {
984 "version": "1.17.1",
985 "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
986 "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
987 "dev": true,
988 "dependencies": {
989 "reusify": "^1.0.4"
990 }
991 },
992 "node_modules/fill-range": {
993 "version": "7.0.1",
994 "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
995 "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
996 "dev": true,
997 "dependencies": {
998 "to-regex-range": "^5.0.1"
999 },
1000 "engines": {
1001 "node": ">=8"
1002 }
1003 },
1004 "node_modules/fs.realpath": {
1005 "version": "1.0.0",
1006 "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
1007 "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
1008 "dev": true
1009 },
1010 "node_modules/fsevents": {
1011 "version": "2.3.3",
1012 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1013 "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1014 "dev": true,
1015 "hasInstallScript": true,
1016 "optional": true,
1017 "os": [
1018 "darwin"
1019 ],
1020 "engines": {
1021 "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1022 }
1023 },
1024 "node_modules/glob": {
1025 "version": "7.2.3",
1026 "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
1027 "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
1028 "dev": true,
1029 "dependencies": {
1030 "fs.realpath": "^1.0.0",
1031 "inflight": "^1.0.4",
1032 "inherits": "2",
1033 "minimatch": "^3.1.1",
1034 "once": "^1.3.0",
1035 "path-is-absolute": "^1.0.0"
1036 },
1037 "engines": {
1038 "node": "*"
1039 },
1040 "funding": {
1041 "url": "https://github.com/sponsors/isaacs"
1042 }
1043 },
1044 "node_modules/glob-parent": {
1045 "version": "5.1.2",
1046 "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
1047 "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
1048 "dev": true,
1049 "dependencies": {
1050 "is-glob": "^4.0.1"
1051 },
1052 "engines": {
1053 "node": ">= 6"
1054 }
1055 },
1056 "node_modules/graceful-fs": {
1057 "version": "4.2.11",
1058 "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
1059 "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
1060 "dev": true
1061 },
1062 "node_modules/import-fresh": {
1063 "version": "3.3.0",
1064 "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
1065 "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
1066 "dev": true,
1067 "dependencies": {
1068 "parent-module": "^1.0.0",
1069 "resolve-from": "^4.0.0"
1070 },
1071 "engines": {
1072 "node": ">=6"
1073 },
1074 "funding": {
1075 "url": "https://github.com/sponsors/sindresorhus"
1076 }
1077 },
1078 "node_modules/inflight": {
1079 "version": "1.0.6",
1080 "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
1081 "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
1082 "dev": true,
1083 "dependencies": {
1084 "once": "^1.3.0",
1085 "wrappy": "1"
1086 }
1087 },
1088 "node_modules/inherits": {
1089 "version": "2.0.4",
1090 "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1091 "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
1092 "dev": true
1093 },
1094 "node_modules/is-binary-path": {
1095 "version": "2.1.0",
1096 "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
1097 "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
1098 "dev": true,
1099 "dependencies": {
1100 "binary-extensions": "^2.0.0"
1101 },
1102 "engines": {
1103 "node": ">=8"
1104 }
1105 },
1106 "node_modules/is-extglob": {
1107 "version": "2.1.1",
1108 "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
1109 "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
1110 "dev": true,
1111 "engines": {
1112 "node": ">=0.10.0"
1113 }
1114 },
1115 "node_modules/is-glob": {
1116 "version": "4.0.3",
1117 "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
1118 "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
1119 "dev": true,
1120 "dependencies": {
1121 "is-extglob": "^2.1.1"
1122 },
1123 "engines": {
1124 "node": ">=0.10.0"
1125 }
1126 },
1127 "node_modules/is-number": {
1128 "version": "7.0.0",
1129 "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
1130 "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
1131 "dev": true,
1132 "engines": {
1133 "node": ">=0.12.0"
1134 }
1135 },
1136 "node_modules/is-reference": {
1137 "version": "3.0.2",
1138 "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
1139 "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
1140 "dependencies": {
1141 "@types/estree": "*"
1142 }
1143 },
1144 "node_modules/kleur": {
1145 "version": "4.1.5",
1146 "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
1147 "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
1148 "dev": true,
1149 "engines": {
1150 "node": ">=6"
1151 }
1152 },
1153 "node_modules/locate-character": {
1154 "version": "3.0.0",
1155 "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
1156 "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
1157 },
1158 "node_modules/magic-string": {
1159 "version": "0.30.7",
1160 "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
1161 "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
1162 "dependencies": {
1163 "@jridgewell/sourcemap-codec": "^1.4.15"
1164 },
1165 "engines": {
1166 "node": ">=12"
1167 }
1168 },
1169 "node_modules/mdn-data": {
1170 "version": "2.0.30",
1171 "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
1172 "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
1173 },
1174 "node_modules/merge2": {
1175 "version": "1.4.1",
1176 "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
1177 "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
1178 "dev": true,
1179 "engines": {
1180 "node": ">= 8"
1181 }
1182 },
1183 "node_modules/micromatch": {
1184 "version": "4.0.5",
1185 "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
1186 "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
1187 "dev": true,
1188 "dependencies": {
1189 "braces": "^3.0.2",
1190 "picomatch": "^2.3.1"
1191 },
1192 "engines": {
1193 "node": ">=8.6"
1194 }
1195 },
1196 "node_modules/min-indent": {
1197 "version": "1.0.1",
1198 "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
1199 "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
1200 "dev": true,
1201 "engines": {
1202 "node": ">=4"
1203 }
1204 },
1205 "node_modules/minimatch": {
1206 "version": "3.1.2",
1207 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
1208 "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
1209 "dev": true,
1210 "dependencies": {
1211 "brace-expansion": "^1.1.7"
1212 },
1213 "engines": {
1214 "node": "*"
1215 }
1216 },
1217 "node_modules/minimist": {
1218 "version": "1.2.8",
1219 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
1220 "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
1221 "dev": true,
1222 "funding": {
1223 "url": "https://github.com/sponsors/ljharb"
1224 }
1225 },
1226 "node_modules/mkdirp": {
1227 "version": "0.5.6",
1228 "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
1229 "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
1230 "dev": true,
1231 "dependencies": {
1232 "minimist": "^1.2.6"
1233 },
1234 "bin": {
1235 "mkdirp": "bin/cmd.js"
1236 }
1237 },
1238 "node_modules/mri": {
1239 "version": "1.2.0",
1240 "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
1241 "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
1242 "dev": true,
1243 "engines": {
1244 "node": ">=4"
1245 }
1246 },
1247 "node_modules/ms": {
1248 "version": "2.1.2",
1249 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1250 "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
1251 "dev": true
1252 },
1253 "node_modules/nanoid": {
1254 "version": "3.3.7",
1255 "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
1256 "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
1257 "dev": true,
1258 "funding": [
1259 {
1260 "type": "github",
1261 "url": "https://github.com/sponsors/ai"
1262 }
1263 ],
1264 "bin": {
1265 "nanoid": "bin/nanoid.cjs"
1266 },
1267 "engines": {
1268 "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1269 }
1270 },
1271 "node_modules/normalize-path": {
1272 "version": "3.0.0",
1273 "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1274 "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1275 "dev": true,
1276 "engines": {
1277 "node": ">=0.10.0"
1278 }
1279 },
1280 "node_modules/once": {
1281 "version": "1.4.0",
1282 "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1283 "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
1284 "dev": true,
1285 "dependencies": {
1286 "wrappy": "1"
1287 }
1288 },
1289 "node_modules/parent-module": {
1290 "version": "1.0.1",
1291 "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
1292 "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
1293 "dev": true,
1294 "dependencies": {
1295 "callsites": "^3.0.0"
1296 },
1297 "engines": {
1298 "node": ">=6"
1299 }
1300 },
1301 "node_modules/path-is-absolute": {
1302 "version": "1.0.1",
1303 "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
1304 "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
1305 "dev": true,
1306 "engines": {
1307 "node": ">=0.10.0"
1308 }
1309 },
1310 "node_modules/periscopic": {
1311 "version": "3.1.0",
1312 "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
1313 "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
1314 "dependencies": {
1315 "@types/estree": "^1.0.0",
1316 "estree-walker": "^3.0.0",
1317 "is-reference": "^3.0.0"
1318 }
1319 },
1320 "node_modules/picocolors": {
1321 "version": "1.0.0",
1322 "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
1323 "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
1324 "dev": true
1325 },
1326 "node_modules/picomatch": {
1327 "version": "2.3.1",
1328 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1329 "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1330 "dev": true,
1331 "engines": {
1332 "node": ">=8.6"
1333 },
1334 "funding": {
1335 "url": "https://github.com/sponsors/jonschlinkert"
1336 }
1337 },
1338 "node_modules/postcss": {
1339 "version": "8.4.35",
1340 "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
1341 "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
1342 "dev": true,
1343 "funding": [
1344 {
1345 "type": "opencollective",
1346 "url": "https://opencollective.com/postcss/"
1347 },
1348 {
1349 "type": "tidelift",
1350 "url": "https://tidelift.com/funding/github/npm/postcss"
1351 },
1352 {
1353 "type": "github",
1354 "url": "https://github.com/sponsors/ai"
1355 }
1356 ],
1357 "dependencies": {
1358 "nanoid": "^3.3.7",
1359 "picocolors": "^1.0.0",
1360 "source-map-js": "^1.0.2"
1361 },
1362 "engines": {
1363 "node": "^10 || ^12 || >=14"
1364 }
1365 },
1366 "node_modules/queue-microtask": {
1367 "version": "1.2.3",
1368 "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
1369 "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
1370 "dev": true,
1371 "funding": [
1372 {
1373 "type": "github",
1374 "url": "https://github.com/sponsors/feross"
1375 },
1376 {
1377 "type": "patreon",
1378 "url": "https://www.patreon.com/feross"
1379 },
1380 {
1381 "type": "consulting",
1382 "url": "https://feross.org/support"
1383 }
1384 ]
1385 },
1386 "node_modules/readdirp": {
1387 "version": "3.6.0",
1388 "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1389 "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1390 "dev": true,
1391 "dependencies": {
1392 "picomatch": "^2.2.1"
1393 },
1394 "engines": {
1395 "node": ">=8.10.0"
1396 }
1397 },
1398 "node_modules/resolve-from": {
1399 "version": "4.0.0",
1400 "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
1401 "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
1402 "dev": true,
1403 "engines": {
1404 "node": ">=4"
1405 }
1406 },
1407 "node_modules/reusify": {
1408 "version": "1.0.4",
1409 "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
1410 "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
1411 "dev": true,
1412 "engines": {
1413 "iojs": ">=1.0.0",
1414 "node": ">=0.10.0"
1415 }
1416 },
1417 "node_modules/rimraf": {
1418 "version": "2.7.1",
1419 "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
1420 "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
1421 "dev": true,
1422 "dependencies": {
1423 "glob": "^7.1.3"
1424 },
1425 "bin": {
1426 "rimraf": "bin.js"
1427 }
1428 },
1429 "node_modules/rollup": {
1430 "version": "4.12.0",
1431 "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
1432 "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
1433 "dev": true,
1434 "dependencies": {
1435 "@types/estree": "1.0.5"
1436 },
1437 "bin": {
1438 "rollup": "dist/bin/rollup"
1439 },
1440 "engines": {
1441 "node": ">=18.0.0",
1442 "npm": ">=8.0.0"
1443 },
1444 "optionalDependencies": {
1445 "@rollup/rollup-android-arm-eabi": "4.12.0",
1446 "@rollup/rollup-android-arm64": "4.12.0",
1447 "@rollup/rollup-darwin-arm64": "4.12.0",
1448 "@rollup/rollup-darwin-x64": "4.12.0",
1449 "@rollup/rollup-linux-arm-gnueabihf": "4.12.0",
1450 "@rollup/rollup-linux-arm64-gnu": "4.12.0",
1451 "@rollup/rollup-linux-arm64-musl": "4.12.0",
1452 "@rollup/rollup-linux-riscv64-gnu": "4.12.0",
1453 "@rollup/rollup-linux-x64-gnu": "4.12.0",
1454 "@rollup/rollup-linux-x64-musl": "4.12.0",
1455 "@rollup/rollup-win32-arm64-msvc": "4.12.0",
1456 "@rollup/rollup-win32-ia32-msvc": "4.12.0",
1457 "@rollup/rollup-win32-x64-msvc": "4.12.0",
1458 "fsevents": "~2.3.2"
1459 }
1460 },
1461 "node_modules/run-parallel": {
1462 "version": "1.2.0",
1463 "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
1464 "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
1465 "dev": true,
1466 "funding": [
1467 {
1468 "type": "github",
1469 "url": "https://github.com/sponsors/feross"
1470 },
1471 {
1472 "type": "patreon",
1473 "url": "https://www.patreon.com/feross"
1474 },
1475 {
1476 "type": "consulting",
1477 "url": "https://feross.org/support"
1478 }
1479 ],
1480 "dependencies": {
1481 "queue-microtask": "^1.2.2"
1482 }
1483 },
1484 "node_modules/sade": {
1485 "version": "1.8.1",
1486 "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
1487 "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
1488 "dev": true,
1489 "dependencies": {
1490 "mri": "^1.1.0"
1491 },
1492 "engines": {
1493 "node": ">=6"
1494 }
1495 },
1496 "node_modules/sander": {
1497 "version": "0.5.1",
1498 "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
1499 "integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==",
1500 "dev": true,
1501 "dependencies": {
1502 "es6-promise": "^3.1.2",
1503 "graceful-fs": "^4.1.3",
1504 "mkdirp": "^0.5.1",
1505 "rimraf": "^2.5.2"
1506 }
1507 },
1508 "node_modules/sorcery": {
1509 "version": "0.11.0",
1510 "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
1511 "integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==",
1512 "dev": true,
1513 "dependencies": {
1514 "@jridgewell/sourcemap-codec": "^1.4.14",
1515 "buffer-crc32": "^0.2.5",
1516 "minimist": "^1.2.0",
1517 "sander": "^0.5.0"
1518 },
1519 "bin": {
1520 "sorcery": "bin/sorcery"
1521 }
1522 },
1523 "node_modules/source-map-js": {
1524 "version": "1.0.2",
1525 "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
1526 "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
1527 "engines": {
1528 "node": ">=0.10.0"
1529 }
1530 },
1531 "node_modules/strip-indent": {
1532 "version": "3.0.0",
1533 "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
1534 "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
1535 "dev": true,
1536 "dependencies": {
1537 "min-indent": "^1.0.0"
1538 },
1539 "engines": {
1540 "node": ">=8"
1541 }
1542 },
1543 "node_modules/svelte": {
1544 "version": "4.2.11",
1545 "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.11.tgz",
1546 "integrity": "sha512-YIQk3J4X89wOLhjsqIW8tqY3JHPuBdtdOIkASP2PZeAMcSW9RsIjQzMesCrxOF3gdWYC0mKknlKF7OqmLM+Zqg==",
1547 "dependencies": {
1548 "@ampproject/remapping": "^2.2.1",
1549 "@jridgewell/sourcemap-codec": "^1.4.15",
1550 "@jridgewell/trace-mapping": "^0.3.18",
1551 "@types/estree": "^1.0.1",
1552 "acorn": "^8.9.0",
1553 "aria-query": "^5.3.0",
1554 "axobject-query": "^4.0.0",
1555 "code-red": "^1.0.3",
1556 "css-tree": "^2.3.1",
1557 "estree-walker": "^3.0.3",
1558 "is-reference": "^3.0.1",
1559 "locate-character": "^3.0.0",
1560 "magic-string": "^0.30.4",
1561 "periscopic": "^3.1.0"
1562 },
1563 "engines": {
1564 "node": ">=16"
1565 }
1566 },
1567 "node_modules/svelte-chartjs": {
1568 "version": "3.1.5",
1569 "resolved": "https://registry.npmjs.org/svelte-chartjs/-/svelte-chartjs-3.1.5.tgz",
1570 "integrity": "sha512-ka2zh7v5FiwfAX1oMflZ0HkNkgjHjFqANgRyC+vNYXfxtx2ku68Zo+2KgbKeBH2nS1ThDqkIACPzGxy4T0UaoA==",
1571 "peerDependencies": {
1572 "chart.js": "^3.5.0 || ^4.0.0",
1573 "svelte": "^4.0.0"
1574 }
1575 },
1576 "node_modules/svelte-check": {
1577 "version": "3.6.4",
1578 "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.6.4.tgz",
1579 "integrity": "sha512-mY/dqucqm46p72M8yZmn81WPZx9mN6uuw8UVfR3ZKQeLxQg5HDGO3HHm5AZuWZPYNMLJ+TRMn+TeN53HfQ/vsw==",
1580 "dev": true,
1581 "dependencies": {
1582 "@jridgewell/trace-mapping": "^0.3.17",
1583 "chokidar": "^3.4.1",
1584 "fast-glob": "^3.2.7",
1585 "import-fresh": "^3.2.1",
1586 "picocolors": "^1.0.0",
1587 "sade": "^1.7.4",
1588 "svelte-preprocess": "^5.1.0",
1589 "typescript": "^5.0.3"
1590 },
1591 "bin": {
1592 "svelte-check": "bin/svelte-check"
1593 },
1594 "peerDependencies": {
1595 "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0"
1596 }
1597 },
1598 "node_modules/svelte-hmr": {
1599 "version": "0.15.3",
1600 "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz",
1601 "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==",
1602 "dev": true,
1603 "engines": {
1604 "node": "^12.20 || ^14.13.1 || >= 16"
1605 },
1606 "peerDependencies": {
1607 "svelte": "^3.19.0 || ^4.0.0"
1608 }
1609 },
1610 "node_modules/svelte-preprocess": {
1611 "version": "5.1.3",
1612 "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.3.tgz",
1613 "integrity": "sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==",
1614 "dev": true,
1615 "hasInstallScript": true,
1616 "dependencies": {
1617 "@types/pug": "^2.0.6",
1618 "detect-indent": "^6.1.0",
1619 "magic-string": "^0.30.5",
1620 "sorcery": "^0.11.0",
1621 "strip-indent": "^3.0.0"
1622 },
1623 "engines": {
1624 "node": ">= 16.0.0",
1625 "pnpm": "^8.0.0"
1626 },
1627 "peerDependencies": {
1628 "@babel/core": "^7.10.2",
1629 "coffeescript": "^2.5.1",
1630 "less": "^3.11.3 || ^4.0.0",
1631 "postcss": "^7 || ^8",
1632 "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
1633 "pug": "^3.0.0",
1634 "sass": "^1.26.8",
1635 "stylus": "^0.55.0",
1636 "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0",
1637 "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0",
1638 "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0"
1639 },
1640 "peerDependenciesMeta": {
1641 "@babel/core": {
1642 "optional": true
1643 },
1644 "coffeescript": {
1645 "optional": true
1646 },
1647 "less": {
1648 "optional": true
1649 },
1650 "postcss": {
1651 "optional": true
1652 },
1653 "postcss-load-config": {
1654 "optional": true
1655 },
1656 "pug": {
1657 "optional": true
1658 },
1659 "sass": {
1660 "optional": true
1661 },
1662 "stylus": {
1663 "optional": true
1664 },
1665 "sugarss": {
1666 "optional": true
1667 },
1668 "typescript": {
1669 "optional": true
1670 }
1671 }
1672 },
1673 "node_modules/to-regex-range": {
1674 "version": "5.0.1",
1675 "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1676 "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1677 "dev": true,
1678 "dependencies": {
1679 "is-number": "^7.0.0"
1680 },
1681 "engines": {
1682 "node": ">=8.0"
1683 }
1684 },
1685 "node_modules/tslib": {
1686 "version": "2.6.2",
1687 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
1688 "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
1689 "dev": true
1690 },
1691 "node_modules/typescript": {
1692 "version": "5.3.3",
1693 "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
1694 "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
1695 "dev": true,
1696 "bin": {
1697 "tsc": "bin/tsc",
1698 "tsserver": "bin/tsserver"
1699 },
1700 "engines": {
1701 "node": ">=14.17"
1702 }
1703 },
1704 "node_modules/vite": {
1705 "version": "5.1.3",
1706 "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz",
1707 "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==",
1708 "dev": true,
1709 "dependencies": {
1710 "esbuild": "^0.19.3",
1711 "postcss": "^8.4.35",
1712 "rollup": "^4.2.0"
1713 },
1714 "bin": {
1715 "vite": "bin/vite.js"
1716 },
1717 "engines": {
1718 "node": "^18.0.0 || >=20.0.0"
1719 },
1720 "funding": {
1721 "url": "https://github.com/vitejs/vite?sponsor=1"
1722 },
1723 "optionalDependencies": {
1724 "fsevents": "~2.3.3"
1725 },
1726 "peerDependencies": {
1727 "@types/node": "^18.0.0 || >=20.0.0",
1728 "less": "*",
1729 "lightningcss": "^1.21.0",
1730 "sass": "*",
1731 "stylus": "*",
1732 "sugarss": "*",
1733 "terser": "^5.4.0"
1734 },
1735 "peerDependenciesMeta": {
1736 "@types/node": {
1737 "optional": true
1738 },
1739 "less": {
1740 "optional": true
1741 },
1742 "lightningcss": {
1743 "optional": true
1744 },
1745 "sass": {
1746 "optional": true
1747 },
1748 "stylus": {
1749 "optional": true
1750 },
1751 "sugarss": {
1752 "optional": true
1753 },
1754 "terser": {
1755 "optional": true
1756 }
1757 }
1758 },
1759 "node_modules/vitefu": {
1760 "version": "0.2.5",
1761 "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz",
1762 "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==",
1763 "dev": true,
1764 "peerDependencies": {
1765 "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
1766 },
1767 "peerDependenciesMeta": {
1768 "vite": {
1769 "optional": true
1770 }
1771 }
1772 },
1773 "node_modules/wrappy": {
1774 "version": "1.0.2",
1775 "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1776 "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
1777 "dev": true
1778 }
1779 }
1780}
diff --git a/fe/package.json b/fe/package.json
new file mode 100644
index 0000000..9c75965
--- /dev/null
+++ b/fe/package.json
@@ -0,0 +1,25 @@
1{
2 "name": "fe",
3 "private": true,
4 "version": "0.0.0",
5 "type": "module",
6 "scripts": {
7 "dev": "vite",
8 "build": "vite build",
9 "preview": "vite preview",
10 "check": "svelte-check --tsconfig ./tsconfig.json"
11 },
12 "devDependencies": {
13 "@sveltejs/vite-plugin-svelte": "^3.0.2",
14 "@tsconfig/svelte": "^5.0.2",
15 "svelte": "^4.2.10",
16 "svelte-check": "^3.6.3",
17 "tslib": "^2.6.2",
18 "typescript": "^5.2.2",
19 "vite": "^5.1.0"
20 },
21 "dependencies": {
22 "chart.js": "^4.4.2",
23 "svelte-chartjs": "^3.1.5"
24 }
25}
diff --git a/fe/public/vite.svg b/fe/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/fe/public/vite.svg
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg> \ No newline at end of file
diff --git a/fe/src/App.svelte b/fe/src/App.svelte
new file mode 100644
index 0000000..25d53dc
--- /dev/null
+++ b/fe/src/App.svelte
@@ -0,0 +1,16 @@
1<script lang="ts">
2 import Layout from "./lib/Layout.svelte";
3 import LoginForm from "./lib/LoginForm.svelte";
4 import DataView from "./lib/DataView.svelte";
5 import { authenticated } from "./stores/auth";
6</script>
7
8<main>
9 <Layout>
10 {#if !$authenticated}
11 <LoginForm />
12 {:else}
13 <DataView />
14 {/if}
15 </Layout>
16</main>
diff --git a/fe/src/app.css b/fe/src/app.css
new file mode 100644
index 0000000..c24c713
--- /dev/null
+++ b/fe/src/app.css
@@ -0,0 +1,120 @@
1:root {
2 font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 line-height: 1.5;
4 font-weight: 400;
5
6 color-scheme: light dark;
7 color: rgba(255, 255, 255, 0.87);
8 background-color: #242424;
9
10 font-synthesis: none;
11 text-rendering: optimizeLegibility;
12 -webkit-font-smoothing: antialiased;
13 -moz-osx-font-smoothing: grayscale;
14
15 --submit: #28a745;
16}
17
18a {
19 font-weight: 500;
20 color: #646cff;
21 text-decoration: inherit;
22}
23
24a:hover {
25 color: #535bf2;
26}
27
28body {
29 margin: 0;
30 display: flex;
31 place-items: center;
32 min-width: 320px;
33 min-height: 100vh;
34}
35
36h1 {
37 font-size: 3.2em;
38 line-height: 1.1;
39}
40
41.card {
42 padding: 2em;
43}
44
45#app {
46 flex-grow: 2;
47 max-width: 1280px;
48 margin: 0 auto;
49}
50
51button {
52 border-radius: 8px;
53 border: 1px solid transparent;
54 padding: 0.6em 1.2em;
55 font-size: 1em;
56 font-weight: 500;
57 font-family: inherit;
58 background-color: #1a1a1a;
59 cursor: pointer;
60 transition: border-color 0.25s;
61}
62
63button:hover {
64 border-color: #646cff;
65}
66
67button:focus,
68button:focus-visible {
69 outline: 4px auto -webkit-focus-ring-color;
70}
71
72@media (prefers-color-scheme: light) {
73 :root {
74 color: #213547;
75 background-color: #ffffff;
76 }
77
78 a:hover {
79 color: #747bff;
80 }
81
82 button {
83 background-color: #f9f9f9;
84 }
85}
86
87@media (prefers-color-scheme: dark) {
88 :root {
89 color: #000;
90 }
91}
92
93.form {
94 display: flex;
95 flex-direction: column;
96}
97
98.form.input.group {
99 display: flex;
100 flex-direction: column;
101 margin-bottom: 1em;
102}
103
104.form.input.group label {
105 margin-bottom: .5em;
106}
107
108.form.input.group input {
109 padding: 1em;
110}
111
112.form.input.group input[type=color] {
113 padding: 0;
114}
115
116.form button[type=submit] {
117 align-self: flex-end;
118 background: var(--submit);
119 color: #fff;
120}
diff --git a/fe/src/assets/svelte.svg b/fe/src/assets/svelte.svg
new file mode 100644
index 0000000..c5e0848
--- /dev/null
+++ b/fe/src/assets/svelte.svg
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg> \ No newline at end of file
diff --git a/fe/src/errors.ts b/fe/src/errors.ts
new file mode 100644
index 0000000..81f7145
--- /dev/null
+++ b/fe/src/errors.ts
@@ -0,0 +1,5 @@
1export class UnauthorizedError extends Error {
2 constructor(message?: string) {
3 super(message);
4 }
5}
diff --git a/fe/src/http.ts b/fe/src/http.ts
new file mode 100644
index 0000000..3b2a4f0
--- /dev/null
+++ b/fe/src/http.ts
@@ -0,0 +1,92 @@
1let instance;
2const baseUrl = import.meta.env?.VITE_API_BASE_URL ?? "http://localhost:8080/api/v1";
3
4class HttpClient {
5 baseURL: string;
6 commonHeaders: Headers;
7
8 constructor(baseURL: string) {
9 this.baseURL = baseURL;
10 this.commonHeaders = new Headers({
11 "Content-Type": "application/json"
12 })
13 if (instance) {
14 throw new Error("New instance cannot be created!");
15 }
16
17 instance = this;
18 }
19
20 private getURL(endpoint: string): URL {
21 return new URL(endpoint, this.baseURL);
22 }
23
24 private token(): string | null {
25 return localStorage.getItem('token');
26 }
27
28 private async makeRequest(request: Request): Promise<Response> {
29 return fetch(request)
30 }
31
32 async get({ endpoint, headers }: IHttpParameters): Promise<Response> {
33 const url: URL = this.getURL(endpoint);
34 headers = Object.assign<Headers, Headers>(headers, this.commonHeaders);
35 const request: Request = new Request(url, {
36 method: "GET",
37 headers
38 });
39
40 return this.makeRequest(request);
41 }
42
43 async post({ endpoint, authenticated, body, headers }: IHttpParameters): Promise<Response> {
44 const url = this.getURL(endpoint);
45
46 if (authenticated) {
47 const token: string | null = this.token();
48 headers.append('Authorization', `Bearer ${token}`);
49 }
50
51 const request: Request = new Request(url, {
52 method: "POST",
53 body: JSON.stringify(body),
54 headers
55 })
56
57 return this.makeRequest(request);
58 }
59
60 async patch({ endpoint, authenticated, headers }: IHttpParameters): Promise<Response> {
61 const url = this.getURL(endpoint);
62 if (authenticated) {
63
64 }
65 const response: Response = await fetch(url, {
66 method: "PATCH",
67 headers
68 });
69 }
70
71 async delete({ endpoint, authenticated, headers }: IHttpParameters): Promise<Response> {
72 const url = this.getURL(endpoint);
73 if (authenticated) {
74
75 }
76 const response: Response = await fetch(url, {
77 method: "DELETE",
78 headers
79 })
80 }
81}
82
83interface IHttpParameters {
84 endpoint: string;
85 body: Record<string, any>;
86 authenticated: boolean;
87 headers: Headers;
88}
89
90let http: Readonly<HttpClient> = Object.freeze(new HttpClient(baseUrl));
91
92export default http;
diff --git a/fe/src/lib/Card.svelte b/fe/src/lib/Card.svelte
new file mode 100644
index 0000000..cd1e02c
--- /dev/null
+++ b/fe/src/lib/Card.svelte
@@ -0,0 +1,23 @@
1<script lang="ts">
2 export let title = "";
3</script>
4
5<div class="card">
6 {#if title}
7 <h2>{title}</h2>
8 {/if}
9 <slot />
10</div>
11
12<style>
13 .card {
14 background: #fff;
15 display: flex;
16 flex-direction: column;
17 align-items: flex-start;
18 border: solid 2px #00000066;
19 border-radius: 0.25em;
20 height: var(--height, fit-content);
21 overflow-y: var(--overflow, initial);
22 }
23</style>
diff --git a/fe/src/lib/Chart.svelte b/fe/src/lib/Chart.svelte
new file mode 100644
index 0000000..b19d932
--- /dev/null
+++ b/fe/src/lib/Chart.svelte
@@ -0,0 +1,63 @@
1<script lang="ts">
2import { onDestroy } from "svelte";
3import ChartJS from "chart.js/auto";
4
5export let data;
6export let labels;
7export let type = 'bar';
8
9let ref: HTMLCanvasElement;
10let chart
11
12function setupChart(result) {
13 [labels, data] = result;
14 chart = new ChartJS(ref, {
15 type,
16 data: {
17 labels,
18 datasets: [
19 {
20 label: "Totals",
21 data,
22 backgroundColor: "rgba(255, 192, 192, 0.2)"
23 }
24 ]
25 },
26 options: {
27 responsive: true,
28 maintainAspectRatio: false,
29 scales: {
30 y: {
31 suggestedMax: 30,
32 beginAtZero: true,
33 ticks: {
34 autoSkip: true,
35 stepSize: 5
36 }
37 }
38 },
39 plugins: {
40 legend: {
41 display: false
42 },
43 title: {
44 display: true,
45 text: "Weekly Breakdown"
46 },
47 subtitle: {
48 display: true,
49 text: "Water consumption over the last week",
50 padding: {bottom: 10}
51 }
52 }
53 }
54 });
55
56 onDestroy(() => {
57 if (chart) chart.destroy();
58 chart = null;
59 })
60}
61</script>
62
63<canvas bind:this={ref} /> \ No newline at end of file
diff --git a/fe/src/lib/Column.svelte b/fe/src/lib/Column.svelte
new file mode 100644
index 0000000..f036073
--- /dev/null
+++ b/fe/src/lib/Column.svelte
@@ -0,0 +1,13 @@
1<div class="column">
2 <slot />
3</div>
4
5<style>
6 .column {
7 display: flex;
8 flex-direction: column;
9 height: 100%;
10 gap: var(--gap, 32px);
11 width: var(--width, initial);
12 }
13</style> \ No newline at end of file
diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte
new file mode 100644
index 0000000..ffc2fe8
--- /dev/null
+++ b/fe/src/lib/DataView.svelte
@@ -0,0 +1,228 @@
1<script lang="ts">
2 import { onDestroy, onMount } from "svelte";
3 import http from "../http";
4 import { token } from "../stores/auth";
5 import { addFormOpen } from "../stores/forms";
6 import Table from "./Table.svelte";
7 import ChartJS from "chart.js/auto";
8 import Chart from './Chart.svelte'
9 import Card from "./Card.svelte";
10 import Column from "./Column.svelte";
11 import AddForm from "./forms/AddForm.svelte";
12 import { apiURL } from "../utils";
13
14 let json: Promise<any>;
15
16 let barCanvasRef: HTMLCanvasElement;
17 let lineCanvasRef: HTMLCanvasElement;
18 let barChart: any;
19 let lineChart: any;
20
21 let lastSevenDays: string[];
22 let lastSevenDaysData: number[];
23
24 let userTotalsLabels: string[];
25 let userTotalsData: number[];
26
27 async function fetchData() {
28 const res = await fetch(apiURL("stats"), {
29 method: "GET",
30 headers: {
31 Authorization: `Bearer ${$token}`
32 }
33 });
34 if (res.ok) {
35 json = res.json();
36 } else {
37 throw new Error("There was a problem with your request");
38 }
39 }
40
41 async function fetchDailyUserStatistics() {
42 const res = await fetch(apiURL("stats/daily"), {
43 method: "GET",
44 headers: {
45 Authorization: `Bearer ${$token}`
46 }
47 });
48
49 if (res.ok) {
50 const json = await res.json();
51 let labels = json.map((d: any) => d.name);
52 let data = json.map((d: any) => d.total);
53 return [labels, data];
54 } else {
55 throw new Error("There was a problem with your request");
56 }
57
58 }
59
60 async function fetchWeeklyTotals() {
61 const res = await fetch(apiURL("stats/weekly"), {
62 method: "GET",
63 headers: {
64 Authorization: `Bearer ${$token}`
65 }
66 });
67
68 if (res.ok) {
69 const json = await res.json();
70 let labels = json.map((d: any) => d.date);
71 let data = json.map((d: any) => d.total);
72 return [labels, data];
73 } else {
74 throw new Error("There was a problem with your request");
75 }
76 }
77
78 function closeDialog() {
79 addFormOpen.set(false);
80 }
81
82 function onStatisticAdd() {
83 closeDialog();
84 fetchData();
85 fetchWeeklyTotals().then(updateWeeklyTotalsChart).catch(err => console.error(err));
86 fetchDailyUserStatistics().then(updateDailyUserTotalsChart).catch(err => console.error(err));
87 }
88
89 function setupWeeklyTotalsChart(result: any) {
90 [lastSevenDays, lastSevenDaysData] = result;
91 lineChart = new ChartJS(lineCanvasRef, {
92 type: "line",
93 data: {
94 labels: lastSevenDays,
95 datasets: [
96 {
97 label: "Totals",
98 data: lastSevenDaysData,
99 backgroundColor: "rgba(255, 192, 192, 0.2)"
100 }
101 ]
102 },
103 options: {
104 responsive: true,
105 maintainAspectRatio: false,
106 scales: {
107 y: {
108 suggestedMax: 30,
109 beginAtZero: true,
110 ticks: {
111 autoSkip: true,
112 stepSize: 5
113 }
114 }
115 },
116 plugins: {
117 legend: {
118 display: false
119 },
120 title: {
121 display: true,
122 text: "Weekly Breakdown"
123 },
124 subtitle: {
125 display: true,
126 text: "Water consumption over the last week",
127 padding: {bottom: 10}
128 }
129 }
130 }
131 });
132 }
133
134 function setupDailyUserTotalsChart(result: any) {
135 [userTotalsLabels, userTotalsData] = result;
136
137 barChart = new ChartJS(barCanvasRef, {
138 type: "bar",
139 data: {
140 labels: userTotalsLabels,
141 datasets: [
142 {
143 data: userTotalsData,
144 backgroundColor: [
145 "#330000",
146 "rgba(100, 200, 192, 0.2)"
147 ]
148 }
149 ]
150 },
151 options: {
152 responsive: true,
153 maintainAspectRatio: false,
154 scales: {
155 y: {
156 beginAtZero: true,
157 suggestedMax: 10,
158 ticks: {
159 autoSkip: true,
160 stepSize: 1
161 }
162 }
163 },
164 plugins: {
165 legend: {
166 display: false
167 },
168 title: {
169 display: true,
170 text: "Daily Total"
171 },
172 subtitle: {
173 display: true,
174 text: "Water Drank Today",
175 padding: {bottom: 10}
176 }
177 }
178 }
179 });
180 }
181
182 function updateWeeklyTotalsChart(result: any) {
183 [, lastSevenDaysData] = result;
184 lineChart.data.datasets[0].data = lastSevenDaysData;
185 lineChart.update();
186 }
187
188 function updateDailyUserTotalsChart(result: any) {
189 [, userTotalsData] = result;
190 barChart.data.datasets[0].data = userTotalsData;
191 barChart.update();
192 }
193
194 onMount(() => {
195 fetchData();
196 fetchWeeklyTotals().then(setupWeeklyTotalsChart);
197 fetchDailyUserStatistics().then(setupDailyUserTotalsChart);
198 });
199
200 onDestroy(() => {
201 if (barChart) barChart.destroy();
202 if (lineChart) lineChart.destroy();
203 barChart = null;
204 lineChart = null;
205 });
206</script>
207
208<Column --width="500px">
209 <Card --height="300px">
210 <!--<Chart />-->
211 <canvas bind:this={barCanvasRef} />
212 </Card>
213 <Card --height="300px">
214 <canvas bind:this={lineCanvasRef} />
215 </Card>
216</Column>
217
218<AddForm open={$addFormOpen} on:submit={onStatisticAdd} on:close={closeDialog} />
219<Column>
220 <Card>
221 {#await json then data}
222 <Table {data} nofooter />
223 {:catch error}
224 <p>{error}</p>
225 {/await}
226 </Card>
227</Column>
228<!-- <Chart /> -->
diff --git a/fe/src/lib/Layout.svelte b/fe/src/lib/Layout.svelte
new file mode 100644
index 0000000..2728dd3
--- /dev/null
+++ b/fe/src/lib/Layout.svelte
@@ -0,0 +1,74 @@
1<script>
2 import { authenticated, token, user, preferences } from "../stores/auth";
3 import PreferencesForm from "./PreferencesForm.svelte";
4 import { addFormOpen } from "../stores/forms";
5
6 const logout = () => {
7 preferences.reset();
8 user.reset();
9 token.unauthenticate();
10 }
11 let preferenceFormOpen = false;
12
13 function showPreferencesDialog() {
14 preferenceFormOpen = true;
15 }
16
17 function closePreferenceDialog() {
18 preferenceFormOpen = false;
19 }
20
21 function showAddDialog() {
22 addFormOpen.set(true);
23 }
24</script>
25
26<div class="layout">
27 {#if $authenticated}
28 <nav>
29 <div>
30 <h1>Water</h1>
31 </div>
32 <div>
33 <button on:click={showAddDialog}>Log Water</button>
34 <button on:click={showPreferencesDialog}>Preference</button>
35 <button on:click={logout}>Logout</button>
36 </div>
37 </nav>
38 <PreferencesForm open={preferenceFormOpen} on:close={closePreferenceDialog} />
39 {/if}
40 <div id="content">
41 <slot />
42 </div>
43</div>
44
45<style>
46 .layout {
47 height: 100vh;
48 }
49
50 nav {
51 display: flex;
52 flex-direction: row;
53 align-items: center;
54 justify-content: space-between;
55 height: 64px;
56 padding: 0 2em;
57 }
58
59 nav div {
60 width: fit-content;
61 }
62
63 nav div h1 {
64 font-size: 1.75em;
65 }
66
67 #content {
68 display: flex;
69 flex-direction: row;
70 justify-content: center;
71 gap: 2em;
72 margin-top: 4em;
73 }
74</style>
diff --git a/fe/src/lib/LoginForm.svelte b/fe/src/lib/LoginForm.svelte
new file mode 100644
index 0000000..cf5febf
--- /dev/null
+++ b/fe/src/lib/LoginForm.svelte
@@ -0,0 +1,86 @@
1<script lang="ts">
2 import { token, user, preferences } from "../stores/auth";
3 import Card from "./Card.svelte";
4 import { apiURL } from "../utils";
5
6 let credentials: CredentialObject = {
7 username: "",
8 password: "",
9 };
10
11 let error: string | null = null;
12
13 interface CredentialObject {
14 username: string;
15 password: string;
16 }
17
18 function prepareCredentials({
19 username,
20 password,
21 }: CredentialObject): string {
22 return btoa(`${username}:${password}`);
23 }
24
25 async function onSubmit(e: Event) {
26 if (!credentials.username || !credentials.password) {
27 error = "please enter your username and password";
28 return;
29 }
30 const auth = prepareCredentials(credentials);
31
32 const response = await fetch(apiURL("auth"), {
33 method: "POST",
34 headers: {
35 Authorization: `Basic ${auth}`,
36 },
37 });
38
39 if (response.status === 401) {
40 error = "Your username or password is wrong";
41 return;
42 }
43
44 if (response.ok) {
45 const {
46 token: apiToken,
47 user: userData,
48 preferences: userPreferences,
49 } = await response.json();
50 user.setUser(userData);
51 preferences.setPreference(userPreferences);
52 token.authenticate(apiToken);
53 }
54
55 error = null;
56 }
57</script>
58
59<Card>
60 <form class="form" on:submit|preventDefault={onSubmit}>
61 <div class="form input group">
62 <label for="username">Username</label>
63 <input
64 bind:value={credentials.username}
65 id="username"
66 name="username"
67 type="text"
68 autocomplete="username"
69 />
70 </div>
71 <div class="form input group">
72 <label for="password">Password</label>
73 <input
74 bind:value={credentials.password}
75 id="password"
76 name="password"
77 type="password"
78 autocomplete="current-password"
79 />
80 </div>
81 {#if error}
82 <p class="error">{error}</p>
83 {/if}
84 <button type="submit">Log in</button>
85 </form>
86</Card>
diff --git a/fe/src/lib/PreferencesForm.svelte b/fe/src/lib/PreferencesForm.svelte
new file mode 100644
index 0000000..74b8a63
--- /dev/null
+++ b/fe/src/lib/PreferencesForm.svelte
@@ -0,0 +1,119 @@
1<script lang="ts">
2 import { user, preferences, token } from "../stores/auth";
3 import { createEventDispatcher, onDestroy, onMount } from "svelte";
4 import type { User } from "../types";
5 import { apiURL } from "../utils";
6
7 export let open: boolean;
8
9 let sizes: Array<any>;
10 let selectedSize: number = 1;
11 let color: string = "#000000";
12
13 const dispatch = createEventDispatcher();
14
15 const unsubscribe = preferences.subscribe(
16 (value: any) => {
17 if (value) {
18 color = value.color;
19 selectedSize = value.size_id;
20 }
21 },
22 );
23
24 function closeDialog() {
25 dispatch("close");
26 }
27
28 async function updateUserPreferences() {
29 const res = await fetch(apiURL("user/preferences"), {
30 method: "PATCH",
31 headers: {
32 Authorization: `Bearer ${$token}`,
33 },
34 body: JSON.stringify($preferences),
35 });
36 }
37
38 async function getUserPreferences() {
39 const res = await fetch(apiURL(`user/${($user as User)!.id}/preferences`),
40 {
41 method: "GET",
42 headers: {
43 Authorization: `Bearer ${$token}`,
44 },
45 },
46 );
47 const updatePreferences = await res.json();
48 preferences.set(updatePreferences);
49 }
50
51 async function onPreferencesSave(): Promise<void> {
52 preferences.update((value) => ({
53 ...value!,
54 size_id: selectedSize,
55 color: color,
56 }));
57
58 await updateUserPreferences();
59 await getUserPreferences();
60
61 dispatch("close");
62 }
63
64 onMount(() => {
65 fetch(apiURL("sizes"), {
66 method: "GET",
67 headers: {
68 Authorization: `Bearer ${$token}`,
69 },
70 })
71 .then((res) => res.json())
72 .then((val) => (sizes = val));
73 });
74
75 onDestroy(() => {
76 unsubscribe();
77 });
78</script>
79
80<dialog {open} on:submit|preventDefault={onPreferencesSave}>
81 <h2>User Preferences</h2>
82 <form method="dialog">
83 <div class="form input group">
84 <label for="color">Color</label>
85 <input
86 id="color"
87 name="color"
88 type="color"
89 bind:value={color}
90 />
91 </div>
92 <div class="form input group">
93 <label for="size">Bottle Size</label>
94 <select
95 bind:value={selectedSize}
96 >
97 {#if sizes}
98 {#each sizes as size}
99 <option value={size.id}>{size.size} {size.unit}</option>
100 {/each}
101 {/if}
102 </select>
103 </div>
104 <button on:click={closeDialog}>Cancel</button>
105 <button type="submit">Save</button>
106 </form>
107</dialog>
108
109<style>
110 dialog {
111 background: white;
112 color: black;
113 }
114
115 input[type="color"] {
116 width: 4em;
117 height: 4em;
118 }
119</style>
diff --git a/fe/src/lib/Table.svelte b/fe/src/lib/Table.svelte
new file mode 100644
index 0000000..621157e
--- /dev/null
+++ b/fe/src/lib/Table.svelte
@@ -0,0 +1,114 @@
1<script lang="ts">
2 export let data: Array<any> | undefined = undefined;
3 export let nofooter: boolean = false;
4 export let noheader: boolean = false;
5 export let omit: string[] = ["id"];
6 export let title: string | undefined = undefined;
7
8 export let sortBy: string = 'date';
9
10 type SortComparator = (a, b) => number
11
12 function getDataKeys(data: any[]): string[] {
13 if (!data || data.length === 0) return [];
14 return Object.keys(data[0])
15 .map((k) => k.split("_").join(" "))
16 .filter((k) => !omit.includes(k));
17 }
18
19 function getRow(row: Record<string, any>): Array<any> {
20 return Object.entries(row).filter((r) => !omit.includes(r[0]));
21 }
22
23 function sort(arr: Array<Record<string, any>>, fn: SortComparator = (a , b) => new Date(b[sortBy]) - new Date(a[sortBy])) {
24 return arr.sort(fn)
25 }
26
27 const formatter = new Intl.DateTimeFormat("en", {
28 year: "numeric",
29 month: "numeric",
30 day: "numeric",
31 hour: "numeric",
32 minute: "2-digit",
33 second: "2-digit",
34 timeZone: "America/New_York"
35 });
36
37 function formatDatum([key, value]: any[]) {
38 if (key === "date") {
39 const parsedDate = new Date(value);
40 return formatter.format(parsedDate);
41 }
42
43 if (key === "user") {
44 return value["name"];
45 }
46
47 return value;
48 }
49</script>
50
51<table>
52 {#if title}
53 <h2>{title}</h2>
54 {/if}
55 {#if !noheader && data}
56 <thead>
57 <tr>
58 {#each getDataKeys(data) as header}
59 <th>{header}</th>
60 {/each}
61 </tr>
62 </thead>
63 {/if}
64 <tbody>
65 {#if data}
66 {#each sort(data) as row}
67 <tr>
68 {#each getRow(row) as datum}
69 <td>{formatDatum(datum)}</td>
70 {/each}
71 </tr>
72 {/each}
73 {:else}
74 <tr> There is not data.</tr>
75 {/if}
76 </tbody>
77 {#if !nofooter}
78 <slot name="footer">
79 <tfoot>
80 <tr>
81 <td>Table Footer</td>
82 </tr>
83 </tfoot>
84 </slot>
85 {/if}
86</table>
87
88<style>
89 table {
90 padding: 16px;
91 margin: 8px;
92 border: solid 1px black;
93 border-collapse: collapse;
94 overflow-y: hidden;
95 }
96
97 th {
98 text-transform: capitalize;
99 }
100
101 thead tr {
102 background: rgba(0, 0, 23, 0.34);
103 }
104
105 tbody tr:nth-child(odd) {
106 background: rgba(0, 0, 23, 0.14);
107 }
108
109 th,
110 td {
111 padding: 1em;
112 border: 1px solid rgba(0, 0, 0, 1);
113 }
114</style>
diff --git a/fe/src/lib/forms/AddForm.svelte b/fe/src/lib/forms/AddForm.svelte
new file mode 100644
index 0000000..f85cce6
--- /dev/null
+++ b/fe/src/lib/forms/AddForm.svelte
@@ -0,0 +1,78 @@
1<script lang='ts'>
2 import { createEventDispatcher } from "svelte";
3 import { token, user } from "../../stores/auth";
4 import type { Statistic } from "../../types";
5 import { apiURL } from "../../utils";
6
7 export let open: boolean;
8
9 const dispatch = createEventDispatcher();
10
11 const statistic: Statistic = newStatistic();
12
13 function newStatistic(): Statistic {
14 let now = new Date(),
15 month,
16 day,
17 year;
18
19 month = `${now.getMonth() + 1}`;
20 day = `${now.getDate()}`;
21 year = now.getFullYear();
22 if (month.length < 2) month = "0" + month;
23 if (day.length < 2) day = "0" + day;
24
25 const date = [year, month, day].join("-");
26
27 return {
28 user_id: $user!.uuid,
29 date,
30 quantity: 1
31 };
32 }
33
34 function closeDialog() {
35 dispatch("close");
36 }
37
38 async function handleSubmitStat()
39 {
40 const { date, quantity } = statistic;
41 await fetch(apiURL("stats"), {
42 method: "POST",
43 headers: {
44 Authorization: `Bearer ${$token}`
45 },
46 body: JSON.stringify({
47 date: new Date(date),
48 user_id: 2,
49 quantity
50 })
51 });
52 dispatch("submit");
53 }
54
55</script>
56
57<dialog {open} on:submit={handleSubmitStat}>
58 <h2>Add Water</h2>
59 <form method="dialog">
60 <div class="form input group">
61 <label for="date">Date:</label>
62 <input bind:value={statistic.date} id="date" name="date" type="date" />
63 </div>
64 <div class="form input group">
65 <label for="quantity">Quantity:</label>
66 <input
67 bind:value={statistic.quantity}
68 id="quantity"
69 name="quantity"
70 type="number"
71 min="0"
72 autocomplete="off"
73 />
74 </div>
75 <button on:click={closeDialog}>Cancel</button>
76 <button type="submit">Submit</button>
77 </form>
78</dialog> \ No newline at end of file
diff --git a/fe/src/main.ts b/fe/src/main.ts
new file mode 100644
index 0000000..ff866d0
--- /dev/null
+++ b/fe/src/main.ts
@@ -0,0 +1,8 @@
1import './app.css'
2import App from './App.svelte'
3
4const app = new App({
5 target: document.getElementById('app') as HTMLElement,
6})
7
8export default app
diff --git a/fe/src/stores/auth.ts b/fe/src/stores/auth.ts
new file mode 100644
index 0000000..63f027e
--- /dev/null
+++ b/fe/src/stores/auth.ts
@@ -0,0 +1,90 @@
1import type { Preference, TokenStore, Nullable, UserStore, User, PreferenceStore } from "../types";
2import { writable, derived } from "svelte/store";
3
4
5function createTokenStore(): TokenStore {
6 const storedToken = localStorage.getItem("token");
7 const { subscribe, set } = writable<string | null>(storedToken);
8
9 function authenticate(newToken: string): void {
10 try {
11 localStorage.setItem("token", newToken);
12 set(newToken);
13 } catch (e) {
14 console.error("error", e);
15 }
16 }
17
18 function unauthenticate(): void {
19 localStorage.removeItem("token");
20 set(null);
21 }
22
23 return {
24 subscribe,
25 authenticate,
26 unauthenticate
27 };
28}
29
30function onTokenChange($token: Nullable<string>): boolean {
31 return $token ? true : false;
32}
33
34function createUserStore(): UserStore {
35 const user = localStorage.getItem("user");
36 const userObj: Nullable<User> = user ? JSON.parse(user) : null;
37 const { subscribe, set } = writable<User | null>(userObj);
38
39 const setUser = (user: User) => {
40 localStorage.setItem("user", JSON.stringify(user));
41 set(user);
42 };
43
44 const reset = () => {
45 localStorage.removeItem("user");
46 set(null);
47 };
48
49 return {
50 subscribe,
51 setUser,
52 reset
53 };
54}
55
56
57function createPreferenceStore(): PreferenceStore {
58 const preferences = localStorage.getItem("preferences");
59 const preferenceObj: Preference = preferences ? JSON.parse(preferences) : {
60 id: 0,
61 color: "#FF0000",
62 size_id: 0,
63 user_id: 0
64 };
65
66 const { subscribe, set, update } = writable<Nullable<Preference>>(preferenceObj);
67
68 const setPreference = (preference: Preference) => {
69 localStorage.setItem("preference", JSON.stringify(preference));
70 set(preference);
71 };
72
73 const reset = () => {
74 localStorage.removeItem("preference");
75 set(null);
76 };
77
78 return {
79 set,
80 subscribe,
81 reset,
82 update,
83 setPreference,
84 };
85}
86
87export const token = createTokenStore();
88export const authenticated = derived(token, onTokenChange);
89export const user = createUserStore();
90export const preferences = createPreferenceStore();
diff --git a/fe/src/stores/forms.ts b/fe/src/stores/forms.ts
new file mode 100644
index 0000000..daf9181
--- /dev/null
+++ b/fe/src/stores/forms.ts
@@ -0,0 +1,6 @@
1import type { Writable } from "svelte/store";
2import { writable } from "svelte/store";
3
4
5export const preferencesFormOpen: Writable<boolean> = writable<boolean>(false);
6export const addFormOpen: Writable<boolean> = writable<boolean>(false); \ No newline at end of file
diff --git a/fe/src/types.ts b/fe/src/types.ts
new file mode 100644
index 0000000..c8f2f00
--- /dev/null
+++ b/fe/src/types.ts
@@ -0,0 +1,53 @@
1import type { Invalidator, Subscriber, Unsubscriber, Updater } from "svelte/store";
2
3export interface Preference {
4 id: number;
5 color: string;
6 size_id: number;
7 user_id: number;
8}
9
10export interface Size {
11 size: number;
12 unit: string;
13}
14
15export interface User {
16 id: number;
17 name: string;
18 uuid: string;
19}
20
21export interface Statistic {
22 user_id: string;
23 date: string;
24 quantity: number;
25}
26
27export type Nullable<T> = T | null;
28
29export interface User {
30 uuid: string;
31 username: string;
32}
33
34export interface TokenStore {
35 subscribe: (run: Subscriber<Nullable<string>>, invalidate?: Invalidator<Nullable<string>>) => Unsubscriber,
36 authenticate: (newToken: string) => void,
37 unauthenticate: () => void
38}
39
40
41export interface UserStore {
42 subscribe: (run: Subscriber<Nullable<User>>, invalidate?: Invalidator<Nullable<User>>) => Unsubscriber,
43 setUser: (user: User) => void,
44 reset: () => void
45}
46
47export interface PreferenceStore {
48 set: (this: void, value: Preference) => void;
49 subscribe: (this: void, run: Subscriber<Nullable<Preference>>, invalidate?: Invalidator<Nullable<Preference>>) => Unsubscriber;
50 reset: () => void;
51 update: (this: void, updater: Updater<Nullable<Preference>>) => void;
52 setPreference: (user: Preference) => void;
53}
diff --git a/fe/src/utils.ts b/fe/src/utils.ts
new file mode 100644
index 0000000..9fddf41
--- /dev/null
+++ b/fe/src/utils.ts
@@ -0,0 +1,14 @@
1export function processFormInput(form: HTMLFormElement) {
2 const formData: FormData = new FormData(form);
3 const data: Record<string, any> = {};
4 for (let field of formData) {
5 const [key, value] = field;
6 data[key] = value;
7 }
8 return data;
9}
10
11export function apiURL (path: string): string {
12 const baseUrl = import.meta.env?.VITE_API_BASE_URL ?? "http://localhost:8080/api/v1";
13 return `${baseUrl}${path}`
14}
diff --git a/fe/src/vite-env.d.ts b/fe/src/vite-env.d.ts
new file mode 100644
index 0000000..4078e74
--- /dev/null
+++ b/fe/src/vite-env.d.ts
@@ -0,0 +1,2 @@
1/// <reference types="svelte" />
2/// <reference types="vite/client" />
diff --git a/fe/svelte.config.js b/fe/svelte.config.js
new file mode 100644
index 0000000..b29bf40
--- /dev/null
+++ b/fe/svelte.config.js
@@ -0,0 +1,8 @@
1import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
2
3export default {
4 // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
5 // for more information about preprocessors
6 preprocess: vitePreprocess(),
7 dev: true
8}
diff --git a/fe/tsconfig.json b/fe/tsconfig.json
new file mode 100644
index 0000000..5fb548f
--- /dev/null
+++ b/fe/tsconfig.json
@@ -0,0 +1,20 @@
1{
2 "extends": "@tsconfig/svelte/tsconfig.json",
3 "compilerOptions": {
4 "target": "ESNext",
5 "useDefineForClassFields": true,
6 "module": "ESNext",
7 "resolveJsonModule": true,
8 /**
9 * Typecheck JS in `.svelte` and `.js` files by default.
10 * Disable checkJs if you'd like to use dynamic types in JS.
11 * Note that setting allowJs false does not prevent the use
12 * of JS in `.svelte` files.
13 */
14 "allowJs": true,
15 "checkJs": true,
16 "isolatedModules": true
17 },
18 "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
19 "references": [{ "path": "./tsconfig.node.json" }]
20}
diff --git a/fe/tsconfig.node.json b/fe/tsconfig.node.json
new file mode 100644
index 0000000..d02c37d
--- /dev/null
+++ b/fe/tsconfig.node.json
@@ -0,0 +1,10 @@
1{
2 "compilerOptions": {
3 "composite": true,
4 "skipLibCheck": true,
5 "module": "ESNext",
6 "moduleResolution": "bundler",
7 "strict": true
8 },
9 "include": ["vite.config.ts"]
10}
diff --git a/fe/vite.config.ts b/fe/vite.config.ts
new file mode 100644
index 0000000..d701969
--- /dev/null
+++ b/fe/vite.config.ts
@@ -0,0 +1,7 @@
1import { defineConfig } from 'vite'
2import { svelte } from '@sveltejs/vite-plugin-svelte'
3
4// https://vitejs.dev/config/
5export default defineConfig({
6 plugins: [svelte()],
7})