import $ from 'jquery'
import { j3pAddElt, j3pAjouteCaseCoche, j3pAjouteZoneTexte, j3pBarre, j3pBoutonRadio, j3pBoutonRadioChecked, j3pDiv, j3pElement, j3pFocus, j3pGetRandomElt, j3pGetRandomInt, j3pImporteAnnexe, j3pMathquillXcas, j3pPaletteMathquill, j3pShowError, j3pValeurde } from 'src/legacy/core/functions'
import loadWebXcas from 'src/legacy/outils/xcas/loadWebXcas'
import textesGeneriques from 'src/lib/core/textes'
import { j3pAffiche } from 'src/lib/mathquill/functions'
const { cBien, cFaux } = textesGeneriques
// Les types valides
// ATTENTION, au 2023-02-01 seul le type singleChoice a été testé (une seule section utilise rexams et elle l’impose)
const typesExos = ['mathquill', 'num', 'multipleChoice', 'singleChoice']
// la fct d’évaluation des expressions par webXcas
let xcas
/**
* Intégration de R-exams dans J3P par Patrick Raffinat 20 Mai 2020
* @module legacy/outils/rexams/main
*/
export function enonceReset (_parcours) {
_parcours.videLesZones()
initTypeExo(_parcours)
enonceComposants(_parcours)
enonceQuestions(_parcours)
}
export function correction (_parcours) {
if (!correctionAvecReponse(_parcours)) {
_parcours.reponseManquante('correction')
} else {
const cbon = correctionTestReponse(_parcours)
if (cbon) {
correctionOk(_parcours)
} else {
correctionKo(_parcours)
}
_parcours.finCorrection()
}
}
export function navigation (_parcours) {
if (!_parcours.sectionTerminee()) {
_parcours.etat = 'enonce'
}
_parcours.finNavigation()
}
function enonceComposants (_parcours) {
const { typeExo, extol } = _parcours.storage
j3pDiv(_parcours.zonesElts.MG,
{
id: 'div_reponse',
contenu: '',
coord: [10, 10],
style: { width: '95%' }
}
)
let coordEnonce = 10
if (typeExo === 'mathquill') {
coordEnonce = 120
j3pElement('div_reponse').innerHTML = '<span style="color:blue">Réponse : </span>'
// var latex = "e^{2.65 x} \\cdot x^2 \\cdot (3 + 2.5 x)";
j3pAffiche('div_reponse', 'rexams_', '&1&',
{
inputmq1: { texte: '' }
}
)
j3pDiv('div_reponse', 'Palette', '')
j3pAddElt('Palette', 'br')
j3pPaletteMathquill('Palette', 'rexams_inputmq1', { liste: ['racine', 'exp', 'fraction', 'puissance'] })
j3pAddElt('Palette', 'br')
j3pElement('rexams_inputmq1').focus()
} else if (typeExo === 'num') {
coordEnonce = 60
let txt = 'Réponse (à ' + extol + ' près) : '
txt = '<span style="color:blue">' + txt + '</span>'
j3pElement('div_reponse').innerHTML = txt
j3pAjouteZoneTexte('div_reponse', { id: 'rexams_rep', maxchars: '10', restrict: /[0-9.]/, texte: '', tailletexte: 20, width: 100 })
j3pFocus('rexams_rep')
}
j3pDiv(_parcours.zonesElts.MG,
{
id: 'div_enonce',
contenu: '',
coord: [10, coordEnonce],
style: { width: '95%' }
}
)
j3pDiv(_parcours.zonesElts.MD,
{
id: 'explications',
contenu: '',
coord: [10, 60],
style: {}
}
)
j3pDiv(_parcours.zonesElts.MD,
{
id: 'correction',
contenu: '',
coord: [10, 10],
style: _parcours.styles.petit.correction
}
)
}
function enonceQuestions (_parcours) {
const { typeExo, fichiersHtmlAnnexes } = _parcours.storage
const n = j3pGetRandomInt(0, fichiersHtmlAnnexes.length - 1) // La question est choisie parmi celles qui n’ont pas encore été posées
const fichier = fichiersHtmlAnnexes[n]
fichiersHtmlAnnexes.splice(n, 1) // On la retire des questions à poser la prochaine fois
j3pImporteAnnexe(`qcmRexams/${fichier}`)
.then(html => {
const stor = _parcours.storage
const divEnonce = j3pElement('div_enonce')
const liste = html.split('\n')
let nbChoix = 0
const listeEnonce = []
const listeSolution = []
let dansEnonce = false
let dansSolution = false
let dansChoix = false
for (let i = 0; i < liste.length - 1; i++) {
if (liste[i].indexOf('<br/>') === 0) {
liste[i] = ''
continue
}
if (liste[i].indexOf('<h4>Question</h4>') >= 0) {
listeEnonce.push(liste[i])
dansEnonce = true
continue
}
if (!dansEnonce && !dansSolution) {
liste[i] = ''
continue
}
if (dansEnonce) {
if (liste[i].indexOf('<ol ') === 0) {
if (typeExo === 'multipleChoice' || typeExo === 'singleChoice') {
divEnonce.innerHTML += '!ol!'
}
} else if (liste[i].indexOf('</ol>') === 0) {
if (typeExo === 'multipleChoice' || typeExo === 'singleChoice') {
divEnonce.innerHTML += '!/ol!'
}
} else if (liste[i].indexOf('<li>') === 0) {
dansChoix = true
} else if (liste[i].indexOf('</li>') === 0) {
dansChoix = false
} else if (liste[i].indexOf('<h4>Solution</h4>') === 0) {
dansEnonce = false
dansSolution = true
listeSolution.push('<h4>Explications</h4>')
nbChoix = 0
} else if (dansChoix) {
if (typeExo === 'singleChoice') {
nbChoix = nbChoix + 1
const id = 'rexams_rep_' + nbChoix
divEnonce.innerHTML += '!li!'
j3pBoutonRadio('div_enonce', id, 'ensemble', String(nbChoix), liste[i])
divEnonce.innerHTML += '!/li!'
} else if (typeExo === 'multipleChoice') {
nbChoix = nbChoix + 1
const id = 'rexams_rep_' + nbChoix
divEnonce.innerHTML += '!li!'
j3pAjouteCaseCoche('div_enonce', { id })
divEnonce.innerHTML += liste[i]
divEnonce.innerHTML += '!/li!'
}
} else {
listeEnonce.push(liste[i])
}
} else if (dansSolution) {
if (liste[i].indexOf('<ol') === 0) {
if (typeExo !== 'singleChoice') listeSolution.push(liste[i])
} else if (liste[i].indexOf('</ol>') === 0) {
dansSolution = false
if (typeExo !== 'singleChoice') listeSolution.push(liste[i])
} else if (liste[i].indexOf('<li>') === 0) {
nbChoix = nbChoix + 1
if (typeExo !== 'singleChoice') listeSolution.push(liste[i])
} else if (liste[i].indexOf('</li>') === 0) {
if (typeExo !== 'singleChoice') listeSolution.push(liste[i])
} else if ((liste[i].indexOf('False') === 0)) {
if (typeExo === 'multipleChoice') {
listeSolution.push(liste[i])
if (nbChoix === 1) stor.solution = []
stor.solution.push(false)
}
} else if ((liste[i].indexOf('True') === 0)) {
if (typeExo === 'singleChoice') {
stor.solution = nbChoix
}
if (typeExo === 'multipleChoice') {
listeSolution.push(liste[i])
if (nbChoix === 1) stor.solution = []
stor.solution.push(true)
}
} else if (liste[i].indexOf('!!!') >= 0 || liste[i].indexOf('<mi>!</mi>') >= 0) {
const i1 = liste[i].indexOf('!!!')
const i2 = liste[i].indexOf('!!!', i1 + 3)
stor.solution = liste[i].substring(i1 + 3, i2) // rep
listeSolution.push(liste[i])
} else {
listeSolution.push(liste[i])
}
}
}
j3pElement('correction').innerHTML = ''
let codeChoix = divEnonce.innerHTML
codeChoix = codeChoix.split('!li!').join('<li style="color:blue">')
codeChoix = codeChoix.split('!/li!').join('</li>')
codeChoix = codeChoix.split('!ol!').join('<ol type="a" style="color:blue">')
codeChoix = codeChoix.split('!/ol!').join('</ol>')
if (typeExo === 'multipleChoice') { // à cause de j3pCoche
codeChoix = codeChoix.split('<br>').join('')
codeChoix = codeChoix.split('j3pGestionCoche(this)').join('')
}
const codeEnonce = listeEnonce.join('\n')
divEnonce.innerHTML = '\n' + codeEnonce
divEnonce.innerHTML += codeChoix
// parade pb MathJax Chrome : on met les explications et on les masque
let codeSolution = listeSolution.join('\n')
codeSolution = codeSolution.split('True.').join('Vrai.')
codeSolution = codeSolution.split('False.').join('Faux.')
codeSolution = codeSolution.split('!!!').join('')
codeSolution = codeSolution.split('<mi>!</mi>').join('')
j3pElement('explications').innerHTML = codeSolution
j3pElement('explications').style.display = 'none'
reloadMathjax()
return loadWebXcas()
})
.then((_xcas) => {
xcas = _xcas
_parcours.finEnonce()
})
.catch(error => {
j3pShowError(error, { message: 'Impossible de charger le contenu de cet exercice' })
})
} // enonceQuestions
function correctionAvecReponse (_parcours) {
const { typeExo, solution } = _parcours.storage
let cbon = true
if (typeExo === 'singleChoice') {
const repEleve = j3pBoutonRadioChecked('ensemble')
cbon = (repEleve[0] >= 0)
} else if (typeExo === 'multipleChoice') {
cbon = false
const nbChoix = solution.length
for (let i = 1; i <= nbChoix; i++) {
if (document.getElementById('rexams_rep_' + i).checked) cbon = true
}
if (!cbon) {
// eslint-disable-next-line no-alert
cbon = confirm('confirmez-vous ne vouloir cocher aucun choix ?')
}
} else if (typeExo === 'num') {
const repEleve = j3pValeurde('rexams_rep')
if (!repEleve.trim()) return false
} else if (typeExo === 'mathquill') {
// en 2026-01 on a aucune section dans ce cas
if (typeof window.jQuery?.fn.mathquill !== 'function') {
throw Error('Mathquill n’a pas été chargé')
}
const repEleve = $(document.getElementById('rexams_inputmq1')).mathquill('latex')
if (!repEleve.trim()) return false
}
return cbon
}
function correctionOk (_parcours) {
_parcours.score++
j3pElement('correction').style.color = _parcours.styles.cbien
j3pElement('correction').innerHTML = cBien
// parade pb MathJax Chrome : on réaffiche les explications
j3pElement('explications').style.display = 'inline' // j3pElement("explications").innerHTML = stor.solutionTxt;
_parcours.etat = 'navigation'
_parcours.sectionCourante()
}
function correctionKo (_parcours) {
const { typeExo } = _parcours.storage
j3pElement('correction').style.color = _parcours.styles.cfaux
j3pElement('correction').innerHTML = cFaux
if (_parcours.essaiCourant >= _parcours.donneesSection.nbchances) {
// Erreur au dernier essai
// parade pb MathJax Chrome : on réaffiche les explications
j3pElement('explications').style.display = 'inline' // j3pElement("explications").innerHTML = stor.solutionTxt;
const stor = _parcours.storage
const repOk = stor.solution
if (typeExo === 'multipleChoice') {
const nbChoix = stor.solution.length
let note = 0
for (let i = 1; i <= nbChoix; i++) {
const repEleve = document.getElementById('rexams_rep_' + i)
if (repEleve.checked !== repOk[i - 1]) {
repEleve.parentNode.style.color = 'red'
} else {
note += 1 / nbChoix
}
}
_parcours.score += note
} else if (typeExo === 'singleChoice') {
document.getElementById('rexams_rep_' + repOk).parentNode.style.color = 'red'
document.getElementById('rexams_rep_' + repOk).parentNode.style.backgroundColor = 'yellow'
let i = 1
while (!document.getElementById('rexams_rep_' + i).checked) {
i = i + 1
}
document.getElementById('rexams_rep_' + i).parentNode.style.color = 'red'
// document.getElementById("rexams_rep_"+i).parentNode.style.textDecoration = "underline overline";
} else if (typeExo === 'num') {
j3pElement('rexams_rep').style.color = 'red'
j3pBarre('rexams_rep')
} else if (typeExo === 'mathquill') {
j3pElement('rexams_inputmq1').style.color = 'red'
j3pBarre('rexams_inputmq1')
}
_parcours.etat = 'navigation'
_parcours.sectionCourante()
}
}
function correctionTestReponse (_parcours) {
const { typeExo, extol } = _parcours.storage
const { solution } = _parcours.storage
if (typeExo === 'singleChoice') {
return Boolean(document.getElementById('rexams_rep_' + solution).checked)
}
if (typeExo === 'multipleChoice') {
return solution.entries().every(([i, reponse]) => document.getElementById('rexams_rep_' + (i + 1)).checked === reponse)
}
if (typeExo === 'num') {
let tolerance = extol
if (!Number.isFinite(extol)) {
console.error(Error('Paramétrage incorrect, il manque la tolérance (extol n’est pas défini)'))
tolerance = solution / 20
}
const repEleve = Number(document.getElementById('rexams_rep').value)
return (Math.abs(solution - repEleve) < tolerance)
}
if (typeExo === 'mathquill') {
const repOk = solution.split('\\cdot').join('\\times')
const repOkXcas = j3pMathquillXcas(repOk)
const repEleve = $('#rexams_inputmq1').mathquill('latex')
const repEleveXcas = j3pMathquillXcas(repEleve)
const diff = xcas('simplify(normal(' + repEleveXcas + '-(' + repOkXcas + ')))')
return diff === 0
}
throw Error(`type d’exercice ${typeExo} non géré`)
}
function reloadMathjax () {
if (navigator.appName.indexOf('irefox') >= 0) return
const src = 'https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
try {
// $('script[src="' + src + '"]').remove();
$('script[src]').remove()
window.MathJax = null
} catch (err1) {
console.error(err1)
}
try {
$('<script>').attr('src', src).appendTo('head')
} catch (err2) {
console.error(err2)
}
}
function initTypeExo (_parcours) {
const stor = _parcours.storage
const ds = _parcours.donneesSection
if (typesExos.includes(stor.typeExo)) return // un seul type ok, on le garde tel quel
if (typesExos.includes(ds.typeExo)) {
// un seul type ok, en param on le garde tel quel en le mettant dans stor
stor.typeExo = ds.typeExo
return
}
// on peut nous passer une liste en param (choisi par l’utilisateur) ou dans stor (déterminé par la section)
let typesExosVoulus = ds.typesExos || stor.typesExos
// si y’a pas de storage.typeExo, on peut fournir une liste dans donneesSection.typesExos
if (typeof typesExosVoulus === 'string') {
typesExosVoulus = typesExosVoulus
.replace(/[[\] ]/g, '') // vire les éventuels crochets et espaces
.split(/[,;]/g) // découpe sur le séparateur , ou ;
// on ne vire pas les inconnus, c’est fait juste en dessous avec un warning
}
let typesExosOk
if (Array.isArray(typesExosVoulus)) {
// on vérifie les types fournis
typesExosOk = typesExosVoulus.filter(type => {
if (!typesExos.includes(type)) {
console.error(Error(`Le type ${type} n’est pas un type valide pour rexams (pas dans ${typesExos.join('|')})`))
return false
}
return true
})
}
if (!typesExosOk?.length) {
console.error(Error('Aucun type fourni (ni typeExo ni typesExos en paramètre)'))
typesExosOk = typesExos
}
// et on init le type (qui sera le même pour toutes les répétitions)
stor.typeExo = j3pGetRandomElt(typesExosOk)
}