initial commit

This commit is contained in:
2025-02-02 15:11:35 +01:00
commit 3640116dd5
39 changed files with 10298 additions and 0 deletions

24
web/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

19
web/codegen.ts Normal file
View File

@@ -0,0 +1,19 @@
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
overwrite: true,
schema: "http://localhost:8080/graphql",
documents: "./src/**/*.svelte",
generates: {
"src/gql/": {
preset: "client",
plugins: [],
config:{
useTypeImports: true,
},
}
}
};
export default config;

13
web/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Beerpong</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

8483
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
web/package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "beerpong-elo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json",
"codegen": "graphql-codegen --config codegen.ts"
},
"devDependencies": {
"@graphql-codegen/cli": "5.0.3",
"@graphql-codegen/client-preset": "4.5.1",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tsconfig/svelte": "^5.0.4",
"autoprefixer": "^10.4.20",
"graphql": "^16.10.0",
"graphql-request": "^7.1.2",
"postcss": "^8.4.49",
"svelte": "^5.15.0",
"svelte-check": "^4.1.1",
"svelte2tsx": "^0.7.33",
"tailwindcss": "^3.4.17",
"typescript": "~5.6.2",
"vite": "^6.0.5"
},
"dependencies": {
"@dvcol/svelte-simple-router": "^1.9.1"
}
}

6
web/postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

33
web/src/App.svelte Normal file
View File

@@ -0,0 +1,33 @@
<script lang="ts">
import Game from "./pages/Game.svelte";
import Player from "./pages/Player.svelte";
import { RouterView } from "@dvcol/svelte-simple-router/components";
import type {
Route,
RouterOptions,
} from "@dvcol/svelte-simple-router/models";
const routes: Readonly<Route[]> = [
{
name: "game",
path: "/game/:{string}:id",
component: Game,
},
{
name: "player",
path: "/player/:{string}:id",
component: Player,
},
];
const options: RouterOptions = {
routes,
};
</script>
<main>
<RouterView {options} />
</main>
<style>
</style>

3
web/src/app.css Normal file
View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

67
web/src/lib/Graph.svelte Normal file
View File

@@ -0,0 +1,67 @@
<script lang="ts">
let {
data,
width,
height,
}: { data: Array<number>; width: number; height: number } = $props();
const lineStep = 1;
const labelStep = 2;
let minValue = $derived(Math.min(...data));
let maxValue = $derived(Math.max(...data));
const xScale = (i: number) => (i / (data.length - 1)) * width;
const yScale = (d: number) =>
height - ((d - minValue) / (maxValue - minValue)) * height;
let dPath = $derived(
data
.map((d, i) => `${i === 0 ? "M" : "L"}${xScale(i)},${yScale(d)}`)
.join(" "),
);
let lines: Array<number> = $derived(
Array.from(
{ length: Math.ceil((maxValue - minValue) / lineStep) + 1 },
(_, i) => minValue + i * lineStep,
),
);
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
{width}
{height}
viewBox={`0 0 ${width} ${height}`}
class="border-solid border-black border-2"
>
<g>
{#each lines as line}
<line
x1="0"
y1={yScale(line)}
x2={width}
y2={yScale(line)}
stroke="#e0e0e0"
stroke-width="1"
data-foo={line}
/>
{#if line % labelStep === 0}
<text
x="30"
y={yScale(line) - 2}
font-size="10"
text-anchor="end"
fill="black"
data-foo={line}
>
{line}
</text>
{/if}
{/each}
<!-- Data line -->
<path d={dPath} fill="none" stroke="orange" stroke-width="2" />
</g>
</svg>

9
web/src/main.ts Normal file
View File

@@ -0,0 +1,9 @@
import { mount } from 'svelte'
import './app.css'
import App from './App.svelte'
const app = mount(App, {
target: document.getElementById('app')!,
})
export default app

160
web/src/pages/Game.svelte Normal file
View File

@@ -0,0 +1,160 @@
<script lang="ts">
import { request } from "graphql-request";
import { graphql } from "./../gql";
import { onMount } from "svelte";
import type { GetGameQuery } from "../gql/graphql";
import { useRoute, link } from "@dvcol/svelte-simple-router/router";
const doc = graphql(`
query getGame($gameID: ID!) {
game(id: $gameID) {
added
overtime
score
author {
ID
name
}
team0player0 {
ID
name
history(game: $gameID) {
startElo
delta
}
}
team0player1 {
ID
name
history(game: $gameID) {
startElo
delta
}
}
team1player0 {
ID
name
history(game: $gameID) {
startElo
delta
}
}
team1player1 {
ID
name
history(game: $gameID) {
startElo
delta
}
}
}
}
`);
interface Player {
name: string;
id: string;
startElo: number;
delta: number;
}
interface Team {
name: string;
p0: Player;
p1: Player;
}
let loading = $state(true);
let errorState = $state(false);
let data: GetGameQuery | undefined = $state();
onMount(() => {
const idParam = useRoute().location?.params.id;
if (typeof idParam == "string") {
request(window.location.origin + "/graphql", doc, {
gameID: idParam,
})
.then((e) => {
data = e;
loading = false;
})
.catch((e) => {
console.error(e);
errorState = true;
loading = false;
});
} else {
loading = false;
}
});
</script>
<h1 class="text-4xl">Game</h1>
{#if loading}
Loading...
{:else if errorState}
An error occurred
{:else if data == null || data.game == null}
Game not found.
{:else}
{#snippet player(p: Player)}
<a use:link href="/player/{p.id}"
>{p.name} ({p.startElo} <span class="{p.delta<0?"text-red-700":"text-green-700"}" >{(p.delta<0?"":"+")+p.delta}</span>)
</a>
{/snippet}
{#snippet team(team: Team)}
<div class="mx-3">
<h2 class="text-xl">{team.name}</h2>
<div>
{@render player(team.p0)}
</div>
<div>
{@render player(team.p1)}
</div>
</div>
{/snippet}
Added by: {data.game.author.name} on {data.game.added}
<div class="flex justify-between">
{@render team({
name: "Team 1",
p0: {
id: data.game.team0player0.ID,
name: data.game.team0player0.name,
startElo: data.game.team0player0.history[0]!.startElo,
delta: data.game.team0player0.history[0]!.delta,
},
p1: {
id: data.game.team0player1.ID,
name: data.game.team0player1.name,
startElo: data.game.team0player1.history[0]!.startElo,
delta: data.game.team0player1.history[0]!.delta,
},
})}
<div class="text-2xl flex items-center">
<div>
<span>{data.game.score > 0 ? data.game.score : 0}</span>
-
<span>{data.game.score < 0 ? -data.game.score : 0}</span>
</div>
</div>
{@render team({
name: "Team 1",
p0: {
id: data.game.team1player0.ID,
name: data.game.team1player0.name,
startElo: data.game.team1player0.history[0]!.startElo,
delta: data.game.team1player0.history[0]!.delta,
},
p1: {
id: data.game.team1player1.ID,
name: data.game.team1player1.name,
startElo: data.game.team1player1.history[0]!.startElo,
delta: data.game.team1player1.history[0]!.delta,
},
})}
</div>
{/if}

View File

@@ -0,0 +1,62 @@
<script lang="ts">
import { request } from "graphql-request";
import { graphql } from "./../gql";
import { onMount } from "svelte";
import type { GetPlayerQuery } from "../gql/graphql";
import { useRoute } from "@dvcol/svelte-simple-router/router";
import Graph from "../lib/Graph.svelte";
const doc = graphql(`
query getPlayer($playerID: ID!) {
player(id: $playerID) {
ID
name
elo
history {
delta
endElo
game {
id
}
}
}
}
`);
let loading = $state(true);
let errorState = $state(false);
let data: GetPlayerQuery | undefined = $state();
onMount(() => {
const idParam = useRoute().location?.params.id;
if (typeof idParam == "string") {
request(window.location.origin + "/graphql", doc, {
playerID: idParam,
})
.then((e) => {
data = e;
loading = false;
})
.catch((e) => {
console.error(e);
errorState = true;
loading = false;
});
} else {
loading = false;
}
});
</script>
{#if loading}
Loading...
{:else if errorState}
An error occurred
{:else if data == null || data.player == null}
Player not found.
{:else}
<div class="m-2">
<h1 class="text-2xl">{data.player.name} ({data.player.elo})</h1>
<Graph height={500} width={800} data={data.player.history.map((e) => e!.endElo)} />
</div>
{/if}

2
web/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

7
web/svelte.config.js Normal file
View File

@@ -0,0 +1,7 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
}

12
web/tailwind.config.js Normal file
View File

@@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{svelte,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

20
web/tsconfig.app.json Normal file
View File

@@ -0,0 +1,20 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true,
"moduleDetection": "force"
},
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
}

7
web/tsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

24
web/tsconfig.node.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

13
web/vite.config.ts Normal file
View File

@@ -0,0 +1,13 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vite.dev/config/
export default defineConfig({
plugins: [svelte()],
server:{
proxy:{
"/graphql": "http://localhost:8080",
},
},
})