Updates and more
Dieser Commit ist enthalten in:
Ursprung
7450ecdabb
Commit
3889f28eb8
1
.gitignore
vendored
1
.gitignore
vendored
@ -21,3 +21,4 @@ pnpm-debug.log*
|
||||
.DS_Store
|
||||
/.astro-i18n/
|
||||
/bun.lockb
|
||||
/src/pages/de/
|
||||
|
@ -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
4519
pnpm-lock.yaml
Normale Datei
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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}
|
33
src/components/dashboard/SchematicInfo.svelte
Normale Datei
33
src/components/dashboard/SchematicInfo.svelte
Normale Datei
@ -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}
|
56
src/components/dashboard/SchematicInfoModal.svelte
Normale Datei
56
src/components/dashboard/SchematicInfoModal.svelte
Normale Datei
@ -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>
|
115
src/components/dashboard/SchematicList.svelte
Normale Datei
115
src/components/dashboard/SchematicList.svelte
Normale Datei
@ -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>
|
58
src/components/dashboard/SchematicListTile.svelte
Normale Datei
58
src/components/dashboard/SchematicListTile.svelte
Normale Datei
@ -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>
|
47
src/components/dashboard/UploadModal.svelte
Normale Datei
47
src/components/dashboard/UploadModal.svelte
Normale Datei
@ -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>
|
41
src/components/dashboard/UserInfo.svelte
Normale Datei
41
src/components/dashboard/UserInfo.svelte
Normale Datei
@ -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>
|
@ -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));
|
||||
}
|
||||
}
|
@ -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) {}
|
||||
|
@ -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
33
src/components/repo/schem.ts
Normale Datei
@ -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
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
@ -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";
|
||||
|
@ -33,3 +33,11 @@ export const SchematicInfoSchema = z.object({
|
||||
})
|
||||
|
||||
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>
|
@ -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
|
||||
}
|
||||
|
4
src/content/modes/airship.json
Normale Datei
4
src/content/modes/airship.json
Normale Datei
@ -0,0 +1,4 @@
|
||||
{
|
||||
"translationKey": "as",
|
||||
"main": true
|
||||
}
|
5
src/content/modes/miniwargear.json
Normale Datei
5
src/content/modes/miniwargear.json
Normale Datei
@ -0,0 +1,5 @@
|
||||
{
|
||||
"translationKey": "mwg",
|
||||
"main": true,
|
||||
"ranked": true
|
||||
}
|
5
src/content/modes/wargear.json
Normale Datei
5
src/content/modes/wargear.json
Normale Datei
@ -0,0 +1,5 @@
|
||||
{
|
||||
"translationKey": "wg",
|
||||
"main": true,
|
||||
"ranked": true
|
||||
}
|
4
src/content/modes/warship.json
Normale Datei
4
src/content/modes/warship.json
Normale Datei
@ -0,0 +1,4 @@
|
||||
{
|
||||
"translationKey": "ws",
|
||||
"main": true
|
||||
}
|
4
src/env.d.ts
vendored
4
src/env.d.ts
vendored
@ -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" {
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
21
src/i18n/pages/dashboard/de.json
Normale Datei
21
src/i18n/pages/dashboard/de.json
Normale Datei
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
src/i18n/pages/dashboard/en.json
Normale Datei
37
src/i18n/pages/dashboard/en.json
Normale Datei
@ -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
16
src/i18n/pages/login/de.json
Normale Datei
@ -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"
|
||||
}
|
||||
}
|
@ -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
17
src/i18n/pages/rules/de.json
Normale Datei
@ -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
21
src/i18n/pages/rules/en.json
Normale Datei
@ -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
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
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
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
src/images/WarShips.png
Normale Datei
Binäre Datei nicht angezeigt.
Nachher Breite: | Höhe: | Größe: 15 KiB |
@ -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
15
src/layouts/PageLayout.astro
Normale Datei
@ -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>
|
@ -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" />
|
||||
<PageLayout title={page.data.title}>
|
||||
<article>
|
||||
<h1 class="text-left">{page.data.title}</h1>
|
||||
<Content />
|
||||
</article>
|
||||
</div>
|
||||
</NavbarLayout>
|
||||
</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;
|
||||
}
|
||||
|
@ -1,16 +1,11 @@
|
||||
---
|
||||
|
||||
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)">
|
||||
<PageLayout title="Dashboard">
|
||||
<script>
|
||||
import {AES} from "crypto-js";
|
||||
import {l} from "../util/util";
|
||||
@ -19,6 +14,4 @@ import Dashboard from "../components/Dashboard.svelte";
|
||||
}
|
||||
</script>
|
||||
<Dashboard client:only="svelte" />
|
||||
</div>
|
||||
</div>
|
||||
</NavbarLayout>
|
||||
</PageLayout>
|
@ -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>
|
||||
</NavbarLayout>
|
36
src/pages/rules.astro
Normale Datei
36
src/pages/rules.astro
Normale Datei
@ -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>
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren