'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