Fix: Dynamic Canvas Height based on bottom most widget. (#3398)

* Fix: Dynamic Canvas Height based on bottom most widget.

* Checking cytest failure reason.

* Revert "Checking cytest failure reason."

This reverts commit 2e3aaa882b282e10e1cf491633101293b72ffa89.

* Using UPDATE_CANVAS_LAYOUT to update the layout.

* Remove unwanted declarations.

* Adding comments.
This commit is contained in:
Ashok Kumar M 2021-03-16 10:31:37 +05:30 committed by GitHub
parent e1fb1203b5
commit fc34901be8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 100 additions and 21 deletions

View File

@ -79,6 +79,7 @@ export interface UpdateWidgetPropertyPayload {
export interface UpdateCanvasLayout {
width: number;
height: number;
}
export interface SetWidgetDynamicPropertyPayload {

View File

@ -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<PageLayout>[],
) => {
AnalyticsUtil.logEvent("CREATE_PAGE", {
pageName,
});
@ -122,6 +126,7 @@ export const createPage = (applicationId: string, pageName: string) => {
payload: {
applicationId,
name: pageName,
layouts,
},
};
};

View File

@ -61,6 +61,7 @@ export interface SavePageResponse extends ApiResponse {
export interface CreatePageRequest {
applicationId: string;
name: string;
layouts: Partial<PageLayout>[];
}
export interface UpdatePageRequest {

View File

@ -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
);
};

View File

@ -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) => {

View File

@ -33,6 +33,7 @@ const canvasWidgetsReducer = createImmerReducer(initialState, {
action: ReduxAction<UpdateCanvasLayout>,
) => {
set(state[MAIN_CONTAINER_WIDGET_ID], "rightColumn", action.payload.width);
set(state[MAIN_CONTAINER_WIDGET_ID], "minHeight", action.payload.height);
},
});

View File

@ -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,

View File

@ -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<WidgetProps>) {
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<WidgetProps>) => {
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<WidgetProps>) => {
};
export const extractCurrentDSL = (
fetchPageResponse: FetchPageResponse,
fetchPageResponse?: FetchPageResponse,
): ContainerWidgetProps<WidgetProps> => {
const currentDSL = fetchPageResponse.data.layouts[0].dsl || defaultDSL;
const currentDSL = fetchPageResponse?.data.layouts[0].dsl || defaultDSL;
return transformDSL(currentDSL);
};

View File

@ -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]);