// Export Web/SCORM (voir le plan "port complet du runtime jouable côté // navigateur") — étape 4 : port des widgets à "special_render" les plus // simples (screens/rendering/render_checkbox_or_radio.py, render_select.py, // render_fieldset.py, render_table.py, render_icone.py, render_onglets.py). // Jauge/Superposition/Personnage/Répéteur (logique/données plus lourde) // vivent dans des fichiers dédiés (render-jauge.js, render-repeater.js). // Dépend de render-element.js (forgeVisibleAttrs/forgeStyleString/ // forgeAttrString/forgeApplyCtx/forgeHtmlEscape/forgeAutoId/ // forgeParentFlexDirection/forgeRenderChildren), chargé avant ce fichier. function forgeRenderCheckboxOrRadio(el, meta, gameData, childrenMap, ctx, parentFlexDirection) { var inputType = el.widget === "case_a_cocher" ? "checkbox" : "radio"; var attrs = forgeVisibleAttrs(el, meta, ctx); attrs.type = inputType; var style = forgeStyleString(el, parentFlexDirection, el.widget) + " display:flex; align-items:center; gap:8px;"; var content = forgeApplyCtx(el.content || "", ctx); return ''; } function forgeRenderSelect(el, meta, gameData, childrenMap, ctx, parentFlexDirection) { var outerAttrs = forgeVisibleAttrs(el, meta, ctx); var bulmaClasses = outerAttrs.class || ""; var selectAttrs = {}, wrapperAttrs = {}; Object.keys(outerAttrs).forEach(function (k) { if (k === "id" || k === "data-element-id" || k === "data-anim-target") wrapperAttrs[k] = outerAttrs[k]; else if (k !== "class") selectAttrs[k] = outerAttrs[k]; }); var style = forgeStyleString(el, parentFlexDirection, el.widget) + " display:flex;"; var content = forgeApplyCtx(el.content || "", ctx); var options = content.split("\n").map(function (o) { return o.trim(); }).filter(Boolean); var optsHtml = options.map(function (o) { var esc = forgeHtmlEscape(o); return ''; }).join(""); var selectHtml = ''; var selectWrapClass = forgeHtmlEscape(("select is-fullwidth " + bulmaClasses).trim()); return '
' + selectHtml + '
'; } function forgeRenderFieldset(el, meta, gameData, childrenMap, ctx, parentFlexDirection) { var attrs = forgeVisibleAttrs(el, meta, ctx); var style = forgeStyleString(el, parentFlexDirection, el.widget); var content = forgeApplyCtx(el.content || "", ctx) || "Groupe de champs"; var childHtml = forgeRenderChildren(el, gameData, childrenMap, ctx); return '
' + forgeHtmlEscape(content) + '' + childHtml + '
'; } function forgeRenderTable(el, meta, gameData, childrenMap, ctx, parentFlexDirection) { var attrs = forgeVisibleAttrs(el, meta, ctx); var headerRow = ["1", "true", "on"].indexOf((el.attributes || {})._header_row) !== -1; var content = forgeApplyCtx(el.content || "", ctx); var style = forgeStyleString(el, parentFlexDirection, el.widget); var rows = content.split("\n").filter(function (r) { return r.trim(); }); var htmlRows = rows.map(function (r, i) { var cells = r.split(";"); var cellTag = (headerRow && i === 0) ? "th" : "td"; var cellsHtml = cells.map(function (c) { return "<" + cellTag + ">" + forgeHtmlEscape(c.trim()) + ""; }).join(""); return "" + cellsHtml + ""; }).join(""); return '' + htmlRows + '
'; } function forgeRenderIcone(el, meta, gameData, childrenMap, ctx, parentFlexDirection) { // Contrairement au serveur, ne revalide pas _icon_slug contre le // catalogue curaté (screens/widgets/fontawesome_icons.py) : une valeur // invalide ne peut être enregistrée que depuis l'éditeur, qui la valide // déjà à la sauvegarde — un jeu EXPORTÉ n'en produit jamais. var rawAttrs = el.attributes || {}; var iconSlug = rawAttrs._icon_slug || "star"; var attrs = forgeVisibleAttrs(el, meta, ctx); var existingClass = attrs.class || ""; delete attrs.class; attrs.class = (existingClass + " icon-svg").trim(); // Chemin RELATIF, et surtout PAS préfixé "static/" : cette URL est // posée dans --icon-url (style inline) mais CONSOMMÉE par `mask-image: // var(--icon-url)` dans static/style.css (voir .icon-svg) — une URL // relative dans une propriété personnalisée CSS se résout par rapport à // LA FEUILLE DE STYLE où le var() est utilisé (static/style.css), pas // où elle est définie (piège CSS connu) : "static/icons/x.svg" y // redeviendrait "static/static/icons/x.svg" (404/CORS bloqué — bug // signalé). "icons/x.svg" est correct une fois résolu depuis // static/style.css (voir aussi _relativize_absolute_urls dans // publish/build_scorm_package.py, même correctif côté rendu initial). var iconUrl = "icons/" + iconSlug + ".svg"; var style = forgeStyleString(el, parentFlexDirection, el.widget) + " --icon-url:url('" + iconUrl + "');"; return ""; } function forgeRenderOnglets(el, meta, gameData, childrenMap, ctx, parentFlexDirection) { var tabs = (childrenMap && childrenMap[el.id]) || []; var attrs = forgeVisibleAttrs(el, meta, ctx); var tabsExtraClasses = attrs.class || ""; delete attrs.class; var style = forgeStyleString(el, parentFlexDirection, el.widget) + " display:flex; flex-direction:column;"; var groupId = forgeAutoId(el); if (!tabs.length) { return '
Onglets — ajoute au moins un onglet dans ses réglages.
'; } var flexDir = forgeParentFlexDirection(el); var barItems = [], panels = []; tabs.forEach(function (tab, i) { var label = tab.name || ("Onglet " + (i + 1)); var active = i === 0; var tabHtml = forgeRenderElementHtml(tab, gameData, childrenMap, ctx, flexDir); var targetId = "tabpanel-" + tab.id; barItems.push( '
  • ' + forgeHtmlEscape(String(label)) + '
  • ' ); var hiddenStyle = active ? "" : ' style="display:none;"'; panels.push('
    ' + tabHtml + '
    '); }); var tabsClass = forgeHtmlEscape(("tabs " + tabsExtraClasses).trim()); return ( '
    ' + '
    ' + '
    ' + panels.join("") + '
    ' + '
    ' ); } // Port de render_personnage.py — lit directement gameData.personnage_animations // (déjà résolu une fois par full_game_payload.py, à l'export) au lieu de // re-résoudre la bibliothèque de sprites Forge/l'URL personnalisée : ce // travail est fait UNE fois, à la génération du paquet, jamais à chaque // rendu côté client. function forgeRenderPersonnage(el, meta, gameData, childrenMap, ctx, parentFlexDirection) { var attrs = forgeVisibleAttrs(el, meta, ctx); attrs["data-personnage"] = "1"; var animations = gameData.personnage_animations && gameData.personnage_animations[String(el.id)]; var frames = animations ? (animations.idle || animations[Object.keys(animations)[0]]) : null; var idleFrame = frames && frames.length ? frames[0] : ""; if (idleFrame) attrs.src = idleFrame; var style = forgeStyleString(el, parentFlexDirection, el.widget); return ""; } if (typeof module !== 'undefined' && module.exports) { module.exports = { forgeRenderCheckboxOrRadio, forgeRenderSelect, forgeRenderFieldset, forgeRenderTable, forgeRenderIcone, forgeRenderOnglets, forgeRenderPersonnage, }; var renderMod = require('./render-element.js'); ["forgeVisibleAttrs", "forgeStyleString", "forgeAttrString", "forgeApplyCtx", "forgeHtmlEscape", "forgeAutoId", "forgeParentFlexDirection", "forgeRenderChildren", "forgeRenderElementHtml"].forEach(function (name) { global[name] = global[name] || renderMod[name]; }); }