CSS Grid + Admin Page Content
This commit is contained in:
parent
6135d18366
commit
3992d964da
19 changed files with 430 additions and 306 deletions
1
src/lib/components/consts.ts
Normal file
1
src/lib/components/consts.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const apiBaseUrl = 'https://wip.chellaris.net/api';
|
5
src/lib/stores/AuthTokenStore.ts
Normal file
5
src/lib/stores/AuthTokenStore.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
|
const AuthTokenStore: Writable<string> = writable("");
|
||||||
|
|
||||||
|
export default AuthTokenStore;
|
5
src/lib/stores/admin-page/EmpireStore.ts
Normal file
5
src/lib/stores/admin-page/EmpireStore.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
|
const AdminSelectedEmpireStore: Writable<{ [key: number]: number }> = writable({});
|
||||||
|
|
||||||
|
export default AdminSelectedEmpireStore;
|
7
src/lib/stores/admin-page/GameDataStore.ts
Normal file
7
src/lib/stores/admin-page/GameDataStore.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
import type { ChellarisInfo } from '../../types/chellaris';
|
||||||
|
import { createChellarisInfo } from '../../types/chellaris';
|
||||||
|
|
||||||
|
const AdminGameDataStore: Writable<ChellarisInfo> = writable(createChellarisInfo());
|
||||||
|
|
||||||
|
export default AdminGameDataStore;
|
5
src/lib/stores/admin-page/GameStore.ts
Normal file
5
src/lib/stores/admin-page/GameStore.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
|
const AdminSelectedGameStore: Writable<number> = writable();
|
||||||
|
|
||||||
|
export default AdminSelectedGameStore;
|
|
@ -29,6 +29,11 @@ export type ChellarisEmpire = {
|
||||||
ethics: { [key: number]: Ethic },
|
ethics: { [key: number]: Ethic },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ChellarisGameInfo = {
|
||||||
|
id: number,
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
export const createChellarisInfo = (): ChellarisInfo => {
|
export const createChellarisInfo = (): ChellarisInfo => {
|
||||||
const newChellarisInfo = {
|
const newChellarisInfo = {
|
||||||
games: [],
|
games: [],
|
||||||
|
|
|
@ -11,19 +11,12 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.app {
|
.app {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-areas:
|
||||||
|
'header'
|
||||||
|
'app';
|
||||||
|
grid-template-rows: 3rem 1fr;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
max-height: 100vh;
|
||||||
|
|
||||||
main {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 0rem;
|
|
||||||
width: 100%;
|
|
||||||
max-height: 95vh;
|
|
||||||
margin: 0 auto;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
|
grid-area: header;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from "$app/environment";
|
import { browser } from "$app/environment";
|
||||||
import Modal from "$lib/components/Modal.svelte";
|
import Modal from "$lib/components/Modal.svelte";
|
||||||
|
import AuthTokenStore from "$lib/stores/AuthTokenStore";
|
||||||
import { fade } from "svelte/transition";
|
import { fade } from "svelte/transition";
|
||||||
|
|
||||||
export let showSettings: boolean;
|
export let showSettings: boolean;
|
||||||
|
@ -13,13 +14,14 @@
|
||||||
showAuthSaved = false;
|
showAuthSaved = false;
|
||||||
}, 2000);
|
}, 2000);
|
||||||
document.cookie = "authToken=" + authToken;
|
document.cookie = "authToken=" + authToken;
|
||||||
|
$AuthTokenStore = authToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
let authToken: string;
|
let authToken: string;
|
||||||
$: console.log(authToken);
|
|
||||||
$: {
|
$: {
|
||||||
if (browser) {
|
if (browser) {
|
||||||
authToken = document.cookie.split("=")[document.cookie.split("=").length - 1];
|
authToken = document.cookie.split("=")[document.cookie.split("=").length - 1];
|
||||||
|
$AuthTokenStore = authToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
import { fail } from '@sveltejs/kit';
|
|
||||||
import { Game } from './game';
|
|
||||||
import type { PageServerLoad, Actions } from './$types';
|
|
||||||
|
|
||||||
export const load = (({ cookies }) => {
|
|
||||||
const game = new Game(cookies.get('sverdle'));
|
|
||||||
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* The player's guessed words so far
|
|
||||||
*/
|
|
||||||
guesses: game.guesses,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of strings like '__x_c' corresponding to the guesses, where 'x' means
|
|
||||||
* an exact match, and 'c' means a close match (right letter, wrong place)
|
|
||||||
*/
|
|
||||||
answers: game.answers,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The correct answer, revealed if the game is over
|
|
||||||
*/
|
|
||||||
answer: game.answers.length >= 6 ? game.answer : null
|
|
||||||
};
|
|
||||||
}) satisfies PageServerLoad;
|
|
||||||
|
|
||||||
export const actions = {
|
|
||||||
/**
|
|
||||||
* Modify game state in reaction to a keypress. If client-side JavaScript
|
|
||||||
* is available, this will happen in the browser instead of here
|
|
||||||
*/
|
|
||||||
update: async ({ request, cookies }) => {
|
|
||||||
const game = new Game(cookies.get('sverdle'));
|
|
||||||
|
|
||||||
const data = await request.formData();
|
|
||||||
const key = data.get('key');
|
|
||||||
|
|
||||||
const i = game.answers.length;
|
|
||||||
|
|
||||||
if (key === 'backspace') {
|
|
||||||
game.guesses[i] = game.guesses[i].slice(0, -1);
|
|
||||||
} else {
|
|
||||||
game.guesses[i] += key;
|
|
||||||
}
|
|
||||||
|
|
||||||
cookies.set('sverdle', game.toString());
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modify game state in reaction to a guessed word. This logic always runs on
|
|
||||||
* the server, so that people can't cheat by peeking at the JavaScript
|
|
||||||
*/
|
|
||||||
enter: async ({ request, cookies }) => {
|
|
||||||
const game = new Game(cookies.get('sverdle'));
|
|
||||||
|
|
||||||
const data = await request.formData();
|
|
||||||
const guess = data.getAll('guess') as string[];
|
|
||||||
|
|
||||||
if (!game.enter(guess)) {
|
|
||||||
return fail(400, { badGuess: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
cookies.set('sverdle', game.toString());
|
|
||||||
},
|
|
||||||
|
|
||||||
restart: async ({ cookies }) => {
|
|
||||||
cookies.delete('sverdle');
|
|
||||||
}
|
|
||||||
} satisfies Actions;
|
|
|
@ -1,121 +1,298 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { confetti } from '@neoconfetti/svelte';
|
import { browser } from '$app/environment';
|
||||||
import { enhance } from '$app/forms';
|
import { apiBaseUrl } from '$lib/components/consts';
|
||||||
import type { PageData, ActionData } from './$types';
|
import AuthTokenStore from '$lib/stores/AuthTokenStore';
|
||||||
import { reduced_motion } from './reduced-motion';
|
import AdminSelectedEmpireStore from '$lib/stores/admin-page/EmpireStore';
|
||||||
|
import AdminSelectedGameStore from '$lib/stores/admin-page/GameStore';
|
||||||
|
import List from './List.svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data;
|
||||||
|
|
||||||
export let form: ActionData;
|
let newGameForm = false;
|
||||||
|
let addingNewGame = false;
|
||||||
/** Whether or not the user has won */
|
let newGameName = '';
|
||||||
$: won = data.answers.at(-1) === 'xxxxx';
|
$: gameList = data.games;
|
||||||
|
|
||||||
/** The index of the current guess */
|
|
||||||
$: i = won ? -1 : data.answers.length;
|
|
||||||
|
|
||||||
/** Whether the current guess can be submitted */
|
|
||||||
$: submittable = data.guesses[i]?.length === 5;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A map of classnames for all letters that have been guessed,
|
|
||||||
* used for styling the keyboard
|
|
||||||
*/
|
|
||||||
let classnames: Record<string, 'exact' | 'close' | 'missing'>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A map of descriptions for all letters that have been guessed,
|
|
||||||
* used for adding text for assistive technology (e.g. screen readers)
|
|
||||||
*/
|
|
||||||
let description: Record<string, string>;
|
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
classnames = {};
|
if (typeof localStorage !== 'undefined') {
|
||||||
description = {};
|
localStorage.setItem('adminGameSelection', JSON.stringify($AdminSelectedGameStore));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
data.answers.forEach((answer, i) => {
|
$: {
|
||||||
const guess = data.guesses[i];
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
localStorage.setItem('adminEmpireSelection', JSON.stringify($AdminSelectedEmpireStore));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (let i = 0; i < 5; i += 1) {
|
const getGameList = () => {
|
||||||
const letter = guess[i];
|
fetch(apiBaseUrl + '/v3/list_games').then((response) => response.json().then((result) => (gameList = result)));
|
||||||
|
};
|
||||||
|
|
||||||
if (answer[i] === 'x') {
|
const setActiveGame = (game: number) => {
|
||||||
classnames[letter] = 'exact';
|
$AdminSelectedGameStore = game;
|
||||||
description[letter] = 'correct';
|
};
|
||||||
} else if (!classnames[letter]) {
|
|
||||||
classnames[letter] = answer[i] === 'c' ? 'close' : 'missing';
|
const addGame = () => {
|
||||||
description[letter] = answer[i] === 'c' ? 'present' : 'absent';
|
let newGame = {
|
||||||
}
|
auth: { token: $AuthTokenStore },
|
||||||
|
game_name: newGameName
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(apiBaseUrl + '/v3/game', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(newGame)
|
||||||
|
}).then((response) => {
|
||||||
|
getGameList();
|
||||||
|
addingNewGame = false;
|
||||||
|
response.json().then((result) => $AdminSelectedGameStore = result.id);
|
||||||
|
});
|
||||||
|
newGameName = '';
|
||||||
|
newGameForm = false;
|
||||||
|
addingNewGame = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteGame = (game_id: number) => {
|
||||||
|
fetch(apiBaseUrl + "/v3/game?game_id=" + game_id, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({token: $AuthTokenStore}),
|
||||||
|
}).then(() => {
|
||||||
|
getGameList();
|
||||||
|
if ($AdminSelectedGameStore == game_id) {
|
||||||
|
$AdminSelectedGameStore = Object.values(gameList)[0].id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
const startNewGameForm = () => {
|
||||||
* Modify the game state without making a trip to the server,
|
newGameForm = true;
|
||||||
* if client-side JavaScript is enabled
|
setActiveGame(Object.values(gameList).length);
|
||||||
*/
|
};
|
||||||
function update(event: MouseEvent) {
|
|
||||||
const guess = data.guesses[i];
|
|
||||||
const key = (event.target as HTMLButtonElement).getAttribute(
|
|
||||||
'data-key'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (key === 'backspace') {
|
const setActiveEmpire = (empireId: number) => {
|
||||||
data.guesses[i] = guess.slice(0, -1);
|
$AdminSelectedEmpireStore[$AdminSelectedGameStore] = empireId;
|
||||||
if (form?.badGuess) form.badGuess = false;
|
};
|
||||||
} else if (guess.length < 5) {
|
|
||||||
data.guesses[i] += key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const addEmpire = () => {
|
||||||
* Trigger form logic in response to a keydown event, so that
|
$AdminSelectedEmpireStore[$AdminSelectedGameStore] = list2.length;
|
||||||
* desktop users can use the keyboard to play the game
|
list2.push(list2.length);
|
||||||
*/
|
};
|
||||||
function keydown(event: KeyboardEvent) {
|
|
||||||
if (event.metaKey) return;
|
|
||||||
|
|
||||||
document
|
let list2 = new Array();
|
||||||
.querySelector(`[data-key="${event.key}" i]`)
|
for (let i = 0; i < 100; i++) {
|
||||||
?.dispatchEvent(new MouseEvent('click', { cancelable: true }));
|
list2.push(i);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={keydown} />
|
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Admin Menu</title>
|
<title>Admin Menu</title>
|
||||||
<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="text-column">
|
<div class="frame">
|
||||||
<h1>Admin Menu: Not yet implemented</h1>
|
<List area="games" listTitle="Games">
|
||||||
|
{#each Object.values(gameList) as game}
|
||||||
<p>Next on the list after proper graphs</p>
|
<button class="list-card" class:active={game.id == $AdminSelectedGameStore ? 'active' : ''} on:click={() => setActiveGame(game.id)}>
|
||||||
|
<div class="card-content">{game.name}</div>
|
||||||
|
<button class="delete-box" on:click={() => deleteGame(game.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>
|
||||||
|
</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 active">
|
||||||
|
<div class="card-content">Adding Game...</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<button class="list-card" on:click={startNewGameForm}>
|
||||||
|
<div class="card-content button">+</div>
|
||||||
|
</button>
|
||||||
|
{/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">
|
||||||
|
{#each list2 as elem}
|
||||||
|
<button class="list-card" class:active={elem == $AdminSelectedEmpireStore[$AdminSelectedGameStore] ? 'active' : ''} on:click={() => setActiveEmpire(elem)}>
|
||||||
|
<div class="card-content" class:active={elem == $AdminSelectedEmpireStore[$AdminSelectedGameStore] ? 'active' : ''}>{elem}</div>
|
||||||
|
<div class="card-content" class:active={elem == $AdminSelectedEmpireStore[$AdminSelectedGameStore] ? 'active' : ''}>{elem}</div>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
<button class="list-card" on:click={addEmpire}>
|
||||||
|
<div class="card-content button">+</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</List>
|
||||||
|
<List area="details" listTitle="Empire Details">
|
||||||
|
{$AdminSelectedEmpireStore[$AdminSelectedGameStore]}
|
||||||
|
</List>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@keyframes wiggle {
|
.frame {
|
||||||
0% {
|
display: grid;
|
||||||
transform: translateX(0);
|
grid-template-areas: 'games empires details';
|
||||||
}
|
grid-template-columns: 20% 40% 40%;
|
||||||
10% {
|
grid-template-rows: 100%;
|
||||||
transform: translateX(-2px);
|
max-height: calc(100%);
|
||||||
}
|
overflow: hidden;
|
||||||
30% {
|
margin: 3rem;
|
||||||
transform: translateX(4px);
|
box-sizing: border-box;
|
||||||
}
|
border: 1px solid white;
|
||||||
50% {
|
}
|
||||||
transform: translateX(-6px);
|
|
||||||
}
|
/* General Classes */
|
||||||
70% {
|
.active {
|
||||||
transform: translateX(+4px);
|
border-color: darkorange !important;
|
||||||
}
|
}
|
||||||
90% {
|
|
||||||
transform: translateX(-2px);
|
.list-card {
|
||||||
}
|
display: flex;
|
||||||
100% {
|
margin: 0.5rem;
|
||||||
transform: translateX(0);
|
border: 1px solid darkcyan;
|
||||||
}
|
cursor: pointer;
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
width: calc(100% - 1rem);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card:hover {
|
||||||
|
border: 1px solid darkorange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card form {
|
||||||
|
padding: 4px 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card input {
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
border: 1px solid darkcyan;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 3px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card input:focus {
|
||||||
|
border: 1px solid darkorange;
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card input[type='submit']:hover {
|
||||||
|
border: 1px solid darkorange;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-left: 1px solid darkcyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-box {
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
justify-self: center;
|
||||||
|
align-self: center;
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
border: 1px solid darkcyan;
|
||||||
|
color: darkcyan;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
display: block;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke: darkcyan;
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-box:hover .checkmark {
|
||||||
|
stroke: darkorange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-box:hover {
|
||||||
|
color: darkorange;
|
||||||
|
border-color: darkorange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empire Classes */
|
||||||
|
.empires-table {
|
||||||
|
margin: 0.5rem;
|
||||||
|
height: calc(100% - 1rem);
|
||||||
|
overflow: hidden;
|
||||||
|
border: 2px solid blueviolet;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-headers {
|
||||||
|
grid-area: empires-header;
|
||||||
|
display: flex;
|
||||||
|
height: 2.3rem;
|
||||||
|
width: calc(100%);
|
||||||
|
padding-right: 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid orange;
|
||||||
|
justify-items: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: center;
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
border-left: 1px solid orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header:first-child {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-content {
|
||||||
|
grid-area: empires-list;
|
||||||
|
height: calc(100% - 2.3rem);
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card:hover .card-content {
|
||||||
|
border-left: 1px solid darkorange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card:hover .card-content:first-child {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content:first-child {
|
||||||
|
border-left: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
30
src/routes/admin/+page.ts
Normal file
30
src/routes/admin/+page.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { apiBaseUrl } from "$lib/components/consts";
|
||||||
|
import AdminSelectedEmpireStore from "$lib/stores/admin-page/EmpireStore";
|
||||||
|
import AdminSelectedGameStore from "$lib/stores/admin-page/GameStore";
|
||||||
|
import type { ChellarisGameInfo } from "$lib/types/chellaris";
|
||||||
|
|
||||||
|
export async function load ({ fetch }) {
|
||||||
|
const gameList: { [key: number]: ChellarisGameInfo } = await (await fetch(apiBaseUrl + "/v3/list_games")).json();
|
||||||
|
|
||||||
|
let store: string | null;
|
||||||
|
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
// Game Selection
|
||||||
|
store = localStorage.getItem('adminGameSelection');
|
||||||
|
if (typeof store === 'string') {
|
||||||
|
AdminSelectedGameStore.set(JSON.parse(store));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empire Selection
|
||||||
|
store = localStorage.getItem('adminEmpireSelection');
|
||||||
|
|
||||||
|
if (typeof store === 'string' && store != "\"\"") {
|
||||||
|
AdminSelectedEmpireStore.set(JSON.parse(store));
|
||||||
|
}
|
||||||
|
else if (typeof store === 'string') {
|
||||||
|
AdminSelectedEmpireStore.set({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { games: gameList };
|
||||||
|
}
|
43
src/routes/admin/List.svelte
Normal file
43
src/routes/admin/List.svelte
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export let area = '';
|
||||||
|
export let listTitle = 'List';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container" style={'--area:' + area}>
|
||||||
|
<div class="header">
|
||||||
|
{listTitle}
|
||||||
|
</div>
|
||||||
|
<div class="list">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
grid-area: var(--area);
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas:
|
||||||
|
'list-header'
|
||||||
|
'list-content';
|
||||||
|
grid-template-rows: 2rem calc(100% - 2rem);
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
min-height: none;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
grid-area: list-header;
|
||||||
|
line-height: 2;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
grid-area: list-content;
|
||||||
|
min-height: none;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--color-text);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -44,19 +44,16 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.app {
|
.app {
|
||||||
display: flex;
|
grid-area: app;
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas:
|
||||||
|
'sub-header'
|
||||||
|
'content';
|
||||||
|
grid-template-rows: 3rem 1fr;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
flex: 1;
|
grid-area: content;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 2rem;
|
|
||||||
width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -23,83 +23,21 @@
|
||||||
<LoadingSpinner size="60" />
|
<LoadingSpinner size="60" />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="fullscreen-margin">
|
<div class="fullscreen-margin">
|
||||||
<div class="half-vertical">
|
<EthicsWeb {data} />
|
||||||
<div class="two-thirds-horizontal">
|
<EthicsBar {data} />
|
||||||
<EthicsWeb {data} />
|
<EmpireStats {data} />
|
||||||
</div>
|
<PopsPie {data} />
|
||||||
<div class="one-third-horizontal">
|
|
||||||
<EthicsBar {data} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="half-vertical">
|
|
||||||
<div class="half-horizontal">
|
|
||||||
<EmpireStats {data} />
|
|
||||||
</div>
|
|
||||||
<div class="half-horizontal">
|
|
||||||
<PopsPie {data} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.fullscreen-margin {
|
.fullscreen-margin {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: row;
|
grid-template-areas:
|
||||||
height: 90vh;
|
'uple upri'
|
||||||
flex-grow: 1;
|
'lole lori';
|
||||||
padding: 2rem;
|
grid-template-columns: 50% 50%;
|
||||||
}
|
grid-template-rows: 50% 50%;
|
||||||
|
grid-area: app;
|
||||||
.half-vertical {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
width: 50%;
|
|
||||||
max-width: 50%;
|
|
||||||
|
|
||||||
min-height: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.half-horizontal {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
min-height: 50%;
|
|
||||||
max-height: 50%;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.one-third-horizontal {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
min-height: 34%;
|
|
||||||
max-height: 34%;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.two-thirds-horizontal {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
min-height: 66%;
|
|
||||||
max-height: 66%;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -14,16 +14,8 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.chart {
|
.chart {
|
||||||
display: flex;
|
grid-area: upri;
|
||||||
flex-direction: column;
|
padding: 2rem;
|
||||||
|
place-self: center;
|
||||||
min-width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
min-height: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -61,6 +61,8 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
animation: {
|
animation: {
|
||||||
duration: 0
|
duration: 0
|
||||||
},
|
},
|
||||||
|
@ -100,16 +102,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.chart {
|
.chart {
|
||||||
display: flex;
|
grid-area: lole;
|
||||||
flex-direction: column;
|
padding: 2rem;
|
||||||
|
|
||||||
min-width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
min-height: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -96,6 +96,8 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
animation: {
|
animation: {
|
||||||
duration: 0
|
duration: 0
|
||||||
},
|
},
|
||||||
|
@ -157,29 +159,33 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="chart">
|
<div class="container">
|
||||||
<Radar data={chart_data} {options} />
|
<div class="chart">
|
||||||
|
<Radar data={chart_data} {options} />
|
||||||
|
</div>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" bind:checked={weighted} on:change={updateChartData} />
|
<input type="checkbox" bind:checked={weighted} on:change={updateChartData} />
|
||||||
Use weighted LegacyEthics
|
Use weighted LegacyEthics
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.chart {
|
.container {
|
||||||
|
grid-area: uple;
|
||||||
|
padding: 2rem;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
padding: 1rem;
|
gap: 1rem;
|
||||||
|
|
||||||
min-width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
min-height: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -42,6 +42,8 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
animation: {
|
animation: {
|
||||||
duration: 0
|
duration: 0
|
||||||
},
|
},
|
||||||
|
@ -86,16 +88,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.chart {
|
.chart {
|
||||||
display: flex;
|
grid-area: lori;
|
||||||
flex-direction: column;
|
padding: 2rem;
|
||||||
|
|
||||||
min-width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
min-height: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Reference in a new issue