Initial commit of MVP frontend

This commit is contained in:
Gabriel Simmer 2022-07-05 11:12:27 +01:00
commit eaa104c180
22 changed files with 5810 additions and 0 deletions

13
.eslintignore Normal file
View file

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

20
.eslintrc.cjs Normal file
View file

@ -0,0 +1,20 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020
},
env: {
browser: true,
es2017: true,
node: true
}
};

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example

1
.npmrc Normal file
View file

@ -0,0 +1 @@
engine-strict=true

13
.prettierignore Normal file
View file

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

6
.prettierrc Normal file
View file

@ -0,0 +1,6 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
}

38
README.md Normal file
View file

@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm init svelte
# create a new project in my-app
npm init svelte my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

5301
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

34
package.json Normal file
View file

@ -0,0 +1,34 @@
{
"name": "wlm-frontend",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"package": "svelte-kit package",
"preview": "svelte-kit preview",
"prepare": "svelte-kit sync",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check --plugin-search-dir=. . && eslint .",
"format": "prettier --write --plugin-search-dir=. ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^1.0.0-next.53",
"@sveltejs/adapter-static": "^1.0.0-next.34",
"@sveltejs/kit": "next",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^4.0.0",
"prettier": "^2.6.2",
"prettier-plugin-svelte": "^2.7.0",
"svelte": "^3.44.0",
"svelte-check": "^2.7.1",
"svelte-preprocess": "^4.10.6",
"tslib": "^2.3.1",
"typescript": "^4.7.2",
"@sveltejs/adapter-node": "^1.0.0-next.78"
},
"type": "module"
}

11
src/app.d.ts vendored Normal file
View file

@ -0,0 +1,11 @@
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Locals {}
// interface Platform {}
// interface Session {}
// interface Stuff {}
}

12
src/app.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body>
<div>%sveltekit.body%</div>
</body>
</html>

View file

@ -0,0 +1,8 @@
<slot></slot>
<style>
:global(body) {
font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif
}
</style>

31
src/routes/index.svelte Normal file
View file

@ -0,0 +1,31 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { onMount } from "svelte";
let loggedIn = false
onMount(async () => {
const request = await fetch("/api/v1/me")
if (request.ok) {
const currentUser = await request.text()
if (currentUser != "") {
loggedIn = true
}
}
})
</script>
{#if !loggedIn}
<a href="/api/v1/auth/redirect">Login</a>
{:else}
<a href="/servers">Manage Servers</a>
{/if}
<slot></slot>
<style>
:global(body) {
font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif
}
</style>

View file

@ -0,0 +1,110 @@
<script lang="ts">
import { server } from "$app/env";
import { goto } from "$app/navigation";
import { page } from '$app/stores';
import { onMount } from "svelte";
const { code } = $page.params;
let invite;
let success = "";
onMount(async () => {
const request = await fetch(`/api/v1/invite/${code}`)
if (request.ok) {
invite = await request.json()
}
})
const acceptInvite = async () => {
const response = await fetch(`/api/v1/invite/${code}/accept`, {
method: "POST",
});
if (response.ok) {
success = await response.text
} else {
console.log("not ok")
}
}
</script>
<svelte:head>
{#if invite}
<title>Accept Invite to {invite.server.name}</title>
{:else}
<title>Accept Invite</title>
{/if}
</svelte:head>
<div>
{#if invite}
{#if success == ""}
<p>Invite {code} from {invite.creator.display_name}</p>
<p>Join {invite.server.name}?</p>
<button on:click="{acceptInvite}">Yes!</button>
{:else}
<p>You have been whitelisted! Connect at: <code>{invite.server.address}</code></p>
{/if}
{:else}
<p>Loading...</p>
{/if}
</div>
<style>
@import "https://fontlibrary.org/face/minecraftia";
div {
background-color: #444a52f6;
color: white;
margin: 0 auto;
width: 25vw;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 10px;
border-radius: 5px;
border: 1px solid white;
position: absolute;
top: 30%;
left: 50%;
transform: translate(-50%, -50%);
}
@media (max-width: 700px) {
div {
width: 90vw;
}
}
button {
color:white;
width: 50%;
margin: 0 auto;
height: 6vh;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMQAAAAPCAAAAACe4j/AAAAEuElEQVRIx1WWS5Ibuw5EtddrdRRJJE4iSKrb4c2/AUuy30iK+jEBHCTwAICMCgAKiGhKVQUwKzJHQo4mQGBy7AIvk1E2AQKhFt6bUGD1gILy0DkD4dmzACkGyzCScgFgQmLZr4LpQpC56QB2wgSYE8i1fd6xd1yPHIAKwAs14Undx2LIHK0X6vdFbzTa6CNBLQE8Y5rapn7oAl3KSDKC6cLfL6sLnIVG7ffnyUgvQ6rwLjUZdIVqWz0iEXXCZ+TJsV8feUdoPHDk+a/IeAbq0ldLT59jtHweHwmKS6gFSUYCqKcVsPami1pEnqolkCMBiAS87FXTICp03geYUr8TV8un3KuyC0BfTUzihOCdwjUya1bkT9VWe+AaFOoJzHdtERXRe/eulyEDhC6pjYwr+JQuIDU9/0wQBnJEAkd+9i6IRTJrV7YWnGfwLA6ESphkBoCu88M0YH1CTTOiRUpDdwpGkkP5yL9phhwn+QVxDarAXq513y9bLKgxBGR/CjDyMl4ECd6lO82CaMLbEHGHp1R8ePDERj392ssaCOigNjJ+dXXl6OLUFfVx6dA1BFJLlHpkUHPam095C3QJLIjn6OLIOB3xe0JS85Zd+govg1/z/UhtG3RdVW98yxNp5I0Vtf/U+6y/dJ/yHtSK4q7G9ARscC3/Df/kXTwiT3d6ovjEISLJIVEvBwWeCXlALZTbpy85xqWuf3sNiBBUmYjI+BqN6aNQqDWpipSYf3W1nOV/WALoykjIiCPreSn97UigsjZdPA5s3AmCDIxaVO0Fa1fV/qkaiijPPz/bEeRyLSBDI8K8j37TO8aH7vmOqUUQ1DboYhvqd+FlFGEo+2T75MbLGnjvPeu7Pl521Ap14dcR3h7p75qv4xieqnNDoyCPLt/yKkf5gOUXl+6S3d9Syq/982fz+fhpsm28C5EhJcxsWWQke6KoPMA7tqm7kKcrn6FLhWcoqCNCxDPyQ8wEKR73nIlEX8GpARSJX7/rzeNtWPD/Dl2IwtOYMwV7osGsHYJanzeU7KqIZVCTcrT0IEbyD4d1e1dkBtW2qLhLXK7a1ogDqsjI1DR4PY7nHEqfF9tn8HUJL+eQ/lGOXdwI1++57CqgmDaReM9CZYLAk4D4ymqti4KZI9Htz9OnrcuOhCQEqJfXaxHPAFG/l/ee1GFJPXmPryYUiUGPutnRGXkagmnjHk5RNpm7xxiclm6CjJEQ4+MD4xPmuT9Ef4b3YT3B51a7d4Np76PF0cavuC+SoaH6PlilipEakoJBJETWT3k7Hf+1REPj2fIx95t5ct6u9S5v3KM2fv2Ve29ax9yC98zT2IZot61MHyYtdPF92nguZ29xfcVxSu9Nj5Gl8G3wcVoW8n1Q/f6BHFEAyy5R2aRIdYH8XfV4453vHKcnqarXPD6mdrD0rlVV5xBPF2xuJM5Oo0SdaaqGMjJva7ii6bYv74IzPMo5pGebpu72ghypwTR+WWDuhQOkFijGyAzF3GXh11lEH0hkPINZivpTqLEtVm2j1hVcn/S0//q0es5dbyxOErxKivQ+bHotvzcT3/XztPf20XqG6fJZMePT2X6ts+nlm9LoSYHMiHuJjGXniB7v2fo/5qT4nuxYg6AAAAAASUVORK5CYII=);
font-family: 'MinecraftiaRegular';
border-color: #AAA #565656 #565656 #AAA;
text-shadow: 3px 3px #4C4C4C;
outline: 2px solid #000;
}
button:hover {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMQAAAAPCAMAAACMV5AuAAAAVFBMVEVtd7ZteLZverdwerdwe7hxfLhyfbl0frl1f7p2gLt4grx6hLx7hb18hr5+iL+AisCBi8GCjMGEjsKGkMOHkcSIksSKk8WLlMWLlcaMlcaNlsePmMdwBHmVAAAEjElEQVRIx1WW27YbKw5F3Ul2GYTWFAJX+pyd///PfgDsTg0/mSqQ1k08AEAeDkAA7kXyDAeyu2QCWREgCGSvAAbIE/D9U2m8Ji4Drw0CEhP7EMiqBFxmDMBEkHvd5AziDsjoCKQXddUl1nuZgMZrf0PMdj1kgNY+Ay+CJPg8kj1rR3X/GS9kxaoJVFaB6QkxIX5TBX65mpAZGQH3HaoCFMj6/GxuYgDyTsyup1hfxwxVN+F08ANGANyf8jqAPaBpranJvhxV+VV08JGPVScmcL8W4EIuAFWFHMZrUkUfNC3WtIsEaFogMWJRl839MES61w1cjMUsI1fb7GKaoENMOdFNykzXPz2mlwekkXjV4ioO9b15rZWZd4AaCL9cxdSutl8buIOUkd8JTgAy01IKoFoFPhAZr65SbDHgxFIGKi5IpLaKdlafAHxaVWDt2SSZ24LQhEx6aLGyj7RtELDLiA7EiD72ehBiQJo5oPq1nOQMYGAIYoY2AwIrImaA+25P8vZRa0KgqrjnQIaACiom/1G9Sla3GUHVLl/mMl/mxeUPGT0T5seAeeAQtF9WV2F7cX4niJ4DYmBd12oh7jymyRkBuq6M81mQuExHVv313beo9VH3ojfM2zZ8AkTG8X7EeDs6OZw+mrY7E5l/gqQJmZx+YySQAi1/dqQZy0as4FL1d7nrcfd1kjWTX1ZIYESTUCnuPZE7+anrqcxlz3diUX2HowHCf15S3AvxrnhRxeMwtQACGYGKZ7wGjNl7vH73MHdP8s/vSXM0FiRqMnPowfbjRsfe6n7nZ/FGIyfgF6+A/t1XTrcWB3Q42AxkMOfMfuJotRPgqGqFMfB8KO6eN+BEpO8F2eLZ18YO0GUdmEDcXMcNey+XuOfvP5P35guSVxAzWRYUpIoSuZiJrJ/p4zPouw0pwX+6XwpIcyNWEcJ/uf5Svuyx+ncTfhmotEhIBPe/8f961F/jI5aQfSs3WFOwChnZZxPk+HQjZu/WBuDFVwobZnoLISBOdkmNLFPE9iadiImsgTcdTDKA8dj1qBT518Xcg6+6L0Yl/nqCLeH+b46InkCSETTBzEQBRoPEwS9lKVUkpEx426M3oIMHYQLhK/B6jHvgXw6if4+YM4mlJdWDpLw4MhHgjwBZgIt+ZJdAVENOEkiv6mY4yy5nFPjnQmGfGVy0IqP+cmbsWF7Se/rT+5ZBzHPdKfajHW2ouWldPJaoTDJ3GUYTNMV/IyaiPYqQuX0VPXK+ra3Mc8c5Wlkp4D/e5S5htjXU3HnPPJsBXraRM/rSpOMX99zMoVradRl9AMxJdVPIYgf8Rhu0DvLM739A5rmjJ51QcW9SFXjcvR85oYOhSKTsd65pqdKWIV59ZO/7kKTDJLbvYw9XVRIyTXKJdfm4rOjE18yT8IHM/euZa0Z0fLvBSIh7h925QLoXR80WNfnqOHHP7Qk5aj+dTLX4E3hhIkafoGd15/IDT/lPTVSVr233dy6OkJtiLm0yBu+byeE2Yc59pVrDdKyPjxgD4h6r6vcFwpYNHMxVBaE2ApmfyTTjf5jQnWRymG7iAAAAAElFTkSuQmCC);
border-color: #BDC6FF #59639A #59639A #BDC6FF;
}
code {
font-family: 'MinecraftiaRegular';
color: white;
padding: 20px 5px;
margin: 20px 0;
display: block;
text-align: center;
font-size: larger;
background-color: black;
border: 3px inset grey;
}
:global(body) {
font-family: 'MinecraftiaRegular';
background-color: #202225;
background-image: url(../dirt.jpg);
}
</style>

View file

@ -0,0 +1,58 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { page } from '$app/stores';
const { id } = $page.params;
let inviteCode = "";
const createInvite = async (e: Event) => {
const form: HTMLFormElement = e.currentTarget;
const url = form.action;
let formFields = new FormData(form);
let formData = Object.fromEntries(formFields.entries());
const payload = {
"server": id,
"uses": Number(formData.uses),
"unlimited": formData.unlimited == "on"
};
if (payload.uses === 0) { payload.uses = 5 }
console.log(payload)
const pl = JSON.stringify(payload);
const response = await fetch("/api/v1/invites", {
method: "POST",
body: pl
});
if (response.ok) {
inviteCode = await response.text()
} else {
console.log("not ok")
}
}
</script>
{#if inviteCode }
<code>{$page.url.hostname}/invite/{inviteCode}</code>
{:else}
<h2>Create Invite</h2>
<form on:submit|preventDefault="{createInvite}" action="/api/v1/invites" method="POST">
<label>Number of uses
<input type="number" name="uses" placeholder="5"></label>
<label>Unlimited?
<input type="checkbox" name="unlimited"></label>
<input type="submit" value="Save">
</form>
{/if}
<style>
label {
margin: 10px 0;
}
input {
border: 1px solid black;
padding: 5px;
border-radius: 5px;
font-size: large;
}
</style>

View file

@ -0,0 +1,22 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { onMount } from "svelte";
let loggedIn = false
onMount(async () => {
const request = await fetch("/api/v1/me")
if (request.ok) {
loggedIn = true
} else {
goto("/")
}
})
</script>
{#if !loggedIn}
<a href="/api/v1/auth/redirect">Login</a>
{/if}
<slot></slot>

View file

@ -0,0 +1,39 @@
<script lang="ts">
import { server } from "$app/env";
import { onMount } from "svelte";
let servers = []
let loaded = false
onMount(async () => {
const request = await fetch("/api/v1/servers")
if (request.ok) {
loaded = true
servers = await request.json()
} else {
console.log("not ok")
}
})
</script>
{#if loaded}
<h2>Your servers <small><a href="/servers/new">new</a></small></h2>
{#if servers.length > 0}
{#each servers as server}
<div>
<h3>{server.name}</h3>
<p>Server Address: {server.address}</p>
<p>Server RCON Address: {server.rcon.address}</p>
<a href="/servers/{server.id}">new invite</a> <button>delete</button>
</div>
{/each}
{/if}
{:else}
<p>Loading</p>
{/if}
<style>
div {
margin: 0 auto;
}
</style>

View file

@ -0,0 +1,57 @@
<script lang="ts">
import { goto } from "$app/navigation";
const createServer = async (e: Event) => {
const form: HTMLFormElement = e.currentTarget;
const url = form.action;
let formFields = new FormData(form);
let formData = Object.fromEntries(formFields.entries());
const payload = {
"address": formData.address,
"name": formData.name,
"rcon": {
"address": formData.rcon_address,
"password": formData.rcon_password
}
};
const pl = JSON.stringify(payload);
const response = await fetch("/api/v1/servers", {
method: "POST",
body: pl
});
if (response.ok) {
goto("/servers")
} else {
console.log("not ok")
}
}
</script>
<h2>Add Server</h2>
<form on:submit|preventDefault="{createServer}" action="/api/v1/servers" method="POST">
<label>Server Name
<input type="text" name="name" placeholder="A Minecraft Server"></label>
<label>Server Address
<input type="text" name="address" placeholder="127.0.0.1"></label>
<label>RCON Address
<input type="text" name="rcon_address" placeholder="127.0.0.1:25575"></label>
<label>RCON Password
<input type="password" name="rcon_password" placeholder="rcon password"></label>
<input type="submit" value="Save">
</form>
<style>
label {
display: block;
margin: 10px 0;
}
input {
border: 1px solid black;
padding: 5px;
border-radius: 5px;
font-size: large;
}
</style>

BIN
static/dirt.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

15
svelte.config.js Normal file
View file

@ -0,0 +1,15 @@
import adapter from '@sveltejs/adapter-node';
import preprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: preprocess(),
kit: {
adapter: adapter()
}
};
export default config;

13
tsconfig.json Normal file
View file

@ -0,0 +1,13 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
}