fix: Switch to preview mode performance issue (#18457)

* In progress fixes for auto height perf

* Revert collapse logic

* Revert changes

* Remove console logs, and fix tests

* Fix scenario where container widgets don't collapse

* Bring back hidden widgets

* fix: Overlapping of widgets while reflowing other widgets (#18460)

* fix: api url z-index to show it above response panel (#18200)

* chore: Switched to sequential checks instead of a parallel one for RTS check (#18440)

fix: Switched to sequential checks instead of a parallel one

* chore: Added size limit check for the message before sending the data to segment (#18453)

* fix: Allowing multi form to json switching and eliminating json to form sw… (#18192)

* Allowing multi form to json switching and eliminating json to form switching unless form data is cleared

* Fix failing jest test case

Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>

* add missed width and height limits instead of incrementing depth by 1

Co-authored-by: Aman Agarwal <aman@appsmith.com>
Co-authored-by: Nidhi <nidhi@appsmith.com>
Co-authored-by: Anagh Hegde <anagh@appsmith.com>
Co-authored-by: Ayangade Adeoluwa <37867493+Irongade@users.noreply.github.com>
Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>

* fix: auto height with limits deselect widget (#18455)

* fix: api url z-index to show it above response panel (#18200)

* chore: Switched to sequential checks instead of a parallel one for RTS check (#18440)

fix: Switched to sequential checks instead of a parallel one

* chore: Added size limit check for the message before sending the data to segment (#18453)

* fix: Allowing multi form to json switching and eliminating json to form sw… (#18192)

* Allowing multi form to json switching and eliminating json to form switching unless form data is cleared

* Fix failing jest test case

Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>

* refactor overlay and handles state into ui reducer and added a check for is limits changing in the widgets editor as well

* added helpful comments at relevant places

Co-authored-by: Aman Agarwal <aman@appsmith.com>
Co-authored-by: Nidhi <nidhi@appsmith.com>
Co-authored-by: Anagh Hegde <anagh@appsmith.com>
Co-authored-by: Ayangade Adeoluwa <37867493+Irongade@users.noreply.github.com>
Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com>

* changes the label on limit handles collision to height

* fix: issues related to tabs widget in auto height (#18468)

* Fix issues related to tabs widget in auto height

* Fix issue where preview mode canvas did not scroll

* Fix scroll issues with fixed containers

* Fix issue where tabs widget computed the canvas height incorrectly

* Re-compute in case of tabs widget

* fix: widgets increase spacing  (#18462)

* Change how the spacing works when widgets push or pull widgets below

* Fix type issues in test file

* Fix tests to reflect changes

* Add comment to describe why we're generating distanceToNearestAbove

Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com>
Co-authored-by: Aman Agarwal <aman@appsmith.com>
Co-authored-by: Nidhi <nidhi@appsmith.com>
Co-authored-by: Anagh Hegde <anagh@appsmith.com>
Co-authored-by: Ayangade Adeoluwa <37867493+Irongade@users.noreply.github.com>
Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
Co-authored-by: ankurrsinghal <ankur@appsmith.com>
Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com>
This commit is contained in:
Abhinav Jha 2022-11-27 22:42:00 +05:30 committed by GitHub
parent 3144c0a452
commit b2070083a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 676 additions and 400 deletions

View File

@ -18,7 +18,7 @@ describe("Preview mode functionality", function() {
it("checks if widgets can be selected or not", function() {
// in preview mode, entity explorer and property pane are not visible
// Also, draggable and resizable components are not available.
const selector = `.t--widget-buttonwidget`;
const selector = `.t--draggable-buttonwidget`;
cy.wait(500);
cy.get(selector)
.first()

View File

@ -22,9 +22,7 @@ function AutoHeightContainerWrapper(props: AutoHeightWrapperProps) {
if (isCanvas) return <>{children}</>;
const onHeightUpdate = (height: number) => {
requestAnimationFrame(() => {
props.onUpdateDynamicHeight(height);
});
props.onUpdateDynamicHeight(height);
};
const maxDynamicHeight = getWidgetMaxAutoHeight(widgetProps);

View File

@ -151,9 +151,9 @@ const AutoHeightLimitHandleGroup: React.FC<AutoHeightLimitHandleGroupProps> = ({
cypressDataID="t--auto-height-overlay-handles-max"
height={maxY}
isActive={isMaxDotActive}
isColliding={isColliding}
isColliding={false}
isDragging={isMaxDotDragging}
label="Max-Height"
label={isColliding ? "Height" : "Max-Height"}
onDragCallbacks={onMaxLimitDragCallbacks}
onMouseHoverFunctions={onMaxLimitMouseHoverCallbacks}
/>

View File

@ -1,6 +1,11 @@
import { focusWidget } from "actions/widgetActions";
import React, { CSSProperties, memo, useEffect, useMemo } from "react";
import { useState } from "react";
import React, {
CSSProperties,
memo,
useEffect,
useMemo,
useReducer,
} from "react";
import { useSelector } from "react-redux";
import { AppState } from "@appsmith/reducers";
import styled from "styled-components";
@ -18,6 +23,11 @@ import { useHoverState, usePositionedStyles } from "./hooks";
import { getSnappedValues } from "./utils";
import { useAutoHeightUIState } from "utils/hooks/autoHeightUIHooks";
import { LayersContext } from "constants/Layers";
import {
AutoHeightOverlayUIStateReducer,
createInitialAutoHeightUIState,
} from "./store";
import { previewModeSelector } from "selectors/editorSelectors";
interface StyledAutoHeightOverlayProps {
layerIndex: number;
@ -72,41 +82,93 @@ const AutoHeightOverlay: React.FC<AutoHeightOverlayProps> = memo(
getParentToOpenSelector(props.widgetId),
);
const showTableFilterPane = useShowTableFilterPane();
const { setIsAutoHeightWithLimitsChanging } = useAutoHeightUIState();
const isAutoHeightWithLimitsChanging = useSelector(
(state: AppState) => state.ui.autoHeightUI.isAutoHeightWithLimitsChanging,
const {
isAutoHeightWithLimitsChanging,
setIsAutoHeightWithLimitsChanging,
} = useAutoHeightUIState();
const [autoHeightUIState, autoHeightUIStateDispatch] = useReducer(
AutoHeightOverlayUIStateReducer,
createInitialAutoHeightUIState({ maxDynamicHeight, minDynamicHeight }),
);
const [isMinDotDragging, setIsMinDotDragging] = useState(false);
const [isMaxDotDragging, setIsMaxDotDragging] = useState(false);
const {
isMaxDotDragging,
isMinDotDragging,
maxdY,
maxY,
mindY,
minY,
} = autoHeightUIState;
const [maxY, setMaxY] = useState(
maxDynamicHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
);
const [maxdY, setMaxdY] = useState(0);
function setIsMaxDotDragging(isMaxDotDragging: boolean) {
autoHeightUIStateDispatch({
type: "SET_IS_MAX_DOT_DRAGGING",
payload: {
isMaxDotDragging,
},
});
}
const [minY, setMinY] = useState(
minDynamicHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
);
const [mindY, setMindY] = useState(0);
function setIsMinDotDragging(isMinDotDragging: boolean) {
autoHeightUIStateDispatch({
type: "SET_IS_MIN_DOT_DRAGGING",
payload: {
isMinDotDragging,
},
});
}
function setMaxY(maxY: number) {
autoHeightUIStateDispatch({
type: "SET_MAX_Y",
payload: {
maxY,
},
});
}
function setMinY(minY: number) {
autoHeightUIStateDispatch({
type: "SET_MIN_Y",
payload: {
minY,
},
});
}
function setMaxdY(maxdY: number) {
autoHeightUIStateDispatch({
type: "SET_MAX_D_Y",
payload: {
maxdY,
},
});
}
function setMindY(mindY: number) {
autoHeightUIStateDispatch({
type: "SET_MIN_D_Y",
payload: {
mindY,
},
});
}
const finalMaxY = maxY + maxdY;
const finalMinY = minY + mindY;
// to be included when min and max fields are
// added back to the property pane
// const {
// isPropertyPaneMaxFieldFocused,
// isPropertyPaneMinFieldFocused,
// } = useMaxMinPropertyPaneFieldsFocused();
useEffect(() => {
setMaxY(maxDynamicHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT);
}, [maxDynamicHeight]);
function onAnyDotStop() {
setIsAutoHeightWithLimitsChanging &&
setIsAutoHeightWithLimitsChanging(false);
// Tell the Canvas that we've stopped resizing
// Put it later in the stack so that other updates like click, are not propagated to the parent container
setTimeout(() => {
setIsAutoHeightWithLimitsChanging &&
setIsAutoHeightWithLimitsChanging(false);
}, 0);
selectWidget && selectWidget(props.widgetId);
@ -317,11 +379,13 @@ const AutoHeightOverlayContainer: React.FC<AutoHeightOverlayContainerProps> = me
selectedWidgets,
} = useSelector((state: AppState) => state.ui.widgetDragResize);
const isPreviewMode = useSelector(previewModeSelector);
const isWidgetSelected = selectedWidget === widgetId;
const multipleWidgetsSelected = selectedWidgets.length > 1;
const isHidden = multipleWidgetsSelected || isDragging || isResizing;
if (isWidgetSelected) {
if (isWidgetSelected && !isPreviewMode) {
return <AutoHeightOverlay isHidden={isHidden} {...props} />;
}

View File

@ -0,0 +1,100 @@
import { GridDefaults } from "constants/WidgetConstants";
interface AutoHeightLimitsUIState {
isMaxDotDragging: boolean;
isMinDotDragging: boolean;
maxY: number; // the actual value
maxdY: number; // the difference during dragging
minY: number; // the actual value
mindY: number; // the difference during dragging
}
type SET_MAX_Y = { type: "SET_MAX_Y"; payload: { maxY: number } };
type SET_MIN_Y = { type: "SET_MIN_Y"; payload: { minY: number } };
type SET_MAX_D_Y = { type: "SET_MAX_D_Y"; payload: { maxdY: number } };
type SET_MIN_D_Y = { type: "SET_MIN_D_Y"; payload: { mindY: number } };
type SET_IS_MIN_DOT_DRAGGING = {
type: "SET_IS_MIN_DOT_DRAGGING";
payload: { isMinDotDragging: boolean };
};
type SET_IS_MAX_DOT_DRAGGING = {
type: "SET_IS_MAX_DOT_DRAGGING";
payload: { isMaxDotDragging: boolean };
};
type AutoHeightLimitsUIAction =
| SET_MAX_Y
| SET_MIN_Y
| SET_MAX_D_Y
| SET_MIN_D_Y
| SET_IS_MIN_DOT_DRAGGING
| SET_IS_MAX_DOT_DRAGGING;
export function AutoHeightOverlayUIStateReducer(
state: AutoHeightLimitsUIState,
action: AutoHeightLimitsUIAction,
) {
if (action.type === "SET_IS_MAX_DOT_DRAGGING") {
return {
...state,
isMaxDotDragging: action.payload.isMaxDotDragging,
};
}
if (action.type === "SET_IS_MIN_DOT_DRAGGING") {
return {
...state,
isMinDotDragging: action.payload.isMinDotDragging,
};
}
if (action.type === "SET_MAX_Y") {
return {
...state,
maxY: action.payload.maxY,
};
}
if (action.type === "SET_MIN_Y") {
return {
...state,
minY: action.payload.minY,
};
}
if (action.type === "SET_MAX_D_Y") {
return {
...state,
maxdY: action.payload.maxdY,
};
}
if (action.type === "SET_MIN_D_Y") {
return {
...state,
mindY: action.payload.mindY,
};
}
return state;
}
interface CreateInitialAutoHeightUIStateProps {
maxDynamicHeight: number;
minDynamicHeight: number;
}
export function createInitialAutoHeightUIState({
maxDynamicHeight,
minDynamicHeight,
}: CreateInitialAutoHeightUIStateProps) {
return {
isMinDotDragging: false,
isMaxDotDragging: false,
maxY: maxDynamicHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT, // the actual value
maxdY: 0, // the difference during dragging
minY: minDynamicHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT, // the actual value
mindY: 0, // the difference during dragging
};
}

View File

@ -2,9 +2,17 @@ import { canDrag } from "./DraggableComponent";
describe("DraggableComponent", () => {
it("it tests draggable canDrag helper function", () => {
expect(canDrag(false, false, { dragDisabled: false }, false)).toBe(true);
expect(canDrag(true, false, { dragDisabled: false }, false)).toBe(false);
expect(canDrag(false, true, { dragDisabled: false }, false)).toBe(false);
expect(canDrag(false, false, { dragDisabled: true }, false)).toBe(false);
expect(canDrag(false, false, { dragDisabled: false }, false, false)).toBe(
true,
);
expect(canDrag(true, false, { dragDisabled: false }, false, false)).toBe(
false,
);
expect(canDrag(false, true, { dragDisabled: false }, false, false)).toBe(
false,
);
expect(canDrag(false, false, { dragDisabled: true }, false, false)).toBe(
false,
);
});
});

View File

@ -9,7 +9,10 @@ import {
useShowTableFilterPane,
useWidgetDragResize,
} from "utils/hooks/dragResizeHooks";
import { snipingModeSelector } from "selectors/editorSelectors";
import {
previewModeSelector,
snipingModeSelector,
} from "selectors/editorSelectors";
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
import {
isCurrentWidgetFocused,
@ -56,12 +59,14 @@ export const canDrag = (
isDraggingDisabled: boolean,
props: any,
isSnipingMode: boolean,
isPreviewMode: boolean,
) => {
return (
!isResizingOrDragging &&
!isDraggingDisabled &&
!props?.dragDisabled &&
!isSnipingMode
!isSnipingMode &&
!isPreviewMode
);
};
@ -69,6 +74,7 @@ function DraggableComponent(props: DraggableComponentProps) {
// Dispatch hook handy to set a widget as focused/selected
const { focusWidget, selectWidget } = useWidgetSelection();
const isSnipingMode = useSelector(snipingModeSelector);
const isPreviewMode = useSelector(previewModeSelector);
// Dispatch hook handy to set any `DraggableComponent` as dragging/ not dragging
// The value is boolean
const { setDraggingCanvas, setDraggingState } = useWidgetDragResize();
@ -136,6 +142,7 @@ function DraggableComponent(props: DraggableComponentProps) {
isDraggingDisabled,
props,
isSnipingMode,
isPreviewMode,
);
const className = `${classNameForTesting}`;
const draggableRef = useRef<HTMLDivElement>(null);

View File

@ -24,9 +24,13 @@ import {
useShowPropertyPane,
useCanvasSnapRowsUpdateHook,
} from "utils/hooks/dragResizeHooks";
import { getOccupiedSpacesSelectorForContainer } from "selectors/editorSelectors";
import {
getOccupiedSpacesSelectorForContainer,
previewModeSelector,
} from "selectors/editorSelectors";
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
import { getDragDetails } from "sagas/selectors";
import { useAutoHeightUIState } from "utils/hooks/autoHeightUIHooks";
type DropTargetComponentProps = WidgetProps & {
children?: ReactNode;
@ -64,19 +68,52 @@ export const DropTargetContext: Context<{
) => number | false;
}> = createContext({});
export function DropTargetComponent(props: DropTargetComponentProps) {
const canDropTargetExtend = props.canExtend;
const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend);
/**
* 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.
*/
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;
}
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(
props.bottomRow,
props.canExtend && !isPreviewMode,
);
// Are we currently resizing?
const isResizing = useSelector(
(state: AppState) => state.ui.widgetDragResize.isResizing,
);
// Are we currently dragging?
const isDragging = useSelector(
(state: AppState) => state.ui.widgetDragResize.isDragging,
);
const isAutoHeightWithLimitsChanging = useSelector(
(state: AppState) => state.ui.autoHeightUI.isAutoHeightWithLimitsChanging,
);
// Are we changing the auto height limits by dragging the signifiers?
const { isAutoHeightWithLimitsChanging } = useAutoHeightUIState();
// dragDetails contains of info needed for a container jump:
// which parent the dragging widget belongs,
@ -87,37 +124,57 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
const { draggedOn } = dragDetails;
// All the widgets in this canvas
const childWidgets: string[] | undefined = useSelector(
(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();
const updateCanvasSnapRows = useCanvasSnapRowsUpdateHook();
const showDragLayer =
(isDragging && draggedOn === props.widgetId) ||
isResizing ||
isAutoHeightWithLimitsChanging;
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);
const snapRows = getCanvasSnapRows(
props.bottomRow,
props.canExtend && !isPreviewMode,
);
// If the current ref is not set to the new snaprows we've received (based on bottomRow)
if (rowRef.current !== snapRows) {
rowRef.current = snapRows;
// This sets the "height" property of the dropTarget div
// This makes the div change heights if new heights are different
updateHeight();
if (canDropTargetExtend) {
// 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]);
}, [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
@ -129,14 +186,38 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
}
}, [isDragging, isResizing, isAutoHeightWithLimitsChanging]);
// Update the drop target height style directly.
const updateHeight = () => {
if (dropTargetRef.current) {
const height = canDropTargetExtend
? `${Math.max(rowRef.current * props.snapRowSpace, props.minHeight)}px`
: "100%";
const height = getDropTargetHeight(
canDropTargetExtend,
isPreviewMode,
rowRef.current,
props.snapRowSpace,
props.minHeight,
);
dropTargetRef.current.style.height = height;
}
};
const handleFocus = (e: any) => {
// Making sure that we don't deselect the widget
// after we are done dragging the limits in auto height with limits
if (!isResizing && !isDragging && !isAutoHeightWithLimitsChanging) {
if (!props.parentId) {
deselectAll();
focusWidget && focusWidget(props.widgetId);
showPropertyPane && showPropertyPane();
}
}
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,
@ -158,23 +239,23 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
}
return false;
};
// memoizing context values
const contextValue = useMemo(() => {
return {
updateDropTargetRows,
};
}, [updateDropTargetRows, occupiedSpacesByChildren]);
const handleFocus = (e: any) => {
if (!isResizing && !isDragging && !isAutoHeightWithLimitsChanging) {
if (!props.parentId) {
deselectAll();
focusWidget && focusWidget(props.widgetId);
showPropertyPane && showPropertyPane();
}
}
// commenting this out to allow propagation of click events
// e.stopPropagation();
e.preventDefault();
};
/** EO PREPARE CONTEXT */
const height = getDropTargetHeight(
canDropTargetExtend,
isPreviewMode,
rowRef.current,
props.snapRowSpace,
props.minHeight,
);
const height = canDropTargetExtend
? `${Math.max(rowRef.current * props.snapRowSpace, props.minHeight)}px`
: "100%";
const boxShadow =
(isResizing || isDragging || isAutoHeightWithLimitsChanging) &&
props.widgetId === MAIN_CONTAINER_WIDGET_ID
@ -185,25 +266,19 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
height,
boxShadow,
};
const dropTargetRef = useRef<HTMLDivElement>(null);
// memoizing context values
const contextValue = useMemo(() => {
return {
updateDropTargetRows,
};
}, [updateDropTargetRows, occupiedSpacesByChildren]);
const shouldOnboard =
!(childWidgets && childWidgets.length) && !isDragging && !props.parentId;
if (props.widgetId !== MAIN_CONTAINER_WIDGET_ID) {
// console.log(
// "Dynamic height: Drop Target Height:",
// { height },
// { snapRows },
// );
}
// The drag layer is the one with the grid dots.
// They need to show in certain scenarios
const showDragLayer =
((isDragging && draggedOn === props.widgetId) ||
isResizing ||
isAutoHeightWithLimitsChanging) &&
!isPreviewMode;
const dropTargetRef = useRef<HTMLDivElement>(null);
return (
<DropTargetContext.Provider value={contextValue}>

View File

@ -32,7 +32,10 @@ import {
BottomRightHandleStyles,
} from "./ResizeStyledComponents";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { snipingModeSelector } from "selectors/editorSelectors";
import {
snipingModeSelector,
previewModeSelector,
} from "selectors/editorSelectors";
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
import { focusWidget } from "actions/widgetActions";
import { GridDefaults } from "constants/WidgetConstants";
@ -58,6 +61,7 @@ export const ResizableComponent = memo(function ResizableComponent(
const { updateWidget } = useContext(EditorContext);
const isSnipingMode = useSelector(snipingModeSelector);
const isPreviewMode = useSelector(previewModeSelector);
const showPropertyPane = useShowPropertyPane();
const showTableFilterPane = useShowTableFilterPane();
@ -243,7 +247,11 @@ export const ResizableComponent = memo(function ResizableComponent(
}, [props]);
const isEnabled =
!isDragging && isWidgetFocused && !props.resizeDisabled && !isSnipingMode;
!isDragging &&
isWidgetFocused &&
!props.resizeDisabled &&
!isSnipingMode &&
!isPreviewMode;
const { updateDropTargetRows } = useContext(DropTargetContext);
const gridProps = {

View File

@ -14,7 +14,10 @@ import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
import WidgetFactory from "utils/WidgetFactory";
const WidgetTypes = WidgetFactory.widgetTypes;
import { snipingModeSelector } from "selectors/editorSelectors";
import {
previewModeSelector,
snipingModeSelector,
} from "selectors/editorSelectors";
import { bindDataToWidget } from "actions/propertyPaneActions";
import { hideErrors } from "selectors/debuggerSelectors";
import { getIsPropertyPaneVisible } from "selectors/propertyPaneSelectors";
@ -54,6 +57,7 @@ type WidgetNameComponentProps = {
export function WidgetNameComponent(props: WidgetNameComponentProps) {
const dispatch = useDispatch();
const isSnipingMode = useSelector(snipingModeSelector);
const isPreviewMode = useSelector(previewModeSelector);
const showTableFilterPane = useShowTableFilterPane();
// Dispatch hook handy to set a widget as focused/selected
const { selectWidget } = useWidgetSelection();
@ -126,6 +130,7 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) {
selectedWidgets.includes(props.widgetId);
const shouldShowWidgetName = () => {
return (
!isPreviewMode &&
!isMultiSelectedWidget &&
(isSnipingMode
? focusedWidget === props.widgetId

View File

@ -34,15 +34,13 @@ export type RenderMode =
| "COMPONENT_PANE"
| "CANVAS"
| "PAGE"
| "CANVAS_SELECTED"
| "PREVIEW";
| "CANVAS_SELECTED";
export const RenderModes: { [id: string]: RenderMode } = {
COMPONENT_PANE: "COMPONENT_PANE",
CANVAS: "CANVAS",
PAGE: "PAGE",
CANVAS_SELECTED: "CANVAS_SELECTED",
PREVIEW: "PREVIEW",
};
export const CSSUnits: { [id: string]: CSSUnit } = {

View File

@ -94,11 +94,9 @@ const Canvas = memo((props: CanvasProps) => {
* background for canvas
*/
let backgroundForCanvas;
let renderMode = RenderModes.CANVAS;
if (isPreviewMode) {
backgroundForCanvas = "initial";
renderMode = RenderModes.PREVIEW;
} else {
backgroundForCanvas = selectedTheme.properties.colors.backgroundColor;
}
@ -125,7 +123,10 @@ const Canvas = memo((props: CanvasProps) => {
}}
>
{props.widgetsStructure.widgetId &&
WidgetFactory.createWidget(props.widgetsStructure, renderMode)}
WidgetFactory.createWidget(
props.widgetsStructure,
RenderModes.CANVAS,
)}
{isMultiplayerEnabledForUser && (
<CanvasMultiPointerArena pageId={pageId} />
)}

View File

@ -29,6 +29,7 @@ import EditorContextProvider from "components/editorComponents/EditorContextProv
import Guide from "../GuidedTour/Guide";
import PropertyPaneContainer from "./PropertyPaneContainer";
import CanvasTopSection from "./EmptyCanvasSection";
import { useAutoHeightUIState } from "utils/hooks/autoHeightUIHooks";
/* eslint-disable react/display-name */
function WidgetsEditor() {
@ -71,16 +72,24 @@ function WidgetsEditor() {
}, [isFetchingPage, selectWidget, guidedTourEnabled]);
const allowDragToSelect = useAllowEditorDragToSelect();
const { isAutoHeightWithLimitsChanging } = useAutoHeightUIState();
const handleWrapperClick = useCallback(() => {
if (allowDragToSelect) {
// Making sure that we don't deselect the widget
// after we are done dragging the limits in auto height with limits
if (allowDragToSelect && !isAutoHeightWithLimitsChanging) {
focusWidget && focusWidget();
deselectAll && deselectAll();
dispatch(closePropertyPane());
dispatch(closeTableFilterPane());
dispatch(setCanvasSelectionFromEditor(false));
}
}, [allowDragToSelect, focusWidget, deselectAll]);
}, [
allowDragToSelect,
focusWidget,
deselectAll,
isAutoHeightWithLimitsChanging,
]);
/**
* drag event handler for selection drawing

View File

@ -736,7 +736,8 @@ function getMovementMapHelper(
collisionTree[accessors.parallelMax] -
collisionTree[accessors.parallelMin],
occupiedLength:
(movementMap[collisionTree.id].horizontalOccupiedLength || 0) + 1,
(movementMap[collisionTree.id].horizontalOccupiedLength || 0) +
HORIZONTAL_RESIZE_LIMIT,
currentEmptySpaces:
(movementMap[collisionTree.id].horizontalEmptySpaces as number) ||
0,
@ -747,7 +748,10 @@ function getMovementMapHelper(
collisionTree[accessors.parallelMax] -
collisionTree[accessors.parallelMin],
occupiedLength:
(movementMap[collisionTree.id].verticalOccupiedLength || 0) + 1,
(movementMap[collisionTree.id].verticalOccupiedLength || 0) +
(collisionTree.fixedHeight && accessors.directionIndicator < 0
? collisionTree.fixedHeight
: VERTICAL_RESIZE_LIMIT),
currentEmptySpaces:
(movementMap[collisionTree.id].verticalEmptySpaces as number) || 0,
};

View File

@ -33,7 +33,6 @@ export function* batchCallsToUpdateWidgetAutoHeightSaga(
const isLayoutUpdating: boolean = yield select(getIsDraggingOrResizing);
const { height, widgetId } = action.payload;
log.debug("Dynamic height: batching update:", { widgetId, height });
addWidgetToAutoHeightUpdateQueue(widgetId, height);
if (isLayoutUpdating) return;
yield put({

View File

@ -28,7 +28,7 @@ export function* dynamicallyUpdateContainersSaga() {
const isCanvasWidget = widget.type === "CANVAS_WIDGET";
const parent = widget.parentId ? stateWidgets[widget.parentId] : undefined;
if (parent?.type === "LIST_WIDGET") return false;
if (!parent) return false;
if (parent === undefined) return false;
return isCanvasWidget;
});
@ -112,7 +112,15 @@ export function* dynamicallyUpdateContainersSaga() {
let maxBottomRow = minDynamicHeightInRows;
// For the child Canvas, use the value in pixels.
let canvasBottomRow = maxBottomRow;
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.
const canvasHeightOffset: number = getCanvasHeightOffset(
parentContainerWidget.type,
parentContainerWidget,
);
// If this canvas has children
// we need to consider the bottom most child for the height
@ -130,19 +138,14 @@ export function* dynamicallyUpdateContainersSaga() {
maxBottomRowBasedOnChildren += GridDefaults.CANVAS_EXTENSION_OFFSET;
// Set the canvas bottom row as a new variable with a new reference
canvasBottomRow = maxBottomRowBasedOnChildren + 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.
const canvasHeightOffset: number = getCanvasHeightOffset(
parentContainerWidget.type,
parentContainerWidget,
);
// 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
@ -156,7 +159,7 @@ export function* dynamicallyUpdateContainersSaga() {
}
canvasBottomRow =
Math.max(maxBottomRow, canvasBottomRow) *
Math.max(maxBottomRow - canvasHeightOffset, canvasBottomRow) *
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
// If we have a new height to set and
@ -189,7 +192,7 @@ export function* dynamicallyUpdateContainersSaga() {
}
}
log.debug(
"Dynamic height: Container computations took:",
"Dynamic height: Container computations time taken:",
performance.now() - start,
"ms",
);

View File

@ -7,8 +7,12 @@ import {
} from "reducers/entityReducers/canvasWidgetsReducer";
import { select } from "redux-saga/effects";
import { getWidgetMetaProps, getWidgets } from "sagas/selectors";
import { previewModeSelector } from "selectors/editorSelectors";
import {
getCanvasHeightOffset,
previewModeSelector,
} from "selectors/editorSelectors";
import { getAppMode } from "selectors/entitiesSelector";
import { TreeNode } from "utils/autoHeight/constants";
export function* shouldWidgetsCollapse() {
const isPreviewMode: boolean = yield select(previewModeSelector);
@ -50,6 +54,22 @@ export function* getChildOfContainerLikeWidget(
}
}
export function getParentCurrentHeightInRows(
tree: Record<string, TreeNode>,
parentId: string,
changesSoFar: Record<string, { bottomRow: number; topRow: number }>,
) {
// Get the parentHeight in rows
let parentHeightInRows = tree[parentId].bottomRow - tree[parentId].topRow;
// If the parent has changed so far.
if (changesSoFar.hasOwnProperty(parentId)) {
parentHeightInRows =
changesSoFar[parentId].bottomRow - changesSoFar[parentId].topRow;
}
return parentHeightInRows;
}
export function* getMinHeightBasedOnChildren(
widgetId: string,
changesSoFar: Record<string, { bottomRow: number; topRow: number }>,
@ -67,17 +87,22 @@ export function* getMinHeightBasedOnChildren(
const { children = [], parentId } = stateWidgets[widgetId];
// If we need to consider the parent height
if (parentId && !ignoreParent) {
// Get the parentHeight in rows
let parentHeightInRows = tree[parentId].bottomRow - tree[parentId].topRow;
// If the parent has changed so far.
if (changesSoFar.hasOwnProperty(parentId)) {
parentHeightInRows =
changesSoFar[parentId].bottomRow - changesSoFar[parentId].topRow;
}
const parent = stateWidgets[parentId];
const parentHeightInRows = getParentCurrentHeightInRows(
tree,
parentId,
changesSoFar,
);
// The canvas will be an extension smaller than the parent?
minHeightInRows = parentHeightInRows - GridDefaults.CANVAS_EXTENSION_OFFSET;
// We will also remove any extra offsets the parent has
// As we're dealing with the child canvas widget here.
const canvasHeightOffset: number = getCanvasHeightOffset(
parent.type,
parent,
);
minHeightInRows = minHeightInRows - canvasHeightOffset;
// If the canvas is empty return the parent's height in rows, without
// the canvas extension offset
if (!children.length) {

View File

@ -8,7 +8,10 @@ import { updateWidgetAutoHeightSaga } from "./widgets";
export default function* autoHeightSagas() {
yield all([
takeLatest(
ReduxActionTypes.CHECK_CONTAINERS_FOR_AUTO_HEIGHT,
[
ReduxActionTypes.CHECK_CONTAINERS_FOR_AUTO_HEIGHT,
ReduxActionTypes.SET_PREVIEW_MODE,
],
dynamicallyUpdateContainersSaga,
),
takeEvery(
@ -16,7 +19,7 @@ export default function* autoHeightSagas() {
batchCallsToUpdateWidgetAutoHeightSaga,
),
debounce(
100,
50,
ReduxActionTypes.PROCESS_AUTO_HEIGHT_UPDATES,
updateWidgetAutoHeightSaga,
),

View File

@ -39,7 +39,7 @@ export function* getLayoutTree(layoutUpdated: boolean) {
}
}
log.debug(
"Dynamic Height: Tree generation took:",
"Dynamic Height: Tree generation time taken:",
performance.now() - start,
"ms",
);

View File

@ -25,6 +25,7 @@ import {
import {
getChildOfContainerLikeWidget,
getMinHeightBasedOnChildren,
getParentCurrentHeightInRows,
shouldWidgetsCollapse,
} from "./helpers";
import { updateMultipleWidgetPropertiesAction } from "actions/controlActions";
@ -63,6 +64,7 @@ export function* updateWidgetAutoHeightSaga() {
const updates = getAutoHeightUpdateQueue();
log.debug("Dynamic Height: updates to process", { updates });
const start = performance.now();
let shouldRecomputeContainers = false;
const shouldCollapse: boolean = yield shouldWidgetsCollapse();
@ -96,6 +98,8 @@ export function* updateWidgetAutoHeightSaga() {
let minDynamicHeightInPixels =
getWidgetMinAutoHeight(widget) * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
if (widget.type === "TABS_WIDGET") shouldRecomputeContainers = true;
// In case of a widget going invisible in view mode
if (updates[widgetId] === 0) {
if (shouldCollapse && isAutoHeightEnabledForWidget(widget)) {
@ -138,7 +142,6 @@ export function* updateWidgetAutoHeightSaga() {
newHeightInPixels / GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
),
parentId: widget.parentId,
hasScroll: widget.isCanvas ? true : false,
});
} else if (widget) {
// For widgets like Modal Widget. (Rather this assumes that it is only the modal widget which needs a change)
@ -297,26 +300,14 @@ export function* updateWidgetAutoHeightSaga() {
// Add extra rows, this is to accommodate for padding and margins in the parent
minCanvasHeightInRows += GridDefaults.CANVAS_EXTENSION_OFFSET;
// Setting this in a variable, as this will be the total scroll height in the canvas.
const minCanvasHeightInPixels =
minCanvasHeightInRows * 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,
},
];
// 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.
const canvasHeightOffset: number = getCanvasHeightOffset(
parentContainerLikeWidget.type,
parentContainerLikeWidget,
);
// Widgets need to consider changing heights, only if they have dynamic height
// enabled.
@ -329,17 +320,30 @@ export function* updateWidgetAutoHeightSaga() {
minHeightInRows = Math.max(
minHeightInRows,
minCanvasHeightInRows,
minCanvasHeightInRows + canvasHeightOffset,
);
// 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.
const canvasHeightOffset: number = getCanvasHeightOffset(
parentContainerLikeWidget.type,
parentContainerLikeWidget,
);
minHeightInRows += 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(
@ -436,6 +440,36 @@ export function* updateWidgetAutoHeightSaga() {
]);
}
}
} else {
let parentContainerHeightInRows = getParentCurrentHeightInRows(
dynamicHeightLayoutTree,
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,
},
];
}
}
}
@ -471,10 +505,8 @@ export function* updateWidgetAutoHeightSaga() {
// Convert the changesSoFar (this are the computed changes)
// To the widgetsToUpdate data structure for final reducer update.
for (const changedWidgetId in changesSoFar) {
const hasScroll = Object.values(expectedUpdates).find(
(entry) => entry.widgetId === changedWidgetId,
)?.hasScroll;
const { originalBottomRow, originalTopRow } = dynamicHeightLayoutTree[
changedWidgetId
];
@ -497,19 +529,22 @@ export function* updateWidgetAutoHeightSaga() {
propertyValue: originalBottomRow,
},
];
if (hasScroll) {
const containerLikeWidget = stateWidgets[changedWidgetId];
const containerLikeWidget = stateWidgets[changedWidgetId];
if (
Array.isArray(containerLikeWidget.children) &&
containerLikeWidget.children.length > 0
) {
const childWidgetId:
| string
| undefined = yield getChildOfContainerLikeWidget(
containerLikeWidget,
);
if (childWidgetId) {
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,
@ -517,11 +552,7 @@ export function* updateWidgetAutoHeightSaga() {
dynamicHeightLayoutTree,
);
canvasHeight += GridDefaults.CANVAS_EXTENSION_OFFSET;
const canvasHeightOffset: number = getCanvasHeightOffset(
containerLikeWidget.type,
containerLikeWidget,
);
canvasHeight -= canvasHeightOffset;
const propertyUpdates = [
{
propertyPath: "minHeight",
@ -547,13 +578,16 @@ export function* updateWidgetAutoHeightSaga() {
}
log.debug("Dynamic 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(false, false));
yield put(
generateAutoHeightLayoutTreeAction(shouldRecomputeContainers, false),
);
}
log.debug(

View File

@ -178,9 +178,9 @@ export const selectURLSlugs = createSelector(
);
export const getRenderMode = (state: AppState) => {
if (state.ui.editor.isPreviewMode) return RenderModes.PREVIEW;
else if (state.entities.app.mode === APP_MODE.EDIT) return RenderModes.CANVAS;
else return RenderModes.PAGE;
return state.entities.app.mode === APP_MODE.EDIT
? RenderModes.CANVAS
: RenderModes.PAGE;
};
export const getViewModePageList = createSelector(

View File

@ -5,6 +5,7 @@ export type TreeNode = {
bottomRow: number;
originalTopRow: number;
originalBottomRow: number;
distanceToNearestAbove: number;
};
export type NodeSpace = {

View File

@ -17,6 +17,7 @@ describe("Generate Auto Height Layout tree", () => {
bottomRow: 30,
originalBottomRow: 30,
originalTopRow: 0,
distanceToNearestAbove: 0,
},
"2": {
aboves: [],
@ -25,6 +26,7 @@ describe("Generate Auto Height Layout tree", () => {
bottomRow: 30,
originalBottomRow: 30,
originalTopRow: 0,
distanceToNearestAbove: 0,
},
};
@ -36,7 +38,7 @@ describe("Generate Auto Height Layout tree", () => {
it("Does conflict when part of the boxes overlap horizontally", () => {
const input: NodeSpace[] = [
{ left: 0, right: 100, top: 0, bottom: 30, id: "1" },
{ left: 80, top: 30, bottom: 40, right: 120, id: "2" },
{ left: 80, top: 40, bottom: 80, right: 120, id: "2" },
];
const previousTree: Record<string, TreeNode> = {};
const layoutUpdated = false;
@ -48,14 +50,16 @@ describe("Generate Auto Height Layout tree", () => {
bottomRow: 30,
originalBottomRow: 30,
originalTopRow: 0,
distanceToNearestAbove: 0,
},
"2": {
aboves: ["1"],
belows: [],
topRow: 30,
bottomRow: 40,
originalBottomRow: 40,
originalTopRow: 30,
topRow: 40,
bottomRow: 80,
originalBottomRow: 80,
originalTopRow: 40,
distanceToNearestAbove: 10,
},
};
@ -77,6 +81,7 @@ describe("Generate Auto Height Layout tree", () => {
bottomRow: 30,
originalBottomRow: 20,
originalTopRow: 0,
distanceToNearestAbove: 0,
},
"2": {
aboves: ["1"],
@ -85,6 +90,7 @@ describe("Generate Auto Height Layout tree", () => {
bottomRow: 40,
originalBottomRow: 30,
originalTopRow: 20,
distanceToNearestAbove: 0,
},
};
const layoutUpdated = false;
@ -96,6 +102,7 @@ describe("Generate Auto Height Layout tree", () => {
bottomRow: 30,
originalBottomRow: 20,
originalTopRow: 0,
distanceToNearestAbove: 0,
},
"2": {
aboves: ["1"],
@ -104,6 +111,7 @@ describe("Generate Auto Height Layout tree", () => {
bottomRow: 40,
originalBottomRow: 30,
originalTopRow: 20,
distanceToNearestAbove: 0,
},
};
@ -125,6 +133,7 @@ describe("Generate Auto Height Layout tree", () => {
bottomRow: 30,
originalBottomRow: 20,
originalTopRow: 0,
distanceToNearestAbove: 0,
},
"2": {
aboves: ["1"],
@ -133,6 +142,7 @@ describe("Generate Auto Height Layout tree", () => {
bottomRow: 40,
originalBottomRow: 30,
originalTopRow: 20,
distanceToNearestAbove: 0,
},
};
const layoutUpdated = true;
@ -144,6 +154,7 @@ describe("Generate Auto Height Layout tree", () => {
bottomRow: 30,
originalBottomRow: 30,
originalTopRow: 0,
distanceToNearestAbove: 0,
},
"2": {
aboves: ["1"],
@ -152,6 +163,7 @@ describe("Generate Auto Height Layout tree", () => {
bottomRow: 40,
originalBottomRow: 40,
originalTopRow: 30,
distanceToNearestAbove: 0,
},
};

View File

@ -1,6 +1,7 @@
import { areIntersecting } from "utils/boxHelpers";
import { pushToArray } from "utils/helpers";
import { MAX_BOX_SIZE, NodeSpace, TreeNode } from "./constants";
import { getNearestAbove } from "./helpers";
// This function uses the spaces occupied by sibling boxes and provides us with
// a data structure which defines the relative vertical positioning of the boxes
@ -76,6 +77,7 @@ export function generateTree(
if (originalBottomRow === undefined || layoutUpdated) {
originalBottomRow = currentSpace.bottom - MAX_BOX_SIZE;
}
tree[currentSpace.id] = {
aboves: aboveMap[currentSpace.id] || [],
belows: belowMap[currentSpace.id] || [],
@ -83,9 +85,21 @@ export function generateTree(
bottomRow: currentSpace.bottom - MAX_BOX_SIZE,
originalTopRow,
originalBottomRow,
distanceToNearestAbove: 0,
};
}
}
for (const boxId in tree) {
// For each box, get the nearest above node
// Then get the distance between this node and the nearest above
// We'll try to maintain this distance when reflowing due to auto height
const nearestAbove = getNearestAbove(tree, boxId, {});
if (nearestAbove.length > 0) {
tree[boxId].distanceToNearestAbove =
tree[boxId].topRow - tree[nearestAbove[0]].bottomRow;
}
}
return tree;
}

View File

@ -0,0 +1,58 @@
import { TreeNode } from "./constants";
/**
* Gets the nearest above box for the current box. Including the aboves which have changes so far.
*
* @param tree: Auto Height Layout Tree
* @param effectedBoxId: Current box in consideration
* @param repositionedBoxes: Boxes repositioned so far
* @returns An array of boxIds which are above and nearest the effectedBoxId
*/
export function getNearestAbove(
tree: Record<string, TreeNode>,
effectedBoxId: string,
repositionedBoxes: Record<string, { topRow: number; bottomRow: number }>,
) {
// Get all the above boxes
const aboves = tree[effectedBoxId].aboves;
// We're trying to find the nearest boxes above this box
return aboves.reduce((prev: string[], next: string) => {
if (!prev[0]) return [next];
// Get the bottomRow of the above box
let nextBottomRow = tree[next].bottomRow;
let prevBottomRow = tree[prev[0]].bottomRow;
// If we've already repositioned this, use the new bottomRow of the box
if (repositionedBoxes[next]) {
nextBottomRow = repositionedBoxes[next].bottomRow;
}
if (repositionedBoxes[prev[0]]) {
prevBottomRow = repositionedBoxes[prev[0]].bottomRow;
}
// If the current box's (next) bottomRow is larger than the previous
// This (next) box is the bottom most above so far
if (nextBottomRow > prevBottomRow) return [next];
// If this (next) box's bottom row is the same as the previous
// We have two bottom most boxes
else if (nextBottomRow === prevBottomRow) {
if (
repositionedBoxes[prev[0]] &&
repositionedBoxes[prev[0]].bottomRow ===
repositionedBoxes[prev[0]].topRow
) {
return prev;
}
if (
repositionedBoxes[next] &&
repositionedBoxes[next].bottomRow === repositionedBoxes[next].topRow
) {
return [next];
}
return [...prev, next];
}
// This (next) box's bottom row is lower than the boxes selected so far
// so, we ignore it.
else return prev;
}, []);
}

View File

@ -18,6 +18,7 @@ describe("reflow", () => {
bottomRow: box1BottomRow,
originalTopRow: box1TopRow,
originalBottomRow: box1BottomRow,
distanceToNearestAbove: 0,
},
"2": {
aboves: ["1"],
@ -26,6 +27,7 @@ describe("reflow", () => {
bottomRow: box2BottomRow,
originalTopRow: box2TopRow,
originalBottomRow: box2BottomRow,
distanceToNearestAbove: 10,
},
};
@ -70,6 +72,7 @@ describe("reflow", () => {
bottomRow: box1BottomRow,
originalTopRow: box1OriginalTopRow,
originalBottomRow: box1OriginalBottomRow,
distanceToNearestAbove: 0,
},
"2": {
aboves: ["1"],
@ -78,6 +81,7 @@ describe("reflow", () => {
bottomRow: box2BottomRow,
originalTopRow: box2OriginalTopRow,
originalBottomRow: box2OriginalBottomRow,
distanceToNearestAbove: 10,
},
};
@ -122,6 +126,7 @@ describe("reflow", () => {
bottomRow: box1BottomRow,
originalTopRow: box1OriginalTopRow,
originalBottomRow: box1OriginalBottomRow,
distanceToNearestAbove: 0,
},
"2": {
aboves: ["1"],
@ -130,6 +135,7 @@ describe("reflow", () => {
bottomRow: box2BottomRow,
originalTopRow: box2OriginalTopRow,
originalBottomRow: box2OriginalBottomRow,
distanceToNearestAbove: 40,
},
};
@ -173,6 +179,7 @@ describe("reflow", () => {
bottomRow: 120,
originalBottomRow: 20,
originalTopRow: 10,
distanceToNearestAbove: 0,
};
const tree: Record<string, TreeNode> = {
@ -183,6 +190,7 @@ describe("reflow", () => {
bottomRow: box1BottomRow,
originalTopRow: box1OriginalTopRow,
originalBottomRow: box1OriginalBottomRow,
distanceToNearestAbove: 0,
},
"2": {
aboves: ["1", "3"],
@ -191,6 +199,7 @@ describe("reflow", () => {
bottomRow: box2BottomRow,
originalTopRow: box2OriginalTopRow,
originalBottomRow: box2OriginalBottomRow,
distanceToNearestAbove: 20,
},
"3": box3,
};
@ -208,8 +217,8 @@ describe("reflow", () => {
bottomRow: box1BottomRow + box1DeltaHeight,
},
"2": {
topRow: 130,
bottomRow: 170,
topRow: 140,
bottomRow: 180,
},
};

View File

@ -1,92 +1,5 @@
import { TreeNode } from "./constants";
/**
*
* @param tree : Auto Height Layout Tree
* @param effectedBoxId : Current box in consideration
* @param aboveId : Above box which may or maynot have changed
* @param offsetSoFar : Offset of the above box, or changes to be applied so far
* @returns : The offset expected to be applied to the effectedBoxId. This is how much this box should move
*/
export function getNegativeOffset(
tree: Record<string, TreeNode>,
effectedBoxId: string,
aboveId: string,
offsetSoFar = 0,
): number {
if (offsetSoFar <= 0) {
// Let's take in to account the old spacing between the effected box and bottom most above box
// when the layout was last updated.
const oldSpacing =
tree[effectedBoxId].originalTopRow - tree[aboveId].originalBottomRow;
// Let's compute the spacing between the effected box and bottom most above box
const currentSpacing = tree[effectedBoxId].topRow - tree[aboveId].bottomRow;
// If the old spacing is less than current spacing and the offset of the bottom most above,
// we need to make sure that we're sticking to the original spacing between the bottom most above
// and the current effected box.
// Note: This applies only if the offset is negative, which is to say that the box is to move up
if (oldSpacing < currentSpacing + offsetSoFar) {
return oldSpacing + offsetSoFar - currentSpacing;
}
}
return offsetSoFar;
}
/**
* Gets the nearest above box for the current box. Including the aboves which have changes so far.
*
* @param tree: Auto Height Layout Tree
* @param effectedBoxId: Current box in consideration
* @param repositionedBoxes: Boxes repositioned so far
* @returns An array of boxIds which are above and nearest the effectedBoxId
*/
export function getNearestAbove(
tree: Record<string, TreeNode>,
effectedBoxId: string,
repositionedBoxes: Record<string, { topRow: number; bottomRow: number }>,
) {
// Get all the above boxes
const aboves = tree[effectedBoxId].aboves;
// We're trying to find the nearest boxes above this box
return aboves.reduce((prev: string[], next: string) => {
if (!prev[0]) return [next];
// Get the bottomRow of the above box
let nextBottomRow = tree[next].bottomRow;
let prevBottomRow = tree[prev[0]].bottomRow;
// If we've already repositioned this, use the new bottomRow of the box
if (repositionedBoxes[next]) {
nextBottomRow = repositionedBoxes[next].bottomRow;
}
if (repositionedBoxes[prev[0]]) {
prevBottomRow = repositionedBoxes[prev[0]].bottomRow;
}
// If the current box's (next) bottomRow is larger than the previous
// This (next) box is the bottom most above so far
if (nextBottomRow > prevBottomRow) return [next];
// If this (next) box's bottom row is the same as the previous
// We have two bottom most boxes
else if (nextBottomRow === prevBottomRow) {
if (
repositionedBoxes[prev[0]] &&
repositionedBoxes[prev[0]].bottomRow ===
repositionedBoxes[prev[0]].topRow
) {
return prev;
}
if (
repositionedBoxes[next] &&
repositionedBoxes[next].bottomRow === repositionedBoxes[next].topRow
) {
return [next];
}
return [...prev, next];
}
// This (next) box's bottom row is lower than the boxes selected so far
// so, we ignore it.
else return prev;
}, []);
}
import { getNearestAbove } from "./helpers";
function getAllEffectedBoxes(
effectorBoxId: string,
@ -159,39 +72,23 @@ export function computeChangeInPositionBasedOnDelta(
// If the above box has been effected by another box change height
// Or, if this above box itself has changed height
if (effectedBoxes.includes(aboveId) || delta[aboveId]) {
// In case the above box has changed heights
const _aboveOffset = repositionedBoxes[aboveId]
? repositionedBoxes[aboveId].bottomRow - tree[aboveId].bottomRow
: 0;
// If so far, we haven't got any _offset updates
// This can happen if this is the first aboveId we're checking
if (_offset === undefined) _offset = _aboveOffset;
const negativeOffset = getNegativeOffset(
tree,
effectedBoxId,
aboveId,
_aboveOffset,
);
// If the bottom most above (_aboveOffset), has moved down (either by increasing height and/or due to its above)
// Let's take the effected boxs' change to be the max of _offset and _aboveOffset
// The _offset so far will be due to other bottomMostAbove effecting this effected box.
if (_aboveOffset > 0) _offset = Math.max(_aboveOffset, _offset);
// If the bottom most above (_aboveOffset) has moved up (either by decreasing height and/or due to its above)
// Let's take the Min (negative values, so max offset in the upward direction) of the _aboveOffset, _offset, negativeOffset.
else if (_aboveOffset < 0) {
_offset = Math.min(_aboveOffset, _offset, negativeOffset);
// If we have the above repositioned
if (repositionedBoxes[aboveId]) {
// Get the new expected top row of this effectedBox
const newTopRow =
repositionedBoxes[aboveId].bottomRow +
tree[effectedBoxId].distanceToNearestAbove;
// Get the offset this effectedBox needs to consider moving
_offset = newTopRow - tree[effectedBoxId].topRow;
} else {
// Since the above hasn't changed, don't change this.
_offset = 0;
}
} else {
// Stick to the widget above if the bottomMost above box hasn't changed
// TODO(abhinav): Here we may want to use the same logic as negativeOffset using originals as done previously.
// Test this.
// Let's take in to account the old spacing between the effected box and bottom most above box
// when the layout was last updated.
const negativeOffset = getNegativeOffset(tree, effectedBoxId, aboveId);
_offset = negativeOffset;
// Maintain distance from the bottom most above.
const newTopRow =
tree[aboveId].bottomRow + tree[effectedBoxId].distanceToNearestAbove;
_offset = newTopRow - tree[effectedBoxId].topRow;
}
}

View File

@ -1,10 +1,14 @@
import { ReduxActionTypes } from "ce/constants/ReduxActionConstants";
import { AppState } from "ce/reducers";
import { useCallback } from "react";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
export const useAutoHeightUIState = () => {
const dispatch = useDispatch();
return {
isAutoHeightWithLimitsChanging: useSelector(
(state: AppState) => state.ui.autoHeightUI.isAutoHeightWithLimitsChanging,
),
setIsAutoHeightWithLimitsChanging: useCallback(
(isAutoHeightWithLimitsChanging: boolean) => {
dispatch({

View File

@ -1,5 +1,8 @@
import { AppState } from "@appsmith/reducers";
import { snipingModeSelector } from "selectors/editorSelectors";
import {
previewModeSelector,
snipingModeSelector,
} from "selectors/editorSelectors";
import { useSelector } from "store";
export const useAllowEditorDragToSelect = () => {
@ -28,6 +31,12 @@ export const useAllowEditorDragToSelect = () => {
// True when any widget is dragging or resizing, including this one
const isResizingOrDragging = !!isResizing || !!isDragging || !!isSelecting;
const isSnipingMode = useSelector(snipingModeSelector);
const isPreviewMode = useSelector(previewModeSelector);
return !isResizingOrDragging && !isDraggingDisabled && !isSnipingMode;
return (
!isResizingOrDragging &&
!isDraggingDisabled &&
!isSnipingMode &&
!isPreviewMode
);
};

View File

@ -420,7 +420,7 @@ abstract class BaseWidget<
if (
isAutoHeightEnabledForWidget(this.props) &&
!this.props.isAutoGeneratedWidget //To skip list widget's auto generated widgets
!this.props.isAutoGeneratedWidget // To skip list widget's auto generated widgets
) {
return (
<AutoHeightContainerWrapper
@ -455,18 +455,12 @@ abstract class BaseWidget<
// return this.getCanvasView();
case RenderModes.PAGE:
case RenderModes.PREVIEW:
content = this.getWidgetComponent();
if (this.props.isVisible) {
if (!this.props.detachFromLayout) {
content = this.makePositioned(content);
}
return content;
} else {
// When widgets are invisible in view mode, they should not take up space.
// We're sending an update that sets the widget to have zero height,
// this should make sure that widgets below this invisible widget move up
// this.updateAutoHeight(0);
}
return null;
default:

View File

@ -42,21 +42,6 @@ const TAB_CONTAINER_HEIGHT = "44px";
const CHILDREN_WRAPPER_HEIGHT_WITH_TABS = `calc(100% - ${TAB_CONTAINER_HEIGHT})`;
const CHILDREN_WRAPPER_HEIGHT_WITHOUT_TABS = "100%";
// const scrollNavControlContainerBaseStyle = css`
// display: flex;
// position: absolute;
// top: 0;
// bottom: 0;
// z-index: 2;
// background: white;
// button {
// z-index: 1;
// border-radius: 0px;
// border-bottom: ${(props) => `1px solid ${props.theme.colors.bodyBG}`};
// }
// `;
const scrollContents = css`
overflow-y: auto;
position: absolute;
@ -111,39 +96,6 @@ export interface TabsContainerProps {
isScrollable: boolean;
}
// const TabsContainer = styled.div<TabsContainerProps>`
// position: absolute;
// top: 0;
// overflow-x: auto;
// overflow-y: hidden;
// display: flex;
// height: ${TAB_CONTAINER_HEIGHT};
// background: ${(props) => props.theme.colors.builderBodyBG};
// overflow: hidden;
// border-bottom: ${(props) => `1px solid ${props.theme.colors.bodyBG}`};
// overflow-x: scroll;
// &::-webkit-scrollbar {
// display: none;
// }
// /* Hide scrollbar for IE, Edge and Firefox */
// -ms-overflow-style: none; /* IE and Edge */
// scrollbar-width: none; /* Firefox */
// && {
// width: 100%;
// display: flex;
// justify-content: flex-start;
// align-items: flex-end;
// }
// `;
// type TabProps = {
// selected?: boolean;
// onClick: (e: React.MouseEvent<HTMLDivElement>) => void;
// primaryColor: string;
// };
const Container = styled.div`
width: 100%;
align-items: flex-end;
@ -194,19 +146,6 @@ export interface ScrollNavControlProps {
className?: string;
}
// function ScrollNavControl(props: ScrollNavControlProps) {
// const { className, disabled, icon, onClick } = props;
// return (
// <Button
// className={className}
// disabled={disabled}
// icon={icon}
// minimal
// onClick={onClick}
// />
// );
// }
function TabsComponent(props: TabsComponentProps) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { onTabChange, tabs, width, ...remainingProps } = props;
@ -251,14 +190,6 @@ function TabsComponent(props: TabsComponentProps) {
},
[tabsRef.current],
);
// eslint-disable-next-line
// const [_intervalRef, _rafRef, requestAF] = useThrottledRAF(scroll, 10);
// useEffect(() => {
// if (!props.shouldScrollContents) {
// tabContainerRef.current?.scrollTo({ top: 0, behavior: "smooth" });
// }
// }, [props.shouldScrollContents]);
return (
<TabsContainerWrapper

View File

@ -25,7 +25,7 @@ export const CONFIG = {
},
},
defaults: {
rows: WidgetHeightLimits.MIN_CANVAS_HEIGHT_IN_ROWS,
rows: WidgetHeightLimits.MIN_CANVAS_HEIGHT_IN_ROWS + 5,
columns: 24,
shouldScrollContents: false,
widgetName: "Tabs",
@ -33,6 +33,7 @@ export const CONFIG = {
borderWidth: 1,
borderColor: Colors.GREY_5,
backgroundColor: Colors.WHITE,
minDynamicHeight: WidgetHeightLimits.MIN_CANVAS_HEIGHT_IN_ROWS + 5,
tabsObj: {
tab1: {
label: "Tab 1",
@ -66,6 +67,7 @@ export const CONFIG = {
tabName: "Tab 1",
children: [],
version: 1,
bottomRow: WidgetHeightLimits.MIN_CANVAS_HEIGHT_IN_ROWS,
},
},
{
@ -81,6 +83,7 @@ export const CONFIG = {
tabName: "Tab 2",
children: [],
version: 1,
bottomRow: WidgetHeightLimits.MIN_CANVAS_HEIGHT_IN_ROWS,
},
},
],

View File

@ -172,9 +172,7 @@ class TabsWidget extends BaseWidget<
controlType: "SWITCH",
isBindProperty: false,
isTriggerProperty: false,
postUpdateActions: [
ReduxActionTypes.CHECK_CONTAINERS_FOR_AUTO_HEIGHT,
],
postUpdateAction: ReduxActionTypes.CHECK_CONTAINERS_FOR_AUTO_HEIGHT,
},
],
},

View File

@ -581,7 +581,7 @@ describe("Auto Height Utils", () => {
expect(result).toBeUndefined();
});
it.skip("should return 4 if widget has AUTO_HEIGHT", () => {
it("should return 20 if widget has AUTO_HEIGHT and props has 20", () => {
const props = {
...DUMMY_WIDGET,
dynamicHeight: "AUTO_HEIGHT",
@ -589,7 +589,7 @@ describe("Auto Height Utils", () => {
};
const result = getWidgetMinAutoHeight(props);
expect(result).toBe(WidgetHeightLimits.MIN_HEIGHT_IN_ROWS);
expect(result).toBe(20);
});
it("should return 20 if widget has AUTO_HEIGHT_WITH_LIMITS", () => {
const props = {
@ -612,7 +612,7 @@ describe("Auto Height Utils", () => {
expect(result).toBe(WidgetHeightLimits.MIN_HEIGHT_IN_ROWS);
});
it.skip("should return undefined if widget is FIXED ", () => {
it("should return undefined if widget is FIXED ", () => {
const props = {
...DUMMY_WIDGET,
dynamicHeight: "FIXED",

View File

@ -772,7 +772,8 @@ export function getWidgetMaxAutoHeight(props: WidgetProps) {
* @returns: The min possible height of the widget (in rows)
*/
export function getWidgetMinAutoHeight(props: WidgetProps) {
return props.minDynamicHeight || WidgetHeightLimits.MIN_HEIGHT_IN_ROWS;
if (props.dynamicHeight !== DynamicHeight.FIXED)
return props.minDynamicHeight || WidgetHeightLimits.MIN_HEIGHT_IN_ROWS;
}
/**

View File

@ -15,6 +15,7 @@ import {
computeMainContainerWidget,
getChildWidgets,
getRenderMode,
previewModeSelector,
} from "selectors/editorSelectors";
import { AppState } from "@appsmith/reducers";
import { useDispatch, useSelector } from "react-redux";
@ -33,6 +34,7 @@ function withWidgetProps(WrappedWidget: typeof BaseWidget) {
props: WidgetProps & { skipWidgetPropsHydration?: boolean },
) {
const { children, skipWidgetPropsHydration, type, widgetId } = props;
const isPreviewMode = useSelector(previewModeSelector);
const canvasWidget = useSelector((state: AppState) =>
getWidget(state, widgetId),
@ -122,25 +124,27 @@ function withWidgetProps(WrappedWidget: typeof BaseWidget) {
const shouldCollapseWidgetInViewOrPreviewMode =
!widgetProps.isVisible &&
(renderMode === RenderModes.PAGE || renderMode === RenderModes.PREVIEW) &&
widgetProps.bottomRow !== widgetProps.topRow;
(renderMode === RenderModes.PAGE || isPreviewMode);
const shouldResetCollapsedContainerHeightInViewOrPreviewMode =
widgetProps.isVisible && widgetProps.topRow === widgetProps.bottomRow;
const shouldResetCollapsedContainerHeightInCanvasMode =
widgetProps.topRow === widgetProps.bottomRow &&
renderMode === RenderModes.CANVAS;
renderMode === RenderModes.CANVAS &&
!isPreviewMode;
// We don't render invisible widgets in view mode
if (shouldCollapseWidgetInViewOrPreviewMode) {
dispatch({
type: ReduxActionTypes.UPDATE_WIDGET_AUTO_HEIGHT,
payload: {
widgetId: props.widgetId,
height: 0,
},
});
if (widgetProps.bottomRow !== widgetProps.topRow) {
dispatch({
type: ReduxActionTypes.UPDATE_WIDGET_AUTO_HEIGHT,
payload: {
widgetId: props.widgetId,
height: 0,
},
});
}
return null;
} else if (
shouldResetCollapsedContainerHeightInViewOrPreviewMode ||