mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-19 14:24:47 -07:00
global: big snapshot
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: thin;
|
||||
padding: 0 var(--main-padding);
|
||||
padding-top: 0.375rem;
|
||||
|
||||
|
||||
@@ -74,5 +74,4 @@ fieldset {
|
||||
min-width: 0;
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: var(--line-height-sm);
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user