* dip * dip * scroll * fixes * dip * dip * dip * dip * dip * dip * dip * dip * dip * dip * dip * dip * dip * dip * dip * dip * dip * dip * solve for canvas glitches * dip * dip * dip * adjust scroll speed * dip * dip(code clean up) * dip * dip * ---dip * dip * dip * dip * middle ware for dropping multiple widgets. * adding scroll to drag for canvas selection. * fixing drag disabled and modal widget(detach from layout) drops * firefox and safari fixes * rebase conflicts. * fixing broken specs. * fixing specs and adding jest tests. * show border and disable resize when multiple widgets are selected. * selection box grab cursor * merge conflicts. * code clean up * fixing specs. * fixed a bug and failed specs. * fixing rerenders. * code clean up * code review comments * always have the drag point inside the widget. * fetching snap spaces instead of calculating. * remove widget_move action * fixing bugs with add widget parent height updation. * fixing specs. * List widget conflict fixes. * fixing canvas drop persistence. * Adding click to drag for modals and fixing few issues.
431 lines
14 KiB
TypeScript
431 lines
14 KiB
TypeScript
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<HTMLDivElement>,
|
|
canvasDrawRef: React.RefObject<HTMLCanvasElement>,
|
|
{
|
|
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,
|
|
};
|
|
};
|