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

289 lines
9.1 KiB
TypeScript
Raw Normal View History

2020-02-18 19:56:58 +00:00
import React, { useContext, memo } from "react";
import { XYCoord } from "react-dnd";
2020-01-17 12:34:58 +00:00
import {
MAIN_CONTAINER_WIDGET_ID,
WidgetTypes,
} from "constants/WidgetConstants";
2020-02-18 19:56:58 +00:00
import { ContainerWidgetProps } from "widgets/ContainerWidget";
import {
WidgetOperations,
WidgetRowCols,
WidgetProps,
} from "widgets/BaseWidget";
import { EditorContext } from "components/editorComponents/EditorContextProvider";
import { generateClassName } from "utils/generators";
2020-01-16 11:46:21 +00:00
import { DropTargetContext } from "./DropTargetComponent";
import {
UIElementSize,
2020-02-18 19:56:58 +00:00
computeFinalRowCols,
computeRowCols,
} from "./ResizableUtils";
2020-01-20 09:00:37 +00:00
import {
useShowPropertyPane,
useWidgetSelection,
useWidgetDragResize,
} from "utils/hooks/dragResizeHooks";
2020-01-21 07:14:47 +00:00
import { useSelector } from "react-redux";
import { AppState } from "reducers";
2020-02-18 19:56:58 +00:00
import { PropertyPaneReduxState } from "reducers/uiReducers/propertyPaneReducer";
import Resizable from "resizable";
import { isDropZoneOccupied } from "utils/WidgetPropsUtils";
import {
VisibilityContainer,
LeftHandleStyles,
RightHandleStyles,
TopHandleStyles,
BottomHandleStyles,
TopLeftHandleStyles,
TopRightHandleStyles,
BottomLeftHandleStyles,
BottomRightHandleStyles,
} from "./ResizeStyledComponents";
2020-02-18 19:56:58 +00:00
export type ResizableComponentProps = ContainerWidgetProps<WidgetProps> & {
paddingOffset: number;
};
/* eslint-disable react/display-name */
export const ResizableComponent = memo((props: ResizableComponentProps) => {
// Fetch information from the context
const { updateWidget, occupiedSpaces } = useContext(EditorContext);
2020-01-16 11:46:21 +00:00
const { updateDropTargetRows, persistDropTargetRows } = useContext(
DropTargetContext,
);
2020-01-17 12:34:58 +00:00
2020-01-20 09:00:37 +00:00
const showPropertyPane = useShowPropertyPane();
const { selectWidget } = useWidgetSelection();
const { setIsResizing } = useWidgetDragResize();
const selectedWidget = useSelector(
(state: AppState) => state.ui.editor.selectedWidget,
);
const focusedWidget = useSelector(
(state: AppState) => state.ui.editor.focusedWidget,
);
const isDragging = useSelector(
(state: AppState) => state.ui.widgetDragResize.isDragging,
);
2020-02-18 19:56:58 +00:00
const isResizing = useSelector(
(state: AppState) => state.ui.widgetDragResize.isResizing,
);
const propertyPaneState: PropertyPaneReduxState = useSelector(
(state: AppState) => state.ui.propertyPane,
);
2020-01-20 09:00:37 +00:00
const occupiedSpacesBySiblingWidgets =
occupiedSpaces && props.parentId && occupiedSpaces[props.parentId]
? occupiedSpaces[props.parentId]
: undefined;
2020-01-17 12:34:58 +00:00
let maxBottomRowOfChildWidgets: number | undefined;
if (props.type === WidgetTypes.CONTAINER_WIDGET) {
const occupiedSpacesByChildren =
occupiedSpaces && occupiedSpaces[props.widgetId];
maxBottomRowOfChildWidgets = occupiedSpacesByChildren?.reduce(
(prev: number, next) => {
if (next.bottom > prev) return next.bottom;
return prev;
},
0,
);
}
// 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
// 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
2020-02-18 19:56:58 +00:00
const boundingElementClassName = generateClassName(props.parentId);
const possibleBoundingElements = document.getElementsByClassName(
boundingElementClassName,
);
const boundingElement =
possibleBoundingElements.length > 0
? possibleBoundingElements[0]
: undefined;
const boundingElementClientRect = boundingElement
? boundingElement.getBoundingClientRect()
: undefined;
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.
2020-02-18 19:56:58 +00:00
const isColliding = (newDimensions: UIElementSize, position: XYCoord) => {
2020-01-16 11:46:21 +00:00
const bottom =
2020-02-18 19:56:58 +00:00
props.topRow +
position.y / props.parentRowSpace +
newDimensions.height / props.parentRowSpace;
2020-01-16 11:46:21 +00:00
// Make sure to calculate collision IF we don't update the main container's rows
let updated = false;
2020-02-18 19:56:58 +00:00
if (updateDropTargetRows && props.parentId === MAIN_CONTAINER_WIDGET_ID) {
2020-01-16 11:46:21 +00:00
updated = updateDropTargetRows(bottom);
2020-02-18 19:56:58 +00:00
}
const delta: UIElementSize = {
height: newDimensions.height - dimensions.height,
width: newDimensions.width - dimensions.width,
};
const newRowCols: WidgetRowCols | false = computeRowCols(
delta,
position,
props,
);
if (
newRowCols.rightColumn - newRowCols.leftColumn < 1 ||
newRowCols.bottomRow - newRowCols.topRow < 1
) {
return true;
}
2020-02-18 19:56:58 +00:00
if (
boundingElementClientRect &&
newRowCols.rightColumn * props.parentColumnSpace >
boundingElementClientRect.width
) {
return true;
}
if (newRowCols && newRowCols.leftColumn < 0) {
return true;
}
2020-01-16 11:46:21 +00:00
if (!updated) {
2020-02-18 19:56:58 +00:00
if (
// If this is a container widget, the maxBottomRow of child widgets should be one less than the max bottom row of the new row cols
maxBottomRowOfChildWidgets &&
newRowCols &&
props.type === WidgetTypes.CONTAINER_WIDGET &&
newRowCols.bottomRow - newRowCols.topRow - 1 <
maxBottomRowOfChildWidgets
) {
return true;
}
if (
boundingElementClientRect &&
newRowCols.bottomRow * props.parentRowSpace >
boundingElementClientRect.height
) {
return true;
}
if (newRowCols && newRowCols.topRow < 0) {
return true;
2020-01-16 11:46:21 +00:00
}
2019-10-08 06:19:10 +00:00
}
2020-02-18 19:56:58 +00:00
// Check if new row cols are occupied by sibling widgets
return isDropZoneOccupied(
{
left: newRowCols.leftColumn,
top: newRowCols.topRow,
bottom: newRowCols.bottomRow,
right: newRowCols.rightColumn,
},
props.widgetId,
occupiedSpacesBySiblingWidgets,
);
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.
2020-02-18 19:56:58 +00:00
const updateSize = (newDimensions: UIElementSize, position: XYCoord) => {
// Get the difference in size of the widget, before and after resizing.
const delta: UIElementSize = {
2020-02-18 19:56:58 +00:00
height: newDimensions.height - dimensions.height,
width: newDimensions.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.
2020-02-18 19:56:58 +00:00
const newRowCols: WidgetRowCols | false = computeFinalRowCols(
delta,
position,
props,
);
if (newRowCols) {
2020-01-16 11:46:21 +00:00
persistDropTargetRows &&
props.parentId === MAIN_CONTAINER_WIDGET_ID &&
persistDropTargetRows(props.widgetId, newRowCols.bottomRow);
updateWidget &&
updateWidget(WidgetOperations.RESIZE, props.widgetId, newRowCols);
}
// Clear border styles
2020-02-18 19:56:58 +00:00
// setIsColliding && setIsColliding(false);
// Tell the Canvas that we've stopped resizing
2020-01-17 12:34:58 +00:00
// Put it laster in the stack so that other updates like click, are not propagated to the parent container
setTimeout(() => {
setIsResizing && setIsResizing(false);
2020-01-17 12:34:58 +00:00
}, 0);
// 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.
2020-02-18 19:56:58 +00:00
showPropertyPane &&
propertyPaneState.widgetId !== props.widgetId &&
showPropertyPane(props.widgetId, true);
};
const handleResizeStart = () => {
setIsResizing && !isResizing && setIsResizing(true);
selectWidget &&
selectedWidget !== props.widgetId &&
selectWidget(props.widgetId);
showPropertyPane && showPropertyPane(props.widgetId, true);
};
2020-02-18 19:56:58 +00:00
return (
2020-02-18 19:56:58 +00:00
<Resizable
handles={{
left: LeftHandleStyles,
top: TopHandleStyles,
bottom: BottomHandleStyles,
right: RightHandleStyles,
bottomRight: BottomRightHandleStyles,
topLeft: TopLeftHandleStyles,
topRight: TopRightHandleStyles,
bottomLeft: BottomLeftHandleStyles,
}}
2020-02-18 19:56:58 +00:00
componentHeight={dimensions.height}
componentWidth={dimensions.width}
onStart={handleResizeStart}
onStop={updateSize}
snapGrid={{ x: props.parentColumnSpace, y: props.parentRowSpace }}
enable={!isDragging && isWidgetFocused}
isColliding={isColliding}
>
<VisibilityContainer
visible={!!props.isVisible}
padding={props.paddingOffset}
>
{props.children}
</VisibilityContainer>
2020-02-18 19:56:58 +00:00
</Resizable>
);
});
export default ResizableComponent;