diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 461a717..a72ef67 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,9 +8,12 @@ "name": "orchard-frontend", "version": "1.0.0", "dependencies": { + "@types/dagre": "^0.7.53", + "dagre": "^0.8.5", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "6.28.0" + "react-router-dom": "6.28.0", + "reactflow": "^11.11.4" }, "devDependencies": { "@testing-library/jest-dom": "^6.4.2", @@ -943,6 +946,102 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@reactflow/background": { + "version": "11.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz", + "integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/controls": { + "version": "11.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz", + "integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/core": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz", + "integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==", + "dependencies": { + "@types/d3": "^7.4.0", + "@types/d3-drag": "^3.0.1", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/minimap": { + "version": "11.7.14", + "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz", + "integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==", + "dependencies": { + "@reactflow/core": "11.11.4", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-resizer": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz", + "integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.4", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-toolbar": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz", + "integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, "node_modules/@remix-run/router": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", @@ -1437,6 +1536,233 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/dagre": { + "version": "0.7.53", + "resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.53.tgz", + "integrity": "sha512-f4gkWqzPZvYmKhOsDnhq/R8mO4UMcKdxZo+i5SCkOU1wvGeHJeUXGIHeE9pnwGyPMDof1Vx5ZQo4nxpeg2TTVQ==" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1444,18 +1770,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==" + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.27", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1963,6 +2294,11 @@ "node": "*" } }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2063,9 +2399,114 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, "node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", @@ -2592,6 +3033,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "dependencies": { + "lodash": "^4.17.15" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -3197,6 +3646,11 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3786,6 +4240,23 @@ "react-dom": ">=16.8" } }, + "node_modules/reactflow": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz", + "integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==", + "dependencies": { + "@reactflow/background": "11.3.14", + "@reactflow/controls": "11.2.14", + "@reactflow/core": "11.11.4", + "@reactflow/minimap": "11.7.14", + "@reactflow/node-resizer": "2.2.14", + "@reactflow/node-toolbar": "1.3.14" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -4344,6 +4815,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", @@ -4712,6 +5191,33 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/frontend/package.json b/frontend/package.json index 984b229..3e95a7b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,9 +12,12 @@ "test:coverage": "vitest run --coverage" }, "dependencies": { + "@types/dagre": "^0.7.53", + "dagre": "^0.8.5", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "6.28.0" + "react-router-dom": "6.28.0", + "reactflow": "^11.11.4" }, "devDependencies": { "@testing-library/jest-dom": "^6.4.2", diff --git a/frontend/src/components/DependencyGraph.css b/frontend/src/components/DependencyGraph.css index 9665934..828fb4d 100644 --- a/frontend/src/components/DependencyGraph.css +++ b/frontend/src/components/DependencyGraph.css @@ -76,171 +76,115 @@ color: var(--text-primary); } -.dependency-graph-toolbar { - display: flex; - align-items: center; - gap: 8px; - padding: 12px 20px; - border-bottom: 1px solid var(--border-primary); - background: var(--bg-secondary); -} - -.zoom-level { - margin-left: auto; - font-size: 0.8125rem; - color: var(--text-muted); - font-family: 'JetBrains Mono', monospace; -} - .dependency-graph-container { flex: 1; overflow: hidden; position: relative; - background: - linear-gradient(90deg, var(--border-primary) 1px, transparent 1px), - linear-gradient(var(--border-primary) 1px, transparent 1px); - background-size: 20px 20px; - background-position: center center; + background: var(--bg-primary); } -.graph-canvas { - padding: 40px; - min-width: 100%; - min-height: 100%; - transform-origin: center center; - transition: transform 0.1s ease-out; +/* React Flow Customization */ +.react-flow__background { + background-color: var(--bg-primary) !important; } -/* Graph Nodes */ -.graph-node-container { - display: flex; - flex-direction: column; - align-items: flex-start; +.react-flow__controls { + background: var(--bg-tertiary); + border: 1px solid var(--border-primary); + border-radius: var(--radius-md); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } -.graph-node { +.react-flow__controls-button { + background: var(--bg-tertiary); + border: none; + border-bottom: 1px solid var(--border-primary); + color: var(--text-secondary); + width: 28px; + height: 28px; +} + +.react-flow__controls-button:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.react-flow__controls-button:last-child { + border-bottom: none; +} + +.react-flow__controls-button svg { + fill: currentColor; +} + +.react-flow__attribution { + background: transparent !important; +} + +.react-flow__attribution a { + color: var(--text-muted) !important; + font-size: 10px; +} + +/* Custom Flow Nodes */ +.flow-node { background: var(--bg-tertiary); border: 2px solid var(--border-primary); border-radius: var(--radius-md); padding: 12px 16px; - min-width: 200px; + min-width: 160px; cursor: pointer; transition: all var(--transition-fast); - position: relative; + text-align: center; } -.graph-node:hover { +.flow-node:hover { border-color: var(--accent-primary); box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2); } -.graph-node--root { +.flow-node--root { background: linear-gradient(135deg, rgba(16, 185, 129, 0.15) 0%, rgba(5, 150, 105, 0.15) 100%); border-color: var(--accent-primary); } -.graph-node--hovered { - transform: scale(1.02); -} - -.graph-node__header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 4px; -} - -.graph-node__name { +.flow-node__name { font-weight: 600; color: var(--accent-primary); font-family: 'JetBrains Mono', monospace; - font-size: 0.875rem; + font-size: 0.8125rem; + margin-bottom: 4px; + word-break: break-word; } -.graph-node__toggle { - background: var(--bg-hover); - border: 1px solid var(--border-primary); - border-radius: 4px; - width: 20px; - height: 20px; +.flow-node__details { display: flex; align-items: center; justify-content: center; - cursor: pointer; - font-size: 0.875rem; - color: var(--text-secondary); - font-weight: 600; - margin-left: auto; -} - -.graph-node__toggle:hover { - background: var(--bg-tertiary); - color: var(--text-primary); -} - -.graph-node__details { - display: flex; - align-items: center; - gap: 12px; - font-size: 0.75rem; + gap: 8px; + font-size: 0.6875rem; color: var(--text-muted); } -.graph-node__version { +.flow-node__version { font-family: 'JetBrains Mono', monospace; color: var(--text-secondary); } -.graph-node__size { +.flow-node__size { color: var(--text-muted); } -/* Graph Children / Tree Structure */ -.graph-children { - display: flex; - padding-left: 24px; - margin-top: 8px; - position: relative; +/* Flow Handles (connection points) */ +.flow-handle { + width: 8px !important; + height: 8px !important; + background: var(--border-primary) !important; + border: 2px solid var(--bg-tertiary) !important; } -.graph-connector { - position: absolute; - left: 12px; - top: 0; - bottom: 50%; - width: 12px; - border-left: 2px solid var(--border-primary); - border-bottom: 2px solid var(--border-primary); - border-bottom-left-radius: 8px; -} - -.graph-children-list { - display: flex; - flex-direction: column; - gap: 8px; - position: relative; -} - -.graph-children-list::before { - content: ''; - position: absolute; - left: -12px; - top: 20px; - bottom: 20px; - border-left: 2px solid var(--border-primary); -} - -.graph-children-list > .graph-node-container { - position: relative; -} - -.graph-children-list > .graph-node-container::before { - content: ''; - position: absolute; - left: -12px; - top: 20px; - width: 12px; - border-top: 2px solid var(--border-primary); +.flow-node:hover .flow-handle { + background: var(--accent-primary) !important; } /* Loading, Error, Empty States */ @@ -283,41 +227,6 @@ line-height: 1.5; } -/* Tooltip */ -.graph-tooltip { - position: fixed; - bottom: 24px; - left: 50%; - transform: translateX(-50%); - background: var(--bg-tertiary); - border: 1px solid var(--border-primary); - border-radius: var(--radius-md); - padding: 12px 16px; - font-size: 0.8125rem; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); - z-index: 1001; -} - -.graph-tooltip strong { - display: block; - color: var(--accent-primary); - font-family: 'JetBrains Mono', monospace; - margin-bottom: 4px; -} - -.graph-tooltip div { - color: var(--text-secondary); - margin-top: 2px; -} - -.tooltip-hint { - margin-top: 8px; - padding-top: 8px; - border-top: 1px solid var(--border-primary); - color: var(--text-muted); - font-size: 0.75rem; -} - /* Missing Dependencies */ .missing-dependencies { border-top: 1px solid var(--border-primary); diff --git a/frontend/src/components/DependencyGraph.tsx b/frontend/src/components/DependencyGraph.tsx index c133eb8..487e942 100644 --- a/frontend/src/components/DependencyGraph.tsx +++ b/frontend/src/components/DependencyGraph.tsx @@ -1,5 +1,19 @@ -import { useState, useEffect, useCallback, useRef } from 'react'; +import { useState, useEffect, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; +import ReactFlow, { + Node, + Edge, + Controls, + Background, + useNodesState, + useEdgesState, + MarkerType, + NodeProps, + Handle, + Position, +} from 'reactflow'; +import dagre from 'dagre'; +import 'reactflow/dist/style.css'; import { ResolvedArtifact, DependencyResolutionResponse, Dependency } from '../types'; import { resolveDependencies, getArtifactDependencies } from '../api'; import './DependencyGraph.css'; @@ -11,15 +25,14 @@ interface DependencyGraphProps { onClose: () => void; } -interface GraphNode { - id: string; +interface NodeData { + label: string; project: string; package: string; version: string | null; size: number; - depth: number; - children: GraphNode[]; - isRoot?: boolean; + isRoot: boolean; + onNavigate: (project: string, pkg: string) => void; } function formatBytes(bytes: number): string { @@ -30,29 +43,89 @@ function formatBytes(bytes: number): string { return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } +// Custom node component +function DependencyNode({ data }: NodeProps) { + return ( +
data.onNavigate(data.project, data.package)} + > + +
{data.package}
+
+ {data.version && {data.version}} + {formatBytes(data.size)} +
+ +
+ ); +} + +const nodeTypes = { dependency: DependencyNode }; + +// Dagre layout function +function getLayoutedElements( + nodes: Node[], + edges: Edge[], + direction: 'TB' | 'LR' = 'TB' +) { + const dagreGraph = new dagre.graphlib.Graph(); + dagreGraph.setDefaultEdgeLabel(() => ({})); + + const nodeWidth = 180; + const nodeHeight = 60; + + dagreGraph.setGraph({ rankdir: direction, nodesep: 50, ranksep: 80 }); + + nodes.forEach((node) => { + dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight }); + }); + + edges.forEach((edge) => { + dagreGraph.setEdge(edge.source, edge.target); + }); + + dagre.layout(dagreGraph); + + const layoutedNodes = nodes.map((node) => { + const nodeWithPosition = dagreGraph.node(node.id); + return { + ...node, + position: { + x: nodeWithPosition.x - nodeWidth / 2, + y: nodeWithPosition.y - nodeHeight / 2, + }, + }; + }); + + return { nodes: layoutedNodes, edges }; +} + function DependencyGraph({ projectName, packageName, tagName, onClose }: DependencyGraphProps) { const navigate = useNavigate(); - const containerRef = useRef(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [resolution, setResolution] = useState(null); - const [graphRoot, setGraphRoot] = useState(null); - const [hoveredNode, setHoveredNode] = useState(null); - const [zoom, setZoom] = useState(1); - const [pan, setPan] = useState({ x: 0, y: 0 }); - const [isDragging, setIsDragging] = useState(false); - const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); - const [collapsedNodes, setCollapsedNodes] = useState>(new Set()); + const [nodes, setNodes, onNodesChange] = useNodesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState([]); + + const handleNavigate = useCallback((project: string, pkg: string) => { + navigate(`/project/${project}/${pkg}`); + onClose(); + }, [navigate, onClose]); // Build graph structure from resolution data - const buildGraph = useCallback(async (resolutionData: DependencyResolutionResponse) => { + const buildFlowGraph = useCallback(async ( + resolutionData: DependencyResolutionResponse, + onNavigate: (project: string, pkg: string) => void + ) => { const artifactMap = new Map(); resolutionData.resolved.forEach(artifact => { artifactMap.set(artifact.artifact_id, artifact); }); - // Fetch dependencies for each artifact to build the tree + // Fetch dependencies for each artifact const depsMap = new Map(); for (const artifact of resolutionData.resolved) { @@ -64,50 +137,82 @@ function DependencyGraph({ projectName, packageName, tagName, onClose }: Depende } } - // Find the root artifact (the requested one) + // Find the root artifact const rootArtifact = resolutionData.resolved.find( a => a.project === resolutionData.requested.project && a.package === resolutionData.requested.package ); if (!rootArtifact) { - return null; + return { nodes: [], edges: [] }; } - // Build tree recursively + const flowNodes: Node[] = []; + const flowEdges: Edge[] = []; const visited = new Set(); + const nodeIdMap = new Map(); // artifact_id -> node id + + // Build nodes and edges recursively + const processNode = (artifact: ResolvedArtifact, isRoot: boolean) => { + if (visited.has(artifact.artifact_id)) { + return nodeIdMap.get(artifact.artifact_id); + } - const buildNode = (artifact: ResolvedArtifact, depth: number): GraphNode => { - const nodeId = `${artifact.project}/${artifact.package}`; visited.add(artifact.artifact_id); + const nodeId = `node-${flowNodes.length}`; + nodeIdMap.set(artifact.artifact_id, nodeId); + + flowNodes.push({ + id: nodeId, + type: 'dependency', + position: { x: 0, y: 0 }, // Will be set by dagre + data: { + label: `${artifact.project}/${artifact.package}`, + project: artifact.project, + package: artifact.package, + version: artifact.version || artifact.tag, + size: artifact.size, + isRoot, + onNavigate, + }, + }); const deps = depsMap.get(artifact.artifact_id) || []; - const children: GraphNode[] = []; for (const dep of deps) { - // Find the resolved artifact for this dependency const childArtifact = resolutionData.resolved.find( a => a.project === dep.project && a.package === dep.package ); - if (childArtifact && !visited.has(childArtifact.artifact_id)) { - children.push(buildNode(childArtifact, depth + 1)); + if (childArtifact) { + const childNodeId = processNode(childArtifact, false); + if (childNodeId) { + flowEdges.push({ + id: `edge-${nodeId}-${childNodeId}`, + source: nodeId, + target: childNodeId, + markerEnd: { + type: MarkerType.ArrowClosed, + width: 15, + height: 15, + color: 'var(--accent-primary)', + }, + style: { + stroke: 'var(--border-primary)', + strokeWidth: 2, + }, + }); + } } } - return { - id: nodeId, - project: artifact.project, - package: artifact.package, - version: artifact.version || artifact.tag, - size: artifact.size, - depth, - children, - isRoot: depth === 0, - }; + return nodeId; }; - return buildNode(rootArtifact, 0); + processNode(rootArtifact, true); + + // Apply dagre layout + return getLayoutedElements(flowNodes, flowEdges); }, []); useEffect(() => { @@ -127,11 +232,11 @@ function DependencyGraph({ projectName, packageName, tagName, onClose }: Depende setResolution(result); - const graph = await buildGraph(result); - setGraphRoot(graph); + const { nodes: layoutedNodes, edges: layoutedEdges } = await buildFlowGraph(result, handleNavigate); + setNodes(layoutedNodes); + setEdges(layoutedEdges); } catch (err) { if (err instanceof Error) { - // Check if it's a resolution error try { const errorData = JSON.parse(err.message); if (errorData.error === 'circular_dependency') { @@ -153,101 +258,15 @@ function DependencyGraph({ projectName, packageName, tagName, onClose }: Depende } loadData(); - }, [projectName, packageName, tagName, buildGraph]); + }, [projectName, packageName, tagName, buildFlowGraph, handleNavigate, onClose, setNodes, setEdges]); - const handleNodeClick = (node: GraphNode) => { - navigate(`/project/${node.project}/${node.package}`); - onClose(); - }; - - const handleNodeToggle = (node: GraphNode, e: React.MouseEvent) => { - e.stopPropagation(); - setCollapsedNodes(prev => { - const next = new Set(prev); - if (next.has(node.id)) { - next.delete(node.id); - } else { - next.add(node.id); - } - return next; - }); - }; - - const handleWheel = (e: React.WheelEvent) => { - e.preventDefault(); - const delta = e.deltaY > 0 ? -0.1 : 0.1; - setZoom(z => Math.max(0.25, Math.min(2, z + delta))); - }; - - const handleMouseDown = (e: React.MouseEvent) => { - if (e.target === containerRef.current || (e.target as HTMLElement).classList.contains('graph-canvas')) { - setIsDragging(true); - setDragStart({ x: e.clientX - pan.x, y: e.clientY - pan.y }); - } - }; - - const handleMouseMove = (e: React.MouseEvent) => { - if (isDragging) { - setPan({ x: e.clientX - dragStart.x, y: e.clientY - dragStart.y }); - } - }; - - const handleMouseUp = () => { - setIsDragging(false); - }; - - const resetView = () => { - setZoom(1); - setPan({ x: 0, y: 0 }); - }; - - const renderNode = (node: GraphNode, index: number = 0): JSX.Element => { - const isCollapsed = collapsedNodes.has(node.id); - const hasChildren = node.children.length > 0; - - return ( -
-
handleNodeClick(node)} - onMouseEnter={() => setHoveredNode(node)} - onMouseLeave={() => setHoveredNode(null)} - > -
- {node.project}/{node.package} - {hasChildren && ( - - )} -
-
- {node.version && @ {node.version}} - {formatBytes(node.size)} -
-
- - {hasChildren && !isCollapsed && ( -
-
-
- {node.children.map((child, i) => renderNode(child, i))} -
-
- )} -
- ); - }; + const defaultViewport = useMemo(() => ({ x: 50, y: 50, zoom: 0.8 }), []); return (
e.stopPropagation()}>
-

DependGraph

+

Dependency Graph

{projectName}/{packageName} @ {tagName} {resolution && ( @@ -268,28 +287,7 @@ function DependencyGraph({ projectName, packageName, tagName, onClose }: Depende
-
- - - - {Math.round(zoom * 100)}% -
- -
+
{loading ? (
@@ -304,16 +302,23 @@ function DependencyGraph({ projectName, packageName, tagName, onClose }: Depende

{error}

- ) : graphRoot ? ( -
0 ? ( + - {renderNode(graphRoot)} -
+ + + ) : (
No dependencies to display
)} @@ -334,15 +339,6 @@ function DependencyGraph({ projectName, packageName, tagName, onClose }: Depende
)} - - {hoveredNode && ( -
- {hoveredNode.project}/{hoveredNode.package} - {hoveredNode.version &&
Version: {hoveredNode.version}
} -
Size: {formatBytes(hoveredNode.size)}
-
Click to navigate
-
- )}
);