interface/Button.js

/*
 * Created by yvesb on 05/10/2016.
 */
/*
 * MathGraph32 Javascript : Software for animating online dynamic mathematics figures
 * https://www.mathgraph32.org/
 * @Author Yves Biton (yves.biton@sesamath.net)
 * @License: GNU AGPLv3 https://www.gnu.org/licenses/agpl-3.0.html
 */
import { cens, getStr, preventDefault } from '../kernel/kernel'
import constantes from '../kernel/constantes'
// import $ from 'jquery'
import $ from 'jquery'

export default Button

/**
 *
 * @param {MtgApp} app
 * @param {string} fileName
 * @param {string} tip
 * @param {number} w
 * @param {number} h
 * @param {boolean} [bframe=true]
 * @param {boolean} [bNoZoomFactor=false]
 * @constructor
 */
function Button (app, fileName, tip, w, h, bframe = true, bNoZoomFactor = false) {
  if (arguments.length === 0) return
  this.app = app
  this.fileName = fileName
  const zf = bNoZoomFactor ? 1 : app.zoomFactor
  this.w = w * zf
  this.h = h * zf
  this.tip = getStr(tip)
  this.bframe = bframe
  this.clicks = 0
  this.isArrow = this.fileName.indexOf('arrow') === 0
}
/**
 * Foonction donnant au bouton l'apparence activé ou non suibant la valeur de bActivate
 * @param {boolean} bActivate
 */
Button.prototype.activate = function (bActivate) {
  $(this.mask).attr('fill-opacity', '0')
  $(this.frameRect).attr('fill', (bActivate)
    ? 'url(#buttonGradAct)'
    : constantes.buttonBackGroundColor)
  $(this.mask).attr('stroke', (bActivate)
    ? constantes.buttonActivatedBackGroundColor
    : constantes.buttonBackGroundColor)
  this.isActivated = bActivate
}
Button.prototype.build = function () {
  const self = this
  const w = this.w
  const h = this.h
  const g = cens('g')
  // g.setAttribute("id",this.tip);
  this.container = g
  const frameRect = cens('rect', {
    width: w,
    height: h,
    x: '0',
    y: '0',
    stroke: constantes.buttonStroke,
    fill: constantes.buttonFill,
    'fill-opacity': '1'
  })
  if (!this.bframe) $(frameRect).css('visibility', 'hidden')
  this.frameRect = frameRect
  g.appendChild(frameRect)
  const mask = cens('rect', {
    width: w,
    height: h,
    x: '0',
    y: '0',
    stroke: constantes.buttonStroke,
    fill: 'url(#buttonGrad)',
    'fill-opacity': '0' // Sera passé à un quand la souris passe dessus
  })
  this.mask = mask
  if (!this.bframe) {
    $(mask).css('visibility', 'hidden').css('pointer-events', 'all')
  }
  g.appendChild(mask)
  if (this.isArrow) {
    const points = '3,1 3,' + String(h - 1) + ' ' + String(w - 2) + ',' + String(h / 2)
    const poly = cens('polygon', {
      points,
      fill: 'url(#arrowGrad)',
      'fill-opacity': '0.5',
      style: 'stroke:#A9A9F5;stroke-width:1',
      'pointer-events': 'none'
    })
    this.poly = poly
    g.appendChild(poly)
    g.addEventListener('mouseover', function () {
      self.poly.setAttribute('fill-opacity', '1')
    })
    g.addEventListener('mouseout', function () {
      self.poly.setAttribute('fill-opacity', '0.5')
    })
  } else {
    if (this.fileName.length !== 0) { // fileName est "" pour les OneColorButton
      const gImage = cens('g', {
        transform: 'translate(1,1)',
        'pointer-events': 'none'// ,
        // opacity: "0.8"
      })
      g.appendChild(gImage)
      this.image = cens('image', {
        width: String(w - 2),
        height: String(h - 2),
        x: '0',
        y: '0'
      })
      gImage.appendChild(this.image)
      // Chargement en lazy-loading de l'image
      import(`src/images/${this.fileName}.png`)
        .then(({ default: img }) => {
          self.image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', img)
        })
        .catch(error => console.error(`impossible de charger src/images/${this.fileName}.png`, error))
    }
  }
  mask.addEventListener('mouseover', function () {
    if (!self.isActivated) self.mask.setAttribute('fill-opacity', '1')
  })
  mask.addEventListener('mouseout', function () { self.mask.setAttribute('fill-opacity', '0') })
  // Initialement les boutons de la barre de gauche ne sont pas visibles.
  // Ils sont rendus visibles quand on crée les expandableBar
  g.setAttribute('visibility', 'hidden') // Sera changé pour les descendants
  const downListener = this.devicedown.bind(this)
  const moveListener = this.deviceMove.bind(this)
  const activeOpts = { capture: false, passive: false }
  g.addEventListener('mousedown', downListener, activeOpts)
  g.addEventListener('mousemove', moveListener, activeOpts)
  // faut préciser l'option passive sur du touch pour éviter les warnings de chrome,
  // ici à false car justement devicedown fait du preventDefault pour empêcher un démarrage de scroll
  g.addEventListener('touchstart', downListener, activeOpts)
  g.addEventListener('touchmove', moveListener, activeOpts)
}

Button.prototype.devicedown = function (evt) {
  // var app = this.app;
  // var doc = app.doc;
  // Sur les périphériques mobiles il peut y avoir deux événements générés quand on touche l'écran : onmousedown et ontouchstart
  // if (doc.type && (doc.type != type)) return;
  // doc.type = type;
  this.clicks++
  if (this.clicks >= 2) {
    this.doubleClickAction()
    this.clicks = 0 // Ajout version 6.1.0
  } else {
    const self = this
    setTimeout(function () {
      switch (self.clicks) {
        case 1 :
          self.singleClickAction()
          self.clicks = 0 // Ajout version 6.1.0
          break
        case 2 :
          self.doubleClickAction()
          self.clicks = 0 // Ajout version 6.1.0
      }
    }, 300)
  }
  preventDefault(evt)
  evt.stopPropagation()
}

Button.prototype.deviceMove = function (evt) {
  // Si le bouton n'a pas de cadre(ToolBarArrow) pas de tip
  if (!this.bframe || self.tipDisplayed || self.tip === '') return
  const app = this.app
  // var doc = app.doc;
  // Sur les périphériques mobiles il peut y avoir deux événements générés quand on touche l'écran : onmousedown et ontouchstart
  // if (doc.type && (doc.type != type)) return;
  // doc.type = type;
  if (!this.tipDisplayed && this.tip !== '') {
    app.cacheTip() // Sans argument pour effacer l'ancien tip quel qu'il soit
    this.tipDisplayed = false
    app.setTip(this)
    const self = this
    setTimeout(function () {
      self.app.cacheTip(self)
    }, 2000)
  }
  preventDefault(evt)
  evt.stopPropagation()
}
/**
 * Fonction appelée lors d'un simple clic sur la fonction
 * A redéfinir pour les descendants
 */
Button.prototype.singleClickAction = function () {
}
/**
 * Fonction appelée lors d'un double clic sur la fonction
 * A redéfinir pour les descendants si double click implémenté
 */
Button.prototype.doubleClickAction = function () {
  this.singleClickAction()
}