Racepicks: Stream loaded data and resolve promises in markup

This commit is contained in:
2025-02-05 20:38:28 +01:00
parent 907e4fefb1
commit 735c73e435
3 changed files with 277 additions and 246 deletions

View File

@ -1,6 +1,14 @@
<script lang="ts">
import { Card, Button, Dropdown } from "$lib/components";
import type { Driver, Race, RacePick, SkeletonData, Substitution } from "$lib/schema";
import type {
CurrentPickedUser,
Driver,
Race,
RacePick,
RaceResult,
SkeletonData,
Substitution,
} from "$lib/schema";
import { get_by_value, get_driver_headshot_template } from "$lib/database";
import type { Action } from "svelte/action";
import { getModalStore, type ModalStore } from "@skeletonlabs/skeleton";
@ -10,7 +18,12 @@
interface RacePickCardProps {
/** Data passed from the page context */
data: SkeletonData & { currentrace: Race };
data: SkeletonData & {
currentrace: Race;
racepicks: Promise<RacePick[]>;
currentpickedusers: Promise<CurrentPickedUser[]>;
raceresults: Promise<RaceResult[]>;
};
/** The [RacePick] object used to prefill values. */
racepick?: RacePick;

View File

@ -2,65 +2,6 @@ import { form_data_clean, form_data_ensure_keys, form_data_get_and_remove_id } f
import type { CurrentPickedUser, Race, RacePick, RaceResult } from "$lib/schema";
import type { Actions, PageServerLoad } from "./$types";
export const load: PageServerLoad = async ({ fetch, locals }) => {
const fetch_racepicks = async (): Promise<RacePick[]> => {
// Don't expand race/pxx/dnf since we already fetched those
const racepicks: RacePick[] = await locals.pb
.collection("racepicks")
.getFullList({ fetch: fetch, expand: "user" });
// TODO: Fill in the expanded race pictogram_url fields
return racepicks;
};
const fetch_currentrace = async (): Promise<Race | null> => {
const currentrace: Race[] = await locals.pb.collection("currentrace").getFullList();
// The currentrace collection either has a single or no entries
if (currentrace.length == 0) return null;
currentrace[0].pictogram_url = await locals.pb.files.getURL(
currentrace[0],
currentrace[0].pictogram,
);
return currentrace[0];
};
const fetch_currentpickedusers = async (): Promise<CurrentPickedUser[]> => {
const currentpickedusers: CurrentPickedUser[] = await locals.pb
.collection("currentpickedusers")
.getFullList();
currentpickedusers.map((currentpickeduser: CurrentPickedUser) => {
if (currentpickeduser.avatar) {
currentpickeduser.avatar_url = locals.pb.files.getURL(
currentpickeduser,
currentpickeduser.avatar,
);
}
});
return currentpickedusers;
};
// TODO: Duplicated code from data/raceresults/+page.server.ts
const fetch_raceresults = async (): Promise<RaceResult[]> => {
// Don't expand races/pxxs/dnfs since we already fetched those
const raceresults: RaceResult[] = await locals.pb.collection("raceresultsdesc").getFullList();
return raceresults;
};
return {
racepicks: await fetch_racepicks(),
currentrace: await fetch_currentrace(),
currentpickedusers: await fetch_currentpickedusers(),
raceresults: await fetch_raceresults(),
};
};
export const actions = {
create_racepick: async ({ request, locals }) => {
const data: FormData = form_data_clean(await request.formData());
@ -91,3 +32,61 @@ export const actions = {
await locals.pb.collection("racepicks").delete(id);
},
} satisfies Actions;
export const load: PageServerLoad = async ({ fetch, locals }) => {
const fetch_currentrace = async (): Promise<Race | null> => {
const currentrace: Race[] = await locals.pb.collection("currentrace").getFullList();
// The currentrace collection either has a single or no entries
if (currentrace.length == 0) return null;
currentrace[0].pictogram_url = await locals.pb.files.getURL(
currentrace[0],
currentrace[0].pictogram,
);
return currentrace[0];
};
const fetch_racepicks = async (): Promise<RacePick[]> => {
// Don't expand race/pxx/dnf since we already fetched those
const racepicks: RacePick[] = await locals.pb
.collection("racepicks")
.getFullList({ fetch: fetch, expand: "user" });
return racepicks;
};
const fetch_currentpickedusers = async (): Promise<CurrentPickedUser[]> => {
const currentpickedusers: CurrentPickedUser[] = await locals.pb
.collection("currentpickedusers")
.getFullList();
currentpickedusers.map((currentpickeduser: CurrentPickedUser) => {
if (currentpickeduser.avatar) {
currentpickeduser.avatar_url = locals.pb.files.getURL(
currentpickeduser,
currentpickeduser.avatar,
);
}
});
return currentpickedusers;
};
// TODO: Duplicated code from data/raceresults/+page.server.ts
const fetch_raceresults = async (): Promise<RaceResult[]> => {
// Don't expand races/pxxs/dnfs since we already fetched those
const raceresults: RaceResult[] = await locals.pb.collection("raceresultsdesc").getFullList();
return raceresults;
};
return {
currentrace: await fetch_currentrace(),
racepicks: fetch_racepicks(),
currentpickedusers: fetch_currentpickedusers(),
raceresults: fetch_raceresults(),
};
};

View File

@ -25,8 +25,9 @@
let { data }: { data: PageData } = $props();
const racepick: RacePick | undefined = $derived(
data.racepicks.filter(
const racepick: Promise<RacePick | undefined> = $derived.by(
async () =>
(await data.racepicks).filter(
(racepick: RacePick) =>
racepick.expand.user.username === data.user?.username &&
racepick.race === data.currentrace?.id,
@ -40,7 +41,7 @@
component: "racePickCard",
meta: {
data,
racepick,
racepick: await racepick,
},
};
@ -48,15 +49,15 @@
};
// Users that have already picked the current race
let pickedusers: CurrentPickedUser[] = $derived(
data.currentpickedusers.filter(
let pickedusers: Promise<CurrentPickedUser[]> = $derived.by(async () =>
(await data.currentpickedusers).filter(
(currentpickeduser: CurrentPickedUser) => currentpickeduser.picked,
),
);
// Users that didn't already pick the current race
let outstandingusers: CurrentPickedUser[] = $derived(
data.currentpickedusers.filter(
let outstandingusers: Promise<CurrentPickedUser[]> = $derived.by(async () =>
(await data.currentpickedusers).filter(
(currentpickeduser: CurrentPickedUser) => !currentpickeduser.picked,
),
);
@ -127,13 +128,14 @@
<!-- Only show the userguess if signed in -->
{#if data.user}
{#await data.graphics then graphics}
{#await data.drivers then drivers}
{#await racepick then pick}
<div class="mt-2 flex gap-2">
<div class="card w-full min-w-40 p-2 pb-0 shadow">
<h1 class="mb-2 text-nowrap font-bold">Your P{data.currentrace.pxx} Pick:</h1>
{#await data.graphics then graphics}
{#await data.drivers then drivers}
<LazyImage
src={get_by_value(drivers, "id", racepick?.pxx ?? "")?.headshot_url ??
src={get_by_value(drivers, "id", pick?.pxx ?? "")?.headshot_url ??
get_driver_headshot_template(graphics)}
imgwidth={DRIVER_HEADSHOT_WIDTH}
imgheight={DRIVER_HEADSHOT_HEIGHT}
@ -142,15 +144,11 @@
hoverzoom
onclick={racepick_handler}
/>
{/await}
{/await}
</div>
<div class="card w-full min-w-40 p-2 pb-0 shadow">
<h1 class="mb-2 text-nowrap font-bold">Your DNF Pick:</h1>
{#await data.graphics then graphics}
{#await data.drivers then drivers}
<LazyImage
src={get_by_value(drivers, "id", racepick?.dnf ?? "")?.headshot_url ??
src={get_by_value(drivers, "id", pick?.dnf ?? "")?.headshot_url ??
get_driver_headshot_template(graphics)}
imgwidth={DRIVER_HEADSHOT_WIDTH}
imgheight={DRIVER_HEADSHOT_HEIGHT}
@ -159,21 +157,24 @@
hoverzoom
onclick={racepick_handler}
/>
{/await}
{/await}
</div>
</div>
{/await}
{/await}
{/await}
{/if}
<!-- Show users that have and have not picked yet -->
{#await data.currentpickedusers then currentpicked}
<div class="mt-2 flex gap-2">
<div class="card w-full min-w-40 p-2 shadow">
{#await pickedusers then picked}
<div class="card w-full min-w-40 p-2 shadow lg:max-w-40">
<h1 class="text-nowrap font-bold">
Picked ({pickedusers.length}/{data.currentpickedusers.length}):
Picked ({picked.length}/{currentpicked.length}):
</h1>
<div class="mt-1 grid grid-cols-4 gap-x-2 gap-y-0.5">
{#await data.graphics then graphics}
{#each pickedusers.slice(0, 16) as user}
{#each picked.slice(0, 16) as user}
<LazyImage
src={user.avatar_url ?? get_driver_headshot_template(graphics)}
imgwidth={AVATAR_WIDTH}
@ -185,13 +186,15 @@
{/await}
</div>
</div>
<div class="card w-full min-w-40 p-2 shadow">
{/await}
{#await outstandingusers then outstanding}
<div class="card w-full min-w-40 p-2 shadow lg:max-w-40">
<h1 class="text-nowrap font-bold">
Outstanding ({outstandingusers.length}/{data.currentpickedusers.length}):
Outstanding ({outstanding.length}/{currentpicked.length}):
</h1>
<div class="mt-1 grid grid-cols-4 gap-x-0 gap-y-0.5">
{#await data.graphics then graphics}
{#each outstandingusers.slice(0, 16) as user}
{#each outstanding.slice(0, 16) as user}
<LazyImage
src={user.avatar_url ?? get_driver_headshot_template(graphics)}
imgwidth={AVATAR_WIDTH}
@ -203,7 +206,9 @@
{/await}
</div>
</div>
{/await}
</div>
{/await}
</div>
</svelte:fragment>
</AccordionItem>
@ -255,7 +260,8 @@
</div>
{#await data.races then races}
{#each data.raceresults as result}
{#await data.raceresults then raceresults}
{#each raceresults as result}
{@const race = get_by_value(races, "id", result.race)}
<div
@ -281,7 +287,10 @@
{@const driver = get_by_value(drivers, "id", pxx)}
<div class="flex gap-2">
<span class="w-8">P{(race?.pxx ?? -100) - 3 + index}:</span>
<span class="badge w-10 p-1 text-center" style="background: {PXX_COLORS[index]};">
<span
class="badge w-10 p-1 text-center"
style="background: {PXX_COLORS[index]};"
>
{driver?.code}
</span>
</div>
@ -305,12 +314,19 @@
</div>
{/each}
{/await}
{/await}
</div>
<div class="hide-scrollbar flex w-full overflow-x-scroll pb-2">
<!-- Not ideal but currentpickedusers contains all users, so we do not need to fetch the users separately -->
{#each data.currentpickedusers as user}
{@const picks = data.racepicks.filter((pick: RacePick) => pick.user === user.id)}
<!-- TODO: Uhhh can I write this await stuff differently??? -->
{#await data.currentpickedusers then currentpicked}
{#await data.racepicks then racepicks}
{#await data.races then races}
{#await data.drivers then drivers}
{#await data.raceresults then raceresults}
{#each currentpicked as user}
{@const picks = racepicks.filter((pick: RacePick) => pick.user === user.id)}
<div
class="card ml-1 mt-2 w-full min-w-12 overflow-hidden py-2 shadow lg:ml-2 {data.user &&
@ -338,14 +354,14 @@
</div>
</div>
{#await data.races then races}
{#await data.drivers then drivers}
{#each data.raceresults as result}
{#each raceresults as result}
{@const race = get_by_value(races, "id", result.race)}
{@const pick = picks.filter((pick: RacePick) => pick.race === race?.id)[0]}
{@const pxxcolor = PXX_COLORS[result.pxxs.indexOf(pick?.pxx ?? "Invalid")]}
{@const dnfcolor =
result.dnfs.indexOf(pick?.dnf ?? "Invalid") >= 0 ? PXX_COLORS[3] : PXX_COLORS[-1]}
result.dnfs.indexOf(pick?.dnf ?? "Invalid") >= 0
? PXX_COLORS[3]
: PXX_COLORS[-1]}
{#if pick}
<div class="mt-2 h-20 w-full border bg-surface-300 p-1 lg:p-2">
@ -368,9 +384,12 @@
<div class="mt-2 h-20 w-full"></div>
{/if}
{/each}
{/await}
{/await}
</div>
{/each}
{/await}
{/await}
{/await}
{/await}
{/await}
</div>
</div>