Source: media/lightbox/Lightbox.js

import { PopupPagination } from "../../map/popup/PopupPagination.js";
import { MediaCaption } from "./utils.js";
import { CloseButton, ShareButton, MetadataButton, ZoomText } from "./uiElements.js"
import { EventObject } from "../../events/EventObject.js";
import { DeviceProperties, ElementProperties } from "../utils/helperTools.js";
import { PhotoswipeLightbox } from "./PhotoswipeLightbox.js";
import { PhotoswipeLightboxEvents } from "./PhotoswipeLightboxEvents.js";
export { Lightbox }

/**
 * Lightbox class to build a media lightbox using the core functionalities of the photoswipe lightbox. However, the pswp UI is replaced by own UI design, thus own DOM structure and elements (e.g., buttons, text).
 * @extends EventObject
 * @author ckraemme <ckraemme@awi.de>
 */
class Lightbox extends EventObject {
    /**
     * Creates the lightbox.
     * @param {Object} lightboxData -  LightboxData class object.
     * @param {Object} lightboxConfig - LightboxConfig class object.
     */
    constructor(lightboxData, lightboxConfig) {
        super({
            "lightbox_open": [], // [1]
            "lightbox_close": [],// [1]
            "lightbox_critical_state": [],
            //[1]: required for enabling/disabling scrollbars
        });

        this.lightboxData = lightboxData;
        this.lightboxConfig = lightboxConfig;
    }

    init() {
        if (this.lightboxConfig.slideOnStartIndex != null) {
            this._build();
        }

        const gallery = this.lightboxConfig.targetElement.querySelector('.pswp-gallery');
        if (gallery) {
            gallery.addEventListener("click", (e) => {
                this.lightboxConfig.slideOnStartIndex = this._getIndexOfItemInGallery(e.target);
                if (Number.isInteger(this.lightboxConfig.slideOnStartIndex)) {
                    e.preventDefault();
                    this._build();
                }
            })
        }
    }

    _opening() {
        this.lightboxWrapper.classList.remove('state-before-opening');
        this.lightboxWrapper.classList.add('state-opening');
        this._initLightboxHistoryEntry();
    }

    _open() {
        this.lightboxWrapper.classList.remove('state-opening');
    }

    _closing() {
        this.lightboxWrapper.classList.add('state-closing');
        this.shareButton.closeContent();
        this._disableClosingAnimationBasedOnItemVisibility();
        this._removeLightboxHistoryEntry();
    }

    _closed() {
        this.lightboxWrapper.remove();
        this.pswpLightbox = null;
        this.lightboxWrapper.classList.remove('state-closing');
        this.lightboxWrapper = null;
    }

    /**
     * FixMe: This feature collides with the VUE routing in the portal. Somehow a page reload is triggered when
     *        history.back is called.
     * 
     * On touch screens, allow the user to close the lightbox using the browser's back button (i.e. swipe back).
     * @private
     */
    _initLightboxHistoryEntry() {
        //if (DeviceProperties.isTouchDevice()) {
        //    if (history.state?.name != "Lightbox")
        //        history.pushState({ name: "Lightbox" }, null, document.URL);
        //
        //    this.historyStateMethod = this._removeLightboxHistoryEntry.bind(this);
        //    window.addEventListener('popstate', this.historyStateMethod);
        //}
    }

    _removeLightboxHistoryEntry() {
        //if (DeviceProperties.isTouchDevice()) {
        //    window.removeEventListener('popstate', this.historyStateMethod);
        //    if (history.state?.name == "Lightbox")
        //        history.back();
        //    this.pswpLightbox.lightbox.pswp?.close();
        //}
    }

    /**
     * Disable lightbox closing animation if the gallery item is not in the viewport.
     * @private
     */
    _disableClosingAnimationBasedOnItemVisibility() {
        const index = this.pswpLightbox.lightbox.pswp.currIndex;
        const placeholderElement = document.querySelectorAll('.pswp-gallery__item')[index];
        const deviation = 1.0;
        const isInViewport = ElementProperties.isInViewport(placeholderElement, deviation);
        if (!isInViewport)
            this.pswpLightbox.lightbox.pswp.options.showHideAnimationType = 'none';
    }

    _getIndexOfItemInGallery(itemNode) {
        const galleryItemNodes = document.querySelectorAll('.pswp-gallery__item');
        let itemIndex;
        for (let index = 0; index < galleryItemNodes.length; index++) {
            const item = galleryItemNodes[index];
            if (item.contains(itemNode)) {
                itemIndex = index;
                break
            }
        }
        return itemIndex
    }

    /**
     * Set the height of the lightbox to window.innerHeight to prevent overflowing
     * content below the address bar in mobile browsers.
     * @private
     */
    _setLightboxResizeListener() {
        this._resetLightboxResizeListener();
        this._resizeListener = () => {
            this.lightboxWrapper.style.height = window.innerHeight + "px";
        };
        window.addEventListener("resize", this._resizeListener);
        this._resizeListener();
    }

    _resetLightboxResizeListener() {
        if (this._resizeListener) {
            window.removeEventListener("resize", this._resizeListener);
        }
    }

