Ajoute un suivi xAPI optionnel (bolt-on) au paquet SCORM exporté
Greffe l'envoi de statements xAPI vers un LRS configurable par le créateur du jeu, en parallèle du reporting SCORM existant : réglages stockés en base (_meta), formulaire dans la modale d'export, injection dans le paquet exporté. Relié aussi bien à l'action de flow "Modifier un score/statut" qu'au parcours quête/quiz (qui alimentait déjà le SCORM classique via un chemin séparé). L'export ne se lance plus automatiquement à l'ouverture de la modale, pour laisser le temps d'enregistrer les réglages xAPI avant de générer le paquet.
This commit is contained in:
+83
-3
@@ -114,14 +114,44 @@
|
||||
<div class="box">
|
||||
<h2 class="title is-4">Exporter « {{ game.name }} » en Web/SCORM</h2>
|
||||
<p class="hint mb-4">Construit un paquet SCORM 1.2 (imsmanifest.xml + page web autonome, aucun serveur requis) : importe le .zip obtenu directement dans ton LMS (Moodle...) — le score et le statut de la partie remontent automatiquement.</p>
|
||||
<div id="scormProgress">
|
||||
<!-- Suivi xAPI (voir publish/build_scorm_package.py, routes/publish/
|
||||
xapi_settings.py) : greffé sur le SCORM ci-dessus, jamais à sa
|
||||
place — un réglage jamais renseigné (endpoint vide) désactive
|
||||
entièrement xAPI pour ce jeu, le SCORM continue seul. Réglé
|
||||
séparément du bouton d'export : ces valeurs sont déjà en base
|
||||
au moment où on clique "Exporter", jamais retapées à chaque export. -->
|
||||
<details class="propGroup mb-4" id="xapiSettingsGroup">
|
||||
<summary>Suivi xAPI (optionnel)</summary>
|
||||
<div class="propGroupBody">
|
||||
<p class="help mb-2">Envoie en plus les résultats vers un LRS (Learning Record Store) externe, en parallèle du SCORM ci-dessus. Utilise une clé API dédiée à cet export, jamais tes identifiants LRS principaux.</p>
|
||||
<div class="field">
|
||||
<label class="label is-small">URL du LRS</label>
|
||||
<div class="control"><input class="input is-small" type="text" id="xapiEndpoint" placeholder="https://mon-lrs.exemple.com/xAPI"></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label is-small">Login</label>
|
||||
<div class="control"><input class="input is-small" type="text" id="xapiLogin" autocomplete="off"></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label is-small">Mot de passe</label>
|
||||
<div class="control"><input class="input is-small" type="password" id="xapiPassword" autocomplete="new-password"></div>
|
||||
<p class="help" id="xapiPasswordHint"></p>
|
||||
</div>
|
||||
<div class="field is-grouped" style="align-items:center;">
|
||||
<div class="control"><button type="button" class="button is-small" onclick="window.forgeSaveXapiSettings()">Enregistrer</button></div>
|
||||
<span class="hint" id="xapiSaveStatus"></span>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<div id="scormProgress" style="display:none;">
|
||||
<p class="hint" id="scormStepText">Préparation du jeu...</p>
|
||||
<div class="passwordStrengthBar" style="margin-top:10px;"><div class="passwordStrengthFill strong" id="scormProgressFill" style="width:100%; animation:forgePublishPulse 1.2s ease-in-out infinite;"></div></div>
|
||||
</div>
|
||||
<div id="scormError" class="notification is-danger" style="display:none;"></div>
|
||||
<div id="scormDone" class="notification is-success" style="display:none;">Téléchargement lancé — importe directement le .zip dans ton LMS.</div>
|
||||
<div class="field mt-4" style="display:flex; justify-content:flex-end;">
|
||||
<div class="field mt-4" style="display:flex; justify-content:flex-end; gap:8px;">
|
||||
<button type="button" class="button is-small" onclick="window.forgeCloseScormModal()">Fermer</button>
|
||||
<button type="button" class="button primary is-small" id="scormExportButton" onclick="window.forgeStartScormExport()">Exporter (Web/SCORM)</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -136,13 +166,63 @@
|
||||
var steps = ["Préparation du jeu...", "Génération du paquet hors ligne...", "Écriture du manifeste SCORM..."];
|
||||
var stepTimer = null;
|
||||
|
||||
var xapiEndpoint = document.getElementById('xapiEndpoint');
|
||||
var xapiLogin = document.getElementById('xapiLogin');
|
||||
var xapiPassword = document.getElementById('xapiPassword');
|
||||
var xapiPasswordHint = document.getElementById('xapiPasswordHint');
|
||||
var xapiSaveStatus = document.getElementById('xapiSaveStatus');
|
||||
|
||||
function forgeLoadXapiSettings() {
|
||||
fetch("{{ url_for('xapi_settings_get', slug=game.slug) }}")
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (json) {
|
||||
xapiEndpoint.value = json.endpoint || '';
|
||||
xapiLogin.value = json.login || '';
|
||||
xapiPassword.value = '';
|
||||
xapiPasswordHint.textContent = json.password_set
|
||||
? 'Mot de passe déjà enregistré — laisse ce champ vide pour le garder.' : '';
|
||||
});
|
||||
}
|
||||
|
||||
// Réglages, jamais retapés à chaque export : sauvegardés indépendamment
|
||||
// du bouton "Exporter" ci-dessous (voir routes/publish/xapi_settings.py).
|
||||
window.forgeSaveXapiSettings = function () {
|
||||
xapiSaveStatus.textContent = 'Enregistrement...';
|
||||
fetch("{{ url_for('xapi_settings_set', slug=game.slug) }}", {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
endpoint: xapiEndpoint.value.trim(), login: xapiLogin.value.trim(),
|
||||
password: xapiPassword.value || null,
|
||||
}),
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (json) {
|
||||
xapiSaveStatus.textContent = json.ok ? 'Enregistré ✓' : 'Échec de l\'enregistrement.';
|
||||
forgeLoadXapiSettings();
|
||||
})
|
||||
.catch(function () { xapiSaveStatus.textContent = 'Échec de l\'enregistrement.'; });
|
||||
};
|
||||
|
||||
// Ouvrir la modale ne lance PLUS l'export tout de suite : le temps que
|
||||
// l'utilisateur renseigne (et surtout enregistre, bouton séparé
|
||||
// ci-dessus) ses réglages xAPI serait sinon une course contre le fetch
|
||||
// d'export, qui pourrait partir avec des réglages pas encore sauvegardés.
|
||||
// L'export est déclenché explicitement via forgeStartScormExport().
|
||||
window.forgeOpenScormModal = function () {
|
||||
progressBox.style.display = 'none';
|
||||
errorBox.style.display = 'none';
|
||||
doneBox.style.display = 'none';
|
||||
xapiSaveStatus.textContent = '';
|
||||
forgeLoadXapiSettings();
|
||||
modal.classList.add('is-active');
|
||||
};
|
||||
|
||||
window.forgeStartScormExport = function () {
|
||||
var stepIndex = 0;
|
||||
stepText.textContent = steps[0];
|
||||
progressBox.style.display = '';
|
||||
errorBox.style.display = 'none';
|
||||
doneBox.style.display = 'none';
|
||||
modal.classList.add('is-active');
|
||||
|
||||
stepTimer = setInterval(function () {
|
||||
stepIndex = (stepIndex + 1) % steps.length;
|
||||
|
||||
Reference in New Issue
Block a user