Code Cleanup™
Alle Prüfungen waren erfolgreich
SteamWarCI Build successful

Dieser Commit ist enthalten in:
Chaoscaot 2024-02-11 11:16:23 +01:00
Ursprung 4b27eb76fe
Commit 9fd8ddb9bd
62 geänderte Dateien mit 663 neuen und 519 gelöschten Zeilen

2
.eslintignore Normale Datei
Datei anzeigen

@ -0,0 +1,2 @@
src/pages/de
src/env.d.ts

Datei anzeigen

@ -48,6 +48,15 @@
"semi": [ "semi": [
"error", "error",
"always" "always"
] ],
"no-console": "error",
"no-debugger": "error",
"no-alert": "error",
"no-undef": "error",
"no-var": "error",
"no-const-assign": "error",
"comma-dangle": ["error", "always-multiline"],
"no-unneeded-ternary": "error",
"multiline-ternary": ["error", "always-multiline"]
} }
} }

Datei anzeigen

@ -15,17 +15,19 @@ const { post } = Astro.props as Props;
<a href={l(`/announcements/${post.slug.split("/").slice(1).join("/")}`)}> <a href={l(`/announcements/${post.slug.split("/").slice(1).join("/")}`)}>
<div class="p-4 flex flex-row"> <div class="p-4 flex flex-row">
{post.data.image != null ? ( {post.data.image != null
? (
<div class="flex-shrink-0 pr-2"> <div class="flex-shrink-0 pr-2">
<Image src={post.data.image} alt="Post Image" class="rounded-2xl shadow-2xl object-cover h-32 w-32 max-w-none transition-transform hover:scale-105" /> <Image src={post.data.image} alt="Post Image" class="rounded-2xl shadow-2xl object-cover h-32 w-32 max-w-none transition-transform hover:scale-105" />
</div> </div>
) : null} )
: null}
<div> <div>
<h2 class="text-2xl font-bold">{post.data.title}</h2> <h2 class="text-2xl font-bold">{post.data.title}</h2>
<P class="text-gray-500">{Intl.DateTimeFormat(astroI18n.locale, { <P class="text-gray-500">{Intl.DateTimeFormat(astroI18n.locale, {
day: "numeric", day: "numeric",
month: "long", month: "long",
year: "numeric" year: "numeric",
}).format(post.data.created)}</P> }).format(post.data.created)}</P>
<P>{post.data.description}</P> <P>{post.data.description}</P>
<div class="mt-1"> <div class="mt-1">

Datei anzeigen

@ -25,24 +25,39 @@
import {tokenStore} from "@repo/repo"; import {tokenStore} from "@repo/repo";
const routes: RouteDefinition = { const routes: RouteDefinition = {
'/': wrap({asyncComponent: () => import('./pages/Home.svelte'), conditions: detail => get(tokenStore) != ""}), "/": wrap({asyncComponent: () => import("./pages/Home.svelte"), conditions: detail => get(tokenStore) != ""}),
'/perms': wrap({asyncComponent: () => import('./pages/Perms.svelte'), conditions: detail => get(tokenStore) != ""}), "/perms": wrap({
'/login': wrap({asyncComponent: () => import('./pages/Login.svelte'), conditions: detail => get(tokenStore) == ""}), asyncComponent: () => import("./pages/Perms.svelte"),
'/event/:id': wrap({asyncComponent: () => import('./pages/Event.svelte'), conditions: detail => get(tokenStore) != ""}), conditions: detail => get(tokenStore) != ""
'/event/:id/generate': wrap({asyncComponent: () => import('./pages/Generate.svelte'), conditions: detail => get(tokenStore) != ""}), }),
'/edit': wrap({asyncComponent: () => import('./pages/Edit.svelte'), conditions: detail => get(tokenStore) != ""}), "/login": wrap({
'*': wrap({asyncComponent: () => import('./pages/NotFound.svelte')}) asyncComponent: () => import("./pages/Login.svelte"),
} conditions: detail => get(tokenStore) == ""
}),
"/event/:id": wrap({
asyncComponent: () => import("./pages/Event.svelte"),
conditions: detail => get(tokenStore) != ""
}),
"/event/:id/generate": wrap({
asyncComponent: () => import("./pages/Generate.svelte"),
conditions: detail => get(tokenStore) != ""
}),
"/edit": wrap({
asyncComponent: () => import("./pages/Edit.svelte"),
conditions: detail => get(tokenStore) != ""
}),
"*": wrap({asyncComponent: () => import("./pages/NotFound.svelte")})
};
function conditionsFailed(event: ConditionsFailedEvent) { function conditionsFailed(event: ConditionsFailedEvent) {
if(event.detail.location === "/login") { if (event.detail.location === "/login") {
replace("/") replace("/");
} else { } else {
replace("/login") replace("/login");
} }
} }
</script> </script>
<main class="dark:bg-gray-900 min-w-full min-h-screen text-gray-900 dark:text-gray-300"> <main class="dark:bg-gray-900 min-w-full min-h-screen text-gray-900 dark:text-gray-300">
<Router {routes} on:conditionsFailed={conditionsFailed} /> <Router {routes} on:conditionsFailed={conditionsFailed}/>
</main> </main>

Datei anzeigen

@ -37,48 +37,48 @@
return { return {
name: player.name, name: player.name,
value: player.id.toString() value: player.id.toString()
} };
}).sort((a, b) => a.name.localeCompare(b.name)); }).sort((a, b) => a.name.localeCompare(b.name));
$: selectableTeams = teams.map(team => { $: selectableTeams = teams.map(team => {
return { return {
name: team.name, name: team.name,
value: team.id.toString() value: team.id.toString()
} };
}).sort((a, b) => a.name.localeCompare(b.name)); }).sort((a, b) => a.name.localeCompare(b.name));
$: selectableGamemodes = $gamemodes.map(gamemode => { $: selectableGamemodes = $gamemodes.map(gamemode => {
return { return {
name: gamemode, name: gamemode,
value: gamemode value: gamemode
} };
}).sort((a, b) => a.name.localeCompare(b.name)); }).sort((a, b) => a.name.localeCompare(b.name));
$: customGamemode = !selectableGamemodes.some((e) => e.name === gamemode) && gamemode !== ''; $: customGamemode = !selectableGamemodes.some((e) => e.name === gamemode) && gamemode !== "";
$: selectableCustomGamemode = [ $: selectableCustomGamemode = [
...selectableGamemodes, { ...selectableGamemodes, {
name: gamemode + ' (custom)', name: gamemode + " (custom)",
value: gamemode value: gamemode
} }
] ];
$: mapsStore = maps(gamemode); $: mapsStore = maps(gamemode);
$: selectableMaps = $mapsStore.map(map => { $: selectableMaps = $mapsStore.map(map => {
return { return {
name: map, name: map,
value: map value: map
} };
}).sort((a, b) => a.name.localeCompare(b.name)); }).sort((a, b) => a.name.localeCompare(b.name));
$: customMap = !selectableMaps.some((e) => e.name === map) && map !== '' $: customMap = !selectableMaps.some((e) => e.name === map) && map !== "";
$: selectableCustomMaps = [ $: selectableCustomMaps = [
...selectableMaps, { ...selectableMaps, {
name: map + ' (custom)', name: map + " (custom)",
value: map value: map
} }
] ];
$: selectableGroups = [{ $: selectableGroups = [{
name: 'None', name: "None",
value: '' value: ""
}, { }, {
value: groupSearch, value: groupSearch,
name: `Create: '${groupSearch}'` name: `Create: '${groupSearch}'`
@ -86,7 +86,7 @@
return { return {
name: group, name: group,
value: group value: group
} };
}).sort((a, b) => a.name.localeCompare(b.name))]; }).sort((a, b) => a.name.localeCompare(b.name))];
</script> </script>
@ -106,11 +106,13 @@
</div> </div>
<div class="m-2"> <div class="m-2">
<Label for="fight-gamemode">Gamemode</Label> <Label for="fight-gamemode">Gamemode</Label>
<Select items={customGamemode ? selectableCustomGamemode : selectableGamemodes} bind:value={gamemode} id="fight-gamemode"></Select> <Select items={customGamemode ? selectableCustomGamemode : selectableGamemodes} bind:value={gamemode}
id="fight-gamemode"></Select>
</div> </div>
<div class="m-2"> <div class="m-2">
<Label for="fight-maps">Map</Label> <Label for="fight-maps">Map</Label>
<Select items={customMap ? selectableCustomMaps : selectableMaps} bind:value={map} id="fight-maps" disabled={customGamemode} class={customGamemode ? "cursor-not-allowed" : ""}></Select> <Select items={customMap ? selectableCustomMaps : selectableMaps} bind:value={map} id="fight-maps"
disabled={customGamemode} class={customGamemode ? "cursor-not-allowed" : ""}></Select>
</div> </div>
<div class="m-2"> <div class="m-2">
<Label for="fight-kampf">Kampfleiter</Label> <Label for="fight-kampf">Kampfleiter</Label>
@ -118,5 +120,6 @@
</div> </div>
<div class="m-2"> <div class="m-2">
<Label for="fight-kampf">Group</Label> <Label for="fight-kampf">Group</Label>
<TypeAheadSearch items={selectableGroups} bind:selected={group} bind:searchValue={groupSearch} all></TypeAheadSearch> <TypeAheadSearch items={selectableGroups} bind:selected={group} bind:searchValue={groupSearch}
all></TypeAheadSearch>
</div> </div>

Datei anzeigen

@ -18,32 +18,35 @@
--> -->
<script lang="ts"> <script lang="ts">
import {Button, Dropdown, Search} from 'flowbite-svelte' import {Button, Dropdown, Search} from "flowbite-svelte";
export let selected: string | null = null export let selected: string | null = null;
export let items: {name: string, value: string}[] = [] export let items: { name: string, value: string }[] = [];
export let maxItems = 5; export let maxItems = 5;
export let leftText: boolean = false; export let leftText: boolean = false;
export let searchValue = items.find(item => item.value === selected)?.name || '' export let searchValue = items.find(item => item.value === selected)?.name || "";
let open = false let open = false;
$: filteredItems = items.filter(item => item.name.toLowerCase().includes(searchValue.toLowerCase())).filter((value, index) => index < maxItems) $: filteredItems = items.filter(item => item.name.toLowerCase().includes(searchValue.toLowerCase())).filter((value, index) => index < maxItems);
function selectItem(item: {name: string, value: string}) { function selectItem(item: { name: string, value: string }) {
selected = item.value selected = item.value;
searchValue = "" searchValue = "";
open = false open = false;
} }
</script> </script>
<Button color="light" on:click={() => open = true}>{selected === null ? 'Auswählen' : items.find(value => value.value === selected)?.name}</Button> <Button color="light"
on:click={() => open = true}>{selected === null ? 'Auswählen' : items.find(value => value.value === selected)?.name}</Button>
<Dropdown bind:open class="w-60"> <Dropdown bind:open class="w-60">
<div class="overflow-y-auto p-3 text-sm w-60" slot="header"> <div class="overflow-y-auto p-3 text-sm w-60" slot="header">
<Search bind:value={searchValue} on:focus={() => open = true} on:keydown={() => open = true}/> <Search bind:value={searchValue} on:focus={() => open = true} on:keydown={() => open = true}/>
</div> </div>
{#each filteredItems as item (item)} {#each filteredItems as item (item)}
<button on:click={() => selectItem(item)} class="rounded p-2 hover:bg-gray-100 dark:hover:bg-gray-600 w-full cursor-pointer border-b border-b-gray-600" class:text-left={leftText}> <button on:click={() => selectItem(item)}
class="rounded p-2 hover:bg-gray-100 dark:hover:bg-gray-600 w-full cursor-pointer border-b border-b-gray-600"
class:text-left={leftText}>
{item.name} {item.name}
</button> </button>
{/each} {/each}

Datei anzeigen

@ -40,68 +40,69 @@
$: availableBranches = $branches.map((branch) => ({ $: availableBranches = $branches.map((branch) => ({
name: branch, name: branch,
value: branch value: branch
})) }));
async function createBranch() { async function createBranch() {
const name = prompt("Branch name:") const name = prompt("Branch name:");
if (name) { if (name) {
selected = null selected = null;
await $pageRepo.createBranch(name) await $pageRepo.createBranch(name);
let inter = setInterval(() => { let inter = setInterval(() => {
branches.reload() branches.reload();
if ($branches.includes(name)) { if ($branches.includes(name)) {
selectedBranch = name selectedBranch = name;
searchValue = "" searchValue = "";
clearInterval(inter) clearInterval(inter);
} }
}, 1000) }, 1000);
} }
} }
function changePage(id: number) { function changePage(id: number) {
if (dirty) { if (dirty) {
if (confirm("You have unsaved changes. Are you sure you want to change the page?")) { if (confirm("You have unsaved changes. Are you sure you want to change the page?")) {
selected = id selected = id;
dirty = false dirty = false;
} }
} else { } else {
selected = id selected = id;
} }
} }
async function deleteBranch(con: boolean) { async function deleteBranch(con: boolean) {
if (selectedBranch !== "master") { if (selectedBranch !== "master") {
let conf = con || confirm("Are you sure you want to delete this branch?") let conf = con || confirm("Are you sure you want to delete this branch?");
if(conf) { if (conf) {
await $pageRepo.deleteBranch(selectedBranch) await $pageRepo.deleteBranch(selectedBranch);
let inter = setInterval(() => { let inter = setInterval(() => {
branches.reload() branches.reload();
if (!$branches.includes(selectedBranch)) { if (!$branches.includes(selectedBranch)) {
selectedBranch = "master" selectedBranch = "master";
searchValue = "" searchValue = "";
clearInterval(inter) clearInterval(inter);
} }
}, 1000) }, 1000);
} }
} else { } else {
alert("You can't delete the master branch") alert("You can't delete the master branch");
} }
} }
async function createFile() { async function createFile() {
let name = prompt("File name:", "[Name].md") let name = prompt("File name:", "[Name].md");
if (name) { if (name) {
await $pageRepo.createFile(`${selectedPath}${name}`, selectedBranch) await $pageRepo.createFile(`${selectedPath}${name}`, selectedBranch);
reload() reload();
} }
} }
function reload() { function reload() {
const w = selectedBranch const w = selectedBranch;
selectedBranch = "###!" selectedBranch = "###!";
selectedBranch = w selectedBranch = w;
} }
</script> </script>
<div class="flex flex-col h-screen overflow-scroll"> <div class="flex flex-col h-screen overflow-scroll">
<Navbar> <Navbar>
<NavBrand href="#"> <NavBrand href="#">
@ -115,17 +116,20 @@
<div class="grid md:grid-cols-3 grid-cols-1 h-full gap-8"> <div class="grid md:grid-cols-3 grid-cols-1 h-full gap-8">
<Card class="h-full flex flex-col !max-w-full"> <Card class="h-full flex flex-col !max-w-full">
{#await pagesFuture} {#await pagesFuture}
<Spinner /> <Spinner/>
{:then pages} {:then pages}
{@const pagesMap = mapToMap(pages)} {@const pagesMap = mapToMap(pages)}
<div class="border-b border-b-gray-600 pb-2 flex justify-between"> <div class="border-b border-b-gray-600 pb-2 flex justify-between">
<div> <div>
<TypeAheadSearch items={availableBranches} bind:selected={selectedBranch} bind:searchValue /> <TypeAheadSearch items={availableBranches} bind:selected={selectedBranch} bind:searchValue/>
<TypeAheadSearch items={Array.from(pagesMap.keys()).map(value => ({value, name: value}))} bind:selected={selectedPath} bind:searchValue={pathSearchValue} maxItems={Number.MAX_VALUE} leftText={true} /> <TypeAheadSearch items={Array.from(pagesMap.keys()).map(value => ({value, name: value}))}
bind:selected={selectedPath} bind:searchValue={pathSearchValue}
maxItems={Number.MAX_VALUE} leftText={true}/>
</div> </div>
<div> <div>
{#if selectedBranch !== "master"} {#if selectedBranch !== "master"}
<Button on:click={createFile} color="alternative" disabled={!selectedPath}>Create File</Button> <Button on:click={createFile} color="alternative" disabled={!selectedPath}>Create File
</Button>
<Button on:click={() => deleteBranch(false)} color="none">Delete Branch</Button> <Button on:click={() => deleteBranch(false)} color="none">Delete Branch</Button>
{:else} {:else}
<Button on:click={createBranch}>Create Branch</Button> <Button on:click={createBranch}>Create Branch</Button>
@ -140,8 +144,12 @@
{@const match = nameRegexExec ? nameRegexExec[0] : ""} {@const match = nameRegexExec ? nameRegexExec[0] : ""}
{@const startIndex = page.path.indexOf(match)} {@const startIndex = page.path.indexOf(match)}
{@const endIndex = startIndex + match.length} {@const endIndex = startIndex + match.length}
<li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer" on:click|preventDefault={() => changePage(page.id)}> <li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer"
<span class:text-orange-600={selected === page.id}>{page.path.substring(0, startIndex)}</span><span class="text-white" class:!text-orange-500={selected === page.id}>{match}</span><span class:text-orange-600={selected === page.id}>{page.path.substring(endIndex, page.path.length)}</span> on:click|preventDefault={() => changePage(page.id)}>
<span class:text-orange-600={selected === page.id}>{page.path.substring(0, startIndex)}</span><span
class="text-white"
class:!text-orange-500={selected === page.id}>{match}</span><span
class:text-orange-600={selected === page.id}>{page.path.substring(endIndex, page.path.length)}</span>
</li> </li>
{/each} {/each}
{/if} {/if}
@ -152,7 +160,7 @@
</Card> </Card>
<Card class="!max-w-full" style="grid-column: 2/4"> <Card class="!max-w-full" style="grid-column: 2/4">
{#if selected} {#if selected}
<Editor pageId={selected} branch={selectedBranch} on:reload={reload} bind:dirty /> <Editor pageId={selected} bind:branch={selectedBranch} on:reload={reload} bind:dirty/>
{/if} {/if}
</Card> </Card>
</div> </div>

Datei anzeigen

@ -48,7 +48,7 @@
<Tabs style="pill" class="mx-4 flex shadow-lg border-b-2 border-gray-700 pb-2" contentClass=""> <Tabs style="pill" class="mx-4 flex shadow-lg border-b-2 border-gray-700 pb-2" contentClass="">
<TabItem open> <TabItem open>
<span slot="title">Event</span> <span slot="title">Event</span>
<EventEdit {data} /> <EventEdit {data}/>
</TabItem> </TabItem>
<TabItem> <TabItem>
<span slot="title">Teams</span> <span slot="title">Teams</span>

Datei anzeigen

@ -26,18 +26,18 @@
import {eventRepo} from "@repo/event.ts"; import {eventRepo} from "@repo/event.ts";
import {tokenStore} from "@repo/repo.ts"; import {tokenStore} from "@repo/repo.ts";
let events = $eventRepo.listEvents() let events = $eventRepo.listEvents();
let showAdd = false let showAdd = false;
let millis = Date.now() let millis = Date.now();
</script> </script>
<Navbar let:hidden let:toggle class="shadow-lg border-b"> <Navbar let:hidden let:toggle class="shadow-lg border-b">
<NavBrand href="#"> <NavBrand href="#">
<span class="self-center whitespace-nowrap text-xl font-semibold dark:text-white"> <span class="self-center whitespace-nowrap text-xl font-semibold dark:text-white">
Admin-Tool Mod-Tool
</span> </span>
</NavBrand> </NavBrand>
<NavHamburger on:click={toggle} /> <NavHamburger on:click={toggle}/>
<NavUl {hidden}> <NavUl {hidden}>
<NavLi href="#/edit">Edit Pages</NavLi> <NavLi href="#/edit">Edit Pages</NavLi>
<NavLi href="#/perms">Permissions</NavLi> <NavLi href="#/perms">Permissions</NavLi>
@ -58,13 +58,13 @@
<h1 class="text-3xl mt-4 ml-4">Upcoming</h1> <h1 class="text-3xl mt-4 ml-4">Upcoming</h1>
<div class="grid gap-4 p-4 border-b" style="grid-template-columns: repeat(auto-fill, minmax(300px, 1fr))"> <div class="grid gap-4 p-4 border-b" style="grid-template-columns: repeat(auto-fill, minmax(300px, 1fr))">
{#each data.filter((e) => e.start > millis) as event} {#each data.filter((e) => e.start > millis) as event}
<EventCard {event} /> <EventCard {event}/>
{/each} {/each}
</div> </div>
<h1 class="text-3xl mt-4 ml-4">Past</h1> <h1 class="text-3xl mt-4 ml-4">Past</h1>
<div class="grid gap-4 p-4" style="grid-template-columns: repeat(auto-fill, minmax(300px, 1fr))"> <div class="grid gap-4 p-4" style="grid-template-columns: repeat(auto-fill, minmax(300px, 1fr))">
{#each data.filter((e) => e.start < millis).reverse() as event} {#each data.filter((e) => e.start < millis).reverse() as event}
<EventCard {event} /> <EventCard {event}/>
{/each} {/each}
</div> </div>
{:catch error} {:catch error}

Datei anzeigen

@ -31,9 +31,9 @@
async function handleSubmit() { async function handleSubmit() {
loading = true; loading = true;
let res = await fetchWithToken(value, "/data") let res = await fetchWithToken(value, "/data");
loading = false; loading = false;
if(res.ok) { if (res.ok) {
$tokenStore = value; $tokenStore = value;
await replace("/"); await replace("/");
} else { } else {
@ -41,7 +41,7 @@
value = ""; value = "";
setTimeout(() => { setTimeout(() => {
error = false; error = false;
}, 5000) }, 5000);
} }
} }
</script> </script>
@ -51,12 +51,13 @@
<div class="grid gap-6 mb-6 md:grid-cols-1"> <div class="grid gap-6 mb-6 md:grid-cols-1">
<div> <div>
<Label for="token-xyz" class="mb-2">Token</Label> <Label for="token-xyz" class="mb-2">Token</Label>
<Input type={show?'text':'password'} id="token-xyz" placeholder="•••••••••" required size="lg" bind:value> <Input type={show?'text':'password'} id="token-xyz" placeholder="•••••••••" required size="lg"
bind:value>
<button slot="left" on:click={() => (show = !show)} class="pointer-events-auto" type="button"> <button slot="left" on:click={() => (show = !show)} class="pointer-events-auto" type="button">
{#if show} {#if show}
<EyeOutline /> <EyeOutline/>
{:else} {:else}
<EyeSlashOutline /> <EyeSlashOutline/>
{/if} {/if}
</button> </button>
</Input> </Input>
@ -64,7 +65,8 @@
</div> </div>
<Button type="submit"> <Button type="submit">
{#if loading} {#if loading}
<Spinner size={4} class="mr-3" color="white"/> <span>Loading...</span> <Spinner size={4} class="mr-3" color="white"/>
<span>Loading...</span>
{:else} {:else}
<span>Submit</span> <span>Submit</span>
{/if} {/if}
@ -74,7 +76,12 @@
<Toast color="red" position="bottom-left" bind:open={error} transition={fly} params="{{x: -200}}"> <Toast color="red" position="bottom-left" bind:open={error} transition={fly} params="{{x: -200}}">
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
<span class="sr-only">Error icon</span> <span class="sr-only">Error icon</span>
</svelte:fragment> </svelte:fragment>
Invalid Token. Invalid Token.

Datei anzeigen

@ -22,7 +22,7 @@
import {replace} from "svelte-spa-router"; import {replace} from "svelte-spa-router";
onMount(() => { onMount(() => {
replace('/') replace("/");
}); });
</script> </script>

Datei anzeigen

@ -44,7 +44,7 @@
activePerms = value.perms; activePerms = value.perms;
prefixEdit = value.prefix.name; prefixEdit = value.prefix.name;
return value; return value;
}) });
} }
function togglePerm(perm: string) { function togglePerm(perm: string) {
@ -54,7 +54,7 @@
} else { } else {
activePerms = [...activePerms, perm]; activePerms = [...activePerms, perm];
} }
} };
} }
function save() { function save() {
@ -76,7 +76,7 @@
} }
playerPerms = loadPlayer(selectedPlayer); playerPerms = loadPlayer(selectedPlayer);
}) });
} }
let permsFuture = $permsRepo.listPerms(); let permsFuture = $permsRepo.listPerms();
@ -102,7 +102,9 @@
{#if filteredPlayers.length < 100} {#if filteredPlayers.length < 100}
<ul class="flex-1 overflow-scroll"> <ul class="flex-1 overflow-scroll">
{#each filteredPlayers as player (player.id)} {#each filteredPlayers as player (player.id)}
<li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer" class:text-orange-500={player.id === selectedPlayer} on:click|preventDefault={() => selectedPlayer = player.id}> <li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer"
class:text-orange-500={player.id === selectedPlayer}
on:click|preventDefault={() => selectedPlayer = player.id}>
{player.name} {player.name}
</li> </li>
{/each} {/each}
@ -120,14 +122,18 @@
{:then player} {:then player}
<h1>Prefix</h1> <h1>Prefix</h1>
{#each Object.entries(perms.prefixes) as [key, prefix]} {#each Object.entries(perms.prefixes) as [key, prefix]}
<Radio name="prefix" bind:group={prefixEdit} value={prefix.name}>{capitalize(prefix.name.substring(7).toLowerCase())}</Radio> <Radio name="prefix" bind:group={prefixEdit}
value={prefix.name}>{capitalize(prefix.name.substring(7).toLowerCase())}</Radio>
{/each} {/each}
<h1>Permissions</h1> <h1>Permissions</h1>
{#each perms.perms as perm} {#each perms.perms as perm}
<Checkbox checked={activePerms.includes(perm)} on:click={togglePerm(perm)}>{capitalize(perm.toLowerCase())}</Checkbox> <Checkbox checked={activePerms.includes(perm)}
on:click={togglePerm(perm)}>{capitalize(perm.toLowerCase())}</Checkbox>
{/each} {/each}
<div class="mt-4"> <div class="mt-4">
<Button disabled={prefixEdit === player.prefix.name && activePerms === player.perms} on:click={save}>Save</Button> <Button disabled={prefixEdit === player.prefix.name && activePerms === player.perms}
on:click={save}>Save
</Button>
</div> </div>
{:catch error} {:catch error}
<p>{error.toString()}</p> <p>{error.toString()}</p>

Datei anzeigen

@ -23,7 +23,7 @@
import CodeMirror from "svelte-codemirror-editor"; import CodeMirror from "svelte-codemirror-editor";
import {base64ToBytes} from "../../util.ts"; import {base64ToBytes} from "../../util.ts";
import type {Page} from "@type/page.ts"; import type {Page} from "@type/page.ts";
import {materialDark} from '@ddietr/codemirror-themes/material-dark.js' import {materialDark} from "@ddietr/codemirror-themes/material-dark.js";
import {createEventDispatcher} from "svelte"; import {createEventDispatcher} from "svelte";
import MDEMarkdownEditor from "./MDEMarkdownEditor.svelte"; import MDEMarkdownEditor from "./MDEMarkdownEditor.svelte";
import {pageRepo} from "@repo/page.ts"; import {pageRepo} from "@repo/page.ts";
@ -40,24 +40,30 @@
function getPage(value: Page): Page { function getPage(value: Page): Page {
page = value; page = value;
if (!dirty || confirm("You have unchanged Changes! Discard them? ")) {
navigator.clipboard.writeText(pageContent);
dirty = false;
pageContent = new TextDecoder().decode(base64ToBytes(value.content)); pageContent = new TextDecoder().decode(base64ToBytes(value.content));
}
return value; return value;
} }
function savePage() { function savePage() {
let message = window.prompt("Commit message:", "Update " + page!.name) let message = window.prompt("Commit message:", "Update " + page!.name);
if (message) { if (message) {
$pageRepo.updatePage(pageId, pageContent, page!.sha, message, branch) $pageRepo.updatePage(pageId, pageContent, page!.sha, message, branch);
dirty = false; dirty = false;
} }
} }
async function deletePage() { async function deletePage() {
let message = window.prompt("Commit message:", "Delete " + page!.name) let message = window.prompt("Commit message:", "Delete " + page!.name);
if (message) { if (message) {
await $pageRepo.deletePage(pageId, message, page!.sha, branch) await $pageRepo.deletePage(pageId, message, page!.sha, branch);
dirty = false; dirty = false;
dispatcher("reload") dispatcher("reload");
} }
} }
</script> </script>
@ -65,9 +71,9 @@
if (dirty) { if (dirty) {
return "You have unsaved changes. Are you sure you want to leave?"; return "You have unsaved changes. Are you sure you want to leave?";
} }
}} /> }}/>
{#await pageFuture} {#await pageFuture}
<Spinner /> <Spinner/>
{:then p} {:then p}
<div> <div>
<div> <div>
@ -83,9 +89,9 @@
</Toolbar> </Toolbar>
</div> </div>
{#if page?.name.endsWith("md")} {#if page?.name.endsWith("md")}
<MDEMarkdownEditor bind:value={pageContent} bind:dirty /> <MDEMarkdownEditor bind:value={pageContent} bind:dirty/>
{:else} {:else}
<CodeMirror bind:value={pageContent} lang={json()} theme={materialDark} on:change={() => dirty = true} /> <CodeMirror bind:value={pageContent} lang={json()} theme={materialDark} on:change={() => dirty = true}/>
{/if} {/if}
</div> </div>
{:catch error} {:catch error}

Datei anzeigen

@ -20,7 +20,7 @@
<script lang="ts"> <script lang="ts">
import {onDestroy, onMount} from "svelte"; import {onDestroy, onMount} from "svelte";
import EasyMDE from "easymde"; import EasyMDE from "easymde";
import "easymde/dist/easymde.min.css" import "easymde/dist/easymde.min.css";
export let value: string; export let value: string;
export let dirty: boolean = false; export let dirty: boolean = false;
@ -33,17 +33,17 @@
element: editor, element: editor,
initialValue: value, initialValue: value,
spellChecker: false spellChecker: false
}) });
mde.codemirror.on("change", () => { mde.codemirror.on("change", () => {
value = mde.value(); value = mde.value();
dirty = true; dirty = true;
}) });
}) });
onDestroy(() => { onDestroy(() => {
mde.toTextArea(); mde.toTextArea();
mde.cleanup(); mde.cleanup();
}) });
</script> </script>
<textarea bind:this={editor} class="editor-preview"> <textarea bind:this={editor} class="editor-preview">

