Merged PR 167: Add Flex and fix bugs (read desc)
Note: The branch name does not fit the new features. - Implement Flex with simplex - Enable rigid body by default (removed IsRigidBody property) <=== possibly a bad idea - Sort children in add and update properties - Implement MaxWidth - Add more docs Fixes : - .env.production url - Symbols: not blocking the linked container when the parent is moving
This commit is contained in:
parent
ec3fddec9d
commit
7f3f6a489a
43 changed files with 1127 additions and 453 deletions
|
@ -1,2 +1,2 @@
|
||||||
|
|
||||||
VITE_API_URL=https://localhost/SmartMenuiserieTemplate
|
VITE_API_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/GetSVGLayoutConfiguration
|
3
.gitattributes
vendored
3
.gitattributes
vendored
|
@ -1 +1,4 @@
|
||||||
*.drawio filter=lfs diff=lfs merge=lfs -text
|
*.drawio filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.dwg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
|
@ -14,6 +14,7 @@ Requierements :
|
||||||
- NodeJS
|
- NodeJS
|
||||||
- npm
|
- npm
|
||||||
- pnpm (optional but recommanded unless you prefer having a huge `node_modules` directory)
|
- pnpm (optional but recommanded unless you prefer having a huge `node_modules` directory)
|
||||||
|
- Chrome > 98
|
||||||
|
|
||||||
# Developping
|
# Developping
|
||||||
|
|
||||||
|
|
BIN
docs/Eric BF/01_141017-WG-11328-SYME-VERNUCCI-DET BF ind A.pdf
(Stored with Git LFS)
Normal file
BIN
docs/Eric BF/01_141017-WG-11328-SYME-VERNUCCI-DET BF ind A.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/Eric BF/02_141017-WG-11328-SYME-VERNUCCI-DET BF ind B.pdf
(Stored with Git LFS)
Normal file
BIN
docs/Eric BF/02_141017-WG-11328-SYME-VERNUCCI-DET BF ind B.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/Eric BF/03_SYME KLINE cde BV.pdf
(Stored with Git LFS)
Normal file
BIN
docs/Eric BF/03_SYME KLINE cde BV.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/Eric BF/04_ARC KL_K1485985-ARC-K1485985.pdf
(Stored with Git LFS)
Normal file
BIN
docs/Eric BF/04_ARC KL_K1485985-ARC-K1485985.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/Eric BF/05_DT_K1485985.pdf
(Stored with Git LFS)
Normal file
BIN
docs/Eric BF/05_DT_K1485985.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/Eric BF/06_ Photo IMG_1406.jpg
(Stored with Git LFS)
Normal file
BIN
docs/Eric BF/06_ Photo IMG_1406.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/Eric BF/131421 KALIA - 15541 - PARVIS DE RODE - D0371837 - BANDE FILANTE PLAN 01 à 07 -IND B.dwg
(Stored with Git LFS)
Normal file
BIN
docs/Eric BF/131421 KALIA - 15541 - PARVIS DE RODE - D0371837 - BANDE FILANTE PLAN 01 à 07 -IND B.dwg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/Eric BF/131421 KALIA - 15541 - PARVIS DE RODE - D0371837 - BANDE FILANTE PLAN 01 à 07 -IND B.pdf
(Stored with Git LFS)
Normal file
BIN
docs/Eric BF/131421 KALIA - 15541 - PARVIS DE RODE - D0371837 - BANDE FILANTE PLAN 01 à 07 -IND B.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
docs/Eric BF/image0000001.jpg
(Stored with Git LFS)
Normal file
BIN
docs/Eric BF/image0000001.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 131 B |
2
public/Interfaces.d.ts
vendored
2
public/Interfaces.d.ts
vendored
|
@ -38,7 +38,7 @@ declare interface IProperties extends React.CSSProperties {
|
||||||
parentId: string | null
|
parentId: string | null
|
||||||
x: number
|
x: number
|
||||||
y: number
|
y: number
|
||||||
isRigidBody: boolean
|
|
||||||
XPositionReference?: XPositionReference
|
XPositionReference?: XPositionReference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { MenuAlt2Icon, MenuAlt3Icon, MenuIcon } from '@heroicons/react/outline';
|
import { MenuAlt2Icon, MenuAlt3Icon, MenuIcon } from '@heroicons/react/outline';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { PropertyType } from '../../Enums/PropertyType';
|
||||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||||
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import { restoreX, transformX } from '../../utils/svg';
|
import { ApplyWidthMargin, ApplyXMargin, RemoveWidthMargin, RemoveXMargin, restoreX, transformX } from '../../utils/svg';
|
||||||
import { InputGroup } from '../InputGroup/InputGroup';
|
import { InputGroup } from '../InputGroup/InputGroup';
|
||||||
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
|
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
|
||||||
import { Select } from '../Select/Select';
|
import { Select } from '../Select/Select';
|
||||||
|
@ -11,12 +12,12 @@ import { Select } from '../Select/Select';
|
||||||
interface IContainerFormProps {
|
interface IContainerFormProps {
|
||||||
properties: IContainerProperties
|
properties: IContainerProperties
|
||||||
symbols: Map<string, ISymbolModel>
|
symbols: Map<string, ISymbolModel>
|
||||||
onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
onChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCSSInputs = (
|
const getCSSInputs = (
|
||||||
properties: IContainerProperties,
|
properties: IContainerProperties,
|
||||||
onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
onChange: (key: string, value: string | number | boolean, type: PropertyType) => void
|
||||||
): JSX.Element[] => {
|
): JSX.Element[] => {
|
||||||
const groupInput: JSX.Element[] = [];
|
const groupInput: JSX.Element[] = [];
|
||||||
for (const key in properties.style) {
|
for (const key in properties.style) {
|
||||||
|
@ -28,7 +29,7 @@ const getCSSInputs = (
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='string'
|
type='string'
|
||||||
value={(properties.style as any)[key]}
|
value={(properties.style as any)[key]}
|
||||||
onChange={(event) => onChange(key, event.target.value, true)}
|
onChange={(event) => onChange(key, event.target.value, PropertyType.STYLE)}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
return groupInput;
|
return groupInput;
|
||||||
|
@ -52,7 +53,16 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='string'
|
type='string'
|
||||||
value={props.properties.parentId?.toString()}
|
value={props.properties.parentId}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
<InputGroup
|
||||||
|
labelText='Type'
|
||||||
|
inputKey='type'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
type='string'
|
||||||
|
value={props.properties.type}
|
||||||
isDisabled={true}
|
isDisabled={true}
|
||||||
/>
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
|
@ -71,8 +81,18 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
isDisabled={props.properties.linkedSymbolId !== ''}
|
isDisabled={props.properties.linkedSymbolId !== ''}
|
||||||
value={transformX(props.properties.x, props.properties.width, props.properties.XPositionReference).toString()}
|
value={transformX(RemoveXMargin(props.properties.x, props.properties.margin.left), props.properties.width, props.properties.XPositionReference).toString()}
|
||||||
onChange={(event) => props.onChange('x', restoreX(Number(event.target.value), props.properties.width, props.properties.XPositionReference))}
|
onChange={(event) => props.onChange(
|
||||||
|
'x',
|
||||||
|
ApplyXMargin(
|
||||||
|
restoreX(
|
||||||
|
Number(event.target.value),
|
||||||
|
props.properties.width,
|
||||||
|
props.properties.XPositionReference
|
||||||
|
),
|
||||||
|
props.properties.margin.left
|
||||||
|
)
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='y'
|
labelText='y'
|
||||||
|
@ -80,8 +100,8 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
value={props.properties.y.toString()}
|
value={(props.properties.y - (props.properties.margin?.top ?? 0)).toString()}
|
||||||
onChange={(event) => props.onChange('y', Number(event.target.value))}
|
onChange={(event) => props.onChange('y', Number(event.target.value) + (props.properties.margin?.top ?? 0))}
|
||||||
/>
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Minimum width'
|
labelText='Minimum width'
|
||||||
|
@ -93,6 +113,16 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
value={props.properties.minWidth.toString()}
|
value={props.properties.minWidth.toString()}
|
||||||
onChange={(event) => props.onChange('minWidth', Number(event.target.value))}
|
onChange={(event) => props.onChange('minWidth', Number(event.target.value))}
|
||||||
/>
|
/>
|
||||||
|
<InputGroup
|
||||||
|
labelText='Maximum width'
|
||||||
|
inputKey='maxWidth'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
type='number'
|
||||||
|
min={1}
|
||||||
|
value={props.properties.maxWidth.toString()}
|
||||||
|
onChange={(event) => props.onChange('maxWidth', Number(event.target.value))}
|
||||||
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Width'
|
labelText='Width'
|
||||||
inputKey='width'
|
inputKey='width'
|
||||||
|
@ -100,8 +130,9 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
min={props.properties.minWidth}
|
min={props.properties.minWidth}
|
||||||
value={props.properties.width.toString()}
|
value={(RemoveWidthMargin(props.properties.width, props.properties.margin.left, props.properties.margin.right)).toString()}
|
||||||
onChange={(event) => props.onChange('width', Number(event.target.value))}
|
onChange={(event) => props.onChange('width', ApplyWidthMargin(Number(event.target.value), props.properties.margin.left, props.properties.margin.right))}
|
||||||
|
isDisabled={props.properties.isFlex}
|
||||||
/>
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Height'
|
labelText='Height'
|
||||||
|
@ -110,17 +141,57 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
min={0}
|
min={0}
|
||||||
value={props.properties.height.toString()}
|
value={(props.properties.height + (props.properties.margin?.top ?? 0) + (props.properties.margin?.bottom ?? 0)).toString()}
|
||||||
onChange={(event) => props.onChange('height', Number(event.target.value))}
|
onChange={(event) => props.onChange('height', Number(event.target.value) - (props.properties.margin?.top ?? 0) - (props.properties.margin?.bottom ?? 0))}
|
||||||
/>
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Rigid'
|
labelText='Margin left'
|
||||||
inputKey='isRigidBody'
|
inputKey='left'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
type='number'
|
||||||
|
min={0}
|
||||||
|
value={(props.properties.margin.left ?? 0).toString()}
|
||||||
|
onChange={(event) => props.onChange('left', Number(event.target.value), PropertyType.MARGIN)}
|
||||||
|
/>
|
||||||
|
<InputGroup
|
||||||
|
labelText='Margin bottom'
|
||||||
|
inputKey='bottom'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
type='number'
|
||||||
|
min={0}
|
||||||
|
value={(props.properties.margin.bottom ?? 0).toString()}
|
||||||
|
onChange={(event) => props.onChange('bottom', Number(event.target.value), PropertyType.MARGIN)}
|
||||||
|
/>
|
||||||
|
<InputGroup
|
||||||
|
labelText='Margin top'
|
||||||
|
inputKey='top'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
type='number'
|
||||||
|
min={0}
|
||||||
|
value={(props.properties.margin.top ?? 0).toString()}
|
||||||
|
onChange={(event) => props.onChange('top', Number(event.target.value), PropertyType.MARGIN)}
|
||||||
|
/>
|
||||||
|
<InputGroup
|
||||||
|
labelText='Margin right'
|
||||||
|
inputKey='right'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
type='number'
|
||||||
|
min={0}
|
||||||
|
value={(props.properties.margin.right ?? 0).toString()}
|
||||||
|
onChange={(event) => props.onChange('right', Number(event.target.value), PropertyType.MARGIN)}
|
||||||
|
/>
|
||||||
|
<InputGroup
|
||||||
|
labelText='Flex'
|
||||||
|
inputKey='isFlex'
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
checked={props.properties.isRigidBody}
|
checked={props.properties.isFlex}
|
||||||
onChange={(event) => props.onChange('isRigidBody', event.target.checked)}
|
onChange={(event) => props.onChange('isFlex', event.target.checked)}
|
||||||
/>
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Anchor'
|
labelText='Anchor'
|
||||||
|
|
|
@ -22,6 +22,7 @@ describe.concurrent('Properties', () => {
|
||||||
it('Some properties, change values with dynamic input', () => {
|
it('Some properties, change values with dynamic input', () => {
|
||||||
const prop: IContainerProperties = {
|
const prop: IContainerProperties = {
|
||||||
id: 'stuff',
|
id: 'stuff',
|
||||||
|
type: 'type',
|
||||||
parentId: 'parentId',
|
parentId: 'parentId',
|
||||||
linkedSymbolId: '',
|
linkedSymbolId: '',
|
||||||
displayedText: 'stuff',
|
displayedText: 'stuff',
|
||||||
|
@ -30,8 +31,10 @@ describe.concurrent('Properties', () => {
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 1,
|
height: 1,
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
|
maxWidth: Infinity,
|
||||||
|
margin: {},
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
isRigidBody: false,
|
isFlex: false,
|
||||||
isAnchor: false
|
isAnchor: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { PropertyType } from '../../Enums/PropertyType';
|
||||||
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import ContainerForm from './ContainerForm';
|
import ContainerForm from './ContainerForm';
|
||||||
|
@ -6,7 +7,7 @@ import ContainerForm from './ContainerForm';
|
||||||
interface IPropertiesProps {
|
interface IPropertiesProps {
|
||||||
properties?: IContainerProperties
|
properties?: IContainerProperties
|
||||||
symbols: Map<string, ISymbolModel>
|
symbols: Map<string, ISymbolModel>
|
||||||
onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
onChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps) => {
|
export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps) => {
|
||||||
|
|
|
@ -10,6 +10,9 @@ import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTY
|
||||||
import { ApplyBehaviors } from '../Behaviors/Behaviors';
|
import { ApplyBehaviors } from '../Behaviors/Behaviors';
|
||||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
import Swal from 'sweetalert2';
|
import Swal from 'sweetalert2';
|
||||||
|
import { ApplyMargin, transformX } from '../../../utils/svg';
|
||||||
|
import { Flex } from '../Behaviors/FlexBehaviors';
|
||||||
|
import { PropertyType } from '../../../Enums/PropertyType';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select a container
|
* Select a container
|
||||||
|
@ -87,6 +90,8 @@ export function DeleteContainer(
|
||||||
throw new Error('[DeleteContainer] Could not find container among parent\'s children');
|
throw new Error('[DeleteContainer] Could not find container among parent\'s children');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Flex(container);
|
||||||
|
|
||||||
// Select the previous container
|
// Select the previous container
|
||||||
// or select the one above
|
// or select the one above
|
||||||
const SelectedContainerId = GetSelectedContainerOnDelete(
|
const SelectedContainerId = GetSelectedContainerOnDelete(
|
||||||
|
@ -213,9 +218,17 @@ export function AddContainer(
|
||||||
if (parentClone === null || parentClone === undefined) {
|
if (parentClone === null || parentClone === undefined) {
|
||||||
throw new Error('[AddContainer] Container model was not found among children of the main container!');
|
throw new Error('[AddContainer] Container model was not found among children of the main container!');
|
||||||
}
|
}
|
||||||
|
const left: number = containerConfig.Margin?.left ?? 0;
|
||||||
|
const bottom: number = containerConfig.Margin?.bottom ?? 0;
|
||||||
|
const top: number = containerConfig.Margin?.top ?? 0;
|
||||||
|
const right: number = containerConfig.Margin?.right ?? 0;
|
||||||
|
|
||||||
let x = containerConfig.DefaultX ?? 0;
|
let x = containerConfig.DefaultX ?? 0;
|
||||||
const y = containerConfig.DefaultY ?? 0;
|
let y = containerConfig.DefaultY ?? 0;
|
||||||
|
let width = containerConfig.Width ?? containerConfig.MaxWidth ?? containerConfig.MinWidth ?? parentClone.properties.width;
|
||||||
|
let height = containerConfig.Height ?? parentClone.properties.height;
|
||||||
|
|
||||||
|
({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right));
|
||||||
|
|
||||||
x = ApplyAddMethod(index, containerConfig, parentClone, x);
|
x = ApplyAddMethod(index, containerConfig, parentClone, x);
|
||||||
|
|
||||||
|
@ -225,6 +238,8 @@ export function AddContainer(
|
||||||
parentClone,
|
parentClone,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
containerConfig
|
containerConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -238,17 +253,15 @@ export function AddContainer(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
ApplyBehaviors(newContainer, current.Symbols);
|
|
||||||
|
|
||||||
// And push it the the parent children
|
|
||||||
if (index === parentClone.children.length) {
|
|
||||||
parentClone.children.push(newContainer);
|
parentClone.children.push(newContainer);
|
||||||
} else {
|
UpdateParentChildrenList(parentClone);
|
||||||
parentClone.children.splice(index, 0, newContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
InitializeDefaultChild(configuration, containerConfig, newContainer, newCounters);
|
InitializeDefaultChild(configuration, containerConfig, newContainer, newCounters);
|
||||||
|
|
||||||
|
ApplyBehaviors(newContainer, current.Symbols);
|
||||||
|
|
||||||
|
ApplyBehaviorsOnSiblings(newContainer, current.Symbols);
|
||||||
|
|
||||||
// Update the state
|
// Update the state
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
|
LastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
|
||||||
|
@ -262,6 +275,16 @@ export function AddContainer(
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function UpdateParentChildrenList(parentClone: IContainerModel | null): void {
|
||||||
|
if (parentClone === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
parentClone.children.sort(
|
||||||
|
(a, b) => transformX(a.properties.x, a.properties.width, a.properties.XPositionReference) -
|
||||||
|
transformX(b.properties.x, b.properties.width, b.properties.XPositionReference)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function InitializeDefaultChild(
|
function InitializeDefaultChild(
|
||||||
configuration: IConfiguration,
|
configuration: IConfiguration,
|
||||||
containerConfig: IAvailableContainer,
|
containerConfig: IAvailableContainer,
|
||||||
|
@ -286,8 +309,17 @@ function InitializeDefaultChild(
|
||||||
}
|
}
|
||||||
|
|
||||||
seen.add(currentConfig.Type);
|
seen.add(currentConfig.Type);
|
||||||
const x = currentConfig.DefaultX ?? 0;
|
|
||||||
const y = currentConfig.DefaultY ?? 0;
|
const left: number = currentConfig.Margin?.left ?? 0;
|
||||||
|
const bottom: number = currentConfig.Margin?.bottom ?? 0;
|
||||||
|
const top: number = currentConfig.Margin?.top ?? 0;
|
||||||
|
const right: number = currentConfig.Margin?.right ?? 0;
|
||||||
|
let x = currentConfig.DefaultX ?? 0;
|
||||||
|
let y = currentConfig.DefaultY ?? 0;
|
||||||
|
let width = currentConfig.Width ?? currentConfig.MaxWidth ?? currentConfig.MinWidth ?? parent.properties.width;
|
||||||
|
let height = currentConfig.Height ?? parent.properties.height;
|
||||||
|
|
||||||
|
({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right));
|
||||||
|
|
||||||
UpdateCounters(newCounters, currentConfig.Type);
|
UpdateCounters(newCounters, currentConfig.Type);
|
||||||
const count = newCounters[currentConfig.Type];
|
const count = newCounters[currentConfig.Type];
|
||||||
|
@ -297,6 +329,8 @@ function InitializeDefaultChild(
|
||||||
parent,
|
parent,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
currentConfig
|
currentConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -353,7 +387,7 @@ function ApplyAddMethod(index: number, containerConfig: IAvailableContainer, par
|
||||||
export function OnPropertyChange(
|
export function OnPropertyChange(
|
||||||
key: string,
|
key: string,
|
||||||
value: string | number | boolean,
|
value: string | number | boolean,
|
||||||
isStyle: boolean = false,
|
type: PropertyType = PropertyType.SIMPLE,
|
||||||
selected: IContainerModel | undefined,
|
selected: IContainerModel | undefined,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
|
@ -375,22 +409,7 @@ export function OnPropertyChange(
|
||||||
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldSymbolId = container.properties.linkedSymbolId;
|
SetContainer(container, key, value, type, current.Symbols);
|
||||||
|
|
||||||
if (isStyle) {
|
|
||||||
(container.properties.style as any)[key] = value;
|
|
||||||
} else {
|
|
||||||
(container.properties as any)[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
LinkSymbol(
|
|
||||||
container.properties.id,
|
|
||||||
oldSymbolId,
|
|
||||||
container.properties.linkedSymbolId,
|
|
||||||
current.Symbols
|
|
||||||
);
|
|
||||||
|
|
||||||
ApplyBehaviors(container, current.Symbols);
|
|
||||||
|
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Change ${key} of ${container.properties.id}`,
|
LastAction: `Change ${key} of ${container.properties.id}`,
|
||||||
|
@ -404,6 +423,79 @@ export function OnPropertyChange(
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the container with properties and behaviors (mutate)
|
||||||
|
* @param container Container to update
|
||||||
|
* @param key Key of the property to update
|
||||||
|
* @param value Value of the property to update
|
||||||
|
* @param type Type of the property to update
|
||||||
|
* @param symbols Current list of symbols
|
||||||
|
*/
|
||||||
|
function SetContainer(
|
||||||
|
container: ContainerModel,
|
||||||
|
key: string, value: string | number | boolean,
|
||||||
|
type: PropertyType,
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
|
): void {
|
||||||
|
// get the old symbol to detect unlink
|
||||||
|
const oldSymbolId = container.properties.linkedSymbolId;
|
||||||
|
|
||||||
|
// update the property
|
||||||
|
AssignProperty(container, key, value, type);
|
||||||
|
|
||||||
|
// link the symbol if it exists
|
||||||
|
LinkSymbol(
|
||||||
|
container.properties.id,
|
||||||
|
oldSymbolId,
|
||||||
|
container.properties.linkedSymbolId,
|
||||||
|
symbols
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply special behaviors: rigid, flex, symbol, anchor
|
||||||
|
ApplyBehaviors(container, symbols);
|
||||||
|
|
||||||
|
// Apply special behaviors on siblings
|
||||||
|
ApplyBehaviorsOnSiblings(container, symbols);
|
||||||
|
|
||||||
|
// sort the children list by their position
|
||||||
|
UpdateParentChildrenList(container.parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AssignProperty(container: ContainerModel, key: string, value: string | number | boolean, type: PropertyType): void {
|
||||||
|
switch (type) {
|
||||||
|
case PropertyType.STYLE:
|
||||||
|
(container.properties.style as any)[key] = value;
|
||||||
|
break;
|
||||||
|
case PropertyType.MARGIN:
|
||||||
|
SetMargin();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
(container.properties as any)[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetMargin(): void {
|
||||||
|
const oldMarginValue: number = (container.properties.margin as any)[key];
|
||||||
|
const diff = Number(value) - oldMarginValue;
|
||||||
|
switch (key) {
|
||||||
|
case 'left':
|
||||||
|
container.properties.x += diff;
|
||||||
|
container.properties.width -= diff;
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
container.properties.width -= diff;
|
||||||
|
break;
|
||||||
|
case 'bottom':
|
||||||
|
container.properties.height -= diff;
|
||||||
|
break;
|
||||||
|
case 'top':
|
||||||
|
container.properties.y += diff;
|
||||||
|
container.properties.height -= diff;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(container.properties.margin as any)[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function LinkSymbol(
|
function LinkSymbol(
|
||||||
containerId: string,
|
containerId: string,
|
||||||
oldSymbolId: string,
|
oldSymbolId: string,
|
||||||
|
@ -422,3 +514,10 @@ function LinkSymbol(
|
||||||
|
|
||||||
newSymbol.linkedContainers.add(containerId);
|
newSymbol.linkedContainers.add(containerId);
|
||||||
}
|
}
|
||||||
|
function ApplyBehaviorsOnSiblings(newContainer: ContainerModel, Symbols: Map<string, ISymbolModel>): void {
|
||||||
|
if (newContainer.parent === null || newContainer.parent === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newContainer.parent.children.filter(container => newContainer !== container).forEach(container => ApplyBehaviors(container, Symbols));
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ export function ApplyAnchor(container: IContainerModel): IContainerModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
const rigidBodies = container.parent.children.filter(
|
const rigidBodies = container.parent.children.filter(
|
||||||
child => child.properties.isRigidBody && !child.properties.isAnchor
|
child => !child.properties.isAnchor
|
||||||
);
|
);
|
||||||
|
|
||||||
const overlappingContainers = getOverlappingContainers(container, rigidBodies);
|
const overlappingContainers = getOverlappingContainers(container, rigidBodies);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
import { APPLY_BEHAVIORS_ON_CHILDREN } from '../../../utils/default';
|
import { APPLY_BEHAVIORS_ON_CHILDREN } from '../../../utils/default';
|
||||||
import { ApplyAnchor } from './AnchorBehaviors';
|
import { ApplyAnchor } from './AnchorBehaviors';
|
||||||
|
import { Flex } from './FlexBehaviors';
|
||||||
import { ApplyRigidBody } from './RigidBodyBehaviors';
|
import { ApplyRigidBody } from './RigidBodyBehaviors';
|
||||||
import { ApplySymbol } from './SymbolBehaviors';
|
import { ApplySymbol } from './SymbolBehaviors';
|
||||||
|
|
||||||
|
@ -16,10 +17,12 @@ export function ApplyBehaviors(container: IContainerModel, symbols: Map<string,
|
||||||
ApplyAnchor(container);
|
ApplyAnchor(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.properties.isRigidBody) {
|
if (container.properties.isFlex) {
|
||||||
ApplyRigidBody(container);
|
Flex(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplyRigidBody(container);
|
||||||
|
|
||||||
const symbol = symbols.get(container.properties.linkedSymbolId);
|
const symbol = symbols.get(container.properties.linkedSymbolId);
|
||||||
if (container.properties.linkedSymbolId !== '' && symbol !== undefined) {
|
if (container.properties.linkedSymbolId !== '' && symbol !== undefined) {
|
||||||
ApplySymbol(container, symbol);
|
ApplySymbol(container, symbol);
|
||||||
|
|
144
src/Components/Editor/Behaviors/FlexBehaviors.ts
Normal file
144
src/Components/Editor/Behaviors/FlexBehaviors.ts
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
|
import { reversePairwise } from '../../../utils/itertools';
|
||||||
|
import { Simplex } from '../../../utils/simplex';
|
||||||
|
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to push the siblings
|
||||||
|
* @param container
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function PushContainers(container: IContainerModel): IContainerModel {
|
||||||
|
if (container.parent === null) {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container.parent.children.length <= 1) {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevIndex = container.parent.children.length - 2;
|
||||||
|
const prev: IContainerModel = container.parent.children[prevIndex];
|
||||||
|
const isOverlapping = prev.properties.x + prev.properties.width > container.properties.x;
|
||||||
|
if (!isOverlapping) {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find hole
|
||||||
|
let lastContainer: IContainerModel | null = null;
|
||||||
|
let space: number = 0;
|
||||||
|
|
||||||
|
while (space.toFixed(2) < container.properties.width.toFixed(2)) {
|
||||||
|
// FIXME: possible infinite loop due to floating point
|
||||||
|
// FIXME: A fix was applied using toFixed(2).
|
||||||
|
// FIXME: A coverture check must be done to ensure that all scenarios are covered
|
||||||
|
|
||||||
|
const it = reversePairwise<IContainerModel>(container.parent.children.filter(child => child !== container));
|
||||||
|
|
||||||
|
for (const { cur, next } of it) {
|
||||||
|
const hasSpaceBetween = next.properties.x + next.properties.width < cur.properties.x;
|
||||||
|
if (hasSpaceBetween) {
|
||||||
|
lastContainer = cur;
|
||||||
|
space = cur.properties.x - (next.properties.x + next.properties.width);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastContainer === null) {
|
||||||
|
// no space between
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexLastContainer = container.parent.children.indexOf(lastContainer);
|
||||||
|
for (let i = indexLastContainer; i <= container.parent.children.length - 2; i++) {
|
||||||
|
const sibling = container.parent.children[i];
|
||||||
|
sibling.properties.x -= space;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasNoSpaceBetween = lastContainer === null;
|
||||||
|
if (hasNoSpaceBetween) {
|
||||||
|
// test gap between the left of the parent and the first container
|
||||||
|
space = container.parent.children[0].properties.x;
|
||||||
|
if (space > 0) {
|
||||||
|
for (let i = 0; i <= container.parent.children.length - 2; i++) {
|
||||||
|
const sibling = container.parent.children[i];
|
||||||
|
sibling.properties.x -= space;
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flex(container);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flex the container and its siblings (mutate)
|
||||||
|
* @param container Container to flex
|
||||||
|
* @returns Flexed container
|
||||||
|
*/
|
||||||
|
export function Flex(container: IContainerModel): void {
|
||||||
|
if (container.parent === null || container.parent === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const flexibleContainers = container.parent.children
|
||||||
|
.filter(sibling => sibling.properties.isFlex);
|
||||||
|
|
||||||
|
const minWidths = flexibleContainers
|
||||||
|
.map(sibling => sibling.properties.minWidth);
|
||||||
|
|
||||||
|
const fixedWidth = container.parent.children
|
||||||
|
.filter(sibling => !sibling.properties.isFlex)
|
||||||
|
.map(sibling => sibling.properties.width)
|
||||||
|
.reduce((partialSum, a) => partialSum + a, 0);
|
||||||
|
|
||||||
|
const requiredMaxWidth = container.parent.properties.width - fixedWidth;
|
||||||
|
|
||||||
|
const minimumPossibleWidth = minWidths.reduce((partialSum, a) => partialSum + a, 0);
|
||||||
|
if (minimumPossibleWidth > requiredMaxWidth) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Cannot fit!',
|
||||||
|
text: 'Cannot fit at all even when squeezing all flex containers to the minimum.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxMinWidths = Math.max(...minWidths);
|
||||||
|
if (maxMinWidths * minWidths.length < requiredMaxWidth) {
|
||||||
|
const wantedWidth = requiredMaxWidth / minWidths.length;
|
||||||
|
// it fits, flex with maxMinWidths and fixed width
|
||||||
|
let right = 0;
|
||||||
|
for (const sibling of container.parent.children) {
|
||||||
|
if (!sibling.properties.isFlex) {
|
||||||
|
sibling.properties.x = right;
|
||||||
|
right += sibling.properties.width;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sibling.properties.x = ApplyXMargin(right, sibling.properties.margin.left);
|
||||||
|
sibling.properties.width = ApplyWidthMargin(wantedWidth, sibling.properties.margin.left, sibling.properties.margin.right);
|
||||||
|
right += wantedWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// does not fit
|
||||||
|
|
||||||
|
/// SIMPLEX ///
|
||||||
|
const solutions: number[] = Simplex(minWidths, requiredMaxWidth);
|
||||||
|
|
||||||
|
// apply the solutions
|
||||||
|
for (let i = 0; i < flexibleContainers.length; i++) {
|
||||||
|
flexibleContainers[i].properties.width = ApplyWidthMargin(solutions[i], flexibleContainers[i].properties.margin.left, flexibleContainers[i].properties.margin.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
// move the containers
|
||||||
|
let right = 0;
|
||||||
|
for (const sibling of container.parent.children) {
|
||||||
|
sibling.properties.x = ApplyXMargin(right, sibling.properties.margin.left);
|
||||||
|
right += sibling.properties.width;
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@
|
||||||
import Swal from 'sweetalert2';
|
import Swal from 'sweetalert2';
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
||||||
|
import { PushContainers } from './FlexBehaviors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "Transform the container into a rigid body"
|
* "Transform the container into a rigid body"
|
||||||
|
@ -23,6 +24,7 @@ export function ApplyRigidBody(
|
||||||
container: IContainerModel
|
container: IContainerModel
|
||||||
): IContainerModel {
|
): IContainerModel {
|
||||||
container = constraintBodyInsideParent(container);
|
container = constraintBodyInsideParent(container);
|
||||||
|
container = PushContainers(container);
|
||||||
container = constraintBodyInsideUnallocatedWidth(container);
|
container = constraintBodyInsideUnallocatedWidth(container);
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
@ -183,7 +185,6 @@ export function constraintBodyInsideUnallocatedWidth(
|
||||||
showConfirmButton: false,
|
showConfirmButton: false,
|
||||||
timer: 5000
|
timer: 5000
|
||||||
});
|
});
|
||||||
container.properties.isRigidBody = false;
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,8 +239,7 @@ function getAvailableWidths(
|
||||||
|
|
||||||
// Ignore the exception
|
// Ignore the exception
|
||||||
// And we will also only uses containers that also are rigid or are anchors
|
// And we will also only uses containers that also are rigid or are anchors
|
||||||
if (child === exception ||
|
if (child === exception) {
|
||||||
(!child.properties.isRigidBody && !child.properties.isAnchor)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const childX = child.properties.x;
|
const childX = child.properties.x;
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
|
import { applyParentTransform } from '../../../utils/itertools';
|
||||||
import { restoreX, transformX } from '../../../utils/svg';
|
import { restoreX, transformX } from '../../../utils/svg';
|
||||||
|
|
||||||
export function ApplySymbol(container: IContainerModel, symbol: ISymbolModel): IContainerModel {
|
export function ApplySymbol(container: IContainerModel, symbol: ISymbolModel): IContainerModel {
|
||||||
container.properties.x = transformX(symbol.x, symbol.width, symbol.config.XPositionReference);
|
container.properties.x = transformX(symbol.x, symbol.width, symbol.config.XPositionReference);
|
||||||
container.properties.x = restoreX(container.properties.x, container.properties.width, container.properties.XPositionReference);
|
container.properties.x = restoreX(container.properties.x, container.properties.width, container.properties.XPositionReference);
|
||||||
|
const [x] = applyParentTransform(container.parent, container.properties.x, 0);
|
||||||
|
container.properties.x = x;
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||||
import { SVG } from '../SVG/SVG';
|
import { SVG } from '../SVG/SVG';
|
||||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
import { UI } from '../UI/UI';
|
import { UI } from '../UI/UI';
|
||||||
import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, AddContainer, OnPropertyChange } from './Actions/ContainerOperations';
|
import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, OnPropertyChange } from './Actions/ContainerOperations';
|
||||||
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
|
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
|
||||||
import { onKeyDown } from './Actions/Shortcuts';
|
import { onKeyDown } from './Actions/Shortcuts';
|
||||||
import EditorEvents from '../../Events/EditorEvents';
|
import EditorEvents from '../../Events/EditorEvents';
|
||||||
|
@ -60,7 +60,9 @@ function useWindowEvents(
|
||||||
history: IHistoryState[],
|
history: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
configuration: IConfiguration,
|
configuration: IConfiguration,
|
||||||
editorRef: React.RefObject<HTMLDivElement>
|
editorRef: React.RefObject<HTMLDivElement>,
|
||||||
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
): void {
|
): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const events = EditorEvents;
|
const events = EditorEvents;
|
||||||
|
@ -72,7 +74,12 @@ function useWindowEvents(
|
||||||
|
|
||||||
const funcs = new Map<string, () => void>();
|
const funcs = new Map<string, () => void>();
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
const func = (): void => event.func(editorState);
|
const func = (eventInitDict?: CustomEventInit): void => event.func(
|
||||||
|
editorState,
|
||||||
|
setHistory,
|
||||||
|
setHistoryCurrentStep,
|
||||||
|
eventInitDict
|
||||||
|
);
|
||||||
editorRef.current?.addEventListener(event.name, func);
|
editorRef.current?.addEventListener(event.name, func);
|
||||||
funcs.set(event.name, func);
|
funcs.set(event.name, func);
|
||||||
}
|
}
|
||||||
|
@ -94,7 +101,14 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
const editorRef = useRef<HTMLDivElement>(null);
|
const editorRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
|
useShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
|
||||||
useWindowEvents(history, historyCurrentStep, props.configuration, editorRef);
|
useWindowEvents(
|
||||||
|
history,
|
||||||
|
historyCurrentStep,
|
||||||
|
props.configuration,
|
||||||
|
editorRef,
|
||||||
|
setHistory,
|
||||||
|
setHistoryCurrentStep
|
||||||
|
);
|
||||||
|
|
||||||
const configuration = props.configuration;
|
const configuration = props.configuration;
|
||||||
const current = getCurrentHistoryState(history, historyCurrentStep);
|
const current = getCurrentHistoryState(history, historyCurrentStep);
|
||||||
|
@ -122,15 +136,15 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
setHistory,
|
setHistory,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
)}
|
)}
|
||||||
OnPropertyChange={(key, value, isStyle) => OnPropertyChange(
|
OnPropertyChange={(key, value, type) => OnPropertyChange(
|
||||||
key, value, isStyle,
|
key, value, type,
|
||||||
selected,
|
selected,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
setHistory,
|
setHistory,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
)}
|
)}
|
||||||
AddContainerToSelectedContainer={(type) => AddContainerToSelectedContainer(
|
AddContainer={(type) => AddContainerToSelectedContainer(
|
||||||
type,
|
type,
|
||||||
selected,
|
selected,
|
||||||
configuration,
|
configuration,
|
||||||
|
@ -139,16 +153,6 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
setHistory,
|
setHistory,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
)}
|
)}
|
||||||
AddContainer={(index, type, parentId) => AddContainer(
|
|
||||||
index,
|
|
||||||
type,
|
|
||||||
parentId,
|
|
||||||
configuration,
|
|
||||||
history,
|
|
||||||
historyCurrentStep,
|
|
||||||
setHistory,
|
|
||||||
setHistoryCurrentStep
|
|
||||||
)}
|
|
||||||
AddSymbol={(type) => AddSymbol(
|
AddSymbol={(type) => AddSymbol(
|
||||||
type,
|
type,
|
||||||
configuration,
|
configuration,
|
||||||
|
|
|
@ -22,9 +22,12 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100,
|
height: 100,
|
||||||
|
margin: {},
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
|
type: 'type',
|
||||||
|
maxWidth: Infinity,
|
||||||
|
isFlex: false,
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
isRigidBody: false,
|
|
||||||
isAnchor: false
|
isAnchor: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
|
@ -35,7 +38,6 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
OnPropertyChange={() => {}}
|
OnPropertyChange={() => {}}
|
||||||
SelectContainer={() => {}}
|
SelectContainer={() => {}}
|
||||||
DeleteContainer={() => {}}
|
DeleteContainer={() => {}}
|
||||||
AddContainer={() => {}}
|
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.getByText(/Elements/i));
|
expect(screen.getByText(/Elements/i));
|
||||||
|
@ -56,8 +58,11 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100,
|
height: 100,
|
||||||
|
margin: {},
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
isRigidBody: false,
|
isFlex: false,
|
||||||
|
maxWidth: Infinity,
|
||||||
|
type: 'type',
|
||||||
isAnchor: false,
|
isAnchor: false,
|
||||||
XPositionReference: XPositionReference.Left
|
XPositionReference: XPositionReference.Left
|
||||||
},
|
},
|
||||||
|
@ -73,7 +78,6 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
OnPropertyChange={() => {}}
|
OnPropertyChange={() => {}}
|
||||||
SelectContainer={() => {}}
|
SelectContainer={() => {}}
|
||||||
DeleteContainer={() => {}}
|
DeleteContainer={() => {}}
|
||||||
AddContainer={() => {}}
|
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.getByText(/Elements/i));
|
expect(screen.getByText(/Elements/i));
|
||||||
|
@ -119,7 +123,10 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100,
|
height: 100,
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
isRigidBody: false,
|
margin: {},
|
||||||
|
isFlex: false,
|
||||||
|
maxWidth: Infinity,
|
||||||
|
type: 'type',
|
||||||
isAnchor: false
|
isAnchor: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
|
@ -139,7 +146,10 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
isRigidBody: false,
|
margin: {},
|
||||||
|
isFlex: false,
|
||||||
|
maxWidth: Infinity,
|
||||||
|
type: 'type',
|
||||||
isAnchor: false,
|
isAnchor: false,
|
||||||
XPositionReference: XPositionReference.Left
|
XPositionReference: XPositionReference.Left
|
||||||
},
|
},
|
||||||
|
@ -158,11 +168,14 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
displayedText: 'child-2',
|
displayedText: 'child-2',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
margin: {},
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
isRigidBody: false,
|
isFlex: false,
|
||||||
|
maxWidth: Infinity,
|
||||||
|
type: 'type',
|
||||||
isAnchor: false
|
isAnchor: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
|
@ -178,7 +191,6 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
OnPropertyChange={() => {}}
|
OnPropertyChange={() => {}}
|
||||||
SelectContainer={() => {}}
|
SelectContainer={() => {}}
|
||||||
DeleteContainer={() => {}}
|
DeleteContainer={() => {}}
|
||||||
AddContainer={() => {}}
|
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.getByText(/Elements/i));
|
expect(screen.getByText(/Elements/i));
|
||||||
|
@ -204,7 +216,10 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100,
|
height: 100,
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
isRigidBody: false,
|
margin: {},
|
||||||
|
isFlex: false,
|
||||||
|
maxWidth: Infinity,
|
||||||
|
type: 'type',
|
||||||
isAnchor: false
|
isAnchor: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
|
@ -224,7 +239,10 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
isRigidBody: false,
|
margin: {},
|
||||||
|
isFlex: false,
|
||||||
|
maxWidth: Infinity,
|
||||||
|
type: 'type',
|
||||||
isAnchor: false
|
isAnchor: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
|
@ -245,7 +263,6 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
OnPropertyChange={() => {}}
|
OnPropertyChange={() => {}}
|
||||||
SelectContainer={selectContainer}
|
SelectContainer={selectContainer}
|
||||||
DeleteContainer={() => {}}
|
DeleteContainer={() => {}}
|
||||||
AddContainer={() => {}}
|
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.getByText(/Elements/i));
|
expect(screen.getByText(/Elements/i));
|
||||||
|
@ -269,7 +286,6 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
OnPropertyChange={() => {}}
|
OnPropertyChange={() => {}}
|
||||||
SelectContainer={selectContainer}
|
SelectContainer={selectContainer}
|
||||||
DeleteContainer={() => {}}
|
DeleteContainer={() => {}}
|
||||||
AddContainer={() => {}}
|
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect((propertyId as HTMLInputElement).value === 'main').toBeFalsy();
|
expect((propertyId as HTMLInputElement).value === 'main').toBeFalsy();
|
||||||
|
|
|
@ -5,10 +5,10 @@ import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import { getDepth, MakeIterator } from '../../utils/itertools';
|
import { getDepth, MakeIterator } from '../../utils/itertools';
|
||||||
import { Menu } from '../Menu/Menu';
|
import { Menu } from '../Menu/Menu';
|
||||||
import { MenuItem } from '../Menu/MenuItem';
|
import { MenuItem } from '../Menu/MenuItem';
|
||||||
import { handleDragLeave, handleDragOver, handleLeftClick, handleOnDrop, handleRightClick, useMouseEvents } from './MouseEventHandlers';
|
import { useMouseEvents } from './MouseEventHandlers';
|
||||||
import { IPoint } from '../../Interfaces/IPoint';
|
import { IPoint } from '../../Interfaces/IPoint';
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import { Dispatch, RefObject, SetStateAction } from 'react';
|
import { PropertyType } from '../../Enums/PropertyType';
|
||||||
|
|
||||||
interface IElementsSidebarProps {
|
interface IElementsSidebarProps {
|
||||||
MainContainer: IContainerModel
|
MainContainer: IContainerModel
|
||||||
|
@ -16,16 +16,23 @@ interface IElementsSidebarProps {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
isHistoryOpen: boolean
|
isHistoryOpen: boolean
|
||||||
SelectedContainer: IContainerModel | undefined
|
SelectedContainer: IContainerModel | undefined
|
||||||
OnPropertyChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
OnPropertyChange: (
|
||||||
|
key: string,
|
||||||
|
value: string | number | boolean,
|
||||||
|
type?: PropertyType
|
||||||
|
) => void
|
||||||
SelectContainer: (containerId: string) => void
|
SelectContainer: (containerId: string) => void
|
||||||
DeleteContainer: (containerid: string) => void
|
DeleteContainer: (containerid: string) => void
|
||||||
AddContainer: (index: number, type: string, parent: string) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElementsSidebarProps): JSX.Element => {
|
export const ElementsSidebar: React.FC<IElementsSidebarProps> = (
|
||||||
|
props: IElementsSidebarProps
|
||||||
|
): JSX.Element => {
|
||||||
// States
|
// States
|
||||||
const [isContextMenuOpen, setIsContextMenuOpen] = React.useState<boolean>(false);
|
const [isContextMenuOpen, setIsContextMenuOpen] =
|
||||||
const [onClickContainerId, setOnClickContainerId] = React.useState<string>('');
|
React.useState<boolean>(false);
|
||||||
|
const [onClickContainerId, setOnClickContainerId] =
|
||||||
|
React.useState<string>('');
|
||||||
const [contextMenuPosition, setContextMenuPosition] = React.useState<IPoint>({
|
const [contextMenuPosition, setContextMenuPosition] = React.useState<IPoint>({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0
|
y: 0
|
||||||
|
@ -45,21 +52,29 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
||||||
// Render
|
// Render
|
||||||
let isOpenClasses = '-right-64';
|
let isOpenClasses = '-right-64';
|
||||||
if (props.isOpen) {
|
if (props.isOpen) {
|
||||||
isOpenClasses = props.isHistoryOpen
|
isOpenClasses = props.isHistoryOpen ? 'right-64' : 'right-0';
|
||||||
? 'right-64'
|
|
||||||
: 'right-0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = MakeIterator(props.MainContainer);
|
const it = MakeIterator(props.MainContainer);
|
||||||
const containers = [...it];
|
const containers = [...it];
|
||||||
const Row = ({ index, style }: {index: number, style: React.CSSProperties}): JSX.Element => {
|
const Row = ({
|
||||||
|
index,
|
||||||
|
style
|
||||||
|
}: {
|
||||||
|
index: number
|
||||||
|
style: React.CSSProperties
|
||||||
|
}): JSX.Element => {
|
||||||
const container = containers[index];
|
const container = containers[index];
|
||||||
const depth: number = getDepth(container);
|
const depth: number = getDepth(container);
|
||||||
const key = container.properties.id.toString();
|
const key = container.properties.id.toString();
|
||||||
const text = container.properties.displayedText === key
|
const text =
|
||||||
|
container.properties.displayedText === key
|
||||||
? `${'|\t'.repeat(depth)} ${key}`
|
? `${'|\t'.repeat(depth)} ${key}`
|
||||||
: `${'|\t'.repeat(depth)} ${container.properties.displayedText} (${key})`;
|
: `${'|\t'.repeat(depth)} ${
|
||||||
const selectedClass: string = props.SelectedContainer !== undefined &&
|
container.properties.displayedText
|
||||||
|
} (${key})`;
|
||||||
|
const selectedClass: string =
|
||||||
|
props.SelectedContainer !== undefined &&
|
||||||
props.SelectedContainer !== null &&
|
props.SelectedContainer !== null &&
|
||||||
props.SelectedContainer.properties.id === container.properties.id
|
props.SelectedContainer.properties.id === container.properties.id
|
||||||
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
|
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
|
||||||
|
@ -67,16 +82,11 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={
|
className={`w-full border-blue-500 elements-sidebar-row whitespace-pre
|
||||||
`w-full border-blue-500 elements-sidebar-row whitespace-pre
|
text-left text-sm font-medium transition-all ${selectedClass}`}
|
||||||
text-left text-sm font-medium transition-all ${selectedClass}`
|
|
||||||
}
|
|
||||||
id={key}
|
id={key}
|
||||||
key={key}
|
key={key}
|
||||||
style={style}
|
style={style}
|
||||||
onDrop={(event) => handleOnDrop(event, props.MainContainer, props.AddContainer)}
|
|
||||||
onDragOver={(event) => handleDragOver(event, props.MainContainer)}
|
|
||||||
onDragLeave={(event) => handleDragLeave(event)}
|
|
||||||
onClick={() => props.SelectContainer(container.properties.id)}
|
onClick={() => props.SelectContainer(container.properties.id)}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
|
@ -85,13 +95,13 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
|
<div
|
||||||
<div className='bg-slate-100 font-bold sidebar-title'>
|
className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}
|
||||||
Elements
|
>
|
||||||
</div>
|
<div className="bg-slate-100 font-bold sidebar-title">Elements</div>
|
||||||
<div ref={elementRef} className='h-96 text-gray-800'>
|
<div ref={elementRef} className="h-96 text-gray-800">
|
||||||
<List
|
<List
|
||||||
className='List divide-y divide-black'
|
className="List divide-y divide-black"
|
||||||
itemCount={containers.length}
|
itemCount={containers.length}
|
||||||
itemSize={35}
|
itemSize={35}
|
||||||
height={384}
|
height={384}
|
||||||
|
@ -101,15 +111,19 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
<Menu
|
<Menu
|
||||||
className='transition-opacity rounded bg-slate-200 py-1 drop-shadow-xl'
|
className="transition-opacity rounded bg-slate-200 py-1 drop-shadow-xl"
|
||||||
x={contextMenuPosition.x}
|
x={contextMenuPosition.x}
|
||||||
y={contextMenuPosition.y}
|
y={contextMenuPosition.y}
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
>
|
>
|
||||||
<MenuItem className='contextmenu-item' text='Delete' onClick={() => {
|
<MenuItem
|
||||||
|
className="contextmenu-item"
|
||||||
|
text="Delete"
|
||||||
|
onClick={() => {
|
||||||
setIsContextMenuOpen(false);
|
setIsContextMenuOpen(false);
|
||||||
props.DeleteContainer(onClickContainerId);
|
props.DeleteContainer(onClickContainerId);
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
<Properties
|
<Properties
|
||||||
properties={props.SelectedContainer?.properties}
|
properties={props.SelectedContainer?.properties}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import React, { RefObject, Dispatch, SetStateAction, useEffect } from 'react';
|
import React, { RefObject, Dispatch, SetStateAction, useEffect } from 'react';
|
||||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
|
||||||
import { IPoint } from '../../Interfaces/IPoint';
|
import { IPoint } from '../../Interfaces/IPoint';
|
||||||
import { findContainerById } from '../../utils/itertools';
|
|
||||||
|
|
||||||
export function useMouseEvents(
|
export function useMouseEvents(
|
||||||
isContextMenuOpen: boolean,
|
isContextMenuOpen: boolean,
|
||||||
|
@ -81,95 +79,3 @@ export function handleLeftClick(
|
||||||
setOnClickContainerId('');
|
setOnClickContainerId('');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeBorderClasses(target: HTMLButtonElement): void {
|
|
||||||
const bordersClasses = ['border-t-8', 'border-8', 'border-b-8'];
|
|
||||||
target.classList.remove(...bordersClasses);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleDragLeave(event: React.DragEvent): void {
|
|
||||||
const target: HTMLButtonElement = event.target as HTMLButtonElement;
|
|
||||||
removeBorderClasses(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleDragOver(
|
|
||||||
event: React.DragEvent,
|
|
||||||
mainContainer: IContainerModel
|
|
||||||
): void {
|
|
||||||
event.preventDefault();
|
|
||||||
const target: HTMLButtonElement = event.target as HTMLButtonElement;
|
|
||||||
const rect = target.getBoundingClientRect();
|
|
||||||
const y = event.clientY - rect.top; // y position within the element.
|
|
||||||
removeBorderClasses(target);
|
|
||||||
|
|
||||||
if (target.id === mainContainer.properties.id) {
|
|
||||||
target.classList.add('border-8');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (y < 12) {
|
|
||||||
target.classList.add('border-t-8');
|
|
||||||
} else if (y < 24) {
|
|
||||||
target.classList.add('border-8');
|
|
||||||
} else {
|
|
||||||
target.classList.add('border-b-8');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleOnDrop(
|
|
||||||
event: React.DragEvent,
|
|
||||||
mainContainer: IContainerModel,
|
|
||||||
addContainer: (index: number, type: string, parent: string) => void
|
|
||||||
): void {
|
|
||||||
event.preventDefault();
|
|
||||||
const type = event.dataTransfer.getData('type');
|
|
||||||
const target: HTMLButtonElement = event.target as HTMLButtonElement;
|
|
||||||
removeBorderClasses(target);
|
|
||||||
|
|
||||||
const targetContainer: IContainerModel | undefined = findContainerById(
|
|
||||||
mainContainer,
|
|
||||||
target.id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (targetContainer === undefined) {
|
|
||||||
throw new Error('[handleOnDrop] Tried to drop onto a unknown container!');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetContainer === mainContainer) {
|
|
||||||
// if the container is the root, only add type as child
|
|
||||||
addContainer(
|
|
||||||
targetContainer.children.length,
|
|
||||||
type,
|
|
||||||
targetContainer.properties.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetContainer.parent === null ||
|
|
||||||
targetContainer.parent === undefined) {
|
|
||||||
throw new Error('[handleDrop] Tried to drop into a child container without a parent!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect = target.getBoundingClientRect();
|
|
||||||
const y = event.clientY - rect.top; // y position within the element.
|
|
||||||
|
|
||||||
// locate the hitboxes
|
|
||||||
if (y < 12) {
|
|
||||||
const index = targetContainer.parent.children.indexOf(targetContainer);
|
|
||||||
addContainer(
|
|
||||||
index,
|
|
||||||
type,
|
|
||||||
targetContainer.parent.properties.id
|
|
||||||
);
|
|
||||||
} else if (y < 24) {
|
|
||||||
addContainer(
|
|
||||||
targetContainer.children.length,
|
|
||||||
type,
|
|
||||||
targetContainer.properties.id);
|
|
||||||
} else {
|
|
||||||
const index = targetContainer.parent.children.indexOf(targetContainer);
|
|
||||||
addContainer(
|
|
||||||
index + 1,
|
|
||||||
type,
|
|
||||||
targetContainer.parent.properties.id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,10 +18,17 @@ interface IContainerProps {
|
||||||
*/
|
*/
|
||||||
export const Container: React.FC<IContainerProps> = (props: IContainerProps) => {
|
export const Container: React.FC<IContainerProps> = (props: IContainerProps) => {
|
||||||
const containersElements = props.model.children.map(child => <Container key={`container-${child.properties.id}`} model={child} />);
|
const containersElements = props.model.children.map(child => <Container key={`container-${child.properties.id}`} model={child} />);
|
||||||
const xText = props.model.properties.width / 2;
|
|
||||||
const yText = props.model.properties.height / 2;
|
|
||||||
|
|
||||||
const transform = `translate(${props.model.properties.x}, ${props.model.properties.y})`;
|
const width: number = props.model.properties.width;
|
||||||
|
const height: number = props.model.properties.height;
|
||||||
|
|
||||||
|
const x = props.model.properties.x;
|
||||||
|
const y = props.model.properties.y;
|
||||||
|
|
||||||
|
const xText = width / 2;
|
||||||
|
const yText = height / 2;
|
||||||
|
|
||||||
|
const transform = `translate(${x}, ${y})`;
|
||||||
|
|
||||||
// g style
|
// g style
|
||||||
const defaultStyle: React.CSSProperties = {
|
const defaultStyle: React.CSSProperties = {
|
||||||
|
@ -39,8 +46,8 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
const svg = (props.model.properties.customSVG != null)
|
const svg = (props.model.properties.customSVG != null)
|
||||||
? CreateReactCustomSVG(props.model.properties.customSVG, props.model.properties)
|
? CreateReactCustomSVG(props.model.properties.customSVG, props.model.properties)
|
||||||
: (<rect
|
: (<rect
|
||||||
width={props.model.properties.width}
|
width={width}
|
||||||
height={props.model.properties.height}
|
height={height}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
</rect>);
|
</rect>);
|
||||||
|
@ -49,10 +56,10 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
const dimensionMargin = DIMENSION_MARGIN * depth;
|
const dimensionMargin = DIMENSION_MARGIN * depth;
|
||||||
const id = `dim-${props.model.properties.id}`;
|
const id = `dim-${props.model.properties.id}`;
|
||||||
const xStart: number = 0;
|
const xStart: number = 0;
|
||||||
const xEnd = props.model.properties.width;
|
const xEnd = width;
|
||||||
const y = -dimensionMargin;
|
const yDim = -dimensionMargin;
|
||||||
const strokeWidth = 1;
|
const strokeWidth = 1;
|
||||||
const text = (props.model.properties.width ?? 0).toString();
|
const text = (width ?? 0).toString();
|
||||||
|
|
||||||
let dimensionChildren: JSX.Element | null = null;
|
let dimensionChildren: JSX.Element | null = null;
|
||||||
if (props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
|
if (props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
|
||||||
|
@ -85,8 +92,8 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
id={id}
|
id={id}
|
||||||
xStart={xStart}
|
xStart={xStart}
|
||||||
xEnd={xEnd}
|
xEnd={xEnd}
|
||||||
yStart={y}
|
yStart={yDim}
|
||||||
yEnd={y}
|
yEnd={yDim}
|
||||||
strokeWidth={strokeWidth}
|
strokeWidth={strokeWidth}
|
||||||
text={text}
|
text={text}
|
||||||
/>
|
/>
|
||||||
|
@ -107,8 +114,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: number):
|
function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: number): { childrenId: string, xChildrenStart: number, xChildrenEnd: number, yChildren: number, textChildren: string } {
|
||||||
{ childrenId: string, xChildrenStart: number, xChildrenEnd: number, yChildren: number, textChildren: string } {
|
|
||||||
const childrenId = `dim-children-${props.model.properties.id}`;
|
const childrenId = `dim-children-${props.model.properties.id}`;
|
||||||
|
|
||||||
const lastChild = props.model.children[props.model.children.length - 1];
|
const lastChild = props.model.children[props.model.children.length - 1];
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { getAbsolutePosition } from '../../../utils/itertools';
|
import { getAbsolutePosition } from '../../../utils/itertools';
|
||||||
|
import { RemoveMargin } from '../../../utils/svg';
|
||||||
|
|
||||||
interface ISelectorProps {
|
interface ISelectorProps {
|
||||||
selected?: IContainerModel
|
selected?: IContainerModel
|
||||||
|
@ -14,11 +15,19 @@ export const Selector: React.FC<ISelectorProps> = (props) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [x, y] = getAbsolutePosition(props.selected);
|
let [x, y] = getAbsolutePosition(props.selected);
|
||||||
const [width, height] = [
|
let [width, height] = [
|
||||||
props.selected.properties.width,
|
props.selected.properties.width,
|
||||||
props.selected.properties.height
|
props.selected.properties.height
|
||||||
];
|
];
|
||||||
|
|
||||||
|
({ x, y, width, height } = RemoveMargin(x, y, width, height,
|
||||||
|
props.selected.properties.margin.left,
|
||||||
|
props.selected.properties.margin.bottom,
|
||||||
|
props.selected.properties.margin.top,
|
||||||
|
props.selected.properties.margin.right
|
||||||
|
));
|
||||||
|
|
||||||
const style: React.CSSProperties = {
|
const style: React.CSSProperties = {
|
||||||
stroke: '#3B82F6', // tw blue-500
|
stroke: '#3B82F6', // tw blue-500
|
||||||
strokeWidth: 4,
|
strokeWidth: 4,
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { Bar } from '../Bar/Bar';
|
||||||
import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
|
import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
|
||||||
import { Symbols } from '../Symbols/Symbols';
|
import { Symbols } from '../Symbols/Symbols';
|
||||||
import { SymbolsSidebar } from '../SymbolsSidebar/SymbolsSidebar';
|
import { SymbolsSidebar } from '../SymbolsSidebar/SymbolsSidebar';
|
||||||
|
import { PropertyType } from '../../Enums/PropertyType';
|
||||||
|
|
||||||
interface IUIProps {
|
interface IUIProps {
|
||||||
SelectedContainer: IContainerModel | undefined
|
SelectedContainer: IContainerModel | undefined
|
||||||
|
@ -21,9 +22,8 @@ interface IUIProps {
|
||||||
AvailableSymbols: IAvailableSymbol[]
|
AvailableSymbols: IAvailableSymbol[]
|
||||||
SelectContainer: (containerId: string) => void
|
SelectContainer: (containerId: string) => void
|
||||||
DeleteContainer: (containerId: string) => void
|
DeleteContainer: (containerId: string) => void
|
||||||
OnPropertyChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
OnPropertyChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
|
||||||
AddContainerToSelectedContainer: (type: string) => void
|
AddContainer: (type: string) => void
|
||||||
AddContainer: (index: number, type: string, parentId: string) => void
|
|
||||||
AddSymbol: (type: string) => void
|
AddSymbol: (type: string) => void
|
||||||
OnSymbolPropertyChange: (key: string, value: string | number | boolean) => void
|
OnSymbolPropertyChange: (key: string, value: string | number | boolean) => void
|
||||||
SelectSymbol: (symbolId: string) => void
|
SelectSymbol: (symbolId: string) => void
|
||||||
|
@ -75,7 +75,7 @@ export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
|
||||||
<Sidebar
|
<Sidebar
|
||||||
componentOptions={props.AvailableContainers}
|
componentOptions={props.AvailableContainers}
|
||||||
isOpen={isSidebarOpen}
|
isOpen={isSidebarOpen}
|
||||||
buttonOnClick={props.AddContainerToSelectedContainer}
|
buttonOnClick={props.AddContainer}
|
||||||
/>
|
/>
|
||||||
<Symbols
|
<Symbols
|
||||||
componentOptions={props.AvailableSymbols}
|
componentOptions={props.AvailableSymbols}
|
||||||
|
@ -91,7 +91,6 @@ export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
|
||||||
OnPropertyChange={props.OnPropertyChange}
|
OnPropertyChange={props.OnPropertyChange}
|
||||||
SelectContainer={props.SelectContainer}
|
SelectContainer={props.SelectContainer}
|
||||||
DeleteContainer={props.DeleteContainer}
|
DeleteContainer={props.DeleteContainer}
|
||||||
AddContainer={props.AddContainer}
|
|
||||||
/>
|
/>
|
||||||
<SymbolsSidebar
|
<SymbolsSidebar
|
||||||
SelectedSymbolId={props.current.SelectedSymbolId}
|
SelectedSymbolId={props.current.SelectedSymbolId}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
* Add method when creating a container
|
* Add method when creating a container
|
||||||
* - Append will append to the last children in list
|
* - Append will append to the last children in list
|
||||||
* - Insert will always place it at the begining
|
* - Insert will always place it at the begining
|
||||||
|
* - Replace will remove the selected container and insert a new one
|
||||||
* (default: Append)
|
* (default: Append)
|
||||||
*/
|
*/
|
||||||
export enum AddMethod {
|
export enum AddMethod {
|
||||||
Append,
|
Append,
|
||||||
Insert
|
Insert,
|
||||||
|
Replace // TODO: Implement this
|
||||||
}
|
}
|
||||||
|
|
22
src/Enums/PropertyType.ts
Normal file
22
src/Enums/PropertyType.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describe the type of the property.
|
||||||
|
* Used for the assignation in the OnPropertyChange function
|
||||||
|
* See ContainerOperations.ts's OnPropertyChange
|
||||||
|
*/
|
||||||
|
export enum PropertyType {
|
||||||
|
/**
|
||||||
|
* Simple property: is not inside any object: id, x, width... (default)
|
||||||
|
*/
|
||||||
|
SIMPLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Style property: is inside the style object: stroke, fillOpacity...
|
||||||
|
*/
|
||||||
|
STYLE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Margin property: is inside the margin property: left, bottom, top, right...
|
||||||
|
*/
|
||||||
|
MARGIN,
|
||||||
|
}
|
|
@ -1,5 +1,15 @@
|
||||||
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
|
import { getCurrentHistory } from '../Components/Editor/Editor';
|
||||||
|
import { IConfiguration } from '../Interfaces/IConfiguration';
|
||||||
import { IEditorState } from '../Interfaces/IEditorState';
|
import { IEditorState } from '../Interfaces/IEditorState';
|
||||||
import { IHistoryState } from '../Interfaces/IHistoryState';
|
import { IHistoryState } from '../Interfaces/IHistoryState';
|
||||||
|
import { ReviveState } from '../utils/saveload';
|
||||||
|
|
||||||
|
|
||||||
|
const initEditor = (configuration: IConfiguration): void => {
|
||||||
|
// faire comme la callback de fetch
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const getEditorState = (editorState: IEditorState): void => {
|
const getEditorState = (editorState: IEditorState): void => {
|
||||||
const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: editorState });
|
const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: editorState });
|
||||||
|
@ -13,14 +23,35 @@ const getCurrentHistoryState = (editorState: IEditorState): void => {
|
||||||
document.dispatchEvent(customEvent);
|
document.dispatchEvent(customEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const appendNewState = (
|
||||||
|
editorState: IEditorState,
|
||||||
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>,
|
||||||
|
eventInitDict?: CustomEventInit
|
||||||
|
): void => {
|
||||||
|
const state: IHistoryState = JSON.parse(eventInitDict?.detail.state);
|
||||||
|
ReviveState(state);
|
||||||
|
const history = getCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
||||||
|
|
||||||
|
history.push(state);
|
||||||
|
setHistory(history);
|
||||||
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
};
|
||||||
|
|
||||||
export interface IEditorEvent {
|
export interface IEditorEvent {
|
||||||
name: string
|
name: string
|
||||||
func: (editorState: IEditorState) => void
|
func: (
|
||||||
|
editorState: IEditorState,
|
||||||
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>,
|
||||||
|
eventInitDict?: CustomEventInit
|
||||||
|
) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const events: IEditorEvent[] = [
|
const events: IEditorEvent[] = [
|
||||||
{ name: 'getEditorState', func: getEditorState },
|
{ name: 'getEditorState', func: getEditorState },
|
||||||
{ name: 'getCurrentHistoryState', func: getCurrentHistoryState }
|
{ name: 'getCurrentHistoryState', func: getCurrentHistoryState },
|
||||||
|
{ name: 'appendNewState', func: appendNewState }
|
||||||
];
|
];
|
||||||
|
|
||||||
export default events;
|
export default events;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AddMethod } from '../Enums/AddMethod';
|
import { AddMethod } from '../Enums/AddMethod';
|
||||||
import { XPositionReference } from '../Enums/XPositionReference';
|
import { XPositionReference } from '../Enums/XPositionReference';
|
||||||
|
import { IMargin } from './IMargin';
|
||||||
|
|
||||||
/** Model of available container used in application configuration */
|
/** Model of available container used in application configuration */
|
||||||
export interface IAvailableContainer {
|
export interface IAvailableContainer {
|
||||||
|
@ -10,6 +11,9 @@ export interface IAvailableContainer {
|
||||||
Width?: number
|
Width?: number
|
||||||
Height?: number
|
Height?: number
|
||||||
MinWidth?: number
|
MinWidth?: number
|
||||||
|
MaxWidth?: number
|
||||||
|
Margin?: IMargin
|
||||||
|
IsFlex?: boolean
|
||||||
AddMethod?: AddMethod
|
AddMethod?: AddMethod
|
||||||
XPositionReference?: XPositionReference
|
XPositionReference?: XPositionReference
|
||||||
CustomSVG?: string
|
CustomSVG?: string
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { XPositionReference } from '../Enums/XPositionReference';
|
import { XPositionReference } from '../Enums/XPositionReference';
|
||||||
|
import { IMargin } from './IMargin';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Properties of a container
|
* Properties of a container
|
||||||
|
@ -8,6 +9,9 @@ export default interface IContainerProperties {
|
||||||
/** id of the container */
|
/** id of the container */
|
||||||
id: string
|
id: string
|
||||||
|
|
||||||
|
/** type matching the configuration on construction */
|
||||||
|
type: string
|
||||||
|
|
||||||
/** id of the parent container (null when there is no parent) */
|
/** id of the parent container (null when there is no parent) */
|
||||||
parentId: string
|
parentId: string
|
||||||
|
|
||||||
|
@ -23,6 +27,9 @@ export default interface IContainerProperties {
|
||||||
/** vertical offset */
|
/** vertical offset */
|
||||||
y: number
|
y: number
|
||||||
|
|
||||||
|
/** margin */
|
||||||
|
margin: IMargin
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum width (min=1)
|
* Minimum width (min=1)
|
||||||
* Allows the container to set isRigidBody to false when it gets squeezed
|
* Allows the container to set isRigidBody to false when it gets squeezed
|
||||||
|
@ -30,18 +37,23 @@ export default interface IContainerProperties {
|
||||||
*/
|
*/
|
||||||
minWidth: number
|
minWidth: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum width
|
||||||
|
*/
|
||||||
|
maxWidth: number
|
||||||
|
|
||||||
/** width */
|
/** width */
|
||||||
width: number
|
width: number
|
||||||
|
|
||||||
/** height */
|
/** height */
|
||||||
height: number
|
height: number
|
||||||
|
|
||||||
/** true if rigid, false otherwise */
|
|
||||||
isRigidBody: boolean
|
|
||||||
|
|
||||||
/** true if anchor, false otherwise */
|
/** true if anchor, false otherwise */
|
||||||
isAnchor: boolean
|
isAnchor: boolean
|
||||||
|
|
||||||
|
/** true if flex, false otherwise */
|
||||||
|
isFlex: boolean
|
||||||
|
|
||||||
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
||||||
XPositionReference: XPositionReference
|
XPositionReference: XPositionReference
|
||||||
|
|
||||||
|
|
6
src/Interfaces/IMargin.ts
Normal file
6
src/Interfaces/IMargin.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export interface IMargin {
|
||||||
|
left?: number
|
||||||
|
bottom?: number
|
||||||
|
top?: number
|
||||||
|
right?: number
|
||||||
|
}
|
|
@ -68,7 +68,7 @@ export const DEFAULT_CONFIG: IConfiguration = {
|
||||||
AvailableContainers: [
|
AvailableContainers: [
|
||||||
{
|
{
|
||||||
Type: 'Container',
|
Type: 'Container',
|
||||||
Width: 75,
|
MaxWidth: 200,
|
||||||
Height: 100,
|
Height: 100,
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 0,
|
fillOpacity: 0,
|
||||||
|
@ -79,7 +79,7 @@ export const DEFAULT_CONFIG: IConfiguration = {
|
||||||
AvailableSymbols: [],
|
AvailableSymbols: [],
|
||||||
MainContainer: {
|
MainContainer: {
|
||||||
Type: 'Container',
|
Type: 'Container',
|
||||||
Width: 2000,
|
Width: 800,
|
||||||
Height: 100,
|
Height: 100,
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 0,
|
fillOpacity: 0,
|
||||||
|
@ -93,16 +93,19 @@ export const DEFAULT_CONFIG: IConfiguration = {
|
||||||
*/
|
*/
|
||||||
export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
|
export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
|
||||||
id: 'main',
|
id: 'main',
|
||||||
|
type: 'container',
|
||||||
parentId: '',
|
parentId: '',
|
||||||
linkedSymbolId: '',
|
linkedSymbolId: '',
|
||||||
displayedText: 'main',
|
displayedText: 'main',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
margin: {},
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
|
maxWidth: Number.MAX_SAFE_INTEGER,
|
||||||
width: Number(DEFAULT_CONFIG.MainContainer.Width),
|
width: Number(DEFAULT_CONFIG.MainContainer.Width),
|
||||||
height: Number(DEFAULT_CONFIG.MainContainer.Height),
|
height: Number(DEFAULT_CONFIG.MainContainer.Height),
|
||||||
isRigidBody: false,
|
|
||||||
isAnchor: false,
|
isAnchor: false,
|
||||||
|
isFlex: false,
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
style: {
|
style: {
|
||||||
stroke: 'black',
|
stroke: 'black',
|
||||||
|
@ -126,20 +129,25 @@ export const GetDefaultContainerProps = (
|
||||||
parent: IContainerModel,
|
parent: IContainerModel,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
containerConfig: IAvailableContainer
|
containerConfig: IAvailableContainer
|
||||||
): IContainerProperties => ({
|
): IContainerProperties => ({
|
||||||
id: `${type}-${typeCount}`,
|
id: `${type}-${typeCount}`,
|
||||||
|
type,
|
||||||
parentId: parent.properties.id,
|
parentId: parent.properties.id,
|
||||||
linkedSymbolId: '',
|
linkedSymbolId: '',
|
||||||
displayedText: `${type}-${typeCount}`,
|
displayedText: `${type}-${typeCount}`,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width: containerConfig.Width ?? containerConfig.MinWidth ?? parent.properties.width,
|
margin: containerConfig.Margin ?? {},
|
||||||
height: containerConfig.Height ?? parent.properties.height,
|
width,
|
||||||
isRigidBody: false, // set this to true to replicate Florian's project
|
height,
|
||||||
isAnchor: false,
|
isAnchor: false,
|
||||||
|
isFlex: containerConfig.IsFlex ?? containerConfig.Width === undefined,
|
||||||
XPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
|
XPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
|
||||||
minWidth: containerConfig.MinWidth ?? 1,
|
minWidth: containerConfig.MinWidth ?? 1,
|
||||||
|
maxWidth: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER,
|
||||||
customSVG: containerConfig.CustomSVG,
|
customSVG: containerConfig.CustomSVG,
|
||||||
style: structuredClone(containerConfig.Style),
|
style: structuredClone(containerConfig.Style),
|
||||||
userData: structuredClone(containerConfig.UserData)
|
userData: structuredClone(containerConfig.UserData)
|
||||||
|
|
|
@ -14,7 +14,7 @@ export function * MakeIterator(root: IContainerModel): Generator<IContainerModel
|
||||||
for (let i = container.children.length - 1; i >= 0; i--) {
|
for (let i = container.children.length - 1; i >= 0; i--) {
|
||||||
const child = container.children[i];
|
const child = container.children[i];
|
||||||
if (visited.has(child)) {
|
if (visited.has(child)) {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
visited.add(child);
|
visited.add(child);
|
||||||
queue.push(child);
|
queue.push(child);
|
||||||
|
@ -71,9 +71,20 @@ export function getDepth(parent: IContainerModel): number {
|
||||||
* @returns The absolute position of the container
|
* @returns The absolute position of the container
|
||||||
*/
|
*/
|
||||||
export function getAbsolutePosition(container: IContainerModel): [number, number] {
|
export function getAbsolutePosition(container: IContainerModel): [number, number] {
|
||||||
let x = container.properties.x;
|
const x = container.properties.x;
|
||||||
let y = container.properties.y;
|
const y = container.properties.y;
|
||||||
let current = container.parent;
|
return cancelParentTransform(container.parent, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel 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
|
||||||
|
*/
|
||||||
|
export function cancelParentTransform(parent: IContainerModel | null, x: number, y: number): [number, number] {
|
||||||
|
let current = parent;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
x += current.properties.x;
|
x += current.properties.x;
|
||||||
y += current.properties.y;
|
y += current.properties.y;
|
||||||
|
@ -82,6 +93,23 @@ export function getAbsolutePosition(container: IContainerModel): [number, number
|
||||||
return [x, y];
|
return [x, y];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel 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
|
||||||
|
*/
|
||||||
|
export function applyParentTransform(parent: IContainerModel | null, x: number, y: number): [number, number] {
|
||||||
|
let current = parent;
|
||||||
|
while (current != null) {
|
||||||
|
x -= current.properties.x;
|
||||||
|
y -= current.properties.y;
|
||||||
|
current = current.parent;
|
||||||
|
}
|
||||||
|
return [x, y];
|
||||||
|
}
|
||||||
|
|
||||||
export function findContainerById(root: IContainerModel, id: string): IContainerModel | undefined {
|
export function findContainerById(root: IContainerModel, id: string): IContainerModel | undefined {
|
||||||
const it = MakeIterator(root);
|
const it = MakeIterator(root);
|
||||||
for (const container of it) {
|
for (const container of it) {
|
||||||
|
@ -91,3 +119,20 @@ export function findContainerById(root: IContainerModel, id: string): IContainer
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IPair<T> {
|
||||||
|
cur: T
|
||||||
|
next: T
|
||||||
|
}
|
||||||
|
|
||||||
|
export function * pairwise<T>(arr: T[]): Generator<IPair<T>, void, unknown> {
|
||||||
|
for (let i = 0; i < arr.length - 1; i++) {
|
||||||
|
yield { cur: arr[i], next: arr[i + 1] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function * reversePairwise<T>(arr: T[]): Generator<IPair<T>, void, unknown> {
|
||||||
|
for (let i = arr.length - 1; i > 0; i--) {
|
||||||
|
yield { cur: arr[i], next: arr[i - 1] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { findContainerById, MakeIterator } from './itertools';
|
import { findContainerById, MakeIterator } from './itertools';
|
||||||
import { IEditorState } from '../Interfaces/IEditorState';
|
import { IEditorState } from '../Interfaces/IEditorState';
|
||||||
|
import { IHistoryState } from '../Interfaces/IHistoryState';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revive the Editor state
|
* Revive the Editor state
|
||||||
|
@ -14,8 +15,15 @@ export function Revive(editorState: IEditorState): void {
|
||||||
|
|
||||||
// restore the parents and the selected container
|
// restore the parents and the selected container
|
||||||
for (const state of history) {
|
for (const state of history) {
|
||||||
|
ReviveState(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ReviveState = (
|
||||||
|
state: IHistoryState
|
||||||
|
): void => {
|
||||||
if (state.MainContainer === null || state.MainContainer === undefined) {
|
if (state.MainContainer === null || state.MainContainer === undefined) {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Symbols = new Map(state.Symbols);
|
state.Symbols = new Map(state.Symbols);
|
||||||
|
@ -36,8 +44,7 @@ export function Revive(editorState: IEditorState): void {
|
||||||
}
|
}
|
||||||
container.parent = parent;
|
container.parent = parent;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export const getCircularReplacer = (): (key: any, value: object | Map<string, any> | null) => object | null | undefined => {
|
export const getCircularReplacer = (): (key: any, value: object | Map<string, any> | null) => object | null | undefined => {
|
||||||
return (key: any, value: object | null) => {
|
return (key: any, value: object | null) => {
|
||||||
|
|
246
src/utils/simplex.ts
Normal file
246
src/utils/simplex.ts
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
/**
|
||||||
|
* @module {Simplex} Apply the simplex algorithm
|
||||||
|
* https://www.imse.iastate.edu/files/2015/08/Explanation-of-Simplex-Method.docx
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the simplex algorithms to the minimum widths
|
||||||
|
*
|
||||||
|
* Note: Some optimizations were made to improve performance in order to solve
|
||||||
|
* with max(minWidths). In point of fact most coefficient are equal to 1 or -1.
|
||||||
|
*
|
||||||
|
* Let the following format be the linear problem :
|
||||||
|
* x >= b are the minimum widths constraint
|
||||||
|
* sum(x) <= b is the maximum width constraint
|
||||||
|
* s are slack variables
|
||||||
|
* @param minWidths
|
||||||
|
* @param requiredMaxWidth
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function Simplex(minWidths: number[], requiredMaxWidth: number): number[] {
|
||||||
|
/// 1) standardized the equations
|
||||||
|
// add the min widths constraints
|
||||||
|
const maximumConstraints = minWidths.map(minWidth => minWidth * -1);
|
||||||
|
|
||||||
|
// add the max width constraint
|
||||||
|
maximumConstraints.push(requiredMaxWidth);
|
||||||
|
|
||||||
|
/// 2) Create the initial matrix
|
||||||
|
// get row length (nVariables + nConstraints + 1 (z) + 1 (b))
|
||||||
|
const nVariables = minWidths.length;
|
||||||
|
const nConstraints = maximumConstraints.length;
|
||||||
|
const rowlength = nVariables + nConstraints + 2;
|
||||||
|
const matrix = GetInitialMatrix(maximumConstraints, rowlength, nVariables);
|
||||||
|
|
||||||
|
/// Apply the algorithm
|
||||||
|
const finalMatrix = ApplyMainLoop(matrix, rowlength);
|
||||||
|
|
||||||
|
// 5) read the solutions
|
||||||
|
const solutions: number[] = GetSolutions(nVariables + nConstraints + 1, finalMatrix);
|
||||||
|
return solutions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_TRIES = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific to min widths algorithm
|
||||||
|
* Get the initial matrix from the maximum constraints
|
||||||
|
* and the number of variables
|
||||||
|
* @param maximumConstraints
|
||||||
|
* @param rowlength
|
||||||
|
* @param nVariables
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function GetInitialMatrix(
|
||||||
|
maximumConstraints: number[],
|
||||||
|
rowlength: number,
|
||||||
|
nVariables: number
|
||||||
|
): number[][] {
|
||||||
|
const nConstraints = maximumConstraints.length;
|
||||||
|
const matrix = maximumConstraints.map((maximumConstraint, index) => {
|
||||||
|
const row: number[] = Array(rowlength).fill(0);
|
||||||
|
|
||||||
|
// insert the variable coefficient a of a*x
|
||||||
|
if (index <= nConstraints - 2) {
|
||||||
|
// insert the the variable coefficient of the minimum widths constraints (negative identity matrix)
|
||||||
|
row[index] = -1;
|
||||||
|
} else {
|
||||||
|
// insert the the variable coefficient of the maximum width constraint
|
||||||
|
row.fill(1, 0, nVariables);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert the slack variable coefficient b of b*s (identity matrix)
|
||||||
|
row[index + nVariables] = 1;
|
||||||
|
|
||||||
|
// insert the constraint coefficient (b)
|
||||||
|
row[rowlength - 1] = maximumConstraint;
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
|
||||||
|
// add objective function in the last row
|
||||||
|
const row: number[] = Array(rowlength).fill(0);
|
||||||
|
|
||||||
|
// insert z coefficient
|
||||||
|
row[rowlength - 2] = 1;
|
||||||
|
|
||||||
|
// insert variable coefficients
|
||||||
|
row.fill(-1, 0, nVariables);
|
||||||
|
matrix.push(row);
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllIndexes(arr: number[], val: number): number[] {
|
||||||
|
const indexes = []; let i = -1;
|
||||||
|
while ((i = arr.indexOf(val, i + 1)) !== -1) {
|
||||||
|
indexes.push(i);
|
||||||
|
}
|
||||||
|
return indexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the main loop of the simplex algorithm and return the final matrix:
|
||||||
|
* - While the last row of the matrix has negative values :
|
||||||
|
* - 1) find the column with the smallest negative coefficient in the last row
|
||||||
|
* - 2) in that column, find the pivot by selecting the row with the smallest ratio
|
||||||
|
* such as ratio = constraint of last column / coefficient of the selected row of the selected column
|
||||||
|
* - 3) create the new matrix such as:
|
||||||
|
* - 4) the selected column must have 1 in the pivot and zeroes in the other rows
|
||||||
|
* - 5) in the selected rows other columns (other than the selected column)
|
||||||
|
* must be divided by that pivot: coef / pivot
|
||||||
|
* - 6) for the others cells, apply the pivot: new value = (-coefficient in the old col) * (coefficient in the new row) + old value
|
||||||
|
* - 7) if in the new matrix there are still negative values in the last row,
|
||||||
|
* redo the algorithm with the new matrix as the base matrix
|
||||||
|
* - 8) otherwise returns the basic variable such as
|
||||||
|
* a basic variable is defined by a single 1 and only zeroes in its column
|
||||||
|
* other variables are equal to zeroes
|
||||||
|
* @param oldMatrix
|
||||||
|
* @param rowlength
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function ApplyMainLoop(oldMatrix: number[][], rowlength: number): number[][] {
|
||||||
|
let matrix = oldMatrix;
|
||||||
|
let tries = MAX_TRIES;
|
||||||
|
const indexesTried: Record<number, number> = {};
|
||||||
|
while (matrix[matrix.length - 1].some((v: number) => v < 0) && tries > 0) {
|
||||||
|
// 1) find the index with smallest coefficient (O(n)+)
|
||||||
|
const lastRow = matrix[matrix.length - 1];
|
||||||
|
const min = Math.min(...lastRow);
|
||||||
|
const indexes = getAllIndexes(lastRow, min);
|
||||||
|
// to avoid infinite loop try to select the least used selected index
|
||||||
|
const pivotColIndex = getLeastUsedIndex(indexes, indexesTried);
|
||||||
|
// record the usage of index by incrementing
|
||||||
|
indexesTried[pivotColIndex] = indexesTried[pivotColIndex] !== undefined ? indexesTried[pivotColIndex] + 1 : 1;
|
||||||
|
|
||||||
|
// 2) find the smallest non negative non null ratio bi/xij (O(m))
|
||||||
|
const ratios = [];
|
||||||
|
for (let i = 0; i <= matrix.length - 2; i++) {
|
||||||
|
const coefficient = matrix[i][pivotColIndex];
|
||||||
|
const constraint = matrix[i][rowlength - 1];
|
||||||
|
if (coefficient === 0) {
|
||||||
|
ratios.push(Infinity);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const ratio = constraint / coefficient;
|
||||||
|
if (ratio < 0) {
|
||||||
|
ratios.push(Infinity);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ratios.push(ratio);
|
||||||
|
}
|
||||||
|
const minRatio = Math.min(...ratios);
|
||||||
|
const pivotRowIndex = ratios.indexOf(minRatio); // i
|
||||||
|
|
||||||
|
/// Init the new matrix
|
||||||
|
const newMatrix = structuredClone(matrix);
|
||||||
|
const pivot = matrix[pivotRowIndex][pivotColIndex];
|
||||||
|
|
||||||
|
// 3) apply on the pivot row the inverse of the pivot
|
||||||
|
const newPivotRow = newMatrix[pivotRowIndex];
|
||||||
|
newPivotRow.forEach((coef, colIndex) => {
|
||||||
|
newPivotRow[colIndex] = coef / pivot;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4) update all values
|
||||||
|
newMatrix.forEach((row, rowIndex) => {
|
||||||
|
if (rowIndex === pivotRowIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
row.forEach((coef, colIndex) => {
|
||||||
|
if (colIndex === pivotColIndex) {
|
||||||
|
// set zeroes on pivot col
|
||||||
|
row[colIndex] = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update value = old value + ((-old coef of pivot column) * (new coef of pivot row))
|
||||||
|
row[colIndex] = coef + (-matrix[rowIndex][pivotColIndex] * newMatrix[pivotRowIndex][colIndex]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
matrix = newMatrix;
|
||||||
|
tries--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tries === 0) {
|
||||||
|
throw new Error('[Flex]Simplexe: Could not find a solution');
|
||||||
|
}
|
||||||
|
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the solutions from the final matrix
|
||||||
|
*
|
||||||
|
* @param {number} nCols Number of solutions that you want to obtain
|
||||||
|
* @param {number[][]} finalMatrix Final matrix after the algorithm is applied
|
||||||
|
* @return {*} {number[]} A list of solutions of the final matrix
|
||||||
|
*/
|
||||||
|
function GetSolutions(nCols: number, finalMatrix: number[][]): number[] {
|
||||||
|
const solutions: number[] = Array(nCols).fill(0);
|
||||||
|
for (let i = 0; i < nCols; i++) {
|
||||||
|
const counts: Record<number, number> = {};
|
||||||
|
const col: number[] = [];
|
||||||
|
for (let j = 0; j < finalMatrix.length; j++) {
|
||||||
|
const row = finalMatrix[j];
|
||||||
|
counts[row[i]] = counts[row[i]] !== undefined ? counts[row[i]] + 1 : 1;
|
||||||
|
col.push(row[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// a basic variable has a single 1 and only zeroes in the column
|
||||||
|
const nRows = finalMatrix.length;
|
||||||
|
const isBasic = counts[1] === 1 && counts[0] === (nRows - 1);
|
||||||
|
if (isBasic) {
|
||||||
|
const oneIndex = col.indexOf(1);
|
||||||
|
const row = finalMatrix[oneIndex];
|
||||||
|
solutions[i] = row[row.length - 1];
|
||||||
|
} else {
|
||||||
|
solutions[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return solutions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the least used index from the indexesTried
|
||||||
|
* @param indexes Indexes of all occurences
|
||||||
|
* @param indexesTried Record of indexes. Count the number of times the index was used.
|
||||||
|
* @returns The least used index
|
||||||
|
*/
|
||||||
|
function getLeastUsedIndex(indexes: number[], indexesTried: Record<number, number>): number {
|
||||||
|
let minUsed = Infinity;
|
||||||
|
let minIndex = -1;
|
||||||
|
for (const index of indexes) {
|
||||||
|
const occ = indexesTried[index];
|
||||||
|
if (occ === undefined) {
|
||||||
|
minIndex = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (occ < minUsed) {
|
||||||
|
minIndex = index;
|
||||||
|
minUsed = occ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minIndex;
|
||||||
|
}
|
|
@ -1,5 +1,15 @@
|
||||||
import { XPositionReference } from '../Enums/XPositionReference';
|
import { XPositionReference } from '../Enums/XPositionReference';
|
||||||
|
|
||||||
|
// TODO: Big refactoring
|
||||||
|
/**
|
||||||
|
* TODO:
|
||||||
|
* All of these methods should have been
|
||||||
|
* inside ContainerModel class
|
||||||
|
* But because of serialization, the methods are lost.
|
||||||
|
* Rather than adding more functions to this class,
|
||||||
|
* 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, xPositionReference = XPositionReference.Left): number {
|
||||||
let transformedX = x;
|
let transformedX = x;
|
||||||
if (xPositionReference === XPositionReference.Center) {
|
if (xPositionReference === XPositionReference.Center) {
|
||||||
|
@ -19,3 +29,69 @@ export function restoreX(x: number, width: number, xPositionReference = XPositio
|
||||||
}
|
}
|
||||||
return transformedX;
|
return transformedX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ApplyMargin(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
left?: number,
|
||||||
|
bottom?: number,
|
||||||
|
top?: number,
|
||||||
|
right?: number
|
||||||
|
): { x: number, y: number, width: number, height: number } {
|
||||||
|
left = left ?? 0;
|
||||||
|
right = right ?? 0;
|
||||||
|
bottom = bottom ?? 0;
|
||||||
|
top = top ?? 0;
|
||||||
|
x = ApplyXMargin(x, left);
|
||||||
|
y += top;
|
||||||
|
width = ApplyWidthMargin(width, left, right);
|
||||||
|
height -= (bottom + top);
|
||||||
|
return { x, y, width, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoveMargin(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
left?: number,
|
||||||
|
bottom?: number,
|
||||||
|
top?: number,
|
||||||
|
right?: number
|
||||||
|
): { x: number, y: number, width: number, height: number } {
|
||||||
|
bottom = bottom ?? 0;
|
||||||
|
top = top ?? 0;
|
||||||
|
x = RemoveXMargin(x, left);
|
||||||
|
y -= top;
|
||||||
|
width = RemoveWidthMargin(width, left, right);
|
||||||
|
height += (bottom + top);
|
||||||
|
return { x, y, width, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ApplyXMargin(x: number, left?: number): number {
|
||||||
|
left = left ?? 0;
|
||||||
|
x += left;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoveXMargin(x: number, left?: number): number {
|
||||||
|
left = left ?? 0;
|
||||||
|
x -= left;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ApplyWidthMargin(width: number, left?: number, right?: number): number {
|
||||||
|
left = left ?? 0;
|
||||||
|
right = right ?? 0;
|
||||||
|
width -= (left + right);
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoveWidthMargin(width: number, left?: number, right?: number): number {
|
||||||
|
left = left ?? 0;
|
||||||
|
right = right ?? 0;
|
||||||
|
width += (left + right);
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
AvailableContainers: [
|
AvailableContainers: [
|
||||||
{
|
{
|
||||||
Type: 'Chassis',
|
Type: 'Chassis',
|
||||||
Width: 500,
|
MaxWidth: 500,
|
||||||
MinWidth: 200,
|
MinWidth: 200,
|
||||||
DefaultChildType: 'Trou',
|
DefaultChildType: 'Trou',
|
||||||
Style: {
|
Style: {
|
||||||
|
@ -65,10 +65,14 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: 'Trou',
|
Type: 'Trou',
|
||||||
DefaultX: 10,
|
DefaultX: 0,
|
||||||
DefaultY: 10,
|
DefaultY: 0,
|
||||||
Width: 480,
|
Margin: {
|
||||||
Height: 180,
|
left: 10,
|
||||||
|
bottom: 10,
|
||||||
|
top: 10,
|
||||||
|
right: 10,
|
||||||
|
},
|
||||||
DefaultChildType: 'Remplissage',
|
DefaultChildType: 'Remplissage',
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 1,
|
fillOpacity: 1,
|
||||||
|
@ -108,6 +112,28 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
stroke: '#713f12',
|
stroke: '#713f12',
|
||||||
fill: '#713f12',
|
fill: '#713f12',
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: '200',
|
||||||
|
MaxWidth: 500,
|
||||||
|
MinWidth: 200,
|
||||||
|
Style: {
|
||||||
|
fillOpacity: 1,
|
||||||
|
strokeWidth: 2,
|
||||||
|
stroke: 'blue',
|
||||||
|
fill: 'blue',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: '400',
|
||||||
|
MaxWidth: 500,
|
||||||
|
MinWidth: 400,
|
||||||
|
Style: {
|
||||||
|
fillOpacity: 1,
|
||||||
|
strokeWidth: 2,
|
||||||
|
stroke: 'red',
|
||||||
|
fill: 'red',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
AvailableSymbols: [
|
AvailableSymbols: [
|
||||||
|
@ -138,7 +164,7 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
],
|
],
|
||||||
MainContainer: {
|
MainContainer: {
|
||||||
Height: 200,
|
Height: 200,
|
||||||
Width: 1000
|
Width: 800
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -53,182 +53,62 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
return {
|
return {
|
||||||
AvailableContainers: [
|
AvailableContainers: [
|
||||||
{
|
{
|
||||||
BodyColor: null,
|
|
||||||
BorderColor: '#ff0000',
|
|
||||||
BorderWidth: 48,
|
|
||||||
ContainerActions: null,
|
|
||||||
ContainerDimensionning: null,
|
|
||||||
DefaultChildrenContainers: null,
|
|
||||||
Height: 0,
|
|
||||||
IsPositionFixed: false,
|
|
||||||
IsWidthFixed: false,
|
|
||||||
MaxHeight: 0,
|
|
||||||
MaxWidth: 3000,
|
|
||||||
MinHeight: 0,
|
|
||||||
MinWidth: 500,
|
|
||||||
Type: 'Chassis',
|
Type: 'Chassis',
|
||||||
TypeChildContainerDefault: 'Trou',
|
|
||||||
Width: 500,
|
Width: 500,
|
||||||
XPositionReference: 0,
|
MinWidth: 200,
|
||||||
|
DefaultChildType: 'Trou',
|
||||||
|
Style: {
|
||||||
|
fillOpacity: 1,
|
||||||
|
strokeWidth: 2,
|
||||||
|
stroke: 'red',
|
||||||
|
fill: '#d3c9b7',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: 'Trou',
|
||||||
|
DefaultX: 10,
|
||||||
|
DefaultY: 10,
|
||||||
|
Width: 480,
|
||||||
|
Height: 180,
|
||||||
|
DefaultChildType: 'Remplissage',
|
||||||
|
Style: {
|
||||||
|
fillOpacity: 1,
|
||||||
|
strokeWidth: 2,
|
||||||
|
stroke: 'green',
|
||||||
|
fill: 'white'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: 'Remplissage',
|
||||||
|
CustomSVG: `
|
||||||
|
<rect width="{width}" height="{height}" style="{style}"></rect>
|
||||||
|
<rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
|
||||||
|
<line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
|
||||||
|
<line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
||||||
|
`
|
||||||
|
,
|
||||||
|
Style: {
|
||||||
|
fillOpacity: 1,
|
||||||
|
strokeWidth: 1,
|
||||||
|
fill: '#bfdbfe'
|
||||||
|
},
|
||||||
|
UserData: {
|
||||||
|
styleLine: {
|
||||||
|
transform: "scaleY(0.5) translateY(100%)",
|
||||||
|
transformBox: "fill-box"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: 'Montant',
|
||||||
|
Width: 10,
|
||||||
|
XPositionReference: 1,
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 0,
|
fillOpacity: 0,
|
||||||
borderWidth: 2,
|
strokeWidth: 2,
|
||||||
stroke: 'red'
|
stroke: '#713f12',
|
||||||
|
fill: '#713f12',
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
BodyColor: null,
|
|
||||||
BorderColor: '#FFFFFF',
|
|
||||||
BorderWidth: 0,
|
|
||||||
ContainerActions: null,
|
|
||||||
ContainerDimensionning: null,
|
|
||||||
DefaultChildrenContainers: null,
|
|
||||||
Height: 0,
|
|
||||||
IsPositionFixed: false,
|
|
||||||
IsWidthFixed: false,
|
|
||||||
MaxHeight: 0,
|
|
||||||
MaxWidth: 0,
|
|
||||||
MinHeight: 0,
|
|
||||||
MinWidth: 0,
|
|
||||||
Type: 'Trou',
|
|
||||||
TypeChildContainerDefault: 'Remplissage',
|
|
||||||
Width: 0,
|
|
||||||
XPositionReference: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BodyColor: '#99C8FF',
|
|
||||||
BorderColor: '#00FF00',
|
|
||||||
BorderWidth: 0,
|
|
||||||
ContainerActions: [
|
|
||||||
{
|
|
||||||
Action: 'SplitRemplissage',
|
|
||||||
AddingBehavior: 0,
|
|
||||||
CustomLogo: {
|
|
||||||
Base64Image: null,
|
|
||||||
Name: null,
|
|
||||||
Svg: null,
|
|
||||||
Url: ''
|
|
||||||
},
|
|
||||||
Description: 'Diviser le remplissage en insérant un montant',
|
|
||||||
Id: null,
|
|
||||||
Label: 'Diviser le remplissage'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
ContainerDimensionning: {
|
|
||||||
DimensionningStyle: 1,
|
|
||||||
ShowDimensionning: false,
|
|
||||||
ShowLabel: false
|
|
||||||
},
|
|
||||||
DefaultChildrenContainers: null,
|
|
||||||
Height: 0,
|
|
||||||
IsPositionFixed: false,
|
|
||||||
IsWidthFixed: false,
|
|
||||||
MaxHeight: 0,
|
|
||||||
MaxWidth: 0,
|
|
||||||
MinHeight: 0,
|
|
||||||
MinWidth: 0,
|
|
||||||
Type: 'Remplissage',
|
|
||||||
TypeChildContainerDefault: null,
|
|
||||||
Width: 0,
|
|
||||||
XPositionReference: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BodyColor: '#FFA947',
|
|
||||||
BorderColor: '#FFA947',
|
|
||||||
BorderWidth: 0,
|
|
||||||
ContainerActions: null,
|
|
||||||
ContainerDimensionning: null,
|
|
||||||
DefaultChildrenContainers: null,
|
|
||||||
Height: 0,
|
|
||||||
IsPositionFixed: false,
|
|
||||||
IsWidthFixed: false,
|
|
||||||
MaxHeight: 0,
|
|
||||||
MaxWidth: 0,
|
|
||||||
MinHeight: 0,
|
|
||||||
MinWidth: 0,
|
|
||||||
Type: 'Montant',
|
|
||||||
TypeChildContainerDefault: null,
|
|
||||||
Width: 50,
|
|
||||||
XPositionReference: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BodyColor: '#FFA3D1',
|
|
||||||
BorderColor: '#FF6DE6',
|
|
||||||
BorderWidth: 0,
|
|
||||||
ContainerActions: null,
|
|
||||||
ContainerDimensionning: {
|
|
||||||
DimensionningStyle: 0,
|
|
||||||
ShowDimensionning: false,
|
|
||||||
ShowLabel: false
|
|
||||||
},
|
|
||||||
DefaultChildrenContainers: null,
|
|
||||||
Height: 0,
|
|
||||||
IsPositionFixed: false,
|
|
||||||
IsWidthFixed: false,
|
|
||||||
MaxHeight: 0,
|
|
||||||
MaxWidth: 0,
|
|
||||||
MinHeight: 0,
|
|
||||||
MinWidth: 0,
|
|
||||||
Type: 'Ouverture',
|
|
||||||
TypeChildContainerDefault: null,
|
|
||||||
Width: 0,
|
|
||||||
XPositionReference: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BodyColor: '#000000',
|
|
||||||
BorderColor: null,
|
|
||||||
BorderWidth: 0,
|
|
||||||
ContainerActions: null,
|
|
||||||
ContainerDimensionning: {
|
|
||||||
DimensionningStyle: 0,
|
|
||||||
ShowDimensionning: false,
|
|
||||||
ShowLabel: false
|
|
||||||
},
|
|
||||||
DefaultChildrenContainers: null,
|
|
||||||
Height: 0,
|
|
||||||
IsPositionFixed: false,
|
|
||||||
IsWidthFixed: false,
|
|
||||||
MaxHeight: 0,
|
|
||||||
MaxWidth: 0,
|
|
||||||
MinHeight: 0,
|
|
||||||
MinWidth: 0,
|
|
||||||
Type: 'Dilatation',
|
|
||||||
TypeChildContainerDefault: null,
|
|
||||||
Width: 8,
|
|
||||||
XPositionReference: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
BodyColor: '#dee2e4',
|
|
||||||
BorderColor: '#54616c',
|
|
||||||
BorderWidth: 0,
|
|
||||||
ContainerActions: [
|
|
||||||
{
|
|
||||||
Action: 'FillHoleWithChassis',
|
|
||||||
AddingBehavior: 1,
|
|
||||||
CustomLogo: {
|
|
||||||
Base64Image: null,
|
|
||||||
Name: null,
|
|
||||||
Svg: null,
|
|
||||||
Url: ''
|
|
||||||
},
|
|
||||||
Description: 'Remplir le trou avec des châssis',
|
|
||||||
Id: null,
|
|
||||||
Label: 'Calepiner'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
ContainerDimensionning: null,
|
|
||||||
DefaultChildrenContainers: null,
|
|
||||||
Height: 0,
|
|
||||||
IsPositionFixed: false,
|
|
||||||
IsWidthFixed: false,
|
|
||||||
MaxHeight: 0,
|
|
||||||
MaxWidth: 0,
|
|
||||||
MinHeight: 0,
|
|
||||||
MinWidth: 0,
|
|
||||||
Type: '',
|
|
||||||
TypeChildContainerDefault: null,
|
|
||||||
Width: 0,
|
|
||||||
XPositionReference: 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
AvailableSymbols: [
|
AvailableSymbols: [
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue