Merged PR 196: Implement Vertical orientation + Upgrade Heroicons to 2.0

Implémenter l'orientation verticale

Modifier l'effet de append

Implementer RigidBody

Implementer Flex et simplex

Implémenter Push

Implémenter Swap

Implement MinMaxHeight without behaviors

Fix Margin for Height

Implement PositionReference

Fix dimension vertical position inside children

Add orientation change in form

Implement sortChildren

Implement Anchor

Fix warning message on overlapping

Fix minimap when root container is vertical

#7287
#7288
#7289
#7290
#7291
#7292
#7294
#7295
#7296
#7297
#7298
#7299
#7300
#7301
#7302
This commit is contained in:
Eric Nguyen 2022-09-28 16:07:56 +00:00
parent 459e83a0c8
commit 18cbacaca1
45 changed files with 2112 additions and 1063 deletions

25
.vscode/launch.json vendored
View file

@ -9,7 +9,30 @@
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}"
"webRoot": "${workspaceFolder}",
},
{
"type": "msedge",
"request": "launch",
"name": "Launch Edge against localhost",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}",
},
{
"type": "chrome",
"request": "launch",
"name": "Launch Brave against localhost",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}",
"runtimeExecutable": "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
},
{
"type": "chrome",
"request": "launch",
"name": "Launch Thorium against localhost",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}",
"runtimeExecutable": "/bin/thorium-browser"
}
]
}

View file

@ -13,44 +13,44 @@
"coverage": "vitest run coverage"
},
"dependencies": {
"@heroicons/react": "^1.0.6",
"@heroicons/react": "^2.0.11",
"interweave": "^13.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-svg-pan-zoom": "^3.11.0",
"react-window": "^1.8.7",
"sweetalert2": "^11.4.28",
"sweetalert2": "^11.4.34",
"sweetalert2-react-content": "^5.0.3"
},
"devDependencies": {
"@testing-library/dom": "^8.16.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.4.1",
"@types/react": "^18.0.15",
"@testing-library/dom": "^8.18.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"@types/react-svg-pan-zoom": "^3.3.5",
"@types/react-window": "^1.8.5",
"@typescript-eslint/eslint-plugin": "^5.31.0",
"@typescript-eslint/parser": "^5.31.0",
"@vitejs/plugin-react": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
"@vitejs/plugin-react": "^2.1.0",
"@vitest/ui": "^0.20.3",
"autoprefixer": "^10.4.8",
"eslint": "^8.20.0",
"autoprefixer": "^10.4.12",
"eslint": "^8.24.0",
"eslint-config-standard": "^17.0.0",
"eslint-config-standard-with-typescript": "^22.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.2.4",
"eslint-plugin-n": "^15.3.0",
"eslint-plugin-only-warn": "^1.0.3",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-promise": "^6.0.1",
"eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^20.0.0",
"postcss": "^8.4.14",
"sass": "^1.54.0",
"tailwindcss": "^3.1.7",
"typescript": "^4.6.4",
"vite": "^3.0.0",
"postcss": "^8.4.16",
"sass": "^1.55.0",
"tailwindcss": "^3.1.8",
"typescript": "^4.8.3",
"vite": "^3.1.3",
"vitest": "^0.20.3"
}
}

