legacy/outils/zoneColoriage/ZoneColoriage.js

import { j3pAddContent, j3pAddElt, j3pAjouteBouton, j3pArrondi, j3pClone, j3pElement, j3pEmpty, j3pGetNewId, j3pGetRandomBool, j3pGetRandomElt, j3pIsHtmlElement, j3pShowError, j3pShuffle } from 'src/legacy/core/functions'
import { j3pCreeSVG } from 'src/legacy/core/functionsSvg'
import { j3pAffiche } from 'src/lib/mathquill/functions'
import { getMtgCore } from 'src/lib/outils/mathgraph'
import MenuContextuel from 'src/legacy/outils/menuContextuel'
import { getJ3pConteneur } from 'src/lib/core/domHelpers'
import ffondd from './ffondd.jpg'

import { modeles } from './datas'
import './zoneColoriage.css'
import { placeJouerSon } from 'src/legacy/outils/zoneStyleMathquill/functions'

/**
 * Le background initial d’une case (utilisé pour savoir si on l’a coloriée)
 * @type {string}
 * @private
 */
// fixme 'tansparant' ca va pas, y’a des dessins en noirs et blanc
const emptyColor = 'url(' + ffondd + ')'

class ZoneColoriage {
  /**
   *
   * @param {Object} values
   * @param {HTMLElement|string} values.conteneur le div contenant le pixel
   * @param {} values.classes un tableau contenant les modèle, les contenus associés
   * @param {} values.dim le cote du carré
   */
  constructor ({ conteneur, classes, dim, j3pCont }) {
    if (typeof conteneur === 'string') conteneur = j3pElement(conteneur)
    if (!j3pIsHtmlElement(conteneur)) throw Error('Conteneur invalide')
    conteneur.classList.add('zoneColoriage')
    /**
     * Le conteneur
     * @type {HTMLElement}
     */
    this.conteneur = conteneur
    this.classes = classes
    this.dim = dim
    this.j3pCont = j3pCont
    this._isMenuOpen = false
    /**
         * La zone j3p totale (class j3pContainer) dans laquelle se trouve notre conteneur
         * @type {HTMLElement}
         */
    this.parent = getJ3pConteneur(conteneur, true) || j3pElement('j3pContainer')
    if (!this.parent) throw Error('Pas trouvé de conteneur')
    if (this.parent.URL) this.parent = j3pElement('j3pContainer')

    // trouve un dessin en fonction de dim et du nb de classes
    const modelesPossibles = modeles.filter(m => m.t === this.dim && m.couleurs.length === this.classes.length)
    if (!modelesPossibles.length) throw Error(`Aucun modèle avec la dimension ${this.dim} et ${this.classes.length} couleurs`)
    // on en prend un au hasard dans la liste
    this.modeleEnCours = j3pGetRandomElt(modelesPossibles)
    if (this.modeleEnCours.changeCoul) {
      this.modeleEnCours.couleurs = j3pShuffle(this.modeleEnCours.couleurs)
    }
    if (this.modeleEnCours.invHautBas && j3pGetRandomBool()) {
      const tabuf = []
      for (let i = 0; i < this.dim; i++) {
        for (let j = 0; j < this.dim; j++) {
          tabuf[i + (this.dim - j - 1) * this.dim] = this.modeleEnCours.tab[i + j * this.dim]
        }
      }
      this.modeleEnCours.tab = tabuf
    }
    if (this.modeleEnCours.InvGaucheDroite && j3pGetRandomBool()) {
      const tabuf = []
      for (let i = 0; i < this.dim; i++) {
        for (let j = 0; j < this.dim; j++) {
          tabuf[this.dim - i - 1 + j * this.dim] = this.modeleEnCours.tab[i + j * this.dim]
        }
      }
      this.modeleEnCours.tab = tabuf
    }
    if (this.modeleEnCours.tourneD && j3pGetRandomBool()) {
      const tabuf = []
      for (let i = 0; i < this.dim; i++) {
        for (let j = 0; j < this.dim; j++) {
          tabuf[j + i * this.dim] = this.modeleEnCours.tab[i + j * this.dim]
        }
      }
      this.modeleEnCours.tab = tabuf
    }
    this.changeCoList = this._changeCorrection.bind(this)
    /**
     * Passe à true dès que mtg est chargé et tout est affiché
     * @type {boolean}
     * @private
     */
    this._isReady = false
    this.oldcarr = undefined
    getMtgCore({ withMathJax: true })
      .then(
        // success
        (mtgAppLecteur) => {
          this.mtgAppLecteur = mtgAppLecteur
          this.mtgAppLecteur.removeAllDoc()
          this._initDisplay()
        },
        // failure
        (error) => {
          j3pShowError(error, { message: 'mathgraph n’est pas correctement chargé', mustNotify: true })
        })
    // plantage dans enonceMain
      .catch(j3pShowError)
  }

