feat(graph): 3D mode toggle — three.js + WebGL fallback
This commit is contained in:
@@ -39,12 +39,14 @@
|
|||||||
"oidc-client-ts": "^3.5.0",
|
"oidc-client-ts": "^3.5.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-force-graph-3d": "^1.29.1",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-i18next": "^17.0.2",
|
"react-i18next": "^17.0.2",
|
||||||
"react-leaflet": "^5.0.0",
|
"react-leaflet": "^5.0.0",
|
||||||
"react-oidc-context": "^3.3.1",
|
"react-oidc-context": "^3.3.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.6.0",
|
"tailwind-merge": "^3.6.0",
|
||||||
|
"three": "^0.184.0",
|
||||||
"topojson-client": "^3.1.0",
|
"topojson-client": "^3.1.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"world-atlas": "^2.0.2"
|
"world-atlas": "^2.0.2"
|
||||||
@@ -67,6 +69,7 @@
|
|||||||
"@types/leaflet": "^1.9.21",
|
"@types/leaflet": "^1.9.21",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"@types/three": "^0.184.1",
|
||||||
"@types/topojson-client": "^3.1.5",
|
"@types/topojson-client": "^3.1.5",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"conventional-changelog-conventionalcommits": "^9.3.1",
|
"conventional-changelog-conventionalcommits": "^9.3.1",
|
||||||
|
|||||||
Generated
+326
@@ -92,6 +92,9 @@ importers:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^19.0.0
|
specifier: ^19.0.0
|
||||||
version: 19.2.5(react@19.2.5)
|
version: 19.2.5(react@19.2.5)
|
||||||
|
react-force-graph-3d:
|
||||||
|
specifier: ^1.29.1
|
||||||
|
version: 1.29.1(react@19.2.5)
|
||||||
react-hook-form:
|
react-hook-form:
|
||||||
specifier: ^7.54.2
|
specifier: ^7.54.2
|
||||||
version: 7.75.0(react@19.2.5)
|
version: 7.75.0(react@19.2.5)
|
||||||
@@ -110,6 +113,9 @@ importers:
|
|||||||
tailwind-merge:
|
tailwind-merge:
|
||||||
specifier: ^3.6.0
|
specifier: ^3.6.0
|
||||||
version: 3.6.0
|
version: 3.6.0
|
||||||
|
three:
|
||||||
|
specifier: ^0.184.0
|
||||||
|
version: 0.184.0
|
||||||
topojson-client:
|
topojson-client:
|
||||||
specifier: ^3.1.0
|
specifier: ^3.1.0
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
@@ -171,6 +177,9 @@ importers:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
specifier: ^19.0.0
|
specifier: ^19.0.0
|
||||||
version: 19.2.3(@types/react@19.2.14)
|
version: 19.2.3(@types/react@19.2.14)
|
||||||
|
'@types/three':
|
||||||
|
specifier: ^0.184.1
|
||||||
|
version: 0.184.1
|
||||||
'@types/topojson-client':
|
'@types/topojson-client':
|
||||||
specifier: ^3.1.5
|
specifier: ^3.1.5
|
||||||
version: 3.1.5
|
version: 3.1.5
|
||||||
@@ -201,6 +210,10 @@ importers:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
3d-force-graph@1.80.0:
|
||||||
|
resolution: {integrity: sha512-tzI353gW1nXPpnC7VTa3JjMg+3cp77qOLUFO0vucPTfF+q5R6sQsNsIqVTbRIb7RSypn14nBa4yfkOe9ThxASw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
'@adobe/css-tools@4.4.4':
|
'@adobe/css-tools@4.4.4':
|
||||||
resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
|
resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
|
||||||
|
|
||||||
@@ -362,6 +375,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
|
resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
|
||||||
engines: {node: '>=20.19.0'}
|
engines: {node: '>=20.19.0'}
|
||||||
|
|
||||||
|
'@dimforge/rapier3d-compat@0.12.0':
|
||||||
|
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.21.5':
|
'@esbuild/aix-ppc64@0.21.5':
|
||||||
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -1619,6 +1635,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@testing-library/dom': '>=7.21.4'
|
'@testing-library/dom': '>=7.21.4'
|
||||||
|
|
||||||
|
'@tweenjs/tween.js@23.1.3':
|
||||||
|
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
||||||
|
|
||||||
|
'@tweenjs/tween.js@25.0.0':
|
||||||
|
resolution: {integrity: sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==}
|
||||||
|
|
||||||
'@types/aria-query@5.0.4':
|
'@types/aria-query@5.0.4':
|
||||||
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
||||||
|
|
||||||
@@ -1663,12 +1685,21 @@ packages:
|
|||||||
'@types/react@19.2.14':
|
'@types/react@19.2.14':
|
||||||
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
|
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
|
||||||
|
|
||||||
|
'@types/stats.js@0.17.4':
|
||||||
|
resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==}
|
||||||
|
|
||||||
|
'@types/three@0.184.1':
|
||||||
|
resolution: {integrity: sha512-6q4VdiqVsrTRqmk62/BnlcAvIrnDM0zf2ZDVKI5kZiniWrSaOHaQzmbp+BNzoggc/8tgW412pL//wZIxu2PPTA==}
|
||||||
|
|
||||||
'@types/topojson-client@3.1.5':
|
'@types/topojson-client@3.1.5':
|
||||||
resolution: {integrity: sha512-C79rySTyPxnQNNguTZNI1Ct4D7IXgvyAs3p9HPecnl6mNrJ5+UhvGNYcZfpROYV2lMHI48kJPxwR+F9C6c7nmw==}
|
resolution: {integrity: sha512-C79rySTyPxnQNNguTZNI1Ct4D7IXgvyAs3p9HPecnl6mNrJ5+UhvGNYcZfpROYV2lMHI48kJPxwR+F9C6c7nmw==}
|
||||||
|
|
||||||
'@types/topojson-specification@1.0.5':
|
'@types/topojson-specification@1.0.5':
|
||||||
resolution: {integrity: sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ==}
|
resolution: {integrity: sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ==}
|
||||||
|
|
||||||
|
'@types/webxr@0.5.24':
|
||||||
|
resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==}
|
||||||
|
|
||||||
'@vitejs/plugin-react@4.7.0':
|
'@vitejs/plugin-react@4.7.0':
|
||||||
resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
|
resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
@@ -1704,6 +1735,10 @@ packages:
|
|||||||
'@vitest/utils@2.1.9':
|
'@vitest/utils@2.1.9':
|
||||||
resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
|
resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
|
||||||
|
|
||||||
|
accessor-fn@1.5.3:
|
||||||
|
resolution: {integrity: sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
agent-base@7.1.4:
|
agent-base@7.1.4:
|
||||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
@@ -2000,26 +2035,72 @@ packages:
|
|||||||
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
|
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-binarytree@1.0.2:
|
||||||
|
resolution: {integrity: sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==}
|
||||||
|
|
||||||
|
d3-color@3.1.0:
|
||||||
|
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
d3-dispatch@3.0.1:
|
d3-dispatch@3.0.1:
|
||||||
resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
|
resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-force-3d@3.0.6:
|
||||||
|
resolution: {integrity: sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
d3-force@3.0.0:
|
d3-force@3.0.0:
|
||||||
resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
|
resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-format@3.1.2:
|
||||||
|
resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
d3-geo@3.1.1:
|
d3-geo@3.1.1:
|
||||||
resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
|
resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-interpolate@3.0.1:
|
||||||
|
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-octree@1.1.0:
|
||||||
|
resolution: {integrity: sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==}
|
||||||
|
|
||||||
d3-quadtree@3.0.1:
|
d3-quadtree@3.0.1:
|
||||||
resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
|
resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-scale-chromatic@3.1.0:
|
||||||
|
resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-scale@4.0.2:
|
||||||
|
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-selection@3.0.0:
|
||||||
|
resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-time-format@4.1.0:
|
||||||
|
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-time@3.1.0:
|
||||||
|
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
d3-timer@3.0.1:
|
d3-timer@3.0.1:
|
||||||
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
data-bind-mapper@1.0.3:
|
||||||
|
resolution: {integrity: sha512-QmU3lyEnbENQPo0M1F9BMu4s6cqNNp8iJA+b/HP2sSb7pf3dxwF3+EP1eO69rwBfH9kFJ1apmzrtogAmVt2/Xw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
data-urls@7.0.0:
|
data-urls@7.0.0:
|
||||||
resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
|
resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
|
||||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||||
@@ -2205,6 +2286,9 @@ packages:
|
|||||||
picomatch:
|
picomatch:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
fflate@0.8.2:
|
||||||
|
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
||||||
|
|
||||||
figures@2.0.0:
|
figures@2.0.0:
|
||||||
resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==}
|
resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -2229,6 +2313,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==}
|
resolution: {integrity: sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
float-tooltip@1.7.5:
|
||||||
|
resolution: {integrity: sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
follow-redirects@1.16.0:
|
follow-redirects@1.16.0:
|
||||||
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
|
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
@@ -2517,6 +2605,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==}
|
resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==}
|
||||||
engines: {node: '>= 0.6.0'}
|
engines: {node: '>= 0.6.0'}
|
||||||
|
|
||||||
|
jerrypick@1.1.2:
|
||||||
|
resolution: {integrity: sha512-YKnxXEekXKzhpf7CLYA0A+oDP8V0OhICNCr5lv96FvSsDEmrb0GKM776JgQvHTMjr7DTTPEVv/1Ciaw0uEWzBA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
jiti@2.6.1:
|
jiti@2.6.1:
|
||||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -2566,6 +2658,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
|
resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
kapsule@1.16.3:
|
||||||
|
resolution: {integrity: sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
keyv@5.6.0:
|
keyv@5.6.0:
|
||||||
resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==}
|
resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==}
|
||||||
|
|
||||||
@@ -2678,6 +2774,10 @@ packages:
|
|||||||
lodash@4.18.1:
|
lodash@4.18.1:
|
||||||
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
|
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
|
||||||
|
|
||||||
|
loose-envify@1.4.0:
|
||||||
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
loupe@3.2.1:
|
loupe@3.2.1:
|
||||||
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
|
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
|
||||||
|
|
||||||
@@ -2740,6 +2840,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
meshoptimizer@1.1.1:
|
||||||
|
resolution: {integrity: sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==}
|
||||||
|
|
||||||
micromatch@4.0.8:
|
micromatch@4.0.8:
|
||||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||||
engines: {node: '>=8.6'}
|
engines: {node: '>=8.6'}
|
||||||
@@ -2793,6 +2896,21 @@ packages:
|
|||||||
nerf-dart@1.0.0:
|
nerf-dart@1.0.0:
|
||||||
resolution: {integrity: sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==}
|
resolution: {integrity: sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==}
|
||||||
|
|
||||||
|
ngraph.events@1.4.0:
|
||||||
|
resolution: {integrity: sha512-NeDGI4DSyjBNBRtA86222JoYietsmCXbs8CEB0dZ51Xeh4lhVl1y3wpWLumczvnha8sFQIW4E0vvVWwgmX2mGw==}
|
||||||
|
|
||||||
|
ngraph.forcelayout@3.3.1:
|
||||||
|
resolution: {integrity: sha512-MKBuEh1wujyQHFTW57y5vd/uuEOK0XfXYxm3lC7kktjJLRdt/KEKEknyOlc6tjXflqBKEuYBBcu7Ax5VY+S6aw==}
|
||||||
|
|
||||||
|
ngraph.graph@20.1.2:
|
||||||
|
resolution: {integrity: sha512-W/G3GBR3Y5UxMLHTUCPP9v+pbtpzwuAEIqP5oZV+9IwgxAIEZwh+Foc60iPc1idlnK7Zxu0p3puxAyNmDvBd0Q==}
|
||||||
|
|
||||||
|
ngraph.merge@1.0.0:
|
||||||
|
resolution: {integrity: sha512-5J8YjGITUJeapsomtTALYsw7rFveYkM+lBj3QiYZ79EymQcuri65Nw3knQtFxQBU1r5iOaVRXrSwMENUPK62Vg==}
|
||||||
|
|
||||||
|
ngraph.random@1.2.0:
|
||||||
|
resolution: {integrity: sha512-4EUeAGbB2HWX9njd6bP6tciN6ByJfoaAvmVL9QTaZSeXrW46eNGA9GajiXiPBbvFqxUWFkEbyo6x5qsACUuVfA==}
|
||||||
|
|
||||||
node-emoji@2.2.0:
|
node-emoji@2.2.0:
|
||||||
resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==}
|
resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -3046,10 +3164,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==}
|
resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
polished@4.3.1:
|
||||||
|
resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
postcss@8.5.13:
|
postcss@8.5.13:
|
||||||
resolution: {integrity: sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==}
|
resolution: {integrity: sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
preact@10.29.1:
|
||||||
|
resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==}
|
||||||
|
|
||||||
prettier@3.8.3:
|
prettier@3.8.3:
|
||||||
resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==}
|
resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -3066,6 +3191,9 @@ packages:
|
|||||||
process-nextick-args@2.0.1:
|
process-nextick-args@2.0.1:
|
||||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||||
|
|
||||||
|
prop-types@15.8.1:
|
||||||
|
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||||
|
|
||||||
proto-list@1.2.4:
|
proto-list@1.2.4:
|
||||||
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
|
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
|
||||||
|
|
||||||
@@ -3096,6 +3224,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^19.2.5
|
react: ^19.2.5
|
||||||
|
|
||||||
|
react-force-graph-3d@1.29.1:
|
||||||
|
resolution: {integrity: sha512-5Vp+PGpYnO+zLwgK2NvNqdXHvsWLrFzpDfJW1vUA1twjo9SPvXqfUYQrnRmAbD+K2tOxkZw1BkbH31l5b4TWHg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '*'
|
||||||
|
|
||||||
react-hook-form@7.75.0:
|
react-hook-form@7.75.0:
|
||||||
resolution: {integrity: sha512-Ovv94H+0p3sJ7B9B5QxPuCP1u8V/cHuVGyH55cSwodYDtoJwK+fqk3vjfIgSX59I2U/bU4z0nRJ9HMLpNiWEmw==}
|
resolution: {integrity: sha512-Ovv94H+0p3sJ7B9B5QxPuCP1u8V/cHuVGyH55cSwodYDtoJwK+fqk3vjfIgSX59I2U/bU4z0nRJ9HMLpNiWEmw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3118,9 +3252,18 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
react-is@16.13.1:
|
||||||
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
|
|
||||||
react-is@17.0.2:
|
react-is@17.0.2:
|
||||||
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
||||||
|
|
||||||
|
react-kapsule@2.5.7:
|
||||||
|
resolution: {integrity: sha512-kifAF4ZPD77qZKc4CKLmozq6GY1sBzPEJTIJb0wWFK6HsePJatK3jXplZn2eeAt3x67CDozgi7/rO8fNQ/AL7A==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.13.1'
|
||||||
|
|
||||||
react-leaflet@5.0.0:
|
react-leaflet@5.0.0:
|
||||||
resolution: {integrity: sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==}
|
resolution: {integrity: sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3424,6 +3567,21 @@ packages:
|
|||||||
thenify@3.3.1:
|
thenify@3.3.1:
|
||||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||||
|
|
||||||
|
three-forcegraph@1.43.4:
|
||||||
|
resolution: {integrity: sha512-FtmiZP/T16ZQaHza3JDaDn0YTXFtg9e7pGnTeU8nzu0NNkx7MpWbF/GvmpbQsWHx3rukHtkRv1fTorLPB3FDEA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
peerDependencies:
|
||||||
|
three: '>=0.118.3'
|
||||||
|
|
||||||
|
three-render-objects@1.41.1:
|
||||||
|
resolution: {integrity: sha512-0H7l7yREPVKfO3HL7RjPQ67T0phHgnyMeEc4ww/OCEfK6jbsm7psEcrR0SGFqGDyS/pDQTPi4DyPbS/xlHRJKw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
peerDependencies:
|
||||||
|
three: '>=0.179'
|
||||||
|
|
||||||
|
three@0.184.0:
|
||||||
|
resolution: {integrity: sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==}
|
||||||
|
|
||||||
through2@2.0.5:
|
through2@2.0.5:
|
||||||
resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
|
resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
|
||||||
|
|
||||||
@@ -3434,6 +3592,9 @@ packages:
|
|||||||
tinybench@2.9.0:
|
tinybench@2.9.0:
|
||||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||||
|
|
||||||
|
tinycolor2@1.6.0:
|
||||||
|
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
||||||
|
|
||||||
tinyexec@0.3.2:
|
tinyexec@0.3.2:
|
||||||
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||||
|
|
||||||
@@ -3773,6 +3934,14 @@ packages:
|
|||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
|
3d-force-graph@1.80.0:
|
||||||
|
dependencies:
|
||||||
|
accessor-fn: 1.5.3
|
||||||
|
kapsule: 1.16.3
|
||||||
|
three: 0.184.0
|
||||||
|
three-forcegraph: 1.43.4(three@0.184.0)
|
||||||
|
three-render-objects: 1.41.1(three@0.184.0)
|
||||||
|
|
||||||
'@adobe/css-tools@4.4.4': {}
|
'@adobe/css-tools@4.4.4': {}
|
||||||
|
|
||||||
'@asamuzakjp/css-color@5.1.11':
|
'@asamuzakjp/css-color@5.1.11':
|
||||||
@@ -3950,6 +4119,8 @@ snapshots:
|
|||||||
|
|
||||||
'@csstools/css-tokenizer@4.0.0': {}
|
'@csstools/css-tokenizer@4.0.0': {}
|
||||||
|
|
||||||
|
'@dimforge/rapier3d-compat@0.12.0': {}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.21.5':
|
'@esbuild/aix-ppc64@0.21.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -5041,6 +5212,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@testing-library/dom': 10.4.1
|
'@testing-library/dom': 10.4.1
|
||||||
|
|
||||||
|
'@tweenjs/tween.js@23.1.3': {}
|
||||||
|
|
||||||
|
'@tweenjs/tween.js@25.0.0': {}
|
||||||
|
|
||||||
'@types/aria-query@5.0.4': {}
|
'@types/aria-query@5.0.4': {}
|
||||||
|
|
||||||
'@types/babel__core@7.20.5':
|
'@types/babel__core@7.20.5':
|
||||||
@@ -5090,6 +5265,17 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
csstype: 3.2.3
|
csstype: 3.2.3
|
||||||
|
|
||||||
|
'@types/stats.js@0.17.4': {}
|
||||||
|
|
||||||
|
'@types/three@0.184.1':
|
||||||
|
dependencies:
|
||||||
|
'@dimforge/rapier3d-compat': 0.12.0
|
||||||
|
'@tweenjs/tween.js': 23.1.3
|
||||||
|
'@types/stats.js': 0.17.4
|
||||||
|
'@types/webxr': 0.5.24
|
||||||
|
fflate: 0.8.2
|
||||||
|
meshoptimizer: 1.1.1
|
||||||
|
|
||||||
'@types/topojson-client@3.1.5':
|
'@types/topojson-client@3.1.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/geojson': 7946.0.16
|
'@types/geojson': 7946.0.16
|
||||||
@@ -5099,6 +5285,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/geojson': 7946.0.16
|
'@types/geojson': 7946.0.16
|
||||||
|
|
||||||
|
'@types/webxr@0.5.24': {}
|
||||||
|
|
||||||
'@vitejs/plugin-react@4.7.0(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0))':
|
'@vitejs/plugin-react@4.7.0(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.0
|
'@babel/core': 7.29.0
|
||||||
@@ -5151,6 +5339,8 @@ snapshots:
|
|||||||
loupe: 3.2.1
|
loupe: 3.2.1
|
||||||
tinyrainbow: 1.2.0
|
tinyrainbow: 1.2.0
|
||||||
|
|
||||||
|
accessor-fn@1.5.3: {}
|
||||||
|
|
||||||
agent-base@7.1.4: {}
|
agent-base@7.1.4: {}
|
||||||
|
|
||||||
aggregate-error@3.1.0:
|
aggregate-error@3.1.0:
|
||||||
@@ -5454,22 +5644,69 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
internmap: 2.0.3
|
internmap: 2.0.3
|
||||||
|
|
||||||
|
d3-binarytree@1.0.2: {}
|
||||||
|
|
||||||
|
d3-color@3.1.0: {}
|
||||||
|
|
||||||
d3-dispatch@3.0.1: {}
|
d3-dispatch@3.0.1: {}
|
||||||
|
|
||||||
|
d3-force-3d@3.0.6:
|
||||||
|
dependencies:
|
||||||
|
d3-binarytree: 1.0.2
|
||||||
|
d3-dispatch: 3.0.1
|
||||||
|
d3-octree: 1.1.0
|
||||||
|
d3-quadtree: 3.0.1
|
||||||
|
d3-timer: 3.0.1
|
||||||
|
|
||||||
d3-force@3.0.0:
|
d3-force@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-dispatch: 3.0.1
|
d3-dispatch: 3.0.1
|
||||||
d3-quadtree: 3.0.1
|
d3-quadtree: 3.0.1
|
||||||
d3-timer: 3.0.1
|
d3-timer: 3.0.1
|
||||||
|
|
||||||
|
d3-format@3.1.2: {}
|
||||||
|
|
||||||
d3-geo@3.1.1:
|
d3-geo@3.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-array: 3.2.4
|
d3-array: 3.2.4
|
||||||
|
|
||||||
|
d3-interpolate@3.0.1:
|
||||||
|
dependencies:
|
||||||
|
d3-color: 3.1.0
|
||||||
|
|
||||||
|
d3-octree@1.1.0: {}
|
||||||
|
|
||||||
d3-quadtree@3.0.1: {}
|
d3-quadtree@3.0.1: {}
|
||||||
|
|
||||||
|
d3-scale-chromatic@3.1.0:
|
||||||
|
dependencies:
|
||||||
|
d3-color: 3.1.0
|
||||||
|
d3-interpolate: 3.0.1
|
||||||
|
|
||||||
|
d3-scale@4.0.2:
|
||||||
|
dependencies:
|
||||||
|
d3-array: 3.2.4
|
||||||
|
d3-format: 3.1.2
|
||||||
|
d3-interpolate: 3.0.1
|
||||||
|
d3-time: 3.1.0
|
||||||
|
d3-time-format: 4.1.0
|
||||||
|
|
||||||
|
d3-selection@3.0.0: {}
|
||||||
|
|
||||||
|
d3-time-format@4.1.0:
|
||||||
|
dependencies:
|
||||||
|
d3-time: 3.1.0
|
||||||
|
|
||||||
|
d3-time@3.1.0:
|
||||||
|
dependencies:
|
||||||
|
d3-array: 3.2.4
|
||||||
|
|
||||||
d3-timer@3.0.1: {}
|
d3-timer@3.0.1: {}
|
||||||
|
|
||||||
|
data-bind-mapper@1.0.3:
|
||||||
|
dependencies:
|
||||||
|
accessor-fn: 1.5.3
|
||||||
|
|
||||||
data-urls@7.0.0:
|
data-urls@7.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
whatwg-mimetype: 5.0.0
|
whatwg-mimetype: 5.0.0
|
||||||
@@ -5694,6 +5931,8 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
picomatch: 4.0.4
|
picomatch: 4.0.4
|
||||||
|
|
||||||
|
fflate@0.8.2: {}
|
||||||
|
|
||||||
figures@2.0.0:
|
figures@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
escape-string-regexp: 1.0.5
|
escape-string-regexp: 1.0.5
|
||||||
@@ -5717,6 +5956,12 @@ snapshots:
|
|||||||
semver-regex: 4.0.5
|
semver-regex: 4.0.5
|
||||||
super-regex: 1.1.0
|
super-regex: 1.1.0
|
||||||
|
|
||||||
|
float-tooltip@1.7.5:
|
||||||
|
dependencies:
|
||||||
|
d3-selection: 3.0.0
|
||||||
|
kapsule: 1.16.3
|
||||||
|
preact: 10.29.1
|
||||||
|
|
||||||
follow-redirects@1.16.0: {}
|
follow-redirects@1.16.0: {}
|
||||||
|
|
||||||
form-data-encoder@4.1.0: {}
|
form-data-encoder@4.1.0: {}
|
||||||
@@ -5976,6 +6221,8 @@ snapshots:
|
|||||||
|
|
||||||
java-properties@1.0.2: {}
|
java-properties@1.0.2: {}
|
||||||
|
|
||||||
|
jerrypick@1.1.2: {}
|
||||||
|
|
||||||
jiti@2.6.1: {}
|
jiti@2.6.1: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
@@ -6030,6 +6277,10 @@ snapshots:
|
|||||||
|
|
||||||
jwt-decode@4.0.0: {}
|
jwt-decode@4.0.0: {}
|
||||||
|
|
||||||
|
kapsule@1.16.3:
|
||||||
|
dependencies:
|
||||||
|
lodash-es: 4.18.1
|
||||||
|
|
||||||
keyv@5.6.0:
|
keyv@5.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@keyv/serialize': 1.1.1
|
'@keyv/serialize': 1.1.1
|
||||||
@@ -6113,6 +6364,10 @@ snapshots:
|
|||||||
|
|
||||||
lodash@4.18.1: {}
|
lodash@4.18.1: {}
|
||||||
|
|
||||||
|
loose-envify@1.4.0:
|
||||||
|
dependencies:
|
||||||
|
js-tokens: 4.0.0
|
||||||
|
|
||||||
loupe@3.2.1: {}
|
loupe@3.2.1: {}
|
||||||
|
|
||||||
lowercase-keys@3.0.0: {}
|
lowercase-keys@3.0.0: {}
|
||||||
@@ -6164,6 +6419,8 @@ snapshots:
|
|||||||
|
|
||||||
merge2@1.4.1: {}
|
merge2@1.4.1: {}
|
||||||
|
|
||||||
|
meshoptimizer@1.1.1: {}
|
||||||
|
|
||||||
micromatch@4.0.8:
|
micromatch@4.0.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
braces: 3.0.3
|
braces: 3.0.3
|
||||||
@@ -6201,6 +6458,22 @@ snapshots:
|
|||||||
|
|
||||||
nerf-dart@1.0.0: {}
|
nerf-dart@1.0.0: {}
|
||||||
|
|
||||||
|
ngraph.events@1.4.0: {}
|
||||||
|
|
||||||
|
ngraph.forcelayout@3.3.1:
|
||||||
|
dependencies:
|
||||||
|
ngraph.events: 1.4.0
|
||||||
|
ngraph.merge: 1.0.0
|
||||||
|
ngraph.random: 1.2.0
|
||||||
|
|
||||||
|
ngraph.graph@20.1.2:
|
||||||
|
dependencies:
|
||||||
|
ngraph.events: 1.4.0
|
||||||
|
|
||||||
|
ngraph.merge@1.0.0: {}
|
||||||
|
|
||||||
|
ngraph.random@1.2.0: {}
|
||||||
|
|
||||||
node-emoji@2.2.0:
|
node-emoji@2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sindresorhus/is': 4.6.0
|
'@sindresorhus/is': 4.6.0
|
||||||
@@ -6352,12 +6625,18 @@ snapshots:
|
|||||||
find-up: 2.1.0
|
find-up: 2.1.0
|
||||||
load-json-file: 4.0.0
|
load-json-file: 4.0.0
|
||||||
|
|
||||||
|
polished@4.3.1:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.29.2
|
||||||
|
|
||||||
postcss@8.5.13:
|
postcss@8.5.13:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.12
|
nanoid: 3.3.12
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
preact@10.29.1: {}
|
||||||
|
|
||||||
prettier@3.8.3: {}
|
prettier@3.8.3: {}
|
||||||
|
|
||||||
pretty-format@27.5.1:
|
pretty-format@27.5.1:
|
||||||
@@ -6372,6 +6651,12 @@ snapshots:
|
|||||||
|
|
||||||
process-nextick-args@2.0.1: {}
|
process-nextick-args@2.0.1: {}
|
||||||
|
|
||||||
|
prop-types@15.8.1:
|
||||||
|
dependencies:
|
||||||
|
loose-envify: 1.4.0
|
||||||
|
object-assign: 4.1.1
|
||||||
|
react-is: 16.13.1
|
||||||
|
|
||||||
proto-list@1.2.4: {}
|
proto-list@1.2.4: {}
|
||||||
|
|
||||||
protocols@2.0.2: {}
|
protocols@2.0.2: {}
|
||||||
@@ -6396,6 +6681,13 @@ snapshots:
|
|||||||
react: 19.2.5
|
react: 19.2.5
|
||||||
scheduler: 0.27.0
|
scheduler: 0.27.0
|
||||||
|
|
||||||
|
react-force-graph-3d@1.29.1(react@19.2.5):
|
||||||
|
dependencies:
|
||||||
|
3d-force-graph: 1.80.0
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 19.2.5
|
||||||
|
react-kapsule: 2.5.7(react@19.2.5)
|
||||||
|
|
||||||
react-hook-form@7.75.0(react@19.2.5):
|
react-hook-form@7.75.0(react@19.2.5):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.5
|
react: 19.2.5
|
||||||
@@ -6411,8 +6703,15 @@ snapshots:
|
|||||||
react-dom: 19.2.5(react@19.2.5)
|
react-dom: 19.2.5(react@19.2.5)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
|
react-is@16.13.1: {}
|
||||||
|
|
||||||
react-is@17.0.2: {}
|
react-is@17.0.2: {}
|
||||||
|
|
||||||
|
react-kapsule@2.5.7(react@19.2.5):
|
||||||
|
dependencies:
|
||||||
|
jerrypick: 1.1.2
|
||||||
|
react: 19.2.5
|
||||||
|
|
||||||
react-leaflet@5.0.0(leaflet@1.9.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
|
react-leaflet@5.0.0(leaflet@1.9.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@react-leaflet/core': 3.0.0(leaflet@1.9.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
'@react-leaflet/core': 3.0.0(leaflet@1.9.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
@@ -6737,6 +7036,31 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
any-promise: 1.3.0
|
any-promise: 1.3.0
|
||||||
|
|
||||||
|
three-forcegraph@1.43.4(three@0.184.0):
|
||||||
|
dependencies:
|
||||||
|
accessor-fn: 1.5.3
|
||||||
|
d3-array: 3.2.4
|
||||||
|
d3-force-3d: 3.0.6
|
||||||
|
d3-scale: 4.0.2
|
||||||
|
d3-scale-chromatic: 3.1.0
|
||||||
|
data-bind-mapper: 1.0.3
|
||||||
|
kapsule: 1.16.3
|
||||||
|
ngraph.forcelayout: 3.3.1
|
||||||
|
ngraph.graph: 20.1.2
|
||||||
|
three: 0.184.0
|
||||||
|
tinycolor2: 1.6.0
|
||||||
|
|
||||||
|
three-render-objects@1.41.1(three@0.184.0):
|
||||||
|
dependencies:
|
||||||
|
'@tweenjs/tween.js': 25.0.0
|
||||||
|
accessor-fn: 1.5.3
|
||||||
|
float-tooltip: 1.7.5
|
||||||
|
kapsule: 1.16.3
|
||||||
|
polished: 4.3.1
|
||||||
|
three: 0.184.0
|
||||||
|
|
||||||
|
three@0.184.0: {}
|
||||||
|
|
||||||
through2@2.0.5:
|
through2@2.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
readable-stream: 2.3.8
|
readable-stream: 2.3.8
|
||||||
@@ -6748,6 +7072,8 @@ snapshots:
|
|||||||
|
|
||||||
tinybench@2.9.0: {}
|
tinybench@2.9.0: {}
|
||||||
|
|
||||||
|
tinycolor2@1.6.0: {}
|
||||||
|
|
||||||
tinyexec@0.3.2: {}
|
tinyexec@0.3.2: {}
|
||||||
|
|
||||||
tinyglobby@0.2.16:
|
tinyglobby@0.2.16:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { Component, Suspense, lazy, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
|
||||||
import { useNavigate } from '@tanstack/react-router'
|
import { useNavigate } from '@tanstack/react-router'
|
||||||
import { useQueries } from '@tanstack/react-query'
|
import { useQueries } from '@tanstack/react-query'
|
||||||
import {
|
import {
|
||||||
@@ -16,6 +16,50 @@ import type { DictionaryDefinition, SchemaDependent } from '@/api/client'
|
|||||||
import { LoadingBlock } from '@/ui'
|
import { LoadingBlock } from '@/ui'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
// 3D-режим лениво грузится — three.js (~650kb) и react-force-graph-3d не
|
||||||
|
// попадают в default chunk. Vite автоматически создаёт отдельный chunk для
|
||||||
|
// dynamic import. Suspense fallback показывается во время загрузки модуля.
|
||||||
|
const DictionaryGraph3D = lazy(() => import('./DictionaryGraph3D'))
|
||||||
|
|
||||||
|
type GraphMode = '2d' | '3d'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scoped error boundary для 3D Suspense — three.js'у нужен WebGL, и если
|
||||||
|
* браузер его не поддерживает (corp VM, headless без --use-gl=*, hardware
|
||||||
|
* accel выключен) — `WebGLRenderer` бросает ошибку в render-цикле. Без
|
||||||
|
* этого ErrorBoundary fallback'a ошибка bubble'ит до AppErrorBoundary и
|
||||||
|
* вся page становится "Что-то пошло не так". Здесь мы её ловим локально,
|
||||||
|
* показываем friendly fallback + предлагаем вернуться в 2D режим.
|
||||||
|
*
|
||||||
|
* `onError` callback позволяет parent auto-reset'ить mode → '2d', чтобы
|
||||||
|
* пользователь не залипал на ошибочном состоянии. Recovery через
|
||||||
|
* componentDidUpdate (resetKey changes → state.hasError = false).
|
||||||
|
*/
|
||||||
|
class Graph3DErrorBoundary extends Component<
|
||||||
|
{ children: ReactNode; onError: () => void; resetKey: number; fallback: ReactNode },
|
||||||
|
{ hasError: boolean }
|
||||||
|
> {
|
||||||
|
state = { hasError: false }
|
||||||
|
static getDerivedStateFromError() {
|
||||||
|
return { hasError: true }
|
||||||
|
}
|
||||||
|
componentDidCatch() {
|
||||||
|
// Auto-notify parent (will switch mode → '2d') чтобы next render не
|
||||||
|
// пытался монтировать 3D снова. Defer через setTimeout — синхронный
|
||||||
|
// setState из catch ломает React reconciliation.
|
||||||
|
setTimeout(() => this.props.onError(), 0)
|
||||||
|
}
|
||||||
|
componentDidUpdate(prev: { resetKey: number }) {
|
||||||
|
if (prev.resetKey !== this.props.resetKey && this.state.hasError) {
|
||||||
|
this.setState({ hasError: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) return this.props.fallback
|
||||||
|
return this.props.children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Граф связей справочников — d3-force network diagram.
|
* Граф связей справочников — d3-force network diagram.
|
||||||
*
|
*
|
||||||
@@ -65,6 +109,16 @@ function colorForBundle(bundle: string): string {
|
|||||||
export function DictionaryGraph() {
|
export function DictionaryGraph() {
|
||||||
const dictsQuery = useDictionaries()
|
const dictsQuery = useDictionaries()
|
||||||
const dicts = dictsQuery.data ?? []
|
const dicts = dictsQuery.data ?? []
|
||||||
|
// localStorage persist чтобы preference сохранился между переходами.
|
||||||
|
// SSR-safe guard на window — vitest setup использует jsdom где window есть,
|
||||||
|
// но defensive чтобы не упасть на SSR в будущем.
|
||||||
|
const [mode, setMode] = useState<GraphMode>(() => {
|
||||||
|
if (typeof window === 'undefined') return '2d'
|
||||||
|
return (localStorage.getItem('graph.mode') as GraphMode) === '3d' ? '3d' : '2d'
|
||||||
|
})
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window !== 'undefined') localStorage.setItem('graph.mode', mode)
|
||||||
|
}, [mode])
|
||||||
|
|
||||||
// Fetch dependents для всех dicts параллельно (cached между mounts).
|
// Fetch dependents для всех dicts параллельно (cached между mounts).
|
||||||
const dependentsResults = useQueries({
|
const dependentsResults = useQueries({
|
||||||
@@ -101,6 +155,8 @@ export function DictionaryGraph() {
|
|||||||
dicts={dicts}
|
dicts={dicts}
|
||||||
dependentsByDict={dependentsByDict}
|
dependentsByDict={dependentsByDict}
|
||||||
depsLoading={allDependentsLoading}
|
depsLoading={allDependentsLoading}
|
||||||
|
mode={mode}
|
||||||
|
onModeChange={setMode}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -109,11 +165,18 @@ function GraphCanvas({
|
|||||||
dicts,
|
dicts,
|
||||||
dependentsByDict,
|
dependentsByDict,
|
||||||
depsLoading,
|
depsLoading,
|
||||||
|
mode,
|
||||||
|
onModeChange,
|
||||||
}: {
|
}: {
|
||||||
dicts: DictionaryDefinition[]
|
dicts: DictionaryDefinition[]
|
||||||
dependentsByDict: Map<string, SchemaDependent[]>
|
dependentsByDict: Map<string, SchemaDependent[]>
|
||||||
depsLoading: boolean
|
depsLoading: boolean
|
||||||
|
mode: GraphMode
|
||||||
|
onModeChange: (m: GraphMode) => void
|
||||||
}) {
|
}) {
|
||||||
|
// WebGL-fallback banner — показывается несколько секунд после авто-возврата
|
||||||
|
// в 2D из-за ошибки 3D renderer'а (нет WebGL / hardware accel выключено).
|
||||||
|
const [webglFallbackNotice, setWebglFallbackNotice] = useState(false)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null)
|
const wrapperRef = useRef<HTMLDivElement>(null)
|
||||||
const svgRef = useRef<SVGSVGElement>(null)
|
const svgRef = useRef<SVGSVGElement>(null)
|
||||||
@@ -230,45 +293,128 @@ function GraphCanvas({
|
|||||||
Загрузка связей…
|
Загрузка связей…
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Zoom controls — overlay top-right. */}
|
{/* Controls overlay — top-right. Stack из 2D/3D toggle + zoom (только в 2D). */}
|
||||||
<div className="absolute top-2 right-2 z-10 inline-flex items-center gap-1 rounded-md border border-line bg-surface/95 backdrop-blur p-0.5 shadow-sm">
|
<div className="absolute top-2 right-2 z-10 flex flex-col items-end gap-1">
|
||||||
<button
|
{/* 2D / 3D mode toggle */}
|
||||||
type="button"
|
<div className="inline-flex items-center rounded-md border border-line bg-surface/95 backdrop-blur p-0.5 shadow-sm">
|
||||||
onClick={() =>
|
<button
|
||||||
setView((v) => ({ ...v, zoom: Math.min(4, v.zoom * 1.25) }))
|
type="button"
|
||||||
}
|
onClick={() => onModeChange('2d')}
|
||||||
aria-label="Zoom in"
|
aria-pressed={mode === '2d'}
|
||||||
title="Zoom in (+)"
|
title="2D режим — SVG force-directed layout"
|
||||||
className="size-7 inline-flex items-center justify-center text-ink-2 hover:text-ink hover:bg-surface-2 rounded-sm transition-colors"
|
className={cn(
|
||||||
>
|
'h-7 px-2.5 inline-flex items-center justify-center text-cap font-mono uppercase tracking-wider rounded-sm transition-colors',
|
||||||
+
|
mode === '2d'
|
||||||
</button>
|
? 'bg-accent text-white'
|
||||||
<button
|
: 'text-mute hover:text-ink hover:bg-surface-2',
|
||||||
type="button"
|
)}
|
||||||
onClick={() =>
|
>
|
||||||
setView((v) => ({ ...v, zoom: Math.max(0.2, v.zoom / 1.25) }))
|
2D
|
||||||
}
|
</button>
|
||||||
aria-label="Zoom out"
|
<button
|
||||||
title="Zoom out (−)"
|
type="button"
|
||||||
className="size-7 inline-flex items-center justify-center text-ink-2 hover:text-ink hover:bg-surface-2 rounded-sm transition-colors"
|
onClick={() => onModeChange('3d')}
|
||||||
>
|
aria-pressed={mode === '3d'}
|
||||||
−
|
title="3D режим — WebGL three.js (camera orbit)"
|
||||||
</button>
|
className={cn(
|
||||||
<button
|
'h-7 px-2.5 inline-flex items-center justify-center text-cap font-mono uppercase tracking-wider rounded-sm transition-colors',
|
||||||
type="button"
|
mode === '3d'
|
||||||
onClick={() => setView({ zoom: 1, panX: 0, panY: 0 })}
|
? 'bg-accent text-white'
|
||||||
aria-label="Reset view"
|
: 'text-mute hover:text-ink hover:bg-surface-2',
|
||||||
title="Reset (1:1)"
|
)}
|
||||||
className="h-7 px-2 inline-flex items-center justify-center text-cap text-mute hover:text-ink hover:bg-surface-2 rounded-sm transition-colors"
|
>
|
||||||
>
|
3D
|
||||||
1:1
|
</button>
|
||||||
</button>
|
</div>
|
||||||
<span className="px-1 text-mono text-cap text-mute tabular-nums">
|
|
||||||
{Math.round(view.zoom * 100)}%
|
{/* Zoom controls — только в 2D, в 3D camera orbit имеет свой wheel-zoom. */}
|
||||||
</span>
|
{mode === '2d' && (
|
||||||
|
<div className="inline-flex items-center gap-1 rounded-md border border-line bg-surface/95 backdrop-blur p-0.5 shadow-sm">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setView((v) => ({ ...v, zoom: Math.min(4, v.zoom * 1.25) }))
|
||||||
|
}
|
||||||
|
aria-label="Zoom in"
|
||||||
|
title="Zoom in (+)"
|
||||||
|
className="size-7 inline-flex items-center justify-center text-ink-2 hover:text-ink hover:bg-surface-2 rounded-sm transition-colors"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setView((v) => ({ ...v, zoom: Math.max(0.2, v.zoom / 1.25) }))
|
||||||
|
}
|
||||||
|
aria-label="Zoom out"
|
||||||
|
title="Zoom out (−)"
|
||||||
|
className="size-7 inline-flex items-center justify-center text-ink-2 hover:text-ink hover:bg-surface-2 rounded-sm transition-colors"
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setView({ zoom: 1, panX: 0, panY: 0 })}
|
||||||
|
aria-label="Reset view"
|
||||||
|
title="Reset (1:1)"
|
||||||
|
className="h-7 px-2 inline-flex items-center justify-center text-cap text-mute hover:text-ink hover:bg-surface-2 rounded-sm transition-colors"
|
||||||
|
>
|
||||||
|
1:1
|
||||||
|
</button>
|
||||||
|
<span className="px-1 text-mono text-cap text-mute tabular-nums">
|
||||||
|
{Math.round(view.zoom * 100)}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<svg
|
{mode === '3d' && (
|
||||||
|
<Graph3DErrorBoundary
|
||||||
|
resetKey={size.width + size.height}
|
||||||
|
onError={() => {
|
||||||
|
// WebGL undefined / hardware accel off → switch back в 2D.
|
||||||
|
// Banner показывается ~5s, потом сам исчезает.
|
||||||
|
onModeChange('2d')
|
||||||
|
setWebglFallbackNotice(true)
|
||||||
|
setTimeout(() => setWebglFallbackNotice(false), 5000)
|
||||||
|
}}
|
||||||
|
fallback={
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center p-6">
|
||||||
|
<div className="text-center max-w-md">
|
||||||
|
<p className="text-title-md text-ink mb-2">3D-режим недоступен</p>
|
||||||
|
<p className="text-cell text-mute">
|
||||||
|
Браузер не смог инициализировать WebGL. Возможно отключено
|
||||||
|
аппаратное ускорение (chrome://gpu) или используется среда без
|
||||||
|
поддержки 3D. Возврат к 2D-режиму…
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<LoadingBlock size="lg" label="Загрузка 3D-движка…" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DictionaryGraph3D
|
||||||
|
dicts={dicts}
|
||||||
|
dependentsByDict={dependentsByDict}
|
||||||
|
width={size.width}
|
||||||
|
height={size.height}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</Graph3DErrorBoundary>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{webglFallbackNotice && (
|
||||||
|
<div className="absolute top-3 left-1/2 -translate-x-1/2 z-20 max-w-md bg-warn/10 border border-warn text-ink-2 text-cell rounded-md px-3 py-2 shadow-sm backdrop-blur">
|
||||||
|
3D недоступен в этом браузере — переключено на 2D.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{mode === '2d' && <svg
|
||||||
ref={svgRef}
|
ref={svgRef}
|
||||||
width={size.width}
|
width={size.width}
|
||||||
height={size.height}
|
height={size.height}
|
||||||
@@ -412,7 +558,7 @@ function GraphCanvas({
|
|||||||
})}
|
})}
|
||||||
</g>
|
</g>
|
||||||
{/* === end zoom/pan transform === */}
|
{/* === end zoom/pan transform === */}
|
||||||
</svg>
|
</svg>}
|
||||||
{/* Legend */}
|
{/* Legend */}
|
||||||
<div className="absolute bottom-3 left-3 flex flex-col gap-1 bg-surface/90 backdrop-blur px-3 py-2 rounded-md border border-line text-cell">
|
<div className="absolute bottom-3 left-3 flex flex-col gap-1 bg-surface/90 backdrop-blur px-3 py-2 rounded-md border border-line text-cell">
|
||||||
<div className="font-display uppercase tracking-wider text-mute mb-0.5">Bundle</div>
|
<div className="font-display uppercase tracking-wider text-mute mb-0.5">Bundle</div>
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
import { useNavigate } from '@tanstack/react-router'
|
||||||
|
import ForceGraph3D, { type ForceGraphMethods } from 'react-force-graph-3d'
|
||||||
|
import type { DictionaryDefinition, SchemaDependent } from '@/api/client'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3D-режим графа справочников через react-force-graph-3d (three.js + d3-force-3d).
|
||||||
|
*
|
||||||
|
* Этот компонент динамически импортируется из DictionaryGraph, чтобы three.js
|
||||||
|
* (~650kb minified) не попадал в default chunk. Lazy-loaded только когда
|
||||||
|
* пользователь явно переключает в 3D режим.
|
||||||
|
*
|
||||||
|
* Поведение:
|
||||||
|
* - Те же nodes/links что в 2D-моде (общая data shape)
|
||||||
|
* - Camera orbit controls (drag = rotate, wheel = zoom)
|
||||||
|
* - Hover → tooltip с displayName + record count
|
||||||
|
* - Click → navigate /dictionaries/$name
|
||||||
|
* - Цвета и размеры узлов идентичны 2D (single source of truth)
|
||||||
|
* - Directional particles на edges — лёгкая визуализация направления FK
|
||||||
|
*
|
||||||
|
* Performance: 3D-граф handles 200+ nodes легко (GPU rendering), но default
|
||||||
|
* configuration уже good enough для current ~37 dicts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Node3D = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
displayName: string
|
||||||
|
bundle: string
|
||||||
|
scope: string
|
||||||
|
recordCount: number
|
||||||
|
// Internal three coords — populated by force engine
|
||||||
|
x?: number
|
||||||
|
y?: number
|
||||||
|
z?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type Link3D = {
|
||||||
|
source: string | Node3D
|
||||||
|
target: string | Node3D
|
||||||
|
sourceField: string
|
||||||
|
targetField: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const BUNDLE_COLORS: Record<string, string> = {
|
||||||
|
cuod: '#b05a2e',
|
||||||
|
geo: '#4f7038',
|
||||||
|
comms: '#a8762a',
|
||||||
|
delivery: '#3a2818',
|
||||||
|
default: '#52483d',
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorForBundle = (bundle: string): string =>
|
||||||
|
BUNDLE_COLORS[bundle] ?? BUNDLE_COLORS.default
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
dicts: DictionaryDefinition[]
|
||||||
|
dependentsByDict: Map<string, SchemaDependent[]>
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DictionaryGraph3D({
|
||||||
|
dicts,
|
||||||
|
dependentsByDict,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}: Props) {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const graphRef = useRef<ForceGraphMethods<Node3D, Link3D> | undefined>(undefined)
|
||||||
|
const [hoverId, setHoverId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
// Build graph data — same logic as 2D mode, без simulation refs (three
|
||||||
|
// worker handles physics).
|
||||||
|
const graphData = useMemo(() => {
|
||||||
|
const nodes: Node3D[] = dicts.map((d) => ({
|
||||||
|
id: d.name,
|
||||||
|
name: d.name,
|
||||||
|
displayName: d.displayName ?? d.name,
|
||||||
|
bundle: d.bundle,
|
||||||
|
scope: d.scope,
|
||||||
|
recordCount: d.recordCount ?? 0,
|
||||||
|
}))
|
||||||
|
const nodeIds = new Set(nodes.map((n) => n.id))
|
||||||
|
const links: Link3D[] = []
|
||||||
|
dicts.forEach((target) => {
|
||||||
|
const deps = dependentsByDict.get(target.name) ?? []
|
||||||
|
deps.forEach((dep) => {
|
||||||
|
if (nodeIds.has(dep.sourceDict)) {
|
||||||
|
links.push({
|
||||||
|
source: dep.sourceDict,
|
||||||
|
target: target.name,
|
||||||
|
sourceField: dep.sourceField,
|
||||||
|
targetField: dep.targetField,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return { nodes, links }
|
||||||
|
}, [dicts, dependentsByDict])
|
||||||
|
|
||||||
|
// Tune camera + force layout once after mount.
|
||||||
|
useEffect(() => {
|
||||||
|
const fg = graphRef.current
|
||||||
|
if (!fg) return
|
||||||
|
// Подальше отвести камеру для просторного 3D-обзора.
|
||||||
|
fg.cameraPosition({ x: 0, y: 0, z: 400 })
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// CSS variable resolution — three не понимает var(--color-*),
|
||||||
|
// grab raw hex values from document root для accent + ink.
|
||||||
|
const accentHex = '#b05a2e'
|
||||||
|
const lineHex = '#e6e1d4'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full h-full">
|
||||||
|
<ForceGraph3D<Node3D, Link3D>
|
||||||
|
ref={graphRef}
|
||||||
|
graphData={graphData}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
backgroundColor="rgba(0,0,0,0)"
|
||||||
|
nodeId="id"
|
||||||
|
nodeLabel={(n) =>
|
||||||
|
`<div style="background:#fff;color:#1a1714;padding:6px 10px;border-radius:4px;border:1px solid ${lineHex};font:13px ui-sans-serif"><strong>${n.displayName}</strong><br/><span style="color:#666;font-size:11px">${n.bundle} · ${n.recordCount} записей</span></div>`
|
||||||
|
}
|
||||||
|
nodeColor={(n) => (hoverId === n.id ? accentHex : colorForBundle(n.bundle))}
|
||||||
|
nodeVal={(n) => 1 + Math.log10((n.recordCount ?? 1) + 1) * 2}
|
||||||
|
nodeOpacity={hoverId ? 0.85 : 0.95}
|
||||||
|
nodeResolution={16}
|
||||||
|
linkColor={(l) => {
|
||||||
|
const sid = typeof l.source === 'string' ? l.source : (l.source as Node3D).id
|
||||||
|
const tid = typeof l.target === 'string' ? l.target : (l.target as Node3D).id
|
||||||
|
return hoverId && (sid === hoverId || tid === hoverId) ? accentHex : lineHex
|
||||||
|
}}
|
||||||
|
linkOpacity={hoverId ? 0.4 : 0.5}
|
||||||
|
linkWidth={(l) => {
|
||||||
|
const sid = typeof l.source === 'string' ? l.source : (l.source as Node3D).id
|
||||||
|
const tid = typeof l.target === 'string' ? l.target : (l.target as Node3D).id
|
||||||
|
return hoverId && (sid === hoverId || tid === hoverId) ? 1.5 : 0.5
|
||||||
|
}}
|
||||||
|
linkDirectionalArrowLength={3}
|
||||||
|
linkDirectionalArrowRelPos={1}
|
||||||
|
linkDirectionalArrowColor={() => accentHex}
|
||||||
|
linkDirectionalParticles={(l) => {
|
||||||
|
const sid = typeof l.source === 'string' ? l.source : (l.source as Node3D).id
|
||||||
|
const tid = typeof l.target === 'string' ? l.target : (l.target as Node3D).id
|
||||||
|
return hoverId && (sid === hoverId || tid === hoverId) ? 2 : 0
|
||||||
|
}}
|
||||||
|
linkDirectionalParticleSpeed={0.006}
|
||||||
|
linkDirectionalParticleWidth={2}
|
||||||
|
onNodeHover={(n) => setHoverId(n?.id ?? null)}
|
||||||
|
onNodeClick={(n) => navigate({ to: '/dictionaries/$name', params: { name: n.name } })}
|
||||||
|
// Slight cooldown extension чтобы layout успел осесть.
|
||||||
|
cooldownTicks={120}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -129,6 +129,20 @@ export default defineConfig({
|
|||||||
// Axios + парсеры
|
// Axios + парсеры
|
||||||
if (id.match(/node_modules\/(axios|jose|crypto-js)\//)) return 'vendor-net'
|
if (id.match(/node_modules\/(axios|jose|crypto-js)\//)) return 'vendor-net'
|
||||||
|
|
||||||
|
// 3D-graph (three.js + react-force-graph-3d + transitive deps).
|
||||||
|
// Изолирован в свой chunk, чтобы НЕ попасть в vendor-misc, который
|
||||||
|
// загружается eagerly. DictionaryGraph3D импортируется через
|
||||||
|
// React.lazy() — Rollup auto-defers этот chunk до первого
|
||||||
|
// дин-импорта. ~500kb gzipped (~600kb three.js + force graph
|
||||||
|
// wrappers), грузится только при переключении в 3D режим.
|
||||||
|
if (
|
||||||
|
id.match(
|
||||||
|
/node_modules\/(three|3d-force-graph|d3-force-3d|kapsule|react-kapsule|three-forcegraph|three-render-objects|react-force-graph-3d|@tweenjs)/,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return 'vendor-graph3d'
|
||||||
|
}
|
||||||
|
|
||||||
// Остальное — в общий vendor (не должно быть много)
|
// Остальное — в общий vendor (не должно быть много)
|
||||||
return 'vendor-misc'
|
return 'vendor-misc'
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user