Source: control/ControlBar.js

import '../../typedef/index'

import Button from 'ol-ext/control/Button'
import TextButton from 'ol-ext/control/TextButton'
import Toggle from 'ol-ext/control/Toggle'
import Bar from 'ol-ext/control/Bar'
import DragZoom from 'ol/interaction/DragZoom'
import OlExtElement from 'ol-ext/util/element'

import LayerSwitcher from 'ol-ext/control/LayerSwitcher'
import SearchPhoton from 'ol-ext/control/SearchPhoton'
import SearchBANAdvanced from './SearchBANAdvanced'
import SearchWFS from './SearchWFS'

import ControlBarSelectCreate from './ControlBarSelectCreate'
import ControlBarLayerVisibility from './ControlLayerVisibility'
import ControlTriangulation from './ControlTriangulation'
import ControlRecordTrace from './ControlRecordTrace'
import ControlLayerFilters from './ControlLayerFilters'

import { getButtonHtml } from './ControlBarUtils'
import { SelectModes } from '../datalayer/data-layer'
import { unByKey } from 'ol/Observable'
import CustomGeoBookmark from './CustomGeoBookmark'
import ControlZoomScaleResolution from './ControlZoomScaleResolution'
import ControlBarZoom from './ControlBarZoom'
import ZoomSlider from 'ol/control/ZoomSlider'
import Rotate from 'ol/control/Rotate'
import * as olEasing from 'ol/easing'

class ControlBar extends Bar {
/**
 * surcharge de olext bar afin de simplifier son utilisation
 *  @param {Object} options Options compatible ol-ext ol.control.bar surchargé de buttonList
 *  @param {string[]} hideOnMapMode Cache la toolbar si un de ces modes de carte est lancé
 *  @param {Array<btnOption>} options.buttonList
 *  @param {String} btnOption.type Type de bouton (control, button, toggle, textButton)
 *  @param {Object} btnOption.control [control] ol.control valide
 *  @param {String} btnOption.className [button,toggle,textButton] classe du bouton
 *  @param {String} btnOption.title [button,toggle,textButton] titre du bouton
 *  @param {String} btnOption.icon [button,toggle] icon du bouton
 *  @param {String} btnOption.iconId [button,toggle] icon du bouton
 *  @param {String} btnOption.text [textButton] texte du bouton
 *  @param {String} btnOption.html [button,toggle,textButton][remplace icon ou text] contenu html
 *  @param {function} btnOption.onClick [button,toggle,textButton] fonction a appeler au click
 *  @param {function} btnOption.handleClick [button,textButton]remplace onClick] fonction a appeler au click
 *  @param {function} btnOption.onToggle [toggle][remplace onClick] fonction a appeler au toggle (or use change:active event)
 *  @param {ol.interaction} btnOption.interaction [toggle] interaction associée au controle
 *  @param {bool} btnOption.active [toggle] the control is created active, default false
 *  @param {bool} btnOption.disable [toggle] the control is created disabled, default false
 *  @param {Bar} btnOption.bar [toggle] a subbar associated with the control (drawn when active if control is nested in a ol.control.Bar)
 *  @param {bool} btnOption.autoActive [toggle] the control will activate when shown in an ol.control.Bar, default false
 */
  constructor (options) {
    super(options)
    // lire les options pour set des boutons, des toggles

    if (options.position) {
      this.setPosition(options.position)
    }
    this.viewer = options.viewer || null
    this.hideOnMapMode = options.hideOnMapMode || []
    this.selectToggle = null
    if (options.buttonList) {
      options.controls = this.buildButtons(options.buttonList)
    }

    this.viewer.on('change:mapmode', (mapMode) => {
      if (this.hideOnMapMode.length === 0) return

      if (this.hideOnMapMode.indexOf(mapMode.name) > -1) { this.setVisible(false) } else if (!this.getVisible()) { this.setVisible(true) }
    })

    /* const listenerRemoveControl = this.viewer.Map.getControls().on('remove', (ev) => {
      if (ev.element === this) {
        unByKey(listenerRemoveControl)
      } else {
        const ctrl = this.getControlByName(ev.element.get('name'))
        const ctrlIndex = this.controls_.findIndex((control) => {
          return control === ctrl
        })

        if (ctrlIndex > -1) {
          this.controls_.splice(ctrlIndex, 1)

        }
      }
    }) */

    // Bar.call(this, options)
  }
}

ControlBar.prototype.setMap = function (map) {
  unByKey(this.listenerRemoveControl)

  Bar.prototype.setMap.call(this, map)

  if (map) {
    this.listenerRemoveControl = map.getControls().on('remove', (ev) => {
      if (ev.element === this) {
        unByKey(this.listenerRemoveControl)
      } else {
        const ctrl = this.getControlByName(ev.element.get('name'))
        const ctrlIndex = this.controls_.findIndex((control) => {
          return control === ctrl
        })

        if (ctrlIndex > -1) {
          this.controls_.splice(ctrlIndex, 1)
        }
      }
    })
  }
}

