chore: Fix layout issues for UI package editor (#40349)

## Description
🏗 Architecture
- Moved mainCanvasReducer from Community Edition (CE) to Enterprise
Edition (EE) structure
- Added proper export structure in EE to maintain backward compatibility
- Added RESET_EDITOR_REQUEST handler to reset canvas state when needed
- 
🐛 Bug Fixes
- Fixed widget tag grouping in groupWidgetCardsByTags to properly handle
tags that aren't predefined
- Implemented support for overrideRenderMode in both
LayoutSystemBasedCanvas and withWidgetProps
- Added emptyMessage prop in UIEntitySidebar for improved user
experience

PR for https://github.com/appsmithorg/appsmith-ee/pull/7245

## 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/14610885832>
> Commit: 54916f79b3009451298496513e0a3a8ee7bccdac
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14610885832&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Wed, 23 Apr 2025 07:02:40 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


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

- **New Features**
- Added support for customizing the empty message in the widget sidebar
when no widgets are found.
- Introduced an option to override the widget render mode in both the
canvas and widget components, enabling more flexible widget behavior in
the editor.

- **Bug Fixes**
- Improved grouping of widget cards by tags to prevent potential errors
when encountering new tags.

- **Refactor**
- Updated internal reducer structure and export patterns for improved
maintainability.
- Adjusted import paths for better modularization and separation between
core and enterprise code.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Ashit Rath 2025-04-26 20:45:55 +05:30 committed by GitHub
parent 4fbac4d68f
commit 76b1ff1406
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 45 additions and 17 deletions

View File

@ -48,7 +48,7 @@ import type { CrudInfoModalReduxState } from "reducers/uiReducers/crudInfoModalR
import type { FormEvaluationState } from "reducers/evaluationReducers/formEvaluationReducer"; import type { FormEvaluationState } from "reducers/evaluationReducers/formEvaluationReducer";
import type { widgetReflow } from "reducers/uiReducers/reflowReducer"; import type { widgetReflow } from "reducers/uiReducers/reflowReducer";
import type { AppThemingState } from "reducers/uiReducers/appThemingReducer"; import type { AppThemingState } from "reducers/uiReducers/appThemingReducer";
import type { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer"; import type { MainCanvasReduxState } from "ee/reducers/uiReducers/mainCanvasReducer";
import type { SettingsReduxState } from "ee/reducers/settingsReducer"; import type { SettingsReduxState } from "ee/reducers/settingsReducer";
import SettingsReducer from "ee/reducers/settingsReducer"; import SettingsReducer from "ee/reducers/settingsReducer";
import type { TriggerValuesEvaluationState } from "reducers/evaluationReducers/triggerReducer"; import type { TriggerValuesEvaluationState } from "reducers/evaluationReducers/triggerReducer";

View File

@ -33,7 +33,7 @@ import crudInfoModalReducer from "reducers/uiReducers/crudInfoModalReducer";
import { widgetReflowReducer } from "reducers/uiReducers/reflowReducer"; import { widgetReflowReducer } from "reducers/uiReducers/reflowReducer";
import jsObjectNameReducer from "reducers/uiReducers/jsObjectNameReducer"; import jsObjectNameReducer from "reducers/uiReducers/jsObjectNameReducer";
import appThemingReducer from "reducers/uiReducers/appThemingReducer"; import appThemingReducer from "reducers/uiReducers/appThemingReducer";
import mainCanvasReducer from "reducers/uiReducers/mainCanvasReducer"; import mainCanvasReducer from "ee/reducers/uiReducers/mainCanvasReducer";
import focusHistoryReducer from "reducers/uiReducers/focusHistoryReducer"; import focusHistoryReducer from "reducers/uiReducers/focusHistoryReducer";
import { editorContextReducer } from "ee/reducers/uiReducers/editorContextReducer"; import { editorContextReducer } from "ee/reducers/uiReducers/editorContextReducer";
import libraryReducer from "reducers/uiReducers/libraryReducer"; import libraryReducer from "reducers/uiReducers/libraryReducer";

View File

@ -7,15 +7,16 @@ import {
} from "constants/WidgetConstants"; } from "constants/WidgetConstants";
import type { UpdateCanvasLayoutPayload } from "actions/controlActions"; import type { UpdateCanvasLayoutPayload } from "actions/controlActions";
import type { UpdateCanvasPayload } from "actions/pageActions"; import type { UpdateCanvasPayload } from "actions/pageActions";
import { klona } from "klona";
const initialState: MainCanvasReduxState = { export const initialState: MainCanvasReduxState = {
initialized: false, initialized: false,
width: 0, width: 0,
height: 0, height: 0,
isMobile: false, isMobile: false,
}; };
const mainCanvasReducer = createImmerReducer(initialState, { export const handlers = {
[ReduxActionTypes.INIT_CANVAS_LAYOUT]: ( [ReduxActionTypes.INIT_CANVAS_LAYOUT]: (
state: MainCanvasReduxState, state: MainCanvasReduxState,
action: ReduxAction<UpdateCanvasPayload>, action: ReduxAction<UpdateCanvasPayload>,
@ -38,7 +39,12 @@ const mainCanvasReducer = createImmerReducer(initialState, {
state.isMobile = state.isMobile =
action.payload.width <= layoutConfigurations.MOBILE.maxWidth; action.payload.width <= layoutConfigurations.MOBILE.maxWidth;
}, },
}); [ReduxActionTypes.RESET_EDITOR_REQUEST]: () => {
return klona(initialState);
},
};
const mainCanvasReducer = createImmerReducer(initialState, handlers);
export interface MainCanvasReduxState { export interface MainCanvasReduxState {
initialized: boolean; initialized: boolean;

View File

@ -126,7 +126,7 @@ import { getPageList } from "ee/selectors/entitiesSelector";
import { setPreviewModeAction } from "actions/editorActions"; import { setPreviewModeAction } from "actions/editorActions";
import { SelectionRequestType } from "sagas/WidgetSelectUtils"; import { SelectionRequestType } from "sagas/WidgetSelectUtils";
import { toast } from "@appsmith/ads"; import { toast } from "@appsmith/ads";
import type { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer"; import type { MainCanvasReduxState } from "ee/reducers/uiReducers/mainCanvasReducer";
import { UserCancelledActionExecutionError } from "sagas/ActionExecution/errorUtils"; import { UserCancelledActionExecutionError } from "sagas/ActionExecution/errorUtils";
import { getInstanceId } from "ee/selectors/organizationSelectors"; import { getInstanceId } from "ee/selectors/organizationSelectors";
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants"; import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";

View File

@ -0,0 +1,3 @@
export * from "ce/reducers/uiReducers/mainCanvasReducer";
import { default as CE_mainCanvasReducer } from "ce/reducers/uiReducers/mainCanvasReducer";
export default CE_mainCanvasReducer;

View File

@ -16,9 +16,19 @@ import { getAppThemeSettings } from "ee/selectors/applicationSelectors";
*/ */
const LayoutSystemBasedCanvas = memo((props: WidgetProps) => { const LayoutSystemBasedCanvas = memo((props: WidgetProps) => {
const renderMode = useSelector(getRenderMode); let renderMode = useSelector(getRenderMode);
const themeSetting = useSelector(getAppThemeSettings); const themeSetting = useSelector(getAppThemeSettings);
// This is primarily used by UI modules in app editor where it wants to load all the underlying
// widgets in page mode as they are not editable and mimics the behavior of view mode.
// Since in app's edit mode the default render mode is canvas and due to this some widgets do not behave
// properly.
// Ideally the renderMode from props should be used instead of the one from the selector but that needs
// to be handled properly as it needs a bigger change and more testing.
if (props.overrideRenderMode) {
renderMode = props.overrideRenderMode;
}
const layoutSystemType = useSelector(getLayoutSystemType); const layoutSystemType = useSelector(getLayoutSystemType);
const { canvasSystem } = useMemo( const { canvasSystem } = useMemo(
() => getLayoutSystem(renderMode, layoutSystemType), () => getLayoutSystem(renderMode, layoutSystemType),

View File

@ -5,6 +5,7 @@ import UIEntitySidebar from "pages/Editor/widgetSidebar/UIEntitySidebar";
import { import {
createMessage, createMessage,
UI_ELEMENT_PANEL_SEARCH_TEXT, UI_ELEMENT_PANEL_SEARCH_TEXT,
WIDGET_PANEL_EMPTY_MESSAGE,
} from "ee/constants/messages"; } from "ee/constants/messages";
interface WidgetsListProps { interface WidgetsListProps {
@ -17,6 +18,7 @@ function WidgetsList({ focusSearchInput }: WidgetsListProps) {
return ( return (
<UIEntitySidebar <UIEntitySidebar
cards={cards} cards={cards}
emptyMessage={createMessage(WIDGET_PANEL_EMPTY_MESSAGE)}
entityLoading={entityLoading} entityLoading={entityLoading}
focusSearchInput={focusSearchInput} focusSearchInput={focusSearchInput}
groupedCards={groupedCards} groupedCards={groupedCards}

View File

@ -328,6 +328,8 @@ export const groupWidgetCardsByTags = (widgetCards: WidgetCardProps[]) => {
item.tags.forEach((tag) => { item.tags.forEach((tag) => {
if (groupedCards[tag]) { if (groupedCards[tag]) {
groupedCards[tag].push(item); groupedCards[tag].push(item);
} else {
groupedCards[tag] = [item];
} }
}); });
} }

View File

@ -1,7 +1,3 @@
import {
WIDGET_PANEL_EMPTY_MESSAGE,
createMessage,
} from "ee/constants/messages";
import AnalyticsUtil from "ee/utils/AnalyticsUtil"; import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import { ENTITY_EXPLORER_SEARCH_ID } from "constants/Explorer"; import { ENTITY_EXPLORER_SEARCH_ID } from "constants/Explorer";
import type { import type {
@ -23,10 +19,12 @@ interface UIEntitySidebarProps {
entityLoading?: Partial<Record<WidgetTags, boolean>>; entityLoading?: Partial<Record<WidgetTags, boolean>>;
groupedCards: WidgetCardsGroupedByTags; groupedCards: WidgetCardsGroupedByTags;
searchPlaceholderText?: string; searchPlaceholderText?: string;
emptyMessage?: string;
} }
function UIEntitySidebar({ function UIEntitySidebar({
cards, cards,
emptyMessage,
entityLoading, entityLoading,
focusSearchInput, focusSearchInput,
groupedCards, groupedCards,
@ -133,8 +131,7 @@ function UIEntitySidebar({
renderAs="p" renderAs="p"
style={{ marginBottom: "15px" }} style={{ marginBottom: "15px" }}
> >
{createMessage(WIDGET_PANEL_EMPTY_MESSAGE)} ` {emptyMessage} `{searchInputRef.current?.value}`
{searchInputRef.current?.value}`
</Text> </Text>
)} )}
<div> <div>

View File

@ -38,7 +38,7 @@ import {
getIsAutoLayoutMobileBreakPoint, getIsAutoLayoutMobileBreakPoint,
getMainCanvasProps, getMainCanvasProps,
} from "selectors/editorSelectors"; } from "selectors/editorSelectors";
import type { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer"; import type { MainCanvasReduxState } from "ee/reducers/uiReducers/mainCanvasReducer";
import { updateLayoutForMobileBreakpointAction } from "actions/autoLayoutActions"; import { updateLayoutForMobileBreakpointAction } from "actions/autoLayoutActions";
import convertDSLtoAuto from "layoutSystems/common/DSLConversions/fixedToAutoLayout"; import convertDSLtoAuto from "layoutSystems/common/DSLConversions/fixedToAutoLayout";
import { convertNormalizedDSLToFixed } from "layoutSystems/common/DSLConversions/autoToFixedLayout"; import { convertNormalizedDSLToFixed } from "layoutSystems/common/DSLConversions/autoToFixedLayout";

View File

@ -26,7 +26,7 @@ import type {
CanvasWidgetsReduxState, CanvasWidgetsReduxState,
FlattenedWidgetProps, FlattenedWidgetProps,
} from "ee/reducers/entityReducers/canvasWidgetsReducer"; } from "ee/reducers/entityReducers/canvasWidgetsReducer";
import type { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer"; import type { MainCanvasReduxState } from "ee/reducers/uiReducers/mainCanvasReducer";
import { all, call, put, select, takeLatest } from "redux-saga/effects"; import { all, call, put, select, takeLatest } from "redux-saga/effects";
import { addAndMoveBuildingBlockToCanvasSaga } from "sagas/BuildingBlockSagas/BuildingBlockAdditionSagas"; import { addAndMoveBuildingBlockToCanvasSaga } from "sagas/BuildingBlockSagas/BuildingBlockAdditionSagas";
import { getUpdateDslAfterCreatingChild } from "sagas/WidgetAdditionSagas"; import { getUpdateDslAfterCreatingChild } from "sagas/WidgetAdditionSagas";

View File

@ -25,7 +25,7 @@ import {
getLoadingEntities, getLoadingEntities,
getConfigTree, getConfigTree,
} from "selectors/dataTreeSelectors"; } from "selectors/dataTreeSelectors";
import type { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer"; import type { MainCanvasReduxState } from "ee/reducers/uiReducers/mainCanvasReducer";
import { getActionEditorSavingMap } from "PluginActionEditor/store"; import { getActionEditorSavingMap } from "PluginActionEditor/store";
import { import {

View File

@ -82,7 +82,7 @@ function withWidgetProps(WrappedWidget: typeof BaseWidget) {
getMainCanvasProps(state), getMainCanvasProps(state),
); );
const googleMapsApiKey = useSelector(getGoogleMapsApiKey); const googleMapsApiKey = useSelector(getGoogleMapsApiKey);
const renderMode = useSelector(getRenderMode); let renderMode = useSelector(getRenderMode);
const widgetName = canvasWidget?.widgetName || metaWidget?.widgetName; const widgetName = canvasWidget?.widgetName || metaWidget?.widgetName;
@ -155,6 +155,14 @@ function withWidgetProps(WrappedWidget: typeof BaseWidget) {
const widget = metaWidget || canvasWidget; const widget = metaWidget || canvasWidget;
// This property is primarily used by UI modules in app editor where it wants to load all the underlying
// widgets in page mode as they are not editable and mimics the behavior of view mode.
// Since in app's edit mode the default render mode is canvas and due to this some widgets do not behave
// properly.
if (widget?.overrideRenderMode) {
renderMode = widget.overrideRenderMode;
}
if (!skipWidgetPropsHydration) { if (!skipWidgetPropsHydration) {
const canvasWidgetProps = (() => { const canvasWidgetProps = (() => {
if (widgetId === MAIN_CONTAINER_WIDGET_ID) { if (widgetId === MAIN_CONTAINER_WIDGET_ID) {