Added rename of Game, Wall and Category

This commit is contained in:
2026-01-02 12:35:44 +01:00
parent 7be5921ef6
commit 5568a5bb99
5 changed files with 284 additions and 60 deletions

49
src/lib/Button.svelte Normal file
View File

@@ -0,0 +1,49 @@
<script lang="ts">
import type { Snippet } from "svelte";
interface Props {
onclick?: (
event: MouseEvent & {
currentTarget: EventTarget & HTMLButtonElement;
}
) => void;
children: Snippet;
class?: string;
}
let { onclick, children, class: classes }: Props = $props();
</script>
<!-- svelte-ignore a11y_consider_explicit_label -->
<button
type="button"
class="btn {classes}"
onclick={(event) => {
if (onclick) onclick(event);
}}>{@render children()}</button
>
<style>
.btn {
border: 1px solid black;
border-radius: 5px;
padding: 4px;
cursor: pointer;
height: fit-content;
}
.btn:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.btn:disabled {
color: grey !important;
border-color: gray !important;
cursor: unset !important;
}
.btn:disabled:hover {
background-color: unset !important;
cursor: unset !important;
}
</style>

View File

@@ -1,12 +1,28 @@
<script lang="ts">
interface Props {
value: string;
label?: string;
}
let { value = $bindable() }: Props = $props();
let { value = $bindable(), label }: Props = $props();
const id = crypto.randomUUID();
</script>
<input type="text" name="textfield" class="borders mt-2 mb-2 w-full" bind:value />
<div>
{#if label}
<label for="textfield-{id}" class="">{label}</label>
{/if}
<div>
<input
type="text"
name="textfield"
id="textfield-{id}"
class="borders mt-2 mb-2 w-full"
bind:value
/>
</div>
</div>
<style>
.borders {

View File

@@ -11,24 +11,33 @@
import { url } from "./util";
import { onMount } from "svelte";
import type { FullWall } from "./games/games";
import Button from "./Button.svelte";
import Modal from "./Modal.svelte";
import Textfield from "./Textfield.svelte";
interface Props {
wall: Wall | FullWall | undefined;
onclick?: (catIndex: number, questionIndex: number) => unknown;
onclickIds?: (catId: CategoryId, questionId: QuestionId) => unknown;
visited: VisitedQuestions;
[key: string]: unknown;
isEditor?: boolean;
}
function isVisited(catIndex: number, queIndex: number): boolean {
return visited[catIndex] && visited[catIndex].includes(queIndex);
}
let { wall, onclick, onclickIds, visited }: Props = $props();
let { wall, onclick, onclickIds, visited, isEditor = false }: Props = $props();
let categories: Category[] = $state([]);
async function fetchCategories(wall: Wall) {
let showRenameCategory = $state(false);
let catToRename: Category | undefined = $state();
let newCatName = $state("");
let error = $state("");
async function fetchCategories(wall: Wall | FullWall | undefined) {
if (wall && isWall(wall)) {
let cats: Promise<Category>[] = [];
for (const catId of wall.categories) {
cats.push(
@@ -45,9 +54,47 @@
categories = cats;
});
}
}
async function renameCategory() {
if (!newCatName || !catToRename) return false;
return axios
.post(
url(`/category/rename`),
{
categoryid: catToRename._id,
name: newCatName
},
{ withCredentials: true }
)
.then((response) => {
if (response.status === 200) {
newCatName = "";
catToRename = undefined;
fetchCategories(wall);
return true;
} else {
console.error(`Failed to rename category: ${response.status}`);
return false;
}
})
.catch((err) => {
console.error(err);
return false;
});
}
function renameCategoryCancel() {
newCatName = "";
catToRename = undefined;
}
$effect(() => {
fetchCategories(wall);
});
onMount(() => {
if (wall && isWall(wall)) fetchCategories(wall);
fetchCategories(wall);
});
</script>
@@ -55,8 +102,17 @@
<div class="grid h-full grow grid-flow-col grid-cols-5 grid-rows-6 gap-4 pb-4">
{#if isWall(wall)}
{#each categories as category, catIndex}
<div class="flex items-center justify-center text-3xl font-semibold">
<div class="flex items-center justify-center gap-2 text-3xl font-semibold">
<div>{category.name}</div>
{#if isEditor}
<Button
onclick={() => {
catToRename = category;
newCatName = category.name;
showRenameCategory = true;
}}><i class="fa-solid fa-pen"></i></Button
>
{/if}
</div>
{#each category.questions as question, queIndex}
<!-- svelte-ignore a11y_click_events_have_key_events -->
@@ -103,6 +159,18 @@
<p>Wall is undefined</p>
{/if}
<Modal bind:showModal={showRenameCategory} okFn={renameCategory} cancelFn={renameCategoryCancel}>
{#snippet header()}
<h2 class="text-3xl">Kategorie umbenennen</h2>
{/snippet}
<div>
<Textfield bind:value={newCatName} label="Name"></Textfield>
{#if error.length > 0}
<div class="text-red-700">{error}</div>
{/if}
</div>
</Modal>
<style>
.visited {
background-color: gray;

View File

@@ -1,6 +1,8 @@
<script lang="ts">
import { goto } from "$app/navigation";
import Button from "$lib/Button.svelte";
import Modal from "$lib/Modal.svelte";
import Textfield from "$lib/Textfield.svelte";
import type { Game } from "$lib/Types";
import { url } from "$lib/util";
import axios from "axios";
@@ -11,7 +13,9 @@
let error = $state("");
let showNewGame = $state(false);
let showRenameGame = $state(false);
let newGameName = $state("");
let gameToRename: Game | undefined = $state();
let showDeleteGame = $state(false);
let gameToDelete: Game | undefined = $state();
@@ -19,7 +23,7 @@
async function addNewGame() {
if (!newGameName) return false;
return axios
.post(url("game"), { name: newGameName }, { withCredentials: true })
.post(url("/game"), { name: newGameName }, { withCredentials: true })
.then((response) => {
if (response.status === 200) {
fetchGames();
@@ -33,6 +37,39 @@
newGameName = "";
}
async function renameGame() {
if (!newGameName || !gameToRename) return false;
return axios
.post(
url(`/game/rename`),
{
gameid: gameToRename._id,
name: newGameName
},
{ withCredentials: true }
)
.then((response) => {
if (response.status === 200) {
newGameName = "";
gameToRename = undefined;
fetchGames();
return true;
} else {
console.error(`Failed to rename game: ${response.status}`);
return false;
}
})
.catch((err) => {
console.error(err);
return false;
});
}
function renameGameCancel() {
newGameName = "";
gameToRename = undefined;
}
async function deleteGame() {
if (gameToDelete === undefined) return false;
return axios
@@ -59,7 +96,7 @@
function fetchGames() {
axios
.get(url("games"), { withCredentials: true })
.get(url("/games"), { withCredentials: true })
.then((response) => {
if (response.status === 200) {
games = response.data;
@@ -93,7 +130,7 @@
<!-- 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="ms-4 me-4 flex items-center gap-2 rounded-xl border-2 p-2 hover:cursor-pointer hover:bg-emerald-200"
onclick={() => {
goto(`/editor/${game._id}`);
}}
@@ -101,15 +138,22 @@
<div>
{game.name}
</div>
<!-- svelte-ignore a11y_consider_explicit_label -->
<button
type="button"
class="btn border-red-600 text-red-600"
<div class="grow"></div>
<Button
onclick={(event) => {
event.stopPropagation();
newGameName = game.name;
gameToRename = game;
showRenameGame = true;
}}><i class="fa-solid fa-pen"></i></Button
>
<Button
class="border-red-600 text-red-600"
onclick={(event) => {
event.stopPropagation();
gameToDelete = game;
showDeleteGame = true;
}}><i class="fa-solid fa-trash"></i></button
}}><i class="fa-solid fa-trash"></i></Button
>
</div>
{/each}
@@ -127,16 +171,19 @@
<h2 class="text-3xl">Neues Spiel</h2>
{/snippet}
<div>
<label for="directory" class="">Name</label>
<div>
<input
type="text"
name="directory"
id="directory"
class="borders mt-2 mb-2 w-full"
bind:value={newGameName}
/>
<Textfield bind:value={newGameName} label="Name"></Textfield>
{#if error.length > 0}
<div class="text-red-700">{error}</div>
{/if}
</div>
</Modal>
<Modal bind:showModal={showRenameGame} okFn={renameGame} cancelFn={renameGameCancel}>
{#snippet header()}
<h2 class="text-3xl">Spiel umbenennen</h2>
{/snippet}
<div>
<Textfield bind:value={newGameName} label="Name"></Textfield>
{#if error.length > 0}
<div class="text-red-700">{error}</div>
{/if}
@@ -153,12 +200,3 @@
<div class="text-red-700">{error}</div>
{/if}
</Modal>
<style>
.borders {
border: 1px solid black;
border-radius: 5px;
display: flex;
padding: 2px;
}
</style>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { page } from "$app/state";
import Button from "$lib/Button.svelte";
import Modal from "$lib/Modal.svelte";
import Textfield from "$lib/Textfield.svelte";
import type { Game, Wall as WallType } from "$lib/Types";
@@ -15,7 +16,9 @@
let selectedWall: WallType | undefined = $state();
let showNewWall = $state(false);
let showRenameWall = $state(false);
let newWallName = $state("");
let walltoRename: WallType | undefined = $state();
let showDeleteWall = $state(false);
let wallToDelete: WallType | undefined = $state();
@@ -79,6 +82,39 @@
newWallName = "";
}
async function renameWall() {
if (!newWallName || !walltoRename) return false;
return axios
.post(
url(`/wall/rename`),
{
wallid: walltoRename._id,
name: newWallName
},
{ withCredentials: true }
)
.then((response) => {
if (response.status === 200) {
newWallName = "";
walltoRename = undefined;
fetchWalls();
return true;
} else {
console.error(`Failed to rename wall: ${response.status}`);
return false;
}
})
.catch((err) => {
console.error(err);
return false;
});
}
function renameWallCancel() {
newWallName = "";
walltoRename = undefined;
}
async function deleteWall() {
if (!wallToDelete) return false;
if (wallToDelete._id === selectedWall?._id) selectedWall = undefined;
@@ -116,15 +152,15 @@
<!-- 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
<Button class="ms-4 me-4" 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="ms-4 me-4 flex items-center gap-2 rounded-xl border-2 p-2 hover:cursor-pointer hover:bg-emerald-200"
class:bg-emerald-200={selectedWall?._id === wall._id}
onclick={() => {
selectedWall = wall;
@@ -133,15 +169,22 @@
<div>
{wall.name}
</div>
<!-- svelte-ignore a11y_consider_explicit_label -->
<button
type="button"
class="btn border-red-600 text-red-600"
<div class="grow"></div>
<Button
onclick={(event) => {
event.stopPropagation();
newWallName = wall.name;
walltoRename = wall;
showRenameWall = true;
}}><i class="fa-solid fa-pen"></i></Button
>
<Button
class="border-red-600 text-red-600"
onclick={(event) => {
event.stopPropagation();
wallToDelete = wall;
showDeleteWall = true;
}}><i class="fa-solid fa-trash"></i></button
}}><i class="fa-solid fa-trash"></i></Button
>
</div>
{/each}
@@ -150,6 +193,7 @@
{#if selectedWall}
<div class="ms-4 me-4 grow">
<Wall
isEditor
wall={selectedWall}
visited={[]}
onclickIds={(catId, queId) => {
@@ -170,10 +214,19 @@
<h2 class="text-3xl">Neue Wand</h2>
{/snippet}
<div>
<label for="directory" class="">Name</label>
<div>
<Textfield bind:value={newWallName}></Textfield>
<Textfield bind:value={newWallName} label="Name"></Textfield>
{#if error.length > 0}
<div class="text-red-700">{error}</div>
{/if}
</div>
</Modal>
<Modal bind:showModal={showRenameWall} okFn={renameWall} cancelFn={renameWallCancel}>
{#snippet header()}
<h2 class="text-3xl">Wand umbenennen</h2>
{/snippet}
<div>
<Textfield bind:value={newWallName} label="Name"></Textfield>
{#if error.length > 0}
<div class="text-red-700">{error}</div>
{/if}