PromucFlow_constructor/app/client/src/sagas/WidgetBlueprintSagas.ts
Aswath K 34b7d93a7c
fix: List widget issues in Auto Layout (#23252)
## Description
- Removes overflow property from Text widgets within List
- Fix issue where the image widget disappears from List widget when
resizing
- Fix issue where last widget crops when scrolling enabled in List
widget container

#### PR fixes following issue(s)
Fixes #23181
Fixes #22833
Fixes #23178

#### Type of change
- Bug fix (non-breaking change which fixes an issue)

## 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
- [ ] Manual
- [ ] Jest
- [ ] Cypress
>
>
#### Test Plan
> Add Testsmith test cases links that relate to this PR
>
>
#### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
>
>
>
## Checklist:
#### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] 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
- [x] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Test-plan-implementation#speedbreaker-features-to-consider-for-every-change)
have been covered
- [ ] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans/_edit#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

---------

Co-authored-by: Preet Sidhu <preetsidhu.bits@gmail.com>
Co-authored-by: rahulramesha <rahul@appsmith.com>
Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com>
2023-05-29 10:06:19 +05:30

280 lines
8.2 KiB
TypeScript

import type { WidgetBlueprint } from "reducers/entityReducers/widgetConfigReducer";
import type { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
import type { WidgetProps } from "widgets/BaseWidget";
import { generateReactKey } from "utils/generators";
import { call, select } from "redux-saga/effects";
import { get } from "lodash";
import WidgetFactory from "utils/WidgetFactory";
import type { WidgetType } from "constants/WidgetConstants";
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
import { BlueprintOperationTypes } from "widgets/constants";
import * as log from "loglevel";
import { toast } from "design-system";
import { getIsAutoLayout } from "selectors/canvasSelectors";
function buildView(view: WidgetBlueprint["view"], widgetId: string) {
const children = [];
if (view) {
for (const template of view) {
//TODO(abhinav): Can we keep rows and size mandatory?
try {
children.push({
widgetId,
type: template.type,
leftColumn: template.position.left || 0,
topRow: template.position.top || 0,
columns: template.size && template.size.cols,
rows: template.size && template.size.rows,
newWidgetId: generateReactKey(),
props: template.props,
});
} catch (e) {
log.error(e);
}
}
}
return children;
}
export function* buildWidgetBlueprint(
blueprint: WidgetBlueprint,
widgetId: string,
) {
const widgetProps: Record<string, unknown> = yield call(
buildView,
blueprint.view,
widgetId,
);
return widgetProps;
}
export type UpdatePropertyArgs = {
widgetId: string;
propertyName: string;
propertyValue: any;
};
export type BlueprintOperationAddActionFn = () => void;
export type BlueprintOperationModifyPropsFn = (
widget: WidgetProps & { children?: WidgetProps[] },
widgets: { [widgetId: string]: FlattenedWidgetProps },
parent?: WidgetProps,
isAutoLayout?: boolean,
) => UpdatePropertyArgs[] | undefined;
export interface ChildOperationFnResponse {
widgets: Record<string, FlattenedWidgetProps>;
message?: string;
}
export type BlueprintOperationChildOperationsFn = (
widgets: { [widgetId: string]: FlattenedWidgetProps },
widgetId: string,
parentId: string,
widgetPropertyMaps: {
defaultPropertyMap: Record<string, string>;
},
isAutoLayout?: boolean,
) => ChildOperationFnResponse;
export type BlueprintBeforeOperationsFn = (
widgets: { [widgetId: string]: FlattenedWidgetProps },
widgetId: string,
parentId: string,
isAutoLayout?: boolean,
) => void;
export type BlueprintOperationFunction =
| BlueprintOperationModifyPropsFn
| BlueprintOperationAddActionFn
| BlueprintOperationChildOperationsFn
| BlueprintBeforeOperationsFn;
export type BlueprintOperationType = keyof typeof BlueprintOperationTypes;
export type BlueprintOperation = {
type: BlueprintOperationType;
fn: BlueprintOperationFunction;
};
export function* executeWidgetBlueprintOperations(
operations: BlueprintOperation[],
widgets: { [widgetId: string]: FlattenedWidgetProps },
widgetId: string,
) {
const isAutoLayout: boolean = yield select(getIsAutoLayout);
operations.forEach((operation: BlueprintOperation) => {
const widget: WidgetProps & { children?: string[] | WidgetProps[] } = {
...widgets[widgetId],
};
switch (operation.type) {
case BlueprintOperationTypes.MODIFY_PROPS:
if (widget.children && widget.children.length > 0) {
widget.children = (widget.children as string[]).map(
(childId: string) => widgets[childId],
) as WidgetProps[];
}
const updatePropertyPayloads: UpdatePropertyArgs[] | undefined = (
operation.fn as BlueprintOperationModifyPropsFn
)(
widget as WidgetProps & { children?: WidgetProps[] },
widgets,
get(widgets, widget.parentId || "", undefined),
isAutoLayout,
);
updatePropertyPayloads &&
updatePropertyPayloads.forEach((params: UpdatePropertyArgs) => {
widgets[params.widgetId][params.propertyName] =
params.propertyValue;
});
break;
}
});
const result: { [widgetId: string]: FlattenedWidgetProps } = yield widgets;
return result;
}
/**
* this saga executes the blueprint child operation
*
* @param parent
* @param newWidgetId
* @param widgets
*
* @returns { [widgetId: string]: FlattenedWidgetProps }
*/
export function* executeWidgetBlueprintChildOperations(
operation: BlueprintOperation,
canvasWidgets: { [widgetId: string]: FlattenedWidgetProps },
widgetIds: string[],
parentId: string,
) {
// TODO(abhinav): Special handling for child operaionts
// This needs to be deprecated soon
let widgets = canvasWidgets,
message;
const isAutoLayout: boolean = yield select(getIsAutoLayout);
for (const widgetId of widgetIds) {
// Get the default properties map of the current widget
// The operation can handle things based on this map
// Little abstraction leak, but will be deprecated soon
const widgetPropertyMaps = {
defaultPropertyMap: WidgetFactory.getWidgetDefaultPropertiesMap(
canvasWidgets[widgetId].type as WidgetType,
),
};
let currMessage;
({ message: currMessage, widgets } = (
operation.fn as BlueprintOperationChildOperationsFn
)(widgets, widgetId, parentId, widgetPropertyMaps, isAutoLayout));
//set message if one of the widget has any message to show
if (currMessage) message = currMessage;
}
// If something odd happens show the message related to the odd scenario
if (message) {
toast.show(message, {
kind: "info",
});
}
// Flow returns to the usual from here.
return widgets;
}
/**
* this saga traverse the tree till we get
* to MAIN_CONTAINER_WIDGET_ID while travesring, if we find
* any widget which has CHILD_OPERATION, we will call the fn in it
*
* @param parent
* @param newWidgetId
* @param widgets
*
* @returns { [widgetId: string]: FlattenedWidgetProps }
*/
export function* traverseTreeAndExecuteBlueprintChildOperations(
parent: FlattenedWidgetProps,
newWidgetIds: string[],
widgets: { [widgetId: string]: FlattenedWidgetProps },
) {
let root = parent;
while (root.parentId && root.widgetId !== MAIN_CONTAINER_WIDGET_ID) {
const parentConfig = WidgetFactory.widgetConfigMap.get(root.type);
// find the blueprint with type CHILD_OPERATIONS
const blueprintChildOperation = get(
parentConfig,
"blueprint.operations",
[],
).find(
(operation: BlueprintOperation) =>
operation.type === BlueprintOperationTypes.CHILD_OPERATIONS,
);
// if there is blueprint operation with CHILD_OPERATION type, call the fn in it
if (blueprintChildOperation) {
const updatedWidgets:
| { [widgetId: string]: FlattenedWidgetProps }
| undefined = yield call(
executeWidgetBlueprintChildOperations,
blueprintChildOperation,
widgets,
newWidgetIds,
root.widgetId,
);
if (updatedWidgets) {
widgets = updatedWidgets;
}
}
root = widgets[root.parentId];
}
return widgets;
}
type ExecuteWidgetBlueprintBeforeOperationsParams = {
parentId: string;
widgetId: string;
widgets: { [widgetId: string]: FlattenedWidgetProps };
widgetType: WidgetType;
};
export function* executeWidgetBlueprintBeforeOperations(
blueprintOperation: Extract<
BlueprintOperationTypes,
| BlueprintOperationTypes.BEFORE_ADD
| BlueprintOperationTypes.BEFORE_DROP
| BlueprintOperationTypes.BEFORE_PASTE
| BlueprintOperationTypes.UPDATE_CREATE_PARAMS_BEFORE_ADD
>,
params: ExecuteWidgetBlueprintBeforeOperationsParams,
) {
const { parentId, widgetId, widgets, widgetType } = params;
const isAutoLayout: boolean = yield select(getIsAutoLayout);
const blueprintOperations: BlueprintOperation[] =
WidgetFactory.widgetConfigMap.get(widgetType)?.blueprint?.operations ?? [];
const beforeAddOperation = blueprintOperations.find(
(operation) => operation.type === blueprintOperation,
);
if (beforeAddOperation)
return (beforeAddOperation.fn as BlueprintBeforeOperationsFn)(
widgets,
widgetId,
parentId,
isAutoLayout,
);
}