'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * Licensed Materials - Property of IBM * IBM Cognos Products: Storytelling * (C) Copyright IBM Corp. 2017, 2019 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['baglass/core-client/js/core-client/utils/BrowserUtils'], function (BrowserUtils) { var AnimationDirector = function () { function AnimationDirector(options) { _classCallCheck(this, AnimationDirector); this.pageContextAPI = options.pageContextAPI; this.widgetHelper = options.widgetHelper; this._inProgressCssAnimations = {}; this._inProgressWidgetsAnimation = {}; this._scrollBarsStateLockCount = 0; } /** * animates a widget * @param payload {object} * { * target: {string} target widget id" * duration: {integer} in ms * animation: {string} animation type * payload: {object} animation specific information * } */ AnimationDirector.prototype.animate = function animate(payload) { var _this = this; var widgetId = payload.target; var node = this.widgetHelper.getContentNode(widgetId); if (!node) { return; } var transform = this._getLayoutNodeTransform(widgetId); transform = transform ? ' rotate(' + transform + 'deg)' : transform; this._finishInProgressCssAnimation(node); if (payload.duration === 0) { this._immediateAnimate(node, transform, payload); return Promise.resolve(); } if (payload.reveal) { return this._reveal(payload.target).finally(function () { return _this._animate(node, transform, payload); }); } else { return this._animate(node, transform, payload); } }; AnimationDirector.prototype._animate = function _animate(node, transform, payload) { var _this2 = this; var width = parseInt(this._getNodeWidth(node), 10); var height = parseInt(this._getNodeHeight(node), 10); var bounds = node.getBoundingClientRect(); var left = parseInt(bounds.left, 10); var top = parseInt(bounds.top, 10); var docWidth = this._getDocumentWidth(); var docHeight = this._getDocumentHeight(); var duration = payload.duration; var opacity = node.style.opacity; var endVal = void 0; if (payload.animation === 'scaleIn') { this._lockScrollBarsState(node); return this._animation(node, { 'transition': 'transform ' + duration + 'ms', 'transform': transform }, function () { _this2._restoreScrollBarsState(node); }); } else if (payload.animation === 'scaleOut') { this._lockScrollBarsState(node); return this._animation(node, { 'transition': 'transform ' + duration + 'ms', 'transform': 'scale(0,0) translate(0,0) rotate(0)' + transform }, function () { node.style.transition = 'all 0s'; _this2._restoreScrollBarsState(node); }); } else if (payload.animation === 'shrinkIn') { this._lockScrollBarsState(node); this._forceCssWrite(node, { 'opacity': '0', 'transition': 'all 0s', 'transform': 'scale(5,5) translate(0,0) rotate(0)' + transform }); return this._animation(node, { 'opacity': opacity, 'transition': 'transform ' + duration + 'ms, opacity ' + duration + 'ms', 'transform': transform }, function () { _this2._restoreScrollBarsState(node); }, 2); } else if (payload.animation === 'expandOut') { this._lockScrollBarsState(node); return this._animation(node, { 'opacity': '0', 'transition': 'transform ' + duration + 'ms, opacity ' + duration + 'ms', 'transform': 'scale(5,5) translate(0,0) rotate(0)' + transform }, function () { node.style.opacity = opacity; node.style.transition = 'all 0s'; node.style.transform = 'scale(0,0) translate(0,0) rotate(0)' + transform; _this2._restoreScrollBarsState(node); }, 2); } else if (payload.animation === 'pivotIn') { this._lockScrollBarsState(node); this._forceCssWrite(node, { 'opacity': '0', 'transition': 'all 0s', 'transform': 'scale(1,1) translate(-50%,50%) rotate(-45deg) translate(50%,-50%)' + transform }); return this._animation(node, { 'opacity': opacity, 'transition': 'transform ' + duration + 'ms, opacity ' + duration + 'ms', 'transform': transform }, function () { _this2._restoreScrollBarsState(node); }, 2); } else if (payload.animation === 'pivotOut') { this._lockScrollBarsState(node); return this._animation(node, { 'opacity': '0', 'transition': 'transform ' + duration + 'ms, opacity ' + duration + 'ms', 'transform': 'scale(1,1) translate(50%,50%) rotate(45deg) translate(-50%,-50%)' + transform }, function () { node.style.opacity = opacity; node.style.transition = 'all 0s'; node.style.transform = 'scale(0,0) translate(0,0) rotate(0)' + transform; _this2._restoreScrollBarsState(node); }, 2); } else if (payload.animation === 'slideInLeft') { endVal = left + width; if (node.parentElement && node.parentElement.offsetLeft) { endVal += node.parentElement.offsetLeft; } // Make sure node is: 1) to scale; 2) at the proper opacity level; 3) at its correct starting position this._forceCssWrite(node, { 'transition': 'all 0s', 'transform': 'scale(1,1) translateX(' + -endVal + 'px) rotate(0)' + transform }); return this._animation(node, { 'transition': 'transform ' + duration + 'ms', 'transform': transform }); } else if (payload.animation === 'slideOutLeft') { /* * Use parentElement.offsetLeft here so that the animation works for objects in a group * because height could be a small value and will be applied relative to the group. * We use the parent's offset so that two objects sliding out, with one in a group * and the other not, slide at the same speed (because they travel the same distance). */ endVal = left + width; if (node.parentElement && node.parentElement.offsetLeft) { endVal += node.parentElement.offsetLeft; } return this._animation(node, { 'transition': 'transform ' + duration + 'ms', 'transform': 'translateX(' + -endVal + 'px)' + transform }, function () { node.style.transition = 'all 0s'; node.style.transform = 'scale(0,0) translate(0,0) rotate(0)' + transform; }); } else if (payload.animation === 'slideInRight') { this._lockScrollBarsState(node); this._forceCssWrite(node, { 'transition': 'all 0s', 'transform': 'scale(1,1) translateX(' + docWidth + 'px)' + transform }); // Make sure node is to scale, at the proper opacity level, and at its correct starting position return this._animation(node, { 'transition': 'transform ' + duration + 'ms', 'transform': transform }, function () { _this2._restoreScrollBarsState(node); }); } else if (payload.animation === 'slideOutRight') { this._lockScrollBarsState(node); return this._animation(node, { 'transition': 'transform ' + duration + 'ms', 'transform': 'translateX(' + docWidth + 'px)' + transform }, function () { node.style.transition = 'all 0s'; node.style.transform = 'scale(0,0) translate(0,0) rotate(0)' + transform; _this2._restoreScrollBarsState(node); }); } else if (payload.animation === 'slideInTop') { endVal = top + height; if (node.parentElement && node.parentElement.offsetTop) { endVal += node.parentElement.offsetTop; } // Make sure node is to scale, at the proper opacity level, and at its correct starting position this._forceCssWrite(node, { 'transition': 'all 0s', 'transform': 'scale(1,1) translateY(' + -endVal + 'px)' + transform }); return this._animation(node, { 'transition': 'transform ' + duration + 'ms', 'transform': transform }); } else if (payload.animation === 'slideOutTop') { /* * Use parentElement.offsetTop here so that the animation works for objects in a group * because height could be a small value and will be applied relative to the group. * We use the parent's offset so that two objects sliding out, with one in a group * and the other not, slide at the same speed (because they travel the same distance). */ endVal = top + height; if (node.parentElement && node.parentElement.offsetTop) { endVal += node.parentElement.offsetTop; } return this._animation(node, { 'transition': 'transform ' + duration + 'ms', 'transform': 'translateY(' + -endVal + 'px)' + transform }, function () { node.style.transition = 'all 0s'; node.style.transform = 'scale(0,0) translate(0,0) rotate(0)' + transform; }); } else if (payload.animation === 'slideInBottom') { this._lockScrollBarsState(node); // Make sure node is to scale, at the proper opacity level, and at its correct starting position this._forceCssWrite(node, { 'transition': 'all 0s', 'transform': 'scale(1,1) translateY(' + docHeight + 'px)' + transform }); return this._animation(node, { 'transition': 'transform ' + duration + 'ms', 'transform': transform }, function () { _this2._restoreScrollBarsState(node); }); } else if (payload.animation === 'slideOutBottom') { this._lockScrollBarsState(node); return this._animation(node, { 'transition': 'transform ' + duration + 'ms', 'transform': 'translateY(' + docHeight + 'px)' + transform }, function () { node.style.transition = 'all 0s'; node.style.transform = 'scale(0,0) translate(0,0) rotate(0)' + transform; _this2._restoreScrollBarsState(node); }); } else if (payload.animation === 'show') { this._forceCssWrite(node, { 'opacity': '0', 'transition': 'all 0s' }); return this._animation(node, { 'transition': 'opacity ' + duration + 'ms', 'transform': transform, 'opacity': opacity }); } else if (payload.animation === 'hide') { return this._animation(node, { 'transition': 'opacity ' + duration + 'ms', 'opacity': '0' }, function () { node.style.transition = 'all 0s'; node.style.transform = 'scale(0,0) translate(0,0) rotate(0)' + transform; node.style.opacity = opacity; }); } else if (payload.animation === 'highlight') { return this._highlightWidget(payload); } else if (payload.animation === 'clearHighlight') { return this._clearHighlightWidget(payload); } return Promise.resolve(); }; AnimationDirector.prototype._reveal = function _reveal(widgetId) { var _this3 = this; this._finishInProgressWidgetAnimation(widgetId); var widget = this.widgetHelper.getWidget(widgetId); return Promise.resolve(widget && widget.getVisApi && widget.getVisApi().ownerWidget.reveal ? widget.getVisApi().ownerWidget.reveal() : null).then(function (info) { if (info && info.renderControlApi) { _this3._inProgressWidgetsAnimation[widgetId] = info.renderControlApi; } }); }; AnimationDirector.prototype.pauseWidgetsAnimation = function pauseWidgetsAnimation() { var _this4 = this; Object.keys(this._inProgressWidgetsAnimation).map(function (key) { return _this4._inProgressWidgetsAnimation[key]; }).forEach(function (api) { api.pause && api.pause(); }); }; AnimationDirector.prototype.resumeWidgetsAnimation = function resumeWidgetsAnimation() { var _this5 = this; Object.keys(this._inProgressWidgetsAnimation).map(function (key) { return _this5._inProgressWidgetsAnimation[key]; }).forEach(function (api) { api.resume && api.resume(); }); }; AnimationDirector.prototype.finishWidgetsAnimation = function finishWidgetsAnimation() { var apisMap = this._inProgressWidgetsAnimation; this._inProgressWidgetsAnimation = {}; Object.keys(apisMap).map(function (key) { return apisMap[key]; }).forEach(function (api) { api.complete && api.complete(); }); }; AnimationDirector.prototype._finishInProgressWidgetAnimation = function _finishInProgressWidgetAnimation(widgetId) { var api = this._inProgressWidgetsAnimation[widgetId]; delete this._inProgressWidgetsAnimation[widgetId]; if (api) { api.complete && api.complete(); } }; /** * finishes any pending animation on a widget immediately * @param {object} payload * @param {string} payload.target id if target widget * { * target: {string} target widget id * } */ AnimationDirector.prototype.finishInProgressAnimation = function finishInProgressAnimation(payload) { var node = this.widgetHelper.getContentNode(payload.target); if (!node) { return; } this._finishInProgressCssAnimation(node); this._finishInProgressWidgetAnimation(payload.target); }; /* This method only exists as a performance optimization. * * In the case of an immediate animation we only want to show or hide the widget. * The multi step code in animate is overkill for that and overwhelms firefox in some cases. */ AnimationDirector.prototype._immediateAnimate = function _immediateAnimate(node, transform, payload) { // use scale3d(0,0,0) to force a repaint in firefox [defect 184181]. It also accelerates css paint by using GPU. // Note: the matrix computed in scale(0,0) equals scale3d(0,0,1) but is not equal to scale3d(0,0,0). We are now setting the scale z to 0 for a repaint. // // multiple browsers now have this repaint problem (IE, chrome and Firefox I'm looking at you) // We do the double set to force a repaint. // In theory this should be GPU accelerated and not called as often as before because of optimization in TimeQueue. // I.e the total number of _immediateAnimate calls should be less in total on a seek switch (payload.animation) { case 'show': case 'slideInLeft': case 'slideInRight': case 'slideInTop': case 'slideInBottom': case 'scaleIn': case 'shrinkIn': case 'pivotIn': node.style.transition = 'all 0s'; node.style.transform = transform; break; case 'hide': case 'slideOutLeft': case 'slideOutRight': case 'slideOutTop': case 'slideOutBottom': case 'scaleOut': case 'expandOut': case 'pivotOut': this._forceCssWrite(node, { 'transition': 'all 0s', 'transform': 'scale(0) translate(0,0) rotate(0)' + transform }); if (!BrowserUtils.isSafari()) { this._forceCssWrite(node, { 'transform': 'scale3d(0,0,0) translate(0,0) rotate(0)' + transform }); } // From Defect 272532: Forcing IE to remove the inner visualizations that are not hidden when necessary if (BrowserUtils.isIE()) { var old_display = node.style.display; node.style.display = 'none'; this._forceRepaint(node); node.style.display = old_display; } break; case 'highlight': this._highlightWidget(payload); break; case 'clearHighlight': this._clearHighlightWidget(payload); break; default: break; } }; /** * Helping method to get this widget's layout information * * @param contentId - id of the content */ AnimationDirector.prototype._getLayoutNodeTransform = function _getLayoutNodeTransform(contentId) { var content = this.widgetHelper.getContent(contentId); var transform = content && content.getPropertyValue('rotateAngle'); if (!transform) { transform = ''; } return transform; }; /** * Private method to perform css animations on a provided DOM node * * @param node - DOM object * @param animationStyleProperties - object - The CSS properties that will be used for the animation * @param immediate - A boolean variable indicating if the animation should happen right away * @param callback - function - A callback function to execute after the transition time of the animation */ AnimationDirector.prototype._animation = function _animation(node, animationStyleProperties, transitionEndCallback) { var _this6 = this; var numAnimatedProperties = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1; return new Promise(function (resolve) { var inProgressCssAnimation = _this6._inProgressCssAnimations[node.id]; if (inProgressCssAnimation) { _this6._finishInProgressCssAnimation(node); } inProgressCssAnimation = _this6._inProgressCssAnimations[node.id] = {}; node.classList.add('animating'); var callback = function callback() { if (transitionEndCallback) { transitionEndCallback(); } node.classList.remove('animating'); resolve(); }; _this6._forceCssWrite(node, animationStyleProperties); inProgressCssAnimation.css = Object.assign({}, animationStyleProperties, { transition: 'none' }); inProgressCssAnimation.onTransitionEnd = _this6._onTransitionEnd.bind(_this6, node); inProgressCssAnimation.onTransitionEndCallback = callback; inProgressCssAnimation.numAnimatedProperties = numAnimatedProperties; inProgressCssAnimation.transitionEndCount = 0; node.addEventListener('transitionend', inProgressCssAnimation.onTransitionEnd); }); }; AnimationDirector.prototype._finishInProgressCssAnimation = function _finishInProgressCssAnimation(node) { var inProgressCssAnimation = this._inProgressCssAnimations[node.id]; if (inProgressCssAnimation) { this._forceCssWrite(node, inProgressCssAnimation.css); node.removeEventListener('transitionend', inProgressCssAnimation.onTransitionEnd); inProgressCssAnimation.onTransitionEndCallback(); delete this._inProgressCssAnimations[node.id]; } }; /** * Private method used in conjunction with performing CSS animations. * This gets called when the 'transitionend' event for a DOM node is fired. * * @param callback - function - A callback function to execute when this method is fired * @param event - el - The DOM element from the 'transitionend' event being fired */ AnimationDirector.prototype._onTransitionEnd = function _onTransitionEnd(node) { var inProgressCssAnimation = this._inProgressCssAnimations[node.id]; if (inProgressCssAnimation && ++inProgressCssAnimation.transitionEndCount === inProgressCssAnimation.numAnimatedProperties) { node.removeEventListener('transitionend', inProgressCssAnimation.onTransitionEnd); inProgressCssAnimation.onTransitionEndCallback(); delete this._inProgressCssAnimations[node.id]; } }; /** * Private method used to force a write of the CSS * This method allows multiple writes to actually be applied. (see comment in method) * * @param node - the widget node * @param cssPayload - object - css data */ AnimationDirector.prototype._forceCssWrite = function _forceCssWrite(node, cssPayload) { var _this7 = this; Object.keys(cssPayload).forEach(function (key) { _this7._applyPrefixStyling(node, key, cssPayload[key]); }); // we are setting multiple conflicting css properties in a row and we want them to be all executed. // the browser does not always do this so we add the read to force a 'repaint'. this._forceRepaint(node); }; AnimationDirector.prototype._applyPrefixStyling = function _applyPrefixStyling(node, property, value) { var prefixList = ['webkit', 'Webkit', 'moz', 'Moz', 'ms', 'o']; prefixList.forEach(function (prefix) { var propertyPrefix = prefix + property.charAt(0).toUpperCase() + property.slice(1); node.style[propertyPrefix] = value; }); node.style[property] = value; }; AnimationDirector.prototype._forceRepaint = function _forceRepaint(node) { void node.offsetHeight; }; AnimationDirector.prototype._getDocumentWidth = function _getDocumentWidth() { return Math.max(document.body.scrollWidth, document.body.offsetWidth, document.documentElement.clientWidth, document.documentElement.scrollWidth, document.documentElement.offsetWidth); }; AnimationDirector.prototype._getDocumentHeight = function _getDocumentHeight() { return Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight); }; AnimationDirector.prototype._getNodeWidth = function _getNodeWidth(node) { return Math.max(node.clientWidth, node.offsetWidth, node.scrollWidth); }; AnimationDirector.prototype._getNodeHeight = function _getNodeHeight(node) { return Math.max(node.clientHeight, node.offsetHeight, node.scrollHeight); }; AnimationDirector.prototype._lockScrollBarsState = function _lockScrollBarsState(element) { var pageContainer = element.closest('.pagecontainer'); if (!pageContainer) { return; } // overflow is set to 'visible' in pan and zoom (see panAndZoom.scss) var computedStyles = window.getComputedStyle(pageContainer); if (computedStyles.overflow === 'visible') { return; } // if the scrollbars are already locked, we leave them alone if (this._scrollBarsStateLockCount++ > 0) { return; } var vscroll = pageContainer.scrollHeight > pageContainer.clientHeight; var hscroll = pageContainer.scrollWidth > pageContainer.clientWidth; pageContainer.style.overflowX = hscroll ? 'scroll' : 'hidden'; pageContainer.style.overflowY = vscroll ? 'scroll' : 'hidden'; }; AnimationDirector.prototype._restoreScrollBarsState = function _restoreScrollBarsState(element) { var pageContainer = element.closest('.pagecontainer'); if (!pageContainer) { return; } // overflow is set to 'visible' in pan and zoom (see panAndZoom.scss) var computedStyles = window.getComputedStyle(pageContainer); if (computedStyles.overflow === 'visible') { return; } // if someone else locked the scrollbars we leave them alone if (--this._scrollBarsStateLockCount > 0) { return; } // in the _lockScrollBarsState we set these values on the element itself to scroll/hidden // We need to remove them complete to allow for css to take back control pageContainer.style.overflowX = ''; pageContainer.style.overflowY = ''; }; AnimationDirector.prototype._clearHighlightWidget = function _clearHighlightWidget(event) { var content = this.widgetHelper.getContent(event.target); var visualization = content && content.getFeature('Visualization'); if (!visualization) { return; } var options = { payloadData: { runtimeOnly: true } }; var dataSource = visualization.getDataSource(); // only clear highlight when there is a data source associated with one widget if (dataSource) { this.pageContextAPI.resetToPersistedValues({ origin: 'visualization', scope: this.widgetHelper.getPageContent(content).getId(), eventSourceId: event.target, sourceId: dataSource.getId(), eventGroupId: this.widgetHelper.getEventGroupId(content.getId()) }, options); } return Promise.resolve(); }; AnimationDirector.prototype._highlightWidget = function _highlightWidget(event) { var _this8 = this; var content = this.widgetHelper.getContent(event.target); var visualization = content && content.getFeature('Visualization'); if (!visualization) { return Promise.resolve(); } var highlights = event.payload || []; var dataSource = visualization.getDataSource(); highlights.forEach(function (highlight) { var pageContent = _this8.widgetHelper.getPageContent(content); var selector = { origin: 'visualization', scope: pageContent && pageContent.getId(), eventSourceId: event.target, sourceId: dataSource.getId(), itemId: highlight.columnId, eventGroupId: _this8.widgetHelper.getEventGroupId(content.getId()), hierarchies: [{ 'hierarchyUniqueName': highlight.columnId }] }; var options = { payloadData: { runtimeOnly: true } }; var command = { command: 'replace', values: highlight.values }; _this8.pageContextAPI.updateFilterContext(selector, command, options); }); return Promise.resolve(); }; return AnimationDirector; }(); return AnimationDirector; }); //# sourceMappingURL=AnimationDirector.js.map