Source: widget/learning/View.js

(function() {
    /**
     * @typedef {Help4.control2.Control.Params} Help4.widget.learning.View.Params
     * @property {Help4.widget.learning.Widget} widget - the owner widget
     */

    /**
     * View for learning widget.
     * @augments Help4.control2.Control
     * @property {Help4.widget.learning.Widget} __widget
     * @property {Help4.widget.learning.Data} __data
     * @property {HTMLElement} __fullDom
     * @property {?Help4.control2.button.Button} _learningCenterBtn
     * @property {?Help4.control2.button.Button} _communityBtn
     * @property {?Help4.control2.container.Container} _assetContainer
     * @property {?Help4.widget.learning.FeedbackBubbleControl} _feedbackBubble
     * @property {Function} _domRefreshExecutor
     * @property {Function} _onDataChange
     */
    Help4.widget.learning.View = class extends Help4.control2.Control {
        /**
         * @override
         * @param {Help4.widget.learning.View.Params} params
         */
        constructor(params) {
            const {/** @type {Help4.widget.learning.Widget} */ widget} = params;

            /** @param {Help4.jscore.ControlBase.PropertyChangeEvent} event */
            const onDataChange = ({name, value}) => {
                switch (name) {
                    case 'learningAppCommunityUrl':
                        this._communityBtn.visible = !!value;
                        break;
                    case 'assets':
                        _fillAssetContainer.call(this);
                        break;
                }
            }

            const domRefreshExecutor = () => this.isDestroyed() || this._feedbackBubble?.align();

            Help4.widget.companionCore.Core.addStandardViewParameters(widget, params, 'contentDiv');
            const context = widget.getContext();
            params.data = context.widget.learning.data;
            params.fullDom = context.controller.getDom2('dom');

            const {TYPES: T} = Help4.jscore.ControlBase;
            super(params, {
                params: {
                    widget:  {type: T.instance, mandatory: true, private: true, readonly: true},
                    data:    {type: T.instance, mandatory: true, private: true, readonly: true},
                    fullDom: {type: T.element, private: true},
                    role:    {init: 'list'}
                },
                statics: {
                    _learningCenterBtn:  {},
                    _communityBtn:       {},
                    _assetContainer:     {},
                    _feedbackBubble:     {},
                    _onDataChange:       {init: onDataChange, destroy: false},
                    _domRefreshExecutor: {init: domRefreshExecutor, destroy: false},
                },
                config: {
                    css: 'widget-learning-view'
                }
            });
        }

        /**
         * @param {Help4.typedef.SystemConfiguration} configuration
         * @param {Help4.controller.Controller} controller
         */
        static getLearningUrl(configuration, controller) {
            const {
                learning: {learningAppBackendUrl, learningAppWorkspace},
                core: {playbackTag}
            } = configuration;

            const {AuthService, widget: {companionCore: {SEN}}} = Help4;
            const serverUrl = SEN.createPubUrl({serverBaseUrl: learningAppBackendUrl, waId: learningAppWorkspace, tag: playbackTag});

            const authUid = AuthService.getUID(serverUrl);
            const authToken = AuthService.getToken(serverUrl);
            const authQuery = authUid && authToken
                ? `uid_web_assistant=${authUid}&token_web_assistant=${authToken}`
                : '';

            const mltEngine = controller.getEngine('MLT');
            const mltQuery = mltEngine.isTranslationActive()
                ? `mlt_active=true&mlt_language=${mltEngine.getTargetLanguage()}`
                : '';

            const baseUrl = 'index.html' + (
                mltQuery && authQuery
                    ? `?${mltQuery}&${authQuery}`
                    : (mltQuery
                        ? `?${mltQuery}`
                        : (authQuery ? `?${authQuery}` : '')
                    )
            );

            return {serverUrl, baseUrl};
        }

        /**
         * @param {Help4.widget.learning.Widget} widget
         * @param {{entityType: string, entityUid: string, caption: string, subtype: ?string}} entityInfo
         */
        static openLearning(widget, {entityType, entityUid, caption, entitySubType = null}) {
            const {
                /** @type {Help4.typedef.SystemConfiguration} */ configuration,
                /** @type {Help4.controller.Controller} */ controller
            } = widget.getContext();

            const {learningAppProjectMode} = configuration.learning;

            const trackingService = controller.getService('tracking');
            if (trackingService) {
                let objectType = entityType;
                if (entityType === 'project') {
                    objectType = 'standard' + ':' +  learningAppProjectMode;
                } else if (entityType === 'media' && entitySubType) {
                    objectType += ':' + entitySubType;
                }

                trackingService.trackLearningApp({
                    verb: 'open',
                    objectType: objectType,
                    type: 'content',
                    id: `${entityType}!${entityUid}`,
                    name: caption
                });
            }

            const {serverUrl, baseUrl} = this.getLearningUrl(configuration, controller);
            const playbackUrl = entityType === Help4.widget.learning.ALLOWED_TYPES.project
                ? `${baseUrl}#show=${entityType}!${entityUid}:${learningAppProjectMode}`
                : `${baseUrl}#show=${entityType}!${entityUid}`;

            Help4.windowOpen(serverUrl + playbackUrl);
        }

        /** @override */
        destroy() {
            const {
                /** @type {Function} */ _domRefreshExecutor,
                /** @type {Function} */ _onDataChange,
                /** @type {Help4.widget.learning.Data} */ __data,
                /** @type {Help4.widget.learning.Widget} */ __widget
            } = this;
            const context = __widget.getContext();

            context && Help4.widget.companionCore.Core.disconnectDomRefresh(_domRefreshExecutor, context);
            __data?.removeListener('dataChange', _onDataChange);

            super.destroy();
        }

        /**
         * @override
         * @returns {Help4.widget.learning.View}
         */
        focus() {
            this._learningCenterBtn?.focus();
            return this;
        }

        /**
         * focus handling - up/down arrow keys
         * @param {string} direction
         */
        focusListItem(direction) {
            const {_assetContainer} = this;
            const visibleControls = [];

            // add elements according to display order
            if (this._learningCenterBtn?.visible) visibleControls.push(this._learningCenterBtn);
            if (this._communityBtn?.visible) visibleControls.push(this._communityBtn);
            _assetContainer.forEach(control => control.visible && visibleControls.push(control));

            const focussedElement = Help4.widget.getActiveElement();
            let index = visibleControls.findIndex(control => control.getDom() === focussedElement || control.getDom().contains(focussedElement));

            if (index >= 0) visibleControls[direction === 'down' ? ++index : --index]?.focus();
        }

        /** opens the learning center */
        openLearningCenter() {
            const {
                /** @type {boolean} */ showLearningCenter,
                /** @type {string|null} */ learningCenterUrl
            } = this.__data;

            if (!showLearningCenter) return;

            learningCenterUrl
                ? Help4.windowOpen(learningCenterUrl)
                : _openLearningCenter.call(this);
        }

        /**
         * @override
         * @param {HTMLElement} dom - control DOM
         */
        _onDomCreated(dom) {
            _createLearningCenterButton.call(this);
            _createCommunityButton.call(this);
            _createAssetContainer.call(this);
            _fillAssetContainer.call(this);
        }

        /** @override */
        _onReady() {
            const {
                /** @type {Function} */ _domRefreshExecutor,
                /** @type {Function} */ _onDataChange,
                /** @type {Help4.widget.learning.Data} */ __data,
                /** @type {Help4.widget.learning.Widget} */ __widget
            } = this;

            const context = __widget.getContext();
            // observe dom refresh
            Help4.widget.companionCore.Core.observeDomRefresh(_domRefreshExecutor, context, this);
            // subscribe to data changes
            __data.addListener('dataChange', _onDataChange);
        }

        /**
         * @override
         * @param {boolean} [onlyVisible = false]
         */
        getTexts(onlyVisible = false) {
            const {
                /** @type {Help4.control2.bubble.Panel} */ panel
            } = this.__widget.getContext();
            return {
                control: super.getTexts(onlyVisible),
                panel: panel.getTexts(),
                feedbackBubble: this._feedbackBubble?.getTexts(onlyVisible)
            };
        }

        /**
         * @override
         * @param {Object} textMap
         */
        setTexts(textMap) {
            const {
                /** @type {Help4.control2.bubble.Panel} */ panel
            } = this.__widget.getContext();
            super.setTexts(textMap.control || {});
            panel.setTexts(textMap.panel || {});
            this._feedbackBubble?.setTexts(textMap.feedbackBubble || {});
            return this;
        }
    }

    /**
     * @memberof Help4.widget.learning.View#
     * @private
     */
    function _createLearningCenterButton() {
        const {
            __data: {/** @type {boolean} */ showLearningCenter},
            /** @type {boolean} */ rtl,
            /** @type {boolean} */ mobile,
            /** @type {string} */ language,
            id
        } = this;

        if (!showLearningCenter) return;

        const {Localization} = Help4;
        this._learningCenterBtn = new Help4.control2.button.Button({
            id: `${id}-learning-center`,
            dom: this.getDom(),
            css: 'tile',
            text: Localization.getText('header.learningapp'),
            title: Localization.getText('tooltip.learningapp.header'),
            rtl,
            mobile,
            language,
            contentLanguage: language
        })
        .addListener('click', () => void this.openLearningCenter());
    }

    /**
     * @memberof Help4.widget.learning.View#
     * @private
     */
    function _createCommunityButton() {
        const {
            __data: {/** @type {string|null} */ learningAppCommunityUrl},
            /** @type {boolean} */ rtl,
            /** @type {boolean} */ mobile,
            /** @type {string} */ language,
            id
        } = this;

        const {Localization} = Help4;
        this._communityBtn = new Help4.control2.button.Button({
            id: `${id}-community`,
            dom: this.getDom(),
            css: 'tile',
            text: Localization.getText('button.learningapp.community'),
            title: Localization.getText('tooltip.learningapp.community'),
            visible: !!learningAppCommunityUrl,
            rtl,
            mobile,
            language,
            contentLanguage: language
        })
        .addListener('click', () => void Help4.windowOpen(learningAppCommunityUrl));
    }

    /**
     * @memberof Help4.widget.learning.View#
     * @private
     */
    function _createAssetContainer() {
        const {
            /** @type {boolean} */ rtl,
            /** @type {boolean} */ mobile,
            /** @type {string} */ language,
            id
        } = this;

        this._assetContainer = new Help4.control2.container.Container({
            id: `${id}-assets`,
            type: 'Help4.widget.learning.TileControl',
            dom: this.getDom(),
            visible: false,
            rtl,
            mobile,
            language,
            contentLanguage: language
        })
        .addListener(['click', 'enter', 'space'], ({target: [container, tile] = []}) => {
            const entityUid = tile?.getMetadata('entityId');
            const {caption, entityType, entitySubType} = tile || {};
            entityType && entityUid && Help4.widget.learning.View.openLearning(this.__widget, {entityType, entityUid, caption, entitySubType});
        })
        .addListener('feedback', ({target: [container, tile] = []}) => {
            const entityUid = tile?.getMetadata('entityId');
            const {entityType} = tile || {};
            entityUid && _openFeedbackBubble.call(this, {entityType, entityUid});
        });
    }

    /**
     * @memberof Help4.widget.learning.View#
     * @private
     */
    function _fillAssetContainer() {
        const {
            /** @type {Help4.control2.container.Container} */ _assetContainer,
            /** @type {Help4.widget.learning.Data} */ __data
        } = this;
        const {/** @type {Help4.jscore.DataContainer} */ assets} = __data;

        _assetContainer.clean();

        if (assets.count()) {
            _assetContainer.add(...assets.toArray());
            _assetContainer.visible = true;
        } else {
            _assetContainer.visible = false;
        }
    }

    /**
     * @memberof Help4.widget.learning.View#
     * @private
     */
    function _openLearningCenter() {
        const {
            /** @type {Help4.typedef.SystemConfiguration} */ configuration,
            /** @type {Help4.controller.Controller} */ controller
        } = this.__widget.getContext();

        const trackingService = controller.getService('tracking');

        /**
         * @param {string} key1
         * @param {string} key2
         * @returns {*}
         */
        const getFromConfig = (key1, key2) => {
            const key = (key1 || `core.${key2}`).split('.');
            let result = configuration;
            do {
                result = result[key.shift()];
            } while (key[0]);
            return result;
        };

        const query = {
            i18nPath: Help4.I18N_PATH,
            themePath: Help4.THEME_PATH
        };
        const list = {
            resourceUrl: 0,
            product: 0, system: 0, version: 0, solution: 0, rtl: 0, mobile: 0,
            infoBarTimeout: 0, infoBarTimeoutError: 0,
            playbackTag: {not: 'published'},

            backendUrl: 'learning.learningAppBackendUrl',
            workspace: 'learning.learningAppWorkspace',
            communityUrl: 'learning.learningAppCommunityUrl',

            playbackMode: {not: 'uebung', key: 'learning.learningAppProjectMode'},
            theme: {not: Help4.DEFAULT_THEME},
            edithelp: 'editor',
            tenant: 'tenant'
        };
        for (const [key, data] of Object.entries(list)) {
            let value;
            if (typeof data === 'object') {
                value = getFromConfig(data.key, key)
                if (data.not && value === data.not) value = null;
            } else {
                value = getFromConfig(data, key);
            }

            if (value != null) query[key] = value === true ? 1 : value;
        }

        const {
            core: {language, defaultLanguage, customTheme, resourceUrl},
            learning: {learningAppFeedback, learningAppBackendUrl},
            tracking: {trackingUrlSEN}
        } = configuration;

        if (!learningAppFeedback) query.enableFeedback = 0;

        if (language.wpb !== Help4.DEFAULT_LOCALE.wpb) {
            query.language = Help4.JSON.stringify(language);
        }
        query.defaultLanguage = Help4.JSON.stringify(defaultLanguage);

        const {AuthService} = Help4;
        const otsToken = AuthService.getToken(learningAppBackendUrl);
        const uidToken =  AuthService.getUID(learningAppBackendUrl);
        if (otsToken) query.otsToken = otsToken;
        if (uidToken) query.uidToken = uidToken;

        /** @type {Help4.jscore.DataContainer} */
        const {assets} = this.__data;
        if (!assets.count()) {
            // XRAY-1222: no screen specific content available
            // add message for external app
            query.message = 'label.learningapp.nomatchwarning';
        }

        if (trackingUrlSEN) {
            query.trackingUrlWPB = trackingUrlSEN;
            query.sessionId = trackingService.getSessionId();
        }

        if (customTheme) {
            const ct = {};
            for (const [key, value] of Object.entries(customTheme)) {
                ct[key] = Help4.CustomTheme.rgbToHex(value);
            }
            query.customTheme = Help4.JSON.stringify(ct);
        }

        trackingService.trackLearningApp({verb: 'open', type: 'app'});

        // transform into URL query string
        const s = [];  // XRAY-1216
        for (const [key, value] of Object.entries(query)) {
            s.push(`${key}=${encodeURIComponent(value)}`);
        }

        const url = resourceUrl + Help4.LEARNING_APP_PATH + '?' + s.join('&');
        Help4.windowOpen(url);
    }

    /**
     * @memberof Help4.widget.learning.View#
     * @private
     * @param {Object} data - tile data
     * @param {string} data.entityType - entity type of tile
     * @param {string} data.entityUid - entity id of tile
     */
    function _openFeedbackBubble({entityType, entityUid}) {
        const backendParams = {
            entityType,
            entityUid,
            /** @type {Help4.widget.learning.FeedbackBubbleContentControl.Data} */ value: {rating: 0, text: ''}
        };

        const {FeedbackBubbleControl} = Help4.widget.learning;
        const {__widget, __fullDom: dom} = this;

        const close = () => {
            bubble.destroy();
            observer.destroy();
            delete this._feedbackBubble;
        }

        const {eventBus} = __widget.getContext();
        const observer = new Help4.observer.EventBusObserver(({hotkey}) => (hotkey === Help4.HOTKEY.escape) && close())
        .observe(eventBus, {type: Help4.EventBus.TYPES.hotkey});

        const bubble = this._feedbackBubble = /** @type {Help4.widget.learning.FeedbackBubbleControl} */ this._createControl(FeedbackBubbleControl, {dom})
        .addListener('change', ({data}) => backendParams.value = data)
        .addListener('close', close)
        .addListener('send', async () => {
            close();

            const {
                widget: {learning: {Backend}},
                control2: {InfoBar: {TYPES}},
                Localization
            } = Help4;

            const {
                configuration,
                service: {/** @type {Help4.service.InfobarService} */ infobarService}
            } = __widget.getContext();
            const success = await Backend.sendFeedback(configuration, backendParams);

            /** @type {Help4.control2.InfoBar.Params} */
            const params = success
                ? {type: TYPES.success, content: Localization.getText('content.feedback.send.success')}
                : {type: TYPES.error, content: Localization.getText('content.feedback.send.error')};

            infobarService.add(params);
        });

        bubble.align();
    }
})();