'use strict'; /** * Licensed Materials - Property of IBM * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2013, 2020 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['jquery', 'underscore', './LayoutBaseView', '../../../../lib/@waca/dashboard-common/dist/ui/interaction/Utils', '../../../../app/nls/StringResources', '../../../../lib/@waca/core-client/js/core-client/utils/Deferred', '../../LayoutHelper', '../../../glass/util/InstrumentationUtil'], function ($, _, BaseLayout, utils, stringResources, Deferred, LayoutHelper, InstrumentationUtil) { var PageLayout = null; PageLayout = BaseLayout.extend({ init: function init(options) { PageLayout.inherited('init', this, arguments); this.services = options.services; this.specializeConsumeView(['setPreferredLocation', 'isLayoutRelatedToDropZone', 'addRelatedModel', 'removeRelatedModel']); this.$el.parent().addClass('templateDropZoneContainer'); this.updateRelatedContentState(); this.whenIsReadyDfd = new Deferred(); this.initializeDropZones(); }, initializeDropZones: function initializeDropZones() { this._dndManager = this.dashboardApi.getFeature('DashboardDnd.internal'); if (!this.centerDrop) { this.centerDrop = $('
' + stringResources.get('dropZoneLabel') + '
'); this.centerDrop.hide(); this.$el.append(this.centerDrop); } var $centerDropZone = this.centerDrop.find('.dropIcon'); this._dndManager.addDropTarget($centerDropZone[0], { accepts: this.accepts.bind(this), onDrop: this.onCentreDrop.bind(this), onDragEnter: this.onCentreDragEnter.bind(this), onDragLeave: this.onCentreDragLeave.bind(this), priority: -100 /* Indicate that any other drop zone takes priority */ }); // Set the layout node as a drop zone. This drop zone will not accept anything // but it is configured to receive the enter/leave/move events. This is used to show the maximize square target this._dndManager.addDropTarget(this.domNode, { accepts: function accepts() { return false; }, onDragEnter: this.onDragZoneEnter.bind(this), onDragLeave: this.onDragZoneLeave.bind(this), onDrop: this.onDropZoneDrop.bind(this), receiveEventsWhenNotAccepting: true, /* absolute pages are -100 priority. * We set the drop zone priority to be higher (i.e. -99) * so that it takes priority in case the drop zone is the same size as the parent absolute page * We also need to set it to negative to indicate that any other drop zone takes priority */ priority: -99 }); this._setupGlassDroppables($centerDropZone); this.whenIsReadyDfd.resolve(); }, whenIsReady: function whenIsReady() { return this.whenIsReadyDfd.promise; }, _setupGlassDroppables: function _setupGlassDroppables($centerDropZone) { if (this.$el.glassDroppableV2) { //we are in the glass, accept pin drops var thisObj = this; //center zone accepts drops (maximizes to area in template) $centerDropZone.glassDroppableV2({ onDrop: this._onPinDrop.bind(this), onEnter: this.onGlassCentreDragEnter.bind(this), onLeave: this.onCentreDragLeave.bind(this), allowOnDropPropagation: false }); //this.$el tracks onEnter, onLeave so that it can show the center drop zone this.$el.glassDroppableV2({ onEnter: function onEnter() { if (this.id === thisObj.$el[0].id) { thisObj.onDragZoneEnter(); } }, onLeave: function onLeave() { if (this.id === thisObj.$el[0].id) { thisObj.onDragZoneLeave(); } }, onDrop: function onDrop() { thisObj.onDropZoneDrop(); return true; }, allowOnDropPropagation: true }); } }, _showCenterDrop: function _showCenterDrop(data, isMovingOneWidget) { var excludeId = null; if (isMovingOneWidget) { var layout = data.nodeInfoList[0].node._layout; if (layout) { excludeId = layout.model.id; } } // We will not show the center drop if we already have a maximized widget // But if we are moving the widget that is already maximized, then we display the center. if (!this.hasMaximizedWidget() || excludeId && this.isWidgetMaximized(excludeId)) { this.centerDrop.show(); } }, onDragZoneEnter: function onDragZoneEnter(dragObject) { this.inDropZone = true; var data = dragObject && dragObject.data || {}; var isNewDrop = !data.nodeInfoList; var isMovingOneWidget = data.nodeInfoList && data.nodeInfoList.length === 1; // Show the center drop when we are dropping a new widget or moving one widget. if (isNewDrop || isMovingOneWidget) { this._showCenterDrop(data, isMovingOneWidget); var dropTarget = this._dndManager.getDropTargetFromNode(this.centerDrop.find('.dropIcon')[0]); this._dndManager.reassessDropTarget(dropTarget); } }, onDragZoneLeave: function onDragZoneLeave() { if (!this.centerDrop.hasClass('active')) { this.centerDrop.hide(); } this.inDropZone = false; }, onDropZoneDrop: function onDropZoneDrop() { this.deactivateAndhideCenterDropZone(); this.inDropZone = false; }, deactivateAndhideCenterDropZone: function deactivateAndhideCenterDropZone() { if (this.centerDrop) { this.centerDrop.removeClass('active'); this.centerDrop.hide(); } }, destroy: function destroy() { if (this._dndManager) { this._dndManager.removeDropTarget(this.centerDrop.find('.dropIcon')[0]); this._dndManager.removeDropTarget(this.domNode); this._dndManager = null; } this.centerDrop.remove(); this.centerDrop = null; if (this.$el.parent().find('.pagetemplateDropZone') <= 1) { this.$el.parent().removeClass('templateDropZoneContainer'); } if ($.glassdnd && $.glassdnd.cancelDroppable) { $.glassdnd.cancelDroppable(this.$el); } PageLayout.inherited('destroy', this, arguments); }, /** * Called by the DnD manager to check if this drop zone accept the dragged object * We only accept object with type widget and pin * @returns {Boolean} */ accepts: function accepts(dragObject) { var canvasDnD = this.dashboardApi.getFeature('CanvasDnD'); return canvasDnD.accepts(dragObject, { fromTemplate: true }); }, /** * Called by the DnD manager when we enter and move inside the centre of drop zone (only if the drop zone accepts the object) * */ onCentreDragEnter: function onCentreDragEnter() { this.centerDrop.addClass('active'); }, /** * Called by the DnD manager when we enter and move inside the centre of drop zone (only if the drop zone accepts the object) * */ onGlassCentreDragEnter: function onGlassCentreDragEnter() { this.centerDrop.show(); this.centerDrop.addClass('active'); }, /** * Called by the drag&drop manager when we leave the centre drop zone * */ onCentreDragLeave: function onCentreDragLeave() { this.centerDrop.removeClass('active'); if (!this.inDropZone) { this.centerDrop.hide(); } }, /** * Called by the drag&drop manager when a drop happens at the centre * * @param dragObject * @param targetNode */ onCentreDrop: function onCentreDrop(dragObject) { var _this = this; var promise = Promise.resolve(); if (dragObject.data.operation === 'move') { promise = Promise.resolve(this._moveDrop(dragObject)); } else if (dragObject.type === 'pin' && dragObject.data.operation === 'new') { promise = Promise.resolve(this._onPinDrop(dragObject)); } else if (dragObject.data.operation === 'new' || dragObject.type === 'MODEL_ITEM' || dragObject.type === 'GRID_HEADER_ITEM') { promise = this._newDrop(dragObject); } return promise.then(function () { return _this.deactivateAndhideCenterDropZone(); }); }, _moveDrop: function _moveDrop(dragObject) { var updateArray = []; var nodeInfo, nodeModel, options; for (var i = 0; i < dragObject.data.nodeInfoList.length; i++) { nodeInfo = dragObject.data.nodeInfoList[i]; nodeModel = nodeInfo.node._layout.model; options = { style: {}, parentId: this.model.getParent().id, id: nodeModel.id }; this.setPreferredLayoutProperties(options); options.insertBefore = this.getWidgetIdForInsertBefore(); updateArray.push(options); } var transactionApi = this.dashboardApi.getFeature('Transaction'); var transactionToken = transactionApi.startTransaction(); var nodeIds = updateArray.map(function (update) { return update.id; }); var validate = false; // for move drop, no need for validation in layoutPropertiesProvider this.dashboardApi.getCanvas().moveContent(this.model.getParent().id, nodeIds, transactionToken); this.updateModel(updateArray, transactionToken, validate); transactionApi.endTransaction(transactionToken); }, _newDrop: function _newDrop(dragObject) { var _this2 = this; return this._getModelToAddFromDragObject(dragObject).then(function (newModel) { if (newModel) { var widgetSpec = { model: newModel, parentId: _this2.id, layoutProperties: dragObject.data.layoutProperties || {} }; InstrumentationUtil.trackWidget('created', _this2.dashboardApi, widgetSpec.model); _this2.setPreferredLocation(widgetSpec); _this2._addWidget(widgetSpec, dragObject.isTouch); } }); }, /** * a helper function that handles pin dropping * * @private * * @param {object} dragObject - The object to be dropped */ _onPinDrop: function _onPinDrop(dragObject) { var pinSpec = dragObject.data.pinSpec; var isTouch = dragObject.isTouch; //gemini widget if (pinSpec.contentType === 'boardFragment') { this.setPreferredLayoutProperties(pinSpec.content.layout); } pinSpec.parentId = this.id; this.setMaximizedStateParentLocation(pinSpec); this._processWidgetSpecForPin(pinSpec, isTouch); this.deactivateAndhideCenterDropZone(); }, setPreferredLocation: function setPreferredLocation(options) { this.setPreferredLayoutProperties(options.layoutProperties); this.setMaximizedStateParentLocation(options); }, setMaximizedStateParentLocation: function setMaximizedStateParentLocation(options) { options.parentId = this.model.getParent().id; options.insertBefore = this.getWidgetIdForInsertBefore(); }, setPreferredLayoutProperties: function setPreferredLayoutProperties(props) { if (!props.style) { props.style = {}; } if (this.$el.is(':visible')) { //for selected tab/scene var contentOffset = this.$el.offset(); var parentOffset = this.$el.parent().offset(); props.style.top = Math.round(contentOffset.top - parentOffset.top); props.style.left = Math.round(contentOffset.left - parentOffset.left); props.style.height = this.$el.outerHeight(); props.style.width = this.$el.outerWidth(); this.moveToFitBoundaries(this.$el.parent().height(), this.$el.parent().width(), props.style); LayoutHelper.styleIntToPx(props.style); } else { //in the case of tab/scene hidden (for moving widgets from one tab/scene to another) var templateDropZone = this.$el[0]; var style = this._calculateStylePercentage(templateDropZone.style); props.style = style; } }, /** * Fix the old behavor where we always insert in a drop zone and place the object behind all existing objects. * The new logic will find the first object that is smaller than the dropzone (with a small amount of forgiveness) and insert before it. */ getWidgetIdForInsertBefore: function getWidgetIdForInsertBefore() { var zoneHeight = this.$el.height(); var zoneWidth = this.$el.width(); var widgets = this.$el.siblings(':not(.pagetemplateDropZone)'); var widgetNode = _.find(widgets, function (node) { if (node._layout) { var $node = $(node); return $node.outerHeight() < zoneHeight - 5 || $node.outerWidth() < zoneWidth - 5; } return false; }); return widgetNode ? widgetNode._layout.model.id : null; }, isLayoutRelatedToDropZone: function isLayoutRelatedToDropZone(node) { var props = {}; this.setPreferredLayoutProperties(props); var isRelated = false; var topIsNear, leftIsNear, heightIsNear, widthIsNear; if (this.$el.is(':visible')) { //for selected tab/scene var position = utils.position(node); var size = utils.widgetSize(node); topIsNear = this.isNear(Math.round(position.top), props.style.top, 5); leftIsNear = this.isNear(Math.round(position.left), props.style.left, 5); heightIsNear = this.isNear(size.height, props.style.height, 10); widthIsNear = this.isNear(size.width, props.style.width, 10); } else { //in the case of tab/scene hidden (for moving widgets from one tab/scene to another) topIsNear = this.isNear(node.style.top, props.style.top, 10); leftIsNear = this.isNear(node.style.left, props.style.left, 10); heightIsNear = this.isNear(node.style.height, props.style.height, 10); widthIsNear = this.isNear(node.style.width, props.style.width, 10); } isRelated = topIsNear && leftIsNear && heightIsNear && widthIsNear; return isRelated; }, /** * set a model as a related model to this drop zone. Related models are usually the ones snapped to this drop zone */ addRelatedModel: function addRelatedModel(id, payloadData) { var relatedWidgets = this.model.relatedLayouts || '|'; if (relatedWidgets.indexOf('|' + id + '|') === -1) { relatedWidgets += id + '|'; } this.model.set({ relatedLayouts: relatedWidgets }, { payloadData: payloadData.data }); this.updateRelatedContentState(); }, hasMaximizedWidget: function hasMaximizedWidget() { return !!this.model.relatedLayouts; }, isWidgetMaximized: function isWidgetMaximized(id) { var relatedWidgets = this.model.relatedLayouts || '|'; return relatedWidgets.indexOf('|' + id + '|') !== -1; }, /** * Remove a model as a related model to this drop zone. Related models are usually the ones snapped to this drop zone */ removeRelatedModel: function removeRelatedModel(id, payloadData) { var relatedWidgets = this.model.relatedLayouts; if (relatedWidgets && relatedWidgets.indexOf('|' + id + '|') !== -1) { relatedWidgets = relatedWidgets.replace('|' + id + '|', '|'); if (relatedWidgets === '|') { relatedWidgets = ''; } this.model.set({ relatedLayouts: relatedWidgets }, { payloadData: payloadData.data }); } this.updateRelatedContentState(); }, /** * Add a css class to indicate of this drop zone has related layouts. This will be used to find an empty spot when adding widgets to the page */ updateRelatedContentState: function updateRelatedContentState() { var relatedWidgets = this.model.relatedLayouts; if (!relatedWidgets) { this.$el.addClass('empty'); } else { this.$el.removeClass('empty'); } }, isNear: function isNear(i, s, variance) { return Math.abs(parseInt(i, 10) - parseInt(s, 10)) <= variance; }, _calculateStylePercentage: function _calculateStylePercentage(style) { var newStyle = {}; newStyle.top = style.top; newStyle.left = style.left; newStyle.width = 100 - parseInt(style.right, 10) - parseInt(style.left, 10) + '%'; newStyle.height = 100 - parseInt(style.bottom, 10) - parseInt(style.top, 10) + '%'; return newStyle; } }); return PageLayout; }); //# sourceMappingURL=TemplateDropZone.js.map