PromucFlow_constructor/app/client/src/sagas/CanvasSagas/AutoLayoutDraggingSagas.ts
Preet Sidhu 46dcf3a8f0
chore: Create layout system structure for Anvil and AnvilFlexComponent. (#27178)
## Description
1. Add new ```appPositioningType``` : ANVIL.
2. Create new code path and folder structure for Anvil layout system.
3. Move common pieces of functionalities between autoLayout and anvil to
anvil folder structure (e.g. ```CanvasResizer```).
4. Create ```AnvilFlexComponent```.
5. Use WDS Flex component in AnvilFlexComponent.
6. Pass min max size props in a data structure that is supported by
container queries in the Flex component.
    e.g. min-width: { base: "120px", "480px": "200px" }
7. Supply the following flex properties (flex-grow flex-shrink
flex-basis) to widgets depending on their ```responsiveBehvaiour```:
    a) Fill: ```flex: 1 1 0%;```
    b) Hug: ```flex: 0 0 auto;```

#### PR fixes following issue(s)
Fixes # (issue number)
1. [#26987](https://github.com/appsmithorg/appsmith/issues/26987)
2. [#26609](https://github.com/appsmithorg/appsmith/issues/26609)
3. [#26611](https://github.com/appsmithorg/appsmith/issues/26611)

#### 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
- [x] 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
- [x] I have added tests that prove my fix is effective or that my
feature works
- [ ] 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/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

---------

Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com>
Co-authored-by: Aswath K <aswath.sana@gmail.com>
Co-authored-by: rahulramesha <rahul@appsmith.com>
Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com>
2023-10-02 15:41:05 -04:00

310 lines
8.7 KiB
TypeScript

import type { WidgetAddChild } from "actions/pageActions";
import { updateAndSaveLayout } from "actions/pageActions";
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
import {
ReduxActionErrorTypes,
ReduxActionTypes,
} from "@appsmith/constants/ReduxActionConstants";
import type { FlexLayerAlignment } from "layoutSystems/common/utils/constants";
import { LayoutDirection } from "layoutSystems/common/utils/constants";
import {
GridDefaults,
MAIN_CONTAINER_WIDGET_ID,
} from "constants/WidgetConstants";
import log from "loglevel";
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
import { all, call, put, select, takeLatest } from "redux-saga/effects";
import { getWidgets, getWidgetsMeta } from "sagas/selectors";
import { getUpdateDslAfterCreatingChild } from "sagas/WidgetAdditionSagas";
import {
addNewLayer,
createFlexLayer,
removeWidgetsFromCurrentLayers,
updateExistingLayer,
updateRelationships,
} from "layoutSystems/autolayout/utils/autoLayoutDraggingUtils";
import type { HighlightInfo } from "layoutSystems/common/utils/types";
import { updatePositionsOfParentAndSiblings } from "layoutSystems/autolayout/utils/positionUtils";
import {
getCanvasWidth,
getIsAutoLayoutMobileBreakPoint,
} from "selectors/editorSelectors";
import { executeWidgetBlueprintBeforeOperations } from "sagas/WidgetBlueprintSagas";
import { BlueprintOperationTypes } from "WidgetProvider/constants";
import type { FlexLayer } from "layoutSystems/autolayout/utils/types";
function* addWidgetAndReorderSaga(
actionPayload: ReduxAction<{
newWidget: WidgetAddChild;
parentId: string;
direction: LayoutDirection;
dropPayload: HighlightInfo;
addToBottom: boolean;
}>,
) {
const start = performance.now();
const { addToBottom, direction, dropPayload, newWidget, parentId } =
actionPayload.payload;
const { alignment, index, isNewLayer, layerIndex, rowIndex } = dropPayload;
const isMobile: boolean = yield select(getIsAutoLayoutMobileBreakPoint);
const allWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
try {
const newParams: { [key: string]: any } = yield call(
executeWidgetBlueprintBeforeOperations,
BlueprintOperationTypes.UPDATE_CREATE_PARAMS_BEFORE_ADD,
{
parentId,
widgetId: newWidget.newWidgetId,
widgets: allWidgets,
widgetType: newWidget.type,
},
);
const updatedParams: WidgetAddChild = { ...newWidget, ...newParams };
const updatedWidgetsOnAddition: CanvasWidgetsReduxState = yield call(
getUpdateDslAfterCreatingChild,
{
...updatedParams,
widgetId: parentId,
},
);
if (!parentId || !updatedWidgetsOnAddition[parentId]) {
return updatedWidgetsOnAddition;
}
let widgetIndex = index;
let currLayerIndex = layerIndex;
let newLayer = isNewLayer;
const canvasWidget = updatedWidgetsOnAddition[parentId];
if (addToBottom && canvasWidget.children && canvasWidget.flexLayers) {
widgetIndex = canvasWidget.children.length;
currLayerIndex = canvasWidget.flexLayers.length;
newLayer = true;
}
const updatedWidgetsOnMove: CanvasWidgetsReduxState = yield call(
reorderAutolayoutChildren,
{
movedWidgets: [newWidget.newWidgetId],
index: widgetIndex,
isNewLayer: newLayer,
parentId,
allWidgets: updatedWidgetsOnAddition,
alignment,
direction,
layerIndex: currLayerIndex,
rowIndex,
isMobile,
},
);
yield put(updateAndSaveLayout(updatedWidgetsOnMove));
log.debug(
"Auto-layout : add new widget took",
performance.now() - start,
"ms",
);
} catch (error) {
yield put({
type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR,
payload: {
action: ReduxActionTypes.AUTOLAYOUT_ADD_NEW_WIDGETS,
error,
},
});
}
}
function* autoLayoutReorderSaga(
actionPayload: ReduxAction<{
movedWidgets: string[];
parentId: string;
direction: LayoutDirection;
dropPayload: HighlightInfo;
}>,
) {
const start = performance.now();
const { direction, dropPayload, movedWidgets, parentId } =
actionPayload.payload;
const { alignment, index, isNewLayer, layerIndex, rowIndex } = dropPayload;
try {
const allWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
const isMobile: boolean = yield select(getIsAutoLayoutMobileBreakPoint);
if (!parentId || !movedWidgets || !movedWidgets.length) return;
const updatedWidgets: CanvasWidgetsReduxState = yield call(
reorderAutolayoutChildren,
{
movedWidgets,
index,
isNewLayer,
parentId,
allWidgets,
alignment,
direction,
layerIndex,
rowIndex,
isMobile,
},
);
yield put(updateAndSaveLayout(updatedWidgets));
log.debug(
"Auto-layout : reorder computations took",
performance.now() - start,
"ms",
);
} catch (error) {
yield put({
type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR,
payload: {
action: ReduxActionTypes.AUTOLAYOUT_REORDER_WIDGETS,
error,
},
});
}
}
function* reorderAutolayoutChildren(params: {
movedWidgets: string[];
index: number;
isNewLayer: boolean;
parentId: string;
allWidgets: CanvasWidgetsReduxState;
alignment: FlexLayerAlignment;
direction: LayoutDirection;
layerIndex: number;
rowIndex: number;
isMobile: boolean;
}) {
const {
alignment,
allWidgets,
direction,
index,
isMobile,
isNewLayer,
layerIndex,
movedWidgets,
parentId,
rowIndex,
} = params;
const widgets = Object.assign({}, allWidgets);
if (!movedWidgets) return widgets;
const mainCanvasWidth: number = yield select(getCanvasWidth);
const selectedWidgets = [...movedWidgets];
const metaProps: Record<string, any> = yield select(getWidgetsMeta);
let updatedWidgets: CanvasWidgetsReduxState = updateRelationships(
selectedWidgets,
widgets,
parentId,
false,
isMobile,
mainCanvasWidth,
metaProps,
);
// Update flexLayers for a vertical stack.
if (direction === LayoutDirection.Vertical) {
const canvas = widgets[parentId];
if (!canvas) return widgets;
const flexLayers = canvas.flexLayers || [];
// Remove moved widgets from the flex layers.
const filteredLayers = removeWidgetsFromCurrentLayers(
selectedWidgets,
flexLayers,
);
// Create a temporary layer from moved widgets.
const newLayer: FlexLayer = createFlexLayer(
selectedWidgets,
widgets,
alignment,
);
// Add the new layer to the flex layers.
updatedWidgets = isNewLayer
? addNewLayer(
newLayer,
updatedWidgets,
parentId,
filteredLayers,
layerIndex,
)
: updateExistingLayer(
newLayer,
updatedWidgets,
parentId,
filteredLayers,
layerIndex,
rowIndex,
);
updatedWidgets = movedWidgets.reduce((widgets, eachWidget) => {
const widget = widgets[eachWidget];
widgets[eachWidget] = {
...widget,
alignment,
};
return widgets;
}, updatedWidgets);
}
// update children of the parent canvas.
const items = [...(widgets[parentId].children || [])];
// remove moved widgets from children
const newItems = items.filter((item) => movedWidgets.indexOf(item) === -1);
// calculate valid position for drop
const pos = index > newItems.length ? newItems.length : index;
updatedWidgets[parentId] = {
...updatedWidgets[parentId],
children: [
...newItems.slice(0, pos),
...movedWidgets,
...newItems.slice(pos),
],
};
const parentWidget =
allWidgets[allWidgets[parentId].parentId || MAIN_CONTAINER_WIDGET_ID];
const isAutoLayoutContainerCanvas =
parentWidget.type === "CONTAINER_WIDGET" &&
!parentWidget.isListItemContainer;
if (isAutoLayoutContainerCanvas) {
const height =
allWidgets[parentId].bottomRow / GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
updatedWidgets[parentWidget.widgetId] = {
...updatedWidgets[parentWidget.widgetId],
bottomRow: parentWidget.topRow + height,
};
}
const widgetsAfterPositionUpdate = updatePositionsOfParentAndSiblings(
updatedWidgets,
parentId,
layerIndex,
isMobile,
mainCanvasWidth,
false,
metaProps,
);
return widgetsAfterPositionUpdate;
}
export default function* autoLayoutDraggingSagas() {
yield all([
takeLatest(
ReduxActionTypes.AUTOLAYOUT_REORDER_WIDGETS,
autoLayoutReorderSaga,
),
takeLatest(
ReduxActionTypes.AUTOLAYOUT_ADD_NEW_WIDGETS,
addWidgetAndReorderSaga,
),
]);
}