Merge branch 'fe/refactor/labling-sidebar' into 'fe/develop'

Refactor: 레이블링 사이드바: 가상 돔, 트리, 이미지 드래그

See merge request s11-s-project/S11P21S002!275
This commit is contained in:
조현수 2024-10-04 08:55:02 +09:00
commit cee46f5605
9 changed files with 1046 additions and 120 deletions

View File

@ -30,12 +30,15 @@
"konva": "^9.3.14",
"lucide-react": "^0.436.0",
"react": "^18.3.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-konva": "^18.2.10",
"react-resizable-panels": "^2.1.1",
"react-router-dom": "^6.26.1",
"react-slick": "^0.30.2",
"react-treebeard": "^3.2.4",
"react-virtualized-auto-sizer": "^1.0.24",
"react-window": "^1.8.10",
"recharts": "^2.12.7",
@ -60,6 +63,7 @@
"@storybook/test": "^8.2.9",
"@types/node": "^22.5.0",
"@types/react": "^18.3.3",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.13.1",
"@typescript-eslint/parser": "^7.13.1",
@ -119,7 +123,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.24.7",
"picocolors": "^1.0.0"
@ -180,7 +183,6 @@
"version": "7.25.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz",
"integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==",
"dev": true,
"dependencies": {
"@babel/types": "^7.25.4",
"@jridgewell/gen-mapping": "^0.3.5",
@ -345,7 +347,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
"integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
"dev": true,
"dependencies": {
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7"
@ -457,7 +458,6 @@
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
@ -466,7 +466,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
@ -511,7 +510,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.24.7",
"chalk": "^2.4.2",
@ -526,7 +524,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
@ -538,7 +535,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@ -552,7 +548,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
@ -560,14 +555,12 @@
"node_modules/@babel/highlight/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
@ -576,7 +569,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
@ -585,7 +577,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
@ -597,7 +588,6 @@
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz",
"integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.25.4"
},
@ -2122,7 +2112,6 @@
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
"integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/parser": "^7.25.0",
@ -2136,7 +2125,6 @@
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz",
"integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.25.4",
@ -2154,7 +2142,6 @@
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true,
"engines": {
"node": ">=4"
}
@ -2163,7 +2150,6 @@
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz",
"integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.24.8",
"@babel/helper-validator-identifier": "^7.24.7",
@ -2260,6 +2246,131 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@emotion/cache": {
"version": "10.0.29",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz",
"integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==",
"dependencies": {
"@emotion/sheet": "0.9.4",
"@emotion/stylis": "0.8.5",
"@emotion/utils": "0.11.3",
"@emotion/weak-memoize": "0.2.5"
}
},
"node_modules/@emotion/core": {
"version": "10.3.1",
"resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.3.1.tgz",
"integrity": "sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==",
"dependencies": {
"@babel/runtime": "^7.5.5",
"@emotion/cache": "^10.0.27",
"@emotion/css": "^10.0.27",
"@emotion/serialize": "^0.11.15",
"@emotion/sheet": "0.9.4",
"@emotion/utils": "0.11.3"
},
"peerDependencies": {
"react": ">=16.3.0"
}
},
"node_modules/@emotion/css": {
"version": "10.0.27",
"resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz",
"integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==",
"dependencies": {
"@emotion/serialize": "^0.11.15",
"@emotion/utils": "0.11.3",
"babel-plugin-emotion": "^10.0.27"
}
},
"node_modules/@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
},
"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==",
"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=="
},
"node_modules/@emotion/serialize": {
"version": "0.11.16",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz",
"integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==",
"dependencies": {
"@emotion/hash": "0.8.0",
"@emotion/memoize": "0.7.4",
"@emotion/unitless": "0.7.5",
"@emotion/utils": "0.11.3",
"csstype": "^2.5.7"
}
},
"node_modules/@emotion/serialize/node_modules/csstype": {
"version": "2.6.21",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
},
"node_modules/@emotion/sheet": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz",
"integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA=="
},
"node_modules/@emotion/styled": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-10.3.0.tgz",
"integrity": "sha512-GgcUpXBBEU5ido+/p/mCT2/Xx+Oqmp9JzQRuC+a4lYM4i4LBBn/dWvc0rQ19N9ObA8/T4NWMrPNe79kMBDJqoQ==",
"dependencies": {
"@emotion/styled-base": "^10.3.0",
"babel-plugin-emotion": "^10.0.27"
},
"peerDependencies": {
"@emotion/core": "^10.0.27",
"react": ">=16.3.0"
}
},
"node_modules/@emotion/styled-base": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/@emotion/styled-base/-/styled-base-10.3.0.tgz",
"integrity": "sha512-PBRqsVKR7QRNkmfH78hTSSwHWcwDpecH9W6heujWAcyp2wdz/64PP73s7fWS1dIPm8/Exc8JAzYS8dEWXjv60w==",
"dependencies": {
"@babel/runtime": "^7.5.5",
"@emotion/is-prop-valid": "0.8.8",
"@emotion/serialize": "^0.11.15",
"@emotion/utils": "0.11.3"
},
"peerDependencies": {
"@emotion/core": "^10.0.28",
"react": ">=16.3.0"
}
},
"node_modules/@emotion/stylis": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
"integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
},
"node_modules/@emotion/unitless": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
},
"node_modules/@emotion/utils": {
"version": "0.11.3",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz",
"integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw=="
},
"node_modules/@emotion/weak-memoize": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@ -4862,6 +4973,21 @@
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
"license": "MIT"
},
"node_modules/@react-dnd/asap": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
"integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="
},
"node_modules/@react-dnd/invariant": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz",
"integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="
},
"node_modules/@react-dnd/shallowequal": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
"integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
},
"node_modules/@remix-run/router": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz",
@ -6853,6 +6979,17 @@
"@types/unist": "*"
}
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
"optional": true,
"peer": true,
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/http-errors": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
@ -6906,6 +7043,11 @@
"undici-types": "~6.19.2"
}
},
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="
},
"node_modules/@types/prop-types": {
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
@ -6934,6 +7076,15 @@
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-beautiful-dnd": {
"version": "13.1.8",
"resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.8.tgz",
"integrity": "sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-dom": {
"version": "18.3.0",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
@ -7644,6 +7795,77 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/babel-plugin-emotion": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz",
"integrity": "sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==",
"dependencies": {
"@babel/helper-module-imports": "^7.0.0",
"@emotion/hash": "0.8.0",
"@emotion/memoize": "0.7.4",
"@emotion/serialize": "^0.11.16",
"babel-plugin-macros": "^2.0.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^1.0.5",
"find-root": "^1.1.0",
"source-map": "^0.5.7"
}
},
"node_modules/babel-plugin-emotion/node_modules/convert-source-map": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
},
"node_modules/babel-plugin-emotion/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/babel-plugin-emotion/node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/babel-plugin-macros": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
"integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
"dependencies": {
"@babel/runtime": "^7.7.2",
"cosmiconfig": "^6.0.0",
"resolve": "^1.12.0"
}
},
"node_modules/babel-plugin-macros/node_modules/cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"dependencies": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.7.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/babel-plugin-macros/node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"engines": {
"node": ">= 6"
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz",
@ -7692,6 +7914,11 @@
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/babel-plugin-syntax-jsx": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
"integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw=="
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -7892,7 +8119,6 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"dev": true,
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
@ -7911,7 +8137,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -8557,7 +8782,6 @@
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "2.1.2"
@ -8586,6 +8810,25 @@
"node": ">=6"
}
},
"node_modules/deep-equal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
"dependencies": {
"is-arguments": "^1.1.1",
"is-date-object": "^1.0.5",
"is-regex": "^1.1.4",
"object-is": "^1.1.5",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.5.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -8609,7 +8852,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
@ -8622,6 +8864,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/defu": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
@ -8713,6 +8971,16 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"license": "MIT"
},
"node_modules/dnd-core": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
"integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
"dependencies": {
"@react-dnd/asap": "^5.0.1",
"@react-dnd/invariant": "^4.0.1",
"redux": "^4.2.0"
}
},
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -8826,7 +9094,6 @@
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.2.1"
@ -8836,7 +9103,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.2.4"
},
@ -8848,7 +9114,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
@ -9439,7 +9704,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-equals": {
@ -9606,6 +9870,11 @@
"url": "https://github.com/avajs/find-cache-dir?sponsor=1"
}
},
"node_modules/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
},
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@ -9846,6 +10115,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/functions-have-names": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -9877,7 +10154,6 @@
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"dev": true,
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
@ -10064,7 +10340,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.1.3"
},
@ -10108,7 +10383,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"dependencies": {
"es-define-property": "^1.0.0"
},
@ -10120,7 +10394,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@ -10132,7 +10405,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@ -10144,7 +10416,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"dependencies": {
"has-symbols": "^1.0.3"
},
@ -10212,6 +10483,19 @@
"integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==",
"dev": true
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/hoist-non-react-statics/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/html-tags": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz",
@ -10308,7 +10592,6 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
"license": "MIT",
"dependencies": {
"parent-module": "^1.0.0",
@ -10401,7 +10684,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
@ -10417,7 +10699,6 @@
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"dev": true,
"license": "MIT"
},
"node_modules/is-binary-path": {
@ -10459,6 +10740,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-date-object": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dependencies": {
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@ -10547,6 +10842,21 @@
"node": ">=0.10.0"
}
},
"node_modules/is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
@ -10706,7 +11016,6 @@
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true,
"bin": {
"jsesc": "bin/jsesc"
},
@ -10725,7 +11034,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true,
"license": "MIT"
},
"node_modules/json-schema-traverse": {
@ -11242,7 +11550,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true,
"license": "MIT"
},
"node_modules/msw": {
@ -11528,6 +11835,29 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ohash": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz",
@ -11670,7 +12000,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"callsites": "^3.0.0"
@ -11683,7 +12012,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.0.0",
@ -11768,7 +12096,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -12406,6 +12733,43 @@
"react": "^16.3.0 || ^17.0.1 || ^18.0.0"
}
},
"node_modules/react-dnd": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
"integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
"dependencies": {
"@react-dnd/invariant": "^4.0.1",
"@react-dnd/shallowequal": "^4.0.1",
"dnd-core": "^16.0.1",
"fast-deep-equal": "^3.1.3",
"hoist-non-react-statics": "^3.3.2"
},
"peerDependencies": {
"@types/hoist-non-react-statics": ">= 3.3.1",
"@types/node": ">= 12",
"@types/react": ">= 16",
"react": ">= 16.14"
},
"peerDependenciesMeta": {
"@types/hoist-non-react-statics": {
"optional": true
},
"@types/node": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/react-dnd-html5-backend": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
"integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
"dependencies": {
"dnd-core": "^16.0.1"
}
},
"node_modules/react-docgen": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-7.0.3.tgz",
@ -12523,6 +12887,11 @@
"react-dom": ">=18.0.0"
}
},
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"node_modules/react-reconciler": {
"version": "0.29.2",
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz",
@ -12714,11 +13083,67 @@
"react-dom": ">=16.6.0"
}
},
"node_modules/react-treebeard": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/react-treebeard/-/react-treebeard-3.2.4.tgz",
"integrity": "sha512-TsvdUq2kbLavRXa8k4mmqfPse8HmSA9G9s1SZUtIpiYSccSwa0Tm6miMgx7DZ5gpKofQ+j/3Ua0rjsahM3/FQg==",
"dependencies": {
"@emotion/core": "^10.0.10",
"@emotion/styled": "^10.0.10",
"deep-equal": "^1.0.1",
"shallowequal": "^1.1.0",
"velocity-react": "^1.4.1"
},
"peerDependencies": {
"@babel/runtime": ">=7.0.0",
"@emotion/styled": "^10.0.10",
"prop-types": ">=15.7.2",
"react": ">=16.7.0",
"react-dom": ">=16.7.0"
}
},
"node_modules/react-treebeard/node_modules/dom-helpers": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
"dependencies": {
"@babel/runtime": "^7.1.2"
}
},
"node_modules/react-treebeard/node_modules/react-transition-group": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
"dependencies": {
"dom-helpers": "^3.4.0",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2",
"react-lifecycles-compat": "^3.0.4"
},
"peerDependencies": {
"react": ">=15.0.0",
"react-dom": ">=15.0.0"
}
},
"node_modules/react-treebeard/node_modules/velocity-react": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/velocity-react/-/velocity-react-1.4.3.tgz",
"integrity": "sha512-zvefGm85A88S3KdF9/dz5vqyFLAiwKYlXGYkHH2EbXl+CZUD1OT0a0aS1tkX/WXWTa/FUYqjBaAzAEFYuSobBQ==",
"dependencies": {
"lodash": "^4.17.5",
"prop-types": "^15.5.8",
"react-transition-group": "^2.0.0",
"velocity-animate": "^1.4.0"
},
"peerDependencies": {
"react": "^15.3.0 || ^16.0.0",
"react-dom": "^15.3.0 || ^16.0.0"
}
},
"node_modules/react-virtualized-auto-sizer": {
"version": "1.0.24",
"resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.24.tgz",
"integrity": "sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==",
"license": "MIT",
"peerDependencies": {
"react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0",
"react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0"
@ -12728,7 +13153,6 @@
"version": "1.8.10",
"resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz",
"integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.0.0",
"memoize-one": ">=3.1.1 <6"
@ -12858,6 +13282,14 @@
"node": ">=8"
}
},
"node_modules/redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -12890,6 +13322,23 @@
"@babel/runtime": "^7.8.4"
}
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
"integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1",
"es-errors": "^1.3.0",
"set-function-name": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/regexpu-core": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz",
@ -13012,7 +13461,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@ -13258,7 +13706,6 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dev": true,
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
@ -13271,6 +13718,20 @@
"node": ">= 0.4"
}
},
"node_modules/set-function-name": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"functions-have-names": "^1.2.3",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@ -13289,6 +13750,11 @@
"node": ">=8"
}
},
"node_modules/shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -14127,7 +14593,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
"dev": true,
"engines": {
"node": ">=4"
}
@ -14678,6 +15143,11 @@
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/velocity-animate": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/velocity-animate/-/velocity-animate-1.5.2.tgz",
"integrity": "sha512-m6EXlCAMetKztO1ppBhGU1/1MR3IiEevO6ESq6rcrSQ3Q77xYSW13jkfXW88o4xMrkXJhy/U7j4wFR/twMB0Eg=="
},
"node_modules/victory-vendor": {
"version": "36.9.2",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",

View File

@ -36,12 +36,15 @@
"konva": "^9.3.14",
"lucide-react": "^0.436.0",
"react": "^18.3.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-konva": "^18.2.10",
"react-resizable-panels": "^2.1.1",
"react-router-dom": "^6.26.1",
"react-slick": "^0.30.2",
"react-treebeard": "^3.2.4",
"react-virtualized-auto-sizer": "^1.0.24",
"react-window": "^1.8.10",
"recharts": "^2.12.7",
@ -66,6 +69,7 @@
"@storybook/test": "^8.2.9",
"@types/node": "^22.5.0",
"@types/react": "^18.3.3",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.13.1",
"@typescript-eslint/parser": "^7.13.1",

View File

@ -2,16 +2,12 @@ import api from '@/api/axiosConfig';
import { ImageMoveRequest, ImageStatusChangeRequest, ImagePresignedUrlResponse } from '@/types';
import axios from 'axios';
export async function getImage(imageId: number, memberId: number) {
return api.get(`/images/${imageId}`, {
params: { memberId },
});
export async function getImage(projectId: number, folderId: number, imageId: number) {
return api.get(`/api/projects/${projectId}/folders/${folderId}/images/${imageId}`);
}
export async function moveImage(imageId: number, memberId: number, moveRequest: ImageMoveRequest) {
return api.put(`/images/${imageId}`, moveRequest, {
params: { memberId },
});
export async function moveImage(projectId: number, folderId: number, imageId: number, moveRequest: ImageMoveRequest) {
return api.put(`/projects/${projectId}/folders/${folderId}/images/${imageId}`, moveRequest);
}
export async function deleteImage(imageId: number, memberId: number) {
@ -23,7 +19,7 @@ export async function deleteImage(imageId: number, memberId: number) {
export async function changeImageStatus(
imageId: number,
memberId: number,
statusChangeRequest: ImageStatusChangeRequest,
statusChangeRequest: ImageStatusChangeRequest
) {
return api
.put(`/images/${imageId}/status`, statusChangeRequest, {
@ -37,7 +33,7 @@ export async function uploadImageFile(
projectId: number,
folderId: number,
files: File[],
processCallback: (progress: number) => void,
processCallback: (progress: number) => void
) {
const formData = new FormData();
files.forEach((file) => {
@ -62,7 +58,7 @@ export async function uploadImageFolderFile(
projectId: number,
folderId: number,
files: File[],
processCallback: (progress: number) => void,
processCallback: (progress: number) => void
) {
const formData = new FormData();
files.forEach((file) => {
@ -87,7 +83,7 @@ export async function uploadImageFolder(
projectId: number,
folderId: number,
files: File[],
processCallback: (progress: number) => void,
processCallback: (progress: number) => void
) {
const formData = new FormData();
files.forEach((file) => {
@ -112,7 +108,7 @@ export async function uploadImageZip(
projectId: number,
folderId: number,
file: File,
processCallback: (progress: number) => void,
processCallback: (progress: number) => void
) {
const formData = new FormData();
formData.append('folderZip', file);
@ -135,7 +131,7 @@ export async function uploadImagePresigned(
projectId: number,
folderId: number,
files: File[],
processCallback: (index: number) => void,
processCallback: (index: number) => void
) {
// 업로드 시작 시간 기록
const startTime = new Date().getTime();
@ -152,11 +148,10 @@ export async function uploadImagePresigned(
imageMetaList,
{
params: { memberId },
},
}
);
// 각 파일을 presigned URL에 맞춰서 업로드 (axios 직접 사용)
// 각 파일을 presigned URL에 맞춰서 업로드 (axios 직접 사용)
for (const presignedUrlInfo of presignedUrlList) {
const file = files[presignedUrlInfo.id];

View File

@ -1,19 +1,43 @@
import { Project } from '@/types';
import ProjectFileItem from './ProjectFileItem';
import ProjectDirectoryItem from './ProjectDirectoryItem';
import useFolderQuery from '@/queries/folders/useFolderQuery';
import useCanvasStore from '@/stores/useCanvasStore';
import { useEffect } from 'react';
import WorkspaceDropdownMenu from '../WorkspaceDropdownMenu';
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { TreeNode } from 'react-treebeard';
import useProjectStore from '@/stores/useProjectStore';
import useCanvasStore from '@/stores/useCanvasStore';
import useTreeData from '@/hooks/useTreeData';
import useProjectCategoriesQuery from '@/queries/category/useProjectCategoriesQuery';
import useMoveImageQuery from '@/queries/images/useMoveImageQuery';
import { Project, ImageResponse } from '@/types';
import WorkspaceDropdownMenu from '../WorkspaceDropdownMenu';
import AutoLabelButton from './AutoLabelButton';
import { Folder, Image as ImageIcon, Minus, Loader, ArrowDownToLine, Send, CircleSlash, Check } from 'lucide-react';
import { Spinner } from '../ui/spinner';
import { ImageStatus } from '@/types';
import { FixedSizeList as List, ListChildComponentProps } from 'react-window';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import useFolderQuery from '@/queries/folders/useFolderQuery';
interface FlatNode extends TreeNode {
depth: number;
isLeaf: boolean;
parent?: FlatNode;
index?: number;
}
const ItemTypes = {
NODE: 'node',
};
export default function ProjectStructure({ project }: { project: Project }) {
const { setProject, setCategories } = useProjectStore();
const { setProject, setCategories, folderId, setFolderId } = useProjectStore();
const { image: selectedImage, setImage } = useCanvasStore();
const { treeData, fetchNodeData, setTreeData } = useTreeData(project.id.toString());
const { data: categories } = useProjectCategoriesQuery(project.id);
const image = useCanvasStore((state) => state.image);
const { data: folderData, refetch } = useFolderQuery(project.id.toString(), 0);
const { isLoading, refetch } = useFolderQuery(project.id.toString(), 0);
const moveImageMutation = useMoveImageQuery();
const containerRef = useRef<HTMLDivElement>(null);
const [containerHeight, setContainerHeight] = useState<number>(400);
useEffect(() => {
setCategories(categories);
@ -23,47 +47,288 @@ export default function ProjectStructure({ project }: { project: Project }) {
setProject(project);
}, [project, setProject]);
return (
<div className="flex h-full min-h-0 grow-0 flex-col">
<div className="flex h-full flex-col overflow-hidden px-1 pb-2">
<header className="flex w-full items-center gap-2 rounded p-1">
<div className="flex w-full min-w-0 items-center gap-1 pr-1">
<h2 className="caption overflow-hidden text-ellipsis whitespace-nowrap text-gray-500">{project.type}</h2>
</div>
<WorkspaceDropdownMenu
projectId={project.id}
folderId={0}
onRefetch={refetch}
/>
</header>
{folderData.children.length === 0 && folderData.images.length === 0 ? (
<div className="body-small flex h-full select-none items-center justify-center text-gray-400">
.
</div>
) : (
<div className="caption flex flex-col overflow-y-auto">
{folderData.children.map((item) => (
<ProjectDirectoryItem
key={`${project.id}-${item.title}`}
projectId={project.id}
item={item}
initialExpanded={true}
/>
))}
{folderData.images.map((item) => (
<ProjectFileItem
key={`${project.id}-${item.imageTitle}`}
item={item}
selected={image?.id === item.id}
/>
))}
</div>
)}
</div>
useEffect(() => {
if (treeData) {
setFolderId(folderId);
}
}, [treeData, setFolderId, folderId]);
<div className="flex">
<AutoLabelButton projectId={project.id} />
useEffect(() => {
if (containerRef.current) {
setContainerHeight(containerRef.current.clientHeight);
}
}, [containerRef, treeData, isLoading]);
const onToggle = useCallback(
async (node: TreeNode, toggled: boolean) => {
if (!node.imageData) {
if (toggled && (!node.children || node.children.length === 0)) {
await fetchNodeData(node);
}
const updateNode = (currentNode: TreeNode): TreeNode => {
if (currentNode.id === node.id) {
return { ...currentNode, toggled };
}
if (currentNode.children) {
return {
...currentNode,
children: currentNode.children.map(updateNode),
};
}
return currentNode;
};
setTreeData((prevData) => prevData && updateNode(prevData));
}
},
[fetchNodeData, setTreeData]
);
const handleImageClick = useCallback(
(image: ImageResponse, parent?: FlatNode) => {
setImage(image);
if (parent) {
setFolderId(Number(parent.id)); // 클릭된 이미지의 상위 폴더 ID 설정
}
},
[setImage, setFolderId]
);
const renderStatusIcon = (status: ImageStatus) => {
const iconProps = { size: 12, className: 'shrink-0' };
const iconColor = {
PENDING: 'stroke-gray-400',
IN_PROGRESS: 'animate-spin stroke-yellow-400',
SAVE: 'stroke-gray-400',
REVIEW_REQUEST: 'stroke-blue-400',
REVIEW_REJECT: 'stroke-red-400',
COMPLETED: 'stroke-green-400',
};
const iconMapping = {
PENDING: (
<Minus
{...iconProps}
className={`${iconProps.className} ${iconColor.PENDING}`}
/>
),
IN_PROGRESS: (
<Loader
{...iconProps}
className={`${iconProps.className} ${iconColor.IN_PROGRESS}`}
/>
),
SAVE: (
<ArrowDownToLine
{...iconProps}
className={`${iconProps.className} ${iconColor.SAVE}`}
/>
),
REVIEW_REQUEST: (
<Send
{...iconProps}
className={`${iconProps.className} ${iconColor.REVIEW_REQUEST}`}
/>
),
REVIEW_REJECT: (
<CircleSlash
{...iconProps}
className={`${iconProps.className} ${iconColor.REVIEW_REJECT}`}
/>
),
COMPLETED: (
<Check
{...iconProps}
className={`${iconProps.className} ${iconColor.COMPLETED}`}
/>
),
};
return iconMapping[status] || null;
};
const flattenTree = useCallback((nodes: TreeNode[], depth: number = 0, parent?: FlatNode): FlatNode[] => {
let flatList: FlatNode[] = [];
nodes.forEach((node, index) => {
const flatNode: FlatNode = {
...node,
depth,
isLeaf: !node.children || node.children.length === 0,
parent,
index,
};
flatList.push(flatNode);
if (node.toggled && node.children) {
flatList = flatList.concat(flattenTree(node.children, depth + 1, flatNode));
}
});
return flatList;
}, []);
const flatData = useMemo(() => {
if (!treeData) return [];
return flattenTree([treeData]);
}, [treeData, flattenTree]);
const getItemKey = (index: number, data: FlatNode[]) => data[index].id!;
const moveNode = useCallback(
(dragItem: FlatNode, hoverItem: FlatNode) => {
const updatedTreeData = (function moveNodeInTree(node: TreeNode): TreeNode {
if (node.id === dragItem.parent?.id) {
const newChildren = node.children?.filter((child) => child.id !== dragItem.id) || [];
return { ...node, children: newChildren };
}
if (node.id === hoverItem.parent?.id) {
const newChildren = [...(node.children || [])];
const hoverIndex = newChildren.findIndex((child) => child.id === hoverItem.id);
newChildren.splice(hoverIndex, 0, { ...dragItem, parent: hoverItem.parent } as FlatNode);
return { ...node, children: newChildren };
}
if (node.children) {
return {
...node,
children: node.children.map(moveNodeInTree),
};
}
return node;
})(treeData!);
setTreeData(updatedTreeData);
if (dragItem.imageData) {
moveImageMutation.mutate({
projectId: project.id,
folderId: Number(dragItem.parent?.id),
imageId: dragItem.imageData.id,
moveRequest: {
moveFolderId: Number(hoverItem.parent?.id),
},
});
}
},
[treeData, setTreeData, moveImageMutation, project.id]
);
const Row = ({ index, style, data }: ListChildComponentProps<FlatNode[]>) => {
const node = data[index];
const ref = useRef<HTMLDivElement>(null);
const [, drop] = useDrop({
accept: ItemTypes.NODE,
drop(item: FlatNode) {
const dragItem = item;
const hoverItem = node;
if (dragItem.id === hoverItem.id) {
return;
}
moveNode(dragItem, hoverItem);
},
});
const [{ isDragging }, drag] = useDrag({
type: ItemTypes.NODE,
item: () => ({ ...node, index }),
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
drag(drop(ref));
return (
<div
ref={ref}
style={{
...style,
opacity: isDragging ? 0.5 : 1,
display: 'flex',
alignItems: 'center',
paddingLeft: `${node.depth * 20}px`,
cursor: 'pointer',
backgroundColor: node.imageData && selectedImage?.id === node.imageData.id ? '#e5e7eb' : 'transparent',
}}
onClick={() => {
if (node.imageData) {
handleImageClick(node.imageData as ImageResponse, node.parent);
} else {
onToggle(node, !node.toggled);
}
}}
>
<div style={{ marginRight: '5px' }}>
{!node.imageData ? (
<Folder
size={16}
className="stroke-gray-500"
/>
) : (
<ImageIcon
size={16}
className="stroke-gray-500"
/>
)}
</div>
<span style={{ color: '#4a4a4a', flexGrow: 1 }}>{node.name}</span>
{node.imageData && <div style={{ marginRight: '20px' }}>{renderStatusIcon(node.imageData.status)}</div>}
</div>
</div>
);
};
return (
<DndProvider backend={HTML5Backend}>
<div
className="box-border flex h-full min-h-0 flex-col bg-gray-50 p-2"
style={{ overflowX: 'hidden' }}
ref={containerRef}
>
<div className="flex h-full flex-col gap-2 overflow-hidden px-1 pb-2">
<header className="flex w-full items-center gap-2 rounded-md bg-white p-2 shadow-sm">
<div className="flex w-full min-w-0 items-center gap-1 pr-1">
<h2 className="caption overflow-hidden text-ellipsis whitespace-nowrap text-gray-600">{project.type}</h2>
</div>
<WorkspaceDropdownMenu
projectId={project.id}
folderId={0}
onRefetch={refetch}
/>
</header>
{isLoading ? (
<div className="flex h-full items-center justify-center">
<Spinner />
</div>
) : !treeData ? (
<div className="body-small flex h-full select-none items-center justify-center text-gray-400">
Loading...
</div>
) : (
<List
height={Math.min(flatData.length * 40, containerHeight)}
itemCount={flatData.length}
itemSize={40}
width={'100%'}
itemData={flatData}
itemKey={getItemKey}
className="flex-1 overflow-auto"
style={{ overflowX: 'hidden' }}
>
{Row}
</List>
)}
</div>
<div className="flex">
<AutoLabelButton projectId={project.id} />
</div>
</div>
</DndProvider>
);
}

View File

@ -0,0 +1,10 @@
import { getImage } from '@/api/imageApi';
import { useQuery } from '@tanstack/react-query';
export default function useImage(projectId: number, folderId: number, imageId: number) {
return useQuery({
queryKey: ['image', projectId, folderId, imageId],
queryFn: () => getImage(projectId, folderId, imageId),
enabled: Boolean(projectId && folderId && imageId),
});
}

View File

@ -0,0 +1,115 @@
import { useState, useCallback, useEffect } from 'react';
import { TreeNode } from 'react-treebeard';
import { ImageResponse, ChildFolder } from '@/types';
import { useQuery } from '@tanstack/react-query';
import { getFolder } from '@/api/folderApi';
export function useFolder(projectId: string, folderId: number) {
return useQuery({
queryKey: ['folder', projectId, folderId],
queryFn: () => getFolder(projectId, folderId),
enabled: folderId === 0,
});
}
export function useChildFolder(projectId: string, folderId: number, enabled: boolean) {
return useQuery({
queryKey: ['folder', projectId, folderId],
queryFn: () => getFolder(projectId, folderId),
enabled: enabled,
});
}
export default function useTreeData(projectId: string) {
const [treeData, setTreeData] = useState<TreeNode | null>(null);
const [currentFolderId, setCurrentFolderId] = useState<number | null>(null);
const { data: rootFolder, isLoading: isRootLoading } = useFolder(projectId, 0);
const { data: childFolder, isFetching: isChildLoading } = useChildFolder(
projectId,
currentFolderId || 0,
currentFolderId !== null
);
useEffect(() => {
if (rootFolder) {
const childFolders: TreeNode[] =
rootFolder.children?.map((child: ChildFolder) => ({
id: child.id.toString(),
name: child.title,
children: [],
})) || [];
const images: TreeNode[] =
rootFolder.images?.map((image: ImageResponse) => ({
id: image.id.toString(),
name: image.imageTitle,
imageData: image,
children: [],
})) || [];
const rootNode: TreeNode = {
id: rootFolder.id.toString(),
name: rootFolder.title,
children: [...childFolders, ...images],
toggled: true,
};
setTreeData(rootNode);
}
}, [rootFolder]);
useEffect(() => {
if (childFolder && currentFolderId !== null) {
const childFolders: TreeNode[] =
childFolder.children?.map((child: ChildFolder) => ({
id: child.id.toString(),
name: child.title,
children: [],
})) || [];
const images: TreeNode[] =
childFolder.images?.map((image: ImageResponse) => ({
id: image.id.toString(),
name: image.imageTitle,
imageData: image,
children: [],
})) || [];
setTreeData((prevData) => {
if (!prevData) return null;
const updateNode = (currentNode: TreeNode): TreeNode => {
if (currentNode.id === currentFolderId.toString()) {
return {
...currentNode,
children: [...childFolders, ...images],
toggled: true,
};
}
if (currentNode.children) {
return {
...currentNode,
children: currentNode.children.map(updateNode),
};
}
return currentNode;
};
return updateNode(prevData);
});
}
}, [childFolder, currentFolderId]);
const fetchNodeData = useCallback((node: TreeNode) => {
setCurrentFolderId(Number(node.id));
}, []);
return {
treeData,
fetchNodeData,
setTreeData,
isLoading: isRootLoading || isChildLoading,
};
}

View File

@ -1,9 +1,9 @@
import { getImage } from '@/api/imageApi';
import { useSuspenseQuery } from '@tanstack/react-query';
export default function useImageQuery(imageId: number, memberId: number) {
export default function useImageQuery(projectId: number, folderId: number, imageId: number) {
return useSuspenseQuery({
queryKey: ['image', imageId, memberId],
queryFn: () => getImage(imageId, memberId),
queryKey: ['image', projectId, folderId, imageId],
queryFn: () => getImage(projectId, folderId, imageId),
});
}

View File

@ -3,8 +3,9 @@ import { moveImage } from '@/api/imageApi';
import { ImageMoveRequest } from '@/types';
interface MoveImageMutationVariables {
projectId: number;
folderId: number;
imageId: number;
memberId: number;
moveRequest: ImageMoveRequest;
}
@ -12,10 +13,12 @@ export default function useMoveImageQuery() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ imageId, memberId, moveRequest }: MoveImageMutationVariables) =>
moveImage(imageId, memberId, moveRequest),
mutationFn: ({ projectId, folderId, imageId, moveRequest }: MoveImageMutationVariables) =>
moveImage(projectId, folderId, imageId, moveRequest),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['image', variables.imageId] });
queryClient.invalidateQueries({ queryKey: ['project', variables.projectId] });
queryClient.invalidateQueries({ queryKey: ['folder', variables.folderId] });
},
});
}

64
frontend/src/types/react-treebeard.d.ts vendored Normal file
View File

@ -0,0 +1,64 @@
declare module 'react-treebeard' {
import React from 'react';
type TreeDecoratorTypes = 'Loading' | 'Toggle' | 'Header' | 'Container';
type CSS = React.CSSProperties;
export type TreeAnimations = object;
export interface TreeTheme {
tree: {
base: CSS;
node: {
base: CSS;
link: CSS;
activeLink: CSS;
toggle: {
base: CSS;
wrapper: CSS;
height: number;
width: number;
arrow: CSS;
};
header: {
base: CSS;
connector: CSS;
title: CSS;
};
subtree: CSS;
loading: CSS;
};
};
}
export type TreeDecorators = { [T in TreeDecoratorTypes]: React.ElementType };
export interface TreeNode {
imageData?: ImageResponse;
/** The component key. If not defined, an auto - generated index is used. */
id?: string;
/** The name prop passed into the Header component. */
name: string;
/** The children attached to the node. This value populates the subtree at the specific node.Each child is built from the same basic data structure.
*
* Tip: Make this an empty array, if you want to asynchronously load a potential parent. */
children?: Array<TreeNode>;
/** Toggled flag. Sets the visibility of a node's children. It also sets the state for the toggle decorator. */
toggled?: boolean;
/** Active flag. If active, the node will be highlighted.The highlight is derived from the node.activeLink style object in the theme. */
active?: boolean;
/** Loading flag. It will populate the treeview with the loading component.Useful when asynchronously pulling the data into the treeview. */
loading?: boolean;
/** Attach specific decorators to a node. Provides the low level functionality to create visuals on a node-by-node basis. */
decorators?: TreeDecorators;
/** Attach specific animations to a node. Provides the low level functionality to create visuals on a node-by-node basis. */
animations?: TreeAnimations;
}
type TreebeardProps = {
data: TreeNode | Array<TreeNode>;
onToggle?: (node: TreeNode, toggled: boolean) => void;
style?: TreeTheme;
animations?: TreeAnimations | boolean;
decorators?: TreeDecorators;
};
export const Treebeard: React.ElementType<TreebeardProps>;
export const decorators: TreeDecorators;
export const animations: TreeAnimations;
export const theme: TreeTheme;
}