Merge branch 'master' into master.7306.symbolY

# Conflicts:
#	src/Components/Canvas/Symbol.ts
#	src/Components/SVG/Elements/DimensionLayer.tsx
#	src/Components/SVG/Elements/SelectorSymbol/SelectorSymbol.tsx
This commit is contained in:
Carl Fuchs 2023-02-20 10:21:54 +01:00
commit d72c2266f3
52 changed files with 1967 additions and 913 deletions

View file

@ -19,7 +19,8 @@ module.exports = {
},
ecmaVersion: 'latest',
sourceType: 'module',
project: './tsconfig.json'
project: './tsconfig.json',
tsconfigRootDir: __dirname
},
plugins: [
'only-warn',
@ -31,6 +32,7 @@ module.exports = {
'prefer-arrow-callback': 'error',
'func-style': ['error', 'declaration'],
'space-before-function-paren': ['error', 'never'],
'max-len': ['error', { 'code': 120 }],
// Import/export
'import/no-default-export': 'error',

View file

@ -9,7 +9,20 @@ You will be able to navigate through this document with the table of contents.
- [Table of contents](#table-of-contents)
- [I want to contribute](#i-want-to-contribute)
- [I want to contribute to the .NETFramework API](#i-want-to-contribute-to-the-netframework-api)
- [Getting Started](#getting-started)
- [Before developing](#before-developing)
- [Testing](#testing)
- [Releasing](#releasing)
- [I want to contribute to the React component](#i-want-to-contribute-to-the-react-component)
- [Getting Started](#getting-started-1)
- [Before developing](#before-developing-1)
- [CORS](#cors)
- [Develop with Vite and pnpm](#develop-with-vite-and-pnpm)
- [Develop with mprocs](#develop-with-mprocs)
- [Testing the external API without .NETFramework or Windows](#testing-the-external-api-without-netframework-or-windows)
- [Setup debugging with chrome](#setup-debugging-with-chrome)
- [Testing](#testing-1)
- [Releasing](#releasing-1)
- [I want to report a bug](#i-want-to-report-a-bug)
- [Before submitting a bug report](#before-submitting-a-bug-report)
- [How do i submit a good bug report?](#how-do-i-submit-a-good-bug-report)
@ -102,6 +115,14 @@ Then run the following command to run the projet in a dev environment:
pnpm dev
```
### Develop with mprocs
[Mprocs](https://github.com/pvolok/mprocs) runs multiple commands in parallel and shows output of each command separately.
It is useful to run `vite` and the test server at the same time with `mprocs`.
Run `pnpm d` or `pnpm mprocs` to run mprocs.
### Testing the external API without .NETFramework or Windows
Use the Node.js server in `/test-server` to simulate the api.

View file

@ -1,28 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace SVGLDLibs.Models
{
[DataContract]
public class DimensionOptions
{
/** positions of the dimension */
[DataMember(EmitDefaultValue = false)]
public Position[] positions;
/** color */
[DataMember(EmitDefaultValue = false)]
public string color;
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace SVGLDLibs.Models
{
[DataContract]
public class DimensionOptions
{
/** positions of the dimension */
[DataMember(EmitDefaultValue = false)]
public Position[] positions;
/** color */
[DataMember(EmitDefaultValue = false)]
public string color;
/** width */
[DataMember(EmitDefaultValue = false)]
public double width;
/** color */
[DataMember(EmitDefaultValue = false)]
public string dashArray;
}
}

7
mprocs.yaml Normal file
View file

@ -0,0 +1,7 @@
procs:
nvim:
shell: "nvim ."
vite:
shell: "npx vite"
test-server:
shell: "npx nodemon ./test-server/http.js"

View file

@ -3,11 +3,12 @@
"private": true,
"version": "v1.0.0",
"type": "module",
"postinstall": "npx patch-package",
"scripts": {
"d": "mprocs",
"dev": "vite",
"build": "tsc && vite build",
"build:dotnet": "dotnet build ./csharp/SVGLDLibs/SVGLDLibs/SVGLDLibs.csproj",
"preview": "vite preview",
"linter": "eslint src",
"test": "vitest",
"test:ui": "vitest --ui",
@ -22,10 +23,11 @@
"interweave": "^13.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-svg-pan-zoom": "^3.11.0",
"react-svg-pan-zoom": "^3.12.1",
"react-window": "^1.8.8",
"sweetalert2": "^11.7.1",
"sweetalert2-react-content": "^5.0.7"
"sweetalert2-react-content": "^5.0.7",
"transformation-matrix": "^2.14.0"
},
"devDependencies": {
"@testing-library/dom": "^8.20.0",
@ -52,11 +54,18 @@
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^21.1.0",
"mprocs": "^0.6.4",
"nodemon": "^2.0.20",
"postcss": "^8.4.21",
"sass": "^1.58.0",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.5",
"vite": "^4.1.1",
"vitest": "^0.28.4"
},
"pnpm": {
"patchedDependencies": {
"@types/react-svg-pan-zoom@3.3.5": "patches/@types__react-svg-pan-zoom@3.3.5.patch"
}
}
}

View file

@ -0,0 +1,17 @@
diff --git a/node_modules/@types/react-svg-pan-zoom/index.d.ts b/node_modules/@types/react-svg-pan-zoom/index.d.ts
index a57d545..83ace9f 100644
--- a/node_modules/@types/react-svg-pan-zoom/index.d.ts
+++ b/node_modules/@types/react-svg-pan-zoom/index.d.ts
@@ -256,7 +256,11 @@ export function zoom(value: Value, SVGPointX: number, SVGPointY: number, scaleFa
export function fitSelection(
value: Value, selectionSVGPointX: number, selectionSVGPointY: number, selectionWidth: number, selectionHeight: number): Value;
-export function fitToViewer(value: Value): Value;
+export function fitToViewer(
+ value: Value,
+ SVGAlignX?: typeof ALIGN_CENTER | typeof ALIGN_LEFT | typeof ALIGN_RIGHT | undefined,
+ SVGAlignY?: typeof ALIGN_CENTER | typeof ALIGN_TOP | typeof ALIGN_BOTTOM | undefined
+): Value;
export function zoomOnViewerCenter(value: Value, scaleFactor: number): Value;

View file

@ -0,0 +1,17 @@
diff --git a/index.d.ts b/index.d.ts
index a57d545d33b2798024b9762d3d3513e58a38e19d..83ace9fc85b7354e128948402a50e00083eacd8c 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -256,7 +256,11 @@ export function zoom(value: Value, SVGPointX: number, SVGPointY: number, scaleFa
export function fitSelection(
value: Value, selectionSVGPointX: number, selectionSVGPointY: number, selectionWidth: number, selectionHeight: number): Value;
-export function fitToViewer(value: Value): Value;
+export function fitToViewer(
+ value: Value,
+ SVGAlignX?: typeof ALIGN_CENTER | typeof ALIGN_LEFT | typeof ALIGN_RIGHT | undefined,
+ SVGAlignY?: typeof ALIGN_CENTER | typeof ALIGN_TOP | typeof ALIGN_BOTTOM | undefined
+): Value;
export function zoomOnViewerCenter(value: Value, scaleFactor: number): Value;

109
pnpm-lock.yaml generated
View file

@ -1,5 +1,10 @@
lockfileVersion: 5.4
patchedDependencies:
'@types/react-svg-pan-zoom@3.3.5':
hash: kv3ctd73j5hnzcxdc2ceiq5wuy
path: patches/@types__react-svg-pan-zoom@3.3.5.patch
specifiers:
'@heroicons/react': ^2.0.14
'@react-hook/size': ^2.1.2
@ -28,15 +33,18 @@ specifiers:
eslint-plugin-react-hooks: ^4.6.0
interweave: ^13.0.0
jsdom: ^21.1.0
mprocs: ^0.6.4
nodemon: ^2.0.20
postcss: ^8.4.21
react: ^18.2.0
react-dom: ^18.2.0
react-svg-pan-zoom: ^3.11.0
react-svg-pan-zoom: ^3.12.1
react-window: ^1.8.8
sass: ^1.58.0
sweetalert2: ^11.7.1
sweetalert2-react-content: ^5.0.7
tailwindcss: ^3.2.4
transformation-matrix: ^2.14.0
typescript: ^4.9.5
vite: ^4.1.1
vitest: ^0.28.4
@ -47,10 +55,11 @@ dependencies:
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-svg-pan-zoom: 3.12.1
react-window: 1.8.8_biqbaboplfbrettd7655fr4n2y
sweetalert2: 11.7.1
sweetalert2-react-content: 5.0.7_5cbezu6w3tvev2ldv5vdmnpfca
transformation-matrix: 2.14.0
devDependencies:
'@testing-library/dom': 8.20.0
@ -59,7 +68,7 @@ devDependencies:
'@testing-library/user-event': 14.4.3_yxlyej73nftwmh2fiao7paxmlm
'@types/react': 18.0.27
'@types/react-dom': 18.0.10
'@types/react-svg-pan-zoom': 3.3.5
'@types/react-svg-pan-zoom': 3.3.5_kv3ctd73j5hnzcxdc2ceiq5wuy
'@types/react-window': 1.8.5
'@typescript-eslint/eslint-plugin': 5.51.0_b635kmla6dsb4frxfihkw4m47e
'@typescript-eslint/parser': 5.51.0_4vsywjlpuriuw3tl5oq6zy5a64
@ -77,6 +86,8 @@ devDependencies:
eslint-plugin-react: 7.32.2_eslint@8.33.0
eslint-plugin-react-hooks: 4.6.0_eslint@8.33.0
jsdom: 21.1.0
mprocs: 0.6.4
nodemon: 2.0.20
postcss: 8.4.21
sass: 1.58.0
tailwindcss: 3.2.4_postcss@8.4.21
@ -923,11 +934,12 @@ packages:
'@types/react': 18.0.27
dev: true
/@types/react-svg-pan-zoom/3.3.5:
/@types/react-svg-pan-zoom/3.3.5_kv3ctd73j5hnzcxdc2ceiq5wuy:
resolution: {integrity: sha512-W8GRFCDy7raSDr5OXGjSyvX5KmdWlIQfv0NLa1jfAYVUO4ClVbgorWeAAom7nY3Pl+4h9blXE1Bnu2CW1iMEvQ==}
dependencies:
'@types/react': 18.0.27
dev: true
patched: true
/@types/react-window/1.8.5:
resolution: {integrity: sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==}
@ -1172,6 +1184,10 @@ packages:
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
dev: true
/abbrev/1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
dev: true
/acorn-globals/7.0.1:
resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==}
dependencies:
@ -1600,6 +1616,18 @@ packages:
ms: 2.1.3
dev: true
/debug/3.2.7_supports-color@5.5.0:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.3
supports-color: 5.5.0
dev: true
/debug/4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
@ -2507,6 +2535,10 @@ packages:
safer-buffer: 2.1.2
dev: true
/ignore-by-default/1.0.1:
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
dev: true
/ignore/5.2.4:
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
engines: {node: '>= 4'}
@ -3010,6 +3042,12 @@ packages:
ufo: 1.0.1
dev: true
/mprocs/0.6.4:
resolution: {integrity: sha512-Y4eqnAjp3mjy0eT+zPoMQ+P/ISOzjgRG/4kh4I5cRA4Tv0rPxTCBRadn3+j+boMF5id7IoLhrVq9NFWFPuzD9A==}
engines: {node: '>=0.10.0'}
hasBin: true
dev: true
/mrmime/1.0.1:
resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==}
engines: {node: '>=10'}
@ -3041,6 +3079,30 @@ packages:
resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
dev: true
/nodemon/2.0.20:
resolution: {integrity: sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==}
engines: {node: '>=8.10.0'}
hasBin: true
dependencies:
chokidar: 3.5.3
debug: 3.2.7_supports-color@5.5.0
ignore-by-default: 1.0.1
minimatch: 3.1.2
pstree.remy: 1.1.8
semver: 5.7.1
simple-update-notifier: 1.1.0
supports-color: 5.5.0
touch: 3.1.0
undefsafe: 2.0.5
dev: true
/nopt/1.0.10:
resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==}
hasBin: true
dependencies:
abbrev: 1.1.1
dev: true
/normalize-path/3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
@ -3352,6 +3414,10 @@ packages:
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
dev: true
/pstree.remy/1.1.8:
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
dev: true
/punycode/2.3.0:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
engines: {node: '>=6'}
@ -3395,13 +3461,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/react-svg-pan-zoom/3.11.0_react@18.2.0:
resolution: {integrity: sha512-xK2tpfp4YksHOfyMZH5zXP52ARLSBgkoJgWNJmJ1B+6O1tkuf23TQp7Q4m9GG5IRSK5KWO0JEGEWlNYG9+iiug==}
peerDependencies:
react: '>=17.0.0'
/react-svg-pan-zoom/3.12.1:
resolution: {integrity: sha512-ug1LHCN5qed56C64xFypr/ClajuMFkig1OKvwJrIgGeSyHOjWM7XGgSgeP3IfHAkNw8QEc6a31ggZRpTijWYRw==}
dependencies:
prop-types: 15.8.1
react: 18.2.0
transformation-matrix: 2.14.0
dev: false
@ -3549,11 +3612,21 @@ packages:
dependencies:
loose-envify: 1.4.0
/semver/5.7.1:
resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
hasBin: true
dev: true
/semver/6.3.0:
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
hasBin: true
dev: true
/semver/7.0.0:
resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==}
hasBin: true
dev: true
/semver/7.3.8:
resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==}
engines: {node: '>=10'}
@ -3586,6 +3659,13 @@ packages:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
dev: true
/simple-update-notifier/1.1.0:
resolution: {integrity: sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==}
engines: {node: '>=8.10.0'}
dependencies:
semver: 7.0.0
dev: true
/sirv/2.0.2:
resolution: {integrity: sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==}
engines: {node: '>= 10'}
@ -3831,6 +3911,13 @@ packages:
engines: {node: '>=6'}
dev: true
/touch/3.1.0:
resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==}
hasBin: true
dependencies:
nopt: 1.0.10
dev: true
/tough-cookie/4.1.2:
resolution: {integrity: sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==}
engines: {node: '>=6'}
@ -3926,6 +4013,10 @@ packages:
which-boxed-primitive: 1.0.2
dev: true
/undefsafe/2.0.5:
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
dev: true
/universalify/0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}

View file

@ -10,7 +10,7 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
import { IContainerModel, ContainerModel } from '../../Interfaces/IContainerModel';
import { IHistoryState } from '../../Interfaces/IHistoryState';
import { IPattern } from '../../Interfaces/IPattern';
import { DEFAULT_MAINCONTAINER_PROPS, GetDefaultContainerProps } from '../../utils/default';
import { DEFAULT_DIMENSION_OPTION, DEFAULT_MAINCONTAINER_PROPS, GetDefaultContainerProps } from '../../utils/default';
import { FetchConfiguration } from './api';
const CSHARP_WEB_API_BASE_URL = 'http://localhost:5209/';
@ -144,23 +144,11 @@ describe.concurrent('Models test suite', () => {
PositionReference: 0,
HideChildrenInTreeview: true,
DimensionOptions: {
childrenDimensions: {
color: '#000000',
positions: []
},
selfDimensions: {
color: '#000000',
positions: []
},
selfMarginsDimensions: {
color: '#000000',
positions: []
},
childrenDimensions: DEFAULT_DIMENSION_OPTION,
selfDimensions: DEFAULT_DIMENSION_OPTION,
selfMarginsDimensions: DEFAULT_DIMENSION_OPTION,
markPosition: [],
dimensionWithMarks: {
color: '#000000',
positions: []
}
dimensionWithMarks: DEFAULT_DIMENSION_OPTION
},
IsHidden: true,
Blacklist: [

View file

@ -17,6 +17,7 @@ import { BarIcon } from './BarIcon';
import { Text } from '../Text/Text';
interface IBarProps {
className: string
isComponentsOpen: boolean
isSymbolsOpen: boolean
isHistoryOpen: boolean
@ -33,7 +34,7 @@ export const BAR_WIDTH = 64; // 4rem
export function Bar(props: IBarProps): JSX.Element {
return (
<div className='bar'>
<div className={`bar ${props.className}`}>
<BarIcon
isActive={props.isComponentsOpen}
title={Text({ textId: '@Components' })}

View file

@ -1,16 +1,85 @@
import React, { useEffect, useRef } from 'react';
import { IPoint } from '../../Interfaces/IPoint';
import { type IPoint } from '../../Interfaces/IPoint';
import { BAR_WIDTH } from '../Bar/Bar';
import { SelectorMode } from '../SVG/SVG';
import { type DrawParams } from '../Viewer/Viewer';
import { RenderContainers, RenderSymbols } from './Renderer';
import { RenderContainerSelector } from './SelectorContainer';
import { RenderSymbolSelector } from './SelectorSymbol';
interface ICanvasProps {
className?: string
width: number
height: number
draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void
className?: string
style?: React.CSSProperties
drawParams: DrawParams
};
function UseCanvas(draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void): React.RefObject<HTMLCanvasElement> {
function Draw(
ctx: CanvasRenderingContext2D,
frameCount: number,
scale: number,
translatePos: IPoint,
{
mainContainer,
selectorMode,
selectedContainer,
selectedSymbol,
containers,
symbols
}: DrawParams
): void {
if (mainContainer === undefined) {
return;
}
const topDim = mainContainer.properties.y;
const leftDim = mainContainer.properties.x;
const rightDim = mainContainer.properties.x + mainContainer.properties.width;
const bottomDim = mainContainer.properties.y + mainContainer.properties.height;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
ctx.setTransform(scale, 0, 0, scale, translatePos.x, translatePos.y);
ctx.fillStyle = '#000000';
// Draw containers and symbol dimensions
RenderContainers(
ctx,
mainContainer,
containers,
leftDim, bottomDim, topDim, rightDim, scale);
// Draw symbols and symbol dimensions
RenderSymbols(ctx, symbols, scale);
// Draw selector
switch (selectorMode) {
case SelectorMode.Containers:
RenderContainerSelector(ctx, frameCount, {
containers,
scale,
selected: selectedContainer
});
break;
case SelectorMode.Symbols:
RenderSymbolSelector(ctx, frameCount, {
symbols,
scale,
selected: selectedSymbol
});
break;
}
ctx.restore();
}
function UseCanvas(
draw: (context: CanvasRenderingContext2D,
frameCount: number,
scale: number,
translatePos: IPoint) => void
): React.RefObject<HTMLCanvasElement> {
const canvasRef = useRef<HTMLCanvasElement>(null);
const frameCount = useRef(0);
const translatePos = useRef({
@ -123,8 +192,21 @@ interface Viewer {
viewerHeight: number
}
export function Canvas({ width, height, draw, style, className }: ICanvasProps): JSX.Element {
const canvasRef = UseCanvas(draw);
export function Canvas({
className,
width,
height,
style,
drawParams
}: ICanvasProps): JSX.Element {
const canvasRef = UseCanvas((
...CanvasProps
) => {
Draw(
...CanvasProps,
drawParams
);
});
const [{ viewerWidth, viewerHeight }, setViewer] = React.useState<Viewer>({
viewerWidth: width,

View file

@ -0,0 +1,18 @@
import { type IContainerModel } from '../../Interfaces/IContainerModel';
export function RenderContainer(
ctx: CanvasRenderingContext2D,
container: IContainerModel,
x: number,
y: number
): void {
ctx.save();
ctx.strokeStyle = container.properties.style?.stroke ?? '#000000';
ctx.fillStyle = container.properties.style?.fill ?? '#000000';
ctx.lineWidth = Number(container.properties.style?.strokeWidth ?? 1);
ctx.globalAlpha = Number(container.properties.style?.fillOpacity ?? 1);
ctx.fillRect(x, y, container.properties.width, container.properties.height);
ctx.globalAlpha = Number(container.properties.style?.strokeOpacity ?? 1);
ctx.strokeRect(x, y, container.properties.width, container.properties.height);
ctx.restore();
}

View file

@ -1,4 +1,5 @@
import { NOTCHES_LENGTH } from '../../utils/default';
import { IDimensionStyle } from '../SVG/Elements/Dimension';
interface IDimensionProps {
id: string
@ -7,7 +8,7 @@ interface IDimensionProps {
xEnd: number
yEnd: number
text: string
strokeWidth: number
style: IDimensionStyle
scale?: number
}
@ -26,8 +27,11 @@ function ApplyParametric(x0: number, t: number, vx: number): number {
export function RenderDimension(ctx: CanvasRenderingContext2D, props: IDimensionProps): void {
const scale = props.scale ?? 1;
const strokeStyle = 'black';
const lineWidth = 2 / scale;
const strokeStyle = props.style.color ?? 'black';
const lineWidth = (props.style.width ?? 2) / scale;
const dashArray: number[] = props.style.dashArray?.split(' ')
.flatMap(array => array.split(','))
.map(stringValue => parseInt(stringValue)) ?? [];
/// We need to find the points of the notches
// Get the vector of the line
@ -59,6 +63,7 @@ export function RenderDimension(ctx: CanvasRenderingContext2D, props: IDimension
ctx.lineWidth = lineWidth;
ctx.strokeStyle = strokeStyle;
ctx.fillStyle = strokeStyle;
ctx.setLineDash(dashArray);
ctx.moveTo(startTopX, startTopY);
ctx.lineTo(startBottomX, startBottomY);
ctx.stroke();
@ -68,6 +73,7 @@ export function RenderDimension(ctx: CanvasRenderingContext2D, props: IDimension
ctx.moveTo(endTopX, endTopY);
ctx.lineTo(endBottomX, endBottomY);
ctx.stroke();
ctx.setLineDash([]);
const textX = (props.xStart + props.xEnd) / 2;
const textY = (props.yStart + props.yEnd) / 2;
ctx.font = `${16 / scale}px Verdana`;

View file

@ -1,14 +1,14 @@
import { Orientation } from '../../Enums/Orientation';
import { Position } from '../../Enums/Position';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { SHOW_SELF_DIMENSIONS, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS } from '../../utils/default';
import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { SHOW_SELF_DIMENSIONS, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, DIMENSION_MARGIN, SHOW_SELF_MARGINS_DIMENSIONS } from '../../utils/default';
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../utils/itertools';
import { TransformX, TransformY } from '../../utils/svg';
import { type IDimensionStyle } from '../SVG/Elements/Dimension';
import { RenderDimension } from './Dimension';
const MODULE_STROKE_WIDTH = 1;
export function AddDimensions(
export function AddContainerDimensions(
ctx: CanvasRenderingContext2D,
containers: Map<string, IContainerModel>,
container: IContainerModel,
@ -18,7 +18,8 @@ export function AddDimensions(
depth: number
): void {
ctx.beginPath();
if (SHOW_SELF_DIMENSIONS && container.properties.dimensionOptions.selfDimensions.positions.length > 0) {
if (SHOW_SELF_DIMENSIONS &&
container.properties.dimensionOptions.selfDimensions.positions.length > 0) {
ActionByPosition(
ctx,
dimMapped,
@ -32,7 +33,24 @@ export function AddDimensions(
);
}
if (SHOW_BORROWER_DIMENSIONS && container.properties.dimensionOptions.dimensionWithMarks.positions.length > 0) {
if (SHOW_SELF_MARGINS_DIMENSIONS &&
container.properties.dimensionOptions.selfMarginsDimensions.positions.length > 0) {
ActionByPosition(
ctx,
dimMapped,
container.properties.dimensionOptions.selfMarginsDimensions.positions,
AddHorizontalSelfMarginsDimension,
AddVerticalSelfMarginDimension,
[
container,
currentTransform,
scale
]
);
}
if (SHOW_BORROWER_DIMENSIONS &&
container.properties.dimensionOptions.dimensionWithMarks.positions.length > 0) {
ActionByPosition(
ctx,
dimMapped,
@ -48,7 +66,9 @@ export function AddDimensions(
);
}
if (SHOW_CHILDREN_DIMENSIONS && container.properties.dimensionOptions.childrenDimensions.positions.length > 0 && container.children.length >= 2) {
if (SHOW_CHILDREN_DIMENSIONS &&
container.properties.dimensionOptions.childrenDimensions.positions.length > 0 &&
container.children.length >= 2) {
ActionByPosition(
ctx,
dimMapped,
@ -64,6 +84,20 @@ export function AddDimensions(
}
}
export function AddSymbolDimensions(
ctx: CanvasRenderingContext2D,
symbol: ISymbolModel,
scale: number,
depth: number
): void {
AddHorizontalSymbolDimension(
ctx,
symbol,
scale,
depth
);
}
/**
* Fonction that call another function given the positions
* @param dimMapped Position mapped depending on the Position enum in order:
@ -108,6 +142,7 @@ function AddHorizontalChildrenDimension(
scale: number
): void {
const childrenId = `dim-y${yDim.toFixed(0)}-children-${container.properties.id}`;
const style = container.properties.dimensionOptions.childrenDimensions;
const lastChildId = container.children[container.children.length - 1];
const lastChild = FindContainerById(containers, lastChildId);
@ -115,8 +150,14 @@ function AddHorizontalChildrenDimension(
if (lastChild === undefined) {
return;
}
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
let xChildrenStart = TransformX(
lastChild.properties.x,
lastChild.properties.width,
lastChild.properties.positionReference);
let xChildrenEnd = TransformX(
lastChild.properties.x,
lastChild.properties.width,
lastChild.properties.positionReference);
// Find the min and max
for (let i = container.children.length - 2; i >= 0; i--) {
@ -152,9 +193,9 @@ function AddHorizontalChildrenDimension(
xEnd: xChildrenEnd + offset,
yStart: yDim,
yEnd: yDim,
strokeWidth: MODULE_STROKE_WIDTH,
text: textChildren,
scale
scale,
style
});
}
@ -168,6 +209,7 @@ function AddVerticalChildrenDimension(
scale: number
): void {
const childrenId = `dim-x${xDim.toFixed(0)}-children-${container.properties.id}`;
const style = container.properties.dimensionOptions.childrenDimensions;
const lastChildId = container.children[container.children.length - 1];
const lastChild = FindContainerById(containers, lastChildId);
@ -176,7 +218,10 @@ function AddVerticalChildrenDimension(
return;
}
let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
let yChildrenStart = TransformY(
lastChild.properties.y,
lastChild.properties.height,
lastChild.properties.positionReference);
let yChildrenEnd = yChildrenStart;
// Find the min and max
@ -218,9 +263,9 @@ function AddVerticalChildrenDimension(
xEnd: xDim,
yStart: yChildrenStart + offset,
yEnd: yChildrenEnd + offset,
strokeWidth: MODULE_STROKE_WIDTH,
text: textChildren,
scale
scale,
style
});
}
@ -234,6 +279,7 @@ function AddHorizontalBorrowerDimension(
scale: number
): void {
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
const style = container.properties.dimensionOptions.dimensionWithMarks;
const marks = []; // list of vertical lines for the dimension
for (const {
container: childContainer, currentTransform: childCurrentTransform
@ -274,9 +320,9 @@ function AddHorizontalBorrowerDimension(
xEnd: next,
yStart: yDim,
yEnd: yDim,
strokeWidth: MODULE_STROKE_WIDTH,
text: value.toFixed(0),
scale
scale,
style
});
count++;
}
@ -293,6 +339,7 @@ function AddVerticalBorrowerDimension(
scale: number
): void {
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
const style = container.properties.dimensionOptions.dimensionWithMarks;
const marks = []; // list of vertical lines for the dimension
for (const {
container: childContainer, currentTransform: childCurrentTransform
@ -338,9 +385,9 @@ function AddVerticalBorrowerDimension(
xEnd: xDim,
yStart: cur,
yEnd: next,
strokeWidth: MODULE_STROKE_WIDTH,
text: value.toFixed(0),
scale
scale,
style
});
count++;
}
@ -354,6 +401,7 @@ function AddVerticalSelfDimension(
currentTransform: [number, number],
scale: number
): void {
const style = container.properties.dimensionOptions.selfDimensions;
const height = container.properties.height;
const idVert = `dim-x${xDim.toFixed(0)}-${container.properties.id}`;
let yStart = container.properties.y + currentTransform[1] + height;
@ -372,9 +420,9 @@ function AddVerticalSelfDimension(
xEnd: xDim,
yStart,
yEnd,
strokeWidth: MODULE_STROKE_WIDTH,
text: textVert,
scale
scale,
style
});
}
@ -385,6 +433,7 @@ function AddHorizontalSelfDimension(
currentTransform: [number, number],
scale: number
): void {
const style = container.properties.dimensionOptions.selfDimensions;
const width = container.properties.width;
const id = `dim-y${yDim.toFixed(0)}-${container.properties.id}`;
const xStart = container.properties.x + currentTransform[0];
@ -398,8 +447,154 @@ function AddHorizontalSelfDimension(
yStart: yDim,
xEnd,
yEnd: yDim,
strokeWidth: MODULE_STROKE_WIDTH,
text,
scale
scale,
style
});
}
function AddHorizontalSelfMarginsDimension(
ctx: CanvasRenderingContext2D,
yDim: number,
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number
): void {
const style = container.properties.dimensionOptions.selfMarginsDimensions;
const left = container.properties.margin.left;
if (left != null) {
const id = `dim-y-margin-left${yDim.toFixed(0)}-${container.properties.id}`;
const xStart = container.properties.x + currentTransform[0] - left;
const xEnd = xStart + left;
const text = left
.toFixed(0)
.toString();
RenderDimension(ctx, {
id,
xStart,
yStart: yDim,
xEnd,
yEnd: yDim,
text,
scale,
style
});
}
const right = container.properties.margin.right;
if (right != null) {
const id = `dim-y-margin-right${yDim.toFixed(0)}-${container.properties.id}`;
const xStart = container.properties.x + container.properties.width + currentTransform[0];
const xEnd = xStart + right;
const text = right
.toFixed(0)
.toString();
RenderDimension(ctx, {
id,
xStart,
yStart: yDim,
xEnd,
yEnd: yDim,
text,
scale,
style
});
}
}
function AddVerticalSelfMarginDimension(
ctx: CanvasRenderingContext2D,
xDim: number,
isRight: boolean,
container: IContainerModel,
currentTransform: [number, number],
scale: number
): void {
const style = container.properties.dimensionOptions.selfMarginsDimensions;
const top = container.properties.margin.top;
if (top != null) {
const idVert = `dim-x-margin-top${xDim.toFixed(0)}-${container.properties.id}`;
let yStart = container.properties.y + currentTransform[1];
let yEnd = yStart - top;
const textVert = top
.toFixed(0)
.toString();
if (isRight) {
[yStart, yEnd] = [yEnd, yStart];
}
RenderDimension(ctx, {
id: idVert,
xStart: xDim,
yStart,
xEnd: xDim,
yEnd,
text: textVert,
scale,
style
});
}
const bottom = container.properties.margin.bottom;
if (bottom != null) {
const idVert = `dim-x-margin-bottom${xDim.toFixed(0)}-${container.properties.id}`;
let yStart = container.properties.y + container.properties.height + bottom + currentTransform[1];
let yEnd = yStart - bottom;
const textVert = bottom
.toFixed(0)
.toString();
if (isRight) {
[yStart, yEnd] = [yEnd, yStart];
}
RenderDimension(ctx, {
id: idVert,
xStart: xDim,
yStart,
xEnd: xDim,
yEnd,
text: textVert,
scale,
style
});
}
}
function AddHorizontalSymbolDimension(
ctx: CanvasRenderingContext2D,
symbol: ISymbolModel,
scale: number,
depth: number
): void {
const width = symbol.x + (symbol.width / 2);
if (width == null || width <= 0) {
return;
}
const id = `dim-y-margin-left${symbol.width.toFixed(0)}-${symbol.id}`;
const offset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const text = width
.toFixed(0)
.toString();
// TODO: Put this in default.ts
const defaultDimensionSymbolStyle: IDimensionStyle = {
color: 'black'
};
RenderDimension(ctx, {
id,
xStart: 0,
yStart: -offset,
xEnd: width,
yEnd: -offset,
text,
scale,
style: defaultDimensionSymbolStyle
});
}

View file

@ -0,0 +1,104 @@
import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { type IHistoryState } from '../../Interfaces/IHistoryState';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { DIMENSION_MARGIN } from '../../utils/default';
import { MakeRecursionDFSIterator } from '../../utils/itertools';
import { RenderContainer } from './Container';
import { AddContainerDimensions, AddSymbolDimensions } from './DimensionLayer';
import { RenderSymbol } from './Symbol';
export function RenderContainers(
ctx: CanvasRenderingContext2D,
root: IContainerModel,
containers: Map<string, IContainerModel>,
leftDim: number,
bottomDim: number,
topDim: number,
rightDim: number,
scale: number
): void {
const it = MakeRecursionDFSIterator(root, containers, 0, [0, 0]);
for (const { container, depth, currentTransform } of it) {
const [x, y] = [
container.properties.x + currentTransform[0],
container.properties.y + currentTransform[1]
];
// Draw container
RenderContainer(ctx, container, x, y);
// Draw dimensions
RenderContainerDimensions(
ctx,
leftDim,
bottomDim,
topDim,
rightDim,
depth,
scale,
containers,
container,
currentTransform
);
}
}
export function RenderContainerDimensions(
ctx: CanvasRenderingContext2D,
leftDim: number,
bottomDim: number,
topDim: number,
rightDim: number,
depth: number,
scale: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
currentTransform: [number, number]
): void {
ctx.save();
const depthOffset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerLeftDim = leftDim - depthOffset;
const containerTopDim = topDim - depthOffset;
const containerBottomDim = bottomDim + depthOffset;
const containerRightDim = rightDim + depthOffset;
const dimMapped = [containerLeftDim, containerBottomDim, containerTopDim, containerRightDim];
AddContainerDimensions(ctx, containers, container, dimMapped, currentTransform, scale, depth);
ctx.restore();
}
export function RenderSymbols(
ctx: CanvasRenderingContext2D,
symbols: Map<string, ISymbolModel>,
scale: number
): void {
let count = 0;
symbols.forEach((symbol: ISymbolModel) => {
RenderSymbol(ctx, symbol, scale);
if (!symbol.showDimension) {
return;
}
AddSymbolDimensions(ctx, symbol, scale, count);
count++;
});
}
export function RenderSymbolDimensions(
ctx: CanvasRenderingContext2D,
depth: number,
scale: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
currentTransform: [number, number]
): void {
ctx.save();
const depthOffset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerLeftDim = -depthOffset;
const containerTopDim = -depthOffset;
const containerBottomDim = depthOffset;
const containerRightDim = depthOffset;
const dimMapped = [containerLeftDim, containerBottomDim, containerTopDim, containerRightDim];
AddContainerDimensions(ctx, containers, container, dimMapped, currentTransform, scale, depth);
ctx.restore();
}

View file

@ -1,33 +1,26 @@
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { SHOW_SELECTOR_TEXT } from '../../utils/default';
import { GetAbsolutePosition } from '../../utils/itertools';
import { RemoveMargin } from '../../utils/svg';
interface ISelectorProps {
containers: Map<string, IContainerModel>
selected?: IContainerModel
scale?: number
text: string
x: number
y: number
width: number
height: number
scale: number
}
export function RenderSelector(ctx: CanvasRenderingContext2D, frameCount: number, props: ISelectorProps): void {
if (props.selected === undefined || props.selected === null) {
return;
}
const scale = (props.scale ?? 1);
let [x, y] = GetAbsolutePosition(props.containers, props.selected);
let [width, height] = [
props.selected.properties.width,
props.selected.properties.height
];
({ x, y, width, height } = RemoveMargin(x, y, width, height,
props.selected.properties.margin.left,
props.selected.properties.margin.bottom,
props.selected.properties.margin.top,
props.selected.properties.margin.right
));
export function RenderSelector(
ctx: CanvasRenderingContext2D,
frameCount: number,
{
text,
x,
y,
width,
height,
scale
}: ISelectorProps
): void {
const xText = x + width / 2;
const yText = y + height / 2;
@ -42,7 +35,7 @@ export function RenderSelector(ctx: CanvasRenderingContext2D, frameCount: number
if (SHOW_SELECTOR_TEXT) {
ctx.font = `${16 / scale}px Verdana`;
ctx.textAlign = 'center';
ctx.fillText(props.selected.properties.displayedText, xText, yText);
ctx.fillText(text, xText, yText);
ctx.textAlign = 'left';
}
}

View file

@ -0,0 +1,48 @@
import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { GetAbsolutePosition } from '../../utils/itertools';
import { RemoveMargin } from '../../utils/svg';
import { RenderSelector } from './Selector';
interface ISelectorProps {
containers: Map<string, IContainerModel>
selected?: IContainerModel
scale?: number
}
export function RenderContainerSelector(
ctx: CanvasRenderingContext2D,
frameCount: number,
props: ISelectorProps
): void {
if (props.selected === undefined || props.selected === null) {
return;
}
const scale = (props.scale ?? 1);
let [x, y] = GetAbsolutePosition(props.containers, props.selected);
let [width, height] = [
props.selected.properties.width,
props.selected.properties.height
];
({ x, y, width, height } = RemoveMargin(x, y, width, height,
props.selected.properties.margin.left,
props.selected.properties.margin.bottom,
props.selected.properties.margin.top,
props.selected.properties.margin.right
));
const text = props.selected.properties.displayedText;
RenderSelector(
ctx,
frameCount,
{
text,
x,
y,
width,
height,
scale
}
);
}

View file

@ -0,0 +1,43 @@
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { SYMBOL_MARGIN } from '../../utils/default';
import { RenderSelector } from './Selector';
interface ISelectorProps {
symbols: Map<string, ISymbolModel>
selected?: ISymbolModel
scale?: number
}
export function RenderSymbolSelector(
ctx: CanvasRenderingContext2D,
frameCount: number,
props: ISelectorProps
): void {
if (props.selected === undefined || props.selected === null) {
return;
}
const scale = (props.scale ?? 1);
const [width, height] = [
props.selected.width / scale,
props.selected.height / scale
];
const [x, y] = [
props.selected.x + props.selected.width / 2,
-SYMBOL_MARGIN - height];
const text = props.selected.displayedText;
RenderSelector(
ctx,
frameCount,
{
text,
x,
y,
width,
height,
scale
}
);
}

View file

@ -1,11 +1,11 @@
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { DIMENSION_MARGIN } from '../../utils/default';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { DIMENSION_MARGIN, SYMBOL_MARGIN } from '../../utils/default';
const IMAGE_CACHE = new Map<string, HTMLImageElement>();
export function RenderSymbol(
symbol: ISymbolModel,
ctx: CanvasRenderingContext2D,
symbol: ISymbolModel,
scale: number): void {
const href = symbol.config.Image.Base64Image ?? symbol.config.Image.Url;
@ -26,6 +26,7 @@ export function RenderSymbol(
DrawImage(ctx, scale, image, symbol);
}
function DrawImage(
ctx: CanvasRenderingContext2D,
scale: number,
@ -34,12 +35,14 @@ function DrawImage(
): void {
ctx.save();
ctx.fillStyle = '#000000';
const width = symbol.width / scale;
const height = symbol.height / scale;
ctx.drawImage(
image,
symbol.offset,
-DIMENSION_MARGIN,
symbol.width,
symbol.height
symbol.offset + symbol.width / 2,
-SYMBOL_MARGIN - height,
width,
height
);
ctx.restore();
}

View file

@ -1,4 +1,4 @@
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
import { EyeIcon, EyeSlashIcon, XCircleIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
import { ICategory } from '../../Interfaces/ICategory';
@ -6,11 +6,15 @@ import { IContainerModel } from '../../Interfaces/IContainerModel';
import { TruncateString } from '../../utils/stringtools';
import { Category } from '../Category/Category';
import { Text } from '../Text/Text';
import { IReplaceContainer } from '../../Interfaces/IReplaceContainer';
import { Dispatch } from 'react';
interface IComponentsProps {
selectedContainer: IContainerModel | undefined
componentOptions: IAvailableContainer[]
categories: ICategory[]
replaceContainer: IReplaceContainer
setReplaceContainer: Dispatch<React.SetStateAction<IReplaceContainer>>
buttonOnClick: (type: string) => void
}
@ -62,6 +66,10 @@ export function Components(props: IComponentsProps): JSX.Element {
disabled = config.Blacklist?.find(type => type === componentOption.Type) !== undefined ?? false;
}
if (props.replaceContainer.isReplacing && componentOption.Category !== props.replaceContainer.category) {
disabled = true;
}
if (disabled && hideDisabled) {
return;
}
@ -96,6 +104,15 @@ export function Components(props: IComponentsProps): JSX.Element {
return (
<div className='h-full'>
<div className='hover:bg-slate-300 h-7 text-right pr-1 pl-1'>
{props.replaceContainer.isReplacing && <button
onClick={() => {
props.setReplaceContainer({ isReplacing: false, id: undefined, category: undefined });
}}
className='h-full hover:bg-slate-400 rounded-lg p-1'
>
<XCircleIcon className='heroicon'></XCircleIcon>
</button>
}
<button
onClick={() => { setHideDisabled(!hideDisabled); }}
className='h-full hover:bg-slate-400 rounded-lg p-1'
@ -117,4 +134,4 @@ export function Components(props: IComponentsProps): JSX.Element {
</div>
</div>
);
};
}

View file

@ -1,7 +1,7 @@
import * as React from 'react';
import { PropertyType } from '../../Enums/PropertyType';
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { type IContainerProperties } from '../../Interfaces/IContainerProperties';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import {
SHOW_BORROWER_DIMENSIONS,
SHOW_CHILDREN_DIMENSIONS,
@ -46,7 +46,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName=''
type='string'
value={props.properties.displayedText?.toString()}
onChange={(value) => props.onChange('displayedText', value)}/>
onChange={(value) => { props.onChange('displayedText', value); }}/>
<OrientationSelector
id='orientation'
name='Orientation'
@ -117,17 +117,19 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
props.properties.width,
props.properties.positionReference
).toString()}
onChange={(value) => props.onChange(
'x',
ApplyXMargin(
RestoreX(
Number(value),
props.properties.width,
props.properties.positionReference
),
props.properties.margin.left
)
)}/>
onChange={(value) => {
props.onChange(
'x',
ApplyXMargin(
RestoreX(
Number(value),
props.properties.width,
props.properties.positionReference
),
props.properties.margin.left
)
);
}}/>
<TextInputGroup
id={`${props.properties.id}-y`}
labelText={Text({ textId: '@ContainerY' })}
@ -140,17 +142,19 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
props.properties.height,
props.properties.positionReference
).toString()}
onChange={(value) => props.onChange(
'y',
ApplyXMargin(
RestoreY(
Number(value),
props.properties.height,
props.properties.positionReference
),
props.properties.margin.top
)
)}/>
onChange={(value) => {
props.onChange(
'y',
ApplyXMargin(
RestoreY(
Number(value),
props.properties.height,
props.properties.positionReference
),
props.properties.margin.top
)
);
}}/>
</div>
</Category>
@ -171,7 +175,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={1}
value={props.properties.minWidth.toString()}
onChange={(value) => props.onChange('minWidth', Number(value))}/>
onChange={(value) => { props.onChange('minWidth', Number(value)); }}/>
<TextInputGroup
id={`${props.properties.id}-width`}
labelText={Text({ textId: '@ContainerWidth' })}
@ -182,7 +186,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
min={props.properties.minWidth}
max={props.properties.maxWidth}
value={(RemoveWidthMargin(props.properties.width, props.properties.margin.left, props.properties.margin.right)).toString()}
onChange={(value) => props.onChange('width', ApplyWidthMargin(Number(value), props.properties.margin.left, props.properties.margin.right))}
onChange={(value) => { props.onChange('width', ApplyWidthMargin(Number(value), props.properties.margin.left, props.properties.margin.right)); }}
isDisabled={props.properties.isFlex}/>
<TextInputGroup
id={`${props.properties.id}-maxWidth`}
@ -193,7 +197,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={1}
value={props.properties.maxWidth.toString()}
onChange={(value) => props.onChange('maxWidth', Number(value))}/>
onChange={(value) => { props.onChange('maxWidth', Number(value)); }}/>
<div className='col-span-5 p-3'></div>
<TextInputGroup
id={`${props.properties.id}-minHeight`}
@ -204,7 +208,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={1}
value={props.properties.minHeight.toString()}
onChange={(value) => props.onChange('minHeight', Number(value))}/>
onChange={(value) => { props.onChange('minHeight', Number(value)); }}/>
<TextInputGroup
id={`${props.properties.id}-height`}
labelText={Text({ textId: '@ContainerHeight' })}
@ -215,7 +219,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
min={props.properties.minHeight}
max={props.properties.maxHeight}
value={(RemoveWidthMargin(props.properties.height, props.properties.margin.top, props.properties.margin.bottom)).toString()}
onChange={(value) => props.onChange('height', ApplyWidthMargin(Number(value), props.properties.margin.top, props.properties.margin.bottom))}
onChange={(value) => { props.onChange('height', ApplyWidthMargin(Number(value), props.properties.margin.top, props.properties.margin.bottom)); }}
isDisabled={props.properties.isFlex}
/>
<TextInputGroup
@ -227,7 +231,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={1}
value={props.properties.maxHeight.toString()}
onChange={(value) => props.onChange('maxHeight', Number(value))}/>
onChange={(value) => { props.onChange('maxHeight', Number(value)); }}/>
</div>
</Category>
@ -247,7 +251,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={0}
value={(props.properties.margin.left ?? 0).toString()}
onChange={(value) => props.onChange('left', Number(value), PropertyType.Margin)}/>
onChange={(value) => { props.onChange('left', Number(value), PropertyType.Margin); }}/>
<TextInputGroup
id={`${props.properties.id}-mb`}
labelText={Text({ textId: '@ContainerMarginBottom' })}
@ -257,7 +261,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={0}
value={(props.properties.margin.bottom ?? 0).toString()}
onChange={(value) => props.onChange('bottom', Number(value), PropertyType.Margin)}/>
onChange={(value) => { props.onChange('bottom', Number(value), PropertyType.Margin); }}/>
<TextInputGroup
id={`${props.properties.id}-mt`}
labelText={Text({ textId: '@ContainerMarginTop' })}
@ -267,7 +271,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={0}
value={(props.properties.margin.top ?? 0).toString()}
onChange={(value) => props.onChange('top', Number(value), PropertyType.Margin)}/>
onChange={(value) => { props.onChange('top', Number(value), PropertyType.Margin); }}/>
<TextInputGroup
id={`${props.properties.id}-mr`}
labelText={Text({ textId: '@ContainerMarginRight' })}
@ -277,7 +281,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={0}
value={(props.properties.margin.right ?? 0).toString()}
onChange={(value) => props.onChange('right', Number(value), PropertyType.Margin)}/>
onChange={(value) => { props.onChange('right', Number(value), PropertyType.Margin); }}/>
</div>
</Category>
@ -295,7 +299,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName='ml-auto mr-auto block'
type={ToggleType.Full}
checked={props.properties.isFlex}
onChange={(event) => props.onChange('isFlex', event.target.checked)}
onChange={(event) => { props.onChange('isFlex', event.target.checked); }}
/>
<ToggleButton
labelText={Text({ textId: '@ContainerAnchor' })}
@ -304,7 +308,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName='ml-auto mr-auto block'
type={ToggleType.Full}
checked={props.properties.isAnchor}
onChange={(event) => props.onChange('isAnchor', event.target.checked)}/>
onChange={(event) => { props.onChange('isAnchor', event.target.checked); }}/>
</div>
</Category>
@ -334,7 +338,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
value: symbol.id
}))}
value={props.properties.linkedSymbolId ?? ''}
onChange={(event) => props.onChange('linkedSymbolId', event.target.value)}/>
onChange={(event) => { props.onChange('linkedSymbolId', event.target.value); }}/>
</div>
</Category>
@ -347,168 +351,244 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<div className='grid grid-cols-1 gap-6 prop-category-body'>
{
SHOW_SELF_DIMENSIONS &&
<div className='grid grid-cols-1 gap-2'>
<PositionCheckboxes
id='positionsSelfDimensions'
attribute='positions'
name='ShowSelfDimensions'
labelText={Text({ textId: '@ContainerShowDimension' })}
value={props.properties.dimensionOptions.selfDimensions.positions}
onChange={(key, value) => props.onChange(key, value, PropertyType.SelfDimension)}
/>
<InputGroup
labelText={Text({ textId: '@Color' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.selfDimensions.color}
onChange={(e) => props.onChange('color', e.target.value, PropertyType.SelfDimension)}/>
</div>
<div className='grid grid-cols-1 gap-2'>
<PositionCheckboxes
id='positionsSelfDimensions'
attribute='positions'
name='ShowSelfDimensions'
labelText={Text({ textId: '@ContainerShowDimension' })}
value={props.properties.dimensionOptions.selfDimensions.positions}
onChange={(key, value) => { props.onChange(key, value, PropertyType.SelfDimension); }}
/>
<InputGroup
labelText={Text({ textId: '@StyleStrokeColor' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.selfDimensions.color}
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.SelfDimension); }}/>
<TextInputGroup
id={`${props.properties.id}-selfDimensions-width`}
labelText={Text({ textId: '@StyleStrokeWidth' })}
inputKey='width'
labelClassName=''
inputClassName=''
type='number'
min={0}
value={(props.properties.dimensionOptions.selfDimensions.width ?? 0).toString()}
onChange={(value) => { props.onChange('width', Number(value), PropertyType.SelfDimension); }}/>
<TextInputGroup
id={`${props.properties.id}-selfDimensions-dasharray`}
labelText={Text({ textId: '@StyleStrokeDashArray' })}
inputKey='dashArray'
labelClassName=''
inputClassName=''
type='text'
value={props.properties.dimensionOptions.selfDimensions.dashArray ?? ''}
onChange={(value) => { props.onChange('dashArray', value, PropertyType.SelfDimension); }}/>
</div>
}
{
SHOW_SELF_MARGINS_DIMENSIONS &&
<div className='grid grid-cols-1 gap-2'>
<PositionCheckboxes
id='positionsSelfMarginsDimensions'
attribute='positions'
name='ShowSelfMarginsDimensions'
labelText={Text({ textId: '@ContainerShowMarginsDimension' })}
value={props.properties.dimensionOptions.selfMarginsDimensions.positions}
onChange={(key, value) => props.onChange(key, value, PropertyType.SelfMarginDimension)}
/>
<InputGroup
labelText={Text({ textId: '@Color' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.selfMarginsDimensions.color}
onChange={(e) => props.onChange('color', e.target.value, PropertyType.SelfMarginDimension)}/>
</div>
<div className='grid grid-cols-1 gap-2'>
<PositionCheckboxes
id='positionsSelfMarginsDimensions'
attribute='positions'
name='ShowSelfMarginsDimensions'
labelText={Text({ textId: '@ContainerShowMarginsDimension' })}
value={props.properties.dimensionOptions.selfMarginsDimensions.positions}
onChange={(key, value) => { props.onChange(key, value, PropertyType.SelfMarginDimension); }}
/>
<InputGroup
labelText={Text({ textId: '@StyleStrokeColor' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.selfMarginsDimensions.color}
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.SelfMarginDimension); }}/>
<TextInputGroup
id={`${props.properties.id}-selfMarginsDimensions-width`}
labelText={Text({ textId: '@StyleStrokeWidth' })}
inputKey='width'
labelClassName=''
inputClassName=''
type='number'
min={0}
value={(props.properties.dimensionOptions.selfMarginsDimensions.width ?? 0).toString()}
onChange={(value) => { props.onChange('width', Number(value), PropertyType.SelfMarginDimension); }}/>
<TextInputGroup
id={`${props.properties.id}-selfMarginsDimensions-dasharray`}
labelText={Text({ textId: '@StyleStrokeDashArray' })}
inputKey='dashArray'
labelClassName=''
inputClassName=''
type='text'
value={props.properties.dimensionOptions.selfMarginsDimensions.dashArray ?? ''}
onChange={(value) => { props.onChange('dashArray', value, PropertyType.SelfMarginDimension); }}/>
</div>
}
{
SHOW_CHILDREN_DIMENSIONS &&
<div className='grid grid-cols-1 gap-2'>
<PositionCheckboxes
id='positionsChildrenDimensions'
attribute='positions'
name='ShowChildrenDimensions'
labelText={Text({ textId: '@ContainerShowChildrenDimension' })}
value={props.properties.dimensionOptions.childrenDimensions.positions}
onChange={(key, value) => props.onChange(key, value, PropertyType.ChildrenDimensions)}
/>
<InputGroup
labelText={Text({ textId: '@Color' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.childrenDimensions.color}
onChange={(e) => props.onChange('color', e.target.value, PropertyType.ChildrenDimensions)}/>
</div>
<div className='grid grid-cols-1 gap-2'>
<PositionCheckboxes
id='positionsChildrenDimensions'
attribute='positions'
name='ShowChildrenDimensions'
labelText={Text({ textId: '@ContainerShowChildrenDimension' })}
value={props.properties.dimensionOptions.childrenDimensions.positions}
onChange={(key, value) => { props.onChange(key, value, PropertyType.ChildrenDimensions); }}
/>
<InputGroup
labelText={Text({ textId: '@StyleStrokeColor' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.childrenDimensions.color}
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.ChildrenDimensions); }}/>
<TextInputGroup
id={`${props.properties.id}-childrenDimensions-width`}
labelText={Text({ textId: '@StyleStrokeWidth' })}
inputKey='width'
labelClassName=''
inputClassName=''
type='number'
min={0}
value={(props.properties.dimensionOptions.childrenDimensions.width ?? 0).toString()}
onChange={(value) => { props.onChange('width', Number(value), PropertyType.ChildrenDimensions); }}/>
<TextInputGroup
id={`${props.properties.id}-childrenDimensions-dasharray`}
labelText={Text({ textId: '@StyleStrokeDashArray' })}
inputKey='dashArray'
labelClassName=''
inputClassName=''
type='text'
value={props.properties.dimensionOptions.childrenDimensions.dashArray ?? ''}
onChange={(value) => { props.onChange('dashArray', value, PropertyType.ChildrenDimensions); }}/>
</div>
}
{
SHOW_BORROWER_DIMENSIONS &&
<>
<div className='grid grid-cols-1 gap-2'>
<OrientationCheckboxes
id='markPosition'
name='MarkPosition'
value={props.properties.dimensionOptions.markPosition}
labelText={Text({ textId: '@ContainerMarkPosition' })}
onChange={(key, value) => props.onChange(key, value, PropertyType.DimensionOptions)}
/>
</div>
<div className='grid grid-cols-1 gap-2'>
<PositionCheckboxes
id='positionsDimensionWithMarks'
attribute='positions'
name='ShowDimensionWithMarks'
labelText={Text({ textId: '@ContainerShowDimensionWithMarks' })}
value={props.properties.dimensionOptions.dimensionWithMarks.positions}
onChange={(key, value) => props.onChange(key, value, PropertyType.DimensionWithMarks)}
/>
<InputGroup
labelText={Text({ textId: '@Color' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.dimensionWithMarks.color}
onChange={(e) => props.onChange('color', e.target.value, PropertyType.DimensionWithMarks)}/>
</div>
</>
<>
<div className='grid grid-cols-1 gap-2'>
<OrientationCheckboxes
id='markPosition'
name='MarkPosition'
value={props.properties.dimensionOptions.markPosition}
labelText={Text({ textId: '@ContainerMarkPosition' })}
onChange={(key, value) => { props.onChange(key, value, PropertyType.DimensionOptions); }}
/>
</div>
<div className='grid grid-cols-1 gap-2'>
<PositionCheckboxes
id='positionsDimensionWithMarks'
attribute='positions'
name='ShowDimensionWithMarks'
labelText={Text({ textId: '@ContainerShowDimensionWithMarks' })}
value={props.properties.dimensionOptions.dimensionWithMarks.positions}
onChange={(key, value) => { props.onChange(key, value, PropertyType.DimensionWithMarks); }}
/>
<InputGroup
labelText={Text({ textId: '@StyleStrokeColor' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.dimensionWithMarks.color}
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.DimensionWithMarks); }}/>
<TextInputGroup
id={`${props.properties.id}-dimensionWithMarks-width`}
labelText={Text({ textId: '@StyleStrokeWidth' })}
inputKey='width'
labelClassName=''
inputClassName=''
type='number'
min={0}
value={(props.properties.dimensionOptions.dimensionWithMarks.width ?? 0).toString()}
onChange={(value) => { props.onChange('width', Number(value), PropertyType.DimensionWithMarks); }}/>
<TextInputGroup
id={`${props.properties.id}-dimensionWithMarks-dasharray`}
labelText={Text({ textId: '@StyleStrokeDashArray' })}
inputKey='dashArray'
labelClassName=''
inputClassName=''
type='text'
value={props.properties.dimensionOptions.dimensionWithMarks.dashArray ?? ''}
onChange={(value) => { props.onChange('dashArray', value, PropertyType.DimensionWithMarks); }}/>
</div>
</>
}
</div>
</Category>
{props.properties.style !== undefined &&
<Category category={{
Type: 'Style',
DisplayedText: Text({ textId: '@ContainerStyle' })
}}
heightClass={`${categoryHeight}`}
>
<div className='grid grid-cols-5 gap-6 items-center prop-category-body'>
<TextInputGroup
id={`${props.properties.id}-stroke`}
labelText={Text({ textId: '@StyleStroke' })}
inputKey='stroke'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='string'
value={props.properties.style.stroke ?? 'black'}
onChange={(value) => props.onChange('stroke', value, PropertyType.Style)}
/>
<InputGroup
labelKey={`${props.properties.id}-strokeOpacity`}
labelText={Text({ textId: '@StyleStrokeOpacity' })}
inputKey='strokeOpacity'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='range'
min={0}
max={1}
step={0.01}
value={(props.properties.style.strokeOpacity ?? 1).toString()}
onChange={(event) => props.onChange('strokeOpacity', Number(event.target.value), PropertyType.Style)}
/>
<TextInputGroup
id={`${props.properties.id}-strokeWidth`}
labelText={Text({ textId: '@StyleStrokeWidth' })}
inputKey='strokeWidth'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='number'
value={(props.properties.style.strokeWidth ?? 1).toString()}
onChange={(value) => props.onChange('strokeWidth', Number(value), PropertyType.Style)}
/>
<TextInputGroup
id={`${props.properties.id}-fill`}
labelText={Text({ textId: '@StyleFill' })}
inputKey='fill'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='string'
value={props.properties.style.fill ?? 'black'}
onChange={(value) => props.onChange('fill', value, PropertyType.Style)}
/>
<InputGroup
labelKey={`${props.properties.id}-fillOpacity`}
labelText={Text({ textId: '@StyleFillOpacity' })}
inputKey='fillOpacity'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='range'
min={0}
max={1}
step={0.01}
value={(props.properties.style.fillOpacity ?? 1).toString()}
onChange={(event) => props.onChange('fillOpacity', Number(event.target.value), PropertyType.Style)}
/>
</div>
</Category>
<Category category={{
Type: 'Style',
DisplayedText: Text({ textId: '@ContainerStyle' })
}}
heightClass={`${categoryHeight}`}
>
<div className='grid grid-cols-5 gap-6 items-center prop-category-body'>
<TextInputGroup
id={`${props.properties.id}-stroke`}
labelText={Text({ textId: '@StyleStroke' })}
inputKey='stroke'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='string'
value={props.properties.style.stroke ?? 'black'}
onChange={(value) => { props.onChange('stroke', value, PropertyType.Style); }}
/>
<InputGroup
labelKey={`${props.properties.id}-strokeOpacity`}
labelText={Text({ textId: '@StyleStrokeOpacity' })}
inputKey='strokeOpacity'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='range'
min={0}
max={1}
step={0.01}
value={(props.properties.style.strokeOpacity ?? 1).toString()}
onChange={(event) => { props.onChange('strokeOpacity', Number(event.target.value), PropertyType.Style); }}
/>
<TextInputGroup
id={`${props.properties.id}-strokeWidth`}
labelText={Text({ textId: '@StyleStrokeWidth' })}
inputKey='strokeWidth'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='number'
value={(props.properties.style.strokeWidth ?? 1).toString()}
onChange={(value) => { props.onChange('strokeWidth', Number(value), PropertyType.Style); }}
/>
<TextInputGroup
id={`${props.properties.id}-fill`}
labelText={Text({ textId: '@StyleFill' })}
inputKey='fill'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='string'
value={props.properties.style.fill ?? 'black'}
onChange={(value) => { props.onChange('fill', value, PropertyType.Style); }}
/>
<InputGroup
labelKey={`${props.properties.id}-fillOpacity`}
labelText={Text({ textId: '@StyleFillOpacity' })}
inputKey='fillOpacity'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='range'
min={0}
max={1}
step={0.01}
value={(props.properties.style.fillOpacity ?? 1).toString()}
onChange={(event) => { props.onChange('fillOpacity', Number(event.target.value), PropertyType.Style); }}
/>
</div>
</Category>
}
</div>
);

View file

@ -5,6 +5,7 @@ import { PositionReference } from '../../Enums/PositionReference';
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
import { Orientation } from '../../Enums/Orientation';
import { ContainerProperties } from './ContainerProperties';
import { DEFAULT_DIMENSION_OPTION } from '../../utils/default';
describe.concurrent('Properties', () => {
it('No properties', () => {
@ -43,23 +44,11 @@ describe.concurrent('Properties', () => {
warning: '',
hideChildrenInTreeview: false,
dimensionOptions: {
childrenDimensions: {
color: '#000000',
positions: []
},
selfDimensions: {
color: '#000000',
positions: []
},
selfMarginsDimensions: {
color: '#000000',
positions: []
},
childrenDimensions: DEFAULT_DIMENSION_OPTION,
selfDimensions: DEFAULT_DIMENSION_OPTION,
selfMarginsDimensions: DEFAULT_DIMENSION_OPTION,
markPosition: [],
dimensionWithMarks: {
color: '#000000',
positions: []
}
dimensionWithMarks: DEFAULT_DIMENSION_OPTION
}
};

View file

@ -8,6 +8,8 @@ import Swal from 'sweetalert2';
import { PropertyType } from '../../../Enums/PropertyType';
import { TransformX, TransformY } from '../../../utils/svg';
import { Orientation } from '../../../Enums/Orientation';
import { AddContainers } from './AddContainer';
import { IConfiguration } from '../../../Interfaces/IConfiguration';
/**
* Select a container
@ -133,6 +135,58 @@ export function DeleteContainer(
return history;
}
/**
* Replace a container
* @param containerId containerId of the container to delete
* @param newContainerId
* @param configuration
* @param fullHistory History of the editor
* @param historyCurrentStep Current step
* @returns New history
*/
export function ReplaceByContainer(
containerId: string,
newContainerId: string,
configuration: IConfiguration,
fullHistory: IHistoryState[],
historyCurrentStep: number
): IHistoryState[] {
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1];
const containerToReplace = FindContainerById(current.containers, containerId);
if (containerToReplace === undefined) {
return history;
}
const containerParent = FindContainerById(current.containers, containerToReplace.properties.parentId);
if (containerParent === undefined) {
return history;
}
const historyAdd = AddContainers(
containerParent.children.indexOf(containerId),
[{ Type: newContainerId }],
containerParent.properties.id,
configuration, fullHistory, historyCurrentStep
);
const historyDelete = DeleteContainer(containerId, historyAdd.history, historyCurrentStep + 1);
const currentDelete = historyDelete[historyDelete.length - 1];
fullHistory.push({
lastAction: `Replace ${containerId} by ${newContainerId}`,
mainContainer: currentDelete.mainContainer,
containers: currentDelete.containers,
selectedContainerId: currentDelete.selectedContainerId,
typeCounters: Object.assign({}, currentDelete.typeCounters),
symbols: current.symbols,
selectedSymbolId: current.selectedSymbolId
});
return fullHistory;
}
/**
* Returns the next container that will be selected
* after the selectedContainer is removed.

View file

@ -16,6 +16,7 @@ import { AddContainers } from './AddContainer';
import { DeleteContainer } from './ContainerOperations';
import { DeleteSymbol } from './SymbolOperations';
import { Text } from '../../Text/Text';
import { IReplaceContainer } from '../../../Interfaces/IReplaceContainer';
export function InitActions(
menuActions: Map<string, IMenuAction[]>,
@ -23,7 +24,8 @@ export function InitActions(
history: IHistoryState[],
historyCurrentStep: number,
setNewHistory: (newHistory: IHistoryState[]) => void,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
setHistoryCurrentStep: Dispatch<SetStateAction<number>>,
setIsReplacingContainer: Dispatch<SetStateAction<IReplaceContainer>>
): void {
menuActions.set(
'',
@ -56,9 +58,24 @@ export function InitActions(
menuActions.set(
'elements-sidebar-row',
[{
text: Text({ textId: '@ReplaceByContainer' }),
title: Text({ textId: '@ReplaceByContainerTitle' }),
shortcut: '<kbd>R</kbd>',
action: (target: HTMLElement) => {
const targetContainer = FindContainerById(history[historyCurrentStep].containers, target.id);
const targetAvailableContainer = configuration.AvailableContainers.find((availableContainer) => availableContainer.Type === targetContainer?.properties.type);
if (targetAvailableContainer === undefined) {
return;
}
setIsReplacingContainer({ isReplacing: true, id: target.id, category: targetAvailableContainer.Category });
}
}, {
text: Text({ textId: '@DeleteContainer' }),
title: Text({ textId: '@DeleteContainerTitle' }),
shortcut: '<kbd>Suppr</kbd>',
action: (target: HTMLElement) => {
const id = target.id;
const newHistory = DeleteContainer(

View file

@ -53,6 +53,7 @@ export function SaveEditorAsSVG(): void {
svg.replaceChildren(...mainSvg);
// remove the selector
// TODO: Fix this with SelectorMode != Nothing or with some html magic
const group = svg.children[svg.children.length - 1];
group.removeChild(group.children[group.children.length - 1]);
if (SHOW_SELECTOR_TEXT) {

View file

@ -7,7 +7,8 @@ export function OnKey(
history: IHistoryState[],
historyCurrentStep: number,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>,
deleteAction: () => void
deleteAction: () => void,
resetState: () => void
): void {
if (!ENABLE_SHORTCUTS) {
return;
@ -27,5 +28,7 @@ export function OnKey(
setHistoryCurrentStep(historyCurrentStep + 1);
} else if (event.key === 'Delete') {
deleteAction();
} else if (event.key === 'Escape') {
resetState();
}
}

View file

@ -1,9 +1,9 @@
import React, { Dispatch, SetStateAction, useEffect, useRef } from 'react';
import React, { type Dispatch, type SetStateAction, useEffect, useRef } from 'react';
import './Editor.scss';
import { IConfiguration } from '../../Interfaces/IConfiguration';
import { IHistoryState } from '../../Interfaces/IHistoryState';
import { type IConfiguration } from '../../Interfaces/IConfiguration';
import { type IHistoryState } from '../../Interfaces/IHistoryState';
import { UI } from '../UI/UI';
import { SelectContainer, DeleteContainer, OnPropertyChange } from './Actions/ContainerOperations';
import { SelectContainer, DeleteContainer, OnPropertyChange, ReplaceByContainer } from './Actions/ContainerOperations';
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
import { OnKey } from './Actions/Shortcuts';
import { UseCustomEvents, UseEditorListener } from '../../Events/EditorEvents';
@ -13,6 +13,7 @@ import { FindContainerById } from '../../utils/itertools';
import { Menu } from '../Menu/Menu';
import { InitActions } from './Actions/ContextMenuActions';
import { AddContainerToSelectedContainer, AddContainer } from './Actions/AddContainer';
import { type IReplaceContainer } from '../../Interfaces/IReplaceContainer';
interface IEditorProps {
root: Element | Document
@ -25,16 +26,18 @@ function UseShortcuts(
history: IHistoryState[],
historyCurrentStep: number,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>,
deleteAction: () => void
deleteAction: () => void,
resetState: () => void
): void {
useEffect(() => {
function OnKeyUp(event: KeyboardEvent): void {
return OnKey(
OnKey(
event,
history,
historyCurrentStep,
setHistoryCurrentStep,
deleteAction
deleteAction,
resetState
);
}
@ -62,13 +65,20 @@ function UseNewHistoryState(
};
}
export function Editor(props: IEditorProps): JSX.Element {
// States
const [history, setHistory] = React.useState<IHistoryState[]>(structuredClone(props.history));
const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(props.historyCurrentStep);
const [replaceContainer, setReplaceContainer] = React.useState<IReplaceContainer>({ isReplacing: false, id: undefined, category: undefined });
const editorRef = useRef<HTMLDivElement>(null);
const setNewHistory = UseNewHistoryState(setHistory, setHistoryCurrentStep);
function ResetState(): void {
setReplaceContainer({ isReplacing: false, id: undefined, category: undefined });
}
// Events
UseShortcuts(
history,
@ -79,7 +89,8 @@ export function Editor(props: IEditorProps): JSX.Element {
setNewHistory(
DeleteContainer(current.selectedContainerId, history, historyCurrentStep)
);
}
},
ResetState
);
UseCustomEvents(
props.root,
@ -104,7 +115,8 @@ export function Editor(props: IEditorProps): JSX.Element {
history,
historyCurrentStep,
setNewHistory,
setHistoryCurrentStep
setHistoryCurrentStep,
setReplaceContainer
);
// Render
@ -113,87 +125,118 @@ export function Editor(props: IEditorProps): JSX.Element {
const selected = FindContainerById(current.containers, current.selectedContainerId);
return (
<div ref={editorRef} className="Editor font-sans h-full">
<div ref={editorRef} className="Editor font-sans h-full ">
<UI
editorState={{
configuration: props.configuration,
history,
historyCurrentStep
}}
selectContainer={(container) => setNewHistory(
SelectContainer(
container,
history,
historyCurrentStep
))}
deleteContainer={(containerId: string) => setNewHistory(
DeleteContainer(
containerId,
history,
historyCurrentStep
))}
onPropertyChange={(key, value, type) => setNewHistory(
OnPropertyChange(
key, value, type,
selected,
history,
historyCurrentStep
))}
addContainer={(type) => {
replaceContainer={replaceContainer}
selectContainer={(container) => {
setNewHistory(
SelectContainer(
container,
history,
historyCurrentStep
));
}}
deleteContainer={(containerId: string) => {
setNewHistory(
DeleteContainer(
containerId,
history,
historyCurrentStep
));
}}
onPropertyChange={(key, value, type) => {
setNewHistory(
OnPropertyChange(
key, value, type,
selected,
history,
historyCurrentStep
));
}}
addOrReplaceContainer={(type) => {
if (selected === null || selected === undefined) {
return;
}
setNewHistory(AddContainerToSelectedContainer(
type,
selected,
configuration,
history,
historyCurrentStep
));
if (replaceContainer.isReplacing && replaceContainer.id !== undefined) {
const newHistory = ReplaceByContainer(
replaceContainer.id,
type,
configuration,
history,
historyCurrentStep
);
setReplaceContainer({ isReplacing: false, id: undefined, category: undefined });
setNewHistory(newHistory);
} else {
setNewHistory(AddContainerToSelectedContainer(
type,
selected,
configuration,
history,
historyCurrentStep
));
}
}}
addContainerAt={(index, type, parent) => setNewHistory(
AddContainer(
index,
type,
parent,
configuration,
addContainerAt={(index, type, parent) => {
setNewHistory(
AddContainer(
index,
type,
parent,
configuration,
history,
historyCurrentStep
)
);
}}
addSymbol={(type) => {
setNewHistory(
AddSymbol(
type,
configuration,
history,
historyCurrentStep
));
}}
onSymbolPropertyChange={(key, value) => {
setNewHistory(
OnSymbolPropertyChange(
key, value,
history,
historyCurrentStep
));
}}
selectSymbol={(symbolId) => {
setNewHistory(
SelectSymbol(
symbolId,
history,
historyCurrentStep
));
}}
deleteSymbol={(symbolId) => {
setNewHistory(
DeleteSymbol(
symbolId,
history,
historyCurrentStep
));
}}
saveEditorAsJSON={() => {
SaveEditorAsJSON(
history,
historyCurrentStep
)
)}
addSymbol={(type) => setNewHistory(
AddSymbol(
type,
configuration,
history,
historyCurrentStep
))}
onSymbolPropertyChange={(key, value) => setNewHistory(
OnSymbolPropertyChange(
key, value,
history,
historyCurrentStep
))}
selectSymbol={(symbolId) => setNewHistory(
SelectSymbol(
symbolId,
history,
historyCurrentStep
))}
deleteSymbol={(symbolId) => setNewHistory(
DeleteSymbol(
symbolId,
history,
historyCurrentStep
))}
saveEditorAsJSON={() => SaveEditorAsJSON(
history,
historyCurrentStep,
configuration
)}
saveEditorAsSVG={() => SaveEditorAsSVG()}
loadState={(move) => setHistoryCurrentStep(move)}
historyCurrentStep,
configuration
);
}}
saveEditorAsSVG={() => { SaveEditorAsSVG(); }}
loadState={(move) => { setHistoryCurrentStep(move); }}
setReplaceContainer={setReplaceContainer}
/>
<Menu
getListener={() => editorRef.current}

View file

@ -1,21 +1,22 @@
import * as React from 'react';
import { useState } from 'react';
import useSize from '@react-hook/size';
import { FixedSizeList as List } from 'react-window';
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
import { ContainerProperties } from '../ContainerProperties/ContainerProperties';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { PropertyType } from '../../Enums/PropertyType';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { type PropertyType } from '../../Enums/PropertyType';
import { ToggleSideBar } from '../Sidebar/ToggleSideBar/ToggleSideBar';
import { Text } from '../Text/Text';
import { ExtendedSidebar } from '../UI/UI';
interface IElementsSideBarProps {
interface IElementsSidebarProps {
containers: Map<string, IContainerModel>
mainContainer: IContainerModel
symbols: Map<string, ISymbolModel>
selectedContainer: IContainerModel | undefined
selectedExtendedSidebar: ExtendedSidebar
onPropertyChange: (
key: string,
value: string | number | boolean | number[],
@ -23,8 +24,7 @@ interface IElementsSideBarProps {
) => void
selectContainer: (containerId: string) => void
addContainer: (index: number, type: string, parent: string) => void
isExpanded: boolean
onExpandChange: () => void
onExpandChange: (value: ExtendedSidebar) => void
}
function RemoveBorderClasses(target: HTMLButtonElement, exception: string = ''): void {
@ -124,11 +124,10 @@ function HandleOnDrop(
}
}
export function ElementsSideBar(props: IElementsSideBarProps): JSX.Element {
export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
// States
const divRef = React.useRef<HTMLDivElement>(null);
const [,height] = useSize(divRef);
const [showProperties, setShowProperties] = useState(props.isExpanded);
// Render
const it = MakeRecursionDFSIterator(props.mainContainer, props.containers, 0, [0, 0], true);
@ -167,18 +166,27 @@ export function ElementsSideBar(props: IElementsSideBarProps): JSX.Element {
return (
<div className='flex flex-row h-full w-full' >
{showProperties &&
<div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'>
<ContainerProperties
properties={props.selectedContainer?.properties}
symbols={props.symbols}
onChange={props.onPropertyChange}
/>
</div>
{props.selectedExtendedSidebar === ExtendedSidebar.Property &&
<div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'>
<ContainerProperties
properties={props.selectedContainer?.properties}
symbols={props.symbols}
onChange={props.onPropertyChange}
/>
</div>
}
<div className='flex w-64' ref={divRef}>
<div className='w-6'>
<ToggleSideBar title={Text({ textId: '@Properties' })} checked={showProperties} onChange={(newValue) => { setShowProperties(newValue); props.onExpandChange(); }} />
<ToggleSideBar
title={Text({ textId: '@Properties' })}
checked={props.selectedExtendedSidebar === ExtendedSidebar.Property}
onClick={() => {
const newValue = props.selectedExtendedSidebar !== ExtendedSidebar.Property
? ExtendedSidebar.Property
: ExtendedSidebar.None;
props.onExpandChange(newValue);
}}
/>
</div>
<List
itemCount={containers.length}
@ -229,10 +237,10 @@ function ElementsListRow(
key={key}
style={style}
title={container.properties.warning}
onClick={() => selectContainer(container.properties.id)}
onDrop={(event) => HandleOnDrop(event, containers, mainContainer, addContainer)}
onDragOver={(event) => HandleDragOver(event, mainContainer)}
onDragLeave={(event) => HandleDragLeave(event)}
onClick={() => { selectContainer(container.properties.id); }}
onDrop={(event) => { HandleOnDrop(event, containers, mainContainer, addContainer); }}
onDragOver={(event) => { HandleDragOver(event, mainContainer); }}
onDragLeave={(event) => { HandleDragLeave(event); }}
>
{verticalBars}
{text}

View file

@ -21,6 +21,7 @@ export interface IMenuAction {
/** function to be called on button click */
action: (target: HTMLElement) => void
}
function UseMouseEvents(
@ -139,7 +140,7 @@ function AddClassSpecificActions(
onClick={() => action.action(target)} />);
});
children.push(<hr key={`contextmenu-hr-${count}`} className='border-slate-400' />);
};
}
return count;
}

View file

@ -1,100 +0,0 @@
import * as React from 'react';
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN } from '../../../utils/default';
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
import { TransformX } from '../../../utils/svg';
import { Dimension } from './Dimension';
interface IDimensionLayerProps {
containers: Map<string, IContainerModel>
roots: IContainerModel | IContainerModel[] | null
scale?: number
}
function GetDimensionsNodes(
containers: Map<string, IContainerModel>,
root: IContainerModel,
scale: number
): React.ReactNode[] {
const it = MakeBFSIterator(root, containers);
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, scale, '#000000', dimensions);
currentDepth = depth;
min = Infinity;
max = -Infinity;
}
const absoluteX = GetAbsolutePosition(containers, container)[0];
const x = TransformX(absoluteX, container.properties.width, container.properties.positionReference);
lastY = container.properties.y + container.properties.height;
if (x < min) {
min = x;
}
if (x > max) {
max = x;
}
}
AddNewDimension(currentDepth, min, max, lastY, scale, '#000000', dimensions);
return dimensions;
}
/**
* A layer containing all dimension
* @param props
* @returns
*/
export function DepthDimensionLayer(props: IDimensionLayerProps): JSX.Element {
let dimensions: React.ReactNode[] = [];
const scale = props.scale ?? 1;
if (Array.isArray(props.roots)) {
props.roots.forEach(child => {
dimensions.concat(GetDimensionsNodes(props.containers, child, scale));
});
} else if (props.roots !== null) {
dimensions = GetDimensionsNodes(props.containers, props.roots, scale);
}
return (
<g>
{dimensions}
</g>
);
}
function AddNewDimension(currentDepth: number, min: number, max: number, lastY: number, scale: number, color: string, dimensions: React.ReactNode[]): void {
const id = `dim-depth-${currentDepth}`;
const xStart = min;
const xEnd = max;
const y = lastY + (DIMENSION_MARGIN * (currentDepth + 1)) / scale;
const width = xEnd - xStart;
const text = width
.toFixed(0)
.toString();
if (width === 0) {
return;
}
dimensions.push(
<Dimension
key={id}
id={id}
xStart={xStart}
yStart={y}
xEnd={xEnd}
yEnd={y}
text={text}
scale={scale}
color={color}
/>
);
}

View file

@ -1,6 +1,9 @@
import * as React from 'react';
import { type IDimensionOptions } from '../../../Interfaces/IDimensionOptions';
import { NOTCHES_LENGTH } from '../../../utils/default';
export type IDimensionStyle = Omit<IDimensionOptions, 'positions'>;
interface IDimensionProps {
id: string
xStart: number
@ -8,7 +11,7 @@ interface IDimensionProps {
xEnd: number
yEnd: number
text: string
color: string
style: IDimensionStyle
scale?: number
}
@ -28,8 +31,9 @@ function ApplyParametric(x0: number, t: number, vx: number): number {
export function Dimension(props: IDimensionProps): JSX.Element {
const scale = props.scale ?? 1;
const style: React.CSSProperties = {
stroke: props.color,
strokeWidth: 2 / scale
stroke: props.style.color,
strokeWidth: (props.style.width ?? 2) / scale,
strokeDasharray: props.style.dashArray
};
/// We need to find the points of the notches
@ -79,9 +83,11 @@ export function Dimension(props: IDimensionProps): JSX.Element {
x2={endBottomX}
y2={endBottomY}
style={style}/>
<text textAnchor={'middle'} alignmentBaseline={'central'}
<text
x={textX}
y={textY}
textAnchor={'middle'}
alignmentBaseline={'central'}
style={{
transform: `rotate(${rotation}turn) scale(${1 / scale})`,
transformOrigin: `${textX}px ${textY}px`

View file

@ -1,7 +1,6 @@
import * as React from 'react';
import { Orientation } from '../../../Enums/Orientation';
import { Position } from '../../../Enums/Position';
import { type IContainerModel } from '../../../Interfaces/IContainerModel';
import {
DIMENSION_MARGIN,
SHOW_BORROWER_DIMENSIONS,
@ -11,7 +10,8 @@ import {
} from '../../../utils/default';
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
import { TransformX, TransformY } from '../../../utils/svg';
import { Dimension } from './Dimension';
import { Dimension, type IDimensionStyle } from './Dimension';
import { type IContainerModel } from '../../../Interfaces/IContainerModel';
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
interface IDimensionLayerProps {
@ -252,10 +252,10 @@ function AddHorizontalChildrenDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const childrenId = `dim-y${yDim.toFixed(0)}-children-${container.properties.id}`;
const style = container.properties.dimensionOptions.childrenDimensions;
const lastChildId = container.children[container.children.length - 1];
const lastChild = FindContainerById(containers, lastChildId);
@ -305,7 +305,7 @@ function AddHorizontalChildrenDimension(
yEnd={yDim}
text={textChildren}
scale={scale}
color={color}/>);
style={style}/>);
}
function AddVerticalChildrenDimension(
@ -315,10 +315,10 @@ function AddVerticalChildrenDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const childrenId = `dim-x${xDim.toFixed(0)}-children-${container.properties.id}`;
const style = container.properties.dimensionOptions.childrenDimensions;
const lastChildId = container.children[container.children.length - 1];
const lastChild = FindContainerById(containers, lastChildId);
@ -373,7 +373,7 @@ function AddVerticalChildrenDimension(
yEnd={yChildrenEnd + offset}
text={textChildren}
scale={scale}
color={color}
style={style}
/>);
}
@ -384,9 +384,9 @@ function AddHorizontalBorrowerDimension(
depth: number,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const style = container.properties.dimensionOptions.dimensionWithMarks;
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
const marks = []; // list of vertical lines for the dimension
for (const {
@ -431,7 +431,7 @@ function AddHorizontalBorrowerDimension(
yEnd={yDim}
text={value.toFixed(0)}
scale={scale}
color={color}/>);
style={style}/>);
count++;
}
}
@ -444,9 +444,9 @@ function AddVerticalBorrowerDimension(
depth: number,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const style = container.properties.dimensionOptions.dimensionWithMarks;
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
const marks = []; // list of vertical lines for the dimension
for (const {
@ -496,7 +496,7 @@ function AddVerticalBorrowerDimension(
yEnd={next}
text={value.toFixed(0)}
scale={scale}
color={color}/>);
style={style}/>);
count++;
}
}
@ -507,9 +507,9 @@ function AddVerticalSelfDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const style = container.properties.dimensionOptions.selfDimensions;
const height = container.properties.height;
const idVert = `dim-x${xDim.toFixed(0)}-${container.properties.id}`;
let yStart = container.properties.y + currentTransform[1] + height;
@ -532,7 +532,7 @@ function AddVerticalSelfDimension(
yEnd={yEnd}
text={textVert}
scale={scale}
color={color}/>
style={style}/>
);
}
@ -541,9 +541,9 @@ function AddHorizontalSelfDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const style = container.properties.dimensionOptions.selfDimensions;
const width = container.properties.width;
const id = `dim-y${yDim.toFixed(0)}-${container.properties.id}`;
const xStart = container.properties.x + currentTransform[0];
@ -561,7 +561,7 @@ function AddHorizontalSelfDimension(
yEnd={yDim}
text={text}
scale={scale}
color={color}/>
style={style}/>
);
}
@ -570,9 +570,9 @@ function AddHorizontalSelfMarginsDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const style = container.properties.dimensionOptions.selfMarginsDimensions;
const left = container.properties.margin.left;
if (left != null) {
const id = `dim-y-margin-left${yDim.toFixed(0)}-${container.properties.id}`;
@ -591,7 +591,7 @@ function AddHorizontalSelfMarginsDimension(
yEnd={yDim}
text={text}
scale={scale}
color={color}/>
style={style}/>
);
}
@ -613,7 +613,7 @@ function AddHorizontalSelfMarginsDimension(
yEnd={yDim}
text={text}
scale={scale}
color={color}/>
style={style}/>
);
}
}
@ -624,9 +624,9 @@ function AddVerticalSelfMarginDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const style = container.properties.dimensionOptions.selfMarginsDimensions;
const top = container.properties.margin.top;
if (top != null) {
const idVert = `dim-x-margin-top${xDim.toFixed(0)}-${container.properties.id}`;
@ -650,7 +650,7 @@ function AddVerticalSelfMarginDimension(
yEnd={yEnd}
text={textVert}
scale={scale}
color={color}/>
style={style}/>
);
}
const bottom = container.properties.margin.bottom;
@ -676,7 +676,44 @@ function AddVerticalSelfMarginDimension(
yEnd={yEnd}
text={textVert}
scale={scale}
color={color}/>
style={style}/>
);
}
}
function AddHorizontalSymbolDimension(
symbol: ISymbolModel,
dimensions: React.ReactNode[],
scale: number,
depth: number
): void {
const width = symbol.x + (symbol.width / 2);
if (width == null || width <= 0) {
return;
}
const id = `dim-y-margin-left${symbol.width.toFixed(0)}-${symbol.id}`;
const offset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const text = width
.toFixed(0)
.toString();
// TODO: Put this in default.ts
const defaultDimensionSymbolStyle: IDimensionStyle = {
color: 'black'
};
dimensions.push(
<Dimension
key={id}
id={id}
xStart={0}
yStart={-offset}
xEnd={width}
yEnd={-offset}
text={text}
scale={scale}
style={defaultDimensionSymbolStyle}/>
);
}

View file

@ -0,0 +1,54 @@
import '../Selector.scss';
import * as React from 'react';
import { SHOW_SELECTOR_TEXT } from '../../../../utils/default';
interface ISelectorProps {
text: string
x: number
y: number
width: number
height: number
scale: number
style?: React.CSSProperties
}
export function Selector({ text, x, y, width, height, scale, style: overrideStyle }: ISelectorProps): JSX.Element {
const xText = x + width / 2;
const yText = y + height / 2;
const style: React.CSSProperties = {
stroke: '#3B82F6',
strokeWidth: 4 / scale,
fillOpacity: 0,
transitionProperty: 'all',
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
transitionDuration: '150ms',
animation: 'fadein 750ms ease-in alternate infinite',
...overrideStyle
};
return (
<>
<rect
x={x}
y={y}
width={width}
height={height}
style={style}
>
</rect>
{SHOW_SELECTOR_TEXT
? <text
x={xText}
y={yText}
style={{
transform: `scale(${1 / scale}) translateX(-50%)`,
transformBox: 'fill-box'
}}
>
{ text }
</text>
: null}
</>
);
}

View file

@ -1,9 +1,9 @@
import '../Selector.scss';
import * as React from 'react';
import { type IContainerModel } from '../../../../Interfaces/IContainerModel';
import { SHOW_SELECTOR_TEXT } from '../../../../utils/default';
import { GetAbsolutePosition } from '../../../../utils/itertools';
import { RemoveMargin } from '../../../../utils/svg';
import { Selector } from '../Selector/Selector';
interface ISelectorContainerProps {
containers: Map<string, IContainerModel>
@ -33,41 +33,14 @@ export function SelectorContainer(props: ISelectorContainerProps): JSX.Element {
props.selected.properties.margin.right
));
const xText = x + width / 2;
const yText = y + height / 2;
const style: React.CSSProperties = {
stroke: '#3B82F6',
strokeWidth: 4 / scale,
fillOpacity: 0,
transitionProperty: 'all',
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
transitionDuration: '150ms',
animation: 'fadein 750ms ease-in alternate infinite'
};
return (
<>
<rect
x={x}
y={y}
width={width}
height={height}
style={style}
>
</rect>
{SHOW_SELECTOR_TEXT
? <text
x={xText}
y={yText}
style={{
transform: `scale(${1 / scale}) translateX(-50%)`,
transformBox: 'fill-box'
}}
>
{props.selected.properties.displayedText}
</text>
: null}
</>
<Selector
text={props.selected.properties.displayedText}
x={x}
y={y}
width={width}
height={height}
scale={scale}
/>
);
}

View file

@ -1,7 +1,8 @@
import '../Selector.scss';
import * as React from 'react';
import { SHOW_SELECTOR_TEXT, SYMBOL_MARGIN } from '../../../../utils/default';
import { SYMBOL_MARGIN } from '../../../../utils/default';
import { type ISymbolModel } from '../../../../Interfaces/ISymbolModel';
import { Selector } from '../Selector/Selector';
interface ISelectorSymbolProps {
symbols: Map<string, ISymbolModel>
@ -19,7 +20,7 @@ export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element {
const scale = (props.scale ?? 1);
const [width, height] = [
props.selected.width,
props.selected.width / scale,
props.selected.height / scale
];
@ -29,45 +30,28 @@ export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element {
x = -SYMBOL_MARGIN;
y = props.selected.offset;
} else {
x = props.selected.offset;
y = -SYMBOL_MARGIN - height;
[x,y] = [
props.selected.offset + props.selected.width / 2,
-SYMBOL_MARGIN - height]
}
const xText = x + width / 2;
const yText = y + height / 2;
const style: React.CSSProperties = {
stroke: '#3B82F6',
strokeWidth: 4 / scale,
fillOpacity: 0,
transitionProperty: 'all',
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
transitionDuration: '150ms',
animation: 'fadein 750ms ease-in alternate infinite'
transform: 'translateX(-50%)',
transformBox: 'fill-box'
};
return (
<>
<rect
x={x}
y={y}
width={width}
height={height}
style={style}
>
</rect>
{SHOW_SELECTOR_TEXT
? <text
x={xText}
y={yText}
style={{
transform: `scale(${1 / scale}) translateX(-50%)`,
transformBox: 'fill-box'
}}
>
{props.selected.displayedText}
</text>
: null}
</>
<Selector
text={props.selected.displayedText}
x={x}
y={y}
width={width}
height={height}
scale={scale}
style={style}
/>
);
}

View file

@ -1,14 +1,13 @@
import * as React from 'react';
import { ReactSVGPanZoom, type Tool, TOOL_PAN, type Value } from 'react-svg-pan-zoom';
import { ReactSVGPanZoom, type Tool, TOOL_PAN, type Value, ALIGN_CENTER } from 'react-svg-pan-zoom';
import { Container } from './Elements/Container';
import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { SelectorContainer } from './Elements/SelectorContainer/SelectorContainer';
import { DepthDimensionLayer } from './Elements/DepthDimensionLayer';
import { MAX_FRAMERATE, SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default';
import { MAX_FRAMERATE } from '../../utils/default';
import { SymbolLayer } from './Elements/SymbolLayer';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { DimensionLayer } from './Elements/DimensionLayer';
import { SelectorSymbol } from './Elements/SelectorSymbol/SelectorSymbol';
import { type IToolbarProps, Toolbar } from './SVGReactPanZoom/ui-toolbar/toolbar';
import { type DrawParams } from '../Viewer/Viewer';
interface ISVGProps {
className?: string
@ -16,19 +15,27 @@ interface ISVGProps {
viewerHeight: number
width: number
height: number
containers: Map<string, IContainerModel>
children: IContainerModel
selectedContainer?: IContainerModel
symbols: Map<string, ISymbolModel>
selectedSymbol?: ISymbolModel
drawParams: DrawParams
selectContainer: (containerId: string) => void
isComponentsOpen: boolean
isSymbolsOpen: boolean
}
export enum SelectorMode {
Nothing,
Containers,
Symbols
}
export const ID = 'svg';
export function SVG(props: ISVGProps): JSX.Element {
const {
mainContainer,
selectorMode,
selectedContainer,
selectedSymbol,
containers,
symbols
} = props.drawParams;
const [tool, setTool] = React.useState<Tool>(TOOL_PAN);
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const [value, setValue] = React.useState<Value>({} as Value);
@ -54,14 +61,33 @@ export function SVG(props: ISVGProps): JSX.Element {
};
const children: React.ReactNode | React.ReactNode[] = <Container
key={`container-${props.children.properties.id}`}
containers={props.containers}
model={props.children}
key={`container-${mainContainer.properties.id}`}
containers={containers}
model={mainContainer}
depth={0}
scale={scale}
selectContainer={props.selectContainer}
/>;
function Selector(): JSX.Element {
switch (selectorMode) {
case SelectorMode.Containers:
return <SelectorContainer
containers={containers}
scale={scale}
selected={selectedContainer}
/>;
case SelectorMode.Symbols:
return <SelectorSymbol
symbols={symbols}
scale={scale}
selected={selectedSymbol}
/>;
default:
return <></>;
}
}
return (
<div id={ID} className={props.className}>
<ReactSVGPanZoom
@ -86,6 +112,9 @@ export function SVG(props: ISVGProps): JSX.Element {
const value = event as Value;
setScale(value.a);
}}
onDoubleClick={() => {
svgViewer?.current?.setPointOnViewerCenter(props.width / 2, props.height / 2, 0.8);
}}
background={'#ffffff'}
defaultTool='pan'
miniatureProps={{
@ -94,17 +123,25 @@ export function SVG(props: ISVGProps): JSX.Element {
width: 120,
height: 120
}}
customToolbar={(props: IToolbarProps) => (
<Toolbar
{...props}
SVGAlignX={ALIGN_CENTER}
SVGAlignY={ALIGN_CENTER}
fittingScale={0.8}
/>
)}
>
<svg {...properties}>
{children}
{SHOW_DIMENSIONS_PER_DEPTH
? <DepthDimensionLayer containers={props.containers} scale={scale} roots={props.children} />
: null}
<DimensionLayer containers={props.containers} symbols={props.symbols} scale={scale} root={props.children} />
<SymbolLayer scale={scale} symbols={props.symbols} />
{/* leave this at the end so it can be removed during the svg export */}
{ props.isComponentsOpen ? <SelectorContainer containers={props.containers} scale={scale} selected={props.selectedContainer} /> : null }
{ props.isSymbolsOpen ? <SelectorSymbol symbols={props.symbols} scale={scale} selected={props.selectedSymbol} /> : null }
<DimensionLayer
containers={containers}
symbols={symbols}
scale={scale}
root={mainContainer}
/>
<SymbolLayer scale={scale} symbols={symbols} />
<Selector />
</svg>
</ReactSVGPanZoom>
</div>
@ -112,7 +149,8 @@ export function SVG(props: ISVGProps): JSX.Element {
}
function UseFitOnce(svgViewer: React.RefObject<ReactSVGPanZoom>, width: number, height: number): void {
React.useEffect(() => {
svgViewer?.current?.fitToViewer();
React.useCallback(() => {
// TODO: Fix this
svgViewer?.current?.setPointOnViewerCenter(width / 2, height / 2, 0.8);
}, [svgViewer, width, height]);
}

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 https://github.com/chrvadala
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,81 @@
import React from 'react';
import { POSITION_TOP, POSITION_BOTTOM } from 'react-svg-pan-zoom';
interface IToolbarButtonProps {
title: string
name: string
toolbarPosition: string
activeColor: string
onClick: (event: React.MouseEvent | React.TouchEvent) => void
active: boolean
children: JSX.Element | JSX.Element[]
}
interface IToolbarButtonState {
hover: boolean
}
export class ToolbarButton extends React.Component<IToolbarButtonProps, IToolbarButtonState> {
public state: IToolbarButtonState;
constructor(props: IToolbarButtonProps) {
super(props);
this.state = { hover: false };
}
change(event: (React.MouseEvent | React.TouchEvent)): void {
event.preventDefault();
event.stopPropagation();
switch (event.type) {
case 'mouseenter':
case 'touchstart':
this.setState({ hover: true });
break;
case 'mouseleave':
case 'touchend':
case 'touchcancel':
this.setState({ hover: false });
break;
default:
// noop
}
}
render(): JSX.Element {
const style = {
display: 'block',
width: '24px',
height: '24px',
margin: [POSITION_TOP, POSITION_BOTTOM].includes(this.props.toolbarPosition) ? '2px 1px' : '1px 2px',
color: this.props.active || this.state.hover ? this.props.activeColor : '#FFF',
transition: 'color 200ms ease',
background: 'none',
padding: '0px',
border: '0px',
outline: '0px',
cursor: 'pointer'
};
return (
<button
onMouseEnter={e => { this.change(e); }}
onMouseLeave={e => { this.change(e); }}
onTouchStart={e => {
this.change(e);
this.props.onClick(e);
}}
onTouchEnd={e => { this.change(e); }}
onTouchCancel={e => { this.change(e); }}
onClick={this.props.onClick}
style={style}
title={this.props.title}
name={this.props.name}
type="button"
>{this.props.children}</button>
);
}
}

View file

@ -0,0 +1,164 @@
import {
ArrowsPointingOutIcon,
CursorArrowRaysIcon,
HandRaisedIcon,
MagnifyingGlassMinusIcon,
MagnifyingGlassPlusIcon
} from '@heroicons/react/24/outline';
import React from 'react';
import { fromObject, scale, transform, translate } from 'transformation-matrix';
import {
fitToViewer,
POSITION_TOP,
POSITION_BOTTOM,
POSITION_LEFT,
POSITION_RIGHT,
TOOL_NONE,
TOOL_PAN,
TOOL_ZOOM_IN,
TOOL_ZOOM_OUT, ALIGN_LEFT, ALIGN_TOP,
type Value,
type Tool,
type ALIGN_BOTTOM,
type ALIGN_CENTER,
type ALIGN_RIGHT,
type ToolbarPosition
} from 'react-svg-pan-zoom';
import { ToolbarButton } from './toolbar-button';
export interface IToolbarProps {
tool: Tool
value: Value
onChangeValue: (value: Value) => void
onChangeTool: (tool: Tool) => void
activeToolColor?: string
position?: ToolbarPosition | undefined
SVGAlignX?: typeof ALIGN_CENTER | typeof ALIGN_LEFT | typeof ALIGN_RIGHT | undefined
SVGAlignY?: typeof ALIGN_CENTER | typeof ALIGN_TOP | typeof ALIGN_BOTTOM | undefined
fittingScale?: number | undefined
}
/**
* Change value
* @param value
* @param patch
* @param action
* @returns {Object}
*/
function set(value: Value, patch: object, action = null): Value {
value = Object.assign({}, value, patch, { lastAction: action });
return Object.freeze(value);
}
export function Toolbar({
tool,
value,
onChangeValue,
onChangeTool,
activeToolColor = '#1CA6FC',
position = POSITION_RIGHT,
SVGAlignX = ALIGN_LEFT,
SVGAlignY = ALIGN_TOP,
fittingScale = undefined
}: IToolbarProps): JSX.Element {
function handleChangeTool(event: React.MouseEvent | React.TouchEvent, tool: Tool): void {
onChangeTool(tool);
event.stopPropagation();
event.preventDefault();
};
function handleFit(event: React.MouseEvent | React.TouchEvent): void {
let fittedValue: Value = fitToViewer(value, SVGAlignX, SVGAlignY);
if (fittingScale !== undefined) {
const { viewerWidth, viewerHeight } = fittedValue;
const matrix = transform(
fromObject(fittedValue),
translate(viewerWidth, viewerHeight),
scale(fittingScale, fittingScale),
translate(-viewerWidth, -viewerHeight)
);
fittedValue = set(fittedValue, {
...matrix
});
}
onChangeValue(fittedValue);
event.stopPropagation();
event.preventDefault();
};
const isHorizontal = [POSITION_TOP, POSITION_BOTTOM].includes(position);
const style: React.CSSProperties = {
// position
position: 'absolute',
transform: [POSITION_TOP, POSITION_BOTTOM].includes(position) ? 'translate(-50%, 0px)' : 'none',
top: [POSITION_LEFT, POSITION_RIGHT, POSITION_TOP].includes(position) ? '5px' : 'unset',
left: [POSITION_TOP, POSITION_BOTTOM].includes(position) ? '50%' : (POSITION_LEFT === position ? '5px' : 'unset'),
right: [POSITION_RIGHT].includes(position) ? '5px' : 'unset',
bottom: [POSITION_BOTTOM].includes(position) ? '5px' : 'unset',
// inner styling
backgroundColor: 'rgba(19, 20, 22, 0.90)',
borderRadius: '2px',
display: 'flex',
flexDirection: isHorizontal ? 'row' : 'column',
padding: isHorizontal ? '1px 2px' : '2px 1px'
};
return (
<div style={style} role="toolbar">
<ToolbarButton
toolbarPosition={position}
active={tool === TOOL_NONE}
activeColor={activeToolColor}
name="unselect-tools"
title="Selection"
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleChangeTool(event, TOOL_NONE); } }>
<CursorArrowRaysIcon/>
</ToolbarButton>
<ToolbarButton
toolbarPosition={position}
active={tool === TOOL_PAN}
activeColor={activeToolColor}
name="select-tool-pan"
title="Pan"
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleChangeTool(event, TOOL_PAN); } }>
<HandRaisedIcon/>
</ToolbarButton>
<ToolbarButton
toolbarPosition={position}
active={tool === TOOL_ZOOM_IN}
activeColor={activeToolColor}
name="select-tool-zoom-in"
title="Zoom in"
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleChangeTool(event, TOOL_ZOOM_IN); } }>
<MagnifyingGlassPlusIcon/>
</ToolbarButton>
<ToolbarButton
toolbarPosition={position}
active={tool === TOOL_ZOOM_OUT}
activeColor={activeToolColor}
name="select-tool-zoom-out"
title="Zoom out"
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleChangeTool(event, TOOL_ZOOM_OUT); } }>
<MagnifyingGlassMinusIcon/>
</ToolbarButton>
<ToolbarButton
toolbarPosition={position}
active={false}
activeColor={activeToolColor}
name="fit-to-viewer"
title="Fit to viewer"
onClick={ (event: React.MouseEvent | React.TouchEvent) => { handleFit(event); } }>
<ArrowsPointingOutIcon/>
</ToolbarButton>
</div>
);
}

View file

@ -4,16 +4,16 @@ import './ToggleSideBar.scss';
interface IToggleSidebarProps {
title: string
checked: boolean
onChange: (newValue: boolean) => void
onClick: () => void
}
export function ToggleSideBar({ title, checked, onChange }: IToggleSidebarProps): JSX.Element {
export function ToggleSideBar({ title, checked, onClick }: IToggleSidebarProps): JSX.Element {
return (
<div className={`${(checked ? 'bg-slate-400 hover:bg-slate-500' : 'bg-slate-300 hover:bg-slate-400')}`}>
<button
className={'w-full py-2'}
type='button'
onClick={() => onChange(!checked)}
onClick={onClick}
>
<p className='text-vertical'>{title}
</p>

View file

@ -5,39 +5,47 @@ import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { SymbolProperties } from '../SymbolProperties/SymbolProperties';
import { ToggleSideBar } from '../Sidebar/ToggleSideBar/ToggleSideBar';
import { Text } from '../Text/Text';
import { useState } from 'react';
import { ExtendedSidebar } from '../UI/UI';
interface ISymbolsSidebarProps {
selectedSymbolId: string
symbols: Map<string, ISymbolModel>
selectedExtendedSidebar: ExtendedSidebar
onPropertyChange: (key: string, value: string | number | boolean) => void
selectSymbol: (symbolId: string) => void
isExpanded: boolean
onExpandChange: (isExpanded: boolean) => void
onExpandChange: (value: ExtendedSidebar) => void
}
export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element {
// States
const divRef = React.useRef<HTMLDivElement>(null);
const height = useSize(divRef)[1];
const [showProperties, setShowProperties] = useState(props.isExpanded);
// Render
const symbols = [...props.symbols.values()];
const selectedSymbol = props.symbols.get(props.selectedSymbolId);
return (
<div className='flex flex-row h-full w-full'>
{showProperties && <div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'>
{(selectedSymbol == null) && <h1 className={'p-4'}>{Text({ textId: '@NoSymbolSelected' })}</h1>}
<SymbolProperties
symbol={selectedSymbol}
symbols={props.symbols}
onChange={props.onPropertyChange}
/>
</div>}
{props.selectedExtendedSidebar === ExtendedSidebar.Property &&
<div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'>
{(selectedSymbol == null) && <h1 className={'p-4'}>{Text({ textId: '@NoSymbolSelected' })}</h1>}
<SymbolProperties
symbol={selectedSymbol}
symbols={props.symbols}
onChange={props.onPropertyChange}
/>
</div>}
<div className={'flex w-64'} ref={divRef}>
<div className='w-6'>
<ToggleSideBar title={Text({ textId: '@Properties' })} checked={showProperties} onChange={(newValue) => { setShowProperties(newValue); props.onExpandChange(newValue); }} />
<ToggleSideBar
title={Text({ textId: '@Properties' })}
checked={props.selectedExtendedSidebar === ExtendedSidebar.Property}
onClick={() => {
const newValue = props.selectedExtendedSidebar !== ExtendedSidebar.Property
? ExtendedSidebar.Property
: ExtendedSidebar.None;
props.onExpandChange(newValue);
}} />
</div>
<List
itemCount={symbols.length}

View file

@ -1,9 +1,9 @@
import * as React from 'react';
import { ElementsSideBar } from '../ElementsList/ElementsSideBar';
import { ElementsSidebar } from '../ElementsSidebar/ElementsSidebar';
import { History } from '../History/History';
import { Bar, BAR_WIDTH } from '../Bar/Bar';
import { Symbols } from '../Symbols/Symbols';
import { SymbolsSidebar } from '../SymbolsList/SymbolsSidebar';
import { SymbolsSidebar } from '../SymbolsSidebar/SymbolsSidebar';
import { type PropertyType } from '../../Enums/PropertyType';
import { Messages } from '../Messages/Messages';
import { Sidebar } from '../Sidebar/Sidebar';
@ -17,13 +17,16 @@ import { FindContainerById } from '../../utils/itertools';
import { type IEditorState } from '../../Interfaces/IEditorState';
import { GetCurrentHistoryState } from '../Editor/Editor';
import { Text } from '../Text/Text';
import { type IReplaceContainer } from '../../Interfaces/IReplaceContainer';
import { type Dispatch } from 'react';
export interface IUIProps {
editorState: IEditorState
replaceContainer: IReplaceContainer
selectContainer: (containerId: string) => void
deleteContainer: (containerId: string) => void
onPropertyChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
addContainer: (type: string) => void
addOrReplaceContainer: (type: string) => void
addContainerAt: (index: number, type: string, parent: string) => void
addSymbol: (type: string) => void
onSymbolPropertyChange: (key: string, value: string | number | boolean) => void
@ -32,19 +35,24 @@ export interface IUIProps {
saveEditorAsJSON: () => void
saveEditorAsSVG: () => void
loadState: (move: number) => void
setReplaceContainer: Dispatch<React.SetStateAction<IReplaceContainer>>
}
export enum SidebarType {
None,
Components,
ComponentsExpanded,
Symbols,
SymbolsExpanded,
History,
Messages,
Settings
}
export enum ExtendedSidebar {
None,
Property
}
function UseSetOrToggleSidebar(
selectedSidebar: SidebarType,
setSelectedSidebar: React.Dispatch<React.SetStateAction<SidebarType>>
@ -59,8 +67,12 @@ function UseSetOrToggleSidebar(
};
}
export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
export function UI({ editorState, replaceContainer, setReplaceContainer, ...methods }: IUIProps): JSX.Element {
const [selectedSidebar, setSelectedSidebar] = React.useState<SidebarType>(SidebarType.Components);
const [
selectedExtendedSidebar,
setSelectedExtendedSidebarType
] = React.useState<ExtendedSidebar>(ExtendedSidebar.None);
const [messages, setMessages] = React.useState<IMessage[]>([]);
const current = GetCurrentHistoryState(editorState.history, editorState.historyCurrentStep);
@ -101,14 +113,16 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
switch (selectedSidebar) {
case SidebarType.Components:
leftSidebarTitle = Text({ textId: '@Components' });
leftChildren = <Components
selectedContainer={selectedContainer}
componentOptions={configuration.AvailableContainers}
categories={configuration.Categories}
buttonOnClick={methods.addContainer}
/>;
buttonOnClick={methods.addOrReplaceContainer}
replaceContainer={replaceContainer}
setReplaceContainer={setReplaceContainer}/>;
rightSidebarTitle = Text({ textId: '@Elements' });
rightChildren = <ElementsSideBar
rightChildren = <ElementsSidebar
containers={current.containers}
mainContainer={mainContainer}
symbols={current.symbols}
@ -116,31 +130,11 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
onPropertyChange={methods.onPropertyChange}
selectContainer={methods.selectContainer}
addContainer={methods.addContainerAt}
isExpanded ={false}
onExpandChange={() => { setOrToggleSidebar(SidebarType.ComponentsExpanded); } }
/>;
break;
case SidebarType.ComponentsExpanded:
leftSidebarTitle = Text({ textId: '@Components' });
leftChildren = <Components
selectedContainer={selectedContainer}
componentOptions={configuration.AvailableContainers}
categories={configuration.Categories}
buttonOnClick={methods.addContainer}
/>;
rightSidebarTitle = Text({ textId: '@Elements' });
rightChildren = <ElementsSideBar
containers={current.containers}
mainContainer={mainContainer}
symbols={current.symbols}
selectedContainer={selectedContainer}
onPropertyChange={methods.onPropertyChange}
selectContainer={methods.selectContainer}
addContainer={methods.addContainerAt}
isExpanded ={true}
onExpandChange={() => { setOrToggleSidebar(SidebarType.Components); } }
selectedExtendedSidebar={selectedExtendedSidebar}
onExpandChange={(value) => { setSelectedExtendedSidebarType(value); } }
/>;
break;
case SidebarType.Symbols:
leftSidebarTitle = Text({ textId: '@SymbolsLeft' });
leftChildren = <Symbols
@ -153,24 +147,8 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
symbols={current.symbols}
onPropertyChange={methods.onSymbolPropertyChange}
selectSymbol={methods.selectSymbol}
isExpanded ={false}
onExpandChange={() => { setOrToggleSidebar(SidebarType.SymbolsExpanded); } }
/>;
break;
case SidebarType.SymbolsExpanded:
leftSidebarTitle = Text({ textId: '@SymbolsLeft' });
leftChildren = <Symbols
componentOptions={configuration.AvailableSymbols}
buttonOnClick={methods.addSymbol}
/>;
rightSidebarTitle = Text({ textId: '@SymbolsRight' });
rightChildren = <SymbolsSidebar
selectedSymbolId={current.selectedSymbolId}
symbols={current.symbols}
onPropertyChange={methods.onSymbolPropertyChange}
selectSymbol={methods.selectSymbol}
isExpanded ={true}
onExpandChange={() => { setOrToggleSidebar(SidebarType.Symbols); }}
selectedExtendedSidebar={selectedExtendedSidebar}
onExpandChange={(value) => { setSelectedExtendedSidebarType(value); } }
/>;
break;
@ -202,8 +180,10 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
}
const isLeftSidebarOpen = selectedSidebar !== SidebarType.None;
const isRightSidebarOpen = selectedSidebar === SidebarType.Components || selectedSidebar === SidebarType.Symbols;
const isRightSidebarOpenExpanded = selectedSidebar === SidebarType.ComponentsExpanded || selectedSidebar === SidebarType.SymbolsExpanded;
const isRightSidebarOpen = [SidebarType.Components, SidebarType.Symbols]
.includes(selectedSidebar);
const isRightSidebarOpenExpanded = isRightSidebarOpen &&
selectedExtendedSidebar !== ExtendedSidebar.None;
const isLeftSidebarOpenClasses = new Set<string>([
'left-sidebar',
@ -243,30 +223,24 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
isLeftSidebarOpenClasses.add('left-sidebar-single');
}
const isComponentsOpen = selectedSidebar === SidebarType.Components || selectedSidebar === SidebarType.ComponentsExpanded;
const isSymbolsOpen = selectedSidebar === SidebarType.Symbols || selectedSidebar === SidebarType.SymbolsExpanded;
const clickRestrictionsClasses = replaceContainer.isReplacing ? 'pointer-events-none opacity-50' : '';
const isComponentsOpen = selectedSidebar === SidebarType.Components;
const isSymbolsOpen = selectedSidebar === SidebarType.Symbols;
return (
<>
<Bar
className={clickRestrictionsClasses}
isComponentsOpen={isComponentsOpen}
isSymbolsOpen={isSymbolsOpen}
isHistoryOpen={selectedSidebar === SidebarType.History}
isMessagesOpen={selectedSidebar === SidebarType.Messages}
isSettingsOpen={selectedSidebar === SidebarType.Settings}
toggleComponents={() => {
if (selectedSidebar === SidebarType.ComponentsExpanded) {
setOrToggleSidebar(SidebarType.ComponentsExpanded);
} else {
setOrToggleSidebar(SidebarType.Components);
}
setOrToggleSidebar(SidebarType.Components);
} }
toggleSymbols={() => {
if (selectedSidebar === SidebarType.SymbolsExpanded) {
setOrToggleSidebar(SidebarType.SymbolsExpanded);
} else {
setOrToggleSidebar(SidebarType.Symbols);
}
setOrToggleSidebar(SidebarType.Symbols);
} }
toggleTimeline={() => {
setOrToggleSidebar(SidebarType.History);
@ -285,7 +259,7 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
{ leftChildren }
</Sidebar>
<Viewer
className={`${[...viewerMarginClasses.values()].join(' ')} w-full h-full`}
className={`${clickRestrictionsClasses} ${[...viewerMarginClasses.values()].join(' ')} w-full h-full`}
current={current}
isComponentsOpen={isComponentsOpen}
isSymbolsOpen={isSymbolsOpen}
@ -295,7 +269,7 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
margin={marginSidebar}
/>
<Sidebar
className={`right-sidebar ${isRightSidebarOpenClasses}`}
className={`right-sidebar ${isRightSidebarOpenClasses} ${clickRestrictionsClasses}`}
title={rightSidebarTitle}
>
{ rightChildren }

View file

@ -1,43 +1,48 @@
import * as React from 'react';
import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { type IHistoryState } from '../../Interfaces/IHistoryState';
import { type IPoint } from '../../Interfaces/IPoint';
import { DIMENSION_MARGIN, USE_EXPERIMENTAL_CANVAS_API } from '../../utils/default';
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
import { USE_EXPERIMENTAL_CANVAS_API } from '../../utils/default';
import { FindContainerById } from '../../utils/itertools';
import { BAR_WIDTH } from '../Bar/Bar';
import { Canvas } from '../Canvas/Canvas';
import { AddDimensions } from '../Canvas/DimensionLayer';
import { RenderSelector } from '../Canvas/Selector';
import { SVG } from '../SVG/SVG';
import { RenderSymbol } from '../Canvas/Symbol';
import { SelectorMode, SVG } from '../SVG/SVG';
import { useState } from 'react';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
interface IViewerProps {
className?: string
className: string
current: IHistoryState
selectedContainer: IContainerModel | undefined
selectContainer: (containerId: string) => void
selectedSymbol: ISymbolModel | undefined
margin: number
isComponentsOpen: boolean
isSymbolsOpen: boolean
selectContainer: (containerId: string) => void
}
export interface DrawParams {
mainContainer: IContainerModel
selectorMode: SelectorMode
selectedContainer: IContainerModel | undefined
selectedSymbol: ISymbolModel | undefined
containers: Map<string, IContainerModel>
symbols: Map<string, ISymbolModel>
}
function computeWidth(margin: number): number {
return window.innerWidth - (window.innerWidth < 768 ? BAR_WIDTH : margin);
}
export function Viewer({
className,
current,
selectedContainer,
selectContainer,
selectedSymbol,
margin,
isComponentsOpen,
isSymbolsOpen
isSymbolsOpen,
selectContainer
}: IViewerProps): JSX.Element {
function computeWidth(margin: number): number {
return window.innerWidth - (window.innerWidth < 768 ? BAR_WIDTH : margin);
}
const [windowSize, setWindowSize] = useState([
computeWidth(margin),
window.innerHeight
@ -71,64 +76,29 @@ export function Viewer({
return <></>;
}
let selectorMode = SelectorMode.Nothing;
if (isComponentsOpen) {
selectorMode = SelectorMode.Containers;
} else if (isSymbolsOpen) {
selectorMode = SelectorMode.Symbols;
}
const drawParams: DrawParams = {
mainContainer,
selectorMode,
selectedContainer,
selectedSymbol,
containers: current.containers,
symbols: current.symbols
};
if (USE_EXPERIMENTAL_CANVAS_API) {
function Draw(ctx: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint): void {
if (mainContainer === undefined) {
return;
}
const topDim = mainContainer.properties.y;
const leftDim = mainContainer.properties.x;
const rightDim = mainContainer.properties.x + mainContainer.properties.width;
const bottomDim = mainContainer.properties.y + mainContainer.properties.height;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
ctx.setTransform(scale, 0, 0, scale, translatePos.x, translatePos.y);
ctx.fillStyle = '#000000';
const it = MakeRecursionDFSIterator(mainContainer, current.containers, 0, [0, 0]);
for (const { container, depth, currentTransform } of it) {
const [x, y] = [
container.properties.x + currentTransform[0],
container.properties.y + currentTransform[1]
];
// Draw container
RenderContainers(ctx, container, x, y);
// Draw dimensions
RenderDimensions(
ctx,
leftDim,
bottomDim,
topDim,
rightDim,
depth,
scale,
current.containers,
container,
currentTransform
);
// Draw selector
RenderSelector(ctx, frameCount, {
containers: current.containers,
scale,
selected: selectedContainer
});
}
// Draw symbols
RenderSymbols(current, ctx, scale);
ctx.restore();
}
return (
<Canvas
draw={Draw}
className='ml-16'
className={className}
width={window.innerWidth - BAR_WIDTH}
height={window.innerHeight}
drawParams={drawParams}
/>
);
}
@ -140,59 +110,8 @@ export function Viewer({
viewerHeight={windowSize[1]}
width={mainContainer.properties.width}
height={mainContainer.properties.height}
containers={current.containers}
selectedContainer={selectedContainer}
symbols={current.symbols}
selectedSymbol={selectedSymbol}
drawParams={drawParams}
selectContainer={selectContainer}
isComponentsOpen={isComponentsOpen}
isSymbolsOpen={isSymbolsOpen}
>
{mainContainer}
</SVG>
/>
);
}
function RenderSymbols(
current: IHistoryState,
ctx: CanvasRenderingContext2D,
scale: number
): void {
current.symbols.forEach((symbol) => {
RenderSymbol(symbol, ctx, scale);
});
}
function RenderDimensions(
ctx: CanvasRenderingContext2D,
leftDim: number,
bottomDim: number,
topDim: number,
rightDim: number,
depth: number,
scale: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
currentTransform: [number, number]
): void {
ctx.save();
const depthOffset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerLeftDim = leftDim - depthOffset;
const containerTopDim = topDim - depthOffset;
const containerBottomDim = bottomDim + depthOffset;
const containerRightDim = rightDim + depthOffset;
const dimMapped = [containerLeftDim, containerBottomDim, containerTopDim, containerRightDim];
AddDimensions(ctx, containers, container, dimMapped, currentTransform, scale, depth);
ctx.restore();
}
function RenderContainers(ctx: CanvasRenderingContext2D, container: IContainerModel, x: number, y: number): void {
ctx.save();
ctx.strokeStyle = container.properties.style?.stroke ?? '#000000';
ctx.fillStyle = container.properties.style?.fill ?? '#000000';
ctx.lineWidth = Number(container.properties.style?.strokeWidth ?? 1);
ctx.globalAlpha = Number(container.properties.style?.fillOpacity ?? 1);
ctx.fillRect(x, y, container.properties.width, container.properties.height);
ctx.globalAlpha = Number(container.properties.style?.strokeOpacity ?? 1);
ctx.strokeRect(x, y, container.properties.width, container.properties.height);
ctx.restore();
}

View file

@ -27,6 +27,6 @@ export enum PropertyType {
SelfMarginDimension,
ChildrenDimensions,
DimensionWithMarks,
DimensionOptions
DimensionOptions
}

View file

@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { AddMethod } from '../Enums/AddMethod';
import { PositionReference } from '../Enums/PositionReference';
import { IAction } from './IAction';
import { IMargin } from './IMargin';
import { Orientation } from '../Enums/Orientation';
import { IKeyValue } from './IKeyValue';
import { IStyle } from './IStyle';
import { IDimensions } from './IDimensions';
import { type AddMethod } from '../Enums/AddMethod';
import { type PositionReference } from '../Enums/PositionReference';
import { type IAction } from './IAction';
import { type IMargin } from './IMargin';
import { type Orientation } from '../Enums/Orientation';
import { type IKeyValue } from './IKeyValue';
import { type IStyle } from './IStyle';
import { type IDimensions } from './IDimensions';
/** Model of available container used in application configuration */
export interface IAvailableContainer {

View file

@ -1,10 +1,17 @@
import { Position } from '../Enums/Position';
import { type Position } from '../Enums/Position';
export interface IDimensionOptions {
positions: Position[]
/**
* Stroke color
*/
color: string
* Stroke color
*/
color?: string
/** stroke-width */
width?: number
/** stroke-dasharray */
dashArray?: string
}

View file

@ -0,0 +1,5 @@
export interface IReplaceContainer {
id: string | undefined
isReplacing: boolean
category: string | undefined
}

View file

@ -22,6 +22,7 @@
"@DeleteContainerTitle": "Delete the container",
"@DeleteSymbol": "Delete",
"@DeleteSymbolTitle": "Delete the container",
"@ReplaceByContainer": "Replace by component",
"@ExportAsJSON": "Export as JSON",
"@ExportAsSVG": "Export as SVG",
@ -59,11 +60,14 @@
"@ContainerAlignmentInput": "Alignment",
"@ContainerAlignWithSymbol": "Align to symbol",
"@ContainerDimensions": "Dimensions",
"@ContainerShowDimension": "Show Dimension",
"@ContainerShowChildrenDimension": "Show surrounding dimension of children",
"@ContainerShowDimension": "Show dimensions",
"@ContainerShowChildrenDimension": "Show surrounding dimensions of children",
"@ContainerMarkPosition": "Mark the position for the parents",
"@ContainerShowDimensionWithMarks": "Show dimension with marked children",
"@ContainerShowDimensionWithMarks": "Show dimensions with marked children",
"@ContainerShowMarginsDimension": "Show margins dimensions",
"@ContainerStyle": "Style",
"@StyleStrokeColor": "Stroke Color",
"@StyleStrokeDashArray": "Stroke Dash Array",
"@StyleStroke": "Stroke",
"@StyleStrokeOpacity": "Stroke Opacity",
"@StyleStrokeWidth": "Stroke Width",

View file

@ -9,7 +9,7 @@
"@Symbols": "Symboles",
"@SymbolsLeft": "Symboles",
"@SymbolsRight": "Symboles",
"@NoSymbolSelected": "Pas de symbol sélectionné",
"@NoSymbolSelected": "Pas de symbole sélectionné",
"@Timeline": "Chronologie",
"@Messages": "Messages",
"@Settings": "Paramètres",
@ -22,6 +22,7 @@
"@DeleteContainerTitle": "Supprimer le conteneur",
"@DeleteSymbol": "Supprimer",
"@DeleteSymbolTitle": "Supprimer le symbole",
"@ReplaceByContainer": "Replacer par un composant",
"@ExportAsJSON": "Exporter en JSON",
"@ExportAsSVG": "Exporter en SVG",
@ -63,7 +64,10 @@
"@ContainerShowChildrenDimension": "Afficher les cotations englobante des enfants",
"@ContainerMarkPosition": "Marquer la position pour les parents",
"@ContainerShowDimensionWithMarks": "Afficher les cotations avec les enfants marqués",
"@ContainerShowMarginsDimension": "Afficher les cotations des marges",
"@ContainerStyle": "Style",
"@StyleStrokeColor": "Couleur du tracé",
"@StyleStrokeDashArray": "Tableau de traits",
"@StyleStroke": "Tracé",
"@StyleStrokeOpacity": "Opacité du tracé",
"@StyleStrokeWidth": "Epaisseur du tracé",

View file

@ -2,12 +2,14 @@
"@StartFromScratch": "Partir de zéro",
"@LoadConfigFile": "Charger un fichier de configuration",
"@GoBack": "Revenir",
"@Properties": "Propriétés",
"@Components": "Composants",
"@Elements": "Éléments",
"@Symbols": "Ancrages",
"@SymbolsLeft": "Ancrages",
"@SymbolsRight": "Ancrages",
"@NoSymbolSelected": "Pas d'ancre sélectionnée",
"@Timeline": "Chronologie",
"@Messages": "Messages",
"@Settings": "Paramètres",
@ -61,9 +63,18 @@
"@ContainerShowChildrenDimension": "Afficher les cotations englobante des enfants",
"@ContainerMarkPosition": "Marquer la position pour les parents",
"@ContainerShowDimensionWithMarks": "Afficher les cotations avec les enfants marqués",
"@ContainerShowMarginsDimension": "Afficher les cotations des jeux",
"@ContainerStyle": "Style",
"@StyleStrokeColor": "Couleur du tracé",
"@StyleStrokeDashArray": "Tableau de traits",
"@StyleStroke": "Tracé",
"@StyleStrokeOpacity": "Opacité du tracé",
"@StyleStrokeWidth": "Epaisseur du tracé",
"@StyleFill": "Remplissage",
"@StyleFillOpacity": "Opacité du remplissage",
"@SymbolName": "Nom",
"@SymbolDisplayedText": "Texte affiché",
"@SymbolX": "x",
"@SymbolHeight": "Hauteur",
"@SymbolWidth": "Largeur"

View file

@ -1,13 +1,14 @@
import { PositionReference } from '../Enums/PositionReference';
import { IAvailableContainer } from '../Interfaces/IAvailableContainer';
import { IAvailableSymbol } from '../Interfaces/IAvailableSymbol';
import { IConfiguration } from '../Interfaces/IConfiguration';
import { ContainerModel, IContainerModel } from '../Interfaces/IContainerModel';
import { IContainerProperties } from '../Interfaces/IContainerProperties';
import { IEditorState } from '../Interfaces/IEditorState';
import { ISymbolModel } from '../Interfaces/ISymbolModel';
import { type IAvailableContainer } from '../Interfaces/IAvailableContainer';
import { type IAvailableSymbol } from '../Interfaces/IAvailableSymbol';
import { type IConfiguration } from '../Interfaces/IConfiguration';
import { ContainerModel, type IContainerModel } from '../Interfaces/IContainerModel';
import { type IContainerProperties } from '../Interfaces/IContainerProperties';
import { type IEditorState } from '../Interfaces/IEditorState';
import { type ISymbolModel } from '../Interfaces/ISymbolModel';
import { Orientation } from '../Enums/Orientation';
import { AppState } from '../Enums/AppState';
import { type IDimensionOptions } from '../Interfaces/IDimensionOptions';
/// EDITOR DEFAULTS ///
@ -65,7 +66,6 @@ export const SHOW_SELF_DIMENSIONS = true;
export const SHOW_SELF_MARGINS_DIMENSIONS = true;
export const SHOW_CHILDREN_DIMENSIONS = true;
export const SHOW_BORROWER_DIMENSIONS = true;
export const SHOW_DIMENSIONS_PER_DEPTH = false;
export const DIMENSION_MARGIN = 50;
export const SYMBOL_MARGIN = 25;
export const NOTCHES_LENGTH = 10;
@ -188,6 +188,12 @@ const DEFAULT_CONTAINER_STYLE = {
strokeWidth: 2
};
export const DEFAULT_DIMENSION_OPTION: IDimensionOptions = {
positions: [],
color: '#000000',
width: 2
};
/**
* Default Main container properties
*/
@ -212,23 +218,11 @@ export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
positionReference: PositionReference.TopLeft,
hideChildrenInTreeview: false,
dimensionOptions: {
childrenDimensions: {
color: '#000000',
positions: []
},
selfDimensions: {
color: '#000000',
positions: []
},
selfMarginsDimensions: {
color: '#000000',
positions: []
},
childrenDimensions: clone(DEFAULT_DIMENSION_OPTION),
selfDimensions: clone(DEFAULT_DIMENSION_OPTION),
selfMarginsDimensions: clone(DEFAULT_DIMENSION_OPTION),
markPosition: [],
dimensionWithMarks: {
color: '#000000',
positions: []
}
dimensionWithMarks: clone(DEFAULT_DIMENSION_OPTION)
},
warning: '',
style: DEFAULT_CONTAINER_STYLE
@ -277,20 +271,20 @@ export function GetDefaultContainerProps(type: string,
hideChildrenInTreeview: containerConfig.HideChildrenInTreeview ?? false,
dimensionOptions: {
childrenDimensions: {
color: containerConfig.DimensionOptions?.childrenDimensions.color ?? '#000000',
...containerConfig.DimensionOptions?.selfDimensions,
positions: containerConfig.DimensionOptions?.childrenDimensions.positions ?? []
},
selfDimensions: {
color: containerConfig.DimensionOptions?.selfDimensions.color ?? '#000000',
...containerConfig.DimensionOptions?.selfDimensions,
positions: containerConfig.DimensionOptions?.selfDimensions.positions ?? []
},
selfMarginsDimensions: {
color: containerConfig.DimensionOptions?.selfMarginsDimensions.color ?? '#000000',
...containerConfig.DimensionOptions?.selfMarginsDimensions,
positions: containerConfig.DimensionOptions?.selfMarginsDimensions.positions ?? []
},
markPosition: containerConfig.DimensionOptions?.markPosition ?? [],
dimensionWithMarks: {
color: containerConfig.DimensionOptions?.dimensionWithMarks.color ?? '#000000',
...containerConfig.DimensionOptions?.dimensionWithMarks,
positions: containerConfig.DimensionOptions?.dimensionWithMarks.positions ?? []
}
},
@ -319,3 +313,10 @@ export function GetDefaultSymbolModel(name: string,
showDimension: false
};
}
/**
* Macro function for JSON.parse(JSON.stringify(obj))
*/
function clone<T>(object: T): T {
return JSON.parse(JSON.stringify(object));
}

View file

@ -84,8 +84,7 @@ const GetSVGLayoutConfiguration = () => {
stroke: 'red',
fill: '#d3c9b7',
},
ShowSelfDimensions: [0, 2],
ShowDimensionWithMarks: [1, 3],
IsFlex: true,
Category: "Stuff"
},
{
@ -100,8 +99,7 @@ const GetSVGLayoutConfiguration = () => {
stroke: 'red',
fill: '#d3c9b7',
},
ShowSelfDimensions: [0, 2],
ShowDimensionWithMarks: [1, 3],
IsFlex: true,
Category: "Stuff"
},
{
@ -121,6 +119,7 @@ const GetSVGLayoutConfiguration = () => {
fill: 'white'
},
Category: "Stuff",
IsFlex: true,
Actions: [
{
Id: "Insert",
@ -173,6 +172,7 @@ const GetSVGLayoutConfiguration = () => {
<line x1="{width}" y1="0" x2="0" y2="{height}" stroke="black" style='{userData.styleLine}'></line>
`
,
IsFlex: true,
Actions: [
{
Id: "SplitRemplissage",
@ -254,6 +254,7 @@ const GetSVGLayoutConfiguration = () => {
Type: '200',
MaxWidth: 500,
MinWidth: 200,
IsFlex: true,
Style: {
fillOpacity: 1,
strokeWidth: 2,
@ -265,6 +266,7 @@ const GetSVGLayoutConfiguration = () => {
Type: '400',
MaxWidth: 500,
MinWidth: 400,
IsFlex: true,
Style: {
fillOpacity: 1,
strokeWidth: 2,
@ -300,7 +302,7 @@ const GetSVGLayoutConfiguration = () => {
Url: './images/arrow-down.svg'
},
Name: 'Arrow',
PositionReference: 1
PositionReference: 0
},
{
Width: 32,
@ -329,8 +331,8 @@ const GetSVGLayoutConfiguration = () => {
MainContainer: {
Type: 'main',
Width: 800,
Height: 800,
Orientation: 1,
Height: 200,
Orientation: 0,
Style: {
stroke: 'black',
strokeWidth: 2,