* added multi select back * (WIP): Complete the dynamc height update logic * (WIP): Dynamic height logic * (WIP): Container computation logic, Next steps: Prevent reflow when resize is disabled. Fix logic of widgets randomly changing positions (Debug) * Fix logic in container computations * Integrate for PoC * fixed the no initial load dynamic height updates * Stop vertical resize and reflow when dynamic height is enabled for a widget * added another container in text widget * enabled dynamic height for container widgets * removed dynamic height feature from list widget * Fixed Button and Input components height increase * added an experiment to overflow the content if maxHEight is less * removed the ref of Textwidget by mistake, added it back * fixed text widget height overflow problem with a little hack * added long labels with text * fixed the table scroll issue * overflow fixed for json form widget * added extra 8px height for Switch, Rating and Checkbox Height * (WIP): Resolve issues * (WIP): Fix widget padding issue * added overflow container for Radio and Switch group widgets * (WIP): Have modals work with dynamic height * added the overlay and the handles * added dragging behavior to the dots * fixed the overlapping with the selection tool * (WIP): Fix issues reported * now we can update the property pane values back from overlay handles * now we can update the property pane values back from overlay handles * (WIP): Fix table widget * Fix package.json * Remove unit tests temporarily * Fix unit test * (WIP): Fix modal resize. Fix cursors. Fix border issue on non-resizable widgets * fetch component heights using the requestAnimationFrame callback * behavioural changes * (WIP): Fix issues on the platform * Update main container size appropriately * more behavioural changes * overlay now only be visible when hovering over the dots * grid showing and widget reselecting * added onfocus and onblur events to property pane listeners * added onfocus and onblur events to property pane listeners * added a range slider for min and max * added demarcations for slider values * (WIP): Fix platform workflows for dynamic height * Fix issues with widgets * Fix removed import * - Add missing cypress files * set the limits * limit increase on change * Fix z-index of min max limit indicators. Fix unused-vars warnings * Fix Table Widget and Text Widget issues * Fix: all the bugs in the bug master list for DH (#16268) * changed the zindex for the signifiers * showing signifiers only when the widget is selected * made changes suggested by Momcilo * activate the dots when the fields are active * created a new centered dot handle * removed overlays on focus and made the border more like deisgn * handles on top of other widgets * hide the overlay when multiple widgets are selected * added a white border * added a white border * bug #15509 resolved * changed the minDynamicHeightLimit to 2 instead of 4 to fix the Bug #15527 * removed the height auto fix from BaseInputComponent to fix the Bug #15388 * removed the condition to not ccalculate dynamic height when the row difference is less than 2 to fix the bug 15353 * made fixes for the bug #16307 * made fixes for the bug #16308 * made fixes for bug 16310 * made fixes for the bug #16402 * removed some log statements * made fixes for the bug #16407 * fixed label problem found in the issue #16543 * made fixes for the issue #16547 * made fixes for the bug #16492 * redeploy * (WIP): Fix to make this branch functional * imported LabelWithTooltip back from design system * signifier is now centered * filled the signifier with primary color * overlay hidden while dragging * made the signifier dashed border also draggable * Fix issue #16590 (#16798) * set the limits to 4 rows * replaced the static 40 value * added signifiers for modal widget * added signifiers for modal widget * tried solving the scroll issue for widgets when there are limits * solved the height problem using ResizeObserver * (WIP): Fix maxDynamicHeight issue with container widgets: * made the changes as per the review * fixed the issue for input widget when label gets out of border * hide text widget overflow options if auto height is enabled * (WIP): In view mode, invisible widgets now donot take space (#16920) * (WIP): In view mode, invisible widgets now donot take space * (WIP): Enable the feature where invisible widgets in view mode don't take space to all widgets irrespective of the dynamic height feature * Remove Replay conditional * removed the scroll container for container type widgets * removed the scroll container for container type widgets * updated the hook to set overflow none for text widget * fixed the should dynamic height logic to respect the min height limit * Modal widget adheres to dynamic height (#16995) * Modal widget adheres to dynamic height * WIP: POC: fix dynamic height issues (#16996) Fix height less than 4 issue. Fix JSONForm adherence to min and max height * POC: Dynamic height undo redo issue (#17085) * Revert debouce timeout * (WIP): Fix issue with undo-redo in dynamic height * fix: Dynamic height issue fixes (#17153) * Dynamic height issue fixes == - Fix issue where nested widgets did not ensure parent dynamic height updates - Fix issue where Modal widget updates came in subsequent renders - Fix issue where JSONForm collapses - Fix performance issue for independent updates * Use functions to get min and max dynamic height * Fix issue where variable might have been undefined * added the dynamic container into the deploy mode as well * added overflow-x hidden when overflow-y is active in the dynamic height container * fix: Dynamic height Issue fixes (#17204) Fix preview mode invisible widgets. Fix Tabs widget dynamic height. * removed a console.log statement * removed the slider control file * imported the LabelWithTooltip from the repo rather than ds * word-break CSS rules added for Switch and Checkbox widget when Dynamic Height is enabled * abstracted the check for dynamic height with limits enabled as isDynamicHeightWithLimitsEnabledForWidget * abstracted the static value of 10 in dynamic height overlay to GridDefaults * abstracted min and max dynamic height limits to getters * fix: replaced all the refs for simpler widgets (#17353) * replaced all the refs for simpler widgets * removed the updateDynamicHeight from componentDidUpdate in BaseWidget * added back lifecycle methods back to BaseWidget * removed the contentRef from SwitchGroup and Table * updating the height from the auto height with limits as well * some hacks to make the limits work * working solution * used setTimeout to send an update to updateDynamicHeight from overlay update * removed a log * added requestanimationframe in settimeout Co-authored-by: Ankur Singhal <ankursinghal@Ankurs-MacBook-Pro-2.local> Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * Fix issues caused during merge * Remove unneeded derived property * removed more unnecessary code which should have been removed after removing the ref dependency * fixed the maxDynamicHeight issue * Fix issue where property configs were not being sent * fix: Auto Height Feature - add selectors for tests (#17687) Add selectors for auto height cypress tests * fix: removed height auto default theme (#17415) removed height auto css rule from the default theme Co-authored-by: Ankur Singhal <ankursinghal@Ankurs-MacBook-Pro-2.local> * fix: Auto Height Feature - Resolve issues and restructure code (#17686) * Fix issues in dynamic height. Restructure code and reduce abstraction leaks * Fix typescript issues * Update based on review comments. Comment migrations, as a cyclic import is causing the jest tests to fail. * Remove unused imports * Decrease code nesting * added the base styles for the overlay like position and z-index in its styled component css * used the isDynamicHeightEnabled prop to set the height of SwitchGroup and RadioGroup widgets from 32px to 100% in case of inline mode * fix: Auto Height - Resolve issues (#17737) * Fix Tabs Widget showTabs toggle based auto height. Revert removal of BaseWidget code. Remove box-intersect and use a bruteforce algorithm. Add base logic for having containers collapse due to hidden child widgets * Hide scroll contents and overflow property pane controls when dynamic height is enabled * Removed the class property expectedHeight from BaseWidget as it is not useful in the overlay logic after some changes * fixed the left alignment issue of label in the rich text editor by adding some styles applied only when the dynamic height is enabled * fixed the input field stretching issue in case of Dynamic height by adding some CSS styles when isDynamicHeight is true * Fix failing modal widget cypress tests * Fix issue with scrollContents and Tabs Widget defaulTab * added a little bit padding of 4px to the right of scroll container of dynamic height with limit * Add test locators for resize handles * removed the dynamic height logic from the table widget * fix: Auto-Height invisible widgets (#17849) * Fix issue where invisible widgets were still taking space * Make sure to collapse only if dynamic height is enabled * Fix issues with reflow (not the invisible widgets) * Fix container min height issues * Fix reflow with original bottom and top values. Testing needed * Fix invisible widgets * fix: enabled dynamic height for stat box widget (#17971) enabled dynamic height for stat box widget Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: added a min height to rich text editor so that it does not collapse (#17970) added a min height to rich text editor so that it does not collapse Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * Fix issue with resizing auto height widget * Add helper text to educate users regarding the scroll disconnect in WYSIWYG * fix: Auto Height Fixes (#18111) AUTO HEIGHT FIXES - Fix JSONForm height discrepancy - Fix issue where widgets moved below the other - Fix droptarget height after parent container resize * fix: sliced up the DynamicHeightOverlay component a little bit (#18100) * sliced up the DynamicHeightOverlay component a little bit * more refactoring * more refactoring * used release event emitter and refactored more Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: rich text editor center alignment issue (#18142) * removed the center alignment from rich text editor * dummy commit Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: old DSL container collapse (#18160) * Fix issue where old containers from old DSLs used to collapse when auto height was enabled * Fix issue where old containers don't allow new widgets to be added when auto height is enabled, this is because the shouldScrollContents is undefined * fix: input widgets issue (#18172) fixed the auto height not working issue Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: preview deploy mode (#18174) fixed the preview and deploy mode Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: auto height limits label intersection with handle dot (#18186) fixed the position of the limits label to the right so that it will not intersect with the handle dot Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: auto height limits rich text editor min height (#18187) decrease the min height of the RTE so that it does not have the boundary issue with the max limit when auto height with limits is enabled Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: grammatical error in the help text (#18188) changed react to reacts in the helpText of the dynamic height property in the proeprty pane Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: auto height tabs double scroll (#18210) solved the issue by disabling the scroll for the child canvas widget in the tabs widget Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: auto height limits resizing (#18213) * fixed the auto height limits resizing issue * made the auto height overlay independent of isResizing and used its own property to show the grid * some more refactoring Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * dummy commit * fix: old apps container issue (#18255) filtered out the widgets which are detached from layout Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: fixing auto height in childless containers. (#18263) fixing auto height in childless containers. * task: Dynamic height reflow fixes in Branch (#18244) dynamic height reflow fixes * fix: compact label issue and min and max limits numeric input (#18282) fixed compact label issue and turned min and max limits to numeric input Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: LabelWithTooltip help icon fix * fix: NaN and min limit for min and max (#18284) * fixed compact label issue and turned min and max limits to numeric input * fixed NaN and set min to be 4 Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: validation issues for min max (#18286) * fixed compact label issue and turned min and max limits to numeric input * fixed NaN and set min to be 4 * validations start working min max Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * added a full stop to container scroll helper text * validations start working min max * dummy commit * feat: stop resizing auto height widgets vertically because of Drag n Drop Reflow (#18267) * reflow fixes * stop resizing auto height widgets vertically because of Drag n Drop Reflow * feat: Analytics for Dynamic height (#18279) * Fix canvas min height issue and invisible widgets issue and remove logs and fix issue where widgets overlapped when coming back from preview mode to edit mode * Fix issue with containers not respecting auto height and decreasing height * Fix issue with modal widget not hugging contents, and container widgets never become visible after going invisible * Fix issue where existing containers don't have correct min height for child canvas * fix: canvasLevelsReducers test (#18301) fixed the canvasLevelsReducers test Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: removed auto height min max config from widget features (#18316) removed auto height min max config from widget features Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: Fixing Modal Height updates (#18317) Fixing Modal Height updates * fix: text widget background auto height (#18319) added background color of Text widget back to the auto height container Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * test: cypress tests for auto height (#17676) * Added tests for dynamic height * updated tests for another usecase * moved locators into commonfile * updated common method * added tests for some more widgets * Added tests for jsonForm / Form widget * Updated the test * updated test for multiple text widgets * updated test with few more usecases * updated the dsl * updated tests for text change * updated tests based on new changes * updated cypress test fixes * fix: auto height container merge poc wrt release (#18334) updated the poc wrt PR already merged in the release regarding the auto height container Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: renamed auto height overlay components and added some tests (#18333) * renamed auto height overlay components and added some tests * replaced the 10 value with GridDefaults * avoiding event to reach drop target Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * updated tests * Merge all code into one branch * Fix failing AutoHeightcontainer test * fix: Fix reflow computations which were causing widget overlap (#18300) * Fix reflow computations which were causing widget overlap * Fix issues with parent container height and overlapping widgets * Remove console logs * Revert comment * Fix issues related to reflow of containers * feat: Making getEffectedBoxes a Recursive function in autoHeight Reflow (#18336) Making getEffectedBoxes a Recursive function in autoHeight Reflow * Return null for invisible widgets from withWidgetProps * Remove duplicate import Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com> * Remove missed console log * fix: Label position gets deselected on selecting already selected option (#18298) * fix: Label position gets deselected on selecting the already selected value * Added migration for Currency & Phone input widgets * simplify migration function using a utility * combine conditions * Increments LATEST_PAGE_VERSION * Update DynamicHeight_Visibility_spec.js updated a check wrt auto height * Handling Modals for canvas size calculations * fix: migrate label position test failing issue (#18365) fixed migrate label postition test failing issue Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * removed the two unwanted imports from DSLMigrations to fix client build * fix: Auto height zero and limits issue (#18366) fixed the auto height zero and limits issue Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> * fix: Auto height regression issues (#18367) * Fix auto height regression issues #18367 * feat: auto height migrations (#18368) Add auto height migrations * Increase file caching size * Use manual array for list of auto height enabled widgets * Fix cypress test dsl versions * Revert changes to shouldUpdateHeightDynamically * Update test results based on code changes * Marginally increase the workbox file size cache * review comment incorporated for test spec * Update container auto height property on drop * added small wait for validation Co-authored-by: Ankur Singhal <ankur@appsmith.com> Co-authored-by: rahulramesha <rahul@appsmith.com> Co-authored-by: Abhinav Jha <zatanna@Abhinavs-iMac.lan> Co-authored-by: Ankur Singhal <ankursinghal@Ankurs-MacBook-Pro-2.local> Co-authored-by: Ankur Singhal <ankurrsinghal@gmail.com> Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com> Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Co-authored-by: Albin <albin@appsmith.com> Co-authored-by: Aswath K <aswath.sana@gmail.com> Co-authored-by: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com> Co-authored-by: Apple <nandan@thinkify.io>
1901 lines
56 KiB
TypeScript
1901 lines
56 KiB
TypeScript
import { OccupiedSpace } from "constants/CanvasEditorConstants";
|
|
import { cloneDeep, isUndefined } from "lodash";
|
|
import { Rect } from "utils/boxHelpers";
|
|
import {
|
|
CollidingSpace,
|
|
CollidingSpaceMap,
|
|
CollisionAccessors,
|
|
CollisionMap,
|
|
CollisionTree,
|
|
CollisionTreeCache,
|
|
GridProps,
|
|
MathComparators,
|
|
MovementLimitMap,
|
|
OrientationAccessors,
|
|
PrevReflowState,
|
|
ReflowDirection,
|
|
ReflowedSpace,
|
|
ReflowedSpaceMap,
|
|
SecondOrderCollisionMap,
|
|
SpaceAttributes,
|
|
SpaceMap,
|
|
SpaceMovementMap,
|
|
} from "./reflowTypes";
|
|
|
|
/**
|
|
* This method is used while calculating MovementMap of the reflowed spaces,
|
|
* if a particular space's movement is already calculated once, this method returns a boolean
|
|
* if the current calculation should replace the previous calculation
|
|
*
|
|
* @param oldMovement
|
|
* @param newMovement
|
|
* @param direction
|
|
* @returns boolean
|
|
*/
|
|
export function shouldReplaceOldMovement(
|
|
oldMovement: ReflowedSpace,
|
|
newMovement: ReflowedSpace,
|
|
direction: ReflowDirection,
|
|
) {
|
|
if (!oldMovement) return true;
|
|
|
|
const { directionIndicator, isHorizontal } = getAccessor(direction);
|
|
|
|
const distanceKey = isHorizontal ? "X" : "Y";
|
|
const dimensionKey = isHorizontal ? "width" : "height";
|
|
|
|
const oldDistance = oldMovement[distanceKey],
|
|
newDistance = newMovement[distanceKey];
|
|
|
|
const oldDimension = oldMovement[dimensionKey] || 0,
|
|
newDimension = newMovement[dimensionKey] || 0;
|
|
|
|
//if either one is undefined and other one is a number, or if both are undefined it should replace
|
|
if (
|
|
(oldDistance === undefined && newDistance !== undefined) ||
|
|
(oldDistance !== undefined && newDistance === undefined) ||
|
|
(oldDistance === undefined && newDistance === undefined)
|
|
)
|
|
return true;
|
|
|
|
if (oldDistance === undefined || newDistance === undefined) {
|
|
return false;
|
|
}
|
|
|
|
// if old movement is in the opposite direction return false
|
|
if (oldDistance > 0 !== newDistance > 0) return false;
|
|
|
|
return newDistance === oldDistance
|
|
? compareNumbers(newDimension, oldDimension, directionIndicator > 0)
|
|
: compareNumbers(newDistance, oldDistance, directionIndicator > 0);
|
|
}
|
|
|
|
/**
|
|
* for calculating all the spaces a particular space is colliding with,
|
|
* we have to resize the space through out it's movement in the direction
|
|
* for example, if a space is moved by 2 rows in the BOTTOM direction,
|
|
* then the space's bottom dimension is increased by 2 rows
|
|
*
|
|
* @param space
|
|
* @param accessors
|
|
* @returns resized Dimensions of the space
|
|
*/
|
|
export function getResizedDimensions(
|
|
space: CollidingSpace,
|
|
{
|
|
direction,
|
|
directionIndicator,
|
|
parallelMax,
|
|
parallelMin,
|
|
}: CollisionAccessors,
|
|
) {
|
|
const reflowedPosition = { ...space, children: [] };
|
|
|
|
reflowedPosition[direction] =
|
|
reflowedPosition.collidingValue +
|
|
directionIndicator *
|
|
(reflowedPosition[parallelMax] - reflowedPosition[parallelMin]);
|
|
|
|
return reflowedPosition;
|
|
}
|
|
|
|
/**
|
|
* sort the collidingSpaces with respect to the distance from the newSpacePositions
|
|
* eg, for the colliding spaces of dragging/moving spaces,
|
|
* it is sorted by distance from dragging/moving spaces
|
|
*
|
|
* @param collidingSpaces
|
|
* @param staticPosition
|
|
* @param isAscending
|
|
*/
|
|
export function sortCollidingSpacesByDistance(
|
|
collidingSpaces: CollidingSpace[],
|
|
isAscending = true,
|
|
) {
|
|
const distanceComparator = getDistanceComparator(isAscending);
|
|
collidingSpaces.sort(distanceComparator);
|
|
}
|
|
|
|
/**
|
|
* This is a comparator for colliding spaces to sort them by distance from dragging/moving spaces
|
|
*
|
|
* @param isAscending
|
|
* @returns comparator function
|
|
*/
|
|
function getDistanceComparator(isAscending = true) {
|
|
return function(spaceA: CollidingSpace, spaceB: CollidingSpace) {
|
|
const accessorA = getAccessor(spaceA.direction);
|
|
const accessorB = getAccessor(spaceB.direction);
|
|
|
|
const distanceA = Math.abs(
|
|
spaceA.collidingValue - spaceA[accessorA.oppositeDirection],
|
|
);
|
|
const distanceB = Math.abs(
|
|
spaceB.collidingValue - spaceB[accessorB.oppositeDirection],
|
|
);
|
|
return isAscending ? distanceB - distanceA : distanceA - distanceB;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Method to generate object map with canHorizontalMove and canVerticalMove for every Dragging/MovingSpace
|
|
*
|
|
* @param existingMovementLimits
|
|
* @param spaceMovementMap
|
|
* @param delta
|
|
* @param beforeLimit
|
|
* @returns object map with canHorizontalMove and canVerticalMove for every Dragging/MovingSpace
|
|
*/
|
|
export function getShouldReflow(
|
|
existingMovementLimits: MovementLimitMap,
|
|
spaceMovementMap: SpaceMovementMap | undefined,
|
|
delta = { X: 0, Y: 0 },
|
|
beforeLimit = false,
|
|
) {
|
|
if (!spaceMovementMap) return;
|
|
|
|
const movementKeys = Object.keys(spaceMovementMap || {});
|
|
|
|
for (const movementKey of movementKeys) {
|
|
const spaceMovements = spaceMovementMap[movementKey];
|
|
|
|
let canHorizontalMove = true,
|
|
canVerticalMove = true;
|
|
for (const movementLimit of spaceMovements) {
|
|
const {
|
|
coordinateKey,
|
|
directionalIndicator,
|
|
isHorizontal,
|
|
maxMovement,
|
|
} = movementLimit;
|
|
|
|
const canMove = compareNumbers(
|
|
delta[coordinateKey],
|
|
maxMovement,
|
|
directionalIndicator < 0,
|
|
beforeLimit,
|
|
);
|
|
|
|
if (!canMove) {
|
|
if (isHorizontal) canHorizontalMove = false;
|
|
else canVerticalMove = false;
|
|
}
|
|
}
|
|
|
|
let prevCanHorizontalMove = true,
|
|
prevCanVerticalMove = true;
|
|
if (existingMovementLimits[movementKey]) {
|
|
({
|
|
canHorizontalMove: prevCanHorizontalMove,
|
|
canVerticalMove: prevCanVerticalMove,
|
|
} = existingMovementLimits[movementKey]);
|
|
}
|
|
|
|
existingMovementLimits[movementKey] = {
|
|
canVerticalMove: canVerticalMove && prevCanVerticalMove,
|
|
canHorizontalMove: canHorizontalMove && prevCanHorizontalMove,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* initializes canHorizontalMove and canVerticalMove as true for all moving/resizing spaces
|
|
*
|
|
* @param newSpacePositions
|
|
* @returns object map with canHorizontalMove and canVerticalMove for every Dragging/MovingSpace
|
|
*/
|
|
export function initializeMovementLimitMap(
|
|
newSpacePositions: OccupiedSpace[],
|
|
): MovementLimitMap {
|
|
const movementLimitMap: MovementLimitMap = {};
|
|
|
|
for (const spacePosition of newSpacePositions) {
|
|
movementLimitMap[spacePosition.id] = {
|
|
canHorizontalMove: true,
|
|
canVerticalMove: true,
|
|
};
|
|
}
|
|
|
|
return movementLimitMap;
|
|
}
|
|
|
|
/**
|
|
* Should return X and Y absolute coordinates of the Dragging/moving spaces relative to the original space positions
|
|
*
|
|
* @param OGSpacePositionsMap
|
|
* @param newSpacePositionsMap
|
|
* @param direction
|
|
* @returns Object with X, Y
|
|
*/
|
|
export function getDelta(
|
|
OGSpacePositionsMap: SpaceMap,
|
|
newSpacePositionsMap: SpaceMap,
|
|
direction: ReflowDirection,
|
|
) {
|
|
const tempId = Object.keys(OGSpacePositionsMap)[0];
|
|
const OGSpacePosition = OGSpacePositionsMap[tempId];
|
|
const newSpacePosition = newSpacePositionsMap[tempId];
|
|
let X = OGSpacePosition.left - newSpacePosition.left,
|
|
Y = OGSpacePosition.top - newSpacePosition.top;
|
|
|
|
if (direction.indexOf("|") > 0) {
|
|
const [verticalDirection, horizontalDirection] = direction.split("|");
|
|
const { direction: xDirection } = getAccessor(
|
|
horizontalDirection as ReflowDirection,
|
|
);
|
|
const { direction: yDirection } = getAccessor(
|
|
verticalDirection as ReflowDirection,
|
|
);
|
|
X = OGSpacePosition[xDirection] - newSpacePosition[xDirection];
|
|
Y = OGSpacePosition[yDirection] - newSpacePosition[yDirection];
|
|
return { X, Y };
|
|
}
|
|
|
|
const { direction: directionalAccessor, isHorizontal } = getAccessor(
|
|
direction,
|
|
);
|
|
const diff =
|
|
OGSpacePosition[directionalAccessor] -
|
|
newSpacePosition[directionalAccessor];
|
|
|
|
if (isHorizontal) X = diff;
|
|
else Y = diff;
|
|
|
|
return { X, Y };
|
|
}
|
|
|
|
/**
|
|
* This method checks if the occupied spaces are overlapping with newSpacePositions,
|
|
* and generates a map with the overlapping spaces with the direction of collision
|
|
*
|
|
* @param newSpacePositions Dragging/Moving Spaces
|
|
* @param occupiedSpaces
|
|
* @param direction
|
|
* @param prevCollidingSpaceMap
|
|
* @param isHorizontalMove
|
|
* @param prevSpacesMap
|
|
* @param forceDirection
|
|
* @param primaryCollisionMap
|
|
* @returns collision spaces Map
|
|
*/
|
|
export function getCollidingSpaceMap(
|
|
newSpacePositions: OccupiedSpace[],
|
|
occupiedSpaces: OccupiedSpace[],
|
|
direction: ReflowDirection,
|
|
prevCollidingSpaceMap: CollidingSpaceMap,
|
|
isHorizontalMove?: boolean,
|
|
prevSpacesMap?: SpaceMap,
|
|
forceDirection = false,
|
|
primaryCollisionMap?: CollisionMap,
|
|
) {
|
|
let isColliding = false;
|
|
const collidingSpaceMap: CollisionMap = {};
|
|
let order = 1;
|
|
const orientationalAccessor = getOrientationAccessor(isHorizontalMove);
|
|
const oppositeOrientationalAccessor = getOrientationAccessor(
|
|
!isHorizontalMove,
|
|
);
|
|
|
|
for (const newSpacePosition of newSpacePositions) {
|
|
for (const occupiedSpace of occupiedSpaces) {
|
|
if (areOverlapping(occupiedSpace, newSpacePosition)) {
|
|
isColliding = true;
|
|
const currentSpaceId = occupiedSpace.id;
|
|
|
|
//sometimes direction mentioned cannot be trusted a direction is intelligently calculated
|
|
let movementDirection = getCorrectedDirection(
|
|
occupiedSpace,
|
|
prevSpacesMap && prevSpacesMap[newSpacePosition.id]
|
|
? prevSpacesMap[newSpacePosition.id]
|
|
: undefined,
|
|
direction,
|
|
forceDirection,
|
|
prevCollidingSpaceMap && prevCollidingSpaceMap[orientationalAccessor],
|
|
isHorizontalMove,
|
|
);
|
|
|
|
// if in case this is a second run of the getMovementMap,
|
|
//direction should be same as first or primary run.
|
|
if (
|
|
primaryCollisionMap &&
|
|
primaryCollisionMap[occupiedSpace.id] &&
|
|
primaryCollisionMap[occupiedSpace.id].collidingId ===
|
|
newSpacePosition.id
|
|
) {
|
|
movementDirection = primaryCollisionMap[occupiedSpace.id].direction;
|
|
}
|
|
|
|
// if incase of the previous run of the entire reflow algorithm, then even though it might be in
|
|
//the opposite orientation of current, we should still consider the previous direction
|
|
if (
|
|
prevCollidingSpaceMap &&
|
|
prevCollidingSpaceMap[oppositeOrientationalAccessor] &&
|
|
prevCollidingSpaceMap[oppositeOrientationalAccessor][
|
|
occupiedSpace.id
|
|
] &&
|
|
prevCollidingSpaceMap[oppositeOrientationalAccessor][occupiedSpace.id]
|
|
.collidingId === newSpacePosition.id
|
|
) {
|
|
movementDirection =
|
|
prevCollidingSpaceMap[oppositeOrientationalAccessor][
|
|
occupiedSpace.id
|
|
].direction;
|
|
}
|
|
|
|
const {
|
|
direction: directionAccessor,
|
|
directionIndicator,
|
|
isHorizontal,
|
|
} = getAccessor(movementDirection);
|
|
|
|
if (isHorizontal !== isHorizontalMove) continue;
|
|
|
|
// If this particular space is already colliding with another dragging space,
|
|
// then the highest in the particular direction will override the lowest value
|
|
const currentCollidingSpace = collidingSpaceMap[currentSpaceId];
|
|
if (
|
|
!currentCollidingSpace ||
|
|
(currentCollidingSpace &&
|
|
compareNumbers(
|
|
newSpacePosition[directionAccessor],
|
|
currentCollidingSpace.collidingValue,
|
|
directionIndicator > 0,
|
|
))
|
|
) {
|
|
collidingSpaceMap[currentSpaceId] = {
|
|
...occupiedSpace,
|
|
direction: movementDirection,
|
|
collidingValue: newSpacePosition[directionAccessor],
|
|
collidingId: newSpacePosition.id,
|
|
isHorizontal,
|
|
order,
|
|
};
|
|
order++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
isColliding,
|
|
collidingSpaceMap,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* This method is used while generating a collision tree,
|
|
* This checks if the newSpacePosition is overlapping with occupiedSpaces
|
|
* and creates a array of those spaces with the direction of collision,
|
|
* This is usually the direction passed down but with some exceptions
|
|
* based on prevReflowState and isDirectCollidingSpace, it can be different
|
|
*
|
|
* @param newSpacePosition
|
|
* @param OGPosition
|
|
* @param globalDirection
|
|
* @param direction
|
|
* @param gridProps
|
|
* @param prevReflowState
|
|
* @param globalCollisionMap
|
|
* @param occupiedSpaces
|
|
* @param occupiedSpaces
|
|
* @param isDirectCollidingSpace
|
|
* @returns Colliding Spaces Array
|
|
*/
|
|
export function getCollidingSpacesInDirection(
|
|
newSpacePosition: CollidingSpace,
|
|
OGPosition: OccupiedSpace,
|
|
globalDirection: ReflowDirection,
|
|
direction: ReflowDirection,
|
|
gridProps: GridProps,
|
|
prevReflowState: PrevReflowState,
|
|
globalCollisionMap: CollisionMap,
|
|
occupiedSpaces?: OccupiedSpace[],
|
|
isDirectCollidingSpace = false,
|
|
) {
|
|
const collidingSpaces: CollidingSpace[] = [];
|
|
const occupiedSpacesInDirection = filterSpaceByDirection(
|
|
newSpacePosition,
|
|
occupiedSpaces,
|
|
direction,
|
|
);
|
|
|
|
const currentOccupiedSpaces = filterSpaceByDirection(
|
|
newSpacePosition,
|
|
occupiedSpaces,
|
|
getOppositeDirection(direction),
|
|
);
|
|
const accessor = getAccessor(direction);
|
|
|
|
const { prevMovementMap, prevSecondOrderCollisionMap } = prevReflowState;
|
|
|
|
let order = 1;
|
|
for (const occupiedSpace of currentOccupiedSpaces) {
|
|
// determines if the space acn be added to the list of colliding spaces, if so in what direction
|
|
const {
|
|
changedDirection,
|
|
collidingValue,
|
|
isHorizontal,
|
|
shouldAddToArray,
|
|
} = ShouldAddToCollisionSpacesArray(
|
|
newSpacePosition,
|
|
OGPosition,
|
|
occupiedSpace,
|
|
direction,
|
|
accessor,
|
|
isDirectCollidingSpace,
|
|
gridProps,
|
|
globalDirection,
|
|
prevMovementMap,
|
|
prevSecondOrderCollisionMap,
|
|
);
|
|
|
|
let currentDirection = direction,
|
|
currentCollidingValue = newSpacePosition[accessor.direction],
|
|
isCurrentHorizontal = accessor.isHorizontal;
|
|
|
|
if (collidingValue !== undefined && changedDirection) {
|
|
currentDirection = changedDirection;
|
|
currentCollidingValue = collidingValue;
|
|
isCurrentHorizontal = !!isHorizontal;
|
|
}
|
|
|
|
const currentAccessor = getAccessor(currentDirection);
|
|
const collidingSpace = globalCollisionMap[occupiedSpace.id];
|
|
|
|
// If the space already collides with a moving/dragging space and it's collisionValue is farther than current's Then don't add.
|
|
if (
|
|
collidingSpace &&
|
|
collidingSpace.direction === currentDirection &&
|
|
compareNumbers(
|
|
collidingSpace.collidingValue,
|
|
currentCollidingValue,
|
|
currentAccessor.directionIndicator > 0,
|
|
)
|
|
)
|
|
continue;
|
|
|
|
if (shouldAddToArray) {
|
|
collidingSpaces.push({
|
|
...occupiedSpace,
|
|
direction: currentDirection,
|
|
collidingValue: currentCollidingValue,
|
|
collidingId: newSpacePosition.id,
|
|
isHorizontal: isCurrentHorizontal,
|
|
order,
|
|
});
|
|
order++;
|
|
}
|
|
}
|
|
|
|
return {
|
|
collidingSpaces,
|
|
occupiedSpacesInDirection,
|
|
skipCollisionTree: false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Checks if the collidingSpace is overlapping with newSpacePosition
|
|
* and check if collidingSpace should be added to the collidingSpaceArray
|
|
* and return and object with boolean and other variables if there is a change in direction
|
|
*
|
|
* @param newSpacePosition
|
|
* @param OGPosition
|
|
* @param collidingSpace
|
|
* @param direction
|
|
* @param accessor
|
|
* @param isDirectCollidingSpace
|
|
* @param gridProps
|
|
* @param globalDirection
|
|
* @param prevMovementMap
|
|
* @param prevSecondOrderCollisionMap
|
|
* @returns object with boolean if the occupied space is to be added to array, also mentions if there is a change in direction
|
|
*/
|
|
export function ShouldAddToCollisionSpacesArray(
|
|
newSpacePosition: CollidingSpace,
|
|
OGPosition: OccupiedSpace,
|
|
collidingSpace: OccupiedSpace,
|
|
direction: ReflowDirection,
|
|
accessor: CollisionAccessors,
|
|
isDirectCollidingSpace: boolean,
|
|
gridProps: GridProps,
|
|
globalDirection: ReflowDirection,
|
|
prevMovementMap?: ReflowedSpaceMap,
|
|
prevSecondOrderCollisionMap?: SecondOrderCollisionMap,
|
|
) {
|
|
// not intersecting then dont add to array
|
|
if (!areOverlapping(collidingSpace, newSpacePosition))
|
|
return { shouldAddToArray: false };
|
|
|
|
const {
|
|
direction: directionAccessor,
|
|
directionIndicator,
|
|
isHorizontal,
|
|
oppositeDirection,
|
|
parallelMax,
|
|
parallelMin,
|
|
} = accessor;
|
|
|
|
const prevCollisionMap: {
|
|
children: {
|
|
[key: string]: any;
|
|
};
|
|
} = (prevSecondOrderCollisionMap &&
|
|
prevSecondOrderCollisionMap[newSpacePosition.id]) || {
|
|
children: {},
|
|
};
|
|
|
|
// if these two spaces previously collided then it calculates how it collided
|
|
if (prevCollisionMap.children[collidingSpace.id]) {
|
|
return getCollisionStatusBasedOnPrevValue(
|
|
newSpacePosition,
|
|
collidingSpace,
|
|
direction,
|
|
accessor,
|
|
gridProps,
|
|
prevCollisionMap,
|
|
prevMovementMap,
|
|
);
|
|
}
|
|
|
|
const movementDirectionAccessor = accessor.isHorizontal
|
|
? "directionX"
|
|
: "directionY";
|
|
|
|
// if it collided in the previous run in this particular orientation the previous direction is considered
|
|
if (
|
|
prevMovementMap &&
|
|
prevMovementMap[collidingSpace.id] &&
|
|
prevMovementMap[collidingSpace.id][movementDirectionAccessor]
|
|
) {
|
|
const prevDirection =
|
|
prevMovementMap[collidingSpace.id][movementDirectionAccessor];
|
|
const shouldAddToArray = prevDirection === direction;
|
|
return { shouldAddToArray };
|
|
}
|
|
|
|
const reflowedSpacePosition = {
|
|
...newSpacePosition,
|
|
[accessor.oppositeDirection]: newSpacePosition.collidingValue,
|
|
};
|
|
|
|
// if it does not collide in it's current reflowed position then it should not be added to the branch
|
|
if (
|
|
!isDirectCollidingSpace &&
|
|
globalDirection !== direction &&
|
|
!areOverlapping(reflowedSpacePosition, collidingSpace)
|
|
)
|
|
return { shouldAddToArray: false };
|
|
|
|
// if this particular space does not collide directly with dragging spaces then can directly be added to list
|
|
if (!isDirectCollidingSpace) return { shouldAddToArray: true };
|
|
|
|
// next to condition return true if they dont have enough data to move forward because it is the first time reflow algorithm is run
|
|
if (!prevSecondOrderCollisionMap) return { shouldAddToArray: true };
|
|
|
|
if (!prevMovementMap || !prevMovementMap[newSpacePosition.id])
|
|
return { shouldAddToArray: true };
|
|
|
|
const { [OGPosition.id]: prevStaticSpace } = getModifiedOccupiedSpacesMap(
|
|
{ [OGPosition.id]: { ...OGPosition } },
|
|
prevMovementMap,
|
|
!isHorizontal,
|
|
gridProps,
|
|
parallelMax,
|
|
parallelMin,
|
|
);
|
|
|
|
//determines if should be added by comparing previous value to now,
|
|
// for example if previous bottom of dragging space is lesser than top of colliding space,
|
|
//then it should collide in the bottom direction
|
|
const shouldAddToArray = compareNumbers(
|
|
collidingSpace[oppositeDirection],
|
|
prevStaticSpace[directionAccessor],
|
|
directionIndicator > 0,
|
|
true,
|
|
);
|
|
|
|
const currentStaticSpace = {
|
|
...newSpacePosition,
|
|
[oppositeDirection]: newSpacePosition.collidingValue,
|
|
};
|
|
|
|
const correctedDirection = getCorrectedDirection(
|
|
collidingSpace,
|
|
prevStaticSpace,
|
|
globalDirection,
|
|
);
|
|
const correctedAccessor = getAccessor(correctedDirection);
|
|
|
|
// if this cant be added in mentioned direction but still intersects, then we determine the correct direction
|
|
// but if it says the currentDirection is same as old one, it should not add
|
|
if (
|
|
!shouldAddToArray &&
|
|
areOverlapping(collidingSpace, currentStaticSpace) &&
|
|
correctedDirection !== direction &&
|
|
correctedAccessor.isHorizontal !== isHorizontal
|
|
) {
|
|
let collidingValue = newSpacePosition.collidingValue;
|
|
if (isHorizontal !== correctedAccessor.isHorizontal) {
|
|
collidingValue = currentStaticSpace[correctedAccessor.direction];
|
|
}
|
|
return {
|
|
shouldAddToArray: true,
|
|
changedDirection: correctedDirection,
|
|
collidingValue: collidingValue,
|
|
isHorizontal: correctedAccessor.isHorizontal,
|
|
};
|
|
}
|
|
return { shouldAddToArray };
|
|
}
|
|
|
|
/**
|
|
* method to filter occupiedSpaces to be after a newSpacePosition in direction
|
|
* eg, if the direction is BOTTOM, this methods returns all the occupiedSpaces below the newSpacePosition
|
|
*
|
|
* @param newSpacePosition
|
|
* @param occupiedSpaces
|
|
* @param direction
|
|
* @returns filtered array of occupied space
|
|
*/
|
|
export function filterSpaceByDirection(
|
|
newSpacePosition: OccupiedSpace,
|
|
occupiedSpaces: OccupiedSpace[] | undefined,
|
|
direction: ReflowDirection,
|
|
): OccupiedSpace[] {
|
|
let filteredSpaces: OccupiedSpace[] = [];
|
|
|
|
const {
|
|
direction: directionAccessor,
|
|
directionIndicator,
|
|
oppositeDirection,
|
|
} = getAccessor(direction);
|
|
|
|
if (occupiedSpaces) {
|
|
filteredSpaces = occupiedSpaces.filter((occupiedSpace) => {
|
|
if (
|
|
occupiedSpace.id === newSpacePosition.id ||
|
|
occupiedSpace.parentId === newSpacePosition.id
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return compareNumbers(
|
|
occupiedSpace[directionAccessor],
|
|
newSpacePosition[oppositeDirection],
|
|
directionIndicator > 0,
|
|
);
|
|
});
|
|
}
|
|
|
|
return filteredSpaces;
|
|
}
|
|
|
|
/**
|
|
* filters out occupiedSpaces, and returns array without a space with id
|
|
*
|
|
* @param id
|
|
* @param occupiedSpaces
|
|
* @returns filtered occupied spaces
|
|
*/
|
|
export function filterSpaceById(
|
|
id: string,
|
|
occupiedSpaces: OccupiedSpace[] | undefined,
|
|
): OccupiedSpace[] {
|
|
let filteredSpaces: OccupiedSpace[] = [];
|
|
if (occupiedSpaces) {
|
|
filteredSpaces = occupiedSpaces.filter((occupiedSpace) => {
|
|
return occupiedSpace.id !== id && occupiedSpace.parentId !== id;
|
|
});
|
|
}
|
|
return filteredSpaces;
|
|
}
|
|
|
|
/**
|
|
* filters out occupiedSpaceMap and removes spaces with ids of newSpacePositionsMap
|
|
*
|
|
* @param newSpacePositionsMap
|
|
* @param occupiedSpaceMap
|
|
* @mutates occupiedSpaceMap
|
|
*/
|
|
export function filterCommonSpaces(
|
|
newSpacePositionsMap: { [key: string]: any },
|
|
occupiedSpaceMap: SpaceMap,
|
|
) {
|
|
const keysToFilter = Object.keys(newSpacePositionsMap);
|
|
for (const key of keysToFilter) {
|
|
if (occupiedSpaceMap[key]) {
|
|
delete occupiedSpaceMap[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* easily the most important method in the algorithm
|
|
*
|
|
* @param r1 space1
|
|
* @param r2 space 2
|
|
* @returns boolean if it is colliding
|
|
*/
|
|
function areOverlapping(r1: Rect, r2: Rect) {
|
|
return !(
|
|
r2.left >= r1.right ||
|
|
r2.right <= r1.left ||
|
|
r2.top >= r1.bottom ||
|
|
r2.bottom <= r1.top
|
|
);
|
|
}
|
|
|
|
/**
|
|
* This method checks if collidingSpace can collide
|
|
* with the moving/dragging spaces in a particular direction
|
|
* if they cant collide in that direction a direction is determined based on prevPositions
|
|
*
|
|
* @param collidingSpace
|
|
* @param prevPositions
|
|
* @param direction
|
|
* @param forceDirection
|
|
* @param prevCollidingSpaces
|
|
* @param isHorizontalMove
|
|
* @returns direction
|
|
*/
|
|
function getCorrectedDirection(
|
|
collidingSpace: OccupiedSpace,
|
|
prevPositions: OccupiedSpace | undefined,
|
|
direction: ReflowDirection,
|
|
forceDirection = false,
|
|
prevCollidingSpaces?: CollisionMap,
|
|
isHorizontalMove?: boolean,
|
|
): ReflowDirection {
|
|
if (forceDirection) return direction;
|
|
|
|
// if previously collided in that direction then it should be that direction
|
|
if (prevCollidingSpaces && prevCollidingSpaces[collidingSpace.id]) {
|
|
return prevCollidingSpaces[collidingSpace.id].direction;
|
|
}
|
|
|
|
let primaryDirection: ReflowDirection = direction,
|
|
secondaryDirection: ReflowDirection | undefined = undefined;
|
|
// this is for composite directions for resizing while dragging the corner handles
|
|
if (direction.indexOf("|") >= 0) {
|
|
const directions = direction.split("|");
|
|
|
|
if (isHorizontalMove) {
|
|
primaryDirection = directions[1] as ReflowDirection;
|
|
secondaryDirection = directions[0] as ReflowDirection;
|
|
} else {
|
|
primaryDirection = directions[0] as ReflowDirection;
|
|
secondaryDirection = directions[1] as ReflowDirection;
|
|
}
|
|
}
|
|
|
|
// if first run the return direction
|
|
if (!prevPositions) return primaryDirection;
|
|
|
|
const primaryAccessors = getAccessor(primaryDirection);
|
|
|
|
// check if they can collide based on previous location of the dragging space
|
|
const isCorrectDirection = compareNumbers(
|
|
collidingSpace[primaryAccessors.oppositeDirection],
|
|
prevPositions[primaryAccessors.direction],
|
|
primaryAccessors.directionIndicator > 0,
|
|
true,
|
|
);
|
|
|
|
if (isCorrectDirection) {
|
|
return primaryDirection;
|
|
} else if (secondaryDirection) {
|
|
return secondaryDirection;
|
|
}
|
|
|
|
//if all the conditions doesn't match then we have to determine manually
|
|
return getVerifiedDirection(
|
|
collidingSpace,
|
|
prevPositions,
|
|
primaryDirection,
|
|
primaryAccessors.isHorizontal,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* if spaces cannot possibly collide in certain direction,
|
|
* this method provides the direction it is most likely to collide in
|
|
* This is determined by checking the previous positions of the dragging/moving spaces
|
|
* with the dimension of collidingSpace
|
|
*
|
|
* @param collidingSpace
|
|
* @param prevPositions
|
|
* @param direction
|
|
* @param isHorizontalMove
|
|
* @returns direction
|
|
*/
|
|
function getVerifiedDirection(
|
|
collidingSpace: OccupiedSpace,
|
|
prevPositions: OccupiedSpace,
|
|
direction: ReflowDirection,
|
|
isHorizontalMove: boolean,
|
|
) {
|
|
// determines direction by comparing if it can collide in all the directions
|
|
if (isHorizontalMove) {
|
|
if (collidingSpace.bottom <= prevPositions.top) {
|
|
return ReflowDirection.TOP;
|
|
} else if (collidingSpace.top >= prevPositions.bottom) {
|
|
return ReflowDirection.BOTTOM;
|
|
} else if (
|
|
direction !== ReflowDirection.RIGHT &&
|
|
collidingSpace.left >= prevPositions.right
|
|
) {
|
|
return ReflowDirection.RIGHT;
|
|
} else if (
|
|
direction !== ReflowDirection.LEFT &&
|
|
collidingSpace.right <= prevPositions.left
|
|
) {
|
|
return ReflowDirection.LEFT;
|
|
}
|
|
} else {
|
|
if (collidingSpace.right <= prevPositions.left) {
|
|
return ReflowDirection.LEFT;
|
|
} else if (collidingSpace.left >= prevPositions.right) {
|
|
return ReflowDirection.RIGHT;
|
|
} else if (
|
|
direction !== ReflowDirection.TOP &&
|
|
collidingSpace.bottom <= prevPositions.top
|
|
) {
|
|
return ReflowDirection.TOP;
|
|
} else if (
|
|
direction !== ReflowDirection.BOTTOM &&
|
|
collidingSpace.top >= prevPositions.bottom
|
|
) {
|
|
return ReflowDirection.BOTTOM;
|
|
}
|
|
}
|
|
|
|
return direction;
|
|
}
|
|
|
|
/**
|
|
* compares numbers and returns boolean
|
|
* @param numberA
|
|
* @param numberB
|
|
* @param isGreaterThan
|
|
* @param isEqual
|
|
* @returns boolean
|
|
*/
|
|
export function compareNumbers(
|
|
numberA: number,
|
|
numberB: number,
|
|
isGreaterThan: boolean,
|
|
isEqual = false,
|
|
): boolean {
|
|
if (isGreaterThan) {
|
|
if (isEqual) {
|
|
return numberA >= numberB;
|
|
}
|
|
return numberA > numberB;
|
|
}
|
|
|
|
if (isEqual) {
|
|
return numberA <= numberB;
|
|
}
|
|
|
|
return numberA < numberB;
|
|
}
|
|
|
|
/**
|
|
* gets opposite direction
|
|
* @param direction
|
|
* @returns ReflowDirection
|
|
*/
|
|
export function getOppositeDirection(
|
|
direction: ReflowDirection,
|
|
): ReflowDirection {
|
|
const directionalAccessors = getAccessor(direction);
|
|
return directionalAccessors.oppositeDirection.toUpperCase() as ReflowDirection;
|
|
}
|
|
|
|
/**
|
|
* Accessors are used to access space's dimension based on direction
|
|
* These are string accessors to get the dimension of the space in a direction
|
|
*
|
|
* @param direction
|
|
* @returns accessors
|
|
*/
|
|
export function getAccessor(direction: ReflowDirection): CollisionAccessors {
|
|
switch (direction) {
|
|
case ReflowDirection.LEFT:
|
|
return {
|
|
direction: SpaceAttributes.left,
|
|
oppositeDirection: SpaceAttributes.right,
|
|
perpendicularMax: SpaceAttributes.bottom,
|
|
perpendicularMin: SpaceAttributes.top,
|
|
parallelMax: SpaceAttributes.right,
|
|
parallelMin: SpaceAttributes.left,
|
|
mathComparator: MathComparators.max,
|
|
directionIndicator: -1,
|
|
isHorizontal: true,
|
|
plane: "horizontal",
|
|
};
|
|
case ReflowDirection.RIGHT:
|
|
return {
|
|
direction: SpaceAttributes.right,
|
|
oppositeDirection: SpaceAttributes.left,
|
|
perpendicularMax: SpaceAttributes.bottom,
|
|
perpendicularMin: SpaceAttributes.top,
|
|
parallelMax: SpaceAttributes.right,
|
|
parallelMin: SpaceAttributes.left,
|
|
mathComparator: MathComparators.min,
|
|
directionIndicator: 1,
|
|
isHorizontal: true,
|
|
plane: "horizontal",
|
|
};
|
|
case ReflowDirection.TOP:
|
|
return {
|
|
direction: SpaceAttributes.top,
|
|
oppositeDirection: SpaceAttributes.bottom,
|
|
perpendicularMax: SpaceAttributes.right,
|
|
perpendicularMin: SpaceAttributes.left,
|
|
parallelMax: SpaceAttributes.bottom,
|
|
parallelMin: SpaceAttributes.top,
|
|
mathComparator: MathComparators.max,
|
|
directionIndicator: -1,
|
|
isHorizontal: false,
|
|
plane: "vertical",
|
|
};
|
|
case ReflowDirection.BOTTOM:
|
|
return {
|
|
direction: SpaceAttributes.bottom,
|
|
oppositeDirection: SpaceAttributes.top,
|
|
perpendicularMax: SpaceAttributes.right,
|
|
perpendicularMin: SpaceAttributes.left,
|
|
parallelMax: SpaceAttributes.bottom,
|
|
parallelMin: SpaceAttributes.top,
|
|
mathComparator: MathComparators.min,
|
|
directionIndicator: 1,
|
|
isHorizontal: false,
|
|
plane: "vertical",
|
|
};
|
|
}
|
|
return {
|
|
direction: SpaceAttributes.bottom,
|
|
oppositeDirection: SpaceAttributes.top,
|
|
perpendicularMax: SpaceAttributes.right,
|
|
perpendicularMin: SpaceAttributes.left,
|
|
parallelMax: SpaceAttributes.bottom,
|
|
parallelMin: SpaceAttributes.top,
|
|
mathComparator: MathComparators.min,
|
|
directionIndicator: 1,
|
|
isHorizontal: false,
|
|
plane: "vertical",
|
|
};
|
|
}
|
|
|
|
/**
|
|
* get Max X coordinate of the the space,
|
|
* MaxX is the maximum a reflowed space can move in the X axis before it should start to resize,
|
|
*
|
|
* @param collisionTree
|
|
* @param gridProps
|
|
* @param direction
|
|
* @param occupiedLength
|
|
* @param maxOccupiedSpace
|
|
* @param shouldResize
|
|
* @returns number
|
|
*/
|
|
export function getMaxX(
|
|
collisionTree: CollisionTree,
|
|
gridProps: GridProps,
|
|
direction: ReflowDirection,
|
|
occupiedLength: number,
|
|
maxOccupiedSpace: number,
|
|
shouldResize: boolean,
|
|
) {
|
|
const accessors = getAccessor(direction);
|
|
const movementLimit = shouldResize ? occupiedLength : maxOccupiedSpace;
|
|
|
|
let maxX = collisionTree[accessors.direction] - movementLimit;
|
|
|
|
if (direction === ReflowDirection.RIGHT) {
|
|
maxX =
|
|
gridProps.maxGridColumns -
|
|
collisionTree[accessors.direction] -
|
|
movementLimit;
|
|
}
|
|
|
|
return accessors.directionIndicator * maxX * gridProps.parentColumnSpace;
|
|
}
|
|
|
|
/**
|
|
* get Max Y coordinate of the the space
|
|
* MaxY is the maximum a reflowed space can move in the Y axis before it should start to resize,
|
|
*
|
|
* @param collisionTree
|
|
* @param gridProps
|
|
* @param direction
|
|
* @param occupiedLength
|
|
* @param maxOccupiedSpace
|
|
* @param shouldResize
|
|
* @returns number
|
|
*/
|
|
export function getMaxY(
|
|
collisionTree: CollisionTree,
|
|
gridProps: GridProps,
|
|
direction: ReflowDirection,
|
|
occupiedLength: number,
|
|
maxOccupiedSpace: number,
|
|
shouldResize: boolean,
|
|
) {
|
|
const accessors = getAccessor(direction);
|
|
const movementLimit = shouldResize ? occupiedLength : maxOccupiedSpace;
|
|
|
|
let maxY =
|
|
(collisionTree[accessors.direction] - movementLimit) *
|
|
gridProps.parentRowSpace;
|
|
|
|
if (direction === ReflowDirection.BOTTOM) {
|
|
maxY = Infinity;
|
|
}
|
|
|
|
return accessors.directionIndicator * maxY;
|
|
}
|
|
|
|
/**
|
|
* calculates the reflowed distance i.e, X or Y of the reflowed space
|
|
* this distance indicates the absolute value by which the reflowed space has to move rom it's original position
|
|
*
|
|
* @param collisionTree
|
|
* @param direction
|
|
* @param maxDistance
|
|
* @param distanceBeforeCollision
|
|
* @param actualDimension
|
|
* @param emptySpaces
|
|
* @param snapGridSpace
|
|
* @param expandableCanvas
|
|
* @returns distance in number
|
|
*/
|
|
export function getReflowDistance(
|
|
collisionTree: CollisionTree,
|
|
direction: ReflowDirection,
|
|
maxDistance: number,
|
|
distanceBeforeCollision: number,
|
|
actualDimension: number,
|
|
emptySpaces: number,
|
|
snapGridSpace: number,
|
|
expandableCanvas = false,
|
|
) {
|
|
const accessors = getAccessor(direction);
|
|
|
|
const originalDimension =
|
|
(collisionTree[accessors.parallelMax] -
|
|
collisionTree[accessors.parallelMin]) *
|
|
snapGridSpace;
|
|
|
|
const value =
|
|
(distanceBeforeCollision + emptySpaces * accessors.directionIndicator) *
|
|
snapGridSpace *
|
|
-1;
|
|
const maxValue = Math[accessors.mathComparator](value, maxDistance);
|
|
|
|
if (expandableCanvas) {
|
|
return maxValue;
|
|
}
|
|
|
|
return accessors.directionIndicator < 0
|
|
? maxValue
|
|
: maxValue + originalDimension - actualDimension;
|
|
}
|
|
|
|
/**
|
|
* returns the reflowed dimension (width or height) of the reflowed space
|
|
* It returns the original dimension if it has not reached the canvas borders
|
|
*
|
|
* @param collisionTree
|
|
* @param direction
|
|
* @param travelDistance
|
|
* @param maxDistance
|
|
* @param distanceBeforeCollision
|
|
* @param snapGridSpace
|
|
* @param emptySpaces
|
|
* @param minDimension
|
|
* @param shouldResize
|
|
* @returns resized width or height of space
|
|
*/
|
|
export function getReflowedDimension(
|
|
collisionTree: CollisionTree,
|
|
direction: ReflowDirection,
|
|
travelDistance: number,
|
|
maxDistance: number,
|
|
distanceBeforeCollision: number,
|
|
snapGridSpace: number,
|
|
emptySpaces: number,
|
|
minDimension: number,
|
|
shouldResize: boolean,
|
|
) {
|
|
const accessors = getAccessor(direction);
|
|
|
|
if (direction === ReflowDirection.TOP && collisionTree.fixedHeight)
|
|
return collisionTree.fixedHeight * snapGridSpace;
|
|
|
|
const currentDistanceBeforeCollision =
|
|
travelDistance +
|
|
(distanceBeforeCollision + emptySpaces * accessors.directionIndicator) *
|
|
snapGridSpace;
|
|
|
|
const originalDimension =
|
|
collisionTree[accessors.parallelMax] - collisionTree[accessors.parallelMin];
|
|
|
|
if (!shouldResize) {
|
|
return originalDimension * snapGridSpace;
|
|
}
|
|
const resizeThreshold = maxDistance + currentDistanceBeforeCollision;
|
|
const resizeLimit =
|
|
resizeThreshold +
|
|
(originalDimension - minDimension) *
|
|
snapGridSpace *
|
|
accessors.directionIndicator;
|
|
|
|
let shrink = 0;
|
|
const canResize = compareNumbers(
|
|
travelDistance,
|
|
resizeThreshold,
|
|
accessors.directionIndicator > 0,
|
|
true,
|
|
);
|
|
|
|
if (canResize) {
|
|
shrink = Math[accessors.mathComparator](travelDistance, resizeLimit);
|
|
shrink = shrink - resizeThreshold;
|
|
}
|
|
|
|
return originalDimension * snapGridSpace - Math.abs(shrink);
|
|
}
|
|
|
|
/**
|
|
* check the limits of each movement map
|
|
* and replace with previous run's movement values if it has already reached the limit
|
|
*
|
|
* @param movementMap
|
|
* @param prevMovementMap
|
|
* @param movementLimit
|
|
* @returns
|
|
*/
|
|
export function getLimitedMovementMap(
|
|
movementMap: ReflowedSpaceMap | undefined,
|
|
prevMovementMap: ReflowedSpaceMap,
|
|
movementLimit: { canVerticalMove: boolean; canHorizontalMove: boolean },
|
|
): ReflowedSpaceMap {
|
|
if (!movementMap) return {};
|
|
const { canHorizontalMove, canVerticalMove } = movementLimit;
|
|
|
|
if (!canVerticalMove && !canHorizontalMove) {
|
|
return prevMovementMap;
|
|
}
|
|
|
|
if (!canVerticalMove) {
|
|
return replaceMovementMapByDirection(movementMap, prevMovementMap, false);
|
|
}
|
|
|
|
if (!canHorizontalMove) {
|
|
return replaceMovementMapByDirection(movementMap, prevMovementMap, true);
|
|
}
|
|
|
|
return movementMap;
|
|
}
|
|
|
|
/**
|
|
* replace movement of the reflowed space with previous run's movement of the reflowed space
|
|
*
|
|
* @param movementMap
|
|
* @param prevMovementMap
|
|
* @param replaceHorizontal
|
|
* @returns
|
|
*/
|
|
function replaceMovementMapByDirection(
|
|
movementMap: ReflowedSpaceMap,
|
|
prevMovementMap: ReflowedSpaceMap,
|
|
replaceHorizontal: boolean,
|
|
): ReflowedSpaceMap {
|
|
const checkKey = replaceHorizontal ? "X" : "Y";
|
|
const currentMovementMap = { ...movementMap };
|
|
const movementMapIds = Object.keys(movementMap);
|
|
|
|
for (const spaceId of movementMapIds) {
|
|
if (currentMovementMap[spaceId][checkKey] !== undefined) {
|
|
delete currentMovementMap[spaceId];
|
|
|
|
if (prevMovementMap[spaceId]) {
|
|
currentMovementMap[spaceId] = { ...prevMovementMap[spaceId] };
|
|
}
|
|
}
|
|
}
|
|
|
|
return currentMovementMap;
|
|
}
|
|
|
|
/**
|
|
* on Container exit, the exited container and the widgets behind it should reflow in opposite direction,
|
|
* So this method checks if that it the case and sets it in the opposite direction
|
|
*
|
|
* @param collidingSpaceMap
|
|
* @param exitContainerId
|
|
* @param direction
|
|
* changes reference of collidingSpaceMap
|
|
*/
|
|
export function changeExitContainerDirection(
|
|
collidingSpaceMap: CollisionMap,
|
|
exitContainerId: string | undefined,
|
|
direction: ReflowDirection,
|
|
) {
|
|
if (!exitContainerId || !collidingSpaceMap[exitContainerId]) {
|
|
return;
|
|
}
|
|
|
|
const oppDirection = getOppositeDirection(direction);
|
|
const { directionIndicator, oppositeDirection } = getAccessor(oppDirection);
|
|
|
|
const collidingSpaces: CollidingSpace[] = Object.values(collidingSpaceMap);
|
|
const oppositeFrom = collidingSpaceMap[exitContainerId][oppositeDirection];
|
|
|
|
const oppositeSpaceIds = collidingSpaces
|
|
.filter((collidingSpace: CollidingSpace) => {
|
|
return compareNumbers(
|
|
collidingSpace[oppositeDirection],
|
|
oppositeFrom,
|
|
directionIndicator > 0,
|
|
true,
|
|
);
|
|
})
|
|
.map((collidingSpace: CollidingSpace) => collidingSpace.id);
|
|
|
|
for (const spaceId of oppositeSpaceIds) {
|
|
collidingSpaceMap[spaceId].direction = oppDirection;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert an array of spaces to map of spaces
|
|
*
|
|
* @param spacesArray
|
|
* @returns space map
|
|
*/
|
|
export function getSpacesMapFromArray(
|
|
spacesArray: OccupiedSpace[] | undefined,
|
|
) {
|
|
if (!spacesArray) return {};
|
|
const spacesMap: SpaceMap = {};
|
|
for (const space of spacesArray) {
|
|
spacesMap[space.id] = space;
|
|
}
|
|
return spacesMap;
|
|
}
|
|
|
|
/**
|
|
* build a collision array to Collision map structure
|
|
*
|
|
* @param spacesArray
|
|
* @returns space map
|
|
*/
|
|
export function buildArrayToCollisionMap(
|
|
collidingSpaces: CollidingSpace[] | undefined,
|
|
) {
|
|
if (!collidingSpaces) return {};
|
|
|
|
const collidingSpaceMap: CollisionMap = {};
|
|
|
|
let order = 1;
|
|
for (const collidingSpace of collidingSpaces) {
|
|
const { directionIndicator } = getAccessor(collidingSpace.direction);
|
|
const prevCollidingSpace = collidingSpaceMap[collidingSpace.id];
|
|
|
|
if (
|
|
!prevCollidingSpace ||
|
|
(prevCollidingSpace &&
|
|
prevCollidingSpace.direction === collidingSpace.direction &&
|
|
compareNumbers(
|
|
collidingSpace.collidingValue,
|
|
prevCollidingSpace.collidingValue,
|
|
directionIndicator > 0,
|
|
))
|
|
) {
|
|
collidingSpaceMap[collidingSpace.id] = { ...collidingSpace, order };
|
|
order++;
|
|
}
|
|
}
|
|
return collidingSpaceMap;
|
|
}
|
|
|
|
/**
|
|
* move the occupied spaces to previous opposite orientation run's position only in a particular orientation
|
|
* eg, if the current orientation is horizontal, then the occupiedSpacesMap's top and bottom positions are modified
|
|
* to match previously reflowed values
|
|
*
|
|
* @param occupiedSpacesMap
|
|
* @param prevMovementMap
|
|
* @param isHorizontal
|
|
* @param gridProps
|
|
* @param directionMax
|
|
* @param directionMin
|
|
* @returns modified occupied space map
|
|
*/
|
|
export function getModifiedOccupiedSpacesMap(
|
|
occupiedSpacesMap: SpaceMap,
|
|
prevMovementMap: ReflowedSpaceMap | undefined,
|
|
isHorizontal: boolean,
|
|
gridProps: GridProps,
|
|
directionMax: SpaceAttributes,
|
|
directionMin: SpaceAttributes,
|
|
) {
|
|
if (!prevMovementMap) return cloneDeep(occupiedSpacesMap);
|
|
|
|
const spaceKeys = Object.keys(occupiedSpacesMap);
|
|
const directionalOccupiedSpacesMap: SpaceMap = {};
|
|
const displaceMentAccessor = isHorizontal ? "Y" : "X";
|
|
const dimensionAccessor = isHorizontal ? "height" : "width";
|
|
const gridGap = isHorizontal
|
|
? gridProps.parentRowSpace
|
|
: gridProps.parentColumnSpace;
|
|
|
|
for (const key of spaceKeys) {
|
|
const movement =
|
|
(prevMovementMap[key] && prevMovementMap[key][displaceMentAccessor]) || 0;
|
|
const dimension =
|
|
prevMovementMap[key] && prevMovementMap[key][dimensionAccessor];
|
|
const currentSpace = occupiedSpacesMap[key];
|
|
directionalOccupiedSpacesMap[key] = {
|
|
...currentSpace,
|
|
[directionMin]:
|
|
currentSpace[directionMin] + Math.round(movement / gridGap),
|
|
[directionMax]: dimension
|
|
? currentSpace[directionMin] +
|
|
Math.round((movement + dimension) / gridGap)
|
|
: currentSpace[directionMax] + Math.round(movement / gridGap),
|
|
};
|
|
}
|
|
return directionalOccupiedSpacesMap;
|
|
}
|
|
|
|
/**
|
|
* Modifies a single CollidingSpace to previous opposite orientation run's position only in a particular orientation
|
|
* eg, if the current orientation is horizontal, then the collidingSpace's top and bottom positions are modified
|
|
* to match previously reflowed values
|
|
*
|
|
* @param collidingSpace
|
|
* @param OGOccupiedSpacesMap
|
|
* @param prevMovementMap
|
|
* @param isHorizontal
|
|
* @param gridProps
|
|
* @param directionMax
|
|
* @param directionMin
|
|
* @returns modified collidingSpace positions
|
|
*/
|
|
export function getModifiedCollidingSpace(
|
|
collidingSpace: CollidingSpace,
|
|
OGOccupiedSpacesMap: SpaceMap,
|
|
prevMovementMap: ReflowedSpaceMap | undefined,
|
|
isHorizontal: boolean,
|
|
gridProps: GridProps,
|
|
directionMax: SpaceAttributes,
|
|
directionMin: SpaceAttributes,
|
|
) {
|
|
if (!prevMovementMap) return { ...collidingSpace };
|
|
|
|
const displaceMentAccessor = isHorizontal ? "Y" : "X";
|
|
const dimensionAccessor = isHorizontal ? "height" : "width";
|
|
const gridGap = isHorizontal
|
|
? gridProps.parentRowSpace
|
|
: gridProps.parentColumnSpace;
|
|
|
|
const spaceId = collidingSpace.id;
|
|
const OGCollidingSpacePosition = OGOccupiedSpacesMap[spaceId];
|
|
|
|
const movement =
|
|
(prevMovementMap[spaceId] &&
|
|
prevMovementMap[spaceId][displaceMentAccessor]) ||
|
|
0;
|
|
const dimension =
|
|
prevMovementMap[spaceId] && prevMovementMap[spaceId][dimensionAccessor];
|
|
const currentCollidingSpace = {
|
|
...collidingSpace,
|
|
[directionMin]:
|
|
OGCollidingSpacePosition[directionMin] + Math.round(movement / gridGap),
|
|
[directionMax]: dimension
|
|
? OGCollidingSpacePosition[directionMin] +
|
|
Math.round((movement + dimension) / gridGap)
|
|
: OGCollidingSpacePosition[directionMax] + Math.round(movement / gridGap),
|
|
};
|
|
|
|
return currentCollidingSpace;
|
|
}
|
|
|
|
/**
|
|
* Check if the new CollidingSpaces are colliding with the new Space positions,
|
|
* if so it is to be added to the Collision map of that new Space position's colliding map
|
|
*
|
|
* @param collidingSpace
|
|
* @param OGCollidingSpacePosition
|
|
* @param globalDirection
|
|
* @param direction
|
|
* @param newSpacePositions
|
|
* @param globalCollidingSpaces
|
|
* @param insertionIndex
|
|
* @param globalProcessedNodes
|
|
* @param collidingSpaceMap
|
|
* @param prevReflowState
|
|
* @param isSecondRun boolean to indicate if it is being run for the second time
|
|
* @returns boolean to stop moving any further
|
|
*/
|
|
export function checkReCollisionWithOtherNewSpacePositions(
|
|
collidingSpace: CollidingSpace,
|
|
OGCollidingSpacePosition: OccupiedSpace,
|
|
globalDirection: ReflowDirection,
|
|
direction: ReflowDirection,
|
|
newSpacePositions: OccupiedSpace[],
|
|
globalCollidingSpaces: CollidingSpace[],
|
|
insertionIndex: number,
|
|
globalProcessedNodes: CollisionTreeCache,
|
|
collidingSpaceMap: CollisionMap,
|
|
prevReflowState: PrevReflowState,
|
|
isSecondRun: boolean,
|
|
): boolean {
|
|
const accessor = getAccessor(direction);
|
|
const { isHorizontal: globalIsHorizontal } = getAccessor(globalDirection);
|
|
const { prevCollidingSpaceMap, prevSpacesMap } = prevReflowState;
|
|
const orientationalAccessor = getOrientationAccessor(globalIsHorizontal);
|
|
const oppositeOrientationalAccessor = getOrientationAccessor(
|
|
!globalIsHorizontal,
|
|
);
|
|
let stopCollisionCheck = false;
|
|
|
|
for (const newSpacePosition of newSpacePositions) {
|
|
if (areOverlapping(newSpacePosition, collidingSpace)) {
|
|
//If it is already colliding directly with a moving/Dragging space then no need to check
|
|
if (
|
|
collidingSpaceMap[collidingSpace.id] &&
|
|
collidingSpaceMap[collidingSpace.id].collidingId === newSpacePosition.id
|
|
)
|
|
continue;
|
|
|
|
let currentDirection = getCorrectedDirection(
|
|
collidingSpace,
|
|
prevSpacesMap && prevSpacesMap[newSpacePosition.id],
|
|
globalDirection,
|
|
false,
|
|
prevCollidingSpaceMap && prevCollidingSpaceMap[orientationalAccessor],
|
|
globalIsHorizontal,
|
|
);
|
|
|
|
//this is to check if the same two widgets collide again it should be in the same direction even if they have opposite orientation
|
|
if (
|
|
prevCollidingSpaceMap &&
|
|
prevCollidingSpaceMap[oppositeOrientationalAccessor] &&
|
|
prevCollidingSpaceMap[oppositeOrientationalAccessor][
|
|
collidingSpace.id
|
|
] &&
|
|
prevCollidingSpaceMap[oppositeOrientationalAccessor][collidingSpace.id]
|
|
.collidingId === newSpacePosition.id
|
|
) {
|
|
currentDirection =
|
|
prevCollidingSpaceMap[oppositeOrientationalAccessor][
|
|
collidingSpace.id
|
|
].direction;
|
|
}
|
|
|
|
//if this is being run for the second orientation, no matter what direction is predicted,
|
|
//it has to collide in the passed direction
|
|
if (isSecondRun) currentDirection = direction;
|
|
|
|
const currentAccessors = getAccessor(currentDirection);
|
|
const currentCollidingSpace: CollidingSpace = {
|
|
...OGCollidingSpacePosition,
|
|
direction: currentDirection,
|
|
collidingId: newSpacePosition.id,
|
|
collidingValue: newSpacePosition[currentAccessors.direction],
|
|
isHorizontal: currentAccessors.isHorizontal,
|
|
order: 0,
|
|
};
|
|
|
|
if (
|
|
currentDirection === direction &&
|
|
compareNumbers(
|
|
currentCollidingSpace.collidingValue,
|
|
collidingSpace.collidingValue,
|
|
accessor.directionIndicator > 0,
|
|
)
|
|
) {
|
|
stopCollisionCheck = true;
|
|
collidingSpaceMap[collidingSpace.id] = currentCollidingSpace;
|
|
globalCollidingSpaces.splice(
|
|
insertionIndex + 1,
|
|
0,
|
|
currentCollidingSpace,
|
|
);
|
|
delete globalProcessedNodes[collidingSpace.id];
|
|
}
|
|
}
|
|
}
|
|
return stopCollisionCheck;
|
|
}
|
|
|
|
/**
|
|
* If exact same spaces collide with each other again in the current run and previous run
|
|
* calculate the direction they collide in
|
|
*
|
|
* @param staticSpace
|
|
* @param collidingSpace
|
|
* @param direction
|
|
* @param accessor
|
|
* @param gridProps
|
|
* @param prevCollisionMap
|
|
* @param prevMovementMap
|
|
* @returns
|
|
*/
|
|
function getCollisionStatusBasedOnPrevValue(
|
|
staticSpace: CollidingSpace,
|
|
collidingSpace: OccupiedSpace,
|
|
direction: ReflowDirection,
|
|
accessor: CollisionAccessors,
|
|
gridProps: GridProps,
|
|
prevCollisionMap: {
|
|
children: {
|
|
[key: string]: any;
|
|
};
|
|
},
|
|
prevMovementMap: ReflowedSpaceMap | undefined,
|
|
): {
|
|
shouldAddToArray: boolean;
|
|
changedDirection?: ReflowDirection;
|
|
collidingValue?: number;
|
|
isHorizontal?: boolean;
|
|
} {
|
|
const prevCollisionSpace = prevCollisionMap.children[collidingSpace.id];
|
|
const reflowedSpacePosition = {
|
|
...staticSpace,
|
|
[accessor.oppositeDirection]: staticSpace.collidingValue,
|
|
};
|
|
|
|
// If it previously as well collided in the current direction, then add to the Colliding spaces List
|
|
if (prevCollisionSpace.direction === direction) {
|
|
return { shouldAddToArray: true };
|
|
} //if it is previously collided in the same orientation but current colliding value is lesser the dont add
|
|
else if (
|
|
prevCollisionSpace.isHorizontal === accessor.isHorizontal &&
|
|
compareNumbers(
|
|
staticSpace.collidingValue,
|
|
collidingSpace[accessor.direction],
|
|
accessor.directionIndicator > 0,
|
|
)
|
|
) {
|
|
return {
|
|
shouldAddToArray: false,
|
|
};
|
|
} //if it is previously collided in the same orientation but current colliding value is greater then add in the previous direction
|
|
else if (prevCollisionSpace.isHorizontal === accessor.isHorizontal) {
|
|
return {
|
|
shouldAddToArray: true,
|
|
changedDirection: prevCollisionSpace.direction,
|
|
collidingValue: staticSpace.collidingValue,
|
|
isHorizontal: prevCollisionSpace.isHorizontal,
|
|
};
|
|
} else if (!areOverlapping(reflowedSpacePosition, collidingSpace)) {
|
|
return { shouldAddToArray: false };
|
|
} else {
|
|
const localAccessor = getAccessor(prevCollisionSpace.direction);
|
|
const dimensionAccessor = localAccessor.isHorizontal ? "width" : "height";
|
|
const gridGap = localAccessor.isHorizontal
|
|
? gridProps.parentColumnSpace
|
|
: gridProps.parentRowSpace;
|
|
|
|
const dimension =
|
|
prevMovementMap &&
|
|
prevMovementMap[staticSpace.id] &&
|
|
prevMovementMap[staticSpace.id][dimensionAccessor] !== undefined
|
|
? Math.round(
|
|
(prevMovementMap[staticSpace.id][dimensionAccessor] || 0) / gridGap,
|
|
)
|
|
: staticSpace[localAccessor.parallelMax] -
|
|
staticSpace[localAccessor.parallelMin];
|
|
|
|
const orientationalDimension = localAccessor.directionIndicator * dimension;
|
|
return {
|
|
shouldAddToArray: true,
|
|
changedDirection: prevCollisionSpace.direction,
|
|
collidingValue:
|
|
staticSpace[localAccessor.oppositeDirection] + orientationalDimension,
|
|
isHorizontal: prevCollisionSpace.isHorizontal,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get orientation accessor for first and second run
|
|
* @param isHorizontal
|
|
* @returns orientation object
|
|
*/
|
|
export function getOrientationAccessors(
|
|
isHorizontal: boolean,
|
|
): OrientationAccessors {
|
|
return isHorizontal
|
|
? { primary: "horizontal", secondary: "vertical" }
|
|
: { primary: "vertical", secondary: "horizontal" };
|
|
}
|
|
|
|
/**
|
|
* get maximum and minimum space attributes in both orientation
|
|
* eg, if primary orientation is horizontal the,
|
|
* primary max is bottom, min is top and
|
|
* secondary max is right, min is left
|
|
*
|
|
* @param accessor
|
|
* @returns
|
|
*/
|
|
export function getMaxSpaceAttributes(accessor: CollisionAccessors) {
|
|
const {
|
|
parallelMax,
|
|
parallelMin,
|
|
perpendicularMax,
|
|
perpendicularMin,
|
|
} = accessor;
|
|
|
|
return {
|
|
primary: { max: perpendicularMax, min: perpendicularMin },
|
|
secondary: { max: parallelMax, min: parallelMin },
|
|
};
|
|
}
|
|
|
|
/**
|
|
* get a particular orientation accessor based on orientation
|
|
* @param isHorizontal
|
|
* @returns horizontal or vertical
|
|
*/
|
|
export function getOrientationAccessor(isHorizontal?: boolean) {
|
|
return isHorizontal ? "horizontal" : "vertical";
|
|
}
|
|
|
|
/**
|
|
* method to get sorted occupied spaces based on direction
|
|
*
|
|
* @param occupiedSpacesMap all the occupied spaces map on the canvas
|
|
* @param accessors collision accessors to access the space's/block's data based on direction
|
|
* @returns array, sorted occupied spaces based on direction
|
|
*/
|
|
export function getSortedOccupiedSpaces(
|
|
occupiedSpacesMap: SpaceMap,
|
|
accessors: CollisionAccessors,
|
|
) {
|
|
const sortedOccupiedSpaces = Object.values(occupiedSpacesMap);
|
|
|
|
sortedOccupiedSpaces.sort((a, b) => {
|
|
return a[accessors.direction] - b[accessors.direction];
|
|
});
|
|
return sortedOccupiedSpaces;
|
|
}
|
|
|
|
/**
|
|
* method to get sorted new spaces based on direction
|
|
*
|
|
* @param newSpacePositions new/current positions array of the space/block
|
|
* @param accessors collision accessors to access the space's/block's data based on direction
|
|
* @returns array, sorted new spaces based on direction
|
|
*/
|
|
export function getSortedNewPositions(
|
|
newSpacePositionsMap: SpaceMap,
|
|
accessors: CollisionAccessors,
|
|
) {
|
|
const newSpacePositions = Object.values(newSpacePositionsMap);
|
|
newSpacePositions
|
|
.sort((a, b) => {
|
|
return a[accessors.direction] - b[accessors.direction];
|
|
})
|
|
.map((a) => {
|
|
return { ...a, order: true };
|
|
});
|
|
return newSpacePositions;
|
|
}
|
|
|
|
/**
|
|
* method to get sorted colliding spaces based on previous collision order
|
|
*
|
|
* @param collidingSpaceMap direct collision spaces map of the current new space positions
|
|
* @param isHorizontal boolean to indicate if the orientation is horizontal
|
|
* @param primaryCollisionMap direct collision spaces map on the previous run of the algorithm
|
|
* @returns array, sorted occupied spaces based on direction
|
|
*/
|
|
export function getSortedCollidingSpaces(
|
|
collidingSpaceMap: CollisionMap,
|
|
isHorizontal: boolean,
|
|
prevCollisionMap: CollisionMap,
|
|
) {
|
|
const collidingSpaces = Object.values(collidingSpaceMap).filter(
|
|
(a) => a.isHorizontal === isHorizontal,
|
|
);
|
|
|
|
if (!collidingSpaces.length) return [];
|
|
|
|
collidingSpaces.sort((a, b) => {
|
|
const collisionKeyA = a.id,
|
|
collisionKeyB = b.id;
|
|
if (prevCollisionMap) {
|
|
if (prevCollisionMap[collisionKeyA] && prevCollisionMap[collisionKeyB]) {
|
|
return (
|
|
prevCollisionMap[collisionKeyA].order -
|
|
prevCollisionMap[collisionKeyB].order
|
|
);
|
|
} else if (prevCollisionMap[collisionKeyA]) return -1;
|
|
else if (prevCollisionMap[collisionKeyB]) return 1;
|
|
}
|
|
|
|
return a.order - b.order;
|
|
});
|
|
|
|
return collidingSpaces;
|
|
}
|
|
|
|
/**
|
|
* method to get a calculated direction based on previous space positions
|
|
*
|
|
* @param newSpacePositionsMap new/current positions map of the space/block
|
|
* @param prevSpacesMap previous space positions map of the space/block
|
|
* @param passedDirection ReflowedDirection Passed from the main method
|
|
* @returns calculated direction
|
|
*/
|
|
export function getCalculatedDirection(
|
|
newSpacePositionsMap: SpaceMap,
|
|
prevSpacesMap: SpaceMap,
|
|
passedDirection: ReflowDirection,
|
|
) {
|
|
if (passedDirection.indexOf("|") >= 0) return [passedDirection];
|
|
for (const key in newSpacePositionsMap) {
|
|
if (newSpacePositionsMap[key] && prevSpacesMap[key]) {
|
|
const { left: newLeft, top: newTop } = newSpacePositionsMap[key];
|
|
const { left: prevLeft, top: prevTop } = prevSpacesMap[key];
|
|
|
|
if (newTop !== prevTop && newLeft !== prevLeft) {
|
|
return [
|
|
compareNumbers(newTop, prevTop, true)
|
|
? ReflowDirection.BOTTOM
|
|
: ReflowDirection.TOP,
|
|
compareNumbers(newLeft, prevLeft, true)
|
|
? ReflowDirection.RIGHT
|
|
: ReflowDirection.LEFT,
|
|
];
|
|
}
|
|
if (newTop !== prevTop)
|
|
return compareNumbers(newTop, prevTop, true)
|
|
? [ReflowDirection.BOTTOM]
|
|
: [ReflowDirection.TOP];
|
|
if (newLeft !== prevLeft)
|
|
return compareNumbers(newLeft, prevLeft, true)
|
|
? [ReflowDirection.RIGHT]
|
|
: [ReflowDirection.LEFT];
|
|
|
|
return [passedDirection];
|
|
}
|
|
}
|
|
return [passedDirection];
|
|
}
|
|
|
|
/**
|
|
* Returns the bottom most row among all the widget
|
|
* @param newPositions
|
|
* @returns number, the bottom most row
|
|
*/
|
|
export function getBottomMostRow(newPositions: OccupiedSpace[]): number {
|
|
let maxBottomRow = 0;
|
|
for (const newPosition of newPositions) {
|
|
maxBottomRow = Math.max(maxBottomRow, newPosition.bottom);
|
|
}
|
|
return maxBottomRow;
|
|
}
|
|
|
|
/**
|
|
* Returns a boolean to indicate if the colliding Space is to be processed if it is already processed once
|
|
*
|
|
* @param collidingSpace
|
|
* @param globalProcessedNodes
|
|
* @returns boolean, if true will process the colliding Space while generating a Tree.
|
|
*/
|
|
export function checkProcessNodeForTree(
|
|
collidingSpace: CollidingSpace,
|
|
globalProcessedNodes: CollisionTreeCache,
|
|
) {
|
|
if (!globalProcessedNodes[collidingSpace.id])
|
|
return { shouldProcessNode: true };
|
|
|
|
const direction = collidingSpace.direction;
|
|
const oppositeDirection = getOppositeDirection(direction);
|
|
|
|
// If the current node is already processed in the opposite direction the return false
|
|
if (!isUndefined(globalProcessedNodes[collidingSpace.id][oppositeDirection]))
|
|
return { shouldProcessNode: false };
|
|
|
|
const { directionIndicator } = getAccessor(direction);
|
|
|
|
// if the current node is not processed return true or if it is processed but
|
|
// the current colliding value is greater than previous' return true
|
|
if (
|
|
isUndefined(
|
|
globalProcessedNodes[collidingSpace.id][collidingSpace.direction],
|
|
) ||
|
|
compareNumbers(
|
|
collidingSpace.collidingValue,
|
|
globalProcessedNodes[collidingSpace.id][direction].value,
|
|
directionIndicator > 0,
|
|
)
|
|
)
|
|
return { shouldProcessNode: true };
|
|
|
|
//if collision values are equal, return the cached values to be used in calculation
|
|
const {
|
|
childNode,
|
|
currentEmptySpaces,
|
|
occupiedLength,
|
|
occupiedSpace,
|
|
value,
|
|
} = globalProcessedNodes[collidingSpace.id][direction];
|
|
if (collidingSpace.collidingValue === value)
|
|
return {
|
|
shouldProcessNode: false,
|
|
currentChildNode: childNode,
|
|
occupiedLength,
|
|
occupiedSpace,
|
|
currentEmptySpaces,
|
|
};
|
|
|
|
return {
|
|
shouldProcessNode: false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* This is to get the colliding value relative to the edge of the canvas.
|
|
* eg, If a widget is colliding with another, near the ege of the canvas.
|
|
* After the point where the widget resizes full and can'yt move or resize, then the colliding value also should not increase
|
|
*
|
|
* @param accessors
|
|
* @param collidingValue
|
|
* @param direction
|
|
* @param gridProps
|
|
* @param occupiedLength
|
|
* @returns number, colliding value to the edge of canvas
|
|
*/
|
|
export function getRelativeCollidingValue(
|
|
accessors: CollisionAccessors,
|
|
collidingValue: number,
|
|
direction: ReflowDirection,
|
|
{ maxGridColumns }: GridProps,
|
|
occupiedLength?: number,
|
|
): number {
|
|
if (direction === ReflowDirection.BOTTOM || !occupiedLength)
|
|
return collidingValue;
|
|
|
|
let calculatedCollidingValue = occupiedLength;
|
|
|
|
if (direction === ReflowDirection.RIGHT)
|
|
calculatedCollidingValue = maxGridColumns - calculatedCollidingValue;
|
|
|
|
// return the maximum or minimum based on the direction
|
|
return Math[accessors.mathComparator](
|
|
calculatedCollidingValue,
|
|
collidingValue,
|
|
);
|
|
}
|