Files
brk/website_next/explore/block/preview/index.js
T
2026-07-09 23:08:47 +02:00

133 lines
3.3 KiB
JavaScript

import {
loadBlockPreview,
loadBlockPreviewFilters,
} from "./data.js";
import {
createPendingPreviewFilters,
createPreviewFilters,
} from "./filters/index.js";
import { createBlockPreviewHeatmap } from "./heatmap/index.js";
import { createBlockPreviewInspector } from "./inspector.js";
function noop() {}
/**
* @param {HTMLElement} body
* @param {HTMLElement} filters
* @param {HTMLElement} [inspector]
*/
function createFigure(body, filters, inspector) {
const figure = document.createElement("figure");
const caption = document.createElement("figcaption");
figure.dataset.blockPreviewFigure = "";
caption.dataset.blockPreviewLegend = "";
caption.append(filters);
figure.append(caption, body);
if (inspector) figure.append(inspector);
return figure;
}
/**
* @template T
* @param {() => Promise<T>} load
*/
function memoize(load) {
let promise = /** @type {Promise<T> | null} */ (null);
return () => {
promise ??= load().catch((error) => {
promise = null;
throw error;
});
return promise;
};
}
/**
* @param {BlockPreviewTransaction[]} transactions
* @param {() => Promise<BlockPreviewFilterState>} loadFilters
* @param {AbortSignal} signal
*/
function createPreview(transactions, loadFilters, signal) {
const loadFilterState = memoize(loadFilters);
const inspector = createBlockPreviewInspector(signal, loadFilterState);
const heatmap = createBlockPreviewHeatmap(transactions, {
onInspect: inspector.inspect,
});
const filters = createPreviewFilters(loadFilterState, heatmap);
return {
destroy() {
inspector.destroy();
filters.destroy();
heatmap.destroy();
},
element: createFigure(heatmap.element, filters.element, inspector.element),
};
}
/**
* @param {HTMLElement} content
* @param {string} status
*/
function renderStatus(content, status) {
const p = document.createElement("p");
p.dataset.blockPreviewStatus = status;
p.textContent = status;
content.replaceChildren(createFigure(p, createPendingPreviewFilters()));
}
/**
* @param {import("../../../modules/brk-client/index.js").BlockInfoV1} block
*/
export function createBlockPreviewPane(block) {
const content = document.createElement("div");
const controller = new AbortController();
let destroyHeatmap = noop;
let live = true;
content.dataset.blockPreview = "";
renderStatus(content, "Loading");
void loadBlockPreview(block, controller.signal)
.then(({ range, transactions }) => {
if (!live) return;
const preview = createPreview(
transactions,
() => loadBlockPreviewFilters(range, controller.signal),
controller.signal,
);
destroyHeatmap = preview.destroy;
content.replaceChildren(preview.element);
})
.catch((error) => {
if (!live) return;
console.error(error);
renderStatus(content, "Unavailable");
});
return {
element: content,
destroy() {
live = false;
controller.abort();
destroyHeatmap();
destroyHeatmap = noop;
},
};
}
/** @typedef {import("./data.js").BlockPreviewTransaction} BlockPreviewTransaction */
/** @typedef {import("./data.js").BlockPreviewFilterState} BlockPreviewFilterState */
/**
* @typedef {Object} BlockPreview
* @property {() => void} destroy
* @property {HTMLElement} element
*/