canvas-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev 93fdaafeda16
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 788ba0fa01083b300ad50913
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
src/lib/layout.ts
// Lays a diagram out with dagre: node sizes from their labels, then positions in layers top to
// bottom (or left to right). Used to draw diagrams on boards and the template pages' previews.
import dagre from "@dagrejs/dagre";
import type { Color, Graph, Shape } from "../../functions/lib/canvas";

export interface LaidNode { id: string; label: string; shape: Shape; color: Color; x: number; y: number; width: number; height: number }

const CHAR_W = 11;
const LINE_H = 25;
const MAX_W = 260;

function size(label: string, shape: Shape) {
  const chars = Math.max(4, label.length);
  const width = Math.min(MAX_W, Math.max(150, chars * CHAR_W + 40));
  const lines = Math.max(1, Math.ceil((chars * CHAR_W) / (width - 40)));
  const height = Math.max(70, lines * LINE_H + 40);
  // Ellipses and diamonds hold their text in a smaller inner box.
  const grow = shape === "diamond" ? 1.6 : shape === "ellipse" ? 1.3 : 1;
  return { width: Math.round(width * grow), height: Math.round(height * grow) };
}

export function layoutGraph(graph: Graph): { nodes: LaidNode[]; width: number; height: number } {
  const g = new dagre.graphlib.Graph({ multigraph: true });
  g.setGraph({ rankdir: graph.direction === "right" ? "LR" : "TB", nodesep: 60, ranksep: 80, marginx: 0, marginy: 0 });
  g.setDefaultEdgeLabel(() => ({}));
  for (const n of graph.nodes) g.setNode(n.id, size(n.label, n.shape ?? "rectangle"));
  graph.edges.forEach((e, i) => g.setEdge(e.from, e.to, e.label ? { width: e.label.length * 9 + 16, height: 24, labelpos: "c" } : {}, `e${i}`));
  dagre.layout(g);
  const nodes = graph.nodes.map((n) => {
    const p = g.node(n.id);
    return {
      id: n.id, label: n.label, shape: n.shape ?? "rectangle", color: n.color ?? "white",
      x: Math.round(p.x - p.width / 2), y: Math.round(p.y - p.height / 2), width: p.width, height: p.height,
    };
  });
  const box = g.graph();
  return { nodes, width: box.width ?? 0, height: box.height ?? 0 };
}

/** Where a line from a node's center towards (tx, ty) leaves its outline, a little outside it. */
export function edgePoint(n: LaidNode, tx: number, ty: number, gap = 8) {
  const cx = n.x + n.width / 2;
  const cy = n.y + n.height / 2;
  const dx = tx - cx;
  const dy = ty - cy;
  const a = n.width / 2;
  const b = n.height / 2;
  if (!dx && !dy) return { x: cx, y: cy };
  const t = n.shape === "ellipse" ? 1 / Math.sqrt((dx / a) ** 2 + (dy / b) ** 2)
    : n.shape === "diamond" ? 1 / (Math.abs(dx) / a + Math.abs(dy) / b)
    : Math.min(Math.abs(a / (dx || 1e-9)), Math.abs(b / (dy || 1e-9)));
  const len = Math.hypot(dx, dy);
  return { x: cx + dx * t + (dx / len) * gap, y: cy + dy * t + (dy / len) * gap };
}

Change history

Point OpenApps links to openapps.free
93fdaafeda · 18 hours ago
Bind AGENT_ADDRESS in policy constants
bf8f43b096 · 20 hours ago
Use functions.openapps.xyz alias, add OpenApp contribution strip
56f1a381b2 · 20 hours ago
Bounded source sync
9ec707aeb8 · 1 day ago
Bounded source sync
5645481c23 · 1 day ago
Bounded source sync
c38fe71874 · 1 day ago
Bounded source sync
28c0350b40 · 1 day ago