FilmStripView.js 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: Storytelling
  5. * (C) Copyright IBM Corp. 2014, 2020
  6. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  7. */
  8. define(['baglass/core-client/js/core-client/ui/core/View', 'baglass/core-client/js/core-client/utils/BrowserUtils', 'baglass/core-client/js/core-client/utils/ContentFormatter', 'baglass/core-client/js/core-client/utils/dom-utils', 'baglass/core-client/js/core-client/utils/Utils', 'text!./templates/FilmStripView.html', 'text!./templates/SceneCell.html', 'text!./templates/AddSceneCell.html', 'text!./templates/OverviewCell.html', 'jquery', 'underscore', 'doT', '../nls/StringResources', 'gemini/app/ui/dialogs/RenameDialog', 'text!../layout/templates/sceneLayoutListing.json', 'storytelling-ui/storytelling-ui.min', './TimelineView', 'gemini/app/ui/dnd/DnDHelper', '../lib/@ba-ui-toolkit/ba-graphics/dist/icons-js/chevron-down_16', '../lib/@ba-ui-toolkit/ba-graphics/dist/icons-js/add_16', '../lib/@ba-ui-toolkit/ba-graphics/dist/icons-js/maximize_16', '../lib/@ba-ui-toolkit/ba-graphics/dist/icons-js/overflow-menu--horizontal_16', '../lib/@ba-ui-toolkit/ba-graphics/dist/illustrations-js/visualization-widget_128', 'react', 'react-dom', 'hammerjs', 'jquery.hammer'], function (View, BrowserUtils, ContentFormatter, DomUtils, Utils, FilmStripViewTemplate, SceneCellTemplate, AddSceneCellTemplate, OverviewCellTemplate, $, _, dot, stringResources, RenameDialog, sceneLayoutListing, StorytellingUI, TimelineView, DnDHelper, ChevronDownIcon, addIcon, maximizeIcon, menuOverflowIcon, visualizationIcon, React, ReactDOM) {
  9. var FilmStripView = View.extend({
  10. templateString: FilmStripViewTemplate,
  11. collapseDuration: 400,
  12. enlargeDuration: 400,
  13. events: {
  14. 'keydown .scene, .overview, .addScene': 'onKeydown'
  15. },
  16. init: function init(options) {
  17. FilmStripView.inherited('init', this, arguments);
  18. this.dashboardApi = options.dashboardApi;
  19. this.controller = options.controller; // StoryPaneController
  20. this.timelineController = this.controller.getTimelineController();
  21. this.canvasController = this.timelineController.canvasController;
  22. this.glassContext = options.glassContext;
  23. this.dndManager = options.dndManager;
  24. this.services = options.services;
  25. this._dropZones = [];
  26. this.logger = this.dashboardApi.getGlassCoreSvc('.Logger');
  27. this.translationService = this.dashboardApi.getDashboardCoreSvc('TranslationService');
  28. this.nextPreviousDuration = 900;
  29. this.nextPreviousDelay = this.nextPreviousDuration / 6;
  30. this.forceReFocus = false;
  31. this.flyoutHolder = document.createElement('div');
  32. this.flyoutHolder.classList.add('flyout-holder');
  33. },
  34. /**
  35. * Renders the film strip
  36. *
  37. * @returns
  38. */
  39. render: function render() {
  40. var _this = this;
  41. this.$el.addClass('filmstrip');
  42. this.contentEl = this.el.parentNode; // FIXME: This line is bad. Filmstrip is technically reaching out of its scope
  43. this.controller.on('change:pageSize', this.onPageSizeChange, this);
  44. this.controller.on('change:showOverviews', this.onShowOverviewsChanged, this);
  45. this.controller.on('layoutType:changed', this.onLayoutTypeChanged, this);
  46. this.controller.on('mode:change', this.onModeChanged, this);
  47. this.controller.on('scene:add', this.onSceneAdded, this);
  48. this.controller.on('scene:collapse', this.onSceneCollapse, this);
  49. this.controller.on('scene:expand', this.onSceneExpand, this);
  50. this.controller.on('scene:remove', this.onSceneRemoved, this);
  51. this.controller.on('scene:select', this.onSceneSelected, this);
  52. this.controller.model.on('change:layout', this.onChangeLayout, this);
  53. this.timelineController.on('scene:reorder', this.onSceneReorder, this);
  54. this.canvasController.addRemoveNotifier.on('widget:addDone', this.onWidgetAdded, this);
  55. this.canvasController.addRemoveNotifier.on('widget:removeDone', this.onWidgetRemoved, this);
  56. var sHtml = this.dotTemplate({});
  57. this.$el.html(sHtml);
  58. this.$sceneSequencer = this.$el.find('.sceneSequencer');
  59. this.$sceneList = this.$el.find('.sceneList');
  60. this.$sceneSequencer.hammer().on('mousedown', '.scene', this.onSceneContextMenu.bind(this)).on('clicktap', '.scene', this.onSceneClick.bind(this)).on('dragstart', '.scene', this.onSceneDragStart.bind(this)) // hammer
  61. .on('hold', '.scene', this.onSelectedSceneHold.bind(this)) // hammer
  62. .on('primaryaction', 'span.overflow', this.onSceneOverflowClick.bind(this)).on('primaryaction', 'span.expandScene', this.onExpandSceneClick.bind(this));
  63. this.cellTemplate = dot.template(SceneCellTemplate);
  64. this.controller.getScenes().forEach(function (item, index) {
  65. var $cell = _this.renderSceneCell(item, index);
  66. _this.$sceneList.append($cell);
  67. _this.middleShortenSceneTitle(_this._getSceneSelector(item.id));
  68. });
  69. this.updateOverviewButton();
  70. this._setupFilmStripDropZone();
  71. this._toggleModeChanged(this.controller.isAuthoring());
  72. return Promise.all([this._addExpandSceneCoachmark(), this._addOverviewSceneCoachmark()]);
  73. },
  74. _scaleCell: function _scaleCell($cell, pageSize) {
  75. if (pageSize && pageSize.width && pageSize.height) {
  76. var DEFAULT_SCENE_HEIGHT = 125;
  77. $cell.css({
  78. width: DEFAULT_SCENE_HEIGHT * pageSize.width / pageSize.height + 'px',
  79. height: DEFAULT_SCENE_HEIGHT + 'px'
  80. });
  81. }
  82. },
  83. renderSceneCell: function renderSceneCell(sceneModel, index) {
  84. var _this2 = this;
  85. var sHtml = this.cellTemplate({
  86. id: sceneModel.id,
  87. index: index,
  88. title: sceneModel.get('title'),
  89. expandSceneLabel: stringResources.get('storySceneExpand'),
  90. sceneActionMenuLabel: stringResources.get('storySceneActionMenu'),
  91. selected: index === this.controller.getSelectedSceneIndex(),
  92. widgetIndicated: this.controller.isScenePopulated(sceneModel.id)
  93. });
  94. var $cell = $(sHtml);
  95. this.translationService.registerView({ view: $cell, model: sceneModel });
  96. Utils.setIcon($cell.find('.expandScene'), maximizeIcon.default.id);
  97. Utils.setIcon($cell.find('.overflow'), menuOverflowIcon.default.id);
  98. Utils.setIcon($cell.find('.widgetIndicator'), visualizationIcon.default.id);
  99. sceneModel.on('change:title', function () {
  100. _this2.onSceneLabelChange(sceneModel);
  101. });
  102. this._scaleCell($cell, this.controller.model.layout.pageSize);
  103. this._setupDropZones($cell);
  104. return $cell;
  105. },
  106. _setupDropZones: function _setupDropZones($cell) {
  107. var _this3 = this;
  108. var cell = $cell[0];
  109. var dropZone = this.dndManager.addDropTarget(cell, {
  110. accepts: function accepts(dragObject) {
  111. return dragObject.type === 'sceneCell' || dragObject.type === 'widget';
  112. },
  113. onDragEnter: function onDragEnter(dragObject, dropNode) {
  114. if (dragObject.type === 'widget') {
  115. dropNode.classList.add('dropZone');
  116. }
  117. },
  118. onDragMove: function onDragMove(dragObject, dropNode) {
  119. if (dragObject.type !== 'sceneCell') {
  120. return;
  121. }
  122. _this3._dropNodeModelId = dropNode.dataset.modelId;
  123. var dropModelIndex = _this3.controller.getSceneIndex(_this3._dropNodeModelId);
  124. if (_this3._dragModelIndex > dropModelIndex) {
  125. _this3.$sceneList[0].insertBefore(_this3._cloneDragEl, dropNode);
  126. } else {
  127. _this3.$sceneList[0].insertBefore(_this3._cloneDragEl, dropNode.nextSibling);
  128. }
  129. },
  130. onDrop: function onDrop(dragObject) {
  131. if (dragObject.type === 'widget') {
  132. DnDHelper.handleWidgetDrop(_this3.dashboardApi.getCanvas(), $cell.attr('data-model-id'), dragObject);
  133. $cell[0].classList.remove('dropZone');
  134. }
  135. },
  136. onDragLeave: function onDragLeave(dragObject, dropNode) {
  137. if (dragObject.type === 'widget') {
  138. dropNode.classList.remove('dropZone');
  139. }
  140. }
  141. });
  142. this._dropZones.push({ dropZone: dropZone, node: cell });
  143. },
  144. /**
  145. * A method to setup a dropzone on the entire filmstrip to handle drag-and-drop events
  146. * on scenes that are dropped outside the sceneList container.
  147. */
  148. _setupFilmStripDropZone: function _setupFilmStripDropZone() {
  149. var _this4 = this;
  150. var filmstrip = this.el;
  151. var sceneList = this.$sceneList[0];
  152. var dropZone = this.dndManager.addDropTarget(filmstrip, {
  153. accepts: function accepts(dragObject) {
  154. return dragObject.type === 'sceneCell';
  155. },
  156. onDragStart: function onDragStart() {
  157. _this4.sceneListBounds = sceneList.getBoundingClientRect();
  158. },
  159. onDragMove: function onDragMove(dragObject) {
  160. var firstScene = sceneList.firstChild;
  161. var lastScene = sceneList.lastChild;
  162. if (dragObject.position.x < _this4.sceneListBounds.left) {
  163. if (firstScene !== _this4._cloneDragEl) {
  164. _this4._dropNodeModelId = firstScene.dataset.modelId;
  165. sceneList.insertBefore(_this4._cloneDragEl, firstScene);
  166. }
  167. } else if (dragObject.position.x > _this4.sceneListBounds.left + _this4.sceneListBounds.width) {
  168. if (lastScene !== _this4._cloneDragEl) {
  169. _this4._dropNodeModelId = lastScene.dataset.modelId;
  170. sceneList.appendChild(_this4._cloneDragEl);
  171. }
  172. }
  173. }
  174. });
  175. this._dropZones.push({ dropZone: dropZone, node: filmstrip });
  176. },
  177. /**
  178. * Create the "add scene" cell that lives at the end of the filmstrip
  179. * but before the end overview scene if it exists and is turned on.
  180. */
  181. renderAddSceneCell: function renderAddSceneCell() {
  182. var addCellTemplate = dot.template(AddSceneCellTemplate);
  183. var sHtml = addCellTemplate({
  184. addTitle: stringResources.get('addSceneBtnTitle'),
  185. chevronTitle: stringResources.get('addSceneChevronBtnTitle')
  186. });
  187. this.$addSceneCell = $(sHtml);
  188. // Place the 'add scene' cell after the 'sceneSequencer' container
  189. this.$sceneSequencer.after(this.$addSceneCell);
  190. this._scaleCell(this.$addSceneCell, this.controller.model.layout.pageSize);
  191. var $plusContainer = this.$addSceneCell.find('.addContainer');
  192. Utils.setIcon($plusContainer, addIcon.default.id);
  193. $plusContainer.on('click', this.onAddSceneClick.bind(this));
  194. var $chevronContainer = this.$addSceneCell.find('.chevron');
  195. Utils.setIcon($chevronContainer, ChevronDownIcon.default.id);
  196. $chevronContainer.on('primaryaction', this.onAddSceneOverflowClick.bind(this));
  197. },
  198. onSceneLabelChange: function onSceneLabelChange(model) {
  199. var sceneSelector = this._getSceneSelector(model.id);
  200. this._getTitleEl(sceneSelector).textContent = model.get('title') || '';
  201. this.middleShortenSceneTitle(sceneSelector);
  202. },
  203. middleShortenSceneTitle: function middleShortenSceneTitle(sceneSelector) {
  204. var sceneTitleEl = this._getTitleEl(sceneSelector);
  205. ContentFormatter.middleShortenString(sceneTitleEl);
  206. },
  207. _getTitleEl: function _getTitleEl(sceneSelector) {
  208. return this.el.querySelector(sceneSelector + ' .pageTitle');
  209. },
  210. onShowOverviewsChanged: function onShowOverviewsChanged() {
  211. this.updateOverviewButton();
  212. },
  213. updateOverviewButton: function updateOverviewButton(updateIcon) {
  214. if (this.controller.showAuthoringOverview()) {
  215. if (!this._$overviewStart) {
  216. this._$overviewStart = this._renderOverviewButton(-1);
  217. this.$el.prepend(this._$overviewStart);
  218. this._scaleCell(this._$overviewStart, this.controller.model.layout.pageSize);
  219. } else if (updateIcon) {
  220. this._updateOverviewIcon(this.$el.find('.overview.start .layoutIcon'), -1);
  221. }
  222. // This bit is for toggling the scene number in the overview frame to show if it is shown or not.
  223. var $pageNum = this._$overviewStart.find('.sceneSubContainer .pageInfo');
  224. if (this.controller.showStartOverview()) {
  225. $pageNum.show();
  226. } else {
  227. $pageNum.hide();
  228. }
  229. } else {
  230. if (this._$overviewStart) {
  231. this._$overviewStart.remove();
  232. this._$overviewStart = null;
  233. }
  234. }
  235. if (this.controller.showEndOverview()) {
  236. if (!this._$overviewEnd) {
  237. this._$overviewEnd = this._renderOverviewButton(-2);
  238. this.$el.append(this._$overviewEnd);
  239. this._scaleCell(this._$overviewEnd, this.controller.model.layout.pageSize);
  240. } else if (updateIcon) {
  241. this._updateOverviewIcon(this.$el.find('.overview.end .layoutIcon'), -2);
  242. }
  243. } else {
  244. if (this._$overviewEnd) {
  245. this._$overviewEnd.remove();
  246. this._$overviewEnd = null;
  247. }
  248. }
  249. },
  250. /**
  251. * Render an overview scene cell
  252. * @param {Number} index The index value that relates to which overview scene to handle rendering of
  253. */
  254. _renderOverviewButton: function _renderOverviewButton(index) {
  255. // if index is -1, this is the beginning overview button (overviewId should be 0)
  256. // if index is -2, this is the end overview button (overviewId should be # scenes + 1)
  257. var label = this._getOverviewLabel(index);
  258. if (!this.overviewCellTemplate) {
  259. this.overviewCellTemplate = dot.template(OverviewCellTemplate);
  260. }
  261. var sHtml = this.overviewCellTemplate({
  262. title: label,
  263. selected: index === this.controller.getSelectedSceneIndex(),
  264. index: this._getOverviewIndex(index)
  265. });
  266. var $el = $(sHtml);
  267. if (index === -1) {
  268. $el.addClass('start').on('primaryaction', this.onOverviewStartClick.bind(this));
  269. } else if (index === -2) {
  270. $el.addClass('end').on('primaryaction', this.onOverviewEndClick.bind(this));
  271. }
  272. this._updateOverviewIcon($el.find('.layoutIcon'), index);
  273. return $el;
  274. },
  275. _getOverviewLabel: function _getOverviewLabel(index) {
  276. var label = void 0;
  277. if (index === -1) {
  278. label = stringResources.get('sceneListStart');
  279. } else if (index === -2) {
  280. label = stringResources.get('sceneListEnd');
  281. } else {
  282. label = stringResources.get('overviewBtnLabel');
  283. }
  284. return label;
  285. },
  286. _getOverviewIndex: function _getOverviewIndex(index) {
  287. var $scenes = this.$sceneList.children();
  288. var overviewIndex = null;
  289. if (index === -1) {
  290. overviewIndex = 0;
  291. } else if (index === -2) {
  292. overviewIndex = $scenes.length + 1;
  293. }
  294. return overviewIndex;
  295. },
  296. _updateOverviewIcon: function _updateOverviewIcon($layoutIcon, index) {
  297. var navModel = this.controller.getNavModel();
  298. var iconName = navModel.indexOf('panAndZoom') >= 0 ? 'dashboard-' + navModel : null;
  299. var label = this._getOverviewLabel(index);
  300. if (iconName) {
  301. $layoutIcon.empty();
  302. Utils.setIcon($layoutIcon, iconName, label);
  303. }
  304. },
  305. remove: function remove() {
  306. var _this5 = this;
  307. if (this.controller) {
  308. this.controller.off('change:pageSize', this.onPageSizeChange, this);
  309. this.controller.off('change:showOverviews', this.onShowOverviewsChanged, this);
  310. this.controller.off('layoutType:changed', this.onLayoutTypeChanged, this);
  311. this.controller.off('mode:change', this.onModeChanged, this);
  312. this.controller.off('scene:add', this.onSceneAdded, this);
  313. this.controller.off('scene:collapse', this.onSceneCollapse, this);
  314. this.controller.off('scene:expand', this.onSceneExpand, this);
  315. this.controller.off('scene:remove', this.onSceneRemoved, this);
  316. this.controller.off('scene:select', this.onSceneSelected, this);
  317. var scenes = this.controller.getScenes() || [];
  318. scenes.forEach(function (scene) {
  319. scene.off('change:title', null, _this5);
  320. });
  321. if (this.controller.model) {
  322. this.controller.model.off('change:layout', this.onChangeLayout, this);
  323. }
  324. }
  325. this.timelineController.off('scene:reorder', this.onSceneReorder, this);
  326. // This check is here because there is a destruction path that causes the
  327. // addRemoveNotifier to be unset before execution gets here
  328. if (this.canvasController && this.canvasController.addRemoveNotifier) {
  329. this.canvasController.addRemoveNotifier.off('widget:addDone', this.onWidgetAdded, this);
  330. this.canvasController.addRemoveNotifier.off('widget:removeDone', this.onWidgetRemoved, this);
  331. }
  332. if (this._dropZones) {
  333. this._dropZones.forEach(function (zone) {
  334. if (zone.dropZone && zone.node) {
  335. zone.dropZone.remove();
  336. // Remove scenes from the filmstrip
  337. if (zone.node !== _this5.el) {
  338. zone.node.parentNode.removeChild(zone.node);
  339. }
  340. }
  341. });
  342. this._dropZones = [];
  343. }
  344. FilmStripView.inherited('remove', this, arguments);
  345. },
  346. onAddSceneClick: function onAddSceneClick() {
  347. var _this6 = this;
  348. this.controller.addScene().catch(function (error) {
  349. _this6.logger.error('Unable to add new scene to story', error);
  350. });
  351. },
  352. onSceneClick: function onSceneClick(event) {
  353. event.stopPropagation();
  354. this._closeFlyout();
  355. var index = this.$sceneList.children().index(event.currentTarget);
  356. var modelId = event.currentTarget.dataset.modelId;
  357. this.controller.selectScene({
  358. index: index,
  359. modelId: modelId
  360. });
  361. },
  362. onOverviewStartClick: function onOverviewStartClick(event) {
  363. event.stopPropagation();
  364. this.controller.selectScene({ index: -1 });
  365. },
  366. onOverviewEndClick: function onOverviewEndClick(event) {
  367. event.stopPropagation();
  368. this.controller.selectScene({ index: -2 });
  369. },
  370. onExpandSceneClick: function onExpandSceneClick(event) {
  371. event.stopPropagation();
  372. this.controller.expandScene();
  373. },
  374. /**
  375. * Controller events
  376. */
  377. onModeChanged: function onModeChanged(event) {
  378. this._toggleModeChanged(event.authoring);
  379. this.updateOverviewButton();
  380. },
  381. /**
  382. * @param {LayoutEvent} event This is the event triggered by StoryPaneController.onAddLayout()
  383. */
  384. onSceneAdded: function onSceneAdded(event) {
  385. this._addScene(event.scene, event.index, event.insertBefore);
  386. this._refreshCellPageNumbers();
  387. },
  388. onSceneRemoved: function onSceneRemoved(event) {
  389. this._removeScene(event.id);
  390. this._refreshCellPageNumbers();
  391. this.updateOverviewButton();
  392. },
  393. onSceneSelected: function onSceneSelected(event) {
  394. this._selectScene(event);
  395. this._refreshTimeline();
  396. },
  397. onSceneExpand: function onSceneExpand(event) {
  398. this._expandScene(this.$sceneList.children().eq(event.index));
  399. },
  400. onSceneCollapse: function onSceneCollapse(event) {
  401. this._collapseScene(this.$sceneList.children().eq(event.index));
  402. },
  403. onLayoutTypeChanged: function onLayoutTypeChanged() {
  404. this.updateOverviewButton(true);
  405. },
  406. onPageSizeChange: function onPageSizeChange(pageSize) {
  407. var _this7 = this;
  408. this.$sceneList.children().each(function (index, cell) {
  409. _this7._scaleCell($(cell), pageSize);
  410. });
  411. this._scaleCell(this.$addSceneCell, pageSize);
  412. if (this._$overviewStart) {
  413. this._scaleCell(this._$overviewStart, pageSize);
  414. }
  415. if (this._$overviewEnd) {
  416. this._scaleCell(this._$overviewEnd, pageSize);
  417. }
  418. },
  419. onSceneContextMenu: function onSceneContextMenu(event) {
  420. // right-click
  421. if (event.which === 3) {
  422. this.onSceneOverflowClick(event);
  423. }
  424. },
  425. onSceneOverflowClick: function onSceneOverflowClick(event) {
  426. event.stopPropagation();
  427. if (this.preventContext || !this.controller.isAuthoring()) {
  428. this.preventContext = false;
  429. } else {
  430. if (this._flyout) {
  431. this._closeFlyout();
  432. return;
  433. }
  434. var $target = $(event.currentTarget).closest('[data-model-id]');
  435. var sceneId = $target.data('modelId');
  436. var options = {
  437. name: this.controller.getScene(this.controller.getSceneIndex(sceneId)).get('title'),
  438. type: 'Scene',
  439. allowEmpty: true,
  440. id: _.uniqueId('renameDialog')
  441. };
  442. var renameDialog = new RenameDialog(options, function (newName) {
  443. this.controller.renameScene(sceneId, newName);
  444. }.bind(this));
  445. var renameDialogCallback = function renameDialogCallback() {
  446. renameDialog.open();
  447. };
  448. // Making sure the anchor element is always the scene
  449. var anchorElement = event.currentTarget;
  450. if (anchorElement.parentNode.id === sceneId + '_tablabel') {
  451. anchorElement = anchorElement.parentNode;
  452. }
  453. this.$el.append(this.flyoutHolder);
  454. this._flyout = React.createElement(StorytellingUI.SceneCellFlyout, {
  455. sceneId: sceneId,
  456. controller: this.controller,
  457. anchorElement: anchorElement,
  458. closeFlyout: this._closeFlyout.bind(this),
  459. renameDialog: renameDialogCallback
  460. });
  461. ReactDOM.render(this._flyout, this.flyoutHolder);
  462. }
  463. },
  464. onAddSceneOverflowClick: function onAddSceneOverflowClick(event) {
  465. //TODO: Find a better way to access this element
  466. var target = event.currentTarget.children[0];
  467. var options = {
  468. target: event.currentTarget,
  469. onOpen: function onOpen() {
  470. // can't use .classList.add() because it is not supported in IE11 for SVG elements
  471. target.setAttribute('class', 'svgIcon selected');
  472. },
  473. onClose: function () {
  474. // can't use .classList.remove() because it is not supported in IE11 for SVG elements
  475. target.setAttribute('class', 'svgIcon');
  476. this._closeFlyout();
  477. }.bind(this)
  478. };
  479. // Toggle open/close of add scene flyout
  480. if (this._flyout) {
  481. options.onClose();
  482. } else {
  483. this._showContextMenu(options);
  484. }
  485. },
  486. _showContextMenu: function _showContextMenu(options) {
  487. if (this.preventContext || !this.controller.isAuthoring()) {
  488. this.preventContext = false;
  489. } else if (this.controller.isAuthoring()) {
  490. this.$el.append(this.flyoutHolder);
  491. this._flyout = React.createElement(StorytellingUI.AddSceneFlyout, {
  492. anchorElement: options.target,
  493. closeFlyout: options.onClose,
  494. onOpen: options.onOpen,
  495. onLayoutSelection: this.controller.addScene.bind(this.controller),
  496. stringResources: this.services.getSvcSync('.StringResources'),
  497. sceneLayoutListing: JSON.parse(sceneLayoutListing).sceneLayouts
  498. });
  499. ReactDOM.render(this._flyout, this.flyoutHolder);
  500. }
  501. },
  502. _closeFlyout: function _closeFlyout() {
  503. if (this._flyout) {
  504. ReactDOM.unmountComponentAtNode(this.flyoutHolder);
  505. this.$el.find('.flyout-holder').remove();
  506. this._flyout = null;
  507. this.preventContext = false;
  508. }
  509. },
  510. /**
  511. * Event handler for 'scene:reorder' event from TimelineController
  512. */
  513. onSceneReorder: function onSceneReorder(event) {
  514. var swapElements = function swapElements(element, target) {
  515. if (element !== target) {
  516. var parent = element.parentNode;
  517. var placeholder = parent.insertBefore(document.createTextNode(''), element);
  518. parent.insertBefore(element, target);
  519. parent.insertBefore(target, placeholder);
  520. parent.removeChild(placeholder);
  521. }
  522. };
  523. // Get the updated list of scenes (after they've been reordered)
  524. // and update the FilmStrip's scene list
  525. var updatedSceneList = event.model.getParent().items;
  526. _.each(updatedSceneList, function (scene, index) {
  527. var $currentCell = this.$sceneList.children().eq(index);
  528. var $cell = this.$sceneList.find(this._getSceneSelector(scene.id));
  529. swapElements($cell[0], $currentCell[0]);
  530. }, this);
  531. // Remove any coach marks from the scene when it is moved
  532. var $scene = this.$sceneList.find('#' + event.model.id + '_tablabel>.expandScene');
  533. $scene.find('.coachMark').remove();
  534. this._refreshCellPageNumbers();
  535. },
  536. /**
  537. * The method used when listening for the 'dragstart' (hammer) event
  538. * @param {Event} event A mouse event
  539. */
  540. onSceneDragStart: function onSceneDragStart(event) {
  541. if (!DomUtils.isPointerTouch(event) && this.controller.isAuthoring()) {
  542. this._startReorderScenes(event);
  543. }
  544. },
  545. /**
  546. * The method used when listening for the 'hold' (hammer) event
  547. * @param {Event} event A mouse event
  548. */
  549. onSelectedSceneHold: function onSelectedSceneHold(event) {
  550. if (event.currentTarget.classList.contains('selected') && this.controller.isAuthoring()) {
  551. this._shakeOnHold(event.currentTarget);
  552. this._startReorderScenes(event);
  553. }
  554. },
  555. /**
  556. * @private
  557. * @param {Event} event The event generated from a mouse drag or touch hold
  558. */
  559. _startReorderScenes: function _startReorderScenes(event) {
  560. var _this8 = this;
  561. var sceneEl = event.currentTarget;
  562. var $sceneEl = $(sceneEl);
  563. var $avatar = this._makeAvatar($sceneEl);
  564. this._cloneDragEl = $avatar[0].cloneNode();
  565. this._cloneDragEl.classList.remove('sceneMoveAvatar');
  566. this._cloneDragEl.classList.add('scenePlaceholder');
  567. this._dragModelIndex = this.controller.getSceneIndex(event.currentTarget.dataset.modelId);
  568. var sceneModel = this.controller.getSceneById(event.currentTarget.dataset.modelId);
  569. var targetSelected = sceneEl.classList.contains('selected');
  570. /* Make sure the avatar is aligned with the labels*/
  571. var offset = $sceneEl.offset();
  572. var eventPos = DomUtils.getEventPos(event);
  573. var avatarYOffset = offset.top - eventPos.pageY;
  574. var avatarXOffset = offset.left - eventPos.pageX;
  575. this.dndManager.startDrag({
  576. event: event,
  577. type: 'sceneCell',
  578. data: sceneModel,
  579. avatar: $avatar[0],
  580. moveXThreshold: 20, // move horizontally 20 pixels to enable dragging. This will allow vertical scrolling/panning
  581. avatarYOffset: avatarYOffset,
  582. avatarXOffset: avatarXOffset,
  583. restrictToXAxis: true,
  584. callerCallbacks: {
  585. onDragStart: function onDragStart() {
  586. _this8.$el.addClass('noScroll');
  587. _this8._dropNodeModelId = null;
  588. _this8.$sceneList[0].insertBefore(_this8._cloneDragEl, sceneEl);
  589. sceneEl.style.display = 'none';
  590. sceneEl.classList.remove('selected');
  591. sceneEl.classList.remove('shake');
  592. },
  593. onDragDone: function onDragDone() {
  594. if (_this8._dropNodeModelId) {
  595. var dropModelIndex = _this8.controller.getSceneIndex(_this8._dropNodeModelId);
  596. var dropModel = _this8.controller.getSceneById(_this8._dropNodeModelId);
  597. _this8.beforeModelId = dropModel.getNextSiblingId();
  598. if (_this8._dragModelIndex > dropModelIndex) {
  599. _this8.beforeModelId = dropModel.id;
  600. }
  601. }
  602. _this8.$sceneList[0].insertBefore(sceneEl, _this8._cloneDragEl);
  603. if (BrowserUtils.isIE11()) {
  604. _this8.$sceneList[0].removeChild(_this8._cloneDragEl);
  605. } else {
  606. _this8._cloneDragEl.remove();
  607. }
  608. sceneEl.style.display = '';
  609. if (targetSelected) {
  610. sceneEl.classList.add('selected');
  611. }
  612. _this8.el.classList.remove('noScroll');
  613. if (_this8._dropNodeModelId) {
  614. _this8.controller.moveViewBefore(sceneModel, _this8.beforeModelId);
  615. }
  616. _this8.beforeModelId = null;
  617. _this8._cloneDragEl = null;
  618. _this8._dropNodeModelId = null;
  619. return true;
  620. }
  621. }
  622. });
  623. },
  624. /**
  625. * Clone an element and append it to the body of the document
  626. * @private
  627. * @param {jQuery} $target A jQuery object representing an element on the page that will be cloned
  628. *
  629. * @return {jQuery} The cloned element as a jquery object
  630. */
  631. _makeAvatar: function _makeAvatar($target) {
  632. var $avatar = $target.clone().addClass('sceneMoveAvatar');
  633. var avatar = $avatar[0];
  634. // Remove some unneeded attributes (from the clone) before placing the element on the page
  635. avatar.removeAttribute('id');
  636. avatar.removeAttribute('tabindex');
  637. avatar.removeAttribute('role');
  638. avatar.removeAttribute('data-model-id');
  639. $avatar.find('.overflow').remove();
  640. $avatar.find('.expandScene').remove();
  641. $avatar.find('.pageInfo').remove();
  642. document.body.appendChild(avatar);
  643. return $avatar;
  644. },
  645. onWidgetAdded: function onWidgetAdded(event) {
  646. var sceneId = this._getWidgetSceneId(event.id);
  647. this._widgetIndicated(sceneId);
  648. },
  649. onWidgetRemoved: function onWidgetRemoved(event) {
  650. var sceneId = this._getWidgetSceneId(event.id);
  651. this._widgetIndicated(sceneId);
  652. },
  653. /*
  654. * Helpers
  655. */
  656. _getWidgetSceneId: function _getWidgetSceneId(widgetId) {
  657. var result = void 0;
  658. if (this.controller && this.controller.model) {
  659. var parent = this.controller.model.layout.findTopLevelParentItem(widgetId);
  660. if (parent) {
  661. result = this.controller.getSceneIndex(parent.id) > -1 ? parent.id : null;
  662. }
  663. }
  664. return result;
  665. },
  666. _widgetIndicated: function _widgetIndicated(sceneId) {
  667. var toggle;
  668. if (sceneId) {
  669. toggle = this.controller.isScenePopulated(sceneId);
  670. this.$el.find(this._getSceneSelector(sceneId) + ' .widgetIndicator').toggleClass('widgetIndicated', toggle);
  671. } else {
  672. toggle = this.timelineController.getTimelineEpisodeCount() > 0;
  673. this.$el.find('.scene.selected .widgetIndicator').toggleClass('widgetIndicated', toggle);
  674. }
  675. },
  676. _widgetIndicatedMove: function _widgetIndicatedMove(moveSceneId, $cell) {
  677. //update widget indicator for selected scene
  678. this._widgetIndicated();
  679. //update widget indicator for scene widget is moved to
  680. var toggle = this.controller.isScenePopulated(moveSceneId);
  681. $cell.find('.widgetIndicator').toggleClass('widgetIndicated', toggle);
  682. },
  683. _findTopLevelParentItem: function _findTopLevelParentItem(value) {
  684. var parents = [];
  685. if (value && value.parameter && value.parameter.updateArray) {
  686. var array = value.parameter.updateArray;
  687. parents = _.map(array, function (element) {
  688. var parent = this.controller.model.layout.findTopLevelParentItem(element.parentId);
  689. return parent && parent.id && !this.controller.isOverview() ? parent.id : element.parentId;
  690. }.bind(this));
  691. }
  692. return parents;
  693. },
  694. onChangeLayout: function onChangeLayout(event) {
  695. this._updateWidgetIndicator(event);
  696. if (this.timelineView) {
  697. this._showTimeline(0);
  698. }
  699. },
  700. _updateWidgetIndicator: function _updateWidgetIndicator(event) {
  701. var prevParentId = this._findTopLevelParentItem(event.prevValue);
  702. var newParentId = this._findTopLevelParentItem(event.value);
  703. if (prevParentId.length === 0 || newParentId.length === 0 || _.intersection(prevParentId, newParentId).length !== 0) {
  704. return;
  705. }
  706. for (var i = 0; i < prevParentId.length; i++) {
  707. this._widgetIndicated(prevParentId[i]);
  708. }
  709. for (i = 0; i < newParentId.length; i++) {
  710. this._widgetIndicated(newParentId[i]);
  711. }
  712. },
  713. _toggleModeChanged: function _toggleModeChanged(authoringFlag) {
  714. this.$el.toggleClass('authoring', authoringFlag);
  715. var $addSceneEl = this.$el.find('.addScene');
  716. if (authoringFlag) {
  717. if ($addSceneEl.length === 0) {
  718. this.renderAddSceneCell();
  719. }
  720. } else {
  721. // Otherwise remove the add scene cell and its events
  722. $addSceneEl.off().remove();
  723. }
  724. // Close timeline if user is leaving authoring mode
  725. if (!authoringFlag && this.timelineView) {
  726. this.controller.collapseScene();
  727. }
  728. },
  729. _getSceneSelector: function _getSceneSelector(id) {
  730. return '#' + id + '_tablabel';
  731. },
  732. /**
  733. * Add a scene cell to the appropriate place in the sceneList
  734. * @private
  735. */
  736. _addScene: function _addScene(scene, index, insertBeforeId) {
  737. var $cell = this.renderSceneCell(scene, index);
  738. if (insertBeforeId) {
  739. this.$sceneList.find(this._getSceneSelector(insertBeforeId)).before($cell);
  740. } else {
  741. this.$sceneList.find('.scene:last-child').after($cell);
  742. }
  743. this._addExpandSceneCoachmark();
  744. },
  745. _removeScene: function _removeScene(id) {
  746. // When a scene is removed from the film strip, we lose focus of the film strip.
  747. // selectScene() only focuses on a scene if the film strip is focused, so we force refocus.
  748. this.forceReFocus = true;
  749. var sceneSelector = this._getSceneSelector(id);
  750. var sceneCell = this.$sceneList[0].querySelector(sceneSelector);
  751. this._dropZones = this._dropZones.filter(function (zone) {
  752. if (zone.node === sceneCell) {
  753. zone.dropZone.remove();
  754. zone.node.parentNode.removeChild(zone.node);
  755. return false;
  756. }
  757. return true;
  758. });
  759. this.translationService.deregisterView(id);
  760. },
  761. _refreshCellPageNumbers: function _refreshCellPageNumbers() {
  762. var $scenes = this.$sceneList.children();
  763. for (var i = 0; i < $scenes.length; i++) {
  764. $scenes.eq(i).find('.pageNumber')[0].setAttribute('data-index', i + 1);
  765. }
  766. // if beginning and end overviews exist, reset their id
  767. // this will change once we allow enabling/disabling each overview
  768. var $overviews = this.$el.find('.overview');
  769. if ($overviews.length >= 2) {
  770. $overviews.eq(0).find('.pageNumber')[0].setAttribute('data-index', 0);
  771. $overviews.eq(1).find('.pageNumber')[0].setAttribute('data-index', $scenes.length + 1);
  772. }
  773. },
  774. /**
  775. * Add an empty div with 100% width to force the scroll view to be scrollable.
  776. *
  777. * Reason: scrolling of the scroll view is necessary during the animation to make it look slick.
  778. * When the scroll view's content is not scrollable (ie. 1 or 2 scenes), then the scroll animation
  779. * can only occur once the resizing cell is large enough to introduce scroll in the parent. This
  780. * results in a jarring animation.
  781. */
  782. _createAnimationDummyDiv: function _createAnimationDummyDiv() {
  783. return $('<div />').css({
  784. width: '100%',
  785. height: '1px',
  786. display: 'inline-block'
  787. });
  788. },
  789. _expandScene: function _expandScene($scene) {
  790. var _this9 = this;
  791. var scrollLeft = this.$el.scrollLeft();
  792. var position = $scene.position();
  793. var marginLeft = parseInt($scene.css('margin-left'), 10);
  794. var originalWidth = $scene.css('width');
  795. var originalHeight = $scene.css('height');
  796. var $sceneElements = $scene.find('.sceneOverlay, .widgetIndicator');
  797. var duration = this.enlargeDuration;
  798. // Add an empty div with 100% width to force the scroll view to be scrollable.
  799. var dummyDiv = this._createAnimationDummyDiv().appendTo(this.$el);
  800. // remove coachmark
  801. this._removeExpandSceneCoachmark();
  802. // close flyout
  803. this._closeFlyout();
  804. this.$el
  805. // Issue on Firefox: scroll location changes when appending items. Reset the scroll.
  806. .scrollLeft(scrollLeft)
  807. // Prevent scrollbars from showing during the animation.
  808. .css('overflow-x', 'hidden');
  809. // The animation is about to begin.
  810. $scene.addClass('animating');
  811. // Fade out the overlay elements and the widgetIndicator element
  812. $sceneElements.fadeOut(duration / 3);
  813. // Animate the cell to fit the full width and height.
  814. $scene.animate({
  815. width: this.$el.outerWidth(true),
  816. height: this.$el.outerHeight(true),
  817. opacity: 0
  818. }, {
  819. duration: duration,
  820. complete: function complete() {
  821. _this9._showTimeline(duration / 4, function () {
  822. // Hide the film strip view as we are now showing the timeline view.
  823. _this9.$el.hide();
  824. // The animation is finished.
  825. $scene.removeClass('animating');
  826. // Reset the cell size.
  827. $scene.css({
  828. width: originalWidth,
  829. height: originalHeight,
  830. opacity: 1
  831. });
  832. // Reset the display overlays and widgetIndicator
  833. $sceneElements.css('display', '');
  834. // Remove the dummy div.
  835. dummyDiv.remove();
  836. });
  837. }
  838. });
  839. // Animate to the end scroll position.
  840. this.$el.animate({
  841. scrollLeft: position.left + marginLeft + scrollLeft
  842. }, duration);
  843. },
  844. _collapseScene: function _collapseScene($scene) {
  845. var _this10 = this;
  846. this.$el.show();
  847. var scrollLeft = this.$el.scrollLeft();
  848. var fullWidth = this.$el.outerWidth(true);
  849. var fullHeight = this.$el.outerHeight(true);
  850. var marginLeft = parseInt($scene.css('margin-left'), 10);
  851. var originalWidth = $scene.css('width');
  852. var originalHeight = $scene.css('height');
  853. var duration = this.collapseDuration;
  854. // Add an empty div with 100% width to force the view to be scrollable.
  855. var $dummyDiv = this._createAnimationDummyDiv().appendTo(this.$sceneList);
  856. // Issue on Firefox: scroll location changes when appending items. Reset the scroll.
  857. this.$el.scrollLeft(scrollLeft);
  858. // The animation is about to begin.
  859. $scene.addClass('animating');
  860. // Fade-out and remove the timeline.
  861. if (this.timelineView) {
  862. this.timelineView.fadeOut({
  863. complete: function complete() {
  864. _this10.timelineView.remove();
  865. _this10.timelineView = null;
  866. },
  867. duration: duration / 4
  868. });
  869. }
  870. // Enlarge the overlays and widgetIndicator after the cell animation completes.
  871. $scene.find('.sceneOverlay, .widgetIndicator').css({
  872. transform: 'scale(0)',
  873. opacity: 0
  874. }).delay(duration).animate({
  875. transform: 1,
  876. opacity: 1
  877. }, {
  878. step: function step(now, fx) {
  879. if (fx.prop === 'transform') {
  880. this.style.transform = 'scale(' + now + ')';
  881. }
  882. },
  883. complete: function complete() {
  884. this.style.transform = null;
  885. this.style.opacity = null;
  886. },
  887. duration: duration / 2
  888. });
  889. // Prior to animation: set the resize cell to the full width and height.
  890. $scene.css({
  891. width: fullWidth,
  892. height: fullHeight,
  893. opacity: 0
  894. });
  895. // Prior to animation: set the scroll so that cell is aligned in the page.
  896. scrollLeft = this.$el.scrollLeft();
  897. this.$el.scrollLeft($scene.position().left + marginLeft + scrollLeft);
  898. // Animate back to the original cell size.
  899. $scene.animate({
  900. width: originalWidth,
  901. height: originalHeight,
  902. opacity: 1
  903. }, {
  904. duration: duration,
  905. complete: function complete() {
  906. // The animation is finished.
  907. $scene.removeClass('animating');
  908. // Reset scrollbars.
  909. _this10.$el.css('overflow-x', 'auto');
  910. // Remove the dummy div.
  911. $dummyDiv.remove();
  912. }
  913. });
  914. // Animate to the end scroll position.
  915. this.$el.animate({
  916. scrollLeft: scrollLeft - parseInt(this.$sceneList.css('margin-left'), 10)
  917. }, duration);
  918. },
  919. _refreshTimeline: function _refreshTimeline() {
  920. var _this11 = this;
  921. if (this.timelineView) {
  922. this.timelineView.fadeOut({
  923. complete: function complete() {
  924. // Guard against concurrent dismissal of the timeline, for instance.
  925. if (_this11.timelineView) {
  926. _this11._showTimeline(_this11.enlargeDuration / 4);
  927. }
  928. }
  929. });
  930. }
  931. },
  932. _showTimeline: function _showTimeline(duration, complete) {
  933. if (this.timelineView) {
  934. this.timelineView.remove();
  935. this.timelineView = null;
  936. }
  937. if (this.timelineController.getCurrentScene() !== null) {
  938. this._createTimeline();
  939. this.timelineView.renderFadeIn({
  940. complete: complete,
  941. duration: duration
  942. });
  943. } else {
  944. // If execution gets in here it means one of two things:
  945. // 1. User navigated to an overview (in GJ)
  946. // 2. The bug in IE when converting caused current scene data to be lost
  947. var sceneIndex = this.controller.currentSceneIndex;
  948. if (sceneIndex === -1) {
  949. sceneIndex = 0;
  950. } else if (sceneIndex === -2) {
  951. sceneIndex = this.controller.getSceneCount() - 1;
  952. }
  953. this._collapseScene(this.$sceneList.children().eq(sceneIndex));
  954. }
  955. },
  956. _createTimeline: function _createTimeline() {
  957. var timelineNode = document.createElement('div');
  958. this.contentEl.appendChild(timelineNode); // FIXME: This line is bad. Filmstrip is technically reaching out of its scope
  959. this.timelineView = new TimelineView({
  960. el: timelineNode,
  961. storyController: this.controller,
  962. controller: this.timelineController,
  963. dashboardApi: this.dashboardApi,
  964. dndManager: this.dndManager,
  965. glassContext: this.glassContext,
  966. services: this.services
  967. });
  968. },
  969. _removeExpandSceneCoachmark: function _removeExpandSceneCoachmark() {
  970. // following line is document scoped because we may need to remove in many opened stories.
  971. // After some testing it seems like glass now removes an inactive perspective's HTML from the DOM
  972. // so this document scoped query isn't needed anymore as it doesn't work as desired
  973. var $firstScenes = $('.expandScene.coachMarkContainer');
  974. if ($firstScenes.find('.coachMark:visible').length) {
  975. var coachMarkReadId = $firstScenes.attr('id');
  976. var persistence = this.dashboardApi.getGlassCoreSvc('.CoachMarkService').getPersistence();
  977. persistence.marksAsRead(coachMarkReadId);
  978. $firstScenes.find('.coachMark').hide();
  979. }
  980. },
  981. _addExpandSceneCoachmark: function _addExpandSceneCoachmark() {
  982. var $target = this.$sceneList.find('.expandScene').first();
  983. var options = {
  984. id: 'com.ibm.bi.dashboard.filmstrip.expandScene',
  985. domElement: $target[0],
  986. title: stringResources.get('expandSceneCoachmarkTitle'),
  987. contents: stringResources.get('expandSceneCoachmarkContents')
  988. };
  989. this.dashboardApi.prepareGlassOptions(options);
  990. var coachMarkApi = this.dashboardApi.getFeature('CoachMark');
  991. return coachMarkApi.addCoachMark(options);
  992. },
  993. _addOverviewSceneCoachmark: function _addOverviewSceneCoachmark() {
  994. var $target = this.$el.find('.overview').first();
  995. if ($target.length === 1) {
  996. var options = {
  997. id: 'com.ibm.bi.dashboard.filmstrip.overview',
  998. domElement: $target[0],
  999. title: stringResources.get('overviewCoachmarkTitle'),
  1000. contents: stringResources.get('overviewCoachmarkContents')
  1001. };
  1002. this.dashboardApi.prepareGlassOptions(options);
  1003. var coachMarkApi = this.dashboardApi.getFeature('CoachMark');
  1004. return coachMarkApi.addCoachMark(options);
  1005. }
  1006. return Promise.resolve();
  1007. },
  1008. _selectScene: function _selectScene(event) {
  1009. this._closeFlyout();
  1010. var sceneId = event.scene && event.scene.id;
  1011. if (!sceneId) {
  1012. return;
  1013. }
  1014. var $scene = void 0;
  1015. if (sceneId === 'start' || sceneId === 'end') {
  1016. $scene = this.$el.find('.overview.' + sceneId);
  1017. } else {
  1018. $scene = this.$sceneList.find(this._getSceneSelector(sceneId));
  1019. }
  1020. if ($scene.length !== 1) {
  1021. return;
  1022. }
  1023. this.$el.find('.overview.selected, .scene.selected').removeClass('selected').attr('tabindex', -1);
  1024. $scene.addClass('selected').attr('tabindex', 0);
  1025. if (this.forceReFocus || $.contains(this.el, document.activeElement) && $scene.is(':visible')) {
  1026. // If we are focused inside the film strip or if the force refocus flag is set, move the focus.
  1027. $scene.focus();
  1028. this.forceReFocus = false;
  1029. }
  1030. var scroller = this._getSceneScrollPosition($scene);
  1031. this.$el.scrollLeft(scroller.scrollLeft).animate({
  1032. scrollLeft: scroller.position
  1033. }, 400);
  1034. },
  1035. /**
  1036. * @param {Element} target The DOM element to add the shake class to
  1037. */
  1038. _shakeOnHold: function _shakeOnHold(target) {
  1039. this._closeFlyout();
  1040. this.preventContext = true;
  1041. target.classList.add('shake');
  1042. this.el.classList.add('noScroll');
  1043. // note: we use "one" here to make sure it only runs once (for each event type)
  1044. $(target).one('mouseup touchup', function () {
  1045. target.classList.remove('shake');
  1046. this.el.classList.remove('noScroll');
  1047. this.preventContext = false;
  1048. }.bind(this));
  1049. },
  1050. _wrapEvent: function _wrapEvent(event) {
  1051. return {
  1052. code: event.keyCode || event.charCode,
  1053. modifier: event.ctrlKey || event.metaKey,
  1054. altKey: event.altKey,
  1055. shiftKey: event.shiftKey,
  1056. originalEvent: event
  1057. };
  1058. },
  1059. onKeydown: function onKeydown(event) {
  1060. var wrappedEvent = this._wrapEvent(event);
  1061. if (this._shouldMoveSceneLeft(wrappedEvent)) {
  1062. // Ctrl-Left/Ctrl-Down
  1063. this._moveSceneLeft(this._getSceneIdFromElement(event));
  1064. } else if (this._shouldMoveSceneRight(wrappedEvent)) {
  1065. // Ctrl-Right/Ctrl-Up
  1066. this._moveSceneRight(this._getSceneIdFromElement(event));
  1067. } else if (this._shouldNavigateLeft(wrappedEvent)) {
  1068. // Left or Down
  1069. this.controller.previousScene();
  1070. } else if (this._shouldNavigateRight(wrappedEvent)) {
  1071. // Right or Up
  1072. this.controller.nextScene();
  1073. } else if (this._shouldShowContextMenu(wrappedEvent)) {
  1074. // Shift+F10 - Show toolbar
  1075. this.onSceneOverflowClick(event);
  1076. } else if (this._shouldDeleteScene(wrappedEvent)) {
  1077. // Delete or Backspace (With no modifier keys)
  1078. this.controller.deleteScene(this._getSceneIdFromElement(event));
  1079. } else if (this._shouldPerformEnter(wrappedEvent)) {
  1080. // Enter
  1081. $(event.target).click();
  1082. } else if (this._shouldPerformExpand(wrappedEvent)) {
  1083. //F10 - expand the scene
  1084. this.controller.expandScene();
  1085. } else if (this._shouldPerformTogglePlayPause(wrappedEvent)) {
  1086. // Spacebar - toggle play/pause
  1087. this.controller.togglePlayPause();
  1088. } else {
  1089. // Don't stop propagation or prevent default.
  1090. return;
  1091. }
  1092. event.stopPropagation();
  1093. event.preventDefault();
  1094. },
  1095. _getSceneIdFromElement: function _getSceneIdFromElement(event) {
  1096. return event.currentTarget.dataset.modelId;
  1097. },
  1098. _shouldMoveSceneLeft: function _shouldMoveSceneLeft(event) {
  1099. return event.modifier && (event.code === 37 || event.code === 38); //Ctrl-Left/Ctrl-Down
  1100. },
  1101. _shouldMoveSceneRight: function _shouldMoveSceneRight(event) {
  1102. return event.modifier && (event.code === 39 || event.code === 40); //Ctrl-Right/Ctrl-Up
  1103. },
  1104. // Left or Down
  1105. _shouldNavigateLeft: function _shouldNavigateLeft(event) {
  1106. return event.code === 37 || event.code === 38;
  1107. },
  1108. // Right or Up
  1109. _shouldNavigateRight: function _shouldNavigateRight(event) {
  1110. return event.code === 39 || event.code === 40;
  1111. },
  1112. // Delete or Backspace (With no modifier keys)
  1113. _shouldDeleteScene: function _shouldDeleteScene(event) {
  1114. var sceneId = this._getSceneIdFromElement(event.originalEvent),
  1115. canDelete = sceneId && this.controller.isAuthoring() && this.controller.getSceneCount() > 1,
  1116. isDeleteOrBackspace = event.code === 46 || event.code === 8,
  1117. notCtrlOrAlt = !event.modifier && !event.altKey;
  1118. return isDeleteOrBackspace && notCtrlOrAlt && canDelete;
  1119. },
  1120. // Shift-F10 when scene on scene
  1121. _shouldShowContextMenu: function _shouldShowContextMenu(event) {
  1122. return event.shiftKey && event.code === 121;
  1123. },
  1124. _shouldPerformEnter: function _shouldPerformEnter(event) {
  1125. return event.code === 13 && !$(event.originalEvent.target).hasClass('coachMark');
  1126. },
  1127. // F10 in windows when scene on scene
  1128. _shouldPerformExpand: function _shouldPerformExpand(event) {
  1129. return event.code === 121;
  1130. },
  1131. _shouldPerformTogglePlayPause: function _shouldPerformTogglePlayPause(event) {
  1132. return event.code === 32;
  1133. },
  1134. _moveSceneLeft: function _moveSceneLeft(sceneId) {
  1135. if (this.controller.isAuthoring() && sceneId) {
  1136. this.controller.moveSceneLeft(sceneId);
  1137. }
  1138. },
  1139. _moveSceneRight: function _moveSceneRight(sceneId) {
  1140. if (this.controller.isAuthoring() && sceneId) {
  1141. this.controller.moveSceneRight(sceneId);
  1142. }
  1143. },
  1144. _getSceneScrollPosition: function _getSceneScrollPosition($scene) {
  1145. var scrollLeft = this.$el.scrollLeft();
  1146. var position = $scene.position();
  1147. var marginLeft = parseInt($scene.css('margin-left'), 10);
  1148. var width = this.$el.outerWidth();
  1149. var left = position.left,
  1150. right = position.left + $scene.outerWidth(true);
  1151. if (left < 0) {
  1152. left += scrollLeft - marginLeft - 30;
  1153. } else if (right > width) {
  1154. left = scrollLeft + marginLeft + 30 + (right - width);
  1155. } else {
  1156. left = scrollLeft;
  1157. }
  1158. if ($scene.index() === this.$sceneList.children().length - 1) {
  1159. left += $scene.width() * 2;
  1160. }
  1161. return {
  1162. position: left,
  1163. scrollLeft: scrollLeft
  1164. };
  1165. }
  1166. });
  1167. return FilmStripView;
  1168. });
  1169. //# sourceMappingURL=FilmStripView.js.map