(function() {
/**
* Bubble controller for tour playback view
* @augments Help4.jscore.Base
* @property {Help4.widget.tour.View} _view
*/
Help4.widget.tour.BubbleController = class extends Help4.jscore.Base {
/**
* @override
* @param {Help4.widget.tour.View} view
*/
constructor(view) {
super({
statics: {
_view: {init: view, destroy: false}
}
});
}
/**
* get bubble control
* @param {?string} [tileId]
* @returns {?Help4.widget.tour.BubbleControl}
*/
get(tileId) {
const {/** @type {Help4.widget.tour.View} */ _view} = this;
for (/** @type {Help4.control2.Control} */ const control of _view) {
if (control instanceof Help4.widget.tour.BubbleControl) {
if (!tileId || control.getMetadata('tileId') === tileId) {
return control;
}
}
}
return null;
}
/**
* @param {string} [reason = 'next']
* @returns {Promise<void>}
*/
async create(reason = 'next') {
const {/** @type {Help4.widget.tour.View} */ _view} = this;
/**
* {@link Help4.controller.Tour}; see showBubble
* {@link Help4.controller.helper.Bubbles.prototype.openTour}
* {@link Help4.service.container.BubbleService.TourBubble.open}
*/
/** @type {Help4.widget.help.ProjectTile} */ const tile = _view.getCurrentTile();
/** @type {Help4.widget.help.Project} */ const {language} = _view.getProject();
/** @type {number} */ const steps = _view.getSteps();
/** @type {number} */ const step = _view.getStep();
const {
/** @type {Help4.controller.Controller} */ controller,
/** @type {Help4.typedef.SystemConfiguration} */ configuration
} = _view.getContext();
const {activeMLTranslation, translationAvailable} = configuration.translation;
// /** @type {string} */ const projectLanguage = Help4.getShell().getLanguage(_view.getProject().language).uacp;
// const systemLanguage = configuration.core.language.uacp;
// const showTranslateButton = translationAvailable && projectLanguage !== systemLanguage;
const showArrow = tile.showArrow || false;
/**
* {@link Help4.service.container.BubbleService.TourBubble.open}
*/
// TODO mlt remove this. handle in engine
const title = (activeMLTranslation && false) && tile._translation.title || tile.title;
const zoomService = controller.getService('zoom');
const mlt = controller.getEngine('MLT');
const controlParams = {
_metadata: {tileId: tile.id, type: 'bubble'},
controlType: 'Help4.widget.tour.BubbleControl',
autoFocus: false, // XRAY-1872
caption: Help4.Placeholder.resolve(title),
showCaption: tile.showTitleBar,
content: _prepareContent.call(this, tile),
modal: false,
showArrow,
size: tile.bubbleSize,
appearance: tile.bubbleType,
// orientation: tile.bubbleOrientation, // XXX: needed???
animationType: tile.bubbleAnimationType,
showPrevButton: _view.isPrevStepAvailable(),
showNextButton: _hasNextButton.call(this),
showFinishButton: step + 1 === steps,
contentLanguage: language,
enableTranslation: mlt.isTranslationActive(),
activeTranslation: mlt.isTranslationActive(),
zoomService
};
/** @type {Help4.widget.tour.BubbleControl} */ const bubble = _view.add(controlParams);
mlt.registerBubble(bubble);
bubble
.addListener('close', () => _view.stop())
.addListener('next', () => _view.nextStep())
.addListener('prev', () => _view.prevStep())
.addListener('translate', () => mlt.onBubbleClick({bubble, active: !bubble.activeTranslation}))
.addListener('destroy', () => mlt.unregisterBubble(bubble))
.addListener('dragdrop', () => {
bubble.setMetadata('dragdrop', true);
bubble.showArrow = false;
});
// XRAY-3807, XRAY-1180: align after scroll and after image/video load
const {Element, jscore: {MediaWatcher}} = Help4;
const dom = bubble.getDom();
await Element.execAfterTransition(dom);
if (!bubble.isDestroyed()) {
await MediaWatcher.observe(dom);
bubble.isDestroyed() || _align.call(this);
}
}
/** @returns {Help4.widget.tour.BubbleController} */
update() {
_align.call(this);
/** @type {?Help4.widget.tour.BubbleControl} */ const bubble = this.get();
if (bubble) bubble.showNextButton = _hasNextButton.call(this);
return this;
}
/**
* called after navigate to an unknown screen
* @returns {boolean}
*/
invalidate() {
/** @type {?Help4.widget.tour.BubbleControl} */ const bubble = this.get();
if (bubble) {
const {/** @type {Help4.widget.tour.View} */ _view} = this;
bubble.showArrow = false;
bubble.showPrevButton = false;
return true;
}
return false;
}
}
/**
* @memberof Help4.widget.tour.BubbleController#
* @private
* @returns {boolean}
*/
function _hasNextButton() {
const {/** @type {Help4.widget.tour.View} */ _view} = this;
const {autoProgress} = _view.getCurrentTile();
const {visible: curVisible} = _view.getCurrentStatus();
/** @type {number} */ let step = _view.getStep();
while (true) {
// check whether next step exists and is on same screen
if (!_view.isNextStepAvailable(step++)) return false;
// check whether next step is visible
const {autoSkipStep} = _view.getTile(step);
const {visible} = _view.getStatus(step);
if (autoSkipStep && !visible) continue; // XRAY-1808: next item is not visible but can be skipped
// XRAY-133: only show next if allowed/configured
// XRAY-1871: always show next button if item is not visible
return !curVisible || autoProgress.includes('next');
}
}
/**
* @memberof Help4.widget.tour.BubbleController#
* @private
* @param {Help4.widget.help.ProjectTile} tile
* @returns {string}
*/
function _prepareContent(tile) {
const {/** @type {Help4.widget.tour.View} */ _view} = this;
const {
/** @type {Help4.controller.Controller} */ controller,
/** @type {Help4.typedef.SystemConfiguration} */ configuration
} = _view.getContext();
const {activeMLTranslation} = configuration.translation;
// XXX: implement MLT
/** {@link Help4.service.container.BubbleService.prototype.prepareContent} */
const content = (activeMLTranslation && false) && tile._translation?.content || tile.content;
const {_ctx: ctx, _mac: mac} = tile;
const text = Help4.Placeholder.resolve(content);
return Help4.control.input.HtmlEditor.transformForPlayback({text, ctx, mac, controller});
}
/**
* @memberof Help4.widget.tour.BubbleController#
* @private
*/
function _align() {
const {/** @type {Help4.widget.tour.View} */ _view} = this;
/** @type {?Help4.widget.tour.BubbleControl} */ const bubble = this.get();
if (!bubble || bubble.getMetadata('dragdrop')) return; // nothing to align or XRAY-2353
const {
bubbleOffset,
bubbleOrientation: orientation,
showArrow
} = /** @type {Help4.widget.help.ProjectTile} */ _view.getCurrentTile();
/** @type {?Help4.control2.hotspot.Connected} */ const hotspot = _view.getHotspotControl();
/** @type {?Help4.control2.ConnectionPoints} */ const hotspotConnectionPoints = hotspot?.getConnectionPoints({useMidPoint: true});
if (hotspotConnectionPoints) {
const ori = Help4.control2.bubble.ORI_MAP[orientation]; // XRAY-1446: bubble offset
if (ori && bubbleOffset) {
/** @type {Help4.control2.PositionXY} */
const point = hotspotConnectionPoints.c || hotspotConnectionPoints[ori.charAt(0)];
if (point) {
const {left: x, top: y} = bubbleOffset;
point.x += x;
point.y += y;
}
}
// align to hotspot as good as possible
/** @type {?Help4.control2.bubble.Alignment} */
const alignment = bubble.align(hotspotConnectionPoints, {orientation}); // XRAY-1425, XRAY-1054
if (Help4.includes(['l', 'r', 't', 'b', 'm'], alignment.orientation)) {
bubble.showArrow = showArrow;
return;
}
}
// align centered
bubble.align();
bubble.showArrow = false;
}
})();