This commit is contained in:
2024-11-24 22:57:21 +01:00
parent bbf13cf203
commit 72933a46d1
48 changed files with 752 additions and 450 deletions

View File

@@ -20,13 +20,19 @@
<script lang="ts">
import {Button, Modal} from "flowbite-svelte";
export let open: boolean = false;
export let error: Error | undefined;
interface Props {
open: boolean;
error: Error | undefined;
}
let { open = $bindable(), error = $bindable() }: Props = $props();
</script>
{#if (error instanceof Error)}
<Modal bind:open title={error.message}>
<p>{error.stack}</p>
<Button slot="footer" on:click={() => open = false}>Close</Button>
{#snippet footer()}
<Button on:click={() => open = false}>Close</Button>
{/snippet}
</Modal>
{/if}

View File

@@ -23,53 +23,67 @@
import {gamemodes, groups, maps, players} from "@stores/stores.ts";
import type {Team} from "@type/team.ts";
export let teams: Team[] = [];
export let blueTeam: string;
export let redTeam: string;
export let start = "";
export let gamemode = "";
export let map = "";
export let spectatePort: string | null = null;
export let group: string | null = "";
export let groupSearch = "";
interface Props {
teams?: Team[];
blueTeam: string;
redTeam: string;
start?: string;
gamemode?: string;
map?: string;
spectatePort?: string | null;
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 {
name: team.name,
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 {
name: gamemode,
value: gamemode
};
}).sort((a, b) => a.name.localeCompare(b.name));
$: customGamemode = !selectableGamemodes.some((e) => e.name === gamemode) && gamemode !== "";
$: selectableCustomGamemode = [
}).sort((a, b) => a.name.localeCompare(b.name)));
let customGamemode = $derived(!selectableGamemodes.some((e) => e.name === gamemode) && gamemode !== "");
let selectableCustomGamemode = $derived([
...selectableGamemodes, {
name: gamemode + " (custom)",
value: gamemode
}
];
]);
$: mapsStore = maps(gamemode);
$: selectableMaps = $mapsStore.map(map => {
let mapsStore = $derived(maps(gamemode));
let selectableMaps = $derived($mapsStore.map(map => {
return {
name: map,
value: map
};
}).sort((a, b) => a.name.localeCompare(b.name));
$: customMap = !selectableMaps.some((e) => e.name === map) && map !== "";
$: selectableCustomMaps = [
}).sort((a, b) => a.name.localeCompare(b.name)));
let customMap = $derived(!selectableMaps.some((e) => e.name === map) && map !== "");
let selectableCustomMaps = $derived([
...selectableMaps, {
name: map + " (custom)",
value: map
}
];
]);
$: selectableGroups = [{
let selectableGroups = $derived([{
name: "None",
value: ""
}, {
@@ -80,7 +94,7 @@
name: group,
value: group
};
}).sort((a, b) => a.name.localeCompare(b.name))];
}).sort((a, b) => a.name.localeCompare(b.name))]);
</script>
<div class="m-2">
@@ -93,8 +107,10 @@
</div>
<div class="mt-4">
<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}/>
{/snippet}
</Input>
</div>
<div class="m-2">
@@ -109,8 +125,10 @@
</div>
<div class="m-2">
<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}/>
{/snippet}
</Input>
</div>
<div class="m-2">

View File