Datei anzeigen

@ -22,7 +22,7 @@
import {Button, Input, Label, Modal, Range, Select, Toast, Toggle} from "flowbite-svelte"; import {Button, Input, Label, Modal, Range, Select, Toast, Toggle} from "flowbite-svelte";
import {schemTypes} from "@stores/stores.ts"; import {schemTypes} from "@stores/stores.ts";
import dayjs from "dayjs"; import dayjs from "dayjs";
import utc from "dayjs/plugin/utc" import utc from "dayjs/plugin/utc";
import {eventRepo, type UpdateEvent} from "@repo/event.ts"; import {eventRepo, type UpdateEvent} from "@repo/event.ts";
import ErrorModal from "../../components/ErrorModal.svelte"; import ErrorModal from "../../components/ErrorModal.svelte";
import {replace} from "svelte-spa-router"; import {replace} from "svelte-spa-router";
@ -55,7 +55,7 @@
return { return {
value: type.db, value: type.db,
name: type.name name: type.name
} };
})]; })];
$: changed = name !== event.name || $: changed = name !== event.name ||
@ -70,7 +70,7 @@
async function del() { async function del() {
try { try {
await $eventRepo.deleteEvent(event.id.toString()); await $eventRepo.deleteEvent(event.id.toString());
await replace("/") await replace("/");
} catch (e) { } catch (e) {
error = e; error = e;
errorOpen = true; errorOpen = true;
@ -86,7 +86,7 @@
maxTeamMembers: member, maxTeamMembers: member,
name: name, name: name,
publicSchemsOnly: publicOnly, publicSchemsOnly: publicOnly,
schemType: schemType ?? 'null', schemType: schemType ?? "null",
spectateSystem: spectateSystem, spectateSystem: spectateSystem,
start: startDate start: startDate
}; };

Datei anzeigen

@ -40,14 +40,14 @@
function dispatchSelect() { function dispatchSelect() {
setTimeout(() => { setTimeout(() => {
if (!deleteOpen && !editOpen) { if (!deleteOpen && !editOpen) {
dispatcher('select'); dispatcher("select");
} }
}, 1); }, 1);
} }
async function deleteFight() { async function deleteFight() {
await $fightRepo.deleteFight(fight.id); await $fightRepo.deleteFight(fight.id);
dispatcher('update'); dispatcher("update");
} }
</script> </script>
@ -93,14 +93,15 @@
<EditOutline/> <EditOutline/>
</ToolbarButton> </ToolbarButton>
<ToolbarButton color="red" on:click={() => deleteOpen = true}> <ToolbarButton color="red" on:click={() => deleteOpen = true}>
<TrashBinOutline /> <TrashBinOutline/>
</ToolbarButton> </ToolbarButton>
</Toolbar> </Toolbar>
{/if} {/if}
</div> </div>
</div> </div>
<Modal title="Delete {fight.blueTeam.name} vs. {fight.redTeam.name}" bind:open={deleteOpen} autoclose outsideclose size="xs"> <Modal title="Delete {fight.blueTeam.name} vs. {fight.redTeam.name}" bind:open={deleteOpen} autoclose outsideclose
size="xs">
<div class="text-center"> <div class="text-center">
<p class="mb-5"> <p class="mb-5">
Are you sure you want to delete this fight? Are you sure you want to delete this fight?

Datei anzeigen

@ -40,8 +40,9 @@
import TypeAheadSearch from "../../components/TypeAheadSearch.svelte"; import TypeAheadSearch from "../../components/TypeAheadSearch.svelte";
import {fightRepo, type UpdateFight} from "@repo/fight.ts"; import {fightRepo, type UpdateFight} from "@repo/fight.ts";
import dayjs from "dayjs"; import dayjs from "dayjs";
import duration from "dayjs/plugin/duration" import duration from "dayjs/plugin/duration";
dayjs.extend(duration)
dayjs.extend(duration);
export let data: ExtendedEvent; export let data: ExtendedEvent;
@ -54,13 +55,13 @@
return { return {
group: group, group: group,
fights: fights.filter(fight => fight.group === group) fights: fights.filter(fight => fight.group === group)
} };
}); });
function cycleSelect() { function cycleSelect() {
if (selectedFights.size === fights.length) { if (selectedFights.size === fights.length) {
selectedFights = new Set(); selectedFights = new Set();
} else if(selectedFights.size === 0){ } else if (selectedFights.size === 0) {
selectedFights = new Set(fights.filter(fight => fight.start > Date.now())); selectedFights = new Set(fights.filter(fight => fight.start > Date.now()));
if (selectedFights.size === 0) { if (selectedFights.size === 0) {
@ -72,7 +73,7 @@
} }
function cycleGroup(groupFights: EventFight[]) { function cycleGroup(groupFights: EventFight[]) {
if(groupFights.every(gf => selectedFights.has(gf))) { if (groupFights.every(gf => selectedFights.has(gf))) {
groupFights.forEach(fight => selectedFights.delete(fight)); groupFights.forEach(fight => selectedFights.delete(fight));
} else { } else {
groupFights.forEach(fight => selectedFights.add(fight)); groupFights.forEach(fight => selectedFights.add(fight));
@ -81,6 +82,7 @@
} }
let deleteOpen = false; let deleteOpen = false;
async function deleteFights() { async function deleteFights() {
for (const fight of selectedFights) { for (const fight of selectedFights) {
await $fightRepo.deleteFight(fight.id); await $fightRepo.deleteFight(fight.id);
@ -95,9 +97,10 @@
return { return {
name: player.name, name: player.name,
value: player.id.toString() value: player.id.toString()
} };
}).sort((a, b) => a.name.localeCompare(b.name)); }).sort((a, b) => a.name.localeCompare(b.name));
let kampfleiter = ""; let kampfleiter = "";
async function updateKampfleiter() { async function updateKampfleiter() {
for (const fight of selectedFights) { for (const fight of selectedFights) {
let f: UpdateFight = { let f: UpdateFight = {
@ -122,8 +125,8 @@
let groupSearch = ""; let groupSearch = "";
$: selectableGroups = [{ $: selectableGroups = [{
name: 'None', name: "None",
value: '' value: ""
}, { }, {
value: groupSearch, value: groupSearch,
name: `Create: '${groupSearch}'` name: `Create: '${groupSearch}'`
@ -131,8 +134,9 @@
return { return {
name: group, name: group,
value: group value: group
} };
}).sort((a, b) => a.name.localeCompare(b.name))]; }).sort((a, b) => a.name.localeCompare(b.name))];
async function updateGroup() { async function updateGroup() {
for (const fight of selectedFights) { for (const fight of selectedFights) {
let f: UpdateFight = { let f: UpdateFight = {
@ -157,7 +161,7 @@
let changeTimeOpen = false; let changeTimeOpen = false;
let changedTime = fights.length != 0 ? dayjs(Math.min(...fights.map(fight => fight.start)))?.utc(true)?.toISOString()?.slice(0, -1) : undefined; let changedTime = fights.length != 0 ? dayjs(Math.min(...fights.map(fight => fight.start)))?.utc(true)?.toISOString()?.slice(0, -1) : undefined;
$: deltaTime = dayjs.duration(dayjs(changedTime).utc(true).diff(minTime)) $: deltaTime = dayjs.duration(dayjs(changedTime).utc(true).diff(minTime));
async function updateStartTime() { async function updateStartTime() {
for (const fight of selectedFights) { for (const fight of selectedFights) {
@ -168,7 +172,7 @@
map: null, map: null,
redTeam: null, redTeam: null,
spielmodus: null, spielmodus: null,
start: dayjs(fight.start).add(deltaTime.asMilliseconds(), 'millisecond') start: dayjs(fight.start).add(deltaTime.asMilliseconds(), "millisecond")
}; };
await $fightRepo.updateFight(fight.id, f); await $fightRepo.updateFight(fight.id, f);
} }
@ -194,7 +198,8 @@
<CalendarWeekOutline/> <CalendarWeekOutline/>
</ToolbarButton> </ToolbarButton>
<Tooltip>Reschedule Fights</Tooltip> <Tooltip>Reschedule Fights</Tooltip>
<ToolbarButton on:click={() => selectedFights.size > 0 ? kampfleiterOpen = true : kampfleiterOpen = false} disabled={changedTime === undefined}> <ToolbarButton on:click={() => selectedFights.size > 0 ? kampfleiterOpen = true : kampfleiterOpen = false}
disabled={changedTime === undefined}>
<ProfileCardOutline/> <ProfileCardOutline/>
</ToolbarButton> </ToolbarButton>
<Tooltip>Change Kampfleiter</Tooltip> <Tooltip>Change Kampfleiter</Tooltip>
@ -204,7 +209,8 @@
<Tooltip>Change Group</Tooltip> <Tooltip>Change Group</Tooltip>
</ToolbarGroup> </ToolbarGroup>
<ToolbarGroup> <ToolbarGroup>
<ToolbarButton color="red" on:click={() => selectedFights.size > 0 ? deleteOpen = true : deleteOpen = false}> <ToolbarButton color="red"
on:click={() => selectedFights.size > 0 ? deleteOpen = true : deleteOpen = false}>
<TrashBinOutline/> <TrashBinOutline/>
</ToolbarButton> </ToolbarButton>
<Tooltip>Delete</Tooltip> <Tooltip>Delete</Tooltip>
@ -212,7 +218,8 @@
</Toolbar> </Toolbar>
{#each groupedFights as group} {#each groupedFights as group}
<div class="flex mt-4"> <div class="flex mt-4">
<Checkbox class="ml-2 text-center" checked={group.fights.every(gf => selectedFights.has(gf))} on:click={() => cycleGroup(group.fights)}/> <Checkbox class="ml-2 text-center" checked={group.fights.every(gf => selectedFights.has(gf))}
on:click={() => cycleGroup(group.fights)}/>
<h1 class="ml-4 text-2xl">{group.group ?? "Ungrouped"}</h1> <h1 class="ml-4 text-2xl">{group.group ?? "Ungrouped"}</h1>
</div> </div>
{#each group.fights.sort((a, b) => a.start - b.start) as fight, i (fight.id)} {#each group.fights.sort((a, b) => a.start - b.start) as fight, i (fight.id)}
@ -231,7 +238,8 @@
{/each} {/each}
</div> </div>
<CreateFightModal {data} bind:open={createOpen} on:create={async () => data.fights = await $fightRepo.listFights(data.event.id)}></CreateFightModal> <CreateFightModal {data} bind:open={createOpen}
on:create={async () => data.fights = await $fightRepo.listFights(data.event.id)}></CreateFightModal>
<Modal bind:open={deleteOpen} title="Delete {selectedFights.size} Fights" autoclose size="sm"> <Modal bind:open={deleteOpen} title="Delete {selectedFights.size} Fights" autoclose size="sm">
<p>Are you sure you want to delete {selectedFights.size} fights?</p> <p>Are you sure you want to delete {selectedFights.size} fights?</p>
@ -255,7 +263,8 @@
<Modal bind:open={groupChangeOpen} title="Change Group" size="sm"> <Modal bind:open={groupChangeOpen} title="Change Group" size="sm">
<div class="m-2"> <div class="m-2">
<Label for="fight-kampf">Group</Label> <Label for="fight-kampf">Group</Label>
<TypeAheadSearch items={selectableGroups} bind:selected={group} bind:searchValue={groupSearch} all></TypeAheadSearch> <TypeAheadSearch items={selectableGroups} bind:selected={group} bind:searchValue={groupSearch}
all></TypeAheadSearch>
</div> </div>
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">
<Button class="ml-auto" on:click={updateGroup}>Change</Button> <Button class="ml-auto" on:click={updateGroup}>Change</Button>
@ -270,7 +279,8 @@
<input type="datetime-local" {...props} bind:value={changedTime}/> <input type="datetime-local" {...props} bind:value={changedTime}/>
</Input> </Input>
</div> </div>
<p>{deltaTime.asMilliseconds() < 0 ? '' : '+'}{("0" + deltaTime.hours()).slice(-2)}:{("0" + deltaTime.minutes()).slice(-2)}</p> <p>{deltaTime.asMilliseconds() < 0 ? '' : '+'}{("0" + deltaTime.hours()).slice(-2)}
:{("0" + deltaTime.minutes()).slice(-2)}</p>
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">
<Button class="ml-auto" on:click={updateStartTime}>Update</Button> <Button class="ml-auto" on:click={updateStartTime}>Update</Button>
<Button on:click={() => changeTimeOpen = false} color="alternative">Cancel</Button> <Button on:click={() => changeTimeOpen = false} color="alternative">Cancel</Button>

Datei anzeigen

@ -29,7 +29,8 @@
<Avatar size="lg">{team.kuerzel}</Avatar> <Avatar size="lg">{team.kuerzel}</Avatar>
<div class="m-2"> <div class="m-2">
<h1 class="text-2xl">{team.name}</h1> <h1 class="text-2xl">{team.name}</h1>
<h2 class="text-lg text-gray-400">Fights: {data.fights.filter(value => value.blueTeam.id === team.id || value.redTeam.id === team.id).length}</h2> <h2 class="text-lg text-gray-400">
Fights: {data.fights.filter(value => value.blueTeam.id === team.id || value.redTeam.id === team.id).length}</h2>
</div> </div>
</div> </div>
{/each} {/each}

Datei anzeigen

@ -60,13 +60,13 @@
kampfleiter: parseInt(kampfleiter!), kampfleiter: parseInt(kampfleiter!),
group, group,
}); });
reset() reset();
dispatch("create") dispatch("create");
} catch (e) { } catch (e) {
error = e; error = e;
errorOpen = true; errorOpen = true;
reset() reset();
} }
} }

Datei anzeigen

@ -37,7 +37,7 @@
let blueTeam = fight.blueTeam.id.toString(); let blueTeam = fight.blueTeam.id.toString();
let start = dayjs(fight.start).utc(true).toISOString().slice(0, -1); let start = dayjs(fight.start).utc(true).toISOString().slice(0, -1);
let kampfleiter = fight.kampfleiter?.id.toString(); let kampfleiter = fight.kampfleiter?.id.toString();
let gamemode = fight.spielmodus let gamemode = fight.spielmodus;
let map = fight.map; let map = fight.map;
let group = fight.group; let group = fight.group;
let groupSearch = fight.group ?? ""; let groupSearch = fight.group ?? "";
@ -46,10 +46,17 @@
let error: any = undefined; let error: any = undefined;
let dispatch = createEventDispatcher(); let dispatch = createEventDispatcher();
function save() { function save() {
const update: UpdateFight = { const update: UpdateFight = {
blueTeam: parseInt(blueTeam), group: group === "" ? null : group, kampfleiter: parseInt(kampfleiter), map: map, redTeam: parseInt(redTeam), spielmodus: gamemode, start: dayjs(start) blueTeam: parseInt(blueTeam),
} group: group === "" ? null : group,
kampfleiter: parseInt(kampfleiter),
map: map,
redTeam: parseInt(redTeam),
spielmodus: gamemode,
start: dayjs(start)
};
$fightRepo.updateFight(fight.id, update) $fightRepo.updateFight(fight.id, update)
.then(value => { .then(value => {
@ -60,7 +67,7 @@
.catch((e) => { .catch((e) => {
error = e.message; error = e.message;
errorOpen = true; errorOpen = true;
}) });
} }
</script> </script>

Datei anzeigen

@ -32,11 +32,12 @@
function handleDrop(ev: DragEvent) { function handleDrop(ev: DragEvent) {
ev.preventDefault(); ev.preventDefault();
dragover = false; dragover = false;
dispatch('drop', ev) dispatch("drop", ev);
} }
</script> </script>
<div class="w-56 bg-gray-800 p-4 rounded" class:border={dragover} class:m-px={!dragover} on:drop={handleDrop} on:dragover={handleDragOver} on:dragleave={() => dragover = false} role="none"> <div class="w-56 bg-gray-800 p-4 rounded" class:border={dragover} class:m-px={!dragover} on:drop={handleDrop}
on:dragover={handleDragOver} on:dragleave={() => dragover = false} role="none">
<slot></slot> <slot></slot>
</div> </div>

Datei anzeigen

@ -42,14 +42,14 @@
} }
function teamDragStart(ev: DragEvent, team: Team) { function teamDragStart(ev: DragEvent, team: Team) {
ev.dataTransfer!.setData("team", team.id.toString()) ev.dataTransfer!.setData("team", team.id.toString());
} }
let resetDragOver = false; let resetDragOver = false;
function resetDragOverEvent(ev: DragEvent) { function resetDragOverEvent(ev: DragEvent) {
resetDragOver = true; resetDragOver = true;
ev.preventDefault() ev.preventDefault();
} }
function dropReset(ev: DragEvent) { function dropReset(ev: DragEvent) {
@ -65,16 +65,16 @@
groups = groups.map((group, i) => i === groupIndex ? [...group.filter(value => value != teamId), teamId] : group.filter(value => value != teamId)).filter(group => group.length > 0); groups = groups.map((group, i) => i === groupIndex ? [...group.filter(value => value != teamId), teamId] : group.filter(value => value != teamId)).filter(group => group.length > 0);
} }
let startTime = dayjs(data.event.start).utc(true).toISOString().slice(0, -1) let startTime = dayjs(data.event.start).utc(true).toISOString().slice(0, -1);
$: startMoment = dayjs(startTime); $: startMoment = dayjs(startTime);
let gamemode = '' let gamemode = "";
let map = '' let map = "";
$: selectableGamemodes = $gamemodes.map(gamemode => { $: selectableGamemodes = $gamemodes.map(gamemode => {
return { return {
name: gamemode, name: gamemode,
value: gamemode value: gamemode
} };
}).sort((a, b) => a.name.localeCompare(b.name)); }).sort((a, b) => a.name.localeCompare(b.name));
$: mapsStore = maps(gamemode); $: mapsStore = maps(gamemode);
@ -82,7 +82,7 @@
return { return {
name: map, name: map,
value: map value: map
} };
}).sort((a, b) => a.name.localeCompare(b.name)); }).sort((a, b) => a.name.localeCompare(b.name));
let roundTime = 30; let roundTime = 30;
@ -95,10 +95,10 @@
let teams = data.teams.map(team => team.id).sort(() => Math.random() - 0.5); let teams = data.teams.map(team => team.id).sort(() => Math.random() - 0.5);
groups = []; groups = [];
for (let i = 0; i < groupCount; i++) { for (let i = 0; i < groupCount; i++) {
groups.push([]) groups.push([]);
} }
while (teams.length > 0) { while (teams.length > 0) {
groups[teams.length % groupCount].push(teams.pop() as number) groups[teams.length % groupCount].push(teams.pop() as number);
} }
showAutoGrouping = false; showAutoGrouping = false;
groups = groups.filter(group => group.length > 0); groups = groups.filter(group => group.length > 0);
@ -111,8 +111,8 @@
let groupFight = []; let groupFight = [];
for (let i = 0; i < round; i++) { for (let i = 0; i < round; i++) {
let availableTeams = [...group]; let availableTeams = [...group];
if(group.length % 2 === 1) { if (group.length % 2 === 1) {
availableTeams = availableTeams.filter((team, index) => index !== i) availableTeams = availableTeams.filter((team, index) => index !== i);
} }
let roundFights = []; let roundFights = [];
while (availableTeams.length > 0) { while (availableTeams.length > 0) {
@ -121,25 +121,25 @@
availableTeams = availableTeams.filter(team => team !== team2); availableTeams = availableTeams.filter(team => team !== team2);
let fight = [team1, team2]; let fight = [team1, team2];
fight.sort(() => Math.random() - 0.5); fight.sort(() => Math.random() - 0.5);
roundFights.push(fight) roundFights.push(fight);
} }
groupFight.push(roundFights) groupFight.push(roundFights);
} }
groupFights.push(groupFight) groupFights.push(groupFight);
}) });
return groupFights; return groupFights;
} }
$: groupsFights = generateGroups(groups) $: groupsFights = generateGroups(groups);
$: generateDisabled = groupsFights.length > 0 && groupsFights.every(value => value.every(value1 => value1.length > 0)) && gamemode !== '' && map !== '' $: generateDisabled = groupsFights.length > 0 && groupsFights.every(value => value.every(value1 => value1.length > 0)) && gamemode !== "" && map !== "";
async function generateFights() { async function generateFights() {
groupsFights.forEach((group, i) => { groupsFights.forEach((group, i) => {
group.forEach((round, j) => { group.forEach((round, j) => {
round.forEach(async (fight, k) => { round.forEach(async (fight, k) => {
const blueTeam = teams.get(fight[0])! const blueTeam = teams.get(fight[0])!;
const redTeam = teams.get(fight[1])! const redTeam = teams.get(fight[1])!;
await $fightRepo.createFight(data.event.id, { await $fightRepo.createFight(data.event.id, {
blueTeam: blueTeam.id, blueTeam: blueTeam.id,
@ -149,17 +149,19 @@
map: map, map: map,
spielmodus: gamemode, spielmodus: gamemode,
start: startMoment.clone().add(roundTime * j, "minutes").add(startDelay * (k + (i * round.length)), "seconds") start: startMoment.clone().add(roundTime * j, "minutes").add(startDelay * (k + (i * round.length)), "seconds")
}) });
}) });
}) });
}) });
await replace("#/event/" + data.event.id) await replace("#/event/" + data.event.id);
} }
</script> </script>
<div class="flex justify-between"> <div class="flex justify-between">
<div id="reseter" class:border-white={resetDragOver} class="flex m-2 bg-gray-800 w-fit p-2 border border-gray-700 rounded ml-4 h-20 pt-6 relative" on:dragover={resetDragOverEvent} on:dragleave={() => resetDragOver = false} on:drop={dropReset} role="group"> <div id="reseter" class:border-white={resetDragOver}
class="flex m-2 bg-gray-800 w-fit p-2 border border-gray-700 rounded ml-4 h-20 pt-6 relative"
on:dragover={resetDragOverEvent} on:dragleave={() => resetDragOver = false} on:drop={dropReset} role="group">
{#each teamsNotInGroup as team (team.id)} {#each teamsNotInGroup as team (team.id)}
<TeamChip {team} on:dragstart={ev => teamDragStart(ev, team)}/> <TeamChip {team} on:dragstart={ev => teamDragStart(ev, team)}/>
{/each} {/each}
@ -247,6 +249,7 @@
top: 0; top: 0;
color: gray; color: gray;
} }
#reseter { #reseter {
min-width: 14rem; min-width: 14rem;
} }

