mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot
This commit is contained in:
@@ -1,24 +1,3 @@
|
||||
/**
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
*/
|
||||
export function range(start, end) {
|
||||
const range = [];
|
||||
while (start <= end) {
|
||||
range.push(start);
|
||||
start += 1;
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T[]} array
|
||||
*/
|
||||
export function randomFromArray(array) {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Typed Object.entries that preserves key types
|
||||
* @template {Record<string, any>} T
|
||||
|
||||
@@ -244,20 +244,20 @@ export const colors = {
|
||||
long: palette.fuchsia,
|
||||
},
|
||||
|
||||
scriptType: seq([
|
||||
"p2pk65",
|
||||
"p2pk33",
|
||||
"p2pkh",
|
||||
"p2ms",
|
||||
"p2sh",
|
||||
"p2wpkh",
|
||||
"p2wsh",
|
||||
"p2tr",
|
||||
"p2a",
|
||||
"opreturn",
|
||||
"unknown",
|
||||
"empty",
|
||||
]),
|
||||
scriptType: {
|
||||
p2pk65: palette.rose,
|
||||
p2pk33: palette.pink,
|
||||
p2pkh: palette.orange,
|
||||
p2ms: palette.teal,
|
||||
p2sh: palette.green,
|
||||
p2wpkh: palette.red,
|
||||
p2wsh: palette.yellow,
|
||||
p2tr: palette.cyan,
|
||||
p2a: palette.indigo,
|
||||
opreturn: palette.purple,
|
||||
unknown: palette.violet,
|
||||
empty: palette.fuchsia,
|
||||
},
|
||||
|
||||
arr: paletteArr,
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
const ONE_DAY_IN_MS = 1000 * 60 * 60 * 24;
|
||||
|
||||
export function todayUTC() {
|
||||
const today = new Date();
|
||||
return new Date(
|
||||
Date.UTC(
|
||||
today.getUTCFullYear(),
|
||||
today.getUTCMonth(),
|
||||
today.getUTCDate(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} date
|
||||
*/
|
||||
export function dateToDateIndex(date) {
|
||||
if (
|
||||
date.getUTCFullYear() === 2009 &&
|
||||
date.getUTCMonth() === 0 &&
|
||||
date.getUTCDate() === 3
|
||||
)
|
||||
return 0;
|
||||
return differenceBetweenDates(date, new Date("2009-01-09"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} start
|
||||
* @param {Date} end
|
||||
*/
|
||||
export function createDateRange(start, end) {
|
||||
const dates = /** @type {Date[]} */ ([]);
|
||||
let currentDate = new Date(start);
|
||||
while (currentDate <= end) {
|
||||
dates.push(new Date(currentDate));
|
||||
currentDate.setUTCDate(currentDate.getUTCDate() + 1);
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} date1
|
||||
* @param {Date} date2
|
||||
*/
|
||||
export function differenceBetweenDates(date1, date2) {
|
||||
return Math.abs(date1.valueOf() - date2.valueOf()) / ONE_DAY_IN_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} date1
|
||||
* @param {Date} date2
|
||||
*/
|
||||
export function roundedDifferenceBetweenDates(date1, date2) {
|
||||
return Math.round(differenceBetweenDates(date1, date2));
|
||||
}
|
||||
@@ -176,6 +176,7 @@ export function createLabeledInput({
|
||||
* @param {(value: T) => void} [args.onChange]
|
||||
* @param {(choice: T) => string} [args.toKey]
|
||||
* @param {(choice: T) => string} [args.toLabel]
|
||||
* @param {(choice: T) => string | undefined} [args.toTitle]
|
||||
*/
|
||||
export function createRadios({
|
||||
id,
|
||||
@@ -184,6 +185,7 @@ export function createRadios({
|
||||
onChange,
|
||||
toKey = /** @type {(choice: T) => string} */ ((/** @type {any} */ c) => c),
|
||||
toLabel = /** @type {(choice: T) => string} */ ((/** @type {any} */ c) => c),
|
||||
toTitle,
|
||||
}) {
|
||||
const field = window.document.createElement("div");
|
||||
field.classList.add("field");
|
||||
@@ -208,6 +210,7 @@ export function createRadios({
|
||||
inputName: fieldId,
|
||||
inputValue: choiceKey,
|
||||
inputChecked: choiceKey === initialKey,
|
||||
title: toTitle?.(choice),
|
||||
type: "radio",
|
||||
});
|
||||
|
||||
@@ -314,31 +317,3 @@ export function createHeader(title = "", level = 1) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {string} Name
|
||||
* @template {string} Value
|
||||
* @template {Value | {name: Name; value: Value}} T
|
||||
* @param {T} arg
|
||||
*/
|
||||
export function createOption(arg) {
|
||||
const option = window.document.createElement("option");
|
||||
if (typeof arg === "object") {
|
||||
option.value = arg.value;
|
||||
option.innerText = arg.name;
|
||||
} else {
|
||||
option.value = arg;
|
||||
option.innerText = arg;
|
||||
}
|
||||
return option;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param {'left' | 'bottom' | 'top' | 'right'} position
|
||||
*/
|
||||
export function createShadow(position) {
|
||||
const div = window.document.createElement("div");
|
||||
div.classList.add(`shadow-${position}`);
|
||||
return div;
|
||||
}
|
||||
|
||||
@@ -10,9 +10,6 @@ export const asideElement = getElementById("aside");
|
||||
export const searchElement = getElementById("search");
|
||||
export const navElement = getElementById("nav");
|
||||
export const chartElement = getElementById("chart");
|
||||
export const tableElement = getElementById("table");
|
||||
export const explorerElement = getElementById("explorer");
|
||||
export const simulationElement = getElementById("simulation");
|
||||
|
||||
export const asideLabelElement = getElementById("aside-selector-label");
|
||||
export const navLabelElement = getElementById(`nav-selector-label`);
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
export const localhost = window.location.hostname === "localhost";
|
||||
export const standalone =
|
||||
"standalone" in window.navigator && !!window.navigator.standalone;
|
||||
export const userAgent = navigator.userAgent.toLowerCase();
|
||||
export const isChrome = userAgent.includes("chrome");
|
||||
export const safari = userAgent.includes("safari");
|
||||
export const safariOnly = safari && !isChrome;
|
||||
export const macOS = userAgent.includes("mac os");
|
||||
export const iphone = userAgent.includes("iphone");
|
||||
export const ipad = userAgent.includes("ipad");
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
const iphone = userAgent.includes("iphone");
|
||||
const ipad = userAgent.includes("ipad");
|
||||
export const ios = iphone || ipad;
|
||||
export const canShare = "canShare" in navigator;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @param {number} [digits]
|
||||
* @param {Intl.NumberFormatOptions} [options]
|
||||
*/
|
||||
export function numberToUSNumber(value, digits, options) {
|
||||
function numberToUSNumber(value, digits, options) {
|
||||
return value.toLocaleString("en-us", {
|
||||
...options,
|
||||
minimumFractionDigits: digits,
|
||||
@@ -11,19 +11,6 @@ export function numberToUSNumber(value, digits, options) {
|
||||
});
|
||||
}
|
||||
|
||||
export const numberToDollars = new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
export const numberToPercentage = new Intl.NumberFormat("en-US", {
|
||||
style: "percent",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
* @param {0 | 2} [digits]
|
||||
|
||||
36
website/scripts/utils/price.js
Normal file
36
website/scripts/utils/price.js
Normal file
@@ -0,0 +1,36 @@
|
||||
let _latest = /** @type {number | null} */ (null);
|
||||
|
||||
/** @type {Set<(price: number) => void>} */
|
||||
const listeners = new Set();
|
||||
|
||||
/** @param {(price: number) => void} callback */
|
||||
export function onPrice(callback) {
|
||||
listeners.add(callback);
|
||||
if (_latest !== null) callback(_latest);
|
||||
return () => listeners.delete(callback);
|
||||
}
|
||||
|
||||
export function latestPrice() {
|
||||
return _latest;
|
||||
}
|
||||
|
||||
/** @param {BrkClient} brk */
|
||||
export function initPrice(brk) {
|
||||
async function poll() {
|
||||
try {
|
||||
const price = await brk.getLivePrice();
|
||||
if (price !== _latest) {
|
||||
_latest = price;
|
||||
listeners.forEach((cb) => cb(price));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("price poll:", e);
|
||||
}
|
||||
}
|
||||
|
||||
poll();
|
||||
setInterval(poll, 5_000);
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
!document.hidden && poll();
|
||||
});
|
||||
}
|
||||
@@ -1,96 +1,3 @@
|
||||
const localhost = window.location.hostname === "localhost";
|
||||
console.log({ localhost });
|
||||
|
||||
export const serdeString = {
|
||||
/**
|
||||
* @param {string} v
|
||||
*/
|
||||
serialize(v) {
|
||||
return v;
|
||||
},
|
||||
/**
|
||||
* @param {string} v
|
||||
*/
|
||||
deserialize(v) {
|
||||
return v;
|
||||
},
|
||||
};
|
||||
|
||||
export const serdeMetrics = {
|
||||
/**
|
||||
* @param {Metric[]} v
|
||||
*/
|
||||
serialize(v) {
|
||||
return v.join(",");
|
||||
},
|
||||
/**
|
||||
* @param {string} v
|
||||
*/
|
||||
deserialize(v) {
|
||||
return /** @type {Metric[]} */ (v.split(","));
|
||||
},
|
||||
};
|
||||
|
||||
export const serdeNumber = {
|
||||
/**
|
||||
* @param {number} v
|
||||
*/
|
||||
serialize(v) {
|
||||
return String(v);
|
||||
},
|
||||
/**
|
||||
* @param {string} v
|
||||
*/
|
||||
deserialize(v) {
|
||||
return Number(v);
|
||||
},
|
||||
};
|
||||
|
||||
export const serdeOptNumber = {
|
||||
/**
|
||||
* @param {number | null} v
|
||||
*/
|
||||
serialize(v) {
|
||||
return v !== null ? String(v) : "";
|
||||
},
|
||||
/**
|
||||
* @param {string} v
|
||||
*/
|
||||
deserialize(v) {
|
||||
return v ? Number(v) : null;
|
||||
},
|
||||
};
|
||||
|
||||
export const serdeDate = {
|
||||
/**
|
||||
* @param {Date} date
|
||||
*/
|
||||
serialize(date) {
|
||||
return date.toString();
|
||||
},
|
||||
/**
|
||||
* @param {string} v
|
||||
*/
|
||||
deserialize(v) {
|
||||
return new Date(v);
|
||||
},
|
||||
};
|
||||
|
||||
export const serdeOptDate = {
|
||||
/**
|
||||
* @param {Date | null} date
|
||||
*/
|
||||
serialize(date) {
|
||||
return date !== null ? date.toString() : "";
|
||||
},
|
||||
/**
|
||||
* @param {string} v
|
||||
*/
|
||||
deserialize(v) {
|
||||
return new Date(v);
|
||||
},
|
||||
};
|
||||
|
||||
export const serdeBool = {
|
||||
/**
|
||||
* @param {boolean} v
|
||||
|
||||
@@ -18,7 +18,7 @@ export function onChange(callback) {
|
||||
}
|
||||
|
||||
/** @param {boolean} value */
|
||||
export function setDark(value) {
|
||||
function setDark(value) {
|
||||
if (dark === value) return;
|
||||
dark = value;
|
||||
apply(value);
|
||||
|
||||
@@ -67,35 +67,6 @@ export function writeParam(key, value) {
|
||||
replaceHistory({ urlParams });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
export function removeParam(key) {
|
||||
writeParam(key, undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
export function readBoolParam(key) {
|
||||
const param = readParam(key);
|
||||
if (param) {
|
||||
return param === "true" || param === "1";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
export function readNumberParam(key) {
|
||||
const param = readParam(key);
|
||||
if (param) {
|
||||
return Number(param);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} key
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
/**
|
||||
* @template T
|
||||
* @param {(callback: (value: T) => void) => WebSocket} creator
|
||||
*/
|
||||
function createWebsocket(creator) {
|
||||
let ws = /** @type {WebSocket | null} */ (null);
|
||||
let _live = false;
|
||||
let _latest = /** @type {T | null} */ (null);
|
||||
|
||||
/** @type {Set<(value: T) => void>} */
|
||||
const listeners = new Set();
|
||||
|
||||
function reinitWebSocket() {
|
||||
if (!ws || ws.readyState === ws.CLOSED) {
|
||||
console.log("ws: reinit");
|
||||
resource.open();
|
||||
}
|
||||
}
|
||||
|
||||
function reinitWebSocketIfDocumentNotHidden() {
|
||||
!window.document.hidden && reinitWebSocket();
|
||||
}
|
||||
|
||||
const resource = {
|
||||
live() {
|
||||
return _live;
|
||||
},
|
||||
latest() {
|
||||
return _latest;
|
||||
},
|
||||
/** @param {(value: T) => void} callback */
|
||||
onLatest(callback) {
|
||||
listeners.add(callback);
|
||||
return () => listeners.delete(callback);
|
||||
},
|
||||
open() {
|
||||
ws = creator((value) => {
|
||||
_latest = value;
|
||||
listeners.forEach((cb) => cb(value));
|
||||
});
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
console.log("ws: open");
|
||||
_live = true;
|
||||
});
|
||||
|
||||
ws.addEventListener("close", () => {
|
||||
console.log("ws: close");
|
||||
_live = false;
|
||||
});
|
||||
|
||||
window.document.addEventListener(
|
||||
"visibilitychange",
|
||||
reinitWebSocketIfDocumentNotHidden,
|
||||
);
|
||||
|
||||
window.document.addEventListener("online", reinitWebSocket);
|
||||
},
|
||||
close() {
|
||||
ws?.close();
|
||||
window.document.removeEventListener(
|
||||
"visibilitychange",
|
||||
reinitWebSocketIfDocumentNotHidden,
|
||||
);
|
||||
window.document.removeEventListener("online", reinitWebSocket);
|
||||
_live = false;
|
||||
ws = null;
|
||||
},
|
||||
};
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(candle: CandlestickData) => void} callback
|
||||
*/
|
||||
function krakenCandleWebSocketCreator(callback) {
|
||||
const ws = new WebSocket("wss://ws.kraken.com/v2");
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
method: "subscribe",
|
||||
params: {
|
||||
channel: "ohlc",
|
||||
symbol: ["BTC/USD"],
|
||||
interval: 1440,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
ws.addEventListener("message", (message) => {
|
||||
const result = JSON.parse(message.data);
|
||||
|
||||
if (result.channel !== "ohlc") return;
|
||||
|
||||
const { interval_begin, open, high, low, close } = result.data.at(-1);
|
||||
|
||||
/** @type {CandlestickData} */
|
||||
const candle = {
|
||||
// index: -1,
|
||||
time: /** @type {Time} */ (new Date(interval_begin).valueOf() / 1000),
|
||||
open: Number(open),
|
||||
high: Number(high),
|
||||
low: Number(low),
|
||||
close: Number(close),
|
||||
};
|
||||
|
||||
candle && callback({ ...candle });
|
||||
});
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
/** @type {ReturnType<typeof createWebsocket<CandlestickData>>} */
|
||||
const kraken1dCandle = createWebsocket((callback) =>
|
||||
krakenCandleWebSocketCreator(callback),
|
||||
);
|
||||
|
||||
kraken1dCandle.open();
|
||||
|
||||
export const webSockets = {
|
||||
kraken1dCandle,
|
||||
};
|
||||
/** @typedef {typeof webSockets} WebSockets */
|
||||
Reference in New Issue
Block a user