This commit is contained in:
commit
3221d97eff
39 changed files with 30948 additions and 795 deletions
|
@ -7,7 +7,7 @@ steps:
|
|||
image: node:16
|
||||
commands:
|
||||
- node ./test-server/node-http.js &
|
||||
- npm ci
|
||||
- npm i
|
||||
- npm run test:nowatch
|
||||
- npm run build
|
||||
|
||||
|
@ -20,6 +20,6 @@ steps:
|
|||
image: node
|
||||
commands:
|
||||
- node ./test-server/node-http.js &
|
||||
- npm ci
|
||||
- npm i
|
||||
- npm run test:nowatch
|
||||
- npm run build
|
|
@ -31,6 +31,8 @@ module.exports = {
|
|||
'@typescript-eslint/semi': ['warn', 'always'],
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'@typescript-eslint/ban-types': ['error'],
|
||||
'@typescript-eslint/no-floating-promises': 'off', // disabled cuz troublesome for SweetAlert since they never reject
|
||||
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
|
||||
'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ It depends on Vite in order to build the project.
|
|||
|
||||
Others dependencies:
|
||||
- [react-dom](https://reactjs.org/docs/react-dom.html): library used to inject the app to `#root` html element.
|
||||
- [react-window](https://www.npmjs.com/package/react-windows): component that offers component dynamic loading over scroll (very useful++)
|
||||
- [react-svg-pan-zoom](https://www.npmjs.com/package/react-svg-pan-zoom): component that offers pan + zoom to a svg element
|
||||
- [react-window](https://www.npmjs.com/package/react-window): component that offers component dynamic loading over scroll (very useful++)
|
||||
- [react-svg-pan-zoom](https://www.npmjs.com/package/react-svg-pan-zoom): component that offers pan + zoom to a svg element (if this gets deprecated, please try to migrate to HTML Canvas before trying a new library)
|
||||
|
||||
|
||||
# [Vite](https://vitejs.dev/)
|
||||
|
@ -49,6 +49,12 @@ Other dependencies:
|
|||
|
||||
SVG Icons that can be used as JSX elements with Tailwind CSS
|
||||
|
||||
# [Interweave](https://interweave.dev/)
|
||||
|
||||
React library to render HTML from string.
|
||||
In this project, it is particularly used for the CustomSVG property.
|
||||
|
||||
If this dependencies gets deprecated please revert [PR#18 e96e4f12](https://dev.azure.com/enguyen0660/SVGLayoutDesignerReact/_git/SVGLayoutDesignerReact/commit/e96e4f123b4aa4c9cdb327d4d617ab0e63dc4d0f?refName=refs%2Fheads%2Fdev)
|
||||
|
||||
# Testing
|
||||
|
||||
|
|
14
docs/Hardcoded.md
Normal file
14
docs/Hardcoded.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
> Here you will find the documentation of desastrous stuff that I made
|
||||
|
||||
# XPositionReference
|
||||
|
||||
XPositionReference is used as a fake horizontal offset indicator.
|
||||
|
||||
The truth is that the svg will always take the left for its transformations and the best for us is to do the same.
|
||||
|
||||
That's why everything that is shown to the user about XPositionReference is an illusion. Like for example:
|
||||
- The inputs, see `PropertiesOperations.ts`, `StaticForm`, `DynamicForm`.
|
||||
- Child dimensions, see `Container.ts`.
|
||||
|
||||
|
||||
Look for use of `transformX()` and `restoreX()`.
|
|
@ -6,7 +6,8 @@ The project is structured this way
|
|||
.
|
||||
├── docs/ Documentation folder
|
||||
├── public/ Public folder in which the index.html
|
||||
│ import its resources
|
||||
│ │ import its resources
|
||||
│ └── workers/ Webworkers folder
|
||||
├── src/ Source folder for the react app
|
||||
│ ├── assets/ Assets folder in which the react app
|
||||
│ │ import its resources
|
||||
|
@ -17,7 +18,6 @@ The project is structured this way
|
|||
│ ├── test/ Setup folder for the tests
|
||||
│ ├── tests/ Other tests + resources
|
||||
│ ├── utils/ Utilities folder
|
||||
│ ├── workers/ Webworkers folder
|
||||
│ ├── index.scss Tailwind CSS extends
|
||||
│ ├── main.tsx Entrypoint for App injection
|
||||
│ └── vite-env.d.ts Types for .env files
|
||||
|
|
|
@ -14,10 +14,13 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"interweave": "^13.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-svg-pan-zoom": "^3.11.0",
|
||||
"react-window": "^1.8.7"
|
||||
"react-window": "^1.8.7",
|
||||
"sweetalert2": "^11.4.28",
|
||||
"sweetalert2-react-content": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "^8.16.1",
|
||||
|
|
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
|
@ -24,6 +24,7 @@ specifiers:
|
|||
eslint-plugin-promise: ^6.0.0
|
||||
eslint-plugin-react: ^7.30.1
|
||||
eslint-plugin-react-hooks: ^4.6.0
|
||||
interweave: ^13.0.0
|
||||
jsdom: ^20.0.0
|
||||
postcss: ^8.4.14
|
||||
react: ^18.2.0
|
||||
|
@ -31,6 +32,8 @@ specifiers:
|
|||
react-svg-pan-zoom: ^3.11.0
|
||||
react-window: ^1.8.7
|
||||
sass: ^1.54.0
|
||||
sweetalert2: ^11.4.28
|
||||
sweetalert2-react-content: ^5.0.3
|
||||
tailwindcss: ^3.1.7
|
||||
typescript: ^4.6.4
|
||||
vite: ^3.0.0
|
||||
|
@ -38,10 +41,13 @@ specifiers:
|
|||
|
||||
dependencies:
|
||||
'@heroicons/react': 1.0.6_react@18.2.0
|
||||
interweave: 13.0.0_react@18.2.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-svg-pan-zoom: 3.11.0_react@18.2.0
|
||||
react-window: 1.8.7_biqbaboplfbrettd7655fr4n2y
|
||||
sweetalert2: 11.4.28
|
||||
sweetalert2-react-content: 5.0.3_m2nzudmh75bb5iwknw4em6omxi
|
||||
|
||||
devDependencies:
|
||||
'@testing-library/dom': 8.16.1
|
||||
|
@ -1522,6 +1528,10 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/escape-html/1.0.3:
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
dev: false
|
||||
|
||||
/escape-string-regexp/1.0.5:
|
||||
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
@ -2177,6 +2187,15 @@ packages:
|
|||
side-channel: 1.0.4
|
||||
dev: true
|
||||
|
||||
/interweave/13.0.0_react@18.2.0:
|
||||
resolution: {integrity: sha512-Mckwj+ix/VtrZu1bRBIIohwrsXj12ZTvJCoYUMZlJmgtvIaQCj0i77eSZ63ckbA1TsPrz2VOvLW9/kTgm5d+mw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
escape-html: 1.0.3
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/is-bigint/1.0.4:
|
||||
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
|
||||
dependencies:
|
||||
|
@ -3183,6 +3202,22 @@ packages:
|
|||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/sweetalert2-react-content/5.0.3_m2nzudmh75bb5iwknw4em6omxi:
|
||||
resolution: {integrity: sha512-DQXblZn0LHTvmaZquNQncZIE3Ljox85sAKKbXjYlDyFejyOibHwprAVvtQQpAUG3bgvyDUeAOE/BDFcVx6KUow==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
sweetalert2: ^11.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
sweetalert2: 11.4.28
|
||||
dev: false
|
||||
|
||||
/sweetalert2/11.4.28:
|
||||
resolution: {integrity: sha512-leCf8Kc/o+R0LNWmLjWXI7l0roMchEHg6X+XibmfTYaOMvOoHXmoxmegHl0it+8vvvZlPIjzyfM6bYBOKTFnRg==}
|
||||
dev: false
|
||||
|
||||
/symbol-tree/3.2.4:
|
||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||
dev: true
|
||||
|
|
|
@ -5,6 +5,7 @@ import { fetchConfiguration } from '../API/api';
|
|||
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||
import { LoadState } from './Load';
|
||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||
import { DEFAULT_MAINCONTAINER_PROPS } from '../../utils/default';
|
||||
|
||||
export function NewEditor(
|
||||
setEditorState: Dispatch<SetStateAction<IEditorState>>,
|
||||
|
@ -17,19 +18,9 @@ export function NewEditor(
|
|||
const MainContainer = new ContainerModel(
|
||||
null,
|
||||
{
|
||||
id: 'main',
|
||||
parentId: 'null',
|
||||
x: 0,
|
||||
y: 0,
|
||||
...DEFAULT_MAINCONTAINER_PROPS,
|
||||
width: Number(configuration.MainContainer.Width),
|
||||
height: Number(configuration.MainContainer.Height),
|
||||
isRigidBody: false,
|
||||
isAnchor: false,
|
||||
XPositionReference: XPositionReference.Left,
|
||||
style: {
|
||||
fillOpacity: 0,
|
||||
stroke: 'black'
|
||||
}
|
||||
height: Number(configuration.MainContainer.Height)
|
||||
}
|
||||
);
|
||||
|
||||
|
|
21
src/Components/Editor/Behaviors/Behaviors.ts
Normal file
21
src/Components/Editor/Behaviors/Behaviors.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { ImposePosition } from './AnchorBehaviors';
|
||||
import { RecalculatePhysics } from './RigidBodyBehaviors';
|
||||
|
||||
/**
|
||||
* Recalculate the position of the container and its neighbors
|
||||
* Mutate and returns the updated container
|
||||
* @param container Container to recalculate its positions
|
||||
* @returns Updated container
|
||||
*/
|
||||
export function ApplyBehaviors(container: IContainerModel): IContainerModel {
|
||||
if (container.properties.isAnchor) {
|
||||
ImposePosition(container);
|
||||
}
|
||||
|
||||
if (container.properties.isRigidBody) {
|
||||
RecalculatePhysics(container);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
* If the contraints fails, an error message will be returned
|
||||
*/
|
||||
|
||||
import Swal from 'sweetalert2';
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
||||
|
||||
|
@ -126,8 +127,13 @@ export function constraintBodyInsideUnallocatedWidth(
|
|||
|
||||
// Check if there is still some space
|
||||
if (availableWidths.length === 0) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Not enough space!',
|
||||
text: 'Try to free the parent a little bit!'
|
||||
});
|
||||
throw new Error(
|
||||
'No available space found on the parent container. Try to free the parent a little before placing it inside.'
|
||||
'No available space found on the parent container. Try to free the parent a bit.'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -152,7 +158,7 @@ export function constraintBodyInsideUnallocatedWidth(
|
|||
// Check if the container actually fit inside
|
||||
// It will usually fit if it was alrady fitting
|
||||
const availableWidthFound = availableWidths.find((width) =>
|
||||
isFitting(container, width)
|
||||
isFitting(container.properties.width, width)
|
||||
);
|
||||
|
||||
if (availableWidthFound === undefined) {
|
||||
|
@ -163,12 +169,26 @@ export function constraintBodyInsideUnallocatedWidth(
|
|||
|
||||
// We want the container to fit automatically inside the available space
|
||||
// even if it means to resize the container
|
||||
// The end goal is that the code never show the error message no matter what action is done
|
||||
// TODO: Actually give an option to not fit and show the error message shown below
|
||||
const availableWidth = availableWidths[0];
|
||||
const availableWidth: ISizePointer | undefined = availableWidths.find((width) => {
|
||||
return isFitting(container.properties.minWidth, width);
|
||||
});
|
||||
|
||||
if (availableWidth === undefined) {
|
||||
console.warn(`Container ${container.properties.id} cannot fit in any space due to its minimum width being to large. Consequently, its rigid body property is disabled.`);
|
||||
Swal.fire({
|
||||
position: 'top-end',
|
||||
title: `Container ${container.properties.id} cannot fit!`,
|
||||
text: 'Its rigid body property is now disabled. Change its the minimum width or free the parent container.',
|
||||
timerProgressBar: true,
|
||||
showConfirmButton: false,
|
||||
timer: 5000
|
||||
});
|
||||
container.properties.isRigidBody = false;
|
||||
return container;
|
||||
}
|
||||
|
||||
container.properties.x = availableWidth.x;
|
||||
container.properties.width = availableWidth.width;
|
||||
// throw new Error('[constraintBodyInsideUnallocatedWidth] BIGERR: No available space found on the parent container, even though there is some.');
|
||||
return container;
|
||||
}
|
||||
|
||||
|
@ -181,6 +201,17 @@ export function constraintBodyInsideUnallocatedWidth(
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a container can fit inside a size space
|
||||
* @param container Container to check
|
||||
* @param sizePointer Size space to check
|
||||
* @returns
|
||||
*/
|
||||
const isFitting = (
|
||||
containerWidth: number,
|
||||
sizePointer: ISizePointer
|
||||
): boolean => containerWidth <= sizePointer.width;
|
||||
|
||||
/**
|
||||
* Get the unallocated widths inside a container
|
||||
* An allocated width is defined by its the widths of the children that are rigid bodies.
|
||||
|
@ -222,10 +253,9 @@ function getAvailableWidths(
|
|||
// We need to calculate the overlap between the two containers
|
||||
// We only works with widths meaning in 1D (with lines)
|
||||
const newUnallocatedWidths = getAvailableWidthsTwoLines(
|
||||
unallocatedSpace.x,
|
||||
unallocatedSpace.x + unallocatedSpace.width,
|
||||
unallocatedSpace,
|
||||
childX,
|
||||
childX + childWidth
|
||||
childWidth
|
||||
);
|
||||
|
||||
// Concat the new list of SizePointer pointing to availables spaces
|
||||
|
@ -240,59 +270,49 @@ function getAvailableWidths(
|
|||
|
||||
/**
|
||||
* Returns the unallocated widths between two lines in 1D
|
||||
* @param unalloctedSpaceLeft left of the first line
|
||||
* @param unallocatedSpaceRight rigth of the first line
|
||||
* @param rectLeft left of the second line
|
||||
* @param rectRight right of the second line
|
||||
* @param unalloctedSpace unallocated space
|
||||
* @param rectX left of the second line
|
||||
* @param rectWidth width of the second line
|
||||
* @returns Available widths
|
||||
*/
|
||||
function getAvailableWidthsTwoLines(
|
||||
unalloctedSpaceLeft: number,
|
||||
unallocatedSpaceRight: number,
|
||||
rectLeft: number,
|
||||
rectRight: number
|
||||
unallocatedSpace: ISizePointer,
|
||||
rectX: number,
|
||||
rectWidth: number
|
||||
): ISizePointer[] {
|
||||
if (unallocatedSpaceRight < rectLeft ||
|
||||
unalloctedSpaceLeft > rectRight
|
||||
) {
|
||||
// object 1 and 2 are not overlapping
|
||||
return [{
|
||||
x: unalloctedSpaceLeft,
|
||||
width: unallocatedSpaceRight - unalloctedSpaceLeft
|
||||
}];
|
||||
const unallocatedSpaceRight = unallocatedSpace.x + unallocatedSpace.width;
|
||||
const rectRight = rectX + rectWidth;
|
||||
|
||||
const isNotOverlapping = unallocatedSpaceRight < rectX ||
|
||||
unallocatedSpace.x > rectRight;
|
||||
if (isNotOverlapping) {
|
||||
return [unallocatedSpace];
|
||||
}
|
||||
|
||||
if (rectLeft < unalloctedSpaceLeft && rectRight > unallocatedSpaceRight) {
|
||||
// object 2 is overlapping full width
|
||||
const isOverlappingFullWidth = rectX < unallocatedSpace.x && rectRight > unallocatedSpaceRight;
|
||||
if (isOverlappingFullWidth) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (unalloctedSpaceLeft >= rectLeft) {
|
||||
// object 2 is partially overlapping on the left
|
||||
return [
|
||||
{
|
||||
x: rectRight,
|
||||
width: unallocatedSpaceRight - rectRight
|
||||
}
|
||||
];
|
||||
const isOverlappingOnTheLeft = unallocatedSpace.x >= rectX;
|
||||
if (isOverlappingOnTheLeft) {
|
||||
return getAvailableWidthsLeft(unallocatedSpaceRight, rectRight);
|
||||
}
|
||||
|
||||
if (rectRight >= unallocatedSpaceRight) {
|
||||
// object 2 is partially overlapping on the right
|
||||
return [
|
||||
{
|
||||
x: unalloctedSpaceLeft,
|
||||
width: rectRight - unalloctedSpaceLeft
|
||||
}
|
||||
];
|
||||
const isOverlappingOnTheRight = rectRight >= unallocatedSpaceRight;
|
||||
if (isOverlappingOnTheRight) {
|
||||
return getAvailableWidthsRight(unallocatedSpace.x, rectX);
|
||||
}
|
||||
|
||||
return getAvailableWidthsMiddle(unallocatedSpace.x, unallocatedSpaceRight, rectX, rectRight);
|
||||
}
|
||||
|
||||
function getAvailableWidthsLeft(unallocatedSpaceRight: number, rectRight: number): ISizePointer[] {
|
||||
if (unallocatedSpaceRight - rectRight <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// object 2 is overlapping in the middle
|
||||
return [
|
||||
{
|
||||
x: unalloctedSpaceLeft,
|
||||
width: rectLeft - unalloctedSpaceLeft
|
||||
},
|
||||
{
|
||||
x: rectRight,
|
||||
width: unallocatedSpaceRight - rectRight
|
||||
|
@ -300,13 +320,40 @@ function getAvailableWidthsTwoLines(
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a container can fit inside a size space
|
||||
* @param container Container to check
|
||||
* @param sizePointer Size space to check
|
||||
* @returns
|
||||
*/
|
||||
const isFitting = (
|
||||
container: IContainerModel,
|
||||
sizePointer: ISizePointer
|
||||
): boolean => container.properties.width <= sizePointer.width;
|
||||
function getAvailableWidthsRight(unallocatedSpaceX: number, rectX: number): ISizePointer[] {
|
||||
if (rectX - unallocatedSpaceX <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
x: unallocatedSpaceX,
|
||||
width: rectX - unallocatedSpaceX
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function getAvailableWidthsMiddle(
|
||||
unallocatedSpaceX: number,
|
||||
unallocatedSpaceRight: number,
|
||||
rectX: number,
|
||||
rectRight: number
|
||||
): ISizePointer[] {
|
||||
const sizePointers: ISizePointer[] = [];
|
||||
|
||||
if (rectX - unallocatedSpaceX > 0) {
|
||||
sizePointers.push({
|
||||
x: unallocatedSpaceX,
|
||||
width: rectX - unallocatedSpaceX
|
||||
});
|
||||
}
|
||||
|
||||
if (unallocatedSpaceRight - rectRight > 0) {
|
||||
sizePointers.push({
|
||||
x: rectRight,
|
||||
width: unallocatedSpaceRight - rectRight
|
||||
});
|
||||
}
|
||||
|
||||
return sizePointers;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
|
|||
import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { findContainerById } from '../../utils/itertools';
|
||||
import { getCurrentHistory } from './Editor';
|
||||
import IProperties from '../../Interfaces/IProperties';
|
||||
import { AddMethod } from '../../Enums/AddMethod';
|
||||
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTYPE_MAX_DEPTH } from '../../utils/default';
|
||||
import { ApplyBehaviors } from './Behaviors/Behaviors';
|
||||
|
||||
/**
|
||||
* Select a container
|
||||
|
@ -181,12 +181,7 @@ export function AddContainer(
|
|||
|
||||
// Set the counter of the object type in order to assign an unique id
|
||||
const newCounters = Object.assign({}, current.TypeCounters);
|
||||
if (newCounters[type] === null ||
|
||||
newCounters[type] === undefined) {
|
||||
newCounters[type] = 0;
|
||||
} else {
|
||||
newCounters[type]++;
|
||||
}
|
||||
UpdateCounters(newCounters, type);
|
||||
const count = newCounters[type];
|
||||
|
||||
// Create maincontainer model
|
||||
|
@ -203,23 +198,17 @@ export function AddContainer(
|
|||
|
||||
let x = containerConfig.DefaultX ?? 0;
|
||||
const y = containerConfig.DefaultY ?? 0;
|
||||
const width = containerConfig.Width ?? parentClone.properties.width;
|
||||
const height = containerConfig.Height ?? parentClone.properties.height;
|
||||
|
||||
x = ApplyAddMethod(index, containerConfig, parentClone, x);
|
||||
|
||||
const defaultProperties: IProperties = {
|
||||
id: `${type}-${count}`,
|
||||
parentId: parentClone.properties.id,
|
||||
const defaultProperties = GetDefaultContainerProps(
|
||||
type,
|
||||
count,
|
||||
parentClone,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
isRigidBody: false,
|
||||
isAnchor: false,
|
||||
XPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
|
||||
style: containerConfig.Style
|
||||
};
|
||||
containerConfig
|
||||
);
|
||||
|
||||
// Create the container
|
||||
const newContainer = new ContainerModel(
|
||||
|
@ -231,6 +220,8 @@ export function AddContainer(
|
|||
}
|
||||
);
|
||||
|
||||
ApplyBehaviors(newContainer);
|
||||
|
||||
// And push it the the parent children
|
||||
if (index === parentClone.children.length) {
|
||||
parentClone.children.push(newContainer);
|
||||
|
@ -238,9 +229,11 @@ export function AddContainer(
|
|||
parentClone.children.splice(index, 0, newContainer);
|
||||
}
|
||||
|
||||
InitializeDefaultChild(configuration, containerConfig, newContainer, newCounters);
|
||||
|
||||
// Update the state
|
||||
history.push({
|
||||
LastAction: 'Add container',
|
||||
LastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
|
||||
MainContainer: clone,
|
||||
SelectedContainer: parentClone,
|
||||
SelectedContainerId: parentClone.properties.id,
|
||||
|
@ -250,6 +243,74 @@ export function AddContainer(
|
|||
setHistoryCurrentStep(history.length - 1);
|
||||
}
|
||||
|
||||
function UpdateCounters(counters: Record<string, number>, type: string): void {
|
||||
if (counters[type] === null ||
|
||||
counters[type] === undefined) {
|
||||
counters[type] = 0;
|
||||
} else {
|
||||
counters[type]++;
|
||||
}
|
||||
}
|
||||
|
||||
function InitializeDefaultChild(
|
||||
configuration: IConfiguration,
|
||||
containerConfig: IAvailableContainer,
|
||||
newContainer: ContainerModel,
|
||||
newCounters: Record<string, number>
|
||||
): void {
|
||||
if (containerConfig.DefaultChildType === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentConfig = configuration.AvailableContainers
|
||||
.find(option => option.Type === containerConfig.DefaultChildType);
|
||||
let parent = newContainer;
|
||||
let depth = 0;
|
||||
const seen = new Set<string>([containerConfig.Type]);
|
||||
|
||||
while (currentConfig !== undefined &&
|
||||
depth <= DEFAULTCHILDTYPE_MAX_DEPTH
|
||||
) {
|
||||
if (!DEFAULTCHILDTYPE_ALLOW_CYCLIC && seen.has(currentConfig.Type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
seen.add(currentConfig.Type);
|
||||
const x = currentConfig.DefaultX ?? 0;
|
||||
const y = currentConfig.DefaultY ?? 0;
|
||||
|
||||
UpdateCounters(newCounters, currentConfig.Type);
|
||||
const count = newCounters[currentConfig.Type];
|
||||
const defaultChildProperties = GetDefaultContainerProps(
|
||||
currentConfig.Type,
|
||||
count,
|
||||
parent,
|
||||
x,
|
||||
y,
|
||||
currentConfig
|
||||
);
|
||||
|
||||
// Create the container
|
||||
const newChildContainer = new ContainerModel(
|
||||
parent,
|
||||
defaultChildProperties,
|
||||
[],
|
||||
{
|
||||
type: currentConfig.Type
|
||||
}
|
||||
);
|
||||
|
||||
// And push it the the parent children
|
||||
parent.children.push(newChildContainer);
|
||||
|
||||
// iterate
|
||||
depth++;
|
||||
parent = newChildContainer;
|
||||
currentConfig = configuration.AvailableContainers
|
||||
.find(option => option.Type === (currentConfig as IAvailableContainer).DefaultChildType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new offset by applying an Add method (append, insert etc.)
|
||||
* See AddMethod
|
||||
|
@ -263,6 +324,7 @@ function ApplyAddMethod(index: number, containerConfig: IAvailableContainer, par
|
|||
if (index > 0 && (
|
||||
containerConfig.AddMethod === undefined ||
|
||||
containerConfig.AddMethod === AddMethod.Append)) {
|
||||
// Append method (default)
|
||||
const lastChild: IContainerModel | undefined = parent.children.at(index - 1);
|
||||
|
||||
if (lastChild !== undefined) {
|
||||
|
|
|
@ -3,10 +3,8 @@ import { IContainerModel, ContainerModel } from '../../Interfaces/IContainerMode
|
|||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||
import { findContainerById } from '../../utils/itertools';
|
||||
import { getCurrentHistory } from './Editor';
|
||||
import { constraintBodyInsideUnallocatedWidth, RecalculatePhysics } from './Behaviors/RigidBodyBehaviors';
|
||||
import { INPUT_TYPES } from '../Properties/PropertiesInputTypes';
|
||||
import { ImposePosition } from './Behaviors/AnchorBehaviors';
|
||||
import { restoreX } from '../SVG/Elements/Container';
|
||||
import { ApplyBehaviors } from './Behaviors/Behaviors';
|
||||
|
||||
/**
|
||||
* Handled the property change event in the properties form
|
||||
|
@ -44,13 +42,7 @@ export function OnPropertyChange(
|
|||
(container.properties as any)[key] = value;
|
||||
}
|
||||
|
||||
if (container.properties.isAnchor) {
|
||||
ImposePosition(container);
|
||||
}
|
||||
|
||||
if (container.properties.isRigidBody) {
|
||||
RecalculatePhysics(container);
|
||||
}
|
||||
ApplyBehaviors(container);
|
||||
|
||||
history.push({
|
||||
LastAction: `Change ${key} of ${container.properties.id}`,
|
||||
|
@ -117,9 +109,8 @@ export function OnPropertiesSubmit(
|
|||
submitCSSForm(form, styleProperty, container);
|
||||
}
|
||||
|
||||
if (container.properties.isRigidBody) {
|
||||
RecalculatePhysics(container);
|
||||
}
|
||||
// Apply the behaviors
|
||||
ApplyBehaviors(container);
|
||||
|
||||
history.push({
|
||||
LastAction: `Change properties of ${container.properties.id}`,
|
||||
|
@ -138,8 +129,9 @@ const submitHTMLInput = (
|
|||
property: string,
|
||||
form: HTMLFormElement
|
||||
): void => {
|
||||
if (INPUT_TYPES[property] !== 'number') {
|
||||
if (input.type !== 'number') {
|
||||
(container.properties as any)[property] = input.value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (property === 'x') {
|
||||
|
@ -188,7 +180,7 @@ const submitRadioButtons = (
|
|||
return;
|
||||
}
|
||||
|
||||
if (INPUT_TYPES[property] === 'number') {
|
||||
if (radiobutton.type === 'radio') {
|
||||
(container.properties as any)[property] = Number(radiobutton.value);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
|
|||
import { getCircularReplacer } from '../../utils/saveload';
|
||||
import { ID } from '../SVG/SVG';
|
||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||
import Worker from '../../workers/worker?worker';
|
||||
|
||||
export function SaveEditorAsJSON(
|
||||
history: IHistoryState[],
|
||||
|
@ -21,7 +20,7 @@ export function SaveEditorAsJSON(
|
|||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
if (window.Worker) {
|
||||
// use webworker for the stringify to avoid freezing
|
||||
const myWorker = new Worker();
|
||||
const myWorker = new Worker('workers/worker.js');
|
||||
myWorker.postMessage({ editorState, spaces });
|
||||
myWorker.onmessage = (event) => {
|
||||
const data = event.data;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||
import { ENABLE_SHORTCUTS } from '../../utils/default';
|
||||
|
||||
export function onKeyDown(
|
||||
event: KeyboardEvent,
|
||||
|
@ -7,6 +8,10 @@ export function onKeyDown(
|
|||
historyCurrentStep: number,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||
): void {
|
||||
if (!ENABLE_SHORTCUTS) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
if (event.isComposing || event.keyCode === 229) {
|
||||
return;
|
||||
|
|
|
@ -14,10 +14,12 @@ describe.concurrent('Elements sidebar', () => {
|
|||
properties: {
|
||||
id: 'main',
|
||||
parentId: null,
|
||||
displayedText: 'main',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 2000,
|
||||
height: 100,
|
||||
minWidth: 1,
|
||||
XPositionReference: XPositionReference.Left,
|
||||
isRigidBody: false,
|
||||
isAnchor: false
|
||||
|
@ -46,10 +48,12 @@ describe.concurrent('Elements sidebar', () => {
|
|||
properties: {
|
||||
id: 'main',
|
||||
parentId: '',
|
||||
displayedText: 'main',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 2000,
|
||||
height: 100,
|
||||
minWidth: 1,
|
||||
isRigidBody: false,
|
||||
isAnchor: false,
|
||||
XPositionReference: XPositionReference.Left
|
||||
|
@ -104,8 +108,10 @@ describe.concurrent('Elements sidebar', () => {
|
|||
properties: {
|
||||
id: 'main',
|
||||
parentId: '',
|
||||
displayedText: 'main',
|
||||
x: 0,
|
||||
y: 0,
|
||||
minWidth: 1,
|
||||
width: 2000,
|
||||
height: 100,
|
||||
XPositionReference: XPositionReference.Left,
|
||||
|
@ -122,8 +128,10 @@ describe.concurrent('Elements sidebar', () => {
|
|||
properties: {
|
||||
id: 'child-1',
|
||||
parentId: 'main',
|
||||
displayedText: 'child-1',
|
||||
x: 0,
|
||||
y: 0,
|
||||
minWidth: 1,
|
||||
width: 0,
|
||||
height: 0,
|
||||
isRigidBody: false,
|
||||
|
@ -141,8 +149,10 @@ describe.concurrent('Elements sidebar', () => {
|
|||
properties: {
|
||||
id: 'child-2',
|
||||
parentId: 'main',
|
||||
displayedText: 'child-2',
|
||||
x: 0,
|
||||
y: 0,
|
||||
minWidth: 1,
|
||||
width: 0,
|
||||
height: 0,
|
||||
XPositionReference: XPositionReference.Left,
|
||||
|
@ -180,8 +190,10 @@ describe.concurrent('Elements sidebar', () => {
|
|||
properties: {
|
||||
id: 'main',
|
||||
parentId: '',
|
||||
displayedText: 'main',
|
||||
x: 0,
|
||||
y: 0,
|
||||
minWidth: 1,
|
||||
width: 2000,
|
||||
height: 100,
|
||||
XPositionReference: XPositionReference.Left,
|
||||
|
@ -197,8 +209,10 @@ describe.concurrent('Elements sidebar', () => {
|
|||
properties: {
|
||||
id: 'child-1',
|
||||
parentId: 'main',
|
||||
displayedText: 'child-1',
|
||||
x: 0,
|
||||
y: 0,
|
||||
minWidth: 1,
|
||||
width: 0,
|
||||
height: 0,
|
||||
XPositionReference: XPositionReference.Left,
|
||||
|
|
|
@ -83,7 +83,9 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
|||
const container = containers[index];
|
||||
const depth: number = getDepth(container);
|
||||
const key = container.properties.id.toString();
|
||||
const text = '|\t'.repeat(depth) + key;
|
||||
const text = container.properties.displayedText === key
|
||||
? `${'|\t'.repeat(depth)} ${key}`
|
||||
: `${'|\t'.repeat(depth)} ${container.properties.displayedText} (${key})`;
|
||||
const selectedClass: string = props.SelectedContainer !== undefined &&
|
||||
props.SelectedContainer !== null &&
|
||||
props.SelectedContainer.properties.id === container.properties.id
|
||||
|
@ -116,7 +118,7 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
|||
</div>
|
||||
<div ref={elementRef} className='h-96 text-gray-800'>
|
||||
<List
|
||||
className='List'
|
||||
className='List divide-y divide-black'
|
||||
itemCount={containers.length}
|
||||
itemSize={35}
|
||||
height={384}
|
||||
|
|
|
@ -11,6 +11,7 @@ interface IInputGroupProps {
|
|||
checked?: boolean
|
||||
defaultValue?: string
|
||||
defaultChecked?: boolean
|
||||
min?: number
|
||||
isDisabled?: boolean
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
}
|
||||
|
@ -42,6 +43,7 @@ export const InputGroup: React.FunctionComponent<IInputGroupProps> = (props) =>
|
|||
checked={props.checked}
|
||||
defaultChecked={props.defaultChecked}
|
||||
onChange={props.onChange}
|
||||
min={props.min}
|
||||
disabled={props.isDisabled}
|
||||
/>
|
||||
</>;
|
||||
|
|
|
@ -52,6 +52,15 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
|
|||
value={props.properties.parentId?.toString()}
|
||||
isDisabled={true}
|
||||
/>
|
||||
<InputGroup
|
||||
labelText='Displayed text'
|
||||
inputKey='displayedText'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='string'
|
||||
value={props.properties.displayedText?.toString()}
|
||||
onChange={(event) => props.onChange('displayedText', event.target.value)}
|
||||
/>
|
||||
<InputGroup
|
||||
labelText='x'
|
||||
inputKey='x'
|
||||
|
@ -70,12 +79,23 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
|
|||
value={props.properties.y.toString()}
|
||||
onChange={(event) => props.onChange('y', Number(event.target.value))}
|
||||
/>
|
||||
<InputGroup
|
||||
labelText='Minimum width'
|
||||
inputKey='minWidth'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='number'
|
||||
min={1}
|
||||
value={props.properties.minWidth.toString()}
|
||||
onChange={(event) => props.onChange('minWidth', Number(event.target.value))}
|
||||
/>
|
||||
<InputGroup
|
||||
labelText='Width'
|
||||
inputKey='width'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='number'
|
||||
min={props.properties.minWidth}
|
||||
value={props.properties.width.toString()}
|
||||
onChange={(event) => props.onChange('width', Number(event.target.value))}
|
||||
/>
|
||||
|
@ -85,6 +105,7 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
|
|||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='number'
|
||||
min={0}
|
||||
value={props.properties.height.toString()}
|
||||
onChange={(event) => props.onChange('height', Number(event.target.value))}
|
||||
/>
|
||||
|
|
|
@ -23,10 +23,12 @@ describe.concurrent('Properties', () => {
|
|||
const prop: IProperties = {
|
||||
id: 'stuff',
|
||||
parentId: 'parentId',
|
||||
displayedText: 'stuff',
|
||||
x: 1,
|
||||
y: 1,
|
||||
width: 1,
|
||||
height: 1,
|
||||
minWidth: 1,
|
||||
XPositionReference: XPositionReference.Left,
|
||||
isRigidBody: false,
|
||||
isAnchor: false
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
export const INPUT_TYPES: Record<string, string> = {
|
||||
x: 'number',
|
||||
y: 'number',
|
||||
width: 'number',
|
||||
height: 'number',
|
||||
isRigidBody: 'checkbox',
|
||||
isAnchor: 'checkbox',
|
||||
XPositionReference: 'number'
|
||||
};
|
|
@ -52,6 +52,14 @@ const StaticForm: React.FunctionComponent<IStaticFormProps> = (props) => {
|
|||
defaultValue={props.properties.parentId?.toString()}
|
||||
isDisabled={true}
|
||||
/>
|
||||
<InputGroup
|
||||
labelText='Displayed text'
|
||||
inputKey='displayedText'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='string'
|
||||
defaultValue={props.properties.displayedText?.toString()}
|
||||
/>
|
||||
<InputGroup
|
||||
labelText='x'
|
||||
inputKey='x'
|
||||
|
@ -68,12 +76,22 @@ const StaticForm: React.FunctionComponent<IStaticFormProps> = (props) => {
|
|||
type='number'
|
||||
defaultValue={props.properties.y.toString()}
|
||||
/>
|
||||
<InputGroup
|
||||
labelText='Minimum width'
|
||||
inputKey='minWidth'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='number'
|
||||
min={0}
|
||||
defaultValue={props.properties.minWidth.toString()}
|
||||
/>
|
||||
<InputGroup
|
||||
labelText='Width'
|
||||
inputKey='width'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='number'
|
||||
min={props.properties.minWidth}
|
||||
defaultValue={props.properties.width.toString()}
|
||||
/>
|
||||
<InputGroup
|
||||
|
@ -82,6 +100,7 @@ const StaticForm: React.FunctionComponent<IStaticFormProps> = (props) => {
|
|||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='number'
|
||||
min={1}
|
||||
defaultValue={props.properties.height.toString()}
|
||||
/>
|
||||
<InputGroup
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import * as React from 'react';
|
||||
import { Interweave, Node } from 'interweave';
|
||||
import { XPositionReference } from '../../../Enums/XPositionReference';
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { DIMENSION_MARGIN } from '../../../utils/default';
|
||||
import { DIMENSION_MARGIN, SHOW_CHILDREN_DIMENSIONS, SHOW_PARENT_DIMENSION, SHOW_TEXT } from '../../../utils/default';
|
||||
import { getDepth } from '../../../utils/itertools';
|
||||
import { Dimension } from './Dimension';
|
||||
import IProperties from '../../../Interfaces/IProperties';
|
||||
|
||||
interface IContainerProps {
|
||||
model: IContainerModel
|
||||
|
@ -33,6 +35,14 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
|||
props.model.properties.style
|
||||
);
|
||||
|
||||
const svg = (props.model.properties.customSVG != null)
|
||||
? CreateReactCustomSVG(props.model.properties.customSVG, props.model.properties)
|
||||
: (<rect
|
||||
width={props.model.properties.width}
|
||||
height={props.model.properties.height}
|
||||
style={style}
|
||||
>
|
||||
</rect>);
|
||||
// Dimension props
|
||||
const depth = getDepth(props.model);
|
||||
const dimensionMargin = DIMENSION_MARGIN * (depth + 1);
|
||||
|
@ -44,7 +54,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
|||
const text = (props.model.properties.width ?? 0).toString();
|
||||
|
||||
let dimensionChildren: JSX.Element | null = null;
|
||||
if (props.model.children.length > 1) {
|
||||
if (props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
|
||||
const {
|
||||
childrenId,
|
||||
xChildrenStart,
|
||||
|
@ -69,28 +79,28 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
|||
transform={transform}
|
||||
key={`container-${props.model.properties.id}`}
|
||||
>
|
||||
<Dimension
|
||||
id={id}
|
||||
xStart={xStart}
|
||||
xEnd={xEnd}
|
||||
yStart={y}
|
||||
yEnd={y}
|
||||
strokeWidth={strokeWidth}
|
||||
text={text}
|
||||
/>
|
||||
{ SHOW_PARENT_DIMENSION
|
||||
? <Dimension
|
||||
id={id}
|
||||
xStart={xStart}
|
||||
xEnd={xEnd}
|
||||
yStart={y}
|
||||
yEnd={y}
|
||||
strokeWidth={strokeWidth}
|
||||
text={text}
|
||||
/>
|
||||
: null
|
||||
}
|
||||
{ dimensionChildren }
|
||||
<rect
|
||||
width={props.model.properties.width}
|
||||
height={props.model.properties.height}
|
||||
style={style}
|
||||
>
|
||||
</rect>
|
||||
<text
|
||||
x={xText}
|
||||
y={yText}
|
||||
>
|
||||
{props.model.properties.id}
|
||||
</text>
|
||||
{ svg }
|
||||
{ SHOW_TEXT
|
||||
? <text
|
||||
x={xText}
|
||||
y={yText}
|
||||
>
|
||||
{props.model.properties.displayedText}
|
||||
</text>
|
||||
: null }
|
||||
{ containersElements }
|
||||
</g>
|
||||
);
|
||||
|
@ -141,3 +151,63 @@ export function restoreX(x: number, width: number, xPositionReference = XPositio
|
|||
}
|
||||
return transformedX;
|
||||
}
|
||||
|
||||
function CreateReactCustomSVG(customSVG: string, props: IProperties): React.ReactNode {
|
||||
return <Interweave
|
||||
tagName='g'
|
||||
disableLineBreaks={true}
|
||||
content={customSVG}
|
||||
allowElements={true}
|
||||
transform={(node, children) => transform(node, children, props)}
|
||||
/>;
|
||||
}
|
||||
|
||||
function transform(node: HTMLElement, children: Node[], props: IProperties): React.ReactNode {
|
||||
const supportedTags = ['line', 'path', 'rect'];
|
||||
if (supportedTags.includes(node.tagName.toLowerCase())) {
|
||||
const attributes: {[att: string]: string | object | null} = {};
|
||||
node.getAttributeNames().forEach(attName => {
|
||||
const attributeValue = node.getAttribute(attName);
|
||||
if (attributeValue === null) {
|
||||
attributes[attName] = attributeValue;
|
||||
return;
|
||||
}
|
||||
|
||||
if (attributeValue.startsWith('{userData.') && attributeValue.endsWith('}')) {
|
||||
// support for userData
|
||||
if (props.userData === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const userDataKey = attributeValue.replace(/userData\./, '');
|
||||
|
||||
const prop = Object.entries(props.userData).find(([key]) => `{${key}}` === userDataKey);
|
||||
if (prop !== undefined) {
|
||||
attributes[camelize(attName)] = prop[1];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (attributeValue.startsWith('{{') && attributeValue.endsWith('}}')) {
|
||||
// support for object
|
||||
const stringObject = attributeValue.slice(1, -1);
|
||||
const object: JSON = JSON.parse(stringObject);
|
||||
attributes[camelize(attName)] = object;
|
||||
return;
|
||||
}
|
||||
|
||||
const prop = Object.entries(props).find(([key]) => `{${key}}` === attributeValue);
|
||||
if (prop !== undefined) {
|
||||
attributes[camelize(attName)] = prop[1];
|
||||
return;
|
||||
}
|
||||
attributes[camelize(attName)] = attributeValue;
|
||||
});
|
||||
return React.createElement(node.tagName.toLowerCase(), attributes, children);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function camelize(str: string): any {
|
||||
return str.split('-').map((word, index) => index > 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word).join('');
|
||||
}
|
||||
|
|
90
src/Components/SVG/Elements/DepthDimensionLayer.tsx
Normal file
90
src/Components/SVG/Elements/DepthDimensionLayer.tsx
Normal file
|
@ -0,0 +1,90 @@
|
|||
import * as React from 'react';
|
||||
import { ContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { DIMENSION_MARGIN } from '../../../utils/default';
|
||||
import { getAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
|
||||
import { transformX } from './Container';
|
||||
import { Dimension } from './Dimension';
|
||||
|
||||
interface IDimensionLayerProps {
|
||||
roots: ContainerModel | ContainerModel[] | null
|
||||
}
|
||||
|
||||
const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
|
||||
const it = MakeBFSIterator(root);
|
||||
const dimensions: React.ReactNode[] = [];
|
||||
let currentDepth = 0;
|
||||
let min = Infinity;
|
||||
let max = -Infinity;
|
||||
let lastY = 0;
|
||||
for (const { container, depth } of it) {
|
||||
if (currentDepth !== depth) {
|
||||
AddNewDimension(currentDepth, min, max, lastY, dimensions);
|
||||
|
||||
currentDepth = depth;
|
||||
min = Infinity;
|
||||
max = -Infinity;
|
||||
}
|
||||
|
||||
const absoluteX = getAbsolutePosition(container)[0];
|
||||
const x = transformX(absoluteX, container.properties.width, container.properties.XPositionReference);
|
||||
lastY = container.properties.y + container.properties.height;
|
||||
if (x < min) {
|
||||
min = x;
|
||||
}
|
||||
|
||||
if (x > max) {
|
||||
max = x;
|
||||
}
|
||||
}
|
||||
|
||||
AddNewDimension(currentDepth, min, max, lastY, dimensions);
|
||||
|
||||
return dimensions;
|
||||
};
|
||||
|
||||
/**
|
||||
* A layer containing all dimension
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const DepthDimensionLayer: React.FC<IDimensionLayerProps> = (props: IDimensionLayerProps) => {
|
||||
let dimensions: React.ReactNode[] = [];
|
||||
if (Array.isArray(props.roots)) {
|
||||
props.roots.forEach(child => {
|
||||
dimensions.concat(getDimensionsNodes(child));
|
||||
});
|
||||
} else if (props.roots !== null) {
|
||||
dimensions = getDimensionsNodes(props.roots);
|
||||
}
|
||||
return (
|
||||
<g>
|
||||
{ dimensions }
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
function AddNewDimension(currentDepth: number, min: number, max: number, lastY: number, dimensions: React.ReactNode[]): void {
|
||||
const id = `dim-depth-${currentDepth}`;
|
||||
const xStart = min;
|
||||
const xEnd = max;
|
||||
const y = lastY + (DIMENSION_MARGIN * (currentDepth + 1));
|
||||
const strokeWidth = 1;
|
||||
const width = xEnd - xStart;
|
||||
const text = width.toString();
|
||||
|
||||
if (width === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dimensions.push(
|
||||
<Dimension
|
||||
key={id}
|
||||
id={id}
|
||||
xStart={xStart}
|
||||
yStart={y}
|
||||
xEnd={xEnd}
|
||||
yEnd={y}
|
||||
strokeWidth={strokeWidth}
|
||||
text={text} />
|
||||
);
|
||||
}
|
57
src/Components/SVG/Elements/DimensionLayer.tsx
Normal file
57
src/Components/SVG/Elements/DimensionLayer.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import * as React from 'react';
|
||||
import { ContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { DIMENSION_MARGIN } from '../../../utils/default';
|
||||
import { getAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
|
||||
import { Dimension } from './Dimension';
|
||||
|
||||
interface IDimensionLayerProps {
|
||||
roots: ContainerModel | ContainerModel[] | null
|
||||
}
|
||||
|
||||
const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
|
||||
const it = MakeBFSIterator(root);
|
||||
const dimensions: React.ReactNode[] = [];
|
||||
for (const { container, depth } of it) {
|
||||
const width = container.properties.width;
|
||||
const id = `dim-${container.properties.id}`;
|
||||
const xStart = getAbsolutePosition(container)[0];
|
||||
const xEnd = xStart + width;
|
||||
const y = (container.properties.y + container.properties.height) + (DIMENSION_MARGIN * (depth + 1));
|
||||
const strokeWidth = 1;
|
||||
const text = width.toString();
|
||||
dimensions.push(
|
||||
<Dimension
|
||||
key={id}
|
||||
id={id}
|
||||
xStart={xStart}
|
||||
yStart={y}
|
||||
xEnd={xEnd}
|
||||
yEnd={y}
|
||||
strokeWidth={strokeWidth}
|
||||
text={text}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return dimensions;
|
||||
};
|
||||
|
||||
/**
|
||||
* A layer containing all dimension
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const DimensionLayer: React.FC<IDimensionLayerProps> = (props: IDimensionLayerProps) => {
|
||||
let dimensions: React.ReactNode[] = [];
|
||||
if (Array.isArray(props.roots)) {
|
||||
props.roots.forEach(child => {
|
||||
dimensions.concat(getDimensionsNodes(child));
|
||||
});
|
||||
} else if (props.roots !== null) {
|
||||
dimensions = getDimensionsNodes(props.roots);
|
||||
}
|
||||
return (
|
||||
<g>
|
||||
{ dimensions }
|
||||
</g>
|
||||
);
|
||||
};
|
|
@ -4,6 +4,9 @@ import { Container } from './Elements/Container';
|
|||
import { ContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { Selector } from './Elements/Selector';
|
||||
import { BAR_WIDTH } from '../Bar/Bar';
|
||||
import { DimensionLayer } from './Elements/DimensionLayer';
|
||||
import { DepthDimensionLayer } from './Elements/DepthDimensionLayer';
|
||||
import { SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default';
|
||||
|
||||
interface ISVGProps {
|
||||
width: number
|
||||
|
@ -74,6 +77,11 @@ export const SVG: React.FC<ISVGProps> = (props: ISVGProps) => {
|
|||
<svg {...properties}>
|
||||
{ children }
|
||||
<Selector selected={props.selected} />
|
||||
{
|
||||
SHOW_DIMENSIONS_PER_DEPTH
|
||||
? <DepthDimensionLayer roots={props.children}/>
|
||||
: null
|
||||
}
|
||||
</svg>
|
||||
</UncontrolledReactSVGPanZoom>
|
||||
</div>
|
||||
|
|
|
@ -5,11 +5,15 @@ import { XPositionReference } from '../Enums/XPositionReference';
|
|||
/** Model of available container used in application configuration */
|
||||
export interface IAvailableContainer {
|
||||
Type: string
|
||||
Width?: number
|
||||
Height?: number
|
||||
DefaultX?: number
|
||||
DefaultY?: number
|
||||
Width?: number
|
||||
Height?: number
|
||||
MinWidth?: number
|
||||
AddMethod?: AddMethod
|
||||
XPositionReference?: XPositionReference
|
||||
Style: React.CSSProperties
|
||||
CustomSVG?: string
|
||||
DefaultChildType?: string
|
||||
Style?: React.CSSProperties
|
||||
UserData?: object
|
||||
}
|
||||
|
|
|
@ -3,22 +3,70 @@ import { XPositionReference } from '../Enums/XPositionReference';
|
|||
|
||||
/**
|
||||
* Properties of a container
|
||||
* @property id id of the container
|
||||
* @property parentId id of the parent container
|
||||
* @property x horizontal offset of the container
|
||||
* @property y vertical offset of the container
|
||||
* @property isRigidBody if true apply rigid body behaviors
|
||||
* @property isAnchor if true apply anchor behaviors
|
||||
*/
|
||||
export default interface IProperties {
|
||||
/** id of the container */
|
||||
id: string
|
||||
|
||||
/** id of the parent container (null when there is no parent) */
|
||||
parentId: string | null
|
||||
|
||||
/** Text displayed in the container */
|
||||
displayedText: string
|
||||
|
||||
/** horizontal offset */
|
||||
x: number
|
||||
|
||||
/** vertical offset */
|
||||
y: number
|
||||
|
||||
/**
|
||||
* Minimum width (min=1)
|
||||
* Allows the container to set isRigidBody to false when it gets squeezed
|
||||
* by an anchor
|
||||
*/
|
||||
minWidth: number
|
||||
|
||||
/** width */
|
||||
width: number
|
||||
|
||||
/** height */
|
||||
height: number
|
||||
|
||||
/** true if rigid, false otherwise */
|
||||
isRigidBody: boolean
|
||||
|
||||
/** true if anchor, false otherwise */
|
||||
isAnchor: boolean
|
||||
|
||||
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
||||
XPositionReference: XPositionReference
|
||||
|
||||
/**
|
||||
* (optional)
|
||||
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows
|
||||
* to draw some patterns that can be bind to the properties of the container
|
||||
* Use {prop} to bind a property. Use {{ styleProp }} to use an object.
|
||||
* Example :
|
||||
* ```
|
||||
* `<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>
|
||||
* `
|
||||
* ```
|
||||
*/
|
||||
customSVG?: string
|
||||
|
||||
/**
|
||||
* (optional)
|
||||
* Style of the <rect>
|
||||
*/
|
||||
style?: React.CSSProperties
|
||||
|
||||
/**
|
||||
* (optional)
|
||||
* User data that can be used for data storage or custom SVG
|
||||
*/
|
||||
userData?: object
|
||||
}
|
||||
|
|
4148
src/tests/resources/chassis2cannotfitminwidth.json
Normal file
4148
src/tests/resources/chassis2cannotfitminwidth.json
Normal file
File diff suppressed because it is too large
Load diff
8145
src/tests/resources/montantMustImpose.json
Normal file
8145
src/tests/resources/montantMustImpose.json
Normal file
File diff suppressed because it is too large
Load diff
4748
src/tests/resources/nospaceleft.json
Normal file
4748
src/tests/resources/nospaceleft.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,436 +0,0 @@
|
|||
{
|
||||
"history": [
|
||||
{
|
||||
"MainContainer": {
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 2000,
|
||||
"height": 100,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"SelectedContainerId": "main",
|
||||
"TypeCounters": {}
|
||||
},
|
||||
{
|
||||
"LastAction": "Add container",
|
||||
"MainContainer": {
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Container-0",
|
||||
"parentId": "main",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 75,
|
||||
"height": 100,
|
||||
"isRigidBody": false,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "green"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Container"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 2000,
|
||||
"height": 100,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"SelectedContainerId": "main",
|
||||
"TypeCounters": {
|
||||
"Container": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"LastAction": "Select container Container-0",
|
||||
"MainContainer": {
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Container-0",
|
||||
"parentId": "main",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 75,
|
||||
"height": 100,
|
||||
"isRigidBody": false,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "green"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Container"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 2000,
|
||||
"height": 100,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"SelectedContainerId": "Container-0",
|
||||
"TypeCounters": {
|
||||
"Container": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"LastAction": "Change property of container Container-0",
|
||||
"MainContainer": {
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Container-0",
|
||||
"parentId": "main",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": "7",
|
||||
"height": 100,
|
||||
"isRigidBody": false,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "green"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Container"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 2000,
|
||||
"height": 100,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"SelectedContainerId": "Container-0",
|
||||
"TypeCounters": {
|
||||
"Container": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"LastAction": "Change property of container Container-0",
|
||||
"MainContainer": {
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Container-0",
|
||||
"parentId": "main",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": "",
|
||||
"height": 100,
|
||||
"isRigidBody": false,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "green"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Container"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 2000,
|
||||
"height": 100,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"SelectedContainerId": "Container-0",
|
||||
"TypeCounters": {
|
||||
"Container": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"LastAction": "Change property of container Container-0",
|
||||
"MainContainer": {
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Container-0",
|
||||
"parentId": "main",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": "2",
|
||||
"height": 100,
|
||||
"isRigidBody": false,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "green"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Container"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 2000,
|
||||
"height": 100,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"SelectedContainerId": "Container-0",
|
||||
"TypeCounters": {
|
||||
"Container": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"LastAction": "Change property of container Container-0",
|
||||
"MainContainer": {
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Container-0",
|
||||
"parentId": "main",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": "20",
|
||||
"height": 100,
|
||||
"isRigidBody": false,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "green"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Container"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 2000,
|
||||
"height": 100,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"SelectedContainerId": "Container-0",
|
||||
"TypeCounters": {
|
||||
"Container": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"LastAction": "Change property of container Container-0",
|
||||
"MainContainer": {
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Container-0",
|
||||
"parentId": "main",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": "200",
|
||||
"height": 100,
|
||||
"isRigidBody": false,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "green"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Container"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 2000,
|
||||
"height": 100,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"SelectedContainerId": "Container-0",
|
||||
"TypeCounters": {
|
||||
"Container": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"LastAction": "Change property of container Container-0",
|
||||
"MainContainer": {
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Container-0",
|
||||
"parentId": "main",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": "2000",
|
||||
"height": 100,
|
||||
"isRigidBody": false,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "green"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Container"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 2000,
|
||||
"height": 100,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"SelectedContainerId": "Container-0",
|
||||
"TypeCounters": {
|
||||
"Container": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"LastAction": "Change property of container Container-0",
|
||||
"MainContainer": {
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Container-0",
|
||||
"parentId": "main",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": "20000",
|
||||
"height": 100,
|
||||
"isRigidBody": false,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "green"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Container"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 2000,
|
||||
"height": 100,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"SelectedContainerId": "Container-0",
|
||||
"TypeCounters": {
|
||||
"Container": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"LastAction": "Change property of container Container-0",
|
||||
"MainContainer": {
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Container-0",
|
||||
"parentId": "main",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 2000,
|
||||
"height": 100,
|
||||
"isRigidBody": true,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "green"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Container"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 2000,
|
||||
"height": 100,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"SelectedContainerId": "Container-0",
|
||||
"TypeCounters": {
|
||||
"Container": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"historyCurrentStep": 10,
|
||||
"configuration": {
|
||||
"AvailableContainers": [
|
||||
{
|
||||
"Type": "Container",
|
||||
"Width": 75,
|
||||
"Height": 100,
|
||||
"Style": {
|
||||
"fillOpacity": 0,
|
||||
"stroke": "green"
|
||||
}
|
||||
}
|
||||
],
|
||||
"AvailableSymbols": [],
|
||||
"MainContainer": {
|
||||
"Type": "Container",
|
||||
"Width": 2000,
|
||||
"Height": 100,
|
||||
"Style": {
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13148
src/tests/resources/squeezeChassis2.json
Normal file
13148
src/tests/resources/squeezeChassis2.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,182 +0,0 @@
|
|||
{
|
||||
"isSidebarOpen": true,
|
||||
"isElementsSidebarOpen": false,
|
||||
"isHistoryOpen": false,
|
||||
"configuration": {
|
||||
"AvailableContainers": [
|
||||
{
|
||||
"Type": "Chassis",
|
||||
"Width": 500,
|
||||
"Style": {
|
||||
"fillOpacity": 0,
|
||||
"borderWidth": 2,
|
||||
"stroke": "red"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Type": "Trou",
|
||||
"Width": 300,
|
||||
"Style": {
|
||||
"fillOpacity": 0,
|
||||
"borderWidth": 2,
|
||||
"stroke": "green"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Type": "Montant",
|
||||
"Width": 100,
|
||||
"Style": {
|
||||
"fillOpacity": 0,
|
||||
"borderWidth": 2,
|
||||
"stroke": "blue",
|
||||
"transform": "translateX(-50%)",
|
||||
"transformOrigin": "center",
|
||||
"transformBox": "fill-box"
|
||||
}
|
||||
}
|
||||
],
|
||||
"AvailableSymbols": [
|
||||
{
|
||||
"Height": 0,
|
||||
"Image": {
|
||||
"Base64Image": null,
|
||||
"Name": null,
|
||||
"Svg": null,
|
||||
"Url": "https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg"
|
||||
},
|
||||
"Name": "Poteau structure",
|
||||
"Width": 0,
|
||||
"XPositionReference": 1
|
||||
},
|
||||
{
|
||||
"Height": 0,
|
||||
"Image": {
|
||||
"Base64Image": null,
|
||||
"Name": null,
|
||||
"Svg": null,
|
||||
"Url": "https://e7.pngegg.com/pngimages/647/127/png-clipart-svg-working-group-information-world-wide-web-internet-structure.png"
|
||||
},
|
||||
"Name": "Joint de structure",
|
||||
"Width": 0,
|
||||
"XPositionReference": 0
|
||||
}
|
||||
],
|
||||
"MainContainer": {
|
||||
"Height": 200,
|
||||
"Width": 1000
|
||||
}
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"MainContainer": {
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1000,
|
||||
"height": 200,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"TypeCounters": {}
|
||||
},
|
||||
{
|
||||
"MainContainer": {
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Chassis-0",
|
||||
"parentId": "main",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 500,
|
||||
"height": 200,
|
||||
"fillOpacity": 0,
|
||||
"borderWidth": 2,
|
||||
"stroke": "red"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Chassis"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1000,
|
||||
"height": 200,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"TypeCounters": {
|
||||
"Chassis": 0
|
||||
},
|
||||
"SelectedContainerId": "main"
|
||||
},
|
||||
{
|
||||
"MainContainer": {
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Chassis-0",
|
||||
"parentId": "main",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 500,
|
||||
"height": 200,
|
||||
"fillOpacity": 0,
|
||||
"borderWidth": 2,
|
||||
"stroke": "red"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Chassis"
|
||||
}
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"properties": {
|
||||
"id": "Chassis-1",
|
||||
"parentId": "main",
|
||||
"x": 500,
|
||||
"y": 0,
|
||||
"width": 500,
|
||||
"height": 200,
|
||||
"fillOpacity": 0,
|
||||
"borderWidth": 2,
|
||||
"stroke": "red"
|
||||
},
|
||||
"userData": {
|
||||
"type": "Chassis"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": "main",
|
||||
"parentId": "null",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1000,
|
||||
"height": 200,
|
||||
"fillOpacity": 0,
|
||||
"stroke": "black"
|
||||
},
|
||||
"userData": {}
|
||||
},
|
||||
"TypeCounters": {
|
||||
"Chassis": 1
|
||||
},
|
||||
"SelectedContainerId": "main"
|
||||
}
|
||||
],
|
||||
"historyCurrentStep": 2
|
||||
}
|
|
@ -1,7 +1,28 @@
|
|||
import { XPositionReference } from '../Enums/XPositionReference';
|
||||
import { IAvailableContainer } from '../Interfaces/IAvailableContainer';
|
||||
import { IConfiguration } from '../Interfaces/IConfiguration';
|
||||
import { IContainerModel } from '../Interfaces/IContainerModel';
|
||||
import IProperties from '../Interfaces/IProperties';
|
||||
|
||||
/// CONTAINRE DEFAULTS ///
|
||||
|
||||
export const SHOW_TEXT = true;
|
||||
export const DEFAULTCHILDTYPE_ALLOW_CYCLIC = false;
|
||||
export const DEFAULTCHILDTYPE_MAX_DEPTH = 10;
|
||||
|
||||
/// DIMENSIONS DEFAULTS ///
|
||||
|
||||
export const SHOW_PARENT_DIMENSION = true;
|
||||
export const SHOW_CHILDREN_DIMENSIONS = false;
|
||||
export const SHOW_DIMENSIONS_PER_DEPTH = true;
|
||||
export const DIMENSION_MARGIN = 50;
|
||||
export const NOTCHES_LENGTH = 4;
|
||||
|
||||
/// EDITOR DEFAULTS ///
|
||||
|
||||
export const ENABLE_SHORTCUTS = true;
|
||||
export const MAX_HISTORY = 200;
|
||||
|
||||
export const DEFAULT_CONFIG: IConfiguration = {
|
||||
AvailableContainers: [
|
||||
{
|
||||
|
@ -29,8 +50,10 @@ export const DEFAULT_CONFIG: IConfiguration = {
|
|||
export const DEFAULT_MAINCONTAINER_PROPS: IProperties = {
|
||||
id: 'main',
|
||||
parentId: 'null',
|
||||
displayedText: 'main',
|
||||
x: 0,
|
||||
y: 0,
|
||||
minWidth: 1,
|
||||
width: Number(DEFAULT_CONFIG.MainContainer.Width),
|
||||
height: Number(DEFAULT_CONFIG.MainContainer.Height),
|
||||
isRigidBody: false,
|
||||
|
@ -42,7 +65,26 @@ export const DEFAULT_MAINCONTAINER_PROPS: IProperties = {
|
|||
}
|
||||
};
|
||||
|
||||
export const DIMENSION_MARGIN = 50;
|
||||
export const NOTCHES_LENGTH = 4;
|
||||
|
||||
export const MAX_HISTORY = 200;
|
||||
export const GetDefaultContainerProps = (
|
||||
type: string,
|
||||
typeCount: number,
|
||||
parent: IContainerModel,
|
||||
x: number,
|
||||
y: number,
|
||||
containerConfig: IAvailableContainer
|
||||
): IProperties => ({
|
||||
id: `${type}-${typeCount}`,
|
||||
parentId: parent.properties.id,
|
||||
displayedText: `${type}-${typeCount}`,
|
||||
x,
|
||||
y,
|
||||
width: containerConfig.Width ?? containerConfig.MinWidth ?? parent.properties.width,
|
||||
height: containerConfig.Height ?? parent.properties.height,
|
||||
isRigidBody: false, // set this to true to replicate Florian's project
|
||||
isAnchor: false,
|
||||
XPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
|
||||
minWidth: containerConfig.MinWidth ?? 0,
|
||||
customSVG: containerConfig.CustomSVG,
|
||||
style: structuredClone(containerConfig.Style),
|
||||
userData: structuredClone(containerConfig.UserData)
|
||||
});
|
||||
|
|
|
@ -22,6 +22,34 @@ export function * MakeIterator(root: IContainerModel): Generator<IContainerModel
|
|||
}
|
||||
}
|
||||
|
||||
export interface ContainerAndDepth {
|
||||
container: IContainerModel
|
||||
depth: number
|
||||
}
|
||||
/**
|
||||
* Returns a Generator iterating of over the children depth-first
|
||||
*/
|
||||
export function * MakeBFSIterator(root: IContainerModel): Generator<ContainerAndDepth, void, unknown> {
|
||||
const queue: IContainerModel[] = [root];
|
||||
let depth = 0;
|
||||
while (queue.length > 0) {
|
||||
let levelSize = queue.length;
|
||||
while (levelSize-- !== 0) {
|
||||
const container = queue.shift() as IContainerModel;
|
||||
yield {
|
||||
container,
|
||||
depth
|
||||
};
|
||||
|
||||
for (let i = container.children.length - 1; i >= 0; i--) {
|
||||
const child = container.children[i];
|
||||
queue.push(child);
|
||||
}
|
||||
}
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the depth of the container
|
||||
* @returns The depth of the container
|
||||
|
|
|
@ -42,7 +42,6 @@ export function Revive(editorState: IEditorState): void {
|
|||
}
|
||||
|
||||
export const getCircularReplacer = (): (key: any, value: object | null) => object | null | undefined => {
|
||||
const seen = new WeakSet();
|
||||
return (key: any, value: object | null) => {
|
||||
if (key === 'parent') {
|
||||
return;
|
||||
|
@ -52,12 +51,6 @@ export const getCircularReplacer = (): (key: any, value: object | null) => objec
|
|||
return;
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -54,12 +54,13 @@ const GetSVGLayoutConfiguration = () => {
|
|||
{
|
||||
Type: 'Chassis',
|
||||
Width: 500,
|
||||
MinWidth: 200,
|
||||
DefaultChildType: 'Trou',
|
||||
Style: {
|
||||
fillOpacity: 1,
|
||||
borderWidth: 2,
|
||||
strokeWidth: 2,
|
||||
stroke: 'red',
|
||||
fill: '#78350F',
|
||||
stroke: 'red'
|
||||
fill: '#d3c9b7',
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -68,20 +69,33 @@ const GetSVGLayoutConfiguration = () => {
|
|||
DefaultY: 10,
|
||||
Width: 480,
|
||||
Height: 180,
|
||||
DefaultChildType: 'Remplissage',
|
||||
Style: {
|
||||
fillOpacity: 1,
|
||||
borderWidth: 2,
|
||||
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,
|
||||
borderWidth: 2,
|
||||
stroke: '#bfdbfe',
|
||||
strokeWidth: 1,
|
||||
fill: '#bfdbfe'
|
||||
},
|
||||
UserData: {
|
||||
styleLine: {
|
||||
transform: "scaleY(0.5) translateY(100%)",
|
||||
transformBox: "fill-box"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -90,7 +104,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
XPositionReference: 1,
|
||||
Style: {
|
||||
fillOpacity: 0,
|
||||
borderWidth: 2,
|
||||
strokeWidth: 2,
|
||||
stroke: '#713f12',
|
||||
fill: '#713f12',
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src", "src/workers"],
|
||||
"include": ["src"],
|
||||
"exclude": ["test-server"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue