Updates
Einige Prüfungen sind fehlgeschlagen
SteamWarCI Build failed

Dieser Commit ist enthalten in:
Chaoscaot 2024-01-06 15:08:54 +01:00
Ursprung efd674eae1
Commit 9ee0fd5448
28 geänderte Dateien mit 1162 neuen und 800 gelöschten Zeilen

Datei anzeigen

@ -18,32 +18,32 @@
"ci": "pnpm run clean:dist && pnpm install && pnpm run i18n:sync && pnpm run build && pnpm run package" "ci": "pnpm run clean:dist && pnpm install && pnpm run i18n:sync && pnpm run build && pnpm run package"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/svelte": "^5.0.2", "@astrojs/svelte": "^5.0.3",
"@astrojs/tailwind": "^5.0.4", "@astrojs/tailwind": "^5.1.0",
"@astropub/icons": "^0.2.0", "@astropub/icons": "^0.2.0",
"@types/color": "^3.0.6", "@types/color": "^3.0.6",
"@types/node": "^20.10.5", "@types/node": "^20.10.6",
"@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.15.0", "@typescript-eslint/parser": "^6.17.0",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"cssnano": "^6.0.2", "cssnano": "^6.0.3",
"esbuild": "^0.19.10", "esbuild": "^0.19.11",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-plugin-astro": "^0.31.0", "eslint-plugin-astro": "^0.31.0",
"eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-svelte": "^2.35.1", "eslint-plugin-svelte": "^2.35.1",
"postcss-nesting": "^12.0.2", "postcss-nesting": "^12.0.2",
"sass": "^1.69.5", "sass": "^1.69.7",
"svelte": "^4.2.8", "svelte": "^4.2.8",
"tailwind-merge": "^2.2.0", "tailwind-merge": "^2.2.0",
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.0",
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },
"dependencies": { "dependencies": {
"@astrojs/sitemap": "^3.0.3", "@astrojs/sitemap": "^3.0.4",
"@codemirror/lang-json": "^6.0.1", "@codemirror/lang-json": "^6.0.1",
"@ddietr/codemirror-themes": "^1.4.2", "@ddietr/codemirror-themes": "^1.4.2",
"astro": "^4.0.7", "astro": "^4.1.0",
"astro-i18n": "^2.2.0", "astro-i18n": "^2.2.0",
"astro-robots-txt": "^1.0.0", "astro-robots-txt": "^1.0.0",
"astro-seo": "^0.8.0", "astro-seo": "^0.8.0",
@ -57,7 +57,7 @@
"flowbite-svelte": "^0.44.21", "flowbite-svelte": "^0.44.21",
"flowbite-svelte-icons": "^0.4.5", "flowbite-svelte-icons": "^0.4.5",
"sharp": "^0.32.6", "sharp": "^0.32.6",
"svelte-awesome": "^3.3.0", "svelte-awesome": "^3.3.1",
"svelte-codemirror-editor": "^1.2.0", "svelte-codemirror-editor": "^1.2.0",
"svelte-spa-router": "^3.3.0", "svelte-spa-router": "^3.3.0",
"zod": "^3.22.4" "zod": "^3.22.4"

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

Datei anzeigen

@ -22,11 +22,12 @@
export let selected: string | null = null export let selected: string | null = null
export let items: {name: string, value: string}[] = [] export let items: {name: string, value: string}[] = []
export let maxItems = 5;
export let searchValue = items.find(item => item.value === selected)?.name || '' export let searchValue = items.find(item => item.value === selected)?.name || ''
let open = false let open = false
$: filteredItems = items.filter(item => item.name.toLowerCase().includes(searchValue.toLowerCase())).filter((value, index) => index < 5) $: filteredItems = items.filter(item => item.name.toLowerCase().includes(searchValue.toLowerCase())).filter((value, index) => index < maxItems)
function selectItem(item: {name: string, value: string}) { function selectItem(item: {name: string, value: string}) {
selected = item.value selected = item.value

Datei anzeigen

@ -18,83 +18,92 @@
--> -->
<script lang="ts"> <script lang="ts">
import {ArrowLeftSolid} from "flowbite-svelte-icons"; import {ArrowLeftSolid} from "flowbite-svelte-icons";
import {Button, Card, Navbar, NavBrand, Spinner} from "flowbite-svelte"; import {Button, Card, Navbar, NavBrand, Spinner} from "flowbite-svelte";
import {mapToMap, nameRegex} from "../util.ts"; import {mapToMap, nameRegex} from "../util.ts";
import TypeAheadSearch from "../components/TypeAheadSearch.svelte"; import TypeAheadSearch from "../components/TypeAheadSearch.svelte";
import {branches} from "@stores/stores.ts"; import {branches} from "@stores/stores.ts";
import Editor from "./edit/Editor.svelte"; import Editor from "./edit/Editor.svelte";
import {pageRepo} from "@repo/page.ts"; import {pageRepo} from "@repo/page.ts";
$: pagesFuture = $pageRepo.listPages(selectedBranch); $: pagesFuture = $pageRepo.listPages(selectedBranch);
let selected: number | null = null; let selected: number | null = null;
let selectedBranch: string = "master"; let selectedBranch: string = "master";
let searchValue: string = ""; let searchValue: string = "";
let dirty = false;
$: availableBranches = $branches.map((branch) => ({ let selectedPath: string = "";
name: branch, let pathSearchValue: string = "";
value: branch
}))
async function createBranch() { $: availableBranches = $branches.map((branch) => ({
const name = prompt("Branch name:") name: branch,
if (name) { value: branch
selected = null }))
await $pageRepo.createBranch(name)
let inter = setInterval(() => {
branches.reload()
if ($branches.includes(name)) {
selectedBranch = name
searchValue = ""
clearInterval(inter)
}
}, 1000)
}
}
async function deleteBranch(con: boolean) { async function createBranch() {
if (selectedBranch !== "master") { const name = prompt("Branch name:")
let conf = con || confirm("Are you sure you want to delete this branch?") if (name) {
if(conf) { selected = null
await $pageRepo.deleteBranch(selectedBranch) await $pageRepo.createBranch(name)
let inter = setInterval(() => { let inter = setInterval(() => {
branches.reload() branches.reload()
if (!$branches.includes(selectedBranch)) { if ($branches.includes(name)) {
selectedBranch = "master" selectedBranch = name
searchValue = "" searchValue = ""
clearInterval(inter) clearInterval(inter)
} }
}, 1000) }, 1000)
} }
} else {
alert("You can't delete the master branch")
} }
}
async function createFile() { function changePage(id: number) {
let name = prompt("File name:", "pages/en/[Name]") if (dirty) {
if (name) { if (confirm("You have unsaved changes. Are you sure you want to change the page?")) {
await $pageRepo.createFile(`${name}`, selectedBranch) selected = id
reload() dirty = false
}
} else {
selected = id
}
} }
}
function reload() { async function deleteBranch(con: boolean) {
const w = selectedBranch if (selectedBranch !== "master") {
selectedBranch = "###!" let conf = con || confirm("Are you sure you want to delete this branch?")
selectedBranch = w if(conf) {
} await $pageRepo.deleteBranch(selectedBranch)
let inter = setInterval(() => {
branches.reload()
if (!$branches.includes(selectedBranch)) {
selectedBranch = "master"
searchValue = ""
clearInterval(inter)
}
}, 1000)
}
} else {
alert("You can't delete the master branch")
}
}
async function mergeBranch() { async function createFile() {
await $pageRepo.merge(selectedBranch, `Go live of ${selectedBranch}`) let name = prompt("File name:", "[Name].md")
await deleteBranch(true) if (name) {
} await $pageRepo.createFile(`${selectedPath}${name}`, selectedBranch)
reload()
}
}
function reload() {
const w = selectedBranch
selectedBranch = "###!"
selectedBranch = w
}
</script> </script>
<div class="flex flex-col h-screen overflow-scroll"> <div class="flex flex-col h-screen overflow-scroll">
<Navbar let:hidden let:toggle> <Navbar>
<NavBrand href="#"> <NavBrand href="#">
<ArrowLeftSolid></ArrowLeftSolid> <ArrowLeftSolid></ArrowLeftSolid>
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white"> <span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
@ -108,44 +117,42 @@ async function mergeBranch() {
{#await pagesFuture} {#await pagesFuture}
<Spinner /> <Spinner />
{:then pages} {:then pages}
{@const pagesMap = mapToMap(pages)}
<div class="border-b border-b-gray-600 pb-2 flex justify-between"> <div class="border-b border-b-gray-600 pb-2 flex justify-between">
<TypeAheadSearch items={availableBranches} bind:selected={selectedBranch} bind:searchValue /> <div>
<TypeAheadSearch items={availableBranches} bind:selected={selectedBranch} bind:searchValue />
<TypeAheadSearch items={Array.from(pagesMap.keys()).map(value => ({value, name: value}))} bind:selected={selectedPath} bind:searchValue={pathSearchValue} maxItems={Number.MAX_VALUE} />
</div>
<div> <div>
{#if selectedBranch !== "master"} {#if selectedBranch !== "master"}
<Button on:click={mergeBranch}>Merge Branch</Button> <Button on:click={createFile} color="alternative" disabled={!selectedPath}>Create File</Button>
<Button on:click={() => deleteBranch(false)} color="none">Delete Branch</Button>
{:else} {:else}
<Button on:click={createBranch}>Create Branch</Button> <Button on:click={createBranch}>Create Branch</Button>
{/if} {/if}
<Button on:click={createFile} color="alternative">Create File</Button>
{#if selectedBranch !== "master"}
<Button on:click={() => deleteBranch(false)} color="none">Delete Branch</Button>
{/if}
</div> </div>
</div> </div>
{@const pagesMap = mapToMap(pages)} <ul>
{#each pagesMap as [key, value]} {#if (selectedPath)}
<details> {@const value = pagesMap.get(selectedPath) || []}
<summary class="p-4 transition-colors hover:bg-gray-700 cursor-pointer">{key}</summary> {#each value as page}
<ul> {@const nameRegexExec = nameRegex.exec(page.path)}
{#each value as page} {@const match = nameRegexExec ? nameRegexExec[0] : ""}
{@const nameRegexExec = nameRegex.exec(page.path)} {@const startIndex = page.path.indexOf(match)}
{@const match = nameRegexExec ? nameRegexExec[0] : ""} {@const endIndex = startIndex + match.length}
{@const startIndex = page.path.indexOf(match)} <li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer" on:click|preventDefault={() => changePage(page.id)}>
{@const endIndex = startIndex + match.length} <span class:text-orange-600={selected === page.id}>{page.path.substring(0, startIndex)}</span><span class="text-white" class:!text-orange-500={selected === page.id}>{match}</span><span class:text-orange-600={selected === page.id}>{page.path.substring(endIndex, page.path.length)}</span>
<li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer" on:click|preventDefault={() => selected = page.id}> </li>
<span class:text-orange-600={selected === page.id}>{page.path.substring(0, startIndex)}</span><span class="text-white" class:!text-orange-500={selected === page.id}>{match}</span><span class:text-orange-600={selected === page.id}>{page.path.substring(endIndex, page.path.length)}</span> {/each}
</li> {/if}
{/each} </ul>
</ul>
</details>
{/each}
{:catch error} {:catch error}
<p>{error.message}</p> <p>{error.message}</p>
{/await} {/await}
</Card> </Card>
<Card class="!max-w-full" style="grid-column: 2/4"> <Card class="!max-w-full" style="grid-column: 2/4">
{#if selected} {#if selected}
<Editor pageId={selected} branch={selectedBranch} on:reload={reload} /> <Editor pageId={selected} branch={selectedBranch} on:reload={reload} bind:dirty />
{/if} {/if}
</Card> </Card>
</div> </div>

Datei anzeigen

@ -30,6 +30,7 @@
export let pageId: number; export let pageId: number;
export let branch: string; export let branch: string;
export let dirty: boolean = false;
let dispatcher = createEventDispatcher(); let dispatcher = createEventDispatcher();
@ -47,6 +48,7 @@
let message = window.prompt("Commit message:", "Update " + page!.name) let message = window.prompt("Commit message:", "Update " + page!.name)
if (message) { if (message) {
$pageRepo.updatePage(pageId, pageContent, page!.sha, message, branch) $pageRepo.updatePage(pageId, pageContent, page!.sha, message, branch)
dirty = false;
} }
} }
@ -54,11 +56,16 @@
let message = window.prompt("Commit message:", "Delete " + page!.name) let message = window.prompt("Commit message:", "Delete " + page!.name)
if (message) { if (message) {
await $pageRepo.deletePage(pageId, message, page!.sha, branch) await $pageRepo.deletePage(pageId, message, page!.sha, branch)
dirty = false;
dispatcher("reload") dispatcher("reload")
} }
} }
</script> </script>
<svelte:window on:beforeunload={() => {
if (dirty) {
return "You have unsaved changes. Are you sure you want to leave?";
}
}} />
{#await pageFuture} {#await pageFuture}
<Spinner /> <Spinner />
{:then p} {:then p}
@ -76,9 +83,9 @@
</Toolbar> </Toolbar>
</div> </div>
{#if page?.name.endsWith("md")} {#if page?.name.endsWith("md")}
<MDEMarkdownEditor bind:value={pageContent} /> <MDEMarkdownEditor bind:value={pageContent} bind:dirty />
{:else} {:else}
<CodeMirror bind:value={pageContent} lang={json()} theme={materialDark} /> <CodeMirror bind:value={pageContent} lang={json()} theme={materialDark} on:change={() => dirty = true} />
{/if} {/if}
</div> </div>
{:catch error} {:catch error}

Datei anzeigen

@ -23,6 +23,8 @@
import "easymde/dist/easymde.min.css" import "easymde/dist/easymde.min.css"
export let value: string; export let value: string;
export let dirty: boolean = false;
let editor: HTMLTextAreaElement; let editor: HTMLTextAreaElement;
let mde: EasyMDE; let mde: EasyMDE;
@ -34,8 +36,10 @@
}) })
mde.codemirror.on("change", () => { mde.codemirror.on("change", () => {
value = mde.value(); value = mde.value();
dirty = true;
}) })
}) })
onDestroy(() => { onDestroy(() => {
mde.toTextArea(); mde.toTextArea();
mde.cleanup(); mde.cleanup();

Datei anzeigen

@ -19,10 +19,11 @@
<script lang="ts"> <script lang="ts">
import {createEventDispatcher} from "svelte"; import {createEventDispatcher} from "svelte";
import {Modal, Spinner} from "flowbite-svelte"; import {Spinner} from "flowbite-svelte";
import SchematicInfoModal from "./SchematicInfoModal.svelte"; import SchematicInfoModal from "./SchematicInfoModal.svelte";
import type {Player} from "@type/data.ts"; import type {Player} from "@type/data.ts";
import {schemRepo} from "@repo/schem.ts"; import {schemRepo} from "@repo/schem.ts";
import SWModal from "@components/styled/SWModal.svelte";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -37,14 +38,14 @@
</script> </script>
{#await schemInfo} {#await schemInfo}
<Modal title="Loading" open on:close={() => dispatch("reset")}> <SWModal title="Loading" open on:close={() => dispatch("reset")}>
<Spinner /> <Spinner />
</Modal> </SWModal>
{:then info} {:then info}
<SchematicInfoModal {user} {info} on:reset /> <SchematicInfoModal {user} {info} on:reset />
{:catch e} {:catch e}
<Modal title="Error" open on:close={() => dispatch("reset")}> <SWModal title="Error" open on:close={() => dispatch("reset")}>
<p>{e.message}</p> <p>{e.message}</p>
<button class="btn !ml-auto" slot="footer" on:click={() => dispatch("reset")}>Close</button> <button class="btn !ml-auto" slot="footer" on:click={() => dispatch("reset")}>Close</button>
</Modal> </SWModal>
{/await} {/await}

Datei anzeigen

@ -20,12 +20,12 @@
<script lang="ts"> <script lang="ts">
import {astroI18n, t} from "astro-i18n"; import {astroI18n, t} from "astro-i18n";
import {CheckSolid, XCircleOutline} from "flowbite-svelte-icons"; import {CheckSolid, XCircleOutline} from "flowbite-svelte-icons";
import {Modal} from "flowbite-svelte";
import type {SchematicInfo} from "@type/schem.ts"; import type {SchematicInfo} from "@type/schem.ts";
import {createEventDispatcher} from "svelte"; import {createEventDispatcher} from "svelte";
import type {Player} from "@type/data.ts"; import type {Player} from "@type/data.ts";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {schemRepo} from "@repo/schem.ts"; import {schemRepo} from "@repo/schem.ts";
import SWModal from "@components/styled/SWModal.svelte";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -39,7 +39,7 @@
} }
</script> </script>
<Modal title={info.schem.name} autoclose open on:close={() => dispatch("reset")}> <SWModal title={info.schem.name} open on:close={() => dispatch("reset")}>
<p>{t("dashboard.schematic.info.path", {path: info.path})}</p> <p>{t("dashboard.schematic.info.path", {path: info.path})}</p>
<p class="flex !mt-0"> <p class="flex !mt-0">
{t("dashboard.schematic.info.replaceColor")} {t("dashboard.schematic.info.replaceColor")}
@ -76,4 +76,4 @@
{/if} {/if}
<button class="btn" class:!ml-auto={info.schem.owner !== user.id} on:click={() => dispatch("reset")}>{t("dashboard.schematic.info.btn.close")}</button> <button class="btn" class:!ml-auto={info.schem.owner !== user.id} on:click={() => dispatch("reset")}>{t("dashboard.schematic.info.btn.close")}</button>
</svelte:fragment> </svelte:fragment>
</Modal> </SWModal>

Datei anzeigen

@ -150,7 +150,7 @@
<div class="w-full flex justify-center mt-4"> <div class="w-full flex justify-center mt-4">
<ul class="inline-flex"> <ul class="inline-flex">
<li> <li>
<button on:click={() => page = 0} class="btn btn-gray h-8 px-3 text-sm flex items-center !m-0 !rounded-l-none"> <button on:click={() => page = 0} class="btn btn-gray h-8 px-3 text-sm flex items-center !m-0 !rounded-r-none">
<span class="sr-only">Next</span> <span class="sr-only">Next</span>
<ChevronDoubleLeftOutline class="w-3 h-3" /> <ChevronDoubleLeftOutline class="w-3 h-3" />
</button> </button>

Datei anzeigen

@ -34,14 +34,13 @@
{#await request} {#await request}
<p>{t("status.loading")}</p> <p>{t("status.loading")}</p>
{:then data} {:then data}
<p>Playtime: {new Intl.NumberFormat(astroI18n.locale, { <p>{t("dashboard.stats.playtime", {playtime: new Intl.NumberFormat(astroI18n.locale, {
minimumFractionDigits: 2, minimumFractionDigits: 2,
maximumFractionDigits: 2 maximumFractionDigits: 2
}).format(data.playtime)}h</p> }).format(data.playtime)})}</p>
<p>Fights: {data.fights}</p> <p>{t("dashboard.stats.fights", {fights: data.fights})}</p>
{#if user.perms.includes("CHECK")} {#if user.perms.includes("CHECK")}
<p>Schematics Checked: {data.acceptedSchematics}</p> <p>{t("dashboard.stats.checked", {checked: data.acceptedSchematics})}</p>
{/if} {/if}
{:catch error} {:catch error}
<p>error: {error}</p>
{/await} {/await}

Datei anzeigen

@ -18,9 +18,10 @@
--> -->
<script lang="ts"> <script lang="ts">
import {Modal} from "flowbite-svelte";
import {createEventDispatcher} from "svelte"; import {createEventDispatcher} from "svelte";
import {schemRepo} from "@repo/schem.ts"; import {schemRepo} from "@repo/schem.ts";
import SWModal from "@components/styled/SWModal.svelte";
import {t} from "astro-i18n"
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -55,12 +56,12 @@
let uploadFile: FileList | null = null; let uploadFile: FileList | null = null;
</script> </script>
<Modal title="Upload Schematic" bind:open autoclose outsideclose> <SWModal title={t("dashboard.schematic.title")} bind:open>
<form> <form>
<input type="file" bind:files={uploadFile} /> <input type="file" bind:files={uploadFile} />
</form> </form>
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">
<button class="btn !ml-auto" on:click={upload}>Upload</button> <button class="btn !ml-auto" on:click={upload}>{t("dashboard.schematic.upload")}</button>
<button class="btn btn-gray" on:click={() => open = false}>Close</button> <button class="btn btn-gray" on:click={() => open = false}>{t("dashboard.schematic.cancel")}</button>
</svelte:fragment> </svelte:fragment>
</Modal> </SWModal>

Datei anzeigen

@ -24,6 +24,8 @@
import Statistics from "./Statistics.svelte"; import Statistics from "./Statistics.svelte";
import {authRepo} from "@repo/auth.ts"; import {authRepo} from "@repo/auth.ts";
import {tokenStore} from "@repo/repo.ts"; import {tokenStore} from "@repo/repo.ts";
import SWModal from "@components/styled/SWModal.svelte";
import SWButton from "@components/styled/SWButton.svelte";
export let user: Player; export let user: Player;
@ -33,6 +35,7 @@
window.location.href = l("/login") window.location.href = l("/login")
} }
</script> </script>
<div class="flex mb-4 flex-col md:flex-row"> <div class="flex mb-4 flex-col md:flex-row">
<div> <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 <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

Datei anzeigen

@ -0,0 +1,88 @@
<!--
- This file is a part of the SteamWar software.
-
- Copyright (C) 2024 SteamWar.de-Serverteam
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<script lang="ts">
import {createEventDispatcher, onMount} from "svelte";
export let title: string;
export let open: boolean;
let internalOpen = open;
const dispatch = createEventDispatcher();
$: if (open && !internalOpen) {
dialog.showModal();
internalOpen = true;
} else if (!open && internalOpen) {
dialog.close();
internalOpen = false;
}
let dialog: HTMLDialogElement;
onMount(() => {
if (open) {
dialog.showModal();
internalOpen = true;
}
});
function close() {
open = false;
internalOpen = false;
dispatch("close");
}
</script>
<dialog bind:this={dialog} on:close={close} on:cancel={close} on:click={() => dialog.close()} aria-hidden="true">
<div on:click|stopPropagation aria-hidden="true">
<div class="spaced bordered">
<h1>{title}</h1>
</div>
<div class="spaced main bordered">
<slot />
</div>
<div class="footer spaced" on:click={() => dialog.close()} aria-hidden="true">
<slot name="footer" />
</div>
</div>
</dialog>
<style lang="scss">
h1 {
@apply text-4xl font-bold;
}
dialog {
@apply max-h-full max-w-md w-full rounded-lg shadow-lg
dark:bg-neutral-800 dark:text-neutral-100;
}
.spaced {
@apply p-6;
}
.bordered {
@apply border-b border-neutral-200 dark:border-neutral-700;
}
.footer {
@apply flex mx-4 my-2;
}
</style>

Datei anzeigen

@ -0,0 +1,38 @@
---
title: AirShip Public Clash 2023 Eventplan
key: airship-public-clash-2023-eventplan
description: Der Eventplan für den AirShip Public Clash 2023.
created: 2023-12-16
tags:
- event
- airship
---
Eventleitung: TheBreadBeard
Kampfleiter: TheBreadBeard, WarGear_Titan, Friderik (änderungen möglich)
| Team | Wins |
|----------------------|------|
| Defenders \[DFD] | 2 |
| Gadgetron \[GAD] | / |
| Halcyon \[Hlcy] | 6 |
| 1gardedivision \[1.] | 3 |
## 17.12.2023
| Startzeit | Begegnung |
|-----------|-------------|
| 16:00 | DFD vs GAD |
| 16:00 | 1. vs Hlcy |
| 16:20 | 1. vs GAD |
| 16:20 | Hlcy vs DFD |
| 16:40 | GAD vs Hlcy |
| 16:40 | 1. vs DFD |
| 17:00 | Pause |
| 17:20 | Hlcy vs DFD |
| 17:20 | 1. vs GAD |
| 17:40 | Hlcy vs GAD |
| 17:40 | DFD vs 1. |
| 18:00 | Hlcy vs 1. |
| 18:00 | DFD vs GAD |

Datei anzeigen

@ -0,0 +1,50 @@
---
title: Bewerbungskriterien für den Supporter-Rang (Stand 2023)
key: bk-supporter-2023
description: Das sind die Bewerbungskriterien für den Supporter-Rang auf Steamwar.
created: 2023-12-13
tags:
- team
---
Wir haben uns dazu entschlossen die Bewerbungskriterien für den Supporterrang einmal zu überarbeiten.
Wichtig zu erwähnen ist noch, dass die unten genannten Kriterien nur als Richtwerte gelten. Das heißt solltet ihr einen oder mehrere dieser Kriterien nicht erfüllen, ist euere Chance angenommen zu werden zwar geringer aber nicht unbedingt gleich null.
Das solltest du mitbringen:
- Alter von 16 Jahren oder die geistige Reife eines/r 16-jährigen
- Gute sprachliche Fähigkeiten
- Kompromissbereitschaft
- Allgemeines Wissen in allen Hauptspielmodi von SteamWar sowie Kenntnis der Regeln dieser
- Respektvolles und vorbildliches Verhalten (natürlich auf SteamWar und dem SteamWar-Discord selbst, aber auch auf anderen (Dc-)Server)
- Generelles Interesse daran, SteamWar aktiv mitzugestalten und voran zu bringen
Nachweise, die du vorlegen können solltest
- 10 freigegebene Schematics (nach Möglichkeit aus verschiedenen Modi)
- 1000 Spielstunden auf SteamWar
- Teilnahme an 30 Fights
- Teilnahme an 2 Events
- Möglichst saubere Historie
Und als wichtigster Punkt solltest du das Vertrauen der Community genießen.
Eigenschaften:
Vertrauensvolle Beziehung zur Community: Vertrauen ist die Grundlage für den Rang des Supporters, sollte gegen dich als Bewerber ein hohes Misstrauen innerhalb der Community herrschen wegen z.B. aktuellen oder vergangenen Techklauvorwürfen bist du als Supporter eher ungeeignet. Wir raten dir dementsprechend derartige Vorwürfe oder Hörensagen aus der Welt zu schaffen und/oder klar zu stellen, bevor du dich bewirbst.
Allgemeines Wissen in allen Spielmodi: Als Supporter sollte dir jeder Spielmodus bekannt sein und du solltest in der Lage sein kurz zu erklären, worum es in dem Modus geht und was ihn ausmacht.
Kenntnis der Regeln: Du solltest mindestens alle Regelwerke der Hauptspielmodi kennen und simple Fragen zu diesen beantworten können. Am wichtigsten ist uns, dass du die Regelwerke verstanden hast und später im Sinne dieser prüfen kannst.
Respektvolles und vorbildliches Verhalten: Grundlegend sollten natürlich alle Spieler respektvoll miteinander umgehen. Wir wissen selber, dass das nicht immer perfekt funktioniert, du als Supporter solltest aber bemüht sein, auch in anspruchsvollen Situationen, dich respektvoll gegenüber anderen zu verhalten. Unter Respekt verstehen wir hierbei, dass du keinen Spieler schadest und diesen nicht in seiner Würde verletzt. Wir erwarten ebenfalls, dass du in Diskussionen versuchst, die Standpunkte aller Parteien zu verstehen und nicht einfach eine Partei als „dumm“ oder ähnliches abstempelst.
Auch auf anderen Server, insbesondere Discordservern von auf SteamWar vertretenden Teams, erwarten wir ein das oben genannte Verhalten.
Geistige Reife: Du solltest dir deiner Taten und ihrer Auswirkungen sowohl auf kurzer als auch langer Sicht bewusst sein und deine Entscheidungen stehts bedacht treffen.
Gute sprachliche Fähigkeiten: Unsere Community ist äußerst diskussionsfreundlich, als Supporter musst du dich im Gefecht der Wörter behaupten können. Dementsprechend wäre es vorteilhaft, einen großen Wortschatz und gute rhetorische Fähigkeiten zu besitzen. Es ist äußerst wichtig das du die Meinungen Anderer verstehst und damit umzugehen weißt. Ebenfalls wichtig ist, dass du die Meinung des Serverteams einbringen und vertreten kannst. (Da du selbst Teil des Serverteams bist, kannst du diese natürlich mitgestalten.)
Kompromissbereitschaft: Bei Konflikten gibt es meist keine Einigung, da die meisten ihre eigene Position nicht wechseln möchten und auch nicht auf die andere Partei zugehen wollen. Du als Supporter solltest zu dem genauem Gegenteil in der Lage sein. Du solltest Kompromisse finden können, die beide Parteien, zumindest grundlegend, zufriedenstellen können, ohne dabei eine Partei dabei stark zu bevorteilen.
Wenn du dich nun dazu entschlossen hast, dich als Supporter zu bewerben, freuen wir uns deine Bewerbung auf unserem Discordserver im Forum „bewerbungen“ lesen zu können. Stelle dich übrigens darauf ein, dass du Feedback und Fragen anderer Spieler erhältst.

Datei anzeigen

@ -6,7 +6,6 @@ created: 2023-12-03
tags: tags:
- event - event
- microwargear - microwargear
image: ../../../images/bau.jpg
--- ---
Liebe Community! Liebe Community!

Datei anzeigen

@ -0,0 +1,41 @@
---
title: WargearSeason2024
key: wargearseason2024
description: Die WargearSeason2024 steht an. Wir freuen uns auf Eure Teilnahme!
created: 2023-12-17
tags:
- event
- wargear
image: ../../../images/WGS24B-1024x1024.png
---
Ahoi Community,
Unser Jährliches Großevent die WGS steht wieder an. Zum 4. Mal in Folge rufen wir die Wargear Teams dazu auf Ihre Stärke unter beweis zu stellen.
Ab dem 9.03.2024 bis zum 27.04.2024 Wird jeden Samstag Abend wieder gekämpft!
Mit dem Befehl: /team Event WarGearSeason2024 oder über unsere Discord- Funktion können sich Teams für die WGS anmelden.
Anbei gebe ich Euch jetzt bereits einige Informationen zum Ablauf mit.
- Standart Wargear Regelwerk (1.20) ohne vor verbautes TNT
- Anmeldeschluss: 17.02.2024
- Max 6 Teams für die 1. Liga zugelassen
- ggf Qualifikationskämpfe 02.03.2024
- Start erster Kampftag: 9.03.2024
- Finale: 27.04.2024
- Jeden Samstag 18 Uhr wird gekämpft
- Pro Team 2 Kämpfe je Kampftag
- Osterpause: 30.03.2024
- Tech KO durch Prozente Deaktiviert. Kampfleiter kann TechKO erklären
- Aufgelöste, Inaktive Teams können durch ein anderes Team ersetzt werden
- Teams dürfen nach einem Kampftag mit Niederlage ein neues WG einsenden
- Min ein Vertreter Pro Team muss den WGS Discord beitreten
- Eventleiter: Lixfel
Ich freue mich wieder auf eine aufregende und spannende Season und wünsche allen Teams viel Spaß und Erfolg
Mit vielen Grüßen
Steamwar Serverteam

Datei anzeigen

@ -18,17 +18,19 @@
*/ */
// @ts-ignore // @ts-ignore
import {defineCollection, reference, z} from 'astro:content'; import {defineCollection, reference, z} from "astro:content";
export const pagesSchema = z.object({
title: z.string().min(1).max(80),
description: z.string().min(1).max(120),
german: z.boolean().optional().default(false),
image: z.string().optional()
});
export const pages = defineCollection({ export const pages = defineCollection({
type: "content", type: "content",
schema: z.object({ schema: pagesSchema
title: z.string().min(1).max(80), });
description: z.string().min(1).max(120),
german: z.boolean().optional().default(false),
image: z.string().optional()
})
})
export const help = defineCollection({ export const help = defineCollection({
type: "content", type: "content",
@ -36,9 +38,9 @@ export const help = defineCollection({
title: z.string().min(1).max(80), title: z.string().min(1).max(80),
description: z.string().min(1).max(120), description: z.string().min(1).max(120),
tags: z.array(z.string()), tags: z.array(z.string()),
related: z.array(reference('help')).optional() related: z.array(reference("help")).optional()
}) })
}) });
export const modes = defineCollection({ export const modes = defineCollection({
type: "data", type: "data",
@ -47,7 +49,7 @@ export const modes = defineCollection({
main: z.boolean(), main: z.boolean(),
ranked: z.boolean().optional().default(false) ranked: z.boolean().optional().default(false)
}) })
}) });
export const downloads = defineCollection({ export const downloads = defineCollection({
type: "data", type: "data",
@ -58,14 +60,14 @@ export const downloads = defineCollection({
.or(z.record(z.string(), z.string())), .or(z.record(z.string(), z.string())),
sourceUrl: z.string().url().optional(), sourceUrl: z.string().url().optional(),
}) })
}) });
export const rules = defineCollection({ export const rules = defineCollection({
type: "content", type: "content",
schema: z.object({ schema: z.object({
translationKey: z.string(), translationKey: z.string(),
}) })
}) });
export const announcements = defineCollection({ export const announcements = defineCollection({
type: "content", type: "content",
@ -77,13 +79,13 @@ export const announcements = defineCollection({
created: z.date(), created: z.date(),
key: z.string() key: z.string()
}) })
}) });
export const collections = { export const collections = {
'pages': pages, "pages": pages,
'help': help, "help": help,
'modes': modes, "modes": modes,
'rules': rules, "rules": rules,
'downloads': downloads, "downloads": downloads,
'announcements': announcements "announcements": announcements
} };

