@aiready/visualizer 0.1.36 → 0.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +46 -17
- package/dist/cli.js.map +1 -1
- package/dist/graph/index.js +36 -13
- package/dist/graph/index.js.map +1 -1
- package/dist/index.js +36 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/web/dist/assets/{index-C9b__qGk.js → index-5leRjvmV.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/report-data.json +224 -742
- package/web/index.html +1 -1
- package/web/package.json +1 -1
- package/web/public/report-data.json +224 -742
- package/web/src/App.tsx +61 -47
- package/web/src/components/ErrorDisplay.tsx +31 -17
- package/web/src/components/GraphCanvas.tsx +90 -24
- package/web/src/components/LegendPanel.tsx +242 -139
- package/web/src/components/LoadingSpinner.tsx +6 -3
- package/web/src/components/Navbar.tsx +66 -52
- package/web/src/components/NodeDetails.tsx +98 -68
- package/web/src/hooks/useTheme.ts +5 -2
- package/web/src/main.tsx +1 -1
- package/web/src/style.css +14 -2
- package/web/src/styles/index.css +7 -5
- package/web/src/utils.ts +86 -25
- package/web/tsconfig.json +1 -1
- package/web/vite.config.ts +9 -4
package/web/src/App.tsx
CHANGED
|
@@ -24,12 +24,15 @@ function App() {
|
|
|
24
24
|
const [selectedNode, setSelectedNode] = useState<FileNode | null>(null);
|
|
25
25
|
const [loading, setLoading] = useState(true);
|
|
26
26
|
const [error, setError] = useState<string | null>(null);
|
|
27
|
-
const [truncatedWarning, setTruncatedWarning] = useState<{
|
|
27
|
+
const [truncatedWarning, setTruncatedWarning] = useState<{
|
|
28
|
+
nodes: boolean;
|
|
29
|
+
edges: boolean;
|
|
30
|
+
} | null>(null);
|
|
28
31
|
|
|
29
32
|
// Filter state - start with all visible
|
|
30
|
-
const [visibleSeverities, setVisibleSeverities] = useState<
|
|
31
|
-
|
|
32
|
-
);
|
|
33
|
+
const [visibleSeverities, setVisibleSeverities] = useState<
|
|
34
|
+
Set<SeverityLevel>
|
|
35
|
+
>(new Set(ALL_SEVERITIES));
|
|
33
36
|
const [visibleEdgeTypes, setVisibleEdgeTypes] = useState<Set<EdgeType>>(
|
|
34
37
|
new Set(ALL_EDGE_TYPES)
|
|
35
38
|
);
|
|
@@ -56,7 +59,7 @@ function App() {
|
|
|
56
59
|
const visualizerConfig = (reportData as any).visualizerConfig;
|
|
57
60
|
const graphData = transformReportToGraph(reportData, visualizerConfig);
|
|
58
61
|
setData(graphData);
|
|
59
|
-
|
|
62
|
+
|
|
60
63
|
// Show warning if graph was truncated
|
|
61
64
|
if (graphData.truncated?.nodes || graphData.truncated?.edges) {
|
|
62
65
|
setTruncatedWarning({
|
|
@@ -64,7 +67,7 @@ function App() {
|
|
|
64
67
|
edges: graphData.truncated.edges,
|
|
65
68
|
});
|
|
66
69
|
}
|
|
67
|
-
|
|
70
|
+
|
|
68
71
|
setLoading(false);
|
|
69
72
|
};
|
|
70
73
|
|
|
@@ -73,7 +76,7 @@ function App() {
|
|
|
73
76
|
|
|
74
77
|
// Toggle severity visibility
|
|
75
78
|
const handleToggleSeverity = (severity: SeverityLevel) => {
|
|
76
|
-
setVisibleSeverities(prev => {
|
|
79
|
+
setVisibleSeverities((prev) => {
|
|
77
80
|
const next = new Set(prev);
|
|
78
81
|
if (next.has(severity)) {
|
|
79
82
|
next.delete(severity);
|
|
@@ -86,7 +89,7 @@ function App() {
|
|
|
86
89
|
|
|
87
90
|
// Toggle edge type visibility
|
|
88
91
|
const handleToggleEdgeType = (edgeType: EdgeType) => {
|
|
89
|
-
setVisibleEdgeTypes(prev => {
|
|
92
|
+
setVisibleEdgeTypes((prev) => {
|
|
90
93
|
const next = new Set(prev);
|
|
91
94
|
if (next.has(edgeType)) {
|
|
92
95
|
next.delete(edgeType);
|
|
@@ -105,15 +108,15 @@ function App() {
|
|
|
105
108
|
// Get set of visible node IDs
|
|
106
109
|
const visibleNodeIds = new Set(
|
|
107
110
|
data.nodes
|
|
108
|
-
.filter(node => {
|
|
111
|
+
.filter((node) => {
|
|
109
112
|
const severity = (node.severity || 'default') as SeverityLevel;
|
|
110
113
|
return visibleSeverities.has(severity);
|
|
111
114
|
})
|
|
112
|
-
.map(node => node.id)
|
|
115
|
+
.map((node) => node.id)
|
|
113
116
|
);
|
|
114
117
|
|
|
115
118
|
// Filter nodes: keep if severity is visible
|
|
116
|
-
const filteredNodes = data.nodes.filter(node => {
|
|
119
|
+
const filteredNodes = data.nodes.filter((node) => {
|
|
117
120
|
const severity = (node.severity || 'default') as SeverityLevel;
|
|
118
121
|
return visibleSeverities.has(severity);
|
|
119
122
|
});
|
|
@@ -121,15 +124,17 @@ function App() {
|
|
|
121
124
|
// Filter edges: keep if:
|
|
122
125
|
// 1. Edge type is visible AND
|
|
123
126
|
// 2. Both source and target nodes are visible
|
|
124
|
-
const filteredEdges = data.edges.filter(edge => {
|
|
127
|
+
const filteredEdges = data.edges.filter((edge) => {
|
|
125
128
|
// Check edge type visibility
|
|
126
129
|
const edgeType = (edge.type || 'default') as EdgeType;
|
|
127
130
|
if (!visibleEdgeTypes.has(edgeType)) {
|
|
128
131
|
return false;
|
|
129
132
|
}
|
|
130
133
|
// Check that both connected nodes are visible
|
|
131
|
-
const sourceId =
|
|
132
|
-
|
|
134
|
+
const sourceId =
|
|
135
|
+
typeof edge.source === 'string' ? edge.source : edge.source.id;
|
|
136
|
+
const targetId =
|
|
137
|
+
typeof edge.target === 'string' ? edge.target : edge.target.id;
|
|
133
138
|
return visibleNodeIds.has(sourceId) && visibleNodeIds.has(targetId);
|
|
134
139
|
});
|
|
135
140
|
|
|
@@ -163,32 +168,38 @@ function App() {
|
|
|
163
168
|
/>
|
|
164
169
|
|
|
165
170
|
{/* Truncation warning banner */}
|
|
166
|
-
{truncatedWarning &&
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
<div className="flex-1">
|
|
172
|
-
<p className="text-sm text-yellow-800 font-medium">
|
|
173
|
-
⚠️ Graph visualization truncated at display limits.
|
|
174
|
-
{truncatedWarning.nodes && ' Too many nodes.'}
|
|
175
|
-
{truncatedWarning.edges && ' Too many edges.'}
|
|
176
|
-
</p>
|
|
177
|
-
<p className="text-xs text-yellow-700 mt-1">
|
|
178
|
-
To show more data, increase graph limits in aiready.json:
|
|
179
|
-
<code className="bg-yellow-100 px-1 rounded" style={{ marginLeft: '4px' }}>
|
|
180
|
-
{'"visualizer": { "graph": { "maxNodes": 2000, "maxEdges": 5000 } }'}
|
|
181
|
-
</code>
|
|
182
|
-
</p>
|
|
183
|
-
</div>
|
|
184
|
-
<button
|
|
185
|
-
onClick={() => setTruncatedWarning(null)}
|
|
186
|
-
className="text-yellow-600 hover:text-yellow-800 ml-4 flex-shrink-0"
|
|
171
|
+
{truncatedWarning &&
|
|
172
|
+
(truncatedWarning.nodes || truncatedWarning.edges) && (
|
|
173
|
+
<div
|
|
174
|
+
className="px-4 py-3 bg-yellow-50 border-b flex items-center justify-between"
|
|
175
|
+
style={{ borderColor: colors.panelBorder }}
|
|
187
176
|
>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
177
|
+
<div className="flex-1">
|
|
178
|
+
<p className="text-sm text-yellow-800 font-medium">
|
|
179
|
+
⚠️ Graph visualization truncated at display limits.
|
|
180
|
+
{truncatedWarning.nodes && ' Too many nodes.'}
|
|
181
|
+
{truncatedWarning.edges && ' Too many edges.'}
|
|
182
|
+
</p>
|
|
183
|
+
<p className="text-xs text-yellow-700 mt-1">
|
|
184
|
+
To show more data, increase graph limits in aiready.json:
|
|
185
|
+
<code
|
|
186
|
+
className="bg-yellow-100 px-1 rounded"
|
|
187
|
+
style={{ marginLeft: '4px' }}
|
|
188
|
+
>
|
|
189
|
+
{
|
|
190
|
+
'"visualizer": { "graph": { "maxNodes": 2000, "maxEdges": 5000 } }'
|
|
191
|
+
}
|
|
192
|
+
</code>
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
<button
|
|
196
|
+
onClick={() => setTruncatedWarning(null)}
|
|
197
|
+
className="text-yellow-600 hover:text-yellow-800 ml-4 flex-shrink-0"
|
|
198
|
+
>
|
|
199
|
+
✕
|
|
200
|
+
</button>
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
192
203
|
|
|
193
204
|
<div className="flex flex-1 overflow-hidden">
|
|
194
205
|
<div ref={containerRef} className="flex-1 relative">
|
|
@@ -204,19 +215,22 @@ function App() {
|
|
|
204
215
|
</div>
|
|
205
216
|
|
|
206
217
|
{/* Right panel: Legend OR NodeDetails */}
|
|
207
|
-
<div
|
|
208
|
-
className="w-80 border-l flex flex-col h-full"
|
|
209
|
-
style={{
|
|
218
|
+
<div
|
|
219
|
+
className="w-80 border-l flex flex-col h-full"
|
|
220
|
+
style={{
|
|
221
|
+
backgroundColor: colors.panel,
|
|
222
|
+
borderColor: colors.panelBorder,
|
|
223
|
+
}}
|
|
210
224
|
>
|
|
211
225
|
{selectedNode ? (
|
|
212
|
-
<NodeDetails
|
|
213
|
-
colors={colors}
|
|
214
|
-
selectedNode={selectedNode}
|
|
215
|
-
onClose={() => setSelectedNode(null)}
|
|
226
|
+
<NodeDetails
|
|
227
|
+
colors={colors}
|
|
228
|
+
selectedNode={selectedNode}
|
|
229
|
+
onClose={() => setSelectedNode(null)}
|
|
216
230
|
/>
|
|
217
231
|
) : (
|
|
218
232
|
<div className="flex-1 overflow-y-auto">
|
|
219
|
-
<LegendPanel
|
|
233
|
+
<LegendPanel
|
|
220
234
|
colors={colors}
|
|
221
235
|
visibleSeverities={visibleSeverities}
|
|
222
236
|
visibleEdgeTypes={visibleEdgeTypes}
|
|
@@ -7,31 +7,45 @@ interface ErrorDisplayProps {
|
|
|
7
7
|
|
|
8
8
|
export function ErrorDisplay({ colors, error }: ErrorDisplayProps) {
|
|
9
9
|
return (
|
|
10
|
-
<div
|
|
10
|
+
<div
|
|
11
|
+
className="flex h-screen items-center justify-center"
|
|
12
|
+
style={{ backgroundColor: colors.bg, color: colors.text }}
|
|
13
|
+
>
|
|
11
14
|
<div className="text-center max-w-md p-6">
|
|
12
|
-
<svg
|
|
13
|
-
className="w-16 h-16 mx-auto text-amber-500 mb-4"
|
|
14
|
-
fill="none"
|
|
15
|
-
viewBox="0 0 24 24"
|
|
15
|
+
<svg
|
|
16
|
+
className="w-16 h-16 mx-auto text-amber-500 mb-4"
|
|
17
|
+
fill="none"
|
|
18
|
+
viewBox="0 0 24 24"
|
|
16
19
|
stroke="currentColor"
|
|
17
20
|
>
|
|
18
|
-
<path
|
|
19
|
-
strokeLinecap="round"
|
|
20
|
-
strokeLinejoin="round"
|
|
21
|
-
strokeWidth={2}
|
|
22
|
-
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
21
|
+
<path
|
|
22
|
+
strokeLinecap="round"
|
|
23
|
+
strokeLinejoin="round"
|
|
24
|
+
strokeWidth={2}
|
|
25
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
23
26
|
/>
|
|
24
27
|
</svg>
|
|
25
|
-
<h2 className="text-xl font-semibold mb-2 text-amber-400">
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
<h2 className="text-xl font-semibold mb-2 text-amber-400">
|
|
29
|
+
No Scan Data Found
|
|
30
|
+
</h2>
|
|
31
|
+
<p className="mb-4" style={{ color: colors.textMuted }}>
|
|
32
|
+
{error}
|
|
33
|
+
</p>
|
|
34
|
+
<div
|
|
35
|
+
className="text-left p-4 rounded-lg text-sm font-mono"
|
|
29
36
|
style={{ backgroundColor: colors.cardBg }}
|
|
30
37
|
>
|
|
31
38
|
<p className="text-cyan-400"># Step 1: Run aiready scan</p>
|
|
32
|
-
<p className="mb-2" style={{ color: colors.textMuted }}>
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
<p className="mb-2" style={{ color: colors.textMuted }}>
|
|
40
|
+
aiready scan . --output json
|
|
41
|
+
</p>
|
|
42
|
+
<p className="text-cyan-400">
|
|
43
|
+
# Step 2: Copy latest report to visualizer
|
|
44
|
+
</p>
|
|
45
|
+
<p style={{ color: colors.textMuted }}>
|
|
46
|
+
cp .aiready/aiready-report-*.json
|
|
47
|
+
packages/visualizer/web/public/report-data.json
|
|
48
|
+
</p>
|
|
35
49
|
</div>
|
|
36
50
|
</div>
|
|
37
51
|
</div>
|
|
@@ -2,7 +2,12 @@ import { useEffect, useRef } from 'react';
|
|
|
2
2
|
import * as d3 from 'd3';
|
|
3
3
|
import { FileNode, GraphData, ThemeColors, EffectiveTheme } from '../types';
|
|
4
4
|
import { severityColors, edgeColors, GRAPH_CONFIG } from '../constants';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getEdgeDistance,
|
|
7
|
+
getEdgeStrength,
|
|
8
|
+
getEdgeOpacity,
|
|
9
|
+
getEdgeStrokeWidth,
|
|
10
|
+
} from '../utils';
|
|
6
11
|
|
|
7
12
|
interface GraphCanvasProps {
|
|
8
13
|
data: GraphData;
|
|
@@ -26,7 +31,7 @@ export function GraphCanvas({
|
|
|
26
31
|
if (!data || !svgRef.current || !data.nodes.length) return;
|
|
27
32
|
|
|
28
33
|
const svg = d3.select(svgRef.current);
|
|
29
|
-
|
|
34
|
+
|
|
30
35
|
// Get actual SVG dimensions from the DOM element
|
|
31
36
|
const svgRect = svgRef.current.getBoundingClientRect();
|
|
32
37
|
const width = svgRect.width;
|
|
@@ -37,7 +42,8 @@ export function GraphCanvas({
|
|
|
37
42
|
const svgGroup = svg.append('g');
|
|
38
43
|
|
|
39
44
|
// Setup zoom
|
|
40
|
-
const zoom = d3
|
|
45
|
+
const zoom = d3
|
|
46
|
+
.zoom<SVGSVGElement, unknown>()
|
|
41
47
|
.scaleExtent([GRAPH_CONFIG.zoomMin, GRAPH_CONFIG.zoomMax])
|
|
42
48
|
.on('zoom', (event) => {
|
|
43
49
|
svgGroup.attr('transform', event.transform);
|
|
@@ -57,22 +63,40 @@ export function GraphCanvas({
|
|
|
57
63
|
y: height / 2 + Math.sin(angle) * radius,
|
|
58
64
|
};
|
|
59
65
|
});
|
|
60
|
-
const links = data.edges.map(d => ({ ...d }));
|
|
66
|
+
const links = data.edges.map((d) => ({ ...d }));
|
|
61
67
|
|
|
62
68
|
// Create force simulation
|
|
63
|
-
const simulation = d3
|
|
69
|
+
const simulation = d3
|
|
70
|
+
.forceSimulation(nodes as d3.SimulationNodeDatum[])
|
|
64
71
|
.force(
|
|
65
72
|
'link',
|
|
66
|
-
d3
|
|
73
|
+
d3
|
|
74
|
+
.forceLink(links)
|
|
67
75
|
.id((d: unknown) => (d as { id: string }).id)
|
|
68
|
-
.distance((d: unknown) =>
|
|
69
|
-
|
|
76
|
+
.distance((d: unknown) =>
|
|
77
|
+
getEdgeDistance((d as { type: string }).type)
|
|
78
|
+
)
|
|
79
|
+
.strength((d: unknown) =>
|
|
80
|
+
getEdgeStrength((d as { type: string }).type)
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
.force(
|
|
84
|
+
'charge',
|
|
85
|
+
d3.forceManyBody().strength(GRAPH_CONFIG.simulation.chargeStrength)
|
|
70
86
|
)
|
|
71
|
-
.force('charge', d3.forceManyBody().strength(GRAPH_CONFIG.simulation.chargeStrength))
|
|
72
87
|
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
73
|
-
.force(
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
.force(
|
|
89
|
+
'collision',
|
|
90
|
+
d3.forceCollide().radius(GRAPH_CONFIG.collisionRadius)
|
|
91
|
+
)
|
|
92
|
+
.force(
|
|
93
|
+
'x',
|
|
94
|
+
d3.forceX(width / 2).strength(GRAPH_CONFIG.simulation.centerStrength)
|
|
95
|
+
)
|
|
96
|
+
.force(
|
|
97
|
+
'y',
|
|
98
|
+
d3.forceY(height / 2).strength(GRAPH_CONFIG.simulation.centerStrength)
|
|
99
|
+
);
|
|
76
100
|
|
|
77
101
|
// Create link group
|
|
78
102
|
const linkGroup = svgGroup.append('g').attr('class', 'links');
|
|
@@ -81,9 +105,17 @@ export function GraphCanvas({
|
|
|
81
105
|
.data(links)
|
|
82
106
|
.enter()
|
|
83
107
|
.append('line')
|
|
84
|
-
.attr(
|
|
85
|
-
|
|
86
|
-
|
|
108
|
+
.attr(
|
|
109
|
+
'stroke',
|
|
110
|
+
(d: unknown) =>
|
|
111
|
+
edgeColors[(d as { type: string }).type] || edgeColors.default
|
|
112
|
+
)
|
|
113
|
+
.attr('stroke-opacity', (d: unknown) =>
|
|
114
|
+
getEdgeOpacity((d as { type: string }).type)
|
|
115
|
+
)
|
|
116
|
+
.attr('stroke-width', (d: unknown) =>
|
|
117
|
+
getEdgeStrokeWidth((d as { type: string }).type)
|
|
118
|
+
);
|
|
87
119
|
|
|
88
120
|
// Create node group
|
|
89
121
|
const nodeGroup = svgGroup.append('g').attr('class', 'nodes');
|
|
@@ -95,7 +127,8 @@ export function GraphCanvas({
|
|
|
95
127
|
.append('g')
|
|
96
128
|
.attr('cursor', 'pointer')
|
|
97
129
|
.call(
|
|
98
|
-
d3
|
|
130
|
+
d3
|
|
131
|
+
.drag<SVGGElement, any>()
|
|
99
132
|
.on('start', dragstarted)
|
|
100
133
|
.on('drag', dragged)
|
|
101
134
|
.on('end', dragended) as any
|
|
@@ -104,17 +137,32 @@ export function GraphCanvas({
|
|
|
104
137
|
// Add circles to nodes
|
|
105
138
|
node
|
|
106
139
|
.append('circle')
|
|
107
|
-
.attr(
|
|
108
|
-
|
|
140
|
+
.attr(
|
|
141
|
+
'r',
|
|
142
|
+
(d: unknown) =>
|
|
143
|
+
Math.sqrt((d as { value: number }).value || 10) +
|
|
144
|
+
GRAPH_CONFIG.nodeBaseRadius
|
|
145
|
+
)
|
|
146
|
+
.attr(
|
|
147
|
+
'fill',
|
|
148
|
+
(d: unknown) => (d as { color: string }).color || severityColors.default
|
|
149
|
+
)
|
|
109
150
|
.attr('stroke', effectiveTheme === 'dark' ? '#fff' : '#000')
|
|
110
151
|
.attr('stroke-width', 1.5);
|
|
111
152
|
|
|
112
153
|
// Add labels to nodes
|
|
113
154
|
node
|
|
114
155
|
.append('text')
|
|
115
|
-
.text(
|
|
156
|
+
.text(
|
|
157
|
+
(d: unknown) =>
|
|
158
|
+
(d as { label: string }).label.split('/').pop() ||
|
|
159
|
+
(d as { label: string }).label
|
|
160
|
+
)
|
|
116
161
|
.attr('x', 0)
|
|
117
|
-
.attr(
|
|
162
|
+
.attr(
|
|
163
|
+
'y',
|
|
164
|
+
(d: unknown) => Math.sqrt((d as { value: number }).value || 10) + 12
|
|
165
|
+
)
|
|
118
166
|
.attr('text-anchor', 'middle')
|
|
119
167
|
.attr('fill', effectiveTheme === 'dark' ? '#e2e8f0' : '#1e293b')
|
|
120
168
|
.attr('font-size', '9px')
|
|
@@ -139,12 +187,17 @@ export function GraphCanvas({
|
|
|
139
187
|
.attr('x2', (d: unknown) => (d as { target: { x: number } }).target.x)
|
|
140
188
|
.attr('y2', (d: unknown) => (d as { target: { y: number } }).target.y);
|
|
141
189
|
|
|
142
|
-
node.attr(
|
|
190
|
+
node.attr(
|
|
191
|
+
'transform',
|
|
192
|
+
(d: unknown) =>
|
|
193
|
+
`translate(${(d as { x: number }).x},${(d as { y: number }).y})`
|
|
194
|
+
);
|
|
143
195
|
});
|
|
144
196
|
|
|
145
197
|
// Drag functions
|
|
146
198
|
function dragstarted(event: unknown, d: unknown) {
|
|
147
|
-
if (!(event as { active: boolean }).active)
|
|
199
|
+
if (!(event as { active: boolean }).active)
|
|
200
|
+
simulation.alphaTarget(0.3).restart();
|
|
148
201
|
(d as { fx: number | null }).fx = (d as { x: number }).x;
|
|
149
202
|
(d as { fy: number | null }).fy = (d as { y: number }).y;
|
|
150
203
|
}
|
|
@@ -178,7 +231,14 @@ export function GraphCanvas({
|
|
|
178
231
|
ref={svgRef}
|
|
179
232
|
width="100%"
|
|
180
233
|
height="100%"
|
|
181
|
-
style={{
|
|
234
|
+
style={{
|
|
235
|
+
display: 'block',
|
|
236
|
+
backgroundColor: 'transparent',
|
|
237
|
+
zIndex: 10,
|
|
238
|
+
position: 'absolute',
|
|
239
|
+
top: 0,
|
|
240
|
+
left: 0,
|
|
241
|
+
}}
|
|
182
242
|
/>
|
|
183
243
|
<div className="absolute bottom-6 left-6 z-20">
|
|
184
244
|
<div
|
|
@@ -189,7 +249,13 @@ export function GraphCanvas({
|
|
|
189
249
|
color: colors.textMuted,
|
|
190
250
|
}}
|
|
191
251
|
>
|
|
192
|
-
<svg
|
|
252
|
+
<svg
|
|
253
|
+
width="16"
|
|
254
|
+
height="16"
|
|
255
|
+
fill="none"
|
|
256
|
+
viewBox="0 0 24 24"
|
|
257
|
+
stroke="currentColor"
|
|
258
|
+
>
|
|
193
259
|
<path
|
|
194
260
|
strokeLinecap="round"
|
|
195
261
|
strokeLinejoin="round"
|