import { CONTAINER_GRID_PADDING, GridDefaults, } from "constants/WidgetConstants"; import { debounce, throttle } from "lodash"; import { CanvasDraggingArenaProps } from "pages/common/CanvasDraggingArena"; import { useEffect } from "react"; import { getNearestParentCanvas } from "utils/generators"; import { noCollision } from "utils/WidgetPropsUtils"; import { useWidgetDragResize } from "./dragResizeHooks"; import { useBlocksToBeDraggedOnCanvas, WidgetDraggingBlock, } from "./useBlocksToBeDraggedOnCanvas"; import { useCanvasDragToScroll } from "./useCanvasDragToScroll"; export const useCanvasDragging = ( canvasRef: React.RefObject, canvasDrawRef: React.RefObject, { canExtend, dropDisabled, noPad, snapColumnSpace, snapRows, snapRowSpace, widgetId, }: CanvasDraggingArenaProps, ) => { const { devicePixelRatio: scale = 1 } = window; const { blocksToDraw, defaultHandlePositions, getSnappedXY, isChildOfCanvas, isCurrentDraggedCanvas, isDragging, isNewWidget, isNewWidgetInitialTargetCanvas, isResizing, occSpaces, onDrop, parentDiff, relativeStartPoints, rowRef, updateRows, } = useBlocksToBeDraggedOnCanvas({ canExtend, noPad, snapColumnSpace, snapRows, snapRowSpace, widgetId, }); const { setDraggingCanvas, setDraggingNewWidget, setDraggingState, } = useWidgetDragResize(); const updateCanvasStyles = () => { const parentCanvas: Element | null = getNearestParentCanvas( canvasRef.current, ); if (parentCanvas && canvasDrawRef.current && canvasRef.current) { const { height } = parentCanvas.getBoundingClientRect(); const { width } = canvasRef.current.getBoundingClientRect(); canvasDrawRef.current.style.width = width + "px"; canvasDrawRef.current.style.position = canExtend ? "absolute" : "sticky"; canvasDrawRef.current.style.left = "0px"; canvasDrawRef.current.style.height = height + "px"; canvasDrawRef.current.style.top = (canExtend ? parentCanvas.scrollTop : 0) + "px"; } }; const canScroll = useCanvasDragToScroll( canvasRef, isCurrentDraggedCanvas, isDragging, snapRows, canExtend, ); useEffect(() => { if ( canvasRef.current && !isResizing && isDragging && blocksToDraw.length > 0 ) { const scrollParent: Element | null = getNearestParentCanvas( canvasRef.current, ); let canvasIsDragging = false; let isUpdatingRows = false; let currentRectanglesToDraw: WidgetDraggingBlock[] = []; const scrollObj: any = {}; const resetCanvasState = () => { if (canvasDrawRef.current && canvasRef.current) { const canvasCtx: any = canvasDrawRef.current.getContext("2d"); canvasCtx.clearRect( 0, 0, canvasDrawRef.current.width, canvasDrawRef.current.height, ); canvasRef.current.style.zIndex = ""; canvasIsDragging = false; } }; if (isDragging) { const startPoints = defaultHandlePositions; const onMouseUp = () => { if (isDragging && canvasIsDragging) { onDrop(currentRectanglesToDraw); } startPoints.top = defaultHandlePositions.top; startPoints.left = defaultHandlePositions.left; resetCanvasState(); if (isCurrentDraggedCanvas) { if (isNewWidget) { setDraggingNewWidget(false, undefined); } else { setDraggingState({ isDragging: false, }); } setDraggingCanvas(); } }; const onFirstMoveOnCanvas = (e: any) => { if ( !isResizing && isDragging && !canvasIsDragging && canvasRef.current ) { if (!isNewWidget) { startPoints.left = relativeStartPoints.left || defaultHandlePositions.left; startPoints.top = relativeStartPoints.top || defaultHandlePositions.top; } if (!isCurrentDraggedCanvas) { // we can just use canvasIsDragging but this is needed to render the relative DragLayerComponent setDraggingCanvas(widgetId); } canvasIsDragging = true; canvasRef.current.style.zIndex = "2"; onMouseMove(e); } }; const onMouseMove = (e: any) => { if (isDragging && canvasIsDragging && canvasRef.current) { const delta = { left: e.offsetX - startPoints.left - parentDiff.left, top: e.offsetY - startPoints.top - parentDiff.top, }; const drawingBlocks = blocksToDraw.map((each) => ({ ...each, left: each.left + delta.left, top: each.top + delta.top, })); const newRows = updateRows(drawingBlocks, rowRef.current); const rowDelta = newRows ? newRows - rowRef.current : 0; rowRef.current = newRows ? newRows : rowRef.current; currentRectanglesToDraw = drawingBlocks.map((each) => ({ ...each, isNotColliding: !dropDisabled && noCollision( { x: each.left, y: each.top }, snapColumnSpace, snapRowSpace, { x: 0, y: 0 }, each.columnWidth, each.rowHeight, each.widgetId, occSpaces, rowRef.current, GridDefaults.DEFAULT_GRID_COLUMNS, each.detachFromLayout, ), })); if (rowDelta && canvasRef.current) { isUpdatingRows = true; canScroll.current = false; renderNewRows(delta); } else if (!isUpdatingRows) { renderBlocks(); } scrollObj.lastMouseMoveEvent = { offsetX: e.offsetX, offsetY: e.offsetY, }; scrollObj.lastScrollTop = scrollParent?.scrollTop; scrollObj.lastScrollHeight = scrollParent?.scrollHeight; } else { onFirstMoveOnCanvas(e); } }; const renderNewRows = debounce((delta) => { isUpdatingRows = true; if (canvasRef.current && canvasDrawRef.current) { const canvasCtx: any = canvasDrawRef.current.getContext("2d"); currentRectanglesToDraw = blocksToDraw.map((each) => { return { ...each, left: each.left + delta.left, top: each.top + delta.top, isNotColliding: !dropDisabled && noCollision( { x: each.left + delta.left, y: each.top + delta.top }, snapColumnSpace, snapRowSpace, { x: 0, y: 0 }, each.columnWidth, each.rowHeight, each.widgetId, occSpaces, rowRef.current, GridDefaults.DEFAULT_GRID_COLUMNS, each.detachFromLayout, ), }; }); canvasCtx.save(); canvasCtx.scale(scale, scale); canvasCtx.clearRect( 0, 0, canvasDrawRef.current.width, canvasDrawRef.current.height, ); canvasCtx.restore(); renderBlocks(); canScroll.current = false; endRenderRows.cancel(); endRenderRows(); } }); const endRenderRows = throttle( () => { canScroll.current = true; }, 50, { leading: false, trailing: true, }, ); const renderBlocks = () => { if ( canvasRef.current && isCurrentDraggedCanvas && canvasIsDragging && canvasDrawRef.current ) { const canvasCtx: any = canvasDrawRef.current.getContext("2d"); canvasCtx.save(); canvasCtx.clearRect( 0, 0, canvasDrawRef.current.width, canvasDrawRef.current.height, ); isUpdatingRows = false; if (canvasIsDragging) { currentRectanglesToDraw.forEach((each) => { drawBlockOnCanvas(each); }); } canvasCtx.restore(); } }; const drawBlockOnCanvas = (blockDimensions: WidgetDraggingBlock) => { if ( canvasDrawRef.current && canvasRef.current && scrollParent && isCurrentDraggedCanvas && canvasIsDragging ) { const canvasCtx: any = canvasDrawRef.current.getContext("2d"); const topOffset = canExtend ? scrollParent.scrollTop : 0; const snappedXY = getSnappedXY( snapColumnSpace, snapRowSpace, { x: blockDimensions.left, y: blockDimensions.top, }, { x: 0, y: 0, }, ); canvasCtx.fillStyle = `${ blockDimensions.isNotColliding ? "rgb(104, 113, 239, 0.6)" : "red" }`; canvasCtx.fillRect( blockDimensions.left + (noPad ? 0 : CONTAINER_GRID_PADDING), blockDimensions.top - topOffset + (noPad ? 0 : CONTAINER_GRID_PADDING), blockDimensions.width, blockDimensions.height, ); canvasCtx.fillStyle = `${ blockDimensions.isNotColliding ? "rgb(233, 250, 243, 0.6)" : "red" }`; const strokeWidth = 1; canvasCtx.setLineDash([3]); canvasCtx.strokeStyle = "rgb(104, 113, 239)"; canvasCtx.strokeRect( snappedXY.X + strokeWidth + (noPad ? 0 : CONTAINER_GRID_PADDING), snappedXY.Y - topOffset + strokeWidth + (noPad ? 0 : CONTAINER_GRID_PADDING), blockDimensions.width - strokeWidth, blockDimensions.height - strokeWidth, ); } }; const onScroll = () => { const { lastMouseMoveEvent, lastScrollHeight, lastScrollTop, } = scrollObj; if ( lastMouseMoveEvent && Number.isInteger(lastScrollHeight) && Number.isInteger(lastScrollTop) && scrollParent && canScroll.current ) { const delta = scrollParent?.scrollHeight + scrollParent?.scrollTop - (lastScrollHeight + lastScrollTop); onMouseMove({ offsetX: lastMouseMoveEvent.offsetX, offsetY: lastMouseMoveEvent.offsetY + delta, }); } }; const initializeListeners = () => { canvasRef.current?.addEventListener("mousemove", onMouseMove, false); canvasRef.current?.addEventListener("mouseup", onMouseUp, false); scrollParent?.addEventListener("scroll", updateCanvasStyles, false); scrollParent?.addEventListener("scroll", onScroll, false); canvasRef.current?.addEventListener( "mouseover", onFirstMoveOnCanvas, false, ); canvasRef.current?.addEventListener( "mouseout", resetCanvasState, false, ); canvasRef.current?.addEventListener( "mouseleave", resetCanvasState, false, ); document.body.addEventListener("mouseup", onMouseUp, false); window.addEventListener("mouseup", onMouseUp, false); }; const startDragging = () => { if (canvasRef.current && canvasDrawRef.current && scrollParent) { const { height } = scrollParent.getBoundingClientRect(); const { width } = canvasRef.current.getBoundingClientRect(); const canvasCtx: any = canvasDrawRef.current.getContext("2d"); canvasDrawRef.current.width = width * scale; canvasDrawRef.current.height = height * scale; canvasCtx.scale(scale, scale); updateCanvasStyles(); initializeListeners(); if ( (isChildOfCanvas || isNewWidgetInitialTargetCanvas) && canvasRef.current ) { canvasRef.current.style.zIndex = "2"; } } }; startDragging(); return () => { canvasRef.current?.removeEventListener("mousemove", onMouseMove); canvasRef.current?.removeEventListener("mouseup", onMouseUp); scrollParent?.removeEventListener("scroll", updateCanvasStyles); scrollParent?.removeEventListener("scroll", onScroll); canvasRef.current?.removeEventListener( "mouseover", onFirstMoveOnCanvas, ); canvasRef.current?.removeEventListener("mouseout", resetCanvasState); canvasRef.current?.removeEventListener( "mouseleave", resetCanvasState, ); document.body.removeEventListener("mouseup", onMouseUp); window.removeEventListener("mouseup", onMouseUp); }; } else { resetCanvasState(); } } }, [isDragging, isResizing, blocksToDraw, snapRows, canExtend]); return { showCanvas: isDragging && !isResizing, }; };