player view donesies (vibe coding might be insane)
This commit is contained in:
@@ -230,6 +230,81 @@ export async function getRegisteredEventsWithPlayers(eventId?: number) {
|
|||||||
return fullEventList;
|
return fullEventList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getPlayerEvents(playerId: number) {
|
||||||
|
const registrations = await getPlayerRegistrations(playerId);
|
||||||
|
if (registrations.length === 0) return [];
|
||||||
|
|
||||||
|
const events = await Promise.all(
|
||||||
|
registrations.map(async (reg) => {
|
||||||
|
const [event] = await db
|
||||||
|
.select()
|
||||||
|
.from(schema.registeredEventsView)
|
||||||
|
.where(eq(schema.registeredEventsView.eventId, reg.eventId));
|
||||||
|
|
||||||
|
if (!event) return null;
|
||||||
|
|
||||||
|
let awardedPoints = 0;
|
||||||
|
if (event.scorePreset && reg.placement > 0) {
|
||||||
|
const [pointsRow] = await db
|
||||||
|
.select()
|
||||||
|
.from(schema.scoringPresets)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(schema.scoringPresets.presetID, event.scorePreset),
|
||||||
|
eq(schema.scoringPresets.placement, reg.placement)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (pointsRow) awardedPoints = pointsRow.points;
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await db
|
||||||
|
.select()
|
||||||
|
.from(schema.registeredResults)
|
||||||
|
.where(eq(schema.registeredResults.registeredPlayerId, reg.registeredPlayerId));
|
||||||
|
|
||||||
|
let winnerInfo: { name: string; color: string; points: number } | null = null;
|
||||||
|
if (event.winner) {
|
||||||
|
const teamData = await getTeams(event.winner);
|
||||||
|
winnerInfo = teamData.teams[0] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let unit = '';
|
||||||
|
if (event.resultPreset) {
|
||||||
|
const { resultPresets } = await getResultPreset(event.resultPreset);
|
||||||
|
if (resultPresets.length > 0) {
|
||||||
|
unit = resultPresets[0].unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: reg.eventId,
|
||||||
|
name: reg.eventName,
|
||||||
|
division: event.division,
|
||||||
|
state: event.state,
|
||||||
|
completed: event.timeCompleted || 0,
|
||||||
|
winner: winnerInfo,
|
||||||
|
bracket: reg.bracket,
|
||||||
|
placement: reg.placement,
|
||||||
|
points: awardedPoints,
|
||||||
|
unit,
|
||||||
|
results: results.map((r) => ({
|
||||||
|
index: r.resultIndex,
|
||||||
|
result: r.result
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return events
|
||||||
|
.filter((e) => e !== null)
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.completed && b.completed) return b.completed - a.completed;
|
||||||
|
if (a.completed) return -1;
|
||||||
|
if (b.completed) return 1;
|
||||||
|
return b.id - a.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function getLedgerEntries(limit: number | 'all' = 100) {
|
export async function getLedgerEntries(limit: number | 'all' = 100) {
|
||||||
let query = db
|
let query = db
|
||||||
.select({
|
.select({
|
||||||
|
|||||||
20
src/routes/api/playerEvents/[playerId]/+server.ts
Normal file
20
src/routes/api/playerEvents/[playerId]/+server.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { getPlayerEvents } from '$lib/server/databaseManager';
|
||||||
|
import { globalEmitter } from '$lib/server/globalEmitter';
|
||||||
|
import { generateEndpoint } from '$lib/server/endpoint';
|
||||||
|
|
||||||
|
export async function GET({ params, request }) {
|
||||||
|
const playerId = parseInt(params.playerId);
|
||||||
|
const endpoint = generateEndpoint(async (enqueue) => {
|
||||||
|
let eventList = async () => {
|
||||||
|
let events = await getPlayerEvents(playerId);
|
||||||
|
enqueue(events);
|
||||||
|
};
|
||||||
|
eventList();
|
||||||
|
globalEmitter.on('eventUpdate', eventList);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
globalEmitter.off('eventUpdate', eventList);
|
||||||
|
};
|
||||||
|
}, request);
|
||||||
|
return (await endpoint).response;
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import { getPlayerInfo } from '$lib/server/databaseManager';
|
import { getPlayerInfo, getPlayerEvents } from '$lib/server/databaseManager';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
// export const load: PageServerLoad = async ({ params }) => {
|
|
||||||
// return {
|
|
||||||
// data: await getPlayerInfo(parseInt(params.playerId))
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params }) => {
|
export const load: PageServerLoad = async ({ params }) => {
|
||||||
|
const playerId = parseInt(params.playerId);
|
||||||
|
const [playerInfo, playerEvents] = await Promise.all([
|
||||||
|
getPlayerInfo(playerId),
|
||||||
|
getPlayerEvents(playerId)
|
||||||
|
]);
|
||||||
return {
|
return {
|
||||||
playerInfo: getPlayerInfo(parseInt(params.playerId))
|
playerInfo,
|
||||||
|
playerEvents
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,29 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
|
|
||||||
let { data }: PageProps = $props();
|
let { data }: PageProps = $props();
|
||||||
|
let { playerInfo, playerEvents: initialEvents } = data;
|
||||||
|
let playerEvents = $state(initialEvents);
|
||||||
|
|
||||||
|
let eventEndpoint: EventSource;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
eventEndpoint = new EventSource(`/api/playerEvents/${playerInfo.id}`);
|
||||||
|
eventEndpoint.onmessage = (e) => {
|
||||||
|
playerEvents = JSON.parse(e.data);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
eventEndpoint?.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
function ordinal(n: number) {
|
||||||
|
const s = ['th', 'st', 'nd', 'rd'];
|
||||||
|
const v = n % 100;
|
||||||
|
return n + (s[(v - 20) % 10] ?? s[v] ?? s[0]);
|
||||||
|
}
|
||||||
|
|
||||||
function marquee(node: HTMLElement) {
|
function marquee(node: HTMLElement) {
|
||||||
function measure() {
|
function measure() {
|
||||||
@@ -22,13 +44,9 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await data.playerInfo}
|
|
||||||
<div class="page-container"><div>Loading...</div></div>
|
|
||||||
{:then playerInfo}
|
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<div class="flex justify-center">
|
|
||||||
<div
|
<div
|
||||||
class="player-box aspect-square w-full max-w-[95vw] md:aspect-2/1 lg:aspect-2/1"
|
class="player-box !max-w-full w-full aspect-2/1 md:aspect-auto"
|
||||||
style="--c:{playerInfo.teamInfo.color}"
|
style="--c:{playerInfo.teamInfo.color}"
|
||||||
>
|
>
|
||||||
<div class="player-ghost" aria-hidden="true">
|
<div class="player-ghost" aria-hidden="true">
|
||||||
@@ -44,11 +62,70 @@
|
|||||||
>
|
>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="goldman player-name-wrap text-5xl" use:marquee>
|
<div
|
||||||
{playerInfo.firstName}
|
class="relative z-1 flex flex-col justify-center h-full gap-0.5 px-5 py-3 md:flex-row md:items-center md:justify-between md:gap-0 md:px-6 md:py-4"
|
||||||
{playerInfo.lastName}
|
>
|
||||||
|
<div class="goldman player-name-wrap text-3xl md:text-4xl lg:text-5xl" use:marquee>
|
||||||
|
{playerInfo.firstName} {playerInfo.lastName}
|
||||||
|
</div>
|
||||||
|
<div class="goldman text-lg md:text-right md:text-xl lg:text-2xl" style="opacity: 0.85;">
|
||||||
|
{playerInfo.teamInfo.name}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p class="section-label">Events</p>
|
||||||
|
|
||||||
|
<section class="events-scroll" style="box-shadow: none; max-height: none; overflow-y: visible;">
|
||||||
|
<div class="events">
|
||||||
|
{#each playerEvents as event (event.id)}
|
||||||
|
<div
|
||||||
|
class="event-card"
|
||||||
|
class:ongoing-event={event.state == 1}
|
||||||
|
class:completed-event={event.state == 2}
|
||||||
|
style={event.state == 2 && event.winner ? `--event-color: ${event.winner.color}` : ''}
|
||||||
|
>
|
||||||
|
<div class="event-header">
|
||||||
|
<a href="/event/{event.id}" class="event-name goldman">{event.name}</a>
|
||||||
|
<span class="event-division">{event.division}</span>
|
||||||
|
{#if event.state == 1}
|
||||||
|
<span class="event-status goldman">ONGOING</span>
|
||||||
|
{:else if event.state == 2}
|
||||||
|
<span
|
||||||
|
class="event-winner event-status goldman"
|
||||||
|
style={event.winner ? `--winner-color:${event.winner.color}` : ''}
|
||||||
|
>Won By {event.winner?.name ?? '?'}</span
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1.5 px-3 py-2 text-sm">
|
||||||
|
<div class="flex items-baseline gap-2">
|
||||||
|
<span class="text-xs opacity-50 goldman">{event.bracket}</span>
|
||||||
|
{#if event.placement > 0}
|
||||||
|
<div class="flex items-baseline gap-1">
|
||||||
|
<span class="text-xs opacity-50">placed</span>
|
||||||
|
<span class="goldman text-base">{ordinal(event.placement)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="ml-auto flex items-baseline gap-1">
|
||||||
|
<span class="goldman text-base">{event.points}</span>
|
||||||
|
<span class="text-xs opacity-50">pts</span>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<span class="text-xs opacity-40">Unranked</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if event.results.length > 0}
|
||||||
|
<div class="text-xs opacity-70">
|
||||||
|
{#each event.results as score, i}
|
||||||
|
<span
|
||||||
|
>{i > 0 ? ' · ' : ''}Run {score.index + 1}: {score.result}{event.unit}</span
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
|
||||||
Reference in New Issue
Block a user