Compare commits
No commits in common. "3221d97eff4b7cf95b668d3434165b6324a21293" and "0452a1745420579c4051842da8352be6a0fe5103" have entirely different histories.
3221d97eff
...
0452a17454
39 changed files with 805 additions and 30958 deletions
|
@ -7,7 +7,7 @@ steps:
|
|||
image: node:16
|
||||
commands:
|
||||
- node ./test-server/node-http.js &
|
||||
- npm i
|
||||
- npm ci
|
||||
- npm run test:nowatch
|
||||
- npm run build
|
||||
|
||||
|
@ -20,6 +20,6 @@ steps:
|
|||
image: node
|
||||
commands:
|
||||
- node ./test-server/node-http.js &
|
||||
- npm i
|
||||
- npm ci
|
||||
- npm run test:nowatch
|
||||
- npm run build
|
|
@ -31,8 +31,6 @@ 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-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)
|
||||
- [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
|
||||
|
||||
|
||||
# [Vite](https://vitejs.dev/)
|
||||
|
@ -49,12 +49,6 @@ 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
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
> 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,8 +6,7 @@ The project is structured this way
|
|||
.
|
||||
├── docs/ Documentation folder
|
||||
├── public/ Public folder in which the index.html
|
||||
│ │ import its resources
|
||||
│ └── workers/ Webworkers folder
|
||||
│ import its resources
|
||||
├── src/ Source folder for the react app
|
||||
│ ├── assets/ Assets folder in which the react app
|
||||
│ │ import its resources
|
||||
|
@ -18,6 +17,7 @@ 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,13 +14,10 @@
|
|||
},
|
||||
"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",
|
||||
"sweetalert2": "^11.4.28",
|
||||
"sweetalert2-react-content": "^5.0.3"
|
||||
"react-window": "^1.8.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "^8.16.1",
|
||||
|
|
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
|
@ -24,7 +24,6 @@ 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
|
||||
|
@ -32,8 +31,6 @@ 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
|
||||
|
@ -41,13 +38,10 @@ 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
|
||||
|
@ -1528,10 +1522,6 @@ 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'}
|
||||
|
@ -2187,15 +2177,6 @@ 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:
|
||||
|
@ -3202,22 +3183,6 @@ 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,7 +5,6 @@ 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>>,
|
||||
|
@ -18,9 +17,19 @@ export function NewEditor(
|
|||
const MainContainer = new ContainerModel(
|
||||
null,
|
||||
{
|
||||
...DEFAULT_MAINCONTAINER_PROPS,
|
||||
id: 'main',
|
||||
parentId: 'null',
|
||||
x: 0,
|
||||
y: 0,
|
||||
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'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
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,7 +6,6 @@
|
|||
* If the contraints fails, an error message will be returned
|
||||
*/
|
||||
|
||||
import Swal from 'sweetalert2';
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
||||
|
||||
|
@ -127,13 +126,8 @@ 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 bit.'
|
||||
'No available space found on the parent container. Try to free the parent a little before placing it inside.'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -158,7 +152,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.properties.width, width)
|
||||
isFitting(container, width)
|
||||
);
|
||||
|
||||
if (availableWidthFound === undefined) {
|
||||
|
@ -169,26 +163,12 @@ export function constraintBodyInsideUnallocatedWidth(
|
|||
|
||||
// We want the container to fit automatically inside the available space
|
||||
// even if it means to resize the container
|
||||
const availableWidth: ISizePointer | undefined = availableWidths.find((width) => {
|
||||
return isFitting(container.properties.minWidth, width);
|
||||
});
|
||||
|
||||
if (availableWidth === undefined) {
|
||||
console.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;
|
||||
}
|
||||
|
||||
// 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];
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -201,17 +181,6 @@ 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.
|
||||
|
@ -253,9 +222,10 @@ 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,
|
||||
unallocatedSpace.x,
|
||||
unallocatedSpace.x + unallocatedSpace.width,
|
||||
childX,
|
||||
childWidth
|
||||
childX + childWidth
|
||||
);
|
||||
|
||||
// Concat the new list of SizePointer pointing to availables spaces
|
||||
|
@ -270,90 +240,73 @@ function getAvailableWidths(
|
|||
|
||||
/**
|
||||
* Returns the unallocated widths between two lines in 1D
|
||||
* @param unalloctedSpace unallocated space
|
||||
* @param rectX left of the second line
|
||||
* @param rectWidth width of the second line
|
||||
* @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
|
||||
* @returns Available widths
|
||||
*/
|
||||
function getAvailableWidthsTwoLines(
|
||||
unallocatedSpace: ISizePointer,
|
||||
rectX: number,
|
||||
rectWidth: number
|
||||
): ISizePointer[] {
|
||||
const unallocatedSpaceRight = unallocatedSpace.x + unallocatedSpace.width;
|
||||
const rectRight = rectX + rectWidth;
|
||||
|
||||
const isNotOverlapping = unallocatedSpaceRight < rectX ||
|
||||
unallocatedSpace.x > rectRight;
|
||||
if (isNotOverlapping) {
|
||||
return [unallocatedSpace];
|
||||
}
|
||||
|
||||
const isOverlappingFullWidth = rectX < unallocatedSpace.x && rectRight > unallocatedSpaceRight;
|
||||
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 [
|
||||
{
|
||||
x: rectRight,
|
||||
width: unallocatedSpaceRight - rectRight
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function getAvailableWidthsRight(unallocatedSpaceX: number, rectX: number): ISizePointer[] {
|
||||
if (rectX - unallocatedSpaceX <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
x: unallocatedSpaceX,
|
||||
width: rectX - unallocatedSpaceX
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function getAvailableWidthsMiddle(
|
||||
unallocatedSpaceX: number,
|
||||
unalloctedSpaceLeft: number,
|
||||
unallocatedSpaceRight: number,
|
||||
rectX: number,
|
||||
rectLeft: number,
|
||||
rectRight: number
|
||||
): ISizePointer[] {
|
||||
const sizePointers: ISizePointer[] = [];
|
||||
|
||||
if (rectX - unallocatedSpaceX > 0) {
|
||||
sizePointers.push({
|
||||
x: unallocatedSpaceX,
|
||||
width: rectX - unallocatedSpaceX
|
||||
});
|
||||
if (unallocatedSpaceRight < rectLeft ||
|
||||
unalloctedSpaceLeft > rectRight
|
||||
) {
|
||||
// object 1 and 2 are not overlapping
|
||||
return [{
|
||||
x: unalloctedSpaceLeft,
|
||||
width: unallocatedSpaceRight - unalloctedSpaceLeft
|
||||
}];
|
||||
}
|
||||
|
||||
if (unallocatedSpaceRight - rectRight > 0) {
|
||||
sizePointers.push({
|
||||
if (rectLeft < unalloctedSpaceLeft && rectRight > unallocatedSpaceRight) {
|
||||
// object 2 is overlapping full width
|
||||
return [];
|
||||
}
|
||||
|
||||
if (unalloctedSpaceLeft >= rectLeft) {
|
||||
// object 2 is partially overlapping on the left
|
||||
return [
|
||||
{
|
||||
x: rectRight,
|
||||
width: unallocatedSpaceRight - rectRight
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (rectRight >= unallocatedSpaceRight) {
|
||||
// object 2 is partially overlapping on the right
|
||||
return [
|
||||
{
|
||||
x: unalloctedSpaceLeft,
|
||||
width: rectRight - unalloctedSpaceLeft
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// object 2 is overlapping in the middle
|
||||
return [
|
||||
{
|
||||
x: unalloctedSpaceLeft,
|
||||
width: rectLeft - unalloctedSpaceLeft
|
||||
},
|
||||
{
|
||||
x: 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 { findContainerById } from '../../utils/itertools';
|
||||
import { getCurrentHistory } from './Editor';
|
||||
import IProperties from '../../Interfaces/IProperties';
|
||||
import { AddMethod } from '../../Enums/AddMethod';
|
||||
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
||||
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTYPE_MAX_DEPTH } from '../../utils/default';
|
||||
import { ApplyBehaviors } from './Behaviors/Behaviors';
|
||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||
|
||||
/**
|
||||
* Select a container
|
||||
|
@ -181,7 +181,12 @@ export function AddContainer(
|
|||
|
||||
// Set the counter of the object type in order to assign an unique id
|
||||
const newCounters = Object.assign({}, current.TypeCounters);
|
||||
UpdateCounters(newCounters, type);
|
||||
if (newCounters[type] === null ||
|
||||
newCounters[type] === undefined) {
|
||||
newCounters[type] = 0;
|
||||
} else {
|
||||
newCounters[type]++;
|
||||
}
|
||||
const count = newCounters[type];
|
||||
|
||||
// Create maincontainer model
|
||||
|
@ -198,17 +203,23 @@ 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 = GetDefaultContainerProps(
|
||||
type,
|
||||
count,
|
||||
parentClone,
|
||||
const defaultProperties: IProperties = {
|
||||
id: `${type}-${count}`,
|
||||
parentId: parentClone.properties.id,
|
||||
x,
|
||||
y,
|
||||
containerConfig
|
||||
);
|
||||
width,
|
||||
height,
|
||||
isRigidBody: false,
|
||||
isAnchor: false,
|
||||
XPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
|
||||
style: containerConfig.Style
|
||||
};
|
||||
|
||||
// Create the container
|
||||
const newContainer = new ContainerModel(
|
||||
|
@ -220,8 +231,6 @@ export function AddContainer(
|
|||
}
|
||||
);
|
||||
|
||||
ApplyBehaviors(newContainer);
|
||||
|
||||
// And push it the the parent children
|
||||
if (index === parentClone.children.length) {
|
||||
parentClone.children.push(newContainer);
|
||||
|
@ -229,11 +238,9 @@ export function AddContainer(
|
|||
parentClone.children.splice(index, 0, newContainer);
|
||||
}
|
||||
|
||||
InitializeDefaultChild(configuration, containerConfig, newContainer, newCounters);
|
||||
|
||||
// Update the state
|
||||
history.push({
|
||||
LastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
|
||||
LastAction: 'Add container',
|
||||
MainContainer: clone,
|
||||
SelectedContainer: parentClone,
|
||||
SelectedContainerId: parentClone.properties.id,
|
||||
|
@ -243,74 +250,6 @@ 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
|
||||
|
@ -324,7 +263,6 @@ 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,8 +3,10 @@ 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
|
||||
|
@ -42,7 +44,13 @@ export function OnPropertyChange(
|
|||
(container.properties as any)[key] = value;
|
||||
}
|
||||
|
||||
ApplyBehaviors(container);
|
||||
if (container.properties.isAnchor) {
|
||||
ImposePosition(container);
|
||||
}
|
||||
|
||||
if (container.properties.isRigidBody) {
|
||||
RecalculatePhysics(container);
|
||||
}
|
||||
|
||||
history.push({
|
||||
LastAction: `Change ${key} of ${container.properties.id}`,
|
||||
|
@ -109,8 +117,9 @@ export function OnPropertiesSubmit(
|
|||
submitCSSForm(form, styleProperty, container);
|
||||
}
|
||||
|
||||
// Apply the behaviors
|
||||
ApplyBehaviors(container);
|
||||
if (container.properties.isRigidBody) {
|
||||
RecalculatePhysics(container);
|
||||
}
|
||||
|
||||
history.push({
|
||||
LastAction: `Change properties of ${container.properties.id}`,
|
||||
|
@ -129,9 +138,8 @@ const submitHTMLInput = (
|
|||
property: string,
|
||||
form: HTMLFormElement
|
||||
): void => {
|
||||
if (input.type !== 'number') {
|
||||
if (INPUT_TYPES[property] !== 'number') {
|
||||
(container.properties as any)[property] = input.value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (property === 'x') {
|
||||
|
@ -180,7 +188,7 @@ const submitRadioButtons = (
|
|||
return;
|
||||
}
|
||||
|
||||
if (radiobutton.type === 'radio') {
|
||||
if (INPUT_TYPES[property] === 'number') {
|
||||
(container.properties as any)[property] = Number(radiobutton.value);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ 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[],
|
||||
|
@ -20,7 +21,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('workers/worker.js');
|
||||
const myWorker = new Worker();
|
||||
myWorker.postMessage({ editorState, spaces });
|
||||
myWorker.onmessage = (event) => {
|
||||
const data = event.data;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||
import { ENABLE_SHORTCUTS } from '../../utils/default';
|
||||
|
||||
export function onKeyDown(
|
||||
event: KeyboardEvent,
|
||||
|
@ -8,10 +7,6 @@ export function onKeyDown(
|
|||
historyCurrentStep: number,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||
): void {
|
||||
if (!ENABLE_SHORTCUTS) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
if (event.isComposing || event.keyCode === 229) {
|
||||
return;
|
||||
|
|
|
@ -14,12 +14,10 @@ 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
|
||||
|
@ -48,12 +46,10 @@ 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
|
||||
|
@ -108,10 +104,8 @@ describe.concurrent('Elements sidebar', () => {
|
|||
properties: {
|
||||
id: 'main',
|
||||
parentId: '',
|
||||
displayedText: 'main',
|
||||
x: 0,
|
||||
y: 0,
|
||||
minWidth: 1,
|
||||
width: 2000,
|
||||
height: 100,
|
||||
XPositionReference: XPositionReference.Left,
|
||||
|
@ -128,10 +122,8 @@ 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,
|
||||
|
@ -149,10 +141,8 @@ 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,
|
||||
|
@ -190,10 +180,8 @@ describe.concurrent('Elements sidebar', () => {
|
|||
properties: {
|
||||
id: 'main',
|
||||
parentId: '',
|
||||
displayedText: 'main',
|
||||
x: 0,
|
||||
y: 0,
|
||||
minWidth: 1,
|
||||
width: 2000,
|
||||
height: 100,
|
||||
XPositionReference: XPositionReference.Left,
|
||||
|
@ -209,10 +197,8 @@ 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,9 +83,7 @@ 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 = container.properties.displayedText === key
|
||||
? `${'|\t'.repeat(depth)} ${key}`
|
||||
: `${'|\t'.repeat(depth)} ${container.properties.displayedText} (${key})`;
|
||||
const text = '|\t'.repeat(depth) + key;
|
||||
const selectedClass: string = props.SelectedContainer !== undefined &&
|
||||
props.SelectedContainer !== null &&
|
||||
props.SelectedContainer.properties.id === container.properties.id
|
||||
|
@ -118,7 +116,7 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
|||
</div>
|
||||
<div ref={elementRef} className='h-96 text-gray-800'>
|
||||
<List
|
||||
className='List divide-y divide-black'
|
||||
className='List'
|
||||
itemCount={containers.length}
|
||||
itemSize={35}
|
||||
height={384}
|
||||
|
|
|
@ -11,7 +11,6 @@ interface IInputGroupProps {
|
|||
checked?: boolean
|
||||
defaultValue?: string
|
||||
defaultChecked?: boolean
|
||||
min?: number
|
||||
isDisabled?: boolean
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
}
|
||||
|
@ -43,7 +42,6 @@ export const InputGroup: React.FunctionComponent<IInputGroupProps> = (props) =>
|
|||
checked={props.checked}
|
||||
defaultChecked={props.defaultChecked}
|
||||
onChange={props.onChange}
|
||||
min={props.min}
|
||||
disabled={props.isDisabled}
|
||||
/>
|
||||
</>;
|
||||
|
|
|
@ -52,15 +52,6 @@ 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'
|
||||
|
@ -79,23 +70,12 @@ 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))}
|
||||
/>
|
||||
|
@ -105,7 +85,6 @@ 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,12 +23,10 @@ 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
|
||||
|
|
9
src/Components/Properties/PropertiesInputTypes.tsx
Normal file
9
src/Components/Properties/PropertiesInputTypes.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
export const INPUT_TYPES: Record<string, string> = {
|
||||
x: 'number',
|
||||
y: 'number',
|
||||
width: 'number',
|
||||
height: 'number',
|
||||
isRigidBody: 'checkbox',
|
||||
isAnchor: 'checkbox',
|
||||
XPositionReference: 'number'
|
||||
};
|
|
@ -52,14 +52,6 @@ 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'
|
||||
|
@ -76,22 +68,12 @@ 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
|
||||
|
@ -100,7 +82,6 @@ const StaticForm: React.FunctionComponent<IStaticFormProps> = (props) => {
|
|||
labelClassName=''
|
||||
inputClassName=''
|
||||
type='number'
|
||||
min={1}
|
||||
defaultValue={props.properties.height.toString()}
|
||||
/>
|
||||
<InputGroup
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import * as React from 'react';
|
||||
import { Interweave, Node } from 'interweave';
|
||||
import { XPositionReference } from '../../../Enums/XPositionReference';
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { DIMENSION_MARGIN, SHOW_CHILDREN_DIMENSIONS, SHOW_PARENT_DIMENSION, SHOW_TEXT } from '../../../utils/default';
|
||||
import { DIMENSION_MARGIN } from '../../../utils/default';
|
||||
import { getDepth } from '../../../utils/itertools';
|
||||
import { Dimension } from './Dimension';
|
||||
import IProperties from '../../../Interfaces/IProperties';
|
||||
|
||||
interface IContainerProps {
|
||||
model: IContainerModel
|
||||
|
@ -35,14 +33,6 @@ 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);
|
||||
|
@ -54,7 +44,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 && SHOW_CHILDREN_DIMENSIONS) {
|
||||
if (props.model.children.length > 1) {
|
||||
const {
|
||||
childrenId,
|
||||
xChildrenStart,
|
||||
|
@ -79,28 +69,28 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
|||
transform={transform}
|
||||
key={`container-${props.model.properties.id}`}
|
||||
>
|
||||
{ SHOW_PARENT_DIMENSION
|
||||
? <Dimension
|
||||
id={id}
|
||||
xStart={xStart}
|
||||
xEnd={xEnd}
|
||||
yStart={y}
|
||||
yEnd={y}
|
||||
strokeWidth={strokeWidth}
|
||||
text={text}
|
||||
/>
|
||||
: null
|
||||
}
|
||||
<Dimension
|
||||
id={id}
|
||||
xStart={xStart}
|
||||
xEnd={xEnd}
|
||||
yStart={y}
|
||||
yEnd={y}
|
||||
strokeWidth={strokeWidth}
|
||||
text={text}
|
||||
/>
|
||||
{ dimensionChildren }
|
||||
{ svg }
|
||||
{ SHOW_TEXT
|
||||
? <text
|
||||
x={xText}
|
||||
y={yText}
|
||||
>
|
||||
{props.model.properties.displayedText}
|
||||
</text>
|
||||
: null }
|
||||
<rect
|
||||
width={props.model.properties.width}
|
||||
height={props.model.properties.height}
|
||||
style={style}
|
||||
>
|
||||
</rect>
|
||||
<text
|
||||
x={xText}
|
||||
y={yText}
|
||||
>
|
||||
{props.model.properties.id}
|
||||
</text>
|
||||
{ containersElements }
|
||||
</g>
|
||||
);
|
||||
|
@ -151,63 +141,3 @@ 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('');
|
||||
}
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
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} />
|
||||
);
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
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,9 +4,6 @@ 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
|
||||
|
@ -77,11 +74,6 @@ 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,15 +5,11 @@ import { XPositionReference } from '../Enums/XPositionReference';
|
|||
/** Model of available container used in application configuration */
|
||||
export interface IAvailableContainer {
|
||||
Type: string
|
||||
DefaultX?: number
|
||||
DefaultY?: number
|
||||
Width?: number
|
||||
Height?: number
|
||||
MinWidth?: number
|
||||
DefaultX?: number
|
||||
DefaultY?: number
|
||||
AddMethod?: AddMethod
|
||||
XPositionReference?: XPositionReference
|
||||
CustomSVG?: string
|
||||
DefaultChildType?: string
|
||||
Style?: React.CSSProperties
|
||||
UserData?: object
|
||||
Style: React.CSSProperties
|
||||
}
|
||||
|
|
|
@ -3,70 +3,22 @@ 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
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
436
src/tests/resources/rigidbodytest.json
Normal file
436
src/tests/resources/rigidbodytest.json
Normal file
|
@ -0,0 +1,436 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
182
src/tests/resources/state.json
Normal file
182
src/tests/resources/state.json
Normal file
|
@ -0,0 +1,182 @@
|
|||
{
|
||||
"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,28 +1,7 @@
|
|||
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: [
|
||||
{
|
||||
|
@ -50,10 +29,8 @@ 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,
|
||||
|
@ -65,26 +42,7 @@ export const DEFAULT_MAINCONTAINER_PROPS: IProperties = {
|
|||
}
|
||||
};
|
||||
|
||||
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)
|
||||
});
|
||||
export const DIMENSION_MARGIN = 50;
|
||||
export const NOTCHES_LENGTH = 4;
|
||||
|
||||
export const MAX_HISTORY = 200;
|
||||
|
|
|
@ -22,34 +22,6 @@ 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,6 +42,7 @@ 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;
|
||||
|
@ -51,6 +52,12 @@ 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,13 +54,12 @@ const GetSVGLayoutConfiguration = () => {
|
|||
{
|
||||
Type: 'Chassis',
|
||||
Width: 500,
|
||||
MinWidth: 200,
|
||||
DefaultChildType: 'Trou',
|
||||
Style: {
|
||||
fillOpacity: 1,
|
||||
strokeWidth: 2,
|
||||
borderWidth: 2,
|
||||
stroke: 'red',
|
||||
fill: '#d3c9b7',
|
||||
fill: '#78350F',
|
||||
stroke: 'red'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -69,33 +68,20 @@ const GetSVGLayoutConfiguration = () => {
|
|||
DefaultY: 10,
|
||||
Width: 480,
|
||||
Height: 180,
|
||||
DefaultChildType: 'Remplissage',
|
||||
Style: {
|
||||
fillOpacity: 1,
|
||||
strokeWidth: 2,
|
||||
borderWidth: 2,
|
||||
stroke: 'green',
|
||||
fill: 'white'
|
||||
}
|
||||
},
|
||||
{
|
||||
Type: 'Remplissage',
|
||||
CustomSVG: `
|
||||
<rect width="{width}" height="{height}" style="{style}"></rect>
|
||||
<rect width="{width}" height="{height}" stroke="black" fill-opacity="0"></rect>
|
||||
<line x1="0" y1="0" x2="{width}" y2="{height}" stroke="black" style='{{ "transform":"scaleY(0.5)"}}'></line>
|
||||
<line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
|
||||
`
|
||||
,
|
||||
Style: {
|
||||
fillOpacity: 1,
|
||||
strokeWidth: 1,
|
||||
borderWidth: 2,
|
||||
stroke: '#bfdbfe',
|
||||
fill: '#bfdbfe'
|
||||
},
|
||||
UserData: {
|
||||
styleLine: {
|
||||
transform: "scaleY(0.5) translateY(100%)",
|
||||
transformBox: "fill-box"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -104,7 +90,7 @@ const GetSVGLayoutConfiguration = () => {
|
|||
XPositionReference: 1,
|
||||
Style: {
|
||||
fillOpacity: 0,
|
||||
strokeWidth: 2,
|
||||
borderWidth: 2,
|
||||
stroke: '#713f12',
|
||||
fill: '#713f12',
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "src/workers"],
|
||||
"exclude": ["test-server"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue