'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(['gemini/lib/@waca/dashboard-common/dist/core/Model', './TimelineEpisodes', 'underscore'], function (Model, TimelineEpisodes, _) { var TimelineModel = Model.extend({ nestedCollections: { episodes: TimelineEpisodes }, whitelistAttrs: ['id', 'story', 'title', 'episodes', 'playThrough', 'kioskMode', 'navigateMarkers', 'refreshData'], init: function init(spec, options) { var _this = this; TimelineModel.inherited('init', this, arguments); if (!this.episodes) { this.set({ 'episodes': [] }); } this.boardModel = options.boardModel; this.dashboardApi = options.dashboardApi; this.dashboardApi.getCanvasWhenReady().then(function (canvas) { canvas.on('add:content:child', _this._addContent, _this); canvas.on('remove:content:child', _this._removeContent, _this); }); this.boardModel.on('addWidget', this._addWidget, this); this.boardModel.on('pre:removeWidget', this._removeWidget, this); this.boardModel.on('addFragment', this._addFragment, this); //at this point the board model does not trigger removeFragment. It only calls removeLayouts. this.boardModel.on('addLayouts', this._addLayouts, this); this.boardModel.on('pre:removeLayouts', this._removeLayouts, this); this.boardModel.on('duplicateLayout', this._duplicateLayout, this); }, /** * @param {Object} options: - Object that includes: * options.currentEndTime - the current end time of widgets that should be modified. * options.newEndTime - the new endtime that should be applied to the matching widgets * options.subset - id's of the subset of widgets that should be considered. * @param {Object} [sender] - sender context. * @param {Object} [payloadData] - extra data. Currently used for undo/redo transaction id. */ stretchEndingEpisodes: function stretchEndingEpisodes(options, sender, payloadData) { if (!options || !options.currentEndTime || !options.newEndTime || !options.subset) { return; } _.each(options.subset, function (id) { var episode = this.episodes.get(id); if (episode) { var act = episode.getExitAct(); if (act && act.timer >= options.currentEndTime) { act.set({ timer: options.newEndTime }, { sender: sender, payloadData: payloadData }); } } }.bind(this)); }, /* * add an episode that may me missing some information. * Once it's added we notify the TimelineController so it can fill in the missing information. */ addEpisodeFragment: function addEpisodeFragment(model, options) { var addedModel = this.episodes.add(model, options); this.trigger('timeline:episodeFragmentAdded', { id: addedModel.id, options: options }); if (options.insertBefore) { this.episodes.reorder(addedModel.id, options.insertBefore, options); } }, _getSceneDuration: function _getSceneDuration(widgetIds) { return _.reduce(widgetIds, function (workingDuration, id) { var ep = this.episodes.get(id); // only widgets that we have in the timeline contribute to the duration. // I.e not groups or anything else. if (ep) { return Math.max(workingDuration, ep.getExitAct().timer); } else { return workingDuration; } }.bind(this), 0); }, getWidgetTransitionMap: function getWidgetTransitionMap(scene1, scene2) { if (!scene1 || !scene2) { return null; } var rootLayout = this.boardModel.layout; var scene1Widgets = rootLayout.listWidgets([scene1.id]); var scene2Widgets = rootLayout.listWidgets([scene2.id]); // are the scenes not empty if (!scene1Widgets.length || !scene2Widgets.length) { return null; } // adjacency test var nextSiblingId = scene1.model.getNextSiblingId(); if (nextSiblingId && nextSiblingId !== scene2.id) { return null; } // remove from scene 1 all the widgets that don't go till the end; var scene1Duration = this._getSceneDuration(scene1Widgets); scene1Widgets = _.filter(scene1Widgets, function (id) { var ep = this.episodes.get(id); return ep ? ep.touchesEnd(scene1Duration) : true; }.bind(this)); // remove from scene 2 all the widgets that don't start at the beginning; scene2Widgets = _.filter(scene2Widgets, function (id) { var ep = this.episodes.get(id); return ep ? ep.touchesStart() : true; }.bind(this)); var transitionMap = { forward: {}, backward: {} }; // loop trough each widgets and find all the ones in scene2 _.each(scene1Widgets, function (scene1WidgetId) { var scene1Widget = rootLayout.findModel(scene1WidgetId); _.each(scene2Widgets, function (scene2WidgetId) { var scene2Widget = rootLayout.findModel(scene2WidgetId); if (scene1Widget.id === scene2Widget.from || scene1Widget.from === scene2Widget.id) { var list = rootLayout.getLinkedLayoutTree(scene1Widget, scene2Widget); _.each(list, function (widgets) { transitionMap.forward[widgets[0].id] = widgets[1].id; transitionMap.backward[widgets[1].id] = widgets[0].id; }); } }.bind(this)); }.bind(this)); if (!Object.keys(transitionMap.backward).length) { return null; } return transitionMap; }, _addContent: function _addContent(payload) { // the use of the context.undoRedo in here is temporary and should be considered internal // please do not use this mechanism. The usage will be changed to properly handle these events when // we switch completely to content API events var isUndoRedo = !!payload.context.undoRedo; if (!isUndoRedo) { return; } var options = payload.info.value; var id = payload.info.newContentId; var payloadData = { skipUndoRedo: isUndoRedo, undoRedoTransactionId: payload.transactionToken && payload.transactionToken.transactionId }; this.addEpisodeFragment({ type: 'widget', id: id }, { payloadData: payloadData, insertBefore: options.insertBefore }); }, _removeContent: function _removeContent(payload) { // the use of the context.undoRedo in here is temporary and should be considered internal // please do not use this mechanism. The usage will be changed to properly handle these events when // we switch completely to content API events var isUndoRedo = !!payload.context.undoRedo; if (!isUndoRedo) { return; } var id = payload.info.value; // dashboard is setting options we don't want // in the end we only want the ID, so we just grab that // we should revisit when we move to use the content API var data = { undoRedoTransactionId: payload.transactionToken && payload.transactionToken.transactionId, skipUndoRedo: isUndoRedo }; this._removeEpisode(id, data); }, _addWidget: function _addWidget(payload) { var options = payload.value.parameter; var sender = payload.sender; var payloadData = payload.data; if (this._isUndoRedoController(sender)) { return; } if (payloadData.replace) { var _payload$idMap; payload.idMap = (_payload$idMap = {}, _payload$idMap[options.insertBefore] = options.model.id, _payload$idMap); this._duplicateLayout(payload); this.episodes.reorder(options.model.id, options.insertBefore, { sender: sender, payloadData: payloadData }); } else { this.addEpisodeFragment({ type: 'widget', id: options.model.id }, { sender: sender, payloadData: payloadData, insertBefore: options.insertBefore }); } }, _removeWidget: function _removeWidget(payload) { var id = payload.id; var sender = payload.sender; // dashboard is setting options we don't want // in the end we only want the ID, so we just grab that // we should revisit when we move to use the content API var payloadData = { undoRedoTransactionId: payload.data && payload.data.undoRedoTransactionId }; if (this._isUndoRedoController(sender)) { return; } this._removeEpisode(id, payloadData, sender); }, _removeEpisode: function _removeEpisode(id, data, sender) { var episode = this.episodes.get(id); if (episode) { var index = this.episodes.indexOf(episode); var beforeID = this.episodes.models[index + 1] ? this.episodes.models[index + 1].id : null; // Added beforeID to make sure the postion doesn't change this.episodes.reorder(id, beforeID, { sender: sender, payloadData: data, forceEvent: true }); this.episodes.remove(episode, { sender: sender, payloadData: data }); } }, _addFragment: function _addFragment(payload) { var options = payload.value.parameter; var sender = payload.sender; var payloadData = payload.data; if (this._isUndoRedoController(sender)) { return; } var fragSpec = options.model; // the boardmodel has updated all the widgets and layouts by now, so the // ID's in the fragspec are the 'new' ones that where generated. // in this case we need a map from new to old, so we inverse the map here. var widgetIdMap = _.invert(options.widgetIdMap); _.each(fragSpec.widgets, function (widgetModel) { var newId = widgetModel.id; var oldId = widgetIdMap[newId]; var episodeJSON = _.find(fragSpec.episodes, function (obj) { return obj.id === oldId; }) || {}; // strip out act id in order to get new act id _.each(episodeJSON.acts, function (act) { delete act.id; }); _.extend(episodeJSON, { id: newId, type: 'widget' }); this.addEpisodeFragment(episodeJSON, { sender: sender, payloadData: payloadData }); }.bind(this)); }, _addLayouts: function _addLayouts(payload) { var _this2 = this; var options = payload.value.parameter; if (this._isUndoRedoController(payload.sender)) { return; } _.each(options.widgetSpecMap, function (widget) { _this2.addEpisodeFragment({ id: widget.id, type: 'widget' }, { sender: payload.sender, payloadData: payload.data }); }); }, _removeLayouts: function _removeLayouts(payload) { var _this3 = this; var sender = payload.sender; // dashboard is setting options we don't want // in the end we only want the ID, so we just grab that // we should revisit when we move to use the content API var payloadData = { undoRedoTransactionId: payload.data && payload.data.undoRedoTransactionId, skipUndoRedo: payload.data && payload.data.skipUndoRedo }; if (this._isUndoRedoController(sender)) { return; } var widgetIds = this.boardModel.layout.listWidgets(payload.idArray); widgetIds.forEach(function (id) { var episode = _this3.episodes.get(id); var index = _this3.episodes.indexOf(episode); var beforeID = _this3.episodes.models[index + 1] ? _this3.episodes.models[index + 1].id : null; // Added beforeID to make sure the postion doesn't change _this3.episodes.reorder(id, beforeID, { sender: sender, payloadData: payloadData, forceEvent: true }); _this3.episodes.remove(episode, { sender: sender, payloadData: payloadData }); }); }, _duplicateLayout: function _duplicateLayout(payload) { var _this4 = this; var idMap = payload.idMap; var sender = payload.sender; var payloadData = payload.data; if (this._isUndoRedoController(sender)) { return; } var clones = []; this.episodes.each(function (episode) { if (idMap[episode.id]) { // strip out act id in order to re-generate a new act id on clone var episodeJSON = episode.toJSON(); if (episodeJSON.acts) { episodeJSON.acts.forEach(function (act) { delete act.id; }); } var clone = new _this4.episodes.modelClass(episodeJSON); clone.replaceIds(idMap); clones.push(clone); } }); if (clones.length) { // preserve timeline order and save to the same undoRedoTransactionId as duplicateLayout this._reorderTimelines(this.episodes, clones, sender, payloadData); this.episodes.add(clones, { sender: sender, payloadData: payloadData, merge: true }); } }, _reorderTimelines: function _reorderTimelines(episodes, clones, sender, payloadData) { for (var i = clones.length - 1; i > 0; i--) { episodes.reorder(clones[i - 1].id, clones[i].id, { sender: sender, payloadData: payloadData }); } }, // the board model manually handles undo/redo and re-triggers events. // In the case of undo redo we have already done the work and we let the model deal with it. _isUndoRedoController: function _isUndoRedoController(sender) { return sender === 'UndoRedoController'; } }); /** * @static * @param {JSON} timeline timeline json object from the model * @param {Array} widgets array of widget objects * @param {Object} options collection options used when adding episodes * * @return {TimelineModel} */ TimelineModel.widgetsToEpisodes = function (timeline, widgets, options) { var episodes = new TimelineEpisodes(timeline.episodes); widgets.forEach(function (widget) { episodes.add({ type: 'widget', id: widget.id }, options); }); timeline.episodes = episodes.toJSON(); }; return TimelineModel; }); //# sourceMappingURL=TimelineModel.js.map