Compare commits
22 Commits
develop/da
...
fa5f25f37e
| Author | SHA1 | Date | |
|---|---|---|---|
| fa5f25f37e | |||
| 260b7b24c4 | |||
|
|
4aea0c7fea | ||
|
|
314ff3e7c3 | ||
| 2f2c1be958 | |||
|
|
41c7df0d68 | ||
|
|
cedf641039 | ||
| d9bdc636e3 | |||
|
|
c8d05cb268 | ||
| cb2564c9ce | |||
|
|
80caf8fe6d | ||
|
|
fd3d621fd5 | ||
| 6377799e1b | |||
| b3598e1ee1 | |||
| b9db5be858 | |||
| 3e54934806 | |||
| 98638f94fc | |||
| 4da8fe50c0 | |||
| 7757978668 | |||
| 9eea0b2b3f | |||
| 063638d016 | |||
| f5a778d9b4 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,3 +26,4 @@ pnpm-debug.log*
|
||||
/src/env.d.ts
|
||||
/src/pages/en/
|
||||
/.idea
|
||||
pnpm-lock.yaml
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@astrojs/tailwind": "^5.1.5",
|
||||
"@astropub/icons": "^0.2.0",
|
||||
"@internationalized/date": "^3.7.0",
|
||||
"@lucide/svelte": "^0.488.0",
|
||||
"@types/color": "^4.2.0",
|
||||
"@types/node": "^22.9.3",
|
||||
"@types/three": "^0.170.0",
|
||||
|
||||
9276
pnpm-lock.yaml
generated
9276
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -18,13 +18,13 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import {twMerge} from "tailwind-merge";
|
||||
import {onMount} from "svelte";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let cardElement: HTMLDivElement = $state();
|
||||
|
||||
function rotateElement(event: MouseEvent) {
|
||||
if(!hoverEffect) return;
|
||||
if (!hoverEffect) return;
|
||||
|
||||
const x = event.clientX;
|
||||
const y = event.clientY;
|
||||
@@ -36,23 +36,23 @@
|
||||
const rotateX = (centerY - y) / 20;
|
||||
const rotateY = -(centerX - x) / 20;
|
||||
|
||||
cardElement.style.setProperty('--rotate-x', `${rotateX}deg`);
|
||||
cardElement.style.setProperty('--rotate-y', `${rotateY}deg`);
|
||||
cardElement.style.setProperty("--rotate-x", `${rotateX}deg`);
|
||||
cardElement.style.setProperty("--rotate-y", `${rotateY}deg`);
|
||||
}
|
||||
|
||||
function resetElement() {
|
||||
cardElement.style.setProperty('--rotate-x', "0");
|
||||
cardElement.style.setProperty('--rotate-y', "0");
|
||||
cardElement.style.setProperty("--rotate-x", "0");
|
||||
cardElement.style.setProperty("--rotate-y", "0");
|
||||
}
|
||||
|
||||
interface Props {
|
||||
hoverEffect?: boolean;
|
||||
extraClasses?: string;
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
interface Props {
|
||||
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))
|
||||
let { hoverEffect = true, extraClasses = "", children }: Props = $props();
|
||||
let classes = $derived(twMerge("w-72 border-2 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg bg-zinc-900 dark:border-gray-800 dark:text-gray-100", extraClasses));
|
||||
</script>
|
||||
|
||||
<div class={classes} bind:this={cardElement} onmousemove={rotateElement} onmouseleave={resetElement} class:hoverEffect>
|
||||
@@ -61,20 +61,20 @@
|
||||
|
||||
<style lang="scss">
|
||||
div {
|
||||
transform: perspective(1000px) rotateX(var(--rotate-x, 0)) rotateY(var(--rotate-y, 0)) !important;
|
||||
transform: perspective(1000px) rotateX(var(--rotate-x, 0)) rotateY(var(--rotate-y, 0)) !important;
|
||||
|
||||
transition: scale 300ms cubic-bezier(.2, 3, .67, .6);
|
||||
transition: scale 300ms cubic-bezier(0.2, 3, 0.67, 0.6);
|
||||
|
||||
:global(h1) {
|
||||
@apply text-xl font-bold mt-4;
|
||||
}
|
||||
:global(h1) {
|
||||
@apply text-xl font-bold mt-4;
|
||||
}
|
||||
|
||||
:global(svg) {
|
||||
@apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl
|
||||
}
|
||||
:global(svg) {
|
||||
@apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl;
|
||||
}
|
||||
}
|
||||
|
||||
.hoverEffect:hover {
|
||||
scale: 105%;
|
||||
scale: 105%;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import FightStatsChart from "./FightStatsChart.svelte";
|
||||
import {t} from "astro-i18n";
|
||||
import {statsRepo} from "@repo/stats.ts";
|
||||
import { t } from "astro-i18n";
|
||||
import { statsRepo } from "@repo/stats.ts";
|
||||
|
||||
let request = getStats();
|
||||
|
||||
@@ -35,4 +35,4 @@
|
||||
<FightStatsChart data={stats} />
|
||||
{:catch error}
|
||||
<p>error: {error}</p>
|
||||
{/await}
|
||||
{/await}
|
||||
|
||||
@@ -19,18 +19,18 @@
|
||||
|
||||
<script lang="ts">
|
||||
import "../styles/button.css";
|
||||
import {CaretDownOutline, SearchOutline} from "flowbite-svelte-icons";
|
||||
import {t} from "astro-i18n";
|
||||
import {l} from "../util/util";
|
||||
import {onMount} from "svelte";
|
||||
import {loggedIn} from "@repo/authv2.ts";
|
||||
interface Props {
|
||||
logo?: import('svelte').Snippet;
|
||||
}
|
||||
import { CaretDownOutline, SearchOutline } from "flowbite-svelte-icons";
|
||||
import { t } from "astro-i18n";
|
||||
import { l } from "../util/util";
|
||||
import { onMount } from "svelte";
|
||||
import { loggedIn } from "@repo/authv2.ts";
|
||||
interface Props {
|
||||
logo?: import("svelte").Snippet;
|
||||
}
|
||||
|
||||
let { logo }: Props = $props();
|
||||
let { logo }: Props = $props();
|
||||
|
||||
let navbar = $state<HTMLDivElement>();
|
||||
let navbar = $state<HTMLElement>();
|
||||
let searchOpen = $state(false);
|
||||
|
||||
let accountBtn = $state<HTMLAnchorElement>();
|
||||
@@ -41,11 +41,11 @@
|
||||
} else {
|
||||
accountBtn!.href = l("/login");
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
handleScroll();
|
||||
})
|
||||
});
|
||||
|
||||
function handleScroll() {
|
||||
if (window.scrollY > 0) {
|
||||
@@ -56,13 +56,17 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onscroll={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="z-20 fixed top-0 left-0 right-0 sm:px-4 py-1 transition-colors 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">
|
||||
<a class="flex items-center" href={l("/")}>
|
||||
{@render logo?.()}
|
||||
<span class="text-2xl uppercase font-bold dark:text-white hidden md:inline-block">
|
||||
<span class="text-2xl uppercase font-bold text-white hidden md:inline-block">
|
||||
{t("navbar.title")}
|
||||
<span class="before:scale-y-100" style="display: none" aria-hidden="true"></span>
|
||||
</span>
|
||||
@@ -73,7 +77,7 @@
|
||||
<a href={l("/")}>
|
||||
<span class="btn__text">{t("navbar.links.home.title")}</span>
|
||||
</a>
|
||||
<CaretDownOutline class="ml-2 mt-auto"/>
|
||||
<CaretDownOutline class="ml-2 mt-auto" />
|
||||
</button>
|
||||
<div>
|
||||
<a class="btn btn-gray" href={l("/announcements")}>{t("navbar.links.home.announcements")}</a>
|
||||
@@ -87,7 +91,7 @@
|
||||
<a rel="prefetch" href={l("/rules")}>
|
||||
<span class="btn__text">{t("navbar.links.rules.title")}</span>
|
||||
</a>
|
||||
<CaretDownOutline class="ml-2 mt-auto"/>
|
||||
<CaretDownOutline class="ml-2 mt-auto" />
|
||||
</button>
|
||||
<div>
|
||||
<a href={l("/rules/wargear")} class="btn btn-gray">{t("navbar.links.rules.wg")}</a>
|
||||
@@ -96,10 +100,8 @@
|
||||
<a href={l("/rules/airship")} class="btn btn-gray">{t("navbar.links.rules.as")}</a>
|
||||
<a href={l("/rules/quickgear")} class="btn btn-gray">{t("navbar.links.rules.qg")}</a>
|
||||
<h2 class="px-2 text-gray-300">{t("navbar.links.rules.rotating")}</h2>
|
||||
<a href={l("/rules/megawargear")}
|
||||
class="btn btn-gray">{t("navbar.links.rules.megawg")}</a>
|
||||
<a href={l("/rules/microwargear")}
|
||||
class="btn btn-gray">{t("navbar.links.rules.micro")}</a>
|
||||
<a href={l("/rules/megawargear")} class="btn btn-gray">{t("navbar.links.rules.megawg")}</a>
|
||||
<a href={l("/rules/microwargear")} class="btn btn-gray">{t("navbar.links.rules.micro")}</a>
|
||||
<a href={l("/rules/streetfight")} class="btn btn-gray">{t("navbar.links.rules.sf")}</a>
|
||||
<h2 class="px-2 text-gray-300">{t("navbar.links.rules.ranked")}</h2>
|
||||
<a href={l("/rangliste/missilewars")} class="btn btn-gray">{t("navbar.links.ranked.mw")}</a>
|
||||
@@ -141,4 +143,4 @@
|
||||
.match {
|
||||
width: min(100vw, 70em);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,50 +1,60 @@
|
||||
---
|
||||
import type {CollectionEntry} from "astro:content";
|
||||
import {l} from "../util/util";
|
||||
import {astroI18n} from "astro-i18n";
|
||||
import {Image} from "astro:assets";
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
import { l } from "../util/util";
|
||||
import { astroI18n } from "astro-i18n";
|
||||
import { Image } from "astro:assets";
|
||||
import TagComponent from "./TagComponent.astro";
|
||||
import P from "./P.astro";
|
||||
import Card from "@components/Card.svelte";
|
||||
|
||||
interface Props {
|
||||
post: CollectionEntry<"announcements">
|
||||
post: CollectionEntry<"announcements">;
|
||||
}
|
||||
|
||||
const { post, slim }: {
|
||||
post: CollectionEntry<"announcements">,
|
||||
slim: boolean,
|
||||
const {
|
||||
post,
|
||||
slim,
|
||||
}: {
|
||||
post: CollectionEntry<"announcements">;
|
||||
slim: boolean;
|
||||
} = Astro.props as Props;
|
||||
|
||||
const postUrl = l(`/announcements/${post.slug.split("/").slice(1).join("/")}`);
|
||||
---
|
||||
|
||||
<Card extraClasses={`w-full items-start mx-0 ${slim ? "m-0 p-1" : ""}`} hoverEffect={false}>
|
||||
<div class={`flex flex-row ${slim ? "":"p-4"}`}>
|
||||
{post.data.image != null
|
||||
? (
|
||||
<Card extraClasses={`w-full items-start mx-0 ${slim ? "m-0 p-1 backdrop-blur-xl bg-transparent" : ""}`} hoverEffect={false}>
|
||||
<div class={`flex flex-row ${slim ? "" : "p-4"}`}>
|
||||
{
|
||||
post.data.image != null ? (
|
||||
<a href={postUrl}>
|
||||
<div class="flex-shrink-0 pr-2">
|
||||
<Image transition:name={post.data.title + "-image"} src={post.data.image} alt="Post Image" class="rounded-2xl shadow-2xl object-cover h-32 w-32 max-w-none transition-transform hover:scale-105" />
|
||||
<Image
|
||||
transition:name={post.data.title + "-image"}
|
||||
src={post.data.image}
|
||||
alt="Post Image"
|
||||
class="rounded-2xl shadow-2xl object-cover h-32 w-32 max-w-none transition-transform hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
: null}
|
||||
) : null
|
||||
}
|
||||
<div>
|
||||
<a href={postUrl} class="flex flex-col items-start">
|
||||
<h2 class="text-2xl font-bold" transition:name={post.data.title + "-title"}>{post.data.title}</h2>
|
||||
<P class="text-gray-500">{Intl.DateTimeFormat(astroI18n.locale, {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}).format(post.data.created)}</P>
|
||||
<P class="text-gray-500"
|
||||
>{
|
||||
Intl.DateTimeFormat(astroI18n.locale, {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}).format(post.data.created)
|
||||
}</P
|
||||
>
|
||||
<P>{post.data.description}</P>
|
||||
</a>
|
||||
<div class="mt-1" transition:name={post.data.title + "-tags"}>
|
||||
{post.data.tags.map((tag) => (
|
||||
<TagComponent tag={tag} />
|
||||
))}
|
||||
{post.data.tags.map((tag) => <TagComponent tag={tag} />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
|
||||
@@ -18,24 +18,35 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import {Input} from "@components/ui/input";
|
||||
import {Label} from "@components/ui/label";
|
||||
import {Popover, PopoverContent, PopoverTrigger} from "@components/ui/popover";
|
||||
import type {SWEvent} from "@type/event.ts"
|
||||
import { Input } from "@components/ui/input";
|
||||
import { Label } from "@components/ui/label";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover";
|
||||
import type { SWEvent } from "@type/event.ts";
|
||||
import DateTimePicker from "@components/ui/datetime-picker/DateTimePicker.svelte";
|
||||
import {fromAbsolute} from "@internationalized/date";
|
||||
import {Button} from "@components/ui/button";
|
||||
import {ChevronsUpDown} from "lucide-svelte";
|
||||
import {Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList} from "@components/ui/command";
|
||||
import {schemTypes} from "@stores/stores.ts";
|
||||
import { fromAbsolute } from "@internationalized/date";
|
||||
import { Button, buttonVariants } from "@components/ui/button";
|
||||
import { ChevronsUpDown } from "lucide-svelte";
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command";
|
||||
import { schemTypes } from "@stores/stores.ts";
|
||||
import Check from "lucide-svelte/icons/check";
|
||||
import {cn} from "@components/utils.ts";
|
||||
import {Switch} from "@components/ui/switch";
|
||||
import {eventRepo} from "@repo/event.ts";
|
||||
import { cn } from "@components/utils.ts";
|
||||
import { Switch } from "@components/ui/switch";
|
||||
import { eventRepo } from "@repo/event.ts";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@components/ui/alert-dialog";
|
||||
|
||||
const { event }: { event: SWEvent } = $props();
|
||||
|
||||
let rootEvent: SWEvent = $state(event)
|
||||
let rootEvent: SWEvent = $state(event);
|
||||
|
||||
let eventName = $state(rootEvent.name);
|
||||
let eventDeadline = $state(fromAbsolute(rootEvent.deadline, "Europe/Berlin"));
|
||||
@@ -45,13 +56,15 @@
|
||||
let eventSchematicType = $state(rootEvent.schemType);
|
||||
let eventPublicsOnly = $state(rootEvent.publicSchemsOnly);
|
||||
|
||||
let dirty = $derived(eventName !== rootEvent.name ||
|
||||
eventDeadline.toDate().getTime() !== rootEvent.deadline ||
|
||||
eventStart.toDate().getTime() !== rootEvent.start ||
|
||||
eventEnd.toDate().getTime() !== rootEvent.end ||
|
||||
eventTeamSize !== rootEvent.maxTeamMembers ||
|
||||
eventSchematicType !== rootEvent.schemType ||
|
||||
eventPublicsOnly !== rootEvent.publicSchemsOnly);
|
||||
let dirty = $derived(
|
||||
eventName !== rootEvent.name ||
|
||||
eventDeadline.toDate().getTime() !== rootEvent.deadline ||
|
||||
eventStart.toDate().getTime() !== rootEvent.start ||
|
||||
eventEnd.toDate().getTime() !== rootEvent.end ||
|
||||
eventTeamSize !== rootEvent.maxTeamMembers ||
|
||||
eventSchematicType !== rootEvent.schemType ||
|
||||
eventPublicsOnly !== rootEvent.publicSchemsOnly
|
||||
);
|
||||
|
||||
async function updateEvent() {
|
||||
rootEvent = await $eventRepo.updateEvent(event.id.toString(), {
|
||||
@@ -62,7 +75,7 @@
|
||||
maxTeamMembers: eventTeamSize,
|
||||
schemType: eventSchematicType,
|
||||
publicSchemsOnly: eventPublicsOnly,
|
||||
})
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -81,13 +94,8 @@
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
{#snippet child({ props })}
|
||||
<Button
|
||||
variant="outline"
|
||||
class="justify-between"
|
||||
{...props}
|
||||
role="combobox"
|
||||
>
|
||||
{$schemTypes.find(value => value.db === eventSchematicType)?.name || eventSchematicType || "Select a schematic type..."}
|
||||
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
||||
{$schemTypes.find((value) => value.db === eventSchematicType)?.name || eventSchematicType || "Select a schematic type..."}
|
||||
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
@@ -100,17 +108,12 @@
|
||||
<CommandGroup>
|
||||
{#each $schemTypes as type}
|
||||
<CommandItem
|
||||
value={type.db}
|
||||
onSelect={() => {
|
||||
eventSchematicType = type.db;
|
||||
}}
|
||||
value={type.db}
|
||||
onSelect={() => {
|
||||
eventSchematicType = type.db;
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
class={cn(
|
||||
"mr-2 size-4",
|
||||
eventSchematicType !== type.db && "text-transparent"
|
||||
)}
|
||||
/>
|
||||
<Check class={cn("mr-2 size-4", eventSchematicType !== type.db && "text-transparent")} />
|
||||
{type.name}
|
||||
</CommandItem>
|
||||
{/each}
|
||||
@@ -122,7 +125,19 @@
|
||||
<Label for="event-publics">Publics Schematics Only</Label>
|
||||
<Switch id="event-publics" bind:checked={eventPublicsOnly} />
|
||||
<div class="flex flex-row justify-end border-t pt-2 gap-4">
|
||||
<Button variant="destructive">Delete</Button>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger class={buttonVariants({ variant: "destructive" })}>Delete</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction disabled>Delete</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<Button disabled={!dirty} onclick={updateEvent}>Update</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,26 +18,27 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type {ExtendedEvent} from "@type/event";
|
||||
import {createSvelteTable, FlexRender} from "@components/ui/data-table";
|
||||
import {
|
||||
type ColumnFiltersState,
|
||||
getCoreRowModel, getFilteredRowModel,
|
||||
getPaginationRowModel, getSortedRowModel,
|
||||
type SortingState,
|
||||
} from "@tanstack/table-core";
|
||||
import { columns } from "./columns"
|
||||
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@components/ui/table";
|
||||
import type { ExtendedEvent } from "@type/event";
|
||||
import { createSvelteTable, FlexRender } from "@components/ui/data-table";
|
||||
import { type ColumnFiltersState, getCoreRowModel, getFilteredRowModel, getGroupedRowModel, getSortedRowModel, type RowSelectionState, type SortingState } from "@tanstack/table-core";
|
||||
import { columns } from "./columns";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@components/ui/table";
|
||||
import { Checkbox } from "@components/ui/checkbox";
|
||||
import { Menubar, MenubarContent, MenubarItem, MenubarGroup, MenubarGroupHeading, MenubarMenu, MenubarSeparator, MenubarTrigger } from "@components/ui/menubar";
|
||||
|
||||
let { data }: { data: ExtendedEvent } = $props();
|
||||
|
||||
let sorting = $state<SortingState>([]);
|
||||
let columnFilters = $state<ColumnFiltersState>([]);
|
||||
let selection = $state<RowSelectionState>({});
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data.fights;
|
||||
},
|
||||
initialState: {
|
||||
columnOrder: ["auswahl", "begegnung", "group"],
|
||||
},
|
||||
state: {
|
||||
get sorting() {
|
||||
return sorting;
|
||||
@@ -45,6 +46,12 @@
|
||||
get columnFilters() {
|
||||
return columnFilters;
|
||||
},
|
||||
get grouping() {
|
||||
return ["group"];
|
||||
},
|
||||
get rowSelection() {
|
||||
return selection;
|
||||
},
|
||||
},
|
||||
onSortingChange: (updater) => {
|
||||
if (typeof updater === "function") {
|
||||
@@ -60,13 +67,47 @@
|
||||
columnFilters = updater;
|
||||
}
|
||||
},
|
||||
onRowSelectionChange: (updater) => {
|
||||
if (typeof updater === "function") {
|
||||
selection = updater(selection);
|
||||
} else {
|
||||
selection = updater;
|
||||
}
|
||||
},
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getGroupedRowModel: getGroupedRowModel(),
|
||||
groupedColumnMode: "remove",
|
||||
getRowId: (row) => row.id.toString(),
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-fit">
|
||||
<Menubar>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Mehrfach Bearbeiten</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem disabled>Gruppe Ändern</MenubarItem>
|
||||
<MenubarItem disabled>Startzeit Verschieben</MenubarItem>
|
||||
<MenubarItem disabled>Spectate Port Ändern</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Erstellen</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem disabled>Fight Erstellen</MenubarItem>
|
||||
<MenubarGroup>
|
||||
<MenubarGroupHeading>Generatoren</MenubarGroupHeading>
|
||||
<MenubarItem disabled>Gruppenphase</MenubarItem>
|
||||
<MenubarItem disabled>K.O. Phase</MenubarItem>
|
||||
</MenubarGroup>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
</div>
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
@@ -74,10 +115,7 @@
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<TableHead>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
<FlexRender content={header.column.columnDef.header} context={header.getContext()} />
|
||||
{/if}
|
||||
</TableHead>
|
||||
{/each}
|
||||
@@ -85,23 +123,41 @@
|
||||
{/each}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<TableRow data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<TableCell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
{#each table.getRowModel().rows as groupRow (groupRow.id)}
|
||||
{#if groupRow.getIsGrouped()}
|
||||
<TableRow class="bg-muted font-bold">
|
||||
<TableCell colspan={columns.length}>
|
||||
<Checkbox
|
||||
checked={groupRow.getIsSelected()}
|
||||
indeterminate={groupRow.getIsSomeSelected() && !groupRow.getIsSelected()}
|
||||
onCheckedChange={() => groupRow.toggleSelected()}
|
||||
class="mr-4"
|
||||
/>
|
||||
Gruppe: {groupRow.getValue("group") ?? "Keine"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{#each groupRow.subRows as row (row.id)}
|
||||
<TableRow data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<TableCell>
|
||||
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
|
||||
</TableCell>
|
||||
{/each}
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableRow>
|
||||
{:else}
|
||||
<TableRow data-state={groupRow.getIsSelected() && "selected"}>
|
||||
{#each groupRow.getVisibleCells() as cell (cell.id)}
|
||||
<TableCell>
|
||||
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
|
||||
</TableCell>
|
||||
{/each}
|
||||
</TableRow>
|
||||
{/if}
|
||||
{:else}
|
||||
<TableRow>
|
||||
<TableCell colspan={columns.length} class="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
<TableCell colspan={columns.length} class="h-24 text-center">No results.</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Table>
|
||||
|
||||
@@ -18,14 +18,13 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type {ExtendedEvent} from "@type/event.ts";
|
||||
import type { ExtendedEvent } from "@type/event.ts";
|
||||
import EventEdit from "@components/moderator/pages/event/EventEdit.svelte";
|
||||
import EventFightList from "@components/moderator/pages/event/EventFightList.svelte";
|
||||
import RefereesList from "@components/moderator/pages/event/RefereesList.svelte";
|
||||
import TeamTable from "@components/moderator/pages/event/TeamTable.svelte";
|
||||
|
||||
const {
|
||||
event
|
||||
}: { event: ExtendedEvent } = $props();
|
||||
const { event }: { event: ExtendedEvent } = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col m-4 p-4 rounded-md border gap-4">
|
||||
@@ -35,12 +34,12 @@
|
||||
<EventEdit event={event.event} />
|
||||
</div>
|
||||
<div class="md:ml-4 md:pl-4 md:border-l md:w-1/3">
|
||||
<h2>Teams</h2>
|
||||
|
||||
<h2 class="text-xl font-bold mb-4">Teams</h2>
|
||||
<TeamTable {event} />
|
||||
</div>
|
||||
<div class="md:ml-4 md:pl-4 md:border-l md:w-1/3">
|
||||
<h2>Referees</h2>
|
||||
<RefereesList event={event} />
|
||||
<h2 class="text-xl font-bold mb-4">Referees</h2>
|
||||
<RefereesList {event} />
|
||||
</div>
|
||||
</div>
|
||||
<EventFightList data={event} />
|
||||
|
||||
@@ -18,38 +18,35 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@components/ui/table/index.js";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@components/ui/command/index.js";
|
||||
import {Popover, PopoverContent, PopoverTrigger} from "@components/ui/popover/index.js";
|
||||
import {Button} from "@components/ui/button/index.js";
|
||||
import type {ExtendedEvent} from "@type/event.ts";
|
||||
import { Table, TableBody, TableCell, TableCaption, TableHead, TableHeader, TableRow } from "@components/ui/table";
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command/index.js";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover/index.js";
|
||||
import { Button } from "@components/ui/button/index.js";
|
||||
import type { ExtendedEvent } from "@type/event.ts";
|
||||
import { eventRepo } from "@repo/event";
|
||||
import { players } from "@stores/stores"
|
||||
import { players } from "@stores/stores";
|
||||
|
||||
const {
|
||||
event
|
||||
}: { event: ExtendedEvent } = $props();
|
||||
const { event }: { event: ExtendedEvent } = $props();
|
||||
|
||||
let referees = $state(event.event.referees)
|
||||
let referees = $state(event.event.referees);
|
||||
|
||||
async function addReferee(value: string) {
|
||||
referees = (await $eventRepo.updateEvent(event.event.id.toString(), {
|
||||
addReferee: [value]
|
||||
})).referees;
|
||||
referees = (
|
||||
await $eventRepo.updateEvent(event.event.id.toString(), {
|
||||
addReferee: [value],
|
||||
})
|
||||
).referees;
|
||||
}
|
||||
|
||||
async function removeReferee(value: string) {
|
||||
referees = (await $eventRepo.updateEvent(event.event.id.toString(), {
|
||||
removeReferee: [value]
|
||||
})).referees;
|
||||
referees = (
|
||||
await $eventRepo.updateEvent(event.event.id.toString(), {
|
||||
removeReferee: [value],
|
||||
})
|
||||
).referees;
|
||||
}
|
||||
|
||||
let playerSearch = $state("");
|
||||
</script>
|
||||
|
||||
<Table>
|
||||
@@ -69,24 +66,27 @@
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
<Popover>
|
||||
<TableCaption>
|
||||
<PopoverTrigger>
|
||||
<Button>Add</Button>
|
||||
</PopoverTrigger>
|
||||
</TableCaption>
|
||||
<PopoverContent class="p-0">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput bind:value={playerSearch} placeholder="Search players..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No Players found :(</CommandEmpty>
|
||||
<CommandGroup heading="Players">
|
||||
{#each $players
|
||||
.filter((v) => v.name.includes(playerSearch))
|
||||
.filter((v, i) => i < 50)
|
||||
.filter((v) => !referees.some((k) => k.uuid === v.uuid)) as player (player.uuid)}
|
||||
<CommandItem value={player.name} onSelect={() => addReferee(player.uuid)} keywords={[player.uuid]}>{player.name}</CommandItem>
|
||||
{/each}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</Table>
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<Button>
|
||||
Add
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search players..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No Players found :(</CommandEmpty>
|
||||
<CommandGroup heading="Players">
|
||||
{#each $players.filter(v => v.perms.length > 0).filter(v => !referees.some(k => k.uuid === v.uuid)) as player (player.uuid)}
|
||||
<CommandItem value={player.uuid} onSelect={() => addReferee(player.uuid)}>{player.name}</CommandItem>
|
||||
{/each}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
55
src/components/moderator/pages/event/TeamTable.svelte
Normal file
55
src/components/moderator/pages/event/TeamTable.svelte
Normal file
@@ -0,0 +1,55 @@
|
||||
<!--
|
||||
- This file is a part of the SteamWar software.
|
||||
-
|
||||
- Copyright (C) 2025 SteamWar.de-Serverteam
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as published by
|
||||
- the Free Software Foundation, either version 3 of the License, or
|
||||
- (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Button } from "@components/ui/button";
|
||||
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell, TableCaption } from "@components/ui/table";
|
||||
import type { ExtendedEvent } from "@type/event.ts";
|
||||
|
||||
const { event }: { event: ExtendedEvent } = $props();
|
||||
</script>
|
||||
|
||||
<Table>
|
||||
<TableCaption>
|
||||
<Button disabled>Add Team</Button>
|
||||
</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Team</TableHead>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Action</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each event.teams as team (team.id)}
|
||||
<TableRow>
|
||||
<TableCell>{team.kuerzel}</TableCell>
|
||||
<TableCell>{team.name}</TableCell>
|
||||
<TableCell>
|
||||
<Button disabled>Remove</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
{#if event.teams.length === 0}
|
||||
<TableRow>
|
||||
<TableCell class="text-center col-span-3">No teams available</TableCell>
|
||||
</TableRow>
|
||||
{/if}
|
||||
</TableBody>
|
||||
</Table>
|
||||
@@ -17,16 +17,64 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type {ColumnDef} from "@tanstack/table-core";
|
||||
import type {EventFight} from "@type/event.ts";
|
||||
import { Checkbox } from "@components/ui/checkbox";
|
||||
import { renderComponent } from "@components/ui/data-table";
|
||||
import type { ColumnDef } from "@tanstack/table-core";
|
||||
import type { EventFight } from "@type/event.ts";
|
||||
|
||||
export const columns: ColumnDef<EventFight> = [
|
||||
{
|
||||
accessorFn: (r) => r.blueTeam.name,
|
||||
header: "Team Blue",
|
||||
id: "auswahl",
|
||||
header: ({ table }) => {
|
||||
return renderComponent(Checkbox, {
|
||||
checked: table.getIsAllRowsSelected(),
|
||||
indeterminate: table.getIsSomeRowsSelected(),
|
||||
onCheckedChange: () => {
|
||||
if (!table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected()) {
|
||||
const now = new Date();
|
||||
const rows = table.getRowModel().rows.filter((row) => new Date(row.original.date) > now);
|
||||
|
||||
if (rows.length > 0) {
|
||||
rows.forEach((row) => {
|
||||
row.toggleSelected();
|
||||
});
|
||||
} else {
|
||||
table.toggleAllRowsSelected(true);
|
||||
}
|
||||
} else if (table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected()) {
|
||||
table.toggleAllRowsSelected(true);
|
||||
} else {
|
||||
table.toggleAllRowsSelected(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(Checkbox, {
|
||||
checked: row.getIsSelected(),
|
||||
onCheckedChange: row.getToggleSelectedHandler(),
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorFn: (r) => r.redTeam.name,
|
||||
header: "Team Red",
|
||||
accessorFn: (r) => r.blueTeam.name + " vs " + r.redTeam.name,
|
||||
id: "begegnung",
|
||||
header: "Begegnung",
|
||||
},
|
||||
];
|
||||
{
|
||||
header: "Gruppe",
|
||||
accessorKey: "group",
|
||||
id: "group",
|
||||
},
|
||||
{
|
||||
header: "Datum",
|
||||
accessorKey: "start",
|
||||
id: "start",
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue("start")).toLocaleString("de-DE", {
|
||||
dateStyle: "short",
|
||||
timeStyle: "medium",
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -3,19 +3,11 @@
|
||||
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = AlertDialogPrimitive.ActionProps;
|
||||
type $$Events = AlertDialogPrimitive.ActionEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AlertDialogPrimitive.ActionProps = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialogPrimitive.Action
|
||||
class={cn(buttonVariants(), className)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
let:builder
|
||||
>
|
||||
<slot {builder} />
|
||||
</AlertDialogPrimitive.Action>
|
||||
<AlertDialogPrimitive.Action bind:ref class={cn(buttonVariants(), className)} {...restProps} />
|
||||
|
||||
@@ -3,19 +3,15 @@
|
||||
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = AlertDialogPrimitive.CancelProps;
|
||||
type $$Events = AlertDialogPrimitive.CancelEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AlertDialogPrimitive.CancelProps = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialogPrimitive.Cancel
|
||||
bind:ref
|
||||
class={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
let:builder
|
||||
>
|
||||
<slot {builder} />
|
||||
</AlertDialogPrimitive.Cancel>
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||
import * as AlertDialog from "./index.js";
|
||||
import { cn, flyAndScale } from "$lib/components/utils.js";
|
||||
import { AlertDialog as AlertDialogPrimitive, type WithoutChild } from "bits-ui";
|
||||
import AlertDialogOverlay from "./alert-dialog-overlay.svelte";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = AlertDialogPrimitive.ContentProps;
|
||||
|
||||
export let transition: $$Props["transition"] = flyAndScale;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = undefined;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
portalProps,
|
||||
...restProps
|
||||
}: WithoutChild<AlertDialogPrimitive.ContentProps> & {
|
||||
portalProps?: AlertDialogPrimitive.PortalProps;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialog.Portal>
|
||||
<AlertDialog.Overlay />
|
||||
<AlertDialogPrimitive.Portal {...portalProps}>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
bind:ref
|
||||
class={cn(
|
||||
"bg-background fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg md:w-full",
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</AlertDialogPrimitive.Content>
|
||||
</AlertDialog.Portal>
|
||||
{...restProps}
|
||||
/>
|
||||
</AlertDialogPrimitive.Portal>
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = AlertDialogPrimitive.DescriptionProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AlertDialogPrimitive.DescriptionProps = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialogPrimitive.Description
|
||||
bind:ref
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</AlertDialogPrimitive.Description>
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
||||
{...$$restProps}
|
||||
{...restProps}
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div class={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...$$restProps}>
|
||||
<slot />
|
||||
<div
|
||||
bind:this={ref}
|
||||
class={cn("flex flex-col space-y-2 text-center sm:text-left", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||
import { fade } from "svelte/transition";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = AlertDialogPrimitive.OverlayProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let transition: $$Props["transition"] = fade;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = {
|
||||
duration: 150,
|
||||
};
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AlertDialogPrimitive.OverlayProps = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialogPrimitive.Overlay
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
class={cn("bg-background/80 fixed inset-0 z-50 backdrop-blur-sm ", className)}
|
||||
{...$$restProps}
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = AlertDialogPrimitive.TitleProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let level: $$Props["level"] = "h3";
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
level = 3,
|
||||
...restProps
|
||||
}: AlertDialogPrimitive.TitleProps = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialogPrimitive.Title class={cn("text-lg font-semibold", className)} {level} {...$$restProps}>
|
||||
<slot />
|
||||
</AlertDialogPrimitive.Title>
|
||||
<AlertDialogPrimitive.Title
|
||||
bind:ref
|
||||
class={cn("text-lg font-semibold", className)}
|
||||
{level}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||
|
||||
import Title from "./alert-dialog-title.svelte";
|
||||
import Action from "./alert-dialog-action.svelte";
|
||||
import Cancel from "./alert-dialog-cancel.svelte";
|
||||
import Portal from "./alert-dialog-portal.svelte";
|
||||
import Footer from "./alert-dialog-footer.svelte";
|
||||
import Header from "./alert-dialog-header.svelte";
|
||||
import Overlay from "./alert-dialog-overlay.svelte";
|
||||
@@ -12,6 +10,7 @@ import Description from "./alert-dialog-description.svelte";
|
||||
|
||||
const Root = AlertDialogPrimitive.Root;
|
||||
const Trigger = AlertDialogPrimitive.Trigger;
|
||||
const Portal = AlertDialogPrimitive.Portal;
|
||||
|
||||
export {
|
||||
Root,
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { Checkbox as CheckboxPrimitive } from "bits-ui";
|
||||
import Check from "lucide-svelte/icons/check";
|
||||
import Minus from "lucide-svelte/icons/minus";
|
||||
import { Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild } from "bits-ui";
|
||||
import Check from "@lucide/svelte/icons/check";
|
||||
import Minus from "@lucide/svelte/icons/minus";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = CheckboxPrimitive.Props;
|
||||
type $$Events = CheckboxPrimitive.Events;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let checked: $$Props["checked"] = false;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
checked = $bindable(false),
|
||||
indeterminate = $bindable(false),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
|
||||
</script>
|
||||
|
||||
<CheckboxPrimitive.Root
|
||||
bind:ref
|
||||
class={cn(
|
||||
"border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer box-content h-4 w-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
|
||||
"border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer box-content size-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
|
||||
className
|
||||
)}
|
||||
bind:checked
|
||||
{...$$restProps}
|
||||
on:click
|
||||
bind:indeterminate
|
||||
{...restProps}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
class={cn("flex h-4 w-4 items-center justify-center text-current")}
|
||||
let:isChecked
|
||||
let:isIndeterminate
|
||||
>
|
||||
{#if isChecked}
|
||||
<Check class="h-3.5 w-3.5" />
|
||||
{:else if isIndeterminate}
|
||||
<Minus class="h-3.5 w-3.5" />
|
||||
{/if}
|
||||
</CheckboxPrimitive.Indicator>
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<div class="flex size-4 items-center justify-center text-current">
|
||||
{#if indeterminate}
|
||||
<Minus class="size-3.5" />
|
||||
{:else}
|
||||
<Check class={cn("size-3.5", !checked && "text-transparent")} />
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
</CheckboxPrimitive.Root>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
|
||||
import Root from "./menubar.svelte";
|
||||
import CheckboxItem from "./menubar-checkbox-item.svelte";
|
||||
import Content from "./menubar-content.svelte";
|
||||
import Item from "./menubar-item.svelte";
|
||||
import Label from "./menubar-label.svelte";
|
||||
import GroupHeading from "./menubar-group-heading.svelte";
|
||||
import RadioItem from "./menubar-radio-item.svelte";
|
||||
import Separator from "./menubar-separator.svelte";
|
||||
import Shortcut from "./menubar-shortcut.svelte";
|
||||
@@ -22,7 +21,7 @@ export {
|
||||
CheckboxItem,
|
||||
Content,
|
||||
Item,
|
||||
Label,
|
||||
GroupHeading,
|
||||
RadioItem,
|
||||
Separator,
|
||||
Shortcut,
|
||||
@@ -38,7 +37,7 @@ export {
|
||||
CheckboxItem as MenubarCheckboxItem,
|
||||
Content as MenubarContent,
|
||||
Item as MenubarItem,
|
||||
Label as MenubarLabel,
|
||||
GroupHeading as MenubarGroupHeading,
|
||||
RadioItem as MenubarRadioItem,
|
||||
Separator as MenubarSeparator,
|
||||
Shortcut as MenubarShortcut,
|
||||
|
||||
@@ -1,35 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import Check from "lucide-svelte/icons/check";
|
||||
import { Menubar as MenubarPrimitive, type WithoutChildrenOrChild } from "bits-ui";
|
||||
import Check from "@lucide/svelte/icons/check";
|
||||
import Minus from "@lucide/svelte/icons/minus";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type $$Props = MenubarPrimitive.CheckboxItemProps;
|
||||
type $$Events = MenubarPrimitive.CheckboxItemEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let checked: $$Props["checked"] = false;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
checked = $bindable(false),
|
||||
indeterminate = $bindable(false),
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<MenubarPrimitive.CheckboxItemProps> & {
|
||||
children?: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.CheckboxItem
|
||||
bind:ref
|
||||
bind:checked
|
||||
bind:indeterminate
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
on:pointerdown
|
||||
{...$$restProps}
|
||||
{...restProps}
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<MenubarPrimitive.CheckboxIndicator>
|
||||
<Check class="h-4 w-4" />
|
||||
</MenubarPrimitive.CheckboxIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if indeterminate}
|
||||
<Minus class="size-4" />
|
||||
{:else}
|
||||
<Check class={cn("size-4", !checked && "text-transparent")} />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</MenubarPrimitive.CheckboxItem>
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/components/utils.js";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = MenubarPrimitive.ContentProps;
|
||||
type $$Events = MenubarPrimitive.ContentEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let sideOffset: $$Props["sideOffset"] = 8;
|
||||
export let alignOffset: $$Props["alignOffset"] = -4;
|
||||
export let align: $$Props["align"] = "start";
|
||||
export let side: $$Props["side"] = "bottom";
|
||||
export let transition: $$Props["transition"] = flyAndScale;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = undefined;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
sideOffset = 8,
|
||||
alignOffset = -4,
|
||||
align = "start",
|
||||
side = "bottom",
|
||||
portalProps,
|
||||
...restProps
|
||||
}: MenubarPrimitive.ContentProps & {
|
||||
portalProps?: MenubarPrimitive.PortalProps;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Content
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
{sideOffset}
|
||||
{align}
|
||||
{alignOffset}
|
||||
{side}
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground z-50 min-w-[12rem] rounded-md border p-1 shadow-md focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:keydown
|
||||
>
|
||||
<slot />
|
||||
</MenubarPrimitive.Content>
|
||||
<MenubarPrimitive.Portal {...portalProps}>
|
||||
<MenubarPrimitive.Content
|
||||
bind:ref
|
||||
{sideOffset}
|
||||
{align}
|
||||
{alignOffset}
|
||||
{side}
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground z-50 min-w-[12rem] rounded-md border p-1 shadow-md focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</MenubarPrimitive.Portal>
|
||||
|
||||
19
src/components/ui/menubar/menubar-group-heading.svelte
Normal file
19
src/components/ui/menubar/menubar-group-heading.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset = undefined,
|
||||
...restProps
|
||||
}: MenubarPrimitive.GroupHeadingProps & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.GroupHeading
|
||||
bind:ref
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -2,30 +2,22 @@
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = MenubarPrimitive.ItemProps & {
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset = undefined,
|
||||
...restProps
|
||||
}: MenubarPrimitive.ItemProps & {
|
||||
inset?: boolean;
|
||||
};
|
||||
type $$Events = MenubarPrimitive.ItemEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let inset: $$Props["inset"] = undefined;
|
||||
export { className as class };
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Item
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
on:pointerdown
|
||||
>
|
||||
<slot />
|
||||
</MenubarPrimitive.Item>
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,35 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import Circle from "lucide-svelte/icons/circle";
|
||||
import { Menubar as MenubarPrimitive, type WithoutChild } from "bits-ui";
|
||||
import Circle from "@lucide/svelte/icons/circle";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = MenubarPrimitive.RadioItemProps;
|
||||
type $$Events = MenubarPrimitive.RadioItemEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let value: $$Props["value"];
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChild<MenubarPrimitive.RadioItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.RadioItem
|
||||
{value}
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
on:pointerdown
|
||||
{...restProps}
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<MenubarPrimitive.RadioIndicator>
|
||||
<Circle class="h-2 w-2 fill-current" />
|
||||
</MenubarPrimitive.RadioIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
{#snippet children({ checked })}
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if checked}
|
||||
<Circle class="size-2 fill-current" />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.({ checked })}
|
||||
{/snippet}
|
||||
</MenubarPrimitive.RadioItem>
|
||||
|
||||
@@ -2,10 +2,15 @@
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = MenubarPrimitive.SeparatorProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: MenubarPrimitive.SeparatorProps = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Separator class={cn("bg-muted -mx-1 my-1 h-px", className)} {...$$restProps} />
|
||||
<MenubarPrimitive.Separator
|
||||
bind:ref
|
||||
class={cn("bg-muted -mx-1 my-1 h-px", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
|
||||
{...$$restProps}
|
||||
{...restProps}
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</span>
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/components/utils.js";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = MenubarPrimitive.SubContentProps;
|
||||
type $$Events = MenubarPrimitive.SubContentEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let transition: $$Props["transition"] = flyAndScale;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = { x: -10, y: 0 };
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: MenubarPrimitive.SubContentProps = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.SubContent
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
bind:ref
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground z-50 min-w-max rounded-md border p-1 focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:focusout
|
||||
on:pointermove
|
||||
on:keydown
|
||||
>
|
||||
<slot />
|
||||
</MenubarPrimitive.SubContent>
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import ChevronRight from "lucide-svelte/icons/chevron-right";
|
||||
import { Menubar as MenubarPrimitive, type WithoutChild } from "bits-ui";
|
||||
import ChevronRight from "@lucide/svelte/icons/chevron-right";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = MenubarPrimitive.SubTriggerProps & {
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset = undefined,
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChild<MenubarPrimitive.SubTriggerProps> & {
|
||||
inset?: boolean;
|
||||
};
|
||||
type $$Events = MenubarPrimitive.SubTriggerEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let inset: $$Props["inset"] = undefined;
|
||||
export { className as class };
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.SubTrigger
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
on:click
|
||||
{...$$restProps}
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
{...restProps}
|
||||
>
|
||||
<slot />
|
||||
<ChevronRight class="ml-auto h-4 w-4" />
|
||||
{@render children?.()}
|
||||
<ChevronRight class="ml-auto size-4" />
|
||||
</MenubarPrimitive.SubTrigger>
|
||||
|
||||
@@ -2,22 +2,18 @@
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = MenubarPrimitive.TriggerProps;
|
||||
type $$Events = MenubarPrimitive.TriggerEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: MenubarPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Trigger
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none",
|
||||
className
|
||||
)}
|
||||
on:click
|
||||
on:keydown
|
||||
on:pointerenter
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</MenubarPrimitive.Trigger>
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/components/utils.js";
|
||||
|
||||
type $$Props = MenubarPrimitive.Props;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: MenubarPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Root
|
||||
bind:ref
|
||||
class={cn("bg-background flex h-10 items-center space-x-1 rounded-md border p-1", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</MenubarPrimitive.Root>
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Spacheship Event Ankündigung und Regelwerk
|
||||
description: Alle Infos, Regeln und Termine zum ersten Spaceship-Event am 19.06.2025 – jetzt mit neuer TNT-Physik und ohne Gravitation!
|
||||
slug: spacheship-event-ankuendigung-und-regelwerk
|
||||
tags:
|
||||
- spaceship
|
||||
- event
|
||||
created: 2025-05-25
|
||||
key: spacheship-event-ankuendigung-und-regelwerk
|
||||
---
|
||||
|
||||
**Ahoi Community**
|
||||
|
||||
Dieser Post dient zur Fragenklärung, Updates zum Event sowie als Ort für Diskussionen bezüglich des Events.
|
||||
|
||||
Am 19.06. findet des erste Spaceship Event statt. Dieser Modus soll Airship ersetzen und hat die Besonderheit, dass TNT (und Sand) keine Gravitation hat. Gezündetes, ungebremstes TNT fliegt durch den typischen Sprung bei der Zündung zum Beispiel mit gleichbleibender Geschwindigkeit schräg nach oben.
|
||||
|
||||
Die neue Physik nutzt ihr mit ***/nogravity*** auf dem Bau
|
||||
|
||||
### Alle Infos zum Event:
|
||||
|
||||
- Die Technik und das Design der Publics werden nochmal seperat Public gestellt. So könnt ihr, wenn ihr keine oder weniger eigene Technik bauen wollt, einfacher die Public technik verwenden.
|
||||
- Das Event findet am 19.06.2025 um 16 Uhr statt
|
||||
- Es gibt kein Entern
|
||||
- Kits können wie bei WG und MWG selbst erstellt werden
|
||||
- es wird Redstone TKO verwendet
|
||||
- Das Event findet in der 1.20 statt
|
||||
|
||||
- **Einsendeschluss** am 24.06.2025 23:59 Uhr
|
||||
- Bis zu diesem Zeitpunkt muss eine **dem Modus entsprechende Kampfmaschine** vorhanden sein
|
||||
- Bis zum 27.06.2025 23:59 Uhr **Hotfix-Phase**
|
||||
- In dieser Phase dürfen lediglich Hotfixes eingesendet werden. Das bedeutet: Die Kampfmaschine darf **keine signifikanten Änderungen** erfahren. Es dürfen z. B. keine ganzen Kanonen ausgetauscht oder die Aufstellung verändert werden.
|
||||
- Die Teams ernennen jeweils einen Repräsentanten der das jeweilige Team vertritt und **gut erreichbar** ist
|
||||
- Es dürfen Maximal 2 Spieler pro Team an einem fight teilnehmen
|
||||
|
||||
### RW Änderungen:
|
||||
|
||||
- Es dürfen keine Blöcke über die Grenze des Designbereiches bewegt werden
|
||||
- Das hat zur Folge: Kein slime und honey Limit
|
||||
- Wasser darf in Kanonen verwendet werden
|
||||
- Dadurch könnt ihr TNT einfacher komprimieren. Wasser muss nicht mehr den Zweck erfüllen, die Kanone vor eigenem Schaden zu bewahren
|
||||
- 32 Projektile pro Seite
|
||||
|
||||
# Regelwerk für das Event:
|
||||
|
||||
## Definitionen
|
||||
|
||||
Ein AirShip ist eine beidseitig bewaffnete Struktur in Minecraft 1.19 und sieht flugfähig aus.
|
||||
|
||||
## Maße
|
||||
|
||||
|
||||
**Technikbereich**:
|
||||
- 70 lang
|
||||
- 35 breit
|
||||
- 24 hoch
|
||||
|
||||
**Designbereich**:
|
||||
- 130 lang
|
||||
- 51 breit
|
||||
- 32 hoch
|
||||
|
||||
Entfernung von Mitte zu Mitte des Gegners: 100 Block
|
||||
|
||||
Es dürfen keine Blöcke über die Grenze des Designbereiches bewegt werden
|
||||
|
||||
## Projektile
|
||||
|
||||
Ein Projektil ist ein gezündetes TNT, welches in die gegnerische Hälfte wechselt. Eine Kanone ist eine Vorrichtung zum Beschleunigen von Projektilen. Es ist verboten, mehrere Kanonen als eine Einzige auszugeben.
|
||||
|
||||
Jeder Seite stehen bis zu 32 Projektile zur Verfügung. Sie müssen auf der dem Gegner zugewandten Seite oder, wenn beidseitig, in der Mitte gezündet werden
|
||||
|
||||
## Siegesbedingung
|
||||
|
||||
- Ein AirShip wird zu 60% (nach prozentualer Redstonekomponenten) zerstört.
|
||||
- Alle Kämpfer eines AirShips sind tot.
|
||||
- Nach 10 Minuten wird ein Unentschieden eingeleitet.
|
||||
- Der Kampfleiter entscheidet.
|
||||
|
||||
## Blöcke
|
||||
|
||||
Es dürfen maximal je 30 TNT und 32 Werfer verbaut werden. Verbaute Blöcke dürfen einen TNT-Widerstand von maximal 6 haben. Ausgenommen davon ist Wasser, welches ausschließlich in Kanonen verbaut werden darf.
|
||||
|
||||
Inventar-Blöcke dürfen nur mit Blumen, Honigflaschen und Pferderüstungen gefüllt sein. Zusätzlich dürfen sich in Kisten und Fässern TNT sowie in Werfern 2·64 Feuerbälle, 2·64 reguläre Pfeile oder 1 Eimer zum Aufheben von powdered Snow befinden.
|
||||
|
||||
Zusätzlich verboten: Monster Spawner, Eis, Nether Portal, Silberfischsteine, Leuchtfeuer, unsichtbare Blöcke (Ausnahme: structure_void) Das Duplizieren von Blöcken und Entities ist verboten.
|
||||
Blöcke aus dem eigenen AS dürfen nicht in großer Menge entfernt werden.
|
||||
Bug-Using ist nicht erwünscht.
|
||||
35
src/content/announcements/de/sw-arcade-fightplan.md
Normal file
35
src/content/announcements/de/sw-arcade-fightplan.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: SteamWar Arcade Eventplan
|
||||
key: sw-arcade-eventplan
|
||||
description: Der Eventplan für SteamWar Arcade
|
||||
created: 2025-05-14
|
||||
tags:
|
||||
- event
|
||||
- towerrun
|
||||
- misslewars
|
||||
- tntleague
|
||||
- arcade
|
||||
image: ../../../images/sw-arcade-image.png
|
||||
---
|
||||
|
||||
### Infos:
|
||||
Eventleitung: Chaoscaot
|
||||
|
||||
Fights werden nach Möglichkeit mit einer Pause von 10 Minuten vorverschoben.
|
||||
|
||||
# Kämpfe
|
||||
## Punkte aus der Gruppenphase
|
||||
|
||||
<group-table data-event="70"> </group-table>
|
||||
|
||||
### Fights
|
||||
|
||||
<fight-table data-event="70" data-group="Gruppe 1"> </fight-table>
|
||||
|
||||
## Auswahl des Spielmodis
|
||||
|
||||
Die Teams können Ingame über den Befehl `/event vote` für einen Spielmodus abstimmen. Diese Abstimmung wird für die nächsten Fights gespeichert.
|
||||
|
||||
Wenn die Teams sich einig sind im Spielmodus, wird dieser gespielt. Wenn nicht, dann wird der von keinem Team gewählte Modus gespielt.
|
||||
|
||||
Sollte ein Team nicht abstimmen, wird zufällig eine Stimme abgegeben.
|
||||
@@ -39,7 +39,7 @@ Die Brücke muss folgende Voraussetzungen erfüllen:
|
||||
|
||||
- Min. 25 m² (1 Block = 1 Meter)
|
||||
- Ein Sichtfenster auf das gegnerische MicroWarGear (Ausnahme bei Freiluftbrücken mit direkter Sicht auf den Gegner, diese benötigen kein extra Sichtfenster)
|
||||
- Die Ansteuerung für min. 4 vom Gegner aus sichtbare Frontscheinwerfer.
|
||||
- Die Ansteuerung für min. 2 vom Gegner aus sichtbare Frontscheinwerfer.
|
||||
- Diese müssen ihren Zustand beibehalten, bis der/ die Mechanismen erneut manuell betätigt wird/ werden
|
||||
- Die einzige Ansteuerung für ggf. vorhandene Werfer, die mit Pfeilen oder Feuerbällen den Gegner beschießen
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ Eine Kanone ist eine Vorrichtung zum Beschleunigen von Projektilen. Eine Kanone
|
||||
|
||||
## Maße
|
||||
|
||||
- Länge: 230 Block
|
||||
- Breite: 35 Block (+ 4 Block Design pro Seite)
|
||||
- Höhe: 30 Block + 20 Block Design
|
||||
- Tiefe: Bis zu 8 Block unter dem Meeresspiegel
|
||||
- Länge: 230 Block
|
||||
- Breite: 35 Block (+ 4 Block Design pro Seite)
|
||||
- Höhe: 30 Block + 20 Block Design
|
||||
- Tiefe: Bis zu 8 Block unter dem Meeresspiegel
|
||||
|
||||
Bei jedem WarShip müssen sich mindestens 10% der absoluten Blöcke (45.000 Blöcke) über der Wasserlinie befinden.
|
||||
|
||||
@@ -45,20 +45,22 @@ Wasser darf nicht zum Schutz des eigenen WarShips missbraucht werden.
|
||||
Größere Hohlräume im Rumpf zum Ausweichen feindlicher Schüsse sind nicht gestattet, auch nicht während des Kampfes. Jedes WarShip braucht eine Flagge.
|
||||
|
||||
Ein WarShip benötigt einen fortbewegungsfähigen Rumpf mit entsprechendem Antrieb (z.B. Segel, Schiffsschrauben).
|
||||
Der Rumpf muss mindestens einen Block tief unter Wasser sowie mindestens 5 Block über dem Meeresspiegel sein (dies gilt auch während des Kampfes relativ zur Wasseroberfläche). Der Rumpf darf max. 16 Block hoch sein.
|
||||
Der Rumpf muss mindestens einen Block tief unter Wasser sowie mindestens 5 Block über dem Meeresspiegel sein (dies gilt auch während des Kampfes relativ zur Wasseroberfläche). Der Rumpf wird ab Wasserienie gemessen und darf max. 16 Blöcke hoch sein.
|
||||
|
||||
Jedes WarShip benötigt eine Brücke, welche die folgenden Kriterien erfüllt:
|
||||
|
||||
- Min. 50 begehbare Blöcke
|
||||
- Min. 2 Block hoch im gesamten Brückenraum
|
||||
- Ansteuerung von min. zwei zum Gegner gewandten Scheinwerfer
|
||||
- Optische Brückeneinrichtung
|
||||
- Min. 50 begehbare Blöcke
|
||||
- Min. 2 Block hoch im gesamten Brückenraum
|
||||
- Ansteuerung von min. zwei zum Gegner gewandten Scheinwerfer
|
||||
- Optische Brückeneinrichtung
|
||||
|
||||
## Anti-Lag-Regeln
|
||||
|
||||
Clocks müssen sich mit Ende ihres Einsatzzweckes selbst abschalten.
|
||||
|
||||
Sämtliche Redstonetechnik zum Schutz des eigenen WarShips muss ihre Aktivität vor dem Verteilen der Kits eingestellt haben.
|
||||
Raketen und Flugmaschinen
|
||||
|
||||
## Raketen und Flugmaschinen
|
||||
|
||||
Ein WarShip darf sich maximal 12 Block vom Technikbereich an weit ausfahren, davon ausgenommen sind Raketen und Flugmaschinen. Raketen und Flugmaschinen dürfen sich im Flug nicht in mehrere Schleim/Honigfahrzeuge aufteilen.
|
||||
|
||||
@@ -74,6 +76,6 @@ Es dürften maximal 1000 der vorverbauten Slime- + Honig- + TNT-Blöcke das WarS
|
||||
|
||||
Der Kampf endet, wenn:
|
||||
|
||||
- Der Kampf länger als 20 Minuten dauert
|
||||
- Der Anführer eines Teams tot ist
|
||||
- Ein WarShip zu 7% beschädigt wurde
|
||||
- Der Kampf länger als 20 Minuten dauert
|
||||
- Der Anführer eines Teams tot ist
|
||||
- Ein WarShip zu 7% beschädigt wurde
|
||||
|
||||
@@ -3,7 +3,7 @@ import "$lib/styles/app.css";
|
||||
import { astroI18n } from "astro-i18n";
|
||||
import { SEO } from "astro-seo";
|
||||
import { ClientRouter } from "astro:transitions";
|
||||
const { title, description, clientSideRouter = true } = Astro.props.frontmatter || Astro.props;
|
||||
const { title, description, clientSideRouter = true, autoDarkMode = true } = Astro.props.frontmatter || Astro.props;
|
||||
import "../../public/fonts/roboto/roboto.css";
|
||||
---
|
||||
|
||||
@@ -32,11 +32,13 @@ import "../../public/fonts/roboto/roboto.css";
|
||||
}))}
|
||||
/>
|
||||
|
||||
<script is:inline data-astro-rerun>
|
||||
if (localStorage["theme-mode"] === "light" || (!("theme-mode" in localStorage) && window.matchMedia("(prefers-color-scheme: light)").matches)) {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
</script>
|
||||
{autoDarkMode && (
|
||||
<script is:inline data-astro-rerun>
|
||||
if (localStorage["theme-mode"] === "light" || (!("theme-mode" in localStorage) && window.matchMedia("(prefers-color-scheme: light)").matches)) {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
</script>
|
||||
)}
|
||||
|
||||
<slot name="head" />
|
||||
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
---
|
||||
import {Image} from "astro:assets";
|
||||
import { Image } from "astro:assets";
|
||||
import Basic from "./Basic.astro";
|
||||
import "../styles/button.css";
|
||||
import localLogo from "../images/logo.png";
|
||||
import {YoutubeSolid, DiscordSolid, FileCodeSolid} from "flowbite-svelte-icons";
|
||||
import {t} from "astro-i18n";
|
||||
import {l} from "../util/util";
|
||||
import { YoutubeSolid, DiscordSolid, FileCodeSolid } from "flowbite-svelte-icons";
|
||||
import { t } from "astro-i18n";
|
||||
import { l } from "../util/util";
|
||||
|
||||
import Navbar from "@components/Navbar.svelte";
|
||||
|
||||
import ServerStatus from "../components/ServerStatus.svelte";
|
||||
|
||||
const {title, description} = Astro.props;
|
||||
const { title, description, transparentFooter = true } = Astro.props;
|
||||
---
|
||||
|
||||
<Basic title={title} description={description}>
|
||||
<slot name="head" slot="head"/>
|
||||
<Basic title={title} description={description} autoDarkMode={false}>
|
||||
<slot name="head" slot="head" />
|
||||
<Fragment>
|
||||
<div class="min-h-screen flex flex-col">
|
||||
<Navbar client:idle>
|
||||
<Image src={localLogo} alt={t("navbar.logo.alt")} width="44" height="44" quality="max"
|
||||
class="mr-2 p-1 bg-black rounded-full" slot="logo"/>
|
||||
<Image src={localLogo} alt={t("navbar.logo.alt")} width="44" height="44" quality="max" class="mr-2 p-1 bg-black rounded-full" slot="logo" />
|
||||
</Navbar>
|
||||
<main class="flex-1" data-pagefind-body>
|
||||
<slot/>
|
||||
<slot />
|
||||
</main>
|
||||
<footer class="bg-gray-900 w-full min-h-80 mt-4 pb-2 rounded-t-2xl flex flex-col dark:bg-neutral-900">
|
||||
<footer
|
||||
class={`min-h-80 mt-4 pb-2 rounded-t-2xl flex flex-col ${transparentFooter ? "backdrop-blur-3xl" : "bg-neutral-900"}`}
|
||||
style="width: min(100%, 75em); margin-left: auto; margin-right: auto;"
|
||||
>
|
||||
<div class="flex-1 flex justify-evenly items-center md:items-start mt-4 md:flex-row flex-col gap-y-4">
|
||||
<div class="footer-card">
|
||||
<h1>Serverstatus</h1>
|
||||
@@ -45,14 +47,17 @@ const {title, description} = Astro.props;
|
||||
<div class="footer-card">
|
||||
<h1>Social Media</h1>
|
||||
<a class="flex" href="/youtube">
|
||||
<YoutubeSolid class="mr-2"/>
|
||||
YouTube</a>
|
||||
<YoutubeSolid class="mr-2" />
|
||||
YouTube</a
|
||||
>
|
||||
<a class="flex" href="/discord">
|
||||
<DiscordSolid class="mr-2"/>
|
||||
Discord</a>
|
||||
<DiscordSolid class="mr-2" />
|
||||
Discord</a
|
||||
>
|
||||
<a class="flex" href="https://git.steamwar.de">
|
||||
<FileCodeSolid class="mr-2"/>
|
||||
Gitea</a>
|
||||
<FileCodeSolid class="mr-2" />
|
||||
Gitea</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-sm text-white text-center mt-1">© SteamWar.de - Made with ❤️ by Chaoscaot</span>
|
||||
@@ -77,4 +82,4 @@ const {title, description} = Astro.props;
|
||||
.match {
|
||||
width: min(100vw, 70em);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
import NavbarLayout from "./NavbarLayout.astro";
|
||||
import BackgroundImage from "../components/BackgroundImage.astro";
|
||||
|
||||
const {title, description} = Astro.props;
|
||||
const { title, description } = Astro.props;
|
||||
---
|
||||
|
||||
<NavbarLayout title={title} description={description}>
|
||||
<slot name="head" slot="head"/>
|
||||
<slot name="head" slot="head" />
|
||||
<div class="h-screen w-screen fixed -z-10">
|
||||
<BackgroundImage />
|
||||
</div>
|
||||
<div class="mx-auto bg-gray-100 p-8 rounded-b-md shadow-md pt-14 relative
|
||||
dark:text-white dark:bg-neutral-900" style="width: min(100%, 75em);">
|
||||
<slot/>
|
||||
<div class="mx-auto p-8 rounded-b-md border-x-gray-100 shadow-md pt-14 relative
|
||||
text-white backdrop-blur-3xl" style="width: min(100%, 75em);">
|
||||
<slot />
|
||||
</div>
|
||||
</NavbarLayout>
|
||||
</NavbarLayout>
|
||||
|
||||
@@ -3,6 +3,6 @@ import App from "../../components/admin/App.svelte";
|
||||
import Basic from "../../layouts/Basic.astro";
|
||||
---
|
||||
|
||||
<Basic clientSideRouter={false}>
|
||||
<App client:only="svelte"/>
|
||||
</Basic>
|
||||
<Basic clientSideRouter={false} autoDarkMode={false}>
|
||||
<App client:only="svelte" />
|
||||
</Basic>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
---
|
||||
|
||||
import Basic from "../../layouts/Basic.astro";
|
||||
import App from "@components/moderator/App.svelte";
|
||||
---
|
||||
|
||||
<Basic clientSideRouter={false}>
|
||||
<App client:only="svelte"/>
|
||||
</Basic>
|
||||
<Basic clientSideRouter={false} autoDarkMode={false}>
|
||||
<App client:only="svelte" />
|
||||
</Basic>
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
---
|
||||
import dayjs from "dayjs";
|
||||
import NavbarLayout from "@layouts/NavbarLayout.astro";
|
||||
import {getCollection} from "astro:content";
|
||||
import {astroI18n} from "astro-i18n";
|
||||
import { getCollection } from "astro:content";
|
||||
import { astroI18n } from "astro-i18n";
|
||||
|
||||
import {Image} from "astro:assets";
|
||||
import { Image } from "astro:assets";
|
||||
import Card from "@components/Card.svelte";
|
||||
import {CaretRight, Pause, Rocket, Crosshair1} from "@astropub/icons";
|
||||
import {t} from "astro-i18n";
|
||||
import {l} from "@utils/util";
|
||||
import { CaretRight, Pause, Rocket, Crosshair1 } from "@astropub/icons";
|
||||
import { t } from "astro-i18n";
|
||||
import { l } from "@utils/util";
|
||||
import PlayerCount from "@components/PlayerCount.svelte";
|
||||
import "../../public/fonts/barlow-condensed/barlow-condensed.css";
|
||||
import {type Player} from "../components/types/data";
|
||||
import { type Player } from "../components/types/data";
|
||||
import PostComponent from "../components/PostComponent.astro";
|
||||
import BackgroundImage from "../components/BackgroundImage.astro";
|
||||
|
||||
const teamMember: { [key: string]: Player[]} = await fetch(import.meta.env.PUBLIC_API_SERVER + "/data/team")
|
||||
.then(value => value.json());
|
||||
const teamMember: { [key: string]: Player[] } = await fetch(import.meta.env.PUBLIC_API_SERVER + "/data/team").then((value) => value.json());
|
||||
|
||||
const posts = await getCollection("announcements", entry => entry.id.split("/")[0] === astroI18n.locale);
|
||||
const posts = await getCollection("announcements", (entry) => entry.id.split("/")[0] === astroI18n.locale);
|
||||
|
||||
const germanPosts = await getCollection("announcements", entry => entry.id.split("/")[0] === astroI18n.fallbackLocale);
|
||||
const germanPosts = await getCollection("announcements", (entry) => entry.id.split("/")[0] === astroI18n.fallbackLocale);
|
||||
|
||||
germanPosts.forEach(value => {
|
||||
if (posts.find(post => post.data.key === value.data.key)) {
|
||||
germanPosts.forEach((value) => {
|
||||
if (posts.find((post) => post.data.key === value.data.key)) {
|
||||
return;
|
||||
} else {
|
||||
posts.push(value);
|
||||
@@ -43,27 +42,29 @@ const prefixColorMap: {
|
||||
};
|
||||
---
|
||||
|
||||
<NavbarLayout title={t("home.page")} description="SteamWar.de Homepage">
|
||||
<div class="w-full h-screen relative mb-4">
|
||||
<NavbarLayout title={t("home.page")} description="SteamWar.de Homepage" transparentFooter={false}>
|
||||
<div class="w-full h-screen relative mb-4 z-10">
|
||||
<div style="height: calc(100vh + 1rem)">
|
||||
<BackgroundImage />
|
||||
</div>
|
||||
<drop-in class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center">
|
||||
<h1 class="text-4xl sm:text-6xl md:text-8xl font-extrabold text-white -translate-y-16 opacity-0 barlow tracking-wider"
|
||||
style="transition: transform .7s ease-out, opacity .7s linear; filter: drop-shadow(2px 2px 5px black);">
|
||||
<span class="bg-gradient-to-tr from-yellow-400 to-yellow-300 bg-clip-text text-transparent">{t("home.title.first")}</span><span
|
||||
class="text-neutral-600">{t("home.title.second")}</span>
|
||||
<h1
|
||||
class="text-4xl sm:text-6xl md:text-8xl font-extrabold text-white -translate-y-16 opacity-0 barlow tracking-wider"
|
||||
style="transition: transform .7s ease-out, opacity .7s linear; filter: drop-shadow(2px 2px 5px black);"
|
||||
>
|
||||
<span class="bg-gradient-to-tr from-yellow-400 to-yellow-300 bg-clip-text text-transparent">{t("home.title.first")}</span><span class="text-neutral-600">{t("home.title.second")}</span>
|
||||
</h1>
|
||||
<text-carousel class="h-20 w-full relative select-none">
|
||||
<h2 class="-translate-y-16">{t("home.subtitle.1")}</h2>
|
||||
<h2>{t("home.subtitle.2")}
|
||||
<h2>
|
||||
{t("home.subtitle.2")}
|
||||
<PlayerCount client:idle />
|
||||
</h2>
|
||||
<h2>{t("home.subtitle.3")}</h2>
|
||||
</text-carousel>
|
||||
<a href={l("join")} class="btn btn-ghost mt-32 px-8 flex"
|
||||
style="animation: normal flyIn forwards 1.2s ease-out">{t("home.join")}
|
||||
<CaretRight width="24" height="24"/>
|
||||
<a href={l("join")} class="btn btn-ghost mt-32 px-8 flex" style="animation: normal flyIn forwards 1.2s ease-out"
|
||||
>{t("home.join")}
|
||||
<CaretRight width="24" height="24" />
|
||||
</a>
|
||||
<style>
|
||||
@keyframes flyIn {
|
||||
@@ -141,18 +142,18 @@ const prefixColorMap: {
|
||||
<section class="w-full flex flex-col items-center justify-center shadow-2xl rounded-b-2xl pb-8">
|
||||
<div class="py-10 flex flex-col lg:flex-row">
|
||||
<Card client:idle>
|
||||
<Crosshair1 height="64" width="64"/>
|
||||
<Crosshair1 height="64" width="64" />
|
||||
<h1>{t("home.benefits.fights.title")}</h1>
|
||||
<p class="mt-4">{t("home.benefits.fights.description.1")}</p>
|
||||
<p class="mt-4">{t("home.benefits.fights.description.2")}</p>
|
||||
</Card>
|
||||
<Card client:idle>
|
||||
<Rocket height="64" width="64"/>
|
||||
<Rocket height="64" width="64" />
|
||||
<h1>{t("home.benefits.bau.title")}</h1>
|
||||
<p class="mt-4">{t("home.benefits.bau.description")}</p>
|
||||
</Card>
|
||||
<Card client:idle>
|
||||
<Pause height="64" width="64"/>
|
||||
<Pause height="64" width="64" />
|
||||
<h1>{t("home.benefits.minigames.title")}</h1>
|
||||
<p class="mt-4">{t("home.benefits.minigames.description.1")}</p>
|
||||
<p class="mt-4">{t("home.benefits.minigames.description.2")}</p>
|
||||
@@ -160,23 +161,29 @@ const prefixColorMap: {
|
||||
</div>
|
||||
</section>
|
||||
<section class="w-full py-12 flex flex-wrap justify-center">
|
||||
{Object.entries(teamMember).map(([prefix, players]) => (
|
||||
<Fragment>
|
||||
{players.map((v, index) => (
|
||||
<div class="inline-flex flex-col justify-end">
|
||||
{index == 0 ? <h2 class="dark:text-white text-4xl font-bold text-center md:text-left md:pl-4">{t("home.prefix." + prefix)}</h2> : null}
|
||||
<Card extraClasses={`pt-8 pb-10 px-8 w-fit shadow-md ${prefixColorMap[prefix]}`} client:idle>
|
||||
<figure class="flex flex-col items-center" style="width: 150px">
|
||||
<figcaption class="text-center mb-4 text-2xl">{v.name}</figcaption>
|
||||
<Image src={`${import.meta.env.PUBLIC_API_SERVER}/data/skin/${v.uuid}`}
|
||||
class="transition duration-300 ease-in-out hover:scale-110 hover:backdrop-blur-lg hover:drop-shadow-2xl"
|
||||
alt={v.name + "s bust"} width="150" height="150"/>
|
||||
</figure>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
{
|
||||
Object.entries(teamMember).map(([prefix, players]) => (
|
||||
<Fragment>
|
||||
{players.map((v, index) => (
|
||||
<div class="inline-flex flex-col justify-end">
|
||||
{index == 0 ? <h2 class="dark:text-white text-4xl font-bold text-center md:text-left md:pl-4">{t("home.prefix." + prefix)}</h2> : null}
|
||||
<Card extraClasses={`pt-8 pb-10 px-8 w-fit shadow-md ${prefixColorMap[prefix]}`} client:idle>
|
||||
<figure class="flex flex-col items-center" style="width: 150px">
|
||||
<figcaption class="text-center mb-4 text-2xl">{v.name}</figcaption>
|
||||
<Image
|
||||
src={`${import.meta.env.PUBLIC_API_SERVER}/data/skin/${v.uuid}`}
|
||||
class="transition duration-300 ease-in-out hover:scale-110 hover:backdrop-blur-lg hover:drop-shadow-2xl"
|
||||
alt={v.name + "s bust"}
|
||||
width="150"
|
||||
height="150"
|
||||
/>
|
||||
</figure>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
))
|
||||
}
|
||||
</section>
|
||||
</NavbarLayout>
|
||||
|
||||
@@ -184,13 +191,17 @@ const prefixColorMap: {
|
||||
text-carousel {
|
||||
> * {
|
||||
@apply absolute top-0 left-0 w-full text-xl sm:text-4xl italic text-white text-center opacity-0;
|
||||
transition: transform .5s ease-out, opacity .5s linear;
|
||||
transition:
|
||||
transform 0.5s ease-out,
|
||||
opacity 0.5s linear;
|
||||
text-shadow: 2px 2px 5px black;
|
||||
}
|
||||
}
|
||||
|
||||
.barlow {
|
||||
font-family: Barlow Condensed, sans-serif;
|
||||
font-family:
|
||||
Barlow Condensed,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -207,7 +218,7 @@ const prefixColorMap: {
|
||||
}
|
||||
|
||||
> svg {
|
||||
@apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl
|
||||
@apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,7 +31,7 @@ table {
|
||||
text-align: center;
|
||||
|
||||
tr:nth-child(odd) {
|
||||
@apply bg-neutral-200 dark:bg-neutral-800;
|
||||
@apply backdrop-brightness-125;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user