@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.
@@ -1,7 +1,7 @@
1
1
  import r from "node:http";
2
2
  import d from "node:fs";
3
- import l from "node:path";
4
- const s = `<!DOCTYPE html>
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 highlight · / = search</div>
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 = 210, gapY = 110;
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 (view !== "modules" || alpha < 0.005) return;
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") showPanel(n ? n.id : null);
247
- else if (n && n.data.moduleId) showPanel(n.data.moduleId);
248
- else showPanel(null);
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 (!matches(id)) return;
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
- ctx.fillStyle = "#d7dde8"; ctx.font = "11px ui-monospace, monospace"; ctx.textAlign = "center";
284
- ctx.fillText(id.split("/").pop(), p[0], p[1] - 13);
285
- ctx.fillStyle = "#55607a"; ctx.font = "10px ui-monospace, monospace";
286
- var sub = d.fullPath || "";
287
- if (d.middlewares.length) sub += " · " + d.middlewares.join(", ");
288
- ctx.fillText(sub, p[0], p[1] + 20);
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 (!matches(e.from.id) && !matches(e.to.id)) return;
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 (!matches(n.id)) return;
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(o) {
355
- const i = l.join(o, ".velojs", "graph.json"), a = r.createServer((n, e) => {
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(s);
407
+ e.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), e.end(l);
368
408
  });
369
409
  return await new Promise((n) => {
370
- a.listen(0, "127.0.0.1", () => {
371
- const e = a.address(), t = typeof e == "object" && e ? e.port : 0;
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mauroandre/velojs",
3
- "version": "0.0.40",
3
+ "version": "0.0.41",
4
4
  "type": "module",
5
5
  "description": "Fullstack web framework built on Hono + Preact with Vite-powered AST transforms",
6
6
  "keywords": [