feat: Auto height instant update (#19082)
## Description This PR adds one of the promised updates to the auto height feature. More specifically, we wanted to add was the ability to see the containers change height as we drag and drop widgets within them instead of after dropping (when auto height is enabled) This PR does that. Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
This commit is contained in:
parent
04f6208f86
commit
20de52000d
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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']",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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}}",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ const WidgetStyle = styled.div<WidgetStyleContainerProps>`
|
|||
border-style: solid;
|
||||
background-color: ${(props) => props.backgroundColor || "transparent"};
|
||||
|
||||
overflow: hidden;
|
||||
& > div {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ const WidgetStyle = styled.div<WidgetStyleContainerProps>`
|
|||
function WidgetStyleContainer(props: WidgetStyleContainerProps) {
|
||||
return (
|
||||
<WidgetStyle {...props} data-testid={`container-wrapper-${props.widgetId}`}>
|
||||
<div>{props.children}</div>
|
||||
{props.children}
|
||||
</WidgetStyle>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {
|
|||
const GRID_POINT_SIZE = 1;
|
||||
const WrappedDragLayer = styled.div<{
|
||||
columnWidth: number;
|
||||
rowHeight: number;
|
||||
noPad: boolean;
|
||||
}>`
|
||||
position: absolute;
|
||||
|
|
@ -32,11 +31,10 @@ const WrappedDragLayer = styled.div<{
|
|||
);
|
||||
background-size: ${(props) =>
|
||||
props.columnWidth - GRID_POINT_SIZE / GridDefaults.DEFAULT_GRID_COLUMNS}px
|
||||
${(props) => props.rowHeight}px;
|
||||
${GridDefaults.DEFAULT_GRID_ROW_HEIGHT}px;
|
||||
`;
|
||||
|
||||
type DragLayerProps = {
|
||||
parentRowHeight: number;
|
||||
parentColumnWidth: number;
|
||||
noPad: boolean;
|
||||
};
|
||||
|
|
@ -44,9 +42,9 @@ type DragLayerProps = {
|
|||
function DragLayerComponent(props: DragLayerProps) {
|
||||
return (
|
||||
<WrappedDragLayer
|
||||
className="drag-layer"
|
||||
columnWidth={props.parentColumnWidth}
|
||||
noPad={props.noPad}
|
||||
rowHeight={props.parentRowHeight}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import React, { CSSProperties, useMemo, useRef } from "react";
|
||||
import styled from "styled-components";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import { WIDGET_PADDING } from "constants/WidgetConstants";
|
||||
import { useSelector } from "react-redux";
|
||||
import { AppState } from "@appsmith/reducers";
|
||||
import { getColorWithOpacity } from "constants/DefaultTheme";
|
||||
import {
|
||||
useShowTableFilterPane,
|
||||
useWidgetDragResize,
|
||||
|
|
@ -18,6 +16,8 @@ import {
|
|||
isCurrentWidgetFocused,
|
||||
isWidgetSelected,
|
||||
} from "selectors/widgetSelectors";
|
||||
import { getColorWithOpacity } from "constants/DefaultTheme";
|
||||
import { WIDGET_PADDING } from "constants/WidgetConstants";
|
||||
import { SelectionRequestType } from "sagas/WidgetSelectUtils";
|
||||
|
||||
const DraggableWrapper = styled.div`
|
||||
|
|
@ -29,9 +29,10 @@ const DraggableWrapper = styled.div`
|
|||
cursor: grab;
|
||||
`;
|
||||
|
||||
type DraggableComponentProps = WidgetProps;
|
||||
|
||||
// Widget Boundaries which is shown to indicate the boundaries of the widget
|
||||
const WidgetBoundaries = styled.div`
|
||||
transform: translate3d(-${WIDGET_PADDING + 1}px, -${WIDGET_PADDING + 1}px, 0);
|
||||
z-index: 0;
|
||||
width: calc(100% + ${WIDGET_PADDING - 2}px);
|
||||
height: calc(100% + ${WIDGET_PADDING - 2}px);
|
||||
|
|
@ -39,12 +40,11 @@ const WidgetBoundaries = styled.div`
|
|||
border: 1px dashed
|
||||
${(props) => getColorWithOpacity(props.theme.colors.textAnchor, 0.5)};
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
`;
|
||||
|
||||
type DraggableComponentProps = WidgetProps;
|
||||
|
||||
/* eslint-disable react/display-name */
|
||||
|
||||
/**
|
||||
* can drag helper function for react-dnd hook
|
||||
*
|
||||
|
|
@ -107,6 +107,7 @@ function DraggableComponent(props: DraggableComponentProps) {
|
|||
const isResizingOrDragging = !!isResizing || !!isDragging;
|
||||
const isCurrentWidgetDragging = isDragging && isSelected;
|
||||
const isCurrentWidgetResizing = isResizing && isSelected;
|
||||
|
||||
// When mouse is over this draggable
|
||||
const handleMouseOver = (e: any) => {
|
||||
focusWidget &&
|
||||
|
|
@ -124,15 +125,9 @@ function DraggableComponent(props: DraggableComponentProps) {
|
|||
const dragBoundariesStyle: React.CSSProperties = useMemo(() => {
|
||||
return {
|
||||
opacity: !isResizingOrDragging || isCurrentWidgetResizing ? 0 : 1,
|
||||
position: "absolute",
|
||||
transform: `translate(-50%, -50%)`,
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
};
|
||||
}, [isResizingOrDragging, isCurrentWidgetResizing]);
|
||||
|
||||
const widgetBoundaries = <WidgetBoundaries style={dragBoundariesStyle} />;
|
||||
|
||||
const classNameForTesting = `t--draggable-${props.type
|
||||
.split("_")
|
||||
.join("")
|
||||
|
|
@ -195,7 +190,10 @@ function DraggableComponent(props: DraggableComponentProps) {
|
|||
style={dragWrapperStyle}
|
||||
>
|
||||
{shouldRenderComponent && props.children}
|
||||
{widgetBoundaries}
|
||||
<WidgetBoundaries
|
||||
className={`widget-boundary-${props.widgetId}`}
|
||||
style={dragBoundariesStyle}
|
||||
/>
|
||||
</DraggableWrapper>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import React, {
|
||||
ReactNode,
|
||||
Context,
|
||||
createContext,
|
||||
memo,
|
||||
useEffect,
|
||||
useRef,
|
||||
useCallback,
|
||||
useMemo,
|
||||
PropsWithChildren,
|
||||
} from "react";
|
||||
import styled from "styled-components";
|
||||
import equal from "fast-deep-equal/es6";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
|
||||
import { getCanvasSnapRows } from "utils/WidgetPropsUtils";
|
||||
import {
|
||||
MAIN_CONTAINER_WIDGET_ID,
|
||||
|
|
@ -19,11 +18,8 @@ import {
|
|||
import { calculateDropTargetRows } from "./DropTargetUtils";
|
||||
import DragLayerComponent from "./DragLayerComponent";
|
||||
import { AppState } from "@appsmith/reducers";
|
||||
import { useSelector } from "react-redux";
|
||||
import {
|
||||
useShowPropertyPane,
|
||||
useCanvasSnapRowsUpdateHook,
|
||||
} from "utils/hooks/dragResizeHooks";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useShowPropertyPane } from "utils/hooks/dragResizeHooks";
|
||||
import {
|
||||
getOccupiedSpacesSelectorForContainer,
|
||||
previewModeSelector,
|
||||
|
|
@ -31,14 +27,17 @@ import {
|
|||
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
|
||||
import { getDragDetails } from "sagas/selectors";
|
||||
import { useAutoHeightUIState } from "utils/hooks/autoHeightUIHooks";
|
||||
import { updateDOMDirectlyBasedOnAutoHeightAction } from "actions/autoHeightActions";
|
||||
import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils";
|
||||
|
||||
type DropTargetComponentProps = WidgetProps & {
|
||||
children?: ReactNode;
|
||||
type DropTargetComponentProps = PropsWithChildren<{
|
||||
snapColumnSpace: number;
|
||||
snapRowSpace: number;
|
||||
minHeight: number;
|
||||
widgetId: string;
|
||||
parentId?: string;
|
||||
noPad?: boolean;
|
||||
};
|
||||
bottomRow: number;
|
||||
minHeight: number;
|
||||
}>;
|
||||
|
||||
const StyledDropTarget = styled.div`
|
||||
transition: height 100ms ease-in;
|
||||
|
|
@ -69,39 +68,123 @@ export const DropTargetContext: Context<{
|
|||
}> = createContext({});
|
||||
|
||||
/**
|
||||
* Gets the dropTarget height
|
||||
* @param canDropTargetExtend boolean: Can we put widgets below the scrollview in this canvas?
|
||||
* @param isPreviewMode boolean: Are we in the preview mode
|
||||
* @param currentHeight number: Current height in the ref and what we have set in the dropTarget
|
||||
* @param snapRowSpace number: This is a static value actually, GridDefaults.DEFAULT_GRID_ROW_HEIGHT
|
||||
* @param minHeight number: The minHeight we've set to the widget in the reducer
|
||||
* @returns number: A new height style to set in the dropTarget.
|
||||
* This function sets the height in pixels to the provided ref to the number of rows
|
||||
* @param ref : The ref to the dropTarget so that we can update the height
|
||||
* @param currentRows : Number of rows to set the height
|
||||
*/
|
||||
function getDropTargetHeight(
|
||||
canDropTargetExtend: boolean,
|
||||
isPreviewMode: boolean,
|
||||
currentHeight: number,
|
||||
snapRowSpace: number,
|
||||
minHeight: number,
|
||||
) {
|
||||
let height = canDropTargetExtend
|
||||
? `${Math.max(currentHeight * snapRowSpace, minHeight)}px`
|
||||
: "100%";
|
||||
if (isPreviewMode && canDropTargetExtend)
|
||||
height = `${currentHeight * snapRowSpace}px`;
|
||||
return height;
|
||||
const updateHeight = (
|
||||
ref: React.MutableRefObject<HTMLDivElement | null>,
|
||||
currentRows: number,
|
||||
isMainContainer: boolean,
|
||||
) => {
|
||||
if (ref.current) {
|
||||
const height = currentRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
ref.current.style.height = `${height}px`;
|
||||
ref.current
|
||||
.closest(".scroll-parent")
|
||||
?.scrollTo({ top: height, behavior: "smooth" });
|
||||
if (isMainContainer) {
|
||||
const artboard = document.getElementById("art-board");
|
||||
if (artboard) {
|
||||
artboard.style.height = `${height}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function useUpdateRows(bottomRow: number, widgetId: string, parentId?: string) {
|
||||
// This gives us the number of rows
|
||||
const snapRows = getCanvasSnapRows(bottomRow);
|
||||
// Put the existing snap rows in a ref.
|
||||
const rowRef = useRef(snapRows);
|
||||
|
||||
const dropTargetRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// The occupied spaces in this canvas. It is a data structure which has the rect values of each child.
|
||||
const selectOccupiedSpaces = useCallback(
|
||||
getOccupiedSpacesSelectorForContainer(widgetId),
|
||||
[widgetId],
|
||||
);
|
||||
|
||||
// Call the selector above.
|
||||
const occupiedSpacesByChildren = useSelector(selectOccupiedSpaces, equal);
|
||||
/*
|
||||
* If the parent has auto height enabled, or if the current widget is the MAIN_CONTAINER_WIDGET_ID
|
||||
*/
|
||||
const isParentAutoHeightEnabled = useSelector((state: AppState) => {
|
||||
return parentId
|
||||
? !isAutoHeightEnabledForWidget(
|
||||
state.entities.canvasWidgets[parentId],
|
||||
true,
|
||||
) &&
|
||||
isAutoHeightEnabledForWidget(state.entities.canvasWidgets[parentId])
|
||||
: false;
|
||||
});
|
||||
const dispatch = useDispatch();
|
||||
// Function which computes and updates the height of the dropTarget
|
||||
// This is used in a context and hence in one of the children of this dropTarget
|
||||
const updateDropTargetRows = (
|
||||
widgetIdsToExclude: string[],
|
||||
widgetBottomRow: number,
|
||||
) => {
|
||||
// Compute expected number of rows this drop target must have
|
||||
const newRows = calculateDropTargetRows(
|
||||
widgetIdsToExclude,
|
||||
widgetBottomRow,
|
||||
occupiedSpacesByChildren,
|
||||
widgetId,
|
||||
);
|
||||
|
||||
// If the current number of rows in the drop target is less
|
||||
// than the expected number of rows in the drop target
|
||||
if (rowRef.current < newRows) {
|
||||
// Set the new value locally
|
||||
rowRef.current = newRows;
|
||||
// If the parent container like widget has auto height enabled
|
||||
// We'd like to immediately update the parent's height
|
||||
// based on the auto height computations
|
||||
// This also updates any "dropTargets" that need to change height
|
||||
// hence, this and the `updateHeight` function are mutually exclusive.
|
||||
if (isParentAutoHeightEnabled && parentId) {
|
||||
dispatch(updateDOMDirectlyBasedOnAutoHeightAction(parentId, newRows));
|
||||
} else {
|
||||
// Basically, we don't have auto height triggering, so the dropTarget height should be updated using
|
||||
// the `updateHeight` function
|
||||
// The difference here is that the `updateHeight` function only updates the "canvas" or the "dropTarget"
|
||||
// and doesn't effect the parent container
|
||||
|
||||
// We can't update the height of the "Canvas" or "dropTarget" using this function
|
||||
// in the previous if clause, because, there could be more "dropTargets" updating
|
||||
// and this information can only be computed using auto height
|
||||
updateHeight(
|
||||
dropTargetRef,
|
||||
rowRef.current,
|
||||
widgetId === MAIN_CONTAINER_WIDGET_ID,
|
||||
);
|
||||
}
|
||||
return newRows;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// memoizing context values
|
||||
const contextValue = useMemo(() => {
|
||||
return {
|
||||
updateDropTargetRows,
|
||||
};
|
||||
}, [updateDropTargetRows, occupiedSpacesByChildren]);
|
||||
|
||||
/** EO PREPARE CONTEXT */
|
||||
return { contextValue, dropTargetRef, rowRef };
|
||||
}
|
||||
|
||||
export function DropTargetComponent(props: DropTargetComponentProps) {
|
||||
// Get if this is in preview mode.
|
||||
const isPreviewMode = useSelector(previewModeSelector);
|
||||
// Pretty much the shouldScrollContents from the parent container like widget
|
||||
const canDropTargetExtend = props.canExtend;
|
||||
// If in preview mode, we don't need that extra row
|
||||
// This gives us the number of rows
|
||||
const snapRows = getCanvasSnapRows(
|
||||
|
||||
const { contextValue, dropTargetRef, rowRef } = useUpdateRows(
|
||||
props.bottomRow,
|
||||
props.canExtend && !isPreviewMode,
|
||||
props.widgetId,
|
||||
props.parentId,
|
||||
);
|
||||
|
||||
// Are we currently resizing?
|
||||
|
|
@ -129,77 +212,26 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
|
|||
(state: AppState) => state.entities.canvasWidgets[props.widgetId]?.children,
|
||||
);
|
||||
|
||||
// The occupied spaces in this canvas. It is a data structure which has the rect values of each child.
|
||||
const selectOccupiedSpaces = useCallback(
|
||||
getOccupiedSpacesSelectorForContainer(props.widgetId),
|
||||
[props.widgetId],
|
||||
);
|
||||
|
||||
// Call the selector above.
|
||||
const occupiedSpacesByChildren = useSelector(selectOccupiedSpaces, equal);
|
||||
|
||||
// Put the existing snap rows in a ref.
|
||||
const rowRef = useRef(snapRows);
|
||||
|
||||
// This shows the property pane
|
||||
const showPropertyPane = useShowPropertyPane();
|
||||
|
||||
const { deselectAll, focusWidget } = useWidgetSelection();
|
||||
|
||||
// This updates the bottomRow of this canvas, as simple as that
|
||||
// This also doesn't cause an eval as it uses the action which is
|
||||
// not registered to cause an eval
|
||||
const updateCanvasSnapRows = useCanvasSnapRowsUpdateHook();
|
||||
|
||||
// Everytime we get a new bottomRow, or we toggle shouldScrollContents
|
||||
// we call this effect
|
||||
useEffect(() => {
|
||||
const snapRows = getCanvasSnapRows(
|
||||
props.bottomRow,
|
||||
props.canExtend && !isPreviewMode,
|
||||
);
|
||||
const snapRows = getCanvasSnapRows(props.bottomRow);
|
||||
|
||||
// If the current ref is not set to the new snaprows we've received (based on bottomRow)
|
||||
if (rowRef.current !== snapRows) {
|
||||
if (rowRef.current !== snapRows && !isDragging && !isResizing) {
|
||||
rowRef.current = snapRows;
|
||||
// This sets the "height" property of the dropTarget div
|
||||
// This makes the div change heights if new heights are different
|
||||
updateHeight();
|
||||
// This sets the new rows in the reducer
|
||||
// Not sure why, as we've just received the values from the props.
|
||||
// seems like a potential way to cause recursive renders
|
||||
// See this: https://github.com/appsmithorg/appsmith/pull/18457#issuecomment-1327615572
|
||||
if (canDropTargetExtend && !isPreviewMode) {
|
||||
updateCanvasSnapRows(props.widgetId, snapRows);
|
||||
}
|
||||
}
|
||||
}, [props.bottomRow, props.canExtend, isPreviewMode]);
|
||||
|
||||
// If we've stopped dragging, resizing or changing auto height limits
|
||||
useEffect(() => {
|
||||
if (!isDragging || !isResizing || !isAutoHeightWithLimitsChanging) {
|
||||
// bottom row of canvas can increase by any number as user moves/resizes any widget towards the bottom of the canvas
|
||||
// but canvas height is not lost when user moves/resizes back top.
|
||||
// it is done that way to have a pleasant building experience.
|
||||
// post drop the bottom most row is used to appropriately calculate the canvas height and lose unwanted height.
|
||||
rowRef.current = snapRows;
|
||||
updateHeight();
|
||||
}
|
||||
}, [isDragging, isResizing, isAutoHeightWithLimitsChanging]);
|
||||
|
||||
// Update the drop target height style directly.
|
||||
const updateHeight = () => {
|
||||
if (dropTargetRef.current) {
|
||||
const height = getDropTargetHeight(
|
||||
canDropTargetExtend,
|
||||
isPreviewMode,
|
||||
rowRef.current,
|
||||
props.snapRowSpace,
|
||||
props.minHeight,
|
||||
updateHeight(
|
||||
dropTargetRef,
|
||||
snapRows,
|
||||
props.widgetId === MAIN_CONTAINER_WIDGET_ID,
|
||||
);
|
||||
|
||||
dropTargetRef.current.style.height = height;
|
||||
}
|
||||
};
|
||||
}, [props.widgetId, props.bottomRow, isDragging, isResizing]);
|
||||
|
||||
const handleFocus = (e: any) => {
|
||||
// Making sure that we don't deselect the widget
|
||||
|
|
@ -214,47 +246,7 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
|
|||
e.preventDefault();
|
||||
};
|
||||
|
||||
/** PREPARE CONTEXT */
|
||||
|
||||
// Function which computes and updates the height of the dropTarget
|
||||
// This is used in a context and hence in one of the children of this dropTarget
|
||||
const updateDropTargetRows = (
|
||||
widgetIdsToExclude: string[],
|
||||
widgetBottomRow: number,
|
||||
) => {
|
||||
if (canDropTargetExtend) {
|
||||
const newRows = calculateDropTargetRows(
|
||||
widgetIdsToExclude,
|
||||
widgetBottomRow,
|
||||
props.minHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
|
||||
occupiedSpacesByChildren,
|
||||
props.widgetId,
|
||||
);
|
||||
if (rowRef.current < newRows) {
|
||||
rowRef.current = newRows;
|
||||
updateHeight();
|
||||
return newRows;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// memoizing context values
|
||||
const contextValue = useMemo(() => {
|
||||
return {
|
||||
updateDropTargetRows,
|
||||
};
|
||||
}, [updateDropTargetRows, occupiedSpacesByChildren]);
|
||||
|
||||
/** EO PREPARE CONTEXT */
|
||||
|
||||
const height = getDropTargetHeight(
|
||||
canDropTargetExtend,
|
||||
isPreviewMode,
|
||||
rowRef.current,
|
||||
props.snapRowSpace,
|
||||
props.minHeight,
|
||||
);
|
||||
const height = `${rowRef.current * GridDefaults.DEFAULT_GRID_ROW_HEIGHT}px`;
|
||||
|
||||
const boxShadow =
|
||||
(isResizing || isDragging || isAutoHeightWithLimitsChanging) &&
|
||||
|
|
@ -278,12 +270,11 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
|
|||
isAutoHeightWithLimitsChanging) &&
|
||||
!isPreviewMode;
|
||||
|
||||
const dropTargetRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<DropTargetContext.Provider value={contextValue}>
|
||||
<StyledDropTarget
|
||||
className="t--drop-target"
|
||||
className={`t--drop-target drop-target-${props.parentId ||
|
||||
MAIN_CONTAINER_WIDGET_ID}`}
|
||||
onClick={handleFocus}
|
||||
ref={dropTargetRef}
|
||||
style={dropTargetStyles}
|
||||
|
|
@ -294,7 +285,6 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
|
|||
<DragLayerComponent
|
||||
noPad={props.noPad || false}
|
||||
parentColumnWidth={props.snapColumnSpace}
|
||||
parentRowHeight={props.snapRowSpace}
|
||||
/>
|
||||
)}
|
||||
</StyledDropTarget>
|
||||
|
|
@ -302,6 +292,4 @@ export function DropTargetComponent(props: DropTargetComponentProps) {
|
|||
);
|
||||
}
|
||||
|
||||
const MemoizedDropTargetComponent = memo(DropTargetComponent);
|
||||
|
||||
export default MemoizedDropTargetComponent;
|
||||
export default DropTargetComponent;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,15 +9,7 @@ type State = { hasError: boolean };
|
|||
const ErrorBoundaryContainer = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
> div {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
// border: 1px solid;
|
||||
// border-color: ${({ isValid, theme }) =>
|
||||
// isValid ? "transparent" : theme.colors.error};
|
||||
|
||||
const RetryLink = styled.span`
|
||||
color: ${(props) => props.theme.colors.primaryDarkest};
|
||||
|
|
@ -42,7 +34,7 @@ class ErrorBoundary extends React.Component<Props, State> {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<ErrorBoundaryContainer>
|
||||
<ErrorBoundaryContainer className="error-boundary">
|
||||
{this.state.hasError ? (
|
||||
<p>
|
||||
Oops, Something went wrong.
|
||||
|
|
|
|||
|
|
@ -291,6 +291,11 @@ export const ResizableComponent = memo(function ResizableComponent(
|
|||
return !isAutoHeightEnabledForWidget(props) && isEnabled;
|
||||
}, [props, isAutoHeightEnabledForWidget, isEnabled]);
|
||||
|
||||
const fixedHeight =
|
||||
isAutoHeightEnabledForWidget(props, true) ||
|
||||
!isAutoHeightEnabledForWidget(props) ||
|
||||
!props.isCanvas;
|
||||
|
||||
return (
|
||||
<Resizable
|
||||
allowResize={!isMultiSelected}
|
||||
|
|
@ -298,17 +303,19 @@ export const ResizableComponent = memo(function ResizableComponent(
|
|||
componentWidth={dimensions.width}
|
||||
enableHorizontalResize={isEnabled}
|
||||
enableVerticalResize={isVerticalResizeEnabled}
|
||||
fixedHeight={fixedHeight}
|
||||
getResizedPositions={getResizedPositions}
|
||||
gridProps={gridProps}
|
||||
handles={handles}
|
||||
maxDynamicHeight={props.maxDynamicHeight}
|
||||
onStart={handleResizeStart}
|
||||
onStop={updateSize}
|
||||
originalPositions={originalPositions}
|
||||
parentId={props.parentId}
|
||||
snapGrid={snapGrid}
|
||||
updateBottomRow={updateBottomRow}
|
||||
widgetId={props.widgetId}
|
||||
// Used only for performance tracking, can be removed after optimization.
|
||||
widgetId={props.widgetId}
|
||||
zWidgetId={props.widgetId}
|
||||
zWidgetType={props.type}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { TAILWIND_COLORS } from "constants/ThemeConstants";
|
|||
import useDSEvent from "utils/hooks/useDSEvent";
|
||||
import { DSEventTypes } from "utils/AppsmithUtils";
|
||||
import { getBrandColors } from "@appsmith/selectors/tenantSelectors";
|
||||
import tinycolor from "tinycolor2";
|
||||
const FocusTrap = require("focus-trap-react");
|
||||
|
||||
const MAX_COLS = 10;
|
||||
|
|
@ -551,7 +552,10 @@ const ColorPickerComponent = React.forwardRef(
|
|||
autoFocus={props.autoFocus}
|
||||
inputRef={inputGroupRef}
|
||||
leftIcon={
|
||||
<LeftIcon color={color} handleInputClick={handleInputClick} />
|
||||
<LeftIcon
|
||||
color={tinycolor(color).toString()}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
}
|
||||
onChange={handleChangeColor}
|
||||
onClick={handleInputClick}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
DS_EVENT,
|
||||
emitInteractionAnalyticsEvent,
|
||||
} from "utils/AppsmithUtils";
|
||||
import tinycolor from "tinycolor2";
|
||||
|
||||
class ColorPickerControl extends BaseControl<ColorPickerControlProps> {
|
||||
componentRef = React.createRef<HTMLDivElement>();
|
||||
|
|
@ -39,7 +40,10 @@ class ColorPickerControl extends BaseControl<ColorPickerControlProps> {
|
|||
};
|
||||
|
||||
handleChangeColor = (color: string, isUpdatedViaKeyboard: boolean) => {
|
||||
this.updateProperty(this.props.propertyName, color, isUpdatedViaKeyboard);
|
||||
let _color = color;
|
||||
_color = tinycolor(color).isValid() ? tinycolor(color).toString() : color;
|
||||
|
||||
this.updateProperty(this.props.propertyName, _color, isUpdatedViaKeyboard);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -1,24 +1,13 @@
|
|||
import log from "loglevel";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import styled from "styled-components";
|
||||
import store from "store";
|
||||
import { CanvasWidgetStructure } from "widgets/constants";
|
||||
import WidgetFactory from "utils/WidgetFactory";
|
||||
import React, { memo, useCallback, useEffect } from "react";
|
||||
import React from "react";
|
||||
|
||||
import CanvasMultiPointerArena, {
|
||||
POINTERS_CANVAS_ID,
|
||||
} from "pages/common/CanvasArenas/CanvasMultiPointerArena";
|
||||
import { throttle } from "lodash";
|
||||
import { RenderModes } from "constants/WidgetConstants";
|
||||
import { isMultiplayerEnabledForUser as isMultiplayerEnabledForUserSelector } from "selectors/appCollabSelectors";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { initPageLevelSocketConnection } from "actions/websocketActions";
|
||||
import { collabShareUserPointerEvent } from "actions/appCollabActions";
|
||||
import { getIsPageLevelSocketConnected } from "selectors/websocketSelectors";
|
||||
import { getCurrentGitBranch } from "selectors/gitSyncSelectors";
|
||||
import { useSelector } from "react-redux";
|
||||
import { getSelectedAppTheme } from "selectors/appThemingSelectors";
|
||||
import { getPageLevelSocketRoomId } from "sagas/WebsocketSagas/utils";
|
||||
import { previewModeSelector } from "selectors/editorSelectors";
|
||||
import useWidgetFocus from "utils/hooks/useWidgetFocus";
|
||||
|
||||
|
|
@ -29,69 +18,22 @@ interface CanvasProps {
|
|||
canvasScale?: number;
|
||||
}
|
||||
|
||||
type PointerEventDataType = {
|
||||
data: { x: number; y: number };
|
||||
user: any;
|
||||
};
|
||||
|
||||
const Container = styled.section<{
|
||||
background: string;
|
||||
width: number;
|
||||
$canvasScale: number;
|
||||
}>`
|
||||
background: ${({ background }) => background};
|
||||
}
|
||||
width: ${(props) => props.width}px;
|
||||
transform: scale(${(props) => props.$canvasScale});
|
||||
transform-origin: "0 0";
|
||||
`;
|
||||
|
||||
const getPointerData = (
|
||||
e: any,
|
||||
pageId: string,
|
||||
isWebsocketConnected: boolean,
|
||||
currentGitBranch?: string,
|
||||
) => {
|
||||
if (store.getState().ui.appCollab.editors.length < 2 || !isWebsocketConnected)
|
||||
return;
|
||||
const selectionCanvas: any = document.getElementById(POINTERS_CANVAS_ID);
|
||||
const rect = selectionCanvas.getBoundingClientRect();
|
||||
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
return {
|
||||
data: { x, y },
|
||||
pageId: getPageLevelSocketRoomId(pageId, currentGitBranch),
|
||||
};
|
||||
};
|
||||
|
||||
const useShareMousePointerEvent = () => {
|
||||
const dispatch = useDispatch();
|
||||
const isWebsocketConnected = useSelector(getIsPageLevelSocketConnected);
|
||||
useEffect(() => {
|
||||
if (!isWebsocketConnected) {
|
||||
dispatch(initPageLevelSocketConnection());
|
||||
}
|
||||
}, [isWebsocketConnected]);
|
||||
|
||||
return (pointerData: PointerEventDataType) =>
|
||||
dispatch(collabShareUserPointerEvent(pointerData));
|
||||
};
|
||||
|
||||
// TODO(abhinav): get the render mode from context
|
||||
const Canvas = memo((props: CanvasProps) => {
|
||||
const { canvasScale = 1, canvasWidth, pageId } = props;
|
||||
const Canvas = (props: CanvasProps) => {
|
||||
const { canvasScale = 1, canvasWidth } = props;
|
||||
const isPreviewMode = useSelector(previewModeSelector);
|
||||
const selectedTheme = useSelector(getSelectedAppTheme);
|
||||
|
||||
const shareMousePointer = useShareMousePointerEvent();
|
||||
const isWebsocketConnected = useSelector(getIsPageLevelSocketConnected);
|
||||
const currentGitBranch = useSelector(getCurrentGitBranch);
|
||||
const isMultiplayerEnabledForUser = useSelector(
|
||||
isMultiplayerEnabledForUserSelector,
|
||||
);
|
||||
const delayedShareMousePointer = useCallback(
|
||||
throttle((data) => shareMousePointer(data), 50, {
|
||||
trailing: false,
|
||||
}),
|
||||
[shareMousePointer, pageId],
|
||||
);
|
||||
|
||||
/**
|
||||
* background for canvas
|
||||
*/
|
||||
|
|
@ -108,35 +50,19 @@ const Canvas = memo((props: CanvasProps) => {
|
|||
try {
|
||||
return (
|
||||
<Container
|
||||
$canvasScale={canvasScale}
|
||||
background={backgroundForCanvas}
|
||||
className="relative mx-auto t--canvas-artboard pb-52"
|
||||
data-testid="t--canvas-artboard"
|
||||
id="art-board"
|
||||
onMouseMove={(e) => {
|
||||
if (!isMultiplayerEnabledForUser) return;
|
||||
const data = getPointerData(
|
||||
e,
|
||||
pageId,
|
||||
isWebsocketConnected,
|
||||
currentGitBranch,
|
||||
);
|
||||
!!data && delayedShareMousePointer(data);
|
||||
}}
|
||||
ref={focusRef}
|
||||
style={{
|
||||
width: canvasWidth,
|
||||
transform: `scale(${canvasScale})`,
|
||||
transformOrigin: "0 0",
|
||||
}}
|
||||
width={canvasWidth}
|
||||
>
|
||||
{props.widgetsStructure.widgetId &&
|
||||
WidgetFactory.createWidget(
|
||||
props.widgetsStructure,
|
||||
RenderModes.CANVAS,
|
||||
)}
|
||||
{isMultiplayerEnabledForUser && (
|
||||
<CanvasMultiPointerArena pageId={pageId} />
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
} catch (error) {
|
||||
|
|
@ -144,8 +70,6 @@ const Canvas = memo((props: CanvasProps) => {
|
|||
Sentry.captureException(error);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
Canvas.displayName = "Canvas";
|
||||
};
|
||||
|
||||
export default Canvas;
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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"] = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ import {
|
|||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import { uniq, get, set } from "lodash";
|
||||
import { Diff, diff } from "deep-diff";
|
||||
import {
|
||||
getCanvasBottomRow,
|
||||
getCanvasWidgetHeightsToUpdate,
|
||||
} from "utils/WidgetSizeUtils";
|
||||
|
||||
/* This type is an object whose keys are widgetIds and values are arrays with property paths
|
||||
and property values
|
||||
|
|
@ -54,7 +58,14 @@ const canvasWidgetsReducer = createImmerReducer(initialState, {
|
|||
state: CanvasWidgetsReduxState,
|
||||
action: ReduxAction<UpdateCanvasPayload>,
|
||||
) => {
|
||||
return action.payload.widgets;
|
||||
const { widgets } = action.payload;
|
||||
for (const [widgetId, widgetProps] of Object.entries(widgets)) {
|
||||
if (widgetProps.type === "CANVAS_WIDGET") {
|
||||
const bottomRow = getCanvasBottomRow(widgetId, widgets);
|
||||
widgets[widgetId].bottomRow = bottomRow;
|
||||
}
|
||||
}
|
||||
return widgets;
|
||||
},
|
||||
[ReduxActionTypes.UPDATE_LAYOUT]: (
|
||||
state: CanvasWidgetsReduxState,
|
||||
|
|
@ -81,6 +92,15 @@ const canvasWidgetsReducer = createImmerReducer(initialState, {
|
|||
delete state[widgetId];
|
||||
}
|
||||
}
|
||||
|
||||
const canvasWidgetHeightsToUpdate: Record<
|
||||
string,
|
||||
number
|
||||
> = getCanvasWidgetHeightsToUpdate(listOfUpdatedWidgets, state);
|
||||
|
||||
for (const widgetId in canvasWidgetHeightsToUpdate) {
|
||||
state[widgetId].bottomRow = canvasWidgetHeightsToUpdate[widgetId];
|
||||
}
|
||||
},
|
||||
[ReduxActionTypes.UPDATE_MULTIPLE_WIDGET_PROPERTIES]: (
|
||||
state: CanvasWidgetsReduxState,
|
||||
|
|
@ -101,6 +121,14 @@ const canvasWidgetsReducer = createImmerReducer(initialState, {
|
|||
set(state, path, propertyValue);
|
||||
});
|
||||
}
|
||||
|
||||
const canvasWidgetHeightsToUpdate: Record<
|
||||
string,
|
||||
number
|
||||
> = getCanvasWidgetHeightsToUpdate(Object.keys(action.payload), state);
|
||||
for (const widgetId in canvasWidgetHeightsToUpdate) {
|
||||
state[widgetId].bottomRow = canvasWidgetHeightsToUpdate[widgetId];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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)`,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -88,7 +88,6 @@ export function* getCanvasSizeAfterWidgetMove(
|
|||
const newRows = calculateDropTargetRows(
|
||||
movedWidgetIds,
|
||||
movedWidgetsBottomRow,
|
||||
canvasMinHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
|
||||
occupiedSpacesByChildren,
|
||||
canvasWidgetId,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -56,9 +56,8 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
|
|||
import log from "loglevel";
|
||||
import { navigateToCanvas } from "pages/Editor/Explorer/Widgets/utils";
|
||||
import {
|
||||
getCanvasHeightOffset,
|
||||
getContainerWidgetSpacesSelector,
|
||||
getCurrentPageId,
|
||||
getContainerWidgetSpacesSelector,
|
||||
} from "selectors/editorSelectors";
|
||||
import { selectWidgetInitAction } from "actions/widgetSelectionActions";
|
||||
|
||||
|
|
@ -96,7 +95,6 @@ import {
|
|||
getNewPositionsForCopiedWidgets,
|
||||
getNextWidgetName,
|
||||
getOccupiedSpacesFromProps,
|
||||
getParentBottomRowAfterAddingWidget,
|
||||
getParentWidgetIdForGrouping,
|
||||
getParentWidgetIdForPasting,
|
||||
getPastePositionMapFromMousePointer,
|
||||
|
|
@ -148,53 +146,6 @@ import { traverseTreeAndExecuteBlueprintChildOperations } from "./WidgetBlueprin
|
|||
import { MetaState } from "reducers/entityReducers/metaReducer";
|
||||
import { SelectionRequestType } from "sagas/WidgetSelectUtils";
|
||||
|
||||
export function* updateAllChildCanvasHeights(
|
||||
currentContainerLikeWidgetId: string,
|
||||
topRow: number,
|
||||
bottomRow: number,
|
||||
allWidgets?: CanvasWidgetsReduxState,
|
||||
) {
|
||||
const containerLikeWidget: FlattenedWidgetProps = yield select(
|
||||
getWidget,
|
||||
currentContainerLikeWidgetId,
|
||||
);
|
||||
let stateWidgets: CanvasWidgetsReduxState | undefined = allWidgets;
|
||||
if (!stateWidgets) stateWidgets = yield select(getWidgets);
|
||||
const canvasHeightOffset: number = getCanvasHeightOffset(
|
||||
containerLikeWidget.type,
|
||||
containerLikeWidget,
|
||||
);
|
||||
const containerLikeWidgetHeightInPx: number =
|
||||
(bottomRow - topRow - canvasHeightOffset) *
|
||||
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
|
||||
const widgets = { ...stateWidgets };
|
||||
if (Array.isArray(containerLikeWidget.children)) {
|
||||
containerLikeWidget.children.forEach((childWidgetId: string) => {
|
||||
const childWidget = { ...widgets[childWidgetId] };
|
||||
if (Array.isArray(childWidget.children)) {
|
||||
const maxChildBottomRow = childWidget.children.reduce((prev, next) => {
|
||||
return widgets[next].bottomRow > prev
|
||||
? widgets[next].bottomRow
|
||||
: prev;
|
||||
}, 0);
|
||||
const maxHeightBasedOnChildrenInPx =
|
||||
maxChildBottomRow * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
const finalHeight = Math.max(
|
||||
containerLikeWidgetHeightInPx,
|
||||
maxHeightBasedOnChildrenInPx,
|
||||
);
|
||||
widgets[childWidgetId] = {
|
||||
...childWidget,
|
||||
bottomRow: finalHeight,
|
||||
minHeight: finalHeight,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
return widgets;
|
||||
}
|
||||
|
||||
export function* resizeSaga(resizeAction: ReduxAction<WidgetResize>) {
|
||||
try {
|
||||
Toaster.clear();
|
||||
|
|
@ -216,7 +167,7 @@ export function* resizeSaga(resizeAction: ReduxAction<WidgetResize>) {
|
|||
const widgets = { ...stateWidgets };
|
||||
|
||||
widget = { ...widget, leftColumn, rightColumn, topRow, bottomRow };
|
||||
let movedWidgets: {
|
||||
const movedWidgets: {
|
||||
[widgetId: string]: FlattenedWidgetProps;
|
||||
} = yield call(
|
||||
reflowWidgets,
|
||||
|
|
@ -226,13 +177,6 @@ export function* resizeSaga(resizeAction: ReduxAction<WidgetResize>) {
|
|||
snapRowSpace,
|
||||
);
|
||||
|
||||
movedWidgets = yield updateAllChildCanvasHeights(
|
||||
widgetId,
|
||||
topRow,
|
||||
bottomRow,
|
||||
movedWidgets,
|
||||
);
|
||||
|
||||
const updatedCanvasBottomRow: number = yield call(
|
||||
getCanvasSizeAfterWidgetMove,
|
||||
parentId,
|
||||
|
|
@ -1352,7 +1296,6 @@ function* pasteWidgetSaga(
|
|||
const selectedWidget: FlattenedWidgetProps<undefined> = yield getSelectedWidgetWhenPasting();
|
||||
|
||||
let reflowedMovementMap,
|
||||
bottomMostRow: number | undefined,
|
||||
gridProps: GridProps | undefined,
|
||||
newPastingPositionMap: SpaceMap | undefined,
|
||||
canvasId;
|
||||
|
|
@ -1386,7 +1329,6 @@ function* pasteWidgetSaga(
|
|||
// If there are already widgets inside the selection box even before grouping
|
||||
//then we will have to move it down to the bottom most row
|
||||
({
|
||||
bottomMostRow,
|
||||
copiedWidgetGroups,
|
||||
gridProps,
|
||||
reflowedMovementMap,
|
||||
|
|
@ -1420,7 +1362,6 @@ function* pasteWidgetSaga(
|
|||
// new pasting positions, the variables are undefined if the positions cannot be calculated,
|
||||
// then it pastes the regular way at the bottom of the canvas
|
||||
({
|
||||
bottomMostRow,
|
||||
canvasId,
|
||||
gridProps,
|
||||
newPastingPositionMap,
|
||||
|
|
@ -1617,16 +1558,11 @@ function* pasteWidgetSaga(
|
|||
// Add the new child to existing children
|
||||
parentChildren = parentChildren.concat(widgetChildren);
|
||||
}
|
||||
const parentBottomRow = getParentBottomRowAfterAddingWidget(
|
||||
widgets[pastingParentId],
|
||||
widget,
|
||||
);
|
||||
|
||||
widgets = {
|
||||
...widgets,
|
||||
[pastingParentId]: {
|
||||
...widgets[pastingParentId],
|
||||
bottomRow: Math.max(parentBottomRow, bottomMostRow || 0),
|
||||
children: parentChildren,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import { put, select } from "redux-saga/effects";
|
||||
import { getWidgets } from "sagas/selectors";
|
||||
import { getCanvasHeightOffset } from "selectors/editorSelectors";
|
||||
import { getCanvasHeightOffset } from "utils/WidgetSizeUtils";
|
||||
import { FlattenedWidgetProps } from "widgets/constants";
|
||||
import {
|
||||
getWidgetMaxAutoHeight,
|
||||
|
|
@ -22,18 +22,24 @@ import {
|
|||
getAutoHeightUpdateQueue,
|
||||
resetAutoHeightUpdateQueue,
|
||||
} from "./batcher";
|
||||
import {
|
||||
getChildOfContainerLikeWidget,
|
||||
getMinHeightBasedOnChildren,
|
||||
getParentCurrentHeightInRows,
|
||||
shouldWidgetsCollapse,
|
||||
} from "./helpers";
|
||||
import { getMinHeightBasedOnChildren, shouldWidgetsCollapse } from "./helpers";
|
||||
import { updateMultipleWidgetPropertiesAction } from "actions/controlActions";
|
||||
import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions";
|
||||
import {
|
||||
generateAutoHeightLayoutTreeAction,
|
||||
UpdateWidgetAutoHeightPayload,
|
||||
} from "actions/autoHeightActions";
|
||||
import { computeChangeInPositionBasedOnDelta } from "utils/autoHeight/reflow";
|
||||
import { CanvasLevelsReduxState } from "reducers/entityReducers/autoHeightReducers/canvasLevelsReducer";
|
||||
import { getCanvasLevelMap } from "selectors/autoHeightSelectors";
|
||||
import {
|
||||
getAutoHeightLayoutTree,
|
||||
getCanvasLevelMap,
|
||||
} from "selectors/autoHeightSelectors";
|
||||
import { getLayoutTree } from "./layoutTree";
|
||||
import { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
|
||||
import { TreeNode } from "utils/autoHeight/constants";
|
||||
import { directlyMutateDOMNodes } from "utils/autoHeight/mutateDOM";
|
||||
import { getAppMode } from "selectors/entitiesSelector";
|
||||
import { APP_MODE } from "entities/App";
|
||||
|
||||
/* TODO(abhinav)
|
||||
hasScroll is no longer needed, as the only way we will be computing for hasScroll, is when we get the updates
|
||||
|
|
@ -60,19 +66,47 @@ import { getLayoutTree } from "./layoutTree";
|
|||
* TODO: PERF_TRACK(abhinav): Make sure to benchmark the computations.
|
||||
* We need to propagate changes within 10ms
|
||||
*/
|
||||
export function* updateWidgetAutoHeightSaga() {
|
||||
const updates = getAutoHeightUpdateQueue();
|
||||
log.debug("Dynamic Height: updates to process", { updates });
|
||||
export function* updateWidgetAutoHeightSaga(
|
||||
action?: ReduxAction<UpdateWidgetAutoHeightPayload>,
|
||||
) {
|
||||
const start = performance.now();
|
||||
let shouldRecomputeContainers = false;
|
||||
|
||||
const shouldCollapse: boolean = yield shouldWidgetsCollapse();
|
||||
const appMode: APP_MODE = yield select(getAppMode);
|
||||
|
||||
const { tree: dynamicHeightLayoutTree } = yield getLayoutTree(false);
|
||||
let updates = getAutoHeightUpdateQueue();
|
||||
|
||||
let dynamicHeightLayoutTree: Record<string, TreeNode>;
|
||||
|
||||
const widgetsMeasuredInPixels = [];
|
||||
const widgetCanvasOffsets: Record<string, number> = {};
|
||||
|
||||
// Get all widgets from canvasWidgetsReducer
|
||||
const stateWidgets: CanvasWidgetsReduxState = yield select(getWidgets);
|
||||
|
||||
if (action?.payload) {
|
||||
const offset = getCanvasHeightOffset(
|
||||
stateWidgets[action.payload.widgetId].type,
|
||||
stateWidgets[action.payload.widgetId],
|
||||
);
|
||||
|
||||
updates = {
|
||||
[action.payload.widgetId]:
|
||||
action.payload.height + offset * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
||||
};
|
||||
/* Creating a new type called dynamicHeightLayoutTree. */
|
||||
dynamicHeightLayoutTree = yield select(getAutoHeightLayoutTree);
|
||||
} else {
|
||||
const result: {
|
||||
tree: Record<string, TreeNode>;
|
||||
canvasLevelsMap: Record<string, number>;
|
||||
} = yield getLayoutTree(false);
|
||||
dynamicHeightLayoutTree = result.tree;
|
||||
}
|
||||
|
||||
log.debug("Auto Height: updates to process", { updates });
|
||||
|
||||
// Initialise all the widgets we will be updating
|
||||
const widgetsToUpdate: UpdateWidgetsPayload = {};
|
||||
|
||||
|
|
@ -147,6 +181,8 @@ export function* updateWidgetAutoHeightSaga() {
|
|||
// For widgets like Modal Widget. (Rather this assumes that it is only the modal widget which needs a change)
|
||||
const newHeight = updates[widgetId];
|
||||
|
||||
widgetsMeasuredInPixels.push(widgetId);
|
||||
|
||||
// Setting the height and dimensions of the Modal Widget
|
||||
widgetsToUpdate[widgetId] = [
|
||||
{
|
||||
|
|
@ -162,23 +198,6 @@ export function* updateWidgetAutoHeightSaga() {
|
|||
propertyValue: widget.topRow,
|
||||
},
|
||||
];
|
||||
// Setting the child canvas widget's dimensions in the Modal Widget
|
||||
if (Array.isArray(widget.children) && widget.children.length === 1) {
|
||||
widgetsToUpdate[widget.children[0]] = [
|
||||
{
|
||||
propertyPath: "minHeight",
|
||||
propertyValue: newHeight,
|
||||
},
|
||||
{
|
||||
propertyPath: "bottomRow",
|
||||
propertyValue: newHeight,
|
||||
},
|
||||
{
|
||||
propertyPath: "topRow",
|
||||
propertyValue: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -323,28 +342,6 @@ export function* updateWidgetAutoHeightSaga() {
|
|||
minCanvasHeightInRows + canvasHeightOffset,
|
||||
);
|
||||
|
||||
// Setting this in a variable, as this will be the total scroll height in the canvas.
|
||||
const minCanvasHeightInPixels =
|
||||
(minHeightInRows - canvasHeightOffset) *
|
||||
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
|
||||
// We need to make sure that the canvas widget doesn't have
|
||||
// any extra scroll, to this end, we need to add the `minHeight` update
|
||||
// for the canvas widgets. Canvas Widgets are never updated in other flows
|
||||
// As they simply take up whatever space the parent has, but this doesn't effect
|
||||
// the `minHeight`, which leads to scroll if the `minHeight` is a larger value.
|
||||
// Also, for canvas widgets, the values are in pure pixels instead of rows.
|
||||
widgetsToUpdate[parentCanvasWidgetId] = [
|
||||
{
|
||||
propertyPath: "bottomRow",
|
||||
propertyValue: minCanvasHeightInPixels,
|
||||
},
|
||||
{
|
||||
propertyPath: "minHeight",
|
||||
propertyValue: minCanvasHeightInPixels,
|
||||
},
|
||||
];
|
||||
|
||||
// Make sure we're not overflowing the max height bounds
|
||||
const maxDynamicHeight = getWidgetMaxAutoHeight(
|
||||
parentContainerLikeWidget,
|
||||
|
|
@ -356,7 +353,15 @@ export function* updateWidgetAutoHeightSaga() {
|
|||
dynamicHeightLayoutTree[parentContainerLikeWidget.widgetId];
|
||||
|
||||
if (layoutData === undefined) {
|
||||
layoutData = parentContainerLikeWidget;
|
||||
layoutData = {
|
||||
bottomRow: parentContainerLikeWidget.bottomRow,
|
||||
topRow: parentContainerLikeWidget.topRow,
|
||||
aboves: [],
|
||||
belows: [],
|
||||
originalBottomRow: parentContainerLikeWidget.bottomRow,
|
||||
originalTopRow: parentContainerLikeWidget.topRow,
|
||||
distanceToNearestAbove: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Convert this change into the standard expected update format.
|
||||
|
|
@ -377,6 +382,10 @@ export function* updateWidgetAutoHeightSaga() {
|
|||
// We need to make sure that we change properties other than bottomRow and topRow
|
||||
// In this case we're updating minHeight and height as well.
|
||||
if (parentContainerLikeWidget.detachFromLayout) {
|
||||
widgetsMeasuredInPixels.push(
|
||||
parentContainerLikeWidget.widgetId,
|
||||
);
|
||||
|
||||
// DRY this
|
||||
widgetsToUpdate[parentContainerLikeWidget.widgetId] = [
|
||||
{
|
||||
|
|
@ -440,80 +449,21 @@ export function* updateWidgetAutoHeightSaga() {
|
|||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Get the parent's topRow and bottomRow from the state
|
||||
let parentBottomRow = parentContainerLikeWidget.bottomRow;
|
||||
let parentTopRow = parentContainerLikeWidget.topRow;
|
||||
// If we have the parent's dimensions in the tree
|
||||
// and it is not a modal widget, then get the topRow
|
||||
// and bottomRow from the tree.
|
||||
if (
|
||||
dynamicHeightLayoutTree[parentContainerLikeWidget.widgetId] &&
|
||||
!parentContainerLikeWidget.detachFromLayout
|
||||
) {
|
||||
parentBottomRow =
|
||||
dynamicHeightLayoutTree[parentContainerLikeWidget.widgetId]
|
||||
.bottomRow;
|
||||
parentTopRow =
|
||||
dynamicHeightLayoutTree[parentContainerLikeWidget.widgetId]
|
||||
.topRow;
|
||||
}
|
||||
|
||||
// If this is a modal widget, then get the bottomRow in rows
|
||||
// as the height and bottomRow could be in pixels.
|
||||
if (
|
||||
parentContainerLikeWidget.detachFromLayout &&
|
||||
parentContainerLikeWidget.height
|
||||
) {
|
||||
parentBottomRow =
|
||||
parentTopRow +
|
||||
Math.ceil(
|
||||
parentContainerLikeWidget.height /
|
||||
GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
||||
);
|
||||
}
|
||||
|
||||
// Get the parent container's height in rows
|
||||
// It is possible that some other update has changed this parent's
|
||||
// dimensions.
|
||||
let parentContainerHeightInRows = getParentCurrentHeightInRows(
|
||||
parentBottomRow,
|
||||
parentTopRow,
|
||||
parentContainerLikeWidget.widgetId,
|
||||
changesSoFar,
|
||||
);
|
||||
|
||||
parentContainerHeightInRows -= canvasHeightOffset;
|
||||
|
||||
// Setting this in a variable, as this will be the total scroll height in the canvas.
|
||||
const minCanvasHeightInPixels =
|
||||
Math.max(minCanvasHeightInRows, parentContainerHeightInRows) *
|
||||
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
|
||||
// We need to make sure that the canvas widget doesn't have
|
||||
// any extra scroll, to this end, we need to add the `minHeight` update
|
||||
// for the canvas widgets. Canvas Widgets are never updated in other flows
|
||||
// As they simply take up whatever space the parent has, but this doesn't effect
|
||||
// the `minHeight`, which leads to scroll if the `minHeight` is a larger value.
|
||||
// Also, for canvas widgets, the values are in pure pixels instead of rows.
|
||||
widgetsToUpdate[parentCanvasWidgetId] = [
|
||||
{
|
||||
propertyPath: "bottomRow",
|
||||
propertyValue: minCanvasHeightInPixels,
|
||||
},
|
||||
{
|
||||
propertyPath: "minHeight",
|
||||
propertyValue: minCanvasHeightInPixels,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Let's consider the minimum Canvas Height
|
||||
const mainContainerMinHeight =
|
||||
stateWidgets[MAIN_CONTAINER_WIDGET_ID].minHeight;
|
||||
const canvasMinHeight: number =
|
||||
appMode === APP_MODE.EDIT && mainContainerMinHeight !== undefined
|
||||
? mainContainerMinHeight
|
||||
: CANVAS_DEFAULT_MIN_HEIGHT_PX;
|
||||
let maxCanvasHeightInRows =
|
||||
CANVAS_DEFAULT_MIN_HEIGHT_PX / GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
canvasMinHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
|
||||
// The same logic to compute the minimum height of the MainContainer
|
||||
// Based on how many rows are being occuped by children.
|
||||
|
||||
|
|
@ -529,13 +479,14 @@ export function* updateWidgetAutoHeightSaga() {
|
|||
maxCanvasHeightInRows,
|
||||
);
|
||||
|
||||
widgetsMeasuredInPixels.push(MAIN_CONTAINER_WIDGET_ID);
|
||||
|
||||
// Add the MainContainer's update.
|
||||
widgetsToUpdate[MAIN_CONTAINER_WIDGET_ID] = [
|
||||
{
|
||||
propertyPath: "bottomRow",
|
||||
propertyValue:
|
||||
(maxCanvasHeightInRows + GridDefaults.MAIN_CANVAS_EXTENSION_OFFSET) *
|
||||
GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
||||
maxCanvasHeightInRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -547,6 +498,15 @@ export function* updateWidgetAutoHeightSaga() {
|
|||
changedWidgetId
|
||||
];
|
||||
|
||||
if (!action?.payload) {
|
||||
const canvasOffset = getCanvasHeightOffset(
|
||||
stateWidgets[changedWidgetId].type,
|
||||
stateWidgets[changedWidgetId],
|
||||
);
|
||||
|
||||
widgetCanvasOffsets[changedWidgetId] = canvasOffset;
|
||||
}
|
||||
|
||||
widgetsToUpdate[changedWidgetId] = [
|
||||
{
|
||||
propertyPath: "bottomRow",
|
||||
|
|
@ -565,64 +525,33 @@ export function* updateWidgetAutoHeightSaga() {
|
|||
propertyValue: originalBottomRow,
|
||||
},
|
||||
];
|
||||
const containerLikeWidget = stateWidgets[changedWidgetId];
|
||||
|
||||
if (
|
||||
Array.isArray(containerLikeWidget.children) &&
|
||||
containerLikeWidget.children.length > 0
|
||||
) {
|
||||
const childWidgetId:
|
||||
| string
|
||||
| undefined = yield getChildOfContainerLikeWidget(
|
||||
containerLikeWidget,
|
||||
);
|
||||
|
||||
if (childWidgetId) {
|
||||
const childCanvasWidget = stateWidgets[childWidgetId];
|
||||
const isCanvasWidget = childCanvasWidget?.type === "CANVAS_WIDGET";
|
||||
if (isCanvasWidget) {
|
||||
let canvasHeight: number = yield getMinHeightBasedOnChildren(
|
||||
childWidgetId,
|
||||
changesSoFar,
|
||||
false,
|
||||
dynamicHeightLayoutTree,
|
||||
);
|
||||
canvasHeight += GridDefaults.CANVAS_EXTENSION_OFFSET;
|
||||
|
||||
const propertyUpdates = [
|
||||
{
|
||||
propertyPath: "minHeight",
|
||||
propertyValue:
|
||||
canvasHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
||||
},
|
||||
{
|
||||
propertyPath: "bottomRow",
|
||||
propertyValue:
|
||||
canvasHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
||||
},
|
||||
];
|
||||
|
||||
containerLikeWidget.children.forEach((childWidgetId) => {
|
||||
if (!widgetsToUpdate.hasOwnProperty(childWidgetId)) {
|
||||
widgetsToUpdate[childWidgetId] = propertyUpdates;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Dynamic height: Widgets to update:", { widgetsToUpdate });
|
||||
log.debug("Auto height: Widgets to update:", { widgetsToUpdate });
|
||||
|
||||
if (Object.keys(widgetsToUpdate).length > 0) {
|
||||
// Push all updates to the CanvasWidgetsReducer.
|
||||
// Note that we're not calling `UPDATE_LAYOUT`
|
||||
// as we don't need to trigger an eval
|
||||
yield put(updateMultipleWidgetPropertiesAction(widgetsToUpdate));
|
||||
resetAutoHeightUpdateQueue();
|
||||
yield put(
|
||||
generateAutoHeightLayoutTreeAction(shouldRecomputeContainers, false),
|
||||
if (!action?.payload) {
|
||||
// Push all updates to the CanvasWidgetsReducer.
|
||||
// Note that we're not calling `UPDATE_LAYOUT`
|
||||
// as we don't need to trigger an eval
|
||||
yield put(updateMultipleWidgetPropertiesAction(widgetsToUpdate));
|
||||
resetAutoHeightUpdateQueue();
|
||||
yield put(
|
||||
generateAutoHeightLayoutTreeAction(
|
||||
false,
|
||||
false,
|
||||
shouldRecomputeContainers,
|
||||
),
|
||||
);
|
||||
}
|
||||
directlyMutateDOMNodes(
|
||||
widgetsToUpdate as Record<
|
||||
string,
|
||||
Array<{ propertyPath: string; propertyValue: number }>
|
||||
>,
|
||||
widgetsMeasuredInPixels,
|
||||
widgetCanvasOffsets,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import {
|
|||
import {
|
||||
MAIN_CONTAINER_WIDGET_ID,
|
||||
RenderModes,
|
||||
WidgetType,
|
||||
} from "constants/WidgetConstants";
|
||||
import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer";
|
||||
import { DataTree, DataTreeWidget } from "entities/DataTree/dataTreeFactory";
|
||||
|
|
@ -36,9 +35,6 @@ import {
|
|||
createLoadingWidget,
|
||||
} from "utils/widgetRenderUtils";
|
||||
import { checkIsDropTarget } from "components/designSystems/appsmith/PositionedContainer";
|
||||
import WidgetFactory, {
|
||||
NonSerialisableWidgetConfigs,
|
||||
} from "utils/WidgetFactory";
|
||||
import { LOCAL_STORAGE_KEYS } from "utils/localStorage";
|
||||
import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils";
|
||||
|
||||
|
|
@ -243,33 +239,6 @@ export const getCurrentPageName = createSelector(
|
|||
?.pageName,
|
||||
);
|
||||
|
||||
/**
|
||||
* This returns the number of rows which is not occupied by a Canvas Widget within
|
||||
* a parent container like widget of type widgetType
|
||||
* For example, the Tabs Widget takes 4 rows for the tabs
|
||||
* @param widgetType Type of widget
|
||||
* @param props Widget properties
|
||||
* @returns the offset in rows
|
||||
*/
|
||||
export const getCanvasHeightOffset = (
|
||||
widgetType: WidgetType,
|
||||
props: WidgetProps,
|
||||
) => {
|
||||
// Get the non serialisable configs for the widget type
|
||||
const config:
|
||||
| Record<NonSerialisableWidgetConfigs, unknown>
|
||||
| undefined = WidgetFactory.nonSerialisableWidgetConfigMap.get(widgetType);
|
||||
let offset = 0;
|
||||
// If this widget has a registered canvasHeightOffset function
|
||||
if (config?.canvasHeightOffset) {
|
||||
// Run the function to get the offset value
|
||||
offset = (config.canvasHeightOffset as (props: WidgetProps) => number)(
|
||||
props,
|
||||
);
|
||||
}
|
||||
return offset;
|
||||
};
|
||||
|
||||
export const getWidgetCards = createSelector(
|
||||
getWidgetConfigs,
|
||||
(widgetConfigs: WidgetConfigReducerState) => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
200
app/client/src/utils/WidgetSizeUtils.test.ts
Normal file
200
app/client/src/utils/WidgetSizeUtils.test.ts
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
import { RenderModes } from "constants/WidgetConstants";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import {
|
||||
getCanvasBottomRow,
|
||||
getCanvasWidgetHeightsToUpdate,
|
||||
} from "./WidgetSizeUtils";
|
||||
|
||||
const DUMMY_WIDGET: WidgetProps = {
|
||||
bottomRow: 0,
|
||||
isLoading: false,
|
||||
leftColumn: 0,
|
||||
parentColumnSpace: 0,
|
||||
parentRowSpace: 0,
|
||||
renderMode: RenderModes.CANVAS,
|
||||
rightColumn: 0,
|
||||
topRow: 0,
|
||||
type: "SKELETON_WIDGET",
|
||||
version: 2,
|
||||
widgetId: "",
|
||||
widgetName: "",
|
||||
};
|
||||
|
||||
it("Computes the bottomRow of the canvas within a container correctly", () => {
|
||||
const canvasWidgets = {
|
||||
x: {
|
||||
...DUMMY_WIDGET,
|
||||
widgetId: "x",
|
||||
bottomRow: 20,
|
||||
topRow: 10,
|
||||
type: "CONTAINER_WIDGET",
|
||||
children: ["m"],
|
||||
},
|
||||
m: {
|
||||
...DUMMY_WIDGET,
|
||||
widgetId: "m",
|
||||
parentId: "x",
|
||||
children: ["n", "o"],
|
||||
type: "CANVAS_WIDGET",
|
||||
},
|
||||
n: {
|
||||
...DUMMY_WIDGET,
|
||||
bottomRow: 30,
|
||||
},
|
||||
o: {
|
||||
...DUMMY_WIDGET,
|
||||
bottomRow: 5,
|
||||
},
|
||||
};
|
||||
|
||||
const result = getCanvasBottomRow("m", canvasWidgets);
|
||||
expect(result).toBe(300);
|
||||
});
|
||||
|
||||
it("Computes the bottomRow of the canvas within a Modal correctly", () => {
|
||||
const canvasWidgets = {
|
||||
x: {
|
||||
...DUMMY_WIDGET,
|
||||
widgetId: "x",
|
||||
bottomRow: 20,
|
||||
topRow: 10,
|
||||
height: 200,
|
||||
type: "MODAL_WIDGET",
|
||||
children: ["m"],
|
||||
},
|
||||
m: {
|
||||
...DUMMY_WIDGET,
|
||||
widgetId: "m",
|
||||
parentId: "x",
|
||||
children: ["n", "o"],
|
||||
type: "CANVAS_WIDGET",
|
||||
},
|
||||
n: {
|
||||
...DUMMY_WIDGET,
|
||||
parentId: "m",
|
||||
bottomRow: 30,
|
||||
},
|
||||
o: {
|
||||
...DUMMY_WIDGET,
|
||||
parentId: "m",
|
||||
bottomRow: 5,
|
||||
},
|
||||
};
|
||||
|
||||
const result = getCanvasBottomRow("m", canvasWidgets);
|
||||
expect(result).toBe(300);
|
||||
});
|
||||
|
||||
it("Computes the bottomRow of the canvas within a Modal correctly", () => {
|
||||
const canvasWidgets = {
|
||||
x: {
|
||||
...DUMMY_WIDGET,
|
||||
widgetId: "x",
|
||||
bottomRow: 20,
|
||||
topRow: 10,
|
||||
height: 500,
|
||||
type: "MODAL_WIDGET",
|
||||
children: ["m"],
|
||||
},
|
||||
m: {
|
||||
...DUMMY_WIDGET,
|
||||
widgetId: "m",
|
||||
parentId: "x",
|
||||
children: ["n", "o"],
|
||||
type: "CANVAS_WIDGET",
|
||||
},
|
||||
n: {
|
||||
...DUMMY_WIDGET,
|
||||
bottomRow: 30,
|
||||
},
|
||||
o: {
|
||||
...DUMMY_WIDGET,
|
||||
bottomRow: 5,
|
||||
},
|
||||
};
|
||||
|
||||
const result = getCanvasBottomRow("m", canvasWidgets);
|
||||
expect(result).toBe(500);
|
||||
});
|
||||
|
||||
it("Computes the bottomRow of the canvas within a Container when the container has larger height correctly", () => {
|
||||
// The Container widget has a height of 10 rows, while the lowest widget is at 6 rows, so, the canvas should take this into account
|
||||
// and return 10 * Row height == 100
|
||||
const canvasWidgets = {
|
||||
x: {
|
||||
...DUMMY_WIDGET,
|
||||
widgetId: "x",
|
||||
bottomRow: 20,
|
||||
topRow: 10,
|
||||
type: "CONTAINER_WIDGET",
|
||||
children: ["m"],
|
||||
},
|
||||
m: {
|
||||
...DUMMY_WIDGET,
|
||||
widgetId: "m",
|
||||
parentId: "x",
|
||||
children: ["n", "o"],
|
||||
type: "CANVAS_WIDGET",
|
||||
},
|
||||
n: {
|
||||
...DUMMY_WIDGET,
|
||||
bottomRow: 6,
|
||||
},
|
||||
o: {
|
||||
...DUMMY_WIDGET,
|
||||
bottomRow: 5,
|
||||
},
|
||||
};
|
||||
|
||||
const result = getCanvasBottomRow("m", canvasWidgets);
|
||||
expect(result).toBe(100);
|
||||
});
|
||||
|
||||
it("Computes all the effected canvases for the changed widgets", () => {
|
||||
const canvasWidgets = {
|
||||
x: {
|
||||
...DUMMY_WIDGET,
|
||||
widgetId: "x",
|
||||
bottomRow: 20,
|
||||
topRow: 10,
|
||||
type: "CONTAINER_WIDGET",
|
||||
children: ["m"],
|
||||
},
|
||||
m: {
|
||||
...DUMMY_WIDGET,
|
||||
widgetId: "m",
|
||||
parentId: "x",
|
||||
children: ["n", "o", "p"],
|
||||
type: "CANVAS_WIDGET",
|
||||
},
|
||||
n: {
|
||||
...DUMMY_WIDGET,
|
||||
bottomRow: 6,
|
||||
},
|
||||
o: {
|
||||
...DUMMY_WIDGET,
|
||||
bottomRow: 5,
|
||||
},
|
||||
p: {
|
||||
...DUMMY_WIDGET,
|
||||
widgetId: "p",
|
||||
bottomRow: 20,
|
||||
topRow: 10,
|
||||
type: "CONTAINER_WIDGET",
|
||||
children: ["q"],
|
||||
parentId: "m",
|
||||
},
|
||||
q: {
|
||||
...DUMMY_WIDGET,
|
||||
widgetId: "q",
|
||||
parentId: "p",
|
||||
children: [],
|
||||
type: "CANVAS_WIDGET",
|
||||
},
|
||||
};
|
||||
|
||||
// Since the container p has changed, it will effect the parent m and the child q
|
||||
|
||||
const result = getCanvasWidgetHeightsToUpdate(["p"], canvasWidgets);
|
||||
expect(result).toStrictEqual({ q: 100, m: 200 });
|
||||
});
|
||||
174
app/client/src/utils/WidgetSizeUtils.ts
Normal file
174
app/client/src/utils/WidgetSizeUtils.ts
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
import { CANVAS_DEFAULT_MIN_HEIGHT_PX } from "constants/AppConstants";
|
||||
import {
|
||||
GridDefaults,
|
||||
MAIN_CONTAINER_WIDGET_ID,
|
||||
} from "constants/WidgetConstants";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import { FlattenedWidgetProps } from "widgets/constants";
|
||||
import WidgetFactory, {
|
||||
NonSerialisableWidgetConfigs,
|
||||
WidgetType,
|
||||
} from "./WidgetFactory";
|
||||
|
||||
/**
|
||||
* This returns the number of rows which is not occupied by a Canvas Widget within
|
||||
* a parent container like widget of type widgetType
|
||||
* For example, the Tabs Widget takes 4 rows for the tabs
|
||||
* @param widgetType Type of widget
|
||||
* @param props Widget properties
|
||||
* @returns the offset in rows
|
||||
*/
|
||||
export const getCanvasHeightOffset = (
|
||||
widgetType: WidgetType,
|
||||
props: WidgetProps,
|
||||
) => {
|
||||
// Get the non serialisable configs for the widget type
|
||||
const config:
|
||||
| Record<NonSerialisableWidgetConfigs, unknown>
|
||||
| undefined = WidgetFactory.nonSerialisableWidgetConfigMap.get(widgetType);
|
||||
let offset = 0;
|
||||
// If this widget has a registered canvasHeightOffset function
|
||||
if (config?.canvasHeightOffset) {
|
||||
// Run the function to get the offset value
|
||||
offset = (config.canvasHeightOffset as (props: WidgetProps) => number)(
|
||||
props,
|
||||
);
|
||||
}
|
||||
return offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function computes the heights of canvas widgets which may be effected by the changes in other widget properties (updatedWidgetIds)
|
||||
* @param updatedWidgetIds Widgets which have updated
|
||||
* @param canvasWidgets The widgets in the redux state, used for computations
|
||||
* @returns A list of canvas widget ids with their heights in pixels
|
||||
*/
|
||||
export function getCanvasWidgetHeightsToUpdate(
|
||||
updatedWidgetIds: string[],
|
||||
canvasWidgets: Record<string, FlattenedWidgetProps>,
|
||||
): Record<string, number> {
|
||||
const updatedCanvasWidgets: Record<string, number> = {};
|
||||
for (const widgetId of updatedWidgetIds) {
|
||||
const widget = canvasWidgets[widgetId];
|
||||
if (widget) {
|
||||
if (
|
||||
widget.type !== "CANVAS_WIDGET" &&
|
||||
Array.isArray(widget.children) &&
|
||||
widget.children.length > 0
|
||||
) {
|
||||
for (const childCanvasWidgetId of widget.children) {
|
||||
if (!updatedCanvasWidgets.hasOwnProperty(childCanvasWidgetId)) {
|
||||
const bottomRow = getCanvasBottomRow(
|
||||
childCanvasWidgetId,
|
||||
canvasWidgets,
|
||||
);
|
||||
if (bottomRow > 0) {
|
||||
updatedCanvasWidgets[childCanvasWidgetId] = bottomRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (widget.parentId) {
|
||||
if (!updatedCanvasWidgets.hasOwnProperty(widget.parentId)) {
|
||||
const bottomRow = getCanvasBottomRow(widget.parentId, canvasWidgets);
|
||||
if (bottomRow > 0) updatedCanvasWidgets[widget.parentId] = bottomRow;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This usually means, that we're deleting a widget.
|
||||
if (!updatedCanvasWidgets.hasOwnProperty(MAIN_CONTAINER_WIDGET_ID)) {
|
||||
const bottomRow = getCanvasBottomRow(
|
||||
MAIN_CONTAINER_WIDGET_ID,
|
||||
canvasWidgets,
|
||||
);
|
||||
if (bottomRow > 0)
|
||||
updatedCanvasWidgets[MAIN_CONTAINER_WIDGET_ID] = bottomRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
return updatedCanvasWidgets;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function to compute the height of a given canvas widget (canvasWidgetId) in pixels
|
||||
* @param canvasWidgetId The CANVAS_WIDGET's widgetId. This canvas widget is the one whose bottomRow we need to compute
|
||||
* @param canvasWidgets The widgets in the redux state. We use this to get appropriate info regarding types, parent and children for computations
|
||||
* @returns The canvas widget's height in pixels (this is also the minHight and bottomRow property values)
|
||||
*/
|
||||
export function getCanvasBottomRow(
|
||||
canvasWidgetId: string,
|
||||
canvasWidgets: Record<string, FlattenedWidgetProps>,
|
||||
) {
|
||||
const canvasWidget = canvasWidgets[canvasWidgetId];
|
||||
// If this widget is not defined
|
||||
// It is likely a part of the list widget's canvases
|
||||
if (canvasWidget === undefined) {
|
||||
return 0;
|
||||
}
|
||||
// If this widget is not a CANVAS_WIDGET
|
||||
if (canvasWidget.type !== "CANVAS_WIDGET") {
|
||||
return canvasWidget.bottomRow;
|
||||
}
|
||||
|
||||
const children = canvasWidget.children;
|
||||
let parentHeightInRows = Math.ceil(
|
||||
canvasWidget.bottomRow / GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
||||
);
|
||||
|
||||
// Hypothetical thoughts:
|
||||
// If this is the MainContainer
|
||||
// We need some special handling.
|
||||
// What we can do is use the viewport height and compute the minimum using that
|
||||
// in the edit mode
|
||||
// In the view mode, we can do the same?
|
||||
// This is because, we might have changed the "bottomRow" somewhere and that will
|
||||
// cause it to consider that value, and give us a large scroll.
|
||||
|
||||
if (canvasWidget.parentId) {
|
||||
const parentWidget = canvasWidgets[canvasWidget.parentId];
|
||||
// If the parent widget is undefined but the parentId exists
|
||||
// It is likely a part of the list widget
|
||||
if (parentWidget === undefined) {
|
||||
return 0;
|
||||
}
|
||||
// If the parent is list widget, let's return the canvasWidget.bottomRow
|
||||
// We'll be handling this specially in withWidgetProps
|
||||
if (parentWidget.type === "LIST_WIDGET") {
|
||||
return canvasWidget.bottomRow;
|
||||
}
|
||||
|
||||
// Widgets like Tabs widget have an offset we need to subtract
|
||||
const parentHeightOffset = getCanvasHeightOffset(
|
||||
parentWidget.type,
|
||||
parentWidget,
|
||||
);
|
||||
// The parent's height in rows
|
||||
parentHeightInRows = parentWidget.bottomRow - parentWidget.topRow;
|
||||
|
||||
// If the parent is modal widget, we need to consider the `height` instead
|
||||
// of the bottomRow
|
||||
// TODO(abhinav): We could use one or the other and not have both, maybe
|
||||
// update the bottomRow of the modal widget instead?
|
||||
if (parentWidget.type === "MODAL_WIDGET" && parentWidget.height) {
|
||||
parentHeightInRows = Math.floor(
|
||||
parentWidget.height / GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
||||
);
|
||||
}
|
||||
// Subtract the canvas offset due to some parent elements
|
||||
parentHeightInRows = parentHeightInRows - parentHeightOffset;
|
||||
} else {
|
||||
parentHeightInRows =
|
||||
CANVAS_DEFAULT_MIN_HEIGHT_PX / GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
}
|
||||
|
||||
if (Array.isArray(children) && children.length > 0) {
|
||||
const bottomRow = children.reduce((prev, next) => {
|
||||
return canvasWidgets[next].bottomRow > prev
|
||||
? canvasWidgets[next].bottomRow
|
||||
: prev;
|
||||
}, parentHeightInRows);
|
||||
|
||||
return bottomRow * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
}
|
||||
return parentHeightInRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
}
|
||||
43
app/client/src/utils/autoHeight/mutateDOM.test.ts
Normal file
43
app/client/src/utils/autoHeight/mutateDOM.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { getNodesAndStylesToUpdate } from "./mutateDOM";
|
||||
describe("DOM mutations", () => {
|
||||
it("Computes the correct values to update based on widgetsToUpdate", () => {
|
||||
const widgetsToUpdate = {
|
||||
m: [
|
||||
{
|
||||
propertyValue: 100,
|
||||
propertyPath: "bottomRow",
|
||||
},
|
||||
{
|
||||
propertyValue: 10,
|
||||
propertyPath: "topRow",
|
||||
},
|
||||
],
|
||||
n: [
|
||||
{
|
||||
propertyValue: 100,
|
||||
propertyPath: "bottomRow",
|
||||
},
|
||||
],
|
||||
o: [
|
||||
{
|
||||
propertyValue: 100,
|
||||
propertyPath: "bottomRow",
|
||||
},
|
||||
{
|
||||
propertyValue: 0,
|
||||
propertyPath: "topRow",
|
||||
},
|
||||
],
|
||||
};
|
||||
const widgetsMeasuredInPixels = ["n", "o"];
|
||||
const result = getNodesAndStylesToUpdate(
|
||||
widgetsToUpdate,
|
||||
widgetsMeasuredInPixels,
|
||||
);
|
||||
expect(result).toStrictEqual({
|
||||
m: { y: 106, height: 900 },
|
||||
n: { y: 6, height: 100 }, // Since we're absolutely positioning the canvas widges, this y value is correct
|
||||
o: { y: 6, height: 100 },
|
||||
});
|
||||
});
|
||||
});
|
||||
130
app/client/src/utils/autoHeight/mutateDOM.ts
Normal file
130
app/client/src/utils/autoHeight/mutateDOM.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import {
|
||||
CONTAINER_GRID_PADDING,
|
||||
GridDefaults,
|
||||
MAIN_CONTAINER_WIDGET_ID,
|
||||
} from "constants/WidgetConstants";
|
||||
import { Classes } from "@blueprintjs/core";
|
||||
|
||||
// Here the data structure is the `widgetsToUpdate` data structure. If possible, we should create the `updates`
|
||||
// we use in the function directly in the `widgets.ts` (auto height saga)
|
||||
// This way, we can avoid looping again in this place.
|
||||
export function getNodesAndStylesToUpdate(
|
||||
widgetsToUpdate: Record<
|
||||
string,
|
||||
Array<{ propertyPath: string; propertyValue: number }>
|
||||
>,
|
||||
widgetsMeasuredInPixels: string[],
|
||||
): Record<string, Record<string, number>> {
|
||||
const result: Record<string, Record<string, number>> = {};
|
||||
// For each widget which has changes
|
||||
for (const widgetId in widgetsToUpdate) {
|
||||
const propertiesToUpdate: Record<string, number> = {};
|
||||
|
||||
// For each update in this widget
|
||||
for (const propertyUpdate of widgetsToUpdate[widgetId]) {
|
||||
// add to the data structure which is a key value pair.
|
||||
propertiesToUpdate[propertyUpdate.propertyPath] =
|
||||
propertyUpdate.propertyValue;
|
||||
}
|
||||
|
||||
// Start with top and height as 0
|
||||
let height = 0;
|
||||
let y = 0;
|
||||
|
||||
// If topRow is not defined, it is most likely the main container
|
||||
if (propertiesToUpdate.topRow === undefined) {
|
||||
propertiesToUpdate.topRow = 0;
|
||||
}
|
||||
|
||||
// If we have already measured in pixels, we don't need to multiply with row height
|
||||
const multiplier = widgetsMeasuredInPixels.includes(widgetId)
|
||||
? 1
|
||||
: GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
|
||||
if (propertiesToUpdate.height) {
|
||||
height = propertiesToUpdate.height * multiplier;
|
||||
} else {
|
||||
height =
|
||||
(propertiesToUpdate.bottomRow - propertiesToUpdate.topRow) * multiplier;
|
||||
}
|
||||
|
||||
y = propertiesToUpdate.topRow * multiplier + CONTAINER_GRID_PADDING;
|
||||
|
||||
result[widgetId] = { y, height };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function directlyMutateDOMNodes(
|
||||
widgetsToUpdate: Record<
|
||||
string,
|
||||
Array<{ propertyPath: string; propertyValue: number }>
|
||||
>,
|
||||
widgetsMeasuredInPixels: string[],
|
||||
widgetCanvasOffsets: Record<string, number>,
|
||||
): void {
|
||||
const updates: Record<
|
||||
string,
|
||||
Record<string, number>
|
||||
> = getNodesAndStylesToUpdate(widgetsToUpdate, widgetsMeasuredInPixels);
|
||||
|
||||
for (const widgetId in updates) {
|
||||
let idSelector = widgetId;
|
||||
let height = updates[widgetId].height;
|
||||
let skipTop = false;
|
||||
// Special handling for main container
|
||||
if (widgetId === MAIN_CONTAINER_WIDGET_ID) {
|
||||
// The element we need to resize is the the art-board
|
||||
idSelector = "art-board";
|
||||
// The height has to match the dropTarget, which means we need to make sure we adjust for padding
|
||||
// and the fact that drop target uses rows - 1 as default.
|
||||
// so, this is expected height - padding on top - padding at bottom - 1 row height
|
||||
height =
|
||||
updates[widgetId].height -
|
||||
CONTAINER_GRID_PADDING * 2 -
|
||||
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
|
||||
// Special handling for Modal widget 🤢
|
||||
// TODO(abhinav): We need to re-structure the modal widget, possibly get away from blueprintjs
|
||||
// Better yet, find a way that doesn't have to deal with these abstraction leaks.
|
||||
}
|
||||
let widgetNode = document.getElementById(idSelector);
|
||||
if (
|
||||
widgetsMeasuredInPixels.indexOf(widgetId) > -1 &&
|
||||
widgetId !== MAIN_CONTAINER_WIDGET_ID
|
||||
) {
|
||||
// We need to select the modal widget's parent overlay to adjust size.
|
||||
widgetNode = (widgetNode?.closest(`.${Classes.OVERLAY_CONTENT}`) ||
|
||||
null) as HTMLElement | null;
|
||||
// We don't mess with the top, and let the widget handle it.
|
||||
skipTop = true;
|
||||
}
|
||||
|
||||
const widgetBoundary = document.querySelector(
|
||||
`.widget-boundary-${widgetId}`,
|
||||
);
|
||||
if (widgetBoundary) {
|
||||
(widgetBoundary as HTMLDivElement).style.opacity = "0";
|
||||
}
|
||||
|
||||
const dropTarget = widgetNode?.querySelector(`.drop-target-${widgetId}`);
|
||||
|
||||
if (widgetNode) {
|
||||
widgetNode.style.height = `${height}px`;
|
||||
// For some widgets the top is going to be useless,
|
||||
// for example, modal widget and main container
|
||||
if (!skipTop) {
|
||||
widgetNode.style.top = `${updates[widgetId].y}px`;
|
||||
}
|
||||
|
||||
if (dropTarget) {
|
||||
const dropTargetHeight =
|
||||
updates[widgetId].height -
|
||||
CONTAINER_GRID_PADDING * 2 -
|
||||
(widgetCanvasOffsets[widgetId] || 0) *
|
||||
GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
(dropTarget as HTMLElement).style.height = `${dropTargetHeight}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,16 +8,45 @@ import {
|
|||
isWidgetSelected,
|
||||
shouldWidgetIgnoreClicksSelector,
|
||||
} from "selectors/widgetSelectors";
|
||||
import styled from "styled-components";
|
||||
import { stopEventPropagation } from "utils/AppsmithUtils";
|
||||
import { scrollCSS } from "widgets/WidgetUtils";
|
||||
import { useWidgetSelection } from "./useWidgetSelection";
|
||||
import { SelectionRequestType } from "sagas/WidgetSelectUtils";
|
||||
import { Colors } from "constants/Colors";
|
||||
|
||||
const ContentWrapper = styled.div<{
|
||||
backgroundColor?: string;
|
||||
borderRadius?: string;
|
||||
}>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: ${({ backgroundColor }) => `${backgroundColor || Colors.WHITE}`};
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
${scrollCSS}
|
||||
`;
|
||||
|
||||
const ScrollWrapper = styled.div<{
|
||||
backgroundColor?: string;
|
||||
borderRadius?: string;
|
||||
}>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: ${({ backgroundColor }) => `${backgroundColor || Colors.WHITE}`};
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export function ClickContentToOpenPropPane({
|
||||
backgroundColor,
|
||||
borderRadius,
|
||||
children,
|
||||
widgetId,
|
||||
}: {
|
||||
widgetId: string;
|
||||
children?: ReactNode;
|
||||
backgroundColor?: string;
|
||||
borderRadius?: string;
|
||||
}) {
|
||||
const { focusWidget } = useWidgetSelection();
|
||||
|
||||
|
|
@ -42,20 +71,20 @@ export function ClickContentToOpenPropPane({
|
|||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const styles = {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={stopEventPropagation}
|
||||
onMouseDownCapture={clickToSelectWidget}
|
||||
onMouseOver={handleMouseOver}
|
||||
style={styles}
|
||||
<ScrollWrapper
|
||||
backgroundColor={backgroundColor}
|
||||
borderRadius={borderRadius}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<ContentWrapper
|
||||
className="scroll-parent"
|
||||
onClick={stopEventPropagation}
|
||||
onMouseDownCapture={clickToSelectWidget}
|
||||
onMouseOver={handleMouseOver}
|
||||
>
|
||||
{children}
|
||||
</ContentWrapper>
|
||||
</ScrollWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,25 +72,6 @@ const TooltipStyles = createGlobalStyle`
|
|||
}
|
||||
`;
|
||||
|
||||
/*
|
||||
Don't use buttonHoverActiveStyles in a nested function it won't work -
|
||||
|
||||
const buttonHoverActiveStyles = css ``
|
||||
|
||||
const Button = styled.button`
|
||||
// won't work
|
||||
${({ buttonColor, theme }) => {
|
||||
&:hover, &:active {
|
||||
${buttonHoverActiveStyles}
|
||||
}
|
||||
}}
|
||||
|
||||
// will work
|
||||
&:hover, &:active {
|
||||
${buttonHoverActiveStyles}
|
||||
}`
|
||||
*/
|
||||
|
||||
const buttonBaseStyle = css<ThemeProp & ButtonStyleProps>`
|
||||
height: 100%;
|
||||
background-image: none !important;
|
||||
|
|
@ -408,6 +389,10 @@ function RecaptchaV3Component(
|
|||
);
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
function BtnWrapper(
|
||||
props: {
|
||||
children: any;
|
||||
|
|
@ -419,14 +404,14 @@ function BtnWrapper(
|
|||
) {
|
||||
if (!props.googleRecaptchaKey) {
|
||||
return (
|
||||
<div
|
||||
<Wrapper
|
||||
className={props.className}
|
||||
onClick={(e: React.MouseEvent<HTMLElement>) =>
|
||||
props.onClick && !props.isLoading && props.onClick(e)
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
</Wrapper>
|
||||
);
|
||||
} else {
|
||||
const handleError = (
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ const CameraContainer = styled.div<CameraContainerProps>`
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
box-shadow: ${({ boxShadow }) => boxShadow};
|
||||
background: ${({ disabled }) => (disabled ? Colors.GREY_3 : Colors.BLACK)};
|
||||
|
|
|
|||
|
|
@ -27,13 +27,22 @@ class CanvasWidget extends ContainerWidget {
|
|||
containerStyle: "none",
|
||||
detachFromLayout: true,
|
||||
minHeight: this.props.minHeight || CANVAS_DEFAULT_MIN_HEIGHT_PX,
|
||||
shouldScrollContents: false,
|
||||
};
|
||||
}
|
||||
|
||||
renderAsDropTarget() {
|
||||
const canvasProps = this.getCanvasProps();
|
||||
const { snapColumnSpace } = this.getSnapSpaces();
|
||||
return (
|
||||
<DropTargetComponent {...canvasProps} {...this.getSnapSpaces()}>
|
||||
<DropTargetComponent
|
||||
bottomRow={this.props.bottomRow}
|
||||
minHeight={this.props.minHeight || CANVAS_DEFAULT_MIN_HEIGHT_PX}
|
||||
noPad={this.props.noPad}
|
||||
parentId={this.props.parentId}
|
||||
snapColumnSpace={snapColumnSpace}
|
||||
widgetId={this.props.widgetId}
|
||||
>
|
||||
{this.renderAsContainerComponent(canvasProps)}
|
||||
</DropTargetComponent>
|
||||
);
|
||||
|
|
@ -56,7 +65,7 @@ class CanvasWidget extends ContainerWidget {
|
|||
|
||||
getPageView() {
|
||||
let height = 0;
|
||||
const snapRows = getCanvasSnapRows(this.props.bottomRow, false);
|
||||
const snapRows = getCanvasSnapRows(this.props.bottomRow);
|
||||
height = snapRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
|
||||
const style: CSSProperties = {
|
||||
width: "100%",
|
||||
|
|
|
|||
|
|
@ -1,59 +1,54 @@
|
|||
import React, { ReactNode, useRef, useEffect, RefObject } from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import tinycolor from "tinycolor2";
|
||||
import { invisible } from "constants/DefaultTheme";
|
||||
import { Color } from "constants/Colors";
|
||||
import React, {
|
||||
MouseEventHandler,
|
||||
PropsWithChildren,
|
||||
ReactNode,
|
||||
RefObject,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
import styled from "styled-components";
|
||||
import { generateClassName, getCanvasClassName } from "utils/generators";
|
||||
import WidgetStyleContainer, {
|
||||
WidgetStyleContainerProps,
|
||||
} from "components/designSystems/appsmith/WidgetStyleContainer";
|
||||
import { pick } from "lodash";
|
||||
import { ComponentProps } from "widgets/BaseComponent";
|
||||
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
|
||||
|
||||
const scrollContents = css`
|
||||
overflow-y: auto;
|
||||
`;
|
||||
import tinycolor from "tinycolor2";
|
||||
import { WidgetType } from "utils/WidgetFactory";
|
||||
import { scrollCSS } from "widgets/WidgetUtils";
|
||||
|
||||
const StyledContainerComponent = styled.div<
|
||||
ContainerComponentProps & {
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
}
|
||||
Omit<ContainerWrapperProps, "widgetId">
|
||||
>`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: ${(props) => props.backgroundColor};
|
||||
overflow: hidden;
|
||||
${(props) => (props.shouldScrollContents ? scrollCSS : ``)}
|
||||
opacity: ${(props) => (props.resizeDisabled ? "0.8" : "1")};
|
||||
position: relative;
|
||||
${(props) => (!props.isVisible ? invisible : "")};
|
||||
box-shadow: ${(props) =>
|
||||
props.selected ? "inset 0px 0px 0px 3px rgba(59,130,246,0.5)" : "none"};
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
|
||||
${(props) =>
|
||||
props.shouldScrollContents === true
|
||||
? scrollContents
|
||||
: props.shouldScrollContents === false
|
||||
? css`
|
||||
overflow: hidden;
|
||||
`
|
||||
: ""}
|
||||
|
||||
background: ${(props) => props.backgroundColor};
|
||||
&:hover {
|
||||
z-index: ${(props) => (props.onClickCapture ? "2" : "1")};
|
||||
cursor: ${(props) => (props.onClickCapture ? "pointer" : "inherit")};
|
||||
background: ${(props) => {
|
||||
background-color: ${(props) => {
|
||||
return props.onClickCapture && props.backgroundColor
|
||||
? tinycolor(props.backgroundColor)
|
||||
.darken(5)
|
||||
.toString()
|
||||
: props.backgroundColor;
|
||||
}};
|
||||
z-index: ${(props) => (props.onClickCapture ? "2" : "1")};
|
||||
cursor: ${(props) => (props.onClickCapture ? "pointer" : "inherit")};
|
||||
}
|
||||
`;
|
||||
|
||||
function ContainerComponentWrapper(props: ContainerComponentProps) {
|
||||
const containerStyle = props.containerStyle || "card";
|
||||
interface ContainerWrapperProps {
|
||||
onClickCapture?: MouseEventHandler<HTMLDivElement>;
|
||||
resizeDisabled?: boolean;
|
||||
shouldScrollContents?: boolean;
|
||||
backgroundColor?: string;
|
||||
widgetId: string;
|
||||
type: WidgetType;
|
||||
}
|
||||
function ContainerComponentWrapper(
|
||||
props: PropsWithChildren<ContainerWrapperProps>,
|
||||
) {
|
||||
const containerRef: RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
if (!props.shouldScrollContents) {
|
||||
|
|
@ -70,15 +65,18 @@ function ContainerComponentWrapper(props: ContainerComponentProps) {
|
|||
}, [props.shouldScrollContents]);
|
||||
return (
|
||||
<StyledContainerComponent
|
||||
{...props}
|
||||
// Before you remove: generateClassName is used for bounding the resizables within this canvas
|
||||
// getCanvasClassName is used to add a scrollable parent.
|
||||
backgroundColor={props.backgroundColor}
|
||||
className={`${
|
||||
props.shouldScrollContents ? getCanvasClassName() : ""
|
||||
} ${generateClassName(props.widgetId)}`}
|
||||
containerStyle={containerStyle}
|
||||
} ${generateClassName(props.widgetId)} container-with-scrollbar`}
|
||||
onClickCapture={props.onClickCapture}
|
||||
ref={containerRef}
|
||||
resizeDisabled={props.resizeDisabled}
|
||||
shouldScrollContents={!!props.shouldScrollContents}
|
||||
tabIndex={props.shouldScrollContents ? undefined : 0}
|
||||
type={props.type}
|
||||
>
|
||||
{props.children}
|
||||
</StyledContainerComponent>
|
||||
|
|
@ -86,38 +84,55 @@ function ContainerComponentWrapper(props: ContainerComponentProps) {
|
|||
}
|
||||
|
||||
function ContainerComponent(props: ContainerComponentProps) {
|
||||
return props.widgetId === MAIN_CONTAINER_WIDGET_ID ? (
|
||||
<ContainerComponentWrapper {...props} />
|
||||
) : (
|
||||
if (props.detachFromLayout) {
|
||||
return (
|
||||
<ContainerComponentWrapper
|
||||
onClickCapture={props.onClickCapture}
|
||||
resizeDisabled={props.resizeDisabled}
|
||||
shouldScrollContents={props.shouldScrollContents}
|
||||
type={props.type}
|
||||
widgetId={props.widgetId}
|
||||
>
|
||||
{props.children}
|
||||
</ContainerComponentWrapper>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<WidgetStyleContainer
|
||||
{...pick(props, [
|
||||
"widgetId",
|
||||
"containerStyle",
|
||||
"backgroundColor",
|
||||
"borderColor",
|
||||
"borderWidth",
|
||||
"borderRadius",
|
||||
"boxShadow",
|
||||
])}
|
||||
backgroundColor={props.backgroundColor}
|
||||
borderColor={props.borderColor}
|
||||
borderRadius={props.borderRadius}
|
||||
borderWidth={props.borderWidth}
|
||||
boxShadow={props.boxShadow}
|
||||
className="style-container"
|
||||
containerStyle={props.containerStyle}
|
||||
widgetId={props.widgetId}
|
||||
>
|
||||
<ContainerComponentWrapper {...props} />
|
||||
<ContainerComponentWrapper
|
||||
backgroundColor={props.backgroundColor}
|
||||
onClickCapture={props.onClickCapture}
|
||||
resizeDisabled={props.resizeDisabled}
|
||||
shouldScrollContents={props.shouldScrollContents}
|
||||
type={props.type}
|
||||
widgetId={props.widgetId}
|
||||
>
|
||||
{props.children}
|
||||
</ContainerComponentWrapper>
|
||||
</WidgetStyleContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export type ContainerStyle = "border" | "card" | "rounded-border" | "none";
|
||||
|
||||
export interface ContainerComponentProps
|
||||
extends ComponentProps,
|
||||
WidgetStyleContainerProps {
|
||||
export interface ContainerComponentProps extends WidgetStyleContainerProps {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
backgroundColor?: Color;
|
||||
shouldScrollContents?: boolean;
|
||||
resizeDisabled?: boolean;
|
||||
selected?: boolean;
|
||||
focused?: boolean;
|
||||
minHeight?: number;
|
||||
detachFromLayout?: boolean;
|
||||
onClickCapture?: MouseEventHandler<HTMLDivElement>;
|
||||
backgroundColor?: string;
|
||||
type: WidgetType;
|
||||
noScroll?: boolean;
|
||||
}
|
||||
|
||||
export default ContainerComponent;
|
||||
|
|
|
|||
|
|
@ -209,7 +209,8 @@ class ContainerWidget extends BaseWidget<
|
|||
};
|
||||
|
||||
renderAsContainerComponent(props: ContainerWidgetProps<WidgetProps>) {
|
||||
const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend);
|
||||
const snapRows = getCanvasSnapRows(props.bottomRow);
|
||||
|
||||
return (
|
||||
<ContainerComponent {...props}>
|
||||
{props.type === "CANVAS_WIDGET" &&
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import PickMyLocation from "./PickMyLocation";
|
|||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const StyledMap = styled.div<Pick<MapProps, "borderRadius" | "boxShadow">>`
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import { getCanvasClassName } from "utils/generators";
|
|||
import { AppState } from "@appsmith/reducers";
|
||||
import { useWidgetDragResize } from "utils/hooks/dragResizeHooks";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { closeTableFilterPane } from "actions/widgetActions";
|
||||
|
||||
const Container = styled.div<{
|
||||
|
|
@ -39,8 +38,6 @@ const Container = styled.div<{
|
|||
maxWidth?: number;
|
||||
minSize?: number;
|
||||
isEditMode?: boolean;
|
||||
backgroundColor: string;
|
||||
borderRadius: string;
|
||||
}>`
|
||||
&&& {
|
||||
.${Classes.OVERLAY} {
|
||||
|
|
@ -78,9 +75,6 @@ const Container = styled.div<{
|
|||
left: ${(props) => props.left}px;
|
||||
bottom: ${(props) => props.bottom}px;
|
||||
right: ${(props) => props.right}px;
|
||||
background: ${({ backgroundColor }) =>
|
||||
`${backgroundColor || Colors.WHITE}`};
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +84,6 @@ const Content = styled.div<{
|
|||
scroll: boolean;
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
}>`
|
||||
overflow-y: ${(props) => (props.scroll ? "visible" : "hidden")};
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
@ -131,8 +124,6 @@ export type ModalComponentProps = {
|
|||
minSize?: number;
|
||||
widgetId: string;
|
||||
widgetName: string;
|
||||
backgroundColor: string;
|
||||
borderRadius: string;
|
||||
isDynamicHeightEnabled: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -272,8 +263,6 @@ export default function ModalComponent(props: ModalComponentProps) {
|
|||
usePortal={false}
|
||||
>
|
||||
<Container
|
||||
backgroundColor={props.backgroundColor}
|
||||
borderRadius={props.borderRadius}
|
||||
bottom={props.bottom}
|
||||
height={props.height}
|
||||
isEditMode={props.isEditMode}
|
||||
|
|
|
|||
|
|
@ -132,11 +132,9 @@ export class ModalWidget extends BaseWidget<ModalWidgetProps, WidgetState> {
|
|||
renderChildWidget = (childWidgetData: WidgetProps): ReactNode => {
|
||||
const childData = { ...childWidgetData };
|
||||
childData.parentId = this.props.widgetId;
|
||||
childData.shouldScrollContents = false;
|
||||
|
||||
childData.canExtend = this.props.shouldScrollContents;
|
||||
childData.bottomRow = this.props.shouldScrollContents
|
||||
? Math.max(childData.bottomRow, this.props.height)
|
||||
: this.props.height;
|
||||
|
||||
childData.containerStyle = "none";
|
||||
childData.minHeight = this.props.height;
|
||||
childData.rightColumn =
|
||||
|
|
@ -203,7 +201,11 @@ export class ModalWidget extends BaseWidget<ModalWidgetProps, WidgetState> {
|
|||
makeModalSelectable(content: ReactNode): ReactNode {
|
||||
// substitute coz the widget lacks draggable and position containers.
|
||||
return (
|
||||
<ClickContentToOpenPropPane widgetId={this.props.widgetId}>
|
||||
<ClickContentToOpenPropPane
|
||||
backgroundColor={this.props.backgroundColor}
|
||||
borderRadius={this.props.borderRadius}
|
||||
widgetId={this.props.widgetId}
|
||||
>
|
||||
{content}
|
||||
</ClickContentToOpenPropPane>
|
||||
);
|
||||
|
|
@ -231,8 +233,6 @@ export class ModalWidget extends BaseWidget<ModalWidgetProps, WidgetState> {
|
|||
|
||||
return (
|
||||
<ModalComponent
|
||||
backgroundColor={this.props.backgroundColor}
|
||||
borderRadius={this.props.borderRadius}
|
||||
canEscapeKeyClose={!!this.props.canEscapeKeyClose}
|
||||
canOutsideClickClose={!!this.props.canOutsideClickClose}
|
||||
className={`t--modal-widget ${generateClassName(this.props.widgetId)}`}
|
||||
|
|
@ -261,14 +261,7 @@ export class ModalWidget extends BaseWidget<ModalWidgetProps, WidgetState> {
|
|||
let children = this.getChildren();
|
||||
children = this.makeModalSelectable(children);
|
||||
children = this.showWidgetName(children, true);
|
||||
if (isAutoHeightEnabledForWidget(this.props, true)) {
|
||||
children = this.addAutoHeightOverlay(children, {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
left: 0,
|
||||
top: 0,
|
||||
});
|
||||
}
|
||||
|
||||
return this.makeModalComponent(children, true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { isNaN } from "lodash";
|
|||
const ProgressBarWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const ProgressBar = styled.div<{
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ const flowAnimation = css`
|
|||
const ProgressContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
// Determinate Linear progress
|
||||
|
|
|
|||
|
|
@ -1,19 +1,13 @@
|
|||
import React, {
|
||||
RefObject,
|
||||
ReactNode,
|
||||
useRef,
|
||||
useState,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import React, { ReactNode, useRef, useState, useCallback } from "react";
|
||||
import styled from "styled-components";
|
||||
import { MaybeElement } from "@blueprintjs/core";
|
||||
import { IconName } from "@blueprintjs/icons";
|
||||
import { ComponentProps } from "widgets/BaseComponent";
|
||||
import { TabsWidgetProps, TabContainerWidgetProps } from "../constants";
|
||||
import { Icon, IconSize } from "design-system-old";
|
||||
import { generateClassName, getCanvasClassName } from "utils/generators";
|
||||
import { Colors } from "constants/Colors";
|
||||
import PageTabs from "./PageTabs";
|
||||
import { scrollCSS } from "widgets/WidgetUtils";
|
||||
|
||||
interface TabsComponentProps extends ComponentProps {
|
||||
children?: ReactNode;
|
||||
|
|
@ -36,19 +30,7 @@ interface TabsComponentProps extends ComponentProps {
|
|||
width: number;
|
||||
}
|
||||
|
||||
type ChildrenWrapperProps = Pick<TabsComponentProps, "shouldShowTabs">;
|
||||
|
||||
const TAB_CONTAINER_HEIGHT = "44px";
|
||||
const CHILDREN_WRAPPER_HEIGHT_WITH_TABS = `calc(100% - ${TAB_CONTAINER_HEIGHT})`;
|
||||
const CHILDREN_WRAPPER_HEIGHT_WITHOUT_TABS = "100%";
|
||||
|
||||
const scrollContents = css`
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
`;
|
||||
|
||||
const TabsContainerWrapper = styled.div<{
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
borderRadius: string;
|
||||
boxShadow?: string;
|
||||
borderWidth?: number;
|
||||
|
|
@ -72,26 +54,6 @@ const TabsContainerWrapper = styled.div<{
|
|||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const ChildrenWrapper = styled.div<ChildrenWrapperProps>`
|
||||
position: relative;
|
||||
height: ${({ shouldShowTabs }) =>
|
||||
shouldShowTabs
|
||||
? CHILDREN_WRAPPER_HEIGHT_WITH_TABS
|
||||
: CHILDREN_WRAPPER_HEIGHT_WITHOUT_TABS};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const ScrollableCanvasWrapper = styled.div<
|
||||
TabsWidgetProps<TabContainerWidgetProps> & {
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
}
|
||||
>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
${(props) => (props.shouldScrollContents ? scrollContents : "")}
|
||||
`;
|
||||
|
||||
export interface TabsContainerProps {
|
||||
isScrollable: boolean;
|
||||
}
|
||||
|
|
@ -99,7 +61,7 @@ export interface TabsContainerProps {
|
|||
const Container = styled.div`
|
||||
width: 100%;
|
||||
align-items: flex-end;
|
||||
height: 44px;
|
||||
height: 40px;
|
||||
|
||||
& {
|
||||
svg path,
|
||||
|
|
@ -115,7 +77,7 @@ const ScrollBtnContainer = styled.div<{ visible: boolean }>`
|
|||
cursor: pointer;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
height: 34px;
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
|
||||
& > span {
|
||||
|
|
@ -146,12 +108,15 @@ export interface ScrollNavControlProps {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
const ScrollCanvas = styled.div<{ $shouldScrollContents: boolean }>`
|
||||
overflow: hidden;
|
||||
${(props) => (props.$shouldScrollContents ? scrollCSS : ``)}
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
function TabsComponent(props: TabsComponentProps) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { onTabChange, tabs, width, ...remainingProps } = props;
|
||||
const tabContainerRef: RefObject<HTMLDivElement> = useRef<HTMLDivElement>(
|
||||
null,
|
||||
);
|
||||
const { onTabChange, tabs } = props;
|
||||
|
||||
const tabsRef = useRef<HTMLElement | null>(null);
|
||||
const [tabsScrollable, setTabsScrollable] = useState(false);
|
||||
const [shouldShowLeftArrow, setShouldShowLeftArrow] = useState(false);
|
||||
|
|
@ -198,7 +163,6 @@ function TabsComponent(props: TabsComponentProps) {
|
|||
borderRadius={props.borderRadius}
|
||||
borderWidth={props.borderWidth}
|
||||
boxShadow={props.boxShadow}
|
||||
ref={tabContainerRef}
|
||||
>
|
||||
{props.shouldShowTabs && (
|
||||
<Container className="relative flex px-6 h-9">
|
||||
|
|
@ -229,16 +193,14 @@ function TabsComponent(props: TabsComponentProps) {
|
|||
</Container>
|
||||
)}
|
||||
|
||||
<ChildrenWrapper shouldShowTabs={props.shouldShowTabs}>
|
||||
<ScrollableCanvasWrapper
|
||||
{...remainingProps}
|
||||
className={`${
|
||||
props.shouldScrollContents ? getCanvasClassName() : ""
|
||||
} ${generateClassName(props.widgetId)}`}
|
||||
>
|
||||
{props.children}
|
||||
</ScrollableCanvasWrapper>
|
||||
</ChildrenWrapper>
|
||||
<ScrollCanvas
|
||||
$shouldScrollContents={!!props.shouldScrollContents}
|
||||
className={`${
|
||||
props.shouldScrollContents ? getCanvasClassName() : ""
|
||||
} ${generateClassName(props.widgetId)}`}
|
||||
>
|
||||
{props.children}
|
||||
</ScrollCanvas>
|
||||
</TabsContainerWrapper>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user