diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..114220a3c --- /dev/null +++ b/TODO.md @@ -0,0 +1,86 @@ +# TODO + +- __crates__ + - _cli_ + - check disk space on first launch + - add custom path support for config.toml + - maybe add bitcoind download and launch support + - via: https://github.com/rust-bitcoin/corepc/blob/master/node + - test read/write speed, add warning if too low (<2gb/s) + - pull latest version and notify is out of date + - _computer_ + - **add rollback of states (in stateful)** + - add support for per index computation + - fix feerate which is always ZERO due to coinbase transaction + - before computing multiple sources check their length, panic if not equal + - add oracle price dataset (https://utxo.live/oracle/UTXOracle.py) + - add address counts relative to all datasets + - make decade, quarter, year datasets `computed` instead of `eager` + - add 6 months interval datasets to builder + - add revived/sent supply datasets + - add `in-sats` version of all price datasets (average and co) + - add `p2pk` group (sum of `p2pk33` and `p2pk65`) + - add more date ranges (3-6 months and more) + - add puell multiple dataset + - add pi cycle dataset + - add all possible charts from: + - https://mainnet.observer + - https://glassnode.com + - https://checkonchain.com + - https://researchbitcoin.net/exciting-update-coming-to-the-bitcoin-lab/ + - https://mempool.space/research + - _indexer_ + - parse only the needed block number + - maybe using https://developer.bitcoin.org/reference/rpc/getblockhash.html + - _interface_ + - create pagination enum + - from to + - from option + - to option + - page + option default 1000 max 1000 + - from/to/count params don’t cap all combinations + - example: from -10,000 count 10, won’t work if underlying vec isn’t 10k or more long + - _parser_ + - save `vec` file instead of `json` + - support lock file, process in read only if already opened in write mode + - if less than X (10 maybe ?) get block using rpc instead of parsing the block files + - _server_ + - api + - add extensions support (.json .csv …) + - if format instead of extension then don't download file + - _vec_ + - add native lock file support (once it's available in stable rust) + - improve compressed mode (slow reads) +- __docs__ + - _README_ + - add a comparison table with alternatives + - add contribution section where help is needed + - documentation/mcp/datasets/different front ends + - add faq +- __websites__ + - _default_ + - explorer + - blocks + - transactions + - addresses + - miners + - maybe xpubs + - charts + - improve some names and colors + - remove `sum` series when it's a duplicate of the `base` (in subsidy for example) + - selected unit sometimes changes when going back end forth + - add support for custom charts + - price scale format depends on unit, hide digits for sats for example (if/when possible) + - table + - pagination + - exports (.json, .csv,…) + - search + - datasets add legend, and keywords ? + - height/address/txid + - api + - add api page with interactivity + - global + - **fix navigation/history** + - move share button to footer ? + - improve behavior when local storage is unavailable + - by having a global state diff --git a/crates/brk_computer/src/vecs/grouped/from_height.rs b/crates/brk_computer/src/vecs/grouped/from_height.rs index a8ed7e0d7..59218ee7d 100644 --- a/crates/brk_computer/src/vecs/grouped/from_height.rs +++ b/crates/brk_computer/src/vecs/grouped/from_height.rs @@ -24,6 +24,7 @@ where pub difficultyepoch: ComputedVecBuilder, pub monthindex: ComputedVecBuilder, pub quarterindex: ComputedVecBuilder, + // 6 months pub yearindex: ComputedVecBuilder, // TODO: pub halvingepoch: StorableVecGeneator, pub decadeindex: ComputedVecBuilder, @@ -144,9 +145,9 @@ where indexes: &indexes::Vecs, starting_indexes: &Indexes, exit: &Exit, - height: Option<&impl AnyIterableVec>, + height_vec: Option<&impl AnyIterableVec>, ) -> color_eyre::Result<()> { - if let Some(height) = height { + if let Some(height) = height_vec { self.height_extra .extend(starting_indexes.height, height, exit)?; diff --git a/crates/brk_computer/src/vecs/stateful/address_cohort.rs b/crates/brk_computer/src/vecs/stateful/address_cohort.rs index 0b64ede52..1e4f7a410 100644 --- a/crates/brk_computer/src/vecs/stateful/address_cohort.rs +++ b/crates/brk_computer/src/vecs/stateful/address_cohort.rs @@ -10,7 +10,9 @@ use brk_vec::{ use crate::{ states::AddressCohortState, vecs::{ - Indexes, fetched, indexes, market, + Indexes, fetched, + grouped::{ComputedVecsFromHeight, StorableVecGeneatorOptions}, + indexes, market, stateful::{ common, r#trait::{CohortVecs, DynCohortVecs}, @@ -26,9 +28,10 @@ pub struct Vecs { pub state: AddressCohortState, - pub height_to_address_count: EagerVec, - pub inner: common::Vecs, + + pub height_to_address_count: EagerVec, + pub indexes_to_address_count: ComputedVecsFromHeight, } impl Vecs { @@ -60,6 +63,14 @@ impl Vecs { version + VERSION + Version::ZERO, format, )?, + indexes_to_address_count: ComputedVecsFromHeight::forced_import( + path, + &suffix("address_count"), + false, + version + VERSION + Version::ZERO, + format, + StorableVecGeneatorOptions::default().add_last(), + )?, inner: common::Vecs::forced_import( path, cohort_name, @@ -160,12 +171,24 @@ impl DynCohortVecs for Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> color_eyre::Result<()> { + self.indexes_to_address_count.compute_rest( + indexes, + starting_indexes, + exit, + Some(&self.height_to_address_count), + )?; + self.inner .compute_rest_part1(indexer, indexes, fetched, starting_indexes, exit) } fn vecs(&self) -> Vec<&dyn AnyCollectableVec> { - [self.inner.vecs(), vec![&self.height_to_address_count]].concat() + [ + self.inner.vecs(), + self.indexes_to_address_count.vecs(), + vec![&self.height_to_address_count], + ] + .concat() } } diff --git a/crates/brk_computer/src/vecs/stateful/mod.rs b/crates/brk_computer/src/vecs/stateful/mod.rs index 5e27adccc..c48d63a4c 100644 --- a/crates/brk_computer/src/vecs/stateful/mod.rs +++ b/crates/brk_computer/src/vecs/stateful/mod.rs @@ -2,7 +2,7 @@ use std::{cmp::Ordering, collections::BTreeMap, mem, path::Path, thread}; use brk_core::{ AddressData, CheckedSub, DateIndex, Dollars, EmptyAddressData, GroupedByAddressType, Height, - InputIndex, OutputIndex, OutputType, Result, Sats, TypeIndex, Version, + InputIndex, OutputIndex, OutputType, Result, Sats, StoredUsize, TypeIndex, Version, }; use brk_exit::Exit; use brk_indexer::Indexer; @@ -17,6 +17,7 @@ use crate::{ BlockState, SupplyState, Transacted, stores::Stores, vecs::{ + grouped::ComputedVecsFromHeight, market, stateful::{ addresstype_to_addresscount::AddressTypeToAddressCount, @@ -52,18 +53,19 @@ const VERSION: Version = Version::new(11); #[derive(Clone)] pub struct Vecs { - chain_state: StoredVec, + pub chain_state: StoredVec, pub height_to_unspendable_supply: EagerVec, pub indexes_to_unspendable_supply: ComputedValueVecsFromHeight, pub height_to_opreturn_supply: EagerVec, pub indexes_to_opreturn_supply: ComputedValueVecsFromHeight, - // pub height_to_address_count: EagerVec, - // pub height_to_empty_address_count: EagerVec, pub addresstype_to_height_to_address_count: AddressTypeToAddressCountVec, pub addresstype_to_height_to_empty_address_count: AddressTypeToAddressCountVec, pub utxo_vecs: utxo_cohorts::Vecs, pub address_vecs: address_cohorts::Vecs, + + pub indexes_to_address_count: ComputedVecsFromHeight, + pub indexes_to_empty_address_count: ComputedVecsFromHeight, } impl Vecs { @@ -119,18 +121,22 @@ impl Vecs { StorableVecGeneatorOptions::default().add_last(), compute_dollars, )?, - // height_to_address_count: EagerVec::forced_import( - // path, - // "address_count", - // version + VERSION + Version::ZERO, - // format, - // )?, - // height_to_empty_address_count: EagerVec::forced_import( - // path, - // "empty_address_count", - // version + VERSION + Version::ZERO, - // format, - // )?, + indexes_to_address_count: ComputedVecsFromHeight::forced_import( + path, + "address_count", + true, + version + VERSION + Version::ZERO, + format, + StorableVecGeneatorOptions::default().add_last(), + )?, + indexes_to_empty_address_count: ComputedVecsFromHeight::forced_import( + path, + "empty_address_count", + true, + version + VERSION + Version::ZERO, + format, + StorableVecGeneatorOptions::default().add_last(), + )?, addresstype_to_height_to_address_count: AddressTypeToAddressCountVec::from( GroupedByAddressType { p2pk65: EagerVec::forced_import( @@ -976,6 +982,7 @@ impl Vecs { .as_mut() .map(|v| is_date_last_height.then(|| *v.unwrap_get_inner(dateindex))); + let dateindex = is_date_last_height.then_some(dateindex); separate_utxo_vecs .into_par_iter() .map(|(_, v)| v as &mut dyn DynCohortVecs).chain( @@ -987,7 +994,7 @@ impl Vecs { v.compute_then_force_push_unrealized_states( height, price, - is_date_last_height.then_some(dateindex), + dateindex, date_price, exit, ) @@ -1039,6 +1046,44 @@ impl Vecs { info!("Computing rest part 1..."); + self.indexes_to_address_count.compute_all( + indexer, + indexes, + starting_indexes, + exit, + |v, _, _, starting_indexes, exit| { + v.compute_sum_of_others( + starting_indexes.height, + &self + .addresstype_to_height_to_address_count + .as_typed_vec() + .into_iter() + .map(|(_, v)| v) + .collect::>(), + exit, + ) + }, + )?; + + self.indexes_to_empty_address_count.compute_all( + indexer, + indexes, + starting_indexes, + exit, + |v, _, _, starting_indexes, exit| { + v.compute_sum_of_others( + starting_indexes.height, + &self + .addresstype_to_height_to_empty_address_count + .as_typed_vec() + .into_iter() + .map(|(_, v)| v) + .collect::>(), + exit, + ) + }, + )?; + thread::scope(|scope| { scope.spawn(|| { self.utxo_vecs @@ -1194,6 +1239,8 @@ impl Vecs { .collect::>(), self.indexes_to_unspendable_supply.vecs(), self.indexes_to_opreturn_supply.vecs(), + self.indexes_to_address_count.vecs(), + self.indexes_to_empty_address_count.vecs(), self.addresstype_to_height_to_address_count .as_typed_vec() .into_iter() @@ -1207,8 +1254,6 @@ impl Vecs { vec![ &self.height_to_unspendable_supply, &self.height_to_opreturn_supply, - // &self.height_to_address_count, - // &self.height_to_empty_address_count, ], ] .into_iter() diff --git a/websites/default/index.html b/websites/default/index.html index bab3e5e87..016031457 100644 --- a/websites/default/index.html +++ b/websites/default/index.html @@ -274,8 +274,6 @@ --default-main-width: 25rem; --bottom-area: 69vh; - - --cube: 50px; } [data-resize] { @@ -1370,41 +1368,71 @@ } #explorer { + --cube: 4.5rem; + #chain { display: flex; - flex-direction: column; - gap: calc(var(--cube) * 1.1); - padding: 1rem; + flex-direction: column-reverse; + gap: calc(var(--cube) * 0.66); + padding: 2rem; .cube { width: var(--cube); height: var(--cube); overflow: hidden; + font-size: var(--font-size-sm); + line-height: var(--line-height-sm); + --color: var(--dark-gray); + color: var(--white); .face { transform-origin: 0 0; position: absolute; width: var(--cube); height: var(--cube); + padding: 0.1rem; } - .front { - background-color: var(--orange); + .right { + background-color: var(--color); transform: rotate(-30deg) skewX(-30deg) translate(calc(var(--cube) * 1.3), calc(var(--cube) * 1.725)) scaleY(0.864); } .top { - background-color: var(--yellow); + background-color: oklch( + from var(--color) calc(l + 0.122) c calc(h + 6) + ); + /* background-color: var(--yellow); */ transform: rotate(30deg) skew(-30deg) translate(calc(var(--cube) * 0.99), calc(var(--cube) * -0.265)) scaleY(0.864); + justify-content: center; + align-items: center; + text-align: center; + display: flex; + font-weight: 900; + text-transform: uppercase; + font-size: var(--font-size-xs); + line-height: var(--line-height-xs); } - .side { - background-color: var(--amber); + .left { + font-size: var(--font-size-xs); + line-height: var(--line-height-xs); + background-color: oklch( + from var(--color) calc(l + 0.066) c calc(h + 3) + ); + /* background-color: var(--amber); */ transform: rotate(30deg) skewX(30deg) translate(calc(var(--cube) * 0.3), calc(var(--cube) * 0.6)) scaleY(0.864); } + .fees { + display: flex; + flex-direction: column; + height: 100%; + justify-content: center; + align-items: center; + } } } } diff --git a/websites/default/scripts/explorer.js b/websites/default/scripts/explorer.js index 74b88e8cd..e861c19e0 100644 --- a/websites/default/scripts/explorer.js +++ b/websites/default/scripts/explorer.js @@ -27,27 +27,90 @@ export function init({ chain.id = "chain"; elements.explorer.append(chain); - chain.append(createCube()); - chain.append(createCube()); - chain.append(createCube()); - chain.append(createCube()); - chain.append(createCube()); + // vecsResources.getOrCreate(/** @satisfies {Height}*/ (5), "height"); + // + const miners = [ + { name: "Foundry USA", color: "orange" }, + { name: "Via BTC", color: "teal" }, + { name: "Ant Pool", color: "emerald" }, + { name: "F2Pool", color: "indigo" }, + { name: "Spider Pool", color: "yellow" }, + { name: "Mara Pool", color: "amber" }, + { name: "SEC Pool", color: "violet" }, + { name: "Luxor", color: "orange" }, + { name: "Brains Pool", color: "cyan" }, + ]; + + for (let i = 0; i <= 10; i++) { + const { name, color } = utils.array.random(miners); + const { cubeElement, leftFaceElement, rightFaceElement, topFaceElement } = + createCube(); + + // cubeElement.style.setProperty("--color", `var(--${color})`); + + const heightElement = window.document.createElement("p"); + const height = (1_000_002 - i).toString(); + const prefixLength = 7 - height.length; + const spanPrefix = window.document.createElement("span"); + spanPrefix.style.opacity = "0.5"; + spanPrefix.style.userSelect = "none"; + heightElement.append(spanPrefix); + spanPrefix.innerHTML = "#" + "0".repeat(prefixLength); + const spanHeight = window.document.createElement("span"); + heightElement.append(spanHeight); + spanHeight.innerHTML = height; + rightFaceElement.append(heightElement); + + const feesElement = window.document.createElement("div"); + feesElement.classList.add("fees"); + leftFaceElement.append(feesElement); + const averageFeeElement = window.document.createElement("p"); + feesElement.append(averageFeeElement); + averageFeeElement.innerHTML = `~1.41`; + const feeRangeElement = window.document.createElement("p"); + feesElement.append(feeRangeElement); + const minFeeElement = window.document.createElement("span"); + minFeeElement.innerHTML = `0.11`; + feeRangeElement.append(minFeeElement); + const dashElement = window.document.createElement("span"); + dashElement.style.opacity = "0.5"; + dashElement.innerHTML = `-`; + feeRangeElement.append(dashElement); + const maxFeeElement = window.document.createElement("span"); + maxFeeElement.innerHTML = `12.1`; + feeRangeElement.append(maxFeeElement); + const feeUnitElement = window.document.createElement("p"); + feesElement.append(feeUnitElement); + feeUnitElement.style.opacity = "0.5"; + feeUnitElement.innerHTML = `sat/vB`; + + const spanMiner = window.document.createElement("span"); + spanMiner.innerHTML = name; + topFaceElement.append(spanMiner); + + chain.prepend(cubeElement); + } } function createCube() { const cubeElement = window.document.createElement("div"); cubeElement.classList.add("cube"); - const faceFrontElement = window.document.createElement("div"); - faceFrontElement.classList.add("face"); - faceFrontElement.classList.add("front"); - cubeElement.append(faceFrontElement); - const faceSideElement = window.document.createElement("div"); - faceSideElement.classList.add("face"); - faceSideElement.classList.add("side"); - cubeElement.append(faceSideElement); - const faceTopElement = window.document.createElement("div"); - faceTopElement.classList.add("face"); - faceTopElement.classList.add("top"); - cubeElement.append(faceTopElement); - return cubeElement; + const rightFaceElement = window.document.createElement("div"); + rightFaceElement.classList.add("face"); + rightFaceElement.classList.add("right"); + cubeElement.append(rightFaceElement); + const leftFaceElement = window.document.createElement("div"); + leftFaceElement.classList.add("face"); + leftFaceElement.classList.add("left"); + cubeElement.append(leftFaceElement); + const topFaceElement = window.document.createElement("div"); + topFaceElement.classList.add("face"); + topFaceElement.classList.add("top"); + cubeElement.append(topFaceElement); + return { + cubeElement, + leftFaceElement, + rightFaceElement, + topFaceElement, + }; } diff --git a/websites/default/scripts/main.js b/websites/default/scripts/main.js index 7bca2d3ac..aa1428c75 100644 --- a/websites/default/scripts/main.js +++ b/websites/default/scripts/main.js @@ -149,6 +149,13 @@ function createUtils() { } return range; }, + /** + * @template T + * @param {T[]} array + */ + random(array) { + return array[Math.floor(Math.random() * array.length)]; + }, }; const dom = { diff --git a/websites/default/scripts/options.js b/websites/default/scripts/options.js index f4b0b68b2..123928456 100644 --- a/websites/default/scripts/options.js +++ b/websites/default/scripts/options.js @@ -1300,20 +1300,19 @@ function createPartialOptions({ env, colors }) { * @property {readonly UTXOGroupObject[]} args.list */ + /** + * @template {"" | CohortId} T + * @param {T} _key + */ + const fixKey = (_key) => + _key !== "" + ? /** @type {Exclude<"" | `${T}_`, "_">} */ (`${_key}_`) + : /** @type {const} */ (""); + /** * @param {UTXOGroupObject | UTXOGroupsObject} args */ - function createUTXOGroupFolder(args) { - /** - * @template {"" | CohortId} T - * @param {T} _key - */ - const fixKey = (_key) => - _key !== "" - ? /** @type {Exclude<"" | `${T}_`, "_">} */ (`${_key}_`) - : /** @type {const} */ (""); - 0; - + function createCohortGroupFolder(args) { const list = "list" in args ? args.list : [args]; const useGroupName = "list" in args; @@ -1474,6 +1473,35 @@ function createPartialOptions({ env, colors }) { ]); }), }, + // list.filter(({ key }) => key.endsWith("address_count")).map(callbackfn), + // { + // name: "loaded", + // title: `${args.title} Loaded Address Count`, + // bottom: list.flatMap(({ color, name, key: _key }) => { + // const key = fixKey(_key); + // return /** @type {const} */ ([ + // createBaseSeries({ + // key: `${key}address_count`, + // name: useGroupName ? name : "Loaded", + // color, + // }), + // createBaseSeries({ + // key: `${key}empty_address_count`, + // name: useGroupName ? name : "Empty", + // color, + // }), + // ]); + // }), + // }, + // { + // name: "empty", + // title: `${args.title} Empty Address Count`, + // bottom: list.flatMap(({ color, name, key: _key }) => { + // const key = fixKey(_key); + // return /** @type {const} */ ([ + // ]); + // }), + // }, { name: "Realized", tree: [ @@ -2980,9 +3008,9 @@ function createPartialOptions({ env, colors }) { ], }, { - name: "UTXOs", + name: "Cohorts", tree: [ - createUTXOGroupFolder({ + createCohortGroupFolder({ key: "", name: "", title: "", @@ -2991,100 +3019,181 @@ function createPartialOptions({ env, colors }) { { name: "term", tree: [ - createUTXOGroupFolder({ + createCohortGroupFolder({ name: "Compare", title: "Compare By Term", list: terms, }), - ...terms.map(createUTXOGroupFolder), + ...terms.map(createCohortGroupFolder), ], }, { name: "Up to date", tree: [ - createUTXOGroupFolder({ + createCohortGroupFolder({ name: "Compare", title: "Compare By Up To", list: upToDate, }), - ...upToDate.map(createUTXOGroupFolder), + ...upToDate.map(createCohortGroupFolder), ], }, { name: "From Date", tree: [ - createUTXOGroupFolder({ + createCohortGroupFolder({ name: "Compare", title: "Compare By From", list: fromDate, }), - ...fromDate.map(createUTXOGroupFolder), + ...fromDate.map(createCohortGroupFolder), ], }, { name: "Date Range", tree: [ - createUTXOGroupFolder({ + createCohortGroupFolder({ name: "Compare", title: "Compare By Range", list: dateRange, }), - ...dateRange.map(createUTXOGroupFolder), + ...dateRange.map(createCohortGroupFolder), ], }, { name: "Epoch", tree: [ - createUTXOGroupFolder({ + createCohortGroupFolder({ name: "Compare", title: "Compare By Epoch", list: epoch, }), - ...epoch.map(createUTXOGroupFolder), - ], - }, - { - name: "Up to size", - tree: [ - createUTXOGroupFolder({ - name: "Compare", - title: "Compare By Up To Size", - list: upToSize, - }), - ...upToSize.map(createUTXOGroupFolder), - ], - }, - { - name: "From size", - tree: [ - createUTXOGroupFolder({ - name: "Compare", - title: "Compare By From Size", - list: fromSize, - }), - ...fromSize.map(createUTXOGroupFolder), - ], - }, - { - name: "Size range", - tree: [ - createUTXOGroupFolder({ - name: "Compare", - title: "Compare By Size Range", - list: sizeRanges, - }), - ...sizeRanges.map(createUTXOGroupFolder), + ...epoch.map(createCohortGroupFolder), ], }, { name: "type", tree: [ - createUTXOGroupFolder({ + createCohortGroupFolder({ name: "Compare", title: "Compare By Type", list: type, }), - ...type.map(createUTXOGroupFolder), + ...type.map(createCohortGroupFolder), + ], + }, + { + name: "UTXOs Up to size", + tree: [ + createCohortGroupFolder({ + name: "Compare", + title: "Compare UTXOs By Up To Size", + list: upToSize, + }), + ...upToSize.map(createCohortGroupFolder), + ], + }, + { + name: "UTXOs From size", + tree: [ + createCohortGroupFolder({ + name: "Compare", + title: "Compare UTXOs By From Size", + list: fromSize, + }), + ...fromSize.map(createCohortGroupFolder), + ], + }, + { + name: "UTXOs Size range", + tree: [ + createCohortGroupFolder({ + name: "Compare", + title: "Compare UTXOs By Size Range", + list: sizeRanges, + }), + ...sizeRanges.map(createCohortGroupFolder), + ], + }, + { + name: "Addresses Up to size", + tree: [ + createCohortGroupFolder({ + name: "Compare", + title: "Compare Addresses By Up To Size", + list: upToSize.map( + (obj) => + /** @type {const} */ ({ + ...obj, + key: `addresses_${obj.key}`, + title: `Addresses ${obj.title}`, + }), + ), + }), + ...upToSize + .map( + (obj) => + /** @type {const} */ ({ + ...obj, + key: `addresses_${obj.key}`, + title: `Addresses ${obj.title}`, + }), + ) + .map(createCohortGroupFolder), + ], + }, + { + name: "Addresses From size", + tree: [ + createCohortGroupFolder({ + name: "Compare", + title: "Compare Addresses By From Size", + list: fromSize.map( + (obj) => + /** @type {const} */ ({ + ...obj, + key: `addresses_${obj.key}`, + title: `Addresses ${obj.title}`, + }), + ), + }), + ...fromSize + .map( + (obj) => + /** @type {const} */ ({ + ...obj, + key: `addresses_${obj.key}`, + title: `Addresses ${obj.title}`, + }), + ) + .map(createCohortGroupFolder), + ], + }, + { + name: "Addresses Size range", + tree: [ + createCohortGroupFolder({ + name: "Compare", + title: "Compare Addresses By Size Range", + list: sizeRanges.map( + (obj) => + /** @type {const} */ ({ + ...obj, + key: `addresses_${obj.key}`, + title: `Addresses ${obj.title}`, + }), + ), + }), + ...sizeRanges + .map( + (obj) => + /** @type {const} */ ({ + ...obj, + key: `addresses_${obj.key}`, + title: `Addresses ${obj.title}`, + }), + ) + .map(createCohortGroupFolder), ], }, ], diff --git a/websites/default/scripts/table.js b/websites/default/scripts/table.js index 5a0602e14..cc5fcf621 100644 --- a/websites/default/scripts/table.js +++ b/websites/default/scripts/table.js @@ -29,11 +29,11 @@ function createTable({ keyPrefix: "table", key: "index", }, - } + }, ) ); const index = signals.createMemo(() => - serializedIndexToIndex(serializedIndex()) + serializedIndexToIndex(serializedIndex()), ); const table = window.document.createElement("table"); @@ -73,7 +73,7 @@ function createTable({ table.append(tbody); const rowElements = signals.createSignal( - /** @type {HTMLTableRowElement[]} */ ([]) + /** @type {HTMLTableRowElement[]} */ ([]), ); /** @@ -322,11 +322,11 @@ function createTable({ unit, }); } - } + }, ); return () => vecId; - } + }, ); }); }); @@ -340,9 +340,7 @@ function createTable({ columns().forEach((vecId, colIndex) => addCol(vecId, colIndex)); obj.addRandomCol = function () { - const vecId = - possibleVecIds[Math.floor(Math.random() * possibleVecIds.length)]; - addCol(vecId); + addCol(utils.array.random(possibleVecIds)); }; return () => index; @@ -393,7 +391,7 @@ export function init({ }, inside: span, title: "Click or tap to add a column to the table", - }) + }), ); } @@ -498,7 +496,7 @@ function createIndexToVecIds(vecIdToIndexes) { }); return arr; }, - /** @type {VecId[][]} */ (Array.from({ length: 24 })) + /** @type {VecId[][]} */ (Array.from({ length: 24 })), ); indexToVecIds.forEach((arr) => { arr.sort();