feat: Auto height instant update (#19082)

## Description
This PR adds one of the promised updates to the auto height feature. 
More specifically, we wanted to add was the ability to see the
containers change height as we drag and drop widgets within them instead
of after dropping (when auto height is enabled)
This PR does that.


Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
This commit is contained in:
Abhinav Jha 2023-02-03 11:17:40 +05:30 committed by GitHub
parent 04f6208f86
commit 20de52000d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 1214 additions and 1005 deletions

View File

@ -110,8 +110,8 @@
"click",
"submit"
],
"topRow": 27.0,
"bottomRow": 31.0,
"topRow": 227.0,
"bottomRow": 231.0,
"parentRowSpace": 10.0,
"type": "BUTTON_WIDGET",
"hideCard": false,

View File

@ -39,7 +39,7 @@ describe("Entity explorer Drag and Drop widgets testcases", function() {
*/
cy.moveToContentTab();
cy.get(formWidgetsPage.formD)
.scrollTo("bottom")
.scrollTo("bottom", { ensureScrollable: false })
.should("be.visible");
_.canvasHelper.OpenWidgetPane();
cy.PublishtheApp();

View File

@ -6,7 +6,7 @@ describe("Canvas Resize", function() {
cy.addDsl(dsl);
});
it("Deleting bottom widget should resize canvas", function() {
const InitHeight = "2960px";
const InitHeight = "2950px";
cy.get(commonlocators.dropTarget).should("have.css", "height", InitHeight);
cy.openPropertyPane("textwidget");
cy.intercept("PUT", "/api/v1/layouts/*/pages/*").as("deleteUpdate");
@ -16,7 +16,7 @@ describe("Canvas Resize", function() {
cy.get(commonlocators.dropTarget).should(
"have.css",
"height",
`${dsl.minHeight}px`,
`${dsl.minHeight - 12}px`, // Reducing 12 px for container padding.
);
});
});

View File

@ -1,7 +1,7 @@
import widgetLocators from "../../../../locators/Widgets.json";
import template from "../../../../locators/TemplatesLocators.json";
const publish = require("../../../../locators/publishWidgetspage.json");
import * as _ from "../../../../support/Objects/ObjectsCore"
import * as _ from "../../../../support/Objects/ObjectsCore";
beforeEach(() => {
// Closes template dialog if it is already open - useful for retry
@ -11,12 +11,11 @@ beforeEach(() => {
}
});
cy.CheckAndUnfoldEntityItem("Pages");
cy.get(`.t--entity-name:contains(Page1)`)
.trigger("mouseover")
.click({ force: true });
cy.get(`.t--entity-name:contains(Page1)`)
.trigger("mouseover")
.click({ force: true });
});
describe("Fork a template to the current app from new page popover", () => {
it("1. Fork template from page section", () => {
cy.wait(5000);

View File

@ -40,7 +40,7 @@ describe("Fork a template to the current app", () => {
)
.scrollIntoView()
.click();
_.agHelper.CheckForErrorToast("INTERNAL_SERVER_ERROR");
_.agHelper.CheckForErrorToast("INTERNAL_SERVER_ERROR");
cy.wait("@getTemplatePages").should(
"have.nested.property",
"response.body.responseMeta.status",

View File

@ -14,7 +14,7 @@ describe("reduce long canvas height on widget operation", () => {
cy.get(`div[data-testid='t--selected']`).should("have.length", 1);
cy.document().then((doc) => {
const element = doc.querySelector("#div-selection-0");
const element = doc.querySelector(".appsmith_widget_0");
const initialHeight = element.getBoundingClientRect().height;
//delete widget
cy.get("body").type(`{del}`);

View File

@ -12,7 +12,7 @@ describe("Test curl import flow", function() {
.should("be.visible")
.click({ force: true });
cy.get(ApiEditor.curlImage).click({ force: true });
cy.get("textarea").type("curl -X GET "+agHelper.mockApiUrl);
cy.get("textarea").type("curl -X GET " + agHelper.mockApiUrl);
cy.importCurl();
cy.get("@curlImport").then((response) => {
cy.expect(response.response.body.responseMeta.success).to.eq(true);

View File

@ -50,7 +50,7 @@
"checkboxGroupInput": ".t--draggable-checkboxgroupwidget span.t--widget-name",
"switchGroupInput": ".t--draggable-switchgroupwidget span.t--widget-name",
"radioAddButton": ".t--property-control-options button.t--property-control-options-add",
"formD": "div[type='FORM_WIDGET']",
"formD": ".t--widget-formwidget div.style-container",
"datepickerFooter": ".bp3-datepicker-footer span",
"datepickerFooterPublish": ".bp3-datepicker-footer span",
"disableJs": ".t--property-control-disabled input[type='checkbox']",

View File

@ -5,7 +5,7 @@
"tabDefault": ".t--property-control-defaulttab .CodeMirror-code",
"tabButton": ".t--property-control-tabs button",
"tabDelete": ".t--property-control-tabs .t--delete-column-btn",
"tabContainer": "div[type='TABS_WIDGET']",
"tabContainer": ".t--widget-tabswidget",
"tabEdit": "//input[@value ='tabName']//parent::div//parent::div//following-sibling::div//div[contains(@class,'t--edit-column-btn')]",
"tabVisibility": ".t--property-control-visible .bp3-control-indicator",
"tabNumber": ".t--number-of-tabs",

View File

@ -47,7 +47,7 @@
"RadioInput": ".t--property-control-options input",
"checkboxInput": ".t--draggable-checkboxwidget span.t--widget-name",
"checkboxLabel": ".t--draggable-checkboxwidget label",
"containerD": "div[type='CONTAINER_WIDGET']",
"containerD": ".t--widget-containerwidget div.style-container",
"containerWrapper": "div[data-testid='container-wrapper']",
"defaultInput": ".t--property-control-defaultvalue .CodeMirror-code",
"placeholder": ".t--property-control-placeholder .CodeMirror-code",
@ -152,7 +152,7 @@
"switchWidgetLoading": ".t--switch-widget-loading",
"colorsAvailable": ".bp3-popover-dismiss",
"listWidget":"[type=LIST_WIDGET]",
"itemContainerWidget":"[type=CONTAINER_WIDGET]",
"itemContainerWidget":".t--widget-containerwidget div.style-container",
"listWidgetName":".t--property-pane-title",
"toggleBackground": ".t--property-control-background .t--js-toggle",
"toggleItemBackground": ".t--property-control-itembackground .t--js-toggle",

View File

@ -8,7 +8,10 @@ export const CONFIG = {
needsMeta: false, // Defines if this widget adds any meta properties
isCanvas: false, // Defines if this widget has a canvas within in which we can drop other widgets
features: {
dynamicHeight: false,
dynamicHeight: {
sectionIndex: 0, // Index of the property pane "General" section
active: false,
},
},
defaults: {
widgetName: "{{name}}",

View File

@ -2,6 +2,7 @@ import {
ReduxActionTypes,
ReduxAction,
} from "@appsmith/constants/ReduxActionConstants";
import { GridDefaults } from "constants/WidgetConstants";
import { TreeNode } from "utils/autoHeight/constants";
export interface UpdateWidgetAutoHeightPayload {
@ -22,12 +23,14 @@ export function setAutoHeightLayoutTreeAction(
export function generateAutoHeightLayoutTreeAction(
shouldCheckContainersForAutoHeightUpdates: boolean,
layoutUpdated?: boolean,
resettingTabs?: boolean,
) {
return {
type: ReduxActionTypes.GENERATE_AUTO_HEIGHT_LAYOUT_TREE,
payload: {
shouldCheckContainersForAutoHeightUpdates,
layoutUpdated: !!layoutUpdated,
resettingTabs: !!resettingTabs,
},
};
}
@ -45,8 +48,24 @@ export function updateWidgetAutoHeightAction(
};
}
export function checkContainersForAutoHeightAction() {
export function checkContainersForAutoHeightAction(resettingTabs?: boolean) {
return {
type: ReduxActionTypes.CHECK_CONTAINERS_FOR_AUTO_HEIGHT,
payload: {
resettingTabs: !!resettingTabs,
},
};
}
export function updateDOMDirectlyBasedOnAutoHeightAction(
widgetId: string,
height: number,
) {
return {
type: ReduxActionTypes.DIRECT_DOM_UPDATE_AUTO_HEIGHT,
payload: {
height: height * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
widgetId,
},
};
}

View File

@ -743,6 +743,7 @@ export const ReduxActionTypes = {
SET_DATASOURCE_SAVE_ACTION_FLAG: "SET_DATASOURCE_SAVE_ACTION_FLAG",
SET_DATASOURCE_SAVE_ACTION_FROM_POPUP_FLAG:
"SET_DATASOURCE_SAVE_ACTION_FROM_POPUP_FLAG",
DIRECT_DOM_UPDATE_AUTO_HEIGHT: "DIRECT_DOM_UPDATE_AUTO_HEIGHT",
SET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET:
"SET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET",
RESET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET:

View File

@ -131,14 +131,12 @@ export function PositionedContainer(props: PositionedContainerProps) {
isDropTarget && effectedByReflow ? { pointerEvents: "none" } : {};
const reflowedPositionStyles: CSSProperties = hasReflowedPosition
? {
transform: `translate(${reflowX}px,${reflowY}px)`,
transition: `transform 100ms linear`,
transform: `translate3d(${reflowX}px,${reflowY}px,0)`,
boxShadow: `0 0 0 1px rgba(104,113,239,0.5)`,
}
: {};
const reflowDimensionsStyles = hasReflowedDimensions
? {
transition: `width 0.1s, height 0.1s`,
boxShadow: `0 0 0 1px rgba(104,113,239,0.5)`,
}
: {};
@ -147,6 +145,7 @@ export function PositionedContainer(props: PositionedContainerProps) {
position: "absolute",
left: x,
top: y,
transition: `transform 100ms ease, width 100ms ease, height 100ms ease`,
height:
reflowHeight || style.componentHeight + (style.heightUnit || "px"),
width: reflowWidth || style.componentWidth + (style.widthUnit || "px"),
@ -186,4 +185,5 @@ export function PositionedContainer(props: PositionedContainerProps) {
}
PositionedContainer.padding = WIDGET_PADDING;
export default PositionedContainer;

View File

@ -36,10 +36,10 @@ const WidgetStyle = styled.div<WidgetStyleContainerProps>`
border-style: solid;
background-color: ${(props) => props.backgroundColor || "transparent"};
overflow: hidden;
& > div {
height: 100%;
width: 100%;
overflow: hidden;
}
`;
@ -47,7 +47,7 @@ const WidgetStyle = styled.div<WidgetStyleContainerProps>`
function WidgetStyleContainer(props: WidgetStyleContainerProps) {
return (
<WidgetStyle {...props} data-testid={`container-wrapper-${props.widgetId}`}>
<div>{props.children}</div>
{props.children}
</WidgetStyle>
);
}

View File

@ -7,7 +7,6 @@ import {
const GRID_POINT_SIZE = 1;
const WrappedDragLayer = styled.div<{
columnWidth: number;
rowHeight: number;
noPad: boolean;
}>`
position: absolute;
@ -32,11 +31,10 @@ const WrappedDragLayer = styled.div<{
);
background-size: ${(props) =>
props.columnWidth - GRID_POINT_SIZE / GridDefaults.DEFAULT_GRID_COLUMNS}px
${(props) => props.rowHeight}px;
${GridDefaults.DEFAULT_GRID_ROW_HEIGHT}px;
`;
type DragLayerProps = {
parentRowHeight: number;
parentColumnWidth: number;
noPad: boolean;
};
@ -44,9 +42,9 @@ type DragLayerProps = {
function DragLayerComponent(props: DragLayerProps) {
return (
<WrappedDragLayer
className="drag-layer"
columnWidth={props.parentColumnWidth}
noPad={props.noPad}
rowHeight={props.parentRowHeight}
/>
);
}

View File

@ -1,10 +1,8 @@
import React, { CSSProperties, useMemo, useRef } from "react";
import styled from "styled-components";
import { WidgetProps } from "widgets/BaseWidget";
import { WIDGET_PADDING } from "constants/WidgetConstants";
import { useSelector } from "react-redux";
import { AppState } from "@appsmith/reducers";
import { getColorWithOpacity } from "constants/DefaultTheme";
import {
useShowTableFilterPane,
useWidgetDragResize,
@ -18,6 +16,8 @@ import {
isCurrentWidgetFocused,
isWidgetSelected,
} from "selectors/widgetSelectors";
import { getColorWithOpacity } from "constants/DefaultTheme";
import { WIDGET_PADDING } from "constants/WidgetConstants";
import { SelectionRequestType } from "sagas/WidgetSelectUtils";
const DraggableWrapper = styled.div`
@ -29,9 +29,10 @@ const DraggableWrapper = styled.div`
cursor: grab;
`;
type DraggableComponentProps = WidgetProps;
// Widget Boundaries which is shown to indicate the boundaries of the widget
const WidgetBoundaries = styled.div`
transform: translate3d(-${WIDGET_PADDING + 1}px, -${WIDGET_PADDING + 1}px, 0);
z-index: 0;
width: calc(100% + ${WIDGET_PADDING - 2}px);
height: calc(100% + ${WIDGET_PADDING - 2}px);
@ -39,12 +40,11 @@ const WidgetBoundaries = styled.div`
border: 1px dashed
${(props) => getColorWithOpacity(props.theme.colors.textAnchor, 0.5)};
pointer-events: none;
top: 0;
position: absolute;
left: 0;
`;
type DraggableComponentProps = WidgetProps;
/* eslint-disable react/display-name */
/**
* can drag helper function for react-dnd hook
*
@ -107,6 +107,7 @@ function DraggableComponent(props: DraggableComponentProps) {
const isResizingOrDragging = !!isResizing || !!isDragging;
const isCurrentWidgetDragging = isDragging && isSelected;
const isCurrentWidgetResizing = isResizing && isSelected;
// When mouse is over this draggable
const handleMouseOver = (e: any) => {
focusWidget &&
@ -124,15 +125,9 @@ function DraggableComponent(props: DraggableComponentProps) {
const dragBoundariesStyle: React.CSSProperties = useMemo(() => {
return {
opacity: !isResizingOrDragging || isCurrentWidgetResizing ? 0 : 1,
position: "absolute",
transform: `translate(-50%, -50%)`,
top: "50%",
left: "50%",
};
}, [isResizingOrDragging, isCurrentWidgetResizing]);
const widgetBoundaries = <WidgetBoundaries style={dragBoundariesStyle} />;
const classNameForTesting = `t--draggable-${props.type
.split("_")
.join("")
@ -195,7 +190,10 @@ function DraggableComponent(props: DraggableComponentProps) {
style={dragWrapperStyle}
>
{shouldRenderComponent && props.children}
{widgetBoundaries}
<WidgetBoundaries
className={`widget-boundary-${props.widgetId}`}
style={dragBoundariesStyle}
/>
</DraggableWrapper>
);
}

View File

@ -1,16 +1,15 @@
import React, {
ReactNode,
Context,
createContext,
memo,
useEffect,
useRef,
useCallback,
useMemo,
PropsWithChildren,
} from "react";
import styled from "styled-components";
import equal from "fast-deep-equal/es6";
import { WidgetProps } from "widgets/BaseWidget";
import { getCanvasSnapRows } from "utils/WidgetPropsUtils";
import {
MAIN_CONTAINER_WIDGET_ID,
@ -19,11 +18,8 @@ import {
import { calculateDropTargetRows } from "./DropTargetUtils";
import DragLayerComponent from "./DragLayerComponent";
import { AppState } from "@appsmith/reducers";
import { useSelector } from "react-redux";
import {
useShowPropertyPane,
useCanvasSnapRowsUpdateHook,
} from "utils/hooks/dragResizeHooks";
import { useDispatch, useSelector } from "react-redux";
import { useShowPropertyPane } from "utils/hooks/dragResizeHooks";
import {
getOccupiedSpacesSelectorForContainer,
previewModeSelector,
@ -31,14 +27,17 @@ import {
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
import { getDragDetails } from "sagas/selectors";
import { useAutoHeightUIState } from "utils/hooks/autoHeightUIHooks";
import { updateDOMDirectlyBasedOnAutoHeightAction } from "actions/autoHeightActions";
import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils";
type DropTargetComponentProps = WidgetProps & {
children?: ReactNode;
type DropTargetComponentProps = PropsWithChildren<{
snapColumnSpace: number;
snapRowSpace: number;
minHeight: number;
widgetId: string;
parentId?: string;
noPad?: boolean;
};
bottomRow: number;
minHeight: number;
}>;
const StyledDropTarget = styled.div`
transition: height 100ms ease-in;
@ -69,39 +68,123 @@ export const DropTargetContext: Context<{
}> = createContext({});
/**
* Gets the dropTarget height
* @param canDropTargetExtend boolean: Can we put widgets below the scrollview in this canvas?
* @param isPreviewMode boolean: Are we in the preview mode
* @param currentHeight number: Current height in the ref and what we have set in the dropTarget
* @param snapRowSpace number: This is a static value actually, GridDefaults.DEFAULT_GRID_ROW_HEIGHT
* @param minHeight number: The minHeight we've set to the widget in the reducer
* @returns number: A new height style to set in the dropTarget.
* This function sets the height in pixels to the provided ref to the number of rows
* @param ref : The ref to the dropTarget so that we can update the height
* @param currentRows : Number of rows to set the height
*/
function getDropTargetHeight(
canDropTargetExtend: boolean,
isPreviewMode: boolean,
currentHeight: number,
snapRowSpace: number,
minHeight: number,
) {
let height = canDropTargetExtend
? `${Math.max(currentHeight * snapRowSpace, minHeight)}px`
: "100%";
if (isPreviewMode && canDropTargetExtend)
height = `${currentHeight * snapRowSpace}px`;
return height;
const updateHeight = (
ref: React.MutableRefObject<HTMLDivElement | null>,
currentRows: number,
isMainContainer: boolean,
) => {
if (ref.current) {
const height = currentRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
ref.current.style.height = `${height}px`;
ref.current
.closest(".scroll-parent")
?.scrollTo({ top: height, behavior: "smooth" });
if (isMainContainer) {
const artboard = document.getElementById("art-board");
if (artboard) {
artboard.style.height = `${height}px`;
}
}
}
};
function useUpdateRows(bottomRow: number, widgetId: string, parentId?: string) {
// This gives us the number of rows
const snapRows = getCanvasSnapRows(bottomRow);
// Put the existing snap rows in a ref.
const rowRef = useRef(snapRows);
const dropTargetRef = useRef<HTMLDivElement>(null);
// The occupied spaces in this canvas. It is a data structure which has the rect values of each child.
const selectOccupiedSpaces = useCallback(
getOccupiedSpacesSelectorForContainer(widgetId),
[widgetId],
);
// Call the selector above.
const occupiedSpacesByChildren = useSelector(selectOccupiedSpaces, equal);
/*
* If the parent has auto height enabled, or if the current widget is the MAIN_CONTAINER_WIDGET_ID
*/
const isParentAutoHeightEnabled = useSelector((state: AppState) => {
return parentId
? !isAutoHeightEnabledForWidget(
state.entities.canvasWidgets[parentId],
true,
) &&
isAutoHeightEnabledForWidget(state.entities.canvasWidgets[parentId])
: false;
});
const dispatch = useDispatch();
// Function which computes and updates the height of the dropTarget
// This is used in a context and hence in one of the children of this dropTarget
const updateDropTargetRows = (
widgetIdsToExclude: string[],
widgetBottomRow: number,
) => {
// Compute expected number of rows this drop target must have
const newRows = calculateDropTargetRows(
widgetIdsToExclude,
widgetBottomRow,
occupiedSpacesByChildren,
widgetId,
);
// If the current number of rows in the drop target is less
// than the expected number of rows in the drop target
if (rowRef.current < newRows) {
// Set the new value locally
rowRef.current = newRows;
// If the parent container like widget has auto height enabled
// We'd like to immediately update the parent's height
// based on the auto height computations
// This also updates any "dropTargets" that need to change height
// hence, this and the `updateHeight` function are mutually exclusive.
if (isParentAutoHeightEnabled && parentId) {
dispatch(updateDOMDirectlyBasedOnAutoHeightAction(parentId, newRows));
} else {
// Basically, we don't have auto height triggering, so the dropTarget height should be updated using
// the `updateHeight` function
// The difference here is that the `updateHeight` function only updates the "canvas" or the "dropTarget"
// and doesn't effect the parent container
// We can't update the height of the "Canvas" or "dropTarget" using this function
// in the previous if clause, because, there could be more "dropTargets" updating
// and this information can only be computed using auto height
updateHeight(
dropTargetRef,
rowRef.current,
widgetId === MAIN_CONTAINER_WIDGET_ID,
);
}
return newRows;
}
return false;
};
// memoizing context values
const contextValue = useMemo(() => {
return {
updateDropTargetRows,
};
}, [updateDropTargetRows, occupiedSpacesByChildren]);
/** EO PREPARE CONTEXT */
return { contextValue, dropTargetRef, rowRef };
}
export function DropTargetComponent(props: DropTargetComponentProps) {
// Get if this is in preview mode.
const isPreviewMode = useSelector(previewModeSelector);
// Pretty much the shouldScrollContents from the parent container like widget
const canDropTargetExtend = props.canExtend;
// If in preview mode, we don't need that extra row
// This gives us the number of rows
const snapRows = getCanvasSnapRows(
const { contextValue, dropTargetRef, rowRef } = useUpdateRows(
props.bottomRow,
props.canExtend && !isPreviewMode,
props.widgetId,
props.parentId,
);
// Are we currently resizing?
@ -129,77 +212,26 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
(state: AppState) => state.entities.canvasWidgets[props.widgetId]?.children,
);
// The occupied spaces in this canvas. It is a data structure which has the rect values of each child.
const selectOccupiedSpaces = useCallback(
getOccupiedSpacesSelectorForContainer(props.widgetId),
[props.widgetId],
);
// Call the selector above.
const occupiedSpacesByChildren = useSelector(selectOccupiedSpaces, equal);
// Put the existing snap rows in a ref.
const rowRef = useRef(snapRows);
// This shows the property pane
const showPropertyPane = useShowPropertyPane();
const { deselectAll, focusWidget } = useWidgetSelection();
// This updates the bottomRow of this canvas, as simple as that
// This also doesn't cause an eval as it uses the action which is
// not registered to cause an eval
const updateCanvasSnapRows = useCanvasSnapRowsUpdateHook();
// Everytime we get a new bottomRow, or we toggle shouldScrollContents
// we call this effect
useEffect(() => {
const snapRows = getCanvasSnapRows(
props.bottomRow,
props.canExtend && !isPreviewMode,
);
const snapRows = getCanvasSnapRows(props.bottomRow);
// If the current ref is not set to the new snaprows we've received (based on bottomRow)
if (rowRef.current !== snapRows) {
if (rowRef.current !== snapRows && !isDragging && !isResizing) {
rowRef.current = snapRows;
// This sets the "height" property of the dropTarget div
// This makes the div change heights if new heights are different
updateHeight();
// This sets the new rows in the reducer
// Not sure why, as we've just received the values from the props.
// seems like a potential way to cause recursive renders
// See this: https://github.com/appsmithorg/appsmith/pull/18457#issuecomment-1327615572
if (canDropTargetExtend && !isPreviewMode) {
updateCanvasSnapRows(props.widgetId, snapRows);
}
}
}, [props.bottomRow, props.canExtend, isPreviewMode]);
// If we've stopped dragging, resizing or changing auto height limits
useEffect(() => {
if (!isDragging || !isResizing || !isAutoHeightWithLimitsChanging) {
// bottom row of canvas can increase by any number as user moves/resizes any widget towards the bottom of the canvas
// but canvas height is not lost when user moves/resizes back top.
// it is done that way to have a pleasant building experience.
// post drop the bottom most row is used to appropriately calculate the canvas height and lose unwanted height.
rowRef.current = snapRows;
updateHeight();
}
}, [isDragging, isResizing, isAutoHeightWithLimitsChanging]);
// Update the drop target height style directly.
const updateHeight = () => {
if (dropTargetRef.current) {
const height = getDropTargetHeight(
canDropTargetExtend,
isPreviewMode,
rowRef.current,
props.snapRowSpace,
props.minHeight,
updateHeight(
dropTargetRef,
snapRows,
props.widgetId === MAIN_CONTAINER_WIDGET_ID,
);
dropTargetRef.current.style.height = height;
}
};
}, [props.widgetId, props.bottomRow, isDragging, isResizing]);
const handleFocus = (e: any) => {
// Making sure that we don't deselect the widget
@ -214,47 +246,7 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
e.preventDefault();
};
/** PREPARE CONTEXT */
// Function which computes and updates the height of the dropTarget
// This is used in a context and hence in one of the children of this dropTarget
const updateDropTargetRows = (
widgetIdsToExclude: string[],
widgetBottomRow: number,
) => {
if (canDropTargetExtend) {
const newRows = calculateDropTargetRows(
widgetIdsToExclude,
widgetBottomRow,
props.minHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
occupiedSpacesByChildren,
props.widgetId,
);
if (rowRef.current < newRows) {
rowRef.current = newRows;
updateHeight();
return newRows;
}
return false;
}
return false;
};
// memoizing context values
const contextValue = useMemo(() => {
return {
updateDropTargetRows,
};
}, [updateDropTargetRows, occupiedSpacesByChildren]);
/** EO PREPARE CONTEXT */
const height = getDropTargetHeight(
canDropTargetExtend,
isPreviewMode,
rowRef.current,
props.snapRowSpace,
props.minHeight,
);
const height = `${rowRef.current * GridDefaults.DEFAULT_GRID_ROW_HEIGHT}px`;
const boxShadow =
(isResizing || isDragging || isAutoHeightWithLimitsChanging) &&
@ -278,12 +270,11 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
isAutoHeightWithLimitsChanging) &&
!isPreviewMode;
const dropTargetRef = useRef<HTMLDivElement>(null);
return (
<DropTargetContext.Provider value={contextValue}>
<StyledDropTarget
className="t--drop-target"
className={`t--drop-target drop-target-${props.parentId ||
MAIN_CONTAINER_WIDGET_ID}`}
onClick={handleFocus}
ref={dropTargetRef}
style={dropTargetStyles}
@ -294,7 +285,6 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
<DragLayerComponent
noPad={props.noPad || false}
parentColumnWidth={props.snapColumnSpace}
parentRowHeight={props.snapRowSpace}
/>
)}
</StyledDropTarget>
@ -302,6 +292,4 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
);
}
const MemoizedDropTargetComponent = memo(DropTargetComponent);
export default MemoizedDropTargetComponent;
export default DropTargetComponent;

View File

@ -7,7 +7,6 @@ import {
export const calculateDropTargetRows = (
widgetIdsToExclude: string[],
widgetBottomRow: number,
defaultRows: number,
occupiedSpacesByChildren?: OccupiedSpace[],
canvasWidgetId?: string,
) => {
@ -27,5 +26,5 @@ export const calculateDropTargetRows = (
? GridDefaults.MAIN_CANVAS_EXTENSION_OFFSET
: GridDefaults.CANVAS_EXTENSION_OFFSET;
return Math.ceil(Math.max(minBottomRow + canvasOffset, defaultRows));
return Math.ceil(minBottomRow + canvasOffset);
};

View File

@ -9,15 +9,7 @@ type State = { hasError: boolean };
const ErrorBoundaryContainer = styled.div`
height: 100%;
width: 100%;
> div {
height: 100%;
width: 100%;
}
`;
// border: 1px solid;
// border-color: ${({ isValid, theme }) =>
// isValid ? "transparent" : theme.colors.error};
const RetryLink = styled.span`
color: ${(props) => props.theme.colors.primaryDarkest};
@ -42,7 +34,7 @@ class ErrorBoundary extends React.Component<Props, State> {
render() {
return (
<ErrorBoundaryContainer>
<ErrorBoundaryContainer className="error-boundary">
{this.state.hasError ? (
<p>
Oops, Something went wrong.

View File

@ -291,6 +291,11 @@ export const ResizableComponent = memo(function ResizableComponent(
return !isAutoHeightEnabledForWidget(props) && isEnabled;
}, [props, isAutoHeightEnabledForWidget, isEnabled]);
const fixedHeight =
isAutoHeightEnabledForWidget(props, true) ||
!isAutoHeightEnabledForWidget(props) ||
!props.isCanvas;
return (
<Resizable
allowResize={!isMultiSelected}
@ -298,17 +303,19 @@ export const ResizableComponent = memo(function ResizableComponent(
componentWidth={dimensions.width}
enableHorizontalResize={isEnabled}
enableVerticalResize={isVerticalResizeEnabled}
fixedHeight={fixedHeight}
getResizedPositions={getResizedPositions}
gridProps={gridProps}
handles={handles}
maxDynamicHeight={props.maxDynamicHeight}
onStart={handleResizeStart}
onStop={updateSize}
originalPositions={originalPositions}
parentId={props.parentId}
snapGrid={snapGrid}
updateBottomRow={updateBottomRow}
widgetId={props.widgetId}
// Used only for performance tracking, can be removed after optimization.
widgetId={props.widgetId}
zWidgetId={props.widgetId}
zWidgetType={props.type}
>

View File

@ -27,6 +27,7 @@ import { TAILWIND_COLORS } from "constants/ThemeConstants";
import useDSEvent from "utils/hooks/useDSEvent";
import { DSEventTypes } from "utils/AppsmithUtils";
import { getBrandColors } from "@appsmith/selectors/tenantSelectors";
import tinycolor from "tinycolor2";
const FocusTrap = require("focus-trap-react");
const MAX_COLS = 10;
@ -551,7 +552,10 @@ const ColorPickerComponent = React.forwardRef(
autoFocus={props.autoFocus}
inputRef={inputGroupRef}
leftIcon={
<LeftIcon color={color} handleInputClick={handleInputClick} />
<LeftIcon
color={tinycolor(color).toString()}
handleInputClick={handleInputClick}
/>
}
onChange={handleChangeColor}
onClick={handleInputClick}

View File

@ -8,6 +8,7 @@ import {
DS_EVENT,
emitInteractionAnalyticsEvent,
} from "utils/AppsmithUtils";
import tinycolor from "tinycolor2";
class ColorPickerControl extends BaseControl<ColorPickerControlProps> {
componentRef = React.createRef<HTMLDivElement>();
@ -39,7 +40,10 @@ class ColorPickerControl extends BaseControl<ColorPickerControlProps> {
};
handleChangeColor = (color: string, isUpdatedViaKeyboard: boolean) => {
this.updateProperty(this.props.propertyName, color, isUpdatedViaKeyboard);
let _color = color;
_color = tinycolor(color).isValid() ? tinycolor(color).toString() : color;
this.updateProperty(this.props.propertyName, _color, isUpdatedViaKeyboard);
};
render() {

View File

@ -1,24 +1,13 @@
import log from "loglevel";
import * as Sentry from "@sentry/react";
import styled from "styled-components";
import store from "store";
import { CanvasWidgetStructure } from "widgets/constants";
import WidgetFactory from "utils/WidgetFactory";
import React, { memo, useCallback, useEffect } from "react";
import React from "react";
import CanvasMultiPointerArena, {
POINTERS_CANVAS_ID,
} from "pages/common/CanvasArenas/CanvasMultiPointerArena";
import { throttle } from "lodash";
import { RenderModes } from "constants/WidgetConstants";
import { isMultiplayerEnabledForUser as isMultiplayerEnabledForUserSelector } from "selectors/appCollabSelectors";
import { useDispatch, useSelector } from "react-redux";
import { initPageLevelSocketConnection } from "actions/websocketActions";
import { collabShareUserPointerEvent } from "actions/appCollabActions";
import { getIsPageLevelSocketConnected } from "selectors/websocketSelectors";
import { getCurrentGitBranch } from "selectors/gitSyncSelectors";
import { useSelector } from "react-redux";
import { getSelectedAppTheme } from "selectors/appThemingSelectors";
import { getPageLevelSocketRoomId } from "sagas/WebsocketSagas/utils";
import { previewModeSelector } from "selectors/editorSelectors";
import useWidgetFocus from "utils/hooks/useWidgetFocus";
@ -29,69 +18,22 @@ interface CanvasProps {
canvasScale?: number;
}
type PointerEventDataType = {
data: { x: number; y: number };
user: any;
};
const Container = styled.section<{
background: string;
width: number;
$canvasScale: number;
}>`
background: ${({ background }) => background};
}
width: ${(props) => props.width}px;
transform: scale(${(props) => props.$canvasScale});
transform-origin: "0 0";
`;
const getPointerData = (
e: any,
pageId: string,
isWebsocketConnected: boolean,
currentGitBranch?: string,
) => {
if (store.getState().ui.appCollab.editors.length < 2 || !isWebsocketConnected)
return;
const selectionCanvas: any = document.getElementById(POINTERS_CANVAS_ID);
const rect = selectionCanvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
return {
data: { x, y },
pageId: getPageLevelSocketRoomId(pageId, currentGitBranch),
};
};
const useShareMousePointerEvent = () => {
const dispatch = useDispatch();
const isWebsocketConnected = useSelector(getIsPageLevelSocketConnected);
useEffect(() => {
if (!isWebsocketConnected) {
dispatch(initPageLevelSocketConnection());
}
}, [isWebsocketConnected]);
return (pointerData: PointerEventDataType) =>
dispatch(collabShareUserPointerEvent(pointerData));
};
// TODO(abhinav): get the render mode from context
const Canvas = memo((props: CanvasProps) => {
const { canvasScale = 1, canvasWidth, pageId } = props;
const Canvas = (props: CanvasProps) => {
const { canvasScale = 1, canvasWidth } = props;
const isPreviewMode = useSelector(previewModeSelector);
const selectedTheme = useSelector(getSelectedAppTheme);
const shareMousePointer = useShareMousePointerEvent();
const isWebsocketConnected = useSelector(getIsPageLevelSocketConnected);
const currentGitBranch = useSelector(getCurrentGitBranch);
const isMultiplayerEnabledForUser = useSelector(
isMultiplayerEnabledForUserSelector,
);
const delayedShareMousePointer = useCallback(
throttle((data) => shareMousePointer(data), 50, {
trailing: false,
}),
[shareMousePointer, pageId],
);
/**
* background for canvas
*/
@ -108,35 +50,19 @@ const Canvas = memo((props: CanvasProps) => {
try {
return (
<Container
$canvasScale={canvasScale}
background={backgroundForCanvas}
className="relative mx-auto t--canvas-artboard pb-52"
data-testid="t--canvas-artboard"
id="art-board"
onMouseMove={(e) => {
if (!isMultiplayerEnabledForUser) return;
const data = getPointerData(
e,
pageId,
isWebsocketConnected,
currentGitBranch,
);
!!data && delayedShareMousePointer(data);
}}
ref={focusRef}
style={{
width: canvasWidth,
transform: `scale(${canvasScale})`,
transformOrigin: "0 0",
}}
width={canvasWidth}
>
{props.widgetsStructure.widgetId &&
WidgetFactory.createWidget(
props.widgetsStructure,
RenderModes.CANVAS,
)}
{isMultiplayerEnabledForUser && (
<CanvasMultiPointerArena pageId={pageId} />
)}
</Container>
);
} catch (error) {
@ -144,8 +70,6 @@ const Canvas = memo((props: CanvasProps) => {
Sentry.captureException(error);
return null;
}
});
Canvas.displayName = "Canvas";
};
export default Canvas;

View File

@ -113,7 +113,7 @@ const HeaderWrapper = styled.div`
height: ${(props) => props.theme.smallHeaderHeight};
flex-direction: row;
box-shadow: none;
border-bottom: 1px solid ${(props) => props.theme.colors.menuBorder};
border-bottom: 1px solid ${(props) => props.theme.colors.menuBorder};
& .editable-application-name {
${getTypographyByKey("h4")}
color: ${(props) => props.theme.colors.header.appName};

View File

@ -82,7 +82,7 @@ const StyledSelectBoxHandleTop = styled.div`
border-top: 1px dashed
${(props) => props.theme.colors.widgetGroupingContextMenu.border};
top: 0px;
left: -1px;
left: 0px;
`;
const StyledSelectBoxHandleLeft = styled.div`
@ -93,7 +93,7 @@ const StyledSelectBoxHandleLeft = styled.div`
border-left: 1px dashed
${(props) => props.theme.colors.widgetGroupingContextMenu.border};
top: 0px;
left: -1px;
left: 0px;
`;
const StyledSelectBoxHandleRight = styled.div`
@ -115,7 +115,7 @@ const StyledSelectBoxHandleBottom = styled.div`
border-bottom: 1px dashed
${(props) => props.theme.colors.widgetGroupingContextMenu.border};
top: 100%;
left: -1px;
left: 0px;
`;
export const PopoverModifiers: IPopoverSharedProps["modifiers"] = {

View File

@ -123,7 +123,9 @@ export const StickyCanvasArena = forwardRef(
};
const observeSlider = () => {
interSectionObserver.current.disconnect();
interSectionObserver.current.observe(slidingArenaRef.current);
if (slidingArenaRef && slidingArenaRef.current) {
interSectionObserver.current.observe(slidingArenaRef.current);
}
};
useEffect(() => {
@ -148,7 +150,9 @@ export const StickyCanvasArena = forwardRef(
return () => {
parentCanvas?.removeEventListener("scroll", observeSlider);
parentCanvas?.removeEventListener("mouseover", observeSlider);
resizeObserver.current.unobserve(slidingArenaRef.current);
if (slidingArenaRef && slidingArenaRef.current) {
resizeObserver.current.unobserve(slidingArenaRef.current);
}
};
}, []);

View File

@ -7,6 +7,10 @@ import {
import { WidgetProps } from "widgets/BaseWidget";
import { uniq, get, set } from "lodash";
import { Diff, diff } from "deep-diff";
import {
getCanvasBottomRow,
getCanvasWidgetHeightsToUpdate,
} from "utils/WidgetSizeUtils";
/* This type is an object whose keys are widgetIds and values are arrays with property paths
and property values
@ -54,7 +58,14 @@ const canvasWidgetsReducer = createImmerReducer(initialState, {
state: CanvasWidgetsReduxState,
action: ReduxAction<UpdateCanvasPayload>,
) => {
return action.payload.widgets;
const { widgets } = action.payload;
for (const [widgetId, widgetProps] of Object.entries(widgets)) {
if (widgetProps.type === "CANVAS_WIDGET") {
const bottomRow = getCanvasBottomRow(widgetId, widgets);
widgets[widgetId].bottomRow = bottomRow;
}
}
return widgets;
},
[ReduxActionTypes.UPDATE_LAYOUT]: (
state: CanvasWidgetsReduxState,
@ -81,6 +92,15 @@ const canvasWidgetsReducer = createImmerReducer(initialState, {
delete state[widgetId];
}
}
const canvasWidgetHeightsToUpdate: Record<
string,
number
> = getCanvasWidgetHeightsToUpdate(listOfUpdatedWidgets, state);
for (const widgetId in canvasWidgetHeightsToUpdate) {
state[widgetId].bottomRow = canvasWidgetHeightsToUpdate[widgetId];
}
},
[ReduxActionTypes.UPDATE_MULTIPLE_WIDGET_PROPERTIES]: (
state: CanvasWidgetsReduxState,
@ -101,6 +121,14 @@ const canvasWidgetsReducer = createImmerReducer(initialState, {
set(state, path, propertyValue);
});
}
const canvasWidgetHeightsToUpdate: Record<
string,
number
> = getCanvasWidgetHeightsToUpdate(Object.keys(action.payload), state);
for (const widgetId in canvasWidgetHeightsToUpdate) {
state[widgetId].bottomRow = canvasWidgetHeightsToUpdate[widgetId];
}
},
});

View File

@ -1,6 +1,10 @@
import React, { ReactNode, useState, useEffect, useRef } from "react";
import styled, { StyledComponent } from "styled-components";
import { WIDGET_PADDING } from "constants/WidgetConstants";
import {
GridDefaults,
WidgetHeightLimits,
WIDGET_PADDING,
} from "constants/WidgetConstants";
import { useDrag } from "react-use-gesture";
import { animated, Spring } from "react-spring";
import PerformanceTracker, {
@ -150,6 +154,8 @@ type ResizableProps = {
canResizeVertically: boolean;
resizedPositions?: OccupiedSpace;
};
fixedHeight: boolean;
maxDynamicHeight?: number;
originalPositions: OccupiedSpace;
onStart: () => void;
onStop: (
@ -511,12 +517,33 @@ export function ReflowResizable(props: ResizableProps) {
}}
from={{
width: props.componentWidth,
height: props.componentHeight,
height: props.fixedHeight
? Math.min(
(props.maxDynamicHeight ||
WidgetHeightLimits.MAX_HEIGHT_IN_ROWS) *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
props.componentHeight,
)
: "auto",
maxHeight:
(props.maxDynamicHeight || WidgetHeightLimits.MAX_HEIGHT_IN_ROWS) *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
}}
immediate={newDimensions.reset ? true : false}
to={{
width: widgetWidth,
height: widgetHeight,
height: props.fixedHeight
? Math.min(
(props.maxDynamicHeight ||
WidgetHeightLimits.MAX_HEIGHT_IN_ROWS) *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
widgetHeight,
)
: "auto",
maxHeight:
(props.maxDynamicHeight || WidgetHeightLimits.MAX_HEIGHT_IN_ROWS) *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
transform: `translate3d(${newDimensions.x}px,${newDimensions.y}px,0)`,
}}
>

View File

@ -88,7 +88,6 @@ export function* getCanvasSizeAfterWidgetMove(
const newRows = calculateDropTargetRows(
movedWidgetIds,
movedWidgetsBottomRow,
canvasMinHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
occupiedSpacesByChildren,
canvasWidgetId,
);

View File

@ -6,10 +6,7 @@ import {
ReduxActionTypes,
WidgetReduxActionTypes,
} from "@appsmith/constants/ReduxActionConstants";
import {
MAIN_CONTAINER_WIDGET_ID,
RenderModes,
} from "constants/WidgetConstants";
import { RenderModes } from "constants/WidgetConstants";
import { ENTITY_TYPE } from "entities/AppsmithConsole";
import {
CanvasWidgetsReduxState,
@ -26,10 +23,6 @@ import {
executeWidgetBlueprintOperations,
traverseTreeAndExecuteBlueprintChildOperations,
} from "./WidgetBlueprintSagas";
import {
getParentBottomRowAfterAddingWidget,
resizeCanvasToLowestWidget,
} from "./WidgetOperationUtils";
import log from "loglevel";
import { getDataTree } from "selectors/dataTreeSelectors";
import { generateReactKey } from "utils/generators";
@ -41,8 +34,6 @@ import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
import { getPropertiesToUpdate } from "./WidgetOperationSagas";
import { klona as clone } from "klona/full";
import { DataTree } from "entities/DataTree/dataTreeFactory";
import { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer";
import { getMainCanvasProps } from "selectors/editorSelectors";
import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions";
const WidgetTypes = WidgetFactory.widgetTypes;
@ -262,18 +253,10 @@ export function* getUpdateDslAfterCreatingChild(
addChildPayload.props?.blueprint,
);
const newWidget = childWidgetPayload.widgets[childWidgetPayload.widgetId];
const parentBottomRow = getParentBottomRowAfterAddingWidget(
stateParent,
newWidget,
);
// Update widgets to put back in the canvasWidgetsReducer
// TODO(abhinav): This won't work if dont already have an empty children: []
const parent = {
...stateParent,
bottomRow: parentBottomRow,
children: [...(stateParent.children || []), childWidgetPayload.widgetId],
};
@ -303,23 +286,6 @@ export function* getUpdateDslAfterCreatingChild(
widgets,
);
if (widgetId === MAIN_CONTAINER_WIDGET_ID) {
const mainCanvasProps: MainCanvasReduxState = yield select(
getMainCanvasProps,
);
const mainCanvasMinHeight = mainCanvasProps?.height;
//updates bottom Row of main Canvas
updatedWidgets[
MAIN_CONTAINER_WIDGET_ID
].bottomRow = resizeCanvasToLowestWidget(
updatedWidgets,
widgetId,
updatedWidgets[MAIN_CONTAINER_WIDGET_ID].bottomRow,
mainCanvasMinHeight,
);
}
return updatedWidgets;
}

View File

@ -11,7 +11,6 @@ import {
ReduxActionTypes,
WidgetReduxActionTypes,
} from "@appsmith/constants/ReduxActionConstants";
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
import { ENTITY_TYPE } from "entities/AppsmithConsole";
import LOG_TYPE from "entities/AppsmithConsole/logtype";
import { flattenDeep, omit, orderBy } from "lodash";
@ -27,7 +26,6 @@ import { WidgetProps } from "widgets/BaseWidget";
import { getSelectedWidget, getWidget, getWidgets } from "./selectors";
import {
getAllWidgetsInTree,
resizeCanvasToLowestWidget,
updateListWidgetPropertiesOnChildDelete,
WidgetsInTree,
} from "./WidgetOperationUtils";
@ -38,8 +36,6 @@ import {
isExploringSelector,
} from "selectors/onboardingSelectors";
import { toggleShowDeviationDialog } from "actions/onboardingActions";
import { getMainCanvasProps } from "selectors/editorSelectors";
import { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer";
import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions";
import { SelectionRequestType } from "sagas/WidgetSelectUtils";
@ -177,24 +173,6 @@ function* getUpdatedDslAfterDeletingWidget(widgetId: string, parentId: string) {
otherWidgetsToDelete.map((widgets) => widgets.widgetId),
);
//Main canvas's minheight keeps varying, hence retrieving updated value
let mainCanvasMinHeight;
if (parentId === MAIN_CONTAINER_WIDGET_ID) {
const mainCanvasProps: MainCanvasReduxState = yield select(
getMainCanvasProps,
);
mainCanvasMinHeight = mainCanvasProps?.height;
}
if (parentId && finalWidgets[parentId]) {
finalWidgets[parentId].bottomRow = resizeCanvasToLowestWidget(
finalWidgets,
parentId,
finalWidgets[parentId].bottomRow,
mainCanvasMinHeight,
);
}
return {
finalWidgets,
otherWidgetsToDelete,
@ -297,26 +275,6 @@ function* deleteAllSelectedWidgetsSaga(
parentUpdatedWidgets,
flattenedWidgets.map((widgets: any) => widgets.widgetId),
);
// assuming only widgets with same parent can be selected
const parentId = widgets[selectedWidgets[0]].parentId;
//Main canvas's minheight keeps varying, hence retrieving updated value
let mainCanvasMinHeight;
if (parentId === MAIN_CONTAINER_WIDGET_ID) {
const mainCanvasProps: MainCanvasReduxState = yield select(
getMainCanvasProps,
);
mainCanvasMinHeight = mainCanvasProps?.height;
}
if (parentId && finalWidgets[parentId]) {
finalWidgets[parentId].bottomRow = resizeCanvasToLowestWidget(
finalWidgets,
parentId,
finalWidgets[parentId].bottomRow,
mainCanvasMinHeight,
);
}
yield put(updateAndSaveLayout(finalWidgets));
yield put(generateAutoHeightLayoutTreeAction(true, true));

View File

@ -56,9 +56,8 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
import log from "loglevel";
import { navigateToCanvas } from "pages/Editor/Explorer/Widgets/utils";
import {
getCanvasHeightOffset,
getContainerWidgetSpacesSelector,
getCurrentPageId,
getContainerWidgetSpacesSelector,
} from "selectors/editorSelectors";
import { selectWidgetInitAction } from "actions/widgetSelectionActions";
@ -96,7 +95,6 @@ import {
getNewPositionsForCopiedWidgets,
getNextWidgetName,
getOccupiedSpacesFromProps,
getParentBottomRowAfterAddingWidget,
getParentWidgetIdForGrouping,
getParentWidgetIdForPasting,
getPastePositionMapFromMousePointer,
@ -148,53 +146,6 @@ import { traverseTreeAndExecuteBlueprintChildOperations } from "./WidgetBlueprin
import { MetaState } from "reducers/entityReducers/metaReducer";
import { SelectionRequestType } from "sagas/WidgetSelectUtils";
export function* updateAllChildCanvasHeights(
currentContainerLikeWidgetId: string,
topRow: number,
bottomRow: number,
allWidgets?: CanvasWidgetsReduxState,
) {
const containerLikeWidget: FlattenedWidgetProps = yield select(
getWidget,
currentContainerLikeWidgetId,
);
let stateWidgets: CanvasWidgetsReduxState | undefined = allWidgets;
if (!stateWidgets) stateWidgets = yield select(getWidgets);
const canvasHeightOffset: number = getCanvasHeightOffset(
containerLikeWidget.type,
containerLikeWidget,
);
const containerLikeWidgetHeightInPx: number =
(bottomRow - topRow - canvasHeightOffset) *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
const widgets = { ...stateWidgets };
if (Array.isArray(containerLikeWidget.children)) {
containerLikeWidget.children.forEach((childWidgetId: string) => {
const childWidget = { ...widgets[childWidgetId] };
if (Array.isArray(childWidget.children)) {
const maxChildBottomRow = childWidget.children.reduce((prev, next) => {
return widgets[next].bottomRow > prev
? widgets[next].bottomRow
: prev;
}, 0);
const maxHeightBasedOnChildrenInPx =
maxChildBottomRow * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
const finalHeight = Math.max(
containerLikeWidgetHeightInPx,
maxHeightBasedOnChildrenInPx,
);
widgets[childWidgetId] = {
...childWidget,
bottomRow: finalHeight,
minHeight: finalHeight,
};
}
});
}
return widgets;
}
export function* resizeSaga(resizeAction: ReduxAction<WidgetResize>) {
try {
Toaster.clear();
@ -216,7 +167,7 @@ export function* resizeSaga(resizeAction: ReduxAction<WidgetResize>) {
const widgets = { ...stateWidgets };
widget = { ...widget, leftColumn, rightColumn, topRow, bottomRow };
let movedWidgets: {
const movedWidgets: {
[widgetId: string]: FlattenedWidgetProps;
} = yield call(
reflowWidgets,
@ -226,13 +177,6 @@ export function* resizeSaga(resizeAction: ReduxAction<WidgetResize>) {
snapRowSpace,
);
movedWidgets = yield updateAllChildCanvasHeights(
widgetId,
topRow,
bottomRow,
movedWidgets,
);
const updatedCanvasBottomRow: number = yield call(
getCanvasSizeAfterWidgetMove,
parentId,
@ -1352,7 +1296,6 @@ function* pasteWidgetSaga(
const selectedWidget: FlattenedWidgetProps<undefined> = yield getSelectedWidgetWhenPasting();
let reflowedMovementMap,
bottomMostRow: number | undefined,
gridProps: GridProps | undefined,
newPastingPositionMap: SpaceMap | undefined,
canvasId;
@ -1386,7 +1329,6 @@ function* pasteWidgetSaga(
// If there are already widgets inside the selection box even before grouping
//then we will have to move it down to the bottom most row
({
bottomMostRow,
copiedWidgetGroups,
gridProps,
reflowedMovementMap,
@ -1420,7 +1362,6 @@ function* pasteWidgetSaga(
// new pasting positions, the variables are undefined if the positions cannot be calculated,
// then it pastes the regular way at the bottom of the canvas
({
bottomMostRow,
canvasId,
gridProps,
newPastingPositionMap,
@ -1617,16 +1558,11 @@ function* pasteWidgetSaga(
// Add the new child to existing children
parentChildren = parentChildren.concat(widgetChildren);
}
const parentBottomRow = getParentBottomRowAfterAddingWidget(
widgets[pastingParentId],
widget,
);
widgets = {
...widgets,
[pastingParentId]: {
...widgets[pastingParentId],
bottomRow: Math.max(parentBottomRow, bottomMostRow || 0),
children: parentChildren,
},
};

View File

@ -2,7 +2,6 @@ import { OccupiedSpace } from "constants/CanvasEditorConstants";
import { klona } from "klona";
import { get } from "lodash";
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
import { CanvasWidgetsStructureReduxState } from "reducers/entityReducers/canvasWidgetsStructureReducer";
import { WidgetProps } from "widgets/BaseWidget";
import { FlattenedWidgetProps } from "widgets/constants";
import {
@ -23,7 +22,6 @@ import {
getReflowedPositions,
getWidgetsFromIds,
getValueFromTree,
resizeCanvasToLowestWidget,
resizePublishedMainCanvasToLowestWidget,
} from "./WidgetOperationUtils";
@ -1812,38 +1810,6 @@ describe("getValueFromTree - ", () => {
4: { bottomRow: 20, children: [] },
} as unknown) as CanvasWidgetsReduxState;
it("should modify main container's bottomRow to minHeight of canvas when it is greater than bottomRow of lowest widget", () => {
const currentWidgets = klona(widgets);
const bottomRow = resizeCanvasToLowestWidget(
currentWidgets,
"0",
currentWidgets["0"].bottomRow,
450,
);
expect(bottomRow).toEqual(450);
});
it("should modify main container's bottomRow to lowest bottomRow of canvas when minHeight is lesser than bottomRow of lowest widget", () => {
const currentWidgets = klona(widgets);
const bottomRow = resizeCanvasToLowestWidget(
currentWidgets,
"0",
currentWidgets["0"].bottomRow,
140,
);
expect(bottomRow).toEqual(430);
});
it("should modify main container's bottomRow to lowest bottomRow of canvas when minHeight is lesser than bottomRow of lowest widget", () => {
const currentWidgets = klona(widgets);
const bottomRow = resizeCanvasToLowestWidget(
currentWidgets,
"1",
currentWidgets["1"].bottomRow,
);
expect(bottomRow).toEqual(260);
});
it("should trim canvas close to the lowest bottomRow of it's children widget", () => {
const currentWidgets = klona(widgets);
resizePublishedMainCanvasToLowestWidget(currentWidgets);

View File

@ -1514,21 +1514,6 @@ export const getAllWidgetsInTree = (
return widgetList;
};
export const getParentBottomRowAfterAddingWidget = (
stateParent: FlattenedWidgetProps,
newWidget: FlattenedWidgetProps,
) => {
const parentRowSpace =
newWidget.parentRowSpace || GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
const newBottomRow =
(newWidget.bottomRow + GridDefaults.CANVAS_EXTENSION_OFFSET) *
parentRowSpace;
const updateBottomRow =
stateParent.type === "CANVAS_WIDGET" &&
newBottomRow > stateParent.bottomRow;
return updateBottomRow ? newBottomRow : stateParent.bottomRow;
};
/**
* sometimes, selected widgets contains the grouped widget,
* in those cases, we will just selected the main container as the
@ -1731,54 +1716,6 @@ export function mergeDynamicPropertyPaths(
return _.unionWith(a, b, (a, b) => a.key === b.key);
}
/**
* returns the BottomRow for CANVAS_WIDGET
* @param finalWidgets
* @param canvasWidgetId
*/
export function resizeCanvasToLowestWidget(
finalWidgets: CanvasWidgetsReduxState,
canvasWidgetId: string | undefined,
currentBottomRow: number,
mainCanvasMinHeight?: number, //defined only if canvasWidgetId is MAIN_CONTAINER_ID
) {
if (!canvasWidgetId) return currentBottomRow;
if (
!finalWidgets[canvasWidgetId] ||
finalWidgets[canvasWidgetId].type !== "CANVAS_WIDGET"
) {
return currentBottomRow;
}
const defaultLowestBottomRow =
mainCanvasMinHeight ||
finalWidgets[canvasWidgetId].minHeight ||
CANVAS_DEFAULT_MIN_HEIGHT_PX;
const childIds = finalWidgets[canvasWidgetId].children || [];
let lowestBottomRow = 0;
// find the lowest row
childIds.forEach((cId) => {
const child = finalWidgets[cId];
if (!child.detachFromLayout && child.bottomRow > lowestBottomRow) {
lowestBottomRow = child.bottomRow;
}
});
const canvasOffset =
canvasWidgetId === MAIN_CONTAINER_WIDGET_ID
? GridDefaults.MAIN_CANVAS_EXTENSION_OFFSET
: GridDefaults.CANVAS_EXTENSION_OFFSET;
return Math.max(
defaultLowestBottomRow,
(lowestBottomRow + canvasOffset) * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
);
}
/**
* Note: Mutates widgets[0].bottomRow for CANVAS_WIDGET
* @param widgets

View File

@ -1,4 +1,7 @@
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import {
ReduxAction,
ReduxActionTypes,
} from "@appsmith/constants/ReduxActionConstants";
import { GridDefaults } from "constants/WidgetConstants";
import log from "loglevel";
import { AutoHeightLayoutTreeReduxState } from "reducers/entityReducers/autoHeightReducers/autoHeightLayoutTreeReducer";
@ -6,7 +9,7 @@ import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsRe
import { call, put, select } from "redux-saga/effects";
import { getMinHeightBasedOnChildren, shouldWidgetsCollapse } from "./helpers";
import { getWidgets } from "sagas/selectors";
import { getCanvasHeightOffset } from "selectors/editorSelectors";
import { getCanvasHeightOffset } from "utils/WidgetSizeUtils";
import { getAutoHeightLayoutTree } from "selectors/autoHeightSelectors";
import { FlattenedWidgetProps } from "widgets/constants";
import {
@ -18,7 +21,9 @@ import { getChildOfContainerLikeWidget } from "./helpers";
import { getDataTree } from "selectors/dataTreeSelectors";
import { DataTree, DataTreeWidget } from "entities/DataTree/dataTreeFactory";
export function* dynamicallyUpdateContainersSaga() {
export function* dynamicallyUpdateContainersSaga(
action?: ReduxAction<{ resettingTabs: boolean }>,
) {
const start = performance.now();
const stateWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
@ -54,34 +59,8 @@ export function* dynamicallyUpdateContainersSaga() {
dataTreeWidget &&
(dataTreeWidget as DataTreeWidget).isVisible !== true &&
shouldCollapse
)
continue;
let bottomRow, topRow;
// If the parent exists in the layout tree
if (dynamicHeightLayoutTree[parentContainerWidget.widgetId]) {
// Get the tree node for the parent
const layoutNode =
dynamicHeightLayoutTree[parentContainerWidget.widgetId];
// Get all the dimensions from the tree node
bottomRow = layoutNode.bottomRow;
topRow = layoutNode.topRow;
} else {
// If it doesn't exist in the layout tree
// It is most likely a Modal Widget
// Use the dimensions as they exist in the widget.
bottomRow = parentContainerWidget.bottomRow;
topRow = parentContainerWidget.topRow;
}
// If this is a Modal widget or some other widget
// which is detached from layout
// use the value 0, as the starting point.
if (
parentContainerWidget.detachFromLayout &&
parentContainerWidget.height
) {
topRow = 0;
continue;
}
if (isAutoHeightEnabledForWidget(parentContainerWidget)) {
@ -98,7 +77,9 @@ export function* dynamicallyUpdateContainersSaga() {
// For example, if this canvas widget in consideration
// is not the selected tab's canvas in a tabs widget
// we don't have to consider it at all
if (childWidgetId !== canvasWidget.widgetId) continue;
if (childWidgetId !== canvasWidget.widgetId) {
continue;
}
// Get the boundaries for possible min and max dynamic height.
const minDynamicHeightInRows = getWidgetMinAutoHeight(
@ -111,9 +92,6 @@ export function* dynamicallyUpdateContainersSaga() {
// Default to the min height expected.
let maxBottomRow = minDynamicHeightInRows;
// For the child Canvas, use the value in pixels.
let canvasBottomRow = maxBottomRow + 0;
// For widgets like Tabs Widget, some of the height is occupied by the
// tabs themselves, the child canvas as a result has less number of rows available
// To accommodate for this, we need to increase the new height by the offset amount.
@ -136,16 +114,12 @@ export function* dynamicallyUpdateContainersSaga() {
);
// Add a canvas extension offset
maxBottomRowBasedOnChildren += GridDefaults.CANVAS_EXTENSION_OFFSET;
// Set the canvas bottom row as a new variable with a new reference
canvasBottomRow = maxBottomRowBasedOnChildren + 0;
// Add the offset to the total height of the parent widget
maxBottomRowBasedOnChildren += canvasHeightOffset;
// Get the larger value between the minDynamicHeightInRows and bottomMostRowForChild
maxBottomRow = Math.max(maxBottomRowBasedOnChildren, maxBottomRow);
} else {
canvasBottomRow = maxBottomRow - canvasHeightOffset;
}
// The following makes sure we stay within bounds
@ -158,20 +132,25 @@ export function* dynamicallyUpdateContainersSaga() {
maxBottomRow = maxDynamicHeightInRows;
}
canvasBottomRow =
Math.max(maxBottomRow - canvasHeightOffset, canvasBottomRow) *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
if (
action?.payload.resettingTabs &&
parentContainerWidget.type === "TABS_WIDGET"
) {
const layoutNode =
dynamicHeightLayoutTree[parentContainerWidget.widgetId];
if (
layoutNode &&
maxBottomRow === layoutNode.bottomRow - layoutNode.topRow
) {
continue;
}
}
// If we have a new height to set and
// If the canvas for some reason doesn't have the correct bottomRow
if (
maxBottomRow !== bottomRow - topRow ||
canvasBottomRow !== canvasWidget.bottomRow
) {
if (!updates.hasOwnProperty(parentContainerWidget.widgetId)) {
updates[parentContainerWidget.widgetId] =
maxBottomRow * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
}
if (!updates.hasOwnProperty(parentContainerWidget.widgetId)) {
updates[parentContainerWidget.widgetId] =
maxBottomRow * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
}
}
}

View File

@ -1,4 +1,7 @@
import { GridDefaults } from "constants/WidgetConstants";
import {
GridDefaults,
MAIN_CONTAINER_WIDGET_ID,
} from "constants/WidgetConstants";
import { APP_MODE } from "entities/App";
import { AutoHeightLayoutTreeReduxState } from "reducers/entityReducers/autoHeightReducers/autoHeightLayoutTreeReducer";
import {
@ -7,11 +10,9 @@ import {
} from "reducers/entityReducers/canvasWidgetsReducer";
import { select } from "redux-saga/effects";
import { getWidgetMetaProps, getWidgets } from "sagas/selectors";
import {
getCanvasHeightOffset,
previewModeSelector,
} from "selectors/editorSelectors";
import { previewModeSelector } from "selectors/editorSelectors";
import { getAppMode } from "selectors/entitiesSelector";
import { getCanvasHeightOffset } from "utils/WidgetSizeUtils";
export function* shouldWidgetsCollapse() {
const isPreviewMode: boolean = yield select(previewModeSelector);
@ -162,5 +163,9 @@ export function* getMinHeightBasedOnChildren(
}
}
if (widgetId === MAIN_CONTAINER_WIDGET_ID) {
return minHeightInRows + GridDefaults.MAIN_CANVAS_EXTENSION_OFFSET;
}
return minHeightInRows;
}

View File

@ -23,10 +23,12 @@ export default function* autoHeightSagas() {
ReduxActionTypes.PROCESS_AUTO_HEIGHT_UPDATES,
updateWidgetAutoHeightSaga,
),
takeEvery(
ReduxActionTypes.DIRECT_DOM_UPDATE_AUTO_HEIGHT,
updateWidgetAutoHeightSaga,
),
takeLatest(
[
ReduxActionTypes.GENERATE_AUTO_HEIGHT_LAYOUT_TREE, // add, move, paste, cut, delete, undo/redo
],
ReduxActionTypes.GENERATE_AUTO_HEIGHT_LAYOUT_TREE, // add, move, paste, cut, delete, undo/redo
generateTreeForAutoHeightComputations,
),
]);

View File

@ -50,6 +50,7 @@ export function* generateTreeForAutoHeightComputations(
action: ReduxAction<{
shouldCheckContainersForAutoHeightUpdates: boolean;
layoutUpdated: boolean;
resettingTabs: boolean;
}>,
) {
const { canvasLevelMap, tree } = yield getLayoutTree(
@ -62,7 +63,7 @@ export function* generateTreeForAutoHeightComputations(
yield put({
type: ReduxActionTypes.PROCESS_AUTO_HEIGHT_UPDATES,
});
yield put(checkContainersForAutoHeightAction());
yield put(checkContainersForAutoHeightAction(action.payload.resettingTabs));
}
return tree;

View File

@ -11,7 +11,7 @@ import {
} from "reducers/entityReducers/canvasWidgetsReducer";
import { put, select } from "redux-saga/effects";
import { getWidgets } from "sagas/selectors";
import { getCanvasHeightOffset } from "selectors/editorSelectors";
import { getCanvasHeightOffset } from "utils/WidgetSizeUtils";
import { FlattenedWidgetProps } from "widgets/constants";
import {
getWidgetMaxAutoHeight,
@ -22,18 +22,24 @@ import {
getAutoHeightUpdateQueue,
resetAutoHeightUpdateQueue,
} from "./batcher";
import {
getChildOfContainerLikeWidget,
getMinHeightBasedOnChildren,
getParentCurrentHeightInRows,
shouldWidgetsCollapse,
} from "./helpers";
import { getMinHeightBasedOnChildren, shouldWidgetsCollapse } from "./helpers";
import { updateMultipleWidgetPropertiesAction } from "actions/controlActions";
import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions";
import {
generateAutoHeightLayoutTreeAction,
UpdateWidgetAutoHeightPayload,
} from "actions/autoHeightActions";
import { computeChangeInPositionBasedOnDelta } from "utils/autoHeight/reflow";
import { CanvasLevelsReduxState } from "reducers/entityReducers/autoHeightReducers/canvasLevelsReducer";
import { getCanvasLevelMap } from "selectors/autoHeightSelectors";
import {
getAutoHeightLayoutTree,
getCanvasLevelMap,
} from "selectors/autoHeightSelectors";
import { getLayoutTree } from "./layoutTree";
import { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
import { TreeNode } from "utils/autoHeight/constants";
import { directlyMutateDOMNodes } from "utils/autoHeight/mutateDOM";
import { getAppMode } from "selectors/entitiesSelector";
import { APP_MODE } from "entities/App";
/* TODO(abhinav)
hasScroll is no longer needed, as the only way we will be computing for hasScroll, is when we get the updates
@ -60,19 +66,47 @@ import { getLayoutTree } from "./layoutTree";
* TODO: PERF_TRACK(abhinav): Make sure to benchmark the computations.
* We need to propagate changes within 10ms
*/
export function* updateWidgetAutoHeightSaga() {
const updates = getAutoHeightUpdateQueue();
log.debug("Dynamic Height: updates to process", { updates });
export function* updateWidgetAutoHeightSaga(
action?: ReduxAction<UpdateWidgetAutoHeightPayload>,
) {
const start = performance.now();
let shouldRecomputeContainers = false;
const shouldCollapse: boolean = yield shouldWidgetsCollapse();
const appMode: APP_MODE = yield select(getAppMode);
const { tree: dynamicHeightLayoutTree } = yield getLayoutTree(false);
let updates = getAutoHeightUpdateQueue();
let dynamicHeightLayoutTree: Record<string, TreeNode>;
const widgetsMeasuredInPixels = [];
const widgetCanvasOffsets: Record<string, number> = {};
// Get all widgets from canvasWidgetsReducer
const stateWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
if (action?.payload) {
const offset = getCanvasHeightOffset(
stateWidgets[action.payload.widgetId].type,
stateWidgets[action.payload.widgetId],
);
updates = {
[action.payload.widgetId]:
action.payload.height + offset * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
};
/* Creating a new type called dynamicHeightLayoutTree. */
dynamicHeightLayoutTree = yield select(getAutoHeightLayoutTree);
} else {
const result: {
tree: Record<string, TreeNode>;
canvasLevelsMap: Record<string, number>;
} = yield getLayoutTree(false);
dynamicHeightLayoutTree = result.tree;
}
log.debug("Auto Height: updates to process", { updates });
// Initialise all the widgets we will be updating
const widgetsToUpdate: UpdateWidgetsPayload = {};
@ -147,6 +181,8 @@ export function* updateWidgetAutoHeightSaga() {
// For widgets like Modal Widget. (Rather this assumes that it is only the modal widget which needs a change)
const newHeight = updates[widgetId];
widgetsMeasuredInPixels.push(widgetId);
// Setting the height and dimensions of the Modal Widget
widgetsToUpdate[widgetId] = [
{
@ -162,23 +198,6 @@ export function* updateWidgetAutoHeightSaga() {
propertyValue: widget.topRow,
},
];
// Setting the child canvas widget's dimensions in the Modal Widget
if (Array.isArray(widget.children) && widget.children.length === 1) {
widgetsToUpdate[widget.children[0]] = [
{
propertyPath: "minHeight",
propertyValue: newHeight,
},
{
propertyPath: "bottomRow",
propertyValue: newHeight,
},
{
propertyPath: "topRow",
propertyValue: 0,
},
];
}
}
}
@ -323,28 +342,6 @@ export function* updateWidgetAutoHeightSaga() {
minCanvasHeightInRows + canvasHeightOffset,
);
// Setting this in a variable, as this will be the total scroll height in the canvas.
const minCanvasHeightInPixels =
(minHeightInRows - canvasHeightOffset) *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
// We need to make sure that the canvas widget doesn't have
// any extra scroll, to this end, we need to add the `minHeight` update
// for the canvas widgets. Canvas Widgets are never updated in other flows
// As they simply take up whatever space the parent has, but this doesn't effect
// the `minHeight`, which leads to scroll if the `minHeight` is a larger value.
// Also, for canvas widgets, the values are in pure pixels instead of rows.
widgetsToUpdate[parentCanvasWidgetId] = [
{
propertyPath: "bottomRow",
propertyValue: minCanvasHeightInPixels,
},
{
propertyPath: "minHeight",
propertyValue: minCanvasHeightInPixels,
},
];
// Make sure we're not overflowing the max height bounds
const maxDynamicHeight = getWidgetMaxAutoHeight(
parentContainerLikeWidget,
@ -356,7 +353,15 @@ export function* updateWidgetAutoHeightSaga() {
dynamicHeightLayoutTree[parentContainerLikeWidget.widgetId];
if (layoutData === undefined) {
layoutData = parentContainerLikeWidget;
layoutData = {
bottomRow: parentContainerLikeWidget.bottomRow,
topRow: parentContainerLikeWidget.topRow,
aboves: [],
belows: [],
originalBottomRow: parentContainerLikeWidget.bottomRow,
originalTopRow: parentContainerLikeWidget.topRow,
distanceToNearestAbove: 0,
};
}
// Convert this change into the standard expected update format.
@ -377,6 +382,10 @@ export function* updateWidgetAutoHeightSaga() {
// We need to make sure that we change properties other than bottomRow and topRow
// In this case we're updating minHeight and height as well.
if (parentContainerLikeWidget.detachFromLayout) {
widgetsMeasuredInPixels.push(
parentContainerLikeWidget.widgetId,
);
// DRY this
widgetsToUpdate[parentContainerLikeWidget.widgetId] = [
{
@ -440,80 +449,21 @@ export function* updateWidgetAutoHeightSaga() {
]);
}
}
} else {
// Get the parent's topRow and bottomRow from the state
let parentBottomRow = parentContainerLikeWidget.bottomRow;
let parentTopRow = parentContainerLikeWidget.topRow;
// If we have the parent's dimensions in the tree
// and it is not a modal widget, then get the topRow
// and bottomRow from the tree.
if (
dynamicHeightLayoutTree[parentContainerLikeWidget.widgetId] &&
!parentContainerLikeWidget.detachFromLayout
) {
parentBottomRow =
dynamicHeightLayoutTree[parentContainerLikeWidget.widgetId]
.bottomRow;
parentTopRow =
dynamicHeightLayoutTree[parentContainerLikeWidget.widgetId]
.topRow;
}
// If this is a modal widget, then get the bottomRow in rows
// as the height and bottomRow could be in pixels.
if (
parentContainerLikeWidget.detachFromLayout &&
parentContainerLikeWidget.height
) {
parentBottomRow =
parentTopRow +
Math.ceil(
parentContainerLikeWidget.height /
GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
);
}
// Get the parent container's height in rows
// It is possible that some other update has changed this parent's
// dimensions.
let parentContainerHeightInRows = getParentCurrentHeightInRows(
parentBottomRow,
parentTopRow,
parentContainerLikeWidget.widgetId,
changesSoFar,
);
parentContainerHeightInRows -= canvasHeightOffset;
// Setting this in a variable, as this will be the total scroll height in the canvas.
const minCanvasHeightInPixels =
Math.max(minCanvasHeightInRows, parentContainerHeightInRows) *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
// We need to make sure that the canvas widget doesn't have
// any extra scroll, to this end, we need to add the `minHeight` update
// for the canvas widgets. Canvas Widgets are never updated in other flows
// As they simply take up whatever space the parent has, but this doesn't effect
// the `minHeight`, which leads to scroll if the `minHeight` is a larger value.
// Also, for canvas widgets, the values are in pure pixels instead of rows.
widgetsToUpdate[parentCanvasWidgetId] = [
{
propertyPath: "bottomRow",
propertyValue: minCanvasHeightInPixels,
},
{
propertyPath: "minHeight",
propertyValue: minCanvasHeightInPixels,
},
];
}
}
}
}
}
// Let's consider the minimum Canvas Height
const mainContainerMinHeight =
stateWidgets[MAIN_CONTAINER_WIDGET_ID].minHeight;
const canvasMinHeight: number =
appMode === APP_MODE.EDIT && mainContainerMinHeight !== undefined
? mainContainerMinHeight
: CANVAS_DEFAULT_MIN_HEIGHT_PX;
let maxCanvasHeightInRows =
CANVAS_DEFAULT_MIN_HEIGHT_PX / GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
canvasMinHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
// The same logic to compute the minimum height of the MainContainer
// Based on how many rows are being occuped by children.
@ -529,13 +479,14 @@ export function* updateWidgetAutoHeightSaga() {
maxCanvasHeightInRows,
);
widgetsMeasuredInPixels.push(MAIN_CONTAINER_WIDGET_ID);
// Add the MainContainer's update.
widgetsToUpdate[MAIN_CONTAINER_WIDGET_ID] = [
{
propertyPath: "bottomRow",
propertyValue:
(maxCanvasHeightInRows + GridDefaults.MAIN_CANVAS_EXTENSION_OFFSET) *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
maxCanvasHeightInRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
},
];
@ -547,6 +498,15 @@ export function* updateWidgetAutoHeightSaga() {
changedWidgetId
];
if (!action?.payload) {
const canvasOffset = getCanvasHeightOffset(
stateWidgets[changedWidgetId].type,
stateWidgets[changedWidgetId],
);
widgetCanvasOffsets[changedWidgetId] = canvasOffset;
}
widgetsToUpdate[changedWidgetId] = [
{
propertyPath: "bottomRow",
@ -565,64 +525,33 @@ export function* updateWidgetAutoHeightSaga() {
propertyValue: originalBottomRow,
},
];
const containerLikeWidget = stateWidgets[changedWidgetId];
if (
Array.isArray(containerLikeWidget.children) &&
containerLikeWidget.children.length > 0
) {
const childWidgetId:
| string
| undefined = yield getChildOfContainerLikeWidget(
containerLikeWidget,
);
if (childWidgetId) {
const childCanvasWidget = stateWidgets[childWidgetId];
const isCanvasWidget = childCanvasWidget?.type === "CANVAS_WIDGET";
if (isCanvasWidget) {
let canvasHeight: number = yield getMinHeightBasedOnChildren(
childWidgetId,
changesSoFar,
false,
dynamicHeightLayoutTree,
);
canvasHeight += GridDefaults.CANVAS_EXTENSION_OFFSET;
const propertyUpdates = [
{
propertyPath: "minHeight",
propertyValue:
canvasHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
},
{
propertyPath: "bottomRow",
propertyValue:
canvasHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
},
];
containerLikeWidget.children.forEach((childWidgetId) => {
if (!widgetsToUpdate.hasOwnProperty(childWidgetId)) {
widgetsToUpdate[childWidgetId] = propertyUpdates;
}
});
}
}
}
}
}
log.debug("Dynamic height: Widgets to update:", { widgetsToUpdate });
log.debug("Auto height: Widgets to update:", { widgetsToUpdate });
if (Object.keys(widgetsToUpdate).length > 0) {
// Push all updates to the CanvasWidgetsReducer.
// Note that we're not calling `UPDATE_LAYOUT`
// as we don't need to trigger an eval
yield put(updateMultipleWidgetPropertiesAction(widgetsToUpdate));
resetAutoHeightUpdateQueue();
yield put(
generateAutoHeightLayoutTreeAction(shouldRecomputeContainers, false),
if (!action?.payload) {
// Push all updates to the CanvasWidgetsReducer.
// Note that we're not calling `UPDATE_LAYOUT`
// as we don't need to trigger an eval
yield put(updateMultipleWidgetPropertiesAction(widgetsToUpdate));
resetAutoHeightUpdateQueue();
yield put(
generateAutoHeightLayoutTreeAction(
false,
false,
shouldRecomputeContainers,
),
);
}
directlyMutateDOMNodes(
widgetsToUpdate as Record<
string,
Array<{ propertyPath: string; propertyValue: number }>
>,
widgetsMeasuredInPixels,
widgetCanvasOffsets,
);
}

View File

@ -18,7 +18,6 @@ import {
import {
MAIN_CONTAINER_WIDGET_ID,
RenderModes,
WidgetType,
} from "constants/WidgetConstants";
import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer";
import { DataTree, DataTreeWidget } from "entities/DataTree/dataTreeFactory";
@ -36,9 +35,6 @@ import {
createLoadingWidget,
} from "utils/widgetRenderUtils";
import { checkIsDropTarget } from "components/designSystems/appsmith/PositionedContainer";
import WidgetFactory, {
NonSerialisableWidgetConfigs,
} from "utils/WidgetFactory";
import { LOCAL_STORAGE_KEYS } from "utils/localStorage";
import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils";
@ -243,33 +239,6 @@ export const getCurrentPageName = createSelector(
?.pageName,
);
/**
* This returns the number of rows which is not occupied by a Canvas Widget within
* a parent container like widget of type widgetType
* For example, the Tabs Widget takes 4 rows for the tabs
* @param widgetType Type of widget
* @param props Widget properties
* @returns the offset in rows
*/
export const getCanvasHeightOffset = (
widgetType: WidgetType,
props: WidgetProps,
) => {
// Get the non serialisable configs for the widget type
const config:
| Record<NonSerialisableWidgetConfigs, unknown>
| undefined = WidgetFactory.nonSerialisableWidgetConfigMap.get(widgetType);
let offset = 0;
// If this widget has a registered canvasHeightOffset function
if (config?.canvasHeightOffset) {
// Run the function to get the offset value
offset = (config.canvasHeightOffset as (props: WidgetProps) => number)(
props,
);
}
return offset;
};
export const getWidgetCards = createSelector(
getWidgetConfigs,
(widgetConfigs: WidgetConfigReducerState) => {

View File

@ -867,10 +867,7 @@ export const transformDSL = (currentDSL: DSLWidget, newPage = false) => {
if (currentDSL.version === 19) {
currentDSL.snapColumns = GridDefaults.DEFAULT_GRID_COLUMNS;
currentDSL.snapRows = getCanvasSnapRows(
currentDSL.bottomRow,
currentDSL.detachFromLayout || false,
);
currentDSL.snapRows = getCanvasSnapRows(currentDSL.bottomRow);
if (!newPage) {
currentDSL = migrateToNewLayout(currentDSL);
}

View File

@ -272,20 +272,11 @@ export const widgetOperationParams = (
};
};
export const getCanvasSnapRows = (
bottomRow: number,
canExtend: boolean,
): number => {
export const getCanvasSnapRows = (bottomRow: number): number => {
const totalRows = Math.floor(
bottomRow / GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
);
// Canvas Widgets do not need to accommodate for widget and container padding.
// Only when they're extensible
if (canExtend) {
return totalRows;
}
// When Canvas widgets are not extensible
return totalRows - 1;
};

View File

@ -0,0 +1,200 @@
import { RenderModes } from "constants/WidgetConstants";
import { WidgetProps } from "widgets/BaseWidget";
import {
getCanvasBottomRow,
getCanvasWidgetHeightsToUpdate,
} from "./WidgetSizeUtils";
const DUMMY_WIDGET: WidgetProps = {
bottomRow: 0,
isLoading: false,
leftColumn: 0,
parentColumnSpace: 0,
parentRowSpace: 0,
renderMode: RenderModes.CANVAS,
rightColumn: 0,
topRow: 0,
type: "SKELETON_WIDGET",
version: 2,
widgetId: "",
widgetName: "",
};
it("Computes the bottomRow of the canvas within a container correctly", () => {
const canvasWidgets = {
x: {
...DUMMY_WIDGET,
widgetId: "x",
bottomRow: 20,
topRow: 10,
type: "CONTAINER_WIDGET",
children: ["m"],
},
m: {
...DUMMY_WIDGET,
widgetId: "m",
parentId: "x",
children: ["n", "o"],
type: "CANVAS_WIDGET",
},
n: {
...DUMMY_WIDGET,
bottomRow: 30,
},
o: {
...DUMMY_WIDGET,
bottomRow: 5,
},
};
const result = getCanvasBottomRow("m", canvasWidgets);
expect(result).toBe(300);
});
it("Computes the bottomRow of the canvas within a Modal correctly", () => {
const canvasWidgets = {
x: {
...DUMMY_WIDGET,
widgetId: "x",
bottomRow: 20,
topRow: 10,
height: 200,
type: "MODAL_WIDGET",
children: ["m"],
},
m: {
...DUMMY_WIDGET,
widgetId: "m",
parentId: "x",
children: ["n", "o"],
type: "CANVAS_WIDGET",
},
n: {
...DUMMY_WIDGET,
parentId: "m",
bottomRow: 30,
},
o: {
...DUMMY_WIDGET,
parentId: "m",
bottomRow: 5,
},
};
const result = getCanvasBottomRow("m", canvasWidgets);
expect(result).toBe(300);
});
it("Computes the bottomRow of the canvas within a Modal correctly", () => {
const canvasWidgets = {
x: {
...DUMMY_WIDGET,
widgetId: "x",
bottomRow: 20,
topRow: 10,
height: 500,
type: "MODAL_WIDGET",
children: ["m"],
},
m: {
...DUMMY_WIDGET,
widgetId: "m",
parentId: "x",
children: ["n", "o"],
type: "CANVAS_WIDGET",
},
n: {
...DUMMY_WIDGET,
bottomRow: 30,
},
o: {
...DUMMY_WIDGET,
bottomRow: 5,
},
};
const result = getCanvasBottomRow("m", canvasWidgets);
expect(result).toBe(500);
});
it("Computes the bottomRow of the canvas within a Container when the container has larger height correctly", () => {
// The Container widget has a height of 10 rows, while the lowest widget is at 6 rows, so, the canvas should take this into account
// and return 10 * Row height == 100
const canvasWidgets = {
x: {
...DUMMY_WIDGET,
widgetId: "x",
bottomRow: 20,
topRow: 10,
type: "CONTAINER_WIDGET",
children: ["m"],
},
m: {
...DUMMY_WIDGET,
widgetId: "m",
parentId: "x",
children: ["n", "o"],
type: "CANVAS_WIDGET",
},
n: {
...DUMMY_WIDGET,
bottomRow: 6,
},
o: {
...DUMMY_WIDGET,
bottomRow: 5,
},
};
const result = getCanvasBottomRow("m", canvasWidgets);
expect(result).toBe(100);
});
it("Computes all the effected canvases for the changed widgets", () => {
const canvasWidgets = {
x: {
...DUMMY_WIDGET,
widgetId: "x",
bottomRow: 20,
topRow: 10,
type: "CONTAINER_WIDGET",
children: ["m"],
},
m: {
...DUMMY_WIDGET,
widgetId: "m",
parentId: "x",
children: ["n", "o", "p"],
type: "CANVAS_WIDGET",
},
n: {
...DUMMY_WIDGET,
bottomRow: 6,
},
o: {
...DUMMY_WIDGET,
bottomRow: 5,
},
p: {
...DUMMY_WIDGET,
widgetId: "p",
bottomRow: 20,
topRow: 10,
type: "CONTAINER_WIDGET",
children: ["q"],
parentId: "m",
},
q: {
...DUMMY_WIDGET,
widgetId: "q",
parentId: "p",
children: [],
type: "CANVAS_WIDGET",
},
};
// Since the container p has changed, it will effect the parent m and the child q
const result = getCanvasWidgetHeightsToUpdate(["p"], canvasWidgets);
expect(result).toStrictEqual({ q: 100, m: 200 });
});

View File

@ -0,0 +1,174 @@
import { CANVAS_DEFAULT_MIN_HEIGHT_PX } from "constants/AppConstants";
import {
GridDefaults,
MAIN_CONTAINER_WIDGET_ID,
} from "constants/WidgetConstants";
import { WidgetProps } from "widgets/BaseWidget";
import { FlattenedWidgetProps } from "widgets/constants";
import WidgetFactory, {
NonSerialisableWidgetConfigs,
WidgetType,
} from "./WidgetFactory";
/**
* This returns the number of rows which is not occupied by a Canvas Widget within
* a parent container like widget of type widgetType
* For example, the Tabs Widget takes 4 rows for the tabs
* @param widgetType Type of widget
* @param props Widget properties
* @returns the offset in rows
*/
export const getCanvasHeightOffset = (
widgetType: WidgetType,
props: WidgetProps,
) => {
// Get the non serialisable configs for the widget type
const config:
| Record<NonSerialisableWidgetConfigs, unknown>
| undefined = WidgetFactory.nonSerialisableWidgetConfigMap.get(widgetType);
let offset = 0;
// If this widget has a registered canvasHeightOffset function
if (config?.canvasHeightOffset) {
// Run the function to get the offset value
offset = (config.canvasHeightOffset as (props: WidgetProps) => number)(
props,
);
}
return offset;
};
/**
* This function computes the heights of canvas widgets which may be effected by the changes in other widget properties (updatedWidgetIds)
* @param updatedWidgetIds Widgets which have updated
* @param canvasWidgets The widgets in the redux state, used for computations
* @returns A list of canvas widget ids with their heights in pixels
*/
export function getCanvasWidgetHeightsToUpdate(
updatedWidgetIds: string[],
canvasWidgets: Record<string, FlattenedWidgetProps>,
): Record<string, number> {
const updatedCanvasWidgets: Record<string, number> = {};
for (const widgetId of updatedWidgetIds) {
const widget = canvasWidgets[widgetId];
if (widget) {
if (
widget.type !== "CANVAS_WIDGET" &&
Array.isArray(widget.children) &&
widget.children.length > 0
) {
for (const childCanvasWidgetId of widget.children) {
if (!updatedCanvasWidgets.hasOwnProperty(childCanvasWidgetId)) {
const bottomRow = getCanvasBottomRow(
childCanvasWidgetId,
canvasWidgets,
);
if (bottomRow > 0) {
updatedCanvasWidgets[childCanvasWidgetId] = bottomRow;
}
}
}
}
if (widget.parentId) {
if (!updatedCanvasWidgets.hasOwnProperty(widget.parentId)) {
const bottomRow = getCanvasBottomRow(widget.parentId, canvasWidgets);
if (bottomRow > 0) updatedCanvasWidgets[widget.parentId] = bottomRow;
}
}
} else {
// This usually means, that we're deleting a widget.
if (!updatedCanvasWidgets.hasOwnProperty(MAIN_CONTAINER_WIDGET_ID)) {
const bottomRow = getCanvasBottomRow(
MAIN_CONTAINER_WIDGET_ID,
canvasWidgets,
);
if (bottomRow > 0)
updatedCanvasWidgets[MAIN_CONTAINER_WIDGET_ID] = bottomRow;
}
}
}
return updatedCanvasWidgets;
}
/**
* A function to compute the height of a given canvas widget (canvasWidgetId) in pixels
* @param canvasWidgetId The CANVAS_WIDGET's widgetId. This canvas widget is the one whose bottomRow we need to compute
* @param canvasWidgets The widgets in the redux state. We use this to get appropriate info regarding types, parent and children for computations
* @returns The canvas widget's height in pixels (this is also the minHight and bottomRow property values)
*/
export function getCanvasBottomRow(
canvasWidgetId: string,
canvasWidgets: Record<string, FlattenedWidgetProps>,
) {
const canvasWidget = canvasWidgets[canvasWidgetId];
// If this widget is not defined
// It is likely a part of the list widget's canvases
if (canvasWidget === undefined) {
return 0;
}
// If this widget is not a CANVAS_WIDGET
if (canvasWidget.type !== "CANVAS_WIDGET") {
return canvasWidget.bottomRow;
}
const children = canvasWidget.children;
let parentHeightInRows = Math.ceil(
canvasWidget.bottomRow / GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
);
// Hypothetical thoughts:
// If this is the MainContainer
// We need some special handling.
// What we can do is use the viewport height and compute the minimum using that
// in the edit mode
// In the view mode, we can do the same?
// This is because, we might have changed the "bottomRow" somewhere and that will
// cause it to consider that value, and give us a large scroll.
if (canvasWidget.parentId) {
const parentWidget = canvasWidgets[canvasWidget.parentId];
// If the parent widget is undefined but the parentId exists
// It is likely a part of the list widget
if (parentWidget === undefined) {
return 0;
}
// If the parent is list widget, let's return the canvasWidget.bottomRow
// We'll be handling this specially in withWidgetProps
if (parentWidget.type === "LIST_WIDGET") {
return canvasWidget.bottomRow;
}
// Widgets like Tabs widget have an offset we need to subtract
const parentHeightOffset = getCanvasHeightOffset(
parentWidget.type,
parentWidget,
);
// The parent's height in rows
parentHeightInRows = parentWidget.bottomRow - parentWidget.topRow;
// If the parent is modal widget, we need to consider the `height` instead
// of the bottomRow
// TODO(abhinav): We could use one or the other and not have both, maybe
// update the bottomRow of the modal widget instead?
if (parentWidget.type === "MODAL_WIDGET" && parentWidget.height) {
parentHeightInRows = Math.floor(
parentWidget.height / GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
);
}
// Subtract the canvas offset due to some parent elements
parentHeightInRows = parentHeightInRows - parentHeightOffset;
} else {
parentHeightInRows =
CANVAS_DEFAULT_MIN_HEIGHT_PX / GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
}
if (Array.isArray(children) && children.length > 0) {
const bottomRow = children.reduce((prev, next) => {
return canvasWidgets[next].bottomRow > prev
? canvasWidgets[next].bottomRow
: prev;
}, parentHeightInRows);
return bottomRow * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
}
return parentHeightInRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
}

View File

@ -0,0 +1,43 @@
import { getNodesAndStylesToUpdate } from "./mutateDOM";
describe("DOM mutations", () => {
it("Computes the correct values to update based on widgetsToUpdate", () => {
const widgetsToUpdate = {
m: [
{
propertyValue: 100,
propertyPath: "bottomRow",
},
{
propertyValue: 10,
propertyPath: "topRow",
},
],
n: [
{
propertyValue: 100,
propertyPath: "bottomRow",
},
],
o: [
{
propertyValue: 100,
propertyPath: "bottomRow",
},
{
propertyValue: 0,
propertyPath: "topRow",
},
],
};
const widgetsMeasuredInPixels = ["n", "o"];
const result = getNodesAndStylesToUpdate(
widgetsToUpdate,
widgetsMeasuredInPixels,
);
expect(result).toStrictEqual({
m: { y: 106, height: 900 },
n: { y: 6, height: 100 }, // Since we're absolutely positioning the canvas widges, this y value is correct
o: { y: 6, height: 100 },
});
});
});

View File

@ -0,0 +1,130 @@
import {
CONTAINER_GRID_PADDING,
GridDefaults,
MAIN_CONTAINER_WIDGET_ID,
} from "constants/WidgetConstants";
import { Classes } from "@blueprintjs/core";
// Here the data structure is the `widgetsToUpdate` data structure. If possible, we should create the `updates`
// we use in the function directly in the `widgets.ts` (auto height saga)
// This way, we can avoid looping again in this place.
export function getNodesAndStylesToUpdate(
widgetsToUpdate: Record<
string,
Array<{ propertyPath: string; propertyValue: number }>
>,
widgetsMeasuredInPixels: string[],
): Record<string, Record<string, number>> {
const result: Record<string, Record<string, number>> = {};
// For each widget which has changes
for (const widgetId in widgetsToUpdate) {
const propertiesToUpdate: Record<string, number> = {};
// For each update in this widget
for (const propertyUpdate of widgetsToUpdate[widgetId]) {
// add to the data structure which is a key value pair.
propertiesToUpdate[propertyUpdate.propertyPath] =
propertyUpdate.propertyValue;
}
// Start with top and height as 0
let height = 0;
let y = 0;
// If topRow is not defined, it is most likely the main container
if (propertiesToUpdate.topRow === undefined) {
propertiesToUpdate.topRow = 0;
}
// If we have already measured in pixels, we don't need to multiply with row height
const multiplier = widgetsMeasuredInPixels.includes(widgetId)
? 1
: GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
if (propertiesToUpdate.height) {
height = propertiesToUpdate.height * multiplier;
} else {
height =
(propertiesToUpdate.bottomRow - propertiesToUpdate.topRow) * multiplier;
}
y = propertiesToUpdate.topRow * multiplier + CONTAINER_GRID_PADDING;
result[widgetId] = { y, height };
}
return result;
}
export function directlyMutateDOMNodes(
widgetsToUpdate: Record<
string,
Array<{ propertyPath: string; propertyValue: number }>
>,
widgetsMeasuredInPixels: string[],
widgetCanvasOffsets: Record<string, number>,
): void {
const updates: Record<
string,
Record<string, number>
> = getNodesAndStylesToUpdate(widgetsToUpdate, widgetsMeasuredInPixels);
for (const widgetId in updates) {
let idSelector = widgetId;
let height = updates[widgetId].height;
let skipTop = false;
// Special handling for main container
if (widgetId === MAIN_CONTAINER_WIDGET_ID) {
// The element we need to resize is the the art-board
idSelector = "art-board";
// The height has to match the dropTarget, which means we need to make sure we adjust for padding
// and the fact that drop target uses rows - 1 as default.
// so, this is expected height - padding on top - padding at bottom - 1 row height
height =
updates[widgetId].height -
CONTAINER_GRID_PADDING * 2 -
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
// Special handling for Modal widget 🤢
// TODO(abhinav): We need to re-structure the modal widget, possibly get away from blueprintjs
// Better yet, find a way that doesn't have to deal with these abstraction leaks.
}
let widgetNode = document.getElementById(idSelector);
if (
widgetsMeasuredInPixels.indexOf(widgetId) > -1 &&
widgetId !== MAIN_CONTAINER_WIDGET_ID
) {
// We need to select the modal widget's parent overlay to adjust size.
widgetNode = (widgetNode?.closest(`.${Classes.OVERLAY_CONTENT}`) ||
null) as HTMLElement | null;
// We don't mess with the top, and let the widget handle it.
skipTop = true;
}
const widgetBoundary = document.querySelector(
`.widget-boundary-${widgetId}`,
);
if (widgetBoundary) {
(widgetBoundary as HTMLDivElement).style.opacity = "0";
}
const dropTarget = widgetNode?.querySelector(`.drop-target-${widgetId}`);
if (widgetNode) {
widgetNode.style.height = `${height}px`;
// For some widgets the top is going to be useless,
// for example, modal widget and main container
if (!skipTop) {
widgetNode.style.top = `${updates[widgetId].y}px`;
}
if (dropTarget) {
const dropTargetHeight =
updates[widgetId].height -
CONTAINER_GRID_PADDING * 2 -
(widgetCanvasOffsets[widgetId] || 0) *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
(dropTarget as HTMLElement).style.height = `${dropTargetHeight}px`;
}
}
}
}

View File

@ -8,16 +8,45 @@ import {
isWidgetSelected,
shouldWidgetIgnoreClicksSelector,
} from "selectors/widgetSelectors";
import styled from "styled-components";
import { stopEventPropagation } from "utils/AppsmithUtils";
import { scrollCSS } from "widgets/WidgetUtils";
import { useWidgetSelection } from "./useWidgetSelection";
import { SelectionRequestType } from "sagas/WidgetSelectUtils";
import { Colors } from "constants/Colors";
const ContentWrapper = styled.div<{
backgroundColor?: string;
borderRadius?: string;
}>`
width: 100%;
height: 100%;
background: ${({ backgroundColor }) => `${backgroundColor || Colors.WHITE}`};
border-radius: ${({ borderRadius }) => borderRadius};
${scrollCSS}
`;
const ScrollWrapper = styled.div<{
backgroundColor?: string;
borderRadius?: string;
}>`
width: 100%;
height: 100%;
background: ${({ backgroundColor }) => `${backgroundColor || Colors.WHITE}`};
border-radius: ${({ borderRadius }) => borderRadius};
overflow: hidden;
`;
export function ClickContentToOpenPropPane({
backgroundColor,
borderRadius,
children,
widgetId,
}: {
widgetId: string;
children?: ReactNode;
backgroundColor?: string;
borderRadius?: string;
}) {
const { focusWidget } = useWidgetSelection();
@ -42,20 +71,20 @@ export function ClickContentToOpenPropPane({
e.stopPropagation();
};
const styles = {
width: "100%",
height: "100%",
};
return (
<div
onClick={stopEventPropagation}
onMouseDownCapture={clickToSelectWidget}
onMouseOver={handleMouseOver}
style={styles}
<ScrollWrapper
backgroundColor={backgroundColor}
borderRadius={borderRadius}
>
{children}
</div>
<ContentWrapper
className="scroll-parent"
onClick={stopEventPropagation}
onMouseDownCapture={clickToSelectWidget}
onMouseOver={handleMouseOver}
>
{children}
</ContentWrapper>
</ScrollWrapper>
);
}

View File

@ -72,25 +72,6 @@ const TooltipStyles = createGlobalStyle`
}
`;
/*
Don't use buttonHoverActiveStyles in a nested function it won't work -
const buttonHoverActiveStyles = css ``
const Button = styled.button`
// won't work
${({ buttonColor, theme }) => {
&:hover, &:active {
${buttonHoverActiveStyles}
}
}}
// will work
&:hover, &:active {
${buttonHoverActiveStyles}
}`
*/
const buttonBaseStyle = css<ThemeProp & ButtonStyleProps>`
height: 100%;
background-image: none !important;
@ -408,6 +389,10 @@ function RecaptchaV3Component(
);
}
const Wrapper = styled.div`
height: 100%;
`;
function BtnWrapper(
props: {
children: any;
@ -419,14 +404,14 @@ function BtnWrapper(
) {
if (!props.googleRecaptchaKey) {
return (
<div
<Wrapper
className={props.className}
onClick={(e: React.MouseEvent<HTMLElement>) =>
props.onClick && !props.isLoading && props.onClick(e)
}
>
{props.children}
</div>
</Wrapper>
);
} else {
const handleError = (

View File

@ -79,6 +79,7 @@ const CameraContainer = styled.div<CameraContainerProps>`
align-items: center;
justify-content: center;
overflow: hidden;
height: 100%;
border-radius: ${({ borderRadius }) => borderRadius};
box-shadow: ${({ boxShadow }) => boxShadow};
background: ${({ disabled }) => (disabled ? Colors.GREY_3 : Colors.BLACK)};

View File

@ -27,13 +27,22 @@ class CanvasWidget extends ContainerWidget {
containerStyle: "none",
detachFromLayout: true,
minHeight: this.props.minHeight || CANVAS_DEFAULT_MIN_HEIGHT_PX,
shouldScrollContents: false,
};
}
renderAsDropTarget() {
const canvasProps = this.getCanvasProps();
const { snapColumnSpace } = this.getSnapSpaces();
return (
<DropTargetComponent {...canvasProps} {...this.getSnapSpaces()}>
<DropTargetComponent
bottomRow={this.props.bottomRow}
minHeight={this.props.minHeight || CANVAS_DEFAULT_MIN_HEIGHT_PX}
noPad={this.props.noPad}
parentId={this.props.parentId}
snapColumnSpace={snapColumnSpace}
widgetId={this.props.widgetId}
>
{this.renderAsContainerComponent(canvasProps)}
</DropTargetComponent>
);
@ -56,7 +65,7 @@ class CanvasWidget extends ContainerWidget {
getPageView() {
let height = 0;
const snapRows = getCanvasSnapRows(this.props.bottomRow, false);
const snapRows = getCanvasSnapRows(this.props.bottomRow);
height = snapRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
const style: CSSProperties = {
width: "100%",

View File

@ -1,59 +1,54 @@
import React, { ReactNode, useRef, useEffect, RefObject } from "react";
import styled, { css } from "styled-components";
import tinycolor from "tinycolor2";
import { invisible } from "constants/DefaultTheme";
import { Color } from "constants/Colors";
import React, {
MouseEventHandler,
PropsWithChildren,
ReactNode,
RefObject,
useEffect,
useRef,
} from "react";
import styled from "styled-components";
import { generateClassName, getCanvasClassName } from "utils/generators";
import WidgetStyleContainer, {
WidgetStyleContainerProps,
} from "components/designSystems/appsmith/WidgetStyleContainer";
import { pick } from "lodash";
import { ComponentProps } from "widgets/BaseComponent";
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
const scrollContents = css`
overflow-y: auto;
`;
import tinycolor from "tinycolor2";
import { WidgetType } from "utils/WidgetFactory";
import { scrollCSS } from "widgets/WidgetUtils";
const StyledContainerComponent = styled.div<
ContainerComponentProps & {
ref: RefObject<HTMLDivElement>;
}
Omit<ContainerWrapperProps, "widgetId">
>`
height: 100%;
width: 100%;
background: ${(props) => props.backgroundColor};
overflow: hidden;
${(props) => (props.shouldScrollContents ? scrollCSS : ``)}
opacity: ${(props) => (props.resizeDisabled ? "0.8" : "1")};
position: relative;
${(props) => (!props.isVisible ? invisible : "")};
box-shadow: ${(props) =>
props.selected ? "inset 0px 0px 0px 3px rgba(59,130,246,0.5)" : "none"};
border-radius: ${({ borderRadius }) => borderRadius};
${(props) =>
props.shouldScrollContents === true
? scrollContents
: props.shouldScrollContents === false
? css`
overflow: hidden;
`
: ""}
background: ${(props) => props.backgroundColor};
&:hover {
z-index: ${(props) => (props.onClickCapture ? "2" : "1")};
cursor: ${(props) => (props.onClickCapture ? "pointer" : "inherit")};
background: ${(props) => {
background-color: ${(props) => {
return props.onClickCapture && props.backgroundColor
? tinycolor(props.backgroundColor)
.darken(5)
.toString()
: props.backgroundColor;
}};
z-index: ${(props) => (props.onClickCapture ? "2" : "1")};
cursor: ${(props) => (props.onClickCapture ? "pointer" : "inherit")};
}
`;
function ContainerComponentWrapper(props: ContainerComponentProps) {
const containerStyle = props.containerStyle || "card";
interface ContainerWrapperProps {
onClickCapture?: MouseEventHandler<HTMLDivElement>;
resizeDisabled?: boolean;
shouldScrollContents?: boolean;
backgroundColor?: string;
widgetId: string;
type: WidgetType;
}
function ContainerComponentWrapper(
props: PropsWithChildren<ContainerWrapperProps>,
) {
const containerRef: RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!props.shouldScrollContents) {
@ -70,15 +65,18 @@ function ContainerComponentWrapper(props: ContainerComponentProps) {
}, [props.shouldScrollContents]);
return (
<StyledContainerComponent
{...props}
// Before you remove: generateClassName is used for bounding the resizables within this canvas
// getCanvasClassName is used to add a scrollable parent.
backgroundColor={props.backgroundColor}
className={`${
props.shouldScrollContents ? getCanvasClassName() : ""
} ${generateClassName(props.widgetId)}`}
containerStyle={containerStyle}
} ${generateClassName(props.widgetId)} container-with-scrollbar`}
onClickCapture={props.onClickCapture}
ref={containerRef}
resizeDisabled={props.resizeDisabled}
shouldScrollContents={!!props.shouldScrollContents}
tabIndex={props.shouldScrollContents ? undefined : 0}
type={props.type}
>
{props.children}
</StyledContainerComponent>
@ -86,38 +84,55 @@ function ContainerComponentWrapper(props: ContainerComponentProps) {
}
function ContainerComponent(props: ContainerComponentProps) {
return props.widgetId === MAIN_CONTAINER_WIDGET_ID ? (
<ContainerComponentWrapper {...props} />
) : (
if (props.detachFromLayout) {
return (
<ContainerComponentWrapper
onClickCapture={props.onClickCapture}
resizeDisabled={props.resizeDisabled}
shouldScrollContents={props.shouldScrollContents}
type={props.type}
widgetId={props.widgetId}
>
{props.children}
</ContainerComponentWrapper>
);
}
return (
<WidgetStyleContainer
{...pick(props, [
"widgetId",
"containerStyle",
"backgroundColor",
"borderColor",
"borderWidth",
"borderRadius",
"boxShadow",
])}
backgroundColor={props.backgroundColor}
borderColor={props.borderColor}
borderRadius={props.borderRadius}
borderWidth={props.borderWidth}
boxShadow={props.boxShadow}
className="style-container"
containerStyle={props.containerStyle}
widgetId={props.widgetId}
>
<ContainerComponentWrapper {...props} />
<ContainerComponentWrapper
backgroundColor={props.backgroundColor}
onClickCapture={props.onClickCapture}
resizeDisabled={props.resizeDisabled}
shouldScrollContents={props.shouldScrollContents}
type={props.type}
widgetId={props.widgetId}
>
{props.children}
</ContainerComponentWrapper>
</WidgetStyleContainer>
);
}
export type ContainerStyle = "border" | "card" | "rounded-border" | "none";
export interface ContainerComponentProps
extends ComponentProps,
WidgetStyleContainerProps {
export interface ContainerComponentProps extends WidgetStyleContainerProps {
children?: ReactNode;
className?: string;
backgroundColor?: Color;
shouldScrollContents?: boolean;
resizeDisabled?: boolean;
selected?: boolean;
focused?: boolean;
minHeight?: number;
detachFromLayout?: boolean;
onClickCapture?: MouseEventHandler<HTMLDivElement>;
backgroundColor?: string;
type: WidgetType;
noScroll?: boolean;
}
export default ContainerComponent;

View File

@ -209,7 +209,8 @@ class ContainerWidget extends BaseWidget<
};
renderAsContainerComponent(props: ContainerWidgetProps<WidgetProps>) {
const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend);
const snapRows = getCanvasSnapRows(props.bottomRow);
return (
<ContainerComponent {...props}>
{props.type === "CANVAS_WIDGET" &&

View File

@ -8,6 +8,7 @@ import PickMyLocation from "./PickMyLocation";
const Wrapper = styled.div`
position: relative;
height: 100%;
`;
const StyledMap = styled.div<Pick<MapProps, "borderRadius" | "boxShadow">>`

View File

@ -25,7 +25,6 @@ import { getCanvasClassName } from "utils/generators";
import { AppState } from "@appsmith/reducers";
import { useWidgetDragResize } from "utils/hooks/dragResizeHooks";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { Colors } from "constants/Colors";
import { closeTableFilterPane } from "actions/widgetActions";
const Container = styled.div<{
@ -39,8 +38,6 @@ const Container = styled.div<{
maxWidth?: number;
minSize?: number;
isEditMode?: boolean;
backgroundColor: string;
borderRadius: string;
}>`
&&& {
.${Classes.OVERLAY} {
@ -78,9 +75,6 @@ const Container = styled.div<{
left: ${(props) => props.left}px;
bottom: ${(props) => props.bottom}px;
right: ${(props) => props.right}px;
background: ${({ backgroundColor }) =>
`${backgroundColor || Colors.WHITE}`};
border-radius: ${({ borderRadius }) => borderRadius};
}
}
}
@ -90,7 +84,6 @@ const Content = styled.div<{
scroll: boolean;
ref: RefObject<HTMLDivElement>;
}>`
overflow-y: ${(props) => (props.scroll ? "visible" : "hidden")};
overflow-x: hidden;
width: 100%;
height: 100%;
@ -131,8 +124,6 @@ export type ModalComponentProps = {
minSize?: number;
widgetId: string;
widgetName: string;
backgroundColor: string;
borderRadius: string;
isDynamicHeightEnabled: boolean;
};
@ -272,8 +263,6 @@ export default function ModalComponent(props: ModalComponentProps) {
usePortal={false}
>
<Container
backgroundColor={props.backgroundColor}
borderRadius={props.borderRadius}
bottom={props.bottom}
height={props.height}
isEditMode={props.isEditMode}

View File

@ -132,11 +132,9 @@ export class ModalWidget extends BaseWidget<ModalWidgetProps, WidgetState> {
renderChildWidget = (childWidgetData: WidgetProps): ReactNode => {
const childData = { ...childWidgetData };
childData.parentId = this.props.widgetId;
childData.shouldScrollContents = false;
childData.canExtend = this.props.shouldScrollContents;
childData.bottomRow = this.props.shouldScrollContents
? Math.max(childData.bottomRow, this.props.height)
: this.props.height;
childData.containerStyle = "none";
childData.minHeight = this.props.height;
childData.rightColumn =
@ -203,7 +201,11 @@ export class ModalWidget extends BaseWidget<ModalWidgetProps, WidgetState> {
makeModalSelectable(content: ReactNode): ReactNode {
// substitute coz the widget lacks draggable and position containers.
return (
<ClickContentToOpenPropPane widgetId={this.props.widgetId}>
<ClickContentToOpenPropPane
backgroundColor={this.props.backgroundColor}
borderRadius={this.props.borderRadius}
widgetId={this.props.widgetId}
>
{content}
</ClickContentToOpenPropPane>
);
@ -231,8 +233,6 @@ export class ModalWidget extends BaseWidget<ModalWidgetProps, WidgetState> {
return (
<ModalComponent
backgroundColor={this.props.backgroundColor}
borderRadius={this.props.borderRadius}
canEscapeKeyClose={!!this.props.canEscapeKeyClose}
canOutsideClickClose={!!this.props.canOutsideClickClose}
className={`t--modal-widget ${generateClassName(this.props.widgetId)}`}
@ -261,14 +261,7 @@ export class ModalWidget extends BaseWidget<ModalWidgetProps, WidgetState> {
let children = this.getChildren();
children = this.makeModalSelectable(children);
children = this.showWidgetName(children, true);
if (isAutoHeightEnabledForWidget(this.props, true)) {
children = this.addAutoHeightOverlay(children, {
width: "100%",
height: "100%",
left: 0,
top: 0,
});
}
return this.makeModalComponent(children, true);
}

View File

@ -15,6 +15,7 @@ export const SliderContainer = styled.div<{
padding-right: 0.4rem;
padding-left: ${({ labelPosition }) =>
labelPosition === LabelPosition.Top ? "0.4rem" : undefined};
height: 100%;
& .${LABEL_CONTAINER_CLASS} {
label {

View File

@ -8,6 +8,7 @@ import { isNaN } from "lodash";
const ProgressBarWrapper = styled.div`
display: flex;
align-items: center;
height: 100%;
`;
const ProgressBar = styled.div<{

View File

@ -128,6 +128,7 @@ const flowAnimation = css`
const ProgressContainer = styled.div`
display: flex;
align-items: center;
height: 100%;
`;
// Determinate Linear progress

View File

@ -1,19 +1,13 @@
import React, {
RefObject,
ReactNode,
useRef,
useState,
useCallback,
} from "react";
import styled, { css } from "styled-components";
import React, { ReactNode, useRef, useState, useCallback } from "react";
import styled from "styled-components";
import { MaybeElement } from "@blueprintjs/core";
import { IconName } from "@blueprintjs/icons";
import { ComponentProps } from "widgets/BaseComponent";
import { TabsWidgetProps, TabContainerWidgetProps } from "../constants";
import { Icon, IconSize } from "design-system-old";
import { generateClassName, getCanvasClassName } from "utils/generators";
import { Colors } from "constants/Colors";
import PageTabs from "./PageTabs";
import { scrollCSS } from "widgets/WidgetUtils";
interface TabsComponentProps extends ComponentProps {
children?: ReactNode;
@ -36,19 +30,7 @@ interface TabsComponentProps extends ComponentProps {
width: number;
}
type ChildrenWrapperProps = Pick<TabsComponentProps, "shouldShowTabs">;
const TAB_CONTAINER_HEIGHT = "44px";
const CHILDREN_WRAPPER_HEIGHT_WITH_TABS = `calc(100% - ${TAB_CONTAINER_HEIGHT})`;
const CHILDREN_WRAPPER_HEIGHT_WITHOUT_TABS = "100%";
const scrollContents = css`
overflow-y: auto;
position: absolute;
`;
const TabsContainerWrapper = styled.div<{
ref: RefObject<HTMLDivElement>;
borderRadius: string;
boxShadow?: string;
borderWidth?: number;
@ -72,26 +54,6 @@ const TabsContainerWrapper = styled.div<{
overflow: hidden;
`;
const ChildrenWrapper = styled.div<ChildrenWrapperProps>`
position: relative;
height: ${({ shouldShowTabs }) =>
shouldShowTabs
? CHILDREN_WRAPPER_HEIGHT_WITH_TABS
: CHILDREN_WRAPPER_HEIGHT_WITHOUT_TABS};
width: 100%;
`;
const ScrollableCanvasWrapper = styled.div<
TabsWidgetProps<TabContainerWidgetProps> & {
ref: RefObject<HTMLDivElement>;
}
>`
width: 100%;
height: 100%;
overflow: hidden;
${(props) => (props.shouldScrollContents ? scrollContents : "")}
`;
export interface TabsContainerProps {
isScrollable: boolean;
}
@ -99,7 +61,7 @@ export interface TabsContainerProps {
const Container = styled.div`
width: 100%;
align-items: flex-end;
height: 44px;
height: 40px;
& {
svg path,
@ -115,7 +77,7 @@ const ScrollBtnContainer = styled.div<{ visible: boolean }>`
cursor: pointer;
display: flex;
position: absolute;
height: 34px;
height: 30px;
padding: 0 10px;
& > span {
@ -146,12 +108,15 @@ export interface ScrollNavControlProps {
className?: string;
}
const ScrollCanvas = styled.div<{ $shouldScrollContents: boolean }>`
overflow: hidden;
${(props) => (props.$shouldScrollContents ? scrollCSS : ``)}
width: 100%;
`;
function TabsComponent(props: TabsComponentProps) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { onTabChange, tabs, width, ...remainingProps } = props;
const tabContainerRef: RefObject<HTMLDivElement> = useRef<HTMLDivElement>(
null,
);
const { onTabChange, tabs } = props;
const tabsRef = useRef<HTMLElement | null>(null);
const [tabsScrollable, setTabsScrollable] = useState(false);
const [shouldShowLeftArrow, setShouldShowLeftArrow] = useState(false);
@ -198,7 +163,6 @@ function TabsComponent(props: TabsComponentProps) {
borderRadius={props.borderRadius}
borderWidth={props.borderWidth}
boxShadow={props.boxShadow}
ref={tabContainerRef}
>
{props.shouldShowTabs && (
<Container className="relative flex px-6 h-9">
@ -229,16 +193,14 @@ function TabsComponent(props: TabsComponentProps) {
</Container>
)}
<ChildrenWrapper shouldShowTabs={props.shouldShowTabs}>
<ScrollableCanvasWrapper
{...remainingProps}
className={`${
props.shouldScrollContents ? getCanvasClassName() : ""
} ${generateClassName(props.widgetId)}`}
>
{props.children}
</ScrollableCanvasWrapper>
</ChildrenWrapper>
<ScrollCanvas
$shouldScrollContents={!!props.shouldScrollContents}
className={`${
props.shouldScrollContents ? getCanvasClassName() : ""
} ${generateClassName(props.widgetId)}`}
>
{props.children}
</ScrollCanvas>
</TabsContainerWrapper>
);
}

View File

@ -17,7 +17,7 @@ export const CONFIG = {
// define them in a Map which the platform understands to have
// them stored only in the WidgetFactory.
canvasHeightOffset: (props: WidgetProps): number =>
props.shouldShowTabs === true ? 5 : 0,
props.shouldShowTabs === true ? 4 : 0,
features: {
dynamicHeight: {
sectionIndex: 1,

View File

@ -23,7 +23,7 @@ import {
ButtonBorderRadiusTypes,
} from "components/constants";
import tinycolor from "tinycolor2";
import { createGlobalStyle } from "styled-components";
import { createGlobalStyle, css } from "styled-components";
import { Classes } from "@blueprintjs/core";
import { Classes as DTClasses } from "@blueprintjs/datetime";
import { BoxShadowTypes } from "components/designSystems/appsmith/WidgetStyleContainer";
@ -863,3 +863,21 @@ export function shouldUpdateWidgetHeightAutomatically(
// If we reach this point, we don't have to change height
return false;
}
// This is to be applied to only those widgets which will scroll for example, container widget, etc.
// But this won't apply to CANVAS_WIDGET.
export const scrollCSS = css`
position: relative;
overflow-y: auto;
overflow-x: hidden;
overflow-y: overlay;
scrollbar-color: #cccccc transparent;
scroolbar-width: thin;
&::-webkit-scrollbar-thumb {
background: #cccccc !important;
}
&::-webkit-scrollbar-track {
background: transparent !important;
}
`;

View File

@ -3,6 +3,7 @@ import React from "react";
import BaseWidget, { WidgetProps } from "./BaseWidget";
import {
GridDefaults,
MAIN_CONTAINER_WIDGET_ID,
RenderModes,
} from "constants/WidgetConstants";
@ -26,6 +27,7 @@ import {
} from "utils/widgetRenderUtils";
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import { checkContainersForAutoHeightAction } from "actions/autoHeightActions";
import { CANVAS_DEFAULT_MIN_HEIGHT_PX } from "constants/AppConstants";
const WIDGETS_WITH_CHILD_WIDGETS = ["LIST_WIDGET", "FORM_WIDGET"];
@ -62,7 +64,31 @@ function withWidgetProps(WrappedWidget: typeof BaseWidget) {
if (!skipWidgetPropsHydration) {
const canvasWidgetProps = (() => {
if (widgetId === MAIN_CONTAINER_WIDGET_ID) {
return computeMainContainerWidget(canvasWidget, mainCanvasProps);
const computed = computeMainContainerWidget(
canvasWidget,
mainCanvasProps,
);
if (renderMode === RenderModes.CANVAS) {
return {
...computed,
bottomRow: Math.max(
computed.minHeight,
computed.bottomRow +
GridDefaults.MAIN_CANVAS_EXTENSION_OFFSET *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
),
};
} else {
return {
...computed,
bottomRow: Math.max(
CANVAS_DEFAULT_MIN_HEIGHT_PX,
computed.bottomRow +
GridDefaults.VIEW_MODE_MAIN_CANVAS_EXTENSION_OFFSET *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
),
};
}
}
return evaluatedWidget
@ -91,10 +117,11 @@ function withWidgetProps(WrappedWidget: typeof BaseWidget) {
props.noPad && props.dropDisabled && props.openParentPropertyPane;
widgetProps.rightColumn = props.rightColumn;
if (widgetProps.bottomRow === undefined || isListWidgetCanvas) {
if (isListWidgetCanvas) {
widgetProps.bottomRow = props.bottomRow;
widgetProps.minHeight = props.minHeight;
}
widgetProps.shouldScrollContents = props.shouldScrollContents;
widgetProps.canExtend = props.canExtend;
widgetProps.parentId = props.parentId;
@ -102,7 +129,6 @@ function withWidgetProps(WrappedWidget: typeof BaseWidget) {
widgetProps.parentColumnSpace = props.parentColumnSpace;
widgetProps.parentRowSpace = props.parentRowSpace;
widgetProps.parentId = props.parentId;
// Form Widget Props
widgetProps.onReset = props.onReset;
if ("isFormValid" in props) widgetProps.isFormValid = props.isFormValid;