(function() {
/**
* @typedef {Object} Help4.API.LinkTileParams
* @property {Help4.control2.Lightbox.Sizing} sizing
* @property {Help4.control2.SizeWH} [size] - size is required if sizing is explicit or explicitFull
* @property {string} [contentUrl] - content or contentUrl is required
* @property {string} [content] - content or contentUrl is required
* @property {string} title
* @property {string} [summaryText]
* @property {string} [id]
*/
/**
* @typedef {Object} Help4.API.HelpTileParams
* @property {string} summaryText
* @property {string} content
* @property {string} title
* @property {string} [id]
* @property {Help4.typedef.BubbleAnimationType} [bubbleAnimationType]
* @property {Help4.control2.PositionLeftTop} [bubbleOffset]
* @property {Help4.typedef.BubbleOrientation} [bubbleOrientation]
* @property {string} [bubbleSize]
* @property {string} [hotspotAnchor]
* @property {string} [hotspotAnimationType]
* @property {boolean} [hotspotCentered]
* @property {Help4.typedef.HotspotIconPosition} [hotspotIconPos]
* @property {Help4.typedef.HotspotIconType} [hotspotIconType]
* @property {Help4.control2.PositionLeftTop} [hotspotManualOffset]
* @property {string} [hotspotSize]
* @property {string} [hotspotStyle]
* @property {Help4.control2.SizeWidthHeight} [hotspotRectDelta]
* @property {string} [hotspotTrianglePos]
* @property {boolean} [alignWithText]
* @property {boolean} [showTitleBar]
* @property {Help4.typedef.TileIcons} [tileIcon]
*/
/**
* @typedef {Object} Help4.API.OpenParams
* @property {Help4.control2.PositionXY|Help4.control2.PositionLeftTop} [position]
* @property {boolean} [showCarousel = false]
*/
/**
* @typedef {Object} Help4.API.TypeProjectInfo
* @property {Object[]} standard - standard content
* @property {Object[]} whatsnew - what's new content
*/
/**
* @typedef {Object} Help4.API.TypeProjectList
* @property {Help4.API.TypeProjectInfo} published - the published data
* @property {Help4.API.TypeProjectInfo} head - the non-published data
*/
/**
* @typedef {Object} Help4.API.RemoteControlParams
* @property {string} type
* @property {Object} [data]
* @property {Object} [params]
* @property {boolean} [visible]
*/
/** for external use; playback mode only! */
Help4.API = class {
/**
* will open/close the SAP Companion based on its current status
* @param {Help4.API.OpenParams} [params] - CMP4 only
* @returns {boolean} - API command executed successfully
*/
static toggle(params) {
return this.toggleWA(params);
}
/**
* will open the SAP Companion
* @param {Help4.API.OpenParams} [params]
* @returns {boolean} - API command executed successfully
*/
static open(params) {
return this.openWA(true, params);
}
/**
* will close the SAP Companion
* @returns {boolean} - API command executed successfully
*/
static close() {
return this.openWA(false);
}
/**
* will open/close the SAP Companion based on its current status
* @deprecated - please use Help4.API.toggle instead
* @param {Help4.API.OpenParams} [params] - CMP4 only
* @returns {boolean} - API command executed successfully
*/
static toggleWA(params) {
return this.openWA('toggle', params);
}
/**
* will open/close the SAP Companion based on a parameter
* @deprecated - please use Help4.API.open instead
* @param {boolean|'toggle'} [open = true]
* @param {Help4.API.OpenParams} [params = {}]
* @returns {boolean} - API command executed successfully
*/
static openWA(open = true, {showCarousel = false, position} = {}) {
const controller = Help4.getController();
const {isEditMode = true, isOpen, CMP4} = controller?.getConfiguration() || {};
if (isEditMode) return false;
const handleCmp4Position = () => {
if ((showCarousel || position) && controller.isMinimized()) {
controller.onMinimize(false);
}
let {x, y, top, left} = position || {};
x ??= left;
y ??= top;
if (typeof x === 'number' && typeof y === 'number') {
const handler = /** @type {Help4.controller.CMP4} */ controller.getCmp4Handler();
const {/** @type {Help4.control2.bubble.Panel} */ panel} = handler;
panel.docked = false;
panel.setPosition(x, y);
}
}
if (open === 'toggle') open = !isOpen;
if (Boolean(open)) {
isOpen || controller.toggle(() => {
CMP4
? handleCmp4Position()
: showCarousel && this.showCarousel(showCarousel);
});
} else if (isOpen) {
controller.close();
}
return true;
}
/**
* will wait until SAP Companion is ready and returns boolean
* @returns {Promise<void>} - will only return if SAP Companion is ready
*/
static waitCompanionReady() {
return new Help4.Promise(resolve => {
const checkCMP = () => {
const {STATUS: {done}} = Help4.StartStatus;
const statusService = Help4.getController()?.getService('startStatus');
statusService?.has(done)
? resolve()
: setTimeout(checkCMP, 250);
}
checkCMP();
});
}
/**
* activate the whatsnew mode if available
* @deprecated - only for internal use; will become private
* @param {boolean} enable - enable whats new mode
* @returns {boolean} - API command executed successfully
*/
static enableWhatsNew(enable) {
const controller = Help4.getController();
const {isEditMode = true, hasWhatsNew, isOpen, CMP4} = controller?.getConfiguration() || {};
if (isEditMode || !isOpen) return false;
if (CMP4) {
const instance = /** @type {?Help4.widget.whatsnew.Widget} */ Help4.widget.getInstance('whatsnew');
if (enable) {
return instance && !instance.isActive()
? void instance.activate() || true
: false;
} else {
return instance?.isActive()
? void instance.deactivate() || true
: false;
}
} else {
if (!hasWhatsNew) return false;
const h = controller.getHandler();
if (h) {
h.setCarouselTab({
tab: enable ? Help4.CAROUSEL_MODES.wn_help : Help4.CAROUSEL_MODES.help,
reason: 'API'
});
}
return !!h;
}
}
/**
* starts a specific project: help, whatsnew or tour projects are possible
* @param {string} pidOrAlias - project ID or alias
* @param {boolean|'all'} [published = false] - show only published, only not-published or all
* @returns {boolean} - API command executed successfully
*/
static startProject(pidOrAlias, published = false) {
const controller = Help4.getController();
const config = controller?.getConfiguration() || {};
const {isEditMode = true, isOpen, CMP4} = config;
if (isEditMode) return false;
let projects = Help4.API.getProjects();
if (published === 'all') {
const {head: {standard: hs, whatsnew: hw}, published: {standard: ps, whatsnew: pw}} = projects;
projects = {standard: hs.concat(ps), whatsnew: hw.concat(pw)};
} else {
projects = projects[published ? 'published' : 'head'];
}
let type = 'standard';
let project = projects.standard.find(p => p.alias === pidOrAlias || p.id === pidOrAlias);
if (!project) {
project = projects.whatsnew.find(p => p.alias === pidOrAlias || p.id === pidOrAlias);
if (!project) return false;
type = 'whatsnew';
}
const mode = project.contextType.toLowerCase();
if (mode === 'tour') {
if (CMP4) {
const {
companionCore: {Core},
tourlist: {Widget: TourlistWidget}
} = Help4.widget;
const catalogueKey = Core.getCatalogueKey({configuration: config});
const {id: projectId, _catalogueType: catalogueType, _dataType: dataType} = project;
TourlistWidget.startTour(config, {projectId, catalogueKey, catalogueType, dataType, whatsnew: type === 'whatsnew'});
return true;
} else {
const params = {dataId: project.id, isWhatsNew: type === 'whatsnew', forceHandler: true};
const handler = controller.getHandler();
if (!isOpen || handler?.isMinimized()) params.autoTour = 'API';
handler?.close(); // XRAY-5134 - allows to start new tour project while an existing tour is being played
controller.changeHandler(Help4.controller.MODES[mode], params);
return true;
}
}
// will be help or whatsnew
this.enableWhatsNew(type === 'whatsnew');
return true;
}
/**
* selects a help tile within the current help project
* will open the appropriate bubble and highlight the tile within carousel
* @deprecated - will be removed
* @param {string} tileId - tile ID
* @returns {boolean} - API command executed successfully
*/
static selectHelpTile(tileId) {
const controller = Help4.getController();
const config = controller?.getConfiguration() || {};
const {isEditMode = true, isHelpMode, isOpen, CMP4} = config;
if (isEditMode || !isHelpMode || !isOpen) return false;
if (CMP4) {
const instance = /** @type {?Help4.widget.Widget} */ Help4.widget.getActiveInstance();
const name = instance?.getName();
if (name !== 'help' && name !== 'whatsnew') return false;
const tile = _getTileCMP4(instance, tileId);
if (!tile) return false;
controller.getPanel().minimized = false;
(/** @type {Help4.widget.help.Widget|Help4.widget.whatsnew.Widget} */ instance).selectTile(tile);
} else {
const h = controller.getHandler();
let t = h && h.getCarouselTab();
if (t !== Help4.CAROUSEL_MODES.help && t !== Help4.CAROUSEL_MODES.wn_help) return false;
if (!(t = _getTileCMP3(tileId))) return false;
h.minimize(false);
t = t.getMetadata('tileId');
controller.getEngine('carouselSelection').selectTile(t);
}
return true;
}
/**
* shows the help bubble for a specific tile within the current help project
* will not update the carousel
* if in remote mode, shows help bubble; API version 2.0
* @deprecated - only to be used for DA/WA; will become private
* @param {string} param - tile ID
* @returns {boolean} - API command executed successfully
*/
// @param {Object} param - tile describing object, only for remote mode
static showHelpBubble(param) {
const c = Help4.getController();
// send to remoteController if remoteControl engine is started
const rc = c?.getEngine('remoteControl');
const po = typeof param === 'object';
if (rc?.isStarted() && po) return _callRemoteControlEngine({type: 'showHelpBubble', data: param});
const {isEditMode = true, isHelpMode, isOpen, CMP4} = c?.getConfiguration() || {};
if (isEditMode || !isHelpMode || !isOpen || po) return false;
if (CMP4) {
return this.selectHelpTile(param);
} else {
const tile = _getTileCMP3(param);
if (tile && tile.getMetadata('tileType') === 'help') {
const h = c.getHandler();
h.clean('+bu');
// override auto hide mechanics of help bubbles in case they have no connection point
h.getBubbles().openHelp(tile.getMetadata('tileId'), {isAPI: true});
return true;
}
}
return false;
}
/**
* selects a tour step with a current tour project
* @param {number|string} sid - step number or step ID
* @returns {Promise<boolean>} - API command executed successfully
*/
static async selectTourStep(sid) {
const controller = Help4.getController();
const {isEditMode = true, isTourMode, isOpen, CMP4} = controller?.getConfiguration() || {};
if (isEditMode || !isOpen) return false;
if (CMP4) {
const instance = /** @type {?Help4.widget.Widget} */ Help4.widget.getActiveInstance();
if (instance?.getName() !== 'tour') return false;
const {widget: {tour: {
/** @type {?Help4.widget.tour.View} */ view,
}}} = /** @type {Help4.widget.tour.Widget.Context} */ instance.getContext();
if (typeof sid !== 'number') sid = view.getTileIndex(sid);
return await view.showStepAt(sid);
} else {
if (!isTourMode) return false;
controller.getHandler().setStep(sid);
return true;
}
}
/**
* selects the next tour step with a current tour project
* @returns {Promise<boolean>} - API command executed successfully
*/
static async tourNextStep() {
const controller = Help4.getController();
const {isEditMode = true, isTourMode, isOpen, CMP4} = controller?.getConfiguration() || {};
if (isEditMode || !isOpen) return false;
if (CMP4) {
const instance = /** @type {?Help4.widget.Widget} */ Help4.widget.getActiveInstance();
if (instance?.getName() !== 'tour') return false;
const {widget: {tour: {
/** @type {?Help4.widget.tour.View} */ view,
}}} = /** @type {Help4.widget.tour.Widget.Context} */ instance.getContext();
return await view.nextStep();
} else {
if (!isTourMode) return false;
return controller.getHandler().nextStep();
}
}
/**
* selects the previous tour step with a current tour project
* @returns {Promise<boolean>} - API command executed successfully
*/
static async tourPrevStep() {
const controller = Help4.getController();
const {isEditMode = true, isTourMode, isOpen = false, CMP4} = controller?.getConfiguration() || {};
if (isEditMode || !isOpen) return false;
if (CMP4) {
const instance = /** @type {?Help4.widget.Widget} */ Help4.widget.getActiveInstance();
if (instance?.getName() !== 'tour') return false;
const {widget: {tour: {
/** @type {?Help4.widget.tour.View} */ view,
}}} = /** @type {Help4.widget.tour.Widget.Context} */ instance.getContext();
return await view.prevStep();
} else {
if (!isTourMode) return false;
return controller.getHandler().prevStep();
}
}
/**
* will open the learning app
* @param {boolean} external - open the panel learning tab or the external learning app
*/
static openLearningApp(external) {
const controller = Help4.getController();
const {isEditMode = true, hasLearningApp, isOpen, CMP4} = controller?.getConfiguration() || {};
if (isEditMode) return;
const f = async () => {
if (CMP4) {
// problem: when tour is playing, on deactivate either tourlist or whatsnew widgets
// will auto-activate; so we deactivate tour widget manually, wait some time
// for tourlist/whatsnew to activate and activate learning afterwards
const instance = /** @type {?Help4.widget.Widget} */ Help4.widget.getActiveInstance();
if (instance?.getName() === 'tour') await instance.deactivate();
// in tourlist mode wait for the tourlist to activate
setTimeout(() => {
const widget = /** @type {?Help4.widget.learning.Widget} */ Help4.widget.getInstance('learning');
widget?.openLearningCenter(external);
}, 100);
} else {
const handler = controller.getHandler();
if (external) {
handler?.openLearningApp();
} else if (hasLearningApp) {
handler?.setCarouselTab({tab: Help4.CAROUSEL_MODES.learning, reason: 'API'});
}
}
};
isOpen ? f() : controller.open(f);
}
/**
* will change the current theme
* @param {string} theme - e.g. default, hcb, light
* @returns {boolean} - API command executed successfully
*/
static setTheme(theme) {
const shell = Help4.getShell();
shell?.setTheme(Help4.THEMES[theme]);
return !!shell;
}
/**
* set the current screen id / app name; alias for setScreen
* @param {string} screenId - appName or screenId
* @returns {boolean} - API command executed successfully
*/
static setAppName(screenId) {
const shell = Help4.getShell();
shell?.setAppName(screenId);
return !!shell;
}
/**
* set the current screen id / app name; alias for Help4.API.setAppName
* @param {string} screenId - appName or screenId
* @returns {boolean} - API command executed successfully
*/
static setScreen(screenId) {
return this.setAppName(screenId);
}
/**
* sets or unsets a subscreen id
* @param {string|null} subscreenId - subscreen ID, null for removing
* @returns {boolean} - API command executed successfully
*/
static setSubScreen(subscreenId) {
const shell = Help4.getShell();
if (!shell) return false;
let sid = (shell.getScreenId() || '').split(':');
sid = sid[0] + (subscreenId ? ':' + subscreenId : '');
shell.setAppName(sid);
return true;
}
/**
* delivers the SAP Companion configuration
* @param {?string} [key = null] - a key to access a certain detail
* @returns {Help4.typedef.SystemConfiguration|null|*} - configuration object or value for key
*/
static getContext(key = null) {
let controller = Help4.getController();
if (!controller) return null;
const config = controller.getConfiguration();
return typeof key === 'string' ? config[key] : config;
}
/**
* delivers a list of all available projects for the current screen
* @returns {?Help4.API.TypeProjectList}
*/
static getProjects() {
const controller = Help4.getController();
const {CMP4} = controller?.getConfiguration() || {};
if (CMP4) {
const widget = /** @type {?Help4.widget.help.Widget} */ Help4.widget.getInstance('help');
const {/** @type {?Help4.widget.help.Data} */ data} = widget?.getContext().widget.help || {};
const {/** @type {?Help4.widget.help.Catalogues} */ catalogues} = data || {};
const filterData = (whatsNew, published) => {
/** @type {Help4.widget.help.CatalogueProject[]} */
const projects = catalogues?.[published ? 'pub' : 'head'].projects || [];
return projects.filter(({_whatsnew}) => whatsNew ? !!_whatsnew : !_whatsnew);
}
return {
published: {standard: filterData(false, true), whatsnew: filterData(true, true)},
head: {standard: filterData(false, false), whatsnew: filterData(true, false)}
}
} else {
const m = controller?.getService('model');
if (!m) return null;
const d = {1: {1: [], 0: []}, 0: {1: [], 0: []}};
const a = [{p: 0, w: 0}, {p: 0, w: 1}, {p: 1, w: 0}, {p: 1, w: 1}];
for (let i = 0, p, h, t, c; c = a[i++];) {
p = {published: Boolean(c.p), whatsNew: Boolean(c.w)};
h = m.getScreenHelp(p);
t = m.getScreenTours(p);
if (h) d[c.p][c.w].push(h);
if (t.length) d[c.p][c.w] = d[c.p][c.w].concat(t);
}
return {
published: {standard: d[1][0], whatsnew: d[1][1]},
head: {standard: d[0][0], whatsnew: d[0][1]}
};
}
}
/**
* sets the value for profile parameter in UACP
* @param {string} profile - the feature profile name
* @returns {boolean} - API command executed successfully
*/
static setFeatureProfileUACP(profile) {
return Help4.getController()?.setFeatureProfileUACP(profile) || false;
}
/**
* gets the value for profile parameter in UACP
* @returns {?string} - the feature profile name
*/
static getFeatureProfileUACP() {
return Help4.getController()?.getConfiguration().featureProfileUACP || null;
}
/**
* sets the key and value for placeholders
* @param {string|Object} key - string or {"key1": "value1", "key2": "value", ...}
* @param {string} [value]
*/
static setPlaceholder(key, value) {
Help4.Placeholder.add(key, value);
}
/**
* sets SAP Companion language
* @param {string} language
* @returns {Promise<boolean>} - API command executed successfully
*/
static async setLanguage(language) {
const shell = Help4.getShell();
return shell ? await shell.setLanguage(language) : false;
}
/**
* passes a condition; these conditions will be used in evaluating availability of tours/helps/tour steps
* @param {string} condition - condition name
* @param {string} value
* @returns {boolean} - API command executed successfully, undefined if controller is not ready and CMP needs to wait
*/
static async setCondition(condition, value) {
return (await _getConditionService()).setCondition(condition, value);
}
/**
* passes set of conditions (as an object)
* @param {Object[]} conditions - [{<name:string>: <value:string>}, ... ]
* @returns {boolean} - API command executed successfully, undefined if controller is not ready and CMP needs to wait
*/
static async setConditions(conditions) {
return (await _getConditionService()).setConditions(conditions);
}
/**
* removes a condition key
* @param {string} condition
* @returns {boolean} - API command executed successfully, undefined if controller is not ready and CMP needs to wait
*/
static async removeCondition(condition) {
return (await _getConditionService()).removeCondition(condition);
}
/**
* delivers list of set conditions
* @returns {?Object[]}
*/
static async getConditions() {
return (await _getConditionService()).getConditions();
}
/**
* shows lightbox; not for remote mode due to promise return
* @param {Object} data
* @param {Help4.control2.Lightbox.Sizing} data.sizing
* @param {Help4.control2.SizeWH} [data.size] - size is required if sizing is explicit or explicitFull
* @param {string} [data.contentUrl] - content or contentUrl is required
* @param {string} [data.content] - content or contentUrl is required
* @param {boolean} data.showDoNotShowAgain - id is required
* @param {string} [data.id] - id is required when showDoNotShowAgain is given
* @returns {Promise<boolean>} - API command executed successfully
*/
static async openLightbox({sizing, size, contentUrl, content, showDoNotShowAgain, id}) {
const controller = Help4.getController();
const handler = controller?.getHandler();
const {isEditMode = true, isOpen, CMP4} = controller?.getConfiguration() || {};
if (isEditMode || (!CMP4 && !handler)) return false;
const lightboxService = controller.getService(CMP4 ? 'lightbox4' : 'lightbox');
// return false if a lightbox is already open
// # is forbidden
if (lightboxService.get() || id && id.includes('#')) return false;
const storageService = controller.getService('storage');
let savedDoNotShow;
if (id && showDoNotShowAgain) {
savedDoNotShow = await storageService.get(Help4.SERIALIZE_LB_API_KEY) || '';
// marked as do not show again before by user
if (savedDoNotShow.includes(id)) return false;
}
const onEventBusEvent = async ({type, value, control}) => {
const lbApiId = control.getMetadata('apiId');
if (lbApiId && type === 'lightboxCheckbox') {
if (value) {
storageService.set(Help4.SERIALIZE_LB_API_KEY, `${savedDoNotShow}#${lbApiId}`);
} else {
savedDoNotShow = (await storageService.get(Help4.SERIALIZE_LB_API_KEY))?.replace(`#${lbApiId}`, '') || '';
storageService.del(Help4.SERIALIZE_LB_API_KEY);
if (savedDoNotShow) storageService.set(Help4.SERIALIZE_LB_API_KEY, savedDoNotShow);
}
}
}
if (contentUrl) content = null;
const {full, client, explicit, explicitFull} = Help4.LIGHTBOX_SIZES;
if (!isOpen) sizing = sizing === client ? full : (sizing === explicit ? explicitFull : sizing);
if (CMP4) {
const panel = controller.getPanel();
const fullArea = {x: 0, y: 0, w: window.innerWidth, h: window.innerHeight};
const clientArea = panel.docked
? Help4.reduceRect(fullArea, panel.getArea())
: fullArea;
lightboxService.add({
_metadata: {
apiId: id,
announcement: true,
},
fullCover: true,
sizing,
clientArea,
size,
url: contentUrl,
content,
showDetach: !content,
checkboxText: id && showDoNotShowAgain ? Help4.Localization.getText('label.doNotShowAgain') : '',
})
.addListener('lightboxCheckbox', ({type, target, active}) => onEventBusEvent({type, control: target[0], value: active}));
} else {
const eventBus = controller.getService('eventBus');
const observer = new Help4.observer.EventBusObserver(onEventBusEvent).observe(eventBus, {type: eventBus.TYPES.lightboxCheckbox});
lightboxService.add({
_metadata: {
apiId: id,
announcement: true
},
fullCover: true,
sizing,
clientArea: handler.getContentArea(),
size,
contentUrl,
content,
showDetach: !content,
checkboxText: id && showDoNotShowAgain ? Help4.Localization.getText('label.doNotShowAgain') : '',
onclose: () => void observer.disconnect()
}, 'control');
}
return true;
}
/**
* adds a link tile to the carousel
* @param {Help4.API.LinkTileParams[]} tileParams
* @returns {Promise<boolean>} - API command executed successfully
*/
static async addLinkTile(tileParams) {
const controller = Help4.getController();
const {isEditMode = true, CMP4} = controller?.getConfiguration() || {};
if (isEditMode || !Help4.isArray(tileParams)) return false;
const {API} = Help4.widget.help.project;
const existingTiles = CMP4
? API.getData()
: controller.getAPILinkTiles();
const existingIds = new Set(existingTiles.map(({id}) => id));
const newTiles = [];
for (const {id, title, summaryText, contentUrl, content, sizing, size} of tileParams) {
if (existingIds.has(id)) continue; // check if same id has been added before
existingIds.add(id);
newTiles.push({
type: 'link',
linkLightbox: true,
id: id || Help4.createId(),
title,
summaryText,
content: contentUrl ? null : content,
linkTo: contentUrl,
lightboxSizing: sizing,
lightboxSize: size ? {width: size.w, height: size.h} : null,
showDetach: !content
});
}
if (!newTiles.length) return false;
CMP4
? await API.addData(newTiles)
: controller.addAPILinkTiles(newTiles);
return true;
}
/**
* adds a help tile to the carousel
* @param {Help4.API.HelpTileParams[]} tileParams
* @returns {Promise<boolean>} - API command executed successfully
*/
static async addHelpTile(tileParams) {
const controller = Help4.getController();
const {isEditMode = true, CMP4} = controller?.getConfiguration() || {};
if (!CMP4 || isEditMode || !Help4.isArray(tileParams)) return false;
const {API} = Help4.widget.help.project;
const existingTiles = API.getData();
const existingIds = new Set(existingTiles.map(({id}) => id));
const newTiles = [];
for (const tileParam of tileParams) {
const {id, content, summaryText, title, hotspotStyle = 'CIRCLE'} = tileParam;
if (existingIds.has(id) || !content || !summaryText || !title) continue; // check if same id has been added before
existingIds.add(id);
newTiles.push({
...tileParam,
type: 'help',
id: id || Help4.createId(),
hotspotStyle
});
}
if (!newTiles.length) return false;
await API.addData(newTiles);
return true;
}
/**
* registers command from target application; invoke callbacks on click of help4api link
* @param {string} command
* @param {Function} callback
* @returns {boolean} - API command executed successfully
*/
static registerCommand(command, callback) {
if (typeof callback == 'function') {
Help4.link_commands ||= {};
Help4.link_commands[command] = callback;
return true;
}
return false;
}
/**
* invoke the callback on click of help4api links
* callback will be registered by target application via Help4.API.registerCommand
* @param {string} command
*/
static runCommand(command) {
const callback = Help4.link_commands?.[command];
callback && setTimeout(callback, 1);
}
/**
* XRAY-5098, will add support for Fiori Spaces data-help-id migration
* @return {boolean}
*/
static migrateSpacesIds() {
(new Help4.MigrateFioriSpaces).execute()
.catch(console.error);
}
/**
* sets new configuration for CMP.
* {product, version, system} parameters are supported, all are optional and minimum one is required.
* refresh help content accordingly and defaults to help playback mode.
* editing possible with new configuration, but doesn't support cross app tour editing.
* new configuration is applied for the current screen and on navigation to another screen, new configuration will be lost and CMP fallbacks to the original configuration.
* @param {Object} params
* @param {string} [params.product] - minimum one parameter is required
* @param {string} [params.version] - minimum one parameter is required
* @returns {boolean} - API command executed successfully
*/
static overrideConfig(params = {}) {
const controller = Help4.getController();
if (!controller) return false;
const {isEditMode, isTourMode, CMP4, product, version} = controller.getConfiguration();
if (isEditMode) return false;
// close tour playback
if (CMP4) {
const activeWidget = Help4.widget.getActiveInstance();
if (activeWidget && activeWidget instanceof Help4.widget.tour.Widget) activeWidget.deactivate();
} else if (isTourMode) {
controller.changeHandler(Help4.controller.MODES.help);
}
return Help4.getShell().setProductVersion(params.product || product, params.version || version);
}
/** for remote mode only */
/**
* shows, hides or toggles the carousel
* @private
* @param {boolean|'toggle'} [show = true] - shows, hides or toggles the carousel
* @returns {boolean} - API command executed successfully
*/
static showCarousel(show = true) {
const controller = Help4.getController();
const {isEditMode = true, isTourMode, isOpen, isRemoteMode, CMP4} = controller?.getConfiguration() || {};
if (isEditMode || isTourMode || !isOpen || !isRemoteMode || CMP4) return false;
const h = controller.getHandler() || controller.getEngine('remoteControl');
h?.minimize(show === 'toggle' ? !h.isMinimized() : !Boolean(show));
return !!h;
}
/**
* sets help data to external source; Remote Mode only
* @param {Object[]} data
* @returns {boolean} - API command executed successfully
* @private
*/
static setHelpObjects(data){
_callRemoteControlEngine({type: 'setHelpObjects', data});
}
/**
* sets hotspot data from external source; Remote Mode only; API version 2.0
* @param {Object[]} data
* @returns {boolean} - API command executed successfully
* @private
*/
static setHotspots(data) {
_callRemoteControlEngine({type: 'setHotspots', data});
}
/**
* updates hotspot data from external source; Remote Mode only; API version 2.0
* @param {Object[]} data
* @returns {boolean} - API command executed successfully
* @private
*/
static updateHotspots(data){
_callRemoteControlEngine({type: 'updateHotspots', data});
}
/**
* selects help object in carousel; Remote Mode only; API version 2.0
* @param {Object} data
* @returns {boolean} - API command executed successfully
* @private
*/
static selectHelpObject(data){
_callRemoteControlEngine({type: 'selectHelpObject', data});
}
/**
* sets tour data from external source; Remote Mode only
* @param {Object[]} data
* @returns {boolean} - API command executed successfully
* @private
*/
static setTours(data){
_callRemoteControlEngine({type: 'setTours', data});
}
/**
* updates help data from external source; Remote Mode only
* @param {Object[]} data
* @returns {boolean} - API command executed successfully
* @private
*/
static updateHelpObjects(data){
_callRemoteControlEngine({type: 'updateHelpObjects', data});
}
/**
* starts tour with given id; Remote Mode only
* if the first tour step given, WA will show it.
* @param {Object} params
* @returns {boolean} - API command executed successfully
* @private
*/
static startTour(params) {
_callRemoteControlEngine({type: 'startTour', params});
}
/**
* shows tour step; Remote Mode only
* @param {Object} data
* @returns {boolean} - API command executed successfully
* @private
*/
static showTourStep(data){
_callRemoteControlEngine({type: 'showTourStep', data});
}
/**
* updates visible tour step; Remote Mode only
* @param {Object} data
* @returns {boolean} - API command executed successfully
* @private
*/
static updateTourStep(data) {
_callRemoteControlEngine({type: 'updateTourStep', data});
}
/**
* stops tour with given id; Remote Mode only
* @param {Object} params
* @returns {boolean} - API command executed successfully
* @private
*/
static stopTour(params) {
_callRemoteControlEngine({type: 'stopTour', params});
}
/**
* sets learning object data from external source; Remote Mode only
* @param {Object[]} data
* @returns {boolean} - API command executed successfully
* @private
*/
static setLearningObjects(data) {
_callRemoteControlEngine({type: 'setLearningObjects', data});
}
/**
* starts learningObject with given id; Remote Mode only
* @param {string} id
* @returns {boolean} - API command executed successfully
* @private
*/
static startLearningObject(id) {
_callRemoteControlEngine({type: 'startLearningObject', id});
}
/**
* stops learningObject with given id; Remote Mode only
* @param {Object} params
* @returns {boolean} - API command executed successfully
* @private
*/
static stopLearningObject(params) {
_callRemoteControlEngine({type: 'stopLearningObject', params});
}
/**
* sets search data from external source; Remote Mode only
* @param {Object[]} data
* @returns {boolean} - API command executed successfully
* @private
*/
static setSearchResults(data) {
_callRemoteControlEngine({type: 'setSearchResults', data});
}
/**
* opens notification; Remote Mode only
* @param {Object} params
* @returns {boolean} - API command executed successfully
* @private
*/
static openNotification(params) {
_callRemoteControlEngine({type: 'openNotification', params});
}
/**
* closes notification with given id; Remote Mode only
* @returns {boolean} - API command executed successfully
* @private
*/
static closeNotification() {
_callRemoteControlEngine({type: 'closeNotification'});
}
/**
* opens dialog; Remote Mode only
* @param {Object} data
* @returns {boolean} - API command executed successfully
* @private
*/
static openDialog(data) {
_callRemoteControlEngine({type: 'openDialog', data});
}
/**
* closes dialog; Remote Mode only
* @returns {boolean} - API command executed successfully
* @private
*/
static closeDialog() {
_callRemoteControlEngine({type: 'closeDialog'});
}
/**
* set tour visibility; Remote Mode only
* @param {boolean} visible
* @returns {boolean} - API command executed successfully
* @private
*/
static setTourVisible(visible) {
_callRemoteControlEngine({type: 'setTourVisible', visible});
}
/**
* set carousel tab; Remote Mode only
* @param {Object} data
* @returns {boolean} - API command executed successfully
* @private
*/
static setTab(data) {
_callRemoteControlEngine({type: 'setTab', data});
}
/**
* shows lightbox; Remote Mode only
* @param {Object} data
* @returns {boolean} - API command executed successfully
* @private
*/
static showLightbox(data) {
_callRemoteControlEngine({type: 'showLightbox', data});
}
/*** for internal use only ***/
/**
* starts selector recording
* @private
* */
static record() {
Help4.selector.methods.Utils.record();
}
/**
* @param {?string} [managerBaseId = null]
* @param {Object} [params = {}]
* @returns {Promise<void>}
* @private
*/
static async startDebugMode(managerBaseId = null, params = {}) {
const root = document.getElementsByTagName('head')[0] || document.getElementsByName('body')[0];
if (!root) throw new Error('No DOM root!');
// prepare new configuration
const acceptedBaseIds = {
dev: 'https://dev-companion.enable-now.cloud.sap',
help4qa: 'https://help4qa-t66de1374.int.sap.eu2.hana.ondemand.com',
canary: 'https://webassistant-outlook.enable-now.cloud.sap'
};
const managerBaseUrl = acceptedBaseIds[managerBaseId] || acceptedBaseIds.dev;
const shell = Help4.getShell();
const currentConfig = shell.constructor._params;
const newConfig = Help4.extendObject(currentConfig, params);
newConfig.resourceUrl = managerBaseUrl + '/web_assistant/framework';
newConfig._isDebugMode = true;
if (params.noParameters) delete newConfig.parameters;
const qaF = (params.qaFramework || '').replace(/[^a-zA-Z_0-9]/g, '');
if (qaF) newConfig.resourceUrl += qaF;
if (shell instanceof Help4.shell.Fiori) {
newConfig._button = shell._button;
}
// shutdown
await shell.destroy();
Help4.cleanOriginalObjects();
Help4.disconnectMessages();
window.Help4 = null;
// reload
const script = document.createElement('script');
Object.assign(script, {
type: 'text/javascript',
src: newConfig.resourceUrl + '/wpb/Help4.js',
async: true,
defer: true,
onload: () => Help4.init(newConfig)
});
root.appendChild(script);
}
/**
* @param {?Object} [params = null]
* @param {boolean} [params.UI5Update]
* @param {boolean} [params.ignoreLongText]
* @param {number} [params.useFixedVersion]
* @param {Help4.engine.ur.UrHarmonizationEngine.UrTextResponse[]} [params.mockTexts]
* @private
*/
static startUrMock(params = null) {
const engine = Help4.getController().getEngine('urHarmonization');
if (params) {
if (params.UI5Update === true) {
Help4.engine.ur.Mock.startUI5(engine);
return;
}
if (typeof params.ignoreLongText === 'boolean') {
engine.ignoreLongText(params.ignoreLongText);
}
if (typeof params.useFixedVersion === 'number') {
engine.useFixedVersion(params.useFixedVersion === 2 ? 2 : 1);
}
if (Array.isArray(params.mockTexts)) {
Help4.engine.ur.Mock.MOCK_TEXTS = params.mockTexts;
}
}
engine.startMock();
}
/**
* simulate UR hotspot shift / update when no parameter is passed or
* simulate UR tour event using params
* @param {?Object} [params = null]
* @param {string} [params.messageType]
* @param {string} [params.hotspotId]
* @param {string|number} [params.key]
* @param {boolean} [params.shift]
* @param {boolean} [params.ctrl]
* @param {boolean} [params.alt]
* @private
*/
static updateUrMock(params = null) {
const engine = Help4.getController().getEngine('urHarmonization');
const {messageType, hotspotId, key, shift, ctrl, alt} = params || {};
const valid = typeof messageType === 'string' && (typeof hotspotId === 'string' || typeof key === 'string' || typeof key === 'number');
if (valid) engine.updateMock({messageType, hotspotId, key, shift, ctrl, alt});
else engine.updateMock();
}
/**
* initializes UR test
* @private
* */
static initUrTest() {
Help4.init({
editor: true,
product: 'HH_Test',
version: '2208',
type: 'fiori',
isComponent: true,
serviceLayerVersion: 'SEN',
resourceUrl: 'https://help4qa-t66de1374.int.sap.eu2.hana.ondemand.com/wa/wa/adaptable/web_assistant_framework/',
dataUrlSEN: 'https://help4qa-t66de1374.int.sap.eu2.hana.ondemand.com/wa/wa',
useABAPHelpTexts: true
});
}
}
/**
* @param {Help4.widget.help.Widget|Help4.widget.whatsnew.Widget} widget
* @param {string} tid
* @returns {?Help4.widget.help.TileDescriptor}
* @private
*/
function _getTileCMP4(widget, tid) {
const {view} = widget.getContext().widget.help;
const tiles = view?.getTiles() || [];
const tile = tiles.find(({id}) => id === tid);
if (tile) {
const {id: tileId, _projectId: projectId, _catalogueType: catalogueType} = tile;
return {tileId, projectId, catalogueType};
}
return null;
}
/**
* @param {string} tid
* @returns {?Help4.control.tile.Tile}
* @private
*/
function _getTileCMP3(tid) {
const controller = Help4.getController();
const handler = controller?.getHandler();
const carousel = handler?.getCarousel();
return carousel
? carousel.getTile({byMetadata: {tileId: tid}})
: null;
}
/**
* @param {Help4.API.RemoteControlParams} params
* @returns {boolean}
* @private
*/
function _callRemoteControlEngine(params) {
const c = Help4.getController();
const {type} = params;
const se = c.getEngine('remoteControl');
return se.isStarted() && se[type] ? se[type](params) : false;
}
/**
* @returns {Promise<Help4.service.ConditionService>}
* @private
*/
function _getConditionService() {
return new Help4.Promise(resolve => {
const getService = () => {
const service = Help4.getController()?.getService('condition');
service
? resolve(service)
: setTimeout(getService, 100);
}
getService();
});
}
})();