Files
racket-wiki/static/js/cmap/view/cmap-boundary-reference-view.js
T

114 lines
4.6 KiB
JavaScript

"use strict";
/** Render references from the active diagram to concepts linked from other CMaps. */
export class CmapBoundaryReferenceView {
constructor({ canvas, zoomFactor, mapTitle, surfaceElement, itemCenter, onOpenReference }) {
this.canvas = canvas;
this.zoomFactor = zoomFactor;
this.mapTitle = mapTitle || "";
this.surfaceElement = surfaceElement;
this.itemCenter = itemCenter;
this.onOpenReference = onOpenReference;
this.layer = null;
}
clear() {
if (this.layer) this.layer.remove();
this.layer = null;
}
render(references = []) {
this.clear();
if (!references.length) return;
const surface = this.surfaceElement();
if (!surface) return;
const layer = document.createElement("div");
layer.className = "rw-cmap-boundary-layer";
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.classList.add("rw-cmap-boundary-lines");
const definitions = document.createElementNS("http://www.w3.org/2000/svg", "defs");
const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
marker.setAttribute("id", "rw-cmap-boundary-arrow");
marker.setAttribute("viewBox", "0 0 10 10");
marker.setAttribute("refX", "9");
marker.setAttribute("refY", "5");
marker.setAttribute("markerWidth", "7");
marker.setAttribute("markerHeight", "7");
marker.setAttribute("orient", "auto-start-reverse");
const arrow = document.createElementNS("http://www.w3.org/2000/svg", "path");
arrow.setAttribute("d", "M 0 0 L 10 5 L 0 10 z");
arrow.setAttribute("fill", "#808080");
marker.append(arrow);
definitions.append(marker);
svg.append(definitions);
layer.append(svg);
surface.append(layer);
this.layer = layer;
const factor = this.zoomFactor();
const viewLeft = this.canvas.scrollLeft / factor;
const viewTop = this.canvas.scrollTop / factor;
const viewWidth = this.canvas.clientWidth / factor;
const viewHeight = this.canvas.clientHeight / factor;
const buttonWidth = 190;
const occupied = { left: [], right: [] };
const reserveY = (side, desired) => {
let y = Math.max(viewTop + 12, Math.min(desired, viewTop + viewHeight - 40));
while (occupied[side].some((used) => Math.abs(used - y) < 34)) y += 34;
if (y > viewTop + viewHeight - 40) y = viewTop + 12;
occupied[side].push(y);
return y;
};
for (const reference of references) {
const inside = this.itemCenter(reference.insideRecord);
const side = inside.x < viewLeft + (viewWidth / 2) ? "left" : "right";
const x = side === "left" ? viewLeft + 12 : viewLeft + viewWidth - buttonWidth - 12;
const y = reserveY(side, inside.y - 15);
const button = document.createElement("button");
button.type = "button";
button.className = `rw-cmap-boundary-reference rw-cmap-boundary-reference-${side}`;
button.style.transform = `translate(${x}px, ${y}px)`;
button.style.width = `${buttonWidth}px`;
if (reference.linkingPhraseLabel) {
button.append(document.createTextNode(reference.linkingPhraseLabel));
button.append(document.createTextNode(" →"));
button.append(document.createElement("br"));
}
button.append(document.createTextNode(reference.label));
if (this.mapTitle) {
button.append(document.createElement("br"));
const mapLabel = document.createElement("small");
const mapName = document.createElement("em");
mapName.textContent = `(${this.mapTitle})`;
mapLabel.append(mapName);
button.append(mapLabel);
}
button.title = "Open the concept map containing this connection";
button.addEventListener("click", () => {
if (this.onOpenReference) this.onOpenReference(reference);
});
layer.append(button);
const boundaryX = side === "left" ? x + buttonWidth : x;
const boundaryY = y + 15;
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
const startX = reference.sourceInside ? inside.x : boundaryX;
const startY = reference.sourceInside ? inside.y : boundaryY;
const endX = reference.sourceInside ? boundaryX : inside.x;
const endY = reference.sourceInside ? boundaryY : inside.y;
path.setAttribute("d", `M ${startX} ${startY} L ${endX} ${endY}`);
path.setAttribute("fill", "none");
path.setAttribute("stroke", "#808080");
path.setAttribute("stroke-width", String(reference.connector.lineWidth || 2));
if (reference.connector.hasArrow) path.setAttribute("marker-end", "url(#rw-cmap-boundary-arrow)");
svg.append(path);
}
}
destroy() {
this.clear();
}
}