Source: media/gallery/Gallery.js

import { isEqual } from "lodash";
// external dependencies
import _$ from "justifiedGallery";
import "justifiedGallery/dist/css/justifiedGallery.css"

// factory for jQuery Plugin
const $ = _$();

import { MediaGallery } from "../grid/mediaGallery.js";
import { LightboxData } from "../data/LightboxData.js";
import { LightboxConfig } from "../lightbox/LightboxConfig.js";
import { GalleryConfigLoader } from "../data/GalleryConfigLoader.js";
import { GalleryOverview } from "../overview/mediaGalleryOverview.js";
import { resolveTemplate } from "../../utils/template/resolveTemplate.js";
import { MediaGalleryService } from "../grid/utils.js";
import { GalleryTimeLineFilter } from "../filter/plot/GalleryTimeLineFilter.js";
import { GalleryMappingScheme } from "../data/utils.js";
import { Lightbox } from "../lightbox/Lightbox.js";

export { Gallery }

/**
 * Class representing a media gallery.
 * @author ckraemme <ckraemme@awi.de>
 */
class Gallery {
    /**
     * Initialize the gallery.
     * @param {Element} containerNode - Target HTML container node.
     */
    constructor(containerNode) {
        this.container_ = containerNode;
        this.gallery_ = null;
        this.galleryOverview_ = null;
        this.allowEmptyGallery = false;

        document.documentElement.classList.add("prevent-scrollbar-reflow");
    }

    /**
     * Update or initialize the gallery.
     */
    update() {
        this._resetUi();
        this._buildGallery();

        // FixMe: HideOnSite does not work anymore. Workaround using global variable
        window.vefGalleryVisible = true;
    }

    /**
     * Remove the gallery.
     */
    dispose() {
        this._resetUi();
        document.documentElement.classList.remove("prevent-scrollbar-reflow");

        // FixMe: HideOnSite does not work anymore. Workaround using global variable
        window.vefGalleryVisible = false;
    }

    /**
     * Tasks that are executed when lightbox is on open state.
     * @private
     */
    _onOpenLightbox() {
        //Note: Prevent the document scroll bar from being displayed when the lightbox is open.
        document.documentElement.classList.add("disable-scroll");
    }

    /**
     * Tasks that are executed when lightbox is on close state.
     * @private
     */
    _onCloseLightbox() {
        document.documentElement.classList.remove("disable-scroll");
    }

    /**
     * Display error message in gallery UI and show stacktrace in console.
     * @param {Error} error - Error object. 
     * @private
     */
    _showError(error) {
        const _truncateText = (str, limit) => {
            const regex = new RegExp(`((?:.|\n){${limit}})(?:.|\n)(?:.|\n)+`, "g");
            return str.replace(regex, "$1...")
        }
        console.error(error);
        const message = error.message || error || '';
        this._resetUi();
        const template = resolveTemplate($("#gallery-error-template").html(), {
            message: _truncateText(message, 100)
        });
        this.container_.innerHTML = template;
    }

    /**
     * Tasks that are executed when the user interface is being reset.
     * @private
     */
    _resetUi() {
        if (this.gallery_) this.gallery_.dispose();
        if (this.galleryOverview_) this.galleryOverview_.dispose();
        this._onCloseLightbox();
        this.container_.innerHTML = "";
    }

    /**
     * Build the gallery.
     * @private
     */
    _buildGallery() {
        this.configuration = new GalleryConfigLoader();
        this.configuration.loadConfigData().then(() => {
            console.log(this.configuration);
            const shareID = this.configuration.queryConfigDict['shareID'];
            this._buildGalleryHeader();
            this._buildGalleryMain(shareID);
        }).catch((error) => {
            this._showError(error)
        });
    }

    /**
     * Build the gallery header area.
     * @private
     */
    _buildGalleryHeader() {
        this.container_.innerHTML = `<div class="gallery-header"></div>`;
        const headerNode = this.container_.querySelector(".gallery-header");

        this.galleryOverview_ = new GalleryOverview(headerNode, this.configuration);
        this.galleryOverview_.on('gallery_overview_critical_state', message => this._showError(message));
        this._buildTimeLineFilter(headerNode);
    }