ControlBar.prototype.addControl = function (control) {
  Bar.prototype.addControl.call(this, control)
  // dans notre bar les contrôles peuvent être "modal", activer un modale désactive les autres
  if (control.get('modal') && control instanceof Toggle) {
    control.on('change:active', (evt) => {
      if (evt.active === false) { return }
      this.getActiveControls().forEach((ctrl) => {
        if (ctrl.get('modal') && evt.target !== ctrl) {
          ctrl.setActive(false)
        }
      })
    })
  }
}

/**
 * Supprime un control de la bar
 * @param {string} name control name utilisé lors de l'initialisation du control
 */
ControlBar.prototype.removeControl = function (name) {
  const ctrl = this.getControlByName(name)
  const ctrlIndex = this.controls_.findIndex((control) => {
    return control === ctrl
  })

  if (ctrlIndex > -1) {
    this.controls_.splice(ctrlIndex, 1)
    this.viewer.Map.removeControl(ctrl)
  }
}

/**
 * construit des boutons suivant les parametre suivant
 * @param {Array<btnOption>} buttonList
 *  @param {String} btnOption.type Type de bouton (control, button, toggle, textButton)
 *  @param {Object} btnOption.control [control] ol.control valide
 *  @param {String} btnOption.className [button,toggle,textButton] classe du bouton
 *  @param {String} btnOption.title [button,toggle,textButton] titre du bouton
 *  @param {String} btnOption.icon [button,toggle] icon du bouton
 *  @param {String} btnOption.iconId [button,toggle] icon du bouton
 *  @param {String} btnOption.text [textButton] texte du bouton
 *  @param {String} btnOption.html [button,toggle,textButton][remplace icon ou text] contenu html
 *  @param {function} btnOption.onClick [button,toggle,textButton] fonction a appeler au click
 *  @param {function} btnOption.handleClick [button,textButton]remplace onClick] fonction a appeler au click
 *  @param {function} btnOption.onToggle [toggle][remplace onClick] fonction a appeler au toggle (or use change:active event)
 *  @param {ol.interaction} btnOption.interaction [toggle] interaction associée au controle
 *  @param {bool} btnOption.active [toggle] the control is created active, default false
 *  @param {bool} btnOption.disable [toggle] the control is created disabled, default false
 *  @param {ol.control.Bar} btnOption.bar [toggle] a subbar associated with the control (drawn when active if control is nested in a ol.control.Bar)
 *  @param {bool} btnOption.autoActive [toggle] the control will activate when shown in an ol.control.Bar, default false
 * @returns Array<ol.control.Control>
 */
ControlBar.prototype.buildButtons = function (buttonList) {
  // type: button, toggle , textButton ou Control

  return buttonList.map((options) => {
    // c'est déjà un control, on l'utilise tel quel
    if (options.type === 'control') { return options.control }

    // prends des options supplémentaire afin d'harmoniser (icon, onclick...)
    // mais si l'option native de olExt est présenté, elle a raison,
    // exemple options.html prend le pas sur options.icon, handleClick > onClick
    switch (options.type) {
      case 'button':
        return new Button({
          html: getButtonHtml(options.html, options.iconId, options.icon, ''),
          handleClick: options.onClick || undefined,
          ...options,
        })

      case 'toggle':
        return new Toggle({
          html: getButtonHtml(options.html, options.iconId, options.icon, ''),
          onToggle: options.onClick,
          ...options,
        })
      case 'textButton':
        return new TextButton({
          html: `${options.text}`,
          handleClick: options.onClick || undefined,
          ...options,
        })
      default: return null
    }
  })
}

/**
 *
 * @param {option} Object
 *  @param {String} option.type Type de bouton (control, button, toggle, textButton)
 *  @param {Object} option.control [control] ol.control valide
 *  @param {String} option.className [button,toggle,textButton] classe du bouton
 *  @param {String} option.title [button,toggle,textButton] titre du bouton
 *  @param {String} option.icon [button,toggle] icon du bouton
 *  @param {String} option.text [textButton] texte du bouton
 *  @param {String} option.html [button,toggle,textButton][remplace icon ou text] contenu html
 *  @param {function} option.onClick [button,toggle,textButton] fonction a appeler au click
 *  @param {function} option.handleClick [button,textButton]remplace onClick] fonction a appeler au click
 *  @param {function} option.onToggle [toggle][remplace onClick] fonction a appeler au toggle (or use change:active event)
 *  @param {ol.interaction} option.interaction [toggle] interaction associée au controle
 *  @param {bool} option.active [toggle] the control is created active, default false
 *  @param {bool} option.disable [toggle] the control is created disabled, default false
 *  @param {ol.control.Bar} option.bar [toggle] a subbar associated with the control (drawn when active if control is nested in a ol.control.Bar)
 *  @param {bool} option.autoActive [toggle] the control will activate when shown in an ol.control.Bar, default false
 * @return {ol.control.Button}
 */
