From 4b09509efb18afb4b6f5de78ed0e4b73dafe1e3d Mon Sep 17 00:00:00 2001 From: RAHEEL <mraheeliftikhar1994@gmail.com> Date: Fri, 30 Aug 2024 22:16:56 +0500 Subject: [PATCH 1/5] update exports for adding innergrid in Kanban comp --- client/packages/lowcoder/src/index.sdk.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/packages/lowcoder/src/index.sdk.ts b/client/packages/lowcoder/src/index.sdk.ts index 1c4f68599..af3fda679 100644 --- a/client/packages/lowcoder/src/index.sdk.ts +++ b/client/packages/lowcoder/src/index.sdk.ts @@ -6,9 +6,13 @@ import * as styledNameExports from "styled-components"; import styledDefault from "styled-components"; export * as styledm from "styled-components"; export * from "comps/comps/containerBase/containerCompBuilder"; +export * from "comps/comps/containerBase/iContainer"; +export * from "comps/comps/containerBase/utils"; +export * from "comps/comps/containerBase/simpleContainerComp"; export * from "comps/utils/backgroundColorContext"; export { getData } from "comps/comps/listViewComp/listViewUtils"; export { gridItemCompToGridItems, InnerGrid } from "comps/comps/containerComp/containerView"; +export type { ContainerBaseProps } from "comps/comps/containerComp/containerView"; export { Layers } from "constants/Layers"; export * from "comps/controls/eventHandlerControl"; @@ -97,6 +101,7 @@ export * from "comps/controls/simpleStringControl"; export * from "comps/controls/stringSimpleControl"; export * from "comps/controls/styleControl"; export * from "comps/controls/styleControlConstants"; +export * from "comps/controls/slotControl"; // generators export * from "comps/generators/changeDataType"; @@ -114,6 +119,7 @@ export * from "comps/generators/withExposing"; export * from "comps/generators/withIsLoading"; export * from "comps/generators/withMethodExposing"; export * from "comps/generators/withType"; +export * from "comps/generators/controlCompBuilder"; export * from "appView/bootstrapAt"; export * from "appView/LowcoderAppView"; From d91fe667aab0dfc053e888423f1eb4c8044c22c7 Mon Sep 17 00:00:00 2001 From: RAHEEL <mraheeliftikhar1994@gmail.com> Date: Fri, 30 Aug 2024 22:18:37 +0500 Subject: [PATCH 2/5] memoize comps to minimize re-rendering --- .../src/comps/generators/uiCompBuilder.tsx | 21 +++++++------- .../src/layout/compSelectionWrapper.tsx | 4 +-- .../packages/lowcoder/src/layout/gridItem.tsx | 28 +++++++++++-------- .../lowcoder/src/layout/gridLayout.tsx | 11 ++++---- .../lowcoder/src/pages/common/help.tsx | 4 ++- .../src/pages/common/previewHeader.tsx | 5 +++- .../src/pages/editor/editorHotKeys.tsx | 12 ++++---- .../lowcoder/src/pages/editor/editorView.tsx | 2 +- .../src/pages/editor/right/RightPanel.tsx | 5 +++- .../src/pages/tutorials/editorTutorials.tsx | 5 +++- 10 files changed, 57 insertions(+), 40 deletions(-) diff --git a/client/packages/lowcoder/src/comps/generators/uiCompBuilder.tsx b/client/packages/lowcoder/src/comps/generators/uiCompBuilder.tsx index b4a5f5ea2..4e9b1d24c 100644 --- a/client/packages/lowcoder/src/comps/generators/uiCompBuilder.tsx +++ b/client/packages/lowcoder/src/comps/generators/uiCompBuilder.tsx @@ -44,10 +44,10 @@ export type NewChildren<ChildrenCompMap extends Record<string, Comp<unknown>>> = version: InstanceType<typeof StringControl>; }; -export function HidableView(props: { +export const HidableView = React.memo((props: { children: JSX.Element | React.ReactNode; hidden: boolean; -}) { +}) => { const { readOnly } = useContext(ExternalEditorContext); if (readOnly) { return <>{props.children}</>; @@ -64,15 +64,15 @@ export function HidableView(props: { </> ); } -} +}) -export function ExtendedPropertyView< +export const ExtendedPropertyView = React.memo(< ChildrenCompMap extends Record<string, Comp<unknown>>, >(props: { children: JSX.Element | React.ReactNode, childrenMap: NewChildren<ChildrenCompMap> } -) { +) => { const [compVersions, setCompVersions] = useState(['latest']); const [compName, setCompName] = useState(''); const editorState = useContext(EditorContext); @@ -129,7 +129,7 @@ export function ExtendedPropertyView< )} </> ); -} +}); export function uiChildren< ChildrenCompMap extends Record<string, Comp<unknown>>, @@ -275,11 +275,11 @@ export const DisabledContext = React.createContext<boolean>(false); /** * Guaranteed to be in a react component, so that react hooks can be used internally */ -function UIView(props: { +const UIView = React.memo((props: { innerRef: React.RefObject<HTMLDivElement>; comp: any; viewFn: any; -}) { +}) => { const comp = props.comp; const childrenProps = childrenToProps(comp.children); const childrenJsonProps = comp.toJsonValue(); @@ -397,8 +397,7 @@ function UIView(props: { width: '100%', height: '100%', margin: '0px', - padding:getPadding() - + padding: getPadding() }} > <HidableView hidden={childrenProps.hidden as boolean}> @@ -406,4 +405,4 @@ function UIView(props: { </HidableView> </div> ); -} +}); diff --git a/client/packages/lowcoder/src/layout/compSelectionWrapper.tsx b/client/packages/lowcoder/src/layout/compSelectionWrapper.tsx index 447daa2a0..fd812a8b2 100644 --- a/client/packages/lowcoder/src/layout/compSelectionWrapper.tsx +++ b/client/packages/lowcoder/src/layout/compSelectionWrapper.tsx @@ -231,7 +231,7 @@ const HiddenIcon = styled(CloseEyeIcon)` } `; -export const CompSelectionWrapper = (props: { +export const CompSelectionWrapper = React.memo((props: { id?: string; compType: UICompType; className?: string; @@ -376,4 +376,4 @@ export const CompSelectionWrapper = (props: { </SelectableDiv> </div> ); -}; +}); diff --git a/client/packages/lowcoder/src/layout/gridItem.tsx b/client/packages/lowcoder/src/layout/gridItem.tsx index 47a04e87b..31893f15b 100644 --- a/client/packages/lowcoder/src/layout/gridItem.tsx +++ b/client/packages/lowcoder/src/layout/gridItem.tsx @@ -6,6 +6,7 @@ import React, { ReactElement, SyntheticEvent, useCallback, + useContext, useMemo, useRef, useState, @@ -26,6 +27,7 @@ import { setTransform, } from "./utils"; import styled from "styled-components"; +import { EditorContext } from "@lowcoder-ee/comps/editorState"; type GridItemCallback<Data extends GridDragEvent | GridResizeEvent> = ( i: string, @@ -104,6 +106,7 @@ export function GridItem(props: GridItemProps) { const [resizing, setResizing] = useState<{ width: number; height: number } | undefined>(); const [dragging, setDragging] = useState<{ top: number; left: number } | undefined>(); const elementRef = useRef<HTMLDivElement>(null); + const editorState = useContext(EditorContext); // record the real height of the comp content const itemHeightRef = useRef<number | undefined>(undefined); @@ -141,14 +144,17 @@ export function GridItem(props: GridItemProps) { onDrag={onDrag} onDragEnd={onDragEnd} onMouseDown={(e) => { - e.stopPropagation(); + const parentContainer = editorState.findUIParentContainer(props.name!)?.toJsonValue(); // allow mouseDown event on lowcoder-comp-kanban to make drag/drop work - if((props.compType as string).includes('lowcoder-comp-kanban')) return; + if( + (props.compType as string).includes('lowcoder-comp-kanban') + || parentContainer?.compType?.includes('lowcoder-comp-kanban') + ) return; // allow mouseDown event on lowcoder-comp-excalidraw to make drag/drop work if((props.compType as string).includes('lowcoder-comp-excalidraw')) return; - + e.stopPropagation(); const event = new MouseEvent("mousedown"); document.dispatchEvent(event); }} @@ -471,11 +477,11 @@ export function GridItem(props: GridItemProps) { return renderResult; } -GridItem.defaultProps = { - className: "", - minH: 1, - minW: 1, - maxH: Infinity, - maxW: Infinity, - transformScale: 1, -}; +// GridItem.defaultProps = { +// className: "", +// minH: 1, +// minW: 1, +// maxH: Infinity, +// maxW: Infinity, +// transformScale: 1, +// }; diff --git a/client/packages/lowcoder/src/layout/gridLayout.tsx b/client/packages/lowcoder/src/layout/gridLayout.tsx index 28e2e6220..77aaf965b 100644 --- a/client/packages/lowcoder/src/layout/gridLayout.tsx +++ b/client/packages/lowcoder/src/layout/gridLayout.tsx @@ -471,16 +471,16 @@ class GridLayout extends React.Component<GridLayoutProps, GridLayoutState> { isDraggable={isDraggable && isItemDraggable(item)} isResizable={isResizable && isItemResizable(item)} isSelectable={selectable} - transformScale={transformScale} + transformScale={transformScale || 1} w={item.w} h={extraItem?.hidden && !extraItem?.isSelected ? 0 : item.h} x={item.x} y={item.y} i={item.i} - minH={item.minH} - minW={item.minW} - maxH={item.maxH} - maxW={item.maxW} + minH={item.minH || 1} + minW={item.minW || 1} + maxH={item.maxH || Infinity} + maxW={item.maxW || Infinity} placeholder={item.placeholder} layoutHide={item.hide} static={item.static} @@ -496,6 +496,7 @@ class GridLayout extends React.Component<GridLayoutProps, GridLayoutState> { bottom: (showName?.bottom ?? 0) + (this.ref.current?.scrollHeight ?? 0), }} zIndex={zIndex} + className="" > {child} </GridItem> diff --git a/client/packages/lowcoder/src/pages/common/help.tsx b/client/packages/lowcoder/src/pages/common/help.tsx index 2baaba225..64a99eca8 100644 --- a/client/packages/lowcoder/src/pages/common/help.tsx +++ b/client/packages/lowcoder/src/pages/common/help.tsx @@ -181,7 +181,7 @@ const introVideoUrl = trans("docUrls.introVideo"); const issueUrl = trans("lowcoderUrl.createIssue"); const discordUrl = trans("lowcoderUrl.discord"); -export function HelpDropdown(props: HelpDropdownProps) { +function HelpDropdownComp(props: HelpDropdownProps) { const [showHelp, setShowHelp] = useState(true); const [version, setVersion] = useState(""); const dispatch = useDispatch(); @@ -446,3 +446,5 @@ export function HelpDropdown(props: HelpDropdownProps) { </HelpWrapper> </>); } + +export const HelpDropdown = React.memo(HelpDropdownComp); diff --git a/client/packages/lowcoder/src/pages/common/previewHeader.tsx b/client/packages/lowcoder/src/pages/common/previewHeader.tsx index faaa28f53..10afb9817 100644 --- a/client/packages/lowcoder/src/pages/common/previewHeader.tsx +++ b/client/packages/lowcoder/src/pages/common/previewHeader.tsx @@ -20,6 +20,7 @@ import { getBrandingConfig } from "../../redux/selectors/configSelectors"; import { HeaderStartDropdown } from "./headerStartDropdown"; import { useParams } from "react-router"; import { AppPathParams } from "constants/applicationConstants"; +import React from "react"; const HeaderFont = styled.div<{ $bgColor: string }>` font-weight: 500; @@ -127,7 +128,7 @@ export function HeaderProfile(props: { user: User }) { ); } -export const PreviewHeader = () => { +const PreviewHeaderComp = () => { const params = useParams<AppPathParams>(); const user = useSelector(getUser); const application = useSelector(currentApplication); @@ -203,3 +204,5 @@ export const PreviewHeader = () => { /> ); }; + +export const PreviewHeader = React.memo(PreviewHeaderComp); diff --git a/client/packages/lowcoder/src/pages/editor/editorHotKeys.tsx b/client/packages/lowcoder/src/pages/editor/editorHotKeys.tsx index 350baa2f7..ed99d4c73 100644 --- a/client/packages/lowcoder/src/pages/editor/editorHotKeys.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorHotKeys.tsx @@ -113,7 +113,7 @@ function handleMouseDown(e: MouseEvent, editorState: EditorState, showLeftPanel: } } -export function EditorGlobalHotKeys(props: GlobalProps) { +export const EditorGlobalHotKeys = React.memo((props: GlobalProps) => { const editorState = useContext(EditorContext); const { history: editorHistory } = useContext(ExternalEditorContext); const { togglePanel, panelStatus, toggleShortcutList } = props; @@ -155,7 +155,7 @@ export function EditorGlobalHotKeys(props: GlobalProps) { children={props.children} /> ); -} +}) // local hotkeys function handleEditorKeyDown(e: React.KeyboardEvent, editorState: EditorState) { @@ -186,7 +186,7 @@ function handleEditorKeyDown(e: React.KeyboardEvent, editorState: EditorState) { } } -export function EditorHotKeys(props: Props) { +export const EditorHotKeys = React.memo((props: Props) => { const editorState = useContext(EditorContext); const onKeyDown = useCallback( (e: React.KeyboardEvent) => handleEditorKeyDown(e, editorState), @@ -200,9 +200,9 @@ export function EditorHotKeys(props: Props) { children={props.children} /> ); -} +}) -export function CustomShortcutWrapper(props: { children: React.ReactNode }) { +export const CustomShortcutWrapper = React.memo((props: { children: React.ReactNode }) => { const editorState = useContext(EditorContext); const handleCustomShortcut = useCallback( (e: KeyboardEvent) => { @@ -215,4 +215,4 @@ export function CustomShortcutWrapper(props: { children: React.ReactNode }) { {props.children} </GlobalShortcutsWrapper> ); -} +}) diff --git a/client/packages/lowcoder/src/pages/editor/editorView.tsx b/client/packages/lowcoder/src/pages/editor/editorView.tsx index fde188950..8be40d20e 100644 --- a/client/packages/lowcoder/src/pages/editor/editorView.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorView.tsx @@ -577,4 +577,4 @@ function EditorView(props: EditorViewProps) { ); } -export default EditorView; +export default React.memo(EditorView); diff --git a/client/packages/lowcoder/src/pages/editor/right/RightPanel.tsx b/client/packages/lowcoder/src/pages/editor/right/RightPanel.tsx index 0ad73e171..e89d959e8 100644 --- a/client/packages/lowcoder/src/pages/editor/right/RightPanel.tsx +++ b/client/packages/lowcoder/src/pages/editor/right/RightPanel.tsx @@ -9,6 +9,7 @@ import { AttributeIcon } from "lowcoder-design"; import { InsertIcon } from "lowcoder-design"; import { trans } from "i18n"; import { isAggregationApp } from "util/appUtils"; +import React from "react"; type RightPanelProps = { onTabChange: (key: string) => void; @@ -17,7 +18,7 @@ type RightPanelProps = { uiComp?: InstanceType<typeof UIComp>; }; -export default function RightPanel(props: RightPanelProps) { +function RightPanel(props: RightPanelProps) { const { onTabChange, showPropertyPane, uiComp } = props; const uiCompType = uiComp && (uiComp.children.compType.getView() as UiLayoutType); const aggregationApp = uiCompType && isAggregationApp(uiCompType); @@ -55,3 +56,5 @@ export default function RightPanel(props: RightPanelProps) { </RightPanelWrapper> ); } + +export default React.memo(RightPanel); diff --git a/client/packages/lowcoder/src/pages/tutorials/editorTutorials.tsx b/client/packages/lowcoder/src/pages/tutorials/editorTutorials.tsx index b1ffe9a72..4e414590e 100644 --- a/client/packages/lowcoder/src/pages/tutorials/editorTutorials.tsx +++ b/client/packages/lowcoder/src/pages/tutorials/editorTutorials.tsx @@ -33,6 +33,7 @@ import { i18nObjs } from "../../i18n/index"; import { DatasourceInfo, HttpConfig } from "api/datasourceApi"; import { enObj } from "i18n/locales"; import { QUICK_REST_API_ID } from "constants/datasourceConstants"; +import React from "react"; const tourSteps: Step[] = [ { @@ -203,7 +204,7 @@ function addQuery(editorState: EditorState, datasourceInfos: DatasourceInfo[]) { editorState.setSelectedBottomRes(queryName, BottomResTypeEnum.Query); } -export default function EditorTutorials() { +function EditorTutorials() { const [run, setRun] = useState(false); const [stepIndex, setStepIndex] = useState(0); const editorState = useContext(EditorContext); @@ -309,3 +310,5 @@ export default function EditorTutorials() { /> ); } + +export default React.memo(EditorTutorials); From 5b45716ef97da68cb31835bee75c42009d114276 Mon Sep 17 00:00:00 2001 From: RAHEEL <mraheeliftikhar1994@gmail.com> Date: Mon, 2 Sep 2024 21:22:02 +0500 Subject: [PATCH 3/5] added memoization --- .../comps/containerComp/containerView.tsx | 30 +- .../comps/comps/gridLayoutComp/canvasView.tsx | 9 +- .../comps/gridLayoutComp/dragSelector.tsx | 4 +- .../lowcoder/src/comps/comps/rootComp.tsx | 8 +- .../packages/lowcoder/src/layout/gridItem.tsx | 374 ++++++++++++------ .../lowcoder/src/layout/gridLayout.tsx | 3 +- .../src/pages/editor/appEditorInternal.tsx | 8 +- .../lowcoder/src/pages/editor/editorView.tsx | 5 +- 8 files changed, 293 insertions(+), 148 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx b/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx index a1b05a021..c83b94fde 100644 --- a/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx +++ b/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx @@ -24,7 +24,7 @@ import { DEFAULT_GRID_COLUMNS, DEFAULT_ROW_HEIGHT, } from "layout/calculateUtils"; -import _ from "lodash"; +import _, { isEqual } from "lodash"; import { ActionExtraInfo, changeChildAction, @@ -313,7 +313,7 @@ const ItemWrapper = styled.div<{ $disableInteract?: boolean }>` pointer-events: ${(props) => (props.$disableInteract ? "none" : "unset")}; `; -const GridItemWrapper = React.forwardRef( +const GridItemWrapper = React.memo(React.forwardRef( ( props: React.PropsWithChildren<HTMLAttributes<HTMLDivElement>>, ref: React.ForwardedRef<HTMLDivElement> @@ -326,11 +326,11 @@ const GridItemWrapper = React.forwardRef( </ItemWrapper> ); } -); +)); type GirdItemViewRecord = Record<string, GridItem>; -export function InnerGrid(props: ViewPropsWithSelect) { +export const InnerGrid = React.memo((props: ViewPropsWithSelect) => { const { positionParams, rowCount = Infinity, @@ -385,7 +385,7 @@ export function InnerGrid(props: ViewPropsWithSelect) { const canAddSelect = useMemo( () => _.size(containerSelectNames) === _.size(editorState.selectedCompNames), - [containerSelectNames, editorState] + [containerSelectNames, editorState.selectedCompNames] ); const dispatchPositionParamsTimerRef = useRef(0); @@ -432,16 +432,21 @@ export function InnerGrid(props: ViewPropsWithSelect) { onPositionParamsChange, onRowCountChange, positionParams, - props, + props.dispatch, + props.containerPadding, ] ); const setSelectedNames = useCallback( (names: Set<string>) => { editorState.setSelectedCompNames(names); }, - [editorState] + [editorState.setSelectedCompNames] ); - const { width, ref } = useResizeDetector({ onResize, handleHeight: isRowCountLocked }); + + const { width, ref } = useResizeDetector({ + onResize, + handleHeight: isRowCountLocked, + }); const itemViewRef = useRef<GirdItemViewRecord>({}); const itemViews = useMemo(() => { @@ -464,9 +469,10 @@ export function InnerGrid(props: ViewPropsWithSelect) { const clickItem = useCallback( ( e: React.MouseEvent<HTMLDivElement, - globalThis.MouseEvent>, name: string + globalThis.MouseEvent>, + name: string, ) => selectItem(e, name, canAddSelect, containerSelectNames, setSelectedNames), - [canAddSelect, containerSelectNames, setSelectedNames] + [selectItem, canAddSelect, containerSelectNames, setSelectedNames] ); useEffect(() => { @@ -555,7 +561,9 @@ export function InnerGrid(props: ViewPropsWithSelect) { {itemViews} </ReactGridLayout> ); -} +}, (prevProps, newProps) => { + return isEqual(prevProps, newProps); +}); function selectItem( e: MouseEvent<HTMLDivElement>, diff --git a/client/packages/lowcoder/src/comps/comps/gridLayoutComp/canvasView.tsx b/client/packages/lowcoder/src/comps/comps/gridLayoutComp/canvasView.tsx index 35f56dca4..cc655730d 100644 --- a/client/packages/lowcoder/src/comps/comps/gridLayoutComp/canvasView.tsx +++ b/client/packages/lowcoder/src/comps/comps/gridLayoutComp/canvasView.tsx @@ -1,6 +1,6 @@ import { EditorContext } from "comps/editorState"; import { EditorContainer } from "pages/common/styledComponent"; -import { Profiler, useContext, useRef, useState } from "react"; +import React, { Profiler, useContext, useRef, useState } from "react"; import styled from "styled-components"; import { profilerCallback } from "util/cacheUtils"; import { @@ -20,6 +20,7 @@ import { CanvasContainerID } from "constants/domLocators"; import { CNRootContainer } from "constants/styleSelectors"; import { ScrollBar } from "lowcoder-design"; import { defaultTheme } from "@lowcoder-ee/constants/themeConstants"; +import { isEqual } from "lodash"; // min-height: 100vh; @@ -72,7 +73,7 @@ function getDragSelectedNames( const EmptySet = new Set<string>(); -export function CanvasView(props: ContainerBaseProps) { +export const CanvasView = React.memo((props: ContainerBaseProps) => { const editorState = useContext(EditorContext); const [dragSelectedComps, setDragSelectedComp] = useState(EmptySet); const scrollContainerRef = useRef(null); @@ -166,4 +167,6 @@ export function CanvasView(props: ContainerBaseProps) { </EditorContainer> </CanvasContainer> ); -} +}, (prevProps, newProps) => { + return isEqual(prevProps, newProps); +}); diff --git a/client/packages/lowcoder/src/comps/comps/gridLayoutComp/dragSelector.tsx b/client/packages/lowcoder/src/comps/comps/gridLayoutComp/dragSelector.tsx index 04aa4cc3b..79e017302 100644 --- a/client/packages/lowcoder/src/comps/comps/gridLayoutComp/dragSelector.tsx +++ b/client/packages/lowcoder/src/comps/comps/gridLayoutComp/dragSelector.tsx @@ -38,7 +38,7 @@ const InitialState = { startPoint: undefined, }; -export class DragSelector extends React.Component<SectionProps, SectionState> { +class DragSelectorComp extends React.Component<SectionProps, SectionState> { private readonly selectAreaRef: React.RefObject<HTMLDivElement>; constructor(props: SectionProps) { @@ -178,3 +178,5 @@ export class DragSelector extends React.Component<SectionProps, SectionState> { }; } } + +export const DragSelector = React.memo(DragSelectorComp); diff --git a/client/packages/lowcoder/src/comps/comps/rootComp.tsx b/client/packages/lowcoder/src/comps/comps/rootComp.tsx index a3080ebb6..5fede0b07 100644 --- a/client/packages/lowcoder/src/comps/comps/rootComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/rootComp.tsx @@ -32,6 +32,8 @@ import { import RefTreeComp from "./refTreeComp"; import { ExternalEditorContext } from "util/context/ExternalEditorContext"; import { useUserViewMode } from "util/hooks"; +import React from "react"; +import { isEqual } from "lodash"; const EditorView = lazy( () => import("pages/editor/editorView"), @@ -55,7 +57,7 @@ const childrenMap = { preload: PreloadComp, }; -function RootView(props: RootViewProps) { +const RootView = React.memo((props: RootViewProps) => { const previewTheme = useContext(ThemeContext); const { comp, isModuleRoot, ...divProps } = props; const [editorState, setEditorState] = useState<EditorState>(); @@ -143,7 +145,9 @@ function RootView(props: RootViewProps) { </PropertySectionContext.Provider> </div> ); -} +}, (prevProps, nextProps) => { + return isEqual(prevProps, nextProps); +}); /** * Root Comp diff --git a/client/packages/lowcoder/src/layout/gridItem.tsx b/client/packages/lowcoder/src/layout/gridItem.tsx index 31893f15b..e64836a0a 100644 --- a/client/packages/lowcoder/src/layout/gridItem.tsx +++ b/client/packages/lowcoder/src/layout/gridItem.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import _ from "lodash"; +import _, { isEqual } from "lodash"; import { UICompType } from "comps/uiCompRegistry"; import React, { DragEvent, @@ -7,6 +7,7 @@ import React, { SyntheticEvent, useCallback, useContext, + useEffect, useMemo, useRef, useState, @@ -101,8 +102,30 @@ const ResizableStyled = styled(Resizable)<{ $zIndex: number, isDroppable : boole /** * An individual item within a ReactGridLayout. */ -export function GridItem(props: GridItemProps) { - const position = calcGridItemPosition(props, props.x, props.y, props.w, props.h); +export const GridItem = React.memo((props: GridItemProps) => { + const position = useMemo(() => + calcGridItemPosition({ + margin: props.margin, + containerPadding: props.containerPadding, + containerWidth: props.containerWidth, + cols: props.cols, + rowHeight: props.rowHeight, + maxRows: props.maxRows, + }, props.x, props.y, props.w, props.h), + [ + props.margin, + props.containerPadding, + props.containerWidth, + props.cols, + props.rowHeight, + props.maxRows, + props.x, + props.y, + props.w, + props.h, + calcGridItemPosition, + ] + ); const [resizing, setResizing] = useState<{ width: number; height: number } | undefined>(); const [dragging, setDragging] = useState<{ top: number; left: number } | undefined>(); const elementRef = useRef<HTMLDivElement>(null); @@ -111,28 +134,152 @@ export function GridItem(props: GridItemProps) { // record the real height of the comp content const itemHeightRef = useRef<number | undefined>(undefined); - const onDragStart = (e: DragEvent<HTMLDivElement>) => { + const onDragStart = useCallback((e: DragEvent<HTMLDivElement>) => { e.stopPropagation(); const { i } = props as Required<GridItemProps>; draggingUtils.clearData(); draggingUtils.setData("i", i); e.dataTransfer.setDragImage(TransparentImg, 0, 0); props.onDragStart?.(i, e, elementRef.current as HTMLDivElement); - }; + }, [props.i, props.onDragStart]); - const onDrag = (e: DragEvent<HTMLDivElement>) => { + const onDrag = useCallback((e: DragEvent<HTMLDivElement>) => { e.stopPropagation(); const { i } = props as Required<GridItemProps>; props.onDrag?.(i, e, elementRef.current as HTMLDivElement); - }; + }, [props.i, props.onDrag]); - const onDragEnd = (e: DragEvent<HTMLDivElement>) => { + const onDragEnd = useCallback((e: DragEvent<HTMLDivElement>) => { const { i } = props as Required<GridItemProps>; props.onDragEnd?.(i, e, elementRef.current as HTMLDivElement); draggingUtils.clearData(); - }; + },[props.i, props.onDragEnd]); - const mixinDraggable = (child: ReactElement, isDraggable: boolean): ReactElement => { + /** + * Wrapper around drag events to provide more useful data. + * All drag events call the function with the given handler name, + * with the signature (index, x, y). + * + * @param {String} handlerName Handler name to wrap. + * @return {Function} Handler function. + */ + const onResizeHandler = useCallback(( + e: SyntheticEvent<Element>, + { node, size, handle }: ResizeCallbackData, + handlerName: "onResizeStart" | "onResize" | "onResizeStop" + ): void => { + e.preventDefault(); + e.stopPropagation(); + const handler = props[handlerName]; + if (!handler) return; + const oldSize = resizing; + let localDragging = dragging; + let { cols, maxRows, x, y, w, h, i, maxH, minH, minW, maxW } = props; + let [xx, yy] = [x, y]; + + const resizeNorth = handle.indexOf("n") >= 0; + const resizeWest = handle.indexOf("w") >= 0; + if (resizeNorth || resizeWest) { + if (handlerName === "onResizeStart") { + localDragging = { + left: position.left, + top: position.top, + }; + } else { + if (!localDragging) { + throw new Error("onResize called before onResizeStart."); + } + if (handlerName === "onResize") { + let deltaX = 0; + let deltaY = 0; + if (oldSize) { + // Left or top handles need repositioning when dragging. Only bottom right can be left alone + const delta = calcResizeXY( + handle, + size.width - oldSize.width, + size.height - oldSize.height + ); + [deltaX, deltaY] = [delta.deltaX, delta.deltaY]; + } + localDragging = getDraggingNewPosition(localDragging, deltaX, deltaY); + } + const xy = calcXY(props, localDragging.top, localDragging.left, props.w, props.h); + [xx, yy] = [xy.x, xy.y]; + } + } + setResizing(handlerName === "onResizeStop" ? undefined : size); + setDragging(handlerName === "onResizeStop" ? undefined : localDragging); + + // Get new XY + let { w: ww, h: hh } = calcWH(props, size.width, size.height); + // Min/max capping + ww = clamp(ww, minW, maxW); + hh = clamp(hh, minH, maxH); + let boundW = resizeWest ? x + w : cols - x; + let boundH = resizeNorth ? y + h : maxRows - y; + ww = clamp(ww, 1, boundW); + hh = clamp(hh, 1, boundH); + handler(i, ww, hh, { + e, + node, + size, + handle, + x: xx, + y: yy, + }); + }, [ + resizing, + dragging, + props.cols, + props.maxRows, + props.x, + props.y, + props.w, + props.h, + props.i, + props.maxH, + props.minH, + props.minW, + props.maxW, + position.left, + position.top, + calcResizeXY, + getDraggingNewPosition, + calcXY, + calcWH, + setResizing, + setDragging, + clamp, + ]); + + /** + * onResizeStop event handler + * @param {Event} e event data + * @param {Object} callbackData an object with node and size information + */ + const onResizeStop = useCallback((e: React.SyntheticEvent, callbackData: ResizeCallbackData) => { + onResizeHandler(e, callbackData, "onResizeStop"); + }, [onResizeHandler]); + + /** + * onResizeStart event handler + * @param {Event} e event data + * @param {Object} callbackData an object with node and size information + */ + const onResizeStart = useCallback((e: React.SyntheticEvent, callbackData: ResizeCallbackData) => { + onResizeHandler(e, callbackData, "onResizeStart"); + }, [onResizeHandler]); + + /** + * onResize event handler + * @param {Event} e event data + * @param {Object} callbackData an object with node and size information + */ + const onResize = useCallback((e: React.SyntheticEvent, callbackData: ResizeCallbackData) => { + onResizeHandler(e, callbackData, "onResize"); + }, [onResizeHandler]); + + const mixinDraggable = useCallback((child: ReactElement, isDraggable: boolean): ReactElement => { const { i } = props as Required<GridItemProps>; const testSelectorClass = `lowcoder-${props.compType}`; return ( @@ -164,7 +311,15 @@ export function GridItem(props: GridItemProps) { </IsDroppable.Provider> </div> ); - }; + }, [ + props.i, + props.name, + props.compType, + onDragStart, + onDragEnd, + onDrag, + editorState.findUIParentContainer, + ]); /** * Mix a Resizable instance into a child. @@ -172,7 +327,7 @@ export function GridItem(props: GridItemProps) { * @param {Object} position Position object (pixel values) * @return {Element} Child wrapped in Resizable. */ - const mixinResizable = ( + const mixinResizable = useCallback(( child: ReactElement, position: Position, isResizable: boolean, @@ -210,110 +365,22 @@ export function GridItem(props: GridItemProps) { {child} </ResizableStyled> ); - }; - - /** - * onResizeStop event handler - * @param {Event} e event data - * @param {Object} callbackData an object with node and size information - */ - const onResizeStop = (e: React.SyntheticEvent, callbackData: ResizeCallbackData) => { - onResizeHandler(e, callbackData, "onResizeStop"); - }; - - /** - * onResizeStart event handler - * @param {Event} e event data - * @param {Object} callbackData an object with node and size information - */ - const onResizeStart = (e: React.SyntheticEvent, callbackData: ResizeCallbackData) => { - onResizeHandler(e, callbackData, "onResizeStart"); - }; - - /** - * onResize event handler - * @param {Event} e event data - * @param {Object} callbackData an object with node and size information - */ - const onResize = (e: React.SyntheticEvent, callbackData: ResizeCallbackData) => { - onResizeHandler(e, callbackData, "onResize"); - }; - - /** - * Wrapper around drag events to provide more useful data. - * All drag events call the function with the given handler name, - * with the signature (index, x, y). - * - * @param {String} handlerName Handler name to wrap. - * @return {Function} Handler function. - */ - const onResizeHandler = ( - e: SyntheticEvent<Element>, - { node, size, handle }: ResizeCallbackData, - handlerName: "onResizeStart" | "onResize" | "onResizeStop" - ): void => { - e.preventDefault(); - e.stopPropagation(); - const handler = props[handlerName]; - if (!handler) return; - const oldSize = resizing; - let localDragging = dragging; - let { cols, maxRows, x, y, w, h, i, maxH, minH, minW, maxW } = props; - let [xx, yy] = [x, y]; - - const resizeNorth = handle.indexOf("n") >= 0; - const resizeWest = handle.indexOf("w") >= 0; - if (resizeNorth || resizeWest) { - if (handlerName === "onResizeStart") { - localDragging = { - left: position.left, - top: position.top, - }; - } else { - if (!localDragging) { - throw new Error("onResize called before onResizeStart."); - } - if (handlerName === "onResize") { - let deltaX = 0; - let deltaY = 0; - if (oldSize) { - // Left or top handles need repositioning when dragging. Only bottom right can be left alone - const delta = calcResizeXY( - handle, - size.width - oldSize.width, - size.height - oldSize.height - ); - [deltaX, deltaY] = [delta.deltaX, delta.deltaY]; - } - localDragging = getDraggingNewPosition(localDragging, deltaX, deltaY); - } - const xy = calcXY(props, localDragging.top, localDragging.left, props.w, props.h); - [xx, yy] = [xy.x, xy.y]; - } - } - setResizing(handlerName === "onResizeStop" ? undefined : size); - setDragging(handlerName === "onResizeStop" ? undefined : localDragging); - - // Get new XY - let { w: ww, h: hh } = calcWH(props, size.width, size.height); - // Min/max capping - ww = clamp(ww, minW, maxW); - hh = clamp(hh, minH, maxH); - let boundW = resizeWest ? x + w : cols - x; - let boundH = resizeNorth ? y + h : maxRows - y; - ww = clamp(ww, 1, boundW); - hh = clamp(hh, 1, boundH); - handler(i, ww, hh, { - e, - node, - size, - handle, - x: xx, - y: yy, - }); - }; + }, [ + props.i, + props.cols, + props.x, + props.minW, + props.minH, + props.maxW, + props.maxH, + props.resizeHandles, + calcGridItemPosition, + onResizeStart, + onResizeStop, + onResize, + ]); - const adjustWrapperHeight = (width?: number, height?: number, src?: string) => { + const adjustWrapperHeight = useCallback((width?: number, height?: number, src?: string) => { if (_.isNil(height)) return; if (!width) { width = position.width; @@ -327,19 +394,27 @@ export function GridItem(props: GridItemProps) { if (props.h !== h) { props.onHeightChange?.(props.i, h); } - }; + }, [ + props.i, + props.h, + props.compType, + position.width, + props.onHeightChange, + getGridItemPadding, + calcWH, + ]); /** * re-calculate the occupied grid-cells * called when item size changes and `autoHeight === true` */ - const onInnerSizeChange = (width?: number, height?: number) => { + const onInnerSizeChange = useCallback((width?: number, height?: number) => { // log.log("onInnerSizeChange. name: ", props.name, " width: ", width, " height: ", height); if (!_.isNil(height)) { itemHeightRef.current = height; } adjustWrapperHeight(width, height); - }; + }, [itemHeightRef, adjustWrapperHeight]); /** * re-calculate the occupied gird-cells. @@ -347,11 +422,11 @@ export function GridItem(props: GridItemProps) { * * called when item wrapper's size changes and autoHeight === true */ - const onWrapperSizeChange = () => { + const onWrapperSizeChange = useCallback(() => { adjustWrapperHeight(undefined, itemHeightRef.current); - }; + }, [itemHeightRef, adjustWrapperHeight]); - const mixinChildWrapper = (child: React.ReactElement): React.ReactElement => { + const mixinChildWrapper = useCallback((child: React.ReactElement): React.ReactElement => { const { i, name, @@ -398,7 +473,27 @@ export function GridItem(props: GridItemProps) { {child} </CompSelectionWrapper> ); - }; + }, [ + props.i, + props.h, + props.name, + props.autoHeight, + props.isSelected, + props.hidden, + props.selectedSize, + props.clickItem, + props.placeholder, + props.showName.bottom, + props.showName.top, + props.isSelectable, + props.isResizable, + props.compType, + props.resizeHandles, + position.top, + position.height, + onInnerSizeChange, + onWrapperSizeChange, + ]); const calcPosition = useCallback((): Position => { let width, height, top, left; @@ -425,8 +520,10 @@ export function GridItem(props: GridItemProps) { }, [dragging, position.height, position.left, position.top, position.width, resizing]); const { isDraggable, isResizable, layoutHide, children, isSelected, clickItem, zIndex } = props; - const pos = calcPosition(); - const render = () => { + + const pos = useMemo(calcPosition, [calcPosition]); + + const render = useMemo(() => { let child = React.Children.only(children); // Create the child element. We clone the existing element but modify its className and style. let newChild: React.ReactElement = React.cloneElement(child, { @@ -470,12 +567,35 @@ export function GridItem(props: GridItemProps) { // Draggable support. This is always on, except for with placeholders. newChild = mixinDraggable(newChild, isDraggable); return newChild; - }; + }, [ + pos, + children, + elementRef, + resizing, + dragging, + isDraggable, + layoutHide, + zIndex, + props.name, + props.compType, + props.className, + props.style, + props.static, + props.autoHeight, + props.hidden, + setTransform, + mixinChildWrapper, + mixinResizable, + mixinDraggable, + ]); - const renderResult = useMemo(render, [pos, children, layoutHide, isSelected, clickItem]); + // const renderResult = useMemo(render, [pos, children, layoutHide, isSelected, clickItem]); + const renderResult = useMemo(() => render, [render]); return renderResult; -} +}, (prevProps, newProps) => { + return isEqual(prevProps, newProps); +}) // GridItem.defaultProps = { // className: "", diff --git a/client/packages/lowcoder/src/layout/gridLayout.tsx b/client/packages/lowcoder/src/layout/gridLayout.tsx index 77aaf965b..52f4a3a1b 100644 --- a/client/packages/lowcoder/src/layout/gridLayout.tsx +++ b/client/packages/lowcoder/src/layout/gridLayout.tsx @@ -1097,7 +1097,8 @@ const LayoutContainer = styled.div<{ }`} `; -export const ReactGridLayout = GridLayout; +// export const ReactGridLayout = React.memo(GridLayout); +export const ReactGridLayout = React.memo(GridLayout); function moveOrResize( e: React.KeyboardEvent, diff --git a/client/packages/lowcoder/src/pages/editor/appEditorInternal.tsx b/client/packages/lowcoder/src/pages/editor/appEditorInternal.tsx index 02b822e55..1ece41909 100644 --- a/client/packages/lowcoder/src/pages/editor/appEditorInternal.tsx +++ b/client/packages/lowcoder/src/pages/editor/appEditorInternal.tsx @@ -24,6 +24,8 @@ import { useUserViewMode } from "../../util/hooks"; import { QueryApi } from "api/queryApi"; import { RootCompInstanceType } from "./useRootCompInstance"; import { getCurrentUser } from "redux/selectors/usersSelectors"; +import React from "react"; +import { isEqual } from "lodash"; /** * FIXME: optimize the logic of saving comps @@ -77,7 +79,7 @@ interface AppEditorInternalViewProps { compInstance: RootCompInstanceType; } -export function AppEditorInternalView(props: AppEditorInternalViewProps) { +export const AppEditorInternalView = React.memo((props: AppEditorInternalViewProps) => { const isUserViewMode = useUserViewMode(); const extraExternalEditorState = useSelector(getExternalEditorState); const dispatch = useDispatch(); @@ -125,4 +127,6 @@ export function AppEditorInternalView(props: AppEditorInternalViewProps) { </ExternalEditorContext.Provider> </ConfigProvider> ); -} +}, (prevProps, nextProps) => { + return isEqual(prevProps, nextProps) +}); diff --git a/client/packages/lowcoder/src/pages/editor/editorView.tsx b/client/packages/lowcoder/src/pages/editor/editorView.tsx index 8be40d20e..42bdf1e91 100644 --- a/client/packages/lowcoder/src/pages/editor/editorView.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorView.tsx @@ -54,6 +54,7 @@ import { import { isAggregationApp } from "util/appUtils"; import EditorSkeletonView from "./editorSkeletonView"; import { getCommonSettings } from "@lowcoder-ee/redux/selectors/commonSettingSelectors"; +import { isEqual } from "lodash"; const LeftContent = lazy( () => import('./LeftContent') @@ -577,4 +578,6 @@ function EditorView(props: EditorViewProps) { ); } -export default React.memo(EditorView); +export default React.memo(EditorView, (prevProps, newProps) => { + return isEqual(prevProps, newProps); +}); From 031ac84a246985b7cdcc2d96306be5c7bce2bcad Mon Sep 17 00:00:00 2001 From: RAHEEL <mraheeliftikhar1994@gmail.com> Date: Mon, 2 Sep 2024 21:44:53 +0500 Subject: [PATCH 4/5] minimize /permission requests --- .../PermissionDialog/AppPermissionDialog.tsx | 4 +- .../lowcoder/src/pages/common/header.tsx | 125 +++++++++--------- .../src/pages/editor/editorSkeletonView.tsx | 4 +- 3 files changed, 70 insertions(+), 63 deletions(-) diff --git a/client/packages/lowcoder/src/components/PermissionDialog/AppPermissionDialog.tsx b/client/packages/lowcoder/src/components/PermissionDialog/AppPermissionDialog.tsx index 9c0b490a6..3a22c96a4 100644 --- a/client/packages/lowcoder/src/components/PermissionDialog/AppPermissionDialog.tsx +++ b/client/packages/lowcoder/src/components/PermissionDialog/AppPermissionDialog.tsx @@ -31,7 +31,7 @@ import { SHARE_TITLE } from "../../constants/apiConstants"; import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"; import { default as Divider } from "antd/es/divider"; -export const AppPermissionDialog = (props: { +export const AppPermissionDialog = React.memo((props: { applicationId: string; visible: boolean; onVisibleChange: (visible: boolean) => void; @@ -148,7 +148,7 @@ export const AppPermissionDialog = (props: { } /> ); -}; +}); const InviteInputBtn = styled.div` display: flex; diff --git a/client/packages/lowcoder/src/pages/common/header.tsx b/client/packages/lowcoder/src/pages/common/header.tsx index ebb948330..4a4ae436f 100644 --- a/client/packages/lowcoder/src/pages/common/header.tsx +++ b/client/packages/lowcoder/src/pages/common/header.tsx @@ -25,7 +25,7 @@ import { } from "lowcoder-design"; import { trans } from "i18n"; import dayjs from "dayjs"; -import { useContext, useState } from "react"; +import { useContext, useEffect, useMemo, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { publishApplication, @@ -453,67 +453,74 @@ export default function Header(props: HeaderProps) { </> ); - const headerEnd = showAppSnapshot ? ( - <HeaderProfile user={user} /> - ) : ( - <> - {applicationId && ( - <AppPermissionDialog - applicationId={applicationId} - visible={permissionDialogVisible} - onVisibleChange={(visible) => - !visible && setPermissionDialogVisible(false) - } - /> - )} - {canManageApp(user, application) && ( - <GrayBtn onClick={() => setPermissionDialogVisible(true)}> - {SHARE_TITLE} - </GrayBtn> - )} - <PreviewBtn buttonType="primary" onClick={() => preview(applicationId)}> - {trans("header.preview")} - </PreviewBtn> - - <Dropdown - className="cypress-header-dropdown" - placement="bottomRight" - trigger={["click"]} - dropdownRender={() => ( - <DropdownMenuStyled - style={{ minWidth: "110px", borderRadius: "4px" }} - onClick={(e) => { - if (e.key === "deploy") { - dispatch(publishApplication({ applicationId })); - } else if (e.key === "snapshot") { - dispatch(setShowAppSnapshot(true)); - } - }} - items={[ - { - key: "deploy", - label: ( - <CommonTextLabel>{trans("header.deploy")}</CommonTextLabel> - ), - }, - { - key: "snapshot", - label: ( - <CommonTextLabel>{trans("header.snapshot")}</CommonTextLabel> - ), - }, - ]} + const headerEnd = useMemo(() => { + return showAppSnapshot ? ( + <HeaderProfile user={user} /> + ) : ( + <> + {applicationId && ( + <AppPermissionDialog + applicationId={applicationId} + visible={permissionDialogVisible} + onVisibleChange={(visible) => + !visible && setPermissionDialogVisible(false) + } /> )} - > - <PackUpBtn buttonType="primary"> - <PackUpIcon /> - </PackUpBtn> - </Dropdown> + {canManageApp(user, application) && ( + <GrayBtn onClick={() => setPermissionDialogVisible(true)}> + {SHARE_TITLE} + </GrayBtn> + )} + <PreviewBtn buttonType="primary" onClick={() => preview(applicationId)}> + {trans("header.preview")} + </PreviewBtn> + + <Dropdown + className="cypress-header-dropdown" + placement="bottomRight" + trigger={["click"]} + dropdownRender={() => ( + <DropdownMenuStyled + style={{ minWidth: "110px", borderRadius: "4px" }} + onClick={(e) => { + if (e.key === "deploy") { + dispatch(publishApplication({ applicationId })); + } else if (e.key === "snapshot") { + dispatch(setShowAppSnapshot(true)); + } + }} + items={[ + { + key: "deploy", + label: ( + <CommonTextLabel>{trans("header.deploy")}</CommonTextLabel> + ), + }, + { + key: "snapshot", + label: ( + <CommonTextLabel>{trans("header.snapshot")}</CommonTextLabel> + ), + }, + ]} + /> + )} + > + <PackUpBtn buttonType="primary"> + <PackUpIcon /> + </PackUpBtn> + </Dropdown> - <HeaderProfile user={user} /> - </> - ); + <HeaderProfile user={user} /> + </> + ); + }, [ + user, + showAppSnapshot, + applicationId, + permissionDialogVisible, + ]); return ( <LayoutHeader diff --git a/client/packages/lowcoder/src/pages/editor/editorSkeletonView.tsx b/client/packages/lowcoder/src/pages/editor/editorSkeletonView.tsx index a38afd104..96a3d905f 100644 --- a/client/packages/lowcoder/src/pages/editor/editorSkeletonView.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorSkeletonView.tsx @@ -59,12 +59,12 @@ export default function EditorSkeletonView() { return ( <> <Height100Div> - <Header + {/* <Header panelStatus={panelStatus} togglePanel={_.noop} editorModeStatus={editorModeStatus} toggleEditorModeStatus={_.noop} - /> + /> */} <Body> <SiderStyled /> {panelStatus.left && ( From e9ebbcfcbf7f60a34ea07d5ebe08d4b034804282 Mon Sep 17 00:00:00 2001 From: RAHEEL <mraheeliftikhar1994@gmail.com> Date: Wed, 4 Sep 2024 22:39:49 +0500 Subject: [PATCH 5/5] small updates --- .../src/comps/comps/containerComp/containerView.tsx | 6 ++++-- client/packages/lowcoder/src/layout/gridItem.tsx | 12 +++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx b/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx index c83b94fde..ce9ddac21 100644 --- a/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx +++ b/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx @@ -348,11 +348,13 @@ export const InnerGrid = React.memo((props: ViewPropsWithSelect) => { // Falk: TODO: Here we can define the inner grid columns dynamically //Added By Aqib Mirza - const defaultGrid = - horizontalGridCells || + const defaultGrid = useMemo(() => { + return horizontalGridCells || currentTheme?.gridColumns || defaultTheme?.gridColumns || "12"; + }, [horizontalGridCells, currentTheme?.gridColumns, defaultTheme?.gridColumns]); + ///////////////////// const isDroppable = useContext(IsDroppable) && (_.isNil(props.isDroppable) || props.isDroppable) && !readOnly; diff --git a/client/packages/lowcoder/src/layout/gridItem.tsx b/client/packages/lowcoder/src/layout/gridItem.tsx index e64836a0a..432c9b453 100644 --- a/client/packages/lowcoder/src/layout/gridItem.tsx +++ b/client/packages/lowcoder/src/layout/gridItem.tsx @@ -207,6 +207,7 @@ export const GridItem = React.memo((props: GridItemProps) => { [xx, yy] = [xy.x, xy.y]; } } + setResizing(handlerName === "onResizeStop" ? undefined : size); setDragging(handlerName === "onResizeStop" ? undefined : localDragging); @@ -517,7 +518,16 @@ export const GridItem = React.memo((props: GridItemProps) => { left = position.left; } return { width, height, top, left }; - }, [dragging, position.height, position.left, position.top, position.width, resizing]); + }, [ + dragging?.top, + dragging?.left, + position.height, + position.left, + position.top, + position.width, + resizing?.width, + resizing?.height, + ]); const { isDraggable, isResizable, layoutHide, children, isSelected, clickItem, zIndex } = props;