import { reflowMoveAction, stopReflowAction } from "actions/reflowActions"; import { OccupiedSpace, WidgetSpace } from "constants/CanvasEditorConstants"; import { isEmpty, throttle } from "lodash"; import { useRef } from "react"; import { useDispatch, useSelector } from "react-redux"; import { getWidgetSpacesSelectorForContainer } from "selectors/editorSelectors"; import { reflow } from "reflow"; import { CollidingSpace, GridProps, ReflowDirection, ReflowedSpaceMap, } from "reflow/reflowTypes"; import { getLimitedMovementMap } from "reflow/reflowUtils"; import { getBottomRowAfterReflow } from "utils/reflowHookUtils"; import { checkIsDropTarget } from "components/designSystems/appsmith/PositionedContainer"; type WidgetCollidingSpace = CollidingSpace & { type: string; }; type WidgetCollidingSpaceMap = { [key: string]: WidgetCollidingSpace; }; export interface ReflowInterface { ( newPositions: OccupiedSpace, OGPositions: OccupiedSpace, direction: ReflowDirection, stopMoveAfterLimit?: boolean, shouldSkipContainerReflow?: boolean, forceDirection?: boolean, immediateExitContainer?: string, ): { canHorizontalMove: boolean; canVerticalMove: boolean; movementMap: ReflowedSpaceMap; bottomMostRow: number; }; } export const useReflow = ( widgetId: string, parentId: string, gridProps: GridProps, ): ReflowInterface => { const dispatch = useDispatch(); const throttledDispatch = throttle(dispatch, 50); const isReflowing = useRef(false); const reflowSpacesSelector = getWidgetSpacesSelectorForContainer(parentId); const widgetSpaces: WidgetSpace[] = useSelector(reflowSpacesSelector) || []; const originalSpacePosition = widgetSpaces?.find( (space) => space.id === widgetId, ); const prevPositions = useRef( originalSpacePosition, ); const prevCollidingSpaces = useRef(); const prevMovementMap = useRef({}); // will become a state if we decide that resize should be a "toggle on-demand" feature const shouldResize = true; return function reflowSpaces( newPositions: OccupiedSpace, OGPositions: OccupiedSpace, direction: ReflowDirection, stopMoveAfterLimit = false, shouldSkipContainerReflow = false, forceDirection = false, immediateExitContainer?: string, ) { const { collidingSpaceMap, movementLimit, movementMap } = reflow( newPositions, OGPositions, widgetSpaces, direction, gridProps, forceDirection, shouldResize, immediateExitContainer, prevPositions.current, prevCollidingSpaces.current, ); prevPositions.current = newPositions; prevCollidingSpaces.current = collidingSpaceMap as WidgetCollidingSpaceMap; let correctedMovementMap = movementMap || {}; if (stopMoveAfterLimit) correctedMovementMap = getLimitedMovementMap( movementMap, prevMovementMap.current, movementLimit, ); if (shouldSkipContainerReflow) { const collidingSpaces = Object.values( (collidingSpaceMap as WidgetCollidingSpaceMap) || {}, ); for (const collidingSpace of collidingSpaces) { if (checkIsDropTarget(collidingSpace.type)) { correctedMovementMap = {}; } } } prevMovementMap.current = correctedMovementMap; if (!isEmpty(correctedMovementMap)) { isReflowing.current = true; if (forceDirection) dispatch(reflowMoveAction(correctedMovementMap)); else throttledDispatch(reflowMoveAction(correctedMovementMap)); } else if (isReflowing.current) { isReflowing.current = false; throttledDispatch.cancel(); dispatch(stopReflowAction()); } const bottomMostRow = getBottomRowAfterReflow( movementMap, newPositions.bottom, widgetSpaces, gridProps, ); return { ...movementLimit, movementMap: correctedMovementMap, bottomMostRow, }; }; };