'use strict'; /** * Licensed Materials - Property of IBM * IBM Cognos Products: Storytelling * (C) Copyright IBM Corp. 2014, 2020 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['baglass/core-client/js/core-client/ui/core/View', 'jquery', 'underscore', 'text!./templates/TimelineView.html', './TimelineRulerView', './TimelineTimeIndicatorView', './TimelineSliderView', './SnapIndicatorView', 'storytelling-ui/storytelling-ui.min', 'react', 'react-dom', '../ScaleManager', 'baglass/core-client/js/core-client/utils/Utils', '../lib/@ba-ui-toolkit/ba-graphics/dist/icons-js/zoom-in_16', '../lib/@ba-ui-toolkit/ba-graphics/dist/icons-js/zoom-out_16', '../lib/@ba-ui-toolkit/ba-graphics/dist/icons-js/zoom-fit_16', '../lib/@ba-ui-toolkit/ba-graphics/dist/icons-js/minimize_16'], function (View, $, _, Template, TimelineRulerView, TimelineTimeIndicatorView, TimelineSliderView, SnapIndicatorView, StorytellingUI, React, ReactDOM, ScaleManager, Utils, zoonInIcon, zoomOutIcon, zoomFitIcon, minimizeIcon) { var TimelineView = View.extend({ templateString: Template, events: { 'primaryaction .zoomIn': 'onZoomInClick', 'primaryaction .zoomOut': 'onZoomOutClick', 'primaryaction .zoomFit': 'onZoomToFitClick', 'primaryaction .timelineRuler': 'onTimelineRulerClick' }, init: function init(options) { TimelineView.inherited('init', this, arguments); this.storyController = options.storyController; this.controller = options.controller; this.dashboardApi = options.dashboardApi; this.dndManager = options.dndManager; this.glassContext = options.glassContext; this.services = options.services; this.stringResources = this.dashboardApi.getDashboardCoreSvc('.StringResources'); this.zoom = 1; this.rangeSliders = null; this.rangeSlidersMap = null; this.scaleManager = new ScaleManager(); }, render: function render() { this.controller.on('duration:changed', this.onDurationChanged, this); this.controller.on('modelEpisode:added', this.onWidgetAdded, this); this.controller.on('modelEpisode:removed', this.onWidgetRemoved, this); this.controller.on('modelEpisode:changed', this.onModelEpisodeChanged, this); this.controller.on('modelEpisodes:reorder', this.onModelEpisodesReorder, this); this.controller.on('slider:change', this.onSliderTitleChange, this); this.controller.on('timeline:change', this.onSliderChange, this); this.controller.on('timeline:willChange', this.onSliderWillChange, this); this.controller.on('timeline:select', this.onSliderSelect, this); this.controller.on('timeline:movingUp', this.onSliderMoveUp, this); this.controller.on('timeline:movingDown', this.onSliderMoveDown, this); this.controller.on('timeline:doneMoving', this.onSliderDrop, this); this.controller.on('timeline:sliderDragStarted', this.onDragStarted, this); this.controller.on('timeline:highlightIndicatorDragStarted', this.highlightIndicatorDragStarted, this); this.controller.on('timeline:highlightIndicatorDragEnded', this.highlightIndicatorDragEnded, this); this.scaleManager.on('scale:change', this.onScaleChanged, this); $(window).on('resize.privateViewEvents' + this.viewId, this.onResize.bind(this)); var sHtml = this.dotTemplate({ timelineRightContainerLabel: this.stringResources.get('timelineRightContainerLabel'), collapseScene: this.stringResources.get('close'), zoomToFit: this.stringResources.get('timelineZoomFit'), zoomIn: this.stringResources.get('timelineZoomIn'), zoomOut: this.stringResources.get('timelineZoomOut') }); this.$el.addClass('timeline').attr('role', 'region').attr('aria-label', this.stringResources.get('timelineViewLabel')).html(sHtml); this.$scrollContainer = this.$('.timelineContent'); this.$timelineSliders = this.$scrollContainer.find('.timelineSliders'); this.$blanket = this.$el.find('.timelineBlanket'); this.$leftContainer = this.$el.find('.leftContainer'); var $rightContainer = this.$el.find('.rightContainer'); this.$zoomOutButton = $rightContainer.find('.zoomOut'); this.$zoomInButton = $rightContainer.find('.zoomIn'); this.$zoomFitButton = $rightContainer.find('.zoomFit'); this.$collapseButton = $rightContainer.find('.collapseScene'); Utils.setIcon(this.$zoomInButton, zoonInIcon.default.id); Utils.setIcon(this.$zoomOutButton, zoomOutIcon.default.id); Utils.setIcon(this.$zoomFitButton, zoomFitIcon.default.id); Utils.setIcon(this.$collapseButton, minimizeIcon.default.id); this.renderTimelineRowTracks(); this.renderTimelines(); this.renderRulerView(); this.scaleToFit(); this.registerHandlers(); this._updateSelectedSliders(); this.setFocus(); }, /** * @param Object fadeInOptions an equivalent object to jquery fadeIn options * @param Function fadeInOptions.complete see jquery fadeIn * @param Number fadeInOptions.duration see jquery fadeIn */ renderFadeIn: function renderFadeIn(fadeInOptions) { this.render(); this.hide(); this.$el.fadeIn(fadeInOptions); }, /** * @param Object fadeOutOptions an equivalent object to jquery fadeOut options * @param Function fadeOutOptions.complete see jquery fadeOut * @param Number fadeOptionOptions.duration see jquery fadeOut */ fadeOut: function fadeOut(fadeOutOptions) { this.$el.fadeOut(fadeOutOptions); }, renderRulerView: function renderRulerView() { this.timeRulerView = new TimelineRulerView({ el: this.$el.find('.timelineRuler'), controller: this.controller, scaleManager: this.scaleManager }); this.timeRulerView.render(); this.timeIndicatorView = new TimelineTimeIndicatorView({ el: this.$el.find('.timelinePosition'), controller: this.controller, scaleManager: this.scaleManager, services: this.services }); this.timeIndicatorView.render(); // Fix the ruler position. this._fixRulerPosition(); }, renderSnapIndicatorView: function renderSnapIndicatorView() { this.snapIndicatorView = new SnapIndicatorView({ $slidersHolder: this.$el.find('.timelineSliders'), controller: this.controller, scaleManager: this.scaleManager }); this.snapIndicatorView.render(); }, registerHandlers: function registerHandlers() { this.$collapseButton.on('primaryaction', this.storyController.collapseScene.bind(this.storyController)); this.$scrollContainer.on('scroll', this.onScroll.bind(this)); }, onSliderTitleChange: function onSliderTitleChange() /*event*/{ if (this.rangeSlidersMap) { _.each(this.rangeSlidersMap, function (view) { var label = this._getWidgetTitle(view.id); if (label !== view.getLabel()) { view.setLabel(label); } }.bind(this)); } }, renderTimelineRowTracks: function renderTimelineRowTracks() { var marginTop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; var timelineRowTrack = React.createElement(StorytellingUI.RowTrackView, { timelineEpisodes: this.controller.getTimelineEpisodes(), marginTop: marginTop }); ReactDOM.render(timelineRowTrack, this.$leftContainer[0]); }, renderTimelines: function renderTimelines() { // Reset the slider list. this.rangeSlidersId = []; if (this.rangeSlidersMap) { _.each(this.rangeSlidersMap, function (view) { view.remove(); }); } this.rangeSlidersMap = {}; // Use an object in order to treat it as a hashset (rather than an array, which allows duplicates) var timelineInfos = this._createTimelineInfos(); _.each(timelineInfos, function (info) { this.renderTimeline(info); }.bind(this)); }, renderTimeline: function renderTimeline(info) { var el = $('
').appendTo(this.$timelineSliders); var view = new TimelineSliderView({ el: el, id: info.id, model: info.model, label: this._getWidgetTitle(info.id), scaleManager: this.scaleManager, timelineController: this.controller, glassContext: this.glassContext, dndManager: this.dndManager, services: this.services, dashboardApi: this.dashboardApi }); view.render(); this.rangeSlidersId.push(info.id); this.rangeSlidersMap[info.id] = view; }, setFocus: function setFocus() { this.$el.find('.timelinePosition .handle').focus(); }, remove: function remove() { $(window).off('resize.privateViewEvents' + this.viewId); this.controller.off('duration:changed', this.onDurationChanged, this); this.controller.off('modelEpisode:added', this.onWidgetAdded, this); this.controller.off('modelEpisode:removed', this.onWidgetRemoved, this); this.controller.off('modelEpisode:changed', this.onModelEpisodeChanged, this); this.controller.off('modelEpisodes:reorder', this.onModelEpisodesReorder, this); this.controller.off('slider:change', this.onSliderTitleChange, this); this.controller.off('timeline:change', this.onSliderChange, this); this.controller.off('timeline:willChange', this.onSliderWillChange, this); this.controller.off('timeline:select', this.onSliderSelect, this); this.controller.off('timeline:movingUp', this.onSliderMoveUp, this); this.controller.off('timeline:movingDown', this.onSliderMoveDown, this); this.controller.off('timeline:doneMoving', this.onSliderDrop, this); this.controller.off('timeline:sliderDragStarted', this.onDragStarted, this); this.controller.off('timeline:highlightIndicatorDragStarted', this.highlightIndicatorDragStarted, this); this.controller.off('timeline:highlightIndicatorDragEnded', this.highlightIndicatorDragEnded, this); this.scaleManager.off('scale:change', this.onScaleChanged, this); this.timeRulerView = null; if (this.timeIndicatorView) { this.timeIndicatorView.remove(); this.timeIndicatorView = null; } if (this.$leftContainer && this.$leftContainer.length) { ReactDOM.unmountComponentAtNode(this.$leftContainer[0]); } this.rangeSlidersId = null; if (this.rangeSlidersMap) { _.each(this.rangeSlidersMap, function (view) { view.remove(); }); } this.rangeSlidersMap = null; TimelineView.inherited('remove', this, arguments); }, scaleToFit: function scaleToFit() { var duration = this.controller.getDuration(); if (duration === 0) { duration = this.controller.getDefaultWidgetDuration(); } this.scaleManager.updateDuration(duration, this.$scrollContainer.outerWidth(false)); this.scaleManager.scaleToFit(); }, /* View events.*/ onResize: function onResize() { if (this.$el.is(':visible')) { // We only resize if the view is visible. this.scaleToFit(); this.scaleManager.stepScale(this.zoom); this._updateTimelineWidth(); } }, onScroll: function onScroll() { this._fixRulerPosition(); this._updateSelectedSliders(); }, onZoomInClick: function onZoomInClick() { var step = 2; this.scaleManager.stepScale(step); this.zoom = this.zoom * step; this._updateZoomButtons(); }, onZoomOutClick: function onZoomOutClick() { var step = 0.5; this.scaleManager.stepScale(step); this.zoom = this.zoom * step; this._updateZoomButtons(); }, onZoomToFitClick: function onZoomToFitClick() { this.scaleToFit(); this.zoom = 1; this._updateZoomButtons(); }, onTimelineRulerClick: function onTimelineRulerClick() { this.timeIndicatorView.setFocus(); }, onSliderWillChange: function onSliderWillChange(event) { if (this.controller.isPlaying()) { this.controller.pause(); } if (event.getDragValue) { this.controller.updateLastEstablishedEventQueue(); if (!this.snapIndicatorView) { this.renderSnapIndicatorView(); } this.snapIndicatorView.showIndicator(event.getDragValue, event.getDraggingElement); } }, onSliderChange: function onSliderChange() { // Update the scale since the width might have changed. this._updateTimelineWidth(); if (this.lastStatePlaying && this.controller.getCursorTime() < this.controller.getDuration()) { this.controller.play(); } if (this.snapIndicatorView) { this.snapIndicatorView.removeIndicator(); } }, onSliderSelect: function onSliderSelect() { this.lastStatePlaying = this.controller.isPlaying(); }, _closeFlyouts: function _closeFlyouts(widgetId) { this.controller.trigger('slider:closeFlyouts', { widgetId: widgetId }); }, /* Controller events.*/ onScaleChanged: function onScaleChanged(event) { this._updateTimelinesScale(event.scale, event.previousScale); this._updateZoomButtons(); }, onDurationChanged: function onDurationChanged() { this._updateBlanket(); }, onWidgetAdded: function onWidgetAdded(id) { // Get the newly added widget timeline. var episode = this.controller.getTimelineEpisodeById(id); if (episode) { var info = this._createTimelineInfo(episode); // Render the timeline. this.renderTimeline(info); this._onWidgetAddRemove(); } }, onWidgetRemoved: function onWidgetRemoved(id) { this._closeFlyouts(id); var slider = this._getSliderByModelId(id); if (slider) { slider.remove(); // Remove the reference to the range slider. var index = _.indexOf(this.rangeSlidersId, id); if (index >= 0) { this.rangeSlidersId.splice(index, 1); this.rangeSlidersMap[id].remove(); delete this.rangeSlidersMap[id]; } this._onWidgetAddRemove(); } }, _getWidgetTitle: function _getWidgetTitle(id) { var content = this.dashboardApi.getCanvas().getContent(id); var spec = content.getFeature('Serializer').toJSON(); var hasGraphic = !!content.getPropertyValue('value.graphic.content'); var contentData = Object.assign({}, spec.features.Models_internal, { hasGraphic: hasGraphic }); return this.dashboardApi.getFeature('.SmartNamingSvc').getWidgetName(contentData); }, onModelEpisodeChanged: function onModelEpisodeChanged(options) { if (this.rangeSlidersMap) { var slider = this.rangeSlidersMap[options.id]; if (slider) { var newValue = this._createTimelineValue(options.value); slider.setValue(newValue, true); } } }, onModelEpisodesReorder: function onModelEpisodesReorder() { var $previousFocus = $(document.activeElement); var timelineInfos = this._createTimelineInfos(); this.rangeSlidersId = []; // this code assumes that the existing timeline entries were reordered // I.e no new timeline entries where added. var $previous = null; _.each(timelineInfos, function (info) { this.rangeSlidersId.push(info.id); var $slider = this._getSliderByModelId(info.id); if (!$previous) { this.$timelineSliders.prepend($slider); } else { $previous.after($slider); } $previous = $slider; }.bind(this)); $previousFocus.focus(); }, onShowSliderSelection: function onShowSliderSelection(evt) { var selectedSlider = this.rangeSlidersMap[evt.widgetId]; //when you select widget outside of focused scene, don't select timeline if (selectedSlider) { selectedSlider.toggleSelected(true); this._updateSelectedSlider(evt.widgetId); } }, onSliderDrop: function onSliderDrop(event) { var index = _.indexOf(this.rangeSlidersId, event.id); this.controller.moveEpisodeBefore(event.id, this.rangeSlidersId[index + 1], { payloadData: event.payloadData }); }, onDragStarted: function onDragStarted(id) { this._closeFlyouts(id); }, highlightIndicatorDragStarted: function highlightIndicatorDragStarted() { // Disable timeline scrolling when a highlight is dragged this.$scrollContainer.css({ overflow: 'hidden' }); }, highlightIndicatorDragEnded: function highlightIndicatorDragEnded() { // Re-enable timeline scrolling when highlight dragging stops this.$scrollContainer.css({ overflow: 'auto' }); }, onSliderMoveUp: function onSliderMoveUp(event) { var $previousFocus = $(document.activeElement); var index = _.indexOf(this.rangeSlidersId, event.id); var toIndex = event.to ? _.indexOf(this.rangeSlidersId, event.to) : index - 1; if (index === -1 || toIndex < 0) { return; } //move the slider up until we get to where we should be. while (index > toIndex) { var $slider = this._getSliderByModelId(this.rangeSlidersId[index]); var $previousSlider = this._getSliderByModelId(this.rangeSlidersId[index - 1]); $slider.insertBefore($previousSlider); var t = this.rangeSlidersId[index - 1]; this.rangeSlidersId[index - 1] = this.rangeSlidersId[index]; this.rangeSlidersId[index] = t; index--; } // fix ie 11 svg icons disappear problem // no need to check browser as embedSVGIcon does the check in the Utils Utils.embedSVGIcon(this.$el); $previousFocus.focus(); }, onSliderMoveDown: function onSliderMoveDown(event) { var $previousFocus = $(document.activeElement); var index = _.indexOf(this.rangeSlidersId, event.id); var toIndex = event.to ? _.indexOf(this.rangeSlidersId, event.to) : index + 1; if (index === -1 || toIndex === -1 || toIndex >= this.rangeSlidersId.length) { return; } //move the slider down until we get to where we should be. while (index < toIndex) { var $slider = this._getSliderByModelId(this.rangeSlidersId[index]); var $nextSlider = this._getSliderByModelId(this.rangeSlidersId[index + 1]); $slider.insertAfter($nextSlider); var t = this.rangeSlidersId[index + 1]; this.rangeSlidersId[index + 1] = this.rangeSlidersId[index]; this.rangeSlidersId[index] = t; index++; } // fix ie 11 svg icons disappear problem Utils.embedSVGIcon(this.$el); $previousFocus.focus(); }, _getSliderByModelId: function _getSliderByModelId(widgetId) { return this.$timelineSliders.find('#timelineWidgetSlider' + widgetId); }, _updateBlanket: function _updateBlanket() { this.$blanket.css('left', this.scaleManager.convertTimeToPosition(this.controller.getDuration()) + 'px'); }, _updateSelectedSliders: function _updateSelectedSliders() { var map = this.controller.getSelectedWidgetMap(); _.each(Object.keys(map), function (key) { if (map[key]) { this.onShowSliderSelection({ widgetId: key }); // Close any flyouts on container scrolling this._closeFlyouts(key); } }.bind(this)); }, _updateSelectedSlider: function _updateSelectedSlider(id) { this.rangeSlidersMap[id].$el.toggleClass('bringToFront', this.$scrollContainer.scrollTop() === 0); }, _clearHoverTimeout: function _clearHoverTimeout() { if (this.hoverTimeout) { clearTimeout(this.hoverTimeout); this.hoverTimeout = null; } }, _onWidgetAddRemove: function _onWidgetAddRemove() { // Update the timeline scales. this._updateTimelineWidth(); // Ensure the indicator is updated. this.timeIndicatorView.refresh(); // Re-render the timeline row track and blanket position this._fixRulerPosition(); }, _updateTimelineWidth: function _updateTimelineWidth() { var durationWidth = 500 + this.scaleManager.convertTimeToPosition(this.controller.getDuration()); var pageWidth = this.$scrollContainer.outerWidth(false); var width = Math.max(durationWidth, pageWidth); this.$timelineSliders.css('width', width + 'px'); this.$blanket.css('width', width - parseInt(this.$blanket.css('left'), 10)); this.timeRulerView.refresh(); }, _updateTimelinesScale: function _updateTimelinesScale(scale) { this._updateBlanket(); this._updateTimelineWidth(); var map = this.rangeSlidersMap; _.each(this.rangeSlidersId, function (id) { var item = map[id]; item.setScale(scale); }); }, _createTimelineValue: function _createTimelineValue(value) { var defaultPixelsPerSecond = this.scaleManager.getScaleToPixelRatio(); return [value[0] / 1000 * defaultPixelsPerSecond, value[1] / 1000 * defaultPixelsPerSecond]; }, _createTimelineInfo: function _createTimelineInfo(episode) { var start = episode.getEntranceAct().timer; var end = episode.getExitAct().timer; var timeValue = this._createTimelineValue([start, end]); return { model: { value: timeValue, scale: this.scaleManager.getScale() }, id: episode.id }; }, _createTimelineInfos: function _createTimelineInfos() { // Create a timeline info object for each timeline. return _.map(this.controller.getTimelineEpisodes(), function (timeline) { return this._createTimelineInfo(timeline); }.bind(this)); }, _fixRulerPosition: function _fixRulerPosition() { // Ensure the ruler stays at the top when the timeline is scrolled vertically. var scrollTop = this.$scrollContainer.scrollTop(); var scrollLeft = this.$scrollContainer.scrollLeft(); this.$blanket.css('top', scrollTop + 'px'); this.timeIndicatorView.onScroll({ scrollTop: scrollTop, scrollLeft: scrollLeft }); this.timeRulerView.setOffsetLeft(scrollLeft); this.renderTimelineRowTracks(-scrollTop); }, _updateZoomButtons: function _updateZoomButtons() { var scale = this.scaleManager.getScale(); var canZoomIn = scale < this.scaleManager.getMaxScale(); var canZoomOut = scale > this.scaleManager.getMinScale(); this.$zoomOutButton.toggleClass('disabled', !canZoomOut); this.$zoomInButton.toggleClass('disabled', !canZoomIn); this.$zoomInButton.attr('aria-disabled', !canZoomIn); this.$zoomOutButton.attr('aria-disabled', !canZoomOut); } }); return TimelineView; }); //# sourceMappingURL=TimelineView.js.map