Benutzer:Patrick Oberdoerfer/ZUM-Apps und H5P/QuizAcademy H5P Converter Webtool/

Aus ZUM Projektwiki
< Benutzer:Patrick Oberdoerfer‎ | ZUM-Apps und H5P
Version vom 24. April 2025, 12:23 Uhr von Patrick Oberdoerfer (Diskussion | Beiträge) (JS eingefügt)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
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>
  );
}