@mauroandre/velojs 0.0.40 → 0.0.41
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/graph-viewer.js +65 -25
- package/package.json +1 -1
package/dist/graph-viewer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import r from "node:http";
|
|
2
2
|
import d from "node:fs";
|
|
3
|
-
import
|
|
4
|
-
const
|
|
3
|
+
import s from "node:path";
|
|
4
|
+
const l = `<!DOCTYPE html>
|
|
5
5
|
<html lang="en">
|
|
6
6
|
<head>
|
|
7
7
|
<meta charset="utf-8">
|
|
@@ -48,7 +48,7 @@ const s = `<!DOCTYPE html>
|
|
|
48
48
|
<div><i style="background:#5b8def"></i>page</div>
|
|
49
49
|
<div><i style="background:#4b5565"></i>module</div>
|
|
50
50
|
</div>
|
|
51
|
-
<div id="hint">drag to pan · scroll to zoom · drag nodes · hover = impact
|
|
51
|
+
<div id="hint">drag to pan · scroll to zoom · drag nodes · hover = impact · click = focus neighborhood · / = search · esc = reset</div>
|
|
52
52
|
<script>
|
|
53
53
|
var KIND_COLORS = { root: "#f59e0b", layout: "#a78bfa", page: "#5b8def", unknown: "#4b5565" };
|
|
54
54
|
|
|
@@ -90,16 +90,16 @@ function buildRouteLayout() {
|
|
|
90
90
|
var leafCount = 0;
|
|
91
91
|
// tidy-ish: leaves get sequential x; parents sit at the midpoint of children
|
|
92
92
|
function layout(node, depth, parent) {
|
|
93
|
-
var rn = { data: node, depth: depth, x: 0, y: depth, parent: parent };
|
|
93
|
+
var rn = { data: node, depth: depth, x: 0, y: depth, parent: parent, kids: [] };
|
|
94
94
|
routeNodes.push(rn);
|
|
95
|
-
if (parent) routeEdges.push({ from: parent, to: rn });
|
|
95
|
+
if (parent) { parent.kids.push(rn); routeEdges.push({ from: parent, to: rn }); }
|
|
96
96
|
if (!node.children.length) { rn.x = leafCount++; return rn; }
|
|
97
97
|
var xs = node.children.map(function (c) { return layout(c, depth + 1, rn).x; });
|
|
98
98
|
rn.x = (xs[0] + xs[xs.length - 1]) / 2;
|
|
99
99
|
return rn;
|
|
100
100
|
}
|
|
101
101
|
graph.routes.forEach(function (r) { layout(r, 0, null); });
|
|
102
|
-
var gapX =
|
|
102
|
+
var gapX = 250, gapY = 130;
|
|
103
103
|
var minX = Math.min.apply(null, routeNodes.map(function (n) { return n.x; }));
|
|
104
104
|
routeNodes.forEach(function (n) { n.px = (n.x - minX) * gapX + 80; n.py = n.y * gapY + 80; });
|
|
105
105
|
}
|
|
@@ -118,8 +118,9 @@ function toWorld(sx, sy) { return [(sx - cam.x) / cam.k, (sy - cam.y) / cam.k];
|
|
|
118
118
|
|
|
119
119
|
// ---------- force simulation (modules) ----------
|
|
120
120
|
var alpha = 1;
|
|
121
|
+
var MAXV = 25;
|
|
121
122
|
function tick() {
|
|
122
|
-
if (
|
|
123
|
+
if (alpha < 0.005) return;
|
|
123
124
|
alpha *= 0.995;
|
|
124
125
|
var i, j, n, m, dx, dy, d2, f;
|
|
125
126
|
for (i = 0; i < modNodes.length; i++) {
|
|
@@ -145,10 +146,37 @@ function tick() {
|
|
|
145
146
|
if (n === dragNode) return;
|
|
146
147
|
n.vx += -n.x * 0.002 * alpha; n.vy += -n.y * 0.002 * alpha;
|
|
147
148
|
n.vx *= 0.85; n.vy *= 0.85;
|
|
149
|
+
if (n.vx > MAXV) n.vx = MAXV; else if (n.vx < -MAXV) n.vx = -MAXV;
|
|
150
|
+
if (n.vy > MAXV) n.vy = MAXV; else if (n.vy < -MAXV) n.vy = -MAXV;
|
|
148
151
|
n.x += n.vx; n.y += n.vy;
|
|
149
152
|
});
|
|
150
153
|
}
|
|
151
154
|
|
|
155
|
+
// ---------- focus mode ----------
|
|
156
|
+
// Click isolates a node's neighborhood: transitive imports + importedBy
|
|
157
|
+
// (modules), or ancestors + subtree (routes). Click empty / ESC exits.
|
|
158
|
+
var focusModSet = null, focusRouteSet = null;
|
|
159
|
+
function computeFocusModules(id) {
|
|
160
|
+
var set = {}, q = [id];
|
|
161
|
+
set[id] = true;
|
|
162
|
+
while (q.length) {
|
|
163
|
+
var m = graph.modules[q.shift()];
|
|
164
|
+
if (!m) continue;
|
|
165
|
+
m.imports.forEach(function (i) { if (modById[i] && !set[i]) { set[i] = true; q.push(i); } });
|
|
166
|
+
m.importedBy.forEach(function (i) { if (modById[i] && !set[i]) { set[i] = true; q.push(i); } });
|
|
167
|
+
}
|
|
168
|
+
return set;
|
|
169
|
+
}
|
|
170
|
+
function computeFocusRoutes(rn) {
|
|
171
|
+
var set = [];
|
|
172
|
+
var p = rn.parent;
|
|
173
|
+
while (p) { set.push(p); p = p.parent; }
|
|
174
|
+
(function down(n) { set.push(n); n.kids.forEach(down); })(rn);
|
|
175
|
+
return set;
|
|
176
|
+
}
|
|
177
|
+
function modVisible(n) { return matches(n.id) && (!focusModSet || focusModSet[n.id]); }
|
|
178
|
+
function routeVisible(n) { return matches(n.data.moduleId || "(wrapper)") && (!focusRouteSet || focusRouteSet.indexOf(n) >= 0); }
|
|
179
|
+
|
|
152
180
|
// ---------- impact highlight ----------
|
|
153
181
|
var hoverNode = null;
|
|
154
182
|
var impactSet = null;
|
|
@@ -205,7 +233,7 @@ var query = "";
|
|
|
205
233
|
document.getElementById("search").addEventListener("input", function (e) { query = e.target.value.toLowerCase(); });
|
|
206
234
|
document.addEventListener("keydown", function (e) {
|
|
207
235
|
if (e.key === "/" && document.activeElement !== document.getElementById("search")) { e.preventDefault(); document.getElementById("search").focus(); }
|
|
208
|
-
if (e.key === "Escape") { panel.className = ""; selected = null; query = ""; document.getElementById("search").value = ""; }
|
|
236
|
+
if (e.key === "Escape") { panel.className = ""; selected = null; query = ""; document.getElementById("search").value = ""; focusModSet = null; focusRouteSet = null; }
|
|
209
237
|
});
|
|
210
238
|
|
|
211
239
|
// ---------- interaction ----------
|
|
@@ -215,6 +243,7 @@ function nodeAt(sx, sy) {
|
|
|
215
243
|
var list = view === "modules" ? modNodes : routeNodes;
|
|
216
244
|
for (var i = list.length - 1; i >= 0; i--) {
|
|
217
245
|
var n = list[i];
|
|
246
|
+
if (view === "modules" ? !modVisible(n) : !routeVisible(n)) continue;
|
|
218
247
|
var nx = view === "modules" ? n.x : n.px, ny = view === "modules" ? n.y : n.py;
|
|
219
248
|
var dx = w[0] - nx, dy = w[1] - ny;
|
|
220
249
|
if (dx * dx + dy * dy < 14 * 14 / (cam.k * cam.k) + 36) return n;
|
|
@@ -243,9 +272,13 @@ canvas.addEventListener("mousemove", function (e) {
|
|
|
243
272
|
canvas.addEventListener("mouseup", function (e) {
|
|
244
273
|
if (!moved) {
|
|
245
274
|
var n = nodeAt(e.clientX, e.clientY);
|
|
246
|
-
if (view === "modules")
|
|
247
|
-
|
|
248
|
-
|
|
275
|
+
if (view === "modules") {
|
|
276
|
+
showPanel(n ? n.id : null);
|
|
277
|
+
focusModSet = n ? computeFocusModules(n.id) : null;
|
|
278
|
+
} else {
|
|
279
|
+
if (n && n.data.moduleId) showPanel(n.data.moduleId);
|
|
280
|
+
focusRouteSet = n ? computeFocusRoutes(n) : null;
|
|
281
|
+
}
|
|
249
282
|
}
|
|
250
283
|
dragNode = null; panning = false;
|
|
251
284
|
});
|
|
@@ -264,6 +297,7 @@ function drawRoutes() {
|
|
|
264
297
|
ctx.clearRect(0, 0, W, H);
|
|
265
298
|
ctx.strokeStyle = "#2a3247"; ctx.lineWidth = 1.2;
|
|
266
299
|
routeEdges.forEach(function (e) {
|
|
300
|
+
if (!routeVisible(e.from) || !routeVisible(e.to)) return;
|
|
267
301
|
var a = toScreen(e.from.px, e.from.py), b = toScreen(e.to.px, e.to.py);
|
|
268
302
|
ctx.beginPath();
|
|
269
303
|
ctx.moveTo(a[0], a[1]);
|
|
@@ -273,19 +307,25 @@ function drawRoutes() {
|
|
|
273
307
|
routeNodes.forEach(function (n) {
|
|
274
308
|
var d = n.data;
|
|
275
309
|
var id = d.moduleId || "(wrapper)";
|
|
276
|
-
if (!
|
|
310
|
+
if (!routeVisible(n)) return;
|
|
277
311
|
var p = toScreen(n.px, n.py);
|
|
278
312
|
var color = d.isRoot ? KIND_COLORS.root : (d.children.length ? KIND_COLORS.layout : KIND_COLORS.page);
|
|
279
313
|
var sel = selected === d.moduleId;
|
|
280
314
|
ctx.beginPath(); ctx.arc(p[0], p[1], sel ? 9 : 6, 0, 7);
|
|
281
315
|
ctx.fillStyle = color; ctx.fill();
|
|
282
316
|
if (sel) { ctx.strokeStyle = "#e8edf5"; ctx.lineWidth = 2; ctx.stroke(); }
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
317
|
+
// level-of-detail: labels only when zoomed in enough, otherwise the
|
|
318
|
+
// overview stays clean (dots only). fullPath needs even more zoom.
|
|
319
|
+
if (cam.k >= 0.55 || sel) {
|
|
320
|
+
ctx.fillStyle = "#d7dde8"; ctx.font = "11px ui-monospace, monospace"; ctx.textAlign = "center";
|
|
321
|
+
ctx.fillText(id.split("/").pop(), p[0], p[1] - 13);
|
|
322
|
+
}
|
|
323
|
+
if (cam.k >= 0.85 || sel) {
|
|
324
|
+
ctx.fillStyle = "#55607a"; ctx.font = "10px ui-monospace, monospace";
|
|
325
|
+
var sub = d.fullPath || "";
|
|
326
|
+
if (d.middlewares.length) sub += " · " + d.middlewares.join(", ");
|
|
327
|
+
ctx.fillText(sub, p[0], p[1] + 20);
|
|
328
|
+
}
|
|
289
329
|
});
|
|
290
330
|
}
|
|
291
331
|
|
|
@@ -293,7 +333,7 @@ function drawModules() {
|
|
|
293
333
|
ctx.clearRect(0, 0, W, H);
|
|
294
334
|
ctx.strokeStyle = "#232a3a"; ctx.lineWidth = 1;
|
|
295
335
|
modEdges.forEach(function (e) {
|
|
296
|
-
if (!
|
|
336
|
+
if (!modVisible(e.from) || !modVisible(e.to)) return;
|
|
297
337
|
var hot = impactSet && impactSet[e.from.id] && impactSet[e.to.id];
|
|
298
338
|
var a = toScreen(e.from.x, e.from.y), b = toScreen(e.to.x, e.to.y);
|
|
299
339
|
ctx.strokeStyle = hot ? "#f59e0b" : "#232a3a";
|
|
@@ -301,7 +341,7 @@ function drawModules() {
|
|
|
301
341
|
ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
|
|
302
342
|
});
|
|
303
343
|
modNodes.forEach(function (n) {
|
|
304
|
-
if (!
|
|
344
|
+
if (!modVisible(n)) return;
|
|
305
345
|
var p = toScreen(n.x, n.y);
|
|
306
346
|
var dim = impactSet && !impactSet[n.id];
|
|
307
347
|
var hot = impactSet && impactSet[n.id];
|
|
@@ -351,8 +391,8 @@ fetch("/graph.json").then(function (r) { return r.json(); }).then(function (g) {
|
|
|
351
391
|
<\/script>
|
|
352
392
|
</body>
|
|
353
393
|
</html>`;
|
|
354
|
-
async function f(
|
|
355
|
-
const i =
|
|
394
|
+
async function f(a) {
|
|
395
|
+
const i = s.join(a, ".velojs", "graph.json"), o = r.createServer((n, e) => {
|
|
356
396
|
if (n.url === "/graph.json") {
|
|
357
397
|
let t;
|
|
358
398
|
try {
|
|
@@ -364,11 +404,11 @@ async function f(o) {
|
|
|
364
404
|
e.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" }), e.end(t);
|
|
365
405
|
return;
|
|
366
406
|
}
|
|
367
|
-
e.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), e.end(
|
|
407
|
+
e.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), e.end(l);
|
|
368
408
|
});
|
|
369
409
|
return await new Promise((n) => {
|
|
370
|
-
|
|
371
|
-
const e =
|
|
410
|
+
o.listen(0, "127.0.0.1", () => {
|
|
411
|
+
const e = o.address(), t = typeof e == "object" && e ? e.port : 0;
|
|
372
412
|
n(`http://127.0.0.1:${t}`);
|
|
373
413
|
});
|
|
374
414
|
});
|