Source: ui/PopupTour.js

import { PopupPagination } from "../map/popup/PopupPagination.js";
import { Window } from "./Window.js";
import "./PopupTour.css";

export { PopupTour };

/**
 * Popup tour to explain the Ui
 * 
 * @author rhess <robin.hess@awi.de>
 * @memberof vef.ui
 */
class PopupTour extends Window {

    constructor(parent, items, useWidget) {
        super(parent, {
            mode: "slim",
            draggable: false,
            open: false
        });

        this.setClass("popup-tour");
        this.items_ = [];
        this.visibleItems_ = [];
        this.currentItem_ = null;

        this.setItems(items);

        // init pagination
        this.pagination_ = new PopupPagination(this.getElement(), {
            pageCount: 0,
            page: 1
        });
        this.pagination_.on("change", () => this.open_());

        // globally add the widget
        this.widget_ = (useWidget) ? this.initWidget_() : null;

        // init with position of first item
        this.calculatePosition_();
    }

    initWidget_() {
        const widget = document.createElement("div");
        widget.classList.add("vef-ui", "vef-widget-button", "popup-tour-widget");
        widget.innerHTML = "<div class='widget-button-icon fas fa-question'></div>";
        widget.style.display = "none";

        widget.addEventListener("click", () => this.open_())

        document.body.appendChild(widget);
        return widget;
    }

    /**
     * Calculates the position and direction of the window.
     * 
     * @private
     * @override
     */
    calculatePosition_() {
        if (!this.currentItem_) return;

        let target = this.currentItem_.target;
        let parent = this.getElement().parentElement;

        if (parent && (typeof target == "string")) target = parent.querySelector(target);
        if (!(target instanceof HTMLElement)) {
            console.error("PopupTour. Target element not found.", this.currentItem_.target);
            return;
        }

        const rect = target.getBoundingClientRect();
        const width = 310;

        let left = rect.left + rect.width + 20;
        let top = rect.top;
        let direction = "left";

        if (window.innerWidth <= (left + width)) {
            left = rect.left - width - 20;
            direction = "right";
        }

        if (left < 0) left = 0;
        if (top < 0) top = 0;

        const element = this.getElement();
        element.classList.remove("pointer-bottom", "pointer-top", "pointer-left", "pointer-right");
        element.classList.add("pointer-" + direction);

        this.setOptions({
            top: top + "px",
            left: left + "px",
            width: width + "px"
        });
    }

    /**
     *  Sets the items of the tour.
     */
    setItems(items = []) {
        if (!Array.isArray(items)) return;

        this.items_ = [];

        for (const item of items || []) {
            const target = item.target;
            const content = item.content;
            if (!(target instanceof HTMLElement) && (typeof target != "string")) throw new Error("Invalid Target Element");
            if (typeof content != "string") throw new Error("Invalid Content");

            this.items_.push({
                target: target,
                content: content
            });
        }
    }

    /**
     * Updates items and visible items, then opens the tour.
     * 
     * @private
     */
    open_() {
        const items = this.updateItems(this.items_);
        if (items != this.items_) this.setItems(items);

        this.updateVisibleItems_();

        if (this.visibleItems_.length == 0) return;

        if (this.widget_) this.widget_.style.display = "none";
        this.getElement().style.removeProperty("opacity");

        this.pagination_.setPageCount(this.visibleItems_.length);

        const index = this.pagination_.getPage() - 1;
        this.currentItem_ = this.visibleItems_[index > this.visibleItems_.length - 1 ? this.visibleItems_.length - 1 : index];

        // create content
        const content = document.createElement("div");
        content.innerHTML = this.currentItem_.content;

        super.setContent(content);
        super.open();
    }

    /**
     * Callback method to update items before opening the tour.
     * 
     * @return {Array} items - the updated items.
     */
    updateItems(items = []) {
        return items;
    }

    /**
     * Updates the existing and visible target items.
     * 
     * @private
     */
    updateVisibleItems_() {
        let parent = this.getElement().parentElement;
        if (!parent) this.visibleItems_ = [];
        this.visibleItems_ = this.items_.filter(item => {
            if (!(typeof item.target == 'string')) return false;
            const $element = parent.querySelector(item.target);
            if (!($element instanceof HTMLElement)) return false;
            const rect = $element.getBoundingClientRect();
            return rect.width > 0 && rect.height > 0;
        });
    }

    /**
     * show next item of the tour
     */
    next() {
        this.pagination_.next();
    }

    /**
     * show previous item of the tour
     */
    previous() {
        this.pagination_.previous();
    }

    /**
     * open the popup tour
     * 
     * @param {number} index start index (starts at current index if omitted)
     */
    open(index) {
        const currentIndex = this.pagination_.getPage() - 1;
        if (typeof index != "number") index = currentIndex;

        if (currentIndex != index) {
            this.pagination_.setPage(index + 1);
        } else {
            this.open_();
        }
    }

    /**
     * Hide the window by removing it from the parent
     */
    close() {
        if (this.widget_) {
            this.widget_.style.removeProperty("display");

            const rect = this.widget_.getBoundingClientRect();
            this.getElement().style.opacity = 0;
            this.getElement().classList.remove("pointer-left", "pointer-right");
            this.setOptions({
                top: rect.top + "px",
                left: rect.left + "px",
                width: rect.width + "px",
                width: rect.height + "px",
            });
            setTimeout(() => super.close(), 333);
        } else {
            super.close();
        }
    }

    /**
     * dispose the Tour
     * @override
     */
    dispose() {
        if (this.widget_) this.widget_.remove();
        super.dispose();
    }
}