mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-29 21:52:09 -07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b6e3f47ab | |||
| 6a9ac9b025 | |||
| ae6aa4088b | |||
| c08f431180 | |||
| 123c1f56e9 | |||
| 35ac65a864 | |||
| e9f362cc87 | |||
| 65685c23e1 | |||
| 2f74748cea |
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
# Builds
|
# Builds
|
||||||
target
|
target
|
||||||
|
dist
|
||||||
|
|
||||||
# Copies
|
# Copies
|
||||||
*\ copy*
|
*\ copy*
|
||||||
|
|||||||
Generated
+1247
-119
File diff suppressed because it is too large
Load Diff
+16
-15
@@ -4,7 +4,7 @@ members = ["crates/*"]
|
|||||||
package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node"
|
package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node"
|
||||||
package.license = "MIT"
|
package.license = "MIT"
|
||||||
package.edition = "2024"
|
package.edition = "2024"
|
||||||
package.version = "0.0.55"
|
package.version = "0.0.59"
|
||||||
package.homepage = "https://bitcoinresearchkit.org"
|
package.homepage = "https://bitcoinresearchkit.org"
|
||||||
package.repository = "https://github.com/bitcoinresearchkit/brk"
|
package.repository = "https://github.com/bitcoinresearchkit/brk"
|
||||||
|
|
||||||
@@ -22,26 +22,27 @@ axum = "0.8.4"
|
|||||||
bincode = { version = "2.0.1", features = ["serde"] }
|
bincode = { version = "2.0.1", features = ["serde"] }
|
||||||
bitcoin = { version = "0.32.6", features = ["serde"] }
|
bitcoin = { version = "0.32.6", features = ["serde"] }
|
||||||
bitcoincore-rpc = "0.19.0"
|
bitcoincore-rpc = "0.19.0"
|
||||||
brk_cli = { version = "0.0.55", path = "crates/brk_cli" }
|
brk_bundler = { version = "0.0.59", path = "crates/brk_bundler" }
|
||||||
brk_computer = { version = "0.0.55", path = "crates/brk_computer" }
|
brk_cli = { version = "0.0.59", path = "crates/brk_cli" }
|
||||||
brk_core = { version = "0.0.55", path = "crates/brk_core" }
|
brk_computer = { version = "0.0.59", path = "crates/brk_computer" }
|
||||||
brk_exit = { version = "0.0.55", path = "crates/brk_exit" }
|
brk_core = { version = "0.0.59", path = "crates/brk_core" }
|
||||||
brk_fetcher = { version = "0.0.55", path = "crates/brk_fetcher" }
|
brk_exit = { version = "0.0.59", path = "crates/brk_exit" }
|
||||||
brk_indexer = { version = "0.0.55", path = "crates/brk_indexer" }
|
brk_fetcher = { version = "0.0.59", path = "crates/brk_fetcher" }
|
||||||
brk_logger = { version = "0.0.55", path = "crates/brk_logger" }
|
brk_indexer = { version = "0.0.59", path = "crates/brk_indexer" }
|
||||||
brk_parser = { version = "0.0.55", path = "crates/brk_parser" }
|
brk_logger = { version = "0.0.59", path = "crates/brk_logger" }
|
||||||
brk_query = { version = "0.0.55", path = "crates/brk_query" }
|
brk_parser = { version = "0.0.59", path = "crates/brk_parser" }
|
||||||
brk_server = { version = "0.0.55", path = "crates/brk_server" }
|
brk_query = { version = "0.0.59", path = "crates/brk_query" }
|
||||||
brk_state = { version = "0.0.55", path = "crates/brk_state" }
|
brk_server = { version = "0.0.59", path = "crates/brk_server" }
|
||||||
brk_store = { version = "0.0.55", path = "crates/brk_store" }
|
brk_state = { version = "0.0.59", path = "crates/brk_state" }
|
||||||
brk_vec = { version = "0.0.55", path = "crates/brk_vec" }
|
brk_store = { version = "0.0.59", path = "crates/brk_store" }
|
||||||
|
brk_vec = { version = "0.0.59", path = "crates/brk_vec" }
|
||||||
byteview = "=0.6.1"
|
byteview = "=0.6.1"
|
||||||
clap = { version = "4.5.40", features = ["string"] }
|
clap = { version = "4.5.40", features = ["string"] }
|
||||||
clap_derive = "4.5.40"
|
clap_derive = "4.5.40"
|
||||||
color-eyre = "0.6.5"
|
color-eyre = "0.6.5"
|
||||||
derive_deref = "1.1.1"
|
derive_deref = "1.1.1"
|
||||||
fjall = "2.11.0"
|
fjall = "2.11.0"
|
||||||
jiff = "0.2.14"
|
jiff = "0.2.15"
|
||||||
log = { version = "0.4.27" }
|
log = { version = "0.4.27" }
|
||||||
minreq = { version = "2.13.4", features = ["https", "serde_json"] }
|
minreq = { version = "2.13.4", features = ["https", "serde_json"] }
|
||||||
rayon = "1.10.0"
|
rayon = "1.10.0"
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ In contrast, existing alternatives tend to be either [very costly](https://studi
|
|||||||
- [`brk_state`](https://crates.io/crates/brk_state): Various states used mainly by the computer
|
- [`brk_state`](https://crates.io/crates/brk_state): Various states used mainly by the computer
|
||||||
- [`brk_store`](https://crates.io/crates/brk_store): A thin wrapper around [`fjall`](https://crates.io/crates/fjall)
|
- [`brk_store`](https://crates.io/crates/brk_store): A thin wrapper around [`fjall`](https://crates.io/crates/fjall)
|
||||||
- [`brk_vec`](https://crates.io/crates/brk_vec): A push-only, truncable, compressable, saveable Vec
|
- [`brk_vec`](https://crates.io/crates/brk_vec): A push-only, truncable, compressable, saveable Vec
|
||||||
|
- [`brk_bundler`](https://crates.io/crates/brk_bundler): A thin wrapper around [`rolldown`](https://rolldown.rs/)
|
||||||
|
|
||||||
## Hosting as a service
|
## Hosting as a service
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ version.workspace = true
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
full = [
|
full = [
|
||||||
|
"bundler",
|
||||||
"core",
|
"core",
|
||||||
"computer",
|
"computer",
|
||||||
"exit",
|
"exit",
|
||||||
@@ -23,6 +24,7 @@ full = [
|
|||||||
"store",
|
"store",
|
||||||
"vec",
|
"vec",
|
||||||
]
|
]
|
||||||
|
bundler = ["brk_bundler"]
|
||||||
core = ["brk_core"]
|
core = ["brk_core"]
|
||||||
computer = ["brk_computer"]
|
computer = ["brk_computer"]
|
||||||
exit = ["brk_exit"]
|
exit = ["brk_exit"]
|
||||||
@@ -37,6 +39,7 @@ store = ["brk_store"]
|
|||||||
vec = ["brk_vec"]
|
vec = ["brk_vec"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
brk_bundler = { workspace = true, optional = true }
|
||||||
brk_cli = { workspace = true }
|
brk_cli = { workspace = true }
|
||||||
brk_core = { workspace = true, optional = true }
|
brk_core = { workspace = true, optional = true }
|
||||||
brk_computer = { workspace = true, optional = true }
|
brk_computer = { workspace = true, optional = true }
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
fn main() {}
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
|
#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
|
||||||
|
|
||||||
|
#[cfg(feature = "bundler")]
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use brk_bundler as bundler;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use brk_cli as cli;
|
||||||
|
|
||||||
#[cfg(feature = "core")]
|
#[cfg(feature = "core")]
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use brk_core as core;
|
pub use brk_core as core;
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "brk_bundler"
|
||||||
|
description = "A thin wrapper around rolldown"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = { workspace = true }
|
||||||
|
notify = "8.0.0"
|
||||||
|
brk_rolldown = "0.0.1"
|
||||||
|
sugar_path = "1.2.0"
|
||||||
|
tokio = { workspace = true }
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
use std::{fs, io, path::Path, sync::Arc};
|
||||||
|
|
||||||
|
use brk_rolldown::{Bundler, BundlerOptions, RawMinifyOptions, SourceMapType};
|
||||||
|
use log::error;
|
||||||
|
use notify::{EventKind, RecursiveMode, Watcher};
|
||||||
|
use sugar_path::SugarPath;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
pub async fn bundle(websites_path: &Path, source_folder: &str, watch: bool) -> io::Result<()> {
|
||||||
|
let source_path = websites_path.join(source_folder);
|
||||||
|
let dist_path = websites_path.join("dist");
|
||||||
|
|
||||||
|
let _ = fs::remove_dir_all(&dist_path);
|
||||||
|
copy_dir_all(&source_path, &dist_path)?;
|
||||||
|
|
||||||
|
let source_scripts = format!("./{source_folder}/scripts");
|
||||||
|
let source_entry = format!("{source_scripts}/entry.js");
|
||||||
|
|
||||||
|
let absolute_websites_path = websites_path.absolutize();
|
||||||
|
|
||||||
|
let mut bundler = Bundler::new(BundlerOptions {
|
||||||
|
input: Some(vec![source_entry.into()]),
|
||||||
|
dir: Some("./dist/scripts".to_string()),
|
||||||
|
cwd: Some(absolute_websites_path),
|
||||||
|
minify: Some(RawMinifyOptions::Bool(true)),
|
||||||
|
sourcemap: Some(SourceMapType::File),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
bundler.write().await.unwrap();
|
||||||
|
|
||||||
|
let absolute_source_index_path = source_path.join("index.html").absolutize();
|
||||||
|
let absolute_source_index_path_clone = absolute_source_index_path.clone();
|
||||||
|
let absolute_source_path = source_path.absolutize();
|
||||||
|
let absolute_source_path_clone = absolute_source_path.clone();
|
||||||
|
let absolute_source_scripts_path = websites_path.join(source_scripts).absolutize();
|
||||||
|
let absolute_source_sw_path = source_path.join("service-worker.js").absolutize();
|
||||||
|
let absolute_source_sw_path_clone = absolute_source_sw_path.clone();
|
||||||
|
|
||||||
|
let absolute_dist_entry_path = dist_path.join("scripts/entry.js").absolutize();
|
||||||
|
let absolute_dist_index_path = dist_path.join("index.html").absolutize();
|
||||||
|
let absolute_dist_path = dist_path.absolutize();
|
||||||
|
let absolute_dist_path_clone = absolute_dist_path.clone();
|
||||||
|
let absolute_dist_sw_path = dist_path.join("service-worker.js").absolutize();
|
||||||
|
|
||||||
|
let write_index = move || {
|
||||||
|
let mut contents = fs::read_to_string(&absolute_source_index_path).unwrap();
|
||||||
|
|
||||||
|
if let Ok(entry) = fs::read_to_string(absolute_dist_path_clone.join("scripts/entry.js")) {
|
||||||
|
let start = entry.find("main").unwrap();
|
||||||
|
let end = entry.find(".js").unwrap();
|
||||||
|
let main_hashed = &entry[start..end];
|
||||||
|
contents = contents.replace("/scripts/main.js", &format!("/scripts/{main_hashed}.js"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = fs::write(&absolute_dist_index_path, contents);
|
||||||
|
};
|
||||||
|
|
||||||
|
let write_sw = move || {
|
||||||
|
let contents = fs::read_to_string(&absolute_source_sw_path)
|
||||||
|
.unwrap()
|
||||||
|
.replace("__VERSION__", &format!("v{VERSION}"));
|
||||||
|
let _ = fs::write(&absolute_dist_sw_path, contents);
|
||||||
|
};
|
||||||
|
|
||||||
|
write_index();
|
||||||
|
write_sw();
|
||||||
|
|
||||||
|
if !watch {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let write_index_clone = write_index.clone();
|
||||||
|
|
||||||
|
let mut entry_watcher = notify::recommended_watcher(
|
||||||
|
move |res: Result<notify::Event, notify::Error>| match res {
|
||||||
|
Ok(_) => write_index_clone(),
|
||||||
|
Err(e) => error!("watch error: {:?}", e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
entry_watcher
|
||||||
|
.watch(&absolute_dist_entry_path, RecursiveMode::Recursive)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut source_watcher = notify::recommended_watcher(
|
||||||
|
move |res: Result<notify::Event, notify::Error>| match res {
|
||||||
|
Ok(event) => match event.kind {
|
||||||
|
EventKind::Create(_) => event.paths,
|
||||||
|
EventKind::Modify(_) => event.paths,
|
||||||
|
_ => vec![],
|
||||||
|
}
|
||||||
|
.into_iter()
|
||||||
|
.filter(|path| path.starts_with(&absolute_source_path))
|
||||||
|
.filter(|path| !path.starts_with(&absolute_source_scripts_path))
|
||||||
|
.for_each(|source_path| {
|
||||||
|
let suffix = source_path.strip_prefix(&absolute_source_path).unwrap();
|
||||||
|
let dist_path = absolute_dist_path.join(suffix);
|
||||||
|
|
||||||
|
if source_path == absolute_source_index_path_clone {
|
||||||
|
write_index();
|
||||||
|
} else if source_path == absolute_source_sw_path_clone {
|
||||||
|
write_sw();
|
||||||
|
} else {
|
||||||
|
let _ = fs::copy(&source_path, &dist_path);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Err(e) => error!("watch error: {:?}", e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
source_watcher
|
||||||
|
.watch(&absolute_source_path_clone, RecursiveMode::Recursive)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let watcher =
|
||||||
|
brk_rolldown::Watcher::new(vec![Arc::new(Mutex::new(bundler))], None).unwrap();
|
||||||
|
|
||||||
|
watcher.start().await;
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||||
|
fs::create_dir_all(&dst)?;
|
||||||
|
for entry in fs::read_dir(src)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let ty = entry.file_type()?;
|
||||||
|
if ty.is_dir() {
|
||||||
|
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
||||||
|
} else {
|
||||||
|
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
+15
-14
@@ -63,8 +63,9 @@ pub fn run(config: RunConfig) -> color_eyre::Result<()> {
|
|||||||
|
|
||||||
let server = Server::new(served_indexer, served_computer, config.website())?;
|
let server = Server::new(served_indexer, served_computer, config.website())?;
|
||||||
|
|
||||||
|
let watch = config.watch();
|
||||||
let opt = Some(tokio::spawn(async move {
|
let opt = Some(tokio::spawn(async move {
|
||||||
server.serve().await.unwrap();
|
server.serve(watch).await.unwrap();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
sleep(Duration::from_secs(1));
|
sleep(Duration::from_secs(1));
|
||||||
@@ -178,6 +179,11 @@ pub struct RunConfig {
|
|||||||
#[arg(long, value_name = "SECONDS")]
|
#[arg(long, value_name = "SECONDS")]
|
||||||
delay: Option<u64>,
|
delay: Option<u64>,
|
||||||
|
|
||||||
|
/// DEV: Activate to watch the selected website's folder for changes, default: false, saved
|
||||||
|
#[serde(default, deserialize_with = "default_on_error")]
|
||||||
|
#[arg(long, value_name = "BOOL")]
|
||||||
|
watch: Option<bool>,
|
||||||
|
|
||||||
/// DEV: Activate checking address hashes for collisions when indexing, default: false, saved
|
/// DEV: Activate checking address hashes for collisions when indexing, default: false, saved
|
||||||
#[serde(default, deserialize_with = "default_on_error")]
|
#[serde(default, deserialize_with = "default_on_error")]
|
||||||
#[arg(long, value_name = "BOOL")]
|
#[arg(long, value_name = "BOOL")]
|
||||||
@@ -255,6 +261,10 @@ impl RunConfig {
|
|||||||
config_saved.check_collisions = Some(check_collisions);
|
config_saved.check_collisions = Some(check_collisions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(watch) = config_args.watch.take() {
|
||||||
|
config_saved.watch = Some(watch);
|
||||||
|
}
|
||||||
|
|
||||||
if config_args != RunConfig::default() {
|
if config_args != RunConfig::default() {
|
||||||
dbg!(config_args);
|
dbg!(config_args);
|
||||||
panic!("Didn't consume the full config")
|
panic!("Didn't consume the full config")
|
||||||
@@ -267,19 +277,6 @@ impl RunConfig {
|
|||||||
|
|
||||||
config.write(&path)?;
|
config.write(&path)?;
|
||||||
|
|
||||||
// info!("Configuration {{");
|
|
||||||
// info!(" bitcoindir: {:?}", config.bitcoindir);
|
|
||||||
// info!(" brkdir: {:?}", config.brkdir);
|
|
||||||
// info!(" services: {:?}", config.services);
|
|
||||||
// info!(" website: {:?}", config.website);
|
|
||||||
// info!(" rpcconnect: {:?}", config.rpcconnect);
|
|
||||||
// info!(" rpcport: {:?}", config.rpcport);
|
|
||||||
// info!(" rpccookiefile: {:?}", config.rpccookiefile);
|
|
||||||
// info!(" rpcuser: {:?}", config.rpcuser);
|
|
||||||
// info!(" rpcpassword: {:?}", config.rpcpassword);
|
|
||||||
// info!(" delay: {:?}", config.delay);
|
|
||||||
// info!("}}");
|
|
||||||
|
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,6 +445,10 @@ impl RunConfig {
|
|||||||
pub fn check_collisions(&self) -> bool {
|
pub fn check_collisions(&self) -> bool {
|
||||||
self.check_collisions.is_some_and(|b| b)
|
self.check_collisions.is_some_and(|b| b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn watch(&self) -> bool {
|
||||||
|
self.watch.is_some_and(|b| b)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
|||||||
@@ -1289,7 +1289,7 @@ impl Vecs {
|
|||||||
base_version + self.height_to_opreturn_supply.inner_version(),
|
base_version + self.height_to_opreturn_supply.inner_version(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut chain_state: Vec<BlockState>;
|
let mut chain_state: Vec<BlockState> = vec![];
|
||||||
let mut chain_state_starting_height = Height::from(self.chain_state.len());
|
let mut chain_state_starting_height = Height::from(self.chain_state.len());
|
||||||
|
|
||||||
let stateful_starting_height = match separate_utxo_vecs
|
let stateful_starting_height = match separate_utxo_vecs
|
||||||
@@ -1322,25 +1322,27 @@ impl Vecs {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
chain_state_starting_height
|
chain_state_starting_height
|
||||||
}
|
}
|
||||||
Ordering::Less => {
|
Ordering::Less => Height::ZERO,
|
||||||
// todo!("rollback instead");
|
|
||||||
chain_state = vec![];
|
|
||||||
chain_state_starting_height = Height::ZERO;
|
|
||||||
Height::ZERO
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
if stateful_starting_height.is_zero() {
|
|
||||||
info!("Starting processing utxos from the start");
|
|
||||||
separate_utxo_vecs
|
|
||||||
.par_iter_mut()
|
|
||||||
.try_for_each(|(_, v)| v.state.price_to_amount.reset())?;
|
|
||||||
}
|
|
||||||
let starting_height = starting_indexes
|
let starting_height = starting_indexes
|
||||||
.height
|
.height
|
||||||
.min(stateful_starting_height)
|
.min(stateful_starting_height)
|
||||||
.min(Height::from(self.height_to_unspendable_supply.len()))
|
.min(Height::from(self.height_to_unspendable_supply.len()))
|
||||||
.min(Height::from(self.height_to_opreturn_supply.len()));
|
.min(Height::from(self.height_to_opreturn_supply.len()));
|
||||||
|
|
||||||
|
if starting_height.is_zero() {
|
||||||
|
info!("Starting processing utxos from the start");
|
||||||
|
|
||||||
|
// todo!("rollback instead");
|
||||||
|
chain_state = vec![];
|
||||||
|
chain_state_starting_height = Height::ZERO;
|
||||||
|
|
||||||
|
separate_utxo_vecs
|
||||||
|
.par_iter_mut()
|
||||||
|
.try_for_each(|(_, v)| v.state.price_to_amount.reset())?;
|
||||||
|
}
|
||||||
|
|
||||||
if starting_height == Height::from(height_to_date_fixed.len()) {
|
if starting_height == Height::from(height_to_date_fixed.len()) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
|
|
||||||
Binance::fetch_1d().map(|b| {
|
Binance::fetch_1d().map(|b| {
|
||||||
dbg!(b.last_key_value());
|
dbg!(b.last_key_value());
|
||||||
});
|
})?;
|
||||||
Kraken::fetch_1d().map(|b| {
|
Kraken::fetch_1d().map(|b| {
|
||||||
dbg!(b.last_key_value());
|
dbg!(b.last_key_value());
|
||||||
});
|
})?;
|
||||||
Binance::fetch_1mn().map(|b| {
|
Binance::fetch_1mn().map(|b| {
|
||||||
dbg!(b.last_key_value());
|
dbg!(b.last_key_value());
|
||||||
});
|
})?;
|
||||||
Kraken::fetch_1mn().map(|b| {
|
Kraken::fetch_1mn().map(|b| {
|
||||||
dbg!(b.last_key_value());
|
dbg!(b.last_key_value());
|
||||||
});
|
})?;
|
||||||
|
|
||||||
dbg!(fetcher.get_date(Date::new(2025, 6, 5))?);
|
dbg!(fetcher.get_date(Date::new(2025, 6, 5))?);
|
||||||
dbg!(fetcher.get_height(
|
dbg!(fetcher.get_height(
|
||||||
|
|||||||
@@ -110,13 +110,7 @@ impl TryFrom<(&mut Vecs, &Stores, &Client)> for Indexes {
|
|||||||
vecs.height_to_blockhash
|
vecs.height_to_blockhash
|
||||||
.iter()
|
.iter()
|
||||||
.get(*height)
|
.get(*height)
|
||||||
.is_none_or(|saved_blockhash| {
|
.is_none_or(|saved_blockhash| &rpc_blockhash != saved_blockhash.as_ref())
|
||||||
let b = &rpc_blockhash != saved_blockhash.as_ref();
|
|
||||||
if b {
|
|
||||||
dbg!(rpc_blockhash, saved_blockhash.as_ref());
|
|
||||||
}
|
|
||||||
b
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.unwrap_or(starting_height);
|
.unwrap_or(starting_height);
|
||||||
|
|
||||||
|
|||||||
@@ -25,50 +25,53 @@ pub fn init(path: Option<&Path>) {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
Builder::from_env(Env::default().default_filter_or("info,fjall=off,lsm_tree=off"))
|
Builder::from_env(
|
||||||
.format(move |buf, record| {
|
Env::default()
|
||||||
let date_time = Timestamp::now()
|
.default_filter_or("info,fjall=off,lsm_tree=off,rolldown=off,brk_rolldown=off"),
|
||||||
.to_zoned(tz::TimeZone::system())
|
)
|
||||||
.strftime("%Y-%m-%d %H:%M:%S")
|
.format(move |buf, record| {
|
||||||
.to_string();
|
let date_time = Timestamp::now()
|
||||||
let level = record.level().as_str().to_lowercase();
|
.to_zoned(tz::TimeZone::system())
|
||||||
let level = format!("{:5}", level);
|
.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
let target = record.target();
|
.to_string();
|
||||||
let dash = "-";
|
let level = record.level().as_str().to_lowercase();
|
||||||
let args = record.args();
|
let level = format!("{:5}", level);
|
||||||
|
let target = record.target();
|
||||||
|
let dash = "-";
|
||||||
|
let args = record.args();
|
||||||
|
|
||||||
if let Some(file) = file.as_ref() {
|
if let Some(file) = file.as_ref() {
|
||||||
let _ = write(
|
let _ = write(
|
||||||
file.try_clone().unwrap(),
|
file.try_clone().unwrap(),
|
||||||
&date_time,
|
&date_time,
|
||||||
target,
|
|
||||||
&level,
|
|
||||||
dash,
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let colored_date_time = date_time.bright_black();
|
|
||||||
let colored_level = match level.chars().next().unwrap() {
|
|
||||||
'e' => level.red().to_string(),
|
|
||||||
'w' => level.yellow().to_string(),
|
|
||||||
'i' => level.green().to_string(),
|
|
||||||
'd' => level.blue().to_string(),
|
|
||||||
't' => level.cyan().to_string(),
|
|
||||||
_ => panic!(),
|
|
||||||
};
|
|
||||||
let colored_dash = dash.bright_black();
|
|
||||||
|
|
||||||
write(
|
|
||||||
buf,
|
|
||||||
colored_date_time,
|
|
||||||
target,
|
target,
|
||||||
colored_level,
|
&level,
|
||||||
colored_dash,
|
dash,
|
||||||
args,
|
args,
|
||||||
)
|
);
|
||||||
})
|
}
|
||||||
.init();
|
|
||||||
|
let colored_date_time = date_time.bright_black();
|
||||||
|
let colored_level = match level.chars().next().unwrap() {
|
||||||
|
'e' => level.red().to_string(),
|
||||||
|
'w' => level.yellow().to_string(),
|
||||||
|
'i' => level.green().to_string(),
|
||||||
|
'd' => level.blue().to_string(),
|
||||||
|
't' => level.cyan().to_string(),
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
let colored_dash = dash.bright_black();
|
||||||
|
|
||||||
|
write(
|
||||||
|
buf,
|
||||||
|
colored_date_time,
|
||||||
|
target,
|
||||||
|
colored_level,
|
||||||
|
colored_dash,
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(
|
fn write(
|
||||||
@@ -80,5 +83,9 @@ fn write(
|
|||||||
args: impl Display,
|
args: impl Display,
|
||||||
) -> Result<(), std::io::Error> {
|
) -> Result<(), std::io::Error> {
|
||||||
writeln!(buf, "{} {} {} {}", date_time, dash, level, args)
|
writeln!(buf, "{} {} {} {}", date_time, dash, level, args)
|
||||||
// writeln!(buf, "{} {} {} {} {}", date_time, _target, level, dash, args)
|
// writeln!(
|
||||||
|
// buf,
|
||||||
|
// "{} {} {} {} {}",
|
||||||
|
// date_time, _target, level, dash, args
|
||||||
|
// )
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ color-eyre = { workspace = true }
|
|||||||
derive_deref = { workspace = true }
|
derive_deref = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
serde_with = "3.12.0"
|
serde_with = "3.13.0"
|
||||||
tabled = { workspace = true }
|
tabled = { workspace = true }
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ repository.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { workspace = true }
|
axum = { workspace = true }
|
||||||
bitcoincore-rpc = { workspace = true }
|
bitcoincore-rpc = { workspace = true }
|
||||||
|
brk_bundler = { workspace = true }
|
||||||
brk_computer = { workspace = true }
|
brk_computer = { workspace = true }
|
||||||
brk_exit = { workspace = true }
|
brk_exit = { workspace = true }
|
||||||
brk_core = { workspace = true }
|
brk_core = { workspace = true }
|
||||||
@@ -25,11 +26,11 @@ 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.72.3", features = ["codegen", "minifier"] }
|
oxc = { version = "0.73.0", features = ["codegen", "minifier"] }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tower-http = { version = "0.6.6", features = ["compression-full", "trace"] }
|
tower-http = { version = "0.6.6", features = ["compression-full", "trace"] }
|
||||||
zip = "4.0.0"
|
zip = "4.1.0"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
|
|
||||||
[package.metadata.cargo-machete]
|
[package.metadata.cargo-machete]
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ pub fn main() -> color_eyre::Result<()> {
|
|||||||
let server = Server::new(served_indexer, served_computer, Website::Default)?;
|
let server = Server::new(served_indexer, served_computer, Website::Default)?;
|
||||||
|
|
||||||
let server = tokio::spawn(async move {
|
let server = tokio::spawn(async move {
|
||||||
server.serve().await.unwrap();
|
server.serve(true).await.unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
if process {
|
if process {
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ use crate::{
|
|||||||
traits::{HeaderMapExtended, ModifiedState, ResponseExtended},
|
traits::{HeaderMapExtended, ModifiedState, ResponseExtended},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::minify::minify_js;
|
|
||||||
|
|
||||||
pub async fn file_handler(
|
pub async fn file_handler(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
@@ -32,16 +30,12 @@ fn any_handler(
|
|||||||
app_state: AppState,
|
app_state: AppState,
|
||||||
path: Option<extract::Path<String>>,
|
path: Option<extract::Path<String>>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let website_path = app_state
|
let dist_path = app_state.dist_path();
|
||||||
.websites_path
|
|
||||||
.as_ref()
|
|
||||||
.expect("Should never reach here is websites_path is None")
|
|
||||||
.join(app_state.website.to_folder_name());
|
|
||||||
|
|
||||||
if let Some(path) = path.as_ref() {
|
if let Some(path) = path.as_ref() {
|
||||||
let path = path.0.replace("..", "").replace("\\", "");
|
let path = path.0.replace("..", "").replace("\\", "");
|
||||||
|
|
||||||
let mut path = website_path.join(&path);
|
let mut path = dist_path.join(&path);
|
||||||
|
|
||||||
if !path.exists() || path.is_dir() {
|
if !path.exists() || path.is_dir() {
|
||||||
if path.extension().is_some() {
|
if path.extension().is_some() {
|
||||||
@@ -55,13 +49,13 @@ fn any_handler(
|
|||||||
|
|
||||||
return response;
|
return response;
|
||||||
} else {
|
} else {
|
||||||
path = website_path.join("index.html");
|
path = dist_path.join("index.html");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
path_to_response(&headers, &path)
|
path_to_response(&headers, &path)
|
||||||
} else {
|
} else {
|
||||||
path_to_response(&headers, &website_path.join("index.html"))
|
path_to_response(&headers, &dist_path.join("index.html"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,49 +79,35 @@ fn path_to_response_(headers: &HeaderMap, path: &Path) -> color_eyre::Result<Res
|
|||||||
return Ok(Response::new_not_modified());
|
return Ok(Response::new_not_modified());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response;
|
let content = fs::read(path).unwrap_or_else(|error| {
|
||||||
|
error!("{error}");
|
||||||
|
let path = path.to_str().unwrap();
|
||||||
|
info!("Can't read file {path}");
|
||||||
|
panic!("")
|
||||||
|
});
|
||||||
|
|
||||||
let is_localhost = headers.check_if_host_is_localhost();
|
let mut response = Response::new(content.into());
|
||||||
|
|
||||||
if !is_localhost
|
|
||||||
&& path.extension().unwrap_or_else(|| {
|
|
||||||
dbg!(path);
|
|
||||||
panic!();
|
|
||||||
}) == "js"
|
|
||||||
{
|
|
||||||
let content = minify_js(path);
|
|
||||||
|
|
||||||
response = Response::new(content.into());
|
|
||||||
} else {
|
|
||||||
let content = fs::read(path).unwrap_or_else(|error| {
|
|
||||||
error!("{error}");
|
|
||||||
let path = path.to_str().unwrap();
|
|
||||||
info!("Can't read file {path}");
|
|
||||||
panic!("")
|
|
||||||
});
|
|
||||||
|
|
||||||
response = Response::new(content.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let headers = response.headers_mut();
|
let headers = response.headers_mut();
|
||||||
headers.insert_cors();
|
headers.insert_cors();
|
||||||
headers.insert_content_type(path);
|
headers.insert_content_type(path);
|
||||||
|
|
||||||
if !is_localhost {
|
let serialized_path = path.to_str().unwrap();
|
||||||
let serialized_path = path.to_str().unwrap();
|
|
||||||
|
|
||||||
if serialized_path.contains("fonts/")
|
if serialized_path.ends_with(".html") || serialized_path.ends_with("service-worker.js") {
|
||||||
|| serialized_path.contains("assets/")
|
headers.insert_cache_control_must_revalidate();
|
||||||
|| serialized_path.contains("packages/")
|
} else if serialized_path.contains("fonts/")
|
||||||
|| path.extension().is_some_and(|extension| {
|
|| serialized_path.contains("assets/")
|
||||||
extension == "pdf"
|
|| serialized_path.contains("packages/")
|
||||||
|| extension == "jpg"
|
|| path.extension().is_some_and(|extension| {
|
||||||
|| extension == "png"
|
extension == "pdf"
|
||||||
|| extension == "woff2"
|
|| extension == "jpg"
|
||||||
})
|
|| extension == "png"
|
||||||
{
|
|| extension == "woff2"
|
||||||
headers.insert_cache_control_immutable();
|
|| extension == "js"
|
||||||
}
|
})
|
||||||
|
{
|
||||||
|
headers.insert_cache_control_immutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.insert_last_modified(date);
|
headers.insert_last_modified(date);
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
// Source: https://github.com/oxc-project/oxc/blob/main/crates/oxc_minifier/examples/minifier.rs
|
|
||||||
|
|
||||||
use std::{fs, path::Path};
|
|
||||||
|
|
||||||
use oxc::{
|
|
||||||
allocator::Allocator,
|
|
||||||
codegen::{Codegen, CodegenOptions, LegalComment},
|
|
||||||
minifier::{CompressOptions, MangleOptions, Minifier, MinifierOptions},
|
|
||||||
parser::Parser,
|
|
||||||
span::SourceType,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn minify_js(path: &Path) -> String {
|
|
||||||
let source_text = fs::read_to_string(path).unwrap();
|
|
||||||
let source_type = SourceType::from_path(path).unwrap();
|
|
||||||
|
|
||||||
let allocator = Allocator::default();
|
|
||||||
|
|
||||||
let parser_return = Parser::new(&allocator, &source_text, source_type).parse();
|
|
||||||
|
|
||||||
let mut program = parser_return.program;
|
|
||||||
|
|
||||||
let minifier_return = Minifier::new(MinifierOptions {
|
|
||||||
mangle: Some(MangleOptions::default()),
|
|
||||||
compress: Some(CompressOptions::default()),
|
|
||||||
})
|
|
||||||
.build(&allocator, &mut program);
|
|
||||||
|
|
||||||
Codegen::new()
|
|
||||||
.with_options(CodegenOptions {
|
|
||||||
minify: true,
|
|
||||||
single_quote: false,
|
|
||||||
comments: false,
|
|
||||||
annotation_comments: false,
|
|
||||||
source_map_path: None,
|
|
||||||
legal_comments: LegalComment::None,
|
|
||||||
})
|
|
||||||
.with_scoping(minifier_return.scoping)
|
|
||||||
.build(&program)
|
|
||||||
.code
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ use axum::{Router, routing::get};
|
|||||||
use super::AppState;
|
use super::AppState;
|
||||||
|
|
||||||
mod file;
|
mod file;
|
||||||
mod minify;
|
|
||||||
mod website;
|
mod website;
|
||||||
|
|
||||||
use file::{file_handler, index_handler};
|
use file::{file_handler, index_handler};
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use axum::{
|
|||||||
routing::get,
|
routing::get,
|
||||||
serve,
|
serve,
|
||||||
};
|
};
|
||||||
|
use brk_bundler::bundle;
|
||||||
use brk_computer::Computer;
|
use brk_computer::Computer;
|
||||||
use brk_core::dot_brk_path;
|
use brk_core::dot_brk_path;
|
||||||
use brk_indexer::Indexer;
|
use brk_indexer::Indexer;
|
||||||
@@ -45,6 +46,15 @@ pub struct AppState {
|
|||||||
websites_path: Option<PathBuf>,
|
websites_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
pub fn dist_path(&self) -> PathBuf {
|
||||||
|
self.websites_path
|
||||||
|
.as_ref()
|
||||||
|
.expect("Should never reach here is websites_path is None")
|
||||||
|
.join("dist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
const DEV_PATH: &str = "../..";
|
const DEV_PATH: &str = "../..";
|
||||||
@@ -103,9 +113,13 @@ impl Server {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn serve(self) -> color_eyre::Result<()> {
|
pub async fn serve(self, watch: bool) -> color_eyre::Result<()> {
|
||||||
let state = self.0;
|
let state = self.0;
|
||||||
|
|
||||||
|
if let Some(websites_path) = state.websites_path.clone() {
|
||||||
|
bundle(&websites_path, state.website.to_folder_name(), watch).await?;
|
||||||
|
}
|
||||||
|
|
||||||
let compression_layer = CompressionLayer::new()
|
let compression_layer = CompressionLayer::new()
|
||||||
.br(true)
|
.br(true)
|
||||||
.deflate(true)
|
.deflate(true)
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ use std::{
|
|||||||
|
|
||||||
use axum::http::{
|
use axum::http::{
|
||||||
HeaderMap,
|
HeaderMap,
|
||||||
header::{self, HOST, IF_MODIFIED_SINCE},
|
header::{self, IF_MODIFIED_SINCE},
|
||||||
};
|
};
|
||||||
use jiff::{Timestamp, civil::DateTime, fmt::strtime, tz::TimeZone};
|
use jiff::{Timestamp, civil::DateTime, fmt::strtime, tz::TimeZone};
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
const STALE_IF_ERROR: u64 = 30_000_000; // 1 Year ish
|
|
||||||
const MODIFIED_SINCE_FORMAT: &str = "%a, %d %b %Y %H:%M:%S GMT";
|
const MODIFIED_SINCE_FORMAT: &str = "%a, %d %b %Y %H:%M:%S GMT";
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
@@ -20,12 +19,6 @@ pub enum ModifiedState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait HeaderMapExtended {
|
pub trait HeaderMapExtended {
|
||||||
fn _get_scheme(&self) -> &str;
|
|
||||||
fn get_host(&self) -> &str;
|
|
||||||
fn check_if_host_is_local(&self) -> bool;
|
|
||||||
fn check_if_host_is_0000(&self) -> bool;
|
|
||||||
fn check_if_host_is_localhost(&self) -> bool;
|
|
||||||
|
|
||||||
fn insert_cors(&mut self);
|
fn insert_cors(&mut self);
|
||||||
|
|
||||||
fn get_if_modified_since(&self) -> Option<DateTime>;
|
fn get_if_modified_since(&self) -> Option<DateTime>;
|
||||||
@@ -36,8 +29,8 @@ pub trait HeaderMapExtended {
|
|||||||
duration: Duration,
|
duration: Duration,
|
||||||
) -> color_eyre::Result<(ModifiedState, DateTime)>;
|
) -> color_eyre::Result<(ModifiedState, DateTime)>;
|
||||||
|
|
||||||
|
fn insert_cache_control_must_revalidate(&mut self);
|
||||||
fn insert_cache_control_immutable(&mut self);
|
fn insert_cache_control_immutable(&mut self);
|
||||||
fn _insert_cache_control_revalidate(&mut self, max_age: u64, stale_while_revalidate: u64);
|
|
||||||
fn insert_last_modified(&mut self, date: DateTime);
|
fn insert_last_modified(&mut self, date: DateTime);
|
||||||
|
|
||||||
fn insert_content_disposition_attachment(&mut self);
|
fn insert_content_disposition_attachment(&mut self);
|
||||||
@@ -59,41 +52,22 @@ pub trait HeaderMapExtended {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HeaderMapExtended for HeaderMap {
|
impl HeaderMapExtended for HeaderMap {
|
||||||
fn _get_scheme(&self) -> &str {
|
|
||||||
if self.check_if_host_is_local() {
|
|
||||||
"http"
|
|
||||||
} else {
|
|
||||||
"https"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_host(&self) -> &str {
|
|
||||||
self[HOST].to_str().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_if_host_is_local(&self) -> bool {
|
|
||||||
self.check_if_host_is_localhost() || self.check_if_host_is_0000()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_if_host_is_0000(&self) -> bool {
|
|
||||||
self.get_host().contains("0.0.0.0")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_if_host_is_localhost(&self) -> bool {
|
|
||||||
self.get_host().contains("localhost")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_cors(&mut self) {
|
fn insert_cors(&mut self) {
|
||||||
self.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*".parse().unwrap());
|
self.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*".parse().unwrap());
|
||||||
self.insert(header::ACCESS_CONTROL_ALLOW_HEADERS, "*".parse().unwrap());
|
self.insert(header::ACCESS_CONTROL_ALLOW_HEADERS, "*".parse().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn insert_cache_control_must_revalidate(&mut self) {
|
||||||
|
self.insert(
|
||||||
|
header::CACHE_CONTROL,
|
||||||
|
"public, max-age=0, must-revalidate".parse().unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn insert_cache_control_immutable(&mut self) {
|
fn insert_cache_control_immutable(&mut self) {
|
||||||
self.insert(
|
self.insert(
|
||||||
header::CACHE_CONTROL,
|
header::CACHE_CONTROL,
|
||||||
format!("public, max-age=604800, immutable, stale-if-error={STALE_IF_ERROR}")
|
"public, max-age=31536000, immutable".parse().unwrap(),
|
||||||
.parse()
|
|
||||||
.unwrap(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,16 +75,6 @@ impl HeaderMapExtended for HeaderMap {
|
|||||||
self.insert(header::CONTENT_DISPOSITION, "attachment".parse().unwrap());
|
self.insert(header::CONTENT_DISPOSITION, "attachment".parse().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _insert_cache_control_revalidate(&mut self, max_age: u64, stale_while_revalidate: u64) {
|
|
||||||
self.insert(
|
|
||||||
header::CACHE_CONTROL,
|
|
||||||
format!(
|
|
||||||
"public, max-age={max_age}, stale-while-revalidate={stale_while_revalidate}, stale-if-error={STALE_IF_ERROR}")
|
|
||||||
.parse()
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_last_modified(&mut self, date: DateTime) {
|
fn insert_last_modified(&mut self, date: DateTime) {
|
||||||
let formatted = date
|
let formatted = date
|
||||||
.to_zoned(TimeZone::system())
|
.to_zoned(TimeZone::system())
|
||||||
@@ -167,7 +131,7 @@ impl HeaderMapExtended for HeaderMap {
|
|||||||
fn insert_content_type(&mut self, path: &Path) {
|
fn insert_content_type(&mut self, path: &Path) {
|
||||||
match path.extension().unwrap().to_str().unwrap() {
|
match path.extension().unwrap().to_str().unwrap() {
|
||||||
"js" => self.insert_content_type_application_javascript(),
|
"js" => self.insert_content_type_application_javascript(),
|
||||||
"json" => self.insert_content_type_application_json(),
|
"json" | "map" => self.insert_content_type_application_json(),
|
||||||
"html" => self.insert_content_type_text_html(),
|
"html" => self.insert_content_type_text_html(),
|
||||||
"css" => self.insert_content_type_text_css(),
|
"css" => self.insert_content_type_text_css(),
|
||||||
"toml" | "txt" => self.insert_content_type_text_plain(),
|
"toml" | "txt" => self.insert_content_type_text_plain(),
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
DATE=$(date -u '+%Y-%m-%d_%H-%M-%S')
|
|
||||||
OUTPUT="/assets/pwa/${DATE}"
|
|
||||||
|
|
||||||
mkdir ".${OUTPUT}"
|
|
||||||
cp "./assets/pwa/index.html" ".${OUTPUT}/"
|
|
||||||
|
|
||||||
pwa-asset-generator "../assets/dove-orange.svg" ".${OUTPUT}" \
|
|
||||||
--index ".${OUTPUT}/index.html" \
|
|
||||||
--manifest "./manifest.webmanifest" \
|
|
||||||
--favicon \
|
|
||||||
--padding "0%" \
|
|
||||||
--path-override "${OUTPUT}" \
|
|
||||||
--quality "100" \
|
|
||||||
--opaque "false"
|
|
||||||
|
|
||||||
pwa-asset-generator "../assets/dove-white.svg" ".${OUTPUT}" \
|
|
||||||
--index ".${OUTPUT}/index.html" \
|
|
||||||
--manifest "./manifest.webmanifest" \
|
|
||||||
--icon-only \
|
|
||||||
--background "#f26610" \
|
|
||||||
--padding "10%" \
|
|
||||||
--path-override "${OUTPUT}" \
|
|
||||||
--quality "100"
|
|
||||||
|
|
||||||
pwa-asset-generator "../assets/logo-stamp-orange.svg" ".${OUTPUT}" \
|
|
||||||
--index ".${OUTPUT}/index.html" \
|
|
||||||
--splash-only \
|
|
||||||
--background "#f26610" \
|
|
||||||
--padding "min(30vh, 30vw)" \
|
|
||||||
--path-override "${OUTPUT}" \
|
|
||||||
--quality "100"
|
|
||||||
+268
-6
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
/>
|
/>
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<script type="module" crossorigin src="/scripts/main.js"></script>
|
<script type="module" src="/scripts/main.js"></script>
|
||||||
|
|
||||||
<!-- ------ -->
|
<!-- ------ -->
|
||||||
<!-- Styles -->
|
<!-- Styles -->
|
||||||
@@ -281,7 +281,7 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Geist mono";
|
font-family: "Geist mono";
|
||||||
src: url("./assets/fonts/geist_mono_var_1_4_01.woff2") format("woff2");
|
src: url("./assets/fonts/geist_mono_var_v1_5_0.woff2") format("woff2");
|
||||||
font-weight: 100 900;
|
font-weight: 100 900;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -1103,6 +1103,268 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#charts {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
padding: var(--main-padding);
|
||||||
|
|
||||||
|
header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
margin-bottom: -2rem;
|
||||||
|
padding-left: var(--main-padding);
|
||||||
|
margin-left: var(--negative-main-padding);
|
||||||
|
padding-right: var(--main-padding);
|
||||||
|
margin-right: var(--negative-main-padding);
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .chart > legend,
|
||||||
|
> fieldset {
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightweight-chart {
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#table {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
padding: var(--main-padding);
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
line--line-height: var(--line-height-xs);
|
||||||
|
font-weight: 450;
|
||||||
|
margin-left: var(--negative-main-padding);
|
||||||
|
margin-right: var(--negative-main-padding);
|
||||||
|
|
||||||
|
table {
|
||||||
|
z-index: 10;
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-style: dashed !important;
|
||||||
|
/* width: 100%; */
|
||||||
|
line-height: var(--line-height-sm);
|
||||||
|
text-transform: uppercase;
|
||||||
|
table-layout: auto;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
/* border: 3px solid purple; */
|
||||||
|
/* min-height: 100%; */
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
/* border-top: 1px; */
|
||||||
|
border-right: 1px;
|
||||||
|
border-bottom: 1px;
|
||||||
|
border-color: var(--off-color);
|
||||||
|
border-style: dashed !important;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
margin: -0.2rem 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:first-child {
|
||||||
|
padding-left: var(--main-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
th[scope="col"] {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-top: 0.275rem;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
text-transform: lowercase;
|
||||||
|
color: var(--off-color);
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
> button {
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
margin: 0 -0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
button:nth-of-type(1) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
button:nth-of-type(2) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
margin-right: -4px;
|
||||||
|
/* width: 100%; */
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
> button {
|
||||||
|
padding: 1rem;
|
||||||
|
min-width: 10rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
border-top-width: 1px;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-style: dashed !important;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
text-align: left;
|
||||||
|
position: sticky;
|
||||||
|
top: 2rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#simulation {
|
||||||
|
min-height: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
padding: var(--main-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
> div:first-child {
|
||||||
|
border-bottom: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-bottom: var(--bottom-area);
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:first-child {
|
||||||
|
max-width: var(--default-main-width);
|
||||||
|
border-right: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:last-child {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-wrap: pretty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
> span {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
small {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
line-height: var(--line-height-sm);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
flex: none;
|
||||||
|
height: 400px;
|
||||||
|
|
||||||
|
.lightweight-chart {
|
||||||
|
margin-left: calc(var(--negative-main-padding) * 0.75);
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
margin-left: -0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- ------- -->
|
<!-- ------- -->
|
||||||
@@ -1113,7 +1375,7 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
const preferredColorSchemeMatchMedia = window.matchMedia(
|
const preferredColorSchemeMatchMedia = window.matchMedia(
|
||||||
"(prefers-color-scheme: dark)"
|
"(prefers-color-scheme: dark)",
|
||||||
);
|
);
|
||||||
|
|
||||||
const themeColor = window.document.createElement("meta");
|
const themeColor = window.document.createElement("meta");
|
||||||
@@ -1123,7 +1385,7 @@
|
|||||||
/** @param {boolean} dark */
|
/** @param {boolean} dark */
|
||||||
function updateThemeColor(dark) {
|
function updateThemeColor(dark) {
|
||||||
const theme = getComputedStyle(
|
const theme = getComputedStyle(
|
||||||
window.document.documentElement
|
window.document.documentElement,
|
||||||
).getPropertyValue(dark ? "--black" : "--white");
|
).getPropertyValue(dark ? "--black" : "--white");
|
||||||
themeColor.content = theme;
|
themeColor.content = theme;
|
||||||
}
|
}
|
||||||
@@ -1133,7 +1395,7 @@
|
|||||||
"change",
|
"change",
|
||||||
({ matches }) => {
|
({ matches }) => {
|
||||||
updateThemeColor(matches);
|
updateThemeColor(matches);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if ("standalone" in window.navigator && !!window.navigator.standalone) {
|
if ("standalone" in window.navigator && !!window.navigator.standalone) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kibo.money",
|
"name": "brk",
|
||||||
"short_name": "kibo",
|
"short_name": "brk",
|
||||||
"description": "A better, FOSS, Bitcoin-only, self-hostable Glassnode",
|
"description": "A better, FOSS, Bitcoin-only, self-hostable Glassnode",
|
||||||
"categories": [
|
"categories": [
|
||||||
"bitcoin",
|
"bitcoin",
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
(async () => {
|
|
||||||
const theme = await (
|
|
||||||
await fetch(
|
|
||||||
"https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/theme.css",
|
|
||||||
)
|
|
||||||
).text();
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
[
|
|
||||||
"red",
|
|
||||||
"orange",
|
|
||||||
"amber",
|
|
||||||
"yellow",
|
|
||||||
"lime",
|
|
||||||
"green",
|
|
||||||
"emerald",
|
|
||||||
"teal",
|
|
||||||
"cyan",
|
|
||||||
"sky",
|
|
||||||
"blue",
|
|
||||||
"indigo",
|
|
||||||
"violet",
|
|
||||||
"purple",
|
|
||||||
"fuchsia",
|
|
||||||
"pink",
|
|
||||||
"rose",
|
|
||||||
]
|
|
||||||
.map((color) => {
|
|
||||||
const [a, b] = [500, 600].map((shade) => {
|
|
||||||
const regExp = new RegExp(
|
|
||||||
`(?<=${`${color}-${shade}: oklch\(`})(.*?)(?=\\s*${`\);`})`,
|
|
||||||
"g",
|
|
||||||
);
|
|
||||||
let res = regExp.exec(theme)?.[2];
|
|
||||||
if (!res) throw "err";
|
|
||||||
res = res.replace("(", "");
|
|
||||||
res = res.replace(")", "");
|
|
||||||
// return res
|
|
||||||
return res.split(" ").map((s) => Number(s));
|
|
||||||
});
|
|
||||||
const mult = 10_000;
|
|
||||||
return `--${color}: oklch(${[0, 1, 2].map((i) => Math.round(((a[i] + b[i]) / 2) * mult) / mult).join(" ")})`;
|
|
||||||
})
|
|
||||||
.join(";\n"),
|
|
||||||
);
|
|
||||||
})();
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
|||||||
|
https://app.unpkg.com/@solidjs/signals@latest/files/dist/prod.js
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { Accessor, Setter } from "./v0.3.2-treeshaked/types/signals";
|
|
||||||
|
|
||||||
export type Signal<T> = Accessor<T> & { set: Setter<T>; reset: VoidFunction };
|
|
||||||
export type Signals = Awaited<typeof import("./wrapper.js").default>;
|
|
||||||
@@ -1,679 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
// src/core/error.ts
|
|
||||||
var NotReadyError = class extends Error {};
|
|
||||||
var EffectError = class extends Error {
|
|
||||||
constructor(effect, cause) {
|
|
||||||
super("");
|
|
||||||
this.cause = cause;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// src/core/constants.ts
|
|
||||||
var STATE_CLEAN = 0;
|
|
||||||
var STATE_CHECK = 1;
|
|
||||||
var STATE_DIRTY = 2;
|
|
||||||
var STATE_DISPOSED = 3;
|
|
||||||
var EFFECT_PURE = 0;
|
|
||||||
var EFFECT_RENDER = 1;
|
|
||||||
var EFFECT_USER = 2;
|
|
||||||
|
|
||||||
// src/core/scheduler.ts
|
|
||||||
var clock = 0;
|
|
||||||
function getClock() {
|
|
||||||
return clock;
|
|
||||||
}
|
|
||||||
function incrementClock() {
|
|
||||||
clock++;
|
|
||||||
}
|
|
||||||
var scheduled = false;
|
|
||||||
function schedule() {
|
|
||||||
if (scheduled) return;
|
|
||||||
scheduled = true;
|
|
||||||
if (!globalQueue.u) queueMicrotask(flushSync);
|
|
||||||
}
|
|
||||||
var pureQueue = [];
|
|
||||||
var Queue = class {
|
|
||||||
i = null;
|
|
||||||
u = false;
|
|
||||||
v = [[], []];
|
|
||||||
t = [];
|
|
||||||
created = clock;
|
|
||||||
enqueue(type, fn) {
|
|
||||||
pureQueue.push(fn);
|
|
||||||
if (type) this.v[type - 1].push(fn);
|
|
||||||
schedule();
|
|
||||||
}
|
|
||||||
run(type) {
|
|
||||||
if (type === EFFECT_PURE) {
|
|
||||||
pureQueue.length && runQueue(pureQueue, type);
|
|
||||||
pureQueue = [];
|
|
||||||
return;
|
|
||||||
} else if (this.v[type - 1].length) {
|
|
||||||
const effects = this.v[type - 1];
|
|
||||||
this.v[type - 1] = [];
|
|
||||||
runQueue(effects, type);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < this.t.length; i++) {
|
|
||||||
this.t[i].run(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flush() {
|
|
||||||
if (this.u) return;
|
|
||||||
this.u = true;
|
|
||||||
try {
|
|
||||||
this.run(EFFECT_PURE);
|
|
||||||
incrementClock();
|
|
||||||
scheduled = false;
|
|
||||||
this.run(EFFECT_RENDER);
|
|
||||||
this.run(EFFECT_USER);
|
|
||||||
} finally {
|
|
||||||
this.u = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addChild(child) {
|
|
||||||
this.t.push(child);
|
|
||||||
child.i = this;
|
|
||||||
}
|
|
||||||
removeChild(child) {
|
|
||||||
const index = this.t.indexOf(child);
|
|
||||||
if (index >= 0) this.t.splice(index, 1);
|
|
||||||
}
|
|
||||||
notify(...args) {
|
|
||||||
if (this.i) return this.i.notify(...args);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var globalQueue = new Queue();
|
|
||||||
function flushSync() {
|
|
||||||
while (scheduled) {
|
|
||||||
globalQueue.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function runQueue(queue, type) {
|
|
||||||
for (let i = 0; i < queue.length; i++) queue[i](type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/core/owner.ts
|
|
||||||
var currentOwner = null;
|
|
||||||
var defaultContext = {};
|
|
||||||
function getOwner() {
|
|
||||||
return currentOwner;
|
|
||||||
}
|
|
||||||
function setOwner(owner) {
|
|
||||||
const out = currentOwner;
|
|
||||||
currentOwner = owner;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
var Owner = class {
|
|
||||||
// We flatten the owner tree into a linked list so that we don't need a pointer to .firstChild
|
|
||||||
// However, the children are actually added in reverse creation order
|
|
||||||
// See comment at the top of the file for an example of the _nextSibling traversal
|
|
||||||
i = null;
|
|
||||||
h = null;
|
|
||||||
l = null;
|
|
||||||
a = STATE_CLEAN;
|
|
||||||
f = null;
|
|
||||||
j = defaultContext;
|
|
||||||
g = globalQueue;
|
|
||||||
F = 0;
|
|
||||||
id = null;
|
|
||||||
constructor(id = null, skipAppend = false) {
|
|
||||||
this.id = id;
|
|
||||||
if (currentOwner) {
|
|
||||||
!skipAppend && currentOwner.append(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
append(child) {
|
|
||||||
child.i = this;
|
|
||||||
child.l = this;
|
|
||||||
if (this.h) this.h.l = child;
|
|
||||||
child.h = this.h;
|
|
||||||
this.h = child;
|
|
||||||
if (this.id != null && child.id == null) child.id = this.getNextChildId();
|
|
||||||
if (child.j !== this.j) {
|
|
||||||
child.j = { ...this.j, ...child.j };
|
|
||||||
}
|
|
||||||
if (this.g) child.g = this.g;
|
|
||||||
}
|
|
||||||
dispose(self = true) {
|
|
||||||
if (this.a === STATE_DISPOSED) return;
|
|
||||||
let head = self ? this.l || this.i : this,
|
|
||||||
current = this.h,
|
|
||||||
next = null;
|
|
||||||
while (current && current.i === this) {
|
|
||||||
current.dispose(true);
|
|
||||||
current.o();
|
|
||||||
next = current.h;
|
|
||||||
current.h = null;
|
|
||||||
current = next;
|
|
||||||
}
|
|
||||||
this.F = 0;
|
|
||||||
if (self) this.o();
|
|
||||||
if (current) current.l = !self ? this : this.l;
|
|
||||||
if (head) head.h = current;
|
|
||||||
}
|
|
||||||
o() {
|
|
||||||
if (this.l) this.l.h = null;
|
|
||||||
this.i = null;
|
|
||||||
this.l = null;
|
|
||||||
this.j = defaultContext;
|
|
||||||
this.a = STATE_DISPOSED;
|
|
||||||
this.emptyDisposal();
|
|
||||||
}
|
|
||||||
emptyDisposal() {
|
|
||||||
if (!this.f) return;
|
|
||||||
if (Array.isArray(this.f)) {
|
|
||||||
for (let i = 0; i < this.f.length; i++) {
|
|
||||||
const callable = this.f[i];
|
|
||||||
callable.call(callable);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.f.call(this.f);
|
|
||||||
}
|
|
||||||
this.f = null;
|
|
||||||
}
|
|
||||||
getNextChildId() {
|
|
||||||
if (this.id != null) return formatId(this.id, this.F++);
|
|
||||||
throw new Error("Cannot get child id from owner without an id");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function onCleanup(fn) {
|
|
||||||
if (!currentOwner) return fn;
|
|
||||||
const node = currentOwner;
|
|
||||||
if (!node.f) {
|
|
||||||
node.f = fn;
|
|
||||||
} else if (Array.isArray(node.f)) {
|
|
||||||
node.f.push(fn);
|
|
||||||
} else {
|
|
||||||
node.f = [node.f, fn];
|
|
||||||
}
|
|
||||||
return fn;
|
|
||||||
}
|
|
||||||
function formatId(prefix, id) {
|
|
||||||
const num = id.toString(36),
|
|
||||||
len = num.length - 1;
|
|
||||||
return prefix + (len ? String.fromCharCode(64 + len) : "") + num;
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/core/flags.ts
|
|
||||||
var ERROR_OFFSET = 0;
|
|
||||||
var ERROR_BIT = 1 << ERROR_OFFSET;
|
|
||||||
var LOADING_OFFSET = 1;
|
|
||||||
var LOADING_BIT = 1 << LOADING_OFFSET;
|
|
||||||
var UNINITIALIZED_OFFSET = 2;
|
|
||||||
var UNINITIALIZED_BIT = 1 << UNINITIALIZED_OFFSET;
|
|
||||||
var DEFAULT_FLAGS = ERROR_BIT;
|
|
||||||
|
|
||||||
// src/core/core.ts
|
|
||||||
var currentObserver = null;
|
|
||||||
var currentMask = DEFAULT_FLAGS;
|
|
||||||
var newSources = null;
|
|
||||||
var newSourcesIndex = 0;
|
|
||||||
var newFlags = 0;
|
|
||||||
var notStale = false;
|
|
||||||
var UNCHANGED = Symbol(0);
|
|
||||||
var Computation = class extends Owner {
|
|
||||||
b = null;
|
|
||||||
c = null;
|
|
||||||
e;
|
|
||||||
w;
|
|
||||||
p;
|
|
||||||
// Used in __DEV__ mode, hopefully removed in production
|
|
||||||
J;
|
|
||||||
// Using false is an optimization as an alternative to _equals: () => false
|
|
||||||
// which could enable more efficient DIRTY notification
|
|
||||||
A = isEqual;
|
|
||||||
G;
|
|
||||||
/** Whether the computation is an error or has ancestors that are unresolved */
|
|
||||||
d = 0;
|
|
||||||
/** Which flags raised by sources are handled, vs. being passed through. */
|
|
||||||
B = DEFAULT_FLAGS;
|
|
||||||
q = -1;
|
|
||||||
n = false;
|
|
||||||
constructor(initialValue, compute2, options) {
|
|
||||||
super(options?.id, compute2 === null);
|
|
||||||
this.p = compute2;
|
|
||||||
this.a = compute2 ? STATE_DIRTY : STATE_CLEAN;
|
|
||||||
this.d = compute2 && initialValue === void 0 ? UNINITIALIZED_BIT : 0;
|
|
||||||
this.e = initialValue;
|
|
||||||
if (options?.equals !== void 0) this.A = options.equals;
|
|
||||||
if (options?.unobserved) this.G = options?.unobserved;
|
|
||||||
}
|
|
||||||
H() {
|
|
||||||
if (this.p) {
|
|
||||||
if (this.d & ERROR_BIT && this.q <= getClock()) update(this);
|
|
||||||
else this.r();
|
|
||||||
}
|
|
||||||
track(this);
|
|
||||||
newFlags |= this.d & ~currentMask;
|
|
||||||
if (this.d & ERROR_BIT) {
|
|
||||||
throw this.w;
|
|
||||||
} else {
|
|
||||||
return this.e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Return the current value of this computation
|
|
||||||
* Automatically re-executes the surrounding computation when the value changes
|
|
||||||
*/
|
|
||||||
read() {
|
|
||||||
return this.H();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Return the current value of this computation
|
|
||||||
* Automatically re-executes the surrounding computation when the value changes
|
|
||||||
*
|
|
||||||
* If the computation has any unresolved ancestors, this function waits for the value to resolve
|
|
||||||
* before continuing
|
|
||||||
*/
|
|
||||||
wait() {
|
|
||||||
if (this.p && this.d & ERROR_BIT && this.q <= getClock()) {
|
|
||||||
update(this);
|
|
||||||
} else {
|
|
||||||
this.r();
|
|
||||||
}
|
|
||||||
track(this);
|
|
||||||
if ((notStale || this.d & UNINITIALIZED_BIT) && this.d & LOADING_BIT) {
|
|
||||||
throw new NotReadyError();
|
|
||||||
}
|
|
||||||
return this.H();
|
|
||||||
}
|
|
||||||
/** Update the computation with a new value. */
|
|
||||||
write(value, flags = 0, raw = false) {
|
|
||||||
const newValue =
|
|
||||||
!raw && typeof value === "function" ? value(this.e) : value;
|
|
||||||
const valueChanged =
|
|
||||||
newValue !== UNCHANGED &&
|
|
||||||
(!!(this.d & UNINITIALIZED_BIT) ||
|
|
||||||
this.d & LOADING_BIT & ~flags ||
|
|
||||||
this.A === false ||
|
|
||||||
!this.A(this.e, newValue));
|
|
||||||
if (valueChanged) {
|
|
||||||
this.e = newValue;
|
|
||||||
this.w = void 0;
|
|
||||||
}
|
|
||||||
const changedFlagsMask = this.d ^ flags,
|
|
||||||
changedFlags = changedFlagsMask & flags;
|
|
||||||
this.d = flags;
|
|
||||||
this.q = getClock() + 1;
|
|
||||||
if (this.c) {
|
|
||||||
for (let i = 0; i < this.c.length; i++) {
|
|
||||||
if (valueChanged) {
|
|
||||||
this.c[i].k(STATE_DIRTY);
|
|
||||||
} else if (changedFlagsMask) {
|
|
||||||
this.c[i].I(changedFlagsMask, changedFlags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.e;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Set the current node's state, and recursively mark all of this node's observers as STATE_CHECK
|
|
||||||
*/
|
|
||||||
k(state, skipQueue) {
|
|
||||||
if (this.a >= state && !this.n) return;
|
|
||||||
this.n = !!skipQueue;
|
|
||||||
this.a = state;
|
|
||||||
if (this.c) {
|
|
||||||
for (let i = 0; i < this.c.length; i++) {
|
|
||||||
this.c[i].k(STATE_CHECK, skipQueue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Notify the computation that one of its sources has changed flags.
|
|
||||||
*
|
|
||||||
* @param mask A bitmask for which flag(s) were changed.
|
|
||||||
* @param newFlags The source's new flags, masked to just the changed ones.
|
|
||||||
*/
|
|
||||||
I(mask, newFlags2) {
|
|
||||||
if (this.a >= STATE_DIRTY) return;
|
|
||||||
if (mask & this.B) {
|
|
||||||
this.k(STATE_DIRTY);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.a >= STATE_CHECK) return;
|
|
||||||
const prevFlags = this.d & mask;
|
|
||||||
const deltaFlags = prevFlags ^ newFlags2;
|
|
||||||
if (newFlags2 === prevFlags);
|
|
||||||
else if (deltaFlags & prevFlags & mask) {
|
|
||||||
this.k(STATE_CHECK);
|
|
||||||
} else {
|
|
||||||
this.d ^= deltaFlags;
|
|
||||||
if (this.c) {
|
|
||||||
for (let i = 0; i < this.c.length; i++) {
|
|
||||||
this.c[i].I(mask, newFlags2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
C(error) {
|
|
||||||
this.w = error;
|
|
||||||
this.write(
|
|
||||||
UNCHANGED,
|
|
||||||
(this.d & ~LOADING_BIT) | ERROR_BIT | UNINITIALIZED_BIT
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This is the core part of the reactivity system, which makes sure that the values are updated
|
|
||||||
* before they are read. We've also adapted it to return the loading state of the computation,
|
|
||||||
* so that we can propagate that to the computation's observers.
|
|
||||||
*
|
|
||||||
* This function will ensure that the value and states we read from the computation are up to date
|
|
||||||
*/
|
|
||||||
r() {
|
|
||||||
if (!this.p) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.a === STATE_DISPOSED) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.a === STATE_CLEAN) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let observerFlags = 0;
|
|
||||||
if (this.a === STATE_CHECK) {
|
|
||||||
for (let i = 0; i < this.b.length; i++) {
|
|
||||||
this.b[i].r();
|
|
||||||
observerFlags |= this.b[i].d;
|
|
||||||
if (this.a === STATE_DIRTY) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.a === STATE_DIRTY) {
|
|
||||||
update(this);
|
|
||||||
} else {
|
|
||||||
this.write(UNCHANGED, observerFlags);
|
|
||||||
this.a = STATE_CLEAN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Remove ourselves from the owner graph and the computation graph
|
|
||||||
*/
|
|
||||||
o() {
|
|
||||||
if (this.a === STATE_DISPOSED) return;
|
|
||||||
if (this.b) removeSourceObservers(this, 0);
|
|
||||||
super.o();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function track(computation) {
|
|
||||||
if (currentObserver) {
|
|
||||||
if (
|
|
||||||
!newSources &&
|
|
||||||
currentObserver.b &&
|
|
||||||
currentObserver.b[newSourcesIndex] === computation
|
|
||||||
) {
|
|
||||||
newSourcesIndex++;
|
|
||||||
} else if (!newSources) newSources = [computation];
|
|
||||||
else if (computation !== newSources[newSources.length - 1]) {
|
|
||||||
newSources.push(computation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function update(node) {
|
|
||||||
const prevSources = newSources,
|
|
||||||
prevSourcesIndex = newSourcesIndex,
|
|
||||||
prevFlags = newFlags;
|
|
||||||
newSources = null;
|
|
||||||
newSourcesIndex = 0;
|
|
||||||
newFlags = 0;
|
|
||||||
try {
|
|
||||||
node.dispose(false);
|
|
||||||
node.emptyDisposal();
|
|
||||||
const result = compute(node, node.p, node);
|
|
||||||
node.write(result, newFlags, true);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof NotReadyError) {
|
|
||||||
node.write(
|
|
||||||
UNCHANGED,
|
|
||||||
newFlags | LOADING_BIT | (node.d & UNINITIALIZED_BIT)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
node.C(error);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (newSources) {
|
|
||||||
if (node.b) removeSourceObservers(node, newSourcesIndex);
|
|
||||||
if (node.b && newSourcesIndex > 0) {
|
|
||||||
node.b.length = newSourcesIndex + newSources.length;
|
|
||||||
for (let i = 0; i < newSources.length; i++) {
|
|
||||||
node.b[newSourcesIndex + i] = newSources[i];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
node.b = newSources;
|
|
||||||
}
|
|
||||||
let source;
|
|
||||||
for (let i = newSourcesIndex; i < node.b.length; i++) {
|
|
||||||
source = node.b[i];
|
|
||||||
if (!source.c) source.c = [node];
|
|
||||||
else source.c.push(node);
|
|
||||||
}
|
|
||||||
} else if (node.b && newSourcesIndex < node.b.length) {
|
|
||||||
removeSourceObservers(node, newSourcesIndex);
|
|
||||||
node.b.length = newSourcesIndex;
|
|
||||||
}
|
|
||||||
newSources = prevSources;
|
|
||||||
newSourcesIndex = prevSourcesIndex;
|
|
||||||
newFlags = prevFlags;
|
|
||||||
node.q = getClock() + 1;
|
|
||||||
node.a = STATE_CLEAN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function removeSourceObservers(node, index) {
|
|
||||||
let source;
|
|
||||||
let swap;
|
|
||||||
for (let i = index; i < node.b.length; i++) {
|
|
||||||
source = node.b[i];
|
|
||||||
if (source.c) {
|
|
||||||
swap = source.c.indexOf(node);
|
|
||||||
source.c[swap] = source.c[source.c.length - 1];
|
|
||||||
source.c.pop();
|
|
||||||
if (!source.c.length) source.G?.();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function isEqual(a, b) {
|
|
||||||
return a === b;
|
|
||||||
}
|
|
||||||
function untrack(fn) {
|
|
||||||
if (currentObserver === null) return fn();
|
|
||||||
return compute(getOwner(), fn, null);
|
|
||||||
}
|
|
||||||
function latest(fn, fallback) {
|
|
||||||
const argLength = arguments.length;
|
|
||||||
const prevFlags = newFlags;
|
|
||||||
const prevNotStale = notStale;
|
|
||||||
notStale = false;
|
|
||||||
try {
|
|
||||||
return fn();
|
|
||||||
} catch (err) {
|
|
||||||
if (argLength > 1 && err instanceof NotReadyError) return fallback;
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
newFlags = prevFlags;
|
|
||||||
notStale = prevNotStale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function compute(owner, fn, observer) {
|
|
||||||
const prevOwner = setOwner(owner),
|
|
||||||
prevObserver = currentObserver,
|
|
||||||
prevMask = currentMask,
|
|
||||||
prevNotStale = notStale;
|
|
||||||
currentObserver = observer;
|
|
||||||
currentMask = observer?.B ?? DEFAULT_FLAGS;
|
|
||||||
notStale = true;
|
|
||||||
try {
|
|
||||||
return fn(observer ? observer.e : void 0);
|
|
||||||
} finally {
|
|
||||||
setOwner(prevOwner);
|
|
||||||
currentObserver = prevObserver;
|
|
||||||
currentMask = prevMask;
|
|
||||||
notStale = prevNotStale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/core/effect.ts
|
|
||||||
var Effect = class extends Computation {
|
|
||||||
x;
|
|
||||||
y;
|
|
||||||
s;
|
|
||||||
D = false;
|
|
||||||
z;
|
|
||||||
m;
|
|
||||||
constructor(initialValue, compute2, effect, error, options) {
|
|
||||||
super(initialValue, compute2, options);
|
|
||||||
this.x = effect;
|
|
||||||
this.y = error;
|
|
||||||
this.z = initialValue;
|
|
||||||
this.m = options?.render ? EFFECT_RENDER : EFFECT_USER;
|
|
||||||
if (this.m === EFFECT_RENDER) {
|
|
||||||
this.p = (p) =>
|
|
||||||
getClock() > this.g.created && !(this.d & ERROR_BIT)
|
|
||||||
? latest(() => compute2(p))
|
|
||||||
: compute2(p);
|
|
||||||
}
|
|
||||||
this.r();
|
|
||||||
!options?.defer &&
|
|
||||||
(this.m === EFFECT_USER
|
|
||||||
? this.g.enqueue(this.m, this.E.bind(this))
|
|
||||||
: this.E(this.m));
|
|
||||||
}
|
|
||||||
write(value, flags = 0) {
|
|
||||||
if (this.a == STATE_DIRTY) {
|
|
||||||
this.d;
|
|
||||||
this.d = flags;
|
|
||||||
if (this.m === EFFECT_RENDER) {
|
|
||||||
this.g.notify(this, LOADING_BIT | ERROR_BIT, flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (value === UNCHANGED) return this.e;
|
|
||||||
this.e = value;
|
|
||||||
this.D = true;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
k(state, skipQueue) {
|
|
||||||
if (this.a >= state || skipQueue) return;
|
|
||||||
if (this.a === STATE_CLEAN) this.g.enqueue(this.m, this.E.bind(this));
|
|
||||||
this.a = state;
|
|
||||||
}
|
|
||||||
C(error) {
|
|
||||||
this.w = error;
|
|
||||||
this.s?.();
|
|
||||||
this.g.notify(this, LOADING_BIT, 0);
|
|
||||||
this.d = ERROR_BIT;
|
|
||||||
if (this.m === EFFECT_USER) {
|
|
||||||
try {
|
|
||||||
return this.y
|
|
||||||
? (this.s = this.y(error))
|
|
||||||
: console.error(new EffectError(this.x, error));
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!this.g.notify(this, ERROR_BIT, ERROR_BIT)) throw error;
|
|
||||||
}
|
|
||||||
o() {
|
|
||||||
if (this.a === STATE_DISPOSED) return;
|
|
||||||
this.x = void 0;
|
|
||||||
this.z = void 0;
|
|
||||||
this.y = void 0;
|
|
||||||
this.s?.();
|
|
||||||
this.s = void 0;
|
|
||||||
super.o();
|
|
||||||
}
|
|
||||||
E(type) {
|
|
||||||
if (type) {
|
|
||||||
if (this.D && this.a !== STATE_DISPOSED) {
|
|
||||||
this.s?.();
|
|
||||||
try {
|
|
||||||
this.s = this.x(this.e, this.z);
|
|
||||||
} catch (e) {
|
|
||||||
if (!this.g.notify(this, ERROR_BIT, ERROR_BIT)) throw e;
|
|
||||||
} finally {
|
|
||||||
this.z = this.e;
|
|
||||||
this.D = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else this.a !== STATE_CLEAN && runTop(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function runTop(node) {
|
|
||||||
const ancestors = [];
|
|
||||||
for (let current = node; current !== null; current = current.i) {
|
|
||||||
if (current.a !== STATE_CLEAN) {
|
|
||||||
ancestors.push(current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
||||||
if (ancestors[i].a !== STATE_DISPOSED) ancestors[i].r();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/signals.ts
|
|
||||||
function createSignal(first, second, third) {
|
|
||||||
if (typeof first === "function") {
|
|
||||||
const memo = createMemo((p) => {
|
|
||||||
const node2 = new Computation(
|
|
||||||
first(p ? untrack(p[0]) : second),
|
|
||||||
null,
|
|
||||||
third
|
|
||||||
);
|
|
||||||
return [node2.read.bind(node2), node2.write.bind(node2)];
|
|
||||||
});
|
|
||||||
return [() => memo()[0](), (value) => memo()[1](value)];
|
|
||||||
}
|
|
||||||
const o = getOwner();
|
|
||||||
const needsId = o?.id != null;
|
|
||||||
const node = new Computation(
|
|
||||||
first,
|
|
||||||
null,
|
|
||||||
needsId ? { id: o.getNextChildId(), ...second } : second
|
|
||||||
);
|
|
||||||
return [node.read.bind(node), node.write.bind(node)];
|
|
||||||
}
|
|
||||||
function createMemo(compute2, value, options) {
|
|
||||||
let node = new Computation(value, compute2, options);
|
|
||||||
let resolvedValue;
|
|
||||||
return () => {
|
|
||||||
if (node) {
|
|
||||||
if (node.a === STATE_DISPOSED) {
|
|
||||||
node = void 0;
|
|
||||||
return resolvedValue;
|
|
||||||
}
|
|
||||||
resolvedValue = node.wait();
|
|
||||||
if (!node.b?.length && node.h?.i !== node) {
|
|
||||||
node.dispose();
|
|
||||||
node = void 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resolvedValue;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function createEffect(compute2, effect, error, value, options) {
|
|
||||||
void new Effect(value, compute2, effect, error, options);
|
|
||||||
}
|
|
||||||
function createRoot(init, options) {
|
|
||||||
const owner = new Owner(options?.id);
|
|
||||||
return compute(
|
|
||||||
owner,
|
|
||||||
!init.length ? init : () => init(() => owner.dispose()),
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function runWithOwner(owner, run) {
|
|
||||||
return compute(owner, run, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
Owner,
|
|
||||||
createEffect,
|
|
||||||
createMemo,
|
|
||||||
createRoot,
|
|
||||||
createSignal,
|
|
||||||
getOwner,
|
|
||||||
onCleanup,
|
|
||||||
runWithOwner,
|
|
||||||
untrack,
|
|
||||||
};
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,157 +1,161 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @import { SignalOptions } from "./v0.3.2-treeshaked/types/core/core"
|
* @import { SignalOptions } from "./v0.3.2/types/core/core"
|
||||||
* @import { getOwner as GetOwner, onCleanup as OnCleanup } from "./v0.3.2-treeshaked/types/core/owner"
|
* @import { getOwner as GetOwner, onCleanup as OnCleanup } from "./v0.3.2/types/core/owner"
|
||||||
* @import { createSignal as CreateSignal, createEffect as CreateEffect, createMemo as CreateMemo, createRoot as CreateRoot, runWithOwner as RunWithOwner, Accessor } from "./v0.3.2-treeshaked/types/signals";
|
* @import { createSignal as CreateSignal, createEffect as CreateEffect, createMemo as CreateMemo, createRoot as CreateRoot, runWithOwner as RunWithOwner, Setter } from "./v0.3.2/types/signals";
|
||||||
* @import { Signal } from "./types";
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {() => T} Accessor
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {Accessor<T> & { set: Setter<T>; reset: VoidFunction }} Signal
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
createSignal,
|
||||||
|
createEffect,
|
||||||
|
getOwner,
|
||||||
|
createMemo,
|
||||||
|
createRoot,
|
||||||
|
runWithOwner,
|
||||||
|
onCleanup,
|
||||||
|
} from "./v0.3.2/script.js";
|
||||||
|
|
||||||
let effectCount = 0;
|
let effectCount = 0;
|
||||||
|
|
||||||
const importSignals = import("./v0.3.2-treeshaked/script.js").then(
|
const signals = {
|
||||||
(_signals) => {
|
createSolidSignal: /** @type {typeof CreateSignal} */ (createSignal),
|
||||||
const signals = {
|
createSolidEffect: /** @type {typeof CreateEffect} */ (createEffect),
|
||||||
createSolidSignal: /** @type {typeof CreateSignal} */ (
|
createEffect: /** @type {typeof CreateEffect} */ (
|
||||||
_signals.createSignal
|
// @ts-ignore
|
||||||
),
|
(compute, effect) => {
|
||||||
createSolidEffect: /** @type {typeof CreateEffect} */ (
|
let dispose = /** @type {VoidFunction | null} */ (null);
|
||||||
_signals.createEffect
|
|
||||||
),
|
|
||||||
createEffect: /** @type {typeof CreateEffect} */ (
|
|
||||||
// @ts-ignore
|
|
||||||
(compute, effect) => {
|
|
||||||
let dispose = /** @type {VoidFunction | null} */ (null);
|
|
||||||
|
|
||||||
if (_signals.getOwner() === null) {
|
if (getOwner() === null) {
|
||||||
throw Error("No owner");
|
throw Error("No owner");
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
if (dispose) {
|
if (dispose) {
|
||||||
dispose();
|
dispose();
|
||||||
dispose = null;
|
dispose = null;
|
||||||
// console.log("effectCount = ", --effectCount);
|
// console.log("effectCount = ", --effectCount);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
_signals.createEffect(compute, (v, oldV) => {
|
|
||||||
// console.log("effectCount = ", ++effectCount);
|
|
||||||
cleanup();
|
|
||||||
signals.createRoot((_dispose) => {
|
|
||||||
dispose = _dispose;
|
|
||||||
return effect(v, oldV);
|
|
||||||
});
|
|
||||||
signals.onCleanup(cleanup);
|
|
||||||
});
|
|
||||||
signals.onCleanup(cleanup);
|
|
||||||
}
|
}
|
||||||
),
|
}
|
||||||
createMemo: /** @type {typeof CreateMemo} */ (_signals.createMemo),
|
|
||||||
createRoot: /** @type {typeof CreateRoot} */ (_signals.createRoot),
|
|
||||||
getOwner: /** @type {typeof GetOwner} */ (_signals.getOwner),
|
|
||||||
runWithOwner: /** @type {typeof RunWithOwner} */ (_signals.runWithOwner),
|
|
||||||
onCleanup: /** @type {typeof OnCleanup} */ (_signals.onCleanup),
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param {T} initialValue
|
|
||||||
* @param {SignalOptions<T> & {save?: {keyPrefix: string | Accessor<string>; key: string; serialize: (v: T) => string; deserialize: (v: string) => T; serializeParam?: boolean}}} [options]
|
|
||||||
* @returns {Signal<T>}
|
|
||||||
*/
|
|
||||||
createSignal(initialValue, options) {
|
|
||||||
const [get, set] = this.createSolidSignal(
|
|
||||||
/** @type {any} */ (initialValue),
|
|
||||||
options
|
|
||||||
);
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
get.set = set;
|
createEffect(compute, (v, oldV) => {
|
||||||
|
// console.log("effectCount = ", ++effectCount);
|
||||||
|
cleanup();
|
||||||
|
signals.createRoot((_dispose) => {
|
||||||
|
dispose = _dispose;
|
||||||
|
return effect(v, oldV);
|
||||||
|
});
|
||||||
|
signals.onCleanup(cleanup);
|
||||||
|
});
|
||||||
|
signals.onCleanup(cleanup);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
createMemo: /** @type {typeof CreateMemo} */ (createMemo),
|
||||||
|
createRoot: /** @type {typeof CreateRoot} */ (createRoot),
|
||||||
|
getOwner: /** @type {typeof GetOwner} */ (getOwner),
|
||||||
|
runWithOwner: /** @type {typeof RunWithOwner} */ (runWithOwner),
|
||||||
|
onCleanup: /** @type {typeof OnCleanup} */ (onCleanup),
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T} initialValue
|
||||||
|
* @param {SignalOptions<T> & {save?: {keyPrefix: string | Accessor<string>; key: string; serialize: (v: T) => string; deserialize: (v: string) => T; serializeParam?: boolean}}} [options]
|
||||||
|
* @returns {Signal<T>}
|
||||||
|
*/
|
||||||
|
createSignal(initialValue, options) {
|
||||||
|
const [get, set] = this.createSolidSignal(
|
||||||
|
/** @type {any} */ (initialValue),
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
get.reset = () => set(initialValue);
|
get.set = set;
|
||||||
|
|
||||||
if (options?.save) {
|
// @ts-ignore
|
||||||
const save = options.save;
|
get.reset = () => set(initialValue);
|
||||||
|
|
||||||
const paramKey = save.key;
|
if (options?.save) {
|
||||||
const storageKey = this.createMemo(
|
const save = options.save;
|
||||||
() =>
|
|
||||||
`${
|
|
||||||
typeof save.keyPrefix === "string"
|
|
||||||
? save.keyPrefix
|
|
||||||
: save.keyPrefix()
|
|
||||||
}-${paramKey}`
|
|
||||||
);
|
|
||||||
|
|
||||||
let serialized = /** @type {string | null} */ (null);
|
const paramKey = save.key;
|
||||||
if (options.save.serializeParam !== false) {
|
const storageKey = this.createMemo(
|
||||||
serialized = new URLSearchParams(window.location.search).get(
|
() =>
|
||||||
paramKey
|
`${
|
||||||
);
|
typeof save.keyPrefix === "string"
|
||||||
|
? save.keyPrefix
|
||||||
|
: save.keyPrefix()
|
||||||
|
}-${paramKey}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
let serialized = /** @type {string | null} */ (null);
|
||||||
|
if (options.save.serializeParam !== false) {
|
||||||
|
serialized = new URLSearchParams(window.location.search).get(paramKey);
|
||||||
|
}
|
||||||
|
if (serialized === null) {
|
||||||
|
serialized = localStorage.getItem(storageKey());
|
||||||
|
}
|
||||||
|
if (serialized) {
|
||||||
|
set(() => (serialized ? save.deserialize(serialized) : initialValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
let firstRun1 = true;
|
||||||
|
this.createEffect(storageKey, (storageKey) => {
|
||||||
|
if (!firstRun1) {
|
||||||
|
serialized = localStorage.getItem(storageKey);
|
||||||
|
set(() => (serialized ? save.deserialize(serialized) : initialValue));
|
||||||
|
}
|
||||||
|
firstRun1 = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
let firstRun2 = true;
|
||||||
|
this.createEffect(get, (value) => {
|
||||||
|
if (!save) return;
|
||||||
|
|
||||||
|
if (!firstRun2) {
|
||||||
|
if (
|
||||||
|
value !== undefined &&
|
||||||
|
value !== null &&
|
||||||
|
(initialValue === undefined ||
|
||||||
|
initialValue === null ||
|
||||||
|
save.serialize(value) !== save.serialize(initialValue))
|
||||||
|
) {
|
||||||
|
localStorage.setItem(storageKey(), save.serialize(value));
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem(storageKey());
|
||||||
}
|
}
|
||||||
if (serialized === null) {
|
|
||||||
serialized = localStorage.getItem(storageKey());
|
|
||||||
}
|
|
||||||
if (serialized) {
|
|
||||||
set(() =>
|
|
||||||
serialized ? save.deserialize(serialized) : initialValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let firstRun1 = true;
|
|
||||||
this.createEffect(storageKey, (storageKey) => {
|
|
||||||
if (!firstRun1) {
|
|
||||||
serialized = localStorage.getItem(storageKey);
|
|
||||||
set(() =>
|
|
||||||
serialized ? save.deserialize(serialized) : initialValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
firstRun1 = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
let firstRun2 = true;
|
|
||||||
this.createEffect(get, (value) => {
|
|
||||||
if (!save) return;
|
|
||||||
|
|
||||||
if (!firstRun2) {
|
|
||||||
if (
|
|
||||||
value !== undefined &&
|
|
||||||
value !== null &&
|
|
||||||
(initialValue === undefined ||
|
|
||||||
initialValue === null ||
|
|
||||||
save.serialize(value) !== save.serialize(initialValue))
|
|
||||||
) {
|
|
||||||
localStorage.setItem(storageKey(), save.serialize(value));
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem(storageKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
value !== undefined &&
|
|
||||||
value !== null &&
|
|
||||||
(initialValue === undefined ||
|
|
||||||
initialValue === null ||
|
|
||||||
save.serialize(value) !== save.serialize(initialValue))
|
|
||||||
) {
|
|
||||||
writeParam(paramKey, save.serialize(value));
|
|
||||||
} else {
|
|
||||||
removeParam(paramKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
firstRun2 = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
if (
|
||||||
return get;
|
value !== undefined &&
|
||||||
},
|
value !== null &&
|
||||||
};
|
(initialValue === undefined ||
|
||||||
|
initialValue === null ||
|
||||||
|
save.serialize(value) !== save.serialize(initialValue))
|
||||||
|
) {
|
||||||
|
writeParam(paramKey, save.serialize(value));
|
||||||
|
} else {
|
||||||
|
removeParam(paramKey);
|
||||||
|
}
|
||||||
|
|
||||||
return signals;
|
firstRun2 = false;
|
||||||
}
|
});
|
||||||
);
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return get;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
/** @typedef {typeof signals} Signals */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
@@ -170,7 +174,7 @@ function writeParam(key, value) {
|
|||||||
window.history.replaceState(
|
window.history.replaceState(
|
||||||
null,
|
null,
|
||||||
"",
|
"",
|
||||||
`${window.location.pathname}?${urlParams.toString()}`
|
`${window.location.pathname}?${urlParams.toString()}`,
|
||||||
);
|
);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
@@ -182,4 +186,4 @@ function removeParam(key) {
|
|||||||
writeParam(key, undefined);
|
writeParam(key, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default importSignals;
|
export default signals;
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ export function init({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const chart = lightweightCharts.createChartElement({
|
const chart = lightweightCharts.createChartElement({
|
||||||
owner: signals.getOwner(),
|
|
||||||
parent: elements.charts,
|
parent: elements.charts,
|
||||||
signals,
|
signals,
|
||||||
colors,
|
colors,
|
||||||
@@ -156,7 +155,7 @@ export function init({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {ISeriesApi<any, number>} params.iseries
|
* @param {ISeries} params.iseries
|
||||||
* @param {Unit} params.unit
|
* @param {Unit} params.unit
|
||||||
* @param {Index} params.index
|
* @param {Index} params.index
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// DO NOT CHANGE, Exact format is expected in `brk_bundler`
|
||||||
|
// @ts-ignore
|
||||||
|
import("./main.js");
|
||||||
@@ -1,14 +1,10 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, ChartableIndex,CreatePriceLineOptions, CreatePriceLine, SeriesType } from "./options"
|
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, ChartableIndex, SeriesType } from "./options"
|
||||||
* @import { Valued, SingleValueData, CandlestickData, ChartData, OHLCTuple, Series } from "../packages/lightweight-charts/wrapper"
|
* @import { Valued, SingleValueData, CandlestickData, ChartData, OHLCTuple, Series, ISeries, LineData, BaselineData, PartialLineStyleOptions, PartialBaselineStyleOptions, PartialCandlestickStyleOptions } from "../packages/lightweight-charts/wrapper"
|
||||||
* @import * as _ from "../packages/ufuzzy/v1.0.18/types"
|
* @import * as _ from "../packages/ufuzzy/v1.0.18/types"
|
||||||
* @import { createChart as CreateClassicChart, LineStyleOptions, DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, ISeriesApi, Time, LineData, LogicalRange, BaselineStyleOptions, SeriesOptionsCommon, BaselineData, CandlestickStyleOptions } from "../packages/lightweight-charts/v5.0.7-treeshaked/types"
|
* @import { Signal, Signals, Accessor } from "../packages/solid-signals/wrapper";
|
||||||
* @import { SignalOptions } from "../packages/solid-signals/v0.3.2-treeshaked/types/core/core"
|
|
||||||
* @import {Signal, Signals} from "../packages/solid-signals/types";
|
|
||||||
* @import { getOwner as GetOwner, onCleanup as OnCleanup, Owner } from "../packages/solid-signals/v0.3.2-treeshaked/types/core/owner"
|
|
||||||
* @import { createEffect as CreateEffect, Accessor, Setter, createMemo as CreateMemo } from "../packages/solid-signals/v0.3.2-treeshaked/types/signals";
|
|
||||||
* @import { DateIndex, DecadeIndex, DifficultyEpoch, Index, HalvingEpoch, Height, MonthIndex, P2PK33Index, P2PK65Index, P2PKHIndex, P2SHIndex, P2MSIndex, P2AIndex, P2TRIndex, P2WPKHIndex, P2WSHIndex, TxIndex, InputIndex, OutputIndex, VecId, WeekIndex, YearIndex, VecIdToIndexes, QuarterIndex, EmptyOutputIndex, OpReturnIndex, UnknownOutputIndex } from "./vecid-to-indexes"
|
* @import { DateIndex, DecadeIndex, DifficultyEpoch, Index, HalvingEpoch, Height, MonthIndex, P2PK33Index, P2PK65Index, P2PKHIndex, P2SHIndex, P2MSIndex, P2AIndex, P2TRIndex, P2WPKHIndex, P2WSHIndex, TxIndex, InputIndex, OutputIndex, VecId, WeekIndex, YearIndex, VecIdToIndexes, QuarterIndex, EmptyOutputIndex, OpReturnIndex, UnknownOutputIndex } from "./vecid-to-indexes"
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -66,14 +62,14 @@ const localhost = window.location.hostname === "localhost";
|
|||||||
function initPackages() {
|
function initPackages() {
|
||||||
const imports = {
|
const imports = {
|
||||||
async signals() {
|
async signals() {
|
||||||
return import("../packages/solid-signals/wrapper.js").then((d) =>
|
return import("../packages/solid-signals/wrapper.js").then(
|
||||||
d.default.then((d) => d),
|
(d) => d.default,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async lightweightCharts() {
|
async lightweightCharts() {
|
||||||
return window.document.fonts.ready.then(() =>
|
return window.document.fonts.ready.then(() =>
|
||||||
import("../packages/lightweight-charts/wrapper.js").then((d) =>
|
import("../packages/lightweight-charts/wrapper.js").then(
|
||||||
d.default.then((d) => d),
|
(d) => d.default,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -350,13 +346,6 @@ function createUtils() {
|
|||||||
head.appendChild(link);
|
head.appendChild(link);
|
||||||
return link;
|
return link;
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* @param {string} href
|
|
||||||
* @param {VoidFunction} callback
|
|
||||||
*/
|
|
||||||
importStyleAndThen(href, callback) {
|
|
||||||
this.importStyle(href).addEventListener("load", callback);
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* @template {Readonly<string[]>} T
|
* @template {Readonly<string[]>} T
|
||||||
* @param {Object} args
|
* @param {Object} args
|
||||||
@@ -366,7 +355,7 @@ function createUtils() {
|
|||||||
* @param {string} [args.keyPrefix]
|
* @param {string} [args.keyPrefix]
|
||||||
* @param {string} args.key
|
* @param {string} args.key
|
||||||
* @param {boolean} [args.sorted]
|
* @param {boolean} [args.sorted]
|
||||||
* @param {{createEffect: CreateEffect, createMemo: CreateMemo, createSignal: Signals["createSignal"]}} args.signals
|
* @param {Signals} args.signals
|
||||||
*/
|
*/
|
||||||
createHorizontalChoiceField({
|
createHorizontalChoiceField({
|
||||||
id,
|
id,
|
||||||
@@ -1268,15 +1257,6 @@ function createUtils() {
|
|||||||
return 0;
|
return 0;
|
||||||
return this.differenceBetween(date, new Date("2009-01-09"));
|
return this.differenceBetween(date, new Date("2009-01-09"));
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* @param {Time} time
|
|
||||||
*/
|
|
||||||
fromTime(time) {
|
|
||||||
return typeof time === "string"
|
|
||||||
? new Date(time)
|
|
||||||
: // @ts-ignore
|
|
||||||
new Date(time.year, time.month, time.day);
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* @param {Date} start
|
* @param {Date} start
|
||||||
*/
|
*/
|
||||||
@@ -1924,7 +1904,7 @@ function initWebSockets(signals, utils) {
|
|||||||
|
|
||||||
/** @type {CandlestickData} */
|
/** @type {CandlestickData} */
|
||||||
const candle = {
|
const candle = {
|
||||||
index: -1,
|
// index: -1,
|
||||||
time: new Date(interval_begin).valueOf() / 1000,
|
time: new Date(interval_begin).valueOf() / 1000,
|
||||||
open: Number(open),
|
open: Number(open),
|
||||||
high: Number(high),
|
high: Number(high),
|
||||||
@@ -2203,25 +2183,22 @@ function main() {
|
|||||||
|
|
||||||
if (firstTimeLoadingChart) {
|
if (firstTimeLoadingChart) {
|
||||||
const lightweightCharts = packages.lightweightCharts();
|
const lightweightCharts = packages.lightweightCharts();
|
||||||
const chartScript = import("./chart.js");
|
import("./chart.js").then(({ init: initChartsElement }) =>
|
||||||
utils.dom.importStyleAndThen("/styles/chart.css", () =>
|
lightweightCharts.then((lightweightCharts) =>
|
||||||
chartScript.then(({ init: initChartsElement }) =>
|
signals.runWithOwner(owner, () =>
|
||||||
lightweightCharts.then((lightweightCharts) =>
|
initChartsElement({
|
||||||
signals.runWithOwner(owner, () =>
|
colors,
|
||||||
initChartsElement({
|
elements,
|
||||||
colors,
|
lightweightCharts,
|
||||||
elements,
|
option: /** @type {Accessor<ChartOption>} */ (
|
||||||
lightweightCharts,
|
chartOption
|
||||||
option: /** @type {Accessor<ChartOption>} */ (
|
),
|
||||||
chartOption
|
signals,
|
||||||
),
|
utils,
|
||||||
signals,
|
webSockets,
|
||||||
utils,
|
vecsResources,
|
||||||
webSockets,
|
vecIdToIndexes,
|
||||||
vecsResources,
|
}),
|
||||||
vecIdToIndexes,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -2234,20 +2211,17 @@ function main() {
|
|||||||
element = elements.table;
|
element = elements.table;
|
||||||
|
|
||||||
if (firstTimeLoadingTable) {
|
if (firstTimeLoadingTable) {
|
||||||
const tableScript = import("./table.js");
|
import("./table.js").then(({ init }) =>
|
||||||
utils.dom.importStyleAndThen("/styles/table.css", () =>
|
signals.runWithOwner(owner, () =>
|
||||||
tableScript.then(({ init }) =>
|
init({
|
||||||
signals.runWithOwner(owner, () =>
|
colors,
|
||||||
init({
|
elements,
|
||||||
colors,
|
signals,
|
||||||
elements,
|
utils,
|
||||||
signals,
|
vecsResources,
|
||||||
utils,
|
option,
|
||||||
vecsResources,
|
vecIdToIndexes,
|
||||||
option,
|
}),
|
||||||
vecIdToIndexes,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2262,24 +2236,19 @@ function main() {
|
|||||||
|
|
||||||
if (firstTimeLoadingSimulation) {
|
if (firstTimeLoadingSimulation) {
|
||||||
const lightweightCharts = packages.lightweightCharts();
|
const lightweightCharts = packages.lightweightCharts();
|
||||||
const simulationScript = import("./simulation.js");
|
import("./simulation.js").then(({ init }) =>
|
||||||
utils.dom.importStyleAndThen(
|
lightweightCharts.then((lightweightCharts) =>
|
||||||
"/styles/simulation.css",
|
signals.runWithOwner(owner, () =>
|
||||||
() =>
|
init({
|
||||||
simulationScript.then(({ init }) =>
|
colors,
|
||||||
lightweightCharts.then((lightweightCharts) =>
|
elements,
|
||||||
signals.runWithOwner(owner, () =>
|
lightweightCharts,
|
||||||
init({
|
signals,
|
||||||
colors,
|
utils,
|
||||||
elements,
|
vecsResources,
|
||||||
lightweightCharts,
|
}),
|
||||||
signals,
|
|
||||||
utils,
|
|
||||||
vecsResources,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
firstTimeLoadingSimulation = false;
|
firstTimeLoadingSimulation = false;
|
||||||
|
|||||||
@@ -16,32 +16,26 @@
|
|||||||
* @property {string} title
|
* @property {string} title
|
||||||
* @property {boolean} [defaultActive]
|
* @property {boolean} [defaultActive]
|
||||||
*
|
*
|
||||||
* @typedef {Object} CreatePriceLine
|
|
||||||
* @property {number} value
|
|
||||||
*
|
|
||||||
* @typedef {Object} CreatePriceLineOptions
|
|
||||||
* @property {CreatePriceLine} createPriceLine
|
|
||||||
*
|
|
||||||
* @typedef {Object} BaselineSeriesBlueprintSpecific
|
* @typedef {Object} BaselineSeriesBlueprintSpecific
|
||||||
* @property {"Baseline"} type
|
* @property {"Baseline"} type
|
||||||
* @property {Color} [color]
|
* @property {Color} [color]
|
||||||
* @property {[Color, Color]} [colors]
|
* @property {[Color, Color]} [colors]
|
||||||
* @property {DeepPartial<BaselineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [options]
|
* @property {PartialBaselineStyleOptions} [options]
|
||||||
* @property {Accessor<BaselineData<number>[]>} [data]
|
* @property {Accessor<BaselineData[]>} [data]
|
||||||
* @typedef {BaseSeriesBlueprint & BaselineSeriesBlueprintSpecific} BaselineSeriesBlueprint
|
* @typedef {BaseSeriesBlueprint & BaselineSeriesBlueprintSpecific} BaselineSeriesBlueprint
|
||||||
*
|
*
|
||||||
* @typedef {Object} CandlestickSeriesBlueprintSpecific
|
* @typedef {Object} CandlestickSeriesBlueprintSpecific
|
||||||
* @property {"Candlestick"} type
|
* @property {"Candlestick"} type
|
||||||
* @property {Color} [color]
|
* @property {Color} [color]
|
||||||
* @property {DeepPartial<CandlestickStyleOptions & SeriesOptionsCommon>} [options]
|
* @property {PartialCandlestickStyleOptions} [options]
|
||||||
* @property {Accessor<CandlestickData[]>} [data]
|
* @property {Accessor<CandlestickData[]>} [data]
|
||||||
* @typedef {BaseSeriesBlueprint & CandlestickSeriesBlueprintSpecific} CandlestickSeriesBlueprint
|
* @typedef {BaseSeriesBlueprint & CandlestickSeriesBlueprintSpecific} CandlestickSeriesBlueprint
|
||||||
*
|
*
|
||||||
* @typedef {Object} LineSeriesBlueprintSpecific
|
* @typedef {Object} LineSeriesBlueprintSpecific
|
||||||
* @property {"Line"} [type]
|
* @property {"Line"} [type]
|
||||||
* @property {Color} [color]
|
* @property {Color} [color]
|
||||||
* @property {DeepPartial<LineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [options]
|
* @property {PartialLineStyleOptions} [options]
|
||||||
* @property {Accessor<LineData<number>[]>} [data]
|
* @property {Accessor<LineData[]>} [data]
|
||||||
* @typedef {BaseSeriesBlueprint & LineSeriesBlueprintSpecific} LineSeriesBlueprint
|
* @typedef {BaseSeriesBlueprint & LineSeriesBlueprintSpecific} LineSeriesBlueprint
|
||||||
*
|
*
|
||||||
* @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint} AnySeriesBlueprint
|
* @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint} AnySeriesBlueprint
|
||||||
@@ -823,7 +817,7 @@ function createPartialOptions(colors) {
|
|||||||
* @param {string} args.name
|
* @param {string} args.name
|
||||||
* @param {Color} [args.color]
|
* @param {Color} [args.color]
|
||||||
* @param {boolean} [args.defaultActive]
|
* @param {boolean} [args.defaultActive]
|
||||||
* @param {DeepPartial<LineStyleOptions & SeriesOptionsCommon>} [args.options]
|
* @param {PartialLineStyleOptions} [args.options]
|
||||||
*/
|
*/
|
||||||
function createBaseSeries({ key, name, color, defaultActive, options }) {
|
function createBaseSeries({ key, name, color, defaultActive, options }) {
|
||||||
return /** @satisfies {AnyFetchedSeriesBlueprint} */ ({
|
return /** @satisfies {AnyFetchedSeriesBlueprint} */ ({
|
||||||
@@ -2847,7 +2841,7 @@ function createPartialOptions(colors) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Status",
|
name: "Status",
|
||||||
url: () => "https://status.kibo.money/",
|
url: () => "https://status.bitcoinresearchkit.org/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Crates",
|
name: "Crates",
|
||||||
@@ -2935,9 +2929,8 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
|
|||||||
* @param {Signal<string | null>} args.qrcode
|
* @param {Signal<string | null>} args.qrcode
|
||||||
* @param {string} [args.name]
|
* @param {string} [args.name]
|
||||||
* @param {string} [args.id]
|
* @param {string} [args.id]
|
||||||
* @param {Owner | null} [args.owner]
|
|
||||||
*/
|
*/
|
||||||
function createOptionElement({ option, frame, name, id, owner, qrcode }) {
|
function createOptionElement({ option, frame, name, id, qrcode }) {
|
||||||
if (option.kind === "url") {
|
if (option.kind === "url") {
|
||||||
const href = option.url();
|
const href = option.url();
|
||||||
|
|
||||||
@@ -2987,13 +2980,7 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (owner !== undefined) {
|
createCheckEffect();
|
||||||
signals.runWithOwner(owner, () => {
|
|
||||||
createCheckEffect();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
createCheckEffect();
|
|
||||||
}
|
|
||||||
|
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function init({
|
|||||||
* @param {number} args.min
|
* @param {number} args.min
|
||||||
* @param {number} args.step
|
* @param {number} args.step
|
||||||
* @param {number} [args.max]
|
* @param {number} [args.max]
|
||||||
* @param {{createEffect: typeof CreateEffect}} args.signals
|
* @param {Signals} args.signals
|
||||||
*/
|
*/
|
||||||
createInputNumberElement({
|
createInputNumberElement({
|
||||||
id,
|
id,
|
||||||
@@ -76,7 +76,7 @@ export function init({
|
|||||||
input.value = value;
|
input.value = value;
|
||||||
stateValue = value;
|
stateValue = value;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
input.addEventListener("input", () => {
|
input.addEventListener("input", () => {
|
||||||
@@ -95,7 +95,7 @@ export function init({
|
|||||||
* @param {string} args.id
|
* @param {string} args.id
|
||||||
* @param {string} args.title
|
* @param {string} args.title
|
||||||
* @param {Signal<number | null>} args.signal
|
* @param {Signal<number | null>} args.signal
|
||||||
* @param {{createEffect: typeof CreateEffect}} args.signals
|
* @param {Signals} args.signals
|
||||||
*/
|
*/
|
||||||
createInputDollar({ id, title, signal, signals }) {
|
createInputDollar({ id, title, signal, signals }) {
|
||||||
return this.createInputNumberElement({
|
return this.createInputNumberElement({
|
||||||
@@ -113,7 +113,7 @@ export function init({
|
|||||||
* @param {string} args.id
|
* @param {string} args.id
|
||||||
* @param {string} args.title
|
* @param {string} args.title
|
||||||
* @param {Signal<Date | null>} args.signal
|
* @param {Signal<Date | null>} args.signal
|
||||||
* @param {{createEffect: typeof CreateEffect}} args.signals
|
* @param {Signals} args.signals
|
||||||
*/
|
*/
|
||||||
createInputDate({ id, title, signal, signals }) {
|
createInputDate({ id, title, signal, signals }) {
|
||||||
const input = window.document.createElement("input");
|
const input = window.document.createElement("input");
|
||||||
@@ -139,7 +139,7 @@ export function init({
|
|||||||
input.value = value;
|
input.value = value;
|
||||||
stateValue = value;
|
stateValue = value;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
input.addEventListener("change", () => {
|
input.addEventListener("change", () => {
|
||||||
@@ -328,7 +328,7 @@ export function init({
|
|||||||
keyPrefix,
|
keyPrefix,
|
||||||
key: "top-up-freq",
|
key: "top-up-freq",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -356,7 +356,7 @@ export function init({
|
|||||||
keyPrefix,
|
keyPrefix,
|
||||||
key: "swap-freq",
|
key: "swap-freq",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -369,7 +369,7 @@ export function init({
|
|||||||
keyPrefix,
|
keyPrefix,
|
||||||
key: "interval-start",
|
key: "interval-start",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
end: signals.createSignal(/** @type {Date | null} */ (new Date()), {
|
end: signals.createSignal(/** @type {Date | null} */ (new Date()), {
|
||||||
save: {
|
save: {
|
||||||
@@ -391,7 +391,7 @@ export function init({
|
|||||||
};
|
};
|
||||||
|
|
||||||
parametersElement.append(
|
parametersElement.append(
|
||||||
utils.dom.createHeader("Save in Bitcoin").headerElement
|
utils.dom.createHeader("Save in Bitcoin").headerElement,
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -431,9 +431,9 @@ export function init({
|
|||||||
title: "Initial Dollar Amount",
|
title: "Initial Dollar Amount",
|
||||||
signal: settings.dollars.initial.amount,
|
signal: settings.dollars.initial.amount,
|
||||||
signals,
|
signals,
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
parametersElement.append(
|
parametersElement.append(
|
||||||
@@ -451,9 +451,9 @@ export function init({
|
|||||||
list: frequencies.list,
|
list: frequencies.list,
|
||||||
signal: settings.dollars.topUp.frenquency,
|
signal: settings.dollars.topUp.frenquency,
|
||||||
deep: true,
|
deep: true,
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
parametersElement.append(
|
parametersElement.append(
|
||||||
@@ -471,9 +471,9 @@ export function init({
|
|||||||
title: "Top Up Dollar Amount",
|
title: "Top Up Dollar Amount",
|
||||||
signal: settings.dollars.topUp.amount,
|
signal: settings.dollars.topUp.amount,
|
||||||
signals,
|
signals,
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
parametersElement.append(
|
parametersElement.append(
|
||||||
@@ -491,9 +491,9 @@ export function init({
|
|||||||
title: "Initial Swap Amount",
|
title: "Initial Swap Amount",
|
||||||
signal: settings.bitcoin.investment.initial,
|
signal: settings.bitcoin.investment.initial,
|
||||||
signals,
|
signals,
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
parametersElement.append(
|
parametersElement.append(
|
||||||
@@ -510,9 +510,9 @@ export function init({
|
|||||||
list: frequencies.list,
|
list: frequencies.list,
|
||||||
signal: settings.bitcoin.investment.frequency,
|
signal: settings.bitcoin.investment.frequency,
|
||||||
deep: true,
|
deep: true,
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
parametersElement.append(
|
parametersElement.append(
|
||||||
@@ -530,9 +530,9 @@ export function init({
|
|||||||
title: "Bitcoin Recurrent Investment",
|
title: "Bitcoin Recurrent Investment",
|
||||||
signal: settings.bitcoin.investment.recurrent,
|
signal: settings.bitcoin.investment.recurrent,
|
||||||
signals,
|
signals,
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
parametersElement.append(
|
parametersElement.append(
|
||||||
@@ -549,9 +549,9 @@ export function init({
|
|||||||
title: "First Simulation Date",
|
title: "First Simulation Date",
|
||||||
signal: settings.interval.start,
|
signal: settings.interval.start,
|
||||||
signals,
|
signals,
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
parametersElement.append(
|
parametersElement.append(
|
||||||
@@ -568,9 +568,9 @@ export function init({
|
|||||||
title: "Last Simulation Day",
|
title: "Last Simulation Day",
|
||||||
signal: settings.interval.end,
|
signal: settings.interval.end,
|
||||||
signals,
|
signals,
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
parametersElement.append(
|
parametersElement.append(
|
||||||
@@ -591,9 +591,9 @@ export function init({
|
|||||||
step: 0.01,
|
step: 0.01,
|
||||||
signals,
|
signals,
|
||||||
placeholder: "Fees",
|
placeholder: "Fees",
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const p1 = window.document.createElement("p");
|
const p1 = window.document.createElement("p");
|
||||||
@@ -608,101 +608,79 @@ export function init({
|
|||||||
const owner = signals.getOwner();
|
const owner = signals.getOwner();
|
||||||
|
|
||||||
const totalInvestedAmountData = signals.createSignal(
|
const totalInvestedAmountData = signals.createSignal(
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
/** @type {LineData[]} */ ([]),
|
||||||
{
|
{
|
||||||
equals: false,
|
equals: false,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const bitcoinValueData = signals.createSignal(
|
const bitcoinValueData = signals.createSignal(
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
/** @type {LineData[]} */ ([]),
|
||||||
{
|
{
|
||||||
equals: false,
|
equals: false,
|
||||||
}
|
},
|
||||||
);
|
|
||||||
const bitcoinData = signals.createSignal(
|
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
|
||||||
{
|
|
||||||
equals: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const resultData = signals.createSignal(
|
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
|
||||||
{
|
|
||||||
equals: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const dollarsLeftData = signals.createSignal(
|
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
|
||||||
{
|
|
||||||
equals: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const totalValueData = signals.createSignal(
|
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
|
||||||
{
|
|
||||||
equals: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const investmentData = signals.createSignal(
|
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
|
||||||
{
|
|
||||||
equals: false,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
const bitcoinData = signals.createSignal(/** @type {LineData[]} */ ([]), {
|
||||||
|
equals: false,
|
||||||
|
});
|
||||||
|
const resultData = signals.createSignal(/** @type {LineData[]} */ ([]), {
|
||||||
|
equals: false,
|
||||||
|
});
|
||||||
|
const dollarsLeftData = signals.createSignal(/** @type {LineData[]} */ ([]), {
|
||||||
|
equals: false,
|
||||||
|
});
|
||||||
|
const totalValueData = signals.createSignal(/** @type {LineData[]} */ ([]), {
|
||||||
|
equals: false,
|
||||||
|
});
|
||||||
|
const investmentData = signals.createSignal(/** @type {LineData[]} */ ([]), {
|
||||||
|
equals: false,
|
||||||
|
});
|
||||||
const bitcoinAddedData = signals.createSignal(
|
const bitcoinAddedData = signals.createSignal(
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
/** @type {LineData[]} */ ([]),
|
||||||
{
|
{
|
||||||
equals: false,
|
equals: false,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const averagePricePaidData = signals.createSignal(
|
const averagePricePaidData = signals.createSignal(
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
/** @type {LineData[]} */ ([]),
|
||||||
{
|
{
|
||||||
equals: false,
|
equals: false,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const bitcoinPriceData = signals.createSignal(
|
const bitcoinPriceData = signals.createSignal(
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
/** @type {LineData[]} */ ([]),
|
||||||
{
|
{
|
||||||
equals: false,
|
equals: false,
|
||||||
}
|
},
|
||||||
);
|
|
||||||
const buyCountData = signals.createSignal(
|
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
|
||||||
{
|
|
||||||
equals: false,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
const buyCountData = signals.createSignal(/** @type {LineData[]} */ ([]), {
|
||||||
|
equals: false,
|
||||||
|
});
|
||||||
const totalFeesPaidData = signals.createSignal(
|
const totalFeesPaidData = signals.createSignal(
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
/** @type {LineData[]} */ ([]),
|
||||||
{
|
{
|
||||||
equals: false,
|
equals: false,
|
||||||
}
|
},
|
||||||
);
|
|
||||||
const daysCountData = signals.createSignal(
|
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
|
||||||
{
|
|
||||||
equals: false,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
const daysCountData = signals.createSignal(/** @type {LineData[]} */ ([]), {
|
||||||
|
equals: false,
|
||||||
|
});
|
||||||
const profitableDaysRatioData = signals.createSignal(
|
const profitableDaysRatioData = signals.createSignal(
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
/** @type {LineData[]} */ ([]),
|
||||||
{
|
{
|
||||||
equals: false,
|
equals: false,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const unprofitableDaysRatioData = signals.createSignal(
|
const unprofitableDaysRatioData = signals.createSignal(
|
||||||
/** @type {LineData<number>[]} */ ([]),
|
/** @type {LineData[]} */ ([]),
|
||||||
{
|
{
|
||||||
equals: false,
|
equals: false,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const index = () => /** @type {DateIndex} */ (0);
|
const index = () => /** @type {DateIndex} */ (0);
|
||||||
|
|
||||||
lightweightCharts.createChartElement({
|
lightweightCharts.createChartElement({
|
||||||
index,
|
index,
|
||||||
owner,
|
|
||||||
parent: resultsElement,
|
parent: resultsElement,
|
||||||
signals,
|
signals,
|
||||||
colors,
|
colors,
|
||||||
@@ -748,7 +726,6 @@ export function init({
|
|||||||
|
|
||||||
lightweightCharts.createChartElement({
|
lightweightCharts.createChartElement({
|
||||||
index,
|
index,
|
||||||
owner,
|
|
||||||
parent: resultsElement,
|
parent: resultsElement,
|
||||||
signals,
|
signals,
|
||||||
colors,
|
colors,
|
||||||
@@ -774,7 +751,6 @@ export function init({
|
|||||||
|
|
||||||
lightweightCharts.createChartElement({
|
lightweightCharts.createChartElement({
|
||||||
index,
|
index,
|
||||||
owner,
|
|
||||||
parent: resultsElement,
|
parent: resultsElement,
|
||||||
signals,
|
signals,
|
||||||
colors,
|
colors,
|
||||||
@@ -806,7 +782,6 @@ export function init({
|
|||||||
|
|
||||||
lightweightCharts.createChartElement({
|
lightweightCharts.createChartElement({
|
||||||
index,
|
index,
|
||||||
owner,
|
|
||||||
parent: resultsElement,
|
parent: resultsElement,
|
||||||
signals,
|
signals,
|
||||||
colors,
|
colors,
|
||||||
@@ -839,7 +814,6 @@ export function init({
|
|||||||
vecsResources,
|
vecsResources,
|
||||||
utils,
|
utils,
|
||||||
elements,
|
elements,
|
||||||
owner,
|
|
||||||
config: [
|
config: [
|
||||||
{
|
{
|
||||||
unit: "percentage",
|
unit: "percentage",
|
||||||
@@ -1096,7 +1070,7 @@ export function init({
|
|||||||
p1.innerHTML = `After exchanging ${serInvestedAmount} in the span of ${serDaysCount} days, you would have accumulated ${serSats} Satoshis (${serBitcoin} Bitcoin) worth today ${serBitcoinValue} at an average price of ${serAveragePricePaid} per Bitcoin with a return of investment of ${serRoi}, have ${serDollars} left and paid a total of ${serTotalFeesPaid} in fees.`;
|
p1.innerHTML = `After exchanging ${serInvestedAmount} in the span of ${serDaysCount} days, you would have accumulated ${serSats} Satoshis (${serBitcoin} Bitcoin) worth today ${serBitcoinValue} at an average price of ${serAveragePricePaid} per Bitcoin with a return of investment of ${serRoi}, have ${serDollars} left and paid a total of ${serTotalFeesPaid} in fees.`;
|
||||||
|
|
||||||
const dayDiff = Math.floor(
|
const dayDiff = Math.floor(
|
||||||
utils.date.differenceBetween(new Date(), lastInvestDay)
|
utils.date.differenceBetween(new Date(), lastInvestDay),
|
||||||
);
|
);
|
||||||
const serDailyInvestment = c("emerald", fd(dailyInvestment));
|
const serDailyInvestment = c("emerald", fd(dailyInvestment));
|
||||||
const setLastSatsAdded = c("orange", f(lastSatsAdded));
|
const setLastSatsAdded = c("orange", f(lastSatsAdded));
|
||||||
@@ -1104,13 +1078,13 @@ export function init({
|
|||||||
"blue",
|
"blue",
|
||||||
dayDiff
|
dayDiff
|
||||||
? `${f(dayDiff)} ${dayDiff > 1 ? "days" : "day"} ago`
|
? `${f(dayDiff)} ${dayDiff > 1 ? "days" : "day"} ago`
|
||||||
: "today"
|
: "today",
|
||||||
)} and exchanged ${serDailyInvestment} for approximately ${setLastSatsAdded} Satoshis`;
|
)} and exchanged ${serDailyInvestment} for approximately ${setLastSatsAdded} Satoshis`;
|
||||||
|
|
||||||
const serProfitableDaysRatio = c("green", fp(profitableDaysRatio));
|
const serProfitableDaysRatio = c("green", fp(profitableDaysRatio));
|
||||||
const serUnprofitableDaysRatio = c(
|
const serUnprofitableDaysRatio = c(
|
||||||
"red",
|
"red",
|
||||||
fp(unprofitableDaysRatio)
|
fp(unprofitableDaysRatio),
|
||||||
);
|
);
|
||||||
|
|
||||||
p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`;
|
p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`;
|
||||||
@@ -1120,7 +1094,7 @@ export function init({
|
|||||||
(lowestAnnual4YReturn) => {
|
(lowestAnnual4YReturn) => {
|
||||||
const serLowestAnnual4YReturn = c(
|
const serLowestAnnual4YReturn = c(
|
||||||
"cyan",
|
"cyan",
|
||||||
`${fp(lowestAnnual4YReturn)}`
|
`${fp(lowestAnnual4YReturn)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const lowestAnnual4YReturnPercentage = 1 + lowestAnnual4YReturn;
|
const lowestAnnual4YReturnPercentage = 1 + lowestAnnual4YReturn;
|
||||||
@@ -1136,22 +1110,22 @@ export function init({
|
|||||||
const bitcoinValueAfter4y = bitcoinValueReturn(4);
|
const bitcoinValueAfter4y = bitcoinValueReturn(4);
|
||||||
const serBitcoinValueAfter4y = c(
|
const serBitcoinValueAfter4y = c(
|
||||||
"purple",
|
"purple",
|
||||||
fd(bitcoinValueAfter4y)
|
fd(bitcoinValueAfter4y),
|
||||||
);
|
);
|
||||||
const bitcoinValueAfter10y = bitcoinValueReturn(10);
|
const bitcoinValueAfter10y = bitcoinValueReturn(10);
|
||||||
const serBitcoinValueAfter10y = c(
|
const serBitcoinValueAfter10y = c(
|
||||||
"fuchsia",
|
"fuchsia",
|
||||||
fd(bitcoinValueAfter10y)
|
fd(bitcoinValueAfter10y),
|
||||||
);
|
);
|
||||||
const bitcoinValueAfter21y = bitcoinValueReturn(21);
|
const bitcoinValueAfter21y = bitcoinValueReturn(21);
|
||||||
const serBitcoinValueAfter21y = c(
|
const serBitcoinValueAfter21y = c(
|
||||||
"pink",
|
"pink",
|
||||||
fd(bitcoinValueAfter21y)
|
fd(bitcoinValueAfter21y),
|
||||||
);
|
);
|
||||||
|
|
||||||
/** @param {number} v */
|
/** @param {number} v */
|
||||||
p4.innerHTML = `The lowest annual return after 4 years has historically been ${serLowestAnnual4YReturn}.<br/>Using it as the baseline, your Bitcoin would be worth ${serBitcoinValueAfter4y} after 4 years, ${serBitcoinValueAfter10y} after 10 years and ${serBitcoinValueAfter21y} after 21 years.`;
|
p4.innerHTML = `The lowest annual return after 4 years has historically been ${serLowestAnnual4YReturn}.<br/>Using it as the baseline, your Bitcoin would be worth ${serBitcoinValueAfter4y} after 4 years, ${serBitcoinValueAfter10y} after 10 years and ${serBitcoinValueAfter21y} after 21 years.`;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
totalInvestedAmountData.set((a) => a);
|
totalInvestedAmountData.set((a) => a);
|
||||||
@@ -1169,7 +1143,7 @@ export function init({
|
|||||||
daysCountData.set((a) => a);
|
daysCountData.set((a) => a);
|
||||||
profitableDaysRatioData.set((a) => a);
|
profitableDaysRatioData.set((a) => a);
|
||||||
unprofitableDaysRatioData.set((a) => a);
|
unprofitableDaysRatioData.set((a) => a);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// File auto-generated, any modifications will be overwritten
|
// File auto-generated, any modifications will be overwritten
|
||||||
//
|
//
|
||||||
|
|
||||||
export const VERSION = "v0.0.54";
|
export const VERSION = "v0.0.58";
|
||||||
|
|
||||||
/** @typedef {0} DateIndex */
|
/** @typedef {0} DateIndex */
|
||||||
/** @typedef {1} DecadeIndex */
|
/** @typedef {1} DecadeIndex */
|
||||||
|
|||||||
@@ -1,24 +1,36 @@
|
|||||||
const CACHE_NAME = "cache";
|
// DO NOT CHANGE, Exact format is expected in `brk_bundler`
|
||||||
|
const CACHE_VERSION = "__VERSION__";
|
||||||
|
|
||||||
|
const SHELL_FILES = ["/", "/index.html"];
|
||||||
|
|
||||||
/** @type {ServiceWorkerGlobalScope} */
|
/** @type {ServiceWorkerGlobalScope} */
|
||||||
const sw = /** @type {any} */ (self);
|
const sw = /** @type {any} */ (self);
|
||||||
|
|
||||||
sw.addEventListener("install", (event) => {
|
sw.addEventListener("install", (event) => {
|
||||||
console.log("sw: install");
|
console.log("sw: install");
|
||||||
event.waitUntil(sw.skipWaiting());
|
event.waitUntil(
|
||||||
|
caches
|
||||||
|
.open(CACHE_VERSION)
|
||||||
|
.then((c) => c.addAll(SHELL_FILES))
|
||||||
|
.then(() => sw.skipWaiting()),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
sw.addEventListener("activate", (event) => {
|
sw.addEventListener("activate", (event) => {
|
||||||
console.log("sw: active");
|
console.log("sw: active");
|
||||||
sw.clients.claim();
|
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches
|
Promise.all([
|
||||||
.keys()
|
sw.clients.claim(),
|
||||||
.then((keys) =>
|
caches
|
||||||
Promise.all(
|
.keys()
|
||||||
keys.filter((key) => key !== "api").map((key) => caches.delete(key)),
|
.then((keys) =>
|
||||||
|
Promise.all(
|
||||||
|
keys
|
||||||
|
.filter((key) => key !== "api" && key !== CACHE_VERSION)
|
||||||
|
.map((key) => caches.delete(key)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -42,7 +54,7 @@ sw.addEventListener("fetch", (event) => {
|
|||||||
return; // let the browser handle it
|
return; // let the browser handle it
|
||||||
}
|
}
|
||||||
|
|
||||||
const cache = caches.open(CACHE_NAME);
|
const cache = caches.open(CACHE_VERSION);
|
||||||
|
|
||||||
// 2) NAVIGATION: network‐first on your shell
|
// 2) NAVIGATION: network‐first on your shell
|
||||||
if (req.mode === "navigate") {
|
if (req.mode === "navigate") {
|
||||||
@@ -76,14 +88,13 @@ sw.addEventListener("fetch", (event) => {
|
|||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
})
|
})
|
||||||
.catch(async () => {
|
.catch(async () =>
|
||||||
return caches
|
caches
|
||||||
.match(req)
|
.match(req)
|
||||||
.then((cached) => {
|
.then((cached) => {
|
||||||
return cached || indexHTMLOrOffline();
|
return cached || indexHTMLOrOffline();
|
||||||
})
|
})
|
||||||
.catch(indexHTMLOrOffline);
|
.catch(indexHTMLOrOffline),
|
||||||
})
|
),
|
||||||
.catch(indexHTMLOrOffline),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
#charts {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 0;
|
|
||||||
padding: var(--main-padding);
|
|
||||||
|
|
||||||
header {
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow-x: auto;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
margin-bottom: -2rem;
|
|
||||||
padding-left: var(--main-padding);
|
|
||||||
margin-left: var(--negative-main-padding);
|
|
||||||
padding-right: var(--main-padding);
|
|
||||||
margin-right: var(--negative-main-padding);
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .chart > legend,
|
|
||||||
> fieldset {
|
|
||||||
z-index: 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightweight-chart {
|
|
||||||
z-index: 40;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
#simulation {
|
|
||||||
min-height: 0;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2rem;
|
|
||||||
padding: var(--main-padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
> div:first-child {
|
|
||||||
border-bottom: 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding-bottom: var(--bottom-area);
|
|
||||||
}
|
|
||||||
|
|
||||||
> div:first-child {
|
|
||||||
max-width: var(--default-main-width);
|
|
||||||
border-right: 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div:last-child {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.5rem;
|
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-wrap: pretty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
> span {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
small {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
line-height: var(--line-height-sm);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart {
|
|
||||||
flex: none;
|
|
||||||
height: 400px;
|
|
||||||
|
|
||||||
.lightweight-chart {
|
|
||||||
margin-left: calc(var(--negative-main-padding) * 0.75);
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
margin-left: -0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
#table {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2rem;
|
|
||||||
padding: var(--main-padding);
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
line--line-height: var(--line-height-xs);
|
|
||||||
font-weight: 450;
|
|
||||||
margin-left: var(--negative-main-padding);
|
|
||||||
margin-right: var(--negative-main-padding);
|
|
||||||
|
|
||||||
table {
|
|
||||||
z-index: 10;
|
|
||||||
border-top-width: 1px;
|
|
||||||
border-style: dashed !important;
|
|
||||||
/* width: 100%; */
|
|
||||||
line-height: var(--line-height-sm);
|
|
||||||
text-transform: uppercase;
|
|
||||||
table-layout: auto;
|
|
||||||
border-collapse: separate;
|
|
||||||
border-spacing: 0;
|
|
||||||
/* border: 3px solid purple; */
|
|
||||||
/* min-height: 100%; */
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
/* border-top: 1px; */
|
|
||||||
border-right: 1px;
|
|
||||||
border-bottom: 1px;
|
|
||||||
border-color: var(--off-color);
|
|
||||||
border-style: dashed !important;
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
text-transform: lowercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
margin: -0.2rem 0;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
th:first-child {
|
|
||||||
padding-left: var(--main-padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
th[scope="col"] {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
background-color: var(--background-color);
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding-top: 0.275rem;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.25rem;
|
|
||||||
text-transform: lowercase;
|
|
||||||
color: var(--off-color);
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
> span {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
> button {
|
|
||||||
padding: 0 0.25rem;
|
|
||||||
margin: 0 -0.25rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
button:nth-of-type(1) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
button:nth-of-type(2) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
margin-right: -4px;
|
|
||||||
/* width: 100%; */
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
> button {
|
|
||||||
padding: 1rem;
|
|
||||||
min-width: 10rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
border-top-width: 1px;
|
|
||||||
width: 100%;
|
|
||||||
border-bottom-width: 1px;
|
|
||||||
border-style: dashed !important;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
text-align: left;
|
|
||||||
position: sticky;
|
|
||||||
top: 2rem;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user