Updates and more

Dieser Commit ist enthalten in:
Chaoscaot 2023-11-12 22:43:42 +01:00
Ursprung 7450ecdabb
Commit 3889f28eb8
43 geänderte Dateien mit 5188 neuen und 322 gelöschten Zeilen

1
.gitignore vendored
Datei anzeigen

@ -21,3 +21,4 @@ pnpm-debug.log*
.DS_Store
/.astro-i18n/
/bun.lockb
/src/pages/de/

Datei anzeigen

@ -16,6 +16,7 @@ export default defineAstroI18nConfig({
join: "jetzt-spielen",
imprint: "impressum",
"code-of-conduct": "verhaltensrichtlinien",
rules: "regeln",
}
}, // { [secondary_locale1]: { about: "about-translated", ... } }
})

4519
pnpm-lock.yaml Normale Datei

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei anzeigen

@ -1,10 +1,7 @@
<script lang="ts">
import {astroI18n, t} from "astro-i18n"
import {l} from "../util/util.ts"
import {fetchWithToken, tokenStore} from "./repo/repo.ts";
import {get} from "svelte/store";
import {SchematicListSchema} from "./types/schem.ts";
import {PlayerSchema} from "./types/data.ts";
import {dataRepo, schemRepo, tokenStore} from "./repo/repo.ts";
import moment from "moment";
import {
CheckSolid,
@ -15,179 +12,38 @@
XCircleOutline
} from "flowbite-svelte-icons";
import {Breadcrumb, BreadcrumbItem, Modal, Tooltip} from "flowbite-svelte";
import SchematicInfo from "./SchematicInfo.svelte";
import SchematicInfo from "./dashboard/SchematicInfo.svelte";
import SchematicListTile from "./dashboard/SchematicListTile.svelte";
import UploadModal from "./dashboard/UploadModal.svelte";
import SchematicList from "./dashboard/SchematicList.svelte";
import UserInfo from "./dashboard/UserInfo.svelte";
let userFetch = getUser();
let schematicFetch = getSchematics();
let uploadOpen = false;
let infoModalId: number | null = null;
function getUser() {
return fetchWithToken(get(tokenStore), "/data/me").then(value => value.json()).then(value => PlayerSchema.parse(value))
}
function logout() {
tokenStore.set("")
window.location.href = l("/login")
return $dataRepo.getMe()
}
function getSchematics() {
return fetchWithToken(get(tokenStore), "/schem/list").then(value => value.json()).then(value => SchematicListSchema.parse(value))
return $schemRepo.getRootSchematicList()
}
function getSchematic(id: number) {
return fetchWithToken(get(tokenStore), "/schem/list/" + id).then(value => value.json()).then(value => SchematicListSchema.parse(value))
}
function schemListClick(isDir: boolean, id: number) {
if (isDir) {
return () => schematicFetch = getSchematic(id)
} else {
return () => infoModalId = id
}
return $schemRepo.getSchematicList(id)
}
</script>
{#await userFetch}
<p>loading...</p>
<p>{t("status.loading")}</p>
{:then user}
<div class="flex mb-4 flex-col md:flex-row">
<div>
<div class="bg-zinc-50 border-gray-100 py-24 px-12 border-2 m-2 transition duration-300 ease-in-out rounded-xl shadow-lg hidden md:block
hover:scale-105 hover:shadow-2xl
dark:bg-neutral-900 dark:border-gray-800 dark:text-white">
<figure>
<figcaption class="text-center mb-4 text-2xl">{user.name}</figcaption>
<img src={`https://visage.surgeplay.com/bust/150/${user.uuid}`} class="transition duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl" alt={user.name + "s bust"} width="150" height="150" />
</figure>
</div>
<div class="flex flex-wrap">
<button class="btn mt-2" on:click={logout}>Logout</button>
{#if user.perms.includes("MODERATION")}
<a class="btn w-fit mt-2" href="/admin">Admin Panel</a>
{/if}
</div>
</div>
<div>
<h1 class="text-4xl font-bold">Hello, {user.name}</h1>
<p>Rang: {t("home.prefix." + user.prefix)}</p>
<p>Permissions:</p>
<ul>
{#each user.perms as permission}
<li class="list-disc ml-6">{permission}</li>
{/each}
</ul>
</div>
</div>
<UserInfo {user} />
<hr>
<div>
{#await schematicFetch}
<p>Loading...</p>
<p>{t("status.loading")}</p>
{:then schematics}
<div class="flex justify-between">
<Breadcrumb navClass="py-4">
<BreadcrumbItem home>
<svelte:fragment slot="icon">
<HomeOutline class="w-6 h-6 mx-2 dark:text-white" />
</svelte:fragment>
<span on:click={() => schematicFetch = getSchematics()} class="hover:underline hover:cursor-pointer text-2xl">Schematics</span>
</BreadcrumbItem>
{#each schematics.breadcrumbs as bread}
<BreadcrumbItem>
<svelte:fragment slot="icon">
<ChevronDoubleRightOutline class="w-4 h-4 mx-2 dark:text-white" />
</svelte:fragment>
<span on:click={() => schematicFetch = getSchematic(bread.id)} class="hover:underline hover:cursor-pointer text-2xl">{bread.name}</span>
</BreadcrumbItem>
{/each}
</Breadcrumb>
<div class="flex flex-col justify-center">
<button class="btn" on:click={() => uploadOpen = true}>
Upload
</button>
</div>
</div>
<table>
<tbody>
<tr class="!cursor-auto">
<th>Type</th>
<th>Name</th>
<th class="hidden sm:table-cell">Owner</th>
<th class="hidden sm:table-cell"></th>
<th class="hidden md:table-cell">Updated</th>
<th>
<InfoCircleOutline />
<Tooltip>
<span>Replace Color</span>
</Tooltip>
</th>
<th>
<InfoCircleOutline />
<Tooltip>
<span>Allow Replay</span>
</Tooltip>
</th>
</tr>
{#if schematics.breadcrumbs.length !== 0}
<tr on:click|preventDefault={() => {
if (schematics.breadcrumbs.length === 1) {
schematicFetch = getSchematics()
} else {
schematicFetch = getSchematic(schematics.breadcrumbs[schematics.breadcrumbs.length - 2].id)
}
}}>
<th>
<FolderOutline />
</th>
<th>../</th>
<th class="hidden sm:table-cell"></th>
<th class="hidden sm:table-cell">Directory</th>
<th class="hidden md:table-cell"></th>
<th></th>
<th></th>
</tr>
{/if}
{#each schematics.schematics as schem}
<tr on:click|preventDefault={schemListClick(schem.type == null, schem.id)}>
<th>
{#if schem.type == null}
<FolderOutline />
{:else}
<FileOutline />
{/if}
</th>
<th>
{schem.name}{#if schem.type == null}/{/if}
</th>
<th class="hidden sm:table-cell">{schematics.players[schem.owner].name}</th>
<th class="hidden sm:table-cell">{schem.type ?? "Directory"}</th>
<th class="hidden md:table-cell">{new Intl.DateTimeFormat(astroI18n.locale, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
day: "2-digit",
month: "2-digit",
year: "numeric"
}).format(moment(schem.lastUpdate).utc(false).toDate())}</th>
<th>
{#if schem.replaceColor}
<CheckSolid class="text-green-500" />
{:else}
<XCircleOutline class="text-red-500" />
{/if}
</th>
<th>
{#if schem.allowReplay}
<CheckSolid class="text-green-500" />
{:else}
<XCircleOutline class="text-red-500" />
{/if}
</th>
</tr>
{/each}
</tbody>
</table>
<SchematicList {schematics} on:reset={() => schematicFetch = getSchematics()} on:to={(e) => schematicFetch = getSchematic(e.detail.id) } />
{:catch error}
<p>error: {error.message}</p>
{/await}
@ -196,32 +52,3 @@
<p>error: {error.message}</p>
{/await}
<Modal title="Upload Schematic" bind:open={uploadOpen} autoclose outsideclose>
<form>
<input type="file" multiple />
</form>
<svelte:fragment slot="footer">
<button class="btn !ml-auto" on:click={() => uploadOpen = false}>Upload</button>
<button class="btn btn-gray" on:click={() => uploadOpen = false}>Close</button>
</svelte:fragment>
</Modal>
{#if infoModalId !== null}
<SchematicInfo schematicId={infoModalId} on:reset={() => infoModalId = null} />
{/if}
<style lang="scss">
table {
@apply w-full;
}
tr {
@apply transition-colors cursor-pointer border-b
dark:hover:bg-gray-800 hover:bg-gray-300;
}
th {
@apply text-left py-4 md:px-2;
}
</style>

Datei anzeigen

@ -11,7 +11,7 @@
let {tokenStore} = await import("./repo/repo.ts");
if (username === "" || token === "") {
token = "";
alert(t("login.error"));
error = t("login.error");
return;
}
try {
@ -22,7 +22,7 @@
}).then(res => res.json());
if (res.name !== username) {
alert(t("login.error"));
error = t("login.error");
token = "";
return;
}
@ -30,7 +30,7 @@
tokenStore.set(token);
window.location.href = l("/dashboard");
} catch (e) {
alert(t("login.error"));
error = t("login.error");
token = "";
return;
}

Datei anzeigen

@ -1,65 +0,0 @@
<script lang="ts">
import {createEventDispatcher} from "svelte";
import {fetchWithToken, tokenStore} from "./repo/repo.ts";
import {get} from "svelte/store";
import {Modal, Spinner} from "flowbite-svelte";
import {SchematicInfoSchema} from "./types/schem.ts";
import {astroI18n} from "astro-i18n";
import moment from "moment/moment";
import {CheckSolid, XCircleOutline} from "flowbite-svelte-icons";
const dispatch = createEventDispatcher();
export let schematicId: number;
let schemInfo = getSchematicInfo(schematicId);
function getSchematicInfo(id: number) {
return fetchWithToken(get(tokenStore), "/schem/info/" + id).then(r => r.json()).then(r => SchematicInfoSchema.parse(r))
}
</script>
{#await schemInfo}
<Modal title="Loading" open>
<Spinner />
</Modal>
{:then info}
<Modal title={info.schem.name} autoclose open>
<p>Path: {info.path}</p>
<p class="flex !mt-0">
Replace Color:
{#if info.schem.replaceColor}
<CheckSolid class="text-green-500 ml-2" />
{:else}
<XCircleOutline class="text-red-500 ml-2" />
{/if}
</p>
<p class="flex !mt-0">
Allow Replay: {#if info.schem.allowReplay}
<CheckSolid class="text-green-500 ml-2" />
{:else}
<XCircleOutline class="text-red-500 ml-2" />
{/if}
</p>
<p class="!mt-0">Type: {info.schem.type ?? "Directory"}</p>
<p class="!mt-0">Updated: {new Intl.DateTimeFormat(astroI18n.locale, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
day: "2-digit",
month: "2-digit",
year: "numeric"
}).format(moment(info.schem.lastUpdate).utc(false).toDate())}</p>
<p class="!mt-0">Item: {info.schem.item ?? (info.schem.type == null ? "CHEST" : "CAULDRON_ITEM")}</p>
{#if info.members.length !== 0}
<p class="!mt-0">Member: {info.members.join(", ")}</p>
{/if}
<button class="btn !ml-auto" slot="footer" on:click={() => dispatch("reset")}>Close</button>
</Modal>
{:catch e}
<Modal title="Error" open>
<p>{e.message}</p>
<button class="btn !ml-auto" slot="footer" on:click={() => dispatch("reset")}>Close</button>
</Modal>
{/await}

Datei anzeigen

@ -0,0 +1,33 @@
<script lang="ts">
import {t} from "astro-i18n"
import {createEventDispatcher} from "svelte";
import {schemRepo} from "../repo/repo.ts";
import {Modal, Spinner} from "flowbite-svelte";
import {astroI18n} from "astro-i18n";
import moment from "moment/moment";
import {CheckSolid, XCircleOutline} from "flowbite-svelte-icons";
import SchematicInfoModal from "./SchematicInfoModal.svelte";
const dispatch = createEventDispatcher();
export let schematicId: number;
let schemInfo = getSchematicInfo(schematicId);
function getSchematicInfo(id: number) {
return $schemRepo.getSchematicInfo(id);
}
</script>
{#await schemInfo}
<Modal title="Loading" open on:close={() => dispatch("reset")}>
<Spinner />
</Modal>
{:then info}
<SchematicInfoModal {info} on:reset />
{:catch e}
<Modal title="Error" open on:close={() => dispatch("reset")}>
<p>{e.message}</p>
<button class="btn !ml-auto" slot="footer" on:click={() => dispatch("reset")}>Close</button>
</Modal>
{/await}

Datei anzeigen

@ -0,0 +1,56 @@
<script lang="ts">
import {astroI18n, t} from "astro-i18n";
import moment from "moment";
import {CheckSolid, XCircleOutline} from "flowbite-svelte-icons";
import {Modal} from "flowbite-svelte";
import type {SchematicInfo} from "../types/schem.ts";
import {createEventDispatcher} from "svelte";
import {schemRepo} from "../repo/repo.ts";
const dispatch = createEventDispatcher();
export let info: SchematicInfo;
async function download() {
const code = await $schemRepo.createDownload(info.schem.id);
window.open(import.meta.env.PUBLIC_API_SERVER + "/download/" + code.code, "_blank")
dispatch("reset")
}
</script>
<Modal title={info.schem.name} autoclose open on:close={() => dispatch("reset")}>
<p>{t("dashboard.schematic.info.path", {path: info.path})}</p>
<p class="flex !mt-0">
{t("dashboard.schematic.info.replaceColor")}
{#if info.schem.replaceColor}
<CheckSolid class="text-green-500 ml-2" />
{:else}
<XCircleOutline class="text-red-500 ml-2" />
{/if}
</p>
<p class="flex !mt-0">
{t("dashboard.schematic.info.allowReplay")}
{#if info.schem.allowReplay}
<CheckSolid class="text-green-500 ml-2" />
{:else}
<XCircleOutline class="text-red-500 ml-2" />
{/if}
</p>
<p class="!mt-0">{t("dashboard.schematic.info.type", {type: info.schem.type ?? t("dashboard.schematic.dir")})}</p>
<p class="!mt-0">{t("dashboard.schematic.info.updated", {updated: new Intl.DateTimeFormat(astroI18n.locale, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
day: "2-digit",
month: "2-digit",
year: "numeric"
}).format(moment(info.schem.lastUpdate).utc(false).toDate())})}</p>
<p class="!mt-0">{t("dashboard.schematic.info.item", {item: info.schem.item ?? (info.schem.type == null ? "CHEST" : "CAULDRON_ITEM")})}</p>
{#if info.members.length !== 0}
<p class="!mt-0">{t("dashboard.schematic.info.members", {members: info.members.join(", ")})}</p>
{/if}
<svelte:fragment slot="footer">
<button class="btn !ml-auto" on:click={download}>{t("dashboard.schematic.info.btn.download")}</button>
<button class="btn" on:click={() => dispatch("reset")}>{t("dashboard.schematic.info.btn.close")}</button>
</svelte:fragment>
</Modal>

Datei anzeigen

@ -0,0 +1,115 @@
<script lang="ts">
import {t} from "astro-i18n";
import {ChevronDoubleRightOutline, FolderOutline, HomeOutline, InfoCircleOutline} from "flowbite-svelte-icons";
import SchematicListTile from "./SchematicListTile.svelte";
import {Breadcrumb, BreadcrumbItem, Tooltip} from "flowbite-svelte";
import {createEventDispatcher} from "svelte";
import type {SchematicList} from "../types/schem.ts";
import SchematicInfo from "./SchematicInfo.svelte";
import UploadModal from "./UploadModal.svelte";
const dispatch = createEventDispatcher();
export let schematics: SchematicList;
let uploadOpen = false;
let infoModalId: number | null = null;
function schemListClick(isDir: boolean, id: number) {
if (isDir) {
return () => dispatch("to", {id})
} else {
return () => infoModalId = id
}
}
</script>
<div class="flex justify-between">
<Breadcrumb navClass="py-4">
<BreadcrumbItem home>
<svelte:fragment slot="icon">
<HomeOutline class="w-6 h-6 mx-2 dark:text-white" />
</svelte:fragment>
<span on:click={() => dispatch("reset")} class="hover:underline hover:cursor-pointer text-2xl">{t("dashboard.schematic.home")}</span>
</BreadcrumbItem>
{#each schematics.breadcrumbs as bread}
<BreadcrumbItem>
<svelte:fragment slot="icon">
<ChevronDoubleRightOutline class="w-4 h-4 mx-2 dark:text-white" />
</svelte:fragment>
<span on:click={() => dispatch("to", {id: bread.id})} class="hover:underline hover:cursor-pointer text-2xl">{bread.name}</span>
</BreadcrumbItem>
{/each}
</Breadcrumb>
<div class="flex flex-col justify-center">
<button class="btn" on:click={() => uploadOpen = true}>
{t("dashboard.schematic.upload")}
</button>
</div>
</div>
<table>
<tbody>
<tr class="!cursor-auto">
<th>{t("dashboard.schematic.head.type")}</th>
<th>{t("dashboard.schematic.head.name")}</th>
<th class="hidden sm:table-cell">{t("dashboard.schematic.head.owner")}</th>
<th class="hidden sm:table-cell"></th>
<th class="hidden md:table-cell">{t("dashboard.schematic.head.updated")}</th>
<th>
<InfoCircleOutline />
<Tooltip>
<span>{t("dashboard.schematic.head.replaceColor")}</span>
</Tooltip>
</th>
<th>
<InfoCircleOutline />
<Tooltip>
<span>{t("dashboard.schematic.head.allowReplay")}</span>
</Tooltip>
</th>
</tr>
{#if schematics.breadcrumbs.length !== 0}
<tr on:click|preventDefault={() => {
if (schematics.breadcrumbs.length === 1) {
dispatch("reset")
} else {
dispatch("to", {id: schematics.breadcrumbs[schematics.breadcrumbs.length - 2].id})
}
}}>
<th>
<FolderOutline />
</th>
<th>../</th>
<th class="hidden sm:table-cell"></th>
<th class="hidden sm:table-cell">{t("dashboard.schematic.dir")}</th>
<th class="hidden md:table-cell"></th>
<th></th>
<th></th>
</tr>
{/if}
{#each schematics.schematics as schem}
<SchematicListTile schem={schem} players={schematics.players} on:click={schemListClick(schem.type == null, schem.id)} />
{/each}
</tbody>
</table>
<UploadModal bind:open={uploadOpen} on:refresh />
{#if infoModalId !== null}
<SchematicInfo schematicId={infoModalId} on:reset={() => infoModalId = null} />
{/if}
<style lang="scss">
table {
@apply w-full;
}
tr {
@apply transition-colors cursor-pointer border-b
dark:hover:bg-gray-800 hover:bg-gray-300;
}
th {
@apply text-left py-4 md:px-2;
}
</style>

Datei anzeigen

@ -0,0 +1,58 @@
<script lang="ts">
import {astroI18n, t} from "astro-i18n";
import moment from "moment/moment.js";
import {CheckSolid, FileOutline, FolderOutline, XCircleOutline} from "flowbite-svelte-icons";
import type {Schematic} from "../types/schem.ts";
import type {Player} from "../types/data.ts";
export let schem: Schematic;
export let players: Record<number, Player>;
</script>
<tr on:click|preventDefault>
<th>
{#if schem.type == null}
<FolderOutline />
{:else}
<FileOutline />
{/if}
</th>
<th>
{schem.name}{#if schem.type == null}/{/if}
</th>
<th class="hidden sm:table-cell">{players[schem.owner].name}</th>
<th class="hidden sm:table-cell">{schem.type ?? t("dashboard.schematic.dir")}</th>
<th class="hidden md:table-cell">{new Intl.DateTimeFormat(astroI18n.locale, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
day: "2-digit",
month: "2-digit",
year: "numeric"
}).format(moment(schem.lastUpdate).utc(false).toDate())}</th>
<th>
{#if schem.replaceColor}
<CheckSolid class="text-green-500" />
{:else}
<XCircleOutline class="text-red-500" />
{/if}
</th>
<th>
{#if schem.allowReplay}
<CheckSolid class="text-green-500" />
{:else}
<XCircleOutline class="text-red-500" />
{/if}
</th>
</tr>
<style lang="scss">
tr {
@apply transition-colors cursor-pointer border-b
dark:hover:bg-gray-800 hover:bg-gray-300;
}
th {
@apply text-left py-4 md:px-2;
}
</style>

Datei anzeigen

@ -0,0 +1,47 @@
<script lang="ts">
import {Modal} from "flowbite-svelte";
import {schemRepo} from "../repo/repo.js";
import {createEventDispatcher} from "svelte";
const dispatch = createEventDispatcher();
export let open = false;
async function upload() {
if (uploadFile == null) {
return
}
let file = uploadFile[0];
let name = file.name;
let type = name.split(".").pop();
if (type !== "schem" && type !== "schematic") {
return
}
let content = await file.arrayBuffer();
// @ts-ignore
let b64 = btoa(String.fromCharCode.apply(null, new Uint8Array(content)));
await $schemRepo.uploadSchematic(name, b64);
open = false;
uploadFile = null;
dispatch("reset")
}
let uploadFile: File[] | null = null;
</script>
<Modal title="Upload Schematic" bind:open autoclose outsideclose>
<form>
<input type="file" bind:files={uploadFile} />
</form>
<svelte:fragment slot="footer">
<button class="btn !ml-auto" on:click={upload}>Upload</button>
<button class="btn btn-gray" on:click={() => open = false}>Close</button>
</svelte:fragment>
</Modal>

Datei anzeigen

@ -0,0 +1,41 @@
<script lang="ts">
import {t} from "astro-i18n";
import type {Player} from "../types/data.ts";
import {tokenStore} from "../repo/repo.ts";
import {l} from "../../util/util.ts";
export let user: Player;
function logout() {
tokenStore.set("")
window.location.href = l("/login")
}
</script>
<div class="flex mb-4 flex-col md:flex-row">
<div>
<div class="bg-zinc-50 border-gray-100 py-24 px-12 border-2 m-2 transition duration-300 ease-in-out rounded-xl shadow-lg hidden md:block
hover:scale-105 hover:shadow-2xl
dark:bg-neutral-900 dark:border-gray-800 dark:text-white">
<figure>
<figcaption class="text-center mb-4 text-2xl">{user.name}</figcaption>
<img src={`https://visage.surgeplay.com/bust/150/${user.uuid}`} class="transition duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl" alt={user.name + "s bust"} width="150" height="150" />
</figure>
</div>
<div class="flex flex-wrap">
<button class="btn mt-2" on:click={logout}>{t("dashboard.buttons.logout")}</button>
{#if user.perms.includes("MODERATION")}
<a class="btn w-fit mt-2" href="/admin">{t("dashboard.buttons.admin")}</a>
{/if}
</div>
</div>
<div>
<h1 class="text-4xl font-bold">{t("dashboard.title", {name: user.name})}</h1>
<p>{t("dashboard.rank", {rank: t("home.prefix." + user.prefix)})}</p>
<p>{t("dashboard.permissions")}</p>
<ul>
{#each user.perms as permission}
<li class="list-disc ml-6">{permission}</li>
{/each}
</ul>
</div>
</div>

Datei anzeigen

@ -1,5 +1,5 @@
import type {Server} from "../types/data.ts";
import {ServerSchema} from "../types/data.ts";
import type {Player, Server} from "../types/data.ts";
import {PlayerSchema, ServerSchema} from "../types/data.ts";
import {fetchWithToken} from "./repo.ts";
export class DataRepo {
@ -8,4 +8,8 @@ export class DataRepo {
public async getServer(): Promise<Server> {
return await fetchWithToken(this.token, "/data/server").then(value => value.json()).then(value => ServerSchema.parse(value));
}
public async getMe(): Promise<Player> {
return await fetchWithToken(this.token, "/data/me").then(value => value.json()).then(value => PlayerSchema.parse(value));
}
}

Datei anzeigen

@ -2,7 +2,6 @@ import type {Page, PageList} from "../types/page.ts";
import {fetchWithToken} from "./repo.ts";
import {PageListSchema, PageSchema} from "../types/page.ts";
import {bytesToBase64} from "../admin/util.ts";
import {branches} from "../stores/stores.ts";
export class PageRepo {
constructor(private token: string) {}

Datei anzeigen

@ -6,6 +6,7 @@ import {PageRepo} from "./page.ts";
import {DataRepo} from "./data.ts";
import { AES, enc, format } from "crypto-js";
import {SchematicRepo} from "./schem.ts";
export { EventRepo } from "./event.js"
@ -29,3 +30,4 @@ export const fightRepo = derived(tokenStore, ($token) => new FightRepo($token))
export const permsRepo = derived(tokenStore, ($token) => new PermsRepo($token))
export const pageRepo = derived(tokenStore, ($token) => new PageRepo($token))
export const dataRepo = derived(tokenStore, ($token) => new DataRepo($token))
export const schemRepo = derived(tokenStore, ($token) => new SchematicRepo($token))

33
src/components/repo/schem.ts Normale Datei
Datei anzeigen

@ -0,0 +1,33 @@
import {fetchWithToken} from "./repo.ts";
import type {SchematicCode, SchematicInfo, SchematicList} from "../types/schem.ts";
import {SchematicCodeSchema, SchematicInfoSchema, SchematicListSchema} from "../types/schem.ts";
export class SchematicRepo {
constructor(private token: string) {}
public async getRootSchematicList(): Promise<SchematicList> {
return await fetchWithToken(this.token, "/schem").then(value => value.json()).then(value => SchematicListSchema.parse(value));
}
public async getSchematicList(id: number): Promise<SchematicList> {
return await fetchWithToken(this.token, `/schem/${id}/list`).then(value => value.json()).then(value => SchematicListSchema.parse(value));
}
public async getSchematicInfo(id: number): Promise<SchematicInfo> {
return await fetchWithToken(this.token, `/schem/${id}`).then(value => value.json()).then(value => SchematicInfoSchema.parse(value));
}
public async createDownload(id: number): Promise<SchematicCode> {
return await fetchWithToken(this.token, `/schem/${id}/download`).then(value => value.json()).then(value => SchematicCodeSchema.parse(value));
}
public async uploadSchematic(name: string, content: string) {
return await fetchWithToken(this.token, `/schem`, {
method: "POST",
body: JSON.stringify({
name,
content
})
})
}
}

Datei anzeigen

@ -1,9 +1,9 @@
import type {Player, SchematicType, Server} from "../types/data.js";
import {PlayerSchema, ServerSchema} from "../types/data.js";
import type {Player, SchematicType} from "../types/data.js";
import {PlayerSchema} from "../types/data.js";
import {cached, cachedFamily} from "./cached.js";
import type {Team} from "../types/team.js";
import {TeamSchema} from "../types/team.js";
import {derived, get, readable, writable} from "svelte/store";
import {derived, get, writable} from "svelte/store";
import {dataRepo, fetchWithToken, pageRepo, tokenStore} from "../repo/repo.js";
import {z} from "zod";
@ -47,7 +47,7 @@ export const branches = cached<string[]>([], async () => {
return z.array(z.string()).parse(res);
})
export const server = readable(new Promise((resolve) => {}), (set) => set(get(dataRepo).getServer()));
export const server = derived(dataRepo, $dataRepo => $dataRepo.getServer());
export const isWide = writable(window.innerWidth >= 640);
window.addEventListener("resize", () => isWide.set(window.innerWidth >= 640));

Datei anzeigen

@ -1,5 +1,3 @@
import type {Team} from "./team.js";
import type {Player} from "./data.js";
import {z} from "zod";
import {TeamSchema} from "./team.js";
import {PlayerSchema} from "./data.js";

Datei anzeigen

@ -32,4 +32,12 @@ export const SchematicInfoSchema = z.object({
schem: SchematicSchema
})
export type SchematicInfo = z.infer<typeof SchematicInfoSchema>
export type SchematicInfo = z.infer<typeof SchematicInfoSchema>
export const SchematicCodeSchema = z.object({
id: z.number().gte(0),
code: z.string(),
expires: z.number().positive()
})
export type SchematicCode = z.infer<typeof SchematicCodeSchema>

Datei anzeigen

@ -32,8 +32,18 @@ export const event = defineCollection({
})
})
export const modes = defineCollection({
type: "data",
schema: z.object({
translationKey: z.string(),
main: z.boolean(),
ranked: z.boolean().optional().default(false)
})
})
export const collections = {
'pages': pages,
'help': help,
'event': event
'event': event,
'modes': modes
}

Datei anzeigen

@ -0,0 +1,4 @@
{
"translationKey": "as",
"main": true
}

Datei anzeigen

@ -0,0 +1,5 @@
{
"translationKey": "mwg",
"main": true,
"ranked": true
}

Datei anzeigen

@ -0,0 +1,5 @@
{
"translationKey": "wg",
"main": true,
"ranked": true
}

Datei anzeigen

@ -0,0 +1,4 @@
{
"translationKey": "ws",
"main": true
}

4
src/env.d.ts vendored
Datei anzeigen

@ -10,9 +10,9 @@
type PrimaryLocale = "en"
type SecondaryLocale = "de"
type Locale = PrimaryLocale | SecondaryLocale
type RouteParameters = {"/dashboard":undefined;"/":undefined;"/login":undefined;"/[...slug]":{"slug":unknown;};"/admin":undefined;"/help":undefined;"/help/[...slug]":{"slug":unknown;};}
type RouteParameters = {"/dashboard":undefined;"/":undefined;"/login":undefined;"/rules":undefined;"/[...slug]":{"slug":unknown;};"/admin":undefined;"/help":undefined;"/help/[...slug]":{"slug":unknown;};}
type Route = keyof RouteParameters
type TranslationVariables = {"login.title":object|undefined;"login.placeholder.username":object|undefined;"login.placeholder.token":object|undefined;"login.label.username":object|undefined;"login.label.token":object|undefined;"login.generateToken":object|undefined;"login.submit":object|undefined;"navbar.title":object|undefined;"navbar.logo.alt":object|undefined;"navbar.links.home.title":object|undefined;"navbar.links.home.about":object|undefined;"navbar.links.home.downloads":object|undefined;"navbar.links.home.faq":object|undefined;"navbar.links.announcements":object|undefined;"navbar.links.rules.title":object|undefined;"navbar.links.rules.gamemode":object|undefined;"navbar.links.rules.wg":object|undefined;"navbar.links.rules.mwg":object|undefined;"navbar.links.rules.ws":object|undefined;"navbar.links.rules.as":object|undefined;"navbar.links.rules.rotating":object|undefined;"navbar.links.rules.megawg":object|undefined;"navbar.links.rules.micro":object|undefined;"navbar.links.rules.sf":object|undefined;"navbar.links.rules.general":object|undefined;"navbar.links.rules.coc":object|undefined;"navbar.links.help.title":object|undefined;"navbar.links.help.center":object|undefined;"navbar.links.help.docs":object|undefined;"navbar.links.account":object|undefined;"status.loading":object|undefined;"status.status":object|undefined;"status.online":object|undefined;"status.offline":object|undefined;"status.players":{"count"?:unknown;}|undefined;"status.version":{"version"?:unknown;}|undefined;"home.page":object|undefined;"home.title.first":object|undefined;"home.title.second":object|undefined;"home.subtitle.1":object|undefined;"home.subtitle.2":object|undefined;"home.subtitle.3":object|undefined;"home.join":object|undefined;"home.benefits.historic.title":object|undefined;"home.benefits.historic.description.1":object|undefined;"home.benefits.historic.description.2":object|undefined;"home.benefits.server.title":object|undefined;"home.benefits.server.description":object|undefined;"home.benefits.events.title":object|undefined;"home.benefits.events.description.1":object|undefined;"home.benefits.events.description.2":object|undefined;"home.prefix.Admin":object|undefined;"home.prefix.Dev":object|undefined;"home.prefix.Mod":object|undefined;"home.prefix.Sup":object|undefined;"home.prefix.Arch":object|undefined;}
type TranslationVariables = {"dashboard.title":{"name"?:unknown;}|undefined;"dashboard.rank":{"rank"?:unknown;}|undefined;"dashboard.permissions":object|undefined;"dashboard.buttons.logout":object|undefined;"dashboard.buttons.admin":object|undefined;"dashboard.schematic.upload":object|undefined;"dashboard.schematic.home":object|undefined;"dashboard.schematic.dir":object|undefined;"dashboard.schematic.head.type":object|undefined;"dashboard.schematic.head.name":object|undefined;"dashboard.schematic.head.owner":object|undefined;"dashboard.schematic.head.updated":object|undefined;"dashboard.schematic.head.replaceColor":object|undefined;"dashboard.schematic.head.allowReplay":object|undefined;"dashboard.schematic.info.path":{"path"?:unknown;}|undefined;"dashboard.schematic.info.replaceColor":object|undefined;"dashboard.schematic.info.allowReplay":object|undefined;"dashboard.schematic.info.type":{"type"?:unknown;}|undefined;"dashboard.schematic.info.updated":{"updated"?:unknown;}|undefined;"dashboard.schematic.info.item":{"item"?:unknown;}|undefined;"dashboard.schematic.info.members":{"members"?:unknown;}|undefined;"dashboard.schematic.info.btn.download":object|undefined;"dashboard.schematic.info.btn.close":object|undefined;"login.title":object|undefined;"login.placeholder.username":object|undefined;"login.placeholder.token":object|undefined;"login.label.username":object|undefined;"login.label.token":object|undefined;"login.generateToken":object|undefined;"login.submit":object|undefined;"wg.title":object|undefined;"wg.description":object|undefined;"as.title":object|undefined;"as.description":object|undefined;"ws.title":object|undefined;"ws.description":object|undefined;"mwg.title":object|undefined;"mwg.description":object|undefined;"rules":object|undefined;"council":object|undefined;"ranking":object|undefined;"navbar.title":object|undefined;"navbar.logo.alt":object|undefined;"navbar.links.home.title":object|undefined;"navbar.links.home.announcements":object|undefined;"navbar.links.home.about":object|undefined;"navbar.links.home.downloads":object|undefined;"navbar.links.home.faq":object|undefined;"navbar.links.rules.title":object|undefined;"navbar.links.rules.gamemode":object|undefined;"navbar.links.rules.wg":object|undefined;"navbar.links.rules.mwg":object|undefined;"navbar.links.rules.ws":object|undefined;"navbar.links.rules.as":object|undefined;"navbar.links.rules.rotating":object|undefined;"navbar.links.rules.megawg":object|undefined;"navbar.links.rules.micro":object|undefined;"navbar.links.rules.sf":object|undefined;"navbar.links.rules.general":object|undefined;"navbar.links.rules.coc":object|undefined;"navbar.links.help.title":object|undefined;"navbar.links.help.center":object|undefined;"navbar.links.help.docs":object|undefined;"navbar.links.account":object|undefined;"status.loading":object|undefined;"status.status":object|undefined;"status.online":object|undefined;"status.offline":object|undefined;"status.players":{"count"?:unknown;}|undefined;"status.version":{"version"?:unknown;}|undefined;"home.page":object|undefined;"home.title.first":object|undefined;"home.title.second":object|undefined;"home.subtitle.1":object|undefined;"home.subtitle.2":object|undefined;"home.subtitle.3":object|undefined;"home.join":object|undefined;"home.benefits.historic.title":object|undefined;"home.benefits.historic.description.1":object|undefined;"home.benefits.historic.description.2":object|undefined;"home.benefits.server.title":object|undefined;"home.benefits.server.description":object|undefined;"home.benefits.events.title":object|undefined;"home.benefits.events.description.1":object|undefined;"home.benefits.events.description.2":object|undefined;"home.prefix.Admin":object|undefined;"home.prefix.Dev":object|undefined;"home.prefix.Mod":object|undefined;"home.prefix.Sup":object|undefined;"home.prefix.Arch":object|undefined;"footer.imprint":object|undefined;"footer.privacy":object|undefined;"footer.coc":object|undefined;"footer.stats":object|undefined;"footer.gamemodes":object|undefined;"footer.announcements":object|undefined;"footer.join":object|undefined;}
type Translation = keyof TranslationVariables
type Environment = "none"|"node"|"browser"
declare module "astro-i18n" {

Datei anzeigen

@ -42,11 +42,11 @@
"links": {
"home": {
"title": "Start",
"announcements": "Ankündigungen",
"about": "Über Uns",
"downloads": "Downloads",
"faq": "FAQ"
},
"announcements": "Ankündigungen",
"rules": {
"title": "Regeln",
"gamemode": "Spielmodi",
@ -67,5 +67,14 @@
},
"account": "Konto"
}
},
"footer": {
"imprint": "Impressum",
"privacy": "Datenschutzerklärung",
"coc": "Verhaltensrichtlinien",
"stats": "Statistiken",
"gamemodes": "Spielmodi",
"announcements": "Ankündigungen",
"join": "Jetzt Spielen"
}
}

Datei anzeigen

@ -7,11 +7,11 @@
"links": {
"home": {
"title": "Home",
"announcements": "Announcements",
"about": "About",
"downloads": "Downloads",
"faq": "FAQ"
},
"announcements": "Announcements",
"rules": {
"title": "Rules",
"gamemode": "Gamemodes",
@ -81,5 +81,14 @@
"Sup": "Supporter",
"Arch": "Builder"
}
},
"footer": {
"imprint": "Imprint",
"privacy": "Privacy Policy",
"coc": "Code of Conduct",
"stats": "Stats",
"gamemodes": "Gamemodes",
"announcements": "Announcements",
"join": "Join Now"
}
}

Datei anzeigen

@ -0,0 +1,21 @@
{
"dashboard": {
"title": "Hallo, {# name #}!",
"rank": "Rang: {# rank #}",
"permissions": "Berechtigungen:",
"buttons": {
"logout": "Abmelden"
},
"schematic": {
"upload": "Hochladen",
"dir": "Ordner",
"head": {
"type": "Typ",
"owner": "Besitzer",
"updated": "Aktualisiert",
"replaceColor": "Farbe ersetzen",
"allowReplay": "Wiederholung erlauben"
}
}
}
}

Datei anzeigen

@ -0,0 +1,37 @@
{
"dashboard": {
"title": "Hello, {# name #}!",
"rank": "Rank: {# rank #}",
"permissions": "Permssions:",
"buttons": {
"logout": "Logout",
"admin": "Admin Panel"
},
"schematic": {
"upload": "Upload",
"home": "Schematics",
"dir": "Directory",
"head": {
"type": "Type",
"name": "Name",
"owner": "Owner",
"updated": "Updated",
"replaceColor": "Replace Color",
"allowReplay": "Allow Replay"
},
"info": {
"path": "Path: {# path #}",
"replaceColor": "Replace Color: ",
"allowReplay": "Allow Replay: ",
"type": "Type: {# type #}",
"updated": "Updated: {# updated #}",
"item": "Item: {# item #}",
"members": "Members: {# members #}",
"btn": {
"download": "Download",
"close": "Close"
}
}
}
}
}

16
src/i18n/pages/login/de.json Normale Datei
Datei anzeigen

@ -0,0 +1,16 @@
{
"login": {
"title": "Login",
"placeholder": {
"username": "Nutzername...",
"token": "***************"
},
"label": {
"username": "Nutzername",
"token": "Token"
},
"generateToken": "Wie generiere ich ein Token?",
"submit": "Login",
"error": "Falscher Nutzername oder falscher Token"
}
}

Datei anzeigen

@ -10,6 +10,7 @@
"token": "Token"
},
"generateToken": "How to generate Token",
"submit": "Login"
"submit": "Login",
"error": "Invalid username or token"
}
}

17
src/i18n/pages/rules/de.json Normale Datei
Datei anzeigen

@ -0,0 +1,17 @@
{
"wg": {
"description": "Heute werden die Schlachtfelder der Erde von schwerem Geschütz bestimmt. Mit unserem traditionellen Regelwerk sind auch die WarGears arenenverwüstende Schwergewichte. Aufgrund der Kanonentechnik mit den meisten Projektilen erwarten dich bei WarGears harte und kurzweilige Kämpfe."
},
"mwg": {
"description": "Im heutigen Straßenkampf hat massives Gerät keinen Platz, weswegen kleinere Maschinen auch heute noch ihre Berechtigung haben. Mit den etwas kleineren Kanonen sind MiniWarGears genau das richtige für Einsteiger, Gelegenheitsspieler und Experimentierfreudige."
},
"ws": {
"description": "Lange Zeit waren Kriegsschiffe das Nonplusultra der Kriegsführung. In Sachen Raketen- und Slimetechnik gilt das auch heute noch für Warships. Durch die begrenzte Kanonenstärke bieten WarShips lange, intensive und abwechslungsreiche Kämpfe, womit es immer neue Technik in der Arena gibt. Durch das Entern verlagert sich ein WarShip-Kampf nach einiger Zeit in das Wasser und bietet damit spannende PvP-Action."
},
"as": {
"description": "Der Traum vom Fliegen beflügelt die Menschheit schon seit Jahrtausenden. Der Spielmodus AirShips bietet dir die nahezu unbegrenzten Möglichkeiten des Himmels. Egal, ob du mit 15 2 Projektil-Kanonen oder 2 15 Projektil-Kanonen antrittst, du hast stets eine realistische Chance auf den Sieg. Denn: Alles hat seinen Preis."
},
"rules": "Regelwerk »",
"council": "Der Rat »",
"ranking": "Rangliste »"
}

21
src/i18n/pages/rules/en.json Normale Datei
Datei anzeigen

@ -0,0 +1,21 @@
{
"wg": {
"title": "WarGears",
"description": "Today, the battlefields of Earth are dominated by heavy artillery. With our traditional rules, WarGears are also arena-wrecking heavyweights. Due to the cannon technology with the most projectiles, you can expect hard and short-lived battles in WarGears."
},
"as": {
"title": "AirShips",
"description": "The dream of flying has inspired humanity for millennia. The AirShips game mode offers you the almost unlimited possibilities of the sky. Whether you compete with 15 2-projectile cannons or 2 15-projectile cannons, you always have a realistic chance of winning. Because: Everything has its price."
},
"ws": {
"title": "WarShips",
"description": "For a long time, warships were the ultimate weapon of war. This is still true for Warships today in terms of rocket and slime technology. Due to the limited cannon power, WarShips offer long, intense and varied battles, with new techniques always being introduced in the arena. After a while, a WarShip battle shifts to the water through boarding, providing exciting PvP action."
},
"mwg": {
"title": "MiniWarGears",
"description": "In today's urban warfare, there is no place for heavy equipment, which is why smaller machines still have their place today. With their slightly smaller cannons, MiniWarGears are the perfect choice for beginners, casual players, and those who like to experiment."
},
"rules": "Rules »",
"council": "Council »",
"ranking": "Ranking »"
}

BIN
src/images/AirShips.png Normale Datei

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 5.6 KiB

BIN
src/images/MiniWarGears.png Normale Datei

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 13 KiB

BIN
src/images/WarGears.png Normale Datei

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 18 KiB

BIN
src/images/WarShips.png Normale Datei

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 15 KiB

Datei anzeigen

@ -34,17 +34,15 @@ const { title } = Astro.props;
<CaretDownOutline class="ml-2 mt-auto" />
</div>
<div>
<a class="btn btn-gray my-1" href={l("/")}>{t("navbar.links.home.announcements")}</a>
<a class="btn btn-gray" href={l("/about")}>{t("navbar.links.home.about")}</a>
<a class="btn btn-gray">{t("navbar.links.home.downloads")}</a>
<a class="btn btn-gray" href={l("/faq")}>{t("navbar.links.home.faq")}</a>
</div>
</div>
<a class="btn btn-gray my-1" href={l("/")}>
<span class="btn__text">{t("navbar.links.announcements")}</span>
</a>
<div class="btn-dropdown my-1">
<div class="btn btn-gray" tabindex="1">
<a rel="prefetch" href={l("/blog")}>
<a rel="prefetch" href={l("/rules")}>
<span class="btn__text">{t("navbar.links.rules.title")}</span>
</a>
<CaretDownOutline class="ml-2 mt-auto" />
@ -115,13 +113,13 @@ const { title } = Astro.props;
<div class="footer-card">
<h1>Links</h1>
<a href={l("/")}>{t("navbar.links.home.title")}</a>
<a href={l("/join")}>Join Now</a>
<a href={l("/")}>Announcements</a>
<a href={l("/")}>Gamemodes</a>
<a href={l("/")}>Stats</a>
<a href={l("/")}>Code of conduct</a>
<a href={l("/")}>Privacy Policy</a>
<a href={l("/imprint")}>Imprint</a>
<a href={l("/join")}>{t("footer.join")}</a>
<a href={l("/")}>{t("footer.announcements")}</a>
<a href={l("/rules")}>{t("footer.gamemodes")}</a>
<a href={l("/")}>{t("footer.stats")}</a>
<a href={l("/code-of-conduct")}>{t("footer.coc")}</a>
<a href={l("/privacy")}>{t("footer.privacy")}</a>
<a href={l("/imprint")}>{t("footer.imprint")}</a>
</div>
<div class="footer-card">
<h1>Social Media</h1>

15
src/layouts/PageLayout.astro Normale Datei
Datei anzeigen

@ -0,0 +1,15 @@
---
import NavbarLayout from "./NavbarLayout.astro";
import localBau from "../images/2023-10-08_20.43.43.png";
import { Image } from "astro:assets";
const { title } = Astro.props;
---
<NavbarLayout title={title}>
<Image src={localBau} alt="Bau" width="1920" height="1080" class="w-screen h-screen dark:brightness-75 fixed -z-10 object-cover" draggable="false" />
<div class="mx-auto bg-gray-100 p-8 rounded-b-md shadow-md pt-40 sm:pt-28 md:pt-14
dark:text-white dark:bg-neutral-900" style="width: min(100vw, 75em);">
<slot />
</div>
</NavbarLayout>

Datei anzeigen

@ -4,6 +4,7 @@ import NavbarLayout from "../layouts/NavbarLayout.astro";
import {astroI18n, createGetStaticPaths} from "astro-i18n";
import localBau from "../images/2023-10-08_20.43.43.png";
import {Image} from "astro:assets";
import PageLayout from "../layouts/PageLayout.astro";
export const getStaticPaths = createGetStaticPaths(async () => {
let posts = await getCollection("pages");
@ -18,22 +19,15 @@ const { Content } = await page.render();
---
<NavbarLayout title={page.data.title}>
<div>
<Image src={localBau} alt="Bau" width="1920" height="1080" class="w-screen h-screen dark:brightness-75 fixed -z-10 object-cover" draggable="false" />
<article>
<h1 class="text-left">{page.data.title}</h1>
<Content />
</article>
</div>
</NavbarLayout>
<PageLayout title={page.data.title}>
<article>
<h1 class="text-left">{page.data.title}</h1>
<Content />
</article>
</PageLayout>
<style is:global>
article {
width: min(100vw, 75em);
@apply mx-auto bg-gray-100 p-8 rounded-b-md shadow-md pt-40 sm:pt-28 md:pt-14
dark:text-white dark:bg-neutral-900;
p {
@apply my-4 leading-7;
}

Datei anzeigen

@ -1,24 +1,17 @@
---
import NavbarLayout from "../layouts/NavbarLayout.astro";
import localBau from "../images/2023-10-08_20.43.43.png";
import { Image } from "astro:assets";
import Dashboard from "../components/Dashboard.svelte";
import {AES} from "crypto-js";
import {l} from "../util/util";
import PageLayout from "../layouts/PageLayout.astro";
---
<NavbarLayout title="Dashboard">
<div>
<Image src={localBau} alt="Bau" width="1920" height="1080" class="w-screen h-screen dark:brightness-75 fixed -z-10 object-cover" draggable="false" />
<div class="mx-auto bg-gray-100 p-8 rounded-b-md shadow-md pt-40 sm:pt-28 md:pt-14
dark:text-white dark:bg-neutral-900" style="width: min(100vw, 75em)">
<script>
import {AES} from "crypto-js";
import {l} from "../util/util";
if (AES.decrypt(localStorage.getItem("sw-session") ?? "", import.meta.env.PUBLIC_SECRET)?.toString() === "") {
window.location.href = l("/login");
}
</script>
<Dashboard client:only="svelte" />
</div>
</div>
</NavbarLayout>
<PageLayout title="Dashboard">
<script>
import {AES} from "crypto-js";
import {l} from "../util/util";
if (AES.decrypt(localStorage.getItem("sw-session") ?? "", import.meta.env.PUBLIC_SECRET)?.toString() === "") {
window.location.href = l("/login");
}
</script>
<Dashboard client:only="svelte" />
</PageLayout>

Datei anzeigen

@ -1,9 +1,7 @@
---
import NavbarLayout from "../layouts/NavbarLayout.astro";
import localBau from "../images/2023-10-08_20.43.43.png";
import {Image} from "astro:assets";
import {l} from "../util/util";
import Login from "../components/Login.svelte";
import NavbarLayout from "../layouts/NavbarLayout.astro";
---
<NavbarLayout title="Login">
@ -15,9 +13,5 @@ import Login from "../components/Login.svelte";
window.location.href = l("/dashboard");
}
</script>
<Image src={localBau} alt="Bau" width="1920" height="1080" class="w-screen h-screen dark:brightness-75 fixed -z-10 object-cover" draggable="false" />
<div class="h-screen mx-auto p-8 rounded-b-md shadow-md pt-40 sm:pt-28 md:pt-14 flex flex-col justify-center items-center
dark:text-white " style="width: min(100vw, 75em);">
<Login client:load />
</div>
<Login client:load />
</NavbarLayout>

36
src/pages/rules.astro Normale Datei
Datei anzeigen

@ -0,0 +1,36 @@
---
import wg from "../images/WarGears.png"
import mwg from "../images/MiniWarGears.png"
import as from "../images/AirShips.png"
import ws from "../images/WarShips.png"
import {t} from "astro-i18n";
import {getCollection} from "astro:content";
import PageLayout from "../layouts/PageLayout.astro";
const imageMap = {
"wg": wg,
"mwg": mwg,
"as": as,
"ws": ws
}
const modes = await getCollection("modes", entry => entry.data.main)
---
<PageLayout title="Rules">
{modes.map(value => (
<div class="dark:bg-neutral-800 rounded-md p-4 border border-neutral-400 shadow-md my-4 flex flex-col
md:flex-row">
<Image height="300" width="300" src={imageMap[value.data.translationKey]} alt={t(value.data.translationKey + ".title")} class="dark:invert"></Image>
<div class="ml-4">
<h1 class="text-2xl font-bold">{t(value.data.translationKey + ".title")}</h1>
<div>{t(value.data.translationKey + ".description")}</div>
<div class="mt-2 flex flex-col">
<a href="/" class="text-yellow-300 hover:underline w-fit">{t("rules")}</a>
<a href="/" class="text-yellow-300 hover:underline w-fit">{t("council")}</a>
{value.data.ranked ? <a href="/" class="text-yellow-300 hover:underline w-fit">{t("ranking")}</a> : null}
</div>
</div>
</div>))}
</PageLayout>