Source: components/userComponents/monitoredArea/Annotation/zones/MonitoredAreaViewAnotation.js

import React from 'react';
import * as ReactDOM from 'react-dom';
import '../../../css/MonitoredAreaAnnotation.css';
import { createZone } from '../../../../restmodules/ZoneRestModule';
import { API } from '../../../../../LocalConfiguration';

/**
 * View annotation
 * @extends Component
 * @hideconstructor
 */
export class MonitoredAreaViewAnotation extends React.Component {

  /**
   * @constructs
   * @param props
   */
  constructor(props) {
    super(props);
    const loadUrl = API + "/screenshot/" + this.props.monitoredAreaID;
    const refreshUrl = API + "/screenshot/" + this.props.monitoredAreaID + "?refresh=true";
    let zones = [];
    this.props.zonesData.map((zoneData) => {
      zones.push(zoneData.cameraVertices);
    });
    this.state = {
      loadUrl: loadUrl,
      refreshUrl: refreshUrl,
      activeUrl: loadUrl,
      loading: true,
      spinnerInterval: null,
      loaded: false,
      zones: zones,
      actualZone: {
        name: "",
        cameraVertices: []
      },
      image: new Image()
    };
    this.actualizeScreenshot = this.actualizeScreenshot.bind(this);
    this.onCanvasClick = this.onCanvasClick.bind(this);
    this.saveActualZone = this.saveActualZone.bind(this);
    this.clearActualZone = this.clearActualZone.bind(this);
  }

  /**
   *
   */
  actualizeScreenshot() {
    if (this.state.loading === true) return;
    let context = this.state.canvas.getContext("2d");
    context.clearRect(0, 0, this.state.canvas.width, this.state.canvas.height);

    this.setState({
      loading: true,
      loaded: false,
      actualZone: {
        name: "",
        cameraVertices: []
      },
      activeUrl: this.state.refreshUrl + "&date=" + new Date()
    });
    this.showSpinner();
  }

  /**
   *
   */
  componentDidMount() {
    let canvas = ReactDOM.findDOMNode(this.refs.drawCanvas);
    let image = ReactDOM.findDOMNode(this.refs.image);
    let context = canvas.getContext("2d");
    context.lineWidth = 3;
    this.setState({
      image: image,
      canvas: canvas
    });

    if (this.state.loading === false) context.drawImage(image, 0, 0, canvas.width, canvas.height);
  }

  /**
   *
   * @param prevProps
   * @param prevState
   * @param snapshot
   */
  componentDidUpdate(prevProps, prevState, snapshot) {
    if (prevProps.zonesData !== this.props.zonesData) {
      let zones = [];
      this.props.zonesData.map((zoneData) => {
        zones.push(zoneData.cameraVertices);
      });
      this.setState({
        zones: zones
      });
    }
    let canvas = this.state.canvas;
    let image = this.state.image;
    let context = canvas.getContext("2d");
    if (this.state.loading === true || this.state.loaded === false) {
      return;
    }

    context.drawImage(image, 0, 0, canvas.width, canvas.height);
    context.strokeStyle = 'rgb(255, 134, 20)';
    context.fillStyle = 'rgba(255, 152, 56, 0.5)';
    if (this.state.zones.length > 0) {
      this.state.zones.map(zone => {
        let start = this.displayToCanvas(this.imageToDisplay(zone[0]));
        context.beginPath();
        context.moveTo(start.x, start.y);

        for (let i = 1; i < zone.length; ++i) {
          let coord = this.displayToCanvas(this.imageToDisplay(zone[i]));
          context.lineTo(coord.x, coord.y);
        }
        context.closePath();
        context.fill();
        context.stroke();
      });
    }

    let actualZoneVertices = this.state.actualZone.cameraVertices;
    context.strokeStyle = 'rgb(0, 255, 0)';
    context.fillStyle = 'rgba(0, 255, 0, 0.5)';
    if (actualZoneVertices.length === 1) {
      let start = this.displayToCanvas(this.imageToDisplay(actualZoneVertices[0]));
      context.beginPath();
      context.arc(start.x, start.y, 2, 0, 2 * Math.PI);
      context.fill();
    } else if (actualZoneVertices.length > 1) {
      let start = this.displayToCanvas(this.imageToDisplay(actualZoneVertices[0]));
      context.beginPath();
      context.moveTo(start.x, start.y);

      for (let i = 1; i < actualZoneVertices.length; ++i) {
        let coords = this.displayToCanvas(this.imageToDisplay(actualZoneVertices[i]));
        context.lineTo(coords.x, coords.y);
      }
      context.closePath();
      context.fill();
      context.stroke();
    }
  }

  /**
   *
   * @param event
   */
  onCanvasClick(event) {
    let actualZoneVertices = this.state.actualZone.cameraVertices.slice();
    actualZoneVertices.push(
      this.displayToImage({ x: event.nativeEvent.offsetX, y: event.nativeEvent.offsetY })
    );
    this.setState({
      actualZone: {
        cameraVertices: actualZoneVertices
      }
    });
  }

  handleNameInputChange(event) {
    let data = this.state.actualZone;
    data.name = event.target.value;

    this.setState({
      actualZone: data,
    });
  }

  /**
   *
   */
  onImageLoaded() {
    window.clearInterval(this.state.spinnerInterval);

    let canvas = ReactDOM.findDOMNode(this.refs.drawCanvas);
    let image = ReactDOM.findDOMNode(this.refs.image);
    this.setState({
      loading: false,
      loaded: true,
      image: image,
      canvas: canvas
    });
  }

  /**
   *
   */
  onImageError() {
    this.setState({
      loading: false,
      loaded: false
    })
  }

  /**
   *
   */
  clearActualZone() {
    this.setState({
      actualZone: {
        name: "",
        cameraVertices: []
      }
    });
  }

