PromucFlow_constructor/app/client/src/sagas/autoHeightSagas/containers.ts
Abhinav Jha 55cf16ae9d
feat: Non auto height invisible widgets (#20118)
## Description
This PR adds another feature update we had planned for Auto Height
- [ ] For new applications, in View and Preview mode, any widget which
is invisible will let go of its space and collapse if it's either on the
main Canvas or a container-like widget which has Auto-height enabled.
- [ ] Widgets within a container-like Widget, say Tabs, that doesn't
have Auto-height enabled, will now let go of their space if they're
invisible.
- [ ] The experience in Edit mode has not changed.

TL;DR: In new applications, in the Preview and Published _AKA_ View
modes, if a widget is invisible and within an Auto-height-enabled
container like a Tab, a Modal, a Form, or the main Canvas, it will fully
collapse, allowing widgets below it to move up and take its space. This
changes the behavior today prior to the release of this PR for
Auto-height-enabled widgets.

Fixes #19983
Fixes #18681
2023-02-14 19:06:19 +05:30

187 lines
6.7 KiB
TypeScript

import {
ReduxAction,
ReduxActionTypes,
} from "@appsmith/constants/ReduxActionConstants";
import { GridDefaults } from "constants/WidgetConstants";
import log from "loglevel";
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
import { call, put, select } from "redux-saga/effects";
import { getMinHeightBasedOnChildren, shouldWidgetsCollapse } from "./helpers";
import { getWidgets } from "sagas/selectors";
import { getCanvasHeightOffset } from "utils/WidgetSizeUtils";
import { FlattenedWidgetProps } from "widgets/constants";
import {
getWidgetMaxAutoHeight,
getWidgetMinAutoHeight,
isAutoHeightEnabledForWidget,
} from "widgets/WidgetUtils";
import { getChildOfContainerLikeWidget } from "./helpers";
import { getDataTree } from "selectors/dataTreeSelectors";
import { DataTree, DataTreeWidget } from "entities/DataTree/dataTreeFactory";
import { getLayoutTree } from "./layoutTree";
export function* dynamicallyUpdateContainersSaga(
action?: ReduxAction<{ resettingTabs: boolean }>,
) {
const start = performance.now();
const stateWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
const canvasWidgets: FlattenedWidgetProps[] | undefined = Object.values(
stateWidgets,
).filter((widget: FlattenedWidgetProps) => {
const isCanvasWidget = widget.type === "CANVAS_WIDGET";
const parent = widget.parentId ? stateWidgets[widget.parentId] : undefined;
if (parent?.type === "LIST_WIDGET") return false;
if (parent === undefined) return false;
return isCanvasWidget;
});
const { tree: dynamicHeightLayoutTree } = yield getLayoutTree(false);
const updates: Record<string, number> = {};
const shouldCollapse: boolean = yield call(shouldWidgetsCollapse);
for (const canvasWidget of canvasWidgets) {
if (canvasWidget.parentId) {
// The parent widget of this canvas widget
const parentContainerWidget = stateWidgets[canvasWidget.parentId];
// Skip this whole process if the parent is collapsed: Process:
// Get the DataTree
const dataTree: DataTree = yield select(getDataTree);
// Get this parentContainerWidget from the DataTree
const dataTreeWidget = dataTree[parentContainerWidget.widgetName];
// If the widget exists, is not visible and we can collapse widgets
if (
dataTreeWidget &&
(dataTreeWidget as DataTreeWidget).isVisible !== true &&
shouldCollapse
) {
continue;
}
if (isAutoHeightEnabledForWidget(parentContainerWidget)) {
// Get the child we need to consider
// For a container widget, it will be the child canvas
// For a tabs widget, it will be the currently open tab's canvas
const childWidgetId:
| string
| undefined = yield getChildOfContainerLikeWidget(
parentContainerWidget,
);
// This can be different from the canvas widget in consideration
// 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;
}
// Get the boundaries for possible min and max dynamic height.
const minDynamicHeightInRows = getWidgetMinAutoHeight(
parentContainerWidget,
);
const maxDynamicHeightInRows = getWidgetMaxAutoHeight(
parentContainerWidget,
);
// Default to the min height expected.
let maxBottomRow = minDynamicHeightInRows;
// 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
if (
Array.isArray(canvasWidget.children) &&
canvasWidget.children.length > 0
) {
let maxBottomRowBasedOnChildren: number = yield getMinHeightBasedOnChildren(
canvasWidget.widgetId,
{},
true,
dynamicHeightLayoutTree,
);
// Add a canvas extension offset
maxBottomRowBasedOnChildren += GridDefaults.CANVAS_EXTENSION_OFFSET;
// 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 {
// If the parent is not supposed to be collapsed
// Use the canvasHeight offset, as that would be the
// minimum
if (
parentContainerWidget.bottomRow - parentContainerWidget.topRow >
0 ||
!shouldCollapse
) {
maxBottomRow += canvasHeightOffset;
}
}
// The following makes sure we stay within bounds
// If the new height is below the min threshold
if (maxBottomRow < minDynamicHeightInRows) {
maxBottomRow = minDynamicHeightInRows;
}
// If the new height is above the max threshold
if (maxBottomRow > maxDynamicHeightInRows) {
maxBottomRow = maxDynamicHeightInRows;
}
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 (!updates.hasOwnProperty(parentContainerWidget.widgetId)) {
updates[parentContainerWidget.widgetId] =
maxBottomRow * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
}
}
}
}
log.debug("Auto Height: Container Updates", { updates });
if (Object.keys(updates).length > 0) {
// TODO(abhinav): Make sure there are no race conditions or scenarios where these updates are not considered.
for (const widgetId in updates) {
yield put({
type: ReduxActionTypes.UPDATE_WIDGET_AUTO_HEIGHT,
payload: {
widgetId,
height: updates[widgetId],
},
});
}
}
log.debug(
"Auto height: Container computations time taken:",
performance.now() - start,
"ms",
);
}