This commit is contained in:
commit
3221d97eff
39 changed files with 30948 additions and 795 deletions
|
@ -7,7 +7,7 @@ steps:
|
||||||
image: node:16
|
image: node:16
|
||||||
commands:
|
commands:
|
||||||
- node ./test-server/node-http.js &
|
- node ./test-server/node-http.js &
|
||||||
- npm ci
|
- npm i
|
||||||
- npm run test:nowatch
|
- npm run test:nowatch
|
||||||
- npm run build
|
- npm run build
|
||||||
|
|
||||||
|
@ -20,6 +20,6 @@ steps:
|
||||||
image: node
|
image: node
|
||||||
commands:
|
commands:
|
||||||
- node ./test-server/node-http.js &
|
- node ./test-server/node-http.js &
|
||||||
- npm ci
|
- npm i
|
||||||
- npm run test:nowatch
|
- npm run test:nowatch
|
||||||
- npm run build
|
- npm run build
|
|
@ -31,6 +31,8 @@ module.exports = {
|
||||||
'@typescript-eslint/semi': ['warn', 'always'],
|
'@typescript-eslint/semi': ['warn', 'always'],
|
||||||
'no-unused-vars': 'off',
|
'no-unused-vars': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': 'error',
|
'@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/rules-of-hooks': 'error', // Checks rules of Hooks
|
||||||
'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies
|
'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ It depends on Vite in order to build the project.
|
||||||
|
|
||||||
Others dependencies:
|
Others dependencies:
|
||||||
- [react-dom](https://reactjs.org/docs/react-dom.html): library used to inject the app to `#root` html element.
|
- [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-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
|
- [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/)
|
# [Vite](https://vitejs.dev/)
|
||||||
|
@ -49,6 +49,12 @@ Other dependencies:
|
||||||
|
|
||||||
SVG Icons that can be used as JSX elements with Tailwind CSS
|
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
|
# 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
|
├── docs/ Documentation folder
|
||||||
├── public/ Public folder in which the index.html
|
├── public/ Public folder in which the index.html
|
||||||
│ import its resources
|
│ │ import its resources
|
||||||
|
│ └── workers/ Webworkers folder
|
||||||
├── src/ Source folder for the react app
|
├── src/ Source folder for the react app
|
||||||
│ ├── assets/ Assets folder in which the react app
|
│ ├── assets/ Assets folder in which the react app
|
||||||
│ │ import its resources
|
│ │ import its resources
|
||||||
|
@ -17,7 +18,6 @@ The project is structured this way
|
||||||
│ ├── test/ Setup folder for the tests
|
│ ├── test/ Setup folder for the tests
|
||||||
│ ├── tests/ Other tests + resources
|
│ ├── tests/ Other tests + resources
|
||||||
│ ├── utils/ Utilities folder
|
│ ├── utils/ Utilities folder
|
||||||
│ ├── workers/ Webworkers folder
|
|
||||||
│ ├── index.scss Tailwind CSS extends
|
│ ├── index.scss Tailwind CSS extends
|
||||||
│ ├── main.tsx Entrypoint for App injection
|
│ ├── main.tsx Entrypoint for App injection
|
||||||
│ └── vite-env.d.ts Types for .env files
|
│ └── vite-env.d.ts Types for .env files
|
||||||
|
|
|
@ -14,10 +14,13 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
|
"interweave": "^13.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-svg-pan-zoom": "^3.11.0",
|
"react-svg-pan-zoom": "^3.11.0",
|
||||||
"react-window": "^1.8.7"
|
"react-window": "^1.8.7",
|
||||||
|
"sweetalert2": "^11.4.28",
|
||||||
|
"sweetalert2-react-content": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/dom": "^8.16.1",
|
"@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-promise: ^6.0.0
|
||||||
eslint-plugin-react: ^7.30.1
|
eslint-plugin-react: ^7.30.1
|
||||||
eslint-plugin-react-hooks: ^4.6.0
|
eslint-plugin-react-hooks: ^4.6.0
|
||||||
|
interweave: ^13.0.0
|
||||||
jsdom: ^20.0.0
|
jsdom: ^20.0.0
|
||||||
postcss: ^8.4.14
|
postcss: ^8.4.14
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
|
@ -31,6 +32,8 @@ specifiers:
|
||||||
react-svg-pan-zoom: ^3.11.0
|
react-svg-pan-zoom: ^3.11.0
|
||||||
react-window: ^1.8.7
|
react-window: ^1.8.7
|
||||||
sass: ^1.54.0
|
sass: ^1.54.0
|
||||||
|
sweetalert2: ^11.4.28
|
||||||
|
sweetalert2-react-content: ^5.0.3
|
||||||
tailwindcss: ^3.1.7
|
tailwindcss: ^3.1.7
|
||||||
typescript: ^4.6.4
|
typescript: ^4.6.4
|
||||||
vite: ^3.0.0
|
vite: ^3.0.0
|
||||||
|
@ -38,10 +41,13 @@ specifiers:
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@heroicons/react': 1.0.6_react@18.2.0
|
'@heroicons/react': 1.0.6_react@18.2.0
|
||||||
|
interweave: 13.0.0_react@18.2.0
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 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-svg-pan-zoom: 3.11.0_react@18.2.0
|
||||||
react-window: 1.8.7_biqbaboplfbrettd7655fr4n2y
|
react-window: 1.8.7_biqbaboplfbrettd7655fr4n2y
|
||||||
|
sweetalert2: 11.4.28
|
||||||
|
sweetalert2-react-content: 5.0.3_m2nzudmh75bb5iwknw4em6omxi
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@testing-library/dom': 8.16.1
|
'@testing-library/dom': 8.16.1
|
||||||
|
@ -1522,6 +1528,10 @@ packages:
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/escape-html/1.0.3:
|
||||||
|
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/escape-string-regexp/1.0.5:
|
/escape-string-regexp/1.0.5:
|
||||||
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=0.8.0'}
|
||||||
|
@ -2177,6 +2187,15 @@ packages:
|
||||||
side-channel: 1.0.4
|
side-channel: 1.0.4
|
||||||
dev: true
|
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:
|
/is-bigint/1.0.4:
|
||||||
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
|
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -3183,6 +3202,22 @@ packages:
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dev: true
|
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:
|
/symbol-tree/3.2.4:
|
||||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { fetchConfiguration } from '../API/api';
|
||||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||||
import { LoadState } from './Load';
|
import { LoadState } from './Load';
|
||||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||||
|
import { DEFAULT_MAINCONTAINER_PROPS } from '../../utils/default';
|
||||||
|
|
||||||
export function NewEditor(
|
export function NewEditor(
|
||||||
setEditorState: Dispatch<SetStateAction<IEditorState>>,
|
setEditorState: Dispatch<SetStateAction<IEditorState>>,
|
||||||
|
@ -17,19 +18,9 @@ export function NewEditor(
|
||||||
const MainContainer = new ContainerModel(
|
const MainContainer = new ContainerModel(
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
id: 'main',
|
...DEFAULT_MAINCONTAINER_PROPS,
|
||||||
parentId: 'null',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: Number(configuration.MainContainer.Width),
|
width: Number(configuration.MainContainer.Width),
|
||||||
height: Number(configuration.MainContainer.Height),
|
height: Number(configuration.MainContainer.Height)
|
||||||
isRigidBody: false,
|
|
||||||
isAnchor: false,
|
|
||||||
XPositionReference: XPositionReference.Left,
|
|
||||||
style: {
|
|
||||||
fillOpacity: 0,
|
|
||||||
stroke: 'black'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
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
|
* If the contraints fails, an error message will be returned
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
||||||
|
|
||||||
|
@ -126,8 +127,13 @@ export function constraintBodyInsideUnallocatedWidth(
|
||||||
|
|
||||||
// Check if there is still some space
|
// Check if there is still some space
|
||||||
if (availableWidths.length === 0) {
|
if (availableWidths.length === 0) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Not enough space!',
|
||||||
|
text: 'Try to free the parent a little bit!'
|
||||||
|
});
|
||||||
throw new Error(
|
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
|
// Check if the container actually fit inside
|
||||||
// It will usually fit if it was alrady fitting
|
// It will usually fit if it was alrady fitting
|
||||||
const availableWidthFound = availableWidths.find((width) =>
|
const availableWidthFound = availableWidths.find((width) =>
|
||||||
isFitting(container, width)
|
isFitting(container.properties.width, width)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (availableWidthFound === undefined) {
|
if (availableWidthFound === undefined) {
|
||||||
|
@ -163,12 +169,26 @@ export function constraintBodyInsideUnallocatedWidth(
|
||||||
|
|
||||||
// We want the container to fit automatically inside the available space
|
// We want the container to fit automatically inside the available space
|
||||||
// even if it means to resize the container
|
// 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
|
const availableWidth: ISizePointer | undefined = availableWidths.find((width) => {
|
||||||
// TODO: Actually give an option to not fit and show the error message shown below
|
return isFitting(container.properties.minWidth, width);
|
||||||
const availableWidth = availableWidths[0];
|
});
|
||||||
|
|
||||||
|
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.x = availableWidth.x;
|
||||||
container.properties.width = availableWidth.width;
|
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;
|
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
|
* Get the unallocated widths inside a container
|
||||||
* An allocated width is defined by its the widths of the children that are rigid bodies.
|
* 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 need to calculate the overlap between the two containers
|
||||||
// We only works with widths meaning in 1D (with lines)
|
// We only works with widths meaning in 1D (with lines)
|
||||||
const newUnallocatedWidths = getAvailableWidthsTwoLines(
|
const newUnallocatedWidths = getAvailableWidthsTwoLines(
|
||||||
unallocatedSpace.x,
|
unallocatedSpace,
|
||||||
unallocatedSpace.x + unallocatedSpace.width,
|
|
||||||
childX,
|
childX,
|
||||||
childX + childWidth
|
childWidth
|
||||||
);
|
);
|
||||||
|
|
||||||
// Concat the new list of SizePointer pointing to availables spaces
|
// Concat the new list of SizePointer pointing to availables spaces
|
||||||
|
@ -240,35 +270,48 @@ function getAvailableWidths(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the unallocated widths between two lines in 1D
|
* Returns the unallocated widths between two lines in 1D
|
||||||
* @param unalloctedSpaceLeft left of the first line
|
* @param unalloctedSpace unallocated space
|
||||||
* @param unallocatedSpaceRight rigth of the first line
|
* @param rectX left of the second line
|
||||||
* @param rectLeft left of the second line
|
* @param rectWidth width of the second line
|
||||||
* @param rectRight right of the second line
|
|
||||||
* @returns Available widths
|
* @returns Available widths
|
||||||
*/
|
*/
|
||||||
function getAvailableWidthsTwoLines(
|
function getAvailableWidthsTwoLines(
|
||||||
unalloctedSpaceLeft: number,
|
unallocatedSpace: ISizePointer,
|
||||||
unallocatedSpaceRight: number,
|
rectX: number,
|
||||||
rectLeft: number,
|
rectWidth: number
|
||||||
rectRight: number
|
|
||||||
): ISizePointer[] {
|
): ISizePointer[] {
|
||||||
if (unallocatedSpaceRight < rectLeft ||
|
const unallocatedSpaceRight = unallocatedSpace.x + unallocatedSpace.width;
|
||||||
unalloctedSpaceLeft > rectRight
|
const rectRight = rectX + rectWidth;
|
||||||
) {
|
|
||||||
// object 1 and 2 are not overlapping
|
const isNotOverlapping = unallocatedSpaceRight < rectX ||
|
||||||
return [{
|
unallocatedSpace.x > rectRight;
|
||||||
x: unalloctedSpaceLeft,
|
if (isNotOverlapping) {
|
||||||
width: unallocatedSpaceRight - unalloctedSpaceLeft
|
return [unallocatedSpace];
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rectLeft < unalloctedSpaceLeft && rectRight > unallocatedSpaceRight) {
|
const isOverlappingFullWidth = rectX < unallocatedSpace.x && rectRight > unallocatedSpaceRight;
|
||||||
// object 2 is overlapping full width
|
if (isOverlappingFullWidth) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOverlappingOnTheLeft = unallocatedSpace.x >= rectX;
|
||||||
|
if (isOverlappingOnTheLeft) {
|
||||||
|
return getAvailableWidthsLeft(unallocatedSpaceRight, rectRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unalloctedSpaceLeft >= rectLeft) {
|
|
||||||
// object 2 is partially overlapping on the left
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
x: rectRight,
|
x: rectRight,
|
||||||
|
@ -277,36 +320,40 @@ function getAvailableWidthsTwoLines(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rectRight >= unallocatedSpaceRight) {
|
function getAvailableWidthsRight(unallocatedSpaceX: number, rectX: number): ISizePointer[] {
|
||||||
// object 2 is partially overlapping on the right
|
if (rectX - unallocatedSpaceX <= 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
x: unalloctedSpaceLeft,
|
x: unallocatedSpaceX,
|
||||||
width: rectRight - unalloctedSpaceLeft
|
width: rectX - unallocatedSpaceX
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// object 2 is overlapping in the middle
|
function getAvailableWidthsMiddle(
|
||||||
return [
|
unallocatedSpaceX: number,
|
||||||
{
|
unallocatedSpaceRight: number,
|
||||||
x: unalloctedSpaceLeft,
|
rectX: number,
|
||||||
width: rectLeft - unalloctedSpaceLeft
|
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,
|
x: rectRight,
|
||||||
width: unallocatedSpaceRight - rectRight
|
width: unallocatedSpaceRight - rectRight
|
||||||
}
|
});
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return sizePointers;
|
||||||
* 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;
|
|
||||||
|
|
|
@ -4,10 +4,10 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||||
import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel';
|
import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import { findContainerById } from '../../utils/itertools';
|
import { findContainerById } from '../../utils/itertools';
|
||||||
import { getCurrentHistory } from './Editor';
|
import { getCurrentHistory } from './Editor';
|
||||||
import IProperties from '../../Interfaces/IProperties';
|
|
||||||
import { AddMethod } from '../../Enums/AddMethod';
|
import { AddMethod } from '../../Enums/AddMethod';
|
||||||
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
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
|
* Select a container
|
||||||
|
@ -181,12 +181,7 @@ export function AddContainer(
|
||||||
|
|
||||||
// Set the counter of the object type in order to assign an unique id
|
// Set the counter of the object type in order to assign an unique id
|
||||||
const newCounters = Object.assign({}, current.TypeCounters);
|
const newCounters = Object.assign({}, current.TypeCounters);
|
||||||
if (newCounters[type] === null ||
|
UpdateCounters(newCounters, type);
|
||||||
newCounters[type] === undefined) {
|
|
||||||
newCounters[type] = 0;
|
|
||||||
} else {
|
|
||||||
newCounters[type]++;
|
|
||||||
}
|
|
||||||
const count = newCounters[type];
|
const count = newCounters[type];
|
||||||
|
|
||||||
// Create maincontainer model
|
// Create maincontainer model
|
||||||
|
@ -203,23 +198,17 @@ export function AddContainer(
|
||||||
|
|
||||||
let x = containerConfig.DefaultX ?? 0;
|
let x = containerConfig.DefaultX ?? 0;
|
||||||
const y = containerConfig.DefaultY ?? 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);
|
x = ApplyAddMethod(index, containerConfig, parentClone, x);
|
||||||
|
|
||||||
const defaultProperties: IProperties = {
|
const defaultProperties = GetDefaultContainerProps(
|
||||||
id: `${type}-${count}`,
|
type,
|
||||||
parentId: parentClone.properties.id,
|
count,
|
||||||
|
parentClone,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width,
|
containerConfig
|
||||||
height,
|
);
|
||||||
isRigidBody: false,
|
|
||||||
isAnchor: false,
|
|
||||||
XPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
|
|
||||||
style: containerConfig.Style
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the container
|
// Create the container
|
||||||
const newContainer = new ContainerModel(
|
const newContainer = new ContainerModel(
|
||||||
|
@ -231,6 +220,8 @@ export function AddContainer(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ApplyBehaviors(newContainer);
|
||||||
|
|
||||||
// And push it the the parent children
|
// And push it the the parent children
|
||||||
if (index === parentClone.children.length) {
|
if (index === parentClone.children.length) {
|
||||||
parentClone.children.push(newContainer);
|
parentClone.children.push(newContainer);
|
||||||
|
@ -238,9 +229,11 @@ export function AddContainer(
|
||||||
parentClone.children.splice(index, 0, newContainer);
|
parentClone.children.splice(index, 0, newContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InitializeDefaultChild(configuration, containerConfig, newContainer, newCounters);
|
||||||
|
|
||||||
// Update the state
|
// Update the state
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: 'Add container',
|
LastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
|
||||||
MainContainer: clone,
|
MainContainer: clone,
|
||||||
SelectedContainer: parentClone,
|
SelectedContainer: parentClone,
|
||||||
SelectedContainerId: parentClone.properties.id,
|
SelectedContainerId: parentClone.properties.id,
|
||||||
|
@ -250,6 +243,74 @@ export function AddContainer(
|
||||||
setHistoryCurrentStep(history.length - 1);
|
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.)
|
* Returns a new offset by applying an Add method (append, insert etc.)
|
||||||
* See AddMethod
|
* See AddMethod
|
||||||
|
@ -263,6 +324,7 @@ function ApplyAddMethod(index: number, containerConfig: IAvailableContainer, par
|
||||||
if (index > 0 && (
|
if (index > 0 && (
|
||||||
containerConfig.AddMethod === undefined ||
|
containerConfig.AddMethod === undefined ||
|
||||||
containerConfig.AddMethod === AddMethod.Append)) {
|
containerConfig.AddMethod === AddMethod.Append)) {
|
||||||
|
// Append method (default)
|
||||||
const lastChild: IContainerModel | undefined = parent.children.at(index - 1);
|
const lastChild: IContainerModel | undefined = parent.children.at(index - 1);
|
||||||
|
|
||||||
if (lastChild !== undefined) {
|
if (lastChild !== undefined) {
|
||||||
|
|
|
@ -3,10 +3,8 @@ import { IContainerModel, ContainerModel } from '../../Interfaces/IContainerMode
|
||||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
import { findContainerById } from '../../utils/itertools';
|
import { findContainerById } from '../../utils/itertools';
|
||||||
import { getCurrentHistory } from './Editor';
|
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 { restoreX } from '../SVG/Elements/Container';
|
||||||
|
import { ApplyBehaviors } from './Behaviors/Behaviors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handled the property change event in the properties form
|
* Handled the property change event in the properties form
|
||||||
|
@ -44,13 +42,7 @@ export function OnPropertyChange(
|
||||||
(container.properties as any)[key] = value;
|
(container.properties as any)[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.properties.isAnchor) {
|
ApplyBehaviors(container);
|
||||||
ImposePosition(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (container.properties.isRigidBody) {
|
|
||||||
RecalculatePhysics(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Change ${key} of ${container.properties.id}`,
|
LastAction: `Change ${key} of ${container.properties.id}`,
|
||||||
|
@ -117,9 +109,8 @@ export function OnPropertiesSubmit(
|
||||||
submitCSSForm(form, styleProperty, container);
|
submitCSSForm(form, styleProperty, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.properties.isRigidBody) {
|
// Apply the behaviors
|
||||||
RecalculatePhysics(container);
|
ApplyBehaviors(container);
|
||||||
}
|
|
||||||
|
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Change properties of ${container.properties.id}`,
|
LastAction: `Change properties of ${container.properties.id}`,
|
||||||
|
@ -138,8 +129,9 @@ const submitHTMLInput = (
|
||||||
property: string,
|
property: string,
|
||||||
form: HTMLFormElement
|
form: HTMLFormElement
|
||||||
): void => {
|
): void => {
|
||||||
if (INPUT_TYPES[property] !== 'number') {
|
if (input.type !== 'number') {
|
||||||
(container.properties as any)[property] = input.value;
|
(container.properties as any)[property] = input.value;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (property === 'x') {
|
if (property === 'x') {
|
||||||
|
@ -188,7 +180,7 @@ const submitRadioButtons = (
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (INPUT_TYPES[property] === 'number') {
|
if (radiobutton.type === 'radio') {
|
||||||
(container.properties as any)[property] = Number(radiobutton.value);
|
(container.properties as any)[property] = Number(radiobutton.value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||||
import { getCircularReplacer } from '../../utils/saveload';
|
import { getCircularReplacer } from '../../utils/saveload';
|
||||||
import { ID } from '../SVG/SVG';
|
import { ID } from '../SVG/SVG';
|
||||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||||
import Worker from '../../workers/worker?worker';
|
|
||||||
|
|
||||||
export function SaveEditorAsJSON(
|
export function SaveEditorAsJSON(
|
||||||
history: IHistoryState[],
|
history: IHistoryState[],
|
||||||
|
@ -21,7 +20,7 @@ export function SaveEditorAsJSON(
|
||||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||||
if (window.Worker) {
|
if (window.Worker) {
|
||||||
// use webworker for the stringify to avoid freezing
|
// use webworker for the stringify to avoid freezing
|
||||||
const myWorker = new Worker();
|
const myWorker = new Worker('workers/worker.js');
|
||||||
myWorker.postMessage({ editorState, spaces });
|
myWorker.postMessage({ editorState, spaces });
|
||||||
myWorker.onmessage = (event) => {
|
myWorker.onmessage = (event) => {
|
||||||
const data = event.data;
|
const data = event.data;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Dispatch, SetStateAction } from 'react';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
|
import { ENABLE_SHORTCUTS } from '../../utils/default';
|
||||||
|
|
||||||
export function onKeyDown(
|
export function onKeyDown(
|
||||||
event: KeyboardEvent,
|
event: KeyboardEvent,
|
||||||
|
@ -7,6 +8,10 @@ export function onKeyDown(
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
): void {
|
): void {
|
||||||
|
if (!ENABLE_SHORTCUTS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (event.isComposing || event.keyCode === 229) {
|
if (event.isComposing || event.keyCode === 229) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -14,10 +14,12 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
properties: {
|
properties: {
|
||||||
id: 'main',
|
id: 'main',
|
||||||
parentId: null,
|
parentId: null,
|
||||||
|
displayedText: 'main',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100,
|
height: 100,
|
||||||
|
minWidth: 1,
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
isRigidBody: false,
|
isRigidBody: false,
|
||||||
isAnchor: false
|
isAnchor: false
|
||||||
|
@ -46,10 +48,12 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
properties: {
|
properties: {
|
||||||
id: 'main',
|
id: 'main',
|
||||||
parentId: '',
|
parentId: '',
|
||||||
|
displayedText: 'main',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100,
|
height: 100,
|
||||||
|
minWidth: 1,
|
||||||
isRigidBody: false,
|
isRigidBody: false,
|
||||||
isAnchor: false,
|
isAnchor: false,
|
||||||
XPositionReference: XPositionReference.Left
|
XPositionReference: XPositionReference.Left
|
||||||
|
@ -104,8 +108,10 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
properties: {
|
properties: {
|
||||||
id: 'main',
|
id: 'main',
|
||||||
parentId: '',
|
parentId: '',
|
||||||
|
displayedText: 'main',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
minWidth: 1,
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100,
|
height: 100,
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
|
@ -122,8 +128,10 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
properties: {
|
properties: {
|
||||||
id: 'child-1',
|
id: 'child-1',
|
||||||
parentId: 'main',
|
parentId: 'main',
|
||||||
|
displayedText: 'child-1',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
minWidth: 1,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
isRigidBody: false,
|
isRigidBody: false,
|
||||||
|
@ -141,8 +149,10 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
properties: {
|
properties: {
|
||||||
id: 'child-2',
|
id: 'child-2',
|
||||||
parentId: 'main',
|
parentId: 'main',
|
||||||
|
displayedText: 'child-2',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
minWidth: 1,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
|
@ -180,8 +190,10 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
properties: {
|
properties: {
|
||||||
id: 'main',
|
id: 'main',
|
||||||
parentId: '',
|
parentId: '',
|
||||||
|
displayedText: 'main',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
minWidth: 1,
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100,
|
height: 100,
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
|
@ -197,8 +209,10 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
properties: {
|
properties: {
|
||||||
id: 'child-1',
|
id: 'child-1',
|
||||||
parentId: 'main',
|
parentId: 'main',
|
||||||
|
displayedText: 'child-1',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
minWidth: 1,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
|
|
|
@ -83,7 +83,9 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
||||||
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 = '|\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 &&
|
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
|
||||||
|
@ -116,7 +118,7 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
||||||
</div>
|
</div>
|
||||||
<div ref={elementRef} className='h-96 text-gray-800'>
|
<div ref={elementRef} className='h-96 text-gray-800'>
|
||||||
<List
|
<List
|
||||||
className='List'
|
className='List divide-y divide-black'
|
||||||
itemCount={containers.length}
|
itemCount={containers.length}
|
||||||
itemSize={35}
|
itemSize={35}
|
||||||
height={384}
|
height={384}
|
||||||
|
|
|
@ -11,6 +11,7 @@ interface IInputGroupProps {
|
||||||
checked?: boolean
|
checked?: boolean
|
||||||
defaultValue?: string
|
defaultValue?: string
|
||||||
defaultChecked?: boolean
|
defaultChecked?: boolean
|
||||||
|
min?: number
|
||||||
isDisabled?: boolean
|
isDisabled?: boolean
|
||||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
}
|
}
|
||||||
|
@ -42,6 +43,7 @@ export const InputGroup: React.FunctionComponent<IInputGroupProps> = (props) =>
|
||||||
checked={props.checked}
|
checked={props.checked}
|
||||||
defaultChecked={props.defaultChecked}
|
defaultChecked={props.defaultChecked}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
|
min={props.min}
|
||||||
disabled={props.isDisabled}
|
disabled={props.isDisabled}
|
||||||
/>
|
/>
|
||||||
</>;
|
</>;
|
||||||
|
|
|
@ -52,6 +52,15 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
|
||||||
value={props.properties.parentId?.toString()}
|
value={props.properties.parentId?.toString()}
|
||||||
isDisabled={true}
|
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
|
<InputGroup
|
||||||
labelText='x'
|
labelText='x'
|
||||||
inputKey='x'
|
inputKey='x'
|
||||||
|
@ -70,12 +79,23 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
|
||||||
value={props.properties.y.toString()}
|
value={props.properties.y.toString()}
|
||||||
onChange={(event) => props.onChange('y', Number(event.target.value))}
|
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
|
<InputGroup
|
||||||
labelText='Width'
|
labelText='Width'
|
||||||
inputKey='width'
|
inputKey='width'
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
|
min={props.properties.minWidth}
|
||||||
value={props.properties.width.toString()}
|
value={props.properties.width.toString()}
|
||||||
onChange={(event) => props.onChange('width', Number(event.target.value))}
|
onChange={(event) => props.onChange('width', Number(event.target.value))}
|
||||||
/>
|
/>
|
||||||
|
@ -85,6 +105,7 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
|
min={0}
|
||||||
value={props.properties.height.toString()}
|
value={props.properties.height.toString()}
|
||||||
onChange={(event) => props.onChange('height', Number(event.target.value))}
|
onChange={(event) => props.onChange('height', Number(event.target.value))}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -23,10 +23,12 @@ describe.concurrent('Properties', () => {
|
||||||
const prop: IProperties = {
|
const prop: IProperties = {
|
||||||
id: 'stuff',
|
id: 'stuff',
|
||||||
parentId: 'parentId',
|
parentId: 'parentId',
|
||||||
|
displayedText: 'stuff',
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 1,
|
y: 1,
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 1,
|
height: 1,
|
||||||
|
minWidth: 1,
|
||||||
XPositionReference: XPositionReference.Left,
|
XPositionReference: XPositionReference.Left,
|
||||||
isRigidBody: false,
|
isRigidBody: false,
|
||||||
isAnchor: 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()}
|
defaultValue={props.properties.parentId?.toString()}
|
||||||
isDisabled={true}
|
isDisabled={true}
|
||||||
/>
|
/>
|
||||||
|
<InputGroup
|
||||||
|
labelText='Displayed text'
|
||||||
|
inputKey='displayedText'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
type='string'
|
||||||
|
defaultValue={props.properties.displayedText?.toString()}
|
||||||
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='x'
|
labelText='x'
|
||||||
inputKey='x'
|
inputKey='x'
|
||||||
|
@ -68,12 +76,22 @@ const StaticForm: React.FunctionComponent<IStaticFormProps> = (props) => {
|
||||||
type='number'
|
type='number'
|
||||||
defaultValue={props.properties.y.toString()}
|
defaultValue={props.properties.y.toString()}
|
||||||
/>
|
/>
|
||||||
|
<InputGroup
|
||||||
|
labelText='Minimum width'
|
||||||
|
inputKey='minWidth'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
type='number'
|
||||||
|
min={0}
|
||||||
|
defaultValue={props.properties.minWidth.toString()}
|
||||||
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Width'
|
labelText='Width'
|
||||||
inputKey='width'
|
inputKey='width'
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
|
min={props.properties.minWidth}
|
||||||
defaultValue={props.properties.width.toString()}
|
defaultValue={props.properties.width.toString()}
|
||||||
/>
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
|
@ -82,6 +100,7 @@ const StaticForm: React.FunctionComponent<IStaticFormProps> = (props) => {
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
|
min={1}
|
||||||
defaultValue={props.properties.height.toString()}
|
defaultValue={props.properties.height.toString()}
|
||||||
/>
|
/>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { Interweave, Node } from 'interweave';
|
||||||
import { XPositionReference } from '../../../Enums/XPositionReference';
|
import { XPositionReference } from '../../../Enums/XPositionReference';
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
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 { getDepth } from '../../../utils/itertools';
|
||||||
import { Dimension } from './Dimension';
|
import { Dimension } from './Dimension';
|
||||||
|
import IProperties from '../../../Interfaces/IProperties';
|
||||||
|
|
||||||
interface IContainerProps {
|
interface IContainerProps {
|
||||||
model: IContainerModel
|
model: IContainerModel
|
||||||
|
@ -33,6 +35,14 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
props.model.properties.style
|
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
|
// Dimension props
|
||||||
const depth = getDepth(props.model);
|
const depth = getDepth(props.model);
|
||||||
const dimensionMargin = DIMENSION_MARGIN * (depth + 1);
|
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();
|
const text = (props.model.properties.width ?? 0).toString();
|
||||||
|
|
||||||
let dimensionChildren: JSX.Element | null = null;
|
let dimensionChildren: JSX.Element | null = null;
|
||||||
if (props.model.children.length > 1) {
|
if (props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
|
||||||
const {
|
const {
|
||||||
childrenId,
|
childrenId,
|
||||||
xChildrenStart,
|
xChildrenStart,
|
||||||
|
@ -69,7 +79,8 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
transform={transform}
|
transform={transform}
|
||||||
key={`container-${props.model.properties.id}`}
|
key={`container-${props.model.properties.id}`}
|
||||||
>
|
>
|
||||||
<Dimension
|
{ SHOW_PARENT_DIMENSION
|
||||||
|
? <Dimension
|
||||||
id={id}
|
id={id}
|
||||||
xStart={xStart}
|
xStart={xStart}
|
||||||
xEnd={xEnd}
|
xEnd={xEnd}
|
||||||
|
@ -78,19 +89,18 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
strokeWidth={strokeWidth}
|
strokeWidth={strokeWidth}
|
||||||
text={text}
|
text={text}
|
||||||
/>
|
/>
|
||||||
|
: null
|
||||||
|
}
|
||||||
{ dimensionChildren }
|
{ dimensionChildren }
|
||||||
<rect
|
{ svg }
|
||||||
width={props.model.properties.width}
|
{ SHOW_TEXT
|
||||||
height={props.model.properties.height}
|
? <text
|
||||||
style={style}
|
|
||||||
>
|
|
||||||
</rect>
|
|
||||||
<text
|
|
||||||
x={xText}
|
x={xText}
|
||||||
y={yText}
|
y={yText}
|
||||||
>
|
>
|
||||||
{props.model.properties.id}
|
{props.model.properties.displayedText}
|
||||||
</text>
|
</text>
|
||||||
|
: null }
|
||||||
{ containersElements }
|
{ containersElements }
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
@ -141,3 +151,63 @@ export function restoreX(x: number, width: number, xPositionReference = XPositio
|
||||||
}
|
}
|
||||||
return transformedX;
|
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 { ContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import { Selector } from './Elements/Selector';
|
import { Selector } from './Elements/Selector';
|
||||||
import { BAR_WIDTH } from '../Bar/Bar';
|
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 {
|
interface ISVGProps {
|
||||||
width: number
|
width: number
|
||||||
|
@ -74,6 +77,11 @@ export const SVG: React.FC<ISVGProps> = (props: ISVGProps) => {
|
||||||
<svg {...properties}>
|
<svg {...properties}>
|
||||||
{ children }
|
{ children }
|
||||||
<Selector selected={props.selected} />
|
<Selector selected={props.selected} />
|
||||||
|
{
|
||||||
|
SHOW_DIMENSIONS_PER_DEPTH
|
||||||
|
? <DepthDimensionLayer roots={props.children}/>
|
||||||
|
: null
|
||||||
|
}
|
||||||
</svg>
|
</svg>
|
||||||
</UncontrolledReactSVGPanZoom>
|
</UncontrolledReactSVGPanZoom>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,11 +5,15 @@ import { XPositionReference } from '../Enums/XPositionReference';
|
||||||
/** Model of available container used in application configuration */
|
/** Model of available container used in application configuration */
|
||||||
export interface IAvailableContainer {
|
export interface IAvailableContainer {
|
||||||
Type: string
|
Type: string
|
||||||
Width?: number
|
|
||||||
Height?: number
|
|
||||||
DefaultX?: number
|
DefaultX?: number
|
||||||
DefaultY?: number
|
DefaultY?: number
|
||||||
|
Width?: number
|
||||||
|
Height?: number
|
||||||
|
MinWidth?: number
|
||||||
AddMethod?: AddMethod
|
AddMethod?: AddMethod
|
||||||
XPositionReference?: XPositionReference
|
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
|
* 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 {
|
export default interface IProperties {
|
||||||
|
/** id of the container */
|
||||||
id: string
|
id: string
|
||||||
|
|
||||||
|
/** id of the parent container (null when there is no parent) */
|
||||||
parentId: string | null
|
parentId: string | null
|
||||||
|
|
||||||
|
/** Text displayed in the container */
|
||||||
|
displayedText: string
|
||||||
|
|
||||||
|
/** horizontal offset */
|
||||||
x: number
|
x: number
|
||||||
|
|
||||||
|
/** vertical offset */
|
||||||
y: number
|
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
|
width: number
|
||||||
|
|
||||||
|
/** height */
|
||||||
height: number
|
height: number
|
||||||
|
|
||||||
|
/** true if rigid, false otherwise */
|
||||||
isRigidBody: boolean
|
isRigidBody: boolean
|
||||||
|
|
||||||
|
/** true if anchor, false otherwise */
|
||||||
isAnchor: boolean
|
isAnchor: boolean
|
||||||
|
|
||||||
|
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
||||||
XPositionReference: XPositionReference
|
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
|
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 { XPositionReference } from '../Enums/XPositionReference';
|
||||||
|
import { IAvailableContainer } from '../Interfaces/IAvailableContainer';
|
||||||
import { IConfiguration } from '../Interfaces/IConfiguration';
|
import { IConfiguration } from '../Interfaces/IConfiguration';
|
||||||
|
import { IContainerModel } from '../Interfaces/IContainerModel';
|
||||||
import IProperties from '../Interfaces/IProperties';
|
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 = {
|
export const DEFAULT_CONFIG: IConfiguration = {
|
||||||
AvailableContainers: [
|
AvailableContainers: [
|
||||||
{
|
{
|
||||||
|
@ -29,8 +50,10 @@ export const DEFAULT_CONFIG: IConfiguration = {
|
||||||
export const DEFAULT_MAINCONTAINER_PROPS: IProperties = {
|
export const DEFAULT_MAINCONTAINER_PROPS: IProperties = {
|
||||||
id: 'main',
|
id: 'main',
|
||||||
parentId: 'null',
|
parentId: 'null',
|
||||||
|
displayedText: 'main',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
minWidth: 1,
|
||||||
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,
|
isRigidBody: false,
|
||||||
|
@ -42,7 +65,26 @@ export const DEFAULT_MAINCONTAINER_PROPS: IProperties = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DIMENSION_MARGIN = 50;
|
export const GetDefaultContainerProps = (
|
||||||
export const NOTCHES_LENGTH = 4;
|
type: string,
|
||||||
|
typeCount: number,
|
||||||
export const MAX_HISTORY = 200;
|
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
|
||||||
* @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 => {
|
export const getCircularReplacer = (): (key: any, value: object | null) => object | null | undefined => {
|
||||||
const seen = new WeakSet();
|
|
||||||
return (key: any, value: object | null) => {
|
return (key: any, value: object | null) => {
|
||||||
if (key === 'parent') {
|
if (key === 'parent') {
|
||||||
return;
|
return;
|
||||||
|
@ -52,12 +51,6 @@ export const getCircularReplacer = (): (key: any, value: object | null) => objec
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === 'object' && value !== null) {
|
|
||||||
if (seen.has(value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
seen.add(value);
|
|
||||||
}
|
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -54,12 +54,13 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
{
|
{
|
||||||
Type: 'Chassis',
|
Type: 'Chassis',
|
||||||
Width: 500,
|
Width: 500,
|
||||||
|
MinWidth: 200,
|
||||||
|
DefaultChildType: 'Trou',
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 1,
|
fillOpacity: 1,
|
||||||
borderWidth: 2,
|
strokeWidth: 2,
|
||||||
stroke: 'red',
|
stroke: 'red',
|
||||||
fill: '#78350F',
|
fill: '#d3c9b7',
|
||||||
stroke: 'red'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -68,20 +69,33 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
DefaultY: 10,
|
DefaultY: 10,
|
||||||
Width: 480,
|
Width: 480,
|
||||||
Height: 180,
|
Height: 180,
|
||||||
|
DefaultChildType: 'Remplissage',
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 1,
|
fillOpacity: 1,
|
||||||
borderWidth: 2,
|
strokeWidth: 2,
|
||||||
stroke: 'green',
|
stroke: 'green',
|
||||||
fill: 'white'
|
fill: 'white'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: 'Remplissage',
|
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: {
|
Style: {
|
||||||
fillOpacity: 1,
|
fillOpacity: 1,
|
||||||
borderWidth: 2,
|
strokeWidth: 1,
|
||||||
stroke: '#bfdbfe',
|
|
||||||
fill: '#bfdbfe'
|
fill: '#bfdbfe'
|
||||||
|
},
|
||||||
|
UserData: {
|
||||||
|
styleLine: {
|
||||||
|
transform: "scaleY(0.5) translateY(100%)",
|
||||||
|
transformBox: "fill-box"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -90,7 +104,7 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
XPositionReference: 1,
|
XPositionReference: 1,
|
||||||
Style: {
|
Style: {
|
||||||
fillOpacity: 0,
|
fillOpacity: 0,
|
||||||
borderWidth: 2,
|
strokeWidth: 2,
|
||||||
stroke: '#713f12',
|
stroke: '#713f12',
|
||||||
fill: '#713f12',
|
fill: '#713f12',
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx"
|
||||||
},
|
},
|
||||||
"include": ["src", "src/workers"],
|
"include": ["src"],
|
||||||
"exclude": ["test-server"],
|
"exclude": ["test-server"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue