(function () {
/**
* @typedef {Help4.widget.help.ProjectTile} Help4.widget.help.ConditionProjectTile
* @property {boolean} _conditionResult
*/
/**
* @typedef {Object} Help4.widget.help.TileParams
* @property {Help4.widget.help.ProjectTile} _tile
* @property {Object} [_data]
* @property {Object} _metadata
* @property {Help4.widget.help.view2.Tile.Descriptor} _metadata.descriptor
* @property {'tile'} _metadata.type
* @property {string} _metadata.originalDescription
* @property {boolean} _metadata.isGlobalHelp
* @property {string} controlType
* @property {?string} icon
* @property {boolean} isGlobalHelp
* @property {boolean} showAsButton
* @property {boolean} linkLightbox
* @property {boolean} hidden
* @property {string} caption
* @property {string} description
* @property {string} title
* @property {string} type
* @property {string} css
* @property {boolean} active
* @property {boolean} visible
* @property {string} contentLanguage
*/
const PRIORITIES = {
GlobalHelp: 0,
QuickTour: 1,
WNTours: 2, // What's New Tours
WTA_UACP_SEN: 3, // WTA UACP/SEN (what's this app)
WTA_UR: 4, // WTA UR Harmonization
UACP_SEN: 5,
UR: 6,
API: 7
}
/** help functionality */
Help4.widget.companionCore.data.Help = class {
/**
* deliver all tiles that are not permanently invisible
* @param {Object} params
* @param {boolean} params.whatsnew
* @param {string} params.screenId
* @returns {Promise<Help4.widget.help.ProjectTile[]>}
*/
static async getAvailableTiles({whatsnew, screenId}) {
const {widget} = _getWidget(whatsnew);
const {
/** @type {Help4.typedef.SystemConfiguration} */ configuration,
widget: {help: {
/** @type {Help4.widget.help.Data|Help4.widget.whatsnew.Data} */
data
}}
} = widget.getContext();
await data.waitCataloguesLoaded(screenId);
await data.waitHelpLoaded();
const {core: {isEditorView}} = configuration;
const tiles = await data.getHelpTiles();
return tiles
// remove all tiles that will never appear
.map((tile) => {
// do not show announcement tiles in PUB mode when they are hidden, but keep them within tiles
// to be able to show the announcement
const {type, linkLightbox, linkTo, splash, hidden} = tile;
if (!isEditorView && type === 'link' && !!linkLightbox && !!linkTo && !!splash && !!hidden) {
tile._hiddenAnnouncement = true;
}
return tile;
})
.filter(tile => _filterTilePermanent(tile, configuration))
// split into categories by priority
.reduce((a, tile) => {
const priority = _tileToPriority(tile);
a[priority] ||= [];
a[priority].push(tile);
return a;
}, [])
// sort categories
.map((category, index) => {
// sort WN tours alphabetically
if (index === PRIORITIES.WNTours) {
category.sort(({title: t1}, {title: t2}) => {
t1 = t1.toLowerCase();
t2 = t2.toLowerCase();
return t1 < t2 ? -1 : (t1 > t2 ? 1 : 0);
});
}
return category;
})
// convert back into one large array
.reduce((a, category) => {
category?.length > 0 && a.push(...category);
return a;
}, []);
}
/**
* extend tiles with a condition result
* @param {Help4.widget.help.ProjectTile[]} tiles
* @param {Object} params
* @param {boolean} params.whatsnew
* @returns {Promise<Help4.widget.help.ConditionProjectTile[]>}
*/
static async evaluateTileConditions(tiles, {whatsnew}) {
const {widget} = _getWidget(whatsnew);
const {
/** @type {Help4.service.ConditionService} */ conditionService
} = widget.getContext().service;
/** @type {Array<Promise<boolean>>} */
const promises = [];
for (const tile of tiles) {
const {conditions, hotspotAnchor, _dataType} = tile;
const promise = /** @type {Promise<boolean>|boolean} */ conditions?.length && Help4.includes(['UACP', 'SEN'], _dataType)
? conditionService.checkConditions({conditions, hotspotAnchor})
: true;
promises.push(promise);
}
const results = await Help4.Promise.all(promises);
return results.map(
/**
* @param {boolean} success
* @param {number} index
* @returns {Help4.widget.help.ConditionProjectTile}
*/
(success, index) => {
/** @type {Help4.widget.help.ConditionProjectTile} */
const clone = Help4.cloneObject(tiles[index]);
clone._conditionResult = success;
return clone;
}
);
}
/**
* filter tiles by condition and deliver a tile descriptor result
* @param {Help4.widget.help.ProjectTile[]} tiles
* @param {Object} params
* @param {boolean} params.whatsnew
* @returns {Promise<Help4.widget.help.ProjectTile[]>}
*/
static async filterTilesByCondition(tiles, {whatsnew}) {
return (await this.evaluateTileConditions(tiles, {whatsnew})) // evaluate conditions
.filter(({_conditionResult}) => !!_conditionResult) // remove all that failed
.map(tile => { // remove _conditionResult
delete tile._conditionResult;
return tile;
});
}
/**
* UrHarmonization uses IdSelectorUI5 in UI5 apps and UrHarmonizationSelector in non-UI5 apps
* if the selector is UR return it, otherwise mark it for migration
* @param {?string} hotspotAnchor
* @param {boolean} [returnOther = false]
* @param {boolean} [returnToMigrate = false]
* @returns {{ur: value:string} | {other: {rule:string, value:string}} | {migrate: {rule:string, value:string}} | null}
*/
static decodeUrHotspot(hotspotAnchor, returnOther = false, returnToMigrate = false) {
if (hotspotAnchor) {
const {Selector} = Help4.selector;
const {rule, value, iframe} = Selector.base64ToUtf8(hotspotAnchor) || {};
if (!iframe) return returnOther ? {other: {rule, value}} : null;
const {rule: fRule, value: fValue} = iframe || {};
if (fRule === 'UrHarmonizationSelector') return {ur: fValue};
else if (returnToMigrate) return {migrate: {rule: fRule, value: fValue}};
}
return null;
}
/**
* filters out UR catalogue tiles if assigned tiles with the same hotspotId exist
* @param {Help4.widget.help.ProjectTile[]} tiles
* @returns {Help4.widget.help.ProjectTile[]}
*/
static filterUrTiles(tiles) {
const {
engine: {ur: {
Connection: {APP_TYPE},
UrHarmonizationEngine: {QUERIES: {UI5}}, Selector}
},
selector,
widget: {help: {catalogues: {UR: {CATALOGUE_TYPE: UR_CATALOGUE_TYPE}}}}
} = Help4;
selector.window = window; /** set for getElement method in {@link Help4.engine.ur.Selector.isAssignedInUI5} */
const urEngine = Help4.getController().getEngine('urHarmonization');
const isUI5 = urEngine._currentApp === APP_TYPE.UI5;
const assignedUrIds = new Set();
const assignedUI5Hotspots = new Set();
let hasWta = false; // non-UR What's This App? tiles overwrite UR tiles
return tiles.filter(tile => {
const {
_apiData,
_catalogueType,
_special,
hotspotAnchor
} = tile;
/** all tiles are already sorted; see {@link getAvailableTiles} and {@link _tileToPriority} */
if (_special === 'wta') {
// remove UR WTA if non-UR WTA exists; non-UR WTA tile always comes first due to sorting
return _catalogueType === UR_CATALOGUE_TYPE && hasWta ? false : hasWta = true;
} else if (_catalogueType === UR_CATALOGUE_TYPE) {
// UR: filter all hotspots that exist in UACP/SEN
// special handling for UI5 hotspots in UR
const {_type, hotspotId} = _apiData;
return _type === UI5
? !Selector.isAssignedInUI5(hotspotId, assignedUI5Hotspots)
: !assignedUrIds.has(_apiData.hotspotId);
}
if (hotspotAnchor) {
// UACP/SEN: collect all assigned and migrated hotspots with UR hotspotId
const {
ur,
other,
migrate
} = this.decodeUrHotspot(hotspotAnchor, isUI5, true) || {};
if (ur) {
assignedUrIds.add(ur);
} else if (other) {
assignedUI5Hotspots.add(other);
} else if (migrate) {
// check if selector was migrated to UrHarmonizationSelector
// if no, add it to the migration map
// if yes, keep track of the hotspotId to hide the relevant backend tile
const {hotspotId, position} = urEngine.getMigratedHotspot(migrate) || {};
if (!hotspotId && !position) urEngine.addSelectorToMigrate(migrate);
else if (hotspotId) assignedUrIds.add(hotspotId);
}
}
return true;
});
}
/**
* transforms a project tile data into control tile data
* @param {Help4.widget.help.ProjectTile} tile
* @param {Object} params
* @param {Help4.widget.help.view2.Tile.Descriptor} descriptor
* @param {boolean} params.hotspotVisible
* @returns {Help4.widget.help.TileParams}
*/
static helpTileToTileParams(tile, {descriptor, hotspotVisible}) {
const {
Placeholder,
Localization,
widget: {help: {
view2: {BUTTON_CSS, LIGHTBOX_CSS, HIDDEN_CSS, GLOBAL_HELP_CSS},
catalogues: {GlobalHelp: {CATALOGUE_TYPE: GlobalHelp_CATALOGUE_TYPE}}
}},
shell: {UI5}
} = Help4;
const {
title: tileCaption,
summaryText: tileDescription,
published,
type,
linkTo,
linkLightbox,
showAsButton,
hidden,
tileIcon: icon,
language,
content,
id,
_projectId: projectId,
_catalogueKey,
_catalogueType: catalogueType,
_dataType: dataType,
_hiddenAnnouncement
} = tile;
const isGlobalHelp = catalogueType === GlobalHelp_CATALOGUE_TYPE;
const caption = Placeholder.resolve(tileCaption);
const description = /** @type {string} */ UI5.hyphenate(Placeholder.resolve(tileDescription));
let title = '';
let state = undefined;
const css = [];
hidden && css.push(HIDDEN_CSS);
let visible = true;
switch (type) {
case 'help':
css.push('help-tile');
// visibility based on hotspot visibility
visible = hotspotVisible;
// GlobalHelp handling
isGlobalHelp && css.push(GLOBAL_HELP_CSS, BUTTON_CSS);
break;
case 'link':
css.push('link-tile');
linkLightbox && css.push(LIGHTBOX_CSS);
showAsButton && css.push(BUTTON_CSS);
// only show, if content or link exists
visible &&= !!content || !!linkTo;
break;
case 'tour':
css.push('tour-project');
if (published && _catalogueKey === 'head' && dataType === 'SEN') {
const tooltip = Localization.getText(`tooltip.tourstatus.${published}`);
title = `${caption}: ${tooltip}`;
state = published;
} else {
title = caption;
}
break;
}
if (_hiddenAnnouncement) visible = false;
/** @type {Help4.widget.help.TileParams} */
return {
_tile: tile,
_metadata: {
descriptor,
type: 'tile',
originalDescription: description,
isGlobalHelp,
tileId: id
},
_data: {state},
controlType: 'Help4.control2.Tile',
icon: showAsButton ? null : icon,
isGlobalHelp,
showAsButton,
linkLightbox,
hidden,
caption,
description,
title,
type,
css: css.join(' '),
active: false,
visible,
contentLanguage: language
}
}
}
/**
* get widget
* @memberof Help4.widget.companionCore.data.Tour
* @private
* @param {boolean} whatsnew
* @returns {{widgetName: string, widget: Help4.widget.whatsnew.Widget|Help4.widget.help.Widget}}
*/
function _getWidget(whatsnew) {
const widgetName = whatsnew ? 'whatsnew' : 'help';
return {
widgetName,
widget: /** @type {Help4.widget.whatsnew.Widget|Help4.widget.help.Widget} */ Help4.widget.getInstance(widgetName)
}
}
/**
* filter tiles that will never appear; make sure to maintain order!
* @memberof Help4.widget.companionCore.data.Help
* @private
* @param {Help4.widget.help.ProjectTile} tile
* @param {Help4.typedef.SystemConfiguration} configuration
* @returns {boolean}
*/
function _filterTilePermanent(tile, {core: {isEditorView}}) {
// do not show references or broken tiles
// do not show hidden tiles outside of editor view
const {id, hidden, _reference, _hiddenAnnouncement} = tile;
return id && !_reference && (isEditorView || !hidden || _hiddenAnnouncement);
}
/**
* prioritizes catalogueType and What's This App?
* @memberof Help4.widget.companionCore.data.Help
* @private
* @param {Help4.widget.help.ProjectTile} tile
* @returns {number} - smaller is higher priority
*/
function _tileToPriority({_catalogueType, _special, _projectId}) {
const {
WNTours: {ID: WNTours_ID},
GlobalHelp: {CATALOGUE_TYPE: GlobalHelp_CATALOGUE_TYPE},
QuickTour: {CATALOGUE_TYPE: QuickTour_CATALOGUE_TYPE},
API: {CATALOGUE_TYPE: API_CATALOGUE_TYPE},
UR: {CATALOGUE_TYPE: UR_CATALOGUE_TYPE}
} = Help4.widget.help.catalogues;
if (_catalogueType === GlobalHelp_CATALOGUE_TYPE) return PRIORITIES.GlobalHelp;
if (_catalogueType === QuickTour_CATALOGUE_TYPE) return PRIORITIES.QuickTour;
if (_projectId === WNTours_ID) return PRIORITIES.WNTours;
if (_catalogueType === API_CATALOGUE_TYPE) return PRIORITIES.API;
return _special === 'wta' // What's this app
? _catalogueType === UR_CATALOGUE_TYPE ? PRIORITIES.WTA_UR : PRIORITIES.WTA_UACP_SEN
: _catalogueType === UR_CATALOGUE_TYPE ? PRIORITIES.UR : PRIORITIES.UACP_SEN;
}
})();