Home Reference Source

src/urlapi/controls/LinkGeneratorButton.js

import $ from 'jquery'
import Collection from 'ol/Collection'
import Draw from 'ol/interaction/Draw'

import { Control } from '../../controls/Control'
import { cssClasses } from '../../globals'
import { Debug } from '../../Debug'
import { filterText, restoreText } from '../../xssprotection'

import '../../../less/linkgeneratorbutton.less'

/**
 * @typedef {g4uControlOptions} LinkGeneratorButtonOptions
 * @property {string} [baseURL] the baseUrl of the link. Defaults to the current location.
 * @property {StyleLike} [style='#defaultStyle'] The style of the marker.
 */

/**
 * Shows a window which contains a link to the current location of the map. Optionally a marker can be positioned on the
 * map by the user and a text for this marker can be added.
 */
export class LinkGeneratorButton extends Control {
  /**
   * @param {LinkGeneratorButtonOptions} options
   */
  constructor (options = {}) {
    options.className = options.className || 'g4u-linkgenerator'
    options.element = $('<div>')[0]

    super(options)

    /**
     * @type {boolean}
     * @private
     */
    this.active_ = false

    /**
     * @type {StyleLike}
     * @private
     */
    this.style_ = options.style || '#defaultStyle'

    /**
     * @type {string}
     * @private
     */
    this.baseURL_ = options.baseURL

    this.setTitle(this.getTitle() || this.getLocaliser().localiseUsingDictionary('LinkGeneratorButton title'))
    this.setTipLabel(this.getTipLabel() || this.getLocaliser().localiseUsingDictionary('LinkGeneratorButton tipLabel'))

    this.createHTML()
  }

  createHTML () {
    const $element = this.get$Element()

    // rtl

    if (this.getLocaliser().isRtl()) {
      $element.prop('dir', 'rtl')
    }

    // title

    $element.append($('<h3>')
      .html(this.getTipLabel()))

    // set marker checkbox

    /**
     * @type {jQuery}
     * @private
     */
    this.$markerCheckbox_ = $('<input type="checkbox">')

    const $checkboxLabel = $('<label>')
      .addClass(this.className_ + '-setmarker-checkbox')
      .html(this.getLocaliser().localiseUsingDictionary('LinkGeneratorButton setMarkerText'))
      .prepend(this.$markerCheckbox_)

    $element.append($checkboxLabel)

    this.$markerCheckbox_.on('click', () => {
      if (this.$markerCheckbox_.is(':checked')) {
        this.placeMarker()
      } else {
        this.hideMarker()
      }
    })

    // marker description text area

    /**
     * @type {jQuery}
     * @private
     */
    this.$markerDescriptionTextArea_ = $('<textarea>')

    this.$markerDescriptionTextArea_.on('input', () => this.updateDescription())

    /**
     * @type {jQuery}
     * @private
     */
    this.$markerDescription_ = $('<label>')
      .addClass(this.className_ + '-setmarker-textbox')
      .addClass(cssClasses.hidden)
      .html(this.getLocaliser().localiseUsingDictionary('LinkGeneratorButton setMarkerTextBoxText'))
      .append(this.$markerDescriptionTextArea_)

    $element.append(this.$markerDescription_)

    // link display

    /**
     * @type {jQuery}
     * @private
     */
    this.$linkDisplay_ = $('<input type="text">')
      .prop('readonly', true)

    $element.append(
      $('<label>')
        .addClass(this.className_ + '-input')
        .html(this.getLocaliser().localiseUsingDictionary('LinkGeneratorButton hint'))
        .append(this.$linkDisplay_)
    )

    this.$linkDisplay_.on('mousedown click', e => {
      if (e.which === 1) {
        e.preventDefault()
      }
      this.$linkDisplay_.focus().select()
    })
  }

  placeMarker () {
    this.enableMap()
    this.drawMarkerPoint()
      .then(point => {
        this.updateMarker(point)
        this.disableMap()
        this.$markerDescription_.removeClass(cssClasses.hidden)
        this.$linkDisplay_.focus().select()
        this.changed()
      })
  }

  hideMarker () {
    this.marker_.setActive(false)
    this.updateURL()
    this.$linkDisplay_.focus().select()
    this.$markerDescription_.addClass(cssClasses.hidden)
    this.changed()
  }

