New Code Editor and fun
This commit is contained in:
44
src/components/EloTable.svelte
Normal file
44
src/components/EloTable.svelte
Normal file
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import {statsRepo} from "./repo/repo.ts";
|
||||
|
||||
export let gamemode: string;
|
||||
|
||||
let request = getRequest();
|
||||
|
||||
function getRequest() {
|
||||
return $statsRepo.getRankings(gamemode);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await request}
|
||||
<p>Loading...</p>
|
||||
{:then data}
|
||||
<div>
|
||||
<table>
|
||||
<tr class="font-bold">
|
||||
<td>Platz</td>
|
||||
<td>Spieler</td>
|
||||
<td>Elo</td>
|
||||
</tr>
|
||||
{#each data as player, i}
|
||||
<tr>
|
||||
<td>{`${i + 1}.`}</td>
|
||||
<td>{player.name}</td>
|
||||
<td>{player.elo}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
</div>
|
||||
{:catch error}
|
||||
<p>{error.message}</p>
|
||||
{/await}
|
||||
|
||||
<style lang="scss">
|
||||
table {
|
||||
@apply w-full;
|
||||
}
|
||||
div {
|
||||
@apply p-3 bg-gray-200 dark:bg-gray-800 rounded-2xl;
|
||||
}
|
||||
</style>
|
||||
|
||||
19
src/components/FightStatistics.svelte
Normal file
19
src/components/FightStatistics.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import {statsRepo} from "./repo/repo.ts";
|
||||
import FightStatsChart from "./FightStatsChart.svelte";
|
||||
import {t} from "astro-i18n";
|
||||
|
||||
let request = getStats();
|
||||
|
||||
function getStats() {
|
||||
return $statsRepo.getFightStats();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await request}
|
||||
<p>{t("status.loading")}</p>
|
||||
{:then stats}
|
||||
<FightStatsChart data={stats} />
|
||||
{:catch error}
|
||||
<p>error: {error}</p>
|
||||
{/await}
|
||||
73
src/components/FightStatsChart.svelte
Normal file
73
src/components/FightStatsChart.svelte
Normal file
@@ -0,0 +1,73 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {
|
||||
Chart,
|
||||
LineController,
|
||||
LineElement,
|
||||
PointElement,
|
||||
LinearScale,
|
||||
TimeScale,
|
||||
Tooltip,
|
||||
Legend,
|
||||
Title
|
||||
} from "chart.js"
|
||||
import type {FightStats} from "./types/stats.ts"
|
||||
import 'chartjs-adapter-moment'
|
||||
|
||||
export let data: FightStats;
|
||||
let chart;
|
||||
let canvas: HTMLCanvasElement;
|
||||
|
||||
onMount(async () => {
|
||||
Chart.register(LineController, LineElement, PointElement, LinearScale, TimeScale, Tooltip, Legend, Title)
|
||||
if (document.body.parentElement!.classList.contains("dark")) {
|
||||
Chart.defaults.color = "#fff"
|
||||
}
|
||||
|
||||
const colors = ["#abfa91", "#279900", "#00ffbe", "#9297fb", "#050b9d", "#b60fff", "#8dddfc", "#0880ad", "#41ff00", "#039973", "#96fce2", "#0009ff", "#7501a4", "#e2a2fb", "#00b9ff"];
|
||||
const map = new Map<string, {x: Date, y: number}[]>()
|
||||
for (const {date, count, gamemode} of data) {
|
||||
if (!map.has(gamemode)) {
|
||||
map.set(gamemode, [])
|
||||
}
|
||||
map.get(gamemode)!!.push({x: new Date(date), y: count})
|
||||
}
|
||||
|
||||
chart = new Chart(
|
||||
canvas,
|
||||
{
|
||||
type: "line",
|
||||
data: {
|
||||
datasets: Array.from(map.entries()).map(([gamemode, data]) => {
|
||||
const color = colors.pop()
|
||||
return {
|
||||
label: gamemode,
|
||||
fill: false,
|
||||
borderColor: color,
|
||||
backgroundColor: color,
|
||||
spanGaps: true,
|
||||
data
|
||||
};
|
||||
})
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
x: {
|
||||
type: "time",
|
||||
time: {
|
||||
unit: "day"
|
||||
}
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<canvas bind:this={canvas} />
|
||||
</div>
|
||||
50
src/components/FightTable.svelte
Normal file
50
src/components/FightTable.svelte
Normal file
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import {eventRepo} from "./repo/repo.ts";
|
||||
import {astroI18n} from "astro-i18n";
|
||||
import type {ExtendedEvent} from "./types/event.ts";
|
||||
|
||||
export let event: ExtendedEvent;
|
||||
export let group: string;
|
||||
export let rows: number = 1;
|
||||
|
||||
function window<T>(arr: T[], len: number): T[][] {
|
||||
let result: T[][] = [];
|
||||
for (let i = 0; i < arr.length; i += len) {
|
||||
result.push(arr.slice(i, i + len));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr class="font-bold border-b">
|
||||
{#each Array(rows) as _}
|
||||
<td>Time</td>
|
||||
<td>Blue Team</td>
|
||||
<td>Red Team</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{#each window(event.fights.filter(f => f.group === group), rows) as fights}
|
||||
<tr>
|
||||
{#each fights as fight}
|
||||
<td>{Intl.DateTimeFormat(astroI18n.locale, {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
}).format(new Date(fight.start))}</td>
|
||||
<td class:font-bold={fight.ergebnis === 1} class:italic={fight.ergebnis === 3}>{fight.blueTeam.kuerzel}</td>
|
||||
<td class:font-bold={fight.ergebnis === 2} class:italic={fight.ergebnis === 3}>{fight.redTeam.kuerzel}</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
table {
|
||||
@apply w-full;
|
||||
}
|
||||
div {
|
||||
@apply p-3 bg-gray-200 dark:bg-gray-800 rounded-2xl w-3/4 mx-auto;
|
||||
}
|
||||
</style>
|
||||
37
src/components/PostComponent.astro
Normal file
37
src/components/PostComponent.astro
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
import {CollectionEntry} from "astro:content"
|
||||
import {l} from "../util/util";
|
||||
import {astroI18n} from "astro-i18n";
|
||||
import {Image} from "astro:assets";
|
||||
import TagComponent from "./TagComponent.astro";
|
||||
|
||||
interface Props {
|
||||
post: CollectionEntry<'announcements'>
|
||||
}
|
||||
|
||||
const { post } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<a href={l(`/announcements/${post.slug.split("/").slice(1).join("/")}`)}>
|
||||
<div class="p-4 flex flex-row">
|
||||
{post.data.image != null ? (
|
||||
<div class="flex-shrink-0 pr-2">
|
||||
<Image src={post.data.image} alt="Post Image" class="rounded-2xl shadow-2xl object-cover h-32 w-32 max-w-none transition-transform hover:scale-105" />
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold">{post.data.title}</h2>
|
||||
<div class="text-gray-500">{Intl.DateTimeFormat(astroI18n.locale, {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric"
|
||||
}).format(post.data.created)}</div>
|
||||
<div>{post.data.description}</div>
|
||||
<div>
|
||||
{post.data.tags.map((tag) => (
|
||||
<TagComponent tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
16
src/components/TagComponent.astro
Normal file
16
src/components/TagComponent.astro
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
|
||||
import {l} from "../util/util";
|
||||
import {capitalize} from "./admin/util";
|
||||
|
||||
interface Props {
|
||||
tag: string;
|
||||
}
|
||||
|
||||
const {tag} = Astro.props;
|
||||
|
||||
---
|
||||
|
||||
<a href={l(`/announcements/tags/${tag}`)}>
|
||||
<span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 shadow-2xl">{capitalize(tag)}</span>
|
||||
</a>
|
||||
@@ -6,7 +6,7 @@
|
||||
import {eventRepo} from "../../repo/repo.js";
|
||||
import TeamList from "./event/TeamList.svelte";
|
||||
|
||||
export let params: { id: number } = {};
|
||||
export let params: { id: number };
|
||||
|
||||
let id = params.id;
|
||||
let event = $eventRepo.getEvent(id.toString());
|
||||
|
||||
@@ -4,20 +4,21 @@
|
||||
import {players} from "../../stores/stores.ts";
|
||||
import {permsRepo} from "../../repo/repo.ts";
|
||||
import {capitalize} from "../util.ts";
|
||||
import type {Player} from "../../types/data.ts";
|
||||
|
||||
let search = "";
|
||||
$: lowerCaseSearch = search.toLowerCase();
|
||||
$: filteredPlayers = $players.filter(value => value.name.toLowerCase().includes(lowerCaseSearch));
|
||||
|
||||
let selectedPlayer = null;
|
||||
let selectedPlayer: number | null = null;
|
||||
$: player = $players.find(value => value.id === selectedPlayer);
|
||||
let playerPerms = loadPlayer(selectedPlayer);
|
||||
$: playerPerms = loadPlayer(selectedPlayer);
|
||||
|
||||
let prefixEdit = "PREFIX_NONE";
|
||||
let activePerms = [];
|
||||
let activePerms: string[] = [];
|
||||
|
||||
function loadPlayer(id: number) {
|
||||
function loadPlayer(id: number | null) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
@@ -39,20 +40,20 @@
|
||||
}
|
||||
|
||||
function save() {
|
||||
playerPerms.then(async perms => {
|
||||
playerPerms!.then(async perms => {
|
||||
if (perms.prefix.name != prefixEdit) {
|
||||
await $permsRepo.setPrefix(selectedPlayer, prefixEdit);
|
||||
await $permsRepo.setPrefix(selectedPlayer!, prefixEdit);
|
||||
}
|
||||
|
||||
for (let value of activePerms) {
|
||||
if (!perms.perms.includes(value)) {
|
||||
await $permsRepo.addPerm(selectedPlayer, value);
|
||||
await $permsRepo.addPerm(selectedPlayer!, value);
|
||||
}
|
||||
}
|
||||
|
||||
for (let value of perms.perms) {
|
||||
if (!activePerms.includes(value)) {
|
||||
await $permsRepo.removePerm(selectedPlayer, value);
|
||||
await $permsRepo.removePerm(selectedPlayer!, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {Spinner, Toolbar, ToolbarButton, ToolbarGroup, Tooltip} from "flowbite-svelte";
|
||||
import {markdown} from "@codemirror/lang-markdown";
|
||||
import {json} from "@codemirror/lang-json";
|
||||
import CodeMirror from "svelte-codemirror-editor";
|
||||
import {pageRepo} from "../../../repo/repo.ts";
|
||||
@@ -8,6 +7,7 @@
|
||||
import type {Page} from "../../../types/page.ts";
|
||||
import {materialDark} from '@ddietr/codemirror-themes/material-dark.js'
|
||||
import {createEventDispatcher} from "svelte";
|
||||
import MDEMarkdownEditor from "./MDEMarkdownEditor.svelte";
|
||||
|
||||
export let pageId: number;
|
||||
export let branch: string;
|
||||
@@ -56,7 +56,11 @@
|
||||
</ToolbarGroup>
|
||||
</Toolbar>
|
||||
</div>
|
||||
<CodeMirror bind:value={pageContent} lang={page?.name.endsWith("md") ? markdown() : json()} theme={materialDark} />
|
||||
{#if page?.name.endsWith("md")}
|
||||
<MDEMarkdownEditor bind:value={pageContent} />
|
||||
{:else}
|
||||
<CodeMirror bind:value={pageContent} lang={json()} theme={materialDark} />
|
||||
{/if}
|
||||
</div>
|
||||
{:catch error}
|
||||
<p>{error.message}</p>
|
||||
|
||||
37
src/components/admin/pages/edit/MDEMarkdownEditor.svelte
Normal file
37
src/components/admin/pages/edit/MDEMarkdownEditor.svelte
Normal file
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import {onDestroy, onMount} from "svelte";
|
||||
import EasyMDE from "easymde";
|
||||
import "easymde/dist/easymde.min.css"
|
||||
|
||||
export let value: string;
|
||||
let editor: HTMLTextAreaElement;
|
||||
let mde: EasyMDE;
|
||||
|
||||
onMount(() => {
|
||||
mde = new EasyMDE({
|
||||
element: editor,
|
||||
initialValue: value,
|
||||
spellChecker: false,
|
||||
})
|
||||
mde.codemirror.on("change", () => {
|
||||
value = mde.value();
|
||||
})
|
||||
})
|
||||
onDestroy(() => {
|
||||
mde.toTextArea();
|
||||
mde.cleanup();
|
||||
})
|
||||
</script>
|
||||
|
||||
<textarea bind:this={editor} class="editor-preview">
|
||||
|
||||
</textarea>
|
||||
|
||||
<style>
|
||||
:global(.editor-preview) {
|
||||
& * {
|
||||
all: revert;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
25
src/components/dashboard/Statistics.svelte
Normal file
25
src/components/dashboard/Statistics.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import type {Player} from "../types/data.ts";
|
||||
import {statsRepo} from "../repo/repo.ts";
|
||||
import {t} from "astro-i18n"
|
||||
|
||||
export let user: Player;
|
||||
|
||||
let request = getRequest();
|
||||
|
||||
function getRequest() {
|
||||
return $statsRepo.getUserStats(user.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await request}
|
||||
<p>{t("status.loading")}</p>
|
||||
{:then data}
|
||||
<p>Playtime: {data.playtime}h</p>
|
||||
<p>Fights: {data.fights}</p>
|
||||
{#if user.perms.includes("CHECK")}
|
||||
<p>Schematics Checked: {data.acceptedSchematics}</p>
|
||||
{/if}
|
||||
{:catch error}
|
||||
<p>error: {error}</p>
|
||||
{/await}
|
||||
@@ -3,6 +3,7 @@
|
||||
import type {Player} from "../types/data.ts";
|
||||
import {tokenStore} from "../repo/repo.ts";
|
||||
import {l} from "../../util/util.ts";
|
||||
import Statistics from "./Statistics.svelte";
|
||||
|
||||
export let user: Player;
|
||||
|
||||
@@ -31,11 +32,6 @@
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold">{t("dashboard.title", {name: user.name})}</h1>
|
||||
<p>{t("dashboard.rank", {rank: t("home.prefix." + user.prefix)})}</p>
|
||||
<p>{t("dashboard.permissions")}</p>
|
||||
<ul>
|
||||
{#each user.perms as permission}
|
||||
<li class="list-disc ml-6">{permission}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<Statistics {user} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -6,10 +6,10 @@ export class DataRepo {
|
||||
constructor(private token: string) {}
|
||||
|
||||
public async getServer(): Promise<Server> {
|
||||
return await fetchWithToken(this.token, "/data/server").then(value => value.json()).then(value => ServerSchema.parse(value));
|
||||
return await fetchWithToken(this.token, "/data/server").then(value => value.json()).then(ServerSchema.parse);
|
||||
}
|
||||
|
||||
public async getMe(): Promise<Player> {
|
||||
return await fetchWithToken(this.token, "/data/me").then(value => value.json()).then(value => PlayerSchema.parse(value));
|
||||
return await fetchWithToken(this.token, "/data/me").then(value => value.json()).then(PlayerSchema.parse);
|
||||
}
|
||||
}
|
||||
@@ -25,44 +25,31 @@ export class EventRepo {
|
||||
constructor(private token: string) {}
|
||||
|
||||
public async listEvents(): Promise<ShortEvent[]> {
|
||||
const res = await fetchWithToken(this.token, "/events");
|
||||
|
||||
if (res.ok) {
|
||||
return z.array(ShortEventSchema).parse(await res.json());
|
||||
} else {
|
||||
throw new Error("Could not fetch events: " + res.statusText);
|
||||
}
|
||||
return await fetchWithToken(this.token, "/events")
|
||||
.then(value => value.json())
|
||||
.then(value => z.array(ShortEventSchema).parse(value));
|
||||
}
|
||||
|
||||
public async getEvent(id: string): Promise<ExtendedEvent> {
|
||||
const res = await fetchWithToken(this.token, `/events/${id}`);
|
||||
|
||||
if (res.ok) {
|
||||
return ExtendedEventSchema.parse(await res.json());
|
||||
} else {
|
||||
throw new Error("Could not fetch event: " + res.statusText);
|
||||
}
|
||||
return await fetchWithToken(this.token, `/events/${id}`)
|
||||
.then(value => value.json())
|
||||
.then(ExtendedEventSchema.parse);
|
||||
}
|
||||
|
||||
public async createEvent(event: CreateEvent): Promise<SWEvent> {
|
||||
const res = await fetchWithToken(this.token, "/events", {
|
||||
return await fetchWithToken(this.token, "/events", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
name: event.name,
|
||||
start: +event.start,
|
||||
end: +event.end
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
return SWEventSchema.parse(await res.json());
|
||||
} else {
|
||||
throw new Error("Could not create event: " + res.statusText);
|
||||
}
|
||||
}).then(value => value.json())
|
||||
.then(SWEventSchema.parse);
|
||||
}
|
||||
|
||||
public async updateEvent(id: string, event: UpdateEvent): Promise<SWEvent> {
|
||||
const res = await fetchWithToken(this.token, `/events/${id}`, {
|
||||
return await fetchWithToken(this.token, `/events/${id}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
name: event.name,
|
||||
@@ -77,13 +64,8 @@ export class EventRepo {
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
return SWEventSchema.parse(await res.json());
|
||||
} else {
|
||||
throw new Error("Could not update event: " + res.statusText);
|
||||
}
|
||||
}).then(value => value.json())
|
||||
.then(SWEventSchema.parse);
|
||||
}
|
||||
|
||||
public async deleteEvent(id: string): Promise<boolean> {
|
||||
|
||||
@@ -28,17 +28,13 @@ export class FightRepo {
|
||||
constructor(private token: string) {}
|
||||
|
||||
public async listFights(eventId: number): Promise<EventFight[]> {
|
||||
const res = await fetchWithToken(this.token, `/events/${eventId}/fights`);
|
||||
|
||||
if (res.ok) {
|
||||
return z.array(EventFightSchema).parse(await res.json());
|
||||
} else {
|
||||
throw new Error("Could not fetch fights: " + res.statusText);
|
||||
}
|
||||
return await fetchWithToken(this.token, `/events/${eventId}/fights`)
|
||||
.then(value => value.json())
|
||||
.then(value => z.array(EventFightSchema).parse(value));
|
||||
}
|
||||
|
||||
public async createFight(eventId: number, fight: CreateFight): Promise<EventFight> {
|
||||
let res = await fetchWithToken(this.token, `/fights`, {
|
||||
return await fetchWithToken(this.token, `/fights`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
event: eventId,
|
||||
@@ -50,17 +46,12 @@ export class FightRepo {
|
||||
kampfleiter: fight.kampfleiter,
|
||||
group: fight.group
|
||||
})
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
return EventFightSchema.parse(await res.json());
|
||||
} else {
|
||||
throw new Error("Could not create fight: " + res.statusText);
|
||||
}
|
||||
}).then(value => value.json())
|
||||
.then(EventFightSchema.parse)
|
||||
}
|
||||
|
||||
public async updateFight(fightId: number, fight: UpdateFight): Promise<EventFight> {
|
||||
let res = await fetchWithToken(this.token, `/fights/${fightId}`, {
|
||||
return await fetchWithToken(this.token, `/fights/${fightId}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
spielmodus: fight.spielmodus,
|
||||
@@ -71,13 +62,8 @@ export class FightRepo {
|
||||
kampfleiter: fight.kampfleiter,
|
||||
group: fight.group
|
||||
})
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
return EventFightSchema.parse(await res.json());
|
||||
} else {
|
||||
throw new Error("Could not update fight: " + res.statusText);
|
||||
}
|
||||
}).then(value => value.json())
|
||||
.then(EventFightSchema.parse)
|
||||
}
|
||||
|
||||
public async deleteFight(fightId: number): Promise<void> {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {Page, PageList} from "../types/page.ts";
|
||||
import {fetchWithToken} from "./repo.ts";
|
||||
import {PageListSchema, PageSchema} from "../types/page.ts";
|
||||
import {bytesToBase64} from "../admin/util.ts";
|
||||
import {z} from "zod";
|
||||
|
||||
export class PageRepo {
|
||||
constructor(private token: string) {}
|
||||
@@ -9,13 +10,14 @@ export class PageRepo {
|
||||
public async listPages(branch: string = "master"): Promise<PageList> {
|
||||
return await fetchWithToken(this.token, `/page?branch=${branch}`)
|
||||
.then(value => value.json())
|
||||
.then(value => PageListSchema.parse(value).map(value1 => ({...value1, path: value1.path.replace("src/content/", "")})))
|
||||
.then(PageListSchema.parse)
|
||||
.then(value => value.map(value1 => ({...value1, path: value1.path.replace("src/content/", "")})))
|
||||
}
|
||||
|
||||
public async getPage(id: number, branch: string = "master"): Promise<Page> {
|
||||
return await fetchWithToken(this.token, `/page/${id}?branch=${branch}`)
|
||||
.then(value => value.json())
|
||||
.then(value => PageSchema.parse(value))
|
||||
.then(PageSchema.parse)
|
||||
}
|
||||
|
||||
public async updatePage(id: number, content: string, sha: string, message: string, branch: string = "master"): Promise<void> {
|
||||
@@ -31,6 +33,7 @@ export class PageRepo {
|
||||
public async getBranches(): Promise<string[]> {
|
||||
return await fetchWithToken(this.token, "/page/branch")
|
||||
.then(value => value.json())
|
||||
.then(value => z.array(z.string()).parse(value))
|
||||
}
|
||||
|
||||
public async createBranch(branch: string): Promise<void> {
|
||||
|
||||
@@ -16,13 +16,7 @@ export class PermsRepo {
|
||||
}
|
||||
|
||||
public async getPerms(userId: number): Promise<UserPerms> {
|
||||
const res = await fetchWithToken(this.token, `/perms/user/${userId}`);
|
||||
|
||||
if (res.ok) {
|
||||
return UserPermsSchema.parse(await res.json());
|
||||
} else {
|
||||
throw new Error("Could not fetch perms: " + res.statusText);
|
||||
}
|
||||
return await fetchWithToken(this.token, `/perms/user/${userId}`).then(value => value.json()).then(UserPermsSchema.parse);
|
||||
}
|
||||
|
||||
public async setPrefix(userId: number, prefix: string): Promise<void> {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {DataRepo} from "./data.ts";
|
||||
|
||||
import { AES, enc, format } from "crypto-js";
|
||||
import {SchematicRepo} from "./schem.ts";
|
||||
import {StatsRepo} from "./stats.ts";
|
||||
|
||||
export { EventRepo } from "./event.js"
|
||||
|
||||
@@ -31,3 +32,4 @@ export const permsRepo = derived(tokenStore, ($token) => new PermsRepo($token))
|
||||
export const pageRepo = derived(tokenStore, ($token) => new PageRepo($token))
|
||||
export const dataRepo = derived(tokenStore, ($token) => new DataRepo($token))
|
||||
export const schemRepo = derived(tokenStore, ($token) => new SchematicRepo($token))
|
||||
export const statsRepo = derived(tokenStore, ($token) => new StatsRepo($token))
|
||||
|
||||
@@ -6,19 +6,19 @@ export class SchematicRepo {
|
||||
constructor(private token: string) {}
|
||||
|
||||
public async getRootSchematicList(): Promise<SchematicList> {
|
||||
return await fetchWithToken(this.token, "/schem").then(value => value.json()).then(value => SchematicListSchema.parse(value));
|
||||
return await fetchWithToken(this.token, "/schem").then(value => value.json()).then(SchematicListSchema.parse);
|
||||
}
|
||||
|
||||
public async getSchematicList(id: number): Promise<SchematicList> {
|
||||
return await fetchWithToken(this.token, `/schem/${id}/list`).then(value => value.json()).then(value => SchematicListSchema.parse(value));
|
||||
return await fetchWithToken(this.token, `/schem/${id}/list`).then(value => value.json()).then(SchematicListSchema.parse);
|
||||
}
|
||||
|
||||
public async getSchematicInfo(id: number): Promise<SchematicInfo> {
|
||||
return await fetchWithToken(this.token, `/schem/${id}`).then(value => value.json()).then(value => SchematicInfoSchema.parse(value));
|
||||
return await fetchWithToken(this.token, `/schem/${id}`).then(value => value.json()).then(SchematicInfoSchema.parse);
|
||||
}
|
||||
|
||||
public async createDownload(id: number): Promise<SchematicCode> {
|
||||
return await fetchWithToken(this.token, `/schem/${id}/download`).then(value => value.json()).then(value => SchematicCodeSchema.parse(value));
|
||||
return await fetchWithToken(this.token, `/schem/${id}/download`).then(value => value.json()).then(SchematicCodeSchema.parse);
|
||||
}
|
||||
|
||||
public async uploadSchematic(name: string, content: string) {
|
||||
|
||||
20
src/components/repo/stats.ts
Normal file
20
src/components/repo/stats.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type {FightStats, Ranking, UserStats} from "../types/stats.ts";
|
||||
import {fetchWithToken} from "./repo.ts";
|
||||
import {FightStatsSchema, RankingSchema, UserStatsSchema} from "../types/stats.ts";
|
||||
|
||||
export class StatsRepo {
|
||||
|
||||
constructor(private token: string) {}
|
||||
|
||||
public async getRankings(gamemode: string): Promise<Ranking> {
|
||||
return await fetchWithToken(this.token, `/stats/ranked/${gamemode}`).then(value => value.json()).then(RankingSchema.parse);
|
||||
}
|
||||
|
||||
public async getFightStats(): Promise<FightStats> {
|
||||
return await fetchWithToken(this.token, `/stats/fights`).then(value => value.json()).then(FightStatsSchema.parse);
|
||||
}
|
||||
|
||||
public async getUserStats(id: number): Promise<UserStats> {
|
||||
return await fetchWithToken(this.token, `/stats/user/${id}`).then(value => value.json()).then(UserStatsSchema.parse);
|
||||
}
|
||||
}
|
||||
26
src/components/types/stats.ts
Normal file
26
src/components/types/stats.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {z} from "zod";
|
||||
|
||||
export const RankingSchema = z.array(z.object({
|
||||
name: z.string(),
|
||||
elo: z.number(),
|
||||
}))
|
||||
|
||||
export type Ranking = z.infer<typeof RankingSchema>;
|
||||
|
||||
export const FightStatsSchema = z.array(z.object({
|
||||
date: z.string(),
|
||||
gamemode: z.string(),
|
||||
count: z.number(),
|
||||
}))
|
||||
|
||||
export type FightStats = z.infer<typeof FightStatsSchema>;
|
||||
|
||||
export const UserStatsSchema = z.object({
|
||||
eventFightParticipation: z.number(),
|
||||
eventParticipation: z.number(),
|
||||
acceptedSchematics: z.number(),
|
||||
fights: z.number(),
|
||||
playtime: z.number()
|
||||
})
|
||||
|
||||
export type UserStats = z.infer<typeof UserStatsSchema>;
|
||||
Reference in New Issue
Block a user