mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-21 07:58:11 -07:00
global: opreturn part 4
This commit is contained in:
@@ -3,6 +3,15 @@ export const HALVING_EPOCH_BLOCKS = 210_000;
|
||||
|
||||
export const MAX_BLOCK_WEIGHT = 4_000_000;
|
||||
|
||||
const RELATIVE_TIME = new Intl.RelativeTimeFormat(undefined);
|
||||
const RELATIVE_UNITS = /** @type {const} */ ([
|
||||
["year", 31_557_600],
|
||||
["month", 2_629_800],
|
||||
["day", 86_400],
|
||||
["hour", 3_600],
|
||||
["minute", 60],
|
||||
]);
|
||||
|
||||
/** @param {number} value */
|
||||
export function formatNumber(value) {
|
||||
return value.toLocaleString();
|
||||
@@ -22,6 +31,23 @@ export function formatDateTime(unixSeconds) {
|
||||
});
|
||||
}
|
||||
|
||||
/** @param {number} unixSeconds */
|
||||
export function formatDateAndAge(unixSeconds) {
|
||||
const date = new Date(unixSeconds * 1_000).toLocaleDateString(undefined, {
|
||||
dateStyle: "medium",
|
||||
});
|
||||
const difference = unixSeconds - Date.now() / 1_000;
|
||||
const absolute = Math.abs(difference);
|
||||
|
||||
if (absolute < 60) return `${date} · just now`;
|
||||
|
||||
const [unit, seconds] = RELATIVE_UNITS.find(([, duration]) => {
|
||||
return absolute >= duration;
|
||||
}) ?? RELATIVE_UNITS.at(-1);
|
||||
|
||||
return `${date} · ${RELATIVE_TIME.format(Math.trunc(difference / seconds), unit)}`;
|
||||
}
|
||||
|
||||
/** @param {number} bytes */
|
||||
export function formatBytes(bytes) {
|
||||
return bytes >= 1_000_000
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createUsdAmount, renderUsdAmount } from "../../../usd/index.js";
|
||||
import { formatDateTime } from "../format.js";
|
||||
import { formatDateAndAge } from "../format.js";
|
||||
import { createBlockTitle } from "../title/index.js";
|
||||
|
||||
/** @typedef {import("../../../modules/brk-client/index.js").BlockInfoV1} Block */
|
||||
@@ -24,38 +24,37 @@ function createHashElement(hash) {
|
||||
/** @param {Node[]} [actions] */
|
||||
export function createBlockHeader(actions = []) {
|
||||
const element = document.createElement("header");
|
||||
const titleRow = document.createElement("div");
|
||||
const main = document.createElement("div");
|
||||
const title = document.createElement("h1");
|
||||
const side = document.createElement("div");
|
||||
const actionList = document.createElement("div");
|
||||
const date = document.createElement("time");
|
||||
const hash = document.createElement("p");
|
||||
const price = createUsdAmount("output", 0, {
|
||||
size: "title",
|
||||
tone: "positive",
|
||||
});
|
||||
let timestamp = 0;
|
||||
let dateTimer = 0;
|
||||
|
||||
element.dataset.blockHeader = "";
|
||||
title.dataset.blockTitleText = "";
|
||||
main.dataset.blockMain = "";
|
||||
titleRow.dataset.blockTitle = "";
|
||||
side.dataset.blockSide = "";
|
||||
actionList.dataset.blockActions = "";
|
||||
date.dataset.blockDate = "";
|
||||
hash.dataset.blockHashLine = "";
|
||||
actionList.append(...actions);
|
||||
side.append(date, price, actionList);
|
||||
main.append(title, hash);
|
||||
titleRow.append(main, side);
|
||||
element.append(titleRow);
|
||||
element.append(title, price, hash, actionList, date);
|
||||
|
||||
function refreshDate() {
|
||||
date.textContent = formatDateAndAge(timestamp);
|
||||
dateTimer = window.setTimeout(refreshDate, 60_000);
|
||||
}
|
||||
|
||||
/** @param {Block} block */
|
||||
function update(block) {
|
||||
title.replaceChildren(...createBlockTitle(block.height));
|
||||
date.dateTime = new Date(block.timestamp * 1_000).toISOString();
|
||||
date.textContent = formatDateTime(block.timestamp);
|
||||
timestamp = block.timestamp;
|
||||
window.clearTimeout(dateTimer);
|
||||
refreshDate();
|
||||
hash.replaceChildren(createHashElement(block.id));
|
||||
renderUsdAmount(price, block.extras.price, {
|
||||
size: "title",
|
||||
tone: "positive",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,44 +5,41 @@
|
||||
--block-title-label-size: 2.5rem;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 0.15rem 1rem;
|
||||
align-items: baseline;
|
||||
padding-bottom: 1.25rem;
|
||||
|
||||
[data-dim] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
[data-block-title] {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 0.35rem 1rem;
|
||||
align-items: start;
|
||||
> h1 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-block-main] {
|
||||
display: grid;
|
||||
gap: 0.15rem;
|
||||
min-width: 0;
|
||||
> output {
|
||||
justify-self: end;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-block-date],
|
||||
[data-block-hash-line] {
|
||||
> p,
|
||||
> time {
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
[data-block-date] {
|
||||
> p {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
[data-block-side] {
|
||||
display: grid;
|
||||
gap: 0.15rem;
|
||||
justify-items: end;
|
||||
min-width: max-content;
|
||||
> time {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-block-actions] {
|
||||
> div {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
@@ -60,11 +57,4 @@
|
||||
padding: 0.25rem 0.375rem;
|
||||
}
|
||||
}
|
||||
|
||||
[data-block-hash-line] {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,12 +34,8 @@ export function orderTransactions(weights, feeRates) {
|
||||
* @param {number} rows
|
||||
*/
|
||||
export function packTransactions(cells, columns, rows) {
|
||||
let layouts = packCells(cells, columns, rows);
|
||||
|
||||
while (layouts === null) {
|
||||
rows += 1;
|
||||
layouts = packCells(cells, columns, rows);
|
||||
}
|
||||
|
||||
return { layouts, resolvedCells: cells };
|
||||
return {
|
||||
layouts: packCells(cells, columns, rows),
|
||||
resolvedCells: cells,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @param {readonly PackCell[]} cells
|
||||
* @param {number} columns
|
||||
* @param {number} rows
|
||||
* @returns {PackLayout[] | null}
|
||||
* @returns {PackLayout[]}
|
||||
*/
|
||||
export function packCells(cells, columns, rows) {
|
||||
const occupied = new Uint8Array(columns * rows);
|
||||
@@ -10,8 +10,8 @@ export function packCells(cells, columns, rows) {
|
||||
const layouts = [];
|
||||
|
||||
for (const cell of cells) {
|
||||
const span = Math.min(cell.span, columns);
|
||||
const position = findPosition(
|
||||
let span = Math.min(cell.span, columns);
|
||||
let position = findPosition(
|
||||
occupied,
|
||||
columns,
|
||||
rows,
|
||||
@@ -19,7 +19,16 @@ export function packCells(cells, columns, rows) {
|
||||
cursors[span],
|
||||
);
|
||||
|
||||
if (position === null) return null;
|
||||
while (position === null) {
|
||||
span -= 1;
|
||||
position = findPosition(
|
||||
occupied,
|
||||
columns,
|
||||
rows,
|
||||
span,
|
||||
cursors[span],
|
||||
);
|
||||
}
|
||||
|
||||
fillCells(occupied, columns, position.x, position.y, span);
|
||||
cursors[span] = position.index + span;
|
||||
|
||||
@@ -76,6 +76,7 @@ function createReceiptBrand() {
|
||||
/** @param {Block} block */
|
||||
function openReceiptDialog(block) {
|
||||
const dialog = document.createElement("dialog");
|
||||
const content = document.createElement("main");
|
||||
const paper = document.createElement("article");
|
||||
const controls = document.createElement("footer");
|
||||
const print = document.createElement("button");
|
||||
@@ -104,14 +105,15 @@ function openReceiptDialog(block) {
|
||||
createReceiptQr(block, url),
|
||||
createReceiptBrand(),
|
||||
);
|
||||
dialog.append(paper, controls);
|
||||
content.append(paper);
|
||||
dialog.append(content, controls);
|
||||
|
||||
print.addEventListener("click", () => {
|
||||
window.print();
|
||||
});
|
||||
openDialog(dialog, document.body);
|
||||
dialog.focus({ preventScroll: true });
|
||||
dialog.scrollTop = 0;
|
||||
content.scrollTop = 0;
|
||||
}
|
||||
|
||||
export function createBlockReceipt() {
|
||||
|
||||
@@ -10,18 +10,25 @@ dialog[data-block-receipt] {
|
||||
position: fixed;
|
||||
inset: 0 0 auto;
|
||||
width: min(100% - 2rem, 28rem);
|
||||
height: 100dvh;
|
||||
max-height: 100dvh;
|
||||
margin: 0 auto;
|
||||
border-radius: 0;
|
||||
overflow-y: auto;
|
||||
padding-block: var(--receipt-dialog-padding-block);
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
color: var(--black);
|
||||
background: transparent;
|
||||
font-family: var(--font-mono);
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: none;
|
||||
translate: none;
|
||||
|
||||
> main {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding-block: var(--receipt-dialog-padding-block);
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
:is(
|
||||
[data-receipt-section] > h3,
|
||||
[data-receipt-row] > span,
|
||||
@@ -190,7 +197,7 @@ dialog[data-block-receipt] {
|
||||
}
|
||||
|
||||
[data-receipt-controls] {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
bottom: 0.5rem;
|
||||
left: 50%;
|
||||
z-index: 1;
|
||||
@@ -265,6 +272,7 @@ dialog[data-block-receipt] {
|
||||
position: static;
|
||||
display: block;
|
||||
width: var(--receipt-page-width);
|
||||
height: auto;
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
margin: 0;
|
||||
@@ -275,6 +283,12 @@ dialog[data-block-receipt] {
|
||||
overscroll-behavior: auto;
|
||||
}
|
||||
|
||||
dialog[data-block-receipt] > main {
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dialog[data-block-receipt]::backdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user