(function() {
/**
* @typedef {Help4.control2.bubble.Bubble.Params} Help4.control2.bubble.Panel.Params
* @property {?string} [logoSrc = null] - URL to possible logo
* @property {boolean} [showBackButton = false] - whether to show back button
* @property {boolean} [showDockButton = true] - whether to show dock button
* @property {boolean} [showMinimizeButton = true] - whether to show minimize button
* @property {boolean} [showCloseButton = true] - whether to show close button
* @property {boolean} [docked = false] - whether panel is docked
* @property {boolean} [minimized = false] - whether panel is minimized
* @property {boolean} [showTranslationButton = false] - whether to show the translation button
* @property {boolean} [translation = false] - whether translation is enabled
* @property {boolean} [animateTranslationButton = false] - whether to animate the translation button
* @property {?string} [caption = null] - caption text
* @property {boolean} [showCaption = true] - caption text
* @property {boolean} [showSearch = true] - whether to show search
* @property {boolean} [showTiles = true] - whether to show the widget tiles
* @property {boolean} [showContentArea = false] - whether to show the content area
* @property {?string} [brandingLogoSrc = null] - image source URL to possible branding logo
* @property {?string} [brandingLogoUrl = null] - navigation link to possible branding logo
* @property {boolean} [showEditButton = false] - whether to show the edit button
* @property {boolean} [showPublishViewButton = false] - whether to show the publish view button
* @property {boolean} [showWmButton = false] - whether to show the wm button
* @property {boolean} [publishView = false] - whether publish view is enabled
* @property {?string} [publishedState = null] - published state of the widget content
*/
/**
* Creates the bubble for the SAP Companion 4 panel.
* @augments Help4.control2.bubble.Bubble
* @property {?string} logoSrc - URL to possible logo
* @property {boolean} showBackButton - whether to show back button
* @property {boolean} showDockButton - whether to show dock button
* @property {boolean} showMinimizeButton - whether to show minimize button
* @property {boolean} showCloseButton - whether to show close button
* @property {boolean} showTranslationButton - whether to show the translation button
* @property {boolean} translation - whether translation is enabled
* @property {boolean} animateTranslationButton - whether to animate the translation button
* @property {boolean} docked - whether panel is docked
* @property {boolean} minimized - whether panel is minimized
* @property {?string} caption - caption text
* @property {boolean} showCaption - caption text
* @property {boolean} showTiles - whether to show the widget tiles
* @property {boolean} showContentArea - whether to show the content area
* @property {boolean} showSearch - whether to show search
* @property {string} searchTerm - the search term
* @property {?string} brandingLogoSrc - image source URL to possible branding logo
* @property {?string} brandingLogoUrl - navigation link to possible branding logo
* @property {boolean} showEditButton - whether to show the edit button
* @property {boolean} showPublishViewButton - whether to show the publish view button
* @property {boolean} showWmButton - whether to show the wm button
* @property {boolean} publishView - whether publish view is enabled
* @property {?string} publishedState - published state of the widget content
*/
Help4.control2.bubble.Panel = class extends Help4.control2.bubble.Bubble {
/**
* @override
* @param {Help4.control2.bubble.Panel.Params} params
*/
constructor(params) {
const {Localization} = Help4;
const caption = Localization.getText('header.panel.howcan');
const ariaLabel = Localization.getText('description.panel');
const ariaRoleDescription = Localization.getText('description.panel.label');
const {TYPES: T} = Help4.jscore.ControlBase;
const {
HEADER_LAYOUT,
CONTENT_LAYOUT,
FOOTER_LAYOUT
} = Help4.control2.bubble;
super(params, {
params: {
// header; keep in sync with bubble.header.Panel!
logoSrc: {type: T.string_null},
showBackButton: {type: T.boolean},
showDockButton: {type: T.boolean, init: true},
showMinimizeButton: {type: T.boolean, init: true},
showCloseButton: {type: T.boolean, init: true},
docked: {type: T.boolean},
minimized: {type: T.boolean},
translation: {type: T.boolean},
showTranslationButton: {type: T.boolean},
animateTranslationButton: {type: T.boolean},
// content; keep in sync with bubble.content.Panel!
caption: {type: T.string_null, init: caption},
showCaption: {type: T.boolean, init: true},
showTiles: {type: T.boolean, init: true},
showContentArea: {type: T.boolean},
showSearch: {type: T.boolean, init: true},
searchTerm: {type: T.string},
brandingLogoSrc: {type: T.string_null, readonly: true},
brandingLogoUrl: {type: T.string_null, readonly: true},
// footer; keep in sync with bubble.footer.Panel!
publishView: {type: T.boolean},
showPublishViewButton: {type: T.boolean},
showWmButton: {type: T.boolean},
showEditButton: {type: T.boolean},
publishedState: {type: T.string_null},
// own properties
size: {init: 'panel'},
role: {init: 'application'},
ariaLabel: {init: ariaLabel},
ariaRoleDescription: {init: ariaRoleDescription},
headerLayout: {init: HEADER_LAYOUT.Panel},
contentLayout: {init: CONTENT_LAYOUT.Panel},
footerLayout: {init: FOOTER_LAYOUT.Panel},
enableDragDrop: {init: true}
},
statics: {
_minimizeButton: {},
_widgetEngineObserver: {},
_windowObserver: {},
_activeWidgetName: {destroy: false},
_beforeActivate: {destroy: false}
},
config: {
css: 'control-panel home noexternal'
}
});
}
/**
* @override
* @param {boolean} [onlyVisible = false]
*/
getTexts(onlyVisible = false) {
const {_minimizeButton} = this;
return _minimizeButton
? _minimizeButton.getTexts(onlyVisible)
: super.getTexts(onlyVisible);
}
/**
* @override
* @param {Object} textMap
*/
setTexts(textMap) {
const {_minimizeButton} = this;
return _minimizeButton
? _minimizeButton.setTexts(textMap)
: super.setTexts(textMap);
}
/**
* @param {number} left
* @param {number} top
*/
setPosition(left, top) {
this.dataFunctions.set({dragPosition: {left, top}}, {allowReadonlyOverride: true}); // this.dragPosition is readonly; allow override
this.setStyle({
left: `${left}px`,
top: `${top}px`
});
_onResize.call(this);
}
/**
* @override
* @returns {Help4.control2.bubble.Panel}
*/
focus() {
this.minimized = false;
Help4.Element.execAfterTransition(this.getDom(), () => {
if (!this.isDestroyed()) this._content?.focus();
});
return this;
}
/**
* focus handling - up/down/left/right arrow keys
* @param {string} direction
*/
focusListItem(direction) {
this._content?.focusListItem(direction);
}
/**
* @override
* @param {HTMLElement} dom - control DOM
*/
_onDomAvailable(dom) {
super._onDomAvailable(dom);
if (this.minimized && this.visible) {
// in case panel is visible and minimized it will flicker during creation
// as it moves out but is immediately hidden through minimize
this.addCss('hidden'); // add hidden class before adding the panel to the DOM
}
const eventBus = /** @type {Help4.EventBus} */ Help4.getController().getService('eventBus');
const {
observer: {EventBusObserver, WindowObserver},
EventBus: {TYPES}
} = Help4;
this._widgetEngineObserver = new EventBusObserver(event => _onWidgetEvent.call(this, event))
.observe(eventBus, {type: [TYPES.widgetStart, TYPES.widgetActivate]});
this._windowObserver = new WindowObserver(event => _onResize.call(this))
.observe(window, {eventObserver: {type: 'resize', capture: true}});
}
/** @override */
_onGetDragDropParams() {
return {
object: this.getDom(),
area: this._header.getDom()
};
}
/**
* incompatible override!
* @protected
*/
_onCreateHeader() {
const {showTranslationButton, translation, animateTranslationButton, showMinimizeButton, showCloseButton} = this;
super._onCreateHeader({showTranslationButton, translation, animateTranslationButton, showMinimizeButton, showCloseButton});
this._header?.addListener(['back', 'dock', 'minimize', 'close', 'translate'], event => void _onEvent.call(this, event));
}
/**
* incompatible override!
* @protected
*/
_onCreateContent() {
const {brandingLogoSrc, brandingLogoUrl} = this;
super._onCreateContent({brandingLogoSrc, brandingLogoUrl});
this._content?.addListener(['widget', 'search'], event => void _onEvent.call(this, event));
this._content?.dataFunctions.onChange.addListener(({name, value}) => (name === 'searchTerm') && (this.searchTerm = value));
}
/**
* incompatible override!
* @protected
*/
_onCreateFooter() {
const {showEditButton, showPublishViewButton, publishView, showWmButton} = this;
super._onCreateFooter({showEditButton, showPublishViewButton, publishView, showWmButton});
this._footer?.addListener(['edit', 'publishView', 'wm'], event => void _onEvent.call(this, event));
}
/**
* @override
* @param {Help4.jscore.ControlBase.PropertyChangeEvent} event - the change event
*/
_applyPropertyToDom({name, value, oldValue}) {
switch (name) {
// header
case 'logoSrc':
case 'showBackButton':
case 'showDockButton':
case 'showMinimizeButton':
case 'showCloseButton':
case 'translation':
case 'showTranslationButton':
case 'animateTranslationButton':
this._header[name] = value;
break;
case 'docked':
const {Localization} = Help4;
this._header.docked = value;
if (value) {
this.suspendDragDrop();
this.removeCss('floating');
this.addCss('docked');
this.ariaRoleDescription = Localization.getText('description.carousel.label');
} else {
this.removeCss('docked');
this.addCss('floating');
this.resumeDragDrop();
this.ariaRoleDescription = Localization.getText('description.panel.label');
}
_adjustPosition.call(this);
break;
case 'minimized':
this._header.minimized = value;
_minimize.call(this, value);
_adjustPosition.call(this);
break;
// content
case 'searchTerm':
const lower = value.toLowerCase();
if (value === lower) {
const filterWidget = /** @type {Help4.widget.filter.Widget} */ Help4.widget.getInstance('filter');
this._content[name] = value;
filterWidget?.setTerm(value); // synchronize with filter widget
} else {
// reset with lowercase
this.searchTerm = lower;
}
break;
case 'caption':
case 'showCaption':
case 'showSearch':
case 'showTiles':
case 'showContentArea':
this._content[name] = value;
break;
// footer
case 'publishView':
case 'showPublishViewButton':
case 'publishedState':
case 'showEditButton':
case 'showWmButton':
this._footer[name] = value;
break;
// own properties
case 'visible':
const {_minimizeButton} = this;
if (_minimizeButton) {
_minimizeButton.visible = value;
} else {
super._applyPropertyToDom({name, value, oldValue});
}
_adjustPosition.call(this);
break;
default:
super._applyPropertyToDom({name, value, oldValue});
break;
}
}
}
/**
* @memberof Help4.control2.bubble.Panel#
* @param {Object} event
* @private
*/
function _onEvent(event) {
switch (event.type) {
case 'back':
if (Help4.widget.getActiveInstance()?.getName() === 'filter') {
this.searchTerm = '';
}
const monitor = /** @type {Help4.widget.monitor.Widget} */ Help4.widget.getInstance('monitor');
monitor?.executeBack() || _deactivateWidget.call(this);
break;
case 'dock':
event.docked = this.docked = !this.docked;
this._fireEvent(event);
break;
case 'minimize':
this.minimized = !this.minimized;
break;
case 'publishView':
case 'translate':
case 'close':
case 'edit':
case 'wm':
this._fireEvent(event);
break;
case 'search':
const filterWidget = /** @type {Help4.widget.filter.Widget} */ Help4.widget.getInstance('filter');
filterWidget?.execSearch(this.searchTerm);
break;
case 'widget':
_activateWidget.call(this, event.id);
break;
}
}
/**
* @memberof Help4.control2.bubble.Panel#
* @param {boolean} minimize
* @private
*/
function _minimize(minimize) {
const {_minimizeButton} = this;
if (minimize && !_minimizeButton) {
// hide the panel w/o changing this.visible
// instead of the panel the minimize button will be shown
this._applyPropertyToDom({name: 'visible', value: false});
this.addCss('minimize');
const {Button, APPEARANCES} = Help4.control2.button;
const title = Help4.Localization.getText('tooltip.carouselhide');
const {visible} = this;
this._minimizeButton = this._createControl(Button, {
doc: this.getDocument(),
dom: this.getOwnerDom(),
appearance: APPEARANCES.icon,
icon: Help4.control2.ICONS.help,
css: 'sap-companion',
title,
ariaLabel: title,
visible
})
.addListener('click', () => {
this.minimized = false;
this.focus();
});
}
if (!minimize && _minimizeButton) {
// take visible state from _minimizeButton as panel state has not been updated
// while minimize button was shown
const visible = _minimizeButton.visible;
this._destroyControl('_minimizeButton');
this.removeCss('minimize');
if (this.visible === visible) {
// make sure to apply to DOM; show panel as button is now hidden
this._applyPropertyToDom({name: 'visible', value: visible});
} else {
this.visible = visible; // take over the new visibility
}
}
this._fireEvent({type: 'minimize', minimized: minimize});
}
/**
* @memberof Help4.control2.bubble.Panel#
* @param {string} name
* @private
*/
function _activateWidget(name) {
Help4.widget.getInstance(name)?.activate();
}
/**
* @memberof Help4.control2.bubble.Panel#
* @private
*/
function _deactivateWidget() {
Help4.widget.getInstance(this._activeWidgetName)?.deactivate();
}
/**
* @memberof Help4.control2.bubble.Panel#
* @param {Help4.widget.Widget.Event} event
* @private
*/
function _onWidgetEvent({type, engine, value}) {
const {TYPES} = Help4.EventBus;
switch (type) {
case TYPES.widgetStart:
if (!value) _deactivateWidget.call(this);
break;
case TYPES.widgetActivate:
if (value) {
this.removeCss('home');
const {caption: c, logoSrc: l} = this;
this._beforeActivate = {c, l};
const {id, tile} = engine.getDescriptor();
this._activeWidgetName = id;
this.logoSrc = null;
this.showBackButton = true;
this.showSearch = tile?.showSearch || false;
this.showTiles = false;
this.caption = tile?.title || '';
this.showContentArea = true;
} else if (this._beforeActivate) {
const {c: caption, l: logoSrc} = this._beforeActivate;
delete this._beforeActivate;
this._activeWidgetName = null;
this.logoSrc = logoSrc;
this.showBackButton = false;
this.showContentArea = false;
this.showTiles = true;
this.showSearch = true;
this.caption = caption;
this.addCss('home');
}
break;
}
}
/**
* @memberof Help4.control2.bubble.Panel#
* @private
*/
function _adjustPosition() {
const dom = this.getDom();
Help4.Element.execAfterTransition(dom, () => {
if (!_isPanelResizable.call(this)) return;
// get panel w,h from CSS variables
const style = getComputedStyle(dom);
const w = parseInt(style.getPropertyValue('--help4-cmp-pane-w'));
const h = parseInt(style.getPropertyValue('--help4-cmp-pane-h'));
// get window size and last drag position
const {innerWidth, innerHeight} = window;
const {left, top} = this.dragPosition || {};
// check whether drag position is still valid
if (isNaN(left) || left + w > innerWidth || top + h > innerHeight) {
// invalid: recalculate
_onResize.call(this);
} else {
// valid: simply apply
this.setStyle({
left: `${left}px`,
top: `${top}px`
});
}
});
}
/**
* @memberof Help4.control2.bubble.Panel#
* @private
*/
function _onResize() {
if (!_isPanelResizable.call(this)) return;
// get size of window
const {innerWidth, innerHeight} = window;
// calculate min, max positions for X and Y
const {x, y} = this.getArea();
// get panel w, h from CSS variables
const style = getComputedStyle(this.getDom());
const w = parseInt(style.getPropertyValue('--help4-cmp-pane-w'));
const h = parseInt(style.getPropertyValue('--help4-cmp-pane-h'));
const minX = 0;
const minY = 0;
const maxX = Math.max(minX, innerWidth - w);
const maxY = Math.max(minY, innerHeight - h);
// adjust panel position
const newX = Math.max(minX, Math.min(x, maxX));
const newY = Math.max(minY, Math.min(y, maxY));
// apply
this.setStyle({
left: `${newX}px`,
top: `${newY}px`
});
// notify watchers
this._onDragStop();
}
/**
* only allow position adjustments and resize for a floating, visible panel
* @memberof Help4.control2.bubble.Panel#
* @private
* @returns {boolean}
*/
function _isPanelResizable() {
const {docked, minimized, visible} = this;
return !docked && !minimized && visible && !this.isDestroyed();
}
})();