Visualizer_DocumentVisualizerWindow.js

import { DocumentProvider } from '../Core/ViewModel/DocumentProvider';

import * as THREE from 'three';

/**
 * Represents the document visualizer, under the form of an oriented image. It
 * is a window without its default style, centered in the view with an opacity
 * control.
 */
export class DocumentVisualizerWindow {
  /**
   * Creates a new document image orienter.
   *
   * @param {*} itownsView The iTowns view.
   * @param {*} provider document provider
   */
  constructor(itownsView, provider) {
    // create dom
    this.domElement = document.createElement('div');

    this.imageDomEl = document.createElement('img');
    this.domElement.appendChild(this.imageDomEl);

    this.ctrlPanelDomEl = document.createElement('div');
    this.domElement.appendChild(this.ctrlPanelDomEl);

    this.closeButton = document.createElement('button');
    this.closeButton.innerText = 'Close';
    this.ctrlPanelDomEl.appendChild(this.closeButton);

    this.sliderDomEl = document.createElement('div');
    this.ctrlPanelDomEl.appendChild(this.sliderDomEl);

    this.sliderLabel = document.createElement('div');
    this.sliderDomEl.appendChild(this.sliderLabel);

    this.labelDiv = document.createElement('label');
    this.sliderLabel.appendChild(this.labelDiv);

    this.outputOpacity = document.createElement('output');
    this.sliderLabel.appendChild(this.outputOpacity);

    this.inputOpacity = document.createElement('input');
    this.inputOpacity.setAttribute('type', 'range');
    this.inputOpacity.min = 0;
    this.inputOpacity.max = 1;
    this.inputOpacity.step = 'any';
    this.sliderDomEl.appendChild(this.inputOpacity);

    const id = 'id_document_visualizer_input_opacity';
    this.inputOpacity.id = id;
    this.outputOpacity.setAttribute('for', id);

    // end dom creation

    /**
     * The iTowns view.
     *
     * @type {any}
     */
    this.itownsView = itownsView;

    /**
     * The visualization camera position.
     *
     * @type {THREE.Vector3}
     */
    this.position = null;

    /**
     * The visualization camera orientation.
     *
     * @type {THREE.Quaternion}
     */
    this.quaternion = null;

    // document provider
    this.provider = provider;

    // callbacks
    this.closeButton.onclick = () => {
      this.domElement.remove();
    };

    this.inputOpacity.oninput = () => {
      this._onOpacityChange();
    };

    // Dispose the window when the displayed document change
    this.provider.addEventListener(
      DocumentProvider.EVENT_DISPLAYED_DOC_CHANGED,
      () => this.domElement.remove()
    );
  }

  // ////////////////////
  // /// TRAVEL & OPACITY

  /**
   * Triggered when the opacity of the slider changes. This method apply the
   * change on the image and the output element.
   *
   * @private
   */
  _onOpacityChange() {
    const opacity = this.inputOpacity.value;
    this.outputOpacity.value = opacity;
    this.imageDomEl.style.opacity = opacity;
  }

  /**
   * Sets the orientation for the camera. `startTravel` should be called after
   * this method to apply the new position.
   *
   * @param {THREE.Vector3} position The visualization camera position.
   */
  setTargetPosition(position) {
    this.position = position;
  }

  /**
   * Sets the orientation for the camera. `startTravel` should be called after
   * this method to apply the new orientation.
   *
   * @param {THREE.Quaternion} quaternion The visualization camera orientation.
   */
  setTargetQuaternion(quaternion) {
    this.quaternion = quaternion;
  }

  /**
   * Sets the image source.
   *
   * @param {string} newSrc The image source.
   */
  setImageSrc(newSrc) {
    this.imageDomEl.src = newSrc;
  }

  /**
   * Retrieve the displayed document and start a travel to its visualization
   * location.
   *
   * @async
   */
  async startTravelToDisplayedDocument() {
    const currentDoc = this.provider.getDisplayedDocument();

    if (!currentDoc) {
      return;
    }

    const imageSrc = await this.provider.getDisplayedDocumentImage();

    if (
      isNaN(currentDoc.visualization.positionX) ||
      isNaN(currentDoc.visualization.quaternionX)
    ) {
      return;
    }

    const docViewPos = new THREE.Vector3();
    docViewPos.x = parseFloat(currentDoc.visualization.positionX);
    docViewPos.y = parseFloat(currentDoc.visualization.positionY);
    docViewPos.z = parseFloat(currentDoc.visualization.positionZ);
    this.setTargetPosition(docViewPos);

    const docViewQuat = new THREE.Quaternion();
    docViewQuat.x = parseFloat(currentDoc.visualization.quaternionX);
    docViewQuat.y = parseFloat(currentDoc.visualization.quaternionY);
    docViewQuat.z = parseFloat(currentDoc.visualization.quaternionZ);
    docViewQuat.w = parseFloat(currentDoc.visualization.quaternionW);
    this.setTargetQuaternion(docViewQuat);

    this.setImageSrc(imageSrc);

    await this.startTravel();
  }

  /**
   * Starts the document orientation. The processes first assign the correct src
   * to the image, then sets the opacity to 0. After the travel is finished,
   * the opacity is gradually restored.
   * To call this function, the `position`, `quaternion` and `imageSrc`
   * attributes must all have been set beforehand.
   *
   * @async
   */
  startTravel() {
    if (!this.itownsView.controls) return; // when PR https://github.com/iTowns/itowns/pull/2046 enabled this would be useless

    this.imageDomEl.style.opacity = 0;
    this.inputOpacity.value = 0;

    this.itownsView.controls.initiateTravel(
      this.position,
      2,
      this.quaternion,
      true
    );
    this.itownsView.notifyChange();

    return new Promise((resolve, reject) => {
      try {
        setTimeout(() => {
          const increaseOpacity = () => {
            const nextValue = Number(this.inputOpacity.value) + 0.01;
            this.inputOpacity.value = nextValue;
            this._onOpacityChange();
            if (nextValue >= 1) {
              clearInterval(intervalHandle);
            }
          };
          const intervalHandle = setInterval(increaseOpacity, 15);
          resolve();
        }, 2000);
      } catch (e) {
        reject(e);
      }
    });
  }
}