Added Editing of Questions
This commit is contained in:
12
src/routes/editor/EditorState.ts
Normal file
12
src/routes/editor/EditorState.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { WallId } from "$lib/Types";
|
||||
|
||||
let selectedWallId: WallId | undefined;
|
||||
|
||||
export default {
|
||||
get selectedWallId(): WallId | undefined {
|
||||
return selectedWallId;
|
||||
},
|
||||
set selectedWallId(id: WallId | undefined) {
|
||||
selectedWallId = id;
|
||||
}
|
||||
};
|
||||
@@ -4,11 +4,13 @@
|
||||
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";
|
||||
import type { Game, WallId, 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";
|
||||
import { fetchGame, fetchWalls } from "../fetchers";
|
||||
import EditorState from "../EditorState";
|
||||
|
||||
let game: Game | undefined = $state();
|
||||
|
||||
@@ -25,26 +27,20 @@
|
||||
|
||||
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}`);
|
||||
function _fetchWalls() {
|
||||
return fetchWalls(`${page.params.gameid}`)
|
||||
.then((fetchedWalls) => {
|
||||
walls = fetchedWalls;
|
||||
if (selectedWall === undefined) {
|
||||
if (EditorState.selectedWallId !== undefined) {
|
||||
for (const wall of walls) {
|
||||
if (wall._id === EditorState.selectedWallId) selectedWall = wall;
|
||||
}
|
||||
} else if (walls.length > 0) selectedWall = walls[0];
|
||||
}
|
||||
})
|
||||
.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];
|
||||
if (selectedWall) {
|
||||
EditorState.selectedWallId = selectedWall._id;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -68,7 +64,7 @@
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
newWallName = "";
|
||||
fetchWalls();
|
||||
_fetchWalls();
|
||||
return true;
|
||||
} else return false;
|
||||
})
|
||||
@@ -97,7 +93,7 @@
|
||||
if (response.status === 200) {
|
||||
newWallName = "";
|
||||
walltoRename = undefined;
|
||||
fetchWalls();
|
||||
_fetchWalls();
|
||||
return true;
|
||||
} else {
|
||||
console.error(`Failed to rename wall: ${response.status}`);
|
||||
@@ -117,13 +113,16 @@
|
||||
|
||||
async function deleteWall() {
|
||||
if (!wallToDelete) return false;
|
||||
if (wallToDelete._id === selectedWall?._id) selectedWall = undefined;
|
||||
if (wallToDelete._id === selectedWall?._id) {
|
||||
selectedWall = undefined;
|
||||
EditorState.selectedWallId = undefined;
|
||||
}
|
||||
return axios
|
||||
.delete(url(`/wall/${wallToDelete._id}`), { withCredentials: true })
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
wallToDelete = undefined;
|
||||
fetchWalls();
|
||||
_fetchWalls();
|
||||
return true;
|
||||
} else return false;
|
||||
})
|
||||
@@ -138,7 +137,15 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
fetchGame().then(() => fetchWalls());
|
||||
if (page.params.gameid)
|
||||
fetchGame(page.params.gameid)
|
||||
.then((fetchedGame) => {
|
||||
game = fetchedGame;
|
||||
return _fetchWalls();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -164,6 +171,7 @@
|
||||
class:bg-emerald-200={selectedWall?._id === wall._id}
|
||||
onclick={() => {
|
||||
selectedWall = wall;
|
||||
EditorState.selectedWallId = selectedWall._id;
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
isRessource,
|
||||
type Category,
|
||||
type Game,
|
||||
type GeneralQuestion,
|
||||
type Wall
|
||||
} from "$lib/Types";
|
||||
import { onMount, untrack } from "svelte";
|
||||
import { fetchCategory, fetchGame, fetchQuestion, fetchWall } from "../../../../fetchers";
|
||||
import { page } from "$app/state";
|
||||
import { goto } from "$app/navigation";
|
||||
import {
|
||||
isAudioMultipleChoiceQuestion,
|
||||
isAudioQuestion,
|
||||
isImageMultipleChoiceQuestion,
|
||||
isImageQuestion,
|
||||
isMultipleChoiceQuestion,
|
||||
isSimpleQuestion,
|
||||
type QuestionType
|
||||
} from "$lib/games/games";
|
||||
import SimpleQuestionComponent from "$lib/SimpleQuestionComponent.svelte";
|
||||
import { convert } from "./questionconverters";
|
||||
import MultipleChoiceQuestionComponent from "$lib/MultipleChoiceQuestionComponent.svelte";
|
||||
import ImageQuestionComponent from "$lib/ImageQuestionComponent.svelte";
|
||||
import ImageMultipleChoiceQuestionComponent from "$lib/ImageMultipleChoiceQuestionComponent.svelte";
|
||||
import AudioQuestionComponent from "$lib/AudioQuestionComponent.svelte";
|
||||
import AudioMultipleChoiceQuestionComponent from "$lib/AudioMultipleChoiceQuestionComponent.svelte";
|
||||
import Button from "$lib/Button.svelte";
|
||||
import Textfield from "$lib/Textfield.svelte";
|
||||
import EditorSimple from "./EditorSimple.svelte";
|
||||
import EditorImage from "./EditorImage.svelte";
|
||||
import EditorAudio from "./EditorAudio.svelte";
|
||||
import EditorMultipleChoice from "./EditorMultipleChoice.svelte";
|
||||
import EditorAudioMultipleChoice from "./EditorAudioMultipleChoice.svelte";
|
||||
import EditorImageMultipleChoice from "./EditorImageMultipleChoice.svelte";
|
||||
import axios, { type AxiosResponse } from "axios";
|
||||
import { url } from "$lib/util";
|
||||
|
||||
let init = true;
|
||||
let game: Game | undefined = $state();
|
||||
let wall: Wall | undefined = $state();
|
||||
let wallNumber = $derived.by(() => {
|
||||
if (game && wall) {
|
||||
return game.walls.indexOf(wall._id) + 1;
|
||||
} else return -1;
|
||||
});
|
||||
let category: Category | undefined = $state();
|
||||
let question: GeneralQuestion | undefined = $state();
|
||||
let questionNumer = $derived.by(() => {
|
||||
if (category && question) {
|
||||
for (let i = 0; i < category.questions.length; i++) {
|
||||
const q = category.questions[i];
|
||||
if (q._id === question._id) return i + 1;
|
||||
}
|
||||
return -1;
|
||||
} else return -1;
|
||||
});
|
||||
let questionType: QuestionType = $state("SIMPLE");
|
||||
|
||||
let showAnswer = $state(true);
|
||||
let saving = $state(false);
|
||||
|
||||
function save() {
|
||||
if (!question) return;
|
||||
saving = true;
|
||||
let q: GeneralQuestion | undefined;
|
||||
let promise: Promise<AxiosResponse> | undefined;
|
||||
if (question.type === "IMAGE" || question.type === "IMAGE_MULTIPLE_CHOICE") {
|
||||
promise = axios.post(
|
||||
url(`/question`),
|
||||
{
|
||||
...question,
|
||||
data: {
|
||||
...question.data,
|
||||
image: isRessource(question.data.image) ? question.data.image._id : null
|
||||
}
|
||||
},
|
||||
{ withCredentials: true }
|
||||
);
|
||||
} else if (question.type === "AUDIO" || question.type === "AUDIO_MULTIPLE_CHOICE") {
|
||||
promise = axios.post(
|
||||
url(`/question`),
|
||||
{
|
||||
...question,
|
||||
data: {
|
||||
...question.data,
|
||||
audio: isRessource(question.data.audio) ? question.data.audio._id : null
|
||||
}
|
||||
},
|
||||
{ withCredentials: true }
|
||||
);
|
||||
} else if (question.type === "SIMPLE" || question.type === "MULTIPLE_CHOICE") {
|
||||
promise = axios.post(url(`/question`), question, { withCredentials: true });
|
||||
}
|
||||
if (promise)
|
||||
promise
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.error("Failed to save: " + response.status);
|
||||
alert("Failed to save: " + response.status);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
alert(`Failed to save: ${err}`);
|
||||
})
|
||||
.finally(() => {
|
||||
saving = false;
|
||||
});
|
||||
else {
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (questionType) {
|
||||
if (init) {
|
||||
init = false;
|
||||
return;
|
||||
}
|
||||
console.log(questionType);
|
||||
untrack(() => {
|
||||
if (question) question = convert(question, questionType);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
let promises: Promise<unknown>[] = [];
|
||||
promises.push(
|
||||
fetchGame(`${page.params.gameid}`).then((fetchedGame) => {
|
||||
game = fetchedGame;
|
||||
})
|
||||
);
|
||||
promises.push(
|
||||
fetchWall(`${page.params.wallid}`).then((fetchedWall) => {
|
||||
wall = fetchedWall;
|
||||
})
|
||||
);
|
||||
promises.push(
|
||||
fetchCategory(`${page.params.categoryid}`).then((fetchedCategory) => {
|
||||
category = fetchedCategory;
|
||||
})
|
||||
);
|
||||
promises.push(
|
||||
fetchQuestion(`${page.params.questionid}`).then((fetchedQuestion) => {
|
||||
question = fetchedQuestion;
|
||||
questionType = question.type;
|
||||
})
|
||||
);
|
||||
|
||||
Promise.all(promises).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="m-4 flex items-center gap-2">
|
||||
<h1 class="text-4xl font-bold">{game ? game.name : "Spiel"}</h1>
|
||||
<!-- svelte-ignore a11y_missing_content -->
|
||||
<h1 class="text-4xl font-bold"><i class="fa-solid fa-angle-right"></i></h1>
|
||||
<h1 class="text-4xl font-bold">
|
||||
{wall
|
||||
? `${wall.name}
|
||||
${wallNumber > 0 ? `(Wand ${wallNumber})` : ""}`
|
||||
: "Wand"}
|
||||
</h1>
|
||||
<!-- svelte-ignore a11y_missing_content -->
|
||||
<h1 class="text-4xl font-bold"><i class="fa-solid fa-angle-right"></i></h1>
|
||||
<h1 class="text-4xl font-bold">{category ? category.name : "Kategorie"}</h1>
|
||||
<!-- svelte-ignore a11y_missing_content -->
|
||||
<h1 class="text-4xl font-bold"><i class="fa-solid fa-angle-right"></i></h1>
|
||||
<h1 class="text-4xl font-bold">{questionNumer ? `Frage ${questionNumer}` : "Frage ?"}</h1>
|
||||
<div class="grow"></div>
|
||||
<button class="btn" type="button" onclick={() => goto(`/editor/${page.params.gameid}`)}
|
||||
>Zurück</button
|
||||
>
|
||||
</div>
|
||||
{#if question}
|
||||
<div class="flex grow">
|
||||
<!-- Sidebar -->
|
||||
<div class="flex h-full max-w-[600px] min-w-[400px] flex-col gap-4 border-r-1">
|
||||
<div class="ms-4 me-4 flex justify-between gap-4">
|
||||
<Button
|
||||
onclick={() => {
|
||||
save();
|
||||
}}>{saving ? "Speichert..." : "Speichern"}</Button
|
||||
>
|
||||
<Button
|
||||
onclick={() => {
|
||||
showAnswer = !showAnswer;
|
||||
}}
|
||||
>
|
||||
{#if showAnswer}
|
||||
<div>
|
||||
<i class="fa-solid fa-exclamation"></i>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<i class="fa-solid fa-question"></i>
|
||||
</div>
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
<select
|
||||
name="QuestionType"
|
||||
id="QuestionType"
|
||||
bind:value={questionType}
|
||||
class="ms-4 me-4 rounded-md border-1 p-1"
|
||||
>
|
||||
<option value="SIMPLE">SIMPLE</option>
|
||||
<option value="MULTIPLE_CHOICE">MULTIPLE_CHOICE</option>
|
||||
<option value="IMAGE">IMAGE</option>
|
||||
<option value="IMAGE_MULTIPLE_CHOICE">IMAGE_MULTIPLE_CHOICE</option>
|
||||
<option value="AUDIO">AUDIO</option>
|
||||
<option value="AUDIO_MULTIPLE_CHOICE">AUDIO_MULTIPLE_CHOICE</option>
|
||||
</select>
|
||||
<div class="ms-4 me-4">
|
||||
<Textfield type="number" bind:value={question.points} label="Punkte"
|
||||
></Textfield>
|
||||
</div>
|
||||
<div class="ms-4 me-4 grow">
|
||||
{#if isSimpleQuestion(question)}
|
||||
<EditorSimple bind:question></EditorSimple>
|
||||
{:else if isMultipleChoiceQuestion(question)}
|
||||
<EditorMultipleChoice bind:question></EditorMultipleChoice>
|
||||
{:else if isImageQuestion(question)}
|
||||
<EditorImage bind:question></EditorImage>
|
||||
{:else if isImageMultipleChoiceQuestion(question)}
|
||||
<EditorImageMultipleChoice bind:question></EditorImageMultipleChoice>
|
||||
{:else if isAudioQuestion(question)}
|
||||
<EditorAudio bind:question></EditorAudio>
|
||||
{:else if isAudioMultipleChoiceQuestion(question)}
|
||||
<EditorAudioMultipleChoice bind:question></EditorAudioMultipleChoice>{/if}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Display -->
|
||||
<div class="ms-4 me-4 flex grow flex-col">
|
||||
{#if isSimpleQuestion(question)}
|
||||
<SimpleQuestionComponent {question} {showAnswer} showQuestion
|
||||
></SimpleQuestionComponent>
|
||||
{:else if isMultipleChoiceQuestion(question)}
|
||||
<MultipleChoiceQuestionComponent
|
||||
{question}
|
||||
{showAnswer}
|
||||
showQuestion
|
||||
randomize={false}
|
||||
></MultipleChoiceQuestionComponent>
|
||||
{:else if isImageQuestion(question)}
|
||||
<ImageQuestionComponent
|
||||
{question}
|
||||
{showAnswer}
|
||||
showQuestion
|
||||
isBuzzed={false}
|
||||
isLegacy={false}
|
||||
></ImageQuestionComponent>
|
||||
{:else if isImageMultipleChoiceQuestion(question)}
|
||||
<ImageMultipleChoiceQuestionComponent
|
||||
{question}
|
||||
{showAnswer}
|
||||
showQuestion
|
||||
isBuzzed={false}
|
||||
isLegacy={false}
|
||||
randomize={false}
|
||||
></ImageMultipleChoiceQuestionComponent>
|
||||
{:else if isAudioQuestion(question)}
|
||||
<AudioQuestionComponent
|
||||
{question}
|
||||
{showAnswer}
|
||||
showQuestion
|
||||
showPlayer
|
||||
isLegacy={false}
|
||||
></AudioQuestionComponent>
|
||||
{:else if isAudioMultipleChoiceQuestion(question)}
|
||||
<AudioMultipleChoiceQuestionComponent
|
||||
{question}
|
||||
{showAnswer}
|
||||
showQuestion
|
||||
showPlayer
|
||||
isLegacy={false}
|
||||
randomize={false}
|
||||
></AudioMultipleChoiceQuestionComponent>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
No question isFileLoadingAllowed, please try again
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { AudioQuestion } from "$lib/games/games";
|
||||
import EditorMediaField from "./EditorMediaField.svelte";
|
||||
import EditorSimple from "./EditorSimple.svelte";
|
||||
|
||||
interface Props {
|
||||
question: AudioQuestion;
|
||||
}
|
||||
|
||||
let { question = $bindable() }: Props = $props();
|
||||
</script>
|
||||
|
||||
<EditorMediaField bind:question></EditorMediaField>
|
||||
<EditorSimple bind:question></EditorSimple>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { AudioMultipleChoiceQuestion } from "$lib/games/games";
|
||||
import EditorMediaField from "./EditorMediaField.svelte";
|
||||
import EditorMultipleChoice from "./EditorMultipleChoice.svelte";
|
||||
|
||||
interface Props {
|
||||
question: AudioMultipleChoiceQuestion;
|
||||
}
|
||||
|
||||
let { question = $bindable() }: Props = $props();
|
||||
</script>
|
||||
|
||||
<EditorMediaField bind:question></EditorMediaField>
|
||||
<EditorMultipleChoice bind:question></EditorMultipleChoice>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { ImageQuestion } from "$lib/games/games";
|
||||
import EditorMediaField from "./EditorMediaField.svelte";
|
||||
import EditorSimple from "./EditorSimple.svelte";
|
||||
|
||||
interface Props {
|
||||
question: ImageQuestion;
|
||||
}
|
||||
|
||||
let { question = $bindable() }: Props = $props();
|
||||
</script>
|
||||
|
||||
<EditorMediaField bind:question></EditorMediaField>
|
||||
<EditorSimple bind:question></EditorSimple>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { ImageMultipleChoiceQuestion } from "$lib/games/games";
|
||||
import EditorMediaField from "./EditorMediaField.svelte";
|
||||
import EditorMultipleChoice from "./EditorMultipleChoice.svelte";
|
||||
|
||||
interface Props {
|
||||
question: ImageMultipleChoiceQuestion;
|
||||
}
|
||||
|
||||
let { question = $bindable() }: Props = $props();
|
||||
</script>
|
||||
|
||||
<EditorMediaField bind:question></EditorMediaField>
|
||||
<EditorMultipleChoice bind:question></EditorMultipleChoice>
|
||||
@@ -0,0 +1,67 @@
|
||||
<script lang="ts">
|
||||
import Button from "$lib/Button.svelte";
|
||||
import {
|
||||
isAudioMultipleChoiceQuestion,
|
||||
isAudioQuestion,
|
||||
type AudioMultipleChoiceQuestion,
|
||||
type AudioQuestion,
|
||||
type ImageMultipleChoiceQuestion,
|
||||
type ImageQuestion
|
||||
} from "$lib/games/games";
|
||||
import RessourceManager from "$lib/RessourceManager.svelte";
|
||||
import Textfield from "$lib/Textfield.svelte";
|
||||
import { isRessource } from "$lib/Types";
|
||||
|
||||
interface Props {
|
||||
question:
|
||||
| AudioQuestion
|
||||
| ImageQuestion
|
||||
| AudioMultipleChoiceQuestion
|
||||
| ImageMultipleChoiceQuestion;
|
||||
}
|
||||
|
||||
let { question = $bindable() }: Props = $props();
|
||||
|
||||
let showRessourceManager = $state(false);
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
{#if isAudioQuestion(question) || isAudioMultipleChoiceQuestion(question)}
|
||||
<div>Audio:</div>
|
||||
{:else}
|
||||
<div>Bild:</div>
|
||||
{/if}
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
{#if isAudioQuestion(question) || isAudioMultipleChoiceQuestion(question)}
|
||||
<Textfield
|
||||
readonly
|
||||
value={question.data.audio && isRessource(question.data.audio)
|
||||
? question.data.audio.name
|
||||
: ""}
|
||||
></Textfield>
|
||||
{:else}
|
||||
<Textfield
|
||||
readonly
|
||||
value={question.data.image && isRessource(question.data.image)
|
||||
? question.data.image.name
|
||||
: ""}
|
||||
></Textfield>
|
||||
{/if}
|
||||
<Button
|
||||
onclick={() => {
|
||||
showRessourceManager = true;
|
||||
}}><i class="fa-solid fa-arrow-up-right-from-square"></i></Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RessourceManager
|
||||
bind:show={showRessourceManager}
|
||||
ok={(res) => {
|
||||
if (isAudioQuestion(question) || isAudioMultipleChoiceQuestion(question)) {
|
||||
question.data.audio = res;
|
||||
} else {
|
||||
question.data.image = res;
|
||||
}
|
||||
}}
|
||||
></RessourceManager>
|
||||
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import Button from "$lib/Button.svelte";
|
||||
import type {
|
||||
AudioMultipleChoiceQuestion,
|
||||
ImageMultipleChoiceQuestion,
|
||||
MultipleChoiceQuestion
|
||||
} from "$lib/games/games";
|
||||
import Textfield from "$lib/Textfield.svelte";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
interface Props {
|
||||
question:
|
||||
| MultipleChoiceQuestion
|
||||
| AudioMultipleChoiceQuestion
|
||||
| ImageMultipleChoiceQuestion;
|
||||
}
|
||||
|
||||
let { question = $bindable() }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Textfield bind:value={question.data.question} label="Frage"></Textfield>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<div>Antworten</div>
|
||||
<Button
|
||||
onclick={() => {
|
||||
question.data.choices.push("Antwort");
|
||||
}}><i class="fa-solid fa-plus"></i></Button
|
||||
>
|
||||
</div>
|
||||
<hr />
|
||||
{#each question.data.choices as _, answerIndex}
|
||||
<div class="flex items-center gap-2">
|
||||
<Textfield
|
||||
bind:value={question.data.choices[answerIndex]}
|
||||
label={answerIndex === 0 ? "Korrekte Antwort" : `Antwort ${answerIndex + 1}`}
|
||||
></Textfield>
|
||||
<Button
|
||||
class="border-red-600 text-red-600"
|
||||
onclick={(event) => {
|
||||
question.data.choices.splice(answerIndex, 1);
|
||||
}}><i class="fa-solid fa-trash"></i></Button
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { AudioQuestion, ImageQuestion, SimpleQuestion } from "$lib/games/games";
|
||||
import Textfield from "$lib/Textfield.svelte";
|
||||
|
||||
interface Props {
|
||||
question: SimpleQuestion | ImageQuestion | AudioQuestion;
|
||||
}
|
||||
|
||||
let { question = $bindable() }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Textfield bind:value={question.data.question} label="Frage"></Textfield>
|
||||
<Textfield bind:value={question.data.answer} label="Antwort"></Textfield>
|
||||
@@ -0,0 +1,303 @@
|
||||
import type {
|
||||
AudioMultipleChoiceQuestion,
|
||||
AudioQuestion,
|
||||
ImageMultipleChoiceQuestion,
|
||||
ImageQuestion,
|
||||
MultipleChoiceQuestion,
|
||||
Question,
|
||||
QuestionType,
|
||||
SimpleQuestion
|
||||
} from "$lib/games/games";
|
||||
import type { GeneralQuestion } from "$lib/Types";
|
||||
|
||||
function defaultConversion(question: Question, type: QuestionType): Question {
|
||||
return {
|
||||
points: question.points,
|
||||
_id: question._id,
|
||||
owner: question.owner,
|
||||
type
|
||||
};
|
||||
}
|
||||
|
||||
export function convert(q: GeneralQuestion, t: QuestionType) {
|
||||
if (q.type === "SIMPLE") {
|
||||
switch (t) {
|
||||
case "SIMPLE":
|
||||
return q;
|
||||
case "MULTIPLE_CHOICE":
|
||||
return simpleToMultipleChoice(q);
|
||||
case "IMAGE":
|
||||
return simpleToImage(q);
|
||||
case "IMAGE_MULTIPLE_CHOICE":
|
||||
return simpleToImageMultipleChoice(q);
|
||||
case "AUDIO":
|
||||
return simpleToAudio(q);
|
||||
case "AUDIO_MULTIPLE_CHOICE":
|
||||
return simpleToAudioMultipleChoice(q);
|
||||
}
|
||||
} else if (q.type === "MULTIPLE_CHOICE") {
|
||||
switch (t) {
|
||||
case "SIMPLE":
|
||||
return multipleChoiceToSimple(q);
|
||||
case "MULTIPLE_CHOICE":
|
||||
return q;
|
||||
case "IMAGE":
|
||||
return multipleChoiceToImage(q);
|
||||
case "IMAGE_MULTIPLE_CHOICE":
|
||||
return multipleChoiceToImageMultipleChoice(q);
|
||||
case "AUDIO":
|
||||
return multipleChoiceToAudio(q);
|
||||
case "AUDIO_MULTIPLE_CHOICE":
|
||||
return multipleChoiceToAudioMultipleChoice(q);
|
||||
}
|
||||
} else if (q.type === "IMAGE") {
|
||||
switch (t) {
|
||||
case "SIMPLE":
|
||||
return mediaToSimple(q);
|
||||
case "MULTIPLE_CHOICE":
|
||||
return simpleToMultipleChoice(q);
|
||||
case "IMAGE":
|
||||
return q;
|
||||
case "IMAGE_MULTIPLE_CHOICE":
|
||||
return imageToImageMultipleChoice(q);
|
||||
case "AUDIO":
|
||||
return simpleToAudio(q);
|
||||
case "AUDIO_MULTIPLE_CHOICE":
|
||||
return simpleToMultipleChoice(q);
|
||||
}
|
||||
} else if (q.type === "IMAGE_MULTIPLE_CHOICE") {
|
||||
switch (t) {
|
||||
case "SIMPLE":
|
||||
return multipleChoiceToSimple(q);
|
||||
case "MULTIPLE_CHOICE":
|
||||
return mediaMultipleChoiceToMultipleChoice(q);
|
||||
case "IMAGE":
|
||||
return imageMultipleChoiceToImage(q);
|
||||
case "IMAGE_MULTIPLE_CHOICE":
|
||||
return q;
|
||||
case "AUDIO":
|
||||
return multipleChoiceToAudio(q);
|
||||
case "AUDIO_MULTIPLE_CHOICE":
|
||||
return multipleChoiceToAudioMultipleChoice(q);
|
||||
}
|
||||
} else if (q.type === "AUDIO") {
|
||||
switch (t) {
|
||||
case "SIMPLE":
|
||||
return mediaToSimple(q);
|
||||
case "MULTIPLE_CHOICE":
|
||||
return simpleToMultipleChoice(q);
|
||||
case "IMAGE":
|
||||
return simpleToImage(q);
|
||||
case "IMAGE_MULTIPLE_CHOICE":
|
||||
return simpleToImageMultipleChoice(q);
|
||||
case "AUDIO":
|
||||
return q;
|
||||
case "AUDIO_MULTIPLE_CHOICE":
|
||||
return audioToAudioMultipleChoice(q);
|
||||
}
|
||||
} else if (q.type === "AUDIO_MULTIPLE_CHOICE") {
|
||||
switch (t) {
|
||||
case "SIMPLE":
|
||||
return multipleChoiceToSimple(q);
|
||||
case "MULTIPLE_CHOICE":
|
||||
return mediaMultipleChoiceToMultipleChoice(q);
|
||||
case "IMAGE":
|
||||
return multipleChoiceToImage(q);
|
||||
case "IMAGE_MULTIPLE_CHOICE":
|
||||
return multipleChoiceToImageMultipleChoice(q);
|
||||
case "AUDIO":
|
||||
return audioMultipleChoiceToAudio(q);
|
||||
case "AUDIO_MULTIPLE_CHOICE":
|
||||
return q;
|
||||
}
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
export function simpleToMultipleChoice(
|
||||
question: SimpleQuestion | ImageQuestion | AudioQuestion
|
||||
): MultipleChoiceQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "MULTIPLE_CHOICE") as MultipleChoiceQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
choices: [question.data.answer]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function simpleToImage(question: SimpleQuestion | AudioQuestion): ImageQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "IMAGE") as ImageQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
answer: question.data.answer,
|
||||
image: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function simpleToImageMultipleChoice(
|
||||
question: SimpleQuestion | AudioQuestion
|
||||
): ImageMultipleChoiceQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "IMAGE_MULTIPLE_CHOICE") as ImageMultipleChoiceQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
choices: [question.data.answer],
|
||||
image: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function simpleToAudio(question: SimpleQuestion | ImageQuestion): AudioQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "AUDIO") as AudioQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
answer: question.data.answer,
|
||||
audio: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function simpleToAudioMultipleChoice(question: SimpleQuestion): AudioMultipleChoiceQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "AUDIO_MULTIPLE_CHOICE") as AudioMultipleChoiceQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
choices: [question.data.answer],
|
||||
audio: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function mediaToSimple(question: ImageQuestion | AudioQuestion): SimpleQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "SIMPLE") as SimpleQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
answer: question.data.answer
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function mediaMultipleChoiceToMultipleChoice(
|
||||
question: ImageMultipleChoiceQuestion | AudioMultipleChoiceQuestion
|
||||
): MultipleChoiceQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "MULTIPLE_CHOICE") as MultipleChoiceQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
choices: [...question.data.choices]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function multipleChoiceToSimple(
|
||||
question: MultipleChoiceQuestion | ImageMultipleChoiceQuestion | AudioMultipleChoiceQuestion
|
||||
): SimpleQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "SIMPLE") as SimpleQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
answer: question.data.choices[0] ? question.data.choices[0] : "Antwort"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function multipleChoiceToImage(
|
||||
question: MultipleChoiceQuestion | AudioMultipleChoiceQuestion
|
||||
): ImageQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "IMAGE") as ImageQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
answer: question.data.choices[0] ? question.data.choices[0] : "Antwort",
|
||||
image: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function multipleChoiceToImageMultipleChoice(
|
||||
question: MultipleChoiceQuestion | AudioMultipleChoiceQuestion
|
||||
): ImageMultipleChoiceQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "IMAGE_MULTIPLE_CHOICE") as ImageMultipleChoiceQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
choices: [...question.data.choices],
|
||||
image: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function multipleChoiceToAudio(
|
||||
question: MultipleChoiceQuestion | ImageMultipleChoiceQuestion
|
||||
): AudioQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "AUDIO") as AudioQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
answer: question.data.choices[0] ? question.data.choices[0] : "Antwort",
|
||||
audio: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function multipleChoiceToAudioMultipleChoice(
|
||||
question: MultipleChoiceQuestion | ImageMultipleChoiceQuestion
|
||||
): AudioMultipleChoiceQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "AUDIO_MULTIPLE_CHOICE") as AudioMultipleChoiceQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
choices: [...question.data.choices],
|
||||
audio: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function imageToImageMultipleChoice(question: ImageQuestion): ImageMultipleChoiceQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "IMAGE_MULTIPLE_CHOICE") as ImageMultipleChoiceQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
choices: [question.data.answer],
|
||||
image: question.data.image
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function imageMultipleChoiceToImage(question: ImageMultipleChoiceQuestion): ImageQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "IMAGE") as ImageQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
answer: question.data.choices[0] ? question.data.choices[0] : "Antwort",
|
||||
image: question.data.image
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function audioToAudioMultipleChoice(question: AudioQuestion): AudioMultipleChoiceQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "AUDIO_MULTIPLE_CHOICE") as AudioMultipleChoiceQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
choices: [question.data.answer],
|
||||
audio: question.data.audio
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function audioMultipleChoiceToAudio(question: AudioMultipleChoiceQuestion): AudioQuestion {
|
||||
return {
|
||||
...(defaultConversion(question, "AUDIO") as AudioQuestion),
|
||||
data: {
|
||||
question: question.data.question,
|
||||
answer: question.data.choices[0] ? question.data.choices[0] : "Antwort",
|
||||
audio: question.data.audio
|
||||
}
|
||||
};
|
||||
}
|
||||
108
src/routes/editor/fetchers.ts
Normal file
108
src/routes/editor/fetchers.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import type {
|
||||
AudioMultipleChoiceQuestion,
|
||||
AudioQuestion,
|
||||
ImageMultipleChoiceQuestion,
|
||||
ImageQuestion,
|
||||
MultipleChoiceQuestion,
|
||||
Question,
|
||||
SimpleQuestion
|
||||
} from "$lib/games/games";
|
||||
import type { Category, Game, Wall } from "$lib/Types";
|
||||
import { url } from "$lib/util";
|
||||
import axios from "axios";
|
||||
|
||||
export function fetchGame(id: string) {
|
||||
return axios.get(url(`/game?id=${id}`), { withCredentials: true }).then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.data as Game;
|
||||
} else {
|
||||
throw `Failed to fetch game: ${response.status}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchWalls(id: string) {
|
||||
return axios.get(url(`/walls/${id}`), { withCredentials: true }).then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.data as Wall[];
|
||||
} else {
|
||||
throw `Failed to fetch walls: ${response.status}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchWall(id: string) {
|
||||
return axios.get(url(`/wall?id=${id}`), { withCredentials: true }).then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.data as Wall;
|
||||
} else {
|
||||
throw `Failed to fetch wall: ${response.status}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchCategory(id: string) {
|
||||
return axios.get(url(`/category?id=${id}`), { withCredentials: true }).then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.data as Category;
|
||||
} else {
|
||||
throw `Failed to fetch category: ${response.status}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchQuestion(id: string) {
|
||||
return axios
|
||||
.get(url(`/question?id=${id}`), { withCredentials: true })
|
||||
.then(async (response) => {
|
||||
if (response.status === 200) {
|
||||
const q = response.data;
|
||||
console.log(q);
|
||||
if (
|
||||
(q as Question).type === "IMAGE" ||
|
||||
(q as Question).type === "IMAGE_MULTIPLE_CHOICE"
|
||||
) {
|
||||
// request ressource
|
||||
return axios
|
||||
.get(url(`/ressource?id=${(q as ImageQuestion).data.image}`), {
|
||||
withCredentials: true
|
||||
})
|
||||
.then((ressourceResponse) => {
|
||||
if (ressourceResponse.status === 200) {
|
||||
(q as ImageQuestion | ImageMultipleChoiceQuestion).data.image =
|
||||
ressourceResponse.data;
|
||||
} else {
|
||||
(q as ImageQuestion | ImageMultipleChoiceQuestion).data.image =
|
||||
null;
|
||||
}
|
||||
return q as ImageQuestion | ImageMultipleChoiceQuestion;
|
||||
})
|
||||
.catch(() => {
|
||||
return q as ImageQuestion | ImageMultipleChoiceQuestion;
|
||||
});
|
||||
} else if (
|
||||
(q as Question).type === "AUDIO" ||
|
||||
(q as Question).type === "AUDIO_MULTIPLE_CHOICE"
|
||||
) {
|
||||
// request ressource
|
||||
return axios
|
||||
.get(url(`/ressource?id=${(q as AudioQuestion).data.audio}`), {
|
||||
withCredentials: true
|
||||
})
|
||||
.then((ressourceResponse) => {
|
||||
if (ressourceResponse.status === 200) {
|
||||
(q as AudioQuestion | AudioMultipleChoiceQuestion).data.audio =
|
||||
ressourceResponse.data;
|
||||
} else {
|
||||
(q as AudioQuestion | AudioMultipleChoiceQuestion).data.audio =
|
||||
null;
|
||||
}
|
||||
return q as AudioQuestion | AudioMultipleChoiceQuestion;
|
||||
})
|
||||
.catch(() => {
|
||||
return q as AudioQuestion | AudioMultipleChoiceQuestion;
|
||||
});
|
||||
} else return q as SimpleQuestion | MultipleChoiceQuestion;
|
||||
} else throw `Failed to fetch question: ${response.status}`;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user