'use strict'; /* * Licensed Materials - Property of IBM * IBM Cognos Products: Storytelling * (C) Copyright IBM Corp. 2014, 2019 * 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/Events', 'dashboard-analytics/apiHelpers/SlotAPIHelper', 'underscore', './AnimationDirector', './TimeQueue', './util/WidgetHelper'], function (Events, SlotAPIHelper, _, AnimationDirector, TimeQueue, WidgetHelper) { var Controller = Events.extend({ authoring: false, duration: 10000, defaultWidgetDuration: 5000, animationDuration: 500, cursorTime: 0, suggestionTimelineCheckingInterval: 100, suggestionTimeRange: [], lastEstablishedEventQueue: [], init: function init(options) { Controller.inherited('init', this, arguments); this.canvasController = options.canvasController; this.dashboardApi = options.dashboardApi; this.timeline = options.model.timeline; this.layout = options.model.layout; this.widgetInstances = options.model.widgetInstances; this.model = options.model; this.eventRouter = options.eventRouter; this.timeQueues = {}; this.selectedWidgetIds = {}; this.authoring = options.authoring; this.services = options.services; this.stringResources = this.services.getSvcSync('.StringResources'); this.widgetHelper = new WidgetHelper({ dashboardApi: this.dashboardApi }); if (this.model.pageContext) { this._animationDirector = new AnimationDirector({ widgetHelper: this.widgetHelper, pageContextAPI: this.model.pageContext.getAPI() }); } this.canvas = this.dashboardApi.getCanvas(); //Listen for events this.eventRouter.on('widget:maximize', this._onMaximizeWidget, this); this.eventRouter.on('widget:restore', this._onRestoreWidget, this); this.eventRouter.on('widget:selected', this.onSelectWidget, this); this.eventRouter.on('widget:deselected', this.onDeselectWidget, this); this.eventRouter.on('timequeue:tick', this.onTimeQueueTick, this); this.eventRouter.on('timequeue:stateChanged', this.onTimeQueueStateChanged, this); this.eventRouter.on('timequeue:durationChanged', this.onDurationChanged, this); this.eventRouter.on('navigation:complete', this.onNavigationComplete, this); this.eventRouter.on('scene:reorder', this.onSceneReorder, this); this.eventRouter.on('scene:swap', this.onSceneSwap, this); this.eventRouter.on('mode:change', this.onModeChanged, this); this.eventRouter.on('rendered', this.onWidgetRendered, this); this.eventRouter.on('widget:animate', this.onAnimate, this); this.eventRouter.on('layoutType:changed', this._onLayoutTypeChanged, this); this.eventRouter.on('timeline:end', this.onTimelinePlaybackEnd, this); this.timeline.on('change:kioskMode', this.onKioskModeChange, this); this.timeline.on('change:navigateMarkers', this.onNavigateMarkersChange, this); this.timeline.on('change:episodes', this.onModelEpisodeChange, this); this.timeline.on('timeline:episodeFragmentAdded', this.onEpisodeFragmentAdded, this); this.timeline.episodes.on('add', this.onEpisodeAdded, this); this.timeline.episodes.on('remove', this.onEpisodeRemoved, this); this.model.on('widget:change', this.onChangeWidget, this); }, destroy: function destroy() { // Please keep the same order as registering events for easier maintenance. this.eventRouter.off('widget:maximize', this._onMaximizeWidget, this); this.eventRouter.off('widget:restore', this._onRestoreWidget, this); this.eventRouter.off('widget:selected', this.onSelectWidget, this); this.eventRouter.off('widget:deselected', this.onDeselectWidget, this); this.eventRouter.off('timequeue:tick', this.onTimeQueueTick, this); this.eventRouter.off('timequeue:stateChanged', this.onTimeQueueStateChanged, this); this.eventRouter.off('timequeue:durationChanged', this.onDurationChanged, this); this.eventRouter.off('navigation:complete', this.onNavigationComplete, this); this.eventRouter.off('scene:reorder', this.onSceneReorder, this); this.eventRouter.off('scene:swap', this.onSceneSwap, this); this.eventRouter.off('mode:change', this.onModeChanged, this); this.eventRouter.off('rendered', this.onWidgetRendered, this); this.eventRouter.off('widget:animate', this.onAnimate, this); this.eventRouter.off('layoutType:changed', this._onLayoutTypeChanged, this); this.eventRouter.off('timeline:end', this.onTimelinePlaybackEnd, this); this.timeline.off('change:kioskMode', this.onKioskModeChange, this); this.timeline.off('change:navigateMarkers', this.onNavigateMarkersChange, this); this.timeline.off('change:episodes', this.onModelEpisodeChange, this); this.timeline.off('timeline:episodeFragmentAdded', this.onEpisodeFragmentAdded, this); this.timeline.episodes.off('add', this.onEpisodeAdded, this); this.timeline.episodes.off('remove', this.onEpisodeRemoved, this); this.model.off('widget:change', this.onChangeWidget, this); var that = this; _.each(this.widgetInstances, function (widgetInstance) { var widget = that.widgetHelper.getWidget(widgetInstance.getId()); if (widget && widget.getVisApi && widget.getVisApi() && widget.getVisApi().ownerWidget) { widget.getVisApi().ownerWidget.off('dwChange:visTransaction', that.onWidgetChange, that); widget.getVisApi().ownerWidget.off('dwChange:visId', that.onWidgetChange, that); } }); }, _onLayoutTypeChanged: function _onLayoutTypeChanged() { this.timeQueues = {}; this.scene = null; }, onWidgetChange: function onWidgetChange(event) { // in the case of undo/redo we have already did have an entry on the undo/redo stack so we don't need to do anything // (also for undo/redo of dwChange:visId the events are in the wrong order so best to just ignore) if (!(event && event.model && event.model.id && event.sender !== 'UndoRedoController')) { return; } var options = event.options || {}; options.payloadData = event.data; options.sender = event.sender; options = this._validateModelOptions(options); var episode = this.getTimelineEpisodeById(event.model.id); if (episode) { //grab the default "empty" and send it off the the model var blankPayloadExemplar = this._getTimelineHighlightsForWidget(event.model.id); episode.acts.updateHighlightPayloadColumns(blankPayloadExemplar, options); } }, setScene: function setScene(scene) { if (scene) { if (this.scene !== scene) { this.scene = scene; this.setCursorTime(0); } } else { this.scene = null; this.trigger('time:update', { currentTime: 0 }); } }, onWidgetRendered: function onWidgetRendered(api) { if (api) { this.trigger('slider:change', { 'name': 'rendered', 'modelId': api.getId() }); } var widget = this.widgetHelper.getWidget(api.getId()); if (widget && widget.getVisApi && widget.getVisApi() && widget.getVisApi().ownerWidget) { // deregister first and re-register widget.getVisApi().ownerWidget.off('dwChange:visTransaction', this.onWidgetChange, this); widget.getVisApi().ownerWidget.on('dwChange:visTransaction', this.onWidgetChange, this); widget.getVisApi().ownerWidget.off('dwChange:visId', this.onWidgetChange, this); widget.getVisApi().ownerWidget.on('dwChange:visId', this.onWidgetChange, this); } }, onRemoveLayout: function onRemoveLayout(evt) { _.each(evt.value.parameter, function (sceneId) { if (this.timeQueues[sceneId]) { delete this.timeQueues[sceneId]; } }.bind(this)); }, _seekStory: function _seekStory(sceneIndex) { if (this.layout.type === 'slideshow') { // scene preparation is limited to three scenes in slideshow var timeQueue = this._currentTimeQueue(); if (timeQueue) { timeQueue.pause(); } var sceneCount = this.model.layout.items.length; this._maxScene(sceneIndex === 0 ? sceneCount - 1 : sceneIndex - 1); this._zeroScene(sceneIndex); this._zeroScene(sceneIndex === sceneCount - 1 ? 0 : sceneIndex + 1); } else { this.maxPreviousScenes(sceneIndex); this.zeroFutureScenes(sceneIndex); } }, onNavigationComplete: function onNavigationComplete(evt) { var play; if (evt) { play = evt.play; this._seekStory(evt.index); } // In consumption mode we auto play when we switch scenes unless an explicit play instruction is received if (play !== false) { if (!this.isAuthoring() && !this.isPlaying() && !this.isNavigateMarkers() || play) { this.play(); } } }, isAuthoring: function isAuthoring() { return this.authoring; }, onModeChanged: function onModeChanged(event) { this.authoring = event.authoring; }, onSceneReorder: function onSceneReorder(event) { this.trigger('scene:reorder', event); }, maxPreviousScenes: function maxPreviousScenes(idx) { var seekPromises = []; if (idx === -2) { // Special case - end of story! idx = this.model.layout.items.length; } for (var i = 0; i < idx; i++) { seekPromises.push(this._maxScene(i)); } return Promise.all(seekPromises); }, zeroFutureScenes: function zeroFutureScenes(idx) { var seekPromises = []; if (idx !== -2) { var sceneCount = this.model.layout.items.length; var i = idx; if (i < 0) { i = 0; } for (; i < sceneCount; i++) { seekPromises.push(this._zeroScene(i)); } } return Promise.all(seekPromises); }, _zeroScene: function _zeroScene(index) { var scene = this.model.layout.items[index]; var timeQueue = this._getTimeQueue(scene.id); if (timeQueue.isPlaying()) { timeQueue.stop(); } return timeQueue.reset(); }, _maxScene: function _maxScene(index) { var scene = this.model.layout.items[index]; var timeQueue = this._getTimeQueue(scene.id); if (timeQueue.isPlaying()) { timeQueue.stop(); } return timeQueue.endScene(); }, getCurrentScene: function getCurrentScene() { return this.scene; }, getTimelineEpisodeCount: function getTimelineEpisodeCount() { return this._getWidgetIds().length; }, getTimelineEpisodeById: function getTimelineEpisodeById(id) { return this._isWidgetInCurrentScene(id) ? this._getEpisodeModel(id) : undefined; }, /* this method must return the episodes in the order they are in the collection*/ getTimelineEpisodes: function getTimelineEpisodes() { var widgets = {}; // create an 'index' of the widget ids for faster lookup this._getWidgetIds().forEach(function (id) { widgets[id] = id; }); return _.filter(this.model.timeline.episodes.models, function (episode) { return episode.id in widgets; }.bind(this)); }, getTimelineEpisodeIDs: function getTimelineEpisodeIDs() { var episodeIDs = []; var episodeObjs = this.getTimelineEpisodes(); Object.keys(episodeObjs).forEach(function (key) { return episodeIDs.push(episodeObjs[key].id); }); return episodeIDs; }, getMarkers: function getMarkers() { var timeQueue = this._currentTimeQueue(); if (timeQueue) { return timeQueue.getMarkers(); } return []; }, isHighlightSupported: function isHighlightSupported(widgetId) { var highlights = this._getTimelineHighlightsForWidget(widgetId); return Boolean(highlights && highlights.length); }, updateLastEstablishedEventQueue: function updateLastEstablishedEventQueue() { //get all the start times in timeQueue._eventQueue this.lastEstablishedEventQueue = []; var timeQueue = this._currentTimeQueue(); if (timeQueue) { this._updateLastEstablishedEventQueue(timeQueue); } return this.lastEstablishedEventQueue; }, _updateLastEstablishedEventQueue: function _updateLastEstablishedEventQueue(timeQueue) { for (var key in timeQueue._eventQueue) { if (timeQueue._eventQueue.hasOwnProperty(key)) { for (var selectedWidgetId in this.selectedWidgetIds) { if (timeQueue._eventQueue[key][0].item !== selectedWidgetId || timeQueue._eventQueue[key].length > 1) { this.lastEstablishedEventQueue.push(key); } } } } }, moveEpisodeBefore: function moveEpisodeBefore(id, beforeId, options) { options = this._validateModelOptions(options); this.timeline.episodes.reorder(id, beforeId, options); }, updateTimelineDuration: function updateTimelineDuration(id, start, end, options) { options = this._validateModelOptions(options); if (this.suggestionTimeRange.length > 0) { start = this.suggestionTimeRange[0]; end = this.suggestionTimeRange[1]; } var episode = this.timeline.episodes.get(id); var previousEpisodeActs = _.map(episode.acts.toArray(), _.clone); var previousEpisodeStart = episode.getEntranceAct().timer; var previousEpisodeEnd = episode.getExitAct().timer; var previousDuration = previousEpisodeEnd - previousEpisodeStart; var newDuration = end - start; // +- 2 ms is close... this should handle all rounding cases in a way that is not noticeable visually. // What is important is that we correctly detect all moves. If we detect a resize of 2 ms as a move that is not an issue. options.isMove = newDuration <= previousDuration + 2 && previousDuration <= newDuration + 2; // if we are reordering - let the reorder model change handle undo-redo var isReorder = options.isMove && Math.abs(previousEpisodeStart - start) <= 5; var notifyUpdateDurationListeners = function (isUndoRedo) { this.trigger('modelEpisode:changed', { id: id, value: [episode.getEntranceAct().timer, episode.getExitAct().timer] }); this.model.trigger('change', isUndoRedo || isReorder ? {} : { value: _.map(episode.acts.toArray(), _.clone), prevValue: previousEpisodeActs, sender: options.sender, data: options.payloadData, senderContext: { applyFn: function (value) { episode.acts.set(value, { 'silent': true }); notifyUpdateDurationListeners(true); }.bind(this) } }); this._refreshTimeQueue(); }.bind(this); episode.updateDuration(start, end, options); notifyUpdateDurationListeners(); this.suggestionTimeRange = []; }, _getTimelineHighlightsForWidget: function _getTimelineHighlightsForWidget(contentId) { var content = this.widgetHelper.getContent(contentId); var visualization = content.getFeature('Visualization'); // this returns only the mapped data slots var slots = visualization && visualization.getSlots().getMappedSlotList(); var slotEligible = function slotEligible(slot, dataItem, index) { // True if type is attribute and slot is not a multi measure series return !SlotAPIHelper.isMultiMeasuresSeriesOrValue(slot, index) && dataItem.getType() === 'attribute'; }; var highlightPayloads = []; _.each(slots, function (slot) { _.each(slot.getDataItemList(), function (dataItem, index) { var columnId = dataItem.getColumnId(); var label = dataItem.getLabel(); if (slotEligible(slot, dataItem, index) && !_.findWhere(highlightPayloads, { columnId: columnId })) { // using dataItem.getUniqueId() for the ID is problematic since the dataitem // that gets mapped to each payload entry can change when columns are dragged around, added, or deleted. highlightPayloads.push({ columnId: columnId, columnLabel: label, id: columnId, values: [] }); } }); }); return highlightPayloads; }, addTimelineHighlight: function addTimelineHighlight(widgetId, timer) { var episodeModel = this._getEpisodeModel(widgetId); var highlights = this._getTimelineHighlightsForWidget(widgetId); // default to current time if no timer is passed in timer = timer || timer === 0 ? timer : this.getCurrentTime(); // we might want that in the timeline models... we need to make sure that the current time is inside the widget time. // we make ensure that it's at a least 1 ms inside the entrance and exit time. timer = Math.max(timer, episodeModel.getEntranceAct().timer + 1); timer = Math.min(timer, episodeModel.getExitAct().timer - 1); var includesAllAct = { timer: timer, action: 'highlight', payload: highlights }; var options = { payloadData: { undoRedoTransactionId: _.uniqueId('highlight') } }; var newAct = episodeModel.acts.add(includesAllAct, options); this.trigger('slider:showHighlightSummary', { episodeModel: episodeModel, actModel: newAct }); return newAct; }, updateTimelineHighlight: function updateTimelineHighlight(widgetId, actId, attributes) { var episodeModel = this._getEpisodeModel(widgetId); var act = episodeModel.acts.get(actId); if (act) { act.set(attributes); } return act; }, deleteTimelineHighlight: function deleteTimelineHighlight(widgetId, actId) { var episodeModel = this._getEpisodeModel(widgetId); if (episodeModel && actId) { var actModel = episodeModel.acts.get(actId); if (actModel && actModel.action === 'highlight') { episodeModel.acts.remove(actModel); } } }, getSnapIndicatorTime: function getSnapIndicatorTime(timeRange, draggingElement) { var timeLength = timeRange[1] - timeRange[0], newTimeRange = [], changed = null, i; for (i = timeRange.length - 1; i >= 0; i--) { newTimeRange.unshift(timeRange[i]); } var checkTimeQueueResult = this._checkTimeQueueForSnapIndicator(timeRange, draggingElement, newTimeRange, timeLength); if (checkTimeQueueResult.changed) { newTimeRange = checkTimeQueueResult.timeRange; changed = checkTimeQueueResult.changed || changed; } var checkStartAndEndResult = this._checkStartAndEndForSnapIndicator(timeRange, draggingElement, newTimeRange); if (checkStartAndEndResult.changed) { newTimeRange = checkStartAndEndResult.timeRange; changed = checkStartAndEndResult.changed || changed; } if (draggingElement === 'content') { if (changed === 'start') { newTimeRange[1] = newTimeRange[0] + timeLength; } else if (changed === 'end') { newTimeRange[0] = newTimeRange[1] - timeLength; } } this.suggestionTimeRange = newTimeRange; return { changed: changed, timeRange: newTimeRange }; }, selectWidgetAndSlider: function selectWidgetAndSlider(widgetId) { this.deselectAllWidgetsAndSliders(); this.canvas.selectContent([widgetId], { hideContextBar: true }); return Promise.resolve(); }, deselectWidgetAndSlider: function deselectWidgetAndSlider(widgetId) { this.deselectAllWidgetsAndSliders(); this.canvas.deselectContent([widgetId]); return Promise.resolve(); }, deselectAllWidgetsAndSliders: function deselectAllWidgetsAndSliders() { var selectedContentList = this.canvas.getSelectedContentList(); var selectedContentIdList = selectedContentList.map(function (content) { return content.getId(); }); this.canvas.deselectContent(selectedContentIdList); }, getDefaultWidgetDuration: function getDefaultWidgetDuration() { return this.defaultWidgetDuration; }, getDuration: function getDuration() { var timeQueue = this._currentTimeQueue(); return timeQueue ? timeQueue.getDuration() : 0; }, isAtEndOfScene: function isAtEndOfScene() { return this.getCurrentTime() === this.getDuration(); }, isAtStartOfScene: function isAtStartOfScene() { return this.getCurrentTime() === 0; }, togglePlayThrough: function togglePlayThrough() { this.timeline.set({ playThrough: !this.isPlayThrough() }, { payloadData: { undoRedoTransactionId: _.uniqueId('togglePlayThrough') } }); }, toggleKioskMode: function toggleKioskMode() { this.timeline.set({ kioskMode: !this.isKioskMode() }, { payloadData: { undoRedoTransactionId: _.uniqueId('toggleKioskMode') } }); }, toggleNavigateMarkers: function toggleNavigateMarkers() { this.timeline.set({ navigateMarkers: !this.isNavigateMarkers() }, { payloadData: { undoRedoTransactionId: _.uniqueId('toggleNavigateMarkers') } }); }, toggleRefreshData: function toggleRefreshData() { this.timeline.set({ refreshData: !this.isRefreshData() }, { payloadData: { undoRedoTransactionId: _.uniqueId('toggleRefreshData') } }); }, isPlayThrough: function isPlayThrough() { return this.timeline.get('playThrough'); }, isKioskMode: function isKioskMode() { return this.timeline.get('kioskMode'); }, isNavigateMarkers: function isNavigateMarkers() { return this.timeline.get('navigateMarkers'); }, isRefreshData: function isRefreshData() { return this.timeline.get('refreshData'); }, jumpToNextMarker: function jumpToNextMarker() { var timeQueue = this._currentTimeQueue(); return timeQueue.jumpToNextMarker(); }, jumpToPreviousMarker: function jumpToPreviousMarker() { var timeQueue = this._currentTimeQueue(); return timeQueue.jumpToPreviousMarker(); }, setCurrentTime: function setCurrentTime(time) { var _this = this; var ret = false; var timeQueue = this._currentTimeQueue(); if (timeQueue) { return timeQueue.seek(time).then(function (result) { _this._animationDirector.finishWidgetsAnimation(); return result; }); } // TO DO: Need to look into to see finishWidgetsAnimation() needs promise chaining. this._animationDirector.finishWidgetsAnimation(); return Promise.resolve(ret); }, getCurrentTime: function getCurrentTime() { var timeQueue = this._currentTimeQueue(); return timeQueue ? timeQueue.getState().currentTime : 0; }, setCursorTime: function setCursorTime(time) { this.cursorTime = time; }, getCursorTime: function getCursorTime() { return this.cursorTime; }, getTickDuration: function getTickDuration() { var timeQueue = this._currentTimeQueue(); return timeQueue ? timeQueue.getTickLength() : 0; }, isPlaying: function isPlaying() { var timeQueue = this._currentTimeQueue(); return timeQueue ? timeQueue.isPlaying() : false; }, isStopped: function isStopped() { var timeQueue = this._currentTimeQueue(); return timeQueue ? timeQueue.isStopped() : true; }, pause: function pause() { var ret = false; var timeQueue = this._currentTimeQueue(); if (timeQueue) { ret = timeQueue.pause(); } this._animationDirector.pauseWidgetsAnimation(); return ret; }, play: function play() { var ret = false; var timeQueue = this._currentTimeQueue(); if (timeQueue) { this.deselectAllWidgetsAndSliders(); ret = timeQueue.play(); } this._animationDirector.resumeWidgetsAnimation(); return ret; }, stop: function stop() { var ret = false; var timeQueue = this._currentTimeQueue(); if (timeQueue) { ret = timeQueue.stop(); } this._animationDirector.finishWidgetsAnimation(); return ret; }, isWidgetSelected: function isWidgetSelected(id) { return this.selectedWidgetIds[id] ? true : false; }, getSelectedWidgetMap: function getSelectedWidgetMap() { return this.selectedWidgetIds; }, getTimeComponents: function getTimeComponents(milliseconds) { var remainingTime = milliseconds || 0; var hours = Math.floor(remainingTime / (60 * 60 * 1000)); remainingTime -= hours * 60 * 60 * 1000; var minutes = Math.floor(remainingTime / (60 * 1000)); remainingTime -= minutes * 60 * 1000; var seconds = Math.floor(remainingTime / 1000); remainingTime -= seconds * 1000; return { hours: hours, minutes: minutes, seconds: seconds, milliseconds: remainingTime }; }, getTimeLabel: function getTimeLabel(milliseconds, precision) { var components = this.getTimeComponents(milliseconds); // Truncate to 1 decimal place. var tenthSeconds = Math.round(components.milliseconds / 100); if (tenthSeconds >= 10) { // There was rounding to 10; we only want 1 decimal place, so round up to a second. tenthSeconds = 0; components.seconds++; } var label = ''; var hoursLabel = components.hours > 0 ? components.hours + ':' : ''; var minutesLabel = components.minutes + ':'; var secondsLabel = components.seconds; if (components.seconds < 10) { secondsLabel = '0' + secondsLabel; } label = label.concat(hoursLabel); label = label.concat(minutesLabel); label = label.concat(secondsLabel); if (precision) { label = label.concat('.' + tenthSeconds); } return label; }, /* * Event handlers. */ onSelectWidget: function onSelectWidget(event) { this.selectedWidgetIds[event.sender] = true; this._selectTimelineSlider(event.sender); }, onDeselectWidget: function onDeselectWidget(event) { delete this.selectedWidgetIds[event.sender]; this._deselectTimelineSlider(event.sender); }, onChangeWidget: function onChangeWidget(event) { this.trigger('slider:change', event); }, onDurationChanged: function onDurationChanged(event) { if (!this.scene || event.sceneId === this.scene.id) { this.trigger('duration:changed', event); } }, onTimeQueueTick: function onTimeQueueTick(event) { if (!this.scene || event.sceneId === this.scene.id) { this._triggerUpdateTime(); } }, onTimeQueueStateChanged: function onTimeQueueStateChanged(event) { if (!this.scene || event.sceneId === this.scene.id) { this._triggerUpdateTime(); this.trigger('playState:change', event); } }, onEpisodeAdded: function onEpisodeAdded(event) { // TODO: the views should probably be listening to the collection directly. this.trigger('modelEpisode:added', event.model.id); }, onEpisodeRemoved: function onEpisodeRemoved(event) { var widget = this.widgetHelper.getWidget(event.model.id); if (widget && widget.getVisApi && widget.getVisApi() && widget.getVisApi().ownerWidget) { widget.getVisApi().ownerWidget.off('dwChange:visTransaction', this.onWidgetChange, this); widget.getVisApi().ownerWidget.off('dwChange:visId', this.onWidgetChange, this); } //TODO: the views should probably listen on the collection directly. this.trigger('modelEpisode:removed', event.model.id); this._refreshTimeQueue(); }, onSceneSwap: function onSceneSwap(event) { var _this2 = this; _.each(event.scenes, function (scene) { _this2._getTimeQueue(scene.id).refresh(); }); }, onKioskModeChange: function onKioskModeChange(event) { this.trigger('kioskMode:change', event); }, onNavigateMarkersChange: function onNavigateMarkersChange(event) { this.trigger('navigateMarkers:change', event); }, onModelEpisodeChange: function onModelEpisodeChange(event) { // we need to rethink this method... all the cases should be different events really. This might mean we need to move some logic to the model. var timelineEvent = event; var episodeEvent = event.origCollectionEvent; var actEvent = event.origCollectionEvent ? event.origCollectionEvent.origCollectionEvent : null; if (actEvent && actEvent.name === 'add') { if (actEvent.model && actEvent.model.action === 'highlight') { this.trigger('slider:addHighlight', { actModel: actEvent.model, episodeModel: episodeEvent.model }); } } else if (actEvent && actEvent.name === 'remove') { if (actEvent.model && actEvent.model.action === 'highlight') { this.trigger('slider:removeHighlight', { actModel: actEvent.model, episodeModel: episodeEvent.model }); } } else if (episodeEvent && episodeEvent.model) { var episode = this._getEpisodeModel(episodeEvent.model.id); if (episode) { var timeRange = [episode.getEntranceAct().timer, episode.getExitAct().timer]; this.trigger('modelEpisode:changed', { id: event.origCollectionEvent.model.id, value: timeRange }); } if (actEvent && event.name === 'action') { this._previewAnimation(event.value, episodeEvent.model, actEvent.model); } } else if (timelineEvent.name === 'reorder') { // order changed, tell everyone about that this.trigger('modelEpisodes:reorder'); } this._refreshTimeQueue(); }, onTimelinePlaybackEnd: function onTimelinePlaybackEnd() { this._animationDirector.finishWidgetsAnimation(); }, _previewAnimation: function _previewAnimation(animation, episode, act) { var _this3 = this; var currentTime = this.getCurrentTime(); var entranceTime = episode.getEntranceAct().timer; var exitTime = episode.getExitAct().timer; var isVisible = currentTime >= entranceTime && currentTime < exitTime; var isEntrance = act.timer === entranceTime; if (isVisible) { if (isEntrance) { this._animationDirector.animate({ target: episode.id, animation: 'hide', duration: 0 }).then(function () { _this3._animationDirector.animate({ target: episode.id, animation: animation, duration: _this3.animationDuration }); }); } else { this._animationDirector.animate({ target: episode.id, animation: animation, duration: this.animationDuration }).then(function () { _this3._animationDirector.animate({ target: episode.id, animation: 'show', duration: 0 }); }); } } else { if (isEntrance) { this._animationDirector.animate({ target: episode.id, animation: animation, duration: this.animationDuration }).then(function () { _this3._animationDirector.animate({ target: episode.id, animation: 'hide', duration: _this3.animationDuration / 2 }); }); } else { this._animationDirector.animate({ target: episode.id, animation: 'show', duration: this.animationDuration / 2 }).then(function () { _this3._animationDirector.animate({ target: episode.id, animation: animation, duration: _this3.animationDuration }); }); } } }, /* * Helpers. */ _validateModelOptions: function _validateModelOptions(options) { options = options || {}; options.sender = options.sender || this; options.payloadData = options.payloadData || {}; options.payloadData.undoRedoTransactionId = options.payloadData.undoRedoTransactionId || _.uniqueId('TimelineController'); return options; }, _selectTimelineSlider: function _selectTimelineSlider(widgetId) { this.trigger('slider:select', { widgetId: widgetId }); }, _deselectTimelineSlider: function _deselectTimelineSlider(widgetId) { this.trigger('slider:deselect', { widgetId: widgetId }); }, _refreshTimeQueue: function _refreshTimeQueue() { var timeQueue = this._currentTimeQueue(); if (timeQueue) { timeQueue.refresh(); } }, _triggerUpdateTime: function _triggerUpdateTime() { var timeQueue = this._currentTimeQueue(); if (timeQueue) { this.trigger('time:update', timeQueue.getState()); } }, _getWidgetIds: function _getWidgetIds() { if (this.scene) { return this.model.layout.listWidgets([this.scene.id]); } return []; }, _isWidgetInCurrentScene: function _isWidgetInCurrentScene(id) { return this._getWidgetIds().indexOf(id) !== -1; }, _getEpisodeModel: function _getEpisodeModel(id) { return this.model.timeline.episodes.get(id); }, _getNewWidgetEpisodeInfo: function _getNewWidgetEpisodeInfo() { var currentTime = this.getCurrentTime(); var duration = this.getDuration(); var widgetDuration = duration - currentTime; // if we are at the end we use the full duration if (widgetDuration === 0) { widgetDuration = this.defaultWidgetDuration; } return { start: currentTime, end: currentTime + widgetDuration }; }, /* * called when an episode fragment is added, * It's the controller's job to adjust the timers to match the current 'scrubber' position. */ onEpisodeFragmentAdded: function onEpisodeFragmentAdded(event) { var options = this._validateModelOptions(event.options || {}); var startingDuration = this.getDuration(); // update the episode with the new duration. // this is needed since the episode is added in the board model with potentially the default values. // once we get here we know how to patch those default to take into account the 'scrubber' position. var info = this._getNewWidgetEpisodeInfo(); this.updateTimelineDuration(event.id, info.start, info.end, options); var currentDuration = this.getDuration(); // currently the duration is only extended when a widget is added at the end. // so we use it as a flag if (currentDuration > startingDuration) { this.timeline.stretchEndingEpisodes({ currentEndTime: startingDuration, newEndTime: currentDuration, subset: this._getWidgetIds() }, this, options.payloadData); } }, _checkTimeQueueForSnapIndicator: function _checkTimeQueueForSnapIndicator(timeRange, draggingElement, newTimeRange) { var eventQueueLength = this.lastEstablishedEventQueue.length; if (eventQueueLength === 0) { return {}; } var checkingInterval = this.suggestionTimelineCheckingInterval, changed = null, isContent, i; var checkSnapRange = function checkSnapRange(index, value) { var timeDifference = Math.abs(timeRange[index] - value), ret = timeDifference < checkingInterval; if (ret) { // convert to string newTimeRange[index] = +value; } return ret; }; //check the closest time in the event queue for (i = 0; i < eventQueueLength && !changed; i++) { isContent = draggingElement === 'content'; if ((isContent || draggingElement === 'leftHandle') && checkSnapRange(0, this.lastEstablishedEventQueue[i])) { changed = 'start'; } if ((isContent || draggingElement === 'rightHandle') && checkSnapRange(1, this.lastEstablishedEventQueue[i])) { changed = 'end'; } } return { timeRange: newTimeRange, changed: changed }; }, _checkStartAndEndForSnapIndicator: function _checkStartAndEndForSnapIndicator(timeRange, draggingElement, newTimeRange) { var changed = null, start = timeRange[0], end = timeRange[1], duration = this.getDuration(), isContent = draggingElement === 'content'; if ((isContent || draggingElement === 'leftHandle') && start > 0 && start < this.suggestionTimelineCheckingInterval) { newTimeRange[0] = 0; changed = 'start'; } if ((isContent || draggingElement === 'rightHandle') && end < duration && end > duration - this.suggestionTimelineCheckingInterval) { newTimeRange[1] = duration; changed = 'end'; } return { timeRange: newTimeRange, changed: changed }; }, _getTimeQueue: function _getTimeQueue(id) { var timeQueue = this.timeQueues[id]; if (!timeQueue) { timeQueue = new TimeQueue({ sceneId: id, boardModel: this.model, eventRouter: this.eventRouter, widgetHelper: this.widgetHelper }); this.timeQueues[id] = timeQueue; timeQueue.reset(); } return timeQueue; }, _currentTimeQueue: function _currentTimeQueue() { var ret = null; if (this.scene && this.scene.id) { ret = this._getTimeQueue(this.scene.id); } return ret; }, _onMaximizeWidget: function _onMaximizeWidget() { if (this.isPlaying()) { this.pause(); this.pausedForMaximize = true; } else { this.pausedForMaximize = false; } }, _onRestoreWidget: function _onRestoreWidget() { if (this.pausedForMaximize) { this.play(); } }, onAnimate: function onAnimate(event) { if (this._animationDirector) { this._animationDirector.animate(event); } } }); return Controller; }); //# sourceMappingURL=TimelineController.js.map