From 78f269bcc8a29888f10e8d0f8ecb2a9b241c8ea4 Mon Sep 17 00:00:00 2001 From: Jacques Ikot Date: Fri, 8 Mar 2024 15:29:29 +0100 Subject: [PATCH] feat: drag and drop building block on canvas with skeleton loader (#31406) ## Description **Goal** To allow users drag and drop building blocks on the canvas just like widgets today. We are also implementing a loading state with the platform skeleton component immediately the user drops the block on the canvas. **Why** The dropping of building blocks needs to make an API call to the backend to process adding the building block functionality to the users app. This loading skeleton acts as a placeholder for the block while the API call is processing. **How** 1. We had listed the building blocks in a previous [PR](https://github.com/appsmithorg/appsmith/pull/31199) 2. Created a new saga to handle widget and building block addition called `addUIEntitySaga` 3. The saga handles widget addition like it did previously, but also handles building block addition to canvas. #### PR fixes following issue(s) Fixes #31314 #### Media #### Type of change > Please delete options that are not relevant. - 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 - [ ] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] 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 - [ ] 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 ## Summary by CodeRabbit - **New Features** - Introduced drag-and-drop functionality for building blocks within the canvas editor. - Added a new feature flag to enable/disable drag-and-drop building blocks. - Implemented new UI components such as `SeeMoreButton`, `UIEntityList`, and `UIEntitySidebar` for enhanced widget management and exploration. - Enhanced widget sidebar with improved search and filter capabilities. - **Enhancements** - Updated various components and sagas to support the new drag-and-drop functionality and building blocks management. - Improved loading and organization of UI explorer items, including widgets and building blocks. - **Refactor** - Adjusted import paths and reorganized imports across multiple files for better code maintenance. - Consolidated widget addition logic under a unified saga for handling different UI entity types. - **Bug Fixes** - Fixed the directory structure for the `useIsEditorPaneSegmentsEnabled` hook to reflect recent changes. --- app/client/src/ce/api/ApplicationApi.tsx | 13 ++++++ .../src/ce/constants/ReduxActionConstants.tsx | 3 ++ app/client/src/constants/WidgetConstants.tsx | 1 + app/client/src/sagas/WidgetAdditionSagas.ts | 41 +++++++++++++++++-- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/app/client/src/ce/api/ApplicationApi.tsx b/app/client/src/ce/api/ApplicationApi.tsx index 777ddaee27..ef1f72ce58 100644 --- a/app/client/src/ce/api/ApplicationApi.tsx +++ b/app/client/src/ce/api/ApplicationApi.tsx @@ -260,6 +260,13 @@ export interface ImportPartialApplicationRequest { pageId: string; } +export interface ImportBuildingBlockToApplicationRequest { + pageId: string; + applicationId: string; + workspaceId: string; + templateId: string; +} + export class ApplicationApi extends Api { static baseURL = "v1/applications"; static publishURLPath = (applicationId: string) => @@ -479,6 +486,12 @@ export class ApplicationApi extends Api { }, ); } + + static async importBuildingBlockToApplication( + request: ImportBuildingBlockToApplicationRequest, + ) { + return Api.post(`${ApplicationApi.baseURL}/import/partial/block`, request); + } } export default ApplicationApi; diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index a626d4265b..0521c7a384 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -698,6 +698,9 @@ const ActionTypes = { RESET_TEMPLATE_FILTERS: "RESET_TEMPLATE_FILTERS", SET_TEMPLATE_SEARCH_QUERY: "SET_TEMPLATE_SEARCH_QUERY", IMPORT_TEMPLATE_TO_APPLICATION_INIT: "IMPORT_TEMPLATE_TO_APPLICATION_INIT", + DRAG_BUILDING_BLOCK_TO_CANVAS_INIT: "DRAG_BUILDING_BLOCK_TO_CANVAS_INIT", + DRAG_BUILDING_BLOCK_TO_CANVAS_SUCCESS: + "DRAG_BUILDING_BLOCK_TO_CANVAS_SUCCESS", IMPORT_STARTER_BUILDING_BLOCK_TO_APPLICATION_INIT: "IMPORT_STARTER_BUILDING_BLOCK_TO_APPLICATION_INIT", IMPORT_STARTER_TEMPLATE_TO_APPLICATION_SUCCESS: diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx index cbc53c3d0f..2a294be3bf 100644 --- a/app/client/src/constants/WidgetConstants.tsx +++ b/app/client/src/constants/WidgetConstants.tsx @@ -267,3 +267,4 @@ export const WIDGET_ID_SHOW_WALKTHROUGH = "WIDGET_ID_SHOW_WALKTHROUGH"; export const DEFAULT_ROWS_FOR_EXPLORER_BUILDING_BLOCKS = 30; export const DEFAULT_COLUMNS_FOR_EXPLORER_BUILDING_BLOCKS = 5; +export const BUILDING_BLOCK_EXPLORER_TYPE = "BUILDING_BLOCK"; diff --git a/app/client/src/sagas/WidgetAdditionSagas.ts b/app/client/src/sagas/WidgetAdditionSagas.ts index fec9e8ba16..5a4ca0584a 100644 --- a/app/client/src/sagas/WidgetAdditionSagas.ts +++ b/app/client/src/sagas/WidgetAdditionSagas.ts @@ -6,7 +6,10 @@ import { ReduxActionTypes, WidgetReduxActionTypes, } from "@appsmith/constants/ReduxActionConstants"; -import { RenderModes } from "constants/WidgetConstants"; +import { + BUILDING_BLOCK_EXPLORER_TYPE, + RenderModes, +} from "constants/WidgetConstants"; import { ENTITY_TYPE } from "@appsmith/entities/AppsmithConsole/utils"; import type { CanvasWidgetsReduxState, @@ -17,7 +20,7 @@ import { all, call, put, select, takeEvery } from "redux-saga/effects"; import AppsmithConsole from "utils/AppsmithConsole"; import { getNextEntityName } from "utils/AppsmithUtils"; import { generateWidgetProps } from "utils/WidgetPropsUtils"; -import { getWidget, getWidgets } from "./selectors"; +import { getDragDetails, getWidget, getWidgets } from "./selectors"; import { buildWidgetBlueprint, executeWidgetBlueprintBeforeOperations, @@ -49,6 +52,7 @@ import { } from "selectors/editorSelectors"; import { getWidgetMinMaxDimensionsInPixel } from "layoutSystems/autolayout/utils/flexWidgetUtils"; import { isFunction } from "lodash"; +import type { DragDetails } from "reducers/uiReducers/dragResizeReducer"; const WidgetTypes = WidgetFactory.widgetTypes; @@ -476,9 +480,40 @@ function* addNewTabChildSaga( yield put(updateAndSaveLayout(updatedWidgets)); } +function* addUIEntitySaga(addEntityAction: ReduxAction) { + try { + if (addEntityAction.payload.type === BUILDING_BLOCK_EXPLORER_TYPE) { + const dragDetails: DragDetails = yield select(getDragDetails); + const buildingblockName = dragDetails.newWidget.displayName; + const skeletonWidgetName = `loading_${buildingblockName + .toLowerCase() + .replace(/ /g, "_")}`; + const addSkeletonWidgetAction: ReduxAction = { + ...addEntityAction, + payload: { + ...addEntityAction.payload, + type: "SKELETON_WIDGET", + widgetName: skeletonWidgetName, + }, + }; + yield call(addChildSaga, addSkeletonWidgetAction); + } else { + yield call(addChildSaga, addEntityAction); + } + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR, + payload: { + action: WidgetReduxActionTypes.WIDGET_ADD_CHILD, + error, + }, + }); + } +} + export default function* widgetAdditionSagas() { yield all([ - takeEvery(WidgetReduxActionTypes.WIDGET_ADD_CHILD, addChildSaga), + takeEvery(WidgetReduxActionTypes.WIDGET_ADD_CHILD, addUIEntitySaga), takeEvery(ReduxActionTypes.WIDGET_ADD_NEW_TAB_CHILD, addNewTabChildSaga), ]); }