mirror of
https://git.sfja.dk/Mikkel/slige.git
synced 2025-04-07 14:13:35 +01:00
186 lines
5.6 KiB
TypeScript
186 lines
5.6 KiB
TypeScript
import { CodeCovRender, loadCodeCoverage } from "./code_coverage.ts";
|
|
import * as data from "./data.ts";
|
|
import { loadFlameGraph } from "./flamegraph.ts";
|
|
|
|
function countLines(code: string) {
|
|
let lines = 0;
|
|
for (const char of code) {
|
|
if (char === "\n") lines += 1;
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
function createLineElement(code: string): HTMLPreElement {
|
|
const lines = countLines(code) + 1;
|
|
const maxLineWidth = lines.toString().length;
|
|
let text = "";
|
|
for (let i = 1; i < lines; ++i) {
|
|
const node = i.toString().padStart(maxLineWidth);
|
|
text += node;
|
|
text += "\n";
|
|
}
|
|
const lineElement = document.createElement("pre");
|
|
lineElement.classList.add("code-lines");
|
|
lineElement.innerText = text;
|
|
return lineElement;
|
|
}
|
|
|
|
async function checkStatus(): Promise<"running" | "done"> {
|
|
const status = await data.status();
|
|
|
|
if (status.running) {
|
|
return "running";
|
|
}
|
|
const statusHtml = document.querySelector<HTMLSpanElement>(
|
|
"#status",
|
|
)!;
|
|
statusHtml.innerText = "Done";
|
|
document.body.classList.remove("status-waiting");
|
|
document.body.classList.add("status-done");
|
|
return "done";
|
|
}
|
|
|
|
function sourceCode(view: Element, codeData: string) {
|
|
const outerContainer = document.createElement("div");
|
|
outerContainer.classList.add("code-container");
|
|
|
|
const innerContainer = document.createElement("div");
|
|
innerContainer.classList.add("code-container-inner");
|
|
|
|
const lines = createLineElement(codeData);
|
|
const code = document.createElement("pre");
|
|
|
|
code.classList.add("code-source");
|
|
code.innerText = codeData;
|
|
innerContainer.append(lines, code);
|
|
outerContainer.append(innerContainer);
|
|
view.replaceChildren(outerContainer);
|
|
}
|
|
|
|
function createRadio(
|
|
id: string,
|
|
content: string,
|
|
checked: boolean,
|
|
): [HTMLDivElement, HTMLInputElement] {
|
|
const label = document.createElement("label");
|
|
label.htmlFor = id;
|
|
label.innerText = content;
|
|
const input = document.createElement("input");
|
|
input.id = id;
|
|
input.name = "coverage-radio";
|
|
input.type = "radio";
|
|
input.hidden = true;
|
|
input.checked = checked;
|
|
const container = document.createElement("div");
|
|
container.classList.add("coverage-radio-group");
|
|
container.append(input, label);
|
|
return [container, input];
|
|
}
|
|
|
|
async function codeCoverage(view: Element, codeData: string) {
|
|
const codeCoverageData = await data.codeCoverageData();
|
|
|
|
const outerContainer = document.createElement("div");
|
|
outerContainer.classList.add("code-container");
|
|
outerContainer.classList.add("code-coverage");
|
|
|
|
const innerContainer = document.createElement("div");
|
|
innerContainer.classList.add("code-container-inner");
|
|
|
|
const [perfGroup, perfInput] = createRadio(
|
|
"performance-coverage",
|
|
"Performance view",
|
|
true,
|
|
);
|
|
const [testGroup, testInput] = createRadio(
|
|
"test-coverage",
|
|
"Test view",
|
|
false,
|
|
);
|
|
|
|
function load(mode: CodeCovRender, tooltip: HTMLDivElement) {
|
|
const lines = createLineElement(codeData);
|
|
const code = loadCodeCoverage(
|
|
codeData,
|
|
codeCoverageData,
|
|
tooltip,
|
|
mode,
|
|
);
|
|
innerContainer.replaceChildren(lines, code);
|
|
}
|
|
|
|
const radios = document.createElement("div");
|
|
radios.append(perfGroup, testGroup);
|
|
radios.classList.add("coverage-radio");
|
|
const tooltip = document.createElement("div");
|
|
tooltip.id = "covers-tooltip";
|
|
tooltip.hidden = true;
|
|
outerContainer.append(innerContainer);
|
|
view.replaceChildren(outerContainer, tooltip, radios);
|
|
|
|
if (perfInput.checked) {
|
|
load("performance", tooltip);
|
|
} else if (testInput.checked) {
|
|
load("test-coverage", tooltip);
|
|
}
|
|
|
|
perfInput.addEventListener("input", () => load("performance", tooltip));
|
|
testInput.addEventListener("input", () => load("test-coverage", tooltip));
|
|
}
|
|
|
|
async function main() {
|
|
type RenderFns = {
|
|
"source-code": () => void;
|
|
"code-coverage": () => void;
|
|
"flame-graph": () => void;
|
|
};
|
|
|
|
const codeData = await data.codeData();
|
|
|
|
const view = document.querySelector("#view")!;
|
|
const renderFunctions: RenderFns = {
|
|
"source-code": () => sourceCode(view, codeData),
|
|
"code-coverage": async () => await codeCoverage(view, codeData),
|
|
"flame-graph": async () => {
|
|
const flameGraphData = await data.flameGraphData();
|
|
const flameGraphFnNames = await data.flameGraphFnNames();
|
|
|
|
const container = document.createElement("div");
|
|
container.classList.add("flame-graph");
|
|
container.id = "flame-graph";
|
|
const view = document.querySelector("#view")!;
|
|
view.replaceChildren(container);
|
|
loadFlameGraph(flameGraphData, flameGraphFnNames, container);
|
|
},
|
|
};
|
|
|
|
const viewRadios: NodeListOf<HTMLInputElement> = document.querySelectorAll(
|
|
'input[name="views"]',
|
|
);
|
|
for (const input of viewRadios) {
|
|
input.addEventListener("input", (ev) => {
|
|
const target = ev.target as HTMLInputElement;
|
|
const value = target.value as keyof RenderFns;
|
|
renderFunctions[value]();
|
|
});
|
|
if (input.checked) {
|
|
const value = input.value as keyof RenderFns;
|
|
renderFunctions[value]();
|
|
}
|
|
}
|
|
|
|
checkStatus().then((status) => {
|
|
if (status == "done") {
|
|
return;
|
|
}
|
|
const interval = setInterval(async () => {
|
|
const status = await checkStatus();
|
|
if (status == "done") {
|
|
clearInterval(interval);
|
|
}
|
|
}, 500);
|
|
});
|
|
}
|
|
|
|
main();
|