PromucFlow_constructor/app/client/src/utils/widgetRenderUtils.tsx
Preet Sidhu c396fc5499
feat: Add copy paste functionality for Anvil. (#30217)
## Description

1. Copy paste functionality for Anvil.
2. Handle automatic creation of sections and zones based on different
scenarios.
3. Handle space distribution on paste.

#### Type of change
- New feature (non-breaking change which adds functionality)

## Testing

#### How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Also
list any relevant details for your test configuration.
> Delete anything that is not relevant
- [x] Manual
- [ ] JUnit
- [ ] Jest
- [ ] Cypress

## Checklist:
#### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-)
have been covered
- [ ] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [ ] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [ ] Cypress test cases have been added and approved by SDET/manual QA
- [ ] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a new paste functionality for widgets within the Anvil
layout system.

- **Improvements**
  - Refined widget movement logic to accommodate different canvas types.
- Expanded widget pasting capabilities with additional checks and
operations.

- **Bug Fixes**
- Addressed potential issues with dragging blocks by ensuring existence
checks before mapping.

- **Refactor**
- Streamlined sagas for widget operations to better align with the Anvil
layout system.
- Consolidated widget position utility functions for more efficient
layout management.

- **User Interface**
- Implemented user-friendly error messaging for paste operations that
fail.

- **Documentation**
- Updated messages and constants to support new paste functionality and
error handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com>
2024-01-24 21:55:08 +05:30

191 lines
5.6 KiB
TypeScript

import type {
CanvasWidgetsReduxState,
FlattenedWidgetProps,
} from "reducers/entityReducers/canvasWidgetsReducer";
import type {
WidgetEntity,
WidgetEntityConfig,
} from "@appsmith/entities/DataTree/types";
import type { ConfigTree, DataTree } from "entities/DataTree/dataTreeTypes";
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
import { pick } from "lodash";
import {
WIDGET_DSL_STRUCTURE_PROPS,
WIDGET_STATIC_PROPS,
} from "constants/WidgetConstants";
import WidgetFactory from "../WidgetProvider/factory";
import type { WidgetProps } from "widgets/BaseWidget";
import type { LoadingEntitiesState } from "reducers/evaluationReducers/loadingEntitiesReducer";
import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer";
import type { WidgetError } from "widgets/BaseWidget";
import { get } from "lodash";
import type { DataTreeError } from "utils/DynamicBindingUtils";
import { EVAL_ERROR_PATH } from "utils/DynamicBindingUtils";
export const createCanvasWidget = (
canvasWidget: FlattenedWidgetProps,
evaluatedWidget: WidgetEntity,
evaluatedWidgetConfig: WidgetEntityConfig,
specificChildProps?: string[],
) => {
/**
* WIDGET_DSL_STRUCTURE_PROPS is required for Building the List widget meta widgets
* requiresFlatWidgetChildren and hasMetaWidgets are the keys required.
*/
const widgetStaticProps = pick(canvasWidget, [
...Object.keys({ ...WIDGET_STATIC_PROPS, ...WIDGET_DSL_STRUCTURE_PROPS }),
...(canvasWidget.additionalStaticProps || []),
]);
//Pick required only contents for specific widgets
const evaluatedStaticProps = specificChildProps
? pick(evaluatedWidget, specificChildProps)
: evaluatedWidget;
const widgetProps = {
...evaluatedStaticProps,
...evaluatedWidgetConfig,
...widgetStaticProps,
} as any;
widgetProps.errors = widgetErrorsFromStaticProps(evaluatedStaticProps);
return widgetProps;
};
function widgetErrorsFromStaticProps(props: Record<string, unknown>) {
/**
* Evaluation Error Map
* {
widgetPropertyName : DataTreeError[]
}
*/
const evaluationErrorMap = get(props, EVAL_ERROR_PATH, {}) as Record<
string,
DataTreeError[]
>;
const widgetErrors: WidgetError[] = [];
Object.keys(evaluationErrorMap).forEach((propertyPath) => {
const propertyErrors = evaluationErrorMap[propertyPath];
propertyErrors.forEach((evalError) => {
const widgetError: WidgetError = {
name: evalError.errorMessage.name,
message: evalError.errorMessage.message,
stack: evalError.raw,
type: "property",
path: propertyPath,
};
widgetErrors.push(widgetError);
});
});
return widgetErrors;
}
const WidgetTypes = WidgetFactory?.widgetTypes;
export const createLoadingWidget = (
canvasWidget: FlattenedWidgetProps,
): WidgetEntity => {
const widgetStaticProps = pick(
canvasWidget,
Object.keys(WIDGET_STATIC_PROPS),
) as WidgetProps;
return {
...widgetStaticProps,
type:
// We don't need to set skeleton type for modals
// since modals are not displayed when the app is loaded
canvasWidget?.type !== "MODAL_WIDGET"
? WidgetTypes.SKELETON_WIDGET
: canvasWidget?.type,
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
bindingPaths: {},
reactivePaths: {},
triggerPaths: {},
validationPaths: {},
logBlackList: {},
isLoading: true,
propertyOverrideDependency: {},
overridingPropertyPaths: {},
privateWidgets: {},
meta: {},
};
};
/**
* Method to build a child widget tree
* This method is used to build the child widgets array for widgets like Form, or List widget,
* That need to know the state of its child or grandChild to derive properties
* This can be replaced with deived properties of the individual widgets
*
* @param canvasWidgets
* @param evaluatedDataTree
* @param loadingEntities
* @param widgetId
* @param requiredWidgetProps
* @returns
*/
export function buildChildWidgetTree(
canvasWidgets: CanvasWidgetsReduxState,
metaWidgets: MetaWidgetsReduxState,
evaluatedDataTree: DataTree,
loadingEntities: LoadingEntitiesState,
configTree: ConfigTree,
widgetId: string,
requiredWidgetProps?: string[],
) {
const parentWidget = canvasWidgets[widgetId] || metaWidgets[widgetId];
// specificChildProps are the only properties required by the parent to derive it's properties
const specificChildProps =
requiredWidgetProps || getWidgetSpecificChildProps(parentWidget.type);
if (parentWidget.children) {
return parentWidget.children.map((childWidgetId) => {
const childWidget =
canvasWidgets[childWidgetId] || metaWidgets[childWidgetId];
const evaluatedWidget = evaluatedDataTree[
childWidget.widgetName
] as WidgetEntity;
const evaluatedWidgetConfig = configTree[
childWidget.widgetName
] as WidgetEntityConfig;
const widget = evaluatedWidget
? createCanvasWidget(
childWidget,
evaluatedWidget,
evaluatedWidgetConfig,
specificChildProps,
)
: createLoadingWidget(childWidget);
widget.isLoading = loadingEntities.has(childWidget.widgetName);
if (widget?.children?.length > 0) {
widget.children = buildChildWidgetTree(
canvasWidgets,
metaWidgets,
evaluatedDataTree,
loadingEntities,
configTree,
childWidgetId,
specificChildProps,
);
}
return widget;
});
}
return [];
}
function getWidgetSpecificChildProps(type: string) {
if (type === "FORM_WIDGET") {
return ["value", "isDirty", "isValid", "isLoading", "children"];
}
}