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", "request": "launch",
"name": "Launch Chrome against localhost", "name": "Launch Chrome against localhost",
"url": "http://localhost:5173", "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" "coverage": "vitest run coverage"
}, },
"dependencies": { "dependencies": {
"@heroicons/react": "^1.0.6", "@heroicons/react": "^2.0.11",
"interweave": "^13.0.0", "interweave": "^13.0.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-svg-pan-zoom": "^3.11.0", "react-svg-pan-zoom": "^3.11.0",
"react-window": "^1.8.7", "react-window": "^1.8.7",
"sweetalert2": "^11.4.28", "sweetalert2": "^11.4.34",
"sweetalert2-react-content": "^5.0.3" "sweetalert2-react-content": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/dom": "^8.16.1", "@testing-library/dom": "^8.18.1",
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.1", "@testing-library/user-event": "^14.4.3",
"@types/react": "^18.0.15", "@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@types/react-svg-pan-zoom": "^3.3.5", "@types/react-svg-pan-zoom": "^3.3.5",
"@types/react-window": "^1.8.5", "@types/react-window": "^1.8.5",
"@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.31.0", "@typescript-eslint/parser": "^5.38.1",
"@vitejs/plugin-react": "^2.0.0", "@vitejs/plugin-react": "^2.1.0",
"@vitest/ui": "^0.20.3", "@vitest/ui": "^0.20.3",
"autoprefixer": "^10.4.8", "autoprefixer": "^10.4.12",
"eslint": "^8.20.0", "eslint": "^8.24.0",
"eslint-config-standard": "^17.0.0", "eslint-config-standard": "^17.0.0",
"eslint-config-standard-with-typescript": "^22.0.0", "eslint-config-standard-with-typescript": "^22.0.0",
"eslint-plugin-import": "^2.26.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-only-warn": "^1.0.3",
"eslint-plugin-promise": "^6.0.0", "eslint-plugin-promise": "^6.0.1",
"eslint-plugin-react": "^7.30.1", "eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^20.0.0", "jsdom": "^20.0.0",
"postcss": "^8.4.14", "postcss": "^8.4.16",
"sass": "^1.54.0", "sass": "^1.55.0",
"tailwindcss": "^3.1.7", "tailwindcss": "^3.1.8",
"typescript": "^4.6.4", "typescript": "^4.8.3",
"vite": "^3.0.0", "vite": "^3.1.3",
"vitest": "^0.20.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 * as React from 'react';
import { ClockIcon, CubeIcon, LinkIcon, MailIcon } from '@heroicons/react/outline'; import { ClockIcon, CubeIcon, LinkIcon, EnvelopeIcon } from '@heroicons/react/24/outline';
import { ClockIcon as ClockIconS, CubeIcon as CubeIconS, LinkIcon as LinkIconS, MailIcon as MailIconS } from '@heroicons/react/solid'; import { ClockIcon as ClockIconS, CubeIcon as CubeIconS, LinkIcon as LinkIconS, EnvelopeIcon as EnvolopeIconS } from '@heroicons/react/24/solid';
import { BarIcon } from './BarIcon'; import { BarIcon } from './BarIcon';
interface IBarProps { interface IBarProps {
@ -56,8 +56,8 @@ export function Bar(props: IBarProps): JSX.Element {
onClick={() => props.toggleMessages()}> onClick={() => props.toggleMessages()}>
{ {
props.isMessagesOpen props.isMessagesOpen
? <MailIconS className='heroicon' /> ? <EnvolopeIconS className='heroicon' />
: <MailIcon className='heroicon' /> : <EnvelopeIcon className='heroicon' />
} }
</BarIcon> </BarIcon>
</div> </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 React, { useState } from 'react';
import { ICategory } from '../../Interfaces/ICategory'; import { ICategory } from '../../Interfaces/ICategory';
import { TruncateString } from '../../utils/stringtools'; 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 * as React from 'react';
import { PropertyType } from '../../Enums/PropertyType'; import { PropertyType } from '../../Enums/PropertyType';
import { XPositionReference } from '../../Enums/XPositionReference'; import { PositionReference } from '../../Enums/PositionReference';
import { IContainerProperties } from '../../Interfaces/IContainerProperties'; import { IContainerProperties } from '../../Interfaces/IContainerProperties';
import { ISymbolModel } from '../../Interfaces/ISymbolModel'; import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS } from '../../utils/default'; 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 { InputGroup } from '../InputGroup/InputGroup';
import { TextInputGroup } from '../InputGroup/TextInputGroup'; import { TextInputGroup } from '../InputGroup/TextInputGroup';
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons'; import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
import { Select } from '../Select/Select'; import { Select } from '../Select/Select';
import { ToggleButton, ToggleType } from '../ToggleButton/ToggleButton';
import { Orientation } from '../../Interfaces/Orientation';
interface IContainerFormProps { interface IContainerFormProps {
properties: IContainerProperties properties: IContainerProperties
@ -37,7 +39,7 @@ function GetCSSInputs(properties: IContainerProperties,
export function ContainerForm(props: IContainerFormProps): JSX.Element { export function ContainerForm(props: IContainerFormProps): JSX.Element {
return ( return (
<div className='grid grid-cols-2 gap-y-4 items-center'> <div className='grid grid-cols-2 gap-y-6 items-center'>
<InputGroup <InputGroup
labelText='Name' labelText='Name'
inputKey='id' inputKey='id'
@ -71,6 +73,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='string' type='string'
value={props.properties.displayedText?.toString()} value={props.properties.displayedText?.toString()}
onChange={(value) => props.onChange('displayedText', value)} /> onChange={(value) => props.onChange('displayedText', value)} />
<OrientationSelector {...props} />
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-x`} id={`${props.properties.id}-x`}
labelText='x' labelText='x'
@ -79,14 +82,18 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName='' inputClassName=''
type='number' type='number'
isDisabled={props.properties.linkedSymbolId !== ''} 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( onChange={(value) => props.onChange(
'x', 'x',
ApplyXMargin( ApplyXMargin(
RestoreX( RestoreX(
Number(value), Number(value),
props.properties.width, props.properties.width,
props.properties.xPositionReference props.properties.positionReference
), ),
props.properties.margin.left props.properties.margin.left
) )
@ -98,11 +105,25 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='number' type='number'
value={(props.properties.y - (props.properties.margin?.top ?? 0)).toString()} value={TransformY(
onChange={(value) => props.onChange('y', Number(value) + (props.properties.margin?.top ?? 0))} /> 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 <TextInputGroup
id={`${props.properties.id}-minWidth`} id={`${props.properties.id}-minWidth`}
labelText='Minimum width' labelText='Minimum Width'
inputKey='minWidth' inputKey='minWidth'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
@ -112,7 +133,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
onChange={(value) => props.onChange('minWidth', Number(value))} /> onChange={(value) => props.onChange('minWidth', Number(value))} />
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-maxWidth`} id={`${props.properties.id}-maxWidth`}
labelText='Maximum width' labelText='Maximum Width'
inputKey='maxWidth' inputKey='maxWidth'
labelClassName='' labelClassName=''
inputClassName='' 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()} 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))} onChange={(value) => props.onChange('width', ApplyWidthMargin(Number(value), props.properties.margin.left, props.properties.margin.right))}
isDisabled={props.properties.isFlex} /> 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 <TextInputGroup
id={`${props.properties.id}-height`} id={`${props.properties.id}-height`}
labelText='Height' labelText='Height'
@ -139,9 +180,12 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='number' type='number'
min={0} min={props.properties.minHeight}
value={(props.properties.height + (props.properties.margin?.top ?? 0) + (props.properties.margin?.bottom ?? 0)).toString()} max={props.properties.maxHeight}
onChange={(value) => props.onChange('height', Number(value) - (props.properties.margin?.top ?? 0) - (props.properties.margin?.bottom ?? 0))} /> 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 <TextInputGroup
id={`${props.properties.id}-ml`} id={`${props.properties.id}-ml`}
labelText='Margin left' labelText='Margin left'
@ -182,60 +226,33 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
min={0} min={0}
value={(props.properties.margin.right ?? 0).toString()} value={(props.properties.margin.right ?? 0).toString()}
onChange={(value) => props.onChange('right', Number(value), PropertyType.Margin)} /> onChange={(value) => props.onChange('right', Number(value), PropertyType.Margin)} />
<InputGroup <ToggleButton
labelText='Flex' labelText='Flex'
inputKey='isFlex' inputKey='isFlex'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='checkbox' type={ToggleType.Full}
checked={props.properties.isFlex} checked={props.properties.isFlex}
onChange={(event) => props.onChange('isFlex', event.target.checked)} /> onChange={(event) => props.onChange('isFlex', event.target.checked)}
<InputGroup />
<ToggleButton
labelText='Anchor' labelText='Anchor'
inputKey='isAnchor' inputKey='isAnchor'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='checkbox' type={ToggleType.Full}
checked={props.properties.isAnchor} checked={props.properties.isAnchor}
onChange={(event) => props.onChange('isAnchor', event.target.checked)} /> onChange={(event) => props.onChange('isAnchor', event.target.checked)} />
<RadioGroupButtons <AlignmentSelector
name='XPositionReference' {...props}
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))} />
<Select <Select
inputKey='linkedSymbolId' inputKey='linkedSymbolId'
labelText='Align with symbol' labelText='Align with symbol'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
inputs={[...props.symbols.values()].map(symbol => ({ inputs={[...props.symbols.values()].map(symbol => ({
key: symbol.id,
text: symbol.id, text: symbol.id,
value: symbol.id value: symbol.id
}))} }))}
@ -244,43 +261,43 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
{GetCSSInputs(props.properties, props.onChange)} {GetCSSInputs(props.properties, props.onChange)}
{ {
SHOW_SELF_DIMENSIONS && SHOW_SELF_DIMENSIONS &&
<InputGroup <ToggleButton
labelText='Show dimension' labelText='Show dimension'
inputKey='showSelfDimensions' inputKey='showSelfDimensions'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='checkbox' type={ToggleType.Full}
checked={props.properties.showSelfDimensions} checked={props.properties.showSelfDimensions}
onChange={(event) => props.onChange('showSelfDimensions', event.target.checked)} /> onChange={(event) => props.onChange('showSelfDimensions', event.target.checked)} />
} }
{ {
SHOW_CHILDREN_DIMENSIONS && SHOW_CHILDREN_DIMENSIONS &&
<InputGroup <ToggleButton
labelText='Show overall dimension of its children' labelText='Show overall dimension of its children'
inputKey='showChildrenDimensions' inputKey='showChildrenDimensions'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='checkbox' type={ToggleType.Full}
checked={props.properties.showChildrenDimensions} checked={props.properties.showChildrenDimensions}
onChange={(event) => props.onChange('showChildrenDimensions', event.target.checked)} /> onChange={(event) => props.onChange('showChildrenDimensions', event.target.checked)} />
} }
{ {
SHOW_BORROWER_DIMENSIONS && SHOW_BORROWER_DIMENSIONS &&
<> <>
<InputGroup <ToggleButton
labelText='Mark the position' labelText='Mark the position'
inputKey='markPositionToDimensionBorrower' inputKey='markPositionToDimensionBorrower'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='checkbox' type={ToggleType.Full}
checked={props.properties.markPositionToDimensionBorrower} checked={props.properties.markPositionToDimensionBorrower}
onChange={(event) => props.onChange('markPositionToDimensionBorrower', event.target.checked)} /> onChange={(event) => props.onChange('markPositionToDimensionBorrower', event.target.checked)} />
<InputGroup <ToggleButton
labelText='Show dimension with marked children' labelText='Show dimension with marked children'
inputKey='isDimensionBorrower' inputKey='isDimensionBorrower'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='checkbox' type={ToggleType.Full}
checked={props.properties.isDimensionBorrower} checked={props.properties.isDimensionBorrower}
onChange={(event) => props.onChange('isDimensionBorrower', event.target.checked)} /> onChange={(event) => props.onChange('isDimensionBorrower', event.target.checked)} />
</> </>
@ -288,3 +305,135 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
</div> </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 { fireEvent, render, screen } from '@testing-library/react';
import * as React from 'react'; import * as React from 'react';
import { expect, describe, it, vi } from 'vitest'; import { expect, describe, it, vi } from 'vitest';
import { XPositionReference } from '../../Enums/XPositionReference'; import { PositionReference } from '../../Enums/PositionReference';
import { IContainerProperties } from '../../Interfaces/IContainerProperties'; import { IContainerProperties } from '../../Interfaces/IContainerProperties';
import { Orientation } from '../../Interfaces/Orientation';
import { Properties } from './ContainerProperties'; import { Properties } from './ContainerProperties';
describe.concurrent('Properties', () => { describe.concurrent('Properties', () => {
@ -26,14 +27,17 @@ describe.concurrent('Properties', () => {
parentId: 'parentId', parentId: 'parentId',
linkedSymbolId: '', linkedSymbolId: '',
displayedText: 'stuff', displayedText: 'stuff',
orientation: Orientation.Horizontal,
x: 1, x: 1,
y: 1, y: 1,
width: 1, width: 1,
height: 1, height: 1,
minWidth: 1, minWidth: 1,
maxWidth: Infinity, maxWidth: Infinity,
minHeight: 1,
maxHeight: Infinity,
margin: {}, margin: {},
xPositionReference: XPositionReference.Left, positionReference: PositionReference.TopLeft,
isFlex: false, isFlex: false,
isAnchor: false, isAnchor: false,
warning: '', warning: '',

View file

@ -9,6 +9,7 @@ import { IContainerModel, ContainerModel } from '../../../Interfaces/IContainerM
import { IHistoryState } from '../../../Interfaces/IHistoryState'; import { IHistoryState } from '../../../Interfaces/IHistoryState';
import { IPattern, GetPattern, ContainerOrPattern } from '../../../Interfaces/IPattern'; import { IPattern, GetPattern, ContainerOrPattern } from '../../../Interfaces/IPattern';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel'; import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { Orientation } from '../../../Interfaces/Orientation';
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_MAX_DEPTH, DEFAULTCHILDTYPE_ALLOW_CYCLIC } from '../../../utils/default'; import { GetDefaultContainerProps, DEFAULTCHILDTYPE_MAX_DEPTH, DEFAULTCHILDTYPE_ALLOW_CYCLIC } from '../../../utils/default';
import { FindContainerById } from '../../../utils/itertools'; import { FindContainerById } from '../../../utils/itertools';
import { ApplyMargin } from '../../../utils/svg'; import { ApplyMargin } from '../../../utils/svg';
@ -137,12 +138,12 @@ function AddNewContainerToParent(
let x = containerConfig.X ?? 0; let x = containerConfig.X ?? 0;
let y = containerConfig.Y ?? 0; let y = containerConfig.Y ?? 0;
let width = containerConfig.Width ?? containerConfig.MaxWidth ?? containerConfig.MinWidth ?? parentClone.properties.width; 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)); ({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right));
// Apply an add method (append or insert/replace) // 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 // Set the counter of the object type in order to assign an unique id
UpdateCounters(newCounters, type); UpdateCounters(newCounters, type);
@ -400,8 +401,9 @@ function ApplyAddMethod(
index: number, index: number,
containerConfig: IAvailableContainer, containerConfig: IAvailableContainer,
parent: IContainerModel, parent: IContainerModel,
x: number x: number,
): number { y: number
): { x: number, y: number } {
if (index > 0 && ( if (index > 0 && (
containerConfig.AddMethod === undefined || containerConfig.AddMethod === undefined ||
containerConfig.AddMethod === null || containerConfig.AddMethod === null ||
@ -412,8 +414,13 @@ function ApplyAddMethod(
.at(index - 1); .at(index - 1);
if (lastChild !== undefined) { 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 { IHistoryState } from '../../../Interfaces/IHistoryState';
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel'; import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
import { FindContainerById, MakeIterator } from '../../../utils/itertools'; import { FindContainerById, MakeDFSIterator } from '../../../utils/itertools';
import { GetCurrentHistory } from '../Editor'; import { GetCurrentHistory } from '../Editor';
import { ApplyBehaviors, ApplyBehaviorsOnSiblings, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors'; import { ApplyBehaviors, ApplyBehaviorsOnSiblings, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel'; import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import Swal from 'sweetalert2'; import Swal from 'sweetalert2';
import { PropertyType } from '../../../Enums/PropertyType'; import { PropertyType } from '../../../Enums/PropertyType';
import { TransformX } from '../../../utils/svg'; import { TransformX, TransformY } from '../../../utils/svg';
import { Orientation } from '../../../Interfaces/Orientation';
/** /**
* Select a container * Select a container
@ -133,7 +134,7 @@ function GetSelectedContainerOnDelete(
* @param container Container to unlink * @param container Container to unlink
*/ */
function UnlinkContainerFromSymbols(symbols: Map<string, ISymbolModel>, container: IContainerModel): void { function UnlinkContainerFromSymbols(symbols: Map<string, ISymbolModel>, container: IContainerModel): void {
const it = MakeIterator(container); const it = MakeDFSIterator(container);
for (const child of it) { for (const child of it) {
const symbol = symbols.get(child.properties.linkedSymbolId); const symbol = symbols.get(child.properties.linkedSymbolId);
if (symbol === undefined) { if (symbol === undefined) {
@ -194,11 +195,34 @@ export function SortChildren(parentClone: IContainerModel | null | undefined): v
if (parentClone === null || parentClone === undefined) { if (parentClone === null || parentClone === undefined) {
return; return;
} }
const isHorizontal = parentClone.properties.orientation === Orientation.Horizontal;
const children = parentClone.children; const children = parentClone.children;
if (!isHorizontal) {
parentClone.children.sort( parentClone.children.sort(
(a, b) => { (a, b) => {
const xA = TransformX(a.properties.x, a.properties.width, a.properties.xPositionReference); const yA = TransformY(a.properties.y, a.properties.height, a.properties.positionReference);
const xB = TransformX(b.properties.x, b.properties.width, b.properties.xPositionReference); 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.positionReference);
const xB = TransformX(b.properties.x, b.properties.width, b.properties.positionReference);
if (xA < xB) { if (xA < xB) {
return -1; return -1;
} }

View file

@ -14,6 +14,7 @@
*/ */
import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Interfaces/Orientation';
import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors'; import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
/** /**
@ -21,17 +22,16 @@ import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
* Apply the following modification to the overlapping rigid body container : * Apply the following modification to the overlapping rigid body container :
* @param container Container to impose its position * @param container Container to impose its position
*/ */
export function ApplyAnchor(container: IContainerModel): IContainerModel { export function ApplyAnchor(container: IContainerModel, parent: IContainerModel): IContainerModel {
if (container.parent === undefined || const rigidBodies = parent.children.filter(
container.parent === null) {
return container;
}
const rigidBodies = container.parent.children.filter(
child => !child.properties.isAnchor 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) { for (const overlappingContainer of overlappingContainers) {
ConstraintBodyInsideUnallocatedWidth(overlappingContainer); ConstraintBodyInsideUnallocatedWidth(overlappingContainer);
} }
@ -44,7 +44,7 @@ export function ApplyAnchor(container: IContainerModel): IContainerModel {
* @param containers A list of containers * @param containers A list of containers
* @returns A list of overlapping containers * @returns A list of overlapping containers
*/ */
export function GetOverlappingContainers( export function GetHorizontallyOverlappingContainers(
container: IContainerModel, container: IContainerModel,
containers: IContainerModel[] containers: IContainerModel[]
): IContainerModel[] { ): IContainerModel[] {
@ -68,3 +68,63 @@ export function GetOverlappingContainers(
} }
return overlappingContainers; 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); ApplySymbol(container, symbol);
} }
if (container.parent !== undefined && container.parent !== null) {
const parent = container.parent;
if (container.properties.isAnchor) { if (container.properties.isAnchor) {
ApplyAnchor(container); ApplyAnchor(container, parent);
} }
if (ENABLE_SWAP) { if (ENABLE_SWAP) {
ApplySwap(container); ApplySwap(container, parent);
} }
Flex(container); Flex(container, parent);
if (ENABLE_RIGID) { if (ENABLE_RIGID) {
ApplyRigidBody(container); ApplyRigidBody(container, parent);
}
} }
if (APPLY_BEHAVIORS_ON_CHILDREN) { if (APPLY_BEHAVIORS_ON_CHILDREN) {
@ -68,13 +72,9 @@ export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel,
newContainer.parent.children newContainer.parent.children
.forEach((container: IContainerModel) => { .forEach((container: IContainerModel) => {
if (container.parent != null) { if (container.parent != null) {
const overlappingContainers = GetOverlappingContainers(container, container.parent.children); UpdateWarning(container, container.parent);
if (overlappingContainers.length > 0) {
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
} else {
container.properties.warning = '';
}
} }
if (container === newContainer) { if (container === newContainer) {
return; return;
} }
@ -85,7 +85,6 @@ export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel,
}); });
} }
/** /**
* Iterate over the siblings of newContainer and apply the behaviors * Iterate over the siblings of newContainer and apply the behaviors
* @param newContainer * @param newContainer
@ -102,13 +101,9 @@ export function ApplyBehaviorsOnSiblings(newContainer: IContainerModel, symbols:
ApplyBehaviors(container, symbols); ApplyBehaviors(container, symbols);
if (container.parent != null) { if (container.parent != null) {
const overlappingContainers = GetOverlappingContainers(container, container.parent.children); UpdateWarning(container, container.parent);
if (overlappingContainers.length > 0) {
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
} else {
container.properties.warning = '';
}
} }
if (container === newContainer) { if (container === newContainer) {
return; 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 { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Interfaces/Orientation';
import { Simplex } from '../../../utils/simplex'; import { Simplex } from '../../../utils/simplex';
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg'; import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
@ -10,27 +11,111 @@ interface IFlexibleGroup {
/** /**
* Flex the container and its siblings (mutate) * Flex the container and its siblings (mutate)
* @param container Container to flex
* @returns Flexed container * @returns Flexed container
*/ */
export function Flex(container: IContainerModel): void { export function Flex(container: IContainerModel, parent: IContainerModel): void {
if (container.parent === null || container.parent === undefined) { 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; 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) { 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 * Apply flex to the group
* @param flexibleGroup Group that contains a list of flexible containers * @param flexibleGroup Group that contains a list of flexible containers
* @returns * @returns
*/ */
function FlexGroup(flexibleGroup: IFlexibleGroup): void { function FlexGroupHorizontally(flexibleGroup: IFlexibleGroup): void {
const children = flexibleGroup.group; const children = flexibleGroup.group;
const { const {
flexibleContainers, 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( function SeparateFlexibleContainers(
containers: IContainerModel[] containers: IContainerModel[]
): { flexibleContainers: IContainerModel[], nonFlexibleContainers: IContainerModel[] } { ): { flexibleContainers: IContainerModel[], nonFlexibleContainers: IContainerModel[] } {
@ -114,41 +270,3 @@ function SeparateFlexibleContainers(
nonFlexibleContainers 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 { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Interfaces/Orientation';
import { ReversePairwise } from '../../../utils/itertools'; import { ReversePairwise } from '../../../utils/itertools';
import { Flex } from './FlexBehaviors'; import { Flex } from './FlexBehaviors';
@ -7,17 +8,27 @@ import { Flex } from './FlexBehaviors';
* @param container * @param container
* @returns * @returns
*/ */
export function PushContainers(container: IContainerModel): IContainerModel { export function ApplyPush(container: IContainerModel, parent: IContainerModel): IContainerModel {
if (container.parent === null) { if (parent.children.length <= 1) {
return container; return container;
} }
if (container.parent.children.length <= 1) { const children = parent.children;
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
if (isHorizontal) {
PushContainersHorizontally(container, children);
} else {
PushContainersVertically(container, children);
}
Flex(container, parent);
return container; return container;
} }
const prevIndex = container.parent.children.length - 2; export function PushContainersHorizontally(container: IContainerModel, children: IContainerModel[]): IContainerModel {
const prev: IContainerModel = container.parent.children[prevIndex]; const prevIndex = children.length - 2;
const prev: IContainerModel = children[prevIndex];
const isOverlapping = prev.properties.x + prev.properties.width > container.properties.x; const isOverlapping = prev.properties.x + prev.properties.width > container.properties.x;
if (!isOverlapping) { if (!isOverlapping) {
return container; return container;
@ -32,7 +43,7 @@ export function PushContainers(container: IContainerModel): IContainerModel {
// FIXME: A fix was applied using toFixed(2). // FIXME: A fix was applied using toFixed(2).
// FIXME: A coverture check must be done to ensure that all scenarios are covered // 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) { for (const { cur, next } of it) {
const hasSpaceBetween = next.properties.x + next.properties.width < cur.properties.x; const hasSpaceBetween = next.properties.x + next.properties.width < cur.properties.x;
@ -48,9 +59,9 @@ export function PushContainers(container: IContainerModel): IContainerModel {
break; break;
} }
const indexLastContainer = container.parent.children.indexOf(lastContainer); const indexLastContainer = children.indexOf(lastContainer);
for (let i = indexLastContainer; i <= container.parent.children.length - 2; i++) { for (let i = indexLastContainer; i <= children.length - 2; i++) {
const sibling = container.parent.children[i]; const sibling = children[i];
sibling.properties.x -= space; sibling.properties.x -= space;
} }
} }
@ -58,16 +69,71 @@ export function PushContainers(container: IContainerModel): IContainerModel {
const hasNoSpaceBetween = lastContainer === null; const hasNoSpaceBetween = lastContainer === null;
if (hasNoSpaceBetween) { if (hasNoSpaceBetween) {
// test gap between the left of the parent and the first container // 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) { if (space > 0) {
for (let i = 0; i <= container.parent.children.length - 2; i++) { for (let i = 0; i <= children.length - 2; i++) {
const sibling = container.parent.children[i]; const sibling = children[i];
sibling.properties.x -= space; sibling.properties.x -= space;
} }
return container; 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; return container;
} }

View file

@ -8,6 +8,7 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { ISizePointer } from '../../../Interfaces/ISizePointer'; import { ISizePointer } from '../../../Interfaces/ISizePointer';
import { Orientation } from '../../../Interfaces/Orientation';
import { ENABLE_HARD_RIGID } from '../../../utils/default'; import { ENABLE_HARD_RIGID } from '../../../utils/default';
/** /**
@ -20,9 +21,10 @@ import { ENABLE_HARD_RIGID } from '../../../utils/default';
* @returns A rigid body container * @returns A rigid body container
*/ */
export function ApplyRigidBody( export function ApplyRigidBody(
container: IContainerModel container: IContainerModel,
parent: IContainerModel
): IContainerModel { ): IContainerModel {
container = ConstraintBodyInsideParent(container); container = ConstraintBodyInsideParent(container, parent);
if (ENABLE_HARD_RIGID) { if (ENABLE_HARD_RIGID) {
container = ConstraintBodyInsideUnallocatedWidth(container); container = ConstraintBodyInsideUnallocatedWidth(container);
@ -40,13 +42,10 @@ export function ApplyRigidBody(
* @returns Updated container * @returns Updated container
*/ */
function ConstraintBodyInsideParent( function ConstraintBodyInsideParent(
container: IContainerModel container: IContainerModel,
parent: IContainerModel
): IContainerModel { ): IContainerModel {
if (container.parent === null || container.parent === undefined) { const parentProperties = parent.properties;
return container;
}
const parentProperties = container.parent.properties;
const parentWidth = parentProperties.width; const parentWidth = parentProperties.width;
const parentHeight = parentProperties.height; const parentHeight = parentProperties.height;
@ -125,9 +124,15 @@ export function ConstraintBodyInsideUnallocatedWidth(
} }
// Get the available spaces of the parent // Get the available spaces of the parent
const availableWidths = GetAvailableWidths(container.parent, container); const isHorizontal =
const containerX = container.properties.x; container.parent.properties.orientation === Orientation.Horizontal;
const containerWidth = container.properties.width; const availableWidths = GetAvailableWidths(
0,
container.parent.properties.width,
container.parent.children,
container,
isHorizontal
);
// Check if there is still some space // Check if there is still some space
if (availableWidths.length === 0) { 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; const middle = containerX + containerWidth / 2;
// Sort the available width to find the space with the closest position // Sort the available width to find the space with the closest position
availableWidths.sort( availableWidths.sort(
@ -153,42 +255,6 @@ export function ConstraintBodyInsideUnallocatedWidth(
return Math.abs(compared1X - middle) - Math.abs(compared2X - middle); 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) * @returns {ISizePointer[]} Array of unallocated widths (x=position of the unallocated space, width=size of the allocated space)
*/ */
function GetAvailableWidths( function GetAvailableWidths(
container: IContainerModel, x: number,
exception: IContainerModel width: number,
children: IContainerModel[],
exception: IContainerModel,
isHorizontal: boolean
): ISizePointer[] { ): ISizePointer[] {
// Initialize the first size pointer // Initialize the first size pointer
// which takes full width of the available space // which takes full width of the available space
const x = 0;
const width = container.properties.width;
let unallocatedSpaces: ISizePointer[] = [{ x, width }]; let unallocatedSpaces: ISizePointer[] = [{ x, width }];
for (const child of container.children) { for (const child of children) {
if (unallocatedSpaces.length < 1) { if (unallocatedSpaces.length < 1) {
return unallocatedSpaces; return unallocatedSpaces;
} }
@ -231,8 +298,8 @@ function GetAvailableWidths(
if (child === exception) { if (child === exception) {
continue; continue;
} }
const childX = child.properties.x; const childX = isHorizontal ? child.properties.x : child.properties.y;
const childWidth = child.properties.width; const childWidth = isHorizontal ? child.properties.width : child.properties.height;
// get the space of the child that is inside the parent // get the space of the child that is inside the parent
let newUnallocatedSpace: ISizePointer[] = []; let newUnallocatedSpace: ISizePointer[] = [];

View file

@ -3,15 +3,23 @@
*/ */
import { IContainerModel } from '../../../Interfaces/IContainerModel'; 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 { export function ApplySwap(container: IContainerModel, parent: IContainerModel): void {
if (container.parent === null || container.parent === undefined) { const children = parent.children;
const isVertical = parent.properties.orientation === Orientation.Vertical;
if (isVertical) {
SwapVertically(container, children);
return; return;
} }
const children = container.parent.children; SwapHorizontally(container, children);
const overlappingContainers = GetOverlappingContainers(container, children); }
export function SwapHorizontally(container: IContainerModel, children: IContainerModel[]): void {
const overlappingContainers = GetHorizontallyOverlappingContainers(container, children);
if (overlappingContainers.length > 1 || overlappingContainers.length === 0) { if (overlappingContainers.length > 1 || overlappingContainers.length === 0) {
return; return;
@ -29,3 +37,23 @@ export function ApplySwap(container: IContainerModel): void {
const indexOverlapping = children.indexOf(overlappingContainer); const indexOverlapping = children.indexOf(overlappingContainer);
[children[indexContainer], children[indexOverlapping]] = [children[indexOverlapping], children[indexContainer]]; [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 { export function ApplySymbol(container: IContainerModel, symbol: ISymbolModel): IContainerModel {
container.properties.x = TransformX(symbol.x, symbol.width, symbol.config.XPositionReference); 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); const [x] = ApplyParentTransform(container.parent, container.properties.x, 0);
container.properties.x = x; container.properties.x = x;
return container; return container;

View file

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

View file

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

View file

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

View file

@ -17,13 +17,11 @@ interface IInputGroupProps {
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
} }
const className = 'input-group';
export function InputGroup(props: IInputGroupProps): JSX.Element { export function InputGroup(props: IInputGroupProps): JSX.Element {
return <> return <>
<label <label
key={props.labelKey} 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} htmlFor={props.inputKey}
> >
{props.labelText} {props.labelText}
@ -32,7 +30,7 @@ export function InputGroup(props: IInputGroupProps): JSX.Element {
<input <input
key={props.inputKey} key={props.inputKey}
id={props.inputKey} id={props.inputKey}
className={`${className} ${props.inputClassName}`} className={`input-group ${props.inputClassName}`}
type={props.type} type={props.type}
value={props.value} value={props.value}
defaultValue={props.defaultValue} defaultValue={props.defaultValue}

View file

@ -42,7 +42,7 @@ export function TextInputGroup(props: ITextInputGroupProps): JSX.Element {
return <> return <>
<label <label
key={props.labelKey} 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} htmlFor={props.inputKey}
> >
{props.labelText} {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 * as React from 'react';
import { FixedSizeList as List } from 'react-window'; import { FixedSizeList as List } from 'react-window';
import { MessageType } from '../../Enums/MessageType'; import { MessageType } from '../../Enums/MessageType';

View file

@ -8,24 +8,43 @@ interface IRadioGroupButtonsProps {
inputClassName: string inputClassName: string
labelText: string labelText: string
inputGroups: IInputGroup[] inputGroups: IInputGroup[]
colQty: number
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void 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 { export function RadioGroupButtons(props: IRadioGroupButtonsProps): JSX.Element {
let inputGroups; let inputGroups;
if (props.value !== undefined) { if (props.value !== undefined) {
// dynamic // dynamic
inputGroups = props.inputGroups.map((inputGroup) => ( inputGroups = props.inputGroups.map((inputGroup) => (
<div key={inputGroup.value}> <div key={inputGroup.key}>
<input <input
id={inputGroup.value} key={inputGroup.key}
id={inputGroup.key}
type='radio' type='radio'
name={props.name} name={props.name}
className={`peer m-2 ${props.inputClassName}`} className={`peer m-2 ${props.inputClassName}`}
value={inputGroup.value} value={inputGroup.value}
checked={props.value === inputGroup.value} checked={props.value === inputGroup.value}
onChange={props.onChange} /> 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} {inputGroup.text}
</label> </label>
</div> </div>
@ -34,28 +53,31 @@ export function RadioGroupButtons(props: IRadioGroupButtonsProps): JSX.Element {
} else { } else {
// static // static
inputGroups = props.inputGroups.map((inputGroup) => ( inputGroups = props.inputGroups.map((inputGroup) => (
<div key={inputGroup.value}> <div key={inputGroup.key}>
<input <input
id={inputGroup.value} key={inputGroup.key}
id={inputGroup.key}
type='radio' type='radio'
name={props.name} name={props.name}
className={`peer m-2 ${props.inputClassName}`} className={`peer m-2 ${props.inputClassName}`}
value={inputGroup.value} value={inputGroup.value}
defaultChecked={props.defaultValue === 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} {inputGroup.text}
</label> </label>
</div> </div>
)); ));
} }
const gridColsClass = GRID_COLS[props.colQty];
return ( return (
<> <>
<label className='mt-4 text-xs font-medium text-gray-800'> <label className='text-xs font-medium text-gray-800'>
{props.labelText} {props.labelText}
</label> </label>
<div id='XPositionReference' <div id='XPositionReference'
className='flex flex-col' className={`grid ${gridColsClass}`}
> >
{inputGroups} {inputGroups}
</div> </div>

View file

@ -1,15 +1,13 @@
import * as React from 'react'; import * as React from 'react';
import { Interweave, Node } from 'interweave'; import { Interweave, Node } from 'interweave';
import { IContainerModel } from '../../../Interfaces/IContainerModel'; 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 { IContainerProperties } from '../../../Interfaces/IContainerProperties';
import { TransformX } from '../../../utils/svg';
import { Camelize } from '../../../utils/stringtools'; import { Camelize } from '../../../utils/stringtools';
import { SHOW_TEXT } from '../../../utils/default';
interface IContainerProps { interface IContainerProps {
model: IContainerModel model: IContainerModel
depth: number
scale: number scale: number
} }
@ -22,6 +20,7 @@ export function Container(props: IContainerProps): JSX.Element {
child => <Container child => <Container
key={`container-${child.properties.id}`} key={`container-${child.properties.id}`}
model={child} model={child}
depth={props.depth + 1}
scale={props.scale} scale={props.scale}
/>); />);
@ -57,71 +56,6 @@ export function Container(props: IContainerProps): JSX.Element {
style={style} style={style}
> >
</rect>); </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 ( return (
<g <g
@ -129,19 +63,6 @@ export function Container(props: IContainerProps): JSX.Element {
transform={transform} transform={transform}
key={`container-${props.model.properties.id}`} 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} {svg}
{SHOW_TEXT {SHOW_TEXT
? <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 { function CreateReactCustomSVG(customSVG: string, props: IContainerProperties): React.ReactNode {
return <Interweave return <Interweave
tagName='g' tagName='g'

View file

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

View file

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

View file

@ -1,23 +1,313 @@
import * as React from 'react'; import * as React from 'react';
import { ContainerModel } from '../../../Interfaces/IContainerModel'; import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN } from '../../../utils/default'; import { DIMENSION_MARGIN, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS } from '../../../utils/default';
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools'; import { MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
import { TransformX, TransformY } from '../../../utils/svg';
import { Dimension } from './Dimension'; import { Dimension } from './Dimension';
interface IDimensionLayerProps { interface IDimensionLayerProps {
roots: ContainerModel | ContainerModel[] | null root: ContainerModel
scale: number
} }
function GetDimensionsNodes(root: ContainerModel): React.ReactNode[] { const MODULE_STROKE_WIDTH = 1;
const it = MakeBFSIterator(root);
function Dimensions({ root, scale }: IDimensionLayerProps): React.ReactNode[] {
const it = MakeRecursionDFSIterator(root, 0, [0, 0]);
const dimensions: React.ReactNode[] = []; const dimensions: React.ReactNode[] = [];
for (const { container, depth } of it) { 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
);
}
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 width = container.properties.width;
const id = `dim-${container.properties.id}`; const id = `dim-${container.properties.id}`;
const xStart = GetAbsolutePosition(container)[0]; const xStart = container.properties.x + currentTransform[0];
const xEnd = xStart + width; const xEnd = xStart + width;
const y = (container.properties.y + container.properties.height) + (DIMENSION_MARGIN * (depth + 1)); const y = topDim;
const strokeWidth = 1;
const text = width const text = width
.toFixed(0) .toFixed(0)
.toString(); .toString();
@ -29,12 +319,11 @@ function GetDimensionsNodes(root: ContainerModel): React.ReactNode[] {
yStart={y} yStart={y}
xEnd={xEnd} xEnd={xEnd}
yEnd={y} yEnd={y}
strokeWidth={strokeWidth} strokeWidth={MODULE_STROKE_WIDTH}
text={text} /> text={text}
scale={scale} />
); );
} }
return dimensions;
}
/** /**
* A layer containing all dimension * A layer containing all dimension
@ -42,17 +331,9 @@ function GetDimensionsNodes(root: ContainerModel): React.ReactNode[] {
* @returns * @returns
*/ */
export function DimensionLayer(props: IDimensionLayerProps): JSX.Element { 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 ( return (
<g> <g>
{dimensions} { Dimensions(props) }
</g> </g>
); );
} }

View file

@ -9,11 +9,12 @@ import { DepthDimensionLayer } from './Elements/DepthDimensionLayer';
import { MAX_FRAMERATE, SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default'; import { MAX_FRAMERATE, SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default';
import { SymbolLayer } from './Elements/SymbolLayer'; import { SymbolLayer } from './Elements/SymbolLayer';
import { ISymbolModel } from '../../Interfaces/ISymbolModel'; import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { DimensionLayer } from './Elements/DimensionLayer';
interface ISVGProps { interface ISVGProps {
width: number width: number
height: number height: number
children: ContainerModel | ContainerModel[] | null children: ContainerModel
selected?: ContainerModel selected?: ContainerModel
symbols: Map<string, ISymbolModel> symbols: Map<string, ISymbolModel>
} }
@ -69,7 +70,7 @@ export function SVG(props: ISVGProps): JSX.Element {
// console.log(renderCounter.current / ((Date.now() - startTimer.current) / 1000)); // console.log(renderCounter.current / ((Date.now() - startTimer.current) / 1000));
UseSVGAutoResizer(setViewer); UseSVGAutoResizer(setViewer);
UseFitOnce(svgViewer); UseFitOnce(svgViewer, props.width, props.height);
const xmlns = '<http://www.w3.org/2000/svg>'; const xmlns = '<http://www.w3.org/2000/svg>';
const properties = { const properties = {
@ -79,20 +80,12 @@ export function SVG(props: ISVGProps): JSX.Element {
}; };
let children: React.ReactNode | React.ReactNode[] = []; 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 children = <Container
key={`container-${props.children.properties.id}`} key={`container-${props.children.properties.id}`}
model={props.children} model={props.children}
depth={0}
scale={scale} scale={scale}
/>; />;
}
return ( return (
<div id={ID} className='ml-16'> <div id={ID} className='ml-16'>
@ -123,7 +116,7 @@ export function SVG(props: ISVGProps): JSX.Element {
miniatureProps={{ miniatureProps={{
position: 'left', position: 'left',
background: '#616264', background: '#616264',
width: window.innerWidth - 12 - BAR_WIDTH, width: 120,
height: 120 height: 120
}} }}
> >
@ -132,6 +125,7 @@ export function SVG(props: ISVGProps): JSX.Element {
{SHOW_DIMENSIONS_PER_DEPTH {SHOW_DIMENSIONS_PER_DEPTH
? <DepthDimensionLayer scale={scale} roots={props.children} /> ? <DepthDimensionLayer scale={scale} roots={props.children} />
: null} : null}
<DimensionLayer scale={scale} root={props.children} />
<SymbolLayer scale={scale} symbols={props.symbols} /> <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 */} <Selector scale={scale} selected={props.selected} /> {/* leave this at the end so it can be removed during the svg export */}
</svg> </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(() => { React.useEffect(() => {
svgViewer?.current?.fitToViewer(); 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 <label
key={props.labelKey} 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} htmlFor={props.inputKey}
> >
{props.labelText} {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 * as React from 'react';
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer'; import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
import { ICategory } from '../../Interfaces/ICategory'; import { ICategory } from '../../Interfaces/ICategory';
@ -108,8 +108,8 @@ export function Sidebar(props: ISidebarProps): JSX.Element {
> >
{ {
hideDisabled hideDisabled
? <EyeOffIcon className='heroicon'></EyeOffIcon> ? <EyeSlashIcon className='heroicon' />
: <EyeIcon className='heroicon'></EyeIcon> : <EyeIcon className='heroicon' />
} }
</button> </button>
</div> </div>

View file

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

View file

@ -5,7 +5,7 @@ import { History } from '../History/History';
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer'; import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
import { IContainerModel } from '../../Interfaces/IContainerModel'; import { IContainerModel } from '../../Interfaces/IContainerModel';
import { IHistoryState } from '../../Interfaces/IHistoryState'; 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 { FloatingButton } from '../FloatingButton/FloatingButton';
import { Bar } from '../Bar/Bar'; import { Bar } from '../Bar/Bar';
import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol'; import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
@ -127,14 +127,14 @@ export function UI(props: IUIProps): JSX.Element {
title='Export as JSON' title='Export as JSON'
onClick={props.saveEditorAsJSON} onClick={props.saveEditorAsJSON}
> >
<UploadIcon className="heroicon text-white" /> <CameraIcon className="heroicon text-white" />
</button> </button>
<button type="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'} 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' title='Export as SVG'
onClick={props.saveEditorAsSVG} onClick={props.saveEditorAsSVG}
> >
<PhotographIcon className="heroicon text-white" /> <ArrowUpOnSquareIcon className="heroicon text-white" />
</button> </button>
</FloatingButton> </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 */ /* eslint-disable @typescript-eslint/naming-convention */
import React from 'react'; import React from 'react';
import { AddMethod } from '../Enums/AddMethod'; import { AddMethod } from '../Enums/AddMethod';
import { XPositionReference } from '../Enums/XPositionReference'; import { PositionReference } from '../Enums/PositionReference';
import { IAction } from './IAction'; import { IAction } from './IAction';
import { IMargin } from './IMargin'; import { IMargin } from './IMargin';
import { Orientation } from './Orientation';
/** Model of available container used in application configuration */ /** Model of available container used in application configuration */
export interface IAvailableContainer { export interface IAvailableContainer {
@ -16,6 +17,9 @@ export interface IAvailableContainer {
/** category */ /** category */
Category?: string Category?: string
/** orientation */
Orientation?: Orientation
/** horizontal offset */ /** horizontal offset */
X?: number X?: number
@ -30,8 +34,6 @@ export interface IAvailableContainer {
/** /**
* Minimum width (min=1) * Minimum width (min=1)
* Allows the container to set isRigidBody to false when it gets squeezed
* by an anchor
*/ */
MinWidth?: number MinWidth?: number
@ -40,6 +42,16 @@ export interface IAvailableContainer {
*/ */
MaxWidth?: number MaxWidth?: number
/**
* Minimum height (min=1)
*/
MinHeight?: number
/**
* Maximum height
*/
MaxHeight?: number
/** margin */ /** margin */
Margin?: IMargin Margin?: IMargin
@ -53,7 +65,7 @@ export interface IAvailableContainer {
AddMethod?: AddMethod AddMethod?: AddMethod
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */ /** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
XPositionReference?: XPositionReference PositionReference?: PositionReference
/** /**
* (optional) * (optional)

View file

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

View file

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

View file

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
export interface IInputGroup { export interface IInputGroup {
key: string
text: React.ReactNode text: React.ReactNode
value: string 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 { IAvailableContainer } from '../Interfaces/IAvailableContainer';
import { IAvailableSymbol } from '../Interfaces/IAvailableSymbol'; import { IAvailableSymbol } from '../Interfaces/IAvailableSymbol';
import { IConfiguration } from '../Interfaces/IConfiguration'; import { IConfiguration } from '../Interfaces/IConfiguration';
@ -6,6 +6,7 @@ import { ContainerModel, IContainerModel } from '../Interfaces/IContainerModel';
import { IContainerProperties } from '../Interfaces/IContainerProperties'; import { IContainerProperties } from '../Interfaces/IContainerProperties';
import { IEditorState } from '../Interfaces/IEditorState'; import { IEditorState } from '../Interfaces/IEditorState';
import { ISymbolModel } from '../Interfaces/ISymbolModel'; import { ISymbolModel } from '../Interfaces/ISymbolModel';
import { Orientation } from '../Interfaces/Orientation';
/// EDITOR DEFAULTS /// /// EDITOR DEFAULTS ///
@ -50,7 +51,7 @@ export const DEFAULTCHILDTYPE_MAX_DEPTH = 10;
/// DIMENSIONS DEFAULTS /// /// DIMENSIONS DEFAULTS ///
export const SHOW_SELF_DIMENSIONS = true; 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_BORROWER_DIMENSIONS = true;
export const SHOW_DIMENSIONS_PER_DEPTH = false; export const SHOW_DIMENSIONS_PER_DEPTH = false;
export const DIMENSION_MARGIN = 50; export const DIMENSION_MARGIN = 50;
@ -173,16 +174,19 @@ export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
parentId: '', parentId: '',
linkedSymbolId: '', linkedSymbolId: '',
displayedText: 'main', displayedText: 'main',
orientation: Orientation.Horizontal,
x: 0, x: 0,
y: 0, y: 0,
margin: {}, margin: {},
minWidth: 1, minWidth: 1,
maxWidth: Number.MAX_SAFE_INTEGER, maxWidth: Number.MAX_SAFE_INTEGER,
minHeight: 1,
maxHeight: Number.MAX_SAFE_INTEGER,
width: Number(DEFAULT_CONFIG.MainContainer.Width), width: Number(DEFAULT_CONFIG.MainContainer.Width),
height: Number(DEFAULT_CONFIG.MainContainer.Height), height: Number(DEFAULT_CONFIG.MainContainer.Height),
isAnchor: false, isAnchor: false,
isFlex: false, isFlex: false,
xPositionReference: XPositionReference.Left, positionReference: PositionReference.TopLeft,
hideChildrenInTreeview: false, hideChildrenInTreeview: false,
showChildrenDimensions: true, // TODO: put the dimension at the top (see pdf) showChildrenDimensions: true, // TODO: put the dimension at the top (see pdf)
showSelfDimensions: true, // TODO: put the dimension at the bottom (see pdf) showSelfDimensions: true, // TODO: put the dimension at the bottom (see pdf)
@ -213,22 +217,28 @@ export function GetDefaultContainerProps(type: string,
width: number, width: number,
height: number, height: number,
containerConfig: IAvailableContainer): IContainerProperties { containerConfig: IAvailableContainer): IContainerProperties {
const orientation = containerConfig.Orientation ?? Orientation.Horizontal;
const defaultIsFlex = (orientation === Orientation.Vertical && containerConfig.Height === undefined) ||
(orientation === Orientation.Horizontal && containerConfig.Width === undefined);
return ({ return ({
id: `${type}-${typeCount}`, id: `${type}-${typeCount}`,
type, type,
parentId: parent?.properties.id ?? '', parentId: parent?.properties.id ?? '',
linkedSymbolId: '', linkedSymbolId: '',
displayedText: `${containerConfig.DisplayedText ?? type}-${typeCount}`, displayedText: `${containerConfig.DisplayedText ?? type}-${typeCount}`,
orientation,
x, x,
y, y,
margin: containerConfig.Margin ?? {}, margin: containerConfig.Margin ?? {},
width, width,
height, height,
isAnchor: containerConfig.IsAnchor ?? false, isAnchor: containerConfig.IsAnchor ?? false,
isFlex: containerConfig.IsFlex ?? containerConfig.Width === undefined, isFlex: containerConfig.IsFlex ?? defaultIsFlex,
xPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left, positionReference: containerConfig.PositionReference ?? PositionReference.TopLeft,
minWidth: containerConfig.MinWidth ?? 1, minWidth: containerConfig.MinWidth ?? 1,
maxWidth: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER, maxWidth: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER,
minHeight: containerConfig.MinWidth ?? 1,
maxHeight: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER,
hideChildrenInTreeview: containerConfig.HideChildrenInTreeview ?? false, hideChildrenInTreeview: containerConfig.HideChildrenInTreeview ?? false,
showChildrenDimensions: containerConfig.ShowChildrenDimensions ?? false, showChildrenDimensions: containerConfig.ShowChildrenDimensions ?? false,
showSelfDimensions: containerConfig.ShowSelfDimensions ?? 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 * 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 queue: IContainerModel[] = [root];
const visited = new Set<IContainerModel>(queue); const visited = new Set<IContainerModel>(queue);
while (queue.length > 0) { while (queue.length > 0) {
@ -30,6 +30,11 @@ export interface ContainerAndDepth {
container: IContainerModel container: IContainerModel
depth: number depth: number
} }
export interface ContainerAndDepthAndTransform extends ContainerAndDepth {
currentTransform: [number, number]
}
/** /**
* Returns a Generator iterating of over the children depth-first * 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
* @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 { export function GetDepth(parent: IContainerModel): number {
let depth = 0; let depth = 0;
@ -80,6 +120,19 @@ export function GetAbsolutePosition(container: IContainerModel): [number, number
return CancelParentTransform(container.parent, x, y); 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 * Cancel the hierarchic transformations to the given x, y
* @param parent Parent of the container to remove its transform * @param parent Parent of the container to remove its transform
@ -93,34 +146,47 @@ export function CancelParentTransform(
y: number, y: number,
stop?: IContainerModel stop?: IContainerModel
): [number, number] { ): [number, number] {
let current = parent; if (parent === null) {
while (current !== stop && current != null) { return [x, y];
}
const it = MakeContainerLinkedListIterator(parent, stop);
for (const current of it) {
x += current.properties.x; x += current.properties.x;
y += current.properties.y; y += current.properties.y;
current = current.parent;
} }
return [x, y]; 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 parent Parent of the container to remove its transform
* @param x value to be restored * @param x value to be restored
* @param y 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] { export function ApplyParentTransform(
let current = parent; parent: IContainerModel | null,
while (current != 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; x -= current.properties.x;
y -= current.properties.y; y -= current.properties.y;
current = current.parent;
} }
return [x, y]; return [x, y];
} }
export function FindContainerById(root: IContainerModel, id: string): IContainerModel | undefined { export function FindContainerById(root: IContainerModel, id: string): IContainerModel | undefined {
const it = MakeIterator(root); const it = MakeDFSIterator(root);
for (const container of it) { for (const container of it) {
if (container.properties.id === id) { if (container.properties.id === id) {
return container; return container;

View file

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

View file

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

View file

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

View file

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