123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- '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
|