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

178 lines
5.8 KiB
TypeScript
Raw Normal View History

import React, { useContext, useState, memo } from "react";
import { ResizeDirection } from "re-resizable";
import { XYCoord } from "react-dnd";
import { getAbsolutePixels } from "utils/helpers";
import { WidgetOperations, WidgetRowCols } from "widgets/BaseWidget";
import { EditorContext } from "components/editorComponents/EditorContextProvider";
import { FocusContext, ResizingContext } from "pages/Editor/CanvasContexts";
import { DraggableComponentContext } from "./DraggableComponent";
import { generateClassName } from "utils/generators";
2019-10-29 10:03:04 +00:00
import ResizableContainer, {
ResizeBorderDotDiv,
ResizableComponentProps,
} from "./ResizableContainer";
import {
UIElementSize,
getHandleSyles,
computeUpdatedRowCols,
hasCollision,
getBorderStyles,
} from "./ResizableUtils";
/* eslint-disable react/display-name */
export const ResizableComponent = memo((props: ResizableComponentProps) => {
// Fetch information from the context
const { isDragging } = useContext(DraggableComponentContext);
2019-10-08 12:31:58 +00:00
const { setIsResizing } = useContext(ResizingContext);
const { updateWidget, occupiedSpaces } = useContext(EditorContext);
2020-01-06 11:02:22 +00:00
const {
showPropertyPane,
selectedWidget,
focusedWidget,
selectWidget,
} = useContext(FocusContext);
const occupiedSpacesBySiblingWidgets =
occupiedSpaces && props.parentId && occupiedSpaces[props.parentId]
? occupiedSpaces[props.parentId]
: undefined;
// Use state flag - isColliding - use to figure out if resize is possible at the current size.
2019-10-08 06:19:10 +00:00
const [isColliding, setIsColliding] = useState(false);
// isFocused (string | boolean) -> isWidgetFocused (boolean)
2020-01-06 11:02:22 +00:00
const isWidgetFocused =
focusedWidget === props.widgetId || selectedWidget === props.widgetId;
2019-10-08 06:19:10 +00:00
// Widget can be resized if
// The widget is focused, and
// There is no drag event in progress on a widget.
const canResize = !isDragging && isWidgetFocused;
// Calculate the dimensions of the widget,
// The ResizableContainer's size prop is controlled
const dimensions: UIElementSize = {
width: (props.rightColumn - props.leftColumn) * props.parentColumnSpace,
height: (props.bottomRow - props.topRow) * props.parentRowSpace,
};
// Resize bound's className - defaults to body
// ResizableContainer accepts the className of the element,
// whose clientRect will act as the bounds for resizing.
// Note, if there are many containers with the same className
// the bounding container becomes the nearest parent with the className
let bounds = "body";
bounds = "." + generateClassName(props.parentId);
2019-10-08 06:19:10 +00:00
// onResize handler
// Checks if the current resize position has any collisions
// If yes, set isColliding flag to true.
// If no, set isColliding flag to false.
2019-10-08 06:19:10 +00:00
const checkForCollision = (
e: MouseEvent,
dir: ResizeDirection,
ref: HTMLDivElement,
delta: UIElementSize,
2019-10-08 06:19:10 +00:00
position: XYCoord,
) => {
const isResizePossible = !hasCollision(
delta,
position,
props,
occupiedSpacesBySiblingWidgets,
);
if (isResizePossible === isColliding) {
setIsColliding(!isColliding);
2019-10-08 06:19:10 +00:00
}
};
// onResizeStop handler
// when done resizing, check if;
// 1) There is no collision
// 2) There is a change in widget size
// Update widget, if both of the above are true.
const updateSize = (
e: MouseEvent,
dir: ResizeDirection,
ref: HTMLDivElement,
d: UIElementSize,
position: XYCoord,
) => {
// Get the difference in size of the widget, before and after resizing.
const delta: UIElementSize = {
height: getAbsolutePixels(ref.style.height) - dimensions.height,
width: getAbsolutePixels(ref.style.width) - dimensions.width,
};
// Get the updated Widget rows and columns props
// False, if there is collision
// False, if none of the rows and cols have changed.
const newRowCols: WidgetRowCols | false = computeUpdatedRowCols(
isColliding,
delta,
position,
props,
);
if (newRowCols) {
updateWidget &&
updateWidget(WidgetOperations.RESIZE, props.widgetId, newRowCols);
}
// Clear border styles
setIsColliding && setIsColliding(false);
// Tell the Canvas that we've stopped resizing
2019-10-03 15:38:46 +00:00
setIsResizing && setIsResizing(false);
// Tell the Canvas to put the focus back to this widget
// By setting the focus, we enable the control buttons on the widget
2020-01-06 11:02:22 +00:00
selectWidget && selectWidget(props.widgetId);
// Let the propertypane show.
// The propertypane decides whether to show itself, based on
// whether it was showing when the widget resize started.
showPropertyPane && showPropertyPane(props.widgetId);
};
const style = getBorderStyles(
isWidgetFocused,
isColliding,
props.paddingOffset,
);
return (
<ResizableContainer
2019-10-29 10:03:04 +00:00
isfocused={isWidgetFocused ? "true" : undefined}
position={{
x: 0,
y: 0,
}}
size={dimensions}
disableDragging
minWidth={props.parentColumnSpace}
minHeight={props.parentRowSpace}
style={style}
onResizeStop={updateSize}
2019-10-08 06:19:10 +00:00
onResize={checkForCollision}
2019-10-03 15:38:46 +00:00
onResizeStart={() => {
setIsResizing && setIsResizing(true);
2020-01-06 11:02:22 +00:00
selectWidget && selectWidget(props.widgetId);
showPropertyPane && showPropertyPane();
2019-10-03 15:38:46 +00:00
}}
resizeGrid={[props.parentColumnSpace, props.parentRowSpace]}
bounds={bounds}
resizeHandleStyles={getHandleSyles()}
enableResizing={{
2019-10-08 12:31:58 +00:00
top: canResize,
right: canResize,
bottom: canResize,
left: canResize,
topRight: canResize,
topLeft: canResize,
bottomRight: canResize,
bottomLeft: canResize,
}}
>
2019-10-29 10:03:04 +00:00
<ResizeBorderDotDiv isfocused={isWidgetFocused}>
{props.children}
</ResizeBorderDotDiv>
</ResizableContainer>
);
});
export default ResizableComponent;