global: big snapshot

This commit is contained in:
nym21
2026-04-26 23:12:17 +02:00
parent 2210443e37
commit 7a0b4b5890
125 changed files with 3833 additions and 3129 deletions

View File

@@ -1,5 +1,5 @@
/**
* @import { IChartApi, ISeriesApi as _ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData, BaselineData as _BaselineData, HistogramData as _HistogramData, SeriesType as LCSeriesType, IPaneApi, LineSeriesPartialOptions as _LineSeriesPartialOptions, HistogramSeriesPartialOptions as _HistogramSeriesPartialOptions, BaselineSeriesPartialOptions as _BaselineSeriesPartialOptions, CandlestickSeriesPartialOptions as _CandlestickSeriesPartialOptions, WhitespaceData, DeepPartial, ChartOptions, Time, LineData as _LineData, createChart as CreateLCChart, LineStyle, createSeriesMarkers as CreateSeriesMarkers, SeriesMarker, ISeriesMarkersPluginApi } from './modules/lightweight-charts/5.1.0/dist/typings.js'
* @import { IChartApi, ISeriesApi as _ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData, BaselineData as _BaselineData, HistogramData as _HistogramData, SeriesType as LCSeriesType, IPaneApi, LineSeriesPartialOptions as _LineSeriesPartialOptions, HistogramSeriesPartialOptions as _HistogramSeriesPartialOptions, BaselineSeriesPartialOptions as _BaselineSeriesPartialOptions, CandlestickSeriesPartialOptions as _CandlestickSeriesPartialOptions, WhitespaceData, DeepPartial, ChartOptions, Time, LineData as _LineData, createChart as CreateLCChart, LineStyle, createSeriesMarkers as CreateSeriesMarkers, SeriesMarker, ISeriesMarkersPluginApi } from './modules/lightweight-charts/5.2.0/dist/typings.js'
*
* @import * as Brk from "./modules/brk-client/index.js"
* @import { BrkClient, Index, SeriesData } from "./modules/brk-client/index.js"

View File

@@ -1,15 +1,19 @@
import { brk } from "../utils/client.js";
import { onPlainClick } from "../utils/dom.js";
import { createCube } from "./cube.js";
import { createHeightElement, formatFeeRate } from "./render.js";
const LOOKAHEAD = 15;
/** @type {HTMLDivElement} */ let chainEl;
/** @type {HTMLDivElement} */ let scrollEl;
/** @type {HTMLDivElement} */ let blocksEl;
/** @type {HTMLAnchorElement | null} */ let selectedCube = null;
/** @type {IntersectionObserver} */ let olderObserver;
/** @type {(block: BlockInfoV1) => void} */ let onSelect = () => {};
/** @type {(cube: HTMLAnchorElement) => void} */ let onCubeClick = () => {};
/** @type {() => void} */ let onTip = () => {};
/** @type {() => void} */ let onGenesis = () => {};
/** @type {Map<BlockHash, BlockInfoV1>} */
const blocksByHash = new Map();
@@ -22,32 +26,51 @@ let reachedTip = false;
/**
* @param {HTMLElement} parent
* @param {{ onSelect: (block: BlockInfoV1) => void, onCubeClick: (cube: HTMLAnchorElement) => void }} callbacks
* @param {{
* onSelect: (block: BlockInfoV1) => void,
* onCubeClick: (cube: HTMLAnchorElement) => void,
* onTip: () => void,
* onGenesis: () => void,
* }} callbacks
*/
export function initChain(parent, callbacks) {
onSelect = callbacks.onSelect;
onCubeClick = callbacks.onCubeClick;
onTip = callbacks.onTip;
onGenesis = callbacks.onGenesis;
chainEl = document.createElement("div");
chainEl.id = "chain";
parent.append(chainEl);
chainEl.append(
createControlLink("tip", "/block/tip", "Jump to chain tip", onTip),
);
chainEl.append(
createControlLink("gen", "/block/0", "Jump to genesis block", onGenesis),
);
scrollEl = document.createElement("div");
scrollEl.classList.add("chain-scroll");
chainEl.append(scrollEl);
blocksEl = document.createElement("div");
blocksEl.classList.add("blocks");
chainEl.append(blocksEl);
scrollEl.append(blocksEl);
olderObserver = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) loadOlder();
},
{ root: chainEl },
{ root: scrollEl },
);
chainEl.addEventListener(
scrollEl.addEventListener(
"scroll",
() => {
if (reachedTip || loadingNewer) return;
if (chainEl.scrollTop <= 50 && chainEl.scrollLeft <= 50) loadNewer();
if (scrollEl.scrollTop <= 50 && scrollEl.scrollLeft <= 50) loadNewer();
},
{ passive: true },
);
@@ -125,8 +148,8 @@ function appendNewerBlocks(blocks) {
if (anchor && anchorRect) {
const r = anchor.getBoundingClientRect();
chainEl.scrollTop += r.top - anchorRect.top;
chainEl.scrollLeft += r.left - anchorRect.left;
scrollEl.scrollTop += r.top - anchorRect.top;
scrollEl.scrollLeft += r.left - anchorRect.left;
}
return true;
}
@@ -164,6 +187,7 @@ async function resolveHeight(hashOrHeight) {
/** @param {BlockHash | Height | string | null} [hashOrHeight] @param {{ silent?: boolean }} [options] */
export async function goToCube(hashOrHeight, { silent } = {}) {
if (hashOrHeight === "tip") hashOrHeight = null;
if (typeof hashOrHeight === "string" && /^\d+$/.test(hashOrHeight)) {
hashOrHeight = Number(hashOrHeight);
}
@@ -263,14 +287,7 @@ function createBlockCube(block) {
const fill = Math.min(1, virtualSize / 1_000_000);
const { topFace, rightFace, leftFace } = createCube(cubeElement, fill);
blocksByHash.set(block.id, block);
// Intercept plain left-clicks for SPA nav; let modified clicks
// (cmd/ctrl/shift/middle) and right-click fall through so the
// anchor's native open-in-new-tab / context-menu behavior works.
cubeElement.addEventListener("click", (e) => {
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
e.preventDefault();
onCubeClick(cubeElement);
});
onPlainClick(cubeElement, () => onCubeClick(cubeElement));
const minerName = pool.name;
@@ -341,3 +358,14 @@ function appendCube(cube) {
setGap(cube);
}
/** @param {"tip" | "gen"} label @param {string} href @param {string} title @param {() => void} handler */
function createControlLink(label, href, title, handler) {
const a = document.createElement("a");
a.classList.add("chain-edge", label);
a.href = href;
a.title = title;
a.textContent = label;
onPlainClick(a, handler);
return a;
}

View File

@@ -77,6 +77,16 @@ export function init(selected) {
navigate();
selectCube(cube);
},
onTip: () => {
history.pushState(null, "", "/block/tip");
navigate();
goToCube(null);
},
onGenesis: () => {
history.pushState(null, "", "/block/0");
navigate();
goToCube(0);
},
});
initBlockDetails(explorerElement, handleLinkClick);

View File

@@ -4,7 +4,7 @@ import {
HistogramSeries,
LineSeries,
BaselineSeries,
} from "../../modules/lightweight-charts/5.1.0/dist/lightweight-charts.standalone.production.mjs";
} from "../../modules/lightweight-charts/5.2.0/dist/lightweight-charts.standalone.production.mjs";
import { createLegend, createSeriesLegend } from "./legend.js";
import { capture } from "./capture.js";
import { colors } from "../colors.js";

View File

@@ -96,6 +96,18 @@ export function createAnchorElement({
return anchor;
}
// Intercept plain left-clicks for SPA nav; let modified clicks
// (cmd/ctrl/shift/middle) and right-click fall through so the
// anchor's native open-in-new-tab / context-menu behavior works.
/** @param {HTMLElement} el @param {() => void} handler */
export function onPlainClick(el, handler) {
el.addEventListener("click", (e) => {
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
e.preventDefault();
handler();
});
}
/**
* @param {Object} arg
* @param {string | HTMLElement} arg.inside

View File

@@ -58,7 +58,6 @@
display: flex;
align-items: center;
overflow-x: auto;
scrollbar-width: thin;
padding: 0 var(--main-padding);
padding-top: 0.375rem;

View File

@@ -74,5 +74,4 @@ fieldset {
min-width: 0;
font-size: var(--font-size-sm);
line-height: var(--line-height-sm);
scrollbar-width: thin;
}

View File

@@ -125,7 +125,7 @@ h3 {
html {
background-color: var(--background-color);
color: var(--color);
scrollbar-color: var(--off-color) var(--background-color);
scrollbar-color: var(--off-color) transparent;
scrollbar-width: thin;
overflow: hidden;
}

View File

@@ -145,7 +145,6 @@ footer {
display: flex;
gap: 1.125rem;
overflow-x: auto;
scrollbar-width: thin;
min-width: 0;
margin: -0.5rem 0;
padding: 0.5rem var(--main-padding);

View File

@@ -89,16 +89,65 @@
#chain {
flex-shrink: 0;
@container aside (max-width: 767px) {
overflow-x: auto;
padding-bottom: 1rem;
}
position: relative;
padding: 0;
@container aside (min-width: 768px) {
height: 100%;
overflow-y: auto;
padding-right: calc(var(--main-padding) / 2);
}
.chain-scroll {
padding: 0 var(--main-padding);
@container aside (max-width: 767px) {
padding-bottom: 1rem;
overflow-x: auto;
}
@container aside (min-width: 768px) {
padding: var(--main-padding);
padding-right: calc(var(--main-padding) / 2);
height: 100%;
overflow-y: auto;
}
}
.chain-edge {
position: absolute;
font-size: var(--font-size-xs);
text-transform: uppercase;
letter-spacing: 0.1em;
font-weight: 500;
@container aside (max-width: 767px) {
display: flex;
height: 100%;
width: var(--main-padding);
justify-content: center;
align-items: center;
writing-mode: vertical-lr;
text-orientation: upright;
text-decoration: none;
}
@container aside (min-width: 768px) {
display: flex;
width: 100%;
height: var(--main-padding);
justify-content: center;
align-items: center;
padding-left: calc(var(--main-padding) / 2);
}
}
.tip {
@container aside (min-width: 768px) { top: 0; }
@container aside (max-width: 767px) { left: 0; }
}
.gen {
@container aside (min-width: 768px) { bottom: 0; }
@container aside (max-width: 767px) { top: 0; right: 0; }
}
.blocks {