    _build() {
        //Observe: 0 to slideOnStartIndex is required so all items before current item are filtered, thus slideOnStartIndex matches with filtered grid 
        this.lightboxData.loadItems(0, this.lightboxConfig.slideOnStartIndex || 1, false, {})
            //this.lightboxData.getItemsQuantity() //TODO: why is it not working?
            //this.lightboxData.getItem(this.lightboxConfig.slideOnStartIndex, [-3, 3]) //replaces: options.preloadFirstSlide=true  //OR put in 'itemData' and activate preloadFirstSlide?
            .then(() => {
                this._initDOM();
                this.pswpLightbox = new PhotoswipeLightbox(this.lightboxConfig.targetElement.querySelector('.pswp-lightbox'), this.lightboxData, this.lightboxConfig.slideOnStartIndex);
                this._handlePswpEvents();
                this._setLightboxResizeListener();
                this.fire("lightbox_open");
            }).catch((error) => {
                this.fire("lightbox_critical_state", error)
            })
    }

    _initDOM() {
        this.lightboxWrapper = document.createElement('div');
        this.lightboxWrapper.classList.add(...['vef-ui', 'custom-lightbox', 'state-before-opening']);
        this._initDOMHeader();
        this._initDOMContent();
        this._initDOMPagination();
        this.lightboxConfig.targetElement.append(this.lightboxWrapper);
    }

    _initDOMHeader() {
        let lightboxHeader = document.createElement('div');
        lightboxHeader.className = 'vef-ui lightbox-header';

        const headerLeft = document.createElement('div');
        headerLeft.classList.add('header-left');
        this.mobileMetadataButton = new MetadataButton(headerLeft);

        const headerRight = document.createElement('div');
        headerRight.classList.add('header-right');

        this.zoomText = new ZoomText(headerRight);
        this.shareButton = new ShareButton(headerRight);
        this.closeButton = new CloseButton(headerRight);

        if (this.lightboxConfig.showShareButton === false)
            this.shareButton.hideButton()

        lightboxHeader.append(headerLeft);
        lightboxHeader.append(headerRight);
        this.lightboxWrapper.prepend(lightboxHeader);
    }

    _initDOMContent() {
        const pswpLightbox = document.createElement('div');
        pswpLightbox.classList.add(...['vef-ui', 'lightbox-content', 'pswp-lightbox']);
        this.lightboxWrapper.append(pswpLightbox);
        this.shareButton.createBox(pswpLightbox);
        PhotoswipeLightboxEvents.preventOnShareBox(this.shareButton.contentBoxElement);
    }

    _initDOMPagination() {
        const lightboxFooter = document.createElement('div');
        lightboxFooter.classList.add(...['vef-ui', 'lightbox-footer']);
        this.pagination = new PopupPagination(lightboxFooter, { pageCount: this.lightboxData.dataItemAmount });
        this.lightboxWrapper.append(lightboxFooter);
    }

    _handlePswpEvents() {

        this.pswpLightbox.on('pswp_lightbox_opening', () => {
            this._opening();
        })

        this.pswpLightbox.on('pswp_lightbox_open', () => {
            this._open();
        })

        this.pswpLightbox.on('pswp_lightbox_ready', () => {
            const lightbox = this.pswpLightbox.lightbox;
            this.pagination.on('next', () => { lightbox.pswp.next() });
            this.pagination.on('previous', () => { lightbox.pswp.prev() });
            this.closeButton.onClick(() => { lightbox.pswp.close() })
        })

        this.pswpLightbox.on("pswp_lightbox_slide_change", (index) => {
            //console.log(`slide change [${index + 1}/${this.lightboxData.galleryItemAmount}]`);
            if (this.lightboxConfig.showShareButton === true) {
                const shareLink = this.lightboxData.getShareLink(index)
                this.shareButton.setLink(shareLink);

                const deactivateShareButton = shareLink == undefined;
                deactivateShareButton ? this.shareButton.deactivateButton() : this.shareButton.activateButton();
            }


            this.pagination.setPage(this.lightboxData.shownItemPosition(index));

            this.lightboxData.getSidebarTemplate().then((metadataTemplate) => {
                const isItemLoaded = this.lightboxData['galleryItems'][index] != null;
                if (isItemLoaded) {
                    const caption = MediaCaption.getCaption(this.lightboxData['galleryItems'][index]['sidebarData'], metadataTemplate);
                    const captionTarget = document.querySelector('.lightbox-content');
                    this.mobileMetadataButton.setContent(caption, captionTarget);
                }
            })

        })

        this.pswpLightbox.on("pswp_lightbox_zoom", ([zoomLevel, showZoomLevel]) => {
            this.zoomText.isContentVisible = showZoomLevel;
            this.zoomText.setZoom(zoomLevel);
        })

        this.pswpLightbox.on('pswp_lightbox_close', () => {
            this.fire("lightbox_close");
            this._resetLightboxResizeListener();
            this._closing();
        })

        this.pswpLightbox.on('pswp_lightbox_destroy', () => {
            this._closed();
        })

        this.pswpLightbox.on('pswp_critical_state', (message) => {
            this.fire("lightbox_critical_state", message);
        })
    }
}