OutputsEmbedding the viewer

Embedding the viewer

@jhzlo/resin/viewer is the playground's diagram, for a page of your own: the canvas that pans, zooms and focuses, with the live glass under the cards, and the panels that describe a table, a service or a column. A page that embeds it shows a schema the way the playground does. It keeps its own controls and layout; the viewer tells it what was pressed.

It needs a browser. The language (@jhzlo/resin) does not, and a page that only compiles or draws to a file never loads the viewer.

import ELK from "elkjs/lib/elk.bundled.js";
import { compile, toSvg } from "@jhzlo/resin";
import { Canvas, tableDetails } from "@jhzlo/resin/viewer";
import "@jhzlo/resin/viewer.css";

const { model } = compile(source);
const panel = document.querySelector<HTMLElement>("#panel")!; // marked class="rz-details"

const canvas = new Canvas(document.querySelector<HTMLElement>("#diagram")!, {
  onTap(hit) {
    if (!model) return;
    if (hit.table && hit.header) {
      panel.replaceChildren(tableDetails(model, hit.table, (name) => canvas.reveal(name), () => {}, () => {}) ?? "");
      canvas.focusTable(hit.table);
    } else if (hit.table) canvas.focus?.table(hit.table);
    else canvas.focus?.clear();
  },
});
canvas.setLook("aurora", matchMedia("(prefers-color-scheme: dark)").matches);
canvas.setGrid(true);

if (model) canvas.show(await toSvg(model, new ELK(), { ...canvas.drawOptions(), columns: "all" }), true);

The element given to Canvas is the page's to size; the canvas fills it. Lay out a large schema in a Web Worker so the page stays responsive: elkjs/lib/elk-api.js takes a workerUrl, and anything with a layout(graph) method that returns a promise can be passed to toSvg.

Canvas

Member What it does
new Canvas(viewport, options) Takes the element over (it gets the class rz-viewer) and puts the drawing in its .rz-content child, made when there is none. Options: onTap(hit, x, y) for a press that did not move, onView(animate) after every pan or zoom, and live: false to leave WebGL out
setLook(stage, dark) The background: aurora, silk or caustic (CANVAS_STAGES), dark or light. Draw again after changing it
drawOptions() The look and stage to pass to toSvg. Over the live glass the SVG carries only what sits on it; without WebGL2, or with live: false, it is the still look
look() The look as a still drawing has it, for exporting what the canvas shows
show(drawing, fit) Puts a toSvg result on the canvas, or a drawing it returned before, and fits it when fit is true. Returns the drawing as shown, which can be shown again later without drawing it anew
focus table(name), row(table, column) and clear(): keep a table or a relation and fade the rest
focusTable(name), focusService(name) Focus a table, or its row where its service is folded; focus a service where it is one card
reveal(name, covered, onlyIfCovered, animate) Brings a table to the middle of what is visible. covered is the width of a panel over the right edge
revealElement(el, covered, animate) The same for any part of the drawing, unless it is already in plain view
card(name), row(table, column), foldedRow(table), serviceElement(name) Where things are drawn
view The pan and zoom: fit(), zoomBy(factor), actualSize(), centerOn(x, y, covered) and scale
setGrid(on), refresh(instant), pause(paused), destroy() The dot grid; drawing the glass again after the focus changed outside a press; stopping while out of sight; letting go of the element, its listeners and the GPU context

A press reports hit: table (the card pressed, a folded service's card being serviceCardId(service)), column (the row pressed), rowTable (in a folded service's card, the table of the row), service (a folded service's card, or the label of a service's area) and header (the press was on a card's header or on an area's label). What it does is up to the page; the playground opens the table panel from a header, the column popover from a row, and the service panel from a service.

Details

tableDetails(model, table, open, pick, openService), serviceDetails(model, service, open, pick, openService) and columnDetails(model, table, column, open) return the playground's panels as elements: a table's columns (key, name, type, NULL or NOT NULL, description), indexes and references; a service's tables and what it depends on and is used by; one column's facts. They return null for a name the model does not have. open, pick and openService are called when a table name, a column or a service name in them is followed. Put them inside an element with the class rz-details.

Parts of a row

cardParts(hidden, toggle) returns the control the playground uses to choose which parts of a row the cards show: one row of a card as a sample (FK customer_id IX Customer bigint NULL), in the card's type, colors and order, with the name of each part above it. Each part but the column's name is a button with aria-pressed; a part that is hidden fades. Pressing one calls toggle with the part (keys, tags, descriptions, types or nulls); keep the hidden parts, pass them to toSvg as hide, and build the control again.

import { CARD_PARTS, type CardPart } from "@jhzlo/resin";
import { cardParts } from "@jhzlo/resin/viewer";

const picker = document.querySelector("#parts")!;
let hide: CardPart[] = [];
const toggle = (part: CardPart) => {
  hide = CARD_PARTS.filter((p) => (p === part) !== hide.includes(p));
  picker.replaceChildren(cardParts(new Set(hide), toggle));
  // draw again with toSvg(model, elk, { ...options, hide })
};
picker.replaceChildren(cardParts(new Set(hide), toggle));

Styles

@jhzlo/resin/viewer.css styles the canvas, the details and the parts of a row. It reads these custom properties, with light defaults; set them on :root or any ancestor, dark values included, to match the page:

Property Used for
--rz-ink, --rz-muted, --rz-faint Text, from strongest to faintest
--rz-rule, --rz-panel, --rz-code Rules between rows, the panel's background, chips
--rz-accent, --rz-accent-soft, --rz-active-line Links and the picked row; rows under the pointer
--rz-k-pk, --rz-k-uk, --rz-k-fk, --rz-warn Key labels, NULL in the panel, a warning in the service panel
--rz-stage Under the drawing before the first frame of the live background
--rz-font-body, --rz-font-mono The two typefaces