All files / src/graph-runtime bootstrap.ts

100% Statements 98/98
71.42% Branches 5/7
100% Functions 5/5
100% Lines 98/98

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 993x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
/** Viewport, loop, and public API. */
export const GRAPH_RUNTIME_BOOTSTRAP = `
  const transform = { x: 0, y: 0, scale: 1 };
  let fitted = false;
 
  function resize() {
    const rect = canvas.parentElement.getBoundingClientRect();
    width = Math.max(320, rect.width);
    height = Math.max(240, rect.height);
    canvas.width = Math.floor(width * dpr);
    canvas.height = Math.floor(height * dpr);
    canvas.style.width = width + "px";
    canvas.style.height = height + "px";
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }
 
  function screenToWorld(sx, sy) {
    return {
      x: (sx - transform.x) / transform.scale,
      y: (sy - transform.y) / transform.scale,
    };
  }
 
  function fitView() {
    if (!nodes.length) return;
    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
    for (const n of nodes) {
      minX = Math.min(minX, n.x - n.r);
      maxX = Math.max(maxX, n.x + n.r);
      minY = Math.min(minY, n.y - n.r);
      maxY = Math.max(maxY, n.y + n.r + 14);
    }
    const pad = 48;
    const gw = maxX - minX || 1;
    const gh = maxY - minY || 1;
    const scale = Math.min((width - pad * 2) / gw, (height - pad * 2) / gh, 2.5);
    transform.scale = Math.max(0.15, scale);
    transform.x = width / 2 - ((minX + maxX) / 2) * transform.scale;
    transform.y = height / 2 - ((minY + maxY) / 2) * transform.scale;
  }
 
  function loop() {
    if (simRunning) tick();
    else if (!fitted) { fitView(); fitted = true; }
    draw();
    requestAnimationFrame(loop);
  }
 
  function initialLayout(model) {
    applyModel(model);
    seedPositions();
    fitted = false;
    startSimulation(1);
  }
 
  function relayout() {
    replotModel({ nodes: nodes.map((n) => ({ id: n.id, label: n.label, status: n.status })), edges: edges.map((e) => ({ from: e.from.id, to: e.to.id })) });
  }
 
  function replotModel(model) {
    applyModel(model || { nodes: [], edges: [] });
    for (const n of nodes) {
      n.pinned = false;
      n.vx = 0;
      n.vy = 0;
    }
    hovered = null;
    draggingNode = null;
    pointerDownNode = null;
    seedPositions();
    fitted = false;
    if (nodes.length) {
      startSimulation(1);
    } else {
      stopSimulation();
      fitView();
      fitted = true;
    }
  }
 
  function reloadModel(model) {
    replotModel(model);
  }
 
  document.getElementById("fitView")?.addEventListener("click", () => fitView());
  document.getElementById("resetSim")?.addEventListener("click", () => relayout());
 
  window.__MERIDIAN_FORCE_GRAPH__ = { reload: reloadModel, replot: replotModel, relayout, fitView };
 
  bindInput();
  if (payload.kind !== "delivery" || !Array.isArray(payload.stories)) {
    initialLayout(payload.model || { nodes: [], edges: [] });
  }
  window.addEventListener("resize", () => { resize(); if (!simRunning) fitView(); });
  resize();
  if (!nodes.length) fitView();
  loop();
`.trim()