global: wip

This commit is contained in:
nym21
2025-06-06 12:23:45 +02:00
parent 1921c3d901
commit a11bf5523b
134 changed files with 333 additions and 708 deletions

View File

@@ -0,0 +1,531 @@
// @ts-check
/**
* @param {Object} args
* @param {VecIdToIndexes} args.vecIdToIndexes
* @param {Option} args.option
* @param {Utilities} args.utils
* @param {Signals} args.signals
* @param {VecsResources} args.vecsResources
*/
function createTable({
utils,
vecIdToIndexes,
signals,
option,
vecsResources,
}) {
const indexToVecIds = createIndexToVecIds(vecIdToIndexes);
const serializedIndexes = createSerializedIndexes();
/** @type {SerializedIndex} */
const defaultSerializedIndex = "height";
const serializedIndex = /** @type {Signal<SerializedIndex>} */ (
signals.createSignal(
/** @type {SerializedIndex} */ (defaultSerializedIndex),
{
save: {
...utils.serde.string,
keyPrefix: "table",
key: "index",
},
},
)
);
const index = signals.createMemo(() =>
serializedIndexToIndex(serializedIndex()),
);
const table = window.document.createElement("table");
const obj = {
element: table,
/** @type {VoidFunction | undefined} */
addRandomCol: undefined,
};
signals.createEffect(index, (index, prevIndex) => {
if (prevIndex !== undefined) {
utils.url.resetParams(option);
}
const possibleVecIds = indexToVecIds[index];
const columns = signals.createSignal(/** @type {VecId[]} */ ([]), {
equals: false,
save: {
...utils.serde.vecIds,
keyPrefix: `table-${serializedIndex()}`,
key: `columns`,
},
});
columns.set((l) => l.filter((id) => possibleVecIds.includes(id)));
signals.createEffect(columns, (columns) => {
console.log(columns);
});
table.innerHTML = "";
const thead = window.document.createElement("thead");
table.append(thead);
const trHead = window.document.createElement("tr");
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 {Unit} [args.unit]
* @param {(event: MouseEvent) => void} [args.onLeft]
* @param {(event: MouseEvent) => void} [args.onRight]
* @param {(event: MouseEvent) => void} [args.onRemove]
*/
function addThCol({ select, onLeft, onRight, onRemove, unit: _unit }) {
const th = window.document.createElement("th");
th.scope = "col";
trHead.append(th);
const div = window.document.createElement("div");
div.append(select);
// const top = window.document.createElement("div");
// div.append(top);
// top.append(select);
// top.append(
// utils.dom.createAnchorElement({
// href: "",
// blank: true,
// }),
// );
const bottom = window.document.createElement("div");
const unit = window.document.createElement("span");
if (_unit) {
unit.innerHTML = _unit;
}
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 || (() => {}),
});
bottom.append(unit);
bottom.append(moveLeft);
bottom.append(moveRight);
bottom.append(remove);
div.append(bottom);
th.append(div);
return {
element: th,
/**
* @param {Unit} _unit
*/
setUnit(_unit) {
unit.innerHTML = _unit;
},
};
}
addThCol({
...utils.dom.createSelect({
list: serializedIndexes,
signal: serializedIndex,
}),
unit: "Index",
});
let from = 0;
let to = 0;
vecsResources
.getOrCreate(index, serializedIndex())
.fetch()
.then((vec) => {
if (!vec) return;
from = /** @type {number} */ (vec[0]);
to = /** @type {number} */ (vec.at(-1)) + 1;
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 = serializeValue({
value,
unit: "Index",
});
th.scope = "row";
tr.append(th);
}
rowElements.set(() => trs);
});
const owner = signals.getOwner();
/**
* @param {VecId} vecId
* @param {number} [_colIndex]
*/
function addCol(vecId, _colIndex = columns().length) {
signals.runWithOwner(owner, () => {
/** @type {VoidFunction | undefined} */
let dispose;
signals.createRoot((_dispose) => {
dispose = _dispose;
const vecIdOption = signals.createSignal({
name: vecId,
value: vecId,
});
const { select } = utils.dom.createSelect({
list: possibleVecIds.map((vecId) => ({
name: vecId,
value: vecId,
})),
signal: vecIdOption,
});
if (_colIndex === columns().length) {
columns.set((l) => {
l.push(vecId);
return l;
});
}
const colIndex = signals.createSignal(_colIndex);
/**
* @param {boolean} right
* @returns {(event: MouseEvent) => void}
*/
function createMoveColumnFunction(right) {
return () => {
const oldColIndex = colIndex();
const newColIndex = oldColIndex + (right ? 1 : -1);
const currentTh = /** @type {HTMLTableCellElement} */ (
trHead.childNodes[oldColIndex + 1]
);
const oterTh = /** @type {HTMLTableCellElement} */ (
trHead.childNodes[newColIndex + 1]
);
if (right) {
oterTh.after(currentTh);
} else {
oterTh.before(currentTh);
}
columns.set((l) => {
[l[oldColIndex], l[newColIndex]] = [
l[newColIndex],
l[oldColIndex],
];
return l;
});
const rows = rowElements();
for (let i = 0; i < rows.length; i++) {
const element = rows[i].childNodes[oldColIndex + 1];
const sibling = rows[i].childNodes[newColIndex + 1];
const temp = element.textContent;
element.textContent = sibling.textContent;
sibling.textContent = temp;
}
};
}
const th = addThCol({
select,
unit: utils.vecidToUnit(vecId),
onLeft: createMoveColumnFunction(false),
onRight: createMoveColumnFunction(true),
onRemove: () => {
const ci = colIndex();
trHead.childNodes[ci + 1].remove();
columns.set((l) => {
l.splice(ci, 1);
return l;
});
const rows = rowElements();
for (let i = 0; i < rows.length; i++) {
rows[i].childNodes[ci + 1].remove();
}
dispose?.();
},
});
signals.createEffect(columns, () => {
colIndex.set(Array.from(trHead.children).indexOf(th.element) - 1);
});
console.log(colIndex());
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, prevVecId) => {
const unit = utils.vecidToUnit(vecId);
th.setUnit(unit);
const vec = vecsResources.getOrCreate(index, vecId);
vec.fetch({ from, to });
const fetchedKey = vecsResources.genFetchedKey({ from, to });
columns.set((l) => {
const i = l.indexOf(prevVecId ?? vecId);
if (i === -1) {
l.push(vecId);
} else {
l[i] = vecId;
}
return l;
});
signals.createEffect(vec.fetched[fetchedKey].vec, (vec) => {
if (!vec) return;
const thIndex = colIndex() + 1;
for (let i = 0; i < rowElements.length; i++) {
const iRev = vec.length - 1 - i;
const value = vec[iRev];
// @ts-ignore
rowElements[i].childNodes[thIndex].innerHTML =
serializeValue({
value,
unit,
});
}
});
return () => vecId;
},
);
});
});
signals.onCleanup(() => {
dispose?.();
});
});
}
columns().forEach((vecId, colIndex) => addCol(vecId, colIndex));
obj.addRandomCol = function () {
const vecId =
possibleVecIds[Math.floor(Math.random() * possibleVecIds.length)];
addCol(vecId);
};
return () => index;
});
return obj;
}
/**
* @param {Object} args
* @param {Colors} args.colors
* @param {Signals} args.signals
* @param {Utilities} args.utils
* @param {Option} args.option
* @param {Elements} args.elements
* @param {VecsResources} args.vecsResources
* @param {VecIdToIndexes} args.vecIdToIndexes
*/
export function init({
colors,
elements,
signals,
option,
utils,
vecsResources,
vecIdToIndexes,
}) {
const parent = elements.table;
const { headerElement } = utils.dom.createHeader({
title: "Table",
});
parent.append(headerElement);
const div = window.document.createElement("div");
parent.append(div);
const table = createTable({
signals,
utils,
vecIdToIndexes,
vecsResources,
option,
});
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} */ ("dateindex"),
/** @satisfies {VecId} */ ("decadeindex"),
/** @satisfies {VecId} */ ("difficultyepoch"),
/** @satisfies {VecId} */ ("emptyoutputindex"),
/** @satisfies {VecId} */ ("halvingepoch"),
/** @satisfies {VecId} */ ("height"),
/** @satisfies {VecId} */ ("inputindex"),
/** @satisfies {VecId} */ ("monthindex"),
/** @satisfies {VecId} */ ("opreturnindex"),
/** @satisfies {VecId} */ ("outputindex"),
/** @satisfies {VecId} */ ("p2aindex"),
/** @satisfies {VecId} */ ("p2msindex"),
/** @satisfies {VecId} */ ("p2pk33index"),
/** @satisfies {VecId} */ ("p2pk65index"),
/** @satisfies {VecId} */ ("p2pkhindex"),
/** @satisfies {VecId} */ ("p2shindex"),
/** @satisfies {VecId} */ ("p2trindex"),
/** @satisfies {VecId} */ ("p2wpkhindex"),
/** @satisfies {VecId} */ ("p2wshindex"),
/** @satisfies {VecId} */ ("quarterindex"),
/** @satisfies {VecId} */ ("txindex"),
/** @satisfies {VecId} */ ("unknownoutputindex"),
/** @satisfies {VecId} */ ("weekindex"),
/** @satisfies {VecId} */ ("yearindex"),
]);
}
/** @typedef {ReturnType<typeof createSerializedIndexes>} SerializedIndexes */
/** @typedef {SerializedIndexes[number]} SerializedIndex */
/**
* @param {SerializedIndex} serializedIndex
* @returns {Index}
*/
function serializedIndexToIndex(serializedIndex) {
switch (serializedIndex) {
case "height":
return /** @satisfies {Height} */ (5);
case "dateindex":
return /** @satisfies {DateIndex} */ (0);
case "weekindex":
return /** @satisfies {WeekIndex} */ (22);
case "difficultyepoch":
return /** @satisfies {DifficultyEpoch} */ (2);
case "monthindex":
return /** @satisfies {MonthIndex} */ (7);
case "quarterindex":
return /** @satisfies {QuarterIndex} */ (19);
case "yearindex":
return /** @satisfies {YearIndex} */ (23);
case "decadeindex":
return /** @satisfies {DecadeIndex} */ (1);
case "halvingepoch":
return /** @satisfies {HalvingEpoch} */ (4);
case "txindex":
return /** @satisfies {TxIndex} */ (20);
case "inputindex":
return /** @satisfies {InputIndex} */ (6);
case "outputindex":
return /** @satisfies {OutputIndex} */ (9);
case "p2pk33index":
return /** @satisfies {P2PK33Index} */ (12);
case "p2pk65index":
return /** @satisfies {P2PK65Index} */ (13);
case "p2pkhindex":
return /** @satisfies {P2PKHIndex} */ (14);
case "p2shindex":
return /** @satisfies {P2SHIndex} */ (15);
case "p2trindex":
return /** @satisfies {P2TRIndex} */ (16);
case "p2wpkhindex":
return /** @satisfies {P2WPKHIndex} */ (17);
case "p2wshindex":
return /** @satisfies {P2WSHIndex} */ (18);
case "p2aindex":
return /** @satisfies {P2AIndex} */ (10);
case "p2msindex":
return /** @satisfies {P2MSIndex} */ (11);
case "opreturnindex":
return /** @satisfies {OpReturnIndex} */ (8);
case "emptyoutputindex":
return /** @satisfies {EmptyOutputIndex} */ (3);
case "unknownoutputindex":
return /** @satisfies {UnknownOutputIndex} */ (21);
}
}
/**
* @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;
}
/**
* @param {Object} args
* @param {number | OHLCTuple} args.value
* @param {Unit} args.unit
*/
function serializeValue({ value, unit }) {
if (typeof value !== "number") {
return String(value);
} else if (value !== 18446744073709552000) {
if (unit === "USD" || unit === "Difficulty" || unit === "sat/vB") {
return value.toLocaleString("en-us", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
} else if (unit === "BTC") {
return value.toLocaleString("en-us", {
minimumFractionDigits: 8,
maximumFractionDigits: 8,
});
} else {
return value.toLocaleString("en-us");
}
} else {
return "";
}
}