ControlBar.prototype.addButton = function (options) {
  const control = this.buildButtons([options])[0]
  this.addControl(control)
  return control
}

/** non implementé */
ControlBar.prototype.addGroup = function (options) {
  // TODO: ajoute un groupe (un controlbar imbriqué)
}

/** non implementé */
ControlBar.prototype.addControlBar = function (options) {
  // TODO: ajoute un sous menu
}

ControlBar.prototype.addSeparator = function (options) {
  options = options || {}
  const className = options.className ? options.className + ' separator' : 'separator'
  return this.addButton({
    type: 'textButton',
    name: 'separator',
    className,
    text: '',
  })
}

/** Met a jour le html d'un control */
ControlBar.prototype.updateControlHtml = function (name, html) {
  const control = this.getControlByName(name)
  if (control) { control.setHtml(html) }
}

/** Met a jour l'icone */
ControlBar.prototype.updateControlIcon = function (name, icon) {
  this.updateControlHtml(name, `<i class="${icon}"></i>`)
}

/**
 * Renvoit un controle de la toolbar
 * @param {string} name
 * @returns {ol.control} control
 */
ControlBar.prototype.getControlByName = function (name) {
  const controls = this.getControlsByName(name)
  return controls.length === 1 ? controls[0] : null
}

/**
 * Supprime la bar de création/sélection de la controlbar
 */
ControlBar.prototype.removeSelectCreateBar = function () {
  this.removeControl('kmapv-select-create-bar')
  this.removeControl('kmapv-clear-selection')
  this.removeControl('kmapv-change-selectmode')
}

/**
 * @typedef {Object} ClearButtonOptions
 * @property {Element|string} [html] contenu du bouton (priorité 1)
 * @property {string} [iconId] id du d'element du DOM (priorité 2)
 * @property {string} [icon] <i class='icon'/> du bouton (priorité 3)
 * @property {string} [title] tooltip du bouton clear
 *
 * Options supplémentaire pour l'ajout de la barre de selection
 * @typedef {ClearButtonOptions} ClearButton
 */

