From 0abfaff004edc38c9fe15885c871393ecd687e3d Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Fri, 13 Dec 2024 19:56:47 +0100 Subject: [PATCH] App: Add TS type information --- src/app.d.ts | 21 +- src/hooks.server.ts | 25 +- src/lib/components/Button.svelte | 67 +++-- src/lib/components/Card.svelte | 44 ++- src/lib/components/Input.svelte | 20 +- src/lib/database.ts | 7 + src/lib/form.ts | 4 +- src/lib/image.ts | 16 +- src/lib/schema.ts | 21 ++ src/routes/+layout.server.ts | 2 +- src/routes/+layout.svelte | 35 ++- src/routes/data/season/+page.server.ts | 113 ++++++++ src/routes/data/season/+page.svelte | 262 ++++++++++++++++++ src/routes/data/seasondata/+layout.svelte | 32 --- .../data/seasondata/drivers/+page.svelte | 1 - src/routes/data/seasondata/races/+page.svelte | 0 .../data/seasondata/teams/+page.server.ts | 62 ----- src/routes/data/seasondata/teams/+page.svelte | 100 ------- .../data/{userdata => user}/+page.svelte | 0 src/routes/profile/+page.server.ts | 24 +- 20 files changed, 576 insertions(+), 280 deletions(-) create mode 100644 src/lib/database.ts create mode 100644 src/lib/schema.ts create mode 100644 src/routes/data/season/+page.server.ts create mode 100644 src/routes/data/season/+page.svelte delete mode 100644 src/routes/data/seasondata/+layout.svelte delete mode 100644 src/routes/data/seasondata/drivers/+page.svelte delete mode 100644 src/routes/data/seasondata/races/+page.svelte delete mode 100644 src/routes/data/seasondata/teams/+page.server.ts delete mode 100644 src/routes/data/seasondata/teams/+page.svelte rename src/routes/data/{userdata => user}/+page.svelte (100%) diff --git a/src/app.d.ts b/src/app.d.ts index 31f50b4..7815ccb 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,13 +1,20 @@ +import type { PocketBase, RecordModel } from "pocketbase"; + // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} + namespace App { + interface Locals { + pb: PocketBase; + user: RecordModel | undefined; + admin: boolean; } + + // interface Error {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } } -export { }; +export {}; diff --git a/src/hooks.server.ts b/src/hooks.server.ts index f171258..839e0c2 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -10,22 +10,22 @@ export const handle: Handle = async ({ event, resolve }) => { event.locals.pb = new PocketBase("http://192.168.86.50:8090"); // Load the most recent authentication data from a cookie (is updated below) - event.locals.pb.authStore.loadFromCookie( - event.request.headers.get("cookie") || "", - ); + event.locals.pb.authStore.loadFromCookie(event.request.headers.get("cookie") || ""); if (event.locals.pb.authStore.isValid) { // If the authentication data is valid, we make a "user" object easily available. event.locals.user = structuredClone(event.locals.pb.authStore.model); - // Fill in the avatar URL - event.locals.user.avatar_url = event.locals.pb.files.getURL( - event.locals.pb.authStore.model, - event.locals.pb.authStore.model.avatar, - ); + if (event.locals.user) { + // Fill in the avatar URL + event.locals.user.avatar_url = event.locals.pb.files.getURL( + event.locals.pb.authStore.model, + event.locals.pb.authStore.model.avatar, + ); - // Set admin status for easier access - event.locals.admin = event.locals.user.admin; + // Set admin status for easier access + event.locals.admin = event.locals.user.admin; + } } else { event.locals.user = undefined; } @@ -34,10 +34,7 @@ export const handle: Handle = async ({ event, resolve }) => { const response = await resolve(event); // Store the current authentication data to a cookie, so it can be loaded above. - response.headers.set( - "set-cookie", - event.locals.pb.authStore.exportToCookie({ secure: false }), - ); + response.headers.set("set-cookie", event.locals.pb.authStore.exportToCookie({ secure: false })); return response; }; diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte index 925041d..b0c322b 100644 --- a/src/lib/components/Button.svelte +++ b/src/lib/components/Button.svelte @@ -1,5 +1,8 @@ {#if href} - - {@render children()} - + +
+ +
{:else} - {@render children()} - {/if} diff --git a/src/lib/components/Card.svelte b/src/lib/components/Card.svelte index a0a9af5..ed60173 100644 --- a/src/lib/components/Card.svelte +++ b/src/lib/components/Card.svelte @@ -1,7 +1,45 @@ -
- {@render children()} +
+ + {#if imgsrc !== undefined} + + {/if} +
+ {@render children()} +
diff --git a/src/lib/components/Input.svelte b/src/lib/components/Input.svelte index fca7b5f..90897cb 100644 --- a/src/lib/components/Input.svelte +++ b/src/lib/components/Input.svelte @@ -1,9 +1,25 @@
-
+
{@render children()}
diff --git a/src/lib/database.ts b/src/lib/database.ts new file mode 100644 index 0000000..49d3ebd --- /dev/null +++ b/src/lib/database.ts @@ -0,0 +1,7 @@ +/** + * Retrieve an arbitrary object with a matching ID from an Array. + * Supposed to use on collections returned by the PocketBase API. + */ +export const get_by_id = (objects: Array, id: string): T | undefined => { + return objects.find((o: T) => ("id" in o ? o.id === id : false)); +}; diff --git a/src/lib/form.ts b/src/lib/form.ts index 55cf1a3..93c35b7 100644 --- a/src/lib/form.ts +++ b/src/lib/form.ts @@ -36,13 +36,13 @@ export const form_data_clean = (data: FormData): FormData => { /** * Throws SvelteKit error(400) if form_data does not contain key. */ -export const form_data_ensure_key = (data: FormData, key: string) => { +export const form_data_ensure_key = (data: FormData, key: string): void => { if (!data.get(key)) error(400, `Key "${key}" missing from form_data!`); }; /** * Throws SvelteKit error(400) if form_data does not contain all keys. */ -export const form_data_ensure_keys = (data: FormData, keys: string[]) => { +export const form_data_ensure_keys = (data: FormData, keys: string[]): void => { keys.map((key) => form_data_ensure_key(data, key)); }; diff --git a/src/lib/image.ts b/src/lib/image.ts index c39926f..68447e2 100644 --- a/src/lib/image.ts +++ b/src/lib/image.ts @@ -1,8 +1,9 @@ /** - * Use this on components. + * Obtain an onchange event handler that updates an component + * with a new image uploaded via a file input element. */ -export const get_avatar_preview_event_handler = (id: string) => { - const handler = (event: Event) => { +export const get_avatar_preview_event_handler = (id: string): ((event: Event) => void) => { + const handler = (event: Event): void => { const target: HTMLInputElement = event.target as HTMLInputElement; const files: FileList | null = target.files; @@ -11,6 +12,7 @@ export const get_avatar_preview_event_handler = (id: string) => { const preview: HTMLImageElement = document.querySelector( `#${id} > img:first-of-type`, ) as HTMLImageElement; + if (preview) { preview.src = src; preview.hidden = false; @@ -22,16 +24,18 @@ export const get_avatar_preview_event_handler = (id: string) => { }; /** - * Use this on raw elements. + * Obtain an onchange event handler that updates an element + * with a new image uploaded via a file input element. */ -export const get_image_preview_event_handler = (id: string) => { - const handler = (event: Event) => { +export const get_image_preview_event_handler = (id: string): ((event: Event) => void) => { + const handler = (event: Event): void => { const target: HTMLInputElement = event.target as HTMLInputElement; const files: FileList | null = target.files; if (files && files.length > 0) { const src: string = URL.createObjectURL(files[0]); const preview: HTMLImageElement = document.getElementById(id) as HTMLImageElement; + if (preview) { preview.src = src; preview.hidden = false; diff --git a/src/lib/schema.ts b/src/lib/schema.ts new file mode 100644 index 0000000..de8d561 --- /dev/null +++ b/src/lib/schema.ts @@ -0,0 +1,21 @@ +export interface Team { + id: string; + name: string; + logo: string; + logo_url?: string; +} + +export interface Driver { + id: string; + firstname: string; + lastname: string; + code: string; + headshot: string; + headshot_url?: string; + team: string; + active: boolean; +} + +export interface Race {} + +export interface Substitution {} diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index d50f476..d2f4377 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -2,7 +2,7 @@ import type { LayoutServerLoad } from "./$types"; // On each page load (every route), this function runs serverside. // The "locals.user" object is only available on the server, -// since it's populated inside hooks.server.ts. +// since it's populated inside hooks.server.ts per request. // It will populate the "user" attribute of each page's "data" object, // so each page has access to the current user (or knows if no one is signed in). export const load: LayoutServerLoad = ({ locals }) => { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index a3cf8ed..40deee4 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -19,6 +19,7 @@ type DrawerSettings, Avatar, FileDropzone, + type DrawerStore, } from "@skeletonlabs/skeleton"; import { computePosition, autoUpdate, offset, shift, flip, arrow } from "@floating-ui/dom"; @@ -26,7 +27,7 @@ // Drawer config initializeStores(); - const drawerStore = getDrawerStore(); + const drawerStore: DrawerStore = getDrawerStore(); const drawer_settings_base: DrawerSettings = { position: "top", @@ -89,7 +90,7 @@ -
+
@@ -114,9 +115,9 @@ -
+

Enter Username and Password

-
+ @@ -127,19 +128,22 @@ - Register
- {:else if $drawerStore.id === "profile_drawer"} + {:else if $drawerStore.id === "profile_drawer" && data.user} -
+

Edit Profile

-
+ @@ -150,8 +154,11 @@ Upload Avatar or Drag and Drop
- Save Changes -