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/comps/comps/containerComp/containerView.tsx b/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx index a1b05a021..ce9ddac21 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>, ref: React.ForwardedRef @@ -326,11 +326,11 @@ const GridItemWrapper = React.forwardRef( ); } -); +)); type GirdItemViewRecord = Record; -export function InnerGrid(props: ViewPropsWithSelect) { +export const InnerGrid = React.memo((props: ViewPropsWithSelect) => { const { positionParams, rowCount = Infinity, @@ -348,11 +348,13 @@ export function InnerGrid(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; @@ -385,7 +387,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 +434,21 @@ export function InnerGrid(props: ViewPropsWithSelect) { onPositionParamsChange, onRowCountChange, positionParams, - props, + props.dispatch, + props.containerPadding, ] ); const setSelectedNames = useCallback( (names: Set) => { editorState.setSelectedCompNames(names); }, - [editorState] + [editorState.setSelectedCompNames] ); - const { width, ref } = useResizeDetector({ onResize, handleHeight: isRowCountLocked }); + + const { width, ref } = useResizeDetector({ + onResize, + handleHeight: isRowCountLocked, + }); const itemViewRef = useRef({}); const itemViews = useMemo(() => { @@ -464,9 +471,10 @@ export function InnerGrid(props: ViewPropsWithSelect) { const clickItem = useCallback( ( e: React.MouseEvent, name: string + globalThis.MouseEvent>, + name: string, ) => selectItem(e, name, canAddSelect, containerSelectNames, setSelectedNames), - [canAddSelect, containerSelectNames, setSelectedNames] + [selectItem, canAddSelect, containerSelectNames, setSelectedNames] ); useEffect(() => { @@ -555,7 +563,9 @@ export function InnerGrid(props: ViewPropsWithSelect) { {itemViews} ); -} +}, (prevProps, newProps) => { + return isEqual(prevProps, newProps); +}); function selectItem( e: MouseEvent, 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(); -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) { ); -} +}, (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 { +class DragSelectorComp extends React.Component { private readonly selectAreaRef: React.RefObject; constructor(props: SectionProps) { @@ -178,3 +178,5 @@ export class DragSelector extends React.Component { }; } } + +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(); @@ -143,7 +145,9 @@ function RootView(props: RootViewProps) { ); -} +}, (prevProps, nextProps) => { + return isEqual(prevProps, nextProps); +}); /** * Root Comp 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>> = version: InstanceType; }; -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>, >(props: { children: JSX.Element | React.ReactNode, childrenMap: NewChildren } -) { +) => { 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>, @@ -275,11 +275,11 @@ export const DisabledContext = React.createContext(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; 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() }} > ); -} +}); 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"; 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: { ); -}; +}); diff --git a/client/packages/lowcoder/src/layout/gridItem.tsx b/client/packages/lowcoder/src/layout/gridItem.tsx index 47a04e87b..432c9b453 100644 --- a/client/packages/lowcoder/src/layout/gridItem.tsx +++ b/client/packages/lowcoder/src/layout/gridItem.tsx @@ -1,11 +1,13 @@ import clsx from "clsx"; -import _ from "lodash"; +import _, { isEqual } from "lodash"; import { UICompType } from "comps/uiCompRegistry"; import React, { DragEvent, ReactElement, SyntheticEvent, useCallback, + useContext, + useEffect, useMemo, useRef, useState, @@ -26,6 +28,7 @@ import { setTransform, } from "./utils"; import styled from "styled-components"; +import { EditorContext } from "@lowcoder-ee/comps/editorState"; type GridItemCallback = ( i: string, @@ -99,139 +102,58 @@ 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(null); + const editorState = useContext(EditorContext); // record the real height of the comp content const itemHeightRef = useRef(undefined); - const onDragStart = (e: DragEvent) => { + const onDragStart = useCallback((e: DragEvent) => { e.stopPropagation(); const { i } = props as Required; 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) => { + const onDrag = useCallback((e: DragEvent) => { e.stopPropagation(); const { i } = props as Required; props.onDrag?.(i, e, elementRef.current as HTMLDivElement); - }; + }, [props.i, props.onDrag]); - const onDragEnd = (e: DragEvent) => { + const onDragEnd = useCallback((e: DragEvent) => { const { i } = props as Required; props.onDragEnd?.(i, e, elementRef.current as HTMLDivElement); draggingUtils.clearData(); - }; - - const mixinDraggable = (child: ReactElement, isDraggable: boolean): ReactElement => { - const { i } = props as Required; - const testSelectorClass = `lowcoder-${props.compType}`; - return ( -
{ - e.stopPropagation(); - - // allow mouseDown event on lowcoder-comp-kanban to make drag/drop work - if((props.compType as string).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; - - const event = new MouseEvent("mousedown"); - document.dispatchEvent(event); - }} - > - - {child} - -
- ); - }; - - /** - * Mix a Resizable instance into a child. - * @param {Element} child Child element. - * @param {Object} position Position object (pixel values) - * @return {Element} Child wrapped in Resizable. - */ - const mixinResizable = ( - child: ReactElement, - position: Position, - isResizable: boolean, - zIndex: number, - ): ReactElement => { - const { cols, x, minW, minH, maxW, maxH, resizeHandles } = props; - // This is the max possible width - doesn't go to infinity because of the width of the window - const maxWidth = calcGridItemPosition(props, 0, 0, cols - x, 0).width; - // Calculate min/max constraints using our min & maxes - const mins = calcGridItemPosition(props, 0, 0, minW as number, minH as number); - const maxes = calcGridItemPosition(props, 0, 0, maxW as number, maxH as number); - const minConstraints: [number, number] = [mins.width, mins.height]; - const maxConstraints: [number, number] = [ - Math.min(maxes.width, Infinity), - Math.min(maxes.height, Infinity), - ]; - return ( - - {child} - - ); - }; - - /** - * 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"); - }; + },[props.i, props.onDragEnd]); /** * Wrapper around drag events to provide more useful data. @@ -241,7 +163,7 @@ export function GridItem(props: GridItemProps) { * @param {String} handlerName Handler name to wrap. * @return {Function} Handler function. */ - const onResizeHandler = ( + const onResizeHandler = useCallback(( e: SyntheticEvent, { node, size, handle }: ResizeCallbackData, handlerName: "onResizeStart" | "onResize" | "onResizeStop" @@ -285,6 +207,7 @@ export function GridItem(props: GridItemProps) { [xx, yy] = [xy.x, xy.y]; } } + setResizing(handlerName === "onResizeStop" ? undefined : size); setDragging(handlerName === "onResizeStop" ? undefined : localDragging); @@ -305,9 +228,160 @@ export function GridItem(props: GridItemProps) { 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 adjustWrapperHeight = (width?: number, height?: number, src?: string) => { + const mixinDraggable = useCallback((child: ReactElement, isDraggable: boolean): ReactElement => { + const { i } = props as Required; + const testSelectorClass = `lowcoder-${props.compType}`; + return ( +
{ + 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') + || 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); + }} + > + + {child} + +
+ ); + }, [ + props.i, + props.name, + props.compType, + onDragStart, + onDragEnd, + onDrag, + editorState.findUIParentContainer, + ]); + + /** + * Mix a Resizable instance into a child. + * @param {Element} child Child element. + * @param {Object} position Position object (pixel values) + * @return {Element} Child wrapped in Resizable. + */ + const mixinResizable = useCallback(( + child: ReactElement, + position: Position, + isResizable: boolean, + zIndex: number, + ): ReactElement => { + const { cols, x, minW, minH, maxW, maxH, resizeHandles } = props; + // This is the max possible width - doesn't go to infinity because of the width of the window + const maxWidth = calcGridItemPosition(props, 0, 0, cols - x, 0).width; + // Calculate min/max constraints using our min & maxes + const mins = calcGridItemPosition(props, 0, 0, minW as number, minH as number); + const maxes = calcGridItemPosition(props, 0, 0, maxW as number, maxH as number); + const minConstraints: [number, number] = [mins.width, mins.height]; + const maxConstraints: [number, number] = [ + Math.min(maxes.width, Infinity), + Math.min(maxes.height, Infinity), + ]; + return ( + + {child} + + ); + }, [ + props.i, + props.cols, + props.x, + props.minW, + props.minH, + props.maxW, + props.maxH, + props.resizeHandles, + calcGridItemPosition, + onResizeStart, + onResizeStop, + onResize, + ]); + + const adjustWrapperHeight = useCallback((width?: number, height?: number, src?: string) => { if (_.isNil(height)) return; if (!width) { width = position.width; @@ -321,19 +395,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. @@ -341,11 +423,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, @@ -392,7 +474,27 @@ export function GridItem(props: GridItemProps) { {child} ); - }; + }, [ + 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; @@ -416,11 +518,22 @@ export function GridItem(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; - 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, { @@ -464,18 +577,41 @@ 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: "", - 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..52f4a3a1b 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 { 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 { bottom: (showName?.bottom ?? 0) + (this.ref.current?.scrollHeight ?? 0), }} zIndex={zIndex} + className="" > {child} @@ -1096,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/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 ? ( - - ) : ( - <> - {applicationId && ( - - !visible && setPermissionDialogVisible(false) - } - /> - )} - {canManageApp(user, application) && ( - setPermissionDialogVisible(true)}> - {SHARE_TITLE} - - )} - preview(applicationId)}> - {trans("header.preview")} - - - ( - { - if (e.key === "deploy") { - dispatch(publishApplication({ applicationId })); - } else if (e.key === "snapshot") { - dispatch(setShowAppSnapshot(true)); - } - }} - items={[ - { - key: "deploy", - label: ( - {trans("header.deploy")} - ), - }, - { - key: "snapshot", - label: ( - {trans("header.snapshot")} - ), - }, - ]} + const headerEnd = useMemo(() => { + return showAppSnapshot ? ( + + ) : ( + <> + {applicationId && ( + + !visible && setPermissionDialogVisible(false) + } /> )} - > - - - - + {canManageApp(user, application) && ( + setPermissionDialogVisible(true)}> + {SHARE_TITLE} + + )} + preview(applicationId)}> + {trans("header.preview")} + + + ( + { + if (e.key === "deploy") { + dispatch(publishApplication({ applicationId })); + } else if (e.key === "snapshot") { + dispatch(setShowAppSnapshot(true)); + } + }} + items={[ + { + key: "deploy", + label: ( + {trans("header.deploy")} + ), + }, + { + key: "snapshot", + label: ( + {trans("header.snapshot")} + ), + }, + ]} + /> + )} + > + + + + - - - ); + + + ); + }, [ + user, + showAppSnapshot, + applicationId, + permissionDialogVisible, + ]); return ( ); } + +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(); 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/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) { ); -} +}, (prevProps, nextProps) => { + return isEqual(prevProps, nextProps) +}); 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} ); -} +}) diff --git a/client/packages/lowcoder/src/pages/editor/editorSkeletonView.tsx b/client/packages/lowcoder/src/pages/editor/editorSkeletonView.tsx index 98ff83f92..a43a655d7 100644 --- a/client/packages/lowcoder/src/pages/editor/editorSkeletonView.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorSkeletonView.tsx @@ -60,12 +60,12 @@ export default function EditorSkeletonView() { return ( <> -
+ /> */} {panelStatus.left && ( diff --git a/client/packages/lowcoder/src/pages/editor/editorView.tsx b/client/packages/lowcoder/src/pages/editor/editorView.tsx index fde188950..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 EditorView; +export default React.memo(EditorView, (prevProps, newProps) => { + return isEqual(prevProps, newProps); +}); 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; }; -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) { ); } + +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);