PromucFlow_constructor/app/client/src/components/editorComponents/DropTargetComponent.tsx

305 lines
9.3 KiB
TypeScript
Raw Normal View History

2020-01-16 11:46:21 +00:00
import React, {
useState,
useContext,
ReactNode,
Context,
createContext,
useEffect,
memo,
2020-01-16 11:46:21 +00:00
} from "react";
import styled from "styled-components";
import { useDrop, XYCoord, DropTargetMonitor } from "react-dnd";
import { WidgetProps } from "widgets/BaseWidget";
import { WidgetConfigProps } from "reducers/entityReducers/widgetConfigReducer";
import {
widgetOperationParams,
noCollision,
getCanvasSnapRows,
} from "utils/WidgetPropsUtils";
import { EditorContext } from "components/editorComponents/EditorContextProvider";
2020-01-16 11:46:21 +00:00
import {
MAIN_CONTAINER_WIDGET_ID,
GridDefaults,
WidgetTypes,
2020-01-16 11:46:21 +00:00
} from "constants/WidgetConstants";
import { calculateDropTargetRows } from "./DropTargetUtils";
import DragLayerComponent from "./DragLayerComponent";
2020-01-16 11:46:21 +00:00
import { AppState } from "reducers";
import { useSelector } from "react-redux";
2020-01-20 09:00:37 +00:00
import {
useShowPropertyPane,
useWidgetSelection,
useCanvasSnapRowsUpdateHook,
2020-01-20 09:00:37 +00:00
} from "utils/hooks/dragResizeHooks";
import { getOccupiedSpaces } from "selectors/editorSelectors";
type DropTargetComponentProps = WidgetProps & {
children?: ReactNode;
snapColumnSpace: number;
snapRowSpace: number;
minHeight: number;
};
type DropTargetBounds = {
x: number;
y: number;
width: number;
height: number;
};
2020-01-16 11:46:21 +00:00
const StyledDropTarget = styled.div`
transition: height 100ms ease-in;
width: 100%;
position: relative;
background: none;
user-select: none;
2020-01-16 11:46:21 +00:00
`;
2020-05-14 06:06:20 +00:00
const Onboarding = () => {
return (
<div style={{ position: "fixed", left: "50%", top: "50vh" }}>
<h2 style={{ color: "#ccc" }}>Drag and drop a widget here</h2>
</div>
);
};
2020-03-06 09:45:21 +00:00
/*
2020-01-16 11:46:21 +00:00
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?: (widgetId: string, row: number) => boolean;
2020-04-03 09:32:13 +00:00
persistDropTargetRows?: (widgetId: string, row: number) => void;
2020-01-16 11:46:21 +00:00
}> = createContext({});
export const DropTargetComponent = memo((props: DropTargetComponentProps) => {
const canDropTargetExtend = props.canExtend;
2020-01-20 09:00:37 +00:00
const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend);
const { updateWidget } = useContext(EditorContext);
const occupiedSpaces = useSelector(getOccupiedSpaces);
2020-01-20 09:00:37 +00:00
const selectedWidget = useSelector(
(state: AppState) => state.ui.widgetDragResize.selectedWidget,
2020-01-20 09:00:37 +00:00
);
const isResizing = useSelector(
(state: AppState) => state.ui.widgetDragResize.isResizing,
2020-01-16 11:46:21 +00:00
);
2020-02-18 19:56:58 +00:00
const isDragging = useSelector(
(state: AppState) => state.ui.widgetDragResize.isDragging,
);
2020-01-16 11:46:21 +00:00
const spacesOccupiedBySiblingWidgets =
occupiedSpaces && occupiedSpaces[props.widgetId]
? occupiedSpaces[props.widgetId]
: undefined;
2020-01-16 11:46:21 +00:00
const childWidgets = useSelector(
(state: AppState) => state.entities.canvasWidgets[props.widgetId].children,
);
const occupiedSpacesByChildren =
occupiedSpaces && occupiedSpaces[props.widgetId];
2020-01-16 11:46:21 +00:00
const [dropTargetOffset, setDropTargetOffset] = useState({ x: 0, y: 0 });
const [rows, setRows] = useState(snapRows);
2020-01-16 11:46:21 +00:00
const showPropertyPane = useShowPropertyPane();
const { selectWidget, focusWidget } = useWidgetSelection();
const updateCanvasSnapRows = useCanvasSnapRowsUpdateHook();
useEffect(() => {
const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend);
setRows(snapRows);
}, [props.bottomRow, props.canExtend]);
2020-04-03 09:32:13 +00:00
const persistDropTargetRows = (widgetId: string, widgetBottomRow: number) => {
const newRows = calculateDropTargetRows(
widgetId,
widgetBottomRow,
props.minHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
occupiedSpacesByChildren,
);
const rowsToPersist = Math.max(
props.minHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
2020-04-03 09:32:13 +00:00
newRows,
);
setRows(rowsToPersist);
if (canDropTargetExtend) {
updateCanvasSnapRows(props.widgetId, rowsToPersist);
2020-01-16 11:46:21 +00:00
}
};
/* Update the rows of the main container based on the current widget's (dragging/resizing) bottom row */
const updateDropTargetRows = (widgetId: string, widgetBottomRow: number) => {
if (canDropTargetExtend) {
const newRows = calculateDropTargetRows(
widgetId,
widgetBottomRow,
props.minHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
occupiedSpacesByChildren,
);
2020-04-03 09:32:13 +00:00
if (rows < newRows) {
setRows(newRows);
2020-01-16 11:46:21 +00:00
return true;
}
return false;
}
return false;
};
const isChildFocused =
!!childWidgets &&
!!selectedWidget &&
childWidgets.length > 0 &&
childWidgets.indexOf(selectedWidget) > -1;
const isChildResizing = !!isResizing && isChildFocused;
// Make this component a drop target
2020-05-14 06:06:20 +00:00
const [{ isExactlyOver, isOver }, drop] = useDrop({
accept: Object.values(WidgetTypes),
options: {
arePropsEqual: () => {
return true;
},
},
drop(widget: WidgetProps & Partial<WidgetConfigProps>, monitor) {
// Make sure we're dropping in this container.
if (isExactlyOver) {
const updateWidgetParams = widgetOperationParams(
widget,
monitor.getSourceClientOffset() as XYCoord,
dropTargetOffset,
props.snapColumnSpace,
props.snapRowSpace,
props.widgetId,
);
2020-01-16 11:46:21 +00:00
2020-04-03 09:32:13 +00:00
// const widgetBottomRow = getWidgetBottomRow(widget, updateWidgetParams);
const widgetBottomRow =
updateWidgetParams.payload.topRow +
(updateWidgetParams.payload.rows || widget.bottomRow - widget.topRow);
// Only show propertypane if this is a new widget.
// If it is not a new widget, then let the DraggableComponent handle it.
// Give evaluations a second to complete.
setTimeout(() => {
if (showPropertyPane && updateWidgetParams.payload.newWidgetId) {
showPropertyPane(updateWidgetParams.payload.newWidgetId);
// toggleEditWidgetName(updateWidgetParams.payload.newWidgetId, true);
}
}, 100);
2020-01-16 11:46:21 +00:00
// Select the widget if it is a new widget
selectWidget && selectWidget(widget.widgetId);
2020-04-03 09:32:13 +00:00
persistDropTargetRows(widget.widgetId, widgetBottomRow);
2020-01-16 11:46:21 +00:00
/* Finally update the widget */
updateWidget &&
updateWidget(
updateWidgetParams.operation,
updateWidgetParams.widgetId,
updateWidgetParams.payload,
);
}
return undefined;
},
// Collect isOver for ui transforms when hovering over this component
collect: (monitor: DropTargetMonitor) => ({
isExactlyOver: monitor.isOver({ shallow: true }),
2020-05-14 06:06:20 +00:00
isOver: monitor.isOver(),
}),
// Only allow drop if the drag object is directly over this component
// As opposed to the drag object being over a child component, or outside the component bounds
2019-09-25 21:21:04 +00:00
// Also only if the dropzone does not overlap any existing children
2019-09-24 12:36:03 +00:00
canDrop: (widget, monitor) => {
2019-10-02 21:14:58 +00:00
// Check if the draggable is the same as the dropTarget
if (isExactlyOver) {
const hasCollision = !noCollision(
monitor.getSourceClientOffset() as XYCoord,
2019-09-25 21:21:04 +00:00
props.snapColumnSpace,
props.snapRowSpace,
widget,
dropTargetOffset,
spacesOccupiedBySiblingWidgets,
2020-01-16 11:46:21 +00:00
rows,
GridDefaults.DEFAULT_GRID_COLUMNS,
2019-09-25 21:21:04 +00:00
);
return !hasCollision;
2019-09-25 21:21:04 +00:00
}
return false;
},
});
const handleBoundsUpdate = (rect: DOMRect) => {
if (rect.x !== dropTargetOffset.x || rect.y !== dropTargetOffset.y) {
setDropTargetOffset({
x: rect.x,
y: rect.y,
});
}
};
const handleFocus = (e: any) => {
if (!isResizing && !isDragging) {
if (!props.parentId) {
selectWidget && selectWidget(props.widgetId);
focusWidget && focusWidget(props.widgetId);
} else {
selectWidget && selectWidget(props.parentId);
focusWidget && focusWidget(props.parentId);
}
showPropertyPane && showPropertyPane();
}
e.stopPropagation();
e.preventDefault();
};
const height = canDropTargetExtend
? `${Math.max(rows * props.snapRowSpace, props.minHeight)}px`
: "100%";
2020-01-16 11:46:21 +00:00
2020-02-13 09:32:24 +00:00
const border =
(isResizing || isDragging) &&
isExactlyOver &&
props.widgetId === MAIN_CONTAINER_WIDGET_ID
? "1px solid #DDDDDD"
: "1px solid transparent";
2020-02-13 09:32:24 +00:00
return (
2020-01-16 11:46:21 +00:00
<DropTargetContext.Provider
value={{ updateDropTargetRows, persistDropTargetRows }}
2019-12-25 09:17:37 +00:00
>
2020-01-16 11:46:21 +00:00
<StyledDropTarget
onClick={handleFocus}
ref={drop}
style={{
height,
2020-02-13 09:32:24 +00:00
border,
2020-01-16 11:46:21 +00:00
}}
2020-12-11 13:48:01 +00:00
className={"t--drop-target"}
2020-01-16 11:46:21 +00:00
>
{props.children}
2020-05-14 06:06:20 +00:00
{!(childWidgets && childWidgets.length) &&
!isDragging &&
!props.parentId && <Onboarding />}
2020-01-16 11:46:21 +00:00
<DragLayerComponent
parentWidgetId={props.widgetId}
canDropTargetExtend={canDropTargetExtend}
2020-01-16 11:46:21 +00:00
parentRowHeight={props.snapRowSpace}
parentColumnWidth={props.snapColumnSpace}
visible={isExactlyOver || isChildResizing}
isOver={isExactlyOver}
occupiedSpaces={spacesOccupiedBySiblingWidgets}
onBoundsUpdate={handleBoundsUpdate}
parentRows={rows}
parentCols={props.snapColumns}
isResizing={isChildResizing}
2020-05-14 06:06:20 +00:00
force={isDragging && !isOver && !props.parentId}
2020-01-16 11:46:21 +00:00
/>
</StyledDropTarget>
</DropTargetContext.Provider>
);
});
export default DropTargetComponent;