  /**
   * Affiche le tableau (dès que mathgraph est chargé)
   * @private
   */
  _initDisplay () {
    this.plateau = j3pAddElt(this.conteneur, 'div', null, {
      style: {
        display: 'grid',
        gridTemplateColumns: 'auto 20px auto',
        gridTemplateRows: 'auto',
        rowGap: '10px'
      }
    })
    this.yy = j3pAddElt(this.plateau, 'div', null, {
      gridColumn: 1,
      gridRow: 1
    })
    this.menu = j3pAddElt(this.plateau, 'div', null, {
      style: {
        gridColumn: 3,
        gridRow: 1,
        display: 'grid',
        gridTemplateColumns: '1fr',
        gridTemplateRows: 'repeat(' + (this.classes.length + 2) + ', auto)',
        rowGap: '1px',
        columnGap: '1px'
      }
    })
    const titre = j3pAddElt(this.menu, 'div', null, {
      style: {
        gridColumn: 1,
        gridRow: 1,
        display: 'flex',
        padding: '10px',
        border: '3px solid blue',
        borderRadius: '1px',
        verticalAlign: 'middle',
        justifyContent: 'center',
        alignItems: 'center',
        background: '#97eed6'
      }
    })
    j3pAddContent(titre, '<b><u>Couleurs disponibles</u></b>')
    this.menuChoices = []
    this.classes.forEach(el => {
      this.menuChoices.push({
        name: String(this.classes.indexOf(el)),
        label: 'couleur ' + (1 + this.classes.indexOf(el))
      })
      const MenuCool = j3pAddElt(this.menu, 'div', null, {
        style: {
          gridColumn: 1,
          gridRow: 2 + this.classes.indexOf(el),
          display: 'flex',
          padding: '10px',
          border: '3px solid black',
          borderRadius: '1px',
          justifyContent: 'left',
          alignItems: 'center',
          background: '#c1f3db'
        }
      })
      j3pAddElt(MenuCool, 'div', null, {
        style: {
          borderRadius: '1px',
          background: this.modeleEnCours.couleurs[this.classes.indexOf(el)],
          width: '40px',
          height: '40px'
        },
        className: 'cases2'
      })
      const lacoulT = j3pAddElt(MenuCool, 'div', null, {
        style: {
          paddingLeft: '10px'
        }
      })
      if (typeof el.mod === 'string') {
        j3pAffiche(lacoulT, null, el.mod)
      } else {
        if (el.mod.type === 'mathgraph') {
          const svgID = j3pGetNewId('mttm')
          const width = (el.mod.width) ? (4 / 3 * el.mod.width - 32) : 130
          const height = (el.mod.content.height) ? (8 / 9 * el.mod.content.height + 7) : 90
          j3pCreeSVG(lacoulT, { id: svgID, width, height })
          this.mtgAppLecteur.addDoc(svgID, el.mod.content.txtFigure, true)
          if (el.mod.A) this.mtgAppLecteur.giveFormula2(svgID, 'A', el.mod.A)
          if (el.mod.ALEA) this.mtgAppLecteur.giveFormula2(svgID, 'ALEA', el.mod.ALEA)
          this.mtgAppLecteur.calculate(svgID, true)
          this.mtgAppLecteur.display(svgID)
        }
        if (el.mod.type === 'image') {
          const immm = j3pAddElt(lacoulT, 'img', '', { src: el.mod.content.txtFigure, width: el.mod.content.width, height: el.mod.content.height })
          immm.style.width = el.mod.content.width + 'px'
          immm.style.height = el.mod.content.height + 'px'
        }
        if (el.mod.type === 'son') {
          const ui = j3pAddElt(lacoulT, 'div')
          ui.style.border = '1px solid black'
          ui.style.borderRadius = '5px'
          ui.style.padding = '5px'
          ui.style.paddingTop = '0px'
          ui.style.background = '#aeef59'
          ui.style.textAlign = 'center'
          placeJouerSon(ui, el.mod.content, this, 'txtFigure')
        }
      }
    })
    this.fig = j3pAddElt(this.yy, 'div', null, {
      style: {
        display: 'grid',
        gridTemplateColumns: 'repeat(' + this.dim + ', 1fr)',
        gridTemplateRows: 'repeat(' + this.dim + ', 1fr)',
        rowGap: '1px',
        columnGap: '1px',
        border: '3px solid black',
        borderRadius: '1px'
      }
    })
    this.cases = []
    for (let i = 0; i < this.dim; i++) {
      this.cases[i] = []
      for (let j = 0; j < this.dim; j++) {
        this.cases[i][j] = {
          carre: j3pAddElt(this.fig, 'div', '', {
            style: {
              gridColumn: i + 1,
              gridRow: j + 1,
              background: emptyColor
            },
            className: 'case',
            backgroundSave: emptyColor
          })
        }
        const am = j3pClone(this.menuChoices)
        am.forEach(el => {
          el.callback = (a, b) => {
            this.cases[i][j].carre.style.background = this.modeleEnCours.couleurs[Number(b.name)]
            this.cases[i][j].carre.backgroundSave = this.modeleEnCours.couleurs[Number(b.name)]
            this.cases[i][j].carre.style.border = ''
          }
        })
        this.cases[i][j].menu = new MenuContextuel(this.cases[i][j].carre, am, {}, {
          pasToucheBackground: true,
          j3pCont: this.j3pCont,
          colle: true,
          callBackOpen: this._getCallbackOpen(i, j),
          callBackClose: () => { this._isMenuOpen = false }
        })
        this.cases[i][j].cont = this.classes[this.modeleEnCours.tab[i + j * this.dim]].func()
        this.cases[i][j].carre.addEventListener('mouseover', this._getMouseOverListener.bind(this, i, j), false)
        for (let k = 0; k < this.classes.length; k++) {
          j3pEmpty(this.cases[i][j].menu.listeChoicesMenu[k].elt)
          const elcoul = j3pAddElt(this.cases[i][j].menu.listeChoicesMenu[k].elt, 'div')
          elcoul.style.border = '1px solid black'
          elcoul.style.background = this.modeleEnCours.couleurs[k]
          elcoul.backgroundSave = this.modeleEnCours.couleurs[k]
          elcoul.style.height = '20px'
          elcoul.style.width = '40px'
        }
      }
    }
    const uu = j3pAddElt(this.menu, 'div', null, {
      style: {
        gridColumn: 1,
        gridRow: 2 + this.classes.length,
        marginTop: '10px',
        // display: 'flex',
        padding: '10px',
        border: '3px solid blue',
        borderRadius: '1px',
        background: '#fff',
        // verticalAlign: 'middle',
        justifyContent: 'center'
        // alignItems: 'center'
      }
    })
    const ff = j3pAddElt(uu, 'div', null, {
      style: {
        gridColumn: 1,
        gridRow: 2,
        textAlign: 'center'
      }
    })
    j3pAddContent(ff, '<u>Contenu de la case selectionnée</u>:<br>')
    this.caseCont = j3pAddElt(uu, 'div', null, {
      style: {
        gridColumn: 1,
        gridRow: 2,
        textAlign: 'center',
        display: 'flex',
        verticalAlign: 'middle',
        justifyContent: 'center',
        alignItems: 'center'
      }
    })
    this._isReady = true
  }

