(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();
}
})();