Added Editor Game and Wall Display

This commit is contained in:
2026-01-02 02:59:40 +01:00
parent dc2766f0ef
commit 7be5921ef6
9 changed files with 372 additions and 38 deletions

View File

@@ -6,10 +6,10 @@
isMultipleChoiceQuestion,
isSimpleQuestion,
isImageQuestion,
type Game,
isAudioQuestion,
isAudioMultipleChoiceQuestion,
isImageMultipleChoiceQuestion
isImageMultipleChoiceQuestion,
type FullGame
} from "$lib/games/games";
import ws from "$lib/websocket.svelte";
import { page } from "$app/state";
@@ -57,7 +57,7 @@
class GameManager {
public state: GameState = $state(GameState.INIT);
public game: Game;
public game: FullGame;
public players: Player[] = $state([
{
name: "Player 1",
@@ -83,7 +83,7 @@
public questionIsShowing = $state(false);
public isBuzzed = $state(false);
constructor(game: Game) {
constructor(game: FullGame) {
this.game = game;
}

View File

@@ -79,11 +79,13 @@
</script>
<div class="flex h-full flex-col">
<div class="flex items-center gap-4">
<div class="mr-4 flex items-center gap-4">
<h1 class="m-4 mb-8 text-7xl font-bold">Editor</h1>
<button class="btn" type="button" onclick={() => (showNewGame = true)}
><i class="fa-solid fa-plus"></i> Neues Spiel</button
>
<div class="grow"></div>
<button class="btn" type="button" onclick={() => goto("/")}>Zurück</button>
</div>
{#if games.length > 0}
<div class="flex flex-col space-y-4 overflow-y-auto">

View File

@@ -0,0 +1,192 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { page } from "$app/state";
import Modal from "$lib/Modal.svelte";
import Textfield from "$lib/Textfield.svelte";
import type { Game, Wall as WallType } from "$lib/Types";
import { url } from "$lib/util";
import Wall from "$lib/Wall.svelte";
import axios from "axios";
import { onMount } from "svelte";
let game: Game | undefined = $state();
let walls: WallType[] = $state([]);
let selectedWall: WallType | undefined = $state();
let showNewWall = $state(false);
let newWallName = $state("");
let showDeleteWall = $state(false);
let wallToDelete: WallType | undefined = $state();
let error = $state("");
function fetchGame() {
return axios
.get(url(`/game?id=${page.params.gameid}`), { withCredentials: true })
.then((response) => {
if (response.status === 200) {
game = response.data;
} else {
console.error(`Failed to fetch game: ${response.status}`);
}
})
.catch((err) => console.error(err));
}
function fetchWalls() {
return axios
.get(url(`/walls/${page.params.gameid}`), { withCredentials: true })
.then((response) => {
if (response.status === 200) {
walls = response.data;
if (selectedWall === undefined && walls.length > 0) selectedWall = walls[0];
}
})
.catch((err) => {
console.error(err);
});
}
async function addNewWall() {
if (!newWallName) return false;
return axios
.post(
url(`/wall`),
{
gameid: page.params.gameid,
name: newWallName
},
{
withCredentials: true
}
)
.then((response) => {
if (response.status === 200) {
newWallName = "";
fetchWalls();
return true;
} else return false;
})
.catch((err) => {
console.error(err);
return false;
});
}
function addNewWallCancel() {
newWallName = "";
}
async function deleteWall() {
if (!wallToDelete) return false;
if (wallToDelete._id === selectedWall?._id) selectedWall = undefined;
return axios
.delete(url(`/wall/${wallToDelete._id}`), { withCredentials: true })
.then((response) => {
if (response.status === 200) {
wallToDelete = undefined;
fetchWalls();
return true;
} else return false;
})
.catch((err) => {
console.error(err);
return false;
});
}
function deleteWallCancel() {
wallToDelete = undefined;
}
onMount(() => {
fetchGame().then(() => fetchWalls());
});
</script>
<div class="flex h-full flex-col">
<div class="mr-4 flex items-center gap-4">
<h1 class="m-4 text-4xl font-bold">{game ? game.name : "Spiel"}</h1>
<div class="grow"></div>
<button class="btn" type="button" onclick={() => goto("/editor")}>Zurück</button>
</div>
<div class="flex grow">
<!-- Sidebar -->
<div class="flex h-full max-w-[600px] min-w-[400px] flex-col gap-4 border-r-1">
<div>
<button class="btn ms-4 me-4" type="button" onclick={() => (showNewWall = true)}
>Wand hinzufügen</button
>
</div>
{#each walls as wall}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="ms-4 me-4 flex items-center justify-between rounded-xl border-2 p-2 hover:cursor-pointer hover:bg-emerald-200"
class:bg-emerald-200={selectedWall?._id === wall._id}
onclick={() => {
selectedWall = wall;
}}
>
<div>
{wall.name}
</div>
<!-- svelte-ignore a11y_consider_explicit_label -->
<button
type="button"
class="btn border-red-600 text-red-600"
onclick={(event) => {
event.stopPropagation();
wallToDelete = wall;
showDeleteWall = true;
}}><i class="fa-solid fa-trash"></i></button
>
</div>
{/each}
</div>
<!-- Wall -->
{#if selectedWall}
<div class="ms-4 me-4 grow">
<Wall
wall={selectedWall}
visited={[]}
onclickIds={(catId, queId) => {
console.log(catId, queId);
if (selectedWall)
goto(
`/editor/${page.params.gameid}/${selectedWall._id}/${catId}/${queId}`
);
}}
></Wall>
</div>
{/if}
</div>
</div>
<Modal bind:showModal={showNewWall} okFn={addNewWall} cancelFn={addNewWallCancel}>
{#snippet header()}
<h2 class="text-3xl">Neue Wand</h2>
{/snippet}
<div>
<label for="directory" class="">Name</label>
<div>
<Textfield bind:value={newWallName}></Textfield>
</div>
{#if error.length > 0}
<div class="text-red-700">{error}</div>
{/if}
</div>
</Modal>
<Modal bind:showModal={showDeleteWall} okFn={deleteWall} cancelFn={deleteWallCancel}>
{#snippet header()}
<h2 class="text-3xl">Wand löschen</h2>
{/snippet}
<div>Soll die Wand {wallToDelete?.name} wirklich gelöscht werden?</div>
{#if error.length > 0}
<div class="text-red-700">{error}</div>
{/if}
</Modal>

View File

@@ -0,0 +1,15 @@
<script lang="ts">
import type { SimpleQuestion } from "$lib/games/games";
import Textfield from "$lib/Textfield.svelte";
interface Props {
question: SimpleQuestion;
}
let { question }: Props = $props();
</script>
<div>
<Textfield bind:value={question.data.question}></Textfield>
<Textfield bind:value={question.data.answer}></Textfield>
</div>