2021-01-28 13:50:25 +00:00
|
|
|
import {MathjaxAdapter} from "./mathjax-adapter";
|
2021-08-29 06:32:15 +00:00
|
|
|
import {hoverAreaAroundElements} from "./mathjax-style-hacks";
|
2021-01-28 13:50:25 +00:00
|
|
|
|
2021-02-10 15:45:36 +00:00
|
|
|
declare let window: {
|
2021-03-08 11:42:32 +00:00
|
|
|
svgPanZoomFun: (svg: SVGElement, options: {
|
|
|
|
fit: boolean;
|
|
|
|
controlIconsEnabled: boolean;
|
|
|
|
customEventsHandler: {
|
|
|
|
init: (options: any) => void;
|
|
|
|
haltEventListeners: string[];
|
|
|
|
destroy: () => void
|
|
|
|
};
|
|
|
|
}) => void;
|
2021-02-10 15:45:36 +00:00
|
|
|
}
|
|
|
|
// these attributes and functions are supported by major browsers, but TS does not know about them
|
|
|
|
declare global {
|
|
|
|
interface SVGElement {
|
2021-03-11 13:56:01 +00:00
|
|
|
getElementById: (id: string) => SVGGraphicsElement | null;
|
2021-02-10 15:45:36 +00:00
|
|
|
viewBox: SVGAnimatedRect;
|
|
|
|
}
|
2021-02-27 20:28:17 +00:00
|
|
|
|
2021-02-10 15:45:36 +00:00
|
|
|
interface SVGGraphicsElement {
|
|
|
|
getTransformToElement: (other: SVGGraphicsElement) => SVGMatrix;
|
|
|
|
}
|
2021-02-27 20:28:17 +00:00
|
|
|
|
2021-02-10 15:45:36 +00:00
|
|
|
interface SVGTransformList {
|
|
|
|
[index: number]: SVGTransform;
|
2021-01-28 13:50:25 +00:00
|
|
|
}
|
2021-02-10 15:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
interface ProofTreeServer {
|
|
|
|
setStepCount: (count: number) => void;
|
|
|
|
}
|
2021-01-28 13:50:25 +00:00
|
|
|
|
2021-02-10 15:45:36 +00:00
|
|
|
class MathjaxProofTree extends MathjaxAdapter {
|
|
|
|
private steps: [SVGElement, SVGElement[]][] = [];
|
|
|
|
private $server: ProofTreeServer | undefined;
|
2021-01-28 13:50:25 +00:00
|
|
|
|
2021-01-28 23:19:21 +00:00
|
|
|
protected showStep(n: number): void {
|
|
|
|
for (let current = 0; current < this.steps.length; current++) {
|
2021-02-03 20:43:40 +00:00
|
|
|
this.steps[current][0].style.display = "none";
|
|
|
|
for (const node of this.steps[current][1]) {
|
|
|
|
node.style.display = "none";
|
|
|
|
}
|
|
|
|
}
|
2021-02-04 19:45:51 +00:00
|
|
|
for (let current = 0; current < this.steps.length && current <= n; current++) {
|
2021-02-03 20:43:40 +00:00
|
|
|
this.steps[current][0].style.display = "";
|
|
|
|
for (const node of this.steps[current][1]) {
|
|
|
|
node.style.display = "";
|
2021-01-28 23:19:21 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-28 13:50:25 +00:00
|
|
|
}
|
|
|
|
|
2021-07-20 07:35:39 +00:00
|
|
|
protected calculateSteps(extraData: any): void {
|
|
|
|
const data = typeof extraData === "string" ? JSON.parse(extraData) : [];
|
|
|
|
const root = this.shadowRoot!;
|
|
|
|
// setup style container for styles applied on hover
|
|
|
|
let hoverStyles = root.querySelector("#typicalc-hover-styles");
|
|
|
|
if (!hoverStyles) {
|
|
|
|
hoverStyles = document.createElement('style');
|
|
|
|
hoverStyles.id = "typicalc-hover-styles";
|
|
|
|
root.querySelector("mjx-head")!.appendChild(hoverStyles);
|
|
|
|
}
|
|
|
|
const unificationEl = root.host.parentElement!.parentElement!.querySelector("tc-unification")!;
|
|
|
|
let hoverStylesUnification = unificationEl.shadowRoot!.querySelector("#typicalc-hover-styles");
|
|
|
|
if (!hoverStylesUnification) {
|
|
|
|
hoverStylesUnification = document.createElement('style');
|
|
|
|
hoverStylesUnification.id = "typicalc-hover-styles";
|
|
|
|
unificationEl.shadowRoot!.querySelector("mjx-head")!.appendChild(hoverStylesUnification);
|
|
|
|
}
|
|
|
|
// set the size of the rendered SVG to the size of the container element
|
|
|
|
if (!root.querySelector("#style-fixes")) {
|
|
|
|
const style = document.createElement('style');
|
|
|
|
style.innerHTML = "\
|
|
|
|
mjx-doc, mjx-body, mjx-container, #tc-content, svg {\
|
|
|
|
height: 100%;\
|
|
|
|
}\
|
|
|
|
mjx-container {\
|
|
|
|
margin: 0 !important;\
|
|
|
|
}\
|
|
|
|
#typicalc-definition-abs, #typicalc-definition-abs-let, #typicalc-definition-app,\
|
|
|
|
#typicalc-definition-const, #typicalc-definition-var, #typicalc-definition-var-let, #typicalc-definition-let {\
|
|
|
|
display: none;\
|
|
|
|
border: 2px solid red;\
|
2021-08-29 06:32:15 +00:00
|
|
|
}" + hoverAreaAroundElements;
|
2021-07-20 07:35:39 +00:00
|
|
|
style.innerHTML += "svg { width: 100%; }";
|
|
|
|
style.id = "style-fixes";
|
|
|
|
root.querySelector("mjx-head")!.appendChild(style);
|
|
|
|
}
|
|
|
|
|
2021-02-10 15:45:36 +00:00
|
|
|
const semanticsMatch = (semantics: string) => semantics.indexOf("bspr_inference:") >= 0;
|
|
|
|
const inferenceRuleSelector = 'g[semantics="bspr_inferenceRule:down"]';
|
|
|
|
const labelSelector = 'g[semantics="bspr_prooflabel:left"]';
|
|
|
|
const stepSelector = 'g[typicalc="step"]';
|
|
|
|
// space between inference premises
|
|
|
|
const padding = 300;
|
2021-03-07 19:24:08 +00:00
|
|
|
console.log("calculating steps..");
|
2021-02-10 15:45:36 +00:00
|
|
|
|
2021-01-28 23:19:21 +00:00
|
|
|
if (this.shadowRoot !== null) {
|
2021-02-10 09:32:52 +00:00
|
|
|
console.time('stepCalculation');
|
2021-06-29 14:27:25 +00:00
|
|
|
const svg = this.shadowRoot.querySelector<SVGElement>("svg")!;
|
2021-07-10 10:47:40 +00:00
|
|
|
let root = this.shadowRoot.querySelector("#typicalc-prooftree")! as SVGElement;
|
2021-07-01 06:05:39 +00:00
|
|
|
while (!root.getAttribute("semantics")) {
|
|
|
|
root = root.parentNode! as SVGElement;
|
|
|
|
}
|
2021-02-03 20:43:40 +00:00
|
|
|
// first, enumerate all of the steps
|
|
|
|
let stepIdx = 0;
|
2021-06-29 14:27:25 +00:00
|
|
|
for (const a of [root, ...root.querySelectorAll<SVGElement>("g[semantics]")]) {
|
2021-02-10 15:45:36 +00:00
|
|
|
let semantics = a.getAttribute("semantics")!;
|
2021-02-04 20:17:03 +00:00
|
|
|
if (semanticsMatch(semantics)) {
|
2021-02-03 20:43:40 +00:00
|
|
|
a.setAttribute("typicalc", "step");
|
|
|
|
a.setAttribute("id", "step" + stepIdx);
|
|
|
|
stepIdx++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// then create the steps
|
2021-02-10 15:45:36 +00:00
|
|
|
let steps: [SVGElement, SVGElement[]][] = [];
|
2021-02-03 20:43:40 +00:00
|
|
|
stepIdx = 0;
|
2021-06-29 14:27:25 +00:00
|
|
|
for (const a of [root, ...root.querySelectorAll<SVGElement>("g[semantics]")]) {
|
2021-02-10 15:45:36 +00:00
|
|
|
let semantics = a.getAttribute("semantics")!;
|
2021-02-04 20:17:03 +00:00
|
|
|
if (semanticsMatch(semantics)) {
|
2021-02-03 20:43:40 +00:00
|
|
|
const id = "step" + stepIdx;
|
2021-02-10 15:45:36 +00:00
|
|
|
const idSelector = `#${id} `;
|
2021-02-03 20:43:40 +00:00
|
|
|
stepIdx++;
|
|
|
|
|
|
|
|
// find the next one/two steps above this one
|
2021-02-10 15:45:36 +00:00
|
|
|
const aboveStep1 = a.querySelector<SVGElement>(idSelector + stepSelector);
|
2021-02-03 20:43:40 +00:00
|
|
|
let above = [];
|
|
|
|
if (aboveStep1 != null) {
|
2021-02-10 15:45:36 +00:00
|
|
|
const parent = aboveStep1.parentElement!.parentElement!;
|
2021-02-03 20:43:40 +00:00
|
|
|
parent.setAttribute("id", "typicalc-selector");
|
2021-02-10 15:45:36 +00:00
|
|
|
for (const node of parent.querySelectorAll<SVGElement>('#typicalc-selector > g > ' + stepSelector)) {
|
|
|
|
above.push(node);
|
2021-02-03 20:43:40 +00:00
|
|
|
}
|
|
|
|
parent.removeAttribute("id");
|
|
|
|
}
|
2021-02-10 15:45:36 +00:00
|
|
|
const rule = a.querySelector(idSelector + inferenceRuleSelector);
|
2021-02-03 20:43:40 +00:00
|
|
|
if (rule !== null) {
|
|
|
|
let i = 0;
|
2021-02-10 15:45:36 +00:00
|
|
|
for (const node of rule.children) {
|
2021-02-03 20:43:40 +00:00
|
|
|
if (i !== 1) {
|
2021-02-10 15:45:36 +00:00
|
|
|
above.push(node as SVGElement);
|
2021-02-03 20:43:40 +00:00
|
|
|
}
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
}
|
2021-02-10 15:45:36 +00:00
|
|
|
const label = a.querySelector<SVGElement>(idSelector + labelSelector);
|
|
|
|
if (label) {
|
|
|
|
above.push(label);
|
2021-02-03 20:43:40 +00:00
|
|
|
}
|
2021-02-04 17:40:41 +00:00
|
|
|
if (stepIdx === 1) {
|
2021-02-10 15:45:36 +00:00
|
|
|
// initial step should not show premises
|
2021-02-04 17:40:41 +00:00
|
|
|
steps.push([a, []]);
|
|
|
|
}
|
2021-02-04 20:17:03 +00:00
|
|
|
steps.push([a, above]);
|
2021-01-28 23:19:21 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-09 13:55:34 +00:00
|
|
|
// limit start zoom
|
|
|
|
svg.viewBox.baseVal.width = Math.min(100000, svg.viewBox.baseVal.width);
|
|
|
|
svg.viewBox.baseVal.width = Math.max(20000, svg.viewBox.baseVal.width);
|
|
|
|
|
2021-02-06 14:57:53 +00:00
|
|
|
// MathJax layout of bussproofs is sometimes wrong:
|
|
|
|
// https://github.com/mathjax/MathJax/issues/2270
|
|
|
|
// https://github.com/mathjax/MathJax/issues/2626
|
2021-03-09 13:55:34 +00:00
|
|
|
// we fix it in two phases (executed at the same time):
|
2021-02-10 09:32:52 +00:00
|
|
|
// 1. fix overlapping by iterating over "rows" in the SVG created by MathJax
|
2021-02-06 14:57:53 +00:00
|
|
|
// in each row, the elements are arranged to not overlap
|
2021-02-10 09:32:52 +00:00
|
|
|
// 2. place inference conclusions under the center of their line
|
2021-06-29 14:27:25 +00:00
|
|
|
const nodeIterator = [...root.querySelectorAll<SVGGraphicsElement>("g[data-mml-node='mtr']," + inferenceRuleSelector)];
|
2021-02-27 20:28:17 +00:00
|
|
|
console.log(`working with ${nodeIterator.length} nodes`);
|
|
|
|
// start layout fixes in the innermost part of the SVG
|
|
|
|
nodeIterator.reverse();
|
|
|
|
for (const row of nodeIterator) {
|
|
|
|
const semantics = row.getAttribute("semantics");
|
|
|
|
if (semantics === "bspr_inferenceRule:down") {
|
|
|
|
const conclusion = row.children[1].querySelector<SVGGraphicsElement>('g[data-mml-node="mstyle"]')!;
|
|
|
|
const conclusionBox = conclusion.getBBox();
|
|
|
|
const line = row.children[2] as SVGGraphicsElement;
|
|
|
|
const bbox = line.getBBox();
|
|
|
|
|
|
|
|
const offset2 = line.getTransformToElement(conclusion);
|
|
|
|
let dx = bbox.width / 2 + offset2.e - conclusionBox.width / 2;
|
|
|
|
dx += Number(line.getAttribute("x1"));
|
|
|
|
let table = row.parentNode as SVGGraphicsElement;
|
|
|
|
let prevNode;
|
|
|
|
let magicNumber = 0;
|
|
|
|
while (table.getAttribute("semantics") !== "bspr_inferenceRule:down") {
|
|
|
|
prevNode = table;
|
|
|
|
table = table.parentNode as SVGGraphicsElement;
|
|
|
|
if (table.getAttribute("data-mml-node") === "mtr" && table.childNodes.length === 3) {
|
|
|
|
for (let i = 0; i < table.childNodes.length; i++) {
|
|
|
|
if (table.childNodes[i] === prevNode) {
|
|
|
|
magicNumber = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (table === svg) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
conclusion.transform.baseVal[0].matrix.e += dx;
|
|
|
|
if (magicNumber !== 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (table === svg) {
|
|
|
|
break; // last step
|
|
|
|
}
|
|
|
|
const lineBelow = table.children[2] as SVGGraphicsElement;
|
|
|
|
let lineStart = 0;
|
|
|
|
if (lineBelow) {
|
|
|
|
const offset1 = lineBelow.getTransformToElement(conclusion);
|
|
|
|
lineStart = -offset1.e;
|
|
|
|
const x1 = Number(lineBelow.getAttribute("x1"));
|
|
|
|
const x2 = Number(lineBelow.getAttribute("x2"));
|
|
|
|
lineBelow.setAttribute("x1", String(x1 + lineStart));
|
|
|
|
lineBelow.setAttribute("x2", String(x2 + lineStart));
|
|
|
|
}
|
|
|
|
const label = table.parentElement!.children[1] as SVGGraphicsElement;
|
|
|
|
if (label && label.transform) {
|
|
|
|
label.transform.baseVal[0].matrix.e += lineStart;
|
|
|
|
}
|
|
|
|
} else {
|
2021-02-10 09:32:52 +00:00
|
|
|
let left = null;
|
|
|
|
let i = 0;
|
2021-02-10 15:45:36 +00:00
|
|
|
for (const rawNode of row.children) {
|
2021-02-10 09:32:52 +00:00
|
|
|
const node = rawNode as SVGGraphicsElement;
|
|
|
|
if (i === 1 || i === 3) {
|
|
|
|
i += 1;
|
2021-02-10 15:45:36 +00:00
|
|
|
continue; // spacing node
|
2021-02-10 09:32:52 +00:00
|
|
|
}
|
|
|
|
const bbox = node.getBBox();
|
|
|
|
const mat = node.transform.baseVal[0];
|
2021-02-10 15:45:36 +00:00
|
|
|
if (mat) {
|
2021-02-10 09:32:52 +00:00
|
|
|
bbox.x += mat.matrix.e;
|
|
|
|
}
|
|
|
|
// move box, and add padding between inference steps
|
|
|
|
if (left == null) {
|
2021-02-10 15:45:36 +00:00
|
|
|
// first box
|
2021-02-10 09:32:52 +00:00
|
|
|
left = bbox.x + bbox.width;
|
|
|
|
} else {
|
2021-02-10 15:45:36 +00:00
|
|
|
// this box has some elements left of it
|
|
|
|
// -> move to free space after other elements
|
2021-02-10 09:32:52 +00:00
|
|
|
mat.matrix.e -= bbox.x - left - padding;
|
|
|
|
left = bbox.x + mat.matrix.e + bbox.width;
|
|
|
|
}
|
|
|
|
if (i == 2) {
|
2021-02-10 15:45:36 +00:00
|
|
|
let parentNode = node.parentNode! as SVGGraphicsElement;
|
2021-02-10 09:32:52 +00:00
|
|
|
while (parentNode.getAttribute("semantics") !== "bspr_inferenceRule:down") {
|
2021-02-10 15:45:36 +00:00
|
|
|
parentNode = parentNode.parentNode! as SVGGraphicsElement;
|
2021-02-10 09:32:52 +00:00
|
|
|
}
|
2021-02-10 15:45:36 +00:00
|
|
|
parentNode = parentNode.children[2] as SVGGraphicsElement;
|
|
|
|
const rule = node.querySelector<SVGGraphicsElement>(inferenceRuleSelector);
|
|
|
|
if (rule) {
|
2021-02-10 09:32:52 +00:00
|
|
|
// this selector should be checked again when updating MathJax
|
2021-02-10 15:45:36 +00:00
|
|
|
const term = rule.children[1].children[0].children[0].children[1].children[0] as SVGGraphicsElement;
|
2021-02-10 09:32:52 +00:00
|
|
|
let w = -parentNode.getTransformToElement(term).e;
|
|
|
|
w += term.getBBox().width;
|
|
|
|
w += padding;
|
|
|
|
parentNode.setAttribute("x2", w.toString());
|
|
|
|
}
|
|
|
|
}
|
2021-02-04 19:30:00 +00:00
|
|
|
i += 1;
|
|
|
|
}
|
2021-02-10 09:32:52 +00:00
|
|
|
}
|
2021-02-04 19:30:00 +00:00
|
|
|
}
|
2021-06-29 14:27:25 +00:00
|
|
|
const conclusion0 = root.querySelector<SVGElement>(inferenceRuleSelector)!.children[1].children[0].children[0].children[1] as SVGGraphicsElement;
|
2021-02-10 09:32:52 +00:00
|
|
|
const conclusionWidth = conclusion0.getBBox().width;
|
|
|
|
const svgWidth = svg.viewBox.baseVal.width;
|
2021-02-10 15:45:36 +00:00
|
|
|
const offset = (svg.children[1] as SVGGraphicsElement).getTransformToElement(conclusion0);
|
|
|
|
(svg.children[1] as SVGGraphicsElement).transform.baseVal[0].matrix.e += offset.e + svgWidth / 2 - conclusionWidth / 2;
|
2021-02-10 09:32:52 +00:00
|
|
|
console.timeEnd('stepCalculation');
|
|
|
|
|
2021-07-27 10:38:36 +00:00
|
|
|
const thisShadowRoot = this.shadowRoot;
|
|
|
|
const hoverTextElID = "typicalc-hover-explainer";
|
|
|
|
let defElBackground: SVGRectElement | null;
|
|
|
|
|
2021-02-27 20:28:17 +00:00
|
|
|
if (nodeIterator.length >= 3) {
|
2021-02-04 19:30:00 +00:00
|
|
|
// should not be used on empty SVGs
|
2021-03-08 11:42:32 +00:00
|
|
|
window.svgPanZoomFun(svg, {
|
|
|
|
fit: false,
|
|
|
|
controlIconsEnabled: true,
|
|
|
|
customEventsHandler: {
|
|
|
|
// Halt all touch events
|
|
|
|
haltEventListeners: ['touchstart', 'touchend', 'touchmove', 'touchleave', 'touchcancel'],
|
|
|
|
|
|
|
|
// Init custom events handler
|
|
|
|
init: function(options) {
|
|
|
|
const instance = options.instance;
|
|
|
|
// Init Hammer
|
|
|
|
// @ts-ignore
|
|
|
|
this.hammer = Hammer(options.svgElement);
|
|
|
|
|
|
|
|
// @ts-ignore
|
2021-03-11 14:07:47 +00:00
|
|
|
this.hammer.get('pinch').set({enable: true});
|
2021-03-08 11:42:32 +00:00
|
|
|
|
|
|
|
// Handle double tap
|
|
|
|
// @ts-ignore
|
2021-03-11 14:07:47 +00:00
|
|
|
this.hammer.on('doubletap', () => {
|
2021-03-08 11:42:32 +00:00
|
|
|
options.instance.zoomIn()
|
|
|
|
});
|
|
|
|
|
|
|
|
let pannedX = 0;
|
|
|
|
let pannedY = 0;
|
|
|
|
// Handle pan
|
|
|
|
// @ts-ignore
|
2021-03-11 14:07:47 +00:00
|
|
|
this.hammer.on('panstart panmove', ev => {
|
2021-03-08 11:42:32 +00:00
|
|
|
// On pan start reset panned variables
|
|
|
|
if (ev.type === 'panstart') {
|
|
|
|
pannedX = 0
|
|
|
|
pannedY = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pan only the difference
|
|
|
|
instance.panBy({x: ev.deltaX - pannedX, y: ev.deltaY - pannedY})
|
|
|
|
pannedX = ev.deltaX
|
|
|
|
pannedY = ev.deltaY
|
2021-07-27 10:38:36 +00:00
|
|
|
// also move the tooltip
|
|
|
|
let explainer = thisShadowRoot.getElementById(hoverTextElID);
|
|
|
|
if (explainer) {
|
|
|
|
const ctm1 = svg.getBoundingClientRect();
|
|
|
|
const ctm2 = defElBackground!.getBoundingClientRect();
|
|
|
|
explainer.style.left = (ctm2.left - ctm1.left) + "px";
|
|
|
|
explainer.style.top = (ctm2.bottom - ctm1.top) + "px";
|
|
|
|
// TODO(performance): this should be more efficient, but somehow flickers
|
|
|
|
/*
|
|
|
|
const dx = (ctm2.left - ctm1.left) - explainer.offsetLeft;
|
|
|
|
const dy = (ctm2.bottom - ctm1.top) - explainer.offsetTop;
|
|
|
|
explainer.style.transform = "translate(" + dx + "px," + dy + "px)";
|
|
|
|
*/
|
|
|
|
}
|
2021-03-11 14:07:47 +00:00
|
|
|
});
|
2021-03-08 11:42:32 +00:00
|
|
|
|
|
|
|
let initialScale = 1;
|
|
|
|
// Handle pinch
|
|
|
|
// @ts-ignore
|
|
|
|
this.hammer.on('pinchstart pinchmove', function(ev){
|
|
|
|
// On pinch start remember initial zoom
|
|
|
|
if (ev.type === 'pinchstart') {
|
|
|
|
initialScale = instance.getZoom()
|
|
|
|
instance.zoomAtPoint(initialScale * ev.scale, {x: ev.center.x, y: ev.center.y})
|
|
|
|
}
|
|
|
|
|
|
|
|
instance.zoomAtPoint(initialScale * ev.scale, {x: ev.center.x, y: ev.center.y})
|
2021-03-11 14:07:47 +00:00
|
|
|
});
|
2021-03-08 11:42:32 +00:00
|
|
|
|
|
|
|
// Prevent moving the page on some devices when panning over SVG
|
|
|
|
options.svgElement.addEventListener('touchmove', function(e: TouchEvent){ e.preventDefault(); });
|
|
|
|
}
|
|
|
|
|
|
|
|
// Destroy custom events handler
|
|
|
|
, destroy: function(){
|
|
|
|
// @ts-ignore
|
|
|
|
this.hammer.destroy()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2021-03-11 13:56:01 +00:00
|
|
|
// add tooltips to buttons
|
|
|
|
const zoomIn = document.createElementNS("http://www.w3.org/2000/svg", "title");
|
|
|
|
zoomIn.append(document.createTextNode("zoom in"));
|
2021-03-12 09:59:50 +00:00
|
|
|
svg.getElementById("svg-pan-zoom-zoom-in")!.appendChild(zoomIn);
|
2021-03-11 13:56:01 +00:00
|
|
|
const zoomOut = document.createElementNS("http://www.w3.org/2000/svg", "title");
|
|
|
|
zoomOut.append(document.createTextNode("zoom out"));
|
|
|
|
svg.getElementById("svg-pan-zoom-zoom-out")!.appendChild(zoomOut);
|
|
|
|
|
2021-03-09 13:55:34 +00:00
|
|
|
// move control to upper left corner
|
2021-03-11 13:56:01 +00:00
|
|
|
let matrix = svg.getElementById("svg-pan-zoom-controls")!.transform.baseVal[0].matrix;
|
2021-03-09 13:55:34 +00:00
|
|
|
matrix.e = 0;
|
|
|
|
matrix.f = 0;
|
2021-02-04 19:30:00 +00:00
|
|
|
}
|
2021-01-28 23:19:21 +00:00
|
|
|
this.steps = steps;
|
|
|
|
this.showStep(0);
|
2021-07-20 07:35:39 +00:00
|
|
|
|
|
|
|
const viewport = svg.querySelector("#step0")!.parentNode as SVGGraphicsElement;
|
|
|
|
const handleMouseEvent = (e: MouseEvent, mouseIn: boolean) => {
|
|
|
|
let typeTarget = e.target! as SVGGraphicsElement;
|
|
|
|
let counter = 0;
|
|
|
|
while (!typeTarget.classList.contains("typicalc-type")
|
|
|
|
&& !typeTarget.classList.contains("typicalc-label")) {
|
|
|
|
typeTarget = typeTarget.parentNode! as SVGGraphicsElement;
|
|
|
|
counter++;
|
|
|
|
if (counter > 3) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let isType = typeTarget.classList.contains("typicalc-type");
|
|
|
|
let isLabel = typeTarget.classList.contains("typicalc-label");
|
|
|
|
if (mouseIn) {
|
|
|
|
if (isType) {
|
|
|
|
const typeClass = typeTarget.classList[1];
|
|
|
|
const css = "." + typeClass + " { color: red; }";
|
|
|
|
hoverStyles!.innerHTML = css;
|
|
|
|
hoverStylesUnification!.innerHTML = css;
|
|
|
|
} else if (isLabel) {
|
2021-08-29 06:32:15 +00:00
|
|
|
let stepTarget = typeTarget;
|
|
|
|
while (stepTarget.getAttribute("typicalc") !== "step" && counter < 15) {
|
|
|
|
stepTarget = stepTarget.parentNode! as SVGGraphicsElement;
|
|
|
|
counter++;
|
|
|
|
}
|
|
|
|
let stepIndex = stepTarget.getAttribute("typicalc") === "step" ? Number(stepTarget.id.substring(4)) : - 1;
|
|
|
|
|
2021-07-20 07:35:39 +00:00
|
|
|
const defId = typeTarget.classList[1].replace("-label-", "-definition-");
|
|
|
|
const defEl = this.shadowRoot!.getElementById(defId)! as unknown as SVGGraphicsElement;
|
|
|
|
const transform = viewport.getTransformToElement(typeTarget);
|
|
|
|
const offsetX = -3000;
|
|
|
|
const offsetY = 5500;
|
|
|
|
defEl.style.display = "block";
|
|
|
|
const svgRect = defEl.getBBox();
|
|
|
|
defEl.transform.baseVal[0].matrix.e = -transform.e - svgRect.width + offsetX + 1000;
|
|
|
|
defEl.transform.baseVal[0].matrix.f = -transform.f - 5500 + offsetY;
|
2021-07-27 10:38:36 +00:00
|
|
|
defElBackground = this.shadowRoot!.getElementById(defId + "-background") as SVGRectElement | null;
|
2021-07-20 07:35:39 +00:00
|
|
|
if (!defElBackground) {
|
|
|
|
defElBackground = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
|
|
defElBackground.id = defId + "-background";
|
|
|
|
defElBackground.setAttribute("x", "0");
|
|
|
|
defElBackground.setAttribute("y", "0");
|
|
|
|
defElBackground.setAttribute("width", String(svgRect.width + 2000));
|
|
|
|
defEl.parentElement!.insertBefore(defElBackground, defEl);
|
|
|
|
}
|
|
|
|
defElBackground.setAttribute("x", String(-transform.e - svgRect.width + offsetX));
|
2021-08-16 08:10:48 +00:00
|
|
|
const defElBackgroundY = -transform.f - 7000 + offsetY;
|
|
|
|
defElBackground.setAttribute("y", String(defElBackgroundY));
|
|
|
|
const defElBackgroundHeight = svgRect.height + 1000;
|
|
|
|
defElBackground.setAttribute("height", String(defElBackgroundHeight));
|
2021-07-20 07:35:39 +00:00
|
|
|
defElBackground.setAttribute("fill", "yellow");
|
|
|
|
// calculate screen coordinates
|
|
|
|
const ctm1 = svg.getBoundingClientRect();
|
|
|
|
const ctm2 = defElBackground.getBoundingClientRect();
|
|
|
|
const p = document.createElement("p");
|
|
|
|
p.id = hoverTextElID;
|
|
|
|
p.style.zIndex = String(99);
|
|
|
|
p.style.position = "absolute";
|
|
|
|
p.style.left = (ctm2.left - ctm1.left) + "px";
|
|
|
|
p.style.top = (ctm2.bottom - ctm1.top) + "px";
|
2021-07-27 13:28:56 +00:00
|
|
|
p.style.backgroundColor = "white";
|
|
|
|
p.style.padding = "5px";
|
2021-07-20 07:35:39 +00:00
|
|
|
p.innerText = data[stepIndex];
|
|
|
|
// @ts-ignore
|
2021-07-27 13:28:56 +00:00
|
|
|
window.MathJax.typesetPromise([p])
|
|
|
|
.then(() => {
|
2021-08-16 08:10:48 +00:00
|
|
|
const svgRect2 = defElBackground!.getBBox();
|
|
|
|
|
2021-07-27 13:28:56 +00:00
|
|
|
const svgP = p.getElementsByTagName("svg")[0];
|
|
|
|
const relevantElement = svgP.childNodes[1]! as SVGGraphicsElement;
|
2021-08-08 16:28:22 +00:00
|
|
|
const relevantElementBox = relevantElement.getBBox();
|
2021-07-27 13:28:56 +00:00
|
|
|
const relevantDefs = svgP.childNodes[0]!;
|
|
|
|
const ourDefs = svg.getElementsByTagName("defs")[0];
|
|
|
|
while (relevantDefs.childNodes.length > 0) {
|
|
|
|
ourDefs.appendChild(relevantDefs.childNodes[0]);
|
|
|
|
}
|
|
|
|
const insertionTarget = svg.getElementsByClassName("svg-pan-zoom_viewport")[0];
|
|
|
|
// remove previous tooltip, if possible
|
|
|
|
let explainers = svg.getElementsByClassName(hoverTextElID);
|
|
|
|
for (const explainer of explainers) {
|
|
|
|
explainer.parentNode!.removeChild(explainer);
|
|
|
|
}
|
2021-08-08 16:28:22 +00:00
|
|
|
const g = insertionTarget.ownerDocument.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
|
|
g.setAttribute("transform", "matrix(1 0 0 -1 0 0)");
|
2021-08-16 08:10:48 +00:00
|
|
|
g.transform.baseVal[0].matrix.f -= svgRect2.height;
|
2021-08-08 16:28:22 +00:00
|
|
|
g.classList.add(hoverTextElID);
|
|
|
|
g.appendChild(relevantElement);
|
|
|
|
defEl.appendChild(g);
|
2021-08-16 08:10:48 +00:00
|
|
|
// increase size of tooltip background
|
|
|
|
const padY = 1000;
|
|
|
|
defElBackground!.setAttribute("y", String(defElBackgroundY - relevantElementBox.height - padY));
|
|
|
|
defElBackground!.setAttribute("height",
|
|
|
|
String(defElBackgroundHeight + relevantElementBox.height + padY));
|
2021-07-27 13:28:56 +00:00
|
|
|
thisShadowRoot.removeChild(p);
|
|
|
|
});
|
2021-07-20 07:35:39 +00:00
|
|
|
this.shadowRoot!.appendChild(p);
|
|
|
|
|
|
|
|
if (typeTarget.classList.length >= 3) {
|
|
|
|
const stepId = typeTarget.classList[2];
|
|
|
|
hoverStylesUnification!.innerHTML = "." + stepId + " { color: red; }";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (isType) {
|
|
|
|
hoverStyles!.innerHTML = "";
|
|
|
|
hoverStylesUnification!.innerHTML = "";
|
|
|
|
} else if (isLabel) {
|
2021-07-27 13:28:56 +00:00
|
|
|
// remove previous tooltip, if possible
|
|
|
|
let explainers = svg.getElementsByClassName(hoverTextElID);
|
|
|
|
// do not use a for..of loop, it won't work
|
|
|
|
while (explainers.length > 0) {
|
|
|
|
const exp = explainers[0];
|
|
|
|
exp.parentNode!.removeChild(exp);
|
|
|
|
}
|
2021-07-20 07:35:39 +00:00
|
|
|
const defId = typeTarget.classList[1].replace("-label-", "-definition-");
|
|
|
|
this.shadowRoot!.getElementById(defId)!.style.display = "none";
|
|
|
|
let defElBackground = this.shadowRoot!.getElementById(defId + "-background");
|
2021-08-10 09:45:40 +00:00
|
|
|
defElBackground!.setAttribute("y", String(-10000));
|
2021-07-20 07:35:39 +00:00
|
|
|
defElBackground!.setAttribute("fill", "transparent");
|
|
|
|
if (typeTarget.classList.length >= 3) {
|
|
|
|
hoverStylesUnification!.innerHTML = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// listen for hover events on types
|
|
|
|
// the class "typicalc-type" is set in LatexCreatorType and in LatexCreatorTerm
|
|
|
|
svg.querySelector("#step0")!.addEventListener("mouseover", e => {
|
|
|
|
handleMouseEvent(e as MouseEvent, true);
|
|
|
|
});
|
|
|
|
svg.querySelector("#step0")!.addEventListener("mouseout", e => {
|
|
|
|
handleMouseEvent(e as MouseEvent, false);
|
|
|
|
});
|
2021-08-29 06:32:15 +00:00
|
|
|
// @ts-ignore
|
|
|
|
this.shadowRoot.host.handleHoverEvent = (e: MouseEvent, mouseIn: boolean) => {
|
|
|
|
handleMouseEvent(e, mouseIn);
|
|
|
|
}
|
2021-07-20 07:35:39 +00:00
|
|
|
|
2021-02-10 15:45:36 +00:00
|
|
|
this.$server!.setStepCount(steps.length);
|
2021-01-28 23:19:21 +00:00
|
|
|
}
|
2021-01-28 13:50:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-03 20:43:40 +00:00
|
|
|
customElements.define('tc-proof-tree', MathjaxProofTree);
|