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": [
"error",
"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("/")}`)}>
<div class="p-4 flex flex-row">
{post.data.image != null ? (
{post.data.image != null
? (
<div class="flex-shrink-0 pr-2">
<Image src={post.data.image} alt="Post Image" class="rounded-2xl shadow-2xl object-cover h-32 w-32 max-w-none transition-transform hover:scale-105" />
</div>
) : null}
)
: null}
<div>
<h2 class="text-2xl font-bold">{post.data.title}</h2>
<P class="text-gray-500">{Intl.DateTimeFormat(astroI18n.locale, {
day: "numeric",
month: "long",
year: "numeric"
year: "numeric",
}).format(post.data.created)}</P>
<P>{post.data.description}</P>
<div class="mt-1">

Datei anzeigen

@ -25,24 +25,39 @@
import {tokenStore} from "@repo/repo";
const routes: RouteDefinition = {
'/': wrap({asyncComponent: () => import('./pages/Home.svelte'), conditions: detail => get(tokenStore) != ""}),
'/perms': wrap({asyncComponent: () => import('./pages/Perms.svelte'), conditions: detail => get(tokenStore) != ""}),
'/login': wrap({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')})
}
"/": wrap({asyncComponent: () => import("./pages/Home.svelte"), conditions: detail => get(tokenStore) != ""}),
"/perms": wrap({
asyncComponent: () => import("./pages/Perms.svelte"),
conditions: detail => get(tokenStore) != ""
}),
"/login": wrap({
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) {
if(event.detail.location === "/login") {
replace("/")
if (event.detail.location === "/login") {
replace("/");
} else {
replace("/login")
replace("/login");
}
}
</script>
<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>

Datei anzeigen

@ -37,48 +37,48 @@
return {
name: player.name,
value: player.id.toString()
}
};
}).sort((a, b) => a.name.localeCompare(b.name));
$: selectableTeams = teams.map(team => {
return {
name: team.name,
value: team.id.toString()
}
};
}).sort((a, b) => a.name.localeCompare(b.name));
$: selectableGamemodes = $gamemodes.map(gamemode => {
return {
name: gamemode,
value: gamemode
}
};
}).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 = [
...selectableGamemodes, {
name: gamemode + ' (custom)',
name: gamemode + " (custom)",
value: gamemode
}
]
];
$: mapsStore = maps(gamemode);
$: selectableMaps = $mapsStore.map(map => {
return {
name: map,
value: map
}
};
}).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 = [
...selectableMaps, {
name: map + ' (custom)',
name: map + " (custom)",
value: map
}
]
];
$: selectableGroups = [{
name: 'None',
value: ''
name: "None",
value: ""
}, {
value: groupSearch,
name: `Create: '${groupSearch}'`
@ -86,7 +86,7 @@
return {
name: group,
value: group
}
};
}).sort((a, b) => a.name.localeCompare(b.name))];
</script>
@ -106,11 +106,13 @@
</div>
<div class="m-2">
<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 class="m-2">
<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 class="m-2">
<Label for="fight-kampf">Kampfleiter</Label>
@ -118,5 +120,6 @@
</div>
<div class="m-2">
<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>

Datei anzeigen