1128
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
import * as React from 'react';
import { ClockIcon, CubeIcon, LinkIcon, MailIcon } from '@heroicons/react/outline';
import { ClockIcon as ClockIconS, CubeIcon as CubeIconS, LinkIcon as LinkIconS, MailIcon as MailIconS } from '@heroicons/react/solid';
import { ClockIcon, CubeIcon, LinkIcon, EnvelopeIcon } from '@heroicons/react/24/outline';
import { ClockIcon as ClockIconS, CubeIcon as CubeIconS, LinkIcon as LinkIconS, EnvelopeIcon as EnvolopeIconS } from '@heroicons/react/24/solid';
import { BarIcon } from './BarIcon';
interface IBarProps {
@ -56,8 +56,8 @@ export function Bar(props: IBarProps): JSX.Element {
onClick={() => props.toggleMessages()}>
{
props.isMessagesOpen
? <MailIconS className='heroicon' />
: <MailIcon className='heroicon' />
? <EnvolopeIconS className='heroicon' />
: <EnvelopeIcon className='heroicon' />
}
</BarIcon>
</div>

View file

@ -1,4 +1,4 @@
import { ChevronRightIcon } from '@heroicons/react/outline';
import { ChevronRightIcon } from '@heroicons/react/24/outline';
import React, { useState } from 'react';
import { ICategory } from '../../Interfaces/ICategory';
import { TruncateString } from '../../utils/stringtools';

View file

@ -1,15 +1,17 @@
import { MenuAlt2Icon, MenuAlt3Icon, MenuIcon } from '@heroicons/react/outline';
import { Bars3BottomLeftIcon, Bars3CenterLeftIcon, Bars3Icon, Bars3BottomRightIcon, Bars2Icon, ViewColumnsIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import { PropertyType } from '../../Enums/PropertyType';
import { XPositionReference } from '../../Enums/XPositionReference';
import { PositionReference } from '../../Enums/PositionReference';
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS } from '../../utils/default';
import { ApplyWidthMargin, ApplyXMargin, RemoveWidthMargin, RemoveXMargin, RestoreX, TransformX } from '../../utils/svg';
import { ApplyWidthMargin, ApplyXMargin, RemoveWidthMargin, RemoveXMargin, RestoreX, RestoreY, TransformX, TransformY } from '../../utils/svg';
import { InputGroup } from '../InputGroup/InputGroup';
import { TextInputGroup } from '../InputGroup/TextInputGroup';
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
import { Select } from '../Select/Select';
import { ToggleButton, ToggleType } from '../ToggleButton/ToggleButton';
import { Orientation } from '../../Interfaces/Orientation';
interface IContainerFormProps {
properties: IContainerProperties
@ -37,7 +39,7 @@ function GetCSSInputs(properties: IContainerProperties,
export function ContainerForm(props: IContainerFormProps): JSX.Element {
return (
<div className='grid grid-cols-2 gap-y-4 items-center'>
<div className='grid grid-cols-2 gap-y-6 items-center'>
<InputGroup
labelText='Name'
inputKey='id'
@ -71,6 +73,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='string'
value={props.properties.displayedText?.toString()}
onChange={(value) => props.onChange('displayedText', value)} />
<OrientationSelector {...props} />
<TextInputGroup
id={`${props.properties.id}-x`}
labelText='x'
@ -79,14 +82,18 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName=''
type='number'
isDisabled={props.properties.linkedSymbolId !== ''}
value={TransformX(RemoveXMargin(props.properties.x, props.properties.margin.left), props.properties.width, props.properties.xPositionReference).toString()}
value={TransformX(
RemoveXMargin(props.properties.x, props.properties.margin.left),
props.properties.width,
props.properties.positionReference
).toString()}
onChange={(value) => props.onChange(
'x',
ApplyXMargin(
RestoreX(
Number(value),
props.properties.width,
props.properties.xPositionReference
props.properties.positionReference
),
props.properties.margin.left
)
@ -98,11 +105,25 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
labelClassName=''
inputClassName=''
type='number'
value={(props.properties.y - (props.properties.margin?.top ?? 0)).toString()}
onChange={(value) => props.onChange('y', Number(value) + (props.properties.margin?.top ?? 0))} />
value={TransformY(
RemoveXMargin(props.properties.y, props.properties.margin.top),
props.properties.height,
props.properties.positionReference
).toString()}
onChange={(value) => props.onChange(
'y',
ApplyXMargin(
RestoreY(
Number(value),
props.properties.height,
props.properties.positionReference
),
props.properties.margin.top
)
)} />
<TextInputGroup
id={`${props.properties.id}-minWidth`}
labelText='Minimum width'
labelText='Minimum Width'
inputKey='minWidth'
labelClassName=''
inputClassName=''
@ -112,7 +133,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
onChange={(value) => props.onChange('minWidth', Number(value))} />
<TextInputGroup
id={`${props.properties.id}-maxWidth`}
labelText='Maximum width'
labelText='Maximum Width'
inputKey='maxWidth'
labelClassName=''
inputClassName=''
@ -132,6 +153,26 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
value={(RemoveWidthMargin(props.properties.width, props.properties.margin.left, props.properties.margin.right)).toString()}
onChange={(value) => props.onChange('width', ApplyWidthMargin(Number(value), props.properties.margin.left, props.properties.margin.right))}
isDisabled={props.properties.isFlex} />
<TextInputGroup
id={`${props.properties.id}-minHeight`}
labelText='Minimum Height'
inputKey='minHeight'
labelClassName=''
inputClassName=''
type='number'
min={1}
value={props.properties.minHeight.toString()}
onChange={(value) => props.onChange('minHeight', Number(value))} />
<TextInputGroup
id={`${props.properties.id}-maxHeight`}
labelText='Maximum Height'
inputKey='maxHeight'
labelClassName=''
inputClassName=''
type='number'
min={1}
value={props.properties.maxHeight.toString()}
onChange={(value) => props.onChange('maxHeight', Number(value))} />
<TextInputGroup
id={`${props.properties.id}-height`}
labelText='Height'
@ -139,9 +180,12 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
labelClassName=''
inputClassName=''
type='number'
min={0}
value={(props.properties.height + (props.properties.margin?.top ?? 0) + (props.properties.margin?.bottom ?? 0)).toString()}
onChange={(value) => props.onChange('height', Number(value) - (props.properties.margin?.top ?? 0) - (props.properties.margin?.bottom ?? 0))} />
min={props.properties.minHeight}
max={props.properties.maxHeight}
value={(RemoveWidthMargin(props.properties.height, props.properties.margin.top, props.properties.margin.bottom)).toString()}
onChange={(value) => props.onChange('height', ApplyWidthMargin(Number(value), props.properties.margin.top, props.properties.margin.bottom))}
isDisabled={props.properties.isFlex}
/>
<TextInputGroup
id={`${props.properties.id}-ml`}
labelText='Margin left'
@ -182,60 +226,33 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
min={0}
value={(props.properties.margin.right ?? 0).toString()}
onChange={(value) => props.onChange('right', Number(value), PropertyType.Margin)} />
<InputGroup
<ToggleButton
labelText='Flex'
inputKey='isFlex'
labelClassName=''
inputClassName=''
type='checkbox'
type={ToggleType.Full}
checked={props.properties.isFlex}
onChange={(event) => props.onChange('isFlex', event.target.checked)} />
<InputGroup
onChange={(event) => props.onChange('isFlex', event.target.checked)}
/>
<ToggleButton
labelText='Anchor'
inputKey='isAnchor'
labelClassName=''
inputClassName=''
type='checkbox'
type={ToggleType.Full}
checked={props.properties.isAnchor}
onChange={(event) => props.onChange('isAnchor', event.target.checked)} />
<RadioGroupButtons
name='XPositionReference'
value={props.properties.xPositionReference.toString()}
inputClassName='hidden'
labelText='Horizontal alignment'
inputGroups={[
{
text: (
<div title='Left' aria-label='left' className='radio-button-icon'>
<MenuAlt2Icon className='heroicon' />
</div>
),
value: XPositionReference.Left.toString()
},
{
text: (
<div title='Center' aria-label='center' className='radio-button-icon'>
<MenuIcon className='heroicon' />
</div>
),
value: XPositionReference.Center.toString()
},
{
text: (
<div title='Right' aria-label='right' className='radio-button-icon'>
<MenuAlt3Icon className='heroicon' />
</div>
),
value: XPositionReference.Right.toString()
}
]}
onChange={(event) => props.onChange('xPositionReference', Number(event.target.value))} />
<AlignmentSelector
{...props}
/>
<Select
inputKey='linkedSymbolId'
labelText='Align with symbol'
labelClassName=''
inputClassName=''
inputs={[...props.symbols.values()].map(symbol => ({
key: symbol.id,
text: symbol.id,
value: symbol.id
}))}
@ -244,43 +261,43 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
{GetCSSInputs(props.properties, props.onChange)}
{
SHOW_SELF_DIMENSIONS &&
<InputGroup
<ToggleButton
labelText='Show dimension'
inputKey='showSelfDimensions'
labelClassName=''
inputClassName=''
type='checkbox'
type={ToggleType.Full}
checked={props.properties.showSelfDimensions}
onChange={(event) => props.onChange('showSelfDimensions', event.target.checked)} />
}
{
SHOW_CHILDREN_DIMENSIONS &&
<InputGroup
<ToggleButton
labelText='Show overall dimension of its children'
inputKey='showChildrenDimensions'
labelClassName=''
inputClassName=''
type='checkbox'
type={ToggleType.Full}
checked={props.properties.showChildrenDimensions}
onChange={(event) => props.onChange('showChildrenDimensions', event.target.checked)} />
}
{
SHOW_BORROWER_DIMENSIONS &&
<>
<InputGroup
<ToggleButton
labelText='Mark the position'
inputKey='markPositionToDimensionBorrower'
labelClassName=''
inputClassName=''
type='checkbox'
type={ToggleType.Full}
checked={props.properties.markPositionToDimensionBorrower}
onChange={(event) => props.onChange('markPositionToDimensionBorrower', event.target.checked)} />
<InputGroup
<ToggleButton
labelText='Show dimension with marked children'
inputKey='isDimensionBorrower'
labelClassName=''
inputClassName=''
type='checkbox'
type={ToggleType.Full}
checked={props.properties.isDimensionBorrower}
onChange={(event) => props.onChange('isDimensionBorrower', event.target.checked)} />
</>
@ -288,3 +305,135 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
</div>
);
}
// TODO: Implement categories in the form
function OrientationSelector(props: IContainerFormProps): JSX.Element {
return <RadioGroupButtons
key='orientation'
name='Orientation'
value={props.properties.orientation.toString()}
inputClassName='hidden'
labelText='Orientation'
colQty={2}
inputGroups={[
{
key: 'orientation-horizontal',
text: (
<div title='Horizontal' aria-label='horizontal' className='radio-button-icon'>
<ViewColumnsIcon className='heroicon' />
</div>
),
value: Orientation.Horizontal.toString()
},
{
key: 'orientation-vertical',
text: (
<div title='Vertical' aria-label='vertical' className='radio-button-icon'>
<ViewColumnsIcon className='heroicon rotate-90' />
</div>
),
value: Orientation.Vertical.toString()
}
]}
onChange={(event) => {
props.onChange('orientation', Number(event.target.value));
}}
/>;
}
function AlignmentSelector(props: IContainerFormProps): JSX.Element {
return <RadioGroupButtons
key='positionReference'
name='PositionReference'
value={props.properties.positionReference.toString()}
inputClassName='hidden'
labelText='Alignment'
colQty={3}
inputGroups={[
{
key: 'position-reference-tl',
text: (
<div title='Top Left' aria-label='top left' className='radio-button-icon'>
<Bars3BottomLeftIcon className='heroicon' />
</div>
),
value: PositionReference.TopLeft.toString()
},
{
key: 'position-reference-tc',
text: (
<div title='Top Center' aria-label='top center' className='radio-button-icon'>
<Bars2Icon className='heroicon -scale-y-100' />
</div>
),
value: PositionReference.TopCenter.toString()
},
{
key: 'position-reference-tr',
text: (
<div title='Top Right' aria-label='top right' className='radio-button-icon'>
<Bars3BottomRightIcon className='heroicon' />
</div>
),
value: PositionReference.TopRight.toString()
},
{
key: 'position-reference-cl',
text: (
<div title='Center Left' aria-label='center left' className='radio-button-icon'>
<Bars3CenterLeftIcon className='heroicon' />
</div>
),
value: PositionReference.CenterLeft.toString()
},
{
key: 'position-reference-cc',
text: (
<div title='Center Center' aria-label='center center' className='radio-button-icon'>
<Bars3Icon className='heroicon' />
</div>
),
value: PositionReference.CenterCenter.toString()
},
{
key: 'position-reference-cr',
text: (
<div title='Center Right' aria-label='center right' className='radio-button-icon'>
<Bars3CenterLeftIcon className='heroicon -scale-x-100' />
</div>
),
value: PositionReference.CenterRight.toString()
},
{
key: 'position-reference-bl',
text: (
<div title='Bottom Left' aria-label='bottom left' className='radio-button-icon'>
<Bars3BottomLeftIcon className='heroicon -scale-y-100' />
</div>
),
value: PositionReference.BottomLeft.toString()
},
{
key: 'position-reference-bc',
text: (
<div title='Bottom Center' aria-label='center center' className='radio-button-icon'>
<Bars2Icon className='heroicon' />
</div>
),
value: PositionReference.BottomCenter.toString()
},
{
key: 'position-reference-br',
text: (
<div title='Bottom Right' aria-label='bottom right' className='radio-button-icon'>
<Bars3BottomRightIcon className='heroicon -scale-y-100' />
</div>
),
value: PositionReference.BottomRight.toString()
}
]}
onChange={(event) => {
props.onChange('positionReference', Number(event.target.value));
}} />;
}

View file

@ -1,8 +1,9 @@
import { fireEvent, render, screen } from '@testing-library/react';
import * as React from 'react';
import { expect, describe, it, vi } from 'vitest';
import { XPositionReference } from '../../Enums/XPositionReference';
import { PositionReference } from '../../Enums/PositionReference';
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
import { Orientation } from '../../Interfaces/Orientation';
import { Properties } from './ContainerProperties';
describe.concurrent('Properties', () => {
@ -26,14 +27,17 @@ describe.concurrent('Properties', () => {
parentId: 'parentId',
linkedSymbolId: '',
displayedText: 'stuff',
orientation: Orientation.Horizontal,
x: 1,
y: 1,
width: 1,
height: 1,
minWidth: 1,
maxWidth: Infinity,
minHeight: 1,
maxHeight: Infinity,
margin: {},
xPositionReference: XPositionReference.Left,
positionReference: PositionReference.TopLeft,
isFlex: false,
isAnchor: false,
warning: '',

View file

@ -9,6 +9,7 @@ import { IContainerModel, ContainerModel } from '../../../Interfaces/IContainerM
import { IHistoryState } from '../../../Interfaces/IHistoryState';
import { IPattern, GetPattern, ContainerOrPattern } from '../../../Interfaces/IPattern';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { Orientation } from '../../../Interfaces/Orientation';
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_MAX_DEPTH, DEFAULTCHILDTYPE_ALLOW_CYCLIC } from '../../../utils/default';
import { FindContainerById } from '../../../utils/itertools';
import { ApplyMargin } from '../../../utils/svg';
@ -137,12 +138,12 @@ function AddNewContainerToParent(
let x = containerConfig.X ?? 0;
let y = containerConfig.Y ?? 0;
let width = containerConfig.Width ?? containerConfig.MaxWidth ?? containerConfig.MinWidth ?? parentClone.properties.width;
let height = containerConfig.Height ?? parentClone.properties.height;
let height = containerConfig.Height ?? containerConfig.MaxHeight ?? containerConfig.MinHeight ?? parentClone.properties.height;
({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right));
// Apply an add method (append or insert/replace)
x = ApplyAddMethod(index + typeIndex, containerConfig, parentClone, x);
({ x, y } = ApplyAddMethod(index + typeIndex, containerConfig, parentClone, x, y));
// Set the counter of the object type in order to assign an unique id
UpdateCounters(newCounters, type);
@ -400,8 +401,9 @@ function ApplyAddMethod(
index: number,
containerConfig: IAvailableContainer,
parent: IContainerModel,
x: number
): number {
x: number,
y: number
): { x: number, y: number } {
if (index > 0 && (
containerConfig.AddMethod === undefined ||
containerConfig.AddMethod === null ||
@ -412,8 +414,13 @@ function ApplyAddMethod(
.at(index - 1);
if (lastChild !== undefined) {
x += (lastChild.properties.x + lastChild.properties.width);
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
if (isHorizontal) {
x += lastChild.properties.x + lastChild.properties.width;
} else {
y += lastChild.properties.y + lastChild.properties.height;
}
}
}
return x;
return { x, y };
}

View file

@ -1,12 +1,13 @@
import { IHistoryState } from '../../../Interfaces/IHistoryState';
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
import { FindContainerById, MakeIterator } from '../../../utils/itertools';
import { FindContainerById, MakeDFSIterator } from '../../../utils/itertools';
import { GetCurrentHistory } from '../Editor';
import { ApplyBehaviors, ApplyBehaviorsOnSiblings, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import Swal from 'sweetalert2';
import { PropertyType } from '../../../Enums/PropertyType';
import { TransformX } from '../../../utils/svg';
import { TransformX, TransformY } from '../../../utils/svg';
import { Orientation } from '../../../Interfaces/Orientation';
/**
* Select a container
@ -133,7 +134,7 @@ function GetSelectedContainerOnDelete(
* @param container Container to unlink
*/
function UnlinkContainerFromSymbols(symbols: Map<string, ISymbolModel>, container: IContainerModel): void {
const it = MakeIterator(container);
const it = MakeDFSIterator(container);
for (const child of it) {
const symbol = symbols.get(child.properties.linkedSymbolId);
if (symbol === undefined) {
@ -194,11 +195,34 @@ export function SortChildren(parentClone: IContainerModel | null | undefined): v
if (parentClone === null || parentClone === undefined) {
return;
}
const isHorizontal = parentClone.properties.orientation === Orientation.Horizontal;
const children = parentClone.children;
if (!isHorizontal) {
parentClone.children.sort(
(a, b) => {
const yA = TransformY(a.properties.y, a.properties.height, a.properties.positionReference);
const yB = TransformY(b.properties.y, b.properties.height, b.properties.positionReference);
if (yA < yB) {
return -1;
}
if (yB < yA) {
return 1;
}
// xA = xB
const indexA = children.indexOf(a);
const indexB = children.indexOf(b);
return indexA - indexB;
}
);
return;
}
parentClone.children.sort(
(a, b) => {
const xA = TransformX(a.properties.x, a.properties.width, a.properties.xPositionReference);
const xB = TransformX(b.properties.x, b.properties.width, b.properties.xPositionReference);
const xA = TransformX(a.properties.x, a.properties.width, a.properties.positionReference);
const xB = TransformX(b.properties.x, b.properties.width, b.properties.positionReference);
if (xA < xB) {
return -1;
}

View file

@ -14,6 +14,7 @@
*/
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Interfaces/Orientation';
import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
/**
@ -21,17 +22,16 @@ import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
* Apply the following modification to the overlapping rigid body container :
* @param container Container to impose its position
*/
export function ApplyAnchor(container: IContainerModel): IContainerModel {
if (container.parent === undefined ||
container.parent === null) {
return container;
}
const rigidBodies = container.parent.children.filter(
export function ApplyAnchor(container: IContainerModel, parent: IContainerModel): IContainerModel {
const rigidBodies = parent.children.filter(
child => !child.properties.isAnchor
);
const overlappingContainers = GetOverlappingContainers(container, rigidBodies);
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
const overlappingContainers = isHorizontal
? GetHorizontallyOverlappingContainers(container, rigidBodies)
: GetVerticallyOverlappingContainers(container, rigidBodies);
for (const overlappingContainer of overlappingContainers) {
ConstraintBodyInsideUnallocatedWidth(overlappingContainer);
}
@ -44,7 +44,7 @@ export function ApplyAnchor(container: IContainerModel): IContainerModel {
* @param containers A list of containers
* @returns A list of overlapping containers
*/
export function GetOverlappingContainers(
export function GetHorizontallyOverlappingContainers(
container: IContainerModel,
containers: IContainerModel[]
): IContainerModel[] {
@ -68,3 +68,63 @@ export function GetOverlappingContainers(
}
return overlappingContainers;
}
/**
* Returns the overlapping containers with container
* @param container A container
* @param containers A list of containers
* @returns A list of overlapping containers
*/
export function GetVerticallyOverlappingContainers(
container: IContainerModel,
containers: IContainerModel[]
): IContainerModel[] {
const min1 = container.properties.y;
const max1 = container.properties.y + container.properties.height;
const overlappingContainers: IContainerModel[] = [];
for (const other of containers) {
if (other === container) {
continue;
}
const min2 = other.properties.y;
const max2 = other.properties.y + other.properties.height;
const isOverlapping = Math.min(max1, max2) - Math.max(min1, min2) > 0;
if (!isOverlapping) {
continue;
}
overlappingContainers.push(other);
}
return overlappingContainers;
}
export function GetOverlappingContainers(
container: IContainerModel,
containers: IContainerModel[]
): IContainerModel[] {
const overlappingContainers: IContainerModel[] = [];
for (const other of containers) {
if (other === container) {
continue;
}
DoOverlap(container, other) && overlappingContainers.push(other);
}
return overlappingContainers;
}
function DoOverlap(container: IContainerModel, other: IContainerModel): boolean {
if (container.properties.x >= other.properties.x + other.properties.width ||
other.properties.x >= container.properties.x + container.properties.width) {
return false;
}
if (container.properties.y >= other.properties.y + other.properties.height ||
other.properties.y >= container.properties.y + container.properties.height) {
return false;
}
return true;
}

View file

@ -20,18 +20,22 @@ export function ApplyBehaviors(container: IContainerModel, symbols: Map<string,
ApplySymbol(container, symbol);
}
if (container.properties.isAnchor) {
ApplyAnchor(container);
}
if (container.parent !== undefined && container.parent !== null) {
const parent = container.parent;
if (ENABLE_SWAP) {
ApplySwap(container);
}
if (container.properties.isAnchor) {
ApplyAnchor(container, parent);
}
Flex(container);
if (ENABLE_SWAP) {
ApplySwap(container, parent);
}
if (ENABLE_RIGID) {
ApplyRigidBody(container);
Flex(container, parent);
if (ENABLE_RIGID) {
ApplyRigidBody(container, parent);
}
}
if (APPLY_BEHAVIORS_ON_CHILDREN) {
@ -68,13 +72,9 @@ export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel,
newContainer.parent.children
.forEach((container: IContainerModel) => {
if (container.parent != null) {
const overlappingContainers = GetOverlappingContainers(container, container.parent.children);
if (overlappingContainers.length > 0) {
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
} else {
container.properties.warning = '';
}
UpdateWarning(container, container.parent);
}
if (container === newContainer) {
return;
}
@ -85,7 +85,6 @@ export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel,
});
}
/**
* Iterate over the siblings of newContainer and apply the behaviors
* @param newContainer
@ -102,13 +101,9 @@ export function ApplyBehaviorsOnSiblings(newContainer: IContainerModel, symbols:
ApplyBehaviors(container, symbols);
if (container.parent != null) {
const overlappingContainers = GetOverlappingContainers(container, container.parent.children);
if (overlappingContainers.length > 0) {
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
} else {
container.properties.warning = '';
}
UpdateWarning(container, container.parent);
}
if (container === newContainer) {
return;
}
@ -118,4 +113,11 @@ export function ApplyBehaviorsOnSiblings(newContainer: IContainerModel, symbols:
}
});
}
function UpdateWarning(container: IContainerModel, parent: IContainerModel): void {
const overlappingContainers = GetOverlappingContainers(container, parent.children);
if (overlappingContainers.length > 0) {
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
} else {
container.properties.warning = '';
}
}

View file

@ -1,4 +1,5 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Interfaces/Orientation';
import { Simplex } from '../../../utils/simplex';
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
@ -10,27 +11,111 @@ interface IFlexibleGroup {
/**
* 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) {
export function Flex(container: IContainerModel, parent: IContainerModel): void {
const isVertical = parent.properties.orientation === Orientation.Vertical;
if (isVertical) {
const wantedWidth = Math.min(container.properties.maxWidth, parent.properties.width);
container.properties.width = ApplyWidthMargin(wantedWidth, container.properties.margin.left, container.properties.margin.right);
const flexibleGroups = GetVerticalFlexibleGroups(parent);
for (const flexibleGroup of flexibleGroups) {
FlexGroupVertically(flexibleGroup);
}
return;
}
const flexibleGroups = GetFlexibleGroups(container.parent);
const wantedHeight = Math.min(container.properties.maxHeight, parent.properties.height);
container.properties.height = ApplyWidthMargin(wantedHeight, container.properties.margin.top, container.properties.margin.bottom);
const flexibleGroups = GetHorizontalFlexibleGroups(parent);
for (const flexibleGroup of flexibleGroups) {
FlexGroup(flexibleGroup);
FlexGroupHorizontally(flexibleGroup);
}
}
/**
* 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 GetHorizontalFlexibleGroups(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;
}
/**
* 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 GetVerticalFlexibleGroups(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.y - offset;
const flexibleGroup: IFlexibleGroup = {
group,
offset,
size
};
flexibleGroups.push(flexibleGroup);
offset = child.properties.y + child.properties.height;
group = [];
continue;
}
group.push(child);
}
size = parent.properties.height - offset;
const flexibleGroup: IFlexibleGroup = {
group,
offset,
size
};
flexibleGroups.push(flexibleGroup);
return flexibleGroups;
}
/**
* Apply flex to the group
* @param flexibleGroup Group that contains a list of flexible containers
* @returns
*/
function FlexGroup(flexibleGroup: IFlexibleGroup): void {
function FlexGroupHorizontally(flexibleGroup: IFlexibleGroup): void {
const children = flexibleGroup.group;
const {
flexibleContainers,
@ -96,6 +181,77 @@ function FlexGroup(flexibleGroup: IFlexibleGroup): void {
}
}
/**
* Apply flex to the group
* @param flexibleGroup Group that contains a list of flexible containers
* @returns
*/
function FlexGroupVertically(flexibleGroup: IFlexibleGroup): void {
const children = flexibleGroup.group;
const {
flexibleContainers,
nonFlexibleContainers
} = SeparateFlexibleContainers(children);
if (flexibleContainers.length === 0) {
return;
}
const minHeights = flexibleContainers
.map(sibling => sibling.properties.minHeight);
const fixedHeight = nonFlexibleContainers
.map(sibling => sibling.properties.height)
.reduce((heightSum, a) => heightSum + a, 0);
const requiredMaxHeight = flexibleGroup.size - fixedHeight;
const minimumPossibleHeight = minHeights.reduce((heightSum, a) => heightSum + a, 0); // sum(minHeights)
const checkSumMinHeightsIsFitting = minimumPossibleHeight > requiredMaxHeight;
if (checkSumMinHeightsIsFitting) {
console.warn('[FlexBehavior] Cannot fit at all even when squeezing all flex containers to the minimum.');
return;
}
const maxMinHeights = Math.max(...minHeights);
if (maxMinHeights * minHeights.length <= requiredMaxHeight) {
const wantedHeight = requiredMaxHeight / minHeights.length;
// it fits, flex with maxMinHeights and fixed height
let right = flexibleGroup.offset;
for (const sibling of children) {
if (!sibling.properties.isFlex) {
sibling.properties.y = right;
right += sibling.properties.height;
continue;
}
sibling.properties.y = ApplyXMargin(right, sibling.properties.margin.top);
sibling.properties.height = ApplyWidthMargin(wantedHeight, sibling.properties.margin.top, sibling.properties.margin.bottom);
right += wantedHeight;
}
return;
}
// does not fit
/// SIMPLEX ///
const maxHeights = flexibleContainers
.map(sibling => sibling.properties.maxHeight);
const solutions: number[] = Simplex(minHeights, maxHeights, requiredMaxHeight);
// apply the solutions
for (let i = 0; i < flexibleContainers.length; i++) {
flexibleContainers[i].properties.height = ApplyWidthMargin(solutions[i], flexibleContainers[i].properties.margin.top, flexibleContainers[i].properties.margin.bottom);
}
// move the containers
let right = flexibleGroup.offset;
for (const sibling of children) {
sibling.properties.y = ApplyXMargin(right, sibling.properties.margin.top);
right += sibling.properties.height;
}
}
function SeparateFlexibleContainers(
containers: IContainerModel[]
): { flexibleContainers: IContainerModel[], nonFlexibleContainers: IContainerModel[] } {
@ -114,41 +270,3 @@ function SeparateFlexibleContainers(
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;
}

View file

@ -1,4 +1,5 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Interfaces/Orientation';
import { ReversePairwise } from '../../../utils/itertools';
import { Flex } from './FlexBehaviors';
@ -7,17 +8,27 @@ import { Flex } from './FlexBehaviors';
* @param container
* @returns
*/
export function PushContainers(container: IContainerModel): IContainerModel {
if (container.parent === null) {
export function ApplyPush(container: IContainerModel, parent: IContainerModel): IContainerModel {
if (parent.children.length <= 1) {
return container;
}
if (container.parent.children.length <= 1) {
return container;
const children = parent.children;
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
if (isHorizontal) {
PushContainersHorizontally(container, children);
} else {
PushContainersVertically(container, children);
}
const prevIndex = container.parent.children.length - 2;
const prev: IContainerModel = container.parent.children[prevIndex];
Flex(container, parent);
return container;
}
export function PushContainersHorizontally(container: IContainerModel, children: IContainerModel[]): IContainerModel {
const prevIndex = children.length - 2;
const prev: IContainerModel = children[prevIndex];
const isOverlapping = prev.properties.x + prev.properties.width > container.properties.x;
if (!isOverlapping) {
return container;
@ -32,7 +43,7 @@ export function PushContainers(container: IContainerModel): IContainerModel {
// 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));
const it = ReversePairwise<IContainerModel>(children.filter(child => child !== container));
for (const { cur, next } of it) {
const hasSpaceBetween = next.properties.x + next.properties.width < cur.properties.x;
@ -48,9 +59,9 @@ export function PushContainers(container: IContainerModel): IContainerModel {
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];
const indexLastContainer = children.indexOf(lastContainer);
for (let i = indexLastContainer; i <= children.length - 2; i++) {
const sibling = children[i];
sibling.properties.x -= space;
}
}
@ -58,16 +69,71 @@ export function PushContainers(container: IContainerModel): IContainerModel {
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;
space = children[0].properties.x;
if (space > 0) {
for (let i = 0; i <= container.parent.children.length - 2; i++) {
const sibling = container.parent.children[i];
for (let i = 0; i <= children.length - 2; i++) {
const sibling = children[i];
sibling.properties.x -= space;
}
return container;
}
}
Flex(container);
return container;
}
export function PushContainersVertically(container: IContainerModel, children: IContainerModel[]): IContainerModel {
const prevIndex = children.length - 2;
const prev: IContainerModel = children[prevIndex];
const isOverlapping = prev.properties.y + prev.properties.height > container.properties.y;
if (!isOverlapping) {
return container;
}
// find hole
let lastContainer: IContainerModel | null = null;
let space: number = 0;
while (space.toFixed(2) < container.properties.height.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>(children.filter(child => child !== container));
for (const { cur, next } of it) {
const hasSpaceBetween = next.properties.y + next.properties.height < cur.properties.y;
if (hasSpaceBetween) {
lastContainer = cur;
space = cur.properties.y - (next.properties.y + next.properties.height);
break;
}
}
if (lastContainer === null) {
// no space between
break;
}
const indexLastContainer = children.indexOf(lastContainer);
for (let i = indexLastContainer; i <= children.length - 2; i++) {
const sibling = children[i];
sibling.properties.y -= space;
}
}
const hasNoSpaceBetween = lastContainer === null;
if (hasNoSpaceBetween) {
// test gap between the left of the parent and the first container
space = children[0].properties.y;
if (space > 0) {
for (let i = 0; i <= children.length - 2; i++) {
const sibling = children[i];
sibling.properties.y -= space;
}
return container;
}
}
return container;
}

View file

@ -8,6 +8,7 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { ISizePointer } from '../../../Interfaces/ISizePointer';
import { Orientation } from '../../../Interfaces/Orientation';
import { ENABLE_HARD_RIGID } from '../../../utils/default';
/**
@ -20,9 +21,10 @@ import { ENABLE_HARD_RIGID } from '../../../utils/default';
* @returns A rigid body container
*/
export function ApplyRigidBody(
container: IContainerModel
container: IContainerModel,
parent: IContainerModel
): IContainerModel {
container = ConstraintBodyInsideParent(container);
container = ConstraintBodyInsideParent(container, parent);
if (ENABLE_HARD_RIGID) {
container = ConstraintBodyInsideUnallocatedWidth(container);
@ -40,13 +42,10 @@ export function ApplyRigidBody(
* @returns Updated container
*/
function ConstraintBodyInsideParent(
container: IContainerModel
container: IContainerModel,
parent: IContainerModel
): IContainerModel {
if (container.parent === null || container.parent === undefined) {
return container;
}
const parentProperties = container.parent.properties;
const parentProperties = parent.properties;
const parentWidth = parentProperties.width;
const parentHeight = parentProperties.height;
@ -125,9 +124,15 @@ export function ConstraintBodyInsideUnallocatedWidth(
}
// Get the available spaces of the parent
const availableWidths = GetAvailableWidths(container.parent, container);
const containerX = container.properties.x;
const containerWidth = container.properties.width;
const isHorizontal =
container.parent.properties.orientation === Orientation.Horizontal;
const availableWidths = GetAvailableWidths(
0,
container.parent.properties.width,
container.parent.children,
container,
isHorizontal
);
// Check if there is still some space
if (availableWidths.length === 0) {
@ -136,6 +141,103 @@ export function ConstraintBodyInsideUnallocatedWidth(
);
}
const containerId = container.properties.id;
if (!isHorizontal) {
const containerY = container.properties.y;
const containerHeight = container.properties.height;
const containerMinHeight = container.properties.minHeight;
SortAvailableWidthsByClosest(containerY, containerHeight, availableWidths);
// Check if the container actually fit inside
// It will usually fit if it was alrady fitting
const availableWidthFound = availableWidths.find((width) =>
IsFitting(containerHeight, width)
);
if (availableWidthFound === undefined) {
const { x, width } = TrySqueeze(containerY, containerHeight, containerMinHeight, containerId, availableWidths);
container.properties.y = x;
container.properties.height = width;
return container;
}
ConstraintBodyInsideSpace(
container,
0,
availableWidthFound.x,
container.parent.properties.width,
availableWidthFound.width
);
return container;
}
const containerX = container.properties.x;
const containerWidth = container.properties.width;
const containerMinWidth = container.properties.minWidth;
SortAvailableWidthsByClosest(containerX, containerWidth, availableWidths);
// Check if the container actually fit inside
// It will usually fit if it was alrady fitting
const availableWidthFound = availableWidths.find((width) =>
IsFitting(containerWidth, width)
);
if (availableWidthFound === undefined) {
const { x, width } = TrySqueeze(containerX, containerWidth, containerMinWidth, containerId, availableWidths);
container.properties.x = x;
container.properties.width = width;
return container;
}
ConstraintBodyInsideSpace(
container,
availableWidthFound.x,
0,
availableWidthFound.width,
container.parent.properties.height
);
return container;
}
function TrySqueeze(
containerX: number,
containerWidth: number,
containerMinWidth: number,
containerId: string,
availableWidths: ISizePointer[]
): { x: number, width: number } {
// Otherwise, it is possible that it does not fit
// There is two way to reach this part of the code
// 1) Enable isRigidBody such as width > availableWidth.width
// 2) Resize a container such as width > availableWidth.width
// We want the container to fit automatically inside the available space
// even if it means to resize the container
const availableWidth: ISizePointer | undefined = availableWidths.find((width) => {
return IsFitting(containerMinWidth, width);
});
if (availableWidth === undefined) {
console.debug(`Container ${containerId} cannot fit in any space due to its minimum width being to large.`);
return {
x: containerX,
width: containerWidth
};
}
return {
x: availableWidth.x,
width: availableWidth.width
};
}
function SortAvailableWidthsByClosest(containerX: number, containerWidth: number, availableWidths: ISizePointer[]): void {
const middle = containerX + containerWidth / 2;
// Sort the available width to find the space with the closest position
availableWidths.sort(
@ -153,42 +255,6 @@ export function ConstraintBodyInsideUnallocatedWidth(
return Math.abs(compared1X - middle) - Math.abs(compared2X - middle);
}
);
// Check if the container actually fit inside
// It will usually fit if it was alrady fitting
const availableWidthFound = availableWidths.find((width) =>
IsFitting(container.properties.width, width)
);
if (availableWidthFound === undefined) {
// Otherwise, it is possible that it does not fit
// There is two way to reach this part of the code
// 1) Enable isRigidBody such as width > availableWidth.width
// 2) Resize a container such as width > availableWidth.width
// We want the container to fit automatically inside the available space
// even if it means to resize the container
const availableWidth: ISizePointer | undefined = availableWidths.find((width) => {
return IsFitting(container.properties.minWidth, width);
});
if (availableWidth === undefined) {
console.debug(`Container ${container.properties.id} cannot fit in any space due to its minimum width being to large.`);
return container;
}
container.properties.x = availableWidth.x;
container.properties.width = availableWidth.width;
return container;
}
return ConstraintBodyInsideSpace(
container,
availableWidthFound.x,
0,
availableWidthFound.width,
container.parent.properties.height
);
}
/**
@ -212,16 +278,17 @@ function IsFitting(containerWidth: number,
* @returns {ISizePointer[]} Array of unallocated widths (x=position of the unallocated space, width=size of the allocated space)
*/
function GetAvailableWidths(
container: IContainerModel,
exception: IContainerModel
x: number,
width: number,
children: IContainerModel[],
exception: IContainerModel,
isHorizontal: boolean
): ISizePointer[] {
// Initialize the first size pointer
// which takes full width of the available space
const x = 0;
const width = container.properties.width;
let unallocatedSpaces: ISizePointer[] = [{ x, width }];
for (const child of container.children) {
for (const child of children) {
if (unallocatedSpaces.length < 1) {
return unallocatedSpaces;
}
@ -231,8 +298,8 @@ function GetAvailableWidths(
if (child === exception) {
continue;
}
const childX = child.properties.x;
const childWidth = child.properties.width;
const childX = isHorizontal ? child.properties.x : child.properties.y;
const childWidth = isHorizontal ? child.properties.width : child.properties.height;
// get the space of the child that is inside the parent
let newUnallocatedSpace: ISizePointer[] = [];

View file

@ -3,15 +3,23 @@
*/
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { GetOverlappingContainers } from './AnchorBehaviors';
import { Orientation } from '../../../Interfaces/Orientation';
import { GetHorizontallyOverlappingContainers, GetVerticallyOverlappingContainers } from './AnchorBehaviors';
export function ApplySwap(container: IContainerModel): void {
if (container.parent === null || container.parent === undefined) {
export function ApplySwap(container: IContainerModel, parent: IContainerModel): void {
const children = parent.children;
const isVertical = parent.properties.orientation === Orientation.Vertical;
if (isVertical) {
SwapVertically(container, children);
return;
}
const children = container.parent.children;
const overlappingContainers = GetOverlappingContainers(container, children);
SwapHorizontally(container, children);
}
export function SwapHorizontally(container: IContainerModel, children: IContainerModel[]): void {
const overlappingContainers = GetHorizontallyOverlappingContainers(container, children);
if (overlappingContainers.length > 1 || overlappingContainers.length === 0) {
return;
@ -29,3 +37,23 @@ export function ApplySwap(container: IContainerModel): void {
const indexOverlapping = children.indexOf(overlappingContainer);
[children[indexContainer], children[indexOverlapping]] = [children[indexOverlapping], children[indexContainer]];
}
export function SwapVertically(container: IContainerModel, children: IContainerModel[]): void {
const overlappingContainers = GetVerticallyOverlappingContainers(container, children);
if (overlappingContainers.length > 1 || overlappingContainers.length === 0) {
return;
}
const overlappingContainer = overlappingContainers.pop();
if (overlappingContainer === null || overlappingContainer === undefined) {
return;
}
// swap positions
[overlappingContainer.properties.y, container.properties.y] = [container.properties.y, overlappingContainer.properties.y];
const indexContainer = children.indexOf(container);
const indexOverlapping = children.indexOf(overlappingContainer);
[children[indexContainer], children[indexOverlapping]] = [children[indexOverlapping], children[indexContainer]];
}

View file

@ -5,7 +5,7 @@ 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);
container.properties.x = RestoreX(container.properties.x, container.properties.width, container.properties.positionReference);
const [x] = ApplyParentTransform(container.parent, container.properties.x, 0);
container.properties.x = x;
return container;

View file

@ -3,9 +3,10 @@ import * as React from 'react';
import { fireEvent, render, screen } from '../../utils/test-utils';
import { ElementsSidebar } from './ElementsSidebar';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { XPositionReference } from '../../Enums/XPositionReference';
import { PositionReference } from '../../Enums/PositionReference';
import { FindContainerById } from '../../utils/itertools';
import { DEFAULT_MAINCONTAINER_PROPS } from '../../utils/default';
import { Orientation } from '../../Interfaces/Orientation';
describe.concurrent('Elements sidebar', () => {
it('With a MainContainer', () => {
@ -94,14 +95,17 @@ describe.concurrent('Elements sidebar', () => {
parentId: 'main',
linkedSymbolId: '',
displayedText: 'child-1',
orientation: Orientation.Horizontal,
x: 0,
y: 0,
minWidth: 1,
minHeight: 1,
width: 0,
height: 0,
margin: {},
isFlex: false,
maxWidth: Infinity,
maxHeight: Infinity,
type: 'type',
isAnchor: false,
warning: '',
@ -110,7 +114,7 @@ describe.concurrent('Elements sidebar', () => {
showSelfDimensions: true,
isDimensionBorrower: true,
markPositionToDimensionBorrower: false,
xPositionReference: XPositionReference.Left
positionReference: PositionReference.TopLeft
},
userData: {}
}
@ -125,18 +129,21 @@ describe.concurrent('Elements sidebar', () => {
parentId: 'main',
linkedSymbolId: '',
displayedText: 'child-2',
orientation: Orientation.Horizontal,
x: 0,
y: 0,
margin: {},
minWidth: 1,
minHeight: 1,
width: 0,
height: 0,
xPositionReference: XPositionReference.Left,
positionReference: PositionReference.TopLeft,
isFlex: false,
maxWidth: Infinity,
maxHeight: Infinity,
type: 'type',
warning: '',
hideChildrenInTreeview: false,
hideChildrenInTreeview: false,
showChildrenDimensions: true,
showSelfDimensions: true,
isDimensionBorrower: true,
@ -182,16 +189,19 @@ describe.concurrent('Elements sidebar', () => {
parentId: 'main',
linkedSymbolId: '',
displayedText: 'child-1',
orientation: Orientation.Horizontal,
x: 0,
y: 0,
minWidth: 1,
minHeight: 1,
width: 0,
height: 0,
warning: '',
xPositionReference: XPositionReference.Left,
positionReference: PositionReference.TopLeft,
margin: {},
isFlex: false,
maxWidth: Infinity,
maxHeight: Infinity,
type: 'type',
hideChildrenInTreeview: false,
showChildrenDimensions: true,

View file

@ -2,10 +2,10 @@ import * as React from 'react';
import { FixedSizeList as List } from 'react-window';
import { Properties } from '../ContainerProperties/ContainerProperties';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { FindContainerById, GetDepth, MakeIterator } from '../../utils/itertools';
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { PropertyType } from '../../Enums/PropertyType';
import { ExclamationIcon } from '@heroicons/react/outline';
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
interface IElementsSidebarProps {
mainContainer: IContainerModel
@ -124,7 +124,7 @@ export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
isOpenClasses = props.isHistoryOpen ? 'right-64' : 'right-0';
}
const it = MakeIterator(props.mainContainer, true);
const it = MakeRecursionDFSIterator(props.mainContainer, 0, [0, 0], true);
const containers = [...it];
function Row({
index, style
@ -132,12 +132,12 @@ export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
index: number
style: React.CSSProperties
}): JSX.Element {
const container = containers[index];
const depth: number = GetDepth(container);
const { container, depth } = containers[index];
const key = container.properties.id.toString();
const tabs = '|\t'.repeat(depth);
const text = container.properties.displayedText === key
? `${'|\t'.repeat(depth)} ${key}`
: `${'|\t'.repeat(depth)} ${container.properties.displayedText} (${key})`;
? `${key}`
: `${container.properties.displayedText}`;
const selectedClass: string = props.selectedContainer !== undefined &&
props.selectedContainer !== null &&
props.selectedContainer.properties.id === container.properties.id
@ -157,9 +157,10 @@ export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
onDragOver={(event) => HandleDragOver(event, props.mainContainer)}
onDragLeave={(event) => HandleDragLeave(event)}
>
{tabs}
{text}
{container.properties.warning.length > 0 &&
<ExclamationIcon></ExclamationIcon>
<ExclamationTriangleIcon className='w-8'/>
}
</button>
);

View file

@ -1,5 +1,5 @@
import * as React from 'react';
import { MenuIcon, XIcon } from '@heroicons/react/outline';
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline';
interface IFloatingButtonProps {
children: React.ReactNode[] | React.ReactNode
@ -15,8 +15,8 @@ export function FloatingButton(props: IFloatingButtonProps): JSX.Element {
const [isHidden, setHidden] = React.useState(true);
const buttonListClasses = isHidden ? 'invisible opacity-0' : 'visible opacity-100';
const icon = isHidden
? <MenuIcon className="floating-btn" />
: <XIcon className="floating-btn" />;
? <Bars3Icon className="floating-btn" />
: <XMarkIcon className="floating-btn" />;
return (
<div className={`transition-all ${props.className}`}>

View file

@ -17,13 +17,11 @@ interface IInputGroupProps {
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
}
const className = 'input-group';
export function InputGroup(props: IInputGroupProps): JSX.Element {
return <>
<label
key={props.labelKey}
className={`mt-4 text-xs font-medium text-gray-800 ${props.labelClassName}`}
className={`text-xs font-medium text-gray-800 cursor-pointer ${props.labelClassName}`}
htmlFor={props.inputKey}
>
{props.labelText}
@ -32,7 +30,7 @@ export function InputGroup(props: IInputGroupProps): JSX.Element {
<input
key={props.inputKey}
id={props.inputKey}
className={`${className} ${props.inputClassName}`}
className={`input-group ${props.inputClassName}`}
type={props.type}
value={props.value}
defaultValue={props.defaultValue}

View file

@ -42,7 +42,7 @@ export function TextInputGroup(props: ITextInputGroupProps): JSX.Element {
return <>
<label
key={props.labelKey}
className={`mt-4 text-xs font-medium text-gray-800 ${props.labelClassName}`}
className={`text-xs font-medium text-gray-800 cursor-pointer ${props.labelClassName}`}
htmlFor={props.inputKey}
>
{props.labelText}

View file

@ -1,4 +1,4 @@
import { TrashIcon } from '@heroicons/react/outline';
import { TrashIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import { FixedSizeList as List } from 'react-window';
import { MessageType } from '../../Enums/MessageType';

View file

@ -8,24 +8,43 @@ interface IRadioGroupButtonsProps {
inputClassName: string
labelText: string
inputGroups: IInputGroup[]
colQty: number
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
}
// Use whole class name for react to preparse
const GRID_COLS = [
'grid-cols-none',
'grid-cols-1',
'grid-cols-2',
'grid-cols-3',
'grid-cols-4',
'grid-cols-5',
'grid-cols-6',
'grid-cols-7',
'grid-cols-8',
'grid-cols-9',
'grid-cols-10',
'grid-cols-11',
'grid-cols-12'
];
export function RadioGroupButtons(props: IRadioGroupButtonsProps): JSX.Element {
let inputGroups;
if (props.value !== undefined) {
// dynamic
inputGroups = props.inputGroups.map((inputGroup) => (
<div key={inputGroup.value}>
<div key={inputGroup.key}>
<input
id={inputGroup.value}
key={inputGroup.key}
id={inputGroup.key}
type='radio'
name={props.name}
className={`peer m-2 ${props.inputClassName}`}
value={inputGroup.value}
checked={props.value === inputGroup.value}
onChange={props.onChange} />
<label htmlFor={inputGroup.value} className='text-gray-400 peer-checked:text-blue-500'>
<label htmlFor={inputGroup.key} className='text-gray-400 peer-checked:text-blue-500'>
{inputGroup.text}
</label>
</div>
@ -34,28 +53,31 @@ export function RadioGroupButtons(props: IRadioGroupButtonsProps): JSX.Element {
} else {
// static
inputGroups = props.inputGroups.map((inputGroup) => (
<div key={inputGroup.value}>
<div key={inputGroup.key}>
<input
id={inputGroup.value}
key={inputGroup.key}
id={inputGroup.key}
type='radio'
name={props.name}
className={`peer m-2 ${props.inputClassName}`}
value={inputGroup.value}
defaultChecked={props.defaultValue === inputGroup.value} />
<label htmlFor={inputGroup.value} className='text-gray-400 peer-checked:text-blue-500'>
<label htmlFor={inputGroup.key} className='text-gray-400 peer-checked:text-blue-500'>
{inputGroup.text}
</label>
</div>
));
}
const gridColsClass = GRID_COLS[props.colQty];
return (
<>
<label className='mt-4 text-xs font-medium text-gray-800'>
<label className='text-xs font-medium text-gray-800'>
{props.labelText}
</label>
<div id='XPositionReference'
className='flex flex-col'
className={`grid ${gridColsClass}`}
>
{inputGroups}
</div>

View file

@ -1,15 +1,13 @@
import * as React from 'react';
import { Interweave, Node } from 'interweave';
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS, SHOW_TEXT } from '../../../utils/default';
import { CancelParentTransform, GetDepth, MakeIterator, Pairwise } from '../../../utils/itertools';
import { Dimension } from './Dimension';
import { IContainerProperties } from '../../../Interfaces/IContainerProperties';
import { TransformX } from '../../../utils/svg';
import { Camelize } from '../../../utils/stringtools';
import { SHOW_TEXT } from '../../../utils/default';
interface IContainerProps {
model: IContainerModel
depth: number
scale: number
}
@ -22,6 +20,7 @@ export function Container(props: IContainerProps): JSX.Element {
child => <Container
key={`container-${child.properties.id}`}
model={child}
depth={props.depth + 1}
scale={props.scale}
/>);
@ -57,71 +56,6 @@ export function Container(props: IContainerProps): JSX.Element {
style={style}
>
</rect>);
// Dimension props
const depth = GetDepth(props.model);
const dimensionMargin = DIMENSION_MARGIN * depth / props.scale;
const id = `dim-${props.model.properties.id}`;
const xStart: number = 0;
const xEnd = width;
const yDim = -dimensionMargin;
const strokeWidth = 1;
const text = (width.toFixed(0) ?? 0).toString();
let childrenDimensions: JSX.Element | null = null;
if (props.model.properties.showChildrenDimensions && props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
const {
childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren
} = GetChildrenDimensionProps(props, dimensionMargin);
childrenDimensions = <Dimension
id={childrenId}
xStart={xChildrenStart}
xEnd={xChildrenEnd}
yStart={yChildren}
yEnd={yChildren}
strokeWidth={strokeWidth}
text={textChildren}
scale={props.scale} />;
}
const borrowerDimensions: JSX.Element[] = [];
if (props.model.properties.isDimensionBorrower && SHOW_BORROWER_DIMENSIONS) {
const it = MakeIterator(props.model);
const marks = [];
for (const container of it) {
if (!container.properties.markPositionToDimensionBorrower) {
continue;
}
const x = TransformX(
container.properties.x,
container.properties.width,
container.properties.xPositionReference
);
const [restoredX] = CancelParentTransform(container.parent, x, 0, props.model);
marks.push(
restoredX
);
}
marks.push(0);
marks.push(props.model.properties.width);
marks.sort((a, b) => a - b);
let count = 0;
for (const { cur, next } of Pairwise(marks)) {
const id = `dim-borrow-${props.model.properties.id}-{${count}}`;
borrowerDimensions.push(<Dimension
key={id}
id={id}
xStart={cur}
xEnd={next}
yStart={props.model.properties.height + yDim * -1}
yEnd={props.model.properties.height + yDim * -1}
strokeWidth={strokeWidth}
text={(next - cur).toFixed(0).toString()}
scale={props.scale}
/>);
count++;
}
}
return (
<g
@ -129,19 +63,6 @@ export function Container(props: IContainerProps): JSX.Element {
transform={transform}
key={`container-${props.model.properties.id}`}
>
{(props.model.properties.showSelfDimensions && SHOW_SELF_DIMENSIONS)
? <Dimension
id={id}
xStart={xStart}
xEnd={xEnd}
yStart={yDim}
yEnd={yDim}
strokeWidth={strokeWidth}
text={text}
scale={props.scale} />
: null}
{childrenDimensions}
{borrowerDimensions}
{svg}
{SHOW_TEXT
? <text
@ -160,33 +81,6 @@ export function Container(props: IContainerProps): JSX.Element {
);
}
function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: number): { childrenId: string, xChildrenStart: number, xChildrenEnd: number, yChildren: number, textChildren: string } {
const childrenId = `dim-children-${props.model.properties.id}`;
const lastChild = props.model.children[props.model.children.length - 1];
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.xPositionReference);
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.xPositionReference);
// Find the min and max
for (let i = props.model.children.length - 2; i >= 0; i--) {
const child = props.model.children[i];
const left = TransformX(child.properties.x, child.properties.width, child.properties.xPositionReference);
if (left < xChildrenStart) {
xChildrenStart = left;
}
const right = TransformX(child.properties.x, child.properties.width, child.properties.xPositionReference);
if (right > xChildrenEnd) {
xChildrenEnd = right;
}
}
const yChildren = props.model.properties.height + dimensionMargin;
const textChildren = (xChildrenEnd - xChildrenStart)
.toFixed(2)
.toString();
return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren };
}
function CreateReactCustomSVG(customSVG: string, props: IContainerProperties): React.ReactNode {
return <Interweave
tagName='g'

View file

@ -3,7 +3,6 @@ import { ContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN } from '../../../utils/default';
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
import { TransformX } from '../../../utils/svg';
import { Properties } from '../../ContainerProperties/ContainerProperties';
import { Dimension } from './Dimension';
interface IDimensionLayerProps {
@ -28,7 +27,7 @@ function GetDimensionsNodes(root: ContainerModel, scale: number): React.ReactNod
}
const absoluteX = GetAbsolutePosition(container)[0];
const x = TransformX(absoluteX, container.properties.width, container.properties.xPositionReference);
const x = TransformX(absoluteX, container.properties.width, container.properties.positionReference);
lastY = container.properties.y + container.properties.height;
if (x < min) {
min = x;

View file

@ -35,6 +35,7 @@ export function Dimension(props: IDimensionProps): JSX.Element {
/// We need to find the points of the notches
// Get the vector of the line
const [deltaX, deltaY] = [(props.xEnd - props.xStart), (props.yEnd - props.yStart)];
const rotation = (Math.atan2(deltaY, deltaX) / (2 * Math.PI));
// Get the unit vector
const norm = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
@ -80,9 +81,9 @@ export function Dimension(props: IDimensionProps): JSX.Element {
style={style} />
<text
x={(props.xStart + props.xEnd) / 2}
y={props.yStart}
y={(props.yStart + props.yEnd) / 2}
style={{
transform: `scale(${1 / scale}) translateX(-50%) translateY(-100%)`,
transform: `rotate(${rotation}turn) scale(${1 / scale}) translateX(-50%) translateY(-100%)`,
transformBox: 'fill-box'
}}
>

View file

@ -1,39 +1,328 @@
import * as React from 'react';
import { ContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN } from '../../../utils/default';
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS } from '../../../utils/default';
import { MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
import { TransformX, TransformY } from '../../../utils/svg';
import { Dimension } from './Dimension';
interface IDimensionLayerProps {
roots: ContainerModel | ContainerModel[] | null
root: ContainerModel
scale: number
}
function GetDimensionsNodes(root: ContainerModel): React.ReactNode[] {
const it = MakeBFSIterator(root);
const MODULE_STROKE_WIDTH = 1;
function Dimensions({ root, scale }: IDimensionLayerProps): React.ReactNode[] {
const it = MakeRecursionDFSIterator(root, 0, [0, 0]);
const dimensions: React.ReactNode[] = [];
for (const { container, depth } of it) {
const width = container.properties.width;
const id = `dim-${container.properties.id}`;
const xStart = GetAbsolutePosition(container)[0];
const xEnd = xStart + width;
const y = (container.properties.y + container.properties.height) + (DIMENSION_MARGIN * (depth + 1));
const strokeWidth = 1;
const text = width
.toFixed(0)
.toString();
dimensions.push(
<Dimension
key={id}
id={id}
xStart={xStart}
yStart={y}
xEnd={xEnd}
yEnd={y}
strokeWidth={strokeWidth}
text={text} />
const topDim = root.properties.y;
const leftDim = root.properties.x;
const rightDim = root.properties.x + root.properties.width;
const bottomDim = root.properties.y + root.properties.height;
if (!SHOW_SELF_DIMENSIONS) {
return [];
}
for (const { container, depth, currentTransform } of it) {
const containerLeftDim = leftDim - (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerTopDim = topDim - (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerBottomDim = bottomDim + (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerRightDim = rightDim + (DIMENSION_MARGIN * (depth + 1)) / scale;
if (SHOW_SELF_DIMENSIONS && container.properties.showSelfDimensions) {
AddSelfDimension(container, currentTransform, containerTopDim, containerLeftDim, scale, dimensions);
}
if (SHOW_BORROWER_DIMENSIONS && container.properties.isDimensionBorrower) {
AddBorrowerDimension(containerBottomDim, containerRightDim, depth, scale, container, currentTransform, dimensions);
}
if (SHOW_CHILDREN_DIMENSIONS && container.properties.showChildrenDimensions && container.children.length > 0) {
AddChildrenDimension(container, currentTransform, dimensions, containerBottomDim, containerRightDim, scale);
}
}
return dimensions;
}
function AddChildrenDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
containerBottomDim: number,
containerRightDim: number,
scale: number
): void {
AddHorizontalChildrenDimension(container, currentTransform, dimensions, containerBottomDim, scale);
AddVerticalChildrenDimension(container, currentTransform, dimensions, containerRightDim, scale);
}
function AddHorizontalChildrenDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
containerBottomDim: number,
scale: number
): void {
const childrenId = `dim-children-${container.properties.id}`;
const lastChild = container.children[container.children.length - 1];
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
// Find the min and max
for (let i = container.children.length - 2; i >= 0; i--) {
const child = container.children[i];
const left = TransformX(child.properties.x, child.properties.width, child.properties.positionReference);
if (left < xChildrenStart) {
xChildrenStart = left;
}
const right = TransformX(child.properties.x, child.properties.width, child.properties.positionReference);
if (right > xChildrenEnd) {
xChildrenEnd = right;
}
}
const textChildren = (xChildrenEnd - xChildrenStart)
.toFixed(2)
.toString();
const offset = currentTransform[0] + container.properties.x;
dimensions.push(<Dimension
key={childrenId}
id={childrenId}
xStart={xChildrenStart + offset}
xEnd={xChildrenEnd + offset}
yStart={containerBottomDim}
yEnd={containerBottomDim}
strokeWidth={MODULE_STROKE_WIDTH}
text={textChildren}
scale={scale} />);
}
function AddVerticalChildrenDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
containerRightDim: number,
scale: number
): void {
const childrenId = `dim-v-children-${container.properties.id}`;
const lastChild = container.children[container.children.length - 1];
let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
let yChildrenEnd = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
// Find the min and max
for (let i = container.children.length - 2; i >= 0; i--) {
const child = container.children[i];
const top = TransformY(child.properties.y, child.properties.height, child.properties.positionReference);
if (top < yChildrenStart) {
yChildrenStart = top;
}
const bottom = TransformY(child.properties.y, child.properties.height, child.properties.positionReference);
if (bottom > yChildrenEnd) {
yChildrenEnd = bottom;
}
}
const textChildren = (yChildrenEnd - yChildrenStart)
.toFixed(2)
.toString();
const offset = currentTransform[0] + container.properties.x;
dimensions.push(<Dimension
key={childrenId}
id={childrenId}
xStart={containerRightDim}
yStart={yChildrenStart + offset}
xEnd={containerRightDim}
yEnd={yChildrenEnd + offset}
strokeWidth={MODULE_STROKE_WIDTH}
text={textChildren}
scale={scale}
/>);
}
function AddBorrowerDimension(
bottomDim: number,
rightDim: number,
depth: number,
scale: number,
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[]
): void {
AddHorizontalBorrowerDimension(bottomDim, container, depth, currentTransform, dimensions, scale);
AddVerticalBorrowerDimension(rightDim, container, depth, currentTransform, dimensions, scale);
}
function AddHorizontalBorrowerDimension(
bottomDim: number,
container: IContainerModel,
depth: number,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number
): void {
const yDim = bottomDim;
const it = MakeRecursionDFSIterator(container, depth, currentTransform);
const marks = []; // list of vertical lines for the dimension
for (const {
container: childContainer, currentTransform: childCurrentTransform
} of it) {
if (!childContainer.properties.markPositionToDimensionBorrower) {
continue;
}
const x = TransformX(
childContainer.properties.x,
childContainer.properties.width,
childContainer.properties.positionReference
);
const restoredX = x + childCurrentTransform[0];
marks.push(
restoredX
);
}
return dimensions;
const restoredX = container.properties.x + currentTransform[0];
marks.push(restoredX);
marks.push(restoredX + container.properties.width);
marks.sort((a, b) => a - b);
let count = 0;
for (const { cur, next } of Pairwise(marks)) {
const id = `dim-borrow-${container.properties.id}-{${count}}`;
dimensions.push(<Dimension
key={id}
id={id}
xStart={cur}
xEnd={next}
yStart={yDim}
yEnd={yDim}
strokeWidth={MODULE_STROKE_WIDTH}
text={(next - cur).toFixed(0).toString()}
scale={scale} />);
count++;
}
}
function AddVerticalBorrowerDimension(
rightDim: number,
container: IContainerModel,
depth: number,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number
): void {
const xDim = rightDim;
const it = MakeRecursionDFSIterator(container, depth, currentTransform);
const marks = []; // list of vertical lines for the dimension
for (const {
container: childContainer, currentTransform: childCurrentTransform
} of it) {
if (!childContainer.properties.markPositionToDimensionBorrower) {
continue;
}
const y = TransformY(
childContainer.properties.y,
childContainer.properties.height,
childContainer.properties.positionReference
);
const restoredy = y + childCurrentTransform[1];
marks.push(
restoredy
);
}
const restoredY = container.properties.y + currentTransform[1];
marks.push(restoredY);
marks.push(restoredY + container.properties.height);
marks.sort((a, b) => a - b);
let count = 0;
for (const { cur, next } of Pairwise(marks)) {
const id = `dim-v-borrow-${container.properties.id}-{${count}}`;
dimensions.push(<Dimension
key={id}
id={id}
xStart={xDim}
xEnd={xDim}
yStart={cur}
yEnd={next}
strokeWidth={MODULE_STROKE_WIDTH}
text={(next - cur).toFixed(0).toString()}
scale={scale} />);
count++;
}
}
function AddSelfDimension(
container: IContainerModel,
currentTransform: [number, number],
topDim: number,
leftDim: number,
scale: number,
dimensions: React.ReactNode[]
): void {
AddHorizontalSelfDimension(container, currentTransform, topDim, dimensions, scale);
AddVerticalSelfDimension(container, currentTransform, leftDim, dimensions, scale);
}
function AddVerticalSelfDimension(container: IContainerModel, currentTransform: [number, number], leftDim: number, dimensions: React.ReactNode[], scale: number): void {
const height = container.properties.height;
const idVert = `dim-v-${container.properties.id}`;
const yStart = container.properties.y + currentTransform[1];
const yEnd = yStart + height;
const x = leftDim;
const textVert = height
.toFixed(0)
.toString();
dimensions.push(
<Dimension
key={idVert}
id={idVert}
xStart={x}
yStart={yStart}
xEnd={x}
yEnd={yEnd}
strokeWidth={MODULE_STROKE_WIDTH}
text={textVert}
scale={scale} />
);
}
function AddHorizontalSelfDimension(
container: IContainerModel,
currentTransform: [number, number],
topDim: number,
dimensions: React.ReactNode[],
scale: number
): void {
const width = container.properties.width;
const id = `dim-${container.properties.id}`;
const xStart = container.properties.x + currentTransform[0];
const xEnd = xStart + width;
const y = topDim;
const text = width
.toFixed(0)
.toString();
dimensions.push(
<Dimension
key={id}
id={id}
xStart={xStart}
yStart={y}
xEnd={xEnd}
yEnd={y}
strokeWidth={MODULE_STROKE_WIDTH}
text={text}
scale={scale} />
);
}
/**
@ -42,17 +331,9 @@ function GetDimensionsNodes(root: ContainerModel): React.ReactNode[] {
* @returns
*/
export function DimensionLayer(props: IDimensionLayerProps): JSX.Element {
let dimensions: React.ReactNode[] = [];
if (Array.isArray(props.roots)) {
props.roots.forEach(child => {
dimensions.concat(GetDimensionsNodes(child));
});
} else if (props.roots !== null) {
dimensions = GetDimensionsNodes(props.roots);
}
return (
<g>
{dimensions}
{ Dimensions(props) }
</g>
);
}

View file

@ -9,11 +9,12 @@ import { DepthDimensionLayer } from './Elements/DepthDimensionLayer';
import { MAX_FRAMERATE, SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default';
import { SymbolLayer } from './Elements/SymbolLayer';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { DimensionLayer } from './Elements/DimensionLayer';
interface ISVGProps {
width: number
height: number
children: ContainerModel | ContainerModel[] | null
children: ContainerModel
selected?: ContainerModel
symbols: Map<string, ISymbolModel>
}
@ -69,7 +70,7 @@ export function SVG(props: ISVGProps): JSX.Element {
// console.log(renderCounter.current / ((Date.now() - startTimer.current) / 1000));
UseSVGAutoResizer(setViewer);
UseFitOnce(svgViewer);
UseFitOnce(svgViewer, props.width, props.height);
const xmlns = '<http://www.w3.org/2000/svg>';
const properties = {
@ -79,20 +80,12 @@ export function SVG(props: ISVGProps): JSX.Element {
};
let children: React.ReactNode | React.ReactNode[] = [];
if (Array.isArray(props.children)) {
children = props.children.map(child =>
<Container
key={`container-${child.properties.id}`}
model={child}
scale={scale}
/>);
} else if (props.children !== null) {
children = <Container
key={`container-${props.children.properties.id}`}
model={props.children}
scale={scale}
/>;
}
children = <Container
key={`container-${props.children.properties.id}`}
model={props.children}
depth={0}
scale={scale}
/>;
return (
<div id={ID} className='ml-16'>
@ -123,7 +116,7 @@ export function SVG(props: ISVGProps): JSX.Element {
miniatureProps={{
position: 'left',
background: '#616264',
width: window.innerWidth - 12 - BAR_WIDTH,
width: 120,
height: 120
}}
>
@ -132,6 +125,7 @@ export function SVG(props: ISVGProps): JSX.Element {
{SHOW_DIMENSIONS_PER_DEPTH
? <DepthDimensionLayer scale={scale} roots={props.children} />
: null}
<DimensionLayer scale={scale} root={props.children} />
<SymbolLayer scale={scale} symbols={props.symbols} />
<Selector scale={scale} selected={props.selected} /> {/* leave this at the end so it can be removed during the svg export */}
</svg>
@ -140,9 +134,8 @@ export function SVG(props: ISVGProps): JSX.Element {
);
}
function UseFitOnce(svgViewer: React.RefObject<ReactSVGPanZoom>): void {
function UseFitOnce(svgViewer: React.RefObject<ReactSVGPanZoom>, width: number, height: number): void {
React.useEffect(() => {
svgViewer?.current?.fitToViewer();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [svgViewer, width, height]);
}

View file

@ -37,7 +37,7 @@ export function Select(props: ISelectProps): JSX.Element {
<>
<label
key={props.labelKey}
className={`mt-4 text-xs font-medium text-gray-800 ${props.labelClassName}`}
className={`text-xs font-medium text-gray-800 cursor-pointer ${props.labelClassName}`}
htmlFor={props.inputKey}
>
{props.labelText}

View file

@ -1,4 +1,4 @@
import { EyeIcon, EyeOffIcon } from '@heroicons/react/outline';
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
import { ICategory } from '../../Interfaces/ICategory';
@ -108,8 +108,8 @@ export function Sidebar(props: ISidebarProps): JSX.Element {
>
{
hideDisabled
? <EyeOffIcon className='heroicon'></EyeOffIcon>
: <EyeIcon className='heroicon'></EyeIcon>
? <EyeSlashIcon className='heroicon' />
: <EyeIcon className='heroicon' />
}
</button>
</div>

View file

@ -2,51 +2,52 @@ import React from 'react';
import './ToggleButton.scss';
interface IToggleButtonProps {
id: string
text: string
labelKey?: string
labelText: string
inputKey: string
labelClassName: string
inputClassName: string
type?: ToggleType
title: string
checked: boolean
onChange: React.ChangeEventHandler<HTMLInputElement>
}
export enum ToggleType {
Material,
IOS
Full
}
export function ToggleButton(props: IToggleButtonProps): JSX.Element {
const id = `toggle-${props.id}`;
const type = props.type ?? ToggleType.Material;
let classLine = 'line w-10 h-4 bg-gray-400 rounded-full shadow-inner';
let classDot = 'dot absolute w-6 h-6 bg-white rounded-full shadow -left-1 -top-1 transition';
if (type === ToggleType.IOS) {
classLine = 'line block bg-gray-600 w-14 h-8 rounded-full';
classDot = 'dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition';
if (type === ToggleType.Full) {
classLine = 'line block bg-gray-600 w-12 h-7 rounded-full';
classDot = 'dot absolute left-1 top-1 bg-white w-5 h-5 rounded-full transition';
}
return (
<div title={props.title}>
<div className="flex items-center justify-center w-full mb-12">
<label
htmlFor={id}
className="flex items-center cursor-pointer"
>
<div className="relative">
<input
id={id}
type="checkbox"
onChange={props.onChange}
checked={props.checked}
className="sr-only" />
<div className={classLine}></div>
<div className={classDot}></div>
</div>
<div className="ml-3 text-gray-700 font-medium">
{props.text}
</div>
</label>
</div>
</div>
);
return <>
<label
key={props.labelKey}
className={`text-xs font-medium text-gray-800 cursor-pointer ${props.labelClassName}`}
htmlFor={props.inputKey}
>
{ props.labelText }
</label>
<label
className="relative cursor-pointer"
htmlFor={props.inputKey}
>
<input
key={props.inputKey}
id={props.inputKey}
type="checkbox"
onChange={props.onChange}
checked={props.checked}
className="sr-only"
/>
<div className={classLine}></div>
<div className={classDot}></div>
</label>
</>;
}

View file

@ -5,7 +5,7 @@ import { History } from '../History/History';
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { IHistoryState } from '../../Interfaces/IHistoryState';
import { PhotographIcon, UploadIcon } from '@heroicons/react/outline';
import { CameraIcon, ArrowUpOnSquareIcon } from '@heroicons/react/24/outline';
import { FloatingButton } from '../FloatingButton/FloatingButton';
import { Bar } from '../Bar/Bar';
import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
@ -127,14 +127,14 @@ export function UI(props: IUIProps): JSX.Element {
title='Export as JSON'
onClick={props.saveEditorAsJSON}
>
<UploadIcon className="heroicon text-white" />
<CameraIcon className="heroicon text-white" />
</button>
<button type="button"
className={'transition-all w-10 h-10 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
title='Export as SVG'
onClick={props.saveEditorAsSVG}
>
<PhotographIcon className="heroicon text-white" />
<ArrowUpOnSquareIcon className="heroicon text-white" />
</button>
</FloatingButton>
</>

View file

@ -0,0 +1,11 @@
export enum PositionReference {
TopLeft,
TopCenter,
TopRight,
CenterLeft,
CenterCenter,
CenterRight,
BottomLeft,
BottomCenter,
BottomRight
}

View file

@ -1,5 +0,0 @@
export enum XPositionReference {
Left,
Center,
Right
}

View file

@ -1,9 +1,10 @@
/* eslint-disable @typescript-eslint/naming-convention */
import React from 'react';
import { AddMethod } from '../Enums/AddMethod';
import { XPositionReference } from '../Enums/XPositionReference';
import { PositionReference } from '../Enums/PositionReference';
import { IAction } from './IAction';
import { IMargin } from './IMargin';
import { Orientation } from './Orientation';
/** Model of available container used in application configuration */
export interface IAvailableContainer {
@ -16,6 +17,9 @@ export interface IAvailableContainer {
/** category */
Category?: string
/** orientation */
Orientation?: Orientation
/** horizontal offset */
X?: number
@ -30,8 +34,6 @@ export interface IAvailableContainer {
/**
* Minimum width (min=1)
* Allows the container to set isRigidBody to false when it gets squeezed
* by an anchor
*/
MinWidth?: number
@ -40,6 +42,16 @@ export interface IAvailableContainer {
*/
MaxWidth?: number
/**
* Minimum height (min=1)
*/
MinHeight?: number
/**
* Maximum height
*/
MaxHeight?: number
/** margin */
Margin?: IMargin
@ -53,7 +65,7 @@ export interface IAvailableContainer {
AddMethod?: AddMethod
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
XPositionReference?: XPositionReference
PositionReference?: PositionReference
/**
* (optional)

View file

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { XPositionReference } from '../Enums/XPositionReference';
import { PositionReference } from '../Enums/PositionReference';
import { IImage } from './IImage';
/**
@ -9,5 +9,5 @@ export interface IAvailableSymbol {
Image: IImage
Width?: number
Height?: number
XPositionReference?: XPositionReference
XPositionReference?: PositionReference
}

View file

@ -1,6 +1,7 @@
import * as React from 'react';
import { XPositionReference } from '../Enums/XPositionReference';
import { PositionReference } from '../Enums/PositionReference';
import { IMargin } from './IMargin';
import { Orientation } from './Orientation';
/**
* Properties of a container
@ -21,6 +22,9 @@ export interface IContainerProperties {
/** Text displayed in the container */
displayedText: string
/** orientation */
orientation: Orientation
/** horizontal offset */
x: number
@ -30,10 +34,14 @@ export interface IContainerProperties {
/** margin */
margin: IMargin
/** width */
width: number
/** height */
height: number
/**
* Minimum width (min=1)
* Allows the container to set isRigidBody to false when it gets squeezed
* by an anchor
*/
minWidth: number
@ -42,11 +50,15 @@ export interface IContainerProperties {
*/
maxWidth: number
/** width */
width: number
/**
* Minimum height (min=1)
*/
minHeight: number
/** height */
height: number
/**
* Maximum height
*/
maxHeight: number
/** true if anchor, false otherwise */
isAnchor: boolean
@ -55,7 +67,7 @@ export interface IContainerProperties {
isFlex: boolean
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
xPositionReference: XPositionReference
positionReference: PositionReference
/** Hide the children in the treeview */
hideChildrenInTreeview: boolean

View file

@ -1,6 +1,7 @@
import React from 'react';
export interface IInputGroup {
key: string
text: React.ReactNode
value: string
}

View file

@ -0,0 +1,4 @@
export enum Orientation {
Horizontal,
Vertical
}

View file

@ -1,4 +1,4 @@
import { XPositionReference } from '../Enums/XPositionReference';
import { PositionReference } from '../Enums/PositionReference';
import { IAvailableContainer } from '../Interfaces/IAvailableContainer';
import { IAvailableSymbol } from '../Interfaces/IAvailableSymbol';
import { IConfiguration } from '../Interfaces/IConfiguration';
@ -6,6 +6,7 @@ import { ContainerModel, IContainerModel } from '../Interfaces/IContainerModel';
import { IContainerProperties } from '../Interfaces/IContainerProperties';
import { IEditorState } from '../Interfaces/IEditorState';
import { ISymbolModel } from '../Interfaces/ISymbolModel';
import { Orientation } from '../Interfaces/Orientation';
/// EDITOR DEFAULTS ///
@ -50,7 +51,7 @@ export const DEFAULTCHILDTYPE_MAX_DEPTH = 10;
/// DIMENSIONS DEFAULTS ///
export const SHOW_SELF_DIMENSIONS = true;
export const SHOW_CHILDREN_DIMENSIONS = false;
export const SHOW_CHILDREN_DIMENSIONS = true;
export const SHOW_BORROWER_DIMENSIONS = true;
export const SHOW_DIMENSIONS_PER_DEPTH = false;
export const DIMENSION_MARGIN = 50;
@ -173,16 +174,19 @@ export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
parentId: '',
linkedSymbolId: '',
displayedText: 'main',
orientation: Orientation.Horizontal,
x: 0,
y: 0,
margin: {},
minWidth: 1,
maxWidth: Number.MAX_SAFE_INTEGER,
minHeight: 1,
maxHeight: Number.MAX_SAFE_INTEGER,
width: Number(DEFAULT_CONFIG.MainContainer.Width),
height: Number(DEFAULT_CONFIG.MainContainer.Height),
isAnchor: false,
isFlex: false,
xPositionReference: XPositionReference.Left,
positionReference: PositionReference.TopLeft,
hideChildrenInTreeview: false,
showChildrenDimensions: true, // TODO: put the dimension at the top (see pdf)
showSelfDimensions: true, // TODO: put the dimension at the bottom (see pdf)
@ -213,22 +217,28 @@ export function GetDefaultContainerProps(type: string,
width: number,
height: number,
containerConfig: IAvailableContainer): IContainerProperties {
const orientation = containerConfig.Orientation ?? Orientation.Horizontal;
const defaultIsFlex = (orientation === Orientation.Vertical && containerConfig.Height === undefined) ||
(orientation === Orientation.Horizontal && containerConfig.Width === undefined);
return ({
id: `${type}-${typeCount}`,
type,
parentId: parent?.properties.id ?? '',
linkedSymbolId: '',
displayedText: `${containerConfig.DisplayedText ?? type}-${typeCount}`,
orientation,
x,
y,
margin: containerConfig.Margin ?? {},
width,
height,
isAnchor: containerConfig.IsAnchor ?? false,
isFlex: containerConfig.IsFlex ?? containerConfig.Width === undefined,
xPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
isFlex: containerConfig.IsFlex ?? defaultIsFlex,
positionReference: containerConfig.PositionReference ?? PositionReference.TopLeft,
minWidth: containerConfig.MinWidth ?? 1,
maxWidth: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER,
minHeight: containerConfig.MinWidth ?? 1,
maxHeight: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER,
hideChildrenInTreeview: containerConfig.HideChildrenInTreeview ?? false,
showChildrenDimensions: containerConfig.ShowChildrenDimensions ?? false,
showSelfDimensions: containerConfig.ShowSelfDimensions ?? false,

View file

@ -3,7 +3,7 @@ import { IContainerModel } from '../Interfaces/IContainerModel';
/**
* Returns a Generator iterating of over the children depth-first
*/
export function * MakeIterator(root: IContainerModel, enableHideChildrenInTreeview = false): Generator<IContainerModel, void, unknown> {
export function * MakeDFSIterator(root: IContainerModel, enableHideChildrenInTreeview = false): Generator<IContainerModel, void, unknown> {
const queue: IContainerModel[] = [root];
const visited = new Set<IContainerModel>(queue);
while (queue.length > 0) {
@ -30,6 +30,11 @@ export interface ContainerAndDepth {
container: IContainerModel
depth: number
}
export interface ContainerAndDepthAndTransform extends ContainerAndDepth {
currentTransform: [number, number]
}
/**
* Returns a Generator iterating of over the children depth-first
*/
@ -54,9 +59,44 @@ export function * MakeBFSIterator(root: IContainerModel): Generator<ContainerAnd
}
}
export function * MakeRecursionDFSIterator(
root: IContainerModel | null,
depth: number,
currentTransform: [number, number],
enableHideChildrenInTreeview: boolean = false
): Generator<ContainerAndDepthAndTransform, void, unknown> {
if (root === null) {
return;
}
yield {
container: root,
depth,
currentTransform
};
if (enableHideChildrenInTreeview && root.properties.hideChildrenInTreeview) {
return;
}
for (const container of root.children) {
yield * MakeRecursionDFSIterator(
container,
depth + 1,
[
currentTransform[0] + root.properties.x,
currentTransform[1] + root.properties.y
],
enableHideChildrenInTreeview
);
}
}
/**
* Returns the depth of the container
* @returns The depth of the container
* @deprecated Please avoid using this function inside an iterationl,
* use recursive DFS or iterative BFS to get the depth
*/
export function GetDepth(parent: IContainerModel): number {
let depth = 0;
@ -80,6 +120,19 @@ export function GetAbsolutePosition(container: IContainerModel): [number, number
return CancelParentTransform(container.parent, x, y);
}
export function GetContainerLinkedList(container: IContainerModel, stop?: IContainerModel): IContainerModel[] {
const it = MakeContainerLinkedListIterator(container, stop);
return [...it];
}
export function * MakeContainerLinkedListIterator(container: IContainerModel, stop?: IContainerModel): Generator<IContainerModel, void, unknown> {
let current: IContainerModel | null = container;
while (current !== stop && current != null) {
yield current;
current = current.parent;
}
}
/**
* Cancel the hierarchic transformations to the given x, y
* @param parent Parent of the container to remove its transform
@ -93,34 +146,47 @@ export function CancelParentTransform(
y: number,
stop?: IContainerModel
): [number, number] {
let current = parent;
while (current !== stop && current != null) {
if (parent === null) {
return [x, y];
}
const it = MakeContainerLinkedListIterator(parent, stop);
for (const current of it) {
x += current.properties.x;
y += current.properties.y;
current = current.parent;
}
return [x, y];
}
/**
* Cancel the hierarchic transformations to the given x, y
* Apply the hierarchic transformations to the given x, y
* @param parent Parent of the container to remove its transform
* @param x value to be restored
* @param y value to be restored
* @returns x and y such that the transformations of the parent are cancelled
* @returns x and y such that the transformations of the parent are applied
*/
export function ApplyParentTransform(parent: IContainerModel | null, x: number, y: number): [number, number] {
let current = parent;
while (current != null) {
export function ApplyParentTransform(
parent: IContainerModel | null,
x: number,
y: number,
stop?: IContainerModel
): [number, number] {
if (parent === null) {
return [x, y];
}
const it = MakeContainerLinkedListIterator(parent, stop);
for (const current of it) {
x -= current.properties.x;
y -= current.properties.y;
current = current.parent;
}
return [x, y];
}
export function FindContainerById(root: IContainerModel, id: string): IContainerModel | undefined {
const it = MakeIterator(root);
const it = MakeDFSIterator(root);
for (const container of it) {
if (container.properties.id === id) {
return container;

View file

@ -1,4 +1,4 @@
import { FindContainerById, MakeIterator } from './itertools';
import { FindContainerById, MakeDFSIterator } from './itertools';
import { IEditorState } from '../Interfaces/IEditorState';
import { IHistoryState } from '../Interfaces/IHistoryState';
@ -33,7 +33,7 @@ export function ReviveState(state: IHistoryState): void {
symbol.linkedContainers = new Set(symbol.linkedContainers);
}
const it = MakeIterator(state.mainContainer);
const it = MakeDFSIterator(state.mainContainer);
for (const container of it) {
const parentId = container.properties.parentId;
if (parentId === null) {

View file

@ -1,4 +1,4 @@
import { XPositionReference } from '../Enums/XPositionReference';
import { PositionReference } from '../Enums/PositionReference';
// TODO: Big refactoring
/**
@ -10,26 +10,78 @@ import { XPositionReference } from '../Enums/XPositionReference';
* it is better to fix serialization with the reviver.
*/
export function TransformX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
export function TransformX(x: number, width: number, positionReference = PositionReference.TopLeft): number {
let transformedX = x;
if (xPositionReference === XPositionReference.Center) {
transformedX += width / 2;
} else if (xPositionReference === XPositionReference.Right) {
transformedX += width;
switch (positionReference) {
case PositionReference.TopCenter:
case PositionReference.CenterCenter:
case PositionReference.BottomCenter:
transformedX += width / 2;
break;
case PositionReference.TopRight:
case PositionReference.CenterRight:
case PositionReference.BottomRight:
transformedX += width;
break;
}
return transformedX;
}
export function RestoreX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
export function RestoreX(x: number, width: number, positionReference = PositionReference.TopLeft): number {
let transformedX = x;
if (xPositionReference === XPositionReference.Center) {
transformedX -= width / 2;
} else if (xPositionReference === XPositionReference.Right) {
transformedX -= width;
switch (positionReference) {
case PositionReference.TopCenter:
case PositionReference.CenterCenter:
case PositionReference.BottomCenter:
transformedX -= width / 2;
break;
case PositionReference.TopRight:
case PositionReference.CenterRight:
case PositionReference.BottomRight:
transformedX -= width;
break;
}
return transformedX;
}
export function TransformY(y: number, height: number, xPositionReference = PositionReference.TopLeft): number {
let transformedY = y;
switch (xPositionReference) {
case PositionReference.CenterLeft:
case PositionReference.CenterCenter:
case PositionReference.CenterRight:
transformedY += height / 2;
break;
case PositionReference.BottomLeft:
case PositionReference.BottomCenter:
case PositionReference.BottomRight:
transformedY += height;
break;
}
return transformedY;
}
export function RestoreY(y: number, height: number, positionReference = PositionReference.TopLeft): number {
let transformedY = y;
switch (positionReference) {
case PositionReference.CenterLeft:
case PositionReference.CenterCenter:
case PositionReference.CenterRight:
transformedY -= height / 2;
break;
case PositionReference.BottomLeft:
case PositionReference.BottomCenter:
case PositionReference.BottomRight:
transformedY -= height;
break;
}
return transformedY;
}
export function ApplyMargin(
x: number,
y: number,
@ -45,9 +97,9 @@ export function ApplyMargin(
bottom = bottom ?? 0;
top = top ?? 0;
x = ApplyXMargin(x, left);
y += top;
y = ApplyXMargin(y, top);
width = ApplyWidthMargin(width, left, right);
height -= (bottom + top);
height = ApplyWidthMargin(height, top, bottom);
return { x, y, width, height };
}
@ -64,9 +116,9 @@ export function RemoveMargin(
bottom = bottom ?? 0;
top = top ?? 0;
x = RemoveXMargin(x, left);
y -= top;
y = RemoveXMargin(y, top);
width = RemoveWidthMargin(width, left, right);
height += (bottom + top);
height = RemoveWidthMargin(height, top, bottom);
return { x, y, width, height };
}

View file

@ -138,7 +138,7 @@ const GetSVGLayoutConfiguration = () => {
{
Type: 'Montant',
Width: 10,
XPositionReference: 1,
PositionReference: 1,
MarkPositionToDimensionBorrower: true,
Style: {
fillOpacity: 0,
@ -150,7 +150,7 @@ const GetSVGLayoutConfiguration = () => {
{
Type: 'Dilatation',
Width: 4,
XPositionReference: 1,
PositionReference: 1,
MarkPositionToDimensionBorrower: true,
Style: {
fillOpacity: 0,
@ -193,7 +193,7 @@ const GetSVGLayoutConfiguration = () => {
Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg'
},
Name: 'Poteau structure',
XPositionReference: 1
PositionReference: 1
},
{
Width: 32,
@ -205,7 +205,7 @@ const GetSVGLayoutConfiguration = () => {
Url: 'https://e7.pngegg.com/pngimages/647/127/png-clipart-svg-working-group-information-world-wide-web-internet-structure.png'
},
Name: 'Joint de structure',
XPositionReference: 0
PositionReference: 0
}
],
Categories: [

View file

@ -69,11 +69,24 @@ const GetSVGLayoutConfiguration = () => {
AvailableContainers: [
{
Type: 'Chassis',
DisplayedText: 'Chassis?',
Whitelist: ["Trou"],
MaxWidth: 500,
MinWidth: 200,
Width: 200,
DisplayedText: 'Chassis horizontal',
MaxHeight: 200,
DefaultChildType: 'Trou',
Style: {
fillOpacity: 1,
strokeWidth: 2,
stroke: 'red',
fill: '#d3c9b7',
},
ShowSelfDimensions: true,
IsDimensionBorrower: true,
Category: "Stuff"
},
{
Type: 'ChassisVertical',
Orientation: 1,
DisplayedText: 'Chassis vertical',
MaxWidth: 200,
DefaultChildType: 'Trou',
Style: {
fillOpacity: 1,
@ -143,7 +156,7 @@ const GetSVGLayoutConfiguration = () => {
{
Type: 'Montant',
Width: 10,
XPositionReference: 1,
PositionReference: 4,
MarkPositionToDimensionBorrower: true,
Style: {
fillOpacity: 0,
@ -152,10 +165,23 @@ const GetSVGLayoutConfiguration = () => {
fill: '#713f12',
}
},
{
Type: 'Traverse',
Height: 10,
PositionReference: 4,
Orientation: 1,
MarkPositionToDimensionBorrower: true,
Style: {
fillOpacity: 0,
strokeWidth: 2,
stroke: '#6517fa',
fill: '#6517fa',
}
},
{
Type: 'Dilatation',
Width: 4,
XPositionReference: 1,
PositionReference: 1,
MarkPositionToDimensionBorrower: true,
Style: {
fillOpacity: 0,
@ -198,7 +224,7 @@ const GetSVGLayoutConfiguration = () => {
Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg'
},
Name: 'Poteau structure',
XPositionReference: 1
PositionReference: 1
},
{
Width: 32,
@ -210,7 +236,7 @@ const GetSVGLayoutConfiguration = () => {
Url: 'https://e7.pngegg.com/pngimages/647/127/png-clipart-svg-working-group-information-world-wide-web-internet-structure.png'
},
Name: 'Joint de structure',
XPositionReference: 0
PositionReference: 0
}
],
Categories: [
@ -227,7 +253,13 @@ const GetSVGLayoutConfiguration = () => {
MainContainer: {
Type: 'main',
Width: 800,
Height: 200
Height: 800,
Orientation: 1,
Style: {
stroke: 'black',
strokeWidth: 2,
fillOpacity: 0
}
}
}
};