Files
brk/websites/kibo.money/scripts/database.js
2025-04-21 23:17:37 +02:00

461 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @ts-check
/**
* @param {Object} args
* @param {VecIdToIndexes} args.vecIdToIndexes
* @param {Utilities} args.utils
* @param {Signals} args.signals
* @param {VecsResources} args.vecsResources
*/
function createTable({ utils, vecIdToIndexes, signals, vecsResources }) {
const indexToVecIds = createIndexToVecIds(vecIdToIndexes);
const serializedIndexes = createSerializedIndexes();
/** @type {SerializedIndex} */
const defaultSerializedIndex = "height";
const serializedIndex = signals.createSignal({
name: /** @type {SerializedIndex} */ (defaultSerializedIndex),
value: String(serializedIndexToIndex(defaultSerializedIndex)),
});
const index = signals.createMemo(() =>
serializedIndexToIndex(serializedIndex().name),
);
const table = window.document.createElement("table");
const obj = {
element: table,
/** @type {VoidFunction | undefined} */
addRandomCol: undefined,
};
signals.createEffect(index, (index) => {
table.innerHTML = "";
const thead = window.document.createElement("thead");
table.append(thead);
const trHead = window.document.createElement("tr");
const selects = signals.createSignal(
/** @type {HTMLSelectElement[]} */ ([]),
{
equals: false,
},
);
thead.append(trHead);
const tbody = window.document.createElement("tbody");
table.append(tbody);
const rowElements = signals.createSignal(
/** @type {HTMLTableRowElement[]} */ ([]),
);
/**
* @param {Object} args
* @param {HTMLSelectElement} args.select
* @param {VoidFunction} [args.onLeft]
* @param {VoidFunction} [args.onRight]
* @param {VoidFunction} [args.onRemove]
*/
function addThCol({ select, onLeft, onRight, onRemove }) {
const th = window.document.createElement("th");
th.scope = "col";
trHead.append(th);
const div = window.document.createElement("div");
div.append(select);
const strip = window.document.createElement("div");
const unit = window.document.createElement("span");
const moveLeft = utils.dom.createButtonElement({
inside: "←",
title: "Move column to the left",
onClick: onLeft || (() => {}),
});
const moveRight = utils.dom.createButtonElement({
inside: "→",
title: "Move column to the right",
onClick: onRight || (() => {}),
});
const remove = utils.dom.createButtonElement({
inside: "×",
title: "Remove column",
onClick: onRemove || (() => {}),
});
strip.append(unit);
strip.append(moveLeft);
strip.append(moveRight);
strip.append(remove);
div.append(strip);
th.append(div);
return {
element: th,
/**
* @param {Unit} _unit
*/
setUnit(_unit) {
unit.innerHTML = _unit;
},
};
}
const { select } = utils.dom.createSelect({
id: "col-index",
list: serializedIndexes.map((serializedIndex) => ({
name: serializedIndex,
value: String(serializedIndexToIndex(serializedIndex)),
})),
signal: serializedIndex,
});
const th = addThCol({ select });
th.setUnit("Index");
vecsResources
.getOrCreate(index, serializedIndex().name)
.fetch()
.then((vec) => {
if (!vec) return;
const trs = /** @type {HTMLTableRowElement[]} */ ([]);
for (let i = vec.length - 1; i >= 0; i--) {
const value = vec[i];
const tr = window.document.createElement("tr");
trs.push(tr);
tbody.append(tr);
const th = window.document.createElement("th");
th.innerHTML = String(value);
th.scope = "row";
tr.append(th);
}
rowElements.set(() => trs);
});
const columnIds = signals.createSignal(/** @type {VecId[]} */ ([]), {
equals: false,
});
const owner = signals.getOwner();
const possibleVecids = indexToVecIds[index];
obj.addRandomCol = function () {
signals.runWithOwner(owner, () => {
const vecId =
possibleVecids[Math.round(Math.random() * possibleVecids.length)];
const colIndex = signals.createSignal(columnIds().length);
const vecIdOption = signals.createSignal({
name: vecId,
value: vecId,
});
const { select } = utils.dom.createSelect({
id: `col-${colIndex() + 1}`,
list: possibleVecids.map((vecId) => ({
name: vecId,
value: vecId,
})),
signal: vecIdOption,
});
selects.set((l) => {
l.push(select);
return l;
});
/**
* @param {boolean} right
*/
function createMoveColumnFunction(right) {
return () =>
colIndex.set((oldI) => {
const newI = oldI + (right ? 1 : -1);
const currentSelect = selects()[oldI];
const currentSelectSibling = currentSelect.nextSibling;
const newSelect = selects()[newI];
[selects()[oldI], selects()[newI]] = [
selects()[newI],
selects()[oldI],
];
console.log(oldI, newI, selects());
const newSelectSibling = newSelect.nextSibling;
newSelectSibling?.before(currentSelect);
currentSelectSibling?.before(newSelect);
return newI;
});
}
const th = addThCol({
select,
onLeft: createMoveColumnFunction(false),
onRight: createMoveColumnFunction(true),
});
signals.createEffect(rowElements, (rowElements) => {
if (!rowElements.length) return;
for (let i = 0; i < rowElements.length; i++) {
const td = window.document.createElement("td");
rowElements[i].append(td);
}
signals.createEffect(vecIdOption, ({ name: vecId }) => {
const unit = utils.vecidToUnit(vecId);
th.setUnit(unit);
const valuesPromise = vecsResources
.getOrCreate(index, vecId)
.fetch();
columnIds.set((l) => {
if (columnIds().length === colIndex()) {
l.push(vecId);
} else {
l[colIndex()] = vecId;
}
console.log(l);
return l;
});
valuesPromise.then((vec) => {
if (!vec) return;
// const diff = vec.length - rowElements.length;
for (let i = 0; i < rowElements.length; i++) {
const iRev = vec.length - 1 - i;
const value = vec[iRev];
/** @type {string | number | undefined} */
let serialized;
if (typeof value !== "number") {
serialized = value;
} else if (value !== 18446744073709552000) {
if (
unit === "USD" ||
unit === "Difficulty" ||
unit === "sat/vB"
) {
serialized = value.toLocaleString("en-us", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
} else if (unit === "BTC") {
serialized = value.toLocaleString("en-us", {
minimumFractionDigits: 8,
maximumFractionDigits: 8,
});
} else {
serialized = value.toLocaleString("en-us");
}
}
signals.runWithOwner(owner, () => {
signals.createEffect(colIndex, (colIndex) => {
// @ts-ignore
rowElements[i].childNodes[colIndex + 1].innerHTML =
serialized;
});
});
}
});
});
});
});
// signals.runWithOwner(owner, () => {
// const thIndex = thHead.length;
// const possibleVecids = indexToVecIds[index()];
// const i = Math.round(Math.random() * possibleVecids.length);
// const vecId = signals.createSignal({
// name: possibleVecids[i],
// value: possibleVecids[i],
// });
// const th = addThCol();
// const { select } = utils.dom.createSelect({
// id: `col-${vecId}`,
// list: possibleVecids.map((vecId) => ({
// name: vecId,
// value: vecId,
// })),
// signal: vecId,
// });
// th.append(select);
// signals.createEffect(
// () => /** @type {const} */ ([index(), vecId(), rowElements()]),
// ([index, vecId, trsBody]) => {
// if (!trsBody.length) return;
// vecsResources
// .getOrCreate(index, vecId.name)
// .fetch()
// .then((vec) => {
// if (!vec) return;
// console.log({ vec, trsBody, index });
// for (let i = 0; i < vec.length; i++) {
// const iRev = vec.length - 1 - i;
// const value = vec[iRev];
// const td = window.document.createElement("td");
// td.innerHTML = String(value);
// trsBody[i].append(td);
// }
// });
// },
// );
// });
};
// setTimeout(addCol, 2000);
// addRandomCol();
// addRandomCol();
// addRandomCol();
});
return obj;
}
/**
* @param {Object} args
* @param {Colors} args.colors
* @param {Signals} args.signals
* @param {Utilities} args.utils
* @param {Elements} args.elements
* @param {VecsResources} args.vecsResources
* @param {VecIdToIndexes} args.vecIdToIndexes
*/
export function init({
colors,
elements,
signals,
utils,
vecsResources,
vecIdToIndexes,
}) {
const parent = elements.database;
const { headerElement } = utils.dom.createHeader({
title: "Database",
});
parent.append(headerElement);
const div = window.document.createElement("div");
parent.append(div);
const table = createTable({
signals,
utils,
vecIdToIndexes,
vecsResources,
});
div.append(table.element);
const span = window.document.createElement("span");
span.innerHTML = "Add column";
div.append(
utils.dom.createButtonElement({
onClick: () => {
table.addRandomCol?.();
},
inside: span,
title: "Click or tap to add a column to the table",
}),
);
}
function createSerializedIndexes() {
return /** @type {const} */ ([
/** @satisfies {VecId} */ ("height"),
/** @satisfies {VecId} */ ("dateindex"),
/** @satisfies {VecId} */ ("weekindex"),
/** @satisfies {VecId} */ ("difficultyepoch"),
/** @satisfies {VecId} */ ("monthindex"),
/** @satisfies {VecId} */ ("quarterindex"),
/** @satisfies {VecId} */ ("yearindex"),
/** @satisfies {VecId} */ ("decadeindex"),
/** @satisfies {VecId} */ ("halvingepoch"),
/** @satisfies {VecId} */ ("addressindex"),
/** @satisfies {VecId} */ ("p2pk33index"),
/** @satisfies {VecId} */ ("p2pk65index"),
/** @satisfies {VecId} */ ("p2pkhindex"),
/** @satisfies {VecId} */ ("p2shindex"),
/** @satisfies {VecId} */ ("p2trindex"),
/** @satisfies {VecId} */ ("p2wpkhindex"),
/** @satisfies {VecId} */ ("p2wshindex"),
/** @satisfies {VecId} */ ("txindex"),
/** @satisfies {VecId} */ ("txinindex"),
/** @satisfies {VecId} */ ("txoutindex"),
/** @satisfies {VecId} */ ("emptyindex"),
/** @satisfies {VecId} */ ("multisigindex"),
/** @satisfies {VecId} */ ("opreturnindex"),
/** @satisfies {VecId} */ ("pushonlyindex"),
/** @satisfies {VecId} */ ("unknownindex"),
]);
}
/** @typedef {ReturnType<typeof createSerializedIndexes>} SerializedIndexes */
/** @typedef {SerializedIndexes[number]} SerializedIndex */
/**
* @param {SerializedIndex} serializedIndex
* @returns {Index}
*/
function serializedIndexToIndex(serializedIndex) {
switch (serializedIndex) {
case "height":
return /** @satisfies {Height} */ (0);
case "dateindex":
return /** @satisfies {Dateindex} */ (1);
case "weekindex":
return /** @satisfies {Weekindex} */ (2);
case "difficultyepoch":
return /** @satisfies {Difficultyepoch} */ (3);
case "monthindex":
return /** @satisfies {Monthindex} */ (4);
case "quarterindex":
return /** @satisfies {Quarterindex} */ (5);
case "yearindex":
return /** @satisfies {Yearindex} */ (6);
case "decadeindex":
return /** @satisfies {Decadeindex} */ (7);
case "halvingepoch":
return /** @satisfies {Halvingepoch} */ (8);
case "addressindex":
return /** @satisfies {Addressindex} */ (9);
case "p2pk33index":
return /** @satisfies {P2PK33index} */ (10);
case "p2pk65index":
return /** @satisfies {P2PK65index} */ (11);
case "p2pkhindex":
return /** @satisfies {P2PKHindex} */ (12);
case "p2shindex":
return /** @satisfies {P2SHindex} */ (13);
case "p2trindex":
return /** @satisfies {P2TRindex} */ (14);
case "p2wpkhindex":
return /** @satisfies {P2WPKHindex} */ (15);
case "p2wshindex":
return /** @satisfies {P2WSHindex} */ (16);
case "txindex":
return /** @satisfies {Txindex} */ (17);
case "txinindex":
return /** @satisfies {Txinindex} */ (18);
case "txoutindex":
return /** @satisfies {Txoutindex} */ (19);
case "emptyindex":
return /** @satisfies {Emptyindex} */ (20);
case "multisigindex":
return /** @satisfies {Multisigindex} */ (21);
case "opreturnindex":
return /** @satisfies {Opreturnindex} */ (22);
case "pushonlyindex":
return /** @satisfies {Pushonlyindex} */ (23);
case "unknownindex":
return /** @satisfies {Unknownindex} */ (24);
}
}
/**
* @param {VecIdToIndexes} vecIdToIndexes
*/
function createIndexToVecIds(vecIdToIndexes) {
const indexToVecIds = Object.entries(vecIdToIndexes).reduce(
(arr, [_id, indexes]) => {
const id = /** @type {VecId} */ (_id);
indexes.forEach((i) => {
arr[i] ??= [];
arr[i].push(id);
});
return arr;
},
/** @type {VecId[][]} */ (new Array(24)),
);
indexToVecIds.forEach((arr) => {
arr.sort();
});
return indexToVecIds;
}