'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * Licensed Materials - Property of IBM * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2019, 2020 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['../../../../lib/@waca/dashboard-common/dist/ui/interaction/Utils', '../../../../app/nls/StringResources', 'jquery', '../../../../lib/@waca/dashboard-common/dist/core/APIFactory', '../../../../lib/@waca/dashboard-common/dist/api/ContentActionsProviderAPI', '../../../../dashboard/util/PxPercentUtil', '../../../../lib/@waca/dashboard-common/dist/ui/interaction/Utils'], function (Utils, stringResources, $, APIFactory, ContentActionsProviderAPI, PxPercentUtil, utils) { var GroupAction = function () { function GroupAction(_ref) { var features = _ref.features; _classCallCheck(this, GroupAction); if (features) { this.dashboard = features.API; features.ContentActions.registerProvider('group', this.getAPI()); this.deleteAction = features.deleteAction; this._icons = features.Icons; } this._handlers = []; } GroupAction.prototype.getAPI = function getAPI() { if (!this._api) { this._api = APIFactory.createAPI(this, [ContentActionsProviderAPI]); } return this._api; }; GroupAction.prototype.getLifeCycleHandlers = function getLifeCycleHandlers() { return [{ name: 'post:dashboard.initialize', action: this.postDashboardInitialize.bind(this) }, { name: 'dashboard.layout.interactions.ready', action: this.onDashboardLayoutInteractionsReady.bind(this) }]; }; GroupAction.prototype.postDashboardInitialize = function postDashboardInitialize() { if (this.dashboard) { this.controller = this.dashboard.getFeature('InteractionController.internal'); this.selectionHandler = this.controller.selectionHandler; this.onDashboardLayoutInteractionsReady(); } return Promise.resolve(); }; GroupAction.prototype.onDashboardLayoutInteractionsReady = function onDashboardLayoutInteractionsReady() { if (this.selectionHandler) { this.deRegisterEvents(); this._handlers.push(this.selectionHandler.on('selection:before', this.beforeSelection, this)); this.deleteAction.setOnBeforeDeleteCallback(this.onBeforeDelete.bind(this)); var resize = this.controller.getInteraction('resize'); if (resize) { this._handlers.push(resize.on('resize:done', this.recalculateGroupRect, this)); } var moveGroupContent = this.controller.getInteraction('moveGroupContent'); if (moveGroupContent) { this._handlers.push(moveGroupContent.on('moveGroupContent:done', this.recalculateGroupRect, this)); } } }; GroupAction.prototype.deRegisterEvents = function deRegisterEvents() { if (this._handlers) { this._handlers.forEach(function (handler) { handler.remove && handler.remove(); }); } this._handlers = []; }; GroupAction.prototype.destroy = function destroy() { this.deRegisterEvents(); this.dashboard = null; this.controller = null; this.selectionHandler = null; this.events = null; this._handlers = null; }; GroupAction.prototype.getNodes = function getNodes(idList) { return Utils.getNodes(this.controller, idList); }; GroupAction.prototype.isGroupEnabled = function isGroupEnabled(idList) { var hasSameParent = false; var nodes = this.getNodes(idList); if (nodes.length > 0) { var parent = null; // If all node have the same parents, then enable the group button. hasSameParent = !nodes.some(function (node) { if (parent && parent !== node.parentNode) { return true; } parent = node.parentNode; }); } return this.dashboard.getMode() === this.dashboard.MODES.EDIT && hasSameParent && nodes.length > 1 && !this._getTopGroup(nodes[0]); }; GroupAction.prototype.isUnGroupEnabled = function isUnGroupEnabled(idList) { var _this = this; var isGroups = false; var nodes = this.getNodes(idList); if (nodes.length > 0) { isGroups = !nodes.some(function (node) { return !$(node).hasClass('pagegroup') && !_this._getTopGroup(node); }); } return this.dashboard.getMode() === this.dashboard.MODES.EDIT && isGroups; }; GroupAction.prototype.getContentActionList = function getContentActionList(idList) { var contentActionList = []; if (this.isGroupEnabled(idList)) { contentActionList.push({ name: 'group', label: stringResources.get('toolbarActionGroup'), icon: this._icons.getIcon('dashboard-group').id, type: 'Button', actions: { apply: this.groupContent.bind(this, idList) } }); } if (this.isUnGroupEnabled(idList)) { contentActionList.push({ name: 'ungroup', label: stringResources.get('toolbarActionUngroup'), icon: this._icons.getIcon('dashboard-ungroup').id, type: 'Button', actions: { apply: this.ungroupContent.bind(this, idList) } }); } return contentActionList; }; /** * Return the top group of a given node. * * @param node * @returns */ GroupAction.prototype._getTopGroup = function _getTopGroup(node) { var nTopGroup = null; var $groups = $(node).parents('.pagegroup'); if ($groups.length > 0) { nTopGroup = $groups[$groups.length - 1]; } return nTopGroup; }; /** * Called when the before selection event is triggered. Here we decide which element to select based on the following rules. * 1- No existing selection and we select an item inside a group, then we select the top group. * 2- A group is already selected and we select an item inside the same group, then we select the child item * 3- A group child is already selected and we select an item outside of the group, then the existing child item will be unselected and the top group will be selected. * * * @param payload */ GroupAction.prototype.beforeSelection = function beforeSelection(payload) { var selectedNode = payload.newSelection; var existingSelections = payload.currentSelection; var nSelectedGroup = this._getTopGroup(selectedNode); var selectionCount = existingSelections.length; if (selectionCount > 0 && this.handleMultipleSelection(existingSelections, nSelectedGroup, payload)) { return; } if (nSelectedGroup) { if (selectionCount === 1 && existingSelections[0] === nSelectedGroup && payload.eventType !== 'mousedown') { // selecting a child of the group. We don't select on mouse down to give a chance if there is a drag event. this.selectionHandler.deselectAll(); } else { payload.newSelection = nSelectedGroup; } } }; /** * Handles the selection when we already have nodes that are current selected using the following rules: * * - If the current selection is not part of the same group. We select the groups. * - If the current selection is also part of the same group. We allow the selection. * * @param existingSelections * @param nSelectedGroup * @param payload * @returns {Boolean} - indicate whether we handled the selection and we want to stop any further handling */ GroupAction.prototype.handleMultipleSelection = function handleMultipleSelection(existingSelections, nSelectedGroup, payload) { var nCurrentSelectionGroup = this._getTopGroup(existingSelections[0]); if (nCurrentSelectionGroup) { if (nCurrentSelectionGroup !== nSelectedGroup) { // the current selection is not part of the same group. We select the groups. this.selectionHandler.deselectAll(); this.selectionHandler.selectNode(nCurrentSelectionGroup); if (nSelectedGroup) { payload.newSelection = nSelectedGroup; } } // the current selection is also part of the same group. We allow the selection. return true; } return false; }; /** * Group a given set of node. This method will create a new div and move the element inside it while maintaining the position/orientation of all children * @param nodes * @param e * @return {*} the promise that indicate the operation is finished */ GroupAction.prototype.groupContent = function groupContent(idList, e) { var _this2 = this; var nodes = this.getNodes(idList); // Build the group options and update the layout model if (nodes.length <= 0) { return; } var nodesPositions = []; for (var i = 0, iLen = nodes.length; i < iLen; i++) { var n = nodes[i]; if (n._layout) { var position = Utils.position(n); var bounds = Utils.widgetSize(n); nodesPositions.push({ position: position, bounds: bounds }); } } // find the model where we are adding the group var parentLayout = nodes[0]._layout.parentLayout; var parentModel = parentLayout.model; // Calculate the size and position of the group var rect = this.calculateGroupRect(nodes); var spec = { type: 'group', style: { top: rect.top + 'px', left: rect.left + 'px', width: rect.width + 'px', height: rect.height + 'px' } }; var content = { containerId: parentModel.id, spec: spec }; var canvasApi = this.dashboard.getCanvas(); var transactionApi = this.dashboard.getFeature('Transaction'); var transactionToken = transactionApi.startTransaction(); return canvasApi.addContent(content, transactionToken).then(function (groupContentApi) { var containerId = groupContentApi.getId(); // TODO - the selection API must be revisited so that we don't have to wait for the view // should use canvasApi.selectContent([containerId]); here return _this2.controller.layoutController.layoutReady(containerId).then(function (view) { var contentIds = nodes.map(function (node) { return node._layout.model.id; }); var contentList = canvasApi.moveContent(containerId, contentIds, transactionToken); for (var _i = 0, _iLen = nodes.length; _i < _iLen; _i++) { var _n = nodes[_i]; if (_n._layout) { var _position = nodesPositions[_i].position; var _bounds = nodesPositions[_i].bounds; var styles = { left: _position.left - rect.left + 'px', top: _position.top - rect.top + 'px', width: _bounds.width + 'px', height: _bounds.height + 'px' }; PxPercentUtil.changePixelPropertiesToPercent(styles, rect); contentList[_i].setPropertyValue('left', styles.left, transactionToken); contentList[_i].setPropertyValue('top', styles.top, transactionToken); contentList[_i].setPropertyValue('width', styles.width, transactionToken); contentList[_i].setPropertyValue('height', styles.height, transactionToken); } } transactionApi.endTransaction(transactionToken); // Select the group after it is created _this2.selectionHandler.deselectAll(); // pass the flag isTouch to properly render the resize edges for touch device. view && _this2.selectionHandler.selectNode(view.domNode, { 'isTouch': e ? e.type === 'touchstart' : false }); }); }); }; /** * Ungroup a set of groups. This method will remove all the children of a group and move it to the parent of the group and delete the group div * We maintain the position and orientation of elements that are being ungrouped. * @param e */ GroupAction.prototype.ungroupContent = function ungroupContent(idList, e, undoRedoTransactionId) { var selectedNodes = this.getNodes(idList); var transactionApi = this.dashboard.getFeature('Transaction'); var transactionToken = void 0; // We use the undo/redo transaction id to group actions like ungrouping an item then resizing the group to fit the new content if (!undoRedoTransactionId) { transactionToken = transactionApi.startTransaction(); } else { transactionToken = transactionApi.startTransactionById(undoRedoTransactionId); } this.selectionHandler.deselectAll(); var nodesToSelect = []; if ($(selectedNodes[0]).hasClass('pagegroup')) { // if we selected a group, we will ungroup the entire group. nodesToSelect = this.ungroupWholeGroups(selectedNodes, transactionToken); } else { this.ungroupIndividualNodes(selectedNodes, transactionToken); nodesToSelect = selectedNodes; } transactionApi.endTransaction(transactionToken); // select the nodes var i, n; for (i = nodesToSelect.length - 1; i >= 0; i--) { n = nodesToSelect[i]; this.selectionHandler.selectNode(n, { 'isTouch': e ? e.type === 'touchstart' : false }); } return nodesToSelect; }; /** * Helper function used to get the operation needed to move a node outside of a group * This method calcualtes the new position taking into consideration the angle of the group * @param n - node to be moved * @param group - indicate where we want to move the node * @param angle - angle of the parent group */ GroupAction.prototype._calculateNewStyleForEachNode = function _calculateNewStyleForEachNode(n, group, angle) { var parentLayout = group._layout.parentLayout; var nodeAngle = Utils.getAngleDegree(n); var clientRect = n.getBoundingClientRect(); var coords = Utils.getRotatedCoordinates(parentLayout.domNode, clientRect.left, clientRect.top); var cs = window.getComputedStyle(n); var fullAngle = angle + nodeAngle; var bounds = Utils.widgetSize(n); return { height: bounds.height + 'px', width: bounds.width + 'px', top: coords.y - Utils.getStyleIntValue(cs.marginTop) + 'px', left: coords.x - Utils.getStyleIntValue(cs.marginLeft) + 'px', rotateAngle: fullAngle }; }; /** * Add the model operation required to ungroup the content of this group. * * @returns all the nodes inside the group */ GroupAction.prototype._ungroupGroupNode = function _ungroupGroupNode(groupNode, transactionToken) { // DOM operation var nodesToSelect = []; var nodes = Utils.getContentWithoutDecoration(groupNode); var node, angle; var newStyles = {}; for (var i = 0, iLen = nodes.length; i < iLen; i++) { node = nodes[i]; angle = Utils.getAngleDegree(groupNode); newStyles[node._layout.id] = this._calculateNewStyleForEachNode(node, groupNode, angle); nodesToSelect.push(node); } // contentApi() operation // 1. move all inside nodes to group's parent // 2. recover all the properties // 3. delete group var canvasApi = this.dashboard.getCanvas(); var groupApi = canvasApi.getContent(groupNode._layout.id); var contentList = canvasApi.moveContent(groupApi.getContainer().getId(), groupApi.getChildren().map(function (content) { return content.getId(); }), transactionToken); canvasApi.removeContent(groupApi.getId(), transactionToken); contentList.forEach(function (content) { var contentId = content.getId(); var propNameList = Object.keys(newStyles[contentId]); propNameList.forEach(function (propName) { content.setPropertyValue(propName, newStyles[contentId][propName], transactionToken); }); }); return nodesToSelect; }; /** * Ungroup a list of groupNodes * * @param groupNodes * @returns {Array} */ GroupAction.prototype.ungroupWholeGroups = function ungroupWholeGroups(groupNodes, transactionToken) { var nodesToSelect = []; // we are ungroup a whole group var i = void 0, iLen = void 0; for (i = 0, iLen = groupNodes.length; i < iLen; i++) { var group = groupNodes[i]; var ungroupedNodes = this._ungroupGroupNode(group, transactionToken); nodesToSelect = nodesToSelect.concat(ungroupedNodes); } return nodesToSelect; }; GroupAction.prototype.getNodeId = function getNodeId(modelId) { this.layoutController.modelIdToNodeId(modelId); }; /** * Add the model operations required to remove the provided node from its group * * @param node * @param isMoveToParent - indicate where we want to move the node to the parent group or outside of the top group */ GroupAction.prototype._ungroupSingleNode = function _ungroupSingleNode(groupNode, node, isMoveToParent, transactionToken) { var $nestedGroups = $(node).parents('.pagegroup'); var nestedGroupLength = $nestedGroups.length; var canvasApi = this.dashboard.getCanvas(); if (nestedGroupLength > 0) { //todo: question? if groupNode === $nestedGroups[0] ?? var topGroup = isMoveToParent ? groupNode : $nestedGroups[nestedGroupLength - 1]; var angle = Utils.radianToDegree(Utils.getAbsoluteAngleRadian(groupNode, topGroup)); var newStyles = this._calculateNewStyleForEachNode(node, topGroup, angle); var nodeContentApi = canvasApi.getContent(node._layout.id); var topGroupApi = canvasApi.getContent(topGroup._layout.id); canvasApi.moveContent(topGroupApi.getContainer().getId(), [node._layout.id], transactionToken); var propNameList = Object.keys(newStyles); propNameList.forEach(function (propName) { nodeContentApi.setPropertyValue(propName, newStyles[propName], transactionToken); }); } }; /** * Remove the provided @nodes from @groupNode * @param nodes * @param groupNode * * @returns nodesToSelect */ GroupAction.prototype._ungroupMultipleNodes = function _ungroupMultipleNodes(nodes, groupNode, transactionToken) { //these nodes share the same parent: groupNode var nodesToSelect = []; //step 1: move all nodes to top Level group. for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; this._ungroupSingleNode(groupNode, node, false, transactionToken); nodesToSelect.push(node); } //step2: checkout each node's var canvasApi = this.dashboard.getCanvas(); var groupContentApi = canvasApi.getContent(groupNode._layout.id); var groupChildren = groupContentApi.findContent(); if (groupChildren.length == 0) { //we should remove the group; canvasApi.removeContent(groupNode._layout.id, transactionToken); } else if (groupChildren.length == 1) { //we should remove the left one out to its parent, and then remove the group var nodeIdToBeRemoved = groupChildren[0].getId(); var nodeToBeRemoved = $(groupNode).find('#' + nodeIdToBeRemoved)[0]; this._ungroupSingleNode(groupNode, nodeToBeRemoved, true, transactionToken); canvasApi.removeContent(groupNode._layout.id, transactionToken); nodesToSelect.push(nodeToBeRemoved); } else { nodesToSelect.push(groupNode); } return nodesToSelect; }; /** * Ungroup a list of nodes inside groups * @param nodes * @returns {Array} */ GroupAction.prototype.ungroupIndividualNodes = function ungroupIndividualNodes(nodes, transactionToken) { var nodesToSelect = []; // group nodes based on the group they belong to so that we can remove them from each group var groupMap = {}; for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; var groupId = node.parentNode._layout.id; if (!groupMap[groupId]) { groupMap[groupId] = []; } groupMap[groupId].push(node); } // For every affected group, we remove the specified nodes. We scan the groups from the most inner one to the most outer one. var groups = this._getAffectedGroups(nodes); var nodesToBeRemoved = void 0, ungroupedNodes = void 0; for (var _i2 = 0; _i2 < groups.length; _i2++) { var group = groups[_i2]; nodesToBeRemoved = groupMap[group.id]; ungroupedNodes = this._ungroupMultipleNodes(nodesToBeRemoved ? nodesToBeRemoved : [], group, transactionToken); nodesToSelect = nodesToSelect.concat(ungroupedNodes); } // Recalculate the group box dimension to reflect new changes for (var _i3 = 0; _i3 < groups.length; _i3++) { if (groups[_i3]._layout) { this.recalculateGroupRectForLayout(groups[_i3]._layout, transactionToken.transactionId); } } return nodesToSelect; }; /** * Calculates the group dimensions based on the nodes being grouped * @param nBox * @param nodes */ GroupAction.prototype.calculateGroupRect = function calculateGroupRect(nodes) { var rect = {}; var topLeft = Utils.getMinMaxTopLeft(nodes, function (n) { var $n = $(n); var position = $n.position(); position.top += $(n).parent().scrollTop(); position.left += $(n).parent().scrollLeft(); var rect = n.getBoundingClientRect(); position.height = rect.height; position.width = rect.width; return position; }); rect.width = topLeft.maxLeft - topLeft.minLeft; rect.height = topLeft.maxTop - topLeft.minTop; rect.top = Math.round(topLeft.minTop); rect.left = Math.round(topLeft.minLeft); return rect; }; /** * Called when one of the children's dimension changes. This method resizes the group rectangle. * @param payload */ GroupAction.prototype.recalculateGroupRect = function recalculateGroupRect(payload) { var undoRedoTransactionId = payload.undoRedoTransactionId; var groups = this._getAffectedGroups(payload.currentSelection); for (var i = 0, iLen = groups.length; i < iLen; i++) { this.recalculateGroupRectForLayout(groups[i]._layout, undoRedoTransactionId); } }; /** * Called when items are deleted. * @param payload */ GroupAction.prototype.onBeforeDelete = function onBeforeDelete(payload) { var _this3 = this; var idList = payload.currentSelection; var nodes = this.getNodes(idList); var nodesToUngroup = []; var ungroupedNodes = []; for (var i = 0; i < nodes.length; i++) { if (nodes[i]._layout.parentLayout.model.type === 'group' || nodes[i]._layout.model.type === 'group') { nodesToUngroup.push(nodes[i]); } else { ungroupedNodes.push(nodes[i]); } } nodesToUngroup.forEach(function (node) { ungroupedNodes.push.apply(ungroupedNodes, _this3.ungroupContent([node.id], null, payload.undoRedoTransactionId)); }); return ungroupedNodes; }; /** * Get a sorted list from child to parent of all the groups surrounding the given nodes * * @param nodes * @returns {Array} */ GroupAction.prototype._getAffectedGroups = function _getAffectedGroups(nodes) { var aParentArrays = []; var i, iLen; for (i = 0, iLen = nodes.length; i < iLen; i++) { aParentArrays.push($(nodes[i]).parents('.pagegroup')); } var aParents = [], index = 0; var hasMore; do { hasMore = this._affectedGroupSortStep(aParentArrays, index, aParents); index++; } while (hasMore); return aParents; }; /** * Sorting function used by the _getAffectedGroups method. * * @param aParentArrays * @param index * @param aParents * @returns {Boolean} */ GroupAction.prototype._affectedGroupSortStep = function _affectedGroupSortStep(aParentArrays, index, aParents) { var hasMore = false; var i, iLen, a, g, j; for (i = 0, iLen = aParentArrays.length; i < iLen; i++) { a = aParentArrays[i]; if (index < a.length) { hasMore = true; g = a[index]; j = aParents.indexOf(g); if (j !== -1) { aParents.splice(j, 1); } aParents.push(g); } } return hasMore; }; /** * Called when one of the children's dimension changes. * This method resizes the group rectangle and re-positions the children of the group * @param payload */ GroupAction.prototype.recalculateGroupRectForLayout = function recalculateGroupRectForLayout(layout, undoRedoTransactionId) { var transactionApi = this.dashboard.getFeature('Transaction'); var transactionToken = transactionApi.startTransactionById(undoRedoTransactionId); var nBox = layout.domNode; var nodes = utils.getContentWithoutDecoration(nBox); var topLeft = utils.getMinMaxTopLeft(nodes, function (n) { var rect = utils.position(n); var $n = $(n); rect.height = $n.outerHeight(); rect.width = $n.outerWidth(); // convert percent style to pixel. $n.height(rect.height); $n.width(rect.width); $n.css('left', rect.left); $n.css('top', rect.top); return rect; }); // Get a reference point to restore to position if it shifts due to rotation var ref = utils.getCenter(nodes[0]); var rotatedRef1 = utils.getRotatedCoordinates(nBox.parentNode, ref.left, ref.top); var groupWidth = topLeft.maxLeft - topLeft.minLeft + 'px'; nBox.style.width = groupWidth; var groupHeight = topLeft.maxTop - topLeft.minTop + 'px'; nBox.style.height = groupHeight; var pos = utils.position(nBox); nBox.style.top = pos.top + topLeft.minTop + 'px'; nBox.style.left = pos.left + topLeft.minLeft + 'px'; // Update the size/postion of the group content var canvasApi = this.dashboard.getCanvas(); var groupRect = { height: nBox.clientHeight, width: nBox.clientWidth }; var i, iLen, n; for (i = 0, iLen = nodes.length; i < iLen; i++) { n = nodes[i]; pos = utils.position(n); n.style.top = pos.top - topLeft.minTop + 'px'; n.style.left = pos.left - topLeft.minLeft + 'px'; if (n._layout) { var style = { top: n.style.top, left: n.style.left, height: $(n).height() + 'px', width: $(n).width() + 'px' }; var content = canvasApi.getContent(n._layout.model.id); if (content) { PxPercentUtil.changePixelPropertiesToPercent(style, groupRect); for (var name in style) { content.setPropertyValue(name, style[name], transactionToken); } } } } ref = utils.getCenter(nodes[0]); var rotatedRef2 = utils.getRotatedCoordinates(nBox.parentNode, ref.left, ref.top); pos = utils.position(nBox); var groupTopValue = pos.top - rotatedRef2.y + rotatedRef1.y + 'px'; nBox.style.top = groupTopValue; var groupLeftValue = pos.left - rotatedRef2.x + rotatedRef1.x + 'px'; nBox.style.left = groupLeftValue; // Update the size/position of the group var group = canvasApi.getContent(layout.model.id); group.setPropertyValue('top', groupTopValue, transactionToken); group.setPropertyValue('left', groupLeftValue, transactionToken); group.setPropertyValue('height', groupHeight, transactionToken); group.setPropertyValue('width', groupWidth, transactionToken); transactionApi.endTransaction(transactionToken); }; return GroupAction; }(); return GroupAction; }); //# sourceMappingURL=GroupAction.js.map