@ -18,32 +18,35 @@
-->
<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 items: {name: string, value: string}[] = []
export let selected: string | null = null;
export let items: { name: string, value: string }[] = [];
export let maxItems = 5;
export let leftText: boolean = false;
export let searchValue = items.find(item => item.value === selected)?.name || ''
let open = false
export let searchValue = items.find(item => item.value === selected)?.name || "";
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}) {
selected = item.value
searchValue = ""
open = false
function selectItem(item: { name: string, value: string }) {
selected = item.value;
searchValue = "";
open = false;
}
</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">
<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}/>
</div>
{#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}
</button>
{/each}

Datei anzeigen

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

Datei anzeigen

@ -26,18 +26,18 @@
import {eventRepo} from "@repo/event.ts";
import {tokenStore} from "@repo/repo.ts";
let events = $eventRepo.listEvents()
let showAdd = false
let millis = Date.now()
let events = $eventRepo.listEvents();
let showAdd = false;
let millis = Date.now();
</script>
<Navbar let:hidden let:toggle class="shadow-lg border-b">
<NavBrand href="#">
<span class="self-center whitespace-nowrap text-xl font-semibold dark:text-white">
Admin-Tool
Mod-Tool
</span>
</NavBrand>
<NavHamburger on:click={toggle} />
<NavHamburger on:click={toggle}/>
<NavUl {hidden}>
<NavLi href="#/edit">Edit Pages</NavLi>
<NavLi href="#/perms">Permissions</NavLi>
@ -58,13 +58,13 @@
<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))">
{#each data.filter((e) => e.start > millis) as event}
<EventCard {event} />
<EventCard {event}/>
{/each}
</div>
<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))">
{#each data.filter((e) => e.start < millis).reverse() as event}
<EventCard {event} />
<EventCard {event}/>
{/each}
</div>
{:catch error}

Datei anzeigen

@ -31,9 +31,9 @@
async function handleSubmit() {
loading = true;
let res = await fetchWithToken(value, "/data")
let res = await fetchWithToken(value, "/data");
loading = false;
if(res.ok) {
if (res.ok) {
$tokenStore = value;
await replace("/");
} else {
@ -41,7 +41,7 @@
value = "";
setTimeout(() => {
error = false;
}, 5000)
}, 5000);
}
}
</script>
@ -51,12 +51,13 @@
<div class="grid gap-6 mb-6 md:grid-cols-1">
<div>
<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">
{#if show}
<EyeOutline />
<EyeOutline/>
{:else}
<EyeSlashOutline />
<EyeSlashOutline/>
{/if}
</button>
</Input>
@ -64,7 +65,8 @@
</div>
<Button type="submit">
{#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}
<span>Submit</span>
{/if}
@ -74,7 +76,12 @@
<Toast color="red" position="bottom-left" bind:open={error} transition={fly} params="{{x: -200}}">
<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>
</svelte:fragment>
Invalid Token.

Datei anzeigen

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

Datei anzeigen

@ -44,7 +44,7 @@
activePerms = value.perms;
prefixEdit = value.prefix.name;
return value;
})
});
}
function togglePerm(perm: string) {
@ -54,7 +54,7 @@
} else {
activePerms = [...activePerms, perm];
}
}
};
}
function save() {
@ -76,7 +76,7 @@
}
playerPerms = loadPlayer(selectedPlayer);
})
});
}
let permsFuture = $permsRepo.listPerms();
@ -102,7 +102,9 @@
{#if filteredPlayers.length < 100}
<ul class="flex-1 overflow-scroll">
{#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}
</li>
{/each}
@ -120,14 +122,18 @@
{:then player}
<h1>Prefix</h1>
{#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}
<h1>Permissions</h1>
{#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}
<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>
{:catch error}
<p>{error.toString()}</p>

Datei anzeigen

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

Datei anzeigen

@ -20,7 +20,7 @@
<script lang="ts">
import {onDestroy, onMount} from "svelte";
import EasyMDE from "easymde";
import "easymde/dist/easymde.min.css"
import "easymde/dist/easymde.min.css";
export let value: string;
export let dirty: boolean = false;
@ -33,17 +33,17 @@
element: editor,
initialValue: value,
spellChecker: false
})
});
mde.codemirror.on("change", () => {
value = mde.value();
dirty = true;
})
})
});
});
onDestroy(() => {
mde.toTextArea();
mde.cleanup();
})
});
</script>
<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 {schemTypes} from "@stores/stores.ts";
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 ErrorModal from "../../components/ErrorModal.svelte";
import {replace} from "svelte-spa-router";
@ -55,7 +55,7 @@
return {
value: type.db,
name: type.name
}
};
})];
$: changed = name !== event.name ||
@ -70,7 +70,7 @@
async function del() {
try {
await $eventRepo.deleteEvent(event.id.toString());
await replace("/")
await replace("/");
} catch (e) {
error = e;
errorOpen = true;
@ -86,7 +86,7 @@
maxTeamMembers: member,
name: name,
publicSchemsOnly: publicOnly,
schemType: schemType ?? 'null',
schemType: schemType ?? "null",
spectateSystem: spectateSystem,
start: startDate
};

Datei anzeigen

@ -40,14 +40,14 @@
function dispatchSelect() {
setTimeout(() => {
if (!deleteOpen && !editOpen) {
dispatcher('select');
dispatcher("select");
}
}, 1);
}
async function deleteFight() {
await $fightRepo.deleteFight(fight.id);
dispatcher('update');
dispatcher("update");
}
</script>
@ -93,14 +93,15 @@
<EditOutline/>
</ToolbarButton>
<ToolbarButton color="red" on:click={() => deleteOpen = true}>
<TrashBinOutline />
<TrashBinOutline/>
</ToolbarButton>
</Toolbar>
{/if}
</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">
<p class="mb-5">
Are you sure you want to delete this fight?

Datei anzeigen

@ -40,8 +40,9 @@
import TypeAheadSearch from "../../components/TypeAheadSearch.svelte";
import {fightRepo, type UpdateFight} from "@repo/fight.ts";
import dayjs from "dayjs";
import duration from "dayjs/plugin/duration"
dayjs.extend(duration)
import duration from "dayjs/plugin/duration";
dayjs.extend(duration);
export let data: ExtendedEvent;
@ -54,13 +55,13 @@
return {
group: group,
fights: fights.filter(fight => fight.group === group)
}
};
});
function cycleSelect() {
if (selectedFights.size === fights.length) {
selectedFights = new Set();
} else if(selectedFights.size === 0){
} else if (selectedFights.size === 0) {
selectedFights = new Set(fights.filter(fight => fight.start > Date.now()));
if (selectedFights.size === 0) {
@ -72,7 +73,7 @@
}
function cycleGroup(groupFights: EventFight[]) {
if(groupFights.every(gf => selectedFights.has(gf))) {
if (groupFights.every(gf => selectedFights.has(gf))) {
groupFights.forEach(fight => selectedFights.delete(fight));
} else {
groupFights.forEach(fight => selectedFights.add(fight));
@ -81,6 +82,7 @@
}
let deleteOpen = false;
async function deleteFights() {
for (const fight of selectedFights) {
await $fightRepo.deleteFight(fight.id);
@ -95,9 +97,10 @@
return {
name: player.name,
value: player.id.toString()
}
};
}).sort((a, b) => a.name.localeCompare(b.name));
let kampfleiter = "";
async function updateKampfleiter() {
for (const fight of selectedFights) {
let f: UpdateFight = {
@ -122,8 +125,8 @@
let groupSearch = "";
$: selectableGroups = [{
name: 'None',
value: ''
name: "None",
value: ""
}, {
value: groupSearch,
name: `Create: '${groupSearch}'`
@ -131,8 +134,9 @@
return {
name: group,
value: group
}
};
}).sort((a, b) => a.name.localeCompare(b.name))];
async function updateGroup() {
for (const fight of selectedFights) {
let f: UpdateFight = {
@ -157,7 +161,7 @@
let changeTimeOpen = false;
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() {
for (const fight of selectedFights) {
@ -168,7 +172,7 @@
map: null,
redTeam: 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);
}
@ -194,7 +198,8 @@
<CalendarWeekOutline/>
</ToolbarButton>
<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/>
</ToolbarButton>
<Tooltip>Change Kampfleiter</Tooltip>
@ -204,7 +209,8 @@
<Tooltip>Change Group</Tooltip>
</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/>
</ToolbarButton>
<Tooltip>Delete</Tooltip>
@ -212,7 +218,8 @@
</Toolbar>
{#each groupedFights as group}
<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>
</div>
{#each group.fights.sort((a, b) => a.start - b.start) as fight, i (fight.id)}
@ -231,7 +238,8 @@
{/each}
</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">
<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">
<div class="m-2">
<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>
<svelte:fragment slot="footer">
<Button class="ml-auto" on:click={updateGroup}>Change</Button>
@ -270,7 +279,8 @@
<input type="datetime-local" {...props} bind:value={changedTime}/>
</Input>
</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">
<Button class="ml-auto" on:click={updateStartTime}>Update</Button>
<Button on:click={() => changeTimeOpen = false} color="alternative">Cancel</Button>

Datei anzeigen

@ -29,7 +29,8 @@
<Avatar size="lg">{team.kuerzel}</Avatar>
<div class="m-2">
<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>
{/each}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -32,11 +32,12 @@
function handleDrop(ev: DragEvent) {
ev.preventDefault();
dragover = false;
dispatch('drop', ev)
dispatch("drop", ev);
}
</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>
</div>

Datei anzeigen

@ -42,14 +42,14 @@
}
function teamDragStart(ev: DragEvent, team: Team) {
ev.dataTransfer!.setData("team", team.id.toString())
ev.dataTransfer!.setData("team", team.id.toString());
}
let resetDragOver = false;
function resetDragOverEvent(ev: DragEvent) {
resetDragOver = true;
ev.preventDefault()
ev.preventDefault();
}
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);
}
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);
let gamemode = ''
let map = ''
let gamemode = "";
let map = "";
$: selectableGamemodes = $gamemodes.map(gamemode => {
return {
name: gamemode,
value: gamemode
}
};
}).sort((a, b) => a.name.localeCompare(b.name));
$: mapsStore = maps(gamemode);
@ -82,7 +82,7 @@
return {
name: map,
value: map
}
};
}).sort((a, b) => a.name.localeCompare(b.name));
let roundTime = 30;
@ -95,10 +95,10 @@
let teams = data.teams.map(team => team.id).sort(() => Math.random() - 0.5);
groups = [];
for (let i = 0; i < groupCount; i++) {
groups.push([])
groups.push([]);
}
while (teams.length > 0) {
groups[teams.length % groupCount].push(teams.pop() as number)
groups[teams.length % groupCount].push(teams.pop() as number);
}
showAutoGrouping = false;
groups = groups.filter(group => group.length > 0);
@ -111,8 +111,8 @@
let groupFight = [];
for (let i = 0; i < round; i++) {
let availableTeams = [...group];
if(group.length % 2 === 1) {
availableTeams = availableTeams.filter((team, index) => index !== i)
if (group.length % 2 === 1) {
availableTeams = availableTeams.filter((team, index) => index !== i);
}
let roundFights = [];
while (availableTeams.length > 0) {
@ -121,25 +121,25 @@
availableTeams = availableTeams.filter(team => team !== team2);
let fight = [team1, team2];
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;
}
$: 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() {
groupsFights.forEach((group, i) => {
group.forEach((round, j) => {
round.forEach(async (fight, k) => {
const blueTeam = teams.get(fight[0])!
const redTeam = teams.get(fight[1])!
const blueTeam = teams.get(fight[0])!;
const redTeam = teams.get(fight[1])!;
await $fightRepo.createFight(data.event.id, {
blueTeam: blueTeam.id,
@ -149,17 +149,19 @@
map: map,
spielmodus: gamemode,
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>
<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)}
<TeamChip {team} on:dragstart={ev => teamDragStart(ev, team)}/>
{/each}
@ -247,6 +249,7 @@
top: 0;
color: gray;
}
#reseter {
min-width: 14rem;
}

Datei anzeigen

@ -27,7 +27,8 @@
</script>
<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:mouseenter={() => hover = true}
on:mouseleave={() => hover = false}

Datei anzeigen

@ -32,11 +32,11 @@
let eventName = "";
let start = "";
$: startDate = dayjs(start)
$: startDate = dayjs(start);
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() {
try {
@ -44,7 +44,7 @@
name: eventName,
start: startDate,
end: endDate
})
});
dispatch("create");
open = false;
} catch (e: any) {

Datei anzeigen

@ -19,7 +19,7 @@
<script lang="ts">
import {Card} from "flowbite-svelte";
import {link} from 'svelte-spa-router'
import {link} from "svelte-spa-router";
import type {ShortEvent} from "@type/event.ts";
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 nameRegex = new RegExp("(?!.*\/).+(?=\\.(md|json))");
export const nameRegex = new RegExp("(?!.*/).+(?=\\.(md|json))");
export function mapToMap(pages: PageList): Map<string, ListPage[]> {
const map = new Map();
@ -82,9 +82,9 @@ export function brightness(color: string) {
return Color(color).isLight();
}
export function base64ToBytes(base64: string) {
export function base64ToBytes(base64: string): Uint8Array {
const binString = atob(base64);
// @ts-ignore
// @ts-expect-error Some Function Missing
return Uint8Array.from(binString, (m) => m.codePointAt(0));
}

Datei anzeigen

@ -28,15 +28,15 @@ export class AuthRepo {
return await fetchWithToken(this.token, "/auth/login", {
body: JSON.stringify({
username,
password
password,
}),
method: "POST",
}).then(value => value.json()).then(value => value.token)
}).then(value => value.json()).then(value => value.token);
}
public async logout(): Promise<void> {
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";
export class DataRepo {
constructor(private token: string) {}
constructor(private token: string) {
}
public async getServer(): Promise<Server> {
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";
export interface CreateEvent {
name: string
start: Dayjs
end: Dayjs
name: string;
start: Dayjs;
end: Dayjs;
}
export interface UpdateEvent {
name: string
start: Dayjs
end: Dayjs
deadline: Dayjs
maxTeamMembers: number
schemType: string | null
publicSchemsOnly: boolean
spectateSystem: boolean
name: string;
start: Dayjs;
end: Dayjs;
deadline: Dayjs;
maxTeamMembers: number;
schemType: string | null;
publicSchemsOnly: boolean;
spectateSystem: boolean;
}
export class EventRepo {
constructor(private token: string) {}
constructor(private token: string) {
}
public async listEvents(): Promise<ShortEvent[]> {
return await fetchWithToken(this.token, "/events")
@ -62,7 +63,7 @@ export class EventRepo {
body: JSON.stringify({
name: event.name,
start: +event.start,
end: +event.end
end: +event.end,
}),
}).then(value => value.json())
.then(SWEventSchema.parse);
@ -79,18 +80,18 @@ export class EventRepo {
maxTeamMembers: event.maxTeamMembers,
schemType: event.schemType,
publicSchemsOnly: event.publicSchemsOnly,
spectateSystem: event.spectateSystem
spectateSystem: event.spectateSystem,
}),
headers: {
"Content-Type": "application/json"
}
"Content-Type": "application/json",
},
}).then(value => value.json())
.then(SWEventSchema.parse);
}
public async deleteEvent(id: string): Promise<boolean> {
const res = await fetchWithToken(this.token, `/events/${id}`, {
method: "DELETE"
method: "DELETE",
});
return res.ok;

Datei anzeigen

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

Datei anzeigen

@ -25,7 +25,8 @@ import {z} from "zod";
import {derived} from "svelte/store";
export class PageRepo {
constructor(private token: string) {}
constructor(private token: string) {
}
public async listPages(branch: string = "master"): Promise<PageList> {
return await fetchWithToken(this.token, `/page?branch=${branch}`)
@ -45,8 +46,8 @@ export class PageRepo {
method: "PUT",
body: JSON.stringify({
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> {
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> {
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";
export class PermsRepo {
constructor(private token: string) {}
constructor(private token: string) {
}
public async listPerms(): Promise<Perms> {
const res = await fetchWithToken(this.token, "/perms");

Datei anzeigen

@ -20,7 +20,12 @@
import {writable} from "svelte/store";
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 => {
if (value.status === 401) {
tokenStore.set("");

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -39,7 +39,7 @@ export const UserStatsSchema = z.object({
eventParticipation: z.number(),
acceptedSchematics: z.number(),
fights: z.number(),
playtime: z.number()
playtime: z.number(),
});
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/>.
*/
// @ts-ignore
import {defineCollection, reference, z} from "astro:content";
export const pagesSchema = z.object({
title: z.string().min(1).max(80),
description: z.string().min(1).max(120),
german: z.boolean().optional().default(false),
image: z.string().optional()
image: z.string().optional(),
});
export const pages = defineCollection({
type: "content",
schema: pagesSchema
schema: pagesSchema,
});
export const help = defineCollection({
@ -38,8 +37,8 @@ export const help = defineCollection({
title: z.string().min(1).max(80),
description: z.string().min(1).max(120),
tags: z.array(z.string()),
related: z.array(reference("help")).optional()
})
related: z.array(reference("help")).optional(),
}),
});
export const modes = defineCollection({
@ -47,8 +46,8 @@ export const modes = defineCollection({
schema: z.object({
translationKey: z.string(),
main: z.boolean(),
ranked: z.boolean().optional().default(false)
})
ranked: z.boolean().optional().default(false),
}),
});
export const downloads = defineCollection({
@ -59,14 +58,14 @@ export const downloads = defineCollection({
url: z.string().url()
.or(z.record(z.string(), z.string())),
sourceUrl: z.string().url().optional(),
})
}),
});
export const rules = defineCollection({
type: "content",
schema: z.object({
translationKey: z.string(),
})
}),
});
export const announcements = defineCollection({
@ -77,8 +76,8 @@ export const announcements = defineCollection({
image: image().optional(),
tags: z.array(z.string()),
created: z.date(),
key: z.string()
})
key: z.string(),
}),
});
export const collections = {
@ -87,5 +86,5 @@ export const collections = {
"modes": modes,
"rules": rules,
"downloads": downloads,
"announcements": announcements
"announcements": announcements,
};

Datei anzeigen

@ -2,8 +2,8 @@
import icon from "../images/logo.png";
import {getImage} from "astro:assets";
import {astroI18n} from "astro-i18n";
const { title, description } = Astro.props.frontmatter || Astro.props;
import { SEO } from "astro-seo";
const {title, description} = Astro.props.frontmatter || Astro.props;
import {SEO} from "astro-seo";
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">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<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
title={title}
description={description}
twitter={{
creator: "@chaoscaot"
creator: "@chaoscaot",
}}
languageAlternates={astroI18n.locales.map((locale) => ({
hrefLang: locale,
href: `/${locale}/`
href: `/${locale}/`,
}))}
/>
<slot name="head" />
<slot name="head"/>
</head>
<body class="dark:bg-zinc-800">
<slot />
<slot/>
</body>
</html>

Datei anzeigen

@ -1,5 +1,5 @@
---
import { Image } from "astro:assets";
import {Image} from "astro:assets";
import Basic from "./Basic.astro";
import "../styles/button.css";
import localLogo from "../images/logo.png";
@ -9,18 +9,19 @@ import {l} from "../util/util";
import ServerStatus from "../components/ServerStatus.svelte";
const { title, description } = Astro.props;
const {title, description} = Astro.props;
---
<Basic title={title} description={description}>
<slot name="head" slot="head" />
<slot name="head" slot="head"/>
<Fragment>
<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 \
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">
<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">
{t("navbar.title")}
</h1>
@ -31,10 +32,11 @@ const { title, description } = Astro.props;
<a href={l("/")}>
<span class="btn__text">{t("navbar.links.home.title")}</span>
</a>
<CaretDownOutline class="ml-2 mt-auto" />
<CaretDownOutline class="ml-2 mt-auto"/>
</button>
<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("/downloads")}>{t("navbar.links.home.downloads")}</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")}>
<span class="btn__text">{t("navbar.links.rules.title")}</span>
</a>
<CaretDownOutline class="ml-2 mt-auto" />
<CaretDownOutline class="ml-2 mt-auto"/>
</button>
<div>
<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/quickgear")} class="btn btn-gray">{t("navbar.links.rules.qg")}</a>
<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/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>
<a href={l("/rules/megawargear")}
class="btn btn-gray">{t("navbar.links.rules.megawg")}</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>
<a class="btn btn-gray" href={l("/code-of-conduct")}>{t("navbar.links.rules.coc")}</a>
</div>
@ -83,7 +87,7 @@ const { title, description } = Astro.props;
</div>
</nav-bar>
<script>
class Navbar extends HTMLElement {
class Navbar extends HTMLElement {
constructor() {
super();
if (window.scrollY != 0) {
@ -100,18 +104,18 @@ class Navbar extends HTMLElement {
}
};
}
}
}
customElements.define("nav-bar", Navbar);
customElements.define("nav-bar", Navbar);
</script>
<main class="flex-1">
<slot />
<slot/>
</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">
<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">
<h1>Serverstatus</h1>
<ServerStatus client:only="svelte" />
<ServerStatus client:only="svelte"/>
</div>
<div class="footer-card">
<h1>Links</h1>
@ -126,8 +130,12 @@ customElements.define("nav-bar", Navbar);
</div>
<div class="footer-card">
<h1>Social Media</h1>
<a class="flex" href="/youtube"><YoutubeSolid class="mr-2" /> YouTube</a>
<a class="flex" href="/discord"><DiscordSolid class="mr-2" /> Discord</a>
<a class="flex" href="/youtube">
<YoutubeSolid class="mr-2"/>
YouTube</a>
<a class="flex" href="/discord">
<DiscordSolid class="mr-2"/>
Discord</a>
</div>
</div>
<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>
.footer-card {
@apply w-40 text-gray-400 flex flex-col;
>h1 {
> h1 {
@apply text-xl font-bold text-gray-100;
}
>a {
> a {
@apply hover:underline;
}
}

Datei anzeigen

@ -1,16 +1,17 @@
---
import NavbarLayout from "./NavbarLayout.astro";
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}>
<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" />
<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"/>
<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);">
<slot />
<slot/>
</div>
</NavbarLayout>

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -10,18 +10,21 @@ const downloads = await getCollection("downloads");
<PageLayout title="Downloads">
{downloads.map(e => (
<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="flex flex-col">
{typeof e.data.url === "object" ?
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>
}
{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>
))}

Datei anzeigen

@ -1,5 +1,5 @@
---
import { getCollection } from "astro:content";
import {getCollection} from "astro:content";
import NavbarLayout from "../../layouts/NavbarLayout.astro";
import {astroI18n, createGetStaticPaths} from "astro-i18n";
@ -7,24 +7,24 @@ export const getStaticPaths = createGetStaticPaths(async () => {
let posts = await getCollection("help");
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 { Content } = await page.render();
const {page} = Astro.props;
const {Content} = await page.render();
---
<NavbarLayout title={page.data.title}>
<article>
<h1 class="text-left">{page.data.title}</h1>
<Content />
<Content/>
</article>
</NavbarLayout>
<style is:global>
article {
>* {
> * {
all: revert;
}

Datei anzeigen

@ -1,7 +1,7 @@
---
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 {CaretRight, Archive, Rocket, Bell} from "@astropub/icons";
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;
}
---
<NavbarLayout title={t("home.page")} description="SteamWar.de Homepage">
<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">
<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;">
<span class="text-yellow-400">{t("home.title.first")}</span><span class="text-neutral-600">{t("home.title.second")}</span>
<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;">
<span class="text-yellow-400">{t("home.title.first")}</span><span
class="text-neutral-600">{t("home.title.second")}</span>
</h1>
<text-carousel class="h-20 w-full relative select-none">
<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>
</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>
class TextCarousel extends HTMLElement {
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 {
connectedCallback() {
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("-translate-y-16");
} 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">
<div class="py-10 flex flex-col lg:flex-row">
<div class="card">
<Archive heigth="64" width="64" />
<Archive heigth="64" width="64"/>
<h1>{t("home.benefits.historic.title")}</h1>
<p>{t("home.benefits.historic.description.1")}</p>
<p>{t("home.benefits.historic.description.2")}</p>
</div>
<div class="card">
<Rocket heigth="64" width="64" />
<Rocket heigth="64" width="64"/>
<h1>{t("home.benefits.server.title")}</h1>
<p>{t("home.benefits.server.description")}</p>
</div>
<div class="card">
<Bell heigth="64" width="64" />
<Bell heigth="64" width="64"/>
<h1>{t("home.benefits.events.title")}</h1>
<p>{t("home.benefits.events.description.1")}</p>
<p>{t("home.benefits.events.description.2")}</p>
</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 class="w-full py-12">
{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">
<figure class="flex flex-col items-center">
<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>
</div>
))}
@ -147,8 +159,8 @@ function mapMap<T, K, J>(i: Map<T, K>, fn: (key: T, value: K) => J): J[] {
</NavbarLayout>
<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;
transition: transform .5s ease-out, opacity .5s linear;
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
dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100
hover:scale-105;
>h1 {
> h1 {
@apply text-xl font-bold mt-4;
}
>p {
> p {
@apply mt-4;
}
>svg {
> svg {
@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")}>
<script>
import {l} from "../util/util";
if (window.location.href.endsWith("/login") || window.location.href.endsWith("/login/")) {
if ((localStorage.getItem("sw-session") ?? "") !== "") {
window.location.href = l("/dashboard");
}
}
</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
dark:text-white " style="width: min(100vw, 75em);">
<Login client:load />
<Login client:load/>
</div>
</NavbarLayout>

Datei anzeigen

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

Datei anzeigen

@ -18,7 +18,7 @@ export const getStaticPaths = createGetStaticPaths(async () => {
});
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
}
const { page, german } = Astro.props;
const { Content } = await page.render();
const {page, german} = Astro.props;
const {Content} = await page.render();
---
<PageLayout title={t("title", {mode: t(`${page.data.translationKey}.title`)}, {route: "/rules"})}>
<article>
{german && (
<LanguageWarning />
<LanguageWarning/>
)}
<Content />
<Content/>
</article>
<script>
@ -57,7 +57,7 @@ const { Content } = await page.render();
<style is:global>
article {
>* {
> * {
all: revert;
}

Datei anzeigen

@ -14,7 +14,7 @@ const imageMap = {
"mwg": mwg,
"as": as,
"ws": ws,
"qg": mwg
"qg": mwg,
};
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 => (
<div class="dark:bg-neutral-800 rounded-md p-4 border border-neutral-400 shadow-md my-4 flex flex-col
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">
<h1 class="text-2xl font-bold">{t(value.data.translationKey + ".title")}</h1>
<div>{t(value.data.translationKey + ".description")}</div>
<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(`/announcements/tags/${value.id}`)} 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}
<a href={l(`/announcements/tags/${value.id}`)}
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>))}

Datei anzeigen

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

Datei anzeigen

@ -20,7 +20,7 @@
.btn {
@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;
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,
outline-width .1s ease-in-out,
outline-color .1s ease-in-out;
@ -34,16 +34,16 @@
.btn-dropdown {
@apply relative mx-2;
>:nth-child(1) {
> :nth-child(1) {
@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;
}
&:hover,&:focus-within {
>:nth-child(2) {
&:hover, &:focus-within {
> :nth-child(2) {
@apply flex;
}
}

Datei anzeigen

@ -17,14 +17,14 @@
* 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"];
export const l = (route: string) => {
const transPath = proxyL(route);
if(import.meta.env.DEV) {
if (import.meta.env.DEV) {
return transPath;
}