Dieser Commit ist enthalten in:
Ursprung
4b27eb76fe
Commit
9fd8ddb9bd
2
.eslintignore
Normale Datei
2
.eslintignore
Normale Datei
@ -0,0 +1,2 @@
|
||||
src/pages/de
|
||||
src/env.d.ts
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -25,20 +25,35 @@
|
||||
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("/")
|
||||
replace("/");
|
||||
} else {
|
||||
replace("/login")
|
||||
replace("/login");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
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}
|
||||
|
@ -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?")
|
||||
let conf = con || confirm("Are you sure you want to delete this branch?");
|
||||
if (conf) {
|
||||
await $pageRepo.deleteBranch(selectedBranch)
|
||||
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="#">
|
||||
@ -121,11 +122,14 @@
|
||||
<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={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>
|
||||
|
@ -26,15 +26,15 @@
|
||||
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}/>
|
||||
|
@ -31,7 +31,7 @@
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
let res = await fetchWithToken(value, "/data")
|
||||
let res = await fetchWithToken(value, "/data");
|
||||
loading = false;
|
||||
if (res.ok) {
|
||||
$tokenStore = value;
|
||||
@ -41,7 +41,7 @@
|
||||
value = "";
|
||||
setTimeout(() => {
|
||||
error = false;
|
||||
}, 5000)
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -51,7 +51,8 @@
|
||||
<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/>
|
||||
@ -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.
|
||||
|
@ -22,7 +22,7 @@
|
||||
import {replace} from "svelte-spa-router";
|
||||
|
||||
onMount(() => {
|
||||
replace('/')
|
||||
replace("/");
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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>
|
||||
|
||||
@ -100,7 +100,8 @@
|
||||
</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?
|
||||
|
@ -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,7 +55,7 @@
|
||||
return {
|
||||
group: group,
|
||||
fights: fights.filter(fight => fight.group === group)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function cycleSelect() {
|
||||
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -60,13 +60,13 @@
|
||||
kampfleiter: parseInt(kampfleiter!),
|
||||
group,
|
||||
});
|
||||
reset()
|
||||
reset();
|
||||
|
||||
dispatch("create")
|
||||
dispatch("create");
|
||||
} catch (e) {
|
||||
error = e;
|
||||
errorOpen = true;
|
||||
reset()
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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);
|
||||
@ -112,7 +112,7 @@
|
||||
for (let i = 0; i < round; i++) {
|
||||
let availableTeams = [...group];
|
||||
if (group.length % 2 === 1) {
|
||||
availableTeams = availableTeams.filter((team, index) => index !== i)
|
||||
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;
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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("");
|
||||
|
@ -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,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -44,7 +44,7 @@ export function cached<T>(normal: T, init: () => Promise<T>): Cached<T> {
|
||||
}
|
||||
return store.subscribe(run, invalidate);
|
||||
},
|
||||
reload
|
||||
reload,
|
||||
};
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ export function cachedFamily<T, K>(normal: K, init: (arg0: T) => Promise<K>): (a
|
||||
}
|
||||
return store.subscribe(run, invalidate);
|
||||
},
|
||||
reload
|
||||
reload,
|
||||
} as Cached<K>;
|
||||
|
||||
stores.set(arg, cachedStore);
|
||||
|
@ -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>;
|
||||
|
@ -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,7 +40,7 @@ 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>;
|
||||
|
@ -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>;
|
||||
|
@ -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>
|
@ -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>;
|
@ -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,
|
||||
};
|
||||
|
@ -23,11 +23,11 @@ const iconImage = await getImage({src: icon, height: 32, width: 32, format: "png
|
||||
title={title}
|
||||
description={description}
|
||||
twitter={{
|
||||
creator: "@chaoscaot"
|
||||
creator: "@chaoscaot",
|
||||
}}
|
||||
languageAlternates={astroI18n.locales.map((locale) => ({
|
||||
hrefLang: locale,
|
||||
href: `/${locale}/`
|
||||
href: `/${locale}/`,
|
||||
}))}
|
||||
/>
|
||||
|
||||
|
@ -20,7 +20,8 @@ const { title, description } = Astro.props;
|
||||
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>
|
||||
@ -34,7 +35,8 @@ const { title, description } = Astro.props;
|
||||
<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>
|
||||
@ -55,8 +57,10 @@ 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/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>
|
||||
@ -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,9 +147,11 @@ customElements.define("nav-bar", Navbar);
|
||||
<style>
|
||||
.footer-card {
|
||||
@apply w-40 text-gray-400 flex flex-col;
|
||||
|
||||
> h1 {
|
||||
@apply text-xl font-bold text-gray-100;
|
||||
}
|
||||
|
||||
> a {
|
||||
@apply hover:underline;
|
||||
}
|
||||
|
@ -8,7 +8,8 @@ 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" />
|
||||
<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/>
|
||||
|
@ -17,11 +17,11 @@ export const getStaticPaths = createGetStaticPaths(async () => {
|
||||
|
||||
return posts.map((page) => ({
|
||||
props: {
|
||||
page
|
||||
page,
|
||||
},
|
||||
params: {
|
||||
slug: fixLink(page.slug)
|
||||
}
|
||||
slug: fixLink(page.slug),
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
@ -47,7 +47,6 @@ const ogImage = await getImage({
|
||||
width: 1200,
|
||||
height: 630,
|
||||
});
|
||||
|
||||
---
|
||||
|
||||
<PageLayout title={post.data.title} description={post.data.description}>
|
||||
@ -71,17 +70,22 @@ 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 => (
|
||||
<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, {
|
||||
))}
|
||||
<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>
|
||||
@ -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,
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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/>}
|
||||
{index !== posts.length - 1 &&
|
||||
<hr/>}
|
||||
</div>
|
||||
))}
|
||||
</PageLayout>
|
@ -36,18 +36,18 @@ 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;
|
||||
@ -57,7 +57,8 @@ const { posts, tag } = Astro.props;
|
||||
{posts.map((post, index) => (
|
||||
<div>
|
||||
<PostComponent post={post}/>
|
||||
{index !== posts.length - 1 && <hr/>}
|
||||
{index !== posts.length - 1 &&
|
||||
<hr/>}
|
||||
</div>
|
||||
))}
|
||||
</PageLayout>
|
@ -7,6 +7,7 @@ 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");
|
||||
|
@ -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>
|
||||
))}
|
||||
|
@ -7,7 +7,7 @@ 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},
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
@ -123,7 +131,9 @@ function mapMap<T, K, J>(i: Map<T, K>, fn: (key: T, value: K) => J): J[] {
|
||||
<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>
|
||||
))}
|
||||
@ -163,12 +175,15 @@ 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 {
|
||||
@apply text-xl font-bold mt-4;
|
||||
}
|
||||
|
||||
> p {
|
||||
@apply mt-4;
|
||||
}
|
||||
|
||||
> svg {
|
||||
@apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl
|
||||
}
|
||||
|
@ -9,13 +9,15 @@ 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/>
|
||||
|
@ -9,16 +9,16 @@ 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;
|
||||
|
@ -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]},
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -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>))}
|
||||
|
@ -1,5 +1,4 @@
|
||||
---
|
||||
|
||||
import PageLayout from "../../layouts/PageLayout.astro";
|
||||
import FightStatistics from "../../components/FightStatistics.svelte";
|
||||
import {t} from "astro-i18n";
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren