import React, { ReactNode, Context, createContext, memo, useEffect, useRef, useCallback, useMemo, } from "react"; import styled from "styled-components"; import { isEqual } from "lodash"; import { WidgetProps } from "widgets/BaseWidget"; import { getCanvasSnapRows } from "utils/WidgetPropsUtils"; import { MAIN_CONTAINER_WIDGET_ID, GridDefaults, } from "constants/WidgetConstants"; import { calculateDropTargetRows } from "./DropTargetUtils"; import DragLayerComponent from "./DragLayerComponent"; import { AppState } from "reducers"; import { useSelector } from "react-redux"; import { useShowPropertyPane, useCanvasSnapRowsUpdateHook, } from "utils/hooks/dragResizeHooks"; import { getOccupiedSpacesSelectorForContainer } from "selectors/editorSelectors"; import { useWidgetSelection } from "utils/hooks/useWidgetSelection"; import { getDragDetails } from "sagas/selectors"; type DropTargetComponentProps = WidgetProps & { children?: ReactNode; snapColumnSpace: number; snapRowSpace: number; minHeight: number; noPad?: boolean; }; const StyledDropTarget = styled.div` transition: height 100ms ease-in; width: 100%; position: relative; background: none; user-select: none; z-index: 1; `; function Onboarding() { return (

Drag and drop a widget here

); } /* This context will provide the function which will help the draglayer and resizablecomponents trigger an update of the main container's rows */ export const DropTargetContext: Context<{ updateDropTargetRows?: ( widgetIdsToExclude: string[], widgetBottomRow: number, ) => number | false; }> = createContext({}); export function DropTargetComponent(props: DropTargetComponentProps) { const canDropTargetExtend = props.canExtend; const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend); const isResizing = useSelector( (state: AppState) => state.ui.widgetDragResize.isResizing, ); const isDragging = useSelector( (state: AppState) => state.ui.widgetDragResize.isDragging, ); // dragDetails contains of info needed for a container jump: // which parent the dragging widget belongs, // which canvas is active(being dragged on), // which widget is grabbed while dragging started, // relative position of mouse pointer wrt to the last grabbed widget. const dragDetails = useSelector(getDragDetails); const { draggedOn } = dragDetails; const childWidgets: string[] | undefined = useSelector( (state: AppState) => state.entities.canvasWidgets[props.widgetId]?.children, ); const selectOccupiedSpaces = useCallback( getOccupiedSpacesSelectorForContainer(props.widgetId), [props.widgetId], ); const occupiedSpacesByChildren = useSelector(selectOccupiedSpaces, isEqual); const rowRef = useRef(snapRows); const showPropertyPane = useShowPropertyPane(); const { deselectAll, focusWidget } = useWidgetSelection(); const updateCanvasSnapRows = useCanvasSnapRowsUpdateHook(); const showDragLayer = (isDragging && draggedOn === props.widgetId) || isResizing; useEffect(() => { const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend); if (rowRef.current !== snapRows) { rowRef.current = snapRows; updateHeight(); if (canDropTargetExtend) { updateCanvasSnapRows(props.widgetId, snapRows); } } }, [props.bottomRow, props.canExtend]); useEffect(() => { if (!isDragging || !isResizing) { // 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]); const updateHeight = () => { if (dropTargetRef.current) { const height = canDropTargetExtend ? `${Math.max(rowRef.current * props.snapRowSpace, props.minHeight)}px` : "100%"; dropTargetRef.current.style.height = height; } }; const updateDropTargetRows = ( widgetIdsToExclude: string[], widgetBottomRow: number, ) => { if (canDropTargetExtend) { const newRows = calculateDropTargetRows( widgetIdsToExclude, widgetBottomRow, props.minHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1, occupiedSpacesByChildren, ); if (rowRef.current < newRows) { rowRef.current = newRows; updateHeight(); return newRows; } return false; } return false; }; const handleFocus = (e: any) => { if (!isResizing && !isDragging) { if (!props.parentId) { deselectAll(); focusWidget && focusWidget(props.widgetId); showPropertyPane && showPropertyPane(); } } // commenting this out to allow propagation of click events // e.stopPropagation(); e.preventDefault(); }; const height = canDropTargetExtend ? `${Math.max(rowRef.current * props.snapRowSpace, props.minHeight)}px` : "100%"; const boxShadow = (isResizing || isDragging) && props.widgetId === MAIN_CONTAINER_WIDGET_ID ? "inset 0px 0px 0px 1px #DDDDDD" : "0px 0px 0px 1px transparent"; const dropTargetRef = useRef(null); // memoizing context values const contextValue = useMemo(() => { return { updateDropTargetRows, }; }, [updateDropTargetRows, occupiedSpacesByChildren]); return ( {props.children} {!(childWidgets && childWidgets.length) && !isDragging && !props.parentId && } {showDragLayer && ( )} ); } const MemoizedDropTargetComponent = memo(DropTargetComponent); export default MemoizedDropTargetComponent;