PromucFlow_constructor/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx
Ashok Kumar M 0149085bf8
feat: Reflow and Resize while Dragging and Resizing widgets. (#9054)
* resize n reflow rough cut

* removing warnings

* relatively stable changes

* minor bug fix

* reflow relative collision

* working dp cut

* fix for reflow of widgets closer next to each other

* disabling scroll

* Drag with reflow

* reflow fix

* overlap and retracing fix

* On Drop updates.

* bug when no displacement but resize update.

* temp fix for new widget addition.

* reflow bug fixes

* new widget addition bug.

* stop reflow on leave.

* fix corner case overlap

* update bottom row when reflowed widgets go beyond bottom boundary.

* capture mouse positions on enter

* enable container jumps with faster mouse movements.

* reflow only for snap changes.

* restructured reflow Algorithm

* collision check and bug fixes

* undo redo fix for new widget drop

* resizable fix snapRows fix

* directional stability

* self collision fix

* first round of perf fixes

* update bottom row while resizing and resize-reflowing

* performance fix and overlapping fix

* Remove eslint warning

* remove eslint warning

* eslint warning

* can reflowed Drop Indication Stability

* container jumps and force direction on entering canvas

* fixing scroll on resize jitters.

* reflow when jumping into container.

* reflow ux fixes while leaving container

* resizing fixes.

* fixes for edge move.

* restrict container jumps into reflowed containers.

* container jump direction reflow

* checkbox dimensions fix.

* Excess bottom rows not lost post dragging or resizing widgets.

* fixing the after drop css glitch.

* double first move trigger bug fix.

* stop reflow only if reflowing

* stabilize container exit directions

* using acceleration and speed instead of movement covered to restrict reflow.

* fixing modal drops.

* remove warnings.

* reflow resize styles

* moving acceleration and movement logic to a monitoring effect.

* adding beta flag for reflow.

* fixing jest tests

* Adding analytics to beta flag toggle.

* Adding placeholder for reflow beta screens.

* fixing initial load's screen

* few more crashes.

* force close onboarding for the session.

* fixing bugs in reset canvas.

* Beta flag bug fixes.

* fixing bugs.

* restrict reflow screens during onboarding.

* disabling reflow screens in tests.

* code review comments.

* fixing store based specs.

* fixing cypress failures.

* fixing specs.

* code cleanup

* reverting yarn lock changes

* removing onboarding screens.

* more cleanup and function descriptors

* keeping reflow under the hood.

Co-authored-by: rahulramesha <rahul@appsmith.com>
Co-authored-by: Arpit Mohan <arpit@appsmith.com>
2022-01-13 18:51:57 +05:30

158 lines
5.3 KiB
TypeScript

import React, { CSSProperties, ReactNode, useCallback, useMemo } from "react";
import { BaseStyle } from "widgets/BaseWidget";
import { WidgetType, WIDGET_PADDING } from "constants/WidgetConstants";
import { generateClassName } from "utils/generators";
import styled from "styled-components";
import { useClickToSelectWidget } from "utils/hooks/useClickToSelectWidget";
import { usePositionedContainerZIndex } from "utils/hooks/usePositionedContainerZIndex";
import { useSelector } from "react-redux";
import { snipingModeSelector } from "selectors/editorSelectors";
import WidgetFactory from "utils/WidgetFactory";
import { isEqual, memoize } from "lodash";
import { getReflowSelector } from "selectors/widgetReflowSelectors";
import { AppState } from "reducers";
const PositionedWidget = styled.div<{ zIndexOnHover: number }>`
&:hover {
z-index: ${(props) => props.zIndexOnHover} !important;
}
`;
export type PositionedContainerProps = {
style: BaseStyle;
children: ReactNode;
parentId?: string;
widgetId: string;
widgetType: WidgetType;
selected?: boolean;
focused?: boolean;
resizeDisabled?: boolean;
};
export const checkIsDropTarget = memoize(function isDropTarget(
type: WidgetType,
) {
return !!WidgetFactory.widgetConfigMap.get(type)?.isCanvas;
});
export function PositionedContainer(props: PositionedContainerProps) {
const x = props.style.xPosition + (props.style.xPositionUnit || "px");
const y = props.style.yPosition + (props.style.yPositionUnit || "px");
const padding = WIDGET_PADDING;
const clickToSelectWidget = useClickToSelectWidget();
const isSnipingMode = useSelector(snipingModeSelector);
// memoized classname
const containerClassName = useMemo(() => {
return (
generateClassName(props.widgetId) +
" positioned-widget " +
`t--widget-${props.widgetType
.split("_")
.join("")
.toLowerCase()}`
);
}, [props.widgetType, props.widgetId]);
const isDropTarget = checkIsDropTarget(props.widgetType);
const { onHoverZIndex, zIndex } = usePositionedContainerZIndex(
props,
isDropTarget,
);
const reflowSelector = getReflowSelector(props.widgetId);
const reflowedPosition = useSelector(reflowSelector, isEqual);
const dragDetails = useSelector(
(state: AppState) => state.ui.widgetDragResize.dragDetails,
);
const isResizing = useSelector(
(state: AppState) => state.ui.widgetDragResize.isResizing,
);
const isCurrentCanvasReflowing =
(dragDetails && dragDetails.draggedOn === props.parentId) || isResizing;
const containerStyle: CSSProperties = useMemo(() => {
const reflowX = reflowedPosition?.X || 0;
const reflowY = reflowedPosition?.Y || 0;
const reflowWidth = reflowedPosition?.width;
const reflowHeight = reflowedPosition?.height;
const reflowEffected = isCurrentCanvasReflowing && reflowedPosition;
const hasReflowedPosition = reflowEffected && reflowX + reflowY !== 0;
const hasReflowedDimensions =
reflowEffected &&
((reflowHeight && reflowHeight !== props.style.componentHeight) ||
(reflowWidth && reflowWidth !== props.style.componentWidth));
const effectedByReflow = hasReflowedPosition || hasReflowedDimensions;
const dropTargetStyles: CSSProperties =
isDropTarget && effectedByReflow ? { pointerEvents: "none" } : {};
const reflowedPositionStyles: CSSProperties = hasReflowedPosition
? {
transform: `translate(${reflowX}px,${reflowY}px)`,
transition: `transform 100ms linear`,
boxShadow: `0 0 0 1px rgba(104,113,239,0.5)`,
}
: {};
const reflowDimensionsStyles = hasReflowedDimensions
? {
transition: `width 0.1s, height 0.1s`,
boxShadow: `0 0 0 1px rgba(104,113,239,0.5)`,
}
: {};
const styles: CSSProperties = {
position: "absolute",
left: x,
top: y,
height:
reflowHeight ||
props.style.componentHeight + (props.style.heightUnit || "px"),
width:
reflowWidth ||
props.style.componentWidth + (props.style.widthUnit || "px"),
padding: padding + "px",
zIndex,
backgroundColor: "inherit",
...reflowedPositionStyles,
...reflowDimensionsStyles,
...dropTargetStyles,
};
return styles;
}, [
props.style,
isCurrentCanvasReflowing,
onHoverZIndex,
zIndex,
reflowSelector,
reflowedPosition,
]);
const onClickFn = useCallback(
(e) => {
clickToSelectWidget(e, props.widgetId);
},
[props.widgetId, clickToSelectWidget],
);
// TODO: Experimental fix for sniping mode. This should be handled with a single event
const stopEventPropagation = (e: any) => {
!isSnipingMode && e.stopPropagation();
};
return (
<PositionedWidget
className={containerClassName}
data-testid="test-widget"
id={props.widgetId}
key={`positioned-container-${props.widgetId}`}
// Positioned Widget is the top enclosure for all widgets and clicks on/inside the widget should not be propogated/bubbled out of this Container.
onClick={stopEventPropagation}
onClickCapture={onClickFn}
//Before you remove: This is used by property pane to reference the element
style={containerStyle}
zIndexOnHover={onHoverZIndex}
>
{props.children}
</PositionedWidget>
);
}
PositionedContainer.padding = WIDGET_PADDING;
export default PositionedContainer;