import $ from 'jquery'
import { j3pDetruit, j3pFreezeElt, j3pStyle } from 'src/legacy/core/functions'
import ZoneStyleMathquillBase from 'src/legacy/outils/zoneStyleMathquill/ZoneStyleMathquillBase'
import { j3pAffiche } from 'src/lib/mathquill/functions'
import { isHtmlElement } from 'sesajs/dom'
import { barreZone } from './functions'
import { blurAllTabs, removeTab, prevTabFocus } from './listeTabulations'
// rien
// et notre css
import './zoneStyleMathquill.scss'
// une fct qui fait rien
const dummy = () => undefined
// pour tester arc & angle http://localhost:8081/?rid=sesabibli/5c97d5764bb1527df9236b29 ou https://j3p.sesamath.net/?rid=sesabibli/5c97d5764bb1527df9236b29
class ZoneStyleMathquill1 extends ZoneStyleMathquillBase {
/**
* Crée une zone de saisie (inline)
* - pour des nombres simples, en acceptant les espaces
* - pour des réponses textes, avec chapeau angle ou arc
* @param {HTMLElement|string} conteneur Le conteneur (ou son id)
* @param {Object} parametres
* @param {string} parametres.clavierR une chaine avec tous les caracteres proposes au clavier virtuel
* si undefined copie de restric
* @todo modifier ici caractere affiché et rendu pour espace|angle|arc|/ ou je sais pas quoi d autre
* faudra que je laisse la possibilite d’un string simple quand mm
* @param {string} [parametres.contenu] Contenu éventuel à mettre dans l’input au départ
* @param {number} [parametres.limite=1000] nb de caracteres max
* @param {function} [parametres.enter] Une callback à appeler sur la touche entrée
* @param {Parcours} parametres.parcours L’instance du parcours courant
* @param {string} parametres.restric une chaine avec tous les caracteres autorisées au clavier
* si undefined ca vaut 0123456789.,+-* /² ()
* @param {boolean} parametres.tabauto Passer true pour que le focus passe automatiquement à l’input suivant lors de la saisie
* @param {string} parametres.id pour permettre à tabulation de choper prevtab
* @constructor
*/
constructor (conteneur, parametres) {
// on doit toujours commencer par appeler le constructeur de la classe parente
super(conteneur, { ...parametres, version: 1 })
// ce qui suit est spécifique à zsm1
if (isHtmlElement(parametres.forceParent)) {
this.parent = parametres.forceParent
}
this.sansBord = Boolean(parametres.sansBord)
this.isRest = false
this.okMod = false
this.chapo = 0
this.hasAngle = this.restric.includes('^')
this.hasArc = this.restric.includes('$')
if (this.hasAngle || this.hasArc) {
// maintenant on vire ^ et $
this.restric = this.restric.replace(/[$^]/g, '') // dans les crochets y’a pas besoin d’échapper $, ni ^ s’il n’est pas au début
// faut donc réaffecter ça aussi
if (!parametres.clavier) this.clavierR = this.restric
}
this.tabauto = parametres.tabauto === true
this.prevTab = parametres.prevTab
this.onModif = parametres.onmodif
this.modeText = parametres.modeText
this.hasAutoKeyboard = parametres.hasAutoKeyboard
this.afaire = parametres.afaire
this.id = parametres.id
// crea de la zone
this.texta = this.conteneur
// 0 pour simple 1 pour frac
this.texta.poscurseur = 0
// 0 si dans num , 1 si dans den (pour pos2
this.texta.elemnum = []
// on a besoin de mettre ces deux listeners en propriété de l’objet pour que disable() puisse les virer
this._gereBlurListener = this.gereblur.bind(this)
this._clickZoneListener = this.clickzone.bind(this)
// celui-là c’est addBtn qui l’utilise
this._onClickClavier = this.clickKlav.bind(this)
// la suite est commune aux 3 zsm
super.constuctorFinalize(parametres.contenu || '')
}
barre () {
return barreZone(this.conteneur)
}
blur () {
if (this.disabled) return
if (this._isLocked) return
const fofo = this.isblur
this.isblur = true
this.okMod = false
this.blurHelper()
if (!fofo && typeof this.onModif === 'function') {
this.onModif()
}
}
buildAutoKeyboard () {
const firstButtons = []
if (this.hasAngle) {
firstButtons.push({
mes: 'ANG',
txt: ' ',
className: 'zsmAngleFondGris'
})
}
if (this.hasArc) {
firstButtons.push({
mes: 'ARC',
txt: ' ',
className: 'zsmArcFondGris'
})
}
super.buildAutoKeyboard(firstButtons)
}
buildKeyboard (useExisting) {
const avirer = this.isCapsLocked ? /[a-z]/g : /[A-Z]/g
const touches = this.clavierR.replace(avirer, '')
const extraButtons = []
if (this.yaangle) {
extraButtons.push({ txt: '', mes: 'ANG', className: 'zsmAngleFondGris' })
}
if (this.hasArc) {
extraButtons.push({ txt: '', mes: 'ARC', className: 'zsmArcFondGris' })
}
this.buildKeyboardHelper(touches, { useExisting, extraButtons })
if (useExisting) this.place(true)
}
cacheKlavier () {
const node = this.divClavier
node.style.visibility = 'hidden'
node.style.height = '0'
}
/**
* Reconstruit le clavier en invertissant min/maj
*/
capsLock () {
this.isCapsLocked = !this.isCapsLocked
this.buildKeyboard(true)
}
clavier (event) {
if (this.isblur) return
if (this.disabled ||
event.code === 'Tab' ||
event.key === 'Tab' ||
event.code === 'Enter' ||
event.code === 'NumpadEnter'
) {
if (this.enter) {
if (event.code === 'Enter' ||
event.code === 'NumpadEnter') {
event.preventDefault()
event.stopPropagation()
this.blur()
this.enter()
}
}
if (event.key === 'Tab' || event.code === 'Tab') {
this.blur()
prevTabFocus(this)
event.preventDefault()
event.stopPropagation()
}
return
}
// pourquoi on désactive la propagation ?
event.preventDefault()
event.stopPropagation()
// pourquoi tester ça ici, ça correspond pas au event.code === 'Enter' déjà traité ?
// je crois que ya des navigateurs qui utilisent pas event.code , mais je sais plus ca fait longtemps
if (event.key === 'Enter') return
/*
var convpoursafari = [];
convpoursafari[43]='+';
convpoursafari[45]='-';
convpoursafari[48]='0';
convpoursafari[49]='1';
convpoursafari[50]='2';
convpoursafari[51]='3';
convpoursafari[52]='4';
convpoursafari[53]='5';
convpoursafari[54]='6';
convpoursafari[55]='7';
convpoursafari[56]='8';
convpoursafari[87]='9';
convpoursafari[47]='/';
convpoursafari[42]='*';
convpoursafari[46]=',';
convpoursafari[44]=',';
if (event.key==undefined){event.key=convpoursafari[event.which];}
*/
let muo = event.key
if (muo === 'Digit0') { muo = '0' }
if (muo === ' ') { muo = ' ' }
if (muo === '.' && this.modeText !== true) { muo = ',' }
let monres = this.restric
if (this.hasAngle) monres += '^'
if (this.hasArc) monres += '$'
if (muo === 'Dead') muo = '^'
if (muo === '^' && monres.includes('µ')) muo = 'µ'
if ((monres.includes(muo))) {
this.okMod = muo !== ''
this.majaffiche(muo)
}
let u
if (event.code === 'ArrowRight') {
if (this.texta.poscurseur < this.texta.elemnum.length) {
if ((this.texta.elemnum[this.texta.poscurseur].length > 1)) {
u = this.texta.elemnum[this.texta.poscurseur]
this.texta.elemnum[this.texta.poscurseur] = u.substring(0, 1)
this.texta.elemnum.splice(this.texta.poscurseur + 1, 0, u.substring(1, u.length))
}
this.texta.poscurseur++
}
this.majaffiche('')
}
if (event.code === 'ArrowLeft') {
if (this.texta.poscurseur > 0) {
if ((this.texta.elemnum[this.texta.poscurseur - 1].length > 1)) {
u = this.texta.elemnum[this.texta.poscurseur - 1]
this.texta.elemnum[this.texta.poscurseur - 1] = u.substring(0, u.length - 1)
this.texta.elemnum.splice(this.texta.poscurseur, 0, u.substring(u.length - 1, u.length))
this.texta.poscurseur++
}
this.texta.poscurseur--
}
this.majaffiche('')
}
if (event.code === 'Backspace') {
if (this.texta.poscurseur > 0) {
if ((this.texta.elemnum[this.texta.poscurseur - 1].length > 1)) {
this.texta.elemnum[this.texta.poscurseur - 1] = this.texta.elemnum[this.texta.poscurseur - 1].substring(0, (this.texta.elemnum[this.texta.poscurseur - 1].length - 1))
} else {
this.texta.elemnum.splice((this.texta.poscurseur - 1), 1)
this.texta.poscurseur--
}
if (this.modifL) this.modifL()
}
this.okMod = true
this.majaffiche('')
}
if (event.code === 'Delete') {
if (this.texta.elemnum.length > this.texta.poscurseur) {
if ((this.texta.elemnum[this.texta.poscurseur].length > 1)) {
this.texta.elemnum[this.texta.poscurseur] = this.texta.elemnum[this.texta.poscurseur].substring(1, (this.texta.elemnum[this.texta.poscurseur].length))
} else {
this.texta.elemnum.splice(this.texta.poscurseur, 1)
}
if (this.modifL) this.modifL()
}
this.okMod = true
this.majaffiche('')
}
if (this.afaire !== undefined) {
this.afaire(this.reponsechap())
}
}
clickKlav (event) {
this._isLocked = true
setTimeout(() => {
this._isLocked = false
this.majaffiche('')
}, 20)
if (event.currentTarget.mes === undefined) return
const add = event.currentTarget.mes
if (add === 'MAJ') return this.capsLock()
if (add === 'ANG') {
return this.clavier({
key: '^',
code: '^',
stopPropagation: dummy,
preventDefault: dummy
})
}
if (add === 'ARC') {
return this.clavier({
key: '$',
code: '$',
stopPropagation: dummy,
preventDefault: dummy
})
}
const fakeEvent = { key: add, code: add, stopPropagation: dummy, preventDefault: dummy }
this.clavier(fakeEvent)
}
clickc (event) {
if (this.disabled) { return }
// e.preventDefault()
// e.stopPropagation()
const { x, width } = event.currentTarget.getBoundingClientRect()
if (event.clientX > x + width / 2) {
this.texta.poscurseur = event.currentTarget.num + 1
} else {
this.texta.poscurseur = event.currentTarget.num
}
this.texta.poscurseurIn = false
this.texta.poscurseurInRac = false
this.unTAbc[0][(event.currentTarget.num + 1) * 2].focus()
}
clickzone () {
if (this.disabled) return
this.isblur = false
this.textaCont.setAttribute('class', 'zsmMqFocused')
this.majaffiche('')
}
corrige (bon) {
super.corrige(bon)
if (bon) {
if (this.chapo === 1) {
this.unTAb.classList.add('zsmAngleVert')
this.unTAb.classList.remove('zsmAngle')
} else if (this.chapo === 2) {
this.unTAb.classList.add('zsmArcVert')
this.unTAb.classList.remove('zsmArc')
}
} else {
if (this.chapo === 1) {
this.unTAb.classList.add('zsmAngleOrange')
this.unTAb.classList.remove('zsmAngle')
} else if (this.chapo === 2) {
this.unTAb.classList.add('zsmArcOrange')
this.unTAb.classList.remove('zsmArc')
}
}
}
disable () {
if (this.disabled) { return }
this.blur()
removeTab(this)
document.removeEventListener('click', this._gereBlurListener, false)
this.disabled = true
this.textaCont.setAttribute('class', '')
this.textaCont.removeEventListener('click', this._clickZoneListener, false)
j3pDetruit(this.textaIm)
this.cacheKlavier()
j3pDetruit(this.divClavier, this.divClavierAuto)
for (let i = 0; i < this.texta.elemnum.length; i++) {
j3pFreezeElt(this.unTAbc[0][i * 2 + 1])
}
}
focus () {
if (this.disabled) { return }
blurAllTabs(this)
this.isblur = false
this.okMod = false
setTimeout(() => {
this.majaffiche('')
this.textaIm.style.display = ''
if (typeof this.onModif === 'function') {
this.onModif()
}
}, 0)
}
gereblur (event) {
if (!$(event.target).closest('#' + this.textaCont.id).length) {
this.blur()
}
}
majaffiche (elem) {
if (this.disabled) return
if (elem === '^' && this.hasAngle) {
// on passe à 0 si c’est 1 et fixe 1 sinon
this.chapo = (this.chapo === 1) ? 0 : 1
} else if (elem === '$') {
this.chapo = (this.chapo === 2) ? 0 : 2
} else if (elem !== '') {
if (this.texta.elemnum.length > this.limite) {
// on le passe sur fond rouge
const eLemCoNt = this.textaCont
// on le passe sur fond rouge
eLemCoNt.style.backgroundColor = '#ff0000'
// et on le remettra en blanc dans 100ms
setTimeout(() => { eLemCoNt.style.backgroundColor = '#ffffff' }, 100)
return
}
if (elem === 'µ') elem = '^'
this.texta.elemnum.splice(this.texta.poscurseur, 0, elem)
this.texta.poscurseur++
}
/// verif 2 chiffres cote a cote
if (elem !== '') {
this.bon = undefined
}
if (this.okMod && typeof this.onModif === 'function') {
this.okMod = false
this.onModif()
}
this.majAfficheHelper2()
if (this.sansBord) this.textaCont.style.border = '0px solid black'
const clickcListener = this.clickc.bind(this)
const metCurseurListener = this.metcurseur.bind(this)
const clavierListener = this.clavier.bind(this)
const vireListener = this.vire.bind(this)
let bufel, elti, eltCi, i
for (i = 0; i < this.texta.elemnum.length; i++) {
bufel = this.texta.elemnum[i]
elti = this.unTAbc[0][2 * i]
eltCi = this.unTAbc[0][2 * i + 1]
if (bufel.includes('widehat')) {
j3pStyle(eltCi, { fontSize: '85%' })
eltCi.vAlign = 'top'
}
if (bufel === ' ' || bufel === ' ') {
eltCi.innerHTML = ' '
} else if (bufel === '^') {
eltCi.innerHTML = '^'
} else {
j3pAffiche(eltCi, null, '$' + this.texta.elemnum[i] + '$')
}
eltCi.num = i
eltCi.num2 = 0
elti.num = i
elti.num2 = 0
elti.tabIndex = 0
eltCi.addEventListener('click', clickcListener, false)
elti.addEventListener('focus', metCurseurListener, false)
elti.addEventListener('keydown', clavierListener, false)
// @todo expliquer pourquoi il faut ajouter un listener qui supprime la propagation de l’événement pour keypress et keyup
elti.addEventListener('keypress', vireListener, false)
elti.addEventListener('keyup', vireListener, false)
eltCi.classList.add('noMarginPadding')
}
// en fait faut faire une fois de plus pour la dernière zone de saisie (tout à droite)
// exactement la mm boucle que au dessus je la met à la fin
elti = this.unTAbc[0][this.unTAbc[0].length - 1]
elti.tabIndex = 0
elti.addEventListener('focus', metCurseurListener, false)
elti.addEventListener('keydown', clavierListener, false)
elti.addEventListener('keypress', vireListener, false)
elti.addEventListener('keyup', vireListener, false)
elti.classList.add('noMarginPadding')
if (i === 0) { this.unTAbc[0][0].innerHTML = ' ' }
this.textaIm.addEventListener('mousedown', this.place.bind(this, false), false)
this.textaIm.style.cursor = 'pointer'
this.textaTab.style.width = (Math.min(this.textaCont.offsetWidth, 10) + this.textaIm.offsetWidth) + 'px'
// mpol
this.textaCont.classList.add('zsmMqFocused')
this.textaIm.classList.add('zsmMq')
if (this.chapo === 0) {
this.unTAb.classList.remove('zsmAngle')
this.unTAb.classList.remove('zsmArc')
} // ça va virer toutes les autres
if (this.chapo === 1) {
const gg = this.bon ? 'zsmAngleVert' : 'zsmAngle'
this.unTAb.classList.add(gg)
}
if (this.chapo === 2) {
const gg = this.bon ? 'zsmArcVert' : 'zsmArc'
this.unTAb.classList.add(gg)
}
this.majAfficheHelper()
if (!this.isblur) {
this.unTAbc[0][this.texta.poscurseur * 2].focus()
}
if (this.tabauto && elem !== '') {
this.blur()
prevTabFocus(this)
}
if (this.bon === false) this.corrige(false)
if (this.modifL && elem !== '' && !this.isRest) this.modifL()
} // majaffiche
metcurseur (event) {
// on est mis en listener avec du bind, donc notre this est bien l’this ZoneStyleMathquill1
if (this.disabled) return
// pour récupérer le this qu’on aurait eu sans le bind (l’élément sur lequel on a appelé addEventListener)
// on utilise currentTarget (target est l’élément sur lequel on a déclenché l’événement, il peut être un enfant de currentTarget)
// cf https://developer.mozilla.org/fr/docs/Web/API/EventTarget/addEventListener#la_valeur_de_this_%C3%A0_lint%C3%A9rieur_du_gestionnaire
event.currentTarget.className = 'zsmMqFocusedCursor'
}
modif (items) {
this.texta.elemnum = []
this.texta.poscurseur = 0
for (const item of items) {
this.majaffiche(item.replace(' ', ' '))
}
this.majaffiche('')
}
reponse () {
let rep = ''
for (const item of this.texta.elemnum) {
if (item === '{}^2') {
rep += '^{2}'
} else {
if (item === '{}^3') {
rep += '^{3}'
} else {
rep += item
}
}
}
return rep
}
reponsechap () {
return [this.reponse(), this.chapo]
}
reset (items) {
this.isRest = true
this.texta.elemnum = [...items]
this.okMod = true
this.texta.poscurseur = 0
this.majaffiche('')
this.isRest = false
}
vire (event) {
event.preventDefault()
event.stopPropagation()
}
}
export default ZoneStyleMathquill1