/**
 * Ajoute la bar de création/sélection sur la controlbar
 * @param {SelectCreateOptions} options Options de barre d'outil de selection / creation
 * @example
 * returns ajoute la sous-barre de création-selection a cette barre d'outil
 * mapviewer.horizontalToolbar.addSelectCreateBar({
 *   "className": "audit-edit-bar",
 *   "allowChangeSelectMode":true
 *   "clearButton": { // paramètre optionnels du bouton de clear sélection
 *       "icon": null, // voir getButtonHtml
 *       "html": "<i class=\\" q - icon map - tool - icon material - icons \\ ">not_interested</i>",
 *       "title": "Effacer la sélection"
 *   },
 *   "tools": [{ // liste des outils a afficher
 *           "type": "select",
 *           "toolId": "select-single", // sera renvoyé lors de la levé de l'event change:selection
 *           "html": "<i class=\\" q - icon map - tool - icon material - icons \\ ">touch_app</i>",
 *           "title": "Outil de sélection"
 *       }, {
 *           "type": "select",
 *           "toolId": "select-and-edit",
 *           "html": "<i class=\\" q - icon map - tool - icon material - icons \\ ">edit</i>",
 *           "title": "Editer",
 *       },
 *       { // separateur dans la barre d'outils
 *           "type": "separator"
 *       }, { // exemple d'un outil de création
 *           "toolId": "create-EQU", // sera renvoyé lors de la levé de l'event createfeature:create
 *           "idFeature" : "efefef-1234",
 *           "properties" : {test:1},
 *           "title": "Créer un Equipement",
 *           "type": "create",
 *           "iconId": "map-create-icon-create-EQU", // voir getButtonHtml
 *           "icon": "mdi-network",
 *           "options": {
 *               "geometryType": "GeometryCollection", // type de geometrie a créer (type geojson)
 *               "idLayer": "features-EQU", // calque ou créer la geométrie
 *               "digitalizeOptions": { // options de l'outil de digitalisation
 *                   "showConstructionPoint": true,
 *                   "maxPoints" : 5,
 *                   "showConstructionLine" : true,
 *               },
 *               "createTemplates": { // templates de création de géométries
 *                   "features": [{ // geojson features
 *                           "type": "Feature",
 *                           "geometry": {
 *                               "type": "MultiPolygon",
 *                               "coordinates": [coordonnées de l objet]
 *                           },
 *                           "properties": { // propriétés recopiées sur la feature openlayers créées (si elle n'existe pas déjà)
 *                               "name": "Assainissement - Arbre"
 *                           },
 *                           "display": { // libellé affiché dans l'ihm de création
 *                               "label": "Assainissement - Arbre"
 *                           }
 *                       }, {
 *                           "type": "Feature",
 *                           "geometry": {
 *                               "type": "MultiPolygon",
 *                               "coordinates": [coordonnées de l objet]
 *                           },
 *                           "properties": {
 *                               "name": "Assainissement - Bac graisse"
 *                           },
 *                           "display": {
 *                               "label": "Assainissement - Bac graisse"
 *                           }
 *                       },
 *                   ]
 *               }
 *           }
 *       }
 *   ]
 *})
*/
ControlBar.prototype.addSelectCreateBar = function (options) {
  // on ne peut avoir qu'une seule selectCreateBar par controlbar
  this.removeSelectCreateBar()

  options = options || { }
  options = {
    ...{ allowChangeSelectMode: true },
    ...options,
    viewer: this.viewer,
  }

  const bar = new ControlBarSelectCreate(options)
  this.addControl(bar)

  // si il y a des outils de sélection, on ajoute le bouton de désélection
  if (options.tools.some((tool) => {
    return ['select', 'rectangular', 'multiselect', 'zonal', 'polygon'].indexOf(tool.type) > -1
  })) {
    if (options.allowChangeSelectMode) {
      const btnRender = {
        replace: { html: '<i class="kmapv-icon kmapv-icon-selectmode-replace"></i>', title: 'Simple : la nouvelle sélection remplace l\'ancienne' },
        toggle: { html: '<i class="kmapv-icon kmapv-icon-selectmode-toggle"></i>', title: 'Inversion : Sélectionner un élément le bascule entre l\'état sélectionné et désélectionné' },
        add: { html: '<i class="kmapv-icon kmapv-icon-selectmode-add"></i>', title: 'Additif: la nouvelle sélection s\'ajoute à l\'ancienne' },
      }

      const currentMode = this.viewer.dataLayer.getSelectMode()

      const changeSelectModeBtn = new Button({
        html: btnRender[currentMode].html,
        name: 'kmapv-change-selectmode',
        title: btnRender[currentMode].title,
        handleClick: () => {
          const currentMode = this.viewer.dataLayer.getSelectMode()
          let newMode = SelectModes.REPLACE
          switch (currentMode) {
            case SelectModes.REPLACE:
              newMode = SelectModes.ADD
              break
            case SelectModes.ADD:
              newMode = SelectModes.TOGGLE
              break
            default:
          }

          this.viewer.dataLayer.setSelectMode(newMode)
        },
      })

      this.viewer.on('change:selectMode', (evt) => {
        changeSelectModeBtn.setHtml(btnRender[evt.selectMode].html)
        changeSelectModeBtn.setTitle(btnRender[evt.selectMode].title)
      })

      this.addControl(changeSelectModeBtn)
    }
    const clearBtn = new Button({
      html: getButtonHtml(options?.clearButton?.html, options?.clearButton?.iconId, options?.clearButton?.icon, '<i class="kmapv-icon kmapv-icon-clear-selection"></i>'),
      name: 'kmapv-clear-selection',
      title: options?.clearButton?.title || 'Effacer la sélection',
      handleClick: () => {
        this.viewer.dataLayer.clearCurrentSelection()
      },
    })

    this.viewer.on('change:selection', (evt) => {
      clearBtn.setVisible(evt.selection.length > 0)
    })

    clearBtn.setVisible(this.viewer.dataLayer.getCurrentSelection().length > 0)

    this.addControl(clearBtn)
  }
}

ControlBar.prototype.addLayerVisibilityBar = function (options) {
  options = options || {}
  options = {
    ...options,
    viewer: this.viewer,
  }
  const bar = new ControlBarLayerVisibility(options)
  this.addControl(bar)
}

