Source: public/javascript/modules/olqv_viewer.js

import {Selector} from "./olqv_selector.js";
import {DOMAccessor} from "./domelements.js";
import { FITS_HEADER } from "./fitsheader.js";
import {getUniqueId} from "./utils.js";

class Viewer {

    static enter(what) {
        console.group(this.name + "." + what);
    }

    static exit() {
        console.groupEnd();
    }

    /**
     * 
     * @param {*} relFITSFilePath 
     * @param {*} width 
     * @param {*} height 
     * @param {*} divId 
     * @param {*} canvasId 
     * @param {*} coordinatesFormatter 
     * @param {*} infosBlock 
     */
    constructor(relFITSFilePath, width, height, divId, canvasId, coordinatesFormatter, infosBlock) {
        Viewer.enter(this.constructor.name);
        this.relFITSFilePath = relFITSFilePath;
        this.width = width;
        this.height = height;
        this.extent = [0, 0, width-1, height-1];
        this.divId = divId;
        this.canvas = document.getElementById(canvasId);
        this.coordinatesFormatter = coordinatesFormatter;
        this._interactionListeners = [];

        //this.addInteractionListener = this.addInteractionListener.bind(this);

        /*
        ** This is the layer which will contain the image
        */ 
        this.layer = null;

        /*
        ** This the layers group for shapes ( Boxes, Markers, Contours...)
        */
        this._shapesLayerGroup = new ol.layer.Group({
            layers : []
        });
        this._shapesLayerGroup.set('title', 'shapes');

        /*
        ** Coordinates and grid
        */
        this.graticule = null;

        this.canvas.width = width;
        this.canvas.height = height;

        /*
        ** A pure pixel projection
        */
        this.projection = new ol.proj.Projection({
            code: 'local_image',
            units: 'pixels',
            extent: this.extent,
            worldExtent: [...this.extent]
        });

        /*
        ** So far the controls are :
        ** * the display of coordinates and possibly some other related infos as the mouse moves over the image
        ** * the mode fullscreen
        ** * 
        ** *  If coordinatesFormatter is null, coordinates are not displayed
        ** *
        ** * Fullscreen mode is displayed in element with id = fullscreen if it exists (2D)

        ** * 
        */       
       let fullscreen_source = "fullscreen";
       if(document.getElementById(fullscreen_source) === null){
        fullscreen_source = "";
       }

       if(this.coordinatesFormatter !== null){
            this.controls = [
                new ol.control.MousePosition({
                    undefinedHTML: '',
                    coordinateFormat: this.coordinatesFormatter.format.bind(this.coordinatesFormatter)
                }),
                new ol.control.FullScreen({ source: fullscreen_source })
            ];
       }else{
        this.controls = [
            new ol.control.FullScreen({ source: fullscreen_source })
        ];
       }
    
        this.map = new ol.Map({
            interactions: ol.interaction.defaults({mouseWheelZoom:false}),
            target: this.divId,
            view: new ol.View({
                projection: this.projection,
                center: ol.extent.getCenter(this.extent),
                //resolution: 1,
                constrainOnlyCenter: true,
                showFullExtent: true,
                zoom : 1,
                //resolution: this.canvas.width / 512,
            }),
            controls: [],
            layers : [this._shapesLayerGroup]
        });

        // prevent filtering on zoomed image
        this.map.on('precompose', function(evt) {
            evt.context.imageSmoothingEnabled = false;
            evt.context.webkitImageSmoothingEnabled = false;
            evt.context.mozImageSmoothingEnabled = false;
            evt.context.msImageSmoothingEnabled = false;
        });

        // ctrl+wheel to zoom
        const mouseWheelInt = new ol.interaction.MouseWheelZoom({
            useAnchor : false
        });
        this.map.addInteraction(mouseWheelInt);
        this.map.on('wheel', function(evt) {
            mouseWheelInt.setActive(ol.events.condition.platformModifierKeyOnly(evt));
         });

        this._infosBlock = infosBlock;

        this.selector = new Selector(this);

        let target = this.map.getTarget();
        this._jTarget = typeof target === "string" ? $("#" + target) : $(target);

        this.sliceRange = null;
        this.dataSteps = null;
        this.statistics = null;

        //document.getElementById(divId).addEventListener("focus", () => { this.enableControls(); this._hasMouse = true; } );
        document.getElementById(divId).addEventListener("mouseenter", () => {this.enableControls(); this._hasMouse = true;} );
        document.getElementById(divId).addEventListener("mouseleave", () => {this.disableControls(); this._hasMouse = false;} );

        this.map.on("pointerdrag", (event) => {
            if(event.pointerEvent.buttons !== 1 && event.pointerEvent.buttons !== 4){
                event.preventDefault();
            }
        });

        this._initialResolution = this.map.getView().getResolution()
        this._hasMouse = false;
        this._currentInteraction = undefined;
        this._currentCursor = undefined;
        Viewer.exit();
    }

    addClickEvent(callback){
        this.map.on("click", (event) => {
            if(event.pointerEvent.buttons > 1){
                event.preventDefault();
            }
            callback(event)
        });
    }

    isInExtent(x, y){
        if(x > this.extent[0] &&
            x < this.extent[2] &&
            y > this.extent[1] &&
            y < this.extent[3] ){

                return true;
            }
        return false;
    }

