PromucFlow_constructor/app/client/src/layoutSystems/common/utils/canvasDraggingUtils.ts

586 lines
17 KiB
TypeScript
Raw Normal View History

chore: BaseWidget Restructuring (#26562) ## Description Create a Basewidget wrapper that supplies Widget Onion as per the layout system. involves extracting widget layers presently in the BaseWidget into HOCs and hooks and make sure layout systems can be scaled. Make sure Modal widget is handled as a overlay widget whose wrappers are supplied by basewidget instead of modal widget implementing its own editing blocks. This PR also separates the drag n drop logic for both auto layout and fixed layout. They are moved into respective Layout system folders to have clear sepsration of concern #### PR fixes following issue(s) Fixes #26674 Fixes #26675 Fixes #26676 Fixes #26570 Fixes #26590 Fixes #26591 Fixes #26592 <img width="931" alt="BaseWidgetHOC" src="https://github.com/appsmithorg/appsmith/assets/35134347/22f4cf1e-e4c5-4475-83a8-6818e7cebe70"> [Miro Link to view the new system](https://miro.com/app/board/uXjVM6vRgf8=/?moveToWidget=3458764560239189204&cot=14) > if no issue exists, please create an issue and ask the maintainers about this first > > #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change > Please delete options that are not relevant. - Breaking change (fix or feature that would cause existing functionality to not work as expected) - Chore (housekeeping or task changes that don't impact user perception) > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [x] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### 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 - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed --------- Co-authored-by: rahulramesha <rahul@appsmith.com> Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Co-authored-by: Preet Sidhu <preetsidhu.bits@gmail.com> Co-authored-by: Aswath K <aswath.sana@gmail.com>
2023-09-11 15:55:11 +00:00
import type {
OccupiedSpace,
WidgetSpace,
} from "constants/CanvasEditorConstants";
import { GridDefaults } from "constants/WidgetConstants";
import { isEmpty } from "lodash";
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
import type { DraggingGroupCenter } from "reducers/uiReducers/dragResizeReducer";
import type {
MovementLimitMap,
ReflowedSpaceMap,
SpaceMap,
} from "reflow/reflowTypes";
import {
HORIZONTAL_RESIZE_MIN_LIMIT,
ReflowDirection,
VERTICAL_RESIZE_MIN_LIMIT,
} from "reflow/reflowTypes";
import type { WidgetType } from "WidgetProvider/factory";
import {
getDraggingSpacesFromBlocks,
getDropZoneOffsets,
noCollision,
} from "utils/WidgetPropsUtils";
chore: Layout system wise restructuring of Canvas Widget (#27496) > Pull Request Template > > Use this template to quickly create a well written pull request. Delete all quotes before creating the pull request. > ## Description In This PR, we are cleaning up Canvas Widget implementation and taking measures to remove it from the widget suite. more detailed explanation of Why and How of the solution [here](https://www.notion.so/Canvas-Widget-73776a3364ba42eb8f783c79046777d0) In this solution we are going to remove implementation of Editing and Layouting Specific implementation from Canvas Widget and create a new view component which is Layout system specific. #### PR fixes following issue(s) Fixes #27003 #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change > Please delete options that are not relevant. - Chore (housekeeping or task changes that don't impact user perception) > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] JUnit - [X] Jest - [ ] Cypress > > #### 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 - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
2023-10-04 11:53:29 +00:00
import type { WidgetDraggingBlock, XYCord } from "../canvasArenas/ArenaTypes";
chore: BaseWidget Restructuring (#26562) ## Description Create a Basewidget wrapper that supplies Widget Onion as per the layout system. involves extracting widget layers presently in the BaseWidget into HOCs and hooks and make sure layout systems can be scaled. Make sure Modal widget is handled as a overlay widget whose wrappers are supplied by basewidget instead of modal widget implementing its own editing blocks. This PR also separates the drag n drop logic for both auto layout and fixed layout. They are moved into respective Layout system folders to have clear sepsration of concern #### PR fixes following issue(s) Fixes #26674 Fixes #26675 Fixes #26676 Fixes #26570 Fixes #26590 Fixes #26591 Fixes #26592 <img width="931" alt="BaseWidgetHOC" src="https://github.com/appsmithorg/appsmith/assets/35134347/22f4cf1e-e4c5-4475-83a8-6818e7cebe70"> [Miro Link to view the new system](https://miro.com/app/board/uXjVM6vRgf8=/?moveToWidget=3458764560239189204&cot=14) > if no issue exists, please create an issue and ask the maintainers about this first > > #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change > Please delete options that are not relevant. - Breaking change (fix or feature that would cause existing functionality to not work as expected) - Chore (housekeeping or task changes that don't impact user perception) > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [x] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### 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 - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed --------- Co-authored-by: rahulramesha <rahul@appsmith.com> Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Co-authored-by: Preet Sidhu <preetsidhu.bits@gmail.com> Co-authored-by: Aswath K <aswath.sana@gmail.com>
2023-09-11 15:55:11 +00:00
/**
* 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;
}
interface NewWidgetBlock {
chore: BaseWidget Restructuring (#26562) ## Description Create a Basewidget wrapper that supplies Widget Onion as per the layout system. involves extracting widget layers presently in the BaseWidget into HOCs and hooks and make sure layout systems can be scaled. Make sure Modal widget is handled as a overlay widget whose wrappers are supplied by basewidget instead of modal widget implementing its own editing blocks. This PR also separates the drag n drop logic for both auto layout and fixed layout. They are moved into respective Layout system folders to have clear sepsration of concern #### PR fixes following issue(s) Fixes #26674 Fixes #26675 Fixes #26676 Fixes #26570 Fixes #26590 Fixes #26591 Fixes #26592 <img width="931" alt="BaseWidgetHOC" src="https://github.com/appsmithorg/appsmith/assets/35134347/22f4cf1e-e4c5-4475-83a8-6818e7cebe70"> [Miro Link to view the new system](https://miro.com/app/board/uXjVM6vRgf8=/?moveToWidget=3458764560239189204&cot=14) > if no issue exists, please create an issue and ask the maintainers about this first > > #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change > Please delete options that are not relevant. - Breaking change (fix or feature that would cause existing functionality to not work as expected) - Chore (housekeeping or task changes that don't impact user perception) > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [x] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### 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 - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed --------- Co-authored-by: rahulramesha <rahul@appsmith.com> Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Co-authored-by: Preet Sidhu <preetsidhu.bits@gmail.com> Co-authored-by: Aswath K <aswath.sana@gmail.com>
2023-09-11 15:55:11 +00:00
columns: number;
rows: number;
widgetId: string;
detachFromLayout: boolean;
isDynamicHeight: boolean;
type: WidgetType;
}
chore: BaseWidget Restructuring (#26562) ## Description Create a Basewidget wrapper that supplies Widget Onion as per the layout system. involves extracting widget layers presently in the BaseWidget into HOCs and hooks and make sure layout systems can be scaled. Make sure Modal widget is handled as a overlay widget whose wrappers are supplied by basewidget instead of modal widget implementing its own editing blocks. This PR also separates the drag n drop logic for both auto layout and fixed layout. They are moved into respective Layout system folders to have clear sepsration of concern #### PR fixes following issue(s) Fixes #26674 Fixes #26675 Fixes #26676 Fixes #26570 Fixes #26590 Fixes #26591 Fixes #26592 <img width="931" alt="BaseWidgetHOC" src="https://github.com/appsmithorg/appsmith/assets/35134347/22f4cf1e-e4c5-4475-83a8-6818e7cebe70"> [Miro Link to view the new system](https://miro.com/app/board/uXjVM6vRgf8=/?moveToWidget=3458764560239189204&cot=14) > if no issue exists, please create an issue and ask the maintainers about this first > > #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change > Please delete options that are not relevant. - Breaking change (fix or feature that would cause existing functionality to not work as expected) - Chore (housekeeping or task changes that don't impact user perception) > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [x] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### 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 - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed --------- Co-authored-by: rahulramesha <rahul@appsmith.com> Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Co-authored-by: Preet Sidhu <preetsidhu.bits@gmail.com> Co-authored-by: Aswath K <aswath.sana@gmail.com>
2023-09-11 15:55:11 +00:00
/**
* This method returns blocks and dragging spaces of the widgets being dragged on canvas..
* @param newWidget details about teh new widget that is being dragged on canvas
* @param allWidgets widgets properties of all the widgets on the page
* @param isNewWidget indicates if the widget is a new widget
* @param snapColumnSpace distance between each grid columns in pixels
* @param snapRowSpace distance between each grid rows in pixels
* @param childrenOccupiedSpaces array of grid positions of all the widgets on canvas
* @param selectedWidgets array of selected widget ids
* @param containerPadding padding in pixels
* @returns blocksToDraw and draggingSpaces,
* blocksToDraw contains information regarding the widget and positions in pixels
* draggingSpaces only contains the position of the widget in grid columns and rows
*/
export const getBlocksToDraw = (
newWidget: NewWidgetBlock,
allWidgets: CanvasWidgetsReduxState,
isNewWidget: boolean,
snapColumnSpace: number,
snapRowSpace: number,
childrenOccupiedSpaces: WidgetSpace[],
selectedWidgets: string[],
containerPadding: number,
): {
blocksToDraw: WidgetDraggingBlock[];
draggingSpaces: OccupiedSpace[];
} => {
if (isNewWidget) {
return {
blocksToDraw: [
{
top: 0,
left: 0,
width: newWidget.columns * snapColumnSpace,
height: newWidget.rows * snapRowSpace,
columnWidth: newWidget.columns,
rowHeight: newWidget.rows,
widgetId: newWidget.widgetId,
detachFromLayout: newWidget.detachFromLayout,
isNotColliding: true,
fixedHeight: newWidget.isDynamicHeight
? newWidget.rows * snapRowSpace
: undefined,
type: newWidget.type,
},
],
draggingSpaces: [
{
top: 0,
left: 0,
right: newWidget.columns,
bottom: newWidget.rows,
id: newWidget.widgetId,
},
],
};
} else {
const draggingSpaces = childrenOccupiedSpaces.filter((each) =>
selectedWidgets.includes(each.id),
);
return {
draggingSpaces,
blocksToDraw: draggingSpaces.map((each) => ({
top: each.top * snapRowSpace + containerPadding,
left: each.left * snapColumnSpace + containerPadding,
width: (each.right - each.left) * snapColumnSpace,
height: (each.bottom - each.top) * snapRowSpace,
columnWidth: each.right - each.left,
rowHeight: each.bottom - each.top,
widgetId: each.id,
isNotColliding: true,
fixedHeight: each.fixedHeight,
type: allWidgets[each.id].type,
})),
};
}
};
/**
* This method returns the bound updateRelativeRows method with the arguments bounded
* @param updateDropTargetRows method to update drop target rows
* @param snapColumnSpace distance between each grid columns in pixels
* @param snapRowSpace distance between each grid rows in pixels
* @returns
*/
export const getBoundUpdateRelativeRowsMethod = (
updateDropTargetRows:
| ((
widgetIdsToExclude: string[],
widgetBottomRow: number,
) => number | false)
| undefined,
snapColumnSpace: number,
snapRowSpace: number,
) => {
return (drawingBlocks: WidgetDraggingBlock[], rows: number) => {
if (drawingBlocks.length) {
const sortedByTopBlocks = drawingBlocks.sort(
(each1, each2) => each2.top + each2.height - (each1.top + each1.height),
);
const bottomMostBlock = sortedByTopBlocks[0];
const [, top] = getDropZoneOffsets(
snapColumnSpace,
snapRowSpace,
{
x: bottomMostBlock.left,
y: bottomMostBlock.top + bottomMostBlock.height,
} as XYCord,
{ x: 0, y: 0 },
);
const widgetIdsToExclude = drawingBlocks.map((a) => a.widgetId);
return updateBottomRow(
top,
rows,
widgetIdsToExclude,
updateDropTargetRows,
);
}
};
};
/**
* This method helps in updating rows for dropTarget/canvas by using calling updateDropTargetRows passed down
* @param bottom new bottom most row of canvas
* @param rows number of rows of canvas
* @param widgetIdsToExclude array of widget ids to be excluded
* @param updateDropTargetRows method to update drop target rows
* @returns
*/
export const updateBottomRow = (
bottom: number,
rows: number,
widgetIdsToExclude: string[],
updateDropTargetRows:
| ((
widgetIdsToExclude: string[],
widgetBottomRow: number,
) => number | false)
| undefined,
) => {
if (bottom > rows - GridDefaults.CANVAS_EXTENSION_OFFSET) {
return (
updateDropTargetRows && updateDropTargetRows(widgetIdsToExclude, bottom)
);
}
};
/**
* Calculates positions in pixels that needs to be offsetted with the widget's positions to get it's actual position on parent canvas
* @param dragCenterSpace position of the current widget that was grabbed while dragging
* @param isDragging indicates if currently in dragging state
* @param isChildOfCanvas indicates if the dragging widgets are the original child of the canvas
* @param snapRowSpace distance between each grid columns in pixels
* @param snapColumnSpace distance between each grid rows in pixels
* @param containerPadding padding in pixels
* @returns
*/
export const getParentDiff = (
dragCenterSpace: { top: number; left: number },
isDragging: boolean,
isChildOfCanvas: boolean,
snapRowSpace: number,
snapColumnSpace: number,
containerPadding: number,
) => {
let parentDiff = {
top: 0,
left: 0,
};
if (isDragging) {
const shouldCalculateParentDiff =
!isChildOfCanvas && !isEmpty(dragCenterSpace);
if (shouldCalculateParentDiff) {
parentDiff = {
top: dragCenterSpace.top * snapRowSpace + containerPadding,
left: dragCenterSpace.left * snapColumnSpace + containerPadding,
};
} else {
parentDiff = {
top: containerPadding,
left: containerPadding,
};
}
}
return parentDiff;
};
/**
* returns the relative drag start points of the dragging blocks with respect to the dragging group's center
* @param dragCenterSpace position of the current widget that was grabbed while dragging
* @param dragOffset offset of the positions at which the widgets were grabbed for dragging
* @param defaultHandlePositions default handle positions in pixels
* @param isDragging indicates if currently in dragging state
* @param isChildOfCanvas indicates if the dragging widgets are the original child of the canvas
* @param snapRowSpace distance between each grid columns in pixels
* @param snapColumnSpace distance between each grid rows in pixels
* @param containerPadding padding in pixels
* @returns
*/
export const getRelativeStartPoints = (
dragCenterSpace: { top: number; left: number },
dragOffset: { top: number; left: number },
defaultHandlePositions: { top: number; left: number },
isDragging: boolean,
isChildOfCanvas: boolean,
snapRowSpace: number,
snapColumnSpace: number,
containerPadding: number,
) => {
let relativeStartPoints = defaultHandlePositions;
if (isDragging && !isEmpty(dragCenterSpace)) {
const dragLeft = isChildOfCanvas ? dragCenterSpace.left : 0;
const dragTop = isChildOfCanvas ? dragCenterSpace.top : 0;
relativeStartPoints = {
left:
(dragLeft + dragOffset?.left || 0) * snapColumnSpace +
2 * containerPadding,
top:
(dragTop + dragOffset?.top || 0) * snapRowSpace + 2 * containerPadding,
};
}
return relativeStartPoints;
};
/**
* returns the position of the current widget that was grabbed while dragging
* @param dragCenter
* @param childrenOccupiedSpaces
* @returns
*/
export const getDragCenterSpace = (
dragCenter: DraggingGroupCenter | undefined,
childrenOccupiedSpaces: WidgetSpace[],
): { top: number; left: number } => {
const defaultDragCenterSpace = { left: 0, top: 0 };
if (dragCenter && dragCenter.widgetId) {
// Dragging by widget
return (
childrenOccupiedSpaces.find((each) => each.id === dragCenter.widgetId) ||
defaultDragCenterSpace
);
} else if (dragCenter && dragCenter.top && dragCenter.left) {
// Dragging by Widget selection box
return { top: dragCenter.top, left: dragCenter.left };
} else {
return defaultDragCenterSpace;
}
};
/**
* 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 | null,
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,
modifyBlock: 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;
if (!modifyBlock) {
// 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;
};