mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
224 lines
5.8 KiB
JavaScript
224 lines
5.8 KiB
JavaScript
import { explorerElement } from "../utils/elements.js";
|
|
import { brk } from "../utils/client.js";
|
|
import { createMapCache } from "../utils/cache.js";
|
|
import {
|
|
initChain,
|
|
loadInitial,
|
|
poll,
|
|
selectCube,
|
|
deselectCube,
|
|
findCube,
|
|
clear as clearChain,
|
|
} from "./chain.js";
|
|
import {
|
|
initBlockDetails,
|
|
update as updateBlock,
|
|
show as showBlock,
|
|
hide as hideBlock,
|
|
} from "./block.js";
|
|
import {
|
|
initTxDetails,
|
|
update as updateTx,
|
|
clear as clearTx,
|
|
show as showTx,
|
|
hide as hideTx,
|
|
} from "./tx.js";
|
|
import {
|
|
initAddrDetails,
|
|
update as updateAddr,
|
|
show as showAddr,
|
|
hide as hideAddr,
|
|
} from "./address.js";
|
|
|
|
/** @returns {string[]} */
|
|
function pathSegments() {
|
|
return window.location.pathname.split("/").filter((v) => v);
|
|
}
|
|
|
|
/** @type {number | undefined} */ let pollInterval;
|
|
let navController = new AbortController();
|
|
const txCache = createMapCache(50);
|
|
|
|
function navigate() {
|
|
navController.abort();
|
|
navController = new AbortController();
|
|
return navController.signal;
|
|
}
|
|
|
|
function showPanel(/** @type {"block" | "tx" | "addr"} */ which) {
|
|
which === "block" ? showBlock() : hideBlock();
|
|
which === "tx" ? showTx() : hideTx();
|
|
which === "addr" ? showAddr() : hideAddr();
|
|
}
|
|
|
|
/** @param {MouseEvent} e */
|
|
function handleLinkClick(e) {
|
|
const a = /** @type {HTMLAnchorElement | null} */ (
|
|
/** @type {HTMLElement} */ (e.target).closest("a[href]")
|
|
);
|
|
if (!a) return;
|
|
const m = a.pathname.match(/^\/(block|tx|address)\/(.+)/);
|
|
if (!m) return;
|
|
e.preventDefault();
|
|
if (m[1] === "block") {
|
|
navigateToBlock(m[2]);
|
|
} else if (m[1] === "tx") {
|
|
history.pushState(null, "", a.href);
|
|
navigateToTx(m[2]);
|
|
} else {
|
|
history.pushState(null, "", a.href);
|
|
navigateToAddr(m[2]);
|
|
}
|
|
}
|
|
|
|
export function init() {
|
|
initChain(explorerElement, {
|
|
onSelect: (block) => {
|
|
updateBlock(block);
|
|
showPanel("block");
|
|
},
|
|
onCubeClick: (cube) => {
|
|
navigate();
|
|
const hash = cube.dataset.hash;
|
|
if (hash) history.pushState(null, "", `/block/${hash}`);
|
|
selectCube(cube);
|
|
},
|
|
});
|
|
|
|
initBlockDetails(explorerElement, handleLinkClick);
|
|
initTxDetails(explorerElement, handleLinkClick);
|
|
initAddrDetails(explorerElement, handleLinkClick);
|
|
|
|
new MutationObserver(() => {
|
|
if (explorerElement.hidden) stopPolling();
|
|
else startPolling();
|
|
}).observe(explorerElement, {
|
|
attributes: true,
|
|
attributeFilter: ["hidden"],
|
|
});
|
|
|
|
document.addEventListener("visibilitychange", () => {
|
|
if (!document.hidden && !explorerElement.hidden) poll();
|
|
});
|
|
|
|
window.addEventListener("popstate", () => {
|
|
const [kind, value] = pathSegments();
|
|
if (kind === "block" && value) navigateToBlock(value, false);
|
|
else if (kind === "tx" && value) navigateToTx(value);
|
|
else if (kind === "address" && value) navigateToAddr(value);
|
|
else showPanel("block");
|
|
});
|
|
|
|
load();
|
|
}
|
|
|
|
function startPolling() {
|
|
stopPolling();
|
|
poll();
|
|
pollInterval = setInterval(poll, 15_000);
|
|
}
|
|
|
|
function stopPolling() {
|
|
if (pollInterval !== undefined) {
|
|
clearInterval(pollInterval);
|
|
pollInterval = undefined;
|
|
}
|
|
}
|
|
|
|
async function load() {
|
|
try {
|
|
const [kind, value] = pathSegments();
|
|
|
|
if (kind === "tx" && value) {
|
|
const tx = txCache.get(value) ?? (await brk.getTx(value));
|
|
txCache.set(value, tx);
|
|
const startHash = await loadInitial(tx.status?.blockHeight ?? null);
|
|
const cube = tx.status?.blockHash ? findCube(tx.status.blockHash) : findCube(startHash);
|
|
if (cube) selectCube(cube, { silent: true });
|
|
updateTx(tx);
|
|
showPanel("tx");
|
|
return;
|
|
}
|
|
|
|
if (kind === "address" && value) {
|
|
const startHash = await loadInitial(null);
|
|
const cube = findCube(startHash);
|
|
if (cube) selectCube(cube, { silent: true });
|
|
navigateToAddr(value);
|
|
return;
|
|
}
|
|
|
|
const height =
|
|
kind === "block" && value
|
|
? /^\d+$/.test(value)
|
|
? Number(value)
|
|
: (await brk.getBlockV1(value)).height
|
|
: null;
|
|
const startHash = await loadInitial(height);
|
|
const cube = findCube(startHash);
|
|
if (cube) selectCube(cube, { scroll: "instant" });
|
|
} catch (e) {
|
|
console.error("explorer load:", e);
|
|
}
|
|
}
|
|
|
|
/** @param {string} hash @param {boolean} [pushUrl] */
|
|
async function navigateToBlock(hash, pushUrl = true) {
|
|
if (pushUrl) history.pushState(null, "", `/block/${hash}`);
|
|
const existing = findCube(hash);
|
|
if (existing) {
|
|
navigate();
|
|
selectCube(existing, { scroll: "smooth" });
|
|
return;
|
|
}
|
|
const signal = navigate();
|
|
try {
|
|
clearChain();
|
|
const height = /^\d+$/.test(hash)
|
|
? Number(hash)
|
|
: (await brk.getBlockV1(hash, { signal })).height;
|
|
if (signal.aborted) return;
|
|
const startHash = await loadInitial(height);
|
|
if (signal.aborted) return;
|
|
const cube = findCube(hash) ?? findCube(startHash);
|
|
if (cube) selectCube(cube);
|
|
} catch (e) {
|
|
if (!signal.aborted) console.error("explorer block:", e);
|
|
}
|
|
}
|
|
|
|
/** @param {string} txid */
|
|
async function navigateToTx(txid) {
|
|
const signal = navigate();
|
|
clearTx();
|
|
showPanel("tx");
|
|
try {
|
|
const tx = txCache.get(txid) ?? (await brk.getTx(txid, { signal }));
|
|
if (signal.aborted) return;
|
|
txCache.set(txid, tx);
|
|
|
|
if (tx.status?.blockHash) {
|
|
let cube = findCube(tx.status.blockHash);
|
|
if (!cube) {
|
|
clearChain();
|
|
const startHash = await loadInitial(tx.status.blockHeight ?? null);
|
|
if (signal.aborted) return;
|
|
cube = findCube(tx.status.blockHash) ?? findCube(startHash);
|
|
}
|
|
if (cube) selectCube(cube, { scroll: "smooth", silent: true });
|
|
}
|
|
|
|
updateTx(tx);
|
|
} catch (e) {
|
|
if (!signal.aborted) console.error("explorer tx:", e);
|
|
}
|
|
}
|
|
|
|
/** @param {string} address */
|
|
function navigateToAddr(address) {
|
|
navigate();
|
|
deselectCube();
|
|
updateAddr(address, navController.signal);
|
|
showPanel("addr");
|
|
}
|