Export Web/SCORM : port complet du runtime jouable côté navigateur
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:
co-authored by
Claude Sonnet 5
parent
9776fbc057
commit
f3b72d73c9
@@ -0,0 +1,77 @@
|
||||
// Export Web/SCORM — port de screens/rendering/render_repeater.py. Lit
|
||||
// gameData.data/fields_meta (au lieu d'une requête SQL) et
|
||||
// gameData.element_type_templates (voir full_game_payload.py — l'arbre
|
||||
// d'un écran-modèle d'élément de jeu réutilisable, précalculé une fois à
|
||||
// l'export) au lieu de screens/element_types/load_template_tree.py.
|
||||
function forgeRenderRepeater(el, meta, gameData, childrenMap, ctx, parentFlexDirection) {
|
||||
var style = forgeStyleString(el, parentFlexDirection, el.widget) + " overflow:auto;";
|
||||
var attrsRaw = el.attributes || {};
|
||||
var definitionId = attrsRaw._definition_id;
|
||||
var definitionAttr = definitionId ? ' data-definition-id="' + forgeHtmlEscape(String(definitionId)) + '"' : "";
|
||||
var templateTypeId = attrsRaw._template_type_id;
|
||||
var template = forgeApplyCtx(el.content || "", ctx);
|
||||
var children = (childrenMap && childrenMap[el.id]) || [];
|
||||
|
||||
if (!definitionId) {
|
||||
return '<div id="' + forgeAutoId(el) + '" data-element-id="' + el.id + '" style="' + forgeHtmlEscape(style) +
|
||||
'"><em>Répéteur : choisis un objet de données dans les propriétés.</em></div>';
|
||||
}
|
||||
definitionId = String(definitionId);
|
||||
var fieldsMetaForDef = gameData.fields_meta && gameData.fields_meta[definitionId];
|
||||
if (!fieldsMetaForDef) {
|
||||
return '<div id="' + forgeAutoId(el) + '" data-element-id="' + el.id + '" style="' + forgeHtmlEscape(style) +
|
||||
'"><em>Objet introuvable — a-t-il été supprimé ?</em></div>';
|
||||
}
|
||||
|
||||
var rows = gameData.data[definitionId] || [];
|
||||
rows = forgeFilterRepeaterRows(gameData, definitionId, rows, attrsRaw);
|
||||
var fieldCols = {};
|
||||
fieldsMetaForDef.forEach(function (f) { fieldCols[f.name] = forgeFieldColumn(fieldsMetaForDef, f.name); });
|
||||
|
||||
var templateTree = null;
|
||||
if (templateTypeId) {
|
||||
templateTree = gameData.element_type_templates && gameData.element_type_templates[String(templateTypeId)];
|
||||
}
|
||||
|
||||
var items = rows.map(function (row) {
|
||||
var rowCtx = Object.assign({}, ctx || {});
|
||||
Object.keys(fieldCols).forEach(function (fname) {
|
||||
var v = row[fieldCols[fname]];
|
||||
rowCtx[fname] = v === null || v === undefined ? "" : String(v);
|
||||
});
|
||||
var inner;
|
||||
if (templateTree) {
|
||||
inner = templateTree.top.map(function (c) {
|
||||
return forgeRenderElementHtml(c, gameData, templateTree.children_map, rowCtx);
|
||||
}).join("");
|
||||
} else if (children.length) {
|
||||
inner = children.map(function (c) {
|
||||
return forgeRenderElementHtml(c, gameData, childrenMap, rowCtx);
|
||||
}).join("");
|
||||
} else {
|
||||
var line = template;
|
||||
Object.keys(rowCtx).forEach(function (fname) {
|
||||
if (fname === "_forge_play_mode") return;
|
||||
line = line.split("{{" + fname + "}}").join(rowCtx[fname]);
|
||||
});
|
||||
inner = forgeHtmlEscape(line);
|
||||
}
|
||||
return '<div class="repeaterItem" data-row-id="' + row.id + '">' + inner + '</div>';
|
||||
});
|
||||
|
||||
var emptyMsg = attrsRaw._filtre_champ ? "Aucune ligne ne correspond au filtre réglé." : "Aucune donnée dans cet objet pour l'instant.";
|
||||
var innerAll = items.length ? items.join("") : "<em>" + emptyMsg + "</em>";
|
||||
return '<div id="' + forgeAutoId(el) + '" data-element-id="' + el.id + '"' + definitionAttr +
|
||||
' style="' + forgeHtmlEscape(style) + '">' + innerAll + '</div>';
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { forgeRenderRepeater };
|
||||
var renderMod = require('./render-element.js');
|
||||
["forgeStyleString", "forgeHtmlEscape", "forgeAutoId", "forgeApplyCtx", "forgeRenderElementHtml"].forEach(function (name) {
|
||||
global[name] = global[name] || renderMod[name];
|
||||
});
|
||||
var filterMod = require('./filter-repeater-rows.js');
|
||||
global.forgeFilterRepeaterRows = global.forgeFilterRepeaterRows || filterMod.forgeFilterRepeaterRows;
|
||||
global.forgeFieldColumn = global.forgeFieldColumn || filterMod.forgeFieldColumn;
|
||||
}
|
||||
Reference in New Issue
Block a user