kibo: part 1

This commit is contained in:
nym21
2025-03-21 16:59:39 +01:00
parent a9929438cd
commit 51fbf148d9
12 changed files with 1050 additions and 975 deletions

88
Cargo.lock generated
View File

@@ -639,9 +639,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.16" version = "1.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@@ -1789,9 +1789,9 @@ checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564"
[[package]] [[package]]
name = "oxc" name = "oxc"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e398ac9650c77d6a43e7f5ed5315c3cae33fd1f84666acd0a55719c8da1555b3" checksum = "d6f2d8bbd880aaaf838456ce101c59d926a762b6a891ef91402794e9dc8d2c2c"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1832,9 +1832,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_allocator" name = "oxc_allocator"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36dd2ba553043fd1cf1f92fb4d685a9a58afcc4f2692e85ebf84e242e6492909" checksum = "fe07aea78e1e1a860d92cfe1b712f81ba10960dee847c6231fa4e9b0665ec5ff"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2",
"bumpalo", "bumpalo",
@@ -1846,9 +1846,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_ast" name = "oxc_ast"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd0607fb00e5f2413b5a99b36eff638b7db57e69149e98ac693d2aaa500d1d26" checksum = "062ec80f1ed9471bc05f57bd481bd4921285373b57018f3028aed49cb3ac353f"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cow-utils", "cow-utils",
@@ -1863,9 +1863,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_ast_macros" name = "oxc_ast_macros"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf0d4b6faf22509c64f78d4a0a0bb760f6ba34fe7acdcb421b57fdc32482867e" checksum = "41e79130c01eaddff0274d504404f80d88835ed70fcc1e91f9c9fd42fd718202"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1874,9 +1874,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_ast_visit" name = "oxc_ast_visit"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "421e72f280ed323f63ad7ca5e4700577ca51f7946fdc9868baeb7e23eeb1c6b1" checksum = "1c0476717291544c614de9ffc1c34c29b06025008c2e604505e67248234725ca"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1886,9 +1886,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_cfg" name = "oxc_cfg"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c213a97298278d4f9a7e3a4e6bce0c5eba416aed5b291a6296bbb8c26ca1e65" checksum = "243535bf553c8d399f20a392eabb6eff5990818eac8519a930a1c345497a8ea7"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"itertools", "itertools",
@@ -1901,9 +1901,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_codegen" name = "oxc_codegen"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a61b0ca6f9d8e9fb6a5a5390ecae8bbd98283cfb38d24ce77979b77ab28fc65" checksum = "4ebcbed8d477c4b9142c895a762be0afd16bd0838f64237e2c006fd9f8ec7e1a"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cow-utils", "cow-utils",
@@ -1922,15 +1922,15 @@ dependencies = [
[[package]] [[package]]
name = "oxc_data_structures" name = "oxc_data_structures"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47c43341de573281bc1883b4cdb36dd9c8c11e56a7d6fda0b8335471add52d2" checksum = "e4c79e0d91ca11d4add13d94f802096564babb2c609956a8e19eb6f83b7f0fb1"
[[package]] [[package]]
name = "oxc_diagnostics" name = "oxc_diagnostics"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f5ce8bc7ebc0fa2c2bd94d526a92636e8622f879a9dd9a41b6c77c12a2b2408" checksum = "b127d339db14984b22bf4255a2583c1be2cd709b1b14f64e3ce4cb5fe87c699d"
dependencies = [ dependencies = [
"cow-utils", "cow-utils",
"oxc-miette", "oxc-miette",
@@ -1938,9 +1938,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_ecmascript" name = "oxc_ecmascript"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce89c6c2764fa6ad1f929a91e09614943fe7a25df1d288d38acae0302581b8f" checksum = "522b7c4d6db500536be627e1d3952cf26705328e77a4d819a2543c2392b702eb"
dependencies = [ dependencies = [
"cow-utils", "cow-utils",
"num-bigint", "num-bigint",
@@ -1952,9 +1952,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_estree" name = "oxc_estree"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a4f605c6f6460d2cb659bb1c2273244ebf8c07bae4155274fa2461d7e0ec35" checksum = "7190d1db8c149324345b14588f24a318712498fde1741513c3a129731ef6b4f9"
[[package]] [[package]]
name = "oxc_index" name = "oxc_index"
@@ -1964,9 +1964,9 @@ checksum = "2fa07b0cfa997730afed43705766ef27792873fdf5215b1391949fec678d2392"
[[package]] [[package]]
name = "oxc_mangler" name = "oxc_mangler"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73d8be639f09e7c62e4503a5cbe3a802d78265c490b09ebaa1fb905d5b9d8bb0" checksum = "cddde40dd8422c56c07eecd370d5c221626afb5cb5966824c1884a8b929305cb"
dependencies = [ dependencies = [
"fixedbitset", "fixedbitset",
"itertools", "itertools",
@@ -1981,9 +1981,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_minifier" name = "oxc_minifier"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea781d79be82fd4e1cd94fe6a6da4517edf075cc604d52f9785a0153157736d8" checksum = "513dd618e9016cb00cc52e547bcbdb160a0379025206f8cce14287c1c624b57a"
dependencies = [ dependencies = [
"cow-utils", "cow-utils",
"oxc_allocator", "oxc_allocator",
@@ -2003,9 +2003,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_parser" name = "oxc_parser"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b633fe51f19c4da6f3cd6fd0885e3a9e12f19317487a477fea0f73892713083" checksum = "afda2c4a47704ff4c68990248be9f693f606626cff1c6d1760034638b54c5413"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cow-utils", "cow-utils",
@@ -2026,9 +2026,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_regular_expression" name = "oxc_regular_expression"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35b1a9babb738e4d99cfc0ef8ad242806d261de028b400a3205afdf7a9a1c03" checksum = "a4354d5b5f48d53cc0d4d000425ec062b12e9b3fbcf395e765064c8eab113921"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast_macros", "oxc_ast_macros",
@@ -2042,9 +2042,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_semantic" name = "oxc_semantic"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78fcfacfa8a2bb020326c37011b86f423c41e776516c197e6c162ac85b6a1e7a" checksum = "e1346102f3550e6b0417fe84f8fb397f976c351e21bcb98b75ff018cc3877bd3"
dependencies = [ dependencies = [
"itertools", "itertools",
"oxc_allocator", "oxc_allocator",
@@ -2078,9 +2078,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_span" name = "oxc_span"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c2940d9a7446ddbe554bf0aa3cd111e6bf7c2dd29460da6673cde9b1c7be77f" checksum = "77b073cb1349f33e04d821e4fd1e51c860a010d74fea0b4660504cb05a87968a"
dependencies = [ dependencies = [
"compact_str", "compact_str",
"oxc-miette", "oxc-miette",
@@ -2091,9 +2091,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_syntax" name = "oxc_syntax"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd9d42c1d620a1da919ec6b56c4476054d4d2c71423c08effbc3a0519c516b61" checksum = "488a2404fca5b741255b8b875e8f6515f5b08df6046a0767b2368d6182e055cb"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cow-utils", "cow-utils",
@@ -2112,9 +2112,9 @@ dependencies = [
[[package]] [[package]]
name = "oxc_traverse" name = "oxc_traverse"
version = "0.61.0" version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f389b5904bc2294fe53ee6b0f6b3b15e395b71079168d8e8204626c4aede3c32" checksum = "02102521f55df8330e9393d425e746e21d9badc5a78650769e645db40102f3c0"
dependencies = [ dependencies = [
"compact_str", "compact_str",
"itoa", "itoa",
@@ -3473,18 +3473,18 @@ dependencies = [
[[package]] [[package]]
name = "zstd-safe" name = "zstd-safe"
version = "7.2.3" version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [ dependencies = [
"zstd-sys", "zstd-sys",
] ]
[[package]] [[package]]
name = "zstd-sys" name = "zstd-sys"
version = "2.0.14+zstd.1.5.7" version = "2.0.15+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
dependencies = [ dependencies = [
"cc", "cc",
"pkg-config", "pkg-config",

View File

@@ -31,7 +31,7 @@ pub use stores::*;
pub use vecs::*; pub use vecs::*;
const SNAPSHOT_BLOCK_RANGE: usize = 1000; const SNAPSHOT_BLOCK_RANGE: usize = 1000;
const COLLISIONS_CHECKED_UP_TO: u32 = 870_000; const COLLISIONS_CHECKED_UP_TO: u32 = 888_000;
#[derive(Clone)] #[derive(Clone)]
pub struct Indexer { pub struct Indexer {

View File

@@ -162,17 +162,6 @@ where
} }
} }
// impl<I, T> Deref for StorableVec<I, T> {
// type Target = brk_vec::StorableVec<I, T>;
// fn deref(&self) -> &Self::Target {
// &self.vec
// }
// }
// impl<I, T> DerefMut for StorableVec<I, T> {
// fn deref_mut(&mut self) -> &mut Self::Target {
// &mut self.vec
// }
// }
impl<I, T> Clone for StorableVec<I, T> impl<I, T> Clone for StorableVec<I, T>
where where
I: StoredIndex, I: StoredIndex,

View File

@@ -21,7 +21,7 @@ color-eyre = { workspace = true }
jiff = { workspace = true } jiff = { workspace = true }
log = { workspace = true } log = { workspace = true }
minreq = { workspace = true } minreq = { workspace = true }
oxc = { version = "0.61.0", features = ["codegen", "minifier"] } oxc = { version = "0.61.1", features = ["codegen", "minifier"] }
serde = { workspace = true } serde = { workspace = true }
tokio = { version = "1.44.1", features = ["full"] } tokio = { version = "1.44.1", features = ["full"] }
tower-http = { version = "0.6.2", features = ["compression-full"] } tower-http = { version = "0.6.2", features = ["compression-full"] }

View File

@@ -1518,7 +1518,6 @@
" "
>希望</small >希望</small
> >
希望.お金
</a> </a>
<small style="display: block"> <small style="display: block">
<strong>Bitcoin</strong> is <strong>Bitcoin</strong> is

View File

@@ -4,6 +4,7 @@
* @import { DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, SingleValueData, ISeriesApi, Time, LogicalRange, SeriesType, BaselineStyleOptions, SeriesOptionsCommon, createChart as CreateClassicChart, createChartEx as CreateCustomChart } from "./v4.2.2/types" * @import { DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, SingleValueData, ISeriesApi, Time, LogicalRange, SeriesType, BaselineStyleOptions, SeriesOptionsCommon, createChart as CreateClassicChart, createChartEx as CreateCustomChart } from "./v4.2.2/types"
*/ */
export default import("./v4.2.2/script.js").then((lightweightCharts) => {
const ids = { const ids = {
from: "from", from: "from",
to: "to", to: "to",
@@ -16,7 +17,6 @@ const ids = {
}, },
}; };
export default import("./v4.2.2/script.js").then((lightweightCharts) => {
const createClassicChart = /** @type {CreateClassicChart} */ ( const createClassicChart = /** @type {CreateClassicChart} */ (
lightweightCharts.createChart lightweightCharts.createChart
); );
@@ -357,6 +357,7 @@ export default import("./v4.2.2/script.js").then((lightweightCharts) => {
* @param {TimeScale} param0.scale * @param {TimeScale} param0.scale
* @param {"static" | "moveable"} param0.kind * @param {"static" | "moveable"} param0.kind
* @param {Utilities} param0.utils * @param {Utilities} param0.utils
* @param {Constants} param0.consts
* @param {Owner | null} [param0.owner] * @param {Owner | null} [param0.owner]
* @param {CreatePaneParameters[]} [param0.config] * @param {CreatePaneParameters[]} [param0.config]
*/ */
@@ -368,6 +369,7 @@ export default import("./v4.2.2/script.js").then((lightweightCharts) => {
kind, kind,
scale, scale,
config, config,
consts,
utils, utils,
owner: _owner, owner: _owner,
}) { }) {

View File

@@ -8,6 +8,7 @@
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Utilities} args.utils * @param {Utilities} args.utils
* @param {Datasets} args.datasets * @param {Datasets} args.datasets
* @param {Constants} args.consts
* @param {WebSockets} args.webSockets * @param {WebSockets} args.webSockets
* @param {Elements} args.elements * @param {Elements} args.elements
*/ */
@@ -19,6 +20,7 @@ export function init({
selected, selected,
signals, signals,
utils, utils,
consts,
webSockets, webSockets,
}) { }) {
console.log("init chart state"); console.log("init chart state");
@@ -43,6 +45,7 @@ export function init({
id: "chart", id: "chart",
scale: scale(), scale: scale(),
kind: "moveable", kind: "moveable",
consts,
utils, utils,
}); });

View File

@@ -7,7 +7,7 @@
/** /**
* @param {Object} args * @param {Object} args
* @param {Colors} args.colors * @param {Colors} args.colors
* @param {Consts} args.consts * @param {Constants} args.consts
* @param {LightweightCharts} args.lightweightCharts * @param {LightweightCharts} args.lightweightCharts
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Utilities} args.utils * @param {Utilities} args.utils

View File

@@ -68,34 +68,35 @@ function initPackages() {
ufuzzy: importPackage("ufuzzy"), ufuzzy: importPackage("ufuzzy"),
}; };
} }
const packages = initPackages();
/** /**
* @typedef {Awaited<ReturnType<typeof packages.lightweightCharts>>} LightweightCharts * @typedef {ReturnType<typeof initPackages>} Packages
* @typedef {Awaited<ReturnType<Packages["lightweightCharts"]>>} LightweightCharts
* @typedef {ReturnType<LightweightCharts['createChart']>} Chart * @typedef {ReturnType<LightweightCharts['createChart']>} Chart
*/ */
const options = import("./options.js"); function createUtils() {
const utils = {
/** /**
* @param {string} serialized * @param {string} serialized
* @returns {boolean} * @returns {boolean}
*/ */
isSerializedBooleanTrue(serialized) { function isSerializedBooleanTrue(serialized) {
return serialized === "true" || serialized === "1"; return serialized === "true" || serialized === "1";
}, }
/** /**
* @param {number} ms * @param {number} ms
*/ */
sleep(ms) { function sleep(ms) {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(resolve, ms); setTimeout(resolve, ms);
}); });
}, }
yield() {
return this.sleep(0); function next() {
}, return sleep(0);
array: { }
const array = {
/** /**
* @param {number} start * @param {number} start
* @param {number} end * @param {number} end
@@ -108,8 +109,9 @@ const utils = {
} }
return range; return range;
}, },
}, };
dom: {
const dom = {
/** /**
* @param {string} id * @param {string} id
* @returns {HTMLElement} * @returns {HTMLElement}
@@ -121,8 +123,9 @@ const utils = {
}, },
/** /**
* @param {string} name * @param {string} name
* @param {Elements} elements
*/ */
queryOrCreateMetaElement(name) { queryOrCreateMetaElement(name, elements) {
let meta = /** @type {HTMLMetaElement | null} */ ( let meta = /** @type {HTMLMetaElement | null} */ (
window.document.querySelector(`meta[name="${name}"]`) window.document.querySelector(`meta[name="${name}"]`)
); );
@@ -289,9 +292,10 @@ const utils = {
}, },
/** /**
* @param {string} url * @param {string} url
* @param {Elements} elements
* @param {boolean} [targetBlank] * @param {boolean} [targetBlank]
*/ */
open(url, targetBlank) { open(url, elements, targetBlank) {
console.log(`open: ${url}`); console.log(`open: ${url}`);
const a = window.document.createElement("a"); const a = window.document.createElement("a");
elements.body.append(a); elements.body.append(a);
@@ -322,7 +326,8 @@ const utils = {
link.type = "text/css"; link.type = "text/css";
link.rel = "stylesheet"; link.rel = "stylesheet";
link.media = "screen,print"; link.media = "screen,print";
elements.head.appendChild(link); const head = window.document.getElementsByTagName("head")[0];
head.appendChild(link);
return link; return link;
}, },
/** /**
@@ -362,7 +367,7 @@ const utils = {
choices.forEach((choice) => { choices.forEach((choice) => {
const inputValue = choice.toLowerCase(); const inputValue = choice.toLowerCase();
const { label } = utils.dom.createLabeledInput({ const { label } = this.createLabeledInput({
inputId: `${id}-${choice.toLowerCase()}`, inputId: `${id}-${choice.toLowerCase()}`,
inputName: id, inputName: id,
inputValue, inputValue,
@@ -476,7 +481,7 @@ const utils = {
const min = "2011-01-01"; const min = "2011-01-01";
const minDate = new Date(min); const minDate = new Date(min);
const maxDate = new Date(); const maxDate = new Date();
const max = utils.date.toString(maxDate); const max = date.toString(maxDate);
input.min = min; input.min = min;
input.max = max; input.max = max;
@@ -484,8 +489,8 @@ const utils = {
signals.createEffect( signals.createEffect(
() => { () => {
const date = signal(); const dateSignal = signal();
return date ? utils.date.toString(date) : ""; return dateSignal ? date.toString(dateSignal) : "";
}, },
(value) => { (value) => {
if (stateValue !== value) { if (stateValue !== value) {
@@ -663,8 +668,9 @@ const utils = {
div.classList.add(`shadow-${position}`); div.classList.add(`shadow-${position}`);
return div; return div;
}, },
}, };
url: {
const url = {
chartParamsWhitelist: ["from", "to"], chartParamsWhitelist: ["from", "to"],
/** /**
* @param {string} pathname * @param {string} pathname
@@ -736,7 +742,7 @@ const utils = {
const parameter = this.readParam(key); const parameter = this.readParam(key);
if (parameter) { if (parameter) {
return utils.isSerializedBooleanTrue(parameter); return isSerializedBooleanTrue(parameter);
} }
return null; return null;
@@ -767,20 +773,23 @@ const utils = {
pathnameToSelectedId() { pathnameToSelectedId() {
return window.document.location.pathname.substring(1); return window.document.location.pathname.substring(1);
}, },
}, };
locale: {
/** /**
* @param {number} value * @param {number} value
* @param {number} [digits] * @param {number} [digits]
* @param {Intl.NumberFormatOptions} [options] * @param {Intl.NumberFormatOptions} [options]
*/ */
numberToUSFormat(value, digits, options) { function numberToUSFormat(value, digits, options) {
return value.toLocaleString("en-us", { return value.toLocaleString("en-us", {
...options, ...options,
minimumFractionDigits: digits, minimumFractionDigits: digits,
maximumFractionDigits: digits, maximumFractionDigits: digits,
}); });
}, }
const locale = {
numberToUSFormat,
/** @param {number} value */ /** @param {number} value */
numberToShortUSFormat(value) { numberToShortUSFormat(value) {
const absoluteValue = Math.abs(value); const absoluteValue = Math.abs(value);
@@ -788,15 +797,15 @@ const utils = {
if (isNaN(value)) { if (isNaN(value)) {
return ""; return "";
} else if (absoluteValue < 10) { } else if (absoluteValue < 10) {
return utils.locale.numberToUSFormat(value, 3); return numberToUSFormat(value, 3);
} else if (absoluteValue < 100) { } else if (absoluteValue < 100) {
return utils.locale.numberToUSFormat(value, 2); return numberToUSFormat(value, 2);
} else if (absoluteValue < 1_000) { } else if (absoluteValue < 1_000) {
return utils.locale.numberToUSFormat(value, 1); return numberToUSFormat(value, 1);
} else if (absoluteValue < 100_000) { } else if (absoluteValue < 100_000) {
return utils.locale.numberToUSFormat(value, 0); return numberToUSFormat(value, 0);
} else if (absoluteValue < 1_000_000) { } else if (absoluteValue < 1_000_000) {
return `${utils.locale.numberToUSFormat(value / 1_000, 1)}K`; return `${numberToUSFormat(value / 1_000, 1)}K`;
} else if (absoluteValue >= 9_000_000_000_000_000) { } else if (absoluteValue >= 9_000_000_000_000_000) {
return "Inf."; return "Inf.";
} }
@@ -810,24 +819,25 @@ const utils = {
const modulused = log % 3; const modulused = log % 3;
if (modulused === 0) { if (modulused === 0) {
return `${utils.locale.numberToUSFormat( return `${numberToUSFormat(
value / (1_000_000 * 1_000 ** letterIndex), value / (1_000_000 * 1_000 ** letterIndex),
3, 3,
)}${letter}`; )}${letter}`;
} else if (modulused === 1) { } else if (modulused === 1) {
return `${utils.locale.numberToUSFormat( return `${numberToUSFormat(
value / (1_000_000 * 1_000 ** letterIndex), value / (1_000_000 * 1_000 ** letterIndex),
2, 2,
)}${letter}`; )}${letter}`;
} else { } else {
return `${utils.locale.numberToUSFormat( return `${numberToUSFormat(
value / (1_000_000 * 1_000 ** letterIndex), value / (1_000_000 * 1_000 ** letterIndex),
1, 1,
)}${letter}`; )}${letter}`;
} }
}, },
}, };
storage: {
const storage = {
/** /**
* @param {string} key * @param {string} key
*/ */
@@ -844,7 +854,7 @@ const utils = {
readBool(key) { readBool(key) {
const saved = this.read(key); const saved = this.read(key);
if (saved) { if (saved) {
return utils.isSerializedBooleanTrue(saved); return isSerializedBooleanTrue(saved);
} }
return null; return null;
}, },
@@ -869,8 +879,9 @@ const utils = {
remove(key) { remove(key) {
this.write(key, undefined); this.write(key, undefined);
}, },
}, };
serde: {
const serde = {
number: { number: {
/** /**
* @param {number} v * @param {number} v
@@ -890,7 +901,7 @@ const utils = {
* @param {Date} v * @param {Date} v
*/ */
serialize(v) { serialize(v) {
return utils.date.toString(v); return date.toString(v);
}, },
/** /**
* @param {string} v * @param {string} v
@@ -919,8 +930,9 @@ const utils = {
} }
}, },
}, },
}, };
formatters: {
const formatters = {
dollars: new Intl.NumberFormat("en-US", { dollars: new Intl.NumberFormat("en-US", {
style: "currency", style: "currency",
currency: "USD", currency: "USD",
@@ -932,8 +944,9 @@ const utils = {
minimumFractionDigits: 2, minimumFractionDigits: 2,
maximumFractionDigits: 2, maximumFractionDigits: 2,
}), }),
}, };
date: {
const date = {
ONE_DAY_IN_MS: 1000 * 60 * 60 * 24, ONE_DAY_IN_MS: 1000 * 60 * 60 * 24,
todayUTC() { todayUTC() {
const today = new Date(); const today = new Date();
@@ -991,8 +1004,9 @@ const utils = {
differenceBetween(date1, date2) { differenceBetween(date1, date2) {
return Math.abs(date1.valueOf() - date2.valueOf()) / this.ONE_DAY_IN_MS; return Math.abs(date1.valueOf() - date2.valueOf()) / this.ONE_DAY_IN_MS;
}, },
}, };
color: {
const color = {
/** /**
* *
* @param {readonly [number, number, number, number, number, number, number, number, number]} A * @param {readonly [number, number, number, number, number, number, number, number, number]} A
@@ -1086,14 +1100,15 @@ const utils = {
}); });
return `#${r}${g}${b}`; return `#${r}${g}${b}`;
}, },
}, };
/** /**
* *
* @template {(...args: any[]) => any} F * @template {(...args: any[]) => any} F
* @param {F} callback * @param {F} callback
* @param {number} [wait=250] * @param {number} [wait=250]
*/ */
debounce(callback, wait = 250) { function debounce(callback, wait = 250) {
/** @type {number | undefined} */ /** @type {number | undefined} */
let timeoutId; let timeoutId;
/** @type {Parameters<F>} */ /** @type {Parameters<F>} */
@@ -1110,45 +1125,69 @@ const utils = {
}, wait); }, wait);
} }
}; };
}, }
/** /**
* @param {VoidFunction} callback * @param {VoidFunction} callback
* @param {number} [timeout = 1] * @param {number} [timeout = 1]
*/ */
runWhenIdle(callback, timeout = 1) { function runWhenIdle(callback, timeout = 1) {
if ("requestIdleCallback" in window) { if ("requestIdleCallback" in window) {
requestIdleCallback(callback); requestIdleCallback(callback);
} else { } else {
setTimeout(callback, timeout); setTimeout(callback, timeout);
} }
}, }
/** /**
* @param {Date} oldest * @param {Date} oldest
* @param {Date} youngest * @param {Date} youngest
* @returns {number} * @returns {number}
*/ */
getNumberOfDaysBetweenTwoDates(oldest, youngest) { function getNumberOfDaysBetweenTwoDates(oldest, youngest) {
const ONE_DAY_IN_MS = 1000 * 60 * 60 * 24;
return Math.round( return Math.round(
Math.abs((youngest.getTime() - oldest.getTime()) / consts.ONE_DAY_IN_MS), Math.abs((youngest.getTime() - oldest.getTime()) / ONE_DAY_IN_MS),
); );
}, }
/**
* @param {string} s
*/
function stringToId(s) {
return s.replace(/\W/g, " ").trim().replace(/ +/g, "-").toLowerCase();
}
/** /**
* @param {TimeScale} scale * @param {TimeScale} scale
* @param {number} id * @param {number} id
*/ */
chunkIdToIndex(scale, id) { function chunkIdToIndex(scale, id) {
return scale === "date" const HEIGHT_CHUNK_SIZE = 10_000;
? id - 2009 return scale === "date" ? id - 2009 : Math.floor(id / HEIGHT_CHUNK_SIZE);
: Math.floor(id / consts.HEIGHT_CHUNK_SIZE); }
},
/** return {
* @param {string} s isSerializedBooleanTrue,
*/ sleep,
stringToId(s) { next,
return s.replace(/\W/g, " ").trim().replace(/ +/g, "-").toLowerCase(); array,
}, dom,
url,
locale,
storage,
serde,
formatters,
date,
color,
debounce,
runWhenIdle,
getNumberOfDaysBetweenTwoDates,
chunkIdToIndex,
stringToId,
}; };
/** @typedef {typeof utils} Utilities */ }
/** @typedef {ReturnType<typeof createUtils>} Utilities */
function initEnv() { function initEnv() {
const standalone = const standalone =
@@ -1175,8 +1214,7 @@ function initEnv() {
localhost: window.location.hostname === "localhost", localhost: window.location.hostname === "localhost",
}; };
} }
const env = initEnv(); /** @typedef {ReturnType<typeof initEnv>} Env */
/** @typedef {typeof env} Env */
function createConstants() { function createConstants() {
const ONE_SECOND_IN_MS = 1_000; const ONE_SECOND_IN_MS = 1_000;
@@ -1207,152 +1245,62 @@ function createConstants() {
MEDIUM_WIDTH, MEDIUM_WIDTH,
}; };
} }
const consts = createConstants(); /** @typedef {ReturnType<typeof createConstants>} Constants */
/** @typedef {typeof consts} Consts */
const ids = /** @type {const} */ ({ function createIds() {
return /** @type {const} */ ({
selectedId: `selected-id`, selectedId: `selected-id`,
asideSelectorLabel: `aside-selector-label`, asideSelectorLabel: `aside-selector-label`,
checkedFrameSelectorLabel: "checked-frame-selector-label", checkedFrameSelectorLabel: "checked-frame-selector-label",
}); });
/** @typedef {typeof ids} Ids */ }
/** @typedef {ReturnType<typeof createIds>} Ids */
const elements = { /**
* @param {Ids} ids
*/
function getElements(ids) {
/**
* @param {string} id
*/
function getElementById(id) {
const element = window.document.getElementById(id);
if (!element) throw `Element with id = "${id}" should exist`;
return element;
}
return {
head: window.document.getElementsByTagName("head")[0], head: window.document.getElementsByTagName("head")[0],
body: window.document.body, body: window.document.body,
main: utils.dom.getElementById("main"), main: getElementById("main"),
aside: utils.dom.getElementById("aside"), aside: getElementById("aside"),
asideLabel: utils.dom.getElementById(ids.asideSelectorLabel), asideLabel: getElementById(ids.asideSelectorLabel),
navLabel: utils.dom.getElementById(`nav-selector-label`), navLabel: getElementById(`nav-selector-label`),
searchLabel: utils.dom.getElementById(`search-selector-label`), searchLabel: getElementById(`search-selector-label`),
search: utils.dom.getElementById("search"), search: getElementById("search"),
nav: utils.dom.getElementById("nav"), nav: getElementById("nav"),
navHeader: utils.dom.getElementById("nav-header"), navHeader: getElementById("nav-header"),
searchInput: /** @type {HTMLInputElement} */ ( searchInput: /** @type {HTMLInputElement} */ (
utils.dom.getElementById("search-input") getElementById("search-input")
), ),
searchSmall: utils.dom.getElementById("search-small"), searchSmall: getElementById("search-small"),
searchResults: utils.dom.getElementById("search-results"), searchResults: getElementById("search-results"),
selectors: utils.dom.getElementById("frame-selectors"), selectors: getElementById("frame-selectors"),
style: getComputedStyle(window.document.documentElement), style: getComputedStyle(window.document.documentElement),
charts: utils.dom.getElementById("charts"), charts: getElementById("charts"),
simulation: utils.dom.getElementById("simulation"), simulation: getElementById("simulation"),
livePrice: utils.dom.getElementById("live-price"), livePrice: getElementById("live-price"),
moscowTime: utils.dom.getElementById("moscow-time"), moscowTime: getElementById("moscow-time"),
}; };
/** @typedef {typeof elements} Elements */
const urlSelected = utils.url.pathnameToSelectedId();
function initFrameSelectors() {
const children = Array.from(elements.selectors.children);
/** @type {HTMLElement | undefined} */
let focusedFrame = undefined;
for (let i = 0; i < children.length; i++) {
const element = children[i];
switch (element.tagName) {
case "LABEL": {
element.addEventListener("click", () => {
const inputId = element.getAttribute("for");
if (!inputId) {
console.log(element, element.getAttribute("for"));
throw "Input id in label not found";
} }
/** @typedef {ReturnType<typeof getElements>} Elements */
const input = window.document.getElementById(inputId);
if (!input || !("value" in input)) {
throw "Not input or no value";
}
const frame = window.document.getElementById(
/** @type {string} */ (input.value),
);
if (!frame) {
console.log(input.value);
throw "Frame element doesn't exist";
}
if (frame === focusedFrame) {
return;
}
frame.hidden = false;
if (focusedFrame) {
focusedFrame.hidden = true;
}
focusedFrame = frame;
});
break;
}
}
}
elements.asideLabel.click();
// When going from mobile view to desktop view, if selected frame was open, go to the nav frame
new IntersectionObserver((entries) => {
for (let i = 0; i < entries.length; i++) {
if (
!entries[i].isIntersecting &&
entries[i].target === elements.asideLabel &&
focusedFrame == elements.aside
) {
elements.navLabel.click();
}
}
}).observe(elements.asideLabel);
function setAsideParent() {
const { clientWidth } = window.document.documentElement;
const { aside, body, main } = elements;
if (clientWidth >= consts.MEDIUM_WIDTH) {
aside.parentElement !== body && body.append(aside);
} else {
aside.parentElement !== main && main.append(aside);
}
}
setAsideParent();
window.addEventListener("resize", setAsideParent);
}
initFrameSelectors();
function createKeyDownEventListener() {
window.document.addEventListener("keydown", (event) => {
switch (event.key) {
case "Escape": {
event.stopPropagation();
event.preventDefault();
elements.navLabel.click();
break;
}
case "/": {
if (window.document.activeElement === elements.searchInput) {
return;
}
event.stopPropagation();
event.preventDefault();
elements.searchLabel.click();
elements.searchInput.focus();
break;
}
}
});
}
createKeyDownEventListener();
/** /**
* @param {Accessor<boolean>} dark * @param {Accessor<boolean>} dark
* @param {Elements} elements
* @param {Utilities} utils
*/ */
function createColors(dark) { function createColors(dark, elements, utils) {
/** /**
* @param {string} color * @param {string} color
*/ */
@@ -1568,8 +1516,10 @@ function createColors(dark) {
/** /**
* @param {Signals} signals * @param {Signals} signals
* @param {Constants} consts
* @param {Utilities} utils
*/ */
function createDatasets(signals) { function createDatasets(signals, consts, utils) {
/** @type {Map<DatePath, ResourceDataset<"date">>} */ /** @type {Map<DatePath, ResourceDataset<"date">>} */
const date = new Map(); const date = new Map();
/** @type {Map<HeightPath, ResourceDataset<"height">>} */ /** @type {Map<HeightPath, ResourceDataset<"height">>} */
@@ -1932,8 +1882,9 @@ function createDatasets(signals) {
/** /**
* @param {Signals} signals * @param {Signals} signals
* @param {Utilities} utils
*/ */
function initWebSockets(signals) { function initWebSockets(signals, utils) {
/** /**
* @template T * @template T
* @param {(callback: (value: T) => void) => WebSocket} creator * @param {(callback: (value: T) => void) => WebSocket} creator
@@ -2072,13 +2023,129 @@ function initWebSockets(signals) {
} }
/** @typedef {ReturnType<typeof initWebSockets>} WebSockets */ /** @typedef {ReturnType<typeof initWebSockets>} WebSockets */
function main() {
const packages = initPackages();
const options = import("./options.js");
const env = initEnv();
const consts = createConstants();
const utils = createUtils();
const ids = createIds();
const elements = getElements(ids);
function initFrameSelectors() {
const children = Array.from(elements.selectors.children);
/** @type {HTMLElement | undefined} */
let focusedFrame = undefined;
for (let i = 0; i < children.length; i++) {
const element = children[i];
switch (element.tagName) {
case "LABEL": {
element.addEventListener("click", () => {
const inputId = element.getAttribute("for");
if (!inputId) {
console.log(element, element.getAttribute("for"));
throw "Input id in label not found";
}
const input = window.document.getElementById(inputId);
if (!input || !("value" in input)) {
throw "Not input or no value";
}
const frame = window.document.getElementById(
/** @type {string} */ (input.value),
);
if (!frame) {
console.log(input.value);
throw "Frame element doesn't exist";
}
if (frame === focusedFrame) {
return;
}
frame.hidden = false;
if (focusedFrame) {
focusedFrame.hidden = true;
}
focusedFrame = frame;
});
break;
}
}
}
elements.asideLabel.click();
// When going from mobile view to desktop view, if selected frame was open, go to the nav frame
new IntersectionObserver((entries) => {
for (let i = 0; i < entries.length; i++) {
if (
!entries[i].isIntersecting &&
entries[i].target === elements.asideLabel &&
focusedFrame == elements.aside
) {
elements.navLabel.click();
}
}
}).observe(elements.asideLabel);
function setAsideParent() {
const { clientWidth } = window.document.documentElement;
const { aside, body, main } = elements;
if (clientWidth >= consts.MEDIUM_WIDTH) {
aside.parentElement !== body && body.append(aside);
} else {
aside.parentElement !== main && main.append(aside);
}
}
setAsideParent();
window.addEventListener("resize", setAsideParent);
}
initFrameSelectors();
function createKeyDownEventListener() {
window.document.addEventListener("keydown", (event) => {
switch (event.key) {
case "Escape": {
event.stopPropagation();
event.preventDefault();
elements.navLabel.click();
break;
}
case "/": {
if (window.document.activeElement === elements.searchInput) {
return;
}
event.stopPropagation();
event.preventDefault();
elements.searchLabel.click();
elements.searchInput.focus();
break;
}
}
});
}
createKeyDownEventListener();
packages.signals().then((signals) => packages.signals().then((signals) =>
options.then(({ initOptions }) => { options.then(({ initOptions }) => {
function initDark() { function initDark() {
const preferredColorSchemeMatchMedia = window.matchMedia( const preferredColorSchemeMatchMedia = window.matchMedia(
"(prefers-color-scheme: dark)", "(prefers-color-scheme: dark)",
); );
const dark = signals.createSignal(preferredColorSchemeMatchMedia.matches); const dark = signals.createSignal(
preferredColorSchemeMatchMedia.matches,
);
preferredColorSchemeMatchMedia.addEventListener( preferredColorSchemeMatchMedia.addEventListener(
"change", "change",
({ matches }) => { ({ matches }) => {
@@ -2129,9 +2196,9 @@ packages.signals().then((signals) =>
} }
createFetchLastValuesWhenNeededEffect(); createFetchLastValuesWhenNeededEffect();
const webSockets = initWebSockets(signals); const webSockets = initWebSockets(signals, utils);
const colors = createColors(dark); const colors = createColors(dark, elements, utils);
const options = initOptions({ const options = initOptions({
colors, colors,
@@ -2144,22 +2211,23 @@ packages.signals().then((signals) =>
qrcode, qrcode,
}); });
function createWindowPopStateEvent() { // const urlSelected = utils.url.pathnameToSelectedId();
window.addEventListener("popstate", (event) => { // function createWindowPopStateEvent() {
const urlSelected = utils.url.pathnameToSelectedId(); // window.addEventListener("popstate", (event) => {
const option = options.list.find((option) => urlSelected === option.id); // const urlSelected = utils.url.pathnameToSelectedId();
if (option) { // const option = options.list.find((option) => urlSelected === option.id);
options.selected.set(option); // if (option) {
} // options.selected.set(option);
}); // }
} // });
// }
// createWindowPopStateEvent(); // createWindowPopStateEvent();
function initSelected() { function initSelected() {
function initSelectedFrame() { function initSelectedFrame() {
console.log("selected: init"); console.log("selected: init");
const datasets = createDatasets(signals); const datasets = createDatasets(signals, consts, utils);
function createApplyOptionEffect() { function createApplyOptionEffect() {
const lastChartOption = signals.createSignal( const lastChartOption = signals.createSignal(
@@ -2214,6 +2282,7 @@ packages.signals().then((signals) =>
colors, colors,
datasets, datasets,
elements, elements,
consts,
lightweightCharts, lightweightCharts,
selected: /** @type {any} */ (lastChartOption), selected: /** @type {any} */ (lastChartOption),
signals, signals,
@@ -2249,6 +2318,7 @@ packages.signals().then((signals) =>
lightweightCharts, lightweightCharts,
signals, signals,
utils, utils,
consts,
lastValues, lastValues,
}), }),
), ),
@@ -2304,7 +2374,9 @@ packages.signals().then((signals) =>
const lightweightCharts = packages.lightweightCharts(); const lightweightCharts = packages.lightweightCharts();
const script = import("./moscow-time.js"); const script = import("./moscow-time.js");
utils.dom.importStyleAndThen("/styles/moscow-time.css", () => utils.dom.importStyleAndThen(
"/styles/moscow-time.css",
() =>
script.then(({ init }) => script.then(({ init }) =>
signals.runWithOwner(owner, () => signals.runWithOwner(owner, () =>
init({ init({
@@ -2431,13 +2503,15 @@ packages.signals().then((signals) =>
details.open = true; details.open = true;
i++; i++;
} catch { } catch {
await utils.yield(); await utils.next();
} }
} }
await utils.yield(); await utils.next();
utils.dom.getElementById(`${selectedId}-nav-selector`).scrollIntoView({ utils.dom
.getElementById(`${selectedId}-nav-selector`)
.scrollIntoView({
behavior: "instant", behavior: "instant",
block: "center", block: "center",
}); });
@@ -2532,7 +2606,9 @@ packages.signals().then((signals) =>
function inputEvent() { function inputEvent() {
signals.createRoot((_dispose) => { signals.createRoot((_dispose) => {
const needle = /** @type {string} */ (elements.searchInput.value); const needle = /** @type {string} */ (
elements.searchInput.value
);
dispose?.(); dispose?.();
@@ -2695,3 +2771,5 @@ packages.signals().then((signals) =>
initDesktopResizeBar(); initDesktopResizeBar();
}), }),
); );
}
main();

View File

@@ -7,7 +7,7 @@
/** /**
* @param {Object} args * @param {Object} args
* @param {Colors} args.colors * @param {Colors} args.colors
* @param {Consts} args.consts * @param {Constants} args.consts
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Utilities} args.utils * @param {Utilities} args.utils
* @param {Options} args.options * @param {Options} args.options

View File

@@ -5,9 +5,6 @@
* @import {AnySpecificSeriesBlueprint, SplitSeriesBlueprint} from '../packages/lightweight-charts/types'; * @import {AnySpecificSeriesBlueprint, SplitSeriesBlueprint} from '../packages/lightweight-charts/types';
*/ */
const DATE_TO_PREFIX = "date-to-";
const HEIGHT_TO_PREFIX = "height-to-";
function initGroups() { function initGroups() {
const xTermHolders = /** @type {const} */ ([ const xTermHolders = /** @type {const} */ ([
{ {
@@ -5204,8 +5201,8 @@ export function initOptions({
if (!blueprint) return undefined; if (!blueprint) return undefined;
const id = blueprint.datasetPath const id = blueprint.datasetPath
.replace(DATE_TO_PREFIX, "") .replace("date-to-", "")
.replace(HEIGHT_TO_PREFIX, ""); .replace("height-to-", "");
return /** @type {LastPath} */ (id); return /** @type {LastPath} */ (id);
} }

View File

@@ -13,6 +13,7 @@
* @param {Utilities} args.utils * @param {Utilities} args.utils
* @param {Datasets} args.datasets * @param {Datasets} args.datasets
* @param {Elements} args.elements * @param {Elements} args.elements
* @param {Constants} args.consts
* @param {Signal<LastValues>} args.lastValues * @param {Signal<LastValues>} args.lastValues
*/ */
export function init({ export function init({
@@ -22,6 +23,7 @@ export function init({
lightweightCharts, lightweightCharts,
signals, signals,
utils, utils,
consts,
lastValues, lastValues,
}) { }) {
const simulationElement = elements.simulation; const simulationElement = elements.simulation;
@@ -31,6 +33,120 @@ export function init({
const resultsElement = window.document.createElement("div"); const resultsElement = window.document.createElement("div");
simulationElement.append(resultsElement); simulationElement.append(resultsElement);
function computeFrequencies() {
const weekDays = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
];
const maxDays = 28;
/** @param {number} day */
function getOrdinalDay(day) {
const rest = (day % 30) % 20;
return `${day}${
rest === 1 ? "st" : rest === 2 ? "nd" : rest === 3 ? "rd" : "th"
}`;
}
/** @satisfies {([Frequency, Frequencies, Frequencies, Frequencies])} */
const list = [
{
name: "Every day",
value: "every-day",
/** @param {Date} _ */
isTriggerDay(_) {
return true;
},
},
{
name: "Once a week",
list: weekDays.map((day, index) => ({
name: day,
value: day.toLowerCase(),
/** @param {Date} date */
isTriggerDay(date) {
let day = date.getUTCDay() - 1;
if (day === -1) {
day = 6;
}
return day === index;
},
})),
},
{
name: "Every two weeks",
list: [...Array(Math.round(maxDays / 2)).keys()].map((day) => {
const day1 = day + 1;
const day2 = day + 15;
return {
value: `${day1}+${day2}`,
name: `The ${getOrdinalDay(day1)} and the ${getOrdinalDay(day2)}`,
/** @param {Date} date */
isTriggerDay(date) {
const d = date.getUTCDate();
return d === day1 || d === day2;
},
};
}),
},
{
name: "Once a month",
list: [...Array(maxDays).keys()].map((day) => {
day++;
return {
name: `The ${getOrdinalDay(day)}`,
value: String(day),
/** @param {Date} date */
isTriggerDay(date) {
const d = date.getUTCDate();
return d === day;
},
};
}),
},
];
/** @type {Record<string, Frequency>} */
const idToFrequency = {};
list.forEach((anyFreq, index) => {
if ("list" in anyFreq) {
anyFreq.list?.forEach((freq) => {
idToFrequency[freq.value] = freq;
});
} else {
idToFrequency[anyFreq.value] = anyFreq;
}
});
const serde = {
/**
* @param {Frequency} v
*/
serialize(v) {
return v.value;
},
/**
* @param {string} v
*/
deserialize(v) {
const freq = idToFrequency[v];
if (!freq) throw "Freq not found";
return freq;
},
};
return { list, serde };
}
const frequencies = computeFrequencies(); const frequencies = computeFrequencies();
const keyPrefix = "save-in-bitcoin"; const keyPrefix = "save-in-bitcoin";
@@ -437,6 +553,7 @@ export function init({
kind: "static", kind: "static",
scale: "date", scale: "date",
utils, utils,
consts,
config: [ config: [
{ {
unit: "US Dollars", unit: "US Dollars",
@@ -478,6 +595,7 @@ export function init({
scale: "date", scale: "date",
kind: "static", kind: "static",
utils, utils,
consts,
config: [ config: [
{ {
unit: "US Dollars", unit: "US Dollars",
@@ -501,6 +619,7 @@ export function init({
scale: "date", scale: "date",
kind: "static", kind: "static",
utils, utils,
consts,
config: [ config: [
{ {
unit: "US Dollars", unit: "US Dollars",
@@ -530,6 +649,7 @@ export function init({
scale: "date", scale: "date",
kind: "static", kind: "static",
utils, utils,
consts,
config: [ config: [
{ {
unit: "US Dollars", unit: "US Dollars",
@@ -563,6 +683,7 @@ export function init({
scale: "date", scale: "date",
utils, utils,
owner, owner,
consts,
config: [ config: [
{ {
unit: "Percentage", unit: "Percentage",
@@ -879,117 +1000,3 @@ export function init({
}); });
}); });
} }
/** @param {number} day */
function getOrdinalDay(day) {
const rest = (day % 30) % 20;
return `${day}${
rest === 1 ? "st" : rest === 2 ? "nd" : rest === 3 ? "rd" : "th"
}`;
}
function computeFrequencies() {
const weekDays = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
];
const maxDays = 28;
/** @satisfies {([Frequency, Frequencies, Frequencies, Frequencies])} */
const list = [
{
name: "Every day",
value: "every-day",
/** @param {Date} _ */
isTriggerDay(_) {
return true;
},
},
{
name: "Once a week",
list: weekDays.map((day, index) => ({
name: day,
value: day.toLowerCase(),
/** @param {Date} date */
isTriggerDay(date) {
let day = date.getUTCDay() - 1;
if (day === -1) {
day = 6;
}
return day === index;
},
})),
},
{
name: "Every two weeks",
list: [...Array(Math.round(maxDays / 2)).keys()].map((day) => {
const day1 = day + 1;
const day2 = day + 15;
return {
value: `${day1}+${day2}`,
name: `The ${getOrdinalDay(day1)} and the ${getOrdinalDay(day2)}`,
/** @param {Date} date */
isTriggerDay(date) {
const d = date.getUTCDate();
return d === day1 || d === day2;
},
};
}),
},
{
name: "Once a month",
list: [...Array(maxDays).keys()].map((day) => {
day++;
return {
name: `The ${getOrdinalDay(day)}`,
value: String(day),
/** @param {Date} date */
isTriggerDay(date) {
const d = date.getUTCDate();
return d === day;
},
};
}),
},
];
/** @type {Record<string, Frequency>} */
const idToFrequency = {};
list.forEach((anyFreq, index) => {
if ("list" in anyFreq) {
anyFreq.list?.forEach((freq) => {
idToFrequency[freq.value] = freq;
});
} else {
idToFrequency[anyFreq.value] = anyFreq;
}
});
const serde = {
/**
* @param {Frequency} v
*/
serialize(v) {
return v.value;
},
/**
* @param {string} v
*/
deserialize(v) {
const freq = idToFrequency[v];
if (!freq) throw "Freq not found";
return freq;
},
};
return { list, serde };
}