Compare commits
No commits in common. "39e74c69f55204f140666d7d4619331bb0461e7c" and "7f7ec4b921f4e80c7258d3a712ee02423a254ced" have entirely different histories.
39e74c69f5
...
7f7ec4b921
61 changed files with 1135 additions and 1388 deletions
|
@ -19,7 +19,6 @@ module.exports = {
|
|||
plugins: [
|
||||
'only-warn',
|
||||
'react',
|
||||
'react-hooks',
|
||||
'@typescript-eslint'
|
||||
],
|
||||
rules: {
|
||||
|
@ -30,7 +29,5 @@ module.exports = {
|
|||
'@typescript-eslint/semi': ['warn', 'always'],
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
|
||||
'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies
|
||||
}
|
||||
};
|
||||
|
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1 +0,0 @@
|
|||
*.drawio filter=lfs diff=lfs merge=lfs -text
|
BIN
docs/ComponentStructure.drawio
(Stored with Git LFS)
BIN
docs/ComponentStructure.drawio
(Stored with Git LFS)
Binary file not shown.
|
@ -3,7 +3,6 @@
|
|||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
</head>
|
||||
|
|
469
package-lock.json
generated
469
package-lock.json
generated
|
@ -9,20 +9,18 @@
|
|||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"framer-motion": "^6.5.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-svg-pan-zoom": "^3.11.0",
|
||||
"react-window": "^1.8.7"
|
||||
"react-svg-pan-zoom": "^3.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "^8.16.1",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^14.4.1",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-svg-pan-zoom": "^3.3.5",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.31.0",
|
||||
"@typescript-eslint/parser": "^5.31.0",
|
||||
"@vitejs/plugin-react": "^2.0.0",
|
||||
|
@ -33,10 +31,8 @@
|
|||
"eslint-config-standard-with-typescript": "^22.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.2.4",
|
||||
"eslint-plugin-only-warn": "^1.0.3",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"jsdom": "^20.0.0",
|
||||
"postcss": "^8.4.14",
|
||||
"sass": "^1.54.0",
|
||||
|
@ -425,6 +421,7 @@
|
|||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz",
|
||||
"integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
|
@ -480,6 +477,21 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/is-prop-valid": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
|
||||
"integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "0.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/memoize": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
|
||||
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",
|
||||
|
@ -602,6 +614,89 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@motionone/animation": {
|
||||
"version": "10.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.13.1.tgz",
|
||||
"integrity": "sha512-dxQ+1wWxL6iFHDy1uv6hhcPjIdOg36eDT56jN4LI7Z5HZRyLpq8x1t7JFQclo/IEIb+6Bk4atmyinGFdXVECuA==",
|
||||
"dependencies": {
|
||||
"@motionone/easing": "^10.13.1",
|
||||
"@motionone/types": "^10.13.0",
|
||||
"@motionone/utils": "^10.13.1",
|
||||
"tslib": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@motionone/animation/node_modules/tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"node_modules/@motionone/dom": {
|
||||
"version": "10.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@motionone/dom/-/dom-10.12.0.tgz",
|
||||
"integrity": "sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw==",
|
||||
"dependencies": {
|
||||
"@motionone/animation": "^10.12.0",
|
||||
"@motionone/generators": "^10.12.0",
|
||||
"@motionone/types": "^10.12.0",
|
||||
"@motionone/utils": "^10.12.0",
|
||||
"hey-listen": "^1.0.8",
|
||||
"tslib": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@motionone/dom/node_modules/tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"node_modules/@motionone/easing": {
|
||||
"version": "10.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@motionone/easing/-/easing-10.13.1.tgz",
|
||||
"integrity": "sha512-INEsInHHDHVgx0dp5qlXi1lMXBqYicgLMMSn3zfGzaIvcaEbI1Uz8BoyNV4BiclTupG7RYIh+T6BU83ZcEe74g==",
|
||||
"dependencies": {
|
||||
"@motionone/utils": "^10.13.1",
|
||||
"tslib": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@motionone/easing/node_modules/tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"node_modules/@motionone/generators": {
|
||||
"version": "10.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@motionone/generators/-/generators-10.13.1.tgz",
|
||||
"integrity": "sha512-+HK5u2YcNJCckTTqfOLgSVcrWv2z1dVwrSZEMVJuAh0EnWEWGDJRvMBoPc0cFf/osbkA2Rq9bH2+vP0Ex/D8uw==",
|
||||
"dependencies": {
|
||||
"@motionone/types": "^10.13.0",
|
||||
"@motionone/utils": "^10.13.1",
|
||||
"tslib": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@motionone/generators/node_modules/tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"node_modules/@motionone/types": {
|
||||
"version": "10.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@motionone/types/-/types-10.13.0.tgz",
|
||||
"integrity": "sha512-qegk4qg8U1N9ZwAJ187BG3TkZz1k9LP/pvNtCSlqdq/PMUDKlCFG4ZnjJ481P0IOH/vIw1OzIbKIuyg0A3rk9g=="
|
||||
},
|
||||
"node_modules/@motionone/utils": {
|
||||
"version": "10.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@motionone/utils/-/utils-10.13.1.tgz",
|
||||
"integrity": "sha512-TjDPTIppaf3ofBXQv4ZzAketJgN0sclALXfZ6mfrkjJkOy83mLls9744F+6S+VKCpBmvbZcBY4PQfrfhAfeMtA==",
|
||||
"dependencies": {
|
||||
"@motionone/types": "^10.13.0",
|
||||
"hey-listen": "^1.0.8",
|
||||
"tslib": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@motionone/utils/node_modules/tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
@ -650,9 +745,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.17.1.tgz",
|
||||
"integrity": "sha512-KnH2MnJUzmFNPW6RIKfd+zf2Wue8mEKX0M3cpX6aKl5ZXrJM1/c/Pc8c2xDNYQCnJO48Sm5ITbMXgqTr3h4jxQ==",
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.16.0.tgz",
|
||||
"integrity": "sha512-uxF4zmnLHHDlmW4l+0WDjcgLVwCvH+OVLpD8Dfp+Bjfz85prwxWGbwXgJdLtkgjD0qfOzkJF9SmA6YZPsMYX4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
|
@ -984,15 +1079,6 @@
|
|||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-window": {
|
||||
"version": "1.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.5.tgz",
|
||||
"integrity": "sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
|
@ -2793,15 +2879,6 @@
|
|||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-only-warn": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-only-warn/-/eslint-plugin-only-warn-1.0.3.tgz",
|
||||
"integrity": "sha512-XQOX/TfLoLw6h8ky51d29uUjXRTQHqBGXPylDEmy5fe/w7LIOnp8MA24b1OSMEn9BQoKow1q3g1kLe5/9uBTvw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-promise": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz",
|
||||
|
@ -2842,18 +2919,6 @@
|
|||
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-hooks": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
|
||||
"integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/doctrine": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
|
||||
|
@ -3266,6 +3331,44 @@
|
|||
"url": "https://www.patreon.com/infusion"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-6.5.1.tgz",
|
||||
"integrity": "sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==",
|
||||
"dependencies": {
|
||||
"@motionone/dom": "10.12.0",
|
||||
"framesync": "6.0.1",
|
||||
"hey-listen": "^1.0.8",
|
||||
"popmotion": "11.0.3",
|
||||
"style-value-types": "5.0.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@emotion/is-prop-valid": "^0.8.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": ">=16.8 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion/node_modules/tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"node_modules/framesync": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz",
|
||||
"integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/framesync/node_modules/tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
|
@ -3503,6 +3606,11 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hey-listen": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
|
||||
"integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||
|
@ -4320,11 +4428,6 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
|
@ -4733,6 +4836,22 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/popmotion": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz",
|
||||
"integrity": "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==",
|
||||
"dependencies": {
|
||||
"framesync": "6.0.1",
|
||||
"hey-listen": "^1.0.8",
|
||||
"style-value-types": "5.0.0",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/popmotion/node_modules/tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
|
||||
|
@ -5010,22 +5129,6 @@
|
|||
"react": ">=17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-window": {
|
||||
"version": "1.8.7",
|
||||
"resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.7.tgz",
|
||||
"integrity": "sha512-JHEZbPXBpKMmoNO1bNhoXOOLg/ujhL/BU4IqVU9r8eQPcy5KQnGHIHDRkJ0ns9IM5+Aq5LNwt3j8t3tIrePQzA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"memoize-one": ">=3.1.1 <6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
@ -5063,7 +5166,8 @@
|
|||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.9",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
||||
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
|
||||
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.4.3",
|
||||
|
@ -5432,6 +5536,20 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/style-value-types": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz",
|
||||
"integrity": "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==",
|
||||
"dependencies": {
|
||||
"hey-listen": "^1.0.8",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/style-value-types/node_modules/tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
@ -6301,6 +6419,7 @@
|
|||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz",
|
||||
"integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
|
@ -6344,6 +6463,21 @@
|
|||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@emotion/is-prop-valid": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
|
||||
"integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@emotion/memoize": "0.7.4"
|
||||
}
|
||||
},
|
||||
"@emotion/memoize": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
|
||||
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
|
||||
"optional": true
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",
|
||||
|
@ -6442,6 +6576,99 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"@motionone/animation": {
|
||||
"version": "10.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.13.1.tgz",
|
||||
"integrity": "sha512-dxQ+1wWxL6iFHDy1uv6hhcPjIdOg36eDT56jN4LI7Z5HZRyLpq8x1t7JFQclo/IEIb+6Bk4atmyinGFdXVECuA==",
|
||||
"requires": {
|
||||
"@motionone/easing": "^10.13.1",
|
||||
"@motionone/types": "^10.13.0",
|
||||
"@motionone/utils": "^10.13.1",
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@motionone/dom": {
|
||||
"version": "10.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@motionone/dom/-/dom-10.12.0.tgz",
|
||||
"integrity": "sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw==",
|
||||
"requires": {
|
||||
"@motionone/animation": "^10.12.0",
|
||||
"@motionone/generators": "^10.12.0",
|
||||
"@motionone/types": "^10.12.0",
|
||||
"@motionone/utils": "^10.12.0",
|
||||
"hey-listen": "^1.0.8",
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@motionone/easing": {
|
||||
"version": "10.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@motionone/easing/-/easing-10.13.1.tgz",
|
||||
"integrity": "sha512-INEsInHHDHVgx0dp5qlXi1lMXBqYicgLMMSn3zfGzaIvcaEbI1Uz8BoyNV4BiclTupG7RYIh+T6BU83ZcEe74g==",
|
||||
"requires": {
|
||||
"@motionone/utils": "^10.13.1",
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@motionone/generators": {
|
||||
"version": "10.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@motionone/generators/-/generators-10.13.1.tgz",
|
||||
"integrity": "sha512-+HK5u2YcNJCckTTqfOLgSVcrWv2z1dVwrSZEMVJuAh0EnWEWGDJRvMBoPc0cFf/osbkA2Rq9bH2+vP0Ex/D8uw==",
|
||||
"requires": {
|
||||
"@motionone/types": "^10.13.0",
|
||||
"@motionone/utils": "^10.13.1",
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@motionone/types": {
|
||||
"version": "10.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@motionone/types/-/types-10.13.0.tgz",
|
||||
"integrity": "sha512-qegk4qg8U1N9ZwAJ187BG3TkZz1k9LP/pvNtCSlqdq/PMUDKlCFG4ZnjJ481P0IOH/vIw1OzIbKIuyg0A3rk9g=="
|
||||
},
|
||||
"@motionone/utils": {
|
||||
"version": "10.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@motionone/utils/-/utils-10.13.1.tgz",
|
||||
"integrity": "sha512-TjDPTIppaf3ofBXQv4ZzAketJgN0sclALXfZ6mfrkjJkOy83mLls9744F+6S+VKCpBmvbZcBY4PQfrfhAfeMtA==",
|
||||
"requires": {
|
||||
"@motionone/types": "^10.13.0",
|
||||
"hey-listen": "^1.0.8",
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
@ -6481,9 +6708,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"@testing-library/dom": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.17.1.tgz",
|
||||
"integrity": "sha512-KnH2MnJUzmFNPW6RIKfd+zf2Wue8mEKX0M3cpX6aKl5ZXrJM1/c/Pc8c2xDNYQCnJO48Sm5ITbMXgqTr3h4jxQ==",
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.16.0.tgz",
|
||||
"integrity": "sha512-uxF4zmnLHHDlmW4l+0WDjcgLVwCvH+OVLpD8Dfp+Bjfz85prwxWGbwXgJdLtkgjD0qfOzkJF9SmA6YZPsMYX4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
|
@ -6749,15 +6976,6 @@
|
|||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-window": {
|
||||
"version": "1.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.5.tgz",
|
||||
"integrity": "sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
|
@ -8036,12 +8254,6 @@
|
|||
"semver": "^7.3.7"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-only-warn": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-only-warn/-/eslint-plugin-only-warn-1.0.3.tgz",
|
||||
"integrity": "sha512-XQOX/TfLoLw6h8ky51d29uUjXRTQHqBGXPylDEmy5fe/w7LIOnp8MA24b1OSMEn9BQoKow1q3g1kLe5/9uBTvw==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-plugin-promise": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz",
|
||||
|
@ -8099,13 +8311,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-react-hooks": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
|
||||
"integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
|
@ -8305,6 +8510,42 @@
|
|||
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
|
||||
"dev": true
|
||||
},
|
||||
"framer-motion": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-6.5.1.tgz",
|
||||
"integrity": "sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==",
|
||||
"requires": {
|
||||
"@emotion/is-prop-valid": "^0.8.2",
|
||||
"@motionone/dom": "10.12.0",
|
||||
"framesync": "6.0.1",
|
||||
"hey-listen": "^1.0.8",
|
||||
"popmotion": "11.0.3",
|
||||
"style-value-types": "5.0.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"framesync": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz",
|
||||
"integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==",
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
|
@ -8469,6 +8710,11 @@
|
|||
"has-symbols": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"hey-listen": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
|
||||
"integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="
|
||||
},
|
||||
"html-encoding-sniffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||
|
@ -9067,11 +9313,6 @@
|
|||
"sourcemap-codec": "^1.4.8"
|
||||
}
|
||||
},
|
||||
"memoize-one": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
|
||||
},
|
||||
"merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
|
@ -9369,6 +9610,24 @@
|
|||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
||||
"dev": true
|
||||
},
|
||||
"popmotion": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz",
|
||||
"integrity": "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==",
|
||||
"requires": {
|
||||
"framesync": "6.0.1",
|
||||
"hey-listen": "^1.0.8",
|
||||
"style-value-types": "5.0.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
|
||||
|
@ -9537,15 +9796,6 @@
|
|||
"transformation-matrix": "^2.11.1"
|
||||
}
|
||||
},
|
||||
"react-window": {
|
||||
"version": "1.8.7",
|
||||
"resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.7.tgz",
|
||||
"integrity": "sha512-JHEZbPXBpKMmoNO1bNhoXOOLg/ujhL/BU4IqVU9r8eQPcy5KQnGHIHDRkJ0ns9IM5+Aq5LNwt3j8t3tIrePQzA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"memoize-one": ">=3.1.1 <6"
|
||||
}
|
||||
},
|
||||
"read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
@ -9577,7 +9827,8 @@
|
|||
"regenerator-runtime": {
|
||||
"version": "0.13.9",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
||||
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
|
||||
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==",
|
||||
"dev": true
|
||||
},
|
||||
"regexp.prototype.flags": {
|
||||
"version": "1.4.3",
|
||||
|
@ -9834,6 +10085,22 @@
|
|||
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
|
||||
"dev": true
|
||||
},
|
||||
"style-value-types": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz",
|
||||
"integrity": "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==",
|
||||
"requires": {
|
||||
"hey-listen": "^1.0.8",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"framer-motion": "^6.5.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-svg-pan-zoom": "^3.11.0",
|
||||
"react-window": "^1.8.7"
|
||||
"react-svg-pan-zoom": "^3.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "^8.16.1",
|
||||
|
@ -27,7 +27,6 @@
|
|||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-svg-pan-zoom": "^3.3.5",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.31.0",
|
||||
"@typescript-eslint/parser": "^5.31.0",
|
||||
"@vitejs/plugin-react": "^2.0.0",
|
||||
|
@ -41,7 +40,6 @@
|
|||
"eslint-plugin-only-warn": "^1.0.3",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"jsdom": "^20.0.0",
|
||||
"postcss": "^8.4.14",
|
||||
"sass": "^1.54.0",
|
||||
|
|
150
pnpm-lock.yaml
generated
150
pnpm-lock.yaml
generated
|
@ -9,7 +9,6 @@ specifiers:
|
|||
'@types/react': ^18.0.15
|
||||
'@types/react-dom': ^18.0.6
|
||||
'@types/react-svg-pan-zoom': ^3.3.5
|
||||
'@types/react-window': ^1.8.5
|
||||
'@typescript-eslint/eslint-plugin': ^5.31.0
|
||||
'@typescript-eslint/parser': ^5.31.0
|
||||
'@vitejs/plugin-react': ^2.0.0
|
||||
|
@ -23,13 +22,12 @@ specifiers:
|
|||
eslint-plugin-only-warn: ^1.0.3
|
||||
eslint-plugin-promise: ^6.0.0
|
||||
eslint-plugin-react: ^7.30.1
|
||||
eslint-plugin-react-hooks: ^4.6.0
|
||||
framer-motion: ^6.5.1
|
||||
jsdom: ^20.0.0
|
||||
postcss: ^8.4.14
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
react-svg-pan-zoom: ^3.11.0
|
||||
react-window: ^1.8.7
|
||||
sass: ^1.54.0
|
||||
tailwindcss: ^3.1.7
|
||||
typescript: ^4.6.4
|
||||
|
@ -38,10 +36,10 @@ specifiers:
|
|||
|
||||
dependencies:
|
||||
'@heroicons/react': 1.0.6_react@18.2.0
|
||||
framer-motion: 6.5.1_biqbaboplfbrettd7655fr4n2y
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-svg-pan-zoom: 3.11.0_react@18.2.0
|
||||
react-window: 1.8.7_biqbaboplfbrettd7655fr4n2y
|
||||
|
||||
devDependencies:
|
||||
'@testing-library/dom': 8.16.1
|
||||
|
@ -51,7 +49,6 @@ devDependencies:
|
|||
'@types/react': 18.0.17
|
||||
'@types/react-dom': 18.0.6
|
||||
'@types/react-svg-pan-zoom': 3.3.5
|
||||
'@types/react-window': 1.8.5
|
||||
'@typescript-eslint/eslint-plugin': 5.32.0_iosr3hrei2tubxveewluhu5lhy
|
||||
'@typescript-eslint/parser': 5.32.0_qugx7qdu5zevzvxaiqyxfiwquq
|
||||
'@vitejs/plugin-react': 2.0.0_vite@3.0.4
|
||||
|
@ -65,7 +62,6 @@ devDependencies:
|
|||
eslint-plugin-only-warn: 1.0.3
|
||||
eslint-plugin-promise: 6.0.0_eslint@8.21.0
|
||||
eslint-plugin-react: 7.30.1_eslint@8.21.0
|
||||
eslint-plugin-react-hooks: 4.6.0_eslint@8.21.0
|
||||
jsdom: 20.0.0
|
||||
postcss: 8.4.16
|
||||
sass: 1.54.3
|
||||
|
@ -316,6 +312,7 @@ packages:
|
|||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
regenerator-runtime: 0.13.9
|
||||
dev: true
|
||||
|
||||
/@babel/template/7.18.10:
|
||||
resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==}
|
||||
|
@ -353,6 +350,19 @@ packages:
|
|||
to-fast-properties: 2.0.0
|
||||
dev: true
|
||||
|
||||
/@emotion/is-prop-valid/0.8.8:
|
||||
resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@emotion/memoize': 0.7.4
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@emotion/memoize/0.7.4:
|
||||
resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==}
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-loong64/0.14.54:
|
||||
resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -451,6 +461,53 @@ packages:
|
|||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
dev: true
|
||||
|
||||
/@motionone/animation/10.13.2:
|
||||
resolution: {integrity: sha512-YGWss58IR2X4lOjW89rv1Q+/Nq/QhfltaggI7i8sZTpKC1yUvM+XYDdvlRpWc6dk8LviMBrddBJAlLdbaqeRmw==}
|
||||
dependencies:
|
||||
'@motionone/easing': 10.13.2
|
||||
'@motionone/types': 10.13.2
|
||||
'@motionone/utils': 10.13.2
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/@motionone/dom/10.12.0:
|
||||
resolution: {integrity: sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw==}
|
||||
dependencies:
|
||||
'@motionone/animation': 10.13.2
|
||||
'@motionone/generators': 10.13.2
|
||||
'@motionone/types': 10.13.2
|
||||
'@motionone/utils': 10.13.2
|
||||
hey-listen: 1.0.8
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/@motionone/easing/10.13.2:
|
||||
resolution: {integrity: sha512-3HqctS5NyDfDQ+8+cZqc3Pu7I6amFCt9zDUjcozHyFXHh4PKYHK4+GJDFjJIS8bCAF2BrJmpmduDQ2V7lFEYeQ==}
|
||||
dependencies:
|
||||
'@motionone/utils': 10.13.2
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/@motionone/generators/10.13.2:
|
||||
resolution: {integrity: sha512-QMoXV1MXEEhR6D3dct/RMMS1FwJlAsW+kMPbFGzBA4NbweblgeYQCft9DcDAVpV9wIwD6qvlBG9u99sOXLfHiA==}
|
||||
dependencies:
|
||||
'@motionone/types': 10.13.2
|
||||
'@motionone/utils': 10.13.2
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/@motionone/types/10.13.2:
|
||||
resolution: {integrity: sha512-yYV4q5v5F0iADhab4wHfqaRJnM/eVtQLjUPhyEcS72aUz/xyOzi09GzD/Gu+K506BDfqn5eULIilUI77QNaqhw==}
|
||||
dev: false
|
||||
|
||||
/@motionone/utils/10.13.2:
|
||||
resolution: {integrity: sha512-6Lw5bDA/w7lrPmT/jYWQ76lkHlHs9fl2NZpJ22cVy1kKDdEH+Cl1U6hMTpdphO6VQktQ6v2APngag91WBKLqlA==}
|
||||
dependencies:
|
||||
'@motionone/types': 10.13.2
|
||||
hey-listen: 1.0.8
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/@nodelib/fs.scandir/2.1.5:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
|
@ -586,12 +643,6 @@ packages:
|
|||
'@types/react': 18.0.17
|
||||
dev: true
|
||||
|
||||
/@types/react-window/1.8.5:
|
||||
resolution: {integrity: sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==}
|
||||
dependencies:
|
||||
'@types/react': 18.0.17
|
||||
dev: true
|
||||
|
||||
/@types/react/18.0.17:
|
||||
resolution: {integrity: sha512-38ETy4tL+rn4uQQi7mB81G7V1g0u2ryquNmsVIOKUAEIDK+3CUjZ6rSRpdvS99dNBnkLFL83qfmtLacGOTIhwQ==}
|
||||
dependencies:
|
||||
|
@ -1689,15 +1740,6 @@ packages:
|
|||
eslint: 8.21.0
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-react-hooks/4.6.0_eslint@8.21.0:
|
||||
resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
|
||||
dependencies:
|
||||
eslint: 8.21.0
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-react/7.30.1_eslint@8.21.0:
|
||||
resolution: {integrity: sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -1944,6 +1986,30 @@ packages:
|
|||
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
||||
dev: true
|
||||
|
||||
/framer-motion/6.5.1_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8 || ^17.0.0 || ^18.0.0'
|
||||
react-dom: '>=16.8 || ^17.0.0 || ^18.0.0'
|
||||
dependencies:
|
||||
'@motionone/dom': 10.12.0
|
||||
framesync: 6.0.1
|
||||
hey-listen: 1.0.8
|
||||
popmotion: 11.0.3
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
style-value-types: 5.0.0
|
||||
tslib: 2.4.0
|
||||
optionalDependencies:
|
||||
'@emotion/is-prop-valid': 0.8.8
|
||||
dev: false
|
||||
|
||||
/framesync/6.0.1:
|
||||
resolution: {integrity: sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==}
|
||||
dependencies:
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/fs.realpath/1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
dev: true
|
||||
|
@ -2095,6 +2161,10 @@ packages:
|
|||
function-bind: 1.1.1
|
||||
dev: true
|
||||
|
||||
/hey-listen/1.0.8:
|
||||
resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==}
|
||||
dev: false
|
||||
|
||||
/html-encoding-sniffer/3.0.0:
|
||||
resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -2479,10 +2549,6 @@ packages:
|
|||
sourcemap-codec: 1.4.8
|
||||
dev: true
|
||||
|
||||
/memoize-one/5.2.1:
|
||||
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
|
||||
dev: false
|
||||
|
||||
/merge2/1.4.1:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
@ -2753,6 +2819,15 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/popmotion/11.0.3:
|
||||
resolution: {integrity: sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==}
|
||||
dependencies:
|
||||
framesync: 6.0.1
|
||||
hey-listen: 1.0.8
|
||||
style-value-types: 5.0.0
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/postcss-import/14.1.0_postcss@8.4.16:
|
||||
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
@ -2912,19 +2987,6 @@ packages:
|
|||
transformation-matrix: 2.12.0
|
||||
dev: false
|
||||
|
||||
/react-window/1.8.7_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-JHEZbPXBpKMmoNO1bNhoXOOLg/ujhL/BU4IqVU9r8eQPcy5KQnGHIHDRkJ0ns9IM5+Aq5LNwt3j8t3tIrePQzA==}
|
||||
engines: {node: '>8.0.0'}
|
||||
peerDependencies:
|
||||
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.9
|
||||
memoize-one: 5.2.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/react/18.2.0:
|
||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -2954,6 +3016,7 @@ packages:
|
|||
|
||||
/regenerator-runtime/0.13.9:
|
||||
resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==}
|
||||
dev: true
|
||||
|
||||
/regexp.prototype.flags/1.4.3:
|
||||
resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}
|
||||
|
@ -3164,6 +3227,13 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/style-value-types/5.0.0:
|
||||
resolution: {integrity: sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==}
|
||||
dependencies:
|
||||
hey-listen: 1.0.8
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/supports-color/5.5.0:
|
||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -3282,6 +3352,10 @@ packages:
|
|||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||
dev: true
|
||||
|
||||
/tslib/2.4.0:
|
||||
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
|
||||
dev: false
|
||||
|
||||
/tsutils/3.21.0_typescript@4.7.4:
|
||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
|
65
public/Interfaces.d.ts
vendored
65
public/Interfaces.d.ts
vendored
|
@ -1,65 +0,0 @@
|
|||
declare interface IHistoryState {
|
||||
LastAction: string
|
||||
MainContainer: IContainerModel
|
||||
SelectedContainer: IContainerModel | null
|
||||
SelectedContainerId: string
|
||||
TypeCounters: Record<string, number>
|
||||
}
|
||||
|
||||
declare interface IAvailableContainer {
|
||||
Type: string
|
||||
Width: number
|
||||
Height: number
|
||||
XPositionReference?: XPositionReference
|
||||
Style: React.CSSProperties
|
||||
}
|
||||
|
||||
declare interface IEditorState {
|
||||
history: IHistoryState[]
|
||||
historyCurrentStep: number
|
||||
configuration: IConfiguration
|
||||
}
|
||||
|
||||
declare interface IConfiguration {
|
||||
AvailableContainers: IAvailableContainer[]
|
||||
AvailableSymbols: IAvailableSymbol[]
|
||||
MainContainer: IAvailableContainer
|
||||
}
|
||||
|
||||
declare interface IContainerModel {
|
||||
children: IContainerModel[]
|
||||
parent: IContainerModel | null
|
||||
properties: IProperties
|
||||
userData: Record<string, string | number>
|
||||
}
|
||||
|
||||
declare interface IProperties extends React.CSSProperties {
|
||||
id: string
|
||||
parentId: string | null
|
||||
x: number
|
||||
y: number
|
||||
isRigidBody: boolean
|
||||
XPositionReference?: XPositionReference
|
||||
}
|
||||
|
||||
declare enum XPositionReference {
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
}
|
||||
|
||||
declare interface IAvailableSymbol {
|
||||
Name: string
|
||||
XPositionReference: XPositionReference
|
||||
Image: IImage
|
||||
Width: number
|
||||
Height: number
|
||||
}
|
||||
|
||||
declare interface IImage {
|
||||
Name: string
|
||||
Url: string
|
||||
Base64Image: string
|
||||
Svg: string
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
html,
|
||||
body,
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||
import { Configuration } from '../../Interfaces/Configuration';
|
||||
|
||||
/**
|
||||
* Fetch the configuration from the API
|
||||
* @returns {Configation} The model of the configuration for the application
|
||||
*/
|
||||
export async function fetchConfiguration(): Promise<IConfiguration> {
|
||||
export async function fetchConfiguration(): Promise<Configuration> {
|
||||
const url = `${import.meta.env.VITE_API_URL}`;
|
||||
// The test library cannot use the Fetch API
|
||||
// @ts-expect-error
|
||||
|
@ -15,7 +15,7 @@ export async function fetchConfiguration(): Promise<IConfiguration> {
|
|||
})
|
||||
.then(async(response) =>
|
||||
await response.json()
|
||||
) as IConfiguration;
|
||||
) as Configuration;
|
||||
}
|
||||
return await new Promise((resolve) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
html,
|
||||
body,
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import './App.scss';
|
||||
import { MainMenu } from '../MainMenu/MainMenu';
|
||||
import { ContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import Editor from '../Editor/Editor';
|
||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||
import { ContainerModel } from '../../Interfaces/ContainerModel';
|
||||
import Editor, { IEditorState } from '../Editor/Editor';
|
||||
import { LoadState } from './Load';
|
||||
import { LoadEditor, NewEditor } from './MenuActions';
|
||||
import { DEFAULT_CONFIG, DEFAULT_MAINCONTAINER_PROPS } from '../../utils/default';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { Revive } from '../../utils/saveload';
|
||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||
import { IEditorState } from '../Editor/Editor';
|
||||
|
||||
export function LoadState(
|
||||
editorState: IEditorState,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||
import { ContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { Configuration } from '../../Interfaces/Configuration';
|
||||
import { ContainerModel } from '../../Interfaces/ContainerModel';
|
||||
import { fetchConfiguration } from '../API/api';
|
||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||
import { IEditorState } from '../Editor/Editor';
|
||||
import { LoadState } from './Load';
|
||||
|
||||
export function NewEditor(
|
||||
|
@ -11,7 +11,7 @@ export function NewEditor(
|
|||
): void {
|
||||
// Fetch the configuration from the API
|
||||
fetchConfiguration()
|
||||
.then((configuration: IConfiguration) => {
|
||||
.then((configuration: Configuration) => {
|
||||
// Set the main container from the given properties of the API
|
||||
const MainContainer = new ContainerModel(
|
||||
null,
|
||||
|
@ -23,7 +23,6 @@ export function NewEditor(
|
|||
width: configuration.MainContainer.Width,
|
||||
height: configuration.MainContainer.Height,
|
||||
isRigidBody: false,
|
||||
isAnchor: false,
|
||||
fillOpacity: 0,
|
||||
stroke: 'black'
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ export const BAR_WIDTH = 64; // 4rem
|
|||
|
||||
export const Bar: React.FC<IBarProps> = (props) => {
|
||||
return (
|
||||
<div className='fixed z-20 flex flex-col top-0 left-0 h-full w-16 bg-slate-100'>
|
||||
<div className='fixed z-20 flex flex-col top-0 left-0 h-screen w-16 bg-slate-100'>
|
||||
<BarIcon
|
||||
isActive={props.isSidebarOpen}
|
||||
title='Components'
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
/**
|
||||
* @module AnchorBehavior
|
||||
*
|
||||
* An anchor is a container that takes physical priority in the representation :
|
||||
* - It cannot be moved by other rigid siblings container
|
||||
* - It cannot be resized by any other siblings container
|
||||
* - It cannot overlap any other siblings rigid container :
|
||||
* - overlapping container are shifted to the nearest available space/width
|
||||
* - or resized when there is no available space left other than theirs
|
||||
* - or lose their rigid body properties when there is no available space left)
|
||||
* Meaning that:
|
||||
* - Moving an anchor container will resize the width of an overlapping container
|
||||
* or make them lose their property as a rigid body
|
||||
*/
|
||||
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { constraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
|
||||
|
||||
/**
|
||||
* Impose the container position to its siblings
|
||||
* Apply the following modification to the overlapping rigid body container :
|
||||
* @param container Container to impose its position
|
||||
*/
|
||||
export function ImposePosition(container: IContainerModel): IContainerModel {
|
||||
if (container.parent === undefined ||
|
||||
container.parent === null) {
|
||||
return container;
|
||||
}
|
||||
|
||||
const rigidBodies = container.parent.children.filter(
|
||||
child => child.properties.isRigidBody && !child.properties.isAnchor
|
||||
);
|
||||
|
||||
const overlappingContainers = getOverlappingContainers(container, rigidBodies);
|
||||
for (const overlappingContainer of overlappingContainers) {
|
||||
constraintBodyInsideUnallocatedWidth(overlappingContainer);
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the overlapping containers with container
|
||||
* @param container A container
|
||||
* @param containers A list of containers
|
||||
* @returns A list of overlapping containers
|
||||
*/
|
||||
function getOverlappingContainers(
|
||||
container: IContainerModel,
|
||||
containers: IContainerModel[]
|
||||
): IContainerModel[] {
|
||||
const min1 = container.properties.x;
|
||||
const max1 = container.properties.x + Number(container.properties.width);
|
||||
const overlappingContainers: IContainerModel[] = [];
|
||||
for (const other of containers) {
|
||||
if (other === container) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const min2 = other.properties.x;
|
||||
const max2 = other.properties.x + Number(other.properties.width);
|
||||
const isOverlapping = Math.min(max1, max2) - Math.max(min1, min2) > 0;
|
||||
|
||||
if (!isOverlapping) {
|
||||
continue;
|
||||
}
|
||||
|
||||
overlappingContainers.push(other);
|
||||
}
|
||||
return overlappingContainers;
|
||||
}
|
|
@ -1,312 +0,0 @@
|
|||
/**
|
||||
* @module RigidBodyBehaviors
|
||||
* Apply the following contraints to the `container` :
|
||||
* - The container must be kept inside its parent
|
||||
* - The container must find an unallocated space within the parent
|
||||
* If the contraints fails, an error message will be returned
|
||||
*/
|
||||
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
||||
|
||||
/**
|
||||
* "Transform the container into a rigid body"
|
||||
* Apply the following contraints to the `container` :
|
||||
* - The container must be kept inside its parent
|
||||
* - The container must find an unallocated space within the parent
|
||||
* If the contraints fails, an error message will be returned
|
||||
* @param container Container to apply its rigid body properties
|
||||
* @returns A rigid body container
|
||||
*/
|
||||
export function RecalculatePhysics(
|
||||
container: IContainerModel
|
||||
): IContainerModel {
|
||||
container = constraintBodyInsideParent(container);
|
||||
container = constraintBodyInsideUnallocatedWidth(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit a rect inside a parent rect by applying the following rules :
|
||||
* it cannot be bigger than the parent
|
||||
* it cannot go out of bound
|
||||
* Mutates and returns the container
|
||||
* @param container
|
||||
* @returns Updated container
|
||||
*/
|
||||
function constraintBodyInsideParent(
|
||||
container: IContainerModel
|
||||
): IContainerModel {
|
||||
if (container.parent === null || container.parent === undefined) {
|
||||
return container;
|
||||
}
|
||||
|
||||
const parentProperties = container.parent.properties;
|
||||
const parentWidth = Number(parentProperties.width);
|
||||
const parentHeight = Number(parentProperties.height);
|
||||
|
||||
return constraintBodyInsideSpace(container, 0, 0, parentWidth, parentHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit a container inside a rectangle
|
||||
* Mutates and returns the container
|
||||
* @param container A container
|
||||
* @param x x of top left of the rectangle
|
||||
* @param y y of top left of the rectangle
|
||||
* @param width width of the rectangle
|
||||
* @param height height of the rectangle
|
||||
* @returns Updated container
|
||||
*/
|
||||
function constraintBodyInsideSpace(
|
||||
container: IContainerModel,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number
|
||||
): IContainerModel {
|
||||
const containerProperties = container.properties;
|
||||
const containerX = Number(containerProperties.x);
|
||||
const containerY = Number(containerProperties.y);
|
||||
const containerWidth = Number(containerProperties.width);
|
||||
const containerHeight = Number(containerProperties.height);
|
||||
|
||||
// Check size bigger than parent
|
||||
const isBodyLargerThanParent = containerWidth > width;
|
||||
const isBodyTallerThanParentHeight = containerHeight > height;
|
||||
if (isBodyLargerThanParent || isBodyTallerThanParentHeight) {
|
||||
if (isBodyLargerThanParent) {
|
||||
containerProperties.x = x;
|
||||
containerProperties.width = width;
|
||||
}
|
||||
if (isBodyTallerThanParentHeight) {
|
||||
containerProperties.y = y;
|
||||
containerProperties.height = height;
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
// Check horizontal out of bound
|
||||
if (containerX < x) {
|
||||
containerProperties.x = x;
|
||||
}
|
||||
if (containerX + containerWidth > x + width) {
|
||||
containerProperties.x = x + width - containerWidth;
|
||||
}
|
||||
|
||||
// Check vertical out of bound
|
||||
if (containerY < y) {
|
||||
containerProperties.y = y;
|
||||
}
|
||||
if (containerY + containerHeight > y + height) {
|
||||
containerProperties.y = y + height - containerHeight;
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constraint the container inside unallocated width/space of the parent container
|
||||
* If there is no unalloacted width/space, an error will be thrown
|
||||
* Mutates and returns the container
|
||||
* @param container
|
||||
* @returns Updated container
|
||||
*/
|
||||
export function constraintBodyInsideUnallocatedWidth(
|
||||
container: IContainerModel
|
||||
): IContainerModel {
|
||||
if (container.parent === null) {
|
||||
return container;
|
||||
}
|
||||
|
||||
// Get the available spaces of the parent
|
||||
const availableWidths = getAvailableWidths(container.parent, container);
|
||||
const containerX = Number(container.properties.x);
|
||||
const containerWidth = Number(container.properties.width);
|
||||
|
||||
// Check if there is still some space
|
||||
if (availableWidths.length === 0) {
|
||||
throw new Error(
|
||||
'No available space found on the parent container. Try to free the parent a little before placing it inside.'
|
||||
);
|
||||
}
|
||||
|
||||
const middle = containerX + containerWidth / 2;
|
||||
// Sort the available width to find the space with the closest position
|
||||
availableWidths.sort(
|
||||
(width1, width2) => {
|
||||
let compared1X = width1.x;
|
||||
if (width1.x < containerX) {
|
||||
compared1X = width1.x + width1.width - containerWidth;
|
||||
}
|
||||
|
||||
let compared2X = width2.x;
|
||||
if (width2.x < containerX) {
|
||||
compared2X = width2.x + width2.width - containerWidth;
|
||||
}
|
||||
|
||||
return Math.abs(compared1X - middle) - Math.abs(compared2X - middle);
|
||||
}
|
||||
);
|
||||
|
||||
// Check if the container actually fit inside
|
||||
// It will usually fit if it was alrady fitting
|
||||
const availableWidthFound = availableWidths.find((width) =>
|
||||
isFitting(container, width)
|
||||
);
|
||||
|
||||
if (availableWidthFound === undefined) {
|
||||
// Otherwise, it is possible that it does not fit
|
||||
// There is two way to reach this part of the code
|
||||
// 1) Enable isRigidBody such as width > availableWidth.width
|
||||
// 2) Resize a container such as width > availableWidth.width
|
||||
|
||||
// We want the container to fit automatically inside the available space
|
||||
// even if it means to resize the container
|
||||
// The end goal is that the code never show the error message no matter what action is done
|
||||
// TODO: Actually give an option to not fit and show the error message shown below
|
||||
const availableWidth = availableWidths[0];
|
||||
container.properties.x = availableWidth.x;
|
||||
container.properties.width = availableWidth.width;
|
||||
// throw new Error('[constraintBodyInsideUnallocatedWidth] BIGERR: No available space found on the parent container, even though there is some.');
|
||||
return container;
|
||||
}
|
||||
|
||||
return constraintBodyInsideSpace(
|
||||
container,
|
||||
availableWidthFound.x,
|
||||
0,
|
||||
availableWidthFound.width,
|
||||
Number(container.parent.properties.height)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unallocated widths inside a container
|
||||
* An allocated width is defined by its the widths of the children that are rigid bodies.
|
||||
* An example of this allocation system is the disk space of an hard drive
|
||||
* (except the fact that disk space is divided by block).
|
||||
* @param container Container where to find an available width
|
||||
* @param exception Container to exclude of the widths (since a container will be moved, it might need to be excluded)
|
||||
* @returns {ISizePointer[]} Array of unallocated widths (x=position of the unallocated space, width=size of the allocated space)
|
||||
*/
|
||||
function getAvailableWidths(
|
||||
container: IContainerModel,
|
||||
exception: IContainerModel
|
||||
): ISizePointer[] {
|
||||
// Initialize the first size pointer
|
||||
// which takes full width of the available space
|
||||
const x = 0;
|
||||
const width = Number(container.properties.width);
|
||||
let unallocatedSpaces: ISizePointer[] = [{ x, width }];
|
||||
|
||||
// We will only uses containers that also are rigid or are anchors
|
||||
const solidBodies = container.children.filter(
|
||||
(child) => child.properties.isRigidBody || child.properties.isAnchor
|
||||
);
|
||||
|
||||
for (const child of solidBodies) {
|
||||
// Ignore the exception
|
||||
if (child === exception) {
|
||||
continue;
|
||||
}
|
||||
const childX = child.properties.x;
|
||||
const childWidth = Number(child.properties.width);
|
||||
|
||||
// get the space of the child that is inside the parent
|
||||
let newUnallocatedSpace: ISizePointer[] = [];
|
||||
|
||||
// We will iterate on a mutable variable in order to divide it
|
||||
for (const unallocatedSpace of unallocatedSpaces) {
|
||||
// In order to find unallocated space,
|
||||
// We need to calculate the overlap between the two containers
|
||||
// We only works with widths meaning in 1D (with lines)
|
||||
const newUnallocatedWidths = getAvailableWidthsTwoLines(
|
||||
unallocatedSpace.x,
|
||||
unallocatedSpace.x + unallocatedSpace.width,
|
||||
childX,
|
||||
childX + childWidth
|
||||
);
|
||||
|
||||
// Concat the new list of SizePointer pointing to availables spaces
|
||||
newUnallocatedSpace = newUnallocatedSpace.concat(newUnallocatedWidths);
|
||||
}
|
||||
// Finally update the availables spaces found, loop again with it
|
||||
unallocatedSpaces = newUnallocatedSpace;
|
||||
}
|
||||
|
||||
return unallocatedSpaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unallocated widths between two lines in 1D
|
||||
* @param unalloctedSpaceLeft left of the first line
|
||||
* @param unallocatedSpaceRight rigth of the first line
|
||||
* @param rectLeft left of the second line
|
||||
* @param rectRight right of the second line
|
||||
* @returns Available widths
|
||||
*/
|
||||
function getAvailableWidthsTwoLines(
|
||||
unalloctedSpaceLeft: number,
|
||||
unallocatedSpaceRight: number,
|
||||
rectLeft: number,
|
||||
rectRight: number
|
||||
): ISizePointer[] {
|
||||
if (unallocatedSpaceRight < rectLeft ||
|
||||
unalloctedSpaceLeft > rectRight
|
||||
) {
|
||||
// object 1 and 2 are not overlapping
|
||||
return [{
|
||||
x: unalloctedSpaceLeft,
|
||||
width: unallocatedSpaceRight - unalloctedSpaceLeft
|
||||
}];
|
||||
}
|
||||
|
||||
if (rectLeft < unalloctedSpaceLeft && rectRight > unallocatedSpaceRight) {
|
||||
// object 2 is overlapping full width
|
||||
return [];
|
||||
}
|
||||
|
||||
if (unalloctedSpaceLeft >= rectLeft) {
|
||||
// object 2 is partially overlapping on the left
|
||||
return [
|
||||
{
|
||||
x: rectRight,
|
||||
width: unallocatedSpaceRight - rectRight
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (rectRight >= unallocatedSpaceRight) {
|
||||
// object 2 is partially overlapping on the right
|
||||
return [
|
||||
{
|
||||
x: unalloctedSpaceLeft,
|
||||
width: rectRight - unalloctedSpaceLeft
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// object 2 is overlapping in the middle
|
||||
return [
|
||||
{
|
||||
x: unalloctedSpaceLeft,
|
||||
width: rectLeft - unalloctedSpaceLeft
|
||||
},
|
||||
{
|
||||
x: rectRight,
|
||||
width: unallocatedSpaceRight - rectRight
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a container can fit inside a size space
|
||||
* @param container Container to check
|
||||
* @param sizePointer Size space to check
|
||||
* @returns
|
||||
*/
|
||||
const isFitting = (
|
||||
container: IContainerModel,
|
||||
sizePointer: ISizePointer
|
||||
): boolean => Number(container.properties.width) <= sizePointer.width;
|
|
@ -1,10 +1,10 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||
import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { HistoryState } from '../../Interfaces/HistoryState';
|
||||
import { Configuration } from '../../Interfaces/Configuration';
|
||||
import { ContainerModel, IContainerModel } from '../../Interfaces/ContainerModel';
|
||||
import { findContainerById } from '../../utils/itertools';
|
||||
import { getCurrentHistory } from './Editor';
|
||||
import IProperties from '../../Interfaces/IProperties';
|
||||
import { SizePointer } from '../../Interfaces/SizePointer';
|
||||
|
||||
/**
|
||||
* Select a container
|
||||
|
@ -12,9 +12,9 @@ import IProperties from '../../Interfaces/IProperties';
|
|||
*/
|
||||
export function SelectContainer(
|
||||
container: ContainerModel,
|
||||
fullHistory: IHistoryState[],
|
||||
fullHistory: HistoryState[],
|
||||
historyCurrentStep: number,
|
||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||
setHistory: Dispatch<SetStateAction<HistoryState[]>>,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||
): void {
|
||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||
|
@ -27,30 +27,21 @@ export function SelectContainer(
|
|||
throw new Error('[SelectContainer] Cannot find container among children of main container!');
|
||||
}
|
||||
|
||||
history.push({
|
||||
LastAction: `Select ${selectedContainer.properties.id}`,
|
||||
setHistory(history.concat([{
|
||||
LastAction: `Select container ${selectedContainer.properties.id}`,
|
||||
MainContainer: mainContainerClone,
|
||||
SelectedContainer: selectedContainer,
|
||||
SelectedContainerId: selectedContainer.properties.id,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||
});
|
||||
setHistory(history);
|
||||
setHistoryCurrentStep(history.length - 1);
|
||||
}]));
|
||||
setHistoryCurrentStep(history.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a container
|
||||
* @param containerId containerId of the container to delete
|
||||
* @param fullHistory History of the editor
|
||||
* @param historyCurrentStep Current step
|
||||
* @param setHistory State setter for History
|
||||
* @param setHistoryCurrentStep State setter for current step
|
||||
*/
|
||||
export function DeleteContainer(
|
||||
containerId: string,
|
||||
fullHistory: IHistoryState[],
|
||||
fullHistory: HistoryState[],
|
||||
historyCurrentStep: number,
|
||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||
setHistory: Dispatch<SetStateAction<HistoryState[]>>,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||
): void {
|
||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||
|
@ -63,58 +54,43 @@ export function DeleteContainer(
|
|||
throw new Error(`[DeleteContainer] Tried to delete a container that is not present in the main container: ${containerId}`);
|
||||
}
|
||||
|
||||
if (container === mainContainerClone ||
|
||||
container.parent === undefined ||
|
||||
container.parent === null) {
|
||||
if (container === mainContainerClone) {
|
||||
// TODO: Implement alert
|
||||
throw new Error('[DeleteContainer] Tried to delete the main container! Deleting the main container is not allowed!');
|
||||
}
|
||||
|
||||
if (container === null || container === undefined) {
|
||||
throw new Error('[DeleteContainer] Container model was not found among children of the main container!');
|
||||
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
||||
}
|
||||
|
||||
const index = container.parent.children.indexOf(container);
|
||||
if (index > -1) {
|
||||
container.parent.children.splice(index, 1);
|
||||
} else {
|
||||
throw new Error('[DeleteContainer] Could not find container among parent\'s children');
|
||||
if (container.parent != null) {
|
||||
const index = container.parent.children.indexOf(container);
|
||||
if (index > -1) {
|
||||
container.parent.children.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Select the previous container
|
||||
// or select the one above
|
||||
const SelectedContainer = findContainerById(mainContainerClone, current.SelectedContainerId) ??
|
||||
container.parent.children.at(index - 1) ??
|
||||
container.parent;
|
||||
const SelectedContainerId = SelectedContainer.properties.id;
|
||||
|
||||
history.push({
|
||||
LastAction: `Delete ${containerId}`,
|
||||
setHistory(history.concat([{
|
||||
LastAction: `Delete container ${containerId}`,
|
||||
MainContainer: mainContainerClone,
|
||||
SelectedContainer,
|
||||
SelectedContainerId,
|
||||
SelectedContainer: null,
|
||||
SelectedContainerId: '',
|
||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||
});
|
||||
setHistory(history);
|
||||
setHistoryCurrentStep(history.length - 1);
|
||||
}]));
|
||||
setHistoryCurrentStep(history.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new container to a selected container
|
||||
* @param type The type of container
|
||||
* @param configuration Configuration of the App
|
||||
* @param fullHistory History of the editor
|
||||
* @param historyCurrentStep Current step
|
||||
* @param setHistory State setter for History
|
||||
* @param setHistoryCurrentStep State setter for current step
|
||||
* @returns void
|
||||
*/
|
||||
export function AddContainerToSelectedContainer(
|
||||
type: string,
|
||||
configuration: IConfiguration,
|
||||
fullHistory: IHistoryState[],
|
||||
configuration: Configuration,
|
||||
fullHistory: HistoryState[],
|
||||
historyCurrentStep: number,
|
||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||
setHistory: Dispatch<SetStateAction<HistoryState[]>>,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||
): void {
|
||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||
|
@ -138,26 +114,14 @@ export function AddContainerToSelectedContainer(
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and add a new container at `index` in children of parent of `parentId`
|
||||
* @param index Index where to insert to the new container
|
||||
* @param type Type of container
|
||||
* @param parentId Parent in which to insert the new container
|
||||
* @param configuration Configuration of the app
|
||||
* @param fullHistory History of the editor
|
||||
* @param historyCurrentStep Current step
|
||||
* @param setHistory State setter of History
|
||||
* @param setHistoryCurrentStep State setter of the current step
|
||||
* @returns void
|
||||
*/
|
||||
export function AddContainer(
|
||||
index: number,
|
||||
type: string,
|
||||
parentId: string,
|
||||
configuration: IConfiguration,
|
||||
fullHistory: IHistoryState[],
|
||||
configuration: Configuration,
|
||||
fullHistory: HistoryState[],
|
||||
historyCurrentStep: number,
|
||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||
setHistory: Dispatch<SetStateAction<HistoryState[]>>,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||
): void {
|
||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||
|
@ -206,23 +170,19 @@ export function AddContainer(
|
|||
}
|
||||
}
|
||||
|
||||
const defaultProperties: IProperties = {
|
||||
id: `${type}-${count}`,
|
||||
parentId: parentClone.properties.id,
|
||||
x,
|
||||
y: 0,
|
||||
width: properties.Width,
|
||||
height: parentClone.properties.height,
|
||||
isRigidBody: false,
|
||||
isAnchor: false,
|
||||
XPositionReference: properties.XPositionReference,
|
||||
...properties.Style
|
||||
};
|
||||
|
||||
// Create the container
|
||||
const newContainer = new ContainerModel(
|
||||
parentClone,
|
||||
defaultProperties,
|
||||
{
|
||||
id: `${type}-${count}`,
|
||||
parentId: parentClone.properties.id,
|
||||
x,
|
||||
y: 0,
|
||||
width: properties?.Width,
|
||||
height: parentClone.properties.height,
|
||||
isRigidBody: false,
|
||||
...properties.Style
|
||||
},
|
||||
[],
|
||||
{
|
||||
type
|
||||
|
@ -237,13 +197,278 @@ export function AddContainer(
|
|||
}
|
||||
|
||||
// Update the state
|
||||
history.push({
|
||||
setHistory(history.concat([{
|
||||
LastAction: 'Add container',
|
||||
MainContainer: clone,
|
||||
SelectedContainer: parentClone,
|
||||
SelectedContainerId: parentClone.properties.id,
|
||||
TypeCounters: newCounters
|
||||
});
|
||||
setHistory(history);
|
||||
setHistoryCurrentStep(history.length - 1);
|
||||
}]));
|
||||
setHistoryCurrentStep(history.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handled the property change event in the properties form
|
||||
* @param key Property name
|
||||
* @param value New value of the property
|
||||
* @returns void
|
||||
*/
|
||||
export function OnPropertyChange(
|
||||
key: string,
|
||||
value: string | number | boolean,
|
||||
fullHistory: HistoryState[],
|
||||
historyCurrentStep: number,
|
||||
setHistory: Dispatch<SetStateAction<HistoryState[]>>,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||
): void {
|
||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||
const current = history[history.length - 1];
|
||||
|
||||
if (current.SelectedContainer === null ||
|
||||
current.SelectedContainer === undefined) {
|
||||
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
||||
}
|
||||
|
||||
if (parent === null) {
|
||||
const selectedContainerClone: IContainerModel = structuredClone(current.SelectedContainer);
|
||||
(selectedContainerClone.properties as any)[key] = value;
|
||||
setHistory(history.concat([{
|
||||
LastAction: 'Change property of main',
|
||||
MainContainer: selectedContainerClone,
|
||||
SelectedContainer: selectedContainerClone,
|
||||
SelectedContainerId: selectedContainerClone.properties.id,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||
}]));
|
||||
setHistoryCurrentStep(history.length);
|
||||
return;
|
||||
}
|
||||
|
||||
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
||||
const container: ContainerModel | undefined = findContainerById(mainContainerClone, current.SelectedContainer.properties.id);
|
||||
|
||||
if (container === null || container === undefined) {
|
||||
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
||||
}
|
||||
|
||||
(container.properties as any)[key] = value;
|
||||
|
||||
if (container.properties.isRigidBody) {
|
||||
RecalculatePhysics(container);
|
||||
}
|
||||
|
||||
setHistory(history.concat([{
|
||||
LastAction: `Change property of container ${container.properties.id}`,
|
||||
MainContainer: mainContainerClone,
|
||||
SelectedContainer: container,
|
||||
SelectedContainerId: container.properties.id,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||
}]));
|
||||
setHistoryCurrentStep(history.length);
|
||||
}
|
||||
|
||||
// TODO put this in a different file
|
||||
|
||||
export function RecalculatePhysics(container: IContainerModel): IContainerModel {
|
||||
container = constraintBodyInsideParent(container);
|
||||
container = constraintBodyInsideUnallocatedWidth(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit a rect inside a parent rect by applying the following rules :
|
||||
* it cannot be bigger than the parent
|
||||
* it cannot go out of bound
|
||||
* @param container
|
||||
* @returns
|
||||
*/
|
||||
function constraintBodyInsideParent(container: IContainerModel): IContainerModel {
|
||||
if (container.parent === null || container.parent === undefined) {
|
||||
return container;
|
||||
}
|
||||
|
||||
const parentProperties = container.parent.properties;
|
||||
const parentWidth = Number(parentProperties.width);
|
||||
const parentHeight = Number(parentProperties.height);
|
||||
|
||||
return constraintBodyInsideSpace(container, 0, 0, parentWidth, parentHeight);
|
||||
}
|
||||
|
||||
function constraintBodyInsideSpace(
|
||||
container: IContainerModel,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number
|
||||
): IContainerModel {
|
||||
const containerProperties = container.properties;
|
||||
const containerX = Number(containerProperties.x);
|
||||
const containerY = Number(containerProperties.y);
|
||||
const containerWidth = Number(containerProperties.width);
|
||||
const containerHeight = Number(containerProperties.height);
|
||||
|
||||
// Check size bigger than parent
|
||||
const isBodyLargerThanParent = containerWidth > width;
|
||||
const isBodyTallerThanParentHeight = containerHeight > height;
|
||||
if (isBodyLargerThanParent || isBodyTallerThanParentHeight) {
|
||||
if (isBodyLargerThanParent) {
|
||||
containerProperties.x = x;
|
||||
containerProperties.width = width;
|
||||
}
|
||||
if (isBodyTallerThanParentHeight) {
|
||||
containerProperties.y = y;
|
||||
containerProperties.height = height;
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
// Check horizontal out of bound
|
||||
if (containerX < x) {
|
||||
containerProperties.x = x;
|
||||
}
|
||||
if (containerX + containerWidth > width) {
|
||||
containerProperties.x = x + width - containerWidth;
|
||||
}
|
||||
|
||||
// Check vertical out of bound
|
||||
if (containerY < y) {
|
||||
containerProperties.y = y;
|
||||
}
|
||||
if (containerY + containerHeight > height) {
|
||||
containerProperties.y = y + height - containerHeight;
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unallocated widths inside a container
|
||||
* An allocated width is defined by its the widths of the children that are rigid bodies.
|
||||
* An example of this allocation system is the disk space
|
||||
* (except the fact that disk space is divided by block).
|
||||
* @param container
|
||||
* @returns {SizePointer[]} Array of unallocated widths (x=position of the unallocated space, width=size of the allocated space)
|
||||
*/
|
||||
function getAvailableWidths(container: IContainerModel, exception: IContainerModel): SizePointer[] {
|
||||
const x = 0;
|
||||
const width = Number(container.properties.width);
|
||||
let unallocatedSpaces: SizePointer[] = [{ x, width }];
|
||||
|
||||
const rigidBodies = container.children.filter(child => child.properties.isRigidBody);
|
||||
for (const child of rigidBodies) {
|
||||
if (child === exception) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the space of the child that is inside the parent
|
||||
let newUnallocatedSpace: SizePointer[] = [];
|
||||
for (const unallocatedSpace of unallocatedSpaces) {
|
||||
const newUnallocatedWidths = getAvailableWidthsTwoLines(
|
||||
unallocatedSpace.x,
|
||||
unallocatedSpace.x + unallocatedSpace.width,
|
||||
child.properties.x,
|
||||
child.properties.x + Number(child.properties.width));
|
||||
newUnallocatedSpace = newUnallocatedSpace.concat(newUnallocatedWidths);
|
||||
}
|
||||
unallocatedSpaces = newUnallocatedSpace;
|
||||
}
|
||||
|
||||
return unallocatedSpaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unallocated widths between two lines in 1D
|
||||
* @param min1 left of the first line
|
||||
* @param max1 rigth of the first line
|
||||
* @param min2 left of the second line
|
||||
* @param max2 right of the second line
|
||||
* @returns Available widths
|
||||
*/
|
||||
function getAvailableWidthsTwoLines(min1: number, max1: number, min2: number, max2: number): SizePointer[] {
|
||||
if (min2 < min1 && max2 > max1) {
|
||||
// object 2 is overlapping full width
|
||||
return [];
|
||||
}
|
||||
|
||||
if (min1 >= min2) {
|
||||
// object 2 is partially overlapping on the left
|
||||
return [{
|
||||
x: max2,
|
||||
width: max1 - max2
|
||||
}];
|
||||
}
|
||||
|
||||
if (max2 >= max1) {
|
||||
// object 2 is partially overlapping on the right
|
||||
return [{
|
||||
x: min2,
|
||||
width: max2 - min1
|
||||
}];
|
||||
}
|
||||
|
||||
// object 2 is overlapping in the middle
|
||||
return [
|
||||
{
|
||||
x: min1,
|
||||
width: min2 - min1
|
||||
},
|
||||
{
|
||||
x: min2,
|
||||
width: max1 - max2
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param container
|
||||
* @returns
|
||||
*/
|
||||
function constraintBodyInsideUnallocatedWidth(container: IContainerModel): IContainerModel {
|
||||
if (container.parent === null) {
|
||||
return container;
|
||||
}
|
||||
|
||||
const availableWidths = getAvailableWidths(container.parent, container);
|
||||
const containerX = Number(container.properties.x);
|
||||
|
||||
// Sort the available width
|
||||
availableWidths
|
||||
.sort((width1, width2) => Math.abs(width1.x - containerX) - Math.abs(width2.x - containerX));
|
||||
|
||||
if (availableWidths.length === 0) {
|
||||
throw new Error('No available space found on the parent container. Try to free the parent a little before placing it inside.');
|
||||
}
|
||||
|
||||
const availableWidthFound = availableWidths.find(
|
||||
width => isFitting(container, width)
|
||||
);
|
||||
|
||||
if (availableWidthFound === undefined) {
|
||||
// There is two way to reach this part of the code
|
||||
// 1) toggle the isRigidBody such as width > availableWidth.width
|
||||
// 2) resize a container such as width > availableWidth.width
|
||||
// We want the container to fit automatically inside the available space
|
||||
// even if it means to resize the container
|
||||
// The end goal is that the code never show the error message no matter what action is done
|
||||
// TODO: Actually give an option to not fit and show the error message shown below
|
||||
const availableWidth = availableWidths[0];
|
||||
container.properties.x = availableWidth.x;
|
||||
container.properties.width = availableWidth.width;
|
||||
// throw new Error('[constraintBodyInsideUnallocatedWidth] BIGERR: No available space found on the parent container, even though there is some.');
|
||||
return container;
|
||||
}
|
||||
|
||||
return constraintBodyInsideSpace(
|
||||
container,
|
||||
availableWidthFound.x,
|
||||
0,
|
||||
availableWidthFound.width,
|
||||
Number(container.parent.properties.height)
|
||||
);
|
||||
}
|
||||
|
||||
function isFitting(container: IContainerModel, sizePointer: SizePointer): boolean {
|
||||
const containerWidth = Number(container.properties.width);
|
||||
|
||||
return containerWidth <= sizePointer.width;
|
||||
}
|
||||
|
|
|
@ -1,35 +1,31 @@
|
|||
import React, { useRef } from 'react';
|
||||
import React from 'react';
|
||||
import './Editor.scss';
|
||||
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||
import { Configuration } from '../../Interfaces/Configuration';
|
||||
import { SVG } from '../SVG/SVG';
|
||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||
import { HistoryState } from '../../Interfaces/HistoryState';
|
||||
import { UI } from '../UI/UI';
|
||||
import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, AddContainer } from './ContainerOperations';
|
||||
import { SelectContainer, DeleteContainer, OnPropertyChange, AddContainerToSelectedContainer, AddContainer } from './ContainerOperations';
|
||||
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Save';
|
||||
import { onKeyDown } from './Shortcuts';
|
||||
import { OnPropertyChange, OnPropertiesSubmit } from './PropertiesOperations';
|
||||
import EditorEvents from '../../Events/EditorEvents';
|
||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||
import { MAX_HISTORY } from '../../utils/default';
|
||||
|
||||
interface IEditorProps {
|
||||
configuration: IConfiguration
|
||||
history: IHistoryState[]
|
||||
configuration: Configuration
|
||||
history: HistoryState[]
|
||||
historyCurrentStep: number
|
||||
}
|
||||
|
||||
export const getCurrentHistory = (history: IHistoryState[], historyCurrentStep: number): IHistoryState[] =>
|
||||
history.slice(
|
||||
Math.max(0, history.length - MAX_HISTORY), // change this to 0 for unlimited (not recommanded because of overflow)
|
||||
historyCurrentStep + 1
|
||||
);
|
||||
export interface IEditorState {
|
||||
history: HistoryState[]
|
||||
historyCurrentStep: number
|
||||
configuration: Configuration
|
||||
}
|
||||
|
||||
export const getCurrentHistoryState = (history: IHistoryState[], historyCurrentStep: number): IHistoryState => history[historyCurrentStep];
|
||||
export const getCurrentHistory = (history: HistoryState[], historyCurrentStep: number): HistoryState[] => history.slice(0, historyCurrentStep + 1);
|
||||
export const getCurrentHistoryState = (history: HistoryState[], historyCurrentStep: number): HistoryState => history[historyCurrentStep];
|
||||
|
||||
const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||
const [history, setHistory] = React.useState<IHistoryState[]>(structuredClone(props.history));
|
||||
const [history, setHistory] = React.useState<HistoryState[]>(structuredClone(props.history));
|
||||
const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(props.historyCurrentStep);
|
||||
const editorRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const onKeyUp = (event: KeyboardEvent): void => onKeyDown(
|
||||
|
@ -41,37 +37,15 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
|||
|
||||
window.addEventListener('keyup', onKeyUp);
|
||||
|
||||
const events = EditorEvents;
|
||||
const editorState: IEditorState = {
|
||||
history,
|
||||
historyCurrentStep,
|
||||
configuration: props.configuration
|
||||
};
|
||||
|
||||
const funcs = new Map<string, () => void>();
|
||||
for (const event of events) {
|
||||
const func = (): void => event.func(editorState);
|
||||
editorRef.current?.addEventListener(event.name, func);
|
||||
funcs.set(event.name, func);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keyup', onKeyUp);
|
||||
|
||||
for (const event of events) {
|
||||
const func = funcs.get(event.name);
|
||||
if (func === undefined) {
|
||||
continue;
|
||||
}
|
||||
editorRef.current?.removeEventListener(event.name, func);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const configuration = props.configuration;
|
||||
const current = getCurrentHistoryState(history, historyCurrentStep);
|
||||
return (
|
||||
<div ref={editorRef} className="Editor font-sans h-full">
|
||||
<div className="App font-sans h-full">
|
||||
<UI
|
||||
current={current}
|
||||
history={history}
|
||||
|
@ -98,14 +72,6 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
|||
setHistory,
|
||||
setHistoryCurrentStep
|
||||
)}
|
||||
OnPropertiesSubmit={(event, properties) => OnPropertiesSubmit(
|
||||
event,
|
||||
properties,
|
||||
history,
|
||||
historyCurrentStep,
|
||||
setHistory,
|
||||
setHistoryCurrentStep
|
||||
)}
|
||||
AddContainerToSelectedContainer={(type) => AddContainerToSelectedContainer(
|
||||
type,
|
||||
configuration,
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { IContainerModel, ContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||
import IProperties from '../../Interfaces/IProperties';
|
||||
import { findContainerById } from '../../utils/itertools';
|
||||
import { getCurrentHistory } from './Editor';
|
||||
import { RecalculatePhysics } from './Behaviors/RigidBodyBehaviors';
|
||||
import { INPUT_TYPES } from '../Properties/PropertiesInputTypes';
|
||||
import { ImposePosition } from './Behaviors/AnchorBehaviors';
|
||||
|
||||
/**
|
||||
* Handled the property change event in the properties form
|
||||
* @param key Property name
|
||||
* @param value New value of the property
|
||||
* @returns void
|
||||
*/
|
||||
export function OnPropertyChange(
|
||||
key: string,
|
||||
value: string | number | boolean,
|
||||
fullHistory: IHistoryState[],
|
||||
historyCurrentStep: number,
|
||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||
): void {
|
||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||
const current = history[history.length - 1];
|
||||
|
||||
if (current.SelectedContainer === null ||
|
||||
current.SelectedContainer === undefined) {
|
||||
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
||||
}
|
||||
|
||||
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
||||
const container: ContainerModel | undefined = findContainerById(mainContainerClone, current.SelectedContainer.properties.id);
|
||||
|
||||
if (container === null || container === undefined) {
|
||||
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
||||
}
|
||||
|
||||
if (INPUT_TYPES[key] === 'number') {
|
||||
(container.properties as any)[key] = Number(value);
|
||||
} else {
|
||||
(container.properties as any)[key] = value;
|
||||
}
|
||||
|
||||
if (container.properties.isAnchor) {
|
||||
ImposePosition(container);
|
||||
}
|
||||
|
||||
if (container.properties.isRigidBody) {
|
||||
RecalculatePhysics(container);
|
||||
}
|
||||
|
||||
history.push({
|
||||
LastAction: `Change ${key} of ${container.properties.id}`,
|
||||
MainContainer: mainContainerClone,
|
||||
SelectedContainer: container,
|
||||
SelectedContainerId: container.properties.id,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||
});
|
||||
setHistory(history);
|
||||
setHistoryCurrentStep(history.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handled the property change event in the properties form
|
||||
* @param key Property name
|
||||
* @param properties Properties of the selected container
|
||||
* @returns void
|
||||
*/
|
||||
export function OnPropertiesSubmit(
|
||||
event: React.SyntheticEvent<HTMLFormElement>,
|
||||
properties: IProperties,
|
||||
fullHistory: IHistoryState[],
|
||||
historyCurrentStep: number,
|
||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||
): void {
|
||||
event.preventDefault();
|
||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||
const current = history[history.length - 1];
|
||||
|
||||
if (current.SelectedContainer === null ||
|
||||
current.SelectedContainer === undefined) {
|
||||
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
||||
}
|
||||
|
||||
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
||||
const container: ContainerModel | undefined = findContainerById(mainContainerClone, current.SelectedContainer.properties.id);
|
||||
|
||||
if (container === null || container === undefined) {
|
||||
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
||||
}
|
||||
|
||||
for (const property in properties) {
|
||||
const input = (event.target as HTMLFormElement).querySelector(`#${property}`);
|
||||
if (input instanceof HTMLInputElement) {
|
||||
(container.properties as any)[property] = input.value;
|
||||
if (INPUT_TYPES[property] === 'number') {
|
||||
(container.properties as any)[property] = Number(input.value);
|
||||
} else {
|
||||
(container.properties as any)[property] = input.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (container.properties.isRigidBody) {
|
||||
RecalculatePhysics(container);
|
||||
}
|
||||
|
||||
history.push({
|
||||
LastAction: `Change properties of ${container.properties.id}`,
|
||||
MainContainer: mainContainerClone,
|
||||
SelectedContainer: container,
|
||||
SelectedContainerId: container.properties.id,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||
});
|
||||
setHistory(history);
|
||||
setHistoryCurrentStep(history.length - 1);
|
||||
}
|
|
@ -1,40 +1,29 @@
|
|||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||
import { HistoryState } from "../../Interfaces/HistoryState";
|
||||
import { Configuration } from '../../Interfaces/Configuration';
|
||||
import { getCircularReplacer } from '../../utils/saveload';
|
||||
import { ID } from '../SVG/SVG';
|
||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||
import Worker from '../../workers/worker?worker';
|
||||
import { IEditorState } from './Editor';
|
||||
|
||||
export function SaveEditorAsJSON(
|
||||
history: IHistoryState[],
|
||||
history: HistoryState[],
|
||||
historyCurrentStep: number,
|
||||
configuration: IConfiguration
|
||||
configuration: Configuration
|
||||
): void {
|
||||
const exportName = 'state.json';
|
||||
const exportName = 'state';
|
||||
const spaces = import.meta.env.DEV ? 4 : 0;
|
||||
const editorState: IEditorState = {
|
||||
history,
|
||||
historyCurrentStep,
|
||||
configuration
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
if (window.Worker) {
|
||||
// use webworker for the stringify to avoid freezing
|
||||
const myWorker = new Worker();
|
||||
myWorker.postMessage({ editorState, spaces });
|
||||
myWorker.onmessage = (event) => {
|
||||
const data = event.data;
|
||||
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(data)}`;
|
||||
createDownloadNode(exportName, dataStr);
|
||||
myWorker.terminate();
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const data = JSON.stringify(editorState, getCircularReplacer(), spaces);
|
||||
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(data)}`;
|
||||
createDownloadNode(exportName, dataStr);
|
||||
const downloadAnchorNode = document.createElement('a');
|
||||
downloadAnchorNode.setAttribute('href', dataStr);
|
||||
downloadAnchorNode.setAttribute('download', `${exportName}.json`);
|
||||
document.body.appendChild(downloadAnchorNode); // required for firefox
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
}
|
||||
|
||||
export function SaveEditorAsSVG(): void {
|
||||
|
@ -43,14 +32,10 @@ export function SaveEditorAsSVG(): void {
|
|||
const preface = '<?xml version="1.0" standalone="no"?>\r\n';
|
||||
const svgBlob = new Blob([preface, svg.outerHTML], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const svgUrl = URL.createObjectURL(svgBlob);
|
||||
createDownloadNode('state.svg', svgUrl);
|
||||
}
|
||||
|
||||
function createDownloadNode(filename: string, datastring: string) {
|
||||
const downloadAnchorNode = document.createElement('a');
|
||||
downloadAnchorNode.href = datastring;
|
||||
downloadAnchorNode.download = filename;
|
||||
document.body.appendChild(downloadAnchorNode); // required for firefox
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = svgUrl;
|
||||
downloadLink.download = 'newesttree.svg';
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
document.body.removeChild(downloadLink);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||
import { HistoryState } from '../../Interfaces/HistoryState';
|
||||
|
||||
export function onKeyDown(
|
||||
event: KeyboardEvent,
|
||||
history: IHistoryState[],
|
||||
history: HistoryState[],
|
||||
historyCurrentStep: number,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||
): void {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest';
|
|||
import * as React from 'react';
|
||||
import { fireEvent, render, screen } from '../../utils/test-utils';
|
||||
import { ElementsSidebar } from './ElementsSidebar';
|
||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { IContainerModel } from '../../Interfaces/ContainerModel';
|
||||
|
||||
describe.concurrent('Elements sidebar', () => {
|
||||
it('With a MainContainer', () => {
|
||||
|
@ -17,8 +17,7 @@ describe.concurrent('Elements sidebar', () => {
|
|||
y: 0,
|
||||
width: 2000,
|
||||
height: 100,
|
||||
isRigidBody: false,
|
||||
isAnchor: false
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
}}
|
||||
|
@ -26,7 +25,6 @@ describe.concurrent('Elements sidebar', () => {
|
|||
isHistoryOpen={false}
|
||||
SelectedContainer={null}
|
||||
OnPropertyChange={() => {}}
|
||||
OnPropertiesSubmit={() => {}}
|
||||
SelectContainer={() => {}}
|
||||
DeleteContainer={() => {}}
|
||||
AddContainer={() => {}}
|
||||
|
@ -48,8 +46,7 @@ describe.concurrent('Elements sidebar', () => {
|
|||
y: 0,
|
||||
width: 2000,
|
||||
height: 100,
|
||||
isRigidBody: false,
|
||||
isAnchor: false
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
};
|
||||
|
@ -60,7 +57,6 @@ describe.concurrent('Elements sidebar', () => {
|
|||
isHistoryOpen={false}
|
||||
SelectedContainer={MainContainer}
|
||||
OnPropertyChange={() => {}}
|
||||
OnPropertiesSubmit={() => {}}
|
||||
SelectContainer={() => {}}
|
||||
DeleteContainer={() => {}}
|
||||
AddContainer={() => {}}
|
||||
|
@ -74,12 +70,12 @@ describe.concurrent('Elements sidebar', () => {
|
|||
expect(screen.queryByText('y')).toBeDefined();
|
||||
expect(screen.queryByText('width')).toBeDefined();
|
||||
expect(screen.queryByText('height')).toBeDefined();
|
||||
const propertyId = container.querySelector('#id');
|
||||
const propertyParentId = container.querySelector('#parentId');
|
||||
const propertyX = container.querySelector('#x');
|
||||
const propertyY = container.querySelector('#y');
|
||||
const propertyWidth = container.querySelector('#width');
|
||||
const propertyHeight = container.querySelector('#height');
|
||||
const propertyId = container.querySelector('#property-id');
|
||||
const propertyParentId = container.querySelector('#property-parentId');
|
||||
const propertyX = container.querySelector('#property-x');
|
||||
const propertyY = container.querySelector('#property-y');
|
||||
const propertyWidth = container.querySelector('#property-width');
|
||||
const propertyHeight = container.querySelector('#property-height');
|
||||
expect((propertyId as HTMLInputElement).value).toBe(MainContainer.properties.id.toString());
|
||||
expect(propertyParentId).toBeDefined();
|
||||
expect((propertyParentId as HTMLInputElement).value).toBe('');
|
||||
|
@ -105,8 +101,7 @@ describe.concurrent('Elements sidebar', () => {
|
|||
y: 0,
|
||||
width: 2000,
|
||||
height: 100,
|
||||
isRigidBody: false,
|
||||
isAnchor: false
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
};
|
||||
|
@ -122,8 +117,7 @@ describe.concurrent('Elements sidebar', () => {
|
|||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
isRigidBody: false,
|
||||
isAnchor: false
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
}
|
||||
|
@ -140,8 +134,7 @@ describe.concurrent('Elements sidebar', () => {
|
|||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
isRigidBody: false,
|
||||
isAnchor: false
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
}
|
||||
|
@ -153,7 +146,6 @@ describe.concurrent('Elements sidebar', () => {
|
|||
isHistoryOpen={false}
|
||||
SelectedContainer={MainContainer}
|
||||
OnPropertyChange={() => {}}
|
||||
OnPropertiesSubmit={() => {}}
|
||||
SelectContainer={() => {}}
|
||||
DeleteContainer={() => {}}
|
||||
AddContainer={() => {}}
|
||||
|
@ -178,8 +170,7 @@ describe.concurrent('Elements sidebar', () => {
|
|||
y: 0,
|
||||
width: 2000,
|
||||
height: 100,
|
||||
isRigidBody: false,
|
||||
isAnchor: false
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
};
|
||||
|
@ -194,8 +185,7 @@ describe.concurrent('Elements sidebar', () => {
|
|||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
isRigidBody: false,
|
||||
isAnchor: false
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
};
|
||||
|
@ -212,7 +202,6 @@ describe.concurrent('Elements sidebar', () => {
|
|||
isHistoryOpen={false}
|
||||
SelectedContainer={SelectedContainer}
|
||||
OnPropertyChange={() => {}}
|
||||
OnPropertiesSubmit={() => {}}
|
||||
SelectContainer={selectContainer}
|
||||
DeleteContainer={() => {}}
|
||||
AddContainer={() => {}}
|
||||
|
@ -223,8 +212,8 @@ describe.concurrent('Elements sidebar', () => {
|
|||
expect(screen.getByText(/main/i));
|
||||
const child1 = screen.getByText(/child-1/i);
|
||||
expect(child1);
|
||||
const propertyId = container.querySelector('#id');
|
||||
const propertyParentId = container.querySelector('#parentId');
|
||||
const propertyId = container.querySelector('#property-id');
|
||||
const propertyParentId = container.querySelector('#property-parentId');
|
||||
expect((propertyId as HTMLInputElement).value).toBe(MainContainer.properties.id.toString());
|
||||
expect((propertyParentId as HTMLInputElement).value).toBe('');
|
||||
|
||||
|
@ -236,7 +225,6 @@ describe.concurrent('Elements sidebar', () => {
|
|||
isHistoryOpen={false}
|
||||
SelectedContainer={SelectedContainer}
|
||||
OnPropertyChange={() => {}}
|
||||
OnPropertiesSubmit={() => {}}
|
||||
SelectContainer={selectContainer}
|
||||
DeleteContainer={() => {}}
|
||||
AddContainer={() => {}}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import * as React from 'react';
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Properties } from '../Properties/Properties';
|
||||
import ContainerProperties from '../../Interfaces/IProperties';
|
||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { IContainerModel } from '../../Interfaces/ContainerModel';
|
||||
import { getDepth, MakeIterator } from '../../utils/itertools';
|
||||
import { Menu } from '../Menu/Menu';
|
||||
import { MenuItem } from '../Menu/MenuItem';
|
||||
import { handleDragLeave, handleDragOver, handleLeftClick, handleOnDrop, handleRightClick } from './MouseEventHandlers';
|
||||
import { IPoint } from '../../Interfaces/IPoint';
|
||||
import { Point } from '../../Interfaces/Point';
|
||||
|
||||
interface IElementsSidebarProps {
|
||||
MainContainer: IContainerModel
|
||||
|
@ -15,17 +14,55 @@ interface IElementsSidebarProps {
|
|||
isHistoryOpen: boolean
|
||||
SelectedContainer: IContainerModel | null
|
||||
OnPropertyChange: (key: string, value: string | number | boolean) => void
|
||||
OnPropertiesSubmit: (event: React.FormEvent<HTMLFormElement>, properties: ContainerProperties) => void
|
||||
SelectContainer: (container: IContainerModel) => void
|
||||
DeleteContainer: (containerid: string) => void
|
||||
AddContainer: (index: number, type: string, parent: string) => void
|
||||
}
|
||||
|
||||
function createRows(
|
||||
container: IContainerModel,
|
||||
props: IElementsSidebarProps,
|
||||
containerRows: React.ReactNode[]
|
||||
): void {
|
||||
const depth: number = getDepth(container);
|
||||
const key = container.properties.id.toString();
|
||||
const text = '|\t'.repeat(depth) + key;
|
||||
const selectedClass: string = props.SelectedContainer !== undefined &&
|
||||
props.SelectedContainer !== null &&
|
||||
props.SelectedContainer.properties.id === container.properties.id
|
||||
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
|
||||
: 'bg-slate-300/60 hover:bg-slate-300';
|
||||
|
||||
containerRows.push(
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 1.2 }}
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{
|
||||
duration: 0.150
|
||||
}}
|
||||
className={
|
||||
`w-full border-blue-500 elements-sidebar-row whitespace-pre
|
||||
text-left text-sm font-medium transition-all ${selectedClass}`
|
||||
}
|
||||
id={key}
|
||||
key={key}
|
||||
onDrop={(event) => handleOnDrop(event, props.MainContainer, props.AddContainer)}
|
||||
onDragOver={(event) => handleDragOver(event, props.MainContainer)}
|
||||
onDragLeave={(event) => handleDragLeave(event)}
|
||||
onClick={() => props.SelectContainer(container)}
|
||||
>
|
||||
{ text }
|
||||
</motion.button>
|
||||
);
|
||||
};
|
||||
|
||||
export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElementsSidebarProps): JSX.Element => {
|
||||
// States
|
||||
const [isContextMenuOpen, setIsContextMenuOpen] = React.useState<boolean>(false);
|
||||
const [onClickContainerId, setOnClickContainerId] = React.useState<string>('');
|
||||
const [contextMenuPosition, setContextMenuPosition] = React.useState<IPoint>({
|
||||
const [contextMenuPosition, setContextMenuPosition] = React.useState<Point>({
|
||||
x: 0,
|
||||
y: 0
|
||||
});
|
||||
|
@ -68,7 +105,7 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
|||
onLeftClick
|
||||
);
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Render
|
||||
let isOpenClasses = '-right-64';
|
||||
|
@ -78,55 +115,24 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
|||
: 'right-0';
|
||||
}
|
||||
|
||||
const containerRows: React.ReactNode[] = [];
|
||||
|
||||
const it = MakeIterator(props.MainContainer);
|
||||
const containers = [...it];
|
||||
const Row = ({ index, style }: {index: number, style: React.CSSProperties}): JSX.Element => {
|
||||
const container = containers[index];
|
||||
const depth: number = getDepth(container);
|
||||
const key = container.properties.id.toString();
|
||||
const text = '|\t'.repeat(depth) + key;
|
||||
const selectedClass: string = props.SelectedContainer !== undefined &&
|
||||
props.SelectedContainer !== null &&
|
||||
props.SelectedContainer.properties.id === container.properties.id
|
||||
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
|
||||
: 'bg-slate-300/60 hover:bg-slate-300';
|
||||
|
||||
return (
|
||||
<button
|
||||
className={
|
||||
`w-full border-blue-500 elements-sidebar-row whitespace-pre
|
||||
text-left text-sm font-medium transition-all ${selectedClass}`
|
||||
}
|
||||
id={key}
|
||||
key={key}
|
||||
style={style}
|
||||
onDrop={(event) => handleOnDrop(event, props.MainContainer, props.AddContainer)}
|
||||
onDragOver={(event) => handleDragOver(event, props.MainContainer)}
|
||||
onDragLeave={(event) => handleDragLeave(event)}
|
||||
onClick={() => props.SelectContainer(container)}
|
||||
>
|
||||
{ text }
|
||||
</button>
|
||||
for (const container of it) {
|
||||
createRows(
|
||||
container,
|
||||
props,
|
||||
containerRows
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const ROW_HEIGHT = 35;
|
||||
const NUMBERS_OF_ROWS = 10;
|
||||
return (
|
||||
<div className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
|
||||
<div className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-screen w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
|
||||
<div className='bg-slate-100 font-bold sidebar-title'>
|
||||
Elements
|
||||
</div>
|
||||
<div ref={elementRef} className='h-96 text-gray-800'>
|
||||
<List
|
||||
className='List'
|
||||
itemCount={containers.length}
|
||||
itemSize={35}
|
||||
height={384}
|
||||
width={256}
|
||||
>
|
||||
{ Row }
|
||||
</List>
|
||||
<div ref={elementRef} className='overflow-y-auto overflow-x-hidden text-gray-800 flex-grow'>
|
||||
{ containerRows }
|
||||
</div>
|
||||
<Menu
|
||||
className='transition-opacity rounded bg-slate-200 py-1 drop-shadow-xl'
|
||||
|
@ -139,11 +145,7 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
|||
props.DeleteContainer(onClickContainerId);
|
||||
}} />
|
||||
</Menu>
|
||||
<Properties
|
||||
properties={props.SelectedContainer?.properties}
|
||||
onChange={props.OnPropertyChange}
|
||||
onSubmit={props.OnPropertiesSubmit}
|
||||
/>
|
||||
<Properties properties={props.SelectedContainer?.properties} onChange={props.OnPropertyChange}></Properties>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { IPoint } from '../../Interfaces/IPoint';
|
||||
import { IContainerModel } from '../../Interfaces/ContainerModel';
|
||||
import { Point } from '../../Interfaces/Point';
|
||||
import { findContainerById } from '../../utils/itertools';
|
||||
|
||||
export function handleRightClick(
|
||||
event: MouseEvent,
|
||||
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>,
|
||||
setContextMenuPosition: React.Dispatch<React.SetStateAction<IPoint>>
|
||||
setContextMenuPosition: React.Dispatch<React.SetStateAction<Point>>
|
||||
): void {
|
||||
event.preventDefault();
|
||||
|
||||
|
@ -16,7 +16,7 @@ export function handleRightClick(
|
|||
return;
|
||||
}
|
||||
|
||||
const contextMenuPosition: IPoint = { x: event.pageX, y: event.pageY };
|
||||
const contextMenuPosition: Point = { x: event.pageX, y: event.pageY };
|
||||
setIsContextMenuOpen(true);
|
||||
setOnClickContainerId(event.target.id);
|
||||
setContextMenuPosition(contextMenuPosition);
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import * as React from 'react';
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||
import { HistoryState } from "../../Interfaces/HistoryState";
|
||||
|
||||
interface IHistoryProps {
|
||||
history: IHistoryState[]
|
||||
history: HistoryState[]
|
||||
historyCurrentStep: number
|
||||
isOpen: boolean
|
||||
jumpTo: (move: number) => void
|
||||
|
@ -11,45 +10,47 @@ interface IHistoryProps {
|
|||
|
||||
export const History: React.FC<IHistoryProps> = (props: IHistoryProps) => {
|
||||
const isOpenClasses = props.isOpen ? 'right-0' : '-right-64';
|
||||
const Row = ({ index, style }: {index: number, style: React.CSSProperties}): JSX.Element => {
|
||||
const reversedIndex = (props.history.length - 1) - index;
|
||||
const step = props.history[reversedIndex];
|
||||
const desc = step.LastAction;
|
||||
|
||||
const selectedClass = reversedIndex === props.historyCurrentStep
|
||||
const states = props.history.map((step, move) => {
|
||||
const desc = move > 0
|
||||
? `Go to modification n°${move}`
|
||||
: 'Go to the beginning';
|
||||
|
||||
const isCurrent = move === props.historyCurrentStep;
|
||||
|
||||
const selectedClass = isCurrent
|
||||
? 'bg-blue-500 hover:bg-blue-600'
|
||||
: 'bg-slate-500 hover:bg-slate-700';
|
||||
|
||||
const isCurrentText = isCurrent
|
||||
? ' (current)'
|
||||
: '';
|
||||
return (
|
||||
|
||||
<button
|
||||
key={reversedIndex}
|
||||
style={style}
|
||||
onClick={() => props.jumpTo(reversedIndex)}
|
||||
title={step.LastAction}
|
||||
key={move}
|
||||
onClick={() => props.jumpTo(move)}
|
||||
className={
|
||||
`w-full elements-sidebar-row whitespace-pre overflow-hidden
|
||||
`w-full elements-sidebar-row whitespace-pre
|
||||
text-left text-sm font-medium transition-all ${selectedClass}`
|
||||
}
|
||||
>
|
||||
{desc}
|
||||
{desc}{isCurrentText}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
// recent first
|
||||
states.reverse();
|
||||
|
||||
return (
|
||||
<div className={`fixed flex flex-col bg-slate-300 text-white transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
|
||||
<div className={`fixed flex flex-col bg-slate-300 text-white transition-all h-screen w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
|
||||
<div className='bg-slate-600 font-bold sidebar-title'>
|
||||
Timeline
|
||||
</div>
|
||||
<List
|
||||
className='List overflow-x-hidden'
|
||||
itemCount={props.history.length}
|
||||
itemSize={35}
|
||||
height={window.innerHeight}
|
||||
width={256}
|
||||
>
|
||||
{ Row }
|
||||
</List>
|
||||
<div className='overflow-y-auto overflow-x-hidden text-slate-300 flex-grow divide-y divide-solid divide-slate-600'>
|
||||
{ states }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -38,8 +38,13 @@ export const MainMenu: React.FC<IMainMenuProps> = (props) => {
|
|||
</form>
|
||||
<button
|
||||
onClick={() => setWindowState(WindowState.MAIN)}
|
||||
className='normal-btn block
|
||||
mt-8 '
|
||||
className='block text-sm
|
||||
mt-8 py-2 px-4
|
||||
rounded-full border-0
|
||||
font-semibold
|
||||
transition-all
|
||||
bg-blue-100 text-blue-700
|
||||
hover:bg-blue-200'
|
||||
>
|
||||
Go back
|
||||
</button>
|
||||
|
|
|
@ -8,7 +8,6 @@ describe.concurrent('Properties', () => {
|
|||
render(<Properties
|
||||
properties={undefined}
|
||||
onChange={() => {}}
|
||||
onSubmit={() => {}}
|
||||
/>);
|
||||
|
||||
expect(screen.queryByText('id')).toBeNull();
|
||||
|
@ -17,14 +16,13 @@ describe.concurrent('Properties', () => {
|
|||
expect(screen.queryByText('y')).toBeNull();
|
||||
});
|
||||
|
||||
it('Some properties, change values with dynamic input', () => {
|
||||
it('Some properties', () => {
|
||||
const prop = {
|
||||
id: 'stuff',
|
||||
parentId: 'parentId',
|
||||
x: 1,
|
||||
y: 1,
|
||||
isRigidBody: false,
|
||||
isAnchor: false
|
||||
isRigidBody: false
|
||||
};
|
||||
|
||||
const handleChange = vi.fn((key, value) => {
|
||||
|
@ -34,7 +32,6 @@ describe.concurrent('Properties', () => {
|
|||
const { container, rerender } = render(<Properties
|
||||
properties={prop}
|
||||
onChange={handleChange}
|
||||
onSubmit={() => {}}
|
||||
/>);
|
||||
|
||||
expect(screen.queryByText('id')).toBeDefined();
|
||||
|
@ -42,10 +39,10 @@ describe.concurrent('Properties', () => {
|
|||
expect(screen.queryByText('x')).toBeDefined();
|
||||
expect(screen.queryByText('y')).toBeDefined();
|
||||
|
||||
let propertyId = container.querySelector('#id');
|
||||
let propertyParentId = container.querySelector('#parentId');
|
||||
let propertyX = container.querySelector('#x');
|
||||
let propertyY = container.querySelector('#y');
|
||||
let propertyId = container.querySelector('#property-id');
|
||||
let propertyParentId = container.querySelector('#property-parentId');
|
||||
let propertyX = container.querySelector('#property-x');
|
||||
let propertyY = container.querySelector('#property-y');
|
||||
expect(propertyId).toBeDefined();
|
||||
expect((propertyId as HTMLInputElement).value).toBe('stuff');
|
||||
expect(propertyParentId).toBeDefined();
|
||||
|
@ -68,13 +65,12 @@ describe.concurrent('Properties', () => {
|
|||
rerender(<Properties
|
||||
properties={Object.assign({}, prop)}
|
||||
onChange={handleChange}
|
||||
onSubmit={() => {}}
|
||||
/>);
|
||||
|
||||
propertyId = container.querySelector('#id');
|
||||
propertyParentId = container.querySelector('#parentId');
|
||||
propertyX = container.querySelector('#x');
|
||||
propertyY = container.querySelector('#y');
|
||||
propertyId = container.querySelector('#property-id');
|
||||
propertyParentId = container.querySelector('#property-parentId');
|
||||
propertyX = container.querySelector('#property-x');
|
||||
propertyY = container.querySelector('#property-y');
|
||||
expect(propertyId).toBeDefined();
|
||||
expect((propertyId as HTMLInputElement).value).toBe('stuffed');
|
||||
expect(propertyParentId).toBeDefined();
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
import React, { useState } from 'react';
|
||||
import ContainerProperties from '../../Interfaces/IProperties';
|
||||
import { ToggleButton } from '../ToggleButton/ToggleButton';
|
||||
import * as React from 'react';
|
||||
import ContainerProperties from '../../Interfaces/Properties';
|
||||
import { INPUT_TYPES } from './PropertiesInputTypes';
|
||||
|
||||
interface IPropertiesProps {
|
||||
properties?: ContainerProperties
|
||||
onChange: (key: string, value: string | number | boolean) => void
|
||||
onSubmit: (event: React.FormEvent<HTMLFormElement>, properties: ContainerProperties) => void
|
||||
}
|
||||
|
||||
export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps) => {
|
||||
const [isDynamicInput, setIsDynamicInput] = useState<boolean>(true);
|
||||
|
||||
if (props.properties === undefined) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
@ -19,33 +15,11 @@ export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps)
|
|||
const groupInput: React.ReactNode[] = [];
|
||||
Object
|
||||
.entries(props.properties)
|
||||
.forEach((pair) => handleProperties(pair, groupInput, isDynamicInput, props.onChange));
|
||||
|
||||
const form = isDynamicInput
|
||||
? <div className='grid grid-cols-2 gap-4'>
|
||||
{ groupInput }
|
||||
</div>
|
||||
: <form
|
||||
key={props.properties.id}
|
||||
onSubmit={(event) => props.onSubmit(event, props.properties as ContainerProperties)}
|
||||
>
|
||||
<input type='submit' className='normal-btn block mx-auto mb-4 border-2 border-blue-400 cursor-pointer' value='Submit'/>
|
||||
<div className='grid grid-cols-2 gap-y-4'>
|
||||
{ groupInput }
|
||||
</div>
|
||||
</form>
|
||||
;
|
||||
.forEach((pair) => handleProperties(pair, groupInput, props.onChange));
|
||||
|
||||
return (
|
||||
<div className='h-3/5 p-3 bg-slate-200 overflow-y-auto'>
|
||||
<ToggleButton
|
||||
id='isDynamic'
|
||||
text='Dynamic update'
|
||||
title='Enable dynamic svg update'
|
||||
checked={isDynamicInput}
|
||||
onChange={() => setIsDynamicInput(!isDynamicInput)}
|
||||
/>
|
||||
{ form }
|
||||
<div className='p-3 bg-slate-200 h-3/5 overflow-y-auto'>
|
||||
{ groupInput }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -53,7 +27,6 @@ export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps)
|
|||
const handleProperties = (
|
||||
[key, value]: [string, string | number],
|
||||
groupInput: React.ReactNode[],
|
||||
isDynamicInput: boolean,
|
||||
onChange: (key: string, value: string | number | boolean) => void
|
||||
): void => {
|
||||
const id = `property-${key}`;
|
||||
|
@ -69,48 +42,31 @@ const handleProperties = (
|
|||
type = INPUT_TYPES[key];
|
||||
}
|
||||
|
||||
const className = `
|
||||
w-full
|
||||
text-xs font-medium transition-all text-gray-800 mt-1 px-3 py-2
|
||||
bg-white border-2 border-white rounded-lg placeholder-gray-800
|
||||
focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500
|
||||
disabled:bg-slate-300 disabled:text-gray-500 disabled:border-slate-300 disabled:shadow-none`;
|
||||
const isDisabled = ['id', 'parentId'].includes(key);
|
||||
const input = isDynamicInput
|
||||
? <input
|
||||
key={key}
|
||||
id={key}
|
||||
className={className}
|
||||
type={type}
|
||||
value={value}
|
||||
checked={checked}
|
||||
onChange={(event) => {
|
||||
if (type === 'checkbox') {
|
||||
onChange(key, event.target.checked);
|
||||
return;
|
||||
}
|
||||
onChange(key, event.target.value);
|
||||
}}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
: <input
|
||||
key={key}
|
||||
id={key}
|
||||
className={className}
|
||||
type={type}
|
||||
defaultValue={value}
|
||||
defaultChecked={checked}
|
||||
disabled={isDisabled}
|
||||
/>;
|
||||
///
|
||||
|
||||
groupInput.push(
|
||||
<label
|
||||
key={id}
|
||||
className='mt-4 text-xs font-medium text-gray-800'
|
||||
htmlFor={key}
|
||||
>
|
||||
{key}
|
||||
</label>
|
||||
<div key={id} className='mt-4'>
|
||||
<label className='text-sm font-medium text-gray-800' htmlFor={id}>{key}</label>
|
||||
<input
|
||||
className='text-base font-medium transition-all text-gray-800 mt-1 block w-full px-3 py-2
|
||||
bg-white border-2 border-white rounded-lg placeholder-gray-800
|
||||
focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500
|
||||
disabled:bg-slate-300 disabled:text-gray-500 disabled:border-slate-300 disabled:shadow-none
|
||||
'
|
||||
type={type}
|
||||
id={id}
|
||||
value={value}
|
||||
checked={checked}
|
||||
onChange={(event) => {
|
||||
if (type === 'checkbox') {
|
||||
onChange(key, event.target.checked);
|
||||
return;
|
||||
}
|
||||
onChange(key, event.target.value);
|
||||
}}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
groupInput.push(input);
|
||||
};
|
||||
|
|
|
@ -3,6 +3,5 @@ export const INPUT_TYPES: Record<string, string> = {
|
|||
y: 'number',
|
||||
width: 'number',
|
||||
height: 'number',
|
||||
isRigidBody: 'checkbox',
|
||||
isAnchor: 'checkbox'
|
||||
isRigidBody: 'checkbox'
|
||||
};
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import * as React from 'react';
|
||||
import { XPositionReference } from '../../../Enums/XPositionReference';
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { IContainerModel } from '../../../Interfaces/ContainerModel';
|
||||
import { getDepth } from '../../../utils/itertools';
|
||||
import { Dimension } from './Dimension';
|
||||
|
||||
interface IContainerProps {
|
||||
export interface IContainerProps {
|
||||
model: IContainerModel
|
||||
}
|
||||
|
||||
|
@ -18,14 +17,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
|||
const containersElements = props.model.children.map(child => <Container key={`container-${child.properties.id}`} model={child} />);
|
||||
const xText = Number(props.model.properties.width) / 2;
|
||||
const yText = Number(props.model.properties.height) / 2;
|
||||
|
||||
const [transformedX, transformedY] = transformPosition(
|
||||
Number(props.model.properties.x),
|
||||
Number(props.model.properties.y),
|
||||
Number(props.model.properties.width),
|
||||
props.model.properties.XPositionReference
|
||||
);
|
||||
const transform = `translate(${transformedX}, ${transformedY})`;
|
||||
const transform = `translate(${Number(props.model.properties.x)}, ${Number(props.model.properties.y)})`;
|
||||
|
||||
// g style
|
||||
const defaultStyle: React.CSSProperties = {
|
||||
|
@ -62,8 +54,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
|||
id={id}
|
||||
xStart={xStart}
|
||||
xEnd={xEnd}
|
||||
yStart={y}
|
||||
yEnd={y}
|
||||
y={y}
|
||||
strokeWidth={strokeWidth}
|
||||
text={text}
|
||||
/>
|
||||
|
@ -83,13 +74,3 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
|||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
function transformPosition(x: number, y: number, width: number, xPositionReference = XPositionReference.Left): [number, number] {
|
||||
let transformedX = x;
|
||||
if (xPositionReference === XPositionReference.Center) {
|
||||
transformedX -= width / 2;
|
||||
} else if (xPositionReference === XPositionReference.Right) {
|
||||
transformedX -= width;
|
||||
}
|
||||
return [transformedX, y];
|
||||
}
|
||||
|
|
|
@ -1,83 +1,47 @@
|
|||
import * as React from 'react';
|
||||
import { NOTCHES_LENGTH } from '../../../utils/default';
|
||||
|
||||
interface IDimensionProps {
|
||||
id: string
|
||||
xStart: number
|
||||
yStart: number
|
||||
xEnd: number
|
||||
yEnd: number
|
||||
y: number
|
||||
text: string
|
||||
strokeWidth: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 2D Parametric function. Returns a new coordinate from the origin coordinate
|
||||
* See for more details https://en.wikipedia.org/wiki/Parametric_equation.
|
||||
* TL;DR a parametric function is a function with a parameter
|
||||
* @param x0 Origin coordinate
|
||||
* @param t The parameter
|
||||
* @param vx Transform vector
|
||||
* @returns Returns a new coordinate from the origin coordinate
|
||||
*/
|
||||
const applyParametric = (x0: number, t: number, vx: number): number => x0 + t * vx;
|
||||
|
||||
export const Dimension: React.FC<IDimensionProps> = (props: IDimensionProps) => {
|
||||
const style: React.CSSProperties = {
|
||||
stroke: 'black'
|
||||
};
|
||||
|
||||
/// We need to find the points of the notches
|
||||
// Get the vector of the line
|
||||
const [deltaX, deltaY] = [(props.xEnd - props.xStart), (props.yEnd - props.yStart)];
|
||||
|
||||
// Get the unit vector
|
||||
const norm = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
const [unitX, unitY] = [deltaX / norm, deltaY / norm];
|
||||
|
||||
// Get the perpandicular vector
|
||||
const [perpVecX, perpVecY] = [unitY, -unitX];
|
||||
|
||||
// Use the parametric function to get the coordinates (x = x0 + t * v.x)
|
||||
const startTopX = applyParametric(props.xStart, NOTCHES_LENGTH, perpVecX);
|
||||
const startTopY = applyParametric(props.yStart, NOTCHES_LENGTH, perpVecY);
|
||||
const startBottomX = applyParametric(props.xStart, -NOTCHES_LENGTH, perpVecX);
|
||||
const startBottomY = applyParametric(props.yStart, -NOTCHES_LENGTH, perpVecY);
|
||||
|
||||
const endTopX = applyParametric(props.xEnd, NOTCHES_LENGTH, perpVecX);
|
||||
const endTopY = applyParametric(props.yEnd, NOTCHES_LENGTH, perpVecY);
|
||||
const endBottomX = applyParametric(props.xEnd, -NOTCHES_LENGTH, perpVecX);
|
||||
const endBottomY = applyParametric(props.yEnd, -NOTCHES_LENGTH, perpVecY);
|
||||
|
||||
return (
|
||||
<g key={props.id}>
|
||||
<line
|
||||
x1={startTopX}
|
||||
y1={startTopY}
|
||||
x2={startBottomX}
|
||||
y2={startBottomY}
|
||||
x1={props.xStart}
|
||||
y1={props.y - 4 * props.strokeWidth}
|
||||
x2={props.xStart}
|
||||
y2={props.y + 4 * props.strokeWidth}
|
||||
strokeWidth={props.strokeWidth}
|
||||
style={style}
|
||||
/>
|
||||
<line
|
||||
x1={props.xStart}
|
||||
y1={props.yStart}
|
||||
y1={props.y}
|
||||
x2={props.xEnd}
|
||||
y2={props.yEnd}
|
||||
y2={props.y}
|
||||
strokeWidth={props.strokeWidth}
|
||||
style={style}
|
||||
/>
|
||||
<line
|
||||
x1={endTopX}
|
||||
y1={endTopY}
|
||||
x2={endBottomX}
|
||||
y2={endBottomY}
|
||||
x1={props.xEnd}
|
||||
y1={props.y - 4 * props.strokeWidth}
|
||||
x2={props.xEnd}
|
||||
y2={props.y + 4 * props.strokeWidth}
|
||||
strokeWidth={props.strokeWidth}
|
||||
style={style}
|
||||
/>
|
||||
<text
|
||||
x={(props.xStart + props.xEnd) / 2}
|
||||
y={props.yStart}
|
||||
y={props.y}
|
||||
>
|
||||
{props.text}
|
||||
</text>
|
||||
|
|
66
src/Components/SVG/Elements/DimensionLayer.tsx
Normal file
66
src/Components/SVG/Elements/DimensionLayer.tsx
Normal file
|
@ -0,0 +1,66 @@
|
|||
import * as React from 'react';
|
||||
import { ContainerModel } from '../../../Interfaces/ContainerModel';
|
||||
import { getDepth, MakeIterator } from '../../../utils/itertools';
|
||||
import { Dimension } from './Dimension';
|
||||
|
||||
interface IDimensionLayerProps {
|
||||
isHidden: boolean
|
||||
roots: ContainerModel | ContainerModel[] | null
|
||||
}
|
||||
|
||||
const GAP: number = 50;
|
||||
|
||||
const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
|
||||
const it = MakeIterator(root);
|
||||
const dimensions: React.ReactNode[] = [];
|
||||
for (const container of it) {
|
||||
// WARN: this might be dangerous later when using other units/rules
|
||||
const width = Number(container.properties.width);
|
||||
|
||||
const id = `dim-${container.properties.id}`;
|
||||
const xStart: number = container.properties.x;
|
||||
const xEnd = xStart + width;
|
||||
const y = -(GAP * (getDepth(container) + 1));
|
||||
const strokeWidth = 1;
|
||||
const text = width.toString();
|
||||
dimensions.push(
|
||||
<Dimension
|
||||
id={id}
|
||||
xStart={xStart}
|
||||
xEnd={xEnd}
|
||||
y={y}
|
||||
strokeWidth={strokeWidth}
|
||||
text={text}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return dimensions;
|
||||
};
|
||||
|
||||
/**
|
||||
* A layer containing all dimension
|
||||
*
|
||||
* @deprecated In order to avoid adding complexity
|
||||
* with computing the position in a group hierarchy,
|
||||
* use Dimension directly inside the Container,
|
||||
* Currently it is glitched as
|
||||
* it does not take parents into account,
|
||||
* and will not work correctly
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const DimensionLayer: React.FC<IDimensionLayerProps> = (props: IDimensionLayerProps) => {
|
||||
let dimensions: React.ReactNode[] = [];
|
||||
if (Array.isArray(props.roots)) {
|
||||
props.roots.forEach(child => {
|
||||
dimensions.concat(getDimensionsNodes(child));
|
||||
});
|
||||
} else if (props.roots !== null) {
|
||||
dimensions = getDimensionsNodes(props.roots);
|
||||
}
|
||||
return (
|
||||
<g visibility={props.isHidden ? 'hidden' : 'visible'}>
|
||||
{ dimensions }
|
||||
</g>
|
||||
);
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { IContainerModel } from '../../../Interfaces/ContainerModel';
|
||||
import { getAbsolutePosition } from '../../../utils/itertools';
|
||||
|
||||
interface ISelectorProps {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import { UncontrolledReactSVGPanZoom } from 'react-svg-pan-zoom';
|
||||
import { Container } from './Elements/Container';
|
||||
import { ContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { ContainerModel } from '../../Interfaces/ContainerModel';
|
||||
import { Selector } from './Elements/Selector';
|
||||
import { BAR_WIDTH } from '../Bar/Bar';
|
||||
|
||||
|
@ -30,7 +30,7 @@ function resizeViewBox(
|
|||
|
||||
export const SVG: React.FC<ISVGProps> = (props: ISVGProps) => {
|
||||
const [viewer, setViewer] = React.useState<Viewer>({
|
||||
viewerWidth: window.innerWidth - BAR_WIDTH,
|
||||
viewerWidth: window.innerWidth,
|
||||
viewerHeight: window.innerHeight
|
||||
});
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as React from 'react';
|
||||
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
||||
import { AvailableContainer } from '../../Interfaces/AvailableContainer';
|
||||
import { truncateString } from '../../utils/stringtools';
|
||||
|
||||
interface ISidebarProps {
|
||||
componentOptions: IAvailableContainer[]
|
||||
componentOptions: AvailableContainer[]
|
||||
isOpen: boolean
|
||||
buttonOnClick: (type: string) => void
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export const Sidebar: React.FC<ISidebarProps> = (props: ISidebarProps) => {
|
|||
const isOpenClasses = props.isOpen ? 'left-16' : '-left-64';
|
||||
return (
|
||||
<div className={`fixed z-10 bg-slate-200
|
||||
text-gray-700 transition-all h-full w-64
|
||||
text-gray-700 transition-all h-screen w-64
|
||||
overflow-y-auto ${isOpenClasses}`}>
|
||||
<div className='bg-slate-100 sidebar-title'>
|
||||
Components
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
input:checked ~ .dot {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
input:checked ~ .line {
|
||||
background-color: #3B82F6;
|
||||
}
|
||||
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
import './ToggleButton.scss';
|
||||
|
||||
interface IToggleButtonProps {
|
||||
id: string
|
||||
text: string
|
||||
type?: TOGGLE_TYPE
|
||||
title: string
|
||||
checked: boolean
|
||||
onChange: React.ChangeEventHandler<HTMLInputElement>
|
||||
}
|
||||
|
||||
export enum TOGGLE_TYPE {
|
||||
MATERIAL,
|
||||
IOS
|
||||
}
|
||||
|
||||
export const ToggleButton: FC<IToggleButtonProps> = (props) => {
|
||||
const id = `toggle-${props.id}`;
|
||||
const type = props.type ?? TOGGLE_TYPE.MATERIAL;
|
||||
let classLine = 'line w-10 h-4 bg-gray-400 rounded-full shadow-inner';
|
||||
let classDot = 'dot absolute w-6 h-6 bg-white rounded-full shadow -left-1 -top-1 transition';
|
||||
if (type === TOGGLE_TYPE.IOS) {
|
||||
classLine = 'line block bg-gray-600 w-14 h-8 rounded-full';
|
||||
classDot = 'dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition';
|
||||
}
|
||||
|
||||
return (
|
||||
<div title={props.title}>
|
||||
<div className="flex items-center justify-center w-full mb-12">
|
||||
<label
|
||||
htmlFor={id}
|
||||
className="flex items-center cursor-pointer"
|
||||
>
|
||||
<div className="relative">
|
||||
<input
|
||||
id={id}
|
||||
type="checkbox"
|
||||
onChange={props.onChange}
|
||||
checked={props.checked}
|
||||
className="sr-only" />
|
||||
<div className={classLine}></div>
|
||||
<div className={classDot}></div>
|
||||
</div>
|
||||
<div className="ml-3 text-gray-700 font-medium">
|
||||
{ props.text }
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -2,23 +2,21 @@ import * as React from 'react';
|
|||
import { ElementsSidebar } from '../ElementsSidebar/ElementsSidebar';
|
||||
import { Sidebar } from '../Sidebar/Sidebar';
|
||||
import { History } from '../History/History';
|
||||
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
||||
import { ContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||
import { AvailableContainer } from '../../Interfaces/AvailableContainer';
|
||||
import { ContainerModel } from '../../Interfaces/ContainerModel';
|
||||
import { HistoryState } from '../../Interfaces/HistoryState';
|
||||
import { PhotographIcon, UploadIcon } from '@heroicons/react/outline';
|
||||
import { FloatingButton } from '../FloatingButton/FloatingButton';
|
||||
import { Bar } from '../Bar/Bar';
|
||||
import IProperties from '../../Interfaces/IProperties';
|
||||
|
||||
interface IUIProps {
|
||||
current: IHistoryState
|
||||
history: IHistoryState[]
|
||||
current: HistoryState
|
||||
history: HistoryState[]
|
||||
historyCurrentStep: number
|
||||
AvailableContainers: IAvailableContainer[]
|
||||
AvailableContainers: AvailableContainer[]
|
||||
SelectContainer: (container: ContainerModel) => void
|
||||
DeleteContainer: (containerId: string) => void
|
||||
OnPropertyChange: (key: string, value: string | number | boolean) => void
|
||||
OnPropertiesSubmit: (event: React.FormEvent<HTMLFormElement>, properties: IProperties) => void
|
||||
AddContainerToSelectedContainer: (type: string) => void
|
||||
AddContainer: (index: number, type: string, parentId: string) => void
|
||||
SaveEditorAsJSON: () => void
|
||||
|
@ -61,7 +59,6 @@ export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
|
|||
isOpen={isElementsSidebarOpen}
|
||||
isHistoryOpen={isHistoryOpen}
|
||||
OnPropertyChange={props.OnPropertyChange}
|
||||
OnPropertiesSubmit={props.OnPropertiesSubmit}
|
||||
SelectContainer={props.SelectContainer}
|
||||
DeleteContainer={props.DeleteContainer}
|
||||
AddContainer={props.AddContainer}
|
||||
|
|
4
src/Enums/AddingBehavior.ts
Normal file
4
src/Enums/AddingBehavior.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export enum AddingBehavior {
|
||||
InsertInto,
|
||||
Replace
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import { IEditorState } from '../Interfaces/IEditorState';
|
||||
import { IHistoryState } from '../Interfaces/IHistoryState';
|
||||
|
||||
const getEditorState = (editorState: IEditorState): void => {
|
||||
const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: editorState });
|
||||
document.dispatchEvent(customEvent);
|
||||
};
|
||||
|
||||
const getCurrentHistoryState = (editorState: IEditorState): void => {
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'getCurrentHistoryState',
|
||||
{ detail: editorState.history[editorState.historyCurrentStep] });
|
||||
document.dispatchEvent(customEvent);
|
||||
};
|
||||
|
||||
export interface IEditorEvent {
|
||||
name: string
|
||||
func: (editorState: IEditorState) => void
|
||||
}
|
||||
|
||||
const events: IEditorEvent[] = [
|
||||
{ name: 'getEditorState', func: getEditorState },
|
||||
{ name: 'getCurrentHistoryState', func: getCurrentHistoryState }
|
||||
];
|
||||
|
||||
export default events;
|
|
@ -1,11 +1,9 @@
|
|||
import React from 'react';
|
||||
import { XPositionReference } from '../Enums/XPositionReference';
|
||||
|
||||
/** Model of available container used in application configuration */
|
||||
export interface IAvailableContainer {
|
||||
export interface AvailableContainer {
|
||||
Type: string
|
||||
Width: number
|
||||
Height: number
|
||||
XPositionReference?: XPositionReference
|
||||
Style: React.CSSProperties
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import { XPositionReference } from '../Enums/XPositionReference';
|
||||
import { IImage } from './IImage';
|
||||
import { Image } from './Image';
|
||||
|
||||
/**
|
||||
* Model of available symbol to configure the application */
|
||||
export interface IAvailableSymbol {
|
||||
export interface AvailableSymbolModel {
|
||||
Name: string
|
||||
XPositionReference: XPositionReference
|
||||
Image: IImage
|
||||
Image: Image
|
||||
Width: number
|
||||
Height: number
|
||||
}
|
9
src/Interfaces/Configuration.ts
Normal file
9
src/Interfaces/Configuration.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { AvailableContainer } from './AvailableContainer';
|
||||
import { AvailableSymbolModel } from './AvailableSymbol';
|
||||
|
||||
/** Model of configuration for the application to configure it */
|
||||
export interface Configuration {
|
||||
AvailableContainers: AvailableContainer[]
|
||||
AvailableSymbols: AvailableSymbolModel[]
|
||||
MainContainer: AvailableContainer
|
||||
}
|
|
@ -1,21 +1,21 @@
|
|||
import IProperties from './IProperties';
|
||||
import Properties from './Properties';
|
||||
|
||||
export interface IContainerModel {
|
||||
children: IContainerModel[]
|
||||
parent: IContainerModel | null
|
||||
properties: IProperties
|
||||
properties: Properties
|
||||
userData: Record<string, string | number>
|
||||
}
|
||||
|
||||
export class ContainerModel implements IContainerModel {
|
||||
public children: IContainerModel[];
|
||||
public parent: IContainerModel | null;
|
||||
public properties: IProperties;
|
||||
public properties: Properties;
|
||||
public userData: Record<string, string | number>;
|
||||
|
||||
constructor(
|
||||
parent: IContainerModel | null,
|
||||
properties: IProperties,
|
||||
properties: Properties,
|
||||
children: IContainerModel[] = [],
|
||||
userData = {}) {
|
||||
this.parent = parent;
|
|
@ -1,6 +1,6 @@
|
|||
import { IContainerModel } from './IContainerModel';
|
||||
import { IContainerModel } from './ContainerModel';
|
||||
|
||||
export interface IHistoryState {
|
||||
export interface HistoryState {
|
||||
LastAction: string
|
||||
MainContainer: IContainerModel
|
||||
SelectedContainer: IContainerModel | null
|
|
@ -1,9 +0,0 @@
|
|||
import { IAvailableContainer } from './IAvailableContainer';
|
||||
import { IAvailableSymbol } from './IAvailableSymbol';
|
||||
|
||||
/** Model of configuration for the application to configure it */
|
||||
export interface IConfiguration {
|
||||
AvailableContainers: IAvailableContainer[]
|
||||
AvailableSymbols: IAvailableSymbol[]
|
||||
MainContainer: IAvailableContainer
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import { IConfiguration } from './IConfiguration';
|
||||
import { IHistoryState } from './IHistoryState';
|
||||
|
||||
export interface IEditorState {
|
||||
history: IHistoryState[]
|
||||
historyCurrentStep: number
|
||||
configuration: IConfiguration
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import { XPositionReference } from '../Enums/XPositionReference';
|
||||
|
||||
/**
|
||||
* Properties of a container
|
||||
* @property id id of the container
|
||||
* @property parentId id of the parent container
|
||||
* @property x horizontal offset of the container
|
||||
* @property y vertical offset of the container
|
||||
* @property isRigidBody if true apply rigid body behaviors
|
||||
* @property isAnchor if true apply anchor behaviors
|
||||
*/
|
||||
export default interface IProperties extends React.CSSProperties {
|
||||
id: string
|
||||
parentId: string | null
|
||||
x: number
|
||||
y: number
|
||||
isRigidBody: boolean
|
||||
isAnchor: boolean
|
||||
XPositionReference?: XPositionReference
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
/**
|
||||
* A SizePointer is a pointer in a 1 dimensional array of width/space
|
||||
* x being the address where the pointer is pointing
|
||||
* width being the overall (un)allocated space affected to the address
|
||||
*/
|
||||
export interface ISizePointer {
|
||||
x: number
|
||||
width: number
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/** Model of an image with multiple source */
|
||||
export interface IImage {
|
||||
export interface Image {
|
||||
Name: string
|
||||
Url: string
|
||||
Base64Image: string
|
|
@ -1,4 +1,4 @@
|
|||
export interface IPoint {
|
||||
export interface Point {
|
||||
x: number
|
||||
y: number
|
||||
}
|
9
src/Interfaces/Properties.ts
Normal file
9
src/Interfaces/Properties.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import * as React from 'react';
|
||||
|
||||
export default interface Properties extends React.CSSProperties {
|
||||
id: string
|
||||
parentId: string | null
|
||||
x: number
|
||||
y: number
|
||||
isRigidBody: boolean
|
||||
}
|
4
src/Interfaces/SizePointer.ts
Normal file
4
src/Interfaces/SizePointer.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export interface SizePointer {
|
||||
x: number
|
||||
width: number
|
||||
}
|
|
@ -23,16 +23,6 @@
|
|||
@apply transition-all bg-blue-100 hover:bg-blue-200 text-blue-700 text-lg font-semibold p-8 rounded-lg
|
||||
}
|
||||
|
||||
.normal-btn {
|
||||
@apply text-sm
|
||||
py-2 px-4
|
||||
rounded-full border-0
|
||||
font-semibold
|
||||
transition-all
|
||||
bg-blue-100 text-blue-700
|
||||
hover:bg-blue-200
|
||||
}
|
||||
|
||||
.floating-btn {
|
||||
@apply h-full w-full text-white align-middle items-center justify-center
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { IConfiguration } from '../Interfaces/IConfiguration';
|
||||
import IProperties from '../Interfaces/IProperties';
|
||||
import { Configuration } from '../Interfaces/Configuration';
|
||||
import Properties from '../Interfaces/Properties';
|
||||
|
||||
export const DEFAULT_CONFIG: IConfiguration = {
|
||||
export const DEFAULT_CONFIG: Configuration = {
|
||||
AvailableContainers: [
|
||||
{
|
||||
Type: 'Container',
|
||||
|
@ -25,19 +25,14 @@ export const DEFAULT_CONFIG: IConfiguration = {
|
|||
}
|
||||
};
|
||||
|
||||
export const DEFAULT_MAINCONTAINER_PROPS: IProperties = {
|
||||
export const DEFAULT_MAINCONTAINER_PROPS: Properties = {
|
||||
id: 'main',
|
||||
parentId: 'null',
|
||||
x: 0,
|
||||
y: 0,
|
||||
isRigidBody: false,
|
||||
width: DEFAULT_CONFIG.MainContainer.Width,
|
||||
height: DEFAULT_CONFIG.MainContainer.Height,
|
||||
isRigidBody: false,
|
||||
isAnchor: false,
|
||||
fillOpacity: 0,
|
||||
stroke: 'black'
|
||||
};
|
||||
|
||||
export const NOTCHES_LENGTH = 4;
|
||||
|
||||
export const MAX_HISTORY = 200;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IContainerModel } from '../Interfaces/IContainerModel';
|
||||
import { IContainerModel } from '../Interfaces/ContainerModel';
|
||||
|
||||
/**
|
||||
* Returns a Generator iterating of over the children depth-first
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { findContainerById, MakeIterator } from './itertools';
|
||||
import { IEditorState } from '../Interfaces/IEditorState';
|
||||
import { IEditorState } from '../Components/Editor/Editor';
|
||||
|
||||
/**
|
||||
* Revive the Editor state
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
onmessage = (e) => {
|
||||
const data = JSON.stringify(e.data.editorState, getCircularReplacer(), e.data.spaces);
|
||||
postMessage(data);
|
||||
};
|
||||
|
||||
const getCircularReplacer = () => {
|
||||
const seen = new WeakSet();
|
||||
return (key, value) => {
|
||||
if (key === 'parent') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === 'SelectedContainer') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
};
|
|
@ -76,6 +76,9 @@ const GetSVGLayoutConfiguration = () => {
|
|||
fillOpacity: 0,
|
||||
borderWidth: 2,
|
||||
stroke: 'blue',
|
||||
transform: 'translateX(-50%)',
|
||||
transformOrigin: 'center',
|
||||
transformBox: 'fill-box'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src", "src/workers"],
|
||||
"include": ["src"],
|
||||
"exclude": ["test-server"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue