PanAndZoomLayout.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2015, 2020
  5. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  6. */
  7. 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) {
  8. var BLACKLISTED_PROPERTIES = ['fitPage'];
  9. /**
  10. * Pan and zoom layout. Uses impress to navigate (pan or zoom) between scenes
  11. */
  12. var PanAndZoomLayout = SceneLayout.extend({
  13. // Following line is for testing :(
  14. _impressFactory: impress,
  15. // PanAndZoomLayout6 uses CSS to scale it's border so here we only capture the templates
  16. _dropZoneSelector: '.pagetemplateDropZone, .pagetemplateIndicator',
  17. _pageNavigationController: null,
  18. init: function init() {
  19. PanAndZoomLayout.inherited('init', this, arguments);
  20. this._trailingOverviewId = '_overview_' + this.model.id;
  21. // Add onto the sceneLayoutApi
  22. this.extendApi({
  23. 'getOverviews': this._getOverviews.bind(this)
  24. });
  25. this._overviews = [{
  26. id: 'start',
  27. $el: $('#start' + this._trailingOverviewId),
  28. isOverview: true
  29. }, {
  30. id: 'end',
  31. $el: $('#end' + this._trailingOverviewId),
  32. isOverview: true
  33. }];
  34. this._sceneLoader = new SceneLoader({
  35. logger: this.logger,
  36. dashboardApi: this.dashboardApi,
  37. layoutController: this.layoutController
  38. });
  39. this._layoutIsReady = new Deferred();
  40. this.changePageSize({ value: this.model.pageSize });
  41. this.model.on('change:pageSize', this._handlePageUpdate, this);
  42. },
  43. destroy: function destroy() {
  44. var _this = this;
  45. this._sceneLoader.stopBackgroundLoad();
  46. // we have to clear the modification we made to the scenes without telling them.
  47. // (in _updateSceneSize)
  48. (this._scenes || []).forEach(function (scene) {
  49. var view = scene.getLayoutView();
  50. if (view) {
  51. view.$el[0].style.height = null;
  52. view.$el[0].style.width = null;
  53. view.$el.find(_this._dropZoneSelector).each(function (index, element) {
  54. element.style.borderWidth = null;
  55. });
  56. }
  57. });
  58. clearInterval(this._viewPortSetIntervalId);
  59. this.$el[0].removeEventListener('impress:stepenter', this._impressStepEnterHandler);
  60. this.model.off('change:pageSize', this._handlePageUpdate, this);
  61. PanAndZoomLayout.inherited('destroy', this, arguments);
  62. },
  63. _handlePageUpdate: function _handlePageUpdate(options) {
  64. this.changePageSize(options);
  65. this.onResize();
  66. },
  67. changePageSize: function changePageSize() {
  68. //to be overwritten
  69. },
  70. getProperties: function getProperties() {
  71. var _this2 = this;
  72. return PanAndZoomLayout.inherited('getProperties', this, arguments).then(function (properties) {
  73. properties = properties.filter(function (property) {
  74. return BLACKLISTED_PROPERTIES.indexOf(property.id) === -1;
  75. });
  76. return properties.concat(_this2._getShowOverviewProperties());
  77. });
  78. },
  79. _getShowOverviewProperties: function _getShowOverviewProperties() {
  80. var _this3 = this;
  81. var overviewsModel = this.model.get('showOverviews');
  82. // the overview property name are very generic
  83. // to prevent clashing with other properties we add a prefix
  84. var propertyPrefix = 'overview_';
  85. var updateOverviewModel = function updateOverviewModel(propertyName, propertyValue) {
  86. var _showOverviews;
  87. //remove prefix
  88. propertyName = propertyName.substring(propertyPrefix.length);
  89. var updateOptions = {
  90. updateArray: [{
  91. id: _this3.model.id,
  92. showOverviews: (_showOverviews = {}, _showOverviews[propertyName] = propertyValue, _showOverviews)
  93. }]
  94. };
  95. _this3.model.updateModel(updateOptions, _this3, { undoRedoTransactionId: _.uniqueId('layoutShowOverview_') });
  96. };
  97. return [{
  98. 'name': propertyPrefix + 'showStart',
  99. 'sectionName': this.stringResources.get('scenesPropertiesSection'),
  100. 'type': 'CheckBox',
  101. 'label': this.stringResources.get('propShowStoryOverviewFirstSlide'),
  102. 'checked': overviewsModel.showStart,
  103. 'onChange': updateOverviewModel,
  104. 'tabName': this.stringResources.get('tabName_general')
  105. }, {
  106. 'name': propertyPrefix + 'showEnd',
  107. 'sectionName': this.stringResources.get('scenesPropertiesSection'),
  108. 'type': 'CheckBox',
  109. 'label': this.stringResources.get('propShowStoryOverviewLastSlide'),
  110. 'checked': overviewsModel.showEnd,
  111. 'onChange': updateOverviewModel,
  112. 'tabName': this.stringResources.get('tabName_general')
  113. }];
  114. },
  115. _setupNavigationEvents: function _setupNavigationEvents() {
  116. this.$el.on('keydown', this._onKeyDown.bind(this));
  117. this.$el.on('swiperight', this._onPlaybackPrevious.bind(this)).on('swipeleft', this._onPlaybackNext.bind(this)).on('clicktap', this._onOverviewSceneClick.bind(this));
  118. // This event was introduced to ensure the navigation state recovers in the case where
  119. // a transition was caused by the hashchange listener inside impress (e.g back browser button).
  120. this._impressStepEnterHandler = function () {
  121. // this is because impress doesn't remove its internal hashchange listener
  122. window.location.hash = '';
  123. var sceneId = this.$el.find('.step.active')[0].dataset.modelId;
  124. // Only do work if we are out of sync.
  125. if (sceneId && this.sceneId !== sceneId) {
  126. this.layoutController.eventRouter.trigger('scene:jump', { modelId: sceneId });
  127. }
  128. }.bind(this);
  129. this.$el[0].addEventListener('impress:stepenter', this._impressStepEnterHandler);
  130. },
  131. _getViewPort: function _getViewPort() {
  132. var viewport = void 0;
  133. var currentSceneIndex = this._getSceneIndexById(this.sceneId);
  134. if (currentSceneIndex < 0) {
  135. // momentarily enable filter-dock if on overviews while getting the viewport size
  136. this.layoutController.eventRouter.trigger('filterDock:enable');
  137. viewport = this.$el[0].getBoundingClientRect();
  138. this.layoutController.eventRouter.trigger('filterDock:disable');
  139. } else {
  140. viewport = this.$el[0].getBoundingClientRect();
  141. }
  142. // If the viewport hasn't rendered yet, we will attempt to resize the layout on show.
  143. if (viewport.height === 0 && viewport.width === 0) {
  144. this.resizeOnShow();
  145. }
  146. return viewport;
  147. },
  148. _onResize: function _onResize() {
  149. var _this4 = this,
  150. _arguments = arguments;
  151. return this._layoutIsReady.promise.then(function () {
  152. var viewport = _this4._getViewPort();
  153. var currentSceneIndex = _this4._getSceneIndexById(_this4.sceneId);
  154. _.each(_this4._scenes, function (scene, index) {
  155. _this4._updateSceneSize(scene, viewport);
  156. if (currentSceneIndex === index) {
  157. _this4._impress.goto(scene.$el[0].id, 0);
  158. }
  159. });
  160. var info = _this4._getOverviewLocation(viewport);
  161. _.each(_this4._overviews, function (item, index) {
  162. _this4._updateElementData(item.$el, info);
  163. if ((index + 1) * -1 === currentSceneIndex) {
  164. _this4._impress.goto(item.$el[0].id, 0);
  165. }
  166. });
  167. _this4.$el.removeClass('scenesHidden');
  168. PanAndZoomLayout.inherited('_onResize', _this4, _arguments);
  169. });
  170. },
  171. onLayoutReady: function onLayoutReady() {
  172. var _this5 = this;
  173. PanAndZoomLayout.inherited('onLayoutReady', this, arguments);
  174. this._updateEndpoints();
  175. this._impress = this._impressFactory('impress_' + this.model.id);
  176. this._impress.init();
  177. this._didImpressInit = true;
  178. var scene = void 0;
  179. if (this.sceneId) {
  180. // the indices -1 and -2 are used as scene IDs for start overview and end overview
  181. var index = parseInt(this.sceneId, 10);
  182. scene = index < 0 ? this._getSceneByIndex(index) : this._getSceneById(this.sceneId);
  183. } else if (this.isAuthoringMode || !this.model.showOverviews.showStart) {
  184. // goto the first scene in authoring mode even if overviews are enabled
  185. scene = this._scenes[0];
  186. } else {
  187. scene = this._overviews[0];
  188. }
  189. // We don't care about what the sceneId was before. We update it here based on the scene that was retrieved
  190. this.sceneId = scene.id;
  191. this._pageNavigationController = new NavigationController({
  192. sceneLayoutApi: this.getSceneLayoutApi(),
  193. transitionController: new TransitionController({ impress: this._impress }),
  194. logger: this.logger,
  195. sceneLoader: this._sceneLoader
  196. });
  197. this._setupNavigationEvents();
  198. this.currentSceneChanged();
  199. // hide the filter dock if rendering an overview scene
  200. if (this._isOverview()) {
  201. this.layoutController.eventRouter.trigger('filterDock:collapse');
  202. }
  203. _.each(this.model.items, function (model) {
  204. this._updateSceneLabel({ model: model });
  205. }.bind(this));
  206. // +1 because the overview is 0 to impress but -1 to us
  207. var sceneIndex = this._getCurrentSceneIndex();
  208. this._impress.goto(sceneIndex + 1, 0);
  209. return this.onJumpScene({
  210. modelId: this.sceneId,
  211. play: false
  212. }).then(function () {
  213. _this5._onResize();
  214. return _this5._layoutIsReady.resolve();
  215. });
  216. },
  217. /**
  218. * @override
  219. * @see SceneLayout._getSceneById
  220. */
  221. _getSceneById: function _getSceneById(sceneId) {
  222. if (sceneId === 'start') {
  223. return this._overviews[0];
  224. } else if (sceneId === 'end') {
  225. return this._overviews[1];
  226. }
  227. return PanAndZoomLayout.inherited('_getSceneById', this, arguments);
  228. },
  229. /**
  230. * @override
  231. * @see SceneLayout._getSceneByIndex
  232. */
  233. _getSceneByIndex: function _getSceneByIndex(index) {
  234. if (index === -1) {
  235. return this._overviews[0];
  236. } else if (index === -2) {
  237. return this._overviews[1];
  238. }
  239. return PanAndZoomLayout.inherited('_getSceneByIndex', this, arguments);
  240. },
  241. /**
  242. * @override
  243. * @see SceneLayout._getSceneIndexById
  244. */
  245. _getSceneIndexById: function _getSceneIndexById(sceneId) {
  246. var sceneIndex = PanAndZoomLayout.inherited('_getSceneIndexById', this, arguments);
  247. if (sceneIndex === -1) {
  248. _.each(this._overviews, function (overview) {
  249. if (overview.id === sceneId && overview.id === 'end') {
  250. sceneIndex = -2;
  251. }
  252. });
  253. }
  254. return sceneIndex;
  255. },
  256. /**
  257. * @override
  258. * @see SceneLayout._getNextScene
  259. */
  260. _getNextScene: function _getNextScene(scene) {
  261. var sceneIndex = this._getCurrentSceneIndex();
  262. if (scene && scene.id !== this.sceneId) {
  263. sceneIndex = this._getSceneIndex(scene);
  264. }
  265. if (sceneIndex >= -2 && sceneIndex < this._scenes.length - 1) {
  266. return this._getSceneByIndex(sceneIndex + 1);
  267. } else if (sceneIndex === this._scenes.length - 1) {
  268. return this._getSceneByIndex(-2);
  269. }
  270. return null;
  271. },
  272. /**
  273. * @override
  274. * @see SceneLayout._getPreviousScene
  275. */
  276. _getPreviousScene: function _getPreviousScene(scene) {
  277. var sceneIndex = this._getCurrentSceneIndex();
  278. if (scene && scene.id !== this.sceneId) {
  279. sceneIndex = this._getSceneIndex(scene);
  280. }
  281. if (sceneIndex >= 0) {
  282. return this._getSceneByIndex(sceneIndex - 1);
  283. } else if (sceneIndex === -2) {
  284. return this._getSceneByIndex(this._scenes.length - 1);
  285. }
  286. return null;
  287. },
  288. onKeyDown: function onKeyDown(event) {
  289. var keyCode = event.keyCode || event.charCode;
  290. switch (keyCode) {
  291. case 13: // enter
  292. case 32:
  293. // space
  294. this._jumpToScene(event);
  295. break;
  296. default:
  297. break;
  298. }
  299. },
  300. /**
  301. * @returns {Promise}
  302. */
  303. didRemovePage: function didRemovePage() {
  304. var _this6 = this;
  305. return PanAndZoomLayout.inherited('didRemovePage', this, arguments).then(function () {
  306. _this6._updateEndpoints();
  307. });
  308. },
  309. _getOverviews: function _getOverviews() {
  310. return this._overviews;
  311. },
  312. _isOverview: function _isOverview() {
  313. return this.sceneId === 'start' || this.sceneId === 'end';
  314. },
  315. /**
  316. * @override
  317. */
  318. _addScene: function _addScene(model) {
  319. PanAndZoomLayout.inherited('_addScene', this, arguments);
  320. var scene = this._getSceneById(model.id);
  321. this._updateSceneSize(scene, this._getViewPort());
  322. // add index to scene in overview
  323. var sceneIndex = this._getSceneIndex(model);
  324. var sceneOrder = scene.$el.find('.sceneOrder');
  325. sceneOrder.attr('data-index', sceneIndex + 1);
  326. },
  327. /**
  328. * @override
  329. */
  330. currentSceneChanged: function currentSceneChanged() {
  331. PanAndZoomLayout.inherited('currentSceneChanged', this, arguments);
  332. this.$el.find('.swapSelected').removeClass('swapSelected');
  333. this.$el.toggleClass('overview', this._isOverview());
  334. },
  335. _onOverviewSceneClick: function _onOverviewSceneClick(event) {
  336. this._jumpToScene(event);
  337. },
  338. _jumpToScene: function _jumpToScene(event) {
  339. var $scene = $(event.target).closest('.overview .step.pageTabContent');
  340. if ($scene.length === 1) {
  341. var sceneId = $scene[0].dataset.modelId;
  342. this.layoutController.eventRouter.trigger('scene:jump', { modelId: sceneId });
  343. event.stopPropagation();
  344. }
  345. },
  346. /**
  347. * Updates the DOM if an existing scene and makes impress notices it's changed.
  348. *
  349. * @param $el element to update
  350. * @param data data to use to update $el. should contain the 3 needed data items
  351. * @param insertBefore id of the next element in the dom.
  352. */
  353. _updateElementData: function _updateElementData($el, data) {
  354. //TODO add to impress
  355. var impress_updateStep = function (id) {
  356. // impress version would just call: initStep(node);
  357. var node = document.getElementById(id);
  358. var nextNode = node.nextElementSibling;
  359. var insertBefore = nextNode ? nextNode.id : undefined;
  360. this._impress.removeStep(id);
  361. this._impress.addStep('#' + id, insertBefore);
  362. }.bind(this);
  363. $el.attr({
  364. 'data-scale': data.scale,
  365. 'data-x': data.x,
  366. 'data-y': data.y
  367. });
  368. if (this._didImpressInit) {
  369. // all steps have an id, impress ensures that.
  370. impress_updateStep($el.attr('id'));
  371. }
  372. },
  373. _updateSceneSize: function _updateSceneSize(scene, viewport) {
  374. var view = scene.getLayoutView();
  375. var overviewBlockerCell = scene.$el.find('.overviewBlockerCell');
  376. if (view) {
  377. var info = this._getSceneLocation(view.model.data.positionIndex, viewport);
  378. this._updateElementData(scene.$el, info);
  379. if (info.stepHeight && info.stepWidth) {
  380. var sceneCss = {
  381. height: info.stepHeight + 'px',
  382. width: info.stepWidth + 'px'
  383. };
  384. var viewCss = {
  385. height: info.sceneHeight + 'px',
  386. width: info.sceneWidth + 'px'
  387. };
  388. scene.$el.css(sceneCss);
  389. view.$el.css(viewCss);
  390. overviewBlockerCell.css(viewCss);
  391. } else {
  392. var css = {
  393. height: info.height + 'px',
  394. width: info.width + 'px'
  395. };
  396. scene.$el.css(css);
  397. view.$el.css(css);
  398. overviewBlockerCell.css(css);
  399. }
  400. }
  401. },
  402. _updateSceneLabel: function _updateSceneLabel(payload) {
  403. this.$el.find('#' + payload.model.id + '_sceneLabel').text(payload.model.get('title') || '');
  404. },
  405. _updateEndpoints: function _updateEndpoints() {
  406. this.$el.find('.sequenceEndpoint').removeClass('sequenceEndpoint sequenceEnd sequenceStart');
  407. var minScenePosition = 0;
  408. var maxScenePosition = this.model.items.length - 1;
  409. var firstSceneItem = this.model.items.find(function (item) {
  410. return item.data.positionIndex === minScenePosition;
  411. });
  412. var firstScene = this._getSceneById(firstSceneItem.id);
  413. firstScene.$el.addClass('sequenceEndpoint sequenceStart');
  414. var lastSceneItem = this.model.items.find(function (item) {
  415. return item.data.positionIndex === maxScenePosition;
  416. });
  417. var lastScene = this._getSceneById(lastSceneItem.id);
  418. lastScene.$el.addClass('sequenceEndpoint sequenceEnd');
  419. },
  420. /**
  421. * Called to get the correct scene scale value from this.sceneLocations
  422. * Note: this function takes care of wrapping sceneIndex based on how many scenes are in this.sceneLocations
  423. *
  424. * @param {number} sceneIndex the index of the scene we want to find the location of
  425. * @returns {number} the scale of the scene corresponding to its value in this.sceneLocations
  426. *
  427. */
  428. getSceneScale: function getSceneScale(sceneIndex) {
  429. var numSceneLocations = this.sceneLocations.length;
  430. return this.sceneLocations[sceneIndex % numSceneLocations].scale;
  431. },
  432. // temporary home for the next 2 methods.
  433. // they are common code used by the template implementation -> derived classes (PnZ 1-6)
  434. //
  435. // These method don't really belong here,
  436. // we need an intermediate base for them, or move to composition and have the base class contain them
  437. _computeViewportScale: function _computeViewportScale(viewport, sceneHeight, sceneWidth) {
  438. var viewportUsage = 0.95;
  439. // viewport should subtrct handle height (svg: 24 + padding: 5), the height will split by top and bottom so times 2
  440. var doubleStoryHandleHeight = 58;
  441. var scale = 1;
  442. if (viewport) {
  443. var vScale = (viewport.height - doubleStoryHandleHeight) / sceneHeight * viewportUsage;
  444. var hScale = viewport.width / sceneWidth * viewportUsage;
  445. scale = Math.min(vScale, hScale);
  446. }
  447. return scale;
  448. },
  449. _computeTemplateScale: function _computeTemplateScale(width, height, padding, factor) {
  450. return Math.min((height * factor + padding) / height, (width * factor + padding) / width);
  451. }
  452. });
  453. return PanAndZoomLayout;
  454. });
  455. //# sourceMappingURL=PanAndZoomLayout.js.map