@mauroandre/velojs 0.0.41 → 0.0.43

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 s from "node:path";
4
- const l = `<!DOCTYPE html>
3
+ import l from "node:path";
4
+ const s = `<!DOCTYPE html>
5
5
  <html lang="en">
6
6
  <head>
7
7
  <meta charset="utf-8">
@@ -48,7 +48,7 @@ const l = `<!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 · click = focus neighborhood · / = search · esc = reset</div>
51
+ <div id="hint">drag to pan · scroll to zoom · drag nodes · hover = highlight · click = pin highlight + details · / = search · esc = reset</div>
52
52
  <script>
53
53
  var KIND_COLORS = { root: "#f59e0b", layout: "#a78bfa", page: "#5b8def", unknown: "#4b5565" };
54
54
 
@@ -60,7 +60,8 @@ var ctx = canvas.getContext("2d");
60
60
  var W = 0, H = 0;
61
61
 
62
62
  function resize() {
63
- W = window.innerWidth; H = window.innerHeight;
63
+ var tb = document.getElementById("topbar");
64
+ W = window.innerWidth; H = window.innerHeight - tb.offsetHeight;
64
65
  canvas.width = W * devicePixelRatio; canvas.height = H * devicePixelRatio;
65
66
  canvas.style.width = W + "px"; canvas.style.height = H + "px";
66
67
  ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
@@ -152,47 +153,34 @@ function tick() {
152
153
  });
153
154
  }
154
155
 
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
-
180
- // ---------- impact highlight ----------
181
- var hoverNode = null;
182
- var impactSet = null;
183
- function computeImpact(n) {
184
- var set = {}, queue = [n.id];
185
- set[n.id] = 2;
186
- while (queue.length) {
187
- var id = queue.shift();
188
- var mod = graph.modules[id];
189
- if (!mod) continue;
190
- mod.importedBy.forEach(function (up) {
191
- if (!(up in set)) { set[up] = 1; queue.push(up); }
192
- });
156
+ // ---------- highlight — hover = transient, click = pinned ----------
157
+ // Same pattern in both views. Related set: modules → transitive
158
+ // importedBy (impact); routes → ancestors + subtree (context).
159
+ var hoverHL = null, pinnedHL = null;
160
+ function activeHL() { return pinnedHL || hoverHL; }
161
+ function relatedNodes(node) {
162
+ var list = [];
163
+ if (view === "modules") {
164
+ var seen = {}, q = [node];
165
+ seen[node.id] = true;
166
+ while (q.length) {
167
+ var cur = q.shift();
168
+ list.push(cur);
169
+ var m = graph.modules[cur.id];
170
+ if (!m) continue;
171
+ m.importedBy.forEach(function (i) {
172
+ if (modById[i] && !seen[i]) { seen[i] = true; q.push(modById[i]); }
173
+ });
174
+ }
175
+ } else {
176
+ var p = node.parent;
177
+ while (p) { list.push(p); p = p.parent; }
178
+ (function down(n) { list.push(n); n.kids.forEach(down); })(node);
193
179
  }
194
- return set;
180
+ return list;
195
181
  }
182
+ function modVisible(n) { return matches(n.id); }
183
+ function routeVisible(n) { return matches(n.data.moduleId || "(wrapper)"); }
196
184
 
197
185
  // ---------- selection / panel ----------
198
186
  var selected = null;
@@ -233,51 +221,53 @@ var query = "";
233
221
  document.getElementById("search").addEventListener("input", function (e) { query = e.target.value.toLowerCase(); });
234
222
  document.addEventListener("keydown", function (e) {
235
223
  if (e.key === "/" && document.activeElement !== document.getElementById("search")) { e.preventDefault(); document.getElementById("search").focus(); }
236
- if (e.key === "Escape") { panel.className = ""; selected = null; query = ""; document.getElementById("search").value = ""; focusModSet = null; focusRouteSet = null; }
224
+ if (e.key === "Escape") { panel.className = ""; selected = null; query = ""; document.getElementById("search").value = ""; hoverHL = null; pinnedHL = null; }
237
225
  });
238
226
 
239
227
  // ---------- interaction ----------
240
228
  var dragNode = null, panning = false, lastX = 0, lastY = 0, moved = false;
241
229
  function nodeAt(sx, sy) {
242
- var w = toWorld(sx, sy);
230
+ // Hit test in SCREEN space, tight to the dot (dot radius + 6px slack)
243
231
  var list = view === "modules" ? modNodes : routeNodes;
244
232
  for (var i = list.length - 1; i >= 0; i--) {
245
233
  var n = list[i];
246
234
  if (view === "modules" ? !modVisible(n) : !routeVisible(n)) continue;
247
- var nx = view === "modules" ? n.x : n.px, ny = view === "modules" ? n.y : n.py;
248
- var dx = w[0] - nx, dy = w[1] - ny;
249
- if (dx * dx + dy * dy < 14 * 14 / (cam.k * cam.k) + 36) return n;
235
+ var wx = view === "modules" ? n.x : n.px, wy = view === "modules" ? n.y : n.py;
236
+ var p = toScreen(wx, wy);
237
+ var rad = (view === "modules" ? (n.kind === "unknown" ? 5 : 8) : 6) + 6;
238
+ var dx = sx - p[0], dy = sy - p[1];
239
+ if (dx * dx + dy * dy < rad * rad) return n;
250
240
  }
251
241
  return null;
252
242
  }
253
243
  canvas.addEventListener("mousedown", function (e) {
254
- var n = nodeAt(e.clientX, e.clientY);
244
+ var n = nodeAt(e.offsetX, e.offsetY);
255
245
  if (n && view === "modules") { dragNode = n; alpha = Math.max(alpha, 0.3); }
256
246
  else panning = true;
257
- lastX = e.clientX; lastY = e.clientY; moved = false;
247
+ lastX = e.offsetX; lastY = e.offsetY; moved = false;
258
248
  });
259
249
  canvas.addEventListener("mousemove", function (e) {
260
- var dx = e.clientX - lastX, dy = e.clientY - lastY;
250
+ var dx = e.offsetX - lastX, dy = e.offsetY - lastY;
261
251
  if (Math.abs(dx) + Math.abs(dy) > 3) moved = true;
262
- if (dragNode) { var w = toWorld(e.clientX, e.clientY); dragNode.x = w[0]; dragNode.y = w[1]; dragNode.vx = dragNode.vy = 0; }
252
+ if (dragNode) { var w = toWorld(e.offsetX, e.offsetY); dragNode.x = w[0]; dragNode.y = w[1]; dragNode.vx = dragNode.vy = 0; }
263
253
  else if (panning) { cam.x += dx; cam.y += dy; }
264
254
  else {
265
- var n = nodeAt(e.clientX, e.clientY);
266
- hoverNode = view === "modules" ? n : null;
267
- impactSet = hoverNode ? computeImpact(hoverNode) : null;
255
+ var n = nodeAt(e.offsetX, e.offsetY);
256
+ hoverHL = n ? { list: relatedNodes(n), origin: n } : null;
268
257
  canvas.style.cursor = n ? "pointer" : "grab";
269
258
  }
270
- lastX = e.clientX; lastY = e.clientY;
259
+ lastX = e.offsetX; lastY = e.offsetY;
271
260
  });
272
261
  canvas.addEventListener("mouseup", function (e) {
273
262
  if (!moved) {
274
- var n = nodeAt(e.clientX, e.clientY);
275
- if (view === "modules") {
276
- showPanel(n ? n.id : null);
277
- focusModSet = n ? computeFocusModules(n.id) : null;
263
+ var n = nodeAt(e.offsetX, e.offsetY);
264
+ if (n) {
265
+ pinnedHL = { list: relatedNodes(n), origin: n };
266
+ var id = view === "modules" ? n.id : n.data.moduleId;
267
+ showPanel(id || null);
278
268
  } else {
279
- if (n && n.data.moduleId) showPanel(n.data.moduleId);
280
- focusRouteSet = n ? computeFocusRoutes(n) : null;
269
+ pinnedHL = null;
270
+ showPanel(null);
281
271
  }
282
272
  }
283
273
  dragNode = null; panning = false;
@@ -285,9 +275,9 @@ canvas.addEventListener("mouseup", function (e) {
285
275
  canvas.addEventListener("wheel", function (e) {
286
276
  e.preventDefault();
287
277
  var k = Math.exp(-e.deltaY * 0.0012);
288
- var w = toWorld(e.clientX, e.clientY);
278
+ var w = toWorld(e.offsetX, e.offsetY);
289
279
  cam.k *= k;
290
- cam.x = e.clientX - w[0] * cam.k; cam.y = e.clientY - w[1] * cam.k;
280
+ cam.x = e.offsetX - w[0] * cam.k; cam.y = e.offsetY - w[1] * cam.k;
291
281
  }, { passive: false });
292
282
 
293
283
  // ---------- rendering ----------
@@ -295,64 +285,73 @@ function matches(id) { return !query || id.toLowerCase().indexOf(query) >= 0; }
295
285
 
296
286
  function drawRoutes() {
297
287
  ctx.clearRect(0, 0, W, H);
298
- ctx.strokeStyle = "#2a3247"; ctx.lineWidth = 1.2;
288
+ var hl = activeHL();
299
289
  routeEdges.forEach(function (e) {
300
290
  if (!routeVisible(e.from) || !routeVisible(e.to)) return;
291
+ var hot = hl && hl.list.indexOf(e.from) >= 0 && hl.list.indexOf(e.to) >= 0;
301
292
  var a = toScreen(e.from.px, e.from.py), b = toScreen(e.to.px, e.to.py);
293
+ ctx.globalAlpha = hl && !hot ? 0.08 : 1;
294
+ ctx.strokeStyle = hot ? "#f59e0b" : "#2a3247";
295
+ ctx.lineWidth = hot ? 2 : 1.2;
302
296
  ctx.beginPath();
303
297
  ctx.moveTo(a[0], a[1]);
304
298
  ctx.bezierCurveTo(a[0], (a[1] + b[1]) / 2, b[0], (a[1] + b[1]) / 2, b[0], b[1]);
305
299
  ctx.stroke();
306
300
  });
301
+ ctx.globalAlpha = 1;
307
302
  routeNodes.forEach(function (n) {
308
303
  var d = n.data;
309
304
  var id = d.moduleId || "(wrapper)";
310
305
  if (!routeVisible(n)) return;
311
306
  var p = toScreen(n.px, n.py);
307
+ var member = hl && hl.list.indexOf(n) >= 0;
308
+ var dim = hl && !member;
309
+ var isOrigin = hl && hl.origin === n;
312
310
  var color = d.isRoot ? KIND_COLORS.root : (d.children.length ? KIND_COLORS.layout : KIND_COLORS.page);
313
311
  var sel = selected === d.moduleId;
314
- ctx.beginPath(); ctx.arc(p[0], p[1], sel ? 9 : 6, 0, 7);
315
- ctx.fillStyle = color; ctx.fill();
312
+ ctx.globalAlpha = dim ? 0.15 : 1;
313
+ ctx.beginPath(); ctx.arc(p[0], p[1], (sel || isOrigin) ? 9 : 6, 0, 7);
314
+ ctx.fillStyle = isOrigin ? "#f59e0b" : color; ctx.fill();
316
315
  if (sel) { ctx.strokeStyle = "#e8edf5"; ctx.lineWidth = 2; ctx.stroke(); }
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";
316
+ if (!dim) {
317
+ ctx.fillStyle = member ? "#e8edf5" : "#d7dde8"; ctx.font = "11px ui-monospace, monospace"; ctx.textAlign = "center";
321
318
  ctx.fillText(id.split("/").pop(), p[0], p[1] - 13);
322
- }
323
- if (cam.k >= 0.85 || sel) {
324
319
  ctx.fillStyle = "#55607a"; ctx.font = "10px ui-monospace, monospace";
325
320
  var sub = d.fullPath || "";
326
321
  if (d.middlewares.length) sub += " · " + d.middlewares.join(", ");
327
322
  ctx.fillText(sub, p[0], p[1] + 20);
328
323
  }
324
+ ctx.globalAlpha = 1;
329
325
  });
330
326
  }
331
327
 
332
328
  function drawModules() {
333
329
  ctx.clearRect(0, 0, W, H);
334
- ctx.strokeStyle = "#232a3a"; ctx.lineWidth = 1;
330
+ var hl = activeHL();
335
331
  modEdges.forEach(function (e) {
336
332
  if (!modVisible(e.from) || !modVisible(e.to)) return;
337
- var hot = impactSet && impactSet[e.from.id] && impactSet[e.to.id];
333
+ var hot = hl && hl.list.indexOf(e.from) >= 0 && hl.list.indexOf(e.to) >= 0;
338
334
  var a = toScreen(e.from.x, e.from.y), b = toScreen(e.to.x, e.to.y);
335
+ ctx.globalAlpha = hl && !hot ? 0.08 : 1;
339
336
  ctx.strokeStyle = hot ? "#f59e0b" : "#232a3a";
340
337
  ctx.lineWidth = hot ? 1.8 : 1;
341
338
  ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
342
339
  });
340
+ ctx.globalAlpha = 1;
343
341
  modNodes.forEach(function (n) {
344
342
  if (!modVisible(n)) return;
345
343
  var p = toScreen(n.x, n.y);
346
- var dim = impactSet && !impactSet[n.id];
347
- var hot = impactSet && impactSet[n.id];
348
- var r = n.kind === "unknown" ? 4 : 7;
344
+ var member = hl && hl.list.indexOf(n) >= 0;
345
+ var dim = hl && !member;
346
+ var isOrigin = hl && hl.origin === n;
347
+ var r = n.kind === "unknown" ? 5 : 8;
349
348
  ctx.globalAlpha = dim ? 0.15 : 1;
350
- ctx.beginPath(); ctx.arc(p[0], p[1], selected === n.id ? r + 3 : r, 0, 7);
351
- ctx.fillStyle = hot === 2 ? "#f59e0b" : KIND_COLORS[n.kind] || KIND_COLORS.unknown;
349
+ ctx.beginPath(); ctx.arc(p[0], p[1], (selected === n.id || isOrigin) ? r + 3 : r, 0, 7);
350
+ ctx.fillStyle = isOrigin ? "#f59e0b" : KIND_COLORS[n.kind] || KIND_COLORS.unknown;
352
351
  ctx.fill();
353
352
  if (selected === n.id) { ctx.strokeStyle = "#e8edf5"; ctx.lineWidth = 2; ctx.stroke(); }
354
353
  if (!dim) {
355
- ctx.fillStyle = hot ? "#e8edf5" : "#9aa5b8";
354
+ ctx.fillStyle = member ? "#e8edf5" : "#9aa5b8";
356
355
  ctx.font = "10px ui-monospace, monospace"; ctx.textAlign = "center";
357
356
  ctx.fillText(n.id.split("/").pop(), p[0], p[1] - r - 5);
358
357
  }
@@ -391,8 +390,8 @@ fetch("/graph.json").then(function (r) { return r.json(); }).then(function (g) {
391
390
  <\/script>
392
391
  </body>
393
392
  </html>`;
394
- async function f(a) {
395
- const i = s.join(a, ".velojs", "graph.json"), o = r.createServer((n, e) => {
393
+ async function m(a) {
394
+ const i = l.join(a, ".velojs", "graph.json"), o = r.createServer((n, e) => {
396
395
  if (n.url === "/graph.json") {
397
396
  let t;
398
397
  try {
@@ -404,7 +403,7 @@ async function f(a) {
404
403
  e.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" }), e.end(t);
405
404
  return;
406
405
  }
407
- e.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), e.end(l);
406
+ e.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), e.end(s);
408
407
  });
409
408
  return await new Promise((n) => {
410
409
  o.listen(0, "127.0.0.1", () => {
@@ -414,5 +413,5 @@ async function f(a) {
414
413
  });
415
414
  }
416
415
  export {
417
- f as serveGraphViewer
416
+ m as serveGraphViewer
418
417
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mauroandre/velojs",
3
- "version": "0.0.41",
3
+ "version": "0.0.43",
4
4
  "type": "module",
5
5
  "description": "Fullstack web framework built on Hono + Preact with Vite-powered AST transforms",
6
6
  "keywords": [