Source: components/viewport/viewport-mini.js

import OlMap from 'ol/Map.js'
import { defaults as defaultControls } from 'ol/control'
import Collection from 'ol/Collection'
import { image2world } from '@dataforsyningen/saul'
import { updateViewportPointer, generatePointerLayer } from '../../custom-plugins/plugin-pointer'
import { footprintHandler } from '../../custom-plugins/plugin-footprint.js'
import { configuration } from '../../modules/configuration.js'
import { state, reaction, when, autorun } from '../../state/index.js'
import {
  updateViewport,
  updateTextContent,
  updatePlugins,
  updateDate
} from './viewport-mixin.js'
import { queryItems } from '../../modules/api.js'

/**
 * HTML web component that displays an image using the OpenLayers library.
 */
export class SkraaFotoViewportMini extends HTMLElement {

  // properties
  coord_image
  coord_world
  map
  layer_image
  layer_icon
  view
  sync = false
  self_sync = true
  compass_element

  template = /*html*/`
    <div class="viewport-map"></div>
    <skraafoto-compass direction="north"></skraafoto-compass>
    <p id="image-date" class="image-date"></p>
  `

  constructor() {
    super()
  }


  // Methods

  createDOM() {
    // Create elements
    this.className = 'viewport-wrapper'
    this.innerHTML = this.template

    this.compass_element = this.querySelector('skraafoto-compass')
    if (configuration.ENABLE_SMALL_FONT) {
      this.querySelector('#image-date').style.fontSize = '0.75rem';
    }
  }

  /**
   * Creates a map object for the mini image map and sets up its controls and interactions.
   */
  createMap() {
    this.map = new OlMap({
      target: this.querySelector('.viewport-map'),
      controls: defaultControls({rotate: false, attribution: false, zoom: false}),
      interactions: new Collection()
    })
  }

  /**
   * Updates non-map elements, such as compass direction, date, and text content.
   */
  updateNonMap(item) {
    if (!item) {
      return
    }
    this.compass_element.setAttribute('direction', item.properties.direction)
    this.querySelector('.image-date').innerText = updateDate(item)
    this.querySelector('.viewport-map').title = updateTextContent(item)
    updatePlugins(this, item)
  }

  /** Toggles the visibility of the loading spinner on the mini image map. */
  toggleSpinner(bool) {
    const boundsElements = this.querySelectorAll('.out-of-bounds')
    if (bool) {
      // Attach a loading animation element while updating
      const spinner_element = document.createElement('ds-spinner')
      this.append(spinner_element)
      // hide out of bounds text while loading
      boundsElements.forEach(function(el) {
        el.hidden = true
      })
    } else {
      // Removes loading animation elements
      setTimeout(() => {
        this.querySelectorAll('ds-spinner').forEach(function(spinner) {
          spinner.remove()
        })
      }, 200)
      // display out of bounds text if done loading
      boundsElements.forEach(function(el) {
        el.hidden = false
      })
    }
  }

  // TODO: Is this method in use?
  // Public method
  toMapZoom(zoom) {
    return zoom + configuration.MINI_ZOOM_DIFFERENCE
  }

  // TODO: Is this method in use?
  // Public method
  toImageZoom(zoom) {
    return zoom - configuration.MINI_ZOOM_DIFFERENCE
  }


  // Lifecycle callbacks

  connectedCallback() {

    this.createDOM()
    this.createMap()

    // Listeners

    // When map has finished loading, remove spinner, etc.
    this.map.on('rendercomplete', () => {
      this.toggleSpinner(false)
    })

    // When state changes, update viewport
    this.reactionDisposer = reaction(
      () => {
        return {
          item: state.items[this.dataset.orientation], 
          view: {
            position: state.view.position,
            zoom: this.toImageZoom(state.view.zoom),
            kote: state.view.kote
          },
          marker: {
            position: state.marker.position,
            kote: state.marker.kote
          }
        }
      },
      (newData, oldData) => {
        if (!newData.item) {
          return
        }
        updateViewport(newData, oldData, this.map).then(() => {
          this.updateNonMap(newData.item)
        })
      }
    )

    // If collection changes, update viewport
    this.collectionUpdateDisposer = reaction(
      () => state.currentCollection,
      (newData, oldData) => {
        if (newData === oldData) {
          return
        }
        queryItems(state.view.position, this.dataset.orientation, newData).then((data) => {
          state.setItem(data.features[0], this.dataset.orientation)
        })
      }
    )

    if (configuration.ENABLE_POINTER) {
      this.map.addLayer(generatePointerLayer())
      this.pointerDisposer = autorun(() => {
        updateViewportPointer(this, state.pointerPosition, this.dataset.orientation)
      })
    }

    // Add event listener,when item is available in state
    this.whenDisposer = when(
      () => state.items[this.dataset.orientation],
      () => {
        this.map.on('pointermove', (event) => {

          // When user moves the pointer over this viewport, update the state to signal update for all other viewports
          if (configuration.ENABLE_POINTER) {
            const coord = image2world(state.items[this.dataset.orientation], event.coordinate[0], event.coordinate[1], state.view.kote)
            state.setPointerPosition = {point: coord, itemkey: this.dataset.orientation}
          }
          
          // When user changes viewport orientation, display image footprint on the map
          footprintHandler(event, state.items[this.dataset.orientation])
        })
      }
    )
  }

  disconnectedCallback() {
    this.pointerDisposer()
    this.reactionDisposer()
    this.whenDisposer()
    this.collectionUpdateDisposer()
  }

}