  updateDescription () {
    const text = filterText(this.$markerDescriptionTextArea_.val())
    this.marker_.setText(text)
    if (text) {
      this.marker_.setPopupVisible(true)
    } else {
      this.marker_.setPopupVisible(false)
    }
    this.updateURL()
  }

  enableMap () {
    this.shield_.setActive(false)
    const $overlayContainers = $(this.getMap().getViewport())
      .children('.ol-overlaycontainer-stopevent, .ol-overlaycontainer')
    $overlayContainers.addClass(cssClasses.hidden)
  }

  disableMap () {
    this.shield_.setActive(true)
    const $overlayContainers = $(this.getMap().getViewport())
      .children('.ol-overlaycontainer-stopevent, .ol-overlaycontainer')
    $overlayContainers.removeClass(cssClasses.hidden)
  }

  updateMarker (point) {
    this.marker_.setPosition(point)

    const text = this.getLocaliser().localiseUsingDictionary('LinkGeneratorButton markerText')
    this.marker_.setText(text)
    this.$markerDescriptionTextArea_.val(restoreText(text))

    if (text) {
      this.marker_.setPopupVisible(true)
    } else {
      this.marker_.setPopupVisible(false)
    }

    this.marker_.setActive(true)
    this.updateURL()
  }

  /**
   * @param {?G4UMap} map
   */
  setMap (map) {
    if (map) {
      /**
       * @type {Marker}
       * @private
       */
      this.marker_ = map.get('marker')

      /**
       * @type {Shield}
       * @private
       */
      this.shield_ = map.get('shield')

      const collection = new Collection()

      /**
       * @type {Draw}
       * @private
       */
      this.drawPoint_ = new Draw({
        features: collection,
        type: 'Point',
        style: map.get('styling').getStyle(this.style_)
      })
      this.drawPoint_.setActive(false)
      map.addSupersedingInteraction('singleclick dblclick pointermove', this.drawPoint_)
    }

    super.setMap(map)
  }

  /**
   * Let the user draw a point for the marker.
   * @returns {Promise.<Feature>}
   */
  drawMarkerPoint () {
    return new Promise(resolve => {
      this.drawPoint_.setActive(true)
      $(this.getMap().getViewport()).addClass(cssClasses.crosshair)

      this.drawPoint_.once('drawend', (e) => {
        this.drawPoint_.setActive(false)
        $(this.getMap().getViewport()).removeClass(cssClasses.crosshair)

        resolve(e.feature.getGeometry().getCoordinates())
      })
    })
      .catch(Debug.defaultErrorHandler)
  }

  updateURL () {
    const urlApi = this.getMap().get('urlApi')
    this.$linkDisplay_.val(urlApi.makeURL({ baseURL: this.baseURL_ }))
  }

  /**
   * @param {boolean} active
   */
  setActive (active) {
    const oldValue = this.active_
    if (oldValue !== active) {
      this.active_ = active

      if (active === true) {
        if (this.marker_.getActive()) {
          this.resetMarker_ = {
            position: this.marker_.getPosition(),
            text: this.marker_.getText(),
            popVis: this.marker_.getPopupVisible()
          }
        }

        this.shield_.setActive(true)
        this.shield_.add$OnTop(this.get$Element())

        this.updateURL()
        this.$linkDisplay_.focus().select()

        if (this.marker_.getActive()) {
          this.$markerCheckbox_.prop('checked', true)
          this.$markerDescription_.removeClass(cssClasses.hidden)
          if (this.marker_.getText()) {
            this.$markerDescriptionTextArea_.val(restoreText(this.marker_.getText()))
          }
          this.marker_.setPopupVisible(true)
        }
      } else {
        this.shield_.setActive(false)
        this.shield_.remove$OnTop(this.get$Element())

        this.$markerDescriptionTextArea_.val('')
        this.$markerCheckbox_.prop('checked', false)
        this.$markerDescription_.addClass(cssClasses.hidden)

        if (this.resetMarker_) {
          this.marker_.setActive(true)

          this.marker_.setPosition(this.resetMarker_.position)
          this.marker_.setText(this.resetMarker_.text)
          this.marker_.setPopupVisible(this.resetMarker_.popVis)

          delete this.resetMarker_
        } else {
          this.marker_.setActive(false)
        }
      }

      this.dispatchEvent({
        type: 'change:active',
        oldValue: oldValue
      })
    }
  }

  /**
   * @returns {boolean}
   */
  getActive () {
    return this.active_
  }
}