Export Web/SCORM : port complet du runtime jouable côté navigateur
Build and deploy / test-python (push) Successful in 9m20s
Build and deploy / test-js (push) Successful in 1m6s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Phase 1.2 de la feuille de route produit — un paquet SCORM tourne seul
dans le LMS du client, sans serveur Forge Engine disponible. Ajoute
static/js/play/offline/ (miroir JS de screens/rendering/ et
data_actions/, sous window.FORGE_OFFLINE) pour que variables, score,
données d'objet, répéteurs et conditions de visibilité fonctionnent
entièrement en mémoire côté navigateur ; branche actions.js et
bindings.js dessus au lieu d'un fetch() serveur. Ajoute
publish/build_scorm_package.py (paquet statique + imsmanifest.xml
SCORM 1.2 + wrapper API SCORM) et le bouton "Exporter (Web/SCORM)".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-02 07:43:02 +02:00
co-authored by Claude Sonnet 5
parent 9776fbc057
commit f3b72d73c9
32 changed files with 2469 additions and 22 deletions
+32 -11
View File
@@ -4,12 +4,12 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ game.name }} — Jouer</title>
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='branding/forge-favicon.svg') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link rel="icon" type="image/svg+xml" href="{{ asset_url('branding/forge-favicon.svg') }}">
<link rel="stylesheet" href="{{ asset_url('style.css') }}">
<script>window.CSRF_TOKEN = {{ csrf_token()|tojson }};</script>
<script src="{{ url_for('static', filename='csrf_fetch.js') }}"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='vendor/fonts/fonts.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='vendor/animate.min.css') }}"><style>
<script src="{{ asset_url('csrf_fetch.js') }}"></script>
<link rel="stylesheet" href="{{ asset_url('vendor/fonts/fonts.css') }}">
<link rel="stylesheet" href="{{ asset_url('vendor/animate.min.css') }}"><style>
body{ margin:0; background:#000; }
/* ---------- Zone de jeu à proportion fixe (voir #playFrame) ----------
Chaque écran est conçu avec un format précis (Portrait/Paysage/Carré,
@@ -113,6 +113,13 @@
// distinctes (voir état par joueur, Phase 1).
let gameData = {{ payload_json|safe }};
let screensData = gameData.screens;
{% if offline_mode %}
// Export Web/SCORM (voir publish/build_scorm_package.py) : paquet
// autonome sans serveur Forge Engine — actions.js/bindings.js basculent
// sur static/js/play/offline/*.js (ci-dessous) au lieu d'un fetch().
window.FORGE_OFFLINE = true;
var FORGE_PLAY_URLS = {};
{% else %}
var FORGE_PLAY_URLS = {
runtimePayload: {{ runtime_payload_url|tojson }},
flowNodeRunData: {{ flow_node_run_data_url|tojson }},
@@ -121,13 +128,27 @@
flowNodeRunStatus: {{ flow_node_run_status_url|tojson }},
flowNodeRunAddRow: {{ flow_node_run_add_row_url|tojson }},
};
{% endif %}
</script>
<script src="{{ url_for('static', filename='js/play/screens.js') }}"></script>
<script src="{{ url_for('static', filename='js/play/conditions.js') }}"></script>
<script src="{{ url_for('static', filename='js/play/actions.js') }}"></script>
<script src="{{ url_for('static', filename='js/play/triggers.js') }}"></script>
<script src="{{ url_for('static', filename='js/play/bindings.js') }}"></script>
<script src="{{ url_for('static', filename='js/play/flow-engine.js') }}"></script>
<script src="{{ asset_url('js/play/screens.js') }}"></script>
<script src="{{ asset_url('js/play/conditions.js') }}"></script>
{% if offline_mode %}
<script src="{{ asset_url('js/play/offline/compute-operation.js') }}"></script>
<script src="{{ asset_url('js/play/offline/apply-actions.js') }}"></script>
<script src="{{ asset_url('js/play/offline/filter-repeater-rows.js') }}"></script>
<script src="{{ asset_url('js/play/offline/visibility-condition.js') }}"></script>
<script src="{{ asset_url('js/play/offline/render-jauge.js') }}"></script>
<script src="{{ asset_url('js/play/offline/render-overlay.js') }}"></script>
<script src="{{ asset_url('js/play/offline/render-special-widgets.js') }}"></script>
<script src="{{ asset_url('js/play/offline/render-repeater.js') }}"></script>
<script src="{{ asset_url('js/play/offline/render-element.js') }}"></script>
<script src="{{ asset_url('js/play/offline/full-payload.js') }}"></script>
{% if scorm_api_wrapper_url %}<script src="{{ scorm_api_wrapper_url }}"></script>{% endif %}
{% endif %}
<script src="{{ asset_url('js/play/actions.js') }}"></script>
<script src="{{ asset_url('js/play/triggers.js') }}"></script>
<script src="{{ asset_url('js/play/bindings.js') }}"></script>
<script src="{{ asset_url('js/play/flow-engine.js') }}"></script>
<script>
// L'écran affiché au démarrage doit être choisi AVANT le premier
// bindClicks() : la recherche du nœud Déclencheur associé à un élément se