mirror of
https://git.sfja.dk/Mikkel/slige.git
synced 2025-01-18 18:36:31 +00:00
web/public js -> ts
This commit is contained in:
parent
c44c4fb9a7
commit
a115cdf78c
12
web/public/bundle.ts
Normal file
12
web/public/bundle.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import * as esbuild from "npm:esbuild";
|
||||
import { denoPlugins } from "jsr:@luca/esbuild-deno-loader";
|
||||
|
||||
await esbuild.build({
|
||||
plugins: [...denoPlugins()],
|
||||
entryPoints: ["./src/index.ts"],
|
||||
outfile: "./dist/bundle.js",
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
});
|
||||
|
||||
esbuild.stop();
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"checkJs": false,
|
||||
"lib": ["dom", "dom.iterable", "dom.asynciterable"],
|
||||
"lib": ["dom", "dom.iterable", "dom.asynciterable"]
|
||||
},
|
||||
"fmt": {
|
||||
"indentWidth": 4
|
||||
|
129
web/public/deno.lock
Normal file
129
web/public/deno.lock
Normal file
@ -0,0 +1,129 @@
|
||||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@luca/esbuild-deno-loader@*": "0.11.0",
|
||||
"jsr:@std/bytes@^1.0.2": "1.0.4",
|
||||
"jsr:@std/encoding@^1.0.5": "1.0.5",
|
||||
"jsr:@std/path@^1.0.6": "1.0.8",
|
||||
"npm:esbuild@*": "0.20.2",
|
||||
"npm:esbuild@0.20.2": "0.20.2"
|
||||
},
|
||||
"jsr": {
|
||||
"@luca/esbuild-deno-loader@0.11.0": {
|
||||
"integrity": "c05a989aa7c4ee6992a27be5f15cfc5be12834cab7ff84cabb47313737c51a2c",
|
||||
"dependencies": [
|
||||
"jsr:@std/bytes",
|
||||
"jsr:@std/encoding",
|
||||
"jsr:@std/path"
|
||||
]
|
||||
},
|
||||
"@std/bytes@1.0.4": {
|
||||
"integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc"
|
||||
},
|
||||
"@std/encoding@1.0.5": {
|
||||
"integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04"
|
||||
},
|
||||
"@std/path@1.0.8": {
|
||||
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@esbuild/aix-ppc64@0.20.2": {
|
||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g=="
|
||||
},
|
||||
"@esbuild/android-arm64@0.20.2": {
|
||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg=="
|
||||
},
|
||||
"@esbuild/android-arm@0.20.2": {
|
||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w=="
|
||||
},
|
||||
"@esbuild/android-x64@0.20.2": {
|
||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg=="
|
||||
},
|
||||
"@esbuild/darwin-arm64@0.20.2": {
|
||||
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA=="
|
||||
},
|
||||
"@esbuild/darwin-x64@0.20.2": {
|
||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA=="
|
||||
},
|
||||
"@esbuild/freebsd-arm64@0.20.2": {
|
||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw=="
|
||||
},
|
||||
"@esbuild/freebsd-x64@0.20.2": {
|
||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw=="
|
||||
},
|
||||
"@esbuild/linux-arm64@0.20.2": {
|
||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A=="
|
||||
},
|
||||
"@esbuild/linux-arm@0.20.2": {
|
||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg=="
|
||||
},
|
||||
"@esbuild/linux-ia32@0.20.2": {
|
||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig=="
|
||||
},
|
||||
"@esbuild/linux-loong64@0.20.2": {
|
||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ=="
|
||||
},
|
||||
"@esbuild/linux-mips64el@0.20.2": {
|
||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA=="
|
||||
},
|
||||
"@esbuild/linux-ppc64@0.20.2": {
|
||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg=="
|
||||
},
|
||||
"@esbuild/linux-riscv64@0.20.2": {
|
||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg=="
|
||||
},
|
||||
"@esbuild/linux-s390x@0.20.2": {
|
||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ=="
|
||||
},
|
||||
"@esbuild/linux-x64@0.20.2": {
|
||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw=="
|
||||
},
|
||||
"@esbuild/netbsd-x64@0.20.2": {
|
||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ=="
|
||||
},
|
||||
"@esbuild/openbsd-x64@0.20.2": {
|
||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ=="
|
||||
},
|
||||
"@esbuild/sunos-x64@0.20.2": {
|
||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w=="
|
||||
},
|
||||
"@esbuild/win32-arm64@0.20.2": {
|
||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ=="
|
||||
},
|
||||
"@esbuild/win32-ia32@0.20.2": {
|
||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ=="
|
||||
},
|
||||
"@esbuild/win32-x64@0.20.2": {
|
||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ=="
|
||||
},
|
||||
"esbuild@0.20.2": {
|
||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
||||
"dependencies": [
|
||||
"@esbuild/aix-ppc64",
|
||||
"@esbuild/android-arm",
|
||||
"@esbuild/android-arm64",
|
||||
"@esbuild/android-x64",
|
||||
"@esbuild/darwin-arm64",
|
||||
"@esbuild/darwin-x64",
|
||||
"@esbuild/freebsd-arm64",
|
||||
"@esbuild/freebsd-x64",
|
||||
"@esbuild/linux-arm",
|
||||
"@esbuild/linux-arm64",
|
||||
"@esbuild/linux-ia32",
|
||||
"@esbuild/linux-loong64",
|
||||
"@esbuild/linux-mips64el",
|
||||
"@esbuild/linux-ppc64",
|
||||
"@esbuild/linux-riscv64",
|
||||
"@esbuild/linux-s390x",
|
||||
"@esbuild/linux-x64",
|
||||
"@esbuild/netbsd-x64",
|
||||
"@esbuild/openbsd-x64",
|
||||
"@esbuild/sunos-x64",
|
||||
"@esbuild/win32-arm64",
|
||||
"@esbuild/win32-ia32",
|
||||
"@esbuild/win32-x64"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
214
web/public/dist/bundle.js
vendored
Normal file
214
web/public/dist/bundle.js
vendored
Normal file
@ -0,0 +1,214 @@
|
||||
// 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");
|
||||
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") {
|
||||
col = 1;
|
||||
line += 1;
|
||||
elements.push("\n");
|
||||
continue;
|
||||
}
|
||||
const entry = entries.find((entry2) => index >= entry2.index);
|
||||
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;
|
||||
elements.push(span);
|
||||
col += 1;
|
||||
}
|
||||
function positionInBox(position, boundingRect) {
|
||||
const [x, y] = position;
|
||||
const outside = x < boundingRect.left || x >= boundingRect.right || y < boundingRect.top || y >= boundingRect.bottom;
|
||||
return !outside;
|
||||
}
|
||||
testytestytesty.append(...elements);
|
||||
document.addEventListener("mousemove", (event) => {
|
||||
const [x, y] = [event.clientX, event.clientY];
|
||||
const outerBox = testytestytesty.getBoundingClientRect();
|
||||
if (!positionInBox([x, y], outerBox)) {
|
||||
console.log("+");
|
||||
return;
|
||||
}
|
||||
const element = elements.find((element2) => {
|
||||
if (!element2.dataset?.covers) {
|
||||
return false;
|
||||
}
|
||||
const isIn = positionInBox([x, y], element2.getBoundingClientRect());
|
||||
return isIn;
|
||||
});
|
||||
if (!element) {
|
||||
tooltip.hidden = true;
|
||||
return;
|
||||
}
|
||||
const covers = parseInt(element.dataset.covers);
|
||||
tooltip.hidden = false;
|
||||
tooltip.style.left = `${event.clientX + 20}px`;
|
||||
tooltip.style.top = `${event.clientY + 20}px`;
|
||||
tooltip.innerText = `Ran ${covers} time${covers !== 1 ? "s" : ""}`;
|
||||
});
|
||||
}
|
||||
function loadCodeCoverage(text, codeCoverageData) {
|
||||
codeCoverageDiv.innerHTML = `
|
||||
<canvas id="code-coverage-canvas"></canvas>
|
||||
<pre><code>${text}</code></pre>
|
||||
<span id="covers-tooltip" hidden></span>
|
||||
`;
|
||||
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 = `
|
||||
<canvas id="flame-graph-canvas"></canvas>
|
||||
<span id="flame-graph-tooltip" hidden></span>
|
||||
`;
|
||||
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) {
|
||||
+ a b
|
||||
}
|
||||
|
||||
let result = 0;
|
||||
let i = 0;
|
||||
loop {
|
||||
if >= i 10 {
|
||||
break;
|
||||
}
|
||||
result = add(result, 5);
|
||||
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"]');
|
||||
for (const input of viewRadios) {
|
||||
input.addEventListener("input", (ev) => {
|
||||
console.log(ev);
|
||||
});
|
||||
}
|
||||
loadCodeCoverage(codeData, codeCoverageData);
|
||||
loadFlameGraph(flameGraphData, {
|
||||
0: "<entry>",
|
||||
12: "add",
|
||||
18: "main"
|
||||
});
|
||||
drawText(codeData, codeCoverageData);
|
||||
}
|
||||
main();
|
318
web/public/dist/bytes.esm.js
vendored
Normal file
318
web/public/dist/bytes.esm.js
vendored
Normal file
@ -0,0 +1,318 @@
|
||||
// https://deno.land/std@0.185.0/bytes/bytes_list.ts
|
||||
var BytesList = class {
|
||||
#len = 0;
|
||||
#chunks = [];
|
||||
constructor() {
|
||||
}
|
||||
/**
|
||||
* Total size of bytes
|
||||
*/
|
||||
size() {
|
||||
return this.#len;
|
||||
}
|
||||
/**
|
||||
* Push bytes with given offset infos
|
||||
*/
|
||||
add(value, start = 0, end = value.byteLength) {
|
||||
if (value.byteLength === 0 || end - start === 0) {
|
||||
return;
|
||||
}
|
||||
checkRange(start, end, value.byteLength);
|
||||
this.#chunks.push({
|
||||
value,
|
||||
end,
|
||||
start,
|
||||
offset: this.#len
|
||||
});
|
||||
this.#len += end - start;
|
||||
}
|
||||
/**
|
||||
* Drop head `n` bytes.
|
||||
*/
|
||||
shift(n) {
|
||||
if (n === 0) {
|
||||
return;
|
||||
}
|
||||
if (this.#len <= n) {
|
||||
this.#chunks = [];
|
||||
this.#len = 0;
|
||||
return;
|
||||
}
|
||||
const idx = this.getChunkIndex(n);
|
||||
this.#chunks.splice(0, idx);
|
||||
const [chunk] = this.#chunks;
|
||||
if (chunk) {
|
||||
const diff = n - chunk.offset;
|
||||
chunk.start += diff;
|
||||
}
|
||||
let offset = 0;
|
||||
for (const chunk2 of this.#chunks) {
|
||||
chunk2.offset = offset;
|
||||
offset += chunk2.end - chunk2.start;
|
||||
}
|
||||
this.#len = offset;
|
||||
}
|
||||
/**
|
||||
* Find chunk index in which `pos` locates by binary-search
|
||||
* returns -1 if out of range
|
||||
*/
|
||||
getChunkIndex(pos) {
|
||||
let max = this.#chunks.length;
|
||||
let min = 0;
|
||||
while (true) {
|
||||
const i = min + Math.floor((max - min) / 2);
|
||||
if (i < 0 || this.#chunks.length <= i) {
|
||||
return -1;
|
||||
}
|
||||
const { offset, start, end } = this.#chunks[i];
|
||||
const len = end - start;
|
||||
if (offset <= pos && pos < offset + len) {
|
||||
return i;
|
||||
} else if (offset + len <= pos) {
|
||||
min = i + 1;
|
||||
} else {
|
||||
max = i - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get indexed byte from chunks
|
||||
*/
|
||||
get(i) {
|
||||
if (i < 0 || this.#len <= i) {
|
||||
throw new Error("out of range");
|
||||
}
|
||||
const idx = this.getChunkIndex(i);
|
||||
const { value, offset, start } = this.#chunks[idx];
|
||||
return value[start + i - offset];
|
||||
}
|
||||
/**
|
||||
* Iterator of bytes from given position
|
||||
*/
|
||||
*iterator(start = 0) {
|
||||
const startIdx = this.getChunkIndex(start);
|
||||
if (startIdx < 0)
|
||||
return;
|
||||
const first = this.#chunks[startIdx];
|
||||
let firstOffset = start - first.offset;
|
||||
for (let i = startIdx; i < this.#chunks.length; i++) {
|
||||
const chunk = this.#chunks[i];
|
||||
for (let j = chunk.start + firstOffset; j < chunk.end; j++) {
|
||||
yield chunk.value[j];
|
||||
}
|
||||
firstOffset = 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns subset of bytes copied
|
||||
*/
|
||||
slice(start, end = this.#len) {
|
||||
if (end === start) {
|
||||
return new Uint8Array();
|
||||
}
|
||||
checkRange(start, end, this.#len);
|
||||
const result = new Uint8Array(end - start);
|
||||
const startIdx = this.getChunkIndex(start);
|
||||
const endIdx = this.getChunkIndex(end - 1);
|
||||
let written = 0;
|
||||
for (let i = startIdx; i <= endIdx; i++) {
|
||||
const {
|
||||
value: chunkValue,
|
||||
start: chunkStart,
|
||||
end: chunkEnd,
|
||||
offset: chunkOffset
|
||||
} = this.#chunks[i];
|
||||
const readStart = chunkStart + (i === startIdx ? start - chunkOffset : 0);
|
||||
const readEnd = i === endIdx ? end - chunkOffset + chunkStart : chunkEnd;
|
||||
const len = readEnd - readStart;
|
||||
result.set(chunkValue.subarray(readStart, readEnd), written);
|
||||
written += len;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Concatenate chunks into single Uint8Array copied.
|
||||
*/
|
||||
concat() {
|
||||
const result = new Uint8Array(this.#len);
|
||||
let sum = 0;
|
||||
for (const { value, start, end } of this.#chunks) {
|
||||
result.set(value.subarray(start, end), sum);
|
||||
sum += end - start;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
function checkRange(start, end, len) {
|
||||
if (start < 0 || len < start || end < 0 || len < end || end < start) {
|
||||
throw new Error("invalid range");
|
||||
}
|
||||
}
|
||||
|
||||
// https://deno.land/std@0.185.0/bytes/concat.ts
|
||||
function concat(...buf) {
|
||||
let length = 0;
|
||||
for (const b of buf) {
|
||||
length += b.length;
|
||||
}
|
||||
const output = new Uint8Array(length);
|
||||
let index = 0;
|
||||
for (const b of buf) {
|
||||
output.set(b, index);
|
||||
index += b.length;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// https://deno.land/std@0.185.0/bytes/copy.ts
|
||||
function copy(src, dst, off = 0) {
|
||||
off = Math.max(0, Math.min(off, dst.byteLength));
|
||||
const dstBytesAvailable = dst.byteLength - off;
|
||||
if (src.byteLength > dstBytesAvailable) {
|
||||
src = src.subarray(0, dstBytesAvailable);
|
||||
}
|
||||
dst.set(src, off);
|
||||
return src.byteLength;
|
||||
}
|
||||
|
||||
// https://deno.land/std@0.185.0/bytes/ends_with.ts
|
||||
function endsWith(source, suffix) {
|
||||
for (let srci = source.length - 1, sfxi = suffix.length - 1; sfxi >= 0; srci--, sfxi--) {
|
||||
if (source[srci] !== suffix[sfxi])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://deno.land/std@0.185.0/bytes/equals.ts
|
||||
function equalsNaive(a, b) {
|
||||
for (let i = 0; i < b.length; i++) {
|
||||
if (a[i] !== b[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function equals32Bit(a, b) {
|
||||
const len = a.length;
|
||||
const compressable = Math.floor(len / 4);
|
||||
const compressedA = new Uint32Array(a.buffer, 0, compressable);
|
||||
const compressedB = new Uint32Array(b.buffer, 0, compressable);
|
||||
for (let i = compressable * 4; i < len; i++) {
|
||||
if (a[i] !== b[i])
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < compressedA.length; i++) {
|
||||
if (compressedA[i] !== compressedB[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function equals(a, b) {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
return a.length < 1e3 ? equalsNaive(a, b) : equals32Bit(a, b);
|
||||
}
|
||||
|
||||
// https://deno.land/std@0.185.0/bytes/index_of_needle.ts
|
||||
function indexOfNeedle(source, needle, start = 0) {
|
||||
if (start >= source.length) {
|
||||
return -1;
|
||||
}
|
||||
if (start < 0) {
|
||||
start = Math.max(0, source.length + start);
|
||||
}
|
||||
const s = needle[0];
|
||||
for (let i = start; i < source.length; i++) {
|
||||
if (source[i] !== s)
|
||||
continue;
|
||||
const pin = i;
|
||||
let matched = 1;
|
||||
let j = i;
|
||||
while (matched < needle.length) {
|
||||
j++;
|
||||
if (source[j] !== needle[j - pin]) {
|
||||
break;
|
||||
}
|
||||
matched++;
|
||||
}
|
||||
if (matched === needle.length) {
|
||||
return pin;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// https://deno.land/std@0.185.0/bytes/includes_needle.ts
|
||||
function includesNeedle(source, needle, start = 0) {
|
||||
return indexOfNeedle(source, needle, start) !== -1;
|
||||
}
|
||||
|
||||
// https://deno.land/std@0.185.0/bytes/last_index_of_needle.ts
|
||||
function lastIndexOfNeedle(source, needle, start = source.length - 1) {
|
||||
if (start < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (start >= source.length) {
|
||||
start = source.length - 1;
|
||||
}
|
||||
const e = needle[needle.length - 1];
|
||||
for (let i = start; i >= 0; i--) {
|
||||
if (source[i] !== e)
|
||||
continue;
|
||||
const pin = i;
|
||||
let matched = 1;
|
||||
let j = i;
|
||||
while (matched < needle.length) {
|
||||
j--;
|
||||
if (source[j] !== needle[needle.length - 1 - (pin - j)]) {
|
||||
break;
|
||||
}
|
||||
matched++;
|
||||
}
|
||||
if (matched === needle.length) {
|
||||
return pin - needle.length + 1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// https://deno.land/std@0.185.0/bytes/repeat.ts
|
||||
function repeat(source, count) {
|
||||
if (count === 0) {
|
||||
return new Uint8Array();
|
||||
}
|
||||
if (count < 0) {
|
||||
throw new RangeError("bytes: negative repeat count");
|
||||
}
|
||||
if (!Number.isInteger(count)) {
|
||||
throw new Error("bytes: repeat count must be an integer");
|
||||
}
|
||||
const nb = new Uint8Array(source.length * count);
|
||||
let bp = copy(source, nb);
|
||||
for (; bp < nb.length; bp *= 2) {
|
||||
copy(nb.slice(0, bp), nb, bp);
|
||||
}
|
||||
return nb;
|
||||
}
|
||||
|
||||
// https://deno.land/std@0.185.0/bytes/starts_with.ts
|
||||
function startsWith(source, prefix) {
|
||||
for (let i = 0, max = prefix.length; i < max; i++) {
|
||||
if (source[i] !== prefix[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
export {
|
||||
BytesList,
|
||||
concat,
|
||||
copy,
|
||||
endsWith,
|
||||
equals,
|
||||
includesNeedle,
|
||||
indexOfNeedle,
|
||||
lastIndexOfNeedle,
|
||||
repeat,
|
||||
startsWith
|
||||
};
|
@ -3,13 +3,35 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="index.js" type="module" defer></script>
|
||||
<script src="dist/bundle.js" type="module" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<body class="status-waiting">
|
||||
<div id="main-layout">
|
||||
<header class="status-header">
|
||||
<div class="status-header-content"><span>Status:</span> <b id="status">Running</b>
|
||||
</header>
|
||||
<div id="views-layout">
|
||||
<nav id="views-nav">
|
||||
<div>
|
||||
<input type="radio" name="views" value="source-code" id="source-code-radio" checked>
|
||||
<label for="source-code-radio">Source code</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" name="views" value="code-coverage" id="code-coverage-radio">
|
||||
<label for="code-coverage-radio">Code coverage</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" name="views" value="flame-graph" id="flame-graph-radio">
|
||||
<label for="flame-graph-radio">Flame graph</label>
|
||||
</div>
|
||||
</nav>
|
||||
<main id="view">
|
||||
<div id="code-coverage"></div>
|
||||
<div id="flame-graph"></div>
|
||||
</main>
|
||||
<pre id="testytestytesty"></pre>
|
||||
<div id="cover">Process is currently running</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,9 +1,18 @@
|
||||
const codeCoverageDiv = document.querySelector("#code-coverage");
|
||||
const flameGraphDiv = document.querySelector("#flame-graph");
|
||||
|
||||
function drawText(text, codeCoverageData) {
|
||||
type CodeCovEntry = {
|
||||
index: number;
|
||||
line: number;
|
||||
col: number;
|
||||
covers: number;
|
||||
};
|
||||
function drawText(text: string, codeCoverageData: CodeCovEntry[]) {
|
||||
const tooltip = document.getElementById("covers-tooltip");
|
||||
const entries = codeCoverageData.toSorted((a, b) => b.index - a.index);
|
||||
const entries = codeCoverageData.toSorted((
|
||||
a: CodeCovEntry,
|
||||
b: CodeCovEntry,
|
||||
) => b.index - a.index);
|
||||
const charEntries = {};
|
||||
const elements = [];
|
||||
let line = 1;
|
||||
@ -79,7 +88,7 @@ function loadCodeCoverage(text, codeCoverageData) {
|
||||
ctx.font = "20px monospace";
|
||||
|
||||
const { width: chWidth } = ctx.measureText("-");
|
||||
const chHeight = 26;
|
||||
const chHeight = 23;
|
||||
|
||||
const color = (ratio) =>
|
||||
`rgba(${255 - 255 * ratio}, ${255 * ratio}, 125, 0.5)`;
|
||||
@ -226,18 +235,29 @@ loop {
|
||||
}
|
||||
`;
|
||||
|
||||
const codeCoverageData = JSON.parse(
|
||||
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(
|
||||
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":[]}]}]}`,
|
||||
);
|
||||
);
|
||||
|
||||
loadCodeCoverage(codeData, codeCoverageData);
|
||||
loadFlameGraph(flameGraphData, {
|
||||
const viewRadios = document.querySelectorAll('input[name="views"]');
|
||||
for (const input of viewRadios) {
|
||||
input.addEventListener("input", (ev) => {
|
||||
console.log(ev);
|
||||
});
|
||||
}
|
||||
|
||||
loadCodeCoverage(codeData, codeCoverageData);
|
||||
loadFlameGraph(flameGraphData, {
|
||||
0: "<entry>",
|
||||
12: "add",
|
||||
18: "main",
|
||||
});
|
||||
drawText(codeData, codeCoverageData);
|
||||
});
|
||||
drawText(codeData, codeCoverageData);
|
||||
}
|
||||
|
||||
main();
|
@ -5,6 +5,11 @@
|
||||
--bg-1: #2b2d31;
|
||||
--bg-2: #313338;
|
||||
--fg-2: #666666;
|
||||
|
||||
--black: #211F1C;
|
||||
--black-transparent: #211F1Caa;
|
||||
--white: #ECEBE9;
|
||||
--code-status: var(--white);
|
||||
}
|
||||
|
||||
* {
|
||||
@ -14,22 +19,75 @@
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
background-color: var(--bg-1);
|
||||
background-color: var(--black);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
body.status-error {
|
||||
--code-status: #FF595E;
|
||||
}
|
||||
|
||||
body.status-waiting {
|
||||
--code-status: #E3B23C;
|
||||
}
|
||||
|
||||
body.status-done {
|
||||
--code-status: #63A46C;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
}
|
||||
main > :not(#cover) {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
main #cover {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--black-transparent);
|
||||
font-size: 2.5em;
|
||||
|
||||
border-radius: 0.25rem;
|
||||
border: 2px solid var(--code-status);
|
||||
}
|
||||
|
||||
.status-header {
|
||||
font-size: 1.75rem;
|
||||
padding: 1rem;
|
||||
background-color: var(--code-status);
|
||||
color: var(--black);
|
||||
}
|
||||
.status-header-content {
|
||||
margin: 0 auto;
|
||||
max-width: 1500px;
|
||||
}
|
||||
|
||||
#views-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
border-radius: 0.25rem;
|
||||
border: 2px solid var(--code-status);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
#views-layout {
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
max-width: 1500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#code-coverage {
|
||||
width: 1000px;
|
||||
height: 500px;
|
||||
margin: 20px;
|
||||
background-color: rgb(240, 220, 200);
|
||||
}
|
||||
#code-coverage pre {
|
||||
@ -63,7 +121,6 @@ main {
|
||||
#flame-graph {
|
||||
width: 1004px;
|
||||
height: 504px;
|
||||
margin: 20px;
|
||||
background-color: var(--bg-2);
|
||||
border: 2px solid rgb(240, 220, 200);
|
||||
padding: 2px;
|
||||
|
Loading…
Reference in New Issue
Block a user