837 lines
21 KiB
TypeScript
837 lines
21 KiB
TypeScript
|
|
import { OccupiedSpace } from "constants/CanvasEditorConstants";
|
||
|
|
import { Rect } from "utils/WidgetPropsUtils";
|
||
|
|
import {
|
||
|
|
CollidingSpace,
|
||
|
|
CollidingSpaceMap,
|
||
|
|
CollisionAccessors,
|
||
|
|
CollisionTree,
|
||
|
|
GridProps,
|
||
|
|
HORIZONTAL_RESIZE_LIMIT,
|
||
|
|
MathComparators,
|
||
|
|
ReflowDirection,
|
||
|
|
ReflowedSpace,
|
||
|
|
ReflowedSpaceMap,
|
||
|
|
SpaceAttributes,
|
||
|
|
SpaceMovement,
|
||
|
|
VERTICAL_RESIZE_LIMIT,
|
||
|
|
} from "./reflowTypes";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get if the space moved horizontally
|
||
|
|
*
|
||
|
|
* @param newPositions
|
||
|
|
* @param prevPositions
|
||
|
|
* @returns boolean
|
||
|
|
*/
|
||
|
|
export function getIsHorizontalMove(
|
||
|
|
newPositions: OccupiedSpace,
|
||
|
|
prevPositions?: OccupiedSpace,
|
||
|
|
) {
|
||
|
|
if (
|
||
|
|
prevPositions?.left !== newPositions.left ||
|
||
|
|
prevPositions?.right !== newPositions.right
|
||
|
|
) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* method to determine if the newly calculated MovementValue should replace an old value of the same Space Id
|
||
|
|
*
|
||
|
|
* @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 oldDistance = oldMovement[distanceKey],
|
||
|
|
newDistance = newMovement[distanceKey];
|
||
|
|
|
||
|
|
if (oldDistance === undefined || newDistance === undefined) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return compareNumbers(oldDistance, newDistance, directionIndicator < 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* method to get resized dimensions of the Space to determine the Spaces colliding with this Space
|
||
|
|
*
|
||
|
|
* @param collisionTree
|
||
|
|
* @param distanceBeforeCollision
|
||
|
|
* @param emptySpaces
|
||
|
|
* @param accessors
|
||
|
|
* @returns resized Direction
|
||
|
|
*/
|
||
|
|
export function getResizedDimensions(
|
||
|
|
collisionTree: CollisionTree,
|
||
|
|
distanceBeforeCollision: number,
|
||
|
|
emptySpaces: number,
|
||
|
|
{ direction }: CollisionAccessors,
|
||
|
|
) {
|
||
|
|
const reflowedPosition = { ...collisionTree, children: [] };
|
||
|
|
|
||
|
|
const newDimension = distanceBeforeCollision + emptySpaces;
|
||
|
|
reflowedPosition[direction] -= newDimension;
|
||
|
|
|
||
|
|
return reflowedPosition;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* sort the collidingSpaces with respect to the distance from the staticPosition
|
||
|
|
*
|
||
|
|
* @param collidingSpaces
|
||
|
|
* @param staticPosition
|
||
|
|
* @param isAscending
|
||
|
|
*/
|
||
|
|
export function sortCollidingSpacesByDistance(
|
||
|
|
collidingSpaces: CollidingSpace[],
|
||
|
|
staticPosition: OccupiedSpace,
|
||
|
|
isAscending = true,
|
||
|
|
) {
|
||
|
|
const distanceComparator = getDistanceComparator(staticPosition, isAscending);
|
||
|
|
collidingSpaces.sort(distanceComparator);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a comparator bound to the
|
||
|
|
*
|
||
|
|
* @param staticPosition
|
||
|
|
* @param isAscending
|
||
|
|
* @returns negative or positive indicator
|
||
|
|
*/
|
||
|
|
function getDistanceComparator(
|
||
|
|
staticPosition: OccupiedSpace,
|
||
|
|
isAscending = true,
|
||
|
|
) {
|
||
|
|
return function(spaceA: CollidingSpace, spaceB: CollidingSpace) {
|
||
|
|
const accessorA = getAccessor(spaceA.direction);
|
||
|
|
const accessorB = getAccessor(spaceB.direction);
|
||
|
|
|
||
|
|
const distanceA = Math.abs(
|
||
|
|
staticPosition[accessorA.direction] - spaceA[accessorA.oppositeDirection],
|
||
|
|
);
|
||
|
|
const distanceB = Math.abs(
|
||
|
|
staticPosition[accessorB.direction] - spaceB[accessorB.oppositeDirection],
|
||
|
|
);
|
||
|
|
return isAscending ? distanceA - distanceB : distanceB - distanceA;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* To Get Indicators if the static widget can continue to reflow without Overlapping
|
||
|
|
*
|
||
|
|
* @param staticPosition
|
||
|
|
* @param delta
|
||
|
|
* @param beforeLimit
|
||
|
|
* @returns object with a boolean each for vertical and horizontal direction
|
||
|
|
*/
|
||
|
|
export function getShouldReflow(
|
||
|
|
staticPosition: SpaceMovement | undefined,
|
||
|
|
delta = { X: 0, Y: 0 },
|
||
|
|
beforeLimit = false,
|
||
|
|
): { canVerticalMove: boolean; canHorizontalMove: boolean } {
|
||
|
|
if (!staticPosition) {
|
||
|
|
return {
|
||
|
|
canHorizontalMove: false,
|
||
|
|
canVerticalMove: false,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
let canHorizontalMove = true,
|
||
|
|
canVerticalMove = true;
|
||
|
|
const { directionalMovements } = staticPosition;
|
||
|
|
for (const movementLimit of directionalMovements) {
|
||
|
|
const {
|
||
|
|
coordinateKey,
|
||
|
|
directionalIndicator,
|
||
|
|
isHorizontal,
|
||
|
|
maxMovement,
|
||
|
|
} = movementLimit;
|
||
|
|
|
||
|
|
const canMove = compareNumbers(
|
||
|
|
delta[coordinateKey],
|
||
|
|
maxMovement,
|
||
|
|
directionalIndicator < 0,
|
||
|
|
beforeLimit,
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!canMove) {
|
||
|
|
if (isHorizontal) canHorizontalMove = false;
|
||
|
|
else canVerticalMove = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
canHorizontalMove,
|
||
|
|
canVerticalMove,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Should return X and Y coordinates of movement from OGPositions to newPositions
|
||
|
|
* in a particular direction
|
||
|
|
*
|
||
|
|
* @param OGPositions
|
||
|
|
* @param newPositions
|
||
|
|
* @param direction
|
||
|
|
* @returns
|
||
|
|
*/
|
||
|
|
export function getDelta(
|
||
|
|
OGPositions: OccupiedSpace,
|
||
|
|
newPositions: OccupiedSpace,
|
||
|
|
direction: ReflowDirection,
|
||
|
|
) {
|
||
|
|
let X = OGPositions.left - newPositions.left,
|
||
|
|
Y = OGPositions.top - newPositions.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 = OGPositions[xDirection] - newPositions[xDirection];
|
||
|
|
Y = OGPositions[yDirection] - newPositions[yDirection];
|
||
|
|
return { X, Y };
|
||
|
|
}
|
||
|
|
|
||
|
|
const { direction: directionalAccessor, isHorizontal } = getAccessor(
|
||
|
|
direction,
|
||
|
|
);
|
||
|
|
const diff =
|
||
|
|
OGPositions[directionalAccessor] - newPositions[directionalAccessor];
|
||
|
|
|
||
|
|
if (isHorizontal) X = diff;
|
||
|
|
else Y = diff;
|
||
|
|
|
||
|
|
return { X, Y };
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* returns Collising Spaces map with the direction of collision
|
||
|
|
*
|
||
|
|
* @param staticPosition
|
||
|
|
* @param direction
|
||
|
|
* @param occupiedSpaces
|
||
|
|
* @param isHorizontalMove
|
||
|
|
* @param prevPositions
|
||
|
|
* @param prevCollidingSpaces
|
||
|
|
* @param forceDirection
|
||
|
|
* @returns collision spaces Map
|
||
|
|
*/
|
||
|
|
export function getCollidingSpaces(
|
||
|
|
staticPosition: OccupiedSpace,
|
||
|
|
direction: ReflowDirection,
|
||
|
|
occupiedSpaces?: OccupiedSpace[],
|
||
|
|
isHorizontalMove?: boolean,
|
||
|
|
prevPositions?: OccupiedSpace,
|
||
|
|
prevCollidingSpaces?: CollidingSpaceMap,
|
||
|
|
forceDirection = false,
|
||
|
|
) {
|
||
|
|
let isColliding = false;
|
||
|
|
const collidingSpaceMap: CollidingSpaceMap = {};
|
||
|
|
const filteredOccupiedSpaces = filterSpaceById(
|
||
|
|
staticPosition.id,
|
||
|
|
occupiedSpaces,
|
||
|
|
);
|
||
|
|
|
||
|
|
for (const occupiedSpace of filteredOccupiedSpaces) {
|
||
|
|
if (areIntersecting(occupiedSpace, staticPosition)) {
|
||
|
|
isColliding = true;
|
||
|
|
const currentSpaceId = occupiedSpace.id;
|
||
|
|
|
||
|
|
const movementDirection = getCorrectedDirection(
|
||
|
|
occupiedSpace,
|
||
|
|
prevPositions,
|
||
|
|
direction,
|
||
|
|
forceDirection,
|
||
|
|
isHorizontalMove,
|
||
|
|
prevCollidingSpaces,
|
||
|
|
);
|
||
|
|
|
||
|
|
collidingSpaceMap[currentSpaceId] = {
|
||
|
|
...occupiedSpace,
|
||
|
|
direction: movementDirection,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
isColliding,
|
||
|
|
collidingSpaceMap,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* returns Collising Spaces map in a particular direction
|
||
|
|
* @param staticPosition
|
||
|
|
* @param direction
|
||
|
|
* @param occupiedSpaces
|
||
|
|
* @returns Colliding Spaces Array
|
||
|
|
*/
|
||
|
|
export function getCollidingSpacesInDirection(
|
||
|
|
staticPosition: OccupiedSpace,
|
||
|
|
direction: ReflowDirection,
|
||
|
|
occupiedSpaces?: OccupiedSpace[],
|
||
|
|
) {
|
||
|
|
const collidingSpaces: CollidingSpace[] = [];
|
||
|
|
const occupiedSpacesInDirection = filterSpaceByDirection(
|
||
|
|
staticPosition,
|
||
|
|
occupiedSpaces,
|
||
|
|
direction,
|
||
|
|
);
|
||
|
|
|
||
|
|
for (const occupiedSpace of occupiedSpacesInDirection) {
|
||
|
|
if (areIntersecting(occupiedSpace, staticPosition)) {
|
||
|
|
collidingSpaces.push({
|
||
|
|
...occupiedSpace,
|
||
|
|
direction,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return { collidingSpaces, occupiedSpacesInDirection };
|
||
|
|
}
|
||
|
|
|
||
|
|
function filterSpaceByDirection(
|
||
|
|
staticPosition: OccupiedSpace,
|
||
|
|
occupiedSpaces: OccupiedSpace[] | undefined,
|
||
|
|
direction: ReflowDirection,
|
||
|
|
): OccupiedSpace[] {
|
||
|
|
let filteredSpaces: OccupiedSpace[] = [];
|
||
|
|
|
||
|
|
const { directionIndicator, oppositeDirection } = getAccessor(direction);
|
||
|
|
if (occupiedSpaces) {
|
||
|
|
filteredSpaces = occupiedSpaces.filter((occupiedSpace) => {
|
||
|
|
if (
|
||
|
|
occupiedSpace.id === staticPosition.id ||
|
||
|
|
occupiedSpace.parentId === staticPosition.id
|
||
|
|
) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return compareNumbers(
|
||
|
|
occupiedSpace[oppositeDirection],
|
||
|
|
staticPosition[oppositeDirection],
|
||
|
|
directionIndicator > 0,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return filteredSpaces;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* filters out a space with an id and returns the filtered Spaces
|
||
|
|
* @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;
|
||
|
|
}
|
||
|
|
|
||
|
|
function areIntersecting(r1: Rect, r2: Rect) {
|
||
|
|
return !(
|
||
|
|
r2.left >= r1.right ||
|
||
|
|
r2.right <= r1.left ||
|
||
|
|
r2.top >= r1.bottom ||
|
||
|
|
r2.bottom <= r1.top
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function getCorrectedDirection(
|
||
|
|
collidingSpace: OccupiedSpace,
|
||
|
|
prevPositions: OccupiedSpace | undefined,
|
||
|
|
direction: ReflowDirection,
|
||
|
|
forceDirection = false,
|
||
|
|
isHorizontalMove?: boolean,
|
||
|
|
prevCollidingSpaces?: CollidingSpaceMap,
|
||
|
|
): ReflowDirection {
|
||
|
|
if (forceDirection) return direction;
|
||
|
|
|
||
|
|
if (prevCollidingSpaces && prevCollidingSpaces[collidingSpace.id]) {
|
||
|
|
return prevCollidingSpaces[collidingSpace.id].direction;
|
||
|
|
}
|
||
|
|
|
||
|
|
let primaryDirection: ReflowDirection = direction,
|
||
|
|
secondaryDirection: ReflowDirection | undefined = undefined;
|
||
|
|
|
||
|
|
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 (!prevPositions) return primaryDirection;
|
||
|
|
|
||
|
|
const primaryAccessors = getAccessor(primaryDirection);
|
||
|
|
|
||
|
|
const isCorrectDirection = compareNumbers(
|
||
|
|
collidingSpace[primaryAccessors.oppositeDirection],
|
||
|
|
prevPositions[primaryAccessors.direction],
|
||
|
|
primaryAccessors.directionIndicator > 0,
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
|
||
|
|
if (isCorrectDirection) {
|
||
|
|
return primaryDirection;
|
||
|
|
} else if (secondaryDirection) {
|
||
|
|
return secondaryDirection;
|
||
|
|
}
|
||
|
|
|
||
|
|
return getVerifiedDirection(
|
||
|
|
collidingSpace,
|
||
|
|
prevPositions,
|
||
|
|
primaryDirection,
|
||
|
|
primaryAccessors.isHorizontal,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function getVerifiedDirection(
|
||
|
|
collidingSpace: OccupiedSpace,
|
||
|
|
prevPositions: OccupiedSpace,
|
||
|
|
direction: ReflowDirection,
|
||
|
|
isHorizontalMove: boolean,
|
||
|
|
) {
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* @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,
|
||
|
|
};
|
||
|
|
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,
|
||
|
|
};
|
||
|
|
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,
|
||
|
|
};
|
||
|
|
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,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
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,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* get Max X coordinate of the the space
|
||
|
|
*
|
||
|
|
* @param collisionTree
|
||
|
|
* @param gridProps
|
||
|
|
* @param direction
|
||
|
|
* @param depth
|
||
|
|
* @param maxOccupiedSpace
|
||
|
|
* @param shouldResize
|
||
|
|
* @returns number
|
||
|
|
*/
|
||
|
|
export function getMaxX(
|
||
|
|
collisionTree: CollisionTree,
|
||
|
|
gridProps: GridProps,
|
||
|
|
direction: ReflowDirection,
|
||
|
|
depth: number,
|
||
|
|
maxOccupiedSpace: number,
|
||
|
|
shouldResize: boolean,
|
||
|
|
) {
|
||
|
|
const accessors = getAccessor(direction);
|
||
|
|
const movementLimit = shouldResize
|
||
|
|
? depth * HORIZONTAL_RESIZE_LIMIT
|
||
|
|
: 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
|
||
|
|
*
|
||
|
|
* @param collisionTree
|
||
|
|
* @param gridProps
|
||
|
|
* @param direction
|
||
|
|
* @param depth
|
||
|
|
* @param maxOccupiedSpace
|
||
|
|
* @param shouldResize
|
||
|
|
* @returns number
|
||
|
|
*/
|
||
|
|
export function getMaxY(
|
||
|
|
collisionTree: CollisionTree,
|
||
|
|
gridProps: GridProps,
|
||
|
|
direction: ReflowDirection,
|
||
|
|
depth: number,
|
||
|
|
maxOccupiedSpace: number,
|
||
|
|
shouldResize: boolean,
|
||
|
|
) {
|
||
|
|
const accessors = getAccessor(direction);
|
||
|
|
const movementLimit = shouldResize
|
||
|
|
? depth * VERTICAL_RESIZE_LIMIT
|
||
|
|
: maxOccupiedSpace;
|
||
|
|
|
||
|
|
let maxY =
|
||
|
|
(collisionTree[accessors.direction] - movementLimit) *
|
||
|
|
gridProps.parentRowSpace;
|
||
|
|
|
||
|
|
if (direction === ReflowDirection.BOTTOM) {
|
||
|
|
maxY = Infinity;
|
||
|
|
}
|
||
|
|
|
||
|
|
return accessors.directionIndicator * maxY;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* get X or Y coordinate distance for space
|
||
|
|
*
|
||
|
|
* @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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* gets the resized dimension of the space along a direction
|
||
|
|
*
|
||
|
|
* @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 getResizedDimension(
|
||
|
|
collisionTree: CollisionTree,
|
||
|
|
direction: ReflowDirection,
|
||
|
|
travelDistance: number,
|
||
|
|
maxDistance: number,
|
||
|
|
distanceBeforeCollision: number,
|
||
|
|
snapGridSpace: number,
|
||
|
|
emptySpaces: number,
|
||
|
|
minDimension: number,
|
||
|
|
shouldResize: boolean,
|
||
|
|
) {
|
||
|
|
const accessors = getAccessor(direction);
|
||
|
|
|
||
|
|
const currentDistanceBeforeCollision =
|
||
|
|
travelDistance +
|
||
|
|
(distanceBeforeCollision + emptySpaces * accessors.directionIndicator) *
|
||
|
|
snapGridSpace;
|
||
|
|
|
||
|
|
const originalDimension =
|
||
|
|
collisionTree[accessors.parallelMax] - collisionTree[accessors.parallelMin];
|
||
|
|
|
||
|
|
if (!shouldResize) {
|
||
|
|
return originalDimension * snapGridSpace;
|
||
|
|
}
|
||
|
|
const resizeTreshold = maxDistance + currentDistanceBeforeCollision;
|
||
|
|
const resizeLimit =
|
||
|
|
resizeTreshold +
|
||
|
|
(originalDimension - minDimension) *
|
||
|
|
snapGridSpace *
|
||
|
|
accessors.directionIndicator;
|
||
|
|
|
||
|
|
let shrink = 0;
|
||
|
|
const canResize = compareNumbers(
|
||
|
|
travelDistance,
|
||
|
|
resizeTreshold,
|
||
|
|
accessors.directionIndicator > 0,
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
|
||
|
|
if (canResize) {
|
||
|
|
shrink = Math[accessors.mathComparator](travelDistance, resizeLimit);
|
||
|
|
shrink = shrink - resizeTreshold;
|
||
|
|
}
|
||
|
|
|
||
|
|
return originalDimension * snapGridSpace - Math.abs(shrink);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* @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;
|
||
|
|
}
|
||
|
|
|
||
|
|
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 exitted container and the widgets behind it should reflow in opposite direction
|
||
|
|
* @param collidingSpaceMap
|
||
|
|
* @param immediateExitContainer
|
||
|
|
* @param direction
|
||
|
|
* changes reference of collidingSpaceMap
|
||
|
|
*/
|
||
|
|
export function changeExitContainerDirection(
|
||
|
|
collidingSpaceMap: CollidingSpaceMap,
|
||
|
|
immediateExitContainer: string | undefined,
|
||
|
|
direction: ReflowDirection,
|
||
|
|
) {
|
||
|
|
if (!immediateExitContainer || !collidingSpaceMap[immediateExitContainer]) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const oppDirection = getOppositeDirection(direction);
|
||
|
|
const { directionIndicator, oppositeDirection } = getAccessor(oppDirection);
|
||
|
|
|
||
|
|
const collidingSpaces: CollidingSpace[] = Object.values(collidingSpaceMap);
|
||
|
|
const oppositeFrom =
|
||
|
|
collidingSpaceMap[immediateExitContainer][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;
|
||
|
|
}
|
||
|
|
}
|