'use strict'; /* * Licensed Materials - Property of IBM * IBM Cognos Products: Storytelling * (C) Copyright IBM Corp. 2017, 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/Class', 'underscore', 'gemini/dashboard/util/PxPercentUtil', './layouts/BlankSceneLayout', './layouts/SceneLayout2', './model/TimelineModel'], function (Class, _, PxPercentUtil, BlankSceneLayout, SceneLayout2, TimelineModel) { var StoryService = Class.extend({ /** * @class */ init: function init(options) { StoryService.inherited('init', this, arguments); this.dashboardApi = options.dashboardApi; this.hasOutOfBoundsWidget = false; this.hasEmptyExploreCards = false; }, /** * * @param {Object} options * @param {Object} options.boardModel The board model to convert * @param {Object} [options.targetInfo] * @param {string} [options.targetInfo.type='slideshow'] * @param {string} [options.targetInfo.transition=null] * * @returns {Promise} The successfully modified version of the boardModel that was passed in. */ createStory: function createStory(options) { var _this = this; // setup this._checkOptions(options); this._setTargetInfo(options); return this._createModelObjectForStory(options.boardModel, options.sourceType).then(function (modelJson) { modelJson.timeline = modelJson.timeline || {}; _this._updateLayout(modelJson.layout); // set the episodes from the array of widgets var widgets = _this._findDescendantsWithType('widget', modelJson.layout.items); TimelineModel.widgetsToEpisodes(modelJson.timeline, widgets, {}); return { model: modelJson, status: { hasOutOfBoundsWidget: options.targetInfo.type !== 'slideshow' && _this.hasOutOfBoundsWidget, hasEmptyExploreCards: _this.hasEmptyExploreCards } }; }); }, _findDescendantsWithType: function _findDescendantsWithType(type, items) { var descendants = []; if (items) { for (var i = 0; i < items.length; i++) { if (items[i].type === type) { descendants.push({ type: 'widget', id: items[i].id }); } else if (items[i].items) { descendants.push.apply(descendants, this._findDescendantsWithType(type, items[i].items)); } } } return descendants; }, /** * @param {Object} options * @param {Object} options.boardModel The board model to convert * @param {Object} [options.targetInfo] * @param {string} [options.targetInfo.type='slideshow'] * @param {string} [options.targetInfo.transition=null] * * @returns {Promise} */ updateStory: function updateStory(options) { // setup this._checkOptions(options); this._setTargetInfo(options); //take a copy of the board model var boardModelJson = JSON.parse(JSON.stringify(options.boardModel.toJSON())); this._convertFreeformToTemplate(boardModelJson.layout); this._updateLayout(boardModelJson.layout); var contentView = this.dashboardApi.getCurrentContentView(); contentView.clearTransientState(); contentView.getDashboardApi().getFeature('DashboardState').setDirty(true); var boardId = contentView.getBoardId() || null; //Retain the same board ID return contentView.reloadFromJSONSpec(boardModelJson, { boardId: boardId }); }, /** * @private * @param {Object} model The BoardModel spec to build off of to convert a Dashboard to a Story. * @param {String} sourceType * @returns {Promise} Returns a promise that will return a model object that holds Storytelling properties */ _createModelObjectForStory: function _createModelObjectForStory(model, sourceType) { var _this2 = this; var modelJson = model.toJSON(); return this.dashboardApi.getGlassSvc('.ConversionService').then(function (srvc) { return srvc.convert(sourceType.toUpperCase(), 'STORY', JSON.stringify(modelJson)); }).then(function (spec) { var specJson = JSON.parse(spec); if (sourceType === 'explore') { if (modelJson.layout.items.length !== specJson.layout.length) { _this2.hasEmptyExploreCards = true; } var _convertExploreLayout = _this2._convertExploreLayoutToStory(specJson), layout = _convertExploreLayout.layout, widgets = _convertExploreLayout.widgets; modelJson.layout = layout; modelJson.widgets = _this2._convertExploreWidgetsToStory(specJson.widgets); // Reserve title placehodler widget(s) from Scenelayout2 template Object.assign(modelJson.widgets, widgets); } else if (sourceType === 'dashboard') { _this2._convertFreeformToTemplate(modelJson.layout); } return modelJson; }).catch(function (error) { if (sourceType === 'explore') { _this2.hasEmptyExploreCards = true; modelJson.layout = _this2._convertExploreLayoutToStory().layout; modelJson.widgets = {}; return modelJson; } return Promise.reject(error); }); }, /** * Convert layout from explore to storytelling layout by using SceneLayout2 template * @private * @param {Array} layouts the converted explore spec. If there is no spec, return a layout spec with an empty scene * @returns return the converted storytelling layout */ _convertExploreLayoutToStory: function _convertExploreLayoutToStory(spec) { var _this3 = this; var storyLayout = { layout: { items: [], layoutPositioning: 'relative', pageSize: { width: 1280, height: 720 }, style: { height: '100%' } }, widgets: {} }; if (spec && spec.layout) { return spec.layout.reduce(function (acc, item) { return _this3._convertCardToSceneLayout(acc, item, spec.widgetToCardMap); }, storyLayout); } else { // empty explore cards var blankSceneLayout = BlankSceneLayout.get().layout; storyLayout.layout.items.push(blankSceneLayout); return storyLayout; } }, _convertCardToSceneLayout: function _convertCardToSceneLayout(acc, item, widgetToCardMap) { var _replaceWidget = this._replaceWidget(item, 'vis1_', SceneLayout2), layout = _replaceWidget.layout, widgets = _replaceWidget.widgets; // Reserve explore card layout id layout.id = widgetToCardMap[item.id]; acc.layout.items.push(layout); Object.assign(acc.widgets, widgets); return acc; }, /** * Replace the widget in the template with the giving item * @param {*} specItem Widget to replace * @param {*} idPrefix The prefix id of the widget that needs replacing * @param {*} layoutTemplate Scene layout template * @returns sceneLayout */ _replaceWidget: function _replaceWidget(specItem, idPrefix, layoutTemplate) { var sceneLayout = layoutTemplate.get(); var placehodler = sceneLayout.layout.items[0].items.find(function (item) { return item.id && item.id.startsWith(idPrefix); }); delete sceneLayout.widgets[placehodler.id]; // Use the style from layoutTemplate delete specItem.style; Object.assign(placehodler, specItem); return sceneLayout; }, /** * Convert explore widgets array to storyteling widget object * Example: * array = [ {id: 'widget1' ...}, {id: 'widget2', ...} ] * returnObj = { widget1: {id: 'widget1' ...}, widget2: {id: 'widget2', ...} } * * @param {Array} widgets converted widgets array from explore * @returns return a storytelling widget object */ _convertExploreWidgetsToStory: function _convertExploreWidgetsToStory(widgets) { return widgets.reduce(function (obj, widget) { obj[widget.id] = widget; return obj; }, {}); }, _checkOptions: function _checkOptions() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (!options.targetInfo) { options.targetInfo = {}; } if (!options.boardModel) { throw new Error('No board model found to convert'); } }, _setTargetInfo: function _setTargetInfo(options) { this._targetInfo = { type: this._validateType(options.targetInfo.type) || 'slideshow' }; // only slideshow type has a transition if (options.targetInfo.type === 'slideshow') { this._targetInfo.transition = this._validateTransition(options.targetInfo.transition) || 'none'; } }, /** * @private * @param {string} type The type of a Board Model being dealt with; dashboard, slideshow, guidedjourney, etc. * * @returns {string | null} If type is valid then it is returned otherwise null is returned */ _validateType: function _validateType(type) { // Add other valid types to the list of cases that are acceptable switch (type) { case 'dashboard': case 'slideshow': case 'panAndZoom1': case 'panAndZoom2': case 'panAndZoom3': case 'panAndZoom4': case 'panAndZoom5': case 'panAndZoom6': return type; default: return null; } }, /** * @private * @param {string} transition The transition type the story uses to move between scenes * * @returns {string | null} If the transition is valid then it is returned otherwise null is returned */ _validateTransition: function _validateTransition(transition) { // Add other valid transitions to the list as more become acceptable switch (transition) { case 'none': case 'scaleAndSlide': case 'animatedPath': return transition; default: return null; } }, /** * @private * @param {Object} layoutJson A JSON object */ _updateLayout: function _updateLayout(layoutJson) { layoutJson.type = this._targetInfo.type; switch (this._targetInfo.type) { case 'dashboard': break; case 'slideshow': this._updateLayoutForSlideShow(layoutJson); break; case 'panAndZoom1': case 'panAndZoom2': case 'panAndZoom3': case 'panAndZoom4': case 'panAndZoom5': case 'panAndZoom6': this._updateLayoutForPanAndZoom(layoutJson); break; default: break; } this._fitWidgetsToScenes(layoutJson); return layoutJson; }, _updateLayoutForSlideShow: function _updateLayoutForSlideShow(layoutJson) { // Step 1: remove GJ-specific properties layoutJson.items.forEach(function (item) { delete item.data; delete item.style; }); delete layoutJson.hasOverview; delete layoutJson.showOverviews; // Step 2: set up slideshow-specific properties // transition defaults to 'none' when converting to slideshow layoutJson.transition = this._targetInfo.transition; }, _updateLayoutForPanAndZoom: function _updateLayoutForPanAndZoom(layoutJson) { // Step 1: remove transition, if it exists (ie. SS -> GJ) delete layoutJson.transition; // add overviews if the previous story type did not support them if (!layoutJson.hasOverview) { layoutJson.showOverviews = { showStart: true, showEnd: true }; layoutJson.hasOverview = true; } layoutJson.items.forEach(function (item, index) { item.data = { positionIndex: index }; }); }, _isFreeform: function _isFreeform(layout) { return layout.type === 'container' && layout.items && layout.items[0].type === 'genericPage' && layout.items[0].layoutPositioning === 'absolute'; }, _convertFreeformToTemplate: function _convertFreeformToTemplate(topLayout) { var _this4 = this; /* Converting models with the layout: * - tab * - container * - layout positioning: absolute * - [widgets] * * to: * - tab * - container * - layout positioning: relative * - [widgets] */ topLayout.items.filter(this._isFreeform.bind(this)).forEach(function (layout) { return _this4._convertToRelativeLayout(topLayout.pageSize, layout.items[0]); }); }, _convertToRelativeLayout: function _convertToRelativeLayout(referencePageSize, layout) { // Change layout to relative layout.layoutPositioning = 'relative'; // loop over items in absolute layout (effectively widgets) layout.items.forEach(function (layoutItem) { // convert widget style represented in px to percent PxPercentUtil.changePixelPropertiesToPercent(layoutItem.style, referencePageSize); }); }, // we have no 'live' model to work with // so we work with the raw JSON _fitWidgetsToScenes: function _fitWidgetsToScenes(layout) { var _this5 = this; (layout.items || []).forEach(function (scene) { _this5._fitWidgetsToScene(scene); }); }, _fitWidgetsToScene: function _fitWidgetsToScene(childLayout) { var _this6 = this; // get all widgets/groups in scene/tab (basically anything with a style) var getStyledLayouts = function getStyledLayouts(layout) { if (layout.style) { return [layout]; } return (layout.items || []).reduce(function (acc, child) { acc.push.apply(acc, getStyledLayouts(child)); return acc; }, []); }; var widgets = getStyledLayouts(childLayout); if (!widgets.length) { return; } // look for any widgets that are sticking out of the scene. // the initial value is 100%, resulting in no scaling if no widgets are more than that. var maxX = widgets.reduce(function (acc, widget) { var newX = (parseFloat(widget.style.left, 10) || 0) + (parseFloat(widget.style.width, 10) || 0); if (newX > 100) { _this6.hasOutOfBoundsWidget = true; } return Math.max(acc, newX); }, 100); var maxY = widgets.reduce(function (acc, widget) { var newY = (parseFloat(widget.style.top, 10) || 0) + (parseFloat(widget.style.height, 10) || 0); if (newY > 100) { _this6.hasOutOfBoundsWidget = true; } return Math.max(acc, newY); }, 100); // maintain aspect ratio var scale = Math.min(100 / maxX, 100 / maxY); if (scale < 1) { widgets.forEach(function (widget) { ['top', 'left', 'width', 'height'].forEach(function (key) { if (widget.style[key] !== undefined) { widget.style[key] = (parseFloat(widget.style[key], 10) || 0) * scale + '%'; } }); }); } } }); return StoryService; }); //# sourceMappingURL=StoryService.js.map