(function() {
const ATTRIBUTES_MAPPING = {
caption: 'title',
h4_loio: 'loio',
h4_product: 'product',
h4_product_version: 'version',
h4_system: 'system'
}
const TRANSFORM = {
/**
* @param {string} value
* @returns {string}
*/
getTileType: value => value === 'guide_click' ? 'tour' : value.replace(/_tile/, ''),
/**
* @param {string} value
* @param {Object} params
* @param {boolean} params.isExtension
* @returns {string}
*/
processText: (value, {isExtension}) => typeof value === 'string' || isExtension ? value : '',
/**
* @param {string} value
* @param {Object} params
* @param {boolean} params.isExtension
* @returns {string}
*/
getHotspotId: (value, {isExtension}) => !isExtension ? `${value}-hs` : undefined,
/**
* @param {boolean} value
* @param {Object} params
* @param {Help4.widget.help.project.SEN.LessonJsMacro} params.macro
* @param {boolean} params.isExtension
* @returns {boolean}
*/
getShowArrow: (value, {macro, isExtension}) => !isExtension && macro.macro_template !== 'guide_click' ? true : value,
/**
* @param {string} value
* @param {Object} params
* @param {Help4.widget.help.project.SEN.LessonJsMacro} params.macro
* @param {boolean} params.isExtension
* @returns {boolean}
*/
getInstantHelp: function(value, {macro, isExtension}) {
return value && macro.hotspot_style === 'CIRCLE' && !isExtension ? false : value; // XRAY-4174
}
}
/**
* @typedef {Object} Help4.widget.help.project.SEN.ProjectUrlConfig
* @property {string} serverBaseUrl - base URL of the content server
* @property {string} pubUrl - URL extension to public end-user content
* @property {string} [headUrl = null] - URL extension to non-public author only content
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.TagInfo
* @property {string} id - tag ID
* @property {string} display_name
* @property {string} description
*/
/**
* @typedef {Help4.widget.help.project.SEN.TagInfo[]} Help4.widget.help.project.SEN.TagInfoList
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.ProjectMeta
* @property {Object[]} Commits
* @property {Object[]} Streams
* @property {number} wt - write token status
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.ProjectTag
* @property {string} tag
* @property {string} version
*/
/**
* @typedef {Help4.widget.help.project.SEN.ProjectTag[]} Help4.widget.help.project.SEN.ProjectTags
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.ProjectVersion
* @property {string} version
* @property {string} user
* @property {string} date
*/
/**
* @typedef {Help4.widget.help.project.SEN.ProjectVersion[]} Help4.widget.help.project.SEN.ProjectVersions
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.Project
* @property {Help4.widget.help.project.SEN.ProjectMeta} Meta
* @property {Help4.widget.help.project.SEN.ProjectTags} Tags
* @property {Help4.widget.help.project.SEN.ProjectVersions} Versions
* @property {boolean} active
* @property {Array<*>} assets
* @property {string} assignee
* @property {string} caption
* @property {string} conditions
* @property {string} context_id
* @property {string} created_by
* @property {string} creation_time
* @property {string} creator
* @property {string} cwa_recommended_sync_mode
* @property {string} description
* @property {*} entity_published_path
* @property {boolean} fallback_has_entity
* @property {string} h4_loio
* @property {string} h4_product
* @property {string} h4_product_version
* @property {string} h4_system
* @property {number} hidden
* @property {string} language
* @property {string} macroset
* @property {string} mastery_score_percent
* @property {string} max_score_percent
* @property {string} maxversion
* @property {string} modification_time
* @property {string} modified_by
* @property {string} ref_status
* @property {string} status
* @property {string} sub_type
* @property {string} uid
* @property {string} workflow_uid
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.EntityTxt
* @property {*} assets
* @property {string} caption
* @property {string} conditions
* @property {string} context_id
* @property {string} created_by
* @property {string} creation_time
* @property {string} creator
* @property {string} cwa_recommended_sync_mode
* @property {string} description
* @property {string} h4_loio
* @property {string} h4_product
* @property {string} h4_product_version
* @property {string} h4_system
* @property {boolean} [hidden]
* @property {string} language
* @property {string} macroset
* @property {string} mastery_score_percent
* @property {string} max_score_percent
* @property {string} modification_time
* @property {string} modified_by
* @property {string} sub_type
* @property {string} tclass
* @property {string} uid
* @property {Object} [info]
* @property {Help4.widget.help.PUBLISHED_STATUS} info.published
* @property {Help4.widget.help.PUBLISHED_STATUS} info.playbackTag
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.LessonJsMacroStartUnit
* @property {number} duration
* @property {'start_unit'} macro_template
* @property {number} time
* @property {string} uid
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.LessonJsMacroDefineTarget
* @property {number} duration
* @property {string} key - screen ID
* @property {'define_target'} macro_template
* @property {number} time
* @property {string} uid
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.LessonJsMacroGuideClick
* @property {boolean} bubble_centered
* @property {string} bubble_orientation
* @property {string} bubble_size
* @property {string} bubble_text
* @property {string} bubble_type
* @property {string} caption
* @property {string} conditions
* @property {number} duration
* @property {string} hotspot
* @property {string} hotspot_element_type
* @property {Object} manual_manual_offset
* @property {number} manual_manual_offset.top
* @property {number} manual_manual_offset.left
* @property {boolean} hotspot_offset
* @property {Object} hotspot_rect_delta
* @property {number} hotspot_rect_delta.width
* @property {number} hotspot_rect_delta.height
* @property {boolean} hotspot_spotlight
* @property {string} hotspot_style
* @property {'guide_click'} macro_template
* @property {boolean} show_arrow
* @property {boolean} show_title_bar
* @property {number} time
* @property {string} tourstep_auto_progress
* @property {boolean} tourstep_auto_skip
* @property {string} uid
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.LessonJsMacroHelpTile
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.LessonJsMacroLinkTile
*/
/**
* @typedef {Help4.widget.help.project.SEN.LessonJsMacroStartUnit|Help4.widget.help.project.SEN.LessonJsMacroLinkTile|Help4.widget.help.project.SEN.LessonJsMacroHelpTile|Help4.widget.help.project.SEN.LessonJsMacroDefineTarget|Help4.widget.help.project.SEN.LessonJsMacroGuideClick} Help4.widget.help.project.SEN.LessonJsMacro
*/
/**
* @typedef {Array<Help4.widget.help.project.SEN.LessonJsMacro>} Help4.widget.help.project.SEN.LessonJsMacroArray
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.LessonJsTourstop
* @property {string} audio
* @property {number} audio_duration
* @property {boolean} callable
* @property {number} duration
* @property {index} index
* @property {boolean} jumpable
* @property {Help4.widget.help.project.SEN.LessonJsMacroArray} macros
* @property {string} title
* @property {string} uid
* @property {boolean} visible
*/
/**
* @typedef {Object} Help4.widget.help.project.SEN.LessonJs
* @property {Object} global_params
* @property {Help4.widget.help.project.SEN.LessonJsTourstop[]} tourstops
* @property {Object} user_header
* @property {string} user_header.audio_ext
* @property {string} user_header.author
* @property {string} user_header.comment
* @property {string} user_header.language
* @property {string} user_header.mastery_score_percent
* @property {string} user_header.max_score_percent
* @property {string} user_header.shelftype
* @property {string} user_header.title
* @property {string} user_header.version
*/
/**
* this backend connector is able to handle SEN project content for a RO model configuration
* - in case of SEN config: will download published and head data
* - in case of EXT config: will only download published data
*/
Help4.widget.help.project.SEN = class {
/**
* @param {string} projectId - project ID
* @param {Help4.typedef.SystemConfiguration} config - the system configuration
* @param {Help4.widget.help.Data} data
* @returns {Promise<Help4.widget.help.Projects|null>}
*/
static async load(projectId, config, data) {
const {wpb, sen, ext} = Help4.SERVICE_LAYER;
const {roModel, serviceUrl} = config.help;
if (!serviceUrl || roModel !== wpb && roModel !== sen) return null;
const {help: {serviceLayer}, core: {editor}} = config;
/** @type {Help4.widget.help.project.SEN.ProjectUrlConfig} */
const {serverBaseUrl, pubUrl, headUrl} = this._getUrls(projectId, config, serviceUrl);
/** @type {Help4.widget.help.Projects} */
const projects = {pub: null};
// important: in case of EXT - always load and use published data for RO source!
const {SEN} = Help4.widget.companionCore;
if (editor && serviceLayer !== ext) {
// editor: load PUB and HEAD
/** @type {?Help4.widget.companionCore.SEN.ProjectResponse} */
const response = await SEN.doProjectRequest(config, serviceUrl, {serverBaseUrl, pubUrl, headUrl});
const {pub, head, tagInfo} = response || {};
if (!tagInfo) return null;
if (pub) {
/** @type {?Help4.widget.companionCore.SEN.PubProjectResponse} */
const assembled = this._assemblePub(pub, tagInfo, config);
if (assembled) projects.pub = this._parse(assembled, config);
}
if (head) {
/** @type {?Help4.widget.companionCore.SEN.PubProjectResponse} */
const assembled = this._assembleHead(head, tagInfo, config);
if (assembled) projects.head = this._parse(assembled, config);
}
} else {
// non-editor or EXT: load PUB only
/** @type {?Help4.widget.companionCore.SEN.PubProjectResponse} */
const response = await SEN.doProjectRequest(config, serviceUrl, {serverBaseUrl, pubUrl});
projects.pub = response ? this._parse(response, config) : null;
}
return projects;
}
/**
* @protected
* @param {string} projectId - the project id
* @param {Help4.typedef.SystemConfiguration} config - the system configuration
* @param {string} serviceUrl - the server base URL
* @returns {Help4.widget.help.project.SEN.ProjectUrlConfig}
*/
static _getUrls(projectId, config, serviceUrl) {
const {SEN} = Help4.widget.companionCore;
const serverBaseUrl = SEN.getServerBaseUrl(serviceUrl);
const {playbackTag: tag} = config.core;
const context = encodeURIComponent(Help4.JSON.stringify({id: projectId}));
const pubUrl = SEN.createPubUrl({serverUrl: serviceUrl, tag}) + `/.context?${context}`;
const headUrl = `${serviceUrl}/project/${projectId}`;
return {serverBaseUrl, pubUrl, headUrl};
}
/**
* @protected
* @param {Help4.widget.companionCore.SEN.ProjectResponsePub} pub
* @param {Help4.widget.companionCore.SEN.PubProjectResponse} pub.context
* @param {Help4.widget.help.project.SEN.Project} pub.project
* @param {Help4.widget.help.project.SEN.TagInfoList} tagInfo
* @param {Help4.typedef.SystemConfiguration} config
* @returns {Help4.widget.companionCore.SEN.PubProjectResponse|null}
*/
static _assemblePub({context, project}, tagInfo, config) {
/** @type {Help4.widget.help.project.SEN.EntityTxt} */
const {entityTxt} = context;
if (entityTxt) {
/** @type {boolean} */
entityTxt.hidden = Help4.clampBoolean(project.hidden, false);
/** @type {{published: Help4.widget.help.PUBLISHED_STATUS, playbackTag: Help4.widget.help.PUBLISHED_STATUS}} */
entityTxt.info = _getPublishedTagInfo(project, tagInfo, config);
return context;
}
return null;
}
/**
* @protected
* @param {Help4.widget.companionCore.SEN.ProjectResponseHead} head
* @param {Document} head.entityXml
* @param {Document} head.projectDpr
* @param {Help4.widget.help.project.SEN.Project} head.project
* @param {Help4.widget.help.project.SEN.TagInfoList} tagInfo
* @param {Help4.typedef.SystemConfiguration} config
* @returns {Help4.widget.companionCore.SEN.PubProjectResponse|null}
*/
static _assembleHead(head, tagInfo, config) {
const {entityXml, projectDpr, project} = head;
const {XmlHelper} = Help4.widget.help;
/** @type {Help4.widget.help.project.SEN.EntityTxt|null} */
const entityTxt = XmlHelper.toEntityTxt(entityXml, project.uid);
if (entityTxt && projectDpr) {
/** @type {Help4.widget.help.ContextTypes} */
const contextType = entityTxt.sub_type.toUpperCase();
/** @type {Help4.widget.help.project.SEN.LessonJs} */
const lessonJs = XmlHelper.toLessonJs(projectDpr, entityTxt);
/** @type {{published: Help4.widget.help.PUBLISHED_STATUS, playbackTag: Help4.widget.help.PUBLISHED_STATUS}} */
entityTxt.info = _getPublishedTagInfo(project, tagInfo, config);
return {contextType, entityTxt, lessonJs};
}
return null;
}
/**
* @protected
* @param {Help4.widget.companionCore.SEN.PubProjectResponse} data
* @param {Help4.widget.help.ContextTypes} data.contextType
* @param {Help4.widget.help.project.SEN.EntityTxt} data.entityTxt
* @param {Help4.widget.help.project.SEN.LessonJs} data.lessonJs
* @param {Help4.typedef.SystemConfiguration} config
* @param {Help4.widget.help.CatalogueTypes} [catalogueType = 'SEN']
* @param {boolean} [isExtension = false]
* @returns {Help4.widget.help.Project}
*/
static _parse(data, config, catalogueType = 'SEN', isExtension = false) {
const {contextType, entityTxt, lessonJs} = data;
const {CtxWPB} = Help4;
/** @param {Help4.widget.help.project.SEN.EntityTxt} entityTxt */
const setDefaults = entityTxt => {
for (const [key, {t, f, /** @type {string} */ wpb}] of Object.entries(Help4.PROJECT_DEFAULTS)) {
const value = entityTxt[wpb || key];
if (value === undefined) {
entityTxt[key] = f;
continue;
}
if (t === 'json') {
try {
entityTxt[key] = Help4.JSON.parse(value);
} catch (e) {
entityTxt[key] = f;
}
} else {
entityTxt[key] = value;
}
}
}
/**
* @param {Help4.widget.help.project.SEN.LessonJsTourstop[]} tourstops
* @param {string} screen
* @param {Help4.typedef.SystemConfiguration} config
* @param {Help4.widget.help.Project} project
* @param {boolean} isExtension
* @returns {Array<Help4.widget.help.ProjectTile>}
*/
const parseTiles = (tourstops, screen, config, project, isExtension) => {
/** @type {Array<Help4.widget.help.ProjectTile>} */
const tiles = [];
for (const tourstop of tourstops) {
const {/** @type {Help4.widget.help.project.SEN.LessonJsMacroArray} */ macros} = tourstop;
/** @type {string|null} */
let pageUrl = null;
for (const macro of macros) {
switch (macro.macro_template) {
case 'define_target':
pageUrl = macro.key;
break;
case 'link_tile':
case 'help_tile': {
/** @type {Help4.widget.help.ProjectTile} */
const tile = parseTile(macro, pageUrl || screen, config, project, isExtension);
tiles.push(tile);
break;
}
case 'guide_click': {
/** @type {Help4.widget.help.ProjectTile} */
const tile = parseTile(macro, pageUrl || screen, config, project, isExtension);
if (!tile.hotspotAnchor && !tile.hotspotCentered) {
// fix for old content, that could have no hotspot but also is not centered
// this constellation is no longer possible
tile.hotspotCentered = true;
}
tiles.push(tile);
break;
}
}
}
}
return tiles;
}
/**
* @param {Help4.widget.help.project.SEN.LessonJsMacro} macro
* @param {string} pageUrl
* @param {Help4.typedef.SystemConfiguration} config
* @param {Help4.widget.help.Project} project
* @param {boolean} isExtension
* @returns {Help4.widget.help.ProjectTile}
*/
const parseTile = (macro, pageUrl, config, project, isExtension) => {
CtxWPB.initContext(true);
CtxWPB.setScope('macro');
CtxWPB.setContext('macro', macro.uid);
const {/** @type {Help4.widget.help.CatalogueTypes} */ _catalogueType, _dataType, language} = project;
const isExtensionOverlay = _catalogueType === 'SEN2';
const isEXT = config.help.serviceLayer === Help4.SERVICE_LAYER.ext;
/** @type {Help4.widget.help.ProjectTile} */
const tile = {
_ctx: CtxWPB.get(),
_mac: macro,
pageUrl,
language,
_projectId: project.id,
_catalogueType: _catalogueType,
_dataType: _dataType
};
for (const [key, {t, f, /** @type {string} */ wpb, /** @type {string} */ wpbGet}] of Object.entries(Help4.DEFAULTS)) {
let value = macro[wpb || key];
if (t === 'json' && value) {
try {
value = Help4.JSON.parse(value);
} catch (e) {
value = f;
}
}
const transform = TRANSFORM[wpbGet];
if (transform) value = transform(value, {macro, isExtension});
value = clampTileValue(value, {t, f}, isExtension);
if (value !== undefined) tile[key] = value;
}
// in EXT mode, add standard url marker to urls for non-extended RO only tiles;
// needed to choose resolving url (CtxWPB) with either RO or RW service url
if (isEXT && !isExtensionOverlay) Help4.control.input.HtmlEditor.addStandardUrlMarker(tile);
if (tile.loio) {
tile._standalone = false;
if (!isExtensionOverlay) tile._reference = true;
} else {
tile._standalone = true;
tile.loio = tile.id;
// XRAY-3291 existing tiles created in extensibility mode does not have showTitleBar value
if (isExtension && tile.showTitleBar == null) tile.showTitleBar = Help4.DEFAULTS.showTitleBar.f;
}
CtxWPB.destroyContext();
return tile;
}
/**
* @param {*} value
* @param {Object} defaults
* @param {string} defaults.t
* @param {*} defaults.f
* @param {boolean} isExtension
* @returns {*}
*/
const clampTileValue = (value, {t, f}, isExtension) => {
if (isExtension) {
return value != null && (t === 'boolean' || t === 'array')
? Help4.clampValue(value, t, f)
: value;
}
return Help4.clampValue(value, t, f);
}
const {tclass, uid, context_id, info} = entityTxt;
CtxWPB.initContext();
CtxWPB.setScope(tclass);
CtxWPB.setContext(tclass, uid);
entityTxt.h4_loio ||= uid;
entityTxt.alias = entityTxt.id = uid;
entityTxt.contextType = contextType;
entityTxt.screen = (context_id || '').replace(/xRay=/, '');
entityTxt.published = info?.published;
entityTxt.playbackTag = info?.playbackTag;
setDefaults(entityTxt);
const {ACCEPTED_ATTRIBUTES} = Help4.widget.help.Data;
/** @type {Help4.widget.help.Project} */
const project = {};
for (const [key, value] of Object.entries(entityTxt)) {
const name = ATTRIBUTES_MAPPING[key] || key;
if (ACCEPTED_ATTRIBUTES.includes(name)) {
project[name] = value;
}
}
project._catalogueType = catalogueType;
project._dataType = 'SEN';
/** @type {Help4.widget.help.project.SEN.LessonJsTourstop[]} */
const {tourstops} = lessonJs;
/** @type {Help4.widget.help.ProjectTile[]} */
project.tiles = parseTiles(tourstops, entityTxt.screen, config, project, isExtension);
CtxWPB.destroyContext();
return project;
}
}
/**
* @memberof Help4.widget.help.project.SEN
* @private
* @param {Help4.widget.help.project.SEN.Project} project
* @param {Help4.widget.help.project.SEN.TagInfoList} tagInfo
* @param {Help4.typedef.SystemConfiguration} config
* @returns {{published: Help4.widget.help.PUBLISHED_STATUS, playbackTag: Help4.widget.help.PUBLISHED_STATUS}}
*/
function _getPublishedTagInfo(project, tagInfo, config) {
const {/** @type {Help4.widget.help.project.SEN.ProjectTags} */ Tags = []} = project;
const {PUBLISHED_STATUS} = Help4.widget.help;
/**
* @param {string} tagName
* @returns {Help4.widget.help.PUBLISHED_STATUS}
*/
const get = tagName => {
/**
* @param {Help4.widget.help.project.SEN.ProjectTag} tag
* @returns {boolean}
*/
const find = tag => tag.tag === tagName;
/** @type {Help4.widget.help.project.SEN.ProjectTag|undefined} */
const tag = Tags.find(find);
return tag
? tag.version === project.maxversion ? PUBLISHED_STATUS.published : PUBLISHED_STATUS.updated
: PUBLISHED_STATUS.new;
}
/** @type {Help4.widget.help.PUBLISHED_STATUS} */
const published = get('published');
let {/** @type {string|Help4.widget.help.PUBLISHED_STATUS} */ playbackTag} = config.core;
if (playbackTag && playbackTag !== 'published') {
const valid = !!tagInfo.find(({id}) => id === playbackTag);
if (valid) {
/** @type {Help4.widget.help.PUBLISHED_STATUS} */
playbackTag = get(playbackTag);
return {published, playbackTag};
}
}
return {published, playbackTag: PUBLISHED_STATUS.invalid};
}
})();