@@ -20,15 +20,25 @@
<script lang="ts">
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 || "";
let open = false;
interface Props {
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 }) {
selected = item.value;
@@ -40,11 +50,13 @@
<Button color="light"
on:click={() => open = true}>{selected === null ? 'Auswählen' : items.find(value => value.value === selected)?.name}</Button>
<Dropdown bind:open class="w-60">
<div class="overflow-y-auto p-3 text-sm w-60" slot="header">
<Search bind:value={searchValue} on:focus={() => open = true} on:keydown={() => open = true}/>
</div>
{#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}/>
</div>
{/snippet}
{#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:text-left={leftText}>
{item.name}

View File

@@ -21,9 +21,13 @@
import {eventRepo} from "@repo/event.ts";
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() {
return $eventRepo.getEvent(params.event)

View File

@@ -18,7 +18,9 @@
-->
<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 {mapToMap, nameRegex} from "../util.ts";
import TypeAheadSearch from "../components/TypeAheadSearch.svelte";
@@ -26,21 +28,16 @@
import Editor from "./edit/Editor.svelte";
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 searchValue: string = "";
let dirty = false;
let selectedBranch: string = $state("master");
let searchValue: string = $state("");
let dirty = $state(false);
let selectedPath: string | null = null;
let pathSearchValue: string = "";
let selectedPath: string | null = $state(null);
let pathSearchValue: string = $state("");
$: availableBranches = $branches.map((branch) => ({
name: branch,
value: branch
}));
async function createBranch(name: string | null = null): Promise<string> {
return new Promise(async (resolve) => {
@@ -139,12 +136,17 @@
alert("Error creating page");
}
}
let pagesFuture = $derived($pageRepo.listPages(selectedBranch));
let availableBranches = $derived($branches.map((branch) => ({
name: branch,
value: branch
})));
</script>
<div class="flex flex-col h-screen overflow-scroll">
<Navbar>
<NavBrand href="#">
<ArrowLeftSolid></ArrowLeftSolid>
<ArrowLeftOutline></ArrowLeftOutline>
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
Edit Pages
</span>
@@ -183,7 +185,7 @@
{@const startIndex = page.path.indexOf(match)}
{@const endIndex = startIndex + match.length}
<li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer"
on:click|preventDefault={() => changePage(page.id)}>
onclick={preventDefault(() => changePage(page.id))}>
<span class:text-orange-600={selected === page.id}>{page.path.substring(0, startIndex)}</span><span
class="text-white"
class:!text-orange-500={selected === page.id}>{match}</span><span

View File

@@ -20,13 +20,17 @@
<script lang="ts">
import {Navbar, NavBrand, Spinner, TabItem, Tabs} from "flowbite-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 TeamList from "./event/TeamList.svelte";
import {eventRepo} from "@repo/event.ts";
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 event = $eventRepo.getEvent(id.toString());
@@ -37,30 +41,40 @@
<Spinner size={16}/>
</div>
{:then data}
<Navbar let:hidden let:toggle>
<Navbar >
{#snippet children({ hidden, toggle })}
<NavBrand href="#">
<ArrowLeftSolid></ArrowLeftSolid>
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
{data.event.name}
</span>
</NavBrand>
<ArrowLeftOutline></ArrowLeftOutline>
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
{data.event.name}
</span>
</NavBrand>
{/snippet}
</Navbar>
<Tabs style="pill" class="mx-4 flex shadow-lg border-b-2 border-gray-700 pb-2" contentClass="">
<TabItem open>
<span slot="title">Event</span>
{#snippet title()}
<span >Event</span>
{/snippet}
<EventEdit {data}/>
</TabItem>
<TabItem>
<span slot="title">Teams</span>
{#snippet title()}
<span >Teams</span>
{/snippet}
<TeamList {data}/>
</TabItem>
<TabItem>
<span slot="title">Schiedsrichter</span>
{#snippet title()}
<span >Schiedsrichter</span>
{/snippet}
<RefereesList {data}/>
</TabItem>
<TabItem>
<span slot="title">Kämpfe</span>
{#snippet title()}
<span >Kämpfe</span>
{/snippet}
<FightList {data}/>
</TabItem>
</Tabs>

View File

@@ -19,11 +19,15 @@
<script lang="ts">
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 {eventRepo} from "@repo/event.ts";
export let params: { id: number };
interface Props {
params: { id: number };
}
let { params }: Props = $props();
let id = params.id;
let event = $eventRepo.getEvent(id.toString());
@@ -34,13 +38,15 @@
<Spinner size={16}/>
</div>
{:then data}
<Navbar let:hidden let:toggle>
<Navbar >
{#snippet children({ hidden, toggle })}
<NavBrand href="#/event/{id}">
<ArrowLeftSolid></ArrowLeftSolid>
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
{data.event.name} - Generate
</span>
</NavBrand>
<ArrowLeftOutline></ArrowLeftOutline>
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
{data.event.name} - Generate
</span>
</NavBrand>
{/snippet}
</Navbar>
<Tabs style="pill" class="mx-4 flex shadow-lg border-b-2 border-gray-700 pb-2" contentClass="">

View File

@@ -19,27 +19,29 @@
<script lang="ts">
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 CreateEventModal from "./home/CreateEventModal.svelte";
import {eventRepo} from "@repo/event.ts";
let events = $eventRepo.listEvents();
let showAdd = false;
let events = $state($eventRepo.listEvents());
let showAdd = $state(false);
let millis = Date.now();
</script>
<Navbar let:hidden let:toggle class="shadow-lg border-b">
<Navbar class="shadow-lg border-b">
{#snippet children({ hidden, toggle })}
<NavBrand href="/">
<span class="self-center whitespace-nowrap text-xl font-semibold dark:text-white">
Mod-Tool
</span>
</NavBrand>
<NavHamburger on:click={toggle}/>
<NavUl {hidden}>
<NavLi href="#/edit">Edit Pages</NavLi>
<NavLi href="#/perms">Permissions</NavLi>
</NavUl>
<span class="self-center whitespace-nowrap text-xl font-semibold dark:text-white">
Mod-Tool
</span>
</NavBrand>
<NavHamburger on:click={toggle}/>
<NavUl {hidden}>
<NavLi href="#/edit">Edit Pages</NavLi>
<NavLi href="#/perms">Permissions</NavLi>
</NavUl>
{/snippet}
</Navbar>
<CreateEventModal bind:open={showAdd} on:create={() => events = $eventRepo.listEvents()}/>
@@ -50,7 +52,7 @@
</div>
{:then data}
<Button class="fixed bottom-6 right-6 !p-4 z-10 shadow-lg" on:click={() => showAdd = true}>
<PlusSolid/>
<PlusOutline/>
</Button>
<h1 class="text-3xl mt-4 ml-4">Upcoming</h1>

View File

@@ -18,16 +18,18 @@
-->
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import {Button, Input, Label, Spinner, Toast} from "flowbite-svelte";
import {fly} from "svelte/transition";
import {replace} from "svelte-spa-router";
import {EyeOutline, EyeSlashOutline} from "flowbite-svelte-icons";
import {fetchWithToken, tokenStore} from "@repo/repo.ts";
let show = false;
let loading = false;
let value = "";
let error = false;
let show = $state(false);
let loading = $state(false);
let value = $state("");
let error = $state(false);
async function handleSubmit() {
loading = true;
@@ -47,19 +49,21 @@
</script>
<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>
<Label for="token-xyz" class="mb-2">Token</Label>
<Input type={show?'text':'password'} id="token-xyz" placeholder="•••••••••" required size="lg"
bind:value>
<button slot="left" on:click={() => (show = !show)} class="pointer-events-auto" type="button">
{#if show}
<EyeOutline/>
{:else}
<EyeSlashOutline/>
{/if}
</button>
{#snippet left()}
<button onclick={() => (show = !show)} class="pointer-events-auto" type="button">
{#if show}
<EyeOutline/>
{:else}
<EyeSlashOutline/>
{/if}
</button>
{/snippet}
</Input>
</div>
</div>
@@ -75,15 +79,17 @@
</div>
<Toast color="red" position="bottom-left" bind:open={error} transition={fly} params="{{x: -200}}">
<svelte:fragment slot="icon">
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
<span class="sr-only">Error icon</span>
</svelte:fragment>
{#snippet icon()}
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
<span class="sr-only">Error icon</span>
{/snippet}
Invalid Token.
</Toast>

View File

@@ -18,8 +18,10 @@
-->
<script lang="ts">
import { run, preventDefault } from 'svelte/legacy';
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 {capitalize} from "../util.ts";
import {permsRepo} from "@repo/perms.ts";
@@ -28,21 +30,17 @@
import SWModal from "@components/styled/SWModal.svelte";
import {userRepo} from "@repo/user.ts";
let search = "";
$: lowerCaseSearch = search.toLowerCase();
$: filteredPlayers = $players.filter(value => value.name.toLowerCase().includes(lowerCaseSearch));
let search = $state("");
let selectedPlayer: string | null = null;
$: player = $players.find(value => value.uuid === selectedPlayer);
let playerPerms = loadPlayer(selectedPlayer);
$: playerPerms = loadPlayer(selectedPlayer);
let selectedPlayer: string | null = $state(null);
let playerPerms = $state(loadPlayer(selectedPlayer));
let prefixEdit = "PREFIX_NONE";
let activePerms: string[] = [];
let prefixEdit = $state("PREFIX_NONE");
let activePerms: string[] = $state([]);
let resetPasswordModal = false;
let resetPassword = "";
let resetPasswordRepeat = "";
let resetPasswordModal = $state(false);
let resetPassword = $state("");
let resetPasswordRepeat = $state("");
function loadPlayer(id: string | null) {
if (!id) {
@@ -101,16 +99,24 @@
resetPasswordRepeat = "";
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>
<div class="flex flex-col h-screen overflow-hidden">
<Navbar let:hidden let:toggle>
<Navbar >
{#snippet children({ hidden, toggle })}
<NavBrand href="#">
<ArrowLeftSolid></ArrowLeftSolid>
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
Permissions
</span>
</NavBrand>
<ArrowLeftOutline></ArrowLeftOutline>
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
Permissions
</span>
</NavBrand>
{/snippet}
</Navbar>
<div class="p-4 flex-1 overflow-hidden">
@@ -125,7 +131,7 @@
{#each filteredPlayers as player (player.uuid)}
<li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer"
class:text-orange-500={player.uuid === selectedPlayer}
on:click|preventDefault={() => selectedPlayer = player.uuid}>
onclick={preventDefault(() => selectedPlayer = player.uuid)}>
{player.name}
</li>
{/each}
@@ -166,14 +172,16 @@
<Label for="repeat_password">Repeat Password</Label>
<Input type="password" id="repeat_password" placeholder="Repeat Password" bind:value={resetPasswordRepeat}/>
<svelte:fragment slot="footer">
<Button class="ml-auto mr-4" on:click={resetResetPassword}>
Cancel
</Button>
<Button disabled={resetPassword === "" || resetPassword !== resetPasswordRepeat} on:click={resetPW}>
Reset Password
</Button>
</svelte:fragment>
{#snippet footer()}
<Button class="ml-auto mr-4" on:click={resetResetPassword}>
Cancel
</Button>
<Button disabled={resetPassword === "" || resetPassword !== resetPasswordRepeat} on:click={resetPW}>
Reset Password
</Button>
{/snippet}
</SWModal>
{/if}
</div>

View File

@@ -21,16 +21,20 @@
import type {ExtendedEvent} from "@type/event.ts";
import {onMount} from "svelte";
export let event: ExtendedEvent;
export let group: string | null = null;
interface Props {
event: ExtendedEvent;
group?: string | null;
}
let { event, group = null }: Props = $props();
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 scrollContainer: HTMLDivElement;
let scroll = 0;
let clock = $state(new Date());
let scrollContainer: HTMLDivElement = $state();
let scroll = $state(0);
onMount(() => {
const interval = setInterval(() => {

View File

@@ -28,15 +28,18 @@
import MDEMarkdownEditor from "./MDEMarkdownEditor.svelte";
import {pageRepo} from "@repo/page.ts";
export let pageId: number;
export let branch: string;
export let dirty: boolean = false;
interface Props {
pageId: number;
branch: string;
dirty?: boolean;
}
let { pageId, branch, dirty = $bindable(false) }: Props = $props();
let dispatcher = createEventDispatcher();
$: pageFuture = $pageRepo.getPage(pageId, branch).then(getPage);
let pageContent = "";
let page: Page | null = null;
let pageContent = $state("");
let page: Page | null = $state(null);
function getPage(value: Page): Page {
page = value;
@@ -66,8 +69,9 @@
dispatcher("reload");
}
}
let pageFuture = $derived($pageRepo.getPage(pageId, branch).then(getPage));
</script>
<svelte:window on:beforeunload={() => {
<svelte:window onbeforeunload={() => {
if (dirty) {
return "You have unsaved changes. Are you sure you want to leave?";
}
@@ -78,14 +82,16 @@
<div>
<div>
<Toolbar class="!bg-gray-900">
<ToolbarGroup slot="end">
<ToolbarButton on:click={deletePage}>
Delete
</ToolbarButton>
<ToolbarButton color="primary" on:click={savePage}>
Save
</ToolbarButton>
</ToolbarGroup>
{#snippet end()}
<ToolbarGroup >
<ToolbarButton on:click={deletePage}>
Delete
</ToolbarButton>
<ToolbarButton color="primary" on:click={savePage}>
Save
</ToolbarButton>
</ToolbarGroup>
{/snippet}
</Toolbar>
</div>
{#if page?.name.endsWith("md") || page?.name.endsWith("mdx")}

View File

@@ -22,10 +22,14 @@
import EasyMDE from "easymde";
import "easymde/dist/easymde.min.css";
export let value: string;
export let dirty: boolean = false;
interface Props {
value: string;
dirty?: boolean;
}
let editor: HTMLTextAreaElement;
let { value = $bindable(), dirty = $bindable(false) }: Props = $props();
let editor: HTMLTextAreaElement = $state();
let mde: EasyMDE;
onMount(async () => {

View File

@@ -30,26 +30,30 @@
dayjs.extend(utc);
export let data: ExtendedEvent;
let event = data.event;
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 end = dayjs(event.end).utc(true).toISOString().slice(0, -1);
let member = event.maxTeamMembers;
let schemType = event.schemType;
let publicOnly = event.publicSchemsOnly;
interface Props {
data: ExtendedEvent;
}
let { data }: Props = $props();
let event = $state(data.event);
let name = $state(event.name);
let deadline = $state(dayjs(event.deadline).utc(true).toISOString().slice(0, -1));
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 removeReferee: {name: string, id: number}[] = [];
let errorOpen = false;
let error: any = undefined;
let deleteOpen = false;
let errorOpen = $state(false);
let error: any = $state(undefined);
let deleteOpen = $state(false);
$: deadlineDate = dayjs(deadline);
$: startDate = dayjs(start);
$: endDate = dayjs(end);
$: selectTypes = [{
let deadlineDate = $derived(dayjs(deadline));
let startDate = $derived(dayjs(start));
let endDate = $derived(dayjs(end));
let selectTypes = $derived([{
value: null,
name: "None"
}, ...$schemTypes.map((type) => {
@@ -57,9 +61,9 @@
value: type.db,
name: type.name
};
})];
})]);
$: changed = name !== event.name ||
let changed = $derived(name !== event.name ||
deadlineDate.diff(dayjs(event.deadline)) !== 0 ||
startDate.diff(dayjs(event.start)) !== 0 ||
endDate.diff(dayjs(event.end)) !== 0 ||
@@ -67,7 +71,7 @@
schemType != event.schemType ||
publicOnly !== event.publicSchemsOnly ||
addReferee.length > 0 ||
removeReferee.length > 0;
removeReferee.length > 0);
async function del() {
@@ -80,7 +84,7 @@
}
}
let successToast: boolean = false;
let successToast: boolean = $state(false);
async function update() {
let ev: UpdateEvent = {
@@ -117,20 +121,26 @@
</div>
<div class="mt-4">
<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}/>
{/snippet}
</Input>
</div>
<div class="mt-4">
<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}/>
{/snippet}
</Input>
</div>
<div class="mt-4">
<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}/>
{/snippet}
</Input>
</div>
<div class="mt-4">
@@ -159,6 +169,8 @@
</Modal>
<Toast bind:open={successToast} position="bottom-left" color="green">
<CheckCircleOutline slot="icon"/>
{#snippet icon()}
<CheckCircleOutline />
{/snippet}
Updated Successfully
</Toast>

View File

@@ -26,14 +26,24 @@
import {isWide} from "@stores/stores.ts";
import {fightRepo} from "@repo/fight.ts";
export let fight: EventFight;
export let data: ExtendedEvent;
export let i: number;
export let selected: boolean = false;
export let hideEdit: boolean = false;
interface Props {
fight: EventFight;
data: ExtendedEvent;
i: number;
selected?: boolean;
hideEdit?: boolean;
}
let deleteOpen = false;
let editOpen = false;
let {
fight,
data = $bindable(),
i,
selected = false,
hideEdit = false
}: Props = $props();
let deleteOpen = $state(false);
let editOpen = $state(false);
let dispatcher = createEventDispatcher();
@@ -52,7 +62,7 @@
</script>
<div class="flex h-16 {i % 2 === 0 ? 'bg-gray-800' : ''} mx-4 mt-6 rounded border {selected ? 'border-orange-700' : 'border-gray-700'} p-2 hover:bg-gray-700 transition justify-between shadow-lg cursor-pointer"
on:click={dispatchSelect} on:keypress={dispatchSelect} role="checkbox" aria-checked={selected} tabindex="0"
onclick={dispatchSelect} onkeypress={dispatchSelect} role="checkbox" aria-checked={selected} tabindex="0"
>
<div class="flex">
<div class="flex flex-col">

View File

@@ -31,8 +31,8 @@
Tooltip
} from "flowbite-svelte";
import {
ArrowsRepeatSolid, CalendarWeekOutline,
PlusSolid, ProfileCardOutline, TrashBinOutline, UsersGroupOutline,
ArrowsRepeatOutline, CalendarWeekOutline,
PlusOutline, ProfileCardOutline, TrashBinOutline, UsersGroupOutline,
} from "flowbite-svelte-icons";
import FightCard from "./FightCard.svelte";
import CreateFightModal from "./modals/CreateFightModal.svelte";
@@ -44,19 +44,23 @@
dayjs.extend(duration);
export let data: ExtendedEvent;
interface Props {
data: ExtendedEvent;
}
let createOpen = false;
let fights = data.fights;
let selectedFights: Set<EventFight> = new Set();
let { data = $bindable() }: Props = $props();
$: groupsMap = new Set(fights.map(fight => fight.group));
$: groupedFights = Array.from(groupsMap).map(group => {
let createOpen = $state(false);
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 {
group: group,
fights: fights.filter(fight => fight.group === group)
};
});
}));
function cycleSelect() {
if (selectedFights.size === fights.length) {
@@ -81,7 +85,7 @@
selectedFights = selectedFights;
}
let deleteOpen = false;
let deleteOpen = $state(false);
async function deleteFights() {
for (const fight of selectedFights) {
@@ -92,14 +96,14 @@
deleteOpen = false;
}
let spectatePortOpen = false;
$: selectPlayers = $players.map(player => {
let spectatePortOpen = $state(false);
let selectPlayers = $derived($players.map(player => {
return {
name: player.name,
value: player.uuid
};
}).sort((a, b) => a.name.localeCompare(b.name));
let spectatePort = "";
}).sort((a, b) => a.name.localeCompare(b.name)));
let spectatePort = $state("");
async function updateSpectatePort() {
for (const fight of selectedFights) {
@@ -120,11 +124,11 @@
spectatePortOpen = false;
}
let groupChangeOpen = false;
let group = "";
let groupSearch = "";
let groupChangeOpen = $state(false);
let group = $state("");
let groupSearch = $state("");
$: selectableGroups = [{
let selectableGroups = $derived([{
name: "Keine",
value: ""
}, {
@@ -135,7 +139,7 @@
name: group,
value: group
};
}).sort((a, b) => a.name.localeCompare(b.name))];
}).sort((a, b) => a.name.localeCompare(b.name))]);
async function updateGroup() {
for (const fight of selectedFights) {
@@ -157,11 +161,11 @@
groupChangeOpen = false;
}
$: minTime = dayjs(Math.min(...fights.map(fight => fight.start))).utc(true);
let changeTimeOpen = false;
let changedTime = fights.length != 0 ? dayjs(Math.min(...fights.map(fight => fight.start)))?.utc(true)?.toISOString()?.slice(0, -1) : undefined;
let minTime = $derived(dayjs(Math.min(...fights.map(fight => fight.start))).utc(true));
let changeTimeOpen = $state(false);
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() {
for (const fight of selectedFights) {
@@ -243,10 +247,12 @@
<Modal bind:open={deleteOpen} title="Delete {selectedFights.size} Fights" autoclose size="sm">
<p>Are you sure you want to delete {selectedFights.size} fights?</p>
<svelte:fragment slot="footer">
<Button color="red" class="ml-auto" on:click={deleteFights}>Delete</Button>
<Button on:click={() => deleteOpen = false} color="alternative">Cancel</Button>
</svelte:fragment>
{#snippet footer()}
<Button color="red" class="ml-auto" on:click={deleteFights}>Delete</Button>
<Button on:click={() => deleteOpen = false} color="alternative">Cancel</Button>
{/snippet}
</Modal>
<Modal bind:open={spectatePortOpen} title="Change Kampfleiter" size="sm">
@@ -254,10 +260,12 @@
<Label for="fight-kampf">Kampfleiter</Label>
<TypeAheadSearch items={selectPlayers} bind:selected={spectatePort}></TypeAheadSearch>
</div>
<svelte:fragment slot="footer">
<Button class="ml-auto" on:click={updateSpectatePort}>Change</Button>
<Button on:click={() => spectatePortOpen = false} color="alternative">Cancel</Button>
</svelte:fragment>
{#snippet footer()}
<Button class="ml-auto" on:click={updateSpectatePort}>Change</Button>
<Button on:click={() => spectatePortOpen = false} color="alternative">Cancel</Button>
{/snippet}
</Modal>
<Modal bind:open={groupChangeOpen} title="Change Group" size="sm">
@@ -266,32 +274,38 @@
<TypeAheadSearch items={selectableGroups} bind:selected={group} bind:searchValue={groupSearch}
all></TypeAheadSearch>
</div>
<svelte:fragment slot="footer">
<Button class="ml-auto" on:click={updateGroup}>Change</Button>
<Button on:click={() => groupChangeOpen = false} color="alternative">Cancel</Button>
</svelte:fragment>
{#snippet footer()}
<Button class="ml-auto" on:click={updateGroup}>Change</Button>
<Button on:click={() => groupChangeOpen = false} color="alternative">Cancel</Button>
{/snippet}
</Modal>
<Modal bind:open={changeTimeOpen} title="Change Start Time" size="sm">
<div class="m-2">
<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}/>
{/snippet}
</Input>
</div>
<p>{deltaTime.asMilliseconds() < 0 ? '' : '+'}{("0" + deltaTime.hours()).slice(-2)}
:{("0" + deltaTime.minutes()).slice(-2)}</p>
<svelte:fragment slot="footer">
<Button class="ml-auto" on:click={updateStartTime}>Update</Button>
<Button on:click={() => changeTimeOpen = false} color="alternative">Cancel</Button>
</svelte:fragment>
{#snippet footer()}
<Button class="ml-auto" on:click={updateStartTime}>Update</Button>
<Button on:click={() => changeTimeOpen = false} color="alternative">Cancel</Button>
{/snippet}
</Modal>
<SpeedDial>
<SpeedDialButton name="Add" on:click={() => createOpen = true}>
<PlusSolid/>
<PlusOutline/>
</SpeedDialButton>
<SpeedDialButton name="Generate" href="#/event/{data.event.id}/generate">
<ArrowsRepeatSolid/>
<ArrowsRepeatOutline/>
</SpeedDialButton>
</SpeedDial>

View File

@@ -20,21 +20,25 @@
<script lang="ts">
import type {ExtendedEvent} from "@type/event.ts";
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 SWButton from "@components/styled/SWButton.svelte";
import TypeAheadSearch from "@components/admin/components/TypeAheadSearch.svelte";
import {players} from "@stores/stores.ts";
import {eventRepo} from "@repo/event.ts";
export let data: ExtendedEvent;
interface Props {
data: ExtendedEvent;
}
let searchValue = "";
let selectedPlayer: string | null = null;
let { data }: Props = $props();
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() {
if (selectedPlayer) {
@@ -76,7 +80,7 @@
</ul>
<Button class="fixed bottom-6 right-6 !p-4 z-10 shadow-lg" on:click={() => showAdd = true}>
<PlusSolid/>
<PlusOutline/>
</Button>
<SWModal title="Schiedsrichter hinzufügen" bind:open={showAdd}>
@@ -86,10 +90,12 @@
items={$players.map(v => ({ name: v.name, value: v.uuid }))}/>
</div>
</div>
<div slot="footer" class="flex flex-grow justify-end">
<SWButton on:click={reset} type="gray">Abbrechen</SWButton>
<SWButton on:click={addReferee}>Hinzufügen</SWButton>
</div>
{#snippet footer()}
<div class="flex flex-grow justify-end">
<SWButton on:click={reset} type="gray">Abbrechen</SWButton>
<SWButton on:click={addReferee}>Hinzufügen</SWButton>
</div>
{/snippet}
</SWModal>
<style>

View File

@@ -21,7 +21,11 @@
import {Avatar} from "flowbite-svelte";
import type {ExtendedEvent} from "@type/event.ts";
export let data: ExtendedEvent;
interface Props {
data: ExtendedEvent;
}
let { data }: Props = $props();
</script>
<div class="m-4">
{#each data.teams as team (team.id)}

View File

@@ -28,25 +28,29 @@
let dispatch = createEventDispatcher();
export let open: boolean = false;
export let data: ExtendedEvent;
interface Props {
open?: boolean;
data: ExtendedEvent;
}
let blueTeam: string = "";
let redTeam: string = "";
let { open = $bindable(false), data }: Props = $props();
let start: string = "";
let blueTeam: string = $state("");
let redTeam: string = $state("");
let gamemode: string = "";
let map: string = "";
let start: string = $state("");
let spectatePort: string | null = null;
let group: string | null = null;
let groupSearch = "";
let gamemode: string = $state("");
let map: string = $state("");
let errorOpen = false;
let error: any = undefined;
let spectatePort: string | null = $state(null);
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() {
try {
@@ -96,10 +100,12 @@
teams={data.teams}
/>
</div>
<svelte:fragment slot="footer">
<Button on:click={create} class="mr-auto" disabled={!canCreate}>Create</Button>
<Button color="light" on:click={() => open = false}>Cancel</Button>
</svelte:fragment>
{#snippet footer()}
<Button on:click={create} class="mr-auto" disabled={!canCreate}>Create</Button>
<Button color="light" on:click={() => open = false}>Cancel</Button>
{/snippet}
</Modal>
<ErrorModal bind:open={errorOpen} bind:error={error} on:close={() => errorOpen = false}/>

View File

@@ -29,21 +29,25 @@
dayjs.extend(utc);
export let fight: EventFight;
export let data: ExtendedEvent;
export let open = false;
interface Props {
fight: EventFight;
data: ExtendedEvent;
open?: boolean;
}
let redTeam = fight.redTeam.id.toString();
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 { fight = $bindable(), data, open = $bindable(false) }: Props = $props();
let errorOpen = false;
let error: any = undefined;
let redTeam = $state(fight.redTeam.id.toString());
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();

View File

@@ -19,8 +19,13 @@
<script lang="ts">
import {createEventDispatcher} from "svelte";
interface Props {
children?: import('svelte').Snippet;
}
let dragover = false;
let { children }: Props = $props();
let dragover = $state(false);
function handleDragOver(e) {
e.preventDefault();
@@ -36,9 +41,9 @@
}
</script>
<div class="w-56 bg-gray-800 p-4 rounded" class:border={dragover} class:m-px={!dragover} on:drop={handleDrop}
on:dragover={handleDragOver} on:dragleave={() => dragover = false} role="none">
<slot></slot>
<div class="w-56 bg-gray-800 p-4 rounded" class:border={dragover} class:m-px={!dragover} ondrop={handleDrop}
ondragover={handleDragOver} ondragleave={() => dragover = false} role="none">
{@render children?.()}
</div>
<style>

View File

@@ -24,7 +24,7 @@
import DragAcceptor from "./DragAcceptor.svelte";
import {Button, Input, Label, Modal, Range, Select} from "flowbite-svelte";
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 dayjs from "dayjs";
import {fightRepo} from "@repo/fight.ts";
@@ -33,11 +33,15 @@
dayjs.extend(utc);
export let data: ExtendedEvent;
$: teams = new Map<number, Team>(data.teams.map(team => [team.id, team]));
interface Props {
data: ExtendedEvent;
}
let groups: number[][] = [];
$: teamsNotInGroup = data.teams.filter(team => !groups.flat().includes(team.id));
let { data }: Props = $props();
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>) {
event.detail.preventDefault();
@@ -49,7 +53,7 @@
ev.dataTransfer!.setData("team", team.id.toString());
}
let resetDragOver = false;
let resetDragOver = $state(false);
function resetDragOverEvent(ev: DragEvent) {
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);
}
let startTime = dayjs(data.event.start).utc(true).toISOString().slice(0, -1);
$: startMoment = dayjs(startTime);
let gamemode = "";
let map = "";
let startTime = $state(dayjs(data.event.start).utc(true).toISOString().slice(0, -1));
let startMoment = $derived(dayjs(startTime));
let gamemode = $state("");
let map = $state("");
$: selectableGamemodes = $gamemodes.map(gamemode => {
let selectableGamemodes = $derived($gamemodes.map(gamemode => {
return {
name: gamemode,
value: gamemode
};
}).sort((a, b) => a.name.localeCompare(b.name));
}).sort((a, b) => a.name.localeCompare(b.name)));
$: mapsStore = maps(gamemode);
$: selectableMaps = $mapsStore.map(map => {
let mapsStore = $derived(maps(gamemode));
let selectableMaps = $derived($mapsStore.map(map => {
return {
name: map,
value: map
};
}).sort((a, b) => a.name.localeCompare(b.name));
}).sort((a, b) => a.name.localeCompare(b.name)));
let roundTime = 30;
let startDelay = 30;
let roundTime = $state(30);
let startDelay = $state(30);
let showAutoGrouping = false;
let groupCount = Math.floor(data.teams.length / 2);
let showAutoGrouping = $state(false);
let groupCount = $state(Math.floor(data.teams.length / 2));
function createGroups() {
let teams = data.teams.map(team => team.id).sort(() => Math.random() - 0.5);
@@ -134,9 +138,9 @@
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() {
groupsFights.forEach((group, i) => {
@@ -165,7 +169,7 @@
<div class="flex justify-between">
<div id="reseter" class:border-white={resetDragOver}
class="flex m-2 bg-gray-800 w-fit p-2 border border-gray-700 rounded ml-4 h-20 pt-6 relative"
on:dragover={resetDragOverEvent} on:dragleave={() => resetDragOver = false} on:drop={dropReset} role="group">
ondragover={resetDragOverEvent} ondragleave={() => resetDragOver = false} ondrop={dropReset} role="group">
{#each teamsNotInGroup as team (team.id)}
<TeamChip {team} on:dragstart={ev => teamDragStart(ev, team)}/>
{/each}
@@ -192,8 +196,10 @@
<div class="m-4 border-b border-gray-700 pb-4">
<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}/>
{/snippet}
</Input>
<div class="mt-2">
<Label for="event-roundtime">Round time: {roundTime}m</Label>
@@ -233,17 +239,19 @@
</div>
<Button class="!p-4 fixed bottom-4 right-4" pill disabled={!generateDisabled} on:click={generateFights}>
<PlusSolid/>
<PlusOutline/>
</Button>
<Modal bind:open={showAutoGrouping} outsideclose title="Auto Grouping" size="sm">
<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)}/>
<svelte:fragment slot="footer">
<Button class="ml-auto" on:click={createGroups}>Create</Button>
<Button color="alternative" on:click={() => showAutoGrouping = false}>Cancel</Button>
</svelte:fragment>
{#snippet footer()}
<Button class="ml-auto" on:click={createGroups}>Create</Button>
<Button color="alternative" on:click={() => showAutoGrouping = false}>Cancel</Button>
{/snippet}
</Modal>
<style lang="scss">

View File

@@ -18,20 +18,27 @@
-->
<script lang="ts">
import { createBubbler } from 'svelte/legacy';
const bubble = createBubbler();
import type {Team} from "@type/team.ts";
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>
<div class="rounded w-fit p-2 border-gray-600 border cursor-grab select-none m-1 flex place-items-center"
style:background-color={hover ? lighten(colorFromTeam(team)) : colorFromTeam(team)}
class:text-black={brightness(colorFromTeam(team))} draggable="true"
on:dragstart
on:mouseenter={() => hover = true}
on:mouseleave={() => hover = false}
ondragstart={bubble('dragstart')}
onmouseenter={() => hover = true}
onmouseleave={() => hover = false}
role="figure">
<span>{team.name}</span>
</div>

View File

@@ -24,19 +24,23 @@
import dayjs from "dayjs";
import {eventRepo} from "@repo/event.ts";
export let open = false;
interface Props {
open?: boolean;
}
let { open = $bindable(false) }: Props = $props();
const dispatch = createEventDispatcher();
let errorOpen = false;
let error: any = undefined;
let errorOpen = $state(false);
let error: any = $state(undefined);
let eventName = "";
let start = "";
$: startDate = dayjs(start);
let end = "";
$: endDate = dayjs(end);
let eventName = $state("");
let start = $state("");
let startDate = $derived(dayjs(start));
let end = $state("");
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() {
try {
@@ -69,21 +73,27 @@
</div>
<div class="w-2/3 m-2">
<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}/>
{/snippet}
</Input>
</div>
<div class="w-2/3 m-2">
<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}/>
{/snippet}
</Input>
</div>
</div>
<svelte:fragment slot="footer">
<Button color="alternative" on:click={() => open = false} class="mr-auto">Cancel</Button>
<Button on:click={createEvent} disabled={!canSubmit}>Create</Button>
</svelte:fragment>
{#snippet footer()}
<Button color="alternative" on:click={() => open = false} class="mr-auto">Cancel</Button>
<Button on:click={createEvent} disabled={!canSubmit}>Create</Button>
{/snippet}
</Modal>
<ErrorModal bind:open={errorOpen} bind:error={error}/>

View File

@@ -22,9 +22,13 @@
import {link} from "svelte-spa-router";
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>
<a href="/event/{event.id}" use:link>