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:
parent
459e83a0c8
commit
18cbacaca1
45 changed files with 2112 additions and 1063 deletions
25
.vscode/launch.json
vendored
25
.vscode/launch.json
vendored
|
@ -9,7 +9,30 @@
|
|||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
"webRoot": "${workspaceFolder}",
|
||||
},
|
||||
{
|
||||
"type": "msedge",
|
||||
"request": "launch",
|
||||
"name": "Launch Edge against localhost",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Brave against localhost",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"runtimeExecutable": "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Thorium against localhost",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"runtimeExecutable": "/bin/thorium-browser"
|
||||
}
|
||||
]
|
||||
}
|
40
package.json
40
package.json
|
@ -13,44 +13,44 @@
|
|||
"coverage": "vitest run coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@heroicons/react": "^2.0.11",
|
||||
"interweave": "^13.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-svg-pan-zoom": "^3.11.0",
|
||||
"react-window": "^1.8.7",
|
||||
"sweetalert2": "^11.4.28",
|
||||
"sweetalert2": "^11.4.34",
|
||||
"sweetalert2-react-content": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "^8.16.1",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^14.4.1",
|
||||
"@types/react": "^18.0.15",
|
||||
"@testing-library/dom": "^8.18.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-svg-pan-zoom": "^3.3.5",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.31.0",
|
||||
"@typescript-eslint/parser": "^5.31.0",
|
||||
"@vitejs/plugin-react": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.38.1",
|
||||
"@typescript-eslint/parser": "^5.38.1",
|
||||
"@vitejs/plugin-react": "^2.1.0",
|
||||
"@vitest/ui": "^0.20.3",
|
||||
"autoprefixer": "^10.4.8",
|
||||
"eslint": "^8.20.0",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-config-standard-with-typescript": "^22.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.2.4",
|
||||
"eslint-plugin-n": "^15.3.0",
|
||||
"eslint-plugin-only-warn": "^1.0.3",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-plugin-promise": "^6.0.1",
|
||||
"eslint-plugin-react": "^7.31.8",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"jsdom": "^20.0.0",
|
||||
"postcss": "^8.4.14",
|
||||
"sass": "^1.54.0",
|
||||
"tailwindcss": "^3.1.7",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.0.0",
|
||||
"postcss": "^8.4.16",
|
||||
"sass": "^1.55.0",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"typescript": "^4.8.3",
|
||||
"vite": "^3.1.3",
|
||||
"vitest": "^0.20.3"
|
||||
}
|
||||
}
|
||||
|
|
1128
pnpm-lock.yaml
generated
1128
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import { ClockIcon, CubeIcon, LinkIcon, MailIcon } from '@heroicons/react/outline';
|
||||
import { ClockIcon as ClockIconS, CubeIcon as CubeIconS, LinkIcon as LinkIconS, MailIcon as MailIconS } from '@heroicons/react/solid';
|
||||
import { ClockIcon, CubeIcon, LinkIcon, EnvelopeIcon } from '@heroicons/react/24/outline';
|
||||
import { ClockIcon as ClockIconS, CubeIcon as CubeIconS, LinkIcon as LinkIconS, EnvelopeIcon as EnvolopeIconS } from '@heroicons/react/24/solid';
|
||||
import { BarIcon } from './BarIcon';
|
||||
|
||||
interface IBarProps {
|
||||
|
@ -56,8 +56,8 @@ export function Bar(props: IBarProps): JSX.Element {
|
|||
onClick={() => props.toggleMessages()}>
|
||||
{
|
||||
props.isMessagesOpen
|
||||
? <MailIconS className='heroicon' />
|
||||
: <MailIcon className='heroicon' />
|
||||
? <EnvolopeIconS className='heroicon' />
|
||||
: <EnvelopeIcon className='heroicon' />
|
||||
}
|
||||
</BarIcon>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ChevronRightIcon } from '@heroicons/react/outline';
|
||||
import { ChevronRightIcon } from '@heroicons/react/24/outline';
|
||||
import React, { useState } from 'react';
|
||||
import { ICategory } from '../../Interfaces/ICategory';
|
||||
import { TruncateString } from '../../utils/stringtools';
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import { MenuAlt2Icon, MenuAlt3Icon, MenuIcon } from '@heroicons/react/outline';
|
||||
import { Bars3BottomLeftIcon, Bars3CenterLeftIcon, Bars3Icon, Bars3BottomRightIcon, Bars2Icon, ViewColumnsIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { PropertyType } from '../../Enums/PropertyType';
|
||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||
import { PositionReference } from '../../Enums/PositionReference';
|
||||
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
|
||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS } from '../../utils/default';
|
||||
import { ApplyWidthMargin, ApplyXMargin, RemoveWidthMargin, RemoveXMargin, RestoreX, TransformX } from '../../utils/svg';
|
||||
import { ApplyWidthMargin, ApplyXMargin, RemoveWidthMargin, RemoveXMargin, RestoreX, RestoreY, TransformX, TransformY } from '../../utils/svg';
|
||||
import { InputGroup } from '../InputGroup/InputGroup';
|
||||
import { TextInputGroup } from '../InputGroup/TextInputGroup';
|
||||
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
|
||||
import { Select } from '../Select/Select';
|
||||
import { ToggleButton, ToggleType } from '../ToggleButton/ToggleButton';
|
||||
import { Orientation } from '../../Interfaces/Orientation';
|
||||
|
||||
interface IContainerFormProps {
|
||||
properties: IContainerProperties
|
||||
|
@ -37,7 +39,7 @@ function GetCSSInputs(properties: IContainerProperties,
|
|||
|
||||
export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||
return (
|
||||
<div className='grid grid-cols-2 gap-y-4 items-center'>
|
||||
<div className='grid grid-cols-2 gap-y-6 items-center'>
|
||||
<InputGroup
|
||||
labelText='Name'
|
||||
inputKey='id'
|
||||
|
@ -71,6 +73,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
type='string'
|
||||
value={props.properties.displayedText?.toString()}
|
||||
onChange={(value) => props.onChange('displayedText', value)} />
|
||||
<OrientationSelector {...props} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-x`}
|
||||
labelText='x'
|
||||
|
@ -79,14 +82,18 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
inputClassName=''
|
||||
type='number'
|
||||
isDisabled={props.properties.linkedSymbolId !== ''}
|
||||
value={TransformX(RemoveXMargin(props.properties.x, props.properties.margin.left), props.properties.width, props.properties.xPositionReference).toString()}
|
||||
value={TransformX(
|
||||
RemoveXMargin(props.properties.x, props.properties.margin.left),
|
||||
props.properties.width,
|
||||
props.properties.positionReference
|
||||
).toString()}
|
||||
onChange={(value) => props.onChange(
|
||||
'x',
|
||||
ApplyXMargin(
|
||||
RestoreX(
|
||||
Number(value),
|
||||
props.properties.width,
|
||||
props.properties.xPositionReference
|
||||
props.properties.positionReference
|
||||
),
|
||||
props.properties.margin.left
|
||||
)
|
||||
|
@ -98,11 +105,25 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='number'
|
||||
value={(props.properties.y - (props.properties.margin?.top ?? 0)).toString()}
|
||||
onChange={(value) => props.onChange('y', Number(value) + (props.properties.margin?.top ?? 0))} />
|
||||
value={TransformY(
|
||||
RemoveXMargin(props.properties.y, props.properties.margin.top),
|
||||
props.properties.height,
|
||||
props.properties.positionReference
|
||||
).toString()}
|
||||
onChange={(value) => props.onChange(
|
||||
'y',
|
||||
ApplyXMargin(
|
||||
RestoreY(
|
||||
Number(value),
|
||||
props.properties.height,
|
||||
props.properties.positionReference
|
||||
),
|
||||
props.properties.margin.top
|
||||
)
|
||||
)} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-minWidth`}
|
||||
labelText='Minimum width'
|
||||
labelText='Minimum Width'
|
||||
inputKey='minWidth'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
@ -112,7 +133,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
onChange={(value) => props.onChange('minWidth', Number(value))} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-maxWidth`}
|
||||
labelText='Maximum width'
|
||||
labelText='Maximum Width'
|
||||
inputKey='maxWidth'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
@ -132,6 +153,26 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
value={(RemoveWidthMargin(props.properties.width, props.properties.margin.left, props.properties.margin.right)).toString()}
|
||||
onChange={(value) => props.onChange('width', ApplyWidthMargin(Number(value), props.properties.margin.left, props.properties.margin.right))}
|
||||
isDisabled={props.properties.isFlex} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-minHeight`}
|
||||
labelText='Minimum Height'
|
||||
inputKey='minHeight'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='number'
|
||||
min={1}
|
||||
value={props.properties.minHeight.toString()}
|
||||
onChange={(value) => props.onChange('minHeight', Number(value))} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-maxHeight`}
|
||||
labelText='Maximum Height'
|
||||
inputKey='maxHeight'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='number'
|
||||
min={1}
|
||||
value={props.properties.maxHeight.toString()}
|
||||
onChange={(value) => props.onChange('maxHeight', Number(value))} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-height`}
|
||||
labelText='Height'
|
||||
|
@ -139,9 +180,12 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='number'
|
||||
min={0}
|
||||
value={(props.properties.height + (props.properties.margin?.top ?? 0) + (props.properties.margin?.bottom ?? 0)).toString()}
|
||||
onChange={(value) => props.onChange('height', Number(value) - (props.properties.margin?.top ?? 0) - (props.properties.margin?.bottom ?? 0))} />
|
||||
min={props.properties.minHeight}
|
||||
max={props.properties.maxHeight}
|
||||
value={(RemoveWidthMargin(props.properties.height, props.properties.margin.top, props.properties.margin.bottom)).toString()}
|
||||
onChange={(value) => props.onChange('height', ApplyWidthMargin(Number(value), props.properties.margin.top, props.properties.margin.bottom))}
|
||||
isDisabled={props.properties.isFlex}
|
||||
/>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-ml`}
|
||||
labelText='Margin left'
|
||||
|
@ -182,60 +226,33 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
min={0}
|
||||
value={(props.properties.margin.right ?? 0).toString()}
|
||||
onChange={(value) => props.onChange('right', Number(value), PropertyType.Margin)} />
|
||||
<InputGroup
|
||||
<ToggleButton
|
||||
labelText='Flex'
|
||||
inputKey='isFlex'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='checkbox'
|
||||
type={ToggleType.Full}
|
||||
checked={props.properties.isFlex}
|
||||
onChange={(event) => props.onChange('isFlex', event.target.checked)} />
|
||||
<InputGroup
|
||||
onChange={(event) => props.onChange('isFlex', event.target.checked)}
|
||||
/>
|
||||
<ToggleButton
|
||||
labelText='Anchor'
|
||||
inputKey='isAnchor'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='checkbox'
|
||||
type={ToggleType.Full}
|
||||
checked={props.properties.isAnchor}
|
||||
onChange={(event) => props.onChange('isAnchor', event.target.checked)} />
|
||||
<RadioGroupButtons
|
||||
name='XPositionReference'
|
||||
value={props.properties.xPositionReference.toString()}
|
||||
inputClassName='hidden'
|
||||
labelText='Horizontal alignment'
|
||||
inputGroups={[
|
||||
{
|
||||
text: (
|
||||
<div title='Left' aria-label='left' className='radio-button-icon'>
|
||||
<MenuAlt2Icon className='heroicon' />
|
||||
</div>
|
||||
),
|
||||
value: XPositionReference.Left.toString()
|
||||
},
|
||||
{
|
||||
text: (
|
||||
<div title='Center' aria-label='center' className='radio-button-icon'>
|
||||
<MenuIcon className='heroicon' />
|
||||
</div>
|
||||
),
|
||||
value: XPositionReference.Center.toString()
|
||||
},
|
||||
{
|
||||
text: (
|
||||
<div title='Right' aria-label='right' className='radio-button-icon'>
|
||||
<MenuAlt3Icon className='heroicon' />
|
||||
</div>
|
||||
),
|
||||
value: XPositionReference.Right.toString()
|
||||
}
|
||||
]}
|
||||
onChange={(event) => props.onChange('xPositionReference', Number(event.target.value))} />
|
||||
<AlignmentSelector
|
||||
{...props}
|
||||
/>
|
||||
<Select
|
||||
inputKey='linkedSymbolId'
|
||||
labelText='Align with symbol'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
inputs={[...props.symbols.values()].map(symbol => ({
|
||||
key: symbol.id,
|
||||
text: symbol.id,
|
||||
value: symbol.id
|
||||
}))}
|
||||
|
@ -244,43 +261,43 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
{GetCSSInputs(props.properties, props.onChange)}
|
||||
{
|
||||
SHOW_SELF_DIMENSIONS &&
|
||||
<InputGroup
|
||||
<ToggleButton
|
||||
labelText='Show dimension'
|
||||
inputKey='showSelfDimensions'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='checkbox'
|
||||
type={ToggleType.Full}
|
||||
checked={props.properties.showSelfDimensions}
|
||||
onChange={(event) => props.onChange('showSelfDimensions', event.target.checked)} />
|
||||
}
|
||||
{
|
||||
SHOW_CHILDREN_DIMENSIONS &&
|
||||
<InputGroup
|
||||
<ToggleButton
|
||||
labelText='Show overall dimension of its children'
|
||||
inputKey='showChildrenDimensions'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='checkbox'
|
||||
type={ToggleType.Full}
|
||||
checked={props.properties.showChildrenDimensions}
|
||||
onChange={(event) => props.onChange('showChildrenDimensions', event.target.checked)} />
|
||||
}
|
||||
{
|
||||
SHOW_BORROWER_DIMENSIONS &&
|
||||
<>
|
||||
<InputGroup
|
||||
<ToggleButton
|
||||
labelText='Mark the position'
|
||||
inputKey='markPositionToDimensionBorrower'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='checkbox'
|
||||
type={ToggleType.Full}
|
||||
checked={props.properties.markPositionToDimensionBorrower}
|
||||
onChange={(event) => props.onChange('markPositionToDimensionBorrower', event.target.checked)} />
|
||||
<InputGroup
|
||||
<ToggleButton
|
||||
labelText='Show dimension with marked children'
|
||||
inputKey='isDimensionBorrower'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='checkbox'
|
||||
type={ToggleType.Full}
|
||||
checked={props.properties.isDimensionBorrower}
|
||||
onChange={(event) => props.onChange('isDimensionBorrower', event.target.checked)} />
|
||||
</>
|
||||
|
@ -288,3 +305,135 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Implement categories in the form
|
||||
|
||||
function OrientationSelector(props: IContainerFormProps): JSX.Element {
|
||||
return <RadioGroupButtons
|
||||
key='orientation'
|
||||
name='Orientation'
|
||||
value={props.properties.orientation.toString()}
|
||||
inputClassName='hidden'
|
||||
labelText='Orientation'
|
||||
colQty={2}
|
||||
inputGroups={[
|
||||
{
|
||||
key: 'orientation-horizontal',
|
||||
text: (
|
||||
<div title='Horizontal' aria-label='horizontal' className='radio-button-icon'>
|
||||
<ViewColumnsIcon className='heroicon' />
|
||||
</div>
|
||||
),
|
||||
value: Orientation.Horizontal.toString()
|
||||
},
|
||||
{
|
||||
key: 'orientation-vertical',
|
||||
text: (
|
||||
<div title='Vertical' aria-label='vertical' className='radio-button-icon'>
|
||||
<ViewColumnsIcon className='heroicon rotate-90' />
|
||||
</div>
|
||||
),
|
||||
value: Orientation.Vertical.toString()
|
||||
}
|
||||
]}
|
||||
onChange={(event) => {
|
||||
props.onChange('orientation', Number(event.target.value));
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
|
||||
function AlignmentSelector(props: IContainerFormProps): JSX.Element {
|
||||
return <RadioGroupButtons
|
||||
key='positionReference'
|
||||
name='PositionReference'
|
||||
value={props.properties.positionReference.toString()}
|
||||
inputClassName='hidden'
|
||||
labelText='Alignment'
|
||||
colQty={3}
|
||||
inputGroups={[
|
||||
{
|
||||
key: 'position-reference-tl',
|
||||
text: (
|
||||
<div title='Top Left' aria-label='top left' className='radio-button-icon'>
|
||||
<Bars3BottomLeftIcon className='heroicon' />
|
||||
</div>
|
||||
),
|
||||
value: PositionReference.TopLeft.toString()
|
||||
},
|
||||
{
|
||||
key: 'position-reference-tc',
|
||||
text: (
|
||||
<div title='Top Center' aria-label='top center' className='radio-button-icon'>
|
||||
<Bars2Icon className='heroicon -scale-y-100' />
|
||||
</div>
|
||||
),
|
||||
value: PositionReference.TopCenter.toString()
|
||||
},
|
||||
{
|
||||
key: 'position-reference-tr',
|
||||
text: (
|
||||
<div title='Top Right' aria-label='top right' className='radio-button-icon'>
|
||||
<Bars3BottomRightIcon className='heroicon' />
|
||||
</div>
|
||||
),
|
||||
value: PositionReference.TopRight.toString()
|
||||
},
|
||||
{
|
||||
key: 'position-reference-cl',
|
||||
text: (
|
||||
<div title='Center Left' aria-label='center left' className='radio-button-icon'>
|
||||
<Bars3CenterLeftIcon className='heroicon' />
|
||||
</div>
|
||||
),
|
||||
value: PositionReference.CenterLeft.toString()
|
||||
},
|
||||
{
|
||||
key: 'position-reference-cc',
|
||||
text: (
|
||||
<div title='Center Center' aria-label='center center' className='radio-button-icon'>
|
||||
<Bars3Icon className='heroicon' />
|
||||
</div>
|
||||
),
|
||||
value: PositionReference.CenterCenter.toString()
|
||||
},
|
||||
{
|
||||
key: 'position-reference-cr',
|
||||
text: (
|
||||
<div title='Center Right' aria-label='center right' className='radio-button-icon'>
|
||||
<Bars3CenterLeftIcon className='heroicon -scale-x-100' />
|
||||
</div>
|
||||
),
|
||||
value: PositionReference.CenterRight.toString()
|
||||
},
|
||||
{
|
||||
key: 'position-reference-bl',
|
||||
text: (
|
||||
<div title='Bottom Left' aria-label='bottom left' className='radio-button-icon'>
|
||||
<Bars3BottomLeftIcon className='heroicon -scale-y-100' />
|
||||
</div>
|
||||
),
|
||||
value: PositionReference.BottomLeft.toString()
|
||||
},
|
||||
{
|
||||
key: 'position-reference-bc',
|
||||
text: (
|
||||
<div title='Bottom Center' aria-label='center center' className='radio-button-icon'>
|
||||
<Bars2Icon className='heroicon' />
|
||||
</div>
|
||||
),
|
||||
value: PositionReference.BottomCenter.toString()
|
||||
},
|
||||
{
|
||||
key: 'position-reference-br',
|
||||
text: (
|
||||
<div title='Bottom Right' aria-label='bottom right' className='radio-button-icon'>
|
||||
<Bars3BottomRightIcon className='heroicon -scale-y-100' />
|
||||
</div>
|
||||
),
|
||||
value: PositionReference.BottomRight.toString()
|
||||
}
|
||||
]}
|
||||
onChange={(event) => {
|
||||
props.onChange('positionReference', Number(event.target.value));
|
||||
}} />;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import * as React from 'react';
|
||||
import { expect, describe, it, vi } from 'vitest';
|
||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||
import { PositionReference } from '../../Enums/PositionReference';
|
||||
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
|
||||
import { Orientation } from '../../Interfaces/Orientation';
|
||||
import { Properties } from './ContainerProperties';
|
||||
|
||||
describe.concurrent('Properties', () => {
|
||||
|
@ -26,14 +27,17 @@ describe.concurrent('Properties', () => {
|
|||
parentId: 'parentId',
|
||||
linkedSymbolId: '',
|
||||
displayedText: 'stuff',
|
||||
orientation: Orientation.Horizontal,
|
||||
x: 1,
|
||||
y: 1,
|
||||
width: 1,
|
||||
height: 1,
|
||||
minWidth: 1,
|
||||
maxWidth: Infinity,
|
||||
minHeight: 1,
|
||||
maxHeight: Infinity,
|
||||
margin: {},
|
||||
xPositionReference: XPositionReference.Left,
|
||||
positionReference: PositionReference.TopLeft,
|
||||
isFlex: false,
|
||||
isAnchor: false,
|
||||
warning: '',
|
||||
|
|
|
@ -9,6 +9,7 @@ import { IContainerModel, ContainerModel } from '../../../Interfaces/IContainerM
|
|||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||
import { IPattern, GetPattern, ContainerOrPattern } from '../../../Interfaces/IPattern';
|
||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_MAX_DEPTH, DEFAULTCHILDTYPE_ALLOW_CYCLIC } from '../../../utils/default';
|
||||
import { FindContainerById } from '../../../utils/itertools';
|
||||
import { ApplyMargin } from '../../../utils/svg';
|
||||
|
@ -137,12 +138,12 @@ function AddNewContainerToParent(
|
|||
let x = containerConfig.X ?? 0;
|
||||
let y = containerConfig.Y ?? 0;
|
||||
let width = containerConfig.Width ?? containerConfig.MaxWidth ?? containerConfig.MinWidth ?? parentClone.properties.width;
|
||||
let height = containerConfig.Height ?? parentClone.properties.height;
|
||||
let height = containerConfig.Height ?? containerConfig.MaxHeight ?? containerConfig.MinHeight ?? parentClone.properties.height;
|
||||
|
||||
({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right));
|
||||
|
||||
// Apply an add method (append or insert/replace)
|
||||
x = ApplyAddMethod(index + typeIndex, containerConfig, parentClone, x);
|
||||
({ x, y } = ApplyAddMethod(index + typeIndex, containerConfig, parentClone, x, y));
|
||||
|
||||
// Set the counter of the object type in order to assign an unique id
|
||||
UpdateCounters(newCounters, type);
|
||||
|
@ -400,8 +401,9 @@ function ApplyAddMethod(
|
|||
index: number,
|
||||
containerConfig: IAvailableContainer,
|
||||
parent: IContainerModel,
|
||||
x: number
|
||||
): number {
|
||||
x: number,
|
||||
y: number
|
||||
): { x: number, y: number } {
|
||||
if (index > 0 && (
|
||||
containerConfig.AddMethod === undefined ||
|
||||
containerConfig.AddMethod === null ||
|
||||
|
@ -412,8 +414,13 @@ function ApplyAddMethod(
|
|||
.at(index - 1);
|
||||
|
||||
if (lastChild !== undefined) {
|
||||
x += (lastChild.properties.x + lastChild.properties.width);
|
||||
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
|
||||
if (isHorizontal) {
|
||||
x += lastChild.properties.x + lastChild.properties.width;
|
||||
} else {
|
||||
y += lastChild.properties.y + lastChild.properties.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
return x;
|
||||
return { x, y };
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { FindContainerById, MakeIterator } from '../../../utils/itertools';
|
||||
import { FindContainerById, MakeDFSIterator } from '../../../utils/itertools';
|
||||
import { GetCurrentHistory } from '../Editor';
|
||||
import { ApplyBehaviors, ApplyBehaviorsOnSiblings, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
|
||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
import Swal from 'sweetalert2';
|
||||
import { PropertyType } from '../../../Enums/PropertyType';
|
||||
import { TransformX } from '../../../utils/svg';
|
||||
import { TransformX, TransformY } from '../../../utils/svg';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
|
||||
/**
|
||||
* Select a container
|
||||
|
@ -133,7 +134,7 @@ function GetSelectedContainerOnDelete(
|
|||
* @param container Container to unlink
|
||||
*/
|
||||
function UnlinkContainerFromSymbols(symbols: Map<string, ISymbolModel>, container: IContainerModel): void {
|
||||
const it = MakeIterator(container);
|
||||
const it = MakeDFSIterator(container);
|
||||
for (const child of it) {
|
||||
const symbol = symbols.get(child.properties.linkedSymbolId);
|
||||
if (symbol === undefined) {
|
||||
|
@ -194,11 +195,34 @@ export function SortChildren(parentClone: IContainerModel | null | undefined): v
|
|||
if (parentClone === null || parentClone === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isHorizontal = parentClone.properties.orientation === Orientation.Horizontal;
|
||||
const children = parentClone.children;
|
||||
|
||||
if (!isHorizontal) {
|
||||
parentClone.children.sort(
|
||||
(a, b) => {
|
||||
const yA = TransformY(a.properties.y, a.properties.height, a.properties.positionReference);
|
||||
const yB = TransformY(b.properties.y, b.properties.height, b.properties.positionReference);
|
||||
if (yA < yB) {
|
||||
return -1;
|
||||
}
|
||||
if (yB < yA) {
|
||||
return 1;
|
||||
}
|
||||
// xA = xB
|
||||
const indexA = children.indexOf(a);
|
||||
const indexB = children.indexOf(b);
|
||||
return indexA - indexB;
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
parentClone.children.sort(
|
||||
(a, b) => {
|
||||
const xA = TransformX(a.properties.x, a.properties.width, a.properties.xPositionReference);
|
||||
const xB = TransformX(b.properties.x, b.properties.width, b.properties.xPositionReference);
|
||||
const xA = TransformX(a.properties.x, a.properties.width, a.properties.positionReference);
|
||||
const xB = TransformX(b.properties.x, b.properties.width, b.properties.positionReference);
|
||||
if (xA < xB) {
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
*/
|
||||
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
|
||||
|
||||
/**
|
||||
|
@ -21,17 +22,16 @@ import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
|
|||
* Apply the following modification to the overlapping rigid body container :
|
||||
* @param container Container to impose its position
|
||||
*/
|
||||
export function ApplyAnchor(container: IContainerModel): IContainerModel {
|
||||
if (container.parent === undefined ||
|
||||
container.parent === null) {
|
||||
return container;
|
||||
}
|
||||
|
||||
const rigidBodies = container.parent.children.filter(
|
||||
export function ApplyAnchor(container: IContainerModel, parent: IContainerModel): IContainerModel {
|
||||
const rigidBodies = parent.children.filter(
|
||||
child => !child.properties.isAnchor
|
||||
);
|
||||
|
||||
const overlappingContainers = GetOverlappingContainers(container, rigidBodies);
|
||||
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
|
||||
const overlappingContainers = isHorizontal
|
||||
? GetHorizontallyOverlappingContainers(container, rigidBodies)
|
||||
: GetVerticallyOverlappingContainers(container, rigidBodies);
|
||||
|
||||
for (const overlappingContainer of overlappingContainers) {
|
||||
ConstraintBodyInsideUnallocatedWidth(overlappingContainer);
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ export function ApplyAnchor(container: IContainerModel): IContainerModel {
|
|||
* @param containers A list of containers
|
||||
* @returns A list of overlapping containers
|
||||
*/
|
||||
export function GetOverlappingContainers(
|
||||
export function GetHorizontallyOverlappingContainers(
|
||||
container: IContainerModel,
|
||||
containers: IContainerModel[]
|
||||
): IContainerModel[] {
|
||||
|
@ -68,3 +68,63 @@ export function GetOverlappingContainers(
|
|||
}
|
||||
return overlappingContainers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the overlapping containers with container
|
||||
* @param container A container
|
||||
* @param containers A list of containers
|
||||
* @returns A list of overlapping containers
|
||||
*/
|
||||
export function GetVerticallyOverlappingContainers(
|
||||
container: IContainerModel,
|
||||
containers: IContainerModel[]
|
||||
): IContainerModel[] {
|
||||
const min1 = container.properties.y;
|
||||
const max1 = container.properties.y + container.properties.height;
|
||||
const overlappingContainers: IContainerModel[] = [];
|
||||
for (const other of containers) {
|
||||
if (other === container) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const min2 = other.properties.y;
|
||||
const max2 = other.properties.y + other.properties.height;
|
||||
const isOverlapping = Math.min(max1, max2) - Math.max(min1, min2) > 0;
|
||||
|
||||
if (!isOverlapping) {
|
||||
continue;
|
||||
}
|
||||
|
||||
overlappingContainers.push(other);
|
||||
}
|
||||
return overlappingContainers;
|
||||
}
|
||||
|
||||
export function GetOverlappingContainers(
|
||||
container: IContainerModel,
|
||||
containers: IContainerModel[]
|
||||
): IContainerModel[] {
|
||||
const overlappingContainers: IContainerModel[] = [];
|
||||
for (const other of containers) {
|
||||
if (other === container) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DoOverlap(container, other) && overlappingContainers.push(other);
|
||||
}
|
||||
return overlappingContainers;
|
||||
}
|
||||
|
||||
function DoOverlap(container: IContainerModel, other: IContainerModel): boolean {
|
||||
if (container.properties.x >= other.properties.x + other.properties.width ||
|
||||
other.properties.x >= container.properties.x + container.properties.width) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (container.properties.y >= other.properties.y + other.properties.height ||
|
||||
other.properties.y >= container.properties.y + container.properties.height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -20,18 +20,22 @@ export function ApplyBehaviors(container: IContainerModel, symbols: Map<string,
|
|||
ApplySymbol(container, symbol);
|
||||
}
|
||||
|
||||
if (container.properties.isAnchor) {
|
||||
ApplyAnchor(container);
|
||||
}
|
||||
if (container.parent !== undefined && container.parent !== null) {
|
||||
const parent = container.parent;
|
||||
|
||||
if (ENABLE_SWAP) {
|
||||
ApplySwap(container);
|
||||
}
|
||||
if (container.properties.isAnchor) {
|
||||
ApplyAnchor(container, parent);
|
||||
}
|
||||
|
||||
Flex(container);
|
||||
if (ENABLE_SWAP) {
|
||||
ApplySwap(container, parent);
|
||||
}
|
||||
|
||||
if (ENABLE_RIGID) {
|
||||
ApplyRigidBody(container);
|
||||
Flex(container, parent);
|
||||
|
||||
if (ENABLE_RIGID) {
|
||||
ApplyRigidBody(container, parent);
|
||||
}
|
||||
}
|
||||
|
||||
if (APPLY_BEHAVIORS_ON_CHILDREN) {
|
||||
|
@ -68,13 +72,9 @@ export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel,
|
|||
newContainer.parent.children
|
||||
.forEach((container: IContainerModel) => {
|
||||
if (container.parent != null) {
|
||||
const overlappingContainers = GetOverlappingContainers(container, container.parent.children);
|
||||
if (overlappingContainers.length > 0) {
|
||||
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
|
||||
} else {
|
||||
container.properties.warning = '';
|
||||
}
|
||||
UpdateWarning(container, container.parent);
|
||||
}
|
||||
|
||||
if (container === newContainer) {
|
||||
return;
|
||||
}
|
||||
|
@ -85,7 +85,6 @@ export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel,
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterate over the siblings of newContainer and apply the behaviors
|
||||
* @param newContainer
|
||||
|
@ -102,13 +101,9 @@ export function ApplyBehaviorsOnSiblings(newContainer: IContainerModel, symbols:
|
|||
ApplyBehaviors(container, symbols);
|
||||
|
||||
if (container.parent != null) {
|
||||
const overlappingContainers = GetOverlappingContainers(container, container.parent.children);
|
||||
if (overlappingContainers.length > 0) {
|
||||
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
|
||||
} else {
|
||||
container.properties.warning = '';
|
||||
}
|
||||
UpdateWarning(container, container.parent);
|
||||
}
|
||||
|
||||
if (container === newContainer) {
|
||||
return;
|
||||
}
|
||||
|
@ -118,4 +113,11 @@ export function ApplyBehaviorsOnSiblings(newContainer: IContainerModel, symbols:
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
function UpdateWarning(container: IContainerModel, parent: IContainerModel): void {
|
||||
const overlappingContainers = GetOverlappingContainers(container, parent.children);
|
||||
if (overlappingContainers.length > 0) {
|
||||
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
|
||||
} else {
|
||||
container.properties.warning = '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
import { Simplex } from '../../../utils/simplex';
|
||||
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
|
||||
|
||||
|
@ -10,27 +11,111 @@ interface IFlexibleGroup {
|
|||
|
||||
/**
|
||||
* Flex the container and its siblings (mutate)
|
||||
* @param container Container to flex
|
||||
* @returns Flexed container
|
||||
*/
|
||||
export function Flex(container: IContainerModel): void {
|
||||
if (container.parent === null || container.parent === undefined) {
|
||||
export function Flex(container: IContainerModel, parent: IContainerModel): void {
|
||||
const isVertical = parent.properties.orientation === Orientation.Vertical;
|
||||
|
||||
if (isVertical) {
|
||||
const wantedWidth = Math.min(container.properties.maxWidth, parent.properties.width);
|
||||
container.properties.width = ApplyWidthMargin(wantedWidth, container.properties.margin.left, container.properties.margin.right);
|
||||
const flexibleGroups = GetVerticalFlexibleGroups(parent);
|
||||
for (const flexibleGroup of flexibleGroups) {
|
||||
FlexGroupVertically(flexibleGroup);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const flexibleGroups = GetFlexibleGroups(container.parent);
|
||||
|
||||
const wantedHeight = Math.min(container.properties.maxHeight, parent.properties.height);
|
||||
container.properties.height = ApplyWidthMargin(wantedHeight, container.properties.margin.top, container.properties.margin.bottom);
|
||||
const flexibleGroups = GetHorizontalFlexibleGroups(parent);
|
||||
for (const flexibleGroup of flexibleGroups) {
|
||||
FlexGroup(flexibleGroup);
|
||||
FlexGroupHorizontally(flexibleGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of groups of flexible containers
|
||||
* @param parent Parent in which the flexible children will be set in groups
|
||||
* @returns a list of groups of flexible containers
|
||||
*/
|
||||
export function GetHorizontalFlexibleGroups(parent: IContainerModel): IFlexibleGroup[] {
|
||||
const flexibleGroups: IFlexibleGroup[] = [];
|
||||
let group: IContainerModel[] = [];
|
||||
let offset = 0;
|
||||
let size = 0;
|
||||
for (const child of parent.children) {
|
||||
if (child.properties.isAnchor) {
|
||||
size = child.properties.x - offset;
|
||||
const flexibleGroup: IFlexibleGroup = {
|
||||
group,
|
||||
offset,
|
||||
size
|
||||
};
|
||||
|
||||
flexibleGroups.push(flexibleGroup);
|
||||
offset = child.properties.x + child.properties.width;
|
||||
group = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
group.push(child);
|
||||
}
|
||||
size = parent.properties.width - offset;
|
||||
const flexibleGroup: IFlexibleGroup = {
|
||||
group,
|
||||
offset,
|
||||
size
|
||||
};
|
||||
|
||||
flexibleGroups.push(flexibleGroup);
|
||||
return flexibleGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of groups of flexible containers
|
||||
* @param parent Parent in which the flexible children will be set in groups
|
||||
* @returns a list of groups of flexible containers
|
||||
*/
|
||||
export function GetVerticalFlexibleGroups(parent: IContainerModel): IFlexibleGroup[] {
|
||||
const flexibleGroups: IFlexibleGroup[] = [];
|
||||
let group: IContainerModel[] = [];
|
||||
let offset = 0;
|
||||
let size = 0;
|
||||
for (const child of parent.children) {
|
||||
if (child.properties.isAnchor) {
|
||||
size = child.properties.y - offset;
|
||||
const flexibleGroup: IFlexibleGroup = {
|
||||
group,
|
||||
offset,
|
||||
size
|
||||
};
|
||||
|
||||
flexibleGroups.push(flexibleGroup);
|
||||
offset = child.properties.y + child.properties.height;
|
||||
group = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
group.push(child);
|
||||
}
|
||||
size = parent.properties.height - offset;
|
||||
const flexibleGroup: IFlexibleGroup = {
|
||||
group,
|
||||
offset,
|
||||
size
|
||||
};
|
||||
|
||||
flexibleGroups.push(flexibleGroup);
|
||||
return flexibleGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply flex to the group
|
||||
* @param flexibleGroup Group that contains a list of flexible containers
|
||||
* @returns
|
||||
*/
|
||||
function FlexGroup(flexibleGroup: IFlexibleGroup): void {
|
||||
function FlexGroupHorizontally(flexibleGroup: IFlexibleGroup): void {
|
||||
const children = flexibleGroup.group;
|
||||
const {
|
||||
flexibleContainers,
|
||||
|
@ -96,6 +181,77 @@ function FlexGroup(flexibleGroup: IFlexibleGroup): void {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply flex to the group
|
||||
* @param flexibleGroup Group that contains a list of flexible containers
|
||||
* @returns
|
||||
*/
|
||||
function FlexGroupVertically(flexibleGroup: IFlexibleGroup): void {
|
||||
const children = flexibleGroup.group;
|
||||
const {
|
||||
flexibleContainers,
|
||||
nonFlexibleContainers
|
||||
} = SeparateFlexibleContainers(children);
|
||||
|
||||
if (flexibleContainers.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const minHeights = flexibleContainers
|
||||
.map(sibling => sibling.properties.minHeight);
|
||||
|
||||
const fixedHeight = nonFlexibleContainers
|
||||
.map(sibling => sibling.properties.height)
|
||||
.reduce((heightSum, a) => heightSum + a, 0);
|
||||
|
||||
const requiredMaxHeight = flexibleGroup.size - fixedHeight;
|
||||
const minimumPossibleHeight = minHeights.reduce((heightSum, a) => heightSum + a, 0); // sum(minHeights)
|
||||
|
||||
const checkSumMinHeightsIsFitting = minimumPossibleHeight > requiredMaxHeight;
|
||||
if (checkSumMinHeightsIsFitting) {
|
||||
console.warn('[FlexBehavior] Cannot fit at all even when squeezing all flex containers to the minimum.');
|
||||
return;
|
||||
}
|
||||
|
||||
const maxMinHeights = Math.max(...minHeights);
|
||||
if (maxMinHeights * minHeights.length <= requiredMaxHeight) {
|
||||
const wantedHeight = requiredMaxHeight / minHeights.length;
|
||||
// it fits, flex with maxMinHeights and fixed height
|
||||
let right = flexibleGroup.offset;
|
||||
for (const sibling of children) {
|
||||
if (!sibling.properties.isFlex) {
|
||||
sibling.properties.y = right;
|
||||
right += sibling.properties.height;
|
||||
continue;
|
||||
}
|
||||
sibling.properties.y = ApplyXMargin(right, sibling.properties.margin.top);
|
||||
sibling.properties.height = ApplyWidthMargin(wantedHeight, sibling.properties.margin.top, sibling.properties.margin.bottom);
|
||||
right += wantedHeight;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// does not fit
|
||||
|
||||
/// SIMPLEX ///
|
||||
const maxHeights = flexibleContainers
|
||||
.map(sibling => sibling.properties.maxHeight);
|
||||
const solutions: number[] = Simplex(minHeights, maxHeights, requiredMaxHeight);
|
||||
|
||||
// apply the solutions
|
||||
for (let i = 0; i < flexibleContainers.length; i++) {
|
||||
flexibleContainers[i].properties.height = ApplyWidthMargin(solutions[i], flexibleContainers[i].properties.margin.top, flexibleContainers[i].properties.margin.bottom);
|
||||
}
|
||||
|
||||
// move the containers
|
||||
let right = flexibleGroup.offset;
|
||||
for (const sibling of children) {
|
||||
sibling.properties.y = ApplyXMargin(right, sibling.properties.margin.top);
|
||||
right += sibling.properties.height;
|
||||
}
|
||||
}
|
||||
|
||||
function SeparateFlexibleContainers(
|
||||
containers: IContainerModel[]
|
||||
): { flexibleContainers: IContainerModel[], nonFlexibleContainers: IContainerModel[] } {
|
||||
|
@ -114,41 +270,3 @@ function SeparateFlexibleContainers(
|
|||
nonFlexibleContainers
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of groups of flexible containers
|
||||
* @param parent Parent in which the flexible children will be set in groups
|
||||
* @returns a list of groups of flexible containers
|
||||
*/
|
||||
export function GetFlexibleGroups(parent: IContainerModel): IFlexibleGroup[] {
|
||||
const flexibleGroups: IFlexibleGroup[] = [];
|
||||
let group: IContainerModel[] = [];
|
||||
let offset = 0;
|
||||
let size = 0;
|
||||
for (const child of parent.children) {
|
||||
if (child.properties.isAnchor) {
|
||||
size = child.properties.x - offset;
|
||||
const flexibleGroup: IFlexibleGroup = {
|
||||
group,
|
||||
offset,
|
||||
size
|
||||
};
|
||||
|
||||
flexibleGroups.push(flexibleGroup);
|
||||
offset = child.properties.x + child.properties.width;
|
||||
group = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
group.push(child);
|
||||
}
|
||||
size = parent.properties.width - offset;
|
||||
const flexibleGroup: IFlexibleGroup = {
|
||||
group,
|
||||
offset,
|
||||
size
|
||||
};
|
||||
|
||||
flexibleGroups.push(flexibleGroup);
|
||||
return flexibleGroups;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
import { ReversePairwise } from '../../../utils/itertools';
|
||||
import { Flex } from './FlexBehaviors';
|
||||
|
||||
|
@ -7,17 +8,27 @@ import { Flex } from './FlexBehaviors';
|
|||
* @param container
|
||||
* @returns
|
||||
*/
|
||||
export function PushContainers(container: IContainerModel): IContainerModel {
|
||||
if (container.parent === null) {
|
||||
export function ApplyPush(container: IContainerModel, parent: IContainerModel): IContainerModel {
|
||||
if (parent.children.length <= 1) {
|
||||
return container;
|
||||
}
|
||||
|
||||
if (container.parent.children.length <= 1) {
|
||||
return container;
|
||||
const children = parent.children;
|
||||
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
|
||||
|
||||
if (isHorizontal) {
|
||||
PushContainersHorizontally(container, children);
|
||||
} else {
|
||||
PushContainersVertically(container, children);
|
||||
}
|
||||
|
||||
const prevIndex = container.parent.children.length - 2;
|
||||
const prev: IContainerModel = container.parent.children[prevIndex];
|
||||
Flex(container, parent);
|
||||
return container;
|
||||
}
|
||||
|
||||
export function PushContainersHorizontally(container: IContainerModel, children: IContainerModel[]): IContainerModel {
|
||||
const prevIndex = children.length - 2;
|
||||
const prev: IContainerModel = children[prevIndex];
|
||||
const isOverlapping = prev.properties.x + prev.properties.width > container.properties.x;
|
||||
if (!isOverlapping) {
|
||||
return container;
|
||||
|
@ -32,7 +43,7 @@ export function PushContainers(container: IContainerModel): IContainerModel {
|
|||
// FIXME: A fix was applied using toFixed(2).
|
||||
// FIXME: A coverture check must be done to ensure that all scenarios are covered
|
||||
|
||||
const it = ReversePairwise<IContainerModel>(container.parent.children.filter(child => child !== container));
|
||||
const it = ReversePairwise<IContainerModel>(children.filter(child => child !== container));
|
||||
|
||||
for (const { cur, next } of it) {
|
||||
const hasSpaceBetween = next.properties.x + next.properties.width < cur.properties.x;
|
||||
|
@ -48,9 +59,9 @@ export function PushContainers(container: IContainerModel): IContainerModel {
|
|||
break;
|
||||
}
|
||||
|
||||
const indexLastContainer = container.parent.children.indexOf(lastContainer);
|
||||
for (let i = indexLastContainer; i <= container.parent.children.length - 2; i++) {
|
||||
const sibling = container.parent.children[i];
|
||||
const indexLastContainer = children.indexOf(lastContainer);
|
||||
for (let i = indexLastContainer; i <= children.length - 2; i++) {
|
||||
const sibling = children[i];
|
||||
sibling.properties.x -= space;
|
||||
}
|
||||
}
|
||||
|
@ -58,16 +69,71 @@ export function PushContainers(container: IContainerModel): IContainerModel {
|
|||
const hasNoSpaceBetween = lastContainer === null;
|
||||
if (hasNoSpaceBetween) {
|
||||
// test gap between the left of the parent and the first container
|
||||
space = container.parent.children[0].properties.x;
|
||||
space = children[0].properties.x;
|
||||
if (space > 0) {
|
||||
for (let i = 0; i <= container.parent.children.length - 2; i++) {
|
||||
const sibling = container.parent.children[i];
|
||||
for (let i = 0; i <= children.length - 2; i++) {
|
||||
const sibling = children[i];
|
||||
sibling.properties.x -= space;
|
||||
}
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
Flex(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
export function PushContainersVertically(container: IContainerModel, children: IContainerModel[]): IContainerModel {
|
||||
const prevIndex = children.length - 2;
|
||||
const prev: IContainerModel = children[prevIndex];
|
||||
const isOverlapping = prev.properties.y + prev.properties.height > container.properties.y;
|
||||
if (!isOverlapping) {
|
||||
return container;
|
||||
}
|
||||
|
||||
// find hole
|
||||
let lastContainer: IContainerModel | null = null;
|
||||
let space: number = 0;
|
||||
|
||||
while (space.toFixed(2) < container.properties.height.toFixed(2)) {
|
||||
// FIXME: possible infinite loop due to floating point
|
||||
// FIXME: A fix was applied using toFixed(2).
|
||||
// FIXME: A coverture check must be done to ensure that all scenarios are covered
|
||||
|
||||
const it = ReversePairwise<IContainerModel>(children.filter(child => child !== container));
|
||||
|
||||
for (const { cur, next } of it) {
|
||||
const hasSpaceBetween = next.properties.y + next.properties.height < cur.properties.y;
|
||||
if (hasSpaceBetween) {
|
||||
lastContainer = cur;
|
||||
space = cur.properties.y - (next.properties.y + next.properties.height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastContainer === null) {
|
||||
// no space between
|
||||
break;
|
||||
}
|
||||
|
||||
const indexLastContainer = children.indexOf(lastContainer);
|
||||
for (let i = indexLastContainer; i <= children.length - 2; i++) {
|
||||
const sibling = children[i];
|
||||
sibling.properties.y -= space;
|
||||
}
|
||||
}
|
||||
|
||||
const hasNoSpaceBetween = lastContainer === null;
|
||||
if (hasNoSpaceBetween) {
|
||||
// test gap between the left of the parent and the first container
|
||||
space = children[0].properties.y;
|
||||
if (space > 0) {
|
||||
for (let i = 0; i <= children.length - 2; i++) {
|
||||
const sibling = children[i];
|
||||
sibling.properties.y -= space;
|
||||
}
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
import { ENABLE_HARD_RIGID } from '../../../utils/default';
|
||||
|
||||
/**
|
||||
|
@ -20,9 +21,10 @@ import { ENABLE_HARD_RIGID } from '../../../utils/default';
|
|||
* @returns A rigid body container
|
||||
*/
|
||||
export function ApplyRigidBody(
|
||||
container: IContainerModel
|
||||
container: IContainerModel,
|
||||
parent: IContainerModel
|
||||
): IContainerModel {
|
||||
container = ConstraintBodyInsideParent(container);
|
||||
container = ConstraintBodyInsideParent(container, parent);
|
||||
|
||||
if (ENABLE_HARD_RIGID) {
|
||||
container = ConstraintBodyInsideUnallocatedWidth(container);
|
||||
|
@ -40,13 +42,10 @@ export function ApplyRigidBody(
|
|||
* @returns Updated container
|
||||
*/
|
||||
function ConstraintBodyInsideParent(
|
||||
container: IContainerModel
|
||||
container: IContainerModel,
|
||||
parent: IContainerModel
|
||||
): IContainerModel {
|
||||
if (container.parent === null || container.parent === undefined) {
|
||||
return container;
|
||||
}
|
||||
|
||||
const parentProperties = container.parent.properties;
|
||||
const parentProperties = parent.properties;
|
||||
const parentWidth = parentProperties.width;
|
||||
const parentHeight = parentProperties.height;
|
||||
|
||||
|
@ -125,9 +124,15 @@ export function ConstraintBodyInsideUnallocatedWidth(
|
|||
}
|
||||
|
||||
// Get the available spaces of the parent
|
||||
const availableWidths = GetAvailableWidths(container.parent, container);
|
||||
const containerX = container.properties.x;
|
||||
const containerWidth = container.properties.width;
|
||||
const isHorizontal =
|
||||
container.parent.properties.orientation === Orientation.Horizontal;
|
||||
const availableWidths = GetAvailableWidths(
|
||||
0,
|
||||
container.parent.properties.width,
|
||||
container.parent.children,
|
||||
container,
|
||||
isHorizontal
|
||||
);
|
||||
|
||||
// Check if there is still some space
|
||||
if (availableWidths.length === 0) {
|
||||
|
@ -136,6 +141,103 @@ export function ConstraintBodyInsideUnallocatedWidth(
|
|||
);
|
||||
}
|
||||
|
||||
const containerId = container.properties.id;
|
||||
|
||||
if (!isHorizontal) {
|
||||
const containerY = container.properties.y;
|
||||
const containerHeight = container.properties.height;
|
||||
const containerMinHeight = container.properties.minHeight;
|
||||
|
||||
SortAvailableWidthsByClosest(containerY, containerHeight, availableWidths);
|
||||
|
||||
// Check if the container actually fit inside
|
||||
// It will usually fit if it was alrady fitting
|
||||
const availableWidthFound = availableWidths.find((width) =>
|
||||
IsFitting(containerHeight, width)
|
||||
);
|
||||
|
||||
if (availableWidthFound === undefined) {
|
||||
const { x, width } = TrySqueeze(containerY, containerHeight, containerMinHeight, containerId, availableWidths);
|
||||
|
||||
container.properties.y = x;
|
||||
container.properties.height = width;
|
||||
return container;
|
||||
}
|
||||
|
||||
ConstraintBodyInsideSpace(
|
||||
container,
|
||||
0,
|
||||
availableWidthFound.x,
|
||||
container.parent.properties.width,
|
||||
availableWidthFound.width
|
||||
);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
const containerX = container.properties.x;
|
||||
const containerWidth = container.properties.width;
|
||||
const containerMinWidth = container.properties.minWidth;
|
||||
|
||||
SortAvailableWidthsByClosest(containerX, containerWidth, availableWidths);
|
||||
|
||||
// Check if the container actually fit inside
|
||||
// It will usually fit if it was alrady fitting
|
||||
const availableWidthFound = availableWidths.find((width) =>
|
||||
IsFitting(containerWidth, width)
|
||||
);
|
||||
|
||||
if (availableWidthFound === undefined) {
|
||||
const { x, width } = TrySqueeze(containerX, containerWidth, containerMinWidth, containerId, availableWidths);
|
||||
container.properties.x = x;
|
||||
container.properties.width = width;
|
||||
return container;
|
||||
}
|
||||
|
||||
ConstraintBodyInsideSpace(
|
||||
container,
|
||||
availableWidthFound.x,
|
||||
0,
|
||||
availableWidthFound.width,
|
||||
container.parent.properties.height
|
||||
);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
function TrySqueeze(
|
||||
containerX: number,
|
||||
containerWidth: number,
|
||||
containerMinWidth: number,
|
||||
containerId: string,
|
||||
availableWidths: ISizePointer[]
|
||||
): { x: number, width: number } {
|
||||
// Otherwise, it is possible that it does not fit
|
||||
// There is two way to reach this part of the code
|
||||
// 1) Enable isRigidBody such as width > availableWidth.width
|
||||
// 2) Resize a container such as width > availableWidth.width
|
||||
|
||||
// We want the container to fit automatically inside the available space
|
||||
// even if it means to resize the container
|
||||
const availableWidth: ISizePointer | undefined = availableWidths.find((width) => {
|
||||
return IsFitting(containerMinWidth, width);
|
||||
});
|
||||
|
||||
if (availableWidth === undefined) {
|
||||
console.debug(`Container ${containerId} cannot fit in any space due to its minimum width being to large.`);
|
||||
return {
|
||||
x: containerX,
|
||||
width: containerWidth
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
x: availableWidth.x,
|
||||
width: availableWidth.width
|
||||
};
|
||||
}
|
||||
|
||||
function SortAvailableWidthsByClosest(containerX: number, containerWidth: number, availableWidths: ISizePointer[]): void {
|
||||
const middle = containerX + containerWidth / 2;
|
||||
// Sort the available width to find the space with the closest position
|
||||
availableWidths.sort(
|
||||
|
@ -153,42 +255,6 @@ export function ConstraintBodyInsideUnallocatedWidth(
|
|||
return Math.abs(compared1X - middle) - Math.abs(compared2X - middle);
|
||||
}
|
||||
);
|
||||
|
||||
// Check if the container actually fit inside
|
||||
// It will usually fit if it was alrady fitting
|
||||
const availableWidthFound = availableWidths.find((width) =>
|
||||
IsFitting(container.properties.width, width)
|
||||
);
|
||||
|
||||
if (availableWidthFound === undefined) {
|
||||
// Otherwise, it is possible that it does not fit
|
||||
// There is two way to reach this part of the code
|
||||
// 1) Enable isRigidBody such as width > availableWidth.width
|
||||
// 2) Resize a container such as width > availableWidth.width
|
||||
|
||||
// We want the container to fit automatically inside the available space
|
||||
// even if it means to resize the container
|
||||
const availableWidth: ISizePointer | undefined = availableWidths.find((width) => {
|
||||
return IsFitting(container.properties.minWidth, width);
|
||||
});
|
||||
|
||||
if (availableWidth === undefined) {
|
||||
console.debug(`Container ${container.properties.id} cannot fit in any space due to its minimum width being to large.`);
|
||||
return container;
|
||||
}
|
||||
|
||||
container.properties.x = availableWidth.x;
|
||||
container.properties.width = availableWidth.width;
|
||||
return container;
|
||||
}
|
||||
|
||||
return ConstraintBodyInsideSpace(
|
||||
container,
|
||||
availableWidthFound.x,
|
||||
0,
|
||||
availableWidthFound.width,
|
||||
container.parent.properties.height
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -212,16 +278,17 @@ function IsFitting(containerWidth: number,
|
|||
* @returns {ISizePointer[]} Array of unallocated widths (x=position of the unallocated space, width=size of the allocated space)
|
||||
*/
|
||||
function GetAvailableWidths(
|
||||
container: IContainerModel,
|
||||
exception: IContainerModel
|
||||
x: number,
|
||||
width: number,
|
||||
children: IContainerModel[],
|
||||
exception: IContainerModel,
|
||||
isHorizontal: boolean
|
||||
): ISizePointer[] {
|
||||
// Initialize the first size pointer
|
||||
// which takes full width of the available space
|
||||
const x = 0;
|
||||
const width = container.properties.width;
|
||||
let unallocatedSpaces: ISizePointer[] = [{ x, width }];
|
||||
|
||||
for (const child of container.children) {
|
||||
for (const child of children) {
|
||||
if (unallocatedSpaces.length < 1) {
|
||||
return unallocatedSpaces;
|
||||
}
|
||||
|
@ -231,8 +298,8 @@ function GetAvailableWidths(
|
|||
if (child === exception) {
|
||||
continue;
|
||||
}
|
||||
const childX = child.properties.x;
|
||||
const childWidth = child.properties.width;
|
||||
const childX = isHorizontal ? child.properties.x : child.properties.y;
|
||||
const childWidth = isHorizontal ? child.properties.width : child.properties.height;
|
||||
|
||||
// get the space of the child that is inside the parent
|
||||
let newUnallocatedSpace: ISizePointer[] = [];
|
||||
|
|
|
@ -3,15 +3,23 @@
|
|||
*/
|
||||
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { GetOverlappingContainers } from './AnchorBehaviors';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
import { GetHorizontallyOverlappingContainers, GetVerticallyOverlappingContainers } from './AnchorBehaviors';
|
||||
|
||||
export function ApplySwap(container: IContainerModel): void {
|
||||
if (container.parent === null || container.parent === undefined) {
|
||||
export function ApplySwap(container: IContainerModel, parent: IContainerModel): void {
|
||||
const children = parent.children;
|
||||
|
||||
const isVertical = parent.properties.orientation === Orientation.Vertical;
|
||||
if (isVertical) {
|
||||
SwapVertically(container, children);
|
||||
return;
|
||||
}
|
||||
|
||||
const children = container.parent.children;
|
||||
const overlappingContainers = GetOverlappingContainers(container, children);
|
||||
SwapHorizontally(container, children);
|
||||
}
|
||||
|
||||
export function SwapHorizontally(container: IContainerModel, children: IContainerModel[]): void {
|
||||
const overlappingContainers = GetHorizontallyOverlappingContainers(container, children);
|
||||
|
||||
if (overlappingContainers.length > 1 || overlappingContainers.length === 0) {
|
||||
return;
|
||||
|
@ -29,3 +37,23 @@ export function ApplySwap(container: IContainerModel): void {
|
|||
const indexOverlapping = children.indexOf(overlappingContainer);
|
||||
[children[indexContainer], children[indexOverlapping]] = [children[indexOverlapping], children[indexContainer]];
|
||||
}
|
||||
|
||||
export function SwapVertically(container: IContainerModel, children: IContainerModel[]): void {
|
||||
const overlappingContainers = GetVerticallyOverlappingContainers(container, children);
|
||||
|
||||
if (overlappingContainers.length > 1 || overlappingContainers.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const overlappingContainer = overlappingContainers.pop();
|
||||
|
||||
if (overlappingContainer === null || overlappingContainer === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// swap positions
|
||||
[overlappingContainer.properties.y, container.properties.y] = [container.properties.y, overlappingContainer.properties.y];
|
||||
const indexContainer = children.indexOf(container);
|
||||
const indexOverlapping = children.indexOf(overlappingContainer);
|
||||
[children[indexContainer], children[indexOverlapping]] = [children[indexOverlapping], children[indexContainer]];
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { RestoreX, TransformX } from '../../../utils/svg';
|
|||
|
||||
export function ApplySymbol(container: IContainerModel, symbol: ISymbolModel): IContainerModel {
|
||||
container.properties.x = TransformX(symbol.x, symbol.width, symbol.config.XPositionReference);
|
||||
container.properties.x = RestoreX(container.properties.x, container.properties.width, container.properties.xPositionReference);
|
||||
container.properties.x = RestoreX(container.properties.x, container.properties.width, container.properties.positionReference);
|
||||
const [x] = ApplyParentTransform(container.parent, container.properties.x, 0);
|
||||
container.properties.x = x;
|
||||
return container;
|
||||
|
|
|
@ -3,9 +3,10 @@ import * as React from 'react';
|
|||
import { fireEvent, render, screen } from '../../utils/test-utils';
|
||||
import { ElementsSidebar } from './ElementsSidebar';
|
||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||
import { PositionReference } from '../../Enums/PositionReference';
|
||||
import { FindContainerById } from '../../utils/itertools';
|
||||
import { DEFAULT_MAINCONTAINER_PROPS } from '../../utils/default';
|
||||
import { Orientation } from '../../Interfaces/Orientation';
|
||||
|
||||
describe.concurrent('Elements sidebar', () => {
|
||||
it('With a MainContainer', () => {
|
||||
|
@ -94,14 +95,17 @@ describe.concurrent('Elements sidebar', () => {
|
|||
parentId: 'main',
|
||||
linkedSymbolId: '',
|
||||
displayedText: 'child-1',
|
||||
orientation: Orientation.Horizontal,
|
||||
x: 0,
|
||||
y: 0,
|
||||
minWidth: 1,
|
||||
minHeight: 1,
|
||||
width: 0,
|
||||
height: 0,
|
||||
margin: {},
|
||||
isFlex: false,
|
||||
maxWidth: Infinity,
|
||||
maxHeight: Infinity,
|
||||
type: 'type',
|
||||
isAnchor: false,
|
||||
warning: '',
|
||||
|
@ -110,7 +114,7 @@ describe.concurrent('Elements sidebar', () => {
|
|||
showSelfDimensions: true,
|
||||
isDimensionBorrower: true,
|
||||
markPositionToDimensionBorrower: false,
|
||||
xPositionReference: XPositionReference.Left
|
||||
positionReference: PositionReference.TopLeft
|
||||
},
|
||||
userData: {}
|
||||
}
|
||||
|
@ -125,18 +129,21 @@ describe.concurrent('Elements sidebar', () => {
|
|||
parentId: 'main',
|
||||
linkedSymbolId: '',
|
||||
displayedText: 'child-2',
|
||||
orientation: Orientation.Horizontal,
|
||||
x: 0,
|
||||
y: 0,
|
||||
margin: {},
|
||||
minWidth: 1,
|
||||
minHeight: 1,
|
||||
width: 0,
|
||||
height: 0,
|
||||
xPositionReference: XPositionReference.Left,
|
||||
positionReference: PositionReference.TopLeft,
|
||||
isFlex: false,
|
||||
maxWidth: Infinity,
|
||||
maxHeight: Infinity,
|
||||
type: 'type',
|
||||
warning: '',
|
||||
hideChildrenInTreeview: false,
|
||||
hideChildrenInTreeview: false,
|
||||
showChildrenDimensions: true,
|
||||
showSelfDimensions: true,
|
||||
isDimensionBorrower: true,
|
||||
|
@ -182,16 +189,19 @@ describe.concurrent('Elements sidebar', () => {
|
|||
parentId: 'main',
|
||||
linkedSymbolId: '',
|
||||
displayedText: 'child-1',
|
||||
orientation: Orientation.Horizontal,
|
||||
x: 0,
|
||||
y: 0,
|
||||
minWidth: 1,
|
||||
minHeight: 1,
|
||||
width: 0,
|
||||
height: 0,
|
||||
warning: '',
|
||||
xPositionReference: XPositionReference.Left,
|
||||
positionReference: PositionReference.TopLeft,
|
||||
margin: {},
|
||||
isFlex: false,
|
||||
maxWidth: Infinity,
|
||||
maxHeight: Infinity,
|
||||
type: 'type',
|
||||
hideChildrenInTreeview: false,
|
||||
showChildrenDimensions: true,
|
||||
|
|
|
@ -2,10 +2,10 @@ import * as React from 'react';
|
|||
import { FixedSizeList as List } from 'react-window';
|
||||
import { Properties } from '../ContainerProperties/ContainerProperties';
|
||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { FindContainerById, GetDepth, MakeIterator } from '../../utils/itertools';
|
||||
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
|
||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { PropertyType } from '../../Enums/PropertyType';
|
||||
import { ExclamationIcon } from '@heroicons/react/outline';
|
||||
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
interface IElementsSidebarProps {
|
||||
mainContainer: IContainerModel
|
||||
|
@ -124,7 +124,7 @@ export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
|
|||
isOpenClasses = props.isHistoryOpen ? 'right-64' : 'right-0';
|
||||
}
|
||||
|
||||
const it = MakeIterator(props.mainContainer, true);
|
||||
const it = MakeRecursionDFSIterator(props.mainContainer, 0, [0, 0], true);
|
||||
const containers = [...it];
|
||||
function Row({
|
||||
index, style
|
||||
|
@ -132,12 +132,12 @@ export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
|
|||
index: number
|
||||
style: React.CSSProperties
|
||||
}): JSX.Element {
|
||||
const container = containers[index];
|
||||
const depth: number = GetDepth(container);
|
||||
const { container, depth } = containers[index];
|
||||
const key = container.properties.id.toString();
|
||||
const tabs = '|\t'.repeat(depth);
|
||||
const text = container.properties.displayedText === key
|
||||
? `${'|\t'.repeat(depth)} ${key}`
|
||||
: `${'|\t'.repeat(depth)} ${container.properties.displayedText} (${key})`;
|
||||
? `${key}`
|
||||
: `${container.properties.displayedText}`;
|
||||
const selectedClass: string = props.selectedContainer !== undefined &&
|
||||
props.selectedContainer !== null &&
|
||||
props.selectedContainer.properties.id === container.properties.id
|
||||
|
@ -157,9 +157,10 @@ export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
|
|||
onDragOver={(event) => HandleDragOver(event, props.mainContainer)}
|
||||
onDragLeave={(event) => HandleDragLeave(event)}
|
||||
>
|
||||
{tabs}
|
||||
{text}
|
||||
{container.properties.warning.length > 0 &&
|
||||
<ExclamationIcon></ExclamationIcon>
|
||||
<ExclamationTriangleIcon className='w-8'/>
|
||||
}
|
||||
</button>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import { MenuIcon, XIcon } from '@heroicons/react/outline';
|
||||
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
interface IFloatingButtonProps {
|
||||
children: React.ReactNode[] | React.ReactNode
|
||||
|
@ -15,8 +15,8 @@ export function FloatingButton(props: IFloatingButtonProps): JSX.Element {
|
|||
const [isHidden, setHidden] = React.useState(true);
|
||||
const buttonListClasses = isHidden ? 'invisible opacity-0' : 'visible opacity-100';
|
||||
const icon = isHidden
|
||||
? <MenuIcon className="floating-btn" />
|
||||
: <XIcon className="floating-btn" />;
|
||||
? <Bars3Icon className="floating-btn" />
|
||||
: <XMarkIcon className="floating-btn" />;
|
||||
|
||||
return (
|
||||
<div className={`transition-all ${props.className}`}>
|
||||
|
|
|
@ -17,13 +17,11 @@ interface IInputGroupProps {
|
|||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
}
|
||||
|
||||
const className = 'input-group';
|
||||
|
||||
export function InputGroup(props: IInputGroupProps): JSX.Element {
|
||||
return <>
|
||||
<label
|
||||
key={props.labelKey}
|
||||
className={`mt-4 text-xs font-medium text-gray-800 ${props.labelClassName}`}
|
||||
className={`text-xs font-medium text-gray-800 cursor-pointer ${props.labelClassName}`}
|
||||
htmlFor={props.inputKey}
|
||||
>
|
||||
{props.labelText}
|
||||
|
@ -32,7 +30,7 @@ export function InputGroup(props: IInputGroupProps): JSX.Element {
|
|||
<input
|
||||
key={props.inputKey}
|
||||
id={props.inputKey}
|
||||
className={`${className} ${props.inputClassName}`}
|
||||
className={`input-group ${props.inputClassName}`}
|
||||
type={props.type}
|
||||
value={props.value}
|
||||
defaultValue={props.defaultValue}
|
||||
|
|
|
@ -42,7 +42,7 @@ export function TextInputGroup(props: ITextInputGroupProps): JSX.Element {
|
|||
return <>
|
||||
<label
|
||||
key={props.labelKey}
|
||||
className={`mt-4 text-xs font-medium text-gray-800 ${props.labelClassName}`}
|
||||
className={`text-xs font-medium text-gray-800 cursor-pointer ${props.labelClassName}`}
|
||||
htmlFor={props.inputKey}
|
||||
>
|
||||
{props.labelText}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TrashIcon } from '@heroicons/react/outline';
|
||||
import { TrashIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
import { MessageType } from '../../Enums/MessageType';
|
||||
|
|
|
@ -8,24 +8,43 @@ interface IRadioGroupButtonsProps {
|
|||
inputClassName: string
|
||||
labelText: string
|
||||
inputGroups: IInputGroup[]
|
||||
colQty: number
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
}
|
||||
|
||||
// Use whole class name for react to preparse
|
||||
const GRID_COLS = [
|
||||
'grid-cols-none',
|
||||
'grid-cols-1',
|
||||
'grid-cols-2',
|
||||
'grid-cols-3',
|
||||
'grid-cols-4',
|
||||
'grid-cols-5',
|
||||
'grid-cols-6',
|
||||
'grid-cols-7',
|
||||
'grid-cols-8',
|
||||
'grid-cols-9',
|
||||
'grid-cols-10',
|
||||
'grid-cols-11',
|
||||
'grid-cols-12'
|
||||
];
|
||||
|
||||
export function RadioGroupButtons(props: IRadioGroupButtonsProps): JSX.Element {
|
||||
let inputGroups;
|
||||
if (props.value !== undefined) {
|
||||
// dynamic
|
||||
inputGroups = props.inputGroups.map((inputGroup) => (
|
||||
<div key={inputGroup.value}>
|
||||
<div key={inputGroup.key}>
|
||||
<input
|
||||
id={inputGroup.value}
|
||||
key={inputGroup.key}
|
||||
id={inputGroup.key}
|
||||
type='radio'
|
||||
name={props.name}
|
||||
className={`peer m-2 ${props.inputClassName}`}
|
||||
value={inputGroup.value}
|
||||
checked={props.value === inputGroup.value}
|
||||
onChange={props.onChange} />
|
||||
<label htmlFor={inputGroup.value} className='text-gray-400 peer-checked:text-blue-500'>
|
||||
<label htmlFor={inputGroup.key} className='text-gray-400 peer-checked:text-blue-500'>
|
||||
{inputGroup.text}
|
||||
</label>
|
||||
</div>
|
||||
|
@ -34,28 +53,31 @@ export function RadioGroupButtons(props: IRadioGroupButtonsProps): JSX.Element {
|
|||
} else {
|
||||
// static
|
||||
inputGroups = props.inputGroups.map((inputGroup) => (
|
||||
<div key={inputGroup.value}>
|
||||
<div key={inputGroup.key}>
|
||||
<input
|
||||
id={inputGroup.value}
|
||||
key={inputGroup.key}
|
||||
id={inputGroup.key}
|
||||
type='radio'
|
||||
name={props.name}
|
||||
className={`peer m-2 ${props.inputClassName}`}
|
||||
value={inputGroup.value}
|
||||
defaultChecked={props.defaultValue === inputGroup.value} />
|
||||
<label htmlFor={inputGroup.value} className='text-gray-400 peer-checked:text-blue-500'>
|
||||
<label htmlFor={inputGroup.key} className='text-gray-400 peer-checked:text-blue-500'>
|
||||
{inputGroup.text}
|
||||
</label>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
const gridColsClass = GRID_COLS[props.colQty];
|
||||
|
||||
return (
|
||||
<>
|
||||
<label className='mt-4 text-xs font-medium text-gray-800'>
|
||||
<label className='text-xs font-medium text-gray-800'>
|
||||
{props.labelText}
|
||||
</label>
|
||||
<div id='XPositionReference'
|
||||
className='flex flex-col'
|
||||
className={`grid ${gridColsClass}`}
|
||||
>
|
||||
{inputGroups}
|
||||
</div>
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import * as React from 'react';
|
||||
import { Interweave, Node } from 'interweave';
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { DIMENSION_MARGIN, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS, SHOW_TEXT } from '../../../utils/default';
|
||||
import { CancelParentTransform, GetDepth, MakeIterator, Pairwise } from '../../../utils/itertools';
|
||||
import { Dimension } from './Dimension';
|
||||
import { IContainerProperties } from '../../../Interfaces/IContainerProperties';
|
||||
import { TransformX } from '../../../utils/svg';
|
||||
import { Camelize } from '../../../utils/stringtools';
|
||||
import { SHOW_TEXT } from '../../../utils/default';
|
||||
|
||||
interface IContainerProps {
|
||||
model: IContainerModel
|
||||
depth: number
|
||||
scale: number
|
||||
}
|
||||
|
||||
|
@ -22,6 +20,7 @@ export function Container(props: IContainerProps): JSX.Element {
|
|||
child => <Container
|
||||
key={`container-${child.properties.id}`}
|
||||
model={child}
|
||||
depth={props.depth + 1}
|
||||
scale={props.scale}
|
||||
/>);
|
||||
|
||||
|
@ -57,71 +56,6 @@ export function Container(props: IContainerProps): JSX.Element {
|
|||
style={style}
|
||||
>
|
||||
</rect>);
|
||||
// Dimension props
|
||||
const depth = GetDepth(props.model);
|
||||
const dimensionMargin = DIMENSION_MARGIN * depth / props.scale;
|
||||
const id = `dim-${props.model.properties.id}`;
|
||||
const xStart: number = 0;
|
||||
const xEnd = width;
|
||||
const yDim = -dimensionMargin;
|
||||
const strokeWidth = 1;
|
||||
const text = (width.toFixed(0) ?? 0).toString();
|
||||
|
||||
let childrenDimensions: JSX.Element | null = null;
|
||||
if (props.model.properties.showChildrenDimensions && props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
|
||||
const {
|
||||
childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren
|
||||
} = GetChildrenDimensionProps(props, dimensionMargin);
|
||||
childrenDimensions = <Dimension
|
||||
id={childrenId}
|
||||
xStart={xChildrenStart}
|
||||
xEnd={xChildrenEnd}
|
||||
yStart={yChildren}
|
||||
yEnd={yChildren}
|
||||
strokeWidth={strokeWidth}
|
||||
text={textChildren}
|
||||
scale={props.scale} />;
|
||||
}
|
||||
|
||||
const borrowerDimensions: JSX.Element[] = [];
|
||||
if (props.model.properties.isDimensionBorrower && SHOW_BORROWER_DIMENSIONS) {
|
||||
const it = MakeIterator(props.model);
|
||||
const marks = [];
|
||||
for (const container of it) {
|
||||
if (!container.properties.markPositionToDimensionBorrower) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const x = TransformX(
|
||||
container.properties.x,
|
||||
container.properties.width,
|
||||
container.properties.xPositionReference
|
||||
);
|
||||
const [restoredX] = CancelParentTransform(container.parent, x, 0, props.model);
|
||||
marks.push(
|
||||
restoredX
|
||||
);
|
||||
}
|
||||
marks.push(0);
|
||||
marks.push(props.model.properties.width);
|
||||
marks.sort((a, b) => a - b);
|
||||
let count = 0;
|
||||
for (const { cur, next } of Pairwise(marks)) {
|
||||
const id = `dim-borrow-${props.model.properties.id}-{${count}}`;
|
||||
borrowerDimensions.push(<Dimension
|
||||
key={id}
|
||||
id={id}
|
||||
xStart={cur}
|
||||
xEnd={next}
|
||||
yStart={props.model.properties.height + yDim * -1}
|
||||
yEnd={props.model.properties.height + yDim * -1}
|
||||
strokeWidth={strokeWidth}
|
||||
text={(next - cur).toFixed(0).toString()}
|
||||
scale={props.scale}
|
||||
/>);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<g
|
||||
|
@ -129,19 +63,6 @@ export function Container(props: IContainerProps): JSX.Element {
|
|||
transform={transform}
|
||||
key={`container-${props.model.properties.id}`}
|
||||
>
|
||||
{(props.model.properties.showSelfDimensions && SHOW_SELF_DIMENSIONS)
|
||||
? <Dimension
|
||||
id={id}
|
||||
xStart={xStart}
|
||||
xEnd={xEnd}
|
||||
yStart={yDim}
|
||||
yEnd={yDim}
|
||||
strokeWidth={strokeWidth}
|
||||
text={text}
|
||||
scale={props.scale} />
|
||||
: null}
|
||||
{childrenDimensions}
|
||||
{borrowerDimensions}
|
||||
{svg}
|
||||
{SHOW_TEXT
|
||||
? <text
|
||||
|
@ -160,33 +81,6 @@ export function Container(props: IContainerProps): JSX.Element {
|
|||
);
|
||||
}
|
||||
|
||||
function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: number): { childrenId: string, xChildrenStart: number, xChildrenEnd: number, yChildren: number, textChildren: string } {
|
||||
const childrenId = `dim-children-${props.model.properties.id}`;
|
||||
|
||||
const lastChild = props.model.children[props.model.children.length - 1];
|
||||
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.xPositionReference);
|
||||
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.xPositionReference);
|
||||
|
||||
// Find the min and max
|
||||
for (let i = props.model.children.length - 2; i >= 0; i--) {
|
||||
const child = props.model.children[i];
|
||||
const left = TransformX(child.properties.x, child.properties.width, child.properties.xPositionReference);
|
||||
if (left < xChildrenStart) {
|
||||
xChildrenStart = left;
|
||||
}
|
||||
const right = TransformX(child.properties.x, child.properties.width, child.properties.xPositionReference);
|
||||
if (right > xChildrenEnd) {
|
||||
xChildrenEnd = right;
|
||||
}
|
||||
}
|
||||
|
||||
const yChildren = props.model.properties.height + dimensionMargin;
|
||||
const textChildren = (xChildrenEnd - xChildrenStart)
|
||||
.toFixed(2)
|
||||
.toString();
|
||||
return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren };
|
||||
}
|
||||
|
||||
function CreateReactCustomSVG(customSVG: string, props: IContainerProperties): React.ReactNode {
|
||||
return <Interweave
|
||||
tagName='g'
|
||||
|
|
|
@ -3,7 +3,6 @@ import { ContainerModel } from '../../../Interfaces/IContainerModel';
|
|||
import { DIMENSION_MARGIN } from '../../../utils/default';
|
||||
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
|
||||
import { TransformX } from '../../../utils/svg';
|
||||
import { Properties } from '../../ContainerProperties/ContainerProperties';
|
||||
import { Dimension } from './Dimension';
|
||||
|
||||
interface IDimensionLayerProps {
|
||||
|
@ -28,7 +27,7 @@ function GetDimensionsNodes(root: ContainerModel, scale: number): React.ReactNod
|
|||
}
|
||||
|
||||
const absoluteX = GetAbsolutePosition(container)[0];
|
||||
const x = TransformX(absoluteX, container.properties.width, container.properties.xPositionReference);
|
||||
const x = TransformX(absoluteX, container.properties.width, container.properties.positionReference);
|
||||
lastY = container.properties.y + container.properties.height;
|
||||
if (x < min) {
|
||||
min = x;
|
||||
|
|
|
@ -35,6 +35,7 @@ export function Dimension(props: IDimensionProps): JSX.Element {
|
|||
/// We need to find the points of the notches
|
||||
// Get the vector of the line
|
||||
const [deltaX, deltaY] = [(props.xEnd - props.xStart), (props.yEnd - props.yStart)];
|
||||
const rotation = (Math.atan2(deltaY, deltaX) / (2 * Math.PI));
|
||||
|
||||
// Get the unit vector
|
||||
const norm = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
|
@ -80,9 +81,9 @@ export function Dimension(props: IDimensionProps): JSX.Element {
|
|||
style={style} />
|
||||
<text
|
||||
x={(props.xStart + props.xEnd) / 2}
|
||||
y={props.yStart}
|
||||
y={(props.yStart + props.yEnd) / 2}
|
||||
style={{
|
||||
transform: `scale(${1 / scale}) translateX(-50%) translateY(-100%)`,
|
||||
transform: `rotate(${rotation}turn) scale(${1 / scale}) translateX(-50%) translateY(-100%)`,
|
||||
transformBox: 'fill-box'
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -1,39 +1,328 @@
|
|||
import * as React from 'react';
|
||||
import { ContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { DIMENSION_MARGIN } from '../../../utils/default';
|
||||
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
|
||||
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { DIMENSION_MARGIN, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS } from '../../../utils/default';
|
||||
import { MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
|
||||
import { TransformX, TransformY } from '../../../utils/svg';
|
||||
import { Dimension } from './Dimension';
|
||||
|
||||
interface IDimensionLayerProps {
|
||||
roots: ContainerModel | ContainerModel[] | null
|
||||
root: ContainerModel
|
||||
scale: number
|
||||
}
|
||||
|
||||
function GetDimensionsNodes(root: ContainerModel): React.ReactNode[] {
|
||||
const it = MakeBFSIterator(root);
|
||||
const MODULE_STROKE_WIDTH = 1;
|
||||
|
||||
function Dimensions({ root, scale }: IDimensionLayerProps): React.ReactNode[] {
|
||||
const it = MakeRecursionDFSIterator(root, 0, [0, 0]);
|
||||
const dimensions: React.ReactNode[] = [];
|
||||
for (const { container, depth } of it) {
|
||||
const width = container.properties.width;
|
||||
const id = `dim-${container.properties.id}`;
|
||||
const xStart = GetAbsolutePosition(container)[0];
|
||||
const xEnd = xStart + width;
|
||||
const y = (container.properties.y + container.properties.height) + (DIMENSION_MARGIN * (depth + 1));
|
||||
const strokeWidth = 1;
|
||||
const text = width
|
||||
.toFixed(0)
|
||||
.toString();
|
||||
dimensions.push(
|
||||
<Dimension
|
||||
key={id}
|
||||
id={id}
|
||||
xStart={xStart}
|
||||
yStart={y}
|
||||
xEnd={xEnd}
|
||||
yEnd={y}
|
||||
strokeWidth={strokeWidth}
|
||||
text={text} />
|
||||
const topDim = root.properties.y;
|
||||
const leftDim = root.properties.x;
|
||||
const rightDim = root.properties.x + root.properties.width;
|
||||
const bottomDim = root.properties.y + root.properties.height;
|
||||
|
||||
if (!SHOW_SELF_DIMENSIONS) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (const { container, depth, currentTransform } of it) {
|
||||
const containerLeftDim = leftDim - (DIMENSION_MARGIN * (depth + 1)) / scale;
|
||||
const containerTopDim = topDim - (DIMENSION_MARGIN * (depth + 1)) / scale;
|
||||
const containerBottomDim = bottomDim + (DIMENSION_MARGIN * (depth + 1)) / scale;
|
||||
const containerRightDim = rightDim + (DIMENSION_MARGIN * (depth + 1)) / scale;
|
||||
if (SHOW_SELF_DIMENSIONS && container.properties.showSelfDimensions) {
|
||||
AddSelfDimension(container, currentTransform, containerTopDim, containerLeftDim, scale, dimensions);
|
||||
}
|
||||
|
||||
if (SHOW_BORROWER_DIMENSIONS && container.properties.isDimensionBorrower) {
|
||||
AddBorrowerDimension(containerBottomDim, containerRightDim, depth, scale, container, currentTransform, dimensions);
|
||||
}
|
||||
|
||||
if (SHOW_CHILDREN_DIMENSIONS && container.properties.showChildrenDimensions && container.children.length > 0) {
|
||||
AddChildrenDimension(container, currentTransform, dimensions, containerBottomDim, containerRightDim, scale);
|
||||
}
|
||||
}
|
||||
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
function AddChildrenDimension(
|
||||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
containerBottomDim: number,
|
||||
containerRightDim: number,
|
||||
scale: number
|
||||
): void {
|
||||
AddHorizontalChildrenDimension(container, currentTransform, dimensions, containerBottomDim, scale);
|
||||
AddVerticalChildrenDimension(container, currentTransform, dimensions, containerRightDim, scale);
|
||||
}
|
||||
|
||||
function AddHorizontalChildrenDimension(
|
||||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
containerBottomDim: number,
|
||||
scale: number
|
||||
): void {
|
||||
const childrenId = `dim-children-${container.properties.id}`;
|
||||
|
||||
const lastChild = container.children[container.children.length - 1];
|
||||
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
|
||||
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
|
||||
|
||||
// Find the min and max
|
||||
for (let i = container.children.length - 2; i >= 0; i--) {
|
||||
const child = container.children[i];
|
||||
const left = TransformX(child.properties.x, child.properties.width, child.properties.positionReference);
|
||||
if (left < xChildrenStart) {
|
||||
xChildrenStart = left;
|
||||
}
|
||||
const right = TransformX(child.properties.x, child.properties.width, child.properties.positionReference);
|
||||
if (right > xChildrenEnd) {
|
||||
xChildrenEnd = right;
|
||||
}
|
||||
}
|
||||
|
||||
const textChildren = (xChildrenEnd - xChildrenStart)
|
||||
.toFixed(2)
|
||||
.toString();
|
||||
|
||||
const offset = currentTransform[0] + container.properties.x;
|
||||
dimensions.push(<Dimension
|
||||
key={childrenId}
|
||||
id={childrenId}
|
||||
xStart={xChildrenStart + offset}
|
||||
xEnd={xChildrenEnd + offset}
|
||||
yStart={containerBottomDim}
|
||||
yEnd={containerBottomDim}
|
||||
strokeWidth={MODULE_STROKE_WIDTH}
|
||||
text={textChildren}
|
||||
scale={scale} />);
|
||||
}
|
||||
|
||||
function AddVerticalChildrenDimension(
|
||||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
containerRightDim: number,
|
||||
scale: number
|
||||
): void {
|
||||
const childrenId = `dim-v-children-${container.properties.id}`;
|
||||
|
||||
const lastChild = container.children[container.children.length - 1];
|
||||
let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
|
||||
let yChildrenEnd = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
|
||||
|
||||
// Find the min and max
|
||||
for (let i = container.children.length - 2; i >= 0; i--) {
|
||||
const child = container.children[i];
|
||||
const top = TransformY(child.properties.y, child.properties.height, child.properties.positionReference);
|
||||
if (top < yChildrenStart) {
|
||||
yChildrenStart = top;
|
||||
}
|
||||
const bottom = TransformY(child.properties.y, child.properties.height, child.properties.positionReference);
|
||||
if (bottom > yChildrenEnd) {
|
||||
yChildrenEnd = bottom;
|
||||
}
|
||||
}
|
||||
|
||||
const textChildren = (yChildrenEnd - yChildrenStart)
|
||||
.toFixed(2)
|
||||
.toString();
|
||||
|
||||
const offset = currentTransform[0] + container.properties.x;
|
||||
dimensions.push(<Dimension
|
||||
key={childrenId}
|
||||
id={childrenId}
|
||||
xStart={containerRightDim}
|
||||
yStart={yChildrenStart + offset}
|
||||
xEnd={containerRightDim}
|
||||
yEnd={yChildrenEnd + offset}
|
||||
strokeWidth={MODULE_STROKE_WIDTH}
|
||||
text={textChildren}
|
||||
scale={scale}
|
||||
/>);
|
||||
}
|
||||
|
||||
function AddBorrowerDimension(
|
||||
bottomDim: number,
|
||||
rightDim: number,
|
||||
depth: number,
|
||||
scale: number,
|
||||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[]
|
||||
): void {
|
||||
AddHorizontalBorrowerDimension(bottomDim, container, depth, currentTransform, dimensions, scale);
|
||||
AddVerticalBorrowerDimension(rightDim, container, depth, currentTransform, dimensions, scale);
|
||||
}
|
||||
|
||||
function AddHorizontalBorrowerDimension(
|
||||
bottomDim: number,
|
||||
container: IContainerModel,
|
||||
depth: number,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number
|
||||
): void {
|
||||
const yDim = bottomDim;
|
||||
const it = MakeRecursionDFSIterator(container, depth, currentTransform);
|
||||
const marks = []; // list of vertical lines for the dimension
|
||||
for (const {
|
||||
container: childContainer, currentTransform: childCurrentTransform
|
||||
} of it) {
|
||||
if (!childContainer.properties.markPositionToDimensionBorrower) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const x = TransformX(
|
||||
childContainer.properties.x,
|
||||
childContainer.properties.width,
|
||||
childContainer.properties.positionReference
|
||||
);
|
||||
|
||||
const restoredX = x + childCurrentTransform[0];
|
||||
|
||||
marks.push(
|
||||
restoredX
|
||||
);
|
||||
}
|
||||
return dimensions;
|
||||
|
||||
const restoredX = container.properties.x + currentTransform[0];
|
||||
marks.push(restoredX);
|
||||
marks.push(restoredX + container.properties.width);
|
||||
marks.sort((a, b) => a - b);
|
||||
let count = 0;
|
||||
for (const { cur, next } of Pairwise(marks)) {
|
||||
const id = `dim-borrow-${container.properties.id}-{${count}}`;
|
||||
dimensions.push(<Dimension
|
||||
key={id}
|
||||
id={id}
|
||||
xStart={cur}
|
||||
xEnd={next}
|
||||
yStart={yDim}
|
||||
yEnd={yDim}
|
||||
strokeWidth={MODULE_STROKE_WIDTH}
|
||||
text={(next - cur).toFixed(0).toString()}
|
||||
scale={scale} />);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
function AddVerticalBorrowerDimension(
|
||||
rightDim: number,
|
||||
container: IContainerModel,
|
||||
depth: number,
|
||||
currentTransform: [number, number],
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number
|
||||
): void {
|
||||
const xDim = rightDim;
|
||||
const it = MakeRecursionDFSIterator(container, depth, currentTransform);
|
||||
const marks = []; // list of vertical lines for the dimension
|
||||
for (const {
|
||||
container: childContainer, currentTransform: childCurrentTransform
|
||||
} of it) {
|
||||
if (!childContainer.properties.markPositionToDimensionBorrower) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const y = TransformY(
|
||||
childContainer.properties.y,
|
||||
childContainer.properties.height,
|
||||
childContainer.properties.positionReference
|
||||
);
|
||||
|
||||
const restoredy = y + childCurrentTransform[1];
|
||||
|
||||
marks.push(
|
||||
restoredy
|
||||
);
|
||||
}
|
||||
|
||||
const restoredY = container.properties.y + currentTransform[1];
|
||||
marks.push(restoredY);
|
||||
marks.push(restoredY + container.properties.height);
|
||||
marks.sort((a, b) => a - b);
|
||||
let count = 0;
|
||||
for (const { cur, next } of Pairwise(marks)) {
|
||||
const id = `dim-v-borrow-${container.properties.id}-{${count}}`;
|
||||
dimensions.push(<Dimension
|
||||
key={id}
|
||||
id={id}
|
||||
xStart={xDim}
|
||||
xEnd={xDim}
|
||||
yStart={cur}
|
||||
yEnd={next}
|
||||
strokeWidth={MODULE_STROKE_WIDTH}
|
||||
text={(next - cur).toFixed(0).toString()}
|
||||
scale={scale} />);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
function AddSelfDimension(
|
||||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
topDim: number,
|
||||
leftDim: number,
|
||||
scale: number,
|
||||
dimensions: React.ReactNode[]
|
||||
): void {
|
||||
AddHorizontalSelfDimension(container, currentTransform, topDim, dimensions, scale);
|
||||
AddVerticalSelfDimension(container, currentTransform, leftDim, dimensions, scale);
|
||||
}
|
||||
|
||||
function AddVerticalSelfDimension(container: IContainerModel, currentTransform: [number, number], leftDim: number, dimensions: React.ReactNode[], scale: number): void {
|
||||
const height = container.properties.height;
|
||||
const idVert = `dim-v-${container.properties.id}`;
|
||||
const yStart = container.properties.y + currentTransform[1];
|
||||
const yEnd = yStart + height;
|
||||
const x = leftDim;
|
||||
const textVert = height
|
||||
.toFixed(0)
|
||||
.toString();
|
||||
dimensions.push(
|
||||
<Dimension
|
||||
key={idVert}
|
||||
id={idVert}
|
||||
xStart={x}
|
||||
yStart={yStart}
|
||||
xEnd={x}
|
||||
yEnd={yEnd}
|
||||
strokeWidth={MODULE_STROKE_WIDTH}
|
||||
text={textVert}
|
||||
scale={scale} />
|
||||
);
|
||||
}
|
||||
|
||||
function AddHorizontalSelfDimension(
|
||||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
topDim: number,
|
||||
dimensions: React.ReactNode[],
|
||||
scale: number
|
||||
): void {
|
||||
const width = container.properties.width;
|
||||
const id = `dim-${container.properties.id}`;
|
||||
const xStart = container.properties.x + currentTransform[0];
|
||||
const xEnd = xStart + width;
|
||||
const y = topDim;
|
||||
const text = width
|
||||
.toFixed(0)
|
||||
.toString();
|
||||
dimensions.push(
|
||||
<Dimension
|
||||
key={id}
|
||||
id={id}
|
||||
xStart={xStart}
|
||||
yStart={y}
|
||||
xEnd={xEnd}
|
||||
yEnd={y}
|
||||
strokeWidth={MODULE_STROKE_WIDTH}
|
||||
text={text}
|
||||
scale={scale} />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,17 +331,9 @@ function GetDimensionsNodes(root: ContainerModel): React.ReactNode[] {
|
|||
* @returns
|
||||
*/
|
||||
export function DimensionLayer(props: IDimensionLayerProps): JSX.Element {
|
||||
let dimensions: React.ReactNode[] = [];
|
||||
if (Array.isArray(props.roots)) {
|
||||
props.roots.forEach(child => {
|
||||
dimensions.concat(GetDimensionsNodes(child));
|
||||
});
|
||||
} else if (props.roots !== null) {
|
||||
dimensions = GetDimensionsNodes(props.roots);
|
||||
}
|
||||
return (
|
||||
<g>
|
||||
{dimensions}
|
||||
{ Dimensions(props) }
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,11 +9,12 @@ import { DepthDimensionLayer } from './Elements/DepthDimensionLayer';
|
|||
import { MAX_FRAMERATE, SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default';
|
||||
import { SymbolLayer } from './Elements/SymbolLayer';
|
||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
import { DimensionLayer } from './Elements/DimensionLayer';
|
||||
|
||||
interface ISVGProps {
|
||||
width: number
|
||||
height: number
|
||||
children: ContainerModel | ContainerModel[] | null
|
||||
children: ContainerModel
|
||||
selected?: ContainerModel
|
||||
symbols: Map<string, ISymbolModel>
|
||||
}
|
||||
|
@ -69,7 +70,7 @@ export function SVG(props: ISVGProps): JSX.Element {
|
|||
// console.log(renderCounter.current / ((Date.now() - startTimer.current) / 1000));
|
||||
|
||||
UseSVGAutoResizer(setViewer);
|
||||
UseFitOnce(svgViewer);
|
||||
UseFitOnce(svgViewer, props.width, props.height);
|
||||
|
||||
const xmlns = '<http://www.w3.org/2000/svg>';
|
||||
const properties = {
|
||||
|
@ -79,20 +80,12 @@ export function SVG(props: ISVGProps): JSX.Element {
|
|||
};
|
||||
|
||||
let children: React.ReactNode | React.ReactNode[] = [];
|
||||
if (Array.isArray(props.children)) {
|
||||
children = props.children.map(child =>
|
||||
<Container
|
||||
key={`container-${child.properties.id}`}
|
||||
model={child}
|
||||
scale={scale}
|
||||
/>);
|
||||
} else if (props.children !== null) {
|
||||
children = <Container
|
||||
key={`container-${props.children.properties.id}`}
|
||||
model={props.children}
|
||||
scale={scale}
|
||||
/>;
|
||||
}
|
||||
children = <Container
|
||||
key={`container-${props.children.properties.id}`}
|
||||
model={props.children}
|
||||
depth={0}
|
||||
scale={scale}
|
||||
/>;
|
||||
|
||||
return (
|
||||
<div id={ID} className='ml-16'>
|
||||
|
@ -123,7 +116,7 @@ export function SVG(props: ISVGProps): JSX.Element {
|
|||
miniatureProps={{
|
||||
position: 'left',
|
||||
background: '#616264',
|
||||
width: window.innerWidth - 12 - BAR_WIDTH,
|
||||
width: 120,
|
||||
height: 120
|
||||
}}
|
||||
>
|
||||
|
@ -132,6 +125,7 @@ export function SVG(props: ISVGProps): JSX.Element {
|
|||
{SHOW_DIMENSIONS_PER_DEPTH
|
||||
? <DepthDimensionLayer scale={scale} roots={props.children} />
|
||||
: null}
|
||||
<DimensionLayer scale={scale} root={props.children} />
|
||||
<SymbolLayer scale={scale} symbols={props.symbols} />
|
||||
<Selector scale={scale} selected={props.selected} /> {/* leave this at the end so it can be removed during the svg export */}
|
||||
</svg>
|
||||
|
@ -140,9 +134,8 @@ export function SVG(props: ISVGProps): JSX.Element {
|
|||
);
|
||||
}
|
||||
|
||||
function UseFitOnce(svgViewer: React.RefObject<ReactSVGPanZoom>): void {
|
||||
function UseFitOnce(svgViewer: React.RefObject<ReactSVGPanZoom>, width: number, height: number): void {
|
||||
React.useEffect(() => {
|
||||
svgViewer?.current?.fitToViewer();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [svgViewer, width, height]);
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ export function Select(props: ISelectProps): JSX.Element {
|
|||
<>
|
||||
<label
|
||||
key={props.labelKey}
|
||||
className={`mt-4 text-xs font-medium text-gray-800 ${props.labelClassName}`}
|
||||
className={`text-xs font-medium text-gray-800 cursor-pointer ${props.labelClassName}`}
|
||||
htmlFor={props.inputKey}
|
||||
>
|
||||
{props.labelText}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { EyeIcon, EyeOffIcon } from '@heroicons/react/outline';
|
||||
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
|
||||
import * as React from 'react';
|
||||
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
||||
import { ICategory } from '../../Interfaces/ICategory';
|
||||
|
@ -108,8 +108,8 @@ export function Sidebar(props: ISidebarProps): JSX.Element {
|
|||
>
|
||||
{
|
||||
hideDisabled
|
||||
? <EyeOffIcon className='heroicon'></EyeOffIcon>
|
||||
: <EyeIcon className='heroicon'></EyeIcon>
|
||||
? <EyeSlashIcon className='heroicon' />
|
||||
: <EyeIcon className='heroicon' />
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -2,51 +2,52 @@ import React from 'react';
|
|||
import './ToggleButton.scss';
|
||||
|
||||
interface IToggleButtonProps {
|
||||
id: string
|
||||
text: string
|
||||
labelKey?: string
|
||||
labelText: string
|
||||
inputKey: string
|
||||
labelClassName: string
|
||||
inputClassName: string
|
||||
type?: ToggleType
|
||||
title: string
|
||||
checked: boolean
|
||||
onChange: React.ChangeEventHandler<HTMLInputElement>
|
||||
}
|
||||
|
||||
export enum ToggleType {
|
||||
Material,
|
||||
IOS
|
||||
Full
|
||||
}
|
||||
|
||||
export function ToggleButton(props: IToggleButtonProps): JSX.Element {
|
||||
const id = `toggle-${props.id}`;
|
||||
const type = props.type ?? ToggleType.Material;
|
||||
let classLine = 'line w-10 h-4 bg-gray-400 rounded-full shadow-inner';
|
||||
let classDot = 'dot absolute w-6 h-6 bg-white rounded-full shadow -left-1 -top-1 transition';
|
||||
if (type === ToggleType.IOS) {
|
||||
classLine = 'line block bg-gray-600 w-14 h-8 rounded-full';
|
||||
classDot = 'dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition';
|
||||
if (type === ToggleType.Full) {
|
||||
classLine = 'line block bg-gray-600 w-12 h-7 rounded-full';
|
||||
classDot = 'dot absolute left-1 top-1 bg-white w-5 h-5 rounded-full transition';
|
||||
}
|
||||
|
||||
return (
|
||||
<div title={props.title}>
|
||||
<div className="flex items-center justify-center w-full mb-12">
|
||||
<label
|
||||
htmlFor={id}
|
||||
className="flex items-center cursor-pointer"
|
||||
>
|
||||
<div className="relative">
|
||||
<input
|
||||
id={id}
|
||||
type="checkbox"
|
||||
onChange={props.onChange}
|
||||
checked={props.checked}
|
||||
className="sr-only" />
|
||||
<div className={classLine}></div>
|
||||
<div className={classDot}></div>
|
||||
</div>
|
||||
<div className="ml-3 text-gray-700 font-medium">
|
||||
{props.text}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <>
|
||||
<label
|
||||
key={props.labelKey}
|
||||
className={`text-xs font-medium text-gray-800 cursor-pointer ${props.labelClassName}`}
|
||||
htmlFor={props.inputKey}
|
||||
>
|
||||
{ props.labelText }
|
||||
</label>
|
||||
<label
|
||||
className="relative cursor-pointer"
|
||||
htmlFor={props.inputKey}
|
||||
>
|
||||
<input
|
||||
key={props.inputKey}
|
||||
id={props.inputKey}
|
||||
type="checkbox"
|
||||
onChange={props.onChange}
|
||||
checked={props.checked}
|
||||
className="sr-only"
|
||||
/>
|
||||
<div className={classLine}></div>
|
||||
<div className={classDot}></div>
|
||||
</label>
|
||||
</>;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { History } from '../History/History';
|
|||
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||
import { PhotographIcon, UploadIcon } from '@heroicons/react/outline';
|
||||
import { CameraIcon, ArrowUpOnSquareIcon } from '@heroicons/react/24/outline';
|
||||
import { FloatingButton } from '../FloatingButton/FloatingButton';
|
||||
import { Bar } from '../Bar/Bar';
|
||||
import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
|
||||
|
@ -127,14 +127,14 @@ export function UI(props: IUIProps): JSX.Element {
|
|||
title='Export as JSON'
|
||||
onClick={props.saveEditorAsJSON}
|
||||
>
|
||||
<UploadIcon className="heroicon text-white" />
|
||||
<CameraIcon className="heroicon text-white" />
|
||||
</button>
|
||||
<button type="button"
|
||||
className={'transition-all w-10 h-10 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
|
||||
title='Export as SVG'
|
||||
onClick={props.saveEditorAsSVG}
|
||||
>
|
||||
<PhotographIcon className="heroicon text-white" />
|
||||
<ArrowUpOnSquareIcon className="heroicon text-white" />
|
||||
</button>
|
||||
</FloatingButton>
|
||||
</>
|
||||
|
|
11
src/Enums/PositionReference.ts
Normal file
11
src/Enums/PositionReference.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
export enum PositionReference {
|
||||
TopLeft,
|
||||
TopCenter,
|
||||
TopRight,
|
||||
CenterLeft,
|
||||
CenterCenter,
|
||||
CenterRight,
|
||||
BottomLeft,
|
||||
BottomCenter,
|
||||
BottomRight
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
export enum XPositionReference {
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import React from 'react';
|
||||
import { AddMethod } from '../Enums/AddMethod';
|
||||
import { XPositionReference } from '../Enums/XPositionReference';
|
||||
import { PositionReference } from '../Enums/PositionReference';
|
||||
import { IAction } from './IAction';
|
||||
import { IMargin } from './IMargin';
|
||||
import { Orientation } from './Orientation';
|
||||
|
||||
/** Model of available container used in application configuration */
|
||||
export interface IAvailableContainer {
|
||||
|
@ -16,6 +17,9 @@ export interface IAvailableContainer {
|
|||
/** category */
|
||||
Category?: string
|
||||
|
||||
/** orientation */
|
||||
Orientation?: Orientation
|
||||
|
||||
/** horizontal offset */
|
||||
X?: number
|
||||
|
||||
|
@ -30,8 +34,6 @@ export interface IAvailableContainer {
|
|||
|
||||
/**
|
||||
* Minimum width (min=1)
|
||||
* Allows the container to set isRigidBody to false when it gets squeezed
|
||||
* by an anchor
|
||||
*/
|
||||
MinWidth?: number
|
||||
|
||||
|
@ -40,6 +42,16 @@ export interface IAvailableContainer {
|
|||
*/
|
||||
MaxWidth?: number
|
||||
|
||||
/**
|
||||
* Minimum height (min=1)
|
||||
*/
|
||||
MinHeight?: number
|
||||
|
||||
/**
|
||||
* Maximum height
|
||||
*/
|
||||
MaxHeight?: number
|
||||
|
||||
/** margin */
|
||||
Margin?: IMargin
|
||||
|
||||
|
@ -53,7 +65,7 @@ export interface IAvailableContainer {
|
|||
AddMethod?: AddMethod
|
||||
|
||||
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
||||
XPositionReference?: XPositionReference
|
||||
PositionReference?: PositionReference
|
||||
|
||||
/**
|
||||
* (optional)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { XPositionReference } from '../Enums/XPositionReference';
|
||||
import { PositionReference } from '../Enums/PositionReference';
|
||||
import { IImage } from './IImage';
|
||||
|
||||
/**
|
||||
|
@ -9,5 +9,5 @@ export interface IAvailableSymbol {
|
|||
Image: IImage
|
||||
Width?: number
|
||||
Height?: number
|
||||
XPositionReference?: XPositionReference
|
||||
XPositionReference?: PositionReference
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import { XPositionReference } from '../Enums/XPositionReference';
|
||||
import { PositionReference } from '../Enums/PositionReference';
|
||||
import { IMargin } from './IMargin';
|
||||
import { Orientation } from './Orientation';
|
||||
|
||||
/**
|
||||
* Properties of a container
|
||||
|
@ -21,6 +22,9 @@ export interface IContainerProperties {
|
|||
/** Text displayed in the container */
|
||||
displayedText: string
|
||||
|
||||
/** orientation */
|
||||
orientation: Orientation
|
||||
|
||||
/** horizontal offset */
|
||||
x: number
|
||||
|
||||
|
@ -30,10 +34,14 @@ export interface IContainerProperties {
|
|||
/** margin */
|
||||
margin: IMargin
|
||||
|
||||
/** width */
|
||||
width: number
|
||||
|
||||
/** height */
|
||||
height: number
|
||||
|
||||
/**
|
||||
* Minimum width (min=1)
|
||||
* Allows the container to set isRigidBody to false when it gets squeezed
|
||||
* by an anchor
|
||||
*/
|
||||
minWidth: number
|
||||
|
||||
|
@ -42,11 +50,15 @@ export interface IContainerProperties {
|
|||
*/
|
||||
maxWidth: number
|
||||
|
||||
/** width */
|
||||
width: number
|
||||
/**
|
||||
* Minimum height (min=1)
|
||||
*/
|
||||
minHeight: number
|
||||
|
||||
/** height */
|
||||
height: number
|
||||
/**
|
||||
* Maximum height
|
||||
*/
|
||||
maxHeight: number
|
||||
|
||||
/** true if anchor, false otherwise */
|
||||
isAnchor: boolean
|
||||
|
@ -55,7 +67,7 @@ export interface IContainerProperties {
|
|||
isFlex: boolean
|
||||
|
||||
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
||||
xPositionReference: XPositionReference
|
||||
positionReference: PositionReference
|
||||
|
||||
/** Hide the children in the treeview */
|
||||
hideChildrenInTreeview: boolean
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
export interface IInputGroup {
|
||||
key: string
|
||||
text: React.ReactNode
|
||||
value: string
|
||||
}
|
||||
|
|
4
src/Interfaces/Orientation.ts
Normal file
4
src/Interfaces/Orientation.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export enum Orientation {
|
||||
Horizontal,
|
||||
Vertical
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { XPositionReference } from '../Enums/XPositionReference';
|
||||
import { PositionReference } from '../Enums/PositionReference';
|
||||
import { IAvailableContainer } from '../Interfaces/IAvailableContainer';
|
||||
import { IAvailableSymbol } from '../Interfaces/IAvailableSymbol';
|
||||
import { IConfiguration } from '../Interfaces/IConfiguration';
|
||||
|
@ -6,6 +6,7 @@ import { ContainerModel, IContainerModel } from '../Interfaces/IContainerModel';
|
|||
import { IContainerProperties } from '../Interfaces/IContainerProperties';
|
||||
import { IEditorState } from '../Interfaces/IEditorState';
|
||||
import { ISymbolModel } from '../Interfaces/ISymbolModel';
|
||||
import { Orientation } from '../Interfaces/Orientation';
|
||||
|
||||
/// EDITOR DEFAULTS ///
|
||||
|
||||
|
@ -50,7 +51,7 @@ export const DEFAULTCHILDTYPE_MAX_DEPTH = 10;
|
|||
/// DIMENSIONS DEFAULTS ///
|
||||
|
||||
export const SHOW_SELF_DIMENSIONS = true;
|
||||
export const SHOW_CHILDREN_DIMENSIONS = false;
|
||||
export const SHOW_CHILDREN_DIMENSIONS = true;
|
||||
export const SHOW_BORROWER_DIMENSIONS = true;
|
||||
export const SHOW_DIMENSIONS_PER_DEPTH = false;
|
||||
export const DIMENSION_MARGIN = 50;
|
||||
|
@ -173,16 +174,19 @@ export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
|
|||
parentId: '',
|
||||
linkedSymbolId: '',
|
||||
displayedText: 'main',
|
||||
orientation: Orientation.Horizontal,
|
||||
x: 0,
|
||||
y: 0,
|
||||
margin: {},
|
||||
minWidth: 1,
|
||||
maxWidth: Number.MAX_SAFE_INTEGER,
|
||||
minHeight: 1,
|
||||
maxHeight: Number.MAX_SAFE_INTEGER,
|
||||
width: Number(DEFAULT_CONFIG.MainContainer.Width),
|
||||
height: Number(DEFAULT_CONFIG.MainContainer.Height),
|
||||
isAnchor: false,
|
||||
isFlex: false,
|
||||
xPositionReference: XPositionReference.Left,
|
||||
positionReference: PositionReference.TopLeft,
|
||||
hideChildrenInTreeview: false,
|
||||
showChildrenDimensions: true, // TODO: put the dimension at the top (see pdf)
|
||||
showSelfDimensions: true, // TODO: put the dimension at the bottom (see pdf)
|
||||
|
@ -213,22 +217,28 @@ export function GetDefaultContainerProps(type: string,
|
|||
width: number,
|
||||
height: number,
|
||||
containerConfig: IAvailableContainer): IContainerProperties {
|
||||
const orientation = containerConfig.Orientation ?? Orientation.Horizontal;
|
||||
const defaultIsFlex = (orientation === Orientation.Vertical && containerConfig.Height === undefined) ||
|
||||
(orientation === Orientation.Horizontal && containerConfig.Width === undefined);
|
||||
return ({
|
||||
id: `${type}-${typeCount}`,
|
||||
type,
|
||||
parentId: parent?.properties.id ?? '',
|
||||
linkedSymbolId: '',
|
||||
displayedText: `${containerConfig.DisplayedText ?? type}-${typeCount}`,
|
||||
orientation,
|
||||
x,
|
||||
y,
|
||||
margin: containerConfig.Margin ?? {},
|
||||
width,
|
||||
height,
|
||||
isAnchor: containerConfig.IsAnchor ?? false,
|
||||
isFlex: containerConfig.IsFlex ?? containerConfig.Width === undefined,
|
||||
xPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
|
||||
isFlex: containerConfig.IsFlex ?? defaultIsFlex,
|
||||
positionReference: containerConfig.PositionReference ?? PositionReference.TopLeft,
|
||||
minWidth: containerConfig.MinWidth ?? 1,
|
||||
maxWidth: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER,
|
||||
minHeight: containerConfig.MinWidth ?? 1,
|
||||
maxHeight: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER,
|
||||
hideChildrenInTreeview: containerConfig.HideChildrenInTreeview ?? false,
|
||||
showChildrenDimensions: containerConfig.ShowChildrenDimensions ?? false,
|
||||
showSelfDimensions: containerConfig.ShowSelfDimensions ?? false,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { IContainerModel } from '../Interfaces/IContainerModel';
|
|||
/**
|
||||
* Returns a Generator iterating of over the children depth-first
|
||||
*/
|
||||
export function * MakeIterator(root: IContainerModel, enableHideChildrenInTreeview = false): Generator<IContainerModel, void, unknown> {
|
||||
export function * MakeDFSIterator(root: IContainerModel, enableHideChildrenInTreeview = false): Generator<IContainerModel, void, unknown> {
|
||||
const queue: IContainerModel[] = [root];
|
||||
const visited = new Set<IContainerModel>(queue);
|
||||
while (queue.length > 0) {
|
||||
|
@ -30,6 +30,11 @@ export interface ContainerAndDepth {
|
|||
container: IContainerModel
|
||||
depth: number
|
||||
}
|
||||
|
||||
export interface ContainerAndDepthAndTransform extends ContainerAndDepth {
|
||||
currentTransform: [number, number]
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Generator iterating of over the children depth-first
|
||||
*/
|
||||
|
@ -54,9 +59,44 @@ export function * MakeBFSIterator(root: IContainerModel): Generator<ContainerAnd
|
|||
}
|
||||
}
|
||||
|
||||
export function * MakeRecursionDFSIterator(
|
||||
root: IContainerModel | null,
|
||||
depth: number,
|
||||
currentTransform: [number, number],
|
||||
enableHideChildrenInTreeview: boolean = false
|
||||
): Generator<ContainerAndDepthAndTransform, void, unknown> {
|
||||
if (root === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
yield {
|
||||
container: root,
|
||||
depth,
|
||||
currentTransform
|
||||
};
|
||||
|
||||
if (enableHideChildrenInTreeview && root.properties.hideChildrenInTreeview) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const container of root.children) {
|
||||
yield * MakeRecursionDFSIterator(
|
||||
container,
|
||||
depth + 1,
|
||||
[
|
||||
currentTransform[0] + root.properties.x,
|
||||
currentTransform[1] + root.properties.y
|
||||
],
|
||||
enableHideChildrenInTreeview
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the depth of the container
|
||||
* @returns The depth of the container
|
||||
* @deprecated Please avoid using this function inside an iterationl,
|
||||
* use recursive DFS or iterative BFS to get the depth
|
||||
*/
|
||||
export function GetDepth(parent: IContainerModel): number {
|
||||
let depth = 0;
|
||||
|
@ -80,6 +120,19 @@ export function GetAbsolutePosition(container: IContainerModel): [number, number
|
|||
return CancelParentTransform(container.parent, x, y);
|
||||
}
|
||||
|
||||
export function GetContainerLinkedList(container: IContainerModel, stop?: IContainerModel): IContainerModel[] {
|
||||
const it = MakeContainerLinkedListIterator(container, stop);
|
||||
return [...it];
|
||||
}
|
||||
|
||||
export function * MakeContainerLinkedListIterator(container: IContainerModel, stop?: IContainerModel): Generator<IContainerModel, void, unknown> {
|
||||
let current: IContainerModel | null = container;
|
||||
while (current !== stop && current != null) {
|
||||
yield current;
|
||||
current = current.parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the hierarchic transformations to the given x, y
|
||||
* @param parent Parent of the container to remove its transform
|
||||
|
@ -93,34 +146,47 @@ export function CancelParentTransform(
|
|||
y: number,
|
||||
stop?: IContainerModel
|
||||
): [number, number] {
|
||||
let current = parent;
|
||||
while (current !== stop && current != null) {
|
||||
if (parent === null) {
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
const it = MakeContainerLinkedListIterator(parent, stop);
|
||||
for (const current of it) {
|
||||
x += current.properties.x;
|
||||
y += current.properties.y;
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the hierarchic transformations to the given x, y
|
||||
* Apply the hierarchic transformations to the given x, y
|
||||
* @param parent Parent of the container to remove its transform
|
||||
* @param x value to be restored
|
||||
* @param y value to be restored
|
||||
* @returns x and y such that the transformations of the parent are cancelled
|
||||
* @returns x and y such that the transformations of the parent are applied
|
||||
*/
|
||||
export function ApplyParentTransform(parent: IContainerModel | null, x: number, y: number): [number, number] {
|
||||
let current = parent;
|
||||
while (current != null) {
|
||||
export function ApplyParentTransform(
|
||||
parent: IContainerModel | null,
|
||||
x: number,
|
||||
y: number,
|
||||
stop?: IContainerModel
|
||||
): [number, number] {
|
||||
if (parent === null) {
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
const it = MakeContainerLinkedListIterator(parent, stop);
|
||||
for (const current of it) {
|
||||
x -= current.properties.x;
|
||||
y -= current.properties.y;
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
export function FindContainerById(root: IContainerModel, id: string): IContainerModel | undefined {
|
||||
const it = MakeIterator(root);
|
||||
const it = MakeDFSIterator(root);
|
||||
for (const container of it) {
|
||||
if (container.properties.id === id) {
|
||||
return container;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { FindContainerById, MakeIterator } from './itertools';
|
||||
import { FindContainerById, MakeDFSIterator } from './itertools';
|
||||
import { IEditorState } from '../Interfaces/IEditorState';
|
||||
import { IHistoryState } from '../Interfaces/IHistoryState';
|
||||
|
||||
|
@ -33,7 +33,7 @@ export function ReviveState(state: IHistoryState): void {
|
|||
symbol.linkedContainers = new Set(symbol.linkedContainers);
|
||||
}
|
||||
|
||||
const it = MakeIterator(state.mainContainer);
|
||||
const it = MakeDFSIterator(state.mainContainer);
|
||||
for (const container of it) {
|
||||
const parentId = container.properties.parentId;
|
||||
if (parentId === null) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { XPositionReference } from '../Enums/XPositionReference';
|
||||
import { PositionReference } from '../Enums/PositionReference';
|
||||
|
||||
// TODO: Big refactoring
|
||||
/**
|
||||
|
@ -10,26 +10,78 @@ import { XPositionReference } from '../Enums/XPositionReference';
|
|||
* it is better to fix serialization with the reviver.
|
||||
*/
|
||||
|
||||
export function TransformX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
|
||||
export function TransformX(x: number, width: number, positionReference = PositionReference.TopLeft): number {
|
||||
let transformedX = x;
|
||||
if (xPositionReference === XPositionReference.Center) {
|
||||
transformedX += width / 2;
|
||||
} else if (xPositionReference === XPositionReference.Right) {
|
||||
transformedX += width;
|
||||
switch (positionReference) {
|
||||
case PositionReference.TopCenter:
|
||||
case PositionReference.CenterCenter:
|
||||
case PositionReference.BottomCenter:
|
||||
transformedX += width / 2;
|
||||
break;
|
||||
case PositionReference.TopRight:
|
||||
case PositionReference.CenterRight:
|
||||
case PositionReference.BottomRight:
|
||||
transformedX += width;
|
||||
break;
|
||||
}
|
||||
|
||||
return transformedX;
|
||||
}
|
||||
|
||||
export function RestoreX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
|
||||
export function RestoreX(x: number, width: number, positionReference = PositionReference.TopLeft): number {
|
||||
let transformedX = x;
|
||||
if (xPositionReference === XPositionReference.Center) {
|
||||
transformedX -= width / 2;
|
||||
} else if (xPositionReference === XPositionReference.Right) {
|
||||
transformedX -= width;
|
||||
switch (positionReference) {
|
||||
case PositionReference.TopCenter:
|
||||
case PositionReference.CenterCenter:
|
||||
case PositionReference.BottomCenter:
|
||||
transformedX -= width / 2;
|
||||
break;
|
||||
case PositionReference.TopRight:
|
||||
case PositionReference.CenterRight:
|
||||
case PositionReference.BottomRight:
|
||||
transformedX -= width;
|
||||
break;
|
||||
}
|
||||
|
||||
return transformedX;
|
||||
}
|
||||
|
||||
export function TransformY(y: number, height: number, xPositionReference = PositionReference.TopLeft): number {
|
||||
let transformedY = y;
|
||||
switch (xPositionReference) {
|
||||
case PositionReference.CenterLeft:
|
||||
case PositionReference.CenterCenter:
|
||||
case PositionReference.CenterRight:
|
||||
transformedY += height / 2;
|
||||
break;
|
||||
case PositionReference.BottomLeft:
|
||||
case PositionReference.BottomCenter:
|
||||
case PositionReference.BottomRight:
|
||||
transformedY += height;
|
||||
break;
|
||||
}
|
||||
|
||||
return transformedY;
|
||||
}
|
||||
|
||||
export function RestoreY(y: number, height: number, positionReference = PositionReference.TopLeft): number {
|
||||
let transformedY = y;
|
||||
switch (positionReference) {
|
||||
case PositionReference.CenterLeft:
|
||||
case PositionReference.CenterCenter:
|
||||
case PositionReference.CenterRight:
|
||||
transformedY -= height / 2;
|
||||
break;
|
||||
case PositionReference.BottomLeft:
|
||||
case PositionReference.BottomCenter:
|
||||
case PositionReference.BottomRight:
|
||||
transformedY -= height;
|
||||
break;
|
||||
}
|
||||
|
||||
return transformedY;
|
||||
}
|
||||
|
||||
export function ApplyMargin(
|
||||
x: number,
|
||||
y: number,
|
||||
|
@ -45,9 +97,9 @@ export function ApplyMargin(
|
|||
bottom = bottom ?? 0;
|
||||
top = top ?? 0;
|
||||
x = ApplyXMargin(x, left);
|
||||
y += top;
|
||||
y = ApplyXMargin(y, top);
|
||||
width = ApplyWidthMargin(width, left, right);
|
||||
height -= (bottom + top);
|
||||
height = ApplyWidthMargin(height, top, bottom);
|
||||
return { x, y, width, height };
|
||||
}
|
||||
|
||||
|
@ -64,9 +116,9 @@ export function RemoveMargin(
|
|||
bottom = bottom ?? 0;
|
||||
top = top ?? 0;
|
||||
x = RemoveXMargin(x, left);
|
||||
y -= top;
|
||||
y = RemoveXMargin(y, top);
|
||||
width = RemoveWidthMargin(width, left, right);
|
||||
height += (bottom + top);
|
||||
height = RemoveWidthMargin(height, top, bottom);
|
||||
return { x, y, width, height };
|
||||
}
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
{
|
||||
Type: 'Montant',
|
||||
Width: 10,
|
||||
XPositionReference: 1,
|
||||
PositionReference: 1,
|
||||
MarkPositionToDimensionBorrower: true,
|
||||
Style: {
|
||||
fillOpacity: 0,
|
||||
|
@ -150,7 +150,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
{
|
||||
Type: 'Dilatation',
|
||||
Width: 4,
|
||||
XPositionReference: 1,
|
||||
PositionReference: 1,
|
||||
MarkPositionToDimensionBorrower: true,
|
||||
Style: {
|
||||
fillOpacity: 0,
|
||||
|
@ -193,7 +193,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg'
|
||||
},
|
||||
Name: 'Poteau structure',
|
||||
XPositionReference: 1
|
||||
PositionReference: 1
|
||||
},
|
||||
{
|
||||
Width: 32,
|
||||
|
@ -205,7 +205,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
Url: 'https://e7.pngegg.com/pngimages/647/127/png-clipart-svg-working-group-information-world-wide-web-internet-structure.png'
|
||||
},
|
||||
Name: 'Joint de structure',
|
||||
XPositionReference: 0
|
||||
PositionReference: 0
|
||||
}
|
||||
],
|
||||
Categories: [
|
||||
|
|
|
@ -69,11 +69,24 @@ const GetSVGLayoutConfiguration = () => {
|
|||
AvailableContainers: [
|
||||
{
|
||||
Type: 'Chassis',
|
||||
DisplayedText: 'Chassis?',
|
||||
Whitelist: ["Trou"],
|
||||
MaxWidth: 500,
|
||||
MinWidth: 200,
|
||||
Width: 200,
|
||||
DisplayedText: 'Chassis horizontal',
|
||||
MaxHeight: 200,
|
||||
DefaultChildType: 'Trou',
|
||||
Style: {
|
||||
fillOpacity: 1,
|
||||
strokeWidth: 2,
|
||||
stroke: 'red',
|
||||
fill: '#d3c9b7',
|
||||
},
|
||||
ShowSelfDimensions: true,
|
||||
IsDimensionBorrower: true,
|
||||
Category: "Stuff"
|
||||
},
|
||||
{
|
||||
Type: 'ChassisVertical',
|
||||
Orientation: 1,
|
||||
DisplayedText: 'Chassis vertical',
|
||||
MaxWidth: 200,
|
||||
DefaultChildType: 'Trou',
|
||||
Style: {
|
||||
fillOpacity: 1,
|
||||
|
@ -143,7 +156,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
{
|
||||
Type: 'Montant',
|
||||
Width: 10,
|
||||
XPositionReference: 1,
|
||||
PositionReference: 4,
|
||||
MarkPositionToDimensionBorrower: true,
|
||||
Style: {
|
||||
fillOpacity: 0,
|
||||
|
@ -152,10 +165,23 @@ const GetSVGLayoutConfiguration = () => {
|
|||
fill: '#713f12',
|
||||
}
|
||||
},
|
||||
{
|
||||
Type: 'Traverse',
|
||||
Height: 10,
|
||||
PositionReference: 4,
|
||||
Orientation: 1,
|
||||
MarkPositionToDimensionBorrower: true,
|
||||
Style: {
|
||||
fillOpacity: 0,
|
||||
strokeWidth: 2,
|
||||
stroke: '#6517fa',
|
||||
fill: '#6517fa',
|
||||
}
|
||||
},
|
||||
{
|
||||
Type: 'Dilatation',
|
||||
Width: 4,
|
||||
XPositionReference: 1,
|
||||
PositionReference: 1,
|
||||
MarkPositionToDimensionBorrower: true,
|
||||
Style: {
|
||||
fillOpacity: 0,
|
||||
|
@ -198,7 +224,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg'
|
||||
},
|
||||
Name: 'Poteau structure',
|
||||
XPositionReference: 1
|
||||
PositionReference: 1
|
||||
},
|
||||
{
|
||||
Width: 32,
|
||||
|
@ -210,7 +236,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
Url: 'https://e7.pngegg.com/pngimages/647/127/png-clipart-svg-working-group-information-world-wide-web-internet-structure.png'
|
||||
},
|
||||
Name: 'Joint de structure',
|
||||
XPositionReference: 0
|
||||
PositionReference: 0
|
||||
}
|
||||
],
|
||||
Categories: [
|
||||
|
@ -227,7 +253,13 @@ const GetSVGLayoutConfiguration = () => {
|
|||
MainContainer: {
|
||||
Type: 'main',
|
||||
Width: 800,
|
||||
Height: 200
|
||||
Height: 800,
|
||||
Orientation: 1,
|
||||
Style: {
|
||||
stroke: 'black',
|
||||
strokeWidth: 2,
|
||||
fillOpacity: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue