svg-layout-designer-react/src/Components/Editor/Behaviors/FlexBehaviors.ts
Eric Nguyen 353f461f4b Merged PR 175: Implement drag drop
- [x] Implement drag drop to create an element at a specific index
- Add Swap behavrior
- Implement max contraints with simplex
~~- [ ] Implement drag drop to swap two container that flex~~

- Fixes tries number for simplex it can now go up to 2 * number of containers
- Fixes flex calling another flex behavior when not needed (remember that flex behavior is the only behavior that needs to communicate with siblings)
- Fix max width being ignored in input group
2022-09-05 07:56:45 +00:00

155 lines
4.6 KiB
TypeScript

import Swal from 'sweetalert2';
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Simplex } from '../../../utils/simplex';
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
interface IFlexibleGroup {
group: IContainerModel[]
offset: number
size: number
}
/**
* 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 flexibleGroups = GetFlexibleGroups(container.parent);
for (const flexibleGroup of flexibleGroups) {
FlexGroup(flexibleGroup);
}
}
/**
* Apply flex to the group
* @param flexibleGroup Group that contains a list of flexible containers
* @returns
*/
function FlexGroup(flexibleGroup: IFlexibleGroup): void {
const children = flexibleGroup.group;
const {
flexibleContainers,
nonFlexibleContainers
} = SeparateFlexibleContainers(children);
const minWidths = flexibleContainers
.map(sibling => sibling.properties.minWidth);
const fixedWidth = nonFlexibleContainers
.map(sibling => sibling.properties.width)
.reduce((widthSum, a) => widthSum + a, 0);
const requiredMaxWidth = flexibleGroup.size - fixedWidth;
const minimumPossibleWidth = minWidths.reduce((widthSum, a) => widthSum + a, 0); // sum(minWidths)
const checkSumMinWidthsIsFitting = minimumPossibleWidth > requiredMaxWidth;
if (checkSumMinWidthsIsFitting) {
Swal.fire({
icon: 'error',
title: 'Cannot fit!',
text: 'Cannot fit at all even when squeezing all flex containers to the minimum.'
});
throw new Error('[FlexBehavior] Cannot fit at all even when squeezing all flex containers to the minimum.');
}
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 = flexibleGroup.offset;
for (const sibling of 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 maxWidths = flexibleContainers
.map(sibling => sibling.properties.maxWidth);
const solutions: number[] = Simplex(minWidths, maxWidths, 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 = flexibleGroup.offset;
for (const sibling of children) {
sibling.properties.x = ApplyXMargin(right, sibling.properties.margin.left);
right += sibling.properties.width;
}
}
function SeparateFlexibleContainers(
containers: IContainerModel[]
): { flexibleContainers: IContainerModel[], nonFlexibleContainers: IContainerModel[] } {
const flexibleContainers: IContainerModel[] = [];
const nonFlexibleContainers: IContainerModel[] = [];
containers.forEach((container) => {
if (container.properties.isFlex) {
flexibleContainers.push(container);
return;
}
nonFlexibleContainers.push(container);
});
return {
flexibleContainers,
nonFlexibleContainers
};
}
/**
* Returns a list of groups of flexible containers
* @param parent Parent in which the flexible children will be set in groups
* @returns a list of groups of flexible containers
*/
export function GetFlexibleGroups(parent: IContainerModel): IFlexibleGroup[] {
const flexibleGroups: IFlexibleGroup[] = [];
let group: IContainerModel[] = [];
let offset = 0;
let size = 0;
for (const child of parent.children) {
if (child.properties.isAnchor) {
size = child.properties.x - offset;
const flexibleGroup: IFlexibleGroup = {
group,
offset,
size
};
flexibleGroups.push(flexibleGroup);
offset = child.properties.x + child.properties.width;
group = [];
continue;
}
group.push(child);
}
size = parent.properties.width - offset;
const flexibleGroup: IFlexibleGroup = {
group,
offset,
size
};
flexibleGroups.push(flexibleGroup);
return flexibleGroups;
}