PromucFlow_constructor/app/client/src/sagas/WidgetOperationUtils.ts
Pawan Kumar fa4e42f4c6
Fix: App crash on list widget after copy/paste (#5605)
* fix list widget in list widget bug when pasting

* remove console.log

* add test

Co-authored-by: root <root@DESKTOP-9GENCK0.localdomain>
2021-07-06 12:24:34 +05:30

335 lines
10 KiB
TypeScript

import {
MAIN_CONTAINER_WIDGET_ID,
WidgetTypes,
} from "constants/WidgetConstants";
import { cloneDeep, get, isString, filter, set } from "lodash";
import {
CanvasWidgetsReduxState,
FlattenedWidgetProps,
} from "reducers/entityReducers/canvasWidgetsReducer";
import { select } from "redux-saga/effects";
import { getDynamicBindings } from "utils/DynamicBindingUtils";
import { WidgetProps } from "widgets/BaseWidget";
import { getWidgetMetaProps } from "./selectors";
/**
* 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 } = getDynamicBindings(value);
const modifiedAction = jsSnippets.reduce(
(prev: string, next: string) => {
return prev + `${next}`;
},
"",
);
value = `{{${listWidget.widgetName}.listData.map((currentItem) => ${modifiedAction})}}`;
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;
};