  /**
   * Retourne une callback pour la propriété callBackOpen d’un MenuContextuel
   * @param i
   * @param j
   * @return {(function(): void)} le listener
   * @private
   */
  _getCallbackOpen (i, j) {
    return () => {
      setTimeout(() => { this._isMenuOpen = true }, 10)
      j3pEmpty(this.caseCont)
      if (this.oldcarr) this.cases[this.oldcarr.i][this.oldcarr.j].carre.style.border = ''
      this.oldcarr = { i, j }
      const current = this.cases[i][j]
      current.carre.style.border = '1px solid red'
      if (typeof current.cont === 'string') {
        j3pAffiche(this.caseCont, null, current.cont)
      } else {
        if (current.cont.type === 'mathgraph') {
          if (this.oldSvg) {
            this.mtgAppLecteur.removeDoc(this.oldSvg)
          }
          const sgvId = j3pGetNewId('mtt')
          const w1 = Number(current.cont.content.width) || Number(current.cont.content.modDimx)
          let width = (w1) ? (4 / 3 * w1 - 32) : 130
          const w2 = Number(current.cont.content.height) || Number(current.cont.content.modDimy)
          let height = (w2) ? (8 / 9 * w2 + 7) : 90
          if (width < 20) width = 130
          if (height < 20) height = 90
          j3pCreeSVG(this.caseCont, { id: sgvId, width, height })
          this.mtgAppLecteur.addDoc(sgvId, current.cont.content.txtFigure, true)
          if (current.cont.content.A) this.mtgAppLecteur.giveFormula2(sgvId, 'A', current.cont.content.A)
          if (current.cont.content.ALEA) this.mtgAppLecteur.giveFormula2(sgvId, 'ALEA', current.cont.content.ALEA)
          this.mtgAppLecteur.calculateAndDisplayAll(true)
          this.oldSvg = sgvId
        }
        if (current.cont.type === 'image') {
          const immm = j3pAddElt(this.caseCont, 'img', '', { src: current.cont.content.txtFigure, width: current.cont.content.width, height: current.cont.content.height })
          immm.style.width = current.cont.content.width + 'px'
          immm.style.height = current.cont.content.height + 'px'
        }
        if (current.cont.type === 'son') {
          const ui = j3pAddElt(this.caseCont, 'div')
          ui.style.border = '1px solid black'
          ui.style.borderRadius = '5px'
          ui.style.padding = '5px'
          ui.style.paddingTop = '0px'
          ui.style.background = '#aeef59'
          ui.style.textAlign = 'center'
          placeJouerSon(ui, current.cont.content, this, 'txtFigure')
        }
      }
    }
  }

