feat: Container Jump Improvements and Code for tracking Container Jumps (#12686)
* conatiner Jump Optimization and tracking * Round of mouse positions ans add negative jest test cases * addressing review comments
This commit is contained in:
parent
c7a4ac7353
commit
faaf977728
|
|
@ -74,7 +74,8 @@ export function PositionedContainer(props: PositionedContainerProps) {
|
|||
const reflowWidth = reflowedPosition?.width;
|
||||
const reflowHeight = reflowedPosition?.height;
|
||||
const reflowEffected = isCurrentCanvasReflowing && reflowedPosition;
|
||||
const hasReflowedPosition = reflowEffected && reflowX + reflowY !== 0;
|
||||
const hasReflowedPosition =
|
||||
reflowEffected && (reflowX !== 0 || reflowY !== 0);
|
||||
const hasReflowedDimensions =
|
||||
reflowEffected &&
|
||||
((reflowHeight && reflowHeight !== props.style.componentHeight) ||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
//class to maintain containerJump metrics across containers.
|
||||
export default class ContainerJumpMetrics<T> {
|
||||
private containerJumpValues: T = {} as T;
|
||||
|
||||
public setMetrics(args: T) {
|
||||
this.containerJumpValues = {
|
||||
...args,
|
||||
};
|
||||
}
|
||||
|
||||
public getMetrics() {
|
||||
return {
|
||||
...this.containerJumpValues,
|
||||
};
|
||||
}
|
||||
|
||||
public clearMetrics() {
|
||||
this.containerJumpValues = {} as T;
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ import { stopReflowAction } from "actions/reflowActions";
|
|||
import { DragDetails } from "reducers/uiReducers/dragResizeReducer";
|
||||
import { getIsReflowing } from "selectors/widgetReflowSelectors";
|
||||
import { XYCord } from "./useCanvasDragging";
|
||||
import ContainerJumpMetrics from "./ContainerJumpMetric";
|
||||
|
||||
export interface WidgetDraggingUpdateParams extends WidgetDraggingBlock {
|
||||
updateWidgetParams: WidgetOperationParams;
|
||||
|
|
@ -46,6 +47,28 @@ export type WidgetDraggingBlock = {
|
|||
detachFromLayout?: boolean;
|
||||
};
|
||||
|
||||
const containerJumpMetrics = new ContainerJumpMetrics<{
|
||||
speed?: number;
|
||||
acceleration?: number;
|
||||
movingInto?: string;
|
||||
}>();
|
||||
|
||||
// This method is called on drop,
|
||||
// This method logs the metrics container jump and marks it as successful container jump,
|
||||
// If widget has moves into a container and drops there.
|
||||
const logContainerJumpOnDrop = () => {
|
||||
const { acceleration, movingInto, speed } = containerJumpMetrics.getMetrics();
|
||||
// If it is dropped into a container after jumping, then
|
||||
if (movingInto) {
|
||||
AnalyticsUtil.logEvent("CONTAINER_JUMP", {
|
||||
speed: speed,
|
||||
acceleration: acceleration,
|
||||
isAccidental: false,
|
||||
});
|
||||
}
|
||||
containerJumpMetrics.clearMetrics();
|
||||
};
|
||||
|
||||
export const useBlocksToBeDraggedOnCanvas = ({
|
||||
noPad,
|
||||
snapColumnSpace,
|
||||
|
|
@ -104,6 +127,52 @@ export const useBlocksToBeDraggedOnCanvas = ({
|
|||
const { updateWidget } = useContext(EditorContext);
|
||||
|
||||
const allWidgets = useSelector(getWidgets);
|
||||
|
||||
//This method is called whenever a there is a canvas change.
|
||||
//canvas is the Layer inside the widgets or on main container where widgets are positioned or dragged.
|
||||
//This method records the container jump metrics when a widget moves into a container from main Canvas,
|
||||
// if the widget moves back to the main Canvas then, it is marked as accidental container jump.
|
||||
const logContainerJump = (
|
||||
dropTargetWidgetId: string,
|
||||
dragSpeed?: number,
|
||||
dragAcceleration?: number,
|
||||
) => {
|
||||
//If triggered on the same canvas that it started dragging on return
|
||||
if (!dragDetails.draggedOn || dropTargetWidgetId === dragDetails.draggedOn)
|
||||
return;
|
||||
|
||||
const {
|
||||
acceleration,
|
||||
movingInto,
|
||||
speed,
|
||||
} = containerJumpMetrics.getMetrics();
|
||||
|
||||
// record Only
|
||||
// if it was not previously recorded
|
||||
// if not moving into mainContainer
|
||||
// dragSpeed and dragAcceleration is not undefined
|
||||
if (
|
||||
!movingInto &&
|
||||
dropTargetWidgetId !== MAIN_CONTAINER_WIDGET_ID &&
|
||||
dragSpeed &&
|
||||
dragAcceleration
|
||||
) {
|
||||
containerJumpMetrics.setMetrics({
|
||||
speed: dragSpeed,
|
||||
acceleration: dragAcceleration,
|
||||
movingInto: dropTargetWidgetId,
|
||||
});
|
||||
} // record only for mainContainer jumps,
|
||||
//If it is coming back to main canvas after moving into a container then it is a accidental container jump
|
||||
else if (movingInto && dropTargetWidgetId === MAIN_CONTAINER_WIDGET_ID) {
|
||||
AnalyticsUtil.logEvent("CONTAINER_JUMP", {
|
||||
speed: speed,
|
||||
acceleration: acceleration,
|
||||
isAccidental: true,
|
||||
});
|
||||
containerJumpMetrics.clearMetrics();
|
||||
}
|
||||
};
|
||||
const getDragCenterSpace = () => {
|
||||
if (dragCenter && dragCenter.widgetId) {
|
||||
// Dragging by widget
|
||||
|
|
@ -203,6 +272,7 @@ export const useBlocksToBeDraggedOnCanvas = ({
|
|||
drawingBlocks: WidgetDraggingBlock[],
|
||||
reflowedPositionsUpdatesWidgets: OccupiedSpace[],
|
||||
) => {
|
||||
logContainerJumpOnDrop();
|
||||
const reflowedBlocks: WidgetDraggingBlock[] = reflowedPositionsUpdatesWidgets.map(
|
||||
(each) => {
|
||||
const widget = allWidgets[each.id];
|
||||
|
|
@ -412,6 +482,7 @@ export const useBlocksToBeDraggedOnCanvas = ({
|
|||
isNewWidgetInitialTargetCanvas,
|
||||
isResizing,
|
||||
lastDraggedCanvas,
|
||||
logContainerJump,
|
||||
occSpaces,
|
||||
draggingSpaces,
|
||||
onDrop,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { ReflowInterface, useReflow } from "utils/hooks/useReflow";
|
|||
import {
|
||||
getDraggingSpacesFromBlocks,
|
||||
getDropZoneOffsets,
|
||||
getMousePositionsOnCanvas,
|
||||
noCollision,
|
||||
} from "utils/WidgetPropsUtils";
|
||||
import {
|
||||
|
|
@ -28,11 +29,22 @@ import {
|
|||
WidgetDraggingBlock,
|
||||
} from "./useBlocksToBeDraggedOnCanvas";
|
||||
import { useCanvasDragToScroll } from "./useCanvasDragToScroll";
|
||||
import ContainerJumpMetrics from "./ContainerJumpMetric";
|
||||
|
||||
export interface XYCord {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
const CONTAINER_JUMP_ACC_THRESHOLD = 8000;
|
||||
const CONTAINER_JUMP_SPEED_THRESHOLD = 800;
|
||||
|
||||
//Since useCanvasDragging's Instance changes during container jump, metrics is stored outside
|
||||
const containerJumpThresholdMetrics = new ContainerJumpMetrics<{
|
||||
speed?: number;
|
||||
acceleration?: number;
|
||||
}>();
|
||||
|
||||
export const useCanvasDragging = (
|
||||
slidingArenaRef: React.RefObject<HTMLDivElement>,
|
||||
stickyCanvasRef: React.RefObject<HTMLCanvasElement>,
|
||||
|
|
@ -62,6 +74,7 @@ export const useCanvasDragging = (
|
|||
isNewWidgetInitialTargetCanvas,
|
||||
isResizing,
|
||||
lastDraggedCanvas,
|
||||
logContainerJump,
|
||||
occSpaces,
|
||||
onDrop,
|
||||
parentDiff,
|
||||
|
|
@ -199,7 +212,13 @@ export const useCanvasDragging = (
|
|||
movementLimitMap?: MovementLimitMap;
|
||||
bottomMostRow: number;
|
||||
movementMap: ReflowedSpaceMap;
|
||||
} = { movementLimitMap: {}, bottomMostRow: 0, movementMap: {} };
|
||||
isIdealToJumpContainer: boolean;
|
||||
} = {
|
||||
movementLimitMap: {},
|
||||
bottomMostRow: 0,
|
||||
movementMap: {},
|
||||
isIdealToJumpContainer: false,
|
||||
};
|
||||
let lastMousePosition = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
|
@ -295,6 +314,13 @@ export const useCanvasDragging = (
|
|||
relativeStartPoints.top || defaultHandlePositions.top;
|
||||
}
|
||||
if (!isCurrentDraggedCanvas) {
|
||||
//Called when canvas Changes
|
||||
const {
|
||||
acceleration,
|
||||
speed,
|
||||
} = containerJumpThresholdMetrics.getMetrics();
|
||||
logContainerJump(widgetId, speed, acceleration);
|
||||
containerJumpThresholdMetrics.clearMetrics();
|
||||
// we can just use canvasIsDragging but this is needed to render the relative DragLayerComponent
|
||||
setDraggingCanvas(widgetId);
|
||||
}
|
||||
|
|
@ -316,18 +342,12 @@ export const useCanvasDragging = (
|
|||
};
|
||||
|
||||
const canReflowForCurrentMouseMove = () => {
|
||||
const {
|
||||
maxNegativeAcc,
|
||||
maxPositiveAcc,
|
||||
maxSpeed,
|
||||
prevAcceleration,
|
||||
prevSpeed,
|
||||
} = mouseAttributesRef.current;
|
||||
const limit = Math.abs(
|
||||
prevAcceleration < 0 ? maxNegativeAcc : maxPositiveAcc,
|
||||
);
|
||||
const { prevAcceleration, prevSpeed } = mouseAttributesRef.current;
|
||||
const acceleration = Math.abs(prevAcceleration);
|
||||
return acceleration < limit / 10 || prevSpeed < maxSpeed / 10;
|
||||
return (
|
||||
acceleration < CONTAINER_JUMP_ACC_THRESHOLD ||
|
||||
prevSpeed < CONTAINER_JUMP_SPEED_THRESHOLD
|
||||
);
|
||||
};
|
||||
const getMouseMoveDirection = (event: any) => {
|
||||
if (lastMousePosition) {
|
||||
|
|
@ -366,7 +386,9 @@ export const useCanvasDragging = (
|
|||
const canReflowBasedOnMouseSpeed = canReflowForCurrentMouseMove();
|
||||
const isReflowing = !isEmpty(currentReflowParams.movementMap);
|
||||
const canReflow =
|
||||
reflowEnabled && !currentRectanglesToDraw[0].detachFromLayout;
|
||||
reflowEnabled &&
|
||||
!currentRectanglesToDraw[0].detachFromLayout &&
|
||||
!dropDisabled;
|
||||
const currentBlock = currentRectanglesToDraw[0];
|
||||
const [leftColumn, topRow] = getDropZoneOffsets(
|
||||
snapColumnSpace,
|
||||
|
|
@ -380,6 +402,7 @@ export const useCanvasDragging = (
|
|||
y: 0,
|
||||
},
|
||||
);
|
||||
const mousePosition = getMousePositionsOnCanvas(e, gridProps);
|
||||
const needsReflow = !(
|
||||
lastSnappedPosition.leftColumn === leftColumn &&
|
||||
lastSnappedPosition.topRow === topRow
|
||||
|
|
@ -408,11 +431,28 @@ export const useCanvasDragging = (
|
|||
!canReflowBasedOnMouseSpeed,
|
||||
firstMove,
|
||||
immediateExitContainer,
|
||||
mousePosition,
|
||||
);
|
||||
}
|
||||
|
||||
if (isReflowing) {
|
||||
const { movementLimitMap } = currentReflowParams;
|
||||
const {
|
||||
isIdealToJumpContainer,
|
||||
movementLimitMap,
|
||||
} = currentReflowParams;
|
||||
|
||||
if (isIdealToJumpContainer) {
|
||||
const {
|
||||
prevAcceleration,
|
||||
prevSpeed: speed,
|
||||
} = mouseAttributesRef.current;
|
||||
const acceleration = Math.abs(prevAcceleration);
|
||||
containerJumpThresholdMetrics.setMetrics({
|
||||
speed,
|
||||
acceleration,
|
||||
});
|
||||
}
|
||||
|
||||
for (const block of currentRectanglesToDraw) {
|
||||
const isWithinParentBoundaries = noCollision(
|
||||
{ x: block.left, y: block.top },
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@ export type EventName =
|
|||
| "GS_REGENERATE_SSH_KEY_MORE_CLICK"
|
||||
| "GS_SWITCH_BRANCH"
|
||||
| "REFLOW_BETA_FLAG"
|
||||
| "CONTAINER_JUMP"
|
||||
| "CONNECT_GIT_CLICK"
|
||||
| "REPO_URL_EDIT"
|
||||
| "GENERATE_KEY_BUTTON_CLICK"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
|
|||
import {
|
||||
extractCurrentDSL,
|
||||
getDraggingSpacesFromBlocks,
|
||||
getMousePositionsOnCanvas,
|
||||
} from "./WidgetPropsUtils";
|
||||
import { WidgetDraggingBlock } from "pages/common/CanvasArenas/hooks/useBlocksToBeDraggedOnCanvas";
|
||||
|
||||
|
|
@ -70,7 +71,42 @@ describe("WidgetProps tests", () => {
|
|||
),
|
||||
).toEqual(draggingSpaces);
|
||||
});
|
||||
|
||||
it("getMousePositionsOnCanvas should Return Mouse Position relative to Canvas", () => {
|
||||
const gridProps = {
|
||||
parentColumnSpace: 10,
|
||||
parentRowSpace: 10,
|
||||
maxGridColumns: 64,
|
||||
};
|
||||
const mouseEvent = ({
|
||||
offsetX: 500,
|
||||
offsetY: 600,
|
||||
} as unknown) as MouseEvent;
|
||||
expect(getMousePositionsOnCanvas(mouseEvent, gridProps)).toEqual({
|
||||
id: "mouse",
|
||||
top: 59,
|
||||
left: 49,
|
||||
bottom: 60,
|
||||
right: 50,
|
||||
});
|
||||
});
|
||||
it("getMousePositionsOnCanvas should even return negative Mouse Position relative to Canvas", () => {
|
||||
const gridProps = {
|
||||
parentColumnSpace: 10,
|
||||
parentRowSpace: 10,
|
||||
maxGridColumns: 64,
|
||||
};
|
||||
const mouseEvent = ({
|
||||
offsetX: 2,
|
||||
offsetY: 5,
|
||||
} as unknown) as MouseEvent;
|
||||
expect(getMousePositionsOnCanvas(mouseEvent, gridProps)).toEqual({
|
||||
id: "mouse",
|
||||
top: -1,
|
||||
left: -1,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
});
|
||||
});
|
||||
it("it checks if array to object migration functions for chart widget ", () => {
|
||||
const input = {
|
||||
type: "CANVAS_WIDGET",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@ import {
|
|||
WidgetOperations,
|
||||
WidgetProps,
|
||||
} from "widgets/BaseWidget";
|
||||
import { GridDefaults, RenderMode } from "constants/WidgetConstants";
|
||||
import {
|
||||
CONTAINER_GRID_PADDING,
|
||||
GridDefaults,
|
||||
RenderMode,
|
||||
WIDGET_PADDING,
|
||||
} from "constants/WidgetConstants";
|
||||
import { snapToGrid } from "./helpers";
|
||||
import { OccupiedSpace } from "constants/CanvasEditorConstants";
|
||||
import defaultTemplate from "templates/default";
|
||||
|
|
@ -16,6 +21,7 @@ import { DSLWidget } from "widgets/constants";
|
|||
import { WidgetDraggingBlock } from "pages/common/CanvasArenas/hooks/useBlocksToBeDraggedOnCanvas";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useCanvasDragging";
|
||||
import { ContainerWidgetProps } from "widgets/ContainerWidget/widget";
|
||||
import { GridProps } from "reflow/reflowTypes";
|
||||
|
||||
export type WidgetOperationParams = {
|
||||
operation: WidgetOperation;
|
||||
|
|
@ -96,6 +102,28 @@ export const getDropZoneOffsets = (
|
|||
);
|
||||
};
|
||||
|
||||
export const getMousePositionsOnCanvas = (
|
||||
e: MouseEvent,
|
||||
gridProps: GridProps,
|
||||
) => {
|
||||
const mouseTop = Math.floor(
|
||||
(e.offsetY - CONTAINER_GRID_PADDING - WIDGET_PADDING) /
|
||||
gridProps.parentRowSpace,
|
||||
);
|
||||
const mouseLeft = Math.floor(
|
||||
(e.offsetX - CONTAINER_GRID_PADDING - WIDGET_PADDING) /
|
||||
gridProps.parentColumnSpace,
|
||||
);
|
||||
|
||||
return {
|
||||
id: "mouse",
|
||||
top: mouseTop,
|
||||
left: mouseLeft,
|
||||
bottom: mouseTop + 1,
|
||||
right: mouseLeft + 1,
|
||||
};
|
||||
};
|
||||
|
||||
export const areIntersecting = (r1: Rect, r2: Rect) => {
|
||||
return !(
|
||||
r2.left >= r1.right ||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { getBottomRowAfterReflow } from "utils/reflowHookUtils";
|
|||
import { checkIsDropTarget } from "components/designSystems/appsmith/PositionedContainer";
|
||||
import { getIsReflowing } from "selectors/widgetReflowSelectors";
|
||||
import { AppState } from "reducers";
|
||||
import { areIntersecting } from "utils/WidgetPropsUtils";
|
||||
|
||||
type WidgetCollidingSpace = CollidingSpace & {
|
||||
type: string;
|
||||
|
|
@ -45,10 +46,12 @@ export interface ReflowInterface {
|
|||
shouldSkipContainerReflow?: boolean,
|
||||
forceDirection?: boolean,
|
||||
immediateExitContainer?: string,
|
||||
mousePosition?: OccupiedSpace,
|
||||
): {
|
||||
movementLimitMap?: MovementLimitMap;
|
||||
movementMap: ReflowedSpaceMap;
|
||||
bottomMostRow: number;
|
||||
isIdealToJumpContainer: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +98,7 @@ export const useReflow = (
|
|||
shouldSkipContainerReflow = false,
|
||||
forceDirection = false,
|
||||
immediateExitContainer?: string,
|
||||
mousePosition?: OccupiedSpace,
|
||||
) {
|
||||
const prevReflowState: PrevReflowState = {
|
||||
prevSpacesMap: getSpacesMapFromArray(prevPositions.current),
|
||||
|
|
@ -103,6 +107,9 @@ export const useReflow = (
|
|||
prevSecondOrderCollisionMap: prevSecondOrderCollisionMap.current,
|
||||
};
|
||||
|
||||
// To track container jumps
|
||||
let isIdealToJumpContainer = false;
|
||||
|
||||
const {
|
||||
collidingSpaceMap,
|
||||
movementLimitMap,
|
||||
|
|
@ -140,7 +147,12 @@ export const useReflow = (
|
|||
] as WidgetCollidingSpace[];
|
||||
|
||||
for (const collidingSpace of collidingSpaces) {
|
||||
if (checkIsDropTarget(collidingSpace.type)) {
|
||||
if (
|
||||
checkIsDropTarget(collidingSpace.type) &&
|
||||
mousePosition &&
|
||||
areIntersecting(mousePosition, collidingSpace)
|
||||
) {
|
||||
isIdealToJumpContainer = true;
|
||||
correctedMovementMap = {};
|
||||
}
|
||||
}
|
||||
|
|
@ -169,6 +181,7 @@ export const useReflow = (
|
|||
movementLimitMap,
|
||||
movementMap: correctedMovementMap,
|
||||
bottomMostRow,
|
||||
isIdealToJumpContainer,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user