  /**
   *
   */
  saveActualZone() {
    console.log(this.state.actualZone);
    createZone(this.props.monitoredAreaID, this.state.actualZone)
    .then(() => {
      this.setState({
        actualZone: {
          name: "",
          cameraVertices: []
        }
      });
      this.props.onZoneAdded();
    })
    .catch(error => console.log(error));
  }

  /**
   *
   * @returns {*}
   */
  render() {
    return (
      <div className="fit-content img-bordered">
        {this.renderAddZoneModal()}
          <div className="pull-right spacing"
               style={{
                 position: 'relative',
                 display: 'inline-block'
               }}>
            <button
              type="submit"
              className="btn btn-primary"
              disabled={this.state.actualZone.cameraVertices.length < 3}
              data-toggle="modal"
              data-target="#AddModal"
            >
              <span className="fa fa-plus"/>
            </button>
            <button
              type="submit"
              className="btn btn-primary"
              onClick={this.actualizeScreenshot}
              disabled={this.state.loading === true}
            >
              <span className="fa fa-repeat"/>
            </button>
            <button
              type="submit"
              className="btn btn-primary"
              onClick={this.clearActualZone}
            >
              <span className="fa fa-remove"/>
            </button>
          </div>
          <canvas
            ref="drawCanvas"
            id="drawCanvas"
            onClick={event => this.onCanvasClick(event)}
            height={600}
            width={700}
            style={{
              position: 'relative',
            }}
          />
          <img
            id="image"
            ref="image"
            src={this.state.activeUrl}
            alt="monitored area view"
            onLoad={() => this.onImageLoaded()}
            onError={() => this.onImageError()}
            className="hide"
            style={{
              position: 'relative',
            }}
          />

      </div>
    )
  }

  renderAddZoneModal() {
    console.log('renderAddZoneModal()');
    let content =
      <form role="form">
        <div className="form-group">
          <label>Zone Name</label>
          <input
            type="text"
            className="form-control"
            placeholder="Name"
            name="name"
            value={this.state.actualZone['name']}
            onChange={(e) => this.handleNameInputChange(e)}
          />
        </div>
      </form>;

    return (
      <div className="modal fade"
           id="AddModal"
           tabIndex="-1"
           role="dialog"
           aria-labelledby="myModalLabel">
        <div className="modal-dialog" role="document">
          <div className="modal-content">
            <div className="modal-header">
              <button type="button" className="close" data-dismiss="modal" aria-label="Close">
                <span aria-hidden="true">&times;</span></button>
              <h4 className="modal-title" id="myModalLabel">Enter name of zone</h4>
            </div>
            <div className="modal-body">

              {content}

            </div>
            <div className="modal-footer">
              <button type="button"
                      className="btn btn-default"
                      data-dismiss="modal">
                Cancel
              </button>
              <button type="button"
                      className="btn btn-primary"
                      data-dismiss="modal"
                      onClick={() => this.saveActualZone()}
                      disabled={this.state.actualZone['name'] === undefined || this.state.actualZone['name'].length === 0}
              >Update
              </button>
            </div>
          </div>
        </div>
      </div>
    )
  }

  /**
   * converts from displayed canvas coordinates to image coordinates
   * @param coords
   * @returns {{x: number, y: number}}
   */
  displayToImage(coords) {
    const widthRatio = this.state.image.width / this.state.canvas.clientWidth;
    const heightRatio = this.state.image.height / this.state.canvas.clientHeight;

    return { x: Math.round(coords.x * widthRatio), y: Math.round(coords.y * heightRatio) };
  }

  /**
   * converts from image coordinates to displayed canvas coordinates
   * @param coords
   * @returns {{x: number, y: number}}
   */
  imageToDisplay(coords) {
    const widthRatio = this.state.image.width / this.state.canvas.clientWidth;
    const heightRatio = this.state.image.height / this.state.canvas.clientHeight;

    return { x: Math.round(coords.x / widthRatio), y: Math.round(coords.y / heightRatio) };
  }

  /**
   * converts from displayed canvas coordinates to logical canvas coordinates
   * @param coords
   * @returns {{x: number, y: number}}
   */
   displayToCanvas(coords) {
    const widthRatio = this.state.canvas.width / this.state.canvas.clientWidth;
    const heightRatio = this.state.canvas.height / this.state.canvas.clientHeight;

    return { x: coords.x * widthRatio, y: coords.y * heightRatio };
  }

  /**
   * code source: https://codepen.io/reneras/pen/HFrmC
  * edited size of spinner
  */
  showSpinner() {
    const canvas = ReactDOM.findDOMNode(this.refs.drawCanvas);
    const context = canvas.getContext('2d');
    const start = new Date();
    const lines = 16,
      cW = context.canvas.width,
      cH = context.canvas.height;

    let draw = function () {
      let rotation = parseInt(((new Date() - start) / 1000) * lines) / lines;
      context.save();
      context.clearRect(0, 0, cW, cH);
      context.translate(cW / 2, cH / 2);
      // context.translate(cW / 2, cH / 2);
      context.rotate(Math.PI * 2 * rotation);
      for (let i = 0; i < lines; i++) {
        context.beginPath();
        context.rotate(Math.PI * 2 / lines);
        // context.moveTo(cW / 10, 0);
        // context.lineTo(cW / 4, 0);
        // context.lineWidth = cW / 30;
        context.moveTo(cW / 30, 0);
        context.lineTo(cW / 12, 0);
        context.lineWidth = cW / 90;
        context.strokeStyle = "rgba(0, 0, 0," + i / lines + ")";
        context.stroke();
      }
      context.restore();
    };
    this.state.spinnerInterval = window.setInterval(draw, 1000 / 30);
  }

}