/**
* Ce module charge un graphe j3p v1 dans le dom, il est chargé dynamiquement par loader sur appel de la fct globale j3pLoad,
* qui est le "player" j3p v1
* {@tutorial chargement})
*
* Au départ, ce fichier charqeait tout (outils et moteur) avant d’instancier un unique Parcours dans le dom.
* Petit à petit chaque section importe elle même les outils dont elle a besoin
* et le code de ce fichier devrait se réduire.
* @module
*/
import { addElement } from 'sesajs/dom'
import { setAppErrorHandler } from 'sesajs/error'
import log from 'sesajstools/utils/log'
import Parcours from 'src/legacy/core/Parcours'
import { j3pBaseUrl } from 'src/lib/core/constantes'
import loadJqueryDialog from 'src/lib/core/loadJqueryDialog'
import { loadSectionV1 } from 'src/lib/core/loadSection'
// faut ajouter notre css
import 'src/legacy/css/j3p.css'
/**
* raccourci vers window
* @type {Window}
* @private
*/
const w = window
let j3pErrorConteneur
// pour les erreurs au chargement, quand on peut pas encore utiliser j3pShowError
const addPageError = (error) => {
console.error(error)
if (j3pErrorConteneur) {
const content = error.message || error
addElement(j3pErrorConteneur, 'p', { content })
} else {
// eslint-disable-next-line no-alert
alert(error.message)
}
}
/**
* Transforme params.pe en un objet phrasesEtat (peKey => peValue)
* @param params
* @return {PlainObject} L’objet phrasesEtat (peKey => peValue)
*/
export function getPhrasesEtat (params) {
const phrasesEtat = {}
if (typeof params?.phrasesEtat === 'object') {
Object.assign(phrasesEtat, params.phrasesEtat)
} else if (Array.isArray(params?.pe)) {
for (const pe of params.pe) {
Object.assign(phrasesEtat, pe)
}
}
return phrasesEtat
}
/**
* Le chargeur principal du player
* @param {HTMLElement} container
* @param {Object} optionsChargement
* @return {Promise<void, Error>}
* @private
*/
function loadMain (container, optionsChargement) {
try {
if (typeof import.meta !== 'object') {
// eslint-disable-next-line no-alert
alert('Votre navigateur est trop ancien, les exercices interactifs ne fonctionneront pas correctement')
}
} catch (error) {
console.error(error)
}
const sections = []
// et on lance les chargements au fur et à mesure.
// jquery-ui sera chargé par Parcours si une section n’a pas btedialogue: non dans ses paramètres
// pour le moment on continue de le charger ici, car il faudrait être sûr que toutes les sections qui utilisent .dialog() le charge
return loadJqueryDialog().then(() => {
// on ajoute les sections
const loadingPromises = []
optionsChargement.graphe.forEach(([num, nomSection], i) => {
if (!nomSection || typeof nomSection !== 'string') throw Error(`graphe invalide (nom de la section de l’élément ${i}`)
// on ajoute à la première apparition son nom si c’est la 1re fois qu’il apparait
if (nomSection.toLowerCase() !== 'fin' && !sections.includes(nomSection)) {
sections.push(nomSection)
loadingPromises.push(loadSectionV1(nomSection))
}
})
return Promise.all(loadingPromises)
}).then((sectionsExports) => {
// sectionsExports est un array avec les exports des sections chargées, dans le même ordre
for (const [index, name] of sections.entries()) {
// on récupère ce qu’elle exporte
const exp = sectionsExports[index]
// pour les sections qui n’exporte qu’une seule fct, faut la mettre dans le prototype de Parcours
if (typeof exp.default === 'function') {
const nomFn = `Section${name}`
Parcours.prototype[nomFn] = exp.default
// on ajoute aussi les params, pour que Parcours puisse les récupérer
Parcours.prototype[nomFn].parametres = exp.params?.parametres ?? []
// et une éventuelle fct d’upgrade des params (pour compatibilité ascendante des graphes)
if (typeof exp.upgradeParametres === 'function') Parcours.prototype[nomFn].upgradeParametres = exp.upgradeParametres
// les pe
Parcours.prototype[nomFn].phrasesEtat = getPhrasesEtat(exp.params)
}
}
// et on peut instancier
const parcours = new Parcours(container.id, 'Mep', optionsChargement)
// on surcharge appErrorHandler pour planter sur les erreurs critiques
setAppErrorHandler((error) => {
console.error(error)
if (error.critical) {
// c'est une erreur bloquante => on arrête là
// (ça va afficher dans MG "erreur interne, impossible de continuer")
parcours.abort()
}
})
w.j3p = parcours
// log('fin du chargement, j3p est créé en global comme instance de Parcours', parcours)
return parcours
})
} // loadMain
// ************************************
// Les méthodes exportées de ce module
// ************************************
/**
* Initialise des params
* @param {object} options Doit contenir la propriété j3pBaseUrl (url absolue du site j3p),
* @param {string} options.j3pBaseUrl (url absolue du site j3p pour y charger css & co)
* @param {number|string} [options.logLevel]
* @private
*/
function init (options) {
if (!options) throw Error('Paramètres de chargement absents')
if (options.logLevel !== undefined) log.setLogLevel(options.logLevel)
}
/**
* Charge un graphe (v1 only) dans le conteneur indiqué
* @param {string|HTMLElement} container
* @param {J3pLoadOptions} options
* @param {Array} options.graphe
* @param {Object} [options.editgraphes] Un objet avec les positions des nœuds dans la représentation graphique du graphe, pour viewer
* @param {number} [options.indexInitial] index du nœud par lequel commencer dans le graphe (démarre à 0)
* @param {boolean} [options.isDebug=false] Passer true pour instancier Parcours en mode debug
* @param {Object} [options.lastResultat] Un éventuel dernier résultat obtenu
* @param {number|string} [options.logLevel] Niveau de log souhaité (0-4 ou debug|notice|warning|error|critical)
* @param {function} [options.resultatCallback] Sera appelée avec le résultat
* @param {string} [options.baseUrl] Passer ici l’url absolue du domaine où charger j3p (indispensable si la page courante n’y est pas)
* @param {Language} [options.language] Une éventuelle langue autre que fra parmi ara|deu|eng|spa
* @param {boolean} [options.isTest] Passer true pour le mode test (@todo à implémenter)
* @param {function} [loadCallback] rappelée avec (error, parcours)
* @returns {Promise|undefined} undefined si loadCallback a été fournie, Promise sinon
*/
function j3pLoad (container, options, loadCallback) {
try {
if (typeof container === 'string') container = document.getElementById(container)
if (!container || typeof options !== 'object') throw Error('paramètres manquants, chargement impossible')
if (options.lastResultat && !options.graphe) {
options.graphe = options.lastResultat.contenu?.graphe
}
const { graphe } = options
// on vérifie que le graphe est au moins un tableau de tableaux
if (!Array.isArray(graphe) || !graphe.length || !graphe.every(e => Array.isArray(e))) throw Error('graphe invalide, chargement impossible')
if (options.baseUrl) {
init({ j3pBaseUrl: options.baseUrl, logLevel: options.logLevel })
} else if (!j3pBaseUrl) {
throw Error('Il faut appeler init avant de lancer le chargement ou passer l’option baseUrl à ce chargeur')
}
// on vide
while (container.lastChild) container.removeChild(container.lastChild)
// log('On va charger ce graphe', stringify(graphe), 'avec les options', stringify(options))
const optionsChargement = {
// on accepte les graphe avec un 1er elt vide ou pas, s’il y en a un on le vire
graphe: graphe[0].length ? graphe : graphe.slice(1),
baseUrl: j3pBaseUrl,
isDebug: Boolean(options.isDebug)
}
// options facultatives
if (typeof options.resultatCallback === 'function') optionsChargement.resultatCallback = options.resultatCallback
if (typeof options.editgraphes === 'object') optionsChargement.editgraphes = options.editgraphes
if (typeof options.lastResultat === 'object') optionsChargement.lastResultat = options.lastResultat
if (typeof options.indexInitial === 'number') optionsChargement.indexInitial = options.indexInitial
if (typeof options.isDebug === 'boolean') optionsChargement.isDebug = options.isDebug
if (typeof options.language === 'string') {
optionsChargement.language = options.language
}
// surcharge éventuelle d'après l'url
const urlParams = new URLSearchParams(window.location.search)
if (urlParams.has('language')) optionsChargement.language = urlParams.get('language')
// on lance analyse du graphe et chargement des outils puis lancement
// mais faut forcer cet id qui est en dur un peu partout dans le code j3p, on créé un div pour ça
j3pErrorConteneur = addElement(container, 'div', { className: 'j3pErrors' })
const j3pContainer = addElement(container, 'div', { id: 'Mepact' })
const promise = loadMain(j3pContainer, optionsChargement)
if (typeof loadCallback !== 'function') return promise
// sinon on gère la callback passée en param
const onSuccess = (parcours) => loadCallback(null, parcours)
const onFailure = error => {
addPageError(error)
loadCallback(error)
}
promise
.then(onSuccess, onFailure)
.catch(addPageError) // au cas où loadCallback plante
} catch (error) {
return (typeof loadCallback === 'function') ? loadCallback(error) : Promise.reject(error)
}
} // j3pLoad
export default j3pLoad