From 16c287ef433eba78320bc5973bc76e4b745f855b Mon Sep 17 00:00:00 2001 From: Rahul Barwal Date: Fri, 26 Apr 2024 16:19:27 +0530 Subject: [PATCH 1/8] Refactor widget addition sagas and widget operation sagas --- app/client/src/sagas/WidgetAdditionSagas.ts | 46 +++++++++++++------ app/client/src/sagas/WidgetOperationSagas.tsx | 15 +++--- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/app/client/src/sagas/WidgetAdditionSagas.ts b/app/client/src/sagas/WidgetAdditionSagas.ts index 8f58936cddd8..59e37b677134 100644 --- a/app/client/src/sagas/WidgetAdditionSagas.ts +++ b/app/client/src/sagas/WidgetAdditionSagas.ts @@ -26,8 +26,6 @@ import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; import type { WidgetAddChild } from "actions/pageActions"; import { updateAndSaveLayout } from "actions/pageActions"; import { runAction } from "actions/pluginActionActions"; -import { pasteWidget } from "actions/widgetActions"; -import { selectWidgetInitAction } from "actions/widgetSelectionActions"; import type { ApiResponse } from "api/ApiResponses"; import type { Template } from "api/TemplatesApi"; import { @@ -49,7 +47,15 @@ import type { FlattenedWidgetProps, } from "reducers/entityReducers/canvasWidgetsReducer"; import type { DragDetails } from "reducers/uiReducers/dragResizeReducer"; -import { all, call, put, select, take, takeEvery } from "redux-saga/effects"; +import { + all, + call, + put, + race, + select, + take, + takeEvery, +} from "redux-saga/effects"; import { getDataTree } from "selectors/dataTreeSelectors"; import { getCanvasWidth, @@ -81,7 +87,6 @@ import { getMousePositionFromCanvasGridPosition, getSnappedGrid, } from "./WidgetOperationUtils"; -import { SelectionRequestType } from "./WidgetSelectUtils"; import { getDragDetails, getWidget, @@ -89,9 +94,9 @@ import { getWidgets, } from "./selectors"; +import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; import { getBuildingBlockDragStartTimestamp } from "selectors/buildingBlocksSelectors"; import { initiateBuildingBlockDropEvent } from "utils/buildingBlockUtils"; -import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; const WidgetTypes = WidgetFactory.widgetTypes; @@ -552,7 +557,7 @@ function* addBuildingBlockActionsToApp(dragDetails: DragDetails) { return response; } -function* getBuildingBlocksDropMousePosition( +export function* getBuildingBlocksDropMousePosition( topRow: number, leftColumn: number, ) { @@ -630,7 +635,14 @@ export function* addBuildingBlockToApplication( }); // makes sure updateAndSaveLayout completes first for skeletonWidget addition - yield take(ReduxActionTypes.SAVE_PAGE_SUCCESS); + const saveResult: unknown = yield race({ + success: take(ReduxActionTypes.SAVE_PAGE_SUCCESS), + failure: take(ReduxActionErrorTypes.SAVE_PAGE_ERROR), + }); + + if (typeof saveResult === "object" && "failure" in saveResult!) { + throw new Error("Save page failed"); + } const response: ApiResponse = yield call(addBuildingBlockActionsToApp, dragDetails); @@ -639,12 +651,6 @@ export function* addBuildingBlockToApplication( if (isValid) { yield saveBuildingBlockWidgetsToStore(response); - const mousePosition: { x: number; y: number } = yield call( - getBuildingBlocksDropMousePosition, - topRow, - leftColumn, - ); - // remove skeleton loader just before pasting the building block yield put({ type: WidgetReduxActionTypes.WIDGET_SINGLE_DELETE, @@ -656,12 +662,22 @@ export function* addBuildingBlockToApplication( }, }); - yield put(pasteWidget(false, mousePosition)); + yield put({ + type: ReduxActionTypes.PASTE_COPIED_WIDGET_INIT, + payload: { + groupWidgets: false, + gridLocation: { + top: topRow, + left: leftColumn, + }, + }, + }); + const timeTakenToDropWidgetsInSeconds = (Date.now() - buildingBlockDragStartTimestamp) / 1000; yield call(postPageAdditionSaga, applicationId); // remove selecting of recently pasted widgets caused by pasteWidget - yield put(selectWidgetInitAction(SelectionRequestType.Empty)); + // yield put(selectWidgetInitAction(SelectionRequestType.Empty)); // stop loading after pasting process is complete yield put({ diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index ce2b4fd697ff..23a26e95d8b1 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -1170,6 +1170,7 @@ const getNewPositions = function* ( copiedTotalWidth: number, copiedTopMostRow: number, copiedLeftMostColumn: number, + gridLocation?: { top: number; left: number }, ) { const selectedWidgetIDs: string[] = yield select(getSelectedWidgets); const canvasWidgets: CanvasWidgetsReduxState = yield select(getWidgets); @@ -1221,6 +1222,7 @@ const getNewPositions = function* ( copiedTotalWidth, copiedTopMostRow, copiedLeftMostColumn, + gridLocation, ); return newPastingPositionDetails; }; @@ -1374,6 +1376,7 @@ function* getNewPositionsBasedOnMousePositions( copiedTotalWidth: number, copiedTopMostRow: number, copiedLeftMostColumn: number, + gridLocation?: { top: number; left: number }, ) { let { canvasDOM, canvasId, containerWidget } = getDefaultCanvas(canvasWidgets); @@ -1395,13 +1398,9 @@ function* getNewPositionsBasedOnMousePositions( ); // get mouse positions in terms of grid rows and columns of the pasting canvas - const mousePositions = getMousePositions( - canvasRect, - canvasId, - snapGrid, - padding, - mouseLocation, - ); + const mousePositions = gridLocation + ? gridLocation + : getMousePositions(canvasRect, canvasId, snapGrid, padding, mouseLocation); if (!snapGrid || !mousePositions) return {}; @@ -1489,6 +1488,7 @@ function* pasteWidgetSaga( action: ReduxAction<{ groupWidgets: boolean; mouseLocation: { x: number; y: number }; + gridLocation?: { top: number; left: number }; }>, ) { const { @@ -1584,6 +1584,7 @@ function* pasteWidgetSaga( copiedTotalWidth, topMostWidget.topRow, leftMostWidget.leftColumn, + action.payload.gridLocation, )); if (canvasId) pastingIntoWidgetId = canvasId; From 555963068f157bf05462d6046fd32cbfcb3409c2 Mon Sep 17 00:00:00 2001 From: Rahul Barwal Date: Fri, 26 Apr 2024 16:21:38 +0530 Subject: [PATCH 2/8] Refactor widget addition sagas and widget operation sagas --- app/client/src/sagas/WidgetAdditionSagas.ts | 40 +-------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/app/client/src/sagas/WidgetAdditionSagas.ts b/app/client/src/sagas/WidgetAdditionSagas.ts index 59e37b677134..25aea3ef11f8 100644 --- a/app/client/src/sagas/WidgetAdditionSagas.ts +++ b/app/client/src/sagas/WidgetAdditionSagas.ts @@ -11,10 +11,7 @@ import { } from "@appsmith/constants/ReduxActionConstants"; import { ENTITY_TYPE } from "@appsmith/entities/AppsmithConsole/utils"; import type { ActionDataState } from "@appsmith/reducers/entityReducers/actionsReducer"; -import { - getActions, - getCanvasWidgets, -} from "@appsmith/selectors/entitiesSelector"; +import { getActions } from "@appsmith/selectors/entitiesSelector"; import { getCurrentWorkspaceId } from "@appsmith/selectors/selectedWorkspaceSelectors"; import type { WidgetBlueprint } from "WidgetProvider/constants"; import { @@ -82,11 +79,6 @@ import { traverseTreeAndExecuteBlueprintChildOperations, } from "./WidgetBlueprintSagas"; import { getPropertiesToUpdate } from "./WidgetOperationSagas"; -import { - getDefaultCanvas, - getMousePositionFromCanvasGridPosition, - getSnappedGrid, -} from "./WidgetOperationUtils"; import { getDragDetails, getWidget, @@ -557,36 +549,6 @@ function* addBuildingBlockActionsToApp(dragDetails: DragDetails) { return response; } -export function* getBuildingBlocksDropMousePosition( - topRow: number, - leftColumn: number, -) { - const canvasWidgets: CanvasWidgetsReduxState = yield select(getCanvasWidgets); - let mousePosition = { x: 0, y: 0 }; - - // convert grid position to mouse position for paste functionality - const { canvasDOM, canvasId, containerWidget } = - getDefaultCanvas(canvasWidgets); - if (!canvasDOM || !containerWidget || !canvasId) { - mousePosition = { x: 0, y: 0 }; - } else { - const canvasRect = canvasDOM.getBoundingClientRect(); - const { padding, snapGrid } = getSnappedGrid( - containerWidget, - canvasRect.width, - ); - mousePosition = getMousePositionFromCanvasGridPosition( - topRow, - leftColumn, - snapGrid, - padding, - canvasId as string, - ); - } - - return mousePosition; -} - function* runSingleAction(actionId: string) { yield put(runAction(actionId)); yield take(ReduxActionTypes.RUN_ACTION_SUCCESS); From 8e7d3a4ec02dd8d8a5dad9c667f97828adb6dd9d Mon Sep 17 00:00:00 2001 From: Rahul Barwal Date: Fri, 26 Apr 2024 19:55:01 +0530 Subject: [PATCH 3/8] Refactor widget addition sagas and widget operation sagas --- app/client/src/sagas/WidgetAdditionSagas.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/client/src/sagas/WidgetAdditionSagas.ts b/app/client/src/sagas/WidgetAdditionSagas.ts index 25aea3ef11f8..2596b73a5059 100644 --- a/app/client/src/sagas/WidgetAdditionSagas.ts +++ b/app/client/src/sagas/WidgetAdditionSagas.ts @@ -89,6 +89,8 @@ import { import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; import { getBuildingBlockDragStartTimestamp } from "selectors/buildingBlocksSelectors"; import { initiateBuildingBlockDropEvent } from "utils/buildingBlockUtils"; +import { selectWidgetInitAction } from "actions/widgetSelectionActions"; +import { SelectionRequestType } from "./WidgetSelectUtils"; const WidgetTypes = WidgetFactory.widgetTypes; @@ -639,7 +641,7 @@ export function* addBuildingBlockToApplication( (Date.now() - buildingBlockDragStartTimestamp) / 1000; yield call(postPageAdditionSaga, applicationId); // remove selecting of recently pasted widgets caused by pasteWidget - // yield put(selectWidgetInitAction(SelectionRequestType.Empty)); + yield put(selectWidgetInitAction(SelectionRequestType.Empty)); // stop loading after pasting process is complete yield put({ From 734d8295cf92c88b85e153aaf3468d1f58c9d139 Mon Sep 17 00:00:00 2001 From: Jacques Ikot Date: Mon, 29 Apr 2024 04:49:50 +0100 Subject: [PATCH 4/8] refactor + clean up --- app/client/src/sagas/BuildingBlocksSagas.ts | 246 ++++++++++++++++- .../sagas/CanvasSagas/DraggingCanvasSagas.ts | 6 +- app/client/src/sagas/WidgetAdditionSagas.ts | 257 +----------------- app/client/src/sagas/WidgetOperationSagas.tsx | 20 +- app/client/src/sagas/WidgetOperationUtils.ts | 35 --- 5 files changed, 261 insertions(+), 303 deletions(-) diff --git a/app/client/src/sagas/BuildingBlocksSagas.ts b/app/client/src/sagas/BuildingBlocksSagas.ts index f81fb0a72419..6a259e34f346 100644 --- a/app/client/src/sagas/BuildingBlocksSagas.ts +++ b/app/client/src/sagas/BuildingBlocksSagas.ts @@ -1,16 +1,26 @@ -import type { ImportBuildingBlockToApplicationResponse } from "@appsmith/api/ApplicationApi"; +import type { + ImportBuildingBlockToApplicationRequest, + ImportBuildingBlockToApplicationResponse, +} from "@appsmith/api/ApplicationApi"; import ApplicationApi from "@appsmith/api/ApplicationApi"; import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants"; import { ReduxActionErrorTypes, ReduxActionTypes, + WidgetReduxActionTypes, } from "@appsmith/constants/ReduxActionConstants"; -import { getCanvasWidgets } from "@appsmith/selectors/entitiesSelector"; +import type { ActionDataState } from "@appsmith/reducers/entityReducers/actionsReducer"; +import { + getActions, + getCanvasWidgets, +} from "@appsmith/selectors/entitiesSelector"; import { getCurrentWorkspaceId } from "@appsmith/selectors/selectedWorkspaceSelectors"; +import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; import { isAirgapped } from "@appsmith/utils/airgapHelpers"; import { flattenDSL } from "@shared/dsl"; import type { WidgetProps } from "@shared/dsl/src/migrate/types"; import type { FlattenedWidgetProps } from "WidgetProvider/constants"; +import type { WidgetAddChild } from "actions/pageActions"; import { runAction } from "actions/pluginActionActions"; import { setCurrentForkingBuildingBlockName, @@ -19,20 +29,36 @@ import { import { pasteWidget } from "actions/widgetActions"; import { selectWidgetInitAction } from "actions/widgetSelectionActions"; import type { ApiResponse } from "api/ApiResponses"; +import type { Template } from "api/TemplatesApi"; import { STARTER_BUILDING_BLOCKS } from "constants/TemplatesConstants"; import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants"; import type { WidgetLayoutPositionInfo } from "layoutSystems/anvil/utils/layouts/widgetPositionUtils"; import type { CopiedWidgetData } from "layoutSystems/anvil/utils/paste/types"; import { getWidgetHierarchy } from "layoutSystems/anvil/utils/paste/utils"; -import { all, call, delay, put, select, takeEvery } from "redux-saga/effects"; +import type { DragDetails } from "reducers/uiReducers/dragResizeReducer"; +import { + all, + call, + delay, + put, + race, + select, + take, + takeEvery, +} from "redux-saga/effects"; +import { getBuildingBlockDragStartTimestamp } from "selectors/buildingBlocksSelectors"; import { getCurrentApplicationId, getCurrentPageId, } from "selectors/editorSelectors"; +import { getTemplatesSelector } from "selectors/templatesSelectors"; +import { initiateBuildingBlockDropEvent } from "utils/buildingBlockUtils"; import { getCopiedWidgets, saveCopiedWidgets } from "utils/storage"; import { validateResponse } from "./ErrorSagas"; import { postPageAdditionSaga } from "./TemplatesSagas"; +import { addChildSaga } from "./WidgetAdditionSagas"; import { SelectionRequestType } from "./WidgetSelectUtils"; +import { getDragDetails, getWidgetByName } from "./selectors"; const isAirgappedInstance = isAirgapped(); @@ -153,6 +179,220 @@ function* forkStarterBuildingBlockToApplicationSaga( yield call(saveCopiedWidgets, JSON.stringify(existingCopiedWidgets)); } } + +function* addBuildingBlockActionsToApp(dragDetails: DragDetails) { + const applicationId: string = yield select(getCurrentApplicationId); + const buildingblockName = dragDetails.newWidget.displayName; + const buildingBlocks: Template[] = yield select(getTemplatesSelector); + const currentPageId: string = yield select(getCurrentPageId); + const workspaceId: string = yield select(getCurrentWorkspaceId); + const selectedBuildingBlock = buildingBlocks.find( + (buildingBlock) => buildingBlock.title === buildingblockName, + ) as Template; + + const body: ImportBuildingBlockToApplicationRequest = { + pageId: currentPageId, + applicationId, + workspaceId, + templateId: selectedBuildingBlock.id, + }; + + // api call adds DS, queries and JS to page and returns new page dsl with building block + const response: ApiResponse = + yield call(ApplicationApi.importBuildingBlockToApplication, body); + + return response; +} + +function* runSingleAction(actionId: string) { + yield put(runAction(actionId)); + yield take(ReduxActionTypes.RUN_ACTION_SUCCESS); +} + +function* runNewlyCreatedActions( + actionsBeforeAddingBuildingBlock: ActionDataState, + actionsAfterAddingBuildingBlocks: ActionDataState, +) { + const actionIdsBeforeAddingBB = actionsBeforeAddingBuildingBlock.map( + (obj) => obj.config.id, + ); + + const newlyAddedActions = actionsAfterAddingBuildingBlocks.filter( + (obj) => !actionIdsBeforeAddingBB.includes(obj.config.id), + ); + + // Run each action sequentially. We have a max of 2-3 actions per building block. + // If we run this in parallel, we will have a racing condition when multiple building blocks are drag and dropped quickly. + for (const action of newlyAddedActions) { + if (action.config.executeOnLoad) { + yield runSingleAction(action.config.id); + } + } +} + +export function* addBuildingBlockToApplication( + buildingBlockWidget: WidgetAddChild, + skeletonLoaderId: string, +) { + const { leftColumn, topRow } = buildingBlockWidget; + try { + const dragDetails: DragDetails = yield select(getDragDetails); + const applicationId: string = yield select(getCurrentApplicationId); + const workspaceId: string = yield select(getCurrentWorkspaceId); + const actionsBeforeAddingBuildingBlock: ActionDataState = + yield select(getActions); + const existingCopiedWidgets: unknown = yield call(getCopiedWidgets); + const buildingBlockDragStartTimestamp: number = yield select( + getBuildingBlockDragStartTimestamp, + ); + + // start loading for dragging building blocks + yield put({ + type: ReduxActionTypes.DRAGGING_BUILDING_BLOCK_TO_CANVAS_INIT, + }); + + // makes sure updateAndSaveLayout completes first for skeletonWidget addition + const saveResult: unknown = yield race({ + success: take(ReduxActionTypes.SAVE_PAGE_SUCCESS), + failure: take(ReduxActionErrorTypes.SAVE_PAGE_ERROR), + }); + + if (typeof saveResult === "object" && "failure" in saveResult!) { + throw new Error("Save page failed"); + } + + const response: ApiResponse = + yield call(addBuildingBlockActionsToApp, dragDetails); + const isValid: boolean = yield validateResponse(response); + + if (isValid) { + yield saveBuildingBlockWidgetsToStore(response); + + // remove skeleton loader just before pasting the building block + yield put({ + type: WidgetReduxActionTypes.WIDGET_SINGLE_DELETE, + payload: { + widgetId: skeletonLoaderId, + parentId: MAIN_CONTAINER_WIDGET_ID, + disallowUndo: true, + isShortcut: false, + }, + }); + + yield put({ + type: ReduxActionTypes.PASTE_COPIED_WIDGET_INIT, + payload: { + groupWidgets: false, + gridPosition: { + top: topRow, + left: leftColumn, + }, + }, + }); + + const timeTakenToDropWidgetsInSeconds = + (Date.now() - buildingBlockDragStartTimestamp) / 1000; + yield call(postPageAdditionSaga, applicationId); + + // stop loading after pasting process is complete + yield put({ + type: ReduxActionTypes.DRAGGING_BUILDING_BLOCK_TO_CANVAS_SUCCESS, + }); + + const actionsAfterAddingBuildingBlocks: ActionDataState = + yield select(getActions); + + if ( + response.data.onPageLoadActions && + response.data.onPageLoadActions.length > 0 + ) { + yield runNewlyCreatedActions( + actionsBeforeAddingBuildingBlock, + actionsAfterAddingBuildingBlocks, + ); + } + + const timeTakenToCompleteInMs = buildingBlockDragStartTimestamp + ? Date.now() - buildingBlockDragStartTimestamp + : 0; + const timeTakenToCompleteInSeconds = timeTakenToCompleteInMs / 1000; + + AnalyticsUtil.logEvent("DROP_BUILDING_BLOCK_COMPLETED", { + applicationId, + workspaceId, + source: "explorer", + eventData: { + buildingBlockName: dragDetails.newWidget.displayName, + timeTakenToCompletion: timeTakenToCompleteInSeconds, + timeTakenToDropWidgets: timeTakenToDropWidgetsInSeconds, + }, + }); + yield put({ + type: ReduxActionTypes.RESET_BUILDING_BLOCK_DRAG_START_TIME, + }); + + if (existingCopiedWidgets) { + yield call(saveCopiedWidgets, JSON.stringify(existingCopiedWidgets)); + } + } + } catch (error) { + yield put({ + type: WidgetReduxActionTypes.WIDGET_SINGLE_DELETE, + payload: { + widgetId: skeletonLoaderId, + parentId: MAIN_CONTAINER_WIDGET_ID, + disallowUndo: true, + isShortcut: false, + }, + }); + yield put({ + type: ReduxActionErrorTypes.DRAGGING_BUILDING_BLOCK_TO_CANVAS_ERROR, + }); + } +} + +export function* addBuildingBlockToCanvasSaga( + addEntityAction: ReduxAction, +) { + const applicationId: string = yield select(getCurrentApplicationId); + const workspaceId: string = yield select(getCurrentWorkspaceId); + const dragDetails: DragDetails = yield select(getDragDetails); + const buildingblockName = dragDetails.newWidget.displayName; + const skeletonWidgetName = `loading_${buildingblockName + .toLowerCase() + .replace(/ /g, "_")}`; + const addSkeletonWidgetAction: ReduxAction< + WidgetAddChild & { shouldReplay: boolean } + > = { + ...addEntityAction, + payload: { + ...addEntityAction.payload, + type: "SKELETON_WIDGET", + widgetName: skeletonWidgetName, + widgetId: MAIN_CONTAINER_WIDGET_ID, + // so that the skeleton loader does not get included when the users uses the undo/redo + shouldReplay: false, + }, + }; + + yield call(initiateBuildingBlockDropEvent, { + applicationId, + workspaceId, + buildingblockName, + }); + + yield call(addChildSaga, addSkeletonWidgetAction); + const skeletonWidget: FlattenedWidgetProps = yield select( + getWidgetByName, + skeletonWidgetName, + ); + yield call( + addBuildingBlockToApplication, + addEntityAction.payload, + skeletonWidget.widgetId, + ); +} + export default function* watchActionSagas() { if (!isAirgappedInstance) yield all([ diff --git a/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts b/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts index 9e74ee7c72ab..b4cf8177c33d 100644 --- a/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts +++ b/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts @@ -29,10 +29,8 @@ import type { import type { DragDetails } from "reducers/uiReducers/dragResizeReducer"; import type { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer"; import { all, call, put, select, takeLatest } from "redux-saga/effects"; -import { - addBuildingBlockToApplication, - getUpdateDslAfterCreatingChild, -} from "sagas/WidgetAdditionSagas"; +import { addBuildingBlockToApplication } from "sagas/BuildingBlocksSagas"; +import { getUpdateDslAfterCreatingChild } from "sagas/WidgetAdditionSagas"; import { executeWidgetBlueprintBeforeOperations, traverseTreeAndExecuteBlueprintChildOperations, diff --git a/app/client/src/sagas/WidgetAdditionSagas.ts b/app/client/src/sagas/WidgetAdditionSagas.ts index 25aea3ef11f8..136b02e10eec 100644 --- a/app/client/src/sagas/WidgetAdditionSagas.ts +++ b/app/client/src/sagas/WidgetAdditionSagas.ts @@ -1,8 +1,3 @@ -import type { - ImportBuildingBlockToApplicationRequest, - ImportBuildingBlockToApplicationResponse, -} from "@appsmith/api/ApplicationApi"; -import ApplicationApi from "@appsmith/api/ApplicationApi"; import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants"; import { ReduxActionErrorTypes, @@ -10,9 +5,6 @@ import { WidgetReduxActionTypes, } from "@appsmith/constants/ReduxActionConstants"; import { ENTITY_TYPE } from "@appsmith/entities/AppsmithConsole/utils"; -import type { ActionDataState } from "@appsmith/reducers/entityReducers/actionsReducer"; -import { getActions } from "@appsmith/selectors/entitiesSelector"; -import { getCurrentWorkspaceId } from "@appsmith/selectors/selectedWorkspaceSelectors"; import type { WidgetBlueprint } from "WidgetProvider/constants"; import { BlueprintOperationTypes, @@ -22,12 +14,8 @@ import WidgetFactory from "WidgetProvider/factory"; import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; import type { WidgetAddChild } from "actions/pageActions"; import { updateAndSaveLayout } from "actions/pageActions"; -import { runAction } from "actions/pluginActionActions"; -import type { ApiResponse } from "api/ApiResponses"; -import type { Template } from "api/TemplatesApi"; import { BUILDING_BLOCK_EXPLORER_TYPE, - MAIN_CONTAINER_WIDGET_ID, RenderModes, } from "constants/WidgetConstants"; import { toast } from "design-system"; @@ -43,35 +31,20 @@ import type { CanvasWidgetsReduxState, FlattenedWidgetProps, } from "reducers/entityReducers/canvasWidgetsReducer"; -import type { DragDetails } from "reducers/uiReducers/dragResizeReducer"; -import { - all, - call, - put, - race, - select, - take, - takeEvery, -} from "redux-saga/effects"; +import { all, call, put, select, takeEvery } from "redux-saga/effects"; import { getDataTree } from "selectors/dataTreeSelectors"; import { getCanvasWidth, - getCurrentApplicationId, - getCurrentPageId, getIsAutoLayout, getIsAutoLayoutMobileBreakPoint, } from "selectors/editorSelectors"; -import { getTemplatesSelector } from "selectors/templatesSelectors"; import AppsmithConsole from "utils/AppsmithConsole"; import { getNextEntityName } from "utils/AppsmithUtils"; import { generateWidgetProps } from "utils/WidgetPropsUtils"; import { generateReactKey } from "utils/generators"; -import { getCopiedWidgets, saveCopiedWidgets } from "utils/storage"; import type { WidgetProps } from "widgets/BaseWidget"; import { isStack } from "../layoutSystems/autolayout/utils/AutoLayoutUtils"; -import { saveBuildingBlockWidgetsToStore } from "./BuildingBlocksSagas"; -import { validateResponse } from "./ErrorSagas"; -import { postPageAdditionSaga } from "./TemplatesSagas"; +import { addBuildingBlockToCanvasSaga } from "./BuildingBlocksSagas"; import { buildWidgetBlueprint, executeWidgetBlueprintBeforeOperations, @@ -79,16 +52,7 @@ import { traverseTreeAndExecuteBlueprintChildOperations, } from "./WidgetBlueprintSagas"; import { getPropertiesToUpdate } from "./WidgetOperationSagas"; -import { - getDragDetails, - getWidget, - getWidgetByName, - getWidgets, -} from "./selectors"; - -import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; -import { getBuildingBlockDragStartTimestamp } from "selectors/buildingBlocksSelectors"; -import { initiateBuildingBlockDropEvent } from "utils/buildingBlockUtils"; +import { getWidget, getWidgets } from "./selectors"; const WidgetTypes = WidgetFactory.widgetTypes; @@ -525,226 +489,13 @@ function* addNewTabChildSaga( yield put(updateAndSaveLayout(updatedWidgets)); } -function* addBuildingBlockActionsToApp(dragDetails: DragDetails) { - const applicationId: string = yield select(getCurrentApplicationId); - const buildingblockName = dragDetails.newWidget.displayName; - const buildingBlocks: Template[] = yield select(getTemplatesSelector); - const currentPageId: string = yield select(getCurrentPageId); - const workspaceId: string = yield select(getCurrentWorkspaceId); - const selectedBuildingBlock = buildingBlocks.find( - (buildingBlock) => buildingBlock.title === buildingblockName, - ) as Template; - - const body: ImportBuildingBlockToApplicationRequest = { - pageId: currentPageId, - applicationId, - workspaceId, - templateId: selectedBuildingBlock.id, - }; - - // api call adds DS, queries and JS to page and returns new page dsl with building block - const response: ApiResponse = - yield call(ApplicationApi.importBuildingBlockToApplication, body); - - return response; -} - -function* runSingleAction(actionId: string) { - yield put(runAction(actionId)); - yield take(ReduxActionTypes.RUN_ACTION_SUCCESS); -} - -function* runNewlyCreatedActions( - actionsBeforeAddingBuildingBlock: ActionDataState, - actionsAfterAddingBuildingBlocks: ActionDataState, -) { - const actionIdsBeforeAddingBB = actionsBeforeAddingBuildingBlock.map( - (obj) => obj.config.id, - ); - - const newlyAddedActions = actionsAfterAddingBuildingBlocks.filter( - (obj) => !actionIdsBeforeAddingBB.includes(obj.config.id), - ); - - // Run each action sequentially. We have a max of 2-3 actions per building block. - // If we run this in parallel, we will have a racing condition when multiple building blocks are drag and dropped quickly. - for (const action of newlyAddedActions) { - if (action.config.executeOnLoad) { - yield runSingleAction(action.config.id); - } - } -} - -export function* addBuildingBlockToApplication( - buildingBlockWidget: WidgetAddChild, - skeletonLoaderId: string, -) { - const { leftColumn, topRow } = buildingBlockWidget; - try { - const dragDetails: DragDetails = yield select(getDragDetails); - const applicationId: string = yield select(getCurrentApplicationId); - const workspaceId: string = yield select(getCurrentWorkspaceId); - const actionsBeforeAddingBuildingBlock: ActionDataState = - yield select(getActions); - const existingCopiedWidgets: unknown = yield call(getCopiedWidgets); - const buildingBlockDragStartTimestamp: number = yield select( - getBuildingBlockDragStartTimestamp, - ); - - // start loading for dragging building blocks - yield put({ - type: ReduxActionTypes.DRAGGING_BUILDING_BLOCK_TO_CANVAS_INIT, - }); - - // makes sure updateAndSaveLayout completes first for skeletonWidget addition - const saveResult: unknown = yield race({ - success: take(ReduxActionTypes.SAVE_PAGE_SUCCESS), - failure: take(ReduxActionErrorTypes.SAVE_PAGE_ERROR), - }); - - if (typeof saveResult === "object" && "failure" in saveResult!) { - throw new Error("Save page failed"); - } - - const response: ApiResponse = - yield call(addBuildingBlockActionsToApp, dragDetails); - const isValid: boolean = yield validateResponse(response); - - if (isValid) { - yield saveBuildingBlockWidgetsToStore(response); - - // remove skeleton loader just before pasting the building block - yield put({ - type: WidgetReduxActionTypes.WIDGET_SINGLE_DELETE, - payload: { - widgetId: skeletonLoaderId, - parentId: MAIN_CONTAINER_WIDGET_ID, - disallowUndo: true, - isShortcut: false, - }, - }); - - yield put({ - type: ReduxActionTypes.PASTE_COPIED_WIDGET_INIT, - payload: { - groupWidgets: false, - gridLocation: { - top: topRow, - left: leftColumn, - }, - }, - }); - - const timeTakenToDropWidgetsInSeconds = - (Date.now() - buildingBlockDragStartTimestamp) / 1000; - yield call(postPageAdditionSaga, applicationId); - // remove selecting of recently pasted widgets caused by pasteWidget - // yield put(selectWidgetInitAction(SelectionRequestType.Empty)); - - // stop loading after pasting process is complete - yield put({ - type: ReduxActionTypes.DRAGGING_BUILDING_BLOCK_TO_CANVAS_SUCCESS, - }); - - const actionsAfterAddingBuildingBlocks: ActionDataState = - yield select(getActions); - - if ( - response.data.onPageLoadActions && - response.data.onPageLoadActions.length > 0 - ) { - yield runNewlyCreatedActions( - actionsBeforeAddingBuildingBlock, - actionsAfterAddingBuildingBlocks, - ); - } - - const timeTakenToCompleteInMs = buildingBlockDragStartTimestamp - ? Date.now() - buildingBlockDragStartTimestamp - : 0; - const timeTakenToCompleteInSeconds = timeTakenToCompleteInMs / 1000; - - AnalyticsUtil.logEvent("DROP_BUILDING_BLOCK_COMPLETED", { - applicationId, - workspaceId, - source: "explorer", - eventData: { - buildingBlockName: dragDetails.newWidget.displayName, - timeTakenToCompletion: timeTakenToCompleteInSeconds, - timeTakenToDropWidgets: timeTakenToDropWidgetsInSeconds, - }, - }); - yield put({ - type: ReduxActionTypes.RESET_BUILDING_BLOCK_DRAG_START_TIME, - }); - - if (existingCopiedWidgets) { - yield call(saveCopiedWidgets, JSON.stringify(existingCopiedWidgets)); - } - } - } catch (error) { - yield put({ - type: WidgetReduxActionTypes.WIDGET_SINGLE_DELETE, - payload: { - widgetId: skeletonLoaderId, - parentId: MAIN_CONTAINER_WIDGET_ID, - disallowUndo: true, - isShortcut: false, - }, - }); - yield put({ - type: ReduxActionErrorTypes.DRAGGING_BUILDING_BLOCK_TO_CANVAS_ERROR, - }); - } -} - -function* addBuildingBlockSaga(addEntityAction: ReduxAction) { - const applicationId: string = yield select(getCurrentApplicationId); - const workspaceId: string = yield select(getCurrentWorkspaceId); - const dragDetails: DragDetails = yield select(getDragDetails); - const buildingblockName = dragDetails.newWidget.displayName; - const skeletonWidgetName = `loading_${buildingblockName - .toLowerCase() - .replace(/ /g, "_")}`; - const addSkeletonWidgetAction: ReduxAction< - WidgetAddChild & { shouldReplay: boolean } - > = { - ...addEntityAction, - payload: { - ...addEntityAction.payload, - type: "SKELETON_WIDGET", - widgetName: skeletonWidgetName, - widgetId: MAIN_CONTAINER_WIDGET_ID, - // so that the skeleton loader does not get included when the users uses the undo/redo - shouldReplay: false, - }, - }; - - yield call(initiateBuildingBlockDropEvent, { - applicationId, - workspaceId, - buildingblockName, - }); - - yield call(addChildSaga, addSkeletonWidgetAction); - const skeletonWidget: FlattenedWidgetProps = yield select( - getWidgetByName, - skeletonWidgetName, - ); - yield call( - addBuildingBlockToApplication, - addEntityAction.payload, - skeletonWidget.widgetId, - ); -} - function* addUIEntitySaga(addEntityAction: ReduxAction) { try { const { payload } = addEntityAction; const { type } = payload; if (type === BUILDING_BLOCK_EXPLORER_TYPE) { - yield call(addBuildingBlockSaga, addEntityAction); + yield call(addBuildingBlockToCanvasSaga, addEntityAction); } else { yield call(addChildSaga, addEntityAction); } diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index 23a26e95d8b1..bc52bc1c24d2 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -1162,6 +1162,7 @@ export function calculateNewWidgetPosition( * @param copiedTotalWidth total width of the copied widgets * @param copiedTopMostRow top row of the top most copied widget * @param copiedLeftMostColumn left column of the left most copied widget + * @param gridPosition left and top canvas grid position values * @returns */ const getNewPositions = function* ( @@ -1170,7 +1171,7 @@ const getNewPositions = function* ( copiedTotalWidth: number, copiedTopMostRow: number, copiedLeftMostColumn: number, - gridLocation?: { top: number; left: number }, + gridPosition?: { top: number; left: number }, ) { const selectedWidgetIDs: string[] = yield select(getSelectedWidgets); const canvasWidgets: CanvasWidgetsReduxState = yield select(getWidgets); @@ -1222,7 +1223,7 @@ const getNewPositions = function* ( copiedTotalWidth, copiedTopMostRow, copiedLeftMostColumn, - gridLocation, + gridPosition, ); return newPastingPositionDetails; }; @@ -1366,6 +1367,7 @@ function* getNewPositionsBasedOnSelectedWidgets( * @param copiedTotalWidth total width of the copied widgets * @param copiedTopMostRow top row of the top most copied widget * @param copiedLeftMostColumn left column of the left most copied widget + * @param gridPosition left and top canvas grid position values * @returns */ function* getNewPositionsBasedOnMousePositions( @@ -1376,7 +1378,7 @@ function* getNewPositionsBasedOnMousePositions( copiedTotalWidth: number, copiedTopMostRow: number, copiedLeftMostColumn: number, - gridLocation?: { top: number; left: number }, + gridPosition?: { top: number; left: number }, ) { let { canvasDOM, canvasId, containerWidget } = getDefaultCanvas(canvasWidgets); @@ -1398,8 +1400,8 @@ function* getNewPositionsBasedOnMousePositions( ); // get mouse positions in terms of grid rows and columns of the pasting canvas - const mousePositions = gridLocation - ? gridLocation + const mousePositions = gridPosition + ? gridPosition : getMousePositions(canvasRect, canvasId, snapGrid, padding, mouseLocation); if (!snapGrid || !mousePositions) return {}; @@ -1482,13 +1484,15 @@ function* getNewPositionsBasedOnMousePositions( } /** - * this saga create a new widget from the copied one to store + * This saga create a new widget from the copied one to store. + * It allows using both mouseLocation or gridPosition to locate where the copied widgets should be dropped. + * If gridPosition is available, use it, else, calculate gridPosition from mousePosition */ function* pasteWidgetSaga( action: ReduxAction<{ groupWidgets: boolean; mouseLocation: { x: number; y: number }; - gridLocation?: { top: number; left: number }; + gridPosition?: { top: number; left: number }; }>, ) { const { @@ -1584,7 +1588,7 @@ function* pasteWidgetSaga( copiedTotalWidth, topMostWidget.topRow, leftMostWidget.leftColumn, - action.payload.gridLocation, + action.payload.gridPosition, )); if (canvasId) pastingIntoWidgetId = canvasId; diff --git a/app/client/src/sagas/WidgetOperationUtils.ts b/app/client/src/sagas/WidgetOperationUtils.ts index 79691a4e8e39..00447a476004 100644 --- a/app/client/src/sagas/WidgetOperationUtils.ts +++ b/app/client/src/sagas/WidgetOperationUtils.ts @@ -700,41 +700,6 @@ function getStickyCanvasDOM(canvasId: string) { return stickyCanvasDOM; } -/** - * calculates mouse positions given canvas grid positions - * - * @param canvasRect canvas DOM rect - * @param canvasId Id of the canvas widget - * @param snapGrid grid parameters - * @param padding padding inside of widget - * @param canvasPosition position in canvas rows and columns - * @returns - */ -export function getMousePositionFromCanvasGridPosition( - top: number, - left: number, - snapGrid: { snapRowSpace: number; snapColumnSpace: number }, - padding: number, - canvasId: string, -) { - // Get the canvas element - const stickyCanvasDOM = getStickyCanvasDOM(canvasId); - - if (!stickyCanvasDOM) return { x: 0, y: 0 }; - - const canvasRect = stickyCanvasDOM.getBoundingClientRect(); - - // Calculate actual mouse positions - const x = left * snapGrid.snapColumnSpace + padding; - const y = top * snapGrid.snapRowSpace + padding; - - // Calculate actual mouse positions relative to the window - const actualX = x + canvasRect.left; - const actualY = y + canvasRect.top; - - return { x: actualX, y: actualY }; -} - /** * calculates mouse positions in terms of grid values * From e7f325d52478fb87c6228308f6b4eb6b43e7c866 Mon Sep 17 00:00:00 2001 From: Jacques Ikot Date: Mon, 29 Apr 2024 06:58:40 +0100 Subject: [PATCH 5/8] refactor + update pastWidget action function --- app/client/src/actions/widgetActions.tsx | 11 +- app/client/src/ce/sagas/index.tsx | 4 +- app/client/src/constants/WidgetConstants.tsx | 9 + .../Editor/GlobalHotKeys/GlobalHotKeys.tsx | 7 +- .../src/pages/Editor/widgetSidebar/hooks.ts | 5 +- ...Sagas.ts => BuildingBlockAdditionSagas.ts} | 246 +++++------------- app/client/src/sagas/BuildingBlockSagas.ts | 170 ++++++++++++ .../sagas/CanvasSagas/DraggingCanvasSagas.ts | 61 +---- .../PartialImportSagas.ts | 7 +- app/client/src/sagas/WidgetAdditionSagas.ts | 2 +- app/client/src/sagas/WidgetOperationSagas.tsx | 11 +- 11 files changed, 278 insertions(+), 255 deletions(-) rename app/client/src/sagas/{BuildingBlocksSagas.ts => BuildingBlockAdditionSagas.ts} (58%) create mode 100644 app/client/src/sagas/BuildingBlockSagas.ts diff --git a/app/client/src/actions/widgetActions.tsx b/app/client/src/actions/widgetActions.tsx index 5effb4cbf98c..19ecc0b85955 100644 --- a/app/client/src/actions/widgetActions.tsx +++ b/app/client/src/actions/widgetActions.tsx @@ -8,6 +8,7 @@ import type { BatchAction } from "actions/batchActions"; import { batchAction } from "actions/batchActions"; import type { WidgetProps } from "widgets/BaseWidget"; import type { PartialExportParams } from "sagas/PartialImportExportSagas"; +import type { PasteWidgetReduxAction } from "constants/WidgetConstants"; export const widgetInitialisationSuccess = () => { return { @@ -95,15 +96,17 @@ export const copyWidget = (isShortcut: boolean) => { }; }; -export const pasteWidget = ( +export const pasteWidget = ({ + gridPosition, groupWidgets = false, - mouseLocation: { x: number; y: number }, -) => { + mouseLocation, +}: PasteWidgetReduxAction) => { return { type: ReduxActionTypes.PASTE_COPIED_WIDGET_INIT, payload: { - groupWidgets: groupWidgets, + groupWidgets, mouseLocation, + gridPosition, }, }; }; diff --git a/app/client/src/ce/sagas/index.tsx b/app/client/src/ce/sagas/index.tsx index 1928ed26f9ad..d6dc60910abf 100644 --- a/app/client/src/ce/sagas/index.tsx +++ b/app/client/src/ce/sagas/index.tsx @@ -41,7 +41,7 @@ import saaSPaneSagas from "sagas/SaaSPaneSagas"; import snapshotSagas from "sagas/SnapshotSagas"; import snipingModeSagas from "sagas/SnipingModeSagas"; import templateSagas from "sagas/TemplatesSagas"; -import buildingBlocksSagas from "sagas/BuildingBlocksSagas"; +import buildingBlockSagas from "sagas/BuildingBlockSagas"; import themeSagas from "sagas/ThemeSaga"; import utilSagas from "sagas/UtilSagas"; import websocketSagas from "sagas/WebsocketSagas/WebsocketSagas"; @@ -111,5 +111,5 @@ export const sagas = [ anvilSagas, ternSagas, ideSagas, - buildingBlocksSagas, + buildingBlockSagas, ]; diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx index 89ee7deda6ea..2aa3df5b3556 100644 --- a/app/client/src/constants/WidgetConstants.tsx +++ b/app/client/src/constants/WidgetConstants.tsx @@ -270,3 +270,12 @@ export const DEFAULT_COLUMNS_FOR_EXPLORER_BUILDING_BLOCKS = 62; export const BUILDING_BLOCK_MIN_HORIZONTAL_LIMIT = 2000; export const BUILDING_BLOCK_MIN_VERTICAL_LIMIT = 800; export const BUILDING_BLOCK_EXPLORER_TYPE = "BUILDING_BLOCK"; + +export type PasteWidgetReduxAction = { + groupWidgets: boolean; + mouseLocation?: { x: number; y: number }; + gridPosition?: { top: number; left: number }; +} & ( + | { mouseLocation: { x: number; y: number }; gridPosition?: never } + | { mouseLocation?: never; gridPosition: { top: number; left: number } } +); diff --git a/app/client/src/pages/Editor/GlobalHotKeys/GlobalHotKeys.tsx b/app/client/src/pages/Editor/GlobalHotKeys/GlobalHotKeys.tsx index ce9557b9c937..5f1c5b65dce2 100644 --- a/app/client/src/pages/Editor/GlobalHotKeys/GlobalHotKeys.tsx +++ b/app/client/src/pages/Editor/GlobalHotKeys/GlobalHotKeys.tsx @@ -360,7 +360,12 @@ const mapDispatchToProps = (dispatch: any) => { return { copySelectedWidget: () => dispatch(copyWidget(true)), pasteCopiedWidget: (mouseLocation: { x: number; y: number }) => - dispatch(pasteWidget(false, mouseLocation)), + dispatch( + pasteWidget({ + groupWidgets: false, + mouseLocation, + }), + ), deleteSelectedWidget: () => dispatch(deleteSelectedWidget(true)), cutSelectedWidget: () => dispatch(cutWidget()), groupSelectedWidget: () => dispatch(groupWidgets()), diff --git a/app/client/src/pages/Editor/widgetSidebar/hooks.ts b/app/client/src/pages/Editor/widgetSidebar/hooks.ts index e50627acf191..118e743af100 100644 --- a/app/client/src/pages/Editor/widgetSidebar/hooks.ts +++ b/app/client/src/pages/Editor/widgetSidebar/hooks.ts @@ -17,9 +17,8 @@ import { isFixedLayoutSelector } from "selectors/layoutSystemSelectors"; * @returns Object containing cards, grouped cards and entity loading states. */ export const useUIExplorerItems = () => { - const releaseDragDropBuildingBlocks = useFeatureFlag( - FEATURE_FLAG.release_drag_drop_building_blocks_enabled, - ); + const releaseDragDropBuildingBlocks = true; + useFeatureFlag(FEATURE_FLAG.release_drag_drop_building_blocks_enabled); const isFixedLayout = useSelector(isFixedLayoutSelector); const dispatch = useDispatch(); // check if entities have loaded diff --git a/app/client/src/sagas/BuildingBlocksSagas.ts b/app/client/src/sagas/BuildingBlockAdditionSagas.ts similarity index 58% rename from app/client/src/sagas/BuildingBlocksSagas.ts rename to app/client/src/sagas/BuildingBlockAdditionSagas.ts index 6a259e34f346..bb0b50e2fe24 100644 --- a/app/client/src/sagas/BuildingBlocksSagas.ts +++ b/app/client/src/sagas/BuildingBlockAdditionSagas.ts @@ -1,51 +1,25 @@ -import type { - ImportBuildingBlockToApplicationRequest, - ImportBuildingBlockToApplicationResponse, +import type { FlattenedWidgetProps } from "WidgetProvider/constants"; +import type { WidgetAddChild } from "actions/pageActions"; +import { runAction } from "actions/pluginActionActions"; +import type { ApiResponse } from "api/ApiResponses"; +import type { Template } from "api/TemplatesApi"; +import ApplicationApi, { + type ImportBuildingBlockToApplicationRequest, + type ImportBuildingBlockToApplicationResponse, } from "@appsmith/api/ApplicationApi"; -import ApplicationApi from "@appsmith/api/ApplicationApi"; -import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants"; import { ReduxActionErrorTypes, ReduxActionTypes, WidgetReduxActionTypes, + type ReduxAction, } from "@appsmith/constants/ReduxActionConstants"; import type { ActionDataState } from "@appsmith/reducers/entityReducers/actionsReducer"; -import { - getActions, - getCanvasWidgets, -} from "@appsmith/selectors/entitiesSelector"; +import { getActions } from "@appsmith/selectors/entitiesSelector"; import { getCurrentWorkspaceId } from "@appsmith/selectors/selectedWorkspaceSelectors"; import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; -import { isAirgapped } from "@appsmith/utils/airgapHelpers"; -import { flattenDSL } from "@shared/dsl"; -import type { WidgetProps } from "@shared/dsl/src/migrate/types"; -import type { FlattenedWidgetProps } from "WidgetProvider/constants"; -import type { WidgetAddChild } from "actions/pageActions"; -import { runAction } from "actions/pluginActionActions"; -import { - setCurrentForkingBuildingBlockName, - showStarterBuildingBlockDatasourcePrompt, -} from "actions/templateActions"; -import { pasteWidget } from "actions/widgetActions"; -import { selectWidgetInitAction } from "actions/widgetSelectionActions"; -import type { ApiResponse } from "api/ApiResponses"; -import type { Template } from "api/TemplatesApi"; -import { STARTER_BUILDING_BLOCKS } from "constants/TemplatesConstants"; import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants"; -import type { WidgetLayoutPositionInfo } from "layoutSystems/anvil/utils/layouts/widgetPositionUtils"; -import type { CopiedWidgetData } from "layoutSystems/anvil/utils/paste/types"; -import { getWidgetHierarchy } from "layoutSystems/anvil/utils/paste/utils"; import type { DragDetails } from "reducers/uiReducers/dragResizeReducer"; -import { - all, - call, - delay, - put, - race, - select, - take, - takeEvery, -} from "redux-saga/effects"; +import { put, race, select, take, call } from "redux-saga/effects"; import { getBuildingBlockDragStartTimestamp } from "selectors/buildingBlocksSelectors"; import { getCurrentApplicationId, @@ -54,133 +28,16 @@ import { import { getTemplatesSelector } from "selectors/templatesSelectors"; import { initiateBuildingBlockDropEvent } from "utils/buildingBlockUtils"; import { getCopiedWidgets, saveCopiedWidgets } from "utils/storage"; +import { saveBuildingBlockWidgetsToStore } from "./BuildingBlockSagas"; import { validateResponse } from "./ErrorSagas"; import { postPageAdditionSaga } from "./TemplatesSagas"; import { addChildSaga } from "./WidgetAdditionSagas"; -import { SelectionRequestType } from "./WidgetSelectUtils"; import { getDragDetails, getWidgetByName } from "./selectors"; +import type { WidgetDraggingUpdateParams } from "layoutSystems/common/canvasArenas/ArenaTypes"; +import { addWidgetAndMoveWidgetsSaga } from "./CanvasSagas/DraggingCanvasSagas"; +import { pasteWidget } from "actions/widgetActions"; -const isAirgappedInstance = isAirgapped(); - -export function* saveBuildingBlockWidgetsToStore( - response: ApiResponse, -) { - const buildingBlockDsl = JSON.parse(response.data.widgetDsl); - const buildingBlockWidgets = buildingBlockDsl.children; - const flattenedBlockWidgets = buildingBlockWidgets.map( - (widget: WidgetProps) => flattenDSL(widget), - ); - - const widgetsToPasteInCanvas: CopiedWidgetData[] = yield all( - flattenedBlockWidgets.map((widget: FlattenedWidgetProps, index: number) => { - const widgetPositionInfo: WidgetLayoutPositionInfo | null = null; - return { - hierarchy: getWidgetHierarchy( - buildingBlockWidgets[index].type, - buildingBlockWidgets[index].widgetId, - ), - list: Object.values(widget) - .map((obj) => ({ ...obj })) - .reverse(), - parentId: MAIN_CONTAINER_WIDGET_ID, - widgetId: buildingBlockWidgets[index].widgetId, - widgetPositionInfo, - }; - }), - ); - - yield saveCopiedWidgets( - JSON.stringify({ - widgets: widgetsToPasteInCanvas, - flexLayers: [], - }), - ); -} - -function* apiCallForForkBuildingBlockToApplication(request: { - templateId: string; - activePageId: string; - applicationId: string; - workspaceId: string; - templateName: string; -}) { - try { - const response: ApiResponse = - yield call(ApplicationApi.importBuildingBlockToApplication, { - pageId: request.activePageId, - templateId: request.templateId, - applicationId: request.applicationId, - workspaceId: request.workspaceId, - }); - const isValid: boolean = yield validateResponse(response); - - yield select(getCanvasWidgets); - - if (isValid) { - yield saveBuildingBlockWidgetsToStore(response); - - yield put(pasteWidget(false, { x: 0, y: 0 })); - yield call(postPageAdditionSaga, request.applicationId); - // remove selecting of recently imported widgets - yield put(selectWidgetInitAction(SelectionRequestType.Empty)); - - // run all actions in the building block, if any, to populate the page with data - if ( - response.data.onPageLoadActions && - response.data.onPageLoadActions.length > 0 - ) { - yield all( - response.data.onPageLoadActions.map(function* (action) { - yield put(runAction(action.id)); - }), - ); - } - yield put({ - type: ReduxActionTypes.IMPORT_STARTER_TEMPLATE_TO_APPLICATION_SUCCESS, - }); - - // Show datasource prompt after 3 seconds - yield delay(STARTER_BUILDING_BLOCKS.DATASOURCE_PROMPT_DELAY); - yield put(setCurrentForkingBuildingBlockName(request.templateName)); - yield put(showStarterBuildingBlockDatasourcePrompt(request.activePageId)); - } else { - throw new Error("Failed importing starter building block"); - } - } catch (error) { - throw error; - } -} - -function* forkStarterBuildingBlockToApplicationSaga( - action: ReduxAction<{ - templateId: string; - templateName: string; - }>, -) { - const existingCopiedWidgets: unknown = yield call(getCopiedWidgets); - try { - const activePageId: string = yield select(getCurrentPageId); - const applicationId: string = yield select(getCurrentApplicationId); - const workspaceId: string = yield select(getCurrentWorkspaceId); - - yield call(apiCallForForkBuildingBlockToApplication, { - templateId: action.payload.templateId, - activePageId, - applicationId, - workspaceId, - templateName: action.payload.templateName, - }); - } catch (error) { - yield put({ - type: ReduxActionErrorTypes.IMPORT_STARTER_BUILDING_BLOCK_TO_APPLICATION_ERROR, - }); - } - if (existingCopiedWidgets) { - yield call(saveCopiedWidgets, JSON.stringify(existingCopiedWidgets)); - } -} - -function* addBuildingBlockActionsToApp(dragDetails: DragDetails) { +function* addBuildingBlockActionsToApplication(dragDetails: DragDetails) { const applicationId: string = yield select(getCurrentApplicationId); const buildingblockName = dragDetails.newWidget.displayName; const buildingBlocks: Template[] = yield select(getTemplatesSelector); @@ -230,7 +87,7 @@ function* runNewlyCreatedActions( } } -export function* addBuildingBlockToApplication( +export function* loadBuildingBlocksIntoApplication( buildingBlockWidget: WidgetAddChild, skeletonLoaderId: string, ) { @@ -246,12 +103,12 @@ export function* addBuildingBlockToApplication( getBuildingBlockDragStartTimestamp, ); - // start loading for dragging building blocks + // start loading for dropping building blocks yield put({ type: ReduxActionTypes.DRAGGING_BUILDING_BLOCK_TO_CANVAS_INIT, }); - // makes sure updateAndSaveLayout completes first for skeletonWidget addition + // makes sure updateAndSaveLayout completes first for initial skeletonWidget addition const saveResult: unknown = yield race({ success: take(ReduxActionTypes.SAVE_PAGE_SUCCESS), failure: take(ReduxActionErrorTypes.SAVE_PAGE_ERROR), @@ -262,7 +119,7 @@ export function* addBuildingBlockToApplication( } const response: ApiResponse = - yield call(addBuildingBlockActionsToApp, dragDetails); + yield call(addBuildingBlockActionsToApplication, dragDetails); const isValid: boolean = yield validateResponse(response); if (isValid) { @@ -279,16 +136,15 @@ export function* addBuildingBlockToApplication( }, }); - yield put({ - type: ReduxActionTypes.PASTE_COPIED_WIDGET_INIT, - payload: { + yield put( + pasteWidget({ groupWidgets: false, gridPosition: { top: topRow, left: leftColumn, }, - }, - }); + }), + ); const timeTakenToDropWidgetsInSeconds = (Date.now() - buildingBlockDragStartTimestamp) / 1000; @@ -387,18 +243,54 @@ export function* addBuildingBlockToCanvasSaga( skeletonWidgetName, ); yield call( - addBuildingBlockToApplication, + loadBuildingBlocksIntoApplication, addEntityAction.payload, skeletonWidget.widgetId, ); } -export default function* watchActionSagas() { - if (!isAirgappedInstance) - yield all([ - takeEvery( - ReduxActionTypes.IMPORT_STARTER_BUILDING_BLOCK_TO_APPLICATION_INIT, - forkStarterBuildingBlockToApplicationSaga, - ), - ]); +export function* addAndMoveBuildingBlockToCanvasSaga( + actionPayload: ReduxAction<{ + newWidget: WidgetAddChild; + draggedBlocksToUpdate: WidgetDraggingUpdateParams[]; + canvasId: string; + }>, +) { + const applicationId: string = yield select(getCurrentApplicationId); + const workspaceId: string = yield select(getCurrentApplicationId); + const dragDetails: DragDetails = yield select(getDragDetails); + const buildingblockName = dragDetails.newWidget.displayName; + const skeletonWidgetName = `loading_${buildingblockName + .toLowerCase() + .replace(/ /g, "_")}`; + + yield call(addWidgetAndMoveWidgetsSaga, { + ...actionPayload, + payload: { + ...actionPayload.payload, + // so that the skeleton loader does not get included when the users uses the undo/redo + shouldReplay: false, + newWidget: { + ...actionPayload.payload.newWidget, + type: "SKELETON_WIDGET", + widgetName: skeletonWidgetName, + widgetId: MAIN_CONTAINER_WIDGET_ID, + }, + }, + }); + yield call(initiateBuildingBlockDropEvent, { + applicationId, + workspaceId, + buildingblockName, + }); + + const skeletonWidget: FlattenedWidgetProps = yield select( + getWidgetByName, + skeletonWidgetName, + ); + yield call( + loadBuildingBlocksIntoApplication, + actionPayload.payload.newWidget, + skeletonWidget.widgetId, + ); } diff --git a/app/client/src/sagas/BuildingBlockSagas.ts b/app/client/src/sagas/BuildingBlockSagas.ts new file mode 100644 index 000000000000..812e95c7b5d1 --- /dev/null +++ b/app/client/src/sagas/BuildingBlockSagas.ts @@ -0,0 +1,170 @@ +import type { ImportBuildingBlockToApplicationResponse } from "@appsmith/api/ApplicationApi"; +import ApplicationApi from "@appsmith/api/ApplicationApi"; +import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants"; +import { + ReduxActionErrorTypes, + ReduxActionTypes, +} from "@appsmith/constants/ReduxActionConstants"; +import { getCanvasWidgets } from "@appsmith/selectors/entitiesSelector"; +import { getCurrentWorkspaceId } from "@appsmith/selectors/selectedWorkspaceSelectors"; +import { isAirgapped } from "@appsmith/utils/airgapHelpers"; +import { flattenDSL } from "@shared/dsl"; +import type { WidgetProps } from "@shared/dsl/src/migrate/types"; +import type { FlattenedWidgetProps } from "WidgetProvider/constants"; +import { runAction } from "actions/pluginActionActions"; +import { + setCurrentForkingBuildingBlockName, + showStarterBuildingBlockDatasourcePrompt, +} from "actions/templateActions"; +import { pasteWidget } from "actions/widgetActions"; +import { selectWidgetInitAction } from "actions/widgetSelectionActions"; +import type { ApiResponse } from "api/ApiResponses"; +import { STARTER_BUILDING_BLOCKS } from "constants/TemplatesConstants"; +import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants"; +import type { WidgetLayoutPositionInfo } from "layoutSystems/anvil/utils/layouts/widgetPositionUtils"; +import type { CopiedWidgetData } from "layoutSystems/anvil/utils/paste/types"; +import { getWidgetHierarchy } from "layoutSystems/anvil/utils/paste/utils"; +import { all, call, delay, put, select, takeEvery } from "redux-saga/effects"; +import { + getCurrentApplicationId, + getCurrentPageId, +} from "selectors/editorSelectors"; +import { getCopiedWidgets, saveCopiedWidgets } from "utils/storage"; +import { validateResponse } from "./ErrorSagas"; +import { postPageAdditionSaga } from "./TemplatesSagas"; +import { SelectionRequestType } from "./WidgetSelectUtils"; + +const isAirgappedInstance = isAirgapped(); + +export function* saveBuildingBlockWidgetsToStore( + response: ApiResponse, +) { + const buildingBlockDsl = JSON.parse(response.data.widgetDsl); + const buildingBlockWidgets = buildingBlockDsl.children; + const flattenedBlockWidgets = buildingBlockWidgets.map( + (widget: WidgetProps) => flattenDSL(widget), + ); + + const widgetsToPasteInCanvas: CopiedWidgetData[] = yield all( + flattenedBlockWidgets.map((widget: FlattenedWidgetProps, index: number) => { + const widgetPositionInfo: WidgetLayoutPositionInfo | null = null; + return { + hierarchy: getWidgetHierarchy( + buildingBlockWidgets[index].type, + buildingBlockWidgets[index].widgetId, + ), + list: Object.values(widget) + .map((obj) => ({ ...obj })) + .reverse(), + parentId: MAIN_CONTAINER_WIDGET_ID, + widgetId: buildingBlockWidgets[index].widgetId, + widgetPositionInfo, + }; + }), + ); + + yield saveCopiedWidgets( + JSON.stringify({ + widgets: widgetsToPasteInCanvas, + flexLayers: [], + }), + ); +} + +function* apiCallForForkBuildingBlockToApplication(request: { + templateId: string; + activePageId: string; + applicationId: string; + workspaceId: string; + templateName: string; +}) { + try { + const response: ApiResponse = + yield call(ApplicationApi.importBuildingBlockToApplication, { + pageId: request.activePageId, + templateId: request.templateId, + applicationId: request.applicationId, + workspaceId: request.workspaceId, + }); + const isValid: boolean = yield validateResponse(response); + + yield select(getCanvasWidgets); + + if (isValid) { + yield saveBuildingBlockWidgetsToStore(response); + + yield put( + pasteWidget({ + groupWidgets: false, + mouseLocation: { x: 0, y: 0 }, + }), + ); + yield call(postPageAdditionSaga, request.applicationId); + // remove selecting of recently imported widgets + yield put(selectWidgetInitAction(SelectionRequestType.Empty)); + + // run all actions in the building block, if any, to populate the page with data + if ( + response.data.onPageLoadActions && + response.data.onPageLoadActions.length > 0 + ) { + yield all( + response.data.onPageLoadActions.map(function* (action) { + yield put(runAction(action.id)); + }), + ); + } + yield put({ + type: ReduxActionTypes.IMPORT_STARTER_TEMPLATE_TO_APPLICATION_SUCCESS, + }); + + // Show datasource prompt after 3 seconds + yield delay(STARTER_BUILDING_BLOCKS.DATASOURCE_PROMPT_DELAY); + yield put(setCurrentForkingBuildingBlockName(request.templateName)); + yield put(showStarterBuildingBlockDatasourcePrompt(request.activePageId)); + } else { + throw new Error("Failed importing starter building block"); + } + } catch (error) { + throw error; + } +} + +function* forkStarterBuildingBlockToApplicationSaga( + action: ReduxAction<{ + templateId: string; + templateName: string; + }>, +) { + const existingCopiedWidgets: unknown = yield call(getCopiedWidgets); + try { + const activePageId: string = yield select(getCurrentPageId); + const applicationId: string = yield select(getCurrentApplicationId); + const workspaceId: string = yield select(getCurrentWorkspaceId); + + yield call(apiCallForForkBuildingBlockToApplication, { + templateId: action.payload.templateId, + activePageId, + applicationId, + workspaceId, + templateName: action.payload.templateName, + }); + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.IMPORT_STARTER_BUILDING_BLOCK_TO_APPLICATION_ERROR, + }); + } + if (existingCopiedWidgets) { + yield call(saveCopiedWidgets, JSON.stringify(existingCopiedWidgets)); + } +} + +export default function* watchActionSagas() { + if (!isAirgappedInstance) + yield all([ + takeEvery( + ReduxActionTypes.IMPORT_STARTER_BUILDING_BLOCK_TO_APPLICATION_INIT, + forkStarterBuildingBlockToApplicationSaga, + ), + ]); +} diff --git a/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts b/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts index b4cf8177c33d..55c6bc191630 100644 --- a/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts +++ b/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts @@ -26,31 +26,22 @@ import type { CanvasWidgetsReduxState, FlattenedWidgetProps, } from "reducers/entityReducers/canvasWidgetsReducer"; -import type { DragDetails } from "reducers/uiReducers/dragResizeReducer"; import type { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer"; import { all, call, put, select, takeLatest } from "redux-saga/effects"; -import { addBuildingBlockToApplication } from "sagas/BuildingBlocksSagas"; +import { addAndMoveBuildingBlockToCanvasSaga } from "sagas/BuildingBlockAdditionSagas"; import { getUpdateDslAfterCreatingChild } from "sagas/WidgetAdditionSagas"; import { executeWidgetBlueprintBeforeOperations, traverseTreeAndExecuteBlueprintChildOperations, } from "sagas/WidgetBlueprintSagas"; -import { - getDragDetails, - getWidget, - getWidgetByName, - getWidgets, - getWidgetsMeta, -} from "sagas/selectors"; +import { getWidget, getWidgets, getWidgetsMeta } from "sagas/selectors"; import { getCanvasWidth, - getCurrentApplicationId, getIsAutoLayoutMobileBreakPoint, getMainCanvasProps, getOccupiedSpacesSelectorForContainer, } from "selectors/editorSelectors"; import { getLayoutSystemType } from "selectors/layoutSystemSelectors"; -import { initiateBuildingBlockDropEvent } from "utils/buildingBlockUtils"; import { collisionCheckPostReflow } from "utils/reflowHookUtils"; import type { WidgetProps } from "widgets/BaseWidget"; @@ -150,52 +141,6 @@ const getBottomMostRowAfterMove = ( return widgetBottomRow; }; -function* addBuildingBlockAndMoveWidgetsSaga( - actionPayload: ReduxAction<{ - newWidget: WidgetAddChild; - draggedBlocksToUpdate: WidgetDraggingUpdateParams[]; - canvasId: string; - }>, -) { - const applicationId: string = yield select(getCurrentApplicationId); - const workspaceId: string = yield select(getCurrentApplicationId); - const dragDetails: DragDetails = yield select(getDragDetails); - const buildingblockName = dragDetails.newWidget.displayName; - const skeletonWidgetName = `loading_${buildingblockName - .toLowerCase() - .replace(/ /g, "_")}`; - - yield call(addWidgetAndMoveWidgetsSaga, { - ...actionPayload, - payload: { - ...actionPayload.payload, - // so that the skeleton loader does not get included when the users uses the undo/redo - shouldReplay: false, - newWidget: { - ...actionPayload.payload.newWidget, - type: "SKELETON_WIDGET", - widgetName: skeletonWidgetName, - widgetId: MAIN_CONTAINER_WIDGET_ID, - }, - }, - }); - yield call(initiateBuildingBlockDropEvent, { - applicationId, - workspaceId, - buildingblockName, - }); - - const skeletonWidget: FlattenedWidgetProps = yield select( - getWidgetByName, - skeletonWidgetName, - ); - yield call( - addBuildingBlockToApplication, - actionPayload.payload.newWidget, - skeletonWidget.widgetId, - ); -} - function* addAndMoveUIEntitySaga( actionPayload: ReduxAction<{ newWidget: WidgetAddChild; @@ -204,7 +149,7 @@ function* addAndMoveUIEntitySaga( }>, ) { if (actionPayload.payload.newWidget.type === BUILDING_BLOCK_EXPLORER_TYPE) { - yield call(addBuildingBlockAndMoveWidgetsSaga, actionPayload); + yield call(addAndMoveBuildingBlockToCanvasSaga, actionPayload); } else { yield call(addWidgetAndMoveWidgetsSaga, actionPayload); } diff --git a/app/client/src/sagas/PartialImportExportSagas/PartialImportSagas.ts b/app/client/src/sagas/PartialImportExportSagas/PartialImportSagas.ts index 8ec0096841f0..078fc6553a09 100644 --- a/app/client/src/sagas/PartialImportExportSagas/PartialImportSagas.ts +++ b/app/client/src/sagas/PartialImportExportSagas/PartialImportSagas.ts @@ -48,7 +48,12 @@ function* partialImportWidgetsSaga(file: File) { if ("widgets" in userUploadedJSON && userUploadedJSON.widgets.length > 0) { yield saveCopiedWidgets(userUploadedJSON.widgets); yield put(selectWidgetInitAction(SelectionRequestType.Empty)); - yield put(pasteWidget(false, { x: 0, y: 0 })); + yield put( + pasteWidget({ + groupWidgets: false, + mouseLocation: { x: 0, y: 0 }, + }), + ); } } finally { if (existingCopiedWidgets) { diff --git a/app/client/src/sagas/WidgetAdditionSagas.ts b/app/client/src/sagas/WidgetAdditionSagas.ts index 136b02e10eec..ae51297db2ff 100644 --- a/app/client/src/sagas/WidgetAdditionSagas.ts +++ b/app/client/src/sagas/WidgetAdditionSagas.ts @@ -44,7 +44,6 @@ import { generateWidgetProps } from "utils/WidgetPropsUtils"; import { generateReactKey } from "utils/generators"; import type { WidgetProps } from "widgets/BaseWidget"; import { isStack } from "../layoutSystems/autolayout/utils/AutoLayoutUtils"; -import { addBuildingBlockToCanvasSaga } from "./BuildingBlocksSagas"; import { buildWidgetBlueprint, executeWidgetBlueprintBeforeOperations, @@ -53,6 +52,7 @@ import { } from "./WidgetBlueprintSagas"; import { getPropertiesToUpdate } from "./WidgetOperationSagas"; import { getWidget, getWidgets } from "./selectors"; +import { addBuildingBlockToCanvasSaga } from "./BuildingBlockAdditionSagas"; const WidgetTypes = WidgetFactory.widgetTypes; diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index bc52bc1c24d2..66df99c2fcae 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -9,6 +9,7 @@ import { } from "@appsmith/constants/ReduxActionConstants"; import { resetWidgetMetaProperty } from "actions/metaActions"; import { selectWidgetInitAction } from "actions/widgetSelectionActions"; +import type { PasteWidgetReduxAction } from "constants/WidgetConstants"; import { GridDefaults, MAIN_CONTAINER_WIDGET_ID, @@ -1488,13 +1489,7 @@ function* getNewPositionsBasedOnMousePositions( * It allows using both mouseLocation or gridPosition to locate where the copied widgets should be dropped. * If gridPosition is available, use it, else, calculate gridPosition from mousePosition */ -function* pasteWidgetSaga( - action: ReduxAction<{ - groupWidgets: boolean; - mouseLocation: { x: number; y: number }; - gridPosition?: { top: number; left: number }; - }>, -) { +function* pasteWidgetSaga(action: ReduxAction) { const { flexLayers, widgets: copiedWidgets, @@ -1584,7 +1579,7 @@ function* pasteWidgetSaga( yield call( getNewPositions, copiedWidgetGroups, - action.payload.mouseLocation, + action.payload.mouseLocation as { x: number; y: number }, copiedTotalWidth, topMostWidget.topRow, leftMostWidget.leftColumn, From 3b6c29dca32544cb38015cb80700566925fcf8e5 Mon Sep 17 00:00:00 2001 From: Jacques Ikot Date: Mon, 29 Apr 2024 06:59:16 +0100 Subject: [PATCH 6/8] remove fixed var --- app/client/src/pages/Editor/widgetSidebar/hooks.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/client/src/pages/Editor/widgetSidebar/hooks.ts b/app/client/src/pages/Editor/widgetSidebar/hooks.ts index 118e743af100..e50627acf191 100644 --- a/app/client/src/pages/Editor/widgetSidebar/hooks.ts +++ b/app/client/src/pages/Editor/widgetSidebar/hooks.ts @@ -17,8 +17,9 @@ import { isFixedLayoutSelector } from "selectors/layoutSystemSelectors"; * @returns Object containing cards, grouped cards and entity loading states. */ export const useUIExplorerItems = () => { - const releaseDragDropBuildingBlocks = true; - useFeatureFlag(FEATURE_FLAG.release_drag_drop_building_blocks_enabled); + const releaseDragDropBuildingBlocks = useFeatureFlag( + FEATURE_FLAG.release_drag_drop_building_blocks_enabled, + ); const isFixedLayout = useSelector(isFixedLayoutSelector); const dispatch = useDispatch(); // check if entities have loaded From 76657272322d81b82f178add21c5135e95e8837c Mon Sep 17 00:00:00 2001 From: Rahul Barwal Date: Mon, 29 Apr 2024 11:58:36 +0530 Subject: [PATCH 7/8] Refactor PasteWidgetReduxAction in WidgetConstants.tsx --- app/client/src/constants/WidgetConstants.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx index 2aa3df5b3556..234c7fd5309b 100644 --- a/app/client/src/constants/WidgetConstants.tsx +++ b/app/client/src/constants/WidgetConstants.tsx @@ -273,8 +273,6 @@ export const BUILDING_BLOCK_EXPLORER_TYPE = "BUILDING_BLOCK"; export type PasteWidgetReduxAction = { groupWidgets: boolean; - mouseLocation?: { x: number; y: number }; - gridPosition?: { top: number; left: number }; } & ( | { mouseLocation: { x: number; y: number }; gridPosition?: never } | { mouseLocation?: never; gridPosition: { top: number; left: number } } From 6f425a394a7349b6461576b3762b0c152eed5659 Mon Sep 17 00:00:00 2001 From: Rahul Barwal Date: Mon, 29 Apr 2024 12:12:07 +0530 Subject: [PATCH 8/8] Refactor PasteWidgetReduxAction and WidgetOperationSagas --- app/client/src/constants/WidgetConstants.tsx | 9 +++--- app/client/src/sagas/WidgetOperationSagas.tsx | 29 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx index 234c7fd5309b..9c7525118107 100644 --- a/app/client/src/constants/WidgetConstants.tsx +++ b/app/client/src/constants/WidgetConstants.tsx @@ -271,9 +271,10 @@ export const BUILDING_BLOCK_MIN_HORIZONTAL_LIMIT = 2000; export const BUILDING_BLOCK_MIN_VERTICAL_LIMIT = 800; export const BUILDING_BLOCK_EXPLORER_TYPE = "BUILDING_BLOCK"; +export type EitherMouseLocationORGridPosition = + | { mouseLocation: { x: number; y: number }; gridPosition?: never } + | { mouseLocation?: never; gridPosition: { top: number; left: number } }; + export type PasteWidgetReduxAction = { groupWidgets: boolean; -} & ( - | { mouseLocation: { x: number; y: number }; gridPosition?: never } - | { mouseLocation?: never; gridPosition: { top: number; left: number } } -); +} & EitherMouseLocationORGridPosition; diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index 66df99c2fcae..9b19df2531eb 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -9,7 +9,10 @@ import { } from "@appsmith/constants/ReduxActionConstants"; import { resetWidgetMetaProperty } from "actions/metaActions"; import { selectWidgetInitAction } from "actions/widgetSelectionActions"; -import type { PasteWidgetReduxAction } from "constants/WidgetConstants"; +import type { + EitherMouseLocationORGridPosition, + PasteWidgetReduxAction, +} from "constants/WidgetConstants"; import { GridDefaults, MAIN_CONTAINER_WIDGET_ID, @@ -1168,11 +1171,10 @@ export function calculateNewWidgetPosition( */ const getNewPositions = function* ( copiedWidgetGroups: CopiedWidgetGroup[], - mouseLocation: { x: number; y: number }, copiedTotalWidth: number, copiedTopMostRow: number, copiedLeftMostColumn: number, - gridPosition?: { top: number; left: number }, + whereToPasteWidget: EitherMouseLocationORGridPosition, ) { const selectedWidgetIDs: string[] = yield select(getSelectedWidgets); const canvasWidgets: CanvasWidgetsReduxState = yield select(getWidgets); @@ -1218,13 +1220,12 @@ const getNewPositions = function* ( const newPastingPositionDetails: NewPastePositionVariables = yield call( getNewPositionsBasedOnMousePositions, copiedWidgetGroups, - mouseLocation, selectedWidgets, canvasWidgets, copiedTotalWidth, copiedTopMostRow, copiedLeftMostColumn, - gridPosition, + whereToPasteWidget, ); return newPastingPositionDetails; }; @@ -1373,13 +1374,12 @@ function* getNewPositionsBasedOnSelectedWidgets( */ function* getNewPositionsBasedOnMousePositions( copiedWidgetGroups: CopiedWidgetGroup[], - mouseLocation: { x: number; y: number }, selectedWidgets: WidgetProps[], canvasWidgets: CanvasWidgetsReduxState, copiedTotalWidth: number, copiedTopMostRow: number, copiedLeftMostColumn: number, - gridPosition?: { top: number; left: number }, + whereToPasteWidget: EitherMouseLocationORGridPosition, ) { let { canvasDOM, canvasId, containerWidget } = getDefaultCanvas(canvasWidgets); @@ -1401,9 +1401,15 @@ function* getNewPositionsBasedOnMousePositions( ); // get mouse positions in terms of grid rows and columns of the pasting canvas - const mousePositions = gridPosition - ? gridPosition - : getMousePositions(canvasRect, canvasId, snapGrid, padding, mouseLocation); + const mousePositions = whereToPasteWidget.gridPosition + ? whereToPasteWidget.gridPosition + : getMousePositions( + canvasRect, + canvasId, + snapGrid, + padding, + whereToPasteWidget.mouseLocation, + ); if (!snapGrid || !mousePositions) return {}; @@ -1579,11 +1585,10 @@ function* pasteWidgetSaga(action: ReduxAction) { yield call( getNewPositions, copiedWidgetGroups, - action.payload.mouseLocation as { x: number; y: number }, copiedTotalWidth, topMostWidget.topRow, leftMostWidget.leftColumn, - action.payload.gridPosition, + action.payload, )); if (canvasId) pastingIntoWidgetId = canvasId;