moved some stuff around and made the scores look nicer
This commit is contained in:
@@ -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
90
src/lib/ui/Table.svelte
Normal 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
36
src/lib/ui/fitText.ts
Normal 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
|
||||||
Reference in New Issue
Block a user