/**
 * Paramètre de recherche PostFilter
 * @typdef {Object} PostFilter
 * @property {'citycode'|'postalcode'|'city'|'context'} type type de filtre (seul les types 'citycode' et 'postalcode' sont pris en charge)
 * @property {Array<string|number>} values liste des valeurs que l'on accepte
 *
 * Paramètre de recherche WfsSource
 * @typdef {Object} WfsSource
 * @property {String} name type de filtre
 * @property {Object} wfs Paramère générale de la source
 * @property {string} wfs.url Url du service
 * @property {string?} [wfs.version=1.0.0] version du service
 * @property {string} featureNS Namespace (a retrouver avec getCapabilities)
 * @property {string} featurePrefix prefix de la source a interroger
 * @property {Array<string>} featureTypes // liste des couches a interroger
 * @property {Array<string>} searchIn // recherche ce que l'utilisateur va taper dans ces champs
 * @property {Object} prefilter préfiltrage sur une des propriétée
 * @property {string | Array} prefilter.key Nom du champ que l'on préfiltre, on peut utiliser une valeur, une liste de valeur ou un mot clé pour utiliser un résultat précédent (code_dep: "94" ou  code_insee: ['94041','94081','94046','94002'] ou code_dep: '__PREVIOUS_RESULT__.commune.code_dep' ou code_com: '__PREVIOUS_RESULT__.commune.<%= code_insee.slice(2) %>')
 * @property {string | Array} value nom du champ ou des champs a retourner (pour utiliser dans la cascade)
 * @property {string} display libellé a afficher dans la liste
 * @property {string?} placeholder surcharge du placeholder de la zone de recherche pour cette étape
 * @property {string?} geometryName  nom de la propriété géométrie (utilisé pour filtre bbox ou lors du zoom vers une feature)
 * @property {boolean?} useBbox tente d'utiliser la bbox du résultat fin le plus proche comme filtre, geometryName est nécessaire
 * @property {number?} minLength surcharge du nombre de caractère minimum a taper pour cette étape
 *
 *
 * Permet d'ajouter un contrôle de recherche
 * @param {Object} options Options de création du contrôle de recherche
 *  @param {'ban'|'photon'|'wfs'} [options.provider=ban] Nom du fournisseur pour la recherche
 *  @param {boolean} [options.reverse=false] Affiche un outil de géocodage inverse d'adresse
 *  @param {string} [options.reverseTitle=Cliquer sur la carte...] Titre à afficher sur le tooltip du bouton de géocodage inverse
 *  @param {boolean} [options.position=true] Priorise les résultats près du centre de la carte affichée
 *  @param {string} [options.label=Rechercher] Libellé affiché pour la zone de recherche
 *  @param {string} [options.placeholder=Rechercher une adresse] Placeholder de la zone de recherche
 *  @param {integer} [options.maxItems=10] Nombre de résultats affichés classés par score
 *  @param {integer} [options.limit=10] Nombre de résultats recherchés (utile lorsque l'on va appliquer un filtre)
 *  @param {number} [options.typing=500] le délais en ms pour lancer la recherche après une saisie utilisateur
 *  @param {integer} [options.minLength=3] la longueur de la chaine de recherche à partir de laquelle lancer la recherche
 *  @param {integer} [options.resultZoom=16] Zoom minimal à appliquer lors de la localisation sur la carte d'un résultat
 *  @param {Array<string>} [options.citycodes=[]] Liste de code insee sur lesquels on va lancer la recherche (attention, une requête sera réalisée par code insee)
 *  @param {Array<PostFilter>} [options.postfilters=[]] Liste des filtres à appliquer sur les résultats (attention, il est possible qu'aucun résultat ne s'affiche)
 *  @param {Array<WfsSource>} [options.wfsSources=[]] Options de recherche pour le type wfs en cascade (dans l'ordre du tableau)
 */
ControlBar.prototype.addSearchControl = function (options = {}) {
  const searchOptions = {
    provider: 'ban',
    reverse: false,
    reverseTitle: 'Cliquer sur la carte...',
    position: true,
    label: 'Rechercher',
    placeholder: 'Rechercher une adresse',
    maxItems: 10,
    typing: 500,
    minLength: 3,
    limit: 10,
    resultZoom: 16,
    citycodes: [],
    postFilters: [],
    wfsSources: [],
    ...options,
  }

  const searchComponents = {
    ban: SearchBANAdvanced,
    photon: SearchPhoton,
    wfs: SearchWFS,
  }

  if (!searchComponents[searchOptions.provider]) {
    return
  }

  const SearchComponent = searchComponents[searchOptions.provider]
  const search = new SearchComponent(searchOptions)

  this.addControl(search)
  // Select feature when click on the reference index
  search.on('select', (event) => {
    const view = this.viewer.Map.getView()
    view.animate({
      center: event.coordinate,
      zoom: Math.max(view.getZoom(), searchOptions.resultZoom),
    })
  })
}

/**
 * Ajoute le bouton de zoom rectangle a la toolbar
 * @param {Object} options
 * @param {Element|string} [options.html] contenu du bouton (priorité 1)
 * @param {string} [options.iconId] id du d'element du DOM (priorité 2)
 * @param {string} [options.icon] <i class='icon'/> du bouton (priorité 3)
 */
ControlBar.prototype.addZoomBox = function (options = {}) {
  const dragZoom = new DragZoom({
    condition: () => { return true },
  })
  const btnDragZoom = new Toggle({
    html: getButtonHtml(options.html, options.iconId, options.icon, '<i class="kmapv-icon kmapv-icon-zoom-area"></i>'),
    name: 'kmapv-zoom-area',
    title: options.title || 'Zoom rectangle',
    interaction: dragZoom,
  })

  // dragZoom.on('boxcancel', () => { btnDragZoom.setActive(false) })
  dragZoom.on('boxend', () => { btnDragZoom.setActive(false) })

  this.addControl(btnDragZoom)
}