1
src/env.d.ts vendored
Datei anzeigen

@ -24,7 +24,6 @@
/// <reference path="..\.astro-i18n\generated.d.ts" /> /// <reference path="..\.astro-i18n\generated.d.ts" />
// ###> astro-i18n/type-generation ### // ###> astro-i18n/type-generation ###
type PrimaryLocale = "en" type PrimaryLocale = "en"
type SecondaryLocale = "de" type SecondaryLocale = "de"

Datei anzeigen

@ -6,6 +6,11 @@
"buttons": { "buttons": {
"logout": "Abmelden" "logout": "Abmelden"
}, },
"stats": {
"playtime": "Spielzeit: {# playtime #}",
"fights": "Kämpfe: {# fights #}",
"checked": "Schematics Geprüft: {# checked #}"
},
"schematic": { "schematic": {
"upload": "Hochladen", "upload": "Hochladen",
"dir": "Ordner", "dir": "Ordner",
@ -15,7 +20,9 @@
"updated": "Aktualisiert", "updated": "Aktualisiert",
"replaceColor": "Farbe ersetzen", "replaceColor": "Farbe ersetzen",
"allowReplay": "Wiederholung erlauben" "allowReplay": "Wiederholung erlauben"
} },
"cancel": "Abbrechen",
"title": "Schematic hochladen"
} }
} }
} }