Datei anzeigen

@ -27,7 +27,8 @@
</script> </script>
<div class="rounded w-fit p-2 border-gray-600 border cursor-grab select-none m-1 flex place-items-center" <div class="rounded w-fit p-2 border-gray-600 border cursor-grab select-none m-1 flex place-items-center"
style:background-color={hover ? lighten(colorFromTeam(team)) : colorFromTeam(team)} class:text-black={brightness(colorFromTeam(team))} draggable="true" style:background-color={hover ? lighten(colorFromTeam(team)) : colorFromTeam(team)}
class:text-black={brightness(colorFromTeam(team))} draggable="true"
on:dragstart on:dragstart
on:mouseenter={() => hover = true} on:mouseenter={() => hover = true}
on:mouseleave={() => hover = false} on:mouseleave={() => hover = false}

Datei anzeigen

@ -32,11 +32,11 @@
let eventName = ""; let eventName = "";
let start = ""; let start = "";
$: startDate = dayjs(start) $: startDate = dayjs(start);
let end = ""; let end = "";
$: endDate = dayjs(end) $: endDate = dayjs(end);
$: canSubmit = eventName.length > 0 && startDate.isValid() && endDate.isValid() && startDate.isBefore(endDate) $: canSubmit = eventName.length > 0 && startDate.isValid() && endDate.isValid() && startDate.isBefore(endDate);
async function createEvent() { async function createEvent() {
try { try {
@ -44,7 +44,7 @@
name: eventName, name: eventName,
start: startDate, start: startDate,
end: endDate end: endDate
}) });
dispatch("create"); dispatch("create");
open = false; open = false;
} catch (e: any) { } catch (e: any) {

Datei anzeigen

@ -19,7 +19,7 @@
<script lang="ts"> <script lang="ts">
import {Card} from "flowbite-svelte"; import {Card} from "flowbite-svelte";
import {link} from 'svelte-spa-router' import {link} from "svelte-spa-router";
import type {ShortEvent} from "@type/event.ts"; import type {ShortEvent} from "@type/event.ts";
export let event: ShortEvent; export let event: ShortEvent;

Datei anzeigen

@ -23,7 +23,7 @@ import type {ListPage, PageList} from "@type/page.ts";
export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
export const nameRegex = new RegExp("(?!.*\/).+(?=\\.(md|json))"); export const nameRegex = new RegExp("(?!.*/).+(?=\\.(md|json))");
export function mapToMap(pages: PageList): Map<string, ListPage[]> { export function mapToMap(pages: PageList): Map<string, ListPage[]> {
const map = new Map(); const map = new Map();
@ -82,9 +82,9 @@ export function brightness(color: string) {
return Color(color).isLight(); return Color(color).isLight();
} }
export function base64ToBytes(base64: string) { export function base64ToBytes(base64: string): Uint8Array {
const binString = atob(base64); const binString = atob(base64);
// @ts-ignore // @ts-expect-error Some Function Missing
return Uint8Array.from(binString, (m) => m.codePointAt(0)); return Uint8Array.from(binString, (m) => m.codePointAt(0));
} }

Datei anzeigen

@ -28,15 +28,15 @@ export class AuthRepo {
return await fetchWithToken(this.token, "/auth/login", { return await fetchWithToken(this.token, "/auth/login", {
body: JSON.stringify({ body: JSON.stringify({
username, username,
password password,
}), }),
method: "POST", method: "POST",
}).then(value => value.json()).then(value => value.token) }).then(value => value.json()).then(value => value.token);
} }
public async logout(): Promise<void> { public async logout(): Promise<void> {
await fetchWithToken(this.token, "/auth/tokens/logout", { await fetchWithToken(this.token, "/auth/tokens/logout", {
method: "POST" method: "POST",
}); });
} }
} }

Datei anzeigen

