2021-08-12 05:45:38 +00:00
|
|
|
import { useContext, useEffect, useRef } from "react";
|
|
|
|
|
import {
|
|
|
|
|
CONTAINER_GRID_PADDING,
|
|
|
|
|
GridDefaults,
|
|
|
|
|
MAIN_CONTAINER_WIDGET_ID,
|
|
|
|
|
} from "constants/WidgetConstants";
|
|
|
|
|
import { useSelector } from "store";
|
|
|
|
|
import { AppState } from "reducers";
|
|
|
|
|
import { getSelectedWidgets } from "selectors/ui";
|
|
|
|
|
import { getOccupiedSpaces } from "selectors/editorSelectors";
|
2021-08-18 10:31:38 +00:00
|
|
|
import { getTableFilterState } from "selectors/tableFilterSelectors";
|
2021-08-12 05:45:38 +00:00
|
|
|
import { OccupiedSpace } from "constants/editorConstants";
|
|
|
|
|
import { getDragDetails, getWidgets } from "sagas/selectors";
|
|
|
|
|
import {
|
|
|
|
|
getDropZoneOffsets,
|
|
|
|
|
WidgetOperationParams,
|
|
|
|
|
widgetOperationParams,
|
|
|
|
|
} from "utils/WidgetPropsUtils";
|
|
|
|
|
import { DropTargetContext } from "components/editorComponents/DropTargetComponent";
|
|
|
|
|
import { XYCoord } from "react-dnd";
|
|
|
|
|
import { isEmpty } from "lodash";
|
|
|
|
|
import { CanvasDraggingArenaProps } from "pages/common/CanvasDraggingArena";
|
|
|
|
|
import { useDispatch } from "react-redux";
|
|
|
|
|
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
|
|
|
|
import { EditorContext } from "components/editorComponents/EditorContextProvider";
|
|
|
|
|
import { useShowPropertyPane } from "./dragResizeHooks";
|
|
|
|
|
import { useWidgetSelection } from "./useWidgetSelection";
|
|
|
|
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
|
|
|
|
import { snapToGrid } from "utils/helpers";
|
|
|
|
|
|
|
|
|
|
export interface WidgetDraggingUpdateParams extends WidgetDraggingBlock {
|
|
|
|
|
updateWidgetParams: WidgetOperationParams;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type WidgetDraggingBlock = {
|
|
|
|
|
left: number;
|
|
|
|
|
top: number;
|
|
|
|
|
width: number;
|
|
|
|
|
height: number;
|
|
|
|
|
columnWidth: number;
|
|
|
|
|
rowHeight: number;
|
|
|
|
|
widgetId: string;
|
|
|
|
|
isNotColliding: boolean;
|
|
|
|
|
detachFromLayout?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useBlocksToBeDraggedOnCanvas = ({
|
|
|
|
|
noPad,
|
|
|
|
|
snapColumnSpace,
|
|
|
|
|
snapRows,
|
|
|
|
|
snapRowSpace,
|
|
|
|
|
widgetId,
|
|
|
|
|
}: CanvasDraggingArenaProps) => {
|
|
|
|
|
const dispatch = useDispatch();
|
|
|
|
|
const showPropertyPane = useShowPropertyPane();
|
|
|
|
|
const { selectWidget } = useWidgetSelection();
|
|
|
|
|
const containerPadding = noPad ? 0 : CONTAINER_GRID_PADDING;
|
|
|
|
|
|
2021-08-18 10:31:38 +00:00
|
|
|
// check any table filter is open or not
|
|
|
|
|
// if filter pane open, close before property pane open
|
|
|
|
|
const tableFilterPaneState = useSelector(getTableFilterState);
|
2021-08-12 05:45:38 +00:00
|
|
|
// 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 defaultHandlePositions = {
|
|
|
|
|
top: 20,
|
|
|
|
|
left: 20,
|
|
|
|
|
};
|
|
|
|
|
const {
|
|
|
|
|
draggingGroupCenter: dragCenter,
|
|
|
|
|
dragGroupActualParent: dragParent,
|
|
|
|
|
newWidget,
|
|
|
|
|
} = dragDetails;
|
|
|
|
|
const isResizing = useSelector(
|
|
|
|
|
(state: AppState) => state.ui.widgetDragResize.isResizing,
|
|
|
|
|
);
|
|
|
|
|
const selectedWidgets = useSelector(getSelectedWidgets);
|
|
|
|
|
const occupiedSpaces = useSelector(getOccupiedSpaces) || {};
|
|
|
|
|
const isNewWidget = !!newWidget && !dragParent;
|
|
|
|
|
const childrenOccupiedSpaces: OccupiedSpace[] =
|
|
|
|
|
(dragParent && occupiedSpaces[dragParent]) || [];
|
|
|
|
|
const isDragging = useSelector(
|
|
|
|
|
(state: AppState) => state.ui.widgetDragResize.isDragging,
|
|
|
|
|
);
|
|
|
|
|
const { updateWidget } = useContext(EditorContext);
|
|
|
|
|
|
|
|
|
|
const allWidgets = useSelector(getWidgets);
|
|
|
|
|
const getDragCenterSpace = () => {
|
|
|
|
|
if (dragCenter && dragCenter.widgetId) {
|
|
|
|
|
// Dragging by widget
|
|
|
|
|
return (
|
|
|
|
|
childrenOccupiedSpaces.find(
|
|
|
|
|
(each) => each.id === dragCenter.widgetId,
|
|
|
|
|
) || {}
|
|
|
|
|
);
|
|
|
|
|
} else if (
|
|
|
|
|
dragCenter &&
|
|
|
|
|
Number.isInteger(dragCenter.top) &&
|
|
|
|
|
Number.isInteger(dragCenter.left)
|
|
|
|
|
) {
|
|
|
|
|
// Dragging by Widget selection box
|
|
|
|
|
return dragCenter;
|
|
|
|
|
} else {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const getSnappedXY = (
|
|
|
|
|
parentColumnWidth: number,
|
|
|
|
|
parentRowHeight: number,
|
|
|
|
|
currentOffset: XYCoord,
|
|
|
|
|
parentOffset: XYCoord,
|
|
|
|
|
) => {
|
|
|
|
|
// TODO(abhinav): There is a simpler math to use.
|
|
|
|
|
const [leftColumn, topRow] = snapToGrid(
|
|
|
|
|
parentColumnWidth,
|
|
|
|
|
parentRowHeight,
|
|
|
|
|
currentOffset.x - parentOffset.x,
|
|
|
|
|
currentOffset.y - parentOffset.y,
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
X: leftColumn * parentColumnWidth,
|
|
|
|
|
Y: topRow * parentRowHeight,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
const getBlocksToDraw = (): WidgetDraggingBlock[] => {
|
|
|
|
|
if (isNewWidget) {
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
top: 0,
|
|
|
|
|
left: 0,
|
|
|
|
|
width: newWidget.columns * snapColumnSpace,
|
|
|
|
|
height: newWidget.rows * snapRowSpace,
|
|
|
|
|
columnWidth: newWidget.columns,
|
|
|
|
|
rowHeight: newWidget.rows,
|
|
|
|
|
widgetId: newWidget.widgetId,
|
|
|
|
|
detachFromLayout: newWidget.detachFromLayout,
|
|
|
|
|
isNotColliding: true,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
} else {
|
|
|
|
|
return childrenOccupiedSpaces
|
|
|
|
|
.filter((each) => selectedWidgets.includes(each.id))
|
|
|
|
|
.map((each) => ({
|
|
|
|
|
top: each.top * snapRowSpace + containerPadding,
|
|
|
|
|
left: each.left * snapColumnSpace + containerPadding,
|
|
|
|
|
width: (each.right - each.left) * snapColumnSpace,
|
|
|
|
|
height: (each.bottom - each.top) * snapRowSpace,
|
|
|
|
|
columnWidth: each.right - each.left,
|
|
|
|
|
rowHeight: each.bottom - each.top,
|
|
|
|
|
widgetId: each.id,
|
|
|
|
|
isNotColliding: true,
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const blocksToDraw = getBlocksToDraw();
|
|
|
|
|
const dragCenterSpace: any = getDragCenterSpace();
|
|
|
|
|
|
|
|
|
|
const filteredChildOccupiedSpaces = childrenOccupiedSpaces.filter(
|
|
|
|
|
(each) => !selectedWidgets.includes(each.id),
|
|
|
|
|
);
|
|
|
|
|
const { persistDropTargetRows, updateDropTargetRows } = useContext(
|
|
|
|
|
DropTargetContext,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const onDrop = (drawingBlocks: WidgetDraggingBlock[]) => {
|
|
|
|
|
const cannotDrop = drawingBlocks.some((each) => {
|
|
|
|
|
return !each.isNotColliding;
|
|
|
|
|
});
|
|
|
|
|
if (!cannotDrop) {
|
|
|
|
|
const draggedBlocksToUpdate = drawingBlocks
|
|
|
|
|
.sort(
|
|
|
|
|
(each1, each2) =>
|
|
|
|
|
each1.top + each1.height - (each2.top + each2.height),
|
|
|
|
|
)
|
|
|
|
|
.map((each) => {
|
|
|
|
|
const widget = newWidget ? newWidget : allWidgets[each.widgetId];
|
|
|
|
|
const updateWidgetParams = widgetOperationParams(
|
|
|
|
|
widget,
|
|
|
|
|
{ x: each.left, y: each.top },
|
|
|
|
|
{ x: 0, y: 0 },
|
|
|
|
|
snapColumnSpace,
|
|
|
|
|
snapRowSpace,
|
|
|
|
|
widget.detachFromLayout ? MAIN_CONTAINER_WIDGET_ID : widgetId,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...each,
|
|
|
|
|
updateWidgetParams,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
dispatchDrop(draggedBlocksToUpdate);
|
|
|
|
|
persistCanvasRows(
|
|
|
|
|
draggedBlocksToUpdate[draggedBlocksToUpdate.length - 1],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const persistCanvasRows = (bottomMostBlock: {
|
|
|
|
|
updateWidgetParams: WidgetOperationParams;
|
|
|
|
|
left: number;
|
|
|
|
|
top: number;
|
|
|
|
|
width: number;
|
|
|
|
|
height: number;
|
|
|
|
|
columnWidth: number;
|
|
|
|
|
rowHeight: number;
|
|
|
|
|
widgetId: string;
|
|
|
|
|
isNotColliding: boolean;
|
|
|
|
|
detachFromLayout?: boolean | undefined;
|
|
|
|
|
}) => {
|
|
|
|
|
const widget = newWidget ? newWidget : allWidgets[bottomMostBlock.widgetId];
|
|
|
|
|
const { updateWidgetParams } = bottomMostBlock;
|
|
|
|
|
const widgetBottomRow =
|
|
|
|
|
updateWidgetParams.payload.topRow +
|
|
|
|
|
(updateWidgetParams.payload.rows || widget.bottomRow - widget.topRow);
|
|
|
|
|
persistDropTargetRows &&
|
|
|
|
|
persistDropTargetRows(bottomMostBlock.widgetId, widgetBottomRow);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const dispatchDrop = (
|
|
|
|
|
draggedBlocksToUpdate: WidgetDraggingUpdateParams[],
|
|
|
|
|
) => {
|
|
|
|
|
if (isNewWidget) {
|
|
|
|
|
addNewWidget(draggedBlocksToUpdate[0]);
|
|
|
|
|
} else {
|
|
|
|
|
bulkMoveWidgets(draggedBlocksToUpdate);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const bulkMoveWidgets = (
|
|
|
|
|
draggedBlocksToUpdate: WidgetDraggingUpdateParams[],
|
|
|
|
|
) => {
|
|
|
|
|
dispatch({
|
|
|
|
|
type: ReduxActionTypes.WIDGETS_MOVE,
|
|
|
|
|
payload: {
|
|
|
|
|
draggedBlocksToUpdate,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const addNewWidget = (newWidget: WidgetDraggingUpdateParams) => {
|
|
|
|
|
const { updateWidgetParams } = newWidget;
|
|
|
|
|
updateWidget &&
|
|
|
|
|
updateWidget(
|
|
|
|
|
updateWidgetParams.operation,
|
|
|
|
|
updateWidgetParams.widgetId,
|
|
|
|
|
updateWidgetParams.payload,
|
|
|
|
|
);
|
2021-08-18 10:31:38 +00:00
|
|
|
// close filter pane if any open, before property pane open
|
|
|
|
|
tableFilterPaneState.isVisible &&
|
|
|
|
|
dispatch({
|
|
|
|
|
type: ReduxActionTypes.HIDE_TABLE_FILTER_PANE,
|
|
|
|
|
payload: { widgetId: tableFilterPaneState.widgetId },
|
|
|
|
|
});
|
2021-08-12 05:45:38 +00:00
|
|
|
// Adding setTimeOut to allow property pane to open only after widget is loaded.
|
|
|
|
|
// Not needed for most widgets except for Modal Widget.
|
|
|
|
|
setTimeout(() => {
|
2021-08-16 09:24:42 +00:00
|
|
|
selectWidget(updateWidgetParams.payload.newWidgetId);
|
2021-08-12 05:45:38 +00:00
|
|
|
showPropertyPane(updateWidgetParams.payload.newWidgetId);
|
|
|
|
|
}, 100);
|
|
|
|
|
AnalyticsUtil.logEvent("WIDGET_CARD_DRAG", {
|
|
|
|
|
widgetType: dragDetails.newWidget.type,
|
|
|
|
|
widgetName: dragDetails.newWidget.widgetCardName,
|
|
|
|
|
didDrop: true,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
const updateRows = (drawingBlocks: WidgetDraggingBlock[], rows: number) => {
|
|
|
|
|
if (drawingBlocks.length) {
|
|
|
|
|
const sortedByTopBlocks = drawingBlocks.sort(
|
|
|
|
|
(each1, each2) => each2.top + each2.height - (each1.top + each1.height),
|
|
|
|
|
);
|
|
|
|
|
const bottomMostBlock = sortedByTopBlocks[0];
|
|
|
|
|
const [, top] = getDropZoneOffsets(
|
|
|
|
|
snapColumnSpace,
|
|
|
|
|
snapRowSpace,
|
|
|
|
|
{
|
|
|
|
|
x: bottomMostBlock.left,
|
|
|
|
|
y: bottomMostBlock.top + bottomMostBlock.height,
|
|
|
|
|
} as XYCoord,
|
|
|
|
|
{ x: 0, y: 0 },
|
|
|
|
|
);
|
|
|
|
|
if (top > rows - GridDefaults.CANVAS_EXTENSION_OFFSET) {
|
|
|
|
|
return updateDropTargetRows && updateDropTargetRows(widgetId, top);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const rowRef = useRef(snapRows);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
rowRef.current = snapRows;
|
|
|
|
|
}, [snapRows, isDragging]);
|
|
|
|
|
|
|
|
|
|
const isChildOfCanvas = dragParent === widgetId;
|
|
|
|
|
const isCurrentDraggedCanvas = dragDetails.draggedOn === widgetId;
|
|
|
|
|
const isNewWidgetInitialTargetCanvas =
|
|
|
|
|
isNewWidget && widgetId === MAIN_CONTAINER_WIDGET_ID;
|
|
|
|
|
const parentDiff = isDragging
|
|
|
|
|
? {
|
|
|
|
|
top:
|
|
|
|
|
!isChildOfCanvas && !isEmpty(dragCenterSpace)
|
|
|
|
|
? dragCenterSpace.top * snapRowSpace + containerPadding
|
|
|
|
|
: containerPadding,
|
|
|
|
|
left:
|
|
|
|
|
!isChildOfCanvas && !isEmpty(dragCenterSpace)
|
|
|
|
|
? dragCenterSpace.left * snapColumnSpace + containerPadding
|
|
|
|
|
: containerPadding,
|
|
|
|
|
}
|
|
|
|
|
: {
|
|
|
|
|
top: 0,
|
|
|
|
|
left: 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const relativeStartPoints =
|
|
|
|
|
isDragging && !isEmpty(dragCenterSpace)
|
|
|
|
|
? {
|
|
|
|
|
left:
|
|
|
|
|
((isChildOfCanvas ? dragCenterSpace.left : 0) +
|
|
|
|
|
dragDetails.dragOffset.left) *
|
|
|
|
|
snapColumnSpace +
|
|
|
|
|
2 * containerPadding,
|
|
|
|
|
top:
|
|
|
|
|
((isChildOfCanvas ? dragCenterSpace.top : 0) +
|
|
|
|
|
dragDetails.dragOffset.top) *
|
|
|
|
|
snapRowSpace +
|
|
|
|
|
2 * containerPadding,
|
|
|
|
|
}
|
|
|
|
|
: defaultHandlePositions;
|
|
|
|
|
const currentOccSpaces = occupiedSpaces[widgetId] || [];
|
|
|
|
|
const occSpaces: OccupiedSpace[] = isChildOfCanvas
|
|
|
|
|
? filteredChildOccupiedSpaces
|
|
|
|
|
: currentOccSpaces;
|
|
|
|
|
return {
|
|
|
|
|
blocksToDraw,
|
|
|
|
|
defaultHandlePositions,
|
|
|
|
|
getSnappedXY,
|
|
|
|
|
isChildOfCanvas,
|
|
|
|
|
isCurrentDraggedCanvas,
|
|
|
|
|
isDragging,
|
|
|
|
|
isNewWidget,
|
|
|
|
|
isNewWidgetInitialTargetCanvas,
|
|
|
|
|
isResizing,
|
|
|
|
|
occSpaces,
|
|
|
|
|
onDrop,
|
|
|
|
|
parentDiff,
|
|
|
|
|
relativeStartPoints,
|
|
|
|
|
rowRef,
|
|
|
|
|
updateRows,
|
|
|
|
|
};
|
|
|
|
|
};
|