2020-03-04 08:10:40 +00:00
|
|
|
import React, { useContext, useMemo } from "react";
|
2019-10-02 19:42:25 +00:00
|
|
|
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";
|
2019-11-13 07:00:25 +00:00
|
|
|
import { EditorContext } from "components/editorComponents/EditorContextProvider";
|
|
|
|
|
import { generateClassName } from "utils/generators";
|
2020-01-16 11:46:21 +00:00
|
|
|
import { DropTargetContext } from "./DropTargetComponent";
|
2019-11-13 07:00:25 +00:00
|
|
|
import {
|
|
|
|
|
UIElementSize,
|
2020-02-18 19:56:58 +00:00
|
|
|
computeFinalRowCols,
|
|
|
|
|
computeRowCols,
|
2019-11-13 07:00:25 +00:00
|
|
|
} 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 Resizable from "resizable";
|
|
|
|
|
import { isDropZoneOccupied } from "utils/WidgetPropsUtils";
|
2020-02-19 10:00:03 +00:00
|
|
|
import {
|
|
|
|
|
VisibilityContainer,
|
|
|
|
|
LeftHandleStyles,
|
|
|
|
|
RightHandleStyles,
|
|
|
|
|
TopHandleStyles,
|
|
|
|
|
BottomHandleStyles,
|
|
|
|
|
TopLeftHandleStyles,
|
|
|
|
|
TopRightHandleStyles,
|
|
|
|
|
BottomLeftHandleStyles,
|
|
|
|
|
BottomRightHandleStyles,
|
|
|
|
|
} from "./ResizeStyledComponents";
|
2020-03-03 07:02:53 +00:00
|
|
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
2020-02-18 19:56:58 +00:00
|
|
|
|
|
|
|
|
export type ResizableComponentProps = ContainerWidgetProps<WidgetProps> & {
|
|
|
|
|
paddingOffset: number;
|
|
|
|
|
};
|
|
|
|
|
|
2019-11-13 07:00:25 +00:00
|
|
|
/* eslint-disable react/display-name */
|
2020-03-04 08:10:40 +00:00
|
|
|
export const ResizableComponent = (props: ResizableComponentProps) => {
|
2019-11-13 07:00:25 +00:00
|
|
|
// 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,
|
|
|
|
|
);
|
2019-11-13 07:00:25 +00:00
|
|
|
const occupiedSpacesBySiblingWidgets =
|
|
|
|
|
occupiedSpaces && props.parentId && occupiedSpaces[props.parentId]
|
|
|
|
|
? occupiedSpaces[props.parentId]
|
|
|
|
|
: undefined;
|
2020-01-17 12:34:58 +00:00
|
|
|
|
2020-03-04 08:10:40 +00:00
|
|
|
const maxBottomRowOfChildWidgets: number | undefined = useMemo(() => {
|
|
|
|
|
if (props.type === WidgetTypes.CONTAINER_WIDGET) {
|
|
|
|
|
const occupiedSpacesByChildren =
|
|
|
|
|
occupiedSpaces && occupiedSpaces[props.widgetId];
|
|
|
|
|
return occupiedSpacesByChildren?.reduce((prev: number, next) => {
|
2020-01-17 12:34:58 +00:00
|
|
|
if (next.bottom > prev) return next.bottom;
|
|
|
|
|
return prev;
|
2020-03-04 08:10:40 +00:00
|
|
|
}, 0);
|
|
|
|
|
}
|
|
|
|
|
}, [occupiedSpaces, props.type, props.widgetId]);
|
2020-01-17 12:34:58 +00:00
|
|
|
|
2019-11-13 07:00:25 +00:00
|
|
|
// 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
|
|
|
|
2019-11-13 07:00:25 +00:00
|
|
|
// Calculate the dimensions of the widget,
|
|
|
|
|
// The ResizableContainer's size prop is controlled
|
|
|
|
|
const dimensions: UIElementSize = {
|
2020-03-06 09:33:20 +00:00
|
|
|
width:
|
|
|
|
|
(props.rightColumn - props.leftColumn) * props.parentColumnSpace -
|
|
|
|
|
2 * props.paddingOffset,
|
|
|
|
|
height:
|
|
|
|
|
(props.bottomRow - props.topRow) * props.parentRowSpace -
|
|
|
|
|
2 * props.paddingOffset,
|
2019-11-13 07:00:25 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
2019-11-13 07:00:25 +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,
|
|
|
|
|
);
|
|
|
|
|
|
2020-02-19 10:00:03 +00:00
|
|
|
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
|
|
|
};
|
2019-11-13 07:00:25 +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) => {
|
2019-11-13 07:00:25 +00:00
|
|
|
// 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,
|
2019-11-13 07:00:25 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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(
|
2019-11-13 07:00:25 +00:00
|
|
|
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);
|
2019-11-13 07:00:25 +00:00
|
|
|
updateWidget &&
|
|
|
|
|
updateWidget(WidgetOperations.RESIZE, props.widgetId, newRowCols);
|
|
|
|
|
}
|
|
|
|
|
// Tell the Canvas that we've stopped resizing
|
2020-03-04 08:10:40 +00:00
|
|
|
// Put it later in the stack so that other updates like click, are not propagated to the parent container
|
2020-01-07 12:28:58 +00:00
|
|
|
setTimeout(() => {
|
|
|
|
|
setIsResizing && setIsResizing(false);
|
2020-01-17 12:34:58 +00:00
|
|
|
}, 0);
|
2019-11-13 07:00:25 +00:00
|
|
|
// Tell the Canvas to put the focus back to this widget
|
|
|
|
|
// By setting the focus, we enable the control buttons on the widget
|
2020-03-04 08:10:40 +00:00
|
|
|
selectWidget &&
|
|
|
|
|
selectedWidget !== props.widgetId &&
|
|
|
|
|
selectWidget(props.widgetId);
|
2019-11-13 07:00:25 +00:00
|
|
|
// Let the propertypane show.
|
|
|
|
|
// The propertypane decides whether to show itself, based on
|
|
|
|
|
// whether it was showing when the widget resize started.
|
2020-03-04 08:10:40 +00:00
|
|
|
showPropertyPane && showPropertyPane(props.widgetId, true);
|
2020-03-03 07:02:53 +00:00
|
|
|
|
|
|
|
|
AnalyticsUtil.logEvent("WIDGET_RESIZE_END", {
|
|
|
|
|
widgetName: props.widgetName,
|
|
|
|
|
widgetType: props.type,
|
|
|
|
|
startHeight: dimensions.height,
|
|
|
|
|
startWidth: dimensions.width,
|
|
|
|
|
endHeight: newDimensions.height,
|
|
|
|
|
endWidth: newDimensions.width,
|
|
|
|
|
});
|
2020-02-18 19:56:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleResizeStart = () => {
|
|
|
|
|
setIsResizing && !isResizing && setIsResizing(true);
|
|
|
|
|
selectWidget &&
|
|
|
|
|
selectedWidget !== props.widgetId &&
|
|
|
|
|
selectWidget(props.widgetId);
|
2020-01-07 12:28:58 +00:00
|
|
|
showPropertyPane && showPropertyPane(props.widgetId, true);
|
2020-03-03 07:02:53 +00:00
|
|
|
AnalyticsUtil.logEvent("WIDGET_RESIZE_START", {
|
|
|
|
|
widgetName: props.widgetName,
|
|
|
|
|
widgetType: props.type,
|
|
|
|
|
});
|
2019-09-22 20:25:05 +00:00
|
|
|
};
|
2020-02-18 19:56:58 +00:00
|
|
|
|
2019-09-22 20:25:05 +00:00
|
|
|
return (
|
2020-02-18 19:56:58 +00:00
|
|
|
<Resizable
|
|
|
|
|
handles={{
|
|
|
|
|
left: LeftHandleStyles,
|
|
|
|
|
top: TopHandleStyles,
|
|
|
|
|
bottom: BottomHandleStyles,
|
|
|
|
|
right: RightHandleStyles,
|
|
|
|
|
bottomRight: BottomRightHandleStyles,
|
2020-02-19 10:00:03 +00:00
|
|
|
topLeft: TopLeftHandleStyles,
|
|
|
|
|
topRight: TopRightHandleStyles,
|
|
|
|
|
bottomLeft: BottomLeftHandleStyles,
|
2019-09-25 21:38:03 +00:00
|
|
|
}}
|
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}
|
2019-09-22 20:25:05 +00:00
|
|
|
>
|
2020-02-18 20:35:52 +00:00
|
|
|
<VisibilityContainer
|
|
|
|
|
visible={!!props.isVisible}
|
|
|
|
|
padding={props.paddingOffset}
|
|
|
|
|
>
|
|
|
|
|
{props.children}
|
|
|
|
|
</VisibilityContainer>
|
2020-02-18 19:56:58 +00:00
|
|
|
</Resizable>
|
2019-09-22 20:25:05 +00:00
|
|
|
);
|
2020-03-04 08:10:40 +00:00
|
|
|
};
|
2019-09-22 20:25:05 +00:00
|
|
|
|
|
|
|
|
export default ResizableComponent;
|