  /**
   * @typedef voidFunction
   * @returns {void}
   */

  /**
   * Retourne un listener mouseover pour l’objet carre de la case (i, j)
   * @param i
   * @param j
   * @return {voidFunction}
   * @private
   */
  _getMouseOverListener (i, j) {
    if (this._isMenuOpen) return
    j3pEmpty(this.caseCont)
    if (!this.disabled) {
      if (this.oldcarr) this.cases[this.oldcarr.i][this.oldcarr.j].carre.style.border = ''
      this.oldcarr = { i, j }
      this.cases[this.oldcarr.i][this.oldcarr.j].carre.style.border = '1px solid red'
    }
    const current = this.cases[i][j]
    if (typeof current.cont === 'string') {
      j3pAffiche(this.caseCont, null, current.cont)
    } else {
      if (current.cont.type === 'mathgraph') {
        if (this.oldSvg) {
          this.mtgAppLecteur.removeDoc(this.oldSvg)
        }
        const sgvId = j3pGetNewId('mtt')
        const w1 = Number(current.cont.content.width) || Number(current.cont.content.modDimx)
        let width = (w1) ? (4 / 3 * w1 - 32) : 130
        const w2 = Number(current.cont.content.height) || Number(current.cont.content.modDimy)
        let height = (w2) ? (8 / 9 * w2 + 7) : 90
        if (width < 20) width = 130
        if (height < 20) height = 90
        j3pCreeSVG(this.caseCont, { id: sgvId, width, height })
        this.mtgAppLecteur.addDoc(sgvId, current.cont.content.txtFigure, true)
        if (current.cont.content.A) this.mtgAppLecteur.giveFormula2(sgvId, 'A', current.cont.content.A)
        if (current.cont.content.ALEA) this.mtgAppLecteur.giveFormula2(sgvId, 'ALEA', current.cont.content.ALEA)
        this.mtgAppLecteur.calculateAndDisplayAll(true)
        this.oldSvg = sgvId
      }
      if (current.cont.type === 'image') {
        const immm = j3pAddElt(this.caseCont, 'img', '', { src: current.cont.content.txtFigure, width: current.cont.content.width, height: current.cont.content.height })
        immm.style.width = current.cont.content.width + 'px'
        immm.style.height = current.cont.content.height + 'px'
      }
      if (current.cont.type === 'son') {
        const ui = j3pAddElt(this.caseCont, 'div')
        ui.style.border = '1px solid black'
        ui.style.borderRadius = '5px'
        ui.style.padding = '5px'
        ui.style.margin = '5px'
        ui.style.paddingTop = '0px'
        ui.style.background = '#aeef59'
        ui.style.textAlign = 'center'
        placeJouerSon(ui, current.cont.content, this, 'txtFigure')
      }
    }
    if (!this.disabled) {
      for (let h = 0; h < this.cases.length; h++) {
        for (let y = 0; y < this.cases.length; y++) {
          if (y === j && h === i) continue
          this.cases[h][y].menu.hide()
        }
      }
    }
  }

