web/public js -> ts

This commit is contained in:
Theis Pieter Hollebeek 2024-12-06 10:39:50 +01:00
parent c44c4fb9a7
commit a115cdf78c
8 changed files with 805 additions and 34 deletions

12
web/public/bundle.ts Normal file
View 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();

View File

@ -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
View 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
View 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
View 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
};

View File

@ -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>

View File

@ -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();

View File

@ -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;