Merged PR 169: Fix bugs with flex + Disable obnoxious swals + Add selector text + Sort SVG scss to different files

- Disable PushBehavior and set it in a different file
- Fix behviors when parent === undefined
- Fix flex behavrios when using anchors
- Fix siblings not applying flex to theirs children on container delete
- Fix flex behavior when using anchors
- Enable flex by default
- Disable obnoxious swals
- Add selector text
- Sort SVG scss to different files

Others: Add some todos
This commit is contained in:
Eric Nguyen 2022-08-26 13:59:03 +00:00
parent a718268972
commit 3f58c5ba5e
15 changed files with 208 additions and 134 deletions

View file

@ -1,3 +1,6 @@
// TODO: https://eslint.org/docs/latest/rules/func-names
// TODO: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/naming-convention.md
module.exports = { module.exports = {
env: { env: {
browser: true, browser: true,

View file

@ -35,6 +35,7 @@ export const App: React.FunctionComponent<IAppProps> = (props) => {
historyCurrentStep: 0 historyCurrentStep: 0
}); });
// TODO: move this into a variable
useEffect(() => { useEffect(() => {
const queryString = window.location.search; const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString); const urlParams = new URLSearchParams(queryString);

View file

@ -90,7 +90,7 @@ export function DeleteContainer(
throw new Error('[DeleteContainer] Could not find container among parent\'s children'); throw new Error('[DeleteContainer] Could not find container among parent\'s children');
} }
Flex(container); ApplyBehaviorsOnSiblings(container, current.Symbols);
// Select the previous container // Select the previous container
// or select the one above // or select the one above
@ -275,8 +275,8 @@ export function AddContainer(
setHistoryCurrentStep(history.length - 1); setHistoryCurrentStep(history.length - 1);
} }
function UpdateParentChildrenList(parentClone: IContainerModel | null): void { function UpdateParentChildrenList(parentClone: IContainerModel | null | undefined): void {
if (parentClone === null) { if (parentClone === null || parentClone === undefined) {
return; return;
} }
parentClone.children.sort( parentClone.children.sort(

View file

@ -3,6 +3,7 @@ import { IConfiguration } from '../../../Interfaces/IConfiguration';
import { getCircularReplacer } from '../../../utils/saveload'; import { getCircularReplacer } from '../../../utils/saveload';
import { ID } from '../../SVG/SVG'; import { ID } from '../../SVG/SVG';
import { IEditorState } from '../../../Interfaces/IEditorState'; import { IEditorState } from '../../../Interfaces/IEditorState';
import { SHOW_SELECTOR_TEXT } from '../../../utils/default';
export function SaveEditorAsJSON( export function SaveEditorAsJSON(
history: IHistoryState[], history: IHistoryState[],
@ -54,6 +55,9 @@ export function SaveEditorAsSVG(): void {
// remove the selector // remove the selector
const group = svg.children[svg.children.length - 1]; const group = svg.children[svg.children.length - 1];
group.removeChild(group.children[group.children.length - 1]); group.removeChild(group.children[group.children.length - 1]);
if (SHOW_SELECTOR_TEXT) {
group.removeChild(group.children[group.children.length - 1]);
}
// get svg source. // get svg source.
const serializer = new XMLSerializer(); const serializer = new XMLSerializer();

View file

@ -17,9 +17,7 @@ export function ApplyBehaviors(container: IContainerModel, symbols: Map<string,
ApplyAnchor(container); ApplyAnchor(container);
} }
if (container.properties.isFlex) { Flex(container);
Flex(container);
}
ApplyRigidBody(container); ApplyRigidBody(container);

View file

@ -1,77 +1,12 @@
import Swal from 'sweetalert2'; import Swal from 'sweetalert2';
import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { reversePairwise } from '../../../utils/itertools';
import { Simplex } from '../../../utils/simplex'; import { Simplex } from '../../../utils/simplex';
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg'; import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
/** interface IFlexibleGroup {
* Try to push the siblings group: IContainerModel[]
* @param container offset: number
* @returns size: number
*/
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;
} }
/** /**
@ -83,26 +18,37 @@ export function Flex(container: IContainerModel): void {
if (container.parent === null || container.parent === undefined) { if (container.parent === null || container.parent === undefined) {
return; return;
} }
const flexibleContainers = container.parent.children
const flexibleGroups = GetFlexibleGroups(container.parent);
for (const flexibleGroup of flexibleGroups) {
FlexGroup(flexibleGroup);
}
}
function FlexGroup(flexibleGroup: IFlexibleGroup): void {
const children = flexibleGroup.group;
const flexibleContainers = children
.filter(sibling => sibling.properties.isFlex); .filter(sibling => sibling.properties.isFlex);
const minWidths = flexibleContainers const minWidths = flexibleContainers
.map(sibling => sibling.properties.minWidth); .map(sibling => sibling.properties.minWidth);
const fixedWidth = container.parent.children const fixedWidth = children
.filter(sibling => !sibling.properties.isFlex) .filter(sibling => !sibling.properties.isFlex)
.map(sibling => sibling.properties.width) .map(sibling => sibling.properties.width)
.reduce((partialSum, a) => partialSum + a, 0); .reduce((partialSum, a) => partialSum + a, 0);
const requiredMaxWidth = container.parent.properties.width - fixedWidth; const requiredMaxWidth = flexibleGroup.size - fixedWidth;
const minimumPossibleWidth = minWidths.reduce((partialSum, a) => partialSum + a, 0); const minimumPossibleWidth = minWidths.reduce((partialSum, a) => partialSum + a, 0);
if (minimumPossibleWidth > requiredMaxWidth) { if (minimumPossibleWidth > requiredMaxWidth) {
Swal.fire({ // Swal.fire({
icon: 'error', // icon: 'error',
title: 'Cannot fit!', // title: 'Cannot fit!',
text: 'Cannot fit at all even when squeezing all flex containers to the minimum.' // text: 'Cannot fit at all even when squeezing all flex containers to the minimum.'
}); // });
console.error('[FlexBehavior] Cannot fit at all even when squeezing all flex containers to the minimum.');
return; return;
} }
@ -110,8 +56,8 @@ export function Flex(container: IContainerModel): void {
if (maxMinWidths * minWidths.length < requiredMaxWidth) { if (maxMinWidths * minWidths.length < requiredMaxWidth) {
const wantedWidth = requiredMaxWidth / minWidths.length; const wantedWidth = requiredMaxWidth / minWidths.length;
// it fits, flex with maxMinWidths and fixed width // it fits, flex with maxMinWidths and fixed width
let right = 0; let right = flexibleGroup.offset;
for (const sibling of container.parent.children) { for (const sibling of children) {
if (!sibling.properties.isFlex) { if (!sibling.properties.isFlex) {
sibling.properties.x = right; sibling.properties.x = right;
right += sibling.properties.width; right += sibling.properties.width;
@ -132,13 +78,46 @@ export function Flex(container: IContainerModel): void {
// apply the solutions // apply the solutions
for (let i = 0; i < flexibleContainers.length; i++) { 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) flexibleContainers[i].properties.width = ApplyWidthMargin(solutions[i], flexibleContainers[i].properties.margin.left, flexibleContainers[i].properties.margin.right);
} }
// move the containers // move the containers
let right = 0; let right = flexibleGroup.offset;
for (const sibling of container.parent.children) { for (const sibling of children) {
sibling.properties.x = ApplyXMargin(right, sibling.properties.margin.left); sibling.properties.x = ApplyXMargin(right, sibling.properties.margin.left);
right += sibling.properties.width; right += sibling.properties.width;
} }
} }
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;
}

View file

@ -0,0 +1,73 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { reversePairwise } from '../../../utils/itertools';
import { Flex } from './FlexBehaviors';
/**
* 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;
}

View file

@ -9,7 +9,6 @@
import Swal from 'sweetalert2'; import Swal from 'sweetalert2';
import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { ISizePointer } from '../../../Interfaces/ISizePointer'; import { ISizePointer } from '../../../Interfaces/ISizePointer';
import { PushContainers } from './FlexBehaviors';
/** /**
* "Transform the container into a rigid body" * "Transform the container into a rigid body"
@ -24,7 +23,6 @@ export function ApplyRigidBody(
container: IContainerModel container: IContainerModel
): IContainerModel { ): IContainerModel {
container = constraintBodyInsideParent(container); container = constraintBodyInsideParent(container);
container = PushContainers(container);
container = constraintBodyInsideUnallocatedWidth(container); container = constraintBodyInsideUnallocatedWidth(container);
return container; return container;
} }
@ -118,7 +116,7 @@ function constraintBodyInsideSpace(
export function constraintBodyInsideUnallocatedWidth( export function constraintBodyInsideUnallocatedWidth(
container: IContainerModel container: IContainerModel
): IContainerModel { ): IContainerModel {
if (container.parent === null) { if (container.parent === null || container.parent === undefined) {
return container; return container;
} }
@ -177,14 +175,14 @@ export function constraintBodyInsideUnallocatedWidth(
if (availableWidth === undefined) { if (availableWidth === undefined) {
console.warn(`Container ${container.properties.id} cannot fit in any space due to its minimum width being to large. Consequently, its rigid body property is disabled.`); console.warn(`Container ${container.properties.id} cannot fit in any space due to its minimum width being to large. Consequently, its rigid body property is disabled.`);
Swal.fire({ // Swal.fire({
position: 'top-end', // position: 'top-end',
title: `Container ${container.properties.id} cannot fit!`, // title: `Container ${container.properties.id} cannot fit!`,
text: 'Its rigid body property is now disabled. Change its the minimum width or free the parent container.', // text: 'Its rigid body property is now disabled. Change its the minimum width or free the parent container.',
timerProgressBar: true, // timerProgressBar: true,
showConfirmButton: false, // showConfirmButton: false,
timer: 5000 // timer: 5000
}); // });
return container; return container;
} }

View file

@ -4,21 +4,4 @@ svg {
width: 100%; width: 100%;
} }
text {
font-size: 18px;
font-weight: 800;
fill: none;
fill-opacity: 0;
stroke: #000000;
stroke-width: 1px;
stroke-linecap: butt;
stroke-linejoin: miter;
stroke-opacity: 1;
transform: translateX(-50%);
transform-box: fill-box;
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}

View file

@ -0,0 +1,4 @@
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}

View file

@ -1,7 +1,9 @@
import './Selector.scss';
import * as React from 'react'; import * as React from 'react';
import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { IContainerModel } from '../../../../Interfaces/IContainerModel';
import { getAbsolutePosition } from '../../../utils/itertools'; import { SHOW_SELECTOR_TEXT } from '../../../../utils/default';
import { RemoveMargin } from '../../../utils/svg'; import { getAbsolutePosition } from '../../../../utils/itertools';
import { RemoveMargin } from '../../../../utils/svg';
interface ISelectorProps { interface ISelectorProps {
selected?: IContainerModel selected?: IContainerModel
@ -28,6 +30,9 @@ export const Selector: React.FC<ISelectorProps> = (props) => {
props.selected.properties.margin.right props.selected.properties.margin.right
)); ));
const xText = x + width / 2;
const yText = y + height / 2;
const style: React.CSSProperties = { const style: React.CSSProperties = {
stroke: '#3B82F6', // tw blue-500 stroke: '#3B82F6', // tw blue-500
strokeWidth: 4, strokeWidth: 4,
@ -39,13 +44,23 @@ export const Selector: React.FC<ISelectorProps> = (props) => {
}; };
return ( return (
<rect <>
x={x} <rect
y={y} x={x}
width={width} y={y}
height={height} width={width}
style={style} height={height}
> style={style}
</rect> >
</rect>
{SHOW_SELECTOR_TEXT
? <text
x={xText}
y={yText}
>
{props.selected.properties.displayedText}
</text>
: null}
</>
); );
}; };

View file

@ -0,0 +1,14 @@
text {
font-family: 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 18px;
font-weight: 800;
fill: #fff;
fill-opacity: 1;
stroke: #000000;
stroke-width: 1px;
stroke-linecap: butt;
stroke-linejoin: miter;
stroke-opacity: 1;
transform: translateX(-50%);
transform-box: fill-box;
}

View file

@ -1,8 +1,9 @@
import './SVG.scss';
import * as React from 'react'; import * as React from 'react';
import { UncontrolledReactSVGPanZoom } from 'react-svg-pan-zoom'; import { UncontrolledReactSVGPanZoom } from 'react-svg-pan-zoom';
import { Container } from './Elements/Container'; import { Container } from './Elements/Container';
import { ContainerModel } from '../../Interfaces/IContainerModel'; import { ContainerModel } from '../../Interfaces/IContainerModel';
import { Selector } from './Elements/Selector'; import { Selector } from './Elements/Selector/Selector';
import { BAR_WIDTH } from '../Bar/Bar'; import { BAR_WIDTH } from '../Bar/Bar';
import { DepthDimensionLayer } from './Elements/DepthDimensionLayer'; import { DepthDimensionLayer } from './Elements/DepthDimensionLayer';
import { SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default'; import { SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default';

View file

@ -9,7 +9,8 @@ import { ISymbolModel } from '../Interfaces/ISymbolModel';
/// CONTAINER DEFAULTS /// /// CONTAINER DEFAULTS ///
export const SHOW_TEXT = true; export const SHOW_TEXT = false;
export const SHOW_SELECTOR_TEXT = true;
export const DEFAULTCHILDTYPE_ALLOW_CYCLIC = false; export const DEFAULTCHILDTYPE_ALLOW_CYCLIC = false;
export const DEFAULTCHILDTYPE_MAX_DEPTH = 10; export const DEFAULTCHILDTYPE_MAX_DEPTH = 10;

View file

@ -20,17 +20,17 @@
export function Simplex(minWidths: number[], requiredMaxWidth: number): number[] { export function Simplex(minWidths: number[], requiredMaxWidth: number): number[] {
/// 1) standardized the equations /// 1) standardized the equations
// add the min widths constraints // add the min widths constraints
const maximumConstraints = minWidths.map(minWidth => minWidth * -1); const constraints = minWidths.map(minWidth => minWidth * -1);
// add the max width constraint // add the max widths constraint
maximumConstraints.push(requiredMaxWidth); constraints.push(requiredMaxWidth);
/// 2) Create the initial matrix /// 2) Create the initial matrix
// get row length (nVariables + nConstraints + 1 (z) + 1 (b)) // get row length (nVariables + nConstraints + 1 (z) + 1 (b))
const nVariables = minWidths.length; const nVariables = minWidths.length;
const nConstraints = maximumConstraints.length; const nConstraints = constraints.length;
const rowlength = nVariables + nConstraints + 2; const rowlength = nVariables + nConstraints + 2;
const matrix = GetInitialMatrix(maximumConstraints, rowlength, nVariables); const matrix = GetInitialMatrix(constraints, rowlength, nVariables);
/// Apply the algorithm /// Apply the algorithm
const finalMatrix = ApplyMainLoop(matrix, rowlength); const finalMatrix = ApplyMainLoop(matrix, rowlength);