ControlBar.prototype.addZoom = function (options = {}) {
  // Contrôle de niveau de zoom
  if (!options.extent) {
    options.extent = this.viewer.zoomOrigin && (() => typeof this.viewer.zoomOrigin === 'function' ? this.viewer.zoomOrigin() : this.viewer.zoomOrigin)
  }

  this.addControl(new ControlBarZoom(options))
}

ControlBar.prototype.addZoomSlider = function (options = {}) {
  // Contrôle de slider de zoom
  this.addControl(new ZoomSlider({}))
}

ControlBar.prototype.addRotate = function (options = {}) {
  this.addControl(new Rotate({
    tipLabel: 'Retour au nord',
    autoHide: true,
    resetNorth: () => {
      // On surcharge la fonction de réinitialisation du
      // nord pour lever l'event resetnorth à l'exterieur
      if (this.viewer.getRotation()) {
        this.viewer.Map.getView().animate({
          rotation: 0,
          duration: 250,
          easing: olEasing.easeOut,
        })
        this.viewer.dispatchEvent('resetnorth')
      }
    },
  }))
}

/**
 * Ajoute le bouton de sélection triangle
 * @param {Object} options
 * @param {Element|string} [options.html] contenu du bouton (priorité 1)
 * @param {string} [options.iconId] id du d'element du DOM (priorité 2)
 * @param {string} [options.icon] <i class='icon'/> du bouton (priorité 3)
 * @param {object} [options.snapOptions] options de snap (doc TODO)
 */
ControlBar.prototype.addTriangulationTool = function (options = { snapOptions: {} }) {
  const btn = new Button({
    html: getButtonHtml(options?.button?.html, options?.button?.iconId, options?.button?.icon, '<i class="kmapv-icon kmapv-icon-triangulation"></i>'),
    name: 'kmapv-triangulation-tool',
    title: options?.button?.title || 'Outil de triangulation',
    handleClick: () => {
      const ctrl = new ControlTriangulation({ viewer: this.viewer, snapOptions: options.snapOptions })
      this.viewer.Map.addControl(ctrl)
    },
  })
  this.addControl(btn)
}

/**
 * Ajoute le bouton d'export de la carte vers une image
 * @param {Object} options
 * @param {Element|string} [options.html] contenu du bouton (priorité 1)
 * @param {string} [options.iconId] id du d'element du DOM (priorité 2)
 * @param {string} [options.icon] <i class='icon'/> du bouton (priorité 3)
 * @param {string} options.imageType A string indicating the image format, default image/png
 * @param {number} options.quality Number between 0 and 1 indicating the image quality to use for image formats that use lossy compression such as image/jpeg and image/webp
 * @param {string} options.orientation Page orientation (landscape/portrait), default guest the best one
 * @param {boolean} options.immediate force print even if render is not complete,  default false
 * @param {string} options.filename Nom de fichier
 */
ControlBar.prototype.addExportMapButton = function (options = { }) {
  const btn = new Button({
    html: getButtonHtml(options?.button?.html, options?.button?.iconId, options?.button?.icon, '<i class="kmapv-icon kmapv-icon-download"></i>'),
    name: 'kmapv-export-map-tool',
    title: options?.button?.title || 'Télécharger une capture d\'écran de la carte',
    handleClick: () => {
      this.viewer.print(options)
    },
  })
  this.addControl(btn)
}

/**
 * Ajoute le bouton de copie de la carte vers le presse papier
 * @param {Object} options
 * @param {Element|string} [options.html] contenu du bouton (priorité 1)
 * @param {string} [options.iconId] id du d'element du DOM (priorité 2)
 * @param {string} [options.icon] <i class='icon'/> du bouton (priorité 3)
 * @param {string} options.imageType A string indicating the image format, default image/png
 * @param {number} options.quality Number between 0 and 1 indicating the image quality to use for image formats that use lossy compression such as image/jpeg and image/webp
 * @param {string} options.orientation Page orientation (landscape/portrait), default guest the best one
 * @param {boolean} options.immediate force print even if render is not complete,  default false
 */
ControlBar.prototype.addCopyMapButton = function (options = { }) {
  const btn = new Button({
    html: getButtonHtml(options?.button?.html, options?.button?.iconId, options?.button?.icon, '<i class="kmapv-icon kmapv-icon-clipboard"></i>'),
    name: 'kmapv-clipboard-tool',
    title: options?.button?.title || 'Copier la carte dans le presse papier',
    handleClick: () => {
      this.viewer.copyMap(options)
    },
  })
  this.addControl(btn)
}

ControlBar.prototype.addRecordTraceButton = function (options = {}) {
  options = { ...options, viewer: this.viewer }
  const btn = new ControlRecordTrace(options)
  this.addControl(btn)
  return btn
}

