src/widgets/QuizCreator.js
author ymh <ymh.work@gmail.com>
Wed, 04 Sep 2024 17:32:50 +0200
changeset 1072 ac1eacb3aa33
parent 1068 7623f9af9272
permissions -rw-r--r--
Migrate source and build to vite.js

/* TODO: Add Social Network Sharing */
import quizCreatorStyles from "./QuizCreator.module.css";

const QuizCreator = function (ns) {
  return class extends ns.Widgets.Widget {
    constructor(player, config) {
      super(player, config);
      var _this = this;
    }

    static defaults = {
      creator_name: "",
      tags: false,
      tag_titles: false,
      pause_on_write: true,
      annotation_type: "Quiz",
      api_serializer: "ldt_annotate",
      api_endpoint_template: "",
      api_method: "POST",
    };

    static messages =  {
      en: {},
      fr: {},
    };

    static template =
      '<div class="Ldt-QuizCreator-Ui Ldt-TraceMe">' +
      '<div class="Ldt-QuizCreator-Question-Form">' +
      '<textarea class="Ldt-QuizCreator-Question-Area" placeholder="Votre question"></textarea><br />' +
      '<textarea class="Ldt-QuizCreator-Resource-Area" placeholder="Ressources (lien vers une image, etc.)"></textarea><br />' +
      "</div>" +
      "<p>Type de question  " +
      '<select name="type" class="Ldt-QuizCreator-Question-Type">' +
      '<option value="unique_choice">Choix unique</option>' +
      '<option value="multiple_choice">Choix multiple</option>' +
      "</select>" +
      ' à <input type="text" placeholder="hh:mm:ss" size="6" class="Ldt-QuizCreator-Time" />' +
      '<div class="Ldt-QuizCreator-Questions-Block">' +
      "</div>" +
      "<div>" +
      '  <button class="Ldt-QuizCreator-Question-Add">Ajouter une réponse</button><hr>' +
      '  <button class="Ldt-QuizCreator-Question-Save">Sauvegarder</button>' +
      "</div>" +
      "</div>";

    /* Hide and clear the interface is case of someone skipped or answer the current question in the Quiz panel*/
    skip() {
      this.$.find(".Ldt-QuizCreator-Time").val("");
      this.$.find(".Ldt-QuizCreator-Question-Area").val("");
      this.$.find(".Ldt-QuizCreator-Resource-Area").val("");
      this.$.find(".Ldt-QuizCreator-Questions-Block").html("");
      this.current_annotation = undefined;
    }

    nbAnswers() {
      var numItems = this.$.find(".Ldt-QuizCreator-Questions-Answer").length;
      return numItems;
    }

    draw() {
      var _this = this;

      this.onMediaEvent("timeupdate", function (_time) {
        _this.setBegin(_time);
      });

      this.onMdpEvent("QuizCreator.show", function () {
        _this.setBegin(_this.media.currentTime);
      });

      this.onMdpEvent("QuizCreator.create", function () {
        _this.skip();
        _this.setBegin(_this.media.currentTime);
      });

      this.onMdpEvent("QuizCreator.skip", function () {
        _this.skip();
      });

      this.onMdpEvent("QuizCreator.edit", function (_annotation) {
        _this.skip();
        _this.addQuestion(_annotation);
      });

      this.$.on("click", ".Ldt-QuizCreator-Remove", function () {
        $(this).parents(".Ldt-QuizCreator-Questions-Answer").remove();
      });

      this.begin = new ns.Model.Time();
      this.end = this.source.getDuration();
      this.answers = [];

      this.renderTemplate();

      /* Quiz creator */

      this.question = new ns.Widgets.UniqueChoiceQuestion();

      this.$.find(".Ldt-QuizCreator-Question-Type").bind(
        "change",
        this.functionWrapper("onQuestionTypeChange")
      );
      this.$.find(".Ldt-QuizCreator-Question-Add").bind(
        "click",
        this.functionWrapper("onQuestionAdd")
      );
      this.$.find(".Ldt-QuizCreator-Question-Save").bind(
        "click",
        this.functionWrapper("onSave")
      );

      this.$.find(".Ldt-QuizCreator-Time").keyup(function () {
        var str = _this.$.find(".Ldt-QuizCreator-Time").val();
        _this.begin = ns.timestamp2ms(str);
        _this.end = _this.begin + 1000;
      });

      this.onMediaEvent("timeupdate", function (_time) {
        // Do not update timecode if description is not empty
        if (_this.getDescription()) {
          _this.setBegin(_time);
        }
      });
    }

    getDescription() {
      return this.$.find(".Ldt-QuizCreator-Question-Area").val().trim();
    }

    addQuestion(annotation, number) {
      var _this = this;

      if (annotation.content.data.type == "multiple_choice") {
        this.question = new ns.Widgets.MultipleChoiceQuestion(annotation);
      } else if (annotation.content.data.type == "unique_choice") {
        this.question = new ns.Widgets.UniqueChoiceQuestion(annotation);
      }

      var answers = annotation.content.data.answers;

      this.answers = [];

      this.$.find(".Ldt-QuizCreator-Time").val(annotation.begin);
      this.$.find(".Ldt-QuizCreator-Question-Area").val(
        annotation.content.data.question
      );
      this.$.find(".Ldt-QuizCreator-Resource-Area").val(
        annotation.content.data.resource
      );
      this.$.find(".Ldt-QuizCreator-Questions-Block").html("");
      answers.forEach(function (ans) {
        _this.onQuestionAdd(null, ans);
      });
      _this.current_annotation = annotation;
    }

    onQuestionTypeChange(e) {
      var _field = this.$.find(".Ldt-QuizCreator-Question-Type");
      var _contents = _field.val();

      var _this = this;
      switch (_contents) {
        case "unique_choice":
          this.question = new ns.Widgets.UniqueChoiceQuestion();
          break;

        case "multiple_choice":
          this.question = new ns.Widgets.MultipleChoiceQuestion();
          break;
      }

      var output = "";

      _this.$.find(".Ldt-QuizCreator-Questions-Block").html(output);

      this.pauseOnWrite();
    }

    // Either e !== undefined, then it has been called by the interface and answer === undefined, generate an empty form.
    // Or e === null && answer !== undefined, an existing answer is provided.
    onQuestionAdd(e, answer) {
      var output =
        '<div class="Ldt-QuizCreator-Questions-Answer">' +
        'Réponse <div class="Ldt-QuizCreator-Questions-Answer-Correct">' +
        this.question.renderFullTemplate(answer, this.nbAnswers()) +
        "</div><br />" +
        '<div class="Ldt-QuizCreator-Questions-Answer-Content">' +
        '<input type="text" class="Ldt-QuizCreator-Answer-Content" data-question="' +
        this.nbAnswers() +
        '" id="question' +
        this.nbAnswers() +
        '"' +
        (answer ? ' value="' + answer.content + '"' : "") +
        "/><br />" +
        'Commentaire <br/><textarea class="Ldt-QuizCreator-Answer-Feedback" data-question="' +
        this.nbAnswers() +
        '" id="feedback' +
        this.nbAnswers() +
        '">' +
        (answer ? answer.feedback : "") +
        "</textarea>" +
        "</div>" +
        '<div class="Ldt-QuizCreator-Questions-Answer-Delete"><div class="Ldt-QuizCreator-Remove">&nbsp;</div></div>' +
        "</div>";
      this.$.find(".Ldt-QuizCreator-Questions-Block").append(output);
      this.$.find(".Ldt-QuizCreator-Answer-Content").last().focus();

      this.pauseOnWrite();
    }

    pauseOnWrite() {
      if (this.pause_on_write && !this.media.getPaused()) {
        this.media.pause();
      }
    }

    setBegin(t) {
      this.begin = new ns.Model.Time(t || 0);
      this.end = this.begin + 500;
      this.$.find(".Ldt-QuizCreator-Time").val(this.begin.toString());
    }

    get_local_annotation(ident) {
      return this.player.getLocalAnnotation(ident);
    }

    save_local_annotations() {
      this.player.saveLocalAnnotations();
      // Merge modifications into widget source
      this.source.merge(this.player.localSource);
    }

    delete_local_annotation(ident) {
      this.source.getAnnotations().removeId(ident);
      this.player.deleteLocalAnnotation(ident);
      this.current_annotation = undefined;
      this.refresh(true);
    }

    show() {
      this.$.find(".Ldt-QuizCreator-Question-Area").focus();
    }

    hide() {
      this.$.find(".Ldt-QuizCreator-Questions-Block").html("");
      this.$.find(".Ldt-QuizCreator-Question-Area").val("");
      this.$.find(".Ldt-QuizCreator-Resource-Area").val("");
      this.$.find(".Ldt-QuizCreator-Time").val("");
    }

    /* Save a local annotation */
    onSave(event, should_publish) {
      // Either the annotation already exists (then we overwrite its
      // content) or it must be created.
      var is_created = false;

      if (this.nbAnswers() <= 0) {
        alert("Vous devez spécifier au moins une réponse à votre question !");
        return false;
      }
      // Check that there is at least 1 valid answer
      if (!this.$.find(".quiz-question-edition:checked").length) {
        alert("Vous n'avez pas indiqué de bonne réponse.");
        return false;
      }
      var _annotation;
      if (this.current_annotation) {
        is_created = false;
        _annotation = this.current_annotation;
      } else {
        is_created = true;
        var _annotationTypes = this.source
            .getAnnotationTypes()
            .searchByTitle(
              this.annotation_type,
              true
            ) /* Récupération du type d'annotation dans lequel l'annotation doit être ajoutée */,
          _annotationType = _annotationTypes.length
            ? _annotationTypes[0]
            : new ns.Model.AnnotationType(
                false,
                this.player.localSource
              ); /* Si le Type d'Annotation n'existe pas, il est créé à la volée */

        /* Si nous avons dû générer un ID d'annotationType à la volée... */
        if (!_annotationTypes.length) {
          /* Il ne faudra pas envoyer l'ID généré au serveur */
          _annotationType.dont_send_id = true;
          /* Il faut inclure le titre dans le type d'annotation */
          _annotationType.title = this.annotation_type;
        }

        _annotation = new ns.Model.Annotation(
          false,
          this.player.localSource
        ); /* Création d'une annotation dans cette source avec un ID généré à la volée (param. false) */

        // Initialize some fields in case of creation
        _annotation.created = new Date(); /* Date de création de l'annotation */
        _annotation.creator = this.creator_name;
        _annotation.setAnnotationType(
          _annotationType.id
        ); /* Id du type d'annotation */
        this.player.localSource.getMedias().push(this.source.currentMedia);
        _annotation.setMedia(
          this.source.currentMedia.id
        ); /* Id du média annoté */
      }

      /*
       * Nous remplissons les données de l'annotation
       * */
      _annotation.setBeginEnd(this.begin, this.end);
      _annotation.modified =
        new Date(); /* Date de modification de l'annotation */
      _annotation.contributor = this.creator_name;
      _annotation.description = this.getDescription();
      _annotation.title = _annotation.description;
      _annotation.content = {};
      _annotation.content.data = {};
      _annotation.content.data.type = this.$.find(
        ".Ldt-QuizCreator-Question-Type"
      ).val();
      _annotation.content.data.question = _annotation.description;
      _annotation.content.data.resource = this.$.find(
        ".Ldt-QuizCreator-Resource-Area"
      ).val();
      _annotation.content.data.answers = $.makeArray(
        $(".Ldt-QuizCreator-Questions-Answer").map(function (ans) {
          return {
            content: $(this).find(".Ldt-QuizCreator-Answer-Content").val(),
            feedback: $(this).find(".Ldt-QuizCreator-Answer-Feedback").val(),
            correct: $(this).find(".Ldt-Quiz-Question-Check").is(":checked"),
          };
        })
      );
      this.current_annotation = _annotation;
      if (is_created) {
        // Add the annotation to the localSource
        this.player.addLocalAnnotation(_annotation);
        // Update also the remote source
        this.source.merge([_annotation]);
        this.player.trigger("Annotation.create", _annotation);
      } else {
        // Update the annotation
        this.player.saveLocalAnnotations();
        this.player.trigger("Annotation.update", _annotation);
      }
      this.player.trigger(
        "AnnotationsList.update"
      ); /* On force le rafraîchissement des widgets AnnotationsList */
      this.player.trigger(
        "Quiz.refresh"
      ); /* On force le rafraîchissement des widgets Quiz */
    }
  };
};

export { QuizCreator, quizCreatorStyles };