Compare commits
5 Commits
77895d9057
...
3ca967591e
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ca967591e | |||
| 6e6ce020a3 | |||
| d54ee01227 | |||
| 3339ffaa5f | |||
| 78ee291795 |
1016
package-lock.json
generated
1016
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@carbon/charts-svelte": "^1.22.18",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@fsouza/prettierd": "^0.26.1",
|
||||
"@skeletonlabs/skeleton": "^2.10.4",
|
||||
|
||||
@ -9,6 +9,7 @@ import type {
|
||||
RacePick,
|
||||
RacePickPoints,
|
||||
RacePickPointsAcc,
|
||||
RacePickPointsTotal,
|
||||
RaceResult,
|
||||
ScrapedDriverStanding,
|
||||
ScrapedRaceResult,
|
||||
@ -281,7 +282,7 @@ export const fetch_racepickpoints = async (
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch all [RacePickPointsAcc] from the database, ordered descendingly by total points.
|
||||
* Fetch all [RacePickPointsAcc] from the database, ordered ascendingly by step.
|
||||
*/
|
||||
export const fetch_racepickpointsacc = async (
|
||||
fetch: (_: any) => Promise<Response>,
|
||||
@ -293,6 +294,19 @@ export const fetch_racepickpointsacc = async (
|
||||
return racepickpointsacc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch all [RacePickPointsTotal] from the database, ordered descendingly by total points.
|
||||
*/
|
||||
export const fetch_racepickpointstotal = async (
|
||||
fetch: (_: any) => Promise<Response>,
|
||||
): Promise<RacePickPointsTotal[]> => {
|
||||
const racepickpointstotal: RacePickPointsTotal[] = await pb
|
||||
.collection("racepickpointstotal")
|
||||
.getFullList({ fetch: fetch });
|
||||
|
||||
return racepickpointstotal;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch all [ScrapedDriverStandings] from the database, ordered ascendingly by position.
|
||||
*/
|
||||
|
||||
@ -140,11 +140,21 @@ export interface RacePickPoints {
|
||||
}
|
||||
|
||||
export interface RacePickPointsAcc {
|
||||
id: string;
|
||||
user: string;
|
||||
step: number;
|
||||
acc_pxx_points: number;
|
||||
acc_dnf_points: number;
|
||||
acc_points: number;
|
||||
}
|
||||
|
||||
export interface RacePickPointsTotal {
|
||||
id: string;
|
||||
user: string;
|
||||
total_pxx_points: number;
|
||||
total_dnf_points: number;
|
||||
total_points: number;
|
||||
total_points_per_pick: number;
|
||||
}
|
||||
|
||||
// Scraped Data
|
||||
|
||||
@ -1,10 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { Table, type TableColumn } from "$lib/components";
|
||||
import { get_by_value } from "$lib/database";
|
||||
import type { RacePickPoints, RacePickPointsAcc, User } from "$lib/schema";
|
||||
import type { PageData } from "./$types";
|
||||
import {
|
||||
LineChart,
|
||||
ScaleTypes,
|
||||
type ChartTabularData,
|
||||
type LineChartOptions,
|
||||
} from "@carbon/charts-svelte";
|
||||
import "@carbon/charts-svelte/styles.css";
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
// Await promises
|
||||
let users: User[] | undefined = $state(undefined);
|
||||
data.users.then((u: User[]) => (users = u));
|
||||
|
||||
let racepickpoints: RacePickPoints[] | undefined = $state(undefined);
|
||||
data.racepickpoints.then((r: RacePickPoints[]) => (racepickpoints = r));
|
||||
|
||||
let racepickpointsacc: RacePickPointsAcc[] | undefined = $state(undefined);
|
||||
data.racepickpointsacc.then((r: RacePickPointsAcc[]) => (racepickpointsacc = r));
|
||||
|
||||
const leaderboard_columns: TableColumn[] = $derived([
|
||||
{
|
||||
data_value_name: "user",
|
||||
@ -14,15 +32,124 @@
|
||||
},
|
||||
{
|
||||
data_value_name: "total_points",
|
||||
label: "Points",
|
||||
label: "Total",
|
||||
},
|
||||
{
|
||||
data_value_name: "total_pxx_points",
|
||||
label: "PXX",
|
||||
},
|
||||
{
|
||||
data_value_name: "total_dnf_points",
|
||||
label: "DNF",
|
||||
},
|
||||
{
|
||||
data_value_name: "total_points_per_pick",
|
||||
label: "Per Pick",
|
||||
valuefun: async (value: string): Promise<string> => Number.parseFloat(value).toFixed(2),
|
||||
},
|
||||
]);
|
||||
|
||||
const points_chart_data: ChartTabularData = $derived.by(() => {
|
||||
if (!users || !racepickpointsacc) return [];
|
||||
|
||||
return users
|
||||
.map((user: User) => {
|
||||
return {
|
||||
group: user.firstname,
|
||||
step: "0",
|
||||
points: 0,
|
||||
};
|
||||
})
|
||||
.concat(
|
||||
racepickpointsacc.map((points: RacePickPointsAcc) => {
|
||||
return {
|
||||
group: get_by_value(users ?? [], "id", points.user)?.firstname || "INVALID",
|
||||
step: points.step.toString(),
|
||||
points: points.acc_points,
|
||||
};
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
const points_chart_options: LineChartOptions = {
|
||||
title: "I ❤️ CumSum",
|
||||
axes: {
|
||||
bottom: {
|
||||
mapsTo: "step",
|
||||
scaleType: ScaleTypes.LABELS,
|
||||
},
|
||||
left: {
|
||||
mapsTo: "points",
|
||||
scaleType: ScaleTypes.LINEAR,
|
||||
},
|
||||
},
|
||||
curve: "curveMonotoneX",
|
||||
// toolbar: {
|
||||
// enabled: false,
|
||||
// },
|
||||
animations: true,
|
||||
// canvasZoom: {
|
||||
// enabled: false,
|
||||
// },
|
||||
grid: {
|
||||
x: {
|
||||
enabled: true,
|
||||
alignWithAxisTicks: true,
|
||||
},
|
||||
y: {
|
||||
enabled: true,
|
||||
alignWithAxisTicks: true,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
enabled: true,
|
||||
clickable: true,
|
||||
position: "top",
|
||||
},
|
||||
points: {
|
||||
enabled: true,
|
||||
radius: 5,
|
||||
},
|
||||
tooltip: {
|
||||
showTotal: false,
|
||||
},
|
||||
resizable: true,
|
||||
width: "100%",
|
||||
height: "400px",
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Formula 11 - Leaderboard</title>
|
||||
</svelte:head>
|
||||
|
||||
{#await Promise.all( [data.users, data.racepickpoints, data.racepickpointsacc], ) then [users, racepickpoints, racepickpointsacc]}
|
||||
<Table data={racepickpointsacc} columns={leaderboard_columns} />
|
||||
{#await Promise.all( [data.users, data.racepickpoints, data.racepickpointsacc, data.racepickpointstotal], ) then [users, racepickpoints, racepickpointsacc, racepickpointstotal]}
|
||||
<div class="flex gap-2">
|
||||
<!-- Podium -->
|
||||
<!-- <div class="card w-60 bg-surface-100 p-2 shadow"> -->
|
||||
<!-- <div class="flex h-20 w-full gap-1"></div> -->
|
||||
<!-- <div class="flex h-20 w-full gap-1"> -->
|
||||
<!-- <div class="w-20"> -->
|
||||
<!-- <div class="h-[30%] w-full"></div> -->
|
||||
<!-- <div class="h-[70%] w-full bg-surface-500"></div> -->
|
||||
<!-- </div> -->
|
||||
<!-- <div class="w-20"> -->
|
||||
<!-- <div class="h-[100%] w-full bg-surface-500"></div> -->
|
||||
<!-- </div> -->
|
||||
<!-- <div class="w-20"> -->
|
||||
<!-- <div class="h-[60%] w-full"></div> -->
|
||||
<!-- <div class="h-[40%] w-full bg-surface-600"></div> -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
|
||||
<!-- Points chart -->
|
||||
<div class="card w-full bg-surface-100 p-2 shadow">
|
||||
<LineChart data={points_chart_data} options={points_chart_options} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<Table data={racepickpointstotal} columns={leaderboard_columns} />
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { fetch_users, fetch_racepickpoints, fetch_racepickpointsacc } from "$lib/fetch";
|
||||
import {
|
||||
fetch_users,
|
||||
fetch_racepickpoints,
|
||||
fetch_racepickpointsacc,
|
||||
fetch_racepickpointstotal,
|
||||
} from "$lib/fetch";
|
||||
import type { PageLoad } from "../$types";
|
||||
|
||||
export const load: PageLoad = async ({ fetch, depends }) => {
|
||||
@ -8,5 +13,6 @@ export const load: PageLoad = async ({ fetch, depends }) => {
|
||||
users: fetch_users(fetch),
|
||||
racepickpoints: fetch_racepickpoints(fetch),
|
||||
racepickpointsacc: fetch_racepickpointsacc(fetch),
|
||||
racepickpointstotal: fetch_racepickpointstotal(fetch),
|
||||
};
|
||||
};
|
||||
|
||||
@ -256,8 +256,8 @@
|
||||
{#await data.currentpickedusers then currentpicked}
|
||||
{#each currentpicked as user}
|
||||
<div
|
||||
class="card ml-1 mt-2 w-full min-w-14 rounded-b-none py-2
|
||||
{$pbUser && $pbUser.username === user.username ? 'bg-primary-300' : ''}"
|
||||
class="card ml-1 mt-2 w-full min-w-14 rounded-b-none bg-surface-400 py-2
|
||||
{$pbUser && $pbUser.username === user.username ? '!bg-primary-400' : ''}"
|
||||
>
|
||||
<!-- Avatar + name display at the top -->
|
||||
<div class="m-auto flex h-10 w-fit">
|
||||
@ -293,7 +293,7 @@
|
||||
|
||||
<div
|
||||
use:popup={race_popupsettings(race?.id ?? "Invalid")}
|
||||
class="card mt-1 flex h-20 w-7 cursor-pointer flex-col !rounded-r-none lg:w-36 lg:p-2"
|
||||
class="card mt-1 flex h-16 w-7 cursor-pointer flex-col !rounded-r-none bg-surface-400 lg:h-20 lg:w-36 lg:p-2"
|
||||
>
|
||||
<!-- For large screens -->
|
||||
<span class="hidden text-sm font-bold lg:block">
|
||||
@ -306,13 +306,13 @@
|
||||
|
||||
<!-- For small screens -->
|
||||
<!-- TODO: This requires the race name to end with an emoji, but this is never enforced -->
|
||||
<div class="mx-[2px] my-[25px] block text-lg font-bold lg:hidden">
|
||||
<div class="mx-[2px] my-[18px] block text-lg font-bold lg:hidden">
|
||||
{runes(race?.name ?? "Invalid").at(-1)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- The race result popup is triggered on click on the race -->
|
||||
<div data-popup={race?.id ?? "Invalid"} class="card z-50 p-2 shadow">
|
||||
<div data-popup={race?.id ?? "Invalid"} class="card z-50 bg-surface-400 p-2 shadow">
|
||||
<span class="font-bold">Result:</span>
|
||||
<div class="mt-2 flex flex-col gap-1">
|
||||
{#each result.pxxs as pxx, index}
|
||||
@ -360,7 +360,7 @@
|
||||
result.dnfs.indexOf(pick?.dnf ?? "Invalid") >= 0 ? PXX_COLORS[3] : PXX_COLORS[-1]}
|
||||
|
||||
{#if pick}
|
||||
<div class="mt-1 h-20 w-full border bg-surface-300 px-1 py-2 lg:px-2">
|
||||
<div class="mt-1 h-16 w-full border bg-surface-200 px-1 py-2 lg:h-20 lg:px-2">
|
||||
<div class="mx-auto flex h-full w-fit flex-col justify-evenly">
|
||||
<span
|
||||
class="w-10 p-1 text-center text-xs rounded-container-token lg:text-sm"
|
||||
@ -377,7 +377,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="mt-1 h-20 w-full px-1 py-2 lg:px-2"></div>
|
||||
<div class="mt-1 h-16 w-full px-1 py-2 lg:h-20 lg:px-2"></div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@ -264,8 +264,8 @@
|
||||
{#await data.seasonpickedusers then seasonpicked}
|
||||
{#each seasonpicked as user}
|
||||
<div
|
||||
class="card ml-1 mt-2 w-full min-w-36 rounded-b-none py-2
|
||||
{$pbUser && $pbUser.username === user.username ? 'bg-primary-300' : ''}"
|
||||
class="card ml-1 mt-2 w-full min-w-36 rounded-b-none bg-surface-400 py-2
|
||||
{$pbUser && $pbUser.username === user.username ? '!bg-primary-400' : ''}"
|
||||
>
|
||||
<!-- Avatar + name display at the top -->
|
||||
<div class="m-auto flex h-10 w-fit">
|
||||
@ -296,7 +296,7 @@
|
||||
class="sticky left-0 z-10 w-7 min-w-7 max-w-7 bg-surface-50 lg:w-36 lg:min-w-36 lg:max-w-36"
|
||||
>
|
||||
<!-- Hottake -->
|
||||
<div class="card mt-1 flex h-32 w-7 flex-col !rounded-r-none p-2 lg:w-36">
|
||||
<div class="card mt-1 flex h-32 w-7 flex-col !rounded-r-none bg-surface-400 p-2 lg:w-36">
|
||||
<span class="hidden text-nowrap text-sm font-bold lg:block">Hottake</span>
|
||||
<span class="block rotate-90 text-nowrap text-xs font-bold lg:hidden">Hottake</span>
|
||||
</div>
|
||||
@ -304,13 +304,13 @@
|
||||
{#await data.seasonpicks then seasonpicks}
|
||||
{#if seasonpicks.length > 0}
|
||||
<!-- Drivers Champion -->
|
||||
<div class="card mt-1 flex h-20 w-7 flex-col !rounded-r-none p-2 lg:w-36">
|
||||
<div class="card mt-1 flex h-20 w-7 flex-col !rounded-r-none bg-surface-400 p-2 lg:w-36">
|
||||
<span class="hidden text-nowrap text-sm font-bold lg:block">Drivers<br />Champion</span>
|
||||
<span class="block rotate-90 text-nowrap text-xs font-bold lg:hidden">WDC</span>
|
||||
</div>
|
||||
|
||||
<!-- Constructors Champion -->
|
||||
<div class="card mt-1 flex h-20 w-7 flex-col !rounded-r-none p-2 lg:w-36">
|
||||
<div class="card mt-1 flex h-20 w-7 flex-col !rounded-r-none bg-surface-400 p-2 lg:w-36">
|
||||
<span class="hidden text-nowrap text-sm font-bold lg:block">
|
||||
Constructors<br />Champion
|
||||
</span>
|
||||
@ -318,25 +318,25 @@
|
||||
</div>
|
||||
|
||||
<!-- Overtakes -->
|
||||
<div class="card mt-1 flex h-20 w-7 flex-col !rounded-r-none p-2 lg:w-36">
|
||||
<div class="card mt-1 flex h-20 w-7 flex-col !rounded-r-none bg-surface-400 p-2 lg:w-36">
|
||||
<span class="hidden text-nowrap text-sm font-bold lg:block">Most Overtakes</span>
|
||||
<span class="block rotate-90 text-nowrap text-xs font-bold lg:hidden">Overtakes</span>
|
||||
</div>
|
||||
|
||||
<!-- DNFs -->
|
||||
<div class="card mt-1 flex h-20 w-7 flex-col !rounded-r-none p-2 lg:w-36">
|
||||
<div class="card mt-1 flex h-20 w-7 flex-col !rounded-r-none bg-surface-400 p-2 lg:w-36">
|
||||
<span class="hidden text-nowrap text-sm font-bold lg:block">Most DNFs</span>
|
||||
<span class="block rotate-90 text-nowrap text-xs font-bold lg:hidden">DNFs</span>
|
||||
</div>
|
||||
|
||||
<!-- Doohan Starts -->
|
||||
<div class="card mt-1 flex h-20 w-7 flex-col !rounded-r-none p-2 lg:w-36">
|
||||
<div class="card mt-1 flex h-20 w-7 flex-col !rounded-r-none bg-surface-400 p-2 lg:w-36">
|
||||
<span class="hidden text-nowrap text-sm font-bold lg:block">Doohan Starts</span>
|
||||
<span class="block rotate-90 text-nowrap text-xs font-bold lg:hidden">Doohan</span>
|
||||
</div>
|
||||
|
||||
<!-- Teamwinners -->
|
||||
<div class="card mt-1 flex h-64 w-7 flex-col !rounded-r-none p-2 lg:w-36">
|
||||
<div class="card mt-1 flex h-64 w-7 flex-col !rounded-r-none bg-surface-400 p-2 lg:w-36">
|
||||
<span class="hidden text-nowrap text-sm font-bold lg:block"
|
||||
>Team-Battle<br />Winners</span
|
||||
>
|
||||
@ -344,7 +344,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Podiums -->
|
||||
<div class="card mt-1 flex h-64 w-7 flex-col !rounded-r-none p-2 lg:w-36">
|
||||
<div class="card mt-1 flex h-64 w-7 flex-col !rounded-r-none bg-surface-400 p-2 lg:w-36">
|
||||
<span class="hidden text-nowrap text-sm font-bold lg:block">Podiums</span>
|
||||
<span class="block rotate-90 text-nowrap text-xs font-bold lg:hidden">Podiums</span>
|
||||
</div>
|
||||
@ -374,7 +374,7 @@
|
||||
<div class="ml-1 w-full min-w-36">
|
||||
<!-- Hottake -->
|
||||
<div
|
||||
class="mt-1 h-32 w-full overflow-y-scroll border bg-surface-300 px-1 py-2 leading-3 lg:px-2"
|
||||
class="mt-1 h-32 w-full overflow-y-scroll border bg-surface-200 px-1 py-2 leading-3 lg:px-2"
|
||||
>
|
||||
<div class="mx-auto w-fit text-xs font-bold lg:text-sm">
|
||||
{hottake?.hottake ?? "?"}
|
||||
@ -383,7 +383,7 @@
|
||||
|
||||
{#if seasonpicks.length > 0}
|
||||
<!-- Drivers Champion -->
|
||||
<div class="mt-1 h-20 w-full border bg-surface-300 px-1 py-2 leading-3 lg:px-2">
|
||||
<div class="mt-1 h-20 w-full border bg-surface-200 px-1 py-2 leading-3 lg:px-2">
|
||||
<div class="mx-auto w-fit">
|
||||
<!-- NOTE: The containerstyle should be 64x64, don't know why that doesn't fit... (also below) -->
|
||||
<LazyImage
|
||||
@ -398,7 +398,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Constructors Champion -->
|
||||
<div class="mt-1 h-20 w-full border bg-surface-300 p-1 px-1 py-2 leading-3 lg:px-2">
|
||||
<div class="mt-1 h-20 w-full border bg-surface-200 p-1 px-1 py-2 leading-3 lg:px-2">
|
||||
<div class="mx-auto w-fit">
|
||||
<LazyImage
|
||||
src={wccwinner?.banner_url ?? get_team_banner_template(data.graphics)}
|
||||
@ -412,7 +412,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Most Overtakes -->
|
||||
<div class="mt-1 h-20 w-full border bg-surface-300 px-1 py-2 leading-3 lg:px-2">
|
||||
<div class="mt-1 h-20 w-full border bg-surface-200 px-1 py-2 leading-3 lg:px-2">
|
||||
<div class="mx-auto w-fit">
|
||||
<LazyImage
|
||||
src={mostovertakes?.headshot_url ?? get_driver_headshot_template(data.graphics)}
|
||||
@ -428,7 +428,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Most DNFs -->
|
||||
<div class="mt-1 h-20 w-full border bg-surface-300 px-1 py-2 leading-3 lg:px-2">
|
||||
<div class="mt-1 h-20 w-full border bg-surface-200 px-1 py-2 leading-3 lg:px-2">
|
||||
<div class="mx-auto w-fit">
|
||||
<LazyImage
|
||||
src={mostdnfs?.headshot_url ?? get_driver_headshot_template(data.graphics)}
|
||||
@ -442,7 +442,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Doohan Starts -->
|
||||
<div class="mt-1 h-20 w-full border bg-surface-300 p-1 px-1 py-2 leading-3 lg:px-2">
|
||||
<div class="mt-1 h-20 w-full border bg-surface-200 p-1 px-1 py-2 leading-3 lg:px-2">
|
||||
<div class="mx-auto w-fit text-xs lg:text-sm">
|
||||
Jack Doohan startet <span class="font-bold">{pick?.doohanstarts ?? "?"}</span> mal.
|
||||
</div>
|
||||
@ -450,7 +450,7 @@
|
||||
|
||||
<!-- Teamwinners -->
|
||||
<div
|
||||
class="mt-1 h-64 w-full overflow-y-scroll border bg-surface-300 p-1 px-1 py-2 leading-3 lg:px-2"
|
||||
class="mt-1 h-64 w-full overflow-y-scroll border bg-surface-200 p-1 px-1 py-2 leading-3 lg:px-2"
|
||||
>
|
||||
{#if pick && pick.teamwinners}
|
||||
<div class="grid grid-cols-2 gap-1">
|
||||
@ -479,7 +479,7 @@
|
||||
<!-- Podiums -->
|
||||
<!-- TODO: Replace all style tags throughout the page with custom classes like height here -->
|
||||
<div
|
||||
class="mt-1 h-64 w-full overflow-y-scroll border bg-surface-300 p-1 px-1 py-2 leading-3 shadow lg:px-2"
|
||||
class="mt-1 h-64 w-full overflow-y-scroll border bg-surface-200 p-1 px-1 py-2 leading-3 shadow lg:px-2"
|
||||
>
|
||||
{#if pick && pick.podiums}
|
||||
<div class="grid grid-cols-2 gap-1">
|
||||
|
||||
@ -3,6 +3,9 @@ import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
ssr: {
|
||||
noExternal: process.env.NODE_ENV === "production" ? ["@carbon/charts"] : [],
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: ["sharp"],
|
||||
|
||||
Reference in New Issue
Block a user