src/components/HintBox.js

goog.module('gep.components.HintBox');

const Completer = goog.require('clulib.async.Completer');
const {Component} = goog.require('clulib.cm');

const {listen,unlisten,EventType} = goog.require('goog.events');
const dataset = goog.require('goog.dom.dataset');
const classlist = goog.require('goog.dom.classlist');

/**
 * This component takes care of the display of hint boxes in the application
 * Component is used from {@Link https://www.npmjs.com/package/clulib}
 * @extends {Component}
 */
class HintBox extends Component
{
    constructor()
    {
        super();

        /**
         * Timeout id for the automatic disappearance
         * @type {number}
         * @private
         */
        this.visibleTimeout_ = 0;

        /**
         * Defines if the hint box is currently displayed
         * @type {boolean}
         * @private
         */
        this.isVisible_ = false;

        /**
         * Active hint box type. Use {@Link HintBoxContentType}.
         * @type {string}
         * @private
         */
        this.currentContentType_ = '';

        /**
         * Completer is a helper which returns a promise. Its solved if open hint box is closed.
         * @type {Completer}
         * @private
         */
        this.completer_ = null;

        /**
         * List of all certain hint box content types which has already been displayed.
         * @type {Array<string>}
         * @private
         */
        this.shownContentTypy_ = [];

        /**
         * Dom element for the close button
         * @type {NodeList}
         * @private
         */
        this.closeButtons_ = null;
    }

    /**
     * Component is ready and had loaded all dependencies (inherit method waitFor and sub components).
     * @inheritDoc
     */
    onInit()
    {
        super.onInit();

        /**
         * List of hint box contents defined by the class `.hint-box-content` and the attribute `data-content=type`
         * @type {NodeList}
         * @private
         */
        this.contents_ = this.getElement().querySelectorAll('.hint-box-content[data-content]');
    }

    /**
     * Displays the hint box of a given type and activates all interactive elements of the hint box content
     * The method returns a Promise that is resolved when the hint box is closed.
     * The type is assigned by setting the attribute to the hint box content element in the dom `app\viwes.index.php`.
     * @example <caption>{@Link HintBoxContentType} `NAVIGATION_MOVEMENT`</caption>
     * <div class="hint-box-content" data-content="navigation-movement"> ...
     * @param {string} contentType Use {@Link HintBoxContentType}
     * @param {number} duration
     * @return {Promise}
     */
    show(contentType, duration = 4)
    {
        if(this.completer_ && !this.completer_.hasCompleted())
            this.completer_.reject('Hint box animation was interrupted!');
        this.completer_ = new Completer();

        if(this.shownContentTypy_.indexOf(contentType) == -1)
            this.shownContentTypy_.push(contentType);

        let activeContent = null;
        this.contents_.forEach((content) => {
            let type = dataset.get(/** @type {Element} */ (content), 'content');
            if(contentType == type)
                activeContent = content;
            classlist.enable(/** @type {Element} */ (content), 'is-visible', contentType == type);
        });
        dataset.set(this.getElement(), 'type', contentType);

        this.closeButtons_ = activeContent.querySelectorAll('.hint-box-close');
        this.closeButtons_.forEach((button) => {
            listen(button, EventType.CLICK, this.handleCloseClick_, false, this);
        });

        this.isVisible_ = true;
        this.currentContentType_ = contentType;
        classlist.remove(this.getElement(), 'is-visible');
        setTimeout(() => {classlist.add(this.getElement(), 'is-visible');}, 0);

        clearTimeout(this.visibleTimeout_);
        if(duration > 0)
        {
            this.visibleTimeout_ = setTimeout(() => {
                this.hide();
            }, duration * 1000);
        }

        return this.completer_.getPromise();
    }

    /**
     * Handle hint box close click
     * @private
     */
    handleCloseClick_()
    {
        if(this.currentContentType_ == HintBoxContentType.ACTIVATE_AUDIO)
            classlist.add(document.documentElement, 'activated-audio');

        this.hide();
    }

    /**
     * Hides the hint box and deactivates all interactive elements of the hint box content
     * @param {string=} contentType Optional {@Link HintBoxContentType} if the methode should only hide a specific type of hint box.
     */
    hide(contentType = '')
    {
        if(contentType != '' && contentType != this.currentContentType_)
            return;

        clearTimeout(this.visibleTimeout_);
        this.visibleTimeout_ = setTimeout(() => {
            if(this.completer_ && !this.completer_.hasCompleted())
                this.completer_.resolve();
        }, 500);

        if(this.closeButtons_) {
            this.closeButtons_.forEach((button) => {
                unlisten(button, EventType.CLICK, this.handleCloseClick_, false, this);
            });
        }
        this.closeButtons_ = null;

        this.isVisible_ = false;
        this.currentContentType_ = '';
        classlist.remove(this.getElement(), 'is-visible');
    }

    /**
     * Checked if a certain hint box content type has already been displayed.
     * @param {string} contentType Use {@Link HintBoxContentType}
     * @return {boolean}
     */
    contentWasShown(contentType)
    {
        return this.shownContentTypy_.indexOf(contentType) != -1;
    }

    /**
     * Getter
     * @return {boolean}
     */
    get isVisible()
    {
        return this.isVisible_;
    }
}

/**
 * @enum {string}
 */
const HintBoxContentType = {
    /** navigation-movement */     NAVIGATION_MOVEMENT: 'navigation-movement',
    /** navigation-perspective */  NAVIGATION_PERSPECTIVE: 'navigation-perspective',
    /** navigation-topview */      NAVIGATION_TOPVIEW: 'navigation-topview',
    /** artwork-info */            ARTWORK_INFO: 'artwork-info',
    /** artwork-single-view */     ARTWORK_SINGLE_VIEW: 'artwork-single-view',
    /** artwork-loading-highres */ ARTWORK_LOADING_HIGHRES: 'artwork-loading-highres',
    /** activate-audio */          ACTIVATE_AUDIO: 'activate-audio',
    /** activate-reshuffling */    ACTIVATE_RESHUFFLING: 'activate-reshuffling'
};

exports = {HintBox,HintBoxContentType};