From 7d67ad0950ec057e7f535ee58faa46733167833d Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Thu, 8 May 2025 21:47:36 +0200 Subject: [PATCH 1/9] Refactor stores and types for improved data handling and schema definitions - Consolidated player fetching logic in stores.ts to utilize dataRepo. - Introduced teams fetching logic in stores.ts. - Updated permissions structure in stores.ts for better clarity. - Enhanced data schemas in data.ts with new ResponseUser and ResponseTeam schemas. - Expanded event-related schemas in event.ts to include groups, relations, and event creation/update structures. - Improved code formatting for consistency and readability across files. --- src/components/moderator/App.svelte | 18 +- .../moderator/layout/NavLinks.svelte | 24 +- .../moderator/pages/event/RefereesList.svelte | 16 +- .../moderator/pages/event/TeamTable.svelte | 54 +- .../moderator/pages/event/columns.ts | 2 +- src/components/repo/data.ts | 32 +- src/components/repo/event.ts | 192 +- src/components/repo/openapi.yaml | 2210 +++++++++++++++++ src/components/stores/stores.ts | 55 +- src/components/types/data.ts | 11 +- src/components/types/event.ts | 107 +- 11 files changed, 2604 insertions(+), 117 deletions(-) create mode 100644 src/components/repo/openapi.yaml diff --git a/src/components/moderator/App.svelte b/src/components/moderator/App.svelte index 386751f..5da559e 100644 --- a/src/components/moderator/App.svelte +++ b/src/components/moderator/App.svelte @@ -18,12 +18,12 @@ -->
- - SteamWar - + SteamWar
- navigate("/admin")} /> + navigate("/admin")} />
@@ -53,4 +51,4 @@
-
\ No newline at end of file +
diff --git a/src/components/moderator/layout/NavLinks.svelte b/src/components/moderator/layout/NavLinks.svelte index 2e4ff27..1b540e7 100644 --- a/src/components/moderator/layout/NavLinks.svelte +++ b/src/components/moderator/layout/NavLinks.svelte @@ -18,23 +18,13 @@ --> \ No newline at end of file + Dashboard + Events + Players + Pages + Schematics + diff --git a/src/components/moderator/pages/event/RefereesList.svelte b/src/components/moderator/pages/event/RefereesList.svelte index b4d9556..0da6088 100644 --- a/src/components/moderator/pages/event/RefereesList.svelte +++ b/src/components/moderator/pages/event/RefereesList.svelte @@ -28,22 +28,16 @@ const { event }: { event: ExtendedEvent } = $props(); - let referees = $state(event.event.referees); + let referees = $state(event.referees); async function addReferee(value: string) { - referees = ( - await $eventRepo.updateEvent(event.event.id.toString(), { - addReferee: [value], - }) - ).referees; + await $eventRepo.updateReferees(event.event.id.toString(), [value]); + referees = await $eventRepo.listReferees(event.event.id.toString()); } async function removeReferee(value: string) { - referees = ( - await $eventRepo.updateEvent(event.event.id.toString(), { - removeReferee: [value], - }) - ).referees; + await $eventRepo.deleteReferees(event.event.id.toString(), [value]); + referees = await $eventRepo.listReferees(event.event.id.toString()); } let playerSearch = $state(""); diff --git a/src/components/moderator/pages/event/TeamTable.svelte b/src/components/moderator/pages/event/TeamTable.svelte index 111be01..50f85a3 100644 --- a/src/components/moderator/pages/event/TeamTable.svelte +++ b/src/components/moderator/pages/event/TeamTable.svelte @@ -21,14 +21,29 @@ import { Button } from "@components/ui/button"; import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell, TableCaption } from "@components/ui/table"; import type { ExtendedEvent } from "@type/event.ts"; + import { eventRepo } from "@repo/event"; + import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover"; + import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command"; + import { teams } from "@components/stores/stores"; const { event }: { event: ExtendedEvent } = $props(); + + let team = $state(event.teams); + + async function addTeam(value: number) { + await $eventRepo.updateTeams(event.event.id.toString(), [value]); + team = await $eventRepo.listTeams(event.event.id.toString()); + } + + async function removeTeam(value: number) { + await $eventRepo.deleteTeams(event.event.id.toString(), [value]); + team = await $eventRepo.listTeams(event.event.id.toString()); + } + + let teamSearch = $state(""); - - - Team @@ -37,19 +52,42 @@ - {#each event.teams as team (team.id)} + {#each team as t (t.id)} - {team.kuerzel} - {team.name} + {t.kuerzel} + {t.name} - + {/each} - {#if event.teams.length === 0} + {#if team.length === 0} No teams available {/if} + + + + + + + + + + + No teams found :( + + {#each $teams + .filter((v) => v.name.includes(teamSearch)) + .filter((v) => !team.some((k) => k.id === v.id)) + .filter((v, i) => i < 50) as t (t.id)} + addTeam(t.id)} keywords={[t.name, t.kuerzel]}>{t.name} + {/each} + + + + +
diff --git a/src/components/moderator/pages/event/columns.ts b/src/components/moderator/pages/event/columns.ts index 527d2c5..6c074ce 100644 --- a/src/components/moderator/pages/event/columns.ts +++ b/src/components/moderator/pages/event/columns.ts @@ -63,7 +63,7 @@ export const columns: ColumnDef = [ }, { header: "Gruppe", - accessorKey: "group", + accessorKey: "group.name", id: "group", }, { diff --git a/src/components/repo/data.ts b/src/components/repo/data.ts index 04dea11..b6a87db 100644 --- a/src/components/repo/data.ts +++ b/src/components/repo/data.ts @@ -17,26 +17,38 @@ * along with this program. If not, see . */ -import type {Player, Server} from "@type/data.ts"; -import {PlayerSchema, ServerSchema} from "@type/data.ts"; -import {fetchWithToken, tokenStore} from "./repo.ts"; -import {derived, get} from "svelte/store"; +import type { Player, Server } from "@type/data.ts"; +import { PlayerSchema, ServerSchema } from "@type/data.ts"; +import { fetchWithToken, tokenStore } from "./repo.ts"; +import { derived, get } from "svelte/store"; +import { TeamSchema, type Team } from "@components/types/team.ts"; export class DataRepo { - constructor(private token: string) { - } + constructor(private token: string) {} public async getServer(): Promise { - return await fetchWithToken(this.token, "/data/server").then(value => value.json()).then(ServerSchema.parse); + return await fetchWithToken(this.token, "/data/server") + .then((value) => value.json()) + .then(ServerSchema.parse); } public async getMe(): Promise { - return await fetchWithToken(this.token, "/data/me").then(value => value.json()).then(PlayerSchema.parse); + return await fetchWithToken(this.token, "/data/me") + .then((value) => value.json()) + .then(PlayerSchema.parse); } public async getPlayers(): Promise { - return await fetchWithToken(get(tokenStore), "/data/admin/users").then(value => value.json()).then(PlayerSchema.array().parse); + return await fetchWithToken(get(tokenStore), "/data/admin/users") + .then((value) => value.json()) + .then(PlayerSchema.array().parse); + } + + public async getTeams(): Promise { + return await fetchWithToken(get(tokenStore), "/data/admin/teams") + .then((value) => value.json()) + .then(TeamSchema.array().parse); } } -export const dataRepo = derived(tokenStore, ($token) => new DataRepo($token)); \ No newline at end of file +export const dataRepo = derived(tokenStore, ($token) => new DataRepo($token)); diff --git a/src/components/repo/event.ts b/src/components/repo/event.ts index 5f4f1dd..18d36e7 100644 --- a/src/components/repo/event.ts +++ b/src/components/repo/event.ts @@ -17,12 +17,26 @@ * along with this program. If not, see . */ -import type {ExtendedEvent, ShortEvent, SWEvent} from "@type/event"; -import {fetchWithToken, tokenStore} from "./repo"; -import {ExtendedEventSchema, ShortEventSchema, SWEventSchema} from "@type/event.js"; -import {z} from "zod"; -import type {Dayjs} from "dayjs"; -import {derived} from "svelte/store"; +import type { ExtendedEvent, ShortEvent, SWEvent, EventFight, ResponseGroups, ResponseRelation, ResponseTeam } from "@type/event"; +import { fetchWithToken, tokenStore } from "./repo"; +import { + ExtendedEventSchema, + ShortEventSchema, + SWEventSchema, + EventFightSchema, + ResponseGroupsSchema, + ResponseRelationSchema, + ResponseTeamSchema, + CreateEventGroupSchema, + UpdateEventGroupSchema, + CreateEventRelationSchema, + UpdateEventRelationSchema, +} from "@type/event.js"; +import type { CreateEventGroup, UpdateEventGroup, CreateEventRelation, UpdateEventRelation } from "@type/event.js"; +import { z } from "zod"; +import type { Dayjs } from "dayjs"; +import { derived } from "svelte/store"; +import { ResponseUserSchema } from "@components/types/data"; export interface CreateEvent { name: string; @@ -42,19 +56,25 @@ export interface UpdateEvent { removeReferee?: string[] | null; } +export interface ResponseUser { + name: string; + uuid: string; + prefix: string; + perms: string[]; +} + export class EventRepo { - constructor(private token: string) { - } + constructor(private token: string) {} public async listEvents(): Promise { return await fetchWithToken(this.token, "/events") - .then(value => value.json()) - .then(value => z.array(ShortEventSchema).parse(value)); + .then((value) => value.json()) + .then((value) => z.array(ShortEventSchema).parse(value)); } public async getEvent(id: string): Promise { return await fetchWithToken(this.token, `/events/${id}`) - .then(value => value.json()) + .then((value) => value.json()) .then(ExtendedEventSchema.parse); } @@ -66,7 +86,8 @@ export class EventRepo { start: +event.start, end: +event.end, }), - }).then(value => value.json()) + }) + .then((value) => value.json()) .then(SWEventSchema.parse); } @@ -87,7 +108,8 @@ export class EventRepo { headers: { "Content-Type": "application/json", }, - }).then(value => value.json()) + }) + .then((value) => value.json()) .then(SWEventSchema.parse); } @@ -98,6 +120,150 @@ export class EventRepo { return res.ok; } + + // Fights + public async listFights(eventId: string): Promise { + return await fetchWithToken(this.token, `/events/${eventId}/fights`) + .then((value) => value.json()) + .then((value) => z.array(EventFightSchema).parse(value)); + } + public async createFight(eventId: string, fight: any): Promise { + return await fetchWithToken(this.token, `/events/${eventId}/fights`, { + method: "POST", + body: JSON.stringify(fight), + headers: { "Content-Type": "application/json" }, + }) + .then((value) => value.json()) + .then(EventFightSchema.parse); + } + public async deleteFight(eventId: string, fightId: string): Promise { + const res = await fetchWithToken(this.token, `/events/${eventId}/fights/${fightId}`, { + method: "DELETE", + }); + return res.ok; + } + + // Groups + public async listGroups(eventId: string): Promise { + return await fetchWithToken(this.token, `/events/${eventId}/groups`) + .then((value) => value.json()) + .then((value) => z.array(ResponseGroupsSchema).parse(value)); + } + public async createGroup(eventId: string, group: CreateEventGroup): Promise { + CreateEventGroupSchema.parse(group); + return await fetchWithToken(this.token, `/events/${eventId}/groups`, { + method: "POST", + body: JSON.stringify(group), + headers: { "Content-Type": "application/json" }, + }) + .then((value) => value.json()) + .then(ResponseGroupsSchema.parse); + } + public async getGroup(eventId: string, groupId: string): Promise { + return await fetchWithToken(this.token, `/events/${eventId}/groups/${groupId}`) + .then((value) => value.json()) + .then(ResponseGroupsSchema.parse); + } + public async updateGroup(eventId: string, groupId: string, group: UpdateEventGroup): Promise { + UpdateEventGroupSchema.parse(group); + return await fetchWithToken(this.token, `/events/${eventId}/groups/${groupId}`, { + method: "PUT", + body: JSON.stringify(group), + headers: { "Content-Type": "application/json" }, + }) + .then((value) => value.json()) + .then(ResponseGroupsSchema.parse); + } + public async deleteGroup(eventId: string, groupId: string): Promise { + const res = await fetchWithToken(this.token, `/events/${eventId}/groups/${groupId}`, { + method: "DELETE", + }); + return res.ok; + } + + // Relations + public async listRelations(eventId: string): Promise { + return await fetchWithToken(this.token, `/events/${eventId}/relations`) + .then((value) => value.json()) + .then((value) => z.array(ResponseRelationSchema).parse(value)); + } + public async createRelation(eventId: string, relation: CreateEventRelation): Promise { + CreateEventRelationSchema.parse(relation); + return await fetchWithToken(this.token, `/events/${eventId}/relations`, { + method: "POST", + body: JSON.stringify(relation), + headers: { "Content-Type": "application/json" }, + }) + .then((value) => value.json()) + .then(ResponseRelationSchema.parse); + } + public async getRelation(eventId: string, relationId: string): Promise { + return await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`) + .then((value) => value.json()) + .then(ResponseRelationSchema.parse); + } + public async updateRelation(eventId: string, relationId: string, relation: UpdateEventRelation): Promise { + UpdateEventRelationSchema.parse(relation); + return await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`, { + method: "PUT", + body: JSON.stringify(relation), + headers: { "Content-Type": "application/json" }, + }) + .then((value) => value.json()) + .then(ResponseRelationSchema.parse); + } + public async deleteRelation(eventId: string, relationId: string): Promise { + const res = await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`, { + method: "DELETE", + }); + return res.ok; + } + + // Teams + public async listTeams(eventId: string): Promise { + return await fetchWithToken(this.token, `/events/${eventId}/teams`) + .then((value) => value.json()) + .then((value) => z.array(ResponseTeamSchema).parse(value)); + } + public async updateTeams(eventId: string, teams: number[]): Promise { + const res = await fetchWithToken(this.token, `/events/${eventId}/teams`, { + method: "PUT", + body: JSON.stringify(teams), + headers: { "Content-Type": "application/json" }, + }); + return res.ok; + } + public async deleteTeams(eventId: string, teams: number[]): Promise { + const res = await fetchWithToken(this.token, `/events/${eventId}/teams`, { + method: "DELETE", + body: JSON.stringify(teams), + headers: { "Content-Type": "application/json" }, + }); + return res.ok; + } + + // Referees + public async listReferees(eventId: string): Promise { + return await fetchWithToken(this.token, `/events/${eventId}/referees`) + .then((value) => value.json()) + .then((value) => z.array(ResponseUserSchema).parse(value)); + } + public async updateReferees(eventId: string, refereeUuids: string[]): Promise { + const res = await fetchWithToken(this.token, `/events/${eventId}/referees`, { + method: "PUT", + body: JSON.stringify(refereeUuids), + headers: { "Content-Type": "application/json" }, + }); + return res.status === 204; + } + public async deleteReferees(eventId: string, refereeUuids: string[]): Promise { + const res = await fetchWithToken(this.token, `/events/${eventId}/referees`, { + method: "DELETE", + body: JSON.stringify(refereeUuids), + headers: { "Content-Type": "application/json" }, + }); + return res.status === 204; + } } export const eventRepo = derived(tokenStore, ($token) => new EventRepo($token)); diff --git a/src/components/repo/openapi.yaml b/src/components/repo/openapi.yaml new file mode 100644 index 0000000..7ebfe22 --- /dev/null +++ b/src/components/repo/openapi.yaml @@ -0,0 +1,2210 @@ +openapi: "3.1.0" +info: + title: "SteamWar API" + description: "SteamWar API" + version: "1.0.0" +servers: + - url: "https://SteamWar" +paths: + /auth: + delete: + description: "" + responses: + "200": + description: "OK" + content: + "*/*": + schema: + type: "object" + post: + description: "" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UsernamePassword" + required: true + responses: + "403": + description: "Forbidden" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid username or password" + code: "invalid" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/AuthTokenResponse" + put: + description: "" + responses: + "403": + description: "Forbidden" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid token type" + code: "invalid" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/AuthTokenResponse" + /data/admin/gamemodes: + get: + description: "" + responses: + "200": + description: "OK" + content: + "*/*": + schema: + type: "array" + items: + type: "string" + /data/admin/gamemodes/{gamemode}/maps: + get: + description: "" + parameters: + - name: "gamemode" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid gamemode" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Gamemode not found" + code: "null" + "200": + description: "OK" + content: + "*/*": + schema: + type: "array" + items: + type: "string" + /data/admin/schematicTypes: + get: + description: "" + responses: + "200": + description: "OK" + content: + "*/*": + schema: + type: "array" + items: + $ref: "#/components/schemas/ResponseSchematicType" + /data/admin/users: + get: + description: "" + responses: + "200": + description: "OK" + content: + "*/*": + schema: + type: "array" + items: + $ref: "#/components/schemas/ResponseUser" + /data/me: + get: + description: "" + responses: + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseUser" + /data/server: + get: + description: "" + responses: + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/StatusResponse" + "500": + description: "Internal Server Error" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + /data/skin/{uuid}: + get: + description: "" + parameters: + - name: "uuid" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid UUID" + code: "null" + "200": + description: "OK
A file response" + content: + application/*: + schema: + type: "object" + format: "binary" + /data/team: + get: + description: "" + responses: + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/Map_String" + /download/{code}: + get: + description: "" + parameters: + - name: "code" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "object" + "410": + description: "Gone" + content: + "*/*": + schema: + type: "object" + "403": + description: "Forbidden" + content: + "*/*": + schema: + type: "object" + "500": + description: "Internal Server Error" + content: + "*/*": + schema: + type: "object" + "200": + description: "OK" + content: + application/octet-stream: + schema: + type: "string" + format: "byte" + /download/{code}/info: + get: + description: "" + parameters: + - name: "code" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "object" + "410": + description: "Gone" + content: + "*/*": + schema: + type: "object" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseSchematic" + /events: + get: + description: "" + responses: + "200": + description: "OK" + content: + "*/*": + schema: + type: "array" + items: + $ref: "#/components/schemas/ShortEvent" + post: + description: "" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid body" + code: "null" + "201": + description: "Created" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseEvent" + /events/{id}: + delete: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "integer" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Event not found" + code: "null" + "204": + description: "No Content" + content: + "*/*": + schema: + type: "object" + get: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid event ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Event not found" + code: "null" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/ExtendedResponseEvent" + put: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid body" + code: "null" + Example#2: + description: "" + value: + error: "Invalid event ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Event not found" + code: "null" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseEvent" + /events/{id}/csv: + get: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid event ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Event not found" + code: "null" + "200": + description: "OK" + content: + text/plain: + schema: + type: "string" + /events/{id}/fights: + get: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid event ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Event not found" + code: "null" + "200": + description: "OK" + content: + "*/*": + schema: + type: "array" + items: + $ref: "#/components/schemas/ResponseEventFight" + post: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid body" + code: "null" + "201": + description: "Created" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseEventFight" + /events/{id}/fights/{fight}: + delete: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + - name: "fight" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid fight ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Fight not found" + code: "null" + "200": + description: "OK" + content: + "*/*": + schema: + type: "object" + put: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + - name: "fight" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid body" + code: "null" + Example#2: + description: "" + value: + error: "Invalid fight ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Fight not found" + code: "null" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseEventFight" + /events/{id}/groups: + get: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid event ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Event not found" + code: "null" + "200": + description: "OK" + content: + "*/*": + schema: + type: "array" + items: + $ref: "#/components/schemas/ResponseGroups" + post: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateEventGroup" + required: true + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid event ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Event not found" + code: "null" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseGroups" + /events/{id}/groups/{group}: + delete: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + - name: "group" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "object" + "204": + description: "No Content" + content: + "*/*": + schema: + type: "object" + get: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + - name: "group" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "object" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseGroups" + put: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + - name: "group" + in: "path" + required: true + schema: + type: "string" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateEventGroup" + required: true + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "object" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseGroups" + /events/{id}/relations: + get: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid event ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Event not found" + code: "null" + "200": + description: "OK" + content: + "*/*": + schema: + type: "array" + items: + $ref: "#/components/schemas/ResponseRelation" + post: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateEventRelation" + required: true + responses: + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "object" + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseRelation" + /events/{id}/relations/{relation}: + delete: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + - name: "relation" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "object" + "204": + description: "No Content" + content: + "*/*": + schema: + type: "object" + get: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + - name: "relation" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "object" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseRelation" + put: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + - name: "relation" + in: "path" + required: true + schema: + type: "string" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateEventRelation" + required: true + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "object" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseRelation" + /events/{id}/teams: + delete: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + requestBody: + content: + application/json: + schema: + type: "array" + items: + type: "integer" + format: "int32" + required: true + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid event ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Event not found" + code: "null" + "204": + description: "No Content" + content: + "*/*": + schema: + type: "object" + get: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid event ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Event not found" + code: "null" + "200": + description: "OK" + content: + "*/*": + schema: + type: "array" + items: + $ref: "#/components/schemas/ResponseTeam" + put: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + requestBody: + content: + application/json: + schema: + type: "array" + items: + type: "integer" + format: "int32" + required: true + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Invalid event ID" + code: "null" + "404": + description: "Not Found" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "Event not found" + code: "null" + "204": + description: "No Content" + content: + "*/*": + schema: + type: "object" + /page: + get: + description: "" + parameters: + - name: "branch" + in: "query" + required: false + schema: + type: "string" + responses: + "200": + description: "OK" + content: + "*/*": + schema: + type: "array" + items: + $ref: "#/components/schemas/PageResponseList" + examples: + Example#1: + description: "" + value: [] + post: + description: "" + parameters: + - name: "branch" + in: "query" + required: false + schema: + type: "string" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreatePageRequest" + required: true + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "string" + examples: + Example#1: + value: "Invalid path" + "200": + description: "" + content: + "*/*": + schema: + type: "object" + /page/{id}: + delete: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "integer" + - name: "branch" + in: "query" + required: false + schema: + type: "string" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/DeletePageRequest" + required: true + responses: + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "string" + examples: + Example#1: + value: "Page not found" + "200": + description: "" + content: + "*/*": + schema: + type: "object" + get: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "integer" + - name: "branch" + in: "query" + required: false + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "string" + examples: + Example#1: + value: "Invalid id" + Example#2: + value: "Invalid id" + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "string" + examples: + Example#1: + value: "Page not found" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/PageResponse" + put: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "integer" + - name: "branch" + in: "query" + required: false + schema: + type: "string" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UpdatePageRequest" + required: true + responses: + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "string" + examples: + Example#1: + value: "Page not found" + "200": + description: "" + content: + "*/*": + schema: + type: "object" + /page/branch: + delete: + description: "" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateBranchRequest" + required: true + responses: + "200": + description: "" + content: + "*/*": + schema: + type: "object" + get: + description: "" + responses: + "200": + description: "" + content: + "*/*": + schema: + type: "array" + items: + type: "string" + post: + description: "" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateBranchRequest" + required: true + responses: + "200": + description: "" + content: + "*/*": + schema: + type: "object" + /perms: + get: + description: "" + responses: + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/RespondUserPerms" + examples: + Example#1: + description: "" + value: + prefixes: "null" + perms: [] + /perms/user/{id}: + get: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/RespondUserPermsPrefix" + examples: + Example#1: + description: "" + value: + prefix: "null" + perms: [] + /perms/user/{id}/{perm}: + delete: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + - name: "perm" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "202": + description: "Accepted" + content: + "*/*": + schema: + type: "object" + "204": + description: "No Content" + content: + "*/*": + schema: + type: "object" + put: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + - name: "perm" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "202": + description: "Accepted" + content: + "*/*": + schema: + type: "object" + "204": + description: "No Content" + content: + "*/*": + schema: + type: "object" + /perms/user/{id}/prefix/{prefix}: + put: + description: "" + parameters: + - name: "id" + in: "path" + required: true + schema: + type: "string" + - name: "prefix" + in: "path" + required: true + schema: + type: "string" + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + type: "object" + "202": + description: "Accepted" + content: + "*/*": + schema: + type: "object" + /schem: + post: + description: "" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UploadSchematic" + required: true + responses: + "400": + description: "Bad Request" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseError" + examples: + Example#1: + description: "" + value: + error: "null" + code: "UPLOAD_ERROR" + Example#2: + description: "" + value: + error: "INVALID_SUFFIX" + code: "null" + Example#3: + description: "" + value: + error: "INVALID_NAME" + code: "null" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/ResponseSchematic" + /stats/fights: + get: + description: "" + responses: + "200": + description: "OK" + content: + "*/*": + schema: {} + /stats/ranked/{gamemode}: + get: + description: "" + parameters: + - name: "gamemode" + in: "path" + required: true + schema: + type: "string" + responses: + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "object" + "200": + description: "OK" + content: + "*/*": + schema: {} + /stats/user: + get: + description: "" + responses: + "404": + description: "Not Found" + content: + "*/*": + schema: + type: "string" + examples: + Example#1: + value: "User not found" + "200": + description: "OK" + content: + "*/*": + schema: + $ref: "#/components/schemas/UserStats" +components: + schemas: + UsernamePassword: + type: "object" + properties: + name: + type: "string" + password: + type: "string" + keepLoggedIn: + type: "boolean" + required: + - "name" + - "password" + - "keepLoggedIn" + ResponseError: + type: "object" + properties: + error: + type: "string" + code: + type: "string" + required: + - "error" + - "code" + ResponseToken: + type: "object" + properties: + token: + type: "string" + expires: + type: "string" + required: + - "token" + - "expires" + AuthTokenResponse: + type: "object" + properties: + accessToken: + $ref: "#/components/schemas/ResponseToken" + refreshToken: + nullable: true + $ref: "#/components/schemas/ResponseToken" + required: + - "accessToken" + ResponseSchematicType: + type: "object" + properties: + name: + type: "string" + db: + type: "string" + required: + - "name" + - "db" + ResponseUser: + type: "object" + properties: + name: + type: "string" + uuid: + type: "string" + prefix: + type: "string" + perms: + type: "array" + items: + type: "string" + required: + - "name" + - "uuid" + - "prefix" + - "perms" + JsonElement: + type: "object" + properties: {} + Player: + type: "object" + properties: + name: + type: "string" + id: + type: "string" + required: + - "name" + - "id" + Players: + type: "object" + properties: + max: + type: "integer" + format: "int32" + online: + type: "integer" + format: "int32" + sample: + type: "array" + items: + $ref: "#/components/schemas/Player" + required: + - "max" + - "online" + - "sample" + Version: + type: "object" + properties: + name: + type: "string" + protocol: + type: "integer" + format: "int32" + required: + - "name" + - "protocol" + StatusResponse: + type: "object" + properties: + description: + $ref: "#/components/schemas/JsonElement" + players: + $ref: "#/components/schemas/Players" + version: + $ref: "#/components/schemas/Version" + favicon: + type: "string" + time: + type: "integer" + format: "int32" + nullable: true + default: "0" + required: + - "description" + - "players" + - "version" + - "favicon" + Map: + type: "object" + properties: {} + Map_String: + type: "string" + ResponseSchematic: + type: "object" + properties: + name: + type: "string" + id: + type: "integer" + format: "int32" + type: + type: "string" + nullable: true + owner: + type: "integer" + format: "int32" + item: + type: "string" + lastUpdate: + type: "integer" + format: "int64" + rank: + type: "integer" + format: "int32" + replaceColor: + type: "boolean" + allowReplay: + type: "boolean" + required: + - "name" + - "id" + - "owner" + - "item" + - "lastUpdate" + - "rank" + - "replaceColor" + - "allowReplay" + ShortEvent: + type: "object" + properties: + id: + type: "integer" + format: "int32" + name: + type: "string" + start: + type: "integer" + format: "int64" + end: + type: "integer" + format: "int64" + required: + - "id" + - "name" + - "start" + - "end" + ResponseEvent: + type: "object" + properties: + id: + type: "integer" + format: "int32" + name: + type: "string" + deadline: + type: "integer" + format: "int64" + start: + type: "integer" + format: "int64" + end: + type: "integer" + format: "int64" + maxTeamMembers: + type: "integer" + format: "int32" + schemType: + type: "string" + nullable: true + publicSchemsOnly: + type: "boolean" + required: + - "id" + - "name" + - "deadline" + - "start" + - "end" + - "maxTeamMembers" + - "publicSchemsOnly" + ResponseTeam: + type: "object" + properties: + id: + type: "integer" + format: "int32" + name: + type: "string" + kuerzel: + type: "string" + color: + type: "string" + required: + - "id" + - "name" + - "kuerzel" + - "color" + ResponseGroups: + type: "object" + properties: + id: + type: "integer" + format: "int32" + name: + type: "string" + pointsPerWin: + type: "integer" + format: "int32" + pointsPerLoss: + type: "integer" + format: "int32" + pointsPerDraw: + type: "integer" + format: "int32" + type: + type: "string" + enum: + - "GROUP_STAGE" + - "ELIMINATION_STAGE" + points: + type: "integer" + format: "int32" + required: + - "id" + - "name" + - "pointsPerWin" + - "pointsPerLoss" + - "pointsPerDraw" + - "type" + - "points" + ResponseEventFight: + type: "object" + properties: + id: + type: "integer" + format: "int32" + spielmodus: + type: "string" + map: + type: "string" + blueTeam: + $ref: "#/components/schemas/ResponseTeam" + redTeam: + $ref: "#/components/schemas/ResponseTeam" + start: + type: "integer" + format: "int64" + ergebnis: + type: "integer" + format: "int32" + spectatePort: + type: "integer" + format: "int32" + nullable: true + group: + nullable: true + $ref: "#/components/schemas/ResponseGroups" + required: + - "id" + - "spielmodus" + - "map" + - "blueTeam" + - "redTeam" + - "start" + - "ergebnis" + ResponseRelation: + type: "object" + properties: + id: + type: "integer" + format: "int32" + fight: + $ref: "#/components/schemas/ResponseEventFight" + type: + type: "string" + enum: + - "FIGHT" + - "GROUP" + fromFight: + nullable: true + $ref: "#/components/schemas/ResponseEventFight" + fromGroup: + nullable: true + $ref: "#/components/schemas/ResponseGroups" + fromPlace: + type: "integer" + format: "int32" + required: + - "id" + - "fight" + - "type" + - "fromPlace" + ExtendedResponseEvent: + type: "object" + properties: + event: + $ref: "#/components/schemas/ResponseEvent" + teams: + type: "array" + items: + $ref: "#/components/schemas/ResponseTeam" + groups: + type: "array" + items: + $ref: "#/components/schemas/ResponseGroups" + fights: + type: "array" + items: + $ref: "#/components/schemas/ResponseEventFight" + referees: + type: "array" + items: + $ref: "#/components/schemas/ResponseUser" + relations: + type: "array" + items: + $ref: "#/components/schemas/ResponseRelation" + required: + - "event" + - "teams" + - "groups" + - "fights" + - "referees" + - "relations" + CreateEventGroup: + type: "object" + properties: + name: + type: "string" + type: + type: "string" + enum: + - "GROUP_STAGE" + - "ELIMINATION_STAGE" + required: + - "name" + - "type" + UpdateEventGroup: + type: "object" + properties: + name: + type: "string" + nullable: true + type: + type: "string" + nullable: true + enum: + - "GROUP_STAGE" + - "ELIMINATION_STAGE" + pointsPerWin: + type: "integer" + format: "int32" + nullable: true + pointsPerLoss: + type: "integer" + format: "int32" + nullable: true + pointsPerDraw: + type: "integer" + format: "int32" + nullable: true + CreateEventRelation: + type: "object" + properties: + fightId: + type: "integer" + format: "int32" + team: + type: "string" + enum: + - "RED" + - "BLUE" + fromType: + type: "string" + enum: + - "FIGHT" + - "GROUP" + fromId: + type: "integer" + format: "int32" + fromPlace: + type: "integer" + format: "int32" + required: + - "fightId" + - "team" + - "fromType" + - "fromId" + - "fromPlace" + UpdateFromRelation: + type: "object" + properties: + fromType: + type: "string" + enum: + - "FIGHT" + - "GROUP" + fromId: + type: "integer" + format: "int32" + fromPlace: + type: "integer" + format: "int32" + required: + - "fromType" + - "fromId" + - "fromPlace" + UpdateEventRelation: + type: "object" + properties: + team: + type: "string" + nullable: true + enum: + - "RED" + - "BLUE" + from: + nullable: true + $ref: "#/components/schemas/UpdateFromRelation" + PageResponseList: + type: "object" + properties: + path: + type: "string" + name: + type: "string" + sha: + type: "string" + downloadUrl: + type: "string" + id: + type: "integer" + format: "int32" + required: + - "path" + - "name" + - "sha" + - "downloadUrl" + - "id" + CreatePageRequest: + type: "object" + properties: + path: + type: "string" + slug: + type: "string" + nullable: true + title: + type: "string" + nullable: true + required: + - "path" + DeletePageRequest: + type: "object" + properties: + sha: + type: "string" + message: + type: "string" + required: + - "sha" + - "message" + PageResponse: + type: "object" + properties: + path: + type: "string" + name: + type: "string" + sha: + type: "string" + downloadUrl: + type: "string" + content: + type: "string" + size: + type: "integer" + format: "int32" + id: + type: "integer" + format: "int32" + required: + - "path" + - "name" + - "sha" + - "downloadUrl" + - "content" + - "size" + - "id" + UpdatePageRequest: + type: "object" + properties: + content: + type: "string" + sha: + type: "string" + message: + type: "string" + required: + - "content" + - "sha" + - "message" + CreateBranchRequest: + type: "object" + properties: + branch: + type: "string" + required: + - "branch" + RespondUserPerms: + type: "object" + properties: + prefixes: + type: "string" + perms: + type: "array" + items: + type: "string" + required: + - "prefixes" + - "perms" + RespondPrefix: + type: "object" + properties: + name: + type: "string" + colorCode: + type: "string" + chatPrefix: + type: "string" + required: + - "name" + - "colorCode" + - "chatPrefix" + RespondUserPermsPrefix: + type: "object" + properties: + prefix: + $ref: "#/components/schemas/RespondPrefix" + perms: + type: "array" + items: + type: "string" + required: + - "prefix" + - "perms" + UploadSchematic: + type: "object" + properties: + name: + type: "string" + content: + type: "string" + required: + - "name" + - "content" + UserStats: + type: "object" + properties: + eventFightParticipation: + type: "integer" + format: "int32" + eventParticipation: + type: "integer" + format: "int32" + acceptedSchematics: + type: "integer" + format: "int32" + fights: + type: "integer" + format: "int32" + playtime: + type: "number" + format: "double" + required: + - "eventFightParticipation" + - "eventParticipation" + - "acceptedSchematics" + - "fights" + - "playtime" diff --git a/src/components/stores/stores.ts b/src/components/stores/stores.ts index f1d53f0..a44b7c7 100644 --- a/src/components/stores/stores.ts +++ b/src/components/stores/stores.ts @@ -17,41 +17,45 @@ * along with this program. If not, see . */ -import type {Player, SchematicType} from "@type/data"; -import {PlayerSchema} from "@type/data.ts"; -import {cached, cachedFamily} from "./cached"; -import type {Team} from "@type/team.ts"; -import {TeamSchema} from "@type/team"; -import {derived, get, writable} from "svelte/store"; -import {z} from "zod"; -import {fetchWithToken, tokenStore} from "@repo/repo.ts"; -import {pageRepo} from "@repo/page.ts"; -import {dataRepo} from "@repo/data.ts"; -import {permsRepo} from "@repo/perms.ts"; +import type { Player, SchematicType } from "@type/data"; +import { PlayerSchema } from "@type/data.ts"; +import { cached, cachedFamily } from "./cached"; +import type { Team } from "@type/team.ts"; +import { TeamSchema } from "@type/team"; +import { derived, get, writable } from "svelte/store"; +import { z } from "zod"; +import { fetchWithToken, tokenStore } from "@repo/repo.ts"; +import { pageRepo } from "@repo/page.ts"; +import { dataRepo } from "@repo/data.ts"; +import { permsRepo } from "@repo/perms.ts"; -export const schemTypes = cached([], () => - fetchWithToken(get(tokenStore), "/data/admin/schematicTypes") - .then(res => res.json())); +export const schemTypes = cached([], () => fetchWithToken(get(tokenStore), "/data/admin/schematicTypes").then((res) => res.json())); export const players = cached([], async () => { - const res = await fetchWithToken(get(tokenStore), "/data/admin/users"); - return z.array(PlayerSchema).parse(await res.json()); + return get(dataRepo).getPlayers(); }); -export const permissions = cached({ - perms: [], - prefixes: {}, -}, async () => { - return get(permsRepo).listPerms(); +export const teams = cached([], async () => { + return get(dataRepo).getTeams(); }); +export const permissions = cached( + { + perms: [], + prefixes: {}, + }, + async () => { + return get(permsRepo).listPerms(); + } +); + export const gamemodes = cached([], async () => { const res = await fetchWithToken(get(tokenStore), "/data/admin/gamemodes"); return z.array(z.string()).parse(await res.json()); }); export const maps = cachedFamily([], async (gamemode) => { - if (get(gamemodes).every(value => value !== gamemode)) return []; + if (get(gamemodes).every((value) => value !== gamemode)) return []; const res = await fetchWithToken(get(tokenStore), `/data/admin/gamemodes/${gamemode}/maps`); if (!res.ok) { @@ -66,17 +70,12 @@ export const groups = cached([], async () => { return z.array(z.string()).parse(await res.json()); }); -export const teams = cached([], async () => { - const res = await fetchWithToken(get(tokenStore), "/team"); - return z.array(TeamSchema).parse(await res.json()); -}); - export const branches = cached([], async () => { const res = await get(pageRepo).getBranches(); return z.array(z.string()).parse(res); }); -export const server = derived(dataRepo, $dataRepo => $dataRepo.getServer()); +export const server = derived(dataRepo, ($dataRepo) => $dataRepo.getServer()); export const isWide = writable(typeof window !== "undefined" && window.innerWidth >= 640); diff --git a/src/components/types/data.ts b/src/components/types/data.ts index cc97a67..2098b68 100644 --- a/src/components/types/data.ts +++ b/src/components/types/data.ts @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -import {z} from "zod"; +import { z } from "zod"; export const SchematicTypeSchema = z.object({ name: z.string(), @@ -57,3 +57,12 @@ export const ResponseErrorSchema = z.object({ }); export type ResponseError = z.infer; + +export const ResponseUserSchema = z.object({ + name: z.string(), + uuid: z.string(), + prefix: z.string(), + perms: z.array(z.string()), +}); + +export type ResponseUser = z.infer; diff --git a/src/components/types/event.ts b/src/components/types/event.ts index 68c2cac..e3a9b84 100644 --- a/src/components/types/event.ts +++ b/src/components/types/event.ts @@ -17,9 +17,46 @@ * along with this program. If not, see . */ -import {z} from "zod"; -import {TeamSchema} from "./team.js"; -import {PlayerSchema} from "./data.js"; +import { z } from "zod"; +import { TeamSchema } from "./team.js"; +import { PlayerSchema, ResponseUserSchema } from "./data.js"; + +export const ResponseGroupsSchema = z.object({ + id: z.number(), + name: z.string(), + pointsPerWin: z.number(), + pointsPerLoss: z.number(), + pointsPerDraw: z.number(), + type: z.enum(["GROUP_STAGE", "ELIMINATION_STAGE"]), + points: z.record(z.string(), z.number()).nullable(), +}); + +export const EventFightSchema = z.object({ + id: z.number(), + spielmodus: z.string(), + map: z.string(), + blueTeam: TeamSchema, + redTeam: TeamSchema, + start: z.number(), + ergebnis: z.number(), + spectatePort: z.number().nullable(), + group: ResponseGroupsSchema.nullable(), +}); + +export type EventFight = z.infer; + +export type ResponseGroups = z.infer; + +export const ResponseRelationSchema = z.object({ + id: z.number(), + fight: EventFightSchema, + type: z.enum(["FIGHT", "GROUP"]), + fromFight: EventFightSchema.nullable(), + fromGroup: ResponseGroupsSchema.nullable(), + fromPlace: z.number(), +}); + +export type ResponseRelation = z.infer; export const ShortEventSchema = z.object({ id: z.number(), @@ -35,29 +72,63 @@ export const SWEventSchema = ShortEventSchema.extend({ maxTeamMembers: z.number(), schemType: z.string().nullable(), publicSchemsOnly: z.boolean(), - referees: z.array(PlayerSchema), }); export type SWEvent = z.infer; -export const EventFightSchema = z.object({ - id: z.number(), - spielmodus: z.string(), - map: z.string(), - blueTeam: TeamSchema, - redTeam: TeamSchema, - start: z.number(), - ergebnis: z.number(), - spectatePort: z.number().nullable(), - group: z.string().nullable(), -}); - -export type EventFight = z.infer; - export const ExtendedEventSchema = z.object({ event: SWEventSchema, teams: z.array(TeamSchema), + groups: z.array(ResponseGroupsSchema), fights: z.array(EventFightSchema), + referees: z.array(ResponseUserSchema), + relations: z.array(ResponseRelationSchema), }); export type ExtendedEvent = z.infer; + +export const ResponseTeamSchema = z.object({ + id: z.number(), + name: z.string(), + kuerzel: z.string(), + color: z.string(), +}); + +export type ResponseTeam = z.infer; + +export const CreateEventGroupSchema = z.object({ + name: z.string(), + type: z.enum(["GROUP_STAGE", "ELIMINATION_STAGE"]), +}); +export type CreateEventGroup = z.infer; + +export const UpdateEventGroupSchema = z.object({ + name: z.string().nullable().optional(), + type: z.enum(["GROUP_STAGE", "ELIMINATION_STAGE"]).nullable().optional(), + pointsPerWin: z.number().nullable().optional(), + pointsPerLoss: z.number().nullable().optional(), + pointsPerDraw: z.number().nullable().optional(), +}); +export type UpdateEventGroup = z.infer; + +export const CreateEventRelationSchema = z.object({ + fightId: z.number(), + team: z.enum(["RED", "BLUE"]), + fromType: z.enum(["FIGHT", "GROUP"]), + fromId: z.number(), + fromPlace: z.number(), +}); +export type CreateEventRelation = z.infer; + +export const UpdateFromRelationSchema = z.object({ + fromType: z.enum(["FIGHT", "GROUP"]), + fromId: z.number(), + fromPlace: z.number(), +}); +export type UpdateFromRelation = z.infer; + +export const UpdateEventRelationSchema = z.object({ + team: z.enum(["RED", "BLUE"]).nullable().optional(), + from: UpdateFromRelationSchema.nullable().optional(), +}); +export type UpdateEventRelation = z.infer; From 1da279bb249dced27c1ab00eb68a91d548f5be56 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sat, 10 May 2025 22:22:12 +0200 Subject: [PATCH 2/9] feat: Add FightEdit and GroupEdit components for enhanced event management --- .../moderator/components/FightEdit.svelte | 219 ++++++++++++++++++ .../moderator/pages/event/EventEdit.svelte | 9 + .../pages/event/EventFightList.svelte | 20 +- .../moderator/pages/event/EventView.svelte | 6 +- .../moderator/pages/event/FightEditRow.svelte | 41 ++++ .../moderator/pages/event/GroupEditRow.svelte | 16 ++ .../moderator/pages/event/TeamTable.svelte | 4 +- 7 files changed, 308 insertions(+), 7 deletions(-) create mode 100644 src/components/moderator/components/FightEdit.svelte create mode 100644 src/components/moderator/pages/event/FightEditRow.svelte create mode 100644 src/components/moderator/pages/event/GroupEditRow.svelte diff --git a/src/components/moderator/components/FightEdit.svelte b/src/components/moderator/components/FightEdit.svelte new file mode 100644 index 0000000..4d0e5a0 --- /dev/null +++ b/src/components/moderator/components/FightEdit.svelte @@ -0,0 +1,219 @@ + + +
+ + + + {#snippet child({ props })} + + {/snippet} + + + + + + No fight modus found. + + {#each $gamemodes as modus} + { + fightModus = modus; + }} + > + + {modus} + + {/each} + + + + + + + + + {#snippet child({ props })} + + {/snippet} + + + + + + No map found. + + {#each $mapsStore as map} + { + fightMap = map; + }} + > + + {map} + + {/each} + + + + + + + + + {#snippet child({ props })} + + {/snippet} + + + + + + No map found. + + {#each teams as team} + { + fightBlueTeam = team; + }} + > + + {team.name} + + {/each} + + + + + + + + + {#snippet child({ props })} + + {/snippet} + + + + + + No map found. + + {#each teams as team} + { + fightRedTeam = team; + }} + > + + {team.name} + + {/each} + + + + + + + + + +
+ +{@render actions(dirty, submit)} diff --git a/src/components/moderator/pages/event/EventEdit.svelte b/src/components/moderator/pages/event/EventEdit.svelte index bb3d0f8..505db45 100644 --- a/src/components/moderator/pages/event/EventEdit.svelte +++ b/src/components/moderator/pages/event/EventEdit.svelte @@ -106,6 +106,15 @@ No schematic type found. + { + eventSchematicType = null; + }} + > + + Keinen + {#each $schemTypes as type}
@@ -35,12 +35,12 @@

Teams

- +

Referees

- + diff --git a/src/components/moderator/pages/event/FightEditRow.svelte b/src/components/moderator/pages/event/FightEditRow.svelte new file mode 100644 index 0000000..0f44410 --- /dev/null +++ b/src/components/moderator/pages/event/FightEditRow.svelte @@ -0,0 +1,41 @@ + + +
+ + + + + + + Fight bearbeiten + Hier kannst du die Daten des Kampfes bearbeiten. + + + {#snippet actions(dirty, submit)} + + + + {/snippet} + + + + +
diff --git a/src/components/moderator/pages/event/GroupEditRow.svelte b/src/components/moderator/pages/event/GroupEditRow.svelte new file mode 100644 index 0000000..4e1f1f0 --- /dev/null +++ b/src/components/moderator/pages/event/GroupEditRow.svelte @@ -0,0 +1,16 @@ + + +
+ + +
diff --git a/src/components/moderator/pages/event/TeamTable.svelte b/src/components/moderator/pages/event/TeamTable.svelte index 50f85a3..94a0975 100644 --- a/src/components/moderator/pages/event/TeamTable.svelte +++ b/src/components/moderator/pages/event/TeamTable.svelte @@ -26,18 +26,20 @@ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command"; import { teams } from "@components/stores/stores"; - const { event }: { event: ExtendedEvent } = $props(); + const { event = $bindable() }: { event: ExtendedEvent } = $props(); let team = $state(event.teams); async function addTeam(value: number) { await $eventRepo.updateTeams(event.event.id.toString(), [value]); team = await $eventRepo.listTeams(event.event.id.toString()); + event.teams = team; } async function removeTeam(value: number) { await $eventRepo.deleteTeams(event.event.id.toString(), [value]); team = await $eventRepo.listTeams(event.event.id.toString()); + event.teams = team; } let teamSearch = $state(""); From 5277c9a3fc55aa130e2371863a8433279f10c84d Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Thu, 22 May 2025 19:41:49 +0200 Subject: [PATCH 3/9] feat: Enhance event management with FightEdit and GroupEdit components, including improved data handling and new functionalities --- .../moderator/components/FightEdit.svelte | 192 +++++++++++++----- .../moderator/components/GroupEdit.svelte | 78 +++++++ .../pages/event/EventFightList.svelte | 90 +++++--- .../moderator/pages/event/EventView.svelte | 2 +- .../moderator/pages/event/FightEditRow.svelte | 30 ++- .../moderator/pages/event/columns.ts | 24 +++ src/components/repo/event.ts | 6 +- src/components/repo/fight.ts | 46 ++--- src/components/types/event.ts | 15 ++ 9 files changed, 371 insertions(+), 112 deletions(-) create mode 100644 src/components/moderator/components/GroupEdit.svelte diff --git a/src/components/moderator/components/FightEdit.svelte b/src/components/moderator/components/FightEdit.svelte index 4d0e5a0..d84a4da 100644 --- a/src/components/moderator/components/FightEdit.svelte +++ b/src/components/moderator/components/FightEdit.svelte @@ -1,45 +1,36 @@
- + {#snippet child({ props })} + {/snippet} + + + + + + + (createOpen = true)}> + + Neue Gruppe + + { + fightGroup = null; + groupSelectOpen = false; + }} + > + {#if fightGroup === null} + + {:else} + + {/if} + Keine Gruppe + + + {#each groups as group} + { + fightGroup = group.id; + groupSelectOpen = false; + }} + > + + {group.name} + + {/each} + + + + + + + + Neue Gruppe erstellen + Hier kannst du eine neue Gruppe erstellen + + + {#snippet actions(dirty, submit)} + + + + {/snippet} + + + + +
-{@render actions(dirty, submit)} +{@render actions(dirty && !loading, submit)} diff --git a/src/components/moderator/components/GroupEdit.svelte b/src/components/moderator/components/GroupEdit.svelte new file mode 100644 index 0000000..c5087d4 --- /dev/null +++ b/src/components/moderator/components/GroupEdit.svelte @@ -0,0 +1,78 @@ + + +
+ + + + + + + {#if groupType === "GROUP_STAGE" && group !== null} + + + + + + + + + {/if} +
+ +{@render actions(group === null ? canSave : dirty, submit)} diff --git a/src/components/moderator/pages/event/EventFightList.svelte b/src/components/moderator/pages/event/EventFightList.svelte index aa9dcdd..4b73873 100644 --- a/src/components/moderator/pages/event/EventFightList.svelte +++ b/src/components/moderator/pages/event/EventFightList.svelte @@ -22,25 +22,29 @@ import GroupEditRow from "./GroupEditRow.svelte"; - import type { ExtendedEvent } from "@type/event"; + import type { EventFightEdit, ExtendedEvent } from "@type/event"; import { createSvelteTable, FlexRender } from "@components/ui/data-table"; import { type ColumnFiltersState, getCoreRowModel, getFilteredRowModel, getGroupedRowModel, getSortedRowModel, type RowSelectionState, type SortingState } from "@tanstack/table-core"; import { columns } from "./columns"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@components/ui/table"; import { Checkbox } from "@components/ui/checkbox"; import { Menubar, MenubarContent, MenubarItem, MenubarGroup, MenubarGroupHeading, MenubarMenu, MenubarSeparator, MenubarTrigger } from "@components/ui/menubar"; + import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@components/ui/dialog"; + import FightEdit from "@components/moderator/components/FightEdit.svelte"; import { Button } from "@components/ui/button"; - import { MenuIcon } from "lucide-svelte"; + import { eventRepo } from "@components/repo/event"; let { data = $bindable() }: { data: ExtendedEvent } = $props(); + let fights = $state(data.fights); + let sorting = $state([]); let columnFilters = $state([]); let selection = $state({}); const table = createSvelteTable({ get data() { - return data.fights; + return fights; }, initialState: { columnOrder: ["auswahl", "begegnung", "group"], @@ -88,30 +92,58 @@ groupedColumnMode: "remove", getRowId: (row) => row.id.toString(), }); + + let createOpen = $state(false); + + async function handleSave(fight: EventFightEdit) { + await $eventRepo.createFight(data.event.id.toString(), { + ...fight, + blueTeam: fight.blueTeam.id, + redTeam: fight.redTeam.id, + }); + + fights = await $eventRepo.listFights(data.event.id.toString()); + createOpen = false; + }
- - - Mehrfach Bearbeiten - - Gruppe Ändern - Startzeit Verschieben - Spectate Port Ändern - - - - Erstellen - - Fight Erstellen - - Generatoren - Gruppenphase - K.O. Phase - - - - + + + + Mehrfach Bearbeiten + + Gruppe Ändern + Startzeit Verschieben + Spectate Port Ändern + + + + Erstellen + + (createOpen = true)}>Fight Erstellen + + Generatoren + Gruppenphase + K.O. Phase + + + + + + + Fight Erstellen + Hier kannst du einen neuen Fight erstellen + + + {#snippet actions(dirty, submit)} + + + + {/snippet} + + +
@@ -131,7 +163,7 @@ {#each table.getRowModel().rows as groupRow (groupRow.id)} {#if groupRow.getIsGrouped()} - + {/each} - + (fights = fights.map((v) => (v.id === update.id ? update : v)))} + > {/each} diff --git a/src/components/moderator/pages/event/EventView.svelte b/src/components/moderator/pages/event/EventView.svelte index 2f39995..d396b90 100644 --- a/src/components/moderator/pages/event/EventView.svelte +++ b/src/components/moderator/pages/event/EventView.svelte @@ -24,7 +24,7 @@ import RefereesList from "@components/moderator/pages/event/RefereesList.svelte"; import TeamTable from "@components/moderator/pages/event/TeamTable.svelte"; - let { event }: { event: ExtendedEvent } = $props(); + let { event = $bindable() }: { event: ExtendedEvent } = $props();
diff --git a/src/components/moderator/pages/event/FightEditRow.svelte b/src/components/moderator/pages/event/FightEditRow.svelte index 0f44410..974a4b1 100644 --- a/src/components/moderator/pages/event/FightEditRow.svelte +++ b/src/components/moderator/pages/event/FightEditRow.svelte @@ -1,21 +1,32 @@
- + @@ -35,7 +46,4 @@ -
diff --git a/src/components/moderator/pages/event/columns.ts b/src/components/moderator/pages/event/columns.ts index 6c074ce..4f4a855 100644 --- a/src/components/moderator/pages/event/columns.ts +++ b/src/components/moderator/pages/event/columns.ts @@ -77,4 +77,28 @@ export const columns: ColumnDef = [ }); }, }, + { + header: "Spielmodus", + accessorKey: "spielmodus", + }, + { + header: "Map", + accessorKey: "map", + }, + { + header: "Ergebnis", + accessorKey: "ergebnis", + cell: ({ row }) => { + const fight = row.original; + if (fight.ergebnis === 0 && fight.start > Date.now()) { + return "Noch nicht gespielt"; + } else if (fight.ergebnis === 1) { + return fight.blueTeam.name + " hat gewonnen"; + } else if (fight.ergebnis === 2) { + return fight.redTeam.name + " hat gewonnen"; + } else { + return "Unentschieden"; + } + }, + }, ]; diff --git a/src/components/repo/event.ts b/src/components/repo/event.ts index 18d36e7..334f6a3 100644 --- a/src/components/repo/event.ts +++ b/src/components/repo/event.ts @@ -128,6 +128,7 @@ export class EventRepo { .then((value) => z.array(EventFightSchema).parse(value)); } public async createFight(eventId: string, fight: any): Promise { + delete fight.ergebnis; return await fetchWithToken(this.token, `/events/${eventId}/fights`, { method: "POST", body: JSON.stringify(fight), @@ -153,7 +154,10 @@ export class EventRepo { CreateEventGroupSchema.parse(group); return await fetchWithToken(this.token, `/events/${eventId}/groups`, { method: "POST", - body: JSON.stringify(group), + body: JSON.stringify({ + name: group.name, + type: group.type, + }), headers: { "Content-Type": "application/json" }, }) .then((value) => value.json()) diff --git a/src/components/repo/fight.ts b/src/components/repo/fight.ts index a3d3a91..7020217 100644 --- a/src/components/repo/fight.ts +++ b/src/components/repo/fight.ts @@ -17,12 +17,12 @@ * along with this program. If not, see . */ -import type {EventFight} from "@type/event.js"; -import {fetchWithToken, tokenStore} from "./repo"; -import {z} from "zod"; -import {EventFightSchema} from "@type/event.js"; -import type {Dayjs} from "dayjs"; -import {derived} from "svelte/store"; +import type { EventFight } from "@type/event.js"; +import { fetchWithToken, tokenStore } from "./repo"; +import { z } from "zod"; +import { EventFightSchema } from "@type/event.js"; +import type { Dayjs } from "dayjs"; +import { derived } from "svelte/store"; export interface CreateFight { spielmodus: string; @@ -39,23 +39,22 @@ export interface UpdateFight { map: string | null; blueTeam: number | null; redTeam: number | null; - start: Dayjs | null; + start: number | null; spectatePort: number | null; - group: string | null; + group: number | null; } export class FightRepo { - constructor(private token: string) { - } + constructor(private token: string) {} public async listFights(eventId: number): Promise { return await fetchWithToken(this.token, `/events/${eventId}/fights`) - .then(value => value.json()) - .then(value => z.array(EventFightSchema).parse(value)); + .then((value) => value.json()) + .then((value) => z.array(EventFightSchema).parse(value)); } public async createFight(eventId: number, fight: CreateFight): Promise { - return await fetchWithToken(this.token, "/fights", { + return await fetchWithToken(this.token, `/events/${eventId}/fights`, { method: "POST", body: JSON.stringify({ event: eventId, @@ -67,28 +66,25 @@ export class FightRepo { spectatePort: fight.spectatePort, group: fight.group, }), - }).then(value => value.json()) + }) + .then((value) => value.json()) .then(EventFightSchema.parse); } - public async updateFight(fightId: number, fight: UpdateFight): Promise { - return await fetchWithToken(this.token, `/fights/${fightId}`, { + public async updateFight(eventId: number, fightId: number, fight: UpdateFight): Promise { + return await fetchWithToken(this.token, `/events/${eventId}/fights/${fightId}`, { method: "PUT", body: JSON.stringify({ - spielmodus: fight.spielmodus, - map: fight.map, - blueTeam: fight.blueTeam, - redTeam: fight.redTeam, + ...fight, start: fight.start?.valueOf(), - spectatePort: fight.spectatePort, - group: fight.group, }), - }).then(value => value.json()) + }) + .then((value) => value.json()) .then(EventFightSchema.parse); } - public async deleteFight(fightId: number): Promise { - const res = await fetchWithToken(this.token, `/fights/${fightId}`, { + public async deleteFight(eventId: number, fightId: number): Promise { + const res = await fetchWithToken(this.token, `/events/${eventId}/fights/${fightId}`, { method: "DELETE", }); diff --git a/src/components/types/event.ts b/src/components/types/event.ts index e3a9b84..919c615 100644 --- a/src/components/types/event.ts +++ b/src/components/types/event.ts @@ -45,6 +45,15 @@ export const EventFightSchema = z.object({ export type EventFight = z.infer; +export const EventFightEditSchema = EventFightSchema.omit({ + id: true, + group: true, +}).extend({ + group: z.number().nullable(), +}); + +export type EventFightEdit = z.infer; + export type ResponseGroups = z.infer; export const ResponseRelationSchema = z.object({ @@ -111,6 +120,12 @@ export const UpdateEventGroupSchema = z.object({ }); export type UpdateEventGroup = z.infer; +export const GroupEditSchema = ResponseGroupsSchema.omit({ + id: true, + points: true, +}); +export type GroupUpdateEdit = z.infer; + export const CreateEventRelationSchema = z.object({ fightId: z.number(), team: z.enum(["RED", "BLUE"]), From 2bf3beb044822dda09e5e6a4e16b34d954cf08c2 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Fri, 23 May 2025 14:23:33 +0200 Subject: [PATCH 4/9] feat: Implement group management features with dialogs for editing and displaying group results, enhance event creation with a form, and update team and referee management UI --- .../moderator/components/FightEdit.svelte | 89 +++++++---- .../pages/event/EventFightList.svelte | 144 +++++++++++------- .../moderator/pages/event/FightEditRow.svelte | 4 +- .../pages/event/GroupEditDialog.svelte | 45 ++++++ .../moderator/pages/event/GroupEditRow.svelte | 16 -- .../pages/event/GroupResultsDialog.svelte | 48 ++++++ .../moderator/pages/event/RefereesList.svelte | 4 +- .../moderator/pages/event/TeamTable.svelte | 4 +- .../moderator/pages/event/columns.ts | 2 +- .../moderator/pages/events/Events.svelte | 128 +++++++++++++++- 10 files changed, 375 insertions(+), 109 deletions(-) create mode 100644 src/components/moderator/pages/event/GroupEditDialog.svelte delete mode 100644 src/components/moderator/pages/event/GroupEditRow.svelte create mode 100644 src/components/moderator/pages/event/GroupResultsDialog.svelte diff --git a/src/components/moderator/components/FightEdit.svelte b/src/components/moderator/components/FightEdit.svelte index d84a4da..5a93d75 100644 --- a/src/components/moderator/components/FightEdit.svelte +++ b/src/components/moderator/components/FightEdit.svelte @@ -23,7 +23,7 @@ event, actions, onSave, - groups, + groups = $bindable(), }: { fight: EventFight | null; teams: Team[]; @@ -139,7 +139,7 @@ - + No map found. @@ -165,17 +165,30 @@ {#snippet child({ props })} {/snippet} - + - No map found. - + No team found. + { + fightBlueTeam = { + id: -1, + name: "?", + color: "7", + kuerzel: "?", + }; + blueTeamSelectOpen = false; + }} + keywords={["?"]}>??? + {#each teams as team} {#snippet child({ props })} {/snippet} - + - No map found. - + No team found. + { + fightRedTeam = { + id: -1, + name: "?", + color: "7", + kuerzel: "?", + }; + redTeamSelectOpen = false; + }} + keywords={["?"]}>??? + {#each teams as team} Neue Gruppe - { - fightGroup = null; - groupSelectOpen = false; - }} - > - {#if fightGroup === null} - - {:else} - - {/if} - Keine Gruppe - - {#each groups as group} + { - fightGroup = group.id; + fightGroup = null; groupSelectOpen = false; }} > - - {group.name} + {#if fightGroup === null} + + {:else} + + {/if} + Keine Gruppe - {/each} + + {#each groups as group} + { + fightGroup = group.id; + groupSelectOpen = false; + }} + > + + {group.name} + + {/each} + diff --git a/src/components/moderator/pages/event/EventFightList.svelte b/src/components/moderator/pages/event/EventFightList.svelte index 4b73873..b21aa9b 100644 --- a/src/components/moderator/pages/event/EventFightList.svelte +++ b/src/components/moderator/pages/event/EventFightList.svelte @@ -20,23 +20,25 @@ -
- - - - Mehrfach Bearbeiten - - Gruppe Ändern - Startzeit Verschieben - Spectate Port Ändern - - - - Erstellen - - (createOpen = true)}>Fight Erstellen - - Generatoren - Gruppenphase - K.O. Phase - - - - - - - Fight Erstellen - Hier kannst du einen neuen Fight erstellen - - - {#snippet actions(dirty, submit)} - - - - {/snippet} - - - + + + + Fight Erstellen + Hier kannst du einen neuen Fight erstellen + + + {#snippet actions(dirty, submit)} + + + + {/snippet} + + + + +{#if selectedGroup} + +{/if} + +{#if selectedGroupForResults} + +{/if} + +
+ + + Mehrfach Bearbeiten + + Gruppe Ändern + Startzeit Verschieben + Spectate Port Ändern + + + + Erstellen + + (createOpen = true)}>Fight Erstellen + + Generatoren + Gruppenphase + K.O. Phase + + + + + Gruppen + + {#each groups as group (group.id)} + + + {group.name} + + + openGroupEditDialog(group)}>Bearbeiten + openGroupResultsDialog(group)}>Gruppen Ergebnisse + + + {/each} + + + +
@@ -163,6 +209,7 @@ {#each table.getRowModel().rows as groupRow (groupRow.id)} {#if groupRow.getIsGrouped()} + {@const group = groups.find((g) => g.id === groupRow.getValue("group"))} groupRow.toggleSelected()} class="mr-4" /> - {groupRow.getValue("group") ?? "Keine Gruppe"} + {group?.name ?? "Keine Gruppe"} - {#if groupRow.original.group != null} - - - - {/if} + {#each groupRow.subRows as row (row.id)} @@ -187,12 +230,7 @@ {/each} - (fights = fights.map((v) => (v.id === update.id ? update : v)))} + (fights = fights.map((v) => (v.id === update.id ? update : v)))} > diff --git a/src/components/moderator/pages/event/FightEditRow.svelte b/src/components/moderator/pages/event/FightEditRow.svelte index 974a4b1..2ecbdbe 100644 --- a/src/components/moderator/pages/event/FightEditRow.svelte +++ b/src/components/moderator/pages/event/FightEditRow.svelte @@ -7,7 +7,7 @@ import type { Team } from "@components/types/team"; import { fightRepo } from "@components/repo/fight"; - let { fight, teams, groups, event, onupdate }: { fight: EventFight; teams: Team[]; groups: ResponseGroups[]; event: SWEvent; onupdate: (update: EventFight) => void } = $props(); + let { fight, teams, groups = $bindable(), event, onupdate }: { fight: EventFight; teams: Team[]; groups: ResponseGroups[]; event: SWEvent; onupdate: (update: EventFight) => void } = $props(); let editOpen = $state(false); @@ -37,7 +37,7 @@ Fight bearbeiten Hier kannst du die Daten des Kampfes bearbeiten. - + {#snippet actions(dirty, submit)} diff --git a/src/components/moderator/pages/event/GroupEditDialog.svelte b/src/components/moderator/pages/event/GroupEditDialog.svelte new file mode 100644 index 0000000..1a52863 --- /dev/null +++ b/src/components/moderator/pages/event/GroupEditDialog.svelte @@ -0,0 +1,45 @@ + + +{#if group} + + + + Gruppe Bearbeiten: {group.name} + Hier kannst du die Gruppendetails bearbeiten. + + + {#snippet actions(dirty, submit)} + + +
+ + +
+
+ {/snippet} +
+
+
+{/if} diff --git a/src/components/moderator/pages/event/GroupEditRow.svelte b/src/components/moderator/pages/event/GroupEditRow.svelte deleted file mode 100644 index 4e1f1f0..0000000 --- a/src/components/moderator/pages/event/GroupEditRow.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - -
- - -
diff --git a/src/components/moderator/pages/event/GroupResultsDialog.svelte b/src/components/moderator/pages/event/GroupResultsDialog.svelte new file mode 100644 index 0000000..e4d3ee8 --- /dev/null +++ b/src/components/moderator/pages/event/GroupResultsDialog.svelte @@ -0,0 +1,48 @@ + + + + + + Ergebnisse: {group?.name} + + Punkte: Sieg: {group?.pointsPerWin}, Unentschieden: {group?.pointsPerDraw}, Niederlage: {group?.pointsPerLoss} + + + {#if group.points !== null} +
+ + + Team + Spiele + Punkte + + + + {#each Object.entries(group.points).toSorted((a, b) => b[1] - a[1]) as [teamIdString, points] (teamIdString)} + {@const teamId = Number(teamIdString)} + {@const team = teams.find((t) => t.id === teamId) as ResponseTeam} + {@const playedGames = fights.filter((f) => f.start > Date.now() && f.group?.id === group.id && (f.blueTeam.id === teamId || f.redTeam.id === teamId)).length} + + {team.name} ({team.kuerzel}) + {playedGames} + {points} + + {/each} + +
+ {:else} +

Noch keine Ergebnisse für diese Gruppe vorhanden oder keine Spiele zugeordnet.

+ {/if} + + + + + diff --git a/src/components/moderator/pages/event/RefereesList.svelte b/src/components/moderator/pages/event/RefereesList.svelte index 0da6088..63ee74b 100644 --- a/src/components/moderator/pages/event/RefereesList.svelte +++ b/src/components/moderator/pages/event/RefereesList.svelte @@ -55,7 +55,7 @@ {referee.name} - + {/each} @@ -63,7 +63,7 @@ - + diff --git a/src/components/moderator/pages/event/TeamTable.svelte b/src/components/moderator/pages/event/TeamTable.svelte index 94a0975..5f06d78 100644 --- a/src/components/moderator/pages/event/TeamTable.svelte +++ b/src/components/moderator/pages/event/TeamTable.svelte @@ -59,7 +59,7 @@ {t.kuerzel} {t.name} - + {/each} @@ -72,7 +72,7 @@ - + diff --git a/src/components/moderator/pages/event/columns.ts b/src/components/moderator/pages/event/columns.ts index 4f4a855..48927c2 100644 --- a/src/components/moderator/pages/event/columns.ts +++ b/src/components/moderator/pages/event/columns.ts @@ -63,7 +63,7 @@ export const columns: ColumnDef = [ }, { header: "Gruppe", - accessorKey: "group.name", + accessorKey: "group.id", id: "group", }, { diff --git a/src/components/moderator/pages/events/Events.svelte b/src/components/moderator/pages/events/Events.svelte index e039b80..2c24b27 100644 --- a/src/components/moderator/pages/events/Events.svelte +++ b/src/components/moderator/pages/events/Events.svelte @@ -20,12 +20,136 @@
+
+

Events

+ + + {#snippet child({ props })} + + {/snippet} + + + + Create New Event + Fill in the details for the new event. Click create when you're done. + +
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+ {#if errorMsg} +

{errorMsg}

+ {/if} +
+ + + {#snippet child({ props })} + + {/snippet} + + + +
+
+
+ {#await eventsFuture}

Loading...

{:then events} @@ -45,7 +169,5 @@ {/each}
- {:catch e} - {/await} - \ No newline at end of file + From 0205108d2d33c97a3266499355d58b4b9856de1b Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Fri, 23 May 2025 14:25:29 +0200 Subject: [PATCH 5/9] Implement code changes to enhance functionality and improve performance --- src/components/repo/openapi.yaml | 2210 ------------------------------ 1 file changed, 2210 deletions(-) delete mode 100644 src/components/repo/openapi.yaml diff --git a/src/components/repo/openapi.yaml b/src/components/repo/openapi.yaml deleted file mode 100644 index 7ebfe22..0000000 --- a/src/components/repo/openapi.yaml +++ /dev/null @@ -1,2210 +0,0 @@ -openapi: "3.1.0" -info: - title: "SteamWar API" - description: "SteamWar API" - version: "1.0.0" -servers: - - url: "https://SteamWar" -paths: - /auth: - delete: - description: "" - responses: - "200": - description: "OK" - content: - "*/*": - schema: - type: "object" - post: - description: "" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/UsernamePassword" - required: true - responses: - "403": - description: "Forbidden" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid username or password" - code: "invalid" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/AuthTokenResponse" - put: - description: "" - responses: - "403": - description: "Forbidden" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid token type" - code: "invalid" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/AuthTokenResponse" - /data/admin/gamemodes: - get: - description: "" - responses: - "200": - description: "OK" - content: - "*/*": - schema: - type: "array" - items: - type: "string" - /data/admin/gamemodes/{gamemode}/maps: - get: - description: "" - parameters: - - name: "gamemode" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid gamemode" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Gamemode not found" - code: "null" - "200": - description: "OK" - content: - "*/*": - schema: - type: "array" - items: - type: "string" - /data/admin/schematicTypes: - get: - description: "" - responses: - "200": - description: "OK" - content: - "*/*": - schema: - type: "array" - items: - $ref: "#/components/schemas/ResponseSchematicType" - /data/admin/users: - get: - description: "" - responses: - "200": - description: "OK" - content: - "*/*": - schema: - type: "array" - items: - $ref: "#/components/schemas/ResponseUser" - /data/me: - get: - description: "" - responses: - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseUser" - /data/server: - get: - description: "" - responses: - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/StatusResponse" - "500": - description: "Internal Server Error" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - /data/skin/{uuid}: - get: - description: "" - parameters: - - name: "uuid" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid UUID" - code: "null" - "200": - description: "OK
A file response" - content: - application/*: - schema: - type: "object" - format: "binary" - /data/team: - get: - description: "" - responses: - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/Map_String" - /download/{code}: - get: - description: "" - parameters: - - name: "code" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "object" - "410": - description: "Gone" - content: - "*/*": - schema: - type: "object" - "403": - description: "Forbidden" - content: - "*/*": - schema: - type: "object" - "500": - description: "Internal Server Error" - content: - "*/*": - schema: - type: "object" - "200": - description: "OK" - content: - application/octet-stream: - schema: - type: "string" - format: "byte" - /download/{code}/info: - get: - description: "" - parameters: - - name: "code" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "object" - "410": - description: "Gone" - content: - "*/*": - schema: - type: "object" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseSchematic" - /events: - get: - description: "" - responses: - "200": - description: "OK" - content: - "*/*": - schema: - type: "array" - items: - $ref: "#/components/schemas/ShortEvent" - post: - description: "" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid body" - code: "null" - "201": - description: "Created" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseEvent" - /events/{id}: - delete: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "integer" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Event not found" - code: "null" - "204": - description: "No Content" - content: - "*/*": - schema: - type: "object" - get: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid event ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Event not found" - code: "null" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/ExtendedResponseEvent" - put: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid body" - code: "null" - Example#2: - description: "" - value: - error: "Invalid event ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Event not found" - code: "null" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseEvent" - /events/{id}/csv: - get: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid event ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Event not found" - code: "null" - "200": - description: "OK" - content: - text/plain: - schema: - type: "string" - /events/{id}/fights: - get: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid event ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Event not found" - code: "null" - "200": - description: "OK" - content: - "*/*": - schema: - type: "array" - items: - $ref: "#/components/schemas/ResponseEventFight" - post: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid body" - code: "null" - "201": - description: "Created" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseEventFight" - /events/{id}/fights/{fight}: - delete: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - - name: "fight" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid fight ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Fight not found" - code: "null" - "200": - description: "OK" - content: - "*/*": - schema: - type: "object" - put: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - - name: "fight" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid body" - code: "null" - Example#2: - description: "" - value: - error: "Invalid fight ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Fight not found" - code: "null" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseEventFight" - /events/{id}/groups: - get: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid event ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Event not found" - code: "null" - "200": - description: "OK" - content: - "*/*": - schema: - type: "array" - items: - $ref: "#/components/schemas/ResponseGroups" - post: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CreateEventGroup" - required: true - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid event ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Event not found" - code: "null" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseGroups" - /events/{id}/groups/{group}: - delete: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - - name: "group" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "object" - "204": - description: "No Content" - content: - "*/*": - schema: - type: "object" - get: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - - name: "group" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "object" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseGroups" - put: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - - name: "group" - in: "path" - required: true - schema: - type: "string" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/UpdateEventGroup" - required: true - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "object" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseGroups" - /events/{id}/relations: - get: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid event ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Event not found" - code: "null" - "200": - description: "OK" - content: - "*/*": - schema: - type: "array" - items: - $ref: "#/components/schemas/ResponseRelation" - post: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CreateEventRelation" - required: true - responses: - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "object" - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseRelation" - /events/{id}/relations/{relation}: - delete: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - - name: "relation" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "object" - "204": - description: "No Content" - content: - "*/*": - schema: - type: "object" - get: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - - name: "relation" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "object" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseRelation" - put: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - - name: "relation" - in: "path" - required: true - schema: - type: "string" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/UpdateEventRelation" - required: true - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "object" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseRelation" - /events/{id}/teams: - delete: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - requestBody: - content: - application/json: - schema: - type: "array" - items: - type: "integer" - format: "int32" - required: true - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid event ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Event not found" - code: "null" - "204": - description: "No Content" - content: - "*/*": - schema: - type: "object" - get: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid event ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Event not found" - code: "null" - "200": - description: "OK" - content: - "*/*": - schema: - type: "array" - items: - $ref: "#/components/schemas/ResponseTeam" - put: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - requestBody: - content: - application/json: - schema: - type: "array" - items: - type: "integer" - format: "int32" - required: true - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Invalid event ID" - code: "null" - "404": - description: "Not Found" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "Event not found" - code: "null" - "204": - description: "No Content" - content: - "*/*": - schema: - type: "object" - /page: - get: - description: "" - parameters: - - name: "branch" - in: "query" - required: false - schema: - type: "string" - responses: - "200": - description: "OK" - content: - "*/*": - schema: - type: "array" - items: - $ref: "#/components/schemas/PageResponseList" - examples: - Example#1: - description: "" - value: [] - post: - description: "" - parameters: - - name: "branch" - in: "query" - required: false - schema: - type: "string" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CreatePageRequest" - required: true - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "string" - examples: - Example#1: - value: "Invalid path" - "200": - description: "" - content: - "*/*": - schema: - type: "object" - /page/{id}: - delete: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "integer" - - name: "branch" - in: "query" - required: false - schema: - type: "string" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/DeletePageRequest" - required: true - responses: - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "string" - examples: - Example#1: - value: "Page not found" - "200": - description: "" - content: - "*/*": - schema: - type: "object" - get: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "integer" - - name: "branch" - in: "query" - required: false - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "string" - examples: - Example#1: - value: "Invalid id" - Example#2: - value: "Invalid id" - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "string" - examples: - Example#1: - value: "Page not found" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/PageResponse" - put: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "integer" - - name: "branch" - in: "query" - required: false - schema: - type: "string" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/UpdatePageRequest" - required: true - responses: - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "string" - examples: - Example#1: - value: "Page not found" - "200": - description: "" - content: - "*/*": - schema: - type: "object" - /page/branch: - delete: - description: "" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CreateBranchRequest" - required: true - responses: - "200": - description: "" - content: - "*/*": - schema: - type: "object" - get: - description: "" - responses: - "200": - description: "" - content: - "*/*": - schema: - type: "array" - items: - type: "string" - post: - description: "" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CreateBranchRequest" - required: true - responses: - "200": - description: "" - content: - "*/*": - schema: - type: "object" - /perms: - get: - description: "" - responses: - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/RespondUserPerms" - examples: - Example#1: - description: "" - value: - prefixes: "null" - perms: [] - /perms/user/{id}: - get: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/RespondUserPermsPrefix" - examples: - Example#1: - description: "" - value: - prefix: "null" - perms: [] - /perms/user/{id}/{perm}: - delete: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - - name: "perm" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "202": - description: "Accepted" - content: - "*/*": - schema: - type: "object" - "204": - description: "No Content" - content: - "*/*": - schema: - type: "object" - put: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - - name: "perm" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "202": - description: "Accepted" - content: - "*/*": - schema: - type: "object" - "204": - description: "No Content" - content: - "*/*": - schema: - type: "object" - /perms/user/{id}/prefix/{prefix}: - put: - description: "" - parameters: - - name: "id" - in: "path" - required: true - schema: - type: "string" - - name: "prefix" - in: "path" - required: true - schema: - type: "string" - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - type: "object" - "202": - description: "Accepted" - content: - "*/*": - schema: - type: "object" - /schem: - post: - description: "" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/UploadSchematic" - required: true - responses: - "400": - description: "Bad Request" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseError" - examples: - Example#1: - description: "" - value: - error: "null" - code: "UPLOAD_ERROR" - Example#2: - description: "" - value: - error: "INVALID_SUFFIX" - code: "null" - Example#3: - description: "" - value: - error: "INVALID_NAME" - code: "null" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/ResponseSchematic" - /stats/fights: - get: - description: "" - responses: - "200": - description: "OK" - content: - "*/*": - schema: {} - /stats/ranked/{gamemode}: - get: - description: "" - parameters: - - name: "gamemode" - in: "path" - required: true - schema: - type: "string" - responses: - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "object" - "200": - description: "OK" - content: - "*/*": - schema: {} - /stats/user: - get: - description: "" - responses: - "404": - description: "Not Found" - content: - "*/*": - schema: - type: "string" - examples: - Example#1: - value: "User not found" - "200": - description: "OK" - content: - "*/*": - schema: - $ref: "#/components/schemas/UserStats" -components: - schemas: - UsernamePassword: - type: "object" - properties: - name: - type: "string" - password: - type: "string" - keepLoggedIn: - type: "boolean" - required: - - "name" - - "password" - - "keepLoggedIn" - ResponseError: - type: "object" - properties: - error: - type: "string" - code: - type: "string" - required: - - "error" - - "code" - ResponseToken: - type: "object" - properties: - token: - type: "string" - expires: - type: "string" - required: - - "token" - - "expires" - AuthTokenResponse: - type: "object" - properties: - accessToken: - $ref: "#/components/schemas/ResponseToken" - refreshToken: - nullable: true - $ref: "#/components/schemas/ResponseToken" - required: - - "accessToken" - ResponseSchematicType: - type: "object" - properties: - name: - type: "string" - db: - type: "string" - required: - - "name" - - "db" - ResponseUser: - type: "object" - properties: - name: - type: "string" - uuid: - type: "string" - prefix: - type: "string" - perms: - type: "array" - items: - type: "string" - required: - - "name" - - "uuid" - - "prefix" - - "perms" - JsonElement: - type: "object" - properties: {} - Player: - type: "object" - properties: - name: - type: "string" - id: - type: "string" - required: - - "name" - - "id" - Players: - type: "object" - properties: - max: - type: "integer" - format: "int32" - online: - type: "integer" - format: "int32" - sample: - type: "array" - items: - $ref: "#/components/schemas/Player" - required: - - "max" - - "online" - - "sample" - Version: - type: "object" - properties: - name: - type: "string" - protocol: - type: "integer" - format: "int32" - required: - - "name" - - "protocol" - StatusResponse: - type: "object" - properties: - description: - $ref: "#/components/schemas/JsonElement" - players: - $ref: "#/components/schemas/Players" - version: - $ref: "#/components/schemas/Version" - favicon: - type: "string" - time: - type: "integer" - format: "int32" - nullable: true - default: "0" - required: - - "description" - - "players" - - "version" - - "favicon" - Map: - type: "object" - properties: {} - Map_String: - type: "string" - ResponseSchematic: - type: "object" - properties: - name: - type: "string" - id: - type: "integer" - format: "int32" - type: - type: "string" - nullable: true - owner: - type: "integer" - format: "int32" - item: - type: "string" - lastUpdate: - type: "integer" - format: "int64" - rank: - type: "integer" - format: "int32" - replaceColor: - type: "boolean" - allowReplay: - type: "boolean" - required: - - "name" - - "id" - - "owner" - - "item" - - "lastUpdate" - - "rank" - - "replaceColor" - - "allowReplay" - ShortEvent: - type: "object" - properties: - id: - type: "integer" - format: "int32" - name: - type: "string" - start: - type: "integer" - format: "int64" - end: - type: "integer" - format: "int64" - required: - - "id" - - "name" - - "start" - - "end" - ResponseEvent: - type: "object" - properties: - id: - type: "integer" - format: "int32" - name: - type: "string" - deadline: - type: "integer" - format: "int64" - start: - type: "integer" - format: "int64" - end: - type: "integer" - format: "int64" - maxTeamMembers: - type: "integer" - format: "int32" - schemType: - type: "string" - nullable: true - publicSchemsOnly: - type: "boolean" - required: - - "id" - - "name" - - "deadline" - - "start" - - "end" - - "maxTeamMembers" - - "publicSchemsOnly" - ResponseTeam: - type: "object" - properties: - id: - type: "integer" - format: "int32" - name: - type: "string" - kuerzel: - type: "string" - color: - type: "string" - required: - - "id" - - "name" - - "kuerzel" - - "color" - ResponseGroups: - type: "object" - properties: - id: - type: "integer" - format: "int32" - name: - type: "string" - pointsPerWin: - type: "integer" - format: "int32" - pointsPerLoss: - type: "integer" - format: "int32" - pointsPerDraw: - type: "integer" - format: "int32" - type: - type: "string" - enum: - - "GROUP_STAGE" - - "ELIMINATION_STAGE" - points: - type: "integer" - format: "int32" - required: - - "id" - - "name" - - "pointsPerWin" - - "pointsPerLoss" - - "pointsPerDraw" - - "type" - - "points" - ResponseEventFight: - type: "object" - properties: - id: - type: "integer" - format: "int32" - spielmodus: - type: "string" - map: - type: "string" - blueTeam: - $ref: "#/components/schemas/ResponseTeam" - redTeam: - $ref: "#/components/schemas/ResponseTeam" - start: - type: "integer" - format: "int64" - ergebnis: - type: "integer" - format: "int32" - spectatePort: - type: "integer" - format: "int32" - nullable: true - group: - nullable: true - $ref: "#/components/schemas/ResponseGroups" - required: - - "id" - - "spielmodus" - - "map" - - "blueTeam" - - "redTeam" - - "start" - - "ergebnis" - ResponseRelation: - type: "object" - properties: - id: - type: "integer" - format: "int32" - fight: - $ref: "#/components/schemas/ResponseEventFight" - type: - type: "string" - enum: - - "FIGHT" - - "GROUP" - fromFight: - nullable: true - $ref: "#/components/schemas/ResponseEventFight" - fromGroup: - nullable: true - $ref: "#/components/schemas/ResponseGroups" - fromPlace: - type: "integer" - format: "int32" - required: - - "id" - - "fight" - - "type" - - "fromPlace" - ExtendedResponseEvent: - type: "object" - properties: - event: - $ref: "#/components/schemas/ResponseEvent" - teams: - type: "array" - items: - $ref: "#/components/schemas/ResponseTeam" - groups: - type: "array" - items: - $ref: "#/components/schemas/ResponseGroups" - fights: - type: "array" - items: - $ref: "#/components/schemas/ResponseEventFight" - referees: - type: "array" - items: - $ref: "#/components/schemas/ResponseUser" - relations: - type: "array" - items: - $ref: "#/components/schemas/ResponseRelation" - required: - - "event" - - "teams" - - "groups" - - "fights" - - "referees" - - "relations" - CreateEventGroup: - type: "object" - properties: - name: - type: "string" - type: - type: "string" - enum: - - "GROUP_STAGE" - - "ELIMINATION_STAGE" - required: - - "name" - - "type" - UpdateEventGroup: - type: "object" - properties: - name: - type: "string" - nullable: true - type: - type: "string" - nullable: true - enum: - - "GROUP_STAGE" - - "ELIMINATION_STAGE" - pointsPerWin: - type: "integer" - format: "int32" - nullable: true - pointsPerLoss: - type: "integer" - format: "int32" - nullable: true - pointsPerDraw: - type: "integer" - format: "int32" - nullable: true - CreateEventRelation: - type: "object" - properties: - fightId: - type: "integer" - format: "int32" - team: - type: "string" - enum: - - "RED" - - "BLUE" - fromType: - type: "string" - enum: - - "FIGHT" - - "GROUP" - fromId: - type: "integer" - format: "int32" - fromPlace: - type: "integer" - format: "int32" - required: - - "fightId" - - "team" - - "fromType" - - "fromId" - - "fromPlace" - UpdateFromRelation: - type: "object" - properties: - fromType: - type: "string" - enum: - - "FIGHT" - - "GROUP" - fromId: - type: "integer" - format: "int32" - fromPlace: - type: "integer" - format: "int32" - required: - - "fromType" - - "fromId" - - "fromPlace" - UpdateEventRelation: - type: "object" - properties: - team: - type: "string" - nullable: true - enum: - - "RED" - - "BLUE" - from: - nullable: true - $ref: "#/components/schemas/UpdateFromRelation" - PageResponseList: - type: "object" - properties: - path: - type: "string" - name: - type: "string" - sha: - type: "string" - downloadUrl: - type: "string" - id: - type: "integer" - format: "int32" - required: - - "path" - - "name" - - "sha" - - "downloadUrl" - - "id" - CreatePageRequest: - type: "object" - properties: - path: - type: "string" - slug: - type: "string" - nullable: true - title: - type: "string" - nullable: true - required: - - "path" - DeletePageRequest: - type: "object" - properties: - sha: - type: "string" - message: - type: "string" - required: - - "sha" - - "message" - PageResponse: - type: "object" - properties: - path: - type: "string" - name: - type: "string" - sha: - type: "string" - downloadUrl: - type: "string" - content: - type: "string" - size: - type: "integer" - format: "int32" - id: - type: "integer" - format: "int32" - required: - - "path" - - "name" - - "sha" - - "downloadUrl" - - "content" - - "size" - - "id" - UpdatePageRequest: - type: "object" - properties: - content: - type: "string" - sha: - type: "string" - message: - type: "string" - required: - - "content" - - "sha" - - "message" - CreateBranchRequest: - type: "object" - properties: - branch: - type: "string" - required: - - "branch" - RespondUserPerms: - type: "object" - properties: - prefixes: - type: "string" - perms: - type: "array" - items: - type: "string" - required: - - "prefixes" - - "perms" - RespondPrefix: - type: "object" - properties: - name: - type: "string" - colorCode: - type: "string" - chatPrefix: - type: "string" - required: - - "name" - - "colorCode" - - "chatPrefix" - RespondUserPermsPrefix: - type: "object" - properties: - prefix: - $ref: "#/components/schemas/RespondPrefix" - perms: - type: "array" - items: - type: "string" - required: - - "prefix" - - "perms" - UploadSchematic: - type: "object" - properties: - name: - type: "string" - content: - type: "string" - required: - - "name" - - "content" - UserStats: - type: "object" - properties: - eventFightParticipation: - type: "integer" - format: "int32" - eventParticipation: - type: "integer" - format: "int32" - acceptedSchematics: - type: "integer" - format: "int32" - fights: - type: "integer" - format: "int32" - playtime: - type: "number" - format: "double" - required: - - "eventFightParticipation" - - "eventParticipation" - - "acceptedSchematics" - - "fights" - - "playtime" From 7d75453be5156c7f104086aa11e8a1fccad01239 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Wed, 28 May 2025 12:30:05 +0200 Subject: [PATCH 6/9] Refactor FightTable and GroupTable components to use numeric group identifiers; enhance event handling in FightEdit and EventFightList; add new Pages management UI with editor tabs; improve event data handling and display logic; update event types to include hasFinished status; optimize announcement page rendering and structure. --- package.json | 172 +++++++++--------- src/components/FightTable.svelte | 30 +-- src/components/GroupTable.svelte | 51 +++--- src/components/admin/pages/edit/Editor.svelte | 64 +++---- src/components/moderator/App.svelte | 6 +- .../moderator/components/FightEdit.svelte | 4 +- .../pages/event/EventFightList.svelte | 30 ++- .../pages/event/GroupResultsDialog.svelte | 6 +- .../moderator/pages/event/columns.ts | 2 +- .../moderator/pages/events/Events.svelte | 2 +- .../pages/pages/EditorWithTabs.svelte | 99 ++++++++++ .../moderator/pages/pages/Pages.svelte | 27 +++ .../moderator/pages/pages/PagesList.svelte | 116 ++++++++++++ .../moderator/pages/pages/page.svelte.ts | 163 +++++++++++++++++ src/components/types/event.ts | 2 + src/pages/ankuendigungen/[...slug].astro | 131 ++++++------- 16 files changed, 679 insertions(+), 226 deletions(-) create mode 100644 src/components/moderator/pages/pages/EditorWithTabs.svelte create mode 100644 src/components/moderator/pages/pages/Pages.svelte create mode 100644 src/components/moderator/pages/pages/PagesList.svelte create mode 100644 src/components/moderator/pages/pages/page.svelte.ts diff --git a/package.json b/package.json index 198346e..efbe2e9 100644 --- a/package.json +++ b/package.json @@ -1,83 +1,93 @@ { - "name": "steamwar-website", - "type": "module", - "version": "0.0.1", - "scripts": { - "dev": "astro dev", - "start": "astro dev", - "build": "astro build", - "preview": "astro preview", - "astro": "astro", - "i18n:extract": "astro-i18n extract", - "i18n:generate:pages": "astro-i18n generate:pages --purge", - "i18n:generate:types": "astro-i18n generate:types", - "i18n:sync": "pnpm run i18n:generate:pages && pnpm run i18n:generate:types", - "clean:dist": "rm -rf dist", - "clean:node_modules": "rm -rf node_modules", - "ci": "pnpm install && pnpm run i18n:sync && pnpm run build" - }, - "devDependencies": { - "@astrojs/svelte": "^7.0.4", - "@astrojs/tailwind": "^5.1.5", - "@astropub/icons": "^0.2.0", - "@internationalized/date": "^3.7.0", - "@lucide/svelte": "^0.488.0", - "@types/color": "^4.2.0", - "@types/node": "^22.9.3", - "@types/three": "^0.170.0", - "@typescript-eslint/eslint-plugin": "^8.15.0", - "@typescript-eslint/parser": "^8.15.0", - "autoprefixer": "^10.4.20", - "bits-ui": "1.3.4", - "clsx": "^2.1.1", - "cmdk-sv": "^0.0.18", - "cssnano": "^7.0.6", - "embla-carousel-svelte": "^8.5.2", - "esbuild": "^0.24.0", - "eslint": "^9.15.0", - "eslint-plugin-astro": "^1.3.1", - "eslint-plugin-jsx-a11y": "^6.10.2", - "eslint-plugin-svelte": "^2.46.0", - "formsnap": "1.0.1", - "lucide-svelte": "^0.476.0", - "mode-watcher": "^0.5.1", - "paneforge": "^0.0.6", - "postcss-nesting": "^13.0.1", - "sass": "^1.81.0", - "svelte": "^5.16.0", - "svelte-sonner": "^0.3.28", - "tailwind-merge": "^2.5.5", - "tailwind-variants": "^0.3.1", - "tailwindcss": "^3.4.15", - "three": "^0.170.0", - "typescript": "^5.7.2", - "vaul-svelte": "^0.3.2", - "zod": "^3.23.8" - }, - "dependencies": { - "@astrojs/mdx": "^4.0.7", - "@astrojs/sitemap": "^3.2.1", - "@codemirror/commands": "^6.8.0", - "@codemirror/lang-json": "^6.0.1", - "@ddietr/codemirror-themes": "^1.4.4", - "@tanstack/table-core": "^8.21.2", - "astro": "^5.1.8", - "astro-i18n": "^2.2.4", - "astro-robots-txt": "^1.0.0", - "astro-seo": "^0.8.4", - "chart.js": "^4.4.6", - "chartjs-adapter-dayjs-4": "^1.0.4", - "chartjs-adapter-moment": "^1.0.1", - "color": "^4.2.3", - "dayjs": "^1.11.13", - "easymde": "^2.18.0", - "flowbite": "^2.5.2", - "flowbite-svelte": "^0.47.3", - "flowbite-svelte-icons": "^2.0.2", - "qs": "^6.13.1", - "sharp": "^0.33.5", - "svelte-awesome": "^3.3.5", - "svelte-codemirror-editor": "^1.4.1", - "svelte-spa-router": "^4.0.1" - } + "name": "steamwar-website", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro", + "i18n:extract": "astro-i18n extract", + "i18n:generate:pages": "astro-i18n generate:pages --purge", + "i18n:generate:types": "astro-i18n generate:types", + "i18n:sync": "pnpm run i18n:generate:pages && pnpm run i18n:generate:types", + "clean:dist": "rm -rf dist", + "clean:node_modules": "rm -rf node_modules", + "ci": "pnpm install && pnpm run i18n:sync && pnpm run build" + }, + "devDependencies": { + "@astrojs/svelte": "^7.1.0", + "@astrojs/tailwind": "^5.1.5", + "@astropub/icons": "^0.2.0", + "@internationalized/date": "^3.8.1", + "@lucide/svelte": "^0.488.0", + "@types/color": "^4.2.0", + "@types/node": "^22.15.23", + "@types/three": "^0.170.0", + "@typescript-eslint/eslint-plugin": "^8.33.0", + "@typescript-eslint/parser": "^8.33.0", + "autoprefixer": "^10.4.21", + "bits-ui": "1.3.4", + "clsx": "^2.1.1", + "cmdk-sv": "^0.0.18", + "cssnano": "^7.0.7", + "embla-carousel-svelte": "^8.6.0", + "esbuild": "^0.24.2", + "eslint": "^9.27.0", + "eslint-plugin-astro": "^1.3.1", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-svelte": "^2.46.1", + "formsnap": "1.0.1", + "lucide-svelte": "^0.476.0", + "mode-watcher": "^0.5.1", + "paneforge": "^0.0.6", + "postcss-nesting": "^13.0.1", + "sass": "^1.89.0", + "svelte": "^5.33.4", + "svelte-sonner": "^0.3.28", + "tailwind-merge": "^2.6.0", + "tailwind-variants": "^0.3.1", + "tailwindcss": "^3.4.17", + "three": "^0.170.0", + "typescript": "^5.8.3", + "vaul-svelte": "^0.3.2", + "zod": "^3.25.31" + }, + "dependencies": { + "@astrojs/mdx": "^4.3.0", + "@astrojs/sitemap": "^3.4.0", + "@codemirror/commands": "^6.8.1", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/view": "^6.36.8", + "@ddietr/codemirror-themes": "^1.5.1", + "@tanstack/table-core": "^8.21.3", + "astro": "^5.8.0", + "astro-i18n": "^2.2.4", + "astro-robots-txt": "^1.0.0", + "astro-seo": "^0.8.4", + "chart.js": "^4.4.9", + "chartjs-adapter-dayjs-4": "^1.0.4", + "chartjs-adapter-moment": "^1.0.1", + "codemirror": "^6.0.1", + "color": "^4.2.3", + "dayjs": "^1.11.13", + "easymde": "^2.20.0", + "flowbite": "^2.5.2", + "flowbite-svelte": "^0.47.4", + "flowbite-svelte-icons": "^2.2.0", + "qs": "^6.14.0", + "sharp": "^0.33.5", + "svelte-awesome": "^3.3.5", + "svelte-spa-router": "^4.0.1" + }, + "pnpm": { + "ignoredBuiltDependencies": [ + "esbuild" + ], + "onlyBuiltDependencies": [ + "@parcel/watcher", + "sharp" + ] + } } diff --git a/src/components/FightTable.svelte b/src/components/FightTable.svelte index 8102329..fbbe51c 100644 --- a/src/components/FightTable.svelte +++ b/src/components/FightTable.svelte @@ -19,25 +19,27 @@ --> @@ -55,13 +57,15 @@ - {#each window(event.fights.filter(f => group === undefined ? true : f.group === group), rows) as fights} + {#each window( event.fights.filter((f) => (group === undefined ? true : f.group?.id === group)), rows ) as fights} {#each fights as fight (fight.id)} - {Intl.DateTimeFormat(astroI18n.locale, { - hour: "numeric", - minute: "numeric", - }).format(new Date(fight.start))} + {Intl.DateTimeFormat(astroI18n.locale, { + hour: "numeric", + minute: "numeric", + }).format(new Date(fight.start))} {fight.blueTeam.kuerzel} {fight.redTeam.kuerzel} {getWinner(fight)} @@ -70,4 +74,4 @@ {/each} - \ No newline at end of file + diff --git a/src/components/GroupTable.svelte b/src/components/GroupTable.svelte index 9b70167..9897329 100644 --- a/src/components/GroupTable.svelte +++ b/src/components/GroupTable.svelte @@ -19,33 +19,40 @@ -->
diff --git a/src/components/admin/pages/edit/Editor.svelte b/src/components/admin/pages/edit/Editor.svelte index ef3f5e0..a4fcda3 100644 --- a/src/components/admin/pages/edit/Editor.svelte +++ b/src/components/admin/pages/edit/Editor.svelte @@ -18,23 +18,22 @@ --> - { - if (dirty) { - return "You have unsaved changes. Are you sure you want to leave?"; - } -}}/> + + { + if (dirty) { + return "You have unsaved changes. Are you sure you want to leave?"; + } + }} +/> {#await pageFuture} - + {:then p}
{#snippet end()} - - - Delete - - - Save - - - {/snippet} + + Delete + Save + + {/snippet}
{#if page?.name.endsWith("md") || page?.name.endsWith("mdx")} - - {:else} - dirty = true}/> - {/if} + + {:else}{/if}
{:catch error}

{error.message}

-{/await} \ No newline at end of file +{/await} diff --git a/src/components/moderator/App.svelte b/src/components/moderator/App.svelte index 5da559e..4598e71 100644 --- a/src/components/moderator/App.svelte +++ b/src/components/moderator/App.svelte @@ -28,12 +28,14 @@ import Events from "@components/moderator/pages/events/Events.svelte"; import Dashboard from "@components/moderator/pages/dashboard/Dashboard.svelte"; import Event from "@components/moderator/pages/event/Event.svelte"; + import Pages from "@components/moderator/pages/pages/Pages.svelte"; const routes: RouteDefinition = { "/": Dashboard, "/events": Events, "/players": Players, "/event/:id": Event, + "/pages": Pages, }; @@ -48,7 +50,5 @@
-
- -
+ diff --git a/src/components/moderator/components/FightEdit.svelte b/src/components/moderator/components/FightEdit.svelte index 5a93d75..17a7330 100644 --- a/src/components/moderator/components/FightEdit.svelte +++ b/src/components/moderator/components/FightEdit.svelte @@ -37,7 +37,7 @@ let fightMap = $state(fight?.map); let fightBlueTeam = $state(fight?.blueTeam); let fightRedTeam = $state(fight?.redTeam); - let fightStart = $state(fight?.start ? fromAbsolute(fight.start, "Europe/Berlin") : now("Europe/Berlin")); + let fightStart = $state(fight?.start ? fromAbsolute(fight.start, "Europe/Berlin") : fromAbsolute(event.start, "Europe/Berlin")); let fightErgebnis = $state(fight?.ergebnis ?? 0); let fightSpectatePort = $state(fight?.spectatePort?.toString() ?? null); let fightGroup = $state(fight?.group?.id ?? null); @@ -139,7 +139,7 @@ - + No map found. diff --git a/src/components/moderator/pages/event/EventFightList.svelte b/src/components/moderator/pages/event/EventFightList.svelte index b21aa9b..ab99fd3 100644 --- a/src/components/moderator/pages/event/EventFightList.svelte +++ b/src/components/moderator/pages/event/EventFightList.svelte @@ -34,6 +34,8 @@ import GroupEditDialog from "./GroupEditDialog.svelte"; import GroupResultsDialog from "./GroupResultsDialog.svelte"; import type { ResponseGroups } from "@type/event"; + import { EditIcon, GroupIcon, LinkIcon } from "lucide-svelte"; + import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@components/ui/dropdown-menu"; let { data = $bindable() }: { data: ExtendedEvent } = $props(); @@ -209,7 +211,7 @@ {#each table.getRowModel().rows as groupRow (groupRow.id)} {#if groupRow.getIsGrouped()} - {@const group = groups.find((g) => g.id === groupRow.getValue("group"))} + {@const group = groups.find((g) => g.id == groupRow.getValue("group"))} {group?.name ?? "Keine Gruppe"} - + + + + + + + + + navigator.clipboard.writeText(` `)} + >Punkte Tabelle + navigator.clipboard.writeText(` `)} + >Kampf Tabelle + + + {#each groupRow.subRows as row (row.id)} diff --git a/src/components/moderator/pages/event/GroupResultsDialog.svelte b/src/components/moderator/pages/event/GroupResultsDialog.svelte index e4d3ee8..cf626f0 100644 --- a/src/components/moderator/pages/event/GroupResultsDialog.svelte +++ b/src/components/moderator/pages/event/GroupResultsDialog.svelte @@ -28,10 +28,10 @@ {#each Object.entries(group.points).toSorted((a, b) => b[1] - a[1]) as [teamIdString, points] (teamIdString)} {@const teamId = Number(teamIdString)} - {@const team = teams.find((t) => t.id === teamId) as ResponseTeam} - {@const playedGames = fights.filter((f) => f.start > Date.now() && f.group?.id === group.id && (f.blueTeam.id === teamId || f.redTeam.id === teamId)).length} + {@const team = teams.find((t) => t.id === teamId) as ResponseTeam | undefined} + {@const playedGames = fights.filter((f) => f.hasFinished && f.group?.id === group.id && (f.blueTeam.id === teamId || f.redTeam.id === teamId)).length} - {team.name} ({team.kuerzel}) + {team?.name ?? "?"} ({team?.kuerzel ?? "?"}) {playedGames} {points} diff --git a/src/components/moderator/pages/event/columns.ts b/src/components/moderator/pages/event/columns.ts index 48927c2..a3d0eb3 100644 --- a/src/components/moderator/pages/event/columns.ts +++ b/src/components/moderator/pages/event/columns.ts @@ -90,7 +90,7 @@ export const columns: ColumnDef = [ accessorKey: "ergebnis", cell: ({ row }) => { const fight = row.original; - if (fight.ergebnis === 0 && fight.start > Date.now()) { + if (!fight.hasFinished) { return "Noch nicht gespielt"; } else if (fight.ergebnis === 1) { return fight.blueTeam.name + " hat gewonnen"; diff --git a/src/components/moderator/pages/events/Events.svelte b/src/components/moderator/pages/events/Events.svelte index 2c24b27..17340ca 100644 --- a/src/components/moderator/pages/events/Events.svelte +++ b/src/components/moderator/pages/events/Events.svelte @@ -94,7 +94,7 @@ } -
+

Events

diff --git a/src/components/moderator/pages/pages/EditorWithTabs.svelte b/src/components/moderator/pages/pages/EditorWithTabs.svelte new file mode 100644 index 0000000..6495cbb --- /dev/null +++ b/src/components/moderator/pages/pages/EditorWithTabs.svelte @@ -0,0 +1,99 @@ + + +
+
+ {#each manager.pages as tab, index} + {@const isActive = manager.openPageIndex === index} + + {/each} +
+ +
+ + +
+
diff --git a/src/components/moderator/pages/pages/Pages.svelte b/src/components/moderator/pages/pages/Pages.svelte new file mode 100644 index 0000000..9f359ae --- /dev/null +++ b/src/components/moderator/pages/pages/Pages.svelte @@ -0,0 +1,27 @@ + + +
+ + +
+ {#await manager.pagesLoad} +

Loading pages...

+ {:then pages} + {#each Object.values(pages.dirs) as page} + + {/each} + {/await} +
+
+ + + + +
+
diff --git a/src/components/moderator/pages/pages/PagesList.svelte b/src/components/moderator/pages/pages/PagesList.svelte new file mode 100644 index 0000000..5a730fd --- /dev/null +++ b/src/components/moderator/pages/pages/PagesList.svelte @@ -0,0 +1,116 @@ + + + +
+ + +{#if open} +
+
+ {#if newPage} + + {/if} + {#each Object.values(page.dirs) as subPage (subPage.name)} + + {/each} + {#each Object.values(page.files) as file (file.id)} + + {/each} +
+
+{/if} diff --git a/src/components/moderator/pages/pages/page.svelte.ts b/src/components/moderator/pages/pages/page.svelte.ts new file mode 100644 index 0000000..dab6b51 --- /dev/null +++ b/src/components/moderator/pages/pages/page.svelte.ts @@ -0,0 +1,163 @@ +import { base64ToBytes } from "@components/admin/util"; +import { pageRepo } from "@components/repo/page"; +import type { ListPage, PageList } from "@components/types/page"; +import { get } from "svelte/store"; + +export class OpenEditPage { + public content: string = ""; + public frontmatter: { [key: string]: string } = $state({}); + public dirty: boolean = $state(false); + + public readonly fileType: string; + + public constructor( + private manager: PageManager, + public readonly pageId: number, + public readonly pageTitle: string, + public readonly sha: string, + public readonly originalContent: string, + private readonly path: string + ) { + this.fileType = this.path.split(".").pop() || "md"; + + this.content = this.removeFrontmatter(originalContent); + this.frontmatter = this.parseFrontmatter(originalContent); + } + + public focus(): boolean { + let index = this.manager.pages.indexOf(this); + + if (index === this.manager.openPageIndex) { + return true; + } + + this.manager.openPageIndex = this.manager.pages.indexOf(this); + return false; + } + + private parseFrontmatter(content: string): { [key: string]: string } { + const frontmatter: { [key: string]: string } = {}; + const lines = content.split("\n"); + + for (const line of lines) { + const match = line.match(/^\s*([a-zA-Z0-9_-]+):\s*(.*)$/); + if (match) { + frontmatter[match[1]] = match[2].trim(); + } + } + + return frontmatter; + } + + private removeFrontmatter(content: string): string { + const lines = content.split("\n"); + let inFrontmatter = false; + const result: string[] = []; + + for (const line of lines) { + if (line.trim() === "---") { + inFrontmatter = !inFrontmatter; + continue; + } + if (!inFrontmatter) { + result.push(line); + } + } + + return result.join("\n").trim(); + } +} + +export interface DirTree { + name: string; + dirs: { [key: string]: DirTree }; + files: { [key: string]: ListPage }; +} + +export class PageManager { + public branch: string = $state("master"); + public pages: OpenEditPage[] = $state([]); + + public openPageIndex: number = $state(-1); + public pagesLoad = $derived(get(pageRepo).listPages(this.branch).then(this.convertToTree)); + + public selectedPage = $derived(this.openPageIndex >= 0 ? this.pages[this.openPageIndex] : undefined); + + private convertToTree(pages: PageList): DirTree { + const tree: DirTree = { dirs: {}, files: {}, name: "/" }; + + pages.forEach((page) => { + const pathParts = page.path.split("/").filter((part) => part !== ""); + let current = tree; + + // Navigate/create directory structure + for (let i = 0; i < pathParts.length - 1; i++) { + const dir = pathParts[i]; + if (!current.dirs[dir]) { + current.dirs[dir] = { dirs: {}, files: {}, name: dir }; + } + current = current.dirs[dir]; + } + + // Add file to the final directory + const fileName = pathParts[pathParts.length - 1]; + current.files[fileName] = page; + }); + + return tree; + } + + public async openPage(pageId: number) { + const existingPage = this.existingPage(pageId); + if (existingPage) { + existingPage.focus(); + return; + } + + let r = await get(pageRepo).getPage(pageId, this.branch); + if (!r) { + return; + } + + const newPage = new OpenEditPage(this, pageId, r.name, r.sha, new TextDecoder().decode(base64ToBytes(r.content)), r.path); + this.pages.push(newPage); + newPage.focus(); + } + + public existingPage(pageId: number): OpenEditPage | undefined { + return this.pages.find((page) => page.pageId === pageId); + } + + public closePage(index: number) { + if (index < 0 || index >= this.pages.length) { + return; + } + + const page = this.pages[index]; + if (page.dirty) { + if (!confirm(`The page "${page.pageTitle}" has unsaved changes. Are you sure you want to close it?`)) { + return; + } + } + + this.pages.splice(index, 1); + if (this.openPageIndex >= index) { + this.openPageIndex = Math.max(0, this.openPageIndex - 1); + } + + if (this.openPageIndex < 0 && this.pages.length > 0) { + this.openPageIndex = 0; + } + + if (this.pages.length === 0) { + this.openPageIndex = -1; + } + } + + public async createPage(path: string, newPageName: string): Promise { + await get(pageRepo).createFile(path, this.branch, newPageName, newPageName); + this.branch = this.branch; + } +} + +export const manager = $state(new PageManager()); diff --git a/src/components/types/event.ts b/src/components/types/event.ts index 919c615..90db0cc 100644 --- a/src/components/types/event.ts +++ b/src/components/types/event.ts @@ -41,6 +41,7 @@ export const EventFightSchema = z.object({ ergebnis: z.number(), spectatePort: z.number().nullable(), group: ResponseGroupsSchema.nullable(), + hasFinished: z.boolean(), }); export type EventFight = z.infer; @@ -48,6 +49,7 @@ export type EventFight = z.infer; export const EventFightEditSchema = EventFightSchema.omit({ id: true, group: true, + hasFinished: true, }).extend({ group: z.number().nullable(), }); diff --git a/src/pages/ankuendigungen/[...slug].astro b/src/pages/ankuendigungen/[...slug].astro index 814a82b..eca0dcc 100644 --- a/src/pages/ankuendigungen/[...slug].astro +++ b/src/pages/ankuendigungen/[...slug].astro @@ -1,29 +1,29 @@ --- -import {astroI18n, createGetStaticPaths} from "astro-i18n"; -import {getCollection, CollectionEntry} from "astro:content"; +import { astroI18n, createGetStaticPaths } from "astro-i18n"; +import { getCollection, CollectionEntry } from "astro:content"; import PageLayout from "@layouts/PageLayout.astro"; -import {TagSolid, CalendarMonthSolid} from "flowbite-svelte-icons"; +import { TagSolid, CalendarMonthSolid } from "flowbite-svelte-icons"; import TagComponent from "@components/TagComponent.astro"; import LanguageWarning from "@components/LanguageWarning.astro"; -import {SEO} from "astro-seo"; +import { SEO } from "astro-seo"; import localBau from "@images/2022-03-28_13.18.25.png"; -import {getImage, Image} from "astro:assets"; +import { getImage, Image } from "astro:assets"; import "@styles/table.css"; export const getStaticPaths = createGetStaticPaths(async () => { - const posts = await getCollection("announcements", entry => entry.id.split("/")[0] === astroI18n.locale); + const posts = await getCollection("announcements", (entry) => entry.id.split("/")[0] === astroI18n.locale); - const germanPosts = await getCollection("announcements", entry => entry.id.split("/")[0] === astroI18n.fallbackLocale); + const germanPosts = await getCollection("announcements", (entry) => entry.id.split("/")[0] === astroI18n.fallbackLocale); - germanPosts.forEach(value => { - if (posts.find(post => post.data.key === value.data.key)) { + germanPosts.forEach((value) => { + if (posts.find((post) => post.data.key === value.data.key)) { return; } else { posts.push(value); } }); - return posts.map(value => ({ + return posts.map((value) => ({ params: { slug: value.slug.split("/").slice(1).join("/"), }, @@ -35,12 +35,12 @@ export const getStaticPaths = createGetStaticPaths(async () => { }); interface Props { - post: CollectionEntry<"announcements">, - german: boolean + post: CollectionEntry<"announcements">; + german: boolean; } -const {post, german} = Astro.props; -const {Content} = await post.render(); +const { post, german } = Astro.props; +const { Content } = await post.render(); const ogImage = await getImage({ src: post.data.image || localBau, @@ -52,64 +52,66 @@ const ogImage = await getImage({ -
- {post.data.image && ( -
- -
- )} + { + post.data.image && ( +
+ +
+ ) + }

{post.data.title}

- +
- {post.data.tags.map(tag => ( - - ))} + {post.data.tags.map((tag) => )}
- - {Intl.DateTimeFormat(astroI18n.locale, { - day: "numeric", - month: "short", - year: "numeric", - }).format(post.data.created)} - {post.data.author && ( - - {post.data.author} - {post.data.author} - - )} + + { + Intl.DateTimeFormat(astroI18n.locale, { + day: "numeric", + month: "short", + year: "numeric", + }).format(post.data.created) + } + { + post.data.author && ( + + {post.data.author} + {post.data.author} + + ) + }
- {german && ( - - )} - + {german && } + + +
+ + Frontmatter + + + + +
+ {#each Object.entries(manager.selectedPage?.frontmatter || {}) as [key, value]} +
+
+ { + const newKey = (e.target as HTMLInputElement).value; + if (newKey !== key) { + manager.selectedPage!.frontmatter[newKey] = manager.selectedPage!.frontmatter[key]; + delete manager.selectedPage?.frontmatter[key]; + manager.selectedPage!.dirty = true; + } + }} + class="px-2 py-1 border rounded text-sm flex-shrink-0 w-32 bg-neutral-900" + placeholder="Key" + /> + : + {#if Array.isArray(value)} + Array ({value.length} items) + {:else if value instanceof Date || key === "created"} + { + const dateValue = (e.target as HTMLInputElement).value; + manager.selectedPage!.frontmatter[key] = dateValue ? new Date(dateValue) : ""; + manager.selectedPage!.dirty = true; + }} + class="px-2 py-1 border rounded text-sm flex-1 bg-neutral-900" + /> + {:else} + (manager.selectedPage!.dirty = true)} + class="px-2 py-1 border rounded text-sm flex-1 bg-neutral-900" + placeholder="Value" + /> + {/if} + +
+ {#if Array.isArray(value)} +
+ {#each value as item, index} +
+ [{index}] + (manager.selectedPage!.dirty = true)} + class="px-2 py-1 border rounded text-sm flex-1 bg-neutral-900" + placeholder="Array item" + /> + +
+ {/each} + +
+ {/if} +
+ {/each} +
+ + +
+
+
diff --git a/src/components/moderator/pages/pages/Pages.svelte b/src/components/moderator/pages/pages/Pages.svelte index 9f359ae..c71a8ec 100644 --- a/src/components/moderator/pages/pages/Pages.svelte +++ b/src/components/moderator/pages/pages/Pages.svelte @@ -1,15 +1,143 @@
+
+ + + {#snippet child({ props })} + + {/snippet} + + + + + + No Branches Found. + + {#each manager.branches as branch} + { + if (manager.anyUnsavedChanges()) { + if (!confirm("You have unsaved changes. Are you sure you want to switch branches?")) { + return; + } + } + + manager.branch = branch; + manager.pages = []; + branchSelectOpen = false; + }} + > + + {branch} + + {/each} + + + + + + + + + {#snippet child({ props })} + + {/snippet} + + + {#await manager.imagesLoad} +

Loading images...

+ {:then images} +
+
+ { + const file = e.target?.files?.[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = async (event) => { + const base64 = event.target?.result?.toString().split(",")[1]; + if (base64) { + await $pageRepo.createImage(file.name, base64, manager.branch); + manager.reloadImages(); + } + }; + reader.readAsDataURL(file); + }} + /> + +
+
+ {#each images as image} + + {/each} +
+
+ {/await} +
+
+ +
+ {#await manager.pagesLoad}

Loading pages...

{:then pages} diff --git a/src/components/moderator/pages/pages/page.svelte.ts b/src/components/moderator/pages/pages/page.svelte.ts index dab6b51..dca0162 100644 --- a/src/components/moderator/pages/pages/page.svelte.ts +++ b/src/components/moderator/pages/pages/page.svelte.ts @@ -2,10 +2,11 @@ import { base64ToBytes } from "@components/admin/util"; import { pageRepo } from "@components/repo/page"; import type { ListPage, PageList } from "@components/types/page"; import { get } from "svelte/store"; +import yaml from "js-yaml"; export class OpenEditPage { public content: string = ""; - public frontmatter: { [key: string]: string } = $state({}); + public frontmatter: { [key: string]: string | string[] | Date } = $state({}); public dirty: boolean = $state(false); public readonly fileType: string; @@ -16,7 +17,7 @@ export class OpenEditPage { public readonly pageTitle: string, public readonly sha: string, public readonly originalContent: string, - private readonly path: string + public readonly path: string ) { this.fileType = this.path.split(".").pop() || "md"; @@ -24,6 +25,24 @@ export class OpenEditPage { this.frontmatter = this.parseFrontmatter(originalContent); } + public async save(): Promise { + if (!this.dirty) { + return; + } + + let contentToSave = ""; + if (this.frontmatter) { + contentToSave += "---\n"; + contentToSave += yaml.dump(this.frontmatter); + contentToSave += "---\n\n"; + } + contentToSave += this.content; + const encodedContent = btoa(new TextEncoder().encode(contentToSave).reduce((data, byte) => data + String.fromCharCode(byte), "")); + await get(pageRepo).updatePage(this.pageId, this.sha, encodedContent, this.manager.branch); + this.dirty = false; + this.manager.reloadImages(); + } + public focus(): boolean { let index = this.manager.pages.indexOf(this); @@ -35,18 +54,35 @@ export class OpenEditPage { return false; } - private parseFrontmatter(content: string): { [key: string]: string } { - const frontmatter: { [key: string]: string } = {}; + private parseFrontmatter(content: string): { [key: string]: string | string[] | Date } { const lines = content.split("\n"); + let inFrontmatter = false; + const frontmatterLines: string[] = []; for (const line of lines) { - const match = line.match(/^\s*([a-zA-Z0-9_-]+):\s*(.*)$/); - if (match) { - frontmatter[match[1]] = match[2].trim(); + if (line.trim() === "---") { + if (inFrontmatter) { + break; // End of frontmatter + } + inFrontmatter = true; + continue; + } + if (inFrontmatter) { + frontmatterLines.push(line); } } - return frontmatter; + if (frontmatterLines.length === 0) { + return {}; + } + + try { + // You'll need to install js-yaml: npm install js-yaml @types/js-yaml + return (yaml.load(frontmatterLines.join("\n")) || {}) as { [key: string]: string | string[] | Date }; + } catch (error) { + console.error("Failed to parse YAML frontmatter:", error); + return {}; + } } private removeFrontmatter(content: string): string { @@ -75,11 +111,34 @@ export interface DirTree { } export class PageManager { + public reloadImages() { + this.updater = this.updater + 1; + } public branch: string = $state("master"); public pages: OpenEditPage[] = $state([]); + public branches: string[] = $state([]); + + constructor() { + this.reloadBranches(); + } + + public reloadBranches() { + get(pageRepo) + .getBranches() + .then((branches) => { + this.branches = branches; + }); + } + + private updater = $state(0); public openPageIndex: number = $state(-1); - public pagesLoad = $derived(get(pageRepo).listPages(this.branch).then(this.convertToTree)); + public pagesLoad = $derived(get(pageRepo).listPages(this.branch).then(this.convertToTree).then(this._t(this.updater))); + public imagesLoad = $derived(get(pageRepo).listImages(this.branch).then(this._t(this.updater))); + + private _t(n: number): (v: T) => T { + return (v: T) => v; + } public selectedPage = $derived(this.openPageIndex >= 0 ? this.pages[this.openPageIndex] : undefined); @@ -158,6 +217,10 @@ export class PageManager { await get(pageRepo).createFile(path, this.branch, newPageName, newPageName); this.branch = this.branch; } + + public anyUnsavedChanges() { + return this.pages.some((page) => page.dirty); + } } export const manager = $state(new PageManager()); diff --git a/src/components/repo/page.ts b/src/components/repo/page.ts index f42b1d8..f3373b2 100644 --- a/src/components/repo/page.ts +++ b/src/components/repo/page.ts @@ -17,27 +17,26 @@ * along with this program. If not, see . */ -import type {Page, PageList} from "@type/page.ts"; -import {fetchWithToken, tokenStore} from "./repo.ts"; -import {PageListSchema, PageSchema} from "@type/page.ts"; -import {bytesToBase64} from "../admin/util.ts"; -import {z} from "zod"; -import {derived} from "svelte/store"; +import type { Page, PageList } from "@type/page.ts"; +import { fetchWithToken, tokenStore } from "./repo.ts"; +import { PageListSchema, PageSchema } from "@type/page.ts"; +import { bytesToBase64 } from "../admin/util.ts"; +import { z } from "zod"; +import { derived } from "svelte/store"; export class PageRepo { - constructor(private token: string) { - } + constructor(private token: string) {} public async listPages(branch: string = "master"): Promise { return await fetchWithToken(this.token, `/page?branch=${branch}`) - .then(value => value.json()) + .then((value) => value.json()) .then(PageListSchema.parse) - .then(value => value.map(value1 => ({...value1, path: value1.path.replace("src/content/", "")}))); + .then((value) => value.map((value1) => ({ ...value1, path: value1.path.replace("src/content/", "") }))); } public async getPage(id: number, branch: string = "master"): Promise { return await fetchWithToken(this.token, `/page/${id}?branch=${branch}`) - .then(value => value.json()) + .then((value) => value.json()) .then(PageSchema.parse); } @@ -46,42 +45,57 @@ export class PageRepo { method: "PUT", body: JSON.stringify({ content: bytesToBase64(new TextEncoder().encode(content)), - sha, message, + sha, + message, }), }); } public async getBranches(): Promise { return await fetchWithToken(this.token, "/page/branch") - .then(value => value.json()) - .then(value => z.array(z.string()).parse(value)); + .then((value) => value.json()) + .then((value) => z.array(z.string()).parse(value)); } public async createBranch(branch: string): Promise { - await fetchWithToken(this.token, "/page/branch", {method: "POST", body: JSON.stringify({branch})}); + await fetchWithToken(this.token, "/page/branch", { method: "POST", body: JSON.stringify({ branch }) }); } public async deleteBranch(branch: string): Promise { - await fetchWithToken(this.token, "/page/branch", {method: "DELETE", body: JSON.stringify({branch})}); + await fetchWithToken(this.token, "/page/branch", { method: "DELETE", body: JSON.stringify({ branch }) }); } public async createFile(path: string, branch: string = "master", slug: string | null = null, title: string | null = null): Promise { - await fetchWithToken(this.token, `/page?branch=${branch}`, {method: "POST", body: JSON.stringify({path, slug, title})}); + await fetchWithToken(this.token, `/page?branch=${branch}`, { method: "POST", body: JSON.stringify({ path, slug, title }) }); } public async merge(branch: string, message: string): Promise { await fetchWithToken(this.token, "/page/branch/merge", { method: "POST", - body: JSON.stringify({branch, message}), + body: JSON.stringify({ branch, message }), }); } public async deletePage(id: number, message: string, sha: string, branch: string = "master"): Promise { await fetchWithToken(this.token, `/page/${id}?branch=${branch}`, { method: "DELETE", - body: JSON.stringify({message, sha}), + body: JSON.stringify({ message, sha }), + }); + } + + public async listImages(branch: string = "master"): Promise { + return await fetchWithToken(this.token, `/page/images?branch=${branch}`) + .then((value) => value.json()) + .then(PageListSchema.parse) + .then((value) => value.map((value1) => ({ ...value1, path: value1.path.replace("src/content/", "") }))); + } + + public async createImage(name: string, data: string, branch: string = "master"): Promise { + await fetchWithToken(this.token, `/page/images?branch=${branch}`, { + method: "POST", + body: JSON.stringify({ name, data }), }); } } -export const pageRepo = derived(tokenStore, ($token) => new PageRepo($token)); \ No newline at end of file +export const pageRepo = derived(tokenStore, ($token) => new PageRepo($token)); From bd1c4f7f4533e9c872c352124c7474b1ea742b45 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Wed, 4 Jun 2025 11:33:11 +0200 Subject: [PATCH 9/9] feat: Refactor event management components and introduce EventModel for better state handling --- src/components/dashboard/UserInfo.svelte | 34 ++-- .../moderator/components/FightEdit.svelte | 176 +++++++----------- .../moderator/components/GroupSelector.svelte | 103 ++++++++++ .../moderator/pages/event/Event.svelte | 25 ++- .../pages/event/EventFightList.svelte | 78 ++++++-- .../moderator/pages/event/EventView.svelte | 6 +- .../moderator/pages/event/TeamTable.svelte | 18 +- .../pages/event/eventmodel.svelte.ts | 21 +++ .../moderator/pages/pages/page.svelte.ts | 4 +- 9 files changed, 301 insertions(+), 164 deletions(-) create mode 100644 src/components/moderator/components/GroupSelector.svelte create mode 100644 src/components/moderator/pages/event/eventmodel.svelte.ts diff --git a/src/components/dashboard/UserInfo.svelte b/src/components/dashboard/UserInfo.svelte index aeb8a20..024a585 100644 --- a/src/components/dashboard/UserInfo.svelte +++ b/src/components/dashboard/UserInfo.svelte @@ -18,19 +18,19 @@ -->
@@ -175,19 +166,34 @@ No team found. - { - fightBlueTeam = { - id: -1, - name: "?", - color: "7", - kuerzel: "?", - }; - blueTeamSelectOpen = false; - }} - keywords={["?"]}>??? + + { + fightBlueTeam = { + id: -1, + name: "?", + color: "7", + kuerzel: "?", + }; + blueTeamSelectOpen = false; + }} + keywords={["?"]}>??? + { + fightBlueTeam = { + id: 0, + name: "Public", + color: "7", + kuerzel: "PUB", + }; + blueTeamSelectOpen = false; + }} + keywords={["PUB", "Public"]}>PUB + {#each teams as team} - + {team.name} {/each} @@ -221,19 +227,34 @@ No team found. - { - fightRedTeam = { - id: -1, - name: "?", - color: "7", - kuerzel: "?", - }; - redTeamSelectOpen = false; - }} - keywords={["?"]}>??? + + { + fightRedTeam = { + id: -1, + name: "?", + color: "7", + kuerzel: "?", + }; + redTeamSelectOpen = false; + }} + keywords={["?"]}>??? + { + fightRedTeam = { + id: 0, + name: "Public", + color: "7", + kuerzel: "PUB", + }; + redTeamSelectOpen = false; + }} + keywords={["PUB", "Public"]}>PUB + {#each teams as team} - + {team.name} {/each} @@ -269,74 +290,7 @@ {/if} - - - - {#snippet child({ props })} - - {/snippet} - - - - - - - (createOpen = true)}> - - Neue Gruppe - - - - { - fightGroup = null; - groupSelectOpen = false; - }} - > - {#if fightGroup === null} - - {:else} - - {/if} - Keine Gruppe - - - {#each groups as group} - { - fightGroup = group.id; - groupSelectOpen = false; - }} - > - - {group.name} - - {/each} - - - - - - - - - Neue Gruppe erstellen - Hier kannst du eine neue Gruppe erstellen - - - {#snippet actions(dirty, submit)} - - - - {/snippet} - - - +
diff --git a/src/components/moderator/components/GroupSelector.svelte b/src/components/moderator/components/GroupSelector.svelte new file mode 100644 index 0000000..f4fe463 --- /dev/null +++ b/src/components/moderator/components/GroupSelector.svelte @@ -0,0 +1,103 @@ + + + + + + {#snippet child({ props })} + + {/snippet} + + + + + + + (createOpen = true)}> + + Neue Gruppe + + + + { + value = null; + groupSelectOpen = false; + }} + > + {#if value === null} + + {:else} + + {/if} + Keine Gruppe + + + {#each groups as group} + { + value = group.id; + groupSelectOpen = false; + }} + > + + {group.name} + + {/each} + + + + + + + + + Neue Gruppe erstellen + Hier kannst du eine neue Gruppe erstellen + + + {#snippet actions(dirty, submit)} + + + + {/snippet} + + + diff --git a/src/components/moderator/pages/event/Event.svelte b/src/components/moderator/pages/event/Event.svelte index b9b8cf5..273e42c 100644 --- a/src/components/moderator/pages/event/Event.svelte +++ b/src/components/moderator/pages/event/Event.svelte @@ -18,8 +18,11 @@ --> -{#await event} +{#if loaded} + +{:else}

Loading...

-{:then data} - -{/await} \ No newline at end of file +{/if} diff --git a/src/components/moderator/pages/event/EventFightList.svelte b/src/components/moderator/pages/event/EventFightList.svelte index ab99fd3..0aa8e7e 100644 --- a/src/components/moderator/pages/event/EventFightList.svelte +++ b/src/components/moderator/pages/event/EventFightList.svelte @@ -20,7 +20,7 @@ @@ -146,19 +146,53 @@ {#if selectedGroup} - + {/if} {#if selectedGroupForResults} - + {/if} + + + + Gruppe Ändern + Hier kannst du die Gruppe der ausgewählten Kämpfe ändern + + + + + + + +
Mehrfach Bearbeiten - Gruppe Ändern + (groupChangeOpen = true)}>Gruppe Ändern Startzeit Verschieben Spectate Port Ändern @@ -175,9 +209,9 @@ - Gruppen + Gruppen - {#each groups as group (group.id)} + {#each data.groups as group (group.id)} {group.name} @@ -191,7 +225,7 @@ - +
@@ -205,13 +239,14 @@ {/if} {/each} + {/each} {#each table.getRowModel().rows as groupRow (groupRow.id)} {#if groupRow.getIsGrouped()} - {@const group = groups.find((g) => g.id == groupRow.getValue("group"))} + {@const group = data.groups.find((g) => g.id == groupRow.getValue("group"))} {/each} - (fights = fights.map((v) => (v.id === update.id ? update : v)))} + (data.fights = data.fights.map((v) => (v.id === update.id ? update : v)))} > diff --git a/src/components/moderator/pages/event/EventView.svelte b/src/components/moderator/pages/event/EventView.svelte index d396b90..0b6bedd 100644 --- a/src/components/moderator/pages/event/EventView.svelte +++ b/src/components/moderator/pages/event/EventView.svelte @@ -18,13 +18,13 @@ -->
@@ -42,5 +42,5 @@
- + diff --git a/src/components/moderator/pages/event/TeamTable.svelte b/src/components/moderator/pages/event/TeamTable.svelte index 5f06d78..14463f8 100644 --- a/src/components/moderator/pages/event/TeamTable.svelte +++ b/src/components/moderator/pages/event/TeamTable.svelte @@ -25,21 +25,19 @@ import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command"; import { teams } from "@components/stores/stores"; + import type { Team } from "@components/types/team"; + import type { EventModel } from "./eventmodel.svelte"; - const { event = $bindable() }: { event: ExtendedEvent } = $props(); - - let team = $state(event.teams); + let { event = $bindable() }: { event: EventModel } = $props(); async function addTeam(value: number) { await $eventRepo.updateTeams(event.event.id.toString(), [value]); - team = await $eventRepo.listTeams(event.event.id.toString()); - event.teams = team; + event.teams = await $eventRepo.listTeams(event.event.id.toString()); } async function removeTeam(value: number) { await $eventRepo.deleteTeams(event.event.id.toString(), [value]); - team = await $eventRepo.listTeams(event.event.id.toString()); - event.teams = team; + event.teams = await $eventRepo.listTeams(event.event.id.toString()); } let teamSearch = $state(""); @@ -54,7 +52,7 @@ - {#each team as t (t.id)} + {#each event.teams as t (t.id)} {t.kuerzel} {t.name} @@ -63,7 +61,7 @@ {/each} - {#if team.length === 0} + {#if event.teams.length === 0} No teams available @@ -83,7 +81,7 @@ {#each $teams .filter((v) => v.name.includes(teamSearch)) - .filter((v) => !team.some((k) => k.id === v.id)) + .filter((v) => !event.teams.some((k) => k.id === v.id)) .filter((v, i) => i < 50) as t (t.id)} addTeam(t.id)} keywords={[t.name, t.kuerzel]}>{t.name} {/each} diff --git a/src/components/moderator/pages/event/eventmodel.svelte.ts b/src/components/moderator/pages/event/eventmodel.svelte.ts new file mode 100644 index 0000000..731fbc3 --- /dev/null +++ b/src/components/moderator/pages/event/eventmodel.svelte.ts @@ -0,0 +1,21 @@ +import type { ResponseUser } from "@components/repo/event"; +import type { EventFight, ExtendedEvent, ResponseGroups, ResponseRelation, SWEvent } from "@components/types/event"; +import type { Team } from "@components/types/team"; + +export class EventModel { + public event: SWEvent = $state({} as SWEvent); + public teams: Array = $state([]); + public groups: Array = $state([]); + public fights: Array = $state([]); + public referees: Array = $state([]); + public relations: Array = $state([]); + + constructor(data: ExtendedEvent) { + this.event = data.event; + this.teams = data.teams; + this.groups = data.groups; + this.fights = data.fights; + this.referees = data.referees; + this.relations = data.relations; + } +} diff --git a/src/components/moderator/pages/pages/page.svelte.ts b/src/components/moderator/pages/pages/page.svelte.ts index dca0162..1487f77 100644 --- a/src/components/moderator/pages/pages/page.svelte.ts +++ b/src/components/moderator/pages/pages/page.svelte.ts @@ -38,7 +38,9 @@ export class OpenEditPage { } contentToSave += this.content; const encodedContent = btoa(new TextEncoder().encode(contentToSave).reduce((data, byte) => data + String.fromCharCode(byte), "")); - await get(pageRepo).updatePage(this.pageId, this.sha, encodedContent, this.manager.branch); + + console.log(encodedContent); + //await get(pageRepo).updatePage(this.pageId, this.sha, encodedContent, this.manager.branch); this.dirty = false; this.manager.reloadImages(); }