Changes
Dieser Commit ist enthalten in:
Ursprung
51a605ffa5
Commit
48961abdf6
@ -28,16 +28,17 @@
|
||||
"tailwindcss": "^3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-markdown": "^6.2.2",
|
||||
"@ddietr/codemirror-themes": "^1.4.2",
|
||||
"astro": "^3.1.1",
|
||||
"color": "^4.2.3",
|
||||
"crypto-js": "^4.1.1",
|
||||
"flowbite": "^1.7.0",
|
||||
"flowbite-svelte": "^0.39.2",
|
||||
"flowbite-svelte-icons": "^0.2.5",
|
||||
"gitea-js": "^1.20.1",
|
||||
"moment": "^2.29.4",
|
||||
"sharp": "^0.32.6",
|
||||
"svelte-awesome": "^3.2.0",
|
||||
"svelte-codemirror-editor": "^1.1.0",
|
||||
"svelte-spa-router": "^3.3.0",
|
||||
"zod": "^3.21.4"
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
Vorher Breite: | Höhe: | Größe: 749 B |
@ -11,6 +11,7 @@
|
||||
'/login': wrap({asyncComponent: () => import('./pages/Login.svelte'), conditions: detail => get(tokenStore) == ""}),
|
||||
'/event/:id': wrap({asyncComponent: () => import('./pages/Event.svelte'), conditions: detail => get(tokenStore) != ""}),
|
||||
'/event/:id/generate': wrap({asyncComponent: () => import('./pages/Generate.svelte'), conditions: detail => get(tokenStore) != ""}),
|
||||
'/edit': wrap({asyncComponent: () => import('./pages/Edit.svelte'), conditions: detail => get(tokenStore) != ""}),
|
||||
'*': wrap({asyncComponent: () => import('./pages/NotFound.svelte')})
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
<script lang="ts">
|
||||
import {Button, Dropdown, DropdownItem, Search} from 'flowbite-svelte'
|
||||
|
||||
export let selected: string = null
|
||||
export let selected: string | null = null
|
||||
export let items: {name: string, value: string}[] = []
|
||||
|
||||
export let searchValue = items.find(item => item.value === selected)?.name || ''
|
||||
let open = false
|
||||
|
||||
$: filteredItems = items.filter(item => item.name.toLowerCase().includes(searchValue.toLowerCase())).filter((value, index) => index < 5)
|
||||
$: console.log(selected)
|
||||
|
||||
function selectItem(item: {name: string, value: string}) {
|
||||
selected = item.value
|
||||
|
107
src/components/admin/pages/Edit.svelte
Normale Datei
107
src/components/admin/pages/Edit.svelte
Normale Datei
@ -0,0 +1,107 @@
|
||||
<script lang="ts">
|
||||
import {ArrowLeftSolid} from "flowbite-svelte-icons";
|
||||
import {Button, Card, Input, Label, Navbar, NavBrand, NavHamburger, NavUl, Spinner} from "flowbite-svelte";
|
||||
import {pageRepo} from "../repo/repo.js";
|
||||
import {mapToMap, nameRegex} from "../util.ts";
|
||||
import Editor from "./edit/Editor.svelte";
|
||||
import TypeAheadSearch from "../components/TypeAheadSearch.svelte";
|
||||
import {branches} from "../stores/stores.ts";
|
||||
|
||||
let pagesFuture = $pageRepo.listPages();
|
||||
let selected: number | null = null;
|
||||
|
||||
let selectedBranch: string = "master";
|
||||
let searchValue: string = "";
|
||||
|
||||
$: availableBranches = $branches.map((branch) => ({
|
||||
name: branch,
|
||||
value: branch
|
||||
}))
|
||||
|
||||
$: console.log(availableBranches)
|
||||
|
||||
async function createBranch() {
|
||||
const name = prompt("Branch name:")
|
||||
if (name) {
|
||||
await $pageRepo.createBranch(name)
|
||||
let inter = setInterval(() => {
|
||||
branches.reload()
|
||||
if ($branches.includes(name)) {
|
||||
selectedBranch = name
|
||||
searchValue = ""
|
||||
clearInterval(inter)
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteBranch() {
|
||||
if (selectedBranch !== "master") {
|
||||
let conf = confirm("Are you sure you want to delete this branch?")
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<div class="flex flex-col h-screen overflow-scroll">
|
||||
<Navbar let:hidden let:toggle>
|
||||
<NavBrand href="#">
|
||||
<ArrowLeftSolid></ArrowLeftSolid>
|
||||
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
|
||||
Edit Pages
|
||||
</span>
|
||||
</NavBrand>
|
||||
</Navbar>
|
||||
<div class="p-4 flex-1">
|
||||
<div class="grid md:grid-cols-3 grid-cols-1 h-full gap-8">
|
||||
<Card class="h-full flex flex-col !max-w-full">
|
||||
{#await pagesFuture}
|
||||
<Spinner />
|
||||
{:then pages}
|
||||
<div class="border-b border-b-gray-600 pb-2 flex justify-between">
|
||||
<TypeAheadSearch items={availableBranches} bind:selected={selectedBranch} bind:searchValue />
|
||||
<div>
|
||||
<Button on:click={deleteBranch} color="ghost">Delete Branch</Button>
|
||||
<Button on:click={createBranch}>Create Branch</Button>
|
||||
</div>
|
||||
</div>
|
||||
{@const pagesMap = mapToMap(pages)}
|
||||
{#each pagesMap as [key, value]}
|
||||
<details>
|
||||
<summary class="p-4 transition-colors hover:bg-gray-700 cursor-pointer">{key}</summary>
|
||||
<ul>
|
||||
{#each value as page}
|
||||
{@const match = nameRegex.exec(page.path) ? nameRegex.exec(page.path)[0] : ""}
|
||||
{@const startIndex = page.path.indexOf(match)}
|
||||
{@const endIndex = startIndex + match.length}
|
||||
<li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer" on:click|preventDefault={() => selected = page.id}>
|
||||
<span class:text-orange-600={selected === page.id}>{page.path.substring(0, startIndex)}</span><span class="text-white" class:!text-orange-500={selected === page.id}>{match}</span><span class:text-orange-600={selected === page.id}>{page.path.substring(endIndex, page.path.length)}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</details>
|
||||
{/each}
|
||||
{:catch error}
|
||||
<p>{error.message}</p>
|
||||
{/await}
|
||||
</Card>
|
||||
<Card class="!max-w-full" style="grid-column: 2/4">
|
||||
{#if selected}
|
||||
<Editor pageId={selected} branch={selectedBranch} />
|
||||
{/if}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -21,11 +21,12 @@
|
||||
<Navbar let:hidden let:toggle class="shadow-lg border-b">
|
||||
<NavBrand href="#">
|
||||
<span class="self-center whitespace-nowrap text-xl font-semibold dark:text-white">
|
||||
Eventplanner
|
||||
Admin-Tool
|
||||
</span>
|
||||
</NavBrand>
|
||||
<NavHamburger on:click={toggle} />
|
||||
<NavUl {hidden}>
|
||||
<NavLi href="#/edit">Edit Pages</NavLi>
|
||||
<NavLi href="#/perms">Permissions</NavLi>
|
||||
<NavLi on:click={() => showLogoutModal = true} class="cursor-pointer select-none">Logout</NavLi>
|
||||
</NavUl>
|
||||
|
@ -3,7 +3,7 @@
|
||||
import {fly} from "svelte/transition";
|
||||
import {replace} from "svelte-spa-router";
|
||||
import {EyeOutline, EyeSlashOutline, EyeSolid} from "flowbite-svelte-icons";
|
||||
import {tokenStore} from "../repo/repo.js";
|
||||
import {fetchWithToken, tokenStore} from "../repo/repo.js";
|
||||
|
||||
let show = false;
|
||||
let loading = false;
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
let res = await fetch("https://steamwar.de/eventplanner-api/data", {headers: {"X-SW-Auth": value}})
|
||||
let res = await fetchWithToken(value, "/data")
|
||||
loading = false;
|
||||
if(res.ok) {
|
||||
$tokenStore = value;
|
||||
|
50
src/components/admin/pages/edit/Editor.svelte
Normale Datei
50
src/components/admin/pages/edit/Editor.svelte
Normale Datei
@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import {Spinner, Toolbar, ToolbarButton, ToolbarGroup, Tooltip} from "flowbite-svelte";
|
||||
import {markdown} from "@codemirror/lang-markdown";
|
||||
import CodeMirror from "svelte-codemirror-editor";
|
||||
import {pageRepo} from "../../repo/repo.ts";
|
||||
import {base64ToBytes} from "../../util.ts";
|
||||
import type {Page} from "../../types/page.ts";
|
||||
import {materialDark} from '@ddietr/codemirror-themes/material-dark.js'
|
||||
import {EditOutline} from "flowbite-svelte-icons";
|
||||
|
||||
export let pageId: number;
|
||||
export let branch: string;
|
||||
$: pageFuture = $pageRepo.getPage(pageId).then(getPage);
|
||||
let pageContent = "";
|
||||
let page: Page | null = null;
|
||||
|
||||
function getPage(value: Page): Page {
|
||||
page = value;
|
||||
pageContent = new TextDecoder().decode(base64ToBytes(value.content));
|
||||
return value;
|
||||
}
|
||||
|
||||
function savePage() {
|
||||
let message = window.prompt("Commit message:")
|
||||
if (message) {
|
||||
$pageRepo.updatePage(pageId, pageContent, page!.sha, message)
|
||||
} else {
|
||||
alert("Commit message is required")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await pageFuture}
|
||||
<Spinner />
|
||||
{:then p}
|
||||
<div>
|
||||
<div>
|
||||
<Toolbar class="!bg-gray-900">
|
||||
<ToolbarGroup slot="end">
|
||||
<ToolbarButton color="primary" on:click={savePage}>
|
||||
Save
|
||||
</ToolbarButton>
|
||||
</ToolbarGroup>
|
||||
</Toolbar>
|
||||
</div>
|
||||
<CodeMirror bind:value={pageContent} lang={markdown()} theme={materialDark} />
|
||||
</div>
|
||||
{:catch error}
|
||||
<p>{error.message}</p>
|
||||
{/await}
|
44
src/components/admin/repo/page.ts
Normale Datei
44
src/components/admin/repo/page.ts
Normale Datei
@ -0,0 +1,44 @@
|
||||
import type {Page, PageList} from "../types/page.ts";
|
||||
import {fetchWithToken} from "./repo.ts";
|
||||
import {PageListSchema, PageSchema} from "../types/page.ts";
|
||||
import {bytesToBase64} from "../util.ts";
|
||||
import {branches} from "../stores/stores.ts";
|
||||
|
||||
export class PageRepo {
|
||||
constructor(private token: string) {}
|
||||
|
||||
public async listPages(branch: string = "master"): Promise<PageList> {
|
||||
return await fetchWithToken(this.token, `/page?branch=${branch}`)
|
||||
.then(value => value.json())
|
||||
.then(value => PageListSchema.parse(value))
|
||||
}
|
||||
|
||||
public async getPage(id: number, branch: string = "master"): Promise<Page> {
|
||||
return await fetchWithToken(this.token, `/page/${id}?branch=${branch}`)
|
||||
.then(value => value.json())
|
||||
.then(value => PageSchema.parse(value))
|
||||
}
|
||||
|
||||
public async updatePage(id: number, content: string, sha: string, message: string, branch: string = "master"): Promise<void> {
|
||||
await fetchWithToken(this.token, `/page/${id}?branch=${branch}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
content: bytesToBase64(new TextEncoder().encode(content)),
|
||||
sha, message
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async getBranches(): Promise<string[]> {
|
||||
return await fetchWithToken(this.token, "/page/branch")
|
||||
.then(value => value.json())
|
||||
}
|
||||
|
||||
public async createBranch(branch: string): Promise<void> {
|
||||
await fetchWithToken(this.token, `/page/branch`, {method: "POST", body: JSON.stringify({branch})})
|
||||
}
|
||||
|
||||
public async deleteBranch(branch: string): Promise<void> {
|
||||
await fetchWithToken(this.token, `/page/branch`, {method: "DELETE", body: JSON.stringify({branch})})
|
||||
}
|
||||
}
|
@ -2,14 +2,17 @@ import {derived, writable} from "svelte/store";
|
||||
import {EventRepo} from "./event.js";
|
||||
import {FightRepo} from "./fight.js";
|
||||
import {PermsRepo} from "./perms.js";
|
||||
import {PageRepo} from "./page.ts";
|
||||
|
||||
export { EventRepo } from "./event.js"
|
||||
|
||||
export const fetchWithToken = (token: string, url: string, params: RequestInit = {}) => fetch(`https://steamwar.de/eventplanner-api${url}`, {...params, headers: {"X-SW-Auth": token, "Content-Type": "application/json", ...params.headers}});
|
||||
export const apiUrl = import.meta.env.PUBLIC_API_SERVER;
|
||||
|
||||
export const fetchWithToken = (token: string, url: string, params: RequestInit = {}) => fetch(`${apiUrl}${url}`, {...params, headers: {"Authorization": "Bearer " + (token), "Content-Type": "application/json", ...params.headers}});
|
||||
export const tokenStore = writable(localStorage.getItem("sw-api-token") ?? "")
|
||||
tokenStore.subscribe((value) => localStorage.setItem("sw-api-token", value))
|
||||
|
||||
export const eventRepo = derived(tokenStore, ($token) => new EventRepo($token))
|
||||
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))
|
||||
|
@ -29,7 +29,7 @@ export function cached<T>(normal: T, init: () => Promise<T>): Cached<T> {
|
||||
};
|
||||
}
|
||||
|
||||
export function cachedFamily<T, K>(normal: K, init: (T) => Promise<K>): (T) => Cached<K> {
|
||||
export function cachedFamily<T, K>(normal: K, init: (arg0: T) => Promise<K>): (arg: T) => Cached<K> {
|
||||
const stores: Map<T, Cached<K>> = new Map();
|
||||
return (arg: T) => {
|
||||
if(stores.has(arg)) {
|
||||
|
@ -4,28 +4,27 @@ import {cached, cachedFamily} from "./cached.js";
|
||||
import type {Team} from "../types/team.js";
|
||||
import {TeamSchema} from "../types/team.js";
|
||||
import {get, writable} from "svelte/store";
|
||||
import {permsRepo, tokenStore} from "../repo/repo.js";
|
||||
import {apiUrl, fetchWithToken, pageRepo, permsRepo, tokenStore} from "../repo/repo.js";
|
||||
import {z} from "zod";
|
||||
|
||||
export const schemTypes = cached<SchematicType[]>([], () => {
|
||||
return fetch("https://steamwar.de/eventplanner-api/data/schematicTypes", {headers: {"X-SW-Auth": get(tokenStore)}})
|
||||
.then(res => res.json())
|
||||
})
|
||||
export const schemTypes = cached<SchematicType[]>([], () =>
|
||||
fetchWithToken(get(tokenStore), `/data/schematicTypes`)
|
||||
.then(res => res.json()))
|
||||
|
||||
export const players = cached<Player[]>([], async () => {
|
||||
const res = await fetch("https://steamwar.de/eventplanner-api/data/users", {headers: {"X-SW-Auth": get(tokenStore)}});
|
||||
const res = await fetchWithToken(get(tokenStore), `/data/users`);
|
||||
return z.array(PlayerSchema).parse(await res.json());
|
||||
})
|
||||
|
||||
export const gamemodes = cached<string[]>([], async () => {
|
||||
const res = await fetch("https://steamwar.de/eventplanner-api/data/gamemodes", {headers: {"X-SW-Auth": get(tokenStore)}});
|
||||
const res = await fetchWithToken(get(tokenStore), `/data/gamemodes`);
|
||||
return z.array(z.string()).parse(await res.json());
|
||||
})
|
||||
|
||||
export const maps = cachedFamily<string, string[]>([], async (gamemode) => {
|
||||
if (get(gamemodes).every(value => value !== gamemode)) return [];
|
||||
|
||||
const res = await fetch(`https://steamwar.de/eventplanner-api/data/gamemodes/${gamemode}/maps`, {headers: {"X-SW-Auth": get(tokenStore)}});
|
||||
const res = await fetchWithToken(get(tokenStore), `/data/gamemodes/${gamemode}/maps`);
|
||||
if (!res.ok) {
|
||||
return [];
|
||||
} else {
|
||||
@ -34,14 +33,19 @@ export const maps = cachedFamily<string, string[]>([], async (gamemode) => {
|
||||
})
|
||||
|
||||
export const groups = cached<string[]>([], async () => {
|
||||
const res = await fetch("https://steamwar.de/eventplanner-api/data/groups", {headers: {"X-SW-Auth": get(tokenStore)}});
|
||||
const res = await fetchWithToken(get(tokenStore), `/data/groups`);
|
||||
return z.array(z.string()).parse(await res.json());
|
||||
})
|
||||
|
||||
export const teams = cached<Team[]>([], async () => {
|
||||
const res = await fetch("https://steamwar.de/eventplanner-api/team", {headers: {"X-SW-Auth": get(tokenStore)}});
|
||||
const res = await fetchWithToken(get(tokenStore), `/team`);
|
||||
return z.array(TeamSchema).parse(await res.json());
|
||||
})
|
||||
|
||||
export const branches = cached<string[]>([], async () => {
|
||||
const res = await get(pageRepo).getBranches();
|
||||
return z.array(z.string()).parse(res);
|
||||
})
|
||||
|
||||
export const isWide = writable(window.innerWidth >= 640);
|
||||
window.addEventListener("resize", () => isWide.set(window.innerWidth >= 640));
|
||||
|
27
src/components/admin/types/page.ts
Normale Datei
27
src/components/admin/types/page.ts
Normale Datei
@ -0,0 +1,27 @@
|
||||
import {z} from "zod";
|
||||
|
||||
export const ListPageSchema = z.object({
|
||||
path: z.string(),
|
||||
name: z.string(),
|
||||
sha: z.string(),
|
||||
downloadUrl: z.string().url(),
|
||||
id: z.number().positive()
|
||||
});
|
||||
|
||||
export type ListPage = z.infer<typeof ListPageSchema>;
|
||||
|
||||
export const PageListSchema = z.array(ListPageSchema)
|
||||
|
||||
export type PageList = z.infer<typeof PageListSchema>;
|
||||
|
||||
export const PageSchema = z.object({
|
||||
path: z.string(),
|
||||
name: z.string(),
|
||||
sha: z.string(),
|
||||
downloadUrl: z.string().url(),
|
||||
content: z.string(),
|
||||
size: z.number().gte(0),
|
||||
id: z.number().positive()
|
||||
})
|
||||
|
||||
export type Page = z.infer<typeof PageSchema>;
|
@ -1,8 +1,23 @@
|
||||
import Color from "color";
|
||||
import type {Team} from "./types/team.js";
|
||||
import type {ListPage, PageList} from "./types/page.ts";
|
||||
|
||||
export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
|
||||
|
||||
export const nameRegex = new RegExp("(?!.*\/).+(?=\\.md)");
|
||||
|
||||
export function mapToMap(pages: PageList): Map<string, ListPage[]> {
|
||||
const map = new Map();
|
||||
for (const page of pages) {
|
||||
let folder = page.path.substring(0, page.path.indexOf(nameRegex.exec(page.path)[0]));
|
||||
if (!map.has(folder)) {
|
||||
map.set(folder, []);
|
||||
}
|
||||
map.get(folder).push(page);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export function colorFromTeam(team: Team): string {
|
||||
switch (team.color) {
|
||||
case "1":
|
||||
@ -47,3 +62,14 @@ export function lighten(color: string) {
|
||||
export function brightness(color: string) {
|
||||
return Color(color).isLight()
|
||||
}
|
||||
|
||||
export function base64ToBytes(base64: string) {
|
||||
const binString = atob(base64);
|
||||
// @ts-ignore
|
||||
return Uint8Array.from(binString, (m) => m.codePointAt(0));
|
||||
}
|
||||
|
||||
export function bytesToBase64(bytes: Uint8Array) {
|
||||
const binString = String.fromCodePoint(...bytes);
|
||||
return btoa(binString);
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
---
|
||||
|
||||
import {astroI18n} from "astro-i18n";
|
||||
import icon from '../images/logo.png';
|
||||
import {getImage} from "astro:assets";
|
||||
const { title, description } = Astro.props.frontmatter || Astro.props;
|
||||
|
||||
const iconImage = await getImage({src: icon, height: 32, width: 32, format: 'png', quality: 100});
|
||||
---
|
||||
|
||||
<html lang={astroI18n.langCode} class="dark">
|
||||
@ -11,6 +15,7 @@ const { title, description } = Astro.props.frontmatter || Astro.props;
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<meta name="description" content={description}/>
|
||||
<link rel="icon" type="imgage/png" href={iconImage.src} />
|
||||
<link rel="stylesheet" href="/fonts/barlow-condensed/barlow-condensed.css" />
|
||||
<title>{title}</title>
|
||||
<slot name="head" />
|
||||
|
@ -4,6 +4,7 @@ import { Image } from 'astro:assets';
|
||||
import '../styles/button.css';
|
||||
import localLogo from "../images/logo.png"
|
||||
import {l, t} from "astro-i18n";
|
||||
import {YoutubeSolid} from "flowbite-svelte-icons"
|
||||
|
||||
const { title } = Astro.props;
|
||||
---
|
||||
@ -13,7 +14,7 @@ const { title } = Astro.props;
|
||||
<Fragment>
|
||||
<div class="min-h-screen flex flex-col">
|
||||
<nav-bar class="fixed top-0 left-0 right-0 px-4 transition-colors z-10 flex justify-center \
|
||||
before:bg-transparent before:absolute before:top-0 before:left-0 before:bottom-0 before:right-0 before:bg-black before:-z-10 before:scale-y-0 before:transition-transform before:origin-top">
|
||||
before:bg-black before:absolute before:top-0 before:left-0 before:bottom-0 before:right-0 before:-z-10 before:scale-y-0 before:transition-transform before:origin-top">
|
||||
<div class="flex flex-col md:flex-row items-center justify-evenly md:justify-between match">
|
||||
<a class="flex items-center" href={l("/")}>
|
||||
<Image src={localLogo} alt={t("navbar.logo.alt")} width="44" height="44" quality="max" class="mr-2 p-1 bg-black rounded-full" />
|
||||
@ -100,7 +101,7 @@ const { title } = Astro.props;
|
||||
</div>
|
||||
<div class="footer-card">
|
||||
<h1>Links</h1>
|
||||
<a href={l("/")}>Startseite</a>
|
||||
<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>
|
||||
@ -111,10 +112,10 @@ const { title } = Astro.props;
|
||||
</div>
|
||||
<div class="footer-card">
|
||||
<h1>Social Media</h1>
|
||||
<a>YouTube</a>
|
||||
<a class="flex" href="/"><YoutubeSolid class="mr-2" /> YouTube</a>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-sm text-white text-center">© SteamWar.de</span>
|
||||
<span class="text-sm text-white text-center">© SteamWar.de - {new Date().getFullYear()}</span>
|
||||
</footer>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
@ -2,7 +2,7 @@
|
||||
import NavbarLayout from "../layouts/NavbarLayout.astro";
|
||||
|
||||
import { Image } from "astro:assets";
|
||||
import localBau from "../images/2022-03-28_13.18.25.png";
|
||||
import localBau from "../images/bau.jpg";
|
||||
import {l, t} from "astro-i18n";
|
||||
import {CaretRight, Archive, Rocket, Bell} from "@astropub/icons"
|
||||
---
|
||||
@ -121,8 +121,9 @@ import {CaretRight, Archive, Rocket, Bell} from "@astropub/icons"
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply w-72 border-2 bg-zinc-50 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg
|
||||
dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100;
|
||||
@apply w-72 border-2 bg-zinc-50 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg transition-transform duration-300 ease-in-out
|
||||
dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100
|
||||
hover:scale-105;
|
||||
>h1 {
|
||||
@apply text-xl font-bold underline mt-4;
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren