Compare commits

...

10 commits
0.1.1 ... main

30 changed files with 1102 additions and 917 deletions

2
.gitignore vendored
View file

@ -10,3 +10,5 @@ node_modules
.output .output
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
/.vscode

View file

@ -1,39 +1,48 @@
{ {
"name": "chellaris-signup-site", "name": "",
"version": "0.0.1", "version": "0.0.1",
"scripts": { "private": true,
"dev": "vite dev", "scripts": {
"build": "vite build", "dev": "vite dev",
"preview": "vite preview", "build": "vite build",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "preview": "vite preview",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"lint": "prettier --plugin-search-dir . --check . && eslint .", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --plugin-search-dir . --write ." "lint": "prettier --plugin-search-dir . --check . && eslint .",
}, "format": "prettier --plugin-search-dir . --write ."
"devDependencies": { },
"@fontsource/fira-mono": "^4.5.10", "devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.20.4",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",
"svelte": "^4.0.5",
"svelte-check": "^3.4.3",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.4.2",
"postcss": "8.4.29",
"autoprefixer": "10.4.15",
"tailwindcss": "3.3.3",
"@skeletonlabs/skeleton": "2.0.0",
"@skeletonlabs/tw-plugin": "0.1.0",
"vite-plugin-tailwind-purgecss": "0.1.3",
"@types/node": "20.6.0",
"@fontsource/fira-mono": "^4.5.10",
"@neoconfetti/svelte": "^1.0.0", "@neoconfetti/svelte": "^1.0.0",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.20.4",
"@types/cookie": "^0.5.1", "@types/cookie": "^0.5.1",
"@typescript-eslint/eslint-plugin": "^5.45.0", "postcss-load-config": "^4.0.1",
"@typescript-eslint/parser": "^5.45.0", "svelte-loading-spinners": "^0.3.4"
"eslint": "^8.28.0", },
"eslint-config-prettier": "^8.5.0", "type": "module",
"eslint-plugin-svelte": "^2.30.0", "dependencies": {
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",
"svelte": "^4.0.5",
"svelte-check": "^3.4.3",
"svelte-loading-spinners": "^0.3.4",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.4.2"
},
"type": "module",
"dependencies": {
"chart.js": "^4.3.3", "chart.js": "^4.3.3",
"chartjs-plugin-datalabels": "^2.2.0", "chartjs-plugin-datalabels": "^2.2.0",
"svelte-chartjs": "^3.1.2" "svelte-chartjs": "^3.1.2"
} }
} }

6
postcss.config.cjs Normal file
View file

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

18
src/app.d.ts vendored
View file

@ -1,15 +1,9 @@
// See https://kit.svelte.dev/docs/types#app // See https://kit.svelte.dev/docs/types#app
// for information about these interfaces // for information about these interfaces
declare global { // and what to do when importing types
namespace App { declare namespace App {
// interface Error {} // interface Locals {}
// interface Locals {} // interface PageData {}
// interface PageData {} // interface Error {}
// interface Platform {} // interface Platform {}
interface Locals {
authenticated: boolean | null
}
}
} }
export {};

View file

@ -1,12 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" class="dark">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover" data-theme="wintry">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents" class="h-screen overflow-hidden contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

6
src/app.postcss Normal file
View file

@ -0,0 +1,6 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind variants;
html, body { @apply h-full overflow-hidden; }

View file

@ -0,0 +1,6 @@
<script lang="ts">
export let text: string;
export let action: any = () => {};
</script>
<button class="btn px-4 py-2 mx-2 rounded-token variant-ringed-primary hover:variant-filled-primary active:variant-filled-primary" on:click={action}>{text}</button>

View file

@ -1,34 +0,0 @@
<script>
export let showModal = false;
export let isPromo = false;
</script>
{#if showModal}
<div class="backdrop" class:promo={isPromo} on:click|self>
<div class="modal">
<slot />
</div>
</div>
{/if}
<style>
.backdrop {
z-index: 200;
width: 100%;
height: 100%;
position: fixed;
background-color: rgba(0, 0, 0, 0.8);
}
.modal {
padding: 10px;
border-radius: 10px;
max-width: 400px;
margin: 10% auto;
text-align: center;
background: #3f3f3f;
}
.promo .modal {
background: crimson;
color: white;
}
</style>

View file

@ -1,49 +0,0 @@
<script lang="ts">
import Modal from "$lib/components/Modal.svelte";
export let showSettings: boolean;
const toggleSettings = () => {
showSettings = !showSettings;
}
const handleSubmit = () => {
console.log("submitted ", gameGroupSettings);
}
let gameGroupSettings: [] = [];
</script>
<Modal showModal={showSettings} on:click={toggleSettings}>
<h3>Settings</h3>
<div class="settings-modal">
<form on:submit|preventDefault={handleSubmit}>
<b>Show Game Groups:</b><br>
<input type="checkbox" bind:group={gameGroupSettings} value="a">Group A<br>
<input type="checkbox" bind:group={gameGroupSettings} value="b">Group B<br>
<input type="checkbox" bind:group={gameGroupSettings} value="-">Ungrouped<br>
<div class="settings-modal-save">
<button>Save</button>
</div>
</form>
</div>
</Modal>
<style>
.settings-modal {
text-align: left;
}
.settings-modal input {
margin-left: 1rem;
}
.settings-modal-save {
text-align: center;
}
</style>

View file

@ -1,2 +1,2 @@
export const apiBaseUrl = 'https://www.chellaris.net/api'; export const apiBaseUrl = 'https://wip.chellaris.net/api';
export const MACHINE_GROUP_ID = 12; export const MACHINE_GROUP_ID = 12;

View file

@ -1,5 +1,5 @@
import { writable, type Writable } from "svelte/store"; import { writable, type Writable } from "svelte/store";
const AdminSelectedGameStore: Writable<number> = writable(1); const AdminSelectedGameStore: Writable<number> = writable(0);
export default AdminSelectedGameStore; export default AdminSelectedGameStore;

View file

@ -1,38 +1,65 @@
<script> <script lang="ts">
import '../app.postcss';
import { Modal, autoModeWatcher, type ModalComponent, getModalStore, type ModalSettings, initializeStores } from '@skeletonlabs/skeleton';
import { apiBaseUrl } from '$lib/components/consts'; import { apiBaseUrl } from '$lib/components/consts';
import { LeanChellarisDataStore } from '$lib/stores/ChellarisData'; import { LeanChellarisDataStore } from '$lib/stores/ChellarisData';
import Header from './Header.svelte'; import Header from './Header.svelte';
import './styles.css'; import { AppShell } from '@skeletonlabs/skeleton';
import Settings from './Settings.svelte';
import AdminSelectedGameStore from '$lib/stores/admin-page/GameStore';
import type { ChellarisGameInfo } from '$lib/types/chellaris';
initializeStores();
$: { $: {
fetch(apiBaseUrl + '/v3/ethics').then((res) => { fetch(apiBaseUrl + '/v3/ethics').then((res) => {
res.json().then((data) => { res.json().then((data) => {
$LeanChellarisDataStore.ethics = data.ethics; $LeanChellarisDataStore.ethics = data.ethics;
}) });
}); });
fetch(apiBaseUrl + '/v3/phenotypes').then((res) => { fetch(apiBaseUrl + '/v3/phenotypes').then((res) => {
res.json().then((data) => { res.json().then((data) => {
$LeanChellarisDataStore.phenotypes = data.phenotypes; $LeanChellarisDataStore.phenotypes = data.phenotypes;
}) });
}); });
}
fetch(apiBaseUrl + '/v3/games').then((res) => {
res.json().then((data: { [key: number]: ChellarisGameInfo }) => {
if ($AdminSelectedGameStore == 1) {
$AdminSelectedGameStore = Object.values(data)[0].id;
}
});
});
}
const modalComponentRegistry: Record<string, ModalComponent> = {
settingsModal: {
ref: Settings,
props: { background: 'bg-red-500'}
}
}
</script> </script>
<div class="app"> <svelte:head>{@html `<script>${autoModeWatcher.toString()} autoModeWatcher();</script>`}</svelte:head>
<Header />
<Modal components={modalComponentRegistry}/>
<AppShell regionPage="relative">
<svelte:fragment slot="header">
<Header/>
</svelte:fragment>
<!-- (sidebarLeft) -->
<!-- (sidebarRight) -->
<!-- <svelte:fragment slot="pageHeader">Page Header</svelte:fragment> -->
<!-- Router Slot -->
<slot /> <slot />
</div> <!-- ---- / ---- -->
<!-- (pageFooter) -->
<style> <!-- (footer) -->
.app { </AppShell>
display: grid;
grid-template-areas:
'header'
'app';
grid-template-rows: 3rem 1fr;
min-height: 100vh;
max-height: 100vh;
}
</style>

View file

@ -1,15 +1,14 @@
<svelte:head> <!-- YOU CAN DELETE EVERYTHING IN THIS PAGE -->
<title>Home</title>
<meta name="description" content="chellaris.net landing page" />
</svelte:head>
<div class="text-column"> <div class="container h-full mx-auto flex justify-center items-center">
<ul> <div class="space-y-5">
<li> <ul>
<a href="/graphs">Graphs</a> <li>
</li> <a class="hover:text-primary-500" href="/graphs">Graphs</a>
<li> </li>
<a href="/legacy-graphs">Game 15 Graphs</a> <li>
</li> <a class="hover:text-primary-500" href="/legacy-graphs">Game 15 Graphs</a>
</ul> </li>
</ul>
</div>
</div> </div>

View file

@ -3,10 +3,13 @@
<meta name="description" content="401: Unauthorized" /> <meta name="description" content="401: Unauthorized" />
</svelte:head> </svelte:head>
<div class="text-column"> <div class="container h-full mx-auto flex justify-center">
<h1>Unauthorized</h1> <div class="my-8 space-y-5">
<h1 class="text-center">Unauthorized</h1>
<p>
Your request was denied, the authentication token in your settings seems to be invalid.
</p>
</div>
<p>
Your request was denied, the authentication token in your settings seems to be invalid.
</p>
</div> </div>

View file

@ -1,164 +1,55 @@
<script> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
import discord from '$lib/images/discord.svg'; import discord from '$lib/images/discord.svg';
import { AppBar, LightSwitch, TabAnchor, TabGroup, getModalStore, type ModalSettings } from '@skeletonlabs/skeleton';
import Settings from './Settings.svelte'; import Settings from './Settings.svelte';
import Button from '$lib/components/Button.svelte';
let showSettings = false; let showSettings = false;
const modalStore = getModalStore();
const openSettings = () => {
const settings: ModalSettings = {
type: 'component',
component: 'settingsModal'
};
modalStore.trigger(settings);
}
</script> </script>
<Settings bind:showSettings={showSettings}/> <AppBar gridColumns="grid-cols-3" slotDefault="place-self-center" slotTrail="place-content-end">
<svelte:fragment slot="lead">
<p />
</svelte:fragment>
<TabGroup
padding="px-4 py-2"
justify="justify-center"
rounded="rounded-tr-xl rounded-tl-xl"
border="border-b-2 border-primary-500"
active="variant-glass-primary hover:variant-ghost-primary"
hover="hover:variant-ghost-primary"
>
<!--<div aria-current={$page.url.pathname.startsWith('/sign-up') ? 'page' : undefined}>
<a href="/sign-up">Empire Sign-Up</a>
</div>-->
<TabAnchor class="mr-1" href="/" selected={$page.url.pathname === '/'}>Home</TabAnchor>
<TabAnchor class="mx-1" href="/graphs" selected={$page.url.pathname.startsWith('/graphs')}>Graphs</TabAnchor>
<TabAnchor class="mx-1" href="/legacy-graphs" selected={$page.url.pathname === '/legacy-graphs'}>Game 15 Graphs</TabAnchor>
<TabAnchor class="mx-1" href="/admin" selected={$page.url.pathname === '/admin'}>Admin Menu</TabAnchor>
<TabAnchor class="ml-1" href="/about" selected={$page.url.pathname === '/about'}>About</TabAnchor>
</TabGroup>
<header> <svelte:fragment slot="trail">
<div class="corner"> <Button text="Settings" action={openSettings} />
</div> <LightSwitch />
<div>
<nav> <!-- Logo -->
<svg viewBox="0 0 2 3" aria-hidden="true"> <a class="lg:!ml-0 w-[32px] lg:w-auto overflow-hidden" href="https://discord.gg/invite/BYNeHaPNh9" target="_blank" rel="noopener noreferrer">
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" /> <img width="32px" height="32px" src={discord} alt="Discord" />
</svg> </a>
<ul> </div>
<li aria-current={$page.url.pathname === '/' ? 'page' : undefined}> </svelte:fragment>
<a href="/">Home</a> </AppBar>
</li> <div />
<li aria-current={$page.url.pathname.startsWith('/graphs') ? 'page' : undefined}>
<a href="/graphs">Graphs</a>
</li>
<li aria-current={$page.url.pathname === '/legacy-graphs' ? 'page' : undefined}>
<a href="/legacy-graphs">Game 15 Graphs</a>
</li>
<!--<li aria-current={$page.url.pathname.startsWith('/sign-up') ? 'page' : undefined}>
<a href="/sign-up">Empire Sign-Up</a>
</li>-->
<li aria-current={$page.url.pathname.startsWith('/admin') ? 'page' : undefined}>
<a href="/admin">Admin Menu</a>
</li>
<li aria-current={$page.url.pathname.startsWith('/about') ? 'page' : undefined}>
<a href="/about">About</a>
</li>
<li>
<button on:click={() => showSettings = !showSettings}>Settings</button>
</li>
</ul>
<svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
</svg>
</nav>
<div class="corner">
<a href="https://discord.gg/invite/BYNeHaPNh9" target="_blank" rel="noopener noreferrer">
<img src={discord} alt="Discord" />
</a>
</div>
</header>
<style>
header {
grid-area: header;
display: flex;
justify-content: space-between;
}
.corner {
width: var(--height-m);
height: var(--height-m);
}
.corner a {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.corner img {
width: 2em;
height: 2em;
object-fit: contain;
}
nav {
display: flex;
justify-content: center;
--background: rgba(0, 0, 0, 0.7);
}
svg {
width: 2em;
height: var(--height-m);
display: block;
}
path {
fill: var(--background);
}
ul {
position: relative;
padding: 0;
margin: 0;
height: var(--height-m);
display: flex;
justify-content: center;
align-items: center;
list-style: none;
background: var(--background);
background-size: contain;
}
li {
position: relative;
height: 100%;
}
li[aria-current='page']::before {
--size: 6px;
content: '';
width: 0;
height: 0;
position: absolute;
top: 0;
left: calc(50% - var(--size));
border: var(--size) solid transparent;
border-top: var(--size) solid var(--color-active-1);
}
nav a {
display: flex;
height: 100%;
align-items: center;
padding: 0 0.5rem;
color: var(--color-text);
font-weight: 700;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.1em;
text-decoration: none;
transition: color 0.2s linear;
}
nav a:hover {
color: var(--color-active-1);
}
nav button {
display: flex;
height: 100%;
align-items: center;
padding: 0 0.5rem;
color: var(--color-text);
font-weight: 700;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.1em;
text-decoration: none;
transition: color 0.2s linear;
cursor: pointer;
background: var(--background);
border: 0;
}
nav button:hover {
color: var(--color-active-1);
}
</style>

View file

@ -1,79 +1,66 @@
<script lang="ts"> <script lang="ts">
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import Modal from "$lib/components/Modal.svelte"; import Button from "$lib/components/Button.svelte";
import AuthTokenStore from "$lib/stores/AuthTokenStore"; import AuthTokenStore from "$lib/stores/AuthTokenStore";
import { fade } from "svelte/transition"; import { getModalStore } from "@skeletonlabs/skeleton";
import { fade, slide } from "svelte/transition";
export let showSettings: boolean; const modalStore = getModalStore();
let showAuthSaved = false; let showAuthSaved = false;
let showPlaceholder = true;
const saveAuth = () => { const saveAuth = () => {
showAuthSaved = true; showAuthSaved = true;
showPlaceholder = false;
setTimeout(function() { setTimeout(function() {
showAuthSaved = false; showAuthSaved = false;
}, 2000); setTimeout(function() {
showPlaceholder = true;
}, 195);
}, 1000);
document.cookie = "authToken=" + authToken; document.cookie = "authToken=" + authToken;
$AuthTokenStore = authToken; $AuthTokenStore = authToken;
} }
let authToken: string; let authToken: string;
$: { if (browser) {
if (browser) { authToken = document.cookie.split("=")[document.cookie.split("=").length - 1];
authToken = document.cookie.split("=")[document.cookie.split("=").length - 1]; $AuthTokenStore = authToken;
$AuthTokenStore = authToken;
}
}
const toggleSettings = () => {
showSettings = !showSettings;
} }
const handleSubmit = () => { const handleSubmit = () => {
console.log("submitted ", gameGroupSettings); console.log("submitted ", gameGroupSettings);
showSettings = false; document.cookie = "authToken=" + authToken;
modalStore.close();
} }
let gameGroupSettings: [] = []; let gameGroupSettings: [] = [];
const formInput = "bg-surface-200-700-token focus:brightness-105 hover:brightness-105 border-token border-surface-400-500-token rounded-token px-3 w-[6rem]"
</script> </script>
<Modal showModal={showSettings} on:click={toggleSettings}> <div class="card p-4 w-modal-slim shadow-xl space-y-4 text-center rounded-xl">
<h3>Settings</h3> <h2>Settings</h2>
<div class="settings-modal"> <div class="p-4 space-y-4">
<form on:submit|preventDefault={handleSubmit} class="modal-form">
<label for="authTokenInput" class="label my-2 gap-1 grid grid-cols-[20%,30%,30%,10%,10%]">
<span></span>
<span>Auth Token:</span>
<input class={formInput} id="authTokenInput" bind:value={authToken} on:input={saveAuth} type="text">
{#if showAuthSaved}
<span transition:fade={{duration: 200}}>Saved!</span>
{/if}
<form on:submit|preventDefault={handleSubmit}> </label>
<label for="authTokenInput">Auth Token:</label>
<input id="authTokenInput" bind:value={authToken} on:input={saveAuth} type="text">
{#if showAuthSaved}
<label for="authTokenInput" in:fade={{duration: 200}} out:fade={{duration: 200}}>Saved!</label>
{/if}
<br> <br>
<br> <br>
<!-- <b>Show Game Groups:</b><br> <!-- <b>Show Game Groups:</b><br>
<input type="checkbox" bind:group={gameGroupSettings} value="b">Group B<br> <input type="checkbox" bind:group={gameGroupSettings} value="b">Group B<br>
<input type="checkbox" bind:group={gameGroupSettings} value="-">Ungrouped<br> --> <input type="checkbox" bind:group={gameGroupSettings} value="-">Ungrouped<br> -->
<div class="settings-modal-save"> <Button text="Save"/>
<button>Save</button>
</div>
</form> </form>
</div> </div>
</div>
</Modal>
<style>
.settings-modal {
text-align: left;
}
.settings-modal input {
margin-left: 1rem;
}
.settings-modal-save {
text-align: center;
}
</style>

View file

@ -3,12 +3,13 @@
<meta name="description" content="About this site" /> <meta name="description" content="About this site" />
</svelte:head> </svelte:head>
<div class="text-column"> <div class="container h-full mx-auto flex justify-center">
<h1>About this site</h1> <div class="my-8 space-y-5">
<h1 class="text-center">About this site</h1>
<p> <p>
This site replaces the long used Excel Sheet and Google Docs used for managing the Chellaris Sign-Up process. This site replaces the long used Excel Sheet and Google Docs used for managing the Chellaris Sign-Up process. For now only the Sign-Up statistics and
For now only the Sign-Up statistics and Admin work have been migrated along with some fancy pants graphs. Admin work have been migrated along with some fancy pants graphs. The Empire Sign-Up process may follow at a later date.
The Empire Sign-Up process may follow at a later date. </p>
</p> </div>
</div> </div>

View file

@ -264,190 +264,185 @@
<meta name="description" content="Admin Menu for managing Chellaris Sign-Ups" /> <meta name="description" content="Admin Menu for managing Chellaris Sign-Ups" />
</svelte:head> </svelte:head>
<div class="frame"> <div class="container p-12 max-h-full min-w-full">
{#if loaded} <div class="max-h-full min-w-full grid grid-rows-2 grid-cols-[20%,40%,40%] frame border border-white">
<List area="games" listTitle="Games"> {#if loaded}
{#each Object.values(gameList) as game} <List area="games" listTitle="Games">
<button class="list-card" class:active={game.id == $AdminSelectedGameStore ? 'active' : ''} on:click={() => setActiveGame(game.id)}> {#each Object.values(gameList) as game}
<div class="card-content">{game.name}</div> <button class="list-card" class:active={game.id == $AdminSelectedGameStore ? 'active' : ''} on:click={() => setActiveGame(game.id)}>
<button class="delete-box" on:click={() => deleteGame(game.id)}> <div class="card-content">{game.name}</div>
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <button class="delete-box" on:click={() => deleteGame(game.id)}>
<path fill="none" d="M0 0 24 24 M24 0 0 24" /> <svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
</svg> <path fill="none" d="M0 0 24 24 M24 0 0 24" />
</button> </svg>
</button> </button>
{/each}
{#if newGameForm}
<div class="list-card active">
<form on:submit={addGame}>
<input bind:value={newGameName} />
<input type="submit" value="Add Game" />
</form>
</div>
{:else if addingNewGame}
<div class="list-card loading">
<div class="card-content">{newGameName}</div>
</div>
{:else}
<button
class="list-card"
on:click={() => {
newGameForm = true;
}}
>
<div class="card-content button">+</div>
</button>
{/if}
</List>
<List area="groups" listTitle="Groups">
{#if loadingGameData}
<LoadingSpinnerLocal />
{:else}
{#each Object.values(groupList) as group}
<button
class="list-card"
class:active={$AdminSelectedGroupStore[$AdminSelectedGameStore]
? $AdminSelectedGroupStore[$AdminSelectedGameStore][group.id]
? 'active'
: ''
: ''}
on:click={() => toggleActiveGroup(group.id)}
>
<div class="card-content">{group.name}</div>
{#if group.name !== 'N/A'}
<button class="delete-box" on:click={() => deleteGroup(group.id)}>
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="none" d="M0 0 24 24 M24 0 0 24" />
</svg>
</button>
{/if}
</button> </button>
{/each} {/each}
{#if newGroupForm} {#if newGameForm}
<div class="list-card active"> <div class="list-card active">
<form on:submit={addGroup}> <form on:submit={addGame}>
<input bind:value={newGroupName} /> <input bind:value={newGameName} />
<input type="submit" value="Add Group" /> <input type="submit" value="Add Game" />
</form> </form>
</div> </div>
{:else if addingNewGroup} {:else if addingNewGame}
<div class="list-card loading"> <div class="list-card loading">
<div class="card-content">{newGroupName}</div> <div class="card-content">{newGameName}</div>
</div> </div>
{:else} {:else}
<button <button
class="list-card" class="list-card"
on:click={() => { on:click={() => {
newGroupForm = true; newGameForm = true;
}} }}
> >
<div class="card-content button">+</div> <div class="card-content button">+</div>
</button> </button>
{/if} {/if}
{/if} </List>
</List> <List area="groups" listTitle="Groups">
<List area="empires" listTitle="Empires"> {#if loadingGameData}
<div class="empires-table"> <LoadingSpinnerLocal />
<div class="table-headers"> {:else}
<div class="table-header">Empire Name</div> {#each Object.values(groupList) as group}
<div class="table-header">Discord User</div> <button
</div> class="list-card"
<div class="table-content"> class:active={$AdminSelectedGroupStore[$AdminSelectedGameStore]
{#if loadingGameData} ? $AdminSelectedGroupStore[$AdminSelectedGameStore][group.id]
<LoadingSpinnerLocal /> ? 'active'
{:else} : ''
{#each Object.values(empireList) as empire} : ''}
<button on:click={() => toggleActiveGroup(group.id)}
class="list-card" >
class:active={empire.id == $AdminSelectedEmpireStore[$AdminSelectedGameStore] ? 'active' : ''} <div class="card-content">{group.name}</div>
on:click={() => setActiveEmpire(empire.id)} {#if group.name !== 'N/A'}
> <button class="delete-box" on:click={() => deleteGroup(group.id)}>
<div class="card-content" class:active={empire.id == $AdminSelectedEmpireStore[$AdminSelectedGameStore] ? 'active' : ''}>{empire.name}</div>
<div class="card-content" class:active={empire.id == $AdminSelectedEmpireStore[$AdminSelectedGameStore] ? 'active' : ''}>
{empire.discord_user || 'N/A'}
</div>
<button class="delete-box" on:click={() => deleteEmpire(empire)}>
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="none" d="M0 0 24 24 M24 0 0 24" /> <path fill="none" d="M0 0 24 24 M24 0 0 24" />
</svg> </svg>
</button> </button>
</button> {/if}
{:else} </button>
<div class="list-card"> {/each}
<div class="card-content">No Empires Present</div> {#if newGroupForm}
</div> <div class="list-card active">
{/each} <form on:submit={addGroup}>
{#if addingNewEmpire} <input bind:value={newGroupName} />
<div class="list-card loading"> <input type="submit" value="Add Group" />
<div class="card-content active">{empireData.name}</div> </form>
<div class="card-content active"> </div>
{empireData.discord_user || 'N/A'} {:else if addingNewGroup}
</div> <div class="list-card loading">
</div> <div class="card-content">{newGroupName}</div>
{:else} </div>
<button class="list-card" on:click={addEmpire}> {:else}
<div class="card-content button">+</div> <button
</button> class="list-card"
{/if} on:click={() => {
newGroupForm = true;
}}
>
<div class="card-content button">+</div>
</button>
{/if} {/if}
{/if}
</List>
<List area="empires" listTitle="Empires">
<div class="empires-table">
<div class="table-headers">
<div class="table-header">Empire Name</div>
<div class="table-header">Discord User</div>
</div>
<div class="table-content">
{#if loadingGameData}
<LoadingSpinnerLocal />
{:else}
{#each Object.values(empireList) as empire}
<button
class="list-card"
class:active={empire.id == $AdminSelectedEmpireStore[$AdminSelectedGameStore] ? 'active' : ''}
on:click={() => setActiveEmpire(empire.id)}
>
<div class="card-content" class:active={empire.id == $AdminSelectedEmpireStore[$AdminSelectedGameStore] ? 'active' : ''}>{empire.name}</div>
<div class="card-content" class:active={empire.id == $AdminSelectedEmpireStore[$AdminSelectedGameStore] ? 'active' : ''}>
{empire.discord_user || 'N/A'}
</div>
<button class="delete-box" on:click={() => deleteEmpire(empire)}>
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="none" d="M0 0 24 24 M24 0 0 24" />
</svg>
</button>
</button>
{:else}
<div class="list-card">
<div class="card-content">No Empires Present</div>
</div>
{/each}
{#if addingNewEmpire}
<div class="list-card loading">
<div class="card-content active">{empireData.name}</div>
<div class="card-content active">
{empireData.discord_user || 'N/A'}
</div>
</div>
{:else}
<button class="list-card" on:click={addEmpire}>
<div class="card-content button">+</div>
</button>
{/if}
{/if}
</div>
</div> </div>
</div> </List>
</List> <List area="details" listTitle="Empire Details">
<List area="details" listTitle="Empire Details"> {#if typeof $AdminSelectedEmpireStore[$AdminSelectedGameStore] === 'undefined'}
{#if typeof $AdminSelectedEmpireStore[$AdminSelectedGameStore] === 'undefined'} <div>
<div> Select Empire
Select Empire </div>
</div> {:else if loadingEmpireData || loadingGameData}
{:else if loadingEmpireData || loadingGameData}
<LoadingSpinnerLocal />
{:else if addingNewEmpire || typeof empireData.group !== 'undefined'}
<EmpireDetails
bind:empire={empireData}
groups={groupList}
{auth}
bind:newEmpire={addingNewEmpire}
on:updated={updateGameData}
on:created={finishAddNewEmpire}
/>
{/if}
</List>
{:else}
<List area="games" listTitle="Games">
<LoadingSpinnerLocal />
</List>
<List area="groups" listTitle="Groups">
<LoadingSpinnerLocal />
</List>
<List area="empires" listTitle="Empires">
<div class="empires-table">
<div class="table-headers">
<div class="table-header">Empire Name</div>
<div class="table-header">Discord User</div>
</div>
<div class="table-content">
<LoadingSpinnerLocal /> <LoadingSpinnerLocal />
{:else if addingNewEmpire || typeof empireData.group !== 'undefined'}
<EmpireDetails
bind:empire={empireData}
groups={groupList}
{auth}
bind:newEmpire={addingNewEmpire}
on:updated={updateGameData}
on:created={finishAddNewEmpire}
/>
{/if}
</List>
{:else}
<List area="games" listTitle="Games">
<LoadingSpinnerLocal />
</List>
<List area="groups" listTitle="Groups">
<LoadingSpinnerLocal />
</List>
<List area="empires" listTitle="Empires">
<div class="empires-table">
<div class="table-headers">
<div class="table-header">Empire Name</div>
<div class="table-header">Discord User</div>
</div>
<div class="table-content">
<LoadingSpinnerLocal />
</div>
</div> </div>
</div> </List>
</List> <List area="details" listTitle="Empire Details">
<List area="details" listTitle="Empire Details"> <LoadingSpinnerLocal />
<LoadingSpinnerLocal /> </List>
</List> {/if}
{/if} </div>
</div> </div>
<style> <style>
.frame { .frame {
display: grid;
grid-template-areas: grid-template-areas:
'games empires details' 'games empires details'
'groups empires details'; 'groups empires details';
grid-template-columns: 20% 40% 40%;
grid-template-rows: 50% 50%;
max-height: calc(100%);
overflow: hidden;
margin: 3rem;
box-sizing: border-box;
border: 1px solid white;
} }
/* General Classes */ /* General Classes */

View file

@ -182,7 +182,8 @@
} }
</script> </script>
{#if newEmpire && newEmpirePrepared} <div>
{#if newEmpire && newEmpirePrepared}
<!-- <div> <!-- <div>
ID: {empire.id} ID: {empire.id}
</div> </div>
@ -208,14 +209,14 @@
<br /> <br />
<!--TODO: Add Scrolling to Dropdown Element--> <!--TODO: Add Scrolling to Dropdown Element-->
<DropDown <DropDown
dropdownTitle={'Species*: ' + $LeanChellarisDataStore.phenotypes[empire.portrait_group_id].species[empire.portrait_id].id} dropdownTitle={'Species*: ' + ($LeanChellarisDataStore.phenotypes[empire.portrait_group_id].species[empire.portrait_id].id + 1)}
dropdownId={speciesDropdownId} dropdownId={speciesDropdownId}
> >
{#each Object.values($LeanChellarisDataStore.phenotypes[empire.portrait_group_id].species) as species} {#each Object.values($LeanChellarisDataStore.phenotypes[empire.portrait_group_id].species) as species}
{#if species} {#if species}
<DropDownElement dropdownId={speciesDropdownId}> <DropDownElement dropdownId={speciesDropdownId}>
<input id={'checkbox' + species.id} data-dropdown={speciesDropdownId} type="radio" bind:group={empire.portrait_id} value={species.id} /> <input id={'checkbox' + species.id} data-dropdown={speciesDropdownId} type="radio" bind:group={empire.portrait_id} value={species.id} />
<label for={'checkbox' + species.id} data-dropdown={speciesDropdownId}>{species.id}</label> <label for={'checkbox' + species.id} data-dropdown={speciesDropdownId}>{species.id + 1}</label>
</DropDownElement> </DropDownElement>
{/if} {/if}
{/each} {/each}
@ -303,14 +304,14 @@
<br /> <br />
<!--TODO: Add Scrolling to Dropdown Element--> <!--TODO: Add Scrolling to Dropdown Element-->
<DropDown <DropDown
dropdownTitle={'Species: ' + $LeanChellarisDataStore.phenotypes[empire.portrait_group_id].species[empire.portrait_id].id} dropdownTitle={'Species: ' + ($LeanChellarisDataStore.phenotypes[empire.portrait_group_id].species[empire.portrait_id].id + 1)}
dropdownId={speciesDropdownId} dropdownId={speciesDropdownId}
> >
{#each Object.values($LeanChellarisDataStore.phenotypes[empire.portrait_group_id].species) as species} {#each Object.values($LeanChellarisDataStore.phenotypes[empire.portrait_group_id].species) as species}
{#if species} {#if species}
<DropDownElement dropdownId={speciesDropdownId}> <DropDownElement dropdownId={speciesDropdownId}>
<input id={'checkbox' + species.id} data-dropdown={speciesDropdownId} type="radio" bind:group={empire.portrait_id} value={species.id} /> <input id={'checkbox' + species.id} data-dropdown={speciesDropdownId} type="radio" bind:group={empire.portrait_id} value={species.id} />
<label for={'checkbox' + species.id} data-dropdown={speciesDropdownId}>{species.id}</label> <label for={'checkbox' + species.id} data-dropdown={speciesDropdownId}>{species.id + 1}</label>
</DropDownElement> </DropDownElement>
{/if} {/if}
{/each} {/each}
@ -378,6 +379,8 @@
<button on:click={updateEmpire}>Save</button> <button on:click={updateEmpire}>Save</button>
{/if} {/if}
</div>
<style> <style>
</style> </style>

View file

@ -3,7 +3,7 @@
export let listTitle = 'List'; export let listTitle = 'List';
</script> </script>
<div class="container" style={'--area:' + area}> <div class="container grid min-h-none max-h-full" style={'grid-area:' + area}>
<div class="header"> <div class="header">
{listTitle} {listTitle}
</div> </div>
@ -14,15 +14,11 @@
<style> <style>
.container { .container {
grid-area: var(--area);
display: grid;
grid-template-areas: grid-template-areas:
'list-header' 'list-header'
'list-content'; 'list-content';
grid-template-rows: 2rem calc(100% - 2rem); grid-template-rows: 2rem calc(100% - 2rem);
grid-template-columns: 100%; grid-template-columns: 100%;
min-height: none;
max-height: 100%;
} }
.header { .header {

View file

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import SubNav from './SubNav.svelte'; import SubNav from './SubNav.svelte';
import '../styles.css'; import '../../app.postcss';
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { page } from "$app/stores"; import { page } from "$app/stores";
import GraphsTabStore from '$lib/stores/GraphsTab'; import GraphsTabStore from '$lib/stores/GraphsTab';
let graphsTab = "tab"; let graphsTab = "excel-style";
// Tab Detection // Tab Detection
GraphsTabStore.subscribe(tab => { GraphsTabStore.subscribe(tab => {
graphsTab = tab; graphsTab = tab;

View file

@ -33,7 +33,7 @@ export const load: LayoutLoad = async ({ fetch }) => {
} }
if (typeof gameSelection === 'undefined') { if (typeof gameSelection === 'undefined') {
gameSelection = chellarisData.games[1].id; gameSelection = Object.values(chellarisData.games)[0].id;
} }
// Game Groups Selection // Game Groups Selection
@ -66,7 +66,7 @@ export const load: LayoutLoad = async ({ fetch }) => {
} }
if (typeof gameSelection === 'undefined') { if (typeof gameSelection === 'undefined') {
gameSelection = chellarisData.games[1].id; gameSelection = Object.values(chellarisData.games)[0].id;
} }
SelectedGameStore.set(gameSelection); SelectedGameStore.set(gameSelection);

View file

@ -20,7 +20,7 @@
} }
}); });
groupPortraitLimit = containsNA groupPortraitLimit = containsNA
? Object.entries($ChellarisDataStore.games[$SelectedGameStore].groups).length - 1 ? Math.max(Object.entries($ChellarisDataStore.games[$SelectedGameStore].groups).length - 1, 1)
: Object.entries(selectedGameGroupData.selectedGroups).length; : Object.entries(selectedGameGroupData.selectedGroups).length;
} }
@ -82,7 +82,6 @@
}); });
Object.values(selectedGame.empires).forEach((empire) => { Object.values(selectedGame.empires).forEach((empire) => {
console.log(empire); //DEBUG
if (empire.group in selectedGameGroupData.selectedGroups) { if (empire.group in selectedGameGroupData.selectedGroups) {
if (typeof newPageData.takenPortraits[empire.portrait_group_id] === 'undefined') { if (typeof newPageData.takenPortraits[empire.portrait_group_id] === 'undefined') {
newPageData.takenPortraits[empire.portrait_group_id] = []; newPageData.takenPortraits[empire.portrait_group_id] = [];
@ -170,7 +169,6 @@
} }
pageData = newPageData; pageData = newPageData;
console.log(pageData);
} }
// Save Tab to Store // Save Tab to Store

View file

@ -1,103 +0,0 @@
@import '@fontsource/fira-mono';
:root {
--font-body: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--font-mono: 'Fira Mono', monospace;
--color-bg: #1a1a1a;
--color-active-1: #00c4a3;
--color-active-2: #4075a6;
--color-warn-1: #FF9900;
--color-warn-2: #EA4335;
--color-text: rgba(255, 255, 255, 0.7);
--column-width: 42rem;
--column-margin-top: 4rem;
font-family: var(--font-body);
color: var(--color-text);
--height-m: 3em;
}
body {
min-height: 100vh;
margin: 0;
background-attachment: fixed;
background-color: var(--color-bg);
background-size: 100vw 100vh;
}
h1,
h2,
p {
font-weight: 400;
}
p {
line-height: 1.5;
}
a {
color: var(--color-active-1);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1 {
font-size: 2rem;
text-align: center;
}
h2 {
font-size: 1rem;
}
pre {
font-size: 16px;
font-family: var(--font-mono);
background-color: rgba(255, 255, 255, 0.45);
border-radius: 3px;
box-shadow: 2px 2px 6px rgb(255 255 255 / 25%);
padding: 0.5em;
overflow-x: auto;
color: var(--color-text);
}
.text-column {
display: flex;
max-width: 48rem;
flex: 0.6;
flex-direction: column;
justify-content: center;
margin: 0 auto;
}
input,
button {
font-size: inherit;
font-family: inherit;
}
button:focus:not(:focus-visible) {
outline: none;
}
@media (min-width: 720px) {
h1 {
font-size: 2.4rem;
}
}
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: auto;
margin: 0;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
white-space: nowrap;
}

View file

@ -1,23 +1,19 @@
import adapter from '@sveltejs/adapter-auto'; import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/kit/vite'; import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
extensions: ['.svelte'],
// Consult https://kit.svelte.dev/docs/integrations#preprocessors // Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors // for more information about preprocessors
preprocess: vitePreprocess(), preprocess: [ vitePreprocess()],
kit: { kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter. // If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters. // See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter() adapter: adapter()
}, }
optimizeDeps: {
exclude: ['svelte-chartjs']
},
}; };
export default config;
export default config;

31
tailwind.config.ts Normal file
View file

@ -0,0 +1,31 @@
import { join } from 'path'
import type { Config } from 'tailwindcss'
import { skeleton } from '@skeletonlabs/tw-plugin'
import plugin from 'tailwindcss/plugin'
export default {
darkMode: 'class',
content: ['./src/**/*.{html,js,svelte,ts}', join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')],
theme: {
extend: {},
},
plugins: [
skeleton({
themes: {
preset: [
{
name: 'wintry',
enhancements: true,
},
],
},
}),
plugin(function({ addBase, theme }) {
addBase({
'h1': { fontSize: theme('fontSize.2xl') },
'h2': { fontSize: theme('fontSize.xl') },
'h3': { fontSize: theme('fontSize.lg') },
})
})
],
} satisfies Config;

View file

@ -14,4 +14,4 @@
// //
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in // from the referenced tsconfig.json - TypeScript does not merge them in
} }

View file

@ -1,8 +1,9 @@
import { purgeCss } from 'vite-plugin-tailwind-purgecss';
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
export default defineConfig({ export default defineConfig({
plugins: [sveltekit()], plugins: [sveltekit(), purgeCss()],
server: { server: {
host: true, host: true,
port: 8004, port: 8004,

36
yarn-error.log Normal file
View file

@ -0,0 +1,36 @@
Arguments:
/usr/bin/node-18 /usr/local/bin/yarn init skeleton-app@latest
PATH:
/home/neshura/.local/bin:/usr/bin:/usr/condabin:/home/neshura/.cargo/bin:/usr/lib64/qt-3.3/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/neshura/.dotnet/tools
Yarn version:
1.22.19
Node version:
18.17.1
Platform:
linux x64
Trace:
Error: canceled
at Interface.<anonymous> (/usr/local/lib/node_modules/yarn/lib/cli.js:137150:13)
at Interface.emit (node:events:514:28)
at [_ttyWrite] [as _ttyWrite] (node:internal/readline/interface:1131:18)
at ReadStream.onkeypress (node:internal/readline/interface:270:20)
at ReadStream.emit (node:events:514:28)
at emitKeys (node:internal/readline/utils:357:14)
at emitKeys.next (<anonymous>)
at ReadStream.onData (node:internal/readline/emitKeypressEvents:64:36)
at ReadStream.emit (node:events:514:28)
at addChunk (node:internal/streams/readable:324:12)
npm manifest:
No manifest
yarn manifest:
No manifest
Lockfile:
No lockfile

888
yarn.lock

File diff suppressed because it is too large Load diff