Files
Forge-Engine/static/js/play/offline/render-jauge.js
T
williamandClaude Sonnet 5 f3b72d73c9
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
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>
2026-09-02 07:43:02 +02:00

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;
}