Source: widget/whatsnew/Widget.js

(function() {
    /**
     * @namespace whatsnew
     * @memberof Help4.widget
     */
    Help4.widget.whatsnew = {};

    /**
     * @typedef {Help4.widget.help.Widget.Context} Help4.widget.whatsnew.Widget.Context
     * @property {Object} widget.whatsnew
     * @property {Help4.widget.whatsnew.Data} widget.whatsnew.data
     */

    /**
     * SEN What's New functionality widget
     * @augments Help4.widget.help.Widget
     * @property {Function} _domRefreshExecutor
     */
    Help4.widget.whatsnew.Widget = class extends Help4.widget.help.Widget {
        static NAME = 'whatsnew';
        static DATA = 'Help4.widget.whatsnew.Data';
        static HELP_NAME = 'help';

        /** @override */
        constructor() {
            const domRefreshExecutor = async () => {
                // recalculate tours based on conditions
                this.isStarted() && await this._data.updateCatalogue();
            }

            super({
                statics: {
                    _domRefreshExecutor: {init: domRefreshExecutor, destroy: false}
                }
            });
        }

        /**
         * @override
         * @returns {Help4.widget.whatsnew.Widget.Context}
         */
        getContext() {
            const {_data: data} = this;
            const context = /** @type {Help4.widget.help.Widget.Context} */ super.getContext();
            context.widget.whatsnew = {data};
            return context;
        }

        /**
         * @override
         * @returns {Promise<Help4.widget.Widget.Descriptor>}
         */
        async _onGetDescriptor() {
            const {
                Localization,
                control2: {ICONS},
                widget: {COLOR}
            } = Help4;

            const {WM} = this.getContext().configuration;
            const {NAME, HELP_NAME} = this.constructor;
            const text = Localization.getText('button.widget.whatsnew');

            return {
                id: NAME,
                enabled: WM < 2,
                showPanel: true,
                requires: {
                    namespaces: [
                        'Help4.widget.help',
                        'Help4.widget.tourlist'
                    ],
                    instances: [HELP_NAME]
                },
                tile: {
                    text,
                    title: text,
                    icon: ICONS.whatsnew,
                    color: COLOR.color3,
                    position: 4
                }
            };
        }

        /** @override */
        async _onBeforeDestroy() {
            await super._onBeforeDestroy();
            Help4.widget.tourlist.Widget.observeDomRefresh(this, true);
        }

        /** @override */
        async _onAfterInit() {
            await super._onAfterInit();
            setTimeout(() => _handleAutoStart.call(this, true), 500);

            Help4.widget.tourlist.Widget.observeDomRefresh(this);
        }

        async _onControllerOpen() {
            await super._onControllerOpen();
            Help4.widget.tourlist.Widget.observeDomRefresh(this);
        }

        async _onControllerClose() {
            await super._onControllerClose();
            Help4.widget.tourlist.Widget.observeDomRefresh(this);
        }

        /**
         * @override
         * @param {Object} [data = null]
         * @returns {Promise<void>}
         */
        async _onAfterActivate(data = null) {
            // activate specific whatsnew theming
            const {NAME} = this.constructor;
            const {Theme, [`_${NAME}`]: rules} = Help4.theme;
            const {controller} = this.getContext();
            const shadowRoot = controller.getDom2('shadow');
            Theme.setCustomVariables(shadowRoot, NAME, rules);

            return super._onAfterActivate(data);
        }

        /**
         * @override
         * @returns {Promise<boolean|void>}
         */
        async _onBeforeDeactivate() {
            // deactivate specific whatsnew theming
            const {Theme} = Help4.theme;
            const {NAME} = this.constructor;
            const {controller} = this.getContext();
            const shadowRoot = controller.getDom2('shadow');
            Theme.removeCustomVariables(shadowRoot, NAME);

            await super._onBeforeDeactivate();
        }

        /** @override */
        async _onSystemNavigate() {
            await super._onSystemNavigate();
            setTimeout(() => _handleAutoStart.call(this), 500);
        }

        /**
         * @override
         * @protected
         * @param {Help4.widget.help.TileDescriptor} data
         * @returns {Promise<void>}
         */
        async _trackProject(data) {
            const {tileId} = data;

            const tiles = await this._data.getHelpTiles();
            const tile = tiles.find(({id}) => id === tileId);
            if (tile && tile.type === 'tour') return;  // will be tracked separately

            await super._trackProject(data, tiles);
        }

        /**
         * @override
         * @protected
         * @param {?Help4.widget.help.view2.SerializedStatus} status
         * @param {Object} [data = null]
         * @param {Object} [params = {}]
         * @param {boolean} [params.stealth]
         * @returns {Promise<void>}
         */
        async _createView2(status, data = null, {stealth} = {}) {
            await super._createView2(status, data, {stealth});

            this._view2.addListener('select', async ({data}) => {
                const {type, tileId: projectId} = data || {};
                if (type !== 'tour') return;

                const {
                    help: {project: {WNTours}},
                    companionCore: {Core}
                } = Help4.widget;

                const {configuration} = this.getContext();
                const catalogueKey = Core.getCatalogueKey({configuration});
                const {catalogueProject} = WNTours.getData(catalogueKey).find(({id}) => id === projectId);
                const {_catalogueType: catalogueType, _dataType: dataType} = catalogueProject;

                const tour = /** @type {Help4.widget.tour.Widget.StartStatus} */ {projectId, catalogueKey, catalogueType, dataType, whatsnew: true};

                const {Widget: TourlistWidget} = Help4.widget.tourlist;
                await this.deactivate({tour: true});
                await TourlistWidget.startTour(configuration, tour);
            });
        }

        /**
         * @override
         * @param {*} [data = null]
         * @returns {{visible: boolean, isEditMode: boolean, isActiveCMP4: boolean}}
         */
        _isView2Visible(data = null) {
            const {configuration: {core: {isEditMode, isActiveCMP4}}} = this.getContext();
            const isActive = this.isActive();

            // show view if
            // - not in edit mode (CMP3)
            // - my widget is active
            return {
                visible: !isEditMode && isActiveCMP4 && isActive,
                isEditMode,
                isActiveCMP4
            }
        }

        /**
         * @override
         * @protected
         * @param {boolean} stealth
         * @returns {Promise<void>}
         */
        async _view2Stealth(stealth) {
            // stealth not supported in whatsnew
            stealth && this._destroyView2();
        }
    }

    /**
     * @memberof Help4.widget.whatsnew.Widget
     * @private
     * @param {boolean} [isBoot = false]
     * @returns {Promise<void>}
     */
    async function _handleAutoStart(isBoot = false) {
        const {
            COOKIE_KEYS: {WHATS_NEW},
            control2: {InfoBar: {TYPES: {whatsnew}}},
            Localization,
            widget
        } = Help4;
        const {help: {Cookie}} = widget;

        // in case whatsnew mode has already been played here; do not start automatically
        const hasWhatsNewCookie = await Cookie.getPageCookie(this, {type: WHATS_NEW});
        if (hasWhatsNewCookie || this.isDestroyed()) return;

        // XRAY-6241: do not do anything in case no whatsnew data is available
        await this._data.waitHelpLoaded();
        if (this.isDestroyed()) return;
        await this._setVisible();
        if (this.isDestroyed() || !this.isVisible()) return;

        const {
            controller,
            service: {infobarService},
            configuration: {
                help: {whatsNewDirect, whatsNewExpiration},
                core: {recentlyClosed},
                isEditMode
            }
        } = this.getContext();

        const infobarOpen = infobarService.count() > 0;
        const activeWidgetName = widget.getActiveInstance()?.getName();
        const isTourMode = activeWidgetName === 'tour';

        // XRAY-1128; in case a tour is running: do not disturb it and do not even mark this screen as visited
        // XRAY-2372: Don't show WN and do not mark the page if there is already an infobar
        // XRAY-5985: do not show whatsnew message in edit mode
        if (isTourMode || isEditMode || infobarOpen) return;

        // currently playing whatsnew content; mark screen
        if (this.isActive()) return Cookie.setPageCookie(this, {type: WHATS_NEW});

        // whatsnew mode not yet active

        // XRAY-1276: do not show whatsnew message after a certain date
        if (typeof whatsNewExpiration === 'number' && whatsNewExpiration - (new Date()).getTime() < 0) return;

        const showWhatsnew = () => {
            if (!controller.isOpen()) controller.open();
            this.activate();
        }

        if (whatsNewDirect && isBoot) {
            // XRAY-1277: start whatsnew directly
            if (!recentlyClosed) {
                await Cookie.setPageCookie(this, {type: WHATS_NEW});  // mark screen
                showWhatsnew();
            }
        } else {
            // show info message that whatsnew is available
            await Cookie.setPageCookie(this, {type: WHATS_NEW});  // mark screen

            const content = Localization.getText('label.whatsnew');
            infobarService.add({
                content,
                clickable: true,
                type: whatsnew
            })
            .addListener('click', showWhatsnew);
        }
    }
})();