TimelineSliderView.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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', '../nls/StringResources', './TimelineSliderContentView', './TimelineHighlightIndicatorView', 'storytelling-ui/storytelling-ui.min', 'react', 'react-dom'], function (View, stringResources, Slider, TimelineIndicator, StorytellingUI, React, ReactDOM) {
  9. var PROVIDER_ID = 'PropertiesPane';
  10. var TimelineSliderView = View.extend({
  11. init: function init(options) {
  12. TimelineSliderView.inherited('init', this, arguments);
  13. this.id = options.id;
  14. this.model = options.model;
  15. this.label = options.label;
  16. this.scaleManager = options.scaleManager;
  17. this.timelineController = options.timelineController;
  18. this.glassContext = options.glassContext;
  19. this.dndManager = options.dndManager;
  20. this.dashboardApi = options.dashboardApi;
  21. this.services = options.services;
  22. this._dashboardState = this.dashboardApi.getFeature('DashboardState');
  23. this._flyoutHolder = document.createElement('div');
  24. this._flyoutHolder.classList.add('flyout-holder');
  25. this.extraCssClassForHighlight = 'timelineHighlight';
  26. this._timelineIndicators = [];
  27. this._highlightActModelListeners = [];
  28. },
  29. render: function render() {
  30. this.el.id = 'timelineWidgetSlider' + this.id;
  31. this.el.classList.add('sliderContent');
  32. this._renderDragSlider();
  33. this._renderTimelineHighlightIndicators();
  34. this._addDropZone();
  35. this.scaleManager.on('scale:change', this.onScaleChanged, this);
  36. this.timelineController.on('slider:addHighlight', this.onAddTimelineHighlight, this);
  37. this.timelineController.on('slider:closeFlyouts', this._closeFlyouts, this);
  38. this.timelineController.on('slider:deselect', this.onHideSliderSelection, this);
  39. this.timelineController.on('slider:removeHighlight', this.onRemoveTimelineHighlight, this);
  40. this.timelineController.on('slider:select', this.onShowSliderSelection, this);
  41. this.timelineController.on('slider:showHighlightSummary', this.onShowHighlightSummary, this);
  42. },
  43. remove: function remove() {
  44. if (this._dropZone) {
  45. this._dropZone.remove();
  46. }
  47. this._removeActModelListeners();
  48. this._removeTimelineIndicators();
  49. if (this._rangeSlider) {
  50. this._rangeSlider.remove();
  51. }
  52. this.scaleManager.off('scale:change', this.onScaleChanged, this);
  53. this.timelineController.off('slider:addHighlight', this.onAddTimelineHighlight, this);
  54. this.timelineController.off('slider:closeFlyouts', this._closeFlyouts, this);
  55. this.timelineController.off('slider:deselect', this.onHideSliderSelection, this);
  56. this.timelineController.off('slider:removeHighlight', this.onRemoveTimelineHighlight, this);
  57. this.timelineController.off('slider:select', this.onShowSliderSelection, this);
  58. this.timelineController.off('slider:showHighlightSummary', this.onShowHighlightSummary, this);
  59. TimelineSliderView.inherited('remove', this, arguments);
  60. },
  61. _removeActModelListeners: function _removeActModelListeners() {
  62. this._highlightActModelListeners.forEach(function (listener) {
  63. listener.remove();
  64. });
  65. this._highlightActModelListeners = [];
  66. },
  67. _removeTimelineIndicators: function _removeTimelineIndicators() {
  68. this._timelineIndicators.forEach(function (indicator) {
  69. indicator.remove();
  70. });
  71. this._timelineIndicators = [];
  72. },
  73. onScaleChanged: function onScaleChanged() {
  74. this._timelineIndicators.forEach(function (indicator) {
  75. indicator.onScaleChange();
  76. });
  77. },
  78. onAddTimelineHighlight: function onAddTimelineHighlight() {
  79. this._refreshTimelineHighlightIndicators();
  80. },
  81. onShowHighlightSummary: function onShowHighlightSummary(event) {
  82. var highlight = this._timelineIndicators.find(function (indicator) {
  83. return indicator.id === event.actModel.id;
  84. });
  85. if (highlight) {
  86. highlight.launchHighlightSummaryDialog();
  87. }
  88. },
  89. _closeFlyouts: function _closeFlyouts(targetElement) {
  90. if (this._flyout) {
  91. ReactDOM.unmountComponentAtNode(this._flyoutHolder);
  92. this._flyout = null;
  93. this.el.removeChild(this._flyoutHolder);
  94. }
  95. this._timelineIndicators.forEach(function (indicator) {
  96. return indicator.closeFlyout();
  97. });
  98. // There is an event triggered in TimelineView that passes an object with a
  99. // widgetId and this method is the one listening on that event but we
  100. // don't seem to be using that widgetId. It is used in a few different places
  101. // though and now we need to check that the parameter (targetElement) is one
  102. // that has a "focus" method on it. UGH!
  103. if (targetElement && targetElement.setActive) {
  104. // A workaround using setActive in IE which will set focus without scrolling
  105. targetElement.setActive();
  106. } else if (targetElement && targetElement.focus) {
  107. targetElement.focus();
  108. }
  109. },
  110. onRemoveTimelineHighlight: function onRemoveTimelineHighlight(event) {
  111. var highlightListener = this._highlightActModelListeners.find(function (listener) {
  112. return listener.id === event.actModel.id;
  113. });
  114. if (highlightListener) {
  115. highlightListener.remove();
  116. this._highlightActModelListeners = this._highlightActModelListeners.filter(function (listener) {
  117. return listener.id !== event.actModel.id;
  118. });
  119. }
  120. var highlight = this._timelineIndicators.find(function (indicator) {
  121. return indicator.id === event.actModel.id;
  122. });
  123. if (highlight) {
  124. highlight.remove();
  125. this._timelineIndicators = this._timelineIndicators.filter(function (indicator) {
  126. return indicator.id !== event.actModel.id;
  127. });
  128. }
  129. },
  130. onShowSliderSelection: function onShowSliderSelection(event) {
  131. if (event.widgetId === this.id) {
  132. this.toggleSelected(true);
  133. var $timelineController = this.$el.parent('.timelineContent'); // FIXME: Reaching out of scope
  134. this.$el.toggleClass('bringToFront', $timelineController.scrollTop() === 0);
  135. }
  136. },
  137. onHideSliderSelection: function onHideSliderSelection(event) {
  138. if (event.widgetId === this.id) {
  139. this.toggleSelected(false);
  140. this._closeFlyouts();
  141. // Update the previously selected slider.
  142. this.$el.removeClass('bringToFront');
  143. }
  144. },
  145. setLabel: function setLabel(label) {
  146. this.label = label;
  147. this._rangeSlider.middleShortenTitle(this.label);
  148. },
  149. getLabel: function getLabel() {
  150. return this.label;
  151. },
  152. setScale: function setScale(scale) {
  153. var scaleUpdated = this.model.scale !== scale;
  154. this.model.scale = scale;
  155. this._rangeSlider.setScale(scale);
  156. // setScale can be called multiple times with the same value. Since
  157. // middleShortenString() is considered expensive, only call it if necessary.
  158. if (scaleUpdated) {
  159. this._rangeSlider.middleShortenTitle(this.label);
  160. }
  161. },
  162. setValue: function setValue(value, preventNotify) {
  163. this._rangeSlider.setValue(value, preventNotify);
  164. this._timelineIndicators.forEach(function (indicator) {
  165. indicator.onSliderChange();
  166. });
  167. this._rangeSlider.middleShortenTitle(this.label);
  168. },
  169. getValue: function getValue() {
  170. return this._rangeSlider.getValue();
  171. },
  172. getDragValue: function getDragValue() {
  173. return this._rangeSlider.dragValue;
  174. },
  175. getDraggingElement: function getDraggingElement() {
  176. return this._rangeSlider.draggingElement;
  177. },
  178. toggleSelected: function toggleSelected(toggle) {
  179. this._rangeSlider.toggleSelected(toggle);
  180. },
  181. onScroll: function onScroll(event) {
  182. this._rangeSlider.onScroll(event);
  183. },
  184. onSliderSelect: function onSliderSelect(event) {
  185. var _this = this;
  186. event.stopPropagation();
  187. event.preventDefault();
  188. this.timelineController.trigger('timeline:select', {
  189. widgetId: this.id,
  190. pageX: event && event.pageX ? event.pageX : 0,
  191. originalEvent: event
  192. });
  193. this.timelineController.selectWidgetAndSlider(this.id).then(function () {
  194. // don't toggle flyout on drag end (which triggers a mouseup or touchend on mobile)
  195. if (!(event.type === 'mouseup' || event.type === 'touchend')) {
  196. _this._toggleFlyout(event);
  197. }
  198. });
  199. },
  200. addTimelineHighlight: function addTimelineHighlight(pageX) {
  201. // pageX is from the event that clicks on timeline slider
  202. var timer = this.scaleManager.convertPositionToTime(pageX - this.$el.offset().left);
  203. this.timelineController.addTimelineHighlight(this.id, timer);
  204. },
  205. isHighlightSupported: function isHighlightSupported() {
  206. return this.timelineController.isHighlightSupported(this.id);
  207. },
  208. _toggleFlyout: function _toggleFlyout(event) {
  209. if (this._flyout) {
  210. this._closeFlyouts(event.currentTarget);
  211. return;
  212. }
  213. var $timelineContent = this.$el.closest('.timelineContent');
  214. var scrollBarOffset = $timelineContent[0].scrollLeft;
  215. var timelineOffset = this.$el.children().first()[0].offsetLeft;
  216. var triggerPosition = this.el.querySelector('.slider-selection').getBoundingClientRect().left;
  217. if (event.pageX) {
  218. triggerPosition = event.pageX;
  219. }
  220. var position = triggerPosition - this.el.getBoundingClientRect().left;
  221. this._flyoutHolder.style.left = (event.pageX ? position : Math.max(scrollBarOffset, timelineOffset)) + 'px';
  222. this.$el.append(this._flyoutHolder);
  223. this._flyout = React.createElement(StorytellingUI.SliderFlyout, {
  224. isHighlightSupported: this.isHighlightSupported.bind(this),
  225. anchorElement: this._flyoutHolder,
  226. pageX: event.pageX || 0,
  227. closeFlyout: this._closeFlyouts.bind(this, event.currentTarget),
  228. openAnimationProperties: this.openAnimationProperties.bind(this),
  229. addTimelineHighlight: this.addTimelineHighlight.bind(this)
  230. });
  231. ReactDOM.render(this._flyout, this._flyoutHolder);
  232. },
  233. openAnimationProperties: function openAnimationProperties() {
  234. var state = this._dashboardState.getUiState();
  235. if (!state.fullScreen) {
  236. this._dashboardState.setSelectionProperties(true);
  237. this._dashboardState.setSidePanelCurrentView(this._getProviderId());
  238. this._dashboardState.setSidePanelOpen(true);
  239. }
  240. },
  241. _getProviderId: function _getProviderId() {
  242. return PROVIDER_ID;
  243. },
  244. onSliderWillChange: function onSliderWillChange() {
  245. this.timelineController.trigger('timeline:willChange', {
  246. widgetId: this.id,
  247. getDragValue: this.getDragValue.bind(this),
  248. getDraggingElement: this.getDraggingElement.bind(this)
  249. });
  250. },
  251. onSliderChange: function onSliderChange(event) {
  252. this._updateWidgetTimeline(this.id, event.value, event.payloadData);
  253. this._rangeSlider.middleShortenTitle(this.label);
  254. this._timelineIndicators.forEach(function (indicator) {
  255. indicator.onSliderChange();
  256. });
  257. this.timelineController.trigger('timeline:change', {
  258. widgetId: this.id
  259. });
  260. },
  261. onSliderDrop: function onSliderDrop(event) {
  262. this.timelineController.trigger('timeline:doneMoving', event);
  263. },
  264. onSliderMoveUp: function onSliderMoveUp(event) {
  265. this.timelineController.trigger('timeline:movingUp', event);
  266. },
  267. onSliderMoveDown: function onSliderMoveDown(event) {
  268. this.timelineController.trigger('timeline:movingDown', event);
  269. },
  270. onSliderDragStarted: function onSliderDragStarted(event) {
  271. this.timelineController.trigger('timeline:sliderDragStarted', event);
  272. },
  273. _addDropZone: function _addDropZone() {
  274. var _this2 = this;
  275. this._dropZone = this.dndManager.addDropTarget(this.$el[0], {
  276. accepts: function accepts(dragObject) {
  277. // we only accept the slider, not the 'handles'
  278. return dragObject.type === 'dragSliderContent';
  279. },
  280. onDragLeave: function onDragLeave(dragObject) {
  281. if (!dragObject || dragObject.type !== 'dragSliderContent' || !dragObject.data || !dragObject.data.slider) {
  282. return;
  283. }
  284. if (_this2._rangeSlider.id === dragObject.data.slider.id) {
  285. // us
  286. return;
  287. }
  288. if (!dragObject.lastPosition) {
  289. dragObject.lastPosition = dragObject.startPosition;
  290. }
  291. if (dragObject.position.y < dragObject.lastPosition.y) {
  292. _this2.timelineController.trigger('timeline:movingUp', { id: dragObject.data.slider.id, to: _this2.id });
  293. } else {
  294. _this2.timelineController.trigger('timeline:movingDown', { id: dragObject.data.slider.id, to: _this2.id });
  295. }
  296. dragObject.lastPosition = dragObject.position;
  297. }
  298. });
  299. },
  300. _renderDragSlider: function _renderDragSlider() {
  301. this._rangeSlider = new Slider({
  302. id: this.id,
  303. el: this.$el,
  304. value: this.model.value,
  305. scale: this.model.scale,
  306. title: this.label,
  307. cssClassSelector: this.extraCssClassForHighlight,
  308. dndManager: this.dndManager,
  309. services: this.services,
  310. timelineController: this.timelineController
  311. });
  312. this._rangeSlider.on('value:changed', this.onSliderChange.bind(this));
  313. this._rangeSlider.on('value:willChange', this.onSliderWillChange.bind(this));
  314. this._rangeSlider.on('dragSlider:select', this.onSliderSelect.bind(this));
  315. this._rangeSlider.on('dragSlider:isDropped', this.onSliderDrop.bind(this));
  316. this._rangeSlider.on('dragSlider:movingUp', this.onSliderMoveUp.bind(this));
  317. this._rangeSlider.on('dragSlider:movingDown', this.onSliderMoveDown.bind(this));
  318. this._rangeSlider.on('dragSlider:dragStarted', this.onSliderDragStarted.bind(this));
  319. this._rangeSlider.render();
  320. this._rangeSlider.middleShortenTitle(this.label);
  321. },
  322. /* Set the time range info of the widgets in selected page.*/
  323. _updateWidgetTimeline: function _updateWidgetTimeline(id, value, payloadData) {
  324. var defaultPixelsPerSecond = this.scaleManager.getScaleToPixelRatio();
  325. this.timelineController.updateTimelineDuration(id, value[0] / defaultPixelsPerSecond * 1000, value[1] / defaultPixelsPerSecond * 1000, {
  326. payloadData: payloadData
  327. });
  328. },
  329. _renderTimelineHighlightIndicators: function _renderTimelineHighlightIndicators() {
  330. var _this3 = this;
  331. var episodeModel = this.timelineController.getTimelineEpisodeById(this.id);
  332. var highlightActModels = episodeModel.acts.filter(function (value) {
  333. return value.get('action') === 'highlight';
  334. });
  335. highlightActModels.sort(function (item1, item2) {
  336. return item1.get('timer') - item2.get('timer');
  337. });
  338. highlightActModels.forEach(function (highlightActModel) {
  339. _this3._renderTimelineHighlightIndicator(episodeModel, highlightActModel);
  340. });
  341. episodeModel.acts.on('change:payload', this.onTimelineHighlightPayloadChange, this);
  342. },
  343. _renderTimelineHighlightIndicator: function _renderTimelineHighlightIndicator(episodeModel, highlightActModel) {
  344. if (episodeModel.id === this.id) {
  345. // Rendering highlight indicators should be done within the TimelineContentSliderView (_rangeSlider)
  346. // instead of this class because the indicators are placed within a div that comes from _rangeSlider
  347. var highlightEl = document.createElement('div');
  348. this._rangeSlider.$highlightsEl.append(highlightEl);
  349. var timelineIndicator = new TimelineIndicator({
  350. el: highlightEl,
  351. widgetId: this.id,
  352. controller: this.timelineController,
  353. scaleManager: this.scaleManager,
  354. episodeModel: episodeModel,
  355. highlightAct: highlightActModel,
  356. extraCssClass: this.extraCssClassForHighlight,
  357. services: this.services,
  358. dashboardApi: this.dashboardApi
  359. });
  360. timelineIndicator.render();
  361. this._timelineIndicators.push(timelineIndicator);
  362. var _highlightActModel$on = highlightActModel.on('change:timer', this.onTimelineHighlightTimerChange, this),
  363. remove = _highlightActModel$on.remove;
  364. this._highlightActModelListeners.push({ id: highlightActModel.id, remove: remove });
  365. }
  366. },
  367. _refreshTimelineHighlightIndicators: function _refreshTimelineHighlightIndicators() {
  368. this._removeActModelListeners();
  369. this._removeTimelineIndicators();
  370. this._renderTimelineHighlightIndicators();
  371. },
  372. onTimelineHighlightTimerChange: function onTimelineHighlightTimerChange() {
  373. var reFocusHighlight = false;
  374. var activeElId = document.activeElement.id;
  375. if (document.activeElement.classList.contains('timelineHighlight')) {
  376. reFocusHighlight = true;
  377. }
  378. this._refreshTimelineHighlightIndicators();
  379. if (reFocusHighlight) {
  380. document.getElementById(activeElId).focus();
  381. }
  382. },
  383. onTimelineHighlightPayloadChange: function onTimelineHighlightPayloadChange() {
  384. // one or more highlight changed....
  385. this._timelineIndicators.forEach(function (indicator) {
  386. return indicator.onPayloadChange();
  387. });
  388. }
  389. });
  390. return TimelineSliderView;
  391. });
  392. //# sourceMappingURL=TimelineSliderView.js.map