    disableControls() {
        Viewer.enter(this.disableControls.name);
        if (this.controls) {
            this.controls.forEach((control) => {
                this.map.removeControl(control);
            });
        }
        Viewer.exit();
    }

    enableControls() {
        Viewer.enter(this.enableControls.name);
        if (this.controls) {
            this.controls.forEach((control) => {
                this.map.addControl(control);
            }); 
        } 
        Viewer.exit();      
    }
    
    get infosBlock() { return this._infosBlock; }


    /*
    ** A getter for the LayerGroup dedicated to the shapes.
    */
    get shapesLayerGroup() { 
        return this._shapesLayerGroup;
    }

    /*
    ** add a Layer to the shapesLayerGroup
    */
    addShapesLayer(layer) {
        this._shapesLayerGroup.getLayers().getArray().push(layer);
    }

    getDivId() {
        return this.divId;
    }

    getRelFITSFilePath() {
        return this.relFITSFilePath;
    }

    getMap() {
        return this.map;
    }

    getStatistics() {
        return this.statistics;
    }

    getSliceRange() {
        return this.sliceRange;
    }

    reset() {
        this.map.getView().setCenter(ol.extent.getCenter(this.extent));
        this.map.getView().setResolution(this._initialResolution);
    }

    imageLoadFunction(image, src) {
        Viewer.enter(this.imageLoadFunction.name);
        DOMAccessor.getLoading().style.display = 'block';
        image.getImage().addEventListener("load", () => {
            Viewer.enter("Listener : image loaded, draw it!");
            DOMAccessor.getLoading().style.display = 'none';
            this.canvas.getContext('2d').drawImage(image.getImage(), 0, 0);
            Viewer.exit();
        });
        image.getImage().src = src;
        image.getImage().crossOrigin = "Anonymous";
        Viewer.exit();
    }

    display(imageURL, sliceRange, dataSteps, statistics) {
        Viewer.enter(this.display.name);
        this.sliceRange = sliceRange;
        if(this.coordinatesFormatter !== null)
            this.coordinatesFormatter.setDataSteps(dataSteps);
        this.statistics = statistics;
        if (this.layer) {
            this.map.removeLayer(this.layer);
        }
        this.layer = new ol.layer.Image({
            source: new ol.source.ImageStatic({
                url: imageURL,
                projection: this.projection,
                imageExtent: this.extent,
                imageLoadFunction: this.imageLoadFunction.bind(this)
            })
        });
        this.map.addLayer(this.layer);
        this.map.getView().fit(this.extent, {size:this.map.getSize(), maxZoom:16})

        Viewer.exit();
    }

    getCoordinatesFormatter() {
        return this.coordinatesFormatter;
    }

    getSelector() {
        return this.selector;
    }

    is3D() {
        return FITS_HEADER.dimensionality() == 3;
    }

    is2D() {
        return FITS_HEADER.dimensionality() == 2;
    }

    numDims() {
        return FITS_HEADER.dimensionality();
    }

    couple(viewer) {
        var updateView = function (event, viewRef) {
            let newValue = event.target.get(event.key);
            viewRef.set(event.key, newValue);
        };
        
        this.map.getView().on('change:resolution', function (event) {
            updateView(event, viewer.getMap().getView());
        });

        this.map.getView().on('change:center', function (event) {
            updateView(event, viewer.getMap().getView());
        });
    }

    set cursor(kursor) {
        Viewer.enter("set cursor");
        this._cursor = kursor;
        this._jTarget.css('cursor', kursor);
        Viewer.exit();
    }


    setCurrentInteraction(interaction, cursor) {
        this.notifyInteractionListeners();
        this._currentInteraction = interaction;
        this._currentCursor = cursor;
    }

    removeCurrentInteraction() {
        if (this._currentInteraction) {
            this.map.removeInteraction(this._currentInteraction);
            this._currentInteraction = undefined;
            this.cursor = 'default';
        }
    }

    stopCurrentInteraction() {
        if (this._currentInteraction) {
            this.map.removeInteraction(this._currentInteraction);
        }
    }

    startCurrentInteraction() {
        if (this._currentInteraction && this._currentCursor) {
            this.map.addInteraction(this._currentInteraction);
            this.cursor = this._currentCursor; 
        }
    }

    restoreDefaultInteraction() {
        this.setCurrentInteraction(this.selector.interaction, this.selector.cursor);
        this.startCurrentInteraction();
    }

    hasCurrentInteraction(){
        if(this._currentInteraction !== undefined){
            return true;
        }
        return false;
    }

    addInteractionListener(listener){
        this._interactionListeners.push(listener);
    }

    removeInteractionListener(listener){
        this._interactionListeners.splice(this._interactionListeners.indexOf(listener), 1);
    }

    notifyInteractionListeners(){
        for(let listener of this._interactionListeners){
            listener.interactionChanged();
        }
    }

    get currentInteraction() {
        return this._currentInteraction;
    }

    get defaultInteraction() {
        return this.selector.interaction;
    }

    get defaultCursor() {
        return this.selector.cursor;
    }

    get maxIndexes() {
        return [this.width-1, this.height-1];
    }

    get hasMouse() { return this._hasMouse; }

    set hasMouse(b) { this._hasMouse = b; }
}

export{
    Viewer
}