@ -23,7 +23,8 @@ import {fetchWithToken, tokenStore} from "./repo.ts";
import {derived} from "svelte/store"; import {derived} from "svelte/store";
export class DataRepo { export class DataRepo {
constructor(private token: string) {} constructor(private token: string) {
}
public async getServer(): Promise<Server> { public async getServer(): Promise<Server> {
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);

Datei anzeigen

@ -25,24 +25,25 @@ import type {Dayjs} from "dayjs";
import {derived} from "svelte/store"; import {derived} from "svelte/store";
export interface CreateEvent { export interface CreateEvent {
name: string name: string;
start: Dayjs start: Dayjs;
end: Dayjs end: Dayjs;
} }
export interface UpdateEvent { export interface UpdateEvent {
name: string name: string;
start: Dayjs start: Dayjs;
end: Dayjs end: Dayjs;
deadline: Dayjs deadline: Dayjs;
maxTeamMembers: number maxTeamMembers: number;
schemType: string | null schemType: string | null;
publicSchemsOnly: boolean publicSchemsOnly: boolean;
spectateSystem: boolean spectateSystem: boolean;
} }
export class EventRepo { export class EventRepo {
constructor(private token: string) {} constructor(private token: string) {
}
public async listEvents(): Promise<ShortEvent[]> { public async listEvents(): Promise<ShortEvent[]> {
return await fetchWithToken(this.token, "/events") return await fetchWithToken(this.token, "/events")
@ -62,7 +63,7 @@ export class EventRepo {
body: JSON.stringify({ body: JSON.stringify({
name: event.name, name: event.name,
start: +event.start, start: +event.start,
end: +event.end end: +event.end,
}), }),
}).then(value => value.json()) }).then(value => value.json())
.then(SWEventSchema.parse); .then(SWEventSchema.parse);
@ -79,18 +80,18 @@ export class EventRepo {
maxTeamMembers: event.maxTeamMembers, maxTeamMembers: event.maxTeamMembers,
schemType: event.schemType, schemType: event.schemType,
publicSchemsOnly: event.publicSchemsOnly, publicSchemsOnly: event.publicSchemsOnly,
spectateSystem: event.spectateSystem spectateSystem: event.spectateSystem,
}), }),
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
} },
}).then(value => value.json()) }).then(value => value.json())
.then(SWEventSchema.parse); .then(SWEventSchema.parse);
} }
public async deleteEvent(id: string): Promise<boolean> { public async deleteEvent(id: string): Promise<boolean> {
const res = await fetchWithToken(this.token, `/events/${id}`, { const res = await fetchWithToken(this.token, `/events/${id}`, {
method: "DELETE" method: "DELETE",
}); });
return res.ok; return res.ok;

Datei anzeigen

@ -25,27 +25,28 @@ import type {Dayjs} from "dayjs";
import {derived} from "svelte/store"; import {derived} from "svelte/store";
export interface CreateFight { export interface CreateFight {
spielmodus: string spielmodus: string;
map: string map: string;
blueTeam: number blueTeam: number;
redTeam: number redTeam: number;
start: Dayjs start: Dayjs;
kampfleiter: number | null kampfleiter: number | null;
group: string | null group: string | null;
} }
export interface UpdateFight { export interface UpdateFight {
spielmodus: string | null spielmodus: string | null;
map: string | null map: string | null;
blueTeam: number | null blueTeam: number | null;
redTeam: number | null redTeam: number | null;
start: Dayjs | null start: Dayjs | null;
kampfleiter: number | null kampfleiter: number | null;
group: string | null group: string | null;
} }
export class FightRepo { export class FightRepo {
constructor(private token: string) {} constructor(private token: string) {
}
public async listFights(eventId: number): Promise<EventFight[]> { public async listFights(eventId: number): Promise<EventFight[]> {
return await fetchWithToken(this.token, `/events/${eventId}/fights`) return await fetchWithToken(this.token, `/events/${eventId}/fights`)
@ -54,7 +55,7 @@ export class FightRepo {
} }
public async createFight(eventId: number, fight: CreateFight): Promise<EventFight> { public async createFight(eventId: number, fight: CreateFight): Promise<EventFight> {
return await fetchWithToken(this.token, `/fights`, { return await fetchWithToken(this.token, "/fights", {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
event: eventId, event: eventId,
@ -64,10 +65,10 @@ export class FightRepo {
redTeam: fight.redTeam, redTeam: fight.redTeam,
start: +fight.start, start: +fight.start,
kampfleiter: fight.kampfleiter, kampfleiter: fight.kampfleiter,
group: fight.group group: fight.group,
}) }),
}).then(value => value.json()) }).then(value => value.json())
.then(EventFightSchema.parse) .then(EventFightSchema.parse);
} }
public async updateFight(fightId: number, fight: UpdateFight): Promise<EventFight> { public async updateFight(fightId: number, fight: UpdateFight): Promise<EventFight> {
@ -80,15 +81,15 @@ export class FightRepo {
redTeam: fight.redTeam, redTeam: fight.redTeam,
start: fight.start?.valueOf(), start: fight.start?.valueOf(),
kampfleiter: fight.kampfleiter, kampfleiter: fight.kampfleiter,
group: fight.group group: fight.group,
}) }),
}).then(value => value.json()) }).then(value => value.json())
.then(EventFightSchema.parse) .then(EventFightSchema.parse);
} }
public async deleteFight(fightId: number): Promise<void> { public async deleteFight(fightId: number): Promise<void> {
const res = await fetchWithToken(this.token, `/fights/${fightId}`, { const res = await fetchWithToken(this.token, `/fights/${fightId}`, {
method: "DELETE" method: "DELETE",
}); });
if (!res.ok) { if (!res.ok) {

Datei anzeigen

@ -25,7 +25,8 @@ import {z} from "zod";
import {derived} from "svelte/store"; import {derived} from "svelte/store";
export class PageRepo { export class PageRepo {
constructor(private token: string) {} constructor(private token: string) {
}
public async listPages(branch: string = "master"): Promise<PageList> { public async listPages(branch: string = "master"): Promise<PageList> {
return await fetchWithToken(this.token, `/page?branch=${branch}`) return await fetchWithToken(this.token, `/page?branch=${branch}`)
@ -45,8 +46,8 @@ export class PageRepo {
method: "PUT", method: "PUT",
body: JSON.stringify({ body: JSON.stringify({
content: bytesToBase64(new TextEncoder().encode(content)), content: bytesToBase64(new TextEncoder().encode(content)),
sha, message sha, message,
}) }),
}); });
} }
@ -69,11 +70,17 @@ export class PageRepo {
} }
public async merge(branch: string, message: string): Promise<void> { public async merge(branch: string, message: string): Promise<void> {
await fetchWithToken(this.token, "/page/branch/merge", {method: "POST", body: JSON.stringify({branch, message})}); await fetchWithToken(this.token, "/page/branch/merge", {
method: "POST",
body: JSON.stringify({branch, message}),
});
} }
public async deletePage(id: number, message: string, sha: string, branch: string = "master"): Promise<void> { public async deletePage(id: number, message: string, sha: string, branch: string = "master"): Promise<void> {
await fetchWithToken(this.token, `/page/${id}?branch=${branch}`, {method: "DELETE", body: JSON.stringify({message, sha})}); await fetchWithToken(this.token, `/page/${id}?branch=${branch}`, {
method: "DELETE",
body: JSON.stringify({message, sha}),
});
} }
} }

Datei anzeigen

@ -23,7 +23,8 @@ import {PermsSchema, UserPermsSchema} from "@type/perms.js";
import {derived} from "svelte/store"; import {derived} from "svelte/store";
export class PermsRepo { export class PermsRepo {
constructor(private token: string) {} constructor(private token: string) {
}
public async listPerms(): Promise<Perms> { public async listPerms(): Promise<Perms> {
const res = await fetchWithToken(this.token, "/perms"); const res = await fetchWithToken(this.token, "/perms");

Datei anzeigen

@ -20,7 +20,12 @@
import {writable} from "svelte/store"; import {writable} from "svelte/store";
export const fetchWithToken = (token: string, url: string, params: RequestInit = {}) => export const fetchWithToken = (token: string, url: string, params: RequestInit = {}) =>
fetch(`${import.meta.env.PUBLIC_API_SERVER}${url}`, {...params, headers: {...(token !== "" ? {"Authorization": "Bearer " + (token)}:{}), "Content-Type": "application/json", ...params.headers}}) fetch(`${import.meta.env.PUBLIC_API_SERVER}${url}`, {...params,
headers: {
...(token !== "" ? {"Authorization": "Bearer " + (token)} : {}),
"Content-Type": "application/json", ...params.headers,
},
})
.then(value => { .then(value => {
if (value.status === 401) { if (value.status === 401) {
tokenStore.set(""); tokenStore.set("");

Datei anzeigen

@ -18,12 +18,13 @@
*/ */
import {fetchWithToken, tokenStore} from "./repo.ts"; import {fetchWithToken, tokenStore} from "./repo.ts";
import type {SchematicCode, SchematicInfo, SchematicList} from "@type/schem.ts"; import type {SchematicInfo, SchematicList} from "@type/schem.ts";
import {SchematicCodeSchema, SchematicInfoSchema, SchematicListSchema} from "@type/schem.ts"; import {SchematicInfoSchema, SchematicListSchema} from "@type/schem.ts";
import {derived} from "svelte/store"; import {derived} from "svelte/store";
export class SchematicRepo { export class SchematicRepo {
constructor(private token: string) {} constructor(private token: string) {
}
public async getRootSchematicList(): Promise<SchematicList> { public async getRootSchematicList(): Promise<SchematicList> {
return await fetchWithToken(this.token, "/schem").then(value => value.json()).then(SchematicListSchema.parse); return await fetchWithToken(this.token, "/schem").then(value => value.json()).then(SchematicListSchema.parse);
@ -42,8 +43,8 @@ export class SchematicRepo {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
name, name,
content content,
}) }),
}); });
} }
} }

Datei anzeigen

@ -24,7 +24,8 @@ import {derived} from "svelte/store";
export class StatsRepo { export class StatsRepo {
constructor(private token: string) {} constructor(private token: string) {
}
public async getRankings(gamemode: string): Promise<Ranking> { public async getRankings(gamemode: string): Promise<Ranking> {
return await fetchWithToken(this.token, `/stats/ranked/${gamemode}`).then(value => value.json()).then(RankingSchema.parse); return await fetchWithToken(this.token, `/stats/ranked/${gamemode}`).then(value => value.json()).then(RankingSchema.parse);

Datei anzeigen

@ -21,7 +21,7 @@ import {readonly, writable} from "svelte/store";
import type {Readable, Subscriber, Unsubscriber} from "svelte/store"; import type {Readable, Subscriber, Unsubscriber} from "svelte/store";
export interface Cached<T> extends Readable<T>{ export interface Cached<T> extends Readable<T> {
reload: () => void; reload: () => void;
} }
@ -38,20 +38,20 @@ export function cached<T>(normal: T, init: () => Promise<T>): Cached<T> {
return { return {
...readonly(store), ...readonly(store),
subscribe: (run: Subscriber<T>, invalidate?: (value?: T) => void): Unsubscriber => { subscribe: (run: Subscriber<T>, invalidate?: (value?: T) => void): Unsubscriber => {
if(first) { if (first) {
first = false; first = false;
reload(); reload();
} }
return store.subscribe(run, invalidate); return store.subscribe(run, invalidate);
}, },
reload reload,
}; };
} }
export function cachedFamily<T, K>(normal: K, init: (arg0: T) => Promise<K>): (arg: T) => Cached<K> { export function cachedFamily<T, K>(normal: K, init: (arg0: T) => Promise<K>): (arg: T) => Cached<K> {
const stores: Map<T, Cached<K>> = new Map(); const stores: Map<T, Cached<K>> = new Map();
return (arg: T) => { return (arg: T) => {
if(stores.has(arg)) { if (stores.has(arg)) {
return stores.get(arg)!; return stores.get(arg)!;
} else { } else {
const store = writable<K>(normal); const store = writable<K>(normal);
@ -66,13 +66,13 @@ export function cachedFamily<T, K>(normal: K, init: (arg0: T) => Promise<K>): (a
const cachedStore = { const cachedStore = {
...readonly(store), ...readonly(store),
subscribe: (run: Subscriber<K>, invalidate?: (value?: K) => void): Unsubscriber => { subscribe: (run: Subscriber<K>, invalidate?: (value?: K) => void): Unsubscriber => {
if(first) { if (first) {
first = false; first = false;
reload(); reload();
} }
return store.subscribe(run, invalidate); return store.subscribe(run, invalidate);
}, },
reload reload,
} as Cached<K>; } as Cached<K>;
stores.set(arg, cachedStore); stores.set(arg, cachedStore);

Datei anzeigen

@ -41,13 +41,13 @@ export const ServerSchema = z.object({
players: z.object({ players: z.object({
online: z.number(), online: z.number(),
max: z.number(), max: z.number(),
sample: z.array(z.any()).optional() sample: z.array(z.any()).optional(),
}), }),
version: z.object({ version: z.object({
name: z.string(), name: z.string(),
protocol: z.number() protocol: z.number(),
}), }),
favicon: z.string().optional() favicon: z.string().optional(),
}); });
export type Server = z.infer<typeof ServerSchema>; export type Server = z.infer<typeof ServerSchema>;

Datei anzeigen

@ -24,7 +24,7 @@ export const ListPageSchema = z.object({
name: z.string(), name: z.string(),
sha: z.string(), sha: z.string(),
downloadUrl: z.string().url(), downloadUrl: z.string().url(),
id: z.number().positive() id: z.number().positive(),
}); });
export type ListPage = z.infer<typeof ListPageSchema>; export type ListPage = z.infer<typeof ListPageSchema>;
@ -40,12 +40,12 @@ export const PageSchema = z.object({
downloadUrl: z.string().url(), downloadUrl: z.string().url(),
content: z.string(), content: z.string(),
size: z.number().gte(0), size: z.number().gte(0),
id: z.number().positive() id: z.number().positive(),
}); });
export type Page = z.infer<typeof PageSchema>; export type Page = z.infer<typeof PageSchema>;
export type Pages = { export type Pages = {
dirs: {[key: string]: Pages}, dirs: { [key: string]: Pages },
files: {[key: string]: ListPage} files: { [key: string]: ListPage }
} }

Datei anzeigen

@ -22,7 +22,7 @@ import {z} from "zod";
export const PrefixSchema = z.object({ export const PrefixSchema = z.object({
name: z.string().startsWith("PREFIX_"), name: z.string().startsWith("PREFIX_"),
colorCode: z.string().length(2).startsWith("§"), colorCode: z.string().length(2).startsWith("§"),
chatPrefix: z.string() chatPrefix: z.string(),
}); });
export type Prefix = z.infer<typeof PrefixSchema>; export type Prefix = z.infer<typeof PrefixSchema>;

Datei anzeigen

@ -29,7 +29,7 @@ export const SchematicSchema = z.object({
lastUpdate: z.number().positive(), lastUpdate: z.number().positive(),
rank: z.number(), rank: z.number(),
replaceColor: z.boolean(), replaceColor: z.boolean(),
allowReplay: z.boolean() allowReplay: z.boolean(),
}); });
export type Schematic = z.infer<typeof SchematicSchema> export type Schematic = z.infer<typeof SchematicSchema>
@ -37,10 +37,10 @@ export type Schematic = z.infer<typeof SchematicSchema>
export const SchematicListSchema = z.object({ export const SchematicListSchema = z.object({
breadcrumbs: z.array(z.object({ breadcrumbs: z.array(z.object({
name: z.string(), name: z.string(),
id: z.number() id: z.number(),
})), })),
schematics: z.array(SchematicSchema), schematics: z.array(SchematicSchema),
players: z.record(z.string(), PlayerSchema) players: z.record(z.string(), PlayerSchema),
}); });
export type SchematicList = z.infer<typeof SchematicListSchema> export type SchematicList = z.infer<typeof SchematicListSchema>
@ -48,7 +48,7 @@ export type SchematicList = z.infer<typeof SchematicListSchema>
export const SchematicInfoSchema = z.object({ export const SchematicInfoSchema = z.object({
members: z.array(PlayerSchema), members: z.array(PlayerSchema),
path: z.string(), path: z.string(),
schem: SchematicSchema schem: SchematicSchema,
}); });
export type SchematicInfo = z.infer<typeof SchematicInfoSchema> export type SchematicInfo = z.infer<typeof SchematicInfoSchema>
@ -56,7 +56,7 @@ export type SchematicInfo = z.infer<typeof SchematicInfoSchema>
export const SchematicCodeSchema = z.object({ export const SchematicCodeSchema = z.object({
id: z.number().gte(0), id: z.number().gte(0),
code: z.string(), code: z.string(),
expires: z.number().positive() expires: z.number().positive(),
}); });
export type SchematicCode = z.infer<typeof SchematicCodeSchema> export type SchematicCode = z.infer<typeof SchematicCodeSchema>

Datei anzeigen

@ -39,7 +39,7 @@ export const UserStatsSchema = z.object({
eventParticipation: z.number(), eventParticipation: z.number(),
acceptedSchematics: z.number(), acceptedSchematics: z.number(),
fights: z.number(), fights: z.number(),
playtime: z.number() playtime: z.number(),
}); });
export type UserStats = z.infer<typeof UserStatsSchema>; export type UserStats = z.infer<typeof UserStatsSchema>;

Datei anzeigen

@ -17,19 +17,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @ts-ignore
import {defineCollection, reference, z} from "astro:content"; import {defineCollection, reference, z} from "astro:content";
export const pagesSchema = z.object({ export const pagesSchema = z.object({
title: z.string().min(1).max(80), title: z.string().min(1).max(80),
description: z.string().min(1).max(120), description: z.string().min(1).max(120),
german: z.boolean().optional().default(false), german: z.boolean().optional().default(false),
image: z.string().optional() image: z.string().optional(),
}); });
export const pages = defineCollection({ export const pages = defineCollection({
type: "content", type: "content",
schema: pagesSchema schema: pagesSchema,
}); });
export const help = defineCollection({ export const help = defineCollection({
@ -38,8 +37,8 @@ export const help = defineCollection({
title: z.string().min(1).max(80), title: z.string().min(1).max(80),
description: z.string().min(1).max(120), description: z.string().min(1).max(120),
tags: z.array(z.string()), tags: z.array(z.string()),
related: z.array(reference("help")).optional() related: z.array(reference("help")).optional(),
}) }),
}); });
export const modes = defineCollection({ export const modes = defineCollection({
@ -47,8 +46,8 @@ export const modes = defineCollection({
schema: z.object({ schema: z.object({
translationKey: z.string(), translationKey: z.string(),
main: z.boolean(), main: z.boolean(),
ranked: z.boolean().optional().default(false) ranked: z.boolean().optional().default(false),
}) }),
}); });
export const downloads = defineCollection({ export const downloads = defineCollection({
@ -59,14 +58,14 @@ export const downloads = defineCollection({
url: z.string().url() url: z.string().url()
.or(z.record(z.string(), z.string())), .or(z.record(z.string(), z.string())),
sourceUrl: z.string().url().optional(), sourceUrl: z.string().url().optional(),
}) }),
}); });
export const rules = defineCollection({ export const rules = defineCollection({
type: "content", type: "content",
schema: z.object({ schema: z.object({
translationKey: z.string(), translationKey: z.string(),
}) }),
}); });
export const announcements = defineCollection({ export const announcements = defineCollection({
@ -77,8 +76,8 @@ export const announcements = defineCollection({
image: image().optional(), image: image().optional(),
tags: z.array(z.string()), tags: z.array(z.string()),
created: z.date(), created: z.date(),
key: z.string() key: z.string(),
}) }),
}); });
export const collections = { export const collections = {
@ -87,5 +86,5 @@ export const collections = {
"modes": modes, "modes": modes,
"rules": rules, "rules": rules,
"downloads": downloads, "downloads": downloads,
"announcements": announcements "announcements": announcements,
}; };

Datei anzeigen

@ -2,8 +2,8 @@
import icon from "../images/logo.png"; import icon from "../images/logo.png";
import {getImage} from "astro:assets"; import {getImage} from "astro:assets";
import {astroI18n} from "astro-i18n"; import {astroI18n} from "astro-i18n";
const { title, description } = Astro.props.frontmatter || Astro.props; const {title, description} = Astro.props.frontmatter || Astro.props;
import { SEO } from "astro-seo"; import {SEO} from "astro-seo";
import "../../public/fonts/roboto/roboto.css"; import "../../public/fonts/roboto/roboto.css";
@ -17,24 +17,24 @@ const iconImage = await getImage({src: icon, height: 32, width: 32, format: "png
content="width=device-width, user-scalable=5, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0"> content="width=device-width, user-scalable=5, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="icbm" content="52.370216;4.895168"/> <meta name="icbm" content="52.370216;4.895168"/>
<link rel="icon" type="imgage/png" href={iconImage.src} /> <link rel="icon" type="imgage/png" href={iconImage.src}/>
<SEO <SEO
title={title} title={title}
description={description} description={description}
twitter={{ twitter={{
creator: "@chaoscaot" creator: "@chaoscaot",
}} }}
languageAlternates={astroI18n.locales.map((locale) => ({ languageAlternates={astroI18n.locales.map((locale) => ({
hrefLang: locale, hrefLang: locale,
href: `/${locale}/` href: `/${locale}/`,
}))} }))}
/> />
<slot name="head" /> <slot name="head"/>
</head> </head>
<body class="dark:bg-zinc-800"> <body class="dark:bg-zinc-800">
<slot /> <slot/>
</body> </body>
</html> </html>

Datei anzeigen

@ -1,5 +1,5 @@
--- ---
import { Image } from "astro:assets"; import {Image} from "astro:assets";
import Basic from "./Basic.astro"; import Basic from "./Basic.astro";
import "../styles/button.css"; import "../styles/button.css";
import localLogo from "../images/logo.png"; import localLogo from "../images/logo.png";
@ -9,18 +9,19 @@ import {l} from "../util/util";
import ServerStatus from "../components/ServerStatus.svelte"; import ServerStatus from "../components/ServerStatus.svelte";
const { title, description } = Astro.props; const {title, description} = Astro.props;
--- ---
<Basic title={title} description={description}> <Basic title={title} description={description}>
<slot name="head" slot="head" /> <slot name="head" slot="head"/>
<Fragment> <Fragment>
<div class="min-h-screen flex flex-col"> <div class="min-h-screen flex flex-col">
<nav-bar class="fixed top-0 left-0 right-0 px-4 transition-colors z-10 flex justify-center \ <nav-bar class="fixed top-0 left-0 right-0 px-4 transition-colors z-10 flex justify-center \
before:bg-black before:absolute before:top-0 before:left-0 before:bottom-0 before:right-0 before:-z-10 before:scale-y-0 before:transition-transform before:origin-top"> before:bg-black before:absolute before:top-0 before:left-0 before:bottom-0 before:right-0 before:-z-10 before:scale-y-0 before:transition-transform before:origin-top">
<div class="flex flex-col md:flex-row items-center justify-evenly md:justify-between match"> <div class="flex flex-col md:flex-row items-center justify-evenly md:justify-between match">
<a class="flex items-center" href={l("/")}> <a class="flex items-center" href={l("/")}>
<Image src={localLogo} alt={t("navbar.logo.alt")} width="44" height="44" quality="max" class="mr-2 p-1 bg-black rounded-full" /> <Image src={localLogo} alt={t("navbar.logo.alt")} width="44" height="44" quality="max"
class="mr-2 p-1 bg-black rounded-full"/>
<h1 class="text-2xl uppercase font-bold inline-block dark:text-white"> <h1 class="text-2xl uppercase font-bold inline-block dark:text-white">
{t("navbar.title")} {t("navbar.title")}
</h1> </h1>
@ -31,10 +32,11 @@ const { title, description } = Astro.props;
<a href={l("/")}> <a href={l("/")}>
<span class="btn__text">{t("navbar.links.home.title")}</span> <span class="btn__text">{t("navbar.links.home.title")}</span>
</a> </a>
<CaretDownOutline class="ml-2 mt-auto" /> <CaretDownOutline class="ml-2 mt-auto"/>
</button> </button>
<div> <div>
<a class="btn btn-gray my-1" href={l("/announcements")}>{t("navbar.links.home.announcements")}</a> <a class="btn btn-gray my-1"
href={l("/announcements")}>{t("navbar.links.home.announcements")}</a>
<a class="btn btn-gray" href={l("/about")}>{t("navbar.links.home.about")}</a> <a class="btn btn-gray" href={l("/about")}>{t("navbar.links.home.about")}</a>
<a class="btn btn-gray" href={l("/downloads")}>{t("navbar.links.home.downloads")}</a> <a class="btn btn-gray" href={l("/downloads")}>{t("navbar.links.home.downloads")}</a>
<a class="btn btn-gray" href={l("/faq")}>{t("navbar.links.home.faq")}</a> <a class="btn btn-gray" href={l("/faq")}>{t("navbar.links.home.faq")}</a>
@ -45,7 +47,7 @@ const { title, description } = Astro.props;
<a rel="prefetch" href={l("/rules")}> <a rel="prefetch" href={l("/rules")}>
<span class="btn__text">{t("navbar.links.rules.title")}</span> <span class="btn__text">{t("navbar.links.rules.title")}</span>
</a> </a>
<CaretDownOutline class="ml-2 mt-auto" /> <CaretDownOutline class="ml-2 mt-auto"/>
</button> </button>
<div> <div>
<h2 class="px-2 text-gray-300">{t("navbar.links.rules.gamemode")}</h2> <h2 class="px-2 text-gray-300">{t("navbar.links.rules.gamemode")}</h2>
@ -55,9 +57,11 @@ const { title, description } = Astro.props;
<a href={l("/rules/airship")} class="btn btn-gray">{t("navbar.links.rules.as")}</a> <a href={l("/rules/airship")} class="btn btn-gray">{t("navbar.links.rules.as")}</a>
<a href={l("/rules/quickgear")} class="btn btn-gray">{t("navbar.links.rules.qg")}</a> <a href={l("/rules/quickgear")} class="btn btn-gray">{t("navbar.links.rules.qg")}</a>
<h2 class="px-2 text-gray-300">{t("navbar.links.rules.rotating")}</h2> <h2 class="px-2 text-gray-300">{t("navbar.links.rules.rotating")}</h2>
<a href={l("/rules/megawargear")}class="btn btn-gray">{t("navbar.links.rules.megawg")}</a> <a href={l("/rules/megawargear")}
<a href={l("/rules/microwargear")}class="btn btn-gray">{t("navbar.links.rules.micro")}</a> class="btn btn-gray">{t("navbar.links.rules.megawg")}</a>
<a href={l("/rules/streetfight")}class="btn btn-gray">{t("navbar.links.rules.sf")}</a> <a href={l("/rules/microwargear")}
class="btn btn-gray">{t("navbar.links.rules.micro")}</a>
<a href={l("/rules/streetfight")} class="btn btn-gray">{t("navbar.links.rules.sf")}</a>
<h2 class="px-2 text-gray-300">{t("navbar.links.rules.general")}</h2> <h2 class="px-2 text-gray-300">{t("navbar.links.rules.general")}</h2>
<a class="btn btn-gray" href={l("/code-of-conduct")}>{t("navbar.links.rules.coc")}</a> <a class="btn btn-gray" href={l("/code-of-conduct")}>{t("navbar.links.rules.coc")}</a>
</div> </div>
@ -83,7 +87,7 @@ const { title, description } = Astro.props;
</div> </div>
</nav-bar> </nav-bar>
<script> <script>
class Navbar extends HTMLElement { class Navbar extends HTMLElement {
constructor() { constructor() {
super(); super();
if (window.scrollY != 0) { if (window.scrollY != 0) {
@ -100,18 +104,18 @@ class Navbar extends HTMLElement {
} }
}; };
} }
} }
customElements.define("nav-bar", Navbar); customElements.define("nav-bar", Navbar);
</script> </script>
<main class="flex-1"> <main class="flex-1">
<slot /> <slot/>
</main> </main>
<footer class="bg-gray-900 w-full min-h-80 mt-4 pb-2 rounded-t-2xl flex flex-col dark:bg-neutral-900"> <footer class="bg-gray-900 w-full min-h-80 mt-4 pb-2 rounded-t-2xl flex flex-col dark:bg-neutral-900">
<div class="flex-1 flex justify-evenly items-center md:items-start mt-4 md:flex-row flex-col gap-y-4"> <div class="flex-1 flex justify-evenly items-center md:items-start mt-4 md:flex-row flex-col gap-y-4">
<div class="footer-card"> <div class="footer-card">
<h1>Serverstatus</h1> <h1>Serverstatus</h1>
<ServerStatus client:only="svelte" /> <ServerStatus client:only="svelte"/>
</div> </div>
<div class="footer-card"> <div class="footer-card">
<h1>Links</h1> <h1>Links</h1>
@ -126,8 +130,12 @@ customElements.define("nav-bar", Navbar);
</div> </div>
<div class="footer-card"> <div class="footer-card">
<h1>Social Media</h1> <h1>Social Media</h1>
<a class="flex" href="/youtube"><YoutubeSolid class="mr-2" /> YouTube</a> <a class="flex" href="/youtube">
<a class="flex" href="/discord"><DiscordSolid class="mr-2" /> Discord</a> <YoutubeSolid class="mr-2"/>
YouTube</a>
<a class="flex" href="/discord">
<DiscordSolid class="mr-2"/>
Discord</a>
</div> </div>
</div> </div>
<span class="text-sm text-white text-center mt-1">© SteamWar.de - Made with ❤️ by Chaoscaot</span> <span class="text-sm text-white text-center mt-1">© SteamWar.de - Made with ❤️ by Chaoscaot</span>
@ -139,10 +147,12 @@ customElements.define("nav-bar", Navbar);
<style> <style>
.footer-card { .footer-card {
@apply w-40 text-gray-400 flex flex-col; @apply w-40 text-gray-400 flex flex-col;
>h1 {
> h1 {
@apply text-xl font-bold text-gray-100; @apply text-xl font-bold text-gray-100;
} }
>a {
> a {
@apply hover:underline; @apply hover:underline;
} }
} }

Datei anzeigen

@ -1,16 +1,17 @@
--- ---
import NavbarLayout from "./NavbarLayout.astro"; import NavbarLayout from "./NavbarLayout.astro";
import localBau from "../images/2023-10-08_20.43.43.png"; import localBau from "../images/2023-10-08_20.43.43.png";
import { Image } from "astro:assets"; import {Image} from "astro:assets";
const { title, description, image } = Astro.props; const {title, description, image} = Astro.props;
--- ---
<NavbarLayout title={title} description={description}> <NavbarLayout title={title} description={description}>
<slot name="head" slot="head" /> <slot name="head" slot="head"/>
<Image src={image || localBau} alt="Bau" width="1920" height="1080" densities={[1.5, 2, 3, 4]} class="w-full h-screen dark:brightness-75 fixed -z-10 object-cover" draggable="false" /> <Image src={image || localBau} alt="Bau" width="1920" height="1080" densities={[1.5, 2, 3, 4]}
class="w-full h-screen dark:brightness-75 fixed -z-10 object-cover" draggable="false"/>
<div class="mx-auto bg-gray-100 p-8 rounded-b-md shadow-md pt-40 sm:pt-28 md:pt-14 relative <div class="mx-auto bg-gray-100 p-8 rounded-b-md shadow-md pt-40 sm:pt-28 md:pt-14 relative
dark:text-white dark:bg-neutral-900" style="width: min(100vw, 75em);"> dark:text-white dark:bg-neutral-900" style="width: min(100vw, 75em);">
<slot /> <slot/>
</div> </div>
</NavbarLayout> </NavbarLayout>

Datei anzeigen

@ -17,31 +17,31 @@ export const getStaticPaths = createGetStaticPaths(async () => {
return posts.map((page) => ({ return posts.map((page) => ({
props: { props: {
page page,
}, },
params: { params: {
slug: fixLink(page.slug) slug: fixLink(page.slug),
} },
})); }));
}); });
const { page } = Astro.props; const {page} = Astro.props;
const { Content } = await page.render(); const {Content} = await page.render();
--- ---
<PageLayout title={page.data.title}> <PageLayout title={page.data.title}>
<article> <article>
{page.data.german && ( {page.data.german && (
<LanguageWarning /> <LanguageWarning/>
)} )}
<h1 class="text-left">{page.data.title}</h1> <h1 class="text-left">{page.data.title}</h1>
<Content /> <Content/>
</article> </article>
</PageLayout> </PageLayout>
<style is:global> <style is:global>
article { article {
>* { > * {
all: revert; all: revert;
} }

Datei anzeigen

@ -4,5 +4,5 @@ import Basic from "../../layouts/Basic.astro";
--- ---
<Basic> <Basic>
<App client:only="svelte" /> <App client:only="svelte"/>
</Basic> </Basic>

Datei anzeigen

@ -5,7 +5,7 @@ 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 TagComponent from "@components/TagComponent.astro";
import LanguageWarning from "@components/LanguageWarning.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 localBau from "@images/2022-03-28_13.18.25.png";
import {getImage, Image} from "astro:assets"; import {getImage, Image} from "astro:assets";
@ -24,12 +24,12 @@ export const getStaticPaths = createGetStaticPaths(async () => {
return posts.map(value => ({ return posts.map(value => ({
params: { params: {
slug: value.slug.split("/").slice(1).join("/") slug: value.slug.split("/").slice(1).join("/"),
}, },
props: { props: {
post: value, post: value,
german: value.id.split("/")[0] != astroI18n.locale german: value.id.split("/")[0] != astroI18n.locale,
} },
})); }));
}); });
@ -39,7 +39,7 @@ interface Props {
} }
const {post, german} = Astro.props; const {post, german} = Astro.props;
const { Content } = await post.render(); const {Content} = await post.render();
const ogImage = await getImage({ const ogImage = await getImage({
src: post.data.image || localBau, src: post.data.image || localBau,
@ -47,7 +47,6 @@ const ogImage = await getImage({
width: 1200, width: 1200,
height: 630, height: 630,
}); });
--- ---
<PageLayout title={post.data.title} description={post.data.description}> <PageLayout title={post.data.title} description={post.data.description}>
@ -71,24 +70,29 @@ const ogImage = await getImage({
<div class={"relative w-full " + (post.data.image ? "aspect-video" : "")}> <div class={"relative w-full " + (post.data.image ? "aspect-video" : "")}>
{post.data.image && ( {post.data.image && (
<div class="absolute top-0 left-0 w-full aspect-video flex justify-center"> <div class="absolute top-0 left-0 w-full aspect-video flex justify-center">
<Image src={post.data.image} height="1080" alt="" class="rounded-2xl linear-fade object-contain h-full" /> <Image src={post.data.image} height="1080" alt=""
class="rounded-2xl linear-fade object-contain h-full"/>
</div> </div>
)} )}
<div class={post.data.image ? "absolute bottom-8 left-2" : "mb-4"}> <div class={post.data.image ? "absolute bottom-8 left-2" : "mb-4"}>
<h1 class="text-4xl mb-0">{post.data.title}</h1> <h1 class="text-4xl mb-0">{post.data.title}</h1>
<h5 class="flex items-center mt-2 text-neutral-300"><TagSolid class="w-4 h-4 mr-2" /> {post.data.tags.map(tag => ( <h5 class="flex items-center mt-2 text-neutral-300">
<TagComponent tag={tag} /> <TagSolid class="w-4 h-4 mr-2"/>
))} <CalendarMonthSolid class="w-4 h-4 mr-2" /> {Intl.DateTimeFormat(astroI18n.locale, { {post.data.tags.map(tag => (
<TagComponent tag={tag}/>
))}
<CalendarMonthSolid class="w-4 h-4 mr-2"/>
{Intl.DateTimeFormat(astroI18n.locale, {
day: "numeric", day: "numeric",
month: "short", month: "short",
year: "numeric" year: "numeric",
}).format(post.data.created)} </h5> }).format(post.data.created)} </h5>
</div> </div>
</div> </div>
{german && ( {german && (
<LanguageWarning /> <LanguageWarning/>
)} )}
<Content /> <Content/>
<script> <script>
import FightTable from "@components/FightTable.svelte"; import FightTable from "@components/FightTable.svelte";
// @ts-expect-error Import Schenanigans // @ts-expect-error Import Schenanigans
@ -96,6 +100,7 @@ const ogImage = await getImage({
import GroupTable from "@components/GroupTable.svelte"; import GroupTable from "@components/GroupTable.svelte";
import {eventRepo} from "../../components/repo/event"; import {eventRepo} from "../../components/repo/event";
import type {ExtendedEvent} from "@type/event"; import type {ExtendedEvent} from "@type/event";
const eventMounts: Map<string, ((ev: ExtendedEvent) => void)[]> = new Map(); const eventMounts: Map<string, ((ev: ExtendedEvent) => void)[]> = new Map();
class FightTableElement extends HTMLElement { class FightTableElement extends HTMLElement {
@ -111,7 +116,7 @@ const ogImage = await getImage({
event: ev, event: ev,
group: this.dataset["group"], group: this.dataset["group"],
rows: !isNaN(rows) ? rows : 1, rows: !isNaN(rows) ? rows : 1,
} },
}); });
}); });
} }
@ -130,7 +135,7 @@ const ogImage = await getImage({
event: ev, event: ev,
group: this.dataset["group"], group: this.dataset["group"],
rows: !isNaN(rows) ? rows : 1, rows: !isNaN(rows) ? rows : 1,
} },
}); });
}); });
} }
@ -160,7 +165,7 @@ const ogImage = await getImage({
display: contents; display: contents;
} }
>* { > * {
all: revert; all: revert;
} }
@ -176,6 +181,6 @@ const ogImage = await getImage({
<style> <style>
.linear-fade { .linear-fade {
mask-image: linear-gradient(to bottom, rgba(0,0,0,1), rgba(0,0,0,1), rgba(0,0,0,1), rgba(0,0,0,0)); mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1), rgba(0, 0, 0, 1), rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
} }
</style> </style>

Datei anzeigen

@ -1,5 +1,5 @@
--- ---
import { getCollection } from "astro:content"; import {getCollection} from "astro:content";
import PageLayout from "../../layouts/PageLayout.astro"; import PageLayout from "../../layouts/PageLayout.astro";
import {astroI18n, t} from "astro-i18n"; import {astroI18n, t} from "astro-i18n";
import PostComponent from "../../components/PostComponent.astro"; import PostComponent from "../../components/PostComponent.astro";
@ -22,14 +22,14 @@ async function getPosts() {
} }
const posts = await getPosts(); const posts = await getPosts();
--- ---
<PageLayout title={t("blog.title")}> <PageLayout title={t("blog.title")}>
{posts.map((post, index) => ( {posts.map((post, index) => (
<div> <div>
<PostComponent post={post} /> <PostComponent post={post}/>
{index !== posts.length - 1 && <hr/>} {index !== posts.length - 1 &&
<hr/>}
</div> </div>
))} ))}
</PageLayout> </PageLayout>

