382 lines
12 KiB
HTML
Raw Permalink Blame History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JSON Question Creator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
<style>
h1 {
color: #FF6900;
font-weight: lighter;
}
img {
width: 30%;
}
body {
font-family: 'Open Sans', Arial, sans-serif;
background-color: #fff;
color: #434F4F;
margin: 0;
padding: 2rem;
}
.container {
max-width: 800px;
margin: auto;
}
.question-block {
border: 2px solid #FF6900;
padding: 1rem;
margin-bottom: 1rem;
border-radius: 8px;
position: relative;
}
.data-block {
padding: 1rem;
margin-bottom: 1rem;
border-radius: 8px;
}
input[type="text"] {
width: 100%;
padding: 0.5rem;
margin: 0.25rem 0;
box-sizing: border-box;
border-radius:4px;
border:1px solid #D9DCDC;
}
button {
background-color: #FF6900;
color: white;
border: none;
padding: 0.75rem 1.5rem;
margin-top: 1rem;
cursor: pointer;
border-radius: 4px;
}
pre {
background: #f4f4f4;
padding: 1rem;
white-space: pre-wrap;
}
.upload-btn {
background-color: #FF6900;
color: white;
margin-top: 1rem;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.8rem;
line-height: 1.5;
}
/* place near your other button styles */
.delete-question {
background-color: #FF6900;
color: #fff;
border: none;
padding: 0.5rem 0.9rem;
border-radius: 4px;
cursor: pointer;
position: absolute;
right: 0.75rem;
bottom: 0.75rem;
font-size: 0.9rem;
}
.smallLabel {
font-size: 0.9rem;
}
</style>
</head>
<body>
<div class="container">
<img src="iwm_logo.svg" alt="IWM Logo">
<h1>Question Creator</h1>
<div class="data-block">
<label for="gameTitle">Titel des Spiels:</label>
<input type="text" id="gameTitle" placeholder="Bitte den Titel des Fragespiels angeben">
<br><br>
<label for="targetAge">Alter:</label>
<input type="text" id="targetAge" placeholder="Bitte das Zielalter der Sch&uuml;ler:innen eingeben">
<br><br>
<label for="subject">Fach:</label>
<input type="text" id="subject" placeholder="Bitte das Fach eingeben">
</div>
<br>
<br>
<div id="questionList"></div>
<button id="addQuestion">Add Question</button>
<button id="generateJson">Generate JSON</button>
<label for="importJson" class="upload-btn" style="float: right;">Import JSON</label>
<input type="file" id="importJson" accept=".json" style="display: none;">
<pre id="output"></pre>
</div>
<script>
const questionList = document.getElementById('questionList');
const addQuestionButton = document.getElementById('addQuestion');
const generateJsonButton = document.getElementById('generateJson');
const output = document.getElementById('output');
questionList.addEventListener('click', (e) => {
if (e.target.classList.contains('delete-question')) {
const block = e.target.closest('.question-block');
if (block && confirm('Diese Frage wirklich l<>schen?')) {
block.remove();
}
}
});
//makes sure the browser remembers the name links for files and doesn't overwrite them with null
function bindFileInput(input, type, initialName = "") {
const span = input.nextElementSibling; // the <20>Linked:<3A> label
// persist current filename on the input element
input.dataset.linkedName = initialName || "";
// set initial label
span.textContent = initialName ? `Linked: ${initialName}` : `No ${type} linked`;
// live updates when user picks/clears a file
input.addEventListener('change', function () {
const name = this.files && this.files[0] ? this.files[0].name : "";
this.dataset.linkedName = name;
span.textContent = name ? `Linked: ${name}` : `No ${type} linked`;
});
}
function createQuestionBlock() {
const div = document.createElement('div');
div.className = 'question-block';
div.innerHTML = `
<label>Frage:</label>
<input type="text" class="questionText" placeholder="Bitte die Frage eingeben">
<br><br>
<label class="smallLabel">Fragen-Bild (optional):</label>
<input type="file" class="questionImage" accept="image/*">
<span class="linked-filename" style="font-size:0.8rem; color:#666; margin-left:0.5rem;">Kein Bild eingebunden</span>
<br><br>
<label class="smallLabel">Fragen-Audio (optional):</label>
<input type="file" class="questionSound" accept="audio/*">
<span class="linked-filename" style="font-size:0.8rem; color:#666; margin-left:0.5rem;">Kein Audio eingebunden</span>
<br><br>
<br><br>
<label>Antworten:</label>
${[1, 2, 3, 4, 5].map(i => `
<div style="margin-bottom: 0.75rem;">
<input type="text" class="answerText" placeholder="Antwort ${i}${i === 1 ? ' (korrekte Antwort hier einf&uuml;gen)' : ''}">
<br>
<input type="file" class="answerImage" accept="image/*">
<span class="linked-filename" style="font-size:0.8rem; color:#666; margin-left:0.5rem;">Kein Bild eingebunden</span>
</div>
`).join('')}
<button type="button" class="delete-question">Frage l&ouml;schen</button>
`;
bindFileInput(div.querySelector('.questionImage'), 'image', "");
bindFileInput(div.querySelector('.questionSound'), 'sound', "");
div.querySelectorAll('.answerImage').forEach(el => bindFileInput(el, 'image', ""));
questionList.appendChild(div);
}
addQuestionButton.addEventListener('click', createQuestionBlock);
function download(filename, text) {
const element = document.createElement('a');
element.setAttribute('href', 'data:text/json;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
// export handler (generate file)
generateJsonButton.addEventListener('click', () => {
const questions = [];
const blocks = document.querySelectorAll('.question-block');
blocks.forEach(block => {
const questionText = (block.querySelector('.questionText')?.value || "").trim();
// collect question media (preserve linked filenames even after reload)
const qImgEl = block.querySelector('.questionImage');
const qSndEl = block.querySelector('.questionSound');
const questionImage = (qImgEl?.files?.[0]?.name || qImgEl?.dataset?.linkedName || "").trim();
const questionSound = (qSndEl?.files?.[0]?.name || qSndEl?.dataset?.linkedName || "").trim();
// collect answers (up to 5), then FILTER OUT empties
const answerTexts = block.querySelectorAll('.answerText');
const answerImages = block.querySelectorAll('.answerImage');
const collected = [];
for (let i = 0; i < 5; i++) {
const text = (answerTexts[i]?.value || "").trim();
const aImgEl = answerImages[i];
const image = (aImgEl?.files?.[0]?.name || aImgEl?.dataset?.linkedName || "").trim();
collected.push({ text, image });
}
// keep only answers that actually have text
const answers = collected.filter(a => a.text.length > 0);
// only include this question if it has a prompt AND at least one answer
if (questionText && answers.length > 0) {
questions.push({
questionText,
questionImage,
questionSound,
answers, // variable length (1..5)
correctAnswerIndex: 0 // first provided answer remains the correct one
});
}
// else: skip empty/unfinished question blocks entirely
});
const metadata = {
title: (document.getElementById('gameTitle').value || "").trim(),
age: (document.getElementById('targetAge').value || "").trim(),
subject: (document.getElementById('subject').value || "").trim()
};
const jsonOutput = JSON.stringify({
title: metadata.title,
age: metadata.age,
subject: metadata.subject,
questions
}, null, 2);
output.textContent = jsonOutput;
let filename = metadata.title || "questions";
filename = filename.replace(/[^a-z0-9_\-]/gi, "_");
download(filename + ".json", jsonOutput);
});
//import handler
document.getElementById('importJson').addEventListener('change', function(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = JSON.parse(e.target.result);
// metadata
if (data.title) document.getElementById('gameTitle').value = data.title;
if (data.age) document.getElementById('targetAge').value = data.age;
if (data.subject) document.getElementById('subject').value = data.subject;
// questions
questionList.innerHTML = '';
if (Array.isArray(data.questions)) {
data.questions.forEach(q => {
const div = document.createElement('div');
div.className = 'question-block';
// backward-compat answers (strings -> objects)
let importedAnswers = (q.answers || []).map(a =>
(typeof a === 'string') ? { text: a, image: "" } : {
text: a.text || "",
image: a.image || ""
}
);
while (importedAnswers.length < 5) {
importedAnswers.push({ text: "", image: "" });
}
importedAnswers = importedAnswers.slice(0, 5);
const answersHTML = importedAnswers.map(a => `
<div style="margin-bottom: 0.75rem;">
<input type="text" class="answerText" value="${a.text}">
<br>
<input type="file" class="answerImage" accept="image/*">
<span class="linked-filename" style="font-size:0.8rem; color:#666; margin-left:0.5rem;">
${a.image ? `Linked: ${a.image}` : 'Kein Bild eingebunden'}
</span>
</div>
`).join('');
div.innerHTML = `
<label>Question:</label>
<input type="text" class="questionText" value="${q.questionText || ''}">
<br><br>
<label>Fragen-Bild (optional):</label>
<input type="file" class="questionImage" accept="image/*">
<span class="linked-filename" style="font-size:0.8rem; color:#666; margin-left:0.5rem;">
${q.questionImage ? `Linked: ${q.questionImage}` : 'Kein Bild eingebunden'}
</span>
<br><br>
<label>Fragen-Audio (optional):</label>
<input type="file" class="questionSound" accept="audio/*">
<span class="linked-filename" style="font-size:0.8rem; color:#666; margin-left:0.5rem;">
${q.questionSound ? `Linked: ${q.questionSound}` : 'Kein Audio eingebunden'}
</span>
<br><br>
<label>Antworten (die erste Antwort ist die korrekte):</label>
${answersHTML}
<button type="button" class="delete-question">Frage l&ouml;schen</button>
`;
questionList.appendChild(div);
// bind datasets + live labels using imported names
bindFileInput(div.querySelector('.questionImage'), 'image', q.questionImage || "");
bindFileInput(div.querySelector('.questionSound'), 'sound', q.questionSound || "");
const aImgEls = div.querySelectorAll('.answerImage');
importedAnswers.forEach((a, i) => {
bindFileInput(aImgEls[i], 'image', a.image || "");
});
});
}
} catch (err) {
alert('Invalid JSON file.');
}
};
reader.readAsText(file);
});
</script>
</body>
</html>