PromucFlow_constructor/app/client/src/sagas/ModalSagas.ts
Ashok Kumar M 7b527c9024
chore: Merge wds and anvil feature flags (#32609)
[![workerB](https://img.shields.io/endpoint?url=https%3A%2F%2Fworkerb.linearb.io%2Fv2%2Fbadge%2Fprivate%2FU2FsdGVkX1LNwrMHgs05enX0VDk8QxZH7uP7Ii4HE%2Fcollaboration.svg%3FcacheSeconds%3D60)](https://workerb.linearb.io/v2/badge/collaboration-page?magicLinkId=M7zehz4)
## Description

Cleaning up three patterns of checks to enable wds and anvil into two.
wds and anvil had to have different flags coz anvil had to play catch up
with wds, now that's not the case so it does not make sense to have two
flags.

Old patterns
- checking if the wds feature flag is enabled
- checking if the anvil feature flag is enabled
- checking if the layout system of the app is anvil

New Pattern
- checking if anvil feature flag is enabled (used only for creating an
anvil app)
- checking if layout system of the app is anvil

Fixes #32590
_or_  
Fixes `Issue URL`
> [!WARNING]  
> _If no issue exists, please create an issue first, and check with the
maintainers if the issue is valid._

## Automation

/ok-to-test tags="@tag.All"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/8663918496>
> Commit: e10cc2a84ed680b29c49c5b2e8175df4c18da2f8
> Cypress dashboard url: <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=8663918496&attempt=1"
target="_blank">Click here!</a>

<!-- end of auto-generated comment: Cypress test results  -->

















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

- **Refactor**
- Consolidated the usage of layout system checks across the application
to use a unified Anvil layout selector, enhancing consistency in
layout-related conditional logic.
- **Bug Fixes**
- Removed outdated feature flags related to the Anvil + WDS integration,
ensuring the application's feature toggling aligns with the current
development strategy.
- **Tests**
- Updated unit tests to align with the new method of layout system
determination, ensuring test environments accurately reflect production
behavior.
- **Chores**
- Cleaned up redundant code and feature flags that are no longer in use,
simplifying the codebase and reducing potential for errors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-04-12 22:54:04 +05:30

368 lines
11 KiB
TypeScript

import {
all,
call,
delay,
put,
select,
takeEvery,
takeLatest,
} from "redux-saga/effects";
import { generateReactKey } from "utils/generators";
import type { ModalWidgetResize, WidgetAddChild } from "actions/pageActions";
import { updateAndSaveLayout } from "actions/pageActions";
import {
GridDefaults,
MAIN_CONTAINER_WIDGET_ID,
} from "constants/WidgetConstants";
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
import {
ReduxActionErrorTypes,
ReduxActionTypes,
WidgetReduxActionTypes,
} from "@appsmith/constants/ReduxActionConstants";
import {
getWidget,
getWidgetByName,
getWidgetIdsByType,
getWidgetMetaProps,
getWidgets,
getWidgetsMeta,
} from "sagas/selectors";
import type {
CanvasWidgetsReduxState,
FlattenedWidgetProps,
} from "reducers/entityReducers/canvasWidgetsReducer";
import { updateWidgetMetaPropAndEval } from "actions/metaActions";
import { focusWidget, showModal } from "actions/widgetActions";
import log from "loglevel";
import { flatten } from "lodash";
import WidgetFactory from "WidgetProvider/factory";
import type { WidgetProps } from "widgets/BaseWidget";
import { selectWidgetInitAction } from "actions/widgetSelectionActions";
import { SelectionRequestType } from "./WidgetSelectUtils";
import { toast } from "design-system";
import { getIsAutoLayout } from "selectors/editorSelectors";
import { recalculateAutoLayoutColumnsAndSave } from "./AutoLayoutUpdateSagas";
import {
FlexLayerAlignment,
LayoutDirection,
} from "layoutSystems/common/utils/constants";
import { getModalWidgetType } from "selectors/widgetSelectors";
import { AnvilReduxActionTypes } from "layoutSystems/anvil/integrations/actions/actionTypes";
import { getWidgetSelectionBlock } from "selectors/ui";
import { getIsAnvilLayout } from "layoutSystems/anvil/integrations/selectors";
const WidgetTypes = WidgetFactory.widgetTypes;
export function* createModalSaga(action: ReduxAction<{ modalName: string }>) {
try {
const modalWidgetId = generateReactKey();
const isAutoLayout: boolean = yield select(getIsAutoLayout);
const modalWidgetType: string = yield select(getModalWidgetType);
const isAnvilLayout: boolean = yield select(getIsAnvilLayout);
const newWidget: WidgetAddChild = {
widgetId: MAIN_CONTAINER_WIDGET_ID,
widgetName: action.payload.modalName,
type: modalWidgetType,
newWidgetId: modalWidgetId,
parentRowSpace: 1,
parentColumnSpace: 1,
leftColumn: 0,
topRow: 0,
columns: 0,
rows: 0,
tabId: "",
};
if (isAutoLayout) {
const dropPayload = {
alignment: FlexLayerAlignment.Center,
index: 0,
isNewLayer: true,
layerIndex: 0,
rowIndex: 0,
};
newWidget.props = {
alignment: FlexLayerAlignment.Center,
};
yield put({
type: ReduxActionTypes.AUTOLAYOUT_ADD_NEW_WIDGETS,
payload: {
dropPayload,
newWidget,
parentId: MAIN_CONTAINER_WIDGET_ID,
direction: LayoutDirection.Vertical,
addToBottom: true,
},
});
} else if (isAnvilLayout) {
//TODO(#30604): Refactor to separate this logic from the anvil layout system
yield put({
type: AnvilReduxActionTypes.ANVIL_ADD_NEW_WIDGET,
payload: {
highlight: { alignment: "none", canvasId: "0" },
newWidget: { ...newWidget, detachFromLayout: true },
dragMeta: {
draggedWidgetTypes: "WIDGETS",
draggedOn: "MAIN_CANVAS",
},
},
});
} else {
yield put({
type: WidgetReduxActionTypes.WIDGET_ADD_CHILD,
payload: newWidget,
});
}
} catch (error) {
log.error(error);
yield put({
type: ReduxActionErrorTypes.CREATE_MODAL_ERROR,
payload: { error },
});
}
}
export function* showModalByNameSaga(
action: ReduxAction<{ modalName: string }>,
) {
const widgets: { [widgetId: string]: FlattenedWidgetProps } =
yield select(getWidgets);
const modal: FlattenedWidgetProps | undefined = Object.values(widgets).find(
(widget: FlattenedWidgetProps) =>
widget.widgetName === action.payload.modalName,
);
if (modal) {
yield put(showModal(modal.widgetId));
}
}
export function* showIfModalSaga(
action: ReduxAction<{ widgetId: string; type: string }>,
) {
if (action.payload.type === "MODAL_WIDGET") {
yield put(showModal(action.payload.widgetId));
}
}
export function* showModalSaga(action: ReduxAction<{ modalId: string }>) {
// First we close the currently open modals (if any)
// Notice the empty payload.
yield call(closeModalSaga, {
type: ReduxActionTypes.CLOSE_MODAL,
payload: {
exclude: action.payload.modalId,
},
});
yield put(focusWidget(action.payload.modalId));
const widgetLikeProps = {
widgetId: action.payload.modalId,
} as WidgetProps;
const metaProps: Record<string, unknown> = yield select(
getWidgetMetaProps,
widgetLikeProps,
);
if (!metaProps || !metaProps.isVisible) {
// Then show the modal we would like to show.
yield put(
updateWidgetMetaPropAndEval(action.payload.modalId, "isVisible", true),
);
yield delay(1000);
}
yield put({
type: ReduxActionTypes.SHOW_PROPERTY_PANE,
payload: {
widgetId: action.payload.modalId,
callForDragOrResize: undefined,
force: true,
},
});
}
export function* closeModalSaga(
action: ReduxAction<{ modalName?: string; exclude?: string }>,
) {
try {
const { modalName } = action.payload;
let widgetIds: string[] = [];
// If modalName is provided, we just want to close this modal
if (modalName) {
const widget: FlattenedWidgetProps | undefined = yield select(
getWidgetByName,
modalName,
);
widgetIds = widget ? [widget.widgetId] : [];
yield put({
type: ReduxActionTypes.SHOW_PROPERTY_PANE,
payload: {},
});
} else {
// If modalName is not provided, find all open modals
// Get all meta prop records
const metaProps: Record<string, any> = yield select(getWidgetsMeta);
const modalWidgetType: string = yield select(getModalWidgetType);
// Get widgetIds of all widgets of type MODAL_WIDGET
// Note: Not updating this code path for WDS_MODAL_WIDGET, as the functionality
// may require us to keep existing modals open.
// In this, the flow of switching back and forth between multiple modals is to be tested.
const modalWidgetIds: string[] = yield select(
getWidgetIdsByType,
modalWidgetType,
);
// Loop through all modal widgetIds
modalWidgetIds.forEach((widgetId: string) => {
// Check if modal is open
if (metaProps[widgetId] && metaProps[widgetId].isVisible) {
// Add to our list of widgetIds
widgetIds.push(widgetId);
}
});
}
widgetIds = action.payload.exclude
? widgetIds.filter((id: string) => id !== action.payload.exclude)
: widgetIds;
// If we have modals to close, set its isVisible to false to close.
if (widgetIds) {
yield all(
flatten(
widgetIds.map((widgetId: string) => {
return [
put(updateWidgetMetaPropAndEval(widgetId, "isVisible", false)),
];
}),
),
);
}
if (modalName) {
const isWidgetSelectionBlocked: boolean = yield select(
getWidgetSelectionBlock,
);
if (!isWidgetSelectionBlocked) {
yield put(selectWidgetInitAction(SelectionRequestType.Empty));
yield put(focusWidget(MAIN_CONTAINER_WIDGET_ID));
}
}
} catch (error) {
log.error(error);
}
}
export function* resizeModalSaga(resizeAction: ReduxAction<ModalWidgetResize>) {
try {
toast.dismiss();
const start = performance.now();
const { canvasWidgetId, height, widgetId, width } = resizeAction.payload;
const stateWidget: FlattenedWidgetProps = yield select(getWidget, widgetId);
const stateWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
const isAutoLayout: boolean = yield select(getIsAutoLayout);
let widget = { ...stateWidget };
const widgets = { ...stateWidgets };
widget = { ...widget, height, width };
widgets[widgetId] = widget;
if (canvasWidgetId) {
const bottomRow = getModalCanvasBottomRow(
widgets,
canvasWidgetId,
height,
);
const stateModalContainerWidget: FlattenedWidgetProps = yield select(
getWidget,
canvasWidgetId,
);
let modalContainerWidget = { ...stateModalContainerWidget };
modalContainerWidget = {
...modalContainerWidget,
bottomRow,
minHeight: height,
};
widgets[canvasWidgetId] = modalContainerWidget;
}
log.debug("resize computations took", performance.now() - start, "ms");
//TODO Identify the updated widgets and pass the values
if (isAutoLayout) {
yield call(recalculateAutoLayoutColumnsAndSave, widgets);
yield put({
type: ReduxActionTypes.PROCESS_AUTO_LAYOUT_DIMENSION_UPDATES,
});
} else {
yield put(updateAndSaveLayout(widgets));
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR,
payload: {
action: WidgetReduxActionTypes.WIDGET_RESIZE,
error,
},
});
}
}
/**
* Note: returns bottomRow of the lowest widget on the canvas
* @param finalWidgets
* @param parentId
* @param height
*/
const getModalCanvasBottomRow = (
finalWidgets: CanvasWidgetsReduxState,
parentId: string,
height: number,
): number => {
if (
!finalWidgets[parentId] ||
finalWidgets[parentId].type !== WidgetTypes.CANVAS_WIDGET
) {
return height;
}
const lowestBottomRowHeight =
height -
GridDefaults.CANVAS_EXTENSION_OFFSET *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT -
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
let lowestBottomRow = Math.ceil(
lowestBottomRowHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
);
const childIds = finalWidgets[parentId].children || [];
// find lowest row
childIds.forEach((cId: string) => {
const child = finalWidgets[cId];
if (child.bottomRow > lowestBottomRow) {
lowestBottomRow = child.bottomRow;
}
});
return (
(lowestBottomRow + GridDefaults.CANVAS_EXTENSION_OFFSET) *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT
);
};
export default function* modalSagas() {
yield all([
takeEvery(ReduxActionTypes.CLOSE_MODAL, closeModalSaga),
takeLatest(ReduxActionTypes.CREATE_MODAL_INIT, createModalSaga),
takeLatest(ReduxActionTypes.SHOW_MODAL, showModalSaga),
takeLatest(ReduxActionTypes.SHOW_MODAL_BY_NAME, showModalByNameSaga),
takeLatest(WidgetReduxActionTypes.WIDGET_CHILD_ADDED, showIfModalSaga),
takeLatest(WidgetReduxActionTypes.WIDGET_MODAL_RESIZE, resizeModalSaga),
]);
}