Source: widget/help/view2/Tile.js

(function() {
    /**
     * @typedef {Object} Help4.widget.help.view2.Tile.Params
     * @property {Help4.widget.help.Widget} widget
     * @property {Help4.widget.help.view.View} view
     * @property {Help4.widget.help.view2.TileContainer} container
     * @property {Help4.control2.container.Container} contentView
     * @property {Help4.control2.container.Container} tileView
     * @property {Help4.widget.help.ProjectTile} projectTile
     * @property {boolean} whatsnew
     * @property {number} [tilePosition = 0]
     * @property {boolean} [widgetActive = true]
     * @property {?Object} hotspotStatus
     * @property {boolean} [stealth = false]
     */

    /**
     * <catalogueKey>:<catalogueType>:<projectId>:<tileId>
     * @typedef {string} Help4.widget.help.view2.Tile.Descriptor
     */

    /**
     * This can be seen as a Collection: <TileData, ?TileControl, ?HotspotControl, ?BubbleControl, ?LightboxControl>
     *
     * @augments Help4.jscore.ControlBase
     * @property {Help4.widget.help.Widget} __widget
     * @property {Help4.widget.help.view.View} __view
     * @property {Help4.widget.help.view2.TileContainer} __container
     * @property {Help4.control2.container.Container} __contentView
     * @property {Help4.control2.container.Container} __tileView
     * @property {Help4.widget.help.ProjectTile} projectTile
     * @property {Help4.widget.help.view2.Tile.Descriptor} descriptor
     * @property {boolean} whatsnew
     * @property {number} tilePosition
     * @property {boolean} widgetActive
     * @property {?Object} hotspotStatus
     * @property {boolean} stealth
     * @property {?string} tileControlId
     * @property {?string} hotspotControlId
     * @property {?string} bubbleControlId
     * @property {?string} lightboxControlId
     * @property {boolean} _blockUpdate
     * @property {boolean} _modified
     * @property {boolean} _selected
     * @property {Function} _onPanelDock
     */
    Help4.widget.help.view2.Tile = class extends Help4.jscore.ControlBase {
        /**
         * @constructor
         * @param {Help4.widget.help.view2.Tile.Params} params
         */
        constructor(params) {
            const onPanelDock = () => _align.call(this);

            const {TYPES: T} = Help4.jscore.ControlBase;
            super(params, {
                params: {
                    widget:         {type: T.instance, mandatory: true, readonly: true, private: true},
                    view:           {type: T.instance, mandatory: true, readonly: true, private: true},
                    container:      {type: T.instance, mandatory: true, readonly: true, private: true},
                    contentView:    {type: T.instance, mandatory: true, readonly: true, private: true},
                    tileView:       {type: T.instance, mandatory: true, readonly: true, private: true},
                    projectTile:    {type: T.object, mandatory: true},
                    descriptor:     {type: T.string, readonly: true},  // will be set below to avoid override by params
                    whatsnew:       {type: T.boolean, readonly: true},
                    tilePosition:   {type: T.number},
                    widgetActive:   {type: T.boolean},
                    hotspotStatus:  {type: T.object_null},
                    stealth:        {type: T.boolean},

                    // internal
                    tileControlId:     {type: T.string_null},
                    hotspotControlId:  {type: T.string_null},
                    bubbleControlId:   {type: T.string_null},
                    lightboxControlId: {type: T.string_null}
                },
                statics: {
                    _blockUpdate:       {init: false, destroy: false},
                    _modified:          {init: false, destroy: false},
                    _selected:          {init: false, destroy: false},
                    _hovered:           {init: false, destroy: false},
                    _onPanelDock:       {init: onPanelDock, destroy: false}
                },
                config: {
                    onPropertyChange: ({name, value}) => {
                        switch (name) {
                            case 'projectTile':
                            case 'hotspotStatus':
                            case 'widgetActive':
                            case 'stealth':
                                this._blockUpdate
                                    ? this._modified = true
                                    : _update.call(this);
                                break;

                            case 'tilePosition':
                                const {TileTile} = Help4.widget.help.view2;
                                TileTile.updateTileControlPosition(this);
                                break;

                            case 'lightboxControlId':
                            case 'bubbleControlId':
                                if (!value) {
                                    // lightbox or bubble have been closed; deselect
                                    const {descriptor} = this;

                                    let type = name.replace(/ControlId/, '');
                                    type = `close${type.charAt(0).toUpperCase()}${type.substring(1)}`;

                                    this._fireEvent({type, descriptor});
                                }
                                break;
                        }
                    }
                }
            });

            const {view2} = Help4.widget.help;
            const descriptor = view2.extractTileDescriptor(params.projectTile);
            this.dataFunctions.set({descriptor}, {allowReadonlyOverride: true});  // allow readonly override

            _update.call(this);
        }

        /** @override */
        destroy() {
            const {__tileView, __contentView, tileControlId, hotspotControlId, lightboxControlId, bubbleControlId} = this;

            if (!__tileView.isDestroyed()) {
                tileControlId && __tileView.remove(tileControlId);
            }

            if (!__contentView.isDestroyed()) {
                hotspotControlId && __contentView.remove(hotspotControlId);
                lightboxControlId && __contentView.remove(lightboxControlId);
                bubbleControlId && __contentView.remove(bubbleControlId);
            }

            super.destroy();
        }

        /**
         * @param {string} controlId
         * @returns {Help4.control2.Control}
         */
        getControl(controlId) {
            return this.__contentView.get(controlId);
        }

        /**
         * @param {Help4.widget.help.ProjectTile} projectTile
         * @returns {boolean}
         */
        equals(projectTile) {
            const {view2} = Help4.widget.help;
            return view2.equalTileDescriptors(this.descriptor, view2.extractTileDescriptor(projectTile));
        }

        /**
         * @param {boolean} select
         * @returns {boolean}
         */
        select(select) {
            const {TileTile, TileHotspot} = Help4.widget.help.view2;
            this._selected = select;
            if (select) {
                TileTile.scrollTileIntoView(this);
                TileHotspot.scrollHotspotIntoView(this);
            }
            return _select.call(this);
        }

        /** @param {boolean} hovered */
        wmHover(hovered) {
            const {TileHotspot} = Help4.widget.help.view2;
            hovered ? TileHotspot.wmHover(this) : TileHotspot.wmLeave(this);
        }

        /**
         * @param {boolean} hovered
         * @param {number} [delay = 0]
         * @returns {Promise<boolean>|void}
         */
        hover(hovered, delay = 0) {
            if (hovered || delay === 0) {
                // immediate action
                const {TileTile} = Help4.widget.help.view2;
                const {_selected} = this;

                this._hovered = hovered;
                hovered && !_selected && TileTile.scrollTileIntoView(this);
                _hover.call(this);
            } else {
                // delayed action
                return new Help4.Promise(resolve => {
                    setTimeout(() => {
                        if (this.isDestroyed()) return;

                        const {__contentView, hotspotControlId, bubbleControlId, descriptor} = this;
                        const hotspot = hotspotControlId && __contentView.get(hotspotControlId);
                        const bubble = bubbleControlId && __contentView.get(bubbleControlId);

                        hotspot && bubble && !hotspot.isMouseOver() && !bubble.isMouseOver()
                            ? resolve({descriptor, success: true})
                            : resolve({descriptor, success: false});
                    }, delay);
                });
            }
        }

        /** @returns {boolean} */
        stopHotspotAnimation() {
            const {TileHotspot} = Help4.widget.help.view2;
            return TileHotspot.stopHotspotAnimation(this);
        }

        /** causes deselection after bubble close */
        closeBubble() {
            const {descriptor} = this;
            this._fireEvent({type: 'closeBubble', descriptor});
        }

        /**
         * @param {Object} [params = {}] - see {@link Help4.widget.help.view2.Tile.Params}
         * @returns {boolean}
         */
        update(params = {}) {
            // this is a bulk change operation;
            // therefore block all control adoptions while the data is just updated
            this._blockUpdate = true;
            this._modified = false;

            /**
             * setting this values will cause a sync call to {@link onPropertyChange}
             * this._modified will be set to true there, if an update is required
             */
            for (const [key, value] of Object.entries(params)) {
                if (this.hasOwnProperty(key)) this[key] = value;
            }

            // if this._modified is still false, no data was changed
            const {_modified} = this;
            this._blockUpdate = false;
            _modified && _update.call(this);

            // align controls
            _align.call(this);

            return _modified;
        }

        /** @returns {boolean} */
        hasHotspot() {
            const {view2} = Help4.widget.help;
            const {__widget, projectTile} = this;
            return view2.hasHotspot(__widget, projectTile);
        }
    }

    /**
     * @memberof Help4.widget.help.view2.Tile#
     * @private
     */
    function _update() {
        const {TileHotspot, TileTile, TileBubble} = Help4.widget.help.view2;

        if (this.stealth) {
            TileHotspot.updateHotspotControl(this);
            TileBubble.handleBubble(this);
        } else {
            const visible = TileHotspot.updateHotspotControl(this);  // either no hotspot or it is visible
            TileTile.updateTileControl(this, visible);

            // notify container about hotspot visibility changes
            const {descriptor} = this;
            const type = visible ? 'hotspotVisible' : 'hotspotHidden';
            this._fireEvent({type, descriptor});
        }
    }

    /**
     * @memberof Help4.widget.help.view2.Tile#
     * @private
     */
    function _align() {
        const {TileLightbox, TileBubble} = Help4.widget.help.view2;
        TileLightbox.alignLightbox(this);
        TileBubble.alignBubble(this);
    }

    /**
     * @memberof Help4.widget.help.view2.Tile#
     * @private
     * @returns {boolean}
     */
    function _select() {
        const {TileTile, TileHotspot, TileLightbox, TileBubble} = Help4.widget.help.view2;

        // in case widget is inactive: there is no visible tile view
        // not need to select any tile as tiles are not available anyway
        const {__widget, stealth} = this;
        const isActive = __widget.isActive();

        const selectionSuccess = isActive ? TileTile.selectTile(this) : true;
        if (!stealth) {
            // not hotspots and bubbles in stealth mode
            TileHotspot.selectHotspot(this);
            TileBubble.handleBubble(this);
        }
        TileLightbox.handleLightbox(this);
        return selectionSuccess;
    }

    /**
     * @memberof Help4.widget.help.view2.Tile#
     * @private
     */
    function _hover() {
        const {TileTile, TileHotspot, TileBubble} = Help4.widget.help.view2;

        TileTile.selectTile(this);
        TileHotspot.selectHotspot(this);
        TileBubble.handleBubble(this);
    }
})();