Added Editor Game and Wall Display
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import type { Game, Wall } from "./games/games";
|
import type { FullGame, FullWall } from "./games/games";
|
||||||
|
|
||||||
interface Player {
|
interface Player {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -9,9 +9,9 @@ const baseRoute = "/connected/display/running";
|
|||||||
let players: Player[] = $state([]);
|
let players: Player[] = $state([]);
|
||||||
let currentPlayer: string = $state("");
|
let currentPlayer: string = $state("");
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
let game: Game | undefined = undefined;
|
let game: FullGame | undefined = undefined;
|
||||||
let gameIndex: number = -1;
|
let gameIndex: number = -1;
|
||||||
let wall: Wall | undefined = undefined;
|
let wall: FullWall | undefined = undefined;
|
||||||
let wallIndex: number = -1;
|
let wallIndex: number = -1;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -29,7 +29,7 @@ export default {
|
|||||||
currentPlayer = str;
|
currentPlayer = str;
|
||||||
},
|
},
|
||||||
baseRoute,
|
baseRoute,
|
||||||
get game(): Game | undefined {
|
get game(): FullGame | undefined {
|
||||||
return game;
|
return game;
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -42,7 +42,7 @@ export default {
|
|||||||
set gameIndex(i: number) {
|
set gameIndex(i: number) {
|
||||||
gameIndex = i;
|
gameIndex = i;
|
||||||
},
|
},
|
||||||
get wall(): Wall | undefined {
|
get wall(): FullWall | undefined {
|
||||||
return wall;
|
return wall;
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|||||||
18
src/lib/Textfield.svelte
Normal file
18
src/lib/Textfield.svelte
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { value = $bindable() }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input type="text" name="textfield" class="borders mt-2 mb-2 w-full" bind:value />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.borders {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: flex;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
import type {
|
||||||
|
AudioMultipleChoiceQuestion,
|
||||||
|
AudioQuestion,
|
||||||
|
FullWall,
|
||||||
|
ImageMultipleChoiceQuestion,
|
||||||
|
ImageQuestion,
|
||||||
|
MultipleChoiceQuestion,
|
||||||
|
SimpleQuestion
|
||||||
|
} from "./games/games";
|
||||||
|
|
||||||
export type VisitedQuestions = number[][];
|
export type VisitedQuestions = number[][];
|
||||||
|
|
||||||
export type Directory = {
|
export type Directory = {
|
||||||
@@ -23,13 +33,49 @@ export function isRessource(ressource: Ressource | Directory): ressource is Ress
|
|||||||
return (ressource as Directory).isDir === undefined;
|
return (ressource as Directory).isDir === undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GameId = string;
|
export type _id = string;
|
||||||
|
|
||||||
|
export type GameId = _id;
|
||||||
|
|
||||||
export type Game = {
|
export type Game = {
|
||||||
name: string;
|
name: string;
|
||||||
owner: string;
|
owner: _id;
|
||||||
_id: GameId;
|
_id: GameId;
|
||||||
walls: WallId[];
|
walls: WallId[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WallId = string;
|
export type WallId = _id;
|
||||||
|
|
||||||
|
export type Wall = {
|
||||||
|
_id: WallId;
|
||||||
|
name: string;
|
||||||
|
owner: _id;
|
||||||
|
categories: CategoryId[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isWall(wall: Wall | FullWall): wall is Wall {
|
||||||
|
return (wall as Wall)._id !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFullWall(wall: Wall | FullWall): wall is FullWall {
|
||||||
|
return !Object.hasOwn(wall, "_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CategoryId = _id;
|
||||||
|
|
||||||
|
export type Category = {
|
||||||
|
_id: CategoryId;
|
||||||
|
name: string;
|
||||||
|
owner: _id;
|
||||||
|
questions: { _id: QuestionId; points: number }[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type QuestionId = _id;
|
||||||
|
|
||||||
|
export type GeneralQuestion =
|
||||||
|
| SimpleQuestion
|
||||||
|
| MultipleChoiceQuestion
|
||||||
|
| ImageQuestion
|
||||||
|
| ImageMultipleChoiceQuestion
|
||||||
|
| AudioQuestion
|
||||||
|
| AudioMultipleChoiceQuestion;
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Wall } from "$lib/games/games";
|
import axios from "axios";
|
||||||
import type { VisitedQuestions } from "./Types";
|
import {
|
||||||
|
isWall,
|
||||||
|
type Category,
|
||||||
|
type CategoryId,
|
||||||
|
type QuestionId,
|
||||||
|
type VisitedQuestions,
|
||||||
|
type Wall
|
||||||
|
} from "./Types";
|
||||||
|
import { url } from "./util";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import type { FullWall } from "./games/games";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
wall: Wall | undefined;
|
wall: Wall | FullWall | undefined;
|
||||||
onclick?: (catIndex: number, questionIndex: number) => unknown;
|
onclick?: (catIndex: number, questionIndex: number) => unknown;
|
||||||
|
onclickIds?: (catId: CategoryId, questionId: QuestionId) => unknown;
|
||||||
visited: VisitedQuestions;
|
visited: VisitedQuestions;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
@@ -13,30 +24,80 @@
|
|||||||
return visited[catIndex] && visited[catIndex].includes(queIndex);
|
return visited[catIndex] && visited[catIndex].includes(queIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
let { wall, onclick, visited }: Props = $props();
|
let { wall, onclick, onclickIds, visited }: Props = $props();
|
||||||
|
|
||||||
|
let categories: Category[] = $state([]);
|
||||||
|
|
||||||
|
async function fetchCategories(wall: Wall) {
|
||||||
|
let cats: Promise<Category>[] = [];
|
||||||
|
for (const catId of wall.categories) {
|
||||||
|
cats.push(
|
||||||
|
axios
|
||||||
|
.get(url(`/category?id=${catId}`), { withCredentials: true })
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
return response.data;
|
||||||
|
} else throw "Failed to fetch: " + response.status;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Promise.all(cats).then((cats) => {
|
||||||
|
categories = cats;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (wall && isWall(wall)) fetchCategories(wall);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if wall != undefined}
|
{#if wall != undefined}
|
||||||
<div class="grid h-full grow grid-flow-col grid-cols-5 grid-rows-6 gap-4 pb-4">
|
<div class="grid h-full grow grid-flow-col grid-cols-5 grid-rows-6 gap-4 pb-4">
|
||||||
{#each wall.categories as category, catIndex}
|
{#if isWall(wall)}
|
||||||
<div class="flex items-center justify-center text-3xl font-semibold">
|
{#each categories as category, catIndex}
|
||||||
<div>{category.name}</div>
|
<div class="flex items-center justify-center text-3xl font-semibold">
|
||||||
</div>
|
<div>{category.name}</div>
|
||||||
{#each category.questions as question, queIndex}
|
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
||||||
<div
|
|
||||||
class="card {isVisited(catIndex, queIndex) ? 'visited' : ''}"
|
|
||||||
role="button"
|
|
||||||
aria-pressed="false"
|
|
||||||
tabindex="0"
|
|
||||||
onclick={() => {
|
|
||||||
if (onclick) onclick(catIndex, queIndex);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="text-6xl font-thin">{question.points}</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
{#each category.questions as question, queIndex}
|
||||||
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
|
<div
|
||||||
|
class="card {isVisited(catIndex, queIndex) ? 'visited' : ''}"
|
||||||
|
role="button"
|
||||||
|
aria-pressed="false"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={() => {
|
||||||
|
if (onclickIds) onclickIds(category._id, question._id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="text-6xl font-thin">
|
||||||
|
{question.points >= 0 ? question.points : "???"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
{:else}
|
||||||
|
{#each wall.categories as category, catIndex}
|
||||||
|
<div class="flex items-center justify-center text-3xl font-semibold">
|
||||||
|
<div>{category.name}</div>
|
||||||
|
</div>
|
||||||
|
{#each category.questions as question, queIndex}
|
||||||
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
|
<div
|
||||||
|
class="card {isVisited(catIndex, queIndex) ? 'visited' : ''}"
|
||||||
|
role="button"
|
||||||
|
aria-pressed="false"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={() => {
|
||||||
|
if (onclick) onclick(catIndex, queIndex);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="text-6xl font-thin">
|
||||||
|
{question.points >= 0 ? question.points : "???"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p>Wall is undefined</p>
|
<p>Wall is undefined</p>
|
||||||
|
|||||||
@@ -1425,16 +1425,16 @@ export type Category = {
|
|||||||
)[];
|
)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Wall = {
|
export type FullWall = {
|
||||||
name: string;
|
name: string;
|
||||||
categories: Category[];
|
categories: Category[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Game = {
|
export type FullGame = {
|
||||||
name: string;
|
name: string;
|
||||||
walls: Wall[];
|
walls: FullWall[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Games = Game[];
|
export type Games = FullGame[];
|
||||||
|
|
||||||
export default games;
|
export default games;
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
isMultipleChoiceQuestion,
|
isMultipleChoiceQuestion,
|
||||||
isSimpleQuestion,
|
isSimpleQuestion,
|
||||||
isImageQuestion,
|
isImageQuestion,
|
||||||
type Game,
|
|
||||||
isAudioQuestion,
|
isAudioQuestion,
|
||||||
isAudioMultipleChoiceQuestion,
|
isAudioMultipleChoiceQuestion,
|
||||||
isImageMultipleChoiceQuestion
|
isImageMultipleChoiceQuestion,
|
||||||
|
type FullGame
|
||||||
} from "$lib/games/games";
|
} from "$lib/games/games";
|
||||||
import ws from "$lib/websocket.svelte";
|
import ws from "$lib/websocket.svelte";
|
||||||
import { page } from "$app/state";
|
import { page } from "$app/state";
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
class GameManager {
|
class GameManager {
|
||||||
public state: GameState = $state(GameState.INIT);
|
public state: GameState = $state(GameState.INIT);
|
||||||
public game: Game;
|
public game: FullGame;
|
||||||
public players: Player[] = $state([
|
public players: Player[] = $state([
|
||||||
{
|
{
|
||||||
name: "Player 1",
|
name: "Player 1",
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
public questionIsShowing = $state(false);
|
public questionIsShowing = $state(false);
|
||||||
public isBuzzed = $state(false);
|
public isBuzzed = $state(false);
|
||||||
|
|
||||||
constructor(game: Game) {
|
constructor(game: FullGame) {
|
||||||
this.game = game;
|
this.game = game;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,11 +79,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-full flex-col">
|
<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>
|
<h1 class="m-4 mb-8 text-7xl font-bold">Editor</h1>
|
||||||
<button class="btn" type="button" onclick={() => (showNewGame = true)}
|
<button class="btn" type="button" onclick={() => (showNewGame = true)}
|
||||||
><i class="fa-solid fa-plus"></i> Neues Spiel</button
|
><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>
|
</div>
|
||||||
{#if games.length > 0}
|
{#if games.length > 0}
|
||||||
<div class="flex flex-col space-y-4 overflow-y-auto">
|
<div class="flex flex-col space-y-4 overflow-y-auto">
|
||||||
|
|||||||
192
src/routes/editor/[gameid]/+page.svelte
Normal file
192
src/routes/editor/[gameid]/+page.svelte
Normal 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>
|
||||||
15
src/routes/editor/[gameid]/EditorSimpleQuestion.svelte
Normal file
15
src/routes/editor/[gameid]/EditorSimpleQuestion.svelte
Normal 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>
|
||||||
Reference in New Issue
Block a user