Enrichit le reporting SCORM/xAPI et met en conformité RGAA le player
SCORM/xAPI : - Ajoute l'export SCORM 2004 (3rd/4th edition) au choix, en plus du 1.2 par défaut : sépare completion_status/success_status (un échec reste "completed" au lieu de retomber à tort en "incomplete" comme force la 1.2), remonte aussi cmi.interactions.n.* par question répondue. - Fournit cmi.core.score.min/max (1.2 et 2004), calculé depuis les récompenses de quiz du jeu, pour que le LMS affiche un vrai pourcentage au lieu du score brut à tort étiqueté "%". - Libellés de verbes xAPI en français en plus de l'anglais. Accessibilité (RGAA/WCAG 2.1 AA) sur le player : - Navigation clavier des objets de scène "au clic"/"au survol" (tabindex, role, Entrée/Espace, focus/blur). - alt sur les images (nom auteur ou décoratif), aria-hidden sur les icônes seules, role="dialog"/aria-live sur les boîtes de dialogue/quiz. - Landmark <main> + titre de page, respect de prefers-reduced-motion. - Le quiz n'avance plus automatiquement après un délai fixe : bouton "Continuer →" explicite (RGAA 2.2.1). - Avertissement de contraste dans l'éditeur de style de dialogue. - Déclaration d'accessibilité téléchargeable depuis la modale d'export.
This commit is contained in:
@@ -21,9 +21,27 @@
|
||||
// génèrent rien ici (bruit inutile pour un suivi volontairement léger,
|
||||
// voir le plan).
|
||||
var FORGE_TO_XAPI_VERB = {
|
||||
termine: { id: "http://adlnet.gov/expapi/verbs/completed", display: "completed" },
|
||||
reussi: { id: "http://adlnet.gov/expapi/verbs/passed", display: "passed" },
|
||||
echoue: { id: "http://adlnet.gov/expapi/verbs/failed", display: "failed" },
|
||||
termine: { id: "http://adlnet.gov/expapi/verbs/completed" },
|
||||
reussi: { id: "http://adlnet.gov/expapi/verbs/passed" },
|
||||
echoue: { id: "http://adlnet.gov/expapi/verbs/failed" },
|
||||
};
|
||||
|
||||
// Libellé humain par verbe — l'id xAPI (http://adlnet.gov/expapi/verbs/...)
|
||||
// reste TOUJOURS en anglais (identifiant technique fixé par le
|
||||
// vocabulaire ADL, jamais traduit), mais le libellé affiché par le LRS
|
||||
// peut porter plusieurs langues : demande utilisateur "c'est possible en
|
||||
// français ?" — un LRS qui préfère le français (ou n'importe quelle
|
||||
// langue ici) affichera la bonne entrée au lieu de rester bloqué sur
|
||||
// l'anglais.
|
||||
var FORGE_XAPI_VERB_DISPLAY = {
|
||||
"http://adlnet.gov/expapi/verbs/initialized": { "en-US": "initialized", "fr-FR": "initialisé" },
|
||||
"http://adlnet.gov/expapi/verbs/terminated": { "en-US": "terminated", "fr-FR": "quitté" },
|
||||
"http://adlnet.gov/expapi/verbs/completed": { "en-US": "completed", "fr-FR": "terminé" },
|
||||
"http://adlnet.gov/expapi/verbs/passed": { "en-US": "passed", "fr-FR": "réussi" },
|
||||
"http://adlnet.gov/expapi/verbs/failed": { "en-US": "failed", "fr-FR": "échoué" },
|
||||
"http://adlnet.gov/expapi/verbs/scored": { "en-US": "scored", "fr-FR": "noté" },
|
||||
"http://adlnet.gov/expapi/verbs/attempted": { "en-US": "attempted", "fr-FR": "commencé" },
|
||||
"http://adlnet.gov/expapi/verbs/answered": { "en-US": "answered", "fr-FR": "répondu" },
|
||||
};
|
||||
|
||||
var forgeXapiLastStatusSent = null; // évite de renvoyer le même statement si le statut ne change pas réellement
|
||||
@@ -54,17 +72,48 @@ function forgeXapiObject() {
|
||||
};
|
||||
}
|
||||
|
||||
// verbId/verbDisplay : voir FORGE_TO_XAPI_VERB, ou directement les
|
||||
// verbes "initialized"/"terminated" (posés une seule fois chacun, voir
|
||||
// plus bas) — result : objet xAPI "result" optionnel (score/completion/
|
||||
// success), fusionné tel quel dans le statement.
|
||||
function forgeXapiSendStatement(verbId, verbDisplay, result) {
|
||||
// Sous-activités xAPI (voir static/js/play/dialogue-box-controller.js) :
|
||||
// une quête/question n'est PAS l'activité de premier niveau du jeu
|
||||
// (forgeXapiObject ci-dessus) mais une activité fille, identifiée par un
|
||||
// id dérivé — assez pour que le LRS les distingue sans configuration
|
||||
// supplémentaire côté créateur du jeu.
|
||||
function forgeXapiQuestObject(quest) {
|
||||
var config = window.FORGE_XAPI_CONFIG;
|
||||
return {
|
||||
objectType: "Activity",
|
||||
id: config.activity_id + ":quest:" + quest.id,
|
||||
definition: { name: { "fr-FR": quest.title || "Quête" }, type: "http://adlnet.gov/expapi/activities/simulation" },
|
||||
};
|
||||
}
|
||||
|
||||
function forgeXapiQuestionObject(quest, questionIndex, questionText) {
|
||||
var config = window.FORGE_XAPI_CONFIG;
|
||||
return {
|
||||
objectType: "Activity",
|
||||
id: config.activity_id + ":quest:" + quest.id + ":question:" + questionIndex,
|
||||
definition: {
|
||||
name: { "fr-FR": "Question " + (questionIndex + 1) + " — " + (quest.title || "Quête") },
|
||||
description: { "fr-FR": questionText || "" },
|
||||
type: "http://adlnet.gov/expapi/activities/cmi.interaction",
|
||||
interactionType: "choice",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// verbId : voir FORGE_TO_XAPI_VERB/FORGE_XAPI_VERB_DISPLAY (le libellé
|
||||
// affiché est dérivé automatiquement du verbe, jamais passé en argument)
|
||||
// — result : objet xAPI "result" optionnel (score/completion/success),
|
||||
// fusionné tel quel dans le statement. object : l'activité visée par CE
|
||||
// statement — l'activité de premier niveau du jeu par défaut
|
||||
// (forgeXapiObject), ou une sous-activité quête/question
|
||||
// (forgeXapiQuestObject/forgeXapiQuestionObject) pour un suivi plus fin.
|
||||
function forgeXapiSendStatement(verbId, result, object) {
|
||||
var config = window.FORGE_XAPI_CONFIG;
|
||||
if (!config || !config.endpoint) return;
|
||||
var statement = {
|
||||
actor: forgeXapiActor(),
|
||||
verb: { id: verbId, display: { "en-US": verbDisplay } },
|
||||
object: forgeXapiObject(),
|
||||
verb: { id: verbId, display: FORGE_XAPI_VERB_DISPLAY[verbId] || { "en-US": verbId } },
|
||||
object: object || forgeXapiObject(),
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
if (result) statement.result = result;
|
||||
@@ -92,17 +141,51 @@ function forgeXapiSendStatement(verbId, verbDisplay, result) {
|
||||
function forgeXapiNotifyScoreChanged(newScore) {
|
||||
if (!window.FORGE_XAPI_CONFIG) return;
|
||||
forgeXapiSendStatement(
|
||||
"http://adlnet.gov/expapi/verbs/scored", "scored",
|
||||
"http://adlnet.gov/expapi/verbs/scored",
|
||||
{ score: { raw: newScore } }
|
||||
);
|
||||
}
|
||||
|
||||
// Granularité quête/quiz (voir dialogue-box-controller.js) : demande
|
||||
// explicite "il faut savoir quand une quête a été acceptée, à telle
|
||||
// question quel point obtenu, bien répondu ou non" — un statement DÉDIÉ
|
||||
// par évènement, distinct du score/statut global (forgeXapiNotify
|
||||
// ScoreChanged/StatusChanged ci-dessus, qui restent l'activité de
|
||||
// premier niveau).
|
||||
function forgeXapiNotifyQuestAccepted(quest) {
|
||||
if (!window.FORGE_XAPI_CONFIG) return;
|
||||
// "attempted" (ADL standard) : pas de verbe dédié "accepted" dans le
|
||||
// vocabulaire ADL de base — attempted rend fidèlement "le joueur s'est
|
||||
// engagé sur cette quête".
|
||||
forgeXapiSendStatement(
|
||||
"http://adlnet.gov/expapi/verbs/attempted",
|
||||
null, forgeXapiQuestObject(quest)
|
||||
);
|
||||
}
|
||||
|
||||
function forgeXapiNotifyQuestCompleted(quest) {
|
||||
if (!window.FORGE_XAPI_CONFIG) return;
|
||||
forgeXapiSendStatement(
|
||||
"http://adlnet.gov/expapi/verbs/completed",
|
||||
{ completion: true }, forgeXapiQuestObject(quest)
|
||||
);
|
||||
}
|
||||
|
||||
function forgeXapiNotifyQuestionAnswered(quest, questionIndex, questionText, correct, scoreAwarded) {
|
||||
if (!window.FORGE_XAPI_CONFIG) return;
|
||||
forgeXapiSendStatement(
|
||||
"http://adlnet.gov/expapi/verbs/answered",
|
||||
{ success: correct, score: { raw: scoreAwarded || 0 } },
|
||||
forgeXapiQuestionObject(quest, questionIndex, questionText)
|
||||
);
|
||||
}
|
||||
|
||||
function forgeXapiNotifyStatusChanged(newStatus) {
|
||||
if (!window.FORGE_XAPI_CONFIG) return;
|
||||
var verb = FORGE_TO_XAPI_VERB[newStatus];
|
||||
if (!verb || newStatus === forgeXapiLastStatusSent) return;
|
||||
forgeXapiLastStatusSent = newStatus;
|
||||
forgeXapiSendStatement(verb.id, verb.display, {
|
||||
forgeXapiSendStatement(verb.id, {
|
||||
completion: true,
|
||||
success: newStatus !== "echoue",
|
||||
});
|
||||
@@ -110,13 +193,17 @@ function forgeXapiNotifyStatusChanged(newStatus) {
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("load", function () {
|
||||
if (window.FORGE_XAPI_CONFIG) forgeXapiSendStatement("http://adlnet.gov/expapi/verbs/initialized", "initialized");
|
||||
if (window.FORGE_XAPI_CONFIG) forgeXapiSendStatement("http://adlnet.gov/expapi/verbs/initialized");
|
||||
});
|
||||
window.addEventListener("beforeunload", function () {
|
||||
if (window.FORGE_XAPI_CONFIG) forgeXapiSendStatement("http://adlnet.gov/expapi/verbs/terminated", "terminated");
|
||||
if (window.FORGE_XAPI_CONFIG) forgeXapiSendStatement("http://adlnet.gov/expapi/verbs/terminated");
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports = { forgeXapiSendStatement, forgeXapiNotifyScoreChanged, forgeXapiNotifyStatusChanged, FORGE_TO_XAPI_VERB };
|
||||
module.exports = {
|
||||
forgeXapiSendStatement, forgeXapiNotifyScoreChanged, forgeXapiNotifyStatusChanged, FORGE_TO_XAPI_VERB,
|
||||
forgeXapiNotifyQuestAccepted, forgeXapiNotifyQuestCompleted, forgeXapiNotifyQuestionAnswered,
|
||||
forgeXapiQuestObject, forgeXapiQuestionObject, FORGE_XAPI_VERB_DISPLAY,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user