ControlBar.prototype.addLayerManagerTool = function (options = { }) {
  const toggle = new Toggle({
    name: 'kmapv-layer-manager-bar',
    html: getButtonHtml(options?.button?.html, options?.button?.iconId, options?.button?.icon, '<i class="kmapv-icon kmapv-icon-layers"></i>'),
    title: options?.button?.title || 'Afficher le gestionnaire de calque',
  })

  // lorsque l'on clique sur un bouton, le toggle va prendre l'icone du bouton actif, sinon celui du bouton "par défaut"
  toggle.on('change:active', (evt) => {
    if (evt.active && this.viewer.layerManager === null) {
      this.viewer.layerManager = new LayerSwitcher({
        ...{
          reordering: false,
          collapsed: false,
        },
        ...options,
      })

      this.viewer.layerManager.on('drawlist', (e) => {
        const ui = e.layer.get(this.viewer.commonLayer.propertiesName.UI_LAYER)
        if (ui?.rightIcon) {
          const rightIcon = OlExtElement.create('I', {
            className: ui.rightIcon,
          })
          e.li.querySelector('.ol-layerswitcher-buttons').appendChild(rightIcon)
        }
      })

      this.viewer.layerManager.on('layer:visible', ({ layer }) => {
        if (layer?.getVisible()) {
          const group = this.viewer.commonLayer.getLayerGroup(layer)
          if (group && !group.getVisible()) {
            group.setVisible(true)
          }
        }
      })

      this.viewer.layerManager.tip = {
        up: 'Réordonner',
        down: 'Descendre',
        info: 'Informations...',
        extent: 'Zoom vers l\'étendue de la couche',
        trash: 'Retirer le calque',
        plus: 'Déplier / Replier',
      }

      this.viewer.Map.addControl(this.viewer.layerManager)
    } else if (evt.active) {
      // show et hide ne marchent pas
      this.viewer.layerManager.element.classList.add('ol-forceopen')
      this.viewer.layerManager.element.classList.remove('ol-collapsed')
      this.viewer.layerManager.overflow()
    } else {
      this.viewer.layerManager.element.classList.remove('ol-forceopen')
      this.viewer.layerManager.element.classList.add('ol-collapsed')
      this.viewer.layerManager.overflow()
    }
  })

  toggle.set('modal', true)
  this.addControl(toggle)
}

ControlBar.prototype.addUnlockButton = function (options = { }) {
  const storageKey = options.storageKey || 'KARTEIS_UNLOCK_TOKEN'

  const btn = new Button({
    html: getButtonHtml(options?.button?.html, options?.button?.iconId, options?.button?.icon, '<i class="kmapv-icon kmapv-icon-unlock"></i>'),
    name: 'kmapv-unlock-tool',
    title: options?.button?.title || 'Se connecter',
    handleClick: () => {
      window.location.href = options.url + `?uriredirect=${encodeURIComponent(window.location.href)}&storageKey=${storageKey}`
    },
  })
  this.addControl(btn)
}

ControlBar.prototype.addMeasureTool = function (options = { }) {
  // bouton pour afficher les longueurs des segments, change l'état des deux interaction
  const showSegmentToggle = new Toggle({
    html: getButtonHtml(options?.buttons?.toggleSegment?.html, options?.buttons?.toggleSegment?.iconId, options?.buttons?.toggleSegment?.icon, '<i class="kmapv-icon kmapv-icon-simplify"></i>'),
    title: options?.buttons?.toggleSegment?.title || 'Afficher/Masquer la longueur des segments',
    onToggle: (active) => {
      this.viewer.dataLayer._measureAreaInteraction.setSegmentVisible(active)
      this.viewer.dataLayer._measureLineInteraction.setSegmentVisible(active)
      onToggle()
    },
  })

  const maintButton = new Toggle({
    html: getButtonHtml(options?.buttons?.measure?.html, options?.buttons?.measure?.iconId, options?.buttons?.measure?.icon, '<i class="kmapv-icon kmapv-icon-measure"></i>'),
    title: options?.buttons?.measure?.title || 'Outil de mesure',
    onToggle: (active) => {
      if (active) {
        // a la re-ouverture, remet le bon état sur le toggle d'affichage de longeur des sgments
        showSegmentToggle.setActive(this.viewer.dataLayer._measureAreaInteraction.getSegmentVisible())
      }
    },
  })

  // quand on clic dessus on ferme les autres sous-menu
  maintButton.set('modal', true)

  const onToggle = function (active) {
    maintButton.toggle()
  }

  const subBar = new Bar({
    // toggleOne: true,
    autoDeactivate: true,
    controls: [
      new Button({
        html: getButtonHtml(options?.buttons?.measureLine?.html, options?.buttons?.measureLine?.iconId, options?.buttons?.measureLine?.icon, '<i class="kmapv-icon kmapv-icon-measure-line"></i>'),
        title: options?.buttons?.measureLine?.title || 'Mesurer une distance',
        // autoActivate: true,
        handleClick: () => {
          this.viewer.dataLayer.changeMapMode('measureLine')
          onToggle()
        },
      }),
      new Button({
        html: getButtonHtml(options?.buttons?.measureArea?.html, options?.buttons?.measureArea?.iconId, options?.buttons?.measureArea?.icon, '<i class="kmapv-icon kmapv-icon-measure-area"></i>'),
        title: options?.buttons?.measureArea?.title || 'Mesurer une surface',
        handleClick: () => {
          this.viewer.dataLayer.changeMapMode('measureArea')
          onToggle()
        },
      }),
      new Button({
        html: getButtonHtml(options?.buttons?.delete?.html, options?.buttons?.delete?.iconId, options?.buttons?.delete?.icon, '<i class="kmapv-icon kmapv-icon-delete"></i>'),
        title: options?.buttons?.delete?.title || 'Effacer les mesures',
        handleClick: () => {
          this.viewer.dataLayer._measureAreaInteraction.clear()
          this.viewer.dataLayer._measureLineInteraction.clear()
          onToggle()
        },
      }),
      new TextButton({
        className: 'separator',
        text: '',
      }),
      showSegmentToggle,
    ],
  })

  maintButton.setSubBar(subBar)

  this.addControl(maintButton)
}