  yareponse () {
    // on retourne false si une case a encore sa couleur initiale
    return !this.cases.some(row => row.some(cell => cell.carre.backgroundSave === emptyColor))
  }

  isOk () {
    let ok = true
    this._nbCasesFausses = 0
    for (let i = 0; i < this.dim; i++) {
      for (let j = 0; j < this.dim; j++) {
        if (this.modeleEnCours.tab[i + j * this.dim] !== this.modeleEnCours.couleurs.indexOf(this.cases[i][j].carre.backgroundSave)) {
          ok = false
          this._nbCasesFausses++
          this.cases[i][j].carre.style.border = '3px solid #ce4e00'
        } else {
          this.cases[i][j].carre.style.border = '3px solid #00FF00'
        }
      }
    }
    return ok
  }

  corrige (bool) {
    if (!this._isReady) {
      // c’est pas prêt on se rappellera dans 0.1s
      return setTimeout(() => this.corrige(bool), 100)
    }
    this._isCorrection = false
    this.caseOub = []
    for (let i = 0; i < this.dim; i++) {
      this.caseOub[i] = []
      for (let j = 0; j < this.dim; j++) {
        this.caseOub[i][j] = { coul: this.cases[i][j].carre.backgroundSave, border: this.cases[i][j].carre.style.border }
      }
    }
    if (bool === false) {
      j3pAddElt(this.yy, 'div', null)
      this._btnCorrection = j3pAjouteBouton(this.yy, this.changeCoList, { value: 'Voir la correction' })
    }
  }

  _changeCorrection () {
    this._isCorrection = !this._isCorrection
    this._btnCorrection.value = (this._isCorrection) ? 'Voir ma réponse' : 'Voir la correction'
    for (let i = 0; i < this.dim; i++) {
      for (let j = 0; j < this.dim; j++) {
        if (this._isCorrection) {
          this.cases[i][j].carre.style.background = this.modeleEnCours.couleurs[this.modeleEnCours.tab[i + j * this.dim]]
          this.cases[i][j].carre.backgroundSave = this.modeleEnCours.couleurs[this.modeleEnCours.tab[i + j * this.dim]]
          this.cases[i][j].carre.style.border = '2px solid #00F'
        } else {
          this.cases[i][j].carre.style.background = this.caseOub[i][j].coul
          this.cases[i][j].carre.backgroundSave = this.caseOub[i][j].coul
          this.cases[i][j].carre.style.border = this.caseOub[i][j].border
        }
      }
    }
  }

  disable () {
    this.disabled = true
    if (!this._isReady) {
      // faut pas détruire le menu s’il n’a pas encore été créé… on revient dans 0.1s
      return setTimeout(() => this.disable(), 100)
    }
    for (let i = 0; i < this.dim; i++) {
      for (let j = 0; j < this.dim; j++) {
        this.cases[i][j].menu.destroy()
      }
    }
  }

  getScore () {
    return j3pArrondi(1 - (this._nbCasesFausses / (this.dim * this.dim)), 1)
  }
}

export default ZoneColoriage