'use strict'; /** * Licensed Materials - Property of IBM * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2015, 2020 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['./SceneLayout', '../../navigation/NavigationController', '../../navigation/SceneLoader', '../../navigation/panandzoom/PanAndZoomTransitionController', 'baglass/core-client/js/core-client/utils/Deferred', 'jquery', 'underscore', 'impress'], function (SceneLayout, NavigationController, SceneLoader, TransitionController, Deferred, $, _, impress) { var BLACKLISTED_PROPERTIES = ['fitPage']; /** * Pan and zoom layout. Uses impress to navigate (pan or zoom) between scenes */ var PanAndZoomLayout = SceneLayout.extend({ // Following line is for testing :( _impressFactory: impress, // PanAndZoomLayout6 uses CSS to scale it's border so here we only capture the templates _dropZoneSelector: '.pagetemplateDropZone, .pagetemplateIndicator', _pageNavigationController: null, init: function init() { PanAndZoomLayout.inherited('init', this, arguments); this._trailingOverviewId = '_overview_' + this.model.id; // Add onto the sceneLayoutApi this.extendApi({ 'getOverviews': this._getOverviews.bind(this) }); this._overviews = [{ id: 'start', $el: $('#start' + this._trailingOverviewId), isOverview: true }, { id: 'end', $el: $('#end' + this._trailingOverviewId), isOverview: true }]; this._sceneLoader = new SceneLoader({ logger: this.logger, dashboardApi: this.dashboardApi, layoutController: this.layoutController }); this._layoutIsReady = new Deferred(); this.changePageSize({ value: this.model.pageSize }); this.model.on('change:pageSize', this._handlePageUpdate, this); }, destroy: function destroy() { var _this = this; this._sceneLoader.stopBackgroundLoad(); // we have to clear the modification we made to the scenes without telling them. // (in _updateSceneSize) (this._scenes || []).forEach(function (scene) { var view = scene.getLayoutView(); if (view) { view.$el[0].style.height = null; view.$el[0].style.width = null; view.$el.find(_this._dropZoneSelector).each(function (index, element) { element.style.borderWidth = null; }); } }); clearInterval(this._viewPortSetIntervalId); this.$el[0].removeEventListener('impress:stepenter', this._impressStepEnterHandler); this.model.off('change:pageSize', this._handlePageUpdate, this); PanAndZoomLayout.inherited('destroy', this, arguments); }, _handlePageUpdate: function _handlePageUpdate(options) { this.changePageSize(options); this.onResize(); }, changePageSize: function changePageSize() { //to be overwritten }, getProperties: function getProperties() { var _this2 = this; return PanAndZoomLayout.inherited('getProperties', this, arguments).then(function (properties) { properties = properties.filter(function (property) { return BLACKLISTED_PROPERTIES.indexOf(property.id) === -1; }); return properties.concat(_this2._getShowOverviewProperties()); }); }, _getShowOverviewProperties: function _getShowOverviewProperties() { var _this3 = this; var overviewsModel = this.model.get('showOverviews'); // the overview property name are very generic // to prevent clashing with other properties we add a prefix var propertyPrefix = 'overview_'; var updateOverviewModel = function updateOverviewModel(propertyName, propertyValue) { var _showOverviews; //remove prefix propertyName = propertyName.substring(propertyPrefix.length); var updateOptions = { updateArray: [{ id: _this3.model.id, showOverviews: (_showOverviews = {}, _showOverviews[propertyName] = propertyValue, _showOverviews) }] }; _this3.model.updateModel(updateOptions, _this3, { undoRedoTransactionId: _.uniqueId('layoutShowOverview_') }); }; return [{ 'name': propertyPrefix + 'showStart', 'sectionName': this.stringResources.get('scenesPropertiesSection'), 'type': 'CheckBox', 'label': this.stringResources.get('propShowStoryOverviewFirstSlide'), 'checked': overviewsModel.showStart, 'onChange': updateOverviewModel, 'tabName': this.stringResources.get('tabName_general') }, { 'name': propertyPrefix + 'showEnd', 'sectionName': this.stringResources.get('scenesPropertiesSection'), 'type': 'CheckBox', 'label': this.stringResources.get('propShowStoryOverviewLastSlide'), 'checked': overviewsModel.showEnd, 'onChange': updateOverviewModel, 'tabName': this.stringResources.get('tabName_general') }]; }, _setupNavigationEvents: function _setupNavigationEvents() { this.$el.on('keydown', this._onKeyDown.bind(this)); this.$el.on('swiperight', this._onPlaybackPrevious.bind(this)).on('swipeleft', this._onPlaybackNext.bind(this)).on('clicktap', this._onOverviewSceneClick.bind(this)); // This event was introduced to ensure the navigation state recovers in the case where // a transition was caused by the hashchange listener inside impress (e.g back browser button). this._impressStepEnterHandler = function () { // this is because impress doesn't remove its internal hashchange listener window.location.hash = ''; var sceneId = this.$el.find('.step.active')[0].dataset.modelId; // Only do work if we are out of sync. if (sceneId && this.sceneId !== sceneId) { this.layoutController.eventRouter.trigger('scene:jump', { modelId: sceneId }); } }.bind(this); this.$el[0].addEventListener('impress:stepenter', this._impressStepEnterHandler); }, _getViewPort: function _getViewPort() { var viewport = void 0; var currentSceneIndex = this._getSceneIndexById(this.sceneId); if (currentSceneIndex < 0) { // momentarily enable filter-dock if on overviews while getting the viewport size this.layoutController.eventRouter.trigger('filterDock:enable'); viewport = this.$el[0].getBoundingClientRect(); this.layoutController.eventRouter.trigger('filterDock:disable'); } else { viewport = this.$el[0].getBoundingClientRect(); } // If the viewport hasn't rendered yet, we will attempt to resize the layout on show. if (viewport.height === 0 && viewport.width === 0) { this.resizeOnShow(); } return viewport; }, _onResize: function _onResize() { var _this4 = this, _arguments = arguments; return this._layoutIsReady.promise.then(function () { var viewport = _this4._getViewPort(); var currentSceneIndex = _this4._getSceneIndexById(_this4.sceneId); _.each(_this4._scenes, function (scene, index) { _this4._updateSceneSize(scene, viewport); if (currentSceneIndex === index) { _this4._impress.goto(scene.$el[0].id, 0); } }); var info = _this4._getOverviewLocation(viewport); _.each(_this4._overviews, function (item, index) { _this4._updateElementData(item.$el, info); if ((index + 1) * -1 === currentSceneIndex) { _this4._impress.goto(item.$el[0].id, 0); } }); _this4.$el.removeClass('scenesHidden'); PanAndZoomLayout.inherited('_onResize', _this4, _arguments); }); }, onLayoutReady: function onLayoutReady() { var _this5 = this; PanAndZoomLayout.inherited('onLayoutReady', this, arguments); this._updateEndpoints(); this._impress = this._impressFactory('impress_' + this.model.id); this._impress.init(); this._didImpressInit = true; var scene = void 0; if (this.sceneId) { // the indices -1 and -2 are used as scene IDs for start overview and end overview var index = parseInt(this.sceneId, 10); scene = index < 0 ? this._getSceneByIndex(index) : this._getSceneById(this.sceneId); } else if (this.isAuthoringMode || !this.model.showOverviews.showStart) { // goto the first scene in authoring mode even if overviews are enabled scene = this._scenes[0]; } else { scene = this._overviews[0]; } // We don't care about what the sceneId was before. We update it here based on the scene that was retrieved this.sceneId = scene.id; this._pageNavigationController = new NavigationController({ sceneLayoutApi: this.getSceneLayoutApi(), transitionController: new TransitionController({ impress: this._impress }), logger: this.logger, sceneLoader: this._sceneLoader }); this._setupNavigationEvents(); this.currentSceneChanged(); // hide the filter dock if rendering an overview scene if (this._isOverview()) { this.layoutController.eventRouter.trigger('filterDock:collapse'); } _.each(this.model.items, function (model) { this._updateSceneLabel({ model: model }); }.bind(this)); // +1 because the overview is 0 to impress but -1 to us var sceneIndex = this._getCurrentSceneIndex(); this._impress.goto(sceneIndex + 1, 0); return this.onJumpScene({ modelId: this.sceneId, play: false }).then(function () { _this5._onResize(); return _this5._layoutIsReady.resolve(); }); }, /** * @override * @see SceneLayout._getSceneById */ _getSceneById: function _getSceneById(sceneId) { if (sceneId === 'start') { return this._overviews[0]; } else if (sceneId === 'end') { return this._overviews[1]; } return PanAndZoomLayout.inherited('_getSceneById', this, arguments); }, /** * @override * @see SceneLayout._getSceneByIndex */ _getSceneByIndex: function _getSceneByIndex(index) { if (index === -1) { return this._overviews[0]; } else if (index === -2) { return this._overviews[1]; } return PanAndZoomLayout.inherited('_getSceneByIndex', this, arguments); }, /** * @override * @see SceneLayout._getSceneIndexById */ _getSceneIndexById: function _getSceneIndexById(sceneId) { var sceneIndex = PanAndZoomLayout.inherited('_getSceneIndexById', this, arguments); if (sceneIndex === -1) { _.each(this._overviews, function (overview) { if (overview.id === sceneId && overview.id === 'end') { sceneIndex = -2; } }); } return sceneIndex; }, /** * @override * @see SceneLayout._getNextScene */ _getNextScene: function _getNextScene(scene) { var sceneIndex = this._getCurrentSceneIndex(); if (scene && scene.id !== this.sceneId) { sceneIndex = this._getSceneIndex(scene); } if (sceneIndex >= -2 && sceneIndex < this._scenes.length - 1) { return this._getSceneByIndex(sceneIndex + 1); } else if (sceneIndex === this._scenes.length - 1) { return this._getSceneByIndex(-2); } return null; }, /** * @override * @see SceneLayout._getPreviousScene */ _getPreviousScene: function _getPreviousScene(scene) { var sceneIndex = this._getCurrentSceneIndex(); if (scene && scene.id !== this.sceneId) { sceneIndex = this._getSceneIndex(scene); } if (sceneIndex >= 0) { return this._getSceneByIndex(sceneIndex - 1); } else if (sceneIndex === -2) { return this._getSceneByIndex(this._scenes.length - 1); } return null; }, onKeyDown: function onKeyDown(event) { var keyCode = event.keyCode || event.charCode; switch (keyCode) { case 13: // enter case 32: // space this._jumpToScene(event); break; default: break; } }, /** * @returns {Promise} */ didRemovePage: function didRemovePage() { var _this6 = this; return PanAndZoomLayout.inherited('didRemovePage', this, arguments).then(function () { _this6._updateEndpoints(); }); }, _getOverviews: function _getOverviews() { return this._overviews; }, _isOverview: function _isOverview() { return this.sceneId === 'start' || this.sceneId === 'end'; }, /** * @override */ _addScene: function _addScene(model) { PanAndZoomLayout.inherited('_addScene', this, arguments); var scene = this._getSceneById(model.id); this._updateSceneSize(scene, this._getViewPort()); // add index to scene in overview var sceneIndex = this._getSceneIndex(model); var sceneOrder = scene.$el.find('.sceneOrder'); sceneOrder.attr('data-index', sceneIndex + 1); }, /** * @override */ currentSceneChanged: function currentSceneChanged() { PanAndZoomLayout.inherited('currentSceneChanged', this, arguments); this.$el.find('.swapSelected').removeClass('swapSelected'); this.$el.toggleClass('overview', this._isOverview()); }, _onOverviewSceneClick: function _onOverviewSceneClick(event) { this._jumpToScene(event); }, _jumpToScene: function _jumpToScene(event) { var $scene = $(event.target).closest('.overview .step.pageTabContent'); if ($scene.length === 1) { var sceneId = $scene[0].dataset.modelId; this.layoutController.eventRouter.trigger('scene:jump', { modelId: sceneId }); event.stopPropagation(); } }, /** * Updates the DOM if an existing scene and makes impress notices it's changed. * * @param $el element to update * @param data data to use to update $el. should contain the 3 needed data items * @param insertBefore id of the next element in the dom. */ _updateElementData: function _updateElementData($el, data) { //TODO add to impress var impress_updateStep = function (id) { // impress version would just call: initStep(node); var node = document.getElementById(id); var nextNode = node.nextElementSibling; var insertBefore = nextNode ? nextNode.id : undefined; this._impress.removeStep(id); this._impress.addStep('#' + id, insertBefore); }.bind(this); $el.attr({ 'data-scale': data.scale, 'data-x': data.x, 'data-y': data.y }); if (this._didImpressInit) { // all steps have an id, impress ensures that. impress_updateStep($el.attr('id')); } }, _updateSceneSize: function _updateSceneSize(scene, viewport) { var view = scene.getLayoutView(); var overviewBlockerCell = scene.$el.find('.overviewBlockerCell'); if (view) { var info = this._getSceneLocation(view.model.data.positionIndex, viewport); this._updateElementData(scene.$el, info); if (info.stepHeight && info.stepWidth) { var sceneCss = { height: info.stepHeight + 'px', width: info.stepWidth + 'px' }; var viewCss = { height: info.sceneHeight + 'px', width: info.sceneWidth + 'px' }; scene.$el.css(sceneCss); view.$el.css(viewCss); overviewBlockerCell.css(viewCss); } else { var css = { height: info.height + 'px', width: info.width + 'px' }; scene.$el.css(css); view.$el.css(css); overviewBlockerCell.css(css); } } }, _updateSceneLabel: function _updateSceneLabel(payload) { this.$el.find('#' + payload.model.id + '_sceneLabel').text(payload.model.get('title') || ''); }, _updateEndpoints: function _updateEndpoints() { this.$el.find('.sequenceEndpoint').removeClass('sequenceEndpoint sequenceEnd sequenceStart'); var minScenePosition = 0; var maxScenePosition = this.model.items.length - 1; var firstSceneItem = this.model.items.find(function (item) { return item.data.positionIndex === minScenePosition; }); var firstScene = this._getSceneById(firstSceneItem.id); firstScene.$el.addClass('sequenceEndpoint sequenceStart'); var lastSceneItem = this.model.items.find(function (item) { return item.data.positionIndex === maxScenePosition; }); var lastScene = this._getSceneById(lastSceneItem.id); lastScene.$el.addClass('sequenceEndpoint sequenceEnd'); }, /** * Called to get the correct scene scale value from this.sceneLocations * Note: this function takes care of wrapping sceneIndex based on how many scenes are in this.sceneLocations * * @param {number} sceneIndex the index of the scene we want to find the location of * @returns {number} the scale of the scene corresponding to its value in this.sceneLocations * */ getSceneScale: function getSceneScale(sceneIndex) { var numSceneLocations = this.sceneLocations.length; return this.sceneLocations[sceneIndex % numSceneLocations].scale; }, // temporary home for the next 2 methods. // they are common code used by the template implementation -> derived classes (PnZ 1-6) // // These method don't really belong here, // we need an intermediate base for them, or move to composition and have the base class contain them _computeViewportScale: function _computeViewportScale(viewport, sceneHeight, sceneWidth) { var viewportUsage = 0.95; // viewport should subtrct handle height (svg: 24 + padding: 5), the height will split by top and bottom so times 2 var doubleStoryHandleHeight = 58; var scale = 1; if (viewport) { var vScale = (viewport.height - doubleStoryHandleHeight) / sceneHeight * viewportUsage; var hScale = viewport.width / sceneWidth * viewportUsage; scale = Math.min(vScale, hScale); } return scale; }, _computeTemplateScale: function _computeTemplateScale(width, height, padding, factor) { return Math.min((height * factor + padding) / height, (width * factor + padding) / width); } }); return PanAndZoomLayout; }); //# sourceMappingURL=PanAndZoomLayout.js.map