diff --git a/app/client/src/actions/controlActions.tsx b/app/client/src/actions/controlActions.tsx index 567438dc51..d04a9410cc 100644 --- a/app/client/src/actions/controlActions.tsx +++ b/app/client/src/actions/controlActions.tsx @@ -79,6 +79,7 @@ export interface UpdateWidgetPropertyPayload { export interface UpdateCanvasLayout { width: number; + height: number; } export interface SetWidgetDynamicPropertyPayload { diff --git a/app/client/src/actions/pageActions.tsx b/app/client/src/actions/pageActions.tsx index e080fe9602..1d4db35c69 100644 --- a/app/client/src/actions/pageActions.tsx +++ b/app/client/src/actions/pageActions.tsx @@ -1,4 +1,4 @@ -import { FetchPageRequest, SavePageResponse } from "api/PageApi"; +import { FetchPageRequest, PageLayout, SavePageResponse } from "api/PageApi"; import { WidgetOperation } from "widgets/BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import { @@ -113,7 +113,11 @@ export const saveLayout = () => { }; }; -export const createPage = (applicationId: string, pageName: string) => { +export const createPage = ( + applicationId: string, + pageName: string, + layouts: Partial[], +) => { AnalyticsUtil.logEvent("CREATE_PAGE", { pageName, }); @@ -122,6 +126,7 @@ export const createPage = (applicationId: string, pageName: string) => { payload: { applicationId, name: pageName, + layouts, }, }; }; diff --git a/app/client/src/api/PageApi.tsx b/app/client/src/api/PageApi.tsx index c39051b9a3..245e5bedcb 100644 --- a/app/client/src/api/PageApi.tsx +++ b/app/client/src/api/PageApi.tsx @@ -61,6 +61,7 @@ export interface SavePageResponse extends ApiResponse { export interface CreatePageRequest { applicationId: string; name: string; + layouts: Partial[]; } export interface UpdatePageRequest { diff --git a/app/client/src/entities/Widget/utils.ts b/app/client/src/entities/Widget/utils.ts index 6502ebf3e4..ce23c7e529 100644 --- a/app/client/src/entities/Widget/utils.ts +++ b/app/client/src/entities/Widget/utils.ts @@ -1,6 +1,7 @@ import { WidgetProps } from "widgets/BaseWidget"; import { PropertyPaneConfig } from "constants/PropertyControlConstants"; import { get } from "lodash"; +import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; export const getAllPathsFromPropertyConfig = ( widget: WidgetProps, @@ -113,3 +114,18 @@ export const getAllPathsFromPropertyConfig = ( return { bindingPaths, triggerPaths }; }; + +export const nextAvailableRowInContainer = ( + parenContainertId: string, + canvasWidgets: { [widgetId: string]: FlattenedWidgetProps }, +) => { + return ( + Object.values(canvasWidgets).reduce( + (prev: number, next: any) => + next?.parentId === parenContainertId && next.bottomRow > prev + ? next.bottomRow + : prev, + 0, + ) + 1 + ); +}; diff --git a/app/client/src/pages/Editor/Explorer/Pages/PageGroup.tsx b/app/client/src/pages/Editor/Explorer/Pages/PageGroup.tsx index ea09de0db9..aa10968696 100644 --- a/app/client/src/pages/Editor/Explorer/Pages/PageGroup.tsx +++ b/app/client/src/pages/Editor/Explorer/Pages/PageGroup.tsx @@ -12,6 +12,7 @@ import { AppState } from "reducers"; import { CanvasStructure } from "reducers/uiReducers/pageCanvasStructureReducer"; import { Datasource } from "entities/Datasource"; import { Plugin } from "api/PluginApi"; +import { extractCurrentDSL } from "utils/WidgetPropsUtils"; type ExplorerPageGroupProps = { searchKeyword?: string; @@ -47,7 +48,11 @@ export const ExplorerPageGroup = memo((props: ExplorerPageGroupProps) => { "Page", pages.map((page: Page) => page.pageName), ); - dispatch(createPage(params.applicationId, name)); + // Default layout is extracted by adding dynamically computed properties like min-height. + const defaultPageLayouts = [ + { dsl: extractCurrentDSL(), layoutOnLoadActions: [] }, + ]; + dispatch(createPage(params.applicationId, name, defaultPageLayouts)); }, [dispatch, pages, params.applicationId]); const pageEntities = pages.map((page) => { diff --git a/app/client/src/reducers/entityReducers/canvasWidgetsReducer.tsx b/app/client/src/reducers/entityReducers/canvasWidgetsReducer.tsx index 1500d393aa..aeece8e481 100644 --- a/app/client/src/reducers/entityReducers/canvasWidgetsReducer.tsx +++ b/app/client/src/reducers/entityReducers/canvasWidgetsReducer.tsx @@ -33,6 +33,7 @@ const canvasWidgetsReducer = createImmerReducer(initialState, { action: ReduxAction, ) => { set(state[MAIN_CONTAINER_WIDGET_ID], "rightColumn", action.payload.width); + set(state[MAIN_CONTAINER_WIDGET_ID], "minHeight", action.payload.height); }, }); diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index 90289981e4..b87a576f85 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -94,7 +94,10 @@ import { WidgetBlueprint } from "reducers/entityReducers/widgetConfigReducer"; import { Toaster } from "components/ads/Toast"; import { Variant } from "components/ads/common"; import { ColumnProperties } from "components/designSystems/appsmith/TableComponent/Constants"; -import { getAllPathsFromPropertyConfig } from "entities/Widget/utils"; +import { + getAllPathsFromPropertyConfig, + nextAvailableRowInContainer, +} from "entities/Widget/utils"; import { getAllPaths } from "workers/evaluationUtils"; import { createMessage, @@ -1114,21 +1117,12 @@ function* copyWidgetSaga(action: ReduxAction<{ isShortcut: boolean }>) { export function calculateNewWidgetPosition( widget: WidgetProps, parentId: string, - canvasWidgets: FlattenedWidgetProps[], + canvasWidgets: { [widgetId: string]: FlattenedWidgetProps }, ) { // Note: This is a very simple algorithm. // We take the bottom most widget in the canvas, then calculate the top,left,right,bottom // co-ordinates for the new widget, such that it can be placed at the bottom of the canvas. - const nextAvailableRow = - Object.values(canvasWidgets).reduce( - (prev: number, next: any) => - next.widgetId !== widget.parentId && - next.parentId === parentId && - next.bottomRow > prev - ? next.bottomRow - : prev, - 0, - ) + 1; + const nextAvailableRow = nextAvailableRowInContainer(parentId, canvasWidgets); return { leftColumn: 0, rightColumn: widget.rightColumn - widget.leftColumn, diff --git a/app/client/src/utils/WidgetPropsUtils.tsx b/app/client/src/utils/WidgetPropsUtils.tsx index 3ebe9b2051..7c3c174ed0 100644 --- a/app/client/src/utils/WidgetPropsUtils.tsx +++ b/app/client/src/utils/WidgetPropsUtils.tsx @@ -10,6 +10,7 @@ import { } from "widgets/BaseWidget"; import { GridDefaults, + MAIN_CONTAINER_WIDGET_ID, RenderMode, WidgetType, WidgetTypes, @@ -28,6 +29,7 @@ import { } from "utils/migrations/TableWidget"; import { migrateIncorrectDynamicBindingPathLists } from "utils/migrations/IncorrectDynamicBindingPathLists"; import * as Sentry from "@sentry/react"; +import { nextAvailableRowInContainer } from "entities/Widget/utils"; export type WidgetOperationParams = { operation: WidgetOperation; @@ -351,13 +353,45 @@ function migrateOldChartData(currentDSL: ContainerWidgetProps) { return currentDSL; } +export const calculateDynamicHeight = ( + canvasWidgets: { + [widgetId: string]: FlattenedWidgetProps; + } = {}, + presentMinimumHeight = CANVAS_DEFAULT_HEIGHT_PX, +) => { + let minmumHeight = presentMinimumHeight; + const nextAvailableRow = nextAvailableRowInContainer( + MAIN_CONTAINER_WIDGET_ID, + canvasWidgets, + ); + const screenHeight = window.innerHeight; + const gridRowHeight = GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + const calculatedCanvasHeight = nextAvailableRow * gridRowHeight; + // DGRH - DEFAULT_GRID_ROW_HEIGHT + // View Mode: Header height + Page Selection Tab = 2 * DGRH (approx) + // Edit Mode: Header height + Canvas control = 2 * DGRH (approx) + // buffer = DGRH, it's not 2 * DGRH coz we already add a buffer on the canvas which is also equal to DGRH. + const buffer = gridRowHeight; + const calculatedMinHeight = + Math.floor((screenHeight - buffer) / gridRowHeight) * gridRowHeight; + if ( + calculatedCanvasHeight < screenHeight && + calculatedMinHeight !== presentMinimumHeight + ) { + minmumHeight = calculatedMinHeight; + } + return minmumHeight; +}; + +// A rudimentary transform function which updates the DSL based on its version. // A more modular approach needs to be designed. const transformDSL = (currentDSL: ContainerWidgetProps) => { if (currentDSL.version === undefined) { // Since this top level widget is a CANVAS_WIDGET, // DropTargetComponent needs to know the minimum height the canvas can take // See DropTargetUtils.ts - currentDSL.minHeight = CANVAS_DEFAULT_HEIGHT_PX; + currentDSL.minHeight = calculateDynamicHeight(); + // For the first time the DSL is created, remove one row from the total possible rows // to adjust for padding and margins. currentDSL.snapRows = @@ -446,9 +480,9 @@ const transformDSL = (currentDSL: ContainerWidgetProps) => { }; export const extractCurrentDSL = ( - fetchPageResponse: FetchPageResponse, + fetchPageResponse?: FetchPageResponse, ): ContainerWidgetProps => { - const currentDSL = fetchPageResponse.data.layouts[0].dsl || defaultDSL; + const currentDSL = fetchPageResponse?.data.layouts[0].dsl || defaultDSL; return transformDSL(currentDSL); }; diff --git a/app/client/src/utils/hooks/useDynamicAppLayout.tsx b/app/client/src/utils/hooks/useDynamicAppLayout.tsx index d3f5394c7c..019345251c 100644 --- a/app/client/src/utils/hooks/useDynamicAppLayout.tsx +++ b/app/client/src/utils/hooks/useDynamicAppLayout.tsx @@ -3,25 +3,30 @@ import { ReduxActionTypes } from "constants/ReduxActionConstants"; import { DefaultLayoutType, layoutConfigurations, + MAIN_CONTAINER_WIDGET_ID, } from "constants/WidgetConstants"; import { debounce } from "lodash"; import { AppsmithDefaultLayout } from "pages/Editor/MainContainerLayoutControl"; import { useCallback, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { AppState } from "reducers"; -import { getWidget } from "sagas/selectors"; +import { getWidget, getWidgets } from "sagas/selectors"; import { getAppMode } from "selectors/applicationSelectors"; import { getCurrentApplicationLayout, getCurrentPageId, } from "selectors/editorSelectors"; +import { calculateDynamicHeight } from "utils/WidgetPropsUtils"; import { useWindowSizeHooks } from "./dragResizeHooks"; export const useDynamicAppLayout = () => { - const { width: screenWidth } = useWindowSizeHooks(); - const mainContainer = useSelector((state: AppState) => getWidget(state, "0")); + const { width: screenWidth, height: screenHeight } = useWindowSizeHooks(); + const mainContainer = useSelector((state: AppState) => + getWidget(state, MAIN_CONTAINER_WIDGET_ID), + ); const currentPageId = useSelector(getCurrentPageId); const appMode = useSelector(getAppMode); + const canvasWidgets = useSelector(getWidgets); const appLayout = useSelector(getCurrentApplicationLayout); const dispatch = useDispatch(); @@ -60,6 +65,7 @@ export const useDynamicAppLayout = () => { type: ReduxActionTypes.UPDATE_CANVAS_LAYOUT, payload: { width: layoutWidth, + height: mainContainer.minHeight, }, }); } @@ -69,6 +75,22 @@ export const useDynamicAppLayout = () => { mainContainer, ]); + useEffect(() => { + const calculatedMinHeight = calculateDynamicHeight( + canvasWidgets, + mainContainer.minHeight, + ); + if (calculatedMinHeight !== mainContainer.minHeight) { + dispatch({ + type: ReduxActionTypes.UPDATE_CANVAS_LAYOUT, + payload: { + height: calculatedMinHeight, + width: mainContainer.rightColumn, + }, + }); + } + }, [screenHeight, mainContainer.minHeight]); + useEffect(() => { debouncedResize(screenWidth, appLayout); }, [screenWidth]);