Retire l'export exécutable (.exe) et la partie publique en ligne (/jouer)
Build and deploy / test-python (push) Successful in 7m5s
Build and deploy / test-js (push) Successful in 58s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Décision produit : seul l'export Web/SCORM (LMS) est pertinent — l'export
exécutable Windows autonome (publish/build_package.py, bouton "Publier")
et la partie publique par joueur (/jouer/<slug>, routes/public_play/,
"Publier en ligne") sont jugés redondants et retirés.

Conserve le mécanisme d'état "par joueur" (db/global_vars,
db/rows, per_player) : infrastructure générique déjà utilisée par
Score/Progression et testée indépendamment de toute route publique
(voir tests/test_player_state.py), aucune raison de la retirer.

_STATIC_ITEMS/_copy_characters (copie sélective des sprites CraftPix
réellement utilisés) migrent de build_package.py vers
build_scorm_package.py, seul appelant restant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-02 10:59:28 +02:00
co-authored by Claude Sonnet 5
parent 82d00b3800
commit e82eb7ce88
37 changed files with 115 additions and 1106 deletions
+10 -99
View File
@@ -54,26 +54,14 @@
(quiz/embranchement/rpg), la version complète pour "custom". -->
<a href="{{ url_for('game_dashboard', slug=game.slug) }}" class="gameNavLink {{ 'active' if request.path == '/game/' ~ game.slug else '' }}" title="{{ 'Écrans du jeu' if game.onboarding_type in ('quiz', 'embranchement', 'rpg') else 'Écrans, objets, templates, variables' }}">📊 Tableau de bord</a>
<a href="{{ url_for('game_play', slug=game.slug) }}" target="_blank" class="gameNavLink" title="Aperçu jouable du jeu (une seule partie partagée, pour la conception)">▶️ Jouer</a>
<button type="button" class="gameNavLink" onclick="window.forgeOpenPublishModal && window.forgeOpenPublishModal()" title="Exporte le jeu en exécutable Windows autonome (zip)">📦 Publier</button>
<!-- Phase 1.2 (feuille de route produit) : paquet STATIQUE (aucun
Python/Flask embarqué, voir publish/build_scorm_package.py), pensé
pour un import dans le LMS du client (Moodle...) — distinct du
zip exécutable ci-dessus, qui a besoin de Windows + double-clic. -->
<!-- Phase 1.2 (feuille de route produit) : seul export retenu — paquet
STATIQUE (aucun Python/Flask embarqué, voir
publish/build_scorm_package.py), pensé pour un import dans le LMS
du client (Moodle...). L'export exécutable Windows ("Publier",
publish/build_package.py) et l'export en ligne (route publique
/jouer/<slug>, routes/public_play/) ont été retirés, jugés
redondants avec celui-ci. -->
<button type="button" class="gameNavLink" onclick="window.forgeOpenScormModal && window.forgeOpenScormModal()" title="Exporte le jeu en paquet Web/SCORM importable dans un LMS">🎓 Exporter (Web/SCORM)</button>
<!-- "Publier en ligne" (état par joueur, Phase 1) : distinct de
"Publier" ci-dessus (export .zip mono-joueur) — active/désactive la
route publique /jouer/<slug>, jouable par n'importe quel visiteur
anonyme avec SA PROPRE partie (voir routes/public_play/,
db/games/is_public_played.py). -->
<form method="post" action="{{ url_for('game_toggle_public_played', slug=game.slug) }}" style="display:inline;">
<input type="hidden" name="value" value="{{ '0' if game.is_public_played else '1' }}">
<button type="submit" class="gameNavLink" title="{{ 'Rendre le jeu privé (désactive /jouer)' if game.is_public_played else 'Rendre le jeu jouable par tous, chacun avec sa propre partie' }}">
{{ '🌐 En ligne ✓' if game.is_public_played else '🌐 Publier en ligne' }}
</button>
</form>
{% if game.is_public_played %}
<a href="{{ url_for('game_play_public', slug=game.slug) }}" target="_blank" class="gameNavLink" title="Ouvre la partie publique, comme un vrai visiteur">🔗 Lien public</a>
{% endif %}
</nav>
{% endif %}
</div>
@@ -107,89 +95,12 @@
<!-- Même remarque que pour le modal des codes de récupération ci-dessus : à L'INTÉRIEUR
de <main> pour que pjax.js le remplace (et rejoue son script JS, voir
runScriptsIn()) à chaque navigation entre pages d'un même jeu — posé
dans #pageChrome (comme le bouton "📦 Publier" qui l'ouvre), son
script ne serait rejoué qu'au tout premier chargement complet de la
page. -->
<div class="modal" id="publishModal">
<div class="modal-background"></div>
<div class="modal-content">
<div class="box">
<h2 class="title is-4">Publier « {{ game.name }} »</h2>
<p class="hint mb-4">Construit un exécutable Windows autonome (Python + serveur embarqués) : dézippé puis lancé en double-cliquant sur <code>run.bat</code>, le jeu se joue hors connexion, sans rien installer.</p>
<div id="publishProgress">
<p class="hint" id="publishStepText">Préparation du jeu...</p>
<div class="passwordStrengthBar" style="margin-top:10px;"><div class="passwordStrengthFill strong" id="publishProgressFill" style="width:100%; animation:forgePublishPulse 1.2s ease-in-out infinite;"></div></div>
</div>
<div id="publishError" class="notification is-danger" style="display:none;"></div>
<div id="publishDone" class="notification is-success" style="display:none;">✅ Téléchargement lancé — dézippe le fichier puis double-clique sur <code>run.bat</code>.</div>
<div class="field mt-4" style="display:flex; justify-content:flex-end;">
<button type="button" class="button is-small" onclick="window.forgeClosePublishModal()">Fermer</button>
</div>
</div>
</div>
</div>
dans #pageChrome (comme le bouton "🎓 Exporter (Web/SCORM)" qui
l'ouvre), son script ne serait rejoué qu'au tout premier chargement
complet de la page. -->
<style>
@keyframes forgePublishPulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
</style>
<script>
(function () {
var modal = document.getElementById('publishModal');
var stepText = document.getElementById('publishStepText');
var progressBox = document.getElementById('publishProgress');
var errorBox = document.getElementById('publishError');
var doneBox = document.getElementById('publishDone');
var steps = ["Préparation du jeu...", "Copie des données...", "Compression du dossier..."];
var stepTimer = null;
window.forgeOpenPublishModal = 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;
stepText.textContent = steps[stepIndex];
}, 1200);
var filename = "{{ game.slug }}.zip";
fetch("{{ url_for('publish_game', slug=game.slug) }}", { method: 'POST' })
.then(function (resp) {
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var disposition = resp.headers.get('Content-Disposition') || '';
var match = /filename="?([^";]+)"?/.exec(disposition);
if (match) filename = match[1];
return resp.blob();
})
.then(function (blob) {
clearInterval(stepTimer);
progressBox.style.display = 'none';
doneBox.style.display = '';
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(function () { URL.revokeObjectURL(url); }, 4000);
})
.catch(function (err) {
clearInterval(stepTimer);
progressBox.style.display = 'none';
errorBox.style.display = '';
errorBox.textContent = "Échec de la publication (" + err.message + "). Réessaie, ou vérifie ta connexion internet (nécessaire uniquement lors de la toute première publication sur ce poste).";
});
};
window.forgeClosePublishModal = function () {
modal.classList.remove('is-active');
if (stepTimer) clearInterval(stepTimer);
};
})();
</script>
<div class="modal" id="scormModal">
<div class="modal-background"></div>
<div class="modal-content">