mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-19 11:19:44 -07:00
global: snapshot
This commit is contained in:
@@ -13,7 +13,7 @@ import { createRadios, createSelect } from "../utils/dom.js";
|
||||
import { createPersistedValue } from "../utils/persisted.js";
|
||||
import { onChange as onThemeChange } from "../utils/theme.js";
|
||||
import { throttle, debounce } from "../utils/timing.js";
|
||||
import { serdeBool, serdeChartableIndex } from "../utils/serde.js";
|
||||
import { serdeBool, INDEX_FROM_LABEL } from "../utils/serde.js";
|
||||
import { stringToId, numberToShortUSFormat } from "../utils/format.js";
|
||||
import { style } from "../utils/elements.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
@@ -87,22 +87,23 @@ export function createChart({ parent, brk, fitContent }) {
|
||||
const getTimeEndpoint = (idx) =>
|
||||
idx === "height"
|
||||
? brk.metrics.blocks.time.timestampMonotonic.by[idx]
|
||||
: /** @type {any} */ (brk.metrics.blocks.time.timestamp)[idx].by[idx];
|
||||
: brk.metrics.blocks.time.timestamp.by[idx];
|
||||
|
||||
const index = {
|
||||
/** @type {Set<(index: ChartableIndex) => void>} */
|
||||
onChange: new Set(),
|
||||
|
||||
get() {
|
||||
return serdeChartableIndex.deserialize(index.name.value);
|
||||
return INDEX_FROM_LABEL[index.name.value];
|
||||
},
|
||||
|
||||
name: createPersistedValue({
|
||||
defaultValue: /** @type {ChartableIndexName} */ ("date"),
|
||||
defaultValue: /** @type {IndexLabel} */ ("1d"),
|
||||
storageKey: "chart-index",
|
||||
urlKey: "i",
|
||||
serialize: (v) => v,
|
||||
deserialize: (s) => /** @type {ChartableIndexName} */ (s),
|
||||
deserialize: (s) =>
|
||||
/** @type {IndexLabel} */ (s in INDEX_FROM_LABEL ? s : "1d"),
|
||||
onChange: () => {
|
||||
range.set(null);
|
||||
index.onChange.forEach((cb) => cb(index.get()));
|
||||
@@ -323,7 +324,12 @@ export function createChart({ parent, brk, fitContent }) {
|
||||
|
||||
ichart.applyOptions({
|
||||
timeScale: {
|
||||
timeVisible: index === "height",
|
||||
timeVisible:
|
||||
index === "height" ||
|
||||
index === "difficultyepoch" ||
|
||||
index === "halvingepoch" ||
|
||||
index.startsWith("minute") ||
|
||||
index.startsWith("hour"),
|
||||
...(!fitContent
|
||||
? {
|
||||
minBarSpacing,
|
||||
@@ -1420,20 +1426,22 @@ export function createChart({ parent, brk, fitContent }) {
|
||||
/** @type {HTMLElement | null} */
|
||||
let indexField = null;
|
||||
|
||||
const lastTd = ichart.chartElement().querySelector("table > tr:last-child > td:nth-child(2)");
|
||||
const lastTd = ichart
|
||||
.chartElement()
|
||||
.querySelector("table > tr:last-child > td:nth-child(2)");
|
||||
|
||||
const chart = {
|
||||
get panes() {
|
||||
return blueprints.panes;
|
||||
},
|
||||
|
||||
/** @param {ChartableIndexName[]} choices */
|
||||
setIndexChoices(choices) {
|
||||
/** @param {{ choices: IndexLabel[], groups: { label: string, items: IndexLabel[] }[] }} arg */
|
||||
setIndexChoices({ choices, groups }) {
|
||||
if (indexField) indexField.remove();
|
||||
|
||||
let currentValue = choices.includes(preferredIndex)
|
||||
? preferredIndex
|
||||
: (choices[0] ?? "date");
|
||||
: (choices[0] ?? "1d");
|
||||
|
||||
if (currentValue !== index.name.value) {
|
||||
index.name.set(currentValue);
|
||||
@@ -1446,6 +1454,7 @@ export function createChart({ parent, brk, fitContent }) {
|
||||
index.name.set(v);
|
||||
},
|
||||
choices,
|
||||
groups,
|
||||
id: "index",
|
||||
});
|
||||
const sep = document.createElement("span");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { localhost } from "../utils/env.js";
|
||||
import { serdeChartableIndex } from "../utils/serde.js";
|
||||
import { INDEX_LABEL } from "../utils/serde.js";
|
||||
|
||||
/**
|
||||
* Check if a metric pattern has at least one chartable index
|
||||
@@ -8,7 +8,7 @@ import { serdeChartableIndex } from "../utils/serde.js";
|
||||
*/
|
||||
function hasChartableIndex(node) {
|
||||
const indexes = node.indexes();
|
||||
return indexes.some((idx) => serdeChartableIndex.serialize(idx) !== null);
|
||||
return indexes.some((idx) => idx in INDEX_LABEL);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createHeader } from "../utils/dom.js";
|
||||
import { chartElement } from "../utils/elements.js";
|
||||
import { serdeChartableIndex } from "../utils/serde.js";
|
||||
import { INDEX_FROM_LABEL } from "../utils/serde.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { createChart } from "../chart/index.js";
|
||||
import { colors } from "../utils/colors.js";
|
||||
@@ -45,7 +45,7 @@ export function init() {
|
||||
const usdPrice = {
|
||||
type: "Candlestick",
|
||||
title: "Price",
|
||||
metric: brk.metrics.prices.ohlc.usd.day1,
|
||||
metric: brk.metrics.prices.ohlc.usd,
|
||||
};
|
||||
result.set(Unit.usd, [usdPrice, ...(optionTop.get(Unit.usd) ?? [])]);
|
||||
|
||||
@@ -54,7 +54,7 @@ export function init() {
|
||||
const satsPrice = {
|
||||
type: "Candlestick",
|
||||
title: "Price",
|
||||
metric: brk.metrics.prices.ohlc.sats.day1,
|
||||
metric: brk.metrics.prices.ohlc.sats,
|
||||
colors: /** @type {const} */ ([colors.bi.p1[1], colors.bi.p1[0]]),
|
||||
};
|
||||
result.set(Unit.sats, [satsPrice, ...(optionTop.get(Unit.sats) ?? [])]);
|
||||
@@ -104,24 +104,32 @@ export function init() {
|
||||
onPrice(updatePriceWithLatest);
|
||||
}
|
||||
|
||||
const ALL_CHOICES = /** @satisfies {ChartableIndexName[]} */ ([
|
||||
"timestamp",
|
||||
"date",
|
||||
"week",
|
||||
"month",
|
||||
"month3",
|
||||
"month6",
|
||||
"year",
|
||||
"year10",
|
||||
]);
|
||||
/** @type {{ label: string, items: IndexLabel[] }[]} */
|
||||
const ALL_GROUPS = [
|
||||
{ label: "Height", items: ["blk", "halv", "diff"] },
|
||||
{
|
||||
label: "Time",
|
||||
items: [
|
||||
"1mn", "5mn", "10mn", "30mn",
|
||||
"1h", "4h", "12h",
|
||||
"1d", "3d", "1w",
|
||||
"1m", "3m", "6m",
|
||||
"1y", "10y",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const ALL_CHOICES = /** @satisfies {IndexLabel[]} */ (
|
||||
ALL_GROUPS.flatMap((g) => g.items)
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {ChartOption} opt
|
||||
* @returns {ChartableIndexName[]}
|
||||
* @returns {{ choices: IndexLabel[], groups: { label: string, items: IndexLabel[] }[] }}
|
||||
*/
|
||||
function computeChoices(opt) {
|
||||
if (!opt.top().size && !opt.bottom().size) {
|
||||
return [...ALL_CHOICES];
|
||||
return { choices: [...ALL_CHOICES], groups: ALL_GROUPS };
|
||||
}
|
||||
const rawIndexes = new Set(
|
||||
[Array.from(opt.top().values()), Array.from(opt.bottom().values())]
|
||||
@@ -133,8 +141,16 @@ function computeChoices(opt) {
|
||||
.flatMap((blueprint) => blueprint.metric.indexes()),
|
||||
);
|
||||
|
||||
return ALL_CHOICES.filter((choice) =>
|
||||
rawIndexes.has(serdeChartableIndex.deserialize(choice)),
|
||||
);
|
||||
const groups = ALL_GROUPS
|
||||
.map(({ label, items }) => ({
|
||||
label,
|
||||
items: items.filter((choice) => rawIndexes.has(INDEX_FROM_LABEL[choice])),
|
||||
}))
|
||||
.filter(({ items }) => items.length > 0);
|
||||
|
||||
return {
|
||||
choices: groups.flatMap((g) => g.items),
|
||||
groups,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
* @import { UnitObject as Unit } from "./utils/units.js"
|
||||
*
|
||||
* @import { ChartableIndexName } from "./utils/serde.js";
|
||||
* @import { ChartableIndex, IndexLabel } from "./utils/serde.js";
|
||||
*/
|
||||
|
||||
// import uFuzzy = require("./modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.d.ts");
|
||||
@@ -215,6 +215,4 @@
|
||||
* Generic tree node type for walking
|
||||
* @typedef {AnyMetricPattern | Record<string, unknown>} TreeNode
|
||||
*
|
||||
* Chartable index IDs (subset of IndexName that can be charted)
|
||||
* @typedef {"height" | "day1" | "week1" | "month1" | "month3" | "month6" | "year1" | "year10"} ChartableIndex
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,15 @@
|
||||
*/
|
||||
export const entries = (obj) => /** @type {[keyof T & string, T[keyof T & string]][]} */ (Object.entries(obj));
|
||||
|
||||
/**
|
||||
* Typed Object.fromEntries that preserves key/value types
|
||||
* @template {string} K
|
||||
* @template V
|
||||
* @param {Iterable<readonly [K, V]>} pairs
|
||||
* @returns {Record<K, V>}
|
||||
*/
|
||||
export const fromEntries = (pairs) => /** @type {Record<K, V>} */ (Object.fromEntries(pairs));
|
||||
|
||||
/**
|
||||
* Type-safe includes that narrows the value type
|
||||
* @template T
|
||||
|
||||
@@ -238,10 +238,12 @@ export function createRadios({
|
||||
* @param {(choice: T) => string} [args.toKey]
|
||||
* @param {(choice: T) => string} [args.toLabel]
|
||||
* @param {boolean} [args.sorted]
|
||||
* @param {{ label: string, items: T[] }[]} [args.groups]
|
||||
*/
|
||||
export function createSelect({
|
||||
id,
|
||||
choices: unsortedChoices,
|
||||
groups,
|
||||
initialValue,
|
||||
onChange,
|
||||
sorted,
|
||||
@@ -271,15 +273,27 @@ export function createSelect({
|
||||
select.name = id ?? "";
|
||||
field.append(select);
|
||||
|
||||
choices.forEach((choice) => {
|
||||
/** @param {T} choice */
|
||||
const createOption = (choice) => {
|
||||
const option = window.document.createElement("option");
|
||||
option.value = toKey(choice);
|
||||
option.textContent = toLabel(choice);
|
||||
if (toKey(choice) === initialKey) {
|
||||
option.selected = true;
|
||||
}
|
||||
select.append(option);
|
||||
});
|
||||
return option;
|
||||
};
|
||||
|
||||
if (groups) {
|
||||
groups.forEach(({ label, items }) => {
|
||||
const optgroup = window.document.createElement("optgroup");
|
||||
optgroup.label = label;
|
||||
items.forEach((choice) => optgroup.append(createOption(choice)));
|
||||
select.append(optgroup);
|
||||
});
|
||||
} else {
|
||||
choices.forEach((choice) => select.append(createOption(choice)));
|
||||
}
|
||||
|
||||
select.addEventListener("change", () => {
|
||||
onChange?.(fromKey(select.value));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { entries, fromEntries } from "./array.js";
|
||||
|
||||
export const serdeBool = {
|
||||
/**
|
||||
* @param {boolean} v
|
||||
@@ -17,64 +19,21 @@ export const serdeBool = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {"timestamp" | "date" | "week" | "month" | "month3" | "month6" | "year" | "year10"} ChartableIndexName
|
||||
*/
|
||||
export const INDEX_LABEL = /** @type {const} */ ({
|
||||
height: "blk",
|
||||
minute1: "1mn", minute5: "5mn", minute10: "10mn", minute30: "30mn",
|
||||
hour1: "1h", hour4: "4h", hour12: "12h",
|
||||
day1: "1d", day3: "3d", week1: "1w",
|
||||
month1: "1m", month3: "3m", month6: "6m",
|
||||
year1: "1y", year10: "10y",
|
||||
halvingepoch: "halv", difficultyepoch: "diff",
|
||||
});
|
||||
|
||||
export const serdeChartableIndex = {
|
||||
/**
|
||||
* @param {IndexName} v
|
||||
* @returns {ChartableIndexName | null}
|
||||
*/
|
||||
serialize(v) {
|
||||
switch (v) {
|
||||
case "day1":
|
||||
return "date";
|
||||
case "year10":
|
||||
return "year10";
|
||||
case "height":
|
||||
return "timestamp";
|
||||
case "month1":
|
||||
return "month";
|
||||
case "month3":
|
||||
return "month3";
|
||||
case "month6":
|
||||
return "month6";
|
||||
case "week1":
|
||||
return "week";
|
||||
case "year1":
|
||||
return "year";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {ChartableIndexName} v
|
||||
* @returns {ChartableIndex}
|
||||
*/
|
||||
deserialize(v) {
|
||||
switch (v) {
|
||||
case "timestamp":
|
||||
return "height";
|
||||
case "date":
|
||||
return "day1";
|
||||
case "week":
|
||||
return "week1";
|
||||
case "month":
|
||||
return "month1";
|
||||
case "month3":
|
||||
return "month3";
|
||||
case "month6":
|
||||
return "month6";
|
||||
case "year":
|
||||
return "year1";
|
||||
case "year10":
|
||||
return "year10";
|
||||
default:
|
||||
throw Error("todo");
|
||||
}
|
||||
},
|
||||
};
|
||||
/** @typedef {typeof INDEX_LABEL} IndexLabelMap */
|
||||
/** @typedef {keyof IndexLabelMap} ChartableIndex */
|
||||
/** @typedef {IndexLabelMap[ChartableIndex]} IndexLabel */
|
||||
|
||||
export const INDEX_FROM_LABEL = fromEntries(entries(INDEX_LABEL).map(([k, v]) => [v, k]));
|
||||
|
||||
/**
|
||||
* @typedef {"" |
|
||||
|
||||
Reference in New Issue
Block a user