/** Bookmark positions on ol maps.

 *  @param {} options Geobookmark's options
 *  @param {string} options.className default ol-bookmark
 *  @param {string | undefined} options.title Title to use for the button tooltip, default "Geobookmarks"
 *  @param {string} options.placeholder input placeholder, default Add a new geomark...
 *  @param {string} [options.deleteTitle='Suppr.'] title for delete buttons
 *  @param {bool} options.editable enable modification, default true
 *  @param {string} options.namespace a namespace to save the boolmark (if more than one on a page), default ol
 *  @param {Array<any>} options.marks a list of default bookmarks:
*/
ControlBar.prototype.addBookMark = function (options = { }) {
  const bookMark = new CustomGeoBookmark({ ...options, viewer: this.viewer })

  this.addControl(bookMark)
}

ControlBar.prototype.addScaleManagerTool = function (options = { }) {
  const zoomScaleResolutionTool = new ControlZoomScaleResolution({ ...options, viewer: this.viewer })

  this.addControl(zoomScaleResolutionTool)
}

ControlBar.prototype.addLayerFilterTool = function (options = {}) {
  if (this.viewer.layerFilter.layerFilterControl === null) {
    this.viewer.layerFilter.layerFilterControl = new ControlLayerFilters({ viewer: this.viewer })
    this.viewer.Map.addControl(this.viewer.layerFilter.layerFilterControl)
  }

  if (!options?.noButton) {
    const toggle = new Toggle({
      name: 'kmapv-layers-filters-bar',
      html: getButtonHtml(options?.button?.html, options?.button?.iconId, options?.button?.icon, '<i class="kmapv-icon kmapv-icon-filter"></i>'),
      title: options?.button?.title || 'Afficher la gestion des filtres de carte',
    })
    // calque l'état du toggle sur l'état visible du gestionnaire de filtres
    this.viewer.layerFilter.layerFilterControl.on('change:visible', (event) => {
      toggle.setActive(event.visible)
    })

    // highlight du bouton si on a une filtre actif
    this.viewer.layerFilterInteraction.on('change:visible', (event) => {
      toggle.element.classList.toggle('ol-highlight', event.visible)
    })

    toggle.on('change:active', (evt) => {
      if (evt.active) {
        this.viewer.layerFilter.layerFilterControl.show()
      } else {
        this.viewer.layerFilter.layerFilterControl.hide()
      }
    })

    toggle.set('modal', true)
    this.addControl(toggle)
  }
}

ControlBar.prototype.addLayerLegendTool = function (options = {}) {
  const toggle = new Toggle({
    name: 'kmapv-layers-legend-bar',
    html: getButtonHtml(options?.button?.html, options?.button?.iconId, options?.button?.icon, '<i class="kmapv-icon kmapv-icon-map-legend"></i>'),
    title: options?.button?.title || 'Afficher la légende',
  })
  // calque l'état du toggle sur l'état visible du gestionnaire de filtres
  this.viewer.legendManager.on('change:visible', (event) => {
    toggle.setActive(event.visible)
  })
  toggle.setActive(this.viewer.legendManager.isVisible())
  toggle.on('change:active', (evt) => {
    if (evt.active) {
      this.viewer.legendManager.show()
    } else {
      this.viewer.legendManager.hide()
    }
  })

  toggle.set('modal', true)
  this.addControl(toggle)
}

export default ControlBar