    /**
     * Build the gallery main area.
     * @private
     * @param {*} shareID - Image identifier for sharing.
     * @param {{}} [filterConfig={}] - Filter configuration, i.e., to set a constant remote data filter.
     */
    _buildGalleryMain(shareID, filterConfig = {}) {

        const _applyAbstractFilter = (data, filterConfig) => {
            const abstractFilter = filterConfig.remoteFilter?.abstractFilter;
            if (abstractFilter) {
                const pointer = data.galleryRemoteFilter.abstractFilterObject;
                pointer.addFilter(pointer.cqlFilter, abstractFilter)
            }
        }

        this._initGalleryData().then((galleryData) => {
            _applyAbstractFilter(galleryData, filterConfig);
            this.gallery_ = new MediaGallery(this.container_, galleryData, this.allowEmptyGallery);
            this.gallery_.on('gallery_critical_state', message => this._showError(message));

            _applyAbstractFilter(galleryData, filterConfig);
            MediaGalleryService.getSharedSlideNumber(this.configuration, shareID).then((slideOnStartIndex) => {

                const lightboxConfig = new LightboxConfig({
                    'targetElement': this.container_,
                    'slideOnStartIndex': slideOnStartIndex,
                    'showShareButton': this.configuration.galleryConfigDict['lightbox']?.['shareButton']?.['enable']
                })
                const lightbox = new Lightbox(galleryData, lightboxConfig);
                lightbox.on("lightbox_open", () => this._onOpenLightbox());
                lightbox.on("lightbox_close", () => this._onCloseLightbox());
                lightbox.on("lightbox_critical_state", (message) => this._showError(message));
                lightbox.init();
            })
        })
    }

    /**
     * Update the gallery main area.
     * @param {Object} eventData - Plotly's event data object that contains a data selection, i.e., from timeline filter. 
     * @private
     */
    _updateGalleryMain(eventData) {

        const isFiredWithoutInfo = eventData === undefined;
        const isNewGalleryData = !isEqual(eventData, this.plotlyEventDataOld);
        if (isFiredWithoutInfo || !isNewGalleryData)
            return

        let rangeFilterItem = {};
        this.plotlyEventDataOld = eventData;

        const rangeFilterValues = [];

        eventData?.selections.forEach(function (selection) {
            const missingUTCPattern = /^\d{0,4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}(?:\.\d+)$/;
            const utcChar = missingUTCPattern.test(selection.x0) ? 'Z' : '';
            var [time0, time1] = [new Date(selection.x0 + utcChar), new Date(selection.x1 + utcChar)];

            const isInvertedOrder = (time1 - time0) < 0;
            if (isInvertedOrder)
                rangeFilterValues.push(["bt", time1.toISOString(), time0.toISOString()])
            else
                rangeFilterValues.push(["bt", time0.toISOString(), time1.toISOString()])
        })

        let filterConfig;
        if (rangeFilterValues.length != 0) {
            rangeFilterItem = {
                column: GalleryMappingScheme.findPropertyKeyInMapping(this.configuration.galleryConfigDict, ['dateTime'])[0],
                type: "time",
                values: rangeFilterValues
            }

            const abstractRangeFilter = [rangeFilterItem];
            filterConfig = {
                'remoteFilter': {
                    'abstractFilter': abstractRangeFilter
                }
            };
        }

        this.gallery_.removeListeners();
        if (this.gallery_) this.gallery_.dispose();

        this.allowEmptyGallery = true;
        this._buildGalleryMain(null, filterConfig);
    }

    /**
     * Build the timeline filter.
     * @param {Element} targetElement - Target HTML element.
     * @private
     */
    _buildTimeLineFilter(targetElement) {
        const activateTimeLineFilter = this.configuration.galleryConfigDict.filters?.timeLineFilter?.enable;
        if (activateTimeLineFilter) {
            const timeLineFilter = new GalleryTimeLineFilter(targetElement, this.configuration);
            timeLineFilter.createPlot(this._updateGalleryMain.bind(this));
            timeLineFilter.on('plot_filter_critical_state', message => this._showError(message));
        }
    }

    /**
     * Init the data object for the media gallery grid and lightbox.
     * @private
     * @returns Gallery data object.
     */
    _initGalleryData() {
        const contentData = new LightboxData(this.configuration.galleryConfigDict);
        window.galleryData = contentData;
        return contentData.initDataService(this.configuration.serviceConfigDict).then(() => contentData)
    }
}