Source: widget/tourlist/Widget.js

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

    const NAME = 'tourlist';
    const HELP_NAME = 'help';
    const TOUR_NAME = 'tour';

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

    /**
     * Guided Tours list functionality widget
     * @augments Help4.widget.Widget
     * @property {?Help4.widget.tourlist.Data} _data
     * @property {?Help4.widget.tourlist.View} _view
     * @property {Function} _onUpdateCatalogue
     * @property {Function} _domRefreshExecutor
     */
    Help4.widget.tourlist.Widget = class extends Help4.widget.Widget {
        /** @override */
        constructor() {
            const onUpdateCatalogue = async (event) => {
                if (event.name === 'catalogues') {
                    const {_data, _view} = this;
                    _data.updateCatalogue();

                    await _setVisible.call(this);
                    if (this.isDestroyed()) return;

                    _view?.update();
                }
            }

            const domRefreshExecutor = () => this.isStarted() && _setVisible.call(this);

            super({
                statics: {
                    _data: {},
                    _view: {},
                    _onUpdateCatalogue:  {init: onUpdateCatalogue, destroy: false},
                    _domRefreshExecutor: {init: domRefreshExecutor, destroy: false}
                }
            });
        }

        /**
         * will observe DOM changes to re-calculate tourlist visibility<br>
         * requirements:<br>
         * 1. widget is initialized; see {@link Help4.widget.tourlist.Widget#_onAfterInit}<br>
         * 2. controller is open; see {@link Help4.widget.tourlist.Widget#_onControllerOpen}<br>
         * otherwise observation is disconnected to minimize footprint
         *
         * @param {Help4.widget.tourlist.Widget|Help4.widget.whatsnew.Widget} widget
         * @param {boolean} [stop = false]
         */
        static observeDomRefresh(widget, stop = false) {
            const {_domRefreshExecutor} = widget;
            if (!_domRefreshExecutor) return;

            const {Core} = Help4.widget.companionCore;
            const context = widget.getContext();

            if (stop) {
                Core.disconnectDomRefresh(_domRefreshExecutor, context);
            } else {
                context.controller?.isOpen()
                    ? Core.observeDomRefresh(_domRefreshExecutor, context, this)  // observe DOM changes to adjust tour tile visibility
                    : Core.disconnectDomRefresh(_domRefreshExecutor, context);  // disconnect
            }
        }

        /**
         * @param {Help4.typedef.SystemConfiguration} configuration
         * @param {Help4.widget.tour.Widget.StartStatus} tour
         * @returns {Promise<void>}
         */
        static async startTour({help: {serviceLayer}}, {projectId, catalogueKey, catalogueType, dataType, whatsnew}) {
            // EXT: automatically change catalogueKey to "pub" for RO model projects
            const isEXT = serviceLayer === Help4.SERVICE_LAYER.ext;
            if (isEXT && (catalogueType === 'UACP' || catalogueType === 'SEN')) catalogueKey = 'pub';

            const status = /** @type {Help4.widget.tour.Widget.StartStatus} */ {projectId, catalogueKey, catalogueType, dataType, whatsnew};
            const {[NAME]: tourlistWidget, [TOUR_NAME]: tourWidget} = /** @type {Help4.widget.tour.Widget} */ Help4.widget.getInstance();
            await tourlistWidget.deactivate({tour: true});
            await tourWidget?.startTour(status);
        }

        /** @override */
        getName() {
            return NAME;
        }

        /**
         * @override
         * @returns {Help4.widget.tourlist.Widget.Context}
         */
        getContext() {
            const helpWidget = /** @type {Help4.widget.help.Widget} */ Help4.widget.getInstance(HELP_NAME);
            /** @type {Help4.widget.help.Widget.Context} */
            const helpContext = helpWidget?.getContext() || {};
            /** @type {Help4.widget.Widget.Context} */
            const context = super.getContext();

            context.widget = {
                tourlist: {
                    data: this._data
                },
                help: {
                    data: helpContext?.widget?.help?.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 text = Localization.getText('button.widget.tours');

            return {
                id: NAME,
                enabled: WM < 2,
                showPanel: true,
                requires: {
                    namespaces: [
                        'Help4.widget.companionCore.Core',
                        'Help4.widget.companionCore.SEN',
                        'Help4.widget.companionCore.UACP'
                    ],
                    instances: [HELP_NAME]
                },
                tile: {
                    text,
                    title: text,
                    icon: ICONS.tour,
                    color: COLOR.color2,
                    position: 2
                }
            };
        }

        /** @override */
        focus() {
            this._view?.focus();
        }

        /**
         * @override
         * @param {string} direction
         */
        focusListItem(direction) {
            this._view?.focusListItem(direction);
        }

        /** @override */
        async _onBeforeDestroy() {
            const {/** @type {Help4.widget.help.Data} */ data} = this.getContext().widget.help;
            data?.removeListener('dataChange', this._onUpdateCatalogue);

            this.constructor.observeDomRefresh(this, true);
        }

        /** @override */
        async _onBeforeInit() {
            this._data = new Help4.widget.tourlist.Data({widget: this});
        }

        /** @override */
        async _onAfterInit() {
            const {/** @type {Help4.widget.help.Data} */ data} = this.getContext().widget.help;

            /**
             * will update the tour list based on the updated catalogue information
             * @param {Help4.jscore.ControlBase.PropertyChangeEvent} event
             */
            const {_onUpdateCatalogue} = this;
            // catalogue is managed by help widget; subscribe to changes
            // from now on we receive every catalogue update
            data.addListener('dataChange', _onUpdateCatalogue);

            // get data that might have been loaded already
            await _onUpdateCatalogue({name: 'catalogues'});
            if (this.isDestroyed()) return;

            // monitor DOM changes to decide visibility
            this.constructor.observeDomRefresh(this);
        }

        /** @override */
        async _onAfterActivate() {
            // initialize tour list view
            this._view = new Help4.widget.tourlist.View({widget: this})
            .addListener('startTour', ({tour}) => {
                const {tourOpen} = Help4.EventBus.TYPES;
                const controller = Help4.getController();
                controller.getService('eventBus').fire({type: tourOpen});

                this.startTour(tour);
            });
        }

        /** @override */
        async _onBeforeDeactivate() {
            this._destroyControl('_view');
        }

        /** @override */
        async redraw() {
            await super.redraw();
            if (this.isDestroyed()) return;

            await this._view?.update();
        }

        /**
         * @param {Help4.widget.tour.Widget.StartStatus} tour
         * @returns {Promise<void>}
         */
        async startTour(tour) {
            const config = this.getContext().configuration;
            await this.constructor.startTour(config, tour);
        }

        /** @override */
        async _onControllerOpen() {
            this.constructor.observeDomRefresh(this);
        }

        /** @override */
        async _onControllerClose() {
            this.constructor.observeDomRefresh(this);
        }

        /**
         * @override
         * @param {Help4.widget.Widget.SearchFilter} search
         * @returns {Promise<Help4.widget.Widget.SearchResult[]>}
         */
        async filter({fulltext}) {
            const {Placeholder, widget: {companionCore: {data: {Tour}}}} = Help4;
            const projects = await Tour.getFilteredTourProjects();
            if (this.isDestroyed()) return [];

            const {configuration} = this.getContext();
            const catalogueKey = Help4.widget.companionCore.Core.getCatalogueKey({configuration});

            const results = [];
            for (const project of projects) {
                if (this._fulltextMatchesText(fulltext, Placeholder.resolve(project.title))) results.push({
                    caption: project.title,
                    projectId: project.id,
                    contentLanguage: project.language,
                    catalogueKey,
                    catalogueType: project._catalogueType,
                    dataType: project._dataType,
                });
            }
            return results;
        }

        /** @override */
        async getTexts() {
            if (this.isActive()) {
                const {panel} = this.getContext();
                return panel.getTexts();
            }
        }

        /**
         * @override
         * @param {Object} texts
         */
        async setTexts(texts) {
            if (this.isActive()) {
                const {panel} = this.getContext();
                panel.setTexts(texts);
            }
        }
    }

    /**
     * @memberof Help4.widget.tourlist.Widget#
     * @private
     * @returns {Promise<void>}
     */
    async function _setVisible() {
        const {Tour} = Help4.widget.companionCore.data;
        const projects = await Tour.getFilteredTourProjects();
        if (this.isDestroyed()) return;

        this.__visible = projects.length > 0;
    }
})();