Datei anzeigen

@ -36,28 +36,29 @@ export const getStaticPaths = createGetStaticPaths(async () => {
return Object.keys(groupedByTags).map(tag => ({ return Object.keys(groupedByTags).map(tag => ({
params: { params: {
tag: tag tag: tag,
}, },
props: { props: {
posts: groupedByTags[tag], posts: groupedByTags[tag],
tag: tag tag: tag,
} },
})); }));
}); });
interface Props { interface Props {
posts: CollectionEntry<"announcements">[] posts: CollectionEntry<"announcements">[];
tag: string tag: string;
} }
const { posts, tag } = Astro.props; const {posts, tag} = Astro.props;
--- ---
<PageLayout title={t("tag.title", {tag: capitalize(tag)})}> <PageLayout title={t("tag.title", {tag: capitalize(tag)})}>
{posts.map((post, index) => ( {posts.map((post, index) => (
<div> <div>
<PostComponent post={post} /> <PostComponent post={post}/>
{index !== posts.length - 1 && <hr/>} {index !== posts.length - 1 &&
<hr/>}
</div> </div>
))} ))}
</PageLayout> </PageLayout>

Datei anzeigen

@ -7,11 +7,12 @@ import {t} from "astro-i18n";
<PageLayout title={t("dashboard.page")}> <PageLayout title={t("dashboard.page")}>
<script> <script>
import {l} from "../util/util"; import {l} from "../util/util";
if (window.location.href.endsWith("/dashboard") || window.location.href.endsWith("/dashboard/")) { if (window.location.href.endsWith("/dashboard") || window.location.href.endsWith("/dashboard/")) {
if ((localStorage.getItem("sw-session") ?? "") === "") { if ((localStorage.getItem("sw-session") ?? "") === "") {
window.location.href = l("/login"); window.location.href = l("/login");
} }
} }
</script> </script>
<Dashboard client:only="svelte" /> <Dashboard client:only="svelte"/>
</PageLayout> </PageLayout>

Datei anzeigen

@ -10,18 +10,21 @@ const downloads = await getCollection("downloads");
<PageLayout title="Downloads"> <PageLayout title="Downloads">
{downloads.map(e => ( {downloads.map(e => (
<div class="pt-4"> <div class="pt-4">
<h1 class="font-bold text-6xl">{e.data.name}</h1> <h1 class="font-bold text-2xl">{e.data.name}</h1>
<div class="py-4">{t(e.data.description)}</div> <div class="py-4">{t(e.data.description)}</div>
<div class="flex flex-col"> <div class="flex flex-col">
{typeof e.data.url === "object" ? {typeof e.data.url === "object" ?
Object.entries(e.data.url).map(value => ( Object.entries(e.data.url).map(value => (
<a href={value[1].startsWith("/") ? l(value[1]) : value[1]} class="text-blue-500 hover:underline w-fit">{t(value[0])}</a> <a href={value[1].startsWith("/") ? l(value[1]) : value[1]}
class="text-blue-500 hover:underline w-fit">{t(value[0])}</a>
)) ))
: :
<a href={e.data.url} class="text-blue-500 hover:underline w-fit">{t("Download")}</a> <a href={e.data.url} class="text-blue-500 hover:underline w-fit">{t("Download")}</a>
} }
{e.data.sourceUrl ? <a class="text-blue-500 hover:underline w-fit" href={e.data.sourceUrl}>Quelle</a> : null} {e.data.sourceUrl ?
<a class="text-blue-500 hover:underline w-fit" href={e.data.sourceUrl}>Quelle</a>
: null}
</div> </div>
</div> </div>
))} ))}

Datei anzeigen

