codeui-python 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
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.
- codeui/__init__.py +1 -1
- codeui/agent/context.py +19 -3
- codeui/analysis/dead_code.py +13 -2
- codeui/analysis/undefined.py +70 -0
- codeui/analysis/unresolved_import.py +34 -4
- codeui/cli/main.py +101 -7
- codeui/core/cache.py +54 -0
- codeui/core/graph.py +233 -3
- codeui/core/override.py +1 -0
- codeui/core/resolver.py +51 -37
- codeui/errors.py +10 -0
- codeui/lang/base.py +5 -2
- codeui/lang/cpp.py +271 -0
- codeui/lang/csharp.py +262 -0
- codeui/lang/dart.py +278 -0
- codeui/lang/generic.py +6 -4
- codeui/lang/go.py +5 -2
- codeui/lang/java.py +320 -0
- codeui/lang/kotlin.py +303 -0
- codeui/lang/php.py +275 -0
- codeui/lang/python.py +49 -7
- codeui/lang/registry.py +22 -2
- codeui/lang/ruby.py +270 -0
- codeui/lang/rust.py +5 -2
- codeui/lang/scala.py +290 -0
- codeui/lang/swift.py +276 -0
- codeui/lang/ts.py +99 -8
- codeui/report/sarif_emitter.py +2 -1
- codeui/server/server.py +156 -85
- codeui/server/static/css/styles.css +1609 -0
- codeui/server/static/index.html +199 -0
- codeui/server/static/js/api.js +485 -0
- codeui/server/static/js/app.js +1044 -0
- codeui/server/static/js/graph_canvas.js +1485 -0
- codeui/server/static/js/minimap.js +383 -0
- codeui/server/static/js/sidebar.js +268 -0
- codeui/server/static/js/state.js +83 -0
- codeui/server/static/js/subgraph_window.js +1208 -0
- codeui/server/static/js/theme.js +332 -0
- codeui/tracer/__init__.py +4 -1
- codeui/tracer/tracer.py +109 -118
- codeui_python-0.1.1.dist-info/METADATA +171 -0
- codeui_python-0.1.1.dist-info/RECORD +71 -0
- {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/licenses/LICENSE +1 -1
- codeui_python-0.1.0.dist-info/METADATA +0 -191
- codeui_python-0.1.0.dist-info/RECORD +0 -52
- {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/WHEEL +0 -0
- {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/entry_points.txt +0 -0
- {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
class MiniMap {
|
|
2
|
+
constructor(mainCanvas) {
|
|
3
|
+
this.mainCanvas = mainCanvas;
|
|
4
|
+
this.container = document.getElementById("graph-minimap-container");
|
|
5
|
+
this.dockBtn = document.getElementById("minimap-dock-btn");
|
|
6
|
+
this.canvas = document.getElementById("minimap-canvas");
|
|
7
|
+
this.countEl = document.getElementById("minimap-count");
|
|
8
|
+
this.dockCountEl = document.getElementById("minimap-dock-count");
|
|
9
|
+
this.tooltip = document.getElementById("minimap-tooltip");
|
|
10
|
+
this.ctx = this.canvas ? this.canvas.getContext("2d") : null;
|
|
11
|
+
this.cssWidth = 220;
|
|
12
|
+
this.cssHeight = 140;
|
|
13
|
+
this.isCollapsed = false;
|
|
14
|
+
this.isDragging = false;
|
|
15
|
+
this.lastMouse = { x: 0, y: 0 };
|
|
16
|
+
this.mapping = {
|
|
17
|
+
scale: 1,
|
|
18
|
+
offsetX: 0,
|
|
19
|
+
offsetY: 0,
|
|
20
|
+
vfX: 0,
|
|
21
|
+
vfY: 0,
|
|
22
|
+
vfW: 0,
|
|
23
|
+
vfH: 0,
|
|
24
|
+
boundsMinX: 0,
|
|
25
|
+
boundsMaxX: 0,
|
|
26
|
+
boundsMinY: 0,
|
|
27
|
+
boundsMaxY: 0
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (this.canvas) {
|
|
31
|
+
this.setupHeaderToggle();
|
|
32
|
+
this.resizeCanvas();
|
|
33
|
+
this.restoreState();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setupHeaderToggle() {
|
|
38
|
+
const header = this.container ? this.container.querySelector(".minimap-header") : null;
|
|
39
|
+
if (header) {
|
|
40
|
+
header.style.cursor = "pointer";
|
|
41
|
+
header.addEventListener("click", (e) => {
|
|
42
|
+
e.stopPropagation();
|
|
43
|
+
this.isCollapsed = !this.isCollapsed;
|
|
44
|
+
const wrapper = this.container.querySelector(".minimap-canvas-wrapper");
|
|
45
|
+
if (wrapper) {
|
|
46
|
+
wrapper.style.display = this.isCollapsed ? "none" : "block";
|
|
47
|
+
}
|
|
48
|
+
if (!this.isCollapsed) {
|
|
49
|
+
this.update();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
restoreState() {
|
|
56
|
+
this.isCollapsed = false;
|
|
57
|
+
if (this.container) this.container.style.display = "flex";
|
|
58
|
+
if (this.dockBtn) this.dockBtn.style.display = "none";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
collapse() {
|
|
62
|
+
this.isCollapsed = false;
|
|
63
|
+
if (this.container) this.container.style.display = "flex";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
expand() {
|
|
67
|
+
this.isCollapsed = false;
|
|
68
|
+
if (this.container) this.container.style.display = "flex";
|
|
69
|
+
if (this.dockBtn) this.dockBtn.style.display = "none";
|
|
70
|
+
this.resizeCanvas();
|
|
71
|
+
this.update();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
resizeCanvas() {
|
|
75
|
+
if (!this.canvas) return;
|
|
76
|
+
const dpr = window.devicePixelRatio || 1;
|
|
77
|
+
this.canvas.width = this.cssWidth * dpr;
|
|
78
|
+
this.canvas.height = this.cssHeight * dpr;
|
|
79
|
+
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
80
|
+
this.ctx.scale(dpr, dpr);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getMinimapPos(e) {
|
|
84
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
85
|
+
return {
|
|
86
|
+
x: e.clientX - rect.left,
|
|
87
|
+
y: e.clientY - rect.top
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
isInsideViewfinder(x, y) {
|
|
92
|
+
const { vfX, vfY, vfW, vfH } = this.mapping;
|
|
93
|
+
const hitPad = 4;
|
|
94
|
+
return (
|
|
95
|
+
x >= vfX - hitPad &&
|
|
96
|
+
x <= vfX + vfW + hitPad &&
|
|
97
|
+
y >= vfY - hitPad &&
|
|
98
|
+
y <= vfY + vfH + hitPad
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setupEvents() {
|
|
103
|
+
if (!this.canvas) return;
|
|
104
|
+
|
|
105
|
+
const onDown = (clientX, clientY) => {
|
|
106
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
107
|
+
const posX = clientX - rect.left;
|
|
108
|
+
const posY = clientY - rect.top;
|
|
109
|
+
|
|
110
|
+
if (this.isInsideViewfinder(posX, posY)) {
|
|
111
|
+
this.isDragging = true;
|
|
112
|
+
this.lastMouse = { x: clientX, y: clientY };
|
|
113
|
+
this.canvas.style.cursor = "grabbing";
|
|
114
|
+
} else {
|
|
115
|
+
const worldX = (posX - this.mapping.offsetX) / this.mapping.scale;
|
|
116
|
+
const worldY = (posY - this.mapping.offsetY) / this.mapping.scale;
|
|
117
|
+
this.mainCanvas.panTo(worldX, worldY, false);
|
|
118
|
+
this.isDragging = true;
|
|
119
|
+
this.lastMouse = { x: clientX, y: clientY };
|
|
120
|
+
this.canvas.style.cursor = "grabbing";
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const onMove = (clientX, clientY) => {
|
|
125
|
+
if (this.isDragging) {
|
|
126
|
+
const dx = (clientX - this.lastMouse.x) / this.mapping.scale;
|
|
127
|
+
const dy = (clientY - this.lastMouse.y) / this.mapping.scale;
|
|
128
|
+
this.mainCanvas.transform.x -= dx * this.mainCanvas.transform.k;
|
|
129
|
+
this.mainCanvas.transform.y -= dy * this.mainCanvas.transform.k;
|
|
130
|
+
this.lastMouse = { x: clientX, y: clientY };
|
|
131
|
+
if (this.tooltip) this.tooltip.style.display = "none";
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
136
|
+
const posX = clientX - rect.left;
|
|
137
|
+
const posY = clientY - rect.top;
|
|
138
|
+
|
|
139
|
+
if (this.isInsideViewfinder(posX, posY)) {
|
|
140
|
+
this.canvas.style.cursor = "grab";
|
|
141
|
+
} else {
|
|
142
|
+
this.canvas.style.cursor = "crosshair";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (this.tooltip) {
|
|
146
|
+
let hoveredNode = null;
|
|
147
|
+
const nodes = this.mainCanvas.nodes || [];
|
|
148
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
149
|
+
const n = nodes[i];
|
|
150
|
+
const nx = n.x * this.mapping.scale + this.mapping.offsetX;
|
|
151
|
+
const ny = n.y * this.mapping.scale + this.mapping.offsetY;
|
|
152
|
+
const d2 = (nx - posX) * (nx - posX) + (ny - posY) * (ny - posY);
|
|
153
|
+
if (d2 <= 49) {
|
|
154
|
+
hoveredNode = n;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (hoveredNode) {
|
|
159
|
+
this.tooltip.textContent = hoveredNode.label || hoveredNode.id;
|
|
160
|
+
const tipX = Math.max(6, Math.min(posX + 8, this.cssWidth - 90));
|
|
161
|
+
const tipY = Math.max(4, posY - 26);
|
|
162
|
+
this.tooltip.style.left = `${tipX}px`;
|
|
163
|
+
this.tooltip.style.top = `${tipY}px`;
|
|
164
|
+
this.tooltip.style.display = "block";
|
|
165
|
+
} else {
|
|
166
|
+
this.tooltip.style.display = "none";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const onUp = () => {
|
|
172
|
+
this.isDragging = false;
|
|
173
|
+
this.canvas.style.cursor = "crosshair";
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
this.canvas.addEventListener("mousedown", e => {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
onDown(e.clientX, e.clientY);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
window.addEventListener("mousemove", e => {
|
|
182
|
+
if (this.isDragging) {
|
|
183
|
+
onMove(e.clientX, e.clientY);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
this.canvas.addEventListener("mousemove", e => {
|
|
188
|
+
if (!this.isDragging) {
|
|
189
|
+
onMove(e.clientX, e.clientY);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
window.addEventListener("mouseup", onUp);
|
|
194
|
+
this.canvas.addEventListener("mouseleave", () => {
|
|
195
|
+
if (!this.isDragging && this.tooltip) {
|
|
196
|
+
this.tooltip.style.display = "none";
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
this.canvas.addEventListener("wheel", e => {
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
const factor = e.deltaY < 0 ? 1.15 : 0.85;
|
|
203
|
+
this.mainCanvas.zoomBy(factor);
|
|
204
|
+
}, { passive: false });
|
|
205
|
+
|
|
206
|
+
this.canvas.addEventListener("touchstart", e => {
|
|
207
|
+
if (e.touches.length === 1) {
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
onDown(e.touches[0].clientX, e.touches[0].clientY);
|
|
210
|
+
}
|
|
211
|
+
}, { passive: false });
|
|
212
|
+
|
|
213
|
+
this.canvas.addEventListener("touchmove", e => {
|
|
214
|
+
if (e.touches.length === 1) {
|
|
215
|
+
e.preventDefault();
|
|
216
|
+
onMove(e.touches[0].clientX, e.touches[0].clientY);
|
|
217
|
+
}
|
|
218
|
+
}, { passive: false });
|
|
219
|
+
|
|
220
|
+
this.canvas.addEventListener("touchend", onUp);
|
|
221
|
+
this.canvas.addEventListener("touchcancel", onUp);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
update() {
|
|
225
|
+
if (!this.mainCanvas || !this.mainCanvas.nodes) return;
|
|
226
|
+
const nodes = this.mainCanvas.nodes;
|
|
227
|
+
const modeLabel = appState.graphMode === "files" ? "files" : "symbols";
|
|
228
|
+
const countText = `${nodes.length} ${modeLabel}`;
|
|
229
|
+
if (this.countEl && this.countEl.textContent !== countText) {
|
|
230
|
+
this.countEl.textContent = countText;
|
|
231
|
+
}
|
|
232
|
+
if (this.dockCountEl && this.dockCountEl.textContent !== `(${countText})`) {
|
|
233
|
+
this.dockCountEl.textContent = `(${countText})`;
|
|
234
|
+
}
|
|
235
|
+
if (this.isCollapsed || !this.ctx || !this.canvas) return;
|
|
236
|
+
|
|
237
|
+
const mainContainer = document.getElementById("canvas-container");
|
|
238
|
+
const mainW = mainContainer ? mainContainer.clientWidth || 800 : 800;
|
|
239
|
+
const mainH = mainContainer ? mainContainer.clientHeight || 600 : 600;
|
|
240
|
+
const k = this.mainCanvas.transform ? this.mainCanvas.transform.k || 1 : 1;
|
|
241
|
+
const tx = this.mainCanvas.transform ? this.mainCanvas.transform.x || 0 : 0;
|
|
242
|
+
const ty = this.mainCanvas.transform ? this.mainCanvas.transform.y || 0 : 0;
|
|
243
|
+
|
|
244
|
+
const visMinX = -tx / k;
|
|
245
|
+
const visMinY = -ty / k;
|
|
246
|
+
const visMaxX = (mainW - tx) / k;
|
|
247
|
+
const visMaxY = (mainH - ty) / k;
|
|
248
|
+
|
|
249
|
+
let nMinX = Infinity, nMaxX = -Infinity, nMinY = Infinity, nMaxY = -Infinity;
|
|
250
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
251
|
+
const n = nodes[i];
|
|
252
|
+
if (isFinite(n.x) && isFinite(n.y)) {
|
|
253
|
+
if (n.x < nMinX) nMinX = n.x;
|
|
254
|
+
if (n.x > nMaxX) nMaxX = n.x;
|
|
255
|
+
if (n.y < nMinY) nMinY = n.y;
|
|
256
|
+
if (n.y > nMaxY) nMaxY = n.y;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (nMinX === Infinity || nodes.length === 0) {
|
|
260
|
+
nMinX = -200; nMaxX = 200; nMinY = -150; nMaxY = 150;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const pad = 40;
|
|
264
|
+
const boundsMinX = Math.min(nMinX - pad, isFinite(visMinX) ? visMinX : -200);
|
|
265
|
+
const boundsMaxX = Math.max(nMaxX + pad, isFinite(visMaxX) ? visMaxX : 200);
|
|
266
|
+
const boundsMinY = Math.min(nMinY - pad, isFinite(visMinY) ? visMinY : -150);
|
|
267
|
+
const boundsMaxY = Math.max(nMaxY + pad, isFinite(visMaxY) ? visMaxY : 150);
|
|
268
|
+
|
|
269
|
+
const spanX = Math.max(80, boundsMaxX - boundsMinX);
|
|
270
|
+
const spanY = Math.max(60, boundsMaxY - boundsMinY);
|
|
271
|
+
|
|
272
|
+
const scaleX = (this.cssWidth - 14) / spanX;
|
|
273
|
+
const scaleY = (this.cssHeight - 14) / spanY;
|
|
274
|
+
const scale = Math.max(0.001, Math.min(scaleX, scaleY));
|
|
275
|
+
|
|
276
|
+
const midX = (boundsMinX + boundsMaxX) / 2;
|
|
277
|
+
const midY = (boundsMinY + boundsMaxY) / 2;
|
|
278
|
+
|
|
279
|
+
const offsetX = this.cssWidth / 2 - midX * scale;
|
|
280
|
+
const offsetY = this.cssHeight / 2 - midY * scale;
|
|
281
|
+
|
|
282
|
+
const vfX = isFinite(visMinX) ? visMinX * scale + offsetX : 0;
|
|
283
|
+
const vfY = isFinite(visMinY) ? visMinY * scale + offsetY : 0;
|
|
284
|
+
const vfW = isFinite(visMaxX) && isFinite(visMinX) ? (visMaxX - visMinX) * scale : this.cssWidth;
|
|
285
|
+
const vfH = isFinite(visMaxY) && isFinite(visMinY) ? (visMaxY - visMinY) * scale : this.cssHeight;
|
|
286
|
+
|
|
287
|
+
this.mapping = {
|
|
288
|
+
scale, offsetX, offsetY,
|
|
289
|
+
vfX, vfY, vfW, vfH,
|
|
290
|
+
boundsMinX, boundsMaxX, boundsMinY, boundsMaxY
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
this.ctx.clearRect(0, 0, this.cssWidth, this.cssHeight);
|
|
294
|
+
|
|
295
|
+
const themeColors = window.ThemeManager ? window.ThemeManager.getCanvasColors() : null;
|
|
296
|
+
const dotsColor = themeColors ? themeColors.minimapDots : "rgba(51, 65, 85, 0.35)";
|
|
297
|
+
const linksColor = themeColors ? themeColors.minimapLinks : "rgba(71, 85, 105, 0.35)";
|
|
298
|
+
const vfBg = themeColors ? themeColors.minimapViewfinder : "rgba(56, 189, 248, 0.12)";
|
|
299
|
+
const vfBorder = themeColors ? themeColors.minimapViewfinderBorder : "#38bdf8";
|
|
300
|
+
|
|
301
|
+
this.ctx.fillStyle = dotsColor;
|
|
302
|
+
for (let gx = 10; gx < this.cssWidth; gx += 20) {
|
|
303
|
+
for (let gy = 10; gy < this.cssHeight; gy += 20) {
|
|
304
|
+
this.ctx.fillRect(gx, gy, 1, 1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const links = this.mainCanvas.links || [];
|
|
309
|
+
this.ctx.strokeStyle = linksColor;
|
|
310
|
+
this.ctx.lineWidth = 0.8;
|
|
311
|
+
this.ctx.beginPath();
|
|
312
|
+
for (let i = 0; i < links.length; i++) {
|
|
313
|
+
const l = links[i];
|
|
314
|
+
const sx = l.source.x * scale + offsetX;
|
|
315
|
+
const sy = l.source.y * scale + offsetY;
|
|
316
|
+
const targetX = l.target.x * scale + offsetX;
|
|
317
|
+
const targetY = l.target.y * scale + offsetY;
|
|
318
|
+
this.ctx.moveTo(sx, sy);
|
|
319
|
+
this.ctx.lineTo(targetX, targetY);
|
|
320
|
+
}
|
|
321
|
+
this.ctx.stroke();
|
|
322
|
+
|
|
323
|
+
const selNodeId = appState.selectedFileNodeId;
|
|
324
|
+
const unusedNodeIds = appState.unusedHighlightMode ? appState.getUnusedNodeIds() : null;
|
|
325
|
+
|
|
326
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
327
|
+
const n = nodes[i];
|
|
328
|
+
const nx = n.x * scale + offsetX;
|
|
329
|
+
const ny = n.y * scale + offsetY;
|
|
330
|
+
const isSelected = selNodeId && n.id === selNodeId;
|
|
331
|
+
|
|
332
|
+
let color = appState.colorMode === "feature"
|
|
333
|
+
? appState.getFeatureColor(n.feature)
|
|
334
|
+
: (appState.LAYER_COLORS[n.layer] || "#0284c7");
|
|
335
|
+
|
|
336
|
+
if (unusedNodeIds && unusedNodeIds.has(n.id) && !isSelected) {
|
|
337
|
+
color = "#94a3b8";
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const r = isSelected ? 4 : 2.5;
|
|
341
|
+
this.ctx.beginPath();
|
|
342
|
+
this.ctx.arc(nx, ny, r, 0, Math.PI * 2);
|
|
343
|
+
this.ctx.fillStyle = color;
|
|
344
|
+
this.ctx.fill();
|
|
345
|
+
|
|
346
|
+
if (isSelected) {
|
|
347
|
+
this.ctx.beginPath();
|
|
348
|
+
this.ctx.arc(nx, ny, r + 2.5, 0, Math.PI * 2);
|
|
349
|
+
this.ctx.strokeStyle = vfBorder;
|
|
350
|
+
this.ctx.lineWidth = 1.5;
|
|
351
|
+
this.ctx.stroke();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
this.ctx.fillStyle = vfBg;
|
|
356
|
+
this.ctx.fillRect(vfX, vfY, vfW, vfH);
|
|
357
|
+
this.ctx.strokeStyle = vfBorder;
|
|
358
|
+
this.ctx.lineWidth = 1.5;
|
|
359
|
+
this.ctx.strokeRect(vfX, vfY, vfW, vfH);
|
|
360
|
+
|
|
361
|
+
const cornerLen = Math.min(6, Math.max(2, Math.min(vfW, vfH) / 3));
|
|
362
|
+
if (cornerLen > 2) {
|
|
363
|
+
this.ctx.strokeStyle = "#ffffff";
|
|
364
|
+
this.ctx.lineWidth = 2;
|
|
365
|
+
this.ctx.beginPath();
|
|
366
|
+
this.ctx.moveTo(vfX, vfY + cornerLen);
|
|
367
|
+
this.ctx.lineTo(vfX, vfY);
|
|
368
|
+
this.ctx.lineTo(vfX + cornerLen, vfY);
|
|
369
|
+
this.ctx.moveTo(vfX + vfW - cornerLen, vfY);
|
|
370
|
+
this.ctx.lineTo(vfX + vfW, vfY);
|
|
371
|
+
this.ctx.lineTo(vfX + vfW, vfY + cornerLen);
|
|
372
|
+
this.ctx.moveTo(vfX, vfY + vfH - cornerLen);
|
|
373
|
+
this.ctx.lineTo(vfX, vfY + vfH);
|
|
374
|
+
this.ctx.lineTo(vfX + cornerLen, vfY + vfH);
|
|
375
|
+
this.ctx.moveTo(vfX + vfW - cornerLen, vfY + vfH);
|
|
376
|
+
this.ctx.lineTo(vfX + vfW, vfY + vfH);
|
|
377
|
+
this.ctx.lineTo(vfX + vfW, vfY + vfH - cornerLen);
|
|
378
|
+
this.ctx.stroke();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
window.MiniMap = MiniMap;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explorer Sidebar Controller for CodeUI.
|
|
3
|
+
*/
|
|
4
|
+
class Sidebar {
|
|
5
|
+
static currentTab = "files";
|
|
6
|
+
static collapsed = true;
|
|
7
|
+
|
|
8
|
+
static init() {
|
|
9
|
+
this.collapsed = true;
|
|
10
|
+
const sidebar = document.getElementById("sidebar");
|
|
11
|
+
const dockBtn = document.getElementById("btn-dock-sidebar");
|
|
12
|
+
if (sidebar) sidebar.classList.add("collapsed");
|
|
13
|
+
if (dockBtn) dockBtn.style.display = "flex";
|
|
14
|
+
if (window.mainCanvas) {
|
|
15
|
+
setTimeout(() => window.mainCanvas.handleResize(), 100);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static switchTab(tabName) {
|
|
20
|
+
this.currentTab = tabName;
|
|
21
|
+
document.querySelectorAll(".sidebar-tabs .tab-btn").forEach(btn => {
|
|
22
|
+
const active = btn.getAttribute("onclick")?.includes(`'${tabName}'`);
|
|
23
|
+
btn.classList.toggle("active", !!active);
|
|
24
|
+
});
|
|
25
|
+
this.renderCurrentTab();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static toggleSidebar() {
|
|
29
|
+
this.collapsed = !this.collapsed;
|
|
30
|
+
const sidebar = document.getElementById("sidebar");
|
|
31
|
+
const dockBtn = document.getElementById("btn-dock-sidebar");
|
|
32
|
+
if (sidebar) sidebar.classList.toggle("collapsed", this.collapsed);
|
|
33
|
+
if (dockBtn) dockBtn.style.display = this.collapsed ? "flex" : "none";
|
|
34
|
+
if (window.mainCanvas) {
|
|
35
|
+
setTimeout(() => window.mainCanvas.handleResize(), 200);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static renderCurrentTab() {
|
|
40
|
+
const container = document.getElementById("sidebar-tab-content");
|
|
41
|
+
if (!container || !window.appState) return;
|
|
42
|
+
|
|
43
|
+
const q = (appState.searchQuery || "").toLowerCase();
|
|
44
|
+
const graph = appState.rawGraph || {};
|
|
45
|
+
|
|
46
|
+
if (this.currentTab === "files") {
|
|
47
|
+
const files = (graph.files || []).filter(f => !q || f.toLowerCase().includes(q));
|
|
48
|
+
if (files.length === 0) {
|
|
49
|
+
container.innerHTML = `<div class="empty-state" style="padding:16px; text-align:center; color:var(--text-dim); font-size:0.75rem;">No files found matching filter</div>`;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const info = graph.files_info || {};
|
|
53
|
+
let html = `<div class="file-card-list">`;
|
|
54
|
+
files.forEach(f => {
|
|
55
|
+
const meta = info[f] || {};
|
|
56
|
+
const layer = meta.layer || "other";
|
|
57
|
+
const isSelected = appState.selectedFileNodeId === f;
|
|
58
|
+
html += `
|
|
59
|
+
<div class="sidebar-item file-card ${isSelected ? 'selected' : ''}" data-id="${f}" onclick="Sidebar.onFileCardClick('${f}')">
|
|
60
|
+
<div class="file-title" style="display:flex; align-items:center; gap:6px; font-weight:600; font-size:0.8rem; color:var(--text-main);">
|
|
61
|
+
<span class="file-icon">📄</span>
|
|
62
|
+
<span class="file-name" title="${f}">${f.split('/').pop()}</span>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="file-meta" style="display:flex; justify-content:space-between; align-items:center; margin-top:4px;">
|
|
65
|
+
<span class="badge layer-${layer}" style="font-size:0.65rem; padding:1px 5px; border-radius:4px; text-transform:uppercase; background:var(--bg-surface); border:1px solid var(--border-subtle); color:var(--text-muted);">${layer}</span>
|
|
66
|
+
<span class="file-path" style="font-size:0.68rem; color:var(--text-dim); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:140px;" title="${f}">${f}</span>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
`;
|
|
70
|
+
});
|
|
71
|
+
html += `</div>`;
|
|
72
|
+
container.innerHTML = html;
|
|
73
|
+
} else if (this.currentTab === "features") {
|
|
74
|
+
const info = graph.files_info || {};
|
|
75
|
+
const featureMap = {};
|
|
76
|
+
(graph.files || []).forEach(f => {
|
|
77
|
+
if (q && !f.toLowerCase().includes(q)) return;
|
|
78
|
+
const feat = info[f]?.feature || "root";
|
|
79
|
+
if (!featureMap[feat]) featureMap[feat] = [];
|
|
80
|
+
featureMap[feat].push(f);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const entries = Object.entries(featureMap);
|
|
84
|
+
if (entries.length === 0) {
|
|
85
|
+
container.innerHTML = `<div class="empty-state" style="padding:16px; text-align:center; color:var(--text-dim); font-size:0.75rem;">No features found</div>`;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let html = `<div class="feature-group-list">`;
|
|
90
|
+
entries.forEach(([feat, fList]) => {
|
|
91
|
+
const featColor = appState.getFeatureColor(feat);
|
|
92
|
+
html += `
|
|
93
|
+
<div class="feature-group" style="margin-bottom:12px;">
|
|
94
|
+
<div class="feature-header" style="display:flex; align-items:center; justify-content:space-between; padding:6px 8px; background:var(--bg-surface); border:1px solid var(--border-subtle); border-radius:6px; margin-bottom:4px; font-size:0.75rem; font-weight:700;">
|
|
95
|
+
<div style="display:flex; align-items:center; gap:6px;">
|
|
96
|
+
<span class="legend-shape" style="background:${featColor}; display:inline-block; width:10px; height:10px; border-radius:50%;"></span>
|
|
97
|
+
<span class="feature-name" style="color:var(--text-main);">${feat}</span>
|
|
98
|
+
</div>
|
|
99
|
+
<span class="count-badge" style="background:var(--bg-base); padding:1px 6px; border-radius:10px; font-size:0.68rem; color:var(--text-muted);">${fList.length}</span>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="feature-files">
|
|
102
|
+
${fList.map(f => `
|
|
103
|
+
<div class="sidebar-item file-card" data-id="${f}" style="padding:4px 8px; margin:2px 0;" onclick="Sidebar.onFileCardClick('${f}')">
|
|
104
|
+
<span class="file-name" style="font-size:0.75rem; color:var(--text-main);">${f.split('/').pop()}</span>
|
|
105
|
+
</div>
|
|
106
|
+
`).join('')}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
`;
|
|
110
|
+
});
|
|
111
|
+
html += `</div>`;
|
|
112
|
+
container.innerHTML = html;
|
|
113
|
+
} else if (this.currentTab === "defects") {
|
|
114
|
+
const allFindings = appState.defectsData || [];
|
|
115
|
+
const apiDriftCount = allFindings.filter(d => d.rule_id === "api_drift").length;
|
|
116
|
+
const defectsCount = allFindings.length - apiDriftCount;
|
|
117
|
+
|
|
118
|
+
if (!this.defectSubFilter) this.defectSubFilter = "all";
|
|
119
|
+
|
|
120
|
+
const defects = allFindings.filter(d => {
|
|
121
|
+
if (this.defectSubFilter === "api_drift" && d.rule_id !== "api_drift") return false;
|
|
122
|
+
if (this.defectSubFilter === "defects" && d.rule_id === "api_drift") return false;
|
|
123
|
+
return !q || d.rule_id.toLowerCase().includes(q) || (d.message || "").toLowerCase().includes(q);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
let html = `
|
|
127
|
+
<div class="defect-filter-bar" style="display:flex; gap:4px; margin-bottom:8px; padding-bottom:6px; border-bottom:1px solid var(--border-subtle); flex-wrap:wrap; align-items:center;">
|
|
128
|
+
<button class="secondary ${this.defectSubFilter === 'all' ? 'active' : ''}" style="font-size:0.68rem; padding:2px 6px;" onclick="Sidebar.setDefectSubFilter('all')">All (${allFindings.length})</button>
|
|
129
|
+
<button class="secondary ${this.defectSubFilter === 'defects' ? 'active' : ''}" style="font-size:0.68rem; padding:2px 6px;" onclick="Sidebar.setDefectSubFilter('defects')">Defects (${defectsCount})</button>
|
|
130
|
+
<button class="secondary ${this.defectSubFilter === 'api_drift' ? 'active' : ''}" style="font-size:0.68rem; padding:2px 6px; color:var(--accent);" onclick="Sidebar.setDefectSubFilter('api_drift')">🌊 API Drift (${apiDriftCount})</button>
|
|
131
|
+
<button class="secondary" style="font-size:0.65rem; padding:2px 6px; color:var(--accent); border-color:var(--border-subtle); margin-left:auto;" onclick="App.setApiBaseline()" title="Capture public symbols baseline snapshot">📸 Set Baseline</button>
|
|
132
|
+
</div>
|
|
133
|
+
`;
|
|
134
|
+
|
|
135
|
+
if (defects.length === 0) {
|
|
136
|
+
html += `<div class="empty-state" style="padding:16px; text-align:center; color:var(--text-dim); font-size:0.75rem;">No findings or API drift issues detected under current filter</div>`;
|
|
137
|
+
container.innerHTML = html;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
html += `<div class="defects-card-list">`;
|
|
142
|
+
defects.forEach(d => {
|
|
143
|
+
const loc = d.location || {};
|
|
144
|
+
const fId = loc.file_id || "unknown";
|
|
145
|
+
const line = loc.start_line || 1;
|
|
146
|
+
const isDrift = d.rule_id === "api_drift";
|
|
147
|
+
const badgeColor = isDrift ? "var(--accent)" : "var(--danger)";
|
|
148
|
+
const badgeLabel = isDrift ? "🌊 API Drift" : `⚠️ ${d.rule_id}`;
|
|
149
|
+
const safeMsg = (d.message || "").replace(/'/g, "\\'");
|
|
150
|
+
const symbolId = d.symbol_id || "";
|
|
151
|
+
html += `
|
|
152
|
+
<div class="sidebar-item defect-card" style="padding:8px; border:1px solid ${isDrift ? 'var(--accent)' : 'var(--border-subtle)'}; border-radius:6px; margin-bottom:6px; background:var(--bg-surface); cursor:pointer;" onclick="Sidebar.openDefectLocation('${fId}', ${line}, '${symbolId}', '${safeMsg}')">
|
|
153
|
+
<div class="defect-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px;">
|
|
154
|
+
<span class="defect-rule" style="font-weight:700; font-size:0.75rem; color:${badgeColor};">${badgeLabel}</span>
|
|
155
|
+
<span class="defect-loc" style="font-size:0.68rem; color:var(--text-dim);">${fId.split('/').pop()}:${line}</span>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="defect-msg" style="font-size:0.72rem; color:var(--text-muted); display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;">${d.message}</div>
|
|
158
|
+
${d.fix_hint ? `<div style="font-size:0.65rem; color:var(--accent); margin-top:4px; font-style:italic;">💡 ${d.fix_hint}</div>` : ''}
|
|
159
|
+
</div>
|
|
160
|
+
`;
|
|
161
|
+
});
|
|
162
|
+
html += `</div>`;
|
|
163
|
+
container.innerHTML = html;
|
|
164
|
+
} else if (this.currentTab === "stats") {
|
|
165
|
+
const filesCount = (graph.files || []).length;
|
|
166
|
+
const symbolsCount = (graph.symbols || []).length;
|
|
167
|
+
const edgesCount = (graph.edges || []).length;
|
|
168
|
+
const allFindings = appState.defectsData || [];
|
|
169
|
+
const defectsCount = allFindings.filter(d => d.rule_id !== "api_drift").length;
|
|
170
|
+
const apiDriftCount = allFindings.filter(d => d.rule_id === "api_drift").length;
|
|
171
|
+
|
|
172
|
+
const layerCounts = {};
|
|
173
|
+
const info = graph.files_info || {};
|
|
174
|
+
(graph.files || []).forEach(f => {
|
|
175
|
+
const l = info[f]?.layer || "other";
|
|
176
|
+
layerCounts[l] = (layerCounts[l] || 0) + 1;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
let html = `
|
|
180
|
+
<div class="stats-panel" style="padding:8px;">
|
|
181
|
+
<div class="stats-grid" style="display:grid; grid-template-columns:1fr 1fr; gap:8px;">
|
|
182
|
+
<div class="stat-card" style="background:var(--bg-surface); padding:8px; border-radius:6px; border:1px solid var(--border-subtle); text-align:center;">
|
|
183
|
+
<div class="stat-num" style="font-size:1.1rem; font-weight:800; color:var(--primary);">${filesCount}</div>
|
|
184
|
+
<div class="stat-label" style="font-size:0.68rem; color:var(--text-dim);">Files</div>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="stat-card" style="background:var(--bg-surface); padding:8px; border-radius:6px; border:1px solid var(--border-subtle); text-align:center;">
|
|
187
|
+
<div class="stat-num" style="font-size:1.1rem; font-weight:800; color:var(--primary);">${symbolsCount}</div>
|
|
188
|
+
<div class="stat-label" style="font-size:0.68rem; color:var(--text-dim);">Symbols</div>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="stat-card" style="background:var(--bg-surface); padding:8px; border-radius:6px; border:1px solid var(--border-subtle); text-align:center;">
|
|
191
|
+
<div class="stat-num" style="font-size:1.1rem; font-weight:800; color:var(--primary);">${edgesCount}</div>
|
|
192
|
+
<div class="stat-label" style="font-size:0.68rem; color:var(--text-dim);">Edges</div>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="stat-card" style="background:var(--bg-surface); padding:8px; border-radius:6px; border:1px solid var(--border-subtle); text-align:center;">
|
|
195
|
+
<div class="stat-num" style="font-size:1.1rem; font-weight:800; color:var(--danger);">${defectsCount}</div>
|
|
196
|
+
<div class="stat-label" style="font-size:0.68rem; color:var(--text-dim);">Defects</div>
|
|
197
|
+
</div>
|
|
198
|
+
<div class="stat-card" style="background:var(--bg-surface); padding:8px; border-radius:6px; border:1px solid var(--border-subtle); grid-column: span 2; text-align:center;">
|
|
199
|
+
<div class="stat-num" style="font-size:1.1rem; font-weight:800; color:var(--accent);">${apiDriftCount}</div>
|
|
200
|
+
<div class="stat-label" style="font-size:0.68rem; color:var(--text-dim);">Public API Drift Warnings</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
<div style="margin-top:16px; font-weight:700; font-size:0.8rem; color:var(--text-muted);">Layers Distribution</div>
|
|
204
|
+
<div class="layers-list" style="margin-top:8px;">
|
|
205
|
+
`;
|
|
206
|
+
Object.entries(layerCounts).forEach(([l, count]) => {
|
|
207
|
+
const color = appState.LAYER_COLORS[l] || "var(--text-dim)";
|
|
208
|
+
html += `
|
|
209
|
+
<div class="layer-stat-row" style="display:flex; justify-content:space-between; align-items:center; padding:4px 0; font-size:0.75rem;">
|
|
210
|
+
<span><span style="display:inline-block; width:8px; height:8px; border-radius:50%; background:${color}; margin-right:6px;"></span>${l}</span>
|
|
211
|
+
<span style="color:var(--text-muted); font-weight:600;">${count} files</span>
|
|
212
|
+
</div>
|
|
213
|
+
`;
|
|
214
|
+
});
|
|
215
|
+
html += `</div></div>`;
|
|
216
|
+
container.innerHTML = html;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
static setDefectSubFilter(sub) {
|
|
221
|
+
this.defectSubFilter = sub;
|
|
222
|
+
this.renderCurrentTab();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
static highlightSelectedCard(nodeId) {
|
|
226
|
+
this.clearSelection();
|
|
227
|
+
if (!nodeId) return;
|
|
228
|
+
const cards = document.querySelectorAll(`.sidebar-item[data-id="${nodeId}"]`);
|
|
229
|
+
cards.forEach(card => {
|
|
230
|
+
card.classList.add("selected");
|
|
231
|
+
card.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
static clearSelection() {
|
|
236
|
+
document.querySelectorAll(".sidebar-item.selected").forEach(c => c.classList.remove("selected"));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
static onFileCardClick(fileId) {
|
|
240
|
+
appState.selectedFileNodeId = fileId;
|
|
241
|
+
this.highlightSelectedCard(fileId);
|
|
242
|
+
if (window.mainCanvas) {
|
|
243
|
+
window.mainCanvas.selectAndCenterNode(fileId);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
static openDefectLocation(fileId, line = 1, symbol = null, message = "") {
|
|
248
|
+
let targetSymbol = symbol && symbol.length > 0 ? symbol : null;
|
|
249
|
+
if (!targetSymbol && appState.rawGraph?.symbols) {
|
|
250
|
+
const fileSyms = appState.rawGraph.symbols.filter(s => {
|
|
251
|
+
const sFile = s.location?.file_id;
|
|
252
|
+
return sFile === fileId || sFile?.endsWith("/" + fileId) || fileId.endsWith("/" + sFile);
|
|
253
|
+
});
|
|
254
|
+
const match = fileSyms.find(s =>
|
|
255
|
+
s.location &&
|
|
256
|
+
s.location.start_line <= line &&
|
|
257
|
+
(s.location.end_line || s.location.start_line) >= line
|
|
258
|
+
);
|
|
259
|
+
if (match) targetSymbol = match.id;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (window.SubGraphManager) {
|
|
263
|
+
SubGraphManager.openWindow(fileId, targetSymbol, line, message, "graph");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
window.Sidebar = Sidebar;
|