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.currentItem_ = null;

        // init items
        if (Array.isArray(items)) {
            items.forEach(item => this.add(item.target, item.content));
            if (this.items_.length > 0) this.currentItem_ = this.items_[0];
        }

        // init pagination
        this.pagination_ = new PopupPagination(this.getElement(), {
            pageCount: this.items_.length,
            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;
    }

    /**
     * calculate 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)) throw new Error("Invalid Target Element");

        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"
        });
    }

    /**
     * 
     * @param {HTMLElement} target 
     * @param {string} content content as string
     */
    add(target, 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
        });
    }

    /**
     * @private
     */
    open_() {
        if (this.widget_) this.widget_.style.display = "none";
        this.getElement().style.removeProperty("opacity");

        const index = this.pagination_.getPage() - 1;
        this.currentItem_ = this.items_[index];

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

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

    /**
     * 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();
    }
}