Code Cleanup™

This commit is contained in:
2024-02-11 11:16:23 +01:00
parent 4b27eb76fe
commit 9fd8ddb9bd
62 changed files with 663 additions and 519 deletions

View File

@@ -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 ? (
<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}
{post.data.image != null
? (
<div class="flex-shrink-0 pr-2">
<Image src={post.data.image} alt="Post Image" class="rounded-2xl shadow-2xl object-cover h-32 w-32 max-w-none transition-transform hover:scale-105" />
</div>
)
: null}
<div>
<h2 class="text-2xl font-bold">{post.data.title}</h2>
<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">

View File

@@ -18,31 +18,46 @@
-->
<script lang="ts">
import type {ConditionsFailedEvent, RouteDefinition} from "svelte-spa-router";
import wrap from "svelte-spa-router/wrap";
import Router, {replace} from "svelte-spa-router";
import {get} from "svelte/store";
import {tokenStore} from "@repo/repo";
import type {ConditionsFailedEvent, RouteDefinition} from "svelte-spa-router";
import wrap from "svelte-spa-router/wrap";
import Router, {replace} from "svelte-spa-router";
import {get} from "svelte/store";
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')})
}
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")})
};
function conditionsFailed(event: ConditionsFailedEvent) {
if(event.detail.location === "/login") {
replace("/")
} else {
replace("/login")
function conditionsFailed(event: ConditionsFailedEvent) {
if (event.detail.location === "/login") {
replace("/");
} else {
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>

View File

@@ -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>

View File

@@ -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}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}

View File

@@ -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.

View File

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

View File

@@ -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,16 +122,20 @@
{: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}
{:catch error}
<p>{error.toString()}</p>
{/await}
{:catch error}

View File

@@ -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;
pageContent = new TextDecoder().decode(base64ToBytes(value.content));
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}

View File

@@ -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">
@@ -52,9 +52,9 @@
<style>
:global(.editor-preview) {
& * {
all: revert;
color: black;
}
& * {
all: revert;
color: black;
}
}
</style>

View File

@@ -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
};

View File

@@ -40,19 +40,19 @@
function dispatchSelect() {
setTimeout(() => {
if (!deleteOpen && !editOpen) {
dispatcher('select');
dispatcher("select");
}
}, 1);
}
async function deleteFight() {
await $fightRepo.deleteFight(fight.id);
dispatcher('update');
dispatcher("update");
}
</script>
<div class="flex h-16 {i % 2 === 0 ? 'bg-gray-800' : ''} mx-4 mt-6 rounded border {selected ? 'border-orange-700' : 'border-gray-700'} p-2 hover:bg-gray-700 transition justify-between shadow-lg cursor-pointer"
on:click={dispatchSelect} on:keypress={dispatchSelect} role="checkbox" aria-checked={selected} tabindex="0"
on:click={dispatchSelect} on:keypress={dispatchSelect} role="checkbox" aria-checked={selected} tabindex="0"
>
<div class="flex">
<div class="flex flex-col">
@@ -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?

View File

@@ -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,12 +218,13 @@
</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)}
<FightCard {fight} {i} {data} selected={selectedFights.has(fight)}
on:select={() => {
on:select={() => {
if (selectedFights.has(fight)) {
selectedFights.delete(fight);
} else {
@@ -225,13 +232,14 @@
}
selectedFights = selectedFights;
}}
on:update={async () => fights = await $fightRepo.listFights(data.event.id)}
on:update={async () => fights = await $fightRepo.listFights(data.event.id)}
/>
{/each}
{/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>

View File

@@ -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}

View File

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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}
@@ -241,15 +243,16 @@
</Modal>
<style lang="scss">
#reseter::before {
content: 'Reset';
position: absolute;
top: 0;
color: gray;
}
#reseter {
min-width: 14rem;
}
#reseter::before {
content: 'Reset';
position: absolute;
top: 0;
color: gray;
}
#reseter {
min-width: 14rem;
}
</style>

View File

@@ -27,11 +27,12 @@
</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}
role="figure">
on:mouseenter={() => hover = true}
on:mouseleave={() => hover = false}
role="figure">
<span>{team.name}</span>
</div>

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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));
}

View File

@@ -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",
});
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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}),
});
}
}

View File

@@ -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");

View File

@@ -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("");

View File

@@ -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,
}),
});
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>;

View File

@@ -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 }
}

View File

@@ -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>;

View File

@@ -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>

View File

@@ -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>;