Files
brk/website/scripts/panes/explorer.js
2026-03-31 22:53:25 +02:00

191 lines
5.5 KiB
JavaScript

import { explorerElement } from "../utils/elements.js";
import { brk } from "../client.js";
/** @type {HTMLDivElement} */
let chain;
/** @type {HTMLDivElement} */
let sentinel;
let newestHeight = -1;
let oldestHeight = Infinity;
let loading = false;
/** @type {number | undefined} */
let pollInterval;
function startPolling() {
stopPolling();
loadLatest();
pollInterval = setInterval(loadLatest, 15_000);
}
function stopPolling() {
if (pollInterval !== undefined) {
clearInterval(pollInterval);
pollInterval = undefined;
}
}
export function init() {
chain = window.document.createElement("div");
chain.id = "chain";
explorerElement.append(chain);
sentinel = window.document.createElement("div");
sentinel.classList.add("sentinel");
chain.append(sentinel);
// Infinite scroll: load older blocks when sentinel becomes visible
new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
loadOlder();
}
}).observe(sentinel);
// Self-contained lifecycle: poll when visible, stop when hidden
new MutationObserver(() => {
if (explorerElement.hidden) {
stopPolling();
} else {
startPolling();
}
}).observe(explorerElement, {
attributes: true,
attributeFilter: ["hidden"],
});
document.addEventListener("visibilitychange", () => {
if (!document.hidden && !explorerElement.hidden) {
loadLatest();
}
});
loadLatest();
}
async function loadLatest() {
if (loading) return;
loading = true;
try {
const blocks = await brk.getBlocksV1();
// First load: insert all blocks before sentinel
if (newestHeight === -1) {
for (const block of blocks) {
sentinel.after(createBlockCube(block));
}
newestHeight = blocks[0].height;
oldestHeight = blocks[blocks.length - 1].height;
} else {
// Subsequent polls: prepend only new blocks
const newBlocks = blocks.filter((b) => b.height > newestHeight);
if (newBlocks.length) {
// sentinel.after(createBlockCube(block));
sentinel.after(...newBlocks.map((b) => createBlockCube(b)));
newestHeight = newBlocks[0].height;
}
}
} catch (e) {
console.error("explorer poll:", e);
}
loading = false;
}
async function loadOlder() {
if (loading || oldestHeight <= 0) return;
loading = true;
try {
const blocks = await brk.getBlocksV1FromHeight(oldestHeight - 1);
for (const block of blocks) {
sentinel.after(createBlockCube(block));
}
if (blocks.length) {
oldestHeight = blocks[blocks.length - 1].height;
}
} catch (e) {
console.error("explorer loadOlder:", e);
}
loading = false;
}
/** @param {BlockInfoV1} block */
function createBlockCube(block) {
const { cubeElement, leftFaceElement, rightFaceElement, topFaceElement } =
createCube();
// cubeElement.style.setProperty("--face-color", `var(--${color})`);
const heightElement = window.document.createElement("p");
const height = block.height.toString();
const prefixLength = 7 - height.length;
const spanPrefix = window.document.createElement("span");
spanPrefix.style.opacity = "0.5";
spanPrefix.style.userSelect = "none";
heightElement.append(spanPrefix);
spanPrefix.innerHTML = "#" + "0".repeat(prefixLength);
const spanHeight = window.document.createElement("span");
heightElement.append(spanHeight);
spanHeight.innerHTML = height;
rightFaceElement.append(heightElement);
const feesElement = window.document.createElement("div");
feesElement.classList.add("fees");
leftFaceElement.append(feesElement);
const extras = block.extras;
const medianFee = extras ? extras.medianFee : 0;
const feeRange = extras ? extras.feeRange : [0, 0, 0, 0, 0, 0, 0];
const averageFeeElement = window.document.createElement("p");
feesElement.append(averageFeeElement);
averageFeeElement.innerHTML = `~${Number(medianFee).toFixed(2)}`;
const feeRangeElement = window.document.createElement("p");
feesElement.append(feeRangeElement);
const minFeeElement = window.document.createElement("span");
minFeeElement.innerHTML = `${Number(feeRange[0]).toFixed(2)}`;
feeRangeElement.append(minFeeElement);
const dashElement = window.document.createElement("span");
dashElement.style.opacity = "0.5";
dashElement.innerHTML = `-`;
feeRangeElement.append(dashElement);
const maxFeeElement = window.document.createElement("span");
maxFeeElement.innerHTML = `${Number(feeRange[6]).toFixed(1)}`;
feeRangeElement.append(maxFeeElement);
const feeUnitElement = window.document.createElement("p");
feesElement.append(feeUnitElement);
feeUnitElement.style.opacity = "0.5";
feeUnitElement.innerHTML = `sat/vB`;
const spanMiner = window.document.createElement("span");
spanMiner.innerHTML = extras ? extras.pool.name : "Unknown";
topFaceElement.append(spanMiner);
return cubeElement;
}
function createCube() {
const cubeElement = window.document.createElement("div");
cubeElement.classList.add("cube");
const rightFaceElement = window.document.createElement("div");
rightFaceElement.classList.add("face");
rightFaceElement.classList.add("right");
cubeElement.append(rightFaceElement);
const leftFaceElement = window.document.createElement("div");
leftFaceElement.classList.add("face");
leftFaceElement.classList.add("left");
cubeElement.append(leftFaceElement);
const topFaceElement = window.document.createElement("div");
topFaceElement.classList.add("face");
topFaceElement.classList.add("top");
cubeElement.append(topFaceElement);
return {
cubeElement,
leftFaceElement,
rightFaceElement,
topFaceElement,
};
}