initial commit
This commit is contained in:
24
web/.gitignore
vendored
Normal file
24
web/.gitignore
vendored
Normal 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
19
web/codegen.ts
Normal 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
13
web/index.html
Normal 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
8483
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
web/package.json
Normal file
32
web/package.json
Normal 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
6
web/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
33
web/src/App.svelte
Normal file
33
web/src/App.svelte
Normal 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
3
web/src/app.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
67
web/src/lib/Graph.svelte
Normal file
67
web/src/lib/Graph.svelte
Normal 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
9
web/src/main.ts
Normal 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
160
web/src/pages/Game.svelte
Normal 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}
|
||||
62
web/src/pages/Player.svelte
Normal file
62
web/src/pages/Player.svelte
Normal 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
2
web/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
7
web/svelte.config.js
Normal file
7
web/svelte.config.js
Normal 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
12
web/tailwind.config.js
Normal 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
20
web/tsconfig.app.json
Normal 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
7
web/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
24
web/tsconfig.node.json
Normal file
24
web/tsconfig.node.json
Normal 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
13
web/vite.config.ts
Normal 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",
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user