import { j3pGetUrlParams } from 'src/legacy/core/functions'
import { loadJs } from 'sesajs/dom'
import FormulaComparator from './FormulaComparator'
/** @module lib/outils/mathgraph/index */
// pour ne lancer le chargement qu’une seule fois, et surtout utiliser
// le mtgLoad mis en global par notre loader pour éviter tout conflit
// (si la section déclare mathgraph comme outil ET utilise ces fcts, ça déconne)
let mtgLoad
const mtgLanguages = ['fr', 'ar', 'de', 'es', 'en']
const defaultMtgLanguage = 'fr'
const languages = {
ara: 'ar',
deu: 'de',
eng: 'en',
fra: 'fr',
spa: 'es',
}
/**
* Initialise mtgOptions avec language fr et decimalDot false s'ils n'étaient pas précisés
* @param {MtgOptions} mtgOptions
*/
function normalizeMtgOptions (mtgOptions) {
// @todo mettre cette conversion dans le mtgLoad de mathgraph
if (mtgOptions.language?.length === 3) {
mtgOptions.language = languages[mtgOptions.language]
}
mtgOptions.language = mtgLanguages.includes(mtgOptions.language) ? mtgOptions.language : defaultMtgLanguage
if (mtgOptions.decimalDot == null) {
// virgule si langue ≠ anglais (valable pour fr|de|es en 2025-06)
mtgOptions.decimalDot = mtgOptions.language === 'en'
}
}
/**
* Retourne la fct mtgLoad
* @return {Promise<function>} Résolue avec la fonction mtgLoad
*/
export async function getMtgLoad (mtgUrl) {
if (mtgLoad) return mtgLoad
if (typeof window.mtgLoad !== 'function') {
const isDev = /^(localhost|.+\.sesamath.dev|.+\.local)$/.test(window.location.hostname)
const url = mtgUrl || j3pGetUrlParams('mtgUrl') || window.mtgUrl || (isDev ? 'https://dev.mathgraph32.org/js/mtgLoad/mtgLoad.js' : 'https://www.mathgraph32.org/js/mtgLoad/mtgLoad.min.js')
const opts = { timeout: 60 }
// si on charge mtg sur son pnpm start, par ex avec http://localhost:8081/#mtgUrl=http://localhost:8082/src/mtgLoad.global.js&…
// faut le faire en type module
if (/local(host(:8082)?)?\/src\/mtgLoad/.test(url)) opts.type = 'module'
await loadJs(url, opts)
if (typeof window.mtgLoad !== 'function') throw Error('Le chargement de Mathgraph a échoué')
} else {
console.warn('il y a déjà un mtgLoad mis en global par j3pLoad, parce qu’une des sections de ce graphe a déclaré l’outil mathgraph => pas de nouveau chargement')
}
// après chargement de ce js, window.mtgLoad est un preloader qui veut une callback, ça ne retourne jamais de promesse, on wrappe ça
mtgLoad = function (container, svgOptions, mtgOptions, cb) {
if (cb) return window.mtgLoad(container, svgOptions, mtgOptions, cb)
return new Promise((resolve, reject) => {
window.mtgLoad(container, svgOptions, mtgOptions, function (error, mtgApp) {
if (error) return reject(error)
resolve(mtgApp)
})
})
}
return mtgLoad
}
/**
* Retourne une promesse qui sera résolue avec une instance de MtgApp (l’éditeur mathgraph, ou le player si mtgOptions.isEditable est mis à false)
* ou rejetée une erreur (ne pas oublier de la traiter, avec à minima un `.catch(j3pShowError)` après le then, qui dans ce cas va aussi
* capturer les erreurs du code mis dans le then, pour dissocier mettre ce catch avant le then puis un autre catch pour les erreurs éventuelles du then)
* Cf la doc {@link https://www.mathgraph32.org/documentation/loading/global.html#mtgLoad} pour les options possibles
* avec des exemples {@link https://www.mathgraph32.org/documentation/loading/tutorial-loadPlayer.html} pour le player
* ou {@link https://www.mathgraph32.org/documentation/loading/tutorial-loadEditor.html} pour l’éditeur
* @param {HTMLElement|string} container
* @param {SvgOptions} svgOptions
* @param {MtgOptions} mtgOptions Si vous précisez loadCoreOnly, il vaut mieux utiliser `getMtgCore()`,
* et si c’est loadCoreWithMathJax alors utilisez `getMtgCore({withMathjax : true })`
* Ça retournera un MtgAppLecteur (en passant ces options ici aussi, mais le typage de ce qui est retourné
* va correspondre à un objet MtgApp alors que vous aurez un MtgAppLecteur)
* @param {boolean} [mtgOptions.isEditable=true] passer false pour récupérer un MtgAppLecteur à la place d’un MtgApp
* @param {boolean} [mtgOptions.loadApi=false] passer true pour avoir les méthodes de l’api
* @return {Promise<MtgApp|MtgAppLecteur|MtgAppApi|MtgAppLecteurApi>} suivant les options loadCoreOnly|loadCoreWithMathJax|isEditable
*/
export async function getMtgApp (container, svgOptions, mtgOptions) {
const mtgLoad = await getMtgLoad()
normalizeMtgOptions(mtgOptions)
const mtgApp = await mtgLoad(container, svgOptions, mtgOptions)
if (!mtgApp) throw Error('Le chargeur de mathgraph n’a pas retourné d’application mathgraph sous la forme attendue')
return mtgApp
}
/**
* Retourne une promesse qui sera résolue avec une instance de MtgAppLecteur (le player
* ou rejetée une erreur (ne pas oublier de la traiter, avec à minima un `.catch(j3pShowError)` après le then, qui dans ce cas va aussi
* capturer les erreurs du code mis dans le then, pour dissocier mettre ce catch avant le then puis un autre catch pour les erreurs éventuelles du then)
* Cf la doc {@link https://www.mathgraph32.org/documentation/loading/global.html#mtgLoad} pour les options possibles
* avec des exemples {@link https://www.mathgraph32.org/documentation/loading/tutorial-loadPlayer.html}
* @param {HTMLElement|string} container
* @param {SvgOptions} [svgOptions]
* @param {MtgOptions} [mtgOptions] Si vous précisez loadCoreOnly, il vaut mieux utiliser `getMtgCore()`,
* et si c’est loadCoreWithMathJax alors utilisez `getMtgCore({withMathjax : true })`
* @param {boolean} [mtgOptions.isEditable=true] passer false pour récupérer un MtgAppLecteur à la place d’un MtgApp
* @param {boolean} [mtgOptions.loadApi=false] passer true pour avoir les méthodes de l’api
* @return {Promise<MtgApp|MtgAppLecteur|MtgAppApi|MtgAppLecteurApi>} suivant les options loadCoreOnly|loadCoreWithMathJax|isEditable
*/
export async function getMtgAppLecteur (container, svgOptions = {}, mtgOptions = {}) {
mtgOptions.isEditable = false
return getMtgApp(container, svgOptions, mtgOptions)
}
/**
* Charge un MtgAppLecteurApi
* @param container
* @param svgOptions
* @param mtgOptions
* @return {Promise<MtgAppLecteurApi>}
*/
export async function getMtgAppLecteurApi (container, svgOptions, mtgOptions) {
normalizeMtgOptions(mtgOptions)
// on impose le MtgAppLecteurApi en sortie
mtgOptions.loadApi = true
mtgOptions.isEditable = false
return getMtgApp(container, svgOptions, mtgOptions)
}
/**
* Retourne une promesse qui sera résolue avec une instance du moteur de mathgraph (un MtgAppLecteur, même s’il n’y a pas de figure à afficher) ou une erreur
* Cf la doc {@link https://www.mathgraph32.org/documentation/loading/tutorial-loadCore.html} pour l’utiliser ensuite
* @param {Object} [options]
* @param {boolean} [options.withMathjax=false] passer true pour charger Mathjax dès le départ (sinon il sera chargé si besoin seulement, d’après ce qu’il aura à traiter)
* @param {boolean} [options.withApi=false] passer true pour récupérer un MtgAppLecteurApi (MtgAppLecteur avec les méthodes de l’api mathgraph synchrone)
* @param {boolean} [options.withApiPromise=false] passer true pour récupérer un MtgAppLecteurApi (MtgAppLecteur avec les méthodes de l’api mathgraph qui retournent des promesses résolues lorsque les objets sont affichés)
* @return {Promise<MtgAppLecteur|MtgAppLecteurApi>}
*/
export function getMtgCore ({ withMathjax = false, withApi = false, withApiPromise = false } = {}) {
// player only, sans figure initiale ni svg (qui devra être créé par la section si besoin)
const mtgOptions = {}
normalizeMtgOptions(mtgOptions)
if (withMathjax) mtgOptions.loadCoreWithMathJax = true
else mtgOptions.loadCoreOnly = true
if (withApi || withApiPromise) {
mtgOptions.loadApi = true
if (withApiPromise) mtgOptions.isPromiseMode = true
}
return getMtgApp('', {}, mtgOptions)
}
// notre singleton qui sera retourné par getComparator
/**
* @typedef compareFormula
* @type {function}
* @param solution
* @param reponse
* @param {Object} [options]
* @param {string} [options.varNames=xyztab] noms des variables formelles utilisées (6 max)
* @param {boolean} [options.equivalenceFracDec=false] Passer true pour que…
* @returns {number} -1 en cas d’erreur de syntaxe (-2 en cas d’erreur de syntaxe sur la solution), 1 si équivalent et 0 sinon
* @throws {SyntaxError} en cas d’erreur de syntaxe dans la solution ou la réponse
*/
let compareFormula
/**
* Retourne la fonction qui permettra de tester des équivalences d’expressions algébriques
* @returns {Promise<compareFormula>}
*/
export async function getComparator () {
// on crée le comparateur au 1er appel
if (!compareFormula) {
const mtgAppLecteur = await getMtgCore()
const formulaComparator = new FormulaComparator(mtgAppLecteur)
compareFormula = formulaComparator.compare.bind(formulaComparator)
}
return compareFormula
}