Merged PR 167: Add Flex and fix bugs (read desc)

Note: The branch name does not fit the new features.

- Implement Flex with simplex
- Enable rigid body by default (removed IsRigidBody property) <=== possibly a bad idea
- Sort children in add and update properties
- Implement MaxWidth
- Add more docs

Fixes :
- .env.production url
- Symbols: not blocking the linked container when the parent is moving
This commit is contained in:
Eric Nguyen 2022-08-25 13:28:32 +00:00
parent ec3fddec9d
commit 7f3f6a489a
43 changed files with 1127 additions and 453 deletions

View file

@ -28,7 +28,7 @@ export function ApplyAnchor(container: IContainerModel): IContainerModel {
}
const rigidBodies = container.parent.children.filter(
child => child.properties.isRigidBody && !child.properties.isAnchor
child => !child.properties.isAnchor
);
const overlappingContainers = getOverlappingContainers(container, rigidBodies);

View file

@ -2,6 +2,7 @@ import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { APPLY_BEHAVIORS_ON_CHILDREN } from '../../../utils/default';
import { ApplyAnchor } from './AnchorBehaviors';
import { Flex } from './FlexBehaviors';
import { ApplyRigidBody } from './RigidBodyBehaviors';
import { ApplySymbol } from './SymbolBehaviors';
@ -16,10 +17,12 @@ export function ApplyBehaviors(container: IContainerModel, symbols: Map<string,
ApplyAnchor(container);
}
if (container.properties.isRigidBody) {
ApplyRigidBody(container);
if (container.properties.isFlex) {
Flex(container);
}
ApplyRigidBody(container);
const symbol = symbols.get(container.properties.linkedSymbolId);
if (container.properties.linkedSymbolId !== '' && symbol !== undefined) {
ApplySymbol(container, symbol);

View file

@ -0,0 +1,144 @@
import Swal from 'sweetalert2';
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { reversePairwise } from '../../../utils/itertools';
import { Simplex } from '../../../utils/simplex';
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
/**
* Try to push the siblings
* @param container
* @returns
*/
export function PushContainers(container: IContainerModel): IContainerModel {
if (container.parent === null) {
return container;
}
if (container.parent.children.length <= 1) {
return container;
}
const prevIndex = container.parent.children.length - 2;
const prev: IContainerModel = container.parent.children[prevIndex];
const isOverlapping = prev.properties.x + prev.properties.width > container.properties.x;
if (!isOverlapping) {
return container;
}
// find hole
let lastContainer: IContainerModel | null = null;
let space: number = 0;
while (space.toFixed(2) < container.properties.width.toFixed(2)) {
// FIXME: possible infinite loop due to floating point
// FIXME: A fix was applied using toFixed(2).
// FIXME: A coverture check must be done to ensure that all scenarios are covered
const it = reversePairwise<IContainerModel>(container.parent.children.filter(child => child !== container));
for (const { cur, next } of it) {
const hasSpaceBetween = next.properties.x + next.properties.width < cur.properties.x;
if (hasSpaceBetween) {
lastContainer = cur;
space = cur.properties.x - (next.properties.x + next.properties.width);
break;
}
}
if (lastContainer === null) {
// no space between
break;
}
const indexLastContainer = container.parent.children.indexOf(lastContainer);
for (let i = indexLastContainer; i <= container.parent.children.length - 2; i++) {
const sibling = container.parent.children[i];
sibling.properties.x -= space;
}
}
const hasNoSpaceBetween = lastContainer === null;
if (hasNoSpaceBetween) {
// test gap between the left of the parent and the first container
space = container.parent.children[0].properties.x;
if (space > 0) {
for (let i = 0; i <= container.parent.children.length - 2; i++) {
const sibling = container.parent.children[i];
sibling.properties.x -= space;
}
return container;
}
}
Flex(container);
return container;
}
/**
* Flex the container and its siblings (mutate)
* @param container Container to flex
* @returns Flexed container
*/
export function Flex(container: IContainerModel): void {
if (container.parent === null || container.parent === undefined) {
return;
}
const flexibleContainers = container.parent.children
.filter(sibling => sibling.properties.isFlex);
const minWidths = flexibleContainers
.map(sibling => sibling.properties.minWidth);
const fixedWidth = container.parent.children
.filter(sibling => !sibling.properties.isFlex)
.map(sibling => sibling.properties.width)
.reduce((partialSum, a) => partialSum + a, 0);
const requiredMaxWidth = container.parent.properties.width - fixedWidth;
const minimumPossibleWidth = minWidths.reduce((partialSum, a) => partialSum + a, 0);
if (minimumPossibleWidth > requiredMaxWidth) {
Swal.fire({
icon: 'error',
title: 'Cannot fit!',
text: 'Cannot fit at all even when squeezing all flex containers to the minimum.'
});
return;
}
const maxMinWidths = Math.max(...minWidths);
if (maxMinWidths * minWidths.length < requiredMaxWidth) {
const wantedWidth = requiredMaxWidth / minWidths.length;
// it fits, flex with maxMinWidths and fixed width
let right = 0;
for (const sibling of container.parent.children) {
if (!sibling.properties.isFlex) {
sibling.properties.x = right;
right += sibling.properties.width;
continue;
}
sibling.properties.x = ApplyXMargin(right, sibling.properties.margin.left);
sibling.properties.width = ApplyWidthMargin(wantedWidth, sibling.properties.margin.left, sibling.properties.margin.right);
right += wantedWidth;
}
return;
}
// does not fit
/// SIMPLEX ///
const solutions: number[] = Simplex(minWidths, requiredMaxWidth);
// apply the solutions
for (let i = 0; i < flexibleContainers.length; i++) {
flexibleContainers[i].properties.width = ApplyWidthMargin(solutions[i], flexibleContainers[i].properties.margin.left, flexibleContainers[i].properties.margin.right)
}
// move the containers
let right = 0;
for (const sibling of container.parent.children) {
sibling.properties.x = ApplyXMargin(right, sibling.properties.margin.left);
right += sibling.properties.width;
}
}

View file

@ -9,6 +9,7 @@
import Swal from 'sweetalert2';
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { ISizePointer } from '../../../Interfaces/ISizePointer';
import { PushContainers } from './FlexBehaviors';
/**
* "Transform the container into a rigid body"
@ -23,6 +24,7 @@ export function ApplyRigidBody(
container: IContainerModel
): IContainerModel {
container = constraintBodyInsideParent(container);
container = PushContainers(container);
container = constraintBodyInsideUnallocatedWidth(container);
return container;
}
@ -183,7 +185,6 @@ export function constraintBodyInsideUnallocatedWidth(
showConfirmButton: false,
timer: 5000
});
container.properties.isRigidBody = false;
return container;
}
@ -238,8 +239,7 @@ function getAvailableWidths(
// Ignore the exception
// And we will also only uses containers that also are rigid or are anchors
if (child === exception ||
(!child.properties.isRigidBody && !child.properties.isAnchor)) {
if (child === exception) {
continue;
}
const childX = child.properties.x;

View file

@ -1,9 +1,12 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { applyParentTransform } from '../../../utils/itertools';
import { restoreX, transformX } from '../../../utils/svg';
export function ApplySymbol(container: IContainerModel, symbol: ISymbolModel): IContainerModel {
container.properties.x = transformX(symbol.x, symbol.width, symbol.config.XPositionReference);
container.properties.x = restoreX(container.properties.x, container.properties.width, container.properties.XPositionReference);
const [x] = applyParentTransform(container.parent, container.properties.x, 0);
container.properties.x = x;
return container;
}