Ursprung
bbf13cf203
Commit
72933a46d1
@ -21,8 +21,7 @@
|
|||||||
import {twMerge} from "tailwind-merge";
|
import {twMerge} from "tailwind-merge";
|
||||||
import {onMount} from "svelte";
|
import {onMount} from "svelte";
|
||||||
|
|
||||||
let cardElement: HTMLDivElement;
|
let cardElement: HTMLDivElement = $state();
|
||||||
export let hoverEffect: boolean = true;
|
|
||||||
|
|
||||||
function rotateElement(event: MouseEvent) {
|
function rotateElement(event: MouseEvent) {
|
||||||
if(!hoverEffect) return;
|
if(!hoverEffect) return;
|
||||||
@ -46,12 +45,18 @@
|
|||||||
cardElement.style.setProperty('--rotate-y', "0");
|
cardElement.style.setProperty('--rotate-y', "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
export let extraClasses: string = '';
|
interface Props {
|
||||||
$: classes = twMerge("w-72 border-2 bg-zinc-50 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100", extraClasses)
|
hoverEffect?: boolean;
|
||||||
|
extraClasses?: string;
|
||||||
|
children?: import('svelte').Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { hoverEffect = true, extraClasses = '', children }: Props = $props();
|
||||||
|
let classes = $derived(twMerge("w-72 border-2 bg-zinc-50 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100", extraClasses))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={classes} bind:this={cardElement} on:mousemove={rotateElement} on:mouseleave={resetElement} class:hoverEffect>
|
<div class={classes} bind:this={cardElement} onmousemove={rotateElement} onmouseleave={resetElement} class:hoverEffect>
|
||||||
<slot></slot>
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -19,11 +19,8 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {t} from "astro-i18n"
|
import {t} from "astro-i18n"
|
||||||
import SchematicList from "./dashboard/SchematicList.svelte";
|
|
||||||
import UserInfo from "./dashboard/UserInfo.svelte";
|
import UserInfo from "./dashboard/UserInfo.svelte";
|
||||||
import {dataRepo} from "@repo/data.ts";
|
import {dataRepo} from "@repo/data.ts";
|
||||||
import {schemRepo} from "@repo/schem.ts";
|
|
||||||
import SWPaginator from "@components/styled/SWPaginator.svelte";
|
|
||||||
import UploadModal from "@components/dashboard/UploadModal.svelte";
|
import UploadModal from "@components/dashboard/UploadModal.svelte";
|
||||||
import SWButton from "@components/styled/SWButton.svelte";
|
import SWButton from "@components/styled/SWButton.svelte";
|
||||||
|
|
||||||
@ -33,7 +30,7 @@
|
|||||||
return $dataRepo.getMe()
|
return $dataRepo.getMe()
|
||||||
}
|
}
|
||||||
|
|
||||||
let uploadOpen = false;
|
let uploadOpen = $state(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await userFetch}
|
{#await userFetch}
|
||||||
|
@ -21,9 +21,13 @@
|
|||||||
import {t} from "astro-i18n";
|
import {t} from "astro-i18n";
|
||||||
import {statsRepo} from "@repo/stats.ts";
|
import {statsRepo} from "@repo/stats.ts";
|
||||||
|
|
||||||
export let gamemode: string;
|
|
||||||
|
|
||||||
export let topFive: boolean = false;
|
interface Props {
|
||||||
|
gamemode: string;
|
||||||
|
topFive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { gamemode, topFive = false }: Props = $props();
|
||||||
|
|
||||||
let request = getRequest();
|
let request = getRequest();
|
||||||
|
|
||||||
|
@ -33,9 +33,13 @@
|
|||||||
import type {FightStats} from "@type/stats.ts"
|
import type {FightStats} from "@type/stats.ts"
|
||||||
import 'chartjs-adapter-dayjs-4'
|
import 'chartjs-adapter-dayjs-4'
|
||||||
|
|
||||||
export let data: FightStats;
|
interface Props {
|
||||||
|
data: FightStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { data }: Props = $props();
|
||||||
let chart: Chart<"line", {x: string; y: number}[], unknown>;
|
let chart: Chart<"line", {x: string; y: number}[], unknown>;
|
||||||
let canvas: HTMLCanvasElement;
|
let canvas: HTMLCanvasElement = $state();
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
Chart.register(LineController, LineElement, PointElement, LinearScale, TimeScale, Tooltip, Legend, Title)
|
Chart.register(LineController, LineElement, PointElement, LinearScale, TimeScale, Tooltip, Legend, Title)
|
||||||
@ -101,5 +105,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<canvas bind:this={canvas} />
|
<canvas bind:this={canvas}></canvas>
|
||||||
</div>
|
</div>
|
@ -1,3 +1,4 @@
|
|||||||
|
<!-- @migration-task Error while migrating Svelte code: `<tr>` is invalid inside `<table>` -->
|
||||||
<!--
|
<!--
|
||||||
- This file is a part of the SteamWar software.
|
- This file is a part of the SteamWar software.
|
||||||
-
|
-
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
<!-- @migration-task Error while migrating Svelte code: `<tr>` is invalid inside `<table>` -->
|
||||||
<!--
|
<!--
|
||||||
- This file is a part of the SteamWar software.
|
- This file is a part of the SteamWar software.
|
||||||
-
|
-
|
||||||
|
@ -20,9 +20,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { l } from "../util/util.ts"
|
import { l } from "../util/util.ts"
|
||||||
|
|
||||||
export let to: string;
|
interface Props {
|
||||||
|
to: string;
|
||||||
|
children?: import('svelte').Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { to, children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a href={l(to)}>
|
<a href={l(to)}>
|
||||||
<slot />
|
{@render children?.()}
|
||||||
</a>
|
</a>
|
@ -18,15 +18,17 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { preventDefault } from 'svelte/legacy';
|
||||||
|
|
||||||
import {l} from "@utils/util.ts";
|
import {l} from "@utils/util.ts";
|
||||||
import {t} from "astro-i18n";
|
import {t} from "astro-i18n";
|
||||||
import {get} from "svelte/store";
|
import {get} from "svelte/store";
|
||||||
import {navigate} from "astro:transitions/client";
|
import {navigate} from "astro:transitions/client";
|
||||||
|
|
||||||
let username: string = "";
|
let username: string = $state("");
|
||||||
let pw: string = "";
|
let pw: string = $state("");
|
||||||
|
|
||||||
let error: string = "";
|
let error: string = $state("");
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
let {tokenStore} = await import("./repo/repo.ts");
|
let {tokenStore} = await import("./repo/repo.ts");
|
||||||
@ -54,7 +56,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="bg-gray-100 dark:bg-neutral-900 p-12 rounded-2xl shadow-2xl border-2 border-gray-600 flex flex-col" on:submit|preventDefault={login}>
|
<form class="bg-gray-100 dark:bg-neutral-900 p-12 rounded-2xl shadow-2xl border-2 border-gray-600 flex flex-col" onsubmit={preventDefault(login)}>
|
||||||
<h1 class="text-4xl text-white text-center">{t("login.title")}</h1>
|
<h1 class="text-4xl text-white text-center">{t("login.title")}</h1>
|
||||||
<div class="ml-2 flex flex-col">
|
<div class="ml-2 flex flex-col">
|
||||||
<label for="username">{t("login.label.username")}</label>
|
<label for="username">{t("login.label.username")}</label>
|
||||||
@ -68,7 +70,7 @@
|
|||||||
{#if error}
|
{#if error}
|
||||||
<p class="mt-2 text-red-500">{error}</p>
|
<p class="mt-2 text-red-500">{error}</p>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="btn mt-4 !mx-0 justify-center" type="submit" on:click|preventDefault={login}>{t("login.submit")}</button>
|
<button class="btn mt-4 !mx-0 justify-center" type="submit" onclick={preventDefault(login)}>{t("login.submit")}</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
|
@ -23,9 +23,14 @@
|
|||||||
import {t} from "astro-i18n";
|
import {t} from "astro-i18n";
|
||||||
import {l} from "../util/util";
|
import {l} from "../util/util";
|
||||||
import {onMount} from "svelte";
|
import {onMount} from "svelte";
|
||||||
|
interface Props {
|
||||||
|
logo?: import('svelte').Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
let navbar: HTMLDivElement;
|
let { logo }: Props = $props();
|
||||||
let searchOpen = false;
|
|
||||||
|
let navbar: HTMLDivElement = $state();
|
||||||
|
let searchOpen = $state(false);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
handleScroll();
|
handleScroll();
|
||||||
@ -40,12 +45,12 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:scroll={handleScroll}/>
|
<svelte:window onscroll={handleScroll}/>
|
||||||
|
|
||||||
<nav data-pagefind-ignore class="fixed top-0 left-0 right-0 sm:px-4 py-1 transition-colors z-10 flex justify-center before:backdrop-blur before:shadow-2xl 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" bind:this={navbar}>
|
<nav data-pagefind-ignore class="fixed top-0 left-0 right-0 sm:px-4 py-1 transition-colors z-10 flex justify-center before:backdrop-blur before:shadow-2xl 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" bind:this={navbar}>
|
||||||
<div class="flex flex-row items-center justify-evenly md:justify-between match">
|
<div class="flex flex-row items-center justify-evenly md:justify-between match">
|
||||||
<a class="flex items-center" href={l("/")}>
|
<a class="flex items-center" href={l("/")}>
|
||||||
<slot name="logo"></slot>
|
{@render logo?.()}
|
||||||
<span class="text-2xl uppercase font-bold dark:text-white hidden md:inline-block">
|
<span class="text-2xl uppercase font-bold dark:text-white hidden md:inline-block">
|
||||||
{t("navbar.title")}
|
{t("navbar.title")}
|
||||||
<span class="before:scale-y-100" style="display: none" aria-hidden="true"></span>
|
<span class="before:scale-y-100" style="display: none" aria-hidden="true"></span>
|
||||||
@ -115,7 +120,7 @@
|
|||||||
|
|
||||||
{#if searchOpen}
|
{#if searchOpen}
|
||||||
{#await import("./SearchComponent.svelte") then c}
|
{#await import("./SearchComponent.svelte") then c}
|
||||||
<svelte:component this={c.default} bind:open={searchOpen} />
|
<c.default bind:open={searchOpen} />
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { schemRepo } from "@repo/schem.ts";
|
import { schemRepo } from "@repo/schem.ts";
|
||||||
|
|
||||||
$: code = new URLSearchParams(window.location.search).get("code") || "";
|
let code = $derived(new URLSearchParams(window.location.search).get("code") || "");
|
||||||
|
|
||||||
$: downloadUrl = $schemRepo.getSchematicDownloadUrl(code);
|
let downloadUrl = $derived($schemRepo.getSchematicDownloadUrl(code));
|
||||||
$: schematic = $schemRepo.getSchematicCodeInfo(code);
|
let schematic = $derived($schemRepo.getSchematicCodeInfo(code));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await schematic then schem}
|
{#await schematic then schem}
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
pagefind.init();
|
pagefind.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
let results: PagefindDocument[] = [];
|
let results: PagefindDocument[] = $state([]);
|
||||||
|
|
||||||
async function search(e: KeyboardEvent) {
|
async function search(e: KeyboardEvent) {
|
||||||
if (e.target instanceof HTMLInputElement) {
|
if (e.target instanceof HTMLInputElement) {
|
||||||
@ -42,13 +42,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export let open = false;
|
interface Props {
|
||||||
|
open?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { open = $bindable(false) }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button transition:fade class="fixed top-0 left-0 w-screen h-screen backdrop-blur z-20 cursor-default" on:click={() => open = false}>
|
<button transition:fade class="fixed top-0 left-0 w-screen h-screen backdrop-blur z-20 cursor-default" onclick={() => open = false}>
|
||||||
</button>
|
</button>
|
||||||
<div transition:slide style="width: min(100%, 75em);" class="fixed top-0 left-1/2 -translate-x-1/2 h-2/3 dark:bg-zinc-900 rounded-b-2xl shadow-2xl z-30 p-4 text-white flex flex-col">
|
<div transition:slide style="width: min(100%, 75em);" class="fixed top-0 left-1/2 -translate-x-1/2 h-2/3 dark:bg-zinc-900 rounded-b-2xl shadow-2xl z-30 p-4 text-white flex flex-col">
|
||||||
<input placeholder="Search..." on:keypress={search}>
|
<input placeholder="Search..." onkeypress={search}>
|
||||||
|
|
||||||
<div class="overflow-y-scroll flex-1 w-full mt-2 rounded-2xl">
|
<div class="overflow-y-scroll flex-1 w-full mt-2 rounded-2xl">
|
||||||
{#each results as result}
|
{#each results as result}
|
||||||
|
@ -20,13 +20,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Button, Modal} from "flowbite-svelte";
|
import {Button, Modal} from "flowbite-svelte";
|
||||||
|
|
||||||
export let open: boolean = false;
|
interface Props {
|
||||||
export let error: Error | undefined;
|
open: boolean;
|
||||||
|
error: Error | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { open = $bindable(), error = $bindable() }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if (error instanceof Error)}
|
{#if (error instanceof Error)}
|
||||||
<Modal bind:open title={error.message}>
|
<Modal bind:open title={error.message}>
|
||||||
<p>{error.stack}</p>
|
<p>{error.stack}</p>
|
||||||
<Button slot="footer" on:click={() => open = false}>Close</Button>
|
{#snippet footer()}
|
||||||
|
<Button on:click={() => open = false}>Close</Button>
|
||||||
|
{/snippet}
|
||||||
</Modal>
|
</Modal>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -23,53 +23,67 @@
|
|||||||
import {gamemodes, groups, maps, players} from "@stores/stores.ts";
|
import {gamemodes, groups, maps, players} from "@stores/stores.ts";
|
||||||
import type {Team} from "@type/team.ts";
|
import type {Team} from "@type/team.ts";
|
||||||
|
|
||||||
export let teams: Team[] = [];
|
interface Props {
|
||||||
export let blueTeam: string;
|
teams?: Team[];
|
||||||
export let redTeam: string;
|
blueTeam: string;
|
||||||
export let start = "";
|
redTeam: string;
|
||||||
export let gamemode = "";
|
start?: string;
|
||||||
export let map = "";
|
gamemode?: string;
|
||||||
export let spectatePort: string | null = null;
|
map?: string;
|
||||||
export let group: string | null = "";
|
spectatePort?: string | null;
|
||||||
export let groupSearch = "";
|
group?: string | null;
|
||||||
|
groupSearch?: string;
|
||||||
|
}
|
||||||
|
|
||||||
$: selectableTeams = teams.map(team => {
|
let {
|
||||||
|
teams = [],
|
||||||
|
blueTeam = $bindable(),
|
||||||
|
redTeam = $bindable(),
|
||||||
|
start = $bindable(""),
|
||||||
|
gamemode = $bindable(""),
|
||||||
|
map = $bindable(""),
|
||||||
|
spectatePort = $bindable(null),
|
||||||
|
group = $bindable(""),
|
||||||
|
groupSearch = $bindable("")
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
let selectableTeams = $derived(teams.map(team => {
|
||||||
return {
|
return {
|
||||||
name: team.name,
|
name: team.name,
|
||||||
value: team.id.toString()
|
value: team.id.toString()
|
||||||
};
|
};
|
||||||
}).sort((a, b) => a.name.localeCompare(b.name));
|
}).sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
|
|
||||||
$: selectableGamemodes = $gamemodes.map(gamemode => {
|
let selectableGamemodes = $derived($gamemodes.map(gamemode => {
|
||||||
return {
|
return {
|
||||||
name: gamemode,
|
name: gamemode,
|
||||||
value: gamemode
|
value: gamemode
|
||||||
};
|
};
|
||||||
}).sort((a, b) => a.name.localeCompare(b.name));
|
}).sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
$: customGamemode = !selectableGamemodes.some((e) => e.name === gamemode) && gamemode !== "";
|
let customGamemode = $derived(!selectableGamemodes.some((e) => e.name === gamemode) && gamemode !== "");
|
||||||
$: selectableCustomGamemode = [
|
let selectableCustomGamemode = $derived([
|
||||||
...selectableGamemodes, {
|
...selectableGamemodes, {
|
||||||
name: gamemode + " (custom)",
|
name: gamemode + " (custom)",
|
||||||
value: gamemode
|
value: gamemode
|
||||||
}
|
}
|
||||||
];
|
]);
|
||||||
|
|
||||||
$: mapsStore = maps(gamemode);
|
let mapsStore = $derived(maps(gamemode));
|
||||||
$: selectableMaps = $mapsStore.map(map => {
|
let selectableMaps = $derived($mapsStore.map(map => {
|
||||||
return {
|
return {
|
||||||
name: map,
|
name: map,
|
||||||
value: map
|
value: map
|
||||||
};
|
};
|
||||||
}).sort((a, b) => a.name.localeCompare(b.name));
|
}).sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
$: customMap = !selectableMaps.some((e) => e.name === map) && map !== "";
|
let customMap = $derived(!selectableMaps.some((e) => e.name === map) && map !== "");
|
||||||
$: selectableCustomMaps = [
|
let selectableCustomMaps = $derived([
|
||||||
...selectableMaps, {
|
...selectableMaps, {
|
||||||
name: map + " (custom)",
|
name: map + " (custom)",
|
||||||
value: map
|
value: map
|
||||||
}
|
}
|
||||||
];
|
]);
|
||||||
|
|
||||||
$: selectableGroups = [{
|
let selectableGroups = $derived([{
|
||||||
name: "None",
|
name: "None",
|
||||||
value: ""
|
value: ""
|
||||||
}, {
|
}, {
|
||||||
@ -80,7 +94,7 @@
|
|||||||
name: group,
|
name: group,
|
||||||
value: group
|
value: group
|
||||||
};
|
};
|
||||||
}).sort((a, b) => a.name.localeCompare(b.name))];
|
}).sort((a, b) => a.name.localeCompare(b.name))]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="m-2">
|
<div class="m-2">
|
||||||
@ -93,8 +107,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<Label for="fight-start">Start</Label>
|
<Label for="fight-start">Start</Label>
|
||||||
<Input id="fight-start" bind:value={start} let:props>
|
<Input id="fight-start" bind:value={start} >
|
||||||
|
{#snippet children({ props })}
|
||||||
<input type="datetime-local" {...props} bind:value={start}/>
|
<input type="datetime-local" {...props} bind:value={start}/>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-2">
|
<div class="m-2">
|
||||||
@ -109,8 +125,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="m-2">
|
<div class="m-2">
|
||||||
<Label for="fight-port">Spectate Port</Label>
|
<Label for="fight-port">Spectate Port</Label>
|
||||||
<Input id="fight-port" bind:value={spectatePort} let:props>
|
<Input id="fight-port" bind:value={spectatePort} >
|
||||||
|
{#snippet children({ props })}
|
||||||
<input type="number" inputmode="numeric" {...props} bind:value={spectatePort}/>
|
<input type="number" inputmode="numeric" {...props} bind:value={spectatePort}/>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-2">
|
<div class="m-2">
|
||||||
|
@ -20,15 +20,25 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Button, Dropdown, Search} from "flowbite-svelte";
|
import {Button, Dropdown, Search} from "flowbite-svelte";
|
||||||
|
|
||||||
export let selected: string | null = null;
|
|
||||||
export let items: { name: string, value: string }[] = [];
|
|
||||||
export let maxItems = 5;
|
|
||||||
export let leftText: boolean = false;
|
|
||||||
|
|
||||||
export let searchValue = items.find(item => item.value === selected)?.name || "";
|
interface Props {
|
||||||
let open = false;
|
selected?: string | null;
|
||||||
|
items?: { name: string, value: string }[];
|
||||||
|
maxItems?: number;
|
||||||
|
leftText?: boolean;
|
||||||
|
searchValue?: any;
|
||||||
|
}
|
||||||
|
|
||||||
$: filteredItems = items.filter(item => item.name.toLowerCase().includes(searchValue.toLowerCase())).filter((value, index) => index < maxItems);
|
let {
|
||||||
|
selected = $bindable(null),
|
||||||
|
items = [],
|
||||||
|
maxItems = 5,
|
||||||
|
leftText = false,
|
||||||
|
searchValue = $bindable(items.find(item => item.value === selected)?.name || "")
|
||||||
|
}: Props = $props();
|
||||||
|
let open = $state(false);
|
||||||
|
|
||||||
|
let filteredItems = $derived(items.filter(item => item.name.toLowerCase().includes(searchValue.toLowerCase())).filter((value, index) => index < maxItems));
|
||||||
|
|
||||||
function selectItem(item: { name: string, value: string }) {
|
function selectItem(item: { name: string, value: string }) {
|
||||||
selected = item.value;
|
selected = item.value;
|
||||||
@ -40,11 +50,13 @@
|
|||||||
<Button color="light"
|
<Button color="light"
|
||||||
on:click={() => open = true}>{selected === null ? 'Auswählen' : items.find(value => value.value === selected)?.name}</Button>
|
on:click={() => open = true}>{selected === null ? 'Auswählen' : items.find(value => value.value === selected)?.name}</Button>
|
||||||
<Dropdown bind:open class="w-60">
|
<Dropdown bind:open class="w-60">
|
||||||
<div class="overflow-y-auto p-3 text-sm w-60" slot="header">
|
{#snippet header()}
|
||||||
|
<div class="overflow-y-auto p-3 text-sm w-60" >
|
||||||
<Search bind:value={searchValue} on:focus={() => open = true} on:keydown={() => open = true}/>
|
<Search bind:value={searchValue} on:focus={() => open = true} on:keydown={() => open = true}/>
|
||||||
</div>
|
</div>
|
||||||
|
{/snippet}
|
||||||
{#each filteredItems as item (item)}
|
{#each filteredItems as item (item)}
|
||||||
<button on:click={() => selectItem(item)}
|
<button onclick={() => 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="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}>
|
class:text-left={leftText}>
|
||||||
{item.name}
|
{item.name}
|
||||||
|
@ -21,9 +21,13 @@
|
|||||||
import {eventRepo} from "@repo/event.ts";
|
import {eventRepo} from "@repo/event.ts";
|
||||||
import EventDisplay from "@components/admin/pages/display/EventDisplay.svelte";
|
import EventDisplay from "@components/admin/pages/display/EventDisplay.svelte";
|
||||||
|
|
||||||
export let params: { event: number };
|
interface Props {
|
||||||
|
params: { event: number };
|
||||||
|
}
|
||||||
|
|
||||||
let eventFuture = getEvent();
|
let { params }: Props = $props();
|
||||||
|
|
||||||
|
let eventFuture = $state(getEvent());
|
||||||
|
|
||||||
function getEvent() {
|
function getEvent() {
|
||||||
return $eventRepo.getEvent(params.event)
|
return $eventRepo.getEvent(params.event)
|
||||||
|
@ -18,7 +18,9 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {ArrowLeftSolid} from "flowbite-svelte-icons";
|
import { preventDefault } from 'svelte/legacy';
|
||||||
|
|
||||||
|
import {ArrowLeftOutline} from "flowbite-svelte-icons";
|
||||||
import {Button, Card, Navbar, NavBrand, Spinner} from "flowbite-svelte";
|
import {Button, Card, Navbar, NavBrand, Spinner} from "flowbite-svelte";
|
||||||
import {mapToMap, nameRegex} from "../util.ts";
|
import {mapToMap, nameRegex} from "../util.ts";
|
||||||
import TypeAheadSearch from "../components/TypeAheadSearch.svelte";
|
import TypeAheadSearch from "../components/TypeAheadSearch.svelte";
|
||||||
@ -26,21 +28,16 @@
|
|||||||
import Editor from "./edit/Editor.svelte";
|
import Editor from "./edit/Editor.svelte";
|
||||||
import {pageRepo} from "@repo/page.ts";
|
import {pageRepo} from "@repo/page.ts";
|
||||||
|
|
||||||
$: pagesFuture = $pageRepo.listPages(selectedBranch);
|
|
||||||
|
|
||||||
let selected: number | null = null;
|
let selected: number | null = $state(null);
|
||||||
|
|
||||||
let selectedBranch: string = "master";
|
let selectedBranch: string = $state("master");
|
||||||
let searchValue: string = "";
|
let searchValue: string = $state("");
|
||||||
let dirty = false;
|
let dirty = $state(false);
|
||||||
|
|
||||||
let selectedPath: string | null = null;
|
let selectedPath: string | null = $state(null);
|
||||||
let pathSearchValue: string = "";
|
let pathSearchValue: string = $state("");
|
||||||
|
|
||||||
$: availableBranches = $branches.map((branch) => ({
|
|
||||||
name: branch,
|
|
||||||
value: branch
|
|
||||||
}));
|
|
||||||
|
|
||||||
async function createBranch(name: string | null = null): Promise<string> {
|
async function createBranch(name: string | null = null): Promise<string> {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
@ -139,12 +136,17 @@
|
|||||||
alert("Error creating page");
|
alert("Error creating page");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let pagesFuture = $derived($pageRepo.listPages(selectedBranch));
|
||||||
|
let availableBranches = $derived($branches.map((branch) => ({
|
||||||
|
name: branch,
|
||||||
|
value: branch
|
||||||
|
})));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col h-screen overflow-scroll">
|
<div class="flex flex-col h-screen overflow-scroll">
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<NavBrand href="#">
|
<NavBrand href="#">
|
||||||
<ArrowLeftSolid></ArrowLeftSolid>
|
<ArrowLeftOutline></ArrowLeftOutline>
|
||||||
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
|
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
|
||||||
Edit Pages
|
Edit Pages
|
||||||
</span>
|
</span>
|
||||||
@ -183,7 +185,7 @@
|
|||||||
{@const startIndex = page.path.indexOf(match)}
|
{@const startIndex = page.path.indexOf(match)}
|
||||||
{@const endIndex = startIndex + match.length}
|
{@const endIndex = startIndex + match.length}
|
||||||
<li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer"
|
<li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer"
|
||||||
on:click|preventDefault={() => changePage(page.id)}>
|
onclick={preventDefault(() => changePage(page.id))}>
|
||||||
<span class:text-orange-600={selected === page.id}>{page.path.substring(0, startIndex)}</span><span
|
<span class:text-orange-600={selected === page.id}>{page.path.substring(0, startIndex)}</span><span
|
||||||
class="text-white"
|
class="text-white"
|
||||||
class:!text-orange-500={selected === page.id}>{match}</span><span
|
class:!text-orange-500={selected === page.id}>{match}</span><span
|
||||||
|
@ -20,13 +20,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Navbar, NavBrand, Spinner, TabItem, Tabs} from "flowbite-svelte";
|
import {Navbar, NavBrand, Spinner, TabItem, Tabs} from "flowbite-svelte";
|
||||||
import EventEdit from "./event/EventEdit.svelte";
|
import EventEdit from "./event/EventEdit.svelte";
|
||||||
import {ArrowLeftSolid} from "flowbite-svelte-icons";
|
import {ArrowLeftOutline} from "flowbite-svelte-icons";
|
||||||
import FightList from "./event/FightList.svelte";
|
import FightList from "./event/FightList.svelte";
|
||||||
import TeamList from "./event/TeamList.svelte";
|
import TeamList from "./event/TeamList.svelte";
|
||||||
import {eventRepo} from "@repo/event.ts";
|
import {eventRepo} from "@repo/event.ts";
|
||||||
import RefereesList from "@components/admin/pages/event/RefereesList.svelte";
|
import RefereesList from "@components/admin/pages/event/RefereesList.svelte";
|
||||||
|
|
||||||
export let params: { id: number };
|
interface Props {
|
||||||
|
params: { id: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
let { params }: Props = $props();
|
||||||
|
|
||||||
let id = params.id;
|
let id = params.id;
|
||||||
let event = $eventRepo.getEvent(id.toString());
|
let event = $eventRepo.getEvent(id.toString());
|
||||||
@ -37,30 +41,40 @@
|
|||||||
<Spinner size={16}/>
|
<Spinner size={16}/>
|
||||||
</div>
|
</div>
|
||||||
{:then data}
|
{:then data}
|
||||||
<Navbar let:hidden let:toggle>
|
<Navbar >
|
||||||
|
{#snippet children({ hidden, toggle })}
|
||||||
<NavBrand href="#">
|
<NavBrand href="#">
|
||||||
<ArrowLeftSolid></ArrowLeftSolid>
|
<ArrowLeftOutline></ArrowLeftOutline>
|
||||||
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
|
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
|
||||||
{data.event.name}
|
{data.event.name}
|
||||||
</span>
|
</span>
|
||||||
</NavBrand>
|
</NavBrand>
|
||||||
|
{/snippet}
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<Tabs style="pill" class="mx-4 flex shadow-lg border-b-2 border-gray-700 pb-2" contentClass="">
|
<Tabs style="pill" class="mx-4 flex shadow-lg border-b-2 border-gray-700 pb-2" contentClass="">
|
||||||
<TabItem open>
|
<TabItem open>
|
||||||
<span slot="title">Event</span>
|
{#snippet title()}
|
||||||
|
<span >Event</span>
|
||||||
|
{/snippet}
|
||||||
<EventEdit {data}/>
|
<EventEdit {data}/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem>
|
<TabItem>
|
||||||
<span slot="title">Teams</span>
|
{#snippet title()}
|
||||||
|
<span >Teams</span>
|
||||||
|
{/snippet}
|
||||||
<TeamList {data}/>
|
<TeamList {data}/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem>
|
<TabItem>
|
||||||
<span slot="title">Schiedsrichter</span>
|
{#snippet title()}
|
||||||
|
<span >Schiedsrichter</span>
|
||||||
|
{/snippet}
|
||||||
<RefereesList {data}/>
|
<RefereesList {data}/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem>
|
<TabItem>
|
||||||
<span slot="title">Kämpfe</span>
|
{#snippet title()}
|
||||||
|
<span >Kämpfe</span>
|
||||||
|
{/snippet}
|
||||||
<FightList {data}/>
|
<FightList {data}/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
@ -19,11 +19,15 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Navbar, NavBrand, Spinner, TabItem, Tabs} from "flowbite-svelte";
|
import {Navbar, NavBrand, Spinner, TabItem, Tabs} from "flowbite-svelte";
|
||||||
import {ArrowLeftSolid} from "flowbite-svelte-icons";
|
import {ArrowLeftOutline} from "flowbite-svelte-icons";
|
||||||
import GroupGenerator from "./generate/GroupGenerator.svelte";
|
import GroupGenerator from "./generate/GroupGenerator.svelte";
|
||||||
import {eventRepo} from "@repo/event.ts";
|
import {eventRepo} from "@repo/event.ts";
|
||||||
|
|
||||||
export let params: { id: number };
|
interface Props {
|
||||||
|
params: { id: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
let { params }: Props = $props();
|
||||||
|
|
||||||
let id = params.id;
|
let id = params.id;
|
||||||
let event = $eventRepo.getEvent(id.toString());
|
let event = $eventRepo.getEvent(id.toString());
|
||||||
@ -34,13 +38,15 @@
|
|||||||
<Spinner size={16}/>
|
<Spinner size={16}/>
|
||||||
</div>
|
</div>
|
||||||
{:then data}
|
{:then data}
|
||||||
<Navbar let:hidden let:toggle>
|
<Navbar >
|
||||||
|
{#snippet children({ hidden, toggle })}
|
||||||
<NavBrand href="#/event/{id}">
|
<NavBrand href="#/event/{id}">
|
||||||
<ArrowLeftSolid></ArrowLeftSolid>
|
<ArrowLeftOutline></ArrowLeftOutline>
|
||||||
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
|
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
|
||||||
{data.event.name} - Generate
|
{data.event.name} - Generate
|
||||||
</span>
|
</span>
|
||||||
</NavBrand>
|
</NavBrand>
|
||||||
|
{/snippet}
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<Tabs style="pill" class="mx-4 flex shadow-lg border-b-2 border-gray-700 pb-2" contentClass="">
|
<Tabs style="pill" class="mx-4 flex shadow-lg border-b-2 border-gray-700 pb-2" contentClass="">
|
||||||
|
@ -19,17 +19,18 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Button, Navbar, NavBrand, NavHamburger, NavLi, NavUl, Spinner} from "flowbite-svelte";
|
import {Button, Navbar, NavBrand, NavHamburger, NavLi, NavUl, Spinner} from "flowbite-svelte";
|
||||||
import {PlusSolid} from "flowbite-svelte-icons";
|
import {PlusOutline} from "flowbite-svelte-icons";
|
||||||
import EventCard from "./home/EventCard.svelte";
|
import EventCard from "./home/EventCard.svelte";
|
||||||
import CreateEventModal from "./home/CreateEventModal.svelte";
|
import CreateEventModal from "./home/CreateEventModal.svelte";
|
||||||
import {eventRepo} from "@repo/event.ts";
|
import {eventRepo} from "@repo/event.ts";
|
||||||
|
|
||||||
let events = $eventRepo.listEvents();
|
let events = $state($eventRepo.listEvents());
|
||||||
let showAdd = false;
|
let showAdd = $state(false);
|
||||||
let millis = Date.now();
|
let millis = Date.now();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Navbar let:hidden let:toggle class="shadow-lg border-b">
|
<Navbar class="shadow-lg border-b">
|
||||||
|
{#snippet children({ hidden, toggle })}
|
||||||
<NavBrand href="/">
|
<NavBrand href="/">
|
||||||
<span class="self-center whitespace-nowrap text-xl font-semibold dark:text-white">
|
<span class="self-center whitespace-nowrap text-xl font-semibold dark:text-white">
|
||||||
Mod-Tool
|
Mod-Tool
|
||||||
@ -40,6 +41,7 @@
|
|||||||
<NavLi href="#/edit">Edit Pages</NavLi>
|
<NavLi href="#/edit">Edit Pages</NavLi>
|
||||||
<NavLi href="#/perms">Permissions</NavLi>
|
<NavLi href="#/perms">Permissions</NavLi>
|
||||||
</NavUl>
|
</NavUl>
|
||||||
|
{/snippet}
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<CreateEventModal bind:open={showAdd} on:create={() => events = $eventRepo.listEvents()}/>
|
<CreateEventModal bind:open={showAdd} on:create={() => events = $eventRepo.listEvents()}/>
|
||||||
@ -50,7 +52,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:then data}
|
{:then data}
|
||||||
<Button class="fixed bottom-6 right-6 !p-4 z-10 shadow-lg" on:click={() => showAdd = true}>
|
<Button class="fixed bottom-6 right-6 !p-4 z-10 shadow-lg" on:click={() => showAdd = true}>
|
||||||
<PlusSolid/>
|
<PlusOutline/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<h1 class="text-3xl mt-4 ml-4">Upcoming</h1>
|
<h1 class="text-3xl mt-4 ml-4">Upcoming</h1>
|
||||||
|
@ -18,16 +18,18 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { preventDefault } from 'svelte/legacy';
|
||||||
|
|
||||||
import {Button, Input, Label, Spinner, Toast} from "flowbite-svelte";
|
import {Button, Input, Label, Spinner, Toast} from "flowbite-svelte";
|
||||||
import {fly} from "svelte/transition";
|
import {fly} from "svelte/transition";
|
||||||
import {replace} from "svelte-spa-router";
|
import {replace} from "svelte-spa-router";
|
||||||
import {EyeOutline, EyeSlashOutline} from "flowbite-svelte-icons";
|
import {EyeOutline, EyeSlashOutline} from "flowbite-svelte-icons";
|
||||||
import {fetchWithToken, tokenStore} from "@repo/repo.ts";
|
import {fetchWithToken, tokenStore} from "@repo/repo.ts";
|
||||||
|
|
||||||
let show = false;
|
let show = $state(false);
|
||||||
let loading = false;
|
let loading = $state(false);
|
||||||
let value = "";
|
let value = $state("");
|
||||||
let error = false;
|
let error = $state(false);
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
loading = true;
|
loading = true;
|
||||||
@ -47,19 +49,21 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-screen w-screen grid place-items-center overflow-hidden">
|
<div class="h-screen w-screen grid place-items-center overflow-hidden">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid">
|
<form onsubmit={preventDefault(handleSubmit)} class="grid">
|
||||||
<div class="grid gap-6 mb-6 md:grid-cols-1">
|
<div class="grid gap-6 mb-6 md:grid-cols-1">
|
||||||
<div>
|
<div>
|
||||||
<Label for="token-xyz" class="mb-2">Token</Label>
|
<Label for="token-xyz" class="mb-2">Token</Label>
|
||||||
<Input type={show?'text':'password'} id="token-xyz" placeholder="•••••••••" required size="lg"
|
<Input type={show?'text':'password'} id="token-xyz" placeholder="•••••••••" required size="lg"
|
||||||
bind:value>
|
bind:value>
|
||||||
<button slot="left" on:click={() => (show = !show)} class="pointer-events-auto" type="button">
|
{#snippet left()}
|
||||||
|
<button onclick={() => (show = !show)} class="pointer-events-auto" type="button">
|
||||||
{#if show}
|
{#if show}
|
||||||
<EyeOutline/>
|
<EyeOutline/>
|
||||||
{:else}
|
{:else}
|
||||||
<EyeSlashOutline/>
|
<EyeSlashOutline/>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -75,7 +79,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Toast color="red" position="bottom-left" bind:open={error} transition={fly} params="{{x: -200}}">
|
<Toast color="red" position="bottom-left" bind:open={error} transition={fly} params="{{x: -200}}">
|
||||||
<svelte:fragment slot="icon">
|
{#snippet icon()}
|
||||||
|
|
||||||
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"
|
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd"
|
<path fill-rule="evenodd"
|
||||||
@ -83,7 +88,8 @@
|
|||||||
clip-rule="evenodd"></path>
|
clip-rule="evenodd"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="sr-only">Error icon</span>
|
<span class="sr-only">Error icon</span>
|
||||||
</svelte:fragment>
|
|
||||||
|
{/snippet}
|
||||||
Invalid Token.
|
Invalid Token.
|
||||||
</Toast>
|
</Toast>
|
||||||
|
|
||||||
|
@ -18,8 +18,10 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { run, preventDefault } from 'svelte/legacy';
|
||||||
|
|
||||||
import {Button, Card, Checkbox, Input, Label, Navbar, NavBrand, Radio, Spinner} from "flowbite-svelte";
|
import {Button, Card, Checkbox, Input, Label, Navbar, NavBrand, Radio, Spinner} from "flowbite-svelte";
|
||||||
import {ArrowLeftSolid} from "flowbite-svelte-icons";
|
import {ArrowLeftOutline} from "flowbite-svelte-icons";
|
||||||
import {players} from "@stores/stores.ts";
|
import {players} from "@stores/stores.ts";
|
||||||
import {capitalize} from "../util.ts";
|
import {capitalize} from "../util.ts";
|
||||||
import {permsRepo} from "@repo/perms.ts";
|
import {permsRepo} from "@repo/perms.ts";
|
||||||
@ -28,21 +30,17 @@
|
|||||||
import SWModal from "@components/styled/SWModal.svelte";
|
import SWModal from "@components/styled/SWModal.svelte";
|
||||||
import {userRepo} from "@repo/user.ts";
|
import {userRepo} from "@repo/user.ts";
|
||||||
|
|
||||||
let search = "";
|
let search = $state("");
|
||||||
$: lowerCaseSearch = search.toLowerCase();
|
|
||||||
$: filteredPlayers = $players.filter(value => value.name.toLowerCase().includes(lowerCaseSearch));
|
|
||||||
|
|
||||||
let selectedPlayer: string | null = null;
|
let selectedPlayer: string | null = $state(null);
|
||||||
$: player = $players.find(value => value.uuid === selectedPlayer);
|
let playerPerms = $state(loadPlayer(selectedPlayer));
|
||||||
let playerPerms = loadPlayer(selectedPlayer);
|
|
||||||
$: playerPerms = loadPlayer(selectedPlayer);
|
|
||||||
|
|
||||||
let prefixEdit = "PREFIX_NONE";
|
let prefixEdit = $state("PREFIX_NONE");
|
||||||
let activePerms: string[] = [];
|
let activePerms: string[] = $state([]);
|
||||||
|
|
||||||
let resetPasswordModal = false;
|
let resetPasswordModal = $state(false);
|
||||||
let resetPassword = "";
|
let resetPassword = $state("");
|
||||||
let resetPasswordRepeat = "";
|
let resetPasswordRepeat = $state("");
|
||||||
|
|
||||||
function loadPlayer(id: string | null) {
|
function loadPlayer(id: string | null) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
@ -101,16 +99,24 @@
|
|||||||
resetPasswordRepeat = "";
|
resetPasswordRepeat = "";
|
||||||
resetPasswordModal = false;
|
resetPasswordModal = false;
|
||||||
}
|
}
|
||||||
|
let lowerCaseSearch = $derived(search.toLowerCase());
|
||||||
|
let filteredPlayers = $derived($players.filter(value => value.name.toLowerCase().includes(lowerCaseSearch)));
|
||||||
|
let player = $derived($players.find(value => value.uuid === selectedPlayer));
|
||||||
|
run(() => {
|
||||||
|
playerPerms = loadPlayer(selectedPlayer);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col h-screen overflow-hidden">
|
<div class="flex flex-col h-screen overflow-hidden">
|
||||||
<Navbar let:hidden let:toggle>
|
<Navbar >
|
||||||
|
{#snippet children({ hidden, toggle })}
|
||||||
<NavBrand href="#">
|
<NavBrand href="#">
|
||||||
<ArrowLeftSolid></ArrowLeftSolid>
|
<ArrowLeftOutline></ArrowLeftOutline>
|
||||||
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
|
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
|
||||||
Permissions
|
Permissions
|
||||||
</span>
|
</span>
|
||||||
</NavBrand>
|
</NavBrand>
|
||||||
|
{/snippet}
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<div class="p-4 flex-1 overflow-hidden">
|
<div class="p-4 flex-1 overflow-hidden">
|
||||||
@ -125,7 +131,7 @@
|
|||||||
{#each filteredPlayers as player (player.uuid)}
|
{#each filteredPlayers as player (player.uuid)}
|
||||||
<li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer"
|
<li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer"
|
||||||
class:text-orange-500={player.uuid === selectedPlayer}
|
class:text-orange-500={player.uuid === selectedPlayer}
|
||||||
on:click|preventDefault={() => selectedPlayer = player.uuid}>
|
onclick={preventDefault(() => selectedPlayer = player.uuid)}>
|
||||||
{player.name}
|
{player.name}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
@ -166,14 +172,16 @@
|
|||||||
<Label for="repeat_password">Repeat Password</Label>
|
<Label for="repeat_password">Repeat Password</Label>
|
||||||
<Input type="password" id="repeat_password" placeholder="Repeat Password" bind:value={resetPasswordRepeat}/>
|
<Input type="password" id="repeat_password" placeholder="Repeat Password" bind:value={resetPasswordRepeat}/>
|
||||||
|
|
||||||
<svelte:fragment slot="footer">
|
{#snippet footer()}
|
||||||
|
|
||||||
<Button class="ml-auto mr-4" on:click={resetResetPassword}>
|
<Button class="ml-auto mr-4" on:click={resetResetPassword}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button disabled={resetPassword === "" || resetPassword !== resetPasswordRepeat} on:click={resetPW}>
|
<Button disabled={resetPassword === "" || resetPassword !== resetPasswordRepeat} on:click={resetPW}>
|
||||||
Reset Password
|
Reset Password
|
||||||
</Button>
|
</Button>
|
||||||
</svelte:fragment>
|
|
||||||
|
{/snippet}
|
||||||
</SWModal>
|
</SWModal>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,16 +21,20 @@
|
|||||||
import type {ExtendedEvent} from "@type/event.ts";
|
import type {ExtendedEvent} from "@type/event.ts";
|
||||||
import {onMount} from "svelte";
|
import {onMount} from "svelte";
|
||||||
|
|
||||||
export let event: ExtendedEvent;
|
interface Props {
|
||||||
export let group: string | null = null;
|
event: ExtendedEvent;
|
||||||
|
group?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { event, group = null }: Props = $props();
|
||||||
|
|
||||||
const scrollTime = 10000;
|
const scrollTime = 10000;
|
||||||
|
|
||||||
$: groupFights = event.fights.filter(fight => group === null || fight.group === group);
|
let groupFights = $derived(event.fights.filter(fight => group === null || fight.group === group));
|
||||||
|
|
||||||
let clock = new Date();
|
let clock = $state(new Date());
|
||||||
let scrollContainer: HTMLDivElement;
|
let scrollContainer: HTMLDivElement = $state();
|
||||||
let scroll = 0;
|
let scroll = $state(0);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
|
@ -28,15 +28,18 @@
|
|||||||
import MDEMarkdownEditor from "./MDEMarkdownEditor.svelte";
|
import MDEMarkdownEditor from "./MDEMarkdownEditor.svelte";
|
||||||
import {pageRepo} from "@repo/page.ts";
|
import {pageRepo} from "@repo/page.ts";
|
||||||
|
|
||||||
export let pageId: number;
|
interface Props {
|
||||||
export let branch: string;
|
pageId: number;
|
||||||
export let dirty: boolean = false;
|
branch: string;
|
||||||
|
dirty?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { pageId, branch, dirty = $bindable(false) }: Props = $props();
|
||||||
|
|
||||||
let dispatcher = createEventDispatcher();
|
let dispatcher = createEventDispatcher();
|
||||||
|
|
||||||
$: pageFuture = $pageRepo.getPage(pageId, branch).then(getPage);
|
let pageContent = $state("");
|
||||||
let pageContent = "";
|
let page: Page | null = $state(null);
|
||||||
let page: Page | null = null;
|
|
||||||
|
|
||||||
function getPage(value: Page): Page {
|
function getPage(value: Page): Page {
|
||||||
page = value;
|
page = value;
|
||||||
@ -66,8 +69,9 @@
|
|||||||
dispatcher("reload");
|
dispatcher("reload");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let pageFuture = $derived($pageRepo.getPage(pageId, branch).then(getPage));
|
||||||
</script>
|
</script>
|
||||||
<svelte:window on:beforeunload={() => {
|
<svelte:window onbeforeunload={() => {
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
return "You have unsaved changes. Are you sure you want to leave?";
|
return "You have unsaved changes. Are you sure you want to leave?";
|
||||||
}
|
}
|
||||||
@ -78,7 +82,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<Toolbar class="!bg-gray-900">
|
<Toolbar class="!bg-gray-900">
|
||||||
<ToolbarGroup slot="end">
|
{#snippet end()}
|
||||||
|
<ToolbarGroup >
|
||||||
<ToolbarButton on:click={deletePage}>
|
<ToolbarButton on:click={deletePage}>
|
||||||
Delete
|
Delete
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
@ -86,6 +91,7 @@
|
|||||||
Save
|
Save
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
|
{/snippet}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</div>
|
</div>
|
||||||
{#if page?.name.endsWith("md") || page?.name.endsWith("mdx")}
|
{#if page?.name.endsWith("md") || page?.name.endsWith("mdx")}
|
||||||
|
@ -22,10 +22,14 @@
|
|||||||
import EasyMDE from "easymde";
|
import EasyMDE from "easymde";
|
||||||
import "easymde/dist/easymde.min.css";
|
import "easymde/dist/easymde.min.css";
|
||||||
|
|
||||||
export let value: string;
|
interface Props {
|
||||||
export let dirty: boolean = false;
|
value: string;
|
||||||
|
dirty?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
let editor: HTMLTextAreaElement;
|
let { value = $bindable(), dirty = $bindable(false) }: Props = $props();
|
||||||
|
|
||||||
|
let editor: HTMLTextAreaElement = $state();
|
||||||
let mde: EasyMDE;
|
let mde: EasyMDE;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
@ -30,26 +30,30 @@
|
|||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
export let data: ExtendedEvent;
|
interface Props {
|
||||||
let event = data.event;
|
data: ExtendedEvent;
|
||||||
let name = event.name;
|
}
|
||||||
let deadline = dayjs(event.deadline).utc(true).toISOString().slice(0, -1);
|
|
||||||
let start = dayjs(event.start).utc(true).toISOString().slice(0, -1);
|
let { data }: Props = $props();
|
||||||
let end = dayjs(event.end).utc(true).toISOString().slice(0, -1);
|
let event = $state(data.event);
|
||||||
let member = event.maxTeamMembers;
|
let name = $state(event.name);
|
||||||
let schemType = event.schemType;
|
let deadline = $state(dayjs(event.deadline).utc(true).toISOString().slice(0, -1));
|
||||||
let publicOnly = event.publicSchemsOnly;
|
let start = $state(dayjs(event.start).utc(true).toISOString().slice(0, -1));
|
||||||
|
let end = $state(dayjs(event.end).utc(true).toISOString().slice(0, -1));
|
||||||
|
let member = $state(event.maxTeamMembers);
|
||||||
|
let schemType = $state(event.schemType);
|
||||||
|
let publicOnly = $state(event.publicSchemsOnly);
|
||||||
let addReferee: {name: string, id: number}[] = [];
|
let addReferee: {name: string, id: number}[] = [];
|
||||||
let removeReferee: {name: string, id: number}[] = [];
|
let removeReferee: {name: string, id: number}[] = [];
|
||||||
|
|
||||||
let errorOpen = false;
|
let errorOpen = $state(false);
|
||||||
let error: any = undefined;
|
let error: any = $state(undefined);
|
||||||
let deleteOpen = false;
|
let deleteOpen = $state(false);
|
||||||
|
|
||||||
$: deadlineDate = dayjs(deadline);
|
let deadlineDate = $derived(dayjs(deadline));
|
||||||
$: startDate = dayjs(start);
|
let startDate = $derived(dayjs(start));
|
||||||
$: endDate = dayjs(end);
|
let endDate = $derived(dayjs(end));
|
||||||
$: selectTypes = [{
|
let selectTypes = $derived([{
|
||||||
value: null,
|
value: null,
|
||||||
name: "None"
|
name: "None"
|
||||||
}, ...$schemTypes.map((type) => {
|
}, ...$schemTypes.map((type) => {
|
||||||
@ -57,9 +61,9 @@
|
|||||||
value: type.db,
|
value: type.db,
|
||||||
name: type.name
|
name: type.name
|
||||||
};
|
};
|
||||||
})];
|
})]);
|
||||||
|
|
||||||
$: changed = name !== event.name ||
|
let changed = $derived(name !== event.name ||
|
||||||
deadlineDate.diff(dayjs(event.deadline)) !== 0 ||
|
deadlineDate.diff(dayjs(event.deadline)) !== 0 ||
|
||||||
startDate.diff(dayjs(event.start)) !== 0 ||
|
startDate.diff(dayjs(event.start)) !== 0 ||
|
||||||
endDate.diff(dayjs(event.end)) !== 0 ||
|
endDate.diff(dayjs(event.end)) !== 0 ||
|
||||||
@ -67,7 +71,7 @@
|
|||||||
schemType != event.schemType ||
|
schemType != event.schemType ||
|
||||||
publicOnly !== event.publicSchemsOnly ||
|
publicOnly !== event.publicSchemsOnly ||
|
||||||
addReferee.length > 0 ||
|
addReferee.length > 0 ||
|
||||||
removeReferee.length > 0;
|
removeReferee.length > 0);
|
||||||
|
|
||||||
|
|
||||||
async function del() {
|
async function del() {
|
||||||
@ -80,7 +84,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let successToast: boolean = false;
|
let successToast: boolean = $state(false);
|
||||||
|
|
||||||
async function update() {
|
async function update() {
|
||||||
let ev: UpdateEvent = {
|
let ev: UpdateEvent = {
|
||||||
@ -117,20 +121,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<Label for="event-deadline">Deadline</Label>
|
<Label for="event-deadline">Deadline</Label>
|
||||||
<Input id="event-deadline" bind:value={name} class="w-80" let:props size="lg">
|
<Input id="event-deadline" bind:value={name} class="w-80" size="lg">
|
||||||
|
{#snippet children({ props })}
|
||||||
<input type="datetime-local" {...props} bind:value={deadline}/>
|
<input type="datetime-local" {...props} bind:value={deadline}/>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<Label for="event-start">Start</Label>
|
<Label for="event-start">Start</Label>
|
||||||
<Input id="event-start" bind:value={name} class="w-80" let:props size="lg">
|
<Input id="event-start" bind:value={name} class="w-80" size="lg">
|
||||||
|
{#snippet children({ props })}
|
||||||
<input type="datetime-local" {...props} bind:value={start}/>
|
<input type="datetime-local" {...props} bind:value={start}/>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<Label for="event-end">End</Label>
|
<Label for="event-end">End</Label>
|
||||||
<Input id="event-end" bind:value={name} class="w-80" let:props size="lg">
|
<Input id="event-end" bind:value={name} class="w-80" size="lg">
|
||||||
|
{#snippet children({ props })}
|
||||||
<input type="datetime-local" {...props} bind:value={end}/>
|
<input type="datetime-local" {...props} bind:value={end}/>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
@ -159,6 +169,8 @@
|
|||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Toast bind:open={successToast} position="bottom-left" color="green">
|
<Toast bind:open={successToast} position="bottom-left" color="green">
|
||||||
<CheckCircleOutline slot="icon"/>
|
{#snippet icon()}
|
||||||
|
<CheckCircleOutline />
|
||||||
|
{/snippet}
|
||||||
Updated Successfully
|
Updated Successfully
|
||||||
</Toast>
|
</Toast>
|
||||||
|
@ -26,14 +26,24 @@
|
|||||||
import {isWide} from "@stores/stores.ts";
|
import {isWide} from "@stores/stores.ts";
|
||||||
import {fightRepo} from "@repo/fight.ts";
|
import {fightRepo} from "@repo/fight.ts";
|
||||||
|
|
||||||
export let fight: EventFight;
|
interface Props {
|
||||||
export let data: ExtendedEvent;
|
fight: EventFight;
|
||||||
export let i: number;
|
data: ExtendedEvent;
|
||||||
export let selected: boolean = false;
|
i: number;
|
||||||
export let hideEdit: boolean = false;
|
selected?: boolean;
|
||||||
|
hideEdit?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
let deleteOpen = false;
|
let {
|
||||||
let editOpen = false;
|
fight,
|
||||||
|
data = $bindable(),
|
||||||
|
i,
|
||||||
|
selected = false,
|
||||||
|
hideEdit = false
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
let deleteOpen = $state(false);
|
||||||
|
let editOpen = $state(false);
|
||||||
|
|
||||||
let dispatcher = createEventDispatcher();
|
let dispatcher = createEventDispatcher();
|
||||||
|
|
||||||
@ -52,7 +62,7 @@
|
|||||||
</script>
|
</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"
|
<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"
|
onclick={dispatchSelect} onkeypress={dispatchSelect} role="checkbox" aria-checked={selected} tabindex="0"
|
||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
@ -31,8 +31,8 @@
|
|||||||
Tooltip
|
Tooltip
|
||||||
} from "flowbite-svelte";
|
} from "flowbite-svelte";
|
||||||
import {
|
import {
|
||||||
ArrowsRepeatSolid, CalendarWeekOutline,
|
ArrowsRepeatOutline, CalendarWeekOutline,
|
||||||
PlusSolid, ProfileCardOutline, TrashBinOutline, UsersGroupOutline,
|
PlusOutline, ProfileCardOutline, TrashBinOutline, UsersGroupOutline,
|
||||||
} from "flowbite-svelte-icons";
|
} from "flowbite-svelte-icons";
|
||||||
import FightCard from "./FightCard.svelte";
|
import FightCard from "./FightCard.svelte";
|
||||||
import CreateFightModal from "./modals/CreateFightModal.svelte";
|
import CreateFightModal from "./modals/CreateFightModal.svelte";
|
||||||
@ -44,19 +44,23 @@
|
|||||||
|
|
||||||
dayjs.extend(duration);
|
dayjs.extend(duration);
|
||||||
|
|
||||||
export let data: ExtendedEvent;
|
interface Props {
|
||||||
|
data: ExtendedEvent;
|
||||||
|
}
|
||||||
|
|
||||||
let createOpen = false;
|
let { data = $bindable() }: Props = $props();
|
||||||
let fights = data.fights;
|
|
||||||
let selectedFights: Set<EventFight> = new Set();
|
|
||||||
|
|
||||||
$: groupsMap = new Set(fights.map(fight => fight.group));
|
let createOpen = $state(false);
|
||||||
$: groupedFights = Array.from(groupsMap).map(group => {
|
let fights = $state(data.fights);
|
||||||
|
let selectedFights: Set<EventFight> = $state(new Set());
|
||||||
|
|
||||||
|
let groupsMap = $derived(new Set(fights.map(fight => fight.group)));
|
||||||
|
let groupedFights = $derived(Array.from(groupsMap).map(group => {
|
||||||
return {
|
return {
|
||||||
group: group,
|
group: group,
|
||||||
fights: fights.filter(fight => fight.group === group)
|
fights: fights.filter(fight => fight.group === group)
|
||||||
};
|
};
|
||||||
});
|
}));
|
||||||
|
|
||||||
function cycleSelect() {
|
function cycleSelect() {
|
||||||
if (selectedFights.size === fights.length) {
|
if (selectedFights.size === fights.length) {
|
||||||
@ -81,7 +85,7 @@
|
|||||||
selectedFights = selectedFights;
|
selectedFights = selectedFights;
|
||||||
}
|
}
|
||||||
|
|
||||||
let deleteOpen = false;
|
let deleteOpen = $state(false);
|
||||||
|
|
||||||
async function deleteFights() {
|
async function deleteFights() {
|
||||||
for (const fight of selectedFights) {
|
for (const fight of selectedFights) {
|
||||||
@ -92,14 +96,14 @@
|
|||||||
deleteOpen = false;
|
deleteOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let spectatePortOpen = false;
|
let spectatePortOpen = $state(false);
|
||||||
$: selectPlayers = $players.map(player => {
|
let selectPlayers = $derived($players.map(player => {
|
||||||
return {
|
return {
|
||||||
name: player.name,
|
name: player.name,
|
||||||
value: player.uuid
|
value: player.uuid
|
||||||
};
|
};
|
||||||
}).sort((a, b) => a.name.localeCompare(b.name));
|
}).sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
let spectatePort = "";
|
let spectatePort = $state("");
|
||||||
|
|
||||||
async function updateSpectatePort() {
|
async function updateSpectatePort() {
|
||||||
for (const fight of selectedFights) {
|
for (const fight of selectedFights) {
|
||||||
@ -120,11 +124,11 @@
|
|||||||
spectatePortOpen = false;
|
spectatePortOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let groupChangeOpen = false;
|
let groupChangeOpen = $state(false);
|
||||||
let group = "";
|
let group = $state("");
|
||||||
let groupSearch = "";
|
let groupSearch = $state("");
|
||||||
|
|
||||||
$: selectableGroups = [{
|
let selectableGroups = $derived([{
|
||||||
name: "Keine",
|
name: "Keine",
|
||||||
value: ""
|
value: ""
|
||||||
}, {
|
}, {
|
||||||
@ -135,7 +139,7 @@
|
|||||||
name: group,
|
name: group,
|
||||||
value: group
|
value: group
|
||||||
};
|
};
|
||||||
}).sort((a, b) => a.name.localeCompare(b.name))];
|
}).sort((a, b) => a.name.localeCompare(b.name))]);
|
||||||
|
|
||||||
async function updateGroup() {
|
async function updateGroup() {
|
||||||
for (const fight of selectedFights) {
|
for (const fight of selectedFights) {
|
||||||
@ -157,11 +161,11 @@
|
|||||||
groupChangeOpen = false;
|
groupChangeOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: minTime = dayjs(Math.min(...fights.map(fight => fight.start))).utc(true);
|
let minTime = $derived(dayjs(Math.min(...fights.map(fight => fight.start))).utc(true));
|
||||||
let changeTimeOpen = false;
|
let changeTimeOpen = $state(false);
|
||||||
let changedTime = fights.length != 0 ? dayjs(Math.min(...fights.map(fight => fight.start)))?.utc(true)?.toISOString()?.slice(0, -1) : undefined;
|
let changedTime = $state(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));
|
let deltaTime = $derived(dayjs.duration(dayjs(changedTime).utc(true).diff(minTime)));
|
||||||
|
|
||||||
async function updateStartTime() {
|
async function updateStartTime() {
|
||||||
for (const fight of selectedFights) {
|
for (const fight of selectedFights) {
|
||||||
@ -243,10 +247,12 @@
|
|||||||
|
|
||||||
<Modal bind:open={deleteOpen} title="Delete {selectedFights.size} Fights" autoclose size="sm">
|
<Modal bind:open={deleteOpen} title="Delete {selectedFights.size} Fights" autoclose size="sm">
|
||||||
<p>Are you sure you want to delete {selectedFights.size} fights?</p>
|
<p>Are you sure you want to delete {selectedFights.size} fights?</p>
|
||||||
<svelte:fragment slot="footer">
|
{#snippet footer()}
|
||||||
|
|
||||||
<Button color="red" class="ml-auto" on:click={deleteFights}>Delete</Button>
|
<Button color="red" class="ml-auto" on:click={deleteFights}>Delete</Button>
|
||||||
<Button on:click={() => deleteOpen = false} color="alternative">Cancel</Button>
|
<Button on:click={() => deleteOpen = false} color="alternative">Cancel</Button>
|
||||||
</svelte:fragment>
|
|
||||||
|
{/snippet}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal bind:open={spectatePortOpen} title="Change Kampfleiter" size="sm">
|
<Modal bind:open={spectatePortOpen} title="Change Kampfleiter" size="sm">
|
||||||
@ -254,10 +260,12 @@
|
|||||||
<Label for="fight-kampf">Kampfleiter</Label>
|
<Label for="fight-kampf">Kampfleiter</Label>
|
||||||
<TypeAheadSearch items={selectPlayers} bind:selected={spectatePort}></TypeAheadSearch>
|
<TypeAheadSearch items={selectPlayers} bind:selected={spectatePort}></TypeAheadSearch>
|
||||||
</div>
|
</div>
|
||||||
<svelte:fragment slot="footer">
|
{#snippet footer()}
|
||||||
|
|
||||||
<Button class="ml-auto" on:click={updateSpectatePort}>Change</Button>
|
<Button class="ml-auto" on:click={updateSpectatePort}>Change</Button>
|
||||||
<Button on:click={() => spectatePortOpen = false} color="alternative">Cancel</Button>
|
<Button on:click={() => spectatePortOpen = false} color="alternative">Cancel</Button>
|
||||||
</svelte:fragment>
|
|
||||||
|
{/snippet}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal bind:open={groupChangeOpen} title="Change Group" size="sm">
|
<Modal bind:open={groupChangeOpen} title="Change Group" size="sm">
|
||||||
@ -266,32 +274,38 @@
|
|||||||
<TypeAheadSearch items={selectableGroups} bind:selected={group} bind:searchValue={groupSearch}
|
<TypeAheadSearch items={selectableGroups} bind:selected={group} bind:searchValue={groupSearch}
|
||||||
all></TypeAheadSearch>
|
all></TypeAheadSearch>
|
||||||
</div>
|
</div>
|
||||||
<svelte:fragment slot="footer">
|
{#snippet footer()}
|
||||||
|
|
||||||
<Button class="ml-auto" on:click={updateGroup}>Change</Button>
|
<Button class="ml-auto" on:click={updateGroup}>Change</Button>
|
||||||
<Button on:click={() => groupChangeOpen = false} color="alternative">Cancel</Button>
|
<Button on:click={() => groupChangeOpen = false} color="alternative">Cancel</Button>
|
||||||
</svelte:fragment>
|
|
||||||
|
{/snippet}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal bind:open={changeTimeOpen} title="Change Start Time" size="sm">
|
<Modal bind:open={changeTimeOpen} title="Change Start Time" size="sm">
|
||||||
<div class="m-2">
|
<div class="m-2">
|
||||||
<Label for="fight-start">New Start Time:</Label>
|
<Label for="fight-start">New Start Time:</Label>
|
||||||
<Input id="fight-start" bind:value={changedTime} let:props>
|
<Input id="fight-start" bind:value={changedTime} >
|
||||||
|
{#snippet children({ props })}
|
||||||
<input type="datetime-local" {...props} bind:value={changedTime}/>
|
<input type="datetime-local" {...props} bind:value={changedTime}/>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
<p>{deltaTime.asMilliseconds() < 0 ? '' : '+'}{("0" + deltaTime.hours()).slice(-2)}
|
<p>{deltaTime.asMilliseconds() < 0 ? '' : '+'}{("0" + deltaTime.hours()).slice(-2)}
|
||||||
:{("0" + deltaTime.minutes()).slice(-2)}</p>
|
:{("0" + deltaTime.minutes()).slice(-2)}</p>
|
||||||
<svelte:fragment slot="footer">
|
{#snippet footer()}
|
||||||
|
|
||||||
<Button class="ml-auto" on:click={updateStartTime}>Update</Button>
|
<Button class="ml-auto" on:click={updateStartTime}>Update</Button>
|
||||||
<Button on:click={() => changeTimeOpen = false} color="alternative">Cancel</Button>
|
<Button on:click={() => changeTimeOpen = false} color="alternative">Cancel</Button>
|
||||||
</svelte:fragment>
|
|
||||||
|
{/snippet}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<SpeedDial>
|
<SpeedDial>
|
||||||
<SpeedDialButton name="Add" on:click={() => createOpen = true}>
|
<SpeedDialButton name="Add" on:click={() => createOpen = true}>
|
||||||
<PlusSolid/>
|
<PlusOutline/>
|
||||||
</SpeedDialButton>
|
</SpeedDialButton>
|
||||||
<SpeedDialButton name="Generate" href="#/event/{data.event.id}/generate">
|
<SpeedDialButton name="Generate" href="#/event/{data.event.id}/generate">
|
||||||
<ArrowsRepeatSolid/>
|
<ArrowsRepeatOutline/>
|
||||||
</SpeedDialButton>
|
</SpeedDialButton>
|
||||||
</SpeedDial>
|
</SpeedDial>
|
||||||
|
@ -20,21 +20,25 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {ExtendedEvent} from "@type/event.ts";
|
import type {ExtendedEvent} from "@type/event.ts";
|
||||||
import {Button} from "flowbite-svelte";
|
import {Button} from "flowbite-svelte";
|
||||||
import {PlusSolid} from "flowbite-svelte-icons";
|
import {PlusOutline} from "flowbite-svelte-icons";
|
||||||
import SWModal from "@components/styled/SWModal.svelte";
|
import SWModal from "@components/styled/SWModal.svelte";
|
||||||
import SWButton from "@components/styled/SWButton.svelte";
|
import SWButton from "@components/styled/SWButton.svelte";
|
||||||
import TypeAheadSearch from "@components/admin/components/TypeAheadSearch.svelte";
|
import TypeAheadSearch from "@components/admin/components/TypeAheadSearch.svelte";
|
||||||
import {players} from "@stores/stores.ts";
|
import {players} from "@stores/stores.ts";
|
||||||
import {eventRepo} from "@repo/event.ts";
|
import {eventRepo} from "@repo/event.ts";
|
||||||
|
|
||||||
export let data: ExtendedEvent;
|
interface Props {
|
||||||
|
data: ExtendedEvent;
|
||||||
|
}
|
||||||
|
|
||||||
let searchValue = "";
|
let { data }: Props = $props();
|
||||||
let selectedPlayer: string | null = null;
|
|
||||||
|
|
||||||
let referees = data.event.referees;
|
let searchValue = $state("");
|
||||||
|
let selectedPlayer: string | null = $state(null);
|
||||||
|
|
||||||
let showAdd = false;
|
let referees = $state(data.event.referees);
|
||||||
|
|
||||||
|
let showAdd = $state(false);
|
||||||
|
|
||||||
async function addReferee() {
|
async function addReferee() {
|
||||||
if (selectedPlayer) {
|
if (selectedPlayer) {
|
||||||
@ -76,7 +80,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<Button class="fixed bottom-6 right-6 !p-4 z-10 shadow-lg" on:click={() => showAdd = true}>
|
<Button class="fixed bottom-6 right-6 !p-4 z-10 shadow-lg" on:click={() => showAdd = true}>
|
||||||
<PlusSolid/>
|
<PlusOutline/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<SWModal title="Schiedsrichter hinzufügen" bind:open={showAdd}>
|
<SWModal title="Schiedsrichter hinzufügen" bind:open={showAdd}>
|
||||||
@ -86,10 +90,12 @@
|
|||||||
items={$players.map(v => ({ name: v.name, value: v.uuid }))}/>
|
items={$players.map(v => ({ name: v.name, value: v.uuid }))}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div slot="footer" class="flex flex-grow justify-end">
|
{#snippet footer()}
|
||||||
|
<div class="flex flex-grow justify-end">
|
||||||
<SWButton on:click={reset} type="gray">Abbrechen</SWButton>
|
<SWButton on:click={reset} type="gray">Abbrechen</SWButton>
|
||||||
<SWButton on:click={addReferee}>Hinzufügen</SWButton>
|
<SWButton on:click={addReferee}>Hinzufügen</SWButton>
|
||||||
</div>
|
</div>
|
||||||
|
{/snippet}
|
||||||
</SWModal>
|
</SWModal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -21,7 +21,11 @@
|
|||||||
import {Avatar} from "flowbite-svelte";
|
import {Avatar} from "flowbite-svelte";
|
||||||
import type {ExtendedEvent} from "@type/event.ts";
|
import type {ExtendedEvent} from "@type/event.ts";
|
||||||
|
|
||||||
export let data: ExtendedEvent;
|
interface Props {
|
||||||
|
data: ExtendedEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { data }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
{#each data.teams as team (team.id)}
|
{#each data.teams as team (team.id)}
|
||||||
|
@ -28,25 +28,29 @@
|
|||||||
|
|
||||||
let dispatch = createEventDispatcher();
|
let dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let open: boolean = false;
|
interface Props {
|
||||||
export let data: ExtendedEvent;
|
open?: boolean;
|
||||||
|
data: ExtendedEvent;
|
||||||
|
}
|
||||||
|
|
||||||
let blueTeam: string = "";
|
let { open = $bindable(false), data }: Props = $props();
|
||||||
let redTeam: string = "";
|
|
||||||
|
|
||||||
let start: string = "";
|
let blueTeam: string = $state("");
|
||||||
|
let redTeam: string = $state("");
|
||||||
|
|
||||||
let gamemode: string = "";
|
let start: string = $state("");
|
||||||
let map: string = "";
|
|
||||||
|
|
||||||
let spectatePort: string | null = null;
|
let gamemode: string = $state("");
|
||||||
let group: string | null = null;
|
let map: string = $state("");
|
||||||
let groupSearch = "";
|
|
||||||
|
|
||||||
let errorOpen = false;
|
let spectatePort: string | null = $state(null);
|
||||||
let error: any = undefined;
|
let group: string | null = $state(null);
|
||||||
|
let groupSearch = $state("");
|
||||||
|
|
||||||
$: canCreate = blueTeam !== "" && redTeam !== "" && start !== "" && gamemode !== "" && map !== "";
|
let errorOpen = $state(false);
|
||||||
|
let error: any = $state(undefined);
|
||||||
|
|
||||||
|
let canCreate = $derived(blueTeam !== "" && redTeam !== "" && start !== "" && gamemode !== "" && map !== "");
|
||||||
|
|
||||||
async function create() {
|
async function create() {
|
||||||
try {
|
try {
|
||||||
@ -96,10 +100,12 @@
|
|||||||
teams={data.teams}
|
teams={data.teams}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<svelte:fragment slot="footer">
|
{#snippet footer()}
|
||||||
|
|
||||||
<Button on:click={create} class="mr-auto" disabled={!canCreate}>Create</Button>
|
<Button on:click={create} class="mr-auto" disabled={!canCreate}>Create</Button>
|
||||||
<Button color="light" on:click={() => open = false}>Cancel</Button>
|
<Button color="light" on:click={() => open = false}>Cancel</Button>
|
||||||
</svelte:fragment>
|
|
||||||
|
{/snippet}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<ErrorModal bind:open={errorOpen} bind:error={error} on:close={() => errorOpen = false}/>
|
<ErrorModal bind:open={errorOpen} bind:error={error} on:close={() => errorOpen = false}/>
|
||||||
|
@ -29,21 +29,25 @@
|
|||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
export let fight: EventFight;
|
interface Props {
|
||||||
export let data: ExtendedEvent;
|
fight: EventFight;
|
||||||
export let open = false;
|
data: ExtendedEvent;
|
||||||
|
open?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
let redTeam = fight.redTeam.id.toString();
|
let { fight = $bindable(), data, open = $bindable(false) }: Props = $props();
|
||||||
let blueTeam = fight.blueTeam.id.toString();
|
|
||||||
let start = dayjs(fight.start).utc(true).toISOString().slice(0, -1);
|
|
||||||
let spectatePort = fight.spectatePort?.toString() ?? null;
|
|
||||||
let gamemode = fight.spielmodus;
|
|
||||||
let map = fight.map;
|
|
||||||
let group = fight.group;
|
|
||||||
let groupSearch = fight.group ?? "";
|
|
||||||
|
|
||||||
let errorOpen = false;
|
let redTeam = $state(fight.redTeam.id.toString());
|
||||||
let error: any = undefined;
|
let blueTeam = $state(fight.blueTeam.id.toString());
|
||||||
|
let start = $state(dayjs(fight.start).utc(true).toISOString().slice(0, -1));
|
||||||
|
let spectatePort = $state(fight.spectatePort?.toString() ?? null);
|
||||||
|
let gamemode = $state(fight.spielmodus);
|
||||||
|
let map = $state(fight.map);
|
||||||
|
let group = $state(fight.group);
|
||||||
|
let groupSearch = $state(fight.group ?? "");
|
||||||
|
|
||||||
|
let errorOpen = $state(false);
|
||||||
|
let error: any = $state(undefined);
|
||||||
|
|
||||||
let dispatch = createEventDispatcher();
|
let dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
@ -19,8 +19,13 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {createEventDispatcher} from "svelte";
|
import {createEventDispatcher} from "svelte";
|
||||||
|
interface Props {
|
||||||
|
children?: import('svelte').Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
let dragover = false;
|
let { children }: Props = $props();
|
||||||
|
|
||||||
|
let dragover = $state(false);
|
||||||
|
|
||||||
function handleDragOver(e) {
|
function handleDragOver(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -36,9 +41,9 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-56 bg-gray-800 p-4 rounded" class:border={dragover} class:m-px={!dragover} on:drop={handleDrop}
|
<div class="w-56 bg-gray-800 p-4 rounded" class:border={dragover} class:m-px={!dragover} ondrop={handleDrop}
|
||||||
on:dragover={handleDragOver} on:dragleave={() => dragover = false} role="none">
|
ondragover={handleDragOver} ondragleave={() => dragover = false} role="none">
|
||||||
<slot></slot>
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
import DragAcceptor from "./DragAcceptor.svelte";
|
import DragAcceptor from "./DragAcceptor.svelte";
|
||||||
import {Button, Input, Label, Modal, Range, Select} from "flowbite-svelte";
|
import {Button, Input, Label, Modal, Range, Select} from "flowbite-svelte";
|
||||||
import {gamemodes, maps} from "@stores/stores.ts";
|
import {gamemodes, maps} from "@stores/stores.ts";
|
||||||
import {PlusSolid} from "flowbite-svelte-icons";
|
import {PlusOutline} from "flowbite-svelte-icons";
|
||||||
import {replace} from "svelte-spa-router";
|
import {replace} from "svelte-spa-router";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {fightRepo} from "@repo/fight.ts";
|
import {fightRepo} from "@repo/fight.ts";
|
||||||
@ -33,11 +33,15 @@
|
|||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
|
|
||||||
export let data: ExtendedEvent;
|
interface Props {
|
||||||
$: teams = new Map<number, Team>(data.teams.map(team => [team.id, team]));
|
data: ExtendedEvent;
|
||||||
|
}
|
||||||
|
|
||||||
let groups: number[][] = [];
|
let { data }: Props = $props();
|
||||||
$: teamsNotInGroup = data.teams.filter(team => !groups.flat().includes(team.id));
|
let teams = $derived(new Map<number, Team>(data.teams.map(team => [team.id, team])));
|
||||||
|
|
||||||
|
let groups: number[][] = $state([]);
|
||||||
|
let teamsNotInGroup = $derived(data.teams.filter(team => !groups.flat().includes(team.id)));
|
||||||
|
|
||||||
function dragToNewGroup(event: CustomEvent<DragEvent>) {
|
function dragToNewGroup(event: CustomEvent<DragEvent>) {
|
||||||
event.detail.preventDefault();
|
event.detail.preventDefault();
|
||||||
@ -49,7 +53,7 @@
|
|||||||
ev.dataTransfer!.setData("team", team.id.toString());
|
ev.dataTransfer!.setData("team", team.id.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
let resetDragOver = false;
|
let resetDragOver = $state(false);
|
||||||
|
|
||||||
function resetDragOverEvent(ev: DragEvent) {
|
function resetDragOverEvent(ev: DragEvent) {
|
||||||
resetDragOver = true;
|
resetDragOver = true;
|
||||||
@ -69,31 +73,31 @@
|
|||||||
groups = groups.map((group, i) => i === groupIndex ? [...group.filter(value => value != teamId), teamId] : group.filter(value => value != teamId)).filter(group => group.length > 0);
|
groups = groups.map((group, i) => i === groupIndex ? [...group.filter(value => value != teamId), teamId] : group.filter(value => value != teamId)).filter(group => group.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let startTime = dayjs(data.event.start).utc(true).toISOString().slice(0, -1);
|
let startTime = $state(dayjs(data.event.start).utc(true).toISOString().slice(0, -1));
|
||||||
$: startMoment = dayjs(startTime);
|
let startMoment = $derived(dayjs(startTime));
|
||||||
let gamemode = "";
|
let gamemode = $state("");
|
||||||
let map = "";
|
let map = $state("");
|
||||||
|
|
||||||
$: selectableGamemodes = $gamemodes.map(gamemode => {
|
let selectableGamemodes = $derived($gamemodes.map(gamemode => {
|
||||||
return {
|
return {
|
||||||
name: gamemode,
|
name: gamemode,
|
||||||
value: gamemode
|
value: gamemode
|
||||||
};
|
};
|
||||||
}).sort((a, b) => a.name.localeCompare(b.name));
|
}).sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
|
|
||||||
$: mapsStore = maps(gamemode);
|
let mapsStore = $derived(maps(gamemode));
|
||||||
$: selectableMaps = $mapsStore.map(map => {
|
let selectableMaps = $derived($mapsStore.map(map => {
|
||||||
return {
|
return {
|
||||||
name: map,
|
name: map,
|
||||||
value: map
|
value: map
|
||||||
};
|
};
|
||||||
}).sort((a, b) => a.name.localeCompare(b.name));
|
}).sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
|
|
||||||
let roundTime = 30;
|
let roundTime = $state(30);
|
||||||
let startDelay = 30;
|
let startDelay = $state(30);
|
||||||
|
|
||||||
let showAutoGrouping = false;
|
let showAutoGrouping = $state(false);
|
||||||
let groupCount = Math.floor(data.teams.length / 2);
|
let groupCount = $state(Math.floor(data.teams.length / 2));
|
||||||
|
|
||||||
function createGroups() {
|
function createGroups() {
|
||||||
let teams = data.teams.map(team => team.id).sort(() => Math.random() - 0.5);
|
let teams = data.teams.map(team => team.id).sort(() => Math.random() - 0.5);
|
||||||
@ -134,9 +138,9 @@
|
|||||||
return groupFights;
|
return groupFights;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: groupsFights = generateGroups(groups);
|
let groupsFights = $derived(generateGroups(groups));
|
||||||
|
|
||||||
$: generateDisabled = groupsFights.length > 0 && groupsFights.every(value => value.every(value1 => value1.length > 0)) && gamemode !== "" && map !== "";
|
let generateDisabled = $derived(groupsFights.length > 0 && groupsFights.every(value => value.every(value1 => value1.length > 0)) && gamemode !== "" && map !== "");
|
||||||
|
|
||||||
async function generateFights() {
|
async function generateFights() {
|
||||||
groupsFights.forEach((group, i) => {
|
groupsFights.forEach((group, i) => {
|
||||||
@ -165,7 +169,7 @@
|
|||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<div id="reseter" class:border-white={resetDragOver}
|
<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"
|
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">
|
ondragover={resetDragOverEvent} ondragleave={() => resetDragOver = false} ondrop={dropReset} role="group">
|
||||||
{#each teamsNotInGroup as team (team.id)}
|
{#each teamsNotInGroup as team (team.id)}
|
||||||
<TeamChip {team} on:dragstart={ev => teamDragStart(ev, team)}/>
|
<TeamChip {team} on:dragstart={ev => teamDragStart(ev, team)}/>
|
||||||
{/each}
|
{/each}
|
||||||
@ -192,8 +196,10 @@
|
|||||||
|
|
||||||
<div class="m-4 border-b border-gray-700 pb-4">
|
<div class="m-4 border-b border-gray-700 pb-4">
|
||||||
<Label for="event-end">Start Time</Label>
|
<Label for="event-end">Start Time</Label>
|
||||||
<Input id="event-end" bind:value={startTime} class="w-80" let:props size="lg">
|
<Input id="event-end" bind:value={startTime} class="w-80" size="lg">
|
||||||
|
{#snippet children({ props })}
|
||||||
<input type="datetime-local" {...props} bind:value={startTime}/>
|
<input type="datetime-local" {...props} bind:value={startTime}/>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<Label for="event-roundtime">Round time: {roundTime}m</Label>
|
<Label for="event-roundtime">Round time: {roundTime}m</Label>
|
||||||
@ -233,17 +239,19 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button class="!p-4 fixed bottom-4 right-4" pill disabled={!generateDisabled} on:click={generateFights}>
|
<Button class="!p-4 fixed bottom-4 right-4" pill disabled={!generateDisabled} on:click={generateFights}>
|
||||||
<PlusSolid/>
|
<PlusOutline/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Modal bind:open={showAutoGrouping} outsideclose title="Auto Grouping" size="sm">
|
<Modal bind:open={showAutoGrouping} outsideclose title="Auto Grouping" size="sm">
|
||||||
<Label for="event-member">Groups: {groupCount}</Label>
|
<Label for="event-member">Groups: {groupCount}</Label>
|
||||||
<Range id="event-member" bind:value={groupCount} step="1" min="1" max={Math.floor(data.teams.length / 2)}/>
|
<Range id="event-member" bind:value={groupCount} step="1" min="1" max={Math.floor(data.teams.length / 2)}/>
|
||||||
|
|
||||||
<svelte:fragment slot="footer">
|
{#snippet footer()}
|
||||||
|
|
||||||
<Button class="ml-auto" on:click={createGroups}>Create</Button>
|
<Button class="ml-auto" on:click={createGroups}>Create</Button>
|
||||||
<Button color="alternative" on:click={() => showAutoGrouping = false}>Cancel</Button>
|
<Button color="alternative" on:click={() => showAutoGrouping = false}>Cancel</Button>
|
||||||
</svelte:fragment>
|
|
||||||
|
{/snippet}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -18,20 +18,27 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { createBubbler } from 'svelte/legacy';
|
||||||
|
|
||||||
|
const bubble = createBubbler();
|
||||||
import type {Team} from "@type/team.ts";
|
import type {Team} from "@type/team.ts";
|
||||||
import {brightness, colorFromTeam, lighten} from "../../util";
|
import {brightness, colorFromTeam, lighten} from "../../util";
|
||||||
|
|
||||||
export let team: Team;
|
interface Props {
|
||||||
|
team: Team;
|
||||||
|
}
|
||||||
|
|
||||||
let hover = false;
|
let { team }: Props = $props();
|
||||||
|
|
||||||
|
let hover = $state(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="rounded w-fit p-2 border-gray-600 border cursor-grab select-none m-1 flex place-items-center"
|
<div class="rounded w-fit p-2 border-gray-600 border cursor-grab select-none m-1 flex place-items-center"
|
||||||
style:background-color={hover ? lighten(colorFromTeam(team)) : colorFromTeam(team)}
|
style:background-color={hover ? lighten(colorFromTeam(team)) : colorFromTeam(team)}
|
||||||
class:text-black={brightness(colorFromTeam(team))} draggable="true"
|
class:text-black={brightness(colorFromTeam(team))} draggable="true"
|
||||||
on:dragstart
|
ondragstart={bubble('dragstart')}
|
||||||
on:mouseenter={() => hover = true}
|
onmouseenter={() => hover = true}
|
||||||
on:mouseleave={() => hover = false}
|
onmouseleave={() => hover = false}
|
||||||
role="figure">
|
role="figure">
|
||||||
<span>{team.name}</span>
|
<span>{team.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,19 +24,23 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {eventRepo} from "@repo/event.ts";
|
import {eventRepo} from "@repo/event.ts";
|
||||||
|
|
||||||
export let open = false;
|
interface Props {
|
||||||
|
open?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { open = $bindable(false) }: Props = $props();
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
let errorOpen = false;
|
let errorOpen = $state(false);
|
||||||
let error: any = undefined;
|
let error: any = $state(undefined);
|
||||||
|
|
||||||
let eventName = "";
|
let eventName = $state("");
|
||||||
let start = "";
|
let start = $state("");
|
||||||
$: startDate = dayjs(start);
|
let startDate = $derived(dayjs(start));
|
||||||
let end = "";
|
let end = $state("");
|
||||||
$: endDate = dayjs(end);
|
let endDate = $derived(dayjs(end));
|
||||||
|
|
||||||
$: canSubmit = eventName.length > 0 && startDate.isValid() && endDate.isValid() && startDate.isBefore(endDate);
|
let canSubmit = $derived(eventName.length > 0 && startDate.isValid() && endDate.isValid() && startDate.isBefore(endDate));
|
||||||
|
|
||||||
async function createEvent() {
|
async function createEvent() {
|
||||||
try {
|
try {
|
||||||
@ -69,21 +73,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w-2/3 m-2">
|
<div class="w-2/3 m-2">
|
||||||
<Label for="event-create-start">End</Label>
|
<Label for="event-create-start">End</Label>
|
||||||
<Input id="event-create-start" bind:value={start} let:props>
|
<Input id="event-create-start" bind:value={start} >
|
||||||
|
{#snippet children({ props })}
|
||||||
<input type="datetime-local" {...props} bind:value={start}/>
|
<input type="datetime-local" {...props} bind:value={start}/>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-2/3 m-2">
|
<div class="w-2/3 m-2">
|
||||||
<Label for="event-create-start">End</Label>
|
<Label for="event-create-start">End</Label>
|
||||||
<Input id="event-create-start" bind:value={end} let:props>
|
<Input id="event-create-start" bind:value={end} >
|
||||||
|
{#snippet children({ props })}
|
||||||
<input type="datetime-local" {...props} bind:value={end}/>
|
<input type="datetime-local" {...props} bind:value={end}/>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<svelte:fragment slot="footer">
|
{#snippet footer()}
|
||||||
|
|
||||||
<Button color="alternative" on:click={() => open = false} class="mr-auto">Cancel</Button>
|
<Button color="alternative" on:click={() => open = false} class="mr-auto">Cancel</Button>
|
||||||
<Button on:click={createEvent} disabled={!canSubmit}>Create</Button>
|
<Button on:click={createEvent} disabled={!canSubmit}>Create</Button>
|
||||||
</svelte:fragment>
|
|
||||||
|
{/snippet}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<ErrorModal bind:open={errorOpen} bind:error={error}/>
|
<ErrorModal bind:open={errorOpen} bind:error={error}/>
|
||||||
|
@ -22,9 +22,13 @@
|
|||||||
import {link} from "svelte-spa-router";
|
import {link} from "svelte-spa-router";
|
||||||
import type {ShortEvent} from "@type/event.ts";
|
import type {ShortEvent} from "@type/event.ts";
|
||||||
|
|
||||||
export let event: ShortEvent;
|
interface Props {
|
||||||
|
event: ShortEvent;
|
||||||
|
}
|
||||||
|
|
||||||
$: sameDate = new Intl.DateTimeFormat().format(event.start) === new Intl.DateTimeFormat().format(event.end);
|
let { event }: Props = $props();
|
||||||
|
|
||||||
|
let sameDate = $derived(new Intl.DateTimeFormat().format(event.start) === new Intl.DateTimeFormat().format(event.end));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a href="/event/{event.id}" use:link>
|
<a href="/event/{event.id}" use:link>
|
||||||
|
@ -27,8 +27,12 @@
|
|||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let schematicId: number;
|
interface Props {
|
||||||
export let user: Player;
|
schematicId: number;
|
||||||
|
user: Player;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { schematicId, user }: Props = $props();
|
||||||
|
|
||||||
let schemInfo = getSchematicInfo(schematicId);
|
let schemInfo = getSchematicInfo(schematicId);
|
||||||
|
|
||||||
@ -46,6 +50,8 @@
|
|||||||
{:catch e}
|
{:catch e}
|
||||||
<SWModal title="Error" open on:close={() => dispatch("reset")}>
|
<SWModal title="Error" open on:close={() => dispatch("reset")}>
|
||||||
<p>{e.message}</p>
|
<p>{e.message}</p>
|
||||||
<button class="btn !ml-auto" slot="footer" on:click={() => dispatch("reset")}>Close</button>
|
{#snippet footer()}
|
||||||
|
<button class="btn !ml-auto" onclick={() => dispatch("reset")}>Close</button>
|
||||||
|
{/snippet}
|
||||||
</SWModal>
|
</SWModal>
|
||||||
{/await}
|
{/await}
|
@ -28,8 +28,12 @@
|
|||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let info: SchematicInfo;
|
interface Props {
|
||||||
export let user: Player;
|
info: SchematicInfo;
|
||||||
|
user: Player;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { info, user }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SWModal title={info.schem.name} open on:close={() => dispatch("reset")}>
|
<SWModal title={info.schem.name} open on:close={() => dispatch("reset")}>
|
||||||
@ -63,5 +67,7 @@
|
|||||||
{#if info.members.length !== 0}
|
{#if info.members.length !== 0}
|
||||||
<p class="!mt-0">{t("dashboard.schematic.info.members", {members: info.members.map(value => value.name).join(", ")})}</p>
|
<p class="!mt-0">{t("dashboard.schematic.info.members", {members: info.members.map(value => value.name).join(", ")})}</p>
|
||||||
{/if}
|
{/if}
|
||||||
<button slot="footer" class="btn ml-auto" on:click={() => dispatch("reset")}>{t("dashboard.schematic.info.btn.close")}</button>
|
{#snippet footer()}
|
||||||
|
<button class="btn ml-auto" onclick={() => dispatch("reset")}>{t("dashboard.schematic.info.btn.close")}</button>
|
||||||
|
{/snippet}
|
||||||
</SWModal>
|
</SWModal>
|
@ -18,6 +18,8 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { preventDefault } from 'svelte/legacy';
|
||||||
|
|
||||||
import {t} from "astro-i18n";
|
import {t} from "astro-i18n";
|
||||||
import {
|
import {
|
||||||
ChevronDoubleRightOutline,
|
ChevronDoubleRightOutline,
|
||||||
@ -37,10 +39,14 @@
|
|||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let schematics: SchematicList;
|
interface Props {
|
||||||
export let user: Player;
|
schematics: SchematicList;
|
||||||
|
user: Player;
|
||||||
|
}
|
||||||
|
|
||||||
let infoModalId: number | null = null;
|
let { schematics, user }: Props = $props();
|
||||||
|
|
||||||
|
let infoModalId: number | null = $state(null);
|
||||||
|
|
||||||
function schemListClick(isDir: boolean, id: number) {
|
function schemListClick(isDir: boolean, id: number) {
|
||||||
if (isDir) {
|
if (isDir) {
|
||||||
@ -50,26 +56,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let page = 0;
|
let page = $state(0);
|
||||||
$: maxPage = Math.ceil(schematics.schematics.length / 15);
|
let maxPage = $derived(Math.ceil(schematics.schematics.length / 15));
|
||||||
$: pagedSchematics = schematics.schematics.slice(page * 15, (page + 1) * 15);
|
let pagedSchematics = $derived(schematics.schematics.slice(page * 15, (page + 1) * 15));
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<Breadcrumb navClass="py-4">
|
<Breadcrumb navClass="py-4">
|
||||||
<BreadcrumbItem home>
|
<BreadcrumbItem home>
|
||||||
<svelte:fragment slot="icon">
|
{#snippet icon()}
|
||||||
|
|
||||||
<HomeOutline class="w-6 h-6 mx-2 dark:text-white" />
|
<HomeOutline class="w-6 h-6 mx-2 dark:text-white" />
|
||||||
</svelte:fragment>
|
|
||||||
<span on:click={() => dispatch("reset")} class="hover:underline hover:cursor-pointer text-2xl">{t("dashboard.schematic.home")}</span>
|
{/snippet}
|
||||||
|
<span onclick={() => dispatch("reset")} class="hover:underline hover:cursor-pointer text-2xl">{t("dashboard.schematic.home")}</span>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
{#each schematics.breadcrumbs as bread (bread.id)}
|
{#each schematics.breadcrumbs as bread (bread.id)}
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<svelte:fragment slot="icon">
|
{#snippet icon()}
|
||||||
|
|
||||||
<ChevronDoubleRightOutline class="w-4 h-4 mx-2 dark:text-white" />
|
<ChevronDoubleRightOutline class="w-4 h-4 mx-2 dark:text-white" />
|
||||||
</svelte:fragment>
|
|
||||||
<span on:click={() => {
|
{/snippet}
|
||||||
|
<span onclick={() => {
|
||||||
page = 0;
|
page = 0;
|
||||||
dispatch("to", {id: bread.id});
|
dispatch("to", {id: bread.id});
|
||||||
}} class="hover:underline hover:cursor-pointer text-2xl">{bread.name}</span>
|
}} class="hover:underline hover:cursor-pointer text-2xl">{bread.name}</span>
|
||||||
@ -99,7 +109,7 @@
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
{#if schematics.breadcrumbs.length !== 0}
|
{#if schematics.breadcrumbs.length !== 0}
|
||||||
<tr on:click|preventDefault={() => {
|
<tr onclick={preventDefault(() => {
|
||||||
if (schematics.breadcrumbs.length === 1) {
|
if (schematics.breadcrumbs.length === 1) {
|
||||||
page = 0;
|
page = 0;
|
||||||
dispatch("reset")
|
dispatch("reset")
|
||||||
@ -107,7 +117,7 @@
|
|||||||
page = 0;
|
page = 0;
|
||||||
dispatch("to", {id: schematics.breadcrumbs[schematics.breadcrumbs.length - 2].id})
|
dispatch("to", {id: schematics.breadcrumbs[schematics.breadcrumbs.length - 2].id})
|
||||||
}
|
}
|
||||||
}}>
|
})}>
|
||||||
<th>
|
<th>
|
||||||
<FolderOutline />
|
<FolderOutline />
|
||||||
</th>
|
</th>
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { createBubbler, preventDefault } from 'svelte/legacy';
|
||||||
|
|
||||||
|
const bubble = createBubbler();
|
||||||
import {astroI18n, t} from "astro-i18n";
|
import {astroI18n, t} from "astro-i18n";
|
||||||
import {CheckSolid, FileOutline, FolderOutline, XCircleOutline} from "flowbite-svelte-icons";
|
import {CheckSolid, FileOutline, FolderOutline, XCircleOutline} from "flowbite-svelte-icons";
|
||||||
import type {Schematic} from "@type/schem.ts";
|
import type {Schematic} from "@type/schem.ts";
|
||||||
@ -27,11 +30,15 @@
|
|||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
export let schem: Schematic;
|
interface Props {
|
||||||
export let players: Record<number, Player>;
|
schem: Schematic;
|
||||||
|
players: Record<number, Player>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { schem, players }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<tr on:click|preventDefault>
|
<tr onclick={preventDefault(bubble('click'))}>
|
||||||
<th>
|
<th>
|
||||||
{#if schem.type == null}
|
{#if schem.type == null}
|
||||||
<FolderOutline />
|
<FolderOutline />
|
||||||
|
@ -22,7 +22,11 @@
|
|||||||
import {astroI18n, t} from "astro-i18n"
|
import {astroI18n, t} from "astro-i18n"
|
||||||
import {statsRepo} from "@repo/stats.ts";
|
import {statsRepo} from "@repo/stats.ts";
|
||||||
|
|
||||||
export let user: Player;
|
interface Props {
|
||||||
|
user: Player;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { user }: Props = $props();
|
||||||
|
|
||||||
let request = getRequest();
|
let request = getRequest();
|
||||||
|
|
||||||
|
@ -25,7 +25,11 @@
|
|||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let open = false;
|
interface Props {
|
||||||
|
open?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { open = $bindable(false) }: Props = $props();
|
||||||
|
|
||||||
async function upload() {
|
async function upload() {
|
||||||
if (uploadFile == null) {
|
if (uploadFile == null) {
|
||||||
@ -53,15 +57,17 @@
|
|||||||
dispatch("reset")
|
dispatch("reset")
|
||||||
}
|
}
|
||||||
|
|
||||||
let uploadFile: FileList | null = null;
|
let uploadFile: FileList | null = $state(null);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SWModal title={t("dashboard.schematic.title")} bind:open>
|
<SWModal title={t("dashboard.schematic.title")} bind:open>
|
||||||
<form>
|
<form>
|
||||||
<input type="file" bind:files={uploadFile} />
|
<input type="file" bind:files={uploadFile} />
|
||||||
</form>
|
</form>
|
||||||
<svelte:fragment slot="footer">
|
{#snippet footer()}
|
||||||
<button class="btn !ml-auto" on:click={upload}>{t("dashboard.schematic.upload")}</button>
|
|
||||||
<button class="btn btn-gray" on:click={() => open = false}>{t("dashboard.schematic.cancel")}</button>
|
<button class="btn !ml-auto" onclick={upload}>{t("dashboard.schematic.upload")}</button>
|
||||||
</svelte:fragment>
|
<button class="btn btn-gray" onclick={() => open = false}>{t("dashboard.schematic.cancel")}</button>
|
||||||
|
|
||||||
|
{/snippet}
|
||||||
</SWModal>
|
</SWModal>
|
@ -24,11 +24,13 @@
|
|||||||
import Statistics from "./Statistics.svelte";
|
import Statistics from "./Statistics.svelte";
|
||||||
import {authRepo} from "@repo/auth.ts";
|
import {authRepo} from "@repo/auth.ts";
|
||||||
import {tokenStore} from "@repo/repo.ts";
|
import {tokenStore} from "@repo/repo.ts";
|
||||||
import SWModal from "@components/styled/SWModal.svelte";
|
|
||||||
import SWButton from "@components/styled/SWButton.svelte";
|
|
||||||
import Card from "@components/Card.svelte";
|
import Card from "@components/Card.svelte";
|
||||||
|
|
||||||
export let user: Player;
|
interface Props {
|
||||||
|
user: Player;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { user }: Props = $props();
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
await $authRepo.logout()
|
await $authRepo.logout()
|
||||||
@ -46,7 +48,7 @@
|
|||||||
</figure>
|
</figure>
|
||||||
</Card>
|
</Card>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<button class="btn mt-2" on:click={logout}>{t("dashboard.buttons.logout")}</button>
|
<button class="btn mt-2" onclick={logout}>{t("dashboard.buttons.logout")}</button>
|
||||||
{#if user.perms.includes("MODERATION")}
|
{#if user.perms.includes("MODERATION")}
|
||||||
<a class="btn w-fit mt-2" href="/admin" data-astro-reload>{t("dashboard.buttons.admin")}</a>
|
<a class="btn w-fit mt-2" href="/admin" data-astro-reload>{t("dashboard.buttons.admin")}</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -24,15 +24,27 @@
|
|||||||
import {onDestroy, onMount} from "svelte";
|
import {onDestroy, onMount} from "svelte";
|
||||||
import { CollectionEntry } from "astro:content";
|
import { CollectionEntry } from "astro:content";
|
||||||
|
|
||||||
export let pub: CollectionEntry<"publics">;
|
interface Props {
|
||||||
export let fov: number = 60;
|
pub: CollectionEntry<"publics">;
|
||||||
export let near: number = 1
|
fov?: number;
|
||||||
export let far: number = 1000;
|
near?: number;
|
||||||
export let distance: number = 100;
|
far?: number;
|
||||||
|
distance?: number;
|
||||||
|
children?: import('svelte').Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
let loaded = false;
|
let {
|
||||||
|
pub,
|
||||||
|
fov = 60,
|
||||||
|
near = 1,
|
||||||
|
far = 1000,
|
||||||
|
distance = 100,
|
||||||
|
children
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
let div: HTMLDivElement;
|
let loaded = $state(false);
|
||||||
|
|
||||||
|
let div: HTMLDivElement = $state();
|
||||||
let scene: THREE.Scene;
|
let scene: THREE.Scene;
|
||||||
let camera: THREE.PerspectiveCamera;
|
let camera: THREE.PerspectiveCamera;
|
||||||
let renderer: THREE.WebGLRenderer;
|
let renderer: THREE.WebGLRenderer;
|
||||||
@ -126,6 +138,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full h-full flex justify-center absolute top-0 left-0 right-0 bottom-0" class:hidden={loaded}>
|
<div class="w-full h-full flex justify-center absolute top-0 left-0 right-0 bottom-0" class:hidden={loaded}>
|
||||||
<slot></slot>
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
<!-- @migration-task Error while migrating Svelte code: This migration would change the name of a slot making the component unusable -->
|
||||||
<!--
|
<!--
|
||||||
- This file is a part of the SteamWar software.
|
- This file is a part of the SteamWar software.
|
||||||
-
|
-
|
||||||
|
@ -18,11 +18,19 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { createBubbler } from 'svelte/legacy';
|
||||||
|
|
||||||
|
const bubble = createBubbler();
|
||||||
import "@styles/button.css"
|
import "@styles/button.css"
|
||||||
|
|
||||||
export let type: "primary" | "ghost" | "gray" = "primary";
|
interface Props {
|
||||||
|
type?: "primary" | "ghost" | "gray";
|
||||||
|
children?: import('svelte').Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { type = "primary", children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button on:click class="btn" class:btn-gray={type === "gray"} class:btn-text={type === "ghost"}>
|
<button onclick={bubble('click')} class="btn" class:btn-gray={type === "gray"} class:btn-text={type === "ghost"}>
|
||||||
<slot />
|
{@render children?.()}
|
||||||
</button>
|
</button>
|
@ -18,23 +18,30 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { run, createBubbler, stopPropagation } from 'svelte/legacy';
|
||||||
|
|
||||||
|
const bubble = createBubbler();
|
||||||
import {createEventDispatcher, onMount} from "svelte";
|
import {createEventDispatcher, onMount} from "svelte";
|
||||||
|
|
||||||
export let title: string;
|
interface Props {
|
||||||
export let open: boolean;
|
title: string;
|
||||||
let internalOpen = open;
|
open: boolean;
|
||||||
|
children?: import('svelte').Snippet;
|
||||||
|
footer?: import('svelte').Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
title,
|
||||||
|
open = $bindable(),
|
||||||
|
children,
|
||||||
|
footer
|
||||||
|
}: Props = $props();
|
||||||
|
let internalOpen = $state(open);
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
$: if (open && !internalOpen) {
|
|
||||||
dialog.showModal();
|
|
||||||
internalOpen = true;
|
|
||||||
} else if (!open && internalOpen) {
|
|
||||||
dialog.close();
|
|
||||||
internalOpen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dialog: HTMLDialogElement;
|
let dialog: HTMLDialogElement = $state();
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
@ -48,18 +55,27 @@
|
|||||||
internalOpen = false;
|
internalOpen = false;
|
||||||
dispatch("close");
|
dispatch("close");
|
||||||
}
|
}
|
||||||
|
run(() => {
|
||||||
|
if (open && !internalOpen) {
|
||||||
|
dialog.showModal();
|
||||||
|
internalOpen = true;
|
||||||
|
} else if (!open && internalOpen) {
|
||||||
|
dialog.close();
|
||||||
|
internalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<dialog bind:this={dialog} on:close={close} on:cancel={close} on:click={() => dialog.close()} aria-hidden="true">
|
<dialog bind:this={dialog} onclose={close} oncancel={close} onclick={() => dialog.close()} aria-hidden="true">
|
||||||
<div on:click|stopPropagation aria-hidden="true">
|
<div onclick={stopPropagation(bubble('click'))} aria-hidden="true">
|
||||||
<div class="spaced bordered">
|
<div class="spaced bordered">
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="spaced main bordered">
|
<div class="spaced main bordered">
|
||||||
<slot />
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
<div class="footer spaced" on:click={() => dialog.close()} aria-hidden="true">
|
<div class="footer spaced" onclick={() => dialog.close()} aria-hidden="true">
|
||||||
<slot name="footer" />
|
{@render footer?.()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { preventDefault } from 'svelte/legacy';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChevronDoubleLeftOutline,
|
ChevronDoubleLeftOutline,
|
||||||
ChevronDoubleRightOutline,
|
ChevronDoubleRightOutline,
|
||||||
@ -25,23 +27,27 @@
|
|||||||
ChevronRightOutline,
|
ChevronRightOutline,
|
||||||
} from "flowbite-svelte-icons";
|
} from "flowbite-svelte-icons";
|
||||||
|
|
||||||
export let page: number = 0;
|
|
||||||
export let maxPage: number;
|
|
||||||
|
|
||||||
$: pages = new Array(maxPage).fill(0)
|
|
||||||
.map((_, i) => i + 1)
|
|
||||||
//.slice(Math.max(page - 2, 0) - Math.abs(Math.max(page + 3 - maxPage, 0)), Math.min(page + 3, maxPage) + Math.abs(Math.min(page - 2, 0)))
|
|
||||||
.map(i => ({
|
|
||||||
name: i.toString(),
|
|
||||||
active: i === page + 1,
|
|
||||||
i: i - 1
|
|
||||||
}));
|
|
||||||
|
|
||||||
export let firstUrl: string = "#";
|
interface Props {
|
||||||
export let lastUrl: string = "#";
|
page?: number;
|
||||||
export let previousUrl: string = "#";
|
maxPage: number;
|
||||||
export let nextUrl: string = "#";
|
firstUrl?: string;
|
||||||
export let pagesUrl: (i: number) => string = () => "#";
|
lastUrl?: string;
|
||||||
|
previousUrl?: string;
|
||||||
|
nextUrl?: string;
|
||||||
|
pagesUrl?: (i: number) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
page = $bindable(0),
|
||||||
|
maxPage,
|
||||||
|
firstUrl = "#",
|
||||||
|
lastUrl = "#",
|
||||||
|
previousUrl = "#",
|
||||||
|
nextUrl = "#",
|
||||||
|
pagesUrl = () => "#"
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
const previous = () => {
|
const previous = () => {
|
||||||
page = Math.max(page - 1, 0);
|
page = Math.max(page - 1, 0);
|
||||||
@ -50,37 +56,45 @@
|
|||||||
const next = () => {
|
const next = () => {
|
||||||
page = Math.min(page + 1, maxPage - 1);
|
page = Math.min(page + 1, maxPage - 1);
|
||||||
};
|
};
|
||||||
|
let pages = $derived(new Array(maxPage).fill(0)
|
||||||
|
.map((_, i) => i + 1)
|
||||||
|
//.slice(Math.max(page - 2, 0) - Math.abs(Math.max(page + 3 - maxPage, 0)), Math.min(page + 3, maxPage) + Math.abs(Math.min(page - 2, 0)))
|
||||||
|
.map(i => ({
|
||||||
|
name: i.toString(),
|
||||||
|
active: i === page + 1,
|
||||||
|
i: i - 1
|
||||||
|
})));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full flex justify-center mt-4">
|
<div class="w-full flex justify-center mt-4">
|
||||||
<ul class="inline-flex flex-wrap">
|
<ul class="inline-flex flex-wrap">
|
||||||
<li>
|
<li>
|
||||||
<a href={firstUrl} on:click|preventDefault={() => page = 0} class="btn btn-neutral h-8 px-3 text-sm flex items-center !m-0 !rounded-r-none">
|
<a href={firstUrl} onclick={preventDefault(() => page = 0)} class="btn btn-neutral h-8 px-3 text-sm flex items-center !m-0 !rounded-r-none">
|
||||||
<span class="sr-only">Next</span>
|
<span class="sr-only">Next</span>
|
||||||
<ChevronDoubleLeftOutline class="w-3 h-3" />
|
<ChevronDoubleLeftOutline class="w-3 h-3" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href={previousUrl} on:click|preventDefault={previous} class="btn btn-neutral h-8 px-3 text-sm flex items-center !m-0 !rounded-none">
|
<a href={previousUrl} onclick={preventDefault(previous)} class="btn btn-neutral h-8 px-3 text-sm flex items-center !m-0 !rounded-none">
|
||||||
<span class="sr-only">Previous</span>
|
<span class="sr-only">Previous</span>
|
||||||
<ChevronLeftOutline class="w-3 h-3" />
|
<ChevronLeftOutline class="w-3 h-3" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{#each pages as p}
|
{#each pages as p}
|
||||||
<li>
|
<li>
|
||||||
<a href={pagesUrl(p.i)} on:click|preventDefault={() => page = p.i} class="btn h-8 px-3 text-sm flex items-center !m-0 !rounded-none" class:btn-neutral={!p.active}>
|
<a href={pagesUrl(p.i)} onclick={preventDefault(() => page = p.i)} class="btn h-8 px-3 text-sm flex items-center !m-0 !rounded-none" class:btn-neutral={!p.active}>
|
||||||
<span>{p.name}</span>
|
<span>{p.name}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
<li>
|
<li>
|
||||||
<a href={nextUrl} on:click|preventDefault={next} class="btn btn-neutral h-8 px-3 text-sm flex items-center !m-0 !rounded-none">
|
<a href={nextUrl} onclick={preventDefault(next)} class="btn btn-neutral h-8 px-3 text-sm flex items-center !m-0 !rounded-none">
|
||||||
<span class="sr-only">Next</span>
|
<span class="sr-only">Next</span>
|
||||||
<ChevronRightOutline class="w-3 h-3" />
|
<ChevronRightOutline class="w-3 h-3" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href={lastUrl} on:click|preventDefault={() => page = maxPage - 1} class="btn btn-neutral h-8 px-3 text-sm flex items-center !m-0 !rounded-l-none">
|
<a href={lastUrl} onclick={preventDefault(() => page = maxPage - 1)} class="btn btn-neutral h-8 px-3 text-sm flex items-center !m-0 !rounded-l-none">
|
||||||
<span class="sr-only">Next</span>
|
<span class="sr-only">Next</span>
|
||||||
<ChevronDoubleRightOutline class="w-3 h-3" />
|
<ChevronDoubleRightOutline class="w-3 h-3" />
|
||||||
</a>
|
</a>
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren