Benutzer:Patrick Oberdoerfer/ZUM-Apps und H5P/QuizAcademy H5P Converter Webtool/
Aus ZUM Projektwiki
import { useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Download } from "lucide-react";
export default function H5PConverter() {
const [externalJson, setExternalJson] = useState("");
const [converted, setConverted] = useState(null);
const [downloadUrl, setDownloadUrl] = useState(null);
function convertToH5P() {
try {
const data = JSON.parse(externalJson);
const questions = data.questions.map((q) => ({
library: "H5P.MultiChoice 1.16",
params: {
question: q.text,
answers: q.answers.map((a) => ({
text: a.text,
correct: a.is_right,
tipsAndFeedback: { tip: "", chosenFeedback: "", notChosenFeedback: "" }
})),
behaviour: {
enableRetry: true,
enableSolutionsButton: true,
enableCheckButton: true,
type: "auto",
singlePoint: false,
randomAnswers: true,
showSolutionsRequiresInput: true,
confirmCheckDialog: false,
confirmRetryDialog: false,
autoCheck: false,
passPercentage: 100,
showScorePoints: true
},
media: { disableImageZooming: false },
overallFeedback: [{ from: 0, to: 100 }],
UI: {
checkAnswerButton: "Überprüfen",
submitAnswerButton: "Absenden",
showSolutionButton: "Lösung anzeigen",
tryAgainButton: "Wiederholen",
tipsLabel: "Hinweis anzeigen",
scoreBarLabel: "Du hast :num von :total Punkten erreicht.",
tipAvailable: "Hinweis verfügbar",
feedbackAvailable: "Rückmeldung verfügbar",
readFeedback: "Rückmeldung vorlesen",
wrongAnswer: "Falsche Antwort",
correctAnswer: "Richtige Antwort",
shouldCheck: "Hätte gewählt werden müssen",
shouldNotCheck: "Hätte nicht gewählt werden sollen",
noInput: "Bitte antworte, bevor du die Lösung ansiehst",
a11yCheck: "Die Antworten überprüfen.",
a11yShowSolution: "Die Lösung anzeigen.",
a11yRetry: "Die Aufgabe wiederholen."
},
confirmCheck: {
header: "Beenden?",
body: "Ganz sicher beenden?",
cancelLabel: "Abbrechen",
confirmLabel: "Beenden"
},
confirmRetry: {
header: "Wiederholen?",
body: "Ganz sicher wiederholen?",
cancelLabel: "Abbrechen",
confirmLabel: "Bestätigen"
}
},
subContentId: crypto.randomUUID(),
metadata: {
contentType: "Multiple Choice",
license: "U",
title: "Frage"
}
}));
const h5pContent = {
introPage: {
showIntroPage: false,
startButtonText: "Quiz starten",
introduction: ""
},
progressType: "dots",
passPercentage: 50,
disableBackwardsNavigation: false,
randomQuestions: false,
endGame: {
showResultPage: true,
showSolutionButton: false,
showRetryButton: true,
noResultMessage: "Quiz beendet",
message: "Dein Ergebnis:",
scoreBarLabel: "Du hast @score von @total Punkten erreicht.",
overallFeedback: [{ from: 0, to: 100 }],
solutionButtonText: "Lösung anzeigen",
retryButtonText: "Wiederholen",
finishButtonText: "Beenden",
submitButtonText: "Absenden",
showAnimations: false,
skippable: false,
skipButtonText: "Video überspringen"
},
override: { checkButton: true },
texts: {
prevButton: "Zurück",
nextButton: "Weiter",
finishButton: "Beenden",
submitButton: "Absenden",
textualProgress: "Aktuelle Frage: @current von @total Fragen",
jumpToQuestion: "Frage %d von %total",
questionLabel: "Frage",
readSpeakerProgress: "Frage @current von @total",
unansweredText: "Unbeantwortet",
answeredText: "Beantwortet",
currentQuestionText: "Aktuelle Frage",
navigationLabel: "Fragen"
},
questions
};
const zip = new JSZip();
zip.file("h5p.json", JSON.stringify({
title: "Fragenset", language: "de", mainLibrary: "H5P.QuestionSet",
embedTypes: ["div"], license: "U",
preloadedDependencies: [
{ machineName: "H5P.QuestionSet", majorVersion: 1, minorVersion: 17 },
{ machineName: "H5P.MultiChoice", majorVersion: 1, minorVersion: 16 }
]
}, null, 2));
zip.folder("content").file("content.json", JSON.stringify(h5pContent, null, 2));
zip.generateAsync({ type: "blob" }).then(blob => {
const url = URL.createObjectURL(blob);
setDownloadUrl(url);
setConverted(JSON.stringify(h5pContent, null, 2));
});
} catch (err) {
setConverted("Fehler beim Verarbeiten der JSON-Daten: " + err.message);
}
}
return (
<div className="p-4 grid gap-4">
<Card>
<CardContent className="space-y-4 p-4">
<h2 className="text-xl font-semibold">Externe Fragen-JSON einfügen</h2>
<Textarea
rows={15}
placeholder="Füge hier die externe JSON-Datei mit den Fragen ein..."
value={externalJson}
onChange={(e) => setExternalJson(e.target.value)}
/>
<Button onClick={convertToH5P}>In H5P Question Set umwandeln</Button>
</CardContent>
</Card>
{converted && (
<Card>
<CardContent className="space-y-4 p-4">
<h2 className="text-xl font-semibold">Konvertiertes H5P content.json</h2>
<Textarea rows={20} readOnly value={converted} />
{downloadUrl && (
<a href={downloadUrl} download="Fragenset.h5p">
<Button className="mt-2" variant="outline"><Download className="mr-2 h-4 w-4" />H5P-Datei herunterladen</Button>
</a>
)}
</CardContent>
</Card>
)}
</div>
);
}