@ -1,5 +1,5 @@
--- ---
import { getCollection } from "astro:content"; import {getCollection} from "astro:content";
import NavbarLayout from "../../layouts/NavbarLayout.astro"; import NavbarLayout from "../../layouts/NavbarLayout.astro";
import {astroI18n, createGetStaticPaths} from "astro-i18n"; import {astroI18n, createGetStaticPaths} from "astro-i18n";
@ -7,24 +7,24 @@ export const getStaticPaths = createGetStaticPaths(async () => {
let posts = await getCollection("help"); let posts = await getCollection("help");
return posts.filter(value => value.id.split("/")[0] === astroI18n.locale).map((page) => ({ return posts.filter(value => value.id.split("/")[0] === astroI18n.locale).map((page) => ({
props: { page }, params: { slug: page.slug } props: {page}, params: {slug: page.slug},
}) ); }));
}); });
const { page } = Astro.props; const {page} = Astro.props;
const { Content } = await page.render(); const {Content} = await page.render();
--- ---
<NavbarLayout title={page.data.title}> <NavbarLayout title={page.data.title}>
<article> <article>
<h1 class="text-left">{page.data.title}</h1> <h1 class="text-left">{page.data.title}</h1>
<Content /> <Content/>
</article> </article>
</NavbarLayout> </NavbarLayout>
<style is:global> <style is:global>
article { article {
>* { > * {
all: revert; all: revert;
} }

Datei anzeigen

@ -1,7 +1,7 @@
--- ---
import NavbarLayout from "@layouts/NavbarLayout.astro"; import NavbarLayout from "@layouts/NavbarLayout.astro";
import { Image } from "astro:assets"; import {Image} from "astro:assets";
import localBau from "@images/2023-10-08_20.43.43.png"; import localBau from "@images/2023-10-08_20.43.43.png";
import {CaretRight, Archive, Rocket, Bell} from "@astropub/icons"; import {CaretRight, Archive, Rocket, Bell} from "@astropub/icons";
import {t} from "astro-i18n"; import {t} from "astro-i18n";
@ -35,22 +35,30 @@ function mapMap<T, K, J>(i: Map<T, K>, fn: (key: T, value: K) => J): J[] {
} }
return arr; return arr;
} }
--- ---
<NavbarLayout title={t("home.page")} description="SteamWar.de Homepage"> <NavbarLayout title={t("home.page")} description="SteamWar.de Homepage">
<div class="w-full h-screen relative mb-4"> <div class="w-full h-screen relative mb-4">
<Image src={localBau} alt="Bau" width="1920" height="1080" densities={[1.5, 2, 3, 4]} class="w-full object-cover rounded-b-2xl shadow-2xl dark:brightness-75" style="height: calc(100vh + 1rem)" draggable="false" /> <Image src={localBau} alt="Bau" width="1920" height="1080" densities={[1.5, 2, 3, 4]}
class="w-full object-cover rounded-b-2xl shadow-2xl dark:brightness-75"
style="height: calc(100vh + 1rem)" draggable="false"/>
<drop-in class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center"> <drop-in class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center">
<h1 class="text-4xl sm:text-6xl md:text-8xl font-extrabold text-white -translate-y-16 opacity-0 barlow" style="transition: transform .7s ease-out, opacity .7s linear; text-shadow: 2px 2px 5px black;"> <h1 class="text-4xl sm:text-6xl md:text-8xl font-extrabold text-white -translate-y-16 opacity-0 barlow"
<span class="text-yellow-400">{t("home.title.first")}</span><span class="text-neutral-600">{t("home.title.second")}</span> style="transition: transform .7s ease-out, opacity .7s linear; text-shadow: 2px 2px 5px black;">
<span class="text-yellow-400">{t("home.title.first")}</span><span
class="text-neutral-600">{t("home.title.second")}</span>
</h1> </h1>
<text-carousel class="h-20 w-full relative select-none"> <text-carousel class="h-20 w-full relative select-none">
<h2 class="-translate-y-16">{t("home.subtitle.1")}</h2> <h2 class="-translate-y-16">{t("home.subtitle.1")}</h2>
<h2>{t("home.subtitle.2")}<PlayerCount client:only="svelte" /></h2> <h2>{t("home.subtitle.2")}
<PlayerCount client:only="svelte"/>
</h2>
<h2>{t("home.subtitle.3")}</h2> <h2>{t("home.subtitle.3")}</h2>
</text-carousel> </text-carousel>
<a href={l("join")} class="btn mt-32 px-8 flex opacity-0 -translate-y-16" style="transition: transform .3s ease-out, opacity .3s linear">{t("home.join")} <CaretRight width="24" heigth="24" /></a> <a href={l("join")} class="btn mt-32 px-8 flex opacity-0 -translate-y-16"
style="transition: transform .3s ease-out, opacity .3s linear">{t("home.join")}
<CaretRight width="24" heigth="24"/>
</a>
<script> <script>
class TextCarousel extends HTMLElement { class TextCarousel extends HTMLElement {
current = 0; current = 0;
@ -87,7 +95,7 @@ function mapMap<T, K, J>(i: Map<T, K>, fn: (key: T, value: K) => J): J[] {
class DropIn extends HTMLElement { class DropIn extends HTMLElement {
connectedCallback() { connectedCallback() {
for (let child of this.children) { for (let child of this.children) {
if(child.classList.contains("opacity-0")) { if (child.classList.contains("opacity-0")) {
child.classList.remove("opacity-0"); child.classList.remove("opacity-0");
child.classList.remove("-translate-y-16"); child.classList.remove("-translate-y-16");
} else { } else {
@ -106,24 +114,26 @@ function mapMap<T, K, J>(i: Map<T, K>, fn: (key: T, value: K) => J): J[] {
<section class="w-full flex flex-col items-center justify-center shadow-2xl rounded-b-2xl pb-8"> <section class="w-full flex flex-col items-center justify-center shadow-2xl rounded-b-2xl pb-8">
<div class="py-10 flex flex-col lg:flex-row"> <div class="py-10 flex flex-col lg:flex-row">
<div class="card"> <div class="card">
<Archive heigth="64" width="64" /> <Archive heigth="64" width="64"/>
<h1>{t("home.benefits.historic.title")}</h1> <h1>{t("home.benefits.historic.title")}</h1>
<p>{t("home.benefits.historic.description.1")}</p> <p>{t("home.benefits.historic.description.1")}</p>
<p>{t("home.benefits.historic.description.2")}</p> <p>{t("home.benefits.historic.description.2")}</p>
</div> </div>
<div class="card"> <div class="card">
<Rocket heigth="64" width="64" /> <Rocket heigth="64" width="64"/>
<h1>{t("home.benefits.server.title")}</h1> <h1>{t("home.benefits.server.title")}</h1>
<p>{t("home.benefits.server.description")}</p> <p>{t("home.benefits.server.description")}</p>
</div> </div>
<div class="card"> <div class="card">
<Bell heigth="64" width="64" /> <Bell heigth="64" width="64"/>
<h1>{t("home.benefits.events.title")}</h1> <h1>{t("home.benefits.events.title")}</h1>
<p>{t("home.benefits.events.description.1")}</p> <p>{t("home.benefits.events.description.1")}</p>
<p>{t("home.benefits.events.description.2")}</p> <p>{t("home.benefits.events.description.2")}</p>
</div> </div>
</div> </div>
<a class="btn px-8 flex" href={l("/about")}>{t("home.benefits.read")}<CaretRight width="24" heigth="24" /></a> <a class="btn px-8 flex" href={l("/about")}>{t("home.benefits.read")}
<CaretRight width="24" heigth="24"/>
</a>
</section> </section>
<section class="w-full py-12"> <section class="w-full py-12">
{mapMap(groupedTeamMember, (key, value) => ( {mapMap(groupedTeamMember, (key, value) => (
@ -136,7 +146,9 @@ function mapMap<T, K, J>(i: Map<T, K>, fn: (key: T, value: K) => J): J[] {
dark:bg-neutral-900 dark:border-gray-800 dark:text-white"> dark:bg-neutral-900 dark:border-gray-800 dark:text-white">
<figure class="flex flex-col items-center"> <figure class="flex flex-col items-center">
<figcaption class="text-center mb-4 text-2xl">{v.name}</figcaption> <figcaption class="text-center mb-4 text-2xl">{v.name}</figcaption>
<Image src={`https://visage.surgeplay.com/bust/150/${v.uuid}`} class="transition duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl" alt={v.name + "s bust"} width="150" height="150" /> <Image src={`https://visage.surgeplay.com/bust/150/${v.uuid}`}
class="transition duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl"
alt={v.name + "s bust"} width="150" height="150"/>
</figure> </figure>
</div> </div>
))} ))}
@ -147,8 +159,8 @@ function mapMap<T, K, J>(i: Map<T, K>, fn: (key: T, value: K) => J): J[] {
</NavbarLayout> </NavbarLayout>
<style> <style>
text-carousel{ text-carousel {
>* { > * {
@apply absolute top-0 left-0 w-full text-xl sm:text-4xl italic text-white text-center opacity-0; @apply absolute top-0 left-0 w-full text-xl sm:text-4xl italic text-white text-center opacity-0;
transition: transform .5s ease-out, opacity .5s linear; transition: transform .5s ease-out, opacity .5s linear;
text-shadow: 2px 2px 5px black; text-shadow: 2px 2px 5px black;
@ -163,13 +175,16 @@ function mapMap<T, K, J>(i: Map<T, K>, fn: (key: T, value: K) => J): J[] {
@apply w-72 border-2 bg-zinc-50 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg transition-transform duration-300 ease-in-out @apply w-72 border-2 bg-zinc-50 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg transition-transform duration-300 ease-in-out
dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100 dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100
hover:scale-105; hover:scale-105;
>h1 {
> h1 {
@apply text-xl font-bold mt-4; @apply text-xl font-bold mt-4;
} }
>p {
> p {
@apply mt-4; @apply mt-4;
} }
>svg {
> svg {
@apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl @apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl
} }
} }

Datei anzeigen

@ -9,15 +9,17 @@ import localBau from "@images/2023-10-08_20.43.43.png";
<NavbarLayout title={t("login.page")}> <NavbarLayout title={t("login.page")}>
<script> <script>
import {l} from "../util/util"; import {l} from "../util/util";
if (window.location.href.endsWith("/login") || window.location.href.endsWith("/login/")) { if (window.location.href.endsWith("/login") || window.location.href.endsWith("/login/")) {
if ((localStorage.getItem("sw-session") ?? "") !== "") { if ((localStorage.getItem("sw-session") ?? "") !== "") {
window.location.href = l("/dashboard"); window.location.href = l("/dashboard");
} }
} }
</script> </script>
<Image src={localBau} alt="Bau" width="1920" height="1080" class="w-screen h-screen dark:brightness-75 fixed -z-10 object-cover" draggable="false" /> <Image src={localBau} alt="Bau" width="1920" height="1080"
class="w-screen h-screen dark:brightness-75 fixed -z-10 object-cover" draggable="false"/>
<div class="h-screen mx-auto p-8 rounded-b-md pt-40 sm:pt-28 md:pt-14 flex flex-col justify-center items-center <div class="h-screen mx-auto p-8 rounded-b-md pt-40 sm:pt-28 md:pt-14 flex flex-col justify-center items-center
dark:text-white " style="width: min(100vw, 75em);"> dark:text-white " style="width: min(100vw, 75em);">
<Login client:load /> <Login client:load/>
</div> </div>
</NavbarLayout> </NavbarLayout>

Datei anzeigen

@ -9,22 +9,22 @@ export const getStaticPaths = createGetStaticPaths(async () => {
return modes.map(value => ({ return modes.map(value => ({
props: { props: {
mode: value mode: value,
}, },
params: { params: {
gamemode: value.id gamemode: value.id,
} },
})); }));
}); });
interface Props { interface Props {
mode: CollectionEntry<"modes"> mode: CollectionEntry<"modes">;
} }
const { mode } = Astro.props; const {mode} = Astro.props;
--- ---
<PageLayout title={t("elo.title", {mode: t(`${mode.data.translationKey}.title`)})}> <PageLayout title={t("elo.title", {mode: t(`${mode.data.translationKey}.title`)})}>
<h1 class="text-2xl mb-2">{t("ranking.heading", { mode: t(`${mode.data.translationKey}.title`) })}</h1> <h1 class="text-2xl mb-2">{t("ranking.heading", {mode: t(`${mode.data.translationKey}.title`)})}</h1>
<EloTable gamemode={mode.id} client:only="svelte" /> <EloTable gamemode={mode.id} client:only="svelte"/>
</PageLayout> </PageLayout>

Datei anzeigen

@ -18,7 +18,7 @@ export const getStaticPaths = createGetStaticPaths(async () => {
}); });
return posts.map((page) => ({ return posts.map((page) => ({
props: { page, german: page.id.split("/")[0] != astroI18n.locale }, params: { mode: page.slug.split("/")[1] } props: {page, german: page.id.split("/")[0] != astroI18n.locale}, params: {mode: page.slug.split("/")[1]},
})); }));
}); });
@ -27,16 +27,16 @@ interface Props {
german: boolean german: boolean
} }
const { page, german } = Astro.props; const {page, german} = Astro.props;
const { Content } = await page.render(); const {Content} = await page.render();
--- ---
<PageLayout title={t("title", {mode: t(`${page.data.translationKey}.title`)}, {route: "/rules"})}> <PageLayout title={t("title", {mode: t(`${page.data.translationKey}.title`)}, {route: "/rules"})}>
<article> <article>
{german && ( {german && (
<LanguageWarning /> <LanguageWarning/>
)} )}
<Content /> <Content/>
</article> </article>
<script> <script>
@ -57,7 +57,7 @@ const { Content } = await page.render();
<style is:global> <style is:global>
article { article {
>* { > * {
all: revert; all: revert;
} }

Datei anzeigen

@ -14,7 +14,7 @@ const imageMap = {
"mwg": mwg, "mwg": mwg,
"as": as, "as": as,
"ws": ws, "ws": ws,
"qg": mwg "qg": mwg,
}; };
const modes = await getCollection("modes", entry => entry.data.main); const modes = await getCollection("modes", entry => entry.data.main);
@ -24,14 +24,19 @@ const modes = await getCollection("modes", entry => entry.data.main);
{modes.map(value => ( {modes.map(value => (
<div class="dark:bg-neutral-800 rounded-md p-4 border border-neutral-400 shadow-md my-4 flex flex-col <div class="dark:bg-neutral-800 rounded-md p-4 border border-neutral-400 shadow-md my-4 flex flex-col
md:flex-row"> md:flex-row">
<Image height="300" width="300" src={imageMap[value.data.translationKey]} alt={t(value.data.translationKey + ".title")} class="dark:invert"></Image> <Image height="300" width="300" src={imageMap[value.data.translationKey]}
alt={t(value.data.translationKey + ".title")} class="dark:invert"></Image>
<div class="ml-4"> <div class="ml-4">
<h1 class="text-2xl font-bold">{t(value.data.translationKey + ".title")}</h1> <h1 class="text-2xl font-bold">{t(value.data.translationKey + ".title")}</h1>
<div>{t(value.data.translationKey + ".description")}</div> <div>{t(value.data.translationKey + ".description")}</div>
<div class="mt-2 flex flex-col"> <div class="mt-2 flex flex-col">
<a href={l(`/rules/${value.id}`)} class="text-yellow-300 hover:underline w-fit">{t("rules")}</a> <a href={l(`/rules/${value.id}`)} class="text-yellow-300 hover:underline w-fit">{t("rules")}</a>
<a href={l(`/announcements/tags/${value.id}`)} class="text-yellow-300 hover:underline w-fit">{t("announcements")}</a> <a href={l(`/announcements/tags/${value.id}`)}
{value.data.ranked ? <a href={l(`/ranked/${value.id}`)} class="text-yellow-300 hover:underline w-fit">{t("ranking")}</a> : null} class="text-yellow-300 hover:underline w-fit">{t("announcements")}</a>
{value.data.ranked
? <a href={l(`/ranked/${value.id}`)}
class="text-yellow-300 hover:underline w-fit">{t("ranking")}</a>
: null}
</div> </div>
</div> </div>
</div>))} </div>))}

Datei anzeigen

@ -1,10 +1,9 @@
--- ---
import PageLayout from "../../layouts/PageLayout.astro"; import PageLayout from "../../layouts/PageLayout.astro";
import FightStatistics from "../../components/FightStatistics.svelte"; import FightStatistics from "../../components/FightStatistics.svelte";
import {t} from "astro-i18n"; import {t} from "astro-i18n";
--- ---
<PageLayout title={t("stats.title")}> <PageLayout title={t("stats.title")}>
<FightStatistics client:only="svelte" /> <FightStatistics client:only="svelte"/>
</PageLayout> </PageLayout>

Datei anzeigen

@ -20,7 +20,7 @@
.btn { .btn {
@apply bg-yellow-400 font-bold py-2 px-4 rounded cursor-pointer select-none mx-2 text-black flex flex-row; @apply bg-yellow-400 font-bold py-2 px-4 rounded cursor-pointer select-none mx-2 text-black flex flex-row;
@apply hover:bg-yellow-300 hover:text-black hover:shadow-2xl hover:scale-105; @apply hover:bg-yellow-300 hover:text-black hover:shadow-2xl hover:scale-105;
transition: all 0.5s cubic-bezier(.2,3,.67,.6), transition: all 0.5s cubic-bezier(.2, 3, .67, .6),
background-color .1s ease-in-out, background-color .1s ease-in-out,
outline-width .1s ease-in-out, outline-width .1s ease-in-out,
outline-color .1s ease-in-out; outline-color .1s ease-in-out;
@ -34,16 +34,16 @@
.btn-dropdown { .btn-dropdown {
@apply relative mx-2; @apply relative mx-2;
>:nth-child(1) { > :nth-child(1) {
@apply !mx-0; @apply !mx-0;
} }
>:nth-child(2) { > :nth-child(2) {
@apply hidden absolute top-full left-1/2 -translate-x-1/2 bg-gray-800 list-none text-white rounded py-2 flex-col text-sm z-20; @apply hidden absolute top-full left-1/2 -translate-x-1/2 bg-gray-800 list-none text-white rounded py-2 flex-col text-sm z-20;
} }
&:hover,&:focus-within { &:hover, &:focus-within {
>:nth-child(2) { > :nth-child(2) {
@apply flex; @apply flex;
} }
} }

Datei anzeigen

@ -17,14 +17,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { l as proxyL } from "astro-i18n"; import {l as proxyL} from "astro-i18n";
const locales = ["de"]; const locales = ["de"];
export const l = (route: string) => { export const l = (route: string) => {
const transPath = proxyL(route); const transPath = proxyL(route);
if(import.meta.env.DEV) { if (import.meta.env.DEV) {
return transPath; return transPath;
} }