'use strict'; /** * Licensed Materials - Property of IBM * IBM Cognos Products: Storytelling (C) Copyright IBM Corp. 2015, 2019 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['gemini/dashboard/layout/authoring/views/PageCollectionView', 'jquery', 'underscore', 'baglass/core-client/js/core-client/utils/Utils', '../../../lib/@ba-ui-toolkit/ba-graphics/dist/illustrations-js/visualization-widget_128'], function (BaseClass, $, _, Utils, visualizationIcon) { var PanAndZoomLayout = BaseClass.extend({ init: function init(options) { PanAndZoomLayout.inherited('init', this, arguments); this.dashboardApi = options.dashboardApi; this._dndManager = this.dashboardApi.getDashboardCoreSvc('.DndManager'); this.startOverview = { id: 'start_overview_' + this.model.id, $el: this.$el.find('#' + 'start_overview_' + this.model.id) }; this.endOverview = { id: 'end_overview_' + this.model.id, $el: this.$el.find('#' + 'end_overview_' + this.model.id) }; this.specializeConsumeView(['currentSceneChanged', 'onKeyDown', 'onLayoutReady', 'onPlaybackNext', 'onPlaybackPrevious']); this._updateSceneIndexes(); this._scaleOverviewElements(); // save the value so we can unregister later this._overviewDragEventCallback = this._onOverviewSceneDrag.bind(this); this.$el.find('.overviewBlockerCell').hammer().on('dragstart', this._overviewDragEventCallback); // This is here because on init of this class the consumeView's internal handling of scenes might not // be loaded yet. When "onLayoutReady" is called (see below) we will call _registerScene on all model // items as we do here. // This block is when we switch between consumption and authoring modes. if (this.consumeView._getScenes().length !== 0) { _.each(this.model.items, this._registerScene.bind(this)); } }, destroy: function destroy() { _.each(this.model.items, this._deRegisterScene.bind(this)); this.$el.find('.overviewBlockerCell').hammer().off('dragstart', this._overviewDragEventCallback); PanAndZoomLayout.inherited('destroy', this, arguments); }, /** * Called when a new scene is added. * * @param {Object} layoutView The scene layout model that is being added/moved * @param {String} [insertBefore] The id value of the scene model to use as the next sibling * @param {Object} [payload] Payload of previously executed events * * @returns {Promise} */ add: function add(layoutView, insertBefore, payload) { if (!this.consumeView._hasScene(layoutView.model)) { this._addNewScene(layoutView, insertBefore, payload); } else { this._reorderScenes(layoutView, insertBefore); } return this.consumeView._selectScene(layoutView.model.id); }, /** * Called when a view is removed * * @param node */ removeChild: function removeChild(layoutView, payload) { payload = this._checkPayloadData(payload); var model = layoutView.model; this._deRegisterScene(model); this.consumeView._impress.removeStep(this.consumeView._getViewId(model)); // Remove the content nodes layoutView.$el.find('.overviewBlockerCell').hammer().off('dragstart', this._overviewDragEventCallback); layoutView.$el.parent().remove(); this.consumeView._removeScene(model); // in the case of undo/redo we have already updated the models. if (payload.sender !== 'UndoRedoController') { this._shiftFollowingScenes(model.data.positionIndex, -1, payload); } this._updateOverviewsLocation(); PanAndZoomLayout.inherited('remove', this, arguments); this._updateSceneIndexes(); }, /** * A specializeConsumeView method * When creating a new story we need to wait for the layout to be ready (which means the DOM is constructed) * before we call _registerScene on the model items. */ onLayoutReady: function onLayoutReady() { var _this = this; return this.overridden.onLayoutReady().then(function () { _.each(_this.model.items, _this._registerScene.bind(_this)); }); }, /** * A specializeConsumeView method */ currentSceneChanged: function currentSceneChanged() { // call consume view this.overridden.currentSceneChanged(); this._scaleOverviewElements(); }, /** * A specializeConsumeView method */ onPlaybackNext: function onPlaybackNext() { return this; }, /** * A specializeConsumeView method */ onPlaybackPrevious: function onPlaybackPrevious() { return this; }, /** * A specializeConsumeView method */ onKeyDown: function onKeyDown(event) { // for now we only deal with keys on the overview. if (!this.$el.hasClass('overview')) { this.overridden.onKeyDown(event); return; } var keyCode = event.keyCode || event.charCode; if (keyCode === 13 /* enter */ || keyCode === 32 /* space */) { var $target = $(event.target).closest('.step.pageTabContent'); var targetId = $target[0].dataset.modelId; var $selected = this.$el.find('.swapSelected.step.pageTabContent').removeClass('swapSelected'); if ($selected.length === 1) { var selectedId = $selected[0].dataset.modelId; // if we have something already selected we swap or remove the selection. // but we already reselected the item so we only deal with the swap if (targetId && targetId !== selectedId) { var scene1 = this.consumeView._getSceneById(selectedId); var scene2 = this.consumeView._getSceneById(targetId); this._swapScenes(scene1, scene2); } } else { // nothing is selected so add the class to target $target.addClass('swapSelected'); } } else { this.overridden.onKeyDown(event); } }, _addNewScene: function _addNewScene(layoutView, insertBefore, payload) { var sceneIndex = this.consumeView.model.items.length - 1; var model = layoutView.model; payload = this._checkPayloadData(payload); // in the case of undo/redo we have already updated the model. if (payload.sender !== 'UndoRedoController') { // if model.data is set and we are here then we are a duplicating a scene if (model.data) { this._shiftFollowingScenes(model.data.positionIndex, 1, payload); // put the duplicated scene after the one it was copied from //and now that we shifted the pattern there is an empty spot there sceneIndex = model.data.positionIndex + 1; } this.model.updateModel({ updateArray: [{ id: model.id, data: { positionIndex: sceneIndex } }] }, payload.sender, payload.data); } var template = this.htmlTemplate.getItemTemplate(this.model.type, '', model); var sceneContent = this.htmlTemplate.replaceLayoutValues(template, model); var $sceneContent = $(sceneContent); $sceneContent.append(layoutView.domNode); $sceneContent.find('.overviewBlockerCell').hammer().on('dragstart', this._overviewDragEventCallback); // This is not optimal, but in case of undo/redo we might not navigate to the new scene. So we have to do this here. $sceneContent.removeClass('hiddenScene'); // impress adds a nested div child to impress section $sceneContent.insertBefore(this.endOverview.$el); this.consumeView._impress.addStep('#' + $sceneContent[0].id, this.endOverview.id); this.consumeView._addScene(model); if (insertBefore) { this._reorderScenes(layoutView, insertBefore); } this.consumeView._updateEndpoints(); this._updateOverviewsLocation(); this._registerScene(model); }, _reorderScenes: function _reorderScenes(layoutView, insertBefore) { var model = layoutView.model; var scene = this.consumeView._getScene(model); scene.$el.detach().append(layoutView.domNode); var $before = this.endOverview.$el; if (insertBefore) { var beforeScene = this.consumeView._getSceneById(insertBefore); $before = beforeScene.$el; } scene.$el.insertBefore($before); this.consumeView._updateSceneList(model, insertBefore); var sceneId = scene.$el[0].id; this.consumeView._impress.removeStep(sceneId); this.consumeView._impress.addStep('#' + sceneId, $before[0].id); this.layoutController.eventRouter.trigger('scene:reorder', { model: model, insertBefore: insertBefore }); this._updateSceneIndexes(); this._scaleOverviewElements(); }, _shiftFollowingScenes: function _shiftFollowingScenes(fromPosition, amount, payload) { var scenes = this.consumeView._getScenes(); var dataUpdateArray = []; _.each(scenes, function (scene) { var model = scene.getLayoutView().model; if (model.data.positionIndex > fromPosition) { var positionIndex = model.data.positionIndex + amount; dataUpdateArray.push({ id: model.id, data: { positionIndex: positionIndex } }); } }.bind(this)); this.model.updateModel({ updateArray: dataUpdateArray }, payload.sender, payload.data); }, _registerScene: function _registerScene(sceneModel) { this._addDropZone(sceneModel); this.consumeView._updateSceneLabel({ model: sceneModel }); sceneModel.on('change:data', this._onSceneModelChanged, this); sceneModel.on('change:title', this.consumeView._updateSceneLabel, this); }, _deRegisterScene: function _deRegisterScene(sceneModel) { this._removeDropZone(sceneModel); sceneModel.off('change:data', this._onSceneModelChanged, this); sceneModel.off('change:title', this.consumeView._updateSceneLabel, this); }, _onSceneModelChanged: function _onSceneModelChanged(payload) { var model = this.model.findModel(payload.modelId); var scene = this.consumeView._getScene(model); if (scene && model) { this.consumeView._updateSceneSize(scene, this.consumeView._getViewPort()); this._scaleOverviewElements(); } }, _updateOverviewsLocation: function _updateOverviewsLocation() { var info = this.consumeView._getOverviewLocation(this.consumeView._getViewPort()); this.consumeView._updateElementData(this.startOverview.$el, info); this.consumeView._updateElementData(this.endOverview.$el, info); this._scaleOverviewElements(); }, /** * Go through all of the scenes on the canvas and update the index number that's shown * on the start/end overviews. */ _updateSceneIndexes: function _updateSceneIndexes() { var items = this.model.items; this.$el.find('.sceneOrder').each(function (i) { this.setAttribute('data-index', i + 1); this.setAttribute('data-order', items[i].data.positionIndex + 1); }); }, /** * Scale border width of scenes based on data-scale should be called any time user * moves from overview to a scene */ _scaleSceneBorderWidth: function _scaleSceneBorderWidth() { // if a scene (not an overview) is currently selected, shrink the border width // so that outline doesn't appear too bulky when zoomed in // OR if a story only has one scene, border width should be same whether viewing // overview or the scene var defaultBorderWidth = 5; var currentSceneIndex = this.consumeView._getSceneIndexById(this.consumeView.sceneId); var isOnScene = currentSceneIndex >= 0; var numScenes = this.consumeView._scenes.length; if (isOnScene || numScenes === 1) { defaultBorderWidth = 2; } this.$el.find('.pageTabContent').each(function (index, sceneDiv) { var visualPositionIndex = this.model.items[index].data.positionIndex; var scale = this.consumeView.getSceneScale(visualPositionIndex) || 1; var newBorderWidth = defaultBorderWidth / scale; $(sceneDiv).find(this.consumeView._dropZoneSelector).each(function (index, dropZoneEl) { dropZoneEl.style.borderWidth = newBorderWidth + 'px'; }); }.bind(this)); }, /** * scale box and font size of scene #'s in overview based on data-scale */ _scaleOverviewElements: function _scaleOverviewElements() { // default sizes in pixels for the width and height of the sceneOrder div, and font var defaultBoxSize = 80; var defaultFontSize = 48; this.$el.find('.sceneOrder').each(function (index, sceneOrderDiv) { var visualPositionIndex = this.model.items[index].data.positionIndex; var scale = this.consumeView.getSceneScale(visualPositionIndex) || 1; var newBoxSize = defaultBoxSize / scale; var newFontSize = defaultFontSize / scale; sceneOrderDiv.style.width = newBoxSize + 'px'; sceneOrderDiv.style.height = newBoxSize + 'px'; sceneOrderDiv.style.fontSize = newFontSize + 'px'; }.bind(this)); this._scaleSceneBorderWidth(); }, _removeDropZone: function _removeDropZone(targetModel) { if (targetModel._dropZone) { targetModel._dropZone.remove(); } }, _addDropZone: function _addDropZone(targetModel) { var targetScene = this.consumeView._getScene(targetModel); if (targetScene) { targetModel._dropZone = this._dndManager.addDropTarget(targetScene.$el[0], { accepts: function accepts(dragObject) { return dragObject.type === 'scene'; }, onDrop: function (dragObject) { targetScene.$el.removeClass('activeDropZone'); this._swapScenes(targetScene, dragObject.data.scene); }.bind(this), onDragEnter: function onDragEnter() { targetScene.$el.addClass('activeDropZone'); }, onDragLeave: function onDragLeave() { targetScene.$el.removeClass('activeDropZone'); } }); } }, _swapScenes: function _swapScenes(scene1, scene2) { var model1 = scene1.getLayoutView().model; var model2 = scene2.getLayoutView().model; var payloadData = { undoRedoTransactionId: _.uniqueId('PanAndZoomLayoutTransaction') }; var model1UpdateArray = _.map(model1.items, function (model) { return { id: model.id, parentId: model2.id }; }); var model2UpdateArray = _.map(model2.items, function (model) { return { id: model.id, parentId: model1.id }; }); var model1TitleOld = model1.get('title'); model1.updateModel({ updateArray: model1UpdateArray }, this, payloadData); model1.set({ title: model2.get('title') }, { payloadData: payloadData }); model2.updateModel({ updateArray: model2UpdateArray }, this, payloadData); model2.set({ title: model1TitleOld }, { payloadData: payloadData }); // moving the content resets the styling we applied, so we need to redo it this._scaleOverviewElements(); this.layoutController.eventRouter.trigger('scene:swap', { scenes: [scene1, scene2] }); }, _onOverviewSceneDrag: function _onOverviewSceneDrag(event) { var sceneId = $(event.target).closest('.overview .step.pageTabContent')[0].dataset.modelId; var scene = this.consumeView._getSceneById(sceneId); if (scene) { var pageContainer = scene.getLayoutView().$el[0]; var bounds = pageContainer.getBoundingClientRect(); var $avatar = $(document.createElement('div')).css({ left: bounds.left, top: bounds.top, width: bounds.width, height: bounds.height, // dragging the avatar from the center instead of a corner looks better transform: 'translate( -50%, -50%)' }).addClass('overviewSceneMoveAvatar'); Utils.setIcon($avatar, visualizationIcon.default.id); this._dndManager.startDrag({ event: event, type: 'scene', data: { scene: scene }, avatar: $avatar[0], callerCallbacks: { onDragStart: function onDragStart() { scene.$el.addClass('sourceDropZone'); }, onDragDone: function onDragDone() { scene.$el.removeClass('sourceDropZone'); } } }); } }, _checkPayloadData: function _checkPayloadData(payload) { payload = payload || {}; payload.sender = payload.sender || this; payload.data = payload.data || { undoRedoTransactionId: _.uniqueId('PanAndZoomLayoutTransaction') }; return payload; } }); return PanAndZoomLayout; }); //# sourceMappingURL=PanAndZoomLayout.js.map