diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 748a5bb..7f605ec 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -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',
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 446b61f..0ee06e3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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.
diff --git a/csharp/SVGLDLibs/SVGLDLibs/Models/DimensionOptions.cs b/csharp/SVGLDLibs/SVGLDLibs/Models/DimensionOptions.cs
index a98e500..10c6542 100644
--- a/csharp/SVGLDLibs/SVGLDLibs/Models/DimensionOptions.cs
+++ b/csharp/SVGLDLibs/SVGLDLibs/Models/DimensionOptions.cs
@@ -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;
+ }
+}
diff --git a/mprocs.yaml b/mprocs.yaml
new file mode 100644
index 0000000..fa0511f
--- /dev/null
+++ b/mprocs.yaml
@@ -0,0 +1,7 @@
+procs:
+ nvim:
+ shell: "nvim ."
+ vite:
+ shell: "npx vite"
+ test-server:
+ shell: "npx nodemon ./test-server/http.js"
diff --git a/package.json b/package.json
index 8fe114a..8804ff9 100644
--- a/package.json
+++ b/package.json
@@ -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"
+ }
}
}
diff --git a/patches/@types+react-svg-pan-zoom+3.3.5.patch b/patches/@types+react-svg-pan-zoom+3.3.5.patch
new file mode 100644
index 0000000..b7fc8cc
--- /dev/null
+++ b/patches/@types+react-svg-pan-zoom+3.3.5.patch
@@ -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;
+
diff --git a/patches/@types__react-svg-pan-zoom@3.3.5.patch b/patches/@types__react-svg-pan-zoom@3.3.5.patch
new file mode 100644
index 0000000..506b048
--- /dev/null
+++ b/patches/@types__react-svg-pan-zoom@3.3.5.patch
@@ -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;
+
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cc7fbe9..8607471 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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'}
diff --git a/src/Components/API/api.test.tsx b/src/Components/API/api.test.tsx
index 53f3ae0..08fcbad 100644
--- a/src/Components/API/api.test.tsx
+++ b/src/Components/API/api.test.tsx
@@ -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: [
diff --git a/src/Components/Bar/Bar.tsx b/src/Components/Bar/Bar.tsx
index 8f28635..a693fc2 100644
--- a/src/Components/Bar/Bar.tsx
+++ b/src/Components/Bar/Bar.tsx
@@ -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 (
-
+
void
- className?: string
style?: React.CSSProperties
+ drawParams: DrawParams
};
-function UseCanvas(draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void): React.RefObject {
+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 {
const canvasRef = useRef(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({
viewerWidth: width,
diff --git a/src/Components/Canvas/Container.ts b/src/Components/Canvas/Container.ts
new file mode 100644
index 0000000..4914b4b
--- /dev/null
+++ b/src/Components/Canvas/Container.ts
@@ -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();
+}
diff --git a/src/Components/Canvas/Dimension.ts b/src/Components/Canvas/Dimension.ts
index e95fc92..639bc2d 100644
--- a/src/Components/Canvas/Dimension.ts
+++ b/src/Components/Canvas/Dimension.ts
@@ -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`;
diff --git a/src/Components/Canvas/DimensionLayer.ts b/src/Components/Canvas/DimensionLayer.ts
index 1b40961..d986de1 100644
--- a/src/Components/Canvas/DimensionLayer.ts
+++ b/src/Components/Canvas/DimensionLayer.ts
@@ -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,
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
});
}
diff --git a/src/Components/Canvas/Renderer.ts b/src/Components/Canvas/Renderer.ts
new file mode 100644
index 0000000..c93e615
--- /dev/null
+++ b/src/Components/Canvas/Renderer.ts
@@ -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,
+ 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,
+ 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,
+ 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,
+ 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();
+}
diff --git a/src/Components/Canvas/Selector.ts b/src/Components/Canvas/Selector.ts
index c9ea814..1dc1654 100644
--- a/src/Components/Canvas/Selector.ts
+++ b/src/Components/Canvas/Selector.ts
@@ -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
- 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';
}
}
diff --git a/src/Components/Canvas/SelectorContainer.ts b/src/Components/Canvas/SelectorContainer.ts
new file mode 100644
index 0000000..c151f6f
--- /dev/null
+++ b/src/Components/Canvas/SelectorContainer.ts
@@ -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
+ 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
+ }
+ );
+}
diff --git a/src/Components/Canvas/SelectorSymbol.ts b/src/Components/Canvas/SelectorSymbol.ts
new file mode 100644
index 0000000..ddfbb29
--- /dev/null
+++ b/src/Components/Canvas/SelectorSymbol.ts
@@ -0,0 +1,43 @@
+import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
+import { SYMBOL_MARGIN } from '../../utils/default';
+import { RenderSelector } from './Selector';
+
+interface ISelectorProps {
+ symbols: Map
+ 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
+ }
+ );
+}
diff --git a/src/Components/Canvas/Symbol.ts b/src/Components/Canvas/Symbol.ts
index 752eb66..c4dad94 100644
--- a/src/Components/Canvas/Symbol.ts
+++ b/src/Components/Canvas/Symbol.ts
@@ -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();
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();
}
diff --git a/src/Components/Components/Components.tsx b/src/Components/Components/Components.tsx
index 53a3dd9..8a4581a 100644
--- a/src/Components/Components/Components.tsx
+++ b/src/Components/Components/Components.tsx
@@ -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>
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 (
+ {props.replaceContainer.isReplacing &&
+ }
);
-};
+}
diff --git a/src/Components/ContainerProperties/ContainerForm.tsx b/src/Components/ContainerProperties/ContainerForm.tsx
index 4bdc262..8b7a505 100644
--- a/src/Components/ContainerProperties/ContainerForm.tsx
+++ b/src/Components/ContainerProperties/ContainerForm.tsx
@@ -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); }}/>
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
+ )
+ );
+ }}/>
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
+ )
+ );
+ }}/>
@@ -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)); }}/>
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}/>
props.onChange('maxWidth', Number(value))}/>
+ onChange={(value) => { props.onChange('maxWidth', Number(value)); }}/>
props.onChange('minHeight', Number(value))}/>
+ onChange={(value) => { props.onChange('minHeight', Number(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}
/>
props.onChange('maxHeight', Number(value))}/>
+ onChange={(value) => { props.onChange('maxHeight', Number(value)); }}/>
@@ -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); }}/>
props.onChange('bottom', Number(value), PropertyType.Margin)}/>
+ onChange={(value) => { props.onChange('bottom', Number(value), PropertyType.Margin); }}/>
props.onChange('top', Number(value), PropertyType.Margin)}/>
+ onChange={(value) => { props.onChange('top', Number(value), PropertyType.Margin); }}/>
props.onChange('right', Number(value), PropertyType.Margin)}/>
+ onChange={(value) => { props.onChange('right', Number(value), PropertyType.Margin); }}/>
@@ -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); }}
/>
props.onChange('isAnchor', event.target.checked)}/>
+ onChange={(event) => { props.onChange('isAnchor', event.target.checked); }}/>
@@ -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); }}/>
@@ -347,168 +351,244 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
{
SHOW_SELF_DIMENSIONS &&
-
-
props.onChange(key, value, PropertyType.SelfDimension)}
- />
- props.onChange('color', e.target.value, PropertyType.SelfDimension)}/>
-
+
+
{ props.onChange(key, value, PropertyType.SelfDimension); }}
+ />
+ { props.onChange('color', e.target.value, PropertyType.SelfDimension); }}/>
+ { props.onChange('width', Number(value), PropertyType.SelfDimension); }}/>
+ { props.onChange('dashArray', value, PropertyType.SelfDimension); }}/>
+
}
{
SHOW_SELF_MARGINS_DIMENSIONS &&
-
-
props.onChange(key, value, PropertyType.SelfMarginDimension)}
- />
- props.onChange('color', e.target.value, PropertyType.SelfMarginDimension)}/>
-
+
+
{ props.onChange(key, value, PropertyType.SelfMarginDimension); }}
+ />
+ { props.onChange('color', e.target.value, PropertyType.SelfMarginDimension); }}/>
+ { props.onChange('width', Number(value), PropertyType.SelfMarginDimension); }}/>
+ { props.onChange('dashArray', value, PropertyType.SelfMarginDimension); }}/>
+
}
{
SHOW_CHILDREN_DIMENSIONS &&
-
-
props.onChange(key, value, PropertyType.ChildrenDimensions)}
- />
- props.onChange('color', e.target.value, PropertyType.ChildrenDimensions)}/>
-
+
+
{ props.onChange(key, value, PropertyType.ChildrenDimensions); }}
+ />
+ { props.onChange('color', e.target.value, PropertyType.ChildrenDimensions); }}/>
+ { props.onChange('width', Number(value), PropertyType.ChildrenDimensions); }}/>
+ { props.onChange('dashArray', value, PropertyType.ChildrenDimensions); }}/>
+
}
{
SHOW_BORROWER_DIMENSIONS &&
- <>
-
- props.onChange(key, value, PropertyType.DimensionOptions)}
- />
-
-
-
props.onChange(key, value, PropertyType.DimensionWithMarks)}
- />
- props.onChange('color', e.target.value, PropertyType.DimensionWithMarks)}/>
-
- >
+ <>
+
+ { props.onChange(key, value, PropertyType.DimensionOptions); }}
+ />
+
+
+
{ props.onChange(key, value, PropertyType.DimensionWithMarks); }}
+ />
+ { props.onChange('color', e.target.value, PropertyType.DimensionWithMarks); }}/>
+ { props.onChange('width', Number(value), PropertyType.DimensionWithMarks); }}/>
+ { props.onChange('dashArray', value, PropertyType.DimensionWithMarks); }}/>
+
+ >
}
{props.properties.style !== undefined &&
-
-
- props.onChange('stroke', value, PropertyType.Style)}
- />
- props.onChange('strokeOpacity', Number(event.target.value), PropertyType.Style)}
- />
- props.onChange('strokeWidth', Number(value), PropertyType.Style)}
- />
- props.onChange('fill', value, PropertyType.Style)}
- />
- props.onChange('fillOpacity', Number(event.target.value), PropertyType.Style)}
- />
-
-
+
+
+ { props.onChange('stroke', value, PropertyType.Style); }}
+ />
+ { props.onChange('strokeOpacity', Number(event.target.value), PropertyType.Style); }}
+ />
+ { props.onChange('strokeWidth', Number(value), PropertyType.Style); }}
+ />
+ { props.onChange('fill', value, PropertyType.Style); }}
+ />
+ { props.onChange('fillOpacity', Number(event.target.value), PropertyType.Style); }}
+ />
+
+
}
);
diff --git a/src/Components/ContainerProperties/ContainerProperties.test.tsx b/src/Components/ContainerProperties/ContainerProperties.test.tsx
index cd2da15..01c36bd 100644
--- a/src/Components/ContainerProperties/ContainerProperties.test.tsx
+++ b/src/Components/ContainerProperties/ContainerProperties.test.tsx
@@ -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
}
};
diff --git a/src/Components/Editor/Actions/ContainerOperations.ts b/src/Components/Editor/Actions/ContainerOperations.ts
index 82162e3..684b443 100644
--- a/src/Components/Editor/Actions/ContainerOperations.ts
+++ b/src/Components/Editor/Actions/ContainerOperations.ts
@@ -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.
diff --git a/src/Components/Editor/Actions/ContextMenuActions.ts b/src/Components/Editor/Actions/ContextMenuActions.ts
index 69a3121..9fb86f4 100644
--- a/src/Components/Editor/Actions/ContextMenuActions.ts
+++ b/src/Components/Editor/Actions/ContextMenuActions.ts
@@ -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,
@@ -23,7 +24,8 @@ export function InitActions(
history: IHistoryState[],
historyCurrentStep: number,
setNewHistory: (newHistory: IHistoryState[]) => void,
- setHistoryCurrentStep: Dispatch>
+ setHistoryCurrentStep: Dispatch>,
+ setIsReplacingContainer: Dispatch>
): void {
menuActions.set(
'',
@@ -56,9 +58,24 @@ export function InitActions(
menuActions.set(
'elements-sidebar-row',
[{
+ text: Text({ textId: '@ReplaceByContainer' }),
+ title: Text({ textId: '@ReplaceByContainerTitle' }),
+ shortcut: 'R',
+ 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: 'Suppr',
+
action: (target: HTMLElement) => {
const id = target.id;
const newHistory = DeleteContainer(
diff --git a/src/Components/Editor/Actions/Save.ts b/src/Components/Editor/Actions/Save.ts
index ae7b4e9..2dc2f95 100644
--- a/src/Components/Editor/Actions/Save.ts
+++ b/src/Components/Editor/Actions/Save.ts
@@ -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) {
diff --git a/src/Components/Editor/Actions/Shortcuts.ts b/src/Components/Editor/Actions/Shortcuts.ts
index a81660c..082770e 100644
--- a/src/Components/Editor/Actions/Shortcuts.ts
+++ b/src/Components/Editor/Actions/Shortcuts.ts
@@ -7,7 +7,8 @@ export function OnKey(
history: IHistoryState[],
historyCurrentStep: number,
setHistoryCurrentStep: Dispatch>,
- 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();
}
}
diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx
index 372ab06..095f644 100644
--- a/src/Components/Editor/Editor.tsx
+++ b/src/Components/Editor/Editor.tsx
@@ -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>,
- 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(structuredClone(props.history));
const [historyCurrentStep, setHistoryCurrentStep] = React.useState(props.historyCurrentStep);
+ const [replaceContainer, setReplaceContainer] = React.useState({ isReplacing: false, id: undefined, category: undefined });
+
const editorRef = useRef(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 (
-
+
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}
/>