(function() {
/**
* @typedef {Object} Help4.widget.tour.HotspotController.ControlParams
* @property {string} animationType
* @property {Help4.control2.SizeWidthHeight} delta
* @property {string} hotspotType
* @property {string} icon
* @property {?number} number
* @property {Help4.control2.PositionXY} point
* @property {string} pos
* @property {Help4.control2.AreaXYWH} rect
* @property {string} size
* @property {boolean} spotlight
* @property {number} spotlightBlur
* @property {number} spotlightOffset
* @property {number} spotlightOpacity
* @property {?string} title
* @property {boolean} active
* @property {boolean} visible
* @property {Object} _metadata
* @property {string} _metadata.tileId
* @property {string} _metadata.hotspotId
* @property {string} _metadata.controlStyle
* @property {string} [controlType]
* @property {string} [ariaLabel]
*/
/**
* Hotspot controller for tour playback view
* @augments Help4.jscore.Base
* @property {Help4.widget.tour.View} _view
*/
Help4.widget.tour.HotspotController = class extends Help4.jscore.Base {
/**
* @override
* @param {Help4.widget.tour.View} view
*/
constructor(view) {
super({
statics: {
_view: {init: view, destroy: false}
}
});
}
/**
* get hotspot control
* @param {?string} [tileId]
* @returns {?Help4.control2.hotspot.Connected}
*/
get(tileId) {
const {Connected} = Help4.control2.hotspot;
const {/** @type {Help4.widget.tour.View} */ _view} = this;
for (/** @type {Help4.control2.Control} */ const control of _view) {
if (control instanceof Connected) {
if (!tileId || control.getMetadata('tileId') === tileId) {
return control;
}
}
}
return null;
}
/**
* remove hotspot control
* @param {?string} [tileId = null]
* @returns {?Help4.control2.hotspot.Connected}
*/
remove(tileId = null) {
const {Connected} = Help4.control2.hotspot;
const {/** @type {Help4.widget.tour.View} */ _view} = this;
for (const [index, /** @type {Help4.control2.Control} */ control] of _view.entries()) {
if (control instanceof Connected) {
if (!tileId || control.getMetadata('tileId') === tileId) {
_view.stopElementObservation();
return _view.remove(index);
}
}
}
return null;
}
/**
* create hotspot control
* @param {boolean} [isRefresh = false]
* @returns {Help4.widget.tour.HotspotController}
*/
create(isRefresh = false) {
const {/** @type {Help4.widget.tour.View} */ _view} = this;
/** @type {Help4.widget.help.ProjectTile} */
const tile = _view.getCurrentTile();
const {id, hotspotAnchor, hotspotCentered, autoProgress} = tile;
if (!hotspotAnchor || !_view.isTileOnScreen(tile)) {
// no hotspot for this step on current screen
this.remove();
return this;
}
/** @type {string} */
const existingHotspotId = this.get()?.getMetadata('tileId');
if (!isRefresh || existingHotspotId !== id) {
// new hotspot or changed hotspot
this.remove(); // remove other/old hotspot
/** @type {?Help4.data.TileData} */ const tileData = _createControl.call(this, tile);
if (!hotspotCentered && tileData) {
if (!isRefresh) _scrollIntoView.call(this, tileData.hotspot);
const map = ['click', 'mouseover', 'tab', 'enter', 'keyup', 'blur'];
if (map.find(eventName => autoProgress.includes(eventName))) {
_view.startElementObservation(tileData);
}
}
} else if (isRefresh && !hotspotCentered) {
// same hotspot; update position
_updateControl.call(this, tile);
}
return this;
}
/** @returns {Help4.widget.tour.HotspotController} */
update() {
this.create(true);
return this;
}
/**
* sets focus to hotspot element
* @param {Help4.widget.help.ProjectTile} tile
* @returns {Promise<boolean>}
*/
async focusElement(tile) {
const {hotspotAnchor} = tile;
if (!hotspotAnchor) return Help4.Promise.resolve(false); // unassigned/centered tour step. hotspot element not available
const tileData = Help4.widget.companionCore.Core.tileToTileData(tile);
const {service: {playbackService}} = this._view.getContext();
return playbackService.focusElement(tileData.hotspot);
}
}
/**
* @memberof Help4.widget.tour.HotspotController#
* @private
* @param {Help4.widget.help.ProjectTile} tile
* @returns {?Help4.data.TileData}
*/
function _createControl(tile) {
const {/** @type {Help4.widget.tour.View} */ _view} = this;
const {tileData, controlParams} = _getControlParams.call(this, tile);
if (controlParams) {
/** {@link Help4.service.container.HotspotService}; see _add */
controlParams.controlType = 'hotspot.Connected';
controlParams.ariaLabel = controlParams.title;
controlParams.title = null; // XRAY-5028 - hotspot tooltip - hide in playback
controlParams._metadata.type = 'hotspot';
// create hotspot control
_view.add(controlParams);
// deliver information
return tileData;
}
// unsuccessful
return null;
}
/**
* @memberof Help4.widget.tour.HotspotController#
* @private
* @param {Help4.widget.help.ProjectTile} tile
*/
function _updateControl(tile) {
/** @type {?Help4.control2.hotspot.Connected} */ const hotspotControl = this.get(tile.id);
if (hotspotControl) {
const {controlParams} = _getControlParams.call(this, tile);
if (controlParams) {
// recorded element is available
const {
/** @type {?Help4.control2.PositionXY} */ point,
/** @type {?Help4.control2.AreaXYWH} */ rect,
/** @type {boolean} */ visible
} = /** @type {Help4.widget.tour.View.HotspotStatus} */ controlParams;
// apply to control
hotspotControl.visible = visible;
hotspotControl.point = point;
hotspotControl.rect = rect;
} else {
// recorded element is gone
this.remove();
}
}
}
/**
* {@link Help4.engine.hotspotManager.HotspotManagerEngine.HotspotController}; see _createControl
* @memberof Help4.widget.tour.HotspotController#
* @private
* @param {Help4.widget.help.ProjectTile} tile
* @returns {{tileData: Help4.data.TileData, controlParams: ?Help4.widget.tour.HotspotController.ControlParams}}
*/
function _getControlParams(tile) {
const {/** @type {Help4.widget.tour.View} */ _view} = this;
const {/** @type {Help4.typedef.SystemConfiguration} */ configuration} = _view.getContext();
/** @type {Help4.data.TileData} */ const tileData = Help4.widget.companionCore.Core.tileToTileData(tile);
/** @type {Help4.data.HotspotData} */ const hotspotData = tileData.hotspot;
// use newest available status
hotspotData.status = _view.getCurrentStatus();
/** @type {?Help4.widget.tour.HotspotController.ControlParams} */
const controlParams = hotspotData.toControlParams(configuration);
return {tileData, controlParams};
}
/**
* {@link Help4.engine.hotspotManager.HotspotManagerEngine.prototype.scrollIntoView}
* @memberof Help4.widget.tour.HotspotController#
* @private
* @param {Help4.data.HotspotData} hotspotData
*/
function _scrollIntoView(hotspotData) {
const {/** @type {Help4.widget.tour.View} */ _view} = this;
const {
/** @type {Help4.widget.tour.View.HotspotStatus} */ status,
/** @type {string} */ hotspotAnchor
} = hotspotData;
const {visible, scrolled, topmost} = status;
if (visible && (scrolled || !topmost)) {
const {
service: {/** @type {Help4.service.recording.PlaybackService} */ playbackService},
/** @type {Help4.controller.Controller} */ controller
} = _view.getContext();
playbackService?.scrollIntoView(hotspotData);
// UR hotspot scrolling is handled by the UR engine through postMessage communication
// executing both actions in case it was a mock hotspot
const {Help} = Help4.widget.companionCore.data;
const value = Help.decodeUrHotspot(hotspotAnchor)?.ur;
if (value) {
/** @type {Help4.engine.ur.UrHarmonizationEngine} */
const urEngine = controller.getEngine('urHarmonization');
urEngine?.scrollIntoView(value);
}
}
}
})();