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>
111 lines
5.4 KiB
JavaScript
111 lines
5.4 KiB
JavaScript
// Export Web/SCORM — port de screens/rendering/render_jauge.py (jauge liée
|
|
// à une donnée : remplissage % + dégradé de couleur recalculés depuis un
|
|
// champ numérique d'un objet). Lit gameData.data/fields_meta directement
|
|
// au lieu d'une requête SQL (voir _read_field_value côté serveur).
|
|
|
|
function forgeHexToRgb(hexColor) {
|
|
var hex = (hexColor || "#5b8cff").replace(/^#/, "");
|
|
if (hex.length !== 6) hex = "5b8cff";
|
|
return [parseInt(hex.substr(0, 2), 16), parseInt(hex.substr(2, 2), 16), parseInt(hex.substr(4, 2), 16)];
|
|
}
|
|
|
|
function forgeInterpolateColor(lowHex, highHex, ratio) {
|
|
var lo = forgeHexToRgb(lowHex), hi = forgeHexToRgb(highHex);
|
|
var mixed = lo.map(function (c, i) { return Math.round(c + (hi[i] - c) * ratio); });
|
|
var toHex = function (n) { return n.toString(16).padStart(2, "0"); };
|
|
return "#" + mixed.map(toHex).join("");
|
|
}
|
|
|
|
// Port de _read_field_value — repli sur la ligne la PLUS RÉCENTE si
|
|
// row_id est absent/introuvable (comportement historique, un objet à une
|
|
// seule ligne n'est jamais impacté).
|
|
function forgeReadFieldValue(gameData, definitionId, rowId, fieldName) {
|
|
if (!definitionId || !fieldName) return null;
|
|
definitionId = String(definitionId);
|
|
var rows = gameData.data && gameData.data[definitionId];
|
|
if (!rows || !rows.length) return null;
|
|
var row = null;
|
|
if (rowId) row = rows.find(function (r) { return String(r.id) === String(rowId); });
|
|
if (!row) row = rows[0];
|
|
return row[fieldName];
|
|
}
|
|
|
|
function forgeRenderJauge(el, meta, gameData, childrenMap, ctx, parentFlexDirection) {
|
|
var attrs = el.attributes || {};
|
|
var nomPosition = attrs._nom_position || "dessus";
|
|
var nomAlignement = attrs._nom_alignement || "centre";
|
|
var nomPolice = attrs._nom_police || "inherit";
|
|
var nomTaille = attrs._nom_taille || "12";
|
|
var nomGras = attrs._nom_gras === "1";
|
|
var nomItalique = attrs._nom_italique === "1";
|
|
var flexDir = nomPosition === "cote" ? "row" : "column";
|
|
var alignItems = nomPosition === "cote" ? "center" : "stretch";
|
|
var style = forgeStyleString(
|
|
el, parentFlexDirection, el.widget
|
|
) + " display:flex; flex-direction:" + flexDir + "; align-items:" + alignItems + "; justify-content:center; gap:4px;";
|
|
|
|
var definitionId = attrs._definition_id;
|
|
var rowId = attrs._row_id;
|
|
var fieldName = (attrs._champ || "").trim();
|
|
var fieldNameNom = (attrs._champ_nom || "").trim();
|
|
var minV = parseFloat(attrs._min); if (isNaN(minV)) minV = 0;
|
|
var maxV = parseFloat(attrs._max); if (isNaN(maxV)) maxV = 100;
|
|
var couleurBasse = attrs._couleur_basse || "#e05252";
|
|
var couleurHaute = attrs._couleur_haute || "#4caf50";
|
|
var afficherValeur = attrs._afficher_valeur === "1";
|
|
|
|
var rawValue = fieldName ? forgeReadFieldValue(gameData, definitionId, rowId, fieldName) : null;
|
|
var value = parseFloat(rawValue);
|
|
if (isNaN(value)) value = minV;
|
|
|
|
var span = (maxV - minV) || 1;
|
|
var ratio = Math.max(0, Math.min(1, (value - minV) / span));
|
|
var percent = Math.round(ratio * 1000) / 10;
|
|
var fillColor = forgeInterpolateColor(couleurBasse, couleurHaute, ratio);
|
|
|
|
var labelHtml = "";
|
|
if (afficherValeur) {
|
|
labelHtml =
|
|
'<div style="position:absolute; inset:0; display:flex; align-items:center; ' +
|
|
'justify-content:center; font-family:system-ui,sans-serif; font-size:12px; ' +
|
|
'color:#e8eaf0; text-shadow:0 1px 2px rgba(0,0,0,.6);">' +
|
|
forgeHtmlEscape(rawValue !== null && rawValue !== undefined ? String(rawValue) : "—") + '</div>';
|
|
}
|
|
|
|
var nameHtml = "";
|
|
if (fieldNameNom) {
|
|
var nomValue = forgeReadFieldValue(gameData, definitionId, rowId, fieldNameNom);
|
|
if (nomValue !== null && nomValue !== undefined && nomValue !== "") {
|
|
var textAlign = (nomPosition === "dessus" && nomAlignement === "gauche") ? "left" : "center";
|
|
nameHtml =
|
|
'<div style="flex:0 0 auto; font-family:' + forgeHtmlEscape(nomPolice) + '; ' +
|
|
'font-size:' + forgeHtmlEscape(String(nomTaille)) + 'px; ' +
|
|
'font-weight:' + (nomGras ? "700" : "400") + '; ' +
|
|
'font-style:' + (nomItalique ? "italic" : "normal") + '; ' +
|
|
'color:#e8eaf0; text-align:' + textAlign + ';">' + forgeHtmlEscape(String(nomValue)) + '</div>';
|
|
}
|
|
}
|
|
|
|
var extraClasses = Object.keys(attrs)
|
|
.filter(function (k) { return k.indexOf("_class_") === 0 && attrs[k]; })
|
|
.map(function (k) { return attrs[k]; }).join(" ");
|
|
var barClass = forgeHtmlEscape(("progress jaugeBar " + extraClasses).trim());
|
|
var bar =
|
|
'<progress class="' + barClass + '" value="' + percent + '" max="100" ' +
|
|
'style="--bulma-progress-value-background-color:' + fillColor + '; --bulma-progress-bar-background-color:#1c2130; ' +
|
|
'margin:0; border-radius:6px; transition:background-color .3s ease;">' + percent + '%</progress>';
|
|
var barWrap =
|
|
'<div style="position:relative; display:flex; align-items:center; ' +
|
|
'flex:1 1 auto; min-height:14px;">' + bar + labelHtml + '</div>';
|
|
return '<div id="' + forgeAutoId(el) + '" data-element-id="' + el.id + '" style="' + forgeHtmlEscape(style) + '">' +
|
|
nameHtml + barWrap + '</div>';
|
|
}
|
|
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = { forgeRenderJauge, forgeHexToRgb, forgeInterpolateColor, forgeReadFieldValue };
|
|
var renderMod = require('./render-element.js');
|
|
global.forgeStyleString = global.forgeStyleString || renderMod.forgeStyleString;
|
|
global.forgeHtmlEscape = global.forgeHtmlEscape || renderMod.forgeHtmlEscape;
|
|
global.forgeAutoId = global.forgeAutoId || renderMod.forgeAutoId;
|
|
}
|