diff --git a/web/public/.gitignore b/web/public/.gitignore new file mode 100644 index 0000000..849ddff --- /dev/null +++ b/web/public/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/web/public/deno.jsonc b/web/public/deno.jsonc index 801f699..97a7ef2 100644 --- a/web/public/deno.jsonc +++ b/web/public/deno.jsonc @@ -1,6 +1,11 @@ { + "tasks": { + "bundle": "deno run -A bundle.ts", + "dev": "deno run --watch -A bundle.ts" + }, "compilerOptions": { - "lib": ["dom", "dom.iterable", "dom.asynciterable"] + "checkJs": false, + "lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"] }, "fmt": { "indentWidth": 4 diff --git a/web/public/dist/bundle.js b/web/public/dist/bundle.js index e7c790d..dab7b11 100644 --- a/web/public/dist/bundle.js +++ b/web/public/dist/bundle.js @@ -1,27 +1,32 @@ // src/index.ts -var codeCoverageDiv = document.querySelector("#code-coverage"); -var flameGraphDiv = document.querySelector("#flame-graph"); -function drawText(text, codeCoverageData) { - const tooltip = document.getElementById("covers-tooltip"); +function loadCodeCoverage(text, codeCoverageData, codeCoverageDiv) { + const tooltip = document.createElement("span"); + tooltip.id = "covers-tooltip"; + codeCoverageDiv.append(tooltip); const entries = codeCoverageData.toSorted((a, b) => b.index - a.index); const charEntries = {}; const elements = []; let line = 1; let col = 1; for (let index = 0; index < text.length; ++index) { - if (text[index] == "\n") { + if (text[index] === "\n") { col = 1; line += 1; - elements.push("\n"); + const newlineSpan = document.createElement("span"); + newlineSpan.innerText = "\n"; + elements.push(newlineSpan); continue; } const entry = entries.find((entry2) => index >= entry2.index); + if (!entry) { + throw new Error("unreachable"); + } charEntries[`${line}-${col}`] = entry; const color = (ratio) => `rgba(${255 - 255 * ratio}, ${255 * ratio}, 125, 0.5)`; const span = document.createElement("span"); span.style.backgroundColor = color(Math.min(entry.covers / 25, 1)); span.innerText = text[index]; - span.dataset.covers = entry.covers; + span.dataset.covers = entry.covers.toString(); elements.push(span); col += 1; } @@ -30,16 +35,18 @@ function drawText(text, codeCoverageData) { const outside = x < boundingRect.left || x >= boundingRect.right || y < boundingRect.top || y >= boundingRect.bottom; return !outside; } - testytestytesty.append(...elements); + codeCoverageDiv.append(...elements); document.addEventListener("mousemove", (event) => { const [x, y] = [event.clientX, event.clientY]; - const outerBox = testytestytesty.getBoundingClientRect(); + const outerBox = codeCoverageDiv.getBoundingClientRect(); if (!positionInBox([x, y], outerBox)) { - console.log("+"); return; } const element = elements.find((element2) => { - if (!element2.dataset?.covers) { + if (typeof element2 === "string") { + return false; + } + if (!element2.dataset.covers) { return false; } const isIn = positionInBox([x, y], element2.getBoundingClientRect()); @@ -56,127 +63,8 @@ function drawText(text, codeCoverageData) { tooltip.innerText = `Ran ${covers} time${covers !== 1 ? "s" : ""}`; }); } -function loadCodeCoverage(text, codeCoverageData) { - codeCoverageDiv.innerHTML = ` - -
${text}
-
- `;
- const canvas = document.querySelector("#code-coverage-canvas");
- canvas.width = 1e3;
- canvas.height = 500;
- const ctx = canvas.getContext("2d");
- ctx.font = "20px monospace";
- const { width: chWidth } = ctx.measureText("-");
- const chHeight = 23;
- const color = (ratio) => `rgba(${255 - 255 * ratio}, ${255 * ratio}, 125, 0.5)`;
- const entries = codeCoverageData.toSorted((a, b) => b.index - a.index);
- const charEntries = {};
- let line = 1;
- let col = 1;
- for (let index = 0; index < text.length; ++index) {
- if (text[index] == "\n") {
- col = 1;
- line += 1;
- continue;
- }
- const entry = entries.find((entry2) => index >= entry2.index);
- charEntries[`${line}-${col}`] = entry;
- ctx.fillStyle = color(Math.min(entry.covers / 25, 1));
- ctx.fillRect(
- (col - 1) * chWidth,
- (line - 1) * chHeight,
- chWidth,
- chHeight
- );
- col += 1;
- }
- const tooltip = document.getElementById("covers-tooltip");
- canvas.addEventListener("mousemove", (e) => {
- const col2 = Math.floor(e.offsetX / chWidth + 1);
- const line2 = Math.floor(e.offsetY / chHeight + 1);
- const key = `${line2}-${col2}`;
- if (!(key in charEntries)) {
- tooltip.hidden = true;
- return;
- }
- const entry = charEntries[key];
- tooltip.innerText = `Ran ${entry.covers} time${entry.covers !== 1 ? "s" : ""}`;
- tooltip.style.left = `${e.clientX + 20}px`;
- tooltip.style.top = `${e.clientY + 20}px`;
- tooltip.hidden = false;
- });
- canvas.addEventListener("mouseleave", () => {
- tooltip.hidden = true;
- });
-}
-function loadFlameGraph(flameGraphData, fnNames) {
- flameGraphDiv.innerHTML = `
-
-
- `;
- const canvas = document.querySelector("#flame-graph-canvas");
- canvas.width = 1e3;
- canvas.height = 500;
- const ctx = canvas.getContext("2d");
- ctx.font = "16px monospace";
- const nodes = [];
- function calculateNodeRects(node, depth, totalAcc, offsetAcc) {
- const x = offsetAcc / totalAcc * canvas.width;
- const y = canvas.height - 30 * depth - 30;
- const w = (node.acc + 1) / totalAcc * canvas.width;
- const h = 30;
- const title = fnNames[node.fn];
- const percent = `${(node.acc / totalAcc * 100).toFixed(1)}%`;
- nodes.push({ x, y, w, h, title, percent });
- const totalChildrenAcc = node.children.reduce(
- (acc, child) => acc + child.acc,
- 0
- );
- let newOffsetAcc = offsetAcc + (node.acc - totalChildrenAcc) / 2;
- for (const child of node.children) {
- calculateNodeRects(child, depth + 1, totalAcc, newOffsetAcc);
- newOffsetAcc += child.acc;
- }
- }
- calculateNodeRects(flameGraphData, 0, flameGraphData.acc, 0);
- for (const node of nodes) {
- const { x, y, w, h, title } = node;
- ctx.fillStyle = "rgb(255, 125, 0)";
- ctx.fillRect(
- x + 1,
- y + 1,
- w - 2,
- h - 2
- );
- ctx.fillStyle = "black";
- ctx.fillText(
- title,
- x + (w - 10) / 2 - ctx.measureText(title).width / 2 + 5,
- y + 20
- );
- }
- const tooltip = document.getElementById("flame-graph-tooltip");
- canvas.addEventListener("mousemove", (e) => {
- const x = e.offsetX;
- const y = e.offsetY;
- const node = nodes.find(
- (node2) => x >= node2.x && x < node2.x + node2.w && y >= node2.y && y < node2.y + node2.h
- );
- if (!node) {
- tooltip.hidden = true;
- return;
- }
- tooltip.innerText = `${node.title} ${node.percent}`;
- tooltip.style.left = `${e.clientX + 20}px`;
- tooltip.style.top = `${e.clientY + 20}px`;
- tooltip.hidden = false;
- });
- canvas.addEventListener("mouseleave", () => {
- tooltip.hidden = true;
- });
-}
-var codeData = `fn add(a, b) {
+function main() {
+ const codeData = `fn add(a, b) {
+ a b
}
@@ -190,25 +78,41 @@ loop {
i = + i 1;
}
`;
-function main() {
const codeCoverageData = JSON.parse(
`[{"index":0,"line":1,"col":1,"covers":2},{"index":28,"line":5,"col":1,"covers":1},{"index":44,"line":6,"col":1,"covers":1},{"index":55,"line":7,"col":1,"covers":1},{"index":66,"line":8,"col":5,"covers":11},{"index":104,"line":11,"col":5,"covers":10},{"index":19,"line":2,"col":5,"covers":10},{"index":133,"line":12,"col":5,"covers":10},{"index":87,"line":9,"col":9,"covers":1}]`
);
const flameGraphData = JSON.parse(
`{"fn":0,"acc":257,"parent":0,"children":[{"fn":18,"acc":251,"parent":0,"children":[{"fn":12,"acc":30,"parent":1,"children":[]}]}]}`
);
- const viewRadios = document.querySelectorAll('input[name="views"]');
+ const view = document.querySelector("#view");
+ const renderFunctions = {
+ "source-code": () => {
+ const code = document.createElement("pre");
+ code.innerHTML = codeData;
+ view.replaceChildren(code);
+ },
+ "code-coverage": () => {
+ const codeCoverageElement = document.createElement("pre");
+ loadCodeCoverage(codeData, codeCoverageData, codeCoverageElement);
+ const view2 = document.querySelector("#view");
+ view2.replaceChildren(codeCoverageElement);
+ },
+ "flame-graph": () => {
+ }
+ };
+ const viewRadios = document.querySelectorAll(
+ 'input[name="views"]'
+ );
for (const input of viewRadios) {
input.addEventListener("input", (ev) => {
- console.log(ev);
+ const target = ev.target;
+ const value = target.value;
+ renderFunctions[value]();
});
+ if (input.checked) {
+ const value = input.value;
+ renderFunctions[value]();
+ }
}
- loadCodeCoverage(codeData, codeCoverageData);
- loadFlameGraph(flameGraphData, {
- 0: "