feat: drag n drop and Container jump enhancements (#19047)
## Description This PR includes Changes for Drag and drop Improvements, That includes, - Resizing dragging widgets along Container edges. - Initially resize widgets against Container/Droptarget widgets. - While holding close to Container/Droptarget widgets for half a second, start to reflow the widget. Fixes #19139 Fixes #12892 Media https://user-images.githubusercontent.com/71900764/209154834-66acecbb-2df8-4598-86d5-4fe7843dd21b.mp4 ## Type of change - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) ## How Has This Been Tested? - Manual - Jest ### Test Plan > Add Testsmith test cases links that relate to this PR ### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) ## Checklist: ### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag ### QA activity: - [ ] Test plan has been approved by relevant developers - [ ] Test plan has been peer reviewed by QA - [ ] Cypress test cases have been added and approved by either SDET or manual QA - [ ] Organized project review call with relevant stakeholders after Round 1/2 of QA - [ ] Added Test Plan Approved label after reveiwing all Cypress test
This commit is contained in:
parent
00019fc0a7
commit
717b2c1610
|
|
@ -90,8 +90,8 @@ Object.entries(widgetsToTest).forEach(([widgetSelector, testConfig], index) => {
|
|||
configureApi();
|
||||
}
|
||||
ee.PinUnpinEntityExplorer(false);
|
||||
ee.DragDropWidgetNVerify(widgetSelector, 100, 200);
|
||||
ee.DragDropWidgetNVerify(WIDGET.BUTTON, 400, 200);
|
||||
ee.DragDropWidgetNVerify(widgetSelector, 300, 200);
|
||||
ee.DragDropWidgetNVerify(WIDGET.BUTTON, 600, 200);
|
||||
//ee.SelectEntityByName(WIDGET.BUTTONNAME("1"));
|
||||
// Set onClick action, storing value
|
||||
propPane.EnterJSContext(
|
||||
|
|
@ -99,7 +99,7 @@ Object.entries(widgetsToTest).forEach(([widgetSelector, testConfig], index) => {
|
|||
`{{storeValue('textPayloadOnSubmit',${testConfig.widgetPrefixName}1.text); FirstAPI.run({ value: ${testConfig.widgetPrefixName}1.text })}}`,
|
||||
);
|
||||
|
||||
ee.DragDropWidgetNVerify(WIDGET.TEXT, 300, 300);
|
||||
ee.DragDropWidgetNVerify(WIDGET.TEXT, 500, 300);
|
||||
//ee.SelectEntityByName(WIDGET.TEXTNAME("1"));
|
||||
// Display the bound store value
|
||||
propPane.UpdatePropertyFieldValue(
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ describe("Guided Tour", function() {
|
|||
cy.get(guidedTourLocators.successButton).click();
|
||||
// Step 6: Drag and drop a widget
|
||||
cy.dragAndDropToCanvas("buttonwidget", {
|
||||
x: 700,
|
||||
y: 400,
|
||||
x: 800,
|
||||
y: 750,
|
||||
});
|
||||
cy.get(guidedTourLocators.successButton).click();
|
||||
cy.get(guidedTourLocators.infoButton).click();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ describe("Divider Widget Functionality", function() {
|
|||
|
||||
it("Add new Divider", () => {
|
||||
cy.get(explorer.addWidget).click();
|
||||
cy.dragAndDropToCanvas("dividerwidget", { x: 320, y: 300 });
|
||||
cy.dragAndDropToCanvas("dividerwidget", { x: 320, y: 200 });
|
||||
cy.get(".t--divider-widget").should("exist");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
ReduxActionTypes,
|
||||
} from "@appsmith/constants/ReduxActionConstants";
|
||||
import { SelectedArenaDimensions } from "pages/common/CanvasArenas/CanvasSelectionArena";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useCanvasDragging";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useRenderBlocksOnCanvas";
|
||||
|
||||
export const setCanvasSelectionFromEditor = (
|
||||
start: boolean,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
|
|||
import { focusWidget } from "actions/widgetActions";
|
||||
import { GridDefaults } from "constants/WidgetConstants";
|
||||
import { DropTargetContext } from "./DropTargetComponent";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useCanvasDragging";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useRenderBlocksOnCanvas";
|
||||
import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils";
|
||||
import { getParentToOpenSelector } from "selectors/widgetSelectors";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { WidgetProps, WidgetRowCols } from "widgets/BaseWidget";
|
||||
import { GridDefaults } from "constants/WidgetConstants";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useCanvasDragging";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useRenderBlocksOnCanvas";
|
||||
import { ReflowDirection } from "reflow/reflowTypes";
|
||||
|
||||
export type UIElementSize = { height: number; width: number };
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export type WidgetSpace = {
|
|||
id: string;
|
||||
type: string;
|
||||
parentId?: string;
|
||||
isDropTarget?: boolean;
|
||||
fixedHeight?: number;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { getNearestParentCanvas } from "utils/generators";
|
||||
import { useCanvasDragToScroll } from "./hooks/useCanvasDragToScroll";
|
||||
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
|
||||
import { XYCord } from "./hooks/useCanvasDragging";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useRenderBlocksOnCanvas";
|
||||
import { theme } from "constants/DefaultTheme";
|
||||
import { getIsDraggingForSelection } from "selectors/canvasSelectors";
|
||||
import { StickyCanvasArena } from "./StickyCanvasArena";
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
//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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,392 @@
|
|||
import { GridDefaults } from "constants/WidgetConstants";
|
||||
import {
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
ReflowDirection,
|
||||
VERTICAL_RESIZE_MIN_LIMIT,
|
||||
} from "reflow/reflowTypes";
|
||||
import {
|
||||
getEdgeDirection,
|
||||
getMoveDirection,
|
||||
getReflowedSpaces,
|
||||
modifyBlockDimension,
|
||||
modifyDrawingRectangles,
|
||||
updateRectanglesPostReflow,
|
||||
} from "./canvasDraggingUtils";
|
||||
|
||||
describe("test canvasDraggingUtils Methods", () => {
|
||||
describe("test getEdgeDirection method", () => {
|
||||
it("should return RIGHT if closest to left edge", () => {
|
||||
expect(getEdgeDirection(5, 10, 100, ReflowDirection.UNSET)).toEqual(
|
||||
ReflowDirection.RIGHT,
|
||||
);
|
||||
});
|
||||
it("should return BOTTOM if closest to left edge", () => {
|
||||
expect(getEdgeDirection(10, 5, 100, ReflowDirection.UNSET)).toEqual(
|
||||
ReflowDirection.BOTTOM,
|
||||
);
|
||||
});
|
||||
it("should return LEFT if closest to left edge", () => {
|
||||
expect(getEdgeDirection(95, 10, 100, ReflowDirection.UNSET)).toEqual(
|
||||
ReflowDirection.LEFT,
|
||||
);
|
||||
});
|
||||
it("should return current direction if width is undefined", () => {
|
||||
expect(getEdgeDirection(5, 10, undefined, ReflowDirection.UNSET)).toEqual(
|
||||
ReflowDirection.UNSET,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("test getReflowedSpaces method, should return reflowed spaces", () => {
|
||||
const occupiedSpace = {
|
||||
id: "id",
|
||||
left: 10,
|
||||
top: 10,
|
||||
right: 50,
|
||||
bottom: 70,
|
||||
};
|
||||
|
||||
const reflowingWidgets = {
|
||||
id: {
|
||||
X: 30,
|
||||
Y: 40,
|
||||
width: 300,
|
||||
height: 500,
|
||||
},
|
||||
};
|
||||
|
||||
const reflowedSpace = {
|
||||
id: "id",
|
||||
left: 13,
|
||||
top: 14,
|
||||
right: 43,
|
||||
bottom: 64,
|
||||
};
|
||||
|
||||
expect(getReflowedSpaces(occupiedSpace, reflowingWidgets, 10, 10)).toEqual(
|
||||
reflowedSpace,
|
||||
);
|
||||
});
|
||||
|
||||
it("test modifyDrawingRectangles method, should return widgetDraggingBlock with dimensions of the space widget", () => {
|
||||
const drawingRectangles = {
|
||||
left: 104,
|
||||
top: 102,
|
||||
width: 600,
|
||||
height: 900,
|
||||
columnWidth: 60,
|
||||
rowHeight: 90,
|
||||
widgetId: "id",
|
||||
isNotColliding: true,
|
||||
};
|
||||
const spaceMap = {
|
||||
id: {
|
||||
left: 25,
|
||||
top: 30,
|
||||
right: 65,
|
||||
bottom: 80,
|
||||
id: "id",
|
||||
},
|
||||
};
|
||||
const modifiedRectangle = {
|
||||
left: 254,
|
||||
top: 302,
|
||||
width: 400,
|
||||
height: 500,
|
||||
columnWidth: 40,
|
||||
rowHeight: 50,
|
||||
widgetId: "id",
|
||||
isNotColliding: true,
|
||||
};
|
||||
|
||||
expect(
|
||||
modifyDrawingRectangles([drawingRectangles], spaceMap, 10, 10),
|
||||
).toEqual([modifiedRectangle]);
|
||||
});
|
||||
|
||||
describe("test getMoveDirection method", () => {
|
||||
const prevPosition = {
|
||||
id: "id",
|
||||
left: 10,
|
||||
top: 20,
|
||||
right: 30,
|
||||
bottom: 40,
|
||||
};
|
||||
|
||||
it("should return RIGHT when moved to Right", () => {
|
||||
const currentPosition = {
|
||||
id: "id",
|
||||
left: 11,
|
||||
top: 20,
|
||||
right: 31,
|
||||
bottom: 40,
|
||||
};
|
||||
expect(
|
||||
getMoveDirection(prevPosition, currentPosition, ReflowDirection.UNSET),
|
||||
).toEqual(ReflowDirection.RIGHT);
|
||||
});
|
||||
it("should return BOTTOM when moved to bottom", () => {
|
||||
const currentPosition = {
|
||||
id: "id",
|
||||
left: 10,
|
||||
top: 21,
|
||||
right: 30,
|
||||
bottom: 41,
|
||||
};
|
||||
expect(
|
||||
getMoveDirection(prevPosition, currentPosition, ReflowDirection.UNSET),
|
||||
).toEqual(ReflowDirection.BOTTOM);
|
||||
});
|
||||
it("should return LEFT when moved to left", () => {
|
||||
const currentPosition = {
|
||||
id: "id",
|
||||
left: 9,
|
||||
top: 20,
|
||||
right: 29,
|
||||
bottom: 40,
|
||||
};
|
||||
expect(
|
||||
getMoveDirection(prevPosition, currentPosition, ReflowDirection.UNSET),
|
||||
).toEqual(ReflowDirection.LEFT);
|
||||
});
|
||||
it("should return TOP when moved to top", () => {
|
||||
const currentPosition = {
|
||||
id: "id",
|
||||
left: 10,
|
||||
top: 19,
|
||||
right: 30,
|
||||
bottom: 39,
|
||||
};
|
||||
expect(
|
||||
getMoveDirection(prevPosition, currentPosition, ReflowDirection.UNSET),
|
||||
).toEqual(ReflowDirection.TOP);
|
||||
});
|
||||
});
|
||||
|
||||
describe("test modifyBlockDimension method", () => {
|
||||
it("should return resized dragging blocks while colliding with canvas edges, for top Left", () => {
|
||||
const draggingBlock = {
|
||||
left: -300,
|
||||
top: -700,
|
||||
width: 600,
|
||||
height: 900,
|
||||
columnWidth: 60,
|
||||
rowHeight: 90,
|
||||
widgetId: "id",
|
||||
isNotColliding: true,
|
||||
};
|
||||
const modifiedBlock = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 300,
|
||||
height: 200,
|
||||
columnWidth: 30,
|
||||
rowHeight: 20,
|
||||
widgetId: "id",
|
||||
isNotColliding: true,
|
||||
};
|
||||
expect(modifyBlockDimension(draggingBlock, 10, 10, 100, true)).toEqual(
|
||||
modifiedBlock,
|
||||
);
|
||||
});
|
||||
|
||||
it("should return resized dragging blocks while colliding with canvas edges to it's limits, for top Left", () => {
|
||||
const draggingBlock = {
|
||||
left: -300,
|
||||
top: -700,
|
||||
width: 310,
|
||||
height: 720,
|
||||
columnWidth: 31,
|
||||
rowHeight: 72,
|
||||
widgetId: "id",
|
||||
isNotColliding: true,
|
||||
};
|
||||
const modifiedBlock = {
|
||||
left: -10,
|
||||
top: -20,
|
||||
width: HORIZONTAL_RESIZE_MIN_LIMIT * 10,
|
||||
height: VERTICAL_RESIZE_MIN_LIMIT * 10,
|
||||
columnWidth: HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
rowHeight: VERTICAL_RESIZE_MIN_LIMIT,
|
||||
widgetId: "id",
|
||||
isNotColliding: true,
|
||||
};
|
||||
expect(modifyBlockDimension(draggingBlock, 10, 10, 100, true)).toEqual(
|
||||
modifiedBlock,
|
||||
);
|
||||
});
|
||||
|
||||
it("should return resized dragging blocks while colliding with canvas edges, for bottom right", () => {
|
||||
const draggingBlock = {
|
||||
left: 400,
|
||||
top: 600,
|
||||
width: 600,
|
||||
height: 900,
|
||||
columnWidth: 60,
|
||||
rowHeight: 90,
|
||||
widgetId: "id",
|
||||
isNotColliding: true,
|
||||
};
|
||||
const modifiedBlock = {
|
||||
left: 400,
|
||||
top: 600,
|
||||
width: GridDefaults.DEFAULT_GRID_COLUMNS * 10 - 400,
|
||||
height: 400,
|
||||
columnWidth: GridDefaults.DEFAULT_GRID_COLUMNS - 40,
|
||||
rowHeight: 40,
|
||||
widgetId: "id",
|
||||
isNotColliding: true,
|
||||
};
|
||||
expect(modifyBlockDimension(draggingBlock, 10, 10, 100, false)).toEqual(
|
||||
modifiedBlock,
|
||||
);
|
||||
});
|
||||
|
||||
it("should return resized dragging blocks while colliding with canvas edges to it's limits, for bottom right", () => {
|
||||
const draggingBlock = {
|
||||
left: 630,
|
||||
top: 600,
|
||||
width: 600,
|
||||
height: 900,
|
||||
columnWidth: 60,
|
||||
rowHeight: 90,
|
||||
widgetId: "id",
|
||||
isNotColliding: true,
|
||||
fixedHeight: 90,
|
||||
};
|
||||
const modifiedBlock = {
|
||||
left: 630,
|
||||
top: 600,
|
||||
width: HORIZONTAL_RESIZE_MIN_LIMIT * 10,
|
||||
height: 900,
|
||||
columnWidth: HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
rowHeight: 90,
|
||||
widgetId: "id",
|
||||
isNotColliding: true,
|
||||
fixedHeight: 90,
|
||||
};
|
||||
expect(modifyBlockDimension(draggingBlock, 10, 10, 100, false)).toEqual(
|
||||
modifiedBlock,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("should test updateRectanglesPostReflow method", () => {
|
||||
it("should update noCollision properties based on respective rectangles", () => {
|
||||
const rectanglesToDraw = [
|
||||
{
|
||||
left: -10,
|
||||
top: 200,
|
||||
width: 600,
|
||||
height: 900,
|
||||
columnWidth: 60,
|
||||
rowHeight: 90,
|
||||
widgetId: "1",
|
||||
isNotColliding: true,
|
||||
},
|
||||
{
|
||||
left: 100,
|
||||
top: 200,
|
||||
width: 700,
|
||||
height: 950,
|
||||
columnWidth: 70,
|
||||
rowHeight: 95,
|
||||
widgetId: "2",
|
||||
isNotColliding: true,
|
||||
},
|
||||
{
|
||||
left: 300,
|
||||
top: 100,
|
||||
width: 200,
|
||||
height: 340,
|
||||
columnWidth: 20,
|
||||
rowHeight: 34,
|
||||
widgetId: "3",
|
||||
isNotColliding: true,
|
||||
},
|
||||
{
|
||||
left: 400,
|
||||
top: 500,
|
||||
width: 200,
|
||||
height: 120,
|
||||
columnWidth: 20,
|
||||
rowHeight: 12,
|
||||
widgetId: "4",
|
||||
isNotColliding: true,
|
||||
},
|
||||
];
|
||||
|
||||
const result = [
|
||||
{
|
||||
left: -10,
|
||||
top: 200,
|
||||
width: 600,
|
||||
height: 900,
|
||||
columnWidth: 60,
|
||||
rowHeight: 90,
|
||||
widgetId: "1",
|
||||
isNotColliding: false,
|
||||
},
|
||||
{
|
||||
left: 100,
|
||||
top: 200,
|
||||
width: 700,
|
||||
height: 950,
|
||||
columnWidth: 70,
|
||||
rowHeight: 95,
|
||||
widgetId: "2",
|
||||
isNotColliding: false,
|
||||
},
|
||||
{
|
||||
left: 300,
|
||||
top: 100,
|
||||
width: 200,
|
||||
height: 340,
|
||||
columnWidth: 20,
|
||||
rowHeight: 34,
|
||||
widgetId: "3",
|
||||
isNotColliding: false,
|
||||
},
|
||||
{
|
||||
left: 400,
|
||||
top: 500,
|
||||
width: 200,
|
||||
height: 120,
|
||||
columnWidth: 20,
|
||||
rowHeight: 12,
|
||||
widgetId: "4",
|
||||
isNotColliding: true,
|
||||
},
|
||||
];
|
||||
|
||||
const movementLimitMap = {
|
||||
"1": {
|
||||
canHorizontalMove: true,
|
||||
canVerticalMove: true,
|
||||
},
|
||||
"2": {
|
||||
canHorizontalMove: true,
|
||||
canVerticalMove: true,
|
||||
},
|
||||
"3": {
|
||||
canHorizontalMove: true,
|
||||
canVerticalMove: false,
|
||||
},
|
||||
"4": {
|
||||
canHorizontalMove: true,
|
||||
canVerticalMove: true,
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
updateRectanglesPostReflow(
|
||||
movementLimitMap,
|
||||
rectanglesToDraw,
|
||||
10,
|
||||
10,
|
||||
2000,
|
||||
),
|
||||
).toEqual(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
import { OccupiedSpace } from "constants/CanvasEditorConstants";
|
||||
import { GridDefaults } from "constants/WidgetConstants";
|
||||
import {
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
MovementLimitMap,
|
||||
ReflowDirection,
|
||||
ReflowedSpaceMap,
|
||||
SpaceMap,
|
||||
VERTICAL_RESIZE_MIN_LIMIT,
|
||||
} from "reflow/reflowTypes";
|
||||
import {
|
||||
getDraggingSpacesFromBlocks,
|
||||
getDropZoneOffsets,
|
||||
noCollision,
|
||||
} from "utils/WidgetPropsUtils";
|
||||
import { WidgetDraggingBlock } from "./useBlocksToBeDraggedOnCanvas";
|
||||
|
||||
/**
|
||||
* Method to get the Direction appropriate to closest edge of the canvas
|
||||
* @param x x coordinate of mouse position
|
||||
* @param y y coordinate of mouse position
|
||||
* @param width width of canvas
|
||||
* @param currentDirection current direction based on mouse movement
|
||||
* @returns closest edge
|
||||
*/
|
||||
export const getEdgeDirection = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number | undefined,
|
||||
currentDirection: ReflowDirection,
|
||||
) => {
|
||||
if (width === undefined) return currentDirection;
|
||||
const topEdgeDist = Math.abs(y);
|
||||
const leftEdgeDist = Math.abs(x);
|
||||
const rightEdgeDist = Math.abs(width - x);
|
||||
const min = Math.min(topEdgeDist, leftEdgeDist, rightEdgeDist);
|
||||
switch (min) {
|
||||
case leftEdgeDist:
|
||||
return ReflowDirection.RIGHT;
|
||||
case rightEdgeDist:
|
||||
return ReflowDirection.LEFT;
|
||||
case topEdgeDist:
|
||||
return ReflowDirection.BOTTOM;
|
||||
default:
|
||||
return currentDirection;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Modify the existing space to the reflowed positions
|
||||
* @param draggingSpace position object of dragging Space
|
||||
* @param reflowingWidgets reflowed parameters of widgets
|
||||
* @param snapColumnSpace width between columns
|
||||
* @param snapRowSpace height between rows
|
||||
* @returns Modified position
|
||||
*/
|
||||
export function getReflowedSpaces(
|
||||
draggingSpace: OccupiedSpace,
|
||||
reflowingWidgets: ReflowedSpaceMap,
|
||||
snapColumnSpace: number,
|
||||
snapRowSpace: number,
|
||||
) {
|
||||
const reflowedWidget = reflowingWidgets[draggingSpace.id];
|
||||
if (
|
||||
reflowedWidget.X !== undefined &&
|
||||
(Math.abs(reflowedWidget.X) || reflowedWidget.width)
|
||||
) {
|
||||
const movement = reflowedWidget.X / snapColumnSpace;
|
||||
const newWidth = reflowedWidget.width
|
||||
? reflowedWidget.width / snapColumnSpace
|
||||
: draggingSpace.right - draggingSpace.left;
|
||||
draggingSpace = {
|
||||
...draggingSpace,
|
||||
left: draggingSpace.left + movement,
|
||||
right: draggingSpace.left + movement + newWidth,
|
||||
};
|
||||
}
|
||||
if (
|
||||
reflowedWidget.Y !== undefined &&
|
||||
(Math.abs(reflowedWidget.Y) || reflowedWidget.height)
|
||||
) {
|
||||
const movement = reflowedWidget.Y / snapRowSpace;
|
||||
const newHeight = reflowedWidget.height
|
||||
? reflowedWidget.height / snapRowSpace
|
||||
: draggingSpace.bottom - draggingSpace.top;
|
||||
draggingSpace = {
|
||||
...draggingSpace,
|
||||
top: draggingSpace.top + movement,
|
||||
bottom: draggingSpace.top + movement + newHeight,
|
||||
};
|
||||
}
|
||||
return draggingSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the rectangles to draw object to match the positions of spaceMap
|
||||
* @param rectanglesToDraw dragging parameters of widget
|
||||
* @param spaceMap Widget Position
|
||||
* @param snapColumnSpace width between columns
|
||||
* @param snapRowSpace height between rows
|
||||
* @returns modified rectangles to draw
|
||||
*/
|
||||
export function modifyDrawingRectangles(
|
||||
rectanglesToDraw: WidgetDraggingBlock[],
|
||||
spaceMap: SpaceMap | undefined,
|
||||
snapColumnSpace: number,
|
||||
snapRowSpace: number,
|
||||
): WidgetDraggingBlock[] {
|
||||
const rectangleToDraw = rectanglesToDraw?.[0];
|
||||
|
||||
if (rectanglesToDraw.length !== 1 || !spaceMap?.[rectangleToDraw?.widgetId])
|
||||
return rectanglesToDraw;
|
||||
|
||||
const { bottom, left, right, top } = spaceMap[rectangleToDraw.widgetId];
|
||||
|
||||
const resizedPosition = getDraggingSpacesFromBlocks(
|
||||
rectanglesToDraw,
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
)[0];
|
||||
|
||||
return [
|
||||
{
|
||||
...rectanglesToDraw[0],
|
||||
left:
|
||||
(left - resizedPosition.left) * snapColumnSpace +
|
||||
rectanglesToDraw[0].left,
|
||||
top: (top - resizedPosition.top) * snapRowSpace + rectanglesToDraw[0].top,
|
||||
width: (right - left) * snapColumnSpace,
|
||||
height: (bottom - top) * snapRowSpace,
|
||||
rowHeight: bottom - top,
|
||||
columnWidth: right - left,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Direction of movement based on previous position of dragging widget
|
||||
* @param prevPosition
|
||||
* @param currentPosition
|
||||
* @param currentDirection
|
||||
* @returns movement direction
|
||||
*/
|
||||
export function getMoveDirection(
|
||||
prevPosition: OccupiedSpace,
|
||||
currentPosition: OccupiedSpace,
|
||||
currentDirection: ReflowDirection,
|
||||
) {
|
||||
if (!prevPosition || !currentPosition) return currentDirection;
|
||||
|
||||
if (
|
||||
currentPosition.right - prevPosition.right > 0 ||
|
||||
currentPosition.left - prevPosition.left > 0
|
||||
)
|
||||
return ReflowDirection.RIGHT;
|
||||
|
||||
if (
|
||||
currentPosition.right - prevPosition.right < 0 ||
|
||||
currentPosition.left - prevPosition.left < 0
|
||||
)
|
||||
return ReflowDirection.LEFT;
|
||||
|
||||
if (
|
||||
currentPosition.bottom - prevPosition.bottom > 0 ||
|
||||
currentPosition.top - prevPosition.top > 0
|
||||
)
|
||||
return ReflowDirection.BOTTOM;
|
||||
|
||||
if (
|
||||
currentPosition.bottom - prevPosition.bottom < 0 ||
|
||||
currentPosition.top - prevPosition.top < 0
|
||||
)
|
||||
return ReflowDirection.TOP;
|
||||
|
||||
return currentDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the dragging Blocks to resize against canvas edges
|
||||
* @param draggingBlock
|
||||
* @param snapColumnSpace
|
||||
* @param snapRowSpace
|
||||
* @param parentBottomRow
|
||||
* @param canExtend
|
||||
* @returns
|
||||
*/
|
||||
export const modifyBlockDimension = (
|
||||
draggingBlock: WidgetDraggingBlock,
|
||||
snapColumnSpace: number,
|
||||
snapRowSpace: number,
|
||||
parentBottomRow: number,
|
||||
canExtend: boolean,
|
||||
) => {
|
||||
const {
|
||||
columnWidth,
|
||||
fixedHeight,
|
||||
height,
|
||||
left,
|
||||
rowHeight,
|
||||
top,
|
||||
width,
|
||||
} = draggingBlock;
|
||||
|
||||
//get left and top of widget on canvas grid
|
||||
const [leftColumn, topRow] = getDropZoneOffsets(
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
{
|
||||
x: left,
|
||||
y: top,
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
);
|
||||
|
||||
let leftOffset = 0,
|
||||
rightOffset = 0,
|
||||
topOffset = 0,
|
||||
bottomOffset = 0;
|
||||
|
||||
// calculate offsets based on collisions and limits
|
||||
if (leftColumn < 0) {
|
||||
leftOffset =
|
||||
leftColumn + columnWidth > HORIZONTAL_RESIZE_MIN_LIMIT
|
||||
? leftColumn
|
||||
: HORIZONTAL_RESIZE_MIN_LIMIT - columnWidth;
|
||||
} else if (leftColumn + columnWidth > GridDefaults.DEFAULT_GRID_COLUMNS) {
|
||||
rightOffset = GridDefaults.DEFAULT_GRID_COLUMNS - leftColumn - columnWidth;
|
||||
rightOffset =
|
||||
columnWidth + rightOffset >= HORIZONTAL_RESIZE_MIN_LIMIT
|
||||
? rightOffset
|
||||
: HORIZONTAL_RESIZE_MIN_LIMIT - columnWidth;
|
||||
}
|
||||
|
||||
if (topRow < 0 && fixedHeight === undefined) {
|
||||
topOffset =
|
||||
topRow + rowHeight > VERTICAL_RESIZE_MIN_LIMIT
|
||||
? topRow
|
||||
: VERTICAL_RESIZE_MIN_LIMIT - rowHeight;
|
||||
}
|
||||
|
||||
if (
|
||||
topRow + rowHeight > parentBottomRow &&
|
||||
!canExtend &&
|
||||
fixedHeight === undefined
|
||||
) {
|
||||
bottomOffset = parentBottomRow - topRow - rowHeight;
|
||||
bottomOffset =
|
||||
rowHeight + bottomOffset >= VERTICAL_RESIZE_MIN_LIMIT
|
||||
? bottomOffset
|
||||
: VERTICAL_RESIZE_MIN_LIMIT - rowHeight;
|
||||
}
|
||||
|
||||
return {
|
||||
...draggingBlock,
|
||||
left: left - leftOffset * snapColumnSpace,
|
||||
top: top - topOffset * snapRowSpace,
|
||||
width: width + (leftOffset + rightOffset) * snapColumnSpace,
|
||||
height: height + (topOffset + bottomOffset) * snapRowSpace,
|
||||
columnWidth: columnWidth + leftOffset + rightOffset,
|
||||
rowHeight: rowHeight + topOffset + bottomOffset,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* updates isColliding of each block based on movementLimitMap post reflow
|
||||
* @param movementLimitMap limits of each widgets
|
||||
* @param currentRectanglesToDraw dragging parameters of widget
|
||||
* @param snapColumnSpace width between each columns
|
||||
* @param snapRowSpace height between each rows
|
||||
* @param rows number of rows in canvas
|
||||
* @returns array of rectangle blocks to draw
|
||||
*/
|
||||
export const updateRectanglesPostReflow = (
|
||||
movementLimitMap: MovementLimitMap | undefined,
|
||||
currentRectanglesToDraw: WidgetDraggingBlock[],
|
||||
snapColumnSpace: number,
|
||||
snapRowSpace: number,
|
||||
rows: number,
|
||||
) => {
|
||||
const rectanglesToDraw: WidgetDraggingBlock[] = [];
|
||||
for (const block of currentRectanglesToDraw) {
|
||||
const isWithinParentBoundaries = noCollision(
|
||||
{ x: block.left, y: block.top },
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
{ x: 0, y: 0 },
|
||||
block.columnWidth,
|
||||
block.rowHeight,
|
||||
block.widgetId,
|
||||
[],
|
||||
rows,
|
||||
GridDefaults.DEFAULT_GRID_COLUMNS,
|
||||
block.detachFromLayout,
|
||||
);
|
||||
|
||||
let isNotReachedLimit = true;
|
||||
const currentBlockLimit =
|
||||
movementLimitMap && movementLimitMap[block.widgetId];
|
||||
|
||||
if (currentBlockLimit) {
|
||||
isNotReachedLimit =
|
||||
currentBlockLimit.canHorizontalMove &&
|
||||
currentBlockLimit.canVerticalMove;
|
||||
}
|
||||
|
||||
rectanglesToDraw.push({
|
||||
...block,
|
||||
isNotColliding: isWithinParentBoundaries && isNotReachedLimit,
|
||||
});
|
||||
}
|
||||
|
||||
return rectanglesToDraw;
|
||||
};
|
||||
|
|
@ -8,7 +8,7 @@ import { AppState } from "@appsmith/reducers";
|
|||
import { getSelectedWidgets } from "selectors/ui";
|
||||
import { getOccupiedSpacesWhileMoving } from "selectors/editorSelectors";
|
||||
import { getTableFilterState } from "selectors/tableFilterSelectors";
|
||||
import { OccupiedSpace } from "constants/CanvasEditorConstants";
|
||||
import { OccupiedSpace, WidgetSpace } from "constants/CanvasEditorConstants";
|
||||
import { getDragDetails, getWidgetByID, getWidgets } from "sagas/selectors";
|
||||
import {
|
||||
getDropZoneOffsets,
|
||||
|
|
@ -28,8 +28,7 @@ import { snapToGrid } from "utils/helpers";
|
|||
import { stopReflowAction } from "actions/reflowActions";
|
||||
import { DragDetails } from "reducers/uiReducers/dragResizeReducer";
|
||||
import { getIsReflowing } from "selectors/widgetReflowSelectors";
|
||||
import { XYCord } from "./useCanvasDragging";
|
||||
import ContainerJumpMetrics from "./ContainerJumpMetric";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useRenderBlocksOnCanvas";
|
||||
|
||||
export interface WidgetDraggingUpdateParams extends WidgetDraggingBlock {
|
||||
updateWidgetParams: WidgetOperationParams;
|
||||
|
|
@ -45,28 +44,7 @@ export type WidgetDraggingBlock = {
|
|||
widgetId: string;
|
||||
isNotColliding: boolean;
|
||||
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();
|
||||
fixedHeight?: number;
|
||||
};
|
||||
|
||||
export const useBlocksToBeDraggedOnCanvas = ({
|
||||
|
|
@ -119,7 +97,7 @@ export const useBlocksToBeDraggedOnCanvas = ({
|
|||
const selectedWidgets = useSelector(getSelectedWidgets);
|
||||
const occupiedSpaces = useSelector(getOccupiedSpacesWhileMoving, equal) || {};
|
||||
const isNewWidget = !!newWidget && !dragParent;
|
||||
const childrenOccupiedSpaces: OccupiedSpace[] =
|
||||
const childrenOccupiedSpaces: WidgetSpace[] =
|
||||
(dragParent && occupiedSpaces[dragParent]) || [];
|
||||
const isDragging = useSelector(
|
||||
(state: AppState) => state.ui.widgetDragResize.isDragging,
|
||||
|
|
@ -128,51 +106,11 @@ export const useBlocksToBeDraggedOnCanvas = ({
|
|||
|
||||
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();
|
||||
}
|
||||
};
|
||||
// modify the positions to have grab position on the right side for new widgets
|
||||
if (isNewWidget) {
|
||||
defaultHandlePositions.left =
|
||||
newWidget.columns * snapColumnSpace - defaultHandlePositions.left;
|
||||
}
|
||||
const getDragCenterSpace = () => {
|
||||
if (dragCenter && dragCenter.widgetId) {
|
||||
// Dragging by widget
|
||||
|
|
@ -227,6 +165,9 @@ export const useBlocksToBeDraggedOnCanvas = ({
|
|||
widgetId: newWidget.widgetId,
|
||||
detachFromLayout: newWidget.detachFromLayout,
|
||||
isNotColliding: true,
|
||||
fixedHeight: newWidget.isDynamicHeight
|
||||
? newWidget.rows * snapRowSpace
|
||||
: undefined,
|
||||
},
|
||||
],
|
||||
draggingSpaces: [
|
||||
|
|
@ -254,6 +195,7 @@ export const useBlocksToBeDraggedOnCanvas = ({
|
|||
rowHeight: each.bottom - each.top,
|
||||
widgetId: each.id,
|
||||
isNotColliding: true,
|
||||
fixedHeight: each.fixedHeight,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
|
@ -272,7 +214,6 @@ export const useBlocksToBeDraggedOnCanvas = ({
|
|||
drawingBlocks: WidgetDraggingBlock[],
|
||||
reflowedPositionsUpdatesWidgets: OccupiedSpace[],
|
||||
) => {
|
||||
logContainerJumpOnDrop();
|
||||
const reflowedBlocks: WidgetDraggingBlock[] = reflowedPositionsUpdatesWidgets.map(
|
||||
(each) => {
|
||||
const widget = allWidgets[each.id];
|
||||
|
|
@ -303,7 +244,11 @@ export const useBlocksToBeDraggedOnCanvas = ({
|
|||
.map((each) => {
|
||||
const widget =
|
||||
newWidget && !reflowedIds.includes(each.widgetId)
|
||||
? newWidget
|
||||
? {
|
||||
...newWidget,
|
||||
columns: each.columnWidth,
|
||||
rows: each.rowHeight,
|
||||
}
|
||||
: allWidgets[each.widgetId];
|
||||
const updateWidgetParams = widgetOperationParams(
|
||||
widget,
|
||||
|
|
@ -482,7 +427,6 @@ export const useBlocksToBeDraggedOnCanvas = ({
|
|||
isNewWidgetInitialTargetCanvas,
|
||||
isResizing,
|
||||
lastDraggedCanvas,
|
||||
logContainerJump,
|
||||
occSpaces,
|
||||
draggingSpaces,
|
||||
onDrop,
|
||||
|
|
|
|||
|
|
@ -1,48 +1,36 @@
|
|||
import { OccupiedSpace } from "constants/CanvasEditorConstants";
|
||||
import {
|
||||
CONTAINER_GRID_PADDING,
|
||||
GridDefaults,
|
||||
} from "constants/WidgetConstants";
|
||||
import { GridDefaults } from "constants/WidgetConstants";
|
||||
import { debounce, isEmpty, throttle } from "lodash";
|
||||
import { CanvasDraggingArenaProps } from "pages/common/CanvasArenas/CanvasDraggingArena";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import {
|
||||
MovementLimitMap,
|
||||
ReflowDirection,
|
||||
ReflowedSpaceMap,
|
||||
SpaceMap,
|
||||
} from "reflow/reflowTypes";
|
||||
import { getZoomLevel } from "selectors/editorSelectors";
|
||||
import { getNearestParentCanvas } from "utils/generators";
|
||||
import { getAbsolutePixels } from "utils/helpers";
|
||||
import { useWidgetDragResize } from "utils/hooks/dragResizeHooks";
|
||||
import { ReflowInterface, useReflow } from "utils/hooks/useReflow";
|
||||
import {
|
||||
getDraggingSpacesFromBlocks,
|
||||
getDropZoneOffsets,
|
||||
getMousePositionsOnCanvas,
|
||||
noCollision,
|
||||
} from "utils/WidgetPropsUtils";
|
||||
import {
|
||||
getEdgeDirection,
|
||||
getMoveDirection,
|
||||
getReflowedSpaces,
|
||||
modifyBlockDimension,
|
||||
modifyDrawingRectangles,
|
||||
updateRectanglesPostReflow,
|
||||
} from "./canvasDraggingUtils";
|
||||
import {
|
||||
useBlocksToBeDraggedOnCanvas,
|
||||
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;
|
||||
}>();
|
||||
import { useRenderBlocksOnCanvas } from "./useRenderBlocksOnCanvas";
|
||||
|
||||
export const useCanvasDragging = (
|
||||
slidingArenaRef: React.RefObject<HTMLDivElement>,
|
||||
|
|
@ -57,7 +45,6 @@ export const useCanvasDragging = (
|
|||
widgetId,
|
||||
}: CanvasDraggingArenaProps,
|
||||
) => {
|
||||
const canvasZoomLevel = useSelector(getZoomLevel);
|
||||
const currentDirection = useRef<ReflowDirection>(ReflowDirection.UNSET);
|
||||
const { devicePixelRatio: scale = 1 } = window;
|
||||
const {
|
||||
|
|
@ -72,7 +59,6 @@ export const useCanvasDragging = (
|
|||
isNewWidgetInitialTargetCanvas,
|
||||
isResizing,
|
||||
lastDraggedCanvas,
|
||||
logContainerJump,
|
||||
occSpaces,
|
||||
onDrop,
|
||||
parentDiff,
|
||||
|
|
@ -96,7 +82,10 @@ export const useCanvasDragging = (
|
|||
paddingOffset: 0,
|
||||
};
|
||||
|
||||
const reflow = useRef<ReflowInterface>();
|
||||
const reflow = useRef<{
|
||||
reflowSpaces: ReflowInterface;
|
||||
resetReflow: () => void;
|
||||
}>();
|
||||
reflow.current = useReflow(draggingSpaces, widgetId || "", gridProps);
|
||||
|
||||
const {
|
||||
|
|
@ -105,32 +94,6 @@ export const useCanvasDragging = (
|
|||
setDraggingState,
|
||||
} = useWidgetDragResize();
|
||||
|
||||
const mouseAttributesRef = useRef<{
|
||||
prevEvent: any;
|
||||
currentEvent: any;
|
||||
prevSpeed: number;
|
||||
prevAcceleration: number;
|
||||
maxPositiveAcc: number;
|
||||
maxNegativeAcc: number;
|
||||
maxSpeed: number;
|
||||
lastMousePositionOutsideCanvas: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}>({
|
||||
prevSpeed: 0,
|
||||
prevAcceleration: 0,
|
||||
maxPositiveAcc: 0,
|
||||
maxNegativeAcc: 0,
|
||||
maxSpeed: 0,
|
||||
prevEvent: null,
|
||||
currentEvent: null,
|
||||
lastMousePositionOutsideCanvas: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const canScroll = useCanvasDragToScroll(
|
||||
slidingArenaRef,
|
||||
isCurrentDraggedCanvas,
|
||||
|
|
@ -139,55 +102,15 @@ export const useCanvasDragging = (
|
|||
canExtend,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const speedCalculationInterval = setInterval(function() {
|
||||
const {
|
||||
currentEvent,
|
||||
maxNegativeAcc,
|
||||
maxPositiveAcc,
|
||||
maxSpeed,
|
||||
prevEvent,
|
||||
prevSpeed,
|
||||
} = mouseAttributesRef.current;
|
||||
if (prevEvent && currentEvent) {
|
||||
const movementX = Math.abs(currentEvent.screenX - prevEvent.screenX);
|
||||
const movementY = Math.abs(currentEvent.screenY - prevEvent.screenY);
|
||||
const movement = Math.sqrt(
|
||||
movementX * movementX + movementY * movementY,
|
||||
);
|
||||
|
||||
const speed = 10 * movement; //current speed
|
||||
|
||||
const acceleration = 10 * (speed - prevSpeed);
|
||||
mouseAttributesRef.current.prevAcceleration = acceleration;
|
||||
mouseAttributesRef.current.prevSpeed = speed;
|
||||
if (speed > maxSpeed) {
|
||||
mouseAttributesRef.current.maxSpeed = speed;
|
||||
}
|
||||
if (acceleration > 0 && acceleration > maxPositiveAcc) {
|
||||
mouseAttributesRef.current.maxPositiveAcc = acceleration;
|
||||
} else if (acceleration < 0 && acceleration < maxNegativeAcc) {
|
||||
mouseAttributesRef.current.maxNegativeAcc = acceleration;
|
||||
}
|
||||
}
|
||||
mouseAttributesRef.current.prevEvent = currentEvent;
|
||||
}, 100);
|
||||
const stopSpeedCalculation = () => {
|
||||
clearInterval(speedCalculationInterval);
|
||||
};
|
||||
const registerMouseMoveEvent = (e: any) => {
|
||||
mouseAttributesRef.current.currentEvent = e;
|
||||
mouseAttributesRef.current.lastMousePositionOutsideCanvas = {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
};
|
||||
};
|
||||
window.addEventListener("mousemove", registerMouseMoveEvent);
|
||||
return () => {
|
||||
stopSpeedCalculation();
|
||||
window.removeEventListener("mousemove", registerMouseMoveEvent);
|
||||
};
|
||||
}, []);
|
||||
const renderBlocks = useRenderBlocksOnCanvas(
|
||||
slidingArenaRef,
|
||||
stickyCanvasRef,
|
||||
!!noPad,
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
getSnappedXY,
|
||||
isCurrentDraggedCanvas,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
|
|
@ -210,24 +133,24 @@ export const useCanvasDragging = (
|
|||
movementLimitMap?: MovementLimitMap;
|
||||
bottomMostRow: number;
|
||||
movementMap: ReflowedSpaceMap;
|
||||
isIdealToJumpContainer: boolean;
|
||||
spacePositionMap: SpaceMap | undefined;
|
||||
} = {
|
||||
movementLimitMap: {},
|
||||
bottomMostRow: 0,
|
||||
movementMap: {},
|
||||
isIdealToJumpContainer: false,
|
||||
spacePositionMap: {},
|
||||
};
|
||||
let lastMousePosition = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
let lastSnappedPosition = {
|
||||
leftColumn: 0,
|
||||
topRow: 0,
|
||||
let lastSnappedPosition: OccupiedSpace = {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
id: "",
|
||||
};
|
||||
|
||||
const resetCanvasState = () => {
|
||||
throttledStopReflowing();
|
||||
reflow.current?.resetReflow();
|
||||
if (stickyCanvasRef.current && slidingArenaRef.current) {
|
||||
const canvasCtx: any = stickyCanvasRef.current.getContext("2d");
|
||||
canvasCtx.clearRect(
|
||||
|
|
@ -240,6 +163,7 @@ export const useCanvasDragging = (
|
|||
canvasIsDragging = false;
|
||||
}
|
||||
};
|
||||
|
||||
if (isDragging) {
|
||||
const startPoints = defaultHandlePositions;
|
||||
const onMouseUp = () => {
|
||||
|
|
@ -247,40 +171,24 @@ export const useCanvasDragging = (
|
|||
const { movementMap: reflowingWidgets } = currentReflowParams;
|
||||
const reflowedPositionsUpdatesWidgets: OccupiedSpace[] = occSpaces
|
||||
.filter((each) => !!reflowingWidgets[each.id])
|
||||
.map((each) => {
|
||||
const reflowedWidget = reflowingWidgets[each.id];
|
||||
if (
|
||||
reflowedWidget.X !== undefined &&
|
||||
(Math.abs(reflowedWidget.X) || reflowedWidget.width)
|
||||
) {
|
||||
const movement = reflowedWidget.X / snapColumnSpace;
|
||||
const newWidth = reflowedWidget.width
|
||||
? reflowedWidget.width / snapColumnSpace
|
||||
: each.right - each.left;
|
||||
each = {
|
||||
...each,
|
||||
left: each.left + movement,
|
||||
right: each.left + movement + newWidth,
|
||||
};
|
||||
}
|
||||
if (
|
||||
reflowedWidget.Y !== undefined &&
|
||||
(Math.abs(reflowedWidget.Y) || reflowedWidget.height)
|
||||
) {
|
||||
const movement = reflowedWidget.Y / snapRowSpace;
|
||||
const newHeight = reflowedWidget.height
|
||||
? reflowedWidget.height / snapRowSpace
|
||||
: each.bottom - each.top;
|
||||
each = {
|
||||
...each,
|
||||
top: each.top + movement,
|
||||
bottom: each.top + movement + newHeight,
|
||||
};
|
||||
}
|
||||
return each;
|
||||
});
|
||||
.map((each) =>
|
||||
getReflowedSpaces(
|
||||
each,
|
||||
reflowingWidgets,
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
),
|
||||
);
|
||||
|
||||
onDrop(currentRectanglesToDraw, reflowedPositionsUpdatesWidgets);
|
||||
onDrop(
|
||||
modifyDrawingRectangles(
|
||||
currentRectanglesToDraw,
|
||||
currentReflowParams.spacePositionMap,
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
),
|
||||
reflowedPositionsUpdatesWidgets,
|
||||
);
|
||||
}
|
||||
startPoints.top = defaultHandlePositions.top;
|
||||
startPoints.left = defaultHandlePositions.left;
|
||||
|
|
@ -312,181 +220,99 @@ 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);
|
||||
}
|
||||
canvasIsDragging = true;
|
||||
slidingArenaRef.current.style.zIndex = "2";
|
||||
if (over) {
|
||||
lastMousePosition = {
|
||||
...mouseAttributesRef.current.lastMousePositionOutsideCanvas,
|
||||
};
|
||||
} else {
|
||||
lastMousePosition = {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
};
|
||||
}
|
||||
|
||||
onMouseMove(e, over);
|
||||
}
|
||||
};
|
||||
|
||||
const canReflowForCurrentMouseMove = () => {
|
||||
const { prevAcceleration, prevSpeed } = mouseAttributesRef.current;
|
||||
const acceleration = Math.abs(prevAcceleration);
|
||||
return (
|
||||
acceleration < CONTAINER_JUMP_ACC_THRESHOLD ||
|
||||
prevSpeed < CONTAINER_JUMP_SPEED_THRESHOLD
|
||||
);
|
||||
};
|
||||
const getMouseMoveDirection = (event: any) => {
|
||||
if (lastMousePosition) {
|
||||
const deltaX = lastMousePosition.x - event.clientX,
|
||||
deltaY = lastMousePosition.y - event.clientY;
|
||||
lastMousePosition = {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
};
|
||||
if (
|
||||
deltaX === 0 &&
|
||||
["TOP", "BOTTOM"].includes(currentDirection.current)
|
||||
) {
|
||||
return currentDirection.current;
|
||||
}
|
||||
if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY > 0) {
|
||||
return ReflowDirection.TOP;
|
||||
} else if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY < 0) {
|
||||
return ReflowDirection.BOTTOM;
|
||||
}
|
||||
if (
|
||||
deltaY === 0 &&
|
||||
["LEFT", "RIGHT"].includes(currentDirection.current)
|
||||
) {
|
||||
return currentDirection.current;
|
||||
}
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX > 0) {
|
||||
return ReflowDirection.LEFT;
|
||||
} else if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX < 0) {
|
||||
return ReflowDirection.RIGHT;
|
||||
}
|
||||
}
|
||||
return currentDirection.current;
|
||||
};
|
||||
const triggerReflow = (e: any, firstMove: boolean) => {
|
||||
const canReflowBasedOnMouseSpeed = canReflowForCurrentMouseMove();
|
||||
const isReflowing = !isEmpty(currentReflowParams.movementMap);
|
||||
const canReflow =
|
||||
!currentRectanglesToDraw[0].detachFromLayout && !dropDisabled;
|
||||
const currentBlock = currentRectanglesToDraw[0];
|
||||
const [leftColumn, topRow] = getDropZoneOffsets(
|
||||
const isReflowing =
|
||||
!isEmpty(currentReflowParams.movementMap) ||
|
||||
(!isEmpty(currentReflowParams.movementLimitMap) &&
|
||||
currentRectanglesToDraw.length === 1);
|
||||
//The position array of dragging Widgets.
|
||||
const resizedPositions = getDraggingSpacesFromBlocks(
|
||||
currentRectanglesToDraw,
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
{
|
||||
x: currentBlock.left,
|
||||
y: currentBlock.top,
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
);
|
||||
const currentBlock = resizedPositions[0];
|
||||
const mousePosition = getMousePositionsOnCanvas(e, gridProps);
|
||||
const needsReflow = !(
|
||||
lastSnappedPosition.leftColumn === leftColumn &&
|
||||
lastSnappedPosition.topRow === topRow
|
||||
lastSnappedPosition.left === currentBlock.left &&
|
||||
lastSnappedPosition.top === currentBlock.top &&
|
||||
lastSnappedPosition.bottom === currentBlock.bottom &&
|
||||
lastSnappedPosition.right === currentBlock.right
|
||||
);
|
||||
lastSnappedPosition = {
|
||||
leftColumn,
|
||||
topRow,
|
||||
};
|
||||
if (canReflow && reflow.current) {
|
||||
if (needsReflow) {
|
||||
//The position array of dragging Widgets.
|
||||
const resizedPositions = getDraggingSpacesFromBlocks(
|
||||
currentRectanglesToDraw,
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
currentDirection.current = getMoveDirection(
|
||||
lastSnappedPosition,
|
||||
currentBlock,
|
||||
currentDirection.current,
|
||||
);
|
||||
currentDirection.current = getMouseMoveDirection(e);
|
||||
const immediateExitContainer = lastDraggedCanvas.current;
|
||||
if (firstMove) {
|
||||
currentDirection.current = getEdgeDirection(
|
||||
e.offsetX,
|
||||
e.offsetY,
|
||||
slidingArenaRef.current?.clientWidth,
|
||||
currentDirection.current,
|
||||
);
|
||||
}
|
||||
lastSnappedPosition = { ...currentBlock };
|
||||
let immediateExitContainer;
|
||||
if (lastDraggedCanvas.current) {
|
||||
immediateExitContainer = lastDraggedCanvas.current;
|
||||
lastDraggedCanvas.current = undefined;
|
||||
}
|
||||
currentReflowParams = reflow.current(
|
||||
currentReflowParams = reflow.current?.reflowSpaces(
|
||||
resizedPositions,
|
||||
currentDirection.current,
|
||||
false,
|
||||
!canReflowBasedOnMouseSpeed,
|
||||
true,
|
||||
firstMove,
|
||||
immediateExitContainer,
|
||||
mousePosition,
|
||||
reflowAfterTimeoutCallback,
|
||||
);
|
||||
}
|
||||
|
||||
if (isReflowing) {
|
||||
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 },
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
{ x: 0, y: 0 },
|
||||
block.columnWidth,
|
||||
block.rowHeight,
|
||||
block.widgetId,
|
||||
[],
|
||||
rowRef.current,
|
||||
GridDefaults.DEFAULT_GRID_COLUMNS,
|
||||
block.detachFromLayout,
|
||||
);
|
||||
|
||||
let isNotReachedLimit = true;
|
||||
const currentBlockLimit =
|
||||
movementLimitMap && movementLimitMap[block.widgetId];
|
||||
if (currentBlockLimit) {
|
||||
isNotReachedLimit =
|
||||
currentBlockLimit.canHorizontalMove &&
|
||||
currentBlockLimit.canVerticalMove;
|
||||
}
|
||||
block.isNotColliding =
|
||||
isWithinParentBoundaries && isNotReachedLimit;
|
||||
}
|
||||
const widgetIdsToExclude = currentRectanglesToDraw.map(
|
||||
(a) => a.widgetId,
|
||||
);
|
||||
const newRows = updateBottomRow(
|
||||
currentReflowParams.bottomMostRow,
|
||||
rowRef.current,
|
||||
widgetIdsToExclude,
|
||||
);
|
||||
rowRef.current = newRows ? newRows : rowRef.current;
|
||||
updateParamsPostReflow();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//update blocks after reflow
|
||||
const updateParamsPostReflow = () => {
|
||||
const { movementLimitMap } = currentReflowParams;
|
||||
|
||||
// update isColliding of each block based on movementLimitMap
|
||||
currentRectanglesToDraw = updateRectanglesPostReflow(
|
||||
movementLimitMap,
|
||||
currentRectanglesToDraw,
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
rowRef.current,
|
||||
);
|
||||
|
||||
const widgetIdsToExclude = currentRectanglesToDraw.map(
|
||||
(a) => a.widgetId,
|
||||
);
|
||||
const newRows = updateBottomRow(
|
||||
currentReflowParams.bottomMostRow,
|
||||
rowRef.current,
|
||||
widgetIdsToExclude,
|
||||
);
|
||||
rowRef.current = newRows ? newRows : rowRef.current;
|
||||
};
|
||||
|
||||
const onMouseMove = (e: any, firstMove = false) => {
|
||||
if (isDragging && canvasIsDragging && slidingArenaRef.current) {
|
||||
const delta = {
|
||||
|
|
@ -494,11 +320,19 @@ export const useCanvasDragging = (
|
|||
top: e.offsetY - startPoints.top - parentDiff.top,
|
||||
};
|
||||
|
||||
const drawingBlocks = blocksToDraw.map((each) => ({
|
||||
...each,
|
||||
left: each.left + delta.left,
|
||||
top: each.top + delta.top,
|
||||
}));
|
||||
const drawingBlocks = blocksToDraw.map((each) =>
|
||||
modifyBlockDimension(
|
||||
{
|
||||
...each,
|
||||
left: each.left + delta.left,
|
||||
top: each.top + delta.top,
|
||||
},
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
rowRef.current - 1,
|
||||
canExtend,
|
||||
),
|
||||
);
|
||||
const newRows = updateRelativeRows(drawingBlocks, rowRef.current);
|
||||
const rowDelta = newRows ? newRows - rowRef.current : 0;
|
||||
rowRef.current = newRows ? newRows : rowRef.current;
|
||||
|
|
@ -526,8 +360,14 @@ export const useCanvasDragging = (
|
|||
renderNewRows(delta);
|
||||
} else if (!isUpdatingRows) {
|
||||
triggerReflow(e, firstMove);
|
||||
renderBlocks();
|
||||
}
|
||||
isUpdatingRows = renderBlocks(
|
||||
currentRectanglesToDraw,
|
||||
currentReflowParams.spacePositionMap,
|
||||
isUpdatingRows,
|
||||
canvasIsDragging,
|
||||
scrollParent,
|
||||
);
|
||||
scrollObj.lastMouseMoveEvent = {
|
||||
offsetX: e.offsetX,
|
||||
offsetY: e.offsetY,
|
||||
|
|
@ -544,24 +384,33 @@ export const useCanvasDragging = (
|
|||
const canvasCtx: any = stickyCanvasRef.current.getContext("2d");
|
||||
|
||||
currentRectanglesToDraw = blocksToDraw.map((each) => {
|
||||
const block = modifyBlockDimension(
|
||||
{
|
||||
...each,
|
||||
left: each.left + delta.left,
|
||||
top: each.top + delta.top,
|
||||
},
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
rowRef.current - 1,
|
||||
canExtend,
|
||||
);
|
||||
return {
|
||||
...each,
|
||||
left: each.left + delta.left,
|
||||
top: each.top + delta.top,
|
||||
...block,
|
||||
isNotColliding:
|
||||
!dropDisabled &&
|
||||
noCollision(
|
||||
{ x: each.left + delta.left, y: each.top + delta.top },
|
||||
{ x: block.left, y: block.top },
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
{ x: 0, y: 0 },
|
||||
each.columnWidth,
|
||||
each.rowHeight,
|
||||
each.widgetId,
|
||||
block.columnWidth,
|
||||
block.rowHeight,
|
||||
block.widgetId,
|
||||
occSpaces,
|
||||
rowRef.current,
|
||||
GridDefaults.DEFAULT_GRID_COLUMNS,
|
||||
each.detachFromLayout,
|
||||
block.detachFromLayout,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
|
@ -574,7 +423,13 @@ export const useCanvasDragging = (
|
|||
stickyCanvasRef.current.height,
|
||||
);
|
||||
canvasCtx.restore();
|
||||
renderBlocks();
|
||||
isUpdatingRows = renderBlocks(
|
||||
currentRectanglesToDraw,
|
||||
currentReflowParams.spacePositionMap,
|
||||
isUpdatingRows,
|
||||
canvasIsDragging,
|
||||
scrollParent,
|
||||
);
|
||||
canScroll.current = false;
|
||||
endRenderRows.cancel();
|
||||
endRenderRows();
|
||||
|
|
@ -592,93 +447,21 @@ export const useCanvasDragging = (
|
|||
},
|
||||
);
|
||||
|
||||
const renderBlocks = () => {
|
||||
if (
|
||||
slidingArenaRef.current &&
|
||||
isCurrentDraggedCanvas &&
|
||||
canvasIsDragging &&
|
||||
stickyCanvasRef.current
|
||||
) {
|
||||
const canvasCtx: any = stickyCanvasRef.current.getContext("2d");
|
||||
canvasCtx.save();
|
||||
canvasCtx.clearRect(
|
||||
0,
|
||||
0,
|
||||
stickyCanvasRef.current.width,
|
||||
stickyCanvasRef.current.height,
|
||||
);
|
||||
isUpdatingRows = false;
|
||||
canvasCtx.transform(canvasZoomLevel, 0, 0, canvasZoomLevel, 0, 0);
|
||||
if (canvasIsDragging) {
|
||||
currentRectanglesToDraw.forEach((each) => {
|
||||
drawBlockOnCanvas(each);
|
||||
});
|
||||
}
|
||||
canvasCtx.restore();
|
||||
}
|
||||
const reflowAfterTimeoutCallback = (reflowParams: {
|
||||
movementMap: ReflowedSpaceMap;
|
||||
spacePositionMap: SpaceMap | undefined;
|
||||
}) => {
|
||||
currentReflowParams = { ...currentReflowParams, ...reflowParams };
|
||||
updateParamsPostReflow();
|
||||
isUpdatingRows = renderBlocks(
|
||||
currentRectanglesToDraw,
|
||||
currentReflowParams.spacePositionMap,
|
||||
isUpdatingRows,
|
||||
canvasIsDragging,
|
||||
scrollParent,
|
||||
);
|
||||
};
|
||||
|
||||
const drawBlockOnCanvas = (blockDimensions: WidgetDraggingBlock) => {
|
||||
if (
|
||||
stickyCanvasRef.current &&
|
||||
slidingArenaRef.current &&
|
||||
scrollParent &&
|
||||
isCurrentDraggedCanvas &&
|
||||
canvasIsDragging
|
||||
) {
|
||||
const canvasCtx: any = stickyCanvasRef.current.getContext("2d");
|
||||
const topOffset = getAbsolutePixels(
|
||||
stickyCanvasRef.current.style.top,
|
||||
);
|
||||
const leftOffset = getAbsolutePixels(
|
||||
stickyCanvasRef.current.style.left,
|
||||
);
|
||||
const snappedXY = getSnappedXY(
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
{
|
||||
x: blockDimensions.left,
|
||||
y: blockDimensions.top,
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
);
|
||||
|
||||
canvasCtx.fillStyle = `${
|
||||
blockDimensions.isNotColliding ? "rgb(104, 113, 239, 0.6)" : "red"
|
||||
}`;
|
||||
canvasCtx.fillRect(
|
||||
blockDimensions.left -
|
||||
leftOffset +
|
||||
(noPad ? 0 : CONTAINER_GRID_PADDING),
|
||||
blockDimensions.top -
|
||||
topOffset +
|
||||
(noPad ? 0 : CONTAINER_GRID_PADDING),
|
||||
blockDimensions.width,
|
||||
blockDimensions.height,
|
||||
);
|
||||
canvasCtx.fillStyle = `${
|
||||
blockDimensions.isNotColliding ? "rgb(233, 250, 243, 0.6)" : "red"
|
||||
}`;
|
||||
const strokeWidth = 1;
|
||||
canvasCtx.setLineDash([3]);
|
||||
canvasCtx.strokeStyle = "rgb(104, 113, 239)";
|
||||
canvasCtx.strokeRect(
|
||||
snappedXY.X -
|
||||
leftOffset +
|
||||
strokeWidth +
|
||||
(noPad ? 0 : CONTAINER_GRID_PADDING),
|
||||
snappedXY.Y -
|
||||
topOffset +
|
||||
strokeWidth +
|
||||
(noPad ? 0 : CONTAINER_GRID_PADDING),
|
||||
blockDimensions.width - strokeWidth,
|
||||
blockDimensions.height - strokeWidth,
|
||||
);
|
||||
}
|
||||
};
|
||||
// Adding setTimeout to make sure this gets called after
|
||||
// the onscroll that resets intersectionObserver in StickyCanvasArena.tsx
|
||||
const onScroll = () =>
|
||||
|
|
@ -705,12 +488,11 @@ export const useCanvasDragging = (
|
|||
});
|
||||
}
|
||||
}, 0);
|
||||
const captureMousePosition = (e: any) => {
|
||||
if (isDragging && !canvasIsDragging) {
|
||||
currentDirection.current = getMouseMoveDirection(e);
|
||||
}
|
||||
const onMouseOver = (e: any) => {
|
||||
onFirstMoveOnCanvas(e, true);
|
||||
};
|
||||
const onMouseOver = (e: any) => onFirstMoveOnCanvas(e, true);
|
||||
|
||||
//Initialize Listeners
|
||||
const initializeListeners = () => {
|
||||
slidingArenaRef.current?.addEventListener(
|
||||
"mousemove",
|
||||
|
|
@ -741,7 +523,6 @@ export const useCanvasDragging = (
|
|||
);
|
||||
document.body.addEventListener("mouseup", onMouseUp, false);
|
||||
window.addEventListener("mouseup", onMouseUp, false);
|
||||
window.addEventListener("mousemove", captureMousePosition);
|
||||
};
|
||||
const startDragging = () => {
|
||||
if (
|
||||
|
|
@ -781,7 +562,6 @@ export const useCanvasDragging = (
|
|||
);
|
||||
document.body.removeEventListener("mouseup", onMouseUp);
|
||||
window.removeEventListener("mouseup", onMouseUp);
|
||||
window.removeEventListener("mousemove", captureMousePosition);
|
||||
};
|
||||
} else {
|
||||
resetCanvasState();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,159 @@
|
|||
import { CONTAINER_GRID_PADDING } from "constants/WidgetConstants";
|
||||
import { useSelector } from "react-redux";
|
||||
import { SpaceMap } from "reflow/reflowTypes";
|
||||
import { getZoomLevel } from "selectors/editorSelectors";
|
||||
import { getAbsolutePixels } from "utils/helpers";
|
||||
import { modifyDrawingRectangles } from "./canvasDraggingUtils";
|
||||
import { WidgetDraggingBlock } from "./useBlocksToBeDraggedOnCanvas";
|
||||
|
||||
export interface XYCord {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a method that renders dragging blocks on canvas
|
||||
* @param slidingArenaRef DOM ref of Sliding Canvas
|
||||
* @param stickyCanvasRef DOM ref of Sticky Canvas
|
||||
* @param noPad Boolean to indicate if the container type widget has padding
|
||||
* @param snapColumnSpace width between columns
|
||||
* @param snapRowSpace height between rows
|
||||
* @param getSnappedXY Method that returns XY on the canvas Grid
|
||||
* @param isCurrentDraggedCanvas boolean if the current canvas is being dragged on
|
||||
* @returns
|
||||
*/
|
||||
export const useRenderBlocksOnCanvas = (
|
||||
slidingArenaRef: React.RefObject<HTMLDivElement>,
|
||||
stickyCanvasRef: React.RefObject<HTMLCanvasElement>,
|
||||
noPad: boolean,
|
||||
snapColumnSpace: number,
|
||||
snapRowSpace: number,
|
||||
getSnappedXY: (
|
||||
parentColumnWidth: number,
|
||||
parentRowHeight: number,
|
||||
currentOffset: XYCord,
|
||||
parentOffset: XYCord,
|
||||
) => {
|
||||
X: number;
|
||||
Y: number;
|
||||
},
|
||||
isCurrentDraggedCanvas: boolean,
|
||||
) => {
|
||||
const canvasZoomLevel = useSelector(getZoomLevel);
|
||||
|
||||
/**
|
||||
* draws the block on canvas
|
||||
* @param blockDimensions Dimensions of block to be drawn
|
||||
* @param scrollParent DOM element of parent
|
||||
*/
|
||||
const drawBlockOnCanvas = (
|
||||
blockDimensions: WidgetDraggingBlock,
|
||||
scrollParent: Element | null,
|
||||
) => {
|
||||
if (
|
||||
stickyCanvasRef.current &&
|
||||
slidingArenaRef.current &&
|
||||
scrollParent &&
|
||||
isCurrentDraggedCanvas
|
||||
) {
|
||||
const canvasCtx: any = stickyCanvasRef.current.getContext("2d");
|
||||
const topOffset = getAbsolutePixels(stickyCanvasRef.current.style.top);
|
||||
const leftOffset = getAbsolutePixels(stickyCanvasRef.current.style.left);
|
||||
const snappedXY = getSnappedXY(
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
{
|
||||
x: blockDimensions.left,
|
||||
y: blockDimensions.top,
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
);
|
||||
|
||||
canvasCtx.fillStyle = `${
|
||||
blockDimensions.isNotColliding
|
||||
? "rgb(104, 113, 239, 0.6)"
|
||||
: "rgb(255, 55, 35, 0.6)"
|
||||
}`;
|
||||
canvasCtx.fillRect(
|
||||
blockDimensions.left -
|
||||
leftOffset +
|
||||
(noPad ? 0 : CONTAINER_GRID_PADDING),
|
||||
blockDimensions.top - topOffset + (noPad ? 0 : CONTAINER_GRID_PADDING),
|
||||
blockDimensions.width,
|
||||
blockDimensions.height,
|
||||
);
|
||||
const strokeWidth = 1;
|
||||
canvasCtx.setLineDash([3]);
|
||||
canvasCtx.strokeStyle = blockDimensions.isNotColliding
|
||||
? "rgb(104, 113, 239)"
|
||||
: "red";
|
||||
canvasCtx.strokeRect(
|
||||
snappedXY.X -
|
||||
leftOffset +
|
||||
strokeWidth +
|
||||
(noPad ? 0 : CONTAINER_GRID_PADDING),
|
||||
snappedXY.Y -
|
||||
topOffset +
|
||||
strokeWidth +
|
||||
(noPad ? 0 : CONTAINER_GRID_PADDING),
|
||||
blockDimensions.width - strokeWidth,
|
||||
blockDimensions.height - strokeWidth,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* renders blocks on Canvas
|
||||
* @param rectanglesToDraw Rectangles that are to be drawn
|
||||
* @param spacePositionMap current dimensions of the dragging widgets
|
||||
* @param isUpdatingRows boolean
|
||||
* @param canvasIsDragging
|
||||
* @param scrollParent DOM element of parent
|
||||
* @returns
|
||||
*/
|
||||
const renderBlocks = (
|
||||
rectanglesToDraw: WidgetDraggingBlock[],
|
||||
spacePositionMap: SpaceMap | undefined,
|
||||
isUpdatingRows: boolean,
|
||||
canvasIsDragging: boolean,
|
||||
scrollParent: Element | null,
|
||||
) => {
|
||||
let isCurrUpdatingRows = isUpdatingRows;
|
||||
const modifiedRectanglesToDraw = modifyDrawingRectangles(
|
||||
rectanglesToDraw,
|
||||
spacePositionMap,
|
||||
snapColumnSpace,
|
||||
snapRowSpace,
|
||||
);
|
||||
if (
|
||||
slidingArenaRef.current &&
|
||||
isCurrentDraggedCanvas &&
|
||||
canvasIsDragging &&
|
||||
stickyCanvasRef.current
|
||||
) {
|
||||
const canvasCtx: any = stickyCanvasRef.current.getContext("2d");
|
||||
canvasCtx.save();
|
||||
canvasCtx.clearRect(
|
||||
0,
|
||||
0,
|
||||
stickyCanvasRef.current.width,
|
||||
stickyCanvasRef.current.height,
|
||||
);
|
||||
isCurrUpdatingRows = false;
|
||||
canvasCtx.transform(canvasZoomLevel, 0, 0, canvasZoomLevel, 0, 0);
|
||||
if (canvasIsDragging) {
|
||||
modifiedRectanglesToDraw.forEach((each) => {
|
||||
drawBlockOnCanvas(each, scrollParent);
|
||||
});
|
||||
}
|
||||
canvasCtx.restore();
|
||||
}
|
||||
|
||||
return isCurrUpdatingRows;
|
||||
};
|
||||
|
||||
return renderBlocks;
|
||||
};
|
||||
|
|
@ -4,7 +4,7 @@ import {
|
|||
ReduxActionTypes,
|
||||
} from "@appsmith/constants/ReduxActionConstants";
|
||||
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useCanvasDragging";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useRenderBlocksOnCanvas";
|
||||
|
||||
const initialState: CanvasSelectionState = {
|
||||
isDraggingForSelection: false,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { OccupiedSpace } from "constants/CanvasEditorConstants";
|
||||
import { getMovementMap } from "./reflowHelpers";
|
||||
import {
|
||||
BlockSpace,
|
||||
CollidingSpaceMap,
|
||||
CollisionMap,
|
||||
GridProps,
|
||||
|
|
@ -31,6 +32,7 @@ import {
|
|||
getCalculatedDirection,
|
||||
getOrientationAccessor,
|
||||
initializeMovementLimitMap,
|
||||
verifyMovementLimits,
|
||||
} from "./reflowUtils";
|
||||
|
||||
/**
|
||||
|
|
@ -46,18 +48,24 @@ import {
|
|||
* @param shouldResize boolean to indicate if colliding spaces should resize
|
||||
* @param prevReflowState this contains a map of reference to the key values of previous reflow method call to back trace widget movements
|
||||
* @param exitContainerId sting, Id of recent exit container
|
||||
* @param mousePosition mouse Position on canvas grid
|
||||
* @param shouldReflowDropTarget boolean which indicates if we should reflow drop targets
|
||||
* @param onTimeout indicates if the reflow is called on timeout
|
||||
* @returns movement information of the dragging/resizing space and other colliding spaces
|
||||
*/
|
||||
export function reflow(
|
||||
newSpacePositions: OccupiedSpace[],
|
||||
newSpacePositions: BlockSpace[],
|
||||
OGSpacePositions: OccupiedSpace[],
|
||||
occupiedSpaces: OccupiedSpace[],
|
||||
occupiedSpaces: BlockSpace[],
|
||||
direction: ReflowDirection,
|
||||
gridProps: GridProps,
|
||||
forceDirection = false,
|
||||
shouldResize = true,
|
||||
prevReflowState: PrevReflowState = {} as PrevReflowState,
|
||||
exitContainerId?: string,
|
||||
mousePosition?: OccupiedSpace,
|
||||
shouldReflowDropTarget = true,
|
||||
onTimeout = false,
|
||||
) {
|
||||
const newSpacePositionsMap = getSpacesMapFromArray(newSpacePositions);
|
||||
const OGSpacePositionsMap = getSpacesMapFromArray(OGSpacePositions);
|
||||
|
|
@ -70,7 +78,7 @@ export function reflow(
|
|||
);
|
||||
|
||||
//initializing variables
|
||||
const movementLimitMap: MovementLimitMap = initializeMovementLimitMap(
|
||||
let movementLimitMap: MovementLimitMap = initializeMovementLimitMap(
|
||||
newSpacePositions,
|
||||
);
|
||||
const globalCollidingSpaces: CollidingSpaceMap = {
|
||||
|
|
@ -112,10 +120,12 @@ export function reflow(
|
|||
//Reflow in the primary orientation
|
||||
const {
|
||||
collidingSpaces: primaryCollidingSpaces,
|
||||
currSpacePositionMap: primarySpacePositionMap,
|
||||
isColliding: primaryIsColliding,
|
||||
movementMap: primaryMovementMap,
|
||||
movementVariablesMap: primaryMovementVariablesMap,
|
||||
secondOrderCollisionMap: primarySecondOrderCollisionMap,
|
||||
shouldRegisterContainerTimeout: primaryShouldRegisterContainerTimeout,
|
||||
} = getOrientationalMovementInfo(
|
||||
newSpacePositionsMap,
|
||||
occupiedSpacesMap,
|
||||
|
|
@ -126,6 +136,9 @@ export function reflow(
|
|||
shouldResize,
|
||||
forceDirection,
|
||||
exitContainerId,
|
||||
shouldReflowDropTarget,
|
||||
onTimeout,
|
||||
mousePosition,
|
||||
maxSpaceAttributes.primary,
|
||||
prevReflowState,
|
||||
);
|
||||
|
|
@ -141,12 +154,14 @@ export function reflow(
|
|||
|
||||
const {
|
||||
collidingSpaces: secondaryCollidingSpaces,
|
||||
currSpacePositionMap: secondarySpacePositionMap,
|
||||
isColliding: secondaryIsColliding,
|
||||
movementMap,
|
||||
movementVariablesMap: secondaryMovementVariablesMap,
|
||||
secondOrderCollisionMap,
|
||||
shouldRegisterContainerTimeout: secondaryShouldRegisterContainerTimeout,
|
||||
} = getOrientationalMovementInfo(
|
||||
newSpacePositionsMap,
|
||||
primarySpacePositionMap,
|
||||
occupiedSpacesMap,
|
||||
currentDirection,
|
||||
!isHorizontal,
|
||||
|
|
@ -155,6 +170,9 @@ export function reflow(
|
|||
shouldResize,
|
||||
forceDirection,
|
||||
exitContainerId,
|
||||
shouldReflowDropTarget,
|
||||
onTimeout,
|
||||
mousePosition,
|
||||
maxSpaceAttributes.secondary,
|
||||
prevReflowState,
|
||||
primaryMovementMap || {},
|
||||
|
|
@ -169,8 +187,23 @@ export function reflow(
|
|||
getShouldReflow(movementLimitMap, secondaryMovementVariablesMap, delta);
|
||||
}
|
||||
|
||||
// If we are not reflowing drop targets, verify the limits of dragging widget
|
||||
if (!shouldReflowDropTarget && newSpacePositions.length === 1) {
|
||||
movementLimitMap = verifyMovementLimits(
|
||||
movementLimitMap,
|
||||
secondarySpacePositionMap,
|
||||
occupiedSpacesMap,
|
||||
);
|
||||
}
|
||||
|
||||
if (!primaryIsColliding && !secondaryIsColliding) {
|
||||
return { movementLimitMap };
|
||||
return {
|
||||
movementLimitMap,
|
||||
spacePositionMap: secondarySpacePositionMap,
|
||||
shouldRegisterContainerTimeout:
|
||||
primaryShouldRegisterContainerTimeout ||
|
||||
secondaryShouldRegisterContainerTimeout,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -179,6 +212,10 @@ export function reflow(
|
|||
collidingSpaceMap: globalCollidingSpaces,
|
||||
secondOrderCollisionMap:
|
||||
secondOrderCollisionMap || primarySecondOrderCollisionMap,
|
||||
shouldRegisterContainerTimeout:
|
||||
primaryShouldRegisterContainerTimeout ||
|
||||
secondaryShouldRegisterContainerTimeout,
|
||||
spacePositionMap: secondarySpacePositionMap,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -195,6 +232,9 @@ export function reflow(
|
|||
* @param shouldResize boolean to indicate if colliding spaces should resize
|
||||
* @param forceDirection boolean to force the direction on certain scenarios
|
||||
* @param exitContainerId string, Id of recent exit container
|
||||
* @param shouldReflowDropTarget boolean which indicates if we should reflow drop targets
|
||||
* @param onTimeout indicates if the reflow is called on timeout
|
||||
* @param mousePosition mouse Position on canvas grid
|
||||
* @param maxSpaceAttributes object containing accessors for maximum and minimum dimensions in a particular direction
|
||||
* @param prevReflowState this contains a map of reference to the key values of previous reflow method call to back trace widget movements
|
||||
* @param primaryMovementMap movement map/information from previous run of the algorithm
|
||||
|
|
@ -211,6 +251,9 @@ function getOrientationalMovementInfo(
|
|||
shouldResize: boolean,
|
||||
forceDirection: boolean,
|
||||
exitContainerId: string | undefined,
|
||||
shouldReflowDropTarget = true,
|
||||
onTimeout = false,
|
||||
mousePosition: OccupiedSpace | undefined,
|
||||
maxSpaceAttributes: { max: SpaceAttributes; min: SpaceAttributes },
|
||||
prevReflowState: PrevReflowState,
|
||||
primaryMovementMap?: ReflowedSpaceMap,
|
||||
|
|
@ -250,7 +293,12 @@ function getOrientationalMovementInfo(
|
|||
(prevCollidingSpaceMap && prevCollidingSpaceMap[orientationAccessor]) || {};
|
||||
|
||||
//gets a map of all colliding spaces of the current dragging spaces
|
||||
const { collidingSpaceMap, isColliding } = getCollidingSpaceMap(
|
||||
const {
|
||||
collidingSpaceMap,
|
||||
currSpacePositions,
|
||||
isColliding,
|
||||
shouldRegisterContainerTimeout,
|
||||
} = getCollidingSpaceMap(
|
||||
newSpacePositions,
|
||||
sortedOccupiedSpaces,
|
||||
direction,
|
||||
|
|
@ -259,18 +307,32 @@ function getOrientationalMovementInfo(
|
|||
prevSpacesMap,
|
||||
forceDirection,
|
||||
primaryCollisionMap,
|
||||
shouldReflowDropTarget,
|
||||
onTimeout,
|
||||
mousePosition,
|
||||
);
|
||||
|
||||
const currSpacePositionMap = getSpacesMapFromArray(currSpacePositions);
|
||||
const collidingSpaces = getSortedCollidingSpaces(
|
||||
collidingSpaceMap,
|
||||
isHorizontal,
|
||||
prevCollisionMap,
|
||||
);
|
||||
|
||||
if (!collidingSpaces.length) return {};
|
||||
if (!collidingSpaces.length)
|
||||
return { currSpacePositionMap, shouldRegisterContainerTimeout };
|
||||
|
||||
if (!primaryMovementMap) {
|
||||
changeExitContainerDirection(collidingSpaceMap, exitContainerId, direction);
|
||||
if (
|
||||
!primaryMovementMap &&
|
||||
shouldReflowDropTarget &&
|
||||
Object.keys(currSpacePositionMap).length === 1
|
||||
) {
|
||||
changeExitContainerDirection(
|
||||
collidingSpaceMap,
|
||||
exitContainerId,
|
||||
mousePosition,
|
||||
currSpacePositionMap,
|
||||
);
|
||||
}
|
||||
|
||||
//if it is the first orientation, we use the original positions of the occupiedSpaces
|
||||
|
|
@ -286,8 +348,8 @@ function getOrientationalMovementInfo(
|
|||
movementVariablesMap,
|
||||
secondOrderCollisionMap,
|
||||
} = getMovementMap(
|
||||
newSpacePositions,
|
||||
newSpacePositionsMap,
|
||||
currSpacePositions,
|
||||
currSpacePositionMap,
|
||||
currentOccupiedSpaces,
|
||||
currentOccupiedSpacesMap,
|
||||
occupiedSpacesMap,
|
||||
|
|
@ -309,5 +371,7 @@ function getOrientationalMovementInfo(
|
|||
secondOrderCollisionMap,
|
||||
isColliding,
|
||||
collidingSpaces,
|
||||
currSpacePositionMap,
|
||||
shouldRegisterContainerTimeout,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@ import {
|
|||
DirectionalMovement,
|
||||
DirectionalVariables,
|
||||
GridProps,
|
||||
HORIZONTAL_RESIZE_LIMIT,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
PrevReflowState,
|
||||
ReflowDirection,
|
||||
ReflowedSpaceMap,
|
||||
SecondOrderCollisionMap,
|
||||
SpaceMap,
|
||||
SpaceMovementMap,
|
||||
VERTICAL_RESIZE_LIMIT,
|
||||
VERTICAL_RESIZE_MIN_LIMIT,
|
||||
} from "./reflowTypes";
|
||||
import {
|
||||
checkReCollisionWithOtherNewSpacePositions,
|
||||
|
|
@ -498,10 +498,10 @@ function getCollisionTreeHelper(
|
|||
occupiedLength:
|
||||
occupiedLength +
|
||||
(accessors.isHorizontal
|
||||
? HORIZONTAL_RESIZE_LIMIT
|
||||
? HORIZONTAL_RESIZE_MIN_LIMIT
|
||||
: collidingSpace.fixedHeight && accessors.directionIndicator < 0
|
||||
? collidingSpace.fixedHeight
|
||||
: VERTICAL_RESIZE_LIMIT),
|
||||
: VERTICAL_RESIZE_MIN_LIMIT),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -737,7 +737,7 @@ function getMovementMapHelper(
|
|||
collisionTree[accessors.parallelMin],
|
||||
occupiedLength:
|
||||
(movementMap[collisionTree.id].horizontalOccupiedLength || 0) +
|
||||
HORIZONTAL_RESIZE_LIMIT,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
currentEmptySpaces:
|
||||
(movementMap[collisionTree.id].horizontalEmptySpaces as number) ||
|
||||
0,
|
||||
|
|
@ -751,7 +751,7 @@ function getMovementMapHelper(
|
|||
(movementMap[collisionTree.id].verticalOccupiedLength || 0) +
|
||||
(collisionTree.fixedHeight && accessors.directionIndicator < 0
|
||||
? collisionTree.fixedHeight
|
||||
: VERTICAL_RESIZE_LIMIT),
|
||||
: VERTICAL_RESIZE_MIN_LIMIT),
|
||||
currentEmptySpaces:
|
||||
(movementMap[collisionTree.id].verticalEmptySpaces as number) || 0,
|
||||
};
|
||||
|
|
@ -770,10 +770,10 @@ function getMovementMapHelper(
|
|||
occupiedLength:
|
||||
occupiedLength +
|
||||
(accessors.isHorizontal
|
||||
? HORIZONTAL_RESIZE_LIMIT
|
||||
? HORIZONTAL_RESIZE_MIN_LIMIT
|
||||
: collisionTree.fixedHeight && accessors.directionIndicator < 0
|
||||
? collisionTree.fixedHeight
|
||||
: VERTICAL_RESIZE_LIMIT),
|
||||
: VERTICAL_RESIZE_MIN_LIMIT),
|
||||
currentEmptySpaces,
|
||||
};
|
||||
}
|
||||
|
|
@ -822,7 +822,7 @@ export function getHorizontalSpaceMovement(
|
|||
distanceBeforeCollision,
|
||||
gridProps.parentColumnSpace,
|
||||
emptySpaces,
|
||||
HORIZONTAL_RESIZE_LIMIT,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
shouldResize,
|
||||
);
|
||||
const spaceMovement = {
|
||||
|
|
@ -891,7 +891,7 @@ export function getVerticalSpaceMovement(
|
|||
distanceBeforeCollision,
|
||||
gridProps.parentRowSpace,
|
||||
emptySpaces,
|
||||
VERTICAL_RESIZE_LIMIT,
|
||||
VERTICAL_RESIZE_MIN_LIMIT,
|
||||
shouldResize,
|
||||
);
|
||||
const spaceMovement = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { OccupiedSpace } from "constants/CanvasEditorConstants";
|
||||
|
||||
export const HORIZONTAL_RESIZE_LIMIT = 2;
|
||||
export const VERTICAL_RESIZE_LIMIT = 4;
|
||||
export const HORIZONTAL_RESIZE_MIN_LIMIT = 2;
|
||||
export const VERTICAL_RESIZE_MIN_LIMIT = 4;
|
||||
|
||||
export enum ReflowDirection {
|
||||
LEFT = "LEFT",
|
||||
|
|
@ -42,6 +42,7 @@ export type CollisionAccessors = {
|
|||
parallelMax: SpaceAttributes;
|
||||
parallelMin: SpaceAttributes;
|
||||
mathComparator: MathComparators;
|
||||
oppositeMathComparator: MathComparators;
|
||||
directionIndicator: 1 | -1;
|
||||
isHorizontal: boolean;
|
||||
plane: "vertical" | "horizontal";
|
||||
|
|
@ -52,7 +53,7 @@ export type Delta = {
|
|||
Y: number;
|
||||
};
|
||||
|
||||
export type CollidingSpace = OccupiedSpace & {
|
||||
export type CollidingSpace = BlockSpace & {
|
||||
direction: ReflowDirection;
|
||||
collidingValue: number;
|
||||
collidingId: string;
|
||||
|
|
@ -61,9 +62,9 @@ export type CollidingSpace = OccupiedSpace & {
|
|||
fixedHeight?: number;
|
||||
};
|
||||
|
||||
export type SecondOrderCollision = OccupiedSpace & {
|
||||
export type SecondOrderCollision = BlockSpace & {
|
||||
children: {
|
||||
[key: string]: OccupiedSpace & {
|
||||
[key: string]: BlockSpace & {
|
||||
direction: ReflowDirection;
|
||||
isHorizontal: boolean;
|
||||
processed?: boolean;
|
||||
|
|
@ -87,7 +88,7 @@ export type CollisionMap = {
|
|||
[key: string]: CollidingSpace;
|
||||
};
|
||||
|
||||
export type CollisionTree = OccupiedSpace & {
|
||||
export type CollisionTree = BlockSpace & {
|
||||
direction: ReflowDirection;
|
||||
children?: {
|
||||
[key: string]: CollisionTree;
|
||||
|
|
@ -154,7 +155,12 @@ export type PrevReflowState = {
|
|||
prevSecondOrderCollisionMap: SecondOrderCollisionMap;
|
||||
};
|
||||
|
||||
export type SpaceMap = { [id: string]: OccupiedSpace };
|
||||
export type BlockSpace = OccupiedSpace & {
|
||||
isDropTarget?: boolean;
|
||||
fixedHeight?: number;
|
||||
};
|
||||
|
||||
export type SpaceMap = { [id: string]: BlockSpace };
|
||||
|
||||
export type DirectionalVariables = {
|
||||
[key: string]: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { OccupiedSpace } from "constants/CanvasEditorConstants";
|
||||
import { cloneDeep, isUndefined } from "lodash";
|
||||
import { Rect } from "utils/boxHelpers";
|
||||
import { areIntersecting, Rect } from "utils/boxHelpers";
|
||||
import {
|
||||
BlockSpace,
|
||||
CollidingSpace,
|
||||
CollidingSpaceMap,
|
||||
CollisionAccessors,
|
||||
|
|
@ -9,6 +10,7 @@ import {
|
|||
CollisionTree,
|
||||
CollisionTreeCache,
|
||||
GridProps,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
MathComparators,
|
||||
MovementLimitMap,
|
||||
OrientationAccessors,
|
||||
|
|
@ -20,6 +22,7 @@ import {
|
|||
SpaceAttributes,
|
||||
SpaceMap,
|
||||
SpaceMovementMap,
|
||||
VERTICAL_RESIZE_MIN_LIMIT,
|
||||
} from "./reflowTypes";
|
||||
|
||||
/**
|
||||
|
|
@ -276,17 +279,24 @@ export function getDelta(
|
|||
* @param prevSpacesMap
|
||||
* @param forceDirection
|
||||
* @param primaryCollisionMap
|
||||
* @param shouldReflowDropTarget boolean which indicates if we should reflow drop targets
|
||||
* @param onTimeout indicates if the reflow is called on timeout
|
||||
* @param mousePosition mouse Position on canvas grid
|
||||
* @returns collision spaces Map
|
||||
*/
|
||||
|
||||
export function getCollidingSpaceMap(
|
||||
newSpacePositions: OccupiedSpace[],
|
||||
occupiedSpaces: OccupiedSpace[],
|
||||
newSpacePositions: BlockSpace[],
|
||||
occupiedSpaces: BlockSpace[],
|
||||
direction: ReflowDirection,
|
||||
prevCollidingSpaceMap: CollidingSpaceMap,
|
||||
isHorizontalMove?: boolean,
|
||||
prevSpacesMap?: SpaceMap,
|
||||
forceDirection = false,
|
||||
primaryCollisionMap?: CollisionMap,
|
||||
shouldReflowDropTarget = true,
|
||||
onTimeOut = false,
|
||||
mousePosition?: OccupiedSpace | undefined,
|
||||
) {
|
||||
let isColliding = false;
|
||||
const collidingSpaceMap: CollisionMap = {};
|
||||
|
|
@ -296,8 +306,34 @@ export function getCollidingSpaceMap(
|
|||
!isHorizontalMove,
|
||||
);
|
||||
|
||||
for (const newSpacePosition of newSpacePositions) {
|
||||
for (const occupiedSpace of occupiedSpaces) {
|
||||
let reflowableOccSpaces = [...occupiedSpaces],
|
||||
currSpacePositions = [...newSpacePositions];
|
||||
|
||||
let shouldRegisterContainerTimeout = false;
|
||||
|
||||
//if droptargets are not to be reflowed, resize space positions
|
||||
// and omit drop targets from the spaces
|
||||
if (!shouldReflowDropTarget) {
|
||||
// reset values based on function's result
|
||||
({
|
||||
currSpacePositions,
|
||||
reflowableOccSpaces,
|
||||
shouldRegisterContainerTimeout,
|
||||
} = resizeOnContainerCollision(
|
||||
newSpacePositions,
|
||||
occupiedSpaces,
|
||||
mousePosition,
|
||||
direction,
|
||||
orientationalAccessor,
|
||||
prevCollidingSpaceMap,
|
||||
forceDirection,
|
||||
isHorizontalMove,
|
||||
prevSpacesMap,
|
||||
));
|
||||
}
|
||||
|
||||
for (const newSpacePosition of currSpacePositions) {
|
||||
for (const occupiedSpace of reflowableOccSpaces) {
|
||||
if (areOverlapping(occupiedSpace, newSpacePosition)) {
|
||||
isColliding = true;
|
||||
const currentSpaceId = occupiedSpace.id;
|
||||
|
|
@ -342,6 +378,14 @@ export function getCollidingSpaceMap(
|
|||
].direction;
|
||||
}
|
||||
|
||||
if (occupiedSpace.isDropTarget && onTimeOut && !forceDirection) {
|
||||
movementDirection = getCollisionDirectionOfDropTarget(
|
||||
occupiedSpace,
|
||||
movementDirection,
|
||||
mousePosition,
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
direction: directionAccessor,
|
||||
directionIndicator,
|
||||
|
|
@ -379,6 +423,8 @@ export function getCollidingSpaceMap(
|
|||
return {
|
||||
isColliding,
|
||||
collidingSpaceMap,
|
||||
currSpacePositions,
|
||||
shouldRegisterContainerTimeout,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -403,13 +449,13 @@ export function getCollidingSpaceMap(
|
|||
*/
|
||||
export function getCollidingSpacesInDirection(
|
||||
newSpacePosition: CollidingSpace,
|
||||
OGPosition: OccupiedSpace,
|
||||
OGPosition: BlockSpace,
|
||||
globalDirection: ReflowDirection,
|
||||
direction: ReflowDirection,
|
||||
gridProps: GridProps,
|
||||
prevReflowState: PrevReflowState,
|
||||
globalCollisionMap: CollisionMap,
|
||||
occupiedSpaces?: OccupiedSpace[],
|
||||
occupiedSpaces?: BlockSpace[],
|
||||
isDirectCollidingSpace = false,
|
||||
) {
|
||||
const collidingSpaces: CollidingSpace[] = [];
|
||||
|
|
@ -513,7 +559,7 @@ export function getCollidingSpacesInDirection(
|
|||
*/
|
||||
export function ShouldAddToCollisionSpacesArray(
|
||||
newSpacePosition: CollidingSpace,
|
||||
OGPosition: OccupiedSpace,
|
||||
OGPosition: BlockSpace,
|
||||
collidingSpace: OccupiedSpace,
|
||||
direction: ReflowDirection,
|
||||
accessor: CollisionAccessors,
|
||||
|
|
@ -659,11 +705,11 @@ export function ShouldAddToCollisionSpacesArray(
|
|||
* @returns filtered array of occupied space
|
||||
*/
|
||||
export function filterSpaceByDirection(
|
||||
newSpacePosition: OccupiedSpace,
|
||||
occupiedSpaces: OccupiedSpace[] | undefined,
|
||||
newSpacePosition: BlockSpace,
|
||||
occupiedSpaces: BlockSpace[] | undefined,
|
||||
direction: ReflowDirection,
|
||||
): OccupiedSpace[] {
|
||||
let filteredSpaces: OccupiedSpace[] = [];
|
||||
): BlockSpace[] {
|
||||
let filteredSpaces: BlockSpace[] = [];
|
||||
|
||||
const {
|
||||
direction: directionAccessor,
|
||||
|
|
@ -931,6 +977,7 @@ export function getAccessor(direction: ReflowDirection): CollisionAccessors {
|
|||
parallelMax: SpaceAttributes.right,
|
||||
parallelMin: SpaceAttributes.left,
|
||||
mathComparator: MathComparators.max,
|
||||
oppositeMathComparator: MathComparators.min,
|
||||
directionIndicator: -1,
|
||||
isHorizontal: true,
|
||||
plane: "horizontal",
|
||||
|
|
@ -944,6 +991,7 @@ export function getAccessor(direction: ReflowDirection): CollisionAccessors {
|
|||
parallelMax: SpaceAttributes.right,
|
||||
parallelMin: SpaceAttributes.left,
|
||||
mathComparator: MathComparators.min,
|
||||
oppositeMathComparator: MathComparators.max,
|
||||
directionIndicator: 1,
|
||||
isHorizontal: true,
|
||||
plane: "horizontal",
|
||||
|
|
@ -957,6 +1005,7 @@ export function getAccessor(direction: ReflowDirection): CollisionAccessors {
|
|||
parallelMax: SpaceAttributes.bottom,
|
||||
parallelMin: SpaceAttributes.top,
|
||||
mathComparator: MathComparators.max,
|
||||
oppositeMathComparator: MathComparators.min,
|
||||
directionIndicator: -1,
|
||||
isHorizontal: false,
|
||||
plane: "vertical",
|
||||
|
|
@ -970,6 +1019,7 @@ export function getAccessor(direction: ReflowDirection): CollisionAccessors {
|
|||
parallelMax: SpaceAttributes.bottom,
|
||||
parallelMin: SpaceAttributes.top,
|
||||
mathComparator: MathComparators.min,
|
||||
oppositeMathComparator: MathComparators.max,
|
||||
directionIndicator: 1,
|
||||
isHorizontal: false,
|
||||
plane: "vertical",
|
||||
|
|
@ -983,6 +1033,7 @@ export function getAccessor(direction: ReflowDirection): CollisionAccessors {
|
|||
parallelMax: SpaceAttributes.bottom,
|
||||
parallelMin: SpaceAttributes.top,
|
||||
mathComparator: MathComparators.min,
|
||||
oppositeMathComparator: MathComparators.max,
|
||||
directionIndicator: 1,
|
||||
isHorizontal: false,
|
||||
plane: "vertical",
|
||||
|
|
@ -1237,28 +1288,46 @@ function replaceMovementMapByDirection(
|
|||
*
|
||||
* @param collidingSpaceMap
|
||||
* @param exitContainerId
|
||||
* @param mousePosition mouse Position on canvas grid
|
||||
* @param spacePositionMap
|
||||
* @param direction
|
||||
* changes reference of collidingSpaceMap
|
||||
*/
|
||||
export function changeExitContainerDirection(
|
||||
collidingSpaceMap: CollisionMap,
|
||||
exitContainerId: string | undefined,
|
||||
direction: ReflowDirection,
|
||||
mousePosition: OccupiedSpace | undefined,
|
||||
spacePositionMap: SpaceMap,
|
||||
) {
|
||||
if (!exitContainerId || !collidingSpaceMap[exitContainerId]) {
|
||||
if (
|
||||
!exitContainerId ||
|
||||
!collidingSpaceMap[exitContainerId] ||
|
||||
!mousePosition
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oppDirection = getOppositeDirection(direction);
|
||||
const { directionIndicator, oppositeDirection } = getAccessor(oppDirection);
|
||||
const exitEdgeDirection = getContainerExitEdge(
|
||||
collidingSpaceMap[exitContainerId],
|
||||
mousePosition,
|
||||
);
|
||||
|
||||
if (!exitEdgeDirection) return;
|
||||
|
||||
const {
|
||||
direction: directionAccessor,
|
||||
directionIndicator,
|
||||
oppositeDirection: exitDirectionAccessor,
|
||||
} = getAccessor(exitEdgeDirection);
|
||||
|
||||
const collidingSpaces: CollidingSpace[] = Object.values(collidingSpaceMap);
|
||||
const oppositeFrom = collidingSpaceMap[exitContainerId][oppositeDirection];
|
||||
const oppositeFrom =
|
||||
collidingSpaceMap[exitContainerId][exitDirectionAccessor];
|
||||
|
||||
const oppositeSpaceIds = collidingSpaces
|
||||
.filter((collidingSpace: CollidingSpace) => {
|
||||
return compareNumbers(
|
||||
collidingSpace[oppositeDirection],
|
||||
collidingSpace[exitDirectionAccessor],
|
||||
oppositeFrom,
|
||||
directionIndicator > 0,
|
||||
true,
|
||||
|
|
@ -1267,8 +1336,20 @@ export function changeExitContainerDirection(
|
|||
.map((collidingSpace: CollidingSpace) => collidingSpace.id);
|
||||
|
||||
for (const spaceId of oppositeSpaceIds) {
|
||||
collidingSpaceMap[spaceId].direction = oppDirection;
|
||||
collidingSpaceMap[spaceId].direction = exitEdgeDirection;
|
||||
collidingSpaceMap[spaceId].collidingValue =
|
||||
spacePositionMap[collidingSpaceMap[spaceId].collidingId][
|
||||
directionAccessor
|
||||
];
|
||||
}
|
||||
|
||||
collidingSpaceMap[exitContainerId].direction = getOppositeDirection(
|
||||
exitEdgeDirection,
|
||||
);
|
||||
collidingSpaceMap[exitContainerId].collidingValue =
|
||||
spacePositionMap[collidingSpaceMap[exitContainerId].collidingId][
|
||||
exitDirectionAccessor
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1277,9 +1358,7 @@ export function changeExitContainerDirection(
|
|||
* @param spacesArray
|
||||
* @returns space map
|
||||
*/
|
||||
export function getSpacesMapFromArray(
|
||||
spacesArray: OccupiedSpace[] | undefined,
|
||||
) {
|
||||
export function getSpacesMapFromArray(spacesArray: BlockSpace[] | undefined) {
|
||||
if (!spacesArray) return {};
|
||||
const spacesMap: SpaceMap = {};
|
||||
for (const space of spacesArray) {
|
||||
|
|
@ -1445,10 +1524,10 @@ export function getModifiedCollidingSpace(
|
|||
*/
|
||||
export function checkReCollisionWithOtherNewSpacePositions(
|
||||
collidingSpace: CollidingSpace,
|
||||
OGCollidingSpacePosition: OccupiedSpace,
|
||||
OGCollidingSpacePosition: BlockSpace,
|
||||
globalDirection: ReflowDirection,
|
||||
direction: ReflowDirection,
|
||||
newSpacePositions: OccupiedSpace[],
|
||||
newSpacePositions: BlockSpace[],
|
||||
globalCollidingSpaces: CollidingSpace[],
|
||||
insertionIndex: number,
|
||||
globalProcessedNodes: CollisionTreeCache,
|
||||
|
|
@ -1898,3 +1977,292 @@ export function getRelativeCollidingValue(
|
|||
collidingValue,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edge from which the widget just exited
|
||||
* @param exitContainer Id of the container that was just exited
|
||||
* @param mousePosition position of mouse on Canvas Grid
|
||||
* @returns
|
||||
*/
|
||||
export function getContainerExitEdge(
|
||||
exitContainer: OccupiedSpace,
|
||||
mousePosition: OccupiedSpace,
|
||||
) {
|
||||
if (
|
||||
mousePosition.top > exitContainer.top &&
|
||||
mousePosition.top < exitContainer.bottom
|
||||
) {
|
||||
if (mousePosition.left >= exitContainer.right) return ReflowDirection.RIGHT;
|
||||
if (mousePosition.left <= exitContainer.left) return ReflowDirection.LEFT;
|
||||
}
|
||||
|
||||
if (
|
||||
mousePosition.left > exitContainer.left &&
|
||||
mousePosition.left < exitContainer.right
|
||||
) {
|
||||
if (mousePosition.top >= exitContainer.bottom)
|
||||
return ReflowDirection.BOTTOM;
|
||||
if (mousePosition.top <= exitContainer.top) return ReflowDirection.TOP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are not reflowing the drop targets,
|
||||
* then we will have to figure out the direction in which it is colliding with dragging Spaces
|
||||
* @param containerSpace Space positions of Container/Droptargets
|
||||
* @param currentDirection current Direction
|
||||
* @param mousePosition Position of mouse on Canvas Grid
|
||||
* @returns
|
||||
*/
|
||||
export function getCollisionDirectionOfDropTarget(
|
||||
containerSpace: BlockSpace,
|
||||
currentDirection: ReflowDirection,
|
||||
mousePosition?: OccupiedSpace,
|
||||
): ReflowDirection {
|
||||
const possiblePushDirections = getPossiblePushDirections(
|
||||
containerSpace,
|
||||
mousePosition,
|
||||
);
|
||||
|
||||
if (
|
||||
possiblePushDirections.length < 1 ||
|
||||
possiblePushDirections.includes(currentDirection)
|
||||
) {
|
||||
return currentDirection;
|
||||
}
|
||||
|
||||
return possiblePushDirections[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the possible directions the Containers/drop targets can be pushed based on mousePosition
|
||||
* @param containerSpace Space positions of Container/Droptargets
|
||||
* @param mousePosition Position of mouse on Canvas Grid
|
||||
* @returns Array of Possible directions, at the max two directions based on mouse positions,
|
||||
* sorted by distance to container of mouse position
|
||||
*/
|
||||
function getPossiblePushDirections(
|
||||
containerSpace: BlockSpace,
|
||||
mousePosition?: OccupiedSpace,
|
||||
): ReflowDirection[] {
|
||||
if (!mousePosition) return [];
|
||||
const directionsWithDistance: {
|
||||
distance: number;
|
||||
direction: ReflowDirection;
|
||||
}[] = [];
|
||||
|
||||
if (containerSpace.left >= mousePosition.left) {
|
||||
directionsWithDistance.push({
|
||||
distance: containerSpace.left - mousePosition.left,
|
||||
direction: ReflowDirection.RIGHT,
|
||||
});
|
||||
} else if (mousePosition.left >= containerSpace.right) {
|
||||
directionsWithDistance.push({
|
||||
distance: mousePosition.left - containerSpace.right,
|
||||
direction: ReflowDirection.LEFT,
|
||||
});
|
||||
}
|
||||
|
||||
if (containerSpace.top >= mousePosition.top) {
|
||||
directionsWithDistance.push({
|
||||
distance: containerSpace.top - mousePosition.top,
|
||||
direction: ReflowDirection.BOTTOM,
|
||||
});
|
||||
} else if (mousePosition.top >= containerSpace.bottom) {
|
||||
directionsWithDistance.push({
|
||||
distance: mousePosition.top - containerSpace.bottom,
|
||||
direction: ReflowDirection.TOP,
|
||||
});
|
||||
}
|
||||
|
||||
return directionsWithDistance
|
||||
.sort((a, b) => {
|
||||
return b.distance - a.distance;
|
||||
})
|
||||
.map((obj) => obj.direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the dragging widget on collision with Container/Droptarget widget
|
||||
* @param newSpacePositions positions of dragging spaces
|
||||
* @param occupiedSpaces occupied spaces of other blocks on the canvas
|
||||
* @param mousePosition position of mouse on canvas grid
|
||||
* @param direction ReflowDirection
|
||||
* @param orientationalAccessor "vertical" or "horizontal"
|
||||
* @param prevCollidingSpaceMap previous colliding map
|
||||
* @param forceDirection boolean to indicate if direction should be forced
|
||||
* @param isHorizontalMove boolean
|
||||
* @param prevSpacesMap previous position maps
|
||||
* @returns
|
||||
*/
|
||||
export function resizeOnContainerCollision(
|
||||
newSpacePositions: BlockSpace[],
|
||||
occupiedSpaces: BlockSpace[],
|
||||
mousePosition: OccupiedSpace | undefined,
|
||||
direction: ReflowDirection,
|
||||
orientationalAccessor: "horizontal" | "vertical",
|
||||
prevCollidingSpaceMap: CollidingSpaceMap,
|
||||
forceDirection: boolean,
|
||||
isHorizontalMove?: boolean,
|
||||
prevSpacesMap?: SpaceMap,
|
||||
): {
|
||||
reflowableOccSpaces: BlockSpace[];
|
||||
currSpacePositions: BlockSpace[];
|
||||
shouldRegisterContainerTimeout: boolean;
|
||||
} {
|
||||
const reflowableOccSpaces = [];
|
||||
|
||||
//boolean to indicate if this run should be registered for timeout
|
||||
let shouldRegisterContainerTimeout = false;
|
||||
// resize space positions only is single space is being dragged
|
||||
if (newSpacePositions.length > 1) {
|
||||
return {
|
||||
reflowableOccSpaces: occupiedSpaces.filter(
|
||||
(occSpace) => !occSpace.isDropTarget,
|
||||
),
|
||||
currSpacePositions: newSpacePositions,
|
||||
shouldRegisterContainerTimeout: occupiedSpaces.some(
|
||||
(space) => space.isDropTarget,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
let currSpacePosition = { ...newSpacePositions[0] };
|
||||
for (const occupiedSpace of occupiedSpaces) {
|
||||
if (areOverlapping(occupiedSpace, currSpacePosition)) {
|
||||
if (!occupiedSpace.isDropTarget) {
|
||||
reflowableOccSpaces.push(occupiedSpace);
|
||||
continue;
|
||||
}
|
||||
|
||||
//get calculated direction
|
||||
let movementDirection = getCorrectedDirection(
|
||||
occupiedSpace,
|
||||
prevSpacesMap && prevSpacesMap[currSpacePosition.id]
|
||||
? prevSpacesMap[currSpacePosition.id]
|
||||
: undefined,
|
||||
direction,
|
||||
false,
|
||||
prevCollidingSpaceMap && prevCollidingSpaceMap[orientationalAccessor],
|
||||
isHorizontalMove,
|
||||
);
|
||||
|
||||
//check if direction could be changed because of mouse Position
|
||||
movementDirection = getCollisionDirectionOfDropTarget(
|
||||
occupiedSpace,
|
||||
movementDirection,
|
||||
mousePosition,
|
||||
);
|
||||
|
||||
//modify/resize dragging position if required
|
||||
currSpacePosition = modifyResizePosition(
|
||||
currSpacePosition,
|
||||
occupiedSpace,
|
||||
forceDirection ? direction : movementDirection,
|
||||
);
|
||||
|
||||
// setting it to true as it should register a timeout function
|
||||
shouldRegisterContainerTimeout = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
reflowableOccSpaces,
|
||||
currSpacePositions: [currSpacePosition],
|
||||
shouldRegisterContainerTimeout,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the Space position when colliding with
|
||||
* @param newSpacePositions positions of dragging spaces
|
||||
* @param collidingContainer Space positions of Container/Droptargets
|
||||
* @param direction ReflowDirection
|
||||
* @returns
|
||||
*/
|
||||
export function modifyResizePosition(
|
||||
newSpacePosition: BlockSpace,
|
||||
collidingContainer: BlockSpace,
|
||||
direction: ReflowDirection,
|
||||
): BlockSpace {
|
||||
if (!direction || direction === ReflowDirection.UNSET) {
|
||||
return newSpacePosition;
|
||||
}
|
||||
|
||||
const spacePosition = { ...newSpacePosition };
|
||||
const {
|
||||
direction: directionAccessor,
|
||||
directionIndicator,
|
||||
isHorizontal,
|
||||
mathComparator,
|
||||
oppositeDirection,
|
||||
oppositeMathComparator,
|
||||
} = getAccessor(direction);
|
||||
|
||||
spacePosition[directionAccessor] = Math[mathComparator](
|
||||
spacePosition[directionAccessor],
|
||||
collidingContainer[oppositeDirection],
|
||||
);
|
||||
|
||||
const minDimension = isHorizontal
|
||||
? HORIZONTAL_RESIZE_MIN_LIMIT
|
||||
: newSpacePosition.fixedHeight === undefined
|
||||
? VERTICAL_RESIZE_MIN_LIMIT
|
||||
: newSpacePosition.fixedHeight;
|
||||
|
||||
spacePosition[directionAccessor] = Math[oppositeMathComparator](
|
||||
spacePosition[directionAccessor],
|
||||
spacePosition[oppositeDirection] + directionIndicator * minDimension,
|
||||
);
|
||||
|
||||
return spacePosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any of the widget has reached it's movements limit and returns true if it has
|
||||
* @param movementLimitMap
|
||||
* @returns boolean
|
||||
*/
|
||||
export function willItCauseUndroppableState(
|
||||
movementLimitMap: MovementLimitMap | undefined,
|
||||
) {
|
||||
if (!movementLimitMap) return true;
|
||||
|
||||
const movementLimits = Object.values(movementLimitMap);
|
||||
|
||||
return movementLimits.some(
|
||||
(limit) => !(limit.canHorizontalMove && limit.canVerticalMove),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* verify the widget being dragged is colliding with drop targets when they are not being reflowed
|
||||
* @param movementLimitMap
|
||||
* @param spacePositionMap
|
||||
* @param occupiedSpacesMap
|
||||
* @returns
|
||||
*/
|
||||
export function verifyMovementLimits(
|
||||
movementLimitMap: MovementLimitMap,
|
||||
spacePositionMap: SpaceMap,
|
||||
occupiedSpacesMap: SpaceMap,
|
||||
) {
|
||||
for (const spaceId in spacePositionMap) {
|
||||
for (const occupiedSpaceId in occupiedSpacesMap) {
|
||||
if (
|
||||
occupiedSpacesMap[occupiedSpaceId].isDropTarget &&
|
||||
areIntersecting(
|
||||
occupiedSpacesMap[occupiedSpaceId],
|
||||
spacePositionMap[spaceId],
|
||||
)
|
||||
) {
|
||||
movementLimitMap[spaceId] = {
|
||||
canHorizontalMove: false,
|
||||
canVerticalMove: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return movementLimitMap;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
HORIZONTAL_RESIZE_LIMIT,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
ReflowDirection,
|
||||
VERTICAL_RESIZE_LIMIT,
|
||||
VERTICAL_RESIZE_MIN_LIMIT,
|
||||
} from "reflow/reflowTypes";
|
||||
import { getAccessor } from "reflow/reflowUtils";
|
||||
import {
|
||||
|
|
@ -368,7 +368,7 @@ describe("Test reflow helper methods", () => {
|
|||
directionY: "BOTTOM",
|
||||
height: 200,
|
||||
maxY: Infinity,
|
||||
verticalOccupiedLength: VERTICAL_RESIZE_LIMIT,
|
||||
verticalOccupiedLength: VERTICAL_RESIZE_MIN_LIMIT,
|
||||
verticalEmptySpaces: 0,
|
||||
verticalMaxOccupiedSpace: 20,
|
||||
},
|
||||
|
|
@ -574,7 +574,7 @@ describe("Test reflow helper methods", () => {
|
|||
gridProps,
|
||||
ReflowDirection.RIGHT,
|
||||
20,
|
||||
3 * HORIZONTAL_RESIZE_LIMIT,
|
||||
3 * HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
-10,
|
||||
7,
|
||||
7,
|
||||
|
|
@ -585,7 +585,7 @@ describe("Test reflow helper methods", () => {
|
|||
X: 30,
|
||||
dimensionXBeforeCollision: -10,
|
||||
directionX: "RIGHT",
|
||||
horizontalOccupiedLength: 3 * HORIZONTAL_RESIZE_LIMIT,
|
||||
horizontalOccupiedLength: 3 * HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
horizontalEmptySpaces: 7,
|
||||
horizontalMaxOccupiedSpace: 20,
|
||||
maxX: 80,
|
||||
|
|
@ -599,7 +599,7 @@ describe("Test reflow helper methods", () => {
|
|||
gridProps,
|
||||
ReflowDirection.BOTTOM,
|
||||
20,
|
||||
3 * VERTICAL_RESIZE_LIMIT,
|
||||
3 * VERTICAL_RESIZE_MIN_LIMIT,
|
||||
-10,
|
||||
7,
|
||||
7,
|
||||
|
|
@ -612,7 +612,7 @@ describe("Test reflow helper methods", () => {
|
|||
directionY: "BOTTOM",
|
||||
height: 200,
|
||||
maxY: Infinity,
|
||||
verticalOccupiedLength: 3 * VERTICAL_RESIZE_LIMIT,
|
||||
verticalOccupiedLength: 3 * VERTICAL_RESIZE_MIN_LIMIT,
|
||||
verticalEmptySpaces: 7,
|
||||
verticalMaxOccupiedSpace: 20,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,8 +26,16 @@ import {
|
|||
initializeMovementLimitMap,
|
||||
checkProcessNodeForTree,
|
||||
getRelativeCollidingValue,
|
||||
getContainerExitEdge,
|
||||
getCollisionDirectionOfDropTarget,
|
||||
modifyResizePosition,
|
||||
willItCauseUndroppableState,
|
||||
verifyMovementLimits,
|
||||
} from "../reflowUtils";
|
||||
import { HORIZONTAL_RESIZE_LIMIT, VERTICAL_RESIZE_LIMIT } from "../reflowTypes";
|
||||
import {
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
VERTICAL_RESIZE_MIN_LIMIT,
|
||||
} from "../reflowTypes";
|
||||
|
||||
const gridProps = {
|
||||
parentColumnSpace: 20,
|
||||
|
|
@ -532,7 +540,7 @@ describe("Test reflow util methods", () => {
|
|||
collisionTree,
|
||||
gridProps,
|
||||
ReflowDirection.LEFT,
|
||||
depth * HORIZONTAL_RESIZE_LIMIT,
|
||||
depth * HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
30,
|
||||
false,
|
||||
),
|
||||
|
|
@ -545,13 +553,13 @@ describe("Test reflow util methods", () => {
|
|||
collisionTree,
|
||||
gridProps,
|
||||
ReflowDirection.LEFT,
|
||||
depth * HORIZONTAL_RESIZE_LIMIT,
|
||||
depth * HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
30,
|
||||
true,
|
||||
),
|
||||
).toBe(
|
||||
-1 *
|
||||
(collisionTree.left - depth * HORIZONTAL_RESIZE_LIMIT) *
|
||||
(collisionTree.left - depth * HORIZONTAL_RESIZE_MIN_LIMIT) *
|
||||
gridProps.parentColumnSpace,
|
||||
);
|
||||
});
|
||||
|
|
@ -562,7 +570,7 @@ describe("Test reflow util methods", () => {
|
|||
collisionTree,
|
||||
gridProps,
|
||||
ReflowDirection.RIGHT,
|
||||
depth * HORIZONTAL_RESIZE_LIMIT,
|
||||
depth * HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
30,
|
||||
false,
|
||||
),
|
||||
|
|
@ -578,14 +586,14 @@ describe("Test reflow util methods", () => {
|
|||
collisionTree,
|
||||
gridProps,
|
||||
ReflowDirection.RIGHT,
|
||||
depth * HORIZONTAL_RESIZE_LIMIT,
|
||||
depth * HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
30,
|
||||
true,
|
||||
),
|
||||
).toBe(
|
||||
(gridProps.maxGridColumns -
|
||||
collisionTree.right -
|
||||
depth * HORIZONTAL_RESIZE_LIMIT) *
|
||||
depth * HORIZONTAL_RESIZE_MIN_LIMIT) *
|
||||
gridProps.parentColumnSpace,
|
||||
);
|
||||
});
|
||||
|
|
@ -608,7 +616,7 @@ describe("Test reflow util methods", () => {
|
|||
collisionTree,
|
||||
gridProps,
|
||||
ReflowDirection.TOP,
|
||||
depth * VERTICAL_RESIZE_LIMIT,
|
||||
depth * VERTICAL_RESIZE_MIN_LIMIT,
|
||||
20,
|
||||
false,
|
||||
),
|
||||
|
|
@ -621,13 +629,13 @@ describe("Test reflow util methods", () => {
|
|||
collisionTree,
|
||||
gridProps,
|
||||
ReflowDirection.TOP,
|
||||
depth * VERTICAL_RESIZE_LIMIT,
|
||||
depth * VERTICAL_RESIZE_MIN_LIMIT,
|
||||
20,
|
||||
true,
|
||||
),
|
||||
).toBe(
|
||||
-1 *
|
||||
(collisionTree.top - depth * VERTICAL_RESIZE_LIMIT) *
|
||||
(collisionTree.top - depth * VERTICAL_RESIZE_MIN_LIMIT) *
|
||||
gridProps.parentRowSpace,
|
||||
);
|
||||
});
|
||||
|
|
@ -638,7 +646,7 @@ describe("Test reflow util methods", () => {
|
|||
collisionTree,
|
||||
gridProps,
|
||||
ReflowDirection.BOTTOM,
|
||||
depth * VERTICAL_RESIZE_LIMIT,
|
||||
depth * VERTICAL_RESIZE_MIN_LIMIT,
|
||||
230,
|
||||
false,
|
||||
),
|
||||
|
|
@ -801,7 +809,7 @@ describe("Test reflow util methods", () => {
|
|||
dimensionBeforeCollision,
|
||||
gridProps.parentRowSpace,
|
||||
emptySpaces,
|
||||
VERTICAL_RESIZE_LIMIT,
|
||||
VERTICAL_RESIZE_MIN_LIMIT,
|
||||
),
|
||||
).toBe(height);
|
||||
});
|
||||
|
|
@ -816,7 +824,7 @@ describe("Test reflow util methods", () => {
|
|||
dimensionBeforeCollision,
|
||||
gridProps.parentRowSpace,
|
||||
emptySpaces,
|
||||
VERTICAL_RESIZE_LIMIT,
|
||||
VERTICAL_RESIZE_MIN_LIMIT,
|
||||
true,
|
||||
),
|
||||
).toBe(height);
|
||||
|
|
@ -835,7 +843,7 @@ describe("Test reflow util methods", () => {
|
|||
dimensionBeforeCollision,
|
||||
gridProps.parentRowSpace,
|
||||
emptySpaces,
|
||||
VERTICAL_RESIZE_LIMIT,
|
||||
VERTICAL_RESIZE_MIN_LIMIT,
|
||||
true,
|
||||
),
|
||||
).toBe(resizedHeight);
|
||||
|
|
@ -851,10 +859,10 @@ describe("Test reflow util methods", () => {
|
|||
dimensionBeforeCollision,
|
||||
gridProps.parentRowSpace,
|
||||
emptySpaces,
|
||||
VERTICAL_RESIZE_LIMIT,
|
||||
VERTICAL_RESIZE_MIN_LIMIT,
|
||||
true,
|
||||
),
|
||||
).toBe(VERTICAL_RESIZE_LIMIT * gridProps.parentRowSpace);
|
||||
).toBe(VERTICAL_RESIZE_MIN_LIMIT * gridProps.parentRowSpace);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -874,7 +882,7 @@ describe("Test reflow util methods", () => {
|
|||
dimensionBeforeCollision,
|
||||
gridProps.parentColumnSpace,
|
||||
emptySpaces,
|
||||
HORIZONTAL_RESIZE_LIMIT,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
),
|
||||
).toBe(width);
|
||||
});
|
||||
|
|
@ -889,7 +897,7 @@ describe("Test reflow util methods", () => {
|
|||
dimensionBeforeCollision,
|
||||
gridProps.parentColumnSpace,
|
||||
emptySpaces,
|
||||
HORIZONTAL_RESIZE_LIMIT,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
true,
|
||||
),
|
||||
).toBe(width);
|
||||
|
|
@ -910,7 +918,7 @@ describe("Test reflow util methods", () => {
|
|||
dimensionBeforeCollision,
|
||||
gridProps.parentColumnSpace,
|
||||
emptySpaces,
|
||||
HORIZONTAL_RESIZE_LIMIT,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
true,
|
||||
),
|
||||
).toBe(resizedWidth);
|
||||
|
|
@ -926,10 +934,10 @@ describe("Test reflow util methods", () => {
|
|||
dimensionBeforeCollision,
|
||||
gridProps.parentColumnSpace,
|
||||
emptySpaces,
|
||||
HORIZONTAL_RESIZE_LIMIT,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
true,
|
||||
),
|
||||
).toBe(HORIZONTAL_RESIZE_LIMIT * gridProps.parentColumnSpace);
|
||||
).toBe(HORIZONTAL_RESIZE_MIN_LIMIT * gridProps.parentColumnSpace);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -949,7 +957,7 @@ describe("Test reflow util methods", () => {
|
|||
dimensionBeforeCollision,
|
||||
gridProps.parentColumnSpace,
|
||||
emptySpaces,
|
||||
HORIZONTAL_RESIZE_LIMIT,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
),
|
||||
).toBe(width);
|
||||
});
|
||||
|
|
@ -964,7 +972,7 @@ describe("Test reflow util methods", () => {
|
|||
dimensionBeforeCollision,
|
||||
gridProps.parentColumnSpace,
|
||||
emptySpaces,
|
||||
HORIZONTAL_RESIZE_LIMIT,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
true,
|
||||
),
|
||||
).toBe(width);
|
||||
|
|
@ -986,7 +994,7 @@ describe("Test reflow util methods", () => {
|
|||
dimensionBeforeCollision,
|
||||
gridProps.parentColumnSpace,
|
||||
emptySpaces,
|
||||
HORIZONTAL_RESIZE_LIMIT,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
true,
|
||||
),
|
||||
).toBe(resizedWidth);
|
||||
|
|
@ -1002,10 +1010,10 @@ describe("Test reflow util methods", () => {
|
|||
dimensionBeforeCollision,
|
||||
gridProps.parentColumnSpace,
|
||||
emptySpaces,
|
||||
HORIZONTAL_RESIZE_LIMIT,
|
||||
HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
true,
|
||||
),
|
||||
).toBe(HORIZONTAL_RESIZE_LIMIT * gridProps.parentColumnSpace);
|
||||
).toBe(HORIZONTAL_RESIZE_MIN_LIMIT * gridProps.parentColumnSpace);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1913,7 +1921,7 @@ describe("Test reflow util methods", () => {
|
|||
"1234": {
|
||||
BOTTOM: {
|
||||
value: 10,
|
||||
occupiedLength: 5 * VERTICAL_RESIZE_LIMIT,
|
||||
occupiedLength: 5 * VERTICAL_RESIZE_MIN_LIMIT,
|
||||
occupiedSpace: 10,
|
||||
currentEmptySpaces: 10,
|
||||
},
|
||||
|
|
@ -1921,7 +1929,7 @@ describe("Test reflow util methods", () => {
|
|||
};
|
||||
expect(checkProcessNodeForTree(collidingSpace, processedNodes)).toEqual({
|
||||
shouldProcessNode: false,
|
||||
occupiedLength: 5 * VERTICAL_RESIZE_LIMIT,
|
||||
occupiedLength: 5 * VERTICAL_RESIZE_MIN_LIMIT,
|
||||
occupiedSpace: 10,
|
||||
currentEmptySpaces: 10,
|
||||
});
|
||||
|
|
@ -1943,7 +1951,7 @@ describe("Test reflow util methods", () => {
|
|||
collidingValue,
|
||||
direction,
|
||||
gridProps,
|
||||
depth * VERTICAL_RESIZE_LIMIT,
|
||||
depth * VERTICAL_RESIZE_MIN_LIMIT,
|
||||
),
|
||||
).toBe(collidingValue);
|
||||
});
|
||||
|
|
@ -1958,7 +1966,7 @@ describe("Test reflow util methods", () => {
|
|||
collidingValue,
|
||||
direction,
|
||||
gridProps,
|
||||
depth * VERTICAL_RESIZE_LIMIT,
|
||||
depth * VERTICAL_RESIZE_MIN_LIMIT,
|
||||
),
|
||||
).toBe(collidingValue);
|
||||
});
|
||||
|
|
@ -1973,9 +1981,9 @@ describe("Test reflow util methods", () => {
|
|||
collidingValue,
|
||||
direction,
|
||||
gridProps,
|
||||
depth * VERTICAL_RESIZE_LIMIT,
|
||||
depth * VERTICAL_RESIZE_MIN_LIMIT,
|
||||
),
|
||||
).toBe(depth * VERTICAL_RESIZE_LIMIT);
|
||||
).toBe(depth * VERTICAL_RESIZE_MIN_LIMIT);
|
||||
});
|
||||
it("should return calculated colliding value if depth is high compared to colliding value in LEFT direction", () => {
|
||||
const direction = ReflowDirection.LEFT;
|
||||
|
|
@ -1988,9 +1996,9 @@ describe("Test reflow util methods", () => {
|
|||
collidingValue,
|
||||
direction,
|
||||
gridProps,
|
||||
depth * HORIZONTAL_RESIZE_LIMIT,
|
||||
depth * HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
),
|
||||
).toBe(depth * HORIZONTAL_RESIZE_LIMIT);
|
||||
).toBe(depth * HORIZONTAL_RESIZE_MIN_LIMIT);
|
||||
});
|
||||
it("should return calculated colliding value if depth is high compared to colliding value in RIGHT direction", () => {
|
||||
const direction = ReflowDirection.RIGHT;
|
||||
|
|
@ -2003,9 +2011,376 @@ describe("Test reflow util methods", () => {
|
|||
collidingValue,
|
||||
direction,
|
||||
gridProps,
|
||||
depth * HORIZONTAL_RESIZE_LIMIT,
|
||||
depth * HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
),
|
||||
).toBe(gridProps.maxGridColumns - depth * HORIZONTAL_RESIZE_LIMIT);
|
||||
).toBe(gridProps.maxGridColumns - depth * HORIZONTAL_RESIZE_MIN_LIMIT);
|
||||
});
|
||||
});
|
||||
|
||||
describe("while testing getContainerExitEdge, it should return edge direction that is closest to mouse", () => {
|
||||
const exitContainer = {
|
||||
id: "exit",
|
||||
left: 20,
|
||||
right: 60,
|
||||
top: 20,
|
||||
bottom: 70,
|
||||
};
|
||||
it("should return RIGHT if closer to right container edge", () => {
|
||||
const mousePointer = {
|
||||
left: 62,
|
||||
top: 40,
|
||||
};
|
||||
expect(getContainerExitEdge(exitContainer, mousePointer)).toEqual(
|
||||
ReflowDirection.RIGHT,
|
||||
);
|
||||
});
|
||||
|
||||
it("should return LEFT if closer to right container edge", () => {
|
||||
const mousePointer = {
|
||||
left: 19,
|
||||
top: 40,
|
||||
};
|
||||
expect(getContainerExitEdge(exitContainer, mousePointer)).toEqual(
|
||||
ReflowDirection.LEFT,
|
||||
);
|
||||
});
|
||||
|
||||
it("should return TOP if closer to top container edge", () => {
|
||||
const mousePointer = {
|
||||
left: 40,
|
||||
top: 19,
|
||||
};
|
||||
expect(getContainerExitEdge(exitContainer, mousePointer)).toEqual(
|
||||
ReflowDirection.TOP,
|
||||
);
|
||||
});
|
||||
|
||||
it("should return BOTTOM if closer to bottom container edge", () => {
|
||||
const mousePointer = {
|
||||
left: 40,
|
||||
top: 72,
|
||||
};
|
||||
expect(getContainerExitEdge(exitContainer, mousePointer)).toEqual(
|
||||
ReflowDirection.BOTTOM,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("test getCollisionDirectionOfDropTarget method", () => {
|
||||
const containerSpace = {
|
||||
id: "container",
|
||||
left: 20,
|
||||
right: 60,
|
||||
top: 20,
|
||||
bottom: 70,
|
||||
};
|
||||
|
||||
it("should return current direction if it is possible push direction", () => {
|
||||
const mousePosition = {
|
||||
left: 63,
|
||||
top: 75,
|
||||
};
|
||||
expect(
|
||||
getCollisionDirectionOfDropTarget(
|
||||
containerSpace,
|
||||
ReflowDirection.LEFT,
|
||||
mousePosition,
|
||||
),
|
||||
).toBe(ReflowDirection.LEFT);
|
||||
|
||||
expect(
|
||||
getCollisionDirectionOfDropTarget(
|
||||
containerSpace,
|
||||
ReflowDirection.TOP,
|
||||
mousePosition,
|
||||
),
|
||||
).toBe(ReflowDirection.TOP);
|
||||
});
|
||||
|
||||
it("should return push direction if mouse is only on one edge even if direction sent is not the same", () => {
|
||||
let mousePosition = {
|
||||
left: 40,
|
||||
top: 14,
|
||||
};
|
||||
expect(
|
||||
getCollisionDirectionOfDropTarget(
|
||||
containerSpace,
|
||||
ReflowDirection.UNSET,
|
||||
mousePosition,
|
||||
),
|
||||
).toBe(ReflowDirection.BOTTOM);
|
||||
|
||||
mousePosition = {
|
||||
left: 67,
|
||||
top: 40,
|
||||
};
|
||||
expect(
|
||||
getCollisionDirectionOfDropTarget(
|
||||
containerSpace,
|
||||
ReflowDirection.UNSET,
|
||||
mousePosition,
|
||||
),
|
||||
).toBe(ReflowDirection.LEFT);
|
||||
});
|
||||
|
||||
it("should return push direction which is farthest from edge, if it has 2 possible directions and the direction sent is not one of them", () => {
|
||||
let mousePosition = {
|
||||
left: 18,
|
||||
top: 14,
|
||||
};
|
||||
expect(
|
||||
getCollisionDirectionOfDropTarget(
|
||||
containerSpace,
|
||||
ReflowDirection.UNSET,
|
||||
mousePosition,
|
||||
),
|
||||
).toBe(ReflowDirection.BOTTOM);
|
||||
});
|
||||
});
|
||||
|
||||
describe("test modifyResizePosition method", () => {
|
||||
const containerSpace = {
|
||||
id: "container",
|
||||
left: 20,
|
||||
right: 60,
|
||||
top: 20,
|
||||
bottom: 70,
|
||||
};
|
||||
it("should return resized position based on direction of collision with Container's left side", () => {
|
||||
const spacePosition = {
|
||||
id: "id",
|
||||
left: 10,
|
||||
right: 30,
|
||||
top: 15,
|
||||
bottom: 40,
|
||||
};
|
||||
const resizedPosition = {
|
||||
id: "id",
|
||||
left: 10,
|
||||
right: 20,
|
||||
top: 15,
|
||||
bottom: 40,
|
||||
};
|
||||
|
||||
expect(
|
||||
modifyResizePosition(
|
||||
spacePosition,
|
||||
containerSpace,
|
||||
ReflowDirection.RIGHT,
|
||||
),
|
||||
).toEqual(resizedPosition);
|
||||
});
|
||||
|
||||
it("should return resized position based on direction of collision with Container's right side", () => {
|
||||
const spacePosition = {
|
||||
id: "id",
|
||||
left: 50,
|
||||
right: 90,
|
||||
top: 15,
|
||||
bottom: 40,
|
||||
};
|
||||
const resizedPosition = {
|
||||
id: "id",
|
||||
left: 60,
|
||||
right: 90,
|
||||
top: 15,
|
||||
bottom: 40,
|
||||
};
|
||||
|
||||
expect(
|
||||
modifyResizePosition(
|
||||
spacePosition,
|
||||
containerSpace,
|
||||
ReflowDirection.LEFT,
|
||||
),
|
||||
).toEqual(resizedPosition);
|
||||
});
|
||||
|
||||
it("should return resized position based on direction of collision with Container's top side", () => {
|
||||
const spacePosition = {
|
||||
id: "id",
|
||||
left: 15,
|
||||
right: 40,
|
||||
top: 5,
|
||||
bottom: 40,
|
||||
};
|
||||
const resizedPosition = {
|
||||
id: "id",
|
||||
left: 15,
|
||||
right: 40,
|
||||
top: 5,
|
||||
bottom: 20,
|
||||
};
|
||||
|
||||
expect(
|
||||
modifyResizePosition(
|
||||
spacePosition,
|
||||
containerSpace,
|
||||
ReflowDirection.BOTTOM,
|
||||
),
|
||||
).toEqual(resizedPosition);
|
||||
});
|
||||
|
||||
it("should return resized position based on direction of collision with Container's bottom side", () => {
|
||||
const spacePosition = {
|
||||
id: "id",
|
||||
left: 15,
|
||||
right: 40,
|
||||
top: 60,
|
||||
bottom: 95,
|
||||
};
|
||||
const resizedPosition = {
|
||||
id: "id",
|
||||
left: 15,
|
||||
right: 40,
|
||||
top: 70,
|
||||
bottom: 95,
|
||||
};
|
||||
|
||||
expect(
|
||||
modifyResizePosition(
|
||||
spacePosition,
|
||||
containerSpace,
|
||||
ReflowDirection.TOP,
|
||||
),
|
||||
).toEqual(resizedPosition);
|
||||
});
|
||||
|
||||
it("should return resized position based on direction of collision but with min heights and widths", () => {
|
||||
let spacePosition = {
|
||||
id: "id",
|
||||
left: 19,
|
||||
right: 40,
|
||||
top: 19,
|
||||
bottom: 95,
|
||||
};
|
||||
let resizedPosition = {
|
||||
id: "id",
|
||||
left: 19,
|
||||
right: 40,
|
||||
top: 19,
|
||||
bottom: 19 + VERTICAL_RESIZE_MIN_LIMIT,
|
||||
};
|
||||
|
||||
expect(
|
||||
modifyResizePosition(
|
||||
spacePosition,
|
||||
containerSpace,
|
||||
ReflowDirection.BOTTOM,
|
||||
),
|
||||
).toEqual(resizedPosition);
|
||||
|
||||
resizedPosition = {
|
||||
id: "id",
|
||||
left: 19,
|
||||
right: 19 + HORIZONTAL_RESIZE_MIN_LIMIT,
|
||||
top: 19,
|
||||
bottom: 95,
|
||||
};
|
||||
|
||||
expect(
|
||||
modifyResizePosition(
|
||||
spacePosition,
|
||||
containerSpace,
|
||||
ReflowDirection.RIGHT,
|
||||
),
|
||||
).toEqual(resizedPosition);
|
||||
|
||||
spacePosition.fixedHeight = 95 - 19;
|
||||
expect(
|
||||
modifyResizePosition(
|
||||
spacePosition,
|
||||
containerSpace,
|
||||
ReflowDirection.BOTTOM,
|
||||
),
|
||||
).toEqual(spacePosition);
|
||||
});
|
||||
});
|
||||
|
||||
it("should test willItCauseUndroppableState method, it should return true if any value is false", () => {
|
||||
const movementLimitMap = {
|
||||
"1": {
|
||||
canVerticalMove: true,
|
||||
canHorizontalMove: true,
|
||||
},
|
||||
"2": {
|
||||
canVerticalMove: true,
|
||||
canHorizontalMove: true,
|
||||
},
|
||||
};
|
||||
|
||||
expect(willItCauseUndroppableState(movementLimitMap)).toEqual(false);
|
||||
|
||||
movementLimitMap["3"] = {
|
||||
canVerticalMove: true,
|
||||
canHorizontalMove: false,
|
||||
};
|
||||
expect(willItCauseUndroppableState(movementLimitMap)).toEqual(true);
|
||||
});
|
||||
|
||||
it("verifyMovementLimits should check if space is colliding with any container and return movementLimits based on that", () => {
|
||||
const movementLimits = {
|
||||
"1": {
|
||||
canVerticalMove: true,
|
||||
canHorizontalMove: true,
|
||||
},
|
||||
"2": {
|
||||
canVerticalMove: true,
|
||||
canHorizontalMove: true,
|
||||
},
|
||||
"3": {
|
||||
canVerticalMove: false,
|
||||
canHorizontalMove: true,
|
||||
},
|
||||
};
|
||||
|
||||
const occupiedSpacesMap = {
|
||||
"4": {
|
||||
left: 50,
|
||||
right: 70,
|
||||
top: 60,
|
||||
bottom: 90,
|
||||
isDropTarget: true,
|
||||
},
|
||||
};
|
||||
const spacePositionMap = {
|
||||
"1": {
|
||||
left: 10,
|
||||
right: 40,
|
||||
top: 20,
|
||||
bottom: 50,
|
||||
},
|
||||
"2": {
|
||||
left: 20,
|
||||
right: 65,
|
||||
top: 20,
|
||||
bottom: 50,
|
||||
},
|
||||
"3": {
|
||||
left: 90,
|
||||
right: 110,
|
||||
top: 20,
|
||||
bottom: 50,
|
||||
},
|
||||
};
|
||||
|
||||
const verifiedMovementLimits = {
|
||||
"1": {
|
||||
canVerticalMove: true,
|
||||
canHorizontalMove: true,
|
||||
},
|
||||
"2": {
|
||||
canVerticalMove: true,
|
||||
canHorizontalMove: true,
|
||||
},
|
||||
"3": {
|
||||
canVerticalMove: false,
|
||||
canHorizontalMove: true,
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
verifyMovementLimits(movementLimits, spacePositionMap, occupiedSpacesMap),
|
||||
).toEqual(verifiedMovementLimits);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ export function ReflowResizable(props: ResizableProps) {
|
|||
|
||||
if (resizedPositions) {
|
||||
//calling reflow to update movements of reflowing widgets and get movementLimit of current resizing widget
|
||||
({ bottomMostRow, movementLimitMap } = reflow(
|
||||
({ bottomMostRow, movementLimitMap } = reflow.reflowSpaces(
|
||||
[resizedPositions],
|
||||
direction,
|
||||
true,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { ActionData } from "reducers/entityReducers/actionsReducer";
|
|||
import { Page } from "@appsmith/constants/ReduxActionConstants";
|
||||
import { getActions, getPlugins } from "selectors/entitiesSelector";
|
||||
import { Plugin } from "api/PluginApi";
|
||||
import { DragDetails } from "reducers/uiReducers/dragResizeReducer";
|
||||
import { DataTreeForActionCreator } from "components/editorComponents/ActionCreator/types";
|
||||
|
||||
export const getWidgets = (state: AppState): CanvasWidgetsReduxState => {
|
||||
|
|
@ -174,6 +175,14 @@ export const getPluginIdOfPackageName = (
|
|||
export const getDragDetails = (state: AppState) => {
|
||||
return state.ui.widgetDragResize.dragDetails;
|
||||
};
|
||||
export const isCurrentCanvasDragging = createSelector(
|
||||
(state: AppState) => state.ui.widgetDragResize.isDragging,
|
||||
getDragDetails,
|
||||
(state: AppState, canvasId: string) => canvasId,
|
||||
(isDragging: boolean, dragDetails: DragDetails, canvasId: string) => {
|
||||
return dragDetails?.draggedOn === canvasId && isDragging;
|
||||
},
|
||||
);
|
||||
|
||||
export const getSelectedWidget = (
|
||||
state: AppState,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import {
|
|||
createCanvasWidget,
|
||||
createLoadingWidget,
|
||||
} from "utils/widgetRenderUtils";
|
||||
import { checkIsDropTarget } from "components/designSystems/appsmith/PositionedContainer";
|
||||
import WidgetFactory, {
|
||||
NonSerialisableWidgetConfigs,
|
||||
} from "utils/WidgetFactory";
|
||||
|
|
@ -286,6 +287,7 @@ export const getWidgetCards = createSelector(
|
|||
displayName,
|
||||
icon: iconSVG,
|
||||
searchTags,
|
||||
isDynamicHeight: isAutoHeightEnabledForWidget(config as WidgetProps),
|
||||
};
|
||||
});
|
||||
const sortedCards = sortBy(_cards, ["displayName"]);
|
||||
|
|
@ -407,6 +409,7 @@ const getWidgetSpacesForContainer = (
|
|||
bottom: widget.bottomRow,
|
||||
right: widget.rightColumn,
|
||||
type: widget.type,
|
||||
isDropTarget: checkIsDropTarget(widget.type),
|
||||
fixedHeight,
|
||||
};
|
||||
return occupiedSpace;
|
||||
|
|
@ -423,9 +426,9 @@ const getWidgetSpacesForContainer = (
|
|||
const generateOccupiedSpacesMap = (
|
||||
widgets: CanvasWidgetsReduxState,
|
||||
fetchNow = true,
|
||||
): { [containerWidgetId: string]: OccupiedSpace[] } | undefined => {
|
||||
): { [containerWidgetId: string]: WidgetSpace[] } | undefined => {
|
||||
const occupiedSpaces: {
|
||||
[containerWidgetId: string]: OccupiedSpace[];
|
||||
[containerWidgetId: string]: WidgetSpace[];
|
||||
} = {};
|
||||
if (!fetchNow) return;
|
||||
// Get all widgets with type "CONTAINER_WIDGET" and has children
|
||||
|
|
@ -446,7 +449,7 @@ const generateOccupiedSpacesMap = (
|
|||
);
|
||||
// Get the occupied spaces in this container
|
||||
// Assign it to the containerWidgetId key in occupiedSpaces
|
||||
occupiedSpaces[containerWidgetId] = getOccupiedSpacesForContainer(
|
||||
occupiedSpaces[containerWidgetId] = getWidgetSpacesForContainer(
|
||||
containerWidgetId,
|
||||
childWidgets.map((widgetId) => widgets[widgetId]),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ import { transformDSL } from "./DSLMigrations";
|
|||
import { WidgetType } from "./WidgetFactory";
|
||||
import { DSLWidget } from "widgets/constants";
|
||||
import { WidgetDraggingBlock } from "pages/common/CanvasArenas/hooks/useBlocksToBeDraggedOnCanvas";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useCanvasDragging";
|
||||
import { XYCord } from "pages/common/CanvasArenas/hooks/useRenderBlocksOnCanvas";
|
||||
import { ContainerWidgetProps } from "widgets/ContainerWidget/widget";
|
||||
import { GridProps } from "reflow/reflowTypes";
|
||||
import { BlockSpace, GridProps } from "reflow/reflowTypes";
|
||||
import { areIntersecting, Rect } from "./boxHelpers";
|
||||
|
||||
export type WidgetOperationParams = {
|
||||
|
|
@ -54,7 +54,7 @@ export function getDraggingSpacesFromBlocks(
|
|||
draggingBlocks: WidgetDraggingBlock[],
|
||||
snapColumnSpace: number,
|
||||
snapRowSpace: number,
|
||||
): OccupiedSpace[] {
|
||||
): BlockSpace[] {
|
||||
const draggingSpaces = [];
|
||||
for (const draggingBlock of draggingBlocks) {
|
||||
//gets top and left position of the block
|
||||
|
|
@ -76,6 +76,10 @@ export function getDraggingSpacesFromBlocks(
|
|||
right: leftColumn + draggingBlock.width / snapColumnSpace,
|
||||
bottom: topRow + draggingBlock.height / snapRowSpace,
|
||||
id: draggingBlock.widgetId,
|
||||
fixedHeight:
|
||||
draggingBlock.fixedHeight !== undefined
|
||||
? draggingBlock.rowHeight
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
return draggingSpaces;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||
import { getContainerWidgetSpacesSelectorWhileMoving } from "selectors/editorSelectors";
|
||||
import { reflow } from "reflow";
|
||||
import {
|
||||
BlockSpace,
|
||||
CollidingSpace,
|
||||
CollidingSpaceMap,
|
||||
GridProps,
|
||||
|
|
@ -14,20 +15,22 @@ import {
|
|||
ReflowDirection,
|
||||
ReflowedSpaceMap,
|
||||
SecondOrderCollisionMap,
|
||||
SpaceMap,
|
||||
} from "reflow/reflowTypes";
|
||||
import {
|
||||
getBottomMostRow,
|
||||
getLimitedMovementMap,
|
||||
getSpacesMapFromArray,
|
||||
willItCauseUndroppableState,
|
||||
} from "reflow/reflowUtils";
|
||||
import { getBottomRowAfterReflow } from "utils/reflowHookUtils";
|
||||
import { checkIsDropTarget } from "components/designSystems/appsmith/PositionedContainer";
|
||||
import { getIsReflowing } from "selectors/widgetReflowSelectors";
|
||||
import { AppState } from "@appsmith/reducers";
|
||||
import { areIntersecting } from "utils/boxHelpers";
|
||||
import { isCurrentCanvasDragging } from "sagas/selectors";
|
||||
|
||||
type WidgetCollidingSpace = CollidingSpace & {
|
||||
type: string;
|
||||
isDropTarget: boolean;
|
||||
};
|
||||
|
||||
type WidgetCollidingSpaceMap = {
|
||||
|
|
@ -40,18 +43,22 @@ export type WidgetCollisionMap = {
|
|||
|
||||
export interface ReflowInterface {
|
||||
(
|
||||
newPositions: OccupiedSpace[],
|
||||
newPositions: BlockSpace[],
|
||||
direction: ReflowDirection,
|
||||
stopMoveAfterLimit?: boolean,
|
||||
shouldSkipContainerReflow?: boolean,
|
||||
forceDirection?: boolean,
|
||||
immediateExitContainer?: string,
|
||||
mousePosition?: OccupiedSpace,
|
||||
reflowAfterTimeoutCallback?: (reflowParams: {
|
||||
movementMap: ReflowedSpaceMap;
|
||||
spacePositionMap: SpaceMap | undefined;
|
||||
}) => void,
|
||||
): {
|
||||
movementLimitMap?: MovementLimitMap;
|
||||
movementMap: ReflowedSpaceMap;
|
||||
bottomMostRow: number;
|
||||
isIdealToJumpContainer: boolean;
|
||||
spacePositionMap: SpaceMap | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -59,11 +66,12 @@ export const useReflow = (
|
|||
OGPositions: OccupiedSpace[],
|
||||
parentId: string,
|
||||
gridProps: GridProps,
|
||||
): ReflowInterface => {
|
||||
): { reflowSpaces: ReflowInterface; resetReflow: () => void } => {
|
||||
const dispatch = useDispatch();
|
||||
const isReflowingGlobal = useSelector(getIsReflowing);
|
||||
const isDragging = useSelector(
|
||||
(state: AppState) => state.ui.widgetDragResize.isDragging,
|
||||
|
||||
const isDraggingCanvas = useSelector((state: AppState) =>
|
||||
isCurrentCanvasDragging(state, parentId),
|
||||
);
|
||||
|
||||
const throttledDispatch = throttle(dispatch, 50);
|
||||
|
|
@ -75,115 +83,211 @@ export const useReflow = (
|
|||
);
|
||||
const widgetSpaces: WidgetSpace[] = useSelector(reflowSpacesSelector) || [];
|
||||
|
||||
// Store previous values of reflow results
|
||||
const prevPositions = useRef<OccupiedSpace[] | undefined>(OGPositions);
|
||||
const prevCollidingSpaces = useRef<WidgetCollidingSpaceMap>();
|
||||
const prevMovementMap = useRef<ReflowedSpaceMap>({});
|
||||
const prevSecondOrderCollisionMap = useRef<SecondOrderCollisionMap>({});
|
||||
|
||||
// Indicates if the Containers should be reflowed
|
||||
const shouldReflowDropTargets = useRef<boolean>(false);
|
||||
// ref of timeout method
|
||||
const timeOutFunction = useRef<any>();
|
||||
// store exit container and mouse position at exit, so that it can be used during timeout
|
||||
const exitContainer = useRef<string | undefined>(undefined);
|
||||
const mousePointerAtContainerExit = useRef<OccupiedSpace | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
//only have it run when the user has completely stopped dragging and stopped Reflowing
|
||||
if (!isReflowingGlobal && !isDragging) {
|
||||
if (!isReflowingGlobal && !isDraggingCanvas) {
|
||||
isReflowing.current = false;
|
||||
prevPositions.current = [...OGPositions];
|
||||
prevCollidingSpaces.current = { horizontal: {}, vertical: {} };
|
||||
prevMovementMap.current = {};
|
||||
prevSecondOrderCollisionMap.current = {};
|
||||
shouldReflowDropTargets.current = false;
|
||||
}
|
||||
}, [isReflowingGlobal, isDragging]);
|
||||
|
||||
if (!isDraggingCanvas) {
|
||||
clearTimeout(timeOutFunction.current);
|
||||
exitContainer.current = undefined;
|
||||
mousePointerAtContainerExit.current = undefined;
|
||||
}
|
||||
}, [isReflowingGlobal, isDraggingCanvas]);
|
||||
|
||||
// will become a state if we decide that resize should be a "toggle on-demand" feature
|
||||
const shouldResize = true;
|
||||
return function reflowSpaces(
|
||||
newPositions: OccupiedSpace[],
|
||||
direction: ReflowDirection,
|
||||
stopMoveAfterLimit = false,
|
||||
shouldSkipContainerReflow = false,
|
||||
forceDirection = false,
|
||||
immediateExitContainer?: string,
|
||||
mousePosition?: OccupiedSpace,
|
||||
) {
|
||||
const prevReflowState: PrevReflowState = {
|
||||
prevSpacesMap: getSpacesMapFromArray(prevPositions.current),
|
||||
prevCollidingSpaceMap: prevCollidingSpaces.current as CollidingSpaceMap,
|
||||
prevMovementMap: prevMovementMap.current,
|
||||
prevSecondOrderCollisionMap: prevSecondOrderCollisionMap.current,
|
||||
};
|
||||
return {
|
||||
reflowSpaces: (
|
||||
newPositions: BlockSpace[],
|
||||
direction: ReflowDirection,
|
||||
stopMoveAfterLimit = false,
|
||||
shouldSkipContainerReflow = false,
|
||||
forceDirection = false,
|
||||
immediateExitContainer?: string,
|
||||
mousePosition?: OccupiedSpace,
|
||||
reflowAfterTimeoutCallback?: (reflowParams: {
|
||||
movementMap: ReflowedSpaceMap;
|
||||
spacePositionMap: SpaceMap | undefined;
|
||||
}) => void,
|
||||
) => {
|
||||
clearTimeout(timeOutFunction.current);
|
||||
|
||||
// To track container jumps
|
||||
let isIdealToJumpContainer = false;
|
||||
const prevReflowState: PrevReflowState = {
|
||||
prevSpacesMap: getSpacesMapFromArray(prevPositions.current),
|
||||
prevCollidingSpaceMap: prevCollidingSpaces.current as CollidingSpaceMap,
|
||||
prevMovementMap: prevMovementMap.current,
|
||||
prevSecondOrderCollisionMap: prevSecondOrderCollisionMap.current,
|
||||
};
|
||||
|
||||
const {
|
||||
collidingSpaceMap,
|
||||
movementLimitMap,
|
||||
movementMap,
|
||||
secondOrderCollisionMap,
|
||||
} = reflow(
|
||||
newPositions,
|
||||
OGPositions,
|
||||
widgetSpaces,
|
||||
direction,
|
||||
gridProps,
|
||||
forceDirection,
|
||||
shouldResize,
|
||||
prevReflowState,
|
||||
immediateExitContainer,
|
||||
);
|
||||
|
||||
prevPositions.current = newPositions;
|
||||
prevCollidingSpaces.current = collidingSpaceMap as WidgetCollidingSpaceMap;
|
||||
prevSecondOrderCollisionMap.current = secondOrderCollisionMap || {};
|
||||
|
||||
let correctedMovementMap = movementMap || {};
|
||||
|
||||
if (stopMoveAfterLimit)
|
||||
correctedMovementMap = getLimitedMovementMap(
|
||||
const {
|
||||
collidingSpaceMap,
|
||||
movementLimitMap,
|
||||
movementMap,
|
||||
prevMovementMap.current,
|
||||
{ canHorizontalMove: true, canVerticalMove: true },
|
||||
secondOrderCollisionMap,
|
||||
shouldRegisterContainerTimeout,
|
||||
spacePositionMap,
|
||||
} = reflow(
|
||||
newPositions,
|
||||
OGPositions,
|
||||
widgetSpaces,
|
||||
direction,
|
||||
gridProps,
|
||||
forceDirection,
|
||||
shouldResize,
|
||||
prevReflowState,
|
||||
immediateExitContainer,
|
||||
mousePosition,
|
||||
!shouldSkipContainerReflow || shouldReflowDropTargets.current,
|
||||
);
|
||||
|
||||
if (shouldSkipContainerReflow && collidingSpaceMap) {
|
||||
prevPositions.current = newPositions;
|
||||
prevCollidingSpaces.current = collidingSpaceMap as WidgetCollidingSpaceMap;
|
||||
prevSecondOrderCollisionMap.current = secondOrderCollisionMap || {};
|
||||
|
||||
//store exit container and mouse pointer if we are not reflowing drop targets and it doesn't already have a value
|
||||
if (!shouldReflowDropTargets.current && !exitContainer.current) {
|
||||
exitContainer.current = immediateExitContainer;
|
||||
mousePointerAtContainerExit.current = mousePosition;
|
||||
}
|
||||
|
||||
let correctedMovementMap = movementMap || {};
|
||||
|
||||
if (stopMoveAfterLimit) {
|
||||
correctedMovementMap = getLimitedMovementMap(
|
||||
movementMap,
|
||||
prevMovementMap.current,
|
||||
{ canHorizontalMove: true, canVerticalMove: true },
|
||||
);
|
||||
}
|
||||
|
||||
prevMovementMap.current = correctedMovementMap;
|
||||
const collidingSpaces = [
|
||||
...Object.values(collidingSpaceMap.horizontal),
|
||||
...Object.values(collidingSpaceMap.vertical),
|
||||
...Object.values(collidingSpaceMap?.horizontal || []),
|
||||
...Object.values(collidingSpaceMap?.vertical || []),
|
||||
] as WidgetCollidingSpace[];
|
||||
|
||||
for (const collidingSpace of collidingSpaces) {
|
||||
if (
|
||||
checkIsDropTarget(collidingSpace.type) &&
|
||||
mousePosition &&
|
||||
areIntersecting(mousePosition, collidingSpace)
|
||||
// Logic for container jump
|
||||
if (shouldSkipContainerReflow) {
|
||||
if (shouldRegisterContainerTimeout) {
|
||||
// register a timeout method to trigger reflow if widget is not moved and is colliding with Droptargets
|
||||
timeOutFunction.current = setTimeout(() => {
|
||||
//call reflow again
|
||||
const {
|
||||
collidingSpaceMap,
|
||||
movementLimitMap,
|
||||
movementMap,
|
||||
secondOrderCollisionMap,
|
||||
} = reflow(
|
||||
newPositions,
|
||||
OGPositions,
|
||||
widgetSpaces,
|
||||
direction,
|
||||
gridProps,
|
||||
forceDirection,
|
||||
shouldResize,
|
||||
prevReflowState,
|
||||
exitContainer.current,
|
||||
mousePointerAtContainerExit.current || mousePosition,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
exitContainer.current = undefined;
|
||||
mousePointerAtContainerExit.current = undefined;
|
||||
|
||||
//if the result causes an undroppable state return
|
||||
if (willItCauseUndroppableState(movementLimitMap)) return;
|
||||
|
||||
// trigger reflow action with result of reflow algorithm
|
||||
if (!isEmpty(movementMap)) {
|
||||
shouldReflowDropTargets.current = true;
|
||||
isReflowing.current = true;
|
||||
dispatch(reflowMoveAction(movementMap || {}));
|
||||
//trigger callback if reflow action is called
|
||||
reflowAfterTimeoutCallback &&
|
||||
reflowAfterTimeoutCallback({
|
||||
movementMap: prevMovementMap.current,
|
||||
spacePositionMap: undefined,
|
||||
});
|
||||
|
||||
prevCollidingSpaces.current = collidingSpaceMap as WidgetCollidingSpaceMap;
|
||||
prevSecondOrderCollisionMap.current =
|
||||
secondOrderCollisionMap || {};
|
||||
prevMovementMap.current = movementMap || {};
|
||||
} else if (isReflowing.current) {
|
||||
isReflowing.current = false;
|
||||
throttledDispatch.cancel();
|
||||
dispatch(stopReflowAction());
|
||||
shouldReflowDropTargets.current = false;
|
||||
}
|
||||
}, 500);
|
||||
} // This checks if colliding space does not contain any drop targets
|
||||
else if (
|
||||
!collidingSpaces.some(
|
||||
(collidingSpaces) => collidingSpaces.isDropTarget,
|
||||
)
|
||||
) {
|
||||
isIdealToJumpContainer = true;
|
||||
correctedMovementMap = {};
|
||||
shouldReflowDropTargets.current = false;
|
||||
mousePointerAtContainerExit.current = undefined;
|
||||
exitContainer.current = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prevMovementMap.current = correctedMovementMap;
|
||||
//Trigger reflow action
|
||||
if (!isEmpty(correctedMovementMap)) {
|
||||
isReflowing.current = true;
|
||||
if (forceDirection) dispatch(reflowMoveAction(correctedMovementMap));
|
||||
else throttledDispatch(reflowMoveAction(correctedMovementMap));
|
||||
} else if (isReflowing.current) {
|
||||
isReflowing.current = false;
|
||||
throttledDispatch.cancel();
|
||||
dispatch(stopReflowAction());
|
||||
shouldReflowDropTargets.current = false;
|
||||
}
|
||||
|
||||
if (!isEmpty(correctedMovementMap)) {
|
||||
isReflowing.current = true;
|
||||
if (forceDirection) dispatch(reflowMoveAction(correctedMovementMap));
|
||||
else throttledDispatch(reflowMoveAction(correctedMovementMap));
|
||||
} else if (isReflowing.current) {
|
||||
isReflowing.current = false;
|
||||
throttledDispatch.cancel();
|
||||
dispatch(stopReflowAction());
|
||||
}
|
||||
//calculate bottom row
|
||||
const bottomMostRow = getBottomRowAfterReflow(
|
||||
movementMap,
|
||||
getBottomMostRow(newPositions),
|
||||
widgetSpaces,
|
||||
gridProps,
|
||||
);
|
||||
|
||||
const bottomMostRow = getBottomRowAfterReflow(
|
||||
movementMap,
|
||||
getBottomMostRow(newPositions),
|
||||
widgetSpaces,
|
||||
gridProps,
|
||||
);
|
||||
|
||||
return {
|
||||
movementLimitMap,
|
||||
movementMap: correctedMovementMap,
|
||||
bottomMostRow,
|
||||
isIdealToJumpContainer,
|
||||
};
|
||||
return {
|
||||
movementLimitMap,
|
||||
movementMap: correctedMovementMap,
|
||||
bottomMostRow,
|
||||
spacePositionMap,
|
||||
};
|
||||
},
|
||||
//reset Reflow parameters when this is called, usually while resetting canvas
|
||||
resetReflow: () => {
|
||||
clearTimeout(timeOutFunction.current);
|
||||
shouldReflowDropTargets.current = false;
|
||||
mousePointerAtContainerExit.current = undefined;
|
||||
exitContainer.current = undefined;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user