diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 13ac012..33736e7 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -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 } }; diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 2def8a6..0000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.drawio filter=lfs diff=lfs merge=lfs -text diff --git a/docs/ComponentStructure.drawio b/docs/ComponentStructure.drawio deleted file mode 100644 index 2fdcefc..0000000 --- a/docs/ComponentStructure.drawio +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:59a34bc3428280ecd661efa7b4756bf9f4930f36c077984a40e7fdc6983aeeff -size 2063 diff --git a/index.html b/index.html index d9f5a08..e0d1c84 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,6 @@ - Vite + React + TS diff --git a/package-lock.json b/package-lock.json index b7e9f41..285432c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 5fdcfa7..1c45135 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be1cc19..10ead3b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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'} diff --git a/public/Interfaces.d.ts b/public/Interfaces.d.ts deleted file mode 100644 index 5a466a1..0000000 --- a/public/Interfaces.d.ts +++ /dev/null @@ -1,65 +0,0 @@ -declare interface IHistoryState { - LastAction: string - MainContainer: IContainerModel - SelectedContainer: IContainerModel | null - SelectedContainerId: string - TypeCounters: Record -} - -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 -} - -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 -} - diff --git a/public/style.css b/public/style.css deleted file mode 100644 index a732b5f..0000000 --- a/public/style.css +++ /dev/null @@ -1,6 +0,0 @@ -html, -body, -#root { - width: 100%; - height: 100%; -} diff --git a/src/Components/API/api.ts b/src/Components/API/api.ts index 9acea24..e113cc9 100644 --- a/src/Components/API/api.ts +++ b/src/Components/API/api.ts @@ -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 { +export async function fetchConfiguration(): Promise { 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 { }) .then(async(response) => await response.json() - ) as IConfiguration; + ) as Configuration; } return await new Promise((resolve) => { const xhr = new XMLHttpRequest(); diff --git a/src/Components/App/App.scss b/src/Components/App/App.scss index e69de29..2cb61ee 100644 --- a/src/Components/App/App.scss +++ b/src/Components/App/App.scss @@ -0,0 +1,6 @@ +html, +body, +#root { + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/src/Components/App/App.tsx b/src/Components/App/App.tsx index c85d322..c25c37e 100644 --- a/src/Components/App/App.tsx +++ b/src/Components/App/App.tsx @@ -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'; diff --git a/src/Components/App/Load.ts b/src/Components/App/Load.ts index 909dfc9..b5459c7 100644 --- a/src/Components/App/Load.ts +++ b/src/Components/App/Load.ts @@ -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, diff --git a/src/Components/App/MenuActions.ts b/src/Components/App/MenuActions.ts index c144acc..2669c1a 100644 --- a/src/Components/App/MenuActions.ts +++ b/src/Components/App/MenuActions.ts @@ -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' } diff --git a/src/Components/Bar/Bar.tsx b/src/Components/Bar/Bar.tsx index 77cb259..1738a3f 100644 --- a/src/Components/Bar/Bar.tsx +++ b/src/Components/Bar/Bar.tsx @@ -15,7 +15,7 @@ export const BAR_WIDTH = 64; // 4rem export const Bar: React.FC = (props) => { return ( -
+
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; -} diff --git a/src/Components/Editor/Behaviors/RigidBodyBehaviors.ts b/src/Components/Editor/Behaviors/RigidBodyBehaviors.ts deleted file mode 100644 index eabec85..0000000 --- a/src/Components/Editor/Behaviors/RigidBodyBehaviors.ts +++ /dev/null @@ -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; diff --git a/src/Components/Editor/ContainerOperations.ts b/src/Components/Editor/ContainerOperations.ts index f1736fc..ea4db42 100644 --- a/src/Components/Editor/ContainerOperations.ts +++ b/src/Components/Editor/ContainerOperations.ts @@ -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>, + setHistory: Dispatch>, setHistoryCurrentStep: Dispatch> ): 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>, + setHistory: Dispatch>, setHistoryCurrentStep: Dispatch> ): 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>, + setHistory: Dispatch>, setHistoryCurrentStep: Dispatch> ): 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>, + setHistory: Dispatch>, setHistoryCurrentStep: Dispatch> ): 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>, + setHistoryCurrentStep: Dispatch> +): 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; } diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index 3a728e3..ad27c22 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -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 = (props) => { - const [history, setHistory] = React.useState(structuredClone(props.history)); + const [history, setHistory] = React.useState(structuredClone(props.history)); const [historyCurrentStep, setHistoryCurrentStep] = React.useState(props.historyCurrentStep); - const editorRef = useRef(null); React.useEffect(() => { const onKeyUp = (event: KeyboardEvent): void => onKeyDown( @@ -41,37 +37,15 @@ const Editor: React.FunctionComponent = (props) => { window.addEventListener('keyup', onKeyUp); - const events = EditorEvents; - const editorState: IEditorState = { - history, - historyCurrentStep, - configuration: props.configuration - }; - - const funcs = new Map 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 ( -
+
= (props) => { setHistory, setHistoryCurrentStep )} - OnPropertiesSubmit={(event, properties) => OnPropertiesSubmit( - event, - properties, - history, - historyCurrentStep, - setHistory, - setHistoryCurrentStep - )} AddContainerToSelectedContainer={(type) => AddContainerToSelectedContainer( type, configuration, diff --git a/src/Components/Editor/PropertiesOperations.ts b/src/Components/Editor/PropertiesOperations.ts deleted file mode 100644 index 4bd4c2d..0000000 --- a/src/Components/Editor/PropertiesOperations.ts +++ /dev/null @@ -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>, - setHistoryCurrentStep: Dispatch> -): 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, - properties: IProperties, - fullHistory: IHistoryState[], - historyCurrentStep: number, - setHistory: Dispatch>, - setHistoryCurrentStep: Dispatch> -): 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); -} diff --git a/src/Components/Editor/Save.ts b/src/Components/Editor/Save.ts index 159959a..91cee2f 100644 --- a/src/Components/Editor/Save.ts +++ b/src/Components/Editor/Save.ts @@ -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 = '\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); } diff --git a/src/Components/Editor/Shortcuts.ts b/src/Components/Editor/Shortcuts.ts index aa20d96..8ac6790 100644 --- a/src/Components/Editor/Shortcuts.ts +++ b/src/Components/Editor/Shortcuts.ts @@ -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> ): void { diff --git a/src/Components/ElementsSidebar/ElementsSidebar.test.tsx b/src/Components/ElementsSidebar/ElementsSidebar.test.tsx index 8d34480..c1372b1 100644 --- a/src/Components/ElementsSidebar/ElementsSidebar.test.tsx +++ b/src/Components/ElementsSidebar/ElementsSidebar.test.tsx @@ -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={() => {}} diff --git a/src/Components/ElementsSidebar/ElementsSidebar.tsx b/src/Components/ElementsSidebar/ElementsSidebar.tsx index 30f1617..873161d 100644 --- a/src/Components/ElementsSidebar/ElementsSidebar.tsx +++ b/src/Components/ElementsSidebar/ElementsSidebar.tsx @@ -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, 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( + handleOnDrop(event, props.MainContainer, props.AddContainer)} + onDragOver={(event) => handleDragOver(event, props.MainContainer)} + onDragLeave={(event) => handleDragLeave(event)} + onClick={() => props.SelectContainer(container)} + > + { text } + + ); +}; + export const ElementsSidebar: React.FC = (props: IElementsSidebarProps): JSX.Element => { // States const [isContextMenuOpen, setIsContextMenuOpen] = React.useState(false); const [onClickContainerId, setOnClickContainerId] = React.useState(''); - const [contextMenuPosition, setContextMenuPosition] = React.useState({ + const [contextMenuPosition, setContextMenuPosition] = React.useState({ x: 0, y: 0 }); @@ -68,7 +105,7 @@ export const ElementsSidebar: React.FC = (props: IElement onLeftClick ); }; - }); + }, []); // Render let isOpenClasses = '-right-64'; @@ -78,55 +115,24 @@ export const ElementsSidebar: React.FC = (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 ( - + for (const container of it) { + createRows( + container, + props, + containerRows ); - }; + } - const ROW_HEIGHT = 35; - const NUMBERS_OF_ROWS = 10; return ( -
+
Elements
-
- - { Row } - +
+ { containerRows }
= (props: IElement props.DeleteContainer(onClickContainerId); }} /> - +
); }; diff --git a/src/Components/ElementsSidebar/MouseEventHandlers.ts b/src/Components/ElementsSidebar/MouseEventHandlers.ts index 1a814d2..53fb14e 100644 --- a/src/Components/ElementsSidebar/MouseEventHandlers.ts +++ b/src/Components/ElementsSidebar/MouseEventHandlers.ts @@ -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>, setOnClickContainerId: React.Dispatch>, - setContextMenuPosition: React.Dispatch> + setContextMenuPosition: React.Dispatch> ): 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); diff --git a/src/Components/History/History.tsx b/src/Components/History/History.tsx index 36e8e80..067a867 100644 --- a/src/Components/History/History.tsx +++ b/src/Components/History/History.tsx @@ -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 = (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 ( + ); - }; + }); + + // recent first + states.reverse(); return ( -
+
Timeline
- - { Row } - +
+ { states } +
); }; diff --git a/src/Components/MainMenu/MainMenu.tsx b/src/Components/MainMenu/MainMenu.tsx index 9c8b27f..1d84f9a 100644 --- a/src/Components/MainMenu/MainMenu.tsx +++ b/src/Components/MainMenu/MainMenu.tsx @@ -38,8 +38,13 @@ export const MainMenu: React.FC = (props) => { diff --git a/src/Components/Properties/Properties.test.tsx b/src/Components/Properties/Properties.test.tsx index 9fbef37..9f0fe13 100644 --- a/src/Components/Properties/Properties.test.tsx +++ b/src/Components/Properties/Properties.test.tsx @@ -8,7 +8,6 @@ describe.concurrent('Properties', () => { render( {}} - 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( {}} />); 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( {}} />); - 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(); diff --git a/src/Components/Properties/Properties.tsx b/src/Components/Properties/Properties.tsx index 516f23d..c1ac966 100644 --- a/src/Components/Properties/Properties.tsx +++ b/src/Components/Properties/Properties.tsx @@ -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, properties: ContainerProperties) => void } export const Properties: React.FC = (props: IPropertiesProps) => { - const [isDynamicInput, setIsDynamicInput] = useState(true); - if (props.properties === undefined) { return
; } @@ -19,33 +15,11 @@ export const Properties: React.FC = (props: IPropertiesProps) const groupInput: React.ReactNode[] = []; Object .entries(props.properties) - .forEach((pair) => handleProperties(pair, groupInput, isDynamicInput, props.onChange)); - - const form = isDynamicInput - ?
- { groupInput } -
- :
props.onSubmit(event, props.properties as ContainerProperties)} - > - -
- { groupInput } -
-
- ; + .forEach((pair) => handleProperties(pair, groupInput, props.onChange)); return ( -
- setIsDynamicInput(!isDynamicInput)} - /> - { form } +
+ { groupInput }
); }; @@ -53,7 +27,6 @@ export const Properties: React.FC = (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 - ? { - if (type === 'checkbox') { - onChange(key, event.target.checked); - return; - } - onChange(key, event.target.value); - }} - disabled={isDisabled} - /> - : ; + /// groupInput.push( - +
+ + { + if (type === 'checkbox') { + onChange(key, event.target.checked); + return; + } + onChange(key, event.target.value); + }} + disabled={isDisabled} + /> +
); - groupInput.push(input); }; diff --git a/src/Components/Properties/PropertiesInputTypes.tsx b/src/Components/Properties/PropertiesInputTypes.tsx index d91ddbc..c62a8fd 100644 --- a/src/Components/Properties/PropertiesInputTypes.tsx +++ b/src/Components/Properties/PropertiesInputTypes.tsx @@ -3,6 +3,5 @@ export const INPUT_TYPES: Record = { y: 'number', width: 'number', height: 'number', - isRigidBody: 'checkbox', - isAnchor: 'checkbox' + isRigidBody: 'checkbox' }; diff --git a/src/Components/SVG/Elements/Container.tsx b/src/Components/SVG/Elements/Container.tsx index 6417570..02dea6f 100644 --- a/src/Components/SVG/Elements/Container.tsx +++ b/src/Components/SVG/Elements/Container.tsx @@ -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 = (props: IContainerProps) => const containersElements = props.model.children.map(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 = (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 = (props: IContainerProps) => ); }; - -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]; -} diff --git a/src/Components/SVG/Elements/Dimension.tsx b/src/Components/SVG/Elements/Dimension.tsx index c5f6f86..ec51873 100644 --- a/src/Components/SVG/Elements/Dimension.tsx +++ b/src/Components/SVG/Elements/Dimension.tsx @@ -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 = (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 ( {props.text} diff --git a/src/Components/SVG/Elements/DimensionLayer.tsx b/src/Components/SVG/Elements/DimensionLayer.tsx new file mode 100644 index 0000000..85f1a43 --- /dev/null +++ b/src/Components/SVG/Elements/DimensionLayer.tsx @@ -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( + + ); + } + 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 = (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 ( + + { dimensions } + + ); +}; diff --git a/src/Components/SVG/Elements/Selector.tsx b/src/Components/SVG/Elements/Selector.tsx index e70ca79..c5937e0 100644 --- a/src/Components/SVG/Elements/Selector.tsx +++ b/src/Components/SVG/Elements/Selector.tsx @@ -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 { diff --git a/src/Components/SVG/SVG.tsx b/src/Components/SVG/SVG.tsx index 652258f..94fb746 100644 --- a/src/Components/SVG/SVG.tsx +++ b/src/Components/SVG/SVG.tsx @@ -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 = (props: ISVGProps) => { const [viewer, setViewer] = React.useState({ - viewerWidth: window.innerWidth - BAR_WIDTH, + viewerWidth: window.innerWidth, viewerHeight: window.innerHeight }); diff --git a/src/Components/Sidebar/Sidebar.tsx b/src/Components/Sidebar/Sidebar.tsx index c39b235..dc2f1a7 100644 --- a/src/Components/Sidebar/Sidebar.tsx +++ b/src/Components/Sidebar/Sidebar.tsx @@ -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 = (props: ISidebarProps) => { const isOpenClasses = props.isOpen ? 'left-16' : '-left-64'; return (
Components diff --git a/src/Components/ToggleButton/ToggleButton.scss b/src/Components/ToggleButton/ToggleButton.scss deleted file mode 100644 index 0948f52..0000000 --- a/src/Components/ToggleButton/ToggleButton.scss +++ /dev/null @@ -1,8 +0,0 @@ -input:checked ~ .dot { - transform: translateX(100%); -} -input:checked ~ .line { - background-color: #3B82F6; -} - - diff --git a/src/Components/ToggleButton/ToggleButton.tsx b/src/Components/ToggleButton/ToggleButton.tsx deleted file mode 100644 index 198bf99..0000000 --- a/src/Components/ToggleButton/ToggleButton.tsx +++ /dev/null @@ -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 -} - -export enum TOGGLE_TYPE { - MATERIAL, - IOS -} - -export const ToggleButton: FC = (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 ( -
-
- -
-
- ); -}; diff --git a/src/Components/UI/UI.tsx b/src/Components/UI/UI.tsx index 4fa36e6..6d6adbe 100644 --- a/src/Components/UI/UI.tsx +++ b/src/Components/UI/UI.tsx @@ -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, 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 = (props: IUIProps) => { isOpen={isElementsSidebarOpen} isHistoryOpen={isHistoryOpen} OnPropertyChange={props.OnPropertyChange} - OnPropertiesSubmit={props.OnPropertiesSubmit} SelectContainer={props.SelectContainer} DeleteContainer={props.DeleteContainer} AddContainer={props.AddContainer} diff --git a/src/Enums/AddingBehavior.ts b/src/Enums/AddingBehavior.ts new file mode 100644 index 0000000..fb6ae67 --- /dev/null +++ b/src/Enums/AddingBehavior.ts @@ -0,0 +1,4 @@ +export enum AddingBehavior { + InsertInto, + Replace +} diff --git a/src/Events/EditorEvents.ts b/src/Events/EditorEvents.ts deleted file mode 100644 index 316b7b8..0000000 --- a/src/Events/EditorEvents.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { IEditorState } from '../Interfaces/IEditorState'; -import { IHistoryState } from '../Interfaces/IHistoryState'; - -const getEditorState = (editorState: IEditorState): void => { - const customEvent = new CustomEvent('getEditorState', { detail: editorState }); - document.dispatchEvent(customEvent); -}; - -const getCurrentHistoryState = (editorState: IEditorState): void => { - const customEvent = new CustomEvent( - '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; diff --git a/src/Interfaces/IAvailableContainer.ts b/src/Interfaces/AvailableContainer.ts similarity index 54% rename from src/Interfaces/IAvailableContainer.ts rename to src/Interfaces/AvailableContainer.ts index c7ad3c1..d7bb22f 100644 --- a/src/Interfaces/IAvailableContainer.ts +++ b/src/Interfaces/AvailableContainer.ts @@ -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 } diff --git a/src/Interfaces/IAvailableSymbol.ts b/src/Interfaces/AvailableSymbol.ts similarity index 71% rename from src/Interfaces/IAvailableSymbol.ts rename to src/Interfaces/AvailableSymbol.ts index 3f3176a..e1d518d 100644 --- a/src/Interfaces/IAvailableSymbol.ts +++ b/src/Interfaces/AvailableSymbol.ts @@ -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 } diff --git a/src/Interfaces/Configuration.ts b/src/Interfaces/Configuration.ts new file mode 100644 index 0000000..f8d4854 --- /dev/null +++ b/src/Interfaces/Configuration.ts @@ -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 +} diff --git a/src/Interfaces/IContainerModel.ts b/src/Interfaces/ContainerModel.ts similarity index 81% rename from src/Interfaces/IContainerModel.ts rename to src/Interfaces/ContainerModel.ts index b180486..1c70ae3 100644 --- a/src/Interfaces/IContainerModel.ts +++ b/src/Interfaces/ContainerModel.ts @@ -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 } export class ContainerModel implements IContainerModel { public children: IContainerModel[]; public parent: IContainerModel | null; - public properties: IProperties; + public properties: Properties; public userData: Record; constructor( parent: IContainerModel | null, - properties: IProperties, + properties: Properties, children: IContainerModel[] = [], userData = {}) { this.parent = parent; diff --git a/src/Interfaces/IHistoryState.ts b/src/Interfaces/HistoryState.ts similarity index 66% rename from src/Interfaces/IHistoryState.ts rename to src/Interfaces/HistoryState.ts index fd46fbc..da1d74b 100644 --- a/src/Interfaces/IHistoryState.ts +++ b/src/Interfaces/HistoryState.ts @@ -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 diff --git a/src/Interfaces/IConfiguration.ts b/src/Interfaces/IConfiguration.ts deleted file mode 100644 index a37647d..0000000 --- a/src/Interfaces/IConfiguration.ts +++ /dev/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 -} diff --git a/src/Interfaces/IEditorState.ts b/src/Interfaces/IEditorState.ts deleted file mode 100644 index 495a868..0000000 --- a/src/Interfaces/IEditorState.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IConfiguration } from './IConfiguration'; -import { IHistoryState } from './IHistoryState'; - -export interface IEditorState { - history: IHistoryState[] - historyCurrentStep: number - configuration: IConfiguration -} diff --git a/src/Interfaces/IProperties.ts b/src/Interfaces/IProperties.ts deleted file mode 100644 index ef2db7e..0000000 --- a/src/Interfaces/IProperties.ts +++ /dev/null @@ -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 -} diff --git a/src/Interfaces/ISizePointer.ts b/src/Interfaces/ISizePointer.ts deleted file mode 100644 index 05f880c..0000000 --- a/src/Interfaces/ISizePointer.ts +++ /dev/null @@ -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 -} diff --git a/src/Interfaces/IImage.ts b/src/Interfaces/Image.ts similarity index 81% rename from src/Interfaces/IImage.ts rename to src/Interfaces/Image.ts index 7432440..b839b09 100644 --- a/src/Interfaces/IImage.ts +++ b/src/Interfaces/Image.ts @@ -1,5 +1,5 @@ /** Model of an image with multiple source */ -export interface IImage { +export interface Image { Name: string Url: string Base64Image: string diff --git a/src/Interfaces/IPoint.ts b/src/Interfaces/Point.ts similarity index 50% rename from src/Interfaces/IPoint.ts rename to src/Interfaces/Point.ts index d2e202a..43fd673 100644 --- a/src/Interfaces/IPoint.ts +++ b/src/Interfaces/Point.ts @@ -1,4 +1,4 @@ -export interface IPoint { +export interface Point { x: number y: number } diff --git a/src/Interfaces/Properties.ts b/src/Interfaces/Properties.ts new file mode 100644 index 0000000..ea5f54e --- /dev/null +++ b/src/Interfaces/Properties.ts @@ -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 +} diff --git a/src/Interfaces/SizePointer.ts b/src/Interfaces/SizePointer.ts new file mode 100644 index 0000000..9a80057 --- /dev/null +++ b/src/Interfaces/SizePointer.ts @@ -0,0 +1,4 @@ +export interface SizePointer { + x: number + width: number +} diff --git a/src/index.scss b/src/index.scss index f653990..1bc3361 100644 --- a/src/index.scss +++ b/src/index.scss @@ -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 } diff --git a/src/utils/default.ts b/src/utils/default.ts index 027c540..552b8a0 100644 --- a/src/utils/default.ts +++ b/src/utils/default.ts @@ -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; diff --git a/src/utils/itertools.ts b/src/utils/itertools.ts index d50a089..67cd40c 100644 --- a/src/utils/itertools.ts +++ b/src/utils/itertools.ts @@ -1,4 +1,4 @@ -import { IContainerModel } from '../Interfaces/IContainerModel'; +import { IContainerModel } from '../Interfaces/ContainerModel'; /** * Returns a Generator iterating of over the children depth-first diff --git a/src/utils/saveload.ts b/src/utils/saveload.ts index 356dd50..a33652e 100644 --- a/src/utils/saveload.ts +++ b/src/utils/saveload.ts @@ -1,5 +1,5 @@ import { findContainerById, MakeIterator } from './itertools'; -import { IEditorState } from '../Interfaces/IEditorState'; +import { IEditorState } from '../Components/Editor/Editor'; /** * Revive the Editor state diff --git a/src/workers/worker.js b/src/workers/worker.js deleted file mode 100644 index c11fa7d..0000000 --- a/src/workers/worker.js +++ /dev/null @@ -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; - }; -}; diff --git a/test-server/http.js b/test-server/http.js index 69088c9..c4f3238 100644 --- a/test-server/http.js +++ b/test-server/http.js @@ -76,6 +76,9 @@ const GetSVGLayoutConfiguration = () => { fillOpacity: 0, borderWidth: 2, stroke: 'blue', + transform: 'translateX(-50%)', + transformOrigin: 'center', + transformBox: 'fill-box' } } ], diff --git a/tsconfig.json b/tsconfig.json index 2fe4d5a..034ee0e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,7 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": ["src", "src/workers"], + "include": ["src"], "exclude": ["test-server"], "references": [{ "path": "./tsconfig.node.json" }] }