diff --git a/app/client/cypress/fixtures/longCanvasDsl.json b/app/client/cypress/fixtures/longCanvasDsl.json index a316bac7c1..705d14dd14 100644 --- a/app/client/cypress/fixtures/longCanvasDsl.json +++ b/app/client/cypress/fixtures/longCanvasDsl.json @@ -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, diff --git a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js index 5e40014bf9..963b13901b 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js @@ -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(); diff --git a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/OtherUIFeatures/Resize_spec.js b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/OtherUIFeatures/Resize_spec.js index 7e93e8a296..5253bc47b1 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/OtherUIFeatures/Resize_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/OtherUIFeatures/Resize_spec.js @@ -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. ); }); }); diff --git a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Templates/Fork_Template_Existing_app_spec.js b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Templates/Fork_Template_Existing_app_spec.js index bd30c62922..8477e980cc 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Templates/Fork_Template_Existing_app_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Templates/Fork_Template_Existing_app_spec.js @@ -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); diff --git a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Templates/Fork_Template_To_App_spec.js b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Templates/Fork_Template_To_App_spec.js index e178a43760..a437f55c99 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Templates/Fork_Template_To_App_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Templates/Fork_Template_To_App_spec.js @@ -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", diff --git a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/WidgetCanvas_spec.js b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/WidgetCanvas_spec.js index 115a410253..6b29623b2e 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/WidgetCanvas_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/WidgetCanvas_spec.js @@ -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}`); diff --git a/app/client/cypress/integration/Regression_TestSuite/ServerSideTests/ApiTests/CurlImportFlow_spec.js b/app/client/cypress/integration/Regression_TestSuite/ServerSideTests/ApiTests/CurlImportFlow_spec.js index 15489a89d2..5372512a9f 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ServerSideTests/ApiTests/CurlImportFlow_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ServerSideTests/ApiTests/CurlImportFlow_spec.js @@ -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); diff --git a/app/client/cypress/locators/FormWidgets.json b/app/client/cypress/locators/FormWidgets.json index f2169c3b03..1782c6e776 100644 --- a/app/client/cypress/locators/FormWidgets.json +++ b/app/client/cypress/locators/FormWidgets.json @@ -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']", diff --git a/app/client/cypress/locators/Layout.json b/app/client/cypress/locators/Layout.json index ac5cf782e4..5155e19bc0 100644 --- a/app/client/cypress/locators/Layout.json +++ b/app/client/cypress/locators/Layout.json @@ -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", diff --git a/app/client/cypress/locators/Widgets.json b/app/client/cypress/locators/Widgets.json index e77246a725..da0e9e684a 100644 --- a/app/client/cypress/locators/Widgets.json +++ b/app/client/cypress/locators/Widgets.json @@ -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", diff --git a/app/client/generators/widget/templates/index.js.hbs b/app/client/generators/widget/templates/index.js.hbs index 99ca3872db..b8ab397b80 100644 --- a/app/client/generators/widget/templates/index.js.hbs +++ b/app/client/generators/widget/templates/index.js.hbs @@ -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}}", diff --git a/app/client/src/actions/autoHeightActions.ts b/app/client/src/actions/autoHeightActions.ts index 72247d1180..b0428d6f6e 100644 --- a/app/client/src/actions/autoHeightActions.ts +++ b/app/client/src/actions/autoHeightActions.ts @@ -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, + }, }; } diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index 5233d9fcb6..2713358f71 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -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: diff --git a/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx b/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx index 59c0252008..9c04bc8e44 100644 --- a/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx +++ b/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx @@ -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; diff --git a/app/client/src/components/designSystems/appsmith/WidgetStyleContainer.tsx b/app/client/src/components/designSystems/appsmith/WidgetStyleContainer.tsx index 3671bb6545..a2befc9567 100644 --- a/app/client/src/components/designSystems/appsmith/WidgetStyleContainer.tsx +++ b/app/client/src/components/designSystems/appsmith/WidgetStyleContainer.tsx @@ -36,10 +36,10 @@ const WidgetStyle = styled.div` 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` function WidgetStyleContainer(props: WidgetStyleContainerProps) { return ( -
{props.children}
+ {props.children}
); } diff --git a/app/client/src/components/editorComponents/DragLayerComponent.tsx b/app/client/src/components/editorComponents/DragLayerComponent.tsx index 90af4ab322..2ec8e26ce5 100644 --- a/app/client/src/components/editorComponents/DragLayerComponent.tsx +++ b/app/client/src/components/editorComponents/DragLayerComponent.tsx @@ -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 ( ); } diff --git a/app/client/src/components/editorComponents/DraggableComponent.tsx b/app/client/src/components/editorComponents/DraggableComponent.tsx index aa80a3c963..e2315e05da 100644 --- a/app/client/src/components/editorComponents/DraggableComponent.tsx +++ b/app/client/src/components/editorComponents/DraggableComponent.tsx @@ -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 = ; - const classNameForTesting = `t--draggable-${props.type .split("_") .join("") @@ -195,7 +190,10 @@ function DraggableComponent(props: DraggableComponentProps) { style={dragWrapperStyle} > {shouldRenderComponent && props.children} - {widgetBoundaries} + ); } diff --git a/app/client/src/components/editorComponents/DropTargetComponent.tsx b/app/client/src/components/editorComponents/DropTargetComponent.tsx index 857cec1ba5..1f9b8ac42e 100644 --- a/app/client/src/components/editorComponents/DropTargetComponent.tsx +++ b/app/client/src/components/editorComponents/DropTargetComponent.tsx @@ -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, + 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(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(null); - return ( )} @@ -302,6 +292,4 @@ export function DropTargetComponent(props: DropTargetComponentProps) { ); } -const MemoizedDropTargetComponent = memo(DropTargetComponent); - -export default MemoizedDropTargetComponent; +export default DropTargetComponent; diff --git a/app/client/src/components/editorComponents/DropTargetUtils.ts b/app/client/src/components/editorComponents/DropTargetUtils.ts index ade93b0b2f..5425be0979 100644 --- a/app/client/src/components/editorComponents/DropTargetUtils.ts +++ b/app/client/src/components/editorComponents/DropTargetUtils.ts @@ -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); }; diff --git a/app/client/src/components/editorComponents/ErrorBoundry.tsx b/app/client/src/components/editorComponents/ErrorBoundry.tsx index 8cc96bee25..14e5d9d3a4 100644 --- a/app/client/src/components/editorComponents/ErrorBoundry.tsx +++ b/app/client/src/components/editorComponents/ErrorBoundry.tsx @@ -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 { render() { return ( - + {this.state.hasError ? (

Oops, Something went wrong. diff --git a/app/client/src/components/editorComponents/ResizableComponent.tsx b/app/client/src/components/editorComponents/ResizableComponent.tsx index eb1c5ba70f..aefe17e6bf 100644 --- a/app/client/src/components/editorComponents/ResizableComponent.tsx +++ b/app/client/src/components/editorComponents/ResizableComponent.tsx @@ -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 ( diff --git a/app/client/src/components/propertyControls/ColorPickerComponentV2.tsx b/app/client/src/components/propertyControls/ColorPickerComponentV2.tsx index 3ff8abaa07..30a1c243f2 100644 --- a/app/client/src/components/propertyControls/ColorPickerComponentV2.tsx +++ b/app/client/src/components/propertyControls/ColorPickerComponentV2.tsx @@ -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={ - + } onChange={handleChangeColor} onClick={handleInputClick} diff --git a/app/client/src/components/propertyControls/ColorPickerControl.tsx b/app/client/src/components/propertyControls/ColorPickerControl.tsx index b5bb2ae7d2..73af171c59 100644 --- a/app/client/src/components/propertyControls/ColorPickerControl.tsx +++ b/app/client/src/components/propertyControls/ColorPickerControl.tsx @@ -8,6 +8,7 @@ import { DS_EVENT, emitInteractionAnalyticsEvent, } from "utils/AppsmithUtils"; +import tinycolor from "tinycolor2"; class ColorPickerControl extends BaseControl { componentRef = React.createRef(); @@ -39,7 +40,10 @@ class ColorPickerControl extends BaseControl { }; 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() { diff --git a/app/client/src/pages/Editor/Canvas.tsx b/app/client/src/pages/Editor/Canvas.tsx index 343f2c10da..76a351fc62 100644 --- a/app/client/src/pages/Editor/Canvas.tsx +++ b/app/client/src/pages/Editor/Canvas.tsx @@ -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 ( { - 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 && ( - - )} ); } catch (error) { @@ -144,8 +70,6 @@ const Canvas = memo((props: CanvasProps) => { Sentry.captureException(error); return null; } -}); - -Canvas.displayName = "Canvas"; +}; export default Canvas; diff --git a/app/client/src/pages/Editor/EditorHeader.tsx b/app/client/src/pages/Editor/EditorHeader.tsx index 9bf3a97e7f..d8c6ec09b7 100644 --- a/app/client/src/pages/Editor/EditorHeader.tsx +++ b/app/client/src/pages/Editor/EditorHeader.tsx @@ -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}; diff --git a/app/client/src/pages/Editor/WidgetsMultiSelectBox.tsx b/app/client/src/pages/Editor/WidgetsMultiSelectBox.tsx index d703788575..a1812e25b6 100644 --- a/app/client/src/pages/Editor/WidgetsMultiSelectBox.tsx +++ b/app/client/src/pages/Editor/WidgetsMultiSelectBox.tsx @@ -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"] = { diff --git a/app/client/src/pages/common/CanvasArenas/StickyCanvasArena.tsx b/app/client/src/pages/common/CanvasArenas/StickyCanvasArena.tsx index 78d72d0ea2..23b519713a 100644 --- a/app/client/src/pages/common/CanvasArenas/StickyCanvasArena.tsx +++ b/app/client/src/pages/common/CanvasArenas/StickyCanvasArena.tsx @@ -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); + } }; }, []); diff --git a/app/client/src/reducers/entityReducers/canvasWidgetsReducer.ts b/app/client/src/reducers/entityReducers/canvasWidgetsReducer.ts index b23d823c35..4612d9a8de 100644 --- a/app/client/src/reducers/entityReducers/canvasWidgetsReducer.ts +++ b/app/client/src/reducers/entityReducers/canvasWidgetsReducer.ts @@ -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, ) => { - 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]; + } }, }); diff --git a/app/client/src/resizable/resizenreflow/index.tsx b/app/client/src/resizable/resizenreflow/index.tsx index 4341e5fe41..f62c08de3a 100644 --- a/app/client/src/resizable/resizenreflow/index.tsx +++ b/app/client/src/resizable/resizenreflow/index.tsx @@ -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)`, }} > diff --git a/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts b/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts index 0e84fb31ad..d21af3881b 100644 --- a/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts +++ b/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts @@ -88,7 +88,6 @@ export function* getCanvasSizeAfterWidgetMove( const newRows = calculateDropTargetRows( movedWidgetIds, movedWidgetsBottomRow, - canvasMinHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1, occupiedSpacesByChildren, canvasWidgetId, ); diff --git a/app/client/src/sagas/WidgetAdditionSagas.ts b/app/client/src/sagas/WidgetAdditionSagas.ts index f059d64f3f..e99f4af1cf 100644 --- a/app/client/src/sagas/WidgetAdditionSagas.ts +++ b/app/client/src/sagas/WidgetAdditionSagas.ts @@ -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; } diff --git a/app/client/src/sagas/WidgetDeletionSagas.ts b/app/client/src/sagas/WidgetDeletionSagas.ts index 8b76706921..dea8cf17f1 100644 --- a/app/client/src/sagas/WidgetDeletionSagas.ts +++ b/app/client/src/sagas/WidgetDeletionSagas.ts @@ -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)); diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index d85079373c..1947ebaee0 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -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) { try { Toaster.clear(); @@ -216,7 +167,7 @@ export function* resizeSaga(resizeAction: ReduxAction) { 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) { snapRowSpace, ); - movedWidgets = yield updateAllChildCanvasHeights( - widgetId, - topRow, - bottomRow, - movedWidgets, - ); - const updatedCanvasBottomRow: number = yield call( getCanvasSizeAfterWidgetMove, parentId, @@ -1352,7 +1296,6 @@ function* pasteWidgetSaga( const selectedWidget: FlattenedWidgetProps = 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, }, }; diff --git a/app/client/src/sagas/WidgetOperationUtils.test.ts b/app/client/src/sagas/WidgetOperationUtils.test.ts index d71032ae81..0e9da9adb6 100644 --- a/app/client/src/sagas/WidgetOperationUtils.test.ts +++ b/app/client/src/sagas/WidgetOperationUtils.test.ts @@ -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); diff --git a/app/client/src/sagas/WidgetOperationUtils.ts b/app/client/src/sagas/WidgetOperationUtils.ts index 023892c756..f1c5e287ec 100644 --- a/app/client/src/sagas/WidgetOperationUtils.ts +++ b/app/client/src/sagas/WidgetOperationUtils.ts @@ -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 diff --git a/app/client/src/sagas/autoHeightSagas/containers.ts b/app/client/src/sagas/autoHeightSagas/containers.ts index f919cc631b..9c261b89d4 100644 --- a/app/client/src/sagas/autoHeightSagas/containers.ts +++ b/app/client/src/sagas/autoHeightSagas/containers.ts @@ -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; } } } diff --git a/app/client/src/sagas/autoHeightSagas/helpers.ts b/app/client/src/sagas/autoHeightSagas/helpers.ts index 8f5c363f96..c933438032 100644 --- a/app/client/src/sagas/autoHeightSagas/helpers.ts +++ b/app/client/src/sagas/autoHeightSagas/helpers.ts @@ -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; } diff --git a/app/client/src/sagas/autoHeightSagas/index.ts b/app/client/src/sagas/autoHeightSagas/index.ts index 003817cff0..d5a02f7751 100644 --- a/app/client/src/sagas/autoHeightSagas/index.ts +++ b/app/client/src/sagas/autoHeightSagas/index.ts @@ -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, ), ]); diff --git a/app/client/src/sagas/autoHeightSagas/layoutTree.ts b/app/client/src/sagas/autoHeightSagas/layoutTree.ts index 9bfc651668..dee6d89538 100644 --- a/app/client/src/sagas/autoHeightSagas/layoutTree.ts +++ b/app/client/src/sagas/autoHeightSagas/layoutTree.ts @@ -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; diff --git a/app/client/src/sagas/autoHeightSagas/widgets.ts b/app/client/src/sagas/autoHeightSagas/widgets.ts index 8696418e8a..ba869dd41f 100644 --- a/app/client/src/sagas/autoHeightSagas/widgets.ts +++ b/app/client/src/sagas/autoHeightSagas/widgets.ts @@ -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, +) { 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; + + const widgetsMeasuredInPixels = []; + const widgetCanvasOffsets: Record = {}; // 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; + canvasLevelsMap: Record; + } = 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, ); } diff --git a/app/client/src/selectors/editorSelectors.tsx b/app/client/src/selectors/editorSelectors.tsx index 94eb78841b..4a9173dc1b 100644 --- a/app/client/src/selectors/editorSelectors.tsx +++ b/app/client/src/selectors/editorSelectors.tsx @@ -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 - | 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) => { diff --git a/app/client/src/utils/DSLMigrations.ts b/app/client/src/utils/DSLMigrations.ts index 1f308d7dbe..48e0dacf47 100644 --- a/app/client/src/utils/DSLMigrations.ts +++ b/app/client/src/utils/DSLMigrations.ts @@ -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); } diff --git a/app/client/src/utils/WidgetPropsUtils.tsx b/app/client/src/utils/WidgetPropsUtils.tsx index 2acd2e6ee1..20833ba67c 100644 --- a/app/client/src/utils/WidgetPropsUtils.tsx +++ b/app/client/src/utils/WidgetPropsUtils.tsx @@ -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; }; diff --git a/app/client/src/utils/WidgetSizeUtils.test.ts b/app/client/src/utils/WidgetSizeUtils.test.ts new file mode 100644 index 0000000000..1ba96f656f --- /dev/null +++ b/app/client/src/utils/WidgetSizeUtils.test.ts @@ -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 }); +}); diff --git a/app/client/src/utils/WidgetSizeUtils.ts b/app/client/src/utils/WidgetSizeUtils.ts new file mode 100644 index 0000000000..e00d0e5ad6 --- /dev/null +++ b/app/client/src/utils/WidgetSizeUtils.ts @@ -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 + | 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, +): Record { + const updatedCanvasWidgets: Record = {}; + 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, +) { + 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; +} diff --git a/app/client/src/utils/autoHeight/mutateDOM.test.ts b/app/client/src/utils/autoHeight/mutateDOM.test.ts new file mode 100644 index 0000000000..02774edfd7 --- /dev/null +++ b/app/client/src/utils/autoHeight/mutateDOM.test.ts @@ -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 }, + }); + }); +}); diff --git a/app/client/src/utils/autoHeight/mutateDOM.ts b/app/client/src/utils/autoHeight/mutateDOM.ts new file mode 100644 index 0000000000..5bf894cd8c --- /dev/null +++ b/app/client/src/utils/autoHeight/mutateDOM.ts @@ -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> { + const result: Record> = {}; + // For each widget which has changes + for (const widgetId in widgetsToUpdate) { + const propertiesToUpdate: Record = {}; + + // 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, +): void { + const updates: Record< + string, + Record + > = 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`; + } + } + } +} diff --git a/app/client/src/utils/hooks/useClickToSelectWidget.tsx b/app/client/src/utils/hooks/useClickToSelectWidget.tsx index 8f2dd8821c..55f62bd746 100644 --- a/app/client/src/utils/hooks/useClickToSelectWidget.tsx +++ b/app/client/src/utils/hooks/useClickToSelectWidget.tsx @@ -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 ( -

- {children} -
+ + {children} + + ); } diff --git a/app/client/src/widgets/ButtonWidget/component/index.tsx b/app/client/src/widgets/ButtonWidget/component/index.tsx index e241679fa8..be1e21b8f6 100644 --- a/app/client/src/widgets/ButtonWidget/component/index.tsx +++ b/app/client/src/widgets/ButtonWidget/component/index.tsx @@ -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` 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 ( -
) => props.onClick && !props.isLoading && props.onClick(e) } > {props.children} -
+ ); } else { const handleError = ( diff --git a/app/client/src/widgets/CameraWidget/component/index.tsx b/app/client/src/widgets/CameraWidget/component/index.tsx index 70727d99a0..2c2666ad84 100644 --- a/app/client/src/widgets/CameraWidget/component/index.tsx +++ b/app/client/src/widgets/CameraWidget/component/index.tsx @@ -79,6 +79,7 @@ const CameraContainer = styled.div` 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)}; diff --git a/app/client/src/widgets/CanvasWidget.tsx b/app/client/src/widgets/CanvasWidget.tsx index 3d4efcb629..cbcd592243 100644 --- a/app/client/src/widgets/CanvasWidget.tsx +++ b/app/client/src/widgets/CanvasWidget.tsx @@ -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 ( - + {this.renderAsContainerComponent(canvasProps)} ); @@ -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%", diff --git a/app/client/src/widgets/ContainerWidget/component/index.tsx b/app/client/src/widgets/ContainerWidget/component/index.tsx index 714d3e546a..333dde6e3d 100644 --- a/app/client/src/widgets/ContainerWidget/component/index.tsx +++ b/app/client/src/widgets/ContainerWidget/component/index.tsx @@ -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; - } + Omit >` 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; + resizeDisabled?: boolean; + shouldScrollContents?: boolean; + backgroundColor?: string; + widgetId: string; + type: WidgetType; +} +function ContainerComponentWrapper( + props: PropsWithChildren, +) { const containerRef: RefObject = useRef(null); useEffect(() => { if (!props.shouldScrollContents) { @@ -70,15 +65,18 @@ function ContainerComponentWrapper(props: ContainerComponentProps) { }, [props.shouldScrollContents]); return ( {props.children} @@ -86,38 +84,55 @@ function ContainerComponentWrapper(props: ContainerComponentProps) { } function ContainerComponent(props: ContainerComponentProps) { - return props.widgetId === MAIN_CONTAINER_WIDGET_ID ? ( - - ) : ( + if (props.detachFromLayout) { + return ( + + {props.children} + + ); + } + return ( - + + {props.children} + ); } 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; + backgroundColor?: string; + type: WidgetType; + noScroll?: boolean; } export default ContainerComponent; diff --git a/app/client/src/widgets/ContainerWidget/widget/index.tsx b/app/client/src/widgets/ContainerWidget/widget/index.tsx index dc1a71fb75..754eb58b05 100644 --- a/app/client/src/widgets/ContainerWidget/widget/index.tsx +++ b/app/client/src/widgets/ContainerWidget/widget/index.tsx @@ -209,7 +209,8 @@ class ContainerWidget extends BaseWidget< }; renderAsContainerComponent(props: ContainerWidgetProps) { - const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend); + const snapRows = getCanvasSnapRows(props.bottomRow); + return ( {props.type === "CANVAS_WIDGET" && diff --git a/app/client/src/widgets/MapWidget/component/Map.tsx b/app/client/src/widgets/MapWidget/component/Map.tsx index ce8e08cd74..7b23e1661c 100644 --- a/app/client/src/widgets/MapWidget/component/Map.tsx +++ b/app/client/src/widgets/MapWidget/component/Map.tsx @@ -8,6 +8,7 @@ import PickMyLocation from "./PickMyLocation"; const Wrapper = styled.div` position: relative; + height: 100%; `; const StyledMap = styled.div>` diff --git a/app/client/src/widgets/ModalWidget/component/index.tsx b/app/client/src/widgets/ModalWidget/component/index.tsx index 06fa00cf71..a3dbd0727a 100644 --- a/app/client/src/widgets/ModalWidget/component/index.tsx +++ b/app/client/src/widgets/ModalWidget/component/index.tsx @@ -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; }>` - 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} > { 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 { makeModalSelectable(content: ReactNode): ReactNode { // substitute coz the widget lacks draggable and position containers. return ( - + {content} ); @@ -231,8 +233,6 @@ export class ModalWidget extends BaseWidget { return ( { 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); } diff --git a/app/client/src/widgets/NumberSliderWidget/component/Container.tsx b/app/client/src/widgets/NumberSliderWidget/component/Container.tsx index 0ec3d5b20f..4dd51abe9a 100644 --- a/app/client/src/widgets/NumberSliderWidget/component/Container.tsx +++ b/app/client/src/widgets/NumberSliderWidget/component/Container.tsx @@ -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 { diff --git a/app/client/src/widgets/ProgressBarWidget/component/index.tsx b/app/client/src/widgets/ProgressBarWidget/component/index.tsx index b1cb7285f6..bae69fca05 100644 --- a/app/client/src/widgets/ProgressBarWidget/component/index.tsx +++ b/app/client/src/widgets/ProgressBarWidget/component/index.tsx @@ -8,6 +8,7 @@ import { isNaN } from "lodash"; const ProgressBarWrapper = styled.div` display: flex; align-items: center; + height: 100%; `; const ProgressBar = styled.div<{ diff --git a/app/client/src/widgets/ProgressWidget/component/index.tsx b/app/client/src/widgets/ProgressWidget/component/index.tsx index 4eef8b783e..23ebeb9725 100644 --- a/app/client/src/widgets/ProgressWidget/component/index.tsx +++ b/app/client/src/widgets/ProgressWidget/component/index.tsx @@ -128,6 +128,7 @@ const flowAnimation = css` const ProgressContainer = styled.div` display: flex; align-items: center; + height: 100%; `; // Determinate Linear progress diff --git a/app/client/src/widgets/TabsWidget/component/index.tsx b/app/client/src/widgets/TabsWidget/component/index.tsx index ca20b38594..928007dc66 100644 --- a/app/client/src/widgets/TabsWidget/component/index.tsx +++ b/app/client/src/widgets/TabsWidget/component/index.tsx @@ -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; - -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; borderRadius: string; boxShadow?: string; borderWidth?: number; @@ -72,26 +54,6 @@ const TabsContainerWrapper = styled.div<{ overflow: hidden; `; -const ChildrenWrapper = styled.div` - position: relative; - height: ${({ shouldShowTabs }) => - shouldShowTabs - ? CHILDREN_WRAPPER_HEIGHT_WITH_TABS - : CHILDREN_WRAPPER_HEIGHT_WITHOUT_TABS}; - width: 100%; -`; - -const ScrollableCanvasWrapper = styled.div< - TabsWidgetProps & { - ref: RefObject; - } ->` - 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 = useRef( - null, - ); + const { onTabChange, tabs } = props; + const tabsRef = useRef(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 && ( @@ -229,16 +193,14 @@ function TabsComponent(props: TabsComponentProps) { )} - - - {props.children} - - + + {props.children} + ); } diff --git a/app/client/src/widgets/TabsWidget/index.ts b/app/client/src/widgets/TabsWidget/index.ts index 2a50fcc023..4927827156 100644 --- a/app/client/src/widgets/TabsWidget/index.ts +++ b/app/client/src/widgets/TabsWidget/index.ts @@ -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, diff --git a/app/client/src/widgets/WidgetUtils.ts b/app/client/src/widgets/WidgetUtils.ts index f0fcfadd2d..28dd057b89 100644 --- a/app/client/src/widgets/WidgetUtils.ts +++ b/app/client/src/widgets/WidgetUtils.ts @@ -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; + } +`; diff --git a/app/client/src/widgets/withWidgetProps.tsx b/app/client/src/widgets/withWidgetProps.tsx index 104a7f4051..19f3587676 100644 --- a/app/client/src/widgets/withWidgetProps.tsx +++ b/app/client/src/widgets/withWidgetProps.tsx @@ -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;