* Cut copy paste first cut * removed different parent groups logic * mouseup on the outer canvas removes selections. * bug fix * remove unwanted dead code. * Adding tests * build fix * min height fixes * fixing specs. * fixing specs. * fix merge conflcits * fix border positioning * fix canvas widgets incorrect bouding box * fix bounding box position issue * fix bounding box position issue * fix * border issue fix * update test case * add colors in theme * use layers + use click capture on actions * add icon for grouping * fix overflow issue in contextmenu in containers * fix context menu display issue * update position of context menu * fix container box-shadow issue * fix container box-shadow issue * revert container box shadow * stop opening of property pane on shift clicking a widget * remove console.log * fix multiselect box issue * add container on copy * add analytics middleware * refactor paste widget saga * change flash element to accept array + revert refactor * add logic to create containers from selected widgets * update positions of grouped widgets * fix comments + remove console * update flashElementbyId to flashElementsById * remove analytics middleware + remove unecessary imports * add shorcut for grouping * fix position issue when pasting * allow grouping only when multi widgets are selected * fix ux issues with widget grouping * fix help text for grouping actions * filter out the modal widget when calculting next row * fix delete issue when grouping * persist positin when grouping if there is no collision * fix typo for new position * changes for review comments * changes for review comments * fix position issue when pasting * fix new container position issue * move utils function to utils * fix import issue * fix the composite widget grouping issue * fix table name bug * remove repeated code * move copied groups existence check; * fix copied group check Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com> Co-authored-by: root <root@DESKTOP-9GENCK0.localdomain> Co-authored-by: Pawan Kumar <pawankumar@Pawans-MacBook-Pro.local>
782 lines
22 KiB
TypeScript
782 lines
22 KiB
TypeScript
import {
|
|
getFocusedWidget,
|
|
getSelectedWidget,
|
|
getWidgetMetaProps,
|
|
getWidgets,
|
|
} from "./selectors";
|
|
import _, { isString } from "lodash";
|
|
import {
|
|
GridDefaults,
|
|
MAIN_CONTAINER_WIDGET_ID,
|
|
RenderModes,
|
|
WidgetType,
|
|
WidgetTypes,
|
|
} from "constants/WidgetConstants";
|
|
import { all, call } from "redux-saga/effects";
|
|
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
|
import { select } from "redux-saga/effects";
|
|
import { getCopiedWidgets } from "utils/storage";
|
|
import { WidgetProps } from "widgets/BaseWidget";
|
|
import { getSelectedWidgets } from "selectors/ui";
|
|
import { generateReactKey } from "utils/generators";
|
|
import {
|
|
CanvasWidgetsReduxState,
|
|
FlattenedWidgetProps,
|
|
} from "reducers/entityReducers/canvasWidgetsReducer";
|
|
import { getDataTree } from "selectors/dataTreeSelectors";
|
|
import {
|
|
getDynamicBindings,
|
|
combineDynamicBindings,
|
|
} from "utils/DynamicBindingUtils";
|
|
import WidgetConfigResponse from "mockResponses/WidgetConfigResponse";
|
|
import { getNextEntityName } from "utils/AppsmithUtils";
|
|
|
|
export interface CopiedWidgetGroup {
|
|
widgetId: string;
|
|
parentId: string;
|
|
list: WidgetProps[];
|
|
}
|
|
|
|
/**
|
|
* checks if triggerpaths contains property path passed
|
|
*
|
|
* @param isTriggerProperty
|
|
* @param propertyPath
|
|
* @param triggerPaths
|
|
* @returns
|
|
*/
|
|
export const doesTriggerPathsContainPropertyPath = (
|
|
isTriggerProperty: boolean,
|
|
propertyPath: string,
|
|
triggerPaths?: string[],
|
|
) => {
|
|
if (!isTriggerProperty) {
|
|
if (
|
|
triggerPaths &&
|
|
triggerPaths.length &&
|
|
triggerPaths.includes(propertyPath)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return isTriggerProperty;
|
|
};
|
|
|
|
/**
|
|
*
|
|
* check if copied widget is being pasted in list widget,
|
|
* if yes, change all keys in template of list widget and
|
|
* update dynamicBindingPathList of ListWidget
|
|
*
|
|
* updates in list widget :
|
|
* 1. `dynamicBindingPathList`
|
|
* 2. `template`
|
|
*
|
|
* @param widget
|
|
* @param widgets
|
|
*/
|
|
export const handleIfParentIsListWidgetWhilePasting = (
|
|
widget: FlattenedWidgetProps,
|
|
widgets: { [widgetId: string]: FlattenedWidgetProps },
|
|
): { [widgetId: string]: FlattenedWidgetProps } => {
|
|
let root = _.get(widgets, `${widget.parentId}`);
|
|
|
|
while (root && root.parentId && root.widgetId !== MAIN_CONTAINER_WIDGET_ID) {
|
|
if (root.type === WidgetTypes.LIST_WIDGET) {
|
|
const listWidget = root;
|
|
const currentWidget = _.cloneDeep(widget);
|
|
let template = _.get(listWidget, "template", {});
|
|
const dynamicBindingPathList: any[] = _.get(
|
|
listWidget,
|
|
"dynamicBindingPathList",
|
|
[],
|
|
).slice();
|
|
|
|
// iterating over each keys of the new createdWidget checking if value contains currentItem
|
|
const keys = Object.keys(currentWidget);
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
const key = keys[i];
|
|
let value = currentWidget[key];
|
|
|
|
if (isString(value) && value.indexOf("currentItem") > -1) {
|
|
const { jsSnippets, stringSegments } = getDynamicBindings(value);
|
|
|
|
const js = combineDynamicBindings(jsSnippets, stringSegments);
|
|
|
|
value = `{{${listWidget.widgetName}.listData.map((currentItem) => ${js})}}`;
|
|
|
|
currentWidget[key] = value;
|
|
|
|
dynamicBindingPathList.push({
|
|
key: `template.${currentWidget.widgetName}.${key}`,
|
|
});
|
|
}
|
|
}
|
|
|
|
template = {
|
|
...template,
|
|
[currentWidget.widgetName]: currentWidget,
|
|
};
|
|
|
|
// now we have updated `dynamicBindingPathList` and updatedTemplate
|
|
// we need to update it the list widget
|
|
widgets[listWidget.widgetId] = {
|
|
...listWidget,
|
|
template,
|
|
dynamicBindingPathList,
|
|
};
|
|
}
|
|
|
|
root = widgets[root.parentId];
|
|
}
|
|
|
|
return widgets;
|
|
};
|
|
|
|
/**
|
|
* this saga handles special cases when pasting the widget
|
|
*
|
|
* for e.g - when the list widget is being copied, we want to update template of list widget
|
|
* with new widgets name
|
|
*
|
|
* @param widget
|
|
* @param widgets
|
|
* @param widgetNameMap
|
|
* @param newWidgetList
|
|
* @returns
|
|
*/
|
|
export const handleSpecificCasesWhilePasting = (
|
|
widget: FlattenedWidgetProps,
|
|
widgets: { [widgetId: string]: FlattenedWidgetProps },
|
|
widgetNameMap: Record<string, string>,
|
|
newWidgetList: FlattenedWidgetProps[],
|
|
) => {
|
|
// this is the case when whole list widget is copied and pasted
|
|
if (widget.type === WidgetTypes.LIST_WIDGET) {
|
|
Object.keys(widget.template).map((widgetName) => {
|
|
const oldWidgetName = widgetName;
|
|
const newWidgetName = widgetNameMap[oldWidgetName];
|
|
|
|
const newWidget = newWidgetList.find(
|
|
(w: any) => w.widgetName === newWidgetName,
|
|
);
|
|
|
|
if (newWidget) {
|
|
newWidget.widgetName = newWidgetName;
|
|
|
|
if (widgetName === oldWidgetName) {
|
|
widget.template[newWidgetName] = {
|
|
...widget.template[oldWidgetName],
|
|
widgetId: newWidget.widgetId,
|
|
widgetName: newWidget.widgetName,
|
|
};
|
|
|
|
delete widget.template[oldWidgetName];
|
|
}
|
|
}
|
|
|
|
// updating dynamicBindingPath in copied widget if the copied widge thas reference to oldWidgetNames
|
|
widget.dynamicBindingPathList = (widget.dynamicBindingPathList || []).map(
|
|
(path: any) => {
|
|
if (path.key.startsWith(`template.${oldWidgetName}`)) {
|
|
return {
|
|
key: path.key.replace(
|
|
`template.${oldWidgetName}`,
|
|
`template.${newWidgetName}`,
|
|
),
|
|
};
|
|
}
|
|
|
|
return path;
|
|
},
|
|
);
|
|
});
|
|
|
|
widgets[widget.widgetId] = widget;
|
|
} else if (widget.type === WidgetTypes.MODAL_WIDGET) {
|
|
// if Modal is being coppied handle all onClose action rename
|
|
const oldWidgetName = Object.keys(widgetNameMap).find(
|
|
(key) => widgetNameMap[key] === widget.widgetName,
|
|
);
|
|
// get all the button, icon widgets
|
|
const copiedBtnIcnWidgets = _.filter(
|
|
newWidgetList,
|
|
(copyWidget) =>
|
|
copyWidget.type === "BUTTON_WIDGET" ||
|
|
copyWidget.type === "ICON_WIDGET",
|
|
);
|
|
// replace oldName with new one if any of this widget have onClick action for old modal
|
|
copiedBtnIcnWidgets.map((copyWidget) => {
|
|
if (copyWidget.onClick) {
|
|
const newOnClick = widgets[copyWidget.widgetId].onClick.replace(
|
|
oldWidgetName,
|
|
widget.widgetName,
|
|
);
|
|
_.set(widgets[copyWidget.widgetId], "onClick", newOnClick);
|
|
}
|
|
});
|
|
}
|
|
|
|
widgets = handleIfParentIsListWidgetWhilePasting(widget, widgets);
|
|
|
|
return widgets;
|
|
};
|
|
|
|
export function getWidgetChildren(
|
|
canvasWidgets: CanvasWidgetsReduxState,
|
|
widgetId: string,
|
|
): any {
|
|
const childrenIds: string[] = [];
|
|
const widget = _.get(canvasWidgets, widgetId);
|
|
// When a form widget tries to resetChildrenMetaProperties
|
|
// But one or more of its container like children
|
|
// have just been deleted, widget can be undefined
|
|
if (widget === undefined) {
|
|
return [];
|
|
}
|
|
const { children = [] } = widget;
|
|
if (children && children.length) {
|
|
for (const childIndex in children) {
|
|
if (children.hasOwnProperty(childIndex)) {
|
|
const child = children[childIndex];
|
|
childrenIds.push(child);
|
|
const grandChildren = getWidgetChildren(canvasWidgets, child);
|
|
if (grandChildren.length) {
|
|
childrenIds.push(...grandChildren);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return childrenIds;
|
|
}
|
|
|
|
export const getParentWidgetIdForPasting = function*(
|
|
widgets: CanvasWidgetsReduxState,
|
|
selectedWidget: FlattenedWidgetProps | undefined,
|
|
) {
|
|
let newWidgetParentId = MAIN_CONTAINER_WIDGET_ID;
|
|
let parentWidget = widgets[MAIN_CONTAINER_WIDGET_ID];
|
|
|
|
// If the selected widget is not the main container
|
|
if (selectedWidget && selectedWidget.widgetId !== MAIN_CONTAINER_WIDGET_ID) {
|
|
// Select the parent of the selected widget if parent is not
|
|
// the main container
|
|
if (
|
|
selectedWidget &&
|
|
selectedWidget.parentId &&
|
|
selectedWidget.parentId !== MAIN_CONTAINER_WIDGET_ID &&
|
|
widgets[selectedWidget.parentId]
|
|
) {
|
|
const children = widgets[selectedWidget.parentId].children || [];
|
|
if (children.length > 0) {
|
|
parentWidget = widgets[selectedWidget.parentId];
|
|
newWidgetParentId = selectedWidget.parentId;
|
|
}
|
|
}
|
|
// Select the selected widget if the widget is container like ( excluding list widget )
|
|
if (
|
|
selectedWidget.children &&
|
|
selectedWidget.type !== WidgetTypes.LIST_WIDGET
|
|
) {
|
|
parentWidget = widgets[selectedWidget.widgetId];
|
|
}
|
|
}
|
|
|
|
// If the parent widget in which to paste the copied widget
|
|
// is not the main container and is not a canvas widget
|
|
if (
|
|
parentWidget.widgetId !== MAIN_CONTAINER_WIDGET_ID &&
|
|
parentWidget.type !== WidgetTypes.CANVAS_WIDGET
|
|
) {
|
|
let childWidget;
|
|
// If the widget in which to paste the new widget is NOT
|
|
// a tabs widget
|
|
if (parentWidget.type !== WidgetTypes.TABS_WIDGET) {
|
|
// The child will be a CANVAS_WIDGET, as we've established
|
|
// this parent widget to be a container like widget
|
|
// Which always has its first child as a canvas widget
|
|
childWidget = parentWidget.children && widgets[parentWidget.children[0]];
|
|
} else {
|
|
// If the widget in which to paste the new widget is a tabs widget
|
|
// Find the currently selected tab canvas widget
|
|
const { selectedTabWidgetId } = yield select(
|
|
getWidgetMetaProps,
|
|
parentWidget.widgetId,
|
|
);
|
|
if (selectedTabWidgetId) childWidget = widgets[selectedTabWidgetId];
|
|
}
|
|
// If the finally selected parent in which to paste the widget
|
|
// is a CANVAS_WIDGET, use its widgetId as the new widget's parent Id
|
|
if (childWidget && childWidget.type === WidgetTypes.CANVAS_WIDGET) {
|
|
newWidgetParentId = childWidget.widgetId;
|
|
}
|
|
}
|
|
return newWidgetParentId;
|
|
};
|
|
|
|
export const checkIfPastingIntoListWidget = function(
|
|
canvasWidgets: CanvasWidgetsReduxState,
|
|
selectedWidget: FlattenedWidgetProps | undefined,
|
|
copiedWidgets: {
|
|
widgetId: string;
|
|
parentId: string;
|
|
list: WidgetProps[];
|
|
}[],
|
|
) {
|
|
// when list widget is selected, if the user is pasting, we want it to be pasted in the template
|
|
// which is first children of list widget
|
|
if (
|
|
selectedWidget &&
|
|
selectedWidget.children &&
|
|
selectedWidget?.type === WidgetTypes.LIST_WIDGET
|
|
) {
|
|
const childrenIds: string[] = getWidgetChildren(
|
|
canvasWidgets,
|
|
selectedWidget.children[0],
|
|
);
|
|
const firstChildId = childrenIds[0];
|
|
|
|
// if any copiedWidget is a list widget, we will paste into the parent of list widget
|
|
for (let i = 0; i < copiedWidgets.length; i++) {
|
|
const copiedWidgetId = copiedWidgets[i].widgetId;
|
|
const copiedWidget = canvasWidgets[copiedWidgetId];
|
|
|
|
if (copiedWidget?.type === WidgetTypes.LIST_WIDGET) {
|
|
return selectedWidget;
|
|
}
|
|
}
|
|
|
|
return _.get(canvasWidgets, firstChildId);
|
|
}
|
|
return selectedWidget;
|
|
};
|
|
|
|
/**
|
|
* get top, left, right, bottom most widgets from copied groups when pasting
|
|
*
|
|
* @param copiedWidgetGroups
|
|
* @returns
|
|
*/
|
|
export const getBoundaryWidgetsFromCopiedGroups = function(
|
|
copiedWidgetGroups: CopiedWidgetGroup[],
|
|
) {
|
|
const topMostWidget = copiedWidgetGroups.sort(
|
|
(a, b) => a.list[0].topRow - b.list[0].topRow,
|
|
)[0].list[0];
|
|
const leftMostWidget = copiedWidgetGroups.sort(
|
|
(a, b) => a.list[0].leftColumn - b.list[0].leftColumn,
|
|
)[0].list[0];
|
|
const rightMostWidget = copiedWidgetGroups.sort(
|
|
(a, b) => b.list[0].rightColumn - a.list[0].rightColumn,
|
|
)[0].list[0];
|
|
const bottomMostWidget = copiedWidgetGroups.sort(
|
|
(a, b) => b.list[0].bottomRow - a.list[0].bottomRow,
|
|
)[0].list[0];
|
|
|
|
return {
|
|
topMostWidget,
|
|
leftMostWidget,
|
|
rightMostWidget,
|
|
bottomMostWidget,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* -------------------------------------------------------------------------------
|
|
* OPERATION = PASTING
|
|
* -------------------------------------------------------------------------------
|
|
*
|
|
* following are the functions are that used in pasting operation
|
|
*/
|
|
|
|
/**
|
|
* selects the selectedWidget.
|
|
* In case of LIST_WIDGET, it selects the list widget instead of selecting the
|
|
* container inside the list widget
|
|
*
|
|
* @param canvasWidgets
|
|
* @param copiedWidgetGroups
|
|
* @returns
|
|
*/
|
|
export const getSelectedWidgetWhenPasting = function*() {
|
|
const canvasWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
|
|
const copiedWidgetGroups: CopiedWidgetGroup[] = yield getCopiedWidgets();
|
|
|
|
let selectedWidget: FlattenedWidgetProps | undefined = yield select(
|
|
getSelectedWidget,
|
|
);
|
|
|
|
const focusedWidget: FlattenedWidgetProps | undefined = yield select(
|
|
getFocusedWidget,
|
|
);
|
|
|
|
selectedWidget = checkIfPastingIntoListWidget(
|
|
canvasWidgets,
|
|
selectedWidget || focusedWidget,
|
|
copiedWidgetGroups,
|
|
);
|
|
|
|
return selectedWidget;
|
|
};
|
|
|
|
/**
|
|
* group copied widgets into a container
|
|
*
|
|
* @param copiedWidgetGroups
|
|
* @param pastingIntoWidgetId
|
|
* @returns
|
|
*/
|
|
export const groupWidgetsIntoContainer = function*(
|
|
copiedWidgetGroups: CopiedWidgetGroup[],
|
|
pastingIntoWidgetId: string,
|
|
) {
|
|
const containerWidgetId = generateReactKey();
|
|
const evalTree: DataTree = yield select(getDataTree);
|
|
const canvasWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
|
|
const newContainerName = getNextWidgetName(
|
|
canvasWidgets,
|
|
WidgetTypes.CONTAINER_WIDGET,
|
|
evalTree,
|
|
);
|
|
const newCanvasName = getNextWidgetName(
|
|
canvasWidgets,
|
|
WidgetTypes.CANVAS_WIDGET,
|
|
evalTree,
|
|
);
|
|
const {
|
|
bottomMostWidget,
|
|
leftMostWidget,
|
|
rightMostWidget,
|
|
topMostWidget,
|
|
} = getBoundaryWidgetsFromCopiedGroups(copiedWidgetGroups);
|
|
|
|
const copiedWidgets = copiedWidgetGroups.map((copiedWidgetGroup) =>
|
|
copiedWidgetGroup.list.find(
|
|
(w) => w.widgetId === copiedWidgetGroup.widgetId,
|
|
),
|
|
);
|
|
const parentColumnSpace =
|
|
copiedWidgetGroups[0].list[0].parentColumnSpace || 1;
|
|
|
|
const boundary = {
|
|
top: _.minBy(copiedWidgets, (copiedWidget) => copiedWidget?.topRow),
|
|
left: _.minBy(copiedWidgets, (copiedWidget) => copiedWidget?.leftColumn),
|
|
bottom: _.maxBy(copiedWidgets, (copiedWidget) => copiedWidget?.bottomRow),
|
|
right: _.maxBy(copiedWidgets, (copiedWidget) => copiedWidget?.rightColumn),
|
|
};
|
|
|
|
const widthPerColumn =
|
|
((rightMostWidget.rightColumn - leftMostWidget.leftColumn) *
|
|
parentColumnSpace) /
|
|
GridDefaults.DEFAULT_GRID_COLUMNS;
|
|
const heightOfCanvas =
|
|
(bottomMostWidget.bottomRow - topMostWidget.topRow) * parentColumnSpace;
|
|
const widthOfCanvas =
|
|
(rightMostWidget.rightColumn - leftMostWidget.leftColumn) *
|
|
parentColumnSpace;
|
|
|
|
const newCanvasWidget: FlattenedWidgetProps = {
|
|
..._.omit(
|
|
_.get(
|
|
WidgetConfigResponse.config[WidgetTypes.CONTAINER_WIDGET],
|
|
"blueprint.view[0]",
|
|
),
|
|
["position"],
|
|
),
|
|
..._.get(
|
|
WidgetConfigResponse.config[WidgetTypes.CONTAINER_WIDGET],
|
|
"blueprint.view[0].props",
|
|
),
|
|
bottomRow: heightOfCanvas,
|
|
isLoading: false,
|
|
isVisible: true,
|
|
leftColumn: 0,
|
|
minHeight: heightOfCanvas,
|
|
parentColumnSpace: 1,
|
|
parentId: pastingIntoWidgetId,
|
|
parentRowSpace: 1,
|
|
rightColumn: widthOfCanvas,
|
|
topRow: 0,
|
|
renderMode: RenderModes.CANVAS,
|
|
version: 1,
|
|
widgetId: generateReactKey(),
|
|
widgetName: newCanvasName,
|
|
};
|
|
const newContainerWidget: FlattenedWidgetProps = {
|
|
..._.omit(WidgetConfigResponse.config[WidgetTypes.CONTAINER_WIDGET], [
|
|
"rows",
|
|
"columns",
|
|
"blueprint",
|
|
]),
|
|
parentId: pastingIntoWidgetId,
|
|
widgetName: newContainerName,
|
|
type: WidgetTypes.CONTAINER_WIDGET,
|
|
widgetId: containerWidgetId,
|
|
leftColumn: boundary.left?.leftColumn || 0,
|
|
topRow: boundary.top?.topRow || 0,
|
|
bottomRow: (boundary.bottom?.bottomRow || 0) + 2,
|
|
rightColumn: boundary.right?.rightColumn || 0,
|
|
tabId: "",
|
|
children: [newCanvasWidget.widgetId],
|
|
renderMode: RenderModes.CANVAS,
|
|
version: 1,
|
|
isLoading: false,
|
|
isVisible: true,
|
|
parentRowSpace: GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
|
parentColumnSpace: widthPerColumn,
|
|
};
|
|
newCanvasWidget.parentId = newContainerWidget.widgetId;
|
|
const percentageIncrease = parentColumnSpace / widthPerColumn;
|
|
|
|
const list = copiedWidgetGroups.map((copiedWidgetGroup) => {
|
|
return [
|
|
...copiedWidgetGroup.list.map((listItem) => {
|
|
if (listItem.widgetId === copiedWidgetGroup.widgetId) {
|
|
newCanvasWidget.children = _.get(newCanvasWidget, "children", []);
|
|
newCanvasWidget.children = [
|
|
...newCanvasWidget.children,
|
|
listItem.widgetId,
|
|
];
|
|
|
|
return {
|
|
...listItem,
|
|
leftColumn:
|
|
(listItem.leftColumn - leftMostWidget.leftColumn) *
|
|
percentageIncrease,
|
|
rightColumn:
|
|
(listItem.rightColumn - leftMostWidget.leftColumn) *
|
|
percentageIncrease,
|
|
topRow: listItem.topRow - topMostWidget.topRow,
|
|
bottomRow: listItem.bottomRow - topMostWidget.topRow,
|
|
parentId: newCanvasWidget.widgetId,
|
|
parentRowSpace: GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
|
parentColumnSpace: widthPerColumn,
|
|
};
|
|
}
|
|
|
|
return listItem;
|
|
}),
|
|
];
|
|
});
|
|
|
|
const flatList = _.flattenDeep(list);
|
|
|
|
return [
|
|
{
|
|
list: [newContainerWidget, newCanvasWidget, ...flatList],
|
|
widgetId: newContainerWidget.widgetId,
|
|
parentId: pastingIntoWidgetId,
|
|
},
|
|
];
|
|
};
|
|
|
|
/**
|
|
* create copiedWidgets objects from selected widgets
|
|
*
|
|
* @returns
|
|
*/
|
|
export const createSelectedWidgetsAsCopiedWidgets = function*() {
|
|
const canvasWidgets: {
|
|
[widgetId: string]: FlattenedWidgetProps;
|
|
} = yield select(getWidgets);
|
|
const selectedWidgetIDs: string[] = yield select(getSelectedWidgets);
|
|
const selectedWidgets = selectedWidgetIDs.map((each) => canvasWidgets[each]);
|
|
|
|
if (!selectedWidgets || !selectedWidgets.length) return;
|
|
|
|
const widgetListsToStore: {
|
|
widgetId: string;
|
|
parentId: string;
|
|
list: FlattenedWidgetProps[];
|
|
}[] = yield all(selectedWidgets.map((each) => call(createWidgetCopy, each)));
|
|
|
|
return widgetListsToStore;
|
|
};
|
|
|
|
/**
|
|
* return canvasWidgets without selectedWidgets and remove the selected widgets
|
|
* ids in the children of parent widget
|
|
*
|
|
* @return
|
|
*/
|
|
export const filterOutSelectedWidgets = function*(
|
|
parentId: string,
|
|
copiedWidgetGroups: CopiedWidgetGroup[],
|
|
) {
|
|
const canvasWidgets: CanvasWidgetsReduxState = yield _.cloneDeep(
|
|
select(getWidgets),
|
|
);
|
|
|
|
const selectedWidgetIDs: string[] = _.flattenDeep(
|
|
copiedWidgetGroups.map((copiedWidgetGroup) => {
|
|
return copiedWidgetGroup.list.map((widget) => widget.widgetId);
|
|
}),
|
|
);
|
|
|
|
const filteredWidgets: CanvasWidgetsReduxState = _.omit(
|
|
canvasWidgets,
|
|
selectedWidgetIDs,
|
|
);
|
|
|
|
return {
|
|
...filteredWidgets,
|
|
[parentId]: {
|
|
...filteredWidgets[parentId],
|
|
// removing the selected widgets ids in the children of parent widget
|
|
children: _.get(filteredWidgets[parentId], "children", []).filter(
|
|
(widgetId) => {
|
|
return !selectedWidgetIDs.includes(widgetId);
|
|
},
|
|
),
|
|
},
|
|
};
|
|
};
|
|
|
|
/**
|
|
* checks if selected widgets are colliding with other widgets or not
|
|
*
|
|
* @param widgets
|
|
* @param copiedWidgetGroups
|
|
* @returns
|
|
*/
|
|
export const isSelectedWidgetsColliding = function*(
|
|
widgets: CanvasWidgetsReduxState,
|
|
copiedWidgetGroups: CopiedWidgetGroup[],
|
|
pastingIntoWidgetId: string,
|
|
) {
|
|
if (!Array.isArray(copiedWidgetGroups)) return false;
|
|
|
|
const {
|
|
bottomMostWidget,
|
|
leftMostWidget,
|
|
rightMostWidget,
|
|
topMostWidget,
|
|
} = getBoundaryWidgetsFromCopiedGroups(copiedWidgetGroups);
|
|
|
|
const widgetsWithSameParent = _.omitBy(widgets, (widget) => {
|
|
return widget.parentId !== pastingIntoWidgetId;
|
|
});
|
|
|
|
const widgetsArray = Object.values(widgetsWithSameParent).filter(
|
|
(widget) =>
|
|
widget.parentId === pastingIntoWidgetId &&
|
|
widget.type !== WidgetTypes.MODAL_WIDGET,
|
|
);
|
|
|
|
let isColliding = false;
|
|
|
|
for (let i = 0; i < widgetsArray.length; i++) {
|
|
const widget = widgetsArray[i];
|
|
|
|
if (
|
|
widget.bottomRow + 2 < topMostWidget.topRow ||
|
|
widget.topRow > bottomMostWidget.bottomRow
|
|
) {
|
|
isColliding = false;
|
|
} else if (
|
|
widget.rightColumn < leftMostWidget.leftColumn ||
|
|
widget.leftColumn > rightMostWidget.rightColumn
|
|
) {
|
|
isColliding = false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return isColliding;
|
|
};
|
|
|
|
/**
|
|
* get next widget name to be used
|
|
*
|
|
* @param widgets
|
|
* @param type
|
|
* @param evalTree
|
|
* @param options
|
|
* @returns
|
|
*/
|
|
export function getNextWidgetName(
|
|
widgets: CanvasWidgetsReduxState,
|
|
type: WidgetType,
|
|
evalTree: DataTree,
|
|
options?: Record<string, unknown>,
|
|
) {
|
|
// Compute the new widget's name
|
|
const defaultConfig: any = WidgetConfigResponse.config[type];
|
|
const widgetNames = Object.keys(widgets).map((w) => widgets[w].widgetName);
|
|
const entityNames = Object.keys(evalTree);
|
|
let prefix = defaultConfig.widgetName;
|
|
if (options && options.prefix) {
|
|
prefix = `${options.prefix}${
|
|
widgetNames.indexOf(options.prefix as string) > -1 ? "Copy" : ""
|
|
}`;
|
|
}
|
|
|
|
return getNextEntityName(
|
|
prefix,
|
|
[...widgetNames, ...entityNames],
|
|
options?.startWithoutIndex as boolean,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* creates widget copied groups
|
|
*
|
|
* @param widget
|
|
* @returns
|
|
*/
|
|
export function* createWidgetCopy(widget: FlattenedWidgetProps) {
|
|
const allWidgets: { [widgetId: string]: FlattenedWidgetProps } = yield select(
|
|
getWidgets,
|
|
);
|
|
const widgetsToStore = getAllWidgetsInTree(widget.widgetId, allWidgets);
|
|
return {
|
|
widgetId: widget.widgetId,
|
|
list: widgetsToStore,
|
|
parentId: widget.parentId,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* get all widgets in tree
|
|
*
|
|
* @param widgetId
|
|
* @param canvasWidgets
|
|
* @returns
|
|
*/
|
|
export const getAllWidgetsInTree = (
|
|
widgetId: string,
|
|
canvasWidgets: CanvasWidgetsReduxState,
|
|
) => {
|
|
const widget = canvasWidgets[widgetId];
|
|
const widgetList = [widget];
|
|
if (widget && widget.children) {
|
|
widget.children
|
|
.filter(Boolean)
|
|
.forEach((childWidgetId: string) =>
|
|
widgetList.push(...getAllWidgetsInTree(childWidgetId, canvasWidgets)),
|
|
);
|
|
}
|
|
return widgetList;
|
|
};
|
|
|
|
export const getParentBottomRowAfterAddingWidget = (
|
|
stateParent: FlattenedWidgetProps,
|
|
newWidget: FlattenedWidgetProps,
|
|
) => {
|
|
const parentRowSpace =
|
|
newWidget.parentRowSpace || GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
|
const updateBottomRow =
|
|
stateParent.type === WidgetTypes.CANVAS_WIDGET &&
|
|
newWidget.bottomRow * parentRowSpace > stateParent.bottomRow;
|
|
return updateBottomRow
|
|
? Math.max(
|
|
(newWidget.bottomRow + GridDefaults.CANVAS_EXTENSION_OFFSET) *
|
|
parentRowSpace,
|
|
stateParent.bottomRow,
|
|
)
|
|
: stateParent.bottomRow;
|
|
};
|