moved some stuff around and made the scores look nicer

This commit is contained in:
2026-05-25 16:42:30 +01:00
parent d6fdddb972
commit 7f4f37608c
5 changed files with 170 additions and 45 deletions

View File

@@ -3,20 +3,18 @@ import { db } from '$lib/server/db';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import * as schema from '$lib/server/db/schema'; import * as schema from '$lib/server/db/schema';
let testScore = 0;
// Emitter that emits // Emitter that emits
export const globalEmitter = new EventEmitter(); export const globalEmitter = new EventEmitter();
//// REFERENCE CODE
// Increment score for testing (remove ts) // Increment score for testing (remove ts)
const increment = () => { // const increment = () => {
testScore++; // testScore++;
console.log('score incremented', testScore); // console.log('score incremented', testScore);
globalEmitter.emit('scoreUpdate'); // globalEmitter.emit('scoreUpdate');
}; // };
// Increment scores when there is an emit // Increment scores when there is an emit
globalEmitter.on('incrementScores', increment); // globalEmitter.on('incrementScores', increment);
// For page.server.ts so that it doesnt look weird before loading // For page.server.ts so that it doesnt look weird before loading
export async function getAllInitialInfo() { export async function getAllInitialInfo() {
@@ -29,11 +27,17 @@ export async function getAllInitialInfo() {
// Get teams object from database // Get teams object from database
export async function getTeams() { export async function getTeams() {
const allTeams = await db.select().from(schema.teamScoresView); const allTeams = await db.select().from(schema.teamScoresView);
for (let team in allTeams) {
let currentTeam = allTeams[team];
if (!currentTeam.totalPoints) {
currentTeam.totalPoints = 0;
}
}
return { return {
teams: allTeams.map((team) => ({ teams: allTeams.map((team) => ({
name: team.teamName, name: team.teamName,
color: team.teamColor, color: team.teamColor,
points: team.totalPoints || testScore points: team.totalPoints
})) }))
}; };
} }

90
src/lib/ui/Table.svelte Normal file
View File

@@ -0,0 +1,90 @@
<script lang="ts" generics="T extends { id: string | number }">
import type { Snippet } from 'svelte';
interface Props {
data: T[];
header?: Snippet;
row: Snippet<[T]>;
maxHeight?: string;
}
let { data, header, row, maxHeight = '300px' }: Props = $props();
let containerRef = $state<HTMLDivElement | null>(null);
let activeId = $state<string | number | null>(null);
/**
* Public function to scroll a specific row into the center of the viewport.
* Can be called manually by the parent component.
*/
export function scrollToId(id: string | number) {
if (!containerRef) return;
const targetRow = containerRef.querySelector(`#row-${id}`) as HTMLElement | null;
if (targetRow) {
// Update local state to highlight the centered row
activeId = id;
// Calculate the midpoint math to center the row
const rowOffsetTop = targetRow.offsetTop;
const rowHeight = targetRow.offsetHeight;
const containerHeight = containerRef.clientHeight;
// Target = Row position - (half of container space) + (half of row height adjustment)
const centerScrollTarget = rowOffsetTop - containerHeight / 2 + rowHeight / 2;
// Using behavior: 'auto' for an instantaneous snap instead of smooth scrolling
containerRef.scrollTo({
top: centerScrollTarget,
behavior: 'auto'
});
}
}
</script>
<div bind:this={containerRef} class="table-container" style="max-height: {maxHeight};">
<table class="w-full table-auto">
{#if header}
<thead>
<tr class="justify-content-center">{@render header()}</tr>
</thead>
{/if}
<tbody>
{#each data as d (d.id)}
<tr id="row-{d.id}" class="text-center" class:highlighted={d.id === activeId}>
{@render row(d)}
</tr>
{/each}
</tbody>
</table>
</div>
<style>
.table-container {
overflow-y: auto;
position: relative;
}
table thead {
position: sticky;
top: 0;
z-index: 1;
background: var(--ctp-mocha-base, #1e1e2e);
}
table :global(th:not(.large)),
table :global(td:not(.large)) {
width: 1%;
padding: 0;
margin: 0;
}
tbody tr:nth-child(2n + 1) {
background: var(--ctp-mocha-surface1);
}
tr.highlighted {
outline: 2px solid var(--ctp-mocha-lavender, #b4befe);
}
</style>

36
src/lib/ui/fitText.ts Normal file
View File

@@ -0,0 +1,36 @@
import type { Action } from 'svelte/action';
export const fitText: Action<HTMLElement> = (node) => {
const container = node.parentElement;
if (!container) return {};
function fit() {
node.style.whiteSpace = 'nowrap';
node.style.transformOrigin = 'top left';
node.style.transform = 'none';
// Step 1: fit to height
let size = 1;
node.style.fontSize = size + 'px';
while (node.scrollHeight <= container!.clientHeight) {
size++;
node.style.fontSize = size + 'px';
}
node.style.fontSize = size - 1 + 'px';
// Step 2: stretch width to fill container
const scaleX = container!.clientWidth / node.scrollWidth;
node.style.transform = `scaleX(${scaleX})`;
}
const observer = new ResizeObserver(fit);
observer.observe(container);
fit();
return {
destroy() {
observer.disconnect();
}
};
};

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
// import { enhance } from '$app/forms'; // import { enhance } from '$app/forms';
import Table from './Table.svelte'; import Table from '$lib/ui/Table.svelte';
// Get initial data from the load thing (innacurate lol) // Get initial data from the load thing (innacurate lol)
let { data }: { data: import('./$types').PageData } = $props(); let { data }: { data: import('./$types').PageData } = $props();
@@ -78,16 +78,31 @@
{/snippet} {/snippet}
<svelte:window onbeforeunload={() => scoreEndpoint?.close()} /> <svelte:window onbeforeunload={() => scoreEndpoint?.close()} />
<Table data={eventTable} {header} {row} />
<div class="p-[2vw]"> <div class="flex max-h-[150vh] flex-col object-contain p-[2vw]">
{#each leaderboard as team (team.name)} {#each leaderboard as team (team.name)}
<div <div
style="--theme-color: {team.color};" style="--theme-color: {team.color};"
class="score-box mb-2 aspect-3/1 rounded-2xl border-5 first:aspect-2/1" class="score-box mx-[10vw] mb-2 grid aspect-3/1 min-h-0 min-w-[70vw] flex-1 grid-cols-1 grid-rows-1 overflow-hidden rounded-2xl border-5 *:col-span-full *:row-end-[-1] *:flex *:items-center *:justify-center first:aspect-2/1"
> >
<div class="text-center">{team.name}</div> <div class="black-ops-one-regular @container uppercase opacity-60">
<div class="items-center justify-center text-center"><p>{team.points}</p></div> <svg viewBox="0 0.1 100 0.6" preserveAspectRatio="none" class="h-full w-full fill-current">
<text
x="0"
y="0.7"
font-size="1"
dominant-baseline="auto"
textLength="100"
lengthAdjust="spacingAndGlyphs"
class="goldman-bold"
>
{team.name}
</text>
</svg>
</div>
<div class="text-[20cqh]">
<p>{team.points.toString().padStart(3, '0')}</p>
</div>
</div> </div>
{/each} {/each}
</div> </div>
@@ -103,8 +118,17 @@
Send update Send update
</button> </button>
<Table data={eventTable} maxHeight="500px" focusId="20" {header} {row} />
<style> <style>
@import url('https://cdn.jsdelivr.net/npm/@catppuccin/palette/css/catppuccin.css'); @import url('https://cdn.jsdelivr.net/npm/@catppuccin/palette/css/catppuccin.css');
@import url('https://fonts.googleapis.com/css2?family=Black+Ops+One&display=swap');
.black-ops-one-regular {
font-family: 'Black Ops One', system-ui;
font-weight: 400;
font-style: normal;
}
.score-box { .score-box {
color: var(--theme-color); color: var(--theme-color);
border-color: var(--theme-color); border-color: var(--theme-color);

View File

@@ -1,29 +0,0 @@
<script>
let { data, header, row } = $props();
</script>
<table class="w-full table-auto">
{#if header}
<thead>
<tr class="justify-content-center">{@render header()}</tr>
</thead>
{/if}
<tbody>
{#each data as d}
<tr class="text-center">{@render row(d)}</tr>
{/each}
</tbody>
</table>
<style>
table :global(th:not(.large)),
table :global(td:not(.large)) {
width: 1%;
padding: 0;
margin: 0;
}
tbody tr:nth-child(2n + 1) {
background: var(--ctp-mocha-surface1);
}
</style>