diff --git a/web/public/index.html b/web/public/index.html
index 25d1473..89abb0c 100644
--- a/web/public/index.html
+++ b/web/public/index.html
@@ -28,7 +28,6 @@
- Process is currently running
diff --git a/web/public/src/code_coverage.ts b/web/public/src/code_coverage.ts
new file mode 100644
index 0000000..c14862f
--- /dev/null
+++ b/web/public/src/code_coverage.ts
@@ -0,0 +1,131 @@
+import * as data from "./data.ts";
+
+type Color = { r: number; g: number; b: number };
+
+function lerp2(ratio: number, start: number, end: number) {
+ return (1 - ratio) * start + ratio * end;
+}
+
+function lerp3(ratio: number, start: number, middle: number, end: number) {
+ return (1 - ratio) * lerp2(ratio, start, middle) +
+ ratio * lerp2(ratio, middle, end);
+}
+
+function colorLerp(
+ ratio: number,
+ start: Color,
+ middle: Color,
+ end: Color,
+): Color {
+ return {
+ r: lerp3(ratio, start.r, middle.r, end.r),
+ g: lerp3(ratio, start.g, middle.g, end.g),
+ b: lerp3(ratio, start.b, middle.b, end.b),
+ };
+}
+
+export function loadCodeCoverage(
+ text: string,
+ input: data.CodeCovEntry[],
+ container: HTMLPreElement,
+ tooltip: HTMLElement,
+) {
+ if (input.length === 0) {
+ return;
+ }
+ const entries = input.toSorted((
+ a: data.CodeCovEntry,
+ b: data.CodeCovEntry,
+ ) => b.index - a.index);
+ const charEntries: { [key: string]: data.CodeCovEntry } = {};
+ const elements: HTMLElement[] = [];
+ let line = 1;
+ let col = 1;
+ const maxCovers = entries.map((v) => v.covers).reduce((acc, v) =>
+ acc > Math.log10(v) ? acc : Math.log10(v)
+ );
+ for (let index = 0; index < text.length; ++index) {
+ if (text[index] === "\n") {
+ col = 1;
+ line += 1;
+ const newlineSpan = document.createElement("span");
+ newlineSpan.innerText = "\n";
+ elements.push(newlineSpan);
+ continue;
+ }
+ const entry = entries.find((entry) => index >= entry.index);
+ if (!entry) {
+ throw new Error("unreachable");
+ }
+ charEntries[`${line}-${col}`] = entry;
+
+ const backgroundColor = (ratio: number) => {
+ const clr = colorLerp(ratio, { r: 42, g: 121, b: 82 }, {
+ r: 247,
+ g: 203,
+ b: 21,
+ }, {
+ r: 167,
+ g: 29,
+ b: 49,
+ });
+ return `rgb(${clr.r}, ${clr.g}, ${clr.b})`;
+ };
+
+ const span = document.createElement("span");
+ span.style.backgroundColor = backgroundColor(
+ Math.log10(entry.covers) / maxCovers,
+ );
+ span.innerText = text[index];
+ span.dataset.covers = entry.covers.toString();
+ elements.push(span);
+ col += 1;
+ }
+ function positionInBox(
+ position: [number, number],
+ boundingRect: {
+ left: number;
+ top: number;
+ right: number;
+ bottom: number;
+ },
+ ) {
+ const [x, y] = position;
+ const outside = x < boundingRect.left ||
+ x >= boundingRect.right || y < boundingRect.top ||
+ y >= boundingRect.bottom;
+ return !outside;
+ }
+ container.append(...elements);
+ document.addEventListener("mousemove", (event) => {
+ const [x, y] = [event.clientX, event.clientY];
+ const outerBox = container.getBoundingClientRect();
+ if (!positionInBox([x, y], outerBox)) {
+ tooltip.hidden = true;
+ return;
+ }
+ const element = elements.find((element) => {
+ if (typeof element === "string") {
+ return false;
+ }
+ if (!element.dataset.covers) {
+ return false;
+ }
+ const isIn = positionInBox([x, y], element.getBoundingClientRect());
+ return isIn;
+ });
+ if (!element) {
+ tooltip.hidden = true;
+ return;
+ }
+ const maybeCovers = element.dataset.covers;
+ if (!maybeCovers) {
+ throw new Error("unreachable");
+ }
+ const covers = parseInt(maybeCovers);
+ 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" : ""}`;
+ });
+}
diff --git a/web/public/src/flamegraph.ts b/web/public/src/flamegraph.ts
new file mode 100644
index 0000000..4655e90
--- /dev/null
+++ b/web/public/src/flamegraph.ts
@@ -0,0 +1,165 @@
+import * as data from "./data.ts";
+
+export function loadFlameGraph(
+ flameGraphData: data.FlameGraphNode,
+ fnNames: data.FlameGraphFnNames,
+ flameGraphDiv: HTMLDivElement,
+) {
+ flameGraphDiv.innerHTML = `
+
+
+
+
+ `;
+
+ const canvas = document.querySelector(
+ "#flame-graph-canvas",
+ )!;
+ const resetButton = document.querySelector(
+ "#flame-graph-reset",
+ )!;
+
+ canvas.width = 1000;
+ canvas.height = 500;
+
+ const fnNameFont = "600 14px monospace";
+
+ const ctx = canvas.getContext("2d")!;
+ ctx.font = fnNameFont;
+
+ type CalcNode = {
+ x: number;
+ y: number;
+ w: number;
+ h: number;
+ title: string;
+ percent: string;
+ fgNode: FlameGraphNode;
+ };
+
+ function calculateNodeRects(
+ nodes: CalcNode[],
+ node: data.FlameGraphNode,
+ depth: number,
+ totalAcc: data.FlameGraphNode["acc"],
+ offsetAcc: data.FlameGraphNode["acc"],
+ ) {
+ 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 = node.fn == 0
+ ? ""
+ : fnNames[node.fn] ?? "";
+ const percent = `${(node.acc / totalAcc * 100).toFixed(1)}%`;
+ nodes.push({ x, y, w, h, title, percent, fgNode: node });
+
+ 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(nodes, child, depth + 1, totalAcc, newOffsetAcc);
+ newOffsetAcc += child.acc;
+ }
+ }
+
+ function drawTextCanvas(node: CalcNode): HTMLCanvasElement {
+ const { w, h, title } = node;
+ const textCanvas = document.createElement("canvas");
+ textCanvas.width = Math.max(w - 8, 1);
+ textCanvas.height = h;
+ const textCtx = textCanvas.getContext("2d")!;
+ textCtx.font = fnNameFont;
+ textCtx.fillStyle = "black";
+ textCtx.fillText(
+ title,
+ ((w - 10) / 2 - ctx.measureText(title).width / 2) + 5 - 4,
+ 20,
+ );
+ return textCanvas;
+ }
+
+ function renderNodes(nodes: CalcNode[]) {
+ for (const node of nodes) {
+ const { x, y, w, h } = node;
+ ctx.fillStyle = "rgb(255, 125, 0)";
+ ctx.fillRect(
+ x + 2,
+ y + 2,
+ w - 4,
+ h - 4,
+ );
+ const textCanvas = drawTextCanvas(node);
+ ctx.drawImage(textCanvas, x + 4, y);
+ }
+ const tooltip = document.getElementById("flame-graph-tooltip")!;
+
+ const mousemoveEvent = (e: MouseEvent) => {
+ const x = e.offsetX;
+ const y = e.offsetY;
+ const node = nodes.find((node) =>
+ x >= node.x && x < node.x + node.w && y >= node.y &&
+ y < node.y + node.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;
+ };
+ const mouseleaveEvent = () => {
+ tooltip.hidden = true;
+ };
+ const mousedownEvent = (e: MouseEvent) => {
+ const x = e.offsetX;
+ const y = e.offsetY;
+ const node = nodes.find((node) =>
+ x >= node.x && x < node.x + node.w && y >= node.y &&
+ y < node.y + node.h
+ );
+ if (!node) {
+ return;
+ }
+ tooltip.hidden = true;
+ const newNodes: CalcNode[] = [];
+ calculateNodeRects(
+ newNodes,
+ node.fgNode,
+ 0,
+ node.fgNode.acc,
+ 0,
+ );
+ canvas.removeEventListener("mousemove", mousemoveEvent);
+ canvas.removeEventListener("mouseleave", mouseleaveEvent);
+ canvas.removeEventListener("mousedown", mousedownEvent);
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ renderNodes(newNodes);
+ };
+
+ canvas.addEventListener("mousemove", mousemoveEvent);
+ canvas.addEventListener("mouseleave", mouseleaveEvent);
+ canvas.addEventListener("mousedown", mousedownEvent);
+ }
+
+ resetButton.addEventListener("click", () => {
+ const nodes: CalcNode[] = [];
+ calculateNodeRects(nodes, flameGraphData, 0, flameGraphData.acc, 0);
+ renderNodes(nodes);
+ });
+
+ const nodes: CalcNode[] = [];
+ calculateNodeRects(nodes, flameGraphData, 0, flameGraphData.acc, 0);
+ renderNodes(nodes);
+}
diff --git a/web/public/src/index.ts b/web/public/src/index.ts
index dcc252d..eab7f27 100644
--- a/web/public/src/index.ts
+++ b/web/public/src/index.ts
@@ -1,298 +1,113 @@
+import { loadCodeCoverage } from "./code_coverage.ts";
import * as data from "./data.ts";
-import { FlameGraphNode } from "./data.ts";
+import { loadFlameGraph } from "./flamegraph.ts";
-type Color = { r: number; g: number; b: number };
-
-function lerp2(ratio: number, start: number, end: number) {
- return (1 - ratio) * start + ratio * end;
-}
-
-function lerp3(ratio: number, start: number, middle: number, end: number) {
- return (1 - ratio) * lerp2(ratio, start, middle) +
- ratio * lerp2(ratio, middle, end);
-}
-
-function colorLerp(
- ratio: number,
- start: Color,
- middle: Color,
- end: Color,
-): Color {
- return {
- r: lerp3(ratio, start.r, middle.r, end.r),
- g: lerp3(ratio, start.g, middle.g, end.g),
- b: lerp3(ratio, start.b, middle.b, end.b),
- };
-}
-
-function loadCodeCoverage(
- text: string,
- input: data.CodeCovEntry[],
- container: HTMLPreElement,
- tooltip: HTMLElement,
-) {
- if (input.length === 0) {
- return;
+function countLines(code: string) {
+ let lines = 0;
+ for (const char of code) {
+ if (char === "\n") lines += 1;
}
- const entries = input.toSorted((
- a: data.CodeCovEntry,
- b: data.CodeCovEntry,
- ) => b.index - a.index);
- const charEntries: { [key: string]: data.CodeCovEntry } = {};
- const elements: HTMLElement[] = [];
- let line = 1;
- let col = 1;
- const maxCovers = entries.map((v) => v.covers).reduce((acc, v) =>
- acc > Math.log10(v) ? acc : Math.log10(v)
+ 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(
+ "#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);
+}
+
+async function codeCoverage(view: Element, codeData: string) {
+ const codeCoverageData = await data.codeCoverageData();
+
+ const outerContainer = document.createElement("div");
+ outerContainer.classList.add("code-container");
+
+ const innerContainer = document.createElement("div");
+ innerContainer.classList.add("code-container-inner");
+
+ function createRadio(
+ id: string,
+ content: string,
+ ): [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;
+ const container = document.createElement("div");
+ container.classList.add("coverage-radio-group");
+ container.append(input, label);
+ return [container, input];
+ }
+ const [perfGroup, perfInput] = createRadio(
+ "performance-coverage",
+ "Performance",
);
- for (let index = 0; index < text.length; ++index) {
- if (text[index] === "\n") {
- col = 1;
- line += 1;
- const newlineSpan = document.createElement("span");
- newlineSpan.innerText = "\n";
- elements.push(newlineSpan);
- continue;
- }
- const entry = entries.find((entry) => index >= entry.index);
- if (!entry) {
- throw new Error("unreachable");
- }
- charEntries[`${line}-${col}`] = entry;
+ const [testGroup, testRadio] = createRadio("test-coverage", "Test");
- const backgroundColor = (ratio: number) => {
- const clr = colorLerp(ratio, { r: 42, g: 121, b: 82 }, {
- r: 247,
- g: 203,
- b: 21,
- }, {
- r: 167,
- g: 29,
- b: 49,
- });
- return `rgb(${clr.r}, ${clr.g}, ${clr.b})`;
- };
+ const radios = document.createElement("div");
+ radios.append(perfGroup, testGroup);
+ radios.classList.add("coverage-radio");
- const span = document.createElement("span");
- span.style.backgroundColor = backgroundColor(
- Math.log10(entry.covers) / maxCovers,
- );
- span.innerText = text[index];
- span.dataset.covers = entry.covers.toString();
- elements.push(span);
- col += 1;
- }
- function positionInBox(
- position: [number, number],
- boundingRect: {
- left: number;
- top: number;
- right: number;
- bottom: number;
- },
- ) {
- const [x, y] = position;
- const outside = x < boundingRect.left ||
- x >= boundingRect.right || y < boundingRect.top ||
- y >= boundingRect.bottom;
- return !outside;
- }
- container.append(...elements);
- document.addEventListener("mousemove", (event) => {
- const [x, y] = [event.clientX, event.clientY];
- const outerBox = container.getBoundingClientRect();
- if (!positionInBox([x, y], outerBox)) {
- tooltip.hidden = true;
- return;
- }
- const element = elements.find((element) => {
- if (typeof element === "string") {
- return false;
- }
- if (!element.dataset.covers) {
- return false;
- }
- const isIn = positionInBox([x, y], element.getBoundingClientRect());
- return isIn;
- });
- if (!element) {
- tooltip.hidden = true;
- return;
- }
- const maybeCovers = element.dataset.covers;
- if (!maybeCovers) {
- throw new Error("unreachable");
- }
- const covers = parseInt(maybeCovers);
- 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 loadFlameGraph(
- flameGraphData: data.FlameGraphNode,
- fnNames: data.FlameGraphFnNames,
- flameGraphDiv: HTMLDivElement,
-) {
- flameGraphDiv.innerHTML = `
-
-
-
-
- `;
-
- const canvas = document.querySelector(
- "#flame-graph-canvas",
- )!;
- const resetButton = document.querySelector(
- "#flame-graph-reset",
- )!;
-
- canvas.width = 1000;
- canvas.height = 500;
-
- const fnNameFont = "600 14px monospace";
-
- const ctx = canvas.getContext("2d")!;
- ctx.font = fnNameFont;
-
- type CalcNode = {
- x: number;
- y: number;
- w: number;
- h: number;
- title: string;
- percent: string;
- fgNode: FlameGraphNode;
- };
-
- function calculateNodeRects(
- nodes: CalcNode[],
- node: data.FlameGraphNode,
- depth: number,
- totalAcc: data.FlameGraphNode["acc"],
- offsetAcc: data.FlameGraphNode["acc"],
- ) {
- 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 = node.fn == 0
- ? ""
- : fnNames[node.fn] ?? "";
- const percent = `${(node.acc / totalAcc * 100).toFixed(1)}%`;
- nodes.push({ x, y, w, h, title, percent, fgNode: node });
-
- 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(nodes, child, depth + 1, totalAcc, newOffsetAcc);
- newOffsetAcc += child.acc;
- }
- }
-
- function drawTextCanvas(node: CalcNode): HTMLCanvasElement {
- const { w, h, title } = node;
- const textCanvas = document.createElement("canvas");
- textCanvas.width = Math.max(w - 8, 1);
- textCanvas.height = h;
- const textCtx = textCanvas.getContext("2d")!;
- textCtx.font = fnNameFont;
- textCtx.fillStyle = "black";
- textCtx.fillText(
- title,
- ((w - 10) / 2 - ctx.measureText(title).width / 2) + 5 - 4,
- 20,
- );
- return textCanvas;
- }
-
- function renderNodes(nodes: CalcNode[]) {
- for (const node of nodes) {
- const { x, y, w, h } = node;
- ctx.fillStyle = "rgb(255, 125, 0)";
- ctx.fillRect(
- x + 2,
- y + 2,
- w - 4,
- h - 4,
- );
- const textCanvas = drawTextCanvas(node);
- ctx.drawImage(textCanvas, x + 4, y);
- }
- const tooltip = document.getElementById("flame-graph-tooltip")!;
-
- const mousemoveEvent = (e: MouseEvent) => {
- const x = e.offsetX;
- const y = e.offsetY;
- const node = nodes.find((node) =>
- x >= node.x && x < node.x + node.w && y >= node.y &&
- y < node.y + node.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;
- };
- const mouseleaveEvent = () => {
- tooltip.hidden = true;
- };
- const mousedownEvent = (e: MouseEvent) => {
- const x = e.offsetX;
- const y = e.offsetY;
- const node = nodes.find((node) =>
- x >= node.x && x < node.x + node.w && y >= node.y &&
- y < node.y + node.h
- );
- if (!node) {
- return;
- }
- tooltip.hidden = true;
- const newNodes: CalcNode[] = [];
- calculateNodeRects(
- newNodes,
- node.fgNode,
- 0,
- node.fgNode.acc,
- 0,
- );
- canvas.removeEventListener("mousemove", mousemoveEvent);
- canvas.removeEventListener("mouseleave", mouseleaveEvent);
- canvas.removeEventListener("mousedown", mousedownEvent);
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- renderNodes(newNodes);
- };
-
- canvas.addEventListener("mousemove", mousemoveEvent);
- canvas.addEventListener("mouseleave", mouseleaveEvent);
- canvas.addEventListener("mousedown", mousedownEvent);
- }
-
- resetButton.addEventListener("click", () => {
- const nodes: CalcNode[] = [];
- calculateNodeRects(nodes, flameGraphData, 0, flameGraphData.acc, 0);
- renderNodes(nodes);
- });
-
- const nodes: CalcNode[] = [];
- calculateNodeRects(nodes, flameGraphData, 0, flameGraphData.acc, 0);
- renderNodes(nodes);
+ const tooltip = document.createElement("div");
+ tooltip.id = "covers-tooltip";
+ tooltip.hidden = true;
+ const code = document.createElement("pre");
+ code.classList.add("code-source");
+ loadCodeCoverage(
+ codeData,
+ codeCoverageData,
+ code,
+ tooltip,
+ );
+ const lines = createLineElement(codeData);
+ innerContainer.append(lines, code);
+ outerContainer.append(innerContainer);
+ view.replaceChildren(outerContainer, tooltip, radios);
}
async function main() {
@@ -302,75 +117,12 @@ async function main() {
"flame-graph": () => void;
};
- 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;
- }
-
const codeData = await data.codeData();
const view = document.querySelector("#view")!;
const renderFunctions: RenderFns = {
- "source-code": () => {
- 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);
- },
- "code-coverage": async () => {
- const codeCoverageData = await data.codeCoverageData();
-
- const outerContainer = document.createElement("div");
- outerContainer.classList.add("code-container");
-
- const innerContainer = document.createElement("div");
- innerContainer.classList.add("code-container-inner");
-
- const tooltip = document.createElement("div");
- tooltip.id = "covers-tooltip";
- tooltip.hidden = true;
- const code = document.createElement("pre");
- code.classList.add("code-source");
- loadCodeCoverage(
- codeData,
- codeCoverageData,
- code,
- tooltip,
- );
- const lines = createLineElement(codeData);
- innerContainer.append(lines, code);
- outerContainer.append(innerContainer);
- const view = document.querySelector("#view")!;
- view.replaceChildren(outerContainer, tooltip);
- },
+ "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();
@@ -399,21 +151,6 @@ async function main() {
}
}
- async function checkStatus(): Promise<"running" | "done"> {
- const status = await data.status();
-
- if (status.running) {
- return "running";
- }
- const statusHtml = document.querySelector(
- "#status",
- )!;
- statusHtml.innerText = "Done";
- document.body.classList.remove("status-waiting");
- document.body.classList.add("status-done");
- return "done";
- }
-
checkStatus().then((status) => {
if (status == "done") {
return;
diff --git a/web/public/style.css b/web/public/style.css
index 1a2e852..924de14 100644
--- a/web/public/style.css
+++ b/web/public/style.css
@@ -24,10 +24,6 @@ body {
color: var(--white);
}
-body.status-error {
- --code-status: #ff595e;
-}
-
body.status-waiting {
--code-status: #e3b23c;
}
@@ -159,6 +155,29 @@ main #cover {
color: #eee;
}
+.coverage-radio {
+ display: flex;
+ flex-direction: row;
+}
+
+.coverage-radio-group {
+ display: flex;
+ justify-content: center;
+ flex: 1;
+ padding: 2rem;
+}
+
+.coverage-radio-group label {
+ padding: 0.5rem;
+ border-radius: 0.25rem;
+ cursor: pointer;
+ border: 2px solid var(--code-status);
+}
+
+.coverage-radio-group input:checked ~ label {
+ background-color: var(--code-status);
+}
+
#flame-graph {
width: min-content;
}