Datei anzeigen

@ -8,8 +8,15 @@
"logout": "Logout", "logout": "Logout",
"admin": "Admin Panel" "admin": "Admin Panel"
}, },
"stats": {
"playtime": "Playtime: {# playtime #}h",
"fights": "Fights: {# fights #}",
"checked": "Schematics Checked: {# checked #}"
},
"schematic": { "schematic": {
"upload": "Upload", "upload": "Upload",
"cancel": "Cancel",
"title": "Upload Schematic",
"home": "Schematics", "home": "Schematics",
"dir": "Directory", "dir": "Directory",
"head": { "head": {

Datei anzeigen

@ -12,6 +12,9 @@
"as": { "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." "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."
}, },
"qg": {
"description": "Nicht immer besteht die Zeit für einen langen Bau. Manchmal muss es schnell gehen. Für diese Fälle gibt es QuickGears. Ohne Qualitätsprüfung und mit nur einem Klick kannst du hier ein Gefährt erstellen. Die Qualität ist dabei nicht immer die beste, aber für einen schnellen Kampf reicht es allemal."
},
"rules": "Regelwerk »", "rules": "Regelwerk »",
"announcements": "Ankündigungen »", "announcements": "Ankündigungen »",
"ranking": "Rangliste »" "ranking": "Rangliste »"

Datei anzeigen

@ -12,6 +12,9 @@
"mwg": { "mwg": {
"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." "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."
}, },
"qg": {
"description": "Sometimes there is no time for a long construction. Sometimes it has to be quick. For these cases there are QuickGears. Without quality control and with just one click you can create a vehicle here. The quality is not always the best, but for a quick fight it is enough."
},
"rules": "Rules »", "rules": "Rules »",
"announcements": "Announcements »", "announcements": "Announcements »",
"ranking": "Ranking »", "ranking": "Ranking »",

Binäre Datei nicht angezeigt.

Nachher

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

Datei anzeigen

@ -108,7 +108,7 @@ customElements.define("nav-bar", Navbar);
<slot /> <slot />
</main> </main>
<footer class="bg-gray-900 w-full min-h-80 mt-4 pb-2 rounded-t-2xl flex flex-col dark:bg-neutral-900"> <footer class="bg-gray-900 w-full min-h-80 mt-4 pb-2 rounded-t-2xl flex flex-col dark:bg-neutral-900">
<div class="flex-1 flex justify-evenly items-center md:items-start mt-4 md:flex-row flex-col"> <div class="flex-1 flex justify-evenly items-center md:items-start mt-4 md:flex-row flex-col gap-y-4">
<div class="footer-card"> <div class="footer-card">
<h1>Serverstatus</h1> <h1>Serverstatus</h1>
<ServerStatus client:only="svelte" /> <ServerStatus client:only="svelte" />

Datei anzeigen

@ -70,7 +70,9 @@ const ogImage = await getImage({
<article> <article>
<div class={"relative w-full " + (post.data.image ? "aspect-video" : "")}> <div class={"relative w-full " + (post.data.image ? "aspect-video" : "")}>
{post.data.image && ( {post.data.image && (
<Image src={post.data.image} width="1920" height="1080" alt="" class="absolute top-0 left-0 w-full rounded-2xl linear-fade" /> <div class="absolute top-0 left-0 w-full aspect-video flex justify-center">
<Image src={post.data.image} height="1080" alt="" class="rounded-2xl linear-fade object-contain h-full" />
</div>
)} )}
<div class={post.data.image ? "absolute bottom-8 left-2" : "mb-4"}> <div class={post.data.image ? "absolute bottom-8 left-2" : "mb-4"}>
<h1 class="text-4xl mb-0">{post.data.title}</h1> <h1 class="text-4xl mb-0">{post.data.title}</h1>

Datei anzeigen

@ -5,10 +5,12 @@ import {t} from "astro-i18n";
--- ---
<PageLayout title={t("dashboard.page")}> <PageLayout title={t("dashboard.page")}>
<script is:inline> <script>
import {l} from "../util/util"; import {l} from "../util/util";
if ((localStorage.getItem("sw-session") ?? "") === "") { if (window.location.href.endsWith("/dashboard") || window.location.href.endsWith("/dashboard/")) {
window.location.href = l("/login"); if ((localStorage.getItem("sw-session") ?? "") === "") {
window.location.href = l("/login");
}
} }
</script> </script>
<Dashboard client:only="svelte" /> <Dashboard client:only="svelte" />

Datei anzeigen

@ -7,15 +7,16 @@ import localBau from "@images/2023-10-08_20.43.43.png";
--- ---
<NavbarLayout title={t("login.page")}> <NavbarLayout title={t("login.page")}>
<script is:inline> <script>
import {l} from "../util/util"; import {l} from "../util/util";
if (window.location.href.endsWith("/login") || window.location.href.endsWith("/login/")) {
if ((localStorage.getItem("sw-session") ?? "") !== "") { if ((localStorage.getItem("sw-session") ?? "") !== "") {
window.location.href = l("/dashboard"); window.location.href = l("/dashboard");
}
} }
</script> </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" /> <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 <div class="h-screen mx-auto p-8 rounded-b-md pt-40 sm:pt-28 md:pt-14 flex flex-col justify-center items-center
dark:text-white " style="width: min(100vw, 75em);"> dark:text-white " style="width: min(100vw, 75em);">
<Login